##// END OF EJS Templates
Option for testing local copy, rather than global. Testing needs more work.
Thomas Kluyver -
Show More
@@ -1,277 +1,283 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
34
35 try:
35 try:
36 # These tools are used by parts of the runtime, so we make the nose
36 # These tools are used by parts of the runtime, so we make the nose
37 # dependency optional at this point. Nose is a hard dependency to run the
37 # dependency optional at this point. Nose is a hard dependency to run the
38 # test suite, but NOT to use ipython itself.
38 # test suite, but NOT to use ipython itself.
39 import nose.tools as nt
39 import nose.tools as nt
40 has_nose = True
40 has_nose = True
41 except ImportError:
41 except ImportError:
42 has_nose = False
42 has_nose = False
43
43
44 from IPython.config.loader import Config
44 from IPython.config.loader import Config
45 from IPython.utils.process import find_cmd, getoutputerror
45 from IPython.utils.process import find_cmd, getoutputerror
46 from IPython.utils.text import list_strings
46 from IPython.utils.text import list_strings
47 from IPython.utils.io import temp_pyfile
47 from IPython.utils.io import temp_pyfile
48
48
49 from . import decorators as dec
49 from . import decorators as dec
50
50
51 # Set to True to test ipython.py in the local directory.
52 LOCALTEST = False
53
51 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
52 # Globals
55 # Globals
53 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
54
57
55 # Make a bunch of nose.tools assert wrappers that can be used in test
58 # Make a bunch of nose.tools assert wrappers that can be used in test
56 # generators. This will expose an assert* function for each one in nose.tools.
59 # generators. This will expose an assert* function for each one in nose.tools.
57
60
58 _tpl = """
61 _tpl = """
59 def %(name)s(*a,**kw):
62 def %(name)s(*a,**kw):
60 return nt.%(name)s(*a,**kw)
63 return nt.%(name)s(*a,**kw)
61 """
64 """
62
65
63 if has_nose:
66 if has_nose:
64 for _x in [a for a in dir(nt) if a.startswith('assert')]:
67 for _x in [a for a in dir(nt) if a.startswith('assert')]:
65 exec _tpl % dict(name=_x)
68 exec _tpl % dict(name=_x)
66
69
67 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
68 # Functions and classes
71 # Functions and classes
69 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
70
73
71 # The docstring for full_path doctests differently on win32 (different path
74 # The docstring for full_path doctests differently on win32 (different path
72 # separator) so just skip the doctest there. The example remains informative.
75 # separator) so just skip the doctest there. The example remains informative.
73 doctest_deco = dec.skip_doctest if sys.platform == 'win32' else dec.null_deco
76 doctest_deco = dec.skip_doctest if sys.platform == 'win32' else dec.null_deco
74
77
75 @doctest_deco
78 @doctest_deco
76 def full_path(startPath,files):
79 def full_path(startPath,files):
77 """Make full paths for all the listed files, based on startPath.
80 """Make full paths for all the listed files, based on startPath.
78
81
79 Only the base part of startPath is kept, since this routine is typically
82 Only the base part of startPath is kept, since this routine is typically
80 used with a script's __file__ variable as startPath. The base of startPath
83 used with a script's __file__ variable as startPath. The base of startPath
81 is then prepended to all the listed files, forming the output list.
84 is then prepended to all the listed files, forming the output list.
82
85
83 Parameters
86 Parameters
84 ----------
87 ----------
85 startPath : string
88 startPath : string
86 Initial path to use as the base for the results. This path is split
89 Initial path to use as the base for the results. This path is split
87 using os.path.split() and only its first component is kept.
90 using os.path.split() and only its first component is kept.
88
91
89 files : string or list
92 files : string or list
90 One or more files.
93 One or more files.
91
94
92 Examples
95 Examples
93 --------
96 --------
94
97
95 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
98 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
96 ['/foo/a.txt', '/foo/b.txt']
99 ['/foo/a.txt', '/foo/b.txt']
97
100
98 >>> full_path('/foo',['a.txt','b.txt'])
101 >>> full_path('/foo',['a.txt','b.txt'])
99 ['/a.txt', '/b.txt']
102 ['/a.txt', '/b.txt']
100
103
101 If a single file is given, the output is still a list:
104 If a single file is given, the output is still a list:
102 >>> full_path('/foo','a.txt')
105 >>> full_path('/foo','a.txt')
103 ['/a.txt']
106 ['/a.txt']
104 """
107 """
105
108
106 files = list_strings(files)
109 files = list_strings(files)
107 base = os.path.split(startPath)[0]
110 base = os.path.split(startPath)[0]
108 return [ os.path.join(base,f) for f in files ]
111 return [ os.path.join(base,f) for f in files ]
109
112
110
113
111 def parse_test_output(txt):
114 def parse_test_output(txt):
112 """Parse the output of a test run and return errors, failures.
115 """Parse the output of a test run and return errors, failures.
113
116
114 Parameters
117 Parameters
115 ----------
118 ----------
116 txt : str
119 txt : str
117 Text output of a test run, assumed to contain a line of one of the
120 Text output of a test run, assumed to contain a line of one of the
118 following forms::
121 following forms::
119 'FAILED (errors=1)'
122 'FAILED (errors=1)'
120 'FAILED (failures=1)'
123 'FAILED (failures=1)'
121 'FAILED (errors=1, failures=1)'
124 'FAILED (errors=1, failures=1)'
122
125
123 Returns
126 Returns
124 -------
127 -------
125 nerr, nfail: number of errors and failures.
128 nerr, nfail: number of errors and failures.
126 """
129 """
127
130
128 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
131 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
129 if err_m:
132 if err_m:
130 nerr = int(err_m.group(1))
133 nerr = int(err_m.group(1))
131 nfail = 0
134 nfail = 0
132 return nerr, nfail
135 return nerr, nfail
133
136
134 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
137 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
135 if fail_m:
138 if fail_m:
136 nerr = 0
139 nerr = 0
137 nfail = int(fail_m.group(1))
140 nfail = int(fail_m.group(1))
138 return nerr, nfail
141 return nerr, nfail
139
142
140 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
143 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
141 re.MULTILINE)
144 re.MULTILINE)
142 if both_m:
145 if both_m:
143 nerr = int(both_m.group(1))
146 nerr = int(both_m.group(1))
144 nfail = int(both_m.group(2))
147 nfail = int(both_m.group(2))
145 return nerr, nfail
148 return nerr, nfail
146
149
147 # If the input didn't match any of these forms, assume no error/failures
150 # If the input didn't match any of these forms, assume no error/failures
148 return 0, 0
151 return 0, 0
149
152
150
153
151 # So nose doesn't think this is a test
154 # So nose doesn't think this is a test
152 parse_test_output.__test__ = False
155 parse_test_output.__test__ = False
153
156
154
157
155 def default_argv():
158 def default_argv():
156 """Return a valid default argv for creating testing instances of ipython"""
159 """Return a valid default argv for creating testing instances of ipython"""
157
160
158 return ['--quick', # so no config file is loaded
161 return ['--quick', # so no config file is loaded
159 # Other defaults to minimize side effects on stdout
162 # Other defaults to minimize side effects on stdout
160 '--colors=NoColor', '--no-term-title','--no-banner',
163 '--colors=NoColor', '--no-term-title','--no-banner',
161 '--autocall=0']
164 '--autocall=0']
162
165
163
166
164 def default_config():
167 def default_config():
165 """Return a config object with good defaults for testing."""
168 """Return a config object with good defaults for testing."""
166 config = Config()
169 config = Config()
167 config.TerminalInteractiveShell.colors = 'NoColor'
170 config.TerminalInteractiveShell.colors = 'NoColor'
168 config.TerminalTerminalInteractiveShell.term_title = False,
171 config.TerminalTerminalInteractiveShell.term_title = False,
169 config.TerminalInteractiveShell.autocall = 0
172 config.TerminalInteractiveShell.autocall = 0
170 return config
173 return config
171
174
172
175
173 def ipexec(fname, options=None):
176 def ipexec(fname, options=None):
174 """Utility to call 'ipython filename'.
177 """Utility to call 'ipython filename'.
175
178
176 Starts IPython witha minimal and safe configuration to make startup as fast
179 Starts IPython witha minimal and safe configuration to make startup as fast
177 as possible.
180 as possible.
178
181
179 Note that this starts IPython in a subprocess!
182 Note that this starts IPython in a subprocess!
180
183
181 Parameters
184 Parameters
182 ----------
185 ----------
183 fname : str
186 fname : str
184 Name of file to be executed (should have .py or .ipy extension).
187 Name of file to be executed (should have .py or .ipy extension).
185
188
186 options : optional, list
189 options : optional, list
187 Extra command-line flags to be passed to IPython.
190 Extra command-line flags to be passed to IPython.
188
191
189 Returns
192 Returns
190 -------
193 -------
191 (stdout, stderr) of ipython subprocess.
194 (stdout, stderr) of ipython subprocess.
192 """
195 """
193 if options is None: options = []
196 if options is None: options = []
194
197
195 # For these subprocess calls, eliminate all prompt printing so we only see
198 # For these subprocess calls, eliminate all prompt printing so we only see
196 # output from script execution
199 # output from script execution
197 prompt_opts = ['--prompt-in1=""', '--prompt-in2=""', '--prompt-out=""']
200 prompt_opts = ['--prompt-in1=""', '--prompt-in2=""', '--prompt-out=""']
198 cmdargs = ' '.join(default_argv() + prompt_opts + options)
201 cmdargs = ' '.join(default_argv() + prompt_opts + options)
199
202
200 _ip = get_ipython()
203 _ip = get_ipython()
201 test_dir = os.path.dirname(__file__)
204 test_dir = os.path.dirname(__file__)
202
205
203 ipython_cmd = find_cmd('ipython')
206 if LOCALTEST:
207 ipython_cmd = os.path.join(os.getcwd(),'ipython.py')
208 else:
209 ipython_cmd = find_cmd('ipython')
204 # Absolute path for filename
210 # Absolute path for filename
205 full_fname = os.path.join(test_dir, fname)
211 full_fname = os.path.join(test_dir, fname)
206 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
212 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
207 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
213 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
208 return getoutputerror(full_cmd)
214 return getoutputerror(full_cmd)
209
215
210
216
211 def ipexec_validate(fname, expected_out, expected_err='',
217 def ipexec_validate(fname, expected_out, expected_err='',
212 options=None):
218 options=None):
213 """Utility to call 'ipython filename' and validate output/error.
219 """Utility to call 'ipython filename' and validate output/error.
214
220
215 This function raises an AssertionError if the validation fails.
221 This function raises an AssertionError if the validation fails.
216
222
217 Note that this starts IPython in a subprocess!
223 Note that this starts IPython in a subprocess!
218
224
219 Parameters
225 Parameters
220 ----------
226 ----------
221 fname : str
227 fname : str
222 Name of the file to be executed (should have .py or .ipy extension).
228 Name of the file to be executed (should have .py or .ipy extension).
223
229
224 expected_out : str
230 expected_out : str
225 Expected stdout of the process.
231 Expected stdout of the process.
226
232
227 expected_err : optional, str
233 expected_err : optional, str
228 Expected stderr of the process.
234 Expected stderr of the process.
229
235
230 options : optional, list
236 options : optional, list
231 Extra command-line flags to be passed to IPython.
237 Extra command-line flags to be passed to IPython.
232
238
233 Returns
239 Returns
234 -------
240 -------
235 None
241 None
236 """
242 """
237
243
238 import nose.tools as nt
244 import nose.tools as nt
239
245
240 out, err = ipexec(fname)
246 out, err = ipexec(fname)
241 #print 'OUT', out # dbg
247 #print 'OUT', out # dbg
242 #print 'ERR', err # dbg
248 #print 'ERR', err # dbg
243 # If there are any errors, we must check those befor stdout, as they may be
249 # If there are any errors, we must check those befor stdout, as they may be
244 # more informative than simply having an empty stdout.
250 # more informative than simply having an empty stdout.
245 if err:
251 if err:
246 if expected_err:
252 if expected_err:
247 nt.assert_equals(err.strip(), expected_err.strip())
253 nt.assert_equals(err.strip(), expected_err.strip())
248 else:
254 else:
249 raise ValueError('Running file %r produced error: %r' %
255 raise ValueError('Running file %r produced error: %r' %
250 (fname, err))
256 (fname, err))
251 # If no errors or output on stderr was expected, match stdout
257 # If no errors or output on stderr was expected, match stdout
252 nt.assert_equals(out.strip(), expected_out.strip())
258 nt.assert_equals(out.strip(), expected_out.strip())
253
259
254
260
255 class TempFileMixin(object):
261 class TempFileMixin(object):
256 """Utility class to create temporary Python/IPython files.
262 """Utility class to create temporary Python/IPython files.
257
263
258 Meant as a mixin class for test cases."""
264 Meant as a mixin class for test cases."""
259
265
260 def mktmp(self, src, ext='.py'):
266 def mktmp(self, src, ext='.py'):
261 """Make a valid python temp file."""
267 """Make a valid python temp file."""
262 fname, f = temp_pyfile(src, ext)
268 fname, f = temp_pyfile(src, ext)
263 self.tmpfile = f
269 self.tmpfile = f
264 self.fname = fname
270 self.fname = fname
265
271
266 def tearDown(self):
272 def tearDown(self):
267 if hasattr(self, 'tmpfile'):
273 if hasattr(self, 'tmpfile'):
268 # If the tmpfile wasn't made because of skipped tests, like in
274 # If the tmpfile wasn't made because of skipped tests, like in
269 # win32, there's nothing to cleanup.
275 # win32, there's nothing to cleanup.
270 self.tmpfile.close()
276 self.tmpfile.close()
271 try:
277 try:
272 os.unlink(self.fname)
278 os.unlink(self.fname)
273 except:
279 except:
274 # On Windows, even though we close the file, we still can't
280 # On Windows, even though we close the file, we still can't
275 # delete it. I have no clue why
281 # delete it. I have no clue why
276 pass
282 pass
277
283
General Comments 0
You need to be logged in to leave comments. Login now