diff -r 5c25b505ffc1 -r b675519e2a5b tests/run-tests.py --- 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 < ' + 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 < '): # 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 = []