nosetester.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. """
  2. Nose test running.
  3. This module implements ``test()`` and ``bench()`` functions for NumPy modules.
  4. """
  5. import os
  6. import sys
  7. import warnings
  8. import numpy as np
  9. from .utils import import_nose, suppress_warnings
  10. __all__ = ['get_package_name', 'run_module_suite', 'NoseTester',
  11. '_numpy_tester', 'get_package_name', 'import_nose',
  12. 'suppress_warnings']
  13. def get_package_name(filepath):
  14. """
  15. Given a path where a package is installed, determine its name.
  16. Parameters
  17. ----------
  18. filepath : str
  19. Path to a file. If the determination fails, "numpy" is returned.
  20. Examples
  21. --------
  22. >>> np.testing.nosetester.get_package_name('nonsense')
  23. 'numpy'
  24. """
  25. fullpath = filepath[:]
  26. pkg_name = []
  27. while 'site-packages' in filepath or 'dist-packages' in filepath:
  28. filepath, p2 = os.path.split(filepath)
  29. if p2 in ('site-packages', 'dist-packages'):
  30. break
  31. pkg_name.append(p2)
  32. # if package name determination failed, just default to numpy/scipy
  33. if not pkg_name:
  34. if 'scipy' in fullpath:
  35. return 'scipy'
  36. else:
  37. return 'numpy'
  38. # otherwise, reverse to get correct order and return
  39. pkg_name.reverse()
  40. # don't include the outer egg directory
  41. if pkg_name[0].endswith('.egg'):
  42. pkg_name.pop(0)
  43. return '.'.join(pkg_name)
  44. def run_module_suite(file_to_run=None, argv=None):
  45. """
  46. Run a test module.
  47. Equivalent to calling ``$ nosetests <argv> <file_to_run>`` from
  48. the command line
  49. Parameters
  50. ----------
  51. file_to_run : str, optional
  52. Path to test module, or None.
  53. By default, run the module from which this function is called.
  54. argv : list of strings
  55. Arguments to be passed to the nose test runner. ``argv[0]`` is
  56. ignored. All command line arguments accepted by ``nosetests``
  57. will work. If it is the default value None, sys.argv is used.
  58. .. versionadded:: 1.9.0
  59. Examples
  60. --------
  61. Adding the following::
  62. if __name__ == "__main__" :
  63. run_module_suite(argv=sys.argv)
  64. at the end of a test module will run the tests when that module is
  65. called in the python interpreter.
  66. Alternatively, calling::
  67. >>> run_module_suite(file_to_run="numpy/tests/test_matlib.py") # doctest: +SKIP
  68. from an interpreter will run all the test routine in 'test_matlib.py'.
  69. """
  70. if file_to_run is None:
  71. f = sys._getframe(1)
  72. file_to_run = f.f_locals.get('__file__', None)
  73. if file_to_run is None:
  74. raise AssertionError
  75. if argv is None:
  76. argv = sys.argv + [file_to_run]
  77. else:
  78. argv = argv + [file_to_run]
  79. nose = import_nose()
  80. from .noseclasses import KnownFailurePlugin
  81. nose.run(argv=argv, addplugins=[KnownFailurePlugin()])
  82. class NoseTester:
  83. """
  84. Nose test runner.
  85. This class is made available as numpy.testing.Tester, and a test function
  86. is typically added to a package's __init__.py like so::
  87. from numpy.testing import Tester
  88. test = Tester().test
  89. Calling this test function finds and runs all tests associated with the
  90. package and all its sub-packages.
  91. Attributes
  92. ----------
  93. package_path : str
  94. Full path to the package to test.
  95. package_name : str
  96. Name of the package to test.
  97. Parameters
  98. ----------
  99. package : module, str or None, optional
  100. The package to test. If a string, this should be the full path to
  101. the package. If None (default), `package` is set to the module from
  102. which `NoseTester` is initialized.
  103. raise_warnings : None, str or sequence of warnings, optional
  104. This specifies which warnings to configure as 'raise' instead
  105. of being shown once during the test execution. Valid strings are:
  106. - "develop" : equals ``(Warning,)``
  107. - "release" : equals ``()``, don't raise on any warnings.
  108. Default is "release".
  109. depth : int, optional
  110. If `package` is None, then this can be used to initialize from the
  111. module of the caller of (the caller of (...)) the code that
  112. initializes `NoseTester`. Default of 0 means the module of the
  113. immediate caller; higher values are useful for utility routines that
  114. want to initialize `NoseTester` objects on behalf of other code.
  115. """
  116. def __init__(self, package=None, raise_warnings="release", depth=0,
  117. check_fpu_mode=False):
  118. # Back-compat: 'None' used to mean either "release" or "develop"
  119. # depending on whether this was a release or develop version of
  120. # numpy. Those semantics were fine for testing numpy, but not so
  121. # helpful for downstream projects like scipy that use
  122. # numpy.testing. (They want to set this based on whether *they* are a
  123. # release or develop version, not whether numpy is.) So we continue to
  124. # accept 'None' for back-compat, but it's now just an alias for the
  125. # default "release".
  126. if raise_warnings is None:
  127. raise_warnings = "release"
  128. package_name = None
  129. if package is None:
  130. f = sys._getframe(1 + depth)
  131. package_path = f.f_locals.get('__file__', None)
  132. if package_path is None:
  133. raise AssertionError
  134. package_path = os.path.dirname(package_path)
  135. package_name = f.f_locals.get('__name__', None)
  136. elif isinstance(package, type(os)):
  137. package_path = os.path.dirname(package.__file__)
  138. package_name = getattr(package, '__name__', None)
  139. else:
  140. package_path = str(package)
  141. self.package_path = package_path
  142. # Find the package name under test; this name is used to limit coverage
  143. # reporting (if enabled).
  144. if package_name is None:
  145. package_name = get_package_name(package_path)
  146. self.package_name = package_name
  147. # Set to "release" in constructor in maintenance branches.
  148. self.raise_warnings = raise_warnings
  149. # Whether to check for FPU mode changes
  150. self.check_fpu_mode = check_fpu_mode
  151. def _test_argv(self, label, verbose, extra_argv):
  152. ''' Generate argv for nosetest command
  153. Parameters
  154. ----------
  155. label : {'fast', 'full', '', attribute identifier}, optional
  156. see ``test`` docstring
  157. verbose : int, optional
  158. Verbosity value for test outputs, in the range 1-10. Default is 1.
  159. extra_argv : list, optional
  160. List with any extra arguments to pass to nosetests.
  161. Returns
  162. -------
  163. argv : list
  164. command line arguments that will be passed to nose
  165. '''
  166. argv = [__file__, self.package_path, '-s']
  167. if label and label != 'full':
  168. if not isinstance(label, str):
  169. raise TypeError('Selection label should be a string')
  170. if label == 'fast':
  171. label = 'not slow'
  172. argv += ['-A', label]
  173. argv += ['--verbosity', str(verbose)]
  174. # When installing with setuptools, and also in some other cases, the
  175. # test_*.py files end up marked +x executable. Nose, by default, does
  176. # not run files marked with +x as they might be scripts. However, in
  177. # our case nose only looks for test_*.py files under the package
  178. # directory, which should be safe.
  179. argv += ['--exe']
  180. if extra_argv:
  181. argv += extra_argv
  182. return argv
  183. def _show_system_info(self):
  184. nose = import_nose()
  185. import numpy
  186. print(f'NumPy version {numpy.__version__}')
  187. relaxed_strides = numpy.ones((10, 1), order="C").flags.f_contiguous
  188. print("NumPy relaxed strides checking option:", relaxed_strides)
  189. npdir = os.path.dirname(numpy.__file__)
  190. print(f'NumPy is installed in {npdir}')
  191. if 'scipy' in self.package_name:
  192. import scipy
  193. print(f'SciPy version {scipy.__version__}')
  194. spdir = os.path.dirname(scipy.__file__)
  195. print(f'SciPy is installed in {spdir}')
  196. pyversion = sys.version.replace('\n', '')
  197. print(f'Python version {pyversion}')
  198. print("nose version %d.%d.%d" % nose.__versioninfo__)
  199. def _get_custom_doctester(self):
  200. """ Return instantiated plugin for doctests
  201. Allows subclassing of this class to override doctester
  202. A return value of None means use the nose builtin doctest plugin
  203. """
  204. from .noseclasses import NumpyDoctest
  205. return NumpyDoctest()
  206. def prepare_test_args(self, label='fast', verbose=1, extra_argv=None,
  207. doctests=False, coverage=False, timer=False):
  208. """
  209. Run tests for module using nose.
  210. This method does the heavy lifting for the `test` method. It takes all
  211. the same arguments, for details see `test`.
  212. See Also
  213. --------
  214. test
  215. """
  216. # fail with nice error message if nose is not present
  217. import_nose()
  218. # compile argv
  219. argv = self._test_argv(label, verbose, extra_argv)
  220. # our way of doing coverage
  221. if coverage:
  222. argv += [f'--cover-package={self.package_name}', '--with-coverage',
  223. '--cover-tests', '--cover-erase']
  224. if timer:
  225. if timer is True:
  226. argv += ['--with-timer']
  227. elif isinstance(timer, int):
  228. argv += ['--with-timer', '--timer-top-n', str(timer)]
  229. # construct list of plugins
  230. import nose.plugins.builtin
  231. from nose.plugins import EntryPointPluginManager
  232. from .noseclasses import (KnownFailurePlugin, Unplugger,
  233. FPUModeCheckPlugin)
  234. plugins = [KnownFailurePlugin()]
  235. plugins += [p() for p in nose.plugins.builtin.plugins]
  236. if self.check_fpu_mode:
  237. plugins += [FPUModeCheckPlugin()]
  238. argv += ["--with-fpumodecheckplugin"]
  239. try:
  240. # External plugins (like nose-timer)
  241. entrypoint_manager = EntryPointPluginManager()
  242. entrypoint_manager.loadPlugins()
  243. plugins += [p for p in entrypoint_manager.plugins]
  244. except ImportError:
  245. # Relies on pkg_resources, not a hard dependency
  246. pass
  247. # add doctesting if required
  248. doctest_argv = '--with-doctest' in argv
  249. if doctests == False and doctest_argv:
  250. doctests = True
  251. plug = self._get_custom_doctester()
  252. if plug is None:
  253. # use standard doctesting
  254. if doctests and not doctest_argv:
  255. argv += ['--with-doctest']
  256. else: # custom doctesting
  257. if doctest_argv: # in fact the unplugger would take care of this
  258. argv.remove('--with-doctest')
  259. plugins += [Unplugger('doctest'), plug]
  260. if doctests:
  261. argv += ['--with-' + plug.name]
  262. return argv, plugins
  263. def test(self, label='fast', verbose=1, extra_argv=None,
  264. doctests=False, coverage=False, raise_warnings=None,
  265. timer=False):
  266. """
  267. Run tests for module using nose.
  268. Parameters
  269. ----------
  270. label : {'fast', 'full', '', attribute identifier}, optional
  271. Identifies the tests to run. This can be a string to pass to
  272. the nosetests executable with the '-A' option, or one of several
  273. special values. Special values are:
  274. * 'fast' - the default - which corresponds to the ``nosetests -A``
  275. option of 'not slow'.
  276. * 'full' - fast (as above) and slow tests as in the
  277. 'no -A' option to nosetests - this is the same as ''.
  278. * None or '' - run all tests.
  279. * attribute_identifier - string passed directly to nosetests as '-A'.
  280. verbose : int, optional
  281. Verbosity value for test outputs, in the range 1-10. Default is 1.
  282. extra_argv : list, optional
  283. List with any extra arguments to pass to nosetests.
  284. doctests : bool, optional
  285. If True, run doctests in module. Default is False.
  286. coverage : bool, optional
  287. If True, report coverage of NumPy code. Default is False.
  288. (This requires the
  289. `coverage module <https://pypi.org/project/coverage/>`_).
  290. raise_warnings : None, str or sequence of warnings, optional
  291. This specifies which warnings to configure as 'raise' instead
  292. of being shown once during the test execution. Valid strings are:
  293. * "develop" : equals ``(Warning,)``
  294. * "release" : equals ``()``, do not raise on any warnings.
  295. timer : bool or int, optional
  296. Timing of individual tests with ``nose-timer`` (which needs to be
  297. installed). If True, time tests and report on all of them.
  298. If an integer (say ``N``), report timing results for ``N`` slowest
  299. tests.
  300. Returns
  301. -------
  302. result : object
  303. Returns the result of running the tests as a
  304. ``nose.result.TextTestResult`` object.
  305. Notes
  306. -----
  307. Each NumPy module exposes `test` in its namespace to run all tests for it.
  308. For example, to run all tests for numpy.lib:
  309. >>> np.lib.test() #doctest: +SKIP
  310. Examples
  311. --------
  312. >>> result = np.lib.test() #doctest: +SKIP
  313. Running unit tests for numpy.lib
  314. ...
  315. Ran 976 tests in 3.933s
  316. OK
  317. >>> result.errors #doctest: +SKIP
  318. []
  319. >>> result.knownfail #doctest: +SKIP
  320. []
  321. """
  322. # cap verbosity at 3 because nose becomes *very* verbose beyond that
  323. verbose = min(verbose, 3)
  324. from . import utils
  325. utils.verbose = verbose
  326. argv, plugins = self.prepare_test_args(
  327. label, verbose, extra_argv, doctests, coverage, timer)
  328. if doctests:
  329. print(f'Running unit tests and doctests for {self.package_name}')
  330. else:
  331. print(f'Running unit tests for {self.package_name}')
  332. self._show_system_info()
  333. # reset doctest state on every run
  334. import doctest
  335. doctest.master = None
  336. if raise_warnings is None:
  337. raise_warnings = self.raise_warnings
  338. _warn_opts = dict(develop=(Warning,),
  339. release=())
  340. if isinstance(raise_warnings, str):
  341. raise_warnings = _warn_opts[raise_warnings]
  342. with suppress_warnings("location") as sup:
  343. # Reset the warning filters to the default state,
  344. # so that running the tests is more repeatable.
  345. warnings.resetwarnings()
  346. # Set all warnings to 'warn', this is because the default 'once'
  347. # has the bad property of possibly shadowing later warnings.
  348. warnings.filterwarnings('always')
  349. # Force the requested warnings to raise
  350. for warningtype in raise_warnings:
  351. warnings.filterwarnings('error', category=warningtype)
  352. # Filter out annoying import messages.
  353. sup.filter(message='Not importing directory')
  354. sup.filter(message="numpy.dtype size changed")
  355. sup.filter(message="numpy.ufunc size changed")
  356. sup.filter(category=np.ModuleDeprecationWarning)
  357. # Filter out boolean '-' deprecation messages. This allows
  358. # older versions of scipy to test without a flood of messages.
  359. sup.filter(message=".*boolean negative.*")
  360. sup.filter(message=".*boolean subtract.*")
  361. # Filter out distutils cpu warnings (could be localized to
  362. # distutils tests). ASV has problems with top level import,
  363. # so fetch module for suppression here.
  364. with warnings.catch_warnings():
  365. warnings.simplefilter("always")
  366. from ...distutils import cpuinfo
  367. sup.filter(category=UserWarning, module=cpuinfo)
  368. # Filter out some deprecation warnings inside nose 1.3.7 when run
  369. # on python 3.5b2. See
  370. # https://github.com/nose-devs/nose/issues/929
  371. # Note: it is hard to filter based on module for sup (lineno could
  372. # be implemented).
  373. warnings.filterwarnings("ignore", message=".*getargspec.*",
  374. category=DeprecationWarning,
  375. module=r"nose\.")
  376. from .noseclasses import NumpyTestProgram
  377. t = NumpyTestProgram(argv=argv, exit=False, plugins=plugins)
  378. return t.result
  379. def bench(self, label='fast', verbose=1, extra_argv=None):
  380. """
  381. Run benchmarks for module using nose.
  382. Parameters
  383. ----------
  384. label : {'fast', 'full', '', attribute identifier}, optional
  385. Identifies the benchmarks to run. This can be a string to pass to
  386. the nosetests executable with the '-A' option, or one of several
  387. special values. Special values are:
  388. * 'fast' - the default - which corresponds to the ``nosetests -A``
  389. option of 'not slow'.
  390. * 'full' - fast (as above) and slow benchmarks as in the
  391. 'no -A' option to nosetests - this is the same as ''.
  392. * None or '' - run all tests.
  393. * attribute_identifier - string passed directly to nosetests as '-A'.
  394. verbose : int, optional
  395. Verbosity value for benchmark outputs, in the range 1-10. Default is 1.
  396. extra_argv : list, optional
  397. List with any extra arguments to pass to nosetests.
  398. Returns
  399. -------
  400. success : bool
  401. Returns True if running the benchmarks works, False if an error
  402. occurred.
  403. Notes
  404. -----
  405. Benchmarks are like tests, but have names starting with "bench" instead
  406. of "test", and can be found under the "benchmarks" sub-directory of the
  407. module.
  408. Each NumPy module exposes `bench` in its namespace to run all benchmarks
  409. for it.
  410. Examples
  411. --------
  412. >>> success = np.lib.bench() #doctest: +SKIP
  413. Running benchmarks for numpy.lib
  414. ...
  415. using 562341 items:
  416. unique:
  417. 0.11
  418. unique1d:
  419. 0.11
  420. ratio: 1.0
  421. nUnique: 56230 == 56230
  422. ...
  423. OK
  424. >>> success #doctest: +SKIP
  425. True
  426. """
  427. print(f'Running benchmarks for {self.package_name}')
  428. self._show_system_info()
  429. argv = self._test_argv(label, verbose, extra_argv)
  430. argv += ['--match', r'(?:^|[\\b_\\.%s-])[Bb]ench' % os.sep]
  431. # import nose or make informative error
  432. nose = import_nose()
  433. # get plugin to disable doctests
  434. from .noseclasses import Unplugger
  435. add_plugins = [Unplugger('doctest')]
  436. return nose.run(argv=argv, addplugins=add_plugins)
  437. def _numpy_tester():
  438. if hasattr(np, "__version__") and ".dev0" in np.__version__:
  439. mode = "develop"
  440. else:
  441. mode = "release"
  442. return NoseTester(raise_warnings=mode, depth=1,
  443. check_fpu_mode=True)