Show More
@@ -0,0 +1,92 b'' | |||
|
1 | """ | |
|
2 | Module containing custom doctests. | |
|
3 | ||
|
4 | """ | |
|
5 | ||
|
6 | def str_to_array(s): | |
|
7 | """ | |
|
8 | Simplistic converter of strings from repr to float NumPy arrays. | |
|
9 | ||
|
10 | If the repr representation has ellipsis in it, then this will fail. | |
|
11 | ||
|
12 | Parameters | |
|
13 | ---------- | |
|
14 | s : str | |
|
15 | The repr version of a NumPy array. | |
|
16 | ||
|
17 | Examples | |
|
18 | -------- | |
|
19 | >>> s = "array([ 0.3, inf, nan])" | |
|
20 | >>> a = str_to_array(s) | |
|
21 | ||
|
22 | """ | |
|
23 | import numpy as np | |
|
24 | ||
|
25 | # Need to make sure eval() knows about inf and nan. | |
|
26 | # This also assumes default printoptions for NumPy. | |
|
27 | from numpy import inf, nan | |
|
28 | ||
|
29 | if s.startswith(u'array'): | |
|
30 | # Remove array( and ) | |
|
31 | s = s[6:-1] | |
|
32 | ||
|
33 | if s.startswith(u'['): | |
|
34 | a = np.array(eval(s), dtype=float) | |
|
35 | else: | |
|
36 | # Assume its a regular float. Force 1D so we can index into it. | |
|
37 | a = np.atleast_1d(float(s)) | |
|
38 | return a | |
|
39 | ||
|
40 | def float_doctest(sphinx_shell, args, input_lines, found, submitted): | |
|
41 | """ | |
|
42 | Doctest which allow the submitted output to vary slightly from the input. | |
|
43 | ||
|
44 | Here is how it might appear in an rst file: | |
|
45 | ||
|
46 | .. code-block:: rst | |
|
47 | ||
|
48 | .. ipython:: | |
|
49 | ||
|
50 | @doctest float | |
|
51 | In [1]: 0.1 + 0.2 | |
|
52 | Out[1]: 0.3 | |
|
53 | ||
|
54 | """ | |
|
55 | import numpy as np | |
|
56 | ||
|
57 | if len(args) == 2: | |
|
58 | rtol = 1e-05 | |
|
59 | atol = 1e-08 | |
|
60 | else: | |
|
61 | # Both must be specified if any are specified. | |
|
62 | try: | |
|
63 | rtol = float(args[2]) | |
|
64 | atol = float(args[3]) | |
|
65 | except IndexError: | |
|
66 | e = ("Both `rtol` and `atol` must be specified " | |
|
67 | "if either are specified: {0}".format(args)) | |
|
68 | raise IndexError(e) | |
|
69 | ||
|
70 | try: | |
|
71 | submitted = str_to_array(submitted) | |
|
72 | found = str_to_array(found) | |
|
73 | except: | |
|
74 | # For example, if the array is huge and there are ellipsis in it. | |
|
75 | error = True | |
|
76 | else: | |
|
77 | found_isnan = np.isnan(found) | |
|
78 | submitted_isnan = np.isnan(submitted) | |
|
79 | error = not np.allclose(found_isnan, submitted_isnan) | |
|
80 | error |= not np.allclose(found[~found_isnan], | |
|
81 | submitted[~submitted_isnan], | |
|
82 | rtol=rtol, atol=atol) | |
|
83 | ||
|
84 | if error: | |
|
85 | e = ('doctest float comparison failure for input_lines={0} with ' | |
|
86 | 'found_output={1} and submitted ' | |
|
87 | 'output="{2}"'.format(input_lines, repr(found), repr(submitted)) ) | |
|
88 | raise RuntimeError(e) | |
|
89 | ||
|
90 | doctests = { | |
|
91 | 'float': float_doctest, | |
|
92 | } |
@@ -136,27 +136,6 b' COMMENT, INPUT, OUTPUT = range(3)' | |||
|
136 | 136 | #----------------------------------------------------------------------------- |
|
137 | 137 | # Functions and class declarations |
|
138 | 138 | #----------------------------------------------------------------------------- |
|
139 | def str_to_array(s): | |
|
140 | """ | |
|
141 | Simplistic converter of strings from repr to float NumPy arrays. | |
|
142 | ||
|
143 | """ | |
|
144 | import numpy as np | |
|
145 | ||
|
146 | # Handle infs (assumes default printoptions for NumPy) | |
|
147 | s = s.replace(u'inf', u'np.inf') | |
|
148 | s = s.replace(u'nan', u'np.nan') | |
|
149 | ||
|
150 | if s.startswith(u'array'): | |
|
151 | # Remove array( and ) | |
|
152 | s = s[6:-1] | |
|
153 | ||
|
154 | if s.startswith(u'['): | |
|
155 | a = np.array(eval(s), dtype=float) | |
|
156 | else: | |
|
157 | # Assume its a regular float. Force 1D so we can index into it. | |
|
158 | a = np.atleast_1d(float(s)) | |
|
159 | return a | |
|
160 | 139 | |
|
161 | 140 | def block_parser(part, rgxin, rgxout, fmtin, fmtout): |
|
162 | 141 | """ |
@@ -252,6 +231,7 b' def block_parser(part, rgxin, rgxout, fmtin, fmtout):' | |||
|
252 | 231 | |
|
253 | 232 | return block |
|
254 | 233 | |
|
234 | ||
|
255 | 235 | class EmbeddedSphinxShell(object): |
|
256 | 236 | """An embedded IPython instance to run inside Sphinx""" |
|
257 | 237 | |
@@ -355,7 +335,6 b' class EmbeddedSphinxShell(object):' | |||
|
355 | 335 | image_directive = '\n'.join(imagerows) |
|
356 | 336 | return image_file, image_directive |
|
357 | 337 | |
|
358 | ||
|
359 | 338 | # Callbacks for each type of token |
|
360 | 339 | def process_input(self, data, input_prompt, lineno): |
|
361 | 340 | """Process data block for INPUT token.""" |
@@ -452,8 +431,7 b' class EmbeddedSphinxShell(object):' | |||
|
452 | 431 | 'output="{2}"'.format(input_lines, found, submitted) ) |
|
453 | 432 | raise RuntimeError(e) |
|
454 | 433 | else: |
|
455 |
self. |
|
|
456 | found, submitted) | |
|
434 | self.custom_doctest(decorator, input_lines, found, submitted) | |
|
457 | 435 | |
|
458 | 436 | def process_comment(self, data): |
|
459 | 437 | """Process data fPblock for COMMENT token.""" |
@@ -594,56 +572,21 b' class EmbeddedSphinxShell(object):' | |||
|
594 | 572 | |
|
595 | 573 | return output |
|
596 | 574 | |
|
597 |
def |
|
|
575 | def custom_doctest(self, decorator, input_lines, found, submitted): | |
|
598 | 576 | """ |
|
599 | 577 | Perform a specialized doctest. |
|
600 | 578 | |
|
601 | 579 | """ |
|
602 | # Requires NumPy | |
|
603 | import numpy as np | |
|
604 | ||
|
605 | valid_types = set(['float']) | |
|
580 | from .custom_doctests import doctests | |
|
606 | 581 | |
|
607 | 582 | args = decorator.split() |
|
608 | ||
|
609 | 583 | doctest_type = args[1] |
|
610 |
if doctest_type |
|
|
584 | if doctest_type in doctests: | |
|
585 | doctests[doctest_type](self, args, input_lines, found, submitted) | |
|
586 | else: | |
|
611 | 587 | e = "Invalid option to @doctest: {0}".format(doctest_type) |
|
612 | 588 | raise Exception(e) |
|
613 | 589 | |
|
614 | if len(args) == 2: | |
|
615 | rtol = 1e-05 | |
|
616 | atol = 1e-08 | |
|
617 | else: | |
|
618 | # Both must be specified if any are specified. | |
|
619 | try: | |
|
620 | rtol = float(args[2]) | |
|
621 | atol = float(args[3]) | |
|
622 | except IndexError: | |
|
623 | e = ("Both `rtol` and `atol` must be specified " | |
|
624 | "if either are specified: {0}".format(args)) | |
|
625 | raise IndexError(e) | |
|
626 | ||
|
627 | try: | |
|
628 | submitted = str_to_array(submitted) | |
|
629 | found = str_to_array(found) | |
|
630 | except: | |
|
631 | # For example, if the array is huge and there are ellipsis in it. | |
|
632 | error = True | |
|
633 | else: | |
|
634 | found_isnan = np.isnan(found) | |
|
635 | submitted_isnan = np.isnan(submitted) | |
|
636 | error = not np.allclose(found_isnan, submitted_isnan) | |
|
637 | error |= not np.allclose(found[~found_isnan], | |
|
638 | submitted[~submitted_isnan], | |
|
639 | rtol=rtol, atol=atol) | |
|
640 | ||
|
641 | if error: | |
|
642 | e = ('doctest float comparison failure for input_lines="{0}" with ' | |
|
643 | 'found_output="{1}" and submitted ' | |
|
644 | 'output="{2}"'.format(input_lines, found, submitted) ) | |
|
645 | raise RuntimeError(e) | |
|
646 | ||
|
647 | 590 | |
|
648 | 591 | class IPythonDirective(Directive): |
|
649 | 592 |
General Comments 0
You need to be logged in to leave comments.
Login now