[PATCH 2 of 3] run-tests: support multiple cases in .t test
Jun Wu
quark at fb.com
Tue May 2 22:01:26 EDT 2017
# HG changeset patch
# User Jun Wu <quark at fb.com>
# Date 1493488388 25200
# Sat Apr 29 10:53:08 2017 -0700
# Node ID 6663e696ae0c2a1b8f6fd735dfcf9b99fb4dac73
# Parent f32a5c7a590fd5d8a9d6c8195b6171331ec9f3a8
# Available At https://bitbucket.org/quark-zju/hg-draft
# hg pull https://bitbucket.org/quark-zju/hg-draft -r 6663e696ae0c
run-tests: support multiple cases in .t test
Sometimes we want to run similar tests with slightly different
configurations. Previously we duplicate the test files. This patch
introduces special "#case" syntax that allows a single .t file to contain
multiple test cases.
For example, if a test should behave the same with or without an
experimental flag, we can add the following to the .t header:
#case default
....
#case experimental-a
$ cat >> $HGRCPATH << EOF
> [experimental]
> feature=a
> EOF
#endcase
The "experimental-a" block won't be executed when running the "default" test
case.
In the test body, if there are case-dependent outputs, we can also gate them
using the same syntax:
#case default
$ hg strip X
... save backup bundle ...
#case experimental-a
$ hg strip X
... pruned ...
#endcase
The implementation allows "#case" to specify multiple values, to make it
easier to test combinations affected by multiple factors. For example:
#case A B
$ flag="$flag --config experimental.factor-a=1"
#case A C
$ flag="$flag --config experimental.factor-b=1"
#case D
#endcase
A later patch will make use of this feature to simplify test-hardlink*.t.
diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -592,4 +592,5 @@ class Test(unittest.TestCase):
self.name = _strpath(self.bname)
self._testdir = os.path.dirname(path)
+ self._tmpname = os.path.basename(path)
self.errpath = os.path.join(self._testdir, b'%s.err' % self.bname)
@@ -651,5 +652,5 @@ class Test(unittest.TestCase):
raise
- name = os.path.basename(self.path)
+ name = self._tmpname
self._testtmp = os.path.join(self._threadtmp, name)
os.mkdir(self._testtmp)
@@ -1061,4 +1062,16 @@ class TTest(Test):
ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
+ def __init__(self, *args, **kwds):
+ # accept an extra "case" parameter
+ case = None
+ if 'case' in kwds:
+ case = kwds.pop('case')
+ self._case = case
+ super(TTest, self).__init__(*args, **kwds)
+ if case:
+ self.name += b' (case %s)' % case
+ self.errpath = b'%s.%s.err' % (self.errpath[:-4], case)
+ self._tmpname += b'-%s' % case
+
@property
def refpath(self):
@@ -1147,4 +1160,7 @@ class TTest(Test):
inpython = False
+ # What "case" we're currently in
+ incases = None
+
if self._debug:
script.append(b'set -x\n')
@@ -1158,4 +1174,13 @@ class TTest(Test):
if not l.endswith(b'\n'):
l += b'\n'
+ # case handling
+ if l.startswith(b'#case '):
+ incases = set(l[6:].split())
+ elif l.startswith(b'#endcase'):
+ incases = None
+ if incases is not None and self._case not in incases:
+ after.setdefault(pos, []).append(l)
+ continue
+ # normal parsing
if l.startswith(b'#require'):
lsplit = l.split()
@@ -2269,7 +2294,27 @@ class TestRunner(object):
args = os.listdir(b'.')
- return [{'path': t} for t in args
- if os.path.basename(t).startswith(b'test-')
- and (t.endswith(b'.py') or t.endswith(b'.t'))]
+ tests = []
+ for t in args:
+ if not (os.path.basename(t).startswith(b'test-')
+ and (t.endswith(b'.py') or t.endswith(b'.t'))):
+ continue
+ if t.endswith(b'.t'):
+ # a .t file may contain multiple test cases
+ # "#case X" indicates a new test case
+ cases = set()
+ try:
+ with open(t, 'rb') as f:
+ for l in f:
+ if l.startswith(b'#case '):
+ cases.update(l[6:].split())
+ except IOError:
+ pass
+ if not cases:
+ tests.append({'path': t})
+ else:
+ tests += [{'path': t, 'case': c} for c in sorted(cases)]
+ else:
+ tests.append({'path': t})
+ return tests
def _runtests(self, tests):
@@ -2277,4 +2322,7 @@ class TestRunner(object):
# convert a test back to its description dict
desc = {'path': test.path}
+ case = getattr(test, '_case', None)
+ if case:
+ desc['case'] = case
return self._gettest(desc, i)
@@ -2375,4 +2423,10 @@ class TestRunner(object):
tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
+ # extra keyword parameters. 'case' is used by .t tests
+ kwds = {}
+ for key in ['case']:
+ if key in test:
+ kwds[key] = test[key]
+
t = testcls(refpath, tmpdir,
keeptmpdir=self.options.keep_tmpdir,
@@ -2385,5 +2439,5 @@ class TestRunner(object):
hgcommand=self._hgcommand,
usechg=bool(self.options.with_chg or self.options.chg),
- useipv6=useipv6)
+ useipv6=useipv6, **kwds)
t.should_reload = True
return t
diff --git a/tests/test-run-tests.t b/tests/test-run-tests.t
--- a/tests/test-run-tests.t
+++ b/tests/test-run-tests.t
@@ -901,2 +901,51 @@ support for bisecting failed tests autom
python hash seed: * (glob)
[1]
+
+ $ cd ..
+
+Test cases in .t files
+======================
+ $ mkdir cases
+ $ cd cases
+ $ cat > test-cases-abc.t <<'EOF'
+ > #case B
+ > $ V=B
+ > #case A
+ > $ V=A
+ > #case C
+ > $ V=C
+ > #endcase
+ > $ echo $V | sed 's/A/C/'
+ > C
+ > #case C
+ > $ [ $V == C ]
+ > #case A
+ > $ [ $V == C ]
+ > [1]
+ > #case A B
+ > $ [ $V == C ]
+ > [1]
+ > #endcase
+ > $ [ $V == D ]
+ > [1]
+ > EOF
+ $ rt
+ .
+ --- $TESTTMP/anothertests/cases/test-cases-abc.t
+ +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
+ @@ -6,7 +6,7 @@
+ $ V=C
+ #endcase
+ $ echo $V | sed 's/A/C/'
+ - C
+ + B
+ #case C
+ $ [ $V == C ]
+ #case A
+
+ ERROR: test-cases-abc.t (case B) output changed
+ !.
+ Failed test-cases-abc.t (case B): output changed
+ # Ran 3 tests, 0 skipped, 0 warned, 1 failed.
+ python hash seed: * (glob)
+ [1]
More information about the Mercurial-devel
mailing list