Show More
@@ -0,0 +1,185 b'' | |||||
|
1 | """Tests for code execution (%run and related), which is particularly tricky. | |||
|
2 | ||||
|
3 | Because of how %run manages namespaces, and the fact that we are trying here to | |||
|
4 | verify subtle object deletion and reference counting issues, the %run tests | |||
|
5 | will be kept in this separate file. This makes it easier to aggregate in one | |||
|
6 | place the tricks needed to handle it; most other magics are much easier to test | |||
|
7 | and we do so in a common test_magic file. | |||
|
8 | """ | |||
|
9 | from __future__ import absolute_import | |||
|
10 | ||||
|
11 | #----------------------------------------------------------------------------- | |||
|
12 | # Imports | |||
|
13 | #----------------------------------------------------------------------------- | |||
|
14 | ||||
|
15 | # stdlib | |||
|
16 | import os | |||
|
17 | import sys | |||
|
18 | import tempfile | |||
|
19 | ||||
|
20 | # third-party | |||
|
21 | import nose.tools as nt | |||
|
22 | ||||
|
23 | # our own | |||
|
24 | from IPython.utils.platutils import find_cmd | |||
|
25 | from IPython.utils import genutils | |||
|
26 | from IPython.testing import decorators as dec | |||
|
27 | from IPython.testing import tools as tt | |||
|
28 | ||||
|
29 | #----------------------------------------------------------------------------- | |||
|
30 | # Test functions begin | |||
|
31 | #----------------------------------------------------------------------------- | |||
|
32 | ||||
|
33 | def doctest_refbug(): | |||
|
34 | """Very nasty problem with references held by multiple runs of a script. | |||
|
35 | See: https://bugs.launchpad.net/ipython/+bug/269966 | |||
|
36 | ||||
|
37 | In [1]: _ip.clear_main_mod_cache() | |||
|
38 | # random | |||
|
39 | ||||
|
40 | In [2]: %run refbug | |||
|
41 | ||||
|
42 | In [3]: call_f() | |||
|
43 | lowercased: hello | |||
|
44 | ||||
|
45 | In [4]: %run refbug | |||
|
46 | ||||
|
47 | In [5]: call_f() | |||
|
48 | lowercased: hello | |||
|
49 | lowercased: hello | |||
|
50 | """ | |||
|
51 | ||||
|
52 | ||||
|
53 | def doctest_run_builtins(): | |||
|
54 | r"""Check that %run doesn't damage __builtins__. | |||
|
55 | ||||
|
56 | In [1]: import tempfile | |||
|
57 | ||||
|
58 | In [2]: bid1 = id(__builtins__) | |||
|
59 | ||||
|
60 | In [3]: fname = tempfile.mkstemp('.py')[1] | |||
|
61 | ||||
|
62 | In [3]: f = open(fname,'w') | |||
|
63 | ||||
|
64 | In [4]: f.write('pass\n') | |||
|
65 | ||||
|
66 | In [5]: f.flush() | |||
|
67 | ||||
|
68 | In [6]: t1 = type(__builtins__) | |||
|
69 | ||||
|
70 | In [7]: %run "$fname" | |||
|
71 | ||||
|
72 | In [7]: f.close() | |||
|
73 | ||||
|
74 | In [8]: bid2 = id(__builtins__) | |||
|
75 | ||||
|
76 | In [9]: t2 = type(__builtins__) | |||
|
77 | ||||
|
78 | In [10]: t1 == t2 | |||
|
79 | Out[10]: True | |||
|
80 | ||||
|
81 | In [10]: bid1 == bid2 | |||
|
82 | Out[10]: True | |||
|
83 | ||||
|
84 | In [12]: try: | |||
|
85 | ....: os.unlink(fname) | |||
|
86 | ....: except: | |||
|
87 | ....: pass | |||
|
88 | ....: | |||
|
89 | """ | |||
|
90 | ||||
|
91 | # For some tests, it will be handy to organize them in a class with a common | |||
|
92 | # setup that makes a temp file | |||
|
93 | ||||
|
94 | class TempFileMixin(object): | |||
|
95 | def mktmp(self, src, ext='.py'): | |||
|
96 | """Make a valid python temp file.""" | |||
|
97 | fname, f = tt.temp_pyfile(src, ext) | |||
|
98 | self.tmpfile = f | |||
|
99 | self.fname = fname | |||
|
100 | ||||
|
101 | def teardown(self): | |||
|
102 | self.tmpfile.close() | |||
|
103 | try: | |||
|
104 | os.unlink(self.fname) | |||
|
105 | except: | |||
|
106 | # On Windows, even though we close the file, we still can't delete | |||
|
107 | # it. I have no clue why | |||
|
108 | pass | |||
|
109 | ||||
|
110 | ||||
|
111 | class TestMagicRunPass(TempFileMixin): | |||
|
112 | ||||
|
113 | def setup(self): | |||
|
114 | """Make a valid python temp file.""" | |||
|
115 | self.mktmp('pass\n') | |||
|
116 | ||||
|
117 | def run_tmpfile(self): | |||
|
118 | _ip = get_ipython() | |||
|
119 | # This fails on Windows if self.tmpfile.name has spaces or "~" in it. | |||
|
120 | # See below and ticket https://bugs.launchpad.net/bugs/366353 | |||
|
121 | _ip.magic('run "%s"' % self.fname) | |||
|
122 | ||||
|
123 | def test_builtins_id(self): | |||
|
124 | """Check that %run doesn't damage __builtins__ """ | |||
|
125 | _ip = get_ipython() | |||
|
126 | # Test that the id of __builtins__ is not modified by %run | |||
|
127 | bid1 = id(_ip.user_ns['__builtins__']) | |||
|
128 | self.run_tmpfile() | |||
|
129 | bid2 = id(_ip.user_ns['__builtins__']) | |||
|
130 | tt.assert_equals(bid1, bid2) | |||
|
131 | ||||
|
132 | def test_builtins_type(self): | |||
|
133 | """Check that the type of __builtins__ doesn't change with %run. | |||
|
134 | ||||
|
135 | However, the above could pass if __builtins__ was already modified to | |||
|
136 | be a dict (it should be a module) by a previous use of %run. So we | |||
|
137 | also check explicitly that it really is a module: | |||
|
138 | """ | |||
|
139 | _ip = get_ipython() | |||
|
140 | self.run_tmpfile() | |||
|
141 | tt.assert_equals(type(_ip.user_ns['__builtins__']),type(sys)) | |||
|
142 | ||||
|
143 | def test_prompts(self): | |||
|
144 | """Test that prompts correctly generate after %run""" | |||
|
145 | self.run_tmpfile() | |||
|
146 | _ip = get_ipython() | |||
|
147 | p2 = str(_ip.outputcache.prompt2).strip() | |||
|
148 | nt.assert_equals(p2[:3], '...') | |||
|
149 | ||||
|
150 | ||||
|
151 | class TestMagicRunSimple(TempFileMixin): | |||
|
152 | ||||
|
153 | def test_simpledef(self): | |||
|
154 | """Test that simple class definitions work.""" | |||
|
155 | src = ("class foo: pass\n" | |||
|
156 | "def f(): return foo()") | |||
|
157 | self.mktmp(src) | |||
|
158 | _ip.magic('run "%s"' % self.fname) | |||
|
159 | _ip.runlines('t = isinstance(f(), foo)') | |||
|
160 | nt.assert_true(_ip.user_ns['t']) | |||
|
161 | ||||
|
162 | def test_obj_del(self): | |||
|
163 | """Test that object's __del__ methods are called on exit.""" | |||
|
164 | ||||
|
165 | # This test is known to fail on win32. | |||
|
166 | # See ticket https://bugs.launchpad.net/bugs/366334 | |||
|
167 | src = ("class A(object):\n" | |||
|
168 | " def __del__(self):\n" | |||
|
169 | " print 'object A deleted'\n" | |||
|
170 | "a = A()\n") | |||
|
171 | self.mktmp(src) | |||
|
172 | tt.ipexec_validate(self.fname, 'object A deleted') | |||
|
173 | ||||
|
174 | def test_tclass(self): | |||
|
175 | mydir = os.path.dirname(__file__) | |||
|
176 | tc = os.path.join(mydir, 'tclass') | |||
|
177 | src = ("%%run '%s' C-first\n" | |||
|
178 | "%%run '%s' C-second\n") % (tc, tc) | |||
|
179 | self.mktmp(src, '.ipy') | |||
|
180 | out = """\ | |||
|
181 | ARGV 1-: ['C-first'] | |||
|
182 | ARGV 1-: ['C-second'] | |||
|
183 | tclass.py: deleting object: C-first | |||
|
184 | """ | |||
|
185 | tt.ipexec_validate(self.fname, out) |
@@ -3506,6 +3506,7 b' Defaulting color scheme to \'NoColor\'"""' | |||||
3506 | """Reload an IPython extension by its module name.""" |
|
3506 | """Reload an IPython extension by its module name.""" | |
3507 | self.reload_extension(module_str) |
|
3507 | self.reload_extension(module_str) | |
3508 |
|
3508 | |||
|
3509 | @testdec.skip_doctest | |||
3509 | def magic_install_profiles(self, s): |
|
3510 | def magic_install_profiles(self, s): | |
3510 | """Install the default IPython profiles into the .ipython dir. |
|
3511 | """Install the default IPython profiles into the .ipython dir. | |
3511 |
|
3512 |
@@ -416,7 +416,7 b' class PrefilterManager(Component):' | |||||
416 | # print "prefiltered line: %r" % prefiltered |
|
416 | # print "prefiltered line: %r" % prefiltered | |
417 | return prefiltered |
|
417 | return prefiltered | |
418 |
|
418 | |||
419 | def prefilter_lines(self, lines, continue_prompt): |
|
419 | def prefilter_lines(self, lines, continue_prompt=False): | |
420 | """Prefilter multiple input lines of text. |
|
420 | """Prefilter multiple input lines of text. | |
421 |
|
421 | |||
422 | This is the main entry point for prefiltering multiple lines of |
|
422 | This is the main entry point for prefiltering multiple lines of |
@@ -1,16 +1,12 b'' | |||||
1 | """Simple script to instantiate a class for testing %run""" |
|
1 | """Simple script to be run *twice*, to check reference counting bugs. | |
2 |
|
|
2 | ||
3 | import sys |
|
3 | See test_run for details.""" | |
4 |
|
||||
5 | # An external test will check that calls to f() work after %run |
|
|||
6 | class foo: pass |
|
|||
7 |
|
4 | |||
8 | def f(): |
|
5 | import sys | |
9 | return foo() |
|
|||
10 |
|
6 | |||
11 |
# We |
|
7 | # We want to ensure that while objects remain available for immediate access, | |
12 |
# |
|
8 | # objects from *previous* runs of the same script get collected, to avoid | |
13 |
# a |
|
9 | # accumulating massive amounts of old references. | |
14 | class C(object): |
|
10 | class C(object): | |
15 | def __init__(self,name): |
|
11 | def __init__(self,name): | |
16 | self.name = name |
|
12 | self.name = name | |
@@ -18,6 +14,7 b' class C(object):' | |||||
18 | def __del__(self): |
|
14 | def __del__(self): | |
19 | print 'tclass.py: deleting object:',self.name |
|
15 | print 'tclass.py: deleting object:',self.name | |
20 |
|
16 | |||
|
17 | ||||
21 | try: |
|
18 | try: | |
22 | name = sys.argv[1] |
|
19 | name = sys.argv[1] | |
23 | except IndexError: |
|
20 | except IndexError: | |
@@ -25,3 +22,8 b' except IndexError:' | |||||
25 | else: |
|
22 | else: | |
26 | if name.startswith('C'): |
|
23 | if name.startswith('C'): | |
27 | c = C(name) |
|
24 | c = C(name) | |
|
25 | ||||
|
26 | #print >> sys.stderr, "ARGV:", sys.argv # dbg | |||
|
27 | # This print statement is NOT debugging, we're making the check on a completely | |||
|
28 | # separate process so we verify by capturing stdout. | |||
|
29 | print 'ARGV 1-:', sys.argv[1:] |
@@ -2,21 +2,31 b'' | |||||
2 |
|
2 | |||
3 | Needs to be run by nose (to make ipython session available). |
|
3 | Needs to be run by nose (to make ipython session available). | |
4 | """ |
|
4 | """ | |
|
5 | from __future__ import absolute_import | |||
5 |
|
6 | |||
|
7 | #----------------------------------------------------------------------------- | |||
|
8 | # Imports | |||
|
9 | #----------------------------------------------------------------------------- | |||
|
10 | ||||
|
11 | # stdlib | |||
6 | import os |
|
12 | import os | |
7 | import sys |
|
13 | import sys | |
8 | import tempfile |
|
14 | import tempfile | |
9 | import types |
|
15 | import types | |
10 | from cStringIO import StringIO |
|
16 | from cStringIO import StringIO | |
11 |
|
17 | |||
|
18 | # third-party | |||
12 | import nose.tools as nt |
|
19 | import nose.tools as nt | |
13 |
|
20 | |||
|
21 | # our own | |||
|
22 | from IPython.utils import genutils | |||
14 | from IPython.utils.platutils import find_cmd, get_long_path_name |
|
23 | from IPython.utils.platutils import find_cmd, get_long_path_name | |
15 | from IPython.testing import decorators as dec |
|
24 | from IPython.testing import decorators as dec | |
16 | from IPython.testing import tools as tt |
|
25 | from IPython.testing import tools as tt | |
17 |
|
26 | |||
18 | #----------------------------------------------------------------------------- |
|
27 | #----------------------------------------------------------------------------- | |
19 | # Test functions begin |
|
28 | # Test functions begin | |
|
29 | #----------------------------------------------------------------------------- | |||
20 |
|
30 | |||
21 | def test_rehashx(): |
|
31 | def test_rehashx(): | |
22 | # clear up everything |
|
32 | # clear up everything | |
@@ -63,17 +73,6 b' def doctest_hist_r():' | |||||
63 | hist -n -r 2 # random |
|
73 | hist -n -r 2 # random | |
64 | """ |
|
74 | """ | |
65 |
|
75 | |||
66 | # This test is known to fail on win32. |
|
|||
67 | # See ticket https://bugs.launchpad.net/bugs/366334 |
|
|||
68 | def test_obj_del(): |
|
|||
69 | _ip = get_ipython() |
|
|||
70 | """Test that object's __del__ methods are called on exit.""" |
|
|||
71 | test_dir = os.path.dirname(__file__) |
|
|||
72 | del_file = os.path.join(test_dir,'obj_del.py') |
|
|||
73 | ipython_cmd = find_cmd('ipython') |
|
|||
74 | out = _ip.getoutput('%s %s' % (ipython_cmd, del_file)) |
|
|||
75 | nt.assert_equals(out,'obj_del.py: object A deleted') |
|
|||
76 |
|
||||
77 |
|
76 | |||
78 | def test_shist(): |
|
77 | def test_shist(): | |
79 | # Simple tests of ShadowHist class - test generator. |
|
78 | # Simple tests of ShadowHist class - test generator. | |
@@ -113,161 +112,6 b' def test_numpy_clear_array_undec():' | |||||
113 | yield (nt.assert_false, 'a' in _ip.user_ns) |
|
112 | yield (nt.assert_false, 'a' in _ip.user_ns) | |
114 |
|
113 | |||
115 |
|
114 | |||
116 | @dec.skip() |
|
|||
117 | def test_fail_dec(*a,**k): |
|
|||
118 | yield nt.assert_true, False |
|
|||
119 |
|
||||
120 | @dec.skip('This one shouldn not run') |
|
|||
121 | def test_fail_dec2(*a,**k): |
|
|||
122 | yield nt.assert_true, False |
|
|||
123 |
|
||||
124 | @dec.skipknownfailure |
|
|||
125 | def test_fail_dec3(*a,**k): |
|
|||
126 | yield nt.assert_true, False |
|
|||
127 |
|
||||
128 |
|
||||
129 | def doctest_refbug(): |
|
|||
130 | """Very nasty problem with references held by multiple runs of a script. |
|
|||
131 | See: https://bugs.launchpad.net/ipython/+bug/269966 |
|
|||
132 |
|
||||
133 | In [1]: _ip.clear_main_mod_cache() |
|
|||
134 |
|
||||
135 | In [2]: run refbug |
|
|||
136 |
|
||||
137 | In [3]: call_f() |
|
|||
138 | lowercased: hello |
|
|||
139 |
|
||||
140 | In [4]: run refbug |
|
|||
141 |
|
||||
142 | In [5]: call_f() |
|
|||
143 | lowercased: hello |
|
|||
144 | lowercased: hello |
|
|||
145 | """ |
|
|||
146 |
|
||||
147 | #----------------------------------------------------------------------------- |
|
|||
148 | # Tests for %run |
|
|||
149 | #----------------------------------------------------------------------------- |
|
|||
150 |
|
||||
151 | # %run is critical enough that it's a good idea to have a solid collection of |
|
|||
152 | # tests for it, some as doctests and some as normal tests. |
|
|||
153 |
|
||||
154 | def doctest_run_ns(): |
|
|||
155 | """Classes declared %run scripts must be instantiable afterwards. |
|
|||
156 |
|
||||
157 | In [11]: run tclass foo |
|
|||
158 |
|
||||
159 | In [12]: isinstance(f(),foo) |
|
|||
160 | Out[12]: True |
|
|||
161 | """ |
|
|||
162 |
|
||||
163 |
|
||||
164 | def doctest_run_ns2(): |
|
|||
165 | """Classes declared %run scripts must be instantiable afterwards. |
|
|||
166 |
|
||||
167 | In [4]: run tclass C-first_pass |
|
|||
168 |
|
||||
169 | In [5]: run tclass C-second_pass |
|
|||
170 | tclass.py: deleting object: C-first_pass |
|
|||
171 | """ |
|
|||
172 |
|
||||
173 | def doctest_run_builtins(): |
|
|||
174 | """Check that %run doesn't damage __builtins__ via a doctest. |
|
|||
175 |
|
||||
176 | This is similar to the test_run_builtins, but I want *both* forms of the |
|
|||
177 | test to catch any possible glitches in our testing machinery, since that |
|
|||
178 | modifies %run somewhat. So for this, we have both a normal test (below) |
|
|||
179 | and a doctest (this one). |
|
|||
180 |
|
||||
181 | In [1]: import tempfile |
|
|||
182 |
|
||||
183 | In [2]: bid1 = id(__builtins__) |
|
|||
184 |
|
||||
185 | In [3]: fname = tempfile.mkstemp()[1] |
|
|||
186 |
|
||||
187 | In [3]: f = open(fname,'w') |
|
|||
188 |
|
||||
189 | In [4]: f.write('pass\\n') |
|
|||
190 |
|
||||
191 | In [5]: f.flush() |
|
|||
192 |
|
||||
193 | In [6]: print type(__builtins__) |
|
|||
194 | <type 'module'> |
|
|||
195 |
|
||||
196 | In [7]: %run "$fname" |
|
|||
197 |
|
||||
198 | In [7]: f.close() |
|
|||
199 |
|
||||
200 | In [8]: bid2 = id(__builtins__) |
|
|||
201 |
|
||||
202 | In [9]: print type(__builtins__) |
|
|||
203 | <type 'module'> |
|
|||
204 |
|
||||
205 | In [10]: bid1 == bid2 |
|
|||
206 | Out[10]: True |
|
|||
207 |
|
||||
208 | In [12]: try: |
|
|||
209 | ....: os.unlink(fname) |
|
|||
210 | ....: except: |
|
|||
211 | ....: pass |
|
|||
212 | ....: |
|
|||
213 | """ |
|
|||
214 |
|
||||
215 | # For some tests, it will be handy to organize them in a class with a common |
|
|||
216 | # setup that makes a temp file |
|
|||
217 |
|
||||
218 | class TestMagicRun(object): |
|
|||
219 |
|
||||
220 | def setup(self): |
|
|||
221 | """Make a valid python temp file.""" |
|
|||
222 | fname = tempfile.mkstemp('.py')[1] |
|
|||
223 | f = open(fname,'w') |
|
|||
224 | f.write('pass\n') |
|
|||
225 | f.flush() |
|
|||
226 | self.tmpfile = f |
|
|||
227 | self.fname = fname |
|
|||
228 |
|
||||
229 | def run_tmpfile(self): |
|
|||
230 | _ip = get_ipython() |
|
|||
231 | # This fails on Windows if self.tmpfile.name has spaces or "~" in it. |
|
|||
232 | # See below and ticket https://bugs.launchpad.net/bugs/366353 |
|
|||
233 | _ip.magic('run "%s"' % self.fname) |
|
|||
234 |
|
||||
235 | def test_builtins_id(self): |
|
|||
236 | """Check that %run doesn't damage __builtins__ """ |
|
|||
237 | _ip = get_ipython() |
|
|||
238 | # Test that the id of __builtins__ is not modified by %run |
|
|||
239 | bid1 = id(_ip.user_ns['__builtins__']) |
|
|||
240 | self.run_tmpfile() |
|
|||
241 | bid2 = id(_ip.user_ns['__builtins__']) |
|
|||
242 | tt.assert_equals(bid1, bid2) |
|
|||
243 |
|
||||
244 | def test_builtins_type(self): |
|
|||
245 | """Check that the type of __builtins__ doesn't change with %run. |
|
|||
246 |
|
||||
247 | However, the above could pass if __builtins__ was already modified to |
|
|||
248 | be a dict (it should be a module) by a previous use of %run. So we |
|
|||
249 | also check explicitly that it really is a module: |
|
|||
250 | """ |
|
|||
251 | _ip = get_ipython() |
|
|||
252 | self.run_tmpfile() |
|
|||
253 | tt.assert_equals(type(_ip.user_ns['__builtins__']),type(sys)) |
|
|||
254 |
|
||||
255 | def test_prompts(self): |
|
|||
256 | """Test that prompts correctly generate after %run""" |
|
|||
257 | self.run_tmpfile() |
|
|||
258 | _ip = get_ipython() |
|
|||
259 | p2 = str(_ip.outputcache.prompt2).strip() |
|
|||
260 | nt.assert_equals(p2[:3], '...') |
|
|||
261 |
|
||||
262 | def teardown(self): |
|
|||
263 | self.tmpfile.close() |
|
|||
264 | try: |
|
|||
265 | os.unlink(self.fname) |
|
|||
266 | except: |
|
|||
267 | # On Windows, even though we close the file, we still can't delete |
|
|||
268 | # it. I have no clue why |
|
|||
269 | pass |
|
|||
270 |
|
||||
271 | # Multiple tests for clipboard pasting |
|
115 | # Multiple tests for clipboard pasting | |
272 | @dec.parametric |
|
116 | @dec.parametric | |
273 | def test_paste(): |
|
117 | def test_paste(): |
@@ -64,6 +64,9 b" if sys.version[0]=='2':" | |||||
64 | else: |
|
64 | else: | |
65 | from _paramtestpy3 import parametric |
|
65 | from _paramtestpy3 import parametric | |
66 |
|
66 | |||
|
67 | # Expose the unittest-driven decorators | |||
|
68 | from ipunittest import ipdoctest, ipdocstring | |||
|
69 | ||||
67 | # Grab the numpy-specific decorators which we keep in a file that we |
|
70 | # Grab the numpy-specific decorators which we keep in a file that we | |
68 | # occasionally update from upstream: decorators.py is a copy of |
|
71 | # occasionally update from upstream: decorators.py is a copy of | |
69 | # numpy.testing.decorators, we expose all of it here. |
|
72 | # numpy.testing.decorators, we expose all of it here. |
@@ -16,6 +16,8 b' For now, this script requires that both nose and twisted are installed. This' | |||||
16 | will change in the future. |
|
16 | will change in the future. | |
17 | """ |
|
17 | """ | |
18 |
|
18 | |||
|
19 | from __future__ import absolute_import | |||
|
20 | ||||
19 | #----------------------------------------------------------------------------- |
|
21 | #----------------------------------------------------------------------------- | |
20 | # Module imports |
|
22 | # Module imports | |
21 | #----------------------------------------------------------------------------- |
|
23 | #----------------------------------------------------------------------------- | |
@@ -34,6 +36,8 b' from nose.core import TestProgram' | |||||
34 |
|
36 | |||
35 | from IPython.utils import genutils |
|
37 | from IPython.utils import genutils | |
36 | from IPython.utils.platutils import find_cmd, FindCmdError |
|
38 | from IPython.utils.platutils import find_cmd, FindCmdError | |
|
39 | from . import globalipapp | |||
|
40 | from .plugin.ipdoctest import IPythonDoctest | |||
37 |
|
41 | |||
38 | pjoin = path.join |
|
42 | pjoin = path.join | |
39 |
|
43 | |||
@@ -76,7 +80,11 b' def make_exclude():' | |||||
76 | # cause testing problems. We should strive to minimize the number of |
|
80 | # cause testing problems. We should strive to minimize the number of | |
77 | # skipped modules, since this means untested code. As the testing |
|
81 | # skipped modules, since this means untested code. As the testing | |
78 | # machinery solidifies, this list should eventually become empty. |
|
82 | # machinery solidifies, this list should eventually become empty. | |
79 | EXCLUDE = [pjoin('IPython', 'external'), |
|
83 | ||
|
84 | # Note that these exclusions only mean that the docstrings are not analyzed | |||
|
85 | # for examples to be run as tests, if there are other test functions in | |||
|
86 | # those modules, they do get run. | |||
|
87 | exclusions = [pjoin('IPython', 'external'), | |||
80 | pjoin('IPython', 'frontend', 'process', 'winprocess.py'), |
|
88 | pjoin('IPython', 'frontend', 'process', 'winprocess.py'), | |
81 | pjoin('IPython_doctest_plugin'), |
|
89 | pjoin('IPython_doctest_plugin'), | |
82 | pjoin('IPython', 'quarantine'), |
|
90 | pjoin('IPython', 'quarantine'), | |
@@ -88,58 +96,58 b' def make_exclude():' | |||||
88 | ] |
|
96 | ] | |
89 |
|
97 | |||
90 | if not have_wx: |
|
98 | if not have_wx: | |
91 |
|
|
99 | exclusions.append(pjoin('IPython', 'gui')) | |
92 |
|
|
100 | exclusions.append(pjoin('IPython', 'frontend', 'wx')) | |
93 |
|
|
101 | exclusions.append(pjoin('IPython', 'lib', 'inputhookwx')) | |
94 |
|
102 | |||
95 | if not have_gtk or not have_gobject: |
|
103 | if not have_gtk or not have_gobject: | |
96 |
|
|
104 | exclusions.append(pjoin('IPython', 'lib', 'inputhookgtk')) | |
97 |
|
105 | |||
98 | if not have_wx_aui: |
|
106 | if not have_wx_aui: | |
99 |
|
|
107 | exclusions.append(pjoin('IPython', 'gui', 'wx', 'wxIPython')) | |
100 |
|
108 | |||
101 | if not have_objc: |
|
109 | if not have_objc: | |
102 |
|
|
110 | exclusions.append(pjoin('IPython', 'frontend', 'cocoa')) | |
103 |
|
111 | |||
104 | if not sys.platform == 'win32': |
|
112 | if not sys.platform == 'win32': | |
105 |
|
|
113 | exclusions.append(pjoin('IPython', 'utils', 'platutils_win32')) | |
106 |
|
114 | |||
107 | # These have to be skipped on win32 because the use echo, rm, cd, etc. |
|
115 | # These have to be skipped on win32 because the use echo, rm, cd, etc. | |
108 | # See ticket https://bugs.launchpad.net/bugs/366982 |
|
116 | # See ticket https://bugs.launchpad.net/bugs/366982 | |
109 | if sys.platform == 'win32': |
|
117 | if sys.platform == 'win32': | |
110 |
|
|
118 | exclusions.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip')) | |
111 |
|
|
119 | exclusions.append(pjoin('IPython', 'testing', 'plugin', 'dtexample')) | |
112 |
|
120 | |||
113 | if not os.name == 'posix': |
|
121 | if not os.name == 'posix': | |
114 |
|
|
122 | exclusions.append(pjoin('IPython', 'utils', 'platutils_posix')) | |
115 |
|
123 | |||
116 | if not have_pexpect: |
|
124 | if not have_pexpect: | |
117 |
|
|
125 | exclusions.append(pjoin('IPython', 'scripts', 'irunner')) | |
118 |
|
126 | |||
119 | # This is scary. We still have things in frontend and testing that |
|
127 | # This is scary. We still have things in frontend and testing that | |
120 | # are being tested by nose that use twisted. We need to rethink |
|
128 | # are being tested by nose that use twisted. We need to rethink | |
121 | # how we are isolating dependencies in testing. |
|
129 | # how we are isolating dependencies in testing. | |
122 | if not (have_twisted and have_zi and have_foolscap): |
|
130 | if not (have_twisted and have_zi and have_foolscap): | |
123 |
|
|
131 | exclusions.append(pjoin('IPython', 'frontend', 'asyncfrontendbase')) | |
124 |
|
|
132 | exclusions.append(pjoin('IPython', 'frontend', 'prefilterfrontend')) | |
125 |
|
|
133 | exclusions.append(pjoin('IPython', 'frontend', 'frontendbase')) | |
126 |
|
|
134 | exclusions.append(pjoin('IPython', 'frontend', 'linefrontendbase')) | |
127 |
|
|
135 | exclusions.append(pjoin('IPython', 'frontend', 'tests', | |
128 | 'test_linefrontend')) |
|
136 | 'test_linefrontend')) | |
129 |
|
|
137 | exclusions.append(pjoin('IPython', 'frontend', 'tests', | |
130 | 'test_frontendbase')) |
|
138 | 'test_frontendbase')) | |
131 |
|
|
139 | exclusions.append(pjoin('IPython', 'frontend', 'tests', | |
132 | 'test_prefilterfrontend')) |
|
140 | 'test_prefilterfrontend')) | |
133 |
|
|
141 | exclusions.append(pjoin('IPython', 'frontend', 'tests', | |
134 | 'test_asyncfrontendbase')), |
|
142 | 'test_asyncfrontendbase')), | |
135 |
|
|
143 | exclusions.append(pjoin('IPython', 'testing', 'parametric')) | |
136 |
|
|
144 | exclusions.append(pjoin('IPython', 'testing', 'util')) | |
137 |
|
145 | |||
138 | # This is needed for the reg-exp to match on win32 in the ipdoctest plugin. |
|
146 | # This is needed for the reg-exp to match on win32 in the ipdoctest plugin. | |
139 | if sys.platform == 'win32': |
|
147 | if sys.platform == 'win32': | |
140 |
|
|
148 | exclusions = [s.replace('\\','\\\\') for s in exclusions] | |
141 |
|
149 | |||
142 | return EXCLUDE |
|
150 | return exclusions | |
143 |
|
151 | |||
144 |
|
152 | |||
145 | #----------------------------------------------------------------------------- |
|
153 | #----------------------------------------------------------------------------- | |
@@ -163,16 +171,16 b' class IPTester(object):' | |||||
163 | if runner == 'iptest': |
|
171 | if runner == 'iptest': | |
164 | # Find our own 'iptest' script OS-level entry point |
|
172 | # Find our own 'iptest' script OS-level entry point | |
165 | try: |
|
173 | try: | |
166 | iptest_path = find_cmd('iptest') |
|
174 | iptest_path = os.path.abspath(find_cmd('iptest')) | |
167 | except FindCmdError: |
|
175 | except FindCmdError: | |
168 | # Script not installed (may be the case for testing situations |
|
176 | # Script not installed (may be the case for testing situations | |
169 | # that are running from a source tree only), pull from internal |
|
177 | # that are running from a source tree only), pull from internal | |
170 | # path: |
|
178 | # path: | |
171 | iptest_path = pjoin(genutils.get_ipython_package_dir(), |
|
179 | iptest_path = pjoin(genutils.get_ipython_package_dir(), | |
172 | 'scripts','iptest') |
|
180 | 'scripts','iptest') | |
173 | self.runner = [iptest_path,'-v'] |
|
181 | self.runner = ['python', iptest_path, '-v'] | |
174 | else: |
|
182 | else: | |
175 | self.runner = [find_cmd('trial')] |
|
183 | self.runner = ['python', os.path.abspath(find_cmd('trial'))] | |
176 | if params is None: |
|
184 | if params is None: | |
177 | params = [] |
|
185 | params = [] | |
178 | if isinstance(params,str): |
|
186 | if isinstance(params,str): | |
@@ -238,11 +246,13 b' def make_runners():' | |||||
238 | nose_packages = ['config', 'core', 'extensions', 'frontend', 'lib', |
|
246 | nose_packages = ['config', 'core', 'extensions', 'frontend', 'lib', | |
239 | 'scripts', 'testing', 'utils'] |
|
247 | 'scripts', 'testing', 'utils'] | |
240 | trial_packages = ['kernel'] |
|
248 | trial_packages = ['kernel'] | |
241 | #trial_packages = [] # dbg |
|
|||
242 |
|
249 | |||
243 | if have_wx: |
|
250 | if have_wx: | |
244 | nose_packages.append('gui') |
|
251 | nose_packages.append('gui') | |
245 |
|
252 | |||
|
253 | #nose_packages = ['core'] # dbg | |||
|
254 | #trial_packages = [] # dbg | |||
|
255 | ||||
246 | nose_packages = ['IPython.%s' % m for m in nose_packages ] |
|
256 | nose_packages = ['IPython.%s' % m for m in nose_packages ] | |
247 | trial_packages = ['IPython.%s' % m for m in trial_packages ] |
|
257 | trial_packages = ['IPython.%s' % m for m in trial_packages ] | |
248 |
|
258 | |||
@@ -268,16 +278,15 b' def run_iptest():' | |||||
268 | warnings.filterwarnings('ignore', |
|
278 | warnings.filterwarnings('ignore', | |
269 | 'This will be removed soon. Use IPython.testing.util instead') |
|
279 | 'This will be removed soon. Use IPython.testing.util instead') | |
270 |
|
280 | |||
271 | argv = sys.argv + [ |
|
281 | argv = sys.argv + [ '--detailed-errors', | |
272 |
# Loading ipdoctest causes problems with Twisted |
|
282 | # Loading ipdoctest causes problems with Twisted, but | |
273 | # I am removing this as a temporary fix to get the |
|
283 | # our test suite runner now separates things and runs | |
274 | # test suite back into working shape. Our nose |
|
284 | # all Twisted tests with trial. | |
275 | # plugin needs to be gone through with a fine |
|
285 | '--with-ipdoctest', | |
276 | # toothed comb to find what is causing the problem. |
|
286 | '--ipdoctest-tests','--ipdoctest-extension=txt', | |
277 |
|
|
287 | ||
278 | # '--ipdoctest-tests','--ipdoctest-extension=txt', |
|
288 | #'-x','-s', # dbg | |
279 |
|
|
289 | ||
280 |
|
||||
281 | # We add --exe because of setuptools' imbecility (it |
|
290 | # We add --exe because of setuptools' imbecility (it | |
282 | # blindly does chmod +x on ALL files). Nose does the |
|
291 | # blindly does chmod +x on ALL files). Nose does the | |
283 | # right thing and it tries to avoid executables, |
|
292 | # right thing and it tries to avoid executables, | |
@@ -300,17 +309,18 b' def run_iptest():' | |||||
300 | if not has_tests: |
|
309 | if not has_tests: | |
301 | argv.append('IPython') |
|
310 | argv.append('IPython') | |
302 |
|
311 | |||
303 | # Construct list of plugins, omitting the existing doctest plugin, which |
|
312 | ## # Construct list of plugins, omitting the existing doctest plugin, which | |
304 | # ours replaces (and extends). |
|
313 | ## # ours replaces (and extends). | |
305 | EXCLUDE = make_exclude() |
|
314 | plugins = [IPythonDoctest(make_exclude())] | |
306 | plugins = [] |
|
|||
307 | # plugins = [IPythonDoctest(EXCLUDE)] |
|
|||
308 | for p in nose.plugins.builtin.plugins: |
|
315 | for p in nose.plugins.builtin.plugins: | |
309 | plug = p() |
|
316 | plug = p() | |
310 | if plug.name == 'doctest': |
|
317 | if plug.name == 'doctest': | |
311 | continue |
|
318 | continue | |
312 | plugins.append(plug) |
|
319 | plugins.append(plug) | |
313 |
|
320 | |||
|
321 | # We need a global ipython running in this process | |||
|
322 | globalipapp.start_ipython() | |||
|
323 | # Now nose can run | |||
314 | TestProgram(argv=argv,plugins=plugins) |
|
324 | TestProgram(argv=argv,plugins=plugins) | |
315 |
|
325 | |||
316 |
|
326 |
@@ -22,6 +22,8 b' Authors' | |||||
22 | - Fernando Perez <Fernando.Perez@berkeley.edu> |
|
22 | - Fernando Perez <Fernando.Perez@berkeley.edu> | |
23 | """ |
|
23 | """ | |
24 |
|
24 | |||
|
25 | from __future__ import absolute_import | |||
|
26 | ||||
25 | #----------------------------------------------------------------------------- |
|
27 | #----------------------------------------------------------------------------- | |
26 | # Copyright (C) 2009 The IPython Development Team |
|
28 | # Copyright (C) 2009 The IPython Development Team | |
27 | # |
|
29 | # | |
@@ -40,14 +42,16 b' import sys' | |||||
40 | import unittest |
|
42 | import unittest | |
41 | from doctest import DocTestFinder, DocTestRunner, TestResults |
|
43 | from doctest import DocTestFinder, DocTestRunner, TestResults | |
42 |
|
44 | |||
43 | # Our own |
|
45 | # Our own, a nose monkeypatch | |
44 | import nosepatch |
|
46 | from . import nosepatch | |
45 |
|
47 | |||
46 | # We already have python3-compliant code for parametric tests |
|
48 | # We already have python3-compliant code for parametric tests | |
47 | if sys.version[0]=='2': |
|
49 | if sys.version[0]=='2': | |
48 | from _paramtestpy2 import ParametricTestCase |
|
50 | from ._paramtestpy2 import ParametricTestCase | |
49 | else: |
|
51 | else: | |
50 | from _paramtestpy3 import ParametricTestCase |
|
52 | from ._paramtestpy3 import ParametricTestCase | |
|
53 | ||||
|
54 | from . import globalipapp | |||
51 |
|
55 | |||
52 | #----------------------------------------------------------------------------- |
|
56 | #----------------------------------------------------------------------------- | |
53 | # Classes and functions |
|
57 | # Classes and functions | |
@@ -68,9 +72,13 b' class IPython2PythonConverter(object):' | |||||
68 | implementation, but for now it only does prompt convertion.""" |
|
72 | implementation, but for now it only does prompt convertion.""" | |
69 |
|
73 | |||
70 | def __init__(self): |
|
74 | def __init__(self): | |
71 | self.ps1 = re.compile(r'In\ \[\d+\]: ') |
|
75 | self.rps1 = re.compile(r'In\ \[\d+\]: ') | |
72 | self.ps2 = re.compile(r'\ \ \ \.\.\.+: ') |
|
76 | self.rps2 = re.compile(r'\ \ \ \.\.\.+: ') | |
73 | self.out = re.compile(r'Out\[\d+\]: \s*?\n?') |
|
77 | self.rout = re.compile(r'Out\[\d+\]: \s*?\n?') | |
|
78 | self.pyps1 = '>>> ' | |||
|
79 | self.pyps2 = '... ' | |||
|
80 | self.rpyps1 = re.compile ('(\s*%s)(.*)$' % self.pyps1) | |||
|
81 | self.rpyps2 = re.compile ('(\s*%s)(.*)$' % self.pyps2) | |||
74 |
|
82 | |||
75 | def __call__(self, ds): |
|
83 | def __call__(self, ds): | |
76 | """Convert IPython prompts to python ones in a string.""" |
|
84 | """Convert IPython prompts to python ones in a string.""" | |
@@ -79,10 +87,34 b' class IPython2PythonConverter(object):' | |||||
79 | pyout = '' |
|
87 | pyout = '' | |
80 |
|
88 | |||
81 | dnew = ds |
|
89 | dnew = ds | |
82 | dnew = self.ps1.sub(pyps1, dnew) |
|
90 | dnew = self.rps1.sub(pyps1, dnew) | |
83 | dnew = self.ps2.sub(pyps2, dnew) |
|
91 | dnew = self.rps2.sub(pyps2, dnew) | |
84 | dnew = self.out.sub(pyout, dnew) |
|
92 | dnew = self.rout.sub(pyout, dnew) | |
85 | return dnew |
|
93 | ip = globalipapp.get_ipython() | |
|
94 | ||||
|
95 | # Convert input IPython source into valid Python. | |||
|
96 | out = [] | |||
|
97 | newline = out.append | |||
|
98 | for line in dnew.splitlines(): | |||
|
99 | ||||
|
100 | mps1 = self.rpyps1.match(line) | |||
|
101 | if mps1 is not None: | |||
|
102 | prompt, text = mps1.groups() | |||
|
103 | newline(prompt+ip.prefilter(text, False)) | |||
|
104 | continue | |||
|
105 | ||||
|
106 | mps2 = self.rpyps2.match(line) | |||
|
107 | if mps2 is not None: | |||
|
108 | prompt, text = mps2.groups() | |||
|
109 | newline(prompt+ip.prefilter(text, True)) | |||
|
110 | continue | |||
|
111 | ||||
|
112 | newline(line) | |||
|
113 | newline('') # ensure a closing newline, needed by doctest | |||
|
114 | #print "PYSRC:", '\n'.join(out) # dbg | |||
|
115 | return '\n'.join(out) | |||
|
116 | ||||
|
117 | #return dnew | |||
86 |
|
118 | |||
87 |
|
119 | |||
88 | class Doc2UnitTester(object): |
|
120 | class Doc2UnitTester(object): |
@@ -49,183 +49,14 b' from nose.util import anyp, getpackage, test_address, resolve_name, tolist' | |||||
49 |
|
49 | |||
50 | #----------------------------------------------------------------------------- |
|
50 | #----------------------------------------------------------------------------- | |
51 | # Module globals and other constants |
|
51 | # Module globals and other constants | |
|
52 | #----------------------------------------------------------------------------- | |||
52 |
|
53 | |||
53 | log = logging.getLogger(__name__) |
|
54 | log = logging.getLogger(__name__) | |
54 |
|
55 | |||
55 | ########################################################################### |
|
|||
56 | # *** HACK *** |
|
|||
57 | # We must start our own ipython object and heavily muck with it so that all the |
|
|||
58 | # modifications IPython makes to system behavior don't send the doctest |
|
|||
59 | # machinery into a fit. This code should be considered a gross hack, but it |
|
|||
60 | # gets the job done. |
|
|||
61 |
|
||||
62 | def default_argv(): |
|
|||
63 | """Return a valid default argv for creating testing instances of ipython""" |
|
|||
64 |
|
||||
65 | # Get the install directory for the user configuration and tell ipython to |
|
|||
66 | # use the default profile from there. |
|
|||
67 | from IPython.config import default |
|
|||
68 | ipcdir = os.path.dirname(default.__file__) |
|
|||
69 | ipconf = os.path.join(ipcdir,'ipython_config.py') |
|
|||
70 | #print 'conf:',ipconf # dbg |
|
|||
71 | return ['--colors=NoColor', '--no-term-title','--no-banner', |
|
|||
72 | '--config-file=%s' % ipconf] |
|
|||
73 |
|
||||
74 |
|
||||
75 | # Hack to modify the %run command so we can sync the user's namespace with the |
|
|||
76 | # test globals. Once we move over to a clean magic system, this will be done |
|
|||
77 | # with much less ugliness. |
|
|||
78 |
|
||||
79 | class py_file_finder(object): |
|
|||
80 | def __init__(self,test_filename): |
|
|||
81 | self.test_filename = test_filename |
|
|||
82 |
|
||||
83 | def __call__(self,name): |
|
|||
84 | from IPython.utils.genutils import get_py_filename |
|
|||
85 | try: |
|
|||
86 | return get_py_filename(name) |
|
|||
87 | except IOError: |
|
|||
88 | test_dir = os.path.dirname(self.test_filename) |
|
|||
89 | new_path = os.path.join(test_dir,name) |
|
|||
90 | return get_py_filename(new_path) |
|
|||
91 |
|
||||
92 |
|
||||
93 | def _run_ns_sync(self,arg_s,runner=None): |
|
|||
94 | """Modified version of %run that syncs testing namespaces. |
|
|||
95 |
|
||||
96 | This is strictly needed for running doctests that call %run. |
|
|||
97 | """ |
|
|||
98 |
|
||||
99 | # When tests call %run directly (not via doctest) these function attributes |
|
|||
100 | # are not set |
|
|||
101 | try: |
|
|||
102 | fname = _run_ns_sync.test_filename |
|
|||
103 | except AttributeError: |
|
|||
104 | fname = arg_s |
|
|||
105 |
|
||||
106 | finder = py_file_finder(fname) |
|
|||
107 | out = _ip.magic_run_ori(arg_s,runner,finder) |
|
|||
108 |
|
||||
109 | # Simliarly, there is no test_globs when a test is NOT a doctest |
|
|||
110 | if hasattr(_run_ns_sync,'test_globs'): |
|
|||
111 | _run_ns_sync.test_globs.update(_ip.user_ns) |
|
|||
112 | return out |
|
|||
113 |
|
||||
114 |
|
||||
115 | class ipnsdict(dict): |
|
|||
116 | """A special subclass of dict for use as an IPython namespace in doctests. |
|
|||
117 |
|
||||
118 | This subclass adds a simple checkpointing capability so that when testing |
|
|||
119 | machinery clears it (we use it as the test execution context), it doesn't |
|
|||
120 | get completely destroyed. |
|
|||
121 | """ |
|
|||
122 |
|
||||
123 | def __init__(self,*a): |
|
|||
124 | dict.__init__(self,*a) |
|
|||
125 | self._savedict = {} |
|
|||
126 |
|
||||
127 | def clear(self): |
|
|||
128 | dict.clear(self) |
|
|||
129 | self.update(self._savedict) |
|
|||
130 |
|
||||
131 | def _checkpoint(self): |
|
|||
132 | self._savedict.clear() |
|
|||
133 | self._savedict.update(self) |
|
|||
134 |
|
||||
135 | def update(self,other): |
|
|||
136 | self._checkpoint() |
|
|||
137 | dict.update(self,other) |
|
|||
138 |
|
||||
139 | # If '_' is in the namespace, python won't set it when executing code, |
|
|||
140 | # and we have examples that test it. So we ensure that the namespace |
|
|||
141 | # is always 'clean' of it before it's used for test code execution. |
|
|||
142 | self.pop('_',None) |
|
|||
143 |
|
||||
144 | # The builtins namespace must *always* be the real __builtin__ module, |
|
|||
145 | # else weird stuff happens. The main ipython code does have provisions |
|
|||
146 | # to ensure this after %run, but since in this class we do some |
|
|||
147 | # aggressive low-level cleaning of the execution namespace, we need to |
|
|||
148 | # correct for that ourselves, to ensure consitency with the 'real' |
|
|||
149 | # ipython. |
|
|||
150 | self['__builtins__'] = __builtin__ |
|
|||
151 |
|
||||
152 |
|
||||
153 | def start_ipython(): |
|
|||
154 | """Start a global IPython shell, which we need for IPython-specific syntax. |
|
|||
155 | """ |
|
|||
156 |
|
||||
157 | # This function should only ever run once! |
|
|||
158 | if hasattr(start_ipython,'already_called'): |
|
|||
159 | return |
|
|||
160 | start_ipython.already_called = True |
|
|||
161 |
|
||||
162 | # Ok, first time we're called, go ahead |
|
|||
163 | import new |
|
|||
164 |
|
||||
165 | import IPython |
|
|||
166 | from IPython.core import ipapp, iplib |
|
|||
167 |
|
||||
168 | def xsys(cmd): |
|
|||
169 | """Execute a command and print its output. |
|
|||
170 |
|
||||
171 | This is just a convenience function to replace the IPython system call |
|
|||
172 | with one that is more doctest-friendly. |
|
|||
173 | """ |
|
|||
174 | cmd = _ip.var_expand(cmd,depth=1) |
|
|||
175 | sys.stdout.write(commands.getoutput(cmd)) |
|
|||
176 | sys.stdout.flush() |
|
|||
177 |
|
||||
178 | # Store certain global objects that IPython modifies |
|
|||
179 | _displayhook = sys.displayhook |
|
|||
180 | _excepthook = sys.excepthook |
|
|||
181 | _main = sys.modules.get('__main__') |
|
|||
182 |
|
||||
183 | argv = default_argv() |
|
|||
184 |
|
||||
185 | # Start IPython instance. We customize it to start with minimal frills. |
|
|||
186 | user_ns,global_ns = iplib.make_user_namespaces(ipnsdict(),{}) |
|
|||
187 | ip = ipapp.IPythonApp(argv, user_ns=user_ns, user_global_ns=global_ns) |
|
|||
188 | ip.initialize() |
|
|||
189 | ip.shell.builtin_trap.set() |
|
|||
190 |
|
||||
191 | # Deactivate the various python system hooks added by ipython for |
|
|||
192 | # interactive convenience so we don't confuse the doctest system |
|
|||
193 | sys.modules['__main__'] = _main |
|
|||
194 | sys.displayhook = _displayhook |
|
|||
195 | sys.excepthook = _excepthook |
|
|||
196 |
|
||||
197 | # So that ipython magics and aliases can be doctested (they work by making |
|
|||
198 | # a call into a global _ip object) |
|
|||
199 | __builtin__._ip = ip.shell |
|
|||
200 |
|
||||
201 | # Modify the IPython system call with one that uses getoutput, so that we |
|
|||
202 | # can capture subcommands and print them to Python's stdout, otherwise the |
|
|||
203 | # doctest machinery would miss them. |
|
|||
204 | ip.shell.system = xsys |
|
|||
205 |
|
||||
206 | # Also patch our %run function in. |
|
|||
207 | im = new.instancemethod(_run_ns_sync,_ip, _ip.__class__) |
|
|||
208 | ip.shell.magic_run_ori = _ip.magic_run |
|
|||
209 | ip.shell.magic_run = im |
|
|||
210 |
|
||||
211 | # XXX - For some very bizarre reason, the loading of %history by default is |
|
|||
212 | # failing. This needs to be fixed later, but for now at least this ensures |
|
|||
213 | # that tests that use %hist run to completion. |
|
|||
214 | from IPython.core import history |
|
|||
215 | history.init_ipython(ip.shell) |
|
|||
216 | if not hasattr(ip.shell,'magic_history'): |
|
|||
217 | raise RuntimeError("Can't load magics, aborting") |
|
|||
218 |
|
||||
219 |
|
||||
220 | # The start call MUST be made here. I'm not sure yet why it doesn't work if |
|
|||
221 | # it is made later, at plugin initialization time, but in all my tests, that's |
|
|||
222 | # the case. |
|
|||
223 | start_ipython() |
|
|||
224 |
|
||||
225 | # *** END HACK *** |
|
|||
226 | ########################################################################### |
|
|||
227 |
|
56 | |||
|
57 | #----------------------------------------------------------------------------- | |||
228 | # Classes and functions |
|
58 | # Classes and functions | |
|
59 | #----------------------------------------------------------------------------- | |||
229 |
|
60 | |||
230 | def is_extension_module(filename): |
|
61 | def is_extension_module(filename): | |
231 | """Return whether the given filename is an extension module. |
|
62 | """Return whether the given filename is an extension module. | |
@@ -288,7 +119,7 b' class DocTestFinder(doctest.DocTestFinder):' | |||||
288 | Find tests for the given object and any contained objects, and |
|
119 | Find tests for the given object and any contained objects, and | |
289 | add them to `tests`. |
|
120 | add them to `tests`. | |
290 | """ |
|
121 | """ | |
291 |
|
122 | #print '_find for:', obj, name, module # dbg | ||
292 | if hasattr(obj,"skip_doctest"): |
|
123 | if hasattr(obj,"skip_doctest"): | |
293 | #print 'SKIPPING DOCTEST FOR:',obj # dbg |
|
124 | #print 'SKIPPING DOCTEST FOR:',obj # dbg | |
294 | obj = DocTestSkip(obj) |
|
125 | obj = DocTestSkip(obj) | |
@@ -396,8 +227,9 b' class DocTestCase(doctests.DocTestCase):' | |||||
396 | self._dt_runner = runner |
|
227 | self._dt_runner = runner | |
397 |
|
228 | |||
398 |
|
229 | |||
399 |
# Each doctest should remember |
|
230 | # Each doctest should remember the directory it was loaded from, so | |
400 | self._ori_dir = os.getcwd() |
|
231 | # things like %run work without too many contortions | |
|
232 | self._ori_dir = os.path.dirname(test.filename) | |||
401 |
|
233 | |||
402 | # Modified runTest from the default stdlib |
|
234 | # Modified runTest from the default stdlib | |
403 | def runTest(self): |
|
235 | def runTest(self): | |
@@ -418,6 +250,7 b' class DocTestCase(doctests.DocTestCase):' | |||||
418 | # test was originally created, in case another doctest did a |
|
250 | # test was originally created, in case another doctest did a | |
419 | # directory change. We'll restore this in the finally clause. |
|
251 | # directory change. We'll restore this in the finally clause. | |
420 | curdir = os.getcwd() |
|
252 | curdir = os.getcwd() | |
|
253 | #print 'runTest in dir:', self._ori_dir # dbg | |||
421 | os.chdir(self._ori_dir) |
|
254 | os.chdir(self._ori_dir) | |
422 |
|
255 | |||
423 | runner.DIVIDER = "-"*70 |
|
256 | runner.DIVIDER = "-"*70 | |
@@ -432,7 +265,7 b' class DocTestCase(doctests.DocTestCase):' | |||||
432 |
|
265 | |||
433 | def setUp(self): |
|
266 | def setUp(self): | |
434 | """Modified test setup that syncs with ipython namespace""" |
|
267 | """Modified test setup that syncs with ipython namespace""" | |
435 |
|
268 | #print "setUp test", self._dt_test.examples # dbg | ||
436 | if isinstance(self._dt_test.examples[0],IPExample): |
|
269 | if isinstance(self._dt_test.examples[0],IPExample): | |
437 | # for IPython examples *only*, we swap the globals with the ipython |
|
270 | # for IPython examples *only*, we swap the globals with the ipython | |
438 | # namespace, after updating it with the globals (which doctest |
|
271 | # namespace, after updating it with the globals (which doctest | |
@@ -731,8 +564,10 b' class IPDocTestRunner(doctest.DocTestRunner,object):' | |||||
731 | # attribute. Our new %run will then only make the namespace update |
|
564 | # attribute. Our new %run will then only make the namespace update | |
732 | # when called (rather than unconconditionally updating test.globs here |
|
565 | # when called (rather than unconconditionally updating test.globs here | |
733 | # for all examples, most of which won't be calling %run anyway). |
|
566 | # for all examples, most of which won't be calling %run anyway). | |
734 |
|
|
567 | #_ip._ipdoctest_test_globs = test.globs | |
735 |
|
|
568 | #_ip._ipdoctest_test_filename = test.filename | |
|
569 | ||||
|
570 | test.globs.update(_ip.user_ns) | |||
736 |
|
571 | |||
737 | return super(IPDocTestRunner,self).run(test, |
|
572 | return super(IPDocTestRunner,self).run(test, | |
738 | compileflags,out,clear_globs) |
|
573 | compileflags,out,clear_globs) | |
@@ -846,6 +681,7 b' class ExtensionDoctest(doctests.Doctest):' | |||||
846 |
|
681 | |||
847 |
|
682 | |||
848 | def loadTestsFromFile(self, filename): |
|
683 | def loadTestsFromFile(self, filename): | |
|
684 | #print "ipdoctest - from file", filename # dbg | |||
849 | if is_extension_module(filename): |
|
685 | if is_extension_module(filename): | |
850 | for t in self.loadTestsFromExtensionModule(filename): |
|
686 | for t in self.loadTestsFromExtensionModule(filename): | |
851 | yield t |
|
687 | yield t | |
@@ -872,7 +708,7 b' class ExtensionDoctest(doctests.Doctest):' | |||||
872 | Modified version that accepts extension modules as valid containers for |
|
708 | Modified version that accepts extension modules as valid containers for | |
873 | doctests. |
|
709 | doctests. | |
874 | """ |
|
710 | """ | |
875 |
# |
|
711 | #print '*** ipdoctest- wantFile:',filename # dbg | |
876 |
|
712 | |||
877 | for pat in self.exclude_patterns: |
|
713 | for pat in self.exclude_patterns: | |
878 | if pat.search(filename): |
|
714 | if pat.search(filename): | |
@@ -890,11 +726,12 b' class IPythonDoctest(ExtensionDoctest):' | |||||
890 | """ |
|
726 | """ | |
891 | name = 'ipdoctest' # call nosetests with --with-ipdoctest |
|
727 | name = 'ipdoctest' # call nosetests with --with-ipdoctest | |
892 | enabled = True |
|
728 | enabled = True | |
893 |
|
729 | |||
894 | def makeTest(self, obj, parent): |
|
730 | def makeTest(self, obj, parent): | |
895 | """Look for doctests in the given object, which will be a |
|
731 | """Look for doctests in the given object, which will be a | |
896 | function, method or class. |
|
732 | function, method or class. | |
897 | """ |
|
733 | """ | |
|
734 | #print 'Plugin analyzing:', obj, parent # dbg | |||
898 | # always use whitespace and ellipsis options |
|
735 | # always use whitespace and ellipsis options | |
899 | optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS |
|
736 | optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS | |
900 |
|
737 | |||
@@ -909,6 +746,7 b' class IPythonDoctest(ExtensionDoctest):' | |||||
909 | checker=self.checker) |
|
746 | checker=self.checker) | |
910 |
|
747 | |||
911 | def options(self, parser, env=os.environ): |
|
748 | def options(self, parser, env=os.environ): | |
|
749 | #print "Options for nose plugin:", self.name # dbg | |||
912 | Plugin.options(self, parser, env) |
|
750 | Plugin.options(self, parser, env) | |
913 | parser.add_option('--ipdoctest-tests', action='store_true', |
|
751 | parser.add_option('--ipdoctest-tests', action='store_true', | |
914 | dest='ipdoctest_tests', |
|
752 | dest='ipdoctest_tests', | |
@@ -929,6 +767,7 b' class IPythonDoctest(ExtensionDoctest):' | |||||
929 | parser.set_defaults(ipdoctest_extension=tolist(env_setting)) |
|
767 | parser.set_defaults(ipdoctest_extension=tolist(env_setting)) | |
930 |
|
768 | |||
931 | def configure(self, options, config): |
|
769 | def configure(self, options, config): | |
|
770 | #print "Configuring nose plugin:", self.name # dbg | |||
932 | Plugin.configure(self, options, config) |
|
771 | Plugin.configure(self, options, config) | |
933 | self.doctest_tests = options.ipdoctest_tests |
|
772 | self.doctest_tests = options.ipdoctest_tests | |
934 | self.extension = tolist(options.ipdoctest_extension) |
|
773 | self.extension = tolist(options.ipdoctest_extension) |
@@ -29,10 +29,11 b' Authors' | |||||
29 | import os |
|
29 | import os | |
30 | import re |
|
30 | import re | |
31 | import sys |
|
31 | import sys | |
|
32 | import tempfile | |||
32 |
|
33 | |||
33 | import nose.tools as nt |
|
34 | import nose.tools as nt | |
34 |
|
35 | |||
35 | from IPython.utils import genutils |
|
36 | from IPython.utils import genutils, platutils | |
36 |
|
37 | |||
37 | #----------------------------------------------------------------------------- |
|
38 | #----------------------------------------------------------------------------- | |
38 | # Globals |
|
39 | # Globals | |
@@ -128,5 +129,93 b' def parse_test_output(txt):' | |||||
128 | # If the input didn't match any of these forms, assume no error/failures |
|
129 | # If the input didn't match any of these forms, assume no error/failures | |
129 | return 0, 0 |
|
130 | return 0, 0 | |
130 |
|
131 | |||
|
132 | ||||
131 | # So nose doesn't think this is a test |
|
133 | # So nose doesn't think this is a test | |
132 | parse_test_output.__test__ = False |
|
134 | parse_test_output.__test__ = False | |
|
135 | ||||
|
136 | ||||
|
137 | def temp_pyfile(src, ext='.py'): | |||
|
138 | """Make a temporary python file, return filename and filehandle. | |||
|
139 | ||||
|
140 | Parameters | |||
|
141 | ---------- | |||
|
142 | src : string or list of strings (no need for ending newlines if list) | |||
|
143 | Source code to be written to the file. | |||
|
144 | ||||
|
145 | ext : optional, string | |||
|
146 | Extension for the generated file. | |||
|
147 | ||||
|
148 | Returns | |||
|
149 | ------- | |||
|
150 | (filename, open filehandle) | |||
|
151 | It is the caller's responsibility to close the open file and unlink it. | |||
|
152 | """ | |||
|
153 | fname = tempfile.mkstemp(ext)[1] | |||
|
154 | f = open(fname,'w') | |||
|
155 | f.write(src) | |||
|
156 | f.flush() | |||
|
157 | return fname, f | |||
|
158 | ||||
|
159 | ||||
|
160 | def default_argv(): | |||
|
161 | """Return a valid default argv for creating testing instances of ipython""" | |||
|
162 | ||||
|
163 | # Get the install directory for the user configuration and tell ipython to | |||
|
164 | # use the default profile from there. | |||
|
165 | from IPython.config import default | |||
|
166 | ipcdir = os.path.dirname(default.__file__) | |||
|
167 | ipconf = os.path.join(ipcdir,'ipython_config.py') | |||
|
168 | #print 'conf:',ipconf # dbg | |||
|
169 | return ['--colors=NoColor', '--no-term-title','--no-banner', | |||
|
170 | '--config-file=%s' % ipconf, '--autocall=0', '--quick'] | |||
|
171 | ||||
|
172 | ||||
|
173 | def ipexec(fname): | |||
|
174 | """Utility to call 'ipython filename'. | |||
|
175 | ||||
|
176 | Starts IPython witha minimal and safe configuration to make startup as fast | |||
|
177 | as possible. | |||
|
178 | ||||
|
179 | Note that this starts IPython in a subprocess! | |||
|
180 | ||||
|
181 | Parameters | |||
|
182 | ---------- | |||
|
183 | fname : str | |||
|
184 | Name of file to be executed (should have .py or .ipy extension). | |||
|
185 | ||||
|
186 | Returns | |||
|
187 | ------- | |||
|
188 | (stdout, stderr) of ipython subprocess. | |||
|
189 | """ | |||
|
190 | _ip = get_ipython() | |||
|
191 | test_dir = os.path.dirname(__file__) | |||
|
192 | full_fname = os.path.join(test_dir, fname) | |||
|
193 | ipython_cmd = platutils.find_cmd('ipython') | |||
|
194 | cmdargs = ' '.join(default_argv()) | |||
|
195 | return genutils.getoutputerror('%s %s' % (ipython_cmd, full_fname)) | |||
|
196 | ||||
|
197 | ||||
|
198 | def ipexec_validate(fname, expected_out, expected_err=None): | |||
|
199 | """Utility to call 'ipython filename' and validate output/error. | |||
|
200 | ||||
|
201 | This function raises an AssertionError if the validation fails. | |||
|
202 | ||||
|
203 | Note that this starts IPython in a subprocess! | |||
|
204 | ||||
|
205 | Parameters | |||
|
206 | ---------- | |||
|
207 | fname : str | |||
|
208 | Name of the file to be executed (should have .py or .ipy extension). | |||
|
209 | ||||
|
210 | expected_out : str | |||
|
211 | Expected stdout of the process. | |||
|
212 | ||||
|
213 | Returns | |||
|
214 | ------- | |||
|
215 | None | |||
|
216 | """ | |||
|
217 | ||||
|
218 | out, err = ipexec(fname) | |||
|
219 | nt.assert_equals(out.strip(), expected_out.strip()) | |||
|
220 | if expected_err: | |||
|
221 | nt.assert_equals(err.strip(), expected_err.strip()) |
General Comments 0
You need to be logged in to leave comments.
Login now