This instance will be upgraded to Heptapod 0.21.0rc3 on 2021-04-23 at 10:30 UTC+2 (a few minutes of down time)

Commit ef083a78 authored by Georges Racinet's avatar Georges Racinet 🦑

WIP demo testhelpers: translating a half of test-revset.t

This corresponds to test-revset.t until this comment:
"Test smartset.slice() by first/last()"

On my workstation, the truncated test-revset.t takes 25-30 seconds to run:

    $ time ./run-tests.py -l test-revset.t
    running 1 tests using 1 parallel processes
    .
    # Ran 1 tests, 0 skipped, 0 failed.
    ./run-tests.py -l test-revset.t  28.51s user 4.03s system 98% cpu 32.869 total

This translation runs in 0.3 seconds

    (venv3) ~/heptapod/hdk/default/py-heptapod $ pytest heptapod/tests/test_demo_testhelpers_revset.py
    =========================== test session starts ===========================
    platform linux -- Python 3.8.6, pytest-3.10.1, py-1.9.0, pluggy-0.13.1
    rootdir: /home/gracinet/heptapod/hdk/default/py-heptapod, inifile: pytest.ini
    plugins: cov-2.6.0, grpc-0.8.0
    collected 1 item

    heptapod/tests/test_demo_testhelpers_revset.py .                    [100%]

    ============================ warnings summary =============================
    heptapod/tests/test_demo_testhelpers_revset.py::test_revset
      /home/gracinet/heptapod/hdk/default/venv3/lib/python3.8/site-packages/mercurial/utils/stringutil.py:635: DeprecationWarning: invalid escape sequence '\d'
        return codecs.escape_decode(s)[0]
      /home/gracinet/heptapod/hdk/default/venv3/lib/python3.8/site-packages/mercurial/utils/stringutil.py:635: DeprecationWarning: invalid escape sequence '\d'
        return codecs.escape_decode(s)[0]

    -- Docs: https://docs.pytest.org/en/latest/warnings.html
    ================== 1 passed, 2 warnings in 0.28 seconds ===================

Remarks and caveats:

  - we're not making assertions about internal representation of the revsets,
    especially to check optimizations. Arguably, these could belong to another
    test case
  - ignored tests related to `r3232`. Don't expect them to be a problem.
  - time to run was almost the same with only a dozen assertions. Could mean
    that most of the time is spent in the fixture. On the other side, the
    truncated `test-revset.t` is faster than the whole (28s vs 59s).
parent 62c5b7e44fa5
Pipeline #13074 passed with stages
in 1 minute and 26 seconds
# *-* coding: utf-8
from datetime import datetime
from mercurial import (
error,
revset,
revsetlang,
)
import pytest
from heptapod.testhelpers import LocalRepoWrapper, as_bytes
def assert_revset_list(repo, revset, expected):
assert list(repo.revs(as_bytes(revset))) == expected
def debug_revspec(repo, expr):
expr = as_bytes(expr)
aliases = repo.ui.configitems(b'revsetalias')
tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
stages = [
(b'parsed', lambda tree: tree),
(
b'expanded',
lambda tree: revsetlang.expandaliases(tree, aliases, repo.ui.warn),
),
(b'concatenated', revsetlang.foldconcat),
(b'analyzed', revsetlang.analyze),
(b'optimized', revsetlang.optimize),
]
for _n, f in stages:
tree = f(tree)
func = revset.makematcher(tree)
revs = func(repo)
return list(revs)
def test_revset(tmpdir):
repo_path = tmpdir.join('repo')
wrapper = LocalRepoWrapper.init(repo_path,
config=dict(ui=dict(username=b'test')))
repo = wrapper.repo
ctx0 = wrapper.write_commit('a', content='a', message="0", branch='a')
ctx1 = wrapper.write_commit('b', content='b', message="1",
branch='b')
# we don't have a wrapper.rm_commit helper
ctx2 = wrapper.commit_remove('a', branch='a-b-c-', user='Bob')
assert list(repo.revs(b"extra('branch', 'a-b-c-')")) == [2]
assert list(repo.revs(b"extra('branch', 're:a')")) == [0, 2]
ctx3 = wrapper.commit_empty(message="3", parent=ctx1, branch="+a+b+c+")
ctx4 = wrapper.write_commit(
'b',
message="4",
content='bb',
branch='-a-b-c-',
# timestamp is UTC, done with local TZ
# hope that's as in test-revset.t (or close enough)
utc_timestamp=datetime(2005, 5, 12).timestamp(),
parent=ctx2)
wrapper.commit_empty(message="5 bug",
parent=ctx3,
branch="!a/b/c/")
wrapper.command('merge', b'4')
wrapper.commit_empty(message="6 issue619", branch="_a_b_c_")
wrapper.commit_empty(message="7", branch=".a.b.c.")
# seems useless but may be a bug reproduction?
wrapper.command('branch', b'all')
wrapper.commit_empty(message="9", parent=ctx4, branch="é")
# NdGr don't know why ui.username isn't interpreted here
wrapper.command('tag', b'1.0', rev=b'6', user=b'test')
wrapper.command('bookmark', b'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
rev=b'6')
# trivial cases
assert_revset_list(repo, '0:1', [0, 1])
# test-revset.t uses `--optimize`. That seems to be for outputing
# the parsed tree after optimization (out of scope for this demo then)
assert_revset_list(repo, ':', list(range(10)))
assert_revset_list(repo, '3::6', [3, 5, 6])
assert_revset_list(repo, '0|1|2', [0, 1, 2])
# names that should work without quoting
assert_revset_list(repo, 'a', [0])
assert_revset_list(repo, 'b-a', [1])
assert_revset_list(repo, '_a_b_c_', [6])
assert_revset_list(repo, '_a_b_c_-a', [6])
assert_revset_list(repo, '.a.b.c.', [7])
assert_revset_list(repo, '.a.b.c.-a', [7])
# names that should be caught by fallback mechanism
# NdGR here debug_revspec does something different than repo.revs()
assert debug_revspec(repo, b'-a-b-c-') == [4]
assert debug_revspec(repo, b'+a+b+c+') == [3]
assert debug_revspec(repo, b'+a+b+c+:') == list(range(3, 10))
assert debug_revspec(repo, b':+a+b+c+') == list(range(4))
assert debug_revspec(repo, b'-a-b-c-:+a+b+c+') == [4, 3]
with pytest.raises(error.RepoLookupError) as exc_info:
debug_revspec(repo, b'-a-b-c--a')
# NdGR I'm not in favor of testing actual error message content
assert b"'-a'" in exc_info.value.args[0]
assert_revset_list(repo, 'é'.encode('utf-8'), [9])
# no quoting needed
assert debug_revspec(repo, b'::a-b-c-') == [0, 1, 2]
# quoting needed
assert_revset_list(repo, '"-a-b-c-"-a', [4])
assert_revset_list(repo, '1 or 2', [1, 2])
assert_revset_list(repo, '1|2', [1, 2])
assert_revset_list(repo, '1&2', [])
# precedence: & is higher
assert_revset_list(repo, '1&2|3', [3])
assert_revset_list(repo, '1|2&3', [1])
# associativity
assert_revset_list(repo, '1&2&3', [])
assert_revset_list(repo, '1|(2|3)', [1, 2, 3])
# tag
assert_revset_list(repo, '1.0', [6])
# branch
assert_revset_list(repo, 'a', [0])
assert_revset_list(repo, ctx0.hex()[:10], [0]) # was 2785f51ee
assert_revset_list(repo, 'date(2005)', [4])
# date invalid syntaxes
with pytest.raises(error.ParseError):
repo.revs('date(this is a test)')
with pytest.raises(error.ParseError):
repo.revs('date()')
with pytest.raises(error.RepoLookupError):
repo.revs('date')
with pytest.raises(error.ParseError):
repo.revs('date(')
with pytest.raises(error.ParseError):
repo.revs(r'date("\xy")')
with pytest.raises(error.ParseError):
repo.revs('date(tip)')
with pytest.raises(error.RepoLookupError):
repo.revs('0:date')
with pytest.raises(error.RepoLookupError):
repo.revs('::date')
wrapper.command('bookmark', b'date', rev=b'4')
assert_revset_list(repo, '0:date', list(range(5)))
assert_revset_list(repo, '::date', [0, 1, 2, 4])
assert_revset_list(repo, '::"date"', [0, 1, 2, 4])
assert_revset_list(repo, 'date(2005) and 1::', [4])
wrapper.command('bookmark', b'date', delete=True)
# function name shoud be a symbol
with pytest.raises(error.ParseError):
repo.revs('"date"(2005)')
# keyword arguments
assert_revset_list(repo, 'extra(branch, value=a)', [0])
# TODO should really add assertions to make distinction from my own
# typos
with pytest.raises(error.ParseError):
repo.revs('extra(branch, a, b)')
with pytest.raises(error.ParseError):
repo.revs('extra(label=branch, default)')
with pytest.raises(error.ParseError):
repo.revs('extra(branch, foo+bar=baz)')
with pytest.raises(error.ParseError):
repo.revs('extra(unkown=branch)')
with pytest.raises(error.ParseError):
repo.revs('extra((), x)')
with pytest.raises(error.ParseError):
repo.revs('extra(label=x, ())')
with pytest.raises(error.ParseError):
repo.revs('foo=bar|baz')
# right-hand side should be optimized recursively
# TODO not sure what to make of that one. Can the point really be
# the resulting graph before raising the error ?
with pytest.raises(error.ParseError):
repo.revs('foo=(not public())')
# relation-subscript operator has the highest binding strength
# (as function call):
assert_revset_list(repo, 'tip:tip^#generations[-1]', [9, 8, 7, 6, 5, 4])
# TODO some really about resulting AST and its optim
# still calling not to cheat on exec times
# $ hg debugrevspec -p parsed --no-show-revs 'not public()#generations[0]'
debug_revspec(repo, b'not public()#generations[0]')
# $ hg debugrevspec -p analyzed -p optimized --no-show-revs \
# > '(not public())#generations[0]'
debug_revspec(repo, b'not public()#generations[0]')
# TODO again only analysis, have to assert on that
with pytest.raises(error.ParseError):
repo.revs('tip[0]')
with pytest.raises(error.ParseError):
repo.revs('tip#rel[0]')
with pytest.raises(error.ParseError):
repo.revs('(tip#rel)[0]')
with pytest.raises(error.ParseError):
repo.revs('tip#rel[0][1]')
with pytest.raises(error.ParseError):
repo.revs('tip#rel0#rel1[1]')
with pytest.raises(error.ParseError):
repo.revs('tip#rel0[0]#rel1[1]')
# parse errors of relation, subscript and relation-subscript operators
# suggested relations
# parsed tree at stages
# NdGR tired of this, not sure it's relevant to the perf demo
# verify optimized tree
# TODO this uses the r3232 primitive introduced by the extension
# not ported to here
# Test that symbols only get parsed as functions if there's an opening
# parenthesis.
wrapper.command('bookmark', b'only', rev=b'9')
assert_revset_list(repo, 'only', [9])
# Outer "only" is a function, inner "only" is the bookmark
assert_revset_list(repo, 'only(only)', [8, 9])
# ':y' behaves like '0:y', but can't be rewritten as such since the
# revision '0' may be hidden (issue5385)
# TODO the point of this one is to check the rewriting,
# we'll need to inspect the AST (actually it would be better to
# separate concerns, here)
assert_revset_list(repo, ':', list(range(10)))
assert_revset_list(repo, ':1', [0, 1])
assert_revset_list(repo, ':(1|2)', [0, 1, 2])
assert_revset_list(repo, ':(1&2)', [])
# infix/suffix resolution of ^ operator (issue2884, issue5764):
# x^:y means (x^):y
assert_revset_list(repo, '1^:2', [0, 1, 2])
assert_revset_list(repo, '1^::2', [0, 1, 2])
assert_revset_list(repo, '1^..2', [0, 1, 2])
assert_revset_list(repo, '9^:', [8, 9])
assert_revset_list(repo, '9^::', [8, 9])
assert_revset_list(repo, '9^..', [8, 9])
# x^:y should be resolved before omitting group operators
# TODO again a case of AST assertion
with pytest.raises(error.ParseError):
repo.revs('1^(:2)')
# x^:y should be resolved recursively
assert_revset_list(repo, 'sort(1^:2)', [0, 1, 2])
assert_revset_list(repo, '(3^:4)^:2', [0, 1, 2])
assert_revset_list(repo, '(3^::4)^::2', [0, 1, 2])
assert_revset_list(repo, '(9^:)^:', [4, 5, 6, 7, 8, 9])
# x^ in alias should also be resolved
# NdGR these are actually testing our debug_revspec() helper!
# They should stay at the functional layer
# using 'AL' instead of 'A' to avoid being an ambiguous node prefix
repo.ui.setconfig(b'revsetalias', b'AL', b'1^:2')
assert debug_revspec(repo, 'AL') == [0, 1, 2]
repo.ui.setconfig(b'revsetalias', b'AL', b'1^')
assert debug_revspec(repo, 'AL:2') == [0, 1, 2]
# but not beyond the boundary of alias expansion, because the resolution
# should be made at the parsing stage
repo.ui.setconfig(b'revsetalias', b'AL', b':2')
# TODO again AST analysis
with pytest.raises(error.ParseError):
debug_revspec(repo, '1^AL')
# :: itself isn't a valid expression
with pytest.raises(error.ParseError):
repo.revs('::')
# ancestor can accept 0 or more arguments
assert_revset_list(repo, 'ancestor()', [])
assert_revset_list(repo, 'ancestor(1)', [1])
assert_revset_list(repo, 'ancestor(4, 5)', [1])
assert_revset_list(repo, 'ancestor(4, 5) and 4', [])
assert_revset_list(repo, 'ancestor(0, 0, 1, 3)', [0])
assert_revset_list(repo, 'ancestor(3, 1, 5, 3, 5, 1)', [1])
assert_revset_list(repo, 'ancestor(0, 1, 3, 5)', [0])
assert_revset_list(repo, 'ancestor(1, 2, 3, 5)', [1])
# test ancestors
# TODO print of graphlog as courtesy for people investigating errors
assert_revset_list(repo, 'ancestors(5)', [0, 1, 3, 5])
assert_revset_list(repo, 'ancestor(ancestors(5))', [0])
# TODO port r3232
# assert_revset_list(repo, '::r3232', [0, 1, 2, 3])
# test common ancestors
assert_revset_list(repo, 'commonancestors(7 + 9)', [0, 1, 2, 4])
assert_revset_list(repo, 'commonancestors(heads(all()))', [0, 1, 2, 4])
assert_revset_list(repo, 'commonancestors(9)', [0, 1, 2, 4, 8, 9])
assert_revset_list(repo, 'commonancestors(8 + 9)', [0, 1, 2, 4, 8])
# test the specialized implementation of heads(commonancestors(..))
# (2 gcas is tested in test-merge-criss-cross.t)
assert_revset_list(repo, 'heads(commonancestors(7 + 9))', [4])
assert_revset_list(repo, 'heads(commonancestors(heads(all())))', [4])
assert_revset_list(repo, 'heads(commonancestors(9))', [9])
assert_revset_list(repo, 'heads(commonancestors(8 + 9))', [8])
# test ancestor variants of empty revision
assert_revset_list(repo, 'ancestor(none())', [])
assert_revset_list(repo, 'ancestors(none())', [])
assert_revset_list(repo, 'commonancestors(none())', [])
assert_revset_list(repo, 'heads(commonancestors(none()))', [])
# test ancestors with depth limit
# (depth=0 selects the node itself)
assert_revset_list(repo, 'reverse(ancestors(9, depth=0))', [9])
# (interleaved: '4' would be missing if heap queue were higher depth first)
assert_revset_list(repo, 'reverse(ancestors(8:9, depth=1))', [9, 8, 4])
# (interleaved: '2' would be missing if heap queue were higher depth first)
assert_revset_list(repo, 'reverse(ancestors(7+8, depth=2))',
[8, 7, 6, 5, 4, 2])
# (walk example above by separate queries)
assert_revset_list(
repo,
'reverse(ancestors(8, depth=2)) + reverse(ancestors(7, depth=2))',
[8, 4, 2, 7, 6, 5])
# (walk 2nd and 3rd ancestors)
assert_revset_list(repo, 'reverse(ancestors(7, depth=3, startdepth=2))',
[5, 4, 3, 2])
# (interleaved: '4' would be missing if higher-depth ancestors
# weren't scanned)
assert_revset_list(repo, 'reverse(ancestors(7+8, depth=2, startdepth=2))',
[5, 4, 2])
# (note that 'ancestors(x, depth=y, startdepth=z)' does not identical to
# 'ancestors(x, depth=y) - ancestors(x, depth=z-1)'
# because a node may have multiple depths)
assert_revset_list(
repo,
'reverse(ancestors(7+8, depth=2) - ancestors(7+8, depth=1))',
[5, 2])
# test bad arguments passed to ancestors()
with pytest.raises(error.ParseError):
repo.revs(b'ancestors(., depth=-1)')
with pytest.raises(error.ParseError):
repo.revs(b'ancestors(., depth=foo)')
# test descendants
# TODO print of graphlog as courtesy for people investigating errors
# (null is ultimate root and has optimized path)
assert_revset_list(repo, 'null:4 & descendants(null)', [-1, 0, 1, 2, 3, 4])
# (including merge)
assert_revset_list(repo, ':8 & descendants(2+5)', [2, 4, 5, 6, 7, 8])
# test descendants with depth limit
# (depth=0 selects the node itself)
assert_revset_list(repo, 'descendants(0, depth=0)', [0])
assert_revset_list(repo, 'null: & descendants(null, depth=0)', [-1])
# (p2 = null should be ignored)
assert_revset_list(repo, 'null: & descendants(null, depth=2)', [-1, 0, 1])
# (multiple paths: depth(6) = (2, 3))
assert_revset_list(repo, 'descendants(1+3, depth=2)', [1, 2, 3, 4, 5, 6])
# (multiple paths: depth(5) = (1, 2), depth(6) = (2, 3))
assert_revset_list(repo, 'descendants(3+1, depth=2, startdepth=2)',
[4, 5, 6])
# (multiple depths: depth(6) = (0, 2, 4), search for depth=2)
assert_revset_list(repo, 'descendants(0+3+6, depth=3, startdepth=1)',
[1, 2, 3, 4, 5, 6, 7])
# (multiple depths: depth(6) = (0, 4), no match)
assert_revset_list(repo, 'descendants(0+6, depth=3, startdepth=1)',
[1, 2, 3, 4, 5, 7])
# test ancestors/descendants relation:
assert_revset_list(repo, 'tip#generations', [0, 1, 2, 4, 8, 9])
assert_revset_list(repo, '3#g', [0, 1, 3, 5, 6, 7])
# TODO maybe assert on the AST for that one
assert_revset_list(repo, 'tip#g', [0, 1, 2, 4, 8, 9])
# test ancestors/descendants relation subscript:
assert_revset_list(repo, 'tip#generations[0]', [9])
assert_revset_list(repo, '.#generations[-1]', [8])
assert_revset_list(repo, '.#g[(-1)]', [8])
assert_revset_list(repo, '6#generations[0:1]', [6, 7])
assert_revset_list(repo, '6#generations[-1:1]', [4, 5, 6, 7])
assert_revset_list(repo, '6#generations[0:]', [6, 7])
assert_revset_list(repo, '5#generations[:0]', [0, 1, 3, 5])
assert_revset_list(repo, '3#generations[:]', [0, 1, 3, 5, 6, 7])
assert_revset_list(repo, 'tip#generations[1:-1]', [])
# NdGR that one with AST (debugrevspec)
assert_revset_list(repo, 'roots(:)#g[2]', [2, 3])
# test author
assert_revset_list(repo, 'author(bob)', [2])
assert_revset_list(repo, 'author("re:bob|test")', list(range(10)))
assert_revset_list(repo, r'author(r"re:\S")', list(range(10)))
assert_revset_list(repo, 'branch(é)'.encode('utf-8'), [8, 9])
assert_revset_list(repo, 'branch(a)', [0])
assert [(r, repo[r].branch())
for r in repo.revs('branch("re:a")')
] == [(0, b'a'),
(2, b'a-b-c-'),
(3, b'+a+b+c+'),
(4, b'-a-b-c-'),
(5, b'!a/b/c/'),
(6, b'_a_b_c_'),
(7, b'.a.b.c.'),
]
assert_revset_list(repo, 'children(4)', [6, 8])
assert_revset_list(repo, 'children(null)', [0])
assert_revset_list(repo, 'closed()', [])
# TODO too hacky, should be enclosed
repo.dirstate._cwd = repo.root
assert_revset_list(repo, b'contains(a)', [0, 1, 3, 5])
# NdGR had to change because B can match a sha (does not happen with
# the tricks the .t system does)
assert_revset_list(repo, 'desc(Bu)', [5])
assert [(r, repo[r].description().splitlines()[0])
for r in repo.revs('desc(r"re:S?u")')
] == [(5, b'5 bug'),
(6, b'6 issue619'),
]
assert_revset_list(repo, 'descendants(2 or 3)', list(range(2, 10)))
assert_revset_list(repo, 'file("b*")', [1, 4])
assert_revset_list(repo, 'filelog(b)', [1, 4])
assert_revset_list(repo, 'filelog("../repo/b")', [1, 4])
assert_revset_list(repo, 'follow()', [0, 1, 2, 4, 8, 9])
assert_revset_list(repo, r'grep("issue\d+")', [6])
# invalid regular expression
with pytest.raises(error.ParseError):
repo.revs(b'grep("(")')
assert_revset_list(repo, r'grep("\bissue\d+")', [])
with pytest.raises(error.ParseError):
repo.revs(br'grep(r"\")')
assert_revset_list(repo, 'head()', [0, 1, 2, 3, 4, 5, 6, 7, 9])
# Test heads
assert_revset_list(repo, 'heads(6::)', [7])
# heads() can be computed in subset '9:'
assert_revset_list(repo, '9: & heads(all())', [9])
# but should follow the order of the subset
assert_revset_list(repo, 'heads(all())', [7, 9])
assert_revset_list(repo, 'heads(tip:0)', [7, 9])
assert_revset_list(repo, 'tip:0 & heads(0:tip)', [9, 7])
assert_revset_list(repo, 'keyword(issue)', [6])
assert_revset_list(repo, 'keyword("test a")', [])
# Test first (=limit) and last
assert_revset_list(repo, 'limit(head(), 1)', [0])
assert_revset_list(repo, 'limit(author("re:bob|test"), 3, 5)', [5, 6, 7])
assert_revset_list(repo, 'limit(author("re:bob|test"), offset=6)', [6])
assert_revset_list(repo, 'limit(author("re:bob|test"), offset=10)', [])
with pytest.raises(error.ParseError):
repo.revs(b'limit(all(), 1, -1)')
with pytest.raises(error.ParseError):
repo.revs(b'limit(all(), -1)')
assert_revset_list(repo, 'limit(all(), 0)', [])
with pytest.raises(error.ParseError):
repo.revs(b'last(all(), -1)')
assert_revset_list(repo, 'last(all(), 0)', [])
assert_revset_list(repo, 'last(all(), 1)', [9])
assert_revset_list(repo, 'last(all(), 2)', [8, 9])
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment