##// END OF EJS Templates
Remove accidentally introduced runtime nose dependencies.
Fernando Perez -
Show More
@@ -1,313 +1,310 b''
1 """Decorators for labeling test objects.
1 """Decorators for labeling test objects.
2
2
3 Decorators that merely return a modified version of the original function
3 Decorators that merely return a modified version of the original function
4 object are straightforward. Decorators that return a new function object need
4 object are straightforward. Decorators that return a new function object need
5 to use nose.tools.make_decorator(original_function)(decorator) in returning the
5 to use nose.tools.make_decorator(original_function)(decorator) in returning the
6 decorator, in order to preserve metadata such as function name, setup and
6 decorator, in order to preserve metadata such as function name, setup and
7 teardown functions and so on - see nose.tools for more information.
7 teardown functions and so on - see nose.tools for more information.
8
8
9 This module provides a set of useful decorators meant to be ready to use in
9 This module provides a set of useful decorators meant to be ready to use in
10 your own tests. See the bottom of the file for the ready-made ones, and if you
10 your own tests. See the bottom of the file for the ready-made ones, and if you
11 find yourself writing a new one that may be of generic use, add it here.
11 find yourself writing a new one that may be of generic use, add it here.
12
12
13 Included decorators:
13 Included decorators:
14
14
15
15
16 Lightweight testing that remains unittest-compatible.
16 Lightweight testing that remains unittest-compatible.
17
17
18 - @parametric, for parametric test support that is vastly easier to use than
18 - @parametric, for parametric test support that is vastly easier to use than
19 nose's for debugging. With ours, if a test fails, the stack under inspection
19 nose's for debugging. With ours, if a test fails, the stack under inspection
20 is that of the test and not that of the test framework.
20 is that of the test and not that of the test framework.
21
21
22 - An @as_unittest decorator can be used to tag any normal parameter-less
22 - An @as_unittest decorator can be used to tag any normal parameter-less
23 function as a unittest TestCase. Then, both nose and normal unittest will
23 function as a unittest TestCase. Then, both nose and normal unittest will
24 recognize it as such. This will make it easier to migrate away from Nose if
24 recognize it as such. This will make it easier to migrate away from Nose if
25 we ever need/want to while maintaining very lightweight tests.
25 we ever need/want to while maintaining very lightweight tests.
26
26
27 NOTE: This file contains IPython-specific decorators and imports the
27 NOTE: This file contains IPython-specific decorators and imports the
28 numpy.testing.decorators file, which we've copied verbatim. Any of our own
28 numpy.testing.decorators file, which we've copied verbatim. Any of our own
29 code will be added at the bottom if we end up extending this.
29 code will be added at the bottom if we end up extending this.
30
30
31 Authors
31 Authors
32 -------
32 -------
33
33
34 - Fernando Perez <Fernando.Perez@berkeley.edu>
34 - Fernando Perez <Fernando.Perez@berkeley.edu>
35 """
35 """
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Copyright (C) 2009-2010 The IPython Development Team
38 # Copyright (C) 2009-2010 The IPython Development Team
39 #
39 #
40 # Distributed under the terms of the BSD License. The full license is in
40 # Distributed under the terms of the BSD License. The full license is in
41 # the file COPYING, distributed as part of this software.
41 # the file COPYING, distributed as part of this software.
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Imports
45 # Imports
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48 # Stdlib imports
48 # Stdlib imports
49 import inspect
49 import inspect
50 import sys
50 import sys
51 import unittest
51 import unittest
52
52
53 # Third-party imports
53 # Third-party imports
54
54
55 # This is Michele Simionato's decorator module, kept verbatim.
55 # This is Michele Simionato's decorator module, kept verbatim.
56 from IPython.external.decorator import decorator, update_wrapper
56 from IPython.external.decorator import decorator, update_wrapper
57
57
58 # Our own modules
59 import nosepatch # monkeypatch nose
60
61 # We already have python3-compliant code for parametric tests
58 # We already have python3-compliant code for parametric tests
62 if sys.version[0]=='2':
59 if sys.version[0]=='2':
63 from _paramtestpy2 import parametric, ParametricTestCase
60 from _paramtestpy2 import parametric, ParametricTestCase
64 else:
61 else:
65 from _paramtestpy3 import parametric, ParametricTestCase
62 from _paramtestpy3 import parametric, ParametricTestCase
66
63
67 # Expose the unittest-driven decorators
64 # Expose the unittest-driven decorators
68 from ipunittest import ipdoctest, ipdocstring
65 from ipunittest import ipdoctest, ipdocstring
69
66
70 # Grab the numpy-specific decorators which we keep in a file that we
67 # Grab the numpy-specific decorators which we keep in a file that we
71 # occasionally update from upstream: decorators.py is a copy of
68 # occasionally update from upstream: decorators.py is a copy of
72 # numpy.testing.decorators, we expose all of it here.
69 # numpy.testing.decorators, we expose all of it here.
73 from IPython.external.decorators import *
70 from IPython.external.decorators import *
74
71
75 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
76 # Classes and functions
73 # Classes and functions
77 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
78
75
79 # Simple example of the basic idea
76 # Simple example of the basic idea
80 def as_unittest(func):
77 def as_unittest(func):
81 """Decorator to make a simple function into a normal test via unittest."""
78 """Decorator to make a simple function into a normal test via unittest."""
82 class Tester(unittest.TestCase):
79 class Tester(unittest.TestCase):
83 def test(self):
80 def test(self):
84 func()
81 func()
85
82
86 Tester.__name__ = func.__name__
83 Tester.__name__ = func.__name__
87
84
88 return Tester
85 return Tester
89
86
90 # Utility functions
87 # Utility functions
91
88
92 def apply_wrapper(wrapper,func):
89 def apply_wrapper(wrapper,func):
93 """Apply a wrapper to a function for decoration.
90 """Apply a wrapper to a function for decoration.
94
91
95 This mixes Michele Simionato's decorator tool with nose's make_decorator,
92 This mixes Michele Simionato's decorator tool with nose's make_decorator,
96 to apply a wrapper in a decorator so that all nose attributes, as well as
93 to apply a wrapper in a decorator so that all nose attributes, as well as
97 function signature and other properties, survive the decoration cleanly.
94 function signature and other properties, survive the decoration cleanly.
98 This will ensure that wrapped functions can still be well introspected via
95 This will ensure that wrapped functions can still be well introspected via
99 IPython, for example.
96 IPython, for example.
100 """
97 """
101 import nose.tools
98 import nose.tools
102
99
103 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
100 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
104
101
105
102
106 def make_label_dec(label,ds=None):
103 def make_label_dec(label,ds=None):
107 """Factory function to create a decorator that applies one or more labels.
104 """Factory function to create a decorator that applies one or more labels.
108
105
109 Parameters
106 Parameters
110 ----------
107 ----------
111 label : string or sequence
108 label : string or sequence
112 One or more labels that will be applied by the decorator to the functions
109 One or more labels that will be applied by the decorator to the functions
113 it decorates. Labels are attributes of the decorated function with their
110 it decorates. Labels are attributes of the decorated function with their
114 value set to True.
111 value set to True.
115
112
116 ds : string
113 ds : string
117 An optional docstring for the resulting decorator. If not given, a
114 An optional docstring for the resulting decorator. If not given, a
118 default docstring is auto-generated.
115 default docstring is auto-generated.
119
116
120 Returns
117 Returns
121 -------
118 -------
122 A decorator.
119 A decorator.
123
120
124 Examples
121 Examples
125 --------
122 --------
126
123
127 A simple labeling decorator:
124 A simple labeling decorator:
128 >>> slow = make_label_dec('slow')
125 >>> slow = make_label_dec('slow')
129 >>> print slow.__doc__
126 >>> print slow.__doc__
130 Labels a test as 'slow'.
127 Labels a test as 'slow'.
131
128
132 And one that uses multiple labels and a custom docstring:
129 And one that uses multiple labels and a custom docstring:
133 >>> rare = make_label_dec(['slow','hard'],
130 >>> rare = make_label_dec(['slow','hard'],
134 ... "Mix labels 'slow' and 'hard' for rare tests.")
131 ... "Mix labels 'slow' and 'hard' for rare tests.")
135 >>> print rare.__doc__
132 >>> print rare.__doc__
136 Mix labels 'slow' and 'hard' for rare tests.
133 Mix labels 'slow' and 'hard' for rare tests.
137
134
138 Now, let's test using this one:
135 Now, let's test using this one:
139 >>> @rare
136 >>> @rare
140 ... def f(): pass
137 ... def f(): pass
141 ...
138 ...
142 >>>
139 >>>
143 >>> f.slow
140 >>> f.slow
144 True
141 True
145 >>> f.hard
142 >>> f.hard
146 True
143 True
147 """
144 """
148
145
149 if isinstance(label,basestring):
146 if isinstance(label,basestring):
150 labels = [label]
147 labels = [label]
151 else:
148 else:
152 labels = label
149 labels = label
153
150
154 # Validate that the given label(s) are OK for use in setattr() by doing a
151 # Validate that the given label(s) are OK for use in setattr() by doing a
155 # dry run on a dummy function.
152 # dry run on a dummy function.
156 tmp = lambda : None
153 tmp = lambda : None
157 for label in labels:
154 for label in labels:
158 setattr(tmp,label,True)
155 setattr(tmp,label,True)
159
156
160 # This is the actual decorator we'll return
157 # This is the actual decorator we'll return
161 def decor(f):
158 def decor(f):
162 for label in labels:
159 for label in labels:
163 setattr(f,label,True)
160 setattr(f,label,True)
164 return f
161 return f
165
162
166 # Apply the user's docstring, or autogenerate a basic one
163 # Apply the user's docstring, or autogenerate a basic one
167 if ds is None:
164 if ds is None:
168 ds = "Labels a test as %r." % label
165 ds = "Labels a test as %r." % label
169 decor.__doc__ = ds
166 decor.__doc__ = ds
170
167
171 return decor
168 return decor
172
169
173
170
174 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
171 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
175 # preserve function metadata better and allows the skip condition to be a
172 # preserve function metadata better and allows the skip condition to be a
176 # callable.
173 # callable.
177 def skipif(skip_condition, msg=None):
174 def skipif(skip_condition, msg=None):
178 ''' Make function raise SkipTest exception if skip_condition is true
175 ''' Make function raise SkipTest exception if skip_condition is true
179
176
180 Parameters
177 Parameters
181 ----------
178 ----------
182 skip_condition : bool or callable.
179 skip_condition : bool or callable.
183 Flag to determine whether to skip test. If the condition is a
180 Flag to determine whether to skip test. If the condition is a
184 callable, it is used at runtime to dynamically make the decision. This
181 callable, it is used at runtime to dynamically make the decision. This
185 is useful for tests that may require costly imports, to delay the cost
182 is useful for tests that may require costly imports, to delay the cost
186 until the test suite is actually executed.
183 until the test suite is actually executed.
187 msg : string
184 msg : string
188 Message to give on raising a SkipTest exception
185 Message to give on raising a SkipTest exception
189
186
190 Returns
187 Returns
191 -------
188 -------
192 decorator : function
189 decorator : function
193 Decorator, which, when applied to a function, causes SkipTest
190 Decorator, which, when applied to a function, causes SkipTest
194 to be raised when the skip_condition was True, and the function
191 to be raised when the skip_condition was True, and the function
195 to be called normally otherwise.
192 to be called normally otherwise.
196
193
197 Notes
194 Notes
198 -----
195 -----
199 You will see from the code that we had to further decorate the
196 You will see from the code that we had to further decorate the
200 decorator with the nose.tools.make_decorator function in order to
197 decorator with the nose.tools.make_decorator function in order to
201 transmit function name, and various other metadata.
198 transmit function name, and various other metadata.
202 '''
199 '''
203
200
204 def skip_decorator(f):
201 def skip_decorator(f):
205 # Local import to avoid a hard nose dependency and only incur the
202 # Local import to avoid a hard nose dependency and only incur the
206 # import time overhead at actual test-time.
203 # import time overhead at actual test-time.
207 import nose
204 import nose
208
205
209 # Allow for both boolean or callable skip conditions.
206 # Allow for both boolean or callable skip conditions.
210 if callable(skip_condition):
207 if callable(skip_condition):
211 skip_val = lambda : skip_condition()
208 skip_val = lambda : skip_condition()
212 else:
209 else:
213 skip_val = lambda : skip_condition
210 skip_val = lambda : skip_condition
214
211
215 def get_msg(func,msg=None):
212 def get_msg(func,msg=None):
216 """Skip message with information about function being skipped."""
213 """Skip message with information about function being skipped."""
217 if msg is None: out = 'Test skipped due to test condition.'
214 if msg is None: out = 'Test skipped due to test condition.'
218 else: out = msg
215 else: out = msg
219 return "Skipping test: %s. %s" % (func.__name__,out)
216 return "Skipping test: %s. %s" % (func.__name__,out)
220
217
221 # We need to define *two* skippers because Python doesn't allow both
218 # We need to define *two* skippers because Python doesn't allow both
222 # return with value and yield inside the same function.
219 # return with value and yield inside the same function.
223 def skipper_func(*args, **kwargs):
220 def skipper_func(*args, **kwargs):
224 """Skipper for normal test functions."""
221 """Skipper for normal test functions."""
225 if skip_val():
222 if skip_val():
226 raise nose.SkipTest(get_msg(f,msg))
223 raise nose.SkipTest(get_msg(f,msg))
227 else:
224 else:
228 return f(*args, **kwargs)
225 return f(*args, **kwargs)
229
226
230 def skipper_gen(*args, **kwargs):
227 def skipper_gen(*args, **kwargs):
231 """Skipper for test generators."""
228 """Skipper for test generators."""
232 if skip_val():
229 if skip_val():
233 raise nose.SkipTest(get_msg(f,msg))
230 raise nose.SkipTest(get_msg(f,msg))
234 else:
231 else:
235 for x in f(*args, **kwargs):
232 for x in f(*args, **kwargs):
236 yield x
233 yield x
237
234
238 # Choose the right skipper to use when building the actual generator.
235 # Choose the right skipper to use when building the actual generator.
239 if nose.util.isgenerator(f):
236 if nose.util.isgenerator(f):
240 skipper = skipper_gen
237 skipper = skipper_gen
241 else:
238 else:
242 skipper = skipper_func
239 skipper = skipper_func
243
240
244 return nose.tools.make_decorator(f)(skipper)
241 return nose.tools.make_decorator(f)(skipper)
245
242
246 return skip_decorator
243 return skip_decorator
247
244
248 # A version with the condition set to true, common case just to attacha message
245 # A version with the condition set to true, common case just to attacha message
249 # to a skip decorator
246 # to a skip decorator
250 def skip(msg=None):
247 def skip(msg=None):
251 """Decorator factory - mark a test function for skipping from test suite.
248 """Decorator factory - mark a test function for skipping from test suite.
252
249
253 Parameters
250 Parameters
254 ----------
251 ----------
255 msg : string
252 msg : string
256 Optional message to be added.
253 Optional message to be added.
257
254
258 Returns
255 Returns
259 -------
256 -------
260 decorator : function
257 decorator : function
261 Decorator, which, when applied to a function, causes SkipTest
258 Decorator, which, when applied to a function, causes SkipTest
262 to be raised, with the optional message added.
259 to be raised, with the optional message added.
263 """
260 """
264
261
265 return skipif(True,msg)
262 return skipif(True,msg)
266
263
267
264
268 #-----------------------------------------------------------------------------
265 #-----------------------------------------------------------------------------
269 # Utility functions for decorators
266 # Utility functions for decorators
270 def numpy_not_available():
267 def numpy_not_available():
271 """Can numpy be imported? Returns true if numpy does NOT import.
268 """Can numpy be imported? Returns true if numpy does NOT import.
272
269
273 This is used to make a decorator to skip tests that require numpy to be
270 This is used to make a decorator to skip tests that require numpy to be
274 available, but delay the 'import numpy' to test execution time.
271 available, but delay the 'import numpy' to test execution time.
275 """
272 """
276 try:
273 try:
277 import numpy
274 import numpy
278 np_not_avail = False
275 np_not_avail = False
279 except ImportError:
276 except ImportError:
280 np_not_avail = True
277 np_not_avail = True
281
278
282 return np_not_avail
279 return np_not_avail
283
280
284 #-----------------------------------------------------------------------------
281 #-----------------------------------------------------------------------------
285 # Decorators for public use
282 # Decorators for public use
286
283
287 skip_doctest = make_label_dec('skip_doctest',
284 skip_doctest = make_label_dec('skip_doctest',
288 """Decorator - mark a function or method for skipping its doctest.
285 """Decorator - mark a function or method for skipping its doctest.
289
286
290 This decorator allows you to mark a function whose docstring you wish to
287 This decorator allows you to mark a function whose docstring you wish to
291 omit from testing, while preserving the docstring for introspection, help,
288 omit from testing, while preserving the docstring for introspection, help,
292 etc.""")
289 etc.""")
293
290
294 # Decorators to skip certain tests on specific platforms.
291 # Decorators to skip certain tests on specific platforms.
295 skip_win32 = skipif(sys.platform == 'win32',
292 skip_win32 = skipif(sys.platform == 'win32',
296 "This test does not run under Windows")
293 "This test does not run under Windows")
297 skip_linux = skipif(sys.platform == 'linux2',
294 skip_linux = skipif(sys.platform == 'linux2',
298 "This test does not run under Linux")
295 "This test does not run under Linux")
299 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
296 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
300
297
301
298
302 # Decorators to skip tests if not on specific platforms.
299 # Decorators to skip tests if not on specific platforms.
303 skip_if_not_win32 = skipif(sys.platform != 'win32',
300 skip_if_not_win32 = skipif(sys.platform != 'win32',
304 "This test only runs under Windows")
301 "This test only runs under Windows")
305 skip_if_not_linux = skipif(sys.platform != 'linux2',
302 skip_if_not_linux = skipif(sys.platform != 'linux2',
306 "This test only runs under Linux")
303 "This test only runs under Linux")
307 skip_if_not_osx = skipif(sys.platform != 'darwin',
304 skip_if_not_osx = skipif(sys.platform != 'darwin',
308 "This test only runs under OSX")
305 "This test only runs under OSX")
309
306
310 # Other skip decorators
307 # Other skip decorators
311 skipif_not_numpy = skipif(numpy_not_available,"This test requires numpy")
308 skipif_not_numpy = skipif(numpy_not_available,"This test requires numpy")
312
309
313 skipknownfailure = skip('This test is known to fail')
310 skipknownfailure = skip('This test is known to fail')
@@ -1,393 +1,401 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) or trial recursively. This
8 calling this script (with different arguments) or trial 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 For now, this script requires that both nose and twisted are installed. This
15 For now, this script requires that both nose and twisted are installed. This
16 will change in the future.
16 will change in the future.
17 """
17 """
18
18
19 from __future__ import absolute_import
19 from __future__ import absolute_import
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Module imports
22 # Module imports
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 # Stdlib
25 import os
26 import os
26 import os.path as path
27 import os.path as path
27 import signal
28 import signal
28 import sys
29 import sys
29 import subprocess
30 import subprocess
30 import tempfile
31 import tempfile
31 import time
32 import time
32 import warnings
33 import warnings
33
34
35 # Note: monkeypatch!
36 # We need to monkeypatch a small problem in nose itself first, before importing
37 # it for actual use. This should get into nose upstream, but its release cycle
38 # is slow and we need it for our parametric tests to work correctly.
39 from . import nosepatch
40 # Now, proceed to import nose itself
34 import nose.plugins.builtin
41 import nose.plugins.builtin
35 from nose.core import TestProgram
42 from nose.core import TestProgram
36
43
44 # Our own imports
37 from IPython.utils import genutils
45 from IPython.utils import genutils
38 from IPython.utils.platutils import find_cmd, FindCmdError
46 from IPython.utils.platutils import find_cmd, FindCmdError
39 from . import globalipapp
47 from . import globalipapp
40 from .plugin.ipdoctest import IPythonDoctest
48 from .plugin.ipdoctest import IPythonDoctest
41
49
42 pjoin = path.join
50 pjoin = path.join
43
51
44 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
45 # Warnings control
53 # Warnings control
46 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
47 # Twisted generates annoying warnings with Python 2.6, as will do other code
55 # Twisted generates annoying warnings with Python 2.6, as will do other code
48 # that imports 'sets' as of today
56 # that imports 'sets' as of today
49 warnings.filterwarnings('ignore', 'the sets module is deprecated',
57 warnings.filterwarnings('ignore', 'the sets module is deprecated',
50 DeprecationWarning )
58 DeprecationWarning )
51
59
52 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
53 # Logic for skipping doctests
61 # Logic for skipping doctests
54 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
55
63
56 def test_for(mod):
64 def test_for(mod):
57 """Test to see if mod is importable."""
65 """Test to see if mod is importable."""
58 try:
66 try:
59 __import__(mod)
67 __import__(mod)
60 except ImportError:
68 except ImportError:
61 return False
69 return False
62 else:
70 else:
63 return True
71 return True
64
72
65 have_curses = test_for('_curses')
73 have_curses = test_for('_curses')
66 have_wx = test_for('wx')
74 have_wx = test_for('wx')
67 have_wx_aui = test_for('wx.aui')
75 have_wx_aui = test_for('wx.aui')
68 have_zi = test_for('zope.interface')
76 have_zi = test_for('zope.interface')
69 have_twisted = test_for('twisted')
77 have_twisted = test_for('twisted')
70 have_foolscap = test_for('foolscap')
78 have_foolscap = test_for('foolscap')
71 have_objc = test_for('objc')
79 have_objc = test_for('objc')
72 have_pexpect = test_for('pexpect')
80 have_pexpect = test_for('pexpect')
73 have_gtk = test_for('gtk')
81 have_gtk = test_for('gtk')
74 have_gobject = test_for('gobject')
82 have_gobject = test_for('gobject')
75
83
76
84
77 def make_exclude():
85 def make_exclude():
78
86
79 # For the IPythonDoctest plugin, we need to exclude certain patterns that
87 # For the IPythonDoctest plugin, we need to exclude certain patterns that
80 # cause testing problems. We should strive to minimize the number of
88 # cause testing problems. We should strive to minimize the number of
81 # skipped modules, since this means untested code. As the testing
89 # skipped modules, since this means untested code. As the testing
82 # machinery solidifies, this list should eventually become empty.
90 # machinery solidifies, this list should eventually become empty.
83 # These modules and packages will NOT get scanned by nose at all for tests
91 # These modules and packages will NOT get scanned by nose at all for tests
84 exclusions = [pjoin('IPython', 'external'),
92 exclusions = [pjoin('IPython', 'external'),
85 pjoin('IPython', 'frontend', 'process', 'winprocess.py'),
93 pjoin('IPython', 'frontend', 'process', 'winprocess.py'),
86 pjoin('IPython_doctest_plugin'),
94 pjoin('IPython_doctest_plugin'),
87 pjoin('IPython', 'quarantine'),
95 pjoin('IPython', 'quarantine'),
88 pjoin('IPython', 'deathrow'),
96 pjoin('IPython', 'deathrow'),
89 pjoin('IPython', 'testing', 'attic'),
97 pjoin('IPython', 'testing', 'attic'),
90 pjoin('IPython', 'testing', 'tools'),
98 pjoin('IPython', 'testing', 'tools'),
91 pjoin('IPython', 'testing', 'mkdoctests'),
99 pjoin('IPython', 'testing', 'mkdoctests'),
92 pjoin('IPython', 'lib', 'inputhook'),
100 pjoin('IPython', 'lib', 'inputhook'),
93 # Config files aren't really importable stand-alone
101 # Config files aren't really importable stand-alone
94 pjoin('IPython', 'config', 'default'),
102 pjoin('IPython', 'config', 'default'),
95 pjoin('IPython', 'config', 'profile'),
103 pjoin('IPython', 'config', 'profile'),
96 ]
104 ]
97
105
98 if not have_wx:
106 if not have_wx:
99 exclusions.append(pjoin('IPython', 'gui'))
107 exclusions.append(pjoin('IPython', 'gui'))
100 exclusions.append(pjoin('IPython', 'frontend', 'wx'))
108 exclusions.append(pjoin('IPython', 'frontend', 'wx'))
101 exclusions.append(pjoin('IPython', 'lib', 'inputhookwx'))
109 exclusions.append(pjoin('IPython', 'lib', 'inputhookwx'))
102
110
103 if not have_gtk or not have_gobject:
111 if not have_gtk or not have_gobject:
104 exclusions.append(pjoin('IPython', 'lib', 'inputhookgtk'))
112 exclusions.append(pjoin('IPython', 'lib', 'inputhookgtk'))
105
113
106 if not have_wx_aui:
114 if not have_wx_aui:
107 exclusions.append(pjoin('IPython', 'gui', 'wx', 'wxIPython'))
115 exclusions.append(pjoin('IPython', 'gui', 'wx', 'wxIPython'))
108
116
109 if not have_objc:
117 if not have_objc:
110 exclusions.append(pjoin('IPython', 'frontend', 'cocoa'))
118 exclusions.append(pjoin('IPython', 'frontend', 'cocoa'))
111
119
112 if not sys.platform == 'win32':
120 if not sys.platform == 'win32':
113 exclusions.append(pjoin('IPython', 'utils', 'platutils_win32'))
121 exclusions.append(pjoin('IPython', 'utils', 'platutils_win32'))
114
122
115 # These have to be skipped on win32 because the use echo, rm, cd, etc.
123 # These have to be skipped on win32 because the use echo, rm, cd, etc.
116 # See ticket https://bugs.launchpad.net/bugs/366982
124 # See ticket https://bugs.launchpad.net/bugs/366982
117 if sys.platform == 'win32':
125 if sys.platform == 'win32':
118 exclusions.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip'))
126 exclusions.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip'))
119 exclusions.append(pjoin('IPython', 'testing', 'plugin', 'dtexample'))
127 exclusions.append(pjoin('IPython', 'testing', 'plugin', 'dtexample'))
120
128
121 if not os.name == 'posix':
129 if not os.name == 'posix':
122 exclusions.append(pjoin('IPython', 'utils', 'platutils_posix'))
130 exclusions.append(pjoin('IPython', 'utils', 'platutils_posix'))
123
131
124 if not have_pexpect:
132 if not have_pexpect:
125 exclusions.append(pjoin('IPython', 'scripts', 'irunner'))
133 exclusions.append(pjoin('IPython', 'scripts', 'irunner'))
126
134
127 # This is scary. We still have things in frontend and testing that
135 # This is scary. We still have things in frontend and testing that
128 # are being tested by nose that use twisted. We need to rethink
136 # are being tested by nose that use twisted. We need to rethink
129 # how we are isolating dependencies in testing.
137 # how we are isolating dependencies in testing.
130 if not (have_twisted and have_zi and have_foolscap):
138 if not (have_twisted and have_zi and have_foolscap):
131 exclusions.append(pjoin('IPython', 'frontend', 'asyncfrontendbase'))
139 exclusions.append(pjoin('IPython', 'frontend', 'asyncfrontendbase'))
132 exclusions.append(pjoin('IPython', 'frontend', 'prefilterfrontend'))
140 exclusions.append(pjoin('IPython', 'frontend', 'prefilterfrontend'))
133 exclusions.append(pjoin('IPython', 'frontend', 'frontendbase'))
141 exclusions.append(pjoin('IPython', 'frontend', 'frontendbase'))
134 exclusions.append(pjoin('IPython', 'frontend', 'linefrontendbase'))
142 exclusions.append(pjoin('IPython', 'frontend', 'linefrontendbase'))
135 exclusions.append(pjoin('IPython', 'frontend', 'tests',
143 exclusions.append(pjoin('IPython', 'frontend', 'tests',
136 'test_linefrontend'))
144 'test_linefrontend'))
137 exclusions.append(pjoin('IPython', 'frontend', 'tests',
145 exclusions.append(pjoin('IPython', 'frontend', 'tests',
138 'test_frontendbase'))
146 'test_frontendbase'))
139 exclusions.append(pjoin('IPython', 'frontend', 'tests',
147 exclusions.append(pjoin('IPython', 'frontend', 'tests',
140 'test_prefilterfrontend'))
148 'test_prefilterfrontend'))
141 exclusions.append(pjoin('IPython', 'frontend', 'tests',
149 exclusions.append(pjoin('IPython', 'frontend', 'tests',
142 'test_asyncfrontendbase')),
150 'test_asyncfrontendbase')),
143 exclusions.append(pjoin('IPython', 'testing', 'parametric'))
151 exclusions.append(pjoin('IPython', 'testing', 'parametric'))
144 exclusions.append(pjoin('IPython', 'testing', 'util'))
152 exclusions.append(pjoin('IPython', 'testing', 'util'))
145
153
146 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
154 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
147 if sys.platform == 'win32':
155 if sys.platform == 'win32':
148 exclusions = [s.replace('\\','\\\\') for s in exclusions]
156 exclusions = [s.replace('\\','\\\\') for s in exclusions]
149
157
150 return exclusions
158 return exclusions
151
159
152
160
153 #-----------------------------------------------------------------------------
161 #-----------------------------------------------------------------------------
154 # Functions and classes
162 # Functions and classes
155 #-----------------------------------------------------------------------------
163 #-----------------------------------------------------------------------------
156
164
157 class IPTester(object):
165 class IPTester(object):
158 """Call that calls iptest or trial in a subprocess.
166 """Call that calls iptest or trial in a subprocess.
159 """
167 """
160 #: string, name of test runner that will be called
168 #: string, name of test runner that will be called
161 runner = None
169 runner = None
162 #: list, parameters for test runner
170 #: list, parameters for test runner
163 params = None
171 params = None
164 #: list, arguments of system call to be made to call test runner
172 #: list, arguments of system call to be made to call test runner
165 call_args = None
173 call_args = None
166 #: list, process ids of subprocesses we start (for cleanup)
174 #: list, process ids of subprocesses we start (for cleanup)
167 pids = None
175 pids = None
168
176
169 def __init__(self,runner='iptest',params=None):
177 def __init__(self,runner='iptest',params=None):
170 """Create new test runner."""
178 """Create new test runner."""
171 if runner == 'iptest':
179 if runner == 'iptest':
172 # Find our own 'iptest' script OS-level entry point
180 # Find our own 'iptest' script OS-level entry point
173 try:
181 try:
174 iptest_path = os.path.abspath(find_cmd('iptest'))
182 iptest_path = os.path.abspath(find_cmd('iptest'))
175 except FindCmdError:
183 except FindCmdError:
176 # Script not installed (may be the case for testing situations
184 # Script not installed (may be the case for testing situations
177 # that are running from a source tree only), pull from internal
185 # that are running from a source tree only), pull from internal
178 # path:
186 # path:
179 iptest_path = pjoin(genutils.get_ipython_package_dir(),
187 iptest_path = pjoin(genutils.get_ipython_package_dir(),
180 'scripts','iptest')
188 'scripts','iptest')
181 self.runner = ['python', iptest_path, '-v']
189 self.runner = ['python', iptest_path, '-v']
182 else:
190 else:
183 self.runner = ['python', os.path.abspath(find_cmd('trial'))]
191 self.runner = ['python', os.path.abspath(find_cmd('trial'))]
184 if params is None:
192 if params is None:
185 params = []
193 params = []
186 if isinstance(params,str):
194 if isinstance(params,str):
187 params = [params]
195 params = [params]
188 self.params = params
196 self.params = params
189
197
190 # Assemble call
198 # Assemble call
191 self.call_args = self.runner+self.params
199 self.call_args = self.runner+self.params
192
200
193 # Store pids of anything we start to clean up on deletion, if possible
201 # Store pids of anything we start to clean up on deletion, if possible
194 # (on posix only, since win32 has no os.kill)
202 # (on posix only, since win32 has no os.kill)
195 self.pids = []
203 self.pids = []
196
204
197 if sys.platform == 'win32':
205 if sys.platform == 'win32':
198 def _run_cmd(self):
206 def _run_cmd(self):
199 # On Windows, use os.system instead of subprocess.call, because I
207 # On Windows, use os.system instead of subprocess.call, because I
200 # was having problems with subprocess and I just don't know enough
208 # was having problems with subprocess and I just don't know enough
201 # about win32 to debug this reliably. Os.system may be the 'old
209 # about win32 to debug this reliably. Os.system may be the 'old
202 # fashioned' way to do it, but it works just fine. If someone
210 # fashioned' way to do it, but it works just fine. If someone
203 # later can clean this up that's fine, as long as the tests run
211 # later can clean this up that's fine, as long as the tests run
204 # reliably in win32.
212 # reliably in win32.
205 return os.system(' '.join(self.call_args))
213 return os.system(' '.join(self.call_args))
206 else:
214 else:
207 def _run_cmd(self):
215 def _run_cmd(self):
208 subp = subprocess.Popen(self.call_args)
216 subp = subprocess.Popen(self.call_args)
209 self.pids.append(subp.pid)
217 self.pids.append(subp.pid)
210 # If this fails, the pid will be left in self.pids and cleaned up
218 # If this fails, the pid will be left in self.pids and cleaned up
211 # later, but if the wait call succeeds, then we can clear the
219 # later, but if the wait call succeeds, then we can clear the
212 # stored pid.
220 # stored pid.
213 retcode = subp.wait()
221 retcode = subp.wait()
214 self.pids.pop()
222 self.pids.pop()
215 return retcode
223 return retcode
216
224
217 def run(self):
225 def run(self):
218 """Run the stored commands"""
226 """Run the stored commands"""
219 try:
227 try:
220 return self._run_cmd()
228 return self._run_cmd()
221 except:
229 except:
222 import traceback
230 import traceback
223 traceback.print_exc()
231 traceback.print_exc()
224 return 1 # signal failure
232 return 1 # signal failure
225
233
226 def __del__(self):
234 def __del__(self):
227 """Cleanup on exit by killing any leftover processes."""
235 """Cleanup on exit by killing any leftover processes."""
228
236
229 if not hasattr(os, 'kill'):
237 if not hasattr(os, 'kill'):
230 return
238 return
231
239
232 for pid in self.pids:
240 for pid in self.pids:
233 try:
241 try:
234 print 'Cleaning stale PID:', pid
242 print 'Cleaning stale PID:', pid
235 os.kill(pid, signal.SIGKILL)
243 os.kill(pid, signal.SIGKILL)
236 except OSError:
244 except OSError:
237 # This is just a best effort, if we fail or the process was
245 # This is just a best effort, if we fail or the process was
238 # really gone, ignore it.
246 # really gone, ignore it.
239 pass
247 pass
240
248
241
249
242 def make_runners():
250 def make_runners():
243 """Define the top-level packages that need to be tested.
251 """Define the top-level packages that need to be tested.
244 """
252 """
245
253
246 nose_packages = ['config', 'core', 'extensions', 'frontend', 'lib',
254 nose_packages = ['config', 'core', 'extensions', 'frontend', 'lib',
247 'scripts', 'testing', 'utils']
255 'scripts', 'testing', 'utils']
248 trial_packages = ['kernel']
256 trial_packages = ['kernel']
249
257
250 if have_wx:
258 if have_wx:
251 nose_packages.append('gui')
259 nose_packages.append('gui')
252
260
253 #nose_packages = ['core'] # dbg
261 #nose_packages = ['core'] # dbg
254 #trial_packages = [] # dbg
262 #trial_packages = [] # dbg
255
263
256 nose_packages = ['IPython.%s' % m for m in nose_packages ]
264 nose_packages = ['IPython.%s' % m for m in nose_packages ]
257 trial_packages = ['IPython.%s' % m for m in trial_packages ]
265 trial_packages = ['IPython.%s' % m for m in trial_packages ]
258
266
259 # Make runners, most with nose
267 # Make runners, most with nose
260 nose_testers = [IPTester(params=v) for v in nose_packages]
268 nose_testers = [IPTester(params=v) for v in nose_packages]
261 runners = dict(zip(nose_packages, nose_testers))
269 runners = dict(zip(nose_packages, nose_testers))
262 # And add twisted ones if conditions are met
270 # And add twisted ones if conditions are met
263 if have_zi and have_twisted and have_foolscap:
271 if have_zi and have_twisted and have_foolscap:
264 trial_testers = [IPTester('trial',params=v) for v in trial_packages]
272 trial_testers = [IPTester('trial',params=v) for v in trial_packages]
265 runners.update(dict(zip(trial_packages,trial_testers)))
273 runners.update(dict(zip(trial_packages,trial_testers)))
266
274
267 return runners
275 return runners
268
276
269
277
270 def run_iptest():
278 def run_iptest():
271 """Run the IPython test suite using nose.
279 """Run the IPython test suite using nose.
272
280
273 This function is called when this script is **not** called with the form
281 This function is called when this script is **not** called with the form
274 `iptest all`. It simply calls nose with appropriate command line flags
282 `iptest all`. It simply calls nose with appropriate command line flags
275 and accepts all of the standard nose arguments.
283 and accepts all of the standard nose arguments.
276 """
284 """
277
285
278 warnings.filterwarnings('ignore',
286 warnings.filterwarnings('ignore',
279 'This will be removed soon. Use IPython.testing.util instead')
287 'This will be removed soon. Use IPython.testing.util instead')
280
288
281 argv = sys.argv + [ '--detailed-errors',
289 argv = sys.argv + [ '--detailed-errors',
282 # Loading ipdoctest causes problems with Twisted, but
290 # Loading ipdoctest causes problems with Twisted, but
283 # our test suite runner now separates things and runs
291 # our test suite runner now separates things and runs
284 # all Twisted tests with trial.
292 # all Twisted tests with trial.
285 '--with-ipdoctest',
293 '--with-ipdoctest',
286 '--ipdoctest-tests','--ipdoctest-extension=txt',
294 '--ipdoctest-tests','--ipdoctest-extension=txt',
287
295
288 #'-x','-s', # dbg
296 #'-x','-s', # dbg
289
297
290 # We add --exe because of setuptools' imbecility (it
298 # We add --exe because of setuptools' imbecility (it
291 # blindly does chmod +x on ALL files). Nose does the
299 # blindly does chmod +x on ALL files). Nose does the
292 # right thing and it tries to avoid executables,
300 # right thing and it tries to avoid executables,
293 # setuptools unfortunately forces our hand here. This
301 # setuptools unfortunately forces our hand here. This
294 # has been discussed on the distutils list and the
302 # has been discussed on the distutils list and the
295 # setuptools devs refuse to fix this problem!
303 # setuptools devs refuse to fix this problem!
296 '--exe',
304 '--exe',
297 ]
305 ]
298
306
299 # Detect if any tests were required by explicitly calling an IPython
307 # Detect if any tests were required by explicitly calling an IPython
300 # submodule or giving a specific path
308 # submodule or giving a specific path
301 has_tests = False
309 has_tests = False
302 for arg in sys.argv:
310 for arg in sys.argv:
303 if 'IPython' in arg or arg.endswith('.py') or \
311 if 'IPython' in arg or arg.endswith('.py') or \
304 (':' in arg and '.py' in arg):
312 (':' in arg and '.py' in arg):
305 has_tests = True
313 has_tests = True
306 break
314 break
307
315
308 # If nothing was specifically requested, test full IPython
316 # If nothing was specifically requested, test full IPython
309 if not has_tests:
317 if not has_tests:
310 argv.append('IPython')
318 argv.append('IPython')
311
319
312 ## # Construct list of plugins, omitting the existing doctest plugin, which
320 ## # Construct list of plugins, omitting the existing doctest plugin, which
313 ## # ours replaces (and extends).
321 ## # ours replaces (and extends).
314 plugins = [IPythonDoctest(make_exclude())]
322 plugins = [IPythonDoctest(make_exclude())]
315 for p in nose.plugins.builtin.plugins:
323 for p in nose.plugins.builtin.plugins:
316 plug = p()
324 plug = p()
317 if plug.name == 'doctest':
325 if plug.name == 'doctest':
318 continue
326 continue
319 plugins.append(plug)
327 plugins.append(plug)
320
328
321 # We need a global ipython running in this process
329 # We need a global ipython running in this process
322 globalipapp.start_ipython()
330 globalipapp.start_ipython()
323 # Now nose can run
331 # Now nose can run
324 TestProgram(argv=argv,plugins=plugins)
332 TestProgram(argv=argv,plugins=plugins)
325
333
326
334
327 def run_iptestall():
335 def run_iptestall():
328 """Run the entire IPython test suite by calling nose and trial.
336 """Run the entire IPython test suite by calling nose and trial.
329
337
330 This function constructs :class:`IPTester` instances for all IPython
338 This function constructs :class:`IPTester` instances for all IPython
331 modules and package and then runs each of them. This causes the modules
339 modules and package and then runs each of them. This causes the modules
332 and packages of IPython to be tested each in their own subprocess using
340 and packages of IPython to be tested each in their own subprocess using
333 nose or twisted.trial appropriately.
341 nose or twisted.trial appropriately.
334 """
342 """
335
343
336 runners = make_runners()
344 runners = make_runners()
337
345
338 # Run the test runners in a temporary dir so we can nuke it when finished
346 # Run the test runners in a temporary dir so we can nuke it when finished
339 # to clean up any junk files left over by accident. This also makes it
347 # to clean up any junk files left over by accident. This also makes it
340 # robust against being run in non-writeable directories by mistake, as the
348 # robust against being run in non-writeable directories by mistake, as the
341 # temp dir will always be user-writeable.
349 # temp dir will always be user-writeable.
342 curdir = os.getcwd()
350 curdir = os.getcwd()
343 testdir = tempfile.gettempdir()
351 testdir = tempfile.gettempdir()
344 os.chdir(testdir)
352 os.chdir(testdir)
345
353
346 # Run all test runners, tracking execution time
354 # Run all test runners, tracking execution time
347 failed = {}
355 failed = {}
348 t_start = time.time()
356 t_start = time.time()
349 try:
357 try:
350 for name,runner in runners.iteritems():
358 for name,runner in runners.iteritems():
351 print '*'*77
359 print '*'*77
352 print 'IPython test group:',name
360 print 'IPython test group:',name
353 res = runner.run()
361 res = runner.run()
354 if res:
362 if res:
355 failed[name] = res
363 failed[name] = res
356 finally:
364 finally:
357 os.chdir(curdir)
365 os.chdir(curdir)
358 t_end = time.time()
366 t_end = time.time()
359 t_tests = t_end - t_start
367 t_tests = t_end - t_start
360 nrunners = len(runners)
368 nrunners = len(runners)
361 nfail = len(failed)
369 nfail = len(failed)
362 # summarize results
370 # summarize results
363 print
371 print
364 print '*'*77
372 print '*'*77
365 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
373 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
366 print
374 print
367 if not failed:
375 if not failed:
368 print 'OK'
376 print 'OK'
369 else:
377 else:
370 # If anything went wrong, point out what command to rerun manually to
378 # If anything went wrong, point out what command to rerun manually to
371 # see the actual errors and individual summary
379 # see the actual errors and individual summary
372 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
380 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
373 for name in failed:
381 for name in failed:
374 failed_runner = runners[name]
382 failed_runner = runners[name]
375 print '-'*40
383 print '-'*40
376 print 'Runner failed:',name
384 print 'Runner failed:',name
377 print 'You may wish to rerun this one individually, with:'
385 print 'You may wish to rerun this one individually, with:'
378 print ' '.join(failed_runner.call_args)
386 print ' '.join(failed_runner.call_args)
379 print
387 print
380
388
381
389
382 def main():
390 def main():
383 if len(sys.argv) == 1:
391 if len(sys.argv) == 1:
384 run_iptestall()
392 run_iptestall()
385 else:
393 else:
386 if sys.argv[1] == 'all':
394 if sys.argv[1] == 'all':
387 run_iptestall()
395 run_iptestall()
388 else:
396 else:
389 run_iptest()
397 run_iptest()
390
398
391
399
392 if __name__ == '__main__':
400 if __name__ == '__main__':
393 main()
401 main()
@@ -1,192 +1,189 b''
1 """Experimental code for cleaner support of IPython syntax with unittest.
1 """Experimental code for cleaner support of IPython syntax with unittest.
2
2
3 In IPython up until 0.10, we've used very hacked up nose machinery for running
3 In IPython up until 0.10, we've used very hacked up nose machinery for running
4 tests with IPython special syntax, and this has proved to be extremely slow.
4 tests with IPython special syntax, and this has proved to be extremely slow.
5 This module provides decorators to try a different approach, stemming from a
5 This module provides decorators to try a different approach, stemming from a
6 conversation Brian and I (FP) had about this problem Sept/09.
6 conversation Brian and I (FP) had about this problem Sept/09.
7
7
8 The goal is to be able to easily write simple functions that can be seen by
8 The goal is to be able to easily write simple functions that can be seen by
9 unittest as tests, and ultimately for these to support doctests with full
9 unittest as tests, and ultimately for these to support doctests with full
10 IPython syntax. Nose already offers this based on naming conventions and our
10 IPython syntax. Nose already offers this based on naming conventions and our
11 hackish plugins, but we are seeking to move away from nose dependencies if
11 hackish plugins, but we are seeking to move away from nose dependencies if
12 possible.
12 possible.
13
13
14 This module follows a different approach, based on decorators.
14 This module follows a different approach, based on decorators.
15
15
16 - A decorator called @ipdoctest can mark any function as having a docstring
16 - A decorator called @ipdoctest can mark any function as having a docstring
17 that should be viewed as a doctest, but after syntax conversion.
17 that should be viewed as a doctest, but after syntax conversion.
18
18
19 Authors
19 Authors
20 -------
20 -------
21
21
22 - Fernando Perez <Fernando.Perez@berkeley.edu>
22 - Fernando Perez <Fernando.Perez@berkeley.edu>
23 """
23 """
24
24
25 from __future__ import absolute_import
25 from __future__ import absolute_import
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Copyright (C) 2009 The IPython Development Team
28 # Copyright (C) 2009 The IPython Development Team
29 #
29 #
30 # Distributed under the terms of the BSD License. The full license is in
30 # Distributed under the terms of the BSD License. The full license is in
31 # the file COPYING, distributed as part of this software.
31 # the file COPYING, distributed as part of this software.
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Imports
36 # Imports
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 # Stdlib
39 # Stdlib
40 import re
40 import re
41 import sys
41 import sys
42 import unittest
42 import unittest
43 from doctest import DocTestFinder, DocTestRunner
43 from doctest import DocTestFinder, DocTestRunner
44 try:
44 try:
45 from doctest import TestResults
45 from doctest import TestResults
46 except:
46 except:
47 from ._doctest26 import TestResults
47 from ._doctest26 import TestResults
48
48
49 # Our own, a nose monkeypatch
50 from . import nosepatch
51
52 # We already have python3-compliant code for parametric tests
49 # We already have python3-compliant code for parametric tests
53 if sys.version[0]=='2':
50 if sys.version[0]=='2':
54 from ._paramtestpy2 import ParametricTestCase
51 from ._paramtestpy2 import ParametricTestCase
55 else:
52 else:
56 from ._paramtestpy3 import ParametricTestCase
53 from ._paramtestpy3 import ParametricTestCase
57
54
58 from . import globalipapp
55 from . import globalipapp
59
56
60 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
61 # Classes and functions
58 # Classes and functions
62 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
63
60
64 def count_failures(runner):
61 def count_failures(runner):
65 """Count number of failures in a doctest runner.
62 """Count number of failures in a doctest runner.
66
63
67 Code modeled after the summarize() method in doctest.
64 Code modeled after the summarize() method in doctest.
68 """
65 """
69 return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0 ]
66 return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0 ]
70
67
71
68
72 class IPython2PythonConverter(object):
69 class IPython2PythonConverter(object):
73 """Convert IPython 'syntax' to valid Python.
70 """Convert IPython 'syntax' to valid Python.
74
71
75 Eventually this code may grow to be the full IPython syntax conversion
72 Eventually this code may grow to be the full IPython syntax conversion
76 implementation, but for now it only does prompt convertion."""
73 implementation, but for now it only does prompt convertion."""
77
74
78 def __init__(self):
75 def __init__(self):
79 self.rps1 = re.compile(r'In\ \[\d+\]: ')
76 self.rps1 = re.compile(r'In\ \[\d+\]: ')
80 self.rps2 = re.compile(r'\ \ \ \.\.\.+: ')
77 self.rps2 = re.compile(r'\ \ \ \.\.\.+: ')
81 self.rout = re.compile(r'Out\[\d+\]: \s*?\n?')
78 self.rout = re.compile(r'Out\[\d+\]: \s*?\n?')
82 self.pyps1 = '>>> '
79 self.pyps1 = '>>> '
83 self.pyps2 = '... '
80 self.pyps2 = '... '
84 self.rpyps1 = re.compile ('(\s*%s)(.*)$' % self.pyps1)
81 self.rpyps1 = re.compile ('(\s*%s)(.*)$' % self.pyps1)
85 self.rpyps2 = re.compile ('(\s*%s)(.*)$' % self.pyps2)
82 self.rpyps2 = re.compile ('(\s*%s)(.*)$' % self.pyps2)
86
83
87 def __call__(self, ds):
84 def __call__(self, ds):
88 """Convert IPython prompts to python ones in a string."""
85 """Convert IPython prompts to python ones in a string."""
89 pyps1 = '>>> '
86 pyps1 = '>>> '
90 pyps2 = '... '
87 pyps2 = '... '
91 pyout = ''
88 pyout = ''
92
89
93 dnew = ds
90 dnew = ds
94 dnew = self.rps1.sub(pyps1, dnew)
91 dnew = self.rps1.sub(pyps1, dnew)
95 dnew = self.rps2.sub(pyps2, dnew)
92 dnew = self.rps2.sub(pyps2, dnew)
96 dnew = self.rout.sub(pyout, dnew)
93 dnew = self.rout.sub(pyout, dnew)
97 ip = globalipapp.get_ipython()
94 ip = globalipapp.get_ipython()
98
95
99 # Convert input IPython source into valid Python.
96 # Convert input IPython source into valid Python.
100 out = []
97 out = []
101 newline = out.append
98 newline = out.append
102 for line in dnew.splitlines():
99 for line in dnew.splitlines():
103
100
104 mps1 = self.rpyps1.match(line)
101 mps1 = self.rpyps1.match(line)
105 if mps1 is not None:
102 if mps1 is not None:
106 prompt, text = mps1.groups()
103 prompt, text = mps1.groups()
107 newline(prompt+ip.prefilter(text, False))
104 newline(prompt+ip.prefilter(text, False))
108 continue
105 continue
109
106
110 mps2 = self.rpyps2.match(line)
107 mps2 = self.rpyps2.match(line)
111 if mps2 is not None:
108 if mps2 is not None:
112 prompt, text = mps2.groups()
109 prompt, text = mps2.groups()
113 newline(prompt+ip.prefilter(text, True))
110 newline(prompt+ip.prefilter(text, True))
114 continue
111 continue
115
112
116 newline(line)
113 newline(line)
117 newline('') # ensure a closing newline, needed by doctest
114 newline('') # ensure a closing newline, needed by doctest
118 #print "PYSRC:", '\n'.join(out) # dbg
115 #print "PYSRC:", '\n'.join(out) # dbg
119 return '\n'.join(out)
116 return '\n'.join(out)
120
117
121 #return dnew
118 #return dnew
122
119
123
120
124 class Doc2UnitTester(object):
121 class Doc2UnitTester(object):
125 """Class whose instances act as a decorator for docstring testing.
122 """Class whose instances act as a decorator for docstring testing.
126
123
127 In practice we're only likely to need one instance ever, made below (though
124 In practice we're only likely to need one instance ever, made below (though
128 no attempt is made at turning it into a singleton, there is no need for
125 no attempt is made at turning it into a singleton, there is no need for
129 that).
126 that).
130 """
127 """
131 def __init__(self, verbose=False):
128 def __init__(self, verbose=False):
132 """New decorator.
129 """New decorator.
133
130
134 Parameters
131 Parameters
135 ----------
132 ----------
136
133
137 verbose : boolean, optional (False)
134 verbose : boolean, optional (False)
138 Passed to the doctest finder and runner to control verbosity.
135 Passed to the doctest finder and runner to control verbosity.
139 """
136 """
140 self.verbose = verbose
137 self.verbose = verbose
141 # We can reuse the same finder for all instances
138 # We can reuse the same finder for all instances
142 self.finder = DocTestFinder(verbose=verbose, recurse=False)
139 self.finder = DocTestFinder(verbose=verbose, recurse=False)
143
140
144 def __call__(self, func):
141 def __call__(self, func):
145 """Use as a decorator: doctest a function's docstring as a unittest.
142 """Use as a decorator: doctest a function's docstring as a unittest.
146
143
147 This version runs normal doctests, but the idea is to make it later run
144 This version runs normal doctests, but the idea is to make it later run
148 ipython syntax instead."""
145 ipython syntax instead."""
149
146
150 # Capture the enclosing instance with a different name, so the new
147 # Capture the enclosing instance with a different name, so the new
151 # class below can see it without confusion regarding its own 'self'
148 # class below can see it without confusion regarding its own 'self'
152 # that will point to the test instance at runtime
149 # that will point to the test instance at runtime
153 d2u = self
150 d2u = self
154
151
155 # Rewrite the function's docstring to have python syntax
152 # Rewrite the function's docstring to have python syntax
156 if func.__doc__ is not None:
153 if func.__doc__ is not None:
157 func.__doc__ = ip2py(func.__doc__)
154 func.__doc__ = ip2py(func.__doc__)
158
155
159 # Now, create a tester object that is a real unittest instance, so
156 # Now, create a tester object that is a real unittest instance, so
160 # normal unittest machinery (or Nose, or Trial) can find it.
157 # normal unittest machinery (or Nose, or Trial) can find it.
161 class Tester(unittest.TestCase):
158 class Tester(unittest.TestCase):
162 def test(self):
159 def test(self):
163 # Make a new runner per function to be tested
160 # Make a new runner per function to be tested
164 runner = DocTestRunner(verbose=d2u.verbose)
161 runner = DocTestRunner(verbose=d2u.verbose)
165 map(runner.run, d2u.finder.find(func, func.__name__))
162 map(runner.run, d2u.finder.find(func, func.__name__))
166 failed = count_failures(runner)
163 failed = count_failures(runner)
167 if failed:
164 if failed:
168 # Since we only looked at a single function's docstring,
165 # Since we only looked at a single function's docstring,
169 # failed should contain at most one item. More than that
166 # failed should contain at most one item. More than that
170 # is a case we can't handle and should error out on
167 # is a case we can't handle and should error out on
171 if len(failed) > 1:
168 if len(failed) > 1:
172 err = "Invalid number of test results:" % failed
169 err = "Invalid number of test results:" % failed
173 raise ValueError(err)
170 raise ValueError(err)
174 # Report a normal failure.
171 # Report a normal failure.
175 self.fail('failed doctests: %s' % str(failed[0]))
172 self.fail('failed doctests: %s' % str(failed[0]))
176
173
177 # Rename it so test reports have the original signature.
174 # Rename it so test reports have the original signature.
178 Tester.__name__ = func.__name__
175 Tester.__name__ = func.__name__
179 return Tester
176 return Tester
180
177
181
178
182 def ipdocstring(func):
179 def ipdocstring(func):
183 """Change the function docstring via ip2py.
180 """Change the function docstring via ip2py.
184 """
181 """
185 if func.__doc__ is not None:
182 if func.__doc__ is not None:
186 func.__doc__ = ip2py(func.__doc__)
183 func.__doc__ = ip2py(func.__doc__)
187 return func
184 return func
188
185
189
186
190 # Make an instance of the classes for public use
187 # Make an instance of the classes for public use
191 ipdoctest = Doc2UnitTester()
188 ipdoctest = Doc2UnitTester()
192 ip2py = IPython2PythonConverter()
189 ip2py = IPython2PythonConverter()
@@ -1,256 +1,266 b''
1 """Generic testing tools that do NOT depend on Twisted.
1 """Generic testing tools that do NOT depend on Twisted.
2
2
3 In particular, this module exposes a set of top-level assert* functions that
3 In particular, this module exposes a set of top-level assert* functions that
4 can be used in place of nose.tools.assert* in method generators (the ones in
4 can be used in place of nose.tools.assert* in method generators (the ones in
5 nose can not, at least as of nose 0.10.4).
5 nose can not, at least as of nose 0.10.4).
6
6
7 Note: our testing package contains testing.util, which does depend on Twisted
7 Note: our testing package contains testing.util, which does depend on Twisted
8 and provides utilities for tests that manage Deferreds. All testing support
8 and provides utilities for tests that manage Deferreds. All testing support
9 tools that only depend on nose, IPython or the standard library should go here
9 tools that only depend on nose, IPython or the standard library should go here
10 instead.
10 instead.
11
11
12
12
13 Authors
13 Authors
14 -------
14 -------
15 - Fernando Perez <Fernando.Perez@berkeley.edu>
15 - Fernando Perez <Fernando.Perez@berkeley.edu>
16 """
16 """
17
17
18 #*****************************************************************************
18 #*****************************************************************************
19 # Copyright (C) 2009 The IPython Development Team
19 # Copyright (C) 2009 The IPython Development Team
20 #
20 #
21 # Distributed under the terms of the BSD License. The full license is in
21 # Distributed under the terms of the BSD License. The full license is in
22 # the file COPYING, distributed as part of this software.
22 # the file COPYING, distributed as part of this software.
23 #*****************************************************************************
23 #*****************************************************************************
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Required modules and packages
26 # Required modules and packages
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 import os
29 import os
30 import re
30 import re
31 import sys
31 import sys
32 import tempfile
32 import tempfile
33
33
34 import nose.tools as nt
34 try:
35 # These tools are used by parts of the runtime, so we make the nose
36 # dependency optional at this point. Nose is a hard dependency to run the
37 # test suite, but NOT to use ipython itself.
38 import nose.tools as nt
39 has_nose = True
40 except ImportError:
41 has_nose = False
35
42
36 from IPython.utils import genutils, platutils
43 from IPython.utils import genutils, platutils
37
44
38 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
39 # Globals
46 # Globals
40 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
41
48
42 # Make a bunch of nose.tools assert wrappers that can be used in test
49 # Make a bunch of nose.tools assert wrappers that can be used in test
43 # generators. This will expose an assert* function for each one in nose.tools.
50 # generators. This will expose an assert* function for each one in nose.tools.
44
51
45 _tpl = """
52 _tpl = """
46 def %(name)s(*a,**kw):
53 def %(name)s(*a,**kw):
47 return nt.%(name)s(*a,**kw)
54 return nt.%(name)s(*a,**kw)
48 """
55 """
49
56
50 for _x in [a for a in dir(nt) if a.startswith('assert')]:
57 if has_nose:
51 exec _tpl % dict(name=_x)
58 for _x in [a for a in dir(nt) if a.startswith('assert')]:
59 exec _tpl % dict(name=_x)
52
60
53 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
54 # Functions and classes
62 # Functions and classes
55 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
56
64
57
65
58 def full_path(startPath,files):
66 def full_path(startPath,files):
59 """Make full paths for all the listed files, based on startPath.
67 """Make full paths for all the listed files, based on startPath.
60
68
61 Only the base part of startPath is kept, since this routine is typically
69 Only the base part of startPath is kept, since this routine is typically
62 used with a script's __file__ variable as startPath. The base of startPath
70 used with a script's __file__ variable as startPath. The base of startPath
63 is then prepended to all the listed files, forming the output list.
71 is then prepended to all the listed files, forming the output list.
64
72
65 Parameters
73 Parameters
66 ----------
74 ----------
67 startPath : string
75 startPath : string
68 Initial path to use as the base for the results. This path is split
76 Initial path to use as the base for the results. This path is split
69 using os.path.split() and only its first component is kept.
77 using os.path.split() and only its first component is kept.
70
78
71 files : string or list
79 files : string or list
72 One or more files.
80 One or more files.
73
81
74 Examples
82 Examples
75 --------
83 --------
76
84
77 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
85 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
78 ['/foo/a.txt', '/foo/b.txt']
86 ['/foo/a.txt', '/foo/b.txt']
79
87
80 >>> full_path('/foo',['a.txt','b.txt'])
88 >>> full_path('/foo',['a.txt','b.txt'])
81 ['/a.txt', '/b.txt']
89 ['/a.txt', '/b.txt']
82
90
83 If a single file is given, the output is still a list:
91 If a single file is given, the output is still a list:
84 >>> full_path('/foo','a.txt')
92 >>> full_path('/foo','a.txt')
85 ['/a.txt']
93 ['/a.txt']
86 """
94 """
87
95
88 files = genutils.list_strings(files)
96 files = genutils.list_strings(files)
89 base = os.path.split(startPath)[0]
97 base = os.path.split(startPath)[0]
90 return [ os.path.join(base,f) for f in files ]
98 return [ os.path.join(base,f) for f in files ]
91
99
92
100
93 def parse_test_output(txt):
101 def parse_test_output(txt):
94 """Parse the output of a test run and return errors, failures.
102 """Parse the output of a test run and return errors, failures.
95
103
96 Parameters
104 Parameters
97 ----------
105 ----------
98 txt : str
106 txt : str
99 Text output of a test run, assumed to contain a line of one of the
107 Text output of a test run, assumed to contain a line of one of the
100 following forms::
108 following forms::
101 'FAILED (errors=1)'
109 'FAILED (errors=1)'
102 'FAILED (failures=1)'
110 'FAILED (failures=1)'
103 'FAILED (errors=1, failures=1)'
111 'FAILED (errors=1, failures=1)'
104
112
105 Returns
113 Returns
106 -------
114 -------
107 nerr, nfail: number of errors and failures.
115 nerr, nfail: number of errors and failures.
108 """
116 """
109
117
110 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
118 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
111 if err_m:
119 if err_m:
112 nerr = int(err_m.group(1))
120 nerr = int(err_m.group(1))
113 nfail = 0
121 nfail = 0
114 return nerr, nfail
122 return nerr, nfail
115
123
116 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
124 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
117 if fail_m:
125 if fail_m:
118 nerr = 0
126 nerr = 0
119 nfail = int(fail_m.group(1))
127 nfail = int(fail_m.group(1))
120 return nerr, nfail
128 return nerr, nfail
121
129
122 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
130 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
123 re.MULTILINE)
131 re.MULTILINE)
124 if both_m:
132 if both_m:
125 nerr = int(both_m.group(1))
133 nerr = int(both_m.group(1))
126 nfail = int(both_m.group(2))
134 nfail = int(both_m.group(2))
127 return nerr, nfail
135 return nerr, nfail
128
136
129 # If the input didn't match any of these forms, assume no error/failures
137 # If the input didn't match any of these forms, assume no error/failures
130 return 0, 0
138 return 0, 0
131
139
132
140
133 # So nose doesn't think this is a test
141 # So nose doesn't think this is a test
134 parse_test_output.__test__ = False
142 parse_test_output.__test__ = False
135
143
136
144
137 def temp_pyfile(src, ext='.py'):
145 def temp_pyfile(src, ext='.py'):
138 """Make a temporary python file, return filename and filehandle.
146 """Make a temporary python file, return filename and filehandle.
139
147
140 Parameters
148 Parameters
141 ----------
149 ----------
142 src : string or list of strings (no need for ending newlines if list)
150 src : string or list of strings (no need for ending newlines if list)
143 Source code to be written to the file.
151 Source code to be written to the file.
144
152
145 ext : optional, string
153 ext : optional, string
146 Extension for the generated file.
154 Extension for the generated file.
147
155
148 Returns
156 Returns
149 -------
157 -------
150 (filename, open filehandle)
158 (filename, open filehandle)
151 It is the caller's responsibility to close the open file and unlink it.
159 It is the caller's responsibility to close the open file and unlink it.
152 """
160 """
153 fname = tempfile.mkstemp(ext)[1]
161 fname = tempfile.mkstemp(ext)[1]
154 f = open(fname,'w')
162 f = open(fname,'w')
155 f.write(src)
163 f.write(src)
156 f.flush()
164 f.flush()
157 return fname, f
165 return fname, f
158
166
159
167
160 def default_argv():
168 def default_argv():
161 """Return a valid default argv for creating testing instances of ipython"""
169 """Return a valid default argv for creating testing instances of ipython"""
162
170
163 # Get the install directory for the user configuration and tell ipython to
171 # Get the install directory for the user configuration and tell ipython to
164 # use the default profile from there.
172 # use the default profile from there.
165 from IPython.config import default
173 from IPython.config import default
166 ipcdir = os.path.dirname(default.__file__)
174 ipcdir = os.path.dirname(default.__file__)
167 ipconf = os.path.join(ipcdir,'ipython_config.py')
175 ipconf = os.path.join(ipcdir,'ipython_config.py')
168 return ['--colors=NoColor', '--no-term-title','--no-banner',
176 return ['--colors=NoColor', '--no-term-title','--no-banner',
169 '--config-file=%s' % ipconf, '--autocall=0',
177 '--config-file=%s' % ipconf, '--autocall=0',
170 '--prompt-out=""']
178 '--prompt-out=""']
171
179
172
180
173 def ipexec(fname, options=None):
181 def ipexec(fname, options=None):
174 """Utility to call 'ipython filename'.
182 """Utility to call 'ipython filename'.
175
183
176 Starts IPython witha minimal and safe configuration to make startup as fast
184 Starts IPython witha minimal and safe configuration to make startup as fast
177 as possible.
185 as possible.
178
186
179 Note that this starts IPython in a subprocess!
187 Note that this starts IPython in a subprocess!
180
188
181 Parameters
189 Parameters
182 ----------
190 ----------
183 fname : str
191 fname : str
184 Name of file to be executed (should have .py or .ipy extension).
192 Name of file to be executed (should have .py or .ipy extension).
185
193
186 options : optional, list
194 options : optional, list
187 Extra command-line flags to be passed to IPython.
195 Extra command-line flags to be passed to IPython.
188
196
189 Returns
197 Returns
190 -------
198 -------
191 (stdout, stderr) of ipython subprocess.
199 (stdout, stderr) of ipython subprocess.
192 """
200 """
193 if options is None: options = []
201 if options is None: options = []
194 cmdargs = ' '.join(default_argv() + options)
202 cmdargs = ' '.join(default_argv() + options)
195
203
196 _ip = get_ipython()
204 _ip = get_ipython()
197 test_dir = os.path.dirname(__file__)
205 test_dir = os.path.dirname(__file__)
198 full_fname = os.path.join(test_dir, fname)
206 full_fname = os.path.join(test_dir, fname)
199 ipython_cmd = platutils.find_cmd('ipython')
207 ipython_cmd = platutils.find_cmd('ipython')
200 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
208 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
201 return genutils.getoutputerror(full_cmd)
209 return genutils.getoutputerror(full_cmd)
202
210
203
211
204 def ipexec_validate(fname, expected_out, expected_err=None,
212 def ipexec_validate(fname, expected_out, expected_err=None,
205 options=None):
213 options=None):
206 """Utility to call 'ipython filename' and validate output/error.
214 """Utility to call 'ipython filename' and validate output/error.
207
215
208 This function raises an AssertionError if the validation fails.
216 This function raises an AssertionError if the validation fails.
209
217
210 Note that this starts IPython in a subprocess!
218 Note that this starts IPython in a subprocess!
211
219
212 Parameters
220 Parameters
213 ----------
221 ----------
214 fname : str
222 fname : str
215 Name of the file to be executed (should have .py or .ipy extension).
223 Name of the file to be executed (should have .py or .ipy extension).
216
224
217 expected_out : str
225 expected_out : str
218 Expected stdout of the process.
226 Expected stdout of the process.
219
227
220 expected_err : optional, str
228 expected_err : optional, str
221 Expected stderr of the process.
229 Expected stderr of the process.
222
230
223 options : optional, list
231 options : optional, list
224 Extra command-line flags to be passed to IPython.
232 Extra command-line flags to be passed to IPython.
225
233
226 Returns
234 Returns
227 -------
235 -------
228 None
236 None
229 """
237 """
230
238
239 import nose.tools as nt
240
231 out, err = ipexec(fname)
241 out, err = ipexec(fname)
232 nt.assert_equals(out.strip(), expected_out.strip())
242 nt.assert_equals(out.strip(), expected_out.strip())
233 if expected_err:
243 if expected_err:
234 nt.assert_equals(err.strip(), expected_err.strip())
244 nt.assert_equals(err.strip(), expected_err.strip())
235
245
236
246
237 class TempFileMixin(object):
247 class TempFileMixin(object):
238 """Utility class to create temporary Python/IPython files.
248 """Utility class to create temporary Python/IPython files.
239
249
240 Meant as a mixin class for test cases."""
250 Meant as a mixin class for test cases."""
241
251
242 def mktmp(self, src, ext='.py'):
252 def mktmp(self, src, ext='.py'):
243 """Make a valid python temp file."""
253 """Make a valid python temp file."""
244 fname, f = temp_pyfile(src, ext)
254 fname, f = temp_pyfile(src, ext)
245 self.tmpfile = f
255 self.tmpfile = f
246 self.fname = fname
256 self.fname = fname
247
257
248 def teardown(self):
258 def teardown(self):
249 self.tmpfile.close()
259 self.tmpfile.close()
250 try:
260 try:
251 os.unlink(self.fname)
261 os.unlink(self.fname)
252 except:
262 except:
253 # On Windows, even though we close the file, we still can't delete
263 # On Windows, even though we close the file, we still can't delete
254 # it. I have no clue why
264 # it. I have no clue why
255 pass
265 pass
256
266
General Comments 0
You need to be logged in to leave comments. Login now