Patchwork [1,of,7,V4] test-revset: show how inconsistent the ordering of compound expressions is

login
register
mail settings
Submitter Yuya Nishihara
Date June 23, 2016, 3:59 p.m.
Message ID <8ae1910bc804846ea065.1466697593@mimosa>
Download mbox | patch
Permalink /patch/15579/
State Accepted
Headers show

Comments

Yuya Nishihara - June 23, 2016, 3:59 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1466600545 -32400
#      Wed Jun 22 22:02:25 2016 +0900
# Node ID 8ae1910bc804846ea065854aea7896e3c46cde05
# Parent  aa1d56003872cba207d908706da059141dd901a5
test-revset: show how inconsistent the ordering of compound expressions is

This adds mostly broken tests that will be fixed by subsequent patches. We
generally don't do that, but this patch series would be hard to review
without a set of broken tests.

Note that some tests pass thanks to the reordering problem in optimize().
For instance, '2:0 & _intlist(0 1 2)' doesn't fail because it is rewritten
as '_intlist(0 1 2) & 2:0'.
via Mercurial-devel - June 23, 2016, 4:07 p.m.
I've queued this to start with. Thanks!

On Thu, Jun 23, 2016 at 8:59 AM, Yuya Nishihara <yuya@tcha.org> wrote:
> # HG changeset patch
> # User Yuya Nishihara <yuya@tcha.org>
> # Date 1466600545 -32400
> #      Wed Jun 22 22:02:25 2016 +0900
> # Node ID 8ae1910bc804846ea065854aea7896e3c46cde05
> # Parent  aa1d56003872cba207d908706da059141dd901a5
> test-revset: show how inconsistent the ordering of compound expressions is
>
> This adds mostly broken tests that will be fixed by subsequent patches. We
> generally don't do that, but this patch series would be hard to review
> without a set of broken tests.
>
> Note that some tests pass thanks to the reordering problem in optimize().
> For instance, '2:0 & _intlist(0 1 2)' doesn't fail because it is rewritten
> as '_intlist(0 1 2) & 2:0'.
>
> diff --git a/tests/test-revset.t b/tests/test-revset.t
> --- a/tests/test-revset.t
> +++ b/tests/test-revset.t
> @@ -31,6 +31,46 @@
>    >   hg log --template '{rev}\n' -r "$1"
>    > }
>
> +extension to build '_intlist()' and '_hexlist()', which is necessary because
> +these predicates use '\0' as a separator:
> +
> +  $ cat <<EOF > debugrevlistspec.py
> +  > from __future__ import absolute_import
> +  > from mercurial import (
> +  >     cmdutil,
> +  >     node as nodemod,
> +  >     revset,
> +  > )
> +  > cmdtable = {}
> +  > command = cmdutil.command(cmdtable)
> +  > @command('debugrevlistspec',
> +  >     [('', 'optimize', None, 'print parsed tree after optimizing'),
> +  >      ('', 'bin', None, 'unhexlify arguments')])
> +  > def debugrevlistspec(ui, repo, fmt, *args, **opts):
> +  >     if opts['bin']:
> +  >         args = map(nodemod.bin, args)
> +  >     expr = revset.formatspec(fmt, list(args))
> +  >     if ui.verbose:
> +  >         tree = revset.parse(expr, lookup=repo.__contains__)
> +  >         ui.note(revset.prettyformat(tree), "\n")
> +  >         if opts["optimize"]:
> +  >             opttree = revset.optimize(tree)
> +  >             ui.note("* optimized:\n", revset.prettyformat(opttree), "\n")
> +  >     func = revset.match(ui, expr, repo)
> +  >     revs = func(repo)
> +  >     if ui.verbose:
> +  >         ui.note("* set:\n", revset.prettyformatset(revs), "\n")
> +  >     for c in revs:
> +  >         ui.write("%s\n" % c)
> +  > EOF
> +  $ cat <<EOF >> $HGRCPATH
> +  > [extensions]
> +  > debugrevlistspec = $TESTTMP/debugrevlistspec.py
> +  > EOF
> +  $ trylist() {
> +  >   hg debugrevlistspec --debug "$@"
> +  > }
> +
>    $ hg init repo
>    $ cd repo
>
> @@ -901,6 +941,10 @@ Test working-directory revision
>  Test order of revisions in compound expression
>  ----------------------------------------------
>
> +The general rule is that only the outermost (= leftmost) predicate can
> +enforce its ordering requirement. The other predicates should take the
> +ordering defined by it.
> +
>   'A & B' should follow the order of 'A':
>
>    $ log '2:0 & 0::2'
> @@ -908,6 +952,432 @@ Test order of revisions in compound expr
>    1
>    0
>
> + 'head()' combines sets in wrong order:
> +
> +  $ log '2:0 & head()'
> +  0
> +  1
> +  2
> + BROKEN: should be '2 1 0'
> +
> + 'a + b', which is optimized to '_list(a b)', should take the ordering of
> + the left expression:
> +
> +  $ try --optimize '2:0 & (0 + 1 + 2)'
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (group
> +      (or
> +        ('symbol', '0')
> +        ('symbol', '1')
> +        ('symbol', '2'))))
> +  * optimized:
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (func
> +      ('symbol', '_list')
> +      ('string', '0\x001\x002')))
> +  * set:
> +  <baseset [0, 1, 2]>
> +  0
> +  1
> +  2
> + BROKEN: should be '2 1 0'
> +
> + 'A + B' should take the ordering of the left expression:
> +
> +  $ try --optimize '2:0 & (0:1 + 2)'
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (group
> +      (or
> +        (range
> +          ('symbol', '0')
> +          ('symbol', '1'))
> +        ('symbol', '2'))))
> +  * optimized:
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (or
> +      (range
> +        ('symbol', '0')
> +        ('symbol', '1'))
> +      ('symbol', '2')))
> +  * set:
> +  <addset
> +    <filteredset
> +      <spanset+ 0:1>,
> +      <spanset- 0:2>>,
> +    <baseset [2]>>
> +  0
> +  1
> +  2
> + BROKEN: should be '2 1 0'
> +
> + '_intlist(a b)' should behave like 'a + b':
> +
> +  $ trylist --optimize '2:0 & %ld' 0 1 2
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (func
> +      ('symbol', '_intlist')
> +      ('string', '0\x001\x002')))
> +  * optimized:
> +  (and
> +    (func
> +      ('symbol', '_intlist')
> +      ('string', '0\x001\x002'))
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0')))
> +  * set:
> +  <filteredset
> +    <spanset- 0:2>,
> +    <baseset [0, 1, 2]>>
> +  2
> +  1
> +  0
> +
> +  $ trylist --optimize '%ld & 2:0' 0 2 1
> +  (and
> +    (func
> +      ('symbol', '_intlist')
> +      ('string', '0\x002\x001'))
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0')))
> +  * optimized:
> +  (and
> +    (func
> +      ('symbol', '_intlist')
> +      ('string', '0\x002\x001'))
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0')))
> +  * set:
> +  <filteredset
> +    <spanset- 0:2>,
> +    <baseset [0, 2, 1]>>
> +  2
> +  1
> +  0
> + BROKEN: should be '0 2 1'
> +
> + '_hexlist(a b)' should behave like 'a + b':
> +
> +  $ trylist --optimize --bin '2:0 & %ln' `hg log -T '{node} ' -r0:2`
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (func
> +      ('symbol', '_hexlist')
> +      ('string', '*'))) (glob)
> +  * optimized:
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (func
> +      ('symbol', '_hexlist')
> +      ('string', '*'))) (glob)
> +  * set:
> +  <baseset [0, 1, 2]>
> +  0
> +  1
> +  2
> + BROKEN: should be '2 1 0'
> +
> +  $ trylist --optimize --bin '%ln & 2:0' `hg log -T '{node} ' -r0+2+1`
> +  (and
> +    (func
> +      ('symbol', '_hexlist')
> +      ('string', '*')) (glob)
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0')))
> +  * optimized:
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (func
> +      ('symbol', '_hexlist')
> +      ('string', '*'))) (glob)
> +  * set:
> +  <baseset [0, 2, 1]>
> +  0
> +  2
> +  1
> +
> + 'present()' should do nothing other than suppressing an error:
> +
> +  $ try --optimize '2:0 & present(0 + 1 + 2)'
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (func
> +      ('symbol', 'present')
> +      (or
> +        ('symbol', '0')
> +        ('symbol', '1')
> +        ('symbol', '2'))))
> +  * optimized:
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (func
> +      ('symbol', 'present')
> +      (func
> +        ('symbol', '_list')
> +        ('string', '0\x001\x002'))))
> +  * set:
> +  <baseset [0, 1, 2]>
> +  0
> +  1
> +  2
> + BROKEN: should be '2 1 0'
> +
> + 'reverse()' should take effect only if it is the outermost expression:
> +
> +  $ try --optimize '0:2 & reverse(all())'
> +  (and
> +    (range
> +      ('symbol', '0')
> +      ('symbol', '2'))
> +    (func
> +      ('symbol', 'reverse')
> +      (func
> +        ('symbol', 'all')
> +        None)))
> +  * optimized:
> +  (and
> +    (range
> +      ('symbol', '0')
> +      ('symbol', '2'))
> +    (func
> +      ('symbol', 'reverse')
> +      (func
> +        ('symbol', 'all')
> +        None)))
> +  * set:
> +  <filteredset
> +    <spanset- 0:2>,
> +    <spanset+ 0:9>>
> +  2
> +  1
> +  0
> + BROKEN: should be '0 1 2'
> +
> + 'sort()' should take effect only if it is the outermost expression:
> +
> +  $ try --optimize '0:2 & sort(all(), -rev)'
> +  (and
> +    (range
> +      ('symbol', '0')
> +      ('symbol', '2'))
> +    (func
> +      ('symbol', 'sort')
> +      (list
> +        (func
> +          ('symbol', 'all')
> +          None)
> +        (negate
> +          ('symbol', 'rev')))))
> +  * optimized:
> +  (and
> +    (range
> +      ('symbol', '0')
> +      ('symbol', '2'))
> +    (func
> +      ('symbol', 'sort')
> +      (list
> +        (func
> +          ('symbol', 'all')
> +          None)
> +        ('string', '-rev'))))
> +  * set:
> +  <filteredset
> +    <spanset- 0:2>,
> +    <spanset+ 0:9>>
> +  2
> +  1
> +  0
> + BROKEN: should be '0 1 2'
> +
> + for 'A & f(B)', 'B' should not be affected by the order of 'A':
> +
> +  $ try --optimize '2:0 & first(1 + 0 + 2)'
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (func
> +      ('symbol', 'first')
> +      (or
> +        ('symbol', '1')
> +        ('symbol', '0')
> +        ('symbol', '2'))))
> +  * optimized:
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (func
> +      ('symbol', 'first')
> +      (func
> +        ('symbol', '_list')
> +        ('string', '1\x000\x002'))))
> +  * set:
> +  <baseset
> +    <limit n=1, offset=0,
> +      <spanset- 0:2>,
> +      <baseset [1, 0, 2]>>>
> +  1
> +
> +  $ try --optimize '2:0 & not last(0 + 2 + 1)'
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (not
> +      (func
> +        ('symbol', 'last')
> +        (or
> +          ('symbol', '0')
> +          ('symbol', '2')
> +          ('symbol', '1')))))
> +  * optimized:
> +  (difference
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (func
> +      ('symbol', 'last')
> +      (func
> +        ('symbol', '_list')
> +        ('string', '0\x002\x001'))))
> +  * set:
> +  <filteredset
> +    <spanset- 0:2>,
> +    <not
> +      <baseset
> +        <last n=1,
> +          <fullreposet+ 0:9>,
> +          <baseset [1, 2, 0]>>>>>
> +  2
> +  0
> +
> + for 'A & (op)(B)', 'B' should not be affected by the order of 'A':
> +
> +  $ try --optimize '2:0 & (1 + 0 + 2):(0 + 2 + 1)'
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (range
> +      (group
> +        (or
> +          ('symbol', '1')
> +          ('symbol', '0')
> +          ('symbol', '2')))
> +      (group
> +        (or
> +          ('symbol', '0')
> +          ('symbol', '2')
> +          ('symbol', '1')))))
> +  * optimized:
> +  (and
> +    (range
> +      ('symbol', '2')
> +      ('symbol', '0'))
> +    (range
> +      (func
> +        ('symbol', '_list')
> +        ('string', '1\x000\x002'))
> +      (func
> +        ('symbol', '_list')
> +        ('string', '0\x002\x001'))))
> +  * set:
> +  <filteredset
> +    <baseset [1]>,
> +    <spanset- 0:2>>
> +  1
> +
> + 'A & B' can be rewritten as 'B & A' by weight, but the ordering rule should
> + be determined before the optimization (i.e. 'B' should take the ordering of
> + 'A'):
> +
> +  $ try --optimize 'contains("glob:*") & (2 + 0 + 1)'
> +  (and
> +    (func
> +      ('symbol', 'contains')
> +      ('string', 'glob:*'))
> +    (group
> +      (or
> +        ('symbol', '2')
> +        ('symbol', '0')
> +        ('symbol', '1'))))
> +  * optimized:
> +  (and
> +    (func
> +      ('symbol', '_list')
> +      ('string', '2\x000\x001'))
> +    (func
> +      ('symbol', 'contains')
> +      ('string', 'glob:*')))
> +  * set:
> +  <filteredset
> +    <baseset [2, 0, 1]>,
> +    <contains 'glob:*'>>
> +  2
> +  0
> +  1
> + BROKEN: should be '0 1 2'
> +
> +  $ try --optimize 'reverse(contains("glob:*")) & (0 + 2 + 1)'
> +  (and
> +    (func
> +      ('symbol', 'reverse')
> +      (func
> +        ('symbol', 'contains')
> +        ('string', 'glob:*')))
> +    (group
> +      (or
> +        ('symbol', '0')
> +        ('symbol', '2')
> +        ('symbol', '1'))))
> +  * optimized:
> +  (and
> +    (func
> +      ('symbol', '_list')
> +      ('string', '0\x002\x001'))
> +    (func
> +      ('symbol', 'reverse')
> +      (func
> +        ('symbol', 'contains')
> +        ('string', 'glob:*'))))
> +  * set:
> +  <filteredset
> +    <baseset [1, 2, 0]>,
> +    <contains 'glob:*'>>
> +  1
> +  2
> +  0
> + BROKEN: should be '2 1 0'
> +
>  test sort revset
>  --------------------------------------------
>
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

