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