##// END OF EJS Templates
Use AssertPrints in tests for autoreload extension.
Thomas Kluyver -
Show More
@@ -1,307 +1,300 b''
1 import os
1 import os
2 import sys
2 import sys
3 import tempfile
3 import tempfile
4 import shutil
4 import shutil
5 import random
5 import random
6 import time
6 import time
7 from StringIO import StringIO
7 from StringIO import StringIO
8
8
9 import nose.tools as nt
9 import nose.tools as nt
10 import IPython.testing.tools as tt
10
11
11 from IPython.extensions.autoreload import AutoreloadInterface
12 from IPython.extensions.autoreload import AutoreloadInterface
12 from IPython.core.hooks import TryNext
13 from IPython.core.hooks import TryNext
13
14
14 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
15 # Test fixture
16 # Test fixture
16 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
17
18
18 class FakeShell(object):
19 class FakeShell(object):
19 def __init__(self):
20 def __init__(self):
20 self.ns = {}
21 self.ns = {}
21 self.reloader = AutoreloadInterface()
22 self.reloader = AutoreloadInterface()
22
23
23 def run_code(self, code):
24 def run_code(self, code):
24 try:
25 try:
25 self.reloader.pre_run_code_hook(self)
26 self.reloader.pre_run_code_hook(self)
26 except TryNext:
27 except TryNext:
27 pass
28 pass
28 exec code in self.ns
29 exec code in self.ns
29
30
30 def push(self, items):
31 def push(self, items):
31 self.ns.update(items)
32 self.ns.update(items)
32
33
33 def magic_autoreload(self, parameter):
34 def magic_autoreload(self, parameter):
34 self.reloader.magic_autoreload(self, parameter)
35 self.reloader.magic_autoreload(self, parameter)
35
36
36 def magic_aimport(self, parameter, stream=None):
37 def magic_aimport(self, parameter, stream=None):
37 self.reloader.magic_aimport(self, parameter, stream=stream)
38 self.reloader.magic_aimport(self, parameter, stream=stream)
38
39
39
40
40 class Fixture(object):
41 class Fixture(object):
41 """Fixture for creating test module files"""
42 """Fixture for creating test module files"""
42
43
43 test_dir = None
44 test_dir = None
44 old_sys_path = None
45 old_sys_path = None
45 filename_chars = "abcdefghijklmopqrstuvwxyz0123456789"
46 filename_chars = "abcdefghijklmopqrstuvwxyz0123456789"
46
47
47 def setUp(self):
48 def setUp(self):
48 self.test_dir = tempfile.mkdtemp()
49 self.test_dir = tempfile.mkdtemp()
49 self.old_sys_path = list(sys.path)
50 self.old_sys_path = list(sys.path)
50 sys.path.insert(0, self.test_dir)
51 sys.path.insert(0, self.test_dir)
51 self.shell = FakeShell()
52 self.shell = FakeShell()
52
53
53 def tearDown(self):
54 def tearDown(self):
54 shutil.rmtree(self.test_dir)
55 shutil.rmtree(self.test_dir)
55 sys.path = self.old_sys_path
56 sys.path = self.old_sys_path
56 self.shell.reloader.enabled = False
57 self.shell.reloader.enabled = False
57
58
58 self.test_dir = None
59 self.test_dir = None
59 self.old_sys_path = None
60 self.old_sys_path = None
60 self.shell = None
61 self.shell = None
61
62
62 def get_module(self):
63 def get_module(self):
63 module_name = "tmpmod_" + "".join(random.sample(self.filename_chars,20))
64 module_name = "tmpmod_" + "".join(random.sample(self.filename_chars,20))
64 if module_name in sys.modules:
65 if module_name in sys.modules:
65 del sys.modules[module_name]
66 del sys.modules[module_name]
66 file_name = os.path.join(self.test_dir, module_name + ".py")
67 file_name = os.path.join(self.test_dir, module_name + ".py")
67 return module_name, file_name
68 return module_name, file_name
68
69
69 def write_file(self, filename, content):
70 def write_file(self, filename, content):
70 """
71 """
71 Write a file, and force a timestamp difference of at least one second
72 Write a file, and force a timestamp difference of at least one second
72
73
73 Notes
74 Notes
74 -----
75 -----
75 Python's .pyc files record the timestamp of their compilation
76 Python's .pyc files record the timestamp of their compilation
76 with a time resolution of one second.
77 with a time resolution of one second.
77
78
78 Therefore, we need to force a timestamp difference between .py
79 Therefore, we need to force a timestamp difference between .py
79 and .pyc, without having the .py file be timestamped in the
80 and .pyc, without having the .py file be timestamped in the
80 future, and without changing the timestamp of the .pyc file
81 future, and without changing the timestamp of the .pyc file
81 (because that is stored in the file). The only reliable way
82 (because that is stored in the file). The only reliable way
82 to achieve this seems to be to sleep.
83 to achieve this seems to be to sleep.
83
84
84 """
85 """
85
86
86 # Sleep one second + eps
87 # Sleep one second + eps
87 time.sleep(1.05)
88 time.sleep(1.05)
88
89
89 # Write
90 # Write
90 f = open(filename, 'w')
91 f = open(filename, 'w')
91 try:
92 try:
92 f.write(content)
93 f.write(content)
93 finally:
94 finally:
94 f.close()
95 f.close()
95
96
96 def new_module(self, code):
97 def new_module(self, code):
97 mod_name, mod_fn = self.get_module()
98 mod_name, mod_fn = self.get_module()
98 f = open(mod_fn, 'w')
99 f = open(mod_fn, 'w')
99 try:
100 try:
100 f.write(code)
101 f.write(code)
101 finally:
102 finally:
102 f.close()
103 f.close()
103 return mod_name, mod_fn
104 return mod_name, mod_fn
104
105
105 #-----------------------------------------------------------------------------
106 #-----------------------------------------------------------------------------
106 # Test automatic reloading
107 # Test automatic reloading
107 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
108
109
109 class TestAutoreload(Fixture):
110 class TestAutoreload(Fixture):
110 def _check_smoketest(self, use_aimport=True):
111 def _check_smoketest(self, use_aimport=True):
111 """
112 """
112 Functional test for the automatic reloader using either
113 Functional test for the automatic reloader using either
113 '%autoreload 1' or '%autoreload 2'
114 '%autoreload 1' or '%autoreload 2'
114 """
115 """
115
116
116 mod_name, mod_fn = self.new_module("""
117 mod_name, mod_fn = self.new_module("""
117 x = 9
118 x = 9
118
119
119 z = 123 # this item will be deleted
120 z = 123 # this item will be deleted
120
121
121 def foo(y):
122 def foo(y):
122 return y + 3
123 return y + 3
123
124
124 class Baz(object):
125 class Baz(object):
125 def __init__(self, x):
126 def __init__(self, x):
126 self.x = x
127 self.x = x
127 def bar(self, y):
128 def bar(self, y):
128 return self.x + y
129 return self.x + y
129 @property
130 @property
130 def quux(self):
131 def quux(self):
131 return 42
132 return 42
132 def zzz(self):
133 def zzz(self):
133 '''This method will be deleted below'''
134 '''This method will be deleted below'''
134 return 99
135 return 99
135
136
136 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
137 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
137 def foo(self):
138 def foo(self):
138 return 1
139 return 1
139 """)
140 """)
140
141
141 #
142 #
142 # Import module, and mark for reloading
143 # Import module, and mark for reloading
143 #
144 #
144 if use_aimport:
145 if use_aimport:
145 self.shell.magic_autoreload("1")
146 self.shell.magic_autoreload("1")
146 self.shell.magic_aimport(mod_name)
147 self.shell.magic_aimport(mod_name)
147 stream = StringIO()
148 stream = StringIO()
148 self.shell.magic_aimport("", stream=stream)
149 self.shell.magic_aimport("", stream=stream)
149 nt.assert_true(("Modules to reload:\n%s" % mod_name) in
150 nt.assert_true(("Modules to reload:\n%s" % mod_name) in
150 stream.getvalue())
151 stream.getvalue())
151
152
152 nt.assert_raises(
153 nt.assert_raises(
153 ImportError,
154 ImportError,
154 self.shell.magic_aimport, "tmpmod_as318989e89ds")
155 self.shell.magic_aimport, "tmpmod_as318989e89ds")
155 else:
156 else:
156 self.shell.magic_autoreload("2")
157 self.shell.magic_autoreload("2")
157 self.shell.run_code("import %s" % mod_name)
158 self.shell.run_code("import %s" % mod_name)
158 stream = StringIO()
159 stream = StringIO()
159 self.shell.magic_aimport("", stream=stream)
160 self.shell.magic_aimport("", stream=stream)
160 nt.assert_true("Modules to reload:\nall-except-skipped" in
161 nt.assert_true("Modules to reload:\nall-except-skipped" in
161 stream.getvalue())
162 stream.getvalue())
162 nt.assert_true(mod_name in self.shell.ns)
163 nt.assert_true(mod_name in self.shell.ns)
163
164
164 mod = sys.modules[mod_name]
165 mod = sys.modules[mod_name]
165
166
166 #
167 #
167 # Test module contents
168 # Test module contents
168 #
169 #
169 old_foo = mod.foo
170 old_foo = mod.foo
170 old_obj = mod.Baz(9)
171 old_obj = mod.Baz(9)
171 old_obj2 = mod.Bar()
172 old_obj2 = mod.Bar()
172
173
173 def check_module_contents():
174 def check_module_contents():
174 nt.assert_equal(mod.x, 9)
175 nt.assert_equal(mod.x, 9)
175 nt.assert_equal(mod.z, 123)
176 nt.assert_equal(mod.z, 123)
176
177
177 nt.assert_equal(old_foo(0), 3)
178 nt.assert_equal(old_foo(0), 3)
178 nt.assert_equal(mod.foo(0), 3)
179 nt.assert_equal(mod.foo(0), 3)
179
180
180 obj = mod.Baz(9)
181 obj = mod.Baz(9)
181 nt.assert_equal(old_obj.bar(1), 10)
182 nt.assert_equal(old_obj.bar(1), 10)
182 nt.assert_equal(obj.bar(1), 10)
183 nt.assert_equal(obj.bar(1), 10)
183 nt.assert_equal(obj.quux, 42)
184 nt.assert_equal(obj.quux, 42)
184 nt.assert_equal(obj.zzz(), 99)
185 nt.assert_equal(obj.zzz(), 99)
185
186
186 obj2 = mod.Bar()
187 obj2 = mod.Bar()
187 nt.assert_equal(old_obj2.foo(), 1)
188 nt.assert_equal(old_obj2.foo(), 1)
188 nt.assert_equal(obj2.foo(), 1)
189 nt.assert_equal(obj2.foo(), 1)
189
190
190 check_module_contents()
191 check_module_contents()
191
192
192 #
193 #
193 # Simulate a failed reload: no reload should occur and exactly
194 # Simulate a failed reload: no reload should occur and exactly
194 # one error message should be printed
195 # one error message should be printed
195 #
196 #
196 self.write_file(mod_fn, """
197 self.write_file(mod_fn, """
197 a syntax error
198 a syntax error
198 """)
199 """)
199
200
200 old_stderr = sys.stderr
201 with tt.AssertPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
201 new_stderr = StringIO()
202 sys.stderr = new_stderr
203 try:
204 self.shell.run_code("pass") # trigger reload
202 self.shell.run_code("pass") # trigger reload
203 with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
205 self.shell.run_code("pass") # trigger another reload
204 self.shell.run_code("pass") # trigger another reload
206 check_module_contents()
205 check_module_contents()
207 finally:
208 sys.stderr = old_stderr
209
210 nt.assert_true(('[autoreload of %s failed:' % mod_name) in
211 new_stderr.getvalue())
212 nt.assert_equal(new_stderr.getvalue().count('[autoreload of'), 1)
213
206
214 #
207 #
215 # Rewrite module (this time reload should succeed)
208 # Rewrite module (this time reload should succeed)
216 #
209 #
217 self.write_file(mod_fn, """
210 self.write_file(mod_fn, """
218 x = 10
211 x = 10
219
212
220 def foo(y):
213 def foo(y):
221 return y + 4
214 return y + 4
222
215
223 class Baz(object):
216 class Baz(object):
224 def __init__(self, x):
217 def __init__(self, x):
225 self.x = x
218 self.x = x
226 def bar(self, y):
219 def bar(self, y):
227 return self.x + y + 1
220 return self.x + y + 1
228 @property
221 @property
229 def quux(self):
222 def quux(self):
230 return 43
223 return 43
231
224
232 class Bar: # old-style class
225 class Bar: # old-style class
233 def foo(self):
226 def foo(self):
234 return 2
227 return 2
235 """)
228 """)
236
229
237 def check_module_contents():
230 def check_module_contents():
238 nt.assert_equal(mod.x, 10)
231 nt.assert_equal(mod.x, 10)
239 nt.assert_false(hasattr(mod, 'z'))
232 nt.assert_false(hasattr(mod, 'z'))
240
233
241 nt.assert_equal(old_foo(0), 4) # superreload magic!
234 nt.assert_equal(old_foo(0), 4) # superreload magic!
242 nt.assert_equal(mod.foo(0), 4)
235 nt.assert_equal(mod.foo(0), 4)
243
236
244 obj = mod.Baz(9)
237 obj = mod.Baz(9)
245 nt.assert_equal(old_obj.bar(1), 11) # superreload magic!
238 nt.assert_equal(old_obj.bar(1), 11) # superreload magic!
246 nt.assert_equal(obj.bar(1), 11)
239 nt.assert_equal(obj.bar(1), 11)
247
240
248 nt.assert_equal(old_obj.quux, 43)
241 nt.assert_equal(old_obj.quux, 43)
249 nt.assert_equal(obj.quux, 43)
242 nt.assert_equal(obj.quux, 43)
250
243
251 nt.assert_false(hasattr(old_obj, 'zzz'))
244 nt.assert_false(hasattr(old_obj, 'zzz'))
252 nt.assert_false(hasattr(obj, 'zzz'))
245 nt.assert_false(hasattr(obj, 'zzz'))
253
246
254 obj2 = mod.Bar()
247 obj2 = mod.Bar()
255 nt.assert_equal(old_obj2.foo(), 2)
248 nt.assert_equal(old_obj2.foo(), 2)
256 nt.assert_equal(obj2.foo(), 2)
249 nt.assert_equal(obj2.foo(), 2)
257
250
258 self.shell.run_code("pass") # trigger reload
251 self.shell.run_code("pass") # trigger reload
259 check_module_contents()
252 check_module_contents()
260
253
261 #
254 #
262 # Another failure case: deleted file (shouldn't reload)
255 # Another failure case: deleted file (shouldn't reload)
263 #
256 #
264 os.unlink(mod_fn)
257 os.unlink(mod_fn)
265
258
266 self.shell.run_code("pass") # trigger reload
259 self.shell.run_code("pass") # trigger reload
267 check_module_contents()
260 check_module_contents()
268
261
269 #
262 #
270 # Disable autoreload and rewrite module: no reload should occur
263 # Disable autoreload and rewrite module: no reload should occur
271 #
264 #
272 if use_aimport:
265 if use_aimport:
273 self.shell.magic_aimport("-" + mod_name)
266 self.shell.magic_aimport("-" + mod_name)
274 stream = StringIO()
267 stream = StringIO()
275 self.shell.magic_aimport("", stream=stream)
268 self.shell.magic_aimport("", stream=stream)
276 nt.assert_true(("Modules to skip:\n%s" % mod_name) in
269 nt.assert_true(("Modules to skip:\n%s" % mod_name) in
277 stream.getvalue())
270 stream.getvalue())
278
271
279 # This should succeed, although no such module exists
272 # This should succeed, although no such module exists
280 self.shell.magic_aimport("-tmpmod_as318989e89ds")
273 self.shell.magic_aimport("-tmpmod_as318989e89ds")
281 else:
274 else:
282 self.shell.magic_autoreload("0")
275 self.shell.magic_autoreload("0")
283
276
284 self.write_file(mod_fn, """
277 self.write_file(mod_fn, """
285 x = -99
278 x = -99
286 """)
279 """)
287
280
288 self.shell.run_code("pass") # trigger reload
281 self.shell.run_code("pass") # trigger reload
289 self.shell.run_code("pass")
282 self.shell.run_code("pass")
290 check_module_contents()
283 check_module_contents()
291
284
292 #
285 #
293 # Re-enable autoreload: reload should now occur
286 # Re-enable autoreload: reload should now occur
294 #
287 #
295 if use_aimport:
288 if use_aimport:
296 self.shell.magic_aimport(mod_name)
289 self.shell.magic_aimport(mod_name)
297 else:
290 else:
298 self.shell.magic_autoreload("")
291 self.shell.magic_autoreload("")
299
292
300 self.shell.run_code("pass") # trigger reload
293 self.shell.run_code("pass") # trigger reload
301 nt.assert_equal(mod.x, -99)
294 nt.assert_equal(mod.x, -99)
302
295
303 def test_smoketest_aimport(self):
296 def test_smoketest_aimport(self):
304 self._check_smoketest(use_aimport=True)
297 self._check_smoketest(use_aimport=True)
305
298
306 def test_smoketest_autoreload(self):
299 def test_smoketest_autoreload(self):
307 self._check_smoketest(use_aimport=False)
300 self._check_smoketest(use_aimport=False)
@@ -1,388 +1,400 b''
1 """Generic testing tools that do NOT depend on Twisted.
1 """Generic testing tools that do NOT depend on Twisted.
2
2
3 In particular, this module exposes a set of top-level assert* functions that
3 In particular, this module exposes a set of top-level assert* functions that
4 can be used in place of nose.tools.assert* in method generators (the ones in
4 can be used in place of nose.tools.assert* in method generators (the ones in
5 nose can not, at least as of nose 0.10.4).
5 nose can not, at least as of nose 0.10.4).
6
6
7 Note: our testing package contains testing.util, which does depend on Twisted
7 Note: our testing package contains testing.util, which does depend on Twisted
8 and provides utilities for tests that manage Deferreds. All testing support
8 and provides utilities for tests that manage Deferreds. All testing support
9 tools that only depend on nose, IPython or the standard library should go here
9 tools that only depend on nose, IPython or the standard library should go here
10 instead.
10 instead.
11
11
12
12
13 Authors
13 Authors
14 -------
14 -------
15 - Fernando Perez <Fernando.Perez@berkeley.edu>
15 - Fernando Perez <Fernando.Perez@berkeley.edu>
16 """
16 """
17
17
18 from __future__ import absolute_import
18 from __future__ import absolute_import
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Copyright (C) 2009 The IPython Development Team
21 # Copyright (C) 2009 The IPython Development Team
22 #
22 #
23 # Distributed under the terms of the BSD License. The full license is in
23 # Distributed under the terms of the BSD License. The full license is in
24 # the file COPYING, distributed as part of this software.
24 # the file COPYING, distributed as part of this software.
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Imports
28 # Imports
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 import os
31 import os
32 import re
32 import re
33 import sys
33 import sys
34 import tempfile
34 import tempfile
35
35
36 from contextlib import contextmanager
36 from contextlib import contextmanager
37 from io import StringIO
37 from io import StringIO
38
38
39 try:
39 try:
40 # These tools are used by parts of the runtime, so we make the nose
40 # These tools are used by parts of the runtime, so we make the nose
41 # dependency optional at this point. Nose is a hard dependency to run the
41 # dependency optional at this point. Nose is a hard dependency to run the
42 # test suite, but NOT to use ipython itself.
42 # test suite, but NOT to use ipython itself.
43 import nose.tools as nt
43 import nose.tools as nt
44 has_nose = True
44 has_nose = True
45 except ImportError:
45 except ImportError:
46 has_nose = False
46 has_nose = False
47
47
48 from IPython.config.loader import Config
48 from IPython.config.loader import Config
49 from IPython.utils.process import find_cmd, getoutputerror
49 from IPython.utils.process import find_cmd, getoutputerror
50 from IPython.utils.text import list_strings, getdefaultencoding
50 from IPython.utils.text import list_strings, getdefaultencoding
51 from IPython.utils.io import temp_pyfile, Tee
51 from IPython.utils.io import temp_pyfile, Tee
52 from IPython.utils import py3compat
52 from IPython.utils import py3compat
53
53
54 from . import decorators as dec
54 from . import decorators as dec
55 from . import skipdoctest
55 from . import skipdoctest
56
56
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 # Globals
58 # Globals
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60
60
61 # Make a bunch of nose.tools assert wrappers that can be used in test
61 # Make a bunch of nose.tools assert wrappers that can be used in test
62 # generators. This will expose an assert* function for each one in nose.tools.
62 # generators. This will expose an assert* function for each one in nose.tools.
63
63
64 _tpl = """
64 _tpl = """
65 def %(name)s(*a,**kw):
65 def %(name)s(*a,**kw):
66 return nt.%(name)s(*a,**kw)
66 return nt.%(name)s(*a,**kw)
67 """
67 """
68
68
69 if has_nose:
69 if has_nose:
70 for _x in [a for a in dir(nt) if a.startswith('assert')]:
70 for _x in [a for a in dir(nt) if a.startswith('assert')]:
71 exec _tpl % dict(name=_x)
71 exec _tpl % dict(name=_x)
72
72
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74 # Functions and classes
74 # Functions and classes
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76
76
77 # The docstring for full_path doctests differently on win32 (different path
77 # The docstring for full_path doctests differently on win32 (different path
78 # separator) so just skip the doctest there. The example remains informative.
78 # separator) so just skip the doctest there. The example remains informative.
79 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
79 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
80
80
81 @doctest_deco
81 @doctest_deco
82 def full_path(startPath,files):
82 def full_path(startPath,files):
83 """Make full paths for all the listed files, based on startPath.
83 """Make full paths for all the listed files, based on startPath.
84
84
85 Only the base part of startPath is kept, since this routine is typically
85 Only the base part of startPath is kept, since this routine is typically
86 used with a script's __file__ variable as startPath. The base of startPath
86 used with a script's __file__ variable as startPath. The base of startPath
87 is then prepended to all the listed files, forming the output list.
87 is then prepended to all the listed files, forming the output list.
88
88
89 Parameters
89 Parameters
90 ----------
90 ----------
91 startPath : string
91 startPath : string
92 Initial path to use as the base for the results. This path is split
92 Initial path to use as the base for the results. This path is split
93 using os.path.split() and only its first component is kept.
93 using os.path.split() and only its first component is kept.
94
94
95 files : string or list
95 files : string or list
96 One or more files.
96 One or more files.
97
97
98 Examples
98 Examples
99 --------
99 --------
100
100
101 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
101 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
102 ['/foo/a.txt', '/foo/b.txt']
102 ['/foo/a.txt', '/foo/b.txt']
103
103
104 >>> full_path('/foo',['a.txt','b.txt'])
104 >>> full_path('/foo',['a.txt','b.txt'])
105 ['/a.txt', '/b.txt']
105 ['/a.txt', '/b.txt']
106
106
107 If a single file is given, the output is still a list:
107 If a single file is given, the output is still a list:
108 >>> full_path('/foo','a.txt')
108 >>> full_path('/foo','a.txt')
109 ['/a.txt']
109 ['/a.txt']
110 """
110 """
111
111
112 files = list_strings(files)
112 files = list_strings(files)
113 base = os.path.split(startPath)[0]
113 base = os.path.split(startPath)[0]
114 return [ os.path.join(base,f) for f in files ]
114 return [ os.path.join(base,f) for f in files ]
115
115
116
116
117 def parse_test_output(txt):
117 def parse_test_output(txt):
118 """Parse the output of a test run and return errors, failures.
118 """Parse the output of a test run and return errors, failures.
119
119
120 Parameters
120 Parameters
121 ----------
121 ----------
122 txt : str
122 txt : str
123 Text output of a test run, assumed to contain a line of one of the
123 Text output of a test run, assumed to contain a line of one of the
124 following forms::
124 following forms::
125 'FAILED (errors=1)'
125 'FAILED (errors=1)'
126 'FAILED (failures=1)'
126 'FAILED (failures=1)'
127 'FAILED (errors=1, failures=1)'
127 'FAILED (errors=1, failures=1)'
128
128
129 Returns
129 Returns
130 -------
130 -------
131 nerr, nfail: number of errors and failures.
131 nerr, nfail: number of errors and failures.
132 """
132 """
133
133
134 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
134 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
135 if err_m:
135 if err_m:
136 nerr = int(err_m.group(1))
136 nerr = int(err_m.group(1))
137 nfail = 0
137 nfail = 0
138 return nerr, nfail
138 return nerr, nfail
139
139
140 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
140 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
141 if fail_m:
141 if fail_m:
142 nerr = 0
142 nerr = 0
143 nfail = int(fail_m.group(1))
143 nfail = int(fail_m.group(1))
144 return nerr, nfail
144 return nerr, nfail
145
145
146 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
146 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
147 re.MULTILINE)
147 re.MULTILINE)
148 if both_m:
148 if both_m:
149 nerr = int(both_m.group(1))
149 nerr = int(both_m.group(1))
150 nfail = int(both_m.group(2))
150 nfail = int(both_m.group(2))
151 return nerr, nfail
151 return nerr, nfail
152
152
153 # If the input didn't match any of these forms, assume no error/failures
153 # If the input didn't match any of these forms, assume no error/failures
154 return 0, 0
154 return 0, 0
155
155
156
156
157 # So nose doesn't think this is a test
157 # So nose doesn't think this is a test
158 parse_test_output.__test__ = False
158 parse_test_output.__test__ = False
159
159
160
160
161 def default_argv():
161 def default_argv():
162 """Return a valid default argv for creating testing instances of ipython"""
162 """Return a valid default argv for creating testing instances of ipython"""
163
163
164 return ['--quick', # so no config file is loaded
164 return ['--quick', # so no config file is loaded
165 # Other defaults to minimize side effects on stdout
165 # Other defaults to minimize side effects on stdout
166 '--colors=NoColor', '--no-term-title','--no-banner',
166 '--colors=NoColor', '--no-term-title','--no-banner',
167 '--autocall=0']
167 '--autocall=0']
168
168
169
169
170 def default_config():
170 def default_config():
171 """Return a config object with good defaults for testing."""
171 """Return a config object with good defaults for testing."""
172 config = Config()
172 config = Config()
173 config.TerminalInteractiveShell.colors = 'NoColor'
173 config.TerminalInteractiveShell.colors = 'NoColor'
174 config.TerminalTerminalInteractiveShell.term_title = False,
174 config.TerminalTerminalInteractiveShell.term_title = False,
175 config.TerminalInteractiveShell.autocall = 0
175 config.TerminalInteractiveShell.autocall = 0
176 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
176 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
177 config.HistoryManager.db_cache_size = 10000
177 config.HistoryManager.db_cache_size = 10000
178 return config
178 return config
179
179
180
180
181 def ipexec(fname, options=None):
181 def ipexec(fname, options=None):
182 """Utility to call 'ipython filename'.
182 """Utility to call 'ipython filename'.
183
183
184 Starts IPython witha minimal and safe configuration to make startup as fast
184 Starts IPython witha minimal and safe configuration to make startup as fast
185 as possible.
185 as possible.
186
186
187 Note that this starts IPython in a subprocess!
187 Note that this starts IPython in a subprocess!
188
188
189 Parameters
189 Parameters
190 ----------
190 ----------
191 fname : str
191 fname : str
192 Name of file to be executed (should have .py or .ipy extension).
192 Name of file to be executed (should have .py or .ipy extension).
193
193
194 options : optional, list
194 options : optional, list
195 Extra command-line flags to be passed to IPython.
195 Extra command-line flags to be passed to IPython.
196
196
197 Returns
197 Returns
198 -------
198 -------
199 (stdout, stderr) of ipython subprocess.
199 (stdout, stderr) of ipython subprocess.
200 """
200 """
201 if options is None: options = []
201 if options is None: options = []
202
202
203 # For these subprocess calls, eliminate all prompt printing so we only see
203 # For these subprocess calls, eliminate all prompt printing so we only see
204 # output from script execution
204 # output from script execution
205 prompt_opts = [ '--InteractiveShell.prompt_in1=""',
205 prompt_opts = [ '--InteractiveShell.prompt_in1=""',
206 '--InteractiveShell.prompt_in2=""',
206 '--InteractiveShell.prompt_in2=""',
207 '--InteractiveShell.prompt_out=""'
207 '--InteractiveShell.prompt_out=""'
208 ]
208 ]
209 cmdargs = ' '.join(default_argv() + prompt_opts + options)
209 cmdargs = ' '.join(default_argv() + prompt_opts + options)
210
210
211 _ip = get_ipython()
211 _ip = get_ipython()
212 test_dir = os.path.dirname(__file__)
212 test_dir = os.path.dirname(__file__)
213
213
214 ipython_cmd = find_cmd('ipython3' if py3compat.PY3 else 'ipython')
214 ipython_cmd = find_cmd('ipython3' if py3compat.PY3 else 'ipython')
215 # Absolute path for filename
215 # Absolute path for filename
216 full_fname = os.path.join(test_dir, fname)
216 full_fname = os.path.join(test_dir, fname)
217 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
217 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
218 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
218 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
219 out = getoutputerror(full_cmd)
219 out = getoutputerror(full_cmd)
220 # `import readline` causes 'ESC[?1034h' to be the first output sometimes,
220 # `import readline` causes 'ESC[?1034h' to be the first output sometimes,
221 # so strip that off the front of the first line if it is found
221 # so strip that off the front of the first line if it is found
222 if out:
222 if out:
223 first = out[0]
223 first = out[0]
224 m = re.match(r'\x1b\[[^h]+h', first)
224 m = re.match(r'\x1b\[[^h]+h', first)
225 if m:
225 if m:
226 # strip initial readline escape
226 # strip initial readline escape
227 out = list(out)
227 out = list(out)
228 out[0] = first[len(m.group()):]
228 out[0] = first[len(m.group()):]
229 out = tuple(out)
229 out = tuple(out)
230 return out
230 return out
231
231
232
232
233 def ipexec_validate(fname, expected_out, expected_err='',
233 def ipexec_validate(fname, expected_out, expected_err='',
234 options=None):
234 options=None):
235 """Utility to call 'ipython filename' and validate output/error.
235 """Utility to call 'ipython filename' and validate output/error.
236
236
237 This function raises an AssertionError if the validation fails.
237 This function raises an AssertionError if the validation fails.
238
238
239 Note that this starts IPython in a subprocess!
239 Note that this starts IPython in a subprocess!
240
240
241 Parameters
241 Parameters
242 ----------
242 ----------
243 fname : str
243 fname : str
244 Name of the file to be executed (should have .py or .ipy extension).
244 Name of the file to be executed (should have .py or .ipy extension).
245
245
246 expected_out : str
246 expected_out : str
247 Expected stdout of the process.
247 Expected stdout of the process.
248
248
249 expected_err : optional, str
249 expected_err : optional, str
250 Expected stderr of the process.
250 Expected stderr of the process.
251
251
252 options : optional, list
252 options : optional, list
253 Extra command-line flags to be passed to IPython.
253 Extra command-line flags to be passed to IPython.
254
254
255 Returns
255 Returns
256 -------
256 -------
257 None
257 None
258 """
258 """
259
259
260 import nose.tools as nt
260 import nose.tools as nt
261
261
262 out, err = ipexec(fname)
262 out, err = ipexec(fname)
263 #print 'OUT', out # dbg
263 #print 'OUT', out # dbg
264 #print 'ERR', err # dbg
264 #print 'ERR', err # dbg
265 # If there are any errors, we must check those befor stdout, as they may be
265 # If there are any errors, we must check those befor stdout, as they may be
266 # more informative than simply having an empty stdout.
266 # more informative than simply having an empty stdout.
267 if err:
267 if err:
268 if expected_err:
268 if expected_err:
269 nt.assert_equals(err.strip(), expected_err.strip())
269 nt.assert_equals(err.strip(), expected_err.strip())
270 else:
270 else:
271 raise ValueError('Running file %r produced error: %r' %
271 raise ValueError('Running file %r produced error: %r' %
272 (fname, err))
272 (fname, err))
273 # If no errors or output on stderr was expected, match stdout
273 # If no errors or output on stderr was expected, match stdout
274 nt.assert_equals(out.strip(), expected_out.strip())
274 nt.assert_equals(out.strip(), expected_out.strip())
275
275
276
276
277 class TempFileMixin(object):
277 class TempFileMixin(object):
278 """Utility class to create temporary Python/IPython files.
278 """Utility class to create temporary Python/IPython files.
279
279
280 Meant as a mixin class for test cases."""
280 Meant as a mixin class for test cases."""
281
281
282 def mktmp(self, src, ext='.py'):
282 def mktmp(self, src, ext='.py'):
283 """Make a valid python temp file."""
283 """Make a valid python temp file."""
284 fname, f = temp_pyfile(src, ext)
284 fname, f = temp_pyfile(src, ext)
285 self.tmpfile = f
285 self.tmpfile = f
286 self.fname = fname
286 self.fname = fname
287
287
288 def tearDown(self):
288 def tearDown(self):
289 if hasattr(self, 'tmpfile'):
289 if hasattr(self, 'tmpfile'):
290 # If the tmpfile wasn't made because of skipped tests, like in
290 # If the tmpfile wasn't made because of skipped tests, like in
291 # win32, there's nothing to cleanup.
291 # win32, there's nothing to cleanup.
292 self.tmpfile.close()
292 self.tmpfile.close()
293 try:
293 try:
294 os.unlink(self.fname)
294 os.unlink(self.fname)
295 except:
295 except:
296 # On Windows, even though we close the file, we still can't
296 # On Windows, even though we close the file, we still can't
297 # delete it. I have no clue why
297 # delete it. I have no clue why
298 pass
298 pass
299
299
300 pair_fail_msg = ("Testing {0}\n\n"
300 pair_fail_msg = ("Testing {0}\n\n"
301 "In:\n"
301 "In:\n"
302 " {1!r}\n"
302 " {1!r}\n"
303 "Expected:\n"
303 "Expected:\n"
304 " {2!r}\n"
304 " {2!r}\n"
305 "Got:\n"
305 "Got:\n"
306 " {3!r}\n")
306 " {3!r}\n")
307 def check_pairs(func, pairs):
307 def check_pairs(func, pairs):
308 """Utility function for the common case of checking a function with a
308 """Utility function for the common case of checking a function with a
309 sequence of input/output pairs.
309 sequence of input/output pairs.
310
310
311 Parameters
311 Parameters
312 ----------
312 ----------
313 func : callable
313 func : callable
314 The function to be tested. Should accept a single argument.
314 The function to be tested. Should accept a single argument.
315 pairs : iterable
315 pairs : iterable
316 A list of (input, expected_output) tuples.
316 A list of (input, expected_output) tuples.
317
317
318 Returns
318 Returns
319 -------
319 -------
320 None. Raises an AssertionError if any output does not match the expected
320 None. Raises an AssertionError if any output does not match the expected
321 value.
321 value.
322 """
322 """
323 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
323 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
324 for inp, expected in pairs:
324 for inp, expected in pairs:
325 out = func(inp)
325 out = func(inp)
326 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
326 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
327
327
328 if py3compat.PY3:
328 if py3compat.PY3:
329 MyStringIO = StringIO
329 MyStringIO = StringIO
330 else:
330 else:
331 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
331 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
332 # so we need a class that can handle both.
332 # so we need a class that can handle both.
333 class MyStringIO(StringIO):
333 class MyStringIO(StringIO):
334 def write(self, s):
334 def write(self, s):
335 s = py3compat.cast_unicode(s, encoding=getdefaultencoding())
335 s = py3compat.cast_unicode(s, encoding=getdefaultencoding())
336 super(MyStringIO, self).write(s)
336 super(MyStringIO, self).write(s)
337
337
338 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
338 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
339 {2!r}"""
339 {2!r}"""
340 class AssertPrints(object):
340 class AssertPrints(object):
341 """Context manager for testing that code prints certain text.
341 """Context manager for testing that code prints certain text.
342
342
343 Examples
343 Examples
344 --------
344 --------
345 >>> with AssertPrints("abc"):
345 >>> with AssertPrints("abc", suppress=False):
346 ... print "abcd"
346 ... print "abcd"
347 ... print "def"
347 ... print "def"
348 ...
348 ...
349 abcd
349 abcd
350 def
350 def
351 """
351 """
352 def __init__(self, s, channel='stdout'):
352 def __init__(self, s, channel='stdout', suppress=True):
353 self.s = s
353 self.s = s
354 self.channel = channel
354 self.channel = channel
355 self.suppress = suppress
355
356
356 def __enter__(self):
357 def __enter__(self):
357 self.orig_stream = getattr(sys, self.channel)
358 self.orig_stream = getattr(sys, self.channel)
358 self.buffer = MyStringIO()
359 self.buffer = MyStringIO()
359 self.tee = Tee(self.buffer, channel=self.channel)
360 self.tee = Tee(self.buffer, channel=self.channel)
360 setattr(sys, self.channel, self.tee)
361 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
361
362
362 def __exit__(self, etype, value, traceback):
363 def __exit__(self, etype, value, traceback):
363 self.tee.flush()
364 self.tee.flush()
364 setattr(sys, self.channel, self.orig_stream)
365 setattr(sys, self.channel, self.orig_stream)
365 printed = self.buffer.getvalue()
366 printed = self.buffer.getvalue()
366 assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed)
367 assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed)
367 return False
368 return False
368
369
370 class AssertNotPrints(AssertPrints):
371 """Context manager for checking that certain output *isn't* produced.
372
373 Counterpart of AssertPrints"""
374 def __exit__(self, etype, value, traceback):
375 self.tee.flush()
376 setattr(sys, self.channel, self.orig_stream)
377 printed = self.buffer.getvalue()
378 assert self.s not in printed, notprinted_msg.format(self.s, self.channel, printed)
379 return False
380
369 @contextmanager
381 @contextmanager
370 def mute_warn():
382 def mute_warn():
371 from IPython.utils import warn
383 from IPython.utils import warn
372 save_warn = warn.warn
384 save_warn = warn.warn
373 warn.warn = lambda *a, **kw: None
385 warn.warn = lambda *a, **kw: None
374 try:
386 try:
375 yield
387 yield
376 finally:
388 finally:
377 warn.warn = save_warn
389 warn.warn = save_warn
378
390
379 @contextmanager
391 @contextmanager
380 def make_tempfile(name):
392 def make_tempfile(name):
381 """ Create an empty, named, temporary file for the duration of the context.
393 """ Create an empty, named, temporary file for the duration of the context.
382 """
394 """
383 f = open(name, 'w')
395 f = open(name, 'w')
384 f.close()
396 f.close()
385 try:
397 try:
386 yield
398 yield
387 finally:
399 finally:
388 os.unlink(name)
400 os.unlink(name)
General Comments 0
You need to be logged in to leave comments. Login now