1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
#!/usr/bin/python
#
# Test fragments of example code to ensure they behave as
# advertised.
#
# Functionally, this is similar to doctest, however our input
# is more regular than that seen by doctest, and therefore our
# parsing is more relaxed.
#
import sys
import os
import traceback
import StringIO
# Can't use sys.ps1, sys.ps2 as these are only defined for interactive sessions
PS1 = '>>>'
PS2 = '...'
class ExampleFailure(Exception):
pass
class ExampleCmd:
def __init__(self, linenum, line):
self.linenum = linenum
self.cmds = [line]
self.expect = []
def add_continuation(self, line):
self.cmds.append(line)
def has_output(self):
return bool(self.expect)
def add_output(self, line):
self.expect.append(line)
def run(self, namespace):
got = None
expect = ''.join(self.expect)
src = ''.join(self.cmds)
try:
saved_stdout = sys.stdout
sys.stdout = StringIO.StringIO()
try:
code = compile(src, '<string>', 'single')
exec code in namespace
got = sys.stdout.getvalue()
finally:
sys.stdout = saved_stdout
except:
# Is it an expected exception?
if ('Traceback (innermost last):\n' in expect or
'Traceback (most recent call last):\n' in expect):
# Only compare exception type and value - the rest of
# the traceback isn't necessary.
exc_type, exc_val = sys.exc_info()[:2]
got = traceback.format_exception_only(exc_type, exc_val)[-1]
expect = self.expect[-1]
else:
# Unexpected exception - print something useful
exc_type, exc_val, exc_tb = sys.exc_info()
exc_tb = exc_tb.tb_next
psrc = PS1 + " " + src.rstrip().replace("\n", PS2 + " ")
pexp = traceback.format_exception(exc_type, exc_val, exc_tb)
pexp = ''.join(pexp).rstrip()
raise ExampleFailure('Line %d:\n%s\n%s' %
(self.linenum, psrc, pexp))
if got != expect:
raise ExampleFailure('Line %d: output does not match example\n'
'Expected:\n%s\nGot:\n%s' %
(self.linenum, expect, got))
class ExampleTest:
def __init__(self, filename):
self.filename = filename
self.result = ""
self.example = []
def report(self):
if self.result:
print "=" * 70
print "Testing \"%s\" failed," % self.filename,
print self.result
print
def test(self):
try:
if not self.example:
self.load_and_parse()
self.result = ""
self.execute_verify_output()
except ExampleFailure, msg:
self.result = msg
return self.result == ""
def load_and_parse(self):
self.example = []
try:
f = open(self.filename)
except IOError, (eno, estr):
raise ExampleFailure("could not load: %s" % estr)
try:
for linenum, line in enumerate(f):
if line.startswith(PS1):
self.example.append(ExampleCmd(linenum + 1,
line[len(PS1)+1:]))
elif line.startswith(PS2):
if not self.example or self.example[-1].has_output():
raise ExampleFailure("Line %d: %r line with no preceeding %r line" % (linenum+1, PS2, PS1))
self.example[-1].add_continuation(line[len(PS2)+1:])
else:
if not self.example:
raise ExampleFailure("Line %d: output with no preceeding %r line" % (linenum+1, PS1))
self.example[-1].add_output(line)
finally:
f.close()
def execute_verify_output(self):
namespace = {}
for cmd in self.example:
cmd.run(namespace)
def files_from_directory(dir):
filenames = []
for fn in os.listdir(dir):
filename = os.path.join(dir, fn)
if fn[0] != '.' and os.path.isfile(filename) \
and not filename.endswith('.py'):
filenames.append(filename)
filenames.sort()
return filenames
def main():
if len(sys.argv) > 1:
filenames = sys.argv[1:]
else:
filenames = files_from_directory("doctest")
fail_cnt = total_cnt = 0
failures = []
for filename in filenames:
if os.path.isfile(filename):
total_cnt += 1
e = ExampleTest(filename)
if not e.test():
failures.append(e)
fail_cnt += 1
sys.stdout.write("!")
else:
sys.stdout.write(".")
sys.stdout.flush()
sys.stdout.write("\n")
for e in failures:
e.report()
print "%d of %d tests failed" % (fail_cnt, total_cnt)
sys.exit(fail_cnt > 0)
if __name__ == '__main__':
main()
|