D4049: testrunner: allow multiple #testcases

martinvonz (Martin von Zweigbergk) phabricator at mercurial-scm.org
Wed Aug 1 23:53:04 UTC 2018


martinvonz created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This lets you have multiple #testcases declarations and they're taken
  to be different dimensions of the test, so their cross product becomes
  the total set of test cases. For example:
  
    #testcases obsstore-on obsstore-off
    #testcases manifest-flat manifest-tree
    
      $ hg rebase ...
      ...
      #if obsstore-on
        $ hg log ...
      #endif
  
  Note that this is an excellent way to slow down the test suite, so use
  it with care.
  
  The feature is implemented by replacing most of the "case" variables
  that were strings before by an array of strings with each item a
  different dimension of the test case. The file names are created by
  joining the dimensions by ":"
  (e.g. test-foo.t.obsstore-on:manifest-flat).

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D4049

AFFECTED FILES
  tests/run-tests.py
  tests/test-run-tests.t

CHANGE DETAILS

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
@@ -896,6 +896,20 @@
   ..
   # Ran 2 tests, 0 skipped, 0 failed.
 
+When using multiple dimensions of "#testcases" in .t files
+
+  $ cat > test-cases.t <<'EOF'
+  > #testcases a b
+  > #testcases c d
+  > #if a d
+  >   $ echo $TESTCASE
+  >   a:d
+  > #endif
+  > EOF
+  $ rt test-cases.t
+  ....
+  # Ran 4 tests, 0 skipped, 0 failed.
+
   $ rm test-cases.t
 
 (reinstall)
diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -285,12 +285,12 @@
 
     If path does not exist, return an empty set.
     """
-    cases = set()
+    cases = []
     try:
         with open(path, 'rb') as f:
             for l in f:
                 if l.startswith(b'#testcases '):
-                    cases.update(l[11:].split())
+                    cases.append(sorted(l[11:].split()))
     except IOError as ex:
         if ex.errno != errno.ENOENT:
             raise
@@ -1242,14 +1242,15 @@
 
     def __init__(self, path, *args, **kwds):
         # accept an extra "case" parameter
-        case = kwds.pop('case', None)
+        case = kwds.pop('case', [])
         self._case = case
-        self._allcases = parsettestcases(path)
+        self._allcases = {x for y in parsettestcases(path) for x in y}
         super(TTest, self).__init__(path, *args, **kwds)
         if case:
-            self.name = '%s#%s' % (self.name, _strpath(case))
-            self.errpath = b'%s.%s.err' % (self.errpath[:-4], case)
-            self._tmpname += b'-%s' % case
+            casepath = _strpath(':'.join(case))
+            self.name = '%s#%s' % (self.name, casepath)
+            self.errpath = b'%s.%s.err' % (self.errpath[:-4], casepath)
+            self._tmpname += b'-%s' % casepath
         self._have = {}
 
     @property
@@ -1323,10 +1324,10 @@
         reqs = []
         for arg in args:
             if arg.startswith(b'no-') and arg[3:] in self._allcases:
-                if arg[3:] == self._case:
+                if arg[3:] in self._case:
                     return False
             elif arg in self._allcases:
-                if arg != self._case:
+                if arg not in self._case:
                     return False
             else:
                 reqs.append(arg)
@@ -1370,10 +1371,11 @@
         if os.getenv('MSYSTEM'):
             script.append(b'alias pwd="pwd -W"\n')
         if self._case:
+            casestr = ':'.join(self._case)
             if isinstance(self._case, str):
-                quoted = shellquote(self._case)
+                quoted = shellquote(casestr)
             else:
-                quoted = shellquote(self._case.decode('utf8')).encode('utf8')
+                quoted = shellquote(casestr.decode('utf8')).encode('utf8')
             script.append(b'TESTCASE=%s\n' % quoted)
             script.append(b'export TESTCASE\n')
 
@@ -2666,31 +2668,42 @@
                 expanded_args.append(arg)
         args = expanded_args
 
-        testcasepattern = re.compile(br'([\w-]+\.t|py)(#([a-zA-Z0-9_\-\.]+))')
+        testcasepattern = re.compile(br'([\w-]+\.t|py)(#([a-zA-Z0-9_\-\.:]+))')
         tests = []
         for t in args:
-            case = None
+            case = []
 
             if not (os.path.basename(t).startswith(b'test-')
                     and (t.endswith(b'.py') or t.endswith(b'.t'))):
 
                 m = testcasepattern.match(t)
                 if m is not None:
-                    t, _, case = m.groups()
+                    t, _, casestr = m.groups()
+                    if casestr:
+                        case = casestr.split(':')
                 else:
                     continue
 
             if t.endswith(b'.t'):
                 # .t file may contain multiple test cases
-                cases = sorted(parsettestcases(t))
-                if cases:
-                    if case is not None and case in cases:
-                        tests += [{'path': t, 'case': case}]
-                    elif case is not None and case not in cases:
+                casedimensions = parsettestcases(t)
+                if casedimensions:
+                    cases = []
+                    def addcases(case, casedimensions):
+                        if not casedimensions:
+                            cases.append(case)
+                        else:
+                            for c in casedimensions[0]:
+                                addcases(case + [c], casedimensions[1:])
+                    addcases([], casedimensions)
+                    if case and case in cases:
+                        cases = [case]
+                    elif case:
                         # Ignore invalid cases
-                        pass
+                        cases = []
                     else:
-                        tests += [{'path': t, 'case': c} for c in sorted(cases)]
+                       pass
+                    tests += [{'path': t, 'case': c} for c in sorted(cases)]
                 else:
                     tests.append({'path': t})
             else:
@@ -2701,7 +2714,7 @@
         def _reloadtest(test, i):
             # convert a test back to its description dict
             desc = {'path': test.path}
-            case = getattr(test, '_case', None)
+            case = getattr(test, '_case', [])
             if case:
                 desc['case'] = case
             return self._gettest(desc, i)
@@ -2713,7 +2726,8 @@
                     desc = testdescs[0]
                     # desc['path'] is a relative path
                     if 'case' in desc:
-                        errpath = b'%s.%s.err' % (desc['path'], desc['case'])
+                        casestr = ':'.join(desc['case'])
+                        errpath = b'%s.%s.err' % (desc['path'], casestr)
                     else:
                         errpath = b'%s.err' % desc['path']
                     errpath = os.path.join(self._outputdir, errpath)



To: martinvonz, #hg-reviewers
Cc: mercurial-devel


More information about the Mercurial-devel mailing list