Patch

diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -31,6 +31,46 @@ 
   >   hg log --template '{rev}\n' -r "$1"
   > }
 
+extension to build '_intlist()' and '_hexlist()', which is necessary because
+these predicates use '\0' as a separator:
+
+  $ cat <<EOF > debugrevlistspec.py
+  > from __future__ import absolute_import
+  > from mercurial import (
+  >     cmdutil,
+  >     node as nodemod,
+  >     revset,
+  > )
+  > cmdtable = {}
+  > command = cmdutil.command(cmdtable)
+  > @command('debugrevlistspec',
+  >     [('', 'optimize', None, 'print parsed tree after optimizing'),
+  >      ('', 'bin', None, 'unhexlify arguments')])
+  > def debugrevlistspec(ui, repo, fmt, *args, **opts):
+  >     if opts['bin']:
+  >         args = map(nodemod.bin, args)
+  >     expr = revset.formatspec(fmt, list(args))
+  >     if ui.verbose:
+  >         tree = revset.parse(expr, lookup=repo.__contains__)
+  >         ui.note(revset.prettyformat(tree), "\n")
+  >         if opts["optimize"]:
+  >             opttree = revset.optimize(tree)
+  >             ui.note("* optimized:\n", revset.prettyformat(opttree), "\n")
+  >     func = revset.match(ui, expr, repo)
+  >     revs = func(repo)
+  >     if ui.verbose:
+  >         ui.note("* set:\n", revset.prettyformatset(revs), "\n")
+  >     for c in revs:
+  >         ui.write("%s\n" % c)
+  > EOF
+  $ cat <<EOF >> $HGRCPATH
+  > [extensions]
+  > debugrevlistspec = $TESTTMP/debugrevlistspec.py
+  > EOF
+  $ trylist() {
+  >   hg debugrevlistspec --debug "$@"
+  > }
+
   $ hg init repo
   $ cd repo
 
