comparison DEPENDENCIES/mingw32/Python27/Lib/site-packages/numpy/testing/noseclasses.py @ 87:2a2c65a20a8b

Add Python libs and headers
author Chris Cannam
date Wed, 25 Feb 2015 14:05:22 +0000
parents
children
comparison
equal deleted inserted replaced
86:413a9d26189e 87:2a2c65a20a8b
1 # These classes implement a doctest runner plugin for nose, a "known failure"
2 # error class, and a customized TestProgram for NumPy.
3
4 # Because this module imports nose directly, it should not
5 # be used except by nosetester.py to avoid a general NumPy
6 # dependency on nose.
7 from __future__ import division, absolute_import, print_function
8
9 import os
10 import doctest
11
12 import nose
13 from nose.plugins import doctests as npd
14 from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin
15 from nose.plugins.base import Plugin
16 from nose.util import src
17 import numpy
18 from .nosetester import get_package_name
19 import inspect
20
21 # Some of the classes in this module begin with 'Numpy' to clearly distinguish
22 # them from the plethora of very similar names from nose/unittest/doctest
23
24 #-----------------------------------------------------------------------------
25 # Modified version of the one in the stdlib, that fixes a python bug (doctests
26 # not found in extension modules, http://bugs.python.org/issue3158)
27 class NumpyDocTestFinder(doctest.DocTestFinder):
28
29 def _from_module(self, module, object):
30 """
31 Return true if the given object is defined in the given
32 module.
33 """
34 if module is None:
35 #print '_fm C1' # dbg
36 return True
37 elif inspect.isfunction(object):
38 #print '_fm C2' # dbg
39 return module.__dict__ is object.__globals__
40 elif inspect.isbuiltin(object):
41 #print '_fm C2-1' # dbg
42 return module.__name__ == object.__module__
43 elif inspect.isclass(object):
44 #print '_fm C3' # dbg
45 return module.__name__ == object.__module__
46 elif inspect.ismethod(object):
47 # This one may be a bug in cython that fails to correctly set the
48 # __module__ attribute of methods, but since the same error is easy
49 # to make by extension code writers, having this safety in place
50 # isn't such a bad idea
51 #print '_fm C3-1' # dbg
52 return module.__name__ == object.__self__.__class__.__module__
53 elif inspect.getmodule(object) is not None:
54 #print '_fm C4' # dbg
55 #print 'C4 mod',module,'obj',object # dbg
56 return module is inspect.getmodule(object)
57 elif hasattr(object, '__module__'):
58 #print '_fm C5' # dbg
59 return module.__name__ == object.__module__
60 elif isinstance(object, property):
61 #print '_fm C6' # dbg
62 return True # [XX] no way not be sure.
63 else:
64 raise ValueError("object must be a class or function")
65
66 def _find(self, tests, obj, name, module, source_lines, globs, seen):
67 """
68 Find tests for the given object and any contained objects, and
69 add them to `tests`.
70 """
71
72 doctest.DocTestFinder._find(self, tests, obj, name, module,
73 source_lines, globs, seen)
74
75 # Below we re-run pieces of the above method with manual modifications,
76 # because the original code is buggy and fails to correctly identify
77 # doctests in extension modules.
78
79 # Local shorthands
80 from inspect import isroutine, isclass, ismodule, isfunction, \
81 ismethod
82
83 # Look for tests in a module's contained objects.
84 if ismodule(obj) and self._recurse:
85 for valname, val in obj.__dict__.items():
86 valname1 = '%s.%s' % (name, valname)
87 if ( (isroutine(val) or isclass(val))
88 and self._from_module(module, val) ):
89
90 self._find(tests, val, valname1, module, source_lines,
91 globs, seen)
92
93
94 # Look for tests in a class's contained objects.
95 if isclass(obj) and self._recurse:
96 #print 'RECURSE into class:',obj # dbg
97 for valname, val in obj.__dict__.items():
98 #valname1 = '%s.%s' % (name, valname) # dbg
99 #print 'N',name,'VN:',valname,'val:',str(val)[:77] # dbg
100 # Special handling for staticmethod/classmethod.
101 if isinstance(val, staticmethod):
102 val = getattr(obj, valname)
103 if isinstance(val, classmethod):
104 val = getattr(obj, valname).__func__
105
106 # Recurse to methods, properties, and nested classes.
107 if ((isfunction(val) or isclass(val) or
108 ismethod(val) or isinstance(val, property)) and
109 self._from_module(module, val)):
110 valname = '%s.%s' % (name, valname)
111 self._find(tests, val, valname, module, source_lines,
112 globs, seen)
113
114
115 # second-chance checker; if the default comparison doesn't
116 # pass, then see if the expected output string contains flags that
117 # tell us to ignore the output
118 class NumpyOutputChecker(doctest.OutputChecker):
119 def check_output(self, want, got, optionflags):
120 ret = doctest.OutputChecker.check_output(self, want, got,
121 optionflags)
122 if not ret:
123 if "#random" in want:
124 return True
125
126 # it would be useful to normalize endianness so that
127 # bigendian machines don't fail all the tests (and there are
128 # actually some bigendian examples in the doctests). Let's try
129 # making them all little endian
130 got = got.replace("'>", "'<")
131 want= want.replace("'>", "'<")
132
133 # try to normalize out 32 and 64 bit default int sizes
134 for sz in [4, 8]:
135 got = got.replace("'<i%d'"%sz, "int")
136 want= want.replace("'<i%d'"%sz, "int")
137
138 ret = doctest.OutputChecker.check_output(self, want,
139 got, optionflags)
140
141 return ret
142
143
144 # Subclass nose.plugins.doctests.DocTestCase to work around a bug in
145 # its constructor that blocks non-default arguments from being passed
146 # down into doctest.DocTestCase
147 class NumpyDocTestCase(npd.DocTestCase):
148 def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
149 checker=None, obj=None, result_var='_'):
150 self._result_var = result_var
151 self._nose_obj = obj
152 doctest.DocTestCase.__init__(self, test,
153 optionflags=optionflags,
154 setUp=setUp, tearDown=tearDown,
155 checker=checker)
156
157
158 print_state = numpy.get_printoptions()
159
160 class NumpyDoctest(npd.Doctest):
161 name = 'numpydoctest' # call nosetests with --with-numpydoctest
162 score = 1000 # load late, after doctest builtin
163
164 # always use whitespace and ellipsis options for doctests
165 doctest_optflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
166
167 # files that should be ignored for doctests
168 doctest_ignore = ['generate_numpy_api.py',
169 'setup.py']
170
171 # Custom classes; class variables to allow subclassing
172 doctest_case_class = NumpyDocTestCase
173 out_check_class = NumpyOutputChecker
174 test_finder_class = NumpyDocTestFinder
175
176 # Don't use the standard doctest option handler; hard-code the option values
177 def options(self, parser, env=os.environ):
178 Plugin.options(self, parser, env)
179 # Test doctests in 'test' files / directories. Standard plugin default
180 # is False
181 self.doctest_tests = True
182 # Variable name; if defined, doctest results stored in this variable in
183 # the top-level namespace. None is the standard default
184 self.doctest_result_var = None
185
186 def configure(self, options, config):
187 # parent method sets enabled flag from command line --with-numpydoctest
188 Plugin.configure(self, options, config)
189 self.finder = self.test_finder_class()
190 self.parser = doctest.DocTestParser()
191 if self.enabled:
192 # Pull standard doctest out of plugin list; there's no reason to run
193 # both. In practice the Unplugger plugin above would cover us when
194 # run from a standard numpy.test() call; this is just in case
195 # someone wants to run our plugin outside the numpy.test() machinery
196 config.plugins.plugins = [p for p in config.plugins.plugins
197 if p.name != 'doctest']
198
199 def set_test_context(self, test):
200 """ Configure `test` object to set test context
201
202 We set the numpy / scipy standard doctest namespace
203
204 Parameters
205 ----------
206 test : test object
207 with ``globs`` dictionary defining namespace
208
209 Returns
210 -------
211 None
212
213 Notes
214 -----
215 `test` object modified in place
216 """
217 # set the namespace for tests
218 pkg_name = get_package_name(os.path.dirname(test.filename))
219
220 # Each doctest should execute in an environment equivalent to
221 # starting Python and executing "import numpy as np", and,
222 # for SciPy packages, an additional import of the local
223 # package (so that scipy.linalg.basic.py's doctests have an
224 # implicit "from scipy import linalg" as well.
225 #
226 # Note: __file__ allows the doctest in NoseTester to run
227 # without producing an error
228 test.globs = {'__builtins__':__builtins__,
229 '__file__':'__main__',
230 '__name__':'__main__',
231 'np':numpy}
232 # add appropriate scipy import for SciPy tests
233 if 'scipy' in pkg_name:
234 p = pkg_name.split('.')
235 p2 = p[-1]
236 test.globs[p2] = __import__(pkg_name, test.globs, {}, [p2])
237
238 # Override test loading to customize test context (with set_test_context
239 # method), set standard docstring options, and install our own test output
240 # checker
241 def loadTestsFromModule(self, module):
242 if not self.matches(module.__name__):
243 npd.log.debug("Doctest doesn't want module %s", module)
244 return
245 try:
246 tests = self.finder.find(module)
247 except AttributeError:
248 # nose allows module.__test__ = False; doctest does not and
249 # throws AttributeError
250 return
251 if not tests:
252 return
253 tests.sort()
254 module_file = src(module.__file__)
255 for test in tests:
256 if not test.examples:
257 continue
258 if not test.filename:
259 test.filename = module_file
260 # Set test namespace; test altered in place
261 self.set_test_context(test)
262 yield self.doctest_case_class(test,
263 optionflags=self.doctest_optflags,
264 checker=self.out_check_class(),
265 result_var=self.doctest_result_var)
266
267 # Add an afterContext method to nose.plugins.doctests.Doctest in order
268 # to restore print options to the original state after each doctest
269 def afterContext(self):
270 numpy.set_printoptions(**print_state)
271
272 # Ignore NumPy-specific build files that shouldn't be searched for tests
273 def wantFile(self, file):
274 bn = os.path.basename(file)
275 if bn in self.doctest_ignore:
276 return False
277 return npd.Doctest.wantFile(self, file)
278
279
280 class Unplugger(object):
281 """ Nose plugin to remove named plugin late in loading
282
283 By default it removes the "doctest" plugin.
284 """
285 name = 'unplugger'
286 enabled = True # always enabled
287 score = 4000 # load late in order to be after builtins
288
289 def __init__(self, to_unplug='doctest'):
290 self.to_unplug = to_unplug
291
292 def options(self, parser, env):
293 pass
294
295 def configure(self, options, config):
296 # Pull named plugin out of plugins list
297 config.plugins.plugins = [p for p in config.plugins.plugins
298 if p.name != self.to_unplug]
299
300
301 class KnownFailureTest(Exception):
302 '''Raise this exception to mark a test as a known failing test.'''
303 pass
304
305
306 class KnownFailure(ErrorClassPlugin):
307 '''Plugin that installs a KNOWNFAIL error class for the
308 KnownFailureClass exception. When KnownFailureTest is raised,
309 the exception will be logged in the knownfail attribute of the
310 result, 'K' or 'KNOWNFAIL' (verbose) will be output, and the
311 exception will not be counted as an error or failure.'''
312 enabled = True
313 knownfail = ErrorClass(KnownFailureTest,
314 label='KNOWNFAIL',
315 isfailure=False)
316
317 def options(self, parser, env=os.environ):
318 env_opt = 'NOSE_WITHOUT_KNOWNFAIL'
319 parser.add_option('--no-knownfail', action='store_true',
320 dest='noKnownFail', default=env.get(env_opt, False),
321 help='Disable special handling of KnownFailureTest '
322 'exceptions')
323
324 def configure(self, options, conf):
325 if not self.can_configure:
326 return
327 self.conf = conf
328 disable = getattr(options, 'noKnownFail', False)
329 if disable:
330 self.enabled = False
331
332
333 # Class allows us to save the results of the tests in runTests - see runTests
334 # method docstring for details
335 class NumpyTestProgram(nose.core.TestProgram):
336 def runTests(self):
337 """Run Tests. Returns true on success, false on failure, and
338 sets self.success to the same value.
339
340 Because nose currently discards the test result object, but we need
341 to return it to the user, override TestProgram.runTests to retain
342 the result
343 """
344 if self.testRunner is None:
345 self.testRunner = nose.core.TextTestRunner(stream=self.config.stream,
346 verbosity=self.config.verbosity,
347 config=self.config)
348 plug_runner = self.config.plugins.prepareTestRunner(self.testRunner)
349 if plug_runner is not None:
350 self.testRunner = plug_runner
351 self.result = self.testRunner.run(self.test)
352 self.success = self.result.wasSuccessful()
353 return self.success