tests/run-tests.py
changeset 1004 b675519e2a5b
parent 1003 59bd7f017103
child 1005 d86b26020b48
--- a/tests/run-tests.py	Mon Oct 24 17:02:10 2011 +0100
+++ b/tests/run-tests.py	Mon Nov 07 13:52:03 2011 +0000
@@ -521,60 +521,115 @@
 def stringescape(s):
     return escapesub(escapef, s)
 
-def transformtst(lines):
-    inblock = False
-    for l in lines:
-        if inblock:
-            if l.startswith('  $ ') or not l.startswith('  '):
-                inblock = False
-                yield '  > EOF\n'
-                yield l
-            else:
-                yield '  > ' + l[2:]
+def rematch(el, l):
+    try:
+        # ensure that the regex matches to the end of the string
+        return re.match(el + r'\Z', l)
+    except re.error:
+        # el is an invalid regex
+        return False
+
+def globmatch(el, l):
+    # The only supported special characters are * and ?. Escaping is
+    # supported.
+    i, n = 0, len(el)
+    res = ''
+    while i < n:
+        c = el[i]
+        i += 1
+        if c == '\\' and el[i] in '*?\\':
+            res += el[i - 1:i + 1]
+            i += 1
+        elif c == '*':
+            res += '.*'
+        elif c == '?':
+            res += '.'
         else:
-            if l.startswith('  >>> '):
-                inblock = True
-                yield '  $ %s -m heredoctest <<EOF\n' % PYTHON
-                yield '  > ' + l[2:]
-            else:
-                yield l
-    if inblock:
-        yield '  > EOF\n'
+            res += re.escape(c)
+    return rematch(res, l)
+
+def linematch(el, l):
+    if el == l: # perfect match (fast)
+        return True
+    if (el and
+        (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', l) or
+         el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', l) or
+         el.endswith(" (esc)\n") and el.decode('string-escape') == l)):
+        return True
+    return False
 
 def tsttest(test, wd, options, replacements):
-    t = open(test)
-    out = []
-    script = []
+    # We generate a shell script which outputs unique markers to line
+    # up script results with our source. These markers include input
+    # line number and the last return code
     salt = "SALT" + str(time.time())
+    def addsalt(line):
+        script.append('echo %s %s $?\n' % (salt, line))
+
+    # After we run the shell script, we re-unify the script output
+    # with non-active parts of the source, with synchronization by our
+    # SALT line number markers. The after table contains the
+    # non-active components, ordered by line number
+    after = {}
+    pos = prepos = -1
 
-    pos = prepos = -1
-    after = {}
+    # Expected shellscript output
     expected = {}
-    for n, l in enumerate(transformtst(t)):
+
+    # We keep track of whether or not we're in a Python block so we
+    # can generate the surrounding doctest magic
+    inpython = False
+
+    f = open(test)
+    t = f.readlines()
+    f.close()
+
+    script = []
+    for n, l in enumerate(t):
         if not l.endswith('\n'):
             l += '\n'
-        if l.startswith('  $ '): # commands
+        if l.startswith('  >>> '): # python inlines
+            if not inpython:
+                # we've just entered a Python block, add the header
+                inpython = True
+                addsalt(n)
+                script.append('%s -m heredoctest <<EOF\n' % PYTHON)
+                prepos = pos
+                pos = n
+            after.setdefault(prepos, []).append(l)
+            script.append(l[2:])
+        elif l.startswith('  $ '): # commands
+            if inpython:
+                script.append("EOF\n")
+                inpython = False
             after.setdefault(pos, []).append(l)
             prepos = pos
             pos = n
-            script.append('echo %s %s $?\n' % (salt, n))
+            addsalt(n)
             script.append(l[4:])
         elif l.startswith('  > '): # continuations
             after.setdefault(prepos, []).append(l)
             script.append(l[4:])
         elif l.startswith('  '): # results
-            # queue up a list of expected results
-            expected.setdefault(pos, []).append(l[2:])
+            if inpython:
+                script.append(l[2:])
+                after.setdefault(prepos, []).append(l)
+            else:
+                # queue up a list of expected results
+                expected.setdefault(pos, []).append(l[2:])
         else:
+            if inpython:
+                script.append("EOF\n")
+                inpython = False
             # non-command/result - queue up for merged output
             after.setdefault(pos, []).append(l)
 
-    t.close()
+    if inpython:
+        script.append("EOF\n")
+    addsalt(n + 1)
 
-    script.append('echo %s %s $?\n' % (salt, n + 1))
-
+    # Write out the script and execute it
     fd, name = tempfile.mkstemp(suffix='hg-tst')
-
     try:
         for l in script:
             os.write(fd, l)
@@ -590,32 +645,7 @@
     finally:
         os.remove(name)
 
-    def rematch(el, l):
-        try:
-            # ensure that the regex matches to the end of the string
-            return re.match(el + r'\Z', l)
-        except re.error:
-            # el is an invalid regex
-            return False
-
-    def globmatch(el, l):
-        # The only supported special characters are * and ?. Escaping is
-        # supported.
-        i, n = 0, len(el)
-        res = ''
-        while i < n:
-            c = el[i]
-            i += 1
-            if c == '\\' and el[i] in '*?\\':
-                res += el[i - 1:i + 1]
-                i += 1
-            elif c == '*':
-                res += '.*'
-            elif c == '?':
-                res += '.'
-            else:
-                res += re.escape(c)
-        return rematch(res, l)
+    # Merge the script output back into a unified test
 
     pos = -1
     postout = []
@@ -627,20 +657,16 @@
 
         if lout:
             if lcmd:
+                # output block had no trailing newline, clean up
                 lout += ' (no-eol)\n'
 
+            # find the expected output at the current position
             el = None
             if pos in expected and expected[pos]:
                 el = expected[pos].pop(0)
 
-            if el == lout: # perfect match (fast)
-                postout.append("  " + lout)
-            elif (el and
-                  (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', lout) or
-                   el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', lout)
-                   or el.endswith(" (esc)\n") and
-                      el.decode('string-escape') == l)):
-                postout.append("  " + el) # fallback regex/glob/esc match
+            if linematch(el, lout):
+                postout.append("  " + el)
             else:
                 if needescape(lout):
                     lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
@@ -652,6 +678,7 @@
             if ret != 0:
                 postout.append("  [%s]\n" % ret)
             if pos in after:
+                # merge in non-active test bits
                 postout += after.pop(pos)
             pos = int(lcmd.split()[0])
 
@@ -853,7 +880,7 @@
         refout = None                   # to match "out is None"
     elif os.path.exists(ref):
         f = open(ref, "r")
-        refout = list(transformtst(splitnewlines(f.read())))
+        refout = list(splitnewlines(f.read()))
         f.close()
     else:
         refout = []