##// END OF EJS Templates
Add 'float' option to @doctest pseudo-decorator.
chebee7i -
Show More
@@ -69,7 +69,6 b' An example usage of the directive is:'
69
69
70 In [3]: print(y)
70 In [3]: print(y)
71
71
72
73 See http://matplotlib.org/sampledoc/ipython_directive.html for more additional
72 See http://matplotlib.org/sampledoc/ipython_directive.html for more additional
74 documentation.
73 documentation.
75
74
@@ -133,6 +132,28 b' COMMENT, INPUT, OUTPUT = range(3)'
133 #-----------------------------------------------------------------------------
132 #-----------------------------------------------------------------------------
134 # Functions and class declarations
133 # Functions and class declarations
135 #-----------------------------------------------------------------------------
134 #-----------------------------------------------------------------------------
135 def str_to_array(s):
136 """
137 Simplistic converter of strings from repr to float NumPy arrays.
138
139 """
140 import numpy as np
141
142 # Handle infs (assumes default printoptions for NumPy)
143 s = s.replace(u'inf', u'np.inf')
144 s = s.replace(u'nan', u'np.nan')
145
146 if s.startswith(u'array'):
147 # Remove array( and )
148 s = s[6:-1]
149
150 if s.startswith(u'['):
151 a = np.array(eval(s), dtype=float)
152 else:
153 # Assume its a regular float. Force 1D so we can index into it.
154 a = np.atleast_1d(float(s))
155 return a
156
136 def block_parser(part, rgxin, rgxout, fmtin, fmtout):
157 def block_parser(part, rgxin, rgxout, fmtin, fmtout):
137 """
158 """
138 part is a string of ipython text, comprised of at most one
159 part is a string of ipython text, comprised of at most one
@@ -339,7 +360,8 b' class EmbeddedSphinxShell(object):'
339 image_directive = None
360 image_directive = None
340 #print 'INPUT:', data # dbg
361 #print 'INPUT:', data # dbg
341 is_verbatim = decorator=='@verbatim' or self.is_verbatim
362 is_verbatim = decorator=='@verbatim' or self.is_verbatim
342 is_doctest = decorator=='@doctest' or self.is_doctest
363 is_doctest = (decorator is not None and \
364 decorator.startswith('@doctest')) or self.is_doctest
343 is_suppress = decorator=='@suppress' or self.is_suppress
365 is_suppress = decorator=='@suppress' or self.is_suppress
344 is_savefig = decorator is not None and \
366 is_savefig = decorator is not None and \
345 decorator.startswith('@savefig')
367 decorator.startswith('@savefig')
@@ -396,38 +418,38 b' class EmbeddedSphinxShell(object):'
396 ret.append('')
418 ret.append('')
397
419
398 self.cout.truncate(0)
420 self.cout.truncate(0)
399 return (ret, input_lines, output, is_doctest, image_file,
421 return (ret, input_lines, output, is_doctest, decorator, image_file,
400 image_directive)
422 image_directive)
401 #print 'OUTPUT', output # dbg
423 #print 'OUTPUT', output # dbg
402
424
403 def process_output(self, data, output_prompt,
425 def process_output(self, data, output_prompt,
404 input_lines, output, is_doctest, image_file):
426 input_lines, output, is_doctest, decorator, image_file):
405 """Process data block for OUTPUT token."""
427 """Process data block for OUTPUT token."""
406 if is_doctest:
428 if is_doctest and output is not None:
407 submitted = data.strip()
408 found = output
409 if found is not None:
410 found = found.strip()
411
412 # XXX - fperez: in 0.11, 'output' never comes with the prompt
413 # in it, just the actual output text. So I think all this code
414 # can be nuked...
415
416 # the above comment does not appear to be accurate... (minrk)
417
429
418 ind = found.find(output_prompt)
430 found = output
419 if ind<0:
431 found = found.strip()
420 e='output prompt="%s" does not match out line=%s' % \
432 submitted = data.strip()
421 (output_prompt, found)
422 raise RuntimeError(e)
423 found = found[len(output_prompt):].strip()
424
433
425 if found!=submitted:
434 # Make sure the output contains the output prompt.
426 e = ('doctest failure for input_lines="%s" with '
435 ind = found.find(output_prompt)
427 'found_output="%s" and submitted output="%s"' %
436 if ind < 0:
428 (input_lines, found, submitted) )
437 e = ('output prompt="{0}" does '
438 'not match out line={1}'.format(output_prompt, found))
439 raise RuntimeError(e)
440 found = found[len(output_prompt):].strip()
441
442 # Handle the actual doctest comparison.
443 if decorator.strip() == '@doctest':
444 # Standard doctest
445 if found != submitted:
446 e = ('doctest failure for input_lines="{0}" with '
447 'found_output="{1}" and submitted '
448 'output="{2}"'.format(input_lines, found, submitted) )
429 raise RuntimeError(e)
449 raise RuntimeError(e)
430 #print 'doctest PASSED for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted)
450 else:
451 self.specialized_doctest(decorator, input_lines,
452 found, submitted)
431
453
432 def process_comment(self, data):
454 def process_comment(self, data):
433 """Process data fPblock for COMMENT token."""
455 """Process data fPblock for COMMENT token."""
@@ -448,7 +470,6 b' class EmbeddedSphinxShell(object):'
448 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
470 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
449 self.clear_cout()
471 self.clear_cout()
450
472
451
452 def process_block(self, block):
473 def process_block(self, block):
453 """
474 """
454 process block from the block_parser and return a list of processed lines
475 process block from the block_parser and return a list of processed lines
@@ -467,14 +488,14 b' class EmbeddedSphinxShell(object):'
467 if token==COMMENT:
488 if token==COMMENT:
468 out_data = self.process_comment(data)
489 out_data = self.process_comment(data)
469 elif token==INPUT:
490 elif token==INPUT:
470 (out_data, input_lines, output, is_doctest, image_file,
491 (out_data, input_lines, output, is_doctest, decorator,
471 image_directive) = \
492 image_file, image_directive) = \
472 self.process_input(data, input_prompt, lineno)
493 self.process_input(data, input_prompt, lineno)
473 elif token==OUTPUT:
494 elif token==OUTPUT:
474 out_data = \
495 out_data = \
475 self.process_output(data, output_prompt,
496 self.process_output(data, output_prompt,
476 input_lines, output, is_doctest,
497 input_lines, output, is_doctest,
477 image_file)
498 decorator, image_file)
478 if out_data:
499 if out_data:
479 ret.extend(out_data)
500 ret.extend(out_data)
480
501
@@ -489,10 +510,11 b' class EmbeddedSphinxShell(object):'
489 return
510 return
490 self.process_input_line('import matplotlib.pyplot as plt',
511 self.process_input_line('import matplotlib.pyplot as plt',
491 store_history=False)
512 store_history=False)
513 self._pyplot_imported = True
492
514
493 def process_pure_python(self, content):
515 def process_pure_python(self, content):
494 """
516 """
495 content is a list of strings. it is unedited directive conent
517 content is a list of strings. it is unedited directive content
496
518
497 This runs it line by line in the InteractiveShell, prepends
519 This runs it line by line in the InteractiveShell, prepends
498 prompts as needed capturing stderr and stdout, then returns
520 prompts as needed capturing stderr and stdout, then returns
@@ -568,6 +590,57 b' class EmbeddedSphinxShell(object):'
568
590
569 return output
591 return output
570
592
593 def specialized_doctest(self, decorator, input_lines, found, submitted):
594 """
595 Perform a specialized doctest.
596
597 """
598 # Requires NumPy
599 import numpy as np
600
601 valid_types = set(['float'])
602
603 args = decorator.split()
604
605 doctest_type = args[1]
606 if doctest_type not in valid_types:
607 e = "Invalid option to @doctest: {0}".format(doctest_type)
608 raise Exception(e)
609
610 if len(args) == 2:
611 rtol = 1e-05
612 atol = 1e-08
613 else:
614 # Both must be specified if any are specified.
615 try:
616 rtol = float(args[2])
617 atol = float(args[3])
618 except IndexError:
619 e = ("Both `rtol` and `atol` must be specified "
620 "if either are specified: {0}".format(args))
621 raise IndexError(e)
622
623 try:
624 submitted = str_to_array(submitted)
625 found = str_to_array(found)
626 except:
627 # For example, if the array is huge and there are ellipsis in it.
628 error = True
629 else:
630 found_isnan = np.isnan(found)
631 submitted_isnan = np.isnan(submitted)
632 error = not np.allclose(found_isnan, submitted_isnan)
633 error |= not np.allclose(found[~found_isnan],
634 submitted[~submitted_isnan],
635 rtol=rtol, atol=atol)
636
637 if error:
638 e = ('doctest float comparison failure for input_lines="{0}" with '
639 'found_output="{1}" and submitted '
640 'output="{2}"'.format(input_lines, found, submitted) )
641 raise RuntimeError(e)
642
643
571 class IPythonDirective(Directive):
644 class IPythonDirective(Directive):
572
645
573 has_content = True
646 has_content = True
@@ -847,6 +920,33 b" In [152]: title('normal distribution')"
847 @savefig hist_with_text.png
920 @savefig hist_with_text.png
848 In [153]: grid(True)
921 In [153]: grid(True)
849
922
923 @doctest float
924 In [154]: 0.1 + 0.2
925 Out[154]: 0.3
926
927 @doctest float
928 In [155]: np.arange(16).reshape(4,4)
929 Out[155]:
930 array([[ 0, 1, 2, 3],
931 [ 4, 5, 6, 7],
932 [ 8, 9, 10, 11],
933 [12, 13, 14, 15]])
934
935 In [1]: x = np.arange(16, dtype=float).reshape(4,4)
936
937 In [2]: x[0,0] = np.inf
938
939 In [3]: x[0,1] = np.nan
940
941 @doctest float
942 In [4]: x
943 Out[4]:
944 array([[ inf, nan, 2., 3.],
945 [ 4., 5., 6., 7.],
946 [ 8., 9., 10., 11.],
947 [ 12., 13., 14., 15.]])
948
949
850 """,
950 """,
851 ]
951 ]
852 # skip local-file depending first example:
952 # skip local-file depending first example:
General Comments 0
You need to be logged in to leave comments. Login now