##// 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 70 In [3]: print(y)
71 71
72
73 72 See http://matplotlib.org/sampledoc/ipython_directive.html for more additional
74 73 documentation.
75 74
@@ -133,6 +132,28 b' COMMENT, INPUT, OUTPUT = range(3)'
133 132 #-----------------------------------------------------------------------------
134 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 157 def block_parser(part, rgxin, rgxout, fmtin, fmtout):
137 158 """
138 159 part is a string of ipython text, comprised of at most one
@@ -339,7 +360,8 b' class EmbeddedSphinxShell(object):'
339 360 image_directive = None
340 361 #print 'INPUT:', data # dbg
341 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 365 is_suppress = decorator=='@suppress' or self.is_suppress
344 366 is_savefig = decorator is not None and \
345 367 decorator.startswith('@savefig')
@@ -396,38 +418,38 b' class EmbeddedSphinxShell(object):'
396 418 ret.append('')
397 419
398 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 422 image_directive)
401 423 #print 'OUTPUT', output # dbg
402 424
403 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 427 """Process data block for OUTPUT token."""
406 if is_doctest:
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)
428 if is_doctest and output is not None:
417 429
418 ind = found.find(output_prompt)
419 if ind<0:
420 e='output prompt="%s" does not match out line=%s' % \
421 (output_prompt, found)
422 raise RuntimeError(e)
423 found = found[len(output_prompt):].strip()
430 found = output
431 found = found.strip()
432 submitted = data.strip()
424 433
425 if found!=submitted:
426 e = ('doctest failure for input_lines="%s" with '
427 'found_output="%s" and submitted output="%s"' %
428 (input_lines, found, submitted) )
434 # Make sure the output contains the output prompt.
435 ind = found.find(output_prompt)
436 if ind < 0:
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 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 454 def process_comment(self, data):
433 455 """Process data fPblock for COMMENT token."""
@@ -448,7 +470,6 b' class EmbeddedSphinxShell(object):'
448 470 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
449 471 self.clear_cout()
450 472
451
452 473 def process_block(self, block):
453 474 """
454 475 process block from the block_parser and return a list of processed lines
@@ -467,14 +488,14 b' class EmbeddedSphinxShell(object):'
467 488 if token==COMMENT:
468 489 out_data = self.process_comment(data)
469 490 elif token==INPUT:
470 (out_data, input_lines, output, is_doctest, image_file,
471 image_directive) = \
491 (out_data, input_lines, output, is_doctest, decorator,
492 image_file, image_directive) = \
472 493 self.process_input(data, input_prompt, lineno)
473 494 elif token==OUTPUT:
474 495 out_data = \
475 496 self.process_output(data, output_prompt,
476 497 input_lines, output, is_doctest,
477 image_file)
498 decorator, image_file)
478 499 if out_data:
479 500 ret.extend(out_data)
480 501
@@ -489,10 +510,11 b' class EmbeddedSphinxShell(object):'
489 510 return
490 511 self.process_input_line('import matplotlib.pyplot as plt',
491 512 store_history=False)
513 self._pyplot_imported = True
492 514
493 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 519 This runs it line by line in the InteractiveShell, prepends
498 520 prompts as needed capturing stderr and stdout, then returns
@@ -568,6 +590,57 b' class EmbeddedSphinxShell(object):'
568 590
569 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 644 class IPythonDirective(Directive):
572 645
573 646 has_content = True
@@ -847,6 +920,33 b" In [152]: title('normal distribution')"
847 920 @savefig hist_with_text.png
848 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 952 # skip local-file depending first example:
General Comments 0
You need to be logged in to leave comments. Login now