##// END OF EJS Templates
Merge pull request #4706 from minrk/irunner...
Thomas Kluyver -
r13859:9a88a54b merge
parent child Browse files
Show More
@@ -0,0 +1,2 b''
1 :mod:`IPython.lib.irunner` and its command-line entry point have been removed.
2 It had fallen out of use long ago. No newline at end of file
@@ -1,15 +1,11 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 from IPython.testing import decorators as dec
2 from IPython.testing import decorators as dec
3
3
4 def test_import_backgroundjobs():
4 def test_import_backgroundjobs():
5 from IPython.lib import backgroundjobs
5 from IPython.lib import backgroundjobs
6
6
7 def test_import_deepreload():
7 def test_import_deepreload():
8 from IPython.lib import deepreload
8 from IPython.lib import deepreload
9
9
10 def test_import_demo():
10 def test_import_demo():
11 from IPython.lib import demo
11 from IPython.lib import demo
12
13 @dec.skip_win32
14 def test_import_irunner():
15 from IPython.lib import irunner
@@ -1,530 +1,525 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython
4 This module provides a main entry point to a user script to test IPython
5 itself from the command line. There are two ways of running this script:
5 itself from the command line. There are two ways of running this script:
6
6
7 1. With the syntax `iptest all`. This runs our entire test suite by
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 calling this script (with different arguments) recursively. This
8 calling this script (with different arguments) recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
13 plugins loaded.
14
14
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Copyright (C) 2009-2011 The IPython Development Team
18 # Copyright (C) 2009-2011 The IPython Development Team
19 #
19 #
20 # Distributed under the terms of the BSD License. The full license is in
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
21 # the file COPYING, distributed as part of this software.
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Imports
25 # Imports
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 from __future__ import print_function
27 from __future__ import print_function
28
28
29 # Stdlib
29 # Stdlib
30 import glob
30 import glob
31 from io import BytesIO
31 from io import BytesIO
32 import os
32 import os
33 import os.path as path
33 import os.path as path
34 import sys
34 import sys
35 from threading import Thread, Lock, Event
35 from threading import Thread, Lock, Event
36 import warnings
36 import warnings
37
37
38 # Now, proceed to import nose itself
38 # Now, proceed to import nose itself
39 import nose.plugins.builtin
39 import nose.plugins.builtin
40 from nose.plugins.xunit import Xunit
40 from nose.plugins.xunit import Xunit
41 from nose import SkipTest
41 from nose import SkipTest
42 from nose.core import TestProgram
42 from nose.core import TestProgram
43 from nose.plugins import Plugin
43 from nose.plugins import Plugin
44 from nose.util import safe_str
44 from nose.util import safe_str
45
45
46 # Our own imports
46 # Our own imports
47 from IPython.utils.process import is_cmd_found
47 from IPython.utils.process import is_cmd_found
48 from IPython.utils.importstring import import_item
48 from IPython.utils.importstring import import_item
49 from IPython.testing.plugin.ipdoctest import IPythonDoctest
49 from IPython.testing.plugin.ipdoctest import IPythonDoctest
50 from IPython.external.decorators import KnownFailure, knownfailureif
50 from IPython.external.decorators import KnownFailure, knownfailureif
51
51
52 pjoin = path.join
52 pjoin = path.join
53
53
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Globals
56 # Globals
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58
58
59
59
60 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
61 # Warnings control
61 # Warnings control
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63
63
64 # Twisted generates annoying warnings with Python 2.6, as will do other code
64 # Twisted generates annoying warnings with Python 2.6, as will do other code
65 # that imports 'sets' as of today
65 # that imports 'sets' as of today
66 warnings.filterwarnings('ignore', 'the sets module is deprecated',
66 warnings.filterwarnings('ignore', 'the sets module is deprecated',
67 DeprecationWarning )
67 DeprecationWarning )
68
68
69 # This one also comes from Twisted
69 # This one also comes from Twisted
70 warnings.filterwarnings('ignore', 'the sha module is deprecated',
70 warnings.filterwarnings('ignore', 'the sha module is deprecated',
71 DeprecationWarning)
71 DeprecationWarning)
72
72
73 # Wx on Fedora11 spits these out
73 # Wx on Fedora11 spits these out
74 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
74 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
75 UserWarning)
75 UserWarning)
76
76
77 # ------------------------------------------------------------------------------
77 # ------------------------------------------------------------------------------
78 # Monkeypatch Xunit to count known failures as skipped.
78 # Monkeypatch Xunit to count known failures as skipped.
79 # ------------------------------------------------------------------------------
79 # ------------------------------------------------------------------------------
80 def monkeypatch_xunit():
80 def monkeypatch_xunit():
81 try:
81 try:
82 knownfailureif(True)(lambda: None)()
82 knownfailureif(True)(lambda: None)()
83 except Exception as e:
83 except Exception as e:
84 KnownFailureTest = type(e)
84 KnownFailureTest = type(e)
85
85
86 def addError(self, test, err, capt=None):
86 def addError(self, test, err, capt=None):
87 if issubclass(err[0], KnownFailureTest):
87 if issubclass(err[0], KnownFailureTest):
88 err = (SkipTest,) + err[1:]
88 err = (SkipTest,) + err[1:]
89 return self.orig_addError(test, err, capt)
89 return self.orig_addError(test, err, capt)
90
90
91 Xunit.orig_addError = Xunit.addError
91 Xunit.orig_addError = Xunit.addError
92 Xunit.addError = addError
92 Xunit.addError = addError
93
93
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95 # Check which dependencies are installed and greater than minimum version.
95 # Check which dependencies are installed and greater than minimum version.
96 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
97 def extract_version(mod):
97 def extract_version(mod):
98 return mod.__version__
98 return mod.__version__
99
99
100 def test_for(item, min_version=None, callback=extract_version):
100 def test_for(item, min_version=None, callback=extract_version):
101 """Test to see if item is importable, and optionally check against a minimum
101 """Test to see if item is importable, and optionally check against a minimum
102 version.
102 version.
103
103
104 If min_version is given, the default behavior is to check against the
104 If min_version is given, the default behavior is to check against the
105 `__version__` attribute of the item, but specifying `callback` allows you to
105 `__version__` attribute of the item, but specifying `callback` allows you to
106 extract the value you are interested in. e.g::
106 extract the value you are interested in. e.g::
107
107
108 In [1]: import sys
108 In [1]: import sys
109
109
110 In [2]: from IPython.testing.iptest import test_for
110 In [2]: from IPython.testing.iptest import test_for
111
111
112 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
112 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
113 Out[3]: True
113 Out[3]: True
114
114
115 """
115 """
116 try:
116 try:
117 check = import_item(item)
117 check = import_item(item)
118 except (ImportError, RuntimeError):
118 except (ImportError, RuntimeError):
119 # GTK reports Runtime error if it can't be initialized even if it's
119 # GTK reports Runtime error if it can't be initialized even if it's
120 # importable.
120 # importable.
121 return False
121 return False
122 else:
122 else:
123 if min_version:
123 if min_version:
124 if callback:
124 if callback:
125 # extra processing step to get version to compare
125 # extra processing step to get version to compare
126 check = callback(check)
126 check = callback(check)
127
127
128 return check >= min_version
128 return check >= min_version
129 else:
129 else:
130 return True
130 return True
131
131
132 # Global dict where we can store information on what we have and what we don't
132 # Global dict where we can store information on what we have and what we don't
133 # have available at test run time
133 # have available at test run time
134 have = {}
134 have = {}
135
135
136 have['curses'] = test_for('_curses')
136 have['curses'] = test_for('_curses')
137 have['matplotlib'] = test_for('matplotlib')
137 have['matplotlib'] = test_for('matplotlib')
138 have['numpy'] = test_for('numpy')
138 have['numpy'] = test_for('numpy')
139 have['pexpect'] = test_for('IPython.external.pexpect')
139 have['pexpect'] = test_for('IPython.external.pexpect')
140 have['pymongo'] = test_for('pymongo')
140 have['pymongo'] = test_for('pymongo')
141 have['pygments'] = test_for('pygments')
141 have['pygments'] = test_for('pygments')
142 have['qt'] = test_for('IPython.external.qt')
142 have['qt'] = test_for('IPython.external.qt')
143 have['rpy2'] = test_for('rpy2')
143 have['rpy2'] = test_for('rpy2')
144 have['sqlite3'] = test_for('sqlite3')
144 have['sqlite3'] = test_for('sqlite3')
145 have['cython'] = test_for('Cython')
145 have['cython'] = test_for('Cython')
146 have['oct2py'] = test_for('oct2py')
146 have['oct2py'] = test_for('oct2py')
147 have['tornado'] = test_for('tornado.version_info', (3,1,0), callback=None)
147 have['tornado'] = test_for('tornado.version_info', (3,1,0), callback=None)
148 have['jinja2'] = test_for('jinja2')
148 have['jinja2'] = test_for('jinja2')
149 have['azure'] = test_for('azure')
149 have['azure'] = test_for('azure')
150 have['requests'] = test_for('requests')
150 have['requests'] = test_for('requests')
151 have['sphinx'] = test_for('sphinx')
151 have['sphinx'] = test_for('sphinx')
152 have['casperjs'] = is_cmd_found('casperjs')
152 have['casperjs'] = is_cmd_found('casperjs')
153
153
154 min_zmq = (2,1,11)
154 min_zmq = (2,1,11)
155
155
156 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
156 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
157
157
158 #-----------------------------------------------------------------------------
158 #-----------------------------------------------------------------------------
159 # Test suite definitions
159 # Test suite definitions
160 #-----------------------------------------------------------------------------
160 #-----------------------------------------------------------------------------
161
161
162 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
162 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
163 'extensions', 'lib', 'terminal', 'testing', 'utils',
163 'extensions', 'lib', 'terminal', 'testing', 'utils',
164 'nbformat', 'qt', 'html', 'nbconvert'
164 'nbformat', 'qt', 'html', 'nbconvert'
165 ]
165 ]
166
166
167 class TestSection(object):
167 class TestSection(object):
168 def __init__(self, name, includes):
168 def __init__(self, name, includes):
169 self.name = name
169 self.name = name
170 self.includes = includes
170 self.includes = includes
171 self.excludes = []
171 self.excludes = []
172 self.dependencies = []
172 self.dependencies = []
173 self.enabled = True
173 self.enabled = True
174
174
175 def exclude(self, module):
175 def exclude(self, module):
176 if not module.startswith('IPython'):
176 if not module.startswith('IPython'):
177 module = self.includes[0] + "." + module
177 module = self.includes[0] + "." + module
178 self.excludes.append(module.replace('.', os.sep))
178 self.excludes.append(module.replace('.', os.sep))
179
179
180 def requires(self, *packages):
180 def requires(self, *packages):
181 self.dependencies.extend(packages)
181 self.dependencies.extend(packages)
182
182
183 @property
183 @property
184 def will_run(self):
184 def will_run(self):
185 return self.enabled and all(have[p] for p in self.dependencies)
185 return self.enabled and all(have[p] for p in self.dependencies)
186
186
187 # Name -> (include, exclude, dependencies_met)
187 # Name -> (include, exclude, dependencies_met)
188 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
188 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
189
189
190 # Exclusions and dependencies
190 # Exclusions and dependencies
191 # ---------------------------
191 # ---------------------------
192
192
193 # core:
193 # core:
194 sec = test_sections['core']
194 sec = test_sections['core']
195 if not have['sqlite3']:
195 if not have['sqlite3']:
196 sec.exclude('tests.test_history')
196 sec.exclude('tests.test_history')
197 sec.exclude('history')
197 sec.exclude('history')
198 if not have['matplotlib']:
198 if not have['matplotlib']:
199 sec.exclude('pylabtools'),
199 sec.exclude('pylabtools'),
200 sec.exclude('tests.test_pylabtools')
200 sec.exclude('tests.test_pylabtools')
201
201
202 # lib:
202 # lib:
203 sec = test_sections['lib']
203 sec = test_sections['lib']
204 if not have['pexpect']:
205 sec.exclude('irunner')
206 sec.exclude('tests.test_irunner')
207 if not have['zmq']:
204 if not have['zmq']:
208 sec.exclude('kernel')
205 sec.exclude('kernel')
209 # We do this unconditionally, so that the test suite doesn't import
206 # We do this unconditionally, so that the test suite doesn't import
210 # gtk, changing the default encoding and masking some unicode bugs.
207 # gtk, changing the default encoding and masking some unicode bugs.
211 sec.exclude('inputhookgtk')
208 sec.exclude('inputhookgtk')
212 # We also do this unconditionally, because wx can interfere with Unix signals.
209 # We also do this unconditionally, because wx can interfere with Unix signals.
213 # There are currently no tests for it anyway.
210 # There are currently no tests for it anyway.
214 sec.exclude('inputhookwx')
211 sec.exclude('inputhookwx')
215 # Testing inputhook will need a lot of thought, to figure out
212 # Testing inputhook will need a lot of thought, to figure out
216 # how to have tests that don't lock up with the gui event
213 # how to have tests that don't lock up with the gui event
217 # loops in the picture
214 # loops in the picture
218 sec.exclude('inputhook')
215 sec.exclude('inputhook')
219
216
220 # testing:
217 # testing:
221 sec = test_sections['testing']
218 sec = test_sections['testing']
222 # This guy is probably attic material
223 sec.exclude('mkdoctests')
224 # These have to be skipped on win32 because they use echo, rm, cd, etc.
219 # These have to be skipped on win32 because they use echo, rm, cd, etc.
225 # See ticket https://github.com/ipython/ipython/issues/87
220 # See ticket https://github.com/ipython/ipython/issues/87
226 if sys.platform == 'win32':
221 if sys.platform == 'win32':
227 sec.exclude('plugin.test_exampleip')
222 sec.exclude('plugin.test_exampleip')
228 sec.exclude('plugin.dtexample')
223 sec.exclude('plugin.dtexample')
229
224
230 # terminal:
225 # terminal:
231 if (not have['pexpect']) or (not have['zmq']):
226 if (not have['pexpect']) or (not have['zmq']):
232 test_sections['terminal'].exclude('console')
227 test_sections['terminal'].exclude('console')
233
228
234 # parallel
229 # parallel
235 sec = test_sections['parallel']
230 sec = test_sections['parallel']
236 sec.requires('zmq')
231 sec.requires('zmq')
237 if not have['pymongo']:
232 if not have['pymongo']:
238 sec.exclude('controller.mongodb')
233 sec.exclude('controller.mongodb')
239 sec.exclude('tests.test_mongodb')
234 sec.exclude('tests.test_mongodb')
240
235
241 # kernel:
236 # kernel:
242 sec = test_sections['kernel']
237 sec = test_sections['kernel']
243 sec.requires('zmq')
238 sec.requires('zmq')
244 # The in-process kernel tests are done in a separate section
239 # The in-process kernel tests are done in a separate section
245 sec.exclude('inprocess')
240 sec.exclude('inprocess')
246 # importing gtk sets the default encoding, which we want to avoid
241 # importing gtk sets the default encoding, which we want to avoid
247 sec.exclude('zmq.gui.gtkembed')
242 sec.exclude('zmq.gui.gtkembed')
248 if not have['matplotlib']:
243 if not have['matplotlib']:
249 sec.exclude('zmq.pylab')
244 sec.exclude('zmq.pylab')
250
245
251 # kernel.inprocess:
246 # kernel.inprocess:
252 test_sections['kernel.inprocess'].requires('zmq')
247 test_sections['kernel.inprocess'].requires('zmq')
253
248
254 # extensions:
249 # extensions:
255 sec = test_sections['extensions']
250 sec = test_sections['extensions']
256 if not have['cython']:
251 if not have['cython']:
257 sec.exclude('cythonmagic')
252 sec.exclude('cythonmagic')
258 sec.exclude('tests.test_cythonmagic')
253 sec.exclude('tests.test_cythonmagic')
259 if not have['oct2py']:
254 if not have['oct2py']:
260 sec.exclude('octavemagic')
255 sec.exclude('octavemagic')
261 sec.exclude('tests.test_octavemagic')
256 sec.exclude('tests.test_octavemagic')
262 if not have['rpy2'] or not have['numpy']:
257 if not have['rpy2'] or not have['numpy']:
263 sec.exclude('rmagic')
258 sec.exclude('rmagic')
264 sec.exclude('tests.test_rmagic')
259 sec.exclude('tests.test_rmagic')
265 # autoreload does some strange stuff, so move it to its own test section
260 # autoreload does some strange stuff, so move it to its own test section
266 sec.exclude('autoreload')
261 sec.exclude('autoreload')
267 sec.exclude('tests.test_autoreload')
262 sec.exclude('tests.test_autoreload')
268 test_sections['autoreload'] = TestSection('autoreload',
263 test_sections['autoreload'] = TestSection('autoreload',
269 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
264 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
270 test_group_names.append('autoreload')
265 test_group_names.append('autoreload')
271
266
272 # qt:
267 # qt:
273 test_sections['qt'].requires('zmq', 'qt', 'pygments')
268 test_sections['qt'].requires('zmq', 'qt', 'pygments')
274
269
275 # html:
270 # html:
276 sec = test_sections['html']
271 sec = test_sections['html']
277 sec.requires('zmq', 'tornado', 'requests')
272 sec.requires('zmq', 'tornado', 'requests')
278 # The notebook 'static' directory contains JS, css and other
273 # The notebook 'static' directory contains JS, css and other
279 # files for web serving. Occasionally projects may put a .py
274 # files for web serving. Occasionally projects may put a .py
280 # file in there (MathJax ships a conf.py), so we might as
275 # file in there (MathJax ships a conf.py), so we might as
281 # well play it safe and skip the whole thing.
276 # well play it safe and skip the whole thing.
282 sec.exclude('static')
277 sec.exclude('static')
283 sec.exclude('fabfile')
278 sec.exclude('fabfile')
284 if not have['jinja2']:
279 if not have['jinja2']:
285 sec.exclude('notebookapp')
280 sec.exclude('notebookapp')
286 if not have['azure']:
281 if not have['azure']:
287 sec.exclude('services.notebooks.azurenbmanager')
282 sec.exclude('services.notebooks.azurenbmanager')
288
283
289 # config:
284 # config:
290 # Config files aren't really importable stand-alone
285 # Config files aren't really importable stand-alone
291 test_sections['config'].exclude('profile')
286 test_sections['config'].exclude('profile')
292
287
293 # nbconvert:
288 # nbconvert:
294 sec = test_sections['nbconvert']
289 sec = test_sections['nbconvert']
295 sec.requires('pygments', 'jinja2', 'sphinx')
290 sec.requires('pygments', 'jinja2', 'sphinx')
296 # Exclude nbconvert directories containing config files used to test.
291 # Exclude nbconvert directories containing config files used to test.
297 # Executing the config files with iptest would cause an exception.
292 # Executing the config files with iptest would cause an exception.
298 sec.exclude('tests.files')
293 sec.exclude('tests.files')
299 sec.exclude('exporters.tests.files')
294 sec.exclude('exporters.tests.files')
300 if not have['tornado']:
295 if not have['tornado']:
301 sec.exclude('nbconvert.post_processors.serve')
296 sec.exclude('nbconvert.post_processors.serve')
302 sec.exclude('nbconvert.post_processors.tests.test_serve')
297 sec.exclude('nbconvert.post_processors.tests.test_serve')
303
298
304 #-----------------------------------------------------------------------------
299 #-----------------------------------------------------------------------------
305 # Functions and classes
300 # Functions and classes
306 #-----------------------------------------------------------------------------
301 #-----------------------------------------------------------------------------
307
302
308 def check_exclusions_exist():
303 def check_exclusions_exist():
309 from IPython.utils.path import get_ipython_package_dir
304 from IPython.utils.path import get_ipython_package_dir
310 from IPython.utils.warn import warn
305 from IPython.utils.warn import warn
311 parent = os.path.dirname(get_ipython_package_dir())
306 parent = os.path.dirname(get_ipython_package_dir())
312 for sec in test_sections:
307 for sec in test_sections:
313 for pattern in sec.exclusions:
308 for pattern in sec.exclusions:
314 fullpath = pjoin(parent, pattern)
309 fullpath = pjoin(parent, pattern)
315 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
310 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
316 warn("Excluding nonexistent file: %r" % pattern)
311 warn("Excluding nonexistent file: %r" % pattern)
317
312
318
313
319 class ExclusionPlugin(Plugin):
314 class ExclusionPlugin(Plugin):
320 """A nose plugin to effect our exclusions of files and directories.
315 """A nose plugin to effect our exclusions of files and directories.
321 """
316 """
322 name = 'exclusions'
317 name = 'exclusions'
323 score = 3000 # Should come before any other plugins
318 score = 3000 # Should come before any other plugins
324
319
325 def __init__(self, exclude_patterns=None):
320 def __init__(self, exclude_patterns=None):
326 """
321 """
327 Parameters
322 Parameters
328 ----------
323 ----------
329
324
330 exclude_patterns : sequence of strings, optional
325 exclude_patterns : sequence of strings, optional
331 Filenames containing these patterns (as raw strings, not as regular
326 Filenames containing these patterns (as raw strings, not as regular
332 expressions) are excluded from the tests.
327 expressions) are excluded from the tests.
333 """
328 """
334 self.exclude_patterns = exclude_patterns or []
329 self.exclude_patterns = exclude_patterns or []
335 super(ExclusionPlugin, self).__init__()
330 super(ExclusionPlugin, self).__init__()
336
331
337 def options(self, parser, env=os.environ):
332 def options(self, parser, env=os.environ):
338 Plugin.options(self, parser, env)
333 Plugin.options(self, parser, env)
339
334
340 def configure(self, options, config):
335 def configure(self, options, config):
341 Plugin.configure(self, options, config)
336 Plugin.configure(self, options, config)
342 # Override nose trying to disable plugin.
337 # Override nose trying to disable plugin.
343 self.enabled = True
338 self.enabled = True
344
339
345 def wantFile(self, filename):
340 def wantFile(self, filename):
346 """Return whether the given filename should be scanned for tests.
341 """Return whether the given filename should be scanned for tests.
347 """
342 """
348 if any(pat in filename for pat in self.exclude_patterns):
343 if any(pat in filename for pat in self.exclude_patterns):
349 return False
344 return False
350 return None
345 return None
351
346
352 def wantDirectory(self, directory):
347 def wantDirectory(self, directory):
353 """Return whether the given directory should be scanned for tests.
348 """Return whether the given directory should be scanned for tests.
354 """
349 """
355 if any(pat in directory for pat in self.exclude_patterns):
350 if any(pat in directory for pat in self.exclude_patterns):
356 return False
351 return False
357 return None
352 return None
358
353
359
354
360 class StreamCapturer(Thread):
355 class StreamCapturer(Thread):
361 daemon = True # Don't hang if main thread crashes
356 daemon = True # Don't hang if main thread crashes
362 started = False
357 started = False
363 def __init__(self):
358 def __init__(self):
364 super(StreamCapturer, self).__init__()
359 super(StreamCapturer, self).__init__()
365 self.streams = []
360 self.streams = []
366 self.buffer = BytesIO()
361 self.buffer = BytesIO()
367 self.readfd, self.writefd = os.pipe()
362 self.readfd, self.writefd = os.pipe()
368 self.buffer_lock = Lock()
363 self.buffer_lock = Lock()
369 self.stop = Event()
364 self.stop = Event()
370
365
371 def run(self):
366 def run(self):
372 self.started = True
367 self.started = True
373
368
374 while not self.stop.is_set():
369 while not self.stop.is_set():
375 chunk = os.read(self.readfd, 1024)
370 chunk = os.read(self.readfd, 1024)
376
371
377 with self.buffer_lock:
372 with self.buffer_lock:
378 self.buffer.write(chunk)
373 self.buffer.write(chunk)
379
374
380 os.close(self.readfd)
375 os.close(self.readfd)
381 os.close(self.writefd)
376 os.close(self.writefd)
382
377
383 def reset_buffer(self):
378 def reset_buffer(self):
384 with self.buffer_lock:
379 with self.buffer_lock:
385 self.buffer.truncate(0)
380 self.buffer.truncate(0)
386 self.buffer.seek(0)
381 self.buffer.seek(0)
387
382
388 def get_buffer(self):
383 def get_buffer(self):
389 with self.buffer_lock:
384 with self.buffer_lock:
390 return self.buffer.getvalue()
385 return self.buffer.getvalue()
391
386
392 def ensure_started(self):
387 def ensure_started(self):
393 if not self.started:
388 if not self.started:
394 self.start()
389 self.start()
395
390
396 def halt(self):
391 def halt(self):
397 """Safely stop the thread."""
392 """Safely stop the thread."""
398 if not self.started:
393 if not self.started:
399 return
394 return
400
395
401 self.stop.set()
396 self.stop.set()
402 os.write(self.writefd, b'wake up') # Ensure we're not locked in a read()
397 os.write(self.writefd, b'wake up') # Ensure we're not locked in a read()
403 self.join()
398 self.join()
404
399
405 class SubprocessStreamCapturePlugin(Plugin):
400 class SubprocessStreamCapturePlugin(Plugin):
406 name='subprocstreams'
401 name='subprocstreams'
407 def __init__(self):
402 def __init__(self):
408 Plugin.__init__(self)
403 Plugin.__init__(self)
409 self.stream_capturer = StreamCapturer()
404 self.stream_capturer = StreamCapturer()
410 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
405 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
411 # This is ugly, but distant parts of the test machinery need to be able
406 # This is ugly, but distant parts of the test machinery need to be able
412 # to redirect streams, so we make the object globally accessible.
407 # to redirect streams, so we make the object globally accessible.
413 nose.iptest_stdstreams_fileno = self.get_write_fileno
408 nose.iptest_stdstreams_fileno = self.get_write_fileno
414
409
415 def get_write_fileno(self):
410 def get_write_fileno(self):
416 if self.destination == 'capture':
411 if self.destination == 'capture':
417 self.stream_capturer.ensure_started()
412 self.stream_capturer.ensure_started()
418 return self.stream_capturer.writefd
413 return self.stream_capturer.writefd
419 elif self.destination == 'discard':
414 elif self.destination == 'discard':
420 return os.open(os.devnull, os.O_WRONLY)
415 return os.open(os.devnull, os.O_WRONLY)
421 else:
416 else:
422 return sys.__stdout__.fileno()
417 return sys.__stdout__.fileno()
423
418
424 def configure(self, options, config):
419 def configure(self, options, config):
425 Plugin.configure(self, options, config)
420 Plugin.configure(self, options, config)
426 # Override nose trying to disable plugin.
421 # Override nose trying to disable plugin.
427 if self.destination == 'capture':
422 if self.destination == 'capture':
428 self.enabled = True
423 self.enabled = True
429
424
430 def startTest(self, test):
425 def startTest(self, test):
431 # Reset log capture
426 # Reset log capture
432 self.stream_capturer.reset_buffer()
427 self.stream_capturer.reset_buffer()
433
428
434 def formatFailure(self, test, err):
429 def formatFailure(self, test, err):
435 # Show output
430 # Show output
436 ec, ev, tb = err
431 ec, ev, tb = err
437 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
432 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
438 if captured.strip():
433 if captured.strip():
439 ev = safe_str(ev)
434 ev = safe_str(ev)
440 out = [ev, '>> begin captured subprocess output <<',
435 out = [ev, '>> begin captured subprocess output <<',
441 captured,
436 captured,
442 '>> end captured subprocess output <<']
437 '>> end captured subprocess output <<']
443 return ec, '\n'.join(out), tb
438 return ec, '\n'.join(out), tb
444
439
445 return err
440 return err
446
441
447 formatError = formatFailure
442 formatError = formatFailure
448
443
449 def finalize(self, result):
444 def finalize(self, result):
450 self.stream_capturer.halt()
445 self.stream_capturer.halt()
451
446
452
447
453 def run_iptest():
448 def run_iptest():
454 """Run the IPython test suite using nose.
449 """Run the IPython test suite using nose.
455
450
456 This function is called when this script is **not** called with the form
451 This function is called when this script is **not** called with the form
457 `iptest all`. It simply calls nose with appropriate command line flags
452 `iptest all`. It simply calls nose with appropriate command line flags
458 and accepts all of the standard nose arguments.
453 and accepts all of the standard nose arguments.
459 """
454 """
460 # Apply our monkeypatch to Xunit
455 # Apply our monkeypatch to Xunit
461 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
456 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
462 monkeypatch_xunit()
457 monkeypatch_xunit()
463
458
464 warnings.filterwarnings('ignore',
459 warnings.filterwarnings('ignore',
465 'This will be removed soon. Use IPython.testing.util instead')
460 'This will be removed soon. Use IPython.testing.util instead')
466
461
467 arg1 = sys.argv[1]
462 arg1 = sys.argv[1]
468 if arg1 in test_sections:
463 if arg1 in test_sections:
469 section = test_sections[arg1]
464 section = test_sections[arg1]
470 sys.argv[1:2] = section.includes
465 sys.argv[1:2] = section.includes
471 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
466 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
472 section = test_sections[arg1[8:]]
467 section = test_sections[arg1[8:]]
473 sys.argv[1:2] = section.includes
468 sys.argv[1:2] = section.includes
474 else:
469 else:
475 section = TestSection(arg1, includes=[arg1])
470 section = TestSection(arg1, includes=[arg1])
476
471
477
472
478 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
473 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
479
474
480 '--with-ipdoctest',
475 '--with-ipdoctest',
481 '--ipdoctest-tests','--ipdoctest-extension=txt',
476 '--ipdoctest-tests','--ipdoctest-extension=txt',
482
477
483 # We add --exe because of setuptools' imbecility (it
478 # We add --exe because of setuptools' imbecility (it
484 # blindly does chmod +x on ALL files). Nose does the
479 # blindly does chmod +x on ALL files). Nose does the
485 # right thing and it tries to avoid executables,
480 # right thing and it tries to avoid executables,
486 # setuptools unfortunately forces our hand here. This
481 # setuptools unfortunately forces our hand here. This
487 # has been discussed on the distutils list and the
482 # has been discussed on the distutils list and the
488 # setuptools devs refuse to fix this problem!
483 # setuptools devs refuse to fix this problem!
489 '--exe',
484 '--exe',
490 ]
485 ]
491 if '-a' not in argv and '-A' not in argv:
486 if '-a' not in argv and '-A' not in argv:
492 argv = argv + ['-a', '!crash']
487 argv = argv + ['-a', '!crash']
493
488
494 if nose.__version__ >= '0.11':
489 if nose.__version__ >= '0.11':
495 # I don't fully understand why we need this one, but depending on what
490 # I don't fully understand why we need this one, but depending on what
496 # directory the test suite is run from, if we don't give it, 0 tests
491 # directory the test suite is run from, if we don't give it, 0 tests
497 # get run. Specifically, if the test suite is run from the source dir
492 # get run. Specifically, if the test suite is run from the source dir
498 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
493 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
499 # even if the same call done in this directory works fine). It appears
494 # even if the same call done in this directory works fine). It appears
500 # that if the requested package is in the current dir, nose bails early
495 # that if the requested package is in the current dir, nose bails early
501 # by default. Since it's otherwise harmless, leave it in by default
496 # by default. Since it's otherwise harmless, leave it in by default
502 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
497 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
503 argv.append('--traverse-namespace')
498 argv.append('--traverse-namespace')
504
499
505 # use our plugin for doctesting. It will remove the standard doctest plugin
500 # use our plugin for doctesting. It will remove the standard doctest plugin
506 # if it finds it enabled
501 # if it finds it enabled
507 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
502 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
508 SubprocessStreamCapturePlugin() ]
503 SubprocessStreamCapturePlugin() ]
509
504
510 # Use working directory set by parent process (see iptestcontroller)
505 # Use working directory set by parent process (see iptestcontroller)
511 if 'IPTEST_WORKING_DIR' in os.environ:
506 if 'IPTEST_WORKING_DIR' in os.environ:
512 os.chdir(os.environ['IPTEST_WORKING_DIR'])
507 os.chdir(os.environ['IPTEST_WORKING_DIR'])
513
508
514 # We need a global ipython running in this process, but the special
509 # We need a global ipython running in this process, but the special
515 # in-process group spawns its own IPython kernels, so for *that* group we
510 # in-process group spawns its own IPython kernels, so for *that* group we
516 # must avoid also opening the global one (otherwise there's a conflict of
511 # must avoid also opening the global one (otherwise there's a conflict of
517 # singletons). Ultimately the solution to this problem is to refactor our
512 # singletons). Ultimately the solution to this problem is to refactor our
518 # assumptions about what needs to be a singleton and what doesn't (app
513 # assumptions about what needs to be a singleton and what doesn't (app
519 # objects should, individual shells shouldn't). But for now, this
514 # objects should, individual shells shouldn't). But for now, this
520 # workaround allows the test suite for the inprocess module to complete.
515 # workaround allows the test suite for the inprocess module to complete.
521 if 'kernel.inprocess' not in section.name:
516 if 'kernel.inprocess' not in section.name:
522 from IPython.testing import globalipapp
517 from IPython.testing import globalipapp
523 globalipapp.start_ipython()
518 globalipapp.start_ipython()
524
519
525 # Now nose can run
520 # Now nose can run
526 TestProgram(argv=argv, addplugins=plugins)
521 TestProgram(argv=argv, addplugins=plugins)
527
522
528 if __name__ == '__main__':
523 if __name__ == '__main__':
529 run_iptest()
524 run_iptest()
530
525
@@ -1,589 +1,587 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 This module defines the things that are used in setup.py for building IPython
3 This module defines the things that are used in setup.py for building IPython
4
4
5 This includes:
5 This includes:
6
6
7 * The basic arguments to setup
7 * The basic arguments to setup
8 * Functions for finding things like packages, package data, etc.
8 * Functions for finding things like packages, package data, etc.
9 * A function for checking dependencies.
9 * A function for checking dependencies.
10 """
10 """
11 from __future__ import print_function
11 from __future__ import print_function
12
12
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19
19
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
23 import errno
23 import errno
24 import os
24 import os
25 import sys
25 import sys
26
26
27 from distutils.command.build_py import build_py
27 from distutils.command.build_py import build_py
28 from distutils.command.build_scripts import build_scripts
28 from distutils.command.build_scripts import build_scripts
29 from distutils.command.install import install
29 from distutils.command.install import install
30 from distutils.command.install_scripts import install_scripts
30 from distutils.command.install_scripts import install_scripts
31 from distutils.cmd import Command
31 from distutils.cmd import Command
32 from glob import glob
32 from glob import glob
33 from subprocess import call
33 from subprocess import call
34
34
35 from setupext import install_data_ext
35 from setupext import install_data_ext
36
36
37 #-------------------------------------------------------------------------------
37 #-------------------------------------------------------------------------------
38 # Useful globals and utility functions
38 # Useful globals and utility functions
39 #-------------------------------------------------------------------------------
39 #-------------------------------------------------------------------------------
40
40
41 # A few handy globals
41 # A few handy globals
42 isfile = os.path.isfile
42 isfile = os.path.isfile
43 pjoin = os.path.join
43 pjoin = os.path.join
44 repo_root = os.path.dirname(os.path.abspath(__file__))
44 repo_root = os.path.dirname(os.path.abspath(__file__))
45
45
46 def oscmd(s):
46 def oscmd(s):
47 print(">", s)
47 print(">", s)
48 os.system(s)
48 os.system(s)
49
49
50 # Py3 compatibility hacks, without assuming IPython itself is installed with
50 # Py3 compatibility hacks, without assuming IPython itself is installed with
51 # the full py3compat machinery.
51 # the full py3compat machinery.
52
52
53 try:
53 try:
54 execfile
54 execfile
55 except NameError:
55 except NameError:
56 def execfile(fname, globs, locs=None):
56 def execfile(fname, globs, locs=None):
57 locs = locs or globs
57 locs = locs or globs
58 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
58 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
59
59
60 # A little utility we'll need below, since glob() does NOT allow you to do
60 # A little utility we'll need below, since glob() does NOT allow you to do
61 # exclusion on multiple endings!
61 # exclusion on multiple endings!
62 def file_doesnt_endwith(test,endings):
62 def file_doesnt_endwith(test,endings):
63 """Return true if test is a file and its name does NOT end with any
63 """Return true if test is a file and its name does NOT end with any
64 of the strings listed in endings."""
64 of the strings listed in endings."""
65 if not isfile(test):
65 if not isfile(test):
66 return False
66 return False
67 for e in endings:
67 for e in endings:
68 if test.endswith(e):
68 if test.endswith(e):
69 return False
69 return False
70 return True
70 return True
71
71
72 #---------------------------------------------------------------------------
72 #---------------------------------------------------------------------------
73 # Basic project information
73 # Basic project information
74 #---------------------------------------------------------------------------
74 #---------------------------------------------------------------------------
75
75
76 # release.py contains version, authors, license, url, keywords, etc.
76 # release.py contains version, authors, license, url, keywords, etc.
77 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
77 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
78
78
79 # Create a dict with the basic information
79 # Create a dict with the basic information
80 # This dict is eventually passed to setup after additional keys are added.
80 # This dict is eventually passed to setup after additional keys are added.
81 setup_args = dict(
81 setup_args = dict(
82 name = name,
82 name = name,
83 version = version,
83 version = version,
84 description = description,
84 description = description,
85 long_description = long_description,
85 long_description = long_description,
86 author = author,
86 author = author,
87 author_email = author_email,
87 author_email = author_email,
88 url = url,
88 url = url,
89 download_url = download_url,
89 download_url = download_url,
90 license = license,
90 license = license,
91 platforms = platforms,
91 platforms = platforms,
92 keywords = keywords,
92 keywords = keywords,
93 classifiers = classifiers,
93 classifiers = classifiers,
94 cmdclass = {'install_data': install_data_ext},
94 cmdclass = {'install_data': install_data_ext},
95 )
95 )
96
96
97
97
98 #---------------------------------------------------------------------------
98 #---------------------------------------------------------------------------
99 # Find packages
99 # Find packages
100 #---------------------------------------------------------------------------
100 #---------------------------------------------------------------------------
101
101
102 def find_packages():
102 def find_packages():
103 """
103 """
104 Find all of IPython's packages.
104 Find all of IPython's packages.
105 """
105 """
106 excludes = ['deathrow', 'quarantine']
106 excludes = ['deathrow', 'quarantine']
107 packages = []
107 packages = []
108 for dir,subdirs,files in os.walk('IPython'):
108 for dir,subdirs,files in os.walk('IPython'):
109 package = dir.replace(os.path.sep, '.')
109 package = dir.replace(os.path.sep, '.')
110 if any(package.startswith('IPython.'+exc) for exc in excludes):
110 if any(package.startswith('IPython.'+exc) for exc in excludes):
111 # package is to be excluded (e.g. deathrow)
111 # package is to be excluded (e.g. deathrow)
112 continue
112 continue
113 if '__init__.py' not in files:
113 if '__init__.py' not in files:
114 # not a package
114 # not a package
115 continue
115 continue
116 packages.append(package)
116 packages.append(package)
117 return packages
117 return packages
118
118
119 #---------------------------------------------------------------------------
119 #---------------------------------------------------------------------------
120 # Find package data
120 # Find package data
121 #---------------------------------------------------------------------------
121 #---------------------------------------------------------------------------
122
122
123 def find_package_data():
123 def find_package_data():
124 """
124 """
125 Find IPython's package_data.
125 Find IPython's package_data.
126 """
126 """
127 # This is not enough for these things to appear in an sdist.
127 # This is not enough for these things to appear in an sdist.
128 # We need to muck with the MANIFEST to get this to work
128 # We need to muck with the MANIFEST to get this to work
129
129
130 # exclude static things that we don't ship (e.g. mathjax)
130 # exclude static things that we don't ship (e.g. mathjax)
131 excludes = ['mathjax']
131 excludes = ['mathjax']
132
132
133 # add 'static/' prefix to exclusions, and tuplify for use in startswith
133 # add 'static/' prefix to exclusions, and tuplify for use in startswith
134 excludes = tuple([os.path.join('static', ex) for ex in excludes])
134 excludes = tuple([os.path.join('static', ex) for ex in excludes])
135
135
136 # walk notebook resources:
136 # walk notebook resources:
137 cwd = os.getcwd()
137 cwd = os.getcwd()
138 os.chdir(os.path.join('IPython', 'html'))
138 os.chdir(os.path.join('IPython', 'html'))
139 static_walk = list(os.walk('static'))
139 static_walk = list(os.walk('static'))
140 static_data = []
140 static_data = []
141 for parent, dirs, files in static_walk:
141 for parent, dirs, files in static_walk:
142 if parent.startswith(excludes):
142 if parent.startswith(excludes):
143 continue
143 continue
144 for f in files:
144 for f in files:
145 static_data.append(os.path.join(parent, f))
145 static_data.append(os.path.join(parent, f))
146
146
147 os.chdir(os.path.join('tests',))
147 os.chdir(os.path.join('tests',))
148 js_tests = glob('casperjs/*.*') + glob('casperjs/*/*')
148 js_tests = glob('casperjs/*.*') + glob('casperjs/*/*')
149 os.chdir(cwd)
149 os.chdir(cwd)
150
150
151 package_data = {
151 package_data = {
152 'IPython.config.profile' : ['README*', '*/*.py'],
152 'IPython.config.profile' : ['README*', '*/*.py'],
153 'IPython.core.tests' : ['*.png', '*.jpg'],
153 'IPython.core.tests' : ['*.png', '*.jpg'],
154 'IPython.lib.tests' : ['*.wav'],
154 'IPython.lib.tests' : ['*.wav'],
155 'IPython.testing' : ['*.txt'],
155 'IPython.testing' : ['*.txt'],
156 'IPython.testing.plugin' : ['*.txt'],
156 'IPython.testing.plugin' : ['*.txt'],
157 'IPython.html' : ['templates/*'] + static_data,
157 'IPython.html' : ['templates/*'] + static_data,
158 'IPython.html.tests' : js_tests,
158 'IPython.html.tests' : js_tests,
159 'IPython.qt.console' : ['resources/icon/*.svg'],
159 'IPython.qt.console' : ['resources/icon/*.svg'],
160 'IPython.nbconvert' : ['templates/*.tpl', 'templates/latex/*.tplx',
160 'IPython.nbconvert' : ['templates/*.tpl', 'templates/latex/*.tplx',
161 'templates/latex/skeleton/*.tplx', 'templates/skeleton/*',
161 'templates/latex/skeleton/*.tplx', 'templates/skeleton/*',
162 'templates/reveal_internals/*.tpl', 'tests/files/*.*',
162 'templates/reveal_internals/*.tpl', 'tests/files/*.*',
163 'exporters/tests/files/*.*'],
163 'exporters/tests/files/*.*'],
164 'IPython.nbformat' : ['tests/*.ipynb']
164 'IPython.nbformat' : ['tests/*.ipynb']
165 }
165 }
166 return package_data
166 return package_data
167
167
168
168
169 #---------------------------------------------------------------------------
169 #---------------------------------------------------------------------------
170 # Find data files
170 # Find data files
171 #---------------------------------------------------------------------------
171 #---------------------------------------------------------------------------
172
172
173 def make_dir_struct(tag,base,out_base):
173 def make_dir_struct(tag,base,out_base):
174 """Make the directory structure of all files below a starting dir.
174 """Make the directory structure of all files below a starting dir.
175
175
176 This is just a convenience routine to help build a nested directory
176 This is just a convenience routine to help build a nested directory
177 hierarchy because distutils is too stupid to do this by itself.
177 hierarchy because distutils is too stupid to do this by itself.
178
178
179 XXX - this needs a proper docstring!
179 XXX - this needs a proper docstring!
180 """
180 """
181
181
182 # we'll use these a lot below
182 # we'll use these a lot below
183 lbase = len(base)
183 lbase = len(base)
184 pathsep = os.path.sep
184 pathsep = os.path.sep
185 lpathsep = len(pathsep)
185 lpathsep = len(pathsep)
186
186
187 out = []
187 out = []
188 for (dirpath,dirnames,filenames) in os.walk(base):
188 for (dirpath,dirnames,filenames) in os.walk(base):
189 # we need to strip out the dirpath from the base to map it to the
189 # we need to strip out the dirpath from the base to map it to the
190 # output (installation) path. This requires possibly stripping the
190 # output (installation) path. This requires possibly stripping the
191 # path separator, because otherwise pjoin will not work correctly
191 # path separator, because otherwise pjoin will not work correctly
192 # (pjoin('foo/','/bar') returns '/bar').
192 # (pjoin('foo/','/bar') returns '/bar').
193
193
194 dp_eff = dirpath[lbase:]
194 dp_eff = dirpath[lbase:]
195 if dp_eff.startswith(pathsep):
195 if dp_eff.startswith(pathsep):
196 dp_eff = dp_eff[lpathsep:]
196 dp_eff = dp_eff[lpathsep:]
197 # The output path must be anchored at the out_base marker
197 # The output path must be anchored at the out_base marker
198 out_path = pjoin(out_base,dp_eff)
198 out_path = pjoin(out_base,dp_eff)
199 # Now we can generate the final filenames. Since os.walk only produces
199 # Now we can generate the final filenames. Since os.walk only produces
200 # filenames, we must join back with the dirpath to get full valid file
200 # filenames, we must join back with the dirpath to get full valid file
201 # paths:
201 # paths:
202 pfiles = [pjoin(dirpath,f) for f in filenames]
202 pfiles = [pjoin(dirpath,f) for f in filenames]
203 # Finally, generate the entry we need, which is a pari of (output
203 # Finally, generate the entry we need, which is a pari of (output
204 # path, files) for use as a data_files parameter in install_data.
204 # path, files) for use as a data_files parameter in install_data.
205 out.append((out_path, pfiles))
205 out.append((out_path, pfiles))
206
206
207 return out
207 return out
208
208
209
209
210 def find_data_files():
210 def find_data_files():
211 """
211 """
212 Find IPython's data_files.
212 Find IPython's data_files.
213
213
214 Most of these are docs.
214 Most of these are docs.
215 """
215 """
216
216
217 docdirbase = pjoin('share', 'doc', 'ipython')
217 docdirbase = pjoin('share', 'doc', 'ipython')
218 manpagebase = pjoin('share', 'man', 'man1')
218 manpagebase = pjoin('share', 'man', 'man1')
219
219
220 # Simple file lists can be made by hand
220 # Simple file lists can be made by hand
221 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
221 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
222 if not manpages:
222 if not manpages:
223 # When running from a source tree, the manpages aren't gzipped
223 # When running from a source tree, the manpages aren't gzipped
224 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
224 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
225
225
226 igridhelpfiles = [f for f in glob(pjoin('IPython','extensions','igrid_help.*')) if isfile(f)]
226 igridhelpfiles = [f for f in glob(pjoin('IPython','extensions','igrid_help.*')) if isfile(f)]
227
227
228 # For nested structures, use the utility above
228 # For nested structures, use the utility above
229 example_files = make_dir_struct(
229 example_files = make_dir_struct(
230 'data',
230 'data',
231 pjoin('docs','examples'),
231 pjoin('docs','examples'),
232 pjoin(docdirbase,'examples')
232 pjoin(docdirbase,'examples')
233 )
233 )
234 manual_files = make_dir_struct(
234 manual_files = make_dir_struct(
235 'data',
235 'data',
236 pjoin('docs','html'),
236 pjoin('docs','html'),
237 pjoin(docdirbase,'manual')
237 pjoin(docdirbase,'manual')
238 )
238 )
239
239
240 # And assemble the entire output list
240 # And assemble the entire output list
241 data_files = [ (manpagebase, manpages),
241 data_files = [ (manpagebase, manpages),
242 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
242 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
243 ] + manual_files + example_files
243 ] + manual_files + example_files
244
244
245 return data_files
245 return data_files
246
246
247
247
248 def make_man_update_target(manpage):
248 def make_man_update_target(manpage):
249 """Return a target_update-compliant tuple for the given manpage.
249 """Return a target_update-compliant tuple for the given manpage.
250
250
251 Parameters
251 Parameters
252 ----------
252 ----------
253 manpage : string
253 manpage : string
254 Name of the manpage, must include the section number (trailing number).
254 Name of the manpage, must include the section number (trailing number).
255
255
256 Example
256 Example
257 -------
257 -------
258
258
259 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
259 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
260 ('docs/man/ipython.1.gz',
260 ('docs/man/ipython.1.gz',
261 ['docs/man/ipython.1'],
261 ['docs/man/ipython.1'],
262 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
262 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
263 """
263 """
264 man_dir = pjoin('docs', 'man')
264 man_dir = pjoin('docs', 'man')
265 manpage_gz = manpage + '.gz'
265 manpage_gz = manpage + '.gz'
266 manpath = pjoin(man_dir, manpage)
266 manpath = pjoin(man_dir, manpage)
267 manpath_gz = pjoin(man_dir, manpage_gz)
267 manpath_gz = pjoin(man_dir, manpage_gz)
268 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
268 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
269 locals() )
269 locals() )
270 return (manpath_gz, [manpath], gz_cmd)
270 return (manpath_gz, [manpath], gz_cmd)
271
271
272 # The two functions below are copied from IPython.utils.path, so we don't need
272 # The two functions below are copied from IPython.utils.path, so we don't need
273 # to import IPython during setup, which fails on Python 3.
273 # to import IPython during setup, which fails on Python 3.
274
274
275 def target_outdated(target,deps):
275 def target_outdated(target,deps):
276 """Determine whether a target is out of date.
276 """Determine whether a target is out of date.
277
277
278 target_outdated(target,deps) -> 1/0
278 target_outdated(target,deps) -> 1/0
279
279
280 deps: list of filenames which MUST exist.
280 deps: list of filenames which MUST exist.
281 target: single filename which may or may not exist.
281 target: single filename which may or may not exist.
282
282
283 If target doesn't exist or is older than any file listed in deps, return
283 If target doesn't exist or is older than any file listed in deps, return
284 true, otherwise return false.
284 true, otherwise return false.
285 """
285 """
286 try:
286 try:
287 target_time = os.path.getmtime(target)
287 target_time = os.path.getmtime(target)
288 except os.error:
288 except os.error:
289 return 1
289 return 1
290 for dep in deps:
290 for dep in deps:
291 dep_time = os.path.getmtime(dep)
291 dep_time = os.path.getmtime(dep)
292 if dep_time > target_time:
292 if dep_time > target_time:
293 #print "For target",target,"Dep failed:",dep # dbg
293 #print "For target",target,"Dep failed:",dep # dbg
294 #print "times (dep,tar):",dep_time,target_time # dbg
294 #print "times (dep,tar):",dep_time,target_time # dbg
295 return 1
295 return 1
296 return 0
296 return 0
297
297
298
298
299 def target_update(target,deps,cmd):
299 def target_update(target,deps,cmd):
300 """Update a target with a given command given a list of dependencies.
300 """Update a target with a given command given a list of dependencies.
301
301
302 target_update(target,deps,cmd) -> runs cmd if target is outdated.
302 target_update(target,deps,cmd) -> runs cmd if target is outdated.
303
303
304 This is just a wrapper around target_outdated() which calls the given
304 This is just a wrapper around target_outdated() which calls the given
305 command if target is outdated."""
305 command if target is outdated."""
306
306
307 if target_outdated(target,deps):
307 if target_outdated(target,deps):
308 os.system(cmd)
308 os.system(cmd)
309
309
310 #---------------------------------------------------------------------------
310 #---------------------------------------------------------------------------
311 # Find scripts
311 # Find scripts
312 #---------------------------------------------------------------------------
312 #---------------------------------------------------------------------------
313
313
314 def find_entry_points():
314 def find_entry_points():
315 """Find IPython's scripts.
315 """Find IPython's scripts.
316
316
317 if entry_points is True:
317 if entry_points is True:
318 return setuptools entry_point-style definitions
318 return setuptools entry_point-style definitions
319 else:
319 else:
320 return file paths of plain scripts [default]
320 return file paths of plain scripts [default]
321
321
322 suffix is appended to script names if entry_points is True, so that the
322 suffix is appended to script names if entry_points is True, so that the
323 Python 3 scripts get named "ipython3" etc.
323 Python 3 scripts get named "ipython3" etc.
324 """
324 """
325 ep = [
325 ep = [
326 'ipython%s = IPython:start_ipython',
326 'ipython%s = IPython:start_ipython',
327 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
327 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
328 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
328 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
329 'iplogger%s = IPython.parallel.apps.iploggerapp:launch_new_instance',
330 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
329 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
331 'iptest%s = IPython.testing.iptestcontroller:main',
330 'iptest%s = IPython.testing.iptestcontroller:main',
332 'irunner%s = IPython.lib.irunner:main',
333 ]
331 ]
334 suffix = str(sys.version_info[0])
332 suffix = str(sys.version_info[0])
335 return [e % '' for e in ep] + [e % suffix for e in ep]
333 return [e % '' for e in ep] + [e % suffix for e in ep]
336
334
337 script_src = """#!{executable}
335 script_src = """#!{executable}
338 # This script was automatically generated by setup.py
336 # This script was automatically generated by setup.py
339 from {mod} import {func}
337 from {mod} import {func}
340 {func}()
338 {func}()
341 """
339 """
342
340
343 class build_scripts_entrypt(build_scripts):
341 class build_scripts_entrypt(build_scripts):
344 def run(self):
342 def run(self):
345 self.mkpath(self.build_dir)
343 self.mkpath(self.build_dir)
346 outfiles = []
344 outfiles = []
347 for script in find_entry_points():
345 for script in find_entry_points():
348 name, entrypt = script.split('=')
346 name, entrypt = script.split('=')
349 name = name.strip()
347 name = name.strip()
350 entrypt = entrypt.strip()
348 entrypt = entrypt.strip()
351 outfile = os.path.join(self.build_dir, name)
349 outfile = os.path.join(self.build_dir, name)
352 outfiles.append(outfile)
350 outfiles.append(outfile)
353 print('Writing script to', outfile)
351 print('Writing script to', outfile)
354
352
355 mod, func = entrypt.split(':')
353 mod, func = entrypt.split(':')
356 with open(outfile, 'w') as f:
354 with open(outfile, 'w') as f:
357 f.write(script_src.format(executable=sys.executable,
355 f.write(script_src.format(executable=sys.executable,
358 mod=mod, func=func))
356 mod=mod, func=func))
359
357
360 return outfiles, outfiles
358 return outfiles, outfiles
361
359
362 class install_lib_symlink(Command):
360 class install_lib_symlink(Command):
363 user_options = [
361 user_options = [
364 ('install-dir=', 'd', "directory to install to"),
362 ('install-dir=', 'd', "directory to install to"),
365 ]
363 ]
366
364
367 def initialize_options(self):
365 def initialize_options(self):
368 self.install_dir = None
366 self.install_dir = None
369
367
370 def finalize_options(self):
368 def finalize_options(self):
371 self.set_undefined_options('symlink',
369 self.set_undefined_options('symlink',
372 ('install_lib', 'install_dir'),
370 ('install_lib', 'install_dir'),
373 )
371 )
374
372
375 def run(self):
373 def run(self):
376 if sys.platform == 'win32':
374 if sys.platform == 'win32':
377 raise Exception("This doesn't work on Windows.")
375 raise Exception("This doesn't work on Windows.")
378 pkg = os.path.join(os.getcwd(), 'IPython')
376 pkg = os.path.join(os.getcwd(), 'IPython')
379 dest = os.path.join(self.install_dir, 'IPython')
377 dest = os.path.join(self.install_dir, 'IPython')
380 print('symlinking %s -> %s' % (pkg, dest))
378 print('symlinking %s -> %s' % (pkg, dest))
381 try:
379 try:
382 os.symlink(pkg, dest)
380 os.symlink(pkg, dest)
383 except OSError as e:
381 except OSError as e:
384 if e.errno == errno.EEXIST:
382 if e.errno == errno.EEXIST:
385 print('ALREADY EXISTS')
383 print('ALREADY EXISTS')
386 else:
384 else:
387 raise
385 raise
388
386
389 class install_symlinked(install):
387 class install_symlinked(install):
390 def run(self):
388 def run(self):
391 if sys.platform == 'win32':
389 if sys.platform == 'win32':
392 raise Exception("This doesn't work on Windows.")
390 raise Exception("This doesn't work on Windows.")
393 install.run(self)
391 install.run(self)
394
392
395 # 'sub_commands': a list of commands this command might have to run to
393 # 'sub_commands': a list of commands this command might have to run to
396 # get its work done. See cmd.py for more info.
394 # get its work done. See cmd.py for more info.
397 sub_commands = [('install_lib_symlink', lambda self:True),
395 sub_commands = [('install_lib_symlink', lambda self:True),
398 ('install_scripts_sym', lambda self:True),
396 ('install_scripts_sym', lambda self:True),
399 ]
397 ]
400
398
401 class install_scripts_for_symlink(install_scripts):
399 class install_scripts_for_symlink(install_scripts):
402 """Redefined to get options from 'symlink' instead of 'install'.
400 """Redefined to get options from 'symlink' instead of 'install'.
403
401
404 I love distutils almost as much as I love setuptools.
402 I love distutils almost as much as I love setuptools.
405 """
403 """
406 def finalize_options(self):
404 def finalize_options(self):
407 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
405 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
408 self.set_undefined_options('symlink',
406 self.set_undefined_options('symlink',
409 ('install_scripts', 'install_dir'),
407 ('install_scripts', 'install_dir'),
410 ('force', 'force'),
408 ('force', 'force'),
411 ('skip_build', 'skip_build'),
409 ('skip_build', 'skip_build'),
412 )
410 )
413
411
414 #---------------------------------------------------------------------------
412 #---------------------------------------------------------------------------
415 # Verify all dependencies
413 # Verify all dependencies
416 #---------------------------------------------------------------------------
414 #---------------------------------------------------------------------------
417
415
418 def check_for_dependencies():
416 def check_for_dependencies():
419 """Check for IPython's dependencies.
417 """Check for IPython's dependencies.
420
418
421 This function should NOT be called if running under setuptools!
419 This function should NOT be called if running under setuptools!
422 """
420 """
423 from setupext.setupext import (
421 from setupext.setupext import (
424 print_line, print_raw, print_status,
422 print_line, print_raw, print_status,
425 check_for_sphinx, check_for_pygments,
423 check_for_sphinx, check_for_pygments,
426 check_for_nose, check_for_pexpect,
424 check_for_nose, check_for_pexpect,
427 check_for_pyzmq, check_for_readline,
425 check_for_pyzmq, check_for_readline,
428 check_for_jinja2, check_for_tornado
426 check_for_jinja2, check_for_tornado
429 )
427 )
430 print_line()
428 print_line()
431 print_raw("BUILDING IPYTHON")
429 print_raw("BUILDING IPYTHON")
432 print_status('python', sys.version)
430 print_status('python', sys.version)
433 print_status('platform', sys.platform)
431 print_status('platform', sys.platform)
434 if sys.platform == 'win32':
432 if sys.platform == 'win32':
435 print_status('Windows version', sys.getwindowsversion())
433 print_status('Windows version', sys.getwindowsversion())
436
434
437 print_raw("")
435 print_raw("")
438 print_raw("OPTIONAL DEPENDENCIES")
436 print_raw("OPTIONAL DEPENDENCIES")
439
437
440 check_for_sphinx()
438 check_for_sphinx()
441 check_for_pygments()
439 check_for_pygments()
442 check_for_nose()
440 check_for_nose()
443 check_for_pexpect()
441 check_for_pexpect()
444 check_for_pyzmq()
442 check_for_pyzmq()
445 check_for_tornado()
443 check_for_tornado()
446 check_for_readline()
444 check_for_readline()
447 check_for_jinja2()
445 check_for_jinja2()
448
446
449 #---------------------------------------------------------------------------
447 #---------------------------------------------------------------------------
450 # VCS related
448 # VCS related
451 #---------------------------------------------------------------------------
449 #---------------------------------------------------------------------------
452
450
453 # utils.submodule has checks for submodule status
451 # utils.submodule has checks for submodule status
454 execfile(pjoin('IPython','utils','submodule.py'), globals())
452 execfile(pjoin('IPython','utils','submodule.py'), globals())
455
453
456 class UpdateSubmodules(Command):
454 class UpdateSubmodules(Command):
457 """Update git submodules
455 """Update git submodules
458
456
459 IPython's external javascript dependencies live in a separate repo.
457 IPython's external javascript dependencies live in a separate repo.
460 """
458 """
461 description = "Update git submodules"
459 description = "Update git submodules"
462 user_options = []
460 user_options = []
463
461
464 def initialize_options(self):
462 def initialize_options(self):
465 pass
463 pass
466
464
467 def finalize_options(self):
465 def finalize_options(self):
468 pass
466 pass
469
467
470 def run(self):
468 def run(self):
471 failure = False
469 failure = False
472 try:
470 try:
473 self.spawn('git submodule init'.split())
471 self.spawn('git submodule init'.split())
474 self.spawn('git submodule update --recursive'.split())
472 self.spawn('git submodule update --recursive'.split())
475 except Exception as e:
473 except Exception as e:
476 failure = e
474 failure = e
477 print(e)
475 print(e)
478
476
479 if not check_submodule_status(repo_root) == 'clean':
477 if not check_submodule_status(repo_root) == 'clean':
480 print("submodules could not be checked out")
478 print("submodules could not be checked out")
481 sys.exit(1)
479 sys.exit(1)
482
480
483
481
484 def git_prebuild(pkg_dir, build_cmd=build_py):
482 def git_prebuild(pkg_dir, build_cmd=build_py):
485 """Return extended build or sdist command class for recording commit
483 """Return extended build or sdist command class for recording commit
486
484
487 records git commit in IPython.utils._sysinfo.commit
485 records git commit in IPython.utils._sysinfo.commit
488
486
489 for use in IPython.utils.sysinfo.sys_info() calls after installation.
487 for use in IPython.utils.sysinfo.sys_info() calls after installation.
490
488
491 Also ensures that submodules exist prior to running
489 Also ensures that submodules exist prior to running
492 """
490 """
493
491
494 class MyBuildPy(build_cmd):
492 class MyBuildPy(build_cmd):
495 ''' Subclass to write commit data into installation tree '''
493 ''' Subclass to write commit data into installation tree '''
496 def run(self):
494 def run(self):
497 build_cmd.run(self)
495 build_cmd.run(self)
498 # this one will only fire for build commands
496 # this one will only fire for build commands
499 if hasattr(self, 'build_lib'):
497 if hasattr(self, 'build_lib'):
500 self._record_commit(self.build_lib)
498 self._record_commit(self.build_lib)
501
499
502 def make_release_tree(self, base_dir, files):
500 def make_release_tree(self, base_dir, files):
503 # this one will fire for sdist
501 # this one will fire for sdist
504 build_cmd.make_release_tree(self, base_dir, files)
502 build_cmd.make_release_tree(self, base_dir, files)
505 self._record_commit(base_dir)
503 self._record_commit(base_dir)
506
504
507 def _record_commit(self, base_dir):
505 def _record_commit(self, base_dir):
508 import subprocess
506 import subprocess
509 proc = subprocess.Popen('git rev-parse --short HEAD',
507 proc = subprocess.Popen('git rev-parse --short HEAD',
510 stdout=subprocess.PIPE,
508 stdout=subprocess.PIPE,
511 stderr=subprocess.PIPE,
509 stderr=subprocess.PIPE,
512 shell=True)
510 shell=True)
513 repo_commit, _ = proc.communicate()
511 repo_commit, _ = proc.communicate()
514 repo_commit = repo_commit.strip().decode("ascii")
512 repo_commit = repo_commit.strip().decode("ascii")
515
513
516 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
514 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
517 if os.path.isfile(out_pth) and not repo_commit:
515 if os.path.isfile(out_pth) and not repo_commit:
518 # nothing to write, don't clobber
516 # nothing to write, don't clobber
519 return
517 return
520
518
521 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
519 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
522
520
523 # remove to avoid overwriting original via hard link
521 # remove to avoid overwriting original via hard link
524 try:
522 try:
525 os.remove(out_pth)
523 os.remove(out_pth)
526 except (IOError, OSError):
524 except (IOError, OSError):
527 pass
525 pass
528 with open(out_pth, 'w') as out_file:
526 with open(out_pth, 'w') as out_file:
529 out_file.writelines([
527 out_file.writelines([
530 '# GENERATED BY setup.py\n',
528 '# GENERATED BY setup.py\n',
531 'commit = "%s"\n' % repo_commit,
529 'commit = "%s"\n' % repo_commit,
532 ])
530 ])
533 return require_submodules(MyBuildPy)
531 return require_submodules(MyBuildPy)
534
532
535
533
536 def require_submodules(command):
534 def require_submodules(command):
537 """decorator for instructing a command to check for submodules before running"""
535 """decorator for instructing a command to check for submodules before running"""
538 class DecoratedCommand(command):
536 class DecoratedCommand(command):
539 def run(self):
537 def run(self):
540 if not check_submodule_status(repo_root) == 'clean':
538 if not check_submodule_status(repo_root) == 'clean':
541 print("submodules missing! Run `setup.py submodule` and try again")
539 print("submodules missing! Run `setup.py submodule` and try again")
542 sys.exit(1)
540 sys.exit(1)
543 command.run(self)
541 command.run(self)
544 return DecoratedCommand
542 return DecoratedCommand
545
543
546 #---------------------------------------------------------------------------
544 #---------------------------------------------------------------------------
547 # Notebook related
545 # Notebook related
548 #---------------------------------------------------------------------------
546 #---------------------------------------------------------------------------
549
547
550 class CompileCSS(Command):
548 class CompileCSS(Command):
551 """Recompile Notebook CSS
549 """Recompile Notebook CSS
552
550
553 Regenerate the compiled CSS from LESS sources.
551 Regenerate the compiled CSS from LESS sources.
554
552
555 Requires various dev dependencies, such as fabric and lessc.
553 Requires various dev dependencies, such as fabric and lessc.
556 """
554 """
557 description = "Recompile Notebook CSS"
555 description = "Recompile Notebook CSS"
558 user_options = []
556 user_options = []
559
557
560 def initialize_options(self):
558 def initialize_options(self):
561 pass
559 pass
562
560
563 def finalize_options(self):
561 def finalize_options(self):
564 pass
562 pass
565
563
566 def run(self):
564 def run(self):
567 call("fab css", shell=True, cwd=pjoin(repo_root, "IPython", "html"))
565 call("fab css", shell=True, cwd=pjoin(repo_root, "IPython", "html"))
568
566
569 class JavascriptVersion(Command):
567 class JavascriptVersion(Command):
570 """write the javascript version to notebook javascript"""
568 """write the javascript version to notebook javascript"""
571 description = "Write IPython version to javascript"
569 description = "Write IPython version to javascript"
572 user_options = []
570 user_options = []
573
571
574 def initialize_options(self):
572 def initialize_options(self):
575 pass
573 pass
576
574
577 def finalize_options(self):
575 def finalize_options(self):
578 pass
576 pass
579
577
580 def run(self):
578 def run(self):
581 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
579 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
582 with open(nsfile) as f:
580 with open(nsfile) as f:
583 lines = f.readlines()
581 lines = f.readlines()
584 with open(nsfile, 'w') as f:
582 with open(nsfile, 'w') as f:
585 for line in lines:
583 for line in lines:
586 if line.startswith("IPython.version"):
584 if line.startswith("IPython.version"):
587 line = 'IPython.version = "{0}";\n'.format(version)
585 line = 'IPython.version = "{0}";\n'.format(version)
588 f.write(line)
586 f.write(line)
589
587
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now