fixed a typo...
[pli.git] / pli / testlog.py
blob1aac854068a5f2cb165e38501f5484236dcda46d
1 #=======================================================================
3 __version__ = '''0.0.01'''
4 __sub_version__ = '''20121219035042'''
5 __copyright__ = '''(c) Alex A. Naanou 2003'''
8 #-----------------------------------------------------------------------
10 import sys
11 from pprint import pprint, pformat
14 #-----------------------------------------------------------------------
16 INDENT = 2
17 TERM_WIDTH = 80
19 TEST_FILE_NAME = '<test>'
20 REPR_FUNCTION = repr
22 MUTE_PREFIX = '!'
23 PPRINT_PREFIX = '>>>'
26 #-----------------------------------------------------------------------
28 # XXX the script does not see the imported modules...
29 # from pprint import pprint
30 # logstr(''''
31 # ## this will priduce a name error!
32 # ! pprint
33 # '')
35 # there are cases when this works...
38 # TODO add error log support...
39 # TODO exception handling both in testing and in failures....
40 # TODO add more clever output handling...
41 # for things like:
42 # logstr(''''
43 # print 123
44 # pprint(321)
45 # '')
46 # TODO add multiline things like if, for, ...
47 # TODO error reporting should generate something not only readable but
48 # reproducable, at this poit it will print the actual result
49 # rather than the expected, which will change the semantics of the
50 # output...
53 #-----------------------------------------------------------------------
54 #-----------------------------------------------------------------log---
55 # XXX rewrite...
56 def log(*cmd, **kw):
57 '''
58 '''
59 depth = kw.pop('depth', 1)
60 rep = kw.pop('repr', REPR_FUNCTION)
61 mute = kw.pop('mute', False)
63 filename = kw.pop('filename', TEST_FILE_NAME)
64 mute_prefix = kw.pop('mute_prefix', MUTE_PREFIX)
66 lcl = sys._getframe(depth).f_locals
67 glbl = sys._getframe(depth).f_globals
68 res = None
69 err = None
71 code = '%(indent)s %(mute_prefix)s%(command)s%(result)s'
72 data = {
73 'indent': '',
74 'mute_prefix': '',
75 'command': '',
76 'result': '',
79 data['indent'] = ' '*(INDENT-1)
80 if len(cmd) == 1:
81 if mute:
82 data['mute_prefix'] = mute_prefix
83 data['command'] = cmd[0].strip()
84 try:
85 ##!!! add exception handling...
86 res = eval(compile(cmd[0].strip(), filename, 'eval'), glbl, lcl)
87 if not mute:
88 if len(cmd[0].strip()) + INDENT >= 8:
89 data['result'] = '\n\t-> %s\n' % rep(res)
90 else:
91 data['result'] = '\t-> %s\n' % rep(res)
92 else:
93 data['result'] = '\n'
94 # we've got a statement...
95 except SyntaxError:
96 # XXX need a more robust way to do this...
97 ##!!! add exception handling...
98 eval(compile(cmd[0].strip(), filename, 'exec'), glbl, lcl)
99 code += ' \n'
100 # we've got an exception...
101 except Exception, e:
102 err = e
103 # XXX figure out a better syntax to represent exceptions...
104 data['result'] = '\n\t-X-> %s\n' % rep(err)
105 elif len(cmd) > 1:
106 if mute:
107 data['mute_prefix'] = mute_prefix
108 data['command'] = ''.join([c.strip() for c in cmd]) + cmd[-1].strip()
109 ##!!! add exception handling...
110 res = eval(compile(cmd[-1].strip(), filename, 'eval'), glbl, lcl)
111 if not mute:
112 if len(cmd[-1].strip()) + INDENT >= 8:
113 data['result'] = '\n\t->%s\n' % rep(res)
114 else:
115 data['result'] = '\t->%s\n' % rep(res)
116 else:
117 data['result'] = '\n'
118 else:
119 code += '\n'
121 return code % data, res, err
124 #----------------------------------------------------------------test---
125 def test(*cmd, **kw):
126 expected, cmd = cmd[-1], cmd[:-1]
127 expected_err = kw.pop('expected_err', None)
128 depth = kw.pop('depth', 1)
129 rep = kw.pop('repr', REPR_FUNCTION)
130 code, res, err = log(depth=depth+1, *cmd)
132 ##!!! for some reason, if we have an exception, we do not reach this spot...
134 if err is not None:
135 if expected_err is None:
136 raise err
137 if expected_err == err:
138 ##!!! stub...
139 res = err
140 ##!!! stub...
141 res = err
142 text = code
143 if res != expected:
144 text += '\t## Error: result did not match the expected: %s' % rep(expected)
145 return False, text
146 return True, text
149 #--------------------------------------------------------pretty_print---
150 ##!!! revise...
151 # XXX this is the same as log but with pretty printing, need to
152 # redesign this to be more like a mixin to the log...
153 def pretty_print(*cmd, **kw):
156 NOTE: this will print the value in a non-printable comment so as to be
157 self-applicamle -- currently multiline structures are not supported.
159 code = cmd[0]
160 depth = kw.pop('depth', 1)
161 rep = kw.pop('repr', REPR_FUNCTION)
162 filename = kw.pop('filename', TEST_FILE_NAME)
163 lcl = sys._getframe(depth).f_locals
164 glbl = sys._getframe(depth).f_globals
166 text = '%s %s' % (PPRINT_PREFIX, code)
167 ##!!! add exception handling...
168 res = pformat(eval(compile(cmd[0].strip(), filename, 'eval'), glbl, lcl), width=80-8-3)
169 text += '%s %s' % ('\n##\t->', '\n##\t '.join(res.split('\n')))
170 return text
173 #------------------------------------------------------------loglines---
174 def loglines(*lines, **kw):
177 depth = kw.pop('depth', 1)
178 mute_prefix = kw.pop('mute_prefix', MUTE_PREFIX)
179 pprint_prefix = kw.pop('pprint_prefix', PPRINT_PREFIX)
181 for line in lines:
183 if line.strip().startswith('##'):
184 continue
186 if line.strip().startswith('#'):
187 print (' '*INDENT) + line.strip()
188 continue
190 if line.strip().startswith('---'):
191 print '-'*(TERM_WIDTH-1)
192 continue
194 if line.strip().startswith('==='):
195 print '='*(TERM_WIDTH-1)
196 continue
198 if line.strip().startswith(pprint_prefix):
199 pretty_print(line.strip()[len(pprint_prefix):],
200 depth=depth+1,
201 pprint_prefix=pprint_prefix,
202 mute_prefix=mute_prefix,
203 **kw)
204 continue
206 if line.strip().startswith(mute_prefix):
207 print log(line.strip()[len(mute_prefix):],
208 depth=depth+1,
209 mute=True,
210 pprint_prefix=pprint_prefix,
211 mute_prefix=mute_prefix,
212 **kw)[0]
213 continue
215 if type(line) in (str, unicode):
216 if line.strip() == '':
217 print
218 continue
219 print log(line, depth=depth+1, **kw)[0]
220 elif type(line) is tuple:
221 test(depth=depth+1, *line, **kw)
222 else:
223 raise TypeError, 'unsupported line type (can handle strings and tuples, got: %s)' % type(line)
224 print
227 #-----------------------------------------------------------loglines2---
228 ##!!! BUG:
229 # XXX if '->' is somewhare inside a string in a test string then we are in trouble :)
230 # e.g. the next line will die with a 'support only one "->" per line' error
231 # 'f("->") -> "some string"'
232 ##!!! make this extend loglines rather than copy most of the functionality....
233 def loglines2(*lines, **kw):
236 depth = kw.pop('depth', 1)
237 mute_prefix = kw.pop('mute_prefix', MUTE_PREFIX)
238 pprint_prefix = kw.pop('pprint_prefix', PPRINT_PREFIX)
239 only_errors = kw.pop('only_errors', False)
241 line_count = 0
242 lines_failed = 0
244 for line in lines:
246 if line.strip().startswith('##'):
247 continue
249 if line.strip().startswith('#'):
250 if not only_errors:
251 yield (' '*INDENT) + line.strip()
252 continue
254 if line.strip().startswith('---'):
255 if not only_errors:
256 yield '-'*(TERM_WIDTH-1)
257 continue
259 if line.strip().startswith('==='):
260 if not only_errors:
261 yield '='*(TERM_WIDTH-1)
262 continue
264 if line.strip().startswith(pprint_prefix):
265 if not only_errors:
266 yield pretty_print(line.strip()[len(pprint_prefix):],
267 depth=depth+1,
268 pprint_prefix=pprint_prefix,
269 mute_prefix=mute_prefix,
270 **kw)
271 continue
273 if line.strip().startswith(mute_prefix):
274 line_count += 1
275 res = log(line.strip()[len(mute_prefix):],
276 depth=depth+1,
277 mute=True,
278 pprint_prefix=pprint_prefix,
279 mute_prefix=mute_prefix,
280 **kw)[0]
281 if not only_errors:
282 yield res
283 continue
285 line = line.split('->')
286 if len(line) == 1:
287 line = line[0]
288 if line.strip() == '':
289 if not only_errors:
290 yield ''
291 continue
292 line_count += 1
293 res = log(line, depth=depth+1, **kw)[0]
294 if not only_errors:
295 yield res
296 elif len(line) == 2:
297 line_count += 1
298 ##!!! add exception handling...
299 res, text = test(line[0], eval(line[1]), depth=depth+1, **kw)
300 if not res:
301 lines_failed += 1
302 if not only_errors or not res:
303 yield text
304 else:
305 raise TypeError, 'support only one "->" per line'
306 yield ''
308 yield {
309 'lines': line_count,
310 'fails': lines_failed,
314 #--------------------------------------------------------------logstr---
315 def logstr(text, **kw):
318 depth = kw.pop('depth', 1)
319 stats = kw.pop('print_stats', True)
321 strs = []
322 for s in text.split('\n'):
323 if s.strip().startswith('->'):
324 strs[-1] += s
325 else:
326 strs += [s]
327 for l in loglines2(depth=depth+1, *strs, **kw):
328 if type(l) not in (str, unicode):
329 if stats and l['lines'] > 0:
330 print (' '*INDENT) + '## executed %(lines)s lines, of which %(fails)s failed.' % l
331 else:
332 print l
336 #-----------------------------------------------------------------------
337 if __name__ == '__main__':
338 from pprint import pprint
339 test_code = '''
340 # this module will define a special DSL based on python. this
341 # language is designed to facilitate module self-testing.
343 # this module can be considered as a usage example. below you see
344 # the lines that both demo and test the functionality of the
345 # module.
347 # comments starting with a double '#' are not shown...
348 ## here's is an example...
350 # basic comment;
351 # NOTE: indent is ignored...
352 # ...but this does not concern comment formatting.
354 # next, a few empty lines...
358 # now an expression...
359 1 + 1
361 # an expression with a test value...
362 1 + 1 -> 2
364 # we can put the expected result on a separate line...
365 2 * 3
366 -> 6
368 # an expression that will fail it's value test...
369 1 * 1 -> 2
370 ## this will break...
372 a = 1
374 # now we can test the value...
375 a -> 1
377 # we can also test for fails...
381 ## -X-> ZeroDivisionError
384 # pretty printing...
385 >>> {1:range(10), 2:range(10), 3:range(10)}
388 # it is also possible to mute result output...
389 ! {1:range(10), 2:range(10), 3:range(10)}
392 # now for some basic markup...
393 # we can do basic lines...
397 # NOTE: one can have more than three dashes, but not less... two
398 # dashes will be passed to python and thus generate a syntax
399 # error.
400 --------
402 # and we can print only errors (same code as up to this point
403 # re-run with errors_only option set)...
406 logstr(test_code, print_stats=False)
408 # enable only error printing...
409 logstr(test_code + '''\n\t# we can also print exec stats...''',
410 print_stats=True, only_errors=True)
412 logstr('''
415 # statements are supported too, but only if no expected result is
416 # given...
417 # NOTE: it is best to avoid things that print things, they will
418 # generate output that is not a valid test script.
419 print '!!!'
422 # oh, and did I mention that logstr is self-applicable?
423 # ...well it is! that is if you avoud mixing the code with prints ;)
426 # that's all at this point.
427 ''')
431 #=======================================================================
432 # vim:set ts=4 sw=4 nowrap :