@@ -901,6 +941,10 @@  Test working-directory revision
 Test order of revisions in compound expression
 ----------------------------------------------
 
+The general rule is that only the outermost (= leftmost) predicate can
+enforce its ordering requirement. The other predicates should take the
+ordering defined by it.
+
  'A & B' should follow the order of 'A':
 
   $ log '2:0 & 0::2'
@@ -908,6 +952,432 @@  Test order of revisions in compound expr
   1
   0
 
+ 'head()' combines sets in wrong order:
+
+  $ log '2:0 & head()'
+  0
+  1
+  2
+ BROKEN: should be '2 1 0'
+
+ 'a + b', which is optimized to '_list(a b)', should take the ordering of
+ the left expression:
+
+  $ try --optimize '2:0 & (0 + 1 + 2)'
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (group
+      (or
+        ('symbol', '0')
+        ('symbol', '1')
+        ('symbol', '2'))))
+  * optimized:
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (func
+      ('symbol', '_list')
+      ('string', '0\x001\x002')))
+  * set:
+  <baseset [0, 1, 2]>
+  0
+  1
+  2
+ BROKEN: should be '2 1 0'
+
+ 'A + B' should take the ordering of the left expression:
+
+  $ try --optimize '2:0 & (0:1 + 2)'
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (group
+      (or
+        (range
+          ('symbol', '0')
+          ('symbol', '1'))
+        ('symbol', '2'))))
+  * optimized:
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (or
+      (range
+        ('symbol', '0')
+        ('symbol', '1'))
+      ('symbol', '2')))
+  * set:
+  <addset
+    <filteredset
+      <spanset+ 0:1>,
+      <spanset- 0:2>>,
+    <baseset [2]>>
+  0
+  1
+  2
+ BROKEN: should be '2 1 0'
+
+ '_intlist(a b)' should behave like 'a + b':
+
+  $ trylist --optimize '2:0 & %ld' 0 1 2
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (func
+      ('symbol', '_intlist')
+      ('string', '0\x001\x002')))
+  * optimized:
+  (and
+    (func
+      ('symbol', '_intlist')
+      ('string', '0\x001\x002'))
+    (range
+      ('symbol', '2')
+      ('symbol', '0')))
+  * set:
+  <filteredset
+    <spanset- 0:2>,
+    <baseset [0, 1, 2]>>
+  2
+  1
+  0
+
+  $ trylist --optimize '%ld & 2:0' 0 2 1
+  (and
+    (func
+      ('symbol', '_intlist')
+      ('string', '0\x002\x001'))
+    (range
+      ('symbol', '2')
+      ('symbol', '0')))
+  * optimized:
+  (and
+    (func
+      ('symbol', '_intlist')
+      ('string', '0\x002\x001'))
+    (range
+      ('symbol', '2')
+      ('symbol', '0')))
+  * set:
+  <filteredset
+    <spanset- 0:2>,
+    <baseset [0, 2, 1]>>
+  2
+  1
+  0
+ BROKEN: should be '0 2 1'
+
+ '_hexlist(a b)' should behave like 'a + b':
+
+  $ trylist --optimize --bin '2:0 & %ln' `hg log -T '{node} ' -r0:2`
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (func
+      ('symbol', '_hexlist')
+      ('string', '*'))) (glob)
+  * optimized:
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (func
+      ('symbol', '_hexlist')
+      ('string', '*'))) (glob)
+  * set:
+  <baseset [0, 1, 2]>
+  0
+  1
+  2
+ BROKEN: should be '2 1 0'
+
+  $ trylist --optimize --bin '%ln & 2:0' `hg log -T '{node} ' -r0+2+1`
+  (and
+    (func
+      ('symbol', '_hexlist')
+      ('string', '*')) (glob)
+    (range
+      ('symbol', '2')
+      ('symbol', '0')))
+  * optimized:
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (func
+      ('symbol', '_hexlist')
+      ('string', '*'))) (glob)
+  * set:
+  <baseset [0, 2, 1]>
+  0
+  2
+  1
+
+ 'present()' should do nothing other than suppressing an error:
+
+  $ try --optimize '2:0 & present(0 + 1 + 2)'
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (func
+      ('symbol', 'present')
+      (or
+        ('symbol', '0')
+        ('symbol', '1')
+        ('symbol', '2'))))
+  * optimized:
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (func
+      ('symbol', 'present')
+      (func
+        ('symbol', '_list')
+        ('string', '0\x001\x002'))))
+  * set:
+  <baseset [0, 1, 2]>
+  0
+  1
+  2
+ BROKEN: should be '2 1 0'
+
+ 'reverse()' should take effect only if it is the outermost expression:
+
+  $ try --optimize '0:2 & reverse(all())'
+  (and
+    (range
+      ('symbol', '0')
+      ('symbol', '2'))
+    (func
+      ('symbol', 'reverse')
+      (func
+        ('symbol', 'all')
+        None)))
+  * optimized:
+  (and
+    (range
+      ('symbol', '0')
+      ('symbol', '2'))
+    (func
+      ('symbol', 'reverse')
+      (func
+        ('symbol', 'all')
+        None)))
+  * set:
+  <filteredset
+    <spanset- 0:2>,
+    <spanset+ 0:9>>
+  2
+  1
+  0
+ BROKEN: should be '0 1 2'
+
+ 'sort()' should take effect only if it is the outermost expression:
+
+  $ try --optimize '0:2 & sort(all(), -rev)'
+  (and
+    (range
+      ('symbol', '0')
+      ('symbol', '2'))
+    (func
+      ('symbol', 'sort')
+      (list
+        (func
+          ('symbol', 'all')
+          None)
+        (negate
+          ('symbol', 'rev')))))
+  * optimized:
+  (and
+    (range
+      ('symbol', '0')
+      ('symbol', '2'))
+    (func
+      ('symbol', 'sort')
+      (list
+        (func
+          ('symbol', 'all')
+          None)
+        ('string', '-rev'))))
+  * set:
+  <filteredset
+    <spanset- 0:2>,
+    <spanset+ 0:9>>
+  2
+  1
+  0
+ BROKEN: should be '0 1 2'
+
+ for 'A & f(B)', 'B' should not be affected by the order of 'A':
+
+  $ try --optimize '2:0 & first(1 + 0 + 2)'
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (func
+      ('symbol', 'first')
+      (or
+        ('symbol', '1')
+        ('symbol', '0')
+        ('symbol', '2'))))
+  * optimized:
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (func
+      ('symbol', 'first')
+      (func
+        ('symbol', '_list')
+        ('string', '1\x000\x002'))))
+  * set:
+  <baseset
+    <limit n=1, offset=0,
+      <spanset- 0:2>,
+      <baseset [1, 0, 2]>>>
+  1
+
+  $ try --optimize '2:0 & not last(0 + 2 + 1)'
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (not
+      (func
+        ('symbol', 'last')
+        (or
+          ('symbol', '0')
+          ('symbol', '2')
+          ('symbol', '1')))))
+  * optimized:
+  (difference
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (func
+      ('symbol', 'last')
+      (func
+        ('symbol', '_list')
+        ('string', '0\x002\x001'))))
+  * set:
+  <filteredset
+    <spanset- 0:2>,
+    <not
+      <baseset
+        <last n=1,
+          <fullreposet+ 0:9>,
+          <baseset [1, 2, 0]>>>>>
+  2
+  0
+
+ for 'A & (op)(B)', 'B' should not be affected by the order of 'A':
+
+  $ try --optimize '2:0 & (1 + 0 + 2):(0 + 2 + 1)'
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (range
+      (group
+        (or
+          ('symbol', '1')
+          ('symbol', '0')
+          ('symbol', '2')))
+      (group
+        (or
+          ('symbol', '0')
+          ('symbol', '2')
+          ('symbol', '1')))))
+  * optimized:
+  (and
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
+    (range
+      (func
+        ('symbol', '_list')
+        ('string', '1\x000\x002'))
+      (func
+        ('symbol', '_list')
+        ('string', '0\x002\x001'))))
+  * set:
+  <filteredset
+    <baseset [1]>,
+    <spanset- 0:2>>
+  1
+
+ 'A & B' can be rewritten as 'B & A' by weight, but the ordering rule should
+ be determined before the optimization (i.e. 'B' should take the ordering of
+ 'A'):
+
+  $ try --optimize 'contains("glob:*") & (2 + 0 + 1)'
+  (and
+    (func
+      ('symbol', 'contains')
+      ('string', 'glob:*'))
+    (group
+      (or
+        ('symbol', '2')
+        ('symbol', '0')
+        ('symbol', '1'))))
+  * optimized:
+  (and
+    (func
+      ('symbol', '_list')
+      ('string', '2\x000\x001'))
+    (func
+      ('symbol', 'contains')
+      ('string', 'glob:*')))
+  * set:
+  <filteredset
+    <baseset [2, 0, 1]>,
+    <contains 'glob:*'>>
+  2
+  0
+  1
+ BROKEN: should be '0 1 2'
+
+  $ try --optimize 'reverse(contains("glob:*")) & (0 + 2 + 1)'
+  (and
+    (func
+      ('symbol', 'reverse')
+      (func
+        ('symbol', 'contains')
+        ('string', 'glob:*')))
+    (group
+      (or
+        ('symbol', '0')
+        ('symbol', '2')
+        ('symbol', '1'))))
+  * optimized:
+  (and
+    (func
+      ('symbol', '_list')
+      ('string', '0\x002\x001'))
+    (func
+      ('symbol', 'reverse')
+      (func
+        ('symbol', 'contains')
+        ('string', 'glob:*'))))
+  * set:
+  <filteredset
+    <baseset [1, 2, 0]>,
+    <contains 'glob:*'>>
+  1
+  2
+  0
+ BROKEN: should be '2 1 0'
+
 test sort revset
 --------------------------------------------