[PATCH 2 of 2 RFC] tests: add support for inline doctests in test files

Idan Kamara idankk86 at gmail.com
Wed Oct 12 15:05:14 CDT 2011


# HG changeset patch
# User Idan Kamara <idankk86 at gmail.com>
# Date 1318449674 -7200
# Node ID 9a40d6434775313492fdda827f3eaaf9515d862c
# Parent  c3793a3a0926288e6d46db8a2dc1e571aee141a5
tests: add support for inline doctests in test files

This adds doctest like syntax to .t files, that can be interleaved with regular
shell code:

  $ echo -n a > file
  >>> print open('file').read()
  a
  >>> open('file', 'a').write('b')
  $ cat file
  ab

The syntax is exactly the same as regular doctests, so multiline statements
look like this:

  >>> for i in range(3):
  ...     print i
  0
  1
  2

Each block has its own context, i.e.:

  >>> x = 0
  >>> print x
  0
  $ echo 'foo'
  foo
  >>> print x

will result in a NameError.

Errors are displayed in standard doctest format:

  >>> print 'foo'
  bar

  --- /home/idan/dev/hg/default/tests/test-test.t
  +++ /home/idan/dev/hg/default/tests/test-test.t.err
  @@ -2,3 +2,16 @@
     > >>> print 'foo'
     > bar
     > EOF
  +  **********************************************************************
  +  File "/tmp/tmps8X_0ohg-tst", line 1, in tmps8X_0ohg-tst
  +  Failed example:
  +      print 'foo'
  +  Expected:
  +      bar
  +  Got:
  +      foo
  +  **********************************************************************
  +  1 items had failures:
  +     1 of   1 in tmps8X_0ohg-tst
  +  ***Test Failed*** 1 failures.
  +  [1]

As for the implementation, it's quite simple: when the test runner sees a line
starting with '>>>' it converts it, and all subsequent lines until the next
line that begins with '$' to a 'python -m heredoctest <<EOF' call with the
proper heredoc to follow. So if we have this test file:

  >>> for c in 'abcd':
  ...     print c
  a
  b
  c
  d
  $ echo foo
  foo

It gets converted to:

  $ python -m heredoctest <<EOF
  > >>> for c in 'abcd':
  > ...     print c
  > a
  > b
  > c
  > d
  > EOF
  $ echo foo
  foo

And then processed like every other test file by converting it to a sh script.

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -521,6 +521,26 @@
 def stringescape(s):
     return escapesub(escapef, s)
 
+def transformtst(lines):
+    inblock = False
+    for l in lines:
+        if inblock:
+            if l.startswith('  $ '):
+                inblock = False
+                yield '  > EOF\n'
+                yield l
+            else:
+                yield '  > ' + l[2:]
+        else:
+            if l.startswith('  >>> '):
+                inblock = True
+                yield '  $ %s -m heredoctest <<EOF\n' % PYTHON
+                yield '  > ' + l[2:]
+            else:
+                yield l
+    if inblock:
+        yield '  > EOF\n'
+
 def tsttest(test, wd, options, replacements):
     t = open(test)
     out = []
@@ -530,7 +550,7 @@
     pos = prepos = -1
     after = {}
     expected = {}
-    for n, l in enumerate(t):
+    for n, l in enumerate(transformtst(t)):
         if not l.endswith('\n'):
             l += '\n'
         if l.startswith('  $ '): # commands
@@ -833,7 +853,7 @@
         refout = None                   # to match "out is None"
     elif os.path.exists(ref):
         f = open(ref, "r")
-        refout = splitnewlines(f.read())
+        refout = list(transformtst(splitnewlines(f.read())))
         f.close()
     else:
         refout = []


More information about the Mercurial-devel mailing list