##// END OF EJS Templates
handle lecture test moving to lib
Min RK -
Show More
@@ -1,131 +1,122 b''
1 1 """Test lexers module"""
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2014 The IPython Development Team
4 #
5 # Distributed under the terms of the BSD License. The full license is in
6 # the file COPYING, distributed as part of this software.
7 #-----------------------------------------------------------------------------
8
9 #-----------------------------------------------------------------------------
10 # Imports
11 #-----------------------------------------------------------------------------
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 from unittest import TestCase
12 7 from pygments.token import Token
13 8
14 from jupyter_nbconvert.tests.base import TestsBase
15 9 from .. import lexers
16 10
17 11
18 #-----------------------------------------------------------------------------
19 # Classes and functions
20 #-----------------------------------------------------------------------------
21 class TestLexers(TestsBase):
12 class TestLexers(TestCase):
22 13 """Collection of lexers tests"""
23 14 def setUp(self):
24 15 self.lexer = lexers.IPythonLexer()
25 16
26 17 def testIPythonLexer(self):
27 18 fragment = '!echo $HOME\n'
28 19 tokens = [
29 20 (Token.Operator, '!'),
30 21 (Token.Name.Builtin, 'echo'),
31 22 (Token.Text, ' '),
32 23 (Token.Name.Variable, '$HOME'),
33 24 (Token.Text, '\n'),
34 25 ]
35 26 self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
36 27
37 28 fragment_2 = '!' + fragment
38 29 tokens_2 = [
39 30 (Token.Operator, '!!'),
40 31 ] + tokens[1:]
41 32 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
42 33
43 34 fragment_2 = '\t %%!\n' + fragment[1:]
44 35 tokens_2 = [
45 36 (Token.Text, '\t '),
46 37 (Token.Operator, '%%!'),
47 38 (Token.Text, '\n'),
48 39 ] + tokens[1:]
49 40 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
50 41
51 42 fragment_2 = 'x = ' + fragment
52 43 tokens_2 = [
53 44 (Token.Name, 'x'),
54 45 (Token.Text, ' '),
55 46 (Token.Operator, '='),
56 47 (Token.Text, ' '),
57 48 ] + tokens
58 49 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
59 50
60 51 fragment_2 = 'x, = ' + fragment
61 52 tokens_2 = [
62 53 (Token.Name, 'x'),
63 54 (Token.Punctuation, ','),
64 55 (Token.Text, ' '),
65 56 (Token.Operator, '='),
66 57 (Token.Text, ' '),
67 58 ] + tokens
68 59 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
69 60
70 61 fragment_2 = 'x, = %sx ' + fragment[1:]
71 62 tokens_2 = [
72 63 (Token.Name, 'x'),
73 64 (Token.Punctuation, ','),
74 65 (Token.Text, ' '),
75 66 (Token.Operator, '='),
76 67 (Token.Text, ' '),
77 68 (Token.Operator, '%'),
78 69 (Token.Keyword, 'sx'),
79 70 (Token.Text, ' '),
80 71 ] + tokens[1:]
81 72 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
82 73
83 74 fragment_2 = 'f = %R function () {}\n'
84 75 tokens_2 = [
85 76 (Token.Name, 'f'),
86 77 (Token.Text, ' '),
87 78 (Token.Operator, '='),
88 79 (Token.Text, ' '),
89 80 (Token.Operator, '%'),
90 81 (Token.Keyword, 'R'),
91 82 (Token.Text, ' function () {}\n'),
92 83 ]
93 84 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
94 85
95 86 fragment_2 = '\t%%xyz\n$foo\n'
96 87 tokens_2 = [
97 88 (Token.Text, '\t'),
98 89 (Token.Operator, '%%'),
99 90 (Token.Keyword, 'xyz'),
100 91 (Token.Text, '\n$foo\n'),
101 92 ]
102 93 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
103 94
104 95 fragment_2 = '%system?\n'
105 96 tokens_2 = [
106 97 (Token.Operator, '%'),
107 98 (Token.Keyword, 'system'),
108 99 (Token.Operator, '?'),
109 100 (Token.Text, '\n'),
110 101 ]
111 102 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
112 103
113 104 fragment_2 = 'x != y\n'
114 105 tokens_2 = [
115 106 (Token.Name, 'x'),
116 107 (Token.Text, ' '),
117 108 (Token.Operator, '!='),
118 109 (Token.Text, ' '),
119 110 (Token.Name, 'y'),
120 111 (Token.Text, '\n'),
121 112 ]
122 113 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
123 114
124 115 fragment_2 = ' ?math.sin\n'
125 116 tokens_2 = [
126 117 (Token.Text, ' '),
127 118 (Token.Operator, '?'),
128 119 (Token.Text, 'math.sin'),
129 120 (Token.Text, '\n'),
130 121 ]
131 122 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
@@ -1,490 +1,492 b''
1 1 # -*- coding: utf-8 -*-
2 2 """IPython Test Suite Runner.
3 3
4 4 This module provides a main entry point to a user script to test IPython
5 5 itself from the command line. There are two ways of running this script:
6 6
7 7 1. With the syntax `iptest all`. This runs our entire test suite by
8 8 calling this script (with different arguments) recursively. This
9 9 causes modules and package to be tested in different processes, using nose
10 10 or trial where appropriate.
11 11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 12 the script simply calls nose, but with special command line flags and
13 13 plugins loaded.
14 14
15 15 """
16 16
17 17 # Copyright (c) IPython Development Team.
18 18 # Distributed under the terms of the Modified BSD License.
19 19
20 20 from __future__ import print_function
21 21
22 22 import glob
23 23 from io import BytesIO
24 24 import os
25 25 import os.path as path
26 26 import sys
27 27 from threading import Thread, Lock, Event
28 28 import warnings
29 29
30 30 import nose.plugins.builtin
31 31 from nose.plugins.xunit import Xunit
32 32 from nose import SkipTest
33 33 from nose.core import TestProgram
34 34 from nose.plugins import Plugin
35 35 from nose.util import safe_str
36 36
37 37 from IPython.utils.process import is_cmd_found
38 38 from IPython.utils.py3compat import bytes_to_str
39 39 from IPython.utils.importstring import import_item
40 40 from IPython.testing.plugin.ipdoctest import IPythonDoctest
41 41 from IPython.external.decorators import KnownFailure, knownfailureif
42 42
43 43 pjoin = path.join
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Warnings control
47 47 #-----------------------------------------------------------------------------
48 48
49 49 # Twisted generates annoying warnings with Python 2.6, as will do other code
50 50 # that imports 'sets' as of today
51 51 warnings.filterwarnings('ignore', 'the sets module is deprecated',
52 52 DeprecationWarning )
53 53
54 54 # This one also comes from Twisted
55 55 warnings.filterwarnings('ignore', 'the sha module is deprecated',
56 56 DeprecationWarning)
57 57
58 58 # Wx on Fedora11 spits these out
59 59 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
60 60 UserWarning)
61 61
62 62 # ------------------------------------------------------------------------------
63 63 # Monkeypatch Xunit to count known failures as skipped.
64 64 # ------------------------------------------------------------------------------
65 65 def monkeypatch_xunit():
66 66 try:
67 67 knownfailureif(True)(lambda: None)()
68 68 except Exception as e:
69 69 KnownFailureTest = type(e)
70 70
71 71 def addError(self, test, err, capt=None):
72 72 if issubclass(err[0], KnownFailureTest):
73 73 err = (SkipTest,) + err[1:]
74 74 return self.orig_addError(test, err, capt)
75 75
76 76 Xunit.orig_addError = Xunit.addError
77 77 Xunit.addError = addError
78 78
79 79 #-----------------------------------------------------------------------------
80 80 # Check which dependencies are installed and greater than minimum version.
81 81 #-----------------------------------------------------------------------------
82 82 def extract_version(mod):
83 83 return mod.__version__
84 84
85 85 def test_for(item, min_version=None, callback=extract_version):
86 86 """Test to see if item is importable, and optionally check against a minimum
87 87 version.
88 88
89 89 If min_version is given, the default behavior is to check against the
90 90 `__version__` attribute of the item, but specifying `callback` allows you to
91 91 extract the value you are interested in. e.g::
92 92
93 93 In [1]: import sys
94 94
95 95 In [2]: from IPython.testing.iptest import test_for
96 96
97 97 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
98 98 Out[3]: True
99 99
100 100 """
101 101 try:
102 102 check = import_item(item)
103 103 except (ImportError, RuntimeError):
104 104 # GTK reports Runtime error if it can't be initialized even if it's
105 105 # importable.
106 106 return False
107 107 else:
108 108 if min_version:
109 109 if callback:
110 110 # extra processing step to get version to compare
111 111 check = callback(check)
112 112
113 113 return check >= min_version
114 114 else:
115 115 return True
116 116
117 117 # Global dict where we can store information on what we have and what we don't
118 118 # have available at test run time
119 119 have = {}
120 120
121 121 have['curses'] = test_for('_curses')
122 122 have['matplotlib'] = test_for('matplotlib')
123 123 have['numpy'] = test_for('numpy')
124 124 have['pexpect'] = test_for('pexpect')
125 125 have['pymongo'] = test_for('pymongo')
126 126 have['pygments'] = test_for('pygments')
127 127 have['sqlite3'] = test_for('sqlite3')
128 128 have['tornado'] = test_for('tornado.version_info', (4,0), callback=None)
129 129 have['jinja2'] = test_for('jinja2')
130 130 have['mistune'] = test_for('mistune')
131 131 have['requests'] = test_for('requests')
132 132 have['sphinx'] = test_for('sphinx')
133 133 have['jsonschema'] = test_for('jsonschema')
134 134 have['terminado'] = test_for('terminado')
135 135 have['casperjs'] = is_cmd_found('casperjs')
136 136 have['phantomjs'] = is_cmd_found('phantomjs')
137 137 have['slimerjs'] = is_cmd_found('slimerjs')
138 138
139 139 min_zmq = (13,)
140 140
141 141 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
142 142
143 143 #-----------------------------------------------------------------------------
144 144 # Test suite definitions
145 145 #-----------------------------------------------------------------------------
146 146
147 147 test_group_names = ['core',
148 148 'extensions', 'lib', 'terminal', 'testing', 'utils',
149 149 'html', 'nbconvert'
150 150 ]
151 151
152 152 class TestSection(object):
153 153 def __init__(self, name, includes):
154 154 self.name = name
155 155 self.includes = includes
156 156 self.excludes = []
157 157 self.dependencies = []
158 158 self.enabled = True
159 159
160 160 def exclude(self, module):
161 161 if not module.startswith('IPython'):
162 162 module = self.includes[0] + "." + module
163 163 self.excludes.append(module.replace('.', os.sep))
164 164
165 165 def requires(self, *packages):
166 166 self.dependencies.extend(packages)
167 167
168 168 @property
169 169 def will_run(self):
170 170 return self.enabled and all(have[p] for p in self.dependencies)
171 171
172 172 shims = {
173 173 'html': 'jupyter_notebook',
174 174 }
175 175
176 176 # Name -> (include, exclude, dependencies_met)
177 177 test_sections = {n:TestSection(n, [shims.get(n, 'IPython.%s' % n)]) for n in test_group_names}
178 178
179 179
180 180 # Exclusions and dependencies
181 181 # ---------------------------
182 182
183 183 # core:
184 184 sec = test_sections['core']
185 185 if not have['sqlite3']:
186 186 sec.exclude('tests.test_history')
187 187 sec.exclude('history')
188 188 if not have['matplotlib']:
189 189 sec.exclude('pylabtools'),
190 190 sec.exclude('tests.test_pylabtools')
191 191
192 192 # lib:
193 193 sec = test_sections['lib']
194 194 if not have['zmq']:
195 195 sec.exclude('kernel')
196 if not have['pygments']:
197 sec.exclude('tests.test_lexers')
196 198 # We do this unconditionally, so that the test suite doesn't import
197 199 # gtk, changing the default encoding and masking some unicode bugs.
198 200 sec.exclude('inputhookgtk')
199 201 # We also do this unconditionally, because wx can interfere with Unix signals.
200 202 # There are currently no tests for it anyway.
201 203 sec.exclude('inputhookwx')
202 204 # Testing inputhook will need a lot of thought, to figure out
203 205 # how to have tests that don't lock up with the gui event
204 206 # loops in the picture
205 207 sec.exclude('inputhook')
206 208
207 209 # testing:
208 210 sec = test_sections['testing']
209 211 # These have to be skipped on win32 because they use echo, rm, cd, etc.
210 212 # See ticket https://github.com/ipython/ipython/issues/87
211 213 if sys.platform == 'win32':
212 214 sec.exclude('plugin.test_exampleip')
213 215 sec.exclude('plugin.dtexample')
214 216
215 217 # don't run jupyter_console tests found via shim
216 218 test_sections['terminal'].exclude('console')
217 219
218 220 # extensions:
219 221 sec = test_sections['extensions']
220 222 # This is deprecated in favour of rpy2
221 223 sec.exclude('rmagic')
222 224 # autoreload does some strange stuff, so move it to its own test section
223 225 sec.exclude('autoreload')
224 226 sec.exclude('tests.test_autoreload')
225 227 test_sections['autoreload'] = TestSection('autoreload',
226 228 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
227 229 test_group_names.append('autoreload')
228 230
229 231 # html:
230 232 sec = test_sections['html']
231 233 sec.requires('zmq', 'tornado', 'requests', 'sqlite3', 'jsonschema')
232 234 # The notebook 'static' directory contains JS, css and other
233 235 # files for web serving. Occasionally projects may put a .py
234 236 # file in there (MathJax ships a conf.py), so we might as
235 237 # well play it safe and skip the whole thing.
236 238 sec.exclude('static')
237 239 sec.exclude('tasks')
238 240 if not have['jinja2']:
239 241 sec.exclude('notebookapp')
240 242 if not have['pygments'] or not have['jinja2']:
241 243 sec.exclude('nbconvert')
242 244 if not have['terminado']:
243 245 sec.exclude('terminal')
244 246
245 247 # nbconvert:
246 248 sec = test_sections['nbconvert']
247 249 sec.requires('pygments', 'jinja2', 'jsonschema', 'mistune')
248 250 # Exclude nbconvert directories containing config files used to test.
249 251 # Executing the config files with iptest would cause an exception.
250 252 sec.exclude('tests.files')
251 253 sec.exclude('exporters.tests.files')
252 254 if not have['tornado']:
253 255 sec.exclude('nbconvert.post_processors.serve')
254 256 sec.exclude('nbconvert.post_processors.tests.test_serve')
255 257
256 258
257 259 #-----------------------------------------------------------------------------
258 260 # Functions and classes
259 261 #-----------------------------------------------------------------------------
260 262
261 263 def check_exclusions_exist():
262 264 from IPython.utils.path import get_ipython_package_dir
263 265 from IPython.utils.warn import warn
264 266 parent = os.path.dirname(get_ipython_package_dir())
265 267 for sec in test_sections:
266 268 for pattern in sec.exclusions:
267 269 fullpath = pjoin(parent, pattern)
268 270 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
269 271 warn("Excluding nonexistent file: %r" % pattern)
270 272
271 273
272 274 class ExclusionPlugin(Plugin):
273 275 """A nose plugin to effect our exclusions of files and directories.
274 276 """
275 277 name = 'exclusions'
276 278 score = 3000 # Should come before any other plugins
277 279
278 280 def __init__(self, exclude_patterns=None):
279 281 """
280 282 Parameters
281 283 ----------
282 284
283 285 exclude_patterns : sequence of strings, optional
284 286 Filenames containing these patterns (as raw strings, not as regular
285 287 expressions) are excluded from the tests.
286 288 """
287 289 self.exclude_patterns = exclude_patterns or []
288 290 super(ExclusionPlugin, self).__init__()
289 291
290 292 def options(self, parser, env=os.environ):
291 293 Plugin.options(self, parser, env)
292 294
293 295 def configure(self, options, config):
294 296 Plugin.configure(self, options, config)
295 297 # Override nose trying to disable plugin.
296 298 self.enabled = True
297 299
298 300 def wantFile(self, filename):
299 301 """Return whether the given filename should be scanned for tests.
300 302 """
301 303 if any(pat in filename for pat in self.exclude_patterns):
302 304 return False
303 305 return None
304 306
305 307 def wantDirectory(self, directory):
306 308 """Return whether the given directory should be scanned for tests.
307 309 """
308 310 if any(pat in directory for pat in self.exclude_patterns):
309 311 return False
310 312 return None
311 313
312 314
313 315 class StreamCapturer(Thread):
314 316 daemon = True # Don't hang if main thread crashes
315 317 started = False
316 318 def __init__(self, echo=False):
317 319 super(StreamCapturer, self).__init__()
318 320 self.echo = echo
319 321 self.streams = []
320 322 self.buffer = BytesIO()
321 323 self.readfd, self.writefd = os.pipe()
322 324 self.buffer_lock = Lock()
323 325 self.stop = Event()
324 326
325 327 def run(self):
326 328 self.started = True
327 329
328 330 while not self.stop.is_set():
329 331 chunk = os.read(self.readfd, 1024)
330 332
331 333 with self.buffer_lock:
332 334 self.buffer.write(chunk)
333 335 if self.echo:
334 336 sys.stdout.write(bytes_to_str(chunk))
335 337
336 338 os.close(self.readfd)
337 339 os.close(self.writefd)
338 340
339 341 def reset_buffer(self):
340 342 with self.buffer_lock:
341 343 self.buffer.truncate(0)
342 344 self.buffer.seek(0)
343 345
344 346 def get_buffer(self):
345 347 with self.buffer_lock:
346 348 return self.buffer.getvalue()
347 349
348 350 def ensure_started(self):
349 351 if not self.started:
350 352 self.start()
351 353
352 354 def halt(self):
353 355 """Safely stop the thread."""
354 356 if not self.started:
355 357 return
356 358
357 359 self.stop.set()
358 360 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
359 361 self.join()
360 362
361 363 class SubprocessStreamCapturePlugin(Plugin):
362 364 name='subprocstreams'
363 365 def __init__(self):
364 366 Plugin.__init__(self)
365 367 self.stream_capturer = StreamCapturer()
366 368 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
367 369 # This is ugly, but distant parts of the test machinery need to be able
368 370 # to redirect streams, so we make the object globally accessible.
369 371 nose.iptest_stdstreams_fileno = self.get_write_fileno
370 372
371 373 def get_write_fileno(self):
372 374 if self.destination == 'capture':
373 375 self.stream_capturer.ensure_started()
374 376 return self.stream_capturer.writefd
375 377 elif self.destination == 'discard':
376 378 return os.open(os.devnull, os.O_WRONLY)
377 379 else:
378 380 return sys.__stdout__.fileno()
379 381
380 382 def configure(self, options, config):
381 383 Plugin.configure(self, options, config)
382 384 # Override nose trying to disable plugin.
383 385 if self.destination == 'capture':
384 386 self.enabled = True
385 387
386 388 def startTest(self, test):
387 389 # Reset log capture
388 390 self.stream_capturer.reset_buffer()
389 391
390 392 def formatFailure(self, test, err):
391 393 # Show output
392 394 ec, ev, tb = err
393 395 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
394 396 if captured.strip():
395 397 ev = safe_str(ev)
396 398 out = [ev, '>> begin captured subprocess output <<',
397 399 captured,
398 400 '>> end captured subprocess output <<']
399 401 return ec, '\n'.join(out), tb
400 402
401 403 return err
402 404
403 405 formatError = formatFailure
404 406
405 407 def finalize(self, result):
406 408 self.stream_capturer.halt()
407 409
408 410
409 411 def run_iptest():
410 412 """Run the IPython test suite using nose.
411 413
412 414 This function is called when this script is **not** called with the form
413 415 `iptest all`. It simply calls nose with appropriate command line flags
414 416 and accepts all of the standard nose arguments.
415 417 """
416 418 # Apply our monkeypatch to Xunit
417 419 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
418 420 monkeypatch_xunit()
419 421
420 422 warnings.filterwarnings('ignore',
421 423 'This will be removed soon. Use IPython.testing.util instead')
422 424
423 425 arg1 = sys.argv[1]
424 426 if arg1 in test_sections:
425 427 section = test_sections[arg1]
426 428 sys.argv[1:2] = section.includes
427 429 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
428 430 section = test_sections[arg1[8:]]
429 431 sys.argv[1:2] = section.includes
430 432 else:
431 433 section = TestSection(arg1, includes=[arg1])
432 434
433 435
434 436 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
435 437 # We add --exe because of setuptools' imbecility (it
436 438 # blindly does chmod +x on ALL files). Nose does the
437 439 # right thing and it tries to avoid executables,
438 440 # setuptools unfortunately forces our hand here. This
439 441 # has been discussed on the distutils list and the
440 442 # setuptools devs refuse to fix this problem!
441 443 '--exe',
442 444 ]
443 445 if '-a' not in argv and '-A' not in argv:
444 446 argv = argv + ['-a', '!crash']
445 447
446 448 if nose.__version__ >= '0.11':
447 449 # I don't fully understand why we need this one, but depending on what
448 450 # directory the test suite is run from, if we don't give it, 0 tests
449 451 # get run. Specifically, if the test suite is run from the source dir
450 452 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
451 453 # even if the same call done in this directory works fine). It appears
452 454 # that if the requested package is in the current dir, nose bails early
453 455 # by default. Since it's otherwise harmless, leave it in by default
454 456 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
455 457 argv.append('--traverse-namespace')
456 458
457 459 plugins = [ ExclusionPlugin(section.excludes), KnownFailure(),
458 460 SubprocessStreamCapturePlugin() ]
459 461
460 462 # we still have some vestigial doctests in core
461 463 if (section.name.startswith(('core', 'IPython.core'))):
462 464 plugins.append(IPythonDoctest())
463 465 argv.extend([
464 466 '--with-ipdoctest',
465 467 '--ipdoctest-tests',
466 468 '--ipdoctest-extension=txt',
467 469 ])
468 470
469 471
470 472 # Use working directory set by parent process (see iptestcontroller)
471 473 if 'IPTEST_WORKING_DIR' in os.environ:
472 474 os.chdir(os.environ['IPTEST_WORKING_DIR'])
473 475
474 476 # We need a global ipython running in this process, but the special
475 477 # in-process group spawns its own IPython kernels, so for *that* group we
476 478 # must avoid also opening the global one (otherwise there's a conflict of
477 479 # singletons). Ultimately the solution to this problem is to refactor our
478 480 # assumptions about what needs to be a singleton and what doesn't (app
479 481 # objects should, individual shells shouldn't). But for now, this
480 482 # workaround allows the test suite for the inprocess module to complete.
481 483 if 'kernel.inprocess' not in section.name:
482 484 from IPython.testing import globalipapp
483 485 globalipapp.start_ipython()
484 486
485 487 # Now nose can run
486 488 TestProgram(argv=argv, addplugins=plugins)
487 489
488 490 if __name__ == '__main__':
489 491 run_iptest()
490 492
General Comments 0
You need to be logged in to leave comments. Login now