##// END OF EJS Templates
de-decorate help output tests
MinRK -
Show More
@@ -1,67 +1,55 b''
1 1 """Test help output of various IPython entry points"""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2013 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 from IPython.testing.tools import help, help_all
14 import IPython.testing.tools as tt
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Tests
18 18 #-----------------------------------------------------------------------------
19 19
20 20
21 @help()
22 21 def test_ipython_help():
23 pass
22 tt.help_output_test()
24 23
25 @help_all()
26 24 def test_ipython_help_all():
27 pass
25 tt.help_all_output_test()
28 26
29 @help("profile")
30 27 def test_profile_help():
31 pass
28 tt.help_output_test("profile")
32 29
33 @help_all("profile")
34 30 def test_profile_help_all():
35 pass
31 tt.help_all_output_test("profile")
36 32
37 @help("profile list")
38 33 def test_profile_list_help():
39 pass
34 tt.help_output_test("profile list")
40 35
41 @help_all("profile list")
42 36 def test_profile_list_help_all():
43 pass
37 tt.help_all_output_test("profile list")
44 38
45 @help("profile create")
46 39 def test_profile_create_help():
47 pass
40 tt.help_output_test("profile create")
48 41
49 @help_all("profile create")
50 42 def test_profile_create_help_all():
51 pass
43 tt.help_all_output_test("profile create")
52 44
53 @help("locate")
54 45 def test_locate_help():
55 pass
46 tt.help_output_test("locate")
56 47
57 @help_all("locate")
58 48 def test_locate_help_all():
59 pass
49 tt.help_all_output_test("locate")
60 50
61 @help("locate profile")
62 51 def test_locate_profile_help():
63 pass
52 tt.help_output_test("locate profile")
64 53
65 @help_all("locate profile")
66 54 def test_locate_profile_all():
67 pass
55 tt.help_all_output_test("locate profile")
@@ -1,465 +1,435 b''
1 1 """Generic testing tools.
2 2
3 3 Authors
4 4 -------
5 5 - Fernando Perez <Fernando.Perez@berkeley.edu>
6 6 """
7 7
8 8 from __future__ import absolute_import
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2009 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 import inspect
22 22 import os
23 23 import re
24 24 import sys
25 25 import tempfile
26 26
27 27 from contextlib import contextmanager
28 28 from io import StringIO
29 29 from subprocess import Popen, PIPE
30 30
31 31 try:
32 32 # These tools are used by parts of the runtime, so we make the nose
33 33 # dependency optional at this point. Nose is a hard dependency to run the
34 34 # test suite, but NOT to use ipython itself.
35 35 import nose.tools as nt
36 36 has_nose = True
37 37 except ImportError:
38 38 has_nose = False
39 39
40 40 from IPython.config.loader import Config
41 41 from IPython.utils.process import get_output_error_code
42 42 from IPython.utils.text import list_strings
43 43 from IPython.utils.io import temp_pyfile, Tee
44 44 from IPython.utils import py3compat
45 45 from IPython.utils.encoding import DEFAULT_ENCODING
46 46
47 47 from . import decorators as dec
48 48 from . import skipdoctest
49 49
50 50 #-----------------------------------------------------------------------------
51 51 # Functions and classes
52 52 #-----------------------------------------------------------------------------
53 53
54 54 # The docstring for full_path doctests differently on win32 (different path
55 55 # separator) so just skip the doctest there. The example remains informative.
56 56 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
57 57
58 58 @doctest_deco
59 59 def full_path(startPath,files):
60 60 """Make full paths for all the listed files, based on startPath.
61 61
62 62 Only the base part of startPath is kept, since this routine is typically
63 63 used with a script's __file__ variable as startPath. The base of startPath
64 64 is then prepended to all the listed files, forming the output list.
65 65
66 66 Parameters
67 67 ----------
68 68 startPath : string
69 69 Initial path to use as the base for the results. This path is split
70 70 using os.path.split() and only its first component is kept.
71 71
72 72 files : string or list
73 73 One or more files.
74 74
75 75 Examples
76 76 --------
77 77
78 78 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
79 79 ['/foo/a.txt', '/foo/b.txt']
80 80
81 81 >>> full_path('/foo',['a.txt','b.txt'])
82 82 ['/a.txt', '/b.txt']
83 83
84 84 If a single file is given, the output is still a list:
85 85 >>> full_path('/foo','a.txt')
86 86 ['/a.txt']
87 87 """
88 88
89 89 files = list_strings(files)
90 90 base = os.path.split(startPath)[0]
91 91 return [ os.path.join(base,f) for f in files ]
92 92
93 93
94 94 def parse_test_output(txt):
95 95 """Parse the output of a test run and return errors, failures.
96 96
97 97 Parameters
98 98 ----------
99 99 txt : str
100 100 Text output of a test run, assumed to contain a line of one of the
101 101 following forms::
102 102
103 103 'FAILED (errors=1)'
104 104 'FAILED (failures=1)'
105 105 'FAILED (errors=1, failures=1)'
106 106
107 107 Returns
108 108 -------
109 109 nerr, nfail: number of errors and failures.
110 110 """
111 111
112 112 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
113 113 if err_m:
114 114 nerr = int(err_m.group(1))
115 115 nfail = 0
116 116 return nerr, nfail
117 117
118 118 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
119 119 if fail_m:
120 120 nerr = 0
121 121 nfail = int(fail_m.group(1))
122 122 return nerr, nfail
123 123
124 124 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
125 125 re.MULTILINE)
126 126 if both_m:
127 127 nerr = int(both_m.group(1))
128 128 nfail = int(both_m.group(2))
129 129 return nerr, nfail
130 130
131 131 # If the input didn't match any of these forms, assume no error/failures
132 132 return 0, 0
133 133
134 134
135 135 # So nose doesn't think this is a test
136 136 parse_test_output.__test__ = False
137 137
138 138
139 139 def default_argv():
140 140 """Return a valid default argv for creating testing instances of ipython"""
141 141
142 142 return ['--quick', # so no config file is loaded
143 143 # Other defaults to minimize side effects on stdout
144 144 '--colors=NoColor', '--no-term-title','--no-banner',
145 145 '--autocall=0']
146 146
147 147
148 148 def default_config():
149 149 """Return a config object with good defaults for testing."""
150 150 config = Config()
151 151 config.TerminalInteractiveShell.colors = 'NoColor'
152 152 config.TerminalTerminalInteractiveShell.term_title = False,
153 153 config.TerminalInteractiveShell.autocall = 0
154 154 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
155 155 config.HistoryManager.db_cache_size = 10000
156 156 return config
157 157
158 158
159 159 def get_ipython_cmd(as_string=False):
160 160 """
161 161 Return appropriate IPython command line name. By default, this will return
162 162 a list that can be used with subprocess.Popen, for example, but passing
163 163 `as_string=True` allows for returning the IPython command as a string.
164 164
165 165 Parameters
166 166 ----------
167 167 as_string: bool
168 168 Flag to allow to return the command as a string.
169 169 """
170 170 ipython_cmd = [sys.executable, "-m", "IPython"]
171 171
172 172 if as_string:
173 173 ipython_cmd = " ".join(ipython_cmd)
174 174
175 175 return ipython_cmd
176 176
177 177 def ipexec(fname, options=None):
178 178 """Utility to call 'ipython filename'.
179 179
180 180 Starts IPython with a minimal and safe configuration to make startup as fast
181 181 as possible.
182 182
183 183 Note that this starts IPython in a subprocess!
184 184
185 185 Parameters
186 186 ----------
187 187 fname : str
188 188 Name of file to be executed (should have .py or .ipy extension).
189 189
190 190 options : optional, list
191 191 Extra command-line flags to be passed to IPython.
192 192
193 193 Returns
194 194 -------
195 195 (stdout, stderr) of ipython subprocess.
196 196 """
197 197 if options is None: options = []
198 198
199 199 # For these subprocess calls, eliminate all prompt printing so we only see
200 200 # output from script execution
201 201 prompt_opts = [ '--PromptManager.in_template=""',
202 202 '--PromptManager.in2_template=""',
203 203 '--PromptManager.out_template=""'
204 204 ]
205 205 cmdargs = default_argv() + prompt_opts + options
206 206
207 207 test_dir = os.path.dirname(__file__)
208 208
209 209 ipython_cmd = get_ipython_cmd()
210 210 # Absolute path for filename
211 211 full_fname = os.path.join(test_dir, fname)
212 212 full_cmd = ipython_cmd + cmdargs + [full_fname]
213 213 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE)
214 214 out, err = p.communicate()
215 215 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
216 216 # `import readline` causes 'ESC[?1034h' to be output sometimes,
217 217 # so strip that out before doing comparisons
218 218 if out:
219 219 out = re.sub(r'\x1b\[[^h]+h', '', out)
220 220 return out, err
221 221
222 222
223 223 def ipexec_validate(fname, expected_out, expected_err='',
224 224 options=None):
225 225 """Utility to call 'ipython filename' and validate output/error.
226 226
227 227 This function raises an AssertionError if the validation fails.
228 228
229 229 Note that this starts IPython in a subprocess!
230 230
231 231 Parameters
232 232 ----------
233 233 fname : str
234 234 Name of the file to be executed (should have .py or .ipy extension).
235 235
236 236 expected_out : str
237 237 Expected stdout of the process.
238 238
239 239 expected_err : optional, str
240 240 Expected stderr of the process.
241 241
242 242 options : optional, list
243 243 Extra command-line flags to be passed to IPython.
244 244
245 245 Returns
246 246 -------
247 247 None
248 248 """
249 249
250 250 import nose.tools as nt
251 251
252 252 out, err = ipexec(fname, options)
253 253 #print 'OUT', out # dbg
254 254 #print 'ERR', err # dbg
255 255 # If there are any errors, we must check those befor stdout, as they may be
256 256 # more informative than simply having an empty stdout.
257 257 if err:
258 258 if expected_err:
259 259 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
260 260 else:
261 261 raise ValueError('Running file %r produced error: %r' %
262 262 (fname, err))
263 263 # If no errors or output on stderr was expected, match stdout
264 264 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
265 265
266 266
267 267 class TempFileMixin(object):
268 268 """Utility class to create temporary Python/IPython files.
269 269
270 270 Meant as a mixin class for test cases."""
271 271
272 272 def mktmp(self, src, ext='.py'):
273 273 """Make a valid python temp file."""
274 274 fname, f = temp_pyfile(src, ext)
275 275 self.tmpfile = f
276 276 self.fname = fname
277 277
278 278 def tearDown(self):
279 279 if hasattr(self, 'tmpfile'):
280 280 # If the tmpfile wasn't made because of skipped tests, like in
281 281 # win32, there's nothing to cleanup.
282 282 self.tmpfile.close()
283 283 try:
284 284 os.unlink(self.fname)
285 285 except:
286 286 # On Windows, even though we close the file, we still can't
287 287 # delete it. I have no clue why
288 288 pass
289 289
290 290 pair_fail_msg = ("Testing {0}\n\n"
291 291 "In:\n"
292 292 " {1!r}\n"
293 293 "Expected:\n"
294 294 " {2!r}\n"
295 295 "Got:\n"
296 296 " {3!r}\n")
297 297 def check_pairs(func, pairs):
298 298 """Utility function for the common case of checking a function with a
299 299 sequence of input/output pairs.
300 300
301 301 Parameters
302 302 ----------
303 303 func : callable
304 304 The function to be tested. Should accept a single argument.
305 305 pairs : iterable
306 306 A list of (input, expected_output) tuples.
307 307
308 308 Returns
309 309 -------
310 310 None. Raises an AssertionError if any output does not match the expected
311 311 value.
312 312 """
313 313 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
314 314 for inp, expected in pairs:
315 315 out = func(inp)
316 316 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
317 317
318 318
319 319 if py3compat.PY3:
320 320 MyStringIO = StringIO
321 321 else:
322 322 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
323 323 # so we need a class that can handle both.
324 324 class MyStringIO(StringIO):
325 325 def write(self, s):
326 326 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
327 327 super(MyStringIO, self).write(s)
328 328
329 329 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
330 330 -------
331 331 {2!s}
332 332 -------
333 333 """
334 334
335 335 class AssertPrints(object):
336 336 """Context manager for testing that code prints certain text.
337 337
338 338 Examples
339 339 --------
340 340 >>> with AssertPrints("abc", suppress=False):
341 341 ... print "abcd"
342 342 ... print "def"
343 343 ...
344 344 abcd
345 345 def
346 346 """
347 347 def __init__(self, s, channel='stdout', suppress=True):
348 348 self.s = s
349 349 self.channel = channel
350 350 self.suppress = suppress
351 351
352 352 def __enter__(self):
353 353 self.orig_stream = getattr(sys, self.channel)
354 354 self.buffer = MyStringIO()
355 355 self.tee = Tee(self.buffer, channel=self.channel)
356 356 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
357 357
358 358 def __exit__(self, etype, value, traceback):
359 359 self.tee.flush()
360 360 setattr(sys, self.channel, self.orig_stream)
361 361 printed = self.buffer.getvalue()
362 362 assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed)
363 363 return False
364 364
365 365 printed_msg = """Found {0!r} in printed output (on {1}):
366 366 -------
367 367 {2!s}
368 368 -------
369 369 """
370 370
371 371 class AssertNotPrints(AssertPrints):
372 372 """Context manager for checking that certain output *isn't* produced.
373 373
374 374 Counterpart of AssertPrints"""
375 375 def __exit__(self, etype, value, traceback):
376 376 self.tee.flush()
377 377 setattr(sys, self.channel, self.orig_stream)
378 378 printed = self.buffer.getvalue()
379 379 assert self.s not in printed, printed_msg.format(self.s, self.channel, printed)
380 380 return False
381 381
382 382 @contextmanager
383 383 def mute_warn():
384 384 from IPython.utils import warn
385 385 save_warn = warn.warn
386 386 warn.warn = lambda *a, **kw: None
387 387 try:
388 388 yield
389 389 finally:
390 390 warn.warn = save_warn
391 391
392 392 @contextmanager
393 393 def make_tempfile(name):
394 394 """ Create an empty, named, temporary file for the duration of the context.
395 395 """
396 396 f = open(name, 'w')
397 397 f.close()
398 398 try:
399 399 yield
400 400 finally:
401 401 os.unlink(name)
402 402
403 403
404 404 @contextmanager
405 405 def monkeypatch(obj, name, attr):
406 406 """
407 407 Context manager to replace attribute named `name` in `obj` with `attr`.
408 408 """
409 409 orig = getattr(obj, name)
410 410 setattr(obj, name, attr)
411 411 yield
412 412 setattr(obj, name, orig)
413 413
414 414
415 415 def help_output_test(subcommand=''):
416 416 """test that `ipython [subcommand] -h` works"""
417 417 cmd = ' '.join(get_ipython_cmd() + [subcommand, '-h'])
418 418 out, err, rc = get_output_error_code(cmd)
419 419 nt.assert_equal(rc, 0, err)
420 420 nt.assert_not_in("Traceback", err)
421 421 nt.assert_in("Options", out)
422 422 nt.assert_in("--help-all", out)
423 423 return out, err
424 424
425 425
426 426 def help_all_output_test(subcommand=''):
427 427 """test that `ipython [subcommand] --help-all` works"""
428 428 cmd = ' '.join(get_ipython_cmd() + [subcommand, '--help-all'])
429 429 out, err, rc = get_output_error_code(cmd)
430 430 nt.assert_equal(rc, 0, err)
431 431 nt.assert_not_in("Traceback", err)
432 432 nt.assert_in("Options", out)
433 433 nt.assert_in("Class parameters", out)
434 434 return out, err
435 435
436
437 def help(cmd=''):
438 """decorator for making a test for `ipython cmd -h`"""
439 def wrap_help_test(f):
440 def test_help_output():
441 out, err = help_output_test(cmd)
442 if inspect.getargspec(f).args:
443 return f(out, err)
444 else:
445 return f()
446 cmds = cmd + ' ' if cmd else ''
447 test_help_output.__doc__ = "ipython {cmds}--help-all works".format(cmds=cmds)
448
449 return test_help_output
450 return wrap_help_test
451
452
453 def help_all(cmd=''):
454 """decorator for making a test for `ipython [cmd] --help-all`"""
455 def wrap_help_test(f):
456 def test_help_output():
457 out, err = help_all_output_test(cmd)
458 if inspect.getargspec(f).args:
459 return f(out, err)
460 else:
461 return f()
462 cmds = cmd + ' ' if cmd else ''
463 test_help_output.__doc__ = "ipython {cmds}--help-all works".format(cmds=cmds)
464 return test_help_output
465 return wrap_help_test
General Comments 0
You need to be logged in to leave comments. Login now