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