tests: add a way to fix revision number usages automatically in tests

Summary:
Some tests use too many revision numbers (ex. test-rebase-scenario-global.t).
It's time consuming to fix it manually. Write a script to help with that.

This will be used by the next diff.

Reviewed By: DurhamG

Differential Revision: D22174956

fbshipit-source-id: a873ad326ecbd9cdfa2df58839b0ef21626e1506
This commit is contained in:
Jun Wu 2020-07-02 13:11:57 -07:00 committed by Facebook GitHub Bot
parent 5265f6d372
commit 473b07c328
4 changed files with 183 additions and 0 deletions

View File

@ -197,6 +197,7 @@ def stringset(repo, subset, x, order):
if x.startswith("-") or x == str(i):
# 'x' was used as a revision number. Maybe warn and log it.
_warnrevnum(repo.ui, x)
scmutil.trackrevnumfortests(repo, [x])
x = i
if x in subset or x == node.nullrev and isinstance(subset, fullreposet):

View File

@ -1384,3 +1384,58 @@ def contextnodesupportingwdir(ctx):
)
return ctx.node()
def trackrevnumfortests(repo, specs):
"""Attempt to collect information to replace revision number with revset
expressions in tests.
This works with the TESTFILE and TESTLINE environment variable set by
run-tests.py.
Information will be written to $TESTDIR/.testrevnum.
"""
if not util.istest():
return
trackrevnum = encoding.environ.get("TRACKREVNUM")
testline = encoding.environ.get("TESTLINE")
testfile = encoding.environ.get("TESTFILE")
testdir = encoding.environ.get("TESTDIR")
if not trackrevnum or not testline or not testfile or not testdir:
return
for spec in specs:
# 'spec' should be in sys.argv
if not any(spec in a for a in pycompat.sysargv):
continue
# Consider 'spec' as a revision number.
rev = int(spec)
if rev < -1:
continue
ctx = repo[rev]
if not ctx:
return
# Check candidate revset expressions.
candidates = []
if rev == -1:
candidates.append("null")
desc = ctx.description()
if desc:
candidates.append("desc(%s)" % desc.split()[0])
candidates.append("max(desc(%s))" % desc.split()[0])
candidates.append("%s" % ctx.hex())
repo = repo.unfiltered()
for candidate in candidates:
try:
nodes = list(repo.nodes(candidate))
except Exception:
continue
if nodes == [ctx.node()]:
with open(testdir + "/.testrevnum", "ab") as f:
f.write(
"fix(%r, %s, %r, %r)\n" % (testfile, testline, spec, candidate)
)
break

125
eden/scm/tests/fix-revnum.py Executable file
View File

@ -0,0 +1,125 @@
#!/usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2.
"""
Rewrite rev numbers in tests reported by scmutil.trackrevnumfortests
Usage:
- Delete `.testrevnum`.
- Set `TRACKREVNUM`.
- Run tests to fix. Rev number usages are written in `.testrevnum`.
- Run this script.
"""
import re
import sys
contents = {} # {path: lines}
fixed = set()
commithashre = re.compile(r"\A[0-9a-f]{6,40}\Z")
quote = repr
progress = 0
def fix(path, linenum, numstr, spec):
"""Attempt to replace numstr to spec at the given file and line"""
# Do not fix the same thing multiple times.
key = (path, linenum, numstr)
if key in fixed:
return
else:
fixed.add(key)
if path not in contents:
with open(path, "rb") as f:
contents[path] = f.read().splitlines(True)
lines = contents[path]
line = lines[linenum].decode("utf-8")
newline = processline(line, numstr, spec)
lines[linenum] = newline.encode("utf-8")
global progress
progress += 1
sys.stderr.write("\r%d fixes" % progress)
sys.stderr.flush()
def processline(line, numstr, spec):
"""Replace numstr with spec in line, with some care about escaping"""
# Do not replace '1' in "hg commit -m 'public 1'".
# Do not replace '1' in "hg clone repo1 repo2".
# Do not replace '1' in "hg debugbuilddag '..<2.*1/2:m<2+3:c<m+3:a<2.:b<m+2:d<2.:e<m+1:f'"
# Do not rewrite non-hg commands, like "initrepo repo1".
if (
any(s in line for s in ["hg commit", "hg clone", "debugbuilddag"])
or "hg" not in line
):
return line
alnumstr = "abcdefghijklmnopqrstuvwxyz0123456789()"
if numstr.startswith("-"):
# A negative rev number.
alnumstr += "-"
alnumstr = set(alnumstr)
newline = ""
buf = ""
singlequoted = False
doublequoted = False
for ch in line:
if ch in alnumstr:
buf += ch
else:
# A separator. Append 'buf'.
# Do not rewrite a commit hash, even if it has numbers in it.
# Do not replace '1' in 'HGPLAIN=1'.
# Do not replace '1' in '-R repo1' or '--cwd repo1'.
# Do not rewrite bookmark-like name (ex. foo@1).
# Do not rewrite redirections like `2>&1`.
# Do not rewrite recursively (ex. 2 -> desc(a2) -> desc(adesc(2))).
if (
not commithashre.match(buf)
and not any(
newline.endswith(c)
for c in ("=", "&", ">", "<", "@", "-R ", "--cwd ")
)
and ch not in {">", "<"}
and spec not in buf
and "desc(" not in buf
):
if singlequoted or doublequoted or "(" not in spec:
buf = buf.replace(numstr, spec)
else:
buf = buf.replace(numstr, quote(spec))
newline += buf
buf = ""
if ch == "'" and not newline.endswith("\\"):
singlequoted = not singlequoted
elif ch == '"' and not newline.endswith("\\"):
doublequoted = not doublequoted
newline += ch
return newline
with open(".testrevnum", "rb") as f:
exec(f.read().decode("utf-8"))
for path, lines in contents.items():
sys.stderr.write("\rwriting %s\n" % path)
sys.stderr.flush()
with open(path, "wb") as f:
f.write(b"".join(lines))
with open(".testrevnum", "wb") as f:
f.write(b"")

View File

@ -1488,6 +1488,7 @@ class Test(unittest.TestCase):
env["PYTHONUSERBASE"] = sysconfig.get_config_var("userbase")
env["HGEMITWARNINGS"] = "1"
env["TESTTMP"] = self._testtmp
env["TESTFILE"] = self.path
env["HOME"] = self._testtmp
if self._usechg:
env["CHGDISABLE"] = "0"
@ -2044,6 +2045,7 @@ class TTest(Test):
cmd = l[4:].split()
if len(cmd) == 2 and cmd[0] == b"cd":
l = b" $ cd %s || exit 1\n" % cmd[1]
script.append(b"export TESTLINE=%d\n" % n)
script.append(l[4:])
elif l.startswith(b" > "): # continuations
after.setdefault(prepos, []).append(l)