summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMassimiliano Culpo <massimiliano.culpo@googlemail.com>2016-12-29 16:48:48 +0100
committerTodd Gamblin <tgamblin@llnl.gov>2016-12-29 07:48:48 -0800
commit7ea10e768ee1a7deab98ae538d916bbeeb0346b8 (patch)
tree0ece6f20cd24c6b6294135c59e1c3506c895bcab /lib
parent88f57d7543a6b08d6b0436cca9649e9dd1eb6b6d (diff)
downloadspack-7ea10e768ee1a7deab98ae538d916bbeeb0346b8.tar.gz
spack-7ea10e768ee1a7deab98ae538d916bbeeb0346b8.tar.bz2
spack-7ea10e768ee1a7deab98ae538d916bbeeb0346b8.tar.xz
spack-7ea10e768ee1a7deab98ae538d916bbeeb0346b8.zip
unit tests: replace nose with pytest (#2502)
* Porting: substitute nose with ytest This huge commit substitutes nose with pytest as a testing system. Things done here: * deleted external/nose as it is no longer used * moved mock resources in their own directory 'test/mock/' * ported two tests (cmd/find, build_system) to pytest native syntax as an example * build_environment, log: used monkeypatch instead of try/catch * moved global mocking of fetch_cache to an auto-used fixture * moved global mocking from test/__init__.py to conftest.py * made `spack test` a wrapper around pytest * run-unit-tests: avoid running python 2.6 tests under coverage to speed them up * use `pytest --cov` instead of coverage run to cut down testing time * mock/packages_test: moved mock yaml configuration to files instead of leaving it in the code as string literals * concretize.py: ported tests to native pytest, reverted multiprocessing in pytest.ini as it was creating the wrong report for coveralls * conftest.py, fixtures: added docstrings * concretize_preferences.py: uses fixtures instead of subclassing MockPackagesTest * directory_layout.py: uses fixtures instead of subclassing MockPackagesTest * install.py: uses fixtures instead of subclassing MockPackagesTest * optional_deps.py: uses fixtures instead of subclassing MockPackagesTest optional_deps.py: uses fixtures instead of subclassing MockPackagesTest * packages.py: uses fixtures instead of subclassing MockPackagesTest * provider_index.py: uses fixtures instead of subclassing MockPackagesTest * spec_yaml.py: uses fixtures instead of subclassing MockPackagesTest * multimethod.py: uses fixtures instead of subclassing MockPackagesTest * install.py: now uses mock_archive_url * git_fetch.py: uses fixtures instead of subclassing MockPackagesTest * hg_fetch.py: uses fixtures instead of subclassing MockPackagesTest * svn_fetch.py, mirror.py: uses fixtures instead of subclassing MockPackagesTest repo.py: deleted * test_compiler_cmd.py: uses fixtures instead of subclassing MockPackagesTest * cmd/module.py, cmd/uninstall.py: uses fixtures instead of subclassing MockDatabase * database.py: uses fixtures instead of subclassing MockDatabase, removed mock/database * pytest: uncluttering fixture implementations * database: changing the scope to 'module' * config.py: uses fixtures instead of subclassing MockPackagesTest * spec_dag.py, spec_semantics.py: uses fixtures instead of subclassing MockPackagesTest * stage.py: uses fixtures instead of subclassing MockPackagesTest. Removed mock directory * pytest: added docstrings to all the fixtures * pytest: final cleanup * build_system_guess.py: fixed naming and docstrings as suggested by @scheibelp * spec_syntax.py: added expected failure on parsing multiple specs closes #1976 * Add pytest and pytest-cov to Spack externals. * Make `spack flake8` ignore externals. * run-unit-tests runs spack test and not pytest. * Remove all the special stuff for `spack test` - Remove `conftest.py` magic and all the special case stuff in `bin/spack` - Spack commands can optionally take unknown arguments, if they want to handle them. - `spack test` is now a command like the others. - `spack test` now just delegates its arguments to `pytest`, but it does it by receiving unknown arguments and NOT taking an explicit help argument. * Fix error in fixtures. * Improve `spack test` command a bit. - Now supports an approximation of the old simple interface - Also supports full pytest options if you want them. * Use external coverage instead of pytest-cov * Make coverage use parallel-mode. * change __init__.py docs to include pytest
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/contribution_guide.rst2
-rw-r--r--lib/spack/external/__init__.py2
-rw-r--r--lib/spack/external/_pytest/AUTHORS141
-rw-r--r--lib/spack/external/_pytest/LICENSE21
-rw-r--r--lib/spack/external/_pytest/README.rst102
-rw-r--r--lib/spack/external/_pytest/__init__.py2
-rw-r--r--lib/spack/external/_pytest/_argcomplete.py102
-rw-r--r--lib/spack/external/_pytest/_code/__init__.py9
-rw-r--r--lib/spack/external/_pytest/_code/_py2traceback.py81
-rw-r--r--lib/spack/external/_pytest/_code/code.py861
-rw-r--r--lib/spack/external/_pytest/_code/source.py414
-rw-r--r--lib/spack/external/_pytest/_pluggy.py11
-rw-r--r--lib/spack/external/_pytest/assertion/__init__.py164
-rw-r--r--lib/spack/external/_pytest/assertion/rewrite.py945
-rw-r--r--lib/spack/external/_pytest/assertion/util.py300
-rw-r--r--lib/spack/external/_pytest/cacheprovider.py245
-rw-r--r--lib/spack/external/_pytest/capture.py491
-rw-r--r--lib/spack/external/_pytest/compat.py230
-rw-r--r--lib/spack/external/_pytest/config.py1340
-rw-r--r--lib/spack/external/_pytest/debugging.py124
-rw-r--r--lib/spack/external/_pytest/deprecated.py24
-rw-r--r--lib/spack/external/_pytest/doctest.py331
-rw-r--r--lib/spack/external/_pytest/fixtures.py1134
-rw-r--r--lib/spack/external/_pytest/freeze_support.py45
-rw-r--r--lib/spack/external/_pytest/helpconfig.py144
-rw-r--r--lib/spack/external/_pytest/hookspec.py314
-rw-r--r--lib/spack/external/_pytest/junitxml.py413
-rw-r--r--lib/spack/external/_pytest/main.py762
-rw-r--r--lib/spack/external/_pytest/mark.py328
-rw-r--r--lib/spack/external/_pytest/monkeypatch.py258
-rw-r--r--lib/spack/external/_pytest/nose.py71
-rw-r--r--lib/spack/external/_pytest/pastebin.py98
-rw-r--r--lib/spack/external/_pytest/pytester.py1139
-rw-r--r--lib/spack/external/_pytest/python.py1578
-rw-r--r--lib/spack/external/_pytest/recwarn.py226
-rw-r--r--lib/spack/external/_pytest/resultlog.py107
-rw-r--r--lib/spack/external/_pytest/runner.py578
-rw-r--r--lib/spack/external/_pytest/setuponly.py72
-rw-r--r--lib/spack/external/_pytest/setupplan.py23
-rw-r--r--lib/spack/external/_pytest/skipping.py375
-rw-r--r--lib/spack/external/_pytest/terminal.py593
-rw-r--r--lib/spack/external/_pytest/tmpdir.py124
-rw-r--r--lib/spack/external/_pytest/unittest.py217
-rw-r--r--lib/spack/external/_pytest/vendored_packages/README.md13
-rw-r--r--lib/spack/external/_pytest/vendored_packages/__init__.py0
-rw-r--r--lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst11
-rw-r--r--lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER1
-rw-r--r--lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt22
-rw-r--r--lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA40
-rw-r--r--lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD9
-rw-r--r--lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL6
-rw-r--r--lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json1
-rw-r--r--lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/top_level.txt1
-rw-r--r--lib/spack/external/_pytest/vendored_packages/pluggy.py802
-rw-r--r--lib/spack/external/nose/LICENSE502
-rw-r--r--lib/spack/external/nose/__init__.py15
-rw-r--r--lib/spack/external/nose/__main__.py8
-rw-r--r--lib/spack/external/nose/case.py397
-rw-r--r--lib/spack/external/nose/commands.py172
-rw-r--r--lib/spack/external/nose/config.py661
-rw-r--r--lib/spack/external/nose/core.py341
-rw-r--r--lib/spack/external/nose/exc.py9
-rw-r--r--lib/spack/external/nose/ext/__init__.py3
-rw-r--r--lib/spack/external/nose/ext/dtcompat.py2272
-rw-r--r--lib/spack/external/nose/failure.py42
-rw-r--r--lib/spack/external/nose/importer.py167
-rw-r--r--lib/spack/external/nose/inspector.py207
-rw-r--r--lib/spack/external/nose/loader.py623
-rw-r--r--lib/spack/external/nose/plugins/__init__.py190
-rw-r--r--lib/spack/external/nose/plugins/allmodules.py45
-rw-r--r--lib/spack/external/nose/plugins/attrib.py286
-rw-r--r--lib/spack/external/nose/plugins/base.py725
-rw-r--r--lib/spack/external/nose/plugins/builtin.py34
-rw-r--r--lib/spack/external/nose/plugins/capture.py115
-rw-r--r--lib/spack/external/nose/plugins/collect.py94
-rw-r--r--lib/spack/external/nose/plugins/cover.py271
-rw-r--r--lib/spack/external/nose/plugins/debug.py67
-rw-r--r--lib/spack/external/nose/plugins/deprecated.py45
-rw-r--r--lib/spack/external/nose/plugins/doctests.py455
-rw-r--r--lib/spack/external/nose/plugins/errorclass.py210
-rw-r--r--lib/spack/external/nose/plugins/failuredetail.py49
-rw-r--r--lib/spack/external/nose/plugins/isolate.py103
-rw-r--r--lib/spack/external/nose/plugins/logcapture.py245
-rw-r--r--lib/spack/external/nose/plugins/manager.py460
-rw-r--r--lib/spack/external/nose/plugins/multiprocess.py835
-rw-r--r--lib/spack/external/nose/plugins/plugintest.py416
-rw-r--r--lib/spack/external/nose/plugins/prof.py154
-rw-r--r--lib/spack/external/nose/plugins/skip.py63
-rw-r--r--lib/spack/external/nose/plugins/testid.py311
-rw-r--r--lib/spack/external/nose/plugins/xunit.py341
-rw-r--r--lib/spack/external/nose/proxy.py188
-rw-r--r--lib/spack/external/nose/pyversion.py215
-rw-r--r--lib/spack/external/nose/result.py200
-rw-r--r--lib/spack/external/nose/selector.py251
-rw-r--r--lib/spack/external/nose/sphinx/__init__.py1
-rw-r--r--lib/spack/external/nose/sphinx/pluginopts.py189
-rw-r--r--lib/spack/external/nose/suite.py609
-rw-r--r--lib/spack/external/nose/tools/__init__.py15
-rw-r--r--lib/spack/external/nose/tools/nontrivial.py151
-rw-r--r--lib/spack/external/nose/tools/trivial.py54
-rw-r--r--lib/spack/external/nose/twistedtools.py173
-rw-r--r--lib/spack/external/nose/usage.txt115
-rw-r--r--lib/spack/external/nose/util.py668
-rwxr-xr-xlib/spack/external/pyqver2.py6
-rw-r--r--lib/spack/external/pytest.py28
-rw-r--r--lib/spack/llnl/util/lang.py12
-rw-r--r--lib/spack/llnl/util/tty/log.py5
-rw-r--r--lib/spack/spack/__init__.py6
-rw-r--r--lib/spack/spack/build_environment.py9
-rw-r--r--lib/spack/spack/cmd/__init__.py10
-rw-r--r--lib/spack/spack/cmd/flake8.py60
-rw-r--r--lib/spack/spack/cmd/test.py123
-rw-r--r--lib/spack/spack/repository.py2
-rw-r--r--lib/spack/spack/test/__init__.py129
-rw-r--r--lib/spack/spack/test/architecture.py223
-rw-r--r--lib/spack/spack/test/build_system_guess.py87
-rw-r--r--lib/spack/spack/test/cmd/find.py51
-rw-r--r--lib/spack/spack/test/cmd/module.py111
-rw-r--r--lib/spack/spack/test/cmd/test_compiler_cmd.py90
-rw-r--r--lib/spack/spack/test/cmd/uninstall.py47
-rw-r--r--lib/spack/spack/test/concretize.py351
-rw-r--r--lib/spack/spack/test/concretize_preferences.py133
-rw-r--r--lib/spack/spack/test/config.py261
-rw-r--r--lib/spack/spack/test/conftest.py515
-rw-r--r--lib/spack/spack/test/data/compilers.yaml116
-rw-r--r--lib/spack/spack/test/data/config.yaml11
-rw-r--r--lib/spack/spack/test/data/packages.yaml14
-rw-r--r--lib/spack/spack/test/database.py547
-rw-r--r--lib/spack/spack/test/directory_layout.py316
-rw-r--r--lib/spack/spack/test/git_fetch.py138
-rw-r--r--lib/spack/spack/test/hg_fetch.py124
-rw-r--r--lib/spack/spack/test/install.py142
-rw-r--r--lib/spack/spack/test/mirror.py230
-rw-r--r--lib/spack/spack/test/mock_database.py108
-rw-r--r--lib/spack/spack/test/mock_packages_test.py281
-rw-r--r--lib/spack/spack/test/mock_repo.py202
-rw-r--r--lib/spack/spack/test/modules.py446
-rw-r--r--lib/spack/spack/test/multimethod.py134
-rw-r--r--lib/spack/spack/test/optional_deps.py166
-rw-r--r--lib/spack/spack/test/packages.py185
-rw-r--r--lib/spack/spack/test/provider_index.py76
-rw-r--r--lib/spack/spack/test/spec_dag.py267
-rw-r--r--lib/spack/spack/test/spec_semantics.py526
-rw-r--r--lib/spack/spack/test/spec_syntax.py144
-rw-r--r--lib/spack/spack/test/spec_yaml.py272
-rw-r--r--lib/spack/spack/test/stage.py605
-rw-r--r--lib/spack/spack/test/svn_fetch.py135
-rw-r--r--lib/spack/spack/test/tally_plugin.py64
-rw-r--r--lib/spack/spack/test/url_extrapolate.py7
149 files changed, 19159 insertions, 17565 deletions
diff --git a/lib/spack/docs/contribution_guide.rst b/lib/spack/docs/contribution_guide.rst
index 49595fecf8..4abf97ef92 100644
--- a/lib/spack/docs/contribution_guide.rst
+++ b/lib/spack/docs/contribution_guide.rst
@@ -75,7 +75,7 @@ This allows you to develop iteratively: make a change, test that change, make
another change, test that change, etc. To get a list of all available unit
tests, run:
-.. command-output:: spack test --list
+.. command-output:: spack test --collect-only
Unit tests are crucial to making sure bugs aren't introduced into Spack. If you
are modifying core Spack libraries or adding new functionality, please consider
diff --git a/lib/spack/external/__init__.py b/lib/spack/external/__init__.py
index 88d39a7654..49886ae595 100644
--- a/lib/spack/external/__init__.py
+++ b/lib/spack/external/__init__.py
@@ -35,7 +35,7 @@ So far:
jsonschema: An implementation of JSON Schema for Python.
- nose: The nose testing framework.
+ pytest: Testing framework used by Spack.
ordereddict: We include our own version to be Python 2.6 compatible.
diff --git a/lib/spack/external/_pytest/AUTHORS b/lib/spack/external/_pytest/AUTHORS
new file mode 100644
index 0000000000..8c7cb19cee
--- /dev/null
+++ b/lib/spack/external/_pytest/AUTHORS
@@ -0,0 +1,141 @@
+Holger Krekel, holger at merlinux eu
+merlinux GmbH, Germany, office at merlinux eu
+
+Contributors include::
+
+Abdeali JK
+Abhijeet Kasurde
+Ahn Ki-Wook
+Alexei Kozlenok
+Anatoly Bubenkoff
+Andreas Zeidler
+Andrzej Ostrowski
+Andy Freeland
+Anthon van der Neut
+Antony Lee
+Armin Rigo
+Aron Curzon
+Aviv Palivoda
+Ben Webb
+Benjamin Peterson
+Bernard Pratz
+Bob Ippolito
+Brian Dorsey
+Brian Okken
+Brianna Laugher
+Bruno Oliveira
+Cal Leeming
+Carl Friedrich Bolz
+Charles Cloud
+Charnjit SiNGH (CCSJ)
+Chris Lamb
+Christian Boelsen
+Christian Theunert
+Christian Tismer
+Christopher Gilling
+Daniel Grana
+Daniel Hahler
+Daniel Nuri
+Daniel Wandschneider
+Danielle Jenkins
+Dave Hunt
+David Díaz-Barquero
+David Mohr
+David Vierra
+Diego Russo
+Dmitry Dygalo
+Duncan Betts
+Edison Gustavo Muenz
+Edoardo Batini
+Eduardo Schettino
+Elizaveta Shashkova
+Endre Galaczi
+Eric Hunsberger
+Eric Siegerman
+Erik M. Bray
+Feng Ma
+Florian Bruhin
+Floris Bruynooghe
+Gabriel Reis
+Georgy Dyuldin
+Graham Horler
+Greg Price
+Grig Gheorghiu
+Grigorii Eremeev (budulianin)
+Guido Wesdorp
+Harald Armin Massa
+Ian Bicking
+Jaap Broekhuizen
+Jan Balster
+Janne Vanhala
+Jason R. Coombs
+Javier Domingo Cansino
+Javier Romero
+John Towler
+Jon Sonesen
+Jordan Guymon
+Joshua Bronson
+Jurko Gospodnetić
+Justyna Janczyszyn
+Kale Kundert
+Katarzyna Jachim
+Kevin Cox
+Lee Kamentsky
+Lev Maximov
+Lukas Bednar
+Luke Murphy
+Maciek Fijalkowski
+Maho
+Marc Schlaich
+Marcin Bachry
+Mark Abramowitz
+Markus Unterwaditzer
+Martijn Faassen
+Martin K. Scherer
+Martin Prusse
+Mathieu Clabaut
+Matt Bachmann
+Matt Williams
+Matthias Hafner
+mbyt
+Michael Aquilina
+Michael Birtwell
+Michael Droettboom
+Michael Seifert
+Mike Lundy
+Ned Batchelder
+Neven Mundar
+Nicolas Delaby
+Oleg Pidsadnyi
+Oliver Bestwalter
+Omar Kohl
+Pieter Mulder
+Piotr Banaszkiewicz
+Punyashloka Biswal
+Quentin Pradet
+Ralf Schmitt
+Raphael Pierzina
+Raquel Alegre
+Roberto Polli
+Romain Dorgueil
+Roman Bolshakov
+Ronny Pfannschmidt
+Ross Lawley
+Russel Winder
+Ryan Wooden
+Samuele Pedroni
+Simon Gomizelj
+Stefan Farmbauer
+Stefan Zimmermann
+Stefano Taschini
+Steffen Allner
+Stephan Obermann
+Tareq Alayan
+Ted Xiao
+Thomas Grainger
+Tom Viner
+Trevor Bekolay
+Tyler Goodlet
+Vasily Kuznetsov
+Wouter van Ackooy
+Xuecong Liao
diff --git a/lib/spack/external/_pytest/LICENSE b/lib/spack/external/_pytest/LICENSE
new file mode 100644
index 0000000000..9e27bd7841
--- /dev/null
+++ b/lib/spack/external/_pytest/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2004-2016 Holger Krekel and others
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/lib/spack/external/_pytest/README.rst b/lib/spack/external/_pytest/README.rst
new file mode 100644
index 0000000000..d5650af655
--- /dev/null
+++ b/lib/spack/external/_pytest/README.rst
@@ -0,0 +1,102 @@
+.. image:: http://docs.pytest.org/en/latest/_static/pytest1.png
+ :target: http://docs.pytest.org
+ :align: center
+ :alt: pytest
+
+------
+
+.. image:: https://img.shields.io/pypi/v/pytest.svg
+ :target: https://pypi.python.org/pypi/pytest
+.. image:: https://img.shields.io/pypi/pyversions/pytest.svg
+ :target: https://pypi.python.org/pypi/pytest
+.. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg
+ :target: https://coveralls.io/r/pytest-dev/pytest
+.. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master
+ :target: https://travis-ci.org/pytest-dev/pytest
+.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
+ :target: https://ci.appveyor.com/project/pytestbot/pytest
+
+The ``pytest`` framework makes it easy to write small tests, yet
+scales to support complex functional testing for applications and libraries.
+
+An example of a simple test:
+
+.. code-block:: python
+
+ # content of test_sample.py
+ def inc(x):
+ return x + 1
+
+ def test_answer():
+ assert inc(3) == 5
+
+
+To execute it::
+
+ $ pytest
+ ============================= test session starts =============================
+ collected 1 items
+
+ test_sample.py F
+
+ ================================== FAILURES ===================================
+ _________________________________ test_answer _________________________________
+
+ def test_answer():
+ > assert inc(3) == 5
+ E assert 4 == 5
+ E + where 4 = inc(3)
+
+ test_sample.py:5: AssertionError
+ ========================== 1 failed in 0.04 seconds ===========================
+
+
+Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <http://docs.pytest.org/en/latest/getting-started.html#our-first-test-run>`_ for more examples.
+
+
+Features
+--------
+
+- Detailed info on failing `assert statements <http://docs.pytest.org/en/latest/assert.html>`_ (no need to remember ``self.assert*`` names);
+
+- `Auto-discovery
+ <http://docs.pytest.org/en/latest/goodpractices.html#python-test-discovery>`_
+ of test modules and functions;
+
+- `Modular fixtures <http://docs.pytest.org/en/latest/fixture.html>`_ for
+ managing small or parametrized long-lived test resources;
+
+- Can run `unittest <http://docs.pytest.org/en/latest/unittest.html>`_ (or trial),
+ `nose <http://docs.pytest.org/en/latest/nose.html>`_ test suites out of the box;
+
+- Python2.6+, Python3.3+, PyPy-2.3, Jython-2.5 (untested);
+
+- Rich plugin architecture, with over 150+ `external plugins <http://docs.pytest.org/en/latest/plugins.html#installing-external-plugins-searching>`_ and thriving community;
+
+
+Documentation
+-------------
+
+For full documentation, including installation, tutorials and PDF documents, please see http://docs.pytest.org.
+
+
+Bugs/Requests
+-------------
+
+Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issues>`_ to submit bugs or request features.
+
+
+Changelog
+---------
+
+Consult the `Changelog <http://docs.pytest.org/en/latest/changelog.html>`__ page for fixes and enhancements of each version.
+
+
+License
+-------
+
+Copyright Holger Krekel and others, 2004-2016.
+
+Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
+
+.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE
diff --git a/lib/spack/external/_pytest/__init__.py b/lib/spack/external/_pytest/__init__.py
new file mode 100644
index 0000000000..be20d3d41c
--- /dev/null
+++ b/lib/spack/external/_pytest/__init__.py
@@ -0,0 +1,2 @@
+#
+__version__ = '3.0.5'
diff --git a/lib/spack/external/_pytest/_argcomplete.py b/lib/spack/external/_pytest/_argcomplete.py
new file mode 100644
index 0000000000..3ab679d8be
--- /dev/null
+++ b/lib/spack/external/_pytest/_argcomplete.py
@@ -0,0 +1,102 @@
+
+"""allow bash-completion for argparse with argcomplete if installed
+needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
+to find the magic string, so _ARGCOMPLETE env. var is never set, and
+this does not need special code.
+
+argcomplete does not support python 2.5 (although the changes for that
+are minor).
+
+Function try_argcomplete(parser) should be called directly before
+the call to ArgumentParser.parse_args().
+
+The filescompleter is what you normally would use on the positional
+arguments specification, in order to get "dirname/" after "dirn<TAB>"
+instead of the default "dirname ":
+
+ optparser.add_argument(Config._file_or_dir, nargs='*'
+ ).completer=filescompleter
+
+Other, application specific, completers should go in the file
+doing the add_argument calls as they need to be specified as .completer
+attributes as well. (If argcomplete is not installed, the function the
+attribute points to will not be used).
+
+SPEEDUP
+=======
+The generic argcomplete script for bash-completion
+(/etc/bash_completion.d/python-argcomplete.sh )
+uses a python program to determine startup script generated by pip.
+You can speed up completion somewhat by changing this script to include
+ # PYTHON_ARGCOMPLETE_OK
+so the the python-argcomplete-check-easy-install-script does not
+need to be called to find the entry point of the code and see if that is
+marked with PYTHON_ARGCOMPLETE_OK
+
+INSTALL/DEBUGGING
+=================
+To include this support in another application that has setup.py generated
+scripts:
+- add the line:
+ # PYTHON_ARGCOMPLETE_OK
+ near the top of the main python entry point
+- include in the file calling parse_args():
+ from _argcomplete import try_argcomplete, filescompleter
+ , call try_argcomplete just before parse_args(), and optionally add
+ filescompleter to the positional arguments' add_argument()
+If things do not work right away:
+- switch on argcomplete debugging with (also helpful when doing custom
+ completers):
+ export _ARC_DEBUG=1
+- run:
+ python-argcomplete-check-easy-install-script $(which appname)
+ echo $?
+ will echo 0 if the magic line has been found, 1 if not
+- sometimes it helps to find early on errors using:
+ _ARGCOMPLETE=1 _ARC_DEBUG=1 appname
+ which should throw a KeyError: 'COMPLINE' (which is properly set by the
+ global argcomplete script).
+"""
+
+import sys
+import os
+from glob import glob
+
+class FastFilesCompleter:
+ 'Fast file completer class'
+ def __init__(self, directories=True):
+ self.directories = directories
+
+ def __call__(self, prefix, **kwargs):
+ """only called on non option completions"""
+ if os.path.sep in prefix[1:]: #
+ prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
+ else:
+ prefix_dir = 0
+ completion = []
+ globbed = []
+ if '*' not in prefix and '?' not in prefix:
+ if prefix[-1] == os.path.sep: # we are on unix, otherwise no bash
+ globbed.extend(glob(prefix + '.*'))
+ prefix += '*'
+ globbed.extend(glob(prefix))
+ for x in sorted(globbed):
+ if os.path.isdir(x):
+ x += '/'
+ # append stripping the prefix (like bash, not like compgen)
+ completion.append(x[prefix_dir:])
+ return completion
+
+
+if os.environ.get('_ARGCOMPLETE'):
+ try:
+ import argcomplete.completers
+ except ImportError:
+ sys.exit(-1)
+ filescompleter = FastFilesCompleter()
+
+ def try_argcomplete(parser):
+ argcomplete.autocomplete(parser)
+else:
+ def try_argcomplete(parser): pass
+ filescompleter = None
diff --git a/lib/spack/external/_pytest/_code/__init__.py b/lib/spack/external/_pytest/_code/__init__.py
new file mode 100644
index 0000000000..3463c11eac
--- /dev/null
+++ b/lib/spack/external/_pytest/_code/__init__.py
@@ -0,0 +1,9 @@
+""" python inspection/code generation API """
+from .code import Code # noqa
+from .code import ExceptionInfo # noqa
+from .code import Frame # noqa
+from .code import Traceback # noqa
+from .code import getrawcode # noqa
+from .source import Source # noqa
+from .source import compile_ as compile # noqa
+from .source import getfslineno # noqa
diff --git a/lib/spack/external/_pytest/_code/_py2traceback.py b/lib/spack/external/_pytest/_code/_py2traceback.py
new file mode 100644
index 0000000000..a830d9899a
--- /dev/null
+++ b/lib/spack/external/_pytest/_code/_py2traceback.py
@@ -0,0 +1,81 @@
+# copied from python-2.7.3's traceback.py
+# CHANGES:
+# - some_str is replaced, trying to create unicode strings
+#
+import types
+
+def format_exception_only(etype, value):
+ """Format the exception part of a traceback.
+
+ The arguments are the exception type and value such as given by
+ sys.last_type and sys.last_value. The return value is a list of
+ strings, each ending in a newline.
+
+ Normally, the list contains a single string; however, for
+ SyntaxError exceptions, it contains several lines that (when
+ printed) display detailed information about where the syntax
+ error occurred.
+
+ The message indicating which exception occurred is always the last
+ string in the list.
+
+ """
+
+ # An instance should not have a meaningful value parameter, but
+ # sometimes does, particularly for string exceptions, such as
+ # >>> raise string1, string2 # deprecated
+ #
+ # Clear these out first because issubtype(string1, SyntaxError)
+ # would throw another exception and mask the original problem.
+ if (isinstance(etype, BaseException) or
+ isinstance(etype, types.InstanceType) or
+ etype is None or type(etype) is str):
+ return [_format_final_exc_line(etype, value)]
+
+ stype = etype.__name__
+
+ if not issubclass(etype, SyntaxError):
+ return [_format_final_exc_line(stype, value)]
+
+ # It was a syntax error; show exactly where the problem was found.
+ lines = []
+ try:
+ msg, (filename, lineno, offset, badline) = value.args
+ except Exception:
+ pass
+ else:
+ filename = filename or "<string>"
+ lines.append(' File "%s", line %d\n' % (filename, lineno))
+ if badline is not None:
+ if isinstance(badline, bytes): # python 2 only
+ badline = badline.decode('utf-8', 'replace')
+ lines.append(u' %s\n' % badline.strip())
+ if offset is not None:
+ caretspace = badline.rstrip('\n')[:offset].lstrip()
+ # non-space whitespace (likes tabs) must be kept for alignment
+ caretspace = ((c.isspace() and c or ' ') for c in caretspace)
+ # only three spaces to account for offset1 == pos 0
+ lines.append(' %s^\n' % ''.join(caretspace))
+ value = msg
+
+ lines.append(_format_final_exc_line(stype, value))
+ return lines
+
+def _format_final_exc_line(etype, value):
+ """Return a list of a single line -- normal case for format_exception_only"""
+ valuestr = _some_str(value)
+ if value is None or not valuestr:
+ line = "%s\n" % etype
+ else:
+ line = "%s: %s\n" % (etype, valuestr)
+ return line
+
+def _some_str(value):
+ try:
+ return unicode(value)
+ except Exception:
+ try:
+ return str(value)
+ except Exception:
+ pass
+ return '<unprintable %s object>' % type(value).__name__
diff --git a/lib/spack/external/_pytest/_code/code.py b/lib/spack/external/_pytest/_code/code.py
new file mode 100644
index 0000000000..616d5c4313
--- /dev/null
+++ b/lib/spack/external/_pytest/_code/code.py
@@ -0,0 +1,861 @@
+import sys
+from inspect import CO_VARARGS, CO_VARKEYWORDS
+import re
+from weakref import ref
+
+import py
+builtin_repr = repr
+
+reprlib = py.builtin._tryimport('repr', 'reprlib')
+
+if sys.version_info[0] >= 3:
+ from traceback import format_exception_only
+else:
+ from ._py2traceback import format_exception_only
+
+
+class Code(object):
+ """ wrapper around Python code objects """
+ def __init__(self, rawcode):
+ if not hasattr(rawcode, "co_filename"):
+ rawcode = getrawcode(rawcode)
+ try:
+ self.filename = rawcode.co_filename
+ self.firstlineno = rawcode.co_firstlineno - 1
+ self.name = rawcode.co_name
+ except AttributeError:
+ raise TypeError("not a code object: %r" %(rawcode,))
+ self.raw = rawcode
+
+ def __eq__(self, other):
+ return self.raw == other.raw
+
+ __hash__ = None
+
+ def __ne__(self, other):
+ return not self == other
+
+ @property
+ def path(self):
+ """ return a path object pointing to source code (note that it
+ might not point to an actually existing file). """
+ try:
+ p = py.path.local(self.raw.co_filename)
+ # maybe don't try this checking
+ if not p.check():
+ raise OSError("py.path check failed.")
+ except OSError:
+ # XXX maybe try harder like the weird logic
+ # in the standard lib [linecache.updatecache] does?
+ p = self.raw.co_filename
+
+ return p
+
+ @property
+ def fullsource(self):
+ """ return a _pytest._code.Source object for the full source file of the code
+ """
+ from _pytest._code import source
+ full, _ = source.findsource(self.raw)
+ return full
+
+ def source(self):
+ """ return a _pytest._code.Source object for the code object's source only
+ """
+ # return source only for that part of code
+ import _pytest._code
+ return _pytest._code.Source(self.raw)
+
+ def getargs(self, var=False):
+ """ return a tuple with the argument names for the code object
+
+ if 'var' is set True also return the names of the variable and
+ keyword arguments when present
+ """
+ # handfull shortcut for getting args
+ raw = self.raw
+ argcount = raw.co_argcount
+ if var:
+ argcount += raw.co_flags & CO_VARARGS
+ argcount += raw.co_flags & CO_VARKEYWORDS
+ return raw.co_varnames[:argcount]
+
+class Frame(object):
+ """Wrapper around a Python frame holding f_locals and f_globals
+ in which expressions can be evaluated."""
+
+ def __init__(self, frame):
+ self.lineno = frame.f_lineno - 1
+ self.f_globals = frame.f_globals
+ self.f_locals = frame.f_locals
+ self.raw = frame
+ self.code = Code(frame.f_code)
+
+ @property
+ def statement(self):
+ """ statement this frame is at """
+ import _pytest._code
+ if self.code.fullsource is None:
+ return _pytest._code.Source("")
+ return self.code.fullsource.getstatement(self.lineno)
+
+ def eval(self, code, **vars):
+ """ evaluate 'code' in the frame
+
+ 'vars' are optional additional local variables
+
+ returns the result of the evaluation
+ """
+ f_locals = self.f_locals.copy()
+ f_locals.update(vars)
+ return eval(code, self.f_globals, f_locals)
+
+ def exec_(self, code, **vars):
+ """ exec 'code' in the frame
+
+ 'vars' are optiona; additional local variables
+ """
+ f_locals = self.f_locals.copy()
+ f_locals.update(vars)
+ py.builtin.exec_(code, self.f_globals, f_locals )
+
+ def repr(self, object):
+ """ return a 'safe' (non-recursive, one-line) string repr for 'object'
+ """
+ return py.io.saferepr(object)
+
+ def is_true(self, object):
+ return object
+
+ def getargs(self, var=False):
+ """ return a list of tuples (name, value) for all arguments
+
+ if 'var' is set True also include the variable and keyword
+ arguments when present
+ """
+ retval = []
+ for arg in self.code.getargs(var):
+ try:
+ retval.append((arg, self.f_locals[arg]))
+ except KeyError:
+ pass # this can occur when using Psyco
+ return retval
+
+class TracebackEntry(object):
+ """ a single entry in a traceback """
+
+ _repr_style = None
+ exprinfo = None
+
+ def __init__(self, rawentry, excinfo=None):
+ self._excinfo = excinfo
+ self._rawentry = rawentry
+ self.lineno = rawentry.tb_lineno - 1
+
+ def set_repr_style(self, mode):
+ assert mode in ("short", "long")
+ self._repr_style = mode
+
+ @property
+ def frame(self):
+ import _pytest._code
+ return _pytest._code.Frame(self._rawentry.tb_frame)
+
+ @property
+ def relline(self):
+ return self.lineno - self.frame.code.firstlineno
+
+ def __repr__(self):
+ return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
+
+ @property
+ def statement(self):
+ """ _pytest._code.Source object for the current statement """
+ source = self.frame.code.fullsource
+ return source.getstatement(self.lineno)
+
+ @property
+ def path(self):
+ """ path to the source code """
+ return self.frame.code.path
+
+ def getlocals(self):
+ return self.frame.f_locals
+ locals = property(getlocals, None, None, "locals of underlaying frame")
+
+ def getfirstlinesource(self):
+ # on Jython this firstlineno can be -1 apparently
+ return max(self.frame.code.firstlineno, 0)
+
+ def getsource(self, astcache=None):
+ """ return failing source code. """
+ # we use the passed in astcache to not reparse asttrees
+ # within exception info printing
+ from _pytest._code.source import getstatementrange_ast
+ source = self.frame.code.fullsource
+ if source is None:
+ return None
+ key = astnode = None
+ if astcache is not None:
+ key = self.frame.code.path
+ if key is not None:
+ astnode = astcache.get(key, None)
+ start = self.getfirstlinesource()
+ try:
+ astnode, _, end = getstatementrange_ast(self.lineno, source,
+ astnode=astnode)
+ except SyntaxError:
+ end = self.lineno + 1
+ else:
+ if key is not None:
+ astcache[key] = astnode
+ return source[start:end]
+
+ source = property(getsource)
+
+ def ishidden(self):
+ """ return True if the current frame has a var __tracebackhide__
+ resolving to True
+
+ If __tracebackhide__ is a callable, it gets called with the
+ ExceptionInfo instance and can decide whether to hide the traceback.
+
+ mostly for internal use
+ """
+ try:
+ tbh = self.frame.f_locals['__tracebackhide__']
+ except KeyError:
+ try:
+ tbh = self.frame.f_globals['__tracebackhide__']
+ except KeyError:
+ return False
+
+ if py.builtin.callable(tbh):
+ return tbh(None if self._excinfo is None else self._excinfo())
+ else:
+ return tbh
+
+ def __str__(self):
+ try:
+ fn = str(self.path)
+ except py.error.Error:
+ fn = '???'
+ name = self.frame.code.name
+ try:
+ line = str(self.statement).lstrip()
+ except KeyboardInterrupt:
+ raise
+ except:
+ line = "???"
+ return " File %r:%d in %s\n %s\n" %(fn, self.lineno+1, name, line)
+
+ def name(self):
+ return self.frame.code.raw.co_name
+ name = property(name, None, None, "co_name of underlaying code")
+
+class Traceback(list):
+ """ Traceback objects encapsulate and offer higher level
+ access to Traceback entries.
+ """
+ Entry = TracebackEntry
+ def __init__(self, tb, excinfo=None):
+ """ initialize from given python traceback object and ExceptionInfo """
+ self._excinfo = excinfo
+ if hasattr(tb, 'tb_next'):
+ def f(cur):
+ while cur is not None:
+ yield self.Entry(cur, excinfo=excinfo)
+ cur = cur.tb_next
+ list.__init__(self, f(tb))
+ else:
+ list.__init__(self, tb)
+
+ def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
+ """ return a Traceback instance wrapping part of this Traceback
+
+ by provding any combination of path, lineno and firstlineno, the
+ first frame to start the to-be-returned traceback is determined
+
+ this allows cutting the first part of a Traceback instance e.g.
+ for formatting reasons (removing some uninteresting bits that deal
+ with handling of the exception/traceback)
+ """
+ for x in self:
+ code = x.frame.code
+ codepath = code.path
+ if ((path is None or codepath == path) and
+ (excludepath is None or not hasattr(codepath, 'relto') or
+ not codepath.relto(excludepath)) and
+ (lineno is None or x.lineno == lineno) and
+ (firstlineno is None or x.frame.code.firstlineno == firstlineno)):
+ return Traceback(x._rawentry, self._excinfo)
+ return self
+
+ def __getitem__(self, key):
+ val = super(Traceback, self).__getitem__(key)
+ if isinstance(key, type(slice(0))):
+ val = self.__class__(val)
+ return val
+
+ def filter(self, fn=lambda x: not x.ishidden()):
+ """ return a Traceback instance with certain items removed
+
+ fn is a function that gets a single argument, a TracebackEntry
+ instance, and should return True when the item should be added
+ to the Traceback, False when not
+
+ by default this removes all the TracebackEntries which are hidden
+ (see ishidden() above)
+ """
+ return Traceback(filter(fn, self), self._excinfo)
+
+ def getcrashentry(self):
+ """ return last non-hidden traceback entry that lead
+ to the exception of a traceback.
+ """
+ for i in range(-1, -len(self)-1, -1):
+ entry = self[i]
+ if not entry.ishidden():
+ return entry
+ return self[-1]
+
+ def recursionindex(self):
+ """ return the index of the frame/TracebackEntry where recursion
+ originates if appropriate, None if no recursion occurred
+ """
+ cache = {}
+ for i, entry in enumerate(self):
+ # id for the code.raw is needed to work around
+ # the strange metaprogramming in the decorator lib from pypi
+ # which generates code objects that have hash/value equality
+ #XXX needs a test
+ key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
+ #print "checking for recursion at", key
+ l = cache.setdefault(key, [])
+ if l:
+ f = entry.frame
+ loc = f.f_locals
+ for otherloc in l:
+ if f.is_true(f.eval(co_equal,
+ __recursioncache_locals_1=loc,
+ __recursioncache_locals_2=otherloc)):
+ return i
+ l.append(entry.frame.f_locals)
+ return None
+
+
+co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
+ '?', 'eval')
+
+class ExceptionInfo(object):
+ """ wraps sys.exc_info() objects and offers
+ help for navigating the traceback.
+ """
+ _striptext = ''
+ def __init__(self, tup=None, exprinfo=None):
+ import _pytest._code
+ if tup is None:
+ tup = sys.exc_info()
+ if exprinfo is None and isinstance(tup[1], AssertionError):
+ exprinfo = getattr(tup[1], 'msg', None)
+ if exprinfo is None:
+ exprinfo = py._builtin._totext(tup[1])
+ if exprinfo and exprinfo.startswith('assert '):
+ self._striptext = 'AssertionError: '
+ self._excinfo = tup
+ #: the exception class
+ self.type = tup[0]
+ #: the exception instance
+ self.value = tup[1]
+ #: the exception raw traceback
+ self.tb = tup[2]
+ #: the exception type name
+ self.typename = self.type.__name__
+ #: the exception traceback (_pytest._code.Traceback instance)
+ self.traceback = _pytest._code.Traceback(self.tb, excinfo=ref(self))
+
+ def __repr__(self):
+ return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
+
+ def exconly(self, tryshort=False):
+ """ return the exception as a string
+
+ when 'tryshort' resolves to True, and the exception is a
+ _pytest._code._AssertionError, only the actual exception part of
+ the exception representation is returned (so 'AssertionError: ' is
+ removed from the beginning)
+ """
+ lines = format_exception_only(self.type, self.value)
+ text = ''.join(lines)
+ text = text.rstrip()
+ if tryshort:
+ if text.startswith(self._striptext):
+ text = text[len(self._striptext):]
+ return text
+
+ def errisinstance(self, exc):
+ """ return True if the exception is an instance of exc """
+ return isinstance(self.value, exc)
+
+ def _getreprcrash(self):
+ exconly = self.exconly(tryshort=True)
+ entry = self.traceback.getcrashentry()
+ path, lineno = entry.frame.code.raw.co_filename, entry.lineno
+ return ReprFileLocation(path, lineno+1, exconly)
+
+ def getrepr(self, showlocals=False, style="long",
+ abspath=False, tbfilter=True, funcargs=False):
+ """ return str()able representation of this exception info.
+ showlocals: show locals per traceback entry
+ style: long|short|no|native traceback style
+ tbfilter: hide entries (where __tracebackhide__ is true)
+
+ in case of style==native, tbfilter and showlocals is ignored.
+ """
+ if style == 'native':
+ return ReprExceptionInfo(ReprTracebackNative(
+ py.std.traceback.format_exception(
+ self.type,
+ self.value,
+ self.traceback[0]._rawentry,
+ )), self._getreprcrash())
+
+ fmt = FormattedExcinfo(showlocals=showlocals, style=style,
+ abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
+ return fmt.repr_excinfo(self)
+
+ def __str__(self):
+ entry = self.traceback[-1]
+ loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
+ return str(loc)
+
+ def __unicode__(self):
+ entry = self.traceback[-1]
+ loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
+ return unicode(loc)
+
+ def match(self, regexp):
+ """
+ Match the regular expression 'regexp' on the string representation of
+ the exception. If it matches then True is returned (so that it is
+ possible to write 'assert excinfo.match()'). If it doesn't match an
+ AssertionError is raised.
+ """
+ __tracebackhide__ = True
+ if not re.search(regexp, str(self.value)):
+ assert 0, "Pattern '{0!s}' not found in '{1!s}'".format(
+ regexp, self.value)
+ return True
+
+
+class FormattedExcinfo(object):
+ """ presenting information about failing Functions and Generators. """
+ # for traceback entries
+ flow_marker = ">"
+ fail_marker = "E"
+
+ def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
+ self.showlocals = showlocals
+ self.style = style
+ self.tbfilter = tbfilter
+ self.funcargs = funcargs
+ self.abspath = abspath
+ self.astcache = {}
+
+ def _getindent(self, source):
+ # figure out indent for given source
+ try:
+ s = str(source.getstatement(len(source)-1))
+ except KeyboardInterrupt:
+ raise
+ except:
+ try:
+ s = str(source[-1])
+ except KeyboardInterrupt:
+ raise
+ except:
+ return 0
+ return 4 + (len(s) - len(s.lstrip()))
+
+ def _getentrysource(self, entry):
+ source = entry.getsource(self.astcache)
+ if source is not None:
+ source = source.deindent()
+ return source
+
+ def _saferepr(self, obj):
+ return py.io.saferepr(obj)
+
+ def repr_args(self, entry):
+ if self.funcargs:
+ args = []
+ for argname, argvalue in entry.frame.getargs(var=True):
+ args.append((argname, self._saferepr(argvalue)))
+ return ReprFuncArgs(args)
+
+ def get_source(self, source, line_index=-1, excinfo=None, short=False):
+ """ return formatted and marked up source lines. """
+ import _pytest._code
+ lines = []
+ if source is None or line_index >= len(source.lines):
+ source = _pytest._code.Source("???")
+ line_index = 0
+ if line_index < 0:
+ line_index += len(source)
+ space_prefix = " "
+ if short:
+ lines.append(space_prefix + source.lines[line_index].strip())
+ else:
+ for line in source.lines[:line_index]:
+ lines.append(space_prefix + line)
+ lines.append(self.flow_marker + " " + source.lines[line_index])
+ for line in source.lines[line_index+1:]:
+ lines.append(space_prefix + line)
+ if excinfo is not None:
+ indent = 4 if short else self._getindent(source)
+ lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
+ return lines
+
+ def get_exconly(self, excinfo, indent=4, markall=False):
+ lines = []
+ indent = " " * indent
+ # get the real exception information out
+ exlines = excinfo.exconly(tryshort=True).split('\n')
+ failindent = self.fail_marker + indent[1:]
+ for line in exlines:
+ lines.append(failindent + line)
+ if not markall:
+ failindent = indent
+ return lines
+
+ def repr_locals(self, locals):
+ if self.showlocals:
+ lines = []
+ keys = [loc for loc in locals if loc[0] != "@"]
+ keys.sort()
+ for name in keys:
+ value = locals[name]
+ if name == '__builtins__':
+ lines.append("__builtins__ = <builtins>")
+ else:
+ # This formatting could all be handled by the
+ # _repr() function, which is only reprlib.Repr in
+ # disguise, so is very configurable.
+ str_repr = self._saferepr(value)
+ #if len(str_repr) < 70 or not isinstance(value,
+ # (list, tuple, dict)):
+ lines.append("%-10s = %s" %(name, str_repr))
+ #else:
+ # self._line("%-10s =\\" % (name,))
+ # # XXX
+ # py.std.pprint.pprint(value, stream=self.excinfowriter)
+ return ReprLocals(lines)
+
+ def repr_traceback_entry(self, entry, excinfo=None):
+ import _pytest._code
+ source = self._getentrysource(entry)
+ if source is None:
+ source = _pytest._code.Source("???")
+ line_index = 0
+ else:
+ # entry.getfirstlinesource() can be -1, should be 0 on jython
+ line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
+
+ lines = []
+ style = entry._repr_style
+ if style is None:
+ style = self.style
+ if style in ("short", "long"):
+ short = style == "short"
+ reprargs = self.repr_args(entry) if not short else None
+ s = self.get_source(source, line_index, excinfo, short=short)
+ lines.extend(s)
+ if short:
+ message = "in %s" %(entry.name)
+ else:
+ message = excinfo and excinfo.typename or ""
+ path = self._makepath(entry.path)
+ filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
+ localsrepr = None
+ if not short:
+ localsrepr = self.repr_locals(entry.locals)
+ return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
+ if excinfo:
+ lines.extend(self.get_exconly(excinfo, indent=4))
+ return ReprEntry(lines, None, None, None, style)
+
+ def _makepath(self, path):
+ if not self.abspath:
+ try:
+ np = py.path.local().bestrelpath(path)
+ except OSError:
+ return path
+ if len(np) < len(str(path)):
+ path = np
+ return path
+
+ def repr_traceback(self, excinfo):
+ traceback = excinfo.traceback
+ if self.tbfilter:
+ traceback = traceback.filter()
+ recursionindex = None
+ if is_recursion_error(excinfo):
+ recursionindex = traceback.recursionindex()
+ last = traceback[-1]
+ entries = []
+ extraline = None
+ for index, entry in enumerate(traceback):
+ einfo = (last == entry) and excinfo or None
+ reprentry = self.repr_traceback_entry(entry, einfo)
+ entries.append(reprentry)
+ if index == recursionindex:
+ extraline = "!!! Recursion detected (same locals & position)"
+ break
+ return ReprTraceback(entries, extraline, style=self.style)
+
+
+ def repr_excinfo(self, excinfo):
+ if sys.version_info[0] < 3:
+ reprtraceback = self.repr_traceback(excinfo)
+ reprcrash = excinfo._getreprcrash()
+
+ return ReprExceptionInfo(reprtraceback, reprcrash)
+ else:
+ repr_chain = []
+ e = excinfo.value
+ descr = None
+ while e is not None:
+ if excinfo:
+ reprtraceback = self.repr_traceback(excinfo)
+ reprcrash = excinfo._getreprcrash()
+ else:
+ # fallback to native repr if the exception doesn't have a traceback:
+ # ExceptionInfo objects require a full traceback to work
+ reprtraceback = ReprTracebackNative(py.std.traceback.format_exception(type(e), e, None))
+ reprcrash = None
+
+ repr_chain += [(reprtraceback, reprcrash, descr)]
+ if e.__cause__ is not None:
+ e = e.__cause__
+ excinfo = ExceptionInfo((type(e), e, e.__traceback__)) if e.__traceback__ else None
+ descr = 'The above exception was the direct cause of the following exception:'
+ elif e.__context__ is not None:
+ e = e.__context__
+ excinfo = ExceptionInfo((type(e), e, e.__traceback__)) if e.__traceback__ else None
+ descr = 'During handling of the above exception, another exception occurred:'
+ else:
+ e = None
+ repr_chain.reverse()
+ return ExceptionChainRepr(repr_chain)
+
+
+class TerminalRepr(object):
+ def __str__(self):
+ s = self.__unicode__()
+ if sys.version_info[0] < 3:
+ s = s.encode('utf-8')
+ return s
+
+ def __unicode__(self):
+ # FYI this is called from pytest-xdist's serialization of exception
+ # information.
+ io = py.io.TextIO()
+ tw = py.io.TerminalWriter(file=io)
+ self.toterminal(tw)
+ return io.getvalue().strip()
+
+ def __repr__(self):
+ return "<%s instance at %0x>" %(self.__class__, id(self))
+
+
+class ExceptionRepr(TerminalRepr):
+ def __init__(self):
+ self.sections = []
+
+ def addsection(self, name, content, sep="-"):
+ self.sections.append((name, content, sep))
+
+ def toterminal(self, tw):
+ for name, content, sep in self.sections:
+ tw.sep(sep, name)
+ tw.line(content)
+
+
+class ExceptionChainRepr(ExceptionRepr):
+ def __init__(self, chain):
+ super(ExceptionChainRepr, self).__init__()
+ self.chain = chain
+ # reprcrash and reprtraceback of the outermost (the newest) exception
+ # in the chain
+ self.reprtraceback = chain[-1][0]
+ self.reprcrash = chain[-1][1]
+
+ def toterminal(self, tw):
+ for element in self.chain:
+ element[0].toterminal(tw)
+ if element[2] is not None:
+ tw.line("")
+ tw.line(element[2], yellow=True)
+ super(ExceptionChainRepr, self).toterminal(tw)
+
+
+class ReprExceptionInfo(ExceptionRepr):
+ def __init__(self, reprtraceback, reprcrash):
+ super(ReprExceptionInfo, self).__init__()
+ self.reprtraceback = reprtraceback
+ self.reprcrash = reprcrash
+
+ def toterminal(self, tw):
+ self.reprtraceback.toterminal(tw)
+ super(ReprExceptionInfo, self).toterminal(tw)
+
+class ReprTraceback(TerminalRepr):
+ entrysep = "_ "
+
+ def __init__(self, reprentries, extraline, style):
+ self.reprentries = reprentries
+ self.extraline = extraline
+ self.style = style
+
+ def toterminal(self, tw):
+ # the entries might have different styles
+ for i, entry in enumerate(self.reprentries):
+ if entry.style == "long":
+ tw.line("")
+ entry.toterminal(tw)
+ if i < len(self.reprentries) - 1:
+ next_entry = self.reprentries[i+1]
+ if entry.style == "long" or \
+ entry.style == "short" and next_entry.style == "long":
+ tw.sep(self.entrysep)
+
+ if self.extraline:
+ tw.line(self.extraline)
+
+class ReprTracebackNative(ReprTraceback):
+ def __init__(self, tblines):
+ self.style = "native"
+ self.reprentries = [ReprEntryNative(tblines)]
+ self.extraline = None
+
+class ReprEntryNative(TerminalRepr):
+ style = "native"
+
+ def __init__(self, tblines):
+ self.lines = tblines
+
+ def toterminal(self, tw):
+ tw.write("".join(self.lines))
+
+class ReprEntry(TerminalRepr):
+ localssep = "_ "
+
+ def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
+ self.lines = lines
+ self.reprfuncargs = reprfuncargs
+ self.reprlocals = reprlocals
+ self.reprfileloc = filelocrepr
+ self.style = style
+
+ def toterminal(self, tw):
+ if self.style == "short":
+ self.reprfileloc.toterminal(tw)
+ for line in self.lines:
+ red = line.startswith("E ")
+ tw.line(line, bold=True, red=red)
+ #tw.line("")
+ return
+ if self.reprfuncargs:
+ self.reprfuncargs.toterminal(tw)
+ for line in self.lines:
+ red = line.startswith("E ")
+ tw.line(line, bold=True, red=red)
+ if self.reprlocals:
+ #tw.sep(self.localssep, "Locals")
+ tw.line("")
+ self.reprlocals.toterminal(tw)
+ if self.reprfileloc:
+ if self.lines:
+ tw.line("")
+ self.reprfileloc.toterminal(tw)
+
+ def __str__(self):
+ return "%s\n%s\n%s" % ("\n".join(self.lines),
+ self.reprlocals,
+ self.reprfileloc)
+
+class ReprFileLocation(TerminalRepr):
+ def __init__(self, path, lineno, message):
+ self.path = str(path)
+ self.lineno = lineno
+ self.message = message
+
+ def toterminal(self, tw):
+ # filename and lineno output for each entry,
+ # using an output format that most editors unterstand
+ msg = self.message
+ i = msg.find("\n")
+ if i != -1:
+ msg = msg[:i]
+ tw.write(self.path, bold=True, red=True)
+ tw.line(":%s: %s" % (self.lineno, msg))
+
+class ReprLocals(TerminalRepr):
+ def __init__(self, lines):
+ self.lines = lines
+
+ def toterminal(self, tw):
+ for line in self.lines:
+ tw.line(line)
+
+class ReprFuncArgs(TerminalRepr):
+ def __init__(self, args):
+ self.args = args
+
+ def toterminal(self, tw):
+ if self.args:
+ linesofar = ""
+ for name, value in self.args:
+ ns = "%s = %s" %(name, value)
+ if len(ns) + len(linesofar) + 2 > tw.fullwidth:
+ if linesofar:
+ tw.line(linesofar)
+ linesofar = ns
+ else:
+ if linesofar:
+ linesofar += ", " + ns
+ else:
+ linesofar = ns
+ if linesofar:
+ tw.line(linesofar)
+ tw.line("")
+
+
+def getrawcode(obj, trycall=True):
+ """ return code object for given function. """
+ try:
+ return obj.__code__
+ except AttributeError:
+ obj = getattr(obj, 'im_func', obj)
+ obj = getattr(obj, 'func_code', obj)
+ obj = getattr(obj, 'f_code', obj)
+ obj = getattr(obj, '__code__', obj)
+ if trycall and not hasattr(obj, 'co_firstlineno'):
+ if hasattr(obj, '__call__') and not py.std.inspect.isclass(obj):
+ x = getrawcode(obj.__call__, trycall=False)
+ if hasattr(x, 'co_firstlineno'):
+ return x
+ return obj
+
+
+if sys.version_info[:2] >= (3, 5): # RecursionError introduced in 3.5
+ def is_recursion_error(excinfo):
+ return excinfo.errisinstance(RecursionError) # noqa
+else:
+ def is_recursion_error(excinfo):
+ if not excinfo.errisinstance(RuntimeError):
+ return False
+ try:
+ return "maximum recursion depth exceeded" in str(excinfo.value)
+ except UnicodeError:
+ return False
diff --git a/lib/spack/external/_pytest/_code/source.py b/lib/spack/external/_pytest/_code/source.py
new file mode 100644
index 0000000000..fcec0f5ca7
--- /dev/null
+++ b/lib/spack/external/_pytest/_code/source.py
@@ -0,0 +1,414 @@
+from __future__ import generators
+
+from bisect import bisect_right
+import sys
+import inspect, tokenize
+import py
+cpy_compile = compile
+
+try:
+ import _ast
+ from _ast import PyCF_ONLY_AST as _AST_FLAG
+except ImportError:
+ _AST_FLAG = 0
+ _ast = None
+
+
+class Source(object):
+ """ a immutable object holding a source code fragment,
+ possibly deindenting it.
+ """
+ _compilecounter = 0
+ def __init__(self, *parts, **kwargs):
+ self.lines = lines = []
+ de = kwargs.get('deindent', True)
+ rstrip = kwargs.get('rstrip', True)
+ for part in parts:
+ if not part:
+ partlines = []
+ if isinstance(part, Source):
+ partlines = part.lines
+ elif isinstance(part, (tuple, list)):
+ partlines = [x.rstrip("\n") for x in part]
+ elif isinstance(part, py.builtin._basestring):
+ partlines = part.split('\n')
+ if rstrip:
+ while partlines:
+ if partlines[-1].strip():
+ break
+ partlines.pop()
+ else:
+ partlines = getsource(part, deindent=de).lines
+ if de:
+ partlines = deindent(partlines)
+ lines.extend(partlines)
+
+ def __eq__(self, other):
+ try:
+ return self.lines == other.lines
+ except AttributeError:
+ if isinstance(other, str):
+ return str(self) == other
+ return False
+
+ __hash__ = None
+
+ def __getitem__(self, key):
+ if isinstance(key, int):
+ return self.lines[key]
+ else:
+ if key.step not in (None, 1):
+ raise IndexError("cannot slice a Source with a step")
+ newsource = Source()
+ newsource.lines = self.lines[key.start:key.stop]
+ return newsource
+
+ def __len__(self):
+ return len(self.lines)
+
+ def strip(self):
+ """ return new source object with trailing
+ and leading blank lines removed.
+ """
+ start, end = 0, len(self)
+ while start < end and not self.lines[start].strip():
+ start += 1
+ while end > start and not self.lines[end-1].strip():
+ end -= 1
+ source = Source()
+ source.lines[:] = self.lines[start:end]
+ return source
+
+ def putaround(self, before='', after='', indent=' ' * 4):
+ """ return a copy of the source object with
+ 'before' and 'after' wrapped around it.
+ """
+ before = Source(before)
+ after = Source(after)
+ newsource = Source()
+ lines = [ (indent + line) for line in self.lines]
+ newsource.lines = before.lines + lines + after.lines
+ return newsource
+
+ def indent(self, indent=' ' * 4):
+ """ return a copy of the source object with
+ all lines indented by the given indent-string.
+ """
+ newsource = Source()
+ newsource.lines = [(indent+line) for line in self.lines]
+ return newsource
+
+ def getstatement(self, lineno, assertion=False):
+ """ return Source statement which contains the
+ given linenumber (counted from 0).
+ """
+ start, end = self.getstatementrange(lineno, assertion)
+ return self[start:end]
+
+ def getstatementrange(self, lineno, assertion=False):
+ """ return (start, end) tuple which spans the minimal
+ statement region which containing the given lineno.
+ """
+ if not (0 <= lineno < len(self)):
+ raise IndexError("lineno out of range")
+ ast, start, end = getstatementrange_ast(lineno, self)
+ return start, end
+
+ def deindent(self, offset=None):
+ """ return a new source object deindented by offset.
+ If offset is None then guess an indentation offset from
+ the first non-blank line. Subsequent lines which have a
+ lower indentation offset will be copied verbatim as
+ they are assumed to be part of multilines.
+ """
+ # XXX maybe use the tokenizer to properly handle multiline
+ # strings etc.pp?
+ newsource = Source()
+ newsource.lines[:] = deindent(self.lines, offset)
+ return newsource
+
+ def isparseable(self, deindent=True):
+ """ return True if source is parseable, heuristically
+ deindenting it by default.
+ """
+ try:
+ import parser
+ except ImportError:
+ syntax_checker = lambda x: compile(x, 'asd', 'exec')
+ else:
+ syntax_checker = parser.suite
+
+ if deindent:
+ source = str(self.deindent())
+ else:
+ source = str(self)
+ try:
+ #compile(source+'\n', "x", "exec")
+ syntax_checker(source+'\n')
+ except KeyboardInterrupt:
+ raise
+ except Exception:
+ return False
+ else:
+ return True
+
+ def __str__(self):
+ return "\n".join(self.lines)
+
+ def compile(self, filename=None, mode='exec',
+ flag=generators.compiler_flag,
+ dont_inherit=0, _genframe=None):
+ """ return compiled code object. if filename is None
+ invent an artificial filename which displays
+ the source/line position of the caller frame.
+ """
+ if not filename or py.path.local(filename).check(file=0):
+ if _genframe is None:
+ _genframe = sys._getframe(1) # the caller
+ fn,lineno = _genframe.f_code.co_filename, _genframe.f_lineno
+ base = "<%d-codegen " % self._compilecounter
+ self.__class__._compilecounter += 1
+ if not filename:
+ filename = base + '%s:%d>' % (fn, lineno)
+ else:
+ filename = base + '%r %s:%d>' % (filename, fn, lineno)
+ source = "\n".join(self.lines) + '\n'
+ try:
+ co = cpy_compile(source, filename, mode, flag)
+ except SyntaxError:
+ ex = sys.exc_info()[1]
+ # re-represent syntax errors from parsing python strings
+ msglines = self.lines[:ex.lineno]
+ if ex.offset:
+ msglines.append(" "*ex.offset + '^')
+ msglines.append("(code was compiled probably from here: %s)" % filename)
+ newex = SyntaxError('\n'.join(msglines))
+ newex.offset = ex.offset
+ newex.lineno = ex.lineno
+ newex.text = ex.text
+ raise newex
+ else:
+ if flag & _AST_FLAG:
+ return co
+ lines = [(x + "\n") for x in self.lines]
+ py.std.linecache.cache[filename] = (1, None, lines, filename)
+ return co
+
+#
+# public API shortcut functions
+#
+
+def compile_(source, filename=None, mode='exec', flags=
+ generators.compiler_flag, dont_inherit=0):
+ """ compile the given source to a raw code object,
+ and maintain an internal cache which allows later
+ retrieval of the source code for the code object
+ and any recursively created code objects.
+ """
+ if _ast is not None and isinstance(source, _ast.AST):
+ # XXX should Source support having AST?
+ return cpy_compile(source, filename, mode, flags, dont_inherit)
+ _genframe = sys._getframe(1) # the caller
+ s = Source(source)
+ co = s.compile(filename, mode, flags, _genframe=_genframe)
+ return co
+
+
+def getfslineno(obj):
+ """ Return source location (path, lineno) for the given object.
+ If the source cannot be determined return ("", -1)
+ """
+ import _pytest._code
+ try:
+ code = _pytest._code.Code(obj)
+ except TypeError:
+ try:
+ fn = (py.std.inspect.getsourcefile(obj) or
+ py.std.inspect.getfile(obj))
+ except TypeError:
+ return "", -1
+
+ fspath = fn and py.path.local(fn) or None
+ lineno = -1
+ if fspath:
+ try:
+ _, lineno = findsource(obj)
+ except IOError:
+ pass
+ else:
+ fspath = code.path
+ lineno = code.firstlineno
+ assert isinstance(lineno, int)
+ return fspath, lineno
+
+#
+# helper functions
+#
+
+def findsource(obj):
+ try:
+ sourcelines, lineno = py.std.inspect.findsource(obj)
+ except py.builtin._sysex:
+ raise
+ except:
+ return None, -1
+ source = Source()
+ source.lines = [line.rstrip() for line in sourcelines]
+ return source, lineno
+
+
+def getsource(obj, **kwargs):
+ import _pytest._code
+ obj = _pytest._code.getrawcode(obj)
+ try:
+ strsrc = inspect.getsource(obj)
+ except IndentationError:
+ strsrc = "\"Buggy python version consider upgrading, cannot get source\""
+ assert isinstance(strsrc, str)
+ return Source(strsrc, **kwargs)
+
+
+def deindent(lines, offset=None):
+ if offset is None:
+ for line in lines:
+ line = line.expandtabs()
+ s = line.lstrip()
+ if s:
+ offset = len(line)-len(s)
+ break
+ else:
+ offset = 0
+ if offset == 0:
+ return list(lines)
+ newlines = []
+
+ def readline_generator(lines):
+ for line in lines:
+ yield line + '\n'
+ while True:
+ yield ''
+
+ it = readline_generator(lines)
+
+ try:
+ for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(lambda: next(it)):
+ if sline > len(lines):
+ break # End of input reached
+ if sline > len(newlines):
+ line = lines[sline - 1].expandtabs()
+ if line.lstrip() and line[:offset].isspace():
+ line = line[offset:] # Deindent
+ newlines.append(line)
+
+ for i in range(sline, eline):
+ # Don't deindent continuing lines of
+ # multiline tokens (i.e. multiline strings)
+ newlines.append(lines[i])
+ except (IndentationError, tokenize.TokenError):
+ pass
+ # Add any lines we didn't see. E.g. if an exception was raised.
+ newlines.extend(lines[len(newlines):])
+ return newlines
+
+
+def get_statement_startend2(lineno, node):
+ import ast
+ # flatten all statements and except handlers into one lineno-list
+ # AST's line numbers start indexing at 1
+ l = []
+ for x in ast.walk(node):
+ if isinstance(x, _ast.stmt) or isinstance(x, _ast.ExceptHandler):
+ l.append(x.lineno - 1)
+ for name in "finalbody", "orelse":
+ val = getattr(x, name, None)
+ if val:
+ # treat the finally/orelse part as its own statement
+ l.append(val[0].lineno - 1 - 1)
+ l.sort()
+ insert_index = bisect_right(l, lineno)
+ start = l[insert_index - 1]
+ if insert_index >= len(l):
+ end = None
+ else:
+ end = l[insert_index]
+ return start, end
+
+
+def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
+ if astnode is None:
+ content = str(source)
+ if sys.version_info < (2,7):
+ content += "\n"
+ try:
+ astnode = compile(content, "source", "exec", 1024) # 1024 for AST
+ except ValueError:
+ start, end = getstatementrange_old(lineno, source, assertion)
+ return None, start, end
+ start, end = get_statement_startend2(lineno, astnode)
+ # we need to correct the end:
+ # - ast-parsing strips comments
+ # - there might be empty lines
+ # - we might have lesser indented code blocks at the end
+ if end is None:
+ end = len(source.lines)
+
+ if end > start + 1:
+ # make sure we don't span differently indented code blocks
+ # by using the BlockFinder helper used which inspect.getsource() uses itself
+ block_finder = inspect.BlockFinder()
+ # if we start with an indented line, put blockfinder to "started" mode
+ block_finder.started = source.lines[start][0].isspace()
+ it = ((x + "\n") for x in source.lines[start:end])
+ try:
+ for tok in tokenize.generate_tokens(lambda: next(it)):
+ block_finder.tokeneater(*tok)
+ except (inspect.EndOfBlock, IndentationError):
+ end = block_finder.last + start
+ except Exception:
+ pass
+
+ # the end might still point to a comment or empty line, correct it
+ while end:
+ line = source.lines[end - 1].lstrip()
+ if line.startswith("#") or not line:
+ end -= 1
+ else:
+ break
+ return astnode, start, end
+
+
+def getstatementrange_old(lineno, source, assertion=False):
+ """ return (start, end) tuple which spans the minimal
+ statement region which containing the given lineno.
+ raise an IndexError if no such statementrange can be found.
+ """
+ # XXX this logic is only used on python2.4 and below
+ # 1. find the start of the statement
+ from codeop import compile_command
+ for start in range(lineno, -1, -1):
+ if assertion:
+ line = source.lines[start]
+ # the following lines are not fully tested, change with care
+ if 'super' in line and 'self' in line and '__init__' in line:
+ raise IndexError("likely a subclass")
+ if "assert" not in line and "raise" not in line:
+ continue
+ trylines = source.lines[start:lineno+1]
+ # quick hack to prepare parsing an indented line with
+ # compile_command() (which errors on "return" outside defs)
+ trylines.insert(0, 'def xxx():')
+ trysource = '\n '.join(trylines)
+ # ^ space here
+ try:
+ compile_command(trysource)
+ except (SyntaxError, OverflowError, ValueError):
+ continue
+
+ # 2. find the end of the statement
+ for end in range(lineno+1, len(source)+1):
+ trysource = source[start:end]
+ if trysource.isparseable():
+ return start, end
+ raise SyntaxError("no valid source range around line %d " % (lineno,))
+
+
diff --git a/lib/spack/external/_pytest/_pluggy.py b/lib/spack/external/_pytest/_pluggy.py
new file mode 100644
index 0000000000..87d32cf8dd
--- /dev/null
+++ b/lib/spack/external/_pytest/_pluggy.py
@@ -0,0 +1,11 @@
+"""
+imports symbols from vendored "pluggy" if available, otherwise
+falls back to importing "pluggy" from the default namespace.
+"""
+
+try:
+ from _pytest.vendored_packages.pluggy import * # noqa
+ from _pytest.vendored_packages.pluggy import __version__ # noqa
+except ImportError:
+ from pluggy import * # noqa
+ from pluggy import __version__ # noqa
diff --git a/lib/spack/external/_pytest/assertion/__init__.py b/lib/spack/external/_pytest/assertion/__init__.py
new file mode 100644
index 0000000000..3f14a7ae76
--- /dev/null
+++ b/lib/spack/external/_pytest/assertion/__init__.py
@@ -0,0 +1,164 @@
+"""
+support for presenting detailed information in failing assertions.
+"""
+import py
+import os
+import sys
+
+from _pytest.assertion import util
+from _pytest.assertion import rewrite
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("debugconfig")
+ group.addoption('--assert',
+ action="store",
+ dest="assertmode",
+ choices=("rewrite", "plain",),
+ default="rewrite",
+ metavar="MODE",
+ help="""Control assertion debugging tools. 'plain'
+ performs no assertion debugging. 'rewrite'
+ (the default) rewrites assert statements in
+ test modules on import to provide assert
+ expression information.""")
+
+
+def pytest_namespace():
+ return {'register_assert_rewrite': register_assert_rewrite}
+
+
+def register_assert_rewrite(*names):
+ """Register one or more module names to be rewritten on import.
+
+ This function will make sure that this module or all modules inside
+ the package will get their assert statements rewritten.
+ Thus you should make sure to call this before the module is
+ actually imported, usually in your __init__.py if you are a plugin
+ using a package.
+
+ :raise TypeError: if the given module names are not strings.
+ """
+ for name in names:
+ if not isinstance(name, str):
+ msg = 'expected module names as *args, got {0} instead'
+ raise TypeError(msg.format(repr(names)))
+ for hook in sys.meta_path:
+ if isinstance(hook, rewrite.AssertionRewritingHook):
+ importhook = hook
+ break
+ else:
+ importhook = DummyRewriteHook()
+ importhook.mark_rewrite(*names)
+
+
+class DummyRewriteHook(object):
+ """A no-op import hook for when rewriting is disabled."""
+
+ def mark_rewrite(self, *names):
+ pass
+
+
+class AssertionState:
+ """State for the assertion plugin."""
+
+ def __init__(self, config, mode):
+ self.mode = mode
+ self.trace = config.trace.root.get("assertion")
+ self.hook = None
+
+
+def install_importhook(config):
+ """Try to install the rewrite hook, raise SystemError if it fails."""
+ # Both Jython and CPython 2.6.0 have AST bugs that make the
+ # assertion rewriting hook malfunction.
+ if (sys.platform.startswith('java') or
+ sys.version_info[:3] == (2, 6, 0)):
+ raise SystemError('rewrite not supported')
+
+ config._assertstate = AssertionState(config, 'rewrite')
+ config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config)
+ sys.meta_path.insert(0, hook)
+ config._assertstate.trace('installed rewrite import hook')
+
+ def undo():
+ hook = config._assertstate.hook
+ if hook is not None and hook in sys.meta_path:
+ sys.meta_path.remove(hook)
+
+ config.add_cleanup(undo)
+ return hook
+
+
+def pytest_collection(session):
+ # this hook is only called when test modules are collected
+ # so for example not in the master process of pytest-xdist
+ # (which does not collect test modules)
+ assertstate = getattr(session.config, '_assertstate', None)
+ if assertstate:
+ if assertstate.hook is not None:
+ assertstate.hook.set_session(session)
+
+
+def _running_on_ci():
+ """Check if we're currently running on a CI system."""
+ env_vars = ['CI', 'BUILD_NUMBER']
+ return any(var in os.environ for var in env_vars)
+
+
+def pytest_runtest_setup(item):
+ """Setup the pytest_assertrepr_compare hook
+
+ The newinterpret and rewrite modules will use util._reprcompare if
+ it exists to use custom reporting via the
+ pytest_assertrepr_compare hook. This sets up this custom
+ comparison for the test.
+ """
+ def callbinrepr(op, left, right):
+ """Call the pytest_assertrepr_compare hook and prepare the result
+
+ This uses the first result from the hook and then ensures the
+ following:
+ * Overly verbose explanations are dropped unless -vv was used or
+ running on a CI.
+ * Embedded newlines are escaped to help util.format_explanation()
+ later.
+ * If the rewrite mode is used embedded %-characters are replaced
+ to protect later % formatting.
+
+ The result can be formatted by util.format_explanation() for
+ pretty printing.
+ """
+ hook_result = item.ihook.pytest_assertrepr_compare(
+ config=item.config, op=op, left=left, right=right)
+ for new_expl in hook_result:
+ if new_expl:
+ if (sum(len(p) for p in new_expl[1:]) > 80*8 and
+ item.config.option.verbose < 2 and
+ not _running_on_ci()):
+ show_max = 10
+ truncated_lines = len(new_expl) - show_max
+ new_expl[show_max:] = [py.builtin._totext(
+ 'Detailed information truncated (%d more lines)'
+ ', use "-vv" to show' % truncated_lines)]
+ new_expl = [line.replace("\n", "\\n") for line in new_expl]
+ res = py.builtin._totext("\n~").join(new_expl)
+ if item.config.getvalue("assertmode") == "rewrite":
+ res = res.replace("%", "%%")
+ return res
+ util._reprcompare = callbinrepr
+
+
+def pytest_runtest_teardown(item):
+ util._reprcompare = None
+
+
+def pytest_sessionfinish(session):
+ assertstate = getattr(session.config, '_assertstate', None)
+ if assertstate:
+ if assertstate.hook is not None:
+ assertstate.hook.set_session(None)
+
+
+# Expose this plugin's implementation for the pytest_assertrepr_compare hook
+pytest_assertrepr_compare = util.assertrepr_compare
diff --git a/lib/spack/external/_pytest/assertion/rewrite.py b/lib/spack/external/_pytest/assertion/rewrite.py
new file mode 100644
index 0000000000..abf5b491fe
--- /dev/null
+++ b/lib/spack/external/_pytest/assertion/rewrite.py
@@ -0,0 +1,945 @@
+"""Rewrite assertion AST to produce nice error messages"""
+
+import ast
+import _ast
+import errno
+import itertools
+import imp
+import marshal
+import os
+import re
+import struct
+import sys
+import types
+from fnmatch import fnmatch
+
+import py
+from _pytest.assertion import util
+
+
+# pytest caches rewritten pycs in __pycache__.
+if hasattr(imp, "get_tag"):
+ PYTEST_TAG = imp.get_tag() + "-PYTEST"
+else:
+ if hasattr(sys, "pypy_version_info"):
+ impl = "pypy"
+ elif sys.platform == "java":
+ impl = "jython"
+ else:
+ impl = "cpython"
+ ver = sys.version_info
+ PYTEST_TAG = "%s-%s%s-PYTEST" % (impl, ver[0], ver[1])
+ del ver, impl
+
+PYC_EXT = ".py" + (__debug__ and "c" or "o")
+PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
+
+REWRITE_NEWLINES = sys.version_info[:2] != (2, 7) and sys.version_info < (3, 2)
+ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3
+
+if sys.version_info >= (3,5):
+ ast_Call = ast.Call
+else:
+ ast_Call = lambda a,b,c: ast.Call(a, b, c, None, None)
+
+
+class AssertionRewritingHook(object):
+ """PEP302 Import hook which rewrites asserts."""
+
+ def __init__(self, config):
+ self.config = config
+ self.fnpats = config.getini("python_files")
+ self.session = None
+ self.modules = {}
+ self._rewritten_names = set()
+ self._register_with_pkg_resources()
+ self._must_rewrite = set()
+
+ def set_session(self, session):
+ self.session = session
+
+ def find_module(self, name, path=None):
+ state = self.config._assertstate
+ state.trace("find_module called for: %s" % name)
+ names = name.rsplit(".", 1)
+ lastname = names[-1]
+ pth = None
+ if path is not None:
+ # Starting with Python 3.3, path is a _NamespacePath(), which
+ # causes problems if not converted to list.
+ path = list(path)
+ if len(path) == 1:
+ pth = path[0]
+ if pth is None:
+ try:
+ fd, fn, desc = imp.find_module(lastname, path)
+ except ImportError:
+ return None
+ if fd is not None:
+ fd.close()
+ tp = desc[2]
+ if tp == imp.PY_COMPILED:
+ if hasattr(imp, "source_from_cache"):
+ try:
+ fn = imp.source_from_cache(fn)
+ except ValueError:
+ # Python 3 doesn't like orphaned but still-importable
+ # .pyc files.
+ fn = fn[:-1]
+ else:
+ fn = fn[:-1]
+ elif tp != imp.PY_SOURCE:
+ # Don't know what this is.
+ return None
+ else:
+ fn = os.path.join(pth, name.rpartition(".")[2] + ".py")
+
+ fn_pypath = py.path.local(fn)
+ if not self._should_rewrite(name, fn_pypath, state):
+ return None
+
+ self._rewritten_names.add(name)
+
+ # The requested module looks like a test file, so rewrite it. This is
+ # the most magical part of the process: load the source, rewrite the
+ # asserts, and load the rewritten source. We also cache the rewritten
+ # module code in a special pyc. We must be aware of the possibility of
+ # concurrent pytest processes rewriting and loading pycs. To avoid
+ # tricky race conditions, we maintain the following invariant: The
+ # cached pyc is always a complete, valid pyc. Operations on it must be
+ # atomic. POSIX's atomic rename comes in handy.
+ write = not sys.dont_write_bytecode
+ cache_dir = os.path.join(fn_pypath.dirname, "__pycache__")
+ if write:
+ try:
+ os.mkdir(cache_dir)
+ except OSError:
+ e = sys.exc_info()[1].errno
+ if e == errno.EEXIST:
+ # Either the __pycache__ directory already exists (the
+ # common case) or it's blocked by a non-dir node. In the
+ # latter case, we'll ignore it in _write_pyc.
+ pass
+ elif e in [errno.ENOENT, errno.ENOTDIR]:
+ # One of the path components was not a directory, likely
+ # because we're in a zip file.
+ write = False
+ elif e in [errno.EACCES, errno.EROFS, errno.EPERM]:
+ state.trace("read only directory: %r" % fn_pypath.dirname)
+ write = False
+ else:
+ raise
+ cache_name = fn_pypath.basename[:-3] + PYC_TAIL
+ pyc = os.path.join(cache_dir, cache_name)
+ # Notice that even if we're in a read-only directory, I'm going
+ # to check for a cached pyc. This may not be optimal...
+ co = _read_pyc(fn_pypath, pyc, state.trace)
+ if co is None:
+ state.trace("rewriting %r" % (fn,))
+ source_stat, co = _rewrite_test(self.config, fn_pypath)
+ if co is None:
+ # Probably a SyntaxError in the test.
+ return None
+ if write:
+ _make_rewritten_pyc(state, source_stat, pyc, co)
+ else:
+ state.trace("found cached rewritten pyc for %r" % (fn,))
+ self.modules[name] = co, pyc
+ return self
+
+ def _should_rewrite(self, name, fn_pypath, state):
+ # always rewrite conftest files
+ fn = str(fn_pypath)
+ if fn_pypath.basename == 'conftest.py':
+ state.trace("rewriting conftest file: %r" % (fn,))
+ return True
+
+ if self.session is not None:
+ if self.session.isinitpath(fn):
+ state.trace("matched test file (was specified on cmdline): %r" %
+ (fn,))
+ return True
+
+ # modules not passed explicitly on the command line are only
+ # rewritten if they match the naming convention for test files
+ for pat in self.fnpats:
+ # use fnmatch instead of fn_pypath.fnmatch because the
+ # latter might trigger an import to fnmatch.fnmatch
+ # internally, which would cause this method to be
+ # called recursively
+ if fnmatch(fn_pypath.basename, pat):
+ state.trace("matched test file %r" % (fn,))
+ return True
+
+ for marked in self._must_rewrite:
+ if name.startswith(marked):
+ state.trace("matched marked file %r (from %r)" % (name, marked))
+ return True
+
+ return False
+
+ def mark_rewrite(self, *names):
+ """Mark import names as needing to be re-written.
+
+ The named module or package as well as any nested modules will
+ be re-written on import.
+ """
+ already_imported = set(names).intersection(set(sys.modules))
+ if already_imported:
+ for name in already_imported:
+ if name not in self._rewritten_names:
+ self._warn_already_imported(name)
+ self._must_rewrite.update(names)
+
+ def _warn_already_imported(self, name):
+ self.config.warn(
+ 'P1',
+ 'Module already imported so can not be re-written: %s' % name)
+
+ def load_module(self, name):
+ # If there is an existing module object named 'fullname' in
+ # sys.modules, the loader must use that existing module. (Otherwise,
+ # the reload() builtin will not work correctly.)
+ if name in sys.modules:
+ return sys.modules[name]
+
+ co, pyc = self.modules.pop(name)
+ # I wish I could just call imp.load_compiled here, but __file__ has to
+ # be set properly. In Python 3.2+, this all would be handled correctly
+ # by load_compiled.
+ mod = sys.modules[name] = imp.new_module(name)
+ try:
+ mod.__file__ = co.co_filename
+ # Normally, this attribute is 3.2+.
+ mod.__cached__ = pyc
+ mod.__loader__ = self
+ py.builtin.exec_(co, mod.__dict__)
+ except:
+ del sys.modules[name]
+ raise
+ return sys.modules[name]
+
+
+
+ def is_package(self, name):
+ try:
+ fd, fn, desc = imp.find_module(name)
+ except ImportError:
+ return False
+ if fd is not None:
+ fd.close()
+ tp = desc[2]
+ return tp == imp.PKG_DIRECTORY
+
+ @classmethod
+ def _register_with_pkg_resources(cls):
+ """
+ Ensure package resources can be loaded from this loader. May be called
+ multiple times, as the operation is idempotent.
+ """
+ try:
+ import pkg_resources
+ # access an attribute in case a deferred importer is present
+ pkg_resources.__name__
+ except ImportError:
+ return
+
+ # Since pytest tests are always located in the file system, the
+ # DefaultProvider is appropriate.
+ pkg_resources.register_loader_type(cls, pkg_resources.DefaultProvider)
+
+ def get_data(self, pathname):
+ """Optional PEP302 get_data API.
+ """
+ with open(pathname, 'rb') as f:
+ return f.read()
+
+
+def _write_pyc(state, co, source_stat, pyc):
+ # Technically, we don't have to have the same pyc format as
+ # (C)Python, since these "pycs" should never be seen by builtin
+ # import. However, there's little reason deviate, and I hope
+ # sometime to be able to use imp.load_compiled to load them. (See
+ # the comment in load_module above.)
+ try:
+ fp = open(pyc, "wb")
+ except IOError:
+ err = sys.exc_info()[1].errno
+ state.trace("error writing pyc file at %s: errno=%s" %(pyc, err))
+ # we ignore any failure to write the cache file
+ # there are many reasons, permission-denied, __pycache__ being a
+ # file etc.
+ return False
+ try:
+ fp.write(imp.get_magic())
+ mtime = int(source_stat.mtime)
+ size = source_stat.size & 0xFFFFFFFF
+ fp.write(struct.pack("<ll", mtime, size))
+ marshal.dump(co, fp)
+ finally:
+ fp.close()
+ return True
+
+
+RN = "\r\n".encode("utf-8")
+N = "\n".encode("utf-8")
+
+cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+")
+BOM_UTF8 = '\xef\xbb\xbf'
+
+def _rewrite_test(config, fn):
+ """Try to read and rewrite *fn* and return the code object."""
+ state = config._assertstate
+ try:
+ stat = fn.stat()
+ source = fn.read("rb")
+ except EnvironmentError:
+ return None, None
+ if ASCII_IS_DEFAULT_ENCODING:
+ # ASCII is the default encoding in Python 2. Without a coding
+ # declaration, Python 2 will complain about any bytes in the file
+ # outside the ASCII range. Sadly, this behavior does not extend to
+ # compile() or ast.parse(), which prefer to interpret the bytes as
+ # latin-1. (At least they properly handle explicit coding cookies.) To
+ # preserve this error behavior, we could force ast.parse() to use ASCII
+ # as the encoding by inserting a coding cookie. Unfortunately, that
+ # messes up line numbers. Thus, we have to check ourselves if anything
+ # is outside the ASCII range in the case no encoding is explicitly
+ # declared. For more context, see issue #269. Yay for Python 3 which
+ # gets this right.
+ end1 = source.find("\n")
+ end2 = source.find("\n", end1 + 1)
+ if (not source.startswith(BOM_UTF8) and
+ cookie_re.match(source[0:end1]) is None and
+ cookie_re.match(source[end1 + 1:end2]) is None):
+ if hasattr(state, "_indecode"):
+ # encodings imported us again, so don't rewrite.
+ return None, None
+ state._indecode = True
+ try:
+ try:
+ source.decode("ascii")
+ except UnicodeDecodeError:
+ # Let it fail in real import.
+ return None, None
+ finally:
+ del state._indecode
+ # On Python versions which are not 2.7 and less than or equal to 3.1, the
+ # parser expects *nix newlines.
+ if REWRITE_NEWLINES:
+ source = source.replace(RN, N) + N
+ try:
+ tree = ast.parse(source)
+ except SyntaxError:
+ # Let this pop up again in the real import.
+ state.trace("failed to parse: %r" % (fn,))
+ return None, None
+ rewrite_asserts(tree, fn, config)
+ try:
+ co = compile(tree, fn.strpath, "exec")
+ except SyntaxError:
+ # It's possible that this error is from some bug in the
+ # assertion rewriting, but I don't know of a fast way to tell.
+ state.trace("failed to compile: %r" % (fn,))
+ return None, None
+ return stat, co
+
+def _make_rewritten_pyc(state, source_stat, pyc, co):
+ """Try to dump rewritten code to *pyc*."""
+ if sys.platform.startswith("win"):
+ # Windows grants exclusive access to open files and doesn't have atomic
+ # rename, so just write into the final file.
+ _write_pyc(state, co, source_stat, pyc)
+ else:
+ # When not on windows, assume rename is atomic. Dump the code object
+ # into a file specific to this process and atomically replace it.
+ proc_pyc = pyc + "." + str(os.getpid())
+ if _write_pyc(state, co, source_stat, proc_pyc):
+ os.rename(proc_pyc, pyc)
+
+def _read_pyc(source, pyc, trace=lambda x: None):
+ """Possibly read a pytest pyc containing rewritten code.
+
+ Return rewritten code if successful or None if not.
+ """
+ try:
+ fp = open(pyc, "rb")
+ except IOError:
+ return None
+ with fp:
+ try:
+ mtime = int(source.mtime())
+ size = source.size()
+ data = fp.read(12)
+ except EnvironmentError as e:
+ trace('_read_pyc(%s): EnvironmentError %s' % (source, e))
+ return None
+ # Check for invalid or out of date pyc file.
+ if (len(data) != 12 or data[:4] != imp.get_magic() or
+ struct.unpack("<ll", data[4:]) != (mtime, size)):
+ trace('_read_pyc(%s): invalid or out of date pyc' % source)
+ return None
+ try:
+ co = marshal.load(fp)
+ except Exception as e:
+ trace('_read_pyc(%s): marshal.load error %s' % (source, e))
+ return None
+ if not isinstance(co, types.CodeType):
+ trace('_read_pyc(%s): not a code object' % source)
+ return None
+ return co
+
+
+def rewrite_asserts(mod, module_path=None, config=None):
+ """Rewrite the assert statements in mod."""
+ AssertionRewriter(module_path, config).run(mod)
+
+
+def _saferepr(obj):
+ """Get a safe repr of an object for assertion error messages.
+
+ The assertion formatting (util.format_explanation()) requires
+ newlines to be escaped since they are a special character for it.
+ Normally assertion.util.format_explanation() does this but for a
+ custom repr it is possible to contain one of the special escape
+ sequences, especially '\n{' and '\n}' are likely to be present in
+ JSON reprs.
+
+ """
+ repr = py.io.saferepr(obj)
+ if py.builtin._istext(repr):
+ t = py.builtin.text
+ else:
+ t = py.builtin.bytes
+ return repr.replace(t("\n"), t("\\n"))
+
+
+from _pytest.assertion.util import format_explanation as _format_explanation # noqa
+
+def _format_assertmsg(obj):
+ """Format the custom assertion message given.
+
+ For strings this simply replaces newlines with '\n~' so that
+ util.format_explanation() will preserve them instead of escaping
+ newlines. For other objects py.io.saferepr() is used first.
+
+ """
+ # reprlib appears to have a bug which means that if a string
+ # contains a newline it gets escaped, however if an object has a
+ # .__repr__() which contains newlines it does not get escaped.
+ # However in either case we want to preserve the newline.
+ if py.builtin._istext(obj) or py.builtin._isbytes(obj):
+ s = obj
+ is_repr = False
+ else:
+ s = py.io.saferepr(obj)
+ is_repr = True
+ if py.builtin._istext(s):
+ t = py.builtin.text
+ else:
+ t = py.builtin.bytes
+ s = s.replace(t("\n"), t("\n~")).replace(t("%"), t("%%"))
+ if is_repr:
+ s = s.replace(t("\\n"), t("\n~"))
+ return s
+
+def _should_repr_global_name(obj):
+ return not hasattr(obj, "__name__") and not py.builtin.callable(obj)
+
+def _format_boolop(explanations, is_or):
+ explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
+ if py.builtin._istext(explanation):
+ t = py.builtin.text
+ else:
+ t = py.builtin.bytes
+ return explanation.replace(t('%'), t('%%'))
+
+def _call_reprcompare(ops, results, expls, each_obj):
+ for i, res, expl in zip(range(len(ops)), results, expls):
+ try:
+ done = not res
+ except Exception:
+ done = True
+ if done:
+ break
+ if util._reprcompare is not None:
+ custom = util._reprcompare(ops[i], each_obj[i], each_obj[i + 1])
+ if custom is not None:
+ return custom
+ return expl
+
+
+unary_map = {
+ ast.Not: "not %s",
+ ast.Invert: "~%s",
+ ast.USub: "-%s",
+ ast.UAdd: "+%s"
+}
+
+binop_map = {
+ ast.BitOr: "|",
+ ast.BitXor: "^",
+ ast.BitAnd: "&",
+ ast.LShift: "<<",
+ ast.RShift: ">>",
+ ast.Add: "+",
+ ast.Sub: "-",
+ ast.Mult: "*",
+ ast.Div: "/",
+ ast.FloorDiv: "//",
+ ast.Mod: "%%", # escaped for string formatting
+ ast.Eq: "==",
+ ast.NotEq: "!=",
+ ast.Lt: "<",
+ ast.LtE: "<=",
+ ast.Gt: ">",
+ ast.GtE: ">=",
+ ast.Pow: "**",
+ ast.Is: "is",
+ ast.IsNot: "is not",
+ ast.In: "in",
+ ast.NotIn: "not in"
+}
+# Python 3.5+ compatibility
+try:
+ binop_map[ast.MatMult] = "@"
+except AttributeError:
+ pass
+
+# Python 3.4+ compatibility
+if hasattr(ast, "NameConstant"):
+ _NameConstant = ast.NameConstant
+else:
+ def _NameConstant(c):
+ return ast.Name(str(c), ast.Load())
+
+
+def set_location(node, lineno, col_offset):
+ """Set node location information recursively."""
+ def _fix(node, lineno, col_offset):
+ if "lineno" in node._attributes:
+ node.lineno = lineno
+ if "col_offset" in node._attributes:
+ node.col_offset = col_offset
+ for child in ast.iter_child_nodes(node):
+ _fix(child, lineno, col_offset)
+ _fix(node, lineno, col_offset)
+ return node
+
+
+class AssertionRewriter(ast.NodeVisitor):
+ """Assertion rewriting implementation.
+
+ The main entrypoint is to call .run() with an ast.Module instance,
+ this will then find all the assert statements and re-write them to
+ provide intermediate values and a detailed assertion error. See
+ http://pybites.blogspot.be/2011/07/behind-scenes-of-pytests-new-assertion.html
+ for an overview of how this works.
+
+ The entry point here is .run() which will iterate over all the
+ statements in an ast.Module and for each ast.Assert statement it
+ finds call .visit() with it. Then .visit_Assert() takes over and
+ is responsible for creating new ast statements to replace the
+ original assert statement: it re-writes the test of an assertion
+ to provide intermediate values and replace it with an if statement
+ which raises an assertion error with a detailed explanation in
+ case the expression is false.
+
+ For this .visit_Assert() uses the visitor pattern to visit all the
+ AST nodes of the ast.Assert.test field, each visit call returning
+ an AST node and the corresponding explanation string. During this
+ state is kept in several instance attributes:
+
+ :statements: All the AST statements which will replace the assert
+ statement.
+
+ :variables: This is populated by .variable() with each variable
+ used by the statements so that they can all be set to None at
+ the end of the statements.
+
+ :variable_counter: Counter to create new unique variables needed
+ by statements. Variables are created using .variable() and
+ have the form of "@py_assert0".
+
+ :on_failure: The AST statements which will be executed if the
+ assertion test fails. This is the code which will construct
+ the failure message and raises the AssertionError.
+
+ :explanation_specifiers: A dict filled by .explanation_param()
+ with %-formatting placeholders and their corresponding
+ expressions to use in the building of an assertion message.
+ This is used by .pop_format_context() to build a message.
+
+ :stack: A stack of the explanation_specifiers dicts maintained by
+ .push_format_context() and .pop_format_context() which allows
+ to build another %-formatted string while already building one.
+
+ This state is reset on every new assert statement visited and used
+ by the other visitors.
+
+ """
+
+ def __init__(self, module_path, config):
+ super(AssertionRewriter, self).__init__()
+ self.module_path = module_path
+ self.config = config
+
+ def run(self, mod):
+ """Find all assert statements in *mod* and rewrite them."""
+ if not mod.body:
+ # Nothing to do.
+ return
+ # Insert some special imports at the top of the module but after any
+ # docstrings and __future__ imports.
+ aliases = [ast.alias(py.builtin.builtins.__name__, "@py_builtins"),
+ ast.alias("_pytest.assertion.rewrite", "@pytest_ar")]
+ expect_docstring = True
+ pos = 0
+ lineno = 0
+ for item in mod.body:
+ if (expect_docstring and isinstance(item, ast.Expr) and
+ isinstance(item.value, ast.Str)):
+ doc = item.value.s
+ if "PYTEST_DONT_REWRITE" in doc:
+ # The module has disabled assertion rewriting.
+ return
+ lineno += len(doc) - 1
+ expect_docstring = False
+ elif (not isinstance(item, ast.ImportFrom) or item.level > 0 or
+ item.module != "__future__"):
+ lineno = item.lineno
+ break
+ pos += 1
+ imports = [ast.Import([alias], lineno=lineno, col_offset=0)
+ for alias in aliases]
+ mod.body[pos:pos] = imports
+ # Collect asserts.
+ nodes = [mod]
+ while nodes:
+ node = nodes.pop()
+ for name, field in ast.iter_fields(node):
+ if isinstance(field, list):
+ new = []
+ for i, child in enumerate(field):
+ if isinstance(child, ast.Assert):
+ # Transform assert.
+ new.extend(self.visit(child))
+ else:
+ new.append(child)
+ if isinstance(child, ast.AST):
+ nodes.append(child)
+ setattr(node, name, new)
+ elif (isinstance(field, ast.AST) and
+ # Don't recurse into expressions as they can't contain
+ # asserts.
+ not isinstance(field, ast.expr)):
+ nodes.append(field)
+
+ def variable(self):
+ """Get a new variable."""
+ # Use a character invalid in python identifiers to avoid clashing.
+ name = "@py_assert" + str(next(self.variable_counter))
+ self.variables.append(name)
+ return name
+
+ def assign(self, expr):
+ """Give *expr* a name."""
+ name = self.variable()
+ self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr))
+ return ast.Name(name, ast.Load())
+
+ def display(self, expr):
+ """Call py.io.saferepr on the expression."""
+ return self.helper("saferepr", expr)
+
+ def helper(self, name, *args):
+ """Call a helper in this module."""
+ py_name = ast.Name("@pytest_ar", ast.Load())
+ attr = ast.Attribute(py_name, "_" + name, ast.Load())
+ return ast_Call(attr, list(args), [])
+
+ def builtin(self, name):
+ """Return the builtin called *name*."""
+ builtin_name = ast.Name("@py_builtins", ast.Load())
+ return ast.Attribute(builtin_name, name, ast.Load())
+
+ def explanation_param(self, expr):
+ """Return a new named %-formatting placeholder for expr.
+
+ This creates a %-formatting placeholder for expr in the
+ current formatting context, e.g. ``%(py0)s``. The placeholder
+ and expr are placed in the current format context so that it
+ can be used on the next call to .pop_format_context().
+
+ """
+ specifier = "py" + str(next(self.variable_counter))
+ self.explanation_specifiers[specifier] = expr
+ return "%(" + specifier + ")s"
+
+ def push_format_context(self):
+ """Create a new formatting context.
+
+ The format context is used for when an explanation wants to
+ have a variable value formatted in the assertion message. In
+ this case the value required can be added using
+ .explanation_param(). Finally .pop_format_context() is used
+ to format a string of %-formatted values as added by
+ .explanation_param().
+
+ """
+ self.explanation_specifiers = {}
+ self.stack.append(self.explanation_specifiers)
+
+ def pop_format_context(self, expl_expr):
+ """Format the %-formatted string with current format context.
+
+ The expl_expr should be an ast.Str instance constructed from
+ the %-placeholders created by .explanation_param(). This will
+ add the required code to format said string to .on_failure and
+ return the ast.Name instance of the formatted string.
+
+ """
+ current = self.stack.pop()
+ if self.stack:
+ self.explanation_specifiers = self.stack[-1]
+ keys = [ast.Str(key) for key in current.keys()]
+ format_dict = ast.Dict(keys, list(current.values()))
+ form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
+ name = "@py_format" + str(next(self.variable_counter))
+ self.on_failure.append(ast.Assign([ast.Name(name, ast.Store())], form))
+ return ast.Name(name, ast.Load())
+
+ def generic_visit(self, node):
+ """Handle expressions we don't have custom code for."""
+ assert isinstance(node, ast.expr)
+ res = self.assign(node)
+ return res, self.explanation_param(self.display(res))
+
+ def visit_Assert(self, assert_):
+ """Return the AST statements to replace the ast.Assert instance.
+
+ This re-writes the test of an assertion to provide
+ intermediate values and replace it with an if statement which
+ raises an assertion error with a detailed explanation in case
+ the expression is false.
+
+ """
+ if isinstance(assert_.test, ast.Tuple) and self.config is not None:
+ fslocation = (self.module_path, assert_.lineno)
+ self.config.warn('R1', 'assertion is always true, perhaps '
+ 'remove parentheses?', fslocation=fslocation)
+ self.statements = []
+ self.variables = []
+ self.variable_counter = itertools.count()
+ self.stack = []
+ self.on_failure = []
+ self.push_format_context()
+ # Rewrite assert into a bunch of statements.
+ top_condition, explanation = self.visit(assert_.test)
+ # Create failure message.
+ body = self.on_failure
+ negation = ast.UnaryOp(ast.Not(), top_condition)
+ self.statements.append(ast.If(negation, body, []))
+ if assert_.msg:
+ assertmsg = self.helper('format_assertmsg', assert_.msg)
+ explanation = "\n>assert " + explanation
+ else:
+ assertmsg = ast.Str("")
+ explanation = "assert " + explanation
+ template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation))
+ msg = self.pop_format_context(template)
+ fmt = self.helper("format_explanation", msg)
+ err_name = ast.Name("AssertionError", ast.Load())
+ exc = ast_Call(err_name, [fmt], [])
+ if sys.version_info[0] >= 3:
+ raise_ = ast.Raise(exc, None)
+ else:
+ raise_ = ast.Raise(exc, None, None)
+ body.append(raise_)
+ # Clear temporary variables by setting them to None.
+ if self.variables:
+ variables = [ast.Name(name, ast.Store())
+ for name in self.variables]
+ clear = ast.Assign(variables, _NameConstant(None))
+ self.statements.append(clear)
+ # Fix line numbers.
+ for stmt in self.statements:
+ set_location(stmt, assert_.lineno, assert_.col_offset)
+ return self.statements
+
+ def visit_Name(self, name):
+ # Display the repr of the name if it's a local variable or
+ # _should_repr_global_name() thinks it's acceptable.
+ locs = ast_Call(self.builtin("locals"), [], [])
+ inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
+ dorepr = self.helper("should_repr_global_name", name)
+ test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
+ expr = ast.IfExp(test, self.display(name), ast.Str(name.id))
+ return name, self.explanation_param(expr)
+
+ def visit_BoolOp(self, boolop):
+ res_var = self.variable()
+ expl_list = self.assign(ast.List([], ast.Load()))
+ app = ast.Attribute(expl_list, "append", ast.Load())
+ is_or = int(isinstance(boolop.op, ast.Or))
+ body = save = self.statements
+ fail_save = self.on_failure
+ levels = len(boolop.values) - 1
+ self.push_format_context()
+ # Process each operand, short-circuting if needed.
+ for i, v in enumerate(boolop.values):
+ if i:
+ fail_inner = []
+ # cond is set in a prior loop iteration below
+ self.on_failure.append(ast.If(cond, fail_inner, [])) # noqa
+ self.on_failure = fail_inner
+ self.push_format_context()
+ res, expl = self.visit(v)
+ body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
+ expl_format = self.pop_format_context(ast.Str(expl))
+ call = ast_Call(app, [expl_format], [])
+ self.on_failure.append(ast.Expr(call))
+ if i < levels:
+ cond = res
+ if is_or:
+ cond = ast.UnaryOp(ast.Not(), cond)
+ inner = []
+ self.statements.append(ast.If(cond, inner, []))
+ self.statements = body = inner
+ self.statements = save
+ self.on_failure = fail_save
+ expl_template = self.helper("format_boolop", expl_list, ast.Num(is_or))
+ expl = self.pop_format_context(expl_template)
+ return ast.Name(res_var, ast.Load()), self.explanation_param(expl)
+
+ def visit_UnaryOp(self, unary):
+ pattern = unary_map[unary.op.__class__]
+ operand_res, operand_expl = self.visit(unary.operand)
+ res = self.assign(ast.UnaryOp(unary.op, operand_res))
+ return res, pattern % (operand_expl,)
+
+ def visit_BinOp(self, binop):
+ symbol = binop_map[binop.op.__class__]
+ left_expr, left_expl = self.visit(binop.left)
+ right_expr, right_expl = self.visit(binop.right)
+ explanation = "(%s %s %s)" % (left_expl, symbol, right_expl)
+ res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
+ return res, explanation
+
+ def visit_Call_35(self, call):
+ """
+ visit `ast.Call` nodes on Python3.5 and after
+ """
+ new_func, func_expl = self.visit(call.func)
+ arg_expls = []
+ new_args = []
+ new_kwargs = []
+ for arg in call.args:
+ res, expl = self.visit(arg)
+ arg_expls.append(expl)
+ new_args.append(res)
+ for keyword in call.keywords:
+ res, expl = self.visit(keyword.value)
+ new_kwargs.append(ast.keyword(keyword.arg, res))
+ if keyword.arg:
+ arg_expls.append(keyword.arg + "=" + expl)
+ else: ## **args have `arg` keywords with an .arg of None
+ arg_expls.append("**" + expl)
+
+ expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
+ new_call = ast.Call(new_func, new_args, new_kwargs)
+ res = self.assign(new_call)
+ res_expl = self.explanation_param(self.display(res))
+ outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
+ return res, outer_expl
+
+ def visit_Starred(self, starred):
+ # From Python 3.5, a Starred node can appear in a function call
+ res, expl = self.visit(starred.value)
+ return starred, '*' + expl
+
+ def visit_Call_legacy(self, call):
+ """
+ visit `ast.Call nodes on 3.4 and below`
+ """
+ new_func, func_expl = self.visit(call.func)
+ arg_expls = []
+ new_args = []
+ new_kwargs = []
+ new_star = new_kwarg = None
+ for arg in call.args:
+ res, expl = self.visit(arg)
+ new_args.append(res)
+ arg_expls.append(expl)
+ for keyword in call.keywords:
+ res, expl = self.visit(keyword.value)
+ new_kwargs.append(ast.keyword(keyword.arg, res))
+ arg_expls.append(keyword.arg + "=" + expl)
+ if call.starargs:
+ new_star, expl = self.visit(call.starargs)
+ arg_expls.append("*" + expl)
+ if call.kwargs:
+ new_kwarg, expl = self.visit(call.kwargs)
+ arg_expls.append("**" + expl)
+ expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
+ new_call = ast.Call(new_func, new_args, new_kwargs,
+ new_star, new_kwarg)
+ res = self.assign(new_call)
+ res_expl = self.explanation_param(self.display(res))
+ outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
+ return res, outer_expl
+
+ # ast.Call signature changed on 3.5,
+ # conditionally change which methods is named
+ # visit_Call depending on Python version
+ if sys.version_info >= (3, 5):
+ visit_Call = visit_Call_35
+ else:
+ visit_Call = visit_Call_legacy
+
+
+ def visit_Attribute(self, attr):
+ if not isinstance(attr.ctx, ast.Load):
+ return self.generic_visit(attr)
+ value, value_expl = self.visit(attr.value)
+ res = self.assign(ast.Attribute(value, attr.attr, ast.Load()))
+ res_expl = self.explanation_param(self.display(res))
+ pat = "%s\n{%s = %s.%s\n}"
+ expl = pat % (res_expl, res_expl, value_expl, attr.attr)
+ return res, expl
+
+ def visit_Compare(self, comp):
+ self.push_format_context()
+ left_res, left_expl = self.visit(comp.left)
+ if isinstance(comp.left, (_ast.Compare, _ast.BoolOp)):
+ left_expl = "({0})".format(left_expl)
+ res_variables = [self.variable() for i in range(len(comp.ops))]
+ load_names = [ast.Name(v, ast.Load()) for v in res_variables]
+ store_names = [ast.Name(v, ast.Store()) for v in res_variables]
+ it = zip(range(len(comp.ops)), comp.ops, comp.comparators)
+ expls = []
+ syms = []
+ results = [left_res]
+ for i, op, next_operand in it:
+ next_res, next_expl = self.visit(next_operand)
+ if isinstance(next_operand, (_ast.Compare, _ast.BoolOp)):
+ next_expl = "({0})".format(next_expl)
+ results.append(next_res)
+ sym = binop_map[op.__class__]
+ syms.append(ast.Str(sym))
+ expl = "%s %s %s" % (left_expl, sym, next_expl)
+ expls.append(ast.Str(expl))
+ res_expr = ast.Compare(left_res, [op], [next_res])
+ self.statements.append(ast.Assign([store_names[i]], res_expr))
+ left_res, left_expl = next_res, next_expl
+ # Use pytest.assertion.util._reprcompare if that's available.
+ expl_call = self.helper("call_reprcompare",
+ ast.Tuple(syms, ast.Load()),
+ ast.Tuple(load_names, ast.Load()),
+ ast.Tuple(expls, ast.Load()),
+ ast.Tuple(results, ast.Load()))
+ if len(comp.ops) > 1:
+ res = ast.BoolOp(ast.And(), load_names)
+ else:
+ res = load_names[0]
+ return res, self.explanation_param(self.pop_format_context(expl_call))
diff --git a/lib/spack/external/_pytest/assertion/util.py b/lib/spack/external/_pytest/assertion/util.py
new file mode 100644
index 0000000000..4a0a4e4310
--- /dev/null
+++ b/lib/spack/external/_pytest/assertion/util.py
@@ -0,0 +1,300 @@
+"""Utilities for assertion debugging"""
+import pprint
+
+import _pytest._code
+import py
+try:
+ from collections import Sequence
+except ImportError:
+ Sequence = list
+
+BuiltinAssertionError = py.builtin.builtins.AssertionError
+u = py.builtin._totext
+
+# The _reprcompare attribute on the util module is used by the new assertion
+# interpretation code and assertion rewriter to detect this plugin was
+# loaded and in turn call the hooks defined here as part of the
+# DebugInterpreter.
+_reprcompare = None
+
+
+# the re-encoding is needed for python2 repr
+# with non-ascii characters (see issue 877 and 1379)
+def ecu(s):
+ try:
+ return u(s, 'utf-8', 'replace')
+ except TypeError:
+ return s
+
+
+def format_explanation(explanation):
+ """This formats an explanation
+
+ Normally all embedded newlines are escaped, however there are
+ three exceptions: \n{, \n} and \n~. The first two are intended
+ cover nested explanations, see function and attribute explanations
+ for examples (.visit_Call(), visit_Attribute()). The last one is
+ for when one explanation needs to span multiple lines, e.g. when
+ displaying diffs.
+ """
+ explanation = ecu(explanation)
+ lines = _split_explanation(explanation)
+ result = _format_lines(lines)
+ return u('\n').join(result)
+
+
+def _split_explanation(explanation):
+ """Return a list of individual lines in the explanation
+
+ This will return a list of lines split on '\n{', '\n}' and '\n~'.
+ Any other newlines will be escaped and appear in the line as the
+ literal '\n' characters.
+ """
+ raw_lines = (explanation or u('')).split('\n')
+ lines = [raw_lines[0]]
+ for l in raw_lines[1:]:
+ if l and l[0] in ['{', '}', '~', '>']:
+ lines.append(l)
+ else:
+ lines[-1] += '\\n' + l
+ return lines
+
+
+def _format_lines(lines):
+ """Format the individual lines
+
+ This will replace the '{', '}' and '~' characters of our mini
+ formatting language with the proper 'where ...', 'and ...' and ' +
+ ...' text, taking care of indentation along the way.
+
+ Return a list of formatted lines.
+ """
+ result = lines[:1]
+ stack = [0]
+ stackcnt = [0]
+ for line in lines[1:]:
+ if line.startswith('{'):
+ if stackcnt[-1]:
+ s = u('and ')
+ else:
+ s = u('where ')
+ stack.append(len(result))
+ stackcnt[-1] += 1
+ stackcnt.append(0)
+ result.append(u(' +') + u(' ')*(len(stack)-1) + s + line[1:])
+ elif line.startswith('}'):
+ stack.pop()
+ stackcnt.pop()
+ result[stack[-1]] += line[1:]
+ else:
+ assert line[0] in ['~', '>']
+ stack[-1] += 1
+ indent = len(stack) if line.startswith('~') else len(stack) - 1
+ result.append(u(' ')*indent + line[1:])
+ assert len(stack) == 1
+ return result
+
+
+# Provide basestring in python3
+try:
+ basestring = basestring
+except NameError:
+ basestring = str
+
+
+def assertrepr_compare(config, op, left, right):
+ """Return specialised explanations for some operators/operands"""
+ width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
+ left_repr = py.io.saferepr(left, maxsize=int(width//2))
+ right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
+
+ summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
+
+ issequence = lambda x: (isinstance(x, (list, tuple, Sequence)) and
+ not isinstance(x, basestring))
+ istext = lambda x: isinstance(x, basestring)
+ isdict = lambda x: isinstance(x, dict)
+ isset = lambda x: isinstance(x, (set, frozenset))
+
+ def isiterable(obj):
+ try:
+ iter(obj)
+ return not istext(obj)
+ except TypeError:
+ return False
+
+ verbose = config.getoption('verbose')
+ explanation = None
+ try:
+ if op == '==':
+ if istext(left) and istext(right):
+ explanation = _diff_text(left, right, verbose)
+ else:
+ if issequence(left) and issequence(right):
+ explanation = _compare_eq_sequence(left, right, verbose)
+ elif isset(left) and isset(right):
+ explanation = _compare_eq_set(left, right, verbose)
+ elif isdict(left) and isdict(right):
+ explanation = _compare_eq_dict(left, right, verbose)
+ if isiterable(left) and isiterable(right):
+ expl = _compare_eq_iterable(left, right, verbose)
+ if explanation is not None:
+ explanation.extend(expl)
+ else:
+ explanation = expl
+ elif op == 'not in':
+ if istext(left) and istext(right):
+ explanation = _notin_text(left, right, verbose)
+ except Exception:
+ explanation = [
+ u('(pytest_assertion plugin: representation of details failed. '
+ 'Probably an object has a faulty __repr__.)'),
+ u(_pytest._code.ExceptionInfo())]
+
+ if not explanation:
+ return None
+
+ return [summary] + explanation
+
+
+def _diff_text(left, right, verbose=False):
+ """Return the explanation for the diff between text or bytes
+
+ Unless --verbose is used this will skip leading and trailing
+ characters which are identical to keep the diff minimal.
+
+ If the input are bytes they will be safely converted to text.
+ """
+ from difflib import ndiff
+ explanation = []
+ if isinstance(left, py.builtin.bytes):
+ left = u(repr(left)[1:-1]).replace(r'\n', '\n')
+ if isinstance(right, py.builtin.bytes):
+ right = u(repr(right)[1:-1]).replace(r'\n', '\n')
+ if not verbose:
+ i = 0 # just in case left or right has zero length
+ for i in range(min(len(left), len(right))):
+ if left[i] != right[i]:
+ break
+ if i > 42:
+ i -= 10 # Provide some context
+ explanation = [u('Skipping %s identical leading '
+ 'characters in diff, use -v to show') % i]
+ left = left[i:]
+ right = right[i:]
+ if len(left) == len(right):
+ for i in range(len(left)):
+ if left[-i] != right[-i]:
+ break
+ if i > 42:
+ i -= 10 # Provide some context
+ explanation += [u('Skipping %s identical trailing '
+ 'characters in diff, use -v to show') % i]
+ left = left[:-i]
+ right = right[:-i]
+ keepends = True
+ explanation += [line.strip('\n')
+ for line in ndiff(left.splitlines(keepends),
+ right.splitlines(keepends))]
+ return explanation
+
+
+def _compare_eq_iterable(left, right, verbose=False):
+ if not verbose:
+ return [u('Use -v to get the full diff')]
+ # dynamic import to speedup pytest
+ import difflib
+
+ try:
+ left_formatting = pprint.pformat(left).splitlines()
+ right_formatting = pprint.pformat(right).splitlines()
+ explanation = [u('Full diff:')]
+ except Exception:
+ # hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling
+ # sorted() on a list would raise. See issue #718.
+ # As a workaround, the full diff is generated by using the repr() string of each item of each container.
+ left_formatting = sorted(repr(x) for x in left)
+ right_formatting = sorted(repr(x) for x in right)
+ explanation = [u('Full diff (fallback to calling repr on each item):')]
+ explanation.extend(line.strip() for line in difflib.ndiff(left_formatting, right_formatting))
+ return explanation
+
+
+def _compare_eq_sequence(left, right, verbose=False):
+ explanation = []
+ for i in range(min(len(left), len(right))):
+ if left[i] != right[i]:
+ explanation += [u('At index %s diff: %r != %r')
+ % (i, left[i], right[i])]
+ break
+ if len(left) > len(right):
+ explanation += [u('Left contains more items, first extra item: %s')
+ % py.io.saferepr(left[len(right)],)]
+ elif len(left) < len(right):
+ explanation += [
+ u('Right contains more items, first extra item: %s') %
+ py.io.saferepr(right[len(left)],)]
+ return explanation
+
+
+def _compare_eq_set(left, right, verbose=False):
+ explanation = []
+ diff_left = left - right
+ diff_right = right - left
+ if diff_left:
+ explanation.append(u('Extra items in the left set:'))
+ for item in diff_left:
+ explanation.append(py.io.saferepr(item))
+ if diff_right:
+ explanation.append(u('Extra items in the right set:'))
+ for item in diff_right:
+ explanation.append(py.io.saferepr(item))
+ return explanation
+
+
+def _compare_eq_dict(left, right, verbose=False):
+ explanation = []
+ common = set(left).intersection(set(right))
+ same = dict((k, left[k]) for k in common if left[k] == right[k])
+ if same and not verbose:
+ explanation += [u('Omitting %s identical items, use -v to show') %
+ len(same)]
+ elif same:
+ explanation += [u('Common items:')]
+ explanation += pprint.pformat(same).splitlines()
+ diff = set(k for k in common if left[k] != right[k])
+ if diff:
+ explanation += [u('Differing items:')]
+ for k in diff:
+ explanation += [py.io.saferepr({k: left[k]}) + ' != ' +
+ py.io.saferepr({k: right[k]})]
+ extra_left = set(left) - set(right)
+ if extra_left:
+ explanation.append(u('Left contains more items:'))
+ explanation.extend(pprint.pformat(
+ dict((k, left[k]) for k in extra_left)).splitlines())
+ extra_right = set(right) - set(left)
+ if extra_right:
+ explanation.append(u('Right contains more items:'))
+ explanation.extend(pprint.pformat(
+ dict((k, right[k]) for k in extra_right)).splitlines())
+ return explanation
+
+
+def _notin_text(term, text, verbose=False):
+ index = text.find(term)
+ head = text[:index]
+ tail = text[index+len(term):]
+ correct_text = head + tail
+ diff = _diff_text(correct_text, text, verbose)
+ newdiff = [u('%s is contained here:') % py.io.saferepr(term, maxsize=42)]
+ for line in diff:
+ if line.startswith(u('Skipping')):
+ continue
+ if line.startswith(u('- ')):
+ continue
+ if line.startswith(u('+ ')):
+ newdiff.append(u(' ') + line[2:])
+ else:
+ newdiff.append(line)
+ return newdiff
diff --git a/lib/spack/external/_pytest/cacheprovider.py b/lib/spack/external/_pytest/cacheprovider.py
new file mode 100644
index 0000000000..0657001f2d
--- /dev/null
+++ b/lib/spack/external/_pytest/cacheprovider.py
@@ -0,0 +1,245 @@
+"""
+merged implementation of the cache provider
+
+the name cache was not choosen to ensure pluggy automatically
+ignores the external pytest-cache
+"""
+
+import py
+import pytest
+import json
+from os.path import sep as _sep, altsep as _altsep
+
+
+class Cache(object):
+ def __init__(self, config):
+ self.config = config
+ self._cachedir = config.rootdir.join(".cache")
+ self.trace = config.trace.root.get("cache")
+ if config.getvalue("cacheclear"):
+ self.trace("clearing cachedir")
+ if self._cachedir.check():
+ self._cachedir.remove()
+ self._cachedir.mkdir()
+
+ def makedir(self, name):
+ """ return a directory path object with the given name. If the
+ directory does not yet exist, it will be created. You can use it
+ to manage files likes e. g. store/retrieve database
+ dumps across test sessions.
+
+ :param name: must be a string not containing a ``/`` separator.
+ Make sure the name contains your plugin or application
+ identifiers to prevent clashes with other cache users.
+ """
+ if _sep in name or _altsep is not None and _altsep in name:
+ raise ValueError("name is not allowed to contain path separators")
+ return self._cachedir.ensure_dir("d", name)
+
+ def _getvaluepath(self, key):
+ return self._cachedir.join('v', *key.split('/'))
+
+ def get(self, key, default):
+ """ return cached value for the given key. If no value
+ was yet cached or the value cannot be read, the specified
+ default is returned.
+
+ :param key: must be a ``/`` separated value. Usually the first
+ name is the name of your plugin or your application.
+ :param default: must be provided in case of a cache-miss or
+ invalid cache values.
+
+ """
+ path = self._getvaluepath(key)
+ if path.check():
+ try:
+ with path.open("r") as f:
+ return json.load(f)
+ except ValueError:
+ self.trace("cache-invalid at %s" % (path,))
+ return default
+
+ def set(self, key, value):
+ """ save value for the given key.
+
+ :param key: must be a ``/`` separated value. Usually the first
+ name is the name of your plugin or your application.
+ :param value: must be of any combination of basic
+ python types, including nested types
+ like e. g. lists of dictionaries.
+ """
+ path = self._getvaluepath(key)
+ try:
+ path.dirpath().ensure_dir()
+ except (py.error.EEXIST, py.error.EACCES):
+ self.config.warn(
+ code='I9', message='could not create cache path %s' % (path,)
+ )
+ return
+ try:
+ f = path.open('w')
+ except py.error.ENOTDIR:
+ self.config.warn(
+ code='I9', message='cache could not write path %s' % (path,))
+ else:
+ with f:
+ self.trace("cache-write %s: %r" % (key, value,))
+ json.dump(value, f, indent=2, sort_keys=True)
+
+
+class LFPlugin:
+ """ Plugin which implements the --lf (run last-failing) option """
+ def __init__(self, config):
+ self.config = config
+ active_keys = 'lf', 'failedfirst'
+ self.active = any(config.getvalue(key) for key in active_keys)
+ if self.active:
+ self.lastfailed = config.cache.get("cache/lastfailed", {})
+ else:
+ self.lastfailed = {}
+
+ def pytest_report_header(self):
+ if self.active:
+ if not self.lastfailed:
+ mode = "run all (no recorded failures)"
+ else:
+ mode = "rerun last %d failures%s" % (
+ len(self.lastfailed),
+ " first" if self.config.getvalue("failedfirst") else "")
+ return "run-last-failure: %s" % mode
+
+ def pytest_runtest_logreport(self, report):
+ if report.failed and "xfail" not in report.keywords:
+ self.lastfailed[report.nodeid] = True
+ elif not report.failed:
+ if report.when == "call":
+ self.lastfailed.pop(report.nodeid, None)
+
+ def pytest_collectreport(self, report):
+ passed = report.outcome in ('passed', 'skipped')
+ if passed:
+ if report.nodeid in self.lastfailed:
+ self.lastfailed.pop(report.nodeid)
+ self.lastfailed.update(
+ (item.nodeid, True)
+ for item in report.result)
+ else:
+ self.lastfailed[report.nodeid] = True
+
+ def pytest_collection_modifyitems(self, session, config, items):
+ if self.active and self.lastfailed:
+ previously_failed = []
+ previously_passed = []
+ for item in items:
+ if item.nodeid in self.lastfailed:
+ previously_failed.append(item)
+ else:
+ previously_passed.append(item)
+ if not previously_failed and previously_passed:
+ # running a subset of all tests with recorded failures outside
+ # of the set of tests currently executing
+ pass
+ elif self.config.getvalue("failedfirst"):
+ items[:] = previously_failed + previously_passed
+ else:
+ items[:] = previously_failed
+ config.hook.pytest_deselected(items=previously_passed)
+
+ def pytest_sessionfinish(self, session):
+ config = self.config
+ if config.getvalue("cacheshow") or hasattr(config, "slaveinput"):
+ return
+ prev_failed = config.cache.get("cache/lastfailed", None) is not None
+ if (session.testscollected and prev_failed) or self.lastfailed:
+ config.cache.set("cache/lastfailed", self.lastfailed)
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group.addoption(
+ '--lf', '--last-failed', action='store_true', dest="lf",
+ help="rerun only the tests that failed "
+ "at the last run (or all if none failed)")
+ group.addoption(
+ '--ff', '--failed-first', action='store_true', dest="failedfirst",
+ help="run all tests but run the last failures first. "
+ "This may re-order tests and thus lead to "
+ "repeated fixture setup/teardown")
+ group.addoption(
+ '--cache-show', action='store_true', dest="cacheshow",
+ help="show cache contents, don't perform collection or tests")
+ group.addoption(
+ '--cache-clear', action='store_true', dest="cacheclear",
+ help="remove all cache contents at start of test run.")
+
+
+def pytest_cmdline_main(config):
+ if config.option.cacheshow:
+ from _pytest.main import wrap_session
+ return wrap_session(config, cacheshow)
+
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_configure(config):
+ config.cache = Cache(config)
+ config.pluginmanager.register(LFPlugin(config), "lfplugin")
+
+
+@pytest.fixture
+def cache(request):
+ """
+ Return a cache object that can persist state between testing sessions.
+
+ cache.get(key, default)
+ cache.set(key, value)
+
+ Keys must be a ``/`` separated value, where the first part is usually the
+ name of your plugin or application to avoid clashes with other cache users.
+
+ Values can be any object handled by the json stdlib module.
+ """
+ return request.config.cache
+
+
+def pytest_report_header(config):
+ if config.option.verbose:
+ relpath = py.path.local().bestrelpath(config.cache._cachedir)
+ return "cachedir: %s" % relpath
+
+
+def cacheshow(config, session):
+ from pprint import pprint
+ tw = py.io.TerminalWriter()
+ tw.line("cachedir: " + str(config.cache._cachedir))
+ if not config.cache._cachedir.check():
+ tw.line("cache is empty")
+ return 0
+ dummy = object()
+ basedir = config.cache._cachedir
+ vdir = basedir.join("v")
+ tw.sep("-", "cache values")
+ for valpath in vdir.visit(lambda x: x.isfile()):
+ key = valpath.relto(vdir).replace(valpath.sep, "/")
+ val = config.cache.get(key, dummy)
+ if val is dummy:
+ tw.line("%s contains unreadable content, "
+ "will be ignored" % key)
+ else:
+ tw.line("%s contains:" % key)
+ stream = py.io.TextIO()
+ pprint(val, stream=stream)
+ for line in stream.getvalue().splitlines():
+ tw.line(" " + line)
+
+ ddir = basedir.join("d")
+ if ddir.isdir() and ddir.listdir():
+ tw.sep("-", "cache directories")
+ for p in basedir.join("d").visit():
+ #if p.check(dir=1):
+ # print("%s/" % p.relto(basedir))
+ if p.isfile():
+ key = p.relto(basedir)
+ tw.line("%s is a file of length %d" % (
+ key, p.size()))
+ return 0
diff --git a/lib/spack/external/_pytest/capture.py b/lib/spack/external/_pytest/capture.py
new file mode 100644
index 0000000000..eea81ca187
--- /dev/null
+++ b/lib/spack/external/_pytest/capture.py
@@ -0,0 +1,491 @@
+"""
+per-test stdout/stderr capturing mechanism.
+
+"""
+from __future__ import with_statement
+
+import contextlib
+import sys
+import os
+from tempfile import TemporaryFile
+
+import py
+import pytest
+
+from py.io import TextIO
+unicode = py.builtin.text
+
+patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'}
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group._addoption(
+ '--capture', action="store",
+ default="fd" if hasattr(os, "dup") else "sys",
+ metavar="method", choices=['fd', 'sys', 'no'],
+ help="per-test capturing method: one of fd|sys|no.")
+ group._addoption(
+ '-s', action="store_const", const="no", dest="capture",
+ help="shortcut for --capture=no.")
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_load_initial_conftests(early_config, parser, args):
+ _readline_workaround()
+ ns = early_config.known_args_namespace
+ pluginmanager = early_config.pluginmanager
+ capman = CaptureManager(ns.capture)
+ pluginmanager.register(capman, "capturemanager")
+
+ # make sure that capturemanager is properly reset at final shutdown
+ early_config.add_cleanup(capman.reset_capturings)
+
+ # make sure logging does not raise exceptions at the end
+ def silence_logging_at_shutdown():
+ if "logging" in sys.modules:
+ sys.modules["logging"].raiseExceptions = False
+ early_config.add_cleanup(silence_logging_at_shutdown)
+
+ # finally trigger conftest loading but while capturing (issue93)
+ capman.init_capturings()
+ outcome = yield
+ out, err = capman.suspendcapture()
+ if outcome.excinfo is not None:
+ sys.stdout.write(out)
+ sys.stderr.write(err)
+
+
+class CaptureManager:
+ def __init__(self, method):
+ self._method = method
+
+ def _getcapture(self, method):
+ if method == "fd":
+ return MultiCapture(out=True, err=True, Capture=FDCapture)
+ elif method == "sys":
+ return MultiCapture(out=True, err=True, Capture=SysCapture)
+ elif method == "no":
+ return MultiCapture(out=False, err=False, in_=False)
+ else:
+ raise ValueError("unknown capturing method: %r" % method)
+
+ def init_capturings(self):
+ assert not hasattr(self, "_capturing")
+ self._capturing = self._getcapture(self._method)
+ self._capturing.start_capturing()
+
+ def reset_capturings(self):
+ cap = self.__dict__.pop("_capturing", None)
+ if cap is not None:
+ cap.pop_outerr_to_orig()
+ cap.stop_capturing()
+
+ def resumecapture(self):
+ self._capturing.resume_capturing()
+
+ def suspendcapture(self, in_=False):
+ self.deactivate_funcargs()
+ cap = getattr(self, "_capturing", None)
+ if cap is not None:
+ try:
+ outerr = cap.readouterr()
+ finally:
+ cap.suspend_capturing(in_=in_)
+ return outerr
+
+ def activate_funcargs(self, pyfuncitem):
+ capfuncarg = pyfuncitem.__dict__.pop("_capfuncarg", None)
+ if capfuncarg is not None:
+ capfuncarg._start()
+ self._capfuncarg = capfuncarg
+
+ def deactivate_funcargs(self):
+ capfuncarg = self.__dict__.pop("_capfuncarg", None)
+ if capfuncarg is not None:
+ capfuncarg.close()
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_make_collect_report(self, collector):
+ if isinstance(collector, pytest.File):
+ self.resumecapture()
+ outcome = yield
+ out, err = self.suspendcapture()
+ rep = outcome.get_result()
+ if out:
+ rep.sections.append(("Captured stdout", out))
+ if err:
+ rep.sections.append(("Captured stderr", err))
+ else:
+ yield
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_runtest_setup(self, item):
+ self.resumecapture()
+ yield
+ self.suspendcapture_item(item, "setup")
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_runtest_call(self, item):
+ self.resumecapture()
+ self.activate_funcargs(item)
+ yield
+ #self.deactivate_funcargs() called from suspendcapture()
+ self.suspendcapture_item(item, "call")
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_runtest_teardown(self, item):
+ self.resumecapture()
+ yield
+ self.suspendcapture_item(item, "teardown")
+
+ @pytest.hookimpl(tryfirst=True)
+ def pytest_keyboard_interrupt(self, excinfo):
+ self.reset_capturings()
+
+ @pytest.hookimpl(tryfirst=True)
+ def pytest_internalerror(self, excinfo):
+ self.reset_capturings()
+
+ def suspendcapture_item(self, item, when, in_=False):
+ out, err = self.suspendcapture(in_=in_)
+ item.add_report_section(when, "stdout", out)
+ item.add_report_section(when, "stderr", err)
+
+
+error_capsysfderror = "cannot use capsys and capfd at the same time"
+
+
+@pytest.fixture
+def capsys(request):
+ """Enable capturing of writes to sys.stdout/sys.stderr and make
+ captured output available via ``capsys.readouterr()`` method calls
+ which return a ``(out, err)`` tuple.
+ """
+ if "capfd" in request.fixturenames:
+ raise request.raiseerror(error_capsysfderror)
+ request.node._capfuncarg = c = CaptureFixture(SysCapture, request)
+ return c
+
+@pytest.fixture
+def capfd(request):
+ """Enable capturing of writes to file descriptors 1 and 2 and make
+ captured output available via ``capfd.readouterr()`` method calls
+ which return a ``(out, err)`` tuple.
+ """
+ if "capsys" in request.fixturenames:
+ request.raiseerror(error_capsysfderror)
+ if not hasattr(os, 'dup'):
+ pytest.skip("capfd funcarg needs os.dup")
+ request.node._capfuncarg = c = CaptureFixture(FDCapture, request)
+ return c
+
+
+class CaptureFixture:
+ def __init__(self, captureclass, request):
+ self.captureclass = captureclass
+ self.request = request
+
+ def _start(self):
+ self._capture = MultiCapture(out=True, err=True, in_=False,
+ Capture=self.captureclass)
+ self._capture.start_capturing()
+
+ def close(self):
+ cap = self.__dict__.pop("_capture", None)
+ if cap is not None:
+ self._outerr = cap.pop_outerr_to_orig()
+ cap.stop_capturing()
+
+ def readouterr(self):
+ try:
+ return self._capture.readouterr()
+ except AttributeError:
+ return self._outerr
+
+ @contextlib.contextmanager
+ def disabled(self):
+ capmanager = self.request.config.pluginmanager.getplugin('capturemanager')
+ capmanager.suspendcapture_item(self.request.node, "call", in_=True)
+ try:
+ yield
+ finally:
+ capmanager.resumecapture()
+
+
+def safe_text_dupfile(f, mode, default_encoding="UTF8"):
+ """ return a open text file object that's a duplicate of f on the
+ FD-level if possible.
+ """
+ encoding = getattr(f, "encoding", None)
+ try:
+ fd = f.fileno()
+ except Exception:
+ if "b" not in getattr(f, "mode", "") and hasattr(f, "encoding"):
+ # we seem to have a text stream, let's just use it
+ return f
+ else:
+ newfd = os.dup(fd)
+ if "b" not in mode:
+ mode += "b"
+ f = os.fdopen(newfd, mode, 0) # no buffering
+ return EncodedFile(f, encoding or default_encoding)
+
+
+class EncodedFile(object):
+ errors = "strict" # possibly needed by py3 code (issue555)
+ def __init__(self, buffer, encoding):
+ self.buffer = buffer
+ self.encoding = encoding
+
+ def write(self, obj):
+ if isinstance(obj, unicode):
+ obj = obj.encode(self.encoding, "replace")
+ self.buffer.write(obj)
+
+ def writelines(self, linelist):
+ data = ''.join(linelist)
+ self.write(data)
+
+ def __getattr__(self, name):
+ return getattr(object.__getattribute__(self, "buffer"), name)
+
+
+class MultiCapture(object):
+ out = err = in_ = None
+
+ def __init__(self, out=True, err=True, in_=True, Capture=None):
+ if in_:
+ self.in_ = Capture(0)
+ if out:
+ self.out = Capture(1)
+ if err:
+ self.err = Capture(2)
+
+ def start_capturing(self):
+ if self.in_:
+ self.in_.start()
+ if self.out:
+ self.out.start()
+ if self.err:
+ self.err.start()
+
+ def pop_outerr_to_orig(self):
+ """ pop current snapshot out/err capture and flush to orig streams. """
+ out, err = self.readouterr()
+ if out:
+ self.out.writeorg(out)
+ if err:
+ self.err.writeorg(err)
+ return out, err
+
+ def suspend_capturing(self, in_=False):
+ if self.out:
+ self.out.suspend()
+ if self.err:
+ self.err.suspend()
+ if in_ and self.in_:
+ self.in_.suspend()
+ self._in_suspended = True
+
+ def resume_capturing(self):
+ if self.out:
+ self.out.resume()
+ if self.err:
+ self.err.resume()
+ if hasattr(self, "_in_suspended"):
+ self.in_.resume()
+ del self._in_suspended
+
+ def stop_capturing(self):
+ """ stop capturing and reset capturing streams """
+ if hasattr(self, '_reset'):
+ raise ValueError("was already stopped")
+ self._reset = True
+ if self.out:
+ self.out.done()
+ if self.err:
+ self.err.done()
+ if self.in_:
+ self.in_.done()
+
+ def readouterr(self):
+ """ return snapshot unicode value of stdout/stderr capturings. """
+ return (self.out.snap() if self.out is not None else "",
+ self.err.snap() if self.err is not None else "")
+
+class NoCapture:
+ __init__ = start = done = suspend = resume = lambda *args: None
+
+class FDCapture:
+ """ Capture IO to/from a given os-level filedescriptor. """
+
+ def __init__(self, targetfd, tmpfile=None):
+ self.targetfd = targetfd
+ try:
+ self.targetfd_save = os.dup(self.targetfd)
+ except OSError:
+ self.start = lambda: None
+ self.done = lambda: None
+ else:
+ if targetfd == 0:
+ assert not tmpfile, "cannot set tmpfile with stdin"
+ tmpfile = open(os.devnull, "r")
+ self.syscapture = SysCapture(targetfd)
+ else:
+ if tmpfile is None:
+ f = TemporaryFile()
+ with f:
+ tmpfile = safe_text_dupfile(f, mode="wb+")
+ if targetfd in patchsysdict:
+ self.syscapture = SysCapture(targetfd, tmpfile)
+ else:
+ self.syscapture = NoCapture()
+ self.tmpfile = tmpfile
+ self.tmpfile_fd = tmpfile.fileno()
+
+ def __repr__(self):
+ return "<FDCapture %s oldfd=%s>" % (self.targetfd, self.targetfd_save)
+
+ def start(self):
+ """ Start capturing on targetfd using memorized tmpfile. """
+ try:
+ os.fstat(self.targetfd_save)
+ except (AttributeError, OSError):
+ raise ValueError("saved filedescriptor not valid anymore")
+ os.dup2(self.tmpfile_fd, self.targetfd)
+ self.syscapture.start()
+
+ def snap(self):
+ f = self.tmpfile
+ f.seek(0)
+ res = f.read()
+ if res:
+ enc = getattr(f, "encoding", None)
+ if enc and isinstance(res, bytes):
+ res = py.builtin._totext(res, enc, "replace")
+ f.truncate(0)
+ f.seek(0)
+ return res
+ return ''
+
+ def done(self):
+ """ stop capturing, restore streams, return original capture file,
+ seeked to position zero. """
+ targetfd_save = self.__dict__.pop("targetfd_save")
+ os.dup2(targetfd_save, self.targetfd)
+ os.close(targetfd_save)
+ self.syscapture.done()
+ self.tmpfile.close()
+
+ def suspend(self):
+ self.syscapture.suspend()
+ os.dup2(self.targetfd_save, self.targetfd)
+
+ def resume(self):
+ self.syscapture.resume()
+ os.dup2(self.tmpfile_fd, self.targetfd)
+
+ def writeorg(self, data):
+ """ write to original file descriptor. """
+ if py.builtin._istext(data):
+ data = data.encode("utf8") # XXX use encoding of original stream
+ os.write(self.targetfd_save, data)
+
+
+class SysCapture:
+ def __init__(self, fd, tmpfile=None):
+ name = patchsysdict[fd]
+ self._old = getattr(sys, name)
+ self.name = name
+ if tmpfile is None:
+ if name == "stdin":
+ tmpfile = DontReadFromInput()
+ else:
+ tmpfile = TextIO()
+ self.tmpfile = tmpfile
+
+ def start(self):
+ setattr(sys, self.name, self.tmpfile)
+
+ def snap(self):
+ f = self.tmpfile
+ res = f.getvalue()
+ f.truncate(0)
+ f.seek(0)
+ return res
+
+ def done(self):
+ setattr(sys, self.name, self._old)
+ del self._old
+ self.tmpfile.close()
+
+ def suspend(self):
+ setattr(sys, self.name, self._old)
+
+ def resume(self):
+ setattr(sys, self.name, self.tmpfile)
+
+ def writeorg(self, data):
+ self._old.write(data)
+ self._old.flush()
+
+
+class DontReadFromInput:
+ """Temporary stub class. Ideally when stdin is accessed, the
+ capturing should be turned off, with possibly all data captured
+ so far sent to the screen. This should be configurable, though,
+ because in automated test runs it is better to crash than
+ hang indefinitely.
+ """
+
+ encoding = None
+
+ def read(self, *args):
+ raise IOError("reading from stdin while output is captured")
+ readline = read
+ readlines = read
+ __iter__ = read
+
+ def fileno(self):
+ raise ValueError("redirected Stdin is pseudofile, has no fileno()")
+
+ def isatty(self):
+ return False
+
+ def close(self):
+ pass
+
+ @property
+ def buffer(self):
+ if sys.version_info >= (3,0):
+ return self
+ else:
+ raise AttributeError('redirected stdin has no attribute buffer')
+
+
+def _readline_workaround():
+ """
+ Ensure readline is imported so that it attaches to the correct stdio
+ handles on Windows.
+
+ Pdb uses readline support where available--when not running from the Python
+ prompt, the readline module is not imported until running the pdb REPL. If
+ running pytest with the --pdb option this means the readline module is not
+ imported until after I/O capture has been started.
+
+ This is a problem for pyreadline, which is often used to implement readline
+ support on Windows, as it does not attach to the correct handles for stdout
+ and/or stdin if they have been redirected by the FDCapture mechanism. This
+ workaround ensures that readline is imported before I/O capture is setup so
+ that it can attach to the actual stdin/out for the console.
+
+ See https://github.com/pytest-dev/pytest/pull/1281
+ """
+
+ if not sys.platform.startswith('win32'):
+ return
+ try:
+ import readline # noqa
+ except ImportError:
+ pass
diff --git a/lib/spack/external/_pytest/compat.py b/lib/spack/external/_pytest/compat.py
new file mode 100644
index 0000000000..51fc3bc5c1
--- /dev/null
+++ b/lib/spack/external/_pytest/compat.py
@@ -0,0 +1,230 @@
+"""
+python version compatibility code
+"""
+import sys
+import inspect
+import types
+import re
+import functools
+
+import py
+
+import _pytest
+
+
+
+try:
+ import enum
+except ImportError: # pragma: no cover
+ # Only available in Python 3.4+ or as a backport
+ enum = None
+
+_PY3 = sys.version_info > (3, 0)
+_PY2 = not _PY3
+
+
+NoneType = type(None)
+NOTSET = object()
+
+if hasattr(inspect, 'signature'):
+ def _format_args(func):
+ return str(inspect.signature(func))
+else:
+ def _format_args(func):
+ return inspect.formatargspec(*inspect.getargspec(func))
+
+isfunction = inspect.isfunction
+isclass = inspect.isclass
+# used to work around a python2 exception info leak
+exc_clear = getattr(sys, 'exc_clear', lambda: None)
+# The type of re.compile objects is not exposed in Python.
+REGEX_TYPE = type(re.compile(''))
+
+
+def is_generator(func):
+ try:
+ return _pytest._code.getrawcode(func).co_flags & 32 # generator function
+ except AttributeError: # builtin functions have no bytecode
+ # assume them to not be generators
+ return False
+
+
+def getlocation(function, curdir):
+ import inspect
+ fn = py.path.local(inspect.getfile(function))
+ lineno = py.builtin._getcode(function).co_firstlineno
+ if fn.relto(curdir):
+ fn = fn.relto(curdir)
+ return "%s:%d" %(fn, lineno+1)
+
+
+def num_mock_patch_args(function):
+ """ return number of arguments used up by mock arguments (if any) """
+ patchings = getattr(function, "patchings", None)
+ if not patchings:
+ return 0
+ mock = sys.modules.get("mock", sys.modules.get("unittest.mock", None))
+ if mock is not None:
+ return len([p for p in patchings
+ if not p.attribute_name and p.new is mock.DEFAULT])
+ return len(patchings)
+
+
+def getfuncargnames(function, startindex=None):
+ # XXX merge with main.py's varnames
+ #assert not isclass(function)
+ realfunction = function
+ while hasattr(realfunction, "__wrapped__"):
+ realfunction = realfunction.__wrapped__
+ if startindex is None:
+ startindex = inspect.ismethod(function) and 1 or 0
+ if realfunction != function:
+ startindex += num_mock_patch_args(function)
+ function = realfunction
+ if isinstance(function, functools.partial):
+ argnames = inspect.getargs(_pytest._code.getrawcode(function.func))[0]
+ partial = function
+ argnames = argnames[len(partial.args):]
+ if partial.keywords:
+ for kw in partial.keywords:
+ argnames.remove(kw)
+ else:
+ argnames = inspect.getargs(_pytest._code.getrawcode(function))[0]
+ defaults = getattr(function, 'func_defaults',
+ getattr(function, '__defaults__', None)) or ()
+ numdefaults = len(defaults)
+ if numdefaults:
+ return tuple(argnames[startindex:-numdefaults])
+ return tuple(argnames[startindex:])
+
+
+
+if sys.version_info[:2] == (2, 6):
+ def isclass(object):
+ """ Return true if the object is a class. Overrides inspect.isclass for
+ python 2.6 because it will return True for objects which always return
+ something on __getattr__ calls (see #1035).
+ Backport of https://hg.python.org/cpython/rev/35bf8f7a8edc
+ """
+ return isinstance(object, (type, types.ClassType))
+
+
+if _PY3:
+ import codecs
+
+ STRING_TYPES = bytes, str
+
+ def _escape_strings(val):
+ """If val is pure ascii, returns it as a str(). Otherwise, escapes
+ bytes objects into a sequence of escaped bytes:
+
+ b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6'
+
+ and escapes unicode objects into a sequence of escaped unicode
+ ids, e.g.:
+
+ '4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944'
+
+ note:
+ the obvious "v.decode('unicode-escape')" will return
+ valid utf-8 unicode if it finds them in bytes, but we
+ want to return escaped bytes for any byte, even if they match
+ a utf-8 string.
+
+ """
+ if isinstance(val, bytes):
+ if val:
+ # source: http://goo.gl/bGsnwC
+ encoded_bytes, _ = codecs.escape_encode(val)
+ return encoded_bytes.decode('ascii')
+ else:
+ # empty bytes crashes codecs.escape_encode (#1087)
+ return ''
+ else:
+ return val.encode('unicode_escape').decode('ascii')
+else:
+ STRING_TYPES = bytes, str, unicode
+
+ def _escape_strings(val):
+ """In py2 bytes and str are the same type, so return if it's a bytes
+ object, return it unchanged if it is a full ascii string,
+ otherwise escape it into its binary form.
+
+ If it's a unicode string, change the unicode characters into
+ unicode escapes.
+
+ """
+ if isinstance(val, bytes):
+ try:
+ return val.encode('ascii')
+ except UnicodeDecodeError:
+ return val.encode('string-escape')
+ else:
+ return val.encode('unicode-escape')
+
+
+def get_real_func(obj):
+ """ gets the real function object of the (possibly) wrapped object by
+ functools.wraps or functools.partial.
+ """
+ while hasattr(obj, "__wrapped__"):
+ obj = obj.__wrapped__
+ if isinstance(obj, functools.partial):
+ obj = obj.func
+ return obj
+
+
+def getfslineno(obj):
+ # xxx let decorators etc specify a sane ordering
+ obj = get_real_func(obj)
+ if hasattr(obj, 'place_as'):
+ obj = obj.place_as
+ fslineno = _pytest._code.getfslineno(obj)
+ assert isinstance(fslineno[1], int), obj
+ return fslineno
+
+
+def getimfunc(func):
+ try:
+ return func.__func__
+ except AttributeError:
+ try:
+ return func.im_func
+ except AttributeError:
+ return func
+
+
+def safe_getattr(object, name, default):
+ """ Like getattr but return default upon any Exception.
+
+ Attribute access can potentially fail for 'evil' Python objects.
+ See issue214
+ """
+ try:
+ return getattr(object, name, default)
+ except Exception:
+ return default
+
+
+def _is_unittest_unexpected_success_a_failure():
+ """Return if the test suite should fail if a @expectedFailure unittest test PASSES.
+
+ From https://docs.python.org/3/library/unittest.html?highlight=unittest#unittest.TestResult.wasSuccessful:
+ Changed in version 3.4: Returns False if there were any
+ unexpectedSuccesses from tests marked with the expectedFailure() decorator.
+ """
+ return sys.version_info >= (3, 4)
+
+
+if _PY3:
+ def safe_str(v):
+ """returns v as string"""
+ return str(v)
+else:
+ def safe_str(v):
+ """returns v as string, converting to ascii if necessary"""
+ try:
+ return str(v)
+ except UnicodeError:
+ errors = 'replace'
+ return v.encode('ascii', errors)
diff --git a/lib/spack/external/_pytest/config.py b/lib/spack/external/_pytest/config.py
new file mode 100644
index 0000000000..fe386ed0b1
--- /dev/null
+++ b/lib/spack/external/_pytest/config.py
@@ -0,0 +1,1340 @@
+""" command line options, ini-file and conftest.py processing. """
+import argparse
+import shlex
+import traceback
+import types
+import warnings
+
+import py
+# DON't import pytest here because it causes import cycle troubles
+import sys, os
+import _pytest._code
+import _pytest.hookspec # the extension point definitions
+import _pytest.assertion
+from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker
+from _pytest.compat import safe_str
+
+hookimpl = HookimplMarker("pytest")
+hookspec = HookspecMarker("pytest")
+
+# pytest startup
+#
+
+
+class ConftestImportFailure(Exception):
+ def __init__(self, path, excinfo):
+ Exception.__init__(self, path, excinfo)
+ self.path = path
+ self.excinfo = excinfo
+
+ def __str__(self):
+ etype, evalue, etb = self.excinfo
+ formatted = traceback.format_tb(etb)
+ # The level of the tracebacks we want to print is hand crafted :(
+ return repr(evalue) + '\n' + ''.join(formatted[2:])
+
+
+def main(args=None, plugins=None):
+ """ return exit code, after performing an in-process test run.
+
+ :arg args: list of command line arguments.
+
+ :arg plugins: list of plugin objects to be auto-registered during
+ initialization.
+ """
+ try:
+ try:
+ config = _prepareconfig(args, plugins)
+ except ConftestImportFailure as e:
+ tw = py.io.TerminalWriter(sys.stderr)
+ for line in traceback.format_exception(*e.excinfo):
+ tw.line(line.rstrip(), red=True)
+ tw.line("ERROR: could not load %s\n" % (e.path), red=True)
+ return 4
+ else:
+ try:
+ config.pluginmanager.check_pending()
+ return config.hook.pytest_cmdline_main(config=config)
+ finally:
+ config._ensure_unconfigure()
+ except UsageError as e:
+ for msg in e.args:
+ sys.stderr.write("ERROR: %s\n" %(msg,))
+ return 4
+
+class cmdline: # compatibility namespace
+ main = staticmethod(main)
+
+
+class UsageError(Exception):
+ """ error in pytest usage or invocation"""
+
+
+def filename_arg(path, optname):
+ """ Argparse type validator for filename arguments.
+
+ :path: path of filename
+ :optname: name of the option
+ """
+ if os.path.isdir(path):
+ raise UsageError("{0} must be a filename, given: {1}".format(optname, path))
+ return path
+
+
+def directory_arg(path, optname):
+ """Argparse type validator for directory arguments.
+
+ :path: path of directory
+ :optname: name of the option
+ """
+ if not os.path.isdir(path):
+ raise UsageError("{0} must be a directory, given: {1}".format(optname, path))
+ return path
+
+
+_preinit = []
+
+default_plugins = (
+ "mark main terminal runner python fixtures debugging unittest capture skipping "
+ "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion "
+ "junitxml resultlog doctest cacheprovider freeze_support "
+ "setuponly setupplan").split()
+
+builtin_plugins = set(default_plugins)
+builtin_plugins.add("pytester")
+
+
+def _preloadplugins():
+ assert not _preinit
+ _preinit.append(get_config())
+
+def get_config():
+ if _preinit:
+ return _preinit.pop(0)
+ # subsequent calls to main will create a fresh instance
+ pluginmanager = PytestPluginManager()
+ config = Config(pluginmanager)
+ for spec in default_plugins:
+ pluginmanager.import_plugin(spec)
+ return config
+
+def get_plugin_manager():
+ """
+ Obtain a new instance of the
+ :py:class:`_pytest.config.PytestPluginManager`, with default plugins
+ already loaded.
+
+ This function can be used by integration with other tools, like hooking
+ into pytest to run tests into an IDE.
+ """
+ return get_config().pluginmanager
+
+def _prepareconfig(args=None, plugins=None):
+ warning = None
+ if args is None:
+ args = sys.argv[1:]
+ elif isinstance(args, py.path.local):
+ args = [str(args)]
+ elif not isinstance(args, (tuple, list)):
+ if not isinstance(args, str):
+ raise ValueError("not a string or argument list: %r" % (args,))
+ args = shlex.split(args, posix=sys.platform != "win32")
+ from _pytest import deprecated
+ warning = deprecated.MAIN_STR_ARGS
+ config = get_config()
+ pluginmanager = config.pluginmanager
+ try:
+ if plugins:
+ for plugin in plugins:
+ if isinstance(plugin, py.builtin._basestring):
+ pluginmanager.consider_pluginarg(plugin)
+ else:
+ pluginmanager.register(plugin)
+ if warning:
+ config.warn('C1', warning)
+ return pluginmanager.hook.pytest_cmdline_parse(
+ pluginmanager=pluginmanager, args=args)
+ except BaseException:
+ config._ensure_unconfigure()
+ raise
+
+
+class PytestPluginManager(PluginManager):
+ """
+ Overwrites :py:class:`pluggy.PluginManager` to add pytest-specific
+ functionality:
+
+ * loading plugins from the command line, ``PYTEST_PLUGIN`` env variable and
+ ``pytest_plugins`` global variables found in plugins being loaded;
+ * ``conftest.py`` loading during start-up;
+ """
+ def __init__(self):
+ super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_")
+ self._conftest_plugins = set()
+
+ # state related to local conftest plugins
+ self._path2confmods = {}
+ self._conftestpath2mod = {}
+ self._confcutdir = None
+ self._noconftest = False
+ self._duplicatepaths = set()
+
+ self.add_hookspecs(_pytest.hookspec)
+ self.register(self)
+ if os.environ.get('PYTEST_DEBUG'):
+ err = sys.stderr
+ encoding = getattr(err, 'encoding', 'utf8')
+ try:
+ err = py.io.dupfile(err, encoding=encoding)
+ except Exception:
+ pass
+ self.trace.root.setwriter(err.write)
+ self.enable_tracing()
+
+ # Config._consider_importhook will set a real object if required.
+ self.rewrite_hook = _pytest.assertion.DummyRewriteHook()
+
+ def addhooks(self, module_or_class):
+ """
+ .. deprecated:: 2.8
+
+ Use :py:meth:`pluggy.PluginManager.add_hookspecs` instead.
+ """
+ warning = dict(code="I2",
+ fslocation=_pytest._code.getfslineno(sys._getframe(1)),
+ nodeid=None,
+ message="use pluginmanager.add_hookspecs instead of "
+ "deprecated addhooks() method.")
+ self._warn(warning)
+ return self.add_hookspecs(module_or_class)
+
+ def parse_hookimpl_opts(self, plugin, name):
+ # pytest hooks are always prefixed with pytest_
+ # so we avoid accessing possibly non-readable attributes
+ # (see issue #1073)
+ if not name.startswith("pytest_"):
+ return
+ # ignore some historic special names which can not be hooks anyway
+ if name == "pytest_plugins" or name.startswith("pytest_funcarg__"):
+ return
+
+ method = getattr(plugin, name)
+ opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
+ if opts is not None:
+ for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
+ opts.setdefault(name, hasattr(method, name))
+ return opts
+
+ def parse_hookspec_opts(self, module_or_class, name):
+ opts = super(PytestPluginManager, self).parse_hookspec_opts(
+ module_or_class, name)
+ if opts is None:
+ method = getattr(module_or_class, name)
+ if name.startswith("pytest_"):
+ opts = {"firstresult": hasattr(method, "firstresult"),
+ "historic": hasattr(method, "historic")}
+ return opts
+
+ def _verify_hook(self, hook, hookmethod):
+ super(PytestPluginManager, self)._verify_hook(hook, hookmethod)
+ if "__multicall__" in hookmethod.argnames:
+ fslineno = _pytest._code.getfslineno(hookmethod.function)
+ warning = dict(code="I1",
+ fslocation=fslineno,
+ nodeid=None,
+ message="%r hook uses deprecated __multicall__ "
+ "argument" % (hook.name))
+ self._warn(warning)
+
+ def register(self, plugin, name=None):
+ ret = super(PytestPluginManager, self).register(plugin, name)
+ if ret:
+ self.hook.pytest_plugin_registered.call_historic(
+ kwargs=dict(plugin=plugin, manager=self))
+ return ret
+
+ def getplugin(self, name):
+ # support deprecated naming because plugins (xdist e.g.) use it
+ return self.get_plugin(name)
+
+ def hasplugin(self, name):
+ """Return True if the plugin with the given name is registered."""
+ return bool(self.get_plugin(name))
+
+ def pytest_configure(self, config):
+ # XXX now that the pluginmanager exposes hookimpl(tryfirst...)
+ # we should remove tryfirst/trylast as markers
+ config.addinivalue_line("markers",
+ "tryfirst: mark a hook implementation function such that the "
+ "plugin machinery will try to call it first/as early as possible.")
+ config.addinivalue_line("markers",
+ "trylast: mark a hook implementation function such that the "
+ "plugin machinery will try to call it last/as late as possible.")
+
+ def _warn(self, message):
+ kwargs = message if isinstance(message, dict) else {
+ 'code': 'I1',
+ 'message': message,
+ 'fslocation': None,
+ 'nodeid': None,
+ }
+ self.hook.pytest_logwarning.call_historic(kwargs=kwargs)
+
+ #
+ # internal API for local conftest plugin handling
+ #
+ def _set_initial_conftests(self, namespace):
+ """ load initial conftest files given a preparsed "namespace".
+ As conftest files may add their own command line options
+ which have arguments ('--my-opt somepath') we might get some
+ false positives. All builtin and 3rd party plugins will have
+ been loaded, however, so common options will not confuse our logic
+ here.
+ """
+ current = py.path.local()
+ self._confcutdir = current.join(namespace.confcutdir, abs=True) \
+ if namespace.confcutdir else None
+ self._noconftest = namespace.noconftest
+ testpaths = namespace.file_or_dir
+ foundanchor = False
+ for path in testpaths:
+ path = str(path)
+ # remove node-id syntax
+ i = path.find("::")
+ if i != -1:
+ path = path[:i]
+ anchor = current.join(path, abs=1)
+ if exists(anchor): # we found some file object
+ self._try_load_conftest(anchor)
+ foundanchor = True
+ if not foundanchor:
+ self._try_load_conftest(current)
+
+ def _try_load_conftest(self, anchor):
+ self._getconftestmodules(anchor)
+ # let's also consider test* subdirs
+ if anchor.check(dir=1):
+ for x in anchor.listdir("test*"):
+ if x.check(dir=1):
+ self._getconftestmodules(x)
+
+ def _getconftestmodules(self, path):
+ if self._noconftest:
+ return []
+ try:
+ return self._path2confmods[path]
+ except KeyError:
+ if path.isfile():
+ clist = self._getconftestmodules(path.dirpath())
+ else:
+ # XXX these days we may rather want to use config.rootdir
+ # and allow users to opt into looking into the rootdir parent
+ # directories instead of requiring to specify confcutdir
+ clist = []
+ for parent in path.parts():
+ if self._confcutdir and self._confcutdir.relto(parent):
+ continue
+ conftestpath = parent.join("conftest.py")
+ if conftestpath.isfile():
+ mod = self._importconftest(conftestpath)
+ clist.append(mod)
+
+ self._path2confmods[path] = clist
+ return clist
+
+ def _rget_with_confmod(self, name, path):
+ modules = self._getconftestmodules(path)
+ for mod in reversed(modules):
+ try:
+ return mod, getattr(mod, name)
+ except AttributeError:
+ continue
+ raise KeyError(name)
+
+ def _importconftest(self, conftestpath):
+ try:
+ return self._conftestpath2mod[conftestpath]
+ except KeyError:
+ pkgpath = conftestpath.pypkgpath()
+ if pkgpath is None:
+ _ensure_removed_sysmodule(conftestpath.purebasename)
+ try:
+ mod = conftestpath.pyimport()
+ except Exception:
+ raise ConftestImportFailure(conftestpath, sys.exc_info())
+
+ self._conftest_plugins.add(mod)
+ self._conftestpath2mod[conftestpath] = mod
+ dirpath = conftestpath.dirpath()
+ if dirpath in self._path2confmods:
+ for path, mods in self._path2confmods.items():
+ if path and path.relto(dirpath) or path == dirpath:
+ assert mod not in mods
+ mods.append(mod)
+ self.trace("loaded conftestmodule %r" %(mod))
+ self.consider_conftest(mod)
+ return mod
+
+ #
+ # API for bootstrapping plugin loading
+ #
+ #
+
+ def consider_preparse(self, args):
+ for opt1,opt2 in zip(args, args[1:]):
+ if opt1 == "-p":
+ self.consider_pluginarg(opt2)
+
+ def consider_pluginarg(self, arg):
+ if arg.startswith("no:"):
+ name = arg[3:]
+ self.set_blocked(name)
+ if not name.startswith("pytest_"):
+ self.set_blocked("pytest_" + name)
+ else:
+ self.import_plugin(arg)
+
+ def consider_conftest(self, conftestmodule):
+ if self.register(conftestmodule, name=conftestmodule.__file__):
+ self.consider_module(conftestmodule)
+
+ def consider_env(self):
+ self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
+
+ def consider_module(self, mod):
+ plugins = getattr(mod, 'pytest_plugins', [])
+ if isinstance(plugins, str):
+ plugins = [plugins]
+ self.rewrite_hook.mark_rewrite(*plugins)
+ self._import_plugin_specs(plugins)
+
+ def _import_plugin_specs(self, spec):
+ if spec:
+ if isinstance(spec, str):
+ spec = spec.split(",")
+ for import_spec in spec:
+ self.import_plugin(import_spec)
+
+ def import_plugin(self, modname):
+ # most often modname refers to builtin modules, e.g. "pytester",
+ # "terminal" or "capture". Those plugins are registered under their
+ # basename for historic purposes but must be imported with the
+ # _pytest prefix.
+ assert isinstance(modname, str)
+ if self.get_plugin(modname) is not None:
+ return
+ if modname in builtin_plugins:
+ importspec = "_pytest." + modname
+ else:
+ importspec = modname
+ try:
+ __import__(importspec)
+ except ImportError as e:
+ new_exc = ImportError('Error importing plugin "%s": %s' % (modname, safe_str(e.args[0])))
+ # copy over name and path attributes
+ for attr in ('name', 'path'):
+ if hasattr(e, attr):
+ setattr(new_exc, attr, getattr(e, attr))
+ raise new_exc
+ except Exception as e:
+ import pytest
+ if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
+ raise
+ self._warn("skipped plugin %r: %s" %((modname, e.msg)))
+ else:
+ mod = sys.modules[importspec]
+ self.register(mod, modname)
+ self.consider_module(mod)
+
+
+class Parser:
+ """ Parser for command line arguments and ini-file values.
+
+ :ivar extra_info: dict of generic param -> value to display in case
+ there's an error processing the command line arguments.
+ """
+
+ def __init__(self, usage=None, processopt=None):
+ self._anonymous = OptionGroup("custom options", parser=self)
+ self._groups = []
+ self._processopt = processopt
+ self._usage = usage
+ self._inidict = {}
+ self._ininames = []
+ self.extra_info = {}
+
+ def processoption(self, option):
+ if self._processopt:
+ if option.dest:
+ self._processopt(option)
+
+ def getgroup(self, name, description="", after=None):
+ """ get (or create) a named option Group.
+
+ :name: name of the option group.
+ :description: long description for --help output.
+ :after: name of other group, used for ordering --help output.
+
+ The returned group object has an ``addoption`` method with the same
+ signature as :py:func:`parser.addoption
+ <_pytest.config.Parser.addoption>` but will be shown in the
+ respective group in the output of ``pytest. --help``.
+ """
+ for group in self._groups:
+ if group.name == name:
+ return group
+ group = OptionGroup(name, description, parser=self)
+ i = 0
+ for i, grp in enumerate(self._groups):
+ if grp.name == after:
+ break
+ self._groups.insert(i+1, group)
+ return group
+
+ def addoption(self, *opts, **attrs):
+ """ register a command line option.
+
+ :opts: option names, can be short or long options.
+ :attrs: same attributes which the ``add_option()`` function of the
+ `argparse library
+ <http://docs.python.org/2/library/argparse.html>`_
+ accepts.
+
+ After command line parsing options are available on the pytest config
+ object via ``config.option.NAME`` where ``NAME`` is usually set
+ by passing a ``dest`` attribute, for example
+ ``addoption("--long", dest="NAME", ...)``.
+ """
+ self._anonymous.addoption(*opts, **attrs)
+
+ def parse(self, args, namespace=None):
+ from _pytest._argcomplete import try_argcomplete
+ self.optparser = self._getparser()
+ try_argcomplete(self.optparser)
+ return self.optparser.parse_args([str(x) for x in args], namespace=namespace)
+
+ def _getparser(self):
+ from _pytest._argcomplete import filescompleter
+ optparser = MyOptionParser(self, self.extra_info)
+ groups = self._groups + [self._anonymous]
+ for group in groups:
+ if group.options:
+ desc = group.description or group.name
+ arggroup = optparser.add_argument_group(desc)
+ for option in group.options:
+ n = option.names()
+ a = option.attrs()
+ arggroup.add_argument(*n, **a)
+ # bash like autocompletion for dirs (appending '/')
+ optparser.add_argument(FILE_OR_DIR, nargs='*').completer=filescompleter
+ return optparser
+
+ def parse_setoption(self, args, option, namespace=None):
+ parsedoption = self.parse(args, namespace=namespace)
+ for name, value in parsedoption.__dict__.items():
+ setattr(option, name, value)
+ return getattr(parsedoption, FILE_OR_DIR)
+
+ def parse_known_args(self, args, namespace=None):
+ """parses and returns a namespace object with known arguments at this
+ point.
+ """
+ return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
+
+ def parse_known_and_unknown_args(self, args, namespace=None):
+ """parses and returns a namespace object with known arguments, and
+ the remaining arguments unknown at this point.
+ """
+ optparser = self._getparser()
+ args = [str(x) for x in args]
+ return optparser.parse_known_args(args, namespace=namespace)
+
+ def addini(self, name, help, type=None, default=None):
+ """ register an ini-file option.
+
+ :name: name of the ini-variable
+ :type: type of the variable, can be ``pathlist``, ``args``, ``linelist``
+ or ``bool``.
+ :default: default value if no ini-file option exists but is queried.
+
+ The value of ini-variables can be retrieved via a call to
+ :py:func:`config.getini(name) <_pytest.config.Config.getini>`.
+ """
+ assert type in (None, "pathlist", "args", "linelist", "bool")
+ self._inidict[name] = (help, type, default)
+ self._ininames.append(name)
+
+
+class ArgumentError(Exception):
+ """
+ Raised if an Argument instance is created with invalid or
+ inconsistent arguments.
+ """
+
+ def __init__(self, msg, option):
+ self.msg = msg
+ self.option_id = str(option)
+
+ def __str__(self):
+ if self.option_id:
+ return "option %s: %s" % (self.option_id, self.msg)
+ else:
+ return self.msg
+
+
+class Argument:
+ """class that mimics the necessary behaviour of optparse.Option
+
+ its currently a least effort implementation
+ and ignoring choices and integer prefixes
+ https://docs.python.org/3/library/optparse.html#optparse-standard-option-types
+ """
+ _typ_map = {
+ 'int': int,
+ 'string': str,
+ 'float': float,
+ 'complex': complex,
+ }
+
+ def __init__(self, *names, **attrs):
+ """store parms in private vars for use in add_argument"""
+ self._attrs = attrs
+ self._short_opts = []
+ self._long_opts = []
+ self.dest = attrs.get('dest')
+ if '%default' in (attrs.get('help') or ''):
+ warnings.warn(
+ 'pytest now uses argparse. "%default" should be'
+ ' changed to "%(default)s" ',
+ DeprecationWarning,
+ stacklevel=3)
+ try:
+ typ = attrs['type']
+ except KeyError:
+ pass
+ else:
+ # this might raise a keyerror as well, don't want to catch that
+ if isinstance(typ, py.builtin._basestring):
+ if typ == 'choice':
+ warnings.warn(
+ 'type argument to addoption() is a string %r.'
+ ' For parsearg this is optional and when supplied'
+ ' should be a type.'
+ ' (options: %s)' % (typ, names),
+ DeprecationWarning,
+ stacklevel=3)
+ # argparse expects a type here take it from
+ # the type of the first element
+ attrs['type'] = type(attrs['choices'][0])
+ else:
+ warnings.warn(
+ 'type argument to addoption() is a string %r.'
+ ' For parsearg this should be a type.'
+ ' (options: %s)' % (typ, names),
+ DeprecationWarning,
+ stacklevel=3)
+ attrs['type'] = Argument._typ_map[typ]
+ # used in test_parseopt -> test_parse_defaultgetter
+ self.type = attrs['type']
+ else:
+ self.type = typ
+ try:
+ # attribute existence is tested in Config._processopt
+ self.default = attrs['default']
+ except KeyError:
+ pass
+ self._set_opt_strings(names)
+ if not self.dest:
+ if self._long_opts:
+ self.dest = self._long_opts[0][2:].replace('-', '_')
+ else:
+ try:
+ self.dest = self._short_opts[0][1:]
+ except IndexError:
+ raise ArgumentError(
+ 'need a long or short option', self)
+
+ def names(self):
+ return self._short_opts + self._long_opts
+
+ def attrs(self):
+ # update any attributes set by processopt
+ attrs = 'default dest help'.split()
+ if self.dest:
+ attrs.append(self.dest)
+ for attr in attrs:
+ try:
+ self._attrs[attr] = getattr(self, attr)
+ except AttributeError:
+ pass
+ if self._attrs.get('help'):
+ a = self._attrs['help']
+ a = a.replace('%default', '%(default)s')
+ #a = a.replace('%prog', '%(prog)s')
+ self._attrs['help'] = a
+ return self._attrs
+
+ def _set_opt_strings(self, opts):
+ """directly from optparse
+
+ might not be necessary as this is passed to argparse later on"""
+ for opt in opts:
+ if len(opt) < 2:
+ raise ArgumentError(
+ "invalid option string %r: "
+ "must be at least two characters long" % opt, self)
+ elif len(opt) == 2:
+ if not (opt[0] == "-" and opt[1] != "-"):
+ raise ArgumentError(
+ "invalid short option string %r: "
+ "must be of the form -x, (x any non-dash char)" % opt,
+ self)
+ self._short_opts.append(opt)
+ else:
+ if not (opt[0:2] == "--" and opt[2] != "-"):
+ raise ArgumentError(
+ "invalid long option string %r: "
+ "must start with --, followed by non-dash" % opt,
+ self)
+ self._long_opts.append(opt)
+
+ def __repr__(self):
+ args = []
+ if self._short_opts:
+ args += ['_short_opts: ' + repr(self._short_opts)]
+ if self._long_opts:
+ args += ['_long_opts: ' + repr(self._long_opts)]
+ args += ['dest: ' + repr(self.dest)]
+ if hasattr(self, 'type'):
+ args += ['type: ' + repr(self.type)]
+ if hasattr(self, 'default'):
+ args += ['default: ' + repr(self.default)]
+ return 'Argument({0})'.format(', '.join(args))
+
+
+class OptionGroup:
+ def __init__(self, name, description="", parser=None):
+ self.name = name
+ self.description = description
+ self.options = []
+ self.parser = parser
+
+ def addoption(self, *optnames, **attrs):
+ """ add an option to this group.
+
+ if a shortened version of a long option is specified it will
+ be suppressed in the help. addoption('--twowords', '--two-words')
+ results in help showing '--two-words' only, but --twowords gets
+ accepted **and** the automatic destination is in args.twowords
+ """
+ conflict = set(optnames).intersection(
+ name for opt in self.options for name in opt.names())
+ if conflict:
+ raise ValueError("option names %s already added" % conflict)
+ option = Argument(*optnames, **attrs)
+ self._addoption_instance(option, shortupper=False)
+
+ def _addoption(self, *optnames, **attrs):
+ option = Argument(*optnames, **attrs)
+ self._addoption_instance(option, shortupper=True)
+
+ def _addoption_instance(self, option, shortupper=False):
+ if not shortupper:
+ for opt in option._short_opts:
+ if opt[0] == '-' and opt[1].islower():
+ raise ValueError("lowercase shortoptions reserved")
+ if self.parser:
+ self.parser.processoption(option)
+ self.options.append(option)
+
+
+class MyOptionParser(argparse.ArgumentParser):
+ def __init__(self, parser, extra_info=None):
+ if not extra_info:
+ extra_info = {}
+ self._parser = parser
+ argparse.ArgumentParser.__init__(self, usage=parser._usage,
+ add_help=False, formatter_class=DropShorterLongHelpFormatter)
+ # extra_info is a dict of (param -> value) to display if there's
+ # an usage error to provide more contextual information to the user
+ self.extra_info = extra_info
+
+ def parse_args(self, args=None, namespace=None):
+ """allow splitting of positional arguments"""
+ args, argv = self.parse_known_args(args, namespace)
+ if argv:
+ for arg in argv:
+ if arg and arg[0] == '-':
+ lines = ['unrecognized arguments: %s' % (' '.join(argv))]
+ for k, v in sorted(self.extra_info.items()):
+ lines.append(' %s: %s' % (k, v))
+ self.error('\n'.join(lines))
+ getattr(args, FILE_OR_DIR).extend(argv)
+ return args
+
+
+class DropShorterLongHelpFormatter(argparse.HelpFormatter):
+ """shorten help for long options that differ only in extra hyphens
+
+ - collapse **long** options that are the same except for extra hyphens
+ - special action attribute map_long_option allows surpressing additional
+ long options
+ - shortcut if there are only two options and one of them is a short one
+ - cache result on action object as this is called at least 2 times
+ """
+ def _format_action_invocation(self, action):
+ orgstr = argparse.HelpFormatter._format_action_invocation(self, action)
+ if orgstr and orgstr[0] != '-': # only optional arguments
+ return orgstr
+ res = getattr(action, '_formatted_action_invocation', None)
+ if res:
+ return res
+ options = orgstr.split(', ')
+ if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2):
+ # a shortcut for '-h, --help' or '--abc', '-a'
+ action._formatted_action_invocation = orgstr
+ return orgstr
+ return_list = []
+ option_map = getattr(action, 'map_long_option', {})
+ if option_map is None:
+ option_map = {}
+ short_long = {}
+ for option in options:
+ if len(option) == 2 or option[2] == ' ':
+ continue
+ if not option.startswith('--'):
+ raise ArgumentError('long optional argument without "--": [%s]'
+ % (option), self)
+ xxoption = option[2:]
+ if xxoption.split()[0] not in option_map:
+ shortened = xxoption.replace('-', '')
+ if shortened not in short_long or \
+ len(short_long[shortened]) < len(xxoption):
+ short_long[shortened] = xxoption
+ # now short_long has been filled out to the longest with dashes
+ # **and** we keep the right option ordering from add_argument
+ for option in options: #
+ if len(option) == 2 or option[2] == ' ':
+ return_list.append(option)
+ if option[2:] == short_long.get(option.replace('-', '')):
+ return_list.append(option.replace(' ', '=', 1))
+ action._formatted_action_invocation = ', '.join(return_list)
+ return action._formatted_action_invocation
+
+
+
+def _ensure_removed_sysmodule(modname):
+ try:
+ del sys.modules[modname]
+ except KeyError:
+ pass
+
+class CmdOptions(object):
+ """ holds cmdline options as attributes."""
+ def __init__(self, values=()):
+ self.__dict__.update(values)
+ def __repr__(self):
+ return "<CmdOptions %r>" %(self.__dict__,)
+ def copy(self):
+ return CmdOptions(self.__dict__)
+
+class Notset:
+ def __repr__(self):
+ return "<NOTSET>"
+
+
+notset = Notset()
+FILE_OR_DIR = 'file_or_dir'
+
+
+class Config(object):
+ """ access to configuration values, pluginmanager and plugin hooks. """
+
+ def __init__(self, pluginmanager):
+ #: access to command line option as attributes.
+ #: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
+ self.option = CmdOptions()
+ _a = FILE_OR_DIR
+ self._parser = Parser(
+ usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a),
+ processopt=self._processopt,
+ )
+ #: a pluginmanager instance
+ self.pluginmanager = pluginmanager
+ self.trace = self.pluginmanager.trace.root.get("config")
+ self.hook = self.pluginmanager.hook
+ self._inicache = {}
+ self._opt2dest = {}
+ self._cleanup = []
+ self._warn = self.pluginmanager._warn
+ self.pluginmanager.register(self, "pytestconfig")
+ self._configured = False
+
+ def do_setns(dic):
+ import pytest
+ setns(pytest, dic)
+
+ self.hook.pytest_namespace.call_historic(do_setns, {})
+ self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
+
+ def add_cleanup(self, func):
+ """ Add a function to be called when the config object gets out of
+ use (usually coninciding with pytest_unconfigure)."""
+ self._cleanup.append(func)
+
+ def _do_configure(self):
+ assert not self._configured
+ self._configured = True
+ self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
+
+ def _ensure_unconfigure(self):
+ if self._configured:
+ self._configured = False
+ self.hook.pytest_unconfigure(config=self)
+ self.hook.pytest_configure._call_history = []
+ while self._cleanup:
+ fin = self._cleanup.pop()
+ fin()
+
+ def warn(self, code, message, fslocation=None):
+ """ generate a warning for this test session. """
+ self.hook.pytest_logwarning.call_historic(kwargs=dict(
+ code=code, message=message,
+ fslocation=fslocation, nodeid=None))
+
+ def get_terminal_writer(self):
+ return self.pluginmanager.get_plugin("terminalreporter")._tw
+
+ def pytest_cmdline_parse(self, pluginmanager, args):
+ # REF1 assert self == pluginmanager.config, (self, pluginmanager.config)
+ self.parse(args)
+ return self
+
+ def notify_exception(self, excinfo, option=None):
+ if option and option.fulltrace:
+ style = "long"
+ else:
+ style = "native"
+ excrepr = excinfo.getrepr(funcargs=True,
+ showlocals=getattr(option, 'showlocals', False),
+ style=style,
+ )
+ res = self.hook.pytest_internalerror(excrepr=excrepr,
+ excinfo=excinfo)
+ if not py.builtin.any(res):
+ for line in str(excrepr).split("\n"):
+ sys.stderr.write("INTERNALERROR> %s\n" %line)
+ sys.stderr.flush()
+
+ def cwd_relative_nodeid(self, nodeid):
+ # nodeid's are relative to the rootpath, compute relative to cwd
+ if self.invocation_dir != self.rootdir:
+ fullpath = self.rootdir.join(nodeid)
+ nodeid = self.invocation_dir.bestrelpath(fullpath)
+ return nodeid
+
+ @classmethod
+ def fromdictargs(cls, option_dict, args):
+ """ constructor useable for subprocesses. """
+ config = get_config()
+ config.option.__dict__.update(option_dict)
+ config.parse(args, addopts=False)
+ for x in config.option.plugins:
+ config.pluginmanager.consider_pluginarg(x)
+ return config
+
+ def _processopt(self, opt):
+ for name in opt._short_opts + opt._long_opts:
+ self._opt2dest[name] = opt.dest
+
+ if hasattr(opt, 'default') and opt.dest:
+ if not hasattr(self.option, opt.dest):
+ setattr(self.option, opt.dest, opt.default)
+
+ @hookimpl(trylast=True)
+ def pytest_load_initial_conftests(self, early_config):
+ self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
+
+ def _initini(self, args):
+ ns, unknown_args = self._parser.parse_known_and_unknown_args(args, namespace=self.option.copy())
+ r = determine_setup(ns.inifilename, ns.file_or_dir + unknown_args, warnfunc=self.warn)
+ self.rootdir, self.inifile, self.inicfg = r
+ self._parser.extra_info['rootdir'] = self.rootdir
+ self._parser.extra_info['inifile'] = self.inifile
+ self.invocation_dir = py.path.local()
+ self._parser.addini('addopts', 'extra command line options', 'args')
+ self._parser.addini('minversion', 'minimally required pytest version')
+
+ def _consider_importhook(self, args, entrypoint_name):
+ """Install the PEP 302 import hook if using assertion re-writing.
+
+ Needs to parse the --assert=<mode> option from the commandline
+ and find all the installed plugins to mark them for re-writing
+ by the importhook.
+ """
+ ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
+ mode = ns.assertmode
+ if mode == 'rewrite':
+ try:
+ hook = _pytest.assertion.install_importhook(self)
+ except SystemError:
+ mode = 'plain'
+ else:
+ import pkg_resources
+ self.pluginmanager.rewrite_hook = hook
+ for entrypoint in pkg_resources.iter_entry_points('pytest11'):
+ # 'RECORD' available for plugins installed normally (pip install)
+ # 'SOURCES.txt' available for plugins installed in dev mode (pip install -e)
+ # for installed plugins 'SOURCES.txt' returns an empty list, and vice-versa
+ # so it shouldn't be an issue
+ for metadata in ('RECORD', 'SOURCES.txt'):
+ for entry in entrypoint.dist._get_metadata(metadata):
+ fn = entry.split(',')[0]
+ is_simple_module = os.sep not in fn and fn.endswith('.py')
+ is_package = fn.count(os.sep) == 1 and fn.endswith('__init__.py')
+ if is_simple_module:
+ module_name, ext = os.path.splitext(fn)
+ hook.mark_rewrite(module_name)
+ elif is_package:
+ package_name = os.path.dirname(fn)
+ hook.mark_rewrite(package_name)
+ self._warn_about_missing_assertion(mode)
+
+ def _warn_about_missing_assertion(self, mode):
+ try:
+ assert False
+ except AssertionError:
+ pass
+ else:
+ if mode == 'plain':
+ sys.stderr.write("WARNING: ASSERTIONS ARE NOT EXECUTED"
+ " and FAILING TESTS WILL PASS. Are you"
+ " using python -O?")
+ else:
+ sys.stderr.write("WARNING: assertions not in test modules or"
+ " plugins will be ignored"
+ " because assert statements are not executed "
+ "by the underlying Python interpreter "
+ "(are you using python -O?)\n")
+
+ def _preparse(self, args, addopts=True):
+ self._initini(args)
+ if addopts:
+ args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args
+ args[:] = self.getini("addopts") + args
+ self._checkversion()
+ entrypoint_name = 'pytest11'
+ self._consider_importhook(args, entrypoint_name)
+ self.pluginmanager.consider_preparse(args)
+ self.pluginmanager.load_setuptools_entrypoints(entrypoint_name)
+ self.pluginmanager.consider_env()
+ self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy())
+ confcutdir = self.known_args_namespace.confcutdir
+ if self.known_args_namespace.confcutdir is None and self.inifile:
+ confcutdir = py.path.local(self.inifile).dirname
+ self.known_args_namespace.confcutdir = confcutdir
+ try:
+ self.hook.pytest_load_initial_conftests(early_config=self,
+ args=args, parser=self._parser)
+ except ConftestImportFailure:
+ e = sys.exc_info()[1]
+ if ns.help or ns.version:
+ # we don't want to prevent --help/--version to work
+ # so just let is pass and print a warning at the end
+ self._warn("could not load initial conftests (%s)\n" % e.path)
+ else:
+ raise
+
+ def _checkversion(self):
+ import pytest
+ minver = self.inicfg.get('minversion', None)
+ if minver:
+ ver = minver.split(".")
+ myver = pytest.__version__.split(".")
+ if myver < ver:
+ raise pytest.UsageError(
+ "%s:%d: requires pytest-%s, actual pytest-%s'" %(
+ self.inicfg.config.path, self.inicfg.lineof('minversion'),
+ minver, pytest.__version__))
+
+ def parse(self, args, addopts=True):
+ # parse given cmdline arguments into this config object.
+ assert not hasattr(self, 'args'), (
+ "can only parse cmdline args at most once per Config object")
+ self._origargs = args
+ self.hook.pytest_addhooks.call_historic(
+ kwargs=dict(pluginmanager=self.pluginmanager))
+ self._preparse(args, addopts=addopts)
+ # XXX deprecated hook:
+ self.hook.pytest_cmdline_preparse(config=self, args=args)
+ args = self._parser.parse_setoption(args, self.option, namespace=self.option)
+ if not args:
+ cwd = os.getcwd()
+ if cwd == self.rootdir:
+ args = self.getini('testpaths')
+ if not args:
+ args = [cwd]
+ self.args = args
+
+ def addinivalue_line(self, name, line):
+ """ add a line to an ini-file option. The option must have been
+ declared but might not yet be set in which case the line becomes the
+ the first line in its value. """
+ x = self.getini(name)
+ assert isinstance(x, list)
+ x.append(line) # modifies the cached list inline
+
+ def getini(self, name):
+ """ return configuration value from an :ref:`ini file <inifiles>`. If the
+ specified name hasn't been registered through a prior
+ :py:func:`parser.addini <pytest.config.Parser.addini>`
+ call (usually from a plugin), a ValueError is raised. """
+ try:
+ return self._inicache[name]
+ except KeyError:
+ self._inicache[name] = val = self._getini(name)
+ return val
+
+ def _getini(self, name):
+ try:
+ description, type, default = self._parser._inidict[name]
+ except KeyError:
+ raise ValueError("unknown configuration value: %r" %(name,))
+ value = self._get_override_ini_value(name)
+ if value is None:
+ try:
+ value = self.inicfg[name]
+ except KeyError:
+ if default is not None:
+ return default
+ if type is None:
+ return ''
+ return []
+ if type == "pathlist":
+ dp = py.path.local(self.inicfg.config.path).dirpath()
+ l = []
+ for relpath in shlex.split(value):
+ l.append(dp.join(relpath, abs=True))
+ return l
+ elif type == "args":
+ return shlex.split(value)
+ elif type == "linelist":
+ return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
+ elif type == "bool":
+ return bool(_strtobool(value.strip()))
+ else:
+ assert type is None
+ return value
+
+ def _getconftest_pathlist(self, name, path):
+ try:
+ mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
+ except KeyError:
+ return None
+ modpath = py.path.local(mod.__file__).dirpath()
+ l = []
+ for relroot in relroots:
+ if not isinstance(relroot, py.path.local):
+ relroot = relroot.replace("/", py.path.local.sep)
+ relroot = modpath.join(relroot, abs=True)
+ l.append(relroot)
+ return l
+
+ def _get_override_ini_value(self, name):
+ value = None
+ # override_ini is a list of list, to support both -o foo1=bar1 foo2=bar2 and
+ # and -o foo1=bar1 -o foo2=bar2 options
+ # always use the last item if multiple value set for same ini-name,
+ # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2
+ if self.getoption("override_ini", None):
+ for ini_config_list in self.option.override_ini:
+ for ini_config in ini_config_list:
+ try:
+ (key, user_ini_value) = ini_config.split("=", 1)
+ except ValueError:
+ raise UsageError("-o/--override-ini expects option=value style.")
+ if key == name:
+ value = user_ini_value
+ return value
+
+ def getoption(self, name, default=notset, skip=False):
+ """ return command line option value.
+
+ :arg name: name of the option. You may also specify
+ the literal ``--OPT`` option instead of the "dest" option name.
+ :arg default: default value if no option of that name exists.
+ :arg skip: if True raise pytest.skip if option does not exists
+ or has a None value.
+ """
+ name = self._opt2dest.get(name, name)
+ try:
+ val = getattr(self.option, name)
+ if val is None and skip:
+ raise AttributeError(name)
+ return val
+ except AttributeError:
+ if default is not notset:
+ return default
+ if skip:
+ import pytest
+ pytest.skip("no %r option found" %(name,))
+ raise ValueError("no option named %r" % (name,))
+
+ def getvalue(self, name, path=None):
+ """ (deprecated, use getoption()) """
+ return self.getoption(name)
+
+ def getvalueorskip(self, name, path=None):
+ """ (deprecated, use getoption(skip=True)) """
+ return self.getoption(name, skip=True)
+
+def exists(path, ignore=EnvironmentError):
+ try:
+ return path.check()
+ except ignore:
+ return False
+
+def getcfg(args, warnfunc=None):
+ """
+ Search the list of arguments for a valid ini-file for pytest,
+ and return a tuple of (rootdir, inifile, cfg-dict).
+
+ note: warnfunc is an optional function used to warn
+ about ini-files that use deprecated features.
+ This parameter should be removed when pytest
+ adopts standard deprecation warnings (#1804).
+ """
+ from _pytest.deprecated import SETUP_CFG_PYTEST
+ inibasenames = ["pytest.ini", "tox.ini", "setup.cfg"]
+ args = [x for x in args if not str(x).startswith("-")]
+ if not args:
+ args = [py.path.local()]
+ for arg in args:
+ arg = py.path.local(arg)
+ for base in arg.parts(reverse=True):
+ for inibasename in inibasenames:
+ p = base.join(inibasename)
+ if exists(p):
+ iniconfig = py.iniconfig.IniConfig(p)
+ if 'pytest' in iniconfig.sections:
+ if inibasename == 'setup.cfg' and warnfunc:
+ warnfunc('C1', SETUP_CFG_PYTEST)
+ return base, p, iniconfig['pytest']
+ if inibasename == 'setup.cfg' and 'tool:pytest' in iniconfig.sections:
+ return base, p, iniconfig['tool:pytest']
+ elif inibasename == "pytest.ini":
+ # allowed to be empty
+ return base, p, {}
+ return None, None, None
+
+
+def get_common_ancestor(args):
+ # args are what we get after early command line parsing (usually
+ # strings, but can be py.path.local objects as well)
+ common_ancestor = None
+ for arg in args:
+ if str(arg)[0] == "-":
+ continue
+ p = py.path.local(arg)
+ if not p.exists():
+ continue
+ if common_ancestor is None:
+ common_ancestor = p
+ else:
+ if p.relto(common_ancestor) or p == common_ancestor:
+ continue
+ elif common_ancestor.relto(p):
+ common_ancestor = p
+ else:
+ shared = p.common(common_ancestor)
+ if shared is not None:
+ common_ancestor = shared
+ if common_ancestor is None:
+ common_ancestor = py.path.local()
+ elif common_ancestor.isfile():
+ common_ancestor = common_ancestor.dirpath()
+ return common_ancestor
+
+
+def get_dirs_from_args(args):
+ return [d for d in (py.path.local(x) for x in args
+ if not str(x).startswith("-"))
+ if d.exists()]
+
+
+def determine_setup(inifile, args, warnfunc=None):
+ dirs = get_dirs_from_args(args)
+ if inifile:
+ iniconfig = py.iniconfig.IniConfig(inifile)
+ try:
+ inicfg = iniconfig["pytest"]
+ except KeyError:
+ inicfg = None
+ rootdir = get_common_ancestor(dirs)
+ else:
+ ancestor = get_common_ancestor(dirs)
+ rootdir, inifile, inicfg = getcfg([ancestor], warnfunc=warnfunc)
+ if rootdir is None:
+ for rootdir in ancestor.parts(reverse=True):
+ if rootdir.join("setup.py").exists():
+ break
+ else:
+ rootdir, inifile, inicfg = getcfg(dirs, warnfunc=warnfunc)
+ if rootdir is None:
+ rootdir = get_common_ancestor([py.path.local(), ancestor])
+ is_fs_root = os.path.splitdrive(str(rootdir))[1] == os.sep
+ if is_fs_root:
+ rootdir = ancestor
+ return rootdir, inifile, inicfg or {}
+
+
+def setns(obj, dic):
+ import pytest
+ for name, value in dic.items():
+ if isinstance(value, dict):
+ mod = getattr(obj, name, None)
+ if mod is None:
+ modname = "pytest.%s" % name
+ mod = types.ModuleType(modname)
+ sys.modules[modname] = mod
+ mod.__all__ = []
+ setattr(obj, name, mod)
+ obj.__all__.append(name)
+ setns(mod, value)
+ else:
+ setattr(obj, name, value)
+ obj.__all__.append(name)
+ #if obj != pytest:
+ # pytest.__all__.append(name)
+ setattr(pytest, name, value)
+
+
+def create_terminal_writer(config, *args, **kwargs):
+ """Create a TerminalWriter instance configured according to the options
+ in the config object. Every code which requires a TerminalWriter object
+ and has access to a config object should use this function.
+ """
+ tw = py.io.TerminalWriter(*args, **kwargs)
+ if config.option.color == 'yes':
+ tw.hasmarkup = True
+ if config.option.color == 'no':
+ tw.hasmarkup = False
+ return tw
+
+
+def _strtobool(val):
+ """Convert a string representation of truth to true (1) or false (0).
+
+ True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
+ are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
+ 'val' is anything else.
+
+ .. note:: copied from distutils.util
+ """
+ val = val.lower()
+ if val in ('y', 'yes', 't', 'true', 'on', '1'):
+ return 1
+ elif val in ('n', 'no', 'f', 'false', 'off', '0'):
+ return 0
+ else:
+ raise ValueError("invalid truth value %r" % (val,))
diff --git a/lib/spack/external/_pytest/debugging.py b/lib/spack/external/_pytest/debugging.py
new file mode 100644
index 0000000000..d96170bd8b
--- /dev/null
+++ b/lib/spack/external/_pytest/debugging.py
@@ -0,0 +1,124 @@
+""" interactive debugging with PDB, the Python Debugger. """
+from __future__ import absolute_import
+import pdb
+import sys
+
+import pytest
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group._addoption(
+ '--pdb', dest="usepdb", action="store_true",
+ help="start the interactive Python debugger on errors.")
+ group._addoption(
+ '--pdbcls', dest="usepdb_cls", metavar="modulename:classname",
+ help="start a custom interactive Python debugger on errors. "
+ "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb")
+
+def pytest_namespace():
+ return {'set_trace': pytestPDB().set_trace}
+
+def pytest_configure(config):
+ if config.getvalue("usepdb") or config.getvalue("usepdb_cls"):
+ config.pluginmanager.register(PdbInvoke(), 'pdbinvoke')
+ if config.getvalue("usepdb_cls"):
+ modname, classname = config.getvalue("usepdb_cls").split(":")
+ __import__(modname)
+ pdb_cls = getattr(sys.modules[modname], classname)
+ else:
+ pdb_cls = pdb.Pdb
+ pytestPDB._pdb_cls = pdb_cls
+
+ old = (pdb.set_trace, pytestPDB._pluginmanager)
+
+ def fin():
+ pdb.set_trace, pytestPDB._pluginmanager = old
+ pytestPDB._config = None
+ pytestPDB._pdb_cls = pdb.Pdb
+
+ pdb.set_trace = pytest.set_trace
+ pytestPDB._pluginmanager = config.pluginmanager
+ pytestPDB._config = config
+ config._cleanup.append(fin)
+
+class pytestPDB:
+ """ Pseudo PDB that defers to the real pdb. """
+ _pluginmanager = None
+ _config = None
+ _pdb_cls = pdb.Pdb
+
+ def set_trace(self):
+ """ invoke PDB set_trace debugging, dropping any IO capturing. """
+ import _pytest.config
+ frame = sys._getframe().f_back
+ if self._pluginmanager is not None:
+ capman = self._pluginmanager.getplugin("capturemanager")
+ if capman:
+ capman.suspendcapture(in_=True)
+ tw = _pytest.config.create_terminal_writer(self._config)
+ tw.line()
+ tw.sep(">", "PDB set_trace (IO-capturing turned off)")
+ self._pluginmanager.hook.pytest_enter_pdb(config=self._config)
+ self._pdb_cls().set_trace(frame)
+
+
+class PdbInvoke:
+ def pytest_exception_interact(self, node, call, report):
+ capman = node.config.pluginmanager.getplugin("capturemanager")
+ if capman:
+ out, err = capman.suspendcapture(in_=True)
+ sys.stdout.write(out)
+ sys.stdout.write(err)
+ _enter_pdb(node, call.excinfo, report)
+
+ def pytest_internalerror(self, excrepr, excinfo):
+ for line in str(excrepr).split("\n"):
+ sys.stderr.write("INTERNALERROR> %s\n" %line)
+ sys.stderr.flush()
+ tb = _postmortem_traceback(excinfo)
+ post_mortem(tb)
+
+
+def _enter_pdb(node, excinfo, rep):
+ # XXX we re-use the TerminalReporter's terminalwriter
+ # because this seems to avoid some encoding related troubles
+ # for not completely clear reasons.
+ tw = node.config.pluginmanager.getplugin("terminalreporter")._tw
+ tw.line()
+ tw.sep(">", "traceback")
+ rep.toterminal(tw)
+ tw.sep(">", "entering PDB")
+ tb = _postmortem_traceback(excinfo)
+ post_mortem(tb)
+ rep._pdbshown = True
+ return rep
+
+
+def _postmortem_traceback(excinfo):
+ # A doctest.UnexpectedException is not useful for post_mortem.
+ # Use the underlying exception instead:
+ from doctest import UnexpectedException
+ if isinstance(excinfo.value, UnexpectedException):
+ return excinfo.value.exc_info[2]
+ else:
+ return excinfo._excinfo[2]
+
+
+def _find_last_non_hidden_frame(stack):
+ i = max(0, len(stack) - 1)
+ while i and stack[i][0].f_locals.get("__tracebackhide__", False):
+ i -= 1
+ return i
+
+
+def post_mortem(t):
+ class Pdb(pytestPDB._pdb_cls):
+ def get_stack(self, f, t):
+ stack, i = pdb.Pdb.get_stack(self, f, t)
+ if f is None:
+ i = _find_last_non_hidden_frame(stack)
+ return stack, i
+ p = Pdb()
+ p.reset()
+ p.interaction(None, t)
diff --git a/lib/spack/external/_pytest/deprecated.py b/lib/spack/external/_pytest/deprecated.py
new file mode 100644
index 0000000000..6edc475f6e
--- /dev/null
+++ b/lib/spack/external/_pytest/deprecated.py
@@ -0,0 +1,24 @@
+"""
+This module contains deprecation messages and bits of code used elsewhere in the codebase
+that is planned to be removed in the next pytest release.
+
+Keeping it in a central location makes it easy to track what is deprecated and should
+be removed when the time comes.
+"""
+
+
+MAIN_STR_ARGS = 'passing a string to pytest.main() is deprecated, ' \
+ 'pass a list of arguments instead.'
+
+YIELD_TESTS = 'yield tests are deprecated, and scheduled to be removed in pytest 4.0'
+
+FUNCARG_PREFIX = (
+ '{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated '
+ 'and scheduled to be removed in pytest 4.0. '
+ 'Please remove the prefix and use the @pytest.fixture decorator instead.')
+
+SETUP_CFG_PYTEST = '[pytest] section in setup.cfg files is deprecated, use [tool:pytest] instead.'
+
+GETFUNCARGVALUE = "use of getfuncargvalue is deprecated, use getfixturevalue"
+
+RESULT_LOG = '--result-log is deprecated and scheduled for removal in pytest 4.0'
diff --git a/lib/spack/external/_pytest/doctest.py b/lib/spack/external/_pytest/doctest.py
new file mode 100644
index 0000000000..f4782dded5
--- /dev/null
+++ b/lib/spack/external/_pytest/doctest.py
@@ -0,0 +1,331 @@
+""" discover and run doctests in modules and test files."""
+from __future__ import absolute_import
+
+import traceback
+
+import pytest
+from _pytest._code.code import ExceptionInfo, ReprFileLocation, TerminalRepr
+from _pytest.fixtures import FixtureRequest
+
+
+DOCTEST_REPORT_CHOICE_NONE = 'none'
+DOCTEST_REPORT_CHOICE_CDIFF = 'cdiff'
+DOCTEST_REPORT_CHOICE_NDIFF = 'ndiff'
+DOCTEST_REPORT_CHOICE_UDIFF = 'udiff'
+DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE = 'only_first_failure'
+
+DOCTEST_REPORT_CHOICES = (
+ DOCTEST_REPORT_CHOICE_NONE,
+ DOCTEST_REPORT_CHOICE_CDIFF,
+ DOCTEST_REPORT_CHOICE_NDIFF,
+ DOCTEST_REPORT_CHOICE_UDIFF,
+ DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE,
+)
+
+def pytest_addoption(parser):
+ parser.addini('doctest_optionflags', 'option flags for doctests',
+ type="args", default=["ELLIPSIS"])
+ group = parser.getgroup("collect")
+ group.addoption("--doctest-modules",
+ action="store_true", default=False,
+ help="run doctests in all .py modules",
+ dest="doctestmodules")
+ group.addoption("--doctest-report",
+ type=str.lower, default="udiff",
+ help="choose another output format for diffs on doctest failure",
+ choices=DOCTEST_REPORT_CHOICES,
+ dest="doctestreport")
+ group.addoption("--doctest-glob",
+ action="append", default=[], metavar="pat",
+ help="doctests file matching pattern, default: test*.txt",
+ dest="doctestglob")
+ group.addoption("--doctest-ignore-import-errors",
+ action="store_true", default=False,
+ help="ignore doctest ImportErrors",
+ dest="doctest_ignore_import_errors")
+
+
+def pytest_collect_file(path, parent):
+ config = parent.config
+ if path.ext == ".py":
+ if config.option.doctestmodules:
+ return DoctestModule(path, parent)
+ elif _is_doctest(config, path, parent):
+ return DoctestTextfile(path, parent)
+
+
+def _is_doctest(config, path, parent):
+ if path.ext in ('.txt', '.rst') and parent.session.isinitpath(path):
+ return True
+ globs = config.getoption("doctestglob") or ['test*.txt']
+ for glob in globs:
+ if path.check(fnmatch=glob):
+ return True
+ return False
+
+
+class ReprFailDoctest(TerminalRepr):
+
+ def __init__(self, reprlocation, lines):
+ self.reprlocation = reprlocation
+ self.lines = lines
+
+ def toterminal(self, tw):
+ for line in self.lines:
+ tw.line(line)
+ self.reprlocation.toterminal(tw)
+
+
+class DoctestItem(pytest.Item):
+ def __init__(self, name, parent, runner=None, dtest=None):
+ super(DoctestItem, self).__init__(name, parent)
+ self.runner = runner
+ self.dtest = dtest
+ self.obj = None
+ self.fixture_request = None
+
+ def setup(self):
+ if self.dtest is not None:
+ self.fixture_request = _setup_fixtures(self)
+ globs = dict(getfixture=self.fixture_request.getfixturevalue)
+ for name, value in self.fixture_request.getfixturevalue('doctest_namespace').items():
+ globs[name] = value
+ self.dtest.globs.update(globs)
+
+ def runtest(self):
+ _check_all_skipped(self.dtest)
+ self.runner.run(self.dtest)
+
+ def repr_failure(self, excinfo):
+ import doctest
+ if excinfo.errisinstance((doctest.DocTestFailure,
+ doctest.UnexpectedException)):
+ doctestfailure = excinfo.value
+ example = doctestfailure.example
+ test = doctestfailure.test
+ filename = test.filename
+ if test.lineno is None:
+ lineno = None
+ else:
+ lineno = test.lineno + example.lineno + 1
+ message = excinfo.type.__name__
+ reprlocation = ReprFileLocation(filename, lineno, message)
+ checker = _get_checker()
+ report_choice = _get_report_choice(self.config.getoption("doctestreport"))
+ if lineno is not None:
+ lines = doctestfailure.test.docstring.splitlines(False)
+ # add line numbers to the left of the error message
+ lines = ["%03d %s" % (i + test.lineno + 1, x)
+ for (i, x) in enumerate(lines)]
+ # trim docstring error lines to 10
+ lines = lines[example.lineno - 9:example.lineno + 1]
+ else:
+ lines = ['EXAMPLE LOCATION UNKNOWN, not showing all tests of that example']
+ indent = '>>>'
+ for line in example.source.splitlines():
+ lines.append('??? %s %s' % (indent, line))
+ indent = '...'
+ if excinfo.errisinstance(doctest.DocTestFailure):
+ lines += checker.output_difference(example,
+ doctestfailure.got, report_choice).split("\n")
+ else:
+ inner_excinfo = ExceptionInfo(excinfo.value.exc_info)
+ lines += ["UNEXPECTED EXCEPTION: %s" %
+ repr(inner_excinfo.value)]
+ lines += traceback.format_exception(*excinfo.value.exc_info)
+ return ReprFailDoctest(reprlocation, lines)
+ else:
+ return super(DoctestItem, self).repr_failure(excinfo)
+
+ def reportinfo(self):
+ return self.fspath, None, "[doctest] %s" % self.name
+
+
+def _get_flag_lookup():
+ import doctest
+ return dict(DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1,
+ DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE,
+ NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE,
+ ELLIPSIS=doctest.ELLIPSIS,
+ IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL,
+ COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
+ ALLOW_UNICODE=_get_allow_unicode_flag(),
+ ALLOW_BYTES=_get_allow_bytes_flag(),
+ )
+
+
+def get_optionflags(parent):
+ optionflags_str = parent.config.getini("doctest_optionflags")
+ flag_lookup_table = _get_flag_lookup()
+ flag_acc = 0
+ for flag in optionflags_str:
+ flag_acc |= flag_lookup_table[flag]
+ return flag_acc
+
+
+class DoctestTextfile(pytest.Module):
+ obj = None
+
+ def collect(self):
+ import doctest
+
+ # inspired by doctest.testfile; ideally we would use it directly,
+ # but it doesn't support passing a custom checker
+ text = self.fspath.read()
+ filename = str(self.fspath)
+ name = self.fspath.basename
+ globs = {'__name__': '__main__'}
+
+
+ optionflags = get_optionflags(self)
+ runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
+ checker=_get_checker())
+
+ parser = doctest.DocTestParser()
+ test = parser.get_doctest(text, globs, name, filename, 0)
+ if test.examples:
+ yield DoctestItem(test.name, self, runner, test)
+
+
+def _check_all_skipped(test):
+ """raises pytest.skip() if all examples in the given DocTest have the SKIP
+ option set.
+ """
+ import doctest
+ all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples)
+ if all_skipped:
+ pytest.skip('all tests skipped by +SKIP option')
+
+
+class DoctestModule(pytest.Module):
+ def collect(self):
+ import doctest
+ if self.fspath.basename == "conftest.py":
+ module = self.config.pluginmanager._importconftest(self.fspath)
+ else:
+ try:
+ module = self.fspath.pyimport()
+ except ImportError:
+ if self.config.getvalue('doctest_ignore_import_errors'):
+ pytest.skip('unable to import module %r' % self.fspath)
+ else:
+ raise
+ # uses internal doctest module parsing mechanism
+ finder = doctest.DocTestFinder()
+ optionflags = get_optionflags(self)
+ runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
+ checker=_get_checker())
+ for test in finder.find(module, module.__name__):
+ if test.examples: # skip empty doctests
+ yield DoctestItem(test.name, self, runner, test)
+
+
+def _setup_fixtures(doctest_item):
+ """
+ Used by DoctestTextfile and DoctestItem to setup fixture information.
+ """
+ def func():
+ pass
+
+ doctest_item.funcargs = {}
+ fm = doctest_item.session._fixturemanager
+ doctest_item._fixtureinfo = fm.getfixtureinfo(node=doctest_item, func=func,
+ cls=None, funcargs=False)
+ fixture_request = FixtureRequest(doctest_item)
+ fixture_request._fillfixtures()
+ return fixture_request
+
+
+def _get_checker():
+ """
+ Returns a doctest.OutputChecker subclass that takes in account the
+ ALLOW_UNICODE option to ignore u'' prefixes in strings and ALLOW_BYTES
+ to strip b'' prefixes.
+ Useful when the same doctest should run in Python 2 and Python 3.
+
+ An inner class is used to avoid importing "doctest" at the module
+ level.
+ """
+ if hasattr(_get_checker, 'LiteralsOutputChecker'):
+ return _get_checker.LiteralsOutputChecker()
+
+ import doctest
+ import re
+
+ class LiteralsOutputChecker(doctest.OutputChecker):
+ """
+ Copied from doctest_nose_plugin.py from the nltk project:
+ https://github.com/nltk/nltk
+
+ Further extended to also support byte literals.
+ """
+
+ _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
+ _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE)
+
+ def check_output(self, want, got, optionflags):
+ res = doctest.OutputChecker.check_output(self, want, got,
+ optionflags)
+ if res:
+ return True
+
+ allow_unicode = optionflags & _get_allow_unicode_flag()
+ allow_bytes = optionflags & _get_allow_bytes_flag()
+ if not allow_unicode and not allow_bytes:
+ return False
+
+ else: # pragma: no cover
+ def remove_prefixes(regex, txt):
+ return re.sub(regex, r'\1\2', txt)
+
+ if allow_unicode:
+ want = remove_prefixes(self._unicode_literal_re, want)
+ got = remove_prefixes(self._unicode_literal_re, got)
+ if allow_bytes:
+ want = remove_prefixes(self._bytes_literal_re, want)
+ got = remove_prefixes(self._bytes_literal_re, got)
+ res = doctest.OutputChecker.check_output(self, want, got,
+ optionflags)
+ return res
+
+ _get_checker.LiteralsOutputChecker = LiteralsOutputChecker
+ return _get_checker.LiteralsOutputChecker()
+
+
+def _get_allow_unicode_flag():
+ """
+ Registers and returns the ALLOW_UNICODE flag.
+ """
+ import doctest
+ return doctest.register_optionflag('ALLOW_UNICODE')
+
+
+def _get_allow_bytes_flag():
+ """
+ Registers and returns the ALLOW_BYTES flag.
+ """
+ import doctest
+ return doctest.register_optionflag('ALLOW_BYTES')
+
+
+def _get_report_choice(key):
+ """
+ This function returns the actual `doctest` module flag value, we want to do it as late as possible to avoid
+ importing `doctest` and all its dependencies when parsing options, as it adds overhead and breaks tests.
+ """
+ import doctest
+
+ return {
+ DOCTEST_REPORT_CHOICE_UDIFF: doctest.REPORT_UDIFF,
+ DOCTEST_REPORT_CHOICE_CDIFF: doctest.REPORT_CDIFF,
+ DOCTEST_REPORT_CHOICE_NDIFF: doctest.REPORT_NDIFF,
+ DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE: doctest.REPORT_ONLY_FIRST_FAILURE,
+ DOCTEST_REPORT_CHOICE_NONE: 0,
+ }[key]
+
+@pytest.fixture(scope='session')
+def doctest_namespace():
+ """
+ Inject names into the doctest namespace.
+ """
+ return dict()
diff --git a/lib/spack/external/_pytest/fixtures.py b/lib/spack/external/_pytest/fixtures.py
new file mode 100644
index 0000000000..28bcd4d8d7
--- /dev/null
+++ b/lib/spack/external/_pytest/fixtures.py
@@ -0,0 +1,1134 @@
+import sys
+
+from py._code.code import FormattedExcinfo
+
+import py
+import pytest
+import warnings
+
+import inspect
+import _pytest
+from _pytest._code.code import TerminalRepr
+from _pytest.compat import (
+ NOTSET, exc_clear, _format_args,
+ getfslineno, get_real_func,
+ is_generator, isclass, getimfunc,
+ getlocation, getfuncargnames,
+)
+
+def pytest_sessionstart(session):
+ session._fixturemanager = FixtureManager(session)
+
+
+scopename2class = {}
+
+
+scope2props = dict(session=())
+scope2props["module"] = ("fspath", "module")
+scope2props["class"] = scope2props["module"] + ("cls",)
+scope2props["instance"] = scope2props["class"] + ("instance", )
+scope2props["function"] = scope2props["instance"] + ("function", "keywords")
+
+def scopeproperty(name=None, doc=None):
+ def decoratescope(func):
+ scopename = name or func.__name__
+
+ def provide(self):
+ if func.__name__ in scope2props[self.scope]:
+ return func(self)
+ raise AttributeError("%s not available in %s-scoped context" % (
+ scopename, self.scope))
+
+ return property(provide, None, None, func.__doc__)
+ return decoratescope
+
+
+def pytest_namespace():
+ scopename2class.update({
+ 'class': pytest.Class,
+ 'module': pytest.Module,
+ 'function': pytest.Item,
+ })
+ return {
+ 'fixture': fixture,
+ 'yield_fixture': yield_fixture,
+ 'collect': {'_fillfuncargs': fillfixtures}
+ }
+
+
+def get_scope_node(node, scope):
+ cls = scopename2class.get(scope)
+ if cls is None:
+ if scope == "session":
+ return node.session
+ raise ValueError("unknown scope")
+ return node.getparent(cls)
+
+
+def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
+ # this function will transform all collected calls to a functions
+ # if they use direct funcargs (i.e. direct parametrization)
+ # because we want later test execution to be able to rely on
+ # an existing FixtureDef structure for all arguments.
+ # XXX we can probably avoid this algorithm if we modify CallSpec2
+ # to directly care for creating the fixturedefs within its methods.
+ if not metafunc._calls[0].funcargs:
+ return # this function call does not have direct parametrization
+ # collect funcargs of all callspecs into a list of values
+ arg2params = {}
+ arg2scope = {}
+ for callspec in metafunc._calls:
+ for argname, argvalue in callspec.funcargs.items():
+ assert argname not in callspec.params
+ callspec.params[argname] = argvalue
+ arg2params_list = arg2params.setdefault(argname, [])
+ callspec.indices[argname] = len(arg2params_list)
+ arg2params_list.append(argvalue)
+ if argname not in arg2scope:
+ scopenum = callspec._arg2scopenum.get(argname,
+ scopenum_function)
+ arg2scope[argname] = scopes[scopenum]
+ callspec.funcargs.clear()
+
+ # register artificial FixtureDef's so that later at test execution
+ # time we can rely on a proper FixtureDef to exist for fixture setup.
+ arg2fixturedefs = metafunc._arg2fixturedefs
+ for argname, valuelist in arg2params.items():
+ # if we have a scope that is higher than function we need
+ # to make sure we only ever create an according fixturedef on
+ # a per-scope basis. We thus store and cache the fixturedef on the
+ # node related to the scope.
+ scope = arg2scope[argname]
+ node = None
+ if scope != "function":
+ node = get_scope_node(collector, scope)
+ if node is None:
+ assert scope == "class" and isinstance(collector, pytest.Module)
+ # use module-level collector for class-scope (for now)
+ node = collector
+ if node and argname in node._name2pseudofixturedef:
+ arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]]
+ else:
+ fixturedef = FixtureDef(fixturemanager, '', argname,
+ get_direct_param_fixture_func,
+ arg2scope[argname],
+ valuelist, False, False)
+ arg2fixturedefs[argname] = [fixturedef]
+ if node is not None:
+ node._name2pseudofixturedef[argname] = fixturedef
+
+
+
+def getfixturemarker(obj):
+ """ return fixturemarker or None if it doesn't exist or raised
+ exceptions."""
+ try:
+ return getattr(obj, "_pytestfixturefunction", None)
+ except KeyboardInterrupt:
+ raise
+ except Exception:
+ # some objects raise errors like request (from flask import request)
+ # we don't expect them to be fixture functions
+ return None
+
+
+
+def get_parametrized_fixture_keys(item, scopenum):
+ """ return list of keys for all parametrized arguments which match
+ the specified scope. """
+ assert scopenum < scopenum_function # function
+ try:
+ cs = item.callspec
+ except AttributeError:
+ pass
+ else:
+ # cs.indictes.items() is random order of argnames but
+ # then again different functions (items) can change order of
+ # arguments so it doesn't matter much probably
+ for argname, param_index in cs.indices.items():
+ if cs._arg2scopenum[argname] != scopenum:
+ continue
+ if scopenum == 0: # session
+ key = (argname, param_index)
+ elif scopenum == 1: # module
+ key = (argname, param_index, item.fspath)
+ elif scopenum == 2: # class
+ key = (argname, param_index, item.fspath, item.cls)
+ yield key
+
+
+# algorithm for sorting on a per-parametrized resource setup basis
+# it is called for scopenum==0 (session) first and performs sorting
+# down to the lower scopes such as to minimize number of "high scope"
+# setups and teardowns
+
+def reorder_items(items):
+ argkeys_cache = {}
+ for scopenum in range(0, scopenum_function):
+ argkeys_cache[scopenum] = d = {}
+ for item in items:
+ keys = set(get_parametrized_fixture_keys(item, scopenum))
+ if keys:
+ d[item] = keys
+ return reorder_items_atscope(items, set(), argkeys_cache, 0)
+
+def reorder_items_atscope(items, ignore, argkeys_cache, scopenum):
+ if scopenum >= scopenum_function or len(items) < 3:
+ return items
+ items_done = []
+ while 1:
+ items_before, items_same, items_other, newignore = \
+ slice_items(items, ignore, argkeys_cache[scopenum])
+ items_before = reorder_items_atscope(
+ items_before, ignore, argkeys_cache,scopenum+1)
+ if items_same is None:
+ # nothing to reorder in this scope
+ assert items_other is None
+ return items_done + items_before
+ items_done.extend(items_before)
+ items = items_same + items_other
+ ignore = newignore
+
+
+def slice_items(items, ignore, scoped_argkeys_cache):
+ # we pick the first item which uses a fixture instance in the
+ # requested scope and which we haven't seen yet. We slice the input
+ # items list into a list of items_nomatch, items_same and
+ # items_other
+ if scoped_argkeys_cache: # do we need to do work at all?
+ it = iter(items)
+ # first find a slicing key
+ for i, item in enumerate(it):
+ argkeys = scoped_argkeys_cache.get(item)
+ if argkeys is not None:
+ argkeys = argkeys.difference(ignore)
+ if argkeys: # found a slicing key
+ slicing_argkey = argkeys.pop()
+ items_before = items[:i]
+ items_same = [item]
+ items_other = []
+ # now slice the remainder of the list
+ for item in it:
+ argkeys = scoped_argkeys_cache.get(item)
+ if argkeys and slicing_argkey in argkeys and \
+ slicing_argkey not in ignore:
+ items_same.append(item)
+ else:
+ items_other.append(item)
+ newignore = ignore.copy()
+ newignore.add(slicing_argkey)
+ return (items_before, items_same, items_other, newignore)
+ return items, None, None, None
+
+
+
+class FuncargnamesCompatAttr:
+ """ helper class so that Metafunc, Function and FixtureRequest
+ don't need to each define the "funcargnames" compatibility attribute.
+ """
+ @property
+ def funcargnames(self):
+ """ alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
+ return self.fixturenames
+
+
+def fillfixtures(function):
+ """ fill missing funcargs for a test function. """
+ try:
+ request = function._request
+ except AttributeError:
+ # XXX this special code path is only expected to execute
+ # with the oejskit plugin. It uses classes with funcargs
+ # and we thus have to work a bit to allow this.
+ fm = function.session._fixturemanager
+ fi = fm.getfixtureinfo(function.parent, function.obj, None)
+ function._fixtureinfo = fi
+ request = function._request = FixtureRequest(function)
+ request._fillfixtures()
+ # prune out funcargs for jstests
+ newfuncargs = {}
+ for name in fi.argnames:
+ newfuncargs[name] = function.funcargs[name]
+ function.funcargs = newfuncargs
+ else:
+ request._fillfixtures()
+
+
+
+def get_direct_param_fixture_func(request):
+ return request.param
+
+class FuncFixtureInfo:
+ def __init__(self, argnames, names_closure, name2fixturedefs):
+ self.argnames = argnames
+ self.names_closure = names_closure
+ self.name2fixturedefs = name2fixturedefs
+
+
+class FixtureRequest(FuncargnamesCompatAttr):
+ """ A request for a fixture from a test or fixture function.
+
+ A request object gives access to the requesting test context
+ and has an optional ``param`` attribute in case
+ the fixture is parametrized indirectly.
+ """
+
+ def __init__(self, pyfuncitem):
+ self._pyfuncitem = pyfuncitem
+ #: fixture for which this request is being performed
+ self.fixturename = None
+ #: Scope string, one of "function", "class", "module", "session"
+ self.scope = "function"
+ self._fixture_values = {} # argname -> fixture value
+ self._fixture_defs = {} # argname -> FixtureDef
+ fixtureinfo = pyfuncitem._fixtureinfo
+ self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
+ self._arg2index = {}
+ self._fixturemanager = pyfuncitem.session._fixturemanager
+
+ @property
+ def fixturenames(self):
+ # backward incompatible note: now a readonly property
+ return list(self._pyfuncitem._fixtureinfo.names_closure)
+
+ @property
+ def node(self):
+ """ underlying collection node (depends on current request scope)"""
+ return self._getscopeitem(self.scope)
+
+
+ def _getnextfixturedef(self, argname):
+ fixturedefs = self._arg2fixturedefs.get(argname, None)
+ if fixturedefs is None:
+ # we arrive here because of a a dynamic call to
+ # getfixturevalue(argname) usage which was naturally
+ # not known at parsing/collection time
+ parentid = self._pyfuncitem.parent.nodeid
+ fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid)
+ self._arg2fixturedefs[argname] = fixturedefs
+ # fixturedefs list is immutable so we maintain a decreasing index
+ index = self._arg2index.get(argname, 0) - 1
+ if fixturedefs is None or (-index > len(fixturedefs)):
+ raise FixtureLookupError(argname, self)
+ self._arg2index[argname] = index
+ return fixturedefs[index]
+
+ @property
+ def config(self):
+ """ the pytest config object associated with this request. """
+ return self._pyfuncitem.config
+
+
+ @scopeproperty()
+ def function(self):
+ """ test function object if the request has a per-function scope. """
+ return self._pyfuncitem.obj
+
+ @scopeproperty("class")
+ def cls(self):
+ """ class (can be None) where the test function was collected. """
+ clscol = self._pyfuncitem.getparent(pytest.Class)
+ if clscol:
+ return clscol.obj
+
+ @property
+ def instance(self):
+ """ instance (can be None) on which test function was collected. """
+ # unittest support hack, see _pytest.unittest.TestCaseFunction
+ try:
+ return self._pyfuncitem._testcase
+ except AttributeError:
+ function = getattr(self, "function", None)
+ if function is not None:
+ return py.builtin._getimself(function)
+
+ @scopeproperty()
+ def module(self):
+ """ python module object where the test function was collected. """
+ return self._pyfuncitem.getparent(pytest.Module).obj
+
+ @scopeproperty()
+ def fspath(self):
+ """ the file system path of the test module which collected this test. """
+ return self._pyfuncitem.fspath
+
+ @property
+ def keywords(self):
+ """ keywords/markers dictionary for the underlying node. """
+ return self.node.keywords
+
+ @property
+ def session(self):
+ """ pytest session object. """
+ return self._pyfuncitem.session
+
+ def addfinalizer(self, finalizer):
+ """ add finalizer/teardown function to be called after the
+ last test within the requesting test context finished
+ execution. """
+ # XXX usually this method is shadowed by fixturedef specific ones
+ self._addfinalizer(finalizer, scope=self.scope)
+
+ def _addfinalizer(self, finalizer, scope):
+ colitem = self._getscopeitem(scope)
+ self._pyfuncitem.session._setupstate.addfinalizer(
+ finalizer=finalizer, colitem=colitem)
+
+ def applymarker(self, marker):
+ """ Apply a marker to a single test function invocation.
+ This method is useful if you don't want to have a keyword/marker
+ on all function invocations.
+
+ :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object
+ created by a call to ``pytest.mark.NAME(...)``.
+ """
+ try:
+ self.node.keywords[marker.markname] = marker
+ except AttributeError:
+ raise ValueError(marker)
+
+ def raiseerror(self, msg):
+ """ raise a FixtureLookupError with the given message. """
+ raise self._fixturemanager.FixtureLookupError(None, self, msg)
+
+ def _fillfixtures(self):
+ item = self._pyfuncitem
+ fixturenames = getattr(item, "fixturenames", self.fixturenames)
+ for argname in fixturenames:
+ if argname not in item.funcargs:
+ item.funcargs[argname] = self.getfixturevalue(argname)
+
+ def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
+ """ (deprecated) Return a testing resource managed by ``setup`` &
+ ``teardown`` calls. ``scope`` and ``extrakey`` determine when the
+ ``teardown`` function will be called so that subsequent calls to
+ ``setup`` would recreate the resource. With pytest-2.3 you often
+ do not need ``cached_setup()`` as you can directly declare a scope
+ on a fixture function and register a finalizer through
+ ``request.addfinalizer()``.
+
+ :arg teardown: function receiving a previously setup resource.
+ :arg setup: a no-argument function creating a resource.
+ :arg scope: a string value out of ``function``, ``class``, ``module``
+ or ``session`` indicating the caching lifecycle of the resource.
+ :arg extrakey: added to internal caching key of (funcargname, scope).
+ """
+ if not hasattr(self.config, '_setupcache'):
+ self.config._setupcache = {} # XXX weakref?
+ cachekey = (self.fixturename, self._getscopeitem(scope), extrakey)
+ cache = self.config._setupcache
+ try:
+ val = cache[cachekey]
+ except KeyError:
+ self._check_scope(self.fixturename, self.scope, scope)
+ val = setup()
+ cache[cachekey] = val
+ if teardown is not None:
+ def finalizer():
+ del cache[cachekey]
+ teardown(val)
+ self._addfinalizer(finalizer, scope=scope)
+ return val
+
+ def getfixturevalue(self, argname):
+ """ Dynamically run a named fixture function.
+
+ Declaring fixtures via function argument is recommended where possible.
+ But if you can only decide whether to use another fixture at test
+ setup time, you may use this function to retrieve it inside a fixture
+ or test function body.
+ """
+ return self._get_active_fixturedef(argname).cached_result[0]
+
+ def getfuncargvalue(self, argname):
+ """ Deprecated, use getfixturevalue. """
+ from _pytest import deprecated
+ warnings.warn(
+ deprecated.GETFUNCARGVALUE,
+ DeprecationWarning)
+ return self.getfixturevalue(argname)
+
+ def _get_active_fixturedef(self, argname):
+ try:
+ return self._fixture_defs[argname]
+ except KeyError:
+ try:
+ fixturedef = self._getnextfixturedef(argname)
+ except FixtureLookupError:
+ if argname == "request":
+ class PseudoFixtureDef:
+ cached_result = (self, [0], None)
+ scope = "function"
+ return PseudoFixtureDef
+ raise
+ # remove indent to prevent the python3 exception
+ # from leaking into the call
+ result = self._getfixturevalue(fixturedef)
+ self._fixture_values[argname] = result
+ self._fixture_defs[argname] = fixturedef
+ return fixturedef
+
+ def _get_fixturestack(self):
+ current = self
+ l = []
+ while 1:
+ fixturedef = getattr(current, "_fixturedef", None)
+ if fixturedef is None:
+ l.reverse()
+ return l
+ l.append(fixturedef)
+ current = current._parent_request
+
+ def _getfixturevalue(self, fixturedef):
+ # prepare a subrequest object before calling fixture function
+ # (latter managed by fixturedef)
+ argname = fixturedef.argname
+ funcitem = self._pyfuncitem
+ scope = fixturedef.scope
+ try:
+ param = funcitem.callspec.getparam(argname)
+ except (AttributeError, ValueError):
+ param = NOTSET
+ param_index = 0
+ if fixturedef.params is not None:
+ frame = inspect.stack()[3]
+ frameinfo = inspect.getframeinfo(frame[0])
+ source_path = frameinfo.filename
+ source_lineno = frameinfo.lineno
+ source_path = py.path.local(source_path)
+ if source_path.relto(funcitem.config.rootdir):
+ source_path = source_path.relto(funcitem.config.rootdir)
+ msg = (
+ "The requested fixture has no parameter defined for the "
+ "current test.\n\nRequested fixture '{0}' defined in:\n{1}"
+ "\n\nRequested here:\n{2}:{3}".format(
+ fixturedef.argname,
+ getlocation(fixturedef.func, funcitem.config.rootdir),
+ source_path,
+ source_lineno,
+ )
+ )
+ pytest.fail(msg)
+ else:
+ # indices might not be set if old-style metafunc.addcall() was used
+ param_index = funcitem.callspec.indices.get(argname, 0)
+ # if a parametrize invocation set a scope it will override
+ # the static scope defined with the fixture function
+ paramscopenum = funcitem.callspec._arg2scopenum.get(argname)
+ if paramscopenum is not None:
+ scope = scopes[paramscopenum]
+
+ subrequest = SubRequest(self, scope, param, param_index, fixturedef)
+
+ # check if a higher-level scoped fixture accesses a lower level one
+ subrequest._check_scope(argname, self.scope, scope)
+
+ # clear sys.exc_info before invoking the fixture (python bug?)
+ # if its not explicitly cleared it will leak into the call
+ exc_clear()
+ try:
+ # call the fixture function
+ val = fixturedef.execute(request=subrequest)
+ finally:
+ # if fixture function failed it might have registered finalizers
+ self.session._setupstate.addfinalizer(fixturedef.finish,
+ subrequest.node)
+ return val
+
+ def _check_scope(self, argname, invoking_scope, requested_scope):
+ if argname == "request":
+ return
+ if scopemismatch(invoking_scope, requested_scope):
+ # try to report something helpful
+ lines = self._factorytraceback()
+ pytest.fail("ScopeMismatch: You tried to access the %r scoped "
+ "fixture %r with a %r scoped request object, "
+ "involved factories\n%s" %(
+ (requested_scope, argname, invoking_scope, "\n".join(lines))),
+ pytrace=False)
+
+ def _factorytraceback(self):
+ lines = []
+ for fixturedef in self._get_fixturestack():
+ factory = fixturedef.func
+ fs, lineno = getfslineno(factory)
+ p = self._pyfuncitem.session.fspath.bestrelpath(fs)
+ args = _format_args(factory)
+ lines.append("%s:%d: def %s%s" %(
+ p, lineno, factory.__name__, args))
+ return lines
+
+ def _getscopeitem(self, scope):
+ if scope == "function":
+ # this might also be a non-function Item despite its attribute name
+ return self._pyfuncitem
+ node = get_scope_node(self._pyfuncitem, scope)
+ if node is None and scope == "class":
+ # fallback to function item itself
+ node = self._pyfuncitem
+ assert node
+ return node
+
+ def __repr__(self):
+ return "<FixtureRequest for %r>" %(self.node)
+
+
+class SubRequest(FixtureRequest):
+ """ a sub request for handling getting a fixture from a
+ test function/fixture. """
+ def __init__(self, request, scope, param, param_index, fixturedef):
+ self._parent_request = request
+ self.fixturename = fixturedef.argname
+ if param is not NOTSET:
+ self.param = param
+ self.param_index = param_index
+ self.scope = scope
+ self._fixturedef = fixturedef
+ self.addfinalizer = fixturedef.addfinalizer
+ self._pyfuncitem = request._pyfuncitem
+ self._fixture_values = request._fixture_values
+ self._fixture_defs = request._fixture_defs
+ self._arg2fixturedefs = request._arg2fixturedefs
+ self._arg2index = request._arg2index
+ self._fixturemanager = request._fixturemanager
+
+ def __repr__(self):
+ return "<SubRequest %r for %r>" % (self.fixturename, self._pyfuncitem)
+
+
+class ScopeMismatchError(Exception):
+ """ A fixture function tries to use a different fixture function which
+ which has a lower scope (e.g. a Session one calls a function one)
+ """
+
+
+scopes = "session module class function".split()
+scopenum_function = scopes.index("function")
+
+
+def scopemismatch(currentscope, newscope):
+ return scopes.index(newscope) > scopes.index(currentscope)
+
+
+def scope2index(scope, descr, where=None):
+ """Look up the index of ``scope`` and raise a descriptive value error
+ if not defined.
+ """
+ try:
+ return scopes.index(scope)
+ except ValueError:
+ raise ValueError(
+ "{0} {1}has an unsupported scope value '{2}'".format(
+ descr, 'from {0} '.format(where) if where else '',
+ scope)
+ )
+
+
+class FixtureLookupError(LookupError):
+ """ could not return a requested Fixture (missing or invalid). """
+ def __init__(self, argname, request, msg=None):
+ self.argname = argname
+ self.request = request
+ self.fixturestack = request._get_fixturestack()
+ self.msg = msg
+
+ def formatrepr(self):
+ tblines = []
+ addline = tblines.append
+ stack = [self.request._pyfuncitem.obj]
+ stack.extend(map(lambda x: x.func, self.fixturestack))
+ msg = self.msg
+ if msg is not None:
+ # the last fixture raise an error, let's present
+ # it at the requesting side
+ stack = stack[:-1]
+ for function in stack:
+ fspath, lineno = getfslineno(function)
+ try:
+ lines, _ = inspect.getsourcelines(get_real_func(function))
+ except (IOError, IndexError, TypeError):
+ error_msg = "file %s, line %s: source code not available"
+ addline(error_msg % (fspath, lineno+1))
+ else:
+ addline("file %s, line %s" % (fspath, lineno+1))
+ for i, line in enumerate(lines):
+ line = line.rstrip()
+ addline(" " + line)
+ if line.lstrip().startswith('def'):
+ break
+
+ if msg is None:
+ fm = self.request._fixturemanager
+ available = []
+ parentid = self.request._pyfuncitem.parent.nodeid
+ for name, fixturedefs in fm._arg2fixturedefs.items():
+ faclist = list(fm._matchfactories(fixturedefs, parentid))
+ if faclist and name not in available:
+ available.append(name)
+ msg = "fixture %r not found" % (self.argname,)
+ msg += "\n available fixtures: %s" %(", ".join(sorted(available)),)
+ msg += "\n use 'pytest --fixtures [testpath]' for help on them."
+
+ return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
+
+
+class FixtureLookupErrorRepr(TerminalRepr):
+ def __init__(self, filename, firstlineno, tblines, errorstring, argname):
+ self.tblines = tblines
+ self.errorstring = errorstring
+ self.filename = filename
+ self.firstlineno = firstlineno
+ self.argname = argname
+
+ def toterminal(self, tw):
+ # tw.line("FixtureLookupError: %s" %(self.argname), red=True)
+ for tbline in self.tblines:
+ tw.line(tbline.rstrip())
+ lines = self.errorstring.split("\n")
+ if lines:
+ tw.line('{0} {1}'.format(FormattedExcinfo.fail_marker,
+ lines[0].strip()), red=True)
+ for line in lines[1:]:
+ tw.line('{0} {1}'.format(FormattedExcinfo.flow_marker,
+ line.strip()), red=True)
+ tw.line()
+ tw.line("%s:%d" % (self.filename, self.firstlineno+1))
+
+
+def fail_fixturefunc(fixturefunc, msg):
+ fs, lineno = getfslineno(fixturefunc)
+ location = "%s:%s" % (fs, lineno+1)
+ source = _pytest._code.Source(fixturefunc)
+ pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location,
+ pytrace=False)
+
+def call_fixture_func(fixturefunc, request, kwargs):
+ yieldctx = is_generator(fixturefunc)
+ if yieldctx:
+ it = fixturefunc(**kwargs)
+ res = next(it)
+
+ def teardown():
+ try:
+ next(it)
+ except StopIteration:
+ pass
+ else:
+ fail_fixturefunc(fixturefunc,
+ "yield_fixture function has more than one 'yield'")
+
+ request.addfinalizer(teardown)
+ else:
+ res = fixturefunc(**kwargs)
+ return res
+
+
+class FixtureDef:
+ """ A container for a factory definition. """
+ def __init__(self, fixturemanager, baseid, argname, func, scope, params,
+ unittest=False, ids=None):
+ self._fixturemanager = fixturemanager
+ self.baseid = baseid or ''
+ self.has_location = baseid is not None
+ self.func = func
+ self.argname = argname
+ self.scope = scope
+ self.scopenum = scope2index(
+ scope or "function",
+ descr='fixture {0}'.format(func.__name__),
+ where=baseid
+ )
+ self.params = params
+ startindex = unittest and 1 or None
+ self.argnames = getfuncargnames(func, startindex=startindex)
+ self.unittest = unittest
+ self.ids = ids
+ self._finalizer = []
+
+ def addfinalizer(self, finalizer):
+ self._finalizer.append(finalizer)
+
+ def finish(self):
+ try:
+ while self._finalizer:
+ func = self._finalizer.pop()
+ func()
+ finally:
+ ihook = self._fixturemanager.session.ihook
+ ihook.pytest_fixture_post_finalizer(fixturedef=self)
+ # even if finalization fails, we invalidate
+ # the cached fixture value
+ if hasattr(self, "cached_result"):
+ del self.cached_result
+
+ def execute(self, request):
+ # get required arguments and register our own finish()
+ # with their finalization
+ for argname in self.argnames:
+ fixturedef = request._get_active_fixturedef(argname)
+ if argname != "request":
+ fixturedef.addfinalizer(self.finish)
+
+ my_cache_key = request.param_index
+ cached_result = getattr(self, "cached_result", None)
+ if cached_result is not None:
+ result, cache_key, err = cached_result
+ if my_cache_key == cache_key:
+ if err is not None:
+ py.builtin._reraise(*err)
+ else:
+ return result
+ # we have a previous but differently parametrized fixture instance
+ # so we need to tear it down before creating a new one
+ self.finish()
+ assert not hasattr(self, "cached_result")
+
+ ihook = self._fixturemanager.session.ihook
+ return ihook.pytest_fixture_setup(fixturedef=self, request=request)
+
+ def __repr__(self):
+ return ("<FixtureDef name=%r scope=%r baseid=%r >" %
+ (self.argname, self.scope, self.baseid))
+
+def pytest_fixture_setup(fixturedef, request):
+ """ Execution of fixture setup. """
+ kwargs = {}
+ for argname in fixturedef.argnames:
+ fixdef = request._get_active_fixturedef(argname)
+ result, arg_cache_key, exc = fixdef.cached_result
+ request._check_scope(argname, request.scope, fixdef.scope)
+ kwargs[argname] = result
+
+ fixturefunc = fixturedef.func
+ if fixturedef.unittest:
+ if request.instance is not None:
+ # bind the unbound method to the TestCase instance
+ fixturefunc = fixturedef.func.__get__(request.instance)
+ else:
+ # the fixture function needs to be bound to the actual
+ # request.instance so that code working with "fixturedef" behaves
+ # as expected.
+ if request.instance is not None:
+ fixturefunc = getimfunc(fixturedef.func)
+ if fixturefunc != fixturedef.func:
+ fixturefunc = fixturefunc.__get__(request.instance)
+ my_cache_key = request.param_index
+ try:
+ result = call_fixture_func(fixturefunc, request, kwargs)
+ except Exception:
+ fixturedef.cached_result = (None, my_cache_key, sys.exc_info())
+ raise
+ fixturedef.cached_result = (result, my_cache_key, None)
+ return result
+
+
+class FixtureFunctionMarker:
+ def __init__(self, scope, params, autouse=False, ids=None, name=None):
+ self.scope = scope
+ self.params = params
+ self.autouse = autouse
+ self.ids = ids
+ self.name = name
+
+ def __call__(self, function):
+ if isclass(function):
+ raise ValueError(
+ "class fixtures not supported (may be in the future)")
+ function._pytestfixturefunction = self
+ return function
+
+
+
+def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
+ """ (return a) decorator to mark a fixture factory function.
+
+ This decorator can be used (with or or without parameters) to define
+ a fixture function. The name of the fixture function can later be
+ referenced to cause its invocation ahead of running tests: test
+ modules or classes can use the pytest.mark.usefixtures(fixturename)
+ marker. Test functions can directly use fixture names as input
+ arguments in which case the fixture instance returned from the fixture
+ function will be injected.
+
+ :arg scope: the scope for which this fixture is shared, one of
+ "function" (default), "class", "module" or "session".
+
+ :arg params: an optional list of parameters which will cause multiple
+ invocations of the fixture function and all of the tests
+ using it.
+
+ :arg autouse: if True, the fixture func is activated for all tests that
+ can see it. If False (the default) then an explicit
+ reference is needed to activate the fixture.
+
+ :arg ids: list of string ids each corresponding to the params
+ so that they are part of the test id. If no ids are provided
+ they will be generated automatically from the params.
+
+ :arg name: the name of the fixture. This defaults to the name of the
+ decorated function. If a fixture is used in the same module in
+ which it is defined, the function name of the fixture will be
+ shadowed by the function arg that requests the fixture; one way
+ to resolve this is to name the decorated function
+ ``fixture_<fixturename>`` and then use
+ ``@pytest.fixture(name='<fixturename>')``.
+
+ Fixtures can optionally provide their values to test functions using a ``yield`` statement,
+ instead of ``return``. In this case, the code block after the ``yield`` statement is executed
+ as teardown code regardless of the test outcome. A fixture function must yield exactly once.
+ """
+ if callable(scope) and params is None and autouse == False:
+ # direct decoration
+ return FixtureFunctionMarker(
+ "function", params, autouse, name=name)(scope)
+ if params is not None and not isinstance(params, (list, tuple)):
+ params = list(params)
+ return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
+
+
+def yield_fixture(scope="function", params=None, autouse=False, ids=None, name=None):
+ """ (return a) decorator to mark a yield-fixture factory function.
+
+ .. deprecated:: 3.0
+ Use :py:func:`pytest.fixture` directly instead.
+ """
+ if callable(scope) and params is None and not autouse:
+ # direct decoration
+ return FixtureFunctionMarker(
+ "function", params, autouse, ids=ids, name=name)(scope)
+ else:
+ return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
+
+
+defaultfuncargprefixmarker = fixture()
+
+
+@fixture(scope="session")
+def pytestconfig(request):
+ """ the pytest config object with access to command line opts."""
+ return request.config
+
+
+class FixtureManager:
+ """
+ pytest fixtures definitions and information is stored and managed
+ from this class.
+
+ During collection fm.parsefactories() is called multiple times to parse
+ fixture function definitions into FixtureDef objects and internal
+ data structures.
+
+ During collection of test functions, metafunc-mechanics instantiate
+ a FuncFixtureInfo object which is cached per node/func-name.
+ This FuncFixtureInfo object is later retrieved by Function nodes
+ which themselves offer a fixturenames attribute.
+
+ The FuncFixtureInfo object holds information about fixtures and FixtureDefs
+ relevant for a particular function. An initial list of fixtures is
+ assembled like this:
+
+ - ini-defined usefixtures
+ - autouse-marked fixtures along the collection chain up from the function
+ - usefixtures markers at module/class/function level
+ - test function funcargs
+
+ Subsequently the funcfixtureinfo.fixturenames attribute is computed
+ as the closure of the fixtures needed to setup the initial fixtures,
+ i. e. fixtures needed by fixture functions themselves are appended
+ to the fixturenames list.
+
+ Upon the test-setup phases all fixturenames are instantiated, retrieved
+ by a lookup of their FuncFixtureInfo.
+ """
+
+ _argprefix = "pytest_funcarg__"
+ FixtureLookupError = FixtureLookupError
+ FixtureLookupErrorRepr = FixtureLookupErrorRepr
+
+ def __init__(self, session):
+ self.session = session
+ self.config = session.config
+ self._arg2fixturedefs = {}
+ self._holderobjseen = set()
+ self._arg2finish = {}
+ self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))]
+ session.config.pluginmanager.register(self, "funcmanage")
+
+
+ def getfixtureinfo(self, node, func, cls, funcargs=True):
+ if funcargs and not hasattr(node, "nofuncargs"):
+ if cls is not None:
+ startindex = 1
+ else:
+ startindex = None
+ argnames = getfuncargnames(func, startindex)
+ else:
+ argnames = ()
+ usefixtures = getattr(func, "usefixtures", None)
+ initialnames = argnames
+ if usefixtures is not None:
+ initialnames = usefixtures.args + initialnames
+ fm = node.session._fixturemanager
+ names_closure, arg2fixturedefs = fm.getfixtureclosure(initialnames,
+ node)
+ return FuncFixtureInfo(argnames, names_closure, arg2fixturedefs)
+
+ def pytest_plugin_registered(self, plugin):
+ nodeid = None
+ try:
+ p = py.path.local(plugin.__file__)
+ except AttributeError:
+ pass
+ else:
+ # construct the base nodeid which is later used to check
+ # what fixtures are visible for particular tests (as denoted
+ # by their test id)
+ if p.basename.startswith("conftest.py"):
+ nodeid = p.dirpath().relto(self.config.rootdir)
+ if p.sep != "/":
+ nodeid = nodeid.replace(p.sep, "/")
+ self.parsefactories(plugin, nodeid)
+
+ def _getautousenames(self, nodeid):
+ """ return a tuple of fixture names to be used. """
+ autousenames = []
+ for baseid, basenames in self._nodeid_and_autousenames:
+ if nodeid.startswith(baseid):
+ if baseid:
+ i = len(baseid)
+ nextchar = nodeid[i:i+1]
+ if nextchar and nextchar not in ":/":
+ continue
+ autousenames.extend(basenames)
+ # make sure autousenames are sorted by scope, scopenum 0 is session
+ autousenames.sort(
+ key=lambda x: self._arg2fixturedefs[x][-1].scopenum)
+ return autousenames
+
+ def getfixtureclosure(self, fixturenames, parentnode):
+ # collect the closure of all fixtures , starting with the given
+ # fixturenames as the initial set. As we have to visit all
+ # factory definitions anyway, we also return a arg2fixturedefs
+ # mapping so that the caller can reuse it and does not have
+ # to re-discover fixturedefs again for each fixturename
+ # (discovering matching fixtures for a given name/node is expensive)
+
+ parentid = parentnode.nodeid
+ fixturenames_closure = self._getautousenames(parentid)
+
+ def merge(otherlist):
+ for arg in otherlist:
+ if arg not in fixturenames_closure:
+ fixturenames_closure.append(arg)
+
+ merge(fixturenames)
+ arg2fixturedefs = {}
+ lastlen = -1
+ while lastlen != len(fixturenames_closure):
+ lastlen = len(fixturenames_closure)
+ for argname in fixturenames_closure:
+ if argname in arg2fixturedefs:
+ continue
+ fixturedefs = self.getfixturedefs(argname, parentid)
+ if fixturedefs:
+ arg2fixturedefs[argname] = fixturedefs
+ merge(fixturedefs[-1].argnames)
+ return fixturenames_closure, arg2fixturedefs
+
+ def pytest_generate_tests(self, metafunc):
+ for argname in metafunc.fixturenames:
+ faclist = metafunc._arg2fixturedefs.get(argname)
+ if faclist:
+ fixturedef = faclist[-1]
+ if fixturedef.params is not None:
+ func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]])
+ # skip directly parametrized arguments
+ argnames = func_params[0]
+ if not isinstance(argnames, (tuple, list)):
+ argnames = [x.strip() for x in argnames.split(",") if x.strip()]
+ if argname not in func_params and argname not in argnames:
+ metafunc.parametrize(argname, fixturedef.params,
+ indirect=True, scope=fixturedef.scope,
+ ids=fixturedef.ids)
+ else:
+ continue # will raise FixtureLookupError at setup time
+
+ def pytest_collection_modifyitems(self, items):
+ # separate parametrized setups
+ items[:] = reorder_items(items)
+
+ def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
+ if nodeid is not NOTSET:
+ holderobj = node_or_obj
+ else:
+ holderobj = node_or_obj.obj
+ nodeid = node_or_obj.nodeid
+ if holderobj in self._holderobjseen:
+ return
+ self._holderobjseen.add(holderobj)
+ autousenames = []
+ for name in dir(holderobj):
+ obj = getattr(holderobj, name, None)
+ # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
+ # or are "@pytest.fixture" marked
+ marker = getfixturemarker(obj)
+ if marker is None:
+ if not name.startswith(self._argprefix):
+ continue
+ if not callable(obj):
+ continue
+ marker = defaultfuncargprefixmarker
+ from _pytest import deprecated
+ self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name))
+ name = name[len(self._argprefix):]
+ elif not isinstance(marker, FixtureFunctionMarker):
+ # magic globals with __getattr__ might have got us a wrong
+ # fixture attribute
+ continue
+ else:
+ if marker.name:
+ name = marker.name
+ msg = 'fixtures cannot have "pytest_funcarg__" prefix ' \
+ 'and be decorated with @pytest.fixture:\n%s' % name
+ assert not name.startswith(self._argprefix), msg
+
+ fixture_def = FixtureDef(self, nodeid, name, obj,
+ marker.scope, marker.params,
+ unittest=unittest, ids=marker.ids)
+
+ faclist = self._arg2fixturedefs.setdefault(name, [])
+ if fixture_def.has_location:
+ faclist.append(fixture_def)
+ else:
+ # fixturedefs with no location are at the front
+ # so this inserts the current fixturedef after the
+ # existing fixturedefs from external plugins but
+ # before the fixturedefs provided in conftests.
+ i = len([f for f in faclist if not f.has_location])
+ faclist.insert(i, fixture_def)
+ if marker.autouse:
+ autousenames.append(name)
+
+ if autousenames:
+ self._nodeid_and_autousenames.append((nodeid or '', autousenames))
+
+ def getfixturedefs(self, argname, nodeid):
+ """
+ Gets a list of fixtures which are applicable to the given node id.
+
+ :param str argname: name of the fixture to search for
+ :param str nodeid: full node id of the requesting test.
+ :return: list[FixtureDef]
+ """
+ try:
+ fixturedefs = self._arg2fixturedefs[argname]
+ except KeyError:
+ return None
+ else:
+ return tuple(self._matchfactories(fixturedefs, nodeid))
+
+ def _matchfactories(self, fixturedefs, nodeid):
+ for fixturedef in fixturedefs:
+ if nodeid.startswith(fixturedef.baseid):
+ yield fixturedef
+
diff --git a/lib/spack/external/_pytest/freeze_support.py b/lib/spack/external/_pytest/freeze_support.py
new file mode 100644
index 0000000000..f78ccd298e
--- /dev/null
+++ b/lib/spack/external/_pytest/freeze_support.py
@@ -0,0 +1,45 @@
+"""
+Provides a function to report all internal modules for using freezing tools
+pytest
+"""
+
+def pytest_namespace():
+ return {'freeze_includes': freeze_includes}
+
+
+def freeze_includes():
+ """
+ Returns a list of module names used by py.test that should be
+ included by cx_freeze.
+ """
+ import py
+ import _pytest
+ result = list(_iter_all_modules(py))
+ result += list(_iter_all_modules(_pytest))
+ return result
+
+
+def _iter_all_modules(package, prefix=''):
+ """
+ Iterates over the names of all modules that can be found in the given
+ package, recursively.
+ Example:
+ _iter_all_modules(_pytest) ->
+ ['_pytest.assertion.newinterpret',
+ '_pytest.capture',
+ '_pytest.core',
+ ...
+ ]
+ """
+ import os
+ import pkgutil
+ if type(package) is not str:
+ path, prefix = package.__path__[0], package.__name__ + '.'
+ else:
+ path = package
+ for _, name, is_package in pkgutil.iter_modules([path]):
+ if is_package:
+ for m in _iter_all_modules(os.path.join(path, name), prefix=name + '.'):
+ yield prefix + m
+ else:
+ yield prefix + name \ No newline at end of file
diff --git a/lib/spack/external/_pytest/helpconfig.py b/lib/spack/external/_pytest/helpconfig.py
new file mode 100644
index 0000000000..6e66b11c48
--- /dev/null
+++ b/lib/spack/external/_pytest/helpconfig.py
@@ -0,0 +1,144 @@
+""" version info, help messages, tracing configuration. """
+import py
+import pytest
+import os, sys
+
+def pytest_addoption(parser):
+ group = parser.getgroup('debugconfig')
+ group.addoption('--version', action="store_true",
+ help="display pytest lib version and import information.")
+ group._addoption("-h", "--help", action="store_true", dest="help",
+ help="show help message and configuration info")
+ group._addoption('-p', action="append", dest="plugins", default = [],
+ metavar="name",
+ help="early-load given plugin (multi-allowed). "
+ "To avoid loading of plugins, use the `no:` prefix, e.g. "
+ "`no:doctest`.")
+ group.addoption('--traceconfig', '--trace-config',
+ action="store_true", default=False,
+ help="trace considerations of conftest.py files."),
+ group.addoption('--debug',
+ action="store_true", dest="debug", default=False,
+ help="store internal tracing debug information in 'pytestdebug.log'.")
+ group._addoption(
+ '-o', '--override-ini', nargs='*', dest="override_ini",
+ action="append",
+ help="override config option with option=value style, e.g. `-o xfail_strict=True`.")
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_cmdline_parse():
+ outcome = yield
+ config = outcome.get_result()
+ if config.option.debug:
+ path = os.path.abspath("pytestdebug.log")
+ debugfile = open(path, 'w')
+ debugfile.write("versions pytest-%s, py-%s, "
+ "python-%s\ncwd=%s\nargs=%s\n\n" %(
+ pytest.__version__, py.__version__,
+ ".".join(map(str, sys.version_info)),
+ os.getcwd(), config._origargs))
+ config.trace.root.setwriter(debugfile.write)
+ undo_tracing = config.pluginmanager.enable_tracing()
+ sys.stderr.write("writing pytestdebug information to %s\n" % path)
+
+ def unset_tracing():
+ debugfile.close()
+ sys.stderr.write("wrote pytestdebug information to %s\n" %
+ debugfile.name)
+ config.trace.root.setwriter(None)
+ undo_tracing()
+
+ config.add_cleanup(unset_tracing)
+
+def pytest_cmdline_main(config):
+ if config.option.version:
+ p = py.path.local(pytest.__file__)
+ sys.stderr.write("This is pytest version %s, imported from %s\n" %
+ (pytest.__version__, p))
+ plugininfo = getpluginversioninfo(config)
+ if plugininfo:
+ for line in plugininfo:
+ sys.stderr.write(line + "\n")
+ return 0
+ elif config.option.help:
+ config._do_configure()
+ showhelp(config)
+ config._ensure_unconfigure()
+ return 0
+
+def showhelp(config):
+ reporter = config.pluginmanager.get_plugin('terminalreporter')
+ tw = reporter._tw
+ tw.write(config._parser.optparser.format_help())
+ tw.line()
+ tw.line()
+ tw.line("[pytest] ini-options in the first "
+ "pytest.ini|tox.ini|setup.cfg file found:")
+ tw.line()
+
+ for name in config._parser._ininames:
+ help, type, default = config._parser._inidict[name]
+ if type is None:
+ type = "string"
+ spec = "%s (%s)" % (name, type)
+ line = " %-24s %s" %(spec, help)
+ tw.line(line[:tw.fullwidth])
+
+ tw.line()
+ tw.line("environment variables:")
+ vars = [
+ ("PYTEST_ADDOPTS", "extra command line options"),
+ ("PYTEST_PLUGINS", "comma-separated plugins to load during startup"),
+ ("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals")
+ ]
+ for name, help in vars:
+ tw.line(" %-24s %s" % (name, help))
+ tw.line()
+ tw.line()
+
+ tw.line("to see available markers type: pytest --markers")
+ tw.line("to see available fixtures type: pytest --fixtures")
+ tw.line("(shown according to specified file_or_dir or current dir "
+ "if not specified)")
+
+ for warningreport in reporter.stats.get('warnings', []):
+ tw.line("warning : " + warningreport.message, red=True)
+ return
+
+
+conftest_options = [
+ ('pytest_plugins', 'list of plugin names to load'),
+]
+
+def getpluginversioninfo(config):
+ lines = []
+ plugininfo = config.pluginmanager.list_plugin_distinfo()
+ if plugininfo:
+ lines.append("setuptools registered plugins:")
+ for plugin, dist in plugininfo:
+ loc = getattr(plugin, '__file__', repr(plugin))
+ content = "%s-%s at %s" % (dist.project_name, dist.version, loc)
+ lines.append(" " + content)
+ return lines
+
+def pytest_report_header(config):
+ lines = []
+ if config.option.debug or config.option.traceconfig:
+ lines.append("using: pytest-%s pylib-%s" %
+ (pytest.__version__,py.__version__))
+
+ verinfo = getpluginversioninfo(config)
+ if verinfo:
+ lines.extend(verinfo)
+
+ if config.option.traceconfig:
+ lines.append("active plugins:")
+ items = config.pluginmanager.list_name_plugin()
+ for name, plugin in items:
+ if hasattr(plugin, '__file__'):
+ r = plugin.__file__
+ else:
+ r = repr(plugin)
+ lines.append(" %-20s: %s" %(name, r))
+ return lines
diff --git a/lib/spack/external/_pytest/hookspec.py b/lib/spack/external/_pytest/hookspec.py
new file mode 100644
index 0000000000..b5f51eccf5
--- /dev/null
+++ b/lib/spack/external/_pytest/hookspec.py
@@ -0,0 +1,314 @@
+""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
+
+from _pytest._pluggy import HookspecMarker
+
+hookspec = HookspecMarker("pytest")
+
+# -------------------------------------------------------------------------
+# Initialization hooks called for every plugin
+# -------------------------------------------------------------------------
+
+@hookspec(historic=True)
+def pytest_addhooks(pluginmanager):
+ """called at plugin registration time to allow adding new hooks via a call to
+ pluginmanager.add_hookspecs(module_or_class, prefix)."""
+
+
+@hookspec(historic=True)
+def pytest_namespace():
+ """return dict of name->object to be made globally available in
+ the pytest namespace. This hook is called at plugin registration
+ time.
+ """
+
+@hookspec(historic=True)
+def pytest_plugin_registered(plugin, manager):
+ """ a new pytest plugin got registered. """
+
+
+@hookspec(historic=True)
+def pytest_addoption(parser):
+ """register argparse-style options and ini-style config values,
+ called once at the beginning of a test run.
+
+ .. note::
+
+ This function should be implemented only in plugins or ``conftest.py``
+ files situated at the tests root directory due to how pytest
+ :ref:`discovers plugins during startup <pluginorder>`.
+
+ :arg parser: To add command line options, call
+ :py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
+ To add ini-file values call :py:func:`parser.addini(...)
+ <_pytest.config.Parser.addini>`.
+
+ Options can later be accessed through the
+ :py:class:`config <_pytest.config.Config>` object, respectively:
+
+ - :py:func:`config.getoption(name) <_pytest.config.Config.getoption>` to
+ retrieve the value of a command line option.
+
+ - :py:func:`config.getini(name) <_pytest.config.Config.getini>` to retrieve
+ a value read from an ini-style file.
+
+ The config object is passed around on many internal objects via the ``.config``
+ attribute or can be retrieved as the ``pytestconfig`` fixture or accessed
+ via (deprecated) ``pytest.config``.
+ """
+
+@hookspec(historic=True)
+def pytest_configure(config):
+ """ called after command line options have been parsed
+ and all plugins and initial conftest files been loaded.
+ This hook is called for every plugin.
+ """
+
+# -------------------------------------------------------------------------
+# Bootstrapping hooks called for plugins registered early enough:
+# internal and 3rd party plugins as well as directly
+# discoverable conftest.py local plugins.
+# -------------------------------------------------------------------------
+
+@hookspec(firstresult=True)
+def pytest_cmdline_parse(pluginmanager, args):
+ """return initialized config object, parsing the specified args. """
+
+def pytest_cmdline_preparse(config, args):
+ """(deprecated) modify command line arguments before option parsing. """
+
+@hookspec(firstresult=True)
+def pytest_cmdline_main(config):
+ """ called for performing the main command line action. The default
+ implementation will invoke the configure hooks and runtest_mainloop. """
+
+def pytest_load_initial_conftests(early_config, parser, args):
+ """ implements the loading of initial conftest files ahead
+ of command line option parsing. """
+
+
+# -------------------------------------------------------------------------
+# collection hooks
+# -------------------------------------------------------------------------
+
+@hookspec(firstresult=True)
+def pytest_collection(session):
+ """ perform the collection protocol for the given session. """
+
+def pytest_collection_modifyitems(session, config, items):
+ """ called after collection has been performed, may filter or re-order
+ the items in-place."""
+
+def pytest_collection_finish(session):
+ """ called after collection has been performed and modified. """
+
+@hookspec(firstresult=True)
+def pytest_ignore_collect(path, config):
+ """ return True to prevent considering this path for collection.
+ This hook is consulted for all files and directories prior to calling
+ more specific hooks.
+ """
+
+@hookspec(firstresult=True)
+def pytest_collect_directory(path, parent):
+ """ called before traversing a directory for collection files. """
+
+def pytest_collect_file(path, parent):
+ """ return collection Node or None for the given path. Any new node
+ needs to have the specified ``parent`` as a parent."""
+
+# logging hooks for collection
+def pytest_collectstart(collector):
+ """ collector starts collecting. """
+
+def pytest_itemcollected(item):
+ """ we just collected a test item. """
+
+def pytest_collectreport(report):
+ """ collector finished collecting. """
+
+def pytest_deselected(items):
+ """ called for test items deselected by keyword. """
+
+@hookspec(firstresult=True)
+def pytest_make_collect_report(collector):
+ """ perform ``collector.collect()`` and return a CollectReport. """
+
+# -------------------------------------------------------------------------
+# Python test function related hooks
+# -------------------------------------------------------------------------
+
+@hookspec(firstresult=True)
+def pytest_pycollect_makemodule(path, parent):
+ """ return a Module collector or None for the given path.
+ This hook will be called for each matching test module path.
+ The pytest_collect_file hook needs to be used if you want to
+ create test modules for files that do not match as a test module.
+ """
+
+@hookspec(firstresult=True)
+def pytest_pycollect_makeitem(collector, name, obj):
+ """ return custom item/collector for a python object in a module, or None. """
+
+@hookspec(firstresult=True)
+def pytest_pyfunc_call(pyfuncitem):
+ """ call underlying test function. """
+
+def pytest_generate_tests(metafunc):
+ """ generate (multiple) parametrized calls to a test function."""
+
+@hookspec(firstresult=True)
+def pytest_make_parametrize_id(config, val):
+ """Return a user-friendly string representation of the given ``val`` that will be used
+ by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``.
+ """
+
+# -------------------------------------------------------------------------
+# generic runtest related hooks
+# -------------------------------------------------------------------------
+
+@hookspec(firstresult=True)
+def pytest_runtestloop(session):
+ """ called for performing the main runtest loop
+ (after collection finished). """
+
+def pytest_itemstart(item, node):
+ """ (deprecated, use pytest_runtest_logstart). """
+
+@hookspec(firstresult=True)
+def pytest_runtest_protocol(item, nextitem):
+ """ implements the runtest_setup/call/teardown protocol for
+ the given test item, including capturing exceptions and calling
+ reporting hooks.
+
+ :arg item: test item for which the runtest protocol is performed.
+
+ :arg nextitem: the scheduled-to-be-next test item (or None if this
+ is the end my friend). This argument is passed on to
+ :py:func:`pytest_runtest_teardown`.
+
+ :return boolean: True if no further hook implementations should be invoked.
+ """
+
+def pytest_runtest_logstart(nodeid, location):
+ """ signal the start of running a single test item. """
+
+def pytest_runtest_setup(item):
+ """ called before ``pytest_runtest_call(item)``. """
+
+def pytest_runtest_call(item):
+ """ called to execute the test ``item``. """
+
+def pytest_runtest_teardown(item, nextitem):
+ """ called after ``pytest_runtest_call``.
+
+ :arg nextitem: the scheduled-to-be-next test item (None if no further
+ test item is scheduled). This argument can be used to
+ perform exact teardowns, i.e. calling just enough finalizers
+ so that nextitem only needs to call setup-functions.
+ """
+
+@hookspec(firstresult=True)
+def pytest_runtest_makereport(item, call):
+ """ return a :py:class:`_pytest.runner.TestReport` object
+ for the given :py:class:`pytest.Item` and
+ :py:class:`_pytest.runner.CallInfo`.
+ """
+
+def pytest_runtest_logreport(report):
+ """ process a test setup/call/teardown report relating to
+ the respective phase of executing a test. """
+
+# -------------------------------------------------------------------------
+# Fixture related hooks
+# -------------------------------------------------------------------------
+
+@hookspec(firstresult=True)
+def pytest_fixture_setup(fixturedef, request):
+ """ performs fixture setup execution. """
+
+def pytest_fixture_post_finalizer(fixturedef):
+ """ called after fixture teardown, but before the cache is cleared so
+ the fixture result cache ``fixturedef.cached_result`` can
+ still be accessed."""
+
+# -------------------------------------------------------------------------
+# test session related hooks
+# -------------------------------------------------------------------------
+
+def pytest_sessionstart(session):
+ """ before session.main() is called. """
+
+def pytest_sessionfinish(session, exitstatus):
+ """ whole test run finishes. """
+
+def pytest_unconfigure(config):
+ """ called before test process is exited. """
+
+
+# -------------------------------------------------------------------------
+# hooks for customising the assert methods
+# -------------------------------------------------------------------------
+
+def pytest_assertrepr_compare(config, op, left, right):
+ """return explanation for comparisons in failing assert expressions.
+
+ Return None for no custom explanation, otherwise return a list
+ of strings. The strings will be joined by newlines but any newlines
+ *in* a string will be escaped. Note that all but the first line will
+ be indented sligthly, the intention is for the first line to be a summary.
+ """
+
+# -------------------------------------------------------------------------
+# hooks for influencing reporting (invoked from _pytest_terminal)
+# -------------------------------------------------------------------------
+
+def pytest_report_header(config, startdir):
+ """ return a string to be displayed as header info for terminal reporting."""
+
+@hookspec(firstresult=True)
+def pytest_report_teststatus(report):
+ """ return result-category, shortletter and verbose word for reporting."""
+
+def pytest_terminal_summary(terminalreporter, exitstatus):
+ """ add additional section in terminal summary reporting. """
+
+
+@hookspec(historic=True)
+def pytest_logwarning(message, code, nodeid, fslocation):
+ """ process a warning specified by a message, a code string,
+ a nodeid and fslocation (both of which may be None
+ if the warning is not tied to a partilar node/location)."""
+
+# -------------------------------------------------------------------------
+# doctest hooks
+# -------------------------------------------------------------------------
+
+@hookspec(firstresult=True)
+def pytest_doctest_prepare_content(content):
+ """ return processed content for a given doctest"""
+
+# -------------------------------------------------------------------------
+# error handling and internal debugging hooks
+# -------------------------------------------------------------------------
+
+def pytest_internalerror(excrepr, excinfo):
+ """ called for internal errors. """
+
+def pytest_keyboard_interrupt(excinfo):
+ """ called for keyboard interrupt. """
+
+def pytest_exception_interact(node, call, report):
+ """called when an exception was raised which can potentially be
+ interactively handled.
+
+ This hook is only called if an exception was raised
+ that is not an internal exception like ``skip.Exception``.
+ """
+
+def pytest_enter_pdb(config):
+ """ called upon pdb.set_trace(), can be used by plugins to take special
+ action just before the python debugger enters in interactive mode.
+
+ :arg config: pytest config object
+ :type config: _pytest.config.Config
+ """
diff --git a/lib/spack/external/_pytest/junitxml.py b/lib/spack/external/_pytest/junitxml.py
new file mode 100644
index 0000000000..317382e637
--- /dev/null
+++ b/lib/spack/external/_pytest/junitxml.py
@@ -0,0 +1,413 @@
+"""
+ report test results in JUnit-XML format,
+ for use with Jenkins and build integration servers.
+
+
+Based on initial code from Ross Lawley.
+"""
+# Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
+# src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
+
+import functools
+import py
+import os
+import re
+import sys
+import time
+import pytest
+from _pytest.config import filename_arg
+
+# Python 2.X and 3.X compatibility
+if sys.version_info[0] < 3:
+ from codecs import open
+else:
+ unichr = chr
+ unicode = str
+ long = int
+
+
+class Junit(py.xml.Namespace):
+ pass
+
+
+# We need to get the subset of the invalid unicode ranges according to
+# XML 1.0 which are valid in this python build. Hence we calculate
+# this dynamically instead of hardcoding it. The spec range of valid
+# chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
+# | [#x10000-#x10FFFF]
+_legal_chars = (0x09, 0x0A, 0x0d)
+_legal_ranges = (
+ (0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF),
+)
+_legal_xml_re = [
+ unicode("%s-%s") % (unichr(low), unichr(high))
+ for (low, high) in _legal_ranges if low < sys.maxunicode
+]
+_legal_xml_re = [unichr(x) for x in _legal_chars] + _legal_xml_re
+illegal_xml_re = re.compile(unicode('[^%s]') % unicode('').join(_legal_xml_re))
+del _legal_chars
+del _legal_ranges
+del _legal_xml_re
+
+_py_ext_re = re.compile(r"\.py$")
+
+
+def bin_xml_escape(arg):
+ def repl(matchobj):
+ i = ord(matchobj.group())
+ if i <= 0xFF:
+ return unicode('#x%02X') % i
+ else:
+ return unicode('#x%04X') % i
+
+ return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
+
+
+class _NodeReporter(object):
+ def __init__(self, nodeid, xml):
+
+ self.id = nodeid
+ self.xml = xml
+ self.add_stats = self.xml.add_stats
+ self.duration = 0
+ self.properties = []
+ self.nodes = []
+ self.testcase = None
+ self.attrs = {}
+
+ def append(self, node):
+ self.xml.add_stats(type(node).__name__)
+ self.nodes.append(node)
+
+ def add_property(self, name, value):
+ self.properties.append((str(name), bin_xml_escape(value)))
+
+ def make_properties_node(self):
+ """Return a Junit node containing custom properties, if any.
+ """
+ if self.properties:
+ return Junit.properties([
+ Junit.property(name=name, value=value)
+ for name, value in self.properties
+ ])
+ return ''
+
+ def record_testreport(self, testreport):
+ assert not self.testcase
+ names = mangle_test_address(testreport.nodeid)
+ classnames = names[:-1]
+ if self.xml.prefix:
+ classnames.insert(0, self.xml.prefix)
+ attrs = {
+ "classname": ".".join(classnames),
+ "name": bin_xml_escape(names[-1]),
+ "file": testreport.location[0],
+ }
+ if testreport.location[1] is not None:
+ attrs["line"] = testreport.location[1]
+ self.attrs = attrs
+
+ def to_xml(self):
+ testcase = Junit.testcase(time=self.duration, **self.attrs)
+ testcase.append(self.make_properties_node())
+ for node in self.nodes:
+ testcase.append(node)
+ return testcase
+
+ def _add_simple(self, kind, message, data=None):
+ data = bin_xml_escape(data)
+ node = kind(data, message=message)
+ self.append(node)
+
+ def _write_captured_output(self, report):
+ for capname in ('out', 'err'):
+ content = getattr(report, 'capstd' + capname)
+ if content:
+ tag = getattr(Junit, 'system-' + capname)
+ self.append(tag(bin_xml_escape(content)))
+
+ def append_pass(self, report):
+ self.add_stats('passed')
+ self._write_captured_output(report)
+
+ def append_failure(self, report):
+ # msg = str(report.longrepr.reprtraceback.extraline)
+ if hasattr(report, "wasxfail"):
+ self._add_simple(
+ Junit.skipped,
+ "xfail-marked test passes unexpectedly")
+ else:
+ if hasattr(report.longrepr, "reprcrash"):
+ message = report.longrepr.reprcrash.message
+ elif isinstance(report.longrepr, (unicode, str)):
+ message = report.longrepr
+ else:
+ message = str(report.longrepr)
+ message = bin_xml_escape(message)
+ fail = Junit.failure(message=message)
+ fail.append(bin_xml_escape(report.longrepr))
+ self.append(fail)
+ self._write_captured_output(report)
+
+ def append_collect_error(self, report):
+ # msg = str(report.longrepr.reprtraceback.extraline)
+ self.append(Junit.error(bin_xml_escape(report.longrepr),
+ message="collection failure"))
+
+ def append_collect_skipped(self, report):
+ self._add_simple(
+ Junit.skipped, "collection skipped", report.longrepr)
+
+ def append_error(self, report):
+ if getattr(report, 'when', None) == 'teardown':
+ msg = "test teardown failure"
+ else:
+ msg = "test setup failure"
+ self._add_simple(
+ Junit.error, msg, report.longrepr)
+ self._write_captured_output(report)
+
+ def append_skipped(self, report):
+ if hasattr(report, "wasxfail"):
+ self._add_simple(
+ Junit.skipped, "expected test failure", report.wasxfail
+ )
+ else:
+ filename, lineno, skipreason = report.longrepr
+ if skipreason.startswith("Skipped: "):
+ skipreason = bin_xml_escape(skipreason[9:])
+ self.append(
+ Junit.skipped("%s:%s: %s" % (filename, lineno, skipreason),
+ type="pytest.skip",
+ message=skipreason))
+ self._write_captured_output(report)
+
+ def finalize(self):
+ data = self.to_xml().unicode(indent=0)
+ self.__dict__.clear()
+ self.to_xml = lambda: py.xml.raw(data)
+
+
+@pytest.fixture
+def record_xml_property(request):
+ """Add extra xml properties to the tag for the calling test.
+ The fixture is callable with ``(name, value)``, with value being automatically
+ xml-encoded.
+ """
+ request.node.warn(
+ code='C3',
+ message='record_xml_property is an experimental feature',
+ )
+ xml = getattr(request.config, "_xml", None)
+ if xml is not None:
+ node_reporter = xml.node_reporter(request.node.nodeid)
+ return node_reporter.add_property
+ else:
+ def add_property_noop(name, value):
+ pass
+
+ return add_property_noop
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("terminal reporting")
+ group.addoption(
+ '--junitxml', '--junit-xml',
+ action="store",
+ dest="xmlpath",
+ metavar="path",
+ type=functools.partial(filename_arg, optname="--junitxml"),
+ default=None,
+ help="create junit-xml style report file at given path.")
+ group.addoption(
+ '--junitprefix', '--junit-prefix',
+ action="store",
+ metavar="str",
+ default=None,
+ help="prepend prefix to classnames in junit-xml output")
+
+
+def pytest_configure(config):
+ xmlpath = config.option.xmlpath
+ # prevent opening xmllog on slave nodes (xdist)
+ if xmlpath and not hasattr(config, 'slaveinput'):
+ config._xml = LogXML(xmlpath, config.option.junitprefix)
+ config.pluginmanager.register(config._xml)
+
+
+def pytest_unconfigure(config):
+ xml = getattr(config, '_xml', None)
+ if xml:
+ del config._xml
+ config.pluginmanager.unregister(xml)
+
+
+def mangle_test_address(address):
+ path, possible_open_bracket, params = address.partition('[')
+ names = path.split("::")
+ try:
+ names.remove('()')
+ except ValueError:
+ pass
+ # convert file path to dotted path
+ names[0] = names[0].replace("/", '.')
+ names[0] = _py_ext_re.sub("", names[0])
+ # put any params back
+ names[-1] += possible_open_bracket + params
+ return names
+
+
+class LogXML(object):
+ def __init__(self, logfile, prefix):
+ logfile = os.path.expanduser(os.path.expandvars(logfile))
+ self.logfile = os.path.normpath(os.path.abspath(logfile))
+ self.prefix = prefix
+ self.stats = dict.fromkeys([
+ 'error',
+ 'passed',
+ 'failure',
+ 'skipped',
+ ], 0)
+ self.node_reporters = {} # nodeid -> _NodeReporter
+ self.node_reporters_ordered = []
+ self.global_properties = []
+
+ def finalize(self, report):
+ nodeid = getattr(report, 'nodeid', report)
+ # local hack to handle xdist report order
+ slavenode = getattr(report, 'node', None)
+ reporter = self.node_reporters.pop((nodeid, slavenode))
+ if reporter is not None:
+ reporter.finalize()
+
+ def node_reporter(self, report):
+ nodeid = getattr(report, 'nodeid', report)
+ # local hack to handle xdist report order
+ slavenode = getattr(report, 'node', None)
+
+ key = nodeid, slavenode
+
+ if key in self.node_reporters:
+ # TODO: breasks for --dist=each
+ return self.node_reporters[key]
+
+ reporter = _NodeReporter(nodeid, self)
+
+ self.node_reporters[key] = reporter
+ self.node_reporters_ordered.append(reporter)
+
+ return reporter
+
+ def add_stats(self, key):
+ if key in self.stats:
+ self.stats[key] += 1
+
+ def _opentestcase(self, report):
+ reporter = self.node_reporter(report)
+ reporter.record_testreport(report)
+ return reporter
+
+ def pytest_runtest_logreport(self, report):
+ """handle a setup/call/teardown report, generating the appropriate
+ xml tags as necessary.
+
+ note: due to plugins like xdist, this hook may be called in interlaced
+ order with reports from other nodes. for example:
+
+ usual call order:
+ -> setup node1
+ -> call node1
+ -> teardown node1
+ -> setup node2
+ -> call node2
+ -> teardown node2
+
+ possible call order in xdist:
+ -> setup node1
+ -> call node1
+ -> setup node2
+ -> call node2
+ -> teardown node2
+ -> teardown node1
+ """
+ if report.passed:
+ if report.when == "call": # ignore setup/teardown
+ reporter = self._opentestcase(report)
+ reporter.append_pass(report)
+ elif report.failed:
+ reporter = self._opentestcase(report)
+ if report.when == "call":
+ reporter.append_failure(report)
+ else:
+ reporter.append_error(report)
+ elif report.skipped:
+ reporter = self._opentestcase(report)
+ reporter.append_skipped(report)
+ self.update_testcase_duration(report)
+ if report.when == "teardown":
+ self.finalize(report)
+
+ def update_testcase_duration(self, report):
+ """accumulates total duration for nodeid from given report and updates
+ the Junit.testcase with the new total if already created.
+ """
+ reporter = self.node_reporter(report)
+ reporter.duration += getattr(report, 'duration', 0.0)
+
+ def pytest_collectreport(self, report):
+ if not report.passed:
+ reporter = self._opentestcase(report)
+ if report.failed:
+ reporter.append_collect_error(report)
+ else:
+ reporter.append_collect_skipped(report)
+
+ def pytest_internalerror(self, excrepr):
+ reporter = self.node_reporter('internal')
+ reporter.attrs.update(classname="pytest", name='internal')
+ reporter._add_simple(Junit.error, 'internal error', excrepr)
+
+ def pytest_sessionstart(self):
+ self.suite_start_time = time.time()
+
+ def pytest_sessionfinish(self):
+ dirname = os.path.dirname(os.path.abspath(self.logfile))
+ if not os.path.isdir(dirname):
+ os.makedirs(dirname)
+ logfile = open(self.logfile, 'w', encoding='utf-8')
+ suite_stop_time = time.time()
+ suite_time_delta = suite_stop_time - self.suite_start_time
+
+ numtests = self.stats['passed'] + self.stats['failure'] + self.stats['skipped'] + self.stats['error']
+
+ logfile.write('<?xml version="1.0" encoding="utf-8"?>')
+
+ logfile.write(Junit.testsuite(
+ self._get_global_properties_node(),
+ [x.to_xml() for x in self.node_reporters_ordered],
+ name="pytest",
+ errors=self.stats['error'],
+ failures=self.stats['failure'],
+ skips=self.stats['skipped'],
+ tests=numtests,
+ time="%.3f" % suite_time_delta, ).unicode(indent=0))
+ logfile.close()
+
+ def pytest_terminal_summary(self, terminalreporter):
+ terminalreporter.write_sep("-",
+ "generated xml file: %s" % (self.logfile))
+
+ def add_global_property(self, name, value):
+ self.global_properties.append((str(name), bin_xml_escape(value)))
+
+ def _get_global_properties_node(self):
+ """Return a Junit node containing custom properties, if any.
+ """
+ if self.global_properties:
+ return Junit.properties(
+ [
+ Junit.property(name=name, value=value)
+ for name, value in self.global_properties
+ ]
+ )
+ return ''
diff --git a/lib/spack/external/_pytest/main.py b/lib/spack/external/_pytest/main.py
new file mode 100644
index 0000000000..52876c12a4
--- /dev/null
+++ b/lib/spack/external/_pytest/main.py
@@ -0,0 +1,762 @@
+""" core implementation of testing process: init, session, runtest loop. """
+import functools
+import os
+import sys
+
+import _pytest
+import _pytest._code
+import py
+import pytest
+try:
+ from collections import MutableMapping as MappingMixin
+except ImportError:
+ from UserDict import DictMixin as MappingMixin
+
+from _pytest.config import directory_arg
+from _pytest.runner import collect_one_node
+
+tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
+
+# exitcodes for the command line
+EXIT_OK = 0
+EXIT_TESTSFAILED = 1
+EXIT_INTERRUPTED = 2
+EXIT_INTERNALERROR = 3
+EXIT_USAGEERROR = 4
+EXIT_NOTESTSCOLLECTED = 5
+
+def pytest_addoption(parser):
+ parser.addini("norecursedirs", "directory patterns to avoid for recursion",
+ type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg'])
+ parser.addini("testpaths", "directories to search for tests when no files or directories are given in the command line.",
+ type="args", default=[])
+ #parser.addini("dirpatterns",
+ # "patterns specifying possible locations of test files",
+ # type="linelist", default=["**/test_*.txt",
+ # "**/test_*.py", "**/*_test.py"]
+ #)
+ group = parser.getgroup("general", "running and selection options")
+ group._addoption('-x', '--exitfirst', action="store_const",
+ dest="maxfail", const=1,
+ help="exit instantly on first error or failed test."),
+ group._addoption('--maxfail', metavar="num",
+ action="store", type=int, dest="maxfail", default=0,
+ help="exit after first num failures or errors.")
+ group._addoption('--strict', action="store_true",
+ help="run pytest in strict mode, warnings become errors.")
+ group._addoption("-c", metavar="file", type=str, dest="inifilename",
+ help="load configuration from `file` instead of trying to locate one of the implicit configuration files.")
+ group._addoption("--continue-on-collection-errors", action="store_true",
+ default=False, dest="continue_on_collection_errors",
+ help="Force test execution even if collection errors occur.")
+
+ group = parser.getgroup("collect", "collection")
+ group.addoption('--collectonly', '--collect-only', action="store_true",
+ help="only collect tests, don't execute them."),
+ group.addoption('--pyargs', action="store_true",
+ help="try to interpret all arguments as python packages.")
+ group.addoption("--ignore", action="append", metavar="path",
+ help="ignore path during collection (multi-allowed).")
+ # when changing this to --conf-cut-dir, config.py Conftest.setinitial
+ # needs upgrading as well
+ group.addoption('--confcutdir', dest="confcutdir", default=None,
+ metavar="dir", type=functools.partial(directory_arg, optname="--confcutdir"),
+ help="only load conftest.py's relative to specified dir.")
+ group.addoption('--noconftest', action="store_true",
+ dest="noconftest", default=False,
+ help="Don't load any conftest.py files.")
+ group.addoption('--keepduplicates', '--keep-duplicates', action="store_true",
+ dest="keepduplicates", default=False,
+ help="Keep duplicate tests.")
+
+ group = parser.getgroup("debugconfig",
+ "test session debugging and configuration")
+ group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
+ help="base temporary directory for this test run.")
+
+
+def pytest_namespace():
+ collect = dict(Item=Item, Collector=Collector, File=File, Session=Session)
+ return dict(collect=collect)
+
+
+def pytest_configure(config):
+ pytest.config = config # compatibiltiy
+
+
+def wrap_session(config, doit):
+ """Skeleton command line program"""
+ session = Session(config)
+ session.exitstatus = EXIT_OK
+ initstate = 0
+ try:
+ try:
+ config._do_configure()
+ initstate = 1
+ config.hook.pytest_sessionstart(session=session)
+ initstate = 2
+ session.exitstatus = doit(config, session) or 0
+ except pytest.UsageError:
+ raise
+ except KeyboardInterrupt:
+ excinfo = _pytest._code.ExceptionInfo()
+ if initstate < 2 and isinstance(
+ excinfo.value, pytest.exit.Exception):
+ sys.stderr.write('{0}: {1}\n'.format(
+ excinfo.typename, excinfo.value.msg))
+ config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
+ session.exitstatus = EXIT_INTERRUPTED
+ except:
+ excinfo = _pytest._code.ExceptionInfo()
+ config.notify_exception(excinfo, config.option)
+ session.exitstatus = EXIT_INTERNALERROR
+ if excinfo.errisinstance(SystemExit):
+ sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
+
+ finally:
+ excinfo = None # Explicitly break reference cycle.
+ session.startdir.chdir()
+ if initstate >= 2:
+ config.hook.pytest_sessionfinish(
+ session=session,
+ exitstatus=session.exitstatus)
+ config._ensure_unconfigure()
+ return session.exitstatus
+
+def pytest_cmdline_main(config):
+ return wrap_session(config, _main)
+
+def _main(config, session):
+ """ default command line protocol for initialization, session,
+ running tests and reporting. """
+ config.hook.pytest_collection(session=session)
+ config.hook.pytest_runtestloop(session=session)
+
+ if session.testsfailed:
+ return EXIT_TESTSFAILED
+ elif session.testscollected == 0:
+ return EXIT_NOTESTSCOLLECTED
+
+def pytest_collection(session):
+ return session.perform_collect()
+
+def pytest_runtestloop(session):
+ if (session.testsfailed and
+ not session.config.option.continue_on_collection_errors):
+ raise session.Interrupted(
+ "%d errors during collection" % session.testsfailed)
+
+ if session.config.option.collectonly:
+ return True
+
+ for i, item in enumerate(session.items):
+ nextitem = session.items[i+1] if i+1 < len(session.items) else None
+ item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
+ if session.shouldstop:
+ raise session.Interrupted(session.shouldstop)
+ return True
+
+def pytest_ignore_collect(path, config):
+ p = path.dirpath()
+ ignore_paths = config._getconftest_pathlist("collect_ignore", path=p)
+ ignore_paths = ignore_paths or []
+ excludeopt = config.getoption("ignore")
+ if excludeopt:
+ ignore_paths.extend([py.path.local(x) for x in excludeopt])
+
+ if path in ignore_paths:
+ return True
+
+ # Skip duplicate paths.
+ keepduplicates = config.getoption("keepduplicates")
+ duplicate_paths = config.pluginmanager._duplicatepaths
+ if not keepduplicates:
+ if path in duplicate_paths:
+ return True
+ else:
+ duplicate_paths.add(path)
+
+ return False
+
+
+class FSHookProxy:
+ def __init__(self, fspath, pm, remove_mods):
+ self.fspath = fspath
+ self.pm = pm
+ self.remove_mods = remove_mods
+
+ def __getattr__(self, name):
+ x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods)
+ self.__dict__[name] = x
+ return x
+
+def compatproperty(name):
+ def fget(self):
+ import warnings
+ warnings.warn("This usage is deprecated, please use pytest.{0} instead".format(name),
+ PendingDeprecationWarning, stacklevel=2)
+ return getattr(pytest, name)
+
+ return property(fget)
+
+class NodeKeywords(MappingMixin):
+ def __init__(self, node):
+ self.node = node
+ self.parent = node.parent
+ self._markers = {node.name: True}
+
+ def __getitem__(self, key):
+ try:
+ return self._markers[key]
+ except KeyError:
+ if self.parent is None:
+ raise
+ return self.parent.keywords[key]
+
+ def __setitem__(self, key, value):
+ self._markers[key] = value
+
+ def __delitem__(self, key):
+ raise ValueError("cannot delete key in keywords dict")
+
+ def __iter__(self):
+ seen = set(self._markers)
+ if self.parent is not None:
+ seen.update(self.parent.keywords)
+ return iter(seen)
+
+ def __len__(self):
+ return len(self.__iter__())
+
+ def keys(self):
+ return list(self)
+
+ def __repr__(self):
+ return "<NodeKeywords for node %s>" % (self.node, )
+
+
+class Node(object):
+ """ base class for Collector and Item the test collection tree.
+ Collector subclasses have children, Items are terminal nodes."""
+
+ def __init__(self, name, parent=None, config=None, session=None):
+ #: a unique name within the scope of the parent node
+ self.name = name
+
+ #: the parent collector node.
+ self.parent = parent
+
+ #: the pytest config object
+ self.config = config or parent.config
+
+ #: the session this node is part of
+ self.session = session or parent.session
+
+ #: filesystem path where this node was collected from (can be None)
+ self.fspath = getattr(parent, 'fspath', None)
+
+ #: keywords/markers collected from all scopes
+ self.keywords = NodeKeywords(self)
+
+ #: allow adding of extra keywords to use for matching
+ self.extra_keyword_matches = set()
+
+ # used for storing artificial fixturedefs for direct parametrization
+ self._name2pseudofixturedef = {}
+
+ @property
+ def ihook(self):
+ """ fspath sensitive hook proxy used to call pytest hooks"""
+ return self.session.gethookproxy(self.fspath)
+
+ Module = compatproperty("Module")
+ Class = compatproperty("Class")
+ Instance = compatproperty("Instance")
+ Function = compatproperty("Function")
+ File = compatproperty("File")
+ Item = compatproperty("Item")
+
+ def _getcustomclass(self, name):
+ cls = getattr(self, name)
+ if cls != getattr(pytest, name):
+ py.log._apiwarn("2.0", "use of node.%s is deprecated, "
+ "use pytest_pycollect_makeitem(...) to create custom "
+ "collection nodes" % name)
+ return cls
+
+ def __repr__(self):
+ return "<%s %r>" %(self.__class__.__name__,
+ getattr(self, 'name', None))
+
+ def warn(self, code, message):
+ """ generate a warning with the given code and message for this
+ item. """
+ assert isinstance(code, str)
+ fslocation = getattr(self, "location", None)
+ if fslocation is None:
+ fslocation = getattr(self, "fspath", None)
+ else:
+ fslocation = "%s:%s" % (fslocation[0], fslocation[1] + 1)
+
+ self.ihook.pytest_logwarning.call_historic(kwargs=dict(
+ code=code, message=message,
+ nodeid=self.nodeid, fslocation=fslocation))
+
+ # methods for ordering nodes
+ @property
+ def nodeid(self):
+ """ a ::-separated string denoting its collection tree address. """
+ try:
+ return self._nodeid
+ except AttributeError:
+ self._nodeid = x = self._makeid()
+ return x
+
+ def _makeid(self):
+ return self.parent.nodeid + "::" + self.name
+
+ def __hash__(self):
+ return hash(self.nodeid)
+
+ def setup(self):
+ pass
+
+ def teardown(self):
+ pass
+
+ def _memoizedcall(self, attrname, function):
+ exattrname = "_ex_" + attrname
+ failure = getattr(self, exattrname, None)
+ if failure is not None:
+ py.builtin._reraise(failure[0], failure[1], failure[2])
+ if hasattr(self, attrname):
+ return getattr(self, attrname)
+ try:
+ res = function()
+ except py.builtin._sysex:
+ raise
+ except:
+ failure = sys.exc_info()
+ setattr(self, exattrname, failure)
+ raise
+ setattr(self, attrname, res)
+ return res
+
+ def listchain(self):
+ """ return list of all parent collectors up to self,
+ starting from root of collection tree. """
+ chain = []
+ item = self
+ while item is not None:
+ chain.append(item)
+ item = item.parent
+ chain.reverse()
+ return chain
+
+ def add_marker(self, marker):
+ """ dynamically add a marker object to the node.
+
+ ``marker`` can be a string or pytest.mark.* instance.
+ """
+ from _pytest.mark import MarkDecorator
+ if isinstance(marker, py.builtin._basestring):
+ marker = MarkDecorator(marker)
+ elif not isinstance(marker, MarkDecorator):
+ raise ValueError("is not a string or pytest.mark.* Marker")
+ self.keywords[marker.name] = marker
+
+ def get_marker(self, name):
+ """ get a marker object from this node or None if
+ the node doesn't have a marker with that name. """
+ val = self.keywords.get(name, None)
+ if val is not None:
+ from _pytest.mark import MarkInfo, MarkDecorator
+ if isinstance(val, (MarkDecorator, MarkInfo)):
+ return val
+
+ def listextrakeywords(self):
+ """ Return a set of all extra keywords in self and any parents."""
+ extra_keywords = set()
+ item = self
+ for item in self.listchain():
+ extra_keywords.update(item.extra_keyword_matches)
+ return extra_keywords
+
+ def listnames(self):
+ return [x.name for x in self.listchain()]
+
+ def addfinalizer(self, fin):
+ """ register a function to be called when this node is finalized.
+
+ This method can only be called when this node is active
+ in a setup chain, for example during self.setup().
+ """
+ self.session._setupstate.addfinalizer(fin, self)
+
+ def getparent(self, cls):
+ """ get the next parent node (including ourself)
+ which is an instance of the given class"""
+ current = self
+ while current and not isinstance(current, cls):
+ current = current.parent
+ return current
+
+ def _prunetraceback(self, excinfo):
+ pass
+
+ def _repr_failure_py(self, excinfo, style=None):
+ fm = self.session._fixturemanager
+ if excinfo.errisinstance(fm.FixtureLookupError):
+ return excinfo.value.formatrepr()
+ tbfilter = True
+ if self.config.option.fulltrace:
+ style="long"
+ else:
+ tb = _pytest._code.Traceback([excinfo.traceback[-1]])
+ self._prunetraceback(excinfo)
+ if len(excinfo.traceback) == 0:
+ excinfo.traceback = tb
+ tbfilter = False # prunetraceback already does it
+ if style == "auto":
+ style = "long"
+ # XXX should excinfo.getrepr record all data and toterminal() process it?
+ if style is None:
+ if self.config.option.tbstyle == "short":
+ style = "short"
+ else:
+ style = "long"
+
+ try:
+ os.getcwd()
+ abspath = False
+ except OSError:
+ abspath = True
+
+ return excinfo.getrepr(funcargs=True, abspath=abspath,
+ showlocals=self.config.option.showlocals,
+ style=style, tbfilter=tbfilter)
+
+ repr_failure = _repr_failure_py
+
+class Collector(Node):
+ """ Collector instances create children through collect()
+ and thus iteratively build a tree.
+ """
+
+ class CollectError(Exception):
+ """ an error during collection, contains a custom message. """
+
+ def collect(self):
+ """ returns a list of children (items and collectors)
+ for this collection node.
+ """
+ raise NotImplementedError("abstract")
+
+ def repr_failure(self, excinfo):
+ """ represent a collection failure. """
+ if excinfo.errisinstance(self.CollectError):
+ exc = excinfo.value
+ return str(exc.args[0])
+ return self._repr_failure_py(excinfo, style="short")
+
+ def _memocollect(self):
+ """ internal helper method to cache results of calling collect(). """
+ return self._memoizedcall('_collected', lambda: list(self.collect()))
+
+ def _prunetraceback(self, excinfo):
+ if hasattr(self, 'fspath'):
+ traceback = excinfo.traceback
+ ntraceback = traceback.cut(path=self.fspath)
+ if ntraceback == traceback:
+ ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
+ excinfo.traceback = ntraceback.filter()
+
+class FSCollector(Collector):
+ def __init__(self, fspath, parent=None, config=None, session=None):
+ fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
+ name = fspath.basename
+ if parent is not None:
+ rel = fspath.relto(parent.fspath)
+ if rel:
+ name = rel
+ name = name.replace(os.sep, "/")
+ super(FSCollector, self).__init__(name, parent, config, session)
+ self.fspath = fspath
+
+ def _makeid(self):
+ relpath = self.fspath.relto(self.config.rootdir)
+ if os.sep != "/":
+ relpath = relpath.replace(os.sep, "/")
+ return relpath
+
+class File(FSCollector):
+ """ base class for collecting tests from a file. """
+
+class Item(Node):
+ """ a basic test invocation item. Note that for a single function
+ there might be multiple test invocation items.
+ """
+ nextitem = None
+
+ def __init__(self, name, parent=None, config=None, session=None):
+ super(Item, self).__init__(name, parent, config, session)
+ self._report_sections = []
+
+ def add_report_section(self, when, key, content):
+ if content:
+ self._report_sections.append((when, key, content))
+
+ def reportinfo(self):
+ return self.fspath, None, ""
+
+ @property
+ def location(self):
+ try:
+ return self._location
+ except AttributeError:
+ location = self.reportinfo()
+ # bestrelpath is a quite slow function
+ cache = self.config.__dict__.setdefault("_bestrelpathcache", {})
+ try:
+ fspath = cache[location[0]]
+ except KeyError:
+ fspath = self.session.fspath.bestrelpath(location[0])
+ cache[location[0]] = fspath
+ location = (fspath, location[1], str(location[2]))
+ self._location = location
+ return location
+
+class NoMatch(Exception):
+ """ raised if matching cannot locate a matching names. """
+
+class Interrupted(KeyboardInterrupt):
+ """ signals an interrupted test run. """
+ __module__ = 'builtins' # for py3
+
+class Session(FSCollector):
+ Interrupted = Interrupted
+
+ def __init__(self, config):
+ FSCollector.__init__(self, config.rootdir, parent=None,
+ config=config, session=self)
+ self.testsfailed = 0
+ self.testscollected = 0
+ self.shouldstop = False
+ self.trace = config.trace.root.get("collection")
+ self._norecursepatterns = config.getini("norecursedirs")
+ self.startdir = py.path.local()
+ self.config.pluginmanager.register(self, name="session")
+
+ def _makeid(self):
+ return ""
+
+ @pytest.hookimpl(tryfirst=True)
+ def pytest_collectstart(self):
+ if self.shouldstop:
+ raise self.Interrupted(self.shouldstop)
+
+ @pytest.hookimpl(tryfirst=True)
+ def pytest_runtest_logreport(self, report):
+ if report.failed and not hasattr(report, 'wasxfail'):
+ self.testsfailed += 1
+ maxfail = self.config.getvalue("maxfail")
+ if maxfail and self.testsfailed >= maxfail:
+ self.shouldstop = "stopping after %d failures" % (
+ self.testsfailed)
+ pytest_collectreport = pytest_runtest_logreport
+
+ def isinitpath(self, path):
+ return path in self._initialpaths
+
+ def gethookproxy(self, fspath):
+ # check if we have the common case of running
+ # hooks with all conftest.py filesall conftest.py
+ pm = self.config.pluginmanager
+ my_conftestmodules = pm._getconftestmodules(fspath)
+ remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
+ if remove_mods:
+ # one or more conftests are not in use at this fspath
+ proxy = FSHookProxy(fspath, pm, remove_mods)
+ else:
+ # all plugis are active for this fspath
+ proxy = self.config.hook
+ return proxy
+
+ def perform_collect(self, args=None, genitems=True):
+ hook = self.config.hook
+ try:
+ items = self._perform_collect(args, genitems)
+ hook.pytest_collection_modifyitems(session=self,
+ config=self.config, items=items)
+ finally:
+ hook.pytest_collection_finish(session=self)
+ self.testscollected = len(items)
+ return items
+
+ def _perform_collect(self, args, genitems):
+ if args is None:
+ args = self.config.args
+ self.trace("perform_collect", self, args)
+ self.trace.root.indent += 1
+ self._notfound = []
+ self._initialpaths = set()
+ self._initialparts = []
+ self.items = items = []
+ for arg in args:
+ parts = self._parsearg(arg)
+ self._initialparts.append(parts)
+ self._initialpaths.add(parts[0])
+ rep = collect_one_node(self)
+ self.ihook.pytest_collectreport(report=rep)
+ self.trace.root.indent -= 1
+ if self._notfound:
+ errors = []
+ for arg, exc in self._notfound:
+ line = "(no name %r in any of %r)" % (arg, exc.args[0])
+ errors.append("not found: %s\n%s" % (arg, line))
+ #XXX: test this
+ raise pytest.UsageError(*errors)
+ if not genitems:
+ return rep.result
+ else:
+ if rep.passed:
+ for node in rep.result:
+ self.items.extend(self.genitems(node))
+ return items
+
+ def collect(self):
+ for parts in self._initialparts:
+ arg = "::".join(map(str, parts))
+ self.trace("processing argument", arg)
+ self.trace.root.indent += 1
+ try:
+ for x in self._collect(arg):
+ yield x
+ except NoMatch:
+ # we are inside a make_report hook so
+ # we cannot directly pass through the exception
+ self._notfound.append((arg, sys.exc_info()[1]))
+
+ self.trace.root.indent -= 1
+
+ def _collect(self, arg):
+ names = self._parsearg(arg)
+ path = names.pop(0)
+ if path.check(dir=1):
+ assert not names, "invalid arg %r" %(arg,)
+ for path in path.visit(fil=lambda x: x.check(file=1),
+ rec=self._recurse, bf=True, sort=True):
+ for x in self._collectfile(path):
+ yield x
+ else:
+ assert path.check(file=1)
+ for x in self.matchnodes(self._collectfile(path), names):
+ yield x
+
+ def _collectfile(self, path):
+ ihook = self.gethookproxy(path)
+ if not self.isinitpath(path):
+ if ihook.pytest_ignore_collect(path=path, config=self.config):
+ return ()
+ return ihook.pytest_collect_file(path=path, parent=self)
+
+ def _recurse(self, path):
+ ihook = self.gethookproxy(path.dirpath())
+ if ihook.pytest_ignore_collect(path=path, config=self.config):
+ return
+ for pat in self._norecursepatterns:
+ if path.check(fnmatch=pat):
+ return False
+ ihook = self.gethookproxy(path)
+ ihook.pytest_collect_directory(path=path, parent=self)
+ return True
+
+ def _tryconvertpyarg(self, x):
+ """Convert a dotted module name to path.
+
+ """
+ import pkgutil
+ try:
+ loader = pkgutil.find_loader(x)
+ except ImportError:
+ return x
+ if loader is None:
+ return x
+ # This method is sometimes invoked when AssertionRewritingHook, which
+ # does not define a get_filename method, is already in place:
+ try:
+ path = loader.get_filename(x)
+ except AttributeError:
+ # Retrieve path from AssertionRewritingHook:
+ path = loader.modules[x][0].co_filename
+ if loader.is_package(x):
+ path = os.path.dirname(path)
+ return path
+
+ def _parsearg(self, arg):
+ """ return (fspath, names) tuple after checking the file exists. """
+ parts = str(arg).split("::")
+ if self.config.option.pyargs:
+ parts[0] = self._tryconvertpyarg(parts[0])
+ relpath = parts[0].replace("/", os.sep)
+ path = self.config.invocation_dir.join(relpath, abs=True)
+ if not path.check():
+ if self.config.option.pyargs:
+ raise pytest.UsageError("file or package not found: " + arg + " (missing __init__.py?)")
+ else:
+ raise pytest.UsageError("file not found: " + arg)
+ parts[0] = path
+ return parts
+
+ def matchnodes(self, matching, names):
+ self.trace("matchnodes", matching, names)
+ self.trace.root.indent += 1
+ nodes = self._matchnodes(matching, names)
+ num = len(nodes)
+ self.trace("matchnodes finished -> ", num, "nodes")
+ self.trace.root.indent -= 1
+ if num == 0:
+ raise NoMatch(matching, names[:1])
+ return nodes
+
+ def _matchnodes(self, matching, names):
+ if not matching or not names:
+ return matching
+ name = names[0]
+ assert name
+ nextnames = names[1:]
+ resultnodes = []
+ for node in matching:
+ if isinstance(node, pytest.Item):
+ if not names:
+ resultnodes.append(node)
+ continue
+ assert isinstance(node, pytest.Collector)
+ rep = collect_one_node(node)
+ if rep.passed:
+ has_matched = False
+ for x in rep.result:
+ # TODO: remove parametrized workaround once collection structure contains parametrization
+ if x.name == name or x.name.split("[")[0] == name:
+ resultnodes.extend(self.matchnodes([x], nextnames))
+ has_matched = True
+ # XXX accept IDs that don't have "()" for class instances
+ if not has_matched and len(rep.result) == 1 and x.name == "()":
+ nextnames.insert(0, name)
+ resultnodes.extend(self.matchnodes([x], nextnames))
+ node.ihook.pytest_collectreport(report=rep)
+ return resultnodes
+
+ def genitems(self, node):
+ self.trace("genitems", node)
+ if isinstance(node, pytest.Item):
+ node.ihook.pytest_itemcollected(item=node)
+ yield node
+ else:
+ assert isinstance(node, pytest.Collector)
+ rep = collect_one_node(node)
+ if rep.passed:
+ for subnode in rep.result:
+ for x in self.genitems(subnode):
+ yield x
+ node.ihook.pytest_collectreport(report=rep)
diff --git a/lib/spack/external/_pytest/mark.py b/lib/spack/external/_pytest/mark.py
new file mode 100644
index 0000000000..357a60492e
--- /dev/null
+++ b/lib/spack/external/_pytest/mark.py
@@ -0,0 +1,328 @@
+""" generic mechanism for marking and selecting python functions. """
+import inspect
+
+
+class MarkerError(Exception):
+
+ """Error in use of a pytest marker/attribute."""
+
+
+def pytest_namespace():
+ return {'mark': MarkGenerator()}
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group._addoption(
+ '-k',
+ action="store", dest="keyword", default='', metavar="EXPRESSION",
+ help="only run tests which match the given substring expression. "
+ "An expression is a python evaluatable expression "
+ "where all names are substring-matched against test names "
+ "and their parent classes. Example: -k 'test_method or test_"
+ "other' matches all test functions and classes whose name "
+ "contains 'test_method' or 'test_other'. "
+ "Additionally keywords are matched to classes and functions "
+ "containing extra names in their 'extra_keyword_matches' set, "
+ "as well as functions which have names assigned directly to them."
+ )
+
+ group._addoption(
+ "-m",
+ action="store", dest="markexpr", default="", metavar="MARKEXPR",
+ help="only run tests matching given mark expression. "
+ "example: -m 'mark1 and not mark2'."
+ )
+
+ group.addoption(
+ "--markers", action="store_true",
+ help="show markers (builtin, plugin and per-project ones)."
+ )
+
+ parser.addini("markers", "markers for test functions", 'linelist')
+
+
+def pytest_cmdline_main(config):
+ import _pytest.config
+ if config.option.markers:
+ config._do_configure()
+ tw = _pytest.config.create_terminal_writer(config)
+ for line in config.getini("markers"):
+ name, rest = line.split(":", 1)
+ tw.write("@pytest.mark.%s:" % name, bold=True)
+ tw.line(rest)
+ tw.line()
+ config._ensure_unconfigure()
+ return 0
+
+
+pytest_cmdline_main.tryfirst = True
+
+
+def pytest_collection_modifyitems(items, config):
+ keywordexpr = config.option.keyword.lstrip()
+ matchexpr = config.option.markexpr
+ if not keywordexpr and not matchexpr:
+ return
+ # pytest used to allow "-" for negating
+ # but today we just allow "-" at the beginning, use "not" instead
+ # we probably remove "-" alltogether soon
+ if keywordexpr.startswith("-"):
+ keywordexpr = "not " + keywordexpr[1:]
+ selectuntil = False
+ if keywordexpr[-1:] == ":":
+ selectuntil = True
+ keywordexpr = keywordexpr[:-1]
+
+ remaining = []
+ deselected = []
+ for colitem in items:
+ if keywordexpr and not matchkeyword(colitem, keywordexpr):
+ deselected.append(colitem)
+ else:
+ if selectuntil:
+ keywordexpr = None
+ if matchexpr:
+ if not matchmark(colitem, matchexpr):
+ deselected.append(colitem)
+ continue
+ remaining.append(colitem)
+
+ if deselected:
+ config.hook.pytest_deselected(items=deselected)
+ items[:] = remaining
+
+
+class MarkMapping:
+ """Provides a local mapping for markers where item access
+ resolves to True if the marker is present. """
+ def __init__(self, keywords):
+ mymarks = set()
+ for key, value in keywords.items():
+ if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator):
+ mymarks.add(key)
+ self._mymarks = mymarks
+
+ def __getitem__(self, name):
+ return name in self._mymarks
+
+
+class KeywordMapping:
+ """Provides a local mapping for keywords.
+ Given a list of names, map any substring of one of these names to True.
+ """
+ def __init__(self, names):
+ self._names = names
+
+ def __getitem__(self, subname):
+ for name in self._names:
+ if subname in name:
+ return True
+ return False
+
+
+def matchmark(colitem, markexpr):
+ """Tries to match on any marker names, attached to the given colitem."""
+ return eval(markexpr, {}, MarkMapping(colitem.keywords))
+
+
+def matchkeyword(colitem, keywordexpr):
+ """Tries to match given keyword expression to given collector item.
+
+ Will match on the name of colitem, including the names of its parents.
+ Only matches names of items which are either a :class:`Class` or a
+ :class:`Function`.
+ Additionally, matches on names in the 'extra_keyword_matches' set of
+ any item, as well as names directly assigned to test functions.
+ """
+ mapped_names = set()
+
+ # Add the names of the current item and any parent items
+ import pytest
+ for item in colitem.listchain():
+ if not isinstance(item, pytest.Instance):
+ mapped_names.add(item.name)
+
+ # Add the names added as extra keywords to current or parent items
+ for name in colitem.listextrakeywords():
+ mapped_names.add(name)
+
+ # Add the names attached to the current function through direct assignment
+ if hasattr(colitem, 'function'):
+ for name in colitem.function.__dict__:
+ mapped_names.add(name)
+
+ mapping = KeywordMapping(mapped_names)
+ if " " not in keywordexpr:
+ # special case to allow for simple "-k pass" and "-k 1.3"
+ return mapping[keywordexpr]
+ elif keywordexpr.startswith("not ") and " " not in keywordexpr[4:]:
+ return not mapping[keywordexpr[4:]]
+ return eval(keywordexpr, {}, mapping)
+
+
+def pytest_configure(config):
+ import pytest
+ if config.option.strict:
+ pytest.mark._config = config
+
+
+class MarkGenerator:
+ """ Factory for :class:`MarkDecorator` objects - exposed as
+ a ``pytest.mark`` singleton instance. Example::
+
+ import pytest
+ @pytest.mark.slowtest
+ def test_function():
+ pass
+
+ will set a 'slowtest' :class:`MarkInfo` object
+ on the ``test_function`` object. """
+
+ def __getattr__(self, name):
+ if name[0] == "_":
+ raise AttributeError("Marker name must NOT start with underscore")
+ if hasattr(self, '_config'):
+ self._check(name)
+ return MarkDecorator(name)
+
+ def _check(self, name):
+ try:
+ if name in self._markers:
+ return
+ except AttributeError:
+ pass
+ self._markers = l = set()
+ for line in self._config.getini("markers"):
+ beginning = line.split(":", 1)
+ x = beginning[0].split("(", 1)[0]
+ l.add(x)
+ if name not in self._markers:
+ raise AttributeError("%r not a registered marker" % (name,))
+
+def istestfunc(func):
+ return hasattr(func, "__call__") and \
+ getattr(func, "__name__", "<lambda>") != "<lambda>"
+
+class MarkDecorator:
+ """ A decorator for test functions and test classes. When applied
+ it will create :class:`MarkInfo` objects which may be
+ :ref:`retrieved by hooks as item keywords <excontrolskip>`.
+ MarkDecorator instances are often created like this::
+
+ mark1 = pytest.mark.NAME # simple MarkDecorator
+ mark2 = pytest.mark.NAME(name1=value) # parametrized MarkDecorator
+
+ and can then be applied as decorators to test functions::
+
+ @mark2
+ def test_function():
+ pass
+
+ When a MarkDecorator instance is called it does the following:
+ 1. If called with a single class as its only positional argument and no
+ additional keyword arguments, it attaches itself to the class so it
+ gets applied automatically to all test cases found in that class.
+ 2. If called with a single function as its only positional argument and
+ no additional keyword arguments, it attaches a MarkInfo object to the
+ function, containing all the arguments already stored internally in
+ the MarkDecorator.
+ 3. When called in any other case, it performs a 'fake construction' call,
+ i.e. it returns a new MarkDecorator instance with the original
+ MarkDecorator's content updated with the arguments passed to this
+ call.
+
+ Note: The rules above prevent MarkDecorator objects from storing only a
+ single function or class reference as their positional argument with no
+ additional keyword or positional arguments.
+
+ """
+ def __init__(self, name, args=None, kwargs=None):
+ self.name = name
+ self.args = args or ()
+ self.kwargs = kwargs or {}
+
+ @property
+ def markname(self):
+ return self.name # for backward-compat (2.4.1 had this attr)
+
+ def __repr__(self):
+ d = self.__dict__.copy()
+ name = d.pop('name')
+ return "<MarkDecorator %r %r>" % (name, d)
+
+ def __call__(self, *args, **kwargs):
+ """ if passed a single callable argument: decorate it with mark info.
+ otherwise add *args/**kwargs in-place to mark information. """
+ if args and not kwargs:
+ func = args[0]
+ is_class = inspect.isclass(func)
+ if len(args) == 1 and (istestfunc(func) or is_class):
+ if is_class:
+ if hasattr(func, 'pytestmark'):
+ mark_list = func.pytestmark
+ if not isinstance(mark_list, list):
+ mark_list = [mark_list]
+ # always work on a copy to avoid updating pytestmark
+ # from a superclass by accident
+ mark_list = mark_list + [self]
+ func.pytestmark = mark_list
+ else:
+ func.pytestmark = [self]
+ else:
+ holder = getattr(func, self.name, None)
+ if holder is None:
+ holder = MarkInfo(
+ self.name, self.args, self.kwargs
+ )
+ setattr(func, self.name, holder)
+ else:
+ holder.add(self.args, self.kwargs)
+ return func
+ kw = self.kwargs.copy()
+ kw.update(kwargs)
+ args = self.args + args
+ return self.__class__(self.name, args=args, kwargs=kw)
+
+
+def extract_argvalue(maybe_marked_args):
+ # TODO: incorrect mark data, the old code wanst able to collect lists
+ # individual parametrized argument sets can be wrapped in a series
+ # of markers in which case we unwrap the values and apply the mark
+ # at Function init
+ newmarks = {}
+ argval = maybe_marked_args
+ while isinstance(argval, MarkDecorator):
+ newmark = MarkDecorator(argval.markname,
+ argval.args[:-1], argval.kwargs)
+ newmarks[newmark.markname] = newmark
+ argval = argval.args[-1]
+ return argval, newmarks
+
+
+class MarkInfo:
+ """ Marking object created by :class:`MarkDecorator` instances. """
+ def __init__(self, name, args, kwargs):
+ #: name of attribute
+ self.name = name
+ #: positional argument list, empty if none specified
+ self.args = args
+ #: keyword argument dictionary, empty if nothing specified
+ self.kwargs = kwargs.copy()
+ self._arglist = [(args, kwargs.copy())]
+
+ def __repr__(self):
+ return "<MarkInfo %r args=%r kwargs=%r>" % (
+ self.name, self.args, self.kwargs
+ )
+
+ def add(self, args, kwargs):
+ """ add a MarkInfo with the given args and kwargs. """
+ self._arglist.append((args, kwargs))
+ self.args += args
+ self.kwargs.update(kwargs)
+
+ def __iter__(self):
+ """ yield MarkInfo objects each relating to a marking-call. """
+ for args, kwargs in self._arglist:
+ yield MarkInfo(self.name, args, kwargs)
diff --git a/lib/spack/external/_pytest/monkeypatch.py b/lib/spack/external/_pytest/monkeypatch.py
new file mode 100644
index 0000000000..852e72beda
--- /dev/null
+++ b/lib/spack/external/_pytest/monkeypatch.py
@@ -0,0 +1,258 @@
+""" monkeypatching and mocking functionality. """
+
+import os, sys
+import re
+
+from py.builtin import _basestring
+
+import pytest
+
+RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
+
+
+@pytest.fixture
+def monkeypatch(request):
+ """The returned ``monkeypatch`` fixture provides these
+ helper methods to modify objects, dictionaries or os.environ::
+
+ monkeypatch.setattr(obj, name, value, raising=True)
+ monkeypatch.delattr(obj, name, raising=True)
+ monkeypatch.setitem(mapping, name, value)
+ monkeypatch.delitem(obj, name, raising=True)
+ monkeypatch.setenv(name, value, prepend=False)
+ monkeypatch.delenv(name, value, raising=True)
+ monkeypatch.syspath_prepend(path)
+ monkeypatch.chdir(path)
+
+ All modifications will be undone after the requesting
+ test function or fixture has finished. The ``raising``
+ parameter determines if a KeyError or AttributeError
+ will be raised if the set/deletion operation has no target.
+ """
+ mpatch = MonkeyPatch()
+ request.addfinalizer(mpatch.undo)
+ return mpatch
+
+
+def resolve(name):
+ # simplified from zope.dottedname
+ parts = name.split('.')
+
+ used = parts.pop(0)
+ found = __import__(used)
+ for part in parts:
+ used += '.' + part
+ try:
+ found = getattr(found, part)
+ except AttributeError:
+ pass
+ else:
+ continue
+ # we use explicit un-nesting of the handling block in order
+ # to avoid nested exceptions on python 3
+ try:
+ __import__(used)
+ except ImportError as ex:
+ # str is used for py2 vs py3
+ expected = str(ex).split()[-1]
+ if expected == used:
+ raise
+ else:
+ raise ImportError(
+ 'import error in %s: %s' % (used, ex)
+ )
+ found = annotated_getattr(found, part, used)
+ return found
+
+
+def annotated_getattr(obj, name, ann):
+ try:
+ obj = getattr(obj, name)
+ except AttributeError:
+ raise AttributeError(
+ '%r object at %s has no attribute %r' % (
+ type(obj).__name__, ann, name
+ )
+ )
+ return obj
+
+
+def derive_importpath(import_path, raising):
+ if not isinstance(import_path, _basestring) or "." not in import_path:
+ raise TypeError("must be absolute import path string, not %r" %
+ (import_path,))
+ module, attr = import_path.rsplit('.', 1)
+ target = resolve(module)
+ if raising:
+ annotated_getattr(target, attr, ann=module)
+ return attr, target
+
+
+class Notset:
+ def __repr__(self):
+ return "<notset>"
+
+
+notset = Notset()
+
+
+class MonkeyPatch:
+ """ Object returned by the ``monkeypatch`` fixture keeping a record of setattr/item/env/syspath changes.
+ """
+
+ def __init__(self):
+ self._setattr = []
+ self._setitem = []
+ self._cwd = None
+ self._savesyspath = None
+
+ def setattr(self, target, name, value=notset, raising=True):
+ """ Set attribute value on target, memorizing the old value.
+ By default raise AttributeError if the attribute did not exist.
+
+ For convenience you can specify a string as ``target`` which
+ will be interpreted as a dotted import path, with the last part
+ being the attribute name. Example:
+ ``monkeypatch.setattr("os.getcwd", lambda x: "/")``
+ would set the ``getcwd`` function of the ``os`` module.
+
+ The ``raising`` value determines if the setattr should fail
+ if the attribute is not already present (defaults to True
+ which means it will raise).
+ """
+ __tracebackhide__ = True
+ import inspect
+
+ if value is notset:
+ if not isinstance(target, _basestring):
+ raise TypeError("use setattr(target, name, value) or "
+ "setattr(target, value) with target being a dotted "
+ "import string")
+ value = name
+ name, target = derive_importpath(target, raising)
+
+ oldval = getattr(target, name, notset)
+ if raising and oldval is notset:
+ raise AttributeError("%r has no attribute %r" % (target, name))
+
+ # avoid class descriptors like staticmethod/classmethod
+ if inspect.isclass(target):
+ oldval = target.__dict__.get(name, notset)
+ self._setattr.append((target, name, oldval))
+ setattr(target, name, value)
+
+ def delattr(self, target, name=notset, raising=True):
+ """ Delete attribute ``name`` from ``target``, by default raise
+ AttributeError it the attribute did not previously exist.
+
+ If no ``name`` is specified and ``target`` is a string
+ it will be interpreted as a dotted import path with the
+ last part being the attribute name.
+
+ If ``raising`` is set to False, no exception will be raised if the
+ attribute is missing.
+ """
+ __tracebackhide__ = True
+ if name is notset:
+ if not isinstance(target, _basestring):
+ raise TypeError("use delattr(target, name) or "
+ "delattr(target) with target being a dotted "
+ "import string")
+ name, target = derive_importpath(target, raising)
+
+ if not hasattr(target, name):
+ if raising:
+ raise AttributeError(name)
+ else:
+ self._setattr.append((target, name, getattr(target, name, notset)))
+ delattr(target, name)
+
+ def setitem(self, dic, name, value):
+ """ Set dictionary entry ``name`` to value. """
+ self._setitem.append((dic, name, dic.get(name, notset)))
+ dic[name] = value
+
+ def delitem(self, dic, name, raising=True):
+ """ Delete ``name`` from dict. Raise KeyError if it doesn't exist.
+
+ If ``raising`` is set to False, no exception will be raised if the
+ key is missing.
+ """
+ if name not in dic:
+ if raising:
+ raise KeyError(name)
+ else:
+ self._setitem.append((dic, name, dic.get(name, notset)))
+ del dic[name]
+
+ def setenv(self, name, value, prepend=None):
+ """ Set environment variable ``name`` to ``value``. If ``prepend``
+ is a character, read the current environment variable value
+ and prepend the ``value`` adjoined with the ``prepend`` character."""
+ value = str(value)
+ if prepend and name in os.environ:
+ value = value + prepend + os.environ[name]
+ self.setitem(os.environ, name, value)
+
+ def delenv(self, name, raising=True):
+ """ Delete ``name`` from the environment. Raise KeyError it does not
+ exist.
+
+ If ``raising`` is set to False, no exception will be raised if the
+ environment variable is missing.
+ """
+ self.delitem(os.environ, name, raising=raising)
+
+ def syspath_prepend(self, path):
+ """ Prepend ``path`` to ``sys.path`` list of import locations. """
+ if self._savesyspath is None:
+ self._savesyspath = sys.path[:]
+ sys.path.insert(0, str(path))
+
+ def chdir(self, path):
+ """ Change the current working directory to the specified path.
+ Path can be a string or a py.path.local object.
+ """
+ if self._cwd is None:
+ self._cwd = os.getcwd()
+ if hasattr(path, "chdir"):
+ path.chdir()
+ else:
+ os.chdir(path)
+
+ def undo(self):
+ """ Undo previous changes. This call consumes the
+ undo stack. Calling it a second time has no effect unless
+ you do more monkeypatching after the undo call.
+
+ There is generally no need to call `undo()`, since it is
+ called automatically during tear-down.
+
+ Note that the same `monkeypatch` fixture is used across a
+ single test function invocation. If `monkeypatch` is used both by
+ the test function itself and one of the test fixtures,
+ calling `undo()` will undo all of the changes made in
+ both functions.
+ """
+ for obj, name, value in reversed(self._setattr):
+ if value is not notset:
+ setattr(obj, name, value)
+ else:
+ delattr(obj, name)
+ self._setattr[:] = []
+ for dictionary, name, value in reversed(self._setitem):
+ if value is notset:
+ try:
+ del dictionary[name]
+ except KeyError:
+ pass # was already deleted, so we have the desired state
+ else:
+ dictionary[name] = value
+ self._setitem[:] = []
+ if self._savesyspath is not None:
+ sys.path[:] = self._savesyspath
+ self._savesyspath = None
+
+ if self._cwd is not None:
+ os.chdir(self._cwd)
+ self._cwd = None
diff --git a/lib/spack/external/_pytest/nose.py b/lib/spack/external/_pytest/nose.py
new file mode 100644
index 0000000000..0387468686
--- /dev/null
+++ b/lib/spack/external/_pytest/nose.py
@@ -0,0 +1,71 @@
+""" run test suites written for nose. """
+
+import sys
+
+import py
+import pytest
+from _pytest import unittest
+
+
+def get_skip_exceptions():
+ skip_classes = set()
+ for module_name in ('unittest', 'unittest2', 'nose'):
+ mod = sys.modules.get(module_name)
+ if hasattr(mod, 'SkipTest'):
+ skip_classes.add(mod.SkipTest)
+ return tuple(skip_classes)
+
+
+def pytest_runtest_makereport(item, call):
+ if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
+ # let's substitute the excinfo with a pytest.skip one
+ call2 = call.__class__(lambda:
+ pytest.skip(str(call.excinfo.value)), call.when)
+ call.excinfo = call2.excinfo
+
+
+@pytest.hookimpl(trylast=True)
+def pytest_runtest_setup(item):
+ if is_potential_nosetest(item):
+ if isinstance(item.parent, pytest.Generator):
+ gen = item.parent
+ if not hasattr(gen, '_nosegensetup'):
+ call_optional(gen.obj, 'setup')
+ if isinstance(gen.parent, pytest.Instance):
+ call_optional(gen.parent.obj, 'setup')
+ gen._nosegensetup = True
+ if not call_optional(item.obj, 'setup'):
+ # call module level setup if there is no object level one
+ call_optional(item.parent.obj, 'setup')
+ #XXX this implies we only call teardown when setup worked
+ item.session._setupstate.addfinalizer((lambda: teardown_nose(item)), item)
+
+def teardown_nose(item):
+ if is_potential_nosetest(item):
+ if not call_optional(item.obj, 'teardown'):
+ call_optional(item.parent.obj, 'teardown')
+ #if hasattr(item.parent, '_nosegensetup'):
+ # #call_optional(item._nosegensetup, 'teardown')
+ # del item.parent._nosegensetup
+
+
+def pytest_make_collect_report(collector):
+ if isinstance(collector, pytest.Generator):
+ call_optional(collector.obj, 'setup')
+
+
+def is_potential_nosetest(item):
+ # extra check needed since we do not do nose style setup/teardown
+ # on direct unittest style classes
+ return isinstance(item, pytest.Function) and \
+ not isinstance(item, unittest.TestCaseFunction)
+
+
+def call_optional(obj, name):
+ method = getattr(obj, name, None)
+ isfixture = hasattr(method, "_pytestfixturefunction")
+ if method is not None and not isfixture and py.builtin.callable(method):
+ # If there's any problems allow the exception to raise rather than
+ # silently ignoring them
+ method()
+ return True
diff --git a/lib/spack/external/_pytest/pastebin.py b/lib/spack/external/_pytest/pastebin.py
new file mode 100644
index 0000000000..9f1cf90637
--- /dev/null
+++ b/lib/spack/external/_pytest/pastebin.py
@@ -0,0 +1,98 @@
+""" submit failure or test session information to a pastebin service. """
+import pytest
+import sys
+import tempfile
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("terminal reporting")
+ group._addoption('--pastebin', metavar="mode",
+ action='store', dest="pastebin", default=None,
+ choices=['failed', 'all'],
+ help="send failed|all info to bpaste.net pastebin service.")
+
+
+@pytest.hookimpl(trylast=True)
+def pytest_configure(config):
+ import py
+ if config.option.pastebin == "all":
+ tr = config.pluginmanager.getplugin('terminalreporter')
+ # if no terminal reporter plugin is present, nothing we can do here;
+ # this can happen when this function executes in a slave node
+ # when using pytest-xdist, for example
+ if tr is not None:
+ # pastebin file will be utf-8 encoded binary file
+ config._pastebinfile = tempfile.TemporaryFile('w+b')
+ oldwrite = tr._tw.write
+
+ def tee_write(s, **kwargs):
+ oldwrite(s, **kwargs)
+ if py.builtin._istext(s):
+ s = s.encode('utf-8')
+ config._pastebinfile.write(s)
+
+ tr._tw.write = tee_write
+
+
+def pytest_unconfigure(config):
+ if hasattr(config, '_pastebinfile'):
+ # get terminal contents and delete file
+ config._pastebinfile.seek(0)
+ sessionlog = config._pastebinfile.read()
+ config._pastebinfile.close()
+ del config._pastebinfile
+ # undo our patching in the terminal reporter
+ tr = config.pluginmanager.getplugin('terminalreporter')
+ del tr._tw.__dict__['write']
+ # write summary
+ tr.write_sep("=", "Sending information to Paste Service")
+ pastebinurl = create_new_paste(sessionlog)
+ tr.write_line("pastebin session-log: %s\n" % pastebinurl)
+
+
+def create_new_paste(contents):
+ """
+ Creates a new paste using bpaste.net service.
+
+ :contents: paste contents as utf-8 encoded bytes
+ :returns: url to the pasted contents
+ """
+ import re
+ if sys.version_info < (3, 0):
+ from urllib import urlopen, urlencode
+ else:
+ from urllib.request import urlopen
+ from urllib.parse import urlencode
+
+ params = {
+ 'code': contents,
+ 'lexer': 'python3' if sys.version_info[0] == 3 else 'python',
+ 'expiry': '1week',
+ }
+ url = 'https://bpaste.net'
+ response = urlopen(url, data=urlencode(params).encode('ascii')).read()
+ m = re.search(r'href="/raw/(\w+)"', response.decode('utf-8'))
+ if m:
+ return '%s/show/%s' % (url, m.group(1))
+ else:
+ return 'bad response: ' + response
+
+
+def pytest_terminal_summary(terminalreporter):
+ import _pytest.config
+ if terminalreporter.config.option.pastebin != "failed":
+ return
+ tr = terminalreporter
+ if 'failed' in tr.stats:
+ terminalreporter.write_sep("=", "Sending information to Paste Service")
+ for rep in terminalreporter.stats.get('failed'):
+ try:
+ msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc
+ except AttributeError:
+ msg = tr._getfailureheadline(rep)
+ tw = _pytest.config.create_terminal_writer(terminalreporter.config, stringio=True)
+ rep.toterminal(tw)
+ s = tw.stringio.getvalue()
+ assert len(s)
+ pastebinurl = create_new_paste(s)
+ tr.write_line("%s --> %s" %(msg, pastebinurl))
diff --git a/lib/spack/external/_pytest/pytester.py b/lib/spack/external/_pytest/pytester.py
new file mode 100644
index 0000000000..17ff529a6c
--- /dev/null
+++ b/lib/spack/external/_pytest/pytester.py
@@ -0,0 +1,1139 @@
+""" (disabled by default) support for testing pytest and pytest plugins. """
+import codecs
+import gc
+import os
+import platform
+import re
+import subprocess
+import sys
+import time
+import traceback
+from fnmatch import fnmatch
+
+from py.builtin import print_
+
+from _pytest._code import Source
+import py
+import pytest
+from _pytest.main import Session, EXIT_OK
+from _pytest.assertion.rewrite import AssertionRewritingHook
+
+
+def pytest_addoption(parser):
+ # group = parser.getgroup("pytester", "pytester (self-tests) options")
+ parser.addoption('--lsof',
+ action="store_true", dest="lsof", default=False,
+ help=("run FD checks if lsof is available"))
+
+ parser.addoption('--runpytest', default="inprocess", dest="runpytest",
+ choices=("inprocess", "subprocess", ),
+ help=("run pytest sub runs in tests using an 'inprocess' "
+ "or 'subprocess' (python -m main) method"))
+
+
+def pytest_configure(config):
+ # This might be called multiple times. Only take the first.
+ global _pytest_fullpath
+ try:
+ _pytest_fullpath
+ except NameError:
+ _pytest_fullpath = os.path.abspath(pytest.__file__.rstrip("oc"))
+ _pytest_fullpath = _pytest_fullpath.replace("$py.class", ".py")
+
+ if config.getvalue("lsof"):
+ checker = LsofFdLeakChecker()
+ if checker.matching_platform():
+ config.pluginmanager.register(checker)
+
+
+class LsofFdLeakChecker(object):
+ def get_open_files(self):
+ out = self._exec_lsof()
+ open_files = self._parse_lsof_output(out)
+ return open_files
+
+ def _exec_lsof(self):
+ pid = os.getpid()
+ return py.process.cmdexec("lsof -Ffn0 -p %d" % pid)
+
+ def _parse_lsof_output(self, out):
+ def isopen(line):
+ return line.startswith('f') and ("deleted" not in line and
+ 'mem' not in line and "txt" not in line and 'cwd' not in line)
+
+ open_files = []
+
+ for line in out.split("\n"):
+ if isopen(line):
+ fields = line.split('\0')
+ fd = fields[0][1:]
+ filename = fields[1][1:]
+ if filename.startswith('/'):
+ open_files.append((fd, filename))
+
+ return open_files
+
+ def matching_platform(self):
+ try:
+ py.process.cmdexec("lsof -v")
+ except (py.process.cmdexec.Error, UnicodeDecodeError):
+ # cmdexec may raise UnicodeDecodeError on Windows systems
+ # with locale other than english:
+ # https://bitbucket.org/pytest-dev/py/issues/66
+ return False
+ else:
+ return True
+
+ @pytest.hookimpl(hookwrapper=True, tryfirst=True)
+ def pytest_runtest_item(self, item):
+ lines1 = self.get_open_files()
+ yield
+ if hasattr(sys, "pypy_version_info"):
+ gc.collect()
+ lines2 = self.get_open_files()
+
+ new_fds = set([t[0] for t in lines2]) - set([t[0] for t in lines1])
+ leaked_files = [t for t in lines2 if t[0] in new_fds]
+ if leaked_files:
+ error = []
+ error.append("***** %s FD leakage detected" % len(leaked_files))
+ error.extend([str(f) for f in leaked_files])
+ error.append("*** Before:")
+ error.extend([str(f) for f in lines1])
+ error.append("*** After:")
+ error.extend([str(f) for f in lines2])
+ error.append(error[0])
+ error.append("*** function %s:%s: %s " % item.location)
+ pytest.fail("\n".join(error), pytrace=False)
+
+
+# XXX copied from execnet's conftest.py - needs to be merged
+winpymap = {
+ 'python2.7': r'C:\Python27\python.exe',
+ 'python2.6': r'C:\Python26\python.exe',
+ 'python3.1': r'C:\Python31\python.exe',
+ 'python3.2': r'C:\Python32\python.exe',
+ 'python3.3': r'C:\Python33\python.exe',
+ 'python3.4': r'C:\Python34\python.exe',
+ 'python3.5': r'C:\Python35\python.exe',
+}
+
+def getexecutable(name, cache={}):
+ try:
+ return cache[name]
+ except KeyError:
+ executable = py.path.local.sysfind(name)
+ if executable:
+ import subprocess
+ popen = subprocess.Popen([str(executable), "--version"],
+ universal_newlines=True, stderr=subprocess.PIPE)
+ out, err = popen.communicate()
+ if name == "jython":
+ if not err or "2.5" not in err:
+ executable = None
+ if "2.5.2" in err:
+ executable = None # http://bugs.jython.org/issue1790
+ elif popen.returncode != 0:
+ # Handle pyenv's 127.
+ executable = None
+ cache[name] = executable
+ return executable
+
+@pytest.fixture(params=['python2.6', 'python2.7', 'python3.3', "python3.4",
+ 'pypy', 'pypy3'])
+def anypython(request):
+ name = request.param
+ executable = getexecutable(name)
+ if executable is None:
+ if sys.platform == "win32":
+ executable = winpymap.get(name, None)
+ if executable:
+ executable = py.path.local(executable)
+ if executable.check():
+ return executable
+ pytest.skip("no suitable %s found" % (name,))
+ return executable
+
+# used at least by pytest-xdist plugin
+@pytest.fixture
+def _pytest(request):
+ """ Return a helper which offers a gethookrecorder(hook)
+ method which returns a HookRecorder instance which helps
+ to make assertions about called hooks.
+ """
+ return PytestArg(request)
+
+class PytestArg:
+ def __init__(self, request):
+ self.request = request
+
+ def gethookrecorder(self, hook):
+ hookrecorder = HookRecorder(hook._pm)
+ self.request.addfinalizer(hookrecorder.finish_recording)
+ return hookrecorder
+
+
+def get_public_names(l):
+ """Only return names from iterator l without a leading underscore."""
+ return [x for x in l if x[0] != "_"]
+
+
+class ParsedCall:
+ def __init__(self, name, kwargs):
+ self.__dict__.update(kwargs)
+ self._name = name
+
+ def __repr__(self):
+ d = self.__dict__.copy()
+ del d['_name']
+ return "<ParsedCall %r(**%r)>" %(self._name, d)
+
+
+class HookRecorder:
+ """Record all hooks called in a plugin manager.
+
+ This wraps all the hook calls in the plugin manager, recording
+ each call before propagating the normal calls.
+
+ """
+
+ def __init__(self, pluginmanager):
+ self._pluginmanager = pluginmanager
+ self.calls = []
+
+ def before(hook_name, hook_impls, kwargs):
+ self.calls.append(ParsedCall(hook_name, kwargs))
+
+ def after(outcome, hook_name, hook_impls, kwargs):
+ pass
+
+ self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after)
+
+ def finish_recording(self):
+ self._undo_wrapping()
+
+ def getcalls(self, names):
+ if isinstance(names, str):
+ names = names.split()
+ return [call for call in self.calls if call._name in names]
+
+ def assert_contains(self, entries):
+ __tracebackhide__ = True
+ i = 0
+ entries = list(entries)
+ backlocals = sys._getframe(1).f_locals
+ while entries:
+ name, check = entries.pop(0)
+ for ind, call in enumerate(self.calls[i:]):
+ if call._name == name:
+ print_("NAMEMATCH", name, call)
+ if eval(check, backlocals, call.__dict__):
+ print_("CHECKERMATCH", repr(check), "->", call)
+ else:
+ print_("NOCHECKERMATCH", repr(check), "-", call)
+ continue
+ i += ind + 1
+ break
+ print_("NONAMEMATCH", name, "with", call)
+ else:
+ pytest.fail("could not find %r check %r" % (name, check))
+
+ def popcall(self, name):
+ __tracebackhide__ = True
+ for i, call in enumerate(self.calls):
+ if call._name == name:
+ del self.calls[i]
+ return call
+ lines = ["could not find call %r, in:" % (name,)]
+ lines.extend([" %s" % str(x) for x in self.calls])
+ pytest.fail("\n".join(lines))
+
+ def getcall(self, name):
+ l = self.getcalls(name)
+ assert len(l) == 1, (name, l)
+ return l[0]
+
+ # functionality for test reports
+
+ def getreports(self,
+ names="pytest_runtest_logreport pytest_collectreport"):
+ return [x.report for x in self.getcalls(names)]
+
+ def matchreport(self, inamepart="",
+ names="pytest_runtest_logreport pytest_collectreport", when=None):
+ """ return a testreport whose dotted import path matches """
+ l = []
+ for rep in self.getreports(names=names):
+ try:
+ if not when and rep.when != "call" and rep.passed:
+ # setup/teardown passing reports - let's ignore those
+ continue
+ except AttributeError:
+ pass
+ if when and getattr(rep, 'when', None) != when:
+ continue
+ if not inamepart or inamepart in rep.nodeid.split("::"):
+ l.append(rep)
+ if not l:
+ raise ValueError("could not find test report matching %r: "
+ "no test reports at all!" % (inamepart,))
+ if len(l) > 1:
+ raise ValueError(
+ "found 2 or more testreports matching %r: %s" %(inamepart, l))
+ return l[0]
+
+ def getfailures(self,
+ names='pytest_runtest_logreport pytest_collectreport'):
+ return [rep for rep in self.getreports(names) if rep.failed]
+
+ def getfailedcollections(self):
+ return self.getfailures('pytest_collectreport')
+
+ def listoutcomes(self):
+ passed = []
+ skipped = []
+ failed = []
+ for rep in self.getreports(
+ "pytest_collectreport pytest_runtest_logreport"):
+ if rep.passed:
+ if getattr(rep, "when", None) == "call":
+ passed.append(rep)
+ elif rep.skipped:
+ skipped.append(rep)
+ elif rep.failed:
+ failed.append(rep)
+ return passed, skipped, failed
+
+ def countoutcomes(self):
+ return [len(x) for x in self.listoutcomes()]
+
+ def assertoutcome(self, passed=0, skipped=0, failed=0):
+ realpassed, realskipped, realfailed = self.listoutcomes()
+ assert passed == len(realpassed)
+ assert skipped == len(realskipped)
+ assert failed == len(realfailed)
+
+ def clear(self):
+ self.calls[:] = []
+
+
+@pytest.fixture
+def linecomp(request):
+ return LineComp()
+
+
+@pytest.fixture(name='LineMatcher')
+def LineMatcher_fixture(request):
+ return LineMatcher
+
+
+@pytest.fixture
+def testdir(request, tmpdir_factory):
+ return Testdir(request, tmpdir_factory)
+
+
+rex_outcome = re.compile("(\d+) ([\w-]+)")
+class RunResult:
+ """The result of running a command.
+
+ Attributes:
+
+ :ret: The return value.
+ :outlines: List of lines captured from stdout.
+ :errlines: List of lines captures from stderr.
+ :stdout: :py:class:`LineMatcher` of stdout, use ``stdout.str()`` to
+ reconstruct stdout or the commonly used
+ ``stdout.fnmatch_lines()`` method.
+ :stderrr: :py:class:`LineMatcher` of stderr.
+ :duration: Duration in seconds.
+
+ """
+ def __init__(self, ret, outlines, errlines, duration):
+ self.ret = ret
+ self.outlines = outlines
+ self.errlines = errlines
+ self.stdout = LineMatcher(outlines)
+ self.stderr = LineMatcher(errlines)
+ self.duration = duration
+
+ def parseoutcomes(self):
+ """ Return a dictionary of outcomestring->num from parsing
+ the terminal output that the test process produced."""
+ for line in reversed(self.outlines):
+ if 'seconds' in line:
+ outcomes = rex_outcome.findall(line)
+ if outcomes:
+ d = {}
+ for num, cat in outcomes:
+ d[cat] = int(num)
+ return d
+
+ def assert_outcomes(self, passed=0, skipped=0, failed=0):
+ """ assert that the specified outcomes appear with the respective
+ numbers (0 means it didn't occur) in the text output from a test run."""
+ d = self.parseoutcomes()
+ assert passed == d.get("passed", 0)
+ assert skipped == d.get("skipped", 0)
+ assert failed == d.get("failed", 0)
+
+
+
+class Testdir:
+ """Temporary test directory with tools to test/run pytest itself.
+
+ This is based on the ``tmpdir`` fixture but provides a number of
+ methods which aid with testing pytest itself. Unless
+ :py:meth:`chdir` is used all methods will use :py:attr:`tmpdir` as
+ current working directory.
+
+ Attributes:
+
+ :tmpdir: The :py:class:`py.path.local` instance of the temporary
+ directory.
+
+ :plugins: A list of plugins to use with :py:meth:`parseconfig` and
+ :py:meth:`runpytest`. Initially this is an empty list but
+ plugins can be added to the list. The type of items to add to
+ the list depend on the method which uses them so refer to them
+ for details.
+
+ """
+
+ def __init__(self, request, tmpdir_factory):
+ self.request = request
+ # XXX remove duplication with tmpdir plugin
+ basetmp = tmpdir_factory.ensuretemp("testdir")
+ name = request.function.__name__
+ for i in range(100):
+ try:
+ tmpdir = basetmp.mkdir(name + str(i))
+ except py.error.EEXIST:
+ continue
+ break
+ self.tmpdir = tmpdir
+ self.plugins = []
+ self._savesyspath = (list(sys.path), list(sys.meta_path))
+ self._savemodulekeys = set(sys.modules)
+ self.chdir() # always chdir
+ self.request.addfinalizer(self.finalize)
+ method = self.request.config.getoption("--runpytest")
+ if method == "inprocess":
+ self._runpytest_method = self.runpytest_inprocess
+ elif method == "subprocess":
+ self._runpytest_method = self.runpytest_subprocess
+
+ def __repr__(self):
+ return "<Testdir %r>" % (self.tmpdir,)
+
+ def finalize(self):
+ """Clean up global state artifacts.
+
+ Some methods modify the global interpreter state and this
+ tries to clean this up. It does not remove the temporary
+ directory however so it can be looked at after the test run
+ has finished.
+
+ """
+ sys.path[:], sys.meta_path[:] = self._savesyspath
+ if hasattr(self, '_olddir'):
+ self._olddir.chdir()
+ self.delete_loaded_modules()
+
+ def delete_loaded_modules(self):
+ """Delete modules that have been loaded during a test.
+
+ This allows the interpreter to catch module changes in case
+ the module is re-imported.
+ """
+ for name in set(sys.modules).difference(self._savemodulekeys):
+ # it seems zope.interfaces is keeping some state
+ # (used by twisted related tests)
+ if name != "zope.interface":
+ del sys.modules[name]
+
+ def make_hook_recorder(self, pluginmanager):
+ """Create a new :py:class:`HookRecorder` for a PluginManager."""
+ assert not hasattr(pluginmanager, "reprec")
+ pluginmanager.reprec = reprec = HookRecorder(pluginmanager)
+ self.request.addfinalizer(reprec.finish_recording)
+ return reprec
+
+ def chdir(self):
+ """Cd into the temporary directory.
+
+ This is done automatically upon instantiation.
+
+ """
+ old = self.tmpdir.chdir()
+ if not hasattr(self, '_olddir'):
+ self._olddir = old
+
+ def _makefile(self, ext, args, kwargs):
+ items = list(kwargs.items())
+ if args:
+ source = py.builtin._totext("\n").join(
+ map(py.builtin._totext, args)) + py.builtin._totext("\n")
+ basename = self.request.function.__name__
+ items.insert(0, (basename, source))
+ ret = None
+ for name, value in items:
+ p = self.tmpdir.join(name).new(ext=ext)
+ p.dirpath().ensure_dir()
+ source = Source(value)
+
+ def my_totext(s, encoding="utf-8"):
+ if py.builtin._isbytes(s):
+ s = py.builtin._totext(s, encoding=encoding)
+ return s
+
+ source_unicode = "\n".join([my_totext(line) for line in source.lines])
+ source = py.builtin._totext(source_unicode)
+ content = source.strip().encode("utf-8") # + "\n"
+ #content = content.rstrip() + "\n"
+ p.write(content, "wb")
+ if ret is None:
+ ret = p
+ return ret
+
+ def makefile(self, ext, *args, **kwargs):
+ """Create a new file in the testdir.
+
+ ext: The extension the file should use, including the dot.
+ E.g. ".py".
+
+ args: All args will be treated as strings and joined using
+ newlines. The result will be written as contents to the
+ file. The name of the file will be based on the test
+ function requesting this fixture.
+ E.g. "testdir.makefile('.txt', 'line1', 'line2')"
+
+ kwargs: Each keyword is the name of a file, while the value of
+ it will be written as contents of the file.
+ E.g. "testdir.makefile('.ini', pytest='[pytest]\naddopts=-rs\n')"
+
+ """
+ return self._makefile(ext, args, kwargs)
+
+ def makeconftest(self, source):
+ """Write a contest.py file with 'source' as contents."""
+ return self.makepyfile(conftest=source)
+
+ def makeini(self, source):
+ """Write a tox.ini file with 'source' as contents."""
+ return self.makefile('.ini', tox=source)
+
+ def getinicfg(self, source):
+ """Return the pytest section from the tox.ini config file."""
+ p = self.makeini(source)
+ return py.iniconfig.IniConfig(p)['pytest']
+
+ def makepyfile(self, *args, **kwargs):
+ """Shortcut for .makefile() with a .py extension."""
+ return self._makefile('.py', args, kwargs)
+
+ def maketxtfile(self, *args, **kwargs):
+ """Shortcut for .makefile() with a .txt extension."""
+ return self._makefile('.txt', args, kwargs)
+
+ def syspathinsert(self, path=None):
+ """Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
+
+ This is undone automatically after the test.
+ """
+ if path is None:
+ path = self.tmpdir
+ sys.path.insert(0, str(path))
+ # a call to syspathinsert() usually means that the caller
+ # wants to import some dynamically created files.
+ # with python3 we thus invalidate import caches.
+ self._possibly_invalidate_import_caches()
+
+ def _possibly_invalidate_import_caches(self):
+ # invalidate caches if we can (py33 and above)
+ try:
+ import importlib
+ except ImportError:
+ pass
+ else:
+ if hasattr(importlib, "invalidate_caches"):
+ importlib.invalidate_caches()
+
+ def mkdir(self, name):
+ """Create a new (sub)directory."""
+ return self.tmpdir.mkdir(name)
+
+ def mkpydir(self, name):
+ """Create a new python package.
+
+ This creates a (sub)direcotry with an empty ``__init__.py``
+ file so that is recognised as a python package.
+
+ """
+ p = self.mkdir(name)
+ p.ensure("__init__.py")
+ return p
+
+ Session = Session
+ def getnode(self, config, arg):
+ """Return the collection node of a file.
+
+ :param config: :py:class:`_pytest.config.Config` instance, see
+ :py:meth:`parseconfig` and :py:meth:`parseconfigure` to
+ create the configuration.
+
+ :param arg: A :py:class:`py.path.local` instance of the file.
+
+ """
+ session = Session(config)
+ assert '::' not in str(arg)
+ p = py.path.local(arg)
+ config.hook.pytest_sessionstart(session=session)
+ res = session.perform_collect([str(p)], genitems=False)[0]
+ config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
+ return res
+
+ def getpathnode(self, path):
+ """Return the collection node of a file.
+
+ This is like :py:meth:`getnode` but uses
+ :py:meth:`parseconfigure` to create the (configured) pytest
+ Config instance.
+
+ :param path: A :py:class:`py.path.local` instance of the file.
+
+ """
+ config = self.parseconfigure(path)
+ session = Session(config)
+ x = session.fspath.bestrelpath(path)
+ config.hook.pytest_sessionstart(session=session)
+ res = session.perform_collect([x], genitems=False)[0]
+ config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
+ return res
+
+ def genitems(self, colitems):
+ """Generate all test items from a collection node.
+
+ This recurses into the collection node and returns a list of
+ all the test items contained within.
+
+ """
+ session = colitems[0].session
+ result = []
+ for colitem in colitems:
+ result.extend(session.genitems(colitem))
+ return result
+
+ def runitem(self, source):
+ """Run the "test_func" Item.
+
+ The calling test instance (the class which contains the test
+ method) must provide a ``.getrunner()`` method which should
+ return a runner which can run the test protocol for a single
+ item, like e.g. :py:func:`_pytest.runner.runtestprotocol`.
+
+ """
+ # used from runner functional tests
+ item = self.getitem(source)
+ # the test class where we are called from wants to provide the runner
+ testclassinstance = self.request.instance
+ runner = testclassinstance.getrunner()
+ return runner(item)
+
+ def inline_runsource(self, source, *cmdlineargs):
+ """Run a test module in process using ``pytest.main()``.
+
+ This run writes "source" into a temporary file and runs
+ ``pytest.main()`` on it, returning a :py:class:`HookRecorder`
+ instance for the result.
+
+ :param source: The source code of the test module.
+
+ :param cmdlineargs: Any extra command line arguments to use.
+
+ :return: :py:class:`HookRecorder` instance of the result.
+
+ """
+ p = self.makepyfile(source)
+ l = list(cmdlineargs) + [p]
+ return self.inline_run(*l)
+
+ def inline_genitems(self, *args):
+ """Run ``pytest.main(['--collectonly'])`` in-process.
+
+ Retuns a tuple of the collected items and a
+ :py:class:`HookRecorder` instance.
+
+ This runs the :py:func:`pytest.main` function to run all of
+ pytest inside the test process itself like
+ :py:meth:`inline_run`. However the return value is a tuple of
+ the collection items and a :py:class:`HookRecorder` instance.
+
+ """
+ rec = self.inline_run("--collect-only", *args)
+ items = [x.item for x in rec.getcalls("pytest_itemcollected")]
+ return items, rec
+
+ def inline_run(self, *args, **kwargs):
+ """Run ``pytest.main()`` in-process, returning a HookRecorder.
+
+ This runs the :py:func:`pytest.main` function to run all of
+ pytest inside the test process itself. This means it can
+ return a :py:class:`HookRecorder` instance which gives more
+ detailed results from then run then can be done by matching
+ stdout/stderr from :py:meth:`runpytest`.
+
+ :param args: Any command line arguments to pass to
+ :py:func:`pytest.main`.
+
+ :param plugin: (keyword-only) Extra plugin instances the
+ ``pytest.main()`` instance should use.
+
+ :return: A :py:class:`HookRecorder` instance.
+ """
+ # When running py.test inline any plugins active in the main
+ # test process are already imported. So this disables the
+ # warning which will trigger to say they can no longer be
+ # re-written, which is fine as they are already re-written.
+ orig_warn = AssertionRewritingHook._warn_already_imported
+
+ def revert():
+ AssertionRewritingHook._warn_already_imported = orig_warn
+
+ self.request.addfinalizer(revert)
+ AssertionRewritingHook._warn_already_imported = lambda *a: None
+
+ rec = []
+
+ class Collect:
+ def pytest_configure(x, config):
+ rec.append(self.make_hook_recorder(config.pluginmanager))
+
+ plugins = kwargs.get("plugins") or []
+ plugins.append(Collect())
+ ret = pytest.main(list(args), plugins=plugins)
+ self.delete_loaded_modules()
+ if len(rec) == 1:
+ reprec = rec.pop()
+ else:
+ class reprec:
+ pass
+ reprec.ret = ret
+
+ # typically we reraise keyboard interrupts from the child run
+ # because it's our user requesting interruption of the testing
+ if ret == 2 and not kwargs.get("no_reraise_ctrlc"):
+ calls = reprec.getcalls("pytest_keyboard_interrupt")
+ if calls and calls[-1].excinfo.type == KeyboardInterrupt:
+ raise KeyboardInterrupt()
+ return reprec
+
+ def runpytest_inprocess(self, *args, **kwargs):
+ """ Return result of running pytest in-process, providing a similar
+ interface to what self.runpytest() provides. """
+ if kwargs.get("syspathinsert"):
+ self.syspathinsert()
+ now = time.time()
+ capture = py.io.StdCapture()
+ try:
+ try:
+ reprec = self.inline_run(*args, **kwargs)
+ except SystemExit as e:
+
+ class reprec:
+ ret = e.args[0]
+
+ except Exception:
+ traceback.print_exc()
+
+ class reprec:
+ ret = 3
+ finally:
+ out, err = capture.reset()
+ sys.stdout.write(out)
+ sys.stderr.write(err)
+
+ res = RunResult(reprec.ret,
+ out.split("\n"), err.split("\n"),
+ time.time()-now)
+ res.reprec = reprec
+ return res
+
+ def runpytest(self, *args, **kwargs):
+ """ Run pytest inline or in a subprocess, depending on the command line
+ option "--runpytest" and return a :py:class:`RunResult`.
+
+ """
+ args = self._ensure_basetemp(args)
+ return self._runpytest_method(*args, **kwargs)
+
+ def _ensure_basetemp(self, args):
+ args = [str(x) for x in args]
+ for x in args:
+ if str(x).startswith('--basetemp'):
+ #print ("basedtemp exists: %s" %(args,))
+ break
+ else:
+ args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp'))
+ #print ("added basetemp: %s" %(args,))
+ return args
+
+ def parseconfig(self, *args):
+ """Return a new pytest Config instance from given commandline args.
+
+ This invokes the pytest bootstrapping code in _pytest.config
+ to create a new :py:class:`_pytest.core.PluginManager` and
+ call the pytest_cmdline_parse hook to create new
+ :py:class:`_pytest.config.Config` instance.
+
+ If :py:attr:`plugins` has been populated they should be plugin
+ modules which will be registered with the PluginManager.
+
+ """
+ args = self._ensure_basetemp(args)
+
+ import _pytest.config
+ config = _pytest.config._prepareconfig(args, self.plugins)
+ # we don't know what the test will do with this half-setup config
+ # object and thus we make sure it gets unconfigured properly in any
+ # case (otherwise capturing could still be active, for example)
+ self.request.addfinalizer(config._ensure_unconfigure)
+ return config
+
+ def parseconfigure(self, *args):
+ """Return a new pytest configured Config instance.
+
+ This returns a new :py:class:`_pytest.config.Config` instance
+ like :py:meth:`parseconfig`, but also calls the
+ pytest_configure hook.
+
+ """
+ config = self.parseconfig(*args)
+ config._do_configure()
+ self.request.addfinalizer(config._ensure_unconfigure)
+ return config
+
+ def getitem(self, source, funcname="test_func"):
+ """Return the test item for a test function.
+
+ This writes the source to a python file and runs pytest's
+ collection on the resulting module, returning the test item
+ for the requested function name.
+
+ :param source: The module source.
+
+ :param funcname: The name of the test function for which the
+ Item must be returned.
+
+ """
+ items = self.getitems(source)
+ for item in items:
+ if item.name == funcname:
+ return item
+ assert 0, "%r item not found in module:\n%s\nitems: %s" %(
+ funcname, source, items)
+
+ def getitems(self, source):
+ """Return all test items collected from the module.
+
+ This writes the source to a python file and runs pytest's
+ collection on the resulting module, returning all test items
+ contained within.
+
+ """
+ modcol = self.getmodulecol(source)
+ return self.genitems([modcol])
+
+ def getmodulecol(self, source, configargs=(), withinit=False):
+ """Return the module collection node for ``source``.
+
+ This writes ``source`` to a file using :py:meth:`makepyfile`
+ and then runs the pytest collection on it, returning the
+ collection node for the test module.
+
+ :param source: The source code of the module to collect.
+
+ :param configargs: Any extra arguments to pass to
+ :py:meth:`parseconfigure`.
+
+ :param withinit: Whether to also write a ``__init__.py`` file
+ to the temporarly directory to ensure it is a package.
+
+ """
+ kw = {self.request.function.__name__: Source(source).strip()}
+ path = self.makepyfile(**kw)
+ if withinit:
+ self.makepyfile(__init__ = "#")
+ self.config = config = self.parseconfigure(path, *configargs)
+ node = self.getnode(config, path)
+ return node
+
+ def collect_by_name(self, modcol, name):
+ """Return the collection node for name from the module collection.
+
+ This will search a module collection node for a collection
+ node matching the given name.
+
+ :param modcol: A module collection node, see
+ :py:meth:`getmodulecol`.
+
+ :param name: The name of the node to return.
+
+ """
+ for colitem in modcol._memocollect():
+ if colitem.name == name:
+ return colitem
+
+ def popen(self, cmdargs, stdout, stderr, **kw):
+ """Invoke subprocess.Popen.
+
+ This calls subprocess.Popen making sure the current working
+ directory is the PYTHONPATH.
+
+ You probably want to use :py:meth:`run` instead.
+
+ """
+ env = os.environ.copy()
+ env['PYTHONPATH'] = os.pathsep.join(filter(None, [
+ str(os.getcwd()), env.get('PYTHONPATH', '')]))
+ kw['env'] = env
+ return subprocess.Popen(cmdargs,
+ stdout=stdout, stderr=stderr, **kw)
+
+ def run(self, *cmdargs):
+ """Run a command with arguments.
+
+ Run a process using subprocess.Popen saving the stdout and
+ stderr.
+
+ Returns a :py:class:`RunResult`.
+
+ """
+ return self._run(*cmdargs)
+
+ def _run(self, *cmdargs):
+ cmdargs = [str(x) for x in cmdargs]
+ p1 = self.tmpdir.join("stdout")
+ p2 = self.tmpdir.join("stderr")
+ print_("running:", ' '.join(cmdargs))
+ print_(" in:", str(py.path.local()))
+ f1 = codecs.open(str(p1), "w", encoding="utf8")
+ f2 = codecs.open(str(p2), "w", encoding="utf8")
+ try:
+ now = time.time()
+ popen = self.popen(cmdargs, stdout=f1, stderr=f2,
+ close_fds=(sys.platform != "win32"))
+ ret = popen.wait()
+ finally:
+ f1.close()
+ f2.close()
+ f1 = codecs.open(str(p1), "r", encoding="utf8")
+ f2 = codecs.open(str(p2), "r", encoding="utf8")
+ try:
+ out = f1.read().splitlines()
+ err = f2.read().splitlines()
+ finally:
+ f1.close()
+ f2.close()
+ self._dump_lines(out, sys.stdout)
+ self._dump_lines(err, sys.stderr)
+ return RunResult(ret, out, err, time.time()-now)
+
+ def _dump_lines(self, lines, fp):
+ try:
+ for line in lines:
+ py.builtin.print_(line, file=fp)
+ except UnicodeEncodeError:
+ print("couldn't print to %s because of encoding" % (fp,))
+
+ def _getpytestargs(self):
+ # we cannot use "(sys.executable,script)"
+ # because on windows the script is e.g. a pytest.exe
+ return (sys.executable, _pytest_fullpath,) # noqa
+
+ def runpython(self, script):
+ """Run a python script using sys.executable as interpreter.
+
+ Returns a :py:class:`RunResult`.
+ """
+ return self.run(sys.executable, script)
+
+ def runpython_c(self, command):
+ """Run python -c "command", return a :py:class:`RunResult`."""
+ return self.run(sys.executable, "-c", command)
+
+ def runpytest_subprocess(self, *args, **kwargs):
+ """Run pytest as a subprocess with given arguments.
+
+ Any plugins added to the :py:attr:`plugins` list will added
+ using the ``-p`` command line option. Addtionally
+ ``--basetemp`` is used put any temporary files and directories
+ in a numbered directory prefixed with "runpytest-" so they do
+ not conflict with the normal numberd pytest location for
+ temporary files and directories.
+
+ Returns a :py:class:`RunResult`.
+
+ """
+ p = py.path.local.make_numbered_dir(prefix="runpytest-",
+ keep=None, rootdir=self.tmpdir)
+ args = ('--basetemp=%s' % p, ) + args
+ #for x in args:
+ # if '--confcutdir' in str(x):
+ # break
+ #else:
+ # pass
+ # args = ('--confcutdir=.',) + args
+ plugins = [x for x in self.plugins if isinstance(x, str)]
+ if plugins:
+ args = ('-p', plugins[0]) + args
+ args = self._getpytestargs() + args
+ return self.run(*args)
+
+ def spawn_pytest(self, string, expect_timeout=10.0):
+ """Run pytest using pexpect.
+
+ This makes sure to use the right pytest and sets up the
+ temporary directory locations.
+
+ The pexpect child is returned.
+
+ """
+ basetemp = self.tmpdir.mkdir("pexpect")
+ invoke = " ".join(map(str, self._getpytestargs()))
+ cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
+ return self.spawn(cmd, expect_timeout=expect_timeout)
+
+ def spawn(self, cmd, expect_timeout=10.0):
+ """Run a command using pexpect.
+
+ The pexpect child is returned.
+ """
+ pexpect = pytest.importorskip("pexpect", "3.0")
+ if hasattr(sys, 'pypy_version_info') and '64' in platform.machine():
+ pytest.skip("pypy-64 bit not supported")
+ if sys.platform.startswith("freebsd"):
+ pytest.xfail("pexpect does not work reliably on freebsd")
+ logfile = self.tmpdir.join("spawn.out").open("wb")
+ child = pexpect.spawn(cmd, logfile=logfile)
+ self.request.addfinalizer(logfile.close)
+ child.timeout = expect_timeout
+ return child
+
+def getdecoded(out):
+ try:
+ return out.decode("utf-8")
+ except UnicodeDecodeError:
+ return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
+ py.io.saferepr(out),)
+
+
+class LineComp:
+ def __init__(self):
+ self.stringio = py.io.TextIO()
+
+ def assert_contains_lines(self, lines2):
+ """ assert that lines2 are contained (linearly) in lines1.
+ return a list of extralines found.
+ """
+ __tracebackhide__ = True
+ val = self.stringio.getvalue()
+ self.stringio.truncate(0)
+ self.stringio.seek(0)
+ lines1 = val.split("\n")
+ return LineMatcher(lines1).fnmatch_lines(lines2)
+
+
+class LineMatcher:
+ """Flexible matching of text.
+
+ This is a convenience class to test large texts like the output of
+ commands.
+
+ The constructor takes a list of lines without their trailing
+ newlines, i.e. ``text.splitlines()``.
+
+ """
+
+ def __init__(self, lines):
+ self.lines = lines
+ self._log_output = []
+
+ def str(self):
+ """Return the entire original text."""
+ return "\n".join(self.lines)
+
+ def _getlines(self, lines2):
+ if isinstance(lines2, str):
+ lines2 = Source(lines2)
+ if isinstance(lines2, Source):
+ lines2 = lines2.strip().lines
+ return lines2
+
+ def fnmatch_lines_random(self, lines2):
+ """Check lines exist in the output.
+
+ The argument is a list of lines which have to occur in the
+ output, in any order. Each line can contain glob whildcards.
+
+ """
+ lines2 = self._getlines(lines2)
+ for line in lines2:
+ for x in self.lines:
+ if line == x or fnmatch(x, line):
+ self._log("matched: ", repr(line))
+ break
+ else:
+ self._log("line %r not found in output" % line)
+ raise ValueError(self._log_text)
+
+ def get_lines_after(self, fnline):
+ """Return all lines following the given line in the text.
+
+ The given line can contain glob wildcards.
+ """
+ for i, line in enumerate(self.lines):
+ if fnline == line or fnmatch(line, fnline):
+ return self.lines[i+1:]
+ raise ValueError("line %r not found in output" % fnline)
+
+ def _log(self, *args):
+ self._log_output.append(' '.join((str(x) for x in args)))
+
+ @property
+ def _log_text(self):
+ return '\n'.join(self._log_output)
+
+ def fnmatch_lines(self, lines2):
+ """Search the text for matching lines.
+
+ The argument is a list of lines which have to match and can
+ use glob wildcards. If they do not match an pytest.fail() is
+ called. The matches and non-matches are also printed on
+ stdout.
+
+ """
+ lines2 = self._getlines(lines2)
+ lines1 = self.lines[:]
+ nextline = None
+ extralines = []
+ __tracebackhide__ = True
+ for line in lines2:
+ nomatchprinted = False
+ while lines1:
+ nextline = lines1.pop(0)
+ if line == nextline:
+ self._log("exact match:", repr(line))
+ break
+ elif fnmatch(nextline, line):
+ self._log("fnmatch:", repr(line))
+ self._log(" with:", repr(nextline))
+ break
+ else:
+ if not nomatchprinted:
+ self._log("nomatch:", repr(line))
+ nomatchprinted = True
+ self._log(" and:", repr(nextline))
+ extralines.append(nextline)
+ else:
+ self._log("remains unmatched: %r" % (line,))
+ pytest.fail(self._log_text)
diff --git a/lib/spack/external/_pytest/python.py b/lib/spack/external/_pytest/python.py
new file mode 100644
index 0000000000..53815da2f0
--- /dev/null
+++ b/lib/spack/external/_pytest/python.py
@@ -0,0 +1,1578 @@
+""" Python test discovery, setup and run of test functions. """
+
+import fnmatch
+import inspect
+import sys
+import collections
+import math
+from itertools import count
+
+import py
+import pytest
+from _pytest.mark import MarkerError
+
+
+import _pytest
+import _pytest._pluggy as pluggy
+from _pytest import fixtures
+from _pytest.compat import (
+ isclass, isfunction, is_generator, _escape_strings,
+ REGEX_TYPE, STRING_TYPES, NoneType, NOTSET,
+ get_real_func, getfslineno, safe_getattr,
+ getlocation, enum,
+)
+
+cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
+cutdir2 = py.path.local(_pytest.__file__).dirpath()
+cutdir3 = py.path.local(py.__file__).dirpath()
+
+
+def filter_traceback(entry):
+ """Return True if a TracebackEntry instance should be removed from tracebacks:
+ * dynamically generated code (no code to show up for it);
+ * internal traceback from pytest or its internal libraries, py and pluggy.
+ """
+ # entry.path might sometimes return a str object when the entry
+ # points to dynamically generated code
+ # see https://bitbucket.org/pytest-dev/py/issues/71
+ raw_filename = entry.frame.code.raw.co_filename
+ is_generated = '<' in raw_filename and '>' in raw_filename
+ if is_generated:
+ return False
+ # entry.path might point to an inexisting file, in which case it will
+ # alsso return a str object. see #1133
+ p = py.path.local(entry.path)
+ return p != cutdir1 and not p.relto(cutdir2) and not p.relto(cutdir3)
+
+
+
+def pyobj_property(name):
+ def get(self):
+ node = self.getparent(getattr(pytest, name))
+ if node is not None:
+ return node.obj
+ doc = "python %s object this node was collected from (can be None)." % (
+ name.lower(),)
+ return property(get, None, None, doc)
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group.addoption('--fixtures', '--funcargs',
+ action="store_true", dest="showfixtures", default=False,
+ help="show available fixtures, sorted by plugin appearance")
+ group.addoption(
+ '--fixtures-per-test',
+ action="store_true",
+ dest="show_fixtures_per_test",
+ default=False,
+ help="show fixtures per test",
+ )
+ parser.addini("usefixtures", type="args", default=[],
+ help="list of default fixtures to be used with this project")
+ parser.addini("python_files", type="args",
+ default=['test_*.py', '*_test.py'],
+ help="glob-style file patterns for Python test module discovery")
+ parser.addini("python_classes", type="args", default=["Test",],
+ help="prefixes or glob names for Python test class discovery")
+ parser.addini("python_functions", type="args", default=["test",],
+ help="prefixes or glob names for Python test function and "
+ "method discovery")
+
+ group.addoption("--import-mode", default="prepend",
+ choices=["prepend", "append"], dest="importmode",
+ help="prepend/append to sys.path when importing test modules, "
+ "default is to prepend.")
+
+
+def pytest_cmdline_main(config):
+ if config.option.showfixtures:
+ showfixtures(config)
+ return 0
+ if config.option.show_fixtures_per_test:
+ show_fixtures_per_test(config)
+ return 0
+
+
+def pytest_generate_tests(metafunc):
+ # those alternative spellings are common - raise a specific error to alert
+ # the user
+ alt_spellings = ['parameterize', 'parametrise', 'parameterise']
+ for attr in alt_spellings:
+ if hasattr(metafunc.function, attr):
+ msg = "{0} has '{1}', spelling should be 'parametrize'"
+ raise MarkerError(msg.format(metafunc.function.__name__, attr))
+ try:
+ markers = metafunc.function.parametrize
+ except AttributeError:
+ return
+ for marker in markers:
+ metafunc.parametrize(*marker.args, **marker.kwargs)
+
+def pytest_configure(config):
+ config.addinivalue_line("markers",
+ "parametrize(argnames, argvalues): call a test function multiple "
+ "times passing in different arguments in turn. argvalues generally "
+ "needs to be a list of values if argnames specifies only one name "
+ "or a list of tuples of values if argnames specifies multiple names. "
+ "Example: @parametrize('arg1', [1,2]) would lead to two calls of the "
+ "decorated test function, one with arg1=1 and another with arg1=2."
+ "see http://pytest.org/latest/parametrize.html for more info and "
+ "examples."
+ )
+ config.addinivalue_line("markers",
+ "usefixtures(fixturename1, fixturename2, ...): mark tests as needing "
+ "all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures "
+ )
+
+@pytest.hookimpl(trylast=True)
+def pytest_namespace():
+ raises.Exception = pytest.fail.Exception
+ return {
+ 'raises': raises,
+ 'approx': approx,
+ 'collect': {
+ 'Module': Module,
+ 'Class': Class,
+ 'Instance': Instance,
+ 'Function': Function,
+ 'Generator': Generator,
+ }
+ }
+
+
+@pytest.hookimpl(trylast=True)
+def pytest_pyfunc_call(pyfuncitem):
+ testfunction = pyfuncitem.obj
+ if pyfuncitem._isyieldedfunction():
+ testfunction(*pyfuncitem._args)
+ else:
+ funcargs = pyfuncitem.funcargs
+ testargs = {}
+ for arg in pyfuncitem._fixtureinfo.argnames:
+ testargs[arg] = funcargs[arg]
+ testfunction(**testargs)
+ return True
+
+def pytest_collect_file(path, parent):
+ ext = path.ext
+ if ext == ".py":
+ if not parent.session.isinitpath(path):
+ for pat in parent.config.getini('python_files'):
+ if path.fnmatch(pat):
+ break
+ else:
+ return
+ ihook = parent.session.gethookproxy(path)
+ return ihook.pytest_pycollect_makemodule(path=path, parent=parent)
+
+def pytest_pycollect_makemodule(path, parent):
+ return Module(path, parent)
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_pycollect_makeitem(collector, name, obj):
+ outcome = yield
+ res = outcome.get_result()
+ if res is not None:
+ raise StopIteration
+ # nothing was collected elsewhere, let's do it here
+ if isclass(obj):
+ if collector.istestclass(obj, name):
+ Class = collector._getcustomclass("Class")
+ outcome.force_result(Class(name, parent=collector))
+ elif collector.istestfunction(obj, name):
+ # mock seems to store unbound methods (issue473), normalize it
+ obj = getattr(obj, "__func__", obj)
+ # We need to try and unwrap the function if it's a functools.partial
+ # or a funtools.wrapped.
+ # We musn't if it's been wrapped with mock.patch (python 2 only)
+ if not (isfunction(obj) or isfunction(get_real_func(obj))):
+ collector.warn(code="C2", message=
+ "cannot collect %r because it is not a function."
+ % name, )
+ elif getattr(obj, "__test__", True):
+ if is_generator(obj):
+ res = Generator(name, parent=collector)
+ else:
+ res = list(collector._genfunctions(name, obj))
+ outcome.force_result(res)
+
+def pytest_make_parametrize_id(config, val):
+ return None
+
+
+
+class PyobjContext(object):
+ module = pyobj_property("Module")
+ cls = pyobj_property("Class")
+ instance = pyobj_property("Instance")
+
+class PyobjMixin(PyobjContext):
+ def obj():
+ def fget(self):
+ obj = getattr(self, '_obj', None)
+ if obj is None:
+ self._obj = obj = self._getobj()
+ return obj
+
+ def fset(self, value):
+ self._obj = value
+
+ return property(fget, fset, None, "underlying python object")
+
+ obj = obj()
+
+ def _getobj(self):
+ return getattr(self.parent.obj, self.name)
+
+ def getmodpath(self, stopatmodule=True, includemodule=False):
+ """ return python path relative to the containing module. """
+ chain = self.listchain()
+ chain.reverse()
+ parts = []
+ for node in chain:
+ if isinstance(node, Instance):
+ continue
+ name = node.name
+ if isinstance(node, Module):
+ assert name.endswith(".py")
+ name = name[:-3]
+ if stopatmodule:
+ if includemodule:
+ parts.append(name)
+ break
+ parts.append(name)
+ parts.reverse()
+ s = ".".join(parts)
+ return s.replace(".[", "[")
+
+ def _getfslineno(self):
+ return getfslineno(self.obj)
+
+ def reportinfo(self):
+ # XXX caching?
+ obj = self.obj
+ compat_co_firstlineno = getattr(obj, 'compat_co_firstlineno', None)
+ if isinstance(compat_co_firstlineno, int):
+ # nose compatibility
+ fspath = sys.modules[obj.__module__].__file__
+ if fspath.endswith(".pyc"):
+ fspath = fspath[:-1]
+ lineno = compat_co_firstlineno
+ else:
+ fspath, lineno = getfslineno(obj)
+ modpath = self.getmodpath()
+ assert isinstance(lineno, int)
+ return fspath, lineno, modpath
+
+class PyCollector(PyobjMixin, pytest.Collector):
+
+ def funcnamefilter(self, name):
+ return self._matches_prefix_or_glob_option('python_functions', name)
+
+ def isnosetest(self, obj):
+ """ Look for the __test__ attribute, which is applied by the
+ @nose.tools.istest decorator
+ """
+ # We explicitly check for "is True" here to not mistakenly treat
+ # classes with a custom __getattr__ returning something truthy (like a
+ # function) as test classes.
+ return safe_getattr(obj, '__test__', False) is True
+
+ def classnamefilter(self, name):
+ return self._matches_prefix_or_glob_option('python_classes', name)
+
+ def istestfunction(self, obj, name):
+ return (
+ (self.funcnamefilter(name) or self.isnosetest(obj)) and
+ safe_getattr(obj, "__call__", False) and fixtures.getfixturemarker(obj) is None
+ )
+
+ def istestclass(self, obj, name):
+ return self.classnamefilter(name) or self.isnosetest(obj)
+
+ def _matches_prefix_or_glob_option(self, option_name, name):
+ """
+ checks if the given name matches the prefix or glob-pattern defined
+ in ini configuration.
+ """
+ for option in self.config.getini(option_name):
+ if name.startswith(option):
+ return True
+ # check that name looks like a glob-string before calling fnmatch
+ # because this is called for every name in each collected module,
+ # and fnmatch is somewhat expensive to call
+ elif ('*' in option or '?' in option or '[' in option) and \
+ fnmatch.fnmatch(name, option):
+ return True
+ return False
+
+ def collect(self):
+ if not getattr(self.obj, "__test__", True):
+ return []
+
+ # NB. we avoid random getattrs and peek in the __dict__ instead
+ # (XXX originally introduced from a PyPy need, still true?)
+ dicts = [getattr(self.obj, '__dict__', {})]
+ for basecls in inspect.getmro(self.obj.__class__):
+ dicts.append(basecls.__dict__)
+ seen = {}
+ l = []
+ for dic in dicts:
+ for name, obj in list(dic.items()):
+ if name in seen:
+ continue
+ seen[name] = True
+ res = self.makeitem(name, obj)
+ if res is None:
+ continue
+ if not isinstance(res, list):
+ res = [res]
+ l.extend(res)
+ l.sort(key=lambda item: item.reportinfo()[:2])
+ return l
+
+ def makeitem(self, name, obj):
+ #assert self.ihook.fspath == self.fspath, self
+ return self.ihook.pytest_pycollect_makeitem(
+ collector=self, name=name, obj=obj)
+
+ def _genfunctions(self, name, funcobj):
+ module = self.getparent(Module).obj
+ clscol = self.getparent(Class)
+ cls = clscol and clscol.obj or None
+ transfer_markers(funcobj, cls, module)
+ fm = self.session._fixturemanager
+ fixtureinfo = fm.getfixtureinfo(self, funcobj, cls)
+ metafunc = Metafunc(funcobj, fixtureinfo, self.config,
+ cls=cls, module=module)
+ methods = []
+ if hasattr(module, "pytest_generate_tests"):
+ methods.append(module.pytest_generate_tests)
+ if hasattr(cls, "pytest_generate_tests"):
+ methods.append(cls().pytest_generate_tests)
+ if methods:
+ self.ihook.pytest_generate_tests.call_extra(methods,
+ dict(metafunc=metafunc))
+ else:
+ self.ihook.pytest_generate_tests(metafunc=metafunc)
+
+ Function = self._getcustomclass("Function")
+ if not metafunc._calls:
+ yield Function(name, parent=self, fixtureinfo=fixtureinfo)
+ else:
+ # add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs
+ fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
+
+ for callspec in metafunc._calls:
+ subname = "%s[%s]" % (name, callspec.id)
+ yield Function(name=subname, parent=self,
+ callspec=callspec, callobj=funcobj,
+ fixtureinfo=fixtureinfo,
+ keywords={callspec.id:True},
+ originalname=name,
+ )
+
+
+def _marked(func, mark):
+ """ Returns True if :func: is already marked with :mark:, False otherwise.
+ This can happen if marker is applied to class and the test file is
+ invoked more than once.
+ """
+ try:
+ func_mark = getattr(func, mark.name)
+ except AttributeError:
+ return False
+ return mark.args == func_mark.args and mark.kwargs == func_mark.kwargs
+
+
+def transfer_markers(funcobj, cls, mod):
+ # XXX this should rather be code in the mark plugin or the mark
+ # plugin should merge with the python plugin.
+ for holder in (cls, mod):
+ try:
+ pytestmark = holder.pytestmark
+ except AttributeError:
+ continue
+ if isinstance(pytestmark, list):
+ for mark in pytestmark:
+ if not _marked(funcobj, mark):
+ mark(funcobj)
+ else:
+ if not _marked(funcobj, pytestmark):
+ pytestmark(funcobj)
+
+class Module(pytest.File, PyCollector):
+ """ Collector for test classes and functions. """
+ def _getobj(self):
+ return self._memoizedcall('_obj', self._importtestmodule)
+
+ def collect(self):
+ self.session._fixturemanager.parsefactories(self)
+ return super(Module, self).collect()
+
+ def _importtestmodule(self):
+ # we assume we are only called once per module
+ importmode = self.config.getoption("--import-mode")
+ try:
+ mod = self.fspath.pyimport(ensuresyspath=importmode)
+ except SyntaxError:
+ raise self.CollectError(
+ _pytest._code.ExceptionInfo().getrepr(style="short"))
+ except self.fspath.ImportMismatchError:
+ e = sys.exc_info()[1]
+ raise self.CollectError(
+ "import file mismatch:\n"
+ "imported module %r has this __file__ attribute:\n"
+ " %s\n"
+ "which is not the same as the test file we want to collect:\n"
+ " %s\n"
+ "HINT: remove __pycache__ / .pyc files and/or use a "
+ "unique basename for your test file modules"
+ % e.args
+ )
+ except ImportError:
+ from _pytest._code.code import ExceptionInfo
+ exc_info = ExceptionInfo()
+ if self.config.getoption('verbose') < 2:
+ exc_info.traceback = exc_info.traceback.filter(filter_traceback)
+ exc_repr = exc_info.getrepr(style='short') if exc_info.traceback else exc_info.exconly()
+ formatted_tb = py._builtin._totext(exc_repr)
+ raise self.CollectError(
+ "ImportError while importing test module '{fspath}'.\n"
+ "Hint: make sure your test modules/packages have valid Python names.\n"
+ "Traceback:\n"
+ "{traceback}".format(fspath=self.fspath, traceback=formatted_tb)
+ )
+ except _pytest.runner.Skipped as e:
+ if e.allow_module_level:
+ raise
+ raise self.CollectError(
+ "Using pytest.skip outside of a test is not allowed. If you are "
+ "trying to decorate a test function, use the @pytest.mark.skip "
+ "or @pytest.mark.skipif decorators instead."
+ )
+ self.config.pluginmanager.consider_module(mod)
+ return mod
+
+ def setup(self):
+ setup_module = _get_xunit_setup_teardown(self.obj, "setUpModule")
+ if setup_module is None:
+ setup_module = _get_xunit_setup_teardown(self.obj, "setup_module")
+ if setup_module is not None:
+ setup_module()
+
+ teardown_module = _get_xunit_setup_teardown(self.obj, 'tearDownModule')
+ if teardown_module is None:
+ teardown_module = _get_xunit_setup_teardown(self.obj, 'teardown_module')
+ if teardown_module is not None:
+ self.addfinalizer(teardown_module)
+
+
+def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
+ """
+ Return a callable to perform xunit-style setup or teardown if
+ the function exists in the ``holder`` object.
+ The ``param_obj`` parameter is the parameter which will be passed to the function
+ when the callable is called without arguments, defaults to the ``holder`` object.
+ Return ``None`` if a suitable callable is not found.
+ """
+ param_obj = param_obj if param_obj is not None else holder
+ result = _get_xunit_func(holder, attr_name)
+ if result is not None:
+ arg_count = result.__code__.co_argcount
+ if inspect.ismethod(result):
+ arg_count -= 1
+ if arg_count:
+ return lambda: result(param_obj)
+ else:
+ return result
+
+
+def _get_xunit_func(obj, name):
+ """Return the attribute from the given object to be used as a setup/teardown
+ xunit-style function, but only if not marked as a fixture to
+ avoid calling it twice.
+ """
+ meth = getattr(obj, name, None)
+ if fixtures.getfixturemarker(meth) is None:
+ return meth
+
+
+class Class(PyCollector):
+ """ Collector for test methods. """
+ def collect(self):
+ if hasinit(self.obj):
+ self.warn("C1", "cannot collect test class %r because it has a "
+ "__init__ constructor" % self.obj.__name__)
+ return []
+ elif hasnew(self.obj):
+ self.warn("C1", "cannot collect test class %r because it has a "
+ "__new__ constructor" % self.obj.__name__)
+ return []
+ return [self._getcustomclass("Instance")(name="()", parent=self)]
+
+ def setup(self):
+ setup_class = _get_xunit_func(self.obj, 'setup_class')
+ if setup_class is not None:
+ setup_class = getattr(setup_class, 'im_func', setup_class)
+ setup_class = getattr(setup_class, '__func__', setup_class)
+ setup_class(self.obj)
+
+ fin_class = getattr(self.obj, 'teardown_class', None)
+ if fin_class is not None:
+ fin_class = getattr(fin_class, 'im_func', fin_class)
+ fin_class = getattr(fin_class, '__func__', fin_class)
+ self.addfinalizer(lambda: fin_class(self.obj))
+
+class Instance(PyCollector):
+ def _getobj(self):
+ return self.parent.obj()
+
+ def collect(self):
+ self.session._fixturemanager.parsefactories(self)
+ return super(Instance, self).collect()
+
+ def newinstance(self):
+ self.obj = self._getobj()
+ return self.obj
+
+class FunctionMixin(PyobjMixin):
+ """ mixin for the code common to Function and Generator.
+ """
+
+ def setup(self):
+ """ perform setup for this test function. """
+ if hasattr(self, '_preservedparent'):
+ obj = self._preservedparent
+ elif isinstance(self.parent, Instance):
+ obj = self.parent.newinstance()
+ self.obj = self._getobj()
+ else:
+ obj = self.parent.obj
+ if inspect.ismethod(self.obj):
+ setup_name = 'setup_method'
+ teardown_name = 'teardown_method'
+ else:
+ setup_name = 'setup_function'
+ teardown_name = 'teardown_function'
+ setup_func_or_method = _get_xunit_setup_teardown(obj, setup_name, param_obj=self.obj)
+ if setup_func_or_method is not None:
+ setup_func_or_method()
+ teardown_func_or_method = _get_xunit_setup_teardown(obj, teardown_name, param_obj=self.obj)
+ if teardown_func_or_method is not None:
+ self.addfinalizer(teardown_func_or_method)
+
+ def _prunetraceback(self, excinfo):
+ if hasattr(self, '_obj') and not self.config.option.fulltrace:
+ code = _pytest._code.Code(get_real_func(self.obj))
+ path, firstlineno = code.path, code.firstlineno
+ traceback = excinfo.traceback
+ ntraceback = traceback.cut(path=path, firstlineno=firstlineno)
+ if ntraceback == traceback:
+ ntraceback = ntraceback.cut(path=path)
+ if ntraceback == traceback:
+ #ntraceback = ntraceback.cut(excludepath=cutdir2)
+ ntraceback = ntraceback.filter(filter_traceback)
+ if not ntraceback:
+ ntraceback = traceback
+
+ excinfo.traceback = ntraceback.filter()
+ # issue364: mark all but first and last frames to
+ # only show a single-line message for each frame
+ if self.config.option.tbstyle == "auto":
+ if len(excinfo.traceback) > 2:
+ for entry in excinfo.traceback[1:-1]:
+ entry.set_repr_style('short')
+
+ def _repr_failure_py(self, excinfo, style="long"):
+ if excinfo.errisinstance(pytest.fail.Exception):
+ if not excinfo.value.pytrace:
+ return py._builtin._totext(excinfo.value)
+ return super(FunctionMixin, self)._repr_failure_py(excinfo,
+ style=style)
+
+ def repr_failure(self, excinfo, outerr=None):
+ assert outerr is None, "XXX outerr usage is deprecated"
+ style = self.config.option.tbstyle
+ if style == "auto":
+ style = "long"
+ return self._repr_failure_py(excinfo, style=style)
+
+
+class Generator(FunctionMixin, PyCollector):
+ def collect(self):
+ # test generators are seen as collectors but they also
+ # invoke setup/teardown on popular request
+ # (induced by the common "test_*" naming shared with normal tests)
+ from _pytest import deprecated
+ self.session._setupstate.prepare(self)
+ # see FunctionMixin.setup and test_setupstate_is_preserved_134
+ self._preservedparent = self.parent.obj
+ l = []
+ seen = {}
+ for i, x in enumerate(self.obj()):
+ name, call, args = self.getcallargs(x)
+ if not callable(call):
+ raise TypeError("%r yielded non callable test %r" %(self.obj, call,))
+ if name is None:
+ name = "[%d]" % i
+ else:
+ name = "['%s']" % name
+ if name in seen:
+ raise ValueError("%r generated tests with non-unique name %r" %(self, name))
+ seen[name] = True
+ l.append(self.Function(name, self, args=args, callobj=call))
+ self.config.warn('C1', deprecated.YIELD_TESTS, fslocation=self.fspath)
+ return l
+
+ def getcallargs(self, obj):
+ if not isinstance(obj, (tuple, list)):
+ obj = (obj,)
+ # explict naming
+ if isinstance(obj[0], py.builtin._basestring):
+ name = obj[0]
+ obj = obj[1:]
+ else:
+ name = None
+ call, args = obj[0], obj[1:]
+ return name, call, args
+
+
+def hasinit(obj):
+ init = getattr(obj, '__init__', None)
+ if init:
+ return init != object.__init__
+
+
+def hasnew(obj):
+ new = getattr(obj, '__new__', None)
+ if new:
+ return new != object.__new__
+
+
+class CallSpec2(object):
+ def __init__(self, metafunc):
+ self.metafunc = metafunc
+ self.funcargs = {}
+ self._idlist = []
+ self.params = {}
+ self._globalid = NOTSET
+ self._globalid_args = set()
+ self._globalparam = NOTSET
+ self._arg2scopenum = {} # used for sorting parametrized resources
+ self.keywords = {}
+ self.indices = {}
+
+ def copy(self, metafunc):
+ cs = CallSpec2(self.metafunc)
+ cs.funcargs.update(self.funcargs)
+ cs.params.update(self.params)
+ cs.keywords.update(self.keywords)
+ cs.indices.update(self.indices)
+ cs._arg2scopenum.update(self._arg2scopenum)
+ cs._idlist = list(self._idlist)
+ cs._globalid = self._globalid
+ cs._globalid_args = self._globalid_args
+ cs._globalparam = self._globalparam
+ return cs
+
+ def _checkargnotcontained(self, arg):
+ if arg in self.params or arg in self.funcargs:
+ raise ValueError("duplicate %r" %(arg,))
+
+ def getparam(self, name):
+ try:
+ return self.params[name]
+ except KeyError:
+ if self._globalparam is NOTSET:
+ raise ValueError(name)
+ return self._globalparam
+
+ @property
+ def id(self):
+ return "-".join(map(str, filter(None, self._idlist)))
+
+ def setmulti(self, valtypes, argnames, valset, id, keywords, scopenum,
+ param_index):
+ for arg,val in zip(argnames, valset):
+ self._checkargnotcontained(arg)
+ valtype_for_arg = valtypes[arg]
+ getattr(self, valtype_for_arg)[arg] = val
+ self.indices[arg] = param_index
+ self._arg2scopenum[arg] = scopenum
+ self._idlist.append(id)
+ self.keywords.update(keywords)
+
+ def setall(self, funcargs, id, param):
+ for x in funcargs:
+ self._checkargnotcontained(x)
+ self.funcargs.update(funcargs)
+ if id is not NOTSET:
+ self._idlist.append(id)
+ if param is not NOTSET:
+ assert self._globalparam is NOTSET
+ self._globalparam = param
+ for arg in funcargs:
+ self._arg2scopenum[arg] = fixtures.scopenum_function
+
+
+class Metafunc(fixtures.FuncargnamesCompatAttr):
+ """
+ Metafunc objects are passed to the ``pytest_generate_tests`` hook.
+ They help to inspect a test function and to generate tests according to
+ test configuration or values specified in the class or module where a
+ test function is defined.
+ """
+ def __init__(self, function, fixtureinfo, config, cls=None, module=None):
+ #: access to the :class:`_pytest.config.Config` object for the test session
+ self.config = config
+
+ #: the module object where the test function is defined in.
+ self.module = module
+
+ #: underlying python test function
+ self.function = function
+
+ #: set of fixture names required by the test function
+ self.fixturenames = fixtureinfo.names_closure
+
+ #: class object where the test function is defined in or ``None``.
+ self.cls = cls
+
+ self._calls = []
+ self._ids = py.builtin.set()
+ self._arg2fixturedefs = fixtureinfo.name2fixturedefs
+
+ def parametrize(self, argnames, argvalues, indirect=False, ids=None,
+ scope=None):
+ """ Add new invocations to the underlying test function using the list
+ of argvalues for the given argnames. Parametrization is performed
+ during the collection phase. If you need to setup expensive resources
+ see about setting indirect to do it rather at test setup time.
+
+ :arg argnames: a comma-separated string denoting one or more argument
+ names, or a list/tuple of argument strings.
+
+ :arg argvalues: The list of argvalues determines how often a
+ test is invoked with different argument values. If only one
+ argname was specified argvalues is a list of values. If N
+ argnames were specified, argvalues must be a list of N-tuples,
+ where each tuple-element specifies a value for its respective
+ argname.
+
+ :arg indirect: The list of argnames or boolean. A list of arguments'
+ names (subset of argnames). If True the list contains all names from
+ the argnames. Each argvalue corresponding to an argname in this list will
+ be passed as request.param to its respective argname fixture
+ function so that it can perform more expensive setups during the
+ setup phase of a test rather than at collection time.
+
+ :arg ids: list of string ids, or a callable.
+ If strings, each is corresponding to the argvalues so that they are
+ part of the test id. If None is given as id of specific test, the
+ automatically generated id for that argument will be used.
+ If callable, it should take one argument (a single argvalue) and return
+ a string or return None. If None, the automatically generated id for that
+ argument will be used.
+ If no ids are provided they will be generated automatically from
+ the argvalues.
+
+ :arg scope: if specified it denotes the scope of the parameters.
+ The scope is used for grouping tests by parameter instances.
+ It will also override any fixture-function defined scope, allowing
+ to set a dynamic scope using test context or configuration.
+ """
+ from _pytest.fixtures import scope2index
+ from _pytest.mark import extract_argvalue
+ from py.io import saferepr
+
+ unwrapped_argvalues = []
+ newkeywords = []
+ for maybe_marked_args in argvalues:
+ argval, newmarks = extract_argvalue(maybe_marked_args)
+ unwrapped_argvalues.append(argval)
+ newkeywords.append(newmarks)
+ argvalues = unwrapped_argvalues
+
+ if not isinstance(argnames, (tuple, list)):
+ argnames = [x.strip() for x in argnames.split(",") if x.strip()]
+ if len(argnames) == 1:
+ argvalues = [(val,) for val in argvalues]
+ if not argvalues:
+ argvalues = [(NOTSET,) * len(argnames)]
+ # we passed a empty list to parameterize, skip that test
+ #
+ fs, lineno = getfslineno(self.function)
+ newmark = pytest.mark.skip(
+ reason="got empty parameter set %r, function %s at %s:%d" % (
+ argnames, self.function.__name__, fs, lineno))
+ newkeywords = [{newmark.markname: newmark}]
+
+ if scope is None:
+ scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
+
+ scopenum = scope2index(
+ scope, descr='call to {0}'.format(self.parametrize))
+ valtypes = {}
+ for arg in argnames:
+ if arg not in self.fixturenames:
+ if isinstance(indirect, (tuple, list)):
+ name = 'fixture' if arg in indirect else 'argument'
+ else:
+ name = 'fixture' if indirect else 'argument'
+ raise ValueError(
+ "%r uses no %s %r" % (
+ self.function, name, arg))
+
+ if indirect is True:
+ valtypes = dict.fromkeys(argnames, "params")
+ elif indirect is False:
+ valtypes = dict.fromkeys(argnames, "funcargs")
+ elif isinstance(indirect, (tuple, list)):
+ valtypes = dict.fromkeys(argnames, "funcargs")
+ for arg in indirect:
+ if arg not in argnames:
+ raise ValueError("indirect given to %r: fixture %r doesn't exist" % (
+ self.function, arg))
+ valtypes[arg] = "params"
+ idfn = None
+ if callable(ids):
+ idfn = ids
+ ids = None
+ if ids:
+ if len(ids) != len(argvalues):
+ raise ValueError('%d tests specified with %d ids' %(
+ len(argvalues), len(ids)))
+ for id_value in ids:
+ if id_value is not None and not isinstance(id_value, py.builtin._basestring):
+ msg = 'ids must be list of strings, found: %s (type: %s)'
+ raise ValueError(msg % (saferepr(id_value), type(id_value).__name__))
+ ids = idmaker(argnames, argvalues, idfn, ids, self.config)
+ newcalls = []
+ for callspec in self._calls or [CallSpec2(self)]:
+ elements = zip(ids, argvalues, newkeywords, count())
+ for a_id, valset, keywords, param_index in elements:
+ assert len(valset) == len(argnames)
+ newcallspec = callspec.copy(self)
+ newcallspec.setmulti(valtypes, argnames, valset, a_id,
+ keywords, scopenum, param_index)
+ newcalls.append(newcallspec)
+ self._calls = newcalls
+
+ def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
+ """ (deprecated, use parametrize) Add a new call to the underlying
+ test function during the collection phase of a test run. Note that
+ request.addcall() is called during the test collection phase prior and
+ independently to actual test execution. You should only use addcall()
+ if you need to specify multiple arguments of a test function.
+
+ :arg funcargs: argument keyword dictionary used when invoking
+ the test function.
+
+ :arg id: used for reporting and identification purposes. If you
+ don't supply an `id` an automatic unique id will be generated.
+
+ :arg param: a parameter which will be exposed to a later fixture function
+ invocation through the ``request.param`` attribute.
+ """
+ assert funcargs is None or isinstance(funcargs, dict)
+ if funcargs is not None:
+ for name in funcargs:
+ if name not in self.fixturenames:
+ pytest.fail("funcarg %r not used in this function." % name)
+ else:
+ funcargs = {}
+ if id is None:
+ raise ValueError("id=None not allowed")
+ if id is NOTSET:
+ id = len(self._calls)
+ id = str(id)
+ if id in self._ids:
+ raise ValueError("duplicate id %r" % id)
+ self._ids.add(id)
+
+ cs = CallSpec2(self)
+ cs.setall(funcargs, id, param)
+ self._calls.append(cs)
+
+
+def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
+ """Find the most appropriate scope for a parametrized call based on its arguments.
+
+ When there's at least one direct argument, always use "function" scope.
+
+ When a test function is parametrized and all its arguments are indirect
+ (e.g. fixtures), return the most narrow scope based on the fixtures used.
+
+ Related to issue #1832, based on code posted by @Kingdread.
+ """
+ from _pytest.fixtures import scopes
+ indirect_as_list = isinstance(indirect, (list, tuple))
+ all_arguments_are_fixtures = indirect is True or \
+ indirect_as_list and len(indirect) == argnames
+ if all_arguments_are_fixtures:
+ fixturedefs = arg2fixturedefs or {}
+ used_scopes = [fixturedef[0].scope for name, fixturedef in fixturedefs.items()]
+ if used_scopes:
+ # Takes the most narrow scope from used fixtures
+ for scope in reversed(scopes):
+ if scope in used_scopes:
+ return scope
+
+ return 'function'
+
+
+def _idval(val, argname, idx, idfn, config=None):
+ if idfn:
+ try:
+ s = idfn(val)
+ if s:
+ return _escape_strings(s)
+ except Exception:
+ pass
+
+ if config:
+ hook_id = config.hook.pytest_make_parametrize_id(config=config, val=val)
+ if hook_id:
+ return hook_id
+
+ if isinstance(val, STRING_TYPES):
+ return _escape_strings(val)
+ elif isinstance(val, (float, int, bool, NoneType)):
+ return str(val)
+ elif isinstance(val, REGEX_TYPE):
+ return _escape_strings(val.pattern)
+ elif enum is not None and isinstance(val, enum.Enum):
+ return str(val)
+ elif isclass(val) and hasattr(val, '__name__'):
+ return val.__name__
+ return str(argname)+str(idx)
+
+def _idvalset(idx, valset, argnames, idfn, ids, config=None):
+ if ids is None or (idx >= len(ids) or ids[idx] is None):
+ this_id = [_idval(val, argname, idx, idfn, config)
+ for val, argname in zip(valset, argnames)]
+ return "-".join(this_id)
+ else:
+ return _escape_strings(ids[idx])
+
+def idmaker(argnames, argvalues, idfn=None, ids=None, config=None):
+ ids = [_idvalset(valindex, valset, argnames, idfn, ids, config)
+ for valindex, valset in enumerate(argvalues)]
+ if len(set(ids)) != len(ids):
+ # The ids are not unique
+ duplicates = [testid for testid in ids if ids.count(testid) > 1]
+ counters = collections.defaultdict(lambda: 0)
+ for index, testid in enumerate(ids):
+ if testid in duplicates:
+ ids[index] = testid + str(counters[testid])
+ counters[testid] += 1
+ return ids
+
+
+def show_fixtures_per_test(config):
+ from _pytest.main import wrap_session
+ return wrap_session(config, _show_fixtures_per_test)
+
+
+def _show_fixtures_per_test(config, session):
+ import _pytest.config
+ session.perform_collect()
+ curdir = py.path.local()
+ tw = _pytest.config.create_terminal_writer(config)
+ verbose = config.getvalue("verbose")
+
+ def get_best_rel(func):
+ loc = getlocation(func, curdir)
+ return curdir.bestrelpath(loc)
+
+ def write_fixture(fixture_def):
+ argname = fixture_def.argname
+
+ if verbose <= 0 and argname.startswith("_"):
+ return
+ if verbose > 0:
+ bestrel = get_best_rel(fixture_def.func)
+ funcargspec = "{0} -- {1}".format(argname, bestrel)
+ else:
+ funcargspec = argname
+ tw.line(funcargspec, green=True)
+
+ INDENT = ' {0}'
+ fixture_doc = fixture_def.func.__doc__
+
+ if fixture_doc:
+ for line in fixture_doc.strip().split('\n'):
+ tw.line(INDENT.format(line.strip()))
+ else:
+ tw.line(INDENT.format('no docstring available'), red=True)
+
+ def write_item(item):
+ name2fixturedefs = item._fixtureinfo.name2fixturedefs
+
+ if not name2fixturedefs:
+ # The given test item does not use any fixtures
+ return
+ bestrel = get_best_rel(item.function)
+
+ tw.line()
+ tw.sep('-', 'fixtures used by {0}'.format(item.name))
+ tw.sep('-', '({0})'.format(bestrel))
+ for argname, fixture_defs in sorted(name2fixturedefs.items()):
+ assert fixture_defs is not None
+ if not fixture_defs:
+ continue
+ # The last fixture def item in the list is expected
+ # to be the one used by the test item
+ write_fixture(fixture_defs[-1])
+
+ for item in session.items:
+ write_item(item)
+
+
+def showfixtures(config):
+ from _pytest.main import wrap_session
+ return wrap_session(config, _showfixtures_main)
+
+def _showfixtures_main(config, session):
+ import _pytest.config
+ session.perform_collect()
+ curdir = py.path.local()
+ tw = _pytest.config.create_terminal_writer(config)
+ verbose = config.getvalue("verbose")
+
+ fm = session._fixturemanager
+
+ available = []
+ seen = set()
+
+ for argname, fixturedefs in fm._arg2fixturedefs.items():
+ assert fixturedefs is not None
+ if not fixturedefs:
+ continue
+ for fixturedef in fixturedefs:
+ loc = getlocation(fixturedef.func, curdir)
+ if (fixturedef.argname, loc) in seen:
+ continue
+ seen.add((fixturedef.argname, loc))
+ available.append((len(fixturedef.baseid),
+ fixturedef.func.__module__,
+ curdir.bestrelpath(loc),
+ fixturedef.argname, fixturedef))
+
+ available.sort()
+ currentmodule = None
+ for baseid, module, bestrel, argname, fixturedef in available:
+ if currentmodule != module:
+ if not module.startswith("_pytest."):
+ tw.line()
+ tw.sep("-", "fixtures defined from %s" %(module,))
+ currentmodule = module
+ if verbose <= 0 and argname[0] == "_":
+ continue
+ if verbose > 0:
+ funcargspec = "%s -- %s" %(argname, bestrel,)
+ else:
+ funcargspec = argname
+ tw.line(funcargspec, green=True)
+ loc = getlocation(fixturedef.func, curdir)
+ doc = fixturedef.func.__doc__ or ""
+ if doc:
+ for line in doc.strip().split("\n"):
+ tw.line(" " + line.strip())
+ else:
+ tw.line(" %s: no docstring available" %(loc,),
+ red=True)
+
+
+# builtin pytest.raises helper
+
+def raises(expected_exception, *args, **kwargs):
+ """
+ Assert that a code block/function call raises ``expected_exception``
+ and raise a failure exception otherwise.
+
+ This helper produces a ``ExceptionInfo()`` object (see below).
+
+ If using Python 2.5 or above, you may use this function as a
+ context manager::
+
+ >>> with raises(ZeroDivisionError):
+ ... 1/0
+
+ .. versionchanged:: 2.10
+
+ In the context manager form you may use the keyword argument
+ ``message`` to specify a custom failure message::
+
+ >>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
+ ... pass
+ Traceback (most recent call last):
+ ...
+ Failed: Expecting ZeroDivisionError
+
+
+ .. note::
+
+ When using ``pytest.raises`` as a context manager, it's worthwhile to
+ note that normal context manager rules apply and that the exception
+ raised *must* be the final line in the scope of the context manager.
+ Lines of code after that, within the scope of the context manager will
+ not be executed. For example::
+
+ >>> value = 15
+ >>> with raises(ValueError) as exc_info:
+ ... if value > 10:
+ ... raise ValueError("value must be <= 10")
+ ... assert str(exc_info.value) == "value must be <= 10" # this will not execute
+
+ Instead, the following approach must be taken (note the difference in
+ scope)::
+
+ >>> with raises(ValueError) as exc_info:
+ ... if value > 10:
+ ... raise ValueError("value must be <= 10")
+ ...
+ >>> assert str(exc_info.value) == "value must be <= 10"
+
+
+ Or you can specify a callable by passing a to-be-called lambda::
+
+ >>> raises(ZeroDivisionError, lambda: 1/0)
+ <ExceptionInfo ...>
+
+ or you can specify an arbitrary callable with arguments::
+
+ >>> def f(x): return 1/x
+ ...
+ >>> raises(ZeroDivisionError, f, 0)
+ <ExceptionInfo ...>
+ >>> raises(ZeroDivisionError, f, x=0)
+ <ExceptionInfo ...>
+
+ A third possibility is to use a string to be executed::
+
+ >>> raises(ZeroDivisionError, "f(0)")
+ <ExceptionInfo ...>
+
+ .. autoclass:: _pytest._code.ExceptionInfo
+ :members:
+
+ .. note::
+ Similar to caught exception objects in Python, explicitly clearing
+ local references to returned ``ExceptionInfo`` objects can
+ help the Python interpreter speed up its garbage collection.
+
+ Clearing those references breaks a reference cycle
+ (``ExceptionInfo`` --> caught exception --> frame stack raising
+ the exception --> current frame stack --> local variables -->
+ ``ExceptionInfo``) which makes Python keep all objects referenced
+ from that cycle (including all local variables in the current
+ frame) alive until the next cyclic garbage collection run. See the
+ official Python ``try`` statement documentation for more detailed
+ information.
+
+ """
+ __tracebackhide__ = True
+ if expected_exception is AssertionError:
+ # we want to catch a AssertionError
+ # replace our subclass with the builtin one
+ # see https://github.com/pytest-dev/pytest/issues/176
+ from _pytest.assertion.util import BuiltinAssertionError \
+ as expected_exception
+ msg = ("exceptions must be old-style classes or"
+ " derived from BaseException, not %s")
+ if isinstance(expected_exception, tuple):
+ for exc in expected_exception:
+ if not isclass(exc):
+ raise TypeError(msg % type(exc))
+ elif not isclass(expected_exception):
+ raise TypeError(msg % type(expected_exception))
+
+ message = "DID NOT RAISE {0}".format(expected_exception)
+
+ if not args:
+ if "message" in kwargs:
+ message = kwargs.pop("message")
+ return RaisesContext(expected_exception, message)
+ elif isinstance(args[0], str):
+ code, = args
+ assert isinstance(code, str)
+ frame = sys._getframe(1)
+ loc = frame.f_locals.copy()
+ loc.update(kwargs)
+ #print "raises frame scope: %r" % frame.f_locals
+ try:
+ code = _pytest._code.Source(code).compile()
+ py.builtin.exec_(code, frame.f_globals, loc)
+ # XXX didn'T mean f_globals == f_locals something special?
+ # this is destroyed here ...
+ except expected_exception:
+ return _pytest._code.ExceptionInfo()
+ else:
+ func = args[0]
+ try:
+ func(*args[1:], **kwargs)
+ except expected_exception:
+ return _pytest._code.ExceptionInfo()
+ pytest.fail(message)
+
+class RaisesContext(object):
+ def __init__(self, expected_exception, message):
+ self.expected_exception = expected_exception
+ self.message = message
+ self.excinfo = None
+
+ def __enter__(self):
+ self.excinfo = object.__new__(_pytest._code.ExceptionInfo)
+ return self.excinfo
+
+ def __exit__(self, *tp):
+ __tracebackhide__ = True
+ if tp[0] is None:
+ pytest.fail(self.message)
+ if sys.version_info < (2, 7):
+ # py26: on __exit__() exc_value often does not contain the
+ # exception value.
+ # http://bugs.python.org/issue7853
+ if not isinstance(tp[1], BaseException):
+ exc_type, value, traceback = tp
+ tp = exc_type, exc_type(value), traceback
+ self.excinfo.__init__(tp)
+ suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
+ if sys.version_info[0] == 2 and suppress_exception:
+ sys.exc_clear()
+ return suppress_exception
+
+
+# builtin pytest.approx helper
+
+class approx(object):
+ """
+ Assert that two numbers (or two sets of numbers) are equal to each other
+ within some tolerance.
+
+ Due to the `intricacies of floating-point arithmetic`__, numbers that we
+ would intuitively expect to be equal are not always so::
+
+ >>> 0.1 + 0.2 == 0.3
+ False
+
+ __ https://docs.python.org/3/tutorial/floatingpoint.html
+
+ This problem is commonly encountered when writing tests, e.g. when making
+ sure that floating-point values are what you expect them to be. One way to
+ deal with this problem is to assert that two floating-point numbers are
+ equal to within some appropriate tolerance::
+
+ >>> abs((0.1 + 0.2) - 0.3) < 1e-6
+ True
+
+ However, comparisons like this are tedious to write and difficult to
+ understand. Furthermore, absolute comparisons like the one above are
+ usually discouraged because there's no tolerance that works well for all
+ situations. ``1e-6`` is good for numbers around ``1``, but too small for
+ very big numbers and too big for very small ones. It's better to express
+ the tolerance as a fraction of the expected value, but relative comparisons
+ like that are even more difficult to write correctly and concisely.
+
+ The ``approx`` class performs floating-point comparisons using a syntax
+ that's as intuitive as possible::
+
+ >>> from pytest import approx
+ >>> 0.1 + 0.2 == approx(0.3)
+ True
+
+ The same syntax also works on sequences of numbers::
+
+ >>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
+ True
+
+ By default, ``approx`` considers numbers within a relative tolerance of
+ ``1e-6`` (i.e. one part in a million) of its expected value to be equal.
+ This treatment would lead to surprising results if the expected value was
+ ``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``.
+ To handle this case less surprisingly, ``approx`` also considers numbers
+ within an absolute tolerance of ``1e-12`` of its expected value to be
+ equal. Infinite numbers are another special case. They are only
+ considered equal to themselves, regardless of the relative tolerance. Both
+ the relative and absolute tolerances can be changed by passing arguments to
+ the ``approx`` constructor::
+
+ >>> 1.0001 == approx(1)
+ False
+ >>> 1.0001 == approx(1, rel=1e-3)
+ True
+ >>> 1.0001 == approx(1, abs=1e-3)
+ True
+
+ If you specify ``abs`` but not ``rel``, the comparison will not consider
+ the relative tolerance at all. In other words, two numbers that are within
+ the default relative tolerance of ``1e-6`` will still be considered unequal
+ if they exceed the specified absolute tolerance. If you specify both
+ ``abs`` and ``rel``, the numbers will be considered equal if either
+ tolerance is met::
+
+ >>> 1 + 1e-8 == approx(1)
+ True
+ >>> 1 + 1e-8 == approx(1, abs=1e-12)
+ False
+ >>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12)
+ True
+
+ If you're thinking about using ``approx``, then you might want to know how
+ it compares to other good ways of comparing floating-point numbers. All of
+ these algorithms are based on relative and absolute tolerances and should
+ agree for the most part, but they do have meaningful differences:
+
+ - ``math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)``: True if the relative
+ tolerance is met w.r.t. either ``a`` or ``b`` or if the absolute
+ tolerance is met. Because the relative tolerance is calculated w.r.t.
+ both ``a`` and ``b``, this test is symmetric (i.e. neither ``a`` nor
+ ``b`` is a "reference value"). You have to specify an absolute tolerance
+ if you want to compare to ``0.0`` because there is no tolerance by
+ default. Only available in python>=3.5. `More information...`__
+
+ __ https://docs.python.org/3/library/math.html#math.isclose
+
+ - ``numpy.isclose(a, b, rtol=1e-5, atol=1e-8)``: True if the difference
+ between ``a`` and ``b`` is less that the sum of the relative tolerance
+ w.r.t. ``b`` and the absolute tolerance. Because the relative tolerance
+ is only calculated w.r.t. ``b``, this test is asymmetric and you can
+ think of ``b`` as the reference value. Support for comparing sequences
+ is provided by ``numpy.allclose``. `More information...`__
+
+ __ http://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.isclose.html
+
+ - ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b``
+ are within an absolute tolerance of ``1e-7``. No relative tolerance is
+ considered and the absolute tolerance cannot be changed, so this function
+ is not appropriate for very large or very small numbers. Also, it's only
+ available in subclasses of ``unittest.TestCase`` and it's ugly because it
+ doesn't follow PEP8. `More information...`__
+
+ __ https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertAlmostEqual
+
+ - ``a == pytest.approx(b, rel=1e-6, abs=1e-12)``: True if the relative
+ tolerance is met w.r.t. ``b`` or if the absolute tolerance is met.
+ Because the relative tolerance is only calculated w.r.t. ``b``, this test
+ is asymmetric and you can think of ``b`` as the reference value. In the
+ special case that you explicitly specify an absolute tolerance but not a
+ relative tolerance, only the absolute tolerance is considered.
+ """
+
+ def __init__(self, expected, rel=None, abs=None):
+ self.expected = expected
+ self.abs = abs
+ self.rel = rel
+
+ def __repr__(self):
+ return ', '.join(repr(x) for x in self.expected)
+
+ def __eq__(self, actual):
+ from collections import Iterable
+ if not isinstance(actual, Iterable):
+ actual = [actual]
+ if len(actual) != len(self.expected):
+ return False
+ return all(a == x for a, x in zip(actual, self.expected))
+
+ __hash__ = None
+
+ def __ne__(self, actual):
+ return not (actual == self)
+
+ @property
+ def expected(self):
+ # Regardless of whether the user-specified expected value is a number
+ # or a sequence of numbers, return a list of ApproxNotIterable objects
+ # that can be compared against.
+ from collections import Iterable
+ approx_non_iter = lambda x: ApproxNonIterable(x, self.rel, self.abs)
+ if isinstance(self._expected, Iterable):
+ return [approx_non_iter(x) for x in self._expected]
+ else:
+ return [approx_non_iter(self._expected)]
+
+ @expected.setter
+ def expected(self, expected):
+ self._expected = expected
+
+
+class ApproxNonIterable(object):
+ """
+ Perform approximate comparisons for single numbers only.
+
+ In other words, the ``expected`` attribute for objects of this class must
+ be some sort of number. This is in contrast to the ``approx`` class, where
+ the ``expected`` attribute can either be a number of a sequence of numbers.
+ This class is responsible for making comparisons, while ``approx`` is
+ responsible for abstracting the difference between numbers and sequences of
+ numbers. Although this class can stand on its own, it's only meant to be
+ used within ``approx``.
+ """
+
+ def __init__(self, expected, rel=None, abs=None):
+ self.expected = expected
+ self.abs = abs
+ self.rel = rel
+
+ def __repr__(self):
+ if isinstance(self.expected, complex):
+ return str(self.expected)
+
+ # Infinities aren't compared using tolerances, so don't show a
+ # tolerance.
+ if math.isinf(self.expected):
+ return str(self.expected)
+
+ # If a sensible tolerance can't be calculated, self.tolerance will
+ # raise a ValueError. In this case, display '???'.
+ try:
+ vetted_tolerance = '{:.1e}'.format(self.tolerance)
+ except ValueError:
+ vetted_tolerance = '???'
+
+ if sys.version_info[0] == 2:
+ return '{0} +- {1}'.format(self.expected, vetted_tolerance)
+ else:
+ return u'{0} \u00b1 {1}'.format(self.expected, vetted_tolerance)
+
+ def __eq__(self, actual):
+ # Short-circuit exact equality.
+ if actual == self.expected:
+ return True
+
+ # Infinity shouldn't be approximately equal to anything but itself, but
+ # if there's a relative tolerance, it will be infinite and infinity
+ # will seem approximately equal to everything. The equal-to-itself
+ # case would have been short circuited above, so here we can just
+ # return false if the expected value is infinite. The abs() call is
+ # for compatibility with complex numbers.
+ if math.isinf(abs(self.expected)):
+ return False
+
+ # Return true if the two numbers are within the tolerance.
+ return abs(self.expected - actual) <= self.tolerance
+
+ __hash__ = None
+
+ def __ne__(self, actual):
+ return not (actual == self)
+
+ @property
+ def tolerance(self):
+ set_default = lambda x, default: x if x is not None else default
+
+ # Figure out what the absolute tolerance should be. ``self.abs`` is
+ # either None or a value specified by the user.
+ absolute_tolerance = set_default(self.abs, 1e-12)
+
+ if absolute_tolerance < 0:
+ raise ValueError("absolute tolerance can't be negative: {0}".format(absolute_tolerance))
+ if math.isnan(absolute_tolerance):
+ raise ValueError("absolute tolerance can't be NaN.")
+
+ # If the user specified an absolute tolerance but not a relative one,
+ # just return the absolute tolerance.
+ if self.rel is None:
+ if self.abs is not None:
+ return absolute_tolerance
+
+ # Figure out what the relative tolerance should be. ``self.rel`` is
+ # either None or a value specified by the user. This is done after
+ # we've made sure the user didn't ask for an absolute tolerance only,
+ # because we don't want to raise errors about the relative tolerance if
+ # we aren't even going to use it.
+ relative_tolerance = set_default(self.rel, 1e-6) * abs(self.expected)
+
+ if relative_tolerance < 0:
+ raise ValueError("relative tolerance can't be negative: {0}".format(absolute_tolerance))
+ if math.isnan(relative_tolerance):
+ raise ValueError("relative tolerance can't be NaN.")
+
+ # Return the larger of the relative and absolute tolerances.
+ return max(relative_tolerance, absolute_tolerance)
+
+
+#
+# the basic pytest Function item
+#
+
+class Function(FunctionMixin, pytest.Item, fixtures.FuncargnamesCompatAttr):
+ """ a Function Item is responsible for setting up and executing a
+ Python test function.
+ """
+ _genid = None
+ def __init__(self, name, parent, args=None, config=None,
+ callspec=None, callobj=NOTSET, keywords=None, session=None,
+ fixtureinfo=None, originalname=None):
+ super(Function, self).__init__(name, parent, config=config,
+ session=session)
+ self._args = args
+ if callobj is not NOTSET:
+ self.obj = callobj
+
+ self.keywords.update(self.obj.__dict__)
+ if callspec:
+ self.callspec = callspec
+ self.keywords.update(callspec.keywords)
+ if keywords:
+ self.keywords.update(keywords)
+
+ if fixtureinfo is None:
+ fixtureinfo = self.session._fixturemanager.getfixtureinfo(
+ self.parent, self.obj, self.cls,
+ funcargs=not self._isyieldedfunction())
+ self._fixtureinfo = fixtureinfo
+ self.fixturenames = fixtureinfo.names_closure
+ self._initrequest()
+
+ #: original function name, without any decorations (for example
+ #: parametrization adds a ``"[...]"`` suffix to function names).
+ #:
+ #: .. versionadded:: 3.0
+ self.originalname = originalname
+
+ def _initrequest(self):
+ self.funcargs = {}
+ if self._isyieldedfunction():
+ assert not hasattr(self, "callspec"), (
+ "yielded functions (deprecated) cannot have funcargs")
+ else:
+ if hasattr(self, "callspec"):
+ callspec = self.callspec
+ assert not callspec.funcargs
+ self._genid = callspec.id
+ if hasattr(callspec, "param"):
+ self.param = callspec.param
+ self._request = fixtures.FixtureRequest(self)
+
+ @property
+ def function(self):
+ "underlying python 'function' object"
+ return getattr(self.obj, 'im_func', self.obj)
+
+ def _getobj(self):
+ name = self.name
+ i = name.find("[") # parametrization
+ if i != -1:
+ name = name[:i]
+ return getattr(self.parent.obj, name)
+
+ @property
+ def _pyfuncitem(self):
+ "(compatonly) for code expecting pytest-2.2 style request objects"
+ return self
+
+ def _isyieldedfunction(self):
+ return getattr(self, "_args", None) is not None
+
+ def runtest(self):
+ """ execute the underlying test function. """
+ self.ihook.pytest_pyfunc_call(pyfuncitem=self)
+
+ def setup(self):
+ super(Function, self).setup()
+ fixtures.fillfixtures(self)
diff --git a/lib/spack/external/_pytest/recwarn.py b/lib/spack/external/_pytest/recwarn.py
new file mode 100644
index 0000000000..87823bfbc6
--- /dev/null
+++ b/lib/spack/external/_pytest/recwarn.py
@@ -0,0 +1,226 @@
+""" recording warnings during test function execution. """
+
+import inspect
+
+import _pytest._code
+import py
+import sys
+import warnings
+import pytest
+
+
+@pytest.yield_fixture
+def recwarn(request):
+ """Return a WarningsRecorder instance that provides these methods:
+
+ * ``pop(category=None)``: return last warning matching the category.
+ * ``clear()``: clear list of warnings
+
+ See http://docs.python.org/library/warnings.html for information
+ on warning categories.
+ """
+ wrec = WarningsRecorder()
+ with wrec:
+ warnings.simplefilter('default')
+ yield wrec
+
+
+def pytest_namespace():
+ return {'deprecated_call': deprecated_call,
+ 'warns': warns}
+
+
+def deprecated_call(func=None, *args, **kwargs):
+ """ assert that calling ``func(*args, **kwargs)`` triggers a
+ ``DeprecationWarning`` or ``PendingDeprecationWarning``.
+
+ This function can be used as a context manager::
+
+ >>> import warnings
+ >>> def api_call_v2():
+ ... warnings.warn('use v3 of this api', DeprecationWarning)
+ ... return 200
+
+ >>> with deprecated_call():
+ ... assert api_call_v2() == 200
+
+ Note: we cannot use WarningsRecorder here because it is still subject
+ to the mechanism that prevents warnings of the same type from being
+ triggered twice for the same module. See #1190.
+ """
+ if not func:
+ return WarningsChecker(expected_warning=DeprecationWarning)
+
+ categories = []
+
+ def warn_explicit(message, category, *args, **kwargs):
+ categories.append(category)
+ old_warn_explicit(message, category, *args, **kwargs)
+
+ def warn(message, category=None, *args, **kwargs):
+ if isinstance(message, Warning):
+ categories.append(message.__class__)
+ else:
+ categories.append(category)
+ old_warn(message, category, *args, **kwargs)
+
+ old_warn = warnings.warn
+ old_warn_explicit = warnings.warn_explicit
+ warnings.warn_explicit = warn_explicit
+ warnings.warn = warn
+ try:
+ ret = func(*args, **kwargs)
+ finally:
+ warnings.warn_explicit = old_warn_explicit
+ warnings.warn = old_warn
+ deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
+ if not any(issubclass(c, deprecation_categories) for c in categories):
+ __tracebackhide__ = True
+ raise AssertionError("%r did not produce DeprecationWarning" % (func,))
+ return ret
+
+
+def warns(expected_warning, *args, **kwargs):
+ """Assert that code raises a particular class of warning.
+
+ Specifically, the input @expected_warning can be a warning class or
+ tuple of warning classes, and the code must return that warning
+ (if a single class) or one of those warnings (if a tuple).
+
+ This helper produces a list of ``warnings.WarningMessage`` objects,
+ one for each warning raised.
+
+ This function can be used as a context manager, or any of the other ways
+ ``pytest.raises`` can be used::
+
+ >>> with warns(RuntimeWarning):
+ ... warnings.warn("my warning", RuntimeWarning)
+ """
+ wcheck = WarningsChecker(expected_warning)
+ if not args:
+ return wcheck
+ elif isinstance(args[0], str):
+ code, = args
+ assert isinstance(code, str)
+ frame = sys._getframe(1)
+ loc = frame.f_locals.copy()
+ loc.update(kwargs)
+
+ with wcheck:
+ code = _pytest._code.Source(code).compile()
+ py.builtin.exec_(code, frame.f_globals, loc)
+ else:
+ func = args[0]
+ with wcheck:
+ return func(*args[1:], **kwargs)
+
+
+class RecordedWarning(object):
+ def __init__(self, message, category, filename, lineno, file, line):
+ self.message = message
+ self.category = category
+ self.filename = filename
+ self.lineno = lineno
+ self.file = file
+ self.line = line
+
+
+class WarningsRecorder(object):
+ """A context manager to record raised warnings.
+
+ Adapted from `warnings.catch_warnings`.
+ """
+
+ def __init__(self, module=None):
+ self._module = sys.modules['warnings'] if module is None else module
+ self._entered = False
+ self._list = []
+
+ @property
+ def list(self):
+ """The list of recorded warnings."""
+ return self._list
+
+ def __getitem__(self, i):
+ """Get a recorded warning by index."""
+ return self._list[i]
+
+ def __iter__(self):
+ """Iterate through the recorded warnings."""
+ return iter(self._list)
+
+ def __len__(self):
+ """The number of recorded warnings."""
+ return len(self._list)
+
+ def pop(self, cls=Warning):
+ """Pop the first recorded warning, raise exception if not exists."""
+ for i, w in enumerate(self._list):
+ if issubclass(w.category, cls):
+ return self._list.pop(i)
+ __tracebackhide__ = True
+ raise AssertionError("%r not found in warning list" % cls)
+
+ def clear(self):
+ """Clear the list of recorded warnings."""
+ self._list[:] = []
+
+ def __enter__(self):
+ if self._entered:
+ __tracebackhide__ = True
+ raise RuntimeError("Cannot enter %r twice" % self)
+ self._entered = True
+ self._filters = self._module.filters
+ self._module.filters = self._filters[:]
+ self._showwarning = self._module.showwarning
+
+ def showwarning(message, category, filename, lineno,
+ file=None, line=None):
+ self._list.append(RecordedWarning(
+ message, category, filename, lineno, file, line))
+
+ # still perform old showwarning functionality
+ self._showwarning(
+ message, category, filename, lineno, file=file, line=line)
+
+ self._module.showwarning = showwarning
+
+ # allow the same warning to be raised more than once
+
+ self._module.simplefilter('always')
+ return self
+
+ def __exit__(self, *exc_info):
+ if not self._entered:
+ __tracebackhide__ = True
+ raise RuntimeError("Cannot exit %r without entering first" % self)
+ self._module.filters = self._filters
+ self._module.showwarning = self._showwarning
+
+
+class WarningsChecker(WarningsRecorder):
+ def __init__(self, expected_warning=None, module=None):
+ super(WarningsChecker, self).__init__(module=module)
+
+ msg = ("exceptions must be old-style classes or "
+ "derived from Warning, not %s")
+ if isinstance(expected_warning, tuple):
+ for exc in expected_warning:
+ if not inspect.isclass(exc):
+ raise TypeError(msg % type(exc))
+ elif inspect.isclass(expected_warning):
+ expected_warning = (expected_warning,)
+ elif expected_warning is not None:
+ raise TypeError(msg % type(expected_warning))
+
+ self.expected_warning = expected_warning
+
+ def __exit__(self, *exc_info):
+ super(WarningsChecker, self).__exit__(*exc_info)
+
+ # only check if we're not currently handling an exception
+ if all(a is None for a in exc_info):
+ if self.expected_warning is not None:
+ if not any(r.category in self.expected_warning for r in self):
+ __tracebackhide__ = True
+ pytest.fail("DID NOT WARN")
diff --git a/lib/spack/external/_pytest/resultlog.py b/lib/spack/external/_pytest/resultlog.py
new file mode 100644
index 0000000000..fc00259834
--- /dev/null
+++ b/lib/spack/external/_pytest/resultlog.py
@@ -0,0 +1,107 @@
+""" log machine-parseable test session result information in a plain
+text file.
+"""
+
+import py
+import os
+
+def pytest_addoption(parser):
+ group = parser.getgroup("terminal reporting", "resultlog plugin options")
+ group.addoption('--resultlog', '--result-log', action="store",
+ metavar="path", default=None,
+ help="DEPRECATED path for machine-readable result log.")
+
+def pytest_configure(config):
+ resultlog = config.option.resultlog
+ # prevent opening resultlog on slave nodes (xdist)
+ if resultlog and not hasattr(config, 'slaveinput'):
+ dirname = os.path.dirname(os.path.abspath(resultlog))
+ if not os.path.isdir(dirname):
+ os.makedirs(dirname)
+ logfile = open(resultlog, 'w', 1) # line buffered
+ config._resultlog = ResultLog(config, logfile)
+ config.pluginmanager.register(config._resultlog)
+
+ from _pytest.deprecated import RESULT_LOG
+ config.warn('C1', RESULT_LOG)
+
+def pytest_unconfigure(config):
+ resultlog = getattr(config, '_resultlog', None)
+ if resultlog:
+ resultlog.logfile.close()
+ del config._resultlog
+ config.pluginmanager.unregister(resultlog)
+
+def generic_path(item):
+ chain = item.listchain()
+ gpath = [chain[0].name]
+ fspath = chain[0].fspath
+ fspart = False
+ for node in chain[1:]:
+ newfspath = node.fspath
+ if newfspath == fspath:
+ if fspart:
+ gpath.append(':')
+ fspart = False
+ else:
+ gpath.append('.')
+ else:
+ gpath.append('/')
+ fspart = True
+ name = node.name
+ if name[0] in '([':
+ gpath.pop()
+ gpath.append(name)
+ fspath = newfspath
+ return ''.join(gpath)
+
+class ResultLog(object):
+ def __init__(self, config, logfile):
+ self.config = config
+ self.logfile = logfile # preferably line buffered
+
+ def write_log_entry(self, testpath, lettercode, longrepr):
+ py.builtin.print_("%s %s" % (lettercode, testpath), file=self.logfile)
+ for line in longrepr.splitlines():
+ py.builtin.print_(" %s" % line, file=self.logfile)
+
+ def log_outcome(self, report, lettercode, longrepr):
+ testpath = getattr(report, 'nodeid', None)
+ if testpath is None:
+ testpath = report.fspath
+ self.write_log_entry(testpath, lettercode, longrepr)
+
+ def pytest_runtest_logreport(self, report):
+ if report.when != "call" and report.passed:
+ return
+ res = self.config.hook.pytest_report_teststatus(report=report)
+ code = res[1]
+ if code == 'x':
+ longrepr = str(report.longrepr)
+ elif code == 'X':
+ longrepr = ''
+ elif report.passed:
+ longrepr = ""
+ elif report.failed:
+ longrepr = str(report.longrepr)
+ elif report.skipped:
+ longrepr = str(report.longrepr[2])
+ self.log_outcome(report, code, longrepr)
+
+ def pytest_collectreport(self, report):
+ if not report.passed:
+ if report.failed:
+ code = "F"
+ longrepr = str(report.longrepr)
+ else:
+ assert report.skipped
+ code = "S"
+ longrepr = "%s:%d: %s" % report.longrepr
+ self.log_outcome(report, code, longrepr)
+
+ def pytest_internalerror(self, excrepr):
+ reprcrash = getattr(excrepr, 'reprcrash', None)
+ path = getattr(reprcrash, "path", None)
+ if path is None:
+ path = "cwd:%s" % py.path.local()
+ self.write_log_entry(path, '!', str(excrepr))
diff --git a/lib/spack/external/_pytest/runner.py b/lib/spack/external/_pytest/runner.py
new file mode 100644
index 0000000000..eb29e7370c
--- /dev/null
+++ b/lib/spack/external/_pytest/runner.py
@@ -0,0 +1,578 @@
+""" basic collect and runtest protocol implementations """
+import bdb
+import sys
+from time import time
+
+import py
+import pytest
+from _pytest._code.code import TerminalRepr, ExceptionInfo
+
+
+def pytest_namespace():
+ return {
+ 'fail' : fail,
+ 'skip' : skip,
+ 'importorskip' : importorskip,
+ 'exit' : exit,
+ }
+
+#
+# pytest plugin hooks
+
+def pytest_addoption(parser):
+ group = parser.getgroup("terminal reporting", "reporting", after="general")
+ group.addoption('--durations',
+ action="store", type=int, default=None, metavar="N",
+ help="show N slowest setup/test durations (N=0 for all)."),
+
+def pytest_terminal_summary(terminalreporter):
+ durations = terminalreporter.config.option.durations
+ if durations is None:
+ return
+ tr = terminalreporter
+ dlist = []
+ for replist in tr.stats.values():
+ for rep in replist:
+ if hasattr(rep, 'duration'):
+ dlist.append(rep)
+ if not dlist:
+ return
+ dlist.sort(key=lambda x: x.duration)
+ dlist.reverse()
+ if not durations:
+ tr.write_sep("=", "slowest test durations")
+ else:
+ tr.write_sep("=", "slowest %s test durations" % durations)
+ dlist = dlist[:durations]
+
+ for rep in dlist:
+ nodeid = rep.nodeid.replace("::()::", "::")
+ tr.write_line("%02.2fs %-8s %s" %
+ (rep.duration, rep.when, nodeid))
+
+def pytest_sessionstart(session):
+ session._setupstate = SetupState()
+def pytest_sessionfinish(session):
+ session._setupstate.teardown_all()
+
+class NodeInfo:
+ def __init__(self, location):
+ self.location = location
+
+def pytest_runtest_protocol(item, nextitem):
+ item.ihook.pytest_runtest_logstart(
+ nodeid=item.nodeid, location=item.location,
+ )
+ runtestprotocol(item, nextitem=nextitem)
+ return True
+
+def runtestprotocol(item, log=True, nextitem=None):
+ hasrequest = hasattr(item, "_request")
+ if hasrequest and not item._request:
+ item._initrequest()
+ rep = call_and_report(item, "setup", log)
+ reports = [rep]
+ if rep.passed:
+ if item.config.option.setupshow:
+ show_test_item(item)
+ if not item.config.option.setuponly:
+ reports.append(call_and_report(item, "call", log))
+ reports.append(call_and_report(item, "teardown", log,
+ nextitem=nextitem))
+ # after all teardown hooks have been called
+ # want funcargs and request info to go away
+ if hasrequest:
+ item._request = False
+ item.funcargs = None
+ return reports
+
+def show_test_item(item):
+ """Show test function, parameters and the fixtures of the test item."""
+ tw = item.config.get_terminal_writer()
+ tw.line()
+ tw.write(' ' * 8)
+ tw.write(item._nodeid)
+ used_fixtures = sorted(item._fixtureinfo.name2fixturedefs.keys())
+ if used_fixtures:
+ tw.write(' (fixtures used: {0})'.format(', '.join(used_fixtures)))
+
+def pytest_runtest_setup(item):
+ item.session._setupstate.prepare(item)
+
+def pytest_runtest_call(item):
+ try:
+ item.runtest()
+ except Exception:
+ # Store trace info to allow postmortem debugging
+ type, value, tb = sys.exc_info()
+ tb = tb.tb_next # Skip *this* frame
+ sys.last_type = type
+ sys.last_value = value
+ sys.last_traceback = tb
+ del tb # Get rid of it in this namespace
+ raise
+
+def pytest_runtest_teardown(item, nextitem):
+ item.session._setupstate.teardown_exact(item, nextitem)
+
+def pytest_report_teststatus(report):
+ if report.when in ("setup", "teardown"):
+ if report.failed:
+ # category, shortletter, verbose-word
+ return "error", "E", "ERROR"
+ elif report.skipped:
+ return "skipped", "s", "SKIPPED"
+ else:
+ return "", "", ""
+
+
+#
+# Implementation
+
+def call_and_report(item, when, log=True, **kwds):
+ call = call_runtest_hook(item, when, **kwds)
+ hook = item.ihook
+ report = hook.pytest_runtest_makereport(item=item, call=call)
+ if log:
+ hook.pytest_runtest_logreport(report=report)
+ if check_interactive_exception(call, report):
+ hook.pytest_exception_interact(node=item, call=call, report=report)
+ return report
+
+def check_interactive_exception(call, report):
+ return call.excinfo and not (
+ hasattr(report, "wasxfail") or
+ call.excinfo.errisinstance(skip.Exception) or
+ call.excinfo.errisinstance(bdb.BdbQuit))
+
+def call_runtest_hook(item, when, **kwds):
+ hookname = "pytest_runtest_" + when
+ ihook = getattr(item.ihook, hookname)
+ return CallInfo(lambda: ihook(item=item, **kwds), when=when)
+
+class CallInfo:
+ """ Result/Exception info a function invocation. """
+ #: None or ExceptionInfo object.
+ excinfo = None
+ def __init__(self, func, when):
+ #: context of invocation: one of "setup", "call",
+ #: "teardown", "memocollect"
+ self.when = when
+ self.start = time()
+ try:
+ self.result = func()
+ except KeyboardInterrupt:
+ self.stop = time()
+ raise
+ except:
+ self.excinfo = ExceptionInfo()
+ self.stop = time()
+
+ def __repr__(self):
+ if self.excinfo:
+ status = "exception: %s" % str(self.excinfo.value)
+ else:
+ status = "result: %r" % (self.result,)
+ return "<CallInfo when=%r %s>" % (self.when, status)
+
+def getslaveinfoline(node):
+ try:
+ return node._slaveinfocache
+ except AttributeError:
+ d = node.slaveinfo
+ ver = "%s.%s.%s" % d['version_info'][:3]
+ node._slaveinfocache = s = "[%s] %s -- Python %s %s" % (
+ d['id'], d['sysplatform'], ver, d['executable'])
+ return s
+
+class BaseReport(object):
+
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+
+ def toterminal(self, out):
+ if hasattr(self, 'node'):
+ out.line(getslaveinfoline(self.node))
+
+ longrepr = self.longrepr
+ if longrepr is None:
+ return
+
+ if hasattr(longrepr, 'toterminal'):
+ longrepr.toterminal(out)
+ else:
+ try:
+ out.line(longrepr)
+ except UnicodeEncodeError:
+ out.line("<unprintable longrepr>")
+
+ def get_sections(self, prefix):
+ for name, content in self.sections:
+ if name.startswith(prefix):
+ yield prefix, content
+
+ @property
+ def longreprtext(self):
+ """
+ Read-only property that returns the full string representation
+ of ``longrepr``.
+
+ .. versionadded:: 3.0
+ """
+ tw = py.io.TerminalWriter(stringio=True)
+ tw.hasmarkup = False
+ self.toterminal(tw)
+ exc = tw.stringio.getvalue()
+ return exc.strip()
+
+ @property
+ def capstdout(self):
+ """Return captured text from stdout, if capturing is enabled
+
+ .. versionadded:: 3.0
+ """
+ return ''.join(content for (prefix, content) in self.get_sections('Captured stdout'))
+
+ @property
+ def capstderr(self):
+ """Return captured text from stderr, if capturing is enabled
+
+ .. versionadded:: 3.0
+ """
+ return ''.join(content for (prefix, content) in self.get_sections('Captured stderr'))
+
+ passed = property(lambda x: x.outcome == "passed")
+ failed = property(lambda x: x.outcome == "failed")
+ skipped = property(lambda x: x.outcome == "skipped")
+
+ @property
+ def fspath(self):
+ return self.nodeid.split("::")[0]
+
+def pytest_runtest_makereport(item, call):
+ when = call.when
+ duration = call.stop-call.start
+ keywords = dict([(x,1) for x in item.keywords])
+ excinfo = call.excinfo
+ sections = []
+ if not call.excinfo:
+ outcome = "passed"
+ longrepr = None
+ else:
+ if not isinstance(excinfo, ExceptionInfo):
+ outcome = "failed"
+ longrepr = excinfo
+ elif excinfo.errisinstance(pytest.skip.Exception):
+ outcome = "skipped"
+ r = excinfo._getreprcrash()
+ longrepr = (str(r.path), r.lineno, r.message)
+ else:
+ outcome = "failed"
+ if call.when == "call":
+ longrepr = item.repr_failure(excinfo)
+ else: # exception in setup or teardown
+ longrepr = item._repr_failure_py(excinfo,
+ style=item.config.option.tbstyle)
+ for rwhen, key, content in item._report_sections:
+ sections.append(("Captured %s %s" %(key, rwhen), content))
+ return TestReport(item.nodeid, item.location,
+ keywords, outcome, longrepr, when,
+ sections, duration)
+
+class TestReport(BaseReport):
+ """ Basic test report object (also used for setup and teardown calls if
+ they fail).
+ """
+ def __init__(self, nodeid, location, keywords, outcome,
+ longrepr, when, sections=(), duration=0, **extra):
+ #: normalized collection node id
+ self.nodeid = nodeid
+
+ #: a (filesystempath, lineno, domaininfo) tuple indicating the
+ #: actual location of a test item - it might be different from the
+ #: collected one e.g. if a method is inherited from a different module.
+ self.location = location
+
+ #: a name -> value dictionary containing all keywords and
+ #: markers associated with a test invocation.
+ self.keywords = keywords
+
+ #: test outcome, always one of "passed", "failed", "skipped".
+ self.outcome = outcome
+
+ #: None or a failure representation.
+ self.longrepr = longrepr
+
+ #: one of 'setup', 'call', 'teardown' to indicate runtest phase.
+ self.when = when
+
+ #: list of pairs ``(str, str)`` of extra information which needs to
+ #: marshallable. Used by pytest to add captured text
+ #: from ``stdout`` and ``stderr``, but may be used by other plugins
+ #: to add arbitrary information to reports.
+ self.sections = list(sections)
+
+ #: time it took to run just the test
+ self.duration = duration
+
+ self.__dict__.update(extra)
+
+ def __repr__(self):
+ return "<TestReport %r when=%r outcome=%r>" % (
+ self.nodeid, self.when, self.outcome)
+
+class TeardownErrorReport(BaseReport):
+ outcome = "failed"
+ when = "teardown"
+ def __init__(self, longrepr, **extra):
+ self.longrepr = longrepr
+ self.sections = []
+ self.__dict__.update(extra)
+
+def pytest_make_collect_report(collector):
+ call = CallInfo(collector._memocollect, "memocollect")
+ longrepr = None
+ if not call.excinfo:
+ outcome = "passed"
+ else:
+ from _pytest import nose
+ skip_exceptions = (Skipped,) + nose.get_skip_exceptions()
+ if call.excinfo.errisinstance(skip_exceptions):
+ outcome = "skipped"
+ r = collector._repr_failure_py(call.excinfo, "line").reprcrash
+ longrepr = (str(r.path), r.lineno, r.message)
+ else:
+ outcome = "failed"
+ errorinfo = collector.repr_failure(call.excinfo)
+ if not hasattr(errorinfo, "toterminal"):
+ errorinfo = CollectErrorRepr(errorinfo)
+ longrepr = errorinfo
+ rep = CollectReport(collector.nodeid, outcome, longrepr,
+ getattr(call, 'result', None))
+ rep.call = call # see collect_one_node
+ return rep
+
+
+class CollectReport(BaseReport):
+ def __init__(self, nodeid, outcome, longrepr, result,
+ sections=(), **extra):
+ self.nodeid = nodeid
+ self.outcome = outcome
+ self.longrepr = longrepr
+ self.result = result or []
+ self.sections = list(sections)
+ self.__dict__.update(extra)
+
+ @property
+ def location(self):
+ return (self.fspath, None, self.fspath)
+
+ def __repr__(self):
+ return "<CollectReport %r lenresult=%s outcome=%r>" % (
+ self.nodeid, len(self.result), self.outcome)
+
+class CollectErrorRepr(TerminalRepr):
+ def __init__(self, msg):
+ self.longrepr = msg
+ def toterminal(self, out):
+ out.line(self.longrepr, red=True)
+
+class SetupState(object):
+ """ shared state for setting up/tearing down test items or collectors. """
+ def __init__(self):
+ self.stack = []
+ self._finalizers = {}
+
+ def addfinalizer(self, finalizer, colitem):
+ """ attach a finalizer to the given colitem.
+ if colitem is None, this will add a finalizer that
+ is called at the end of teardown_all().
+ """
+ assert colitem and not isinstance(colitem, tuple)
+ assert py.builtin.callable(finalizer)
+ #assert colitem in self.stack # some unit tests don't setup stack :/
+ self._finalizers.setdefault(colitem, []).append(finalizer)
+
+ def _pop_and_teardown(self):
+ colitem = self.stack.pop()
+ self._teardown_with_finalization(colitem)
+
+ def _callfinalizers(self, colitem):
+ finalizers = self._finalizers.pop(colitem, None)
+ exc = None
+ while finalizers:
+ fin = finalizers.pop()
+ try:
+ fin()
+ except Exception:
+ # XXX Only first exception will be seen by user,
+ # ideally all should be reported.
+ if exc is None:
+ exc = sys.exc_info()
+ if exc:
+ py.builtin._reraise(*exc)
+
+ def _teardown_with_finalization(self, colitem):
+ self._callfinalizers(colitem)
+ if hasattr(colitem, "teardown"):
+ colitem.teardown()
+ for colitem in self._finalizers:
+ assert colitem is None or colitem in self.stack \
+ or isinstance(colitem, tuple)
+
+ def teardown_all(self):
+ while self.stack:
+ self._pop_and_teardown()
+ for key in list(self._finalizers):
+ self._teardown_with_finalization(key)
+ assert not self._finalizers
+
+ def teardown_exact(self, item, nextitem):
+ needed_collectors = nextitem and nextitem.listchain() or []
+ self._teardown_towards(needed_collectors)
+
+ def _teardown_towards(self, needed_collectors):
+ while self.stack:
+ if self.stack == needed_collectors[:len(self.stack)]:
+ break
+ self._pop_and_teardown()
+
+ def prepare(self, colitem):
+ """ setup objects along the collector chain to the test-method
+ and teardown previously setup objects."""
+ needed_collectors = colitem.listchain()
+ self._teardown_towards(needed_collectors)
+
+ # check if the last collection node has raised an error
+ for col in self.stack:
+ if hasattr(col, '_prepare_exc'):
+ py.builtin._reraise(*col._prepare_exc)
+ for col in needed_collectors[len(self.stack):]:
+ self.stack.append(col)
+ try:
+ col.setup()
+ except Exception:
+ col._prepare_exc = sys.exc_info()
+ raise
+
+def collect_one_node(collector):
+ ihook = collector.ihook
+ ihook.pytest_collectstart(collector=collector)
+ rep = ihook.pytest_make_collect_report(collector=collector)
+ call = rep.__dict__.pop("call", None)
+ if call and check_interactive_exception(call, rep):
+ ihook.pytest_exception_interact(node=collector, call=call, report=rep)
+ return rep
+
+
+# =============================================================
+# Test OutcomeExceptions and helpers for creating them.
+
+
+class OutcomeException(Exception):
+ """ OutcomeException and its subclass instances indicate and
+ contain info about test and collection outcomes.
+ """
+ def __init__(self, msg=None, pytrace=True):
+ Exception.__init__(self, msg)
+ self.msg = msg
+ self.pytrace = pytrace
+
+ def __repr__(self):
+ if self.msg:
+ val = self.msg
+ if isinstance(val, bytes):
+ val = py._builtin._totext(val, errors='replace')
+ return val
+ return "<%s instance>" %(self.__class__.__name__,)
+ __str__ = __repr__
+
+class Skipped(OutcomeException):
+ # XXX hackish: on 3k we fake to live in the builtins
+ # in order to have Skipped exception printing shorter/nicer
+ __module__ = 'builtins'
+
+ def __init__(self, msg=None, pytrace=True, allow_module_level=False):
+ OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
+ self.allow_module_level = allow_module_level
+
+
+class Failed(OutcomeException):
+ """ raised from an explicit call to pytest.fail() """
+ __module__ = 'builtins'
+
+
+class Exit(KeyboardInterrupt):
+ """ raised for immediate program exits (no tracebacks/summaries)"""
+ def __init__(self, msg="unknown reason"):
+ self.msg = msg
+ KeyboardInterrupt.__init__(self, msg)
+
+# exposed helper methods
+
+def exit(msg):
+ """ exit testing process as if KeyboardInterrupt was triggered. """
+ __tracebackhide__ = True
+ raise Exit(msg)
+
+
+exit.Exception = Exit
+
+
+def skip(msg=""):
+ """ skip an executing test with the given message. Note: it's usually
+ better to use the pytest.mark.skipif marker to declare a test to be
+ skipped under certain conditions like mismatching platforms or
+ dependencies. See the pytest_skipping plugin for details.
+ """
+ __tracebackhide__ = True
+ raise Skipped(msg=msg)
+
+
+skip.Exception = Skipped
+
+
+def fail(msg="", pytrace=True):
+ """ explicitly fail an currently-executing test with the given Message.
+
+ :arg pytrace: if false the msg represents the full failure information
+ and no python traceback will be reported.
+ """
+ __tracebackhide__ = True
+ raise Failed(msg=msg, pytrace=pytrace)
+
+
+fail.Exception = Failed
+
+
+def importorskip(modname, minversion=None):
+ """ return imported module if it has at least "minversion" as its
+ __version__ attribute. If no minversion is specified the a skip
+ is only triggered if the module can not be imported.
+ """
+ __tracebackhide__ = True
+ compile(modname, '', 'eval') # to catch syntaxerrors
+ should_skip = False
+ try:
+ __import__(modname)
+ except ImportError:
+ # Do not raise chained exception here(#1485)
+ should_skip = True
+ if should_skip:
+ raise Skipped("could not import %r" %(modname,), allow_module_level=True)
+ mod = sys.modules[modname]
+ if minversion is None:
+ return mod
+ verattr = getattr(mod, '__version__', None)
+ if minversion is not None:
+ try:
+ from pkg_resources import parse_version as pv
+ except ImportError:
+ raise Skipped("we have a required version for %r but can not import "
+ "pkg_resources to parse version strings." % (modname,),
+ allow_module_level=True)
+ if verattr is None or pv(verattr) < pv(minversion):
+ raise Skipped("module %r has __version__ %r, required is: %r" %(
+ modname, verattr, minversion), allow_module_level=True)
+ return mod
+
diff --git a/lib/spack/external/_pytest/setuponly.py b/lib/spack/external/_pytest/setuponly.py
new file mode 100644
index 0000000000..1752c575f5
--- /dev/null
+++ b/lib/spack/external/_pytest/setuponly.py
@@ -0,0 +1,72 @@
+import pytest
+import sys
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("debugconfig")
+ group.addoption('--setuponly', '--setup-only', action="store_true",
+ help="only setup fixtures, do not execute tests.")
+ group.addoption('--setupshow', '--setup-show', action="store_true",
+ help="show setup of fixtures while executing tests.")
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_fixture_setup(fixturedef, request):
+ yield
+ config = request.config
+ if config.option.setupshow:
+ if hasattr(request, 'param'):
+ # Save the fixture parameter so ._show_fixture_action() can
+ # display it now and during the teardown (in .finish()).
+ if fixturedef.ids:
+ if callable(fixturedef.ids):
+ fixturedef.cached_param = fixturedef.ids(request.param)
+ else:
+ fixturedef.cached_param = fixturedef.ids[
+ request.param_index]
+ else:
+ fixturedef.cached_param = request.param
+ _show_fixture_action(fixturedef, 'SETUP')
+
+
+def pytest_fixture_post_finalizer(fixturedef):
+ if hasattr(fixturedef, "cached_result"):
+ config = fixturedef._fixturemanager.config
+ if config.option.setupshow:
+ _show_fixture_action(fixturedef, 'TEARDOWN')
+ if hasattr(fixturedef, "cached_param"):
+ del fixturedef.cached_param
+
+
+def _show_fixture_action(fixturedef, msg):
+ config = fixturedef._fixturemanager.config
+ capman = config.pluginmanager.getplugin('capturemanager')
+ if capman:
+ out, err = capman.suspendcapture()
+
+ tw = config.get_terminal_writer()
+ tw.line()
+ tw.write(' ' * 2 * fixturedef.scopenum)
+ tw.write('{step} {scope} {fixture}'.format(
+ step=msg.ljust(8), # align the output to TEARDOWN
+ scope=fixturedef.scope[0].upper(),
+ fixture=fixturedef.argname))
+
+ if msg == 'SETUP':
+ deps = sorted(arg for arg in fixturedef.argnames if arg != 'request')
+ if deps:
+ tw.write(' (fixtures used: {0})'.format(', '.join(deps)))
+
+ if hasattr(fixturedef, 'cached_param'):
+ tw.write('[{0}]'.format(fixturedef.cached_param))
+
+ if capman:
+ capman.resumecapture()
+ sys.stdout.write(out)
+ sys.stderr.write(err)
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_cmdline_main(config):
+ if config.option.setuponly:
+ config.option.setupshow = True
diff --git a/lib/spack/external/_pytest/setupplan.py b/lib/spack/external/_pytest/setupplan.py
new file mode 100644
index 0000000000..f0853dee54
--- /dev/null
+++ b/lib/spack/external/_pytest/setupplan.py
@@ -0,0 +1,23 @@
+import pytest
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("debugconfig")
+ group.addoption('--setupplan', '--setup-plan', action="store_true",
+ help="show what fixtures and tests would be executed but "
+ "don't execute anything.")
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_fixture_setup(fixturedef, request):
+ # Will return a dummy fixture if the setuponly option is provided.
+ if request.config.option.setupplan:
+ fixturedef.cached_result = (None, None, None)
+ return fixturedef.cached_result
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_cmdline_main(config):
+ if config.option.setupplan:
+ config.option.setuponly = True
+ config.option.setupshow = True
diff --git a/lib/spack/external/_pytest/skipping.py b/lib/spack/external/_pytest/skipping.py
new file mode 100644
index 0000000000..a8eaea98aa
--- /dev/null
+++ b/lib/spack/external/_pytest/skipping.py
@@ -0,0 +1,375 @@
+""" support for skip/xfail functions and markers. """
+import os
+import sys
+import traceback
+
+import py
+import pytest
+from _pytest.mark import MarkInfo, MarkDecorator
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group.addoption('--runxfail',
+ action="store_true", dest="runxfail", default=False,
+ help="run tests even if they are marked xfail")
+
+ parser.addini("xfail_strict", "default for the strict parameter of xfail "
+ "markers when not given explicitly (default: "
+ "False)",
+ default=False,
+ type="bool")
+
+
+def pytest_configure(config):
+ if config.option.runxfail:
+ old = pytest.xfail
+ config._cleanup.append(lambda: setattr(pytest, "xfail", old))
+
+ def nop(*args, **kwargs):
+ pass
+
+ nop.Exception = XFailed
+ setattr(pytest, "xfail", nop)
+
+ config.addinivalue_line("markers",
+ "skip(reason=None): skip the given test function with an optional reason. "
+ "Example: skip(reason=\"no way of currently testing this\") skips the "
+ "test."
+ )
+ config.addinivalue_line("markers",
+ "skipif(condition): skip the given test function if eval(condition) "
+ "results in a True value. Evaluation happens within the "
+ "module global context. Example: skipif('sys.platform == \"win32\"') "
+ "skips the test if we are on the win32 platform. see "
+ "http://pytest.org/latest/skipping.html"
+ )
+ config.addinivalue_line("markers",
+ "xfail(condition, reason=None, run=True, raises=None, strict=False): "
+ "mark the the test function as an expected failure if eval(condition) "
+ "has a True value. Optionally specify a reason for better reporting "
+ "and run=False if you don't even want to execute the test function. "
+ "If only specific exception(s) are expected, you can list them in "
+ "raises, and if the test fails in other ways, it will be reported as "
+ "a true failure. See http://pytest.org/latest/skipping.html"
+ )
+
+
+def pytest_namespace():
+ return dict(xfail=xfail)
+
+
+class XFailed(pytest.fail.Exception):
+ """ raised from an explicit call to pytest.xfail() """
+
+
+def xfail(reason=""):
+ """ xfail an executing test or setup functions with the given reason."""
+ __tracebackhide__ = True
+ raise XFailed(reason)
+
+
+xfail.Exception = XFailed
+
+
+class MarkEvaluator:
+ def __init__(self, item, name):
+ self.item = item
+ self.name = name
+
+ @property
+ def holder(self):
+ return self.item.keywords.get(self.name)
+
+ def __bool__(self):
+ return bool(self.holder)
+ __nonzero__ = __bool__
+
+ def wasvalid(self):
+ return not hasattr(self, 'exc')
+
+ def invalidraise(self, exc):
+ raises = self.get('raises')
+ if not raises:
+ return
+ return not isinstance(exc, raises)
+
+ def istrue(self):
+ try:
+ return self._istrue()
+ except Exception:
+ self.exc = sys.exc_info()
+ if isinstance(self.exc[1], SyntaxError):
+ msg = [" " * (self.exc[1].offset + 4) + "^",]
+ msg.append("SyntaxError: invalid syntax")
+ else:
+ msg = traceback.format_exception_only(*self.exc[:2])
+ pytest.fail("Error evaluating %r expression\n"
+ " %s\n"
+ "%s"
+ %(self.name, self.expr, "\n".join(msg)),
+ pytrace=False)
+
+ def _getglobals(self):
+ d = {'os': os, 'sys': sys, 'config': self.item.config}
+ d.update(self.item.obj.__globals__)
+ return d
+
+ def _istrue(self):
+ if hasattr(self, 'result'):
+ return self.result
+ if self.holder:
+ d = self._getglobals()
+ if self.holder.args or 'condition' in self.holder.kwargs:
+ self.result = False
+ # "holder" might be a MarkInfo or a MarkDecorator; only
+ # MarkInfo keeps track of all parameters it received in an
+ # _arglist attribute
+ if hasattr(self.holder, '_arglist'):
+ arglist = self.holder._arglist
+ else:
+ arglist = [(self.holder.args, self.holder.kwargs)]
+ for args, kwargs in arglist:
+ if 'condition' in kwargs:
+ args = (kwargs['condition'],)
+ for expr in args:
+ self.expr = expr
+ if isinstance(expr, py.builtin._basestring):
+ result = cached_eval(self.item.config, expr, d)
+ else:
+ if "reason" not in kwargs:
+ # XXX better be checked at collection time
+ msg = "you need to specify reason=STRING " \
+ "when using booleans as conditions."
+ pytest.fail(msg)
+ result = bool(expr)
+ if result:
+ self.result = True
+ self.reason = kwargs.get('reason', None)
+ self.expr = expr
+ return self.result
+ else:
+ self.result = True
+ return getattr(self, 'result', False)
+
+ def get(self, attr, default=None):
+ return self.holder.kwargs.get(attr, default)
+
+ def getexplanation(self):
+ expl = getattr(self, 'reason', None) or self.get('reason', None)
+ if not expl:
+ if not hasattr(self, 'expr'):
+ return ""
+ else:
+ return "condition: " + str(self.expr)
+ return expl
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_runtest_setup(item):
+ # Check if skip or skipif are specified as pytest marks
+
+ skipif_info = item.keywords.get('skipif')
+ if isinstance(skipif_info, (MarkInfo, MarkDecorator)):
+ eval_skipif = MarkEvaluator(item, 'skipif')
+ if eval_skipif.istrue():
+ item._evalskip = eval_skipif
+ pytest.skip(eval_skipif.getexplanation())
+
+ skip_info = item.keywords.get('skip')
+ if isinstance(skip_info, (MarkInfo, MarkDecorator)):
+ item._evalskip = True
+ if 'reason' in skip_info.kwargs:
+ pytest.skip(skip_info.kwargs['reason'])
+ elif skip_info.args:
+ pytest.skip(skip_info.args[0])
+ else:
+ pytest.skip("unconditional skip")
+
+ item._evalxfail = MarkEvaluator(item, 'xfail')
+ check_xfail_no_run(item)
+
+
+@pytest.mark.hookwrapper
+def pytest_pyfunc_call(pyfuncitem):
+ check_xfail_no_run(pyfuncitem)
+ outcome = yield
+ passed = outcome.excinfo is None
+ if passed:
+ check_strict_xfail(pyfuncitem)
+
+
+def check_xfail_no_run(item):
+ """check xfail(run=False)"""
+ if not item.config.option.runxfail:
+ evalxfail = item._evalxfail
+ if evalxfail.istrue():
+ if not evalxfail.get('run', True):
+ pytest.xfail("[NOTRUN] " + evalxfail.getexplanation())
+
+
+def check_strict_xfail(pyfuncitem):
+ """check xfail(strict=True) for the given PASSING test"""
+ evalxfail = pyfuncitem._evalxfail
+ if evalxfail.istrue():
+ strict_default = pyfuncitem.config.getini('xfail_strict')
+ is_strict_xfail = evalxfail.get('strict', strict_default)
+ if is_strict_xfail:
+ del pyfuncitem._evalxfail
+ explanation = evalxfail.getexplanation()
+ pytest.fail('[XPASS(strict)] ' + explanation, pytrace=False)
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_runtest_makereport(item, call):
+ outcome = yield
+ rep = outcome.get_result()
+ evalxfail = getattr(item, '_evalxfail', None)
+ evalskip = getattr(item, '_evalskip', None)
+ # unitttest special case, see setting of _unexpectedsuccess
+ if hasattr(item, '_unexpectedsuccess') and rep.when == "call":
+ from _pytest.compat import _is_unittest_unexpected_success_a_failure
+ if item._unexpectedsuccess:
+ rep.longrepr = "Unexpected success: {0}".format(item._unexpectedsuccess)
+ else:
+ rep.longrepr = "Unexpected success"
+ if _is_unittest_unexpected_success_a_failure():
+ rep.outcome = "failed"
+ else:
+ rep.outcome = "passed"
+ rep.wasxfail = rep.longrepr
+ elif item.config.option.runxfail:
+ pass # don't interefere
+ elif call.excinfo and call.excinfo.errisinstance(pytest.xfail.Exception):
+ rep.wasxfail = "reason: " + call.excinfo.value.msg
+ rep.outcome = "skipped"
+ elif evalxfail and not rep.skipped and evalxfail.wasvalid() and \
+ evalxfail.istrue():
+ if call.excinfo:
+ if evalxfail.invalidraise(call.excinfo.value):
+ rep.outcome = "failed"
+ else:
+ rep.outcome = "skipped"
+ rep.wasxfail = evalxfail.getexplanation()
+ elif call.when == "call":
+ strict_default = item.config.getini('xfail_strict')
+ is_strict_xfail = evalxfail.get('strict', strict_default)
+ explanation = evalxfail.getexplanation()
+ if is_strict_xfail:
+ rep.outcome = "failed"
+ rep.longrepr = "[XPASS(strict)] {0}".format(explanation)
+ else:
+ rep.outcome = "passed"
+ rep.wasxfail = explanation
+ elif evalskip is not None and rep.skipped and type(rep.longrepr) is tuple:
+ # skipped by mark.skipif; change the location of the failure
+ # to point to the item definition, otherwise it will display
+ # the location of where the skip exception was raised within pytest
+ filename, line, reason = rep.longrepr
+ filename, line = item.location[:2]
+ rep.longrepr = filename, line, reason
+
+# called by terminalreporter progress reporting
+def pytest_report_teststatus(report):
+ if hasattr(report, "wasxfail"):
+ if report.skipped:
+ return "xfailed", "x", "xfail"
+ elif report.passed:
+ return "xpassed", "X", ("XPASS", {'yellow': True})
+
+# called by the terminalreporter instance/plugin
+def pytest_terminal_summary(terminalreporter):
+ tr = terminalreporter
+ if not tr.reportchars:
+ #for name in "xfailed skipped failed xpassed":
+ # if not tr.stats.get(name, 0):
+ # tr.write_line("HINT: use '-r' option to see extra "
+ # "summary info about tests")
+ # break
+ return
+
+ lines = []
+ for char in tr.reportchars:
+ if char == "x":
+ show_xfailed(terminalreporter, lines)
+ elif char == "X":
+ show_xpassed(terminalreporter, lines)
+ elif char in "fF":
+ show_simple(terminalreporter, lines, 'failed', "FAIL %s")
+ elif char in "sS":
+ show_skipped(terminalreporter, lines)
+ elif char == "E":
+ show_simple(terminalreporter, lines, 'error', "ERROR %s")
+ elif char == 'p':
+ show_simple(terminalreporter, lines, 'passed', "PASSED %s")
+
+ if lines:
+ tr._tw.sep("=", "short test summary info")
+ for line in lines:
+ tr._tw.line(line)
+
+def show_simple(terminalreporter, lines, stat, format):
+ failed = terminalreporter.stats.get(stat)
+ if failed:
+ for rep in failed:
+ pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
+ lines.append(format %(pos,))
+
+def show_xfailed(terminalreporter, lines):
+ xfailed = terminalreporter.stats.get("xfailed")
+ if xfailed:
+ for rep in xfailed:
+ pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
+ reason = rep.wasxfail
+ lines.append("XFAIL %s" % (pos,))
+ if reason:
+ lines.append(" " + str(reason))
+
+def show_xpassed(terminalreporter, lines):
+ xpassed = terminalreporter.stats.get("xpassed")
+ if xpassed:
+ for rep in xpassed:
+ pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
+ reason = rep.wasxfail
+ lines.append("XPASS %s %s" %(pos, reason))
+
+def cached_eval(config, expr, d):
+ if not hasattr(config, '_evalcache'):
+ config._evalcache = {}
+ try:
+ return config._evalcache[expr]
+ except KeyError:
+ import _pytest._code
+ exprcode = _pytest._code.compile(expr, mode="eval")
+ config._evalcache[expr] = x = eval(exprcode, d)
+ return x
+
+
+def folded_skips(skipped):
+ d = {}
+ for event in skipped:
+ key = event.longrepr
+ assert len(key) == 3, (event, key)
+ d.setdefault(key, []).append(event)
+ l = []
+ for key, events in d.items():
+ l.append((len(events),) + key)
+ return l
+
+def show_skipped(terminalreporter, lines):
+ tr = terminalreporter
+ skipped = tr.stats.get('skipped', [])
+ if skipped:
+ #if not tr.hasopt('skipped'):
+ # tr.write_line(
+ # "%d skipped tests, specify -rs for more info" %
+ # len(skipped))
+ # return
+ fskips = folded_skips(skipped)
+ if fskips:
+ #tr.write_sep("_", "skipped test summary")
+ for num, fspath, lineno, reason in fskips:
+ if reason.startswith("Skipped: "):
+ reason = reason[9:]
+ lines.append("SKIP [%d] %s:%d: %s" %
+ (num, fspath, lineno, reason))
diff --git a/lib/spack/external/_pytest/terminal.py b/lib/spack/external/_pytest/terminal.py
new file mode 100644
index 0000000000..16bf757338
--- /dev/null
+++ b/lib/spack/external/_pytest/terminal.py
@@ -0,0 +1,593 @@
+""" terminal reporting of the full testing process.
+
+This is a good source for looking at the various reporting hooks.
+"""
+from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
+ EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
+import pytest
+import py
+import sys
+import time
+import platform
+
+import _pytest._pluggy as pluggy
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("terminal reporting", "reporting", after="general")
+ group._addoption('-v', '--verbose', action="count",
+ dest="verbose", default=0, help="increase verbosity."),
+ group._addoption('-q', '--quiet', action="count",
+ dest="quiet", default=0, help="decrease verbosity."),
+ group._addoption('-r',
+ action="store", dest="reportchars", default='', metavar="chars",
+ help="show extra test summary info as specified by chars (f)ailed, "
+ "(E)error, (s)skipped, (x)failed, (X)passed, "
+ "(p)passed, (P)passed with output, (a)all except pP. "
+ "The pytest warnings are displayed at all times except when "
+ "--disable-pytest-warnings is set")
+ group._addoption('--disable-pytest-warnings', default=False,
+ dest='disablepytestwarnings', action='store_true',
+ help='disable warnings summary, overrides -r w flag')
+ group._addoption('-l', '--showlocals',
+ action="store_true", dest="showlocals", default=False,
+ help="show locals in tracebacks (disabled by default).")
+ group._addoption('--tb', metavar="style",
+ action="store", dest="tbstyle", default='auto',
+ choices=['auto', 'long', 'short', 'no', 'line', 'native'],
+ help="traceback print mode (auto/long/short/line/native/no).")
+ group._addoption('--fulltrace', '--full-trace',
+ action="store_true", default=False,
+ help="don't cut any tracebacks (default is to cut).")
+ group._addoption('--color', metavar="color",
+ action="store", dest="color", default='auto',
+ choices=['yes', 'no', 'auto'],
+ help="color terminal output (yes/no/auto).")
+
+def pytest_configure(config):
+ config.option.verbose -= config.option.quiet
+ reporter = TerminalReporter(config, sys.stdout)
+ config.pluginmanager.register(reporter, 'terminalreporter')
+ if config.option.debug or config.option.traceconfig:
+ def mywriter(tags, args):
+ msg = " ".join(map(str, args))
+ reporter.write_line("[traceconfig] " + msg)
+ config.trace.root.setprocessor("pytest:config", mywriter)
+
+def getreportopt(config):
+ reportopts = ""
+ reportchars = config.option.reportchars
+ if not config.option.disablepytestwarnings and 'w' not in reportchars:
+ reportchars += 'w'
+ elif config.option.disablepytestwarnings and 'w' in reportchars:
+ reportchars = reportchars.replace('w', '')
+ if reportchars:
+ for char in reportchars:
+ if char not in reportopts and char != 'a':
+ reportopts += char
+ elif char == 'a':
+ reportopts = 'fEsxXw'
+ return reportopts
+
+def pytest_report_teststatus(report):
+ if report.passed:
+ letter = "."
+ elif report.skipped:
+ letter = "s"
+ elif report.failed:
+ letter = "F"
+ if report.when != "call":
+ letter = "f"
+ return report.outcome, letter, report.outcome.upper()
+
+class WarningReport:
+ def __init__(self, code, message, nodeid=None, fslocation=None):
+ self.code = code
+ self.message = message
+ self.nodeid = nodeid
+ self.fslocation = fslocation
+
+
+class TerminalReporter:
+ def __init__(self, config, file=None):
+ import _pytest.config
+ self.config = config
+ self.verbosity = self.config.option.verbose
+ self.showheader = self.verbosity >= 0
+ self.showfspath = self.verbosity >= 0
+ self.showlongtestinfo = self.verbosity > 0
+ self._numcollected = 0
+
+ self.stats = {}
+ self.startdir = py.path.local()
+ if file is None:
+ file = sys.stdout
+ self._tw = self.writer = _pytest.config.create_terminal_writer(config,
+ file)
+ self.currentfspath = None
+ self.reportchars = getreportopt(config)
+ self.hasmarkup = self._tw.hasmarkup
+ self.isatty = file.isatty()
+
+ def hasopt(self, char):
+ char = {'xfailed': 'x', 'skipped': 's'}.get(char, char)
+ return char in self.reportchars
+
+ def write_fspath_result(self, nodeid, res):
+ fspath = self.config.rootdir.join(nodeid.split("::")[0])
+ if fspath != self.currentfspath:
+ self.currentfspath = fspath
+ fspath = self.startdir.bestrelpath(fspath)
+ self._tw.line()
+ self._tw.write(fspath + " ")
+ self._tw.write(res)
+
+ def write_ensure_prefix(self, prefix, extra="", **kwargs):
+ if self.currentfspath != prefix:
+ self._tw.line()
+ self.currentfspath = prefix
+ self._tw.write(prefix)
+ if extra:
+ self._tw.write(extra, **kwargs)
+ self.currentfspath = -2
+
+ def ensure_newline(self):
+ if self.currentfspath:
+ self._tw.line()
+ self.currentfspath = None
+
+ def write(self, content, **markup):
+ self._tw.write(content, **markup)
+
+ def write_line(self, line, **markup):
+ if not py.builtin._istext(line):
+ line = py.builtin.text(line, errors="replace")
+ self.ensure_newline()
+ self._tw.line(line, **markup)
+
+ def rewrite(self, line, **markup):
+ line = str(line)
+ self._tw.write("\r" + line, **markup)
+
+ def write_sep(self, sep, title=None, **markup):
+ self.ensure_newline()
+ self._tw.sep(sep, title, **markup)
+
+ def section(self, title, sep="=", **kw):
+ self._tw.sep(sep, title, **kw)
+
+ def line(self, msg, **kw):
+ self._tw.line(msg, **kw)
+
+ def pytest_internalerror(self, excrepr):
+ for line in py.builtin.text(excrepr).split("\n"):
+ self.write_line("INTERNALERROR> " + line)
+ return 1
+
+ def pytest_logwarning(self, code, fslocation, message, nodeid):
+ warnings = self.stats.setdefault("warnings", [])
+ if isinstance(fslocation, tuple):
+ fslocation = "%s:%d" % fslocation
+ warning = WarningReport(code=code, fslocation=fslocation,
+ message=message, nodeid=nodeid)
+ warnings.append(warning)
+
+ def pytest_plugin_registered(self, plugin):
+ if self.config.option.traceconfig:
+ msg = "PLUGIN registered: %s" % (plugin,)
+ # XXX this event may happen during setup/teardown time
+ # which unfortunately captures our output here
+ # which garbles our output if we use self.write_line
+ self.write_line(msg)
+
+ def pytest_deselected(self, items):
+ self.stats.setdefault('deselected', []).extend(items)
+
+ def pytest_runtest_logstart(self, nodeid, location):
+ # ensure that the path is printed before the
+ # 1st test of a module starts running
+ if self.showlongtestinfo:
+ line = self._locationline(nodeid, *location)
+ self.write_ensure_prefix(line, "")
+ elif self.showfspath:
+ fsid = nodeid.split("::")[0]
+ self.write_fspath_result(fsid, "")
+
+ def pytest_runtest_logreport(self, report):
+ rep = report
+ res = self.config.hook.pytest_report_teststatus(report=rep)
+ cat, letter, word = res
+ self.stats.setdefault(cat, []).append(rep)
+ self._tests_ran = True
+ if not letter and not word:
+ # probably passed setup/teardown
+ return
+ if self.verbosity <= 0:
+ if not hasattr(rep, 'node') and self.showfspath:
+ self.write_fspath_result(rep.nodeid, letter)
+ else:
+ self._tw.write(letter)
+ else:
+ if isinstance(word, tuple):
+ word, markup = word
+ else:
+ if rep.passed:
+ markup = {'green':True}
+ elif rep.failed:
+ markup = {'red':True}
+ elif rep.skipped:
+ markup = {'yellow':True}
+ line = self._locationline(rep.nodeid, *rep.location)
+ if not hasattr(rep, 'node'):
+ self.write_ensure_prefix(line, word, **markup)
+ #self._tw.write(word, **markup)
+ else:
+ self.ensure_newline()
+ if hasattr(rep, 'node'):
+ self._tw.write("[%s] " % rep.node.gateway.id)
+ self._tw.write(word, **markup)
+ self._tw.write(" " + line)
+ self.currentfspath = -2
+
+ def pytest_collection(self):
+ if not self.isatty and self.config.option.verbose >= 1:
+ self.write("collecting ... ", bold=True)
+
+ def pytest_collectreport(self, report):
+ if report.failed:
+ self.stats.setdefault("error", []).append(report)
+ elif report.skipped:
+ self.stats.setdefault("skipped", []).append(report)
+ items = [x for x in report.result if isinstance(x, pytest.Item)]
+ self._numcollected += len(items)
+ if self.isatty:
+ #self.write_fspath_result(report.nodeid, 'E')
+ self.report_collect()
+
+ def report_collect(self, final=False):
+ if self.config.option.verbose < 0:
+ return
+
+ errors = len(self.stats.get('error', []))
+ skipped = len(self.stats.get('skipped', []))
+ if final:
+ line = "collected "
+ else:
+ line = "collecting "
+ line += str(self._numcollected) + " items"
+ if errors:
+ line += " / %d errors" % errors
+ if skipped:
+ line += " / %d skipped" % skipped
+ if self.isatty:
+ if final:
+ line += " \n"
+ self.rewrite(line, bold=True)
+ else:
+ self.write_line(line)
+
+ def pytest_collection_modifyitems(self):
+ self.report_collect(True)
+
+ @pytest.hookimpl(trylast=True)
+ def pytest_sessionstart(self, session):
+ self._sessionstarttime = time.time()
+ if not self.showheader:
+ return
+ self.write_sep("=", "test session starts", bold=True)
+ verinfo = platform.python_version()
+ msg = "platform %s -- Python %s" % (sys.platform, verinfo)
+ if hasattr(sys, 'pypy_version_info'):
+ verinfo = ".".join(map(str, sys.pypy_version_info[:3]))
+ msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3])
+ msg += ", pytest-%s, py-%s, pluggy-%s" % (
+ pytest.__version__, py.__version__, pluggy.__version__)
+ if self.verbosity > 0 or self.config.option.debug or \
+ getattr(self.config.option, 'pastebin', None):
+ msg += " -- " + str(sys.executable)
+ self.write_line(msg)
+ lines = self.config.hook.pytest_report_header(
+ config=self.config, startdir=self.startdir)
+ lines.reverse()
+ for line in flatten(lines):
+ self.write_line(line)
+
+ def pytest_report_header(self, config):
+ inifile = ""
+ if config.inifile:
+ inifile = config.rootdir.bestrelpath(config.inifile)
+ lines = ["rootdir: %s, inifile: %s" %(config.rootdir, inifile)]
+
+ plugininfo = config.pluginmanager.list_plugin_distinfo()
+ if plugininfo:
+
+ lines.append(
+ "plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
+ return lines
+
+ def pytest_collection_finish(self, session):
+ if self.config.option.collectonly:
+ self._printcollecteditems(session.items)
+ if self.stats.get('failed'):
+ self._tw.sep("!", "collection failures")
+ for rep in self.stats.get('failed'):
+ rep.toterminal(self._tw)
+ return 1
+ return 0
+ if not self.showheader:
+ return
+ #for i, testarg in enumerate(self.config.args):
+ # self.write_line("test path %d: %s" %(i+1, testarg))
+
+ def _printcollecteditems(self, items):
+ # to print out items and their parent collectors
+ # we take care to leave out Instances aka ()
+ # because later versions are going to get rid of them anyway
+ if self.config.option.verbose < 0:
+ if self.config.option.verbose < -1:
+ counts = {}
+ for item in items:
+ name = item.nodeid.split('::', 1)[0]
+ counts[name] = counts.get(name, 0) + 1
+ for name, count in sorted(counts.items()):
+ self._tw.line("%s: %d" % (name, count))
+ else:
+ for item in items:
+ nodeid = item.nodeid
+ nodeid = nodeid.replace("::()::", "::")
+ self._tw.line(nodeid)
+ return
+ stack = []
+ indent = ""
+ for item in items:
+ needed_collectors = item.listchain()[1:] # strip root node
+ while stack:
+ if stack == needed_collectors[:len(stack)]:
+ break
+ stack.pop()
+ for col in needed_collectors[len(stack):]:
+ stack.append(col)
+ #if col.name == "()":
+ # continue
+ indent = (len(stack) - 1) * " "
+ self._tw.line("%s%s" % (indent, col))
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_sessionfinish(self, exitstatus):
+ outcome = yield
+ outcome.get_result()
+ self._tw.line("")
+ summary_exit_codes = (
+ EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, EXIT_USAGEERROR,
+ EXIT_NOTESTSCOLLECTED)
+ if exitstatus in summary_exit_codes:
+ self.config.hook.pytest_terminal_summary(terminalreporter=self,
+ exitstatus=exitstatus)
+ self.summary_errors()
+ self.summary_failures()
+ self.summary_warnings()
+ self.summary_passes()
+ if exitstatus == EXIT_INTERRUPTED:
+ self._report_keyboardinterrupt()
+ del self._keyboardinterrupt_memo
+ self.summary_deselected()
+ self.summary_stats()
+
+ def pytest_keyboard_interrupt(self, excinfo):
+ self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
+
+ def pytest_unconfigure(self):
+ if hasattr(self, '_keyboardinterrupt_memo'):
+ self._report_keyboardinterrupt()
+
+ def _report_keyboardinterrupt(self):
+ excrepr = self._keyboardinterrupt_memo
+ msg = excrepr.reprcrash.message
+ self.write_sep("!", msg)
+ if "KeyboardInterrupt" in msg:
+ if self.config.option.fulltrace:
+ excrepr.toterminal(self._tw)
+ else:
+ self._tw.line("to show a full traceback on KeyboardInterrupt use --fulltrace", yellow=True)
+ excrepr.reprcrash.toterminal(self._tw)
+
+ def _locationline(self, nodeid, fspath, lineno, domain):
+ def mkrel(nodeid):
+ line = self.config.cwd_relative_nodeid(nodeid)
+ if domain and line.endswith(domain):
+ line = line[:-len(domain)]
+ l = domain.split("[")
+ l[0] = l[0].replace('.', '::') # don't replace '.' in params
+ line += "[".join(l)
+ return line
+ # collect_fspath comes from testid which has a "/"-normalized path
+
+ if fspath:
+ res = mkrel(nodeid).replace("::()", "") # parens-normalization
+ if nodeid.split("::")[0] != fspath.replace("\\", "/"):
+ res += " <- " + self.startdir.bestrelpath(fspath)
+ else:
+ res = "[location]"
+ return res + " "
+
+ def _getfailureheadline(self, rep):
+ if hasattr(rep, 'location'):
+ fspath, lineno, domain = rep.location
+ return domain
+ else:
+ return "test session" # XXX?
+
+ def _getcrashline(self, rep):
+ try:
+ return str(rep.longrepr.reprcrash)
+ except AttributeError:
+ try:
+ return str(rep.longrepr)[:50]
+ except AttributeError:
+ return ""
+
+ #
+ # summaries for sessionfinish
+ #
+ def getreports(self, name):
+ l = []
+ for x in self.stats.get(name, []):
+ if not hasattr(x, '_pdbshown'):
+ l.append(x)
+ return l
+
+ def summary_warnings(self):
+ if self.hasopt("w"):
+ warnings = self.stats.get("warnings")
+ if not warnings:
+ return
+ self.write_sep("=", "pytest-warning summary")
+ for w in warnings:
+ self._tw.line("W%s %s %s" % (w.code,
+ w.fslocation, w.message))
+
+ def summary_passes(self):
+ if self.config.option.tbstyle != "no":
+ if self.hasopt("P"):
+ reports = self.getreports('passed')
+ if not reports:
+ return
+ self.write_sep("=", "PASSES")
+ for rep in reports:
+ msg = self._getfailureheadline(rep)
+ self.write_sep("_", msg)
+ self._outrep_summary(rep)
+
+ def print_teardown_sections(self, rep):
+ for secname, content in rep.sections:
+ if 'teardown' in secname:
+ self._tw.sep('-', secname)
+ if content[-1:] == "\n":
+ content = content[:-1]
+ self._tw.line(content)
+
+
+ def summary_failures(self):
+ if self.config.option.tbstyle != "no":
+ reports = self.getreports('failed')
+ if not reports:
+ return
+ self.write_sep("=", "FAILURES")
+ for rep in reports:
+ if self.config.option.tbstyle == "line":
+ line = self._getcrashline(rep)
+ self.write_line(line)
+ else:
+ msg = self._getfailureheadline(rep)
+ markup = {'red': True, 'bold': True}
+ self.write_sep("_", msg, **markup)
+ self._outrep_summary(rep)
+ for report in self.getreports(''):
+ if report.nodeid == rep.nodeid and report.when == 'teardown':
+ self.print_teardown_sections(report)
+
+ def summary_errors(self):
+ if self.config.option.tbstyle != "no":
+ reports = self.getreports('error')
+ if not reports:
+ return
+ self.write_sep("=", "ERRORS")
+ for rep in self.stats['error']:
+ msg = self._getfailureheadline(rep)
+ if not hasattr(rep, 'when'):
+ # collect
+ msg = "ERROR collecting " + msg
+ elif rep.when == "setup":
+ msg = "ERROR at setup of " + msg
+ elif rep.when == "teardown":
+ msg = "ERROR at teardown of " + msg
+ self.write_sep("_", msg)
+ self._outrep_summary(rep)
+
+ def _outrep_summary(self, rep):
+ rep.toterminal(self._tw)
+ for secname, content in rep.sections:
+ self._tw.sep("-", secname)
+ if content[-1:] == "\n":
+ content = content[:-1]
+ self._tw.line(content)
+
+ def summary_stats(self):
+ session_duration = time.time() - self._sessionstarttime
+ (line, color) = build_summary_stats_line(self.stats)
+ msg = "%s in %.2f seconds" % (line, session_duration)
+ markup = {color: True, 'bold': True}
+
+ if self.verbosity >= 0:
+ self.write_sep("=", msg, **markup)
+ if self.verbosity == -1:
+ self.write_line(msg, **markup)
+
+ def summary_deselected(self):
+ if 'deselected' in self.stats:
+ self.write_sep("=", "%d tests deselected" % (
+ len(self.stats['deselected'])), bold=True)
+
+def repr_pythonversion(v=None):
+ if v is None:
+ v = sys.version_info
+ try:
+ return "%s.%s.%s-%s-%s" % v
+ except (TypeError, ValueError):
+ return str(v)
+
+def flatten(l):
+ for x in l:
+ if isinstance(x, (list, tuple)):
+ for y in flatten(x):
+ yield y
+ else:
+ yield x
+
+def build_summary_stats_line(stats):
+ keys = ("failed passed skipped deselected "
+ "xfailed xpassed warnings error").split()
+ key_translation = {'warnings': 'pytest-warnings'}
+ unknown_key_seen = False
+ for key in stats.keys():
+ if key not in keys:
+ if key: # setup/teardown reports have an empty key, ignore them
+ keys.append(key)
+ unknown_key_seen = True
+ parts = []
+ for key in keys:
+ val = stats.get(key, None)
+ if val:
+ key_name = key_translation.get(key, key)
+ parts.append("%d %s" % (len(val), key_name))
+
+ if parts:
+ line = ", ".join(parts)
+ else:
+ line = "no tests ran"
+
+ if 'failed' in stats or 'error' in stats:
+ color = 'red'
+ elif 'warnings' in stats or unknown_key_seen:
+ color = 'yellow'
+ elif 'passed' in stats:
+ color = 'green'
+ else:
+ color = 'yellow'
+
+ return (line, color)
+
+
+def _plugin_nameversions(plugininfo):
+ l = []
+ for plugin, dist in plugininfo:
+ # gets us name and version!
+ name = '{dist.project_name}-{dist.version}'.format(dist=dist)
+ # questionable convenience, but it keeps things short
+ if name.startswith("pytest-"):
+ name = name[7:]
+ # we decided to print python package names
+ # they can have more than one plugin
+ if name not in l:
+ l.append(name)
+ return l
diff --git a/lib/spack/external/_pytest/tmpdir.py b/lib/spack/external/_pytest/tmpdir.py
new file mode 100644
index 0000000000..28a6b06366
--- /dev/null
+++ b/lib/spack/external/_pytest/tmpdir.py
@@ -0,0 +1,124 @@
+""" support for providing temporary directories to test functions. """
+import re
+
+import pytest
+import py
+from _pytest.monkeypatch import MonkeyPatch
+
+
+class TempdirFactory:
+ """Factory for temporary directories under the common base temp directory.
+
+ The base directory can be configured using the ``--basetemp`` option.
+ """
+
+ def __init__(self, config):
+ self.config = config
+ self.trace = config.trace.get("tmpdir")
+
+ def ensuretemp(self, string, dir=1):
+ """ (deprecated) return temporary directory path with
+ the given string as the trailing part. It is usually
+ better to use the 'tmpdir' function argument which
+ provides an empty unique-per-test-invocation directory
+ and is guaranteed to be empty.
+ """
+ #py.log._apiwarn(">1.1", "use tmpdir function argument")
+ return self.getbasetemp().ensure(string, dir=dir)
+
+ def mktemp(self, basename, numbered=True):
+ """Create a subdirectory of the base temporary directory and return it.
+ If ``numbered``, ensure the directory is unique by adding a number
+ prefix greater than any existing one.
+ """
+ basetemp = self.getbasetemp()
+ if not numbered:
+ p = basetemp.mkdir(basename)
+ else:
+ p = py.path.local.make_numbered_dir(prefix=basename,
+ keep=0, rootdir=basetemp, lock_timeout=None)
+ self.trace("mktemp", p)
+ return p
+
+ def getbasetemp(self):
+ """ return base temporary directory. """
+ try:
+ return self._basetemp
+ except AttributeError:
+ basetemp = self.config.option.basetemp
+ if basetemp:
+ basetemp = py.path.local(basetemp)
+ if basetemp.check():
+ basetemp.remove()
+ basetemp.mkdir()
+ else:
+ temproot = py.path.local.get_temproot()
+ user = get_user()
+ if user:
+ # use a sub-directory in the temproot to speed-up
+ # make_numbered_dir() call
+ rootdir = temproot.join('pytest-of-%s' % user)
+ else:
+ rootdir = temproot
+ rootdir.ensure(dir=1)
+ basetemp = py.path.local.make_numbered_dir(prefix='pytest-',
+ rootdir=rootdir)
+ self._basetemp = t = basetemp.realpath()
+ self.trace("new basetemp", t)
+ return t
+
+ def finish(self):
+ self.trace("finish")
+
+
+def get_user():
+ """Return the current user name, or None if getuser() does not work
+ in the current environment (see #1010).
+ """
+ import getpass
+ try:
+ return getpass.getuser()
+ except (ImportError, KeyError):
+ return None
+
+
+# backward compatibility
+TempdirHandler = TempdirFactory
+
+
+def pytest_configure(config):
+ """Create a TempdirFactory and attach it to the config object.
+
+ This is to comply with existing plugins which expect the handler to be
+ available at pytest_configure time, but ideally should be moved entirely
+ to the tmpdir_factory session fixture.
+ """
+ mp = MonkeyPatch()
+ t = TempdirFactory(config)
+ config._cleanup.extend([mp.undo, t.finish])
+ mp.setattr(config, '_tmpdirhandler', t, raising=False)
+ mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False)
+
+
+@pytest.fixture(scope='session')
+def tmpdir_factory(request):
+ """Return a TempdirFactory instance for the test session.
+ """
+ return request.config._tmpdirhandler
+
+
+@pytest.fixture
+def tmpdir(request, tmpdir_factory):
+ """Return a temporary directory path object
+ which is unique to each test function invocation,
+ created as a sub directory of the base temporary
+ directory. The returned object is a `py.path.local`_
+ path object.
+ """
+ name = request.node.name
+ name = re.sub("[\W]", "_", name)
+ MAXVAL = 30
+ if len(name) > MAXVAL:
+ name = name[:MAXVAL]
+ x = tmpdir_factory.mktemp(name, numbered=True)
+ return x
diff --git a/lib/spack/external/_pytest/unittest.py b/lib/spack/external/_pytest/unittest.py
new file mode 100644
index 0000000000..73224010b2
--- /dev/null
+++ b/lib/spack/external/_pytest/unittest.py
@@ -0,0 +1,217 @@
+""" discovery and running of std-library "unittest" style tests. """
+from __future__ import absolute_import
+
+import sys
+import traceback
+
+import pytest
+# for transfering markers
+import _pytest._code
+from _pytest.python import transfer_markers
+from _pytest.skipping import MarkEvaluator
+
+
+def pytest_pycollect_makeitem(collector, name, obj):
+ # has unittest been imported and is obj a subclass of its TestCase?
+ try:
+ if not issubclass(obj, sys.modules["unittest"].TestCase):
+ return
+ except Exception:
+ return
+ # yes, so let's collect it
+ return UnitTestCase(name, parent=collector)
+
+
+class UnitTestCase(pytest.Class):
+ # marker for fixturemanger.getfixtureinfo()
+ # to declare that our children do not support funcargs
+ nofuncargs = True
+
+ def setup(self):
+ cls = self.obj
+ if getattr(cls, '__unittest_skip__', False):
+ return # skipped
+ setup = getattr(cls, 'setUpClass', None)
+ if setup is not None:
+ setup()
+ teardown = getattr(cls, 'tearDownClass', None)
+ if teardown is not None:
+ self.addfinalizer(teardown)
+ super(UnitTestCase, self).setup()
+
+ def collect(self):
+ from unittest import TestLoader
+ cls = self.obj
+ if not getattr(cls, "__test__", True):
+ return
+ self.session._fixturemanager.parsefactories(self, unittest=True)
+ loader = TestLoader()
+ module = self.getparent(pytest.Module).obj
+ foundsomething = False
+ for name in loader.getTestCaseNames(self.obj):
+ x = getattr(self.obj, name)
+ if not getattr(x, '__test__', True):
+ continue
+ funcobj = getattr(x, 'im_func', x)
+ transfer_markers(funcobj, cls, module)
+ yield TestCaseFunction(name, parent=self)
+ foundsomething = True
+
+ if not foundsomething:
+ runtest = getattr(self.obj, 'runTest', None)
+ if runtest is not None:
+ ut = sys.modules.get("twisted.trial.unittest", None)
+ if ut is None or runtest != ut.TestCase.runTest:
+ yield TestCaseFunction('runTest', parent=self)
+
+
+
+class TestCaseFunction(pytest.Function):
+ _excinfo = None
+
+ def setup(self):
+ self._testcase = self.parent.obj(self.name)
+ self._fix_unittest_skip_decorator()
+ self._obj = getattr(self._testcase, self.name)
+ if hasattr(self._testcase, 'setup_method'):
+ self._testcase.setup_method(self._obj)
+ if hasattr(self, "_request"):
+ self._request._fillfixtures()
+
+ def _fix_unittest_skip_decorator(self):
+ """
+ The @unittest.skip decorator calls functools.wraps(self._testcase)
+ The call to functools.wraps() fails unless self._testcase
+ has a __name__ attribute. This is usually automatically supplied
+ if the test is a function or method, but we need to add manually
+ here.
+
+ See issue #1169
+ """
+ if sys.version_info[0] == 2:
+ setattr(self._testcase, "__name__", self.name)
+
+ def teardown(self):
+ if hasattr(self._testcase, 'teardown_method'):
+ self._testcase.teardown_method(self._obj)
+ # Allow garbage collection on TestCase instance attributes.
+ self._testcase = None
+ self._obj = None
+
+ def startTest(self, testcase):
+ pass
+
+ def _addexcinfo(self, rawexcinfo):
+ # unwrap potential exception info (see twisted trial support below)
+ rawexcinfo = getattr(rawexcinfo, '_rawexcinfo', rawexcinfo)
+ try:
+ excinfo = _pytest._code.ExceptionInfo(rawexcinfo)
+ except TypeError:
+ try:
+ try:
+ l = traceback.format_exception(*rawexcinfo)
+ l.insert(0, "NOTE: Incompatible Exception Representation, "
+ "displaying natively:\n\n")
+ pytest.fail("".join(l), pytrace=False)
+ except (pytest.fail.Exception, KeyboardInterrupt):
+ raise
+ except:
+ pytest.fail("ERROR: Unknown Incompatible Exception "
+ "representation:\n%r" %(rawexcinfo,), pytrace=False)
+ except KeyboardInterrupt:
+ raise
+ except pytest.fail.Exception:
+ excinfo = _pytest._code.ExceptionInfo()
+ self.__dict__.setdefault('_excinfo', []).append(excinfo)
+
+ def addError(self, testcase, rawexcinfo):
+ self._addexcinfo(rawexcinfo)
+ def addFailure(self, testcase, rawexcinfo):
+ self._addexcinfo(rawexcinfo)
+
+ def addSkip(self, testcase, reason):
+ try:
+ pytest.skip(reason)
+ except pytest.skip.Exception:
+ self._evalskip = MarkEvaluator(self, 'SkipTest')
+ self._evalskip.result = True
+ self._addexcinfo(sys.exc_info())
+
+ def addExpectedFailure(self, testcase, rawexcinfo, reason=""):
+ try:
+ pytest.xfail(str(reason))
+ except pytest.xfail.Exception:
+ self._addexcinfo(sys.exc_info())
+
+ def addUnexpectedSuccess(self, testcase, reason=""):
+ self._unexpectedsuccess = reason
+
+ def addSuccess(self, testcase):
+ pass
+
+ def stopTest(self, testcase):
+ pass
+
+ def runtest(self):
+ if self.config.pluginmanager.get_plugin("pdbinvoke") is None:
+ self._testcase(result=self)
+ else:
+ # disables tearDown and cleanups for post mortem debugging (see #1890)
+ self._testcase.debug()
+
+
+ def _prunetraceback(self, excinfo):
+ pytest.Function._prunetraceback(self, excinfo)
+ traceback = excinfo.traceback.filter(
+ lambda x:not x.frame.f_globals.get('__unittest'))
+ if traceback:
+ excinfo.traceback = traceback
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_runtest_makereport(item, call):
+ if isinstance(item, TestCaseFunction):
+ if item._excinfo:
+ call.excinfo = item._excinfo.pop(0)
+ try:
+ del call.result
+ except AttributeError:
+ pass
+
+# twisted trial support
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_runtest_protocol(item):
+ if isinstance(item, TestCaseFunction) and \
+ 'twisted.trial.unittest' in sys.modules:
+ ut = sys.modules['twisted.python.failure']
+ Failure__init__ = ut.Failure.__init__
+ check_testcase_implements_trial_reporter()
+
+ def excstore(self, exc_value=None, exc_type=None, exc_tb=None,
+ captureVars=None):
+ if exc_value is None:
+ self._rawexcinfo = sys.exc_info()
+ else:
+ if exc_type is None:
+ exc_type = type(exc_value)
+ self._rawexcinfo = (exc_type, exc_value, exc_tb)
+ try:
+ Failure__init__(self, exc_value, exc_type, exc_tb,
+ captureVars=captureVars)
+ except TypeError:
+ Failure__init__(self, exc_value, exc_type, exc_tb)
+
+ ut.Failure.__init__ = excstore
+ yield
+ ut.Failure.__init__ = Failure__init__
+ else:
+ yield
+
+
+def check_testcase_implements_trial_reporter(done=[]):
+ if done:
+ return
+ from zope.interface import classImplements
+ from twisted.trial.itrial import IReporter
+ classImplements(TestCaseFunction, IReporter)
+ done.append(1)
diff --git a/lib/spack/external/_pytest/vendored_packages/README.md b/lib/spack/external/_pytest/vendored_packages/README.md
new file mode 100644
index 0000000000..b5fe6febb0
--- /dev/null
+++ b/lib/spack/external/_pytest/vendored_packages/README.md
@@ -0,0 +1,13 @@
+This directory vendors the `pluggy` module.
+
+For a more detailed discussion for the reasons to vendoring this
+package, please see [this issue](https://github.com/pytest-dev/pytest/issues/944).
+
+To update the current version, execute:
+
+```
+$ pip install -U pluggy==<version> --no-compile --target=_pytest/vendored_packages
+```
+
+And commit the modified files. The `pluggy-<version>.dist-info` directory
+created by `pip` should be added as well.
diff --git a/lib/spack/external/_pytest/vendored_packages/__init__.py b/lib/spack/external/_pytest/vendored_packages/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/spack/external/_pytest/vendored_packages/__init__.py
diff --git a/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst
new file mode 100644
index 0000000000..da0e7a6ed7
--- /dev/null
+++ b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst
@@ -0,0 +1,11 @@
+
+Plugin registration and hook calling for Python
+===============================================
+
+This is the plugin manager as used by pytest but stripped
+of pytest specific details.
+
+During the 0.x series this plugin does not have much documentation
+except extensive docstrings in the pluggy.py module.
+
+
diff --git a/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER
new file mode 100644
index 0000000000..a1b589e38a
--- /dev/null
+++ b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt
new file mode 100644
index 0000000000..121017d086
--- /dev/null
+++ b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 holger krekel (rather uses bitbucket/hpk42)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA
new file mode 100644
index 0000000000..bd88517c94
--- /dev/null
+++ b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA
@@ -0,0 +1,40 @@
+Metadata-Version: 2.0
+Name: pluggy
+Version: 0.4.0
+Summary: plugin and hook calling mechanisms for python
+Home-page: https://github.com/pytest-dev/pluggy
+Author: Holger Krekel
+Author-email: holger at merlinux.eu
+License: MIT license
+Platform: unix
+Platform: linux
+Platform: osx
+Platform: win32
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Topic :: Software Development :: Testing
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Utilities
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+
+
+Plugin registration and hook calling for Python
+===============================================
+
+This is the plugin manager as used by pytest but stripped
+of pytest specific details.
+
+During the 0.x series this plugin does not have much documentation
+except extensive docstrings in the pluggy.py module.
+
+
diff --git a/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD
new file mode 100644
index 0000000000..3003a3bf2b
--- /dev/null
+++ b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD
@@ -0,0 +1,9 @@
+pluggy.py,sha256=u0oG9cv-oLOkNvEBlwnnu8pp1AyxpoERgUO00S3rvpQ,31543
+pluggy-0.4.0.dist-info/DESCRIPTION.rst,sha256=ltvjkFd40LW_xShthp6RRVM6OB_uACYDFR3kTpKw7o4,307
+pluggy-0.4.0.dist-info/LICENSE.txt,sha256=ruwhUOyV1HgE9F35JVL9BCZ9vMSALx369I4xq9rhpkM,1134
+pluggy-0.4.0.dist-info/METADATA,sha256=pe2hbsqKFaLHC6wAQPpFPn0KlpcPfLBe_BnS4O70bfk,1364
+pluggy-0.4.0.dist-info/RECORD,,
+pluggy-0.4.0.dist-info/WHEEL,sha256=9Z5Xm-eel1bTS7e6ogYiKz0zmPEqDwIypurdHN1hR40,116
+pluggy-0.4.0.dist-info/metadata.json,sha256=T3go5L2qOa_-H-HpCZi3EoVKb8sZ3R-fOssbkWo2nvM,1119
+pluggy-0.4.0.dist-info/top_level.txt,sha256=xKSCRhai-v9MckvMuWqNz16c1tbsmOggoMSwTgcpYHE,7
+pluggy-0.4.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
diff --git a/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL
new file mode 100644
index 0000000000..8b6dd1b5a8
--- /dev/null
+++ b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.29.0)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json
new file mode 100644
index 0000000000..cde22aff02
--- /dev/null
+++ b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json
@@ -0,0 +1 @@
+{"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Topic :: Software Development :: Testing", "Topic :: Software Development :: Libraries", "Topic :: Utilities", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"], "extensions": {"python.details": {"contacts": [{"email": "holger at merlinux.eu", "name": "Holger Krekel", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst", "license": "LICENSE.txt"}, "project_urls": {"Home": "https://github.com/pytest-dev/pluggy"}}}, "generator": "bdist_wheel (0.29.0)", "license": "MIT license", "metadata_version": "2.0", "name": "pluggy", "platform": "unix", "summary": "plugin and hook calling mechanisms for python", "version": "0.4.0"} \ No newline at end of file
diff --git a/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/top_level.txt b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/top_level.txt
new file mode 100644
index 0000000000..11bdb5c1f5
--- /dev/null
+++ b/lib/spack/external/_pytest/vendored_packages/pluggy-0.4.0.dist-info/top_level.txt
@@ -0,0 +1 @@
+pluggy
diff --git a/lib/spack/external/_pytest/vendored_packages/pluggy.py b/lib/spack/external/_pytest/vendored_packages/pluggy.py
new file mode 100644
index 0000000000..9c13932b36
--- /dev/null
+++ b/lib/spack/external/_pytest/vendored_packages/pluggy.py
@@ -0,0 +1,802 @@
+"""
+PluginManager, basic initialization and tracing.
+
+pluggy is the cristallized core of plugin management as used
+by some 150 plugins for pytest.
+
+Pluggy uses semantic versioning. Breaking changes are only foreseen for
+Major releases (incremented X in "X.Y.Z"). If you want to use pluggy in
+your project you should thus use a dependency restriction like
+"pluggy>=0.1.0,<1.0" to avoid surprises.
+
+pluggy is concerned with hook specification, hook implementations and hook
+calling. For any given hook specification a hook call invokes up to N implementations.
+A hook implementation can influence its position and type of execution:
+if attributed "tryfirst" or "trylast" it will be tried to execute
+first or last. However, if attributed "hookwrapper" an implementation
+can wrap all calls to non-hookwrapper implementations. A hookwrapper
+can thus execute some code ahead and after the execution of other hooks.
+
+Hook specification is done by way of a regular python function where
+both the function name and the names of all its arguments are significant.
+Each hook implementation function is verified against the original specification
+function, including the names of all its arguments. To allow for hook specifications
+to evolve over the livetime of a project, hook implementations can
+accept less arguments. One can thus add new arguments and semantics to
+a hook specification by adding another argument typically without breaking
+existing hook implementations.
+
+The chosen approach is meant to let a hook designer think carefuly about
+which objects are needed by an extension writer. By contrast, subclass-based
+extension mechanisms often expose a lot more state and behaviour than needed,
+thus restricting future developments.
+
+Pluggy currently consists of functionality for:
+
+- a way to register new hook specifications. Without a hook
+ specification no hook calling can be performed.
+
+- a registry of plugins which contain hook implementation functions. It
+ is possible to register plugins for which a hook specification is not yet
+ known and validate all hooks when the system is in a more referentially
+ consistent state. Setting an "optionalhook" attribution to a hook
+ implementation will avoid PluginValidationError's if a specification
+ is missing. This allows to have optional integration between plugins.
+
+- a "hook" relay object from which you can launch 1:N calls to
+ registered hook implementation functions
+
+- a mechanism for ordering hook implementation functions
+
+- mechanisms for two different type of 1:N calls: "firstresult" for when
+ the call should stop when the first implementation returns a non-None result.
+ And the other (default) way of guaranteeing that all hook implementations
+ will be called and their non-None result collected.
+
+- mechanisms for "historic" extension points such that all newly
+ registered functions will receive all hook calls that happened
+ before their registration.
+
+- a mechanism for discovering plugin objects which are based on
+ setuptools based entry points.
+
+- a simple tracing mechanism, including tracing of plugin calls and
+ their arguments.
+
+"""
+import sys
+import inspect
+
+__version__ = '0.4.0'
+
+__all__ = ["PluginManager", "PluginValidationError", "HookCallError",
+ "HookspecMarker", "HookimplMarker"]
+
+_py3 = sys.version_info > (3, 0)
+
+
+class HookspecMarker:
+ """ Decorator helper class for marking functions as hook specifications.
+
+ You can instantiate it with a project_name to get a decorator.
+ Calling PluginManager.add_hookspecs later will discover all marked functions
+ if the PluginManager uses the same project_name.
+ """
+
+ def __init__(self, project_name):
+ self.project_name = project_name
+
+ def __call__(self, function=None, firstresult=False, historic=False):
+ """ if passed a function, directly sets attributes on the function
+ which will make it discoverable to add_hookspecs(). If passed no
+ function, returns a decorator which can be applied to a function
+ later using the attributes supplied.
+
+ If firstresult is True the 1:N hook call (N being the number of registered
+ hook implementation functions) will stop at I<=N when the I'th function
+ returns a non-None result.
+
+ If historic is True calls to a hook will be memorized and replayed
+ on later registered plugins.
+
+ """
+ def setattr_hookspec_opts(func):
+ if historic and firstresult:
+ raise ValueError("cannot have a historic firstresult hook")
+ setattr(func, self.project_name + "_spec",
+ dict(firstresult=firstresult, historic=historic))
+ return func
+
+ if function is not None:
+ return setattr_hookspec_opts(function)
+ else:
+ return setattr_hookspec_opts
+
+
+class HookimplMarker:
+ """ Decorator helper class for marking functions as hook implementations.
+
+ You can instantiate with a project_name to get a decorator.
+ Calling PluginManager.register later will discover all marked functions
+ if the PluginManager uses the same project_name.
+ """
+ def __init__(self, project_name):
+ self.project_name = project_name
+
+ def __call__(self, function=None, hookwrapper=False, optionalhook=False,
+ tryfirst=False, trylast=False):
+
+ """ if passed a function, directly sets attributes on the function
+ which will make it discoverable to register(). If passed no function,
+ returns a decorator which can be applied to a function later using
+ the attributes supplied.
+
+ If optionalhook is True a missing matching hook specification will not result
+ in an error (by default it is an error if no matching spec is found).
+
+ If tryfirst is True this hook implementation will run as early as possible
+ in the chain of N hook implementations for a specfication.
+
+ If trylast is True this hook implementation will run as late as possible
+ in the chain of N hook implementations.
+
+ If hookwrapper is True the hook implementations needs to execute exactly
+ one "yield". The code before the yield is run early before any non-hookwrapper
+ function is run. The code after the yield is run after all non-hookwrapper
+ function have run. The yield receives an ``_CallOutcome`` object representing
+ the exception or result outcome of the inner calls (including other hookwrapper
+ calls).
+
+ """
+ def setattr_hookimpl_opts(func):
+ setattr(func, self.project_name + "_impl",
+ dict(hookwrapper=hookwrapper, optionalhook=optionalhook,
+ tryfirst=tryfirst, trylast=trylast))
+ return func
+
+ if function is None:
+ return setattr_hookimpl_opts
+ else:
+ return setattr_hookimpl_opts(function)
+
+
+def normalize_hookimpl_opts(opts):
+ opts.setdefault("tryfirst", False)
+ opts.setdefault("trylast", False)
+ opts.setdefault("hookwrapper", False)
+ opts.setdefault("optionalhook", False)
+
+
+class _TagTracer:
+ def __init__(self):
+ self._tag2proc = {}
+ self.writer = None
+ self.indent = 0
+
+ def get(self, name):
+ return _TagTracerSub(self, (name,))
+
+ def format_message(self, tags, args):
+ if isinstance(args[-1], dict):
+ extra = args[-1]
+ args = args[:-1]
+ else:
+ extra = {}
+
+ content = " ".join(map(str, args))
+ indent = " " * self.indent
+
+ lines = [
+ "%s%s [%s]\n" % (indent, content, ":".join(tags))
+ ]
+
+ for name, value in extra.items():
+ lines.append("%s %s: %s\n" % (indent, name, value))
+ return lines
+
+ def processmessage(self, tags, args):
+ if self.writer is not None and args:
+ lines = self.format_message(tags, args)
+ self.writer(''.join(lines))
+ try:
+ self._tag2proc[tags](tags, args)
+ except KeyError:
+ pass
+
+ def setwriter(self, writer):
+ self.writer = writer
+
+ def setprocessor(self, tags, processor):
+ if isinstance(tags, str):
+ tags = tuple(tags.split(":"))
+ else:
+ assert isinstance(tags, tuple)
+ self._tag2proc[tags] = processor
+
+
+class _TagTracerSub:
+ def __init__(self, root, tags):
+ self.root = root
+ self.tags = tags
+
+ def __call__(self, *args):
+ self.root.processmessage(self.tags, args)
+
+ def setmyprocessor(self, processor):
+ self.root.setprocessor(self.tags, processor)
+
+ def get(self, name):
+ return self.__class__(self.root, self.tags + (name,))
+
+
+def _raise_wrapfail(wrap_controller, msg):
+ co = wrap_controller.gi_code
+ raise RuntimeError("wrap_controller at %r %s:%d %s" %
+ (co.co_name, co.co_filename, co.co_firstlineno, msg))
+
+
+def _wrapped_call(wrap_controller, func):
+ """ Wrap calling to a function with a generator which needs to yield
+ exactly once. The yield point will trigger calling the wrapped function
+ and return its _CallOutcome to the yield point. The generator then needs
+ to finish (raise StopIteration) in order for the wrapped call to complete.
+ """
+ try:
+ next(wrap_controller) # first yield
+ except StopIteration:
+ _raise_wrapfail(wrap_controller, "did not yield")
+ call_outcome = _CallOutcome(func)
+ try:
+ wrap_controller.send(call_outcome)
+ _raise_wrapfail(wrap_controller, "has second yield")
+ except StopIteration:
+ pass
+ return call_outcome.get_result()
+
+
+class _CallOutcome:
+ """ Outcome of a function call, either an exception or a proper result.
+ Calling the ``get_result`` method will return the result or reraise
+ the exception raised when the function was called. """
+ excinfo = None
+
+ def __init__(self, func):
+ try:
+ self.result = func()
+ except BaseException:
+ self.excinfo = sys.exc_info()
+
+ def force_result(self, result):
+ self.result = result
+ self.excinfo = None
+
+ def get_result(self):
+ if self.excinfo is None:
+ return self.result
+ else:
+ ex = self.excinfo
+ if _py3:
+ raise ex[1].with_traceback(ex[2])
+ _reraise(*ex) # noqa
+
+if not _py3:
+ exec("""
+def _reraise(cls, val, tb):
+ raise cls, val, tb
+""")
+
+
+class _TracedHookExecution:
+ def __init__(self, pluginmanager, before, after):
+ self.pluginmanager = pluginmanager
+ self.before = before
+ self.after = after
+ self.oldcall = pluginmanager._inner_hookexec
+ assert not isinstance(self.oldcall, _TracedHookExecution)
+ self.pluginmanager._inner_hookexec = self
+
+ def __call__(self, hook, hook_impls, kwargs):
+ self.before(hook.name, hook_impls, kwargs)
+ outcome = _CallOutcome(lambda: self.oldcall(hook, hook_impls, kwargs))
+ self.after(outcome, hook.name, hook_impls, kwargs)
+ return outcome.get_result()
+
+ def undo(self):
+ self.pluginmanager._inner_hookexec = self.oldcall
+
+
+class PluginManager(object):
+ """ Core Pluginmanager class which manages registration
+ of plugin objects and 1:N hook calling.
+
+ You can register new hooks by calling ``add_hookspec(module_or_class)``.
+ You can register plugin objects (which contain hooks) by calling
+ ``register(plugin)``. The Pluginmanager is initialized with a
+ prefix that is searched for in the names of the dict of registered
+ plugin objects. An optional excludefunc allows to blacklist names which
+ are not considered as hooks despite a matching prefix.
+
+ For debugging purposes you can call ``enable_tracing()``
+ which will subsequently send debug information to the trace helper.
+ """
+
+ def __init__(self, project_name, implprefix=None):
+ """ if implprefix is given implementation functions
+ will be recognized if their name matches the implprefix. """
+ self.project_name = project_name
+ self._name2plugin = {}
+ self._plugin2hookcallers = {}
+ self._plugin_distinfo = []
+ self.trace = _TagTracer().get("pluginmanage")
+ self.hook = _HookRelay(self.trace.root.get("hook"))
+ self._implprefix = implprefix
+ self._inner_hookexec = lambda hook, methods, kwargs: \
+ _MultiCall(methods, kwargs, hook.spec_opts).execute()
+
+ def _hookexec(self, hook, methods, kwargs):
+ # called from all hookcaller instances.
+ # enable_tracing will set its own wrapping function at self._inner_hookexec
+ return self._inner_hookexec(hook, methods, kwargs)
+
+ def register(self, plugin, name=None):
+ """ Register a plugin and return its canonical name or None if the name
+ is blocked from registering. Raise a ValueError if the plugin is already
+ registered. """
+ plugin_name = name or self.get_canonical_name(plugin)
+
+ if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers:
+ if self._name2plugin.get(plugin_name, -1) is None:
+ return # blocked plugin, return None to indicate no registration
+ raise ValueError("Plugin already registered: %s=%s\n%s" %
+ (plugin_name, plugin, self._name2plugin))
+
+ # XXX if an error happens we should make sure no state has been
+ # changed at point of return
+ self._name2plugin[plugin_name] = plugin
+
+ # register matching hook implementations of the plugin
+ self._plugin2hookcallers[plugin] = hookcallers = []
+ for name in dir(plugin):
+ hookimpl_opts = self.parse_hookimpl_opts(plugin, name)
+ if hookimpl_opts is not None:
+ normalize_hookimpl_opts(hookimpl_opts)
+ method = getattr(plugin, name)
+ hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts)
+ hook = getattr(self.hook, name, None)
+ if hook is None:
+ hook = _HookCaller(name, self._hookexec)
+ setattr(self.hook, name, hook)
+ elif hook.has_spec():
+ self._verify_hook(hook, hookimpl)
+ hook._maybe_apply_history(hookimpl)
+ hook._add_hookimpl(hookimpl)
+ hookcallers.append(hook)
+ return plugin_name
+
+ def parse_hookimpl_opts(self, plugin, name):
+ method = getattr(plugin, name)
+ try:
+ res = getattr(method, self.project_name + "_impl", None)
+ except Exception:
+ res = {}
+ if res is not None and not isinstance(res, dict):
+ # false positive
+ res = None
+ elif res is None and self._implprefix and name.startswith(self._implprefix):
+ res = {}
+ return res
+
+ def unregister(self, plugin=None, name=None):
+ """ unregister a plugin object and all its contained hook implementations
+ from internal data structures. """
+ if name is None:
+ assert plugin is not None, "one of name or plugin needs to be specified"
+ name = self.get_name(plugin)
+
+ if plugin is None:
+ plugin = self.get_plugin(name)
+
+ # if self._name2plugin[name] == None registration was blocked: ignore
+ if self._name2plugin.get(name):
+ del self._name2plugin[name]
+
+ for hookcaller in self._plugin2hookcallers.pop(plugin, []):
+ hookcaller._remove_plugin(plugin)
+
+ return plugin
+
+ def set_blocked(self, name):
+ """ block registrations of the given name, unregister if already registered. """
+ self.unregister(name=name)
+ self._name2plugin[name] = None
+
+ def is_blocked(self, name):
+ """ return True if the name blogs registering plugins of that name. """
+ return name in self._name2plugin and self._name2plugin[name] is None
+
+ def add_hookspecs(self, module_or_class):
+ """ add new hook specifications defined in the given module_or_class.
+ Functions are recognized if they have been decorated accordingly. """
+ names = []
+ for name in dir(module_or_class):
+ spec_opts = self.parse_hookspec_opts(module_or_class, name)
+ if spec_opts is not None:
+ hc = getattr(self.hook, name, None)
+ if hc is None:
+ hc = _HookCaller(name, self._hookexec, module_or_class, spec_opts)
+ setattr(self.hook, name, hc)
+ else:
+ # plugins registered this hook without knowing the spec
+ hc.set_specification(module_or_class, spec_opts)
+ for hookfunction in (hc._wrappers + hc._nonwrappers):
+ self._verify_hook(hc, hookfunction)
+ names.append(name)
+
+ if not names:
+ raise ValueError("did not find any %r hooks in %r" %
+ (self.project_name, module_or_class))
+
+ def parse_hookspec_opts(self, module_or_class, name):
+ method = getattr(module_or_class, name)
+ return getattr(method, self.project_name + "_spec", None)
+
+ def get_plugins(self):
+ """ return the set of registered plugins. """
+ return set(self._plugin2hookcallers)
+
+ def is_registered(self, plugin):
+ """ Return True if the plugin is already registered. """
+ return plugin in self._plugin2hookcallers
+
+ def get_canonical_name(self, plugin):
+ """ Return canonical name for a plugin object. Note that a plugin
+ may be registered under a different name which was specified
+ by the caller of register(plugin, name). To obtain the name
+ of an registered plugin use ``get_name(plugin)`` instead."""
+ return getattr(plugin, "__name__", None) or str(id(plugin))
+
+ def get_plugin(self, name):
+ """ Return a plugin or None for the given name. """
+ return self._name2plugin.get(name)
+
+ def has_plugin(self, name):
+ """ Return True if a plugin with the given name is registered. """
+ return self.get_plugin(name) is not None
+
+ def get_name(self, plugin):
+ """ Return name for registered plugin or None if not registered. """
+ for name, val in self._name2plugin.items():
+ if plugin == val:
+ return name
+
+ def _verify_hook(self, hook, hookimpl):
+ if hook.is_historic() and hookimpl.hookwrapper:
+ raise PluginValidationError(
+ "Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" %
+ (hookimpl.plugin_name, hook.name))
+
+ for arg in hookimpl.argnames:
+ if arg not in hook.argnames:
+ raise PluginValidationError(
+ "Plugin %r\nhook %r\nargument %r not available\n"
+ "plugin definition: %s\n"
+ "available hookargs: %s" %
+ (hookimpl.plugin_name, hook.name, arg,
+ _formatdef(hookimpl.function), ", ".join(hook.argnames)))
+
+ def check_pending(self):
+ """ Verify that all hooks which have not been verified against
+ a hook specification are optional, otherwise raise PluginValidationError"""
+ for name in self.hook.__dict__:
+ if name[0] != "_":
+ hook = getattr(self.hook, name)
+ if not hook.has_spec():
+ for hookimpl in (hook._wrappers + hook._nonwrappers):
+ if not hookimpl.optionalhook:
+ raise PluginValidationError(
+ "unknown hook %r in plugin %r" %
+ (name, hookimpl.plugin))
+
+ def load_setuptools_entrypoints(self, entrypoint_name):
+ """ Load modules from querying the specified setuptools entrypoint name.
+ Return the number of loaded plugins. """
+ from pkg_resources import (iter_entry_points, DistributionNotFound,
+ VersionConflict)
+ for ep in iter_entry_points(entrypoint_name):
+ # is the plugin registered or blocked?
+ if self.get_plugin(ep.name) or self.is_blocked(ep.name):
+ continue
+ try:
+ plugin = ep.load()
+ except DistributionNotFound:
+ continue
+ except VersionConflict as e:
+ raise PluginValidationError(
+ "Plugin %r could not be loaded: %s!" % (ep.name, e))
+ self.register(plugin, name=ep.name)
+ self._plugin_distinfo.append((plugin, ep.dist))
+ return len(self._plugin_distinfo)
+
+ def list_plugin_distinfo(self):
+ """ return list of distinfo/plugin tuples for all setuptools registered
+ plugins. """
+ return list(self._plugin_distinfo)
+
+ def list_name_plugin(self):
+ """ return list of name/plugin pairs. """
+ return list(self._name2plugin.items())
+
+ def get_hookcallers(self, plugin):
+ """ get all hook callers for the specified plugin. """
+ return self._plugin2hookcallers.get(plugin)
+
+ def add_hookcall_monitoring(self, before, after):
+ """ add before/after tracing functions for all hooks
+ and return an undo function which, when called,
+ will remove the added tracers.
+
+ ``before(hook_name, hook_impls, kwargs)`` will be called ahead
+ of all hook calls and receive a hookcaller instance, a list
+ of HookImpl instances and the keyword arguments for the hook call.
+
+ ``after(outcome, hook_name, hook_impls, kwargs)`` receives the
+ same arguments as ``before`` but also a :py:class:`_CallOutcome`` object
+ which represents the result of the overall hook call.
+ """
+ return _TracedHookExecution(self, before, after).undo
+
+ def enable_tracing(self):
+ """ enable tracing of hook calls and return an undo function. """
+ hooktrace = self.hook._trace
+
+ def before(hook_name, methods, kwargs):
+ hooktrace.root.indent += 1
+ hooktrace(hook_name, kwargs)
+
+ def after(outcome, hook_name, methods, kwargs):
+ if outcome.excinfo is None:
+ hooktrace("finish", hook_name, "-->", outcome.result)
+ hooktrace.root.indent -= 1
+
+ return self.add_hookcall_monitoring(before, after)
+
+ def subset_hook_caller(self, name, remove_plugins):
+ """ Return a new _HookCaller instance for the named method
+ which manages calls to all registered plugins except the
+ ones from remove_plugins. """
+ orig = getattr(self.hook, name)
+ plugins_to_remove = [plug for plug in remove_plugins if hasattr(plug, name)]
+ if plugins_to_remove:
+ hc = _HookCaller(orig.name, orig._hookexec, orig._specmodule_or_class,
+ orig.spec_opts)
+ for hookimpl in (orig._wrappers + orig._nonwrappers):
+ plugin = hookimpl.plugin
+ if plugin not in plugins_to_remove:
+ hc._add_hookimpl(hookimpl)
+ # we also keep track of this hook caller so it
+ # gets properly removed on plugin unregistration
+ self._plugin2hookcallers.setdefault(plugin, []).append(hc)
+ return hc
+ return orig
+
+
+class _MultiCall:
+ """ execute a call into multiple python functions/methods. """
+
+ # XXX note that the __multicall__ argument is supported only
+ # for pytest compatibility reasons. It was never officially
+ # supported there and is explicitely deprecated since 2.8
+ # so we can remove it soon, allowing to avoid the below recursion
+ # in execute() and simplify/speed up the execute loop.
+
+ def __init__(self, hook_impls, kwargs, specopts={}):
+ self.hook_impls = hook_impls
+ self.kwargs = kwargs
+ self.kwargs["__multicall__"] = self
+ self.specopts = specopts
+
+ def execute(self):
+ all_kwargs = self.kwargs
+ self.results = results = []
+ firstresult = self.specopts.get("firstresult")
+
+ while self.hook_impls:
+ hook_impl = self.hook_impls.pop()
+ try:
+ args = [all_kwargs[argname] for argname in hook_impl.argnames]
+ except KeyError:
+ for argname in hook_impl.argnames:
+ if argname not in all_kwargs:
+ raise HookCallError(
+ "hook call must provide argument %r" % (argname,))
+ if hook_impl.hookwrapper:
+ return _wrapped_call(hook_impl.function(*args), self.execute)
+ res = hook_impl.function(*args)
+ if res is not None:
+ if firstresult:
+ return res
+ results.append(res)
+
+ if not firstresult:
+ return results
+
+ def __repr__(self):
+ status = "%d meths" % (len(self.hook_impls),)
+ if hasattr(self, "results"):
+ status = ("%d results, " % len(self.results)) + status
+ return "<_MultiCall %s, kwargs=%r>" % (status, self.kwargs)
+
+
+def varnames(func, startindex=None):
+ """ return argument name tuple for a function, method, class or callable.
+
+ In case of a class, its "__init__" method is considered.
+ For methods the "self" parameter is not included unless you are passing
+ an unbound method with Python3 (which has no supports for unbound methods)
+ """
+ cache = getattr(func, "__dict__", {})
+ try:
+ return cache["_varnames"]
+ except KeyError:
+ pass
+ if inspect.isclass(func):
+ try:
+ func = func.__init__
+ except AttributeError:
+ return ()
+ startindex = 1
+ else:
+ if not inspect.isfunction(func) and not inspect.ismethod(func):
+ try:
+ func = getattr(func, '__call__', func)
+ except Exception:
+ return ()
+ if startindex is None:
+ startindex = int(inspect.ismethod(func))
+
+ try:
+ rawcode = func.__code__
+ except AttributeError:
+ return ()
+ try:
+ x = rawcode.co_varnames[startindex:rawcode.co_argcount]
+ except AttributeError:
+ x = ()
+ else:
+ defaults = func.__defaults__
+ if defaults:
+ x = x[:-len(defaults)]
+ try:
+ cache["_varnames"] = x
+ except TypeError:
+ pass
+ return x
+
+
+class _HookRelay:
+ """ hook holder object for performing 1:N hook calls where N is the number
+ of registered plugins.
+
+ """
+
+ def __init__(self, trace):
+ self._trace = trace
+
+
+class _HookCaller(object):
+ def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None):
+ self.name = name
+ self._wrappers = []
+ self._nonwrappers = []
+ self._hookexec = hook_execute
+ if specmodule_or_class is not None:
+ assert spec_opts is not None
+ self.set_specification(specmodule_or_class, spec_opts)
+
+ def has_spec(self):
+ return hasattr(self, "_specmodule_or_class")
+
+ def set_specification(self, specmodule_or_class, spec_opts):
+ assert not self.has_spec()
+ self._specmodule_or_class = specmodule_or_class
+ specfunc = getattr(specmodule_or_class, self.name)
+ argnames = varnames(specfunc, startindex=inspect.isclass(specmodule_or_class))
+ assert "self" not in argnames # sanity check
+ self.argnames = ["__multicall__"] + list(argnames)
+ self.spec_opts = spec_opts
+ if spec_opts.get("historic"):
+ self._call_history = []
+
+ def is_historic(self):
+ return hasattr(self, "_call_history")
+
+ def _remove_plugin(self, plugin):
+ def remove(wrappers):
+ for i, method in enumerate(wrappers):
+ if method.plugin == plugin:
+ del wrappers[i]
+ return True
+ if remove(self._wrappers) is None:
+ if remove(self._nonwrappers) is None:
+ raise ValueError("plugin %r not found" % (plugin,))
+
+ def _add_hookimpl(self, hookimpl):
+ if hookimpl.hookwrapper:
+ methods = self._wrappers
+ else:
+ methods = self._nonwrappers
+
+ if hookimpl.trylast:
+ methods.insert(0, hookimpl)
+ elif hookimpl.tryfirst:
+ methods.append(hookimpl)
+ else:
+ # find last non-tryfirst method
+ i = len(methods) - 1
+ while i >= 0 and methods[i].tryfirst:
+ i -= 1
+ methods.insert(i + 1, hookimpl)
+
+ def __repr__(self):
+ return "<_HookCaller %r>" % (self.name,)
+
+ def __call__(self, **kwargs):
+ assert not self.is_historic()
+ return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
+
+ def call_historic(self, proc=None, kwargs=None):
+ self._call_history.append((kwargs or {}, proc))
+ # historizing hooks don't return results
+ self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
+
+ def call_extra(self, methods, kwargs):
+ """ Call the hook with some additional temporarily participating
+ methods using the specified kwargs as call parameters. """
+ old = list(self._nonwrappers), list(self._wrappers)
+ for method in methods:
+ opts = dict(hookwrapper=False, trylast=False, tryfirst=False)
+ hookimpl = HookImpl(None, "<temp>", method, opts)
+ self._add_hookimpl(hookimpl)
+ try:
+ return self(**kwargs)
+ finally:
+ self._nonwrappers, self._wrappers = old
+
+ def _maybe_apply_history(self, method):
+ if self.is_historic():
+ for kwargs, proc in self._call_history:
+ res = self._hookexec(self, [method], kwargs)
+ if res and proc is not None:
+ proc(res[0])
+
+
+class HookImpl:
+ def __init__(self, plugin, plugin_name, function, hook_impl_opts):
+ self.function = function
+ self.argnames = varnames(self.function)
+ self.plugin = plugin
+ self.opts = hook_impl_opts
+ self.plugin_name = plugin_name
+ self.__dict__.update(hook_impl_opts)
+
+
+class PluginValidationError(Exception):
+ """ plugin failed validation. """
+
+
+class HookCallError(Exception):
+ """ Hook was called wrongly. """
+
+
+if hasattr(inspect, 'signature'):
+ def _formatdef(func):
+ return "%s%s" % (
+ func.__name__,
+ str(inspect.signature(func))
+ )
+else:
+ def _formatdef(func):
+ return "%s%s" % (
+ func.__name__,
+ inspect.formatargspec(*inspect.getargspec(func))
+ )
diff --git a/lib/spack/external/nose/LICENSE b/lib/spack/external/nose/LICENSE
deleted file mode 100644
index 9f6e791624..0000000000
--- a/lib/spack/external/nose/LICENSE
+++ /dev/null
@@ -1,502 +0,0 @@
- GNU LESSER GENERAL PUBLIC LICENSE
- Version 2.1, February 1999
-
- Copyright (C) 1991, 1999 Free Software Foundation, Inc.
- 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-[This is the first released version of the Lesser GPL. It also counts
- as the successor of the GNU Library Public License, version 2, hence
- the version number 2.1.]
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-Licenses are intended to guarantee your freedom to share and change
-free software--to make sure the software is free for all its users.
-
- This license, the Lesser General Public License, applies to some
-specially designated software packages--typically libraries--of the
-Free Software Foundation and other authors who decide to use it. You
-can use it too, but we suggest you first think carefully about whether
-this license or the ordinary General Public License is the better
-strategy to use in any particular case, based on the explanations below.
-
- When we speak of free software, we are referring to freedom of use,
-not price. Our General Public Licenses are designed to make sure that
-you have the freedom to distribute copies of free software (and charge
-for this service if you wish); that you receive source code or can get
-it if you want it; that you can change the software and use pieces of
-it in new free programs; and that you are informed that you can do
-these things.
-
- To protect your rights, we need to make restrictions that forbid
-distributors to deny you these rights or to ask you to surrender these
-rights. These restrictions translate to certain responsibilities for
-you if you distribute copies of the library or if you modify it.
-
- For example, if you distribute copies of the library, whether gratis
-or for a fee, you must give the recipients all the rights that we gave
-you. You must make sure that they, too, receive or can get the source
-code. If you link other code with the library, you must provide
-complete object files to the recipients, so that they can relink them
-with the library after making changes to the library and recompiling
-it. And you must show them these terms so they know their rights.
-
- We protect your rights with a two-step method: (1) we copyright the
-library, and (2) we offer you this license, which gives you legal
-permission to copy, distribute and/or modify the library.
-
- To protect each distributor, we want to make it very clear that
-there is no warranty for the free library. Also, if the library is
-modified by someone else and passed on, the recipients should know
-that what they have is not the original version, so that the original
-author's reputation will not be affected by problems that might be
-introduced by others.
-
- Finally, software patents pose a constant threat to the existence of
-any free program. We wish to make sure that a company cannot
-effectively restrict the users of a free program by obtaining a
-restrictive license from a patent holder. Therefore, we insist that
-any patent license obtained for a version of the library must be
-consistent with the full freedom of use specified in this license.
-
- Most GNU software, including some libraries, is covered by the
-ordinary GNU General Public License. This license, the GNU Lesser
-General Public License, applies to certain designated libraries, and
-is quite different from the ordinary General Public License. We use
-this license for certain libraries in order to permit linking those
-libraries into non-free programs.
-
- When a program is linked with a library, whether statically or using
-a shared library, the combination of the two is legally speaking a
-combined work, a derivative of the original library. The ordinary
-General Public License therefore permits such linking only if the
-entire combination fits its criteria of freedom. The Lesser General
-Public License permits more lax criteria for linking other code with
-the library.
-
- We call this license the "Lesser" General Public License because it
-does Less to protect the user's freedom than the ordinary General
-Public License. It also provides other free software developers Less
-of an advantage over competing non-free programs. These disadvantages
-are the reason we use the ordinary General Public License for many
-libraries. However, the Lesser license provides advantages in certain
-special circumstances.
-
- For example, on rare occasions, there may be a special need to
-encourage the widest possible use of a certain library, so that it becomes
-a de-facto standard. To achieve this, non-free programs must be
-allowed to use the library. A more frequent case is that a free
-library does the same job as widely used non-free libraries. In this
-case, there is little to gain by limiting the free library to free
-software only, so we use the Lesser General Public License.
-
- In other cases, permission to use a particular library in non-free
-programs enables a greater number of people to use a large body of
-free software. For example, permission to use the GNU C Library in
-non-free programs enables many more people to use the whole GNU
-operating system, as well as its variant, the GNU/Linux operating
-system.
-
- Although the Lesser General Public License is Less protective of the
-users' freedom, it does ensure that the user of a program that is
-linked with the Library has the freedom and the wherewithal to run
-that program using a modified version of the Library.
-
- The precise terms and conditions for copying, distribution and
-modification follow. Pay close attention to the difference between a
-"work based on the library" and a "work that uses the library". The
-former contains code derived from the library, whereas the latter must
-be combined with the library in order to run.
-
- GNU LESSER GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License Agreement applies to any software library or other
-program which contains a notice placed by the copyright holder or
-other authorized party saying it may be distributed under the terms of
-this Lesser General Public License (also called "this License").
-Each licensee is addressed as "you".
-
- A "library" means a collection of software functions and/or data
-prepared so as to be conveniently linked with application programs
-(which use some of those functions and data) to form executables.
-
- The "Library", below, refers to any such software library or work
-which has been distributed under these terms. A "work based on the
-Library" means either the Library or any derivative work under
-copyright law: that is to say, a work containing the Library or a
-portion of it, either verbatim or with modifications and/or translated
-straightforwardly into another language. (Hereinafter, translation is
-included without limitation in the term "modification".)
-
- "Source code" for a work means the preferred form of the work for
-making modifications to it. For a library, complete source code means
-all the source code for all modules it contains, plus any associated
-interface definition files, plus the scripts used to control compilation
-and installation of the library.
-
- Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running a program using the Library is not restricted, and output from
-such a program is covered only if its contents constitute a work based
-on the Library (independent of the use of the Library in a tool for
-writing it). Whether that is true depends on what the Library does
-and what the program that uses the Library does.
-
- 1. You may copy and distribute verbatim copies of the Library's
-complete source code as you receive it, in any medium, provided that
-you conspicuously and appropriately publish on each copy an
-appropriate copyright notice and disclaimer of warranty; keep intact
-all the notices that refer to this License and to the absence of any
-warranty; and distribute a copy of this License along with the
-Library.
-
- You may charge a fee for the physical act of transferring a copy,
-and you may at your option offer warranty protection in exchange for a
-fee.
-
- 2. You may modify your copy or copies of the Library or any portion
-of it, thus forming a work based on the Library, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) The modified work must itself be a software library.
-
- b) You must cause the files modified to carry prominent notices
- stating that you changed the files and the date of any change.
-
- c) You must cause the whole of the work to be licensed at no
- charge to all third parties under the terms of this License.
-
- d) If a facility in the modified Library refers to a function or a
- table of data to be supplied by an application program that uses
- the facility, other than as an argument passed when the facility
- is invoked, then you must make a good faith effort to ensure that,
- in the event an application does not supply such function or
- table, the facility still operates, and performs whatever part of
- its purpose remains meaningful.
-
- (For example, a function in a library to compute square roots has
- a purpose that is entirely well-defined independent of the
- application. Therefore, Subsection 2d requires that any
- application-supplied function or table used by this function must
- be optional: if the application does not supply it, the square
- root function must still compute square roots.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Library,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Library, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote
-it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Library.
-
-In addition, mere aggregation of another work not based on the Library
-with the Library (or with a work based on the Library) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may opt to apply the terms of the ordinary GNU General Public
-License instead of this License to a given copy of the Library. To do
-this, you must alter all the notices that refer to this License, so
-that they refer to the ordinary GNU General Public License, version 2,
-instead of to this License. (If a newer version than version 2 of the
-ordinary GNU General Public License has appeared, then you can specify
-that version instead if you wish.) Do not make any other change in
-these notices.
-
- Once this change is made in a given copy, it is irreversible for
-that copy, so the ordinary GNU General Public License applies to all
-subsequent copies and derivative works made from that copy.
-
- This option is useful when you wish to copy part of the code of
-the Library into a program that is not a library.
-
- 4. You may copy and distribute the Library (or a portion or
-derivative of it, under Section 2) in object code or executable form
-under the terms of Sections 1 and 2 above provided that you accompany
-it with the complete corresponding machine-readable source code, which
-must be distributed under the terms of Sections 1 and 2 above on a
-medium customarily used for software interchange.
-
- If distribution of object code is made by offering access to copy
-from a designated place, then offering equivalent access to copy the
-source code from the same place satisfies the requirement to
-distribute the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 5. A program that contains no derivative of any portion of the
-Library, but is designed to work with the Library by being compiled or
-linked with it, is called a "work that uses the Library". Such a
-work, in isolation, is not a derivative work of the Library, and
-therefore falls outside the scope of this License.
-
- However, linking a "work that uses the Library" with the Library
-creates an executable that is a derivative of the Library (because it
-contains portions of the Library), rather than a "work that uses the
-library". The executable is therefore covered by this License.
-Section 6 states terms for distribution of such executables.
-
- When a "work that uses the Library" uses material from a header file
-that is part of the Library, the object code for the work may be a
-derivative work of the Library even though the source code is not.
-Whether this is true is especially significant if the work can be
-linked without the Library, or if the work is itself a library. The
-threshold for this to be true is not precisely defined by law.
-
- If such an object file uses only numerical parameters, data
-structure layouts and accessors, and small macros and small inline
-functions (ten lines or less in length), then the use of the object
-file is unrestricted, regardless of whether it is legally a derivative
-work. (Executables containing this object code plus portions of the
-Library will still fall under Section 6.)
-
- Otherwise, if the work is a derivative of the Library, you may
-distribute the object code for the work under the terms of Section 6.
-Any executables containing that work also fall under Section 6,
-whether or not they are linked directly with the Library itself.
-
- 6. As an exception to the Sections above, you may also combine or
-link a "work that uses the Library" with the Library to produce a
-work containing portions of the Library, and distribute that work
-under terms of your choice, provided that the terms permit
-modification of the work for the customer's own use and reverse
-engineering for debugging such modifications.
-
- You must give prominent notice with each copy of the work that the
-Library is used in it and that the Library and its use are covered by
-this License. You must supply a copy of this License. If the work
-during execution displays copyright notices, you must include the
-copyright notice for the Library among them, as well as a reference
-directing the user to the copy of this License. Also, you must do one
-of these things:
-
- a) Accompany the work with the complete corresponding
- machine-readable source code for the Library including whatever
- changes were used in the work (which must be distributed under
- Sections 1 and 2 above); and, if the work is an executable linked
- with the Library, with the complete machine-readable "work that
- uses the Library", as object code and/or source code, so that the
- user can modify the Library and then relink to produce a modified
- executable containing the modified Library. (It is understood
- that the user who changes the contents of definitions files in the
- Library will not necessarily be able to recompile the application
- to use the modified definitions.)
-
- b) Use a suitable shared library mechanism for linking with the
- Library. A suitable mechanism is one that (1) uses at run time a
- copy of the library already present on the user's computer system,
- rather than copying library functions into the executable, and (2)
- will operate properly with a modified version of the library, if
- the user installs one, as long as the modified version is
- interface-compatible with the version that the work was made with.
-
- c) Accompany the work with a written offer, valid for at
- least three years, to give the same user the materials
- specified in Subsection 6a, above, for a charge no more
- than the cost of performing this distribution.
-
- d) If distribution of the work is made by offering access to copy
- from a designated place, offer equivalent access to copy the above
- specified materials from the same place.
-
- e) Verify that the user has already received a copy of these
- materials or that you have already sent this user a copy.
-
- For an executable, the required form of the "work that uses the
-Library" must include any data and utility programs needed for
-reproducing the executable from it. However, as a special exception,
-the materials to be distributed need not include anything that is
-normally distributed (in either source or binary form) with the major
-components (compiler, kernel, and so on) of the operating system on
-which the executable runs, unless that component itself accompanies
-the executable.
-
- It may happen that this requirement contradicts the license
-restrictions of other proprietary libraries that do not normally
-accompany the operating system. Such a contradiction means you cannot
-use both them and the Library together in an executable that you
-distribute.
-
- 7. You may place library facilities that are a work based on the
-Library side-by-side in a single library together with other library
-facilities not covered by this License, and distribute such a combined
-library, provided that the separate distribution of the work based on
-the Library and of the other library facilities is otherwise
-permitted, and provided that you do these two things:
-
- a) Accompany the combined library with a copy of the same work
- based on the Library, uncombined with any other library
- facilities. This must be distributed under the terms of the
- Sections above.
-
- b) Give prominent notice with the combined library of the fact
- that part of it is a work based on the Library, and explaining
- where to find the accompanying uncombined form of the same work.
-
- 8. You may not copy, modify, sublicense, link with, or distribute
-the Library except as expressly provided under this License. Any
-attempt otherwise to copy, modify, sublicense, link with, or
-distribute the Library is void, and will automatically terminate your
-rights under this License. However, parties who have received copies,
-or rights, from you under this License will not have their licenses
-terminated so long as such parties remain in full compliance.
-
- 9. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Library or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Library (or any work based on the
-Library), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Library or works based on it.
-
- 10. Each time you redistribute the Library (or any work based on the
-Library), the recipient automatically receives a license from the
-original licensor to copy, distribute, link with or modify the Library
-subject to these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties with
-this License.
-
- 11. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Library at all. For example, if a patent
-license would not permit royalty-free redistribution of the Library by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Library.
-
-If any portion of this section is held invalid or unenforceable under any
-particular circumstance, the balance of the section is intended to apply,
-and the section as a whole is intended to apply in other circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 12. If the distribution and/or use of the Library is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Library under this License may add
-an explicit geographical distribution limitation excluding those countries,
-so that distribution is permitted only in or among countries not thus
-excluded. In such case, this License incorporates the limitation as if
-written in the body of this License.
-
- 13. The Free Software Foundation may publish revised and/or new
-versions of the Lesser General Public License from time to time.
-Such new versions will be similar in spirit to the present version,
-but may differ in detail to address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Library
-specifies a version number of this License which applies to it and
-"any later version", you have the option of following the terms and
-conditions either of that version or of any later version published by
-the Free Software Foundation. If the Library does not specify a
-license version number, you may choose any version ever published by
-the Free Software Foundation.
-
- 14. If you wish to incorporate parts of the Library into other free
-programs whose distribution conditions are incompatible with these,
-write to the author to ask for permission. For software which is
-copyrighted by the Free Software Foundation, write to the Free
-Software Foundation; we sometimes make exceptions for this. Our
-decision will be guided by the two goals of preserving the free status
-of all derivatives of our free software and of promoting the sharing
-and reuse of software generally.
-
- NO WARRANTY
-
- 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
-WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
-EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
-OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
-KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
-LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
-THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
-WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
-AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
-FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
-CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
-LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
-RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
-FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
-SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Libraries
-
- If you develop a new library, and you want it to be of the greatest
-possible use to the public, we recommend making it free software that
-everyone can redistribute and change. You can do so by permitting
-redistribution under these terms (or, alternatively, under the terms of the
-ordinary General Public License).
-
- To apply these terms, attach the following notices to the library. It is
-safest to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least the
-"copyright" line and a pointer to where the full notice is found.
-
- <one line to give the library's name and a brief idea of what it does.>
- Copyright (C) <year> <name of author>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-Also add information on how to contact you by electronic and paper mail.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the library, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the
- library `Frob' (a library for tweaking knobs) written by James Random Hacker.
-
- <signature of Ty Coon>, 1 April 1990
- Ty Coon, President of Vice
-
-That's all there is to it!
diff --git a/lib/spack/external/nose/__init__.py b/lib/spack/external/nose/__init__.py
deleted file mode 100644
index 1ae1362b7a..0000000000
--- a/lib/spack/external/nose/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from nose.core import collector, main, run, run_exit, runmodule
-# backwards compatibility
-from nose.exc import SkipTest, DeprecatedTest
-from nose.tools import with_setup
-
-__author__ = 'Jason Pellerin'
-__versioninfo__ = (1, 3, 7)
-__version__ = '.'.join(map(str, __versioninfo__))
-
-__all__ = [
- 'main', 'run', 'run_exit', 'runmodule', 'with_setup',
- 'SkipTest', 'DeprecatedTest', 'collector'
- ]
-
-
diff --git a/lib/spack/external/nose/__main__.py b/lib/spack/external/nose/__main__.py
deleted file mode 100644
index b402d9df12..0000000000
--- a/lib/spack/external/nose/__main__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import sys
-
-from nose.core import run_exit
-
-if sys.argv[0].endswith('__main__.py'):
- sys.argv[0] = '%s -m nose' % sys.executable
-
-run_exit()
diff --git a/lib/spack/external/nose/case.py b/lib/spack/external/nose/case.py
deleted file mode 100644
index cffa4ab4c9..0000000000
--- a/lib/spack/external/nose/case.py
+++ /dev/null
@@ -1,397 +0,0 @@
-"""nose unittest.TestCase subclasses. It is not necessary to subclass these
-classes when writing tests; they are used internally by nose.loader.TestLoader
-to create test cases from test functions and methods in test classes.
-"""
-import logging
-import sys
-import unittest
-from inspect import isfunction
-from nose.config import Config
-from nose.failure import Failure # for backwards compatibility
-from nose.util import resolve_name, test_address, try_run
-
-log = logging.getLogger(__name__)
-
-
-__all__ = ['Test']
-
-
-class Test(unittest.TestCase):
- """The universal test case wrapper.
-
- When a plugin sees a test, it will always see an instance of this
- class. To access the actual test case that will be run, access the
- test property of the nose.case.Test instance.
- """
- __test__ = False # do not collect
- def __init__(self, test, config=None, resultProxy=None):
- # sanity check
- if not callable(test):
- raise TypeError("nose.case.Test called with argument %r that "
- "is not callable. A callable is required."
- % test)
- self.test = test
- if config is None:
- config = Config()
- self.config = config
- self.tbinfo = None
- self.capturedOutput = None
- self.resultProxy = resultProxy
- self.plugins = config.plugins
- self.passed = None
- unittest.TestCase.__init__(self)
-
- def __call__(self, *arg, **kwarg):
- return self.run(*arg, **kwarg)
-
- def __str__(self):
- name = self.plugins.testName(self)
- if name is not None:
- return name
- return str(self.test)
-
- def __repr__(self):
- return "Test(%r)" % self.test
-
- def afterTest(self, result):
- """Called after test is complete (after result.stopTest)
- """
- try:
- afterTest = result.afterTest
- except AttributeError:
- pass
- else:
- afterTest(self.test)
-
- def beforeTest(self, result):
- """Called before test is run (before result.startTest)
- """
- try:
- beforeTest = result.beforeTest
- except AttributeError:
- pass
- else:
- beforeTest(self.test)
-
- def exc_info(self):
- """Extract exception info.
- """
- exc, exv, tb = sys.exc_info()
- return (exc, exv, tb)
-
- def id(self):
- """Get a short(er) description of the test
- """
- return self.test.id()
-
- def address(self):
- """Return a round-trip name for this test, a name that can be
- fed back as input to loadTestByName and (assuming the same
- plugin configuration) result in the loading of this test.
- """
- if hasattr(self.test, 'address'):
- return self.test.address()
- else:
- # not a nose case
- return test_address(self.test)
-
- def _context(self):
- try:
- return self.test.context
- except AttributeError:
- pass
- try:
- return self.test.__class__
- except AttributeError:
- pass
- try:
- return resolve_name(self.test.__module__)
- except AttributeError:
- pass
- return None
- context = property(_context, None, None,
- """Get the context object of this test (if any).""")
-
- def run(self, result):
- """Modified run for the test wrapper.
-
- From here we don't call result.startTest or stopTest or
- addSuccess. The wrapper calls addError/addFailure only if its
- own setup or teardown fails, or running the wrapped test fails
- (eg, if the wrapped "test" is not callable).
-
- Two additional methods are called, beforeTest and
- afterTest. These give plugins a chance to modify the wrapped
- test before it is called and do cleanup after it is
- called. They are called unconditionally.
- """
- if self.resultProxy:
- result = self.resultProxy(result, self)
- try:
- try:
- self.beforeTest(result)
- self.runTest(result)
- except KeyboardInterrupt:
- raise
- except:
- err = sys.exc_info()
- result.addError(self, err)
- finally:
- self.afterTest(result)
-
- def runTest(self, result):
- """Run the test. Plugins may alter the test by returning a
- value from prepareTestCase. The value must be callable and
- must accept one argument, the result instance.
- """
- test = self.test
- plug_test = self.config.plugins.prepareTestCase(self)
- if plug_test is not None:
- test = plug_test
- test(result)
-
- def shortDescription(self):
- desc = self.plugins.describeTest(self)
- if desc is not None:
- return desc
- # work around bug in unittest.TestCase.shortDescription
- # with multiline docstrings.
- test = self.test
- try:
- test._testMethodDoc = test._testMethodDoc.strip()# 2.5
- except AttributeError:
- try:
- # 2.4 and earlier
- test._TestCase__testMethodDoc = \
- test._TestCase__testMethodDoc.strip()
- except AttributeError:
- pass
- # 2.7 compat: shortDescription() always returns something
- # which is a change from 2.6 and below, and breaks the
- # testName plugin call.
- try:
- desc = self.test.shortDescription()
- except Exception:
- # this is probably caused by a problem in test.__str__() and is
- # only triggered by python 3.1's unittest!
- pass
- try:
- if desc == str(self.test):
- return
- except Exception:
- # If str() triggers an exception then ignore it.
- # see issue 422
- pass
- return desc
-
-
-class TestBase(unittest.TestCase):
- """Common functionality for FunctionTestCase and MethodTestCase.
- """
- __test__ = False # do not collect
-
- def id(self):
- return str(self)
-
- def runTest(self):
- self.test(*self.arg)
-
- def shortDescription(self):
- if hasattr(self.test, 'description'):
- return self.test.description
- func, arg = self._descriptors()
- doc = getattr(func, '__doc__', None)
- if not doc:
- doc = str(self)
- return doc.strip().split("\n")[0].strip()
-
-
-class FunctionTestCase(TestBase):
- """TestCase wrapper for test functions.
-
- Don't use this class directly; it is used internally in nose to
- create test cases for test functions.
- """
- __test__ = False # do not collect
-
- def __init__(self, test, setUp=None, tearDown=None, arg=tuple(),
- descriptor=None):
- """Initialize the MethodTestCase.
-
- Required argument:
-
- * test -- the test function to call.
-
- Optional arguments:
-
- * setUp -- function to run at setup.
-
- * tearDown -- function to run at teardown.
-
- * arg -- arguments to pass to the test function. This is to support
- generator functions that yield arguments.
-
- * descriptor -- the function, other than the test, that should be used
- to construct the test name. This is to support generator functions.
- """
-
- self.test = test
- self.setUpFunc = setUp
- self.tearDownFunc = tearDown
- self.arg = arg
- self.descriptor = descriptor
- TestBase.__init__(self)
-
- def address(self):
- """Return a round-trip name for this test, a name that can be
- fed back as input to loadTestByName and (assuming the same
- plugin configuration) result in the loading of this test.
- """
- if self.descriptor is not None:
- return test_address(self.descriptor)
- else:
- return test_address(self.test)
-
- def _context(self):
- return resolve_name(self.test.__module__)
- context = property(_context, None, None,
- """Get context (module) of this test""")
-
- def setUp(self):
- """Run any setup function attached to the test function
- """
- if self.setUpFunc:
- self.setUpFunc()
- else:
- names = ('setup', 'setUp', 'setUpFunc')
- try_run(self.test, names)
-
- def tearDown(self):
- """Run any teardown function attached to the test function
- """
- if self.tearDownFunc:
- self.tearDownFunc()
- else:
- names = ('teardown', 'tearDown', 'tearDownFunc')
- try_run(self.test, names)
-
- def __str__(self):
- func, arg = self._descriptors()
- if hasattr(func, 'compat_func_name'):
- name = func.compat_func_name
- else:
- name = func.__name__
- name = "%s.%s" % (func.__module__, name)
- if arg:
- name = "%s%s" % (name, arg)
- # FIXME need to include the full dir path to disambiguate
- # in cases where test module of the same name was seen in
- # another directory (old fromDirectory)
- return name
- __repr__ = __str__
-
- def _descriptors(self):
- """Get the descriptors of the test function: the function and
- arguments that will be used to construct the test name. In
- most cases, this is the function itself and no arguments. For
- tests generated by generator functions, the original
- (generator) function and args passed to the generated function
- are returned.
- """
- if self.descriptor:
- return self.descriptor, self.arg
- else:
- return self.test, self.arg
-
-
-class MethodTestCase(TestBase):
- """Test case wrapper for test methods.
-
- Don't use this class directly; it is used internally in nose to
- create test cases for test methods.
- """
- __test__ = False # do not collect
-
- def __init__(self, method, test=None, arg=tuple(), descriptor=None):
- """Initialize the MethodTestCase.
-
- Required argument:
-
- * method -- the method to call, may be bound or unbound. In either
- case, a new instance of the method's class will be instantiated to
- make the call. Note: In Python 3.x, if using an unbound method, you
- must wrap it using pyversion.unbound_method.
-
- Optional arguments:
-
- * test -- the test function to call. If this is passed, it will be
- called instead of getting a new bound method of the same name as the
- desired method from the test instance. This is to support generator
- methods that yield inline functions.
-
- * arg -- arguments to pass to the test function. This is to support
- generator methods that yield arguments.
-
- * descriptor -- the function, other than the test, that should be used
- to construct the test name. This is to support generator methods.
- """
- self.method = method
- self.test = test
- self.arg = arg
- self.descriptor = descriptor
- if isfunction(method):
- raise ValueError("Unbound methods must be wrapped using pyversion.unbound_method before passing to MethodTestCase")
- self.cls = method.im_class
- self.inst = self.cls()
- if self.test is None:
- method_name = self.method.__name__
- self.test = getattr(self.inst, method_name)
- TestBase.__init__(self)
-
- def __str__(self):
- func, arg = self._descriptors()
- if hasattr(func, 'compat_func_name'):
- name = func.compat_func_name
- else:
- name = func.__name__
- name = "%s.%s.%s" % (self.cls.__module__,
- self.cls.__name__,
- name)
- if arg:
- name = "%s%s" % (name, arg)
- return name
- __repr__ = __str__
-
- def address(self):
- """Return a round-trip name for this test, a name that can be
- fed back as input to loadTestByName and (assuming the same
- plugin configuration) result in the loading of this test.
- """
- if self.descriptor is not None:
- return test_address(self.descriptor)
- else:
- return test_address(self.method)
-
- def _context(self):
- return self.cls
- context = property(_context, None, None,
- """Get context (class) of this test""")
-
- def setUp(self):
- try_run(self.inst, ('setup', 'setUp'))
-
- def tearDown(self):
- try_run(self.inst, ('teardown', 'tearDown'))
-
- def _descriptors(self):
- """Get the descriptors of the test method: the method and
- arguments that will be used to construct the test name. In
- most cases, this is the method itself and no arguments. For
- tests generated by generator methods, the original
- (generator) method and args passed to the generated method
- or function are returned.
- """
- if self.descriptor:
- return self.descriptor, self.arg
- else:
- return self.method, self.arg
diff --git a/lib/spack/external/nose/commands.py b/lib/spack/external/nose/commands.py
deleted file mode 100644
index ef0e9caed4..0000000000
--- a/lib/spack/external/nose/commands.py
+++ /dev/null
@@ -1,172 +0,0 @@
-"""
-nosetests setuptools command
-----------------------------
-
-The easiest way to run tests with nose is to use the `nosetests` setuptools
-command::
-
- python setup.py nosetests
-
-This command has one *major* benefit over the standard `test` command: *all
-nose plugins are supported*.
-
-To configure the `nosetests` command, add a [nosetests] section to your
-setup.cfg. The [nosetests] section can contain any command line arguments that
-nosetests supports. The differences between issuing an option on the command
-line and adding it to setup.cfg are:
-
-* In setup.cfg, the -- prefix must be excluded
-* In setup.cfg, command line flags that take no arguments must be given an
- argument flag (1, T or TRUE for active, 0, F or FALSE for inactive)
-
-Here's an example [nosetests] setup.cfg section::
-
- [nosetests]
- verbosity=1
- detailed-errors=1
- with-coverage=1
- cover-package=nose
- debug=nose.loader
- pdb=1
- pdb-failures=1
-
-If you commonly run nosetests with a large number of options, using
-the nosetests setuptools command and configuring with setup.cfg can
-make running your tests much less tedious. (Note that the same options
-and format supported in setup.cfg are supported in all other config
-files, and the nosetests script will also load config files.)
-
-Another reason to run tests with the command is that the command will
-install packages listed in your `tests_require`, as well as doing a
-complete build of your package before running tests. For packages with
-dependencies or that build C extensions, using the setuptools command
-can be more convenient than building by hand and running the nosetests
-script.
-
-Bootstrapping
--------------
-
-If you are distributing your project and want users to be able to run tests
-without having to install nose themselves, add nose to the setup_requires
-section of your setup()::
-
- setup(
- # ...
- setup_requires=['nose>=1.0']
- )
-
-This will direct setuptools to download and activate nose during the setup
-process, making the ``nosetests`` command available.
-
-"""
-try:
- from setuptools import Command
-except ImportError:
- Command = nosetests = None
-else:
- from nose.config import Config, option_blacklist, user_config_files, \
- flag, _bool
- from nose.core import TestProgram
- from nose.plugins import DefaultPluginManager
-
-
- def get_user_options(parser):
- """convert a optparse option list into a distutils option tuple list"""
- opt_list = []
- for opt in parser.option_list:
- if opt._long_opts[0][2:] in option_blacklist:
- continue
- long_name = opt._long_opts[0][2:]
- if opt.action not in ('store_true', 'store_false'):
- long_name = long_name + "="
- short_name = None
- if opt._short_opts:
- short_name = opt._short_opts[0][1:]
- opt_list.append((long_name, short_name, opt.help or ""))
- return opt_list
-
-
- class nosetests(Command):
- description = "Run unit tests using nosetests"
- __config = Config(files=user_config_files(),
- plugins=DefaultPluginManager())
- __parser = __config.getParser()
- user_options = get_user_options(__parser)
-
- def initialize_options(self):
- """create the member variables, but change hyphens to
- underscores
- """
-
- self.option_to_cmds = {}
- for opt in self.__parser.option_list:
- cmd_name = opt._long_opts[0][2:]
- option_name = cmd_name.replace('-', '_')
- self.option_to_cmds[option_name] = cmd_name
- setattr(self, option_name, None)
- self.attr = None
-
- def finalize_options(self):
- """nothing to do here"""
- pass
-
- def run(self):
- """ensure tests are capable of being run, then
- run nose.main with a reconstructed argument list"""
- if getattr(self.distribution, 'use_2to3', False):
- # If we run 2to3 we can not do this inplace:
-
- # Ensure metadata is up-to-date
- build_py = self.get_finalized_command('build_py')
- build_py.inplace = 0
- build_py.run()
- bpy_cmd = self.get_finalized_command("build_py")
- build_path = bpy_cmd.build_lib
-
- # Build extensions
- egg_info = self.get_finalized_command('egg_info')
- egg_info.egg_base = build_path
- egg_info.run()
-
- build_ext = self.get_finalized_command('build_ext')
- build_ext.inplace = 0
- build_ext.run()
- else:
- self.run_command('egg_info')
-
- # Build extensions in-place
- build_ext = self.get_finalized_command('build_ext')
- build_ext.inplace = 1
- build_ext.run()
-
- if self.distribution.install_requires:
- self.distribution.fetch_build_eggs(
- self.distribution.install_requires)
- if self.distribution.tests_require:
- self.distribution.fetch_build_eggs(
- self.distribution.tests_require)
-
- ei_cmd = self.get_finalized_command("egg_info")
- argv = ['nosetests', '--where', ei_cmd.egg_base]
- for (option_name, cmd_name) in self.option_to_cmds.items():
- if option_name in option_blacklist:
- continue
- value = getattr(self, option_name)
- if value is not None:
- argv.extend(
- self.cfgToArg(option_name.replace('_', '-'), value))
- TestProgram(argv=argv, config=self.__config)
-
- def cfgToArg(self, optname, value):
- argv = []
- long_optname = '--' + optname
- opt = self.__parser.get_option(long_optname)
- if opt.action in ('store_true', 'store_false'):
- if not flag(value):
- raise ValueError("Invalid value '%s' for '%s'" % (
- value, optname))
- if _bool(value):
- argv.append(long_optname)
- else:
- argv.extend([long_optname, value])
- return argv
diff --git a/lib/spack/external/nose/config.py b/lib/spack/external/nose/config.py
deleted file mode 100644
index 125eb5579d..0000000000
--- a/lib/spack/external/nose/config.py
+++ /dev/null
@@ -1,661 +0,0 @@
-import logging
-import optparse
-import os
-import re
-import sys
-import ConfigParser
-from optparse import OptionParser
-from nose.util import absdir, tolist
-from nose.plugins.manager import NoPlugins
-from warnings import warn, filterwarnings
-
-log = logging.getLogger(__name__)
-
-# not allowed in config files
-option_blacklist = ['help', 'verbose']
-
-config_files = [
- # Linux users will prefer this
- "~/.noserc",
- # Windows users will prefer this
- "~/nose.cfg"
- ]
-
-# plaforms on which the exe check defaults to off
-# Windows and IronPython
-exe_allowed_platforms = ('win32', 'cli')
-
-filterwarnings("always", category=DeprecationWarning,
- module=r'(.*\.)?nose\.config')
-
-class NoSuchOptionError(Exception):
- def __init__(self, name):
- Exception.__init__(self, name)
- self.name = name
-
-
-class ConfigError(Exception):
- pass
-
-
-class ConfiguredDefaultsOptionParser(object):
- """
- Handler for options from commandline and config files.
- """
- def __init__(self, parser, config_section, error=None, file_error=None):
- self._parser = parser
- self._config_section = config_section
- if error is None:
- error = self._parser.error
- self._error = error
- if file_error is None:
- file_error = lambda msg, **kw: error(msg)
- self._file_error = file_error
-
- def _configTuples(self, cfg, filename):
- config = []
- if self._config_section in cfg.sections():
- for name, value in cfg.items(self._config_section):
- config.append((name, value, filename))
- return config
-
- def _readFromFilenames(self, filenames):
- config = []
- for filename in filenames:
- cfg = ConfigParser.RawConfigParser()
- try:
- cfg.read(filename)
- except ConfigParser.Error, exc:
- raise ConfigError("Error reading config file %r: %s" %
- (filename, str(exc)))
- config.extend(self._configTuples(cfg, filename))
- return config
-
- def _readFromFileObject(self, fh):
- cfg = ConfigParser.RawConfigParser()
- try:
- filename = fh.name
- except AttributeError:
- filename = '<???>'
- try:
- cfg.readfp(fh)
- except ConfigParser.Error, exc:
- raise ConfigError("Error reading config file %r: %s" %
- (filename, str(exc)))
- return self._configTuples(cfg, filename)
-
- def _readConfiguration(self, config_files):
- try:
- config_files.readline
- except AttributeError:
- filename_or_filenames = config_files
- if isinstance(filename_or_filenames, basestring):
- filenames = [filename_or_filenames]
- else:
- filenames = filename_or_filenames
- config = self._readFromFilenames(filenames)
- else:
- fh = config_files
- config = self._readFromFileObject(fh)
- return config
-
- def _processConfigValue(self, name, value, values, parser):
- opt_str = '--' + name
- option = parser.get_option(opt_str)
- if option is None:
- raise NoSuchOptionError(name)
- else:
- option.process(opt_str, value, values, parser)
-
- def _applyConfigurationToValues(self, parser, config, values):
- for name, value, filename in config:
- if name in option_blacklist:
- continue
- try:
- self._processConfigValue(name, value, values, parser)
- except NoSuchOptionError, exc:
- self._file_error(
- "Error reading config file %r: "
- "no such option %r" % (filename, exc.name),
- name=name, filename=filename)
- except optparse.OptionValueError, exc:
- msg = str(exc).replace('--' + name, repr(name), 1)
- self._file_error("Error reading config file %r: "
- "%s" % (filename, msg),
- name=name, filename=filename)
-
- def parseArgsAndConfigFiles(self, args, config_files):
- values = self._parser.get_default_values()
- try:
- config = self._readConfiguration(config_files)
- except ConfigError, exc:
- self._error(str(exc))
- else:
- try:
- self._applyConfigurationToValues(self._parser, config, values)
- except ConfigError, exc:
- self._error(str(exc))
- return self._parser.parse_args(args, values)
-
-
-class Config(object):
- """nose configuration.
-
- Instances of Config are used throughout nose to configure
- behavior, including plugin lists. Here are the default values for
- all config keys::
-
- self.env = env = kw.pop('env', {})
- self.args = ()
- self.testMatch = re.compile(r'(?:^|[\\b_\\.%s-])[Tt]est' % os.sep)
- self.addPaths = not env.get('NOSE_NOPATH', False)
- self.configSection = 'nosetests'
- self.debug = env.get('NOSE_DEBUG')
- self.debugLog = env.get('NOSE_DEBUG_LOG')
- self.exclude = None
- self.getTestCaseNamesCompat = False
- self.includeExe = env.get('NOSE_INCLUDE_EXE',
- sys.platform in exe_allowed_platforms)
- self.ignoreFiles = (re.compile(r'^\.'),
- re.compile(r'^_'),
- re.compile(r'^setup\.py$')
- )
- self.include = None
- self.loggingConfig = None
- self.logStream = sys.stderr
- self.options = NoOptions()
- self.parser = None
- self.plugins = NoPlugins()
- self.srcDirs = ('lib', 'src')
- self.runOnInit = True
- self.stopOnError = env.get('NOSE_STOP', False)
- self.stream = sys.stderr
- self.testNames = ()
- self.verbosity = int(env.get('NOSE_VERBOSE', 1))
- self.where = ()
- self.py3where = ()
- self.workingDir = None
- """
-
- def __init__(self, **kw):
- self.env = env = kw.pop('env', {})
- self.args = ()
- self.testMatchPat = env.get('NOSE_TESTMATCH',
- r'(?:^|[\b_\.%s-])[Tt]est' % os.sep)
- self.testMatch = re.compile(self.testMatchPat)
- self.addPaths = not env.get('NOSE_NOPATH', False)
- self.configSection = 'nosetests'
- self.debug = env.get('NOSE_DEBUG')
- self.debugLog = env.get('NOSE_DEBUG_LOG')
- self.exclude = None
- self.getTestCaseNamesCompat = False
- self.includeExe = env.get('NOSE_INCLUDE_EXE',
- sys.platform in exe_allowed_platforms)
- self.ignoreFilesDefaultStrings = [r'^\.',
- r'^_',
- r'^setup\.py$',
- ]
- self.ignoreFiles = map(re.compile, self.ignoreFilesDefaultStrings)
- self.include = None
- self.loggingConfig = None
- self.logStream = sys.stderr
- self.options = NoOptions()
- self.parser = None
- self.plugins = NoPlugins()
- self.srcDirs = ('lib', 'src')
- self.runOnInit = True
- self.stopOnError = env.get('NOSE_STOP', False)
- self.stream = sys.stderr
- self.testNames = []
- self.verbosity = int(env.get('NOSE_VERBOSE', 1))
- self.where = ()
- self.py3where = ()
- self.workingDir = os.getcwd()
- self.traverseNamespace = False
- self.firstPackageWins = False
- self.parserClass = OptionParser
- self.worker = False
-
- self._default = self.__dict__.copy()
- self.update(kw)
- self._orig = self.__dict__.copy()
-
- def __getstate__(self):
- state = self.__dict__.copy()
- del state['stream']
- del state['_orig']
- del state['_default']
- del state['env']
- del state['logStream']
- # FIXME remove plugins, have only plugin manager class
- state['plugins'] = self.plugins.__class__
- return state
-
- def __setstate__(self, state):
- plugincls = state.pop('plugins')
- self.update(state)
- self.worker = True
- # FIXME won't work for static plugin lists
- self.plugins = plugincls()
- self.plugins.loadPlugins()
- # needed so .can_configure gets set appropriately
- dummy_parser = self.parserClass()
- self.plugins.addOptions(dummy_parser, {})
- self.plugins.configure(self.options, self)
-
- def __repr__(self):
- d = self.__dict__.copy()
- # don't expose env, could include sensitive info
- d['env'] = {}
- keys = [ k for k in d.keys()
- if not k.startswith('_') ]
- keys.sort()
- return "Config(%s)" % ', '.join([ '%s=%r' % (k, d[k])
- for k in keys ])
- __str__ = __repr__
-
- def _parseArgs(self, argv, cfg_files):
- def warn_sometimes(msg, name=None, filename=None):
- if (hasattr(self.plugins, 'excludedOption') and
- self.plugins.excludedOption(name)):
- msg = ("Option %r in config file %r ignored: "
- "excluded by runtime environment" %
- (name, filename))
- warn(msg, RuntimeWarning)
- else:
- raise ConfigError(msg)
- parser = ConfiguredDefaultsOptionParser(
- self.getParser(), self.configSection, file_error=warn_sometimes)
- return parser.parseArgsAndConfigFiles(argv[1:], cfg_files)
-
- def configure(self, argv=None, doc=None):
- """Configure the nose running environment. Execute configure before
- collecting tests with nose.TestCollector to enable output capture and
- other features.
- """
- env = self.env
- if argv is None:
- argv = sys.argv
-
- cfg_files = getattr(self, 'files', [])
- options, args = self._parseArgs(argv, cfg_files)
- # If -c --config has been specified on command line,
- # load those config files and reparse
- if getattr(options, 'files', []):
- options, args = self._parseArgs(argv, options.files)
-
- self.options = options
- if args:
- self.testNames = args
- if options.testNames is not None:
- self.testNames.extend(tolist(options.testNames))
-
- if options.py3where is not None:
- if sys.version_info >= (3,):
- options.where = options.py3where
-
- # `where` is an append action, so it can't have a default value
- # in the parser, or that default will always be in the list
- if not options.where:
- options.where = env.get('NOSE_WHERE', None)
-
- # include and exclude also
- if not options.ignoreFiles:
- options.ignoreFiles = env.get('NOSE_IGNORE_FILES', [])
- if not options.include:
- options.include = env.get('NOSE_INCLUDE', [])
- if not options.exclude:
- options.exclude = env.get('NOSE_EXCLUDE', [])
-
- self.addPaths = options.addPaths
- self.stopOnError = options.stopOnError
- self.verbosity = options.verbosity
- self.includeExe = options.includeExe
- self.traverseNamespace = options.traverseNamespace
- self.debug = options.debug
- self.debugLog = options.debugLog
- self.loggingConfig = options.loggingConfig
- self.firstPackageWins = options.firstPackageWins
- self.configureLogging()
-
- if not options.byteCompile:
- sys.dont_write_bytecode = True
-
- if options.where is not None:
- self.configureWhere(options.where)
-
- if options.testMatch:
- self.testMatch = re.compile(options.testMatch)
-
- if options.ignoreFiles:
- self.ignoreFiles = map(re.compile, tolist(options.ignoreFiles))
- log.info("Ignoring files matching %s", options.ignoreFiles)
- else:
- log.info("Ignoring files matching %s", self.ignoreFilesDefaultStrings)
-
- if options.include:
- self.include = map(re.compile, tolist(options.include))
- log.info("Including tests matching %s", options.include)
-
- if options.exclude:
- self.exclude = map(re.compile, tolist(options.exclude))
- log.info("Excluding tests matching %s", options.exclude)
-
- # When listing plugins we don't want to run them
- if not options.showPlugins:
- self.plugins.configure(options, self)
- self.plugins.begin()
-
- def configureLogging(self):
- """Configure logging for nose, or optionally other packages. Any logger
- name may be set with the debug option, and that logger will be set to
- debug level and be assigned the same handler as the nose loggers, unless
- it already has a handler.
- """
- if self.loggingConfig:
- from logging.config import fileConfig
- fileConfig(self.loggingConfig)
- return
-
- format = logging.Formatter('%(name)s: %(levelname)s: %(message)s')
- if self.debugLog:
- handler = logging.FileHandler(self.debugLog)
- else:
- handler = logging.StreamHandler(self.logStream)
- handler.setFormatter(format)
-
- logger = logging.getLogger('nose')
- logger.propagate = 0
-
- # only add our default handler if there isn't already one there
- # this avoids annoying duplicate log messages.
- found = False
- if self.debugLog:
- debugLogAbsPath = os.path.abspath(self.debugLog)
- for h in logger.handlers:
- if type(h) == logging.FileHandler and \
- h.baseFilename == debugLogAbsPath:
- found = True
- else:
- for h in logger.handlers:
- if type(h) == logging.StreamHandler and \
- h.stream == self.logStream:
- found = True
- if not found:
- logger.addHandler(handler)
-
- # default level
- lvl = logging.WARNING
- if self.verbosity >= 5:
- lvl = 0
- elif self.verbosity >= 4:
- lvl = logging.DEBUG
- elif self.verbosity >= 3:
- lvl = logging.INFO
- logger.setLevel(lvl)
-
- # individual overrides
- if self.debug:
- # no blanks
- debug_loggers = [ name for name in self.debug.split(',')
- if name ]
- for logger_name in debug_loggers:
- l = logging.getLogger(logger_name)
- l.setLevel(logging.DEBUG)
- if not l.handlers and not logger_name.startswith('nose'):
- l.addHandler(handler)
-
- def configureWhere(self, where):
- """Configure the working directory or directories for the test run.
- """
- from nose.importer import add_path
- self.workingDir = None
- where = tolist(where)
- warned = False
- for path in where:
- if not self.workingDir:
- abs_path = absdir(path)
- if abs_path is None:
- raise ValueError("Working directory '%s' not found, or "
- "not a directory" % path)
- log.info("Set working dir to %s", abs_path)
- self.workingDir = abs_path
- if self.addPaths and \
- os.path.exists(os.path.join(abs_path, '__init__.py')):
- log.info("Working directory %s is a package; "
- "adding to sys.path" % abs_path)
- add_path(abs_path)
- continue
- if not warned:
- warn("Use of multiple -w arguments is deprecated and "
- "support may be removed in a future release. You can "
- "get the same behavior by passing directories without "
- "the -w argument on the command line, or by using the "
- "--tests argument in a configuration file.",
- DeprecationWarning)
- warned = True
- self.testNames.append(path)
-
- def default(self):
- """Reset all config values to defaults.
- """
- self.__dict__.update(self._default)
-
- def getParser(self, doc=None):
- """Get the command line option parser.
- """
- if self.parser:
- return self.parser
- env = self.env
- parser = self.parserClass(doc)
- parser.add_option(
- "-V","--version", action="store_true",
- dest="version", default=False,
- help="Output nose version and exit")
- parser.add_option(
- "-p", "--plugins", action="store_true",
- dest="showPlugins", default=False,
- help="Output list of available plugins and exit. Combine with "
- "higher verbosity for greater detail")
- parser.add_option(
- "-v", "--verbose",
- action="count", dest="verbosity",
- default=self.verbosity,
- help="Be more verbose. [NOSE_VERBOSE]")
- parser.add_option(
- "--verbosity", action="store", dest="verbosity",
- metavar='VERBOSITY',
- type="int", help="Set verbosity; --verbosity=2 is "
- "the same as -v")
- parser.add_option(
- "-q", "--quiet", action="store_const", const=0, dest="verbosity",
- help="Be less verbose")
- parser.add_option(
- "-c", "--config", action="append", dest="files",
- metavar="FILES",
- help="Load configuration from config file(s). May be specified "
- "multiple times; in that case, all config files will be "
- "loaded and combined")
- parser.add_option(
- "-w", "--where", action="append", dest="where",
- metavar="WHERE",
- help="Look for tests in this directory. "
- "May be specified multiple times. The first directory passed "
- "will be used as the working directory, in place of the current "
- "working directory, which is the default. Others will be added "
- "to the list of tests to execute. [NOSE_WHERE]"
- )
- parser.add_option(
- "--py3where", action="append", dest="py3where",
- metavar="PY3WHERE",
- help="Look for tests in this directory under Python 3.x. "
- "Functions the same as 'where', but only applies if running under "
- "Python 3.x or above. Note that, if present under 3.x, this "
- "option completely replaces any directories specified with "
- "'where', so the 'where' option becomes ineffective. "
- "[NOSE_PY3WHERE]"
- )
- parser.add_option(
- "-m", "--match", "--testmatch", action="store",
- dest="testMatch", metavar="REGEX",
- help="Files, directories, function names, and class names "
- "that match this regular expression are considered tests. "
- "Default: %s [NOSE_TESTMATCH]" % self.testMatchPat,
- default=self.testMatchPat)
- parser.add_option(
- "--tests", action="store", dest="testNames", default=None,
- metavar='NAMES',
- help="Run these tests (comma-separated list). This argument is "
- "useful mainly from configuration files; on the command line, "
- "just pass the tests to run as additional arguments with no "
- "switch.")
- parser.add_option(
- "-l", "--debug", action="store",
- dest="debug", default=self.debug,
- help="Activate debug logging for one or more systems. "
- "Available debug loggers: nose, nose.importer, "
- "nose.inspector, nose.plugins, nose.result and "
- "nose.selector. Separate multiple names with a comma.")
- parser.add_option(
- "--debug-log", dest="debugLog", action="store",
- default=self.debugLog, metavar="FILE",
- help="Log debug messages to this file "
- "(default: sys.stderr)")
- parser.add_option(
- "--logging-config", "--log-config",
- dest="loggingConfig", action="store",
- default=self.loggingConfig, metavar="FILE",
- help="Load logging config from this file -- bypasses all other"
- " logging config settings.")
- parser.add_option(
- "-I", "--ignore-files", action="append", dest="ignoreFiles",
- metavar="REGEX",
- help="Completely ignore any file that matches this regular "
- "expression. Takes precedence over any other settings or "
- "plugins. "
- "Specifying this option will replace the default setting. "
- "Specify this option multiple times "
- "to add more regular expressions [NOSE_IGNORE_FILES]")
- parser.add_option(
- "-e", "--exclude", action="append", dest="exclude",
- metavar="REGEX",
- help="Don't run tests that match regular "
- "expression [NOSE_EXCLUDE]")
- parser.add_option(
- "-i", "--include", action="append", dest="include",
- metavar="REGEX",
- help="This regular expression will be applied to files, "
- "directories, function names, and class names for a chance "
- "to include additional tests that do not match TESTMATCH. "
- "Specify this option multiple times "
- "to add more regular expressions [NOSE_INCLUDE]")
- parser.add_option(
- "-x", "--stop", action="store_true", dest="stopOnError",
- default=self.stopOnError,
- help="Stop running tests after the first error or failure")
- parser.add_option(
- "-P", "--no-path-adjustment", action="store_false",
- dest="addPaths",
- default=self.addPaths,
- help="Don't make any changes to sys.path when "
- "loading tests [NOSE_NOPATH]")
- parser.add_option(
- "--exe", action="store_true", dest="includeExe",
- default=self.includeExe,
- help="Look for tests in python modules that are "
- "executable. Normal behavior is to exclude executable "
- "modules, since they may not be import-safe "
- "[NOSE_INCLUDE_EXE]")
- parser.add_option(
- "--noexe", action="store_false", dest="includeExe",
- help="DO NOT look for tests in python modules that are "
- "executable. (The default on the windows platform is to "
- "do so.)")
- parser.add_option(
- "--traverse-namespace", action="store_true",
- default=self.traverseNamespace, dest="traverseNamespace",
- help="Traverse through all path entries of a namespace package")
- parser.add_option(
- "--first-package-wins", "--first-pkg-wins", "--1st-pkg-wins",
- action="store_true", default=False, dest="firstPackageWins",
- help="nose's importer will normally evict a package from sys."
- "modules if it sees a package with the same name in a different "
- "location. Set this option to disable that behavior.")
- parser.add_option(
- "--no-byte-compile",
- action="store_false", default=True, dest="byteCompile",
- help="Prevent nose from byte-compiling the source into .pyc files "
- "while nose is scanning for and running tests.")
-
- self.plugins.loadPlugins()
- self.pluginOpts(parser)
-
- self.parser = parser
- return parser
-
- def help(self, doc=None):
- """Return the generated help message
- """
- return self.getParser(doc).format_help()
-
- def pluginOpts(self, parser):
- self.plugins.addOptions(parser, self.env)
-
- def reset(self):
- self.__dict__.update(self._orig)
-
- def todict(self):
- return self.__dict__.copy()
-
- def update(self, d):
- self.__dict__.update(d)
-
-
-class NoOptions(object):
- """Options container that returns None for all options.
- """
- def __getstate__(self):
- return {}
-
- def __setstate__(self, state):
- pass
-
- def __getnewargs__(self):
- return ()
-
- def __nonzero__(self):
- return False
-
-
-def user_config_files():
- """Return path to any existing user config files
- """
- return filter(os.path.exists,
- map(os.path.expanduser, config_files))
-
-
-def all_config_files():
- """Return path to any existing user config files, plus any setup.cfg
- in the current working directory.
- """
- user = user_config_files()
- if os.path.exists('setup.cfg'):
- return user + ['setup.cfg']
- return user
-
-
-# used when parsing config files
-def flag(val):
- """Does the value look like an on/off flag?"""
- if val == 1:
- return True
- elif val == 0:
- return False
- val = str(val)
- if len(val) > 5:
- return False
- return val.upper() in ('1', '0', 'F', 'T', 'TRUE', 'FALSE', 'ON', 'OFF')
-
-
-def _bool(val):
- return str(val).upper() in ('1', 'T', 'TRUE', 'ON')
diff --git a/lib/spack/external/nose/core.py b/lib/spack/external/nose/core.py
deleted file mode 100644
index 49e7939b98..0000000000
--- a/lib/spack/external/nose/core.py
+++ /dev/null
@@ -1,341 +0,0 @@
-"""Implements nose test program and collector.
-"""
-from __future__ import generators
-
-import logging
-import os
-import sys
-import time
-import unittest
-
-from nose.config import Config, all_config_files
-from nose.loader import defaultTestLoader
-from nose.plugins.manager import PluginManager, DefaultPluginManager, \
- RestrictedPluginManager
-from nose.result import TextTestResult
-from nose.suite import FinalizingSuiteWrapper
-from nose.util import isclass, tolist
-
-
-log = logging.getLogger('nose.core')
-compat_24 = sys.version_info >= (2, 4)
-
-__all__ = ['TestProgram', 'main', 'run', 'run_exit', 'runmodule', 'collector',
- 'TextTestRunner']
-
-
-class TextTestRunner(unittest.TextTestRunner):
- """Test runner that uses nose's TextTestResult to enable errorClasses,
- as well as providing hooks for plugins to override or replace the test
- output stream, results, and the test case itself.
- """
- def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1,
- config=None):
- if config is None:
- config = Config()
- self.config = config
- unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity)
-
-
- def _makeResult(self):
- return TextTestResult(self.stream,
- self.descriptions,
- self.verbosity,
- self.config)
-
- def run(self, test):
- """Overrides to provide plugin hooks and defer all output to
- the test result class.
- """
- wrapper = self.config.plugins.prepareTest(test)
- if wrapper is not None:
- test = wrapper
-
- # plugins can decorate or capture the output stream
- wrapped = self.config.plugins.setOutputStream(self.stream)
- if wrapped is not None:
- self.stream = wrapped
-
- result = self._makeResult()
- start = time.time()
- try:
- test(result)
- except KeyboardInterrupt:
- pass
- stop = time.time()
- result.printErrors()
- result.printSummary(start, stop)
- self.config.plugins.finalize(result)
- return result
-
-
-class TestProgram(unittest.TestProgram):
- """Collect and run tests, returning success or failure.
-
- The arguments to TestProgram() are the same as to
- :func:`main()` and :func:`run()`:
-
- * module: All tests are in this module (default: None)
- * defaultTest: Tests to load (default: '.')
- * argv: Command line arguments (default: None; sys.argv is read)
- * testRunner: Test runner instance (default: None)
- * testLoader: Test loader instance (default: None)
- * env: Environment; ignored if config is provided (default: None;
- os.environ is read)
- * config: :class:`nose.config.Config` instance (default: None)
- * suite: Suite or list of tests to run (default: None). Passing a
- suite or lists of tests will bypass all test discovery and
- loading. *ALSO NOTE* that if you pass a unittest.TestSuite
- instance as the suite, context fixtures at the class, module and
- package level will not be used, and many plugin hooks will not
- be called. If you want normal nose behavior, either pass a list
- of tests, or a fully-configured :class:`nose.suite.ContextSuite`.
- * exit: Exit after running tests and printing report (default: True)
- * plugins: List of plugins to use; ignored if config is provided
- (default: load plugins with DefaultPluginManager)
- * addplugins: List of **extra** plugins to use. Pass a list of plugin
- instances in this argument to make custom plugins available while
- still using the DefaultPluginManager.
- """
- verbosity = 1
-
- def __init__(self, module=None, defaultTest='.', argv=None,
- testRunner=None, testLoader=None, env=None, config=None,
- suite=None, exit=True, plugins=None, addplugins=None):
- if env is None:
- env = os.environ
- if config is None:
- config = self.makeConfig(env, plugins)
- if addplugins:
- config.plugins.addPlugins(extraplugins=addplugins)
- self.config = config
- self.suite = suite
- self.exit = exit
- extra_args = {}
- version = sys.version_info[0:2]
- if version >= (2,7) and version != (3,0):
- extra_args['exit'] = exit
- unittest.TestProgram.__init__(
- self, module=module, defaultTest=defaultTest,
- argv=argv, testRunner=testRunner, testLoader=testLoader,
- **extra_args)
-
- def getAllConfigFiles(self, env=None):
- env = env or {}
- if env.get('NOSE_IGNORE_CONFIG_FILES', False):
- return []
- else:
- return all_config_files()
-
- def makeConfig(self, env, plugins=None):
- """Load a Config, pre-filled with user config files if any are
- found.
- """
- cfg_files = self.getAllConfigFiles(env)
- if plugins:
- manager = PluginManager(plugins=plugins)
- else:
- manager = DefaultPluginManager()
- return Config(
- env=env, files=cfg_files, plugins=manager)
-
- def parseArgs(self, argv):
- """Parse argv and env and configure running environment.
- """
- self.config.configure(argv, doc=self.usage())
- log.debug("configured %s", self.config)
-
- # quick outs: version, plugins (optparse would have already
- # caught and exited on help)
- if self.config.options.version:
- from nose import __version__
- sys.stdout = sys.__stdout__
- print "%s version %s" % (os.path.basename(sys.argv[0]), __version__)
- sys.exit(0)
-
- if self.config.options.showPlugins:
- self.showPlugins()
- sys.exit(0)
-
- if self.testLoader is None:
- self.testLoader = defaultTestLoader(config=self.config)
- elif isclass(self.testLoader):
- self.testLoader = self.testLoader(config=self.config)
- plug_loader = self.config.plugins.prepareTestLoader(self.testLoader)
- if plug_loader is not None:
- self.testLoader = plug_loader
- log.debug("test loader is %s", self.testLoader)
-
- # FIXME if self.module is a string, add it to self.testNames? not sure
-
- if self.config.testNames:
- self.testNames = self.config.testNames
- else:
- self.testNames = tolist(self.defaultTest)
- log.debug('defaultTest %s', self.defaultTest)
- log.debug('Test names are %s', self.testNames)
- if self.config.workingDir is not None:
- os.chdir(self.config.workingDir)
- self.createTests()
-
- def createTests(self):
- """Create the tests to run. If a self.suite
- is set, then that suite will be used. Otherwise, tests will be
- loaded from the given test names (self.testNames) using the
- test loader.
- """
- log.debug("createTests called with %s", self.suite)
- if self.suite is not None:
- # We were given an explicit suite to run. Make sure it's
- # loaded and wrapped correctly.
- self.test = self.testLoader.suiteClass(self.suite)
- else:
- self.test = self.testLoader.loadTestsFromNames(self.testNames)
-
- def runTests(self):
- """Run Tests. Returns true on success, false on failure, and sets
- self.success to the same value.
- """
- log.debug("runTests called")
- if self.testRunner is None:
- self.testRunner = TextTestRunner(stream=self.config.stream,
- verbosity=self.config.verbosity,
- config=self.config)
- plug_runner = self.config.plugins.prepareTestRunner(self.testRunner)
- if plug_runner is not None:
- self.testRunner = plug_runner
- result = self.testRunner.run(self.test)
- self.success = result.wasSuccessful()
- if self.exit:
- sys.exit(not self.success)
- return self.success
-
- def showPlugins(self):
- """Print list of available plugins.
- """
- import textwrap
-
- class DummyParser:
- def __init__(self):
- self.options = []
- def add_option(self, *arg, **kw):
- self.options.append((arg, kw.pop('help', '')))
-
- v = self.config.verbosity
- self.config.plugins.sort()
- for p in self.config.plugins:
- print "Plugin %s" % p.name
- if v >= 2:
- print " score: %s" % p.score
- print '\n'.join(textwrap.wrap(p.help().strip(),
- initial_indent=' ',
- subsequent_indent=' '))
- if v >= 3:
- parser = DummyParser()
- p.addOptions(parser)
- if len(parser.options):
- print
- print " Options:"
- for opts, help in parser.options:
- print ' %s' % (', '.join(opts))
- if help:
- print '\n'.join(
- textwrap.wrap(help.strip(),
- initial_indent=' ',
- subsequent_indent=' '))
- print
-
- def usage(cls):
- import nose
- try:
- ld = nose.__loader__
- text = ld.get_data(os.path.join(
- os.path.dirname(__file__), 'usage.txt'))
- except AttributeError:
- f = open(os.path.join(
- os.path.dirname(__file__), 'usage.txt'), 'r')
- try:
- text = f.read()
- finally:
- f.close()
- # Ensure that we return str, not bytes.
- if not isinstance(text, str):
- text = text.decode('utf-8')
- return text
- usage = classmethod(usage)
-
-# backwards compatibility
-run_exit = main = TestProgram
-
-
-def run(*arg, **kw):
- """Collect and run tests, returning success or failure.
-
- The arguments to `run()` are the same as to `main()`:
-
- * module: All tests are in this module (default: None)
- * defaultTest: Tests to load (default: '.')
- * argv: Command line arguments (default: None; sys.argv is read)
- * testRunner: Test runner instance (default: None)
- * testLoader: Test loader instance (default: None)
- * env: Environment; ignored if config is provided (default: None;
- os.environ is read)
- * config: :class:`nose.config.Config` instance (default: None)
- * suite: Suite or list of tests to run (default: None). Passing a
- suite or lists of tests will bypass all test discovery and
- loading. *ALSO NOTE* that if you pass a unittest.TestSuite
- instance as the suite, context fixtures at the class, module and
- package level will not be used, and many plugin hooks will not
- be called. If you want normal nose behavior, either pass a list
- of tests, or a fully-configured :class:`nose.suite.ContextSuite`.
- * plugins: List of plugins to use; ignored if config is provided
- (default: load plugins with DefaultPluginManager)
- * addplugins: List of **extra** plugins to use. Pass a list of plugin
- instances in this argument to make custom plugins available while
- still using the DefaultPluginManager.
-
- With the exception that the ``exit`` argument is always set
- to False.
- """
- kw['exit'] = False
- return TestProgram(*arg, **kw).success
-
-
-def runmodule(name='__main__', **kw):
- """Collect and run tests in a single module only. Defaults to running
- tests in __main__. Additional arguments to TestProgram may be passed
- as keyword arguments.
- """
- main(defaultTest=name, **kw)
-
-
-def collector():
- """TestSuite replacement entry point. Use anywhere you might use a
- unittest.TestSuite. The collector will, by default, load options from
- all config files and execute loader.loadTestsFromNames() on the
- configured testNames, or '.' if no testNames are configured.
- """
- # plugins that implement any of these methods are disabled, since
- # we don't control the test runner and won't be able to run them
- # finalize() is also not called, but plugins that use it aren't disabled,
- # because capture needs it.
- setuptools_incompat = ('report', 'prepareTest',
- 'prepareTestLoader', 'prepareTestRunner',
- 'setOutputStream')
-
- plugins = RestrictedPluginManager(exclude=setuptools_incompat)
- conf = Config(files=all_config_files(),
- plugins=plugins)
- conf.configure(argv=['collector'])
- loader = defaultTestLoader(conf)
-
- if conf.testNames:
- suite = loader.loadTestsFromNames(conf.testNames)
- else:
- suite = loader.loadTestsFromNames(('.',))
- return FinalizingSuiteWrapper(suite, plugins.finalize)
-
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/spack/external/nose/exc.py b/lib/spack/external/nose/exc.py
deleted file mode 100644
index 8b780db0d4..0000000000
--- a/lib/spack/external/nose/exc.py
+++ /dev/null
@@ -1,9 +0,0 @@
-"""Exceptions for marking tests as skipped or deprecated.
-
-This module exists to provide backwards compatibility with previous
-versions of nose where skipped and deprecated tests were core
-functionality, rather than being provided by plugins. It may be
-removed in a future release.
-"""
-from nose.plugins.skip import SkipTest
-from nose.plugins.deprecated import DeprecatedTest
diff --git a/lib/spack/external/nose/ext/__init__.py b/lib/spack/external/nose/ext/__init__.py
deleted file mode 100644
index 5fd1516a09..0000000000
--- a/lib/spack/external/nose/ext/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-External or vendor files
-"""
diff --git a/lib/spack/external/nose/ext/dtcompat.py b/lib/spack/external/nose/ext/dtcompat.py
deleted file mode 100644
index 332cf08c12..0000000000
--- a/lib/spack/external/nose/ext/dtcompat.py
+++ /dev/null
@@ -1,2272 +0,0 @@
-# Module doctest.
-# Released to the public domain 16-Jan-2001, by Tim Peters (tim@python.org).
-# Major enhancements and refactoring by:
-# Jim Fulton
-# Edward Loper
-
-# Provided as-is; use at your own risk; no warranty; no promises; enjoy!
-#
-# Modified for inclusion in nose to provide support for DocFileTest in
-# python 2.3:
-#
-# - all doctests removed from module (they fail under 2.3 and 2.5)
-# - now handles the $py.class extension when ran under Jython
-
-r"""Module doctest -- a framework for running examples in docstrings.
-
-In simplest use, end each module M to be tested with:
-
-def _test():
- import doctest
- doctest.testmod()
-
-if __name__ == "__main__":
- _test()
-
-Then running the module as a script will cause the examples in the
-docstrings to get executed and verified:
-
-python M.py
-
-This won't display anything unless an example fails, in which case the
-failing example(s) and the cause(s) of the failure(s) are printed to stdout
-(why not stderr? because stderr is a lame hack <0.2 wink>), and the final
-line of output is "Test failed.".
-
-Run it with the -v switch instead:
-
-python M.py -v
-
-and a detailed report of all examples tried is printed to stdout, along
-with assorted summaries at the end.
-
-You can force verbose mode by passing "verbose=True" to testmod, or prohibit
-it by passing "verbose=False". In either of those cases, sys.argv is not
-examined by testmod.
-
-There are a variety of other ways to run doctests, including integration
-with the unittest framework, and support for running non-Python text
-files containing doctests. There are also many ways to override parts
-of doctest's default behaviors. See the Library Reference Manual for
-details.
-"""
-
-__docformat__ = 'reStructuredText en'
-
-__all__ = [
- # 0, Option Flags
- 'register_optionflag',
- 'DONT_ACCEPT_TRUE_FOR_1',
- 'DONT_ACCEPT_BLANKLINE',
- 'NORMALIZE_WHITESPACE',
- 'ELLIPSIS',
- 'IGNORE_EXCEPTION_DETAIL',
- 'COMPARISON_FLAGS',
- 'REPORT_UDIFF',
- 'REPORT_CDIFF',
- 'REPORT_NDIFF',
- 'REPORT_ONLY_FIRST_FAILURE',
- 'REPORTING_FLAGS',
- # 1. Utility Functions
- 'is_private',
- # 2. Example & DocTest
- 'Example',
- 'DocTest',
- # 3. Doctest Parser
- 'DocTestParser',
- # 4. Doctest Finder
- 'DocTestFinder',
- # 5. Doctest Runner
- 'DocTestRunner',
- 'OutputChecker',
- 'DocTestFailure',
- 'UnexpectedException',
- 'DebugRunner',
- # 6. Test Functions
- 'testmod',
- 'testfile',
- 'run_docstring_examples',
- # 7. Tester
- 'Tester',
- # 8. Unittest Support
- 'DocTestSuite',
- 'DocFileSuite',
- 'set_unittest_reportflags',
- # 9. Debugging Support
- 'script_from_examples',
- 'testsource',
- 'debug_src',
- 'debug',
-]
-
-import __future__
-
-import sys, traceback, inspect, linecache, os, re
-import unittest, difflib, pdb, tempfile
-import warnings
-from StringIO import StringIO
-
-# Don't whine about the deprecated is_private function in this
-# module's tests.
-warnings.filterwarnings("ignore", "is_private", DeprecationWarning,
- __name__, 0)
-
-# There are 4 basic classes:
-# - Example: a <source, want> pair, plus an intra-docstring line number.
-# - DocTest: a collection of examples, parsed from a docstring, plus
-# info about where the docstring came from (name, filename, lineno).
-# - DocTestFinder: extracts DocTests from a given object's docstring and
-# its contained objects' docstrings.
-# - DocTestRunner: runs DocTest cases, and accumulates statistics.
-#
-# So the basic picture is:
-#
-# list of:
-# +------+ +---------+ +-------+
-# |object| --DocTestFinder-> | DocTest | --DocTestRunner-> |results|
-# +------+ +---------+ +-------+
-# | Example |
-# | ... |
-# | Example |
-# +---------+
-
-# Option constants.
-
-OPTIONFLAGS_BY_NAME = {}
-def register_optionflag(name):
- # Create a new flag unless `name` is already known.
- return OPTIONFLAGS_BY_NAME.setdefault(name, 1 << len(OPTIONFLAGS_BY_NAME))
-
-DONT_ACCEPT_TRUE_FOR_1 = register_optionflag('DONT_ACCEPT_TRUE_FOR_1')
-DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE')
-NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE')
-ELLIPSIS = register_optionflag('ELLIPSIS')
-IGNORE_EXCEPTION_DETAIL = register_optionflag('IGNORE_EXCEPTION_DETAIL')
-
-COMPARISON_FLAGS = (DONT_ACCEPT_TRUE_FOR_1 |
- DONT_ACCEPT_BLANKLINE |
- NORMALIZE_WHITESPACE |
- ELLIPSIS |
- IGNORE_EXCEPTION_DETAIL)
-
-REPORT_UDIFF = register_optionflag('REPORT_UDIFF')
-REPORT_CDIFF = register_optionflag('REPORT_CDIFF')
-REPORT_NDIFF = register_optionflag('REPORT_NDIFF')
-REPORT_ONLY_FIRST_FAILURE = register_optionflag('REPORT_ONLY_FIRST_FAILURE')
-
-REPORTING_FLAGS = (REPORT_UDIFF |
- REPORT_CDIFF |
- REPORT_NDIFF |
- REPORT_ONLY_FIRST_FAILURE)
-
-# Special string markers for use in `want` strings:
-BLANKLINE_MARKER = '<BLANKLINE>'
-ELLIPSIS_MARKER = '...'
-
-######################################################################
-## Table of Contents
-######################################################################
-# 1. Utility Functions
-# 2. Example & DocTest -- store test cases
-# 3. DocTest Parser -- extracts examples from strings
-# 4. DocTest Finder -- extracts test cases from objects
-# 5. DocTest Runner -- runs test cases
-# 6. Test Functions -- convenient wrappers for testing
-# 7. Tester Class -- for backwards compatibility
-# 8. Unittest Support
-# 9. Debugging Support
-# 10. Example Usage
-
-######################################################################
-## 1. Utility Functions
-######################################################################
-
-def is_private(prefix, base):
- """prefix, base -> true iff name prefix + "." + base is "private".
-
- Prefix may be an empty string, and base does not contain a period.
- Prefix is ignored (although functions you write conforming to this
- protocol may make use of it).
- Return true iff base begins with an (at least one) underscore, but
- does not both begin and end with (at least) two underscores.
- """
- warnings.warn("is_private is deprecated; it wasn't useful; "
- "examine DocTestFinder.find() lists instead",
- DeprecationWarning, stacklevel=2)
- return base[:1] == "_" and not base[:2] == "__" == base[-2:]
-
-def _extract_future_flags(globs):
- """
- Return the compiler-flags associated with the future features that
- have been imported into the given namespace (globs).
- """
- flags = 0
- for fname in __future__.all_feature_names:
- feature = globs.get(fname, None)
- if feature is getattr(__future__, fname):
- flags |= feature.compiler_flag
- return flags
-
-def _normalize_module(module, depth=2):
- """
- Return the module specified by `module`. In particular:
- - If `module` is a module, then return module.
- - If `module` is a string, then import and return the
- module with that name.
- - If `module` is None, then return the calling module.
- The calling module is assumed to be the module of
- the stack frame at the given depth in the call stack.
- """
- if inspect.ismodule(module):
- return module
- elif isinstance(module, (str, unicode)):
- return __import__(module, globals(), locals(), ["*"])
- elif module is None:
- return sys.modules[sys._getframe(depth).f_globals['__name__']]
- else:
- raise TypeError("Expected a module, string, or None")
-
-def _indent(s, indent=4):
- """
- Add the given number of space characters to the beginning every
- non-blank line in `s`, and return the result.
- """
- # This regexp matches the start of non-blank lines:
- return re.sub('(?m)^(?!$)', indent*' ', s)
-
-def _exception_traceback(exc_info):
- """
- Return a string containing a traceback message for the given
- exc_info tuple (as returned by sys.exc_info()).
- """
- # Get a traceback message.
- excout = StringIO()
- exc_type, exc_val, exc_tb = exc_info
- traceback.print_exception(exc_type, exc_val, exc_tb, file=excout)
- return excout.getvalue()
-
-# Override some StringIO methods.
-class _SpoofOut(StringIO):
- def getvalue(self):
- result = StringIO.getvalue(self)
- # If anything at all was written, make sure there's a trailing
- # newline. There's no way for the expected output to indicate
- # that a trailing newline is missing.
- if result and not result.endswith("\n"):
- result += "\n"
- # Prevent softspace from screwing up the next test case, in
- # case they used print with a trailing comma in an example.
- if hasattr(self, "softspace"):
- del self.softspace
- return result
-
- def truncate(self, size=None):
- StringIO.truncate(self, size)
- if hasattr(self, "softspace"):
- del self.softspace
-
-# Worst-case linear-time ellipsis matching.
-def _ellipsis_match(want, got):
- if ELLIPSIS_MARKER not in want:
- return want == got
-
- # Find "the real" strings.
- ws = want.split(ELLIPSIS_MARKER)
- assert len(ws) >= 2
-
- # Deal with exact matches possibly needed at one or both ends.
- startpos, endpos = 0, len(got)
- w = ws[0]
- if w: # starts with exact match
- if got.startswith(w):
- startpos = len(w)
- del ws[0]
- else:
- return False
- w = ws[-1]
- if w: # ends with exact match
- if got.endswith(w):
- endpos -= len(w)
- del ws[-1]
- else:
- return False
-
- if startpos > endpos:
- # Exact end matches required more characters than we have, as in
- # _ellipsis_match('aa...aa', 'aaa')
- return False
-
- # For the rest, we only need to find the leftmost non-overlapping
- # match for each piece. If there's no overall match that way alone,
- # there's no overall match period.
- for w in ws:
- # w may be '' at times, if there are consecutive ellipses, or
- # due to an ellipsis at the start or end of `want`. That's OK.
- # Search for an empty string succeeds, and doesn't change startpos.
- startpos = got.find(w, startpos, endpos)
- if startpos < 0:
- return False
- startpos += len(w)
-
- return True
-
-def _comment_line(line):
- "Return a commented form of the given line"
- line = line.rstrip()
- if line:
- return '# '+line
- else:
- return '#'
-
-class _OutputRedirectingPdb(pdb.Pdb):
- """
- A specialized version of the python debugger that redirects stdout
- to a given stream when interacting with the user. Stdout is *not*
- redirected when traced code is executed.
- """
- def __init__(self, out):
- self.__out = out
- pdb.Pdb.__init__(self)
-
- def trace_dispatch(self, *args):
- # Redirect stdout to the given stream.
- save_stdout = sys.stdout
- sys.stdout = self.__out
- # Call Pdb's trace dispatch method.
- try:
- return pdb.Pdb.trace_dispatch(self, *args)
- finally:
- sys.stdout = save_stdout
-
-# [XX] Normalize with respect to os.path.pardir?
-def _module_relative_path(module, path):
- if not inspect.ismodule(module):
- raise TypeError, 'Expected a module: %r' % module
- if path.startswith('/'):
- raise ValueError, 'Module-relative files may not have absolute paths'
-
- # Find the base directory for the path.
- if hasattr(module, '__file__'):
- # A normal module/package
- basedir = os.path.split(module.__file__)[0]
- elif module.__name__ == '__main__':
- # An interactive session.
- if len(sys.argv)>0 and sys.argv[0] != '':
- basedir = os.path.split(sys.argv[0])[0]
- else:
- basedir = os.curdir
- else:
- # A module w/o __file__ (this includes builtins)
- raise ValueError("Can't resolve paths relative to the module " +
- module + " (it has no __file__)")
-
- # Combine the base directory and the path.
- return os.path.join(basedir, *(path.split('/')))
-
-######################################################################
-## 2. Example & DocTest
-######################################################################
-## - An "example" is a <source, want> pair, where "source" is a
-## fragment of source code, and "want" is the expected output for
-## "source." The Example class also includes information about
-## where the example was extracted from.
-##
-## - A "doctest" is a collection of examples, typically extracted from
-## a string (such as an object's docstring). The DocTest class also
-## includes information about where the string was extracted from.
-
-class Example:
- """
- A single doctest example, consisting of source code and expected
- output. `Example` defines the following attributes:
-
- - source: A single Python statement, always ending with a newline.
- The constructor adds a newline if needed.
-
- - want: The expected output from running the source code (either
- from stdout, or a traceback in case of exception). `want` ends
- with a newline unless it's empty, in which case it's an empty
- string. The constructor adds a newline if needed.
-
- - exc_msg: The exception message generated by the example, if
- the example is expected to generate an exception; or `None` if
- it is not expected to generate an exception. This exception
- message is compared against the return value of
- `traceback.format_exception_only()`. `exc_msg` ends with a
- newline unless it's `None`. The constructor adds a newline
- if needed.
-
- - lineno: The line number within the DocTest string containing
- this Example where the Example begins. This line number is
- zero-based, with respect to the beginning of the DocTest.
-
- - indent: The example's indentation in the DocTest string.
- I.e., the number of space characters that preceed the
- example's first prompt.
-
- - options: A dictionary mapping from option flags to True or
- False, which is used to override default options for this
- example. Any option flags not contained in this dictionary
- are left at their default value (as specified by the
- DocTestRunner's optionflags). By default, no options are set.
- """
- def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
- options=None):
- # Normalize inputs.
- if not source.endswith('\n'):
- source += '\n'
- if want and not want.endswith('\n'):
- want += '\n'
- if exc_msg is not None and not exc_msg.endswith('\n'):
- exc_msg += '\n'
- # Store properties.
- self.source = source
- self.want = want
- self.lineno = lineno
- self.indent = indent
- if options is None: options = {}
- self.options = options
- self.exc_msg = exc_msg
-
-class DocTest:
- """
- A collection of doctest examples that should be run in a single
- namespace. Each `DocTest` defines the following attributes:
-
- - examples: the list of examples.
-
- - globs: The namespace (aka globals) that the examples should
- be run in.
-
- - name: A name identifying the DocTest (typically, the name of
- the object whose docstring this DocTest was extracted from).
-
- - filename: The name of the file that this DocTest was extracted
- from, or `None` if the filename is unknown.
-
- - lineno: The line number within filename where this DocTest
- begins, or `None` if the line number is unavailable. This
- line number is zero-based, with respect to the beginning of
- the file.
-
- - docstring: The string that the examples were extracted from,
- or `None` if the string is unavailable.
- """
- def __init__(self, examples, globs, name, filename, lineno, docstring):
- """
- Create a new DocTest containing the given examples. The
- DocTest's globals are initialized with a copy of `globs`.
- """
- assert not isinstance(examples, basestring), \
- "DocTest no longer accepts str; use DocTestParser instead"
- self.examples = examples
- self.docstring = docstring
- self.globs = globs.copy()
- self.name = name
- self.filename = filename
- self.lineno = lineno
-
- def __repr__(self):
- if len(self.examples) == 0:
- examples = 'no examples'
- elif len(self.examples) == 1:
- examples = '1 example'
- else:
- examples = '%d examples' % len(self.examples)
- return ('<DocTest %s from %s:%s (%s)>' %
- (self.name, self.filename, self.lineno, examples))
-
-
- # This lets us sort tests by name:
- def __cmp__(self, other):
- if not isinstance(other, DocTest):
- return -1
- return cmp((self.name, self.filename, self.lineno, id(self)),
- (other.name, other.filename, other.lineno, id(other)))
-
-######################################################################
-## 3. DocTestParser
-######################################################################
-
-class DocTestParser:
- """
- A class used to parse strings containing doctest examples.
- """
- # This regular expression is used to find doctest examples in a
- # string. It defines three groups: `source` is the source code
- # (including leading indentation and prompts); `indent` is the
- # indentation of the first (PS1) line of the source code; and
- # `want` is the expected output (including leading indentation).
- _EXAMPLE_RE = re.compile(r'''
- # Source consists of a PS1 line followed by zero or more PS2 lines.
- (?P<source>
- (?:^(?P<indent> [ ]*) >>> .*) # PS1 line
- (?:\n [ ]* \.\.\. .*)*) # PS2 lines
- \n?
- # Want consists of any non-blank lines that do not start with PS1.
- (?P<want> (?:(?![ ]*$) # Not a blank line
- (?![ ]*>>>) # Not a line starting with PS1
- .*$\n? # But any other line
- )*)
- ''', re.MULTILINE | re.VERBOSE)
-
- # A regular expression for handling `want` strings that contain
- # expected exceptions. It divides `want` into three pieces:
- # - the traceback header line (`hdr`)
- # - the traceback stack (`stack`)
- # - the exception message (`msg`), as generated by
- # traceback.format_exception_only()
- # `msg` may have multiple lines. We assume/require that the
- # exception message is the first non-indented line starting with a word
- # character following the traceback header line.
- _EXCEPTION_RE = re.compile(r"""
- # Grab the traceback header. Different versions of Python have
- # said different things on the first traceback line.
- ^(?P<hdr> Traceback\ \(
- (?: most\ recent\ call\ last
- | innermost\ last
- ) \) :
- )
- \s* $ # toss trailing whitespace on the header.
- (?P<stack> .*?) # don't blink: absorb stuff until...
- ^ (?P<msg> \w+ .*) # a line *starts* with alphanum.
- """, re.VERBOSE | re.MULTILINE | re.DOTALL)
-
- # A callable returning a true value iff its argument is a blank line
- # or contains a single comment.
- _IS_BLANK_OR_COMMENT = re.compile(r'^[ ]*(#.*)?$').match
-
- def parse(self, string, name='<string>'):
- """
- Divide the given string into examples and intervening text,
- and return them as a list of alternating Examples and strings.
- Line numbers for the Examples are 0-based. The optional
- argument `name` is a name identifying this string, and is only
- used for error messages.
- """
- string = string.expandtabs()
- # If all lines begin with the same indentation, then strip it.
- min_indent = self._min_indent(string)
- if min_indent > 0:
- string = '\n'.join([l[min_indent:] for l in string.split('\n')])
-
- output = []
- charno, lineno = 0, 0
- # Find all doctest examples in the string:
- for m in self._EXAMPLE_RE.finditer(string):
- # Add the pre-example text to `output`.
- output.append(string[charno:m.start()])
- # Update lineno (lines before this example)
- lineno += string.count('\n', charno, m.start())
- # Extract info from the regexp match.
- (source, options, want, exc_msg) = \
- self._parse_example(m, name, lineno)
- # Create an Example, and add it to the list.
- if not self._IS_BLANK_OR_COMMENT(source):
- output.append( Example(source, want, exc_msg,
- lineno=lineno,
- indent=min_indent+len(m.group('indent')),
- options=options) )
- # Update lineno (lines inside this example)
- lineno += string.count('\n', m.start(), m.end())
- # Update charno.
- charno = m.end()
- # Add any remaining post-example text to `output`.
- output.append(string[charno:])
- return output
-
- def get_doctest(self, string, globs, name, filename, lineno):
- """
- Extract all doctest examples from the given string, and
- collect them into a `DocTest` object.
-
- `globs`, `name`, `filename`, and `lineno` are attributes for
- the new `DocTest` object. See the documentation for `DocTest`
- for more information.
- """
- return DocTest(self.get_examples(string, name), globs,
- name, filename, lineno, string)
-
- def get_examples(self, string, name='<string>'):
- """
- Extract all doctest examples from the given string, and return
- them as a list of `Example` objects. Line numbers are
- 0-based, because it's most common in doctests that nothing
- interesting appears on the same line as opening triple-quote,
- and so the first interesting line is called \"line 1\" then.
-
- The optional argument `name` is a name identifying this
- string, and is only used for error messages.
- """
- return [x for x in self.parse(string, name)
- if isinstance(x, Example)]
-
- def _parse_example(self, m, name, lineno):
- """
- Given a regular expression match from `_EXAMPLE_RE` (`m`),
- return a pair `(source, want)`, where `source` is the matched
- example's source code (with prompts and indentation stripped);
- and `want` is the example's expected output (with indentation
- stripped).
-
- `name` is the string's name, and `lineno` is the line number
- where the example starts; both are used for error messages.
- """
- # Get the example's indentation level.
- indent = len(m.group('indent'))
-
- # Divide source into lines; check that they're properly
- # indented; and then strip their indentation & prompts.
- source_lines = m.group('source').split('\n')
- self._check_prompt_blank(source_lines, indent, name, lineno)
- self._check_prefix(source_lines[1:], ' '*indent + '.', name, lineno)
- source = '\n'.join([sl[indent+4:] for sl in source_lines])
-
- # Divide want into lines; check that it's properly indented; and
- # then strip the indentation. Spaces before the last newline should
- # be preserved, so plain rstrip() isn't good enough.
- want = m.group('want')
- want_lines = want.split('\n')
- if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
- del want_lines[-1] # forget final newline & spaces after it
- self._check_prefix(want_lines, ' '*indent, name,
- lineno + len(source_lines))
- want = '\n'.join([wl[indent:] for wl in want_lines])
-
- # If `want` contains a traceback message, then extract it.
- m = self._EXCEPTION_RE.match(want)
- if m:
- exc_msg = m.group('msg')
- else:
- exc_msg = None
-
- # Extract options from the source.
- options = self._find_options(source, name, lineno)
-
- return source, options, want, exc_msg
-
- # This regular expression looks for option directives in the
- # source code of an example. Option directives are comments
- # starting with "doctest:". Warning: this may give false
- # positives for string-literals that contain the string
- # "#doctest:". Eliminating these false positives would require
- # actually parsing the string; but we limit them by ignoring any
- # line containing "#doctest:" that is *followed* by a quote mark.
- _OPTION_DIRECTIVE_RE = re.compile(r'#\s*doctest:\s*([^\n\'"]*)$',
- re.MULTILINE)
-
- def _find_options(self, source, name, lineno):
- """
- Return a dictionary containing option overrides extracted from
- option directives in the given source string.
-
- `name` is the string's name, and `lineno` is the line number
- where the example starts; both are used for error messages.
- """
- options = {}
- # (note: with the current regexp, this will match at most once:)
- for m in self._OPTION_DIRECTIVE_RE.finditer(source):
- option_strings = m.group(1).replace(',', ' ').split()
- for option in option_strings:
- if (option[0] not in '+-' or
- option[1:] not in OPTIONFLAGS_BY_NAME):
- raise ValueError('line %r of the doctest for %s '
- 'has an invalid option: %r' %
- (lineno+1, name, option))
- flag = OPTIONFLAGS_BY_NAME[option[1:]]
- options[flag] = (option[0] == '+')
- if options and self._IS_BLANK_OR_COMMENT(source):
- raise ValueError('line %r of the doctest for %s has an option '
- 'directive on a line with no example: %r' %
- (lineno, name, source))
- return options
-
- # This regular expression finds the indentation of every non-blank
- # line in a string.
- _INDENT_RE = re.compile('^([ ]*)(?=\S)', re.MULTILINE)
-
- def _min_indent(self, s):
- "Return the minimum indentation of any non-blank line in `s`"
- indents = [len(indent) for indent in self._INDENT_RE.findall(s)]
- if len(indents) > 0:
- return min(indents)
- else:
- return 0
-
- def _check_prompt_blank(self, lines, indent, name, lineno):
- """
- Given the lines of a source string (including prompts and
- leading indentation), check to make sure that every prompt is
- followed by a space character. If any line is not followed by
- a space character, then raise ValueError.
- """
- for i, line in enumerate(lines):
- if len(line) >= indent+4 and line[indent+3] != ' ':
- raise ValueError('line %r of the docstring for %s '
- 'lacks blank after %s: %r' %
- (lineno+i+1, name,
- line[indent:indent+3], line))
-
- def _check_prefix(self, lines, prefix, name, lineno):
- """
- Check that every line in the given list starts with the given
- prefix; if any line does not, then raise a ValueError.
- """
- for i, line in enumerate(lines):
- if line and not line.startswith(prefix):
- raise ValueError('line %r of the docstring for %s has '
- 'inconsistent leading whitespace: %r' %
- (lineno+i+1, name, line))
-
-
-######################################################################
-## 4. DocTest Finder
-######################################################################
-
-class DocTestFinder:
- """
- A class used to extract the DocTests that are relevant to a given
- object, from its docstring and the docstrings of its contained
- objects. Doctests can currently be extracted from the following
- object types: modules, functions, classes, methods, staticmethods,
- classmethods, and properties.
- """
-
- def __init__(self, verbose=False, parser=DocTestParser(),
- recurse=True, _namefilter=None, exclude_empty=True):
- """
- Create a new doctest finder.
-
- The optional argument `parser` specifies a class or
- function that should be used to create new DocTest objects (or
- objects that implement the same interface as DocTest). The
- signature for this factory function should match the signature
- of the DocTest constructor.
-
- If the optional argument `recurse` is false, then `find` will
- only examine the given object, and not any contained objects.
-
- If the optional argument `exclude_empty` is false, then `find`
- will include tests for objects with empty docstrings.
- """
- self._parser = parser
- self._verbose = verbose
- self._recurse = recurse
- self._exclude_empty = exclude_empty
- # _namefilter is undocumented, and exists only for temporary backward-
- # compatibility support of testmod's deprecated isprivate mess.
- self._namefilter = _namefilter
-
- def find(self, obj, name=None, module=None, globs=None,
- extraglobs=None):
- """
- Return a list of the DocTests that are defined by the given
- object's docstring, or by any of its contained objects'
- docstrings.
-
- The optional parameter `module` is the module that contains
- the given object. If the module is not specified or is None, then
- the test finder will attempt to automatically determine the
- correct module. The object's module is used:
-
- - As a default namespace, if `globs` is not specified.
- - To prevent the DocTestFinder from extracting DocTests
- from objects that are imported from other modules.
- - To find the name of the file containing the object.
- - To help find the line number of the object within its
- file.
-
- Contained objects whose module does not match `module` are ignored.
-
- If `module` is False, no attempt to find the module will be made.
- This is obscure, of use mostly in tests: if `module` is False, or
- is None but cannot be found automatically, then all objects are
- considered to belong to the (non-existent) module, so all contained
- objects will (recursively) be searched for doctests.
-
- The globals for each DocTest is formed by combining `globs`
- and `extraglobs` (bindings in `extraglobs` override bindings
- in `globs`). A new copy of the globals dictionary is created
- for each DocTest. If `globs` is not specified, then it
- defaults to the module's `__dict__`, if specified, or {}
- otherwise. If `extraglobs` is not specified, then it defaults
- to {}.
-
- """
- # If name was not specified, then extract it from the object.
- if name is None:
- name = getattr(obj, '__name__', None)
- if name is None:
- raise ValueError("DocTestFinder.find: name must be given "
- "when obj.__name__ doesn't exist: %r" %
- (type(obj),))
-
- # Find the module that contains the given object (if obj is
- # a module, then module=obj.). Note: this may fail, in which
- # case module will be None.
- if module is False:
- module = None
- elif module is None:
- module = inspect.getmodule(obj)
-
- # Read the module's source code. This is used by
- # DocTestFinder._find_lineno to find the line number for a
- # given object's docstring.
- try:
- file = inspect.getsourcefile(obj) or inspect.getfile(obj)
- source_lines = linecache.getlines(file)
- if not source_lines:
- source_lines = None
- except TypeError:
- source_lines = None
-
- # Initialize globals, and merge in extraglobs.
- if globs is None:
- if module is None:
- globs = {}
- else:
- globs = module.__dict__.copy()
- else:
- globs = globs.copy()
- if extraglobs is not None:
- globs.update(extraglobs)
-
- # Recursively expore `obj`, extracting DocTests.
- tests = []
- self._find(tests, obj, name, module, source_lines, globs, {})
- # Sort the tests by alpha order of names, for consistency in
- # verbose-mode output. This was a feature of doctest in Pythons
- # <= 2.3 that got lost by accident in 2.4. It was repaired in
- # 2.4.4 and 2.5.
- tests.sort()
- return tests
-
- def _filter(self, obj, prefix, base):
- """
- Return true if the given object should not be examined.
- """
- return (self._namefilter is not None and
- self._namefilter(prefix, base))
-
- def _from_module(self, module, object):
- """
- Return true if the given object is defined in the given
- module.
- """
- if module is None:
- return True
- elif inspect.isfunction(object):
- return module.__dict__ is object.func_globals
- elif inspect.isclass(object):
- # Some jython classes don't set __module__
- return module.__name__ == getattr(object, '__module__', None)
- elif inspect.getmodule(object) is not None:
- return module is inspect.getmodule(object)
- elif hasattr(object, '__module__'):
- return module.__name__ == object.__module__
- elif isinstance(object, property):
- return True # [XX] no way not be sure.
- else:
- raise ValueError("object must be a class or function")
-
- def _find(self, tests, obj, name, module, source_lines, globs, seen):
- """
- Find tests for the given object and any contained objects, and
- add them to `tests`.
- """
- if self._verbose:
- print 'Finding tests in %s' % name
-
- # If we've already processed this object, then ignore it.
- if id(obj) in seen:
- return
- seen[id(obj)] = 1
-
- # Find a test for this object, and add it to the list of tests.
- test = self._get_test(obj, name, module, globs, source_lines)
- if test is not None:
- tests.append(test)
-
- # Look for tests in a module's contained objects.
- if inspect.ismodule(obj) and self._recurse:
- for valname, val in obj.__dict__.items():
- # Check if this contained object should be ignored.
- if self._filter(val, name, valname):
- continue
- valname = '%s.%s' % (name, valname)
- # Recurse to functions & classes.
- if ((inspect.isfunction(val) or inspect.isclass(val)) and
- self._from_module(module, val)):
- self._find(tests, val, valname, module, source_lines,
- globs, seen)
-
- # Look for tests in a module's __test__ dictionary.
- if inspect.ismodule(obj) and self._recurse:
- for valname, val in getattr(obj, '__test__', {}).items():
- if not isinstance(valname, basestring):
- raise ValueError("DocTestFinder.find: __test__ keys "
- "must be strings: %r" %
- (type(valname),))
- if not (inspect.isfunction(val) or inspect.isclass(val) or
- inspect.ismethod(val) or inspect.ismodule(val) or
- isinstance(val, basestring)):
- raise ValueError("DocTestFinder.find: __test__ values "
- "must be strings, functions, methods, "
- "classes, or modules: %r" %
- (type(val),))
- valname = '%s.__test__.%s' % (name, valname)
- self._find(tests, val, valname, module, source_lines,
- globs, seen)
-
- # Look for tests in a class's contained objects.
- if inspect.isclass(obj) and self._recurse:
- for valname, val in obj.__dict__.items():
- # Check if this contained object should be ignored.
- if self._filter(val, name, valname):
- continue
- # Special handling for staticmethod/classmethod.
- if isinstance(val, staticmethod):
- val = getattr(obj, valname)
- if isinstance(val, classmethod):
- val = getattr(obj, valname).im_func
-
- # Recurse to methods, properties, and nested classes.
- if ((inspect.isfunction(val) or inspect.isclass(val) or
- isinstance(val, property)) and
- self._from_module(module, val)):
- valname = '%s.%s' % (name, valname)
- self._find(tests, val, valname, module, source_lines,
- globs, seen)
-
- def _get_test(self, obj, name, module, globs, source_lines):
- """
- Return a DocTest for the given object, if it defines a docstring;
- otherwise, return None.
- """
- # Extract the object's docstring. If it doesn't have one,
- # then return None (no test for this object).
- if isinstance(obj, basestring):
- docstring = obj
- else:
- try:
- if obj.__doc__ is None:
- docstring = ''
- else:
- docstring = obj.__doc__
- if not isinstance(docstring, basestring):
- docstring = str(docstring)
- except (TypeError, AttributeError):
- docstring = ''
-
- # Find the docstring's location in the file.
- lineno = self._find_lineno(obj, source_lines)
-
- # Don't bother if the docstring is empty.
- if self._exclude_empty and not docstring:
- return None
-
- # Return a DocTest for this object.
- if module is None:
- filename = None
- else:
- filename = getattr(module, '__file__', module.__name__)
- if filename[-4:] in (".pyc", ".pyo"):
- filename = filename[:-1]
- elif sys.platform.startswith('java') and \
- filename.endswith('$py.class'):
- filename = '%s.py' % filename[:-9]
- return self._parser.get_doctest(docstring, globs, name,
- filename, lineno)
-
- def _find_lineno(self, obj, source_lines):
- """
- Return a line number of the given object's docstring. Note:
- this method assumes that the object has a docstring.
- """
- lineno = None
-
- # Find the line number for modules.
- if inspect.ismodule(obj):
- lineno = 0
-
- # Find the line number for classes.
- # Note: this could be fooled if a class is defined multiple
- # times in a single file.
- if inspect.isclass(obj):
- if source_lines is None:
- return None
- pat = re.compile(r'^\s*class\s*%s\b' %
- getattr(obj, '__name__', '-'))
- for i, line in enumerate(source_lines):
- if pat.match(line):
- lineno = i
- break
-
- # Find the line number for functions & methods.
- if inspect.ismethod(obj): obj = obj.im_func
- if inspect.isfunction(obj): obj = obj.func_code
- if inspect.istraceback(obj): obj = obj.tb_frame
- if inspect.isframe(obj): obj = obj.f_code
- if inspect.iscode(obj):
- lineno = getattr(obj, 'co_firstlineno', None)-1
-
- # Find the line number where the docstring starts. Assume
- # that it's the first line that begins with a quote mark.
- # Note: this could be fooled by a multiline function
- # signature, where a continuation line begins with a quote
- # mark.
- if lineno is not None:
- if source_lines is None:
- return lineno+1
- pat = re.compile('(^|.*:)\s*\w*("|\')')
- for lineno in range(lineno, len(source_lines)):
- if pat.match(source_lines[lineno]):
- return lineno
-
- # We couldn't find the line number.
- return None
-
-######################################################################
-## 5. DocTest Runner
-######################################################################
-
-class DocTestRunner:
- # This divider string is used to separate failure messages, and to
- # separate sections of the summary.
- DIVIDER = "*" * 70
-
- def __init__(self, checker=None, verbose=None, optionflags=0):
- """
- Create a new test runner.
-
- Optional keyword arg `checker` is the `OutputChecker` that
- should be used to compare the expected outputs and actual
- outputs of doctest examples.
-
- Optional keyword arg 'verbose' prints lots of stuff if true,
- only failures if false; by default, it's true iff '-v' is in
- sys.argv.
-
- Optional argument `optionflags` can be used to control how the
- test runner compares expected output to actual output, and how
- it displays failures. See the documentation for `testmod` for
- more information.
- """
- self._checker = checker or OutputChecker()
- if verbose is None:
- verbose = '-v' in sys.argv
- self._verbose = verbose
- self.optionflags = optionflags
- self.original_optionflags = optionflags
-
- # Keep track of the examples we've run.
- self.tries = 0
- self.failures = 0
- self._name2ft = {}
-
- # Create a fake output target for capturing doctest output.
- self._fakeout = _SpoofOut()
-
- #/////////////////////////////////////////////////////////////////
- # Reporting methods
- #/////////////////////////////////////////////////////////////////
-
- def report_start(self, out, test, example):
- """
- Report that the test runner is about to process the given
- example. (Only displays a message if verbose=True)
- """
- if self._verbose:
- if example.want:
- out('Trying:\n' + _indent(example.source) +
- 'Expecting:\n' + _indent(example.want))
- else:
- out('Trying:\n' + _indent(example.source) +
- 'Expecting nothing\n')
-
- def report_success(self, out, test, example, got):
- """
- Report that the given example ran successfully. (Only
- displays a message if verbose=True)
- """
- if self._verbose:
- out("ok\n")
-
- def report_failure(self, out, test, example, got):
- """
- Report that the given example failed.
- """
- out(self._failure_header(test, example) +
- self._checker.output_difference(example, got, self.optionflags))
-
- def report_unexpected_exception(self, out, test, example, exc_info):
- """
- Report that the given example raised an unexpected exception.
- """
- out(self._failure_header(test, example) +
- 'Exception raised:\n' + _indent(_exception_traceback(exc_info)))
-
- def _failure_header(self, test, example):
- out = [self.DIVIDER]
- if test.filename:
- if test.lineno is not None and example.lineno is not None:
- lineno = test.lineno + example.lineno + 1
- else:
- lineno = '?'
- out.append('File "%s", line %s, in %s' %
- (test.filename, lineno, test.name))
- else:
- out.append('Line %s, in %s' % (example.lineno+1, test.name))
- out.append('Failed example:')
- source = example.source
- out.append(_indent(source))
- return '\n'.join(out)
-
- #/////////////////////////////////////////////////////////////////
- # DocTest Running
- #/////////////////////////////////////////////////////////////////
-
- def __run(self, test, compileflags, out):
- """
- Run the examples in `test`. Write the outcome of each example
- with one of the `DocTestRunner.report_*` methods, using the
- writer function `out`. `compileflags` is the set of compiler
- flags that should be used to execute examples. Return a tuple
- `(f, t)`, where `t` is the number of examples tried, and `f`
- is the number of examples that failed. The examples are run
- in the namespace `test.globs`.
- """
- # Keep track of the number of failures and tries.
- failures = tries = 0
-
- # Save the option flags (since option directives can be used
- # to modify them).
- original_optionflags = self.optionflags
-
- SUCCESS, FAILURE, BOOM = range(3) # `outcome` state
-
- check = self._checker.check_output
-
- # Process each example.
- for examplenum, example in enumerate(test.examples):
-
- # If REPORT_ONLY_FIRST_FAILURE is set, then supress
- # reporting after the first failure.
- quiet = (self.optionflags & REPORT_ONLY_FIRST_FAILURE and
- failures > 0)
-
- # Merge in the example's options.
- self.optionflags = original_optionflags
- if example.options:
- for (optionflag, val) in example.options.items():
- if val:
- self.optionflags |= optionflag
- else:
- self.optionflags &= ~optionflag
-
- # Record that we started this example.
- tries += 1
- if not quiet:
- self.report_start(out, test, example)
-
- # Use a special filename for compile(), so we can retrieve
- # the source code during interactive debugging (see
- # __patched_linecache_getlines).
- filename = '<doctest %s[%d]>' % (test.name, examplenum)
-
- # Run the example in the given context (globs), and record
- # any exception that gets raised. (But don't intercept
- # keyboard interrupts.)
- try:
- # Don't blink! This is where the user's code gets run.
- exec compile(example.source, filename, "single",
- compileflags, 1) in test.globs
- self.debugger.set_continue() # ==== Example Finished ====
- exception = None
- except KeyboardInterrupt:
- raise
- except:
- exception = sys.exc_info()
- self.debugger.set_continue() # ==== Example Finished ====
-
- got = self._fakeout.getvalue() # the actual output
- self._fakeout.truncate(0)
- outcome = FAILURE # guilty until proved innocent or insane
-
- # If the example executed without raising any exceptions,
- # verify its output.
- if exception is None:
- if check(example.want, got, self.optionflags):
- outcome = SUCCESS
-
- # The example raised an exception: check if it was expected.
- else:
- exc_info = sys.exc_info()
- exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
- if not quiet:
- got += _exception_traceback(exc_info)
-
- # If `example.exc_msg` is None, then we weren't expecting
- # an exception.
- if example.exc_msg is None:
- outcome = BOOM
-
- # We expected an exception: see whether it matches.
- elif check(example.exc_msg, exc_msg, self.optionflags):
- outcome = SUCCESS
-
- # Another chance if they didn't care about the detail.
- elif self.optionflags & IGNORE_EXCEPTION_DETAIL:
- m1 = re.match(r'[^:]*:', example.exc_msg)
- m2 = re.match(r'[^:]*:', exc_msg)
- if m1 and m2 and check(m1.group(0), m2.group(0),
- self.optionflags):
- outcome = SUCCESS
-
- # Report the outcome.
- if outcome is SUCCESS:
- if not quiet:
- self.report_success(out, test, example, got)
- elif outcome is FAILURE:
- if not quiet:
- self.report_failure(out, test, example, got)
- failures += 1
- elif outcome is BOOM:
- if not quiet:
- self.report_unexpected_exception(out, test, example,
- exc_info)
- failures += 1
- else:
- assert False, ("unknown outcome", outcome)
-
- # Restore the option flags (in case they were modified)
- self.optionflags = original_optionflags
-
- # Record and return the number of failures and tries.
- self.__record_outcome(test, failures, tries)
- return failures, tries
-
- def __record_outcome(self, test, f, t):
- """
- Record the fact that the given DocTest (`test`) generated `f`
- failures out of `t` tried examples.
- """
- f2, t2 = self._name2ft.get(test.name, (0,0))
- self._name2ft[test.name] = (f+f2, t+t2)
- self.failures += f
- self.tries += t
-
- __LINECACHE_FILENAME_RE = re.compile(r'<doctest '
- r'(?P<name>[\w\.]+)'
- r'\[(?P<examplenum>\d+)\]>$')
- def __patched_linecache_getlines(self, filename):
- m = self.__LINECACHE_FILENAME_RE.match(filename)
- if m and m.group('name') == self.test.name:
- example = self.test.examples[int(m.group('examplenum'))]
- return example.source.splitlines(True)
- else:
- return self.save_linecache_getlines(filename)
-
- def run(self, test, compileflags=None, out=None, clear_globs=True):
- """
- Run the examples in `test`, and display the results using the
- writer function `out`.
-
- The examples are run in the namespace `test.globs`. If
- `clear_globs` is true (the default), then this namespace will
- be cleared after the test runs, to help with garbage
- collection. If you would like to examine the namespace after
- the test completes, then use `clear_globs=False`.
-
- `compileflags` gives the set of flags that should be used by
- the Python compiler when running the examples. If not
- specified, then it will default to the set of future-import
- flags that apply to `globs`.
-
- The output of each example is checked using
- `DocTestRunner.check_output`, and the results are formatted by
- the `DocTestRunner.report_*` methods.
- """
- self.test = test
-
- if compileflags is None:
- compileflags = _extract_future_flags(test.globs)
-
- save_stdout = sys.stdout
- if out is None:
- out = save_stdout.write
- sys.stdout = self._fakeout
-
- # Patch pdb.set_trace to restore sys.stdout during interactive
- # debugging (so it's not still redirected to self._fakeout).
- # Note that the interactive output will go to *our*
- # save_stdout, even if that's not the real sys.stdout; this
- # allows us to write test cases for the set_trace behavior.
- save_set_trace = pdb.set_trace
- self.debugger = _OutputRedirectingPdb(save_stdout)
- self.debugger.reset()
- pdb.set_trace = self.debugger.set_trace
-
- # Patch linecache.getlines, so we can see the example's source
- # when we're inside the debugger.
- self.save_linecache_getlines = linecache.getlines
- linecache.getlines = self.__patched_linecache_getlines
-
- try:
- return self.__run(test, compileflags, out)
- finally:
- sys.stdout = save_stdout
- pdb.set_trace = save_set_trace
- linecache.getlines = self.save_linecache_getlines
- if clear_globs:
- test.globs.clear()
-
- #/////////////////////////////////////////////////////////////////
- # Summarization
- #/////////////////////////////////////////////////////////////////
- def summarize(self, verbose=None):
- """
- Print a summary of all the test cases that have been run by
- this DocTestRunner, and return a tuple `(f, t)`, where `f` is
- the total number of failed examples, and `t` is the total
- number of tried examples.
-
- The optional `verbose` argument controls how detailed the
- summary is. If the verbosity is not specified, then the
- DocTestRunner's verbosity is used.
- """
- if verbose is None:
- verbose = self._verbose
- notests = []
- passed = []
- failed = []
- totalt = totalf = 0
- for x in self._name2ft.items():
- name, (f, t) = x
- assert f <= t
- totalt += t
- totalf += f
- if t == 0:
- notests.append(name)
- elif f == 0:
- passed.append( (name, t) )
- else:
- failed.append(x)
- if verbose:
- if notests:
- print len(notests), "items had no tests:"
- notests.sort()
- for thing in notests:
- print " ", thing
- if passed:
- print len(passed), "items passed all tests:"
- passed.sort()
- for thing, count in passed:
- print " %3d tests in %s" % (count, thing)
- if failed:
- print self.DIVIDER
- print len(failed), "items had failures:"
- failed.sort()
- for thing, (f, t) in failed:
- print " %3d of %3d in %s" % (f, t, thing)
- if verbose:
- print totalt, "tests in", len(self._name2ft), "items."
- print totalt - totalf, "passed and", totalf, "failed."
- if totalf:
- print "***Test Failed***", totalf, "failures."
- elif verbose:
- print "Test passed."
- return totalf, totalt
-
- #/////////////////////////////////////////////////////////////////
- # Backward compatibility cruft to maintain doctest.master.
- #/////////////////////////////////////////////////////////////////
- def merge(self, other):
- d = self._name2ft
- for name, (f, t) in other._name2ft.items():
- if name in d:
- print "*** DocTestRunner.merge: '" + name + "' in both" \
- " testers; summing outcomes."
- f2, t2 = d[name]
- f = f + f2
- t = t + t2
- d[name] = f, t
-
-class OutputChecker:
- """
- A class used to check the whether the actual output from a doctest
- example matches the expected output. `OutputChecker` defines two
- methods: `check_output`, which compares a given pair of outputs,
- and returns true if they match; and `output_difference`, which
- returns a string describing the differences between two outputs.
- """
- def check_output(self, want, got, optionflags):
- """
- Return True iff the actual output from an example (`got`)
- matches the expected output (`want`). These strings are
- always considered to match if they are identical; but
- depending on what option flags the test runner is using,
- several non-exact match types are also possible. See the
- documentation for `TestRunner` for more information about
- option flags.
- """
- # Handle the common case first, for efficiency:
- # if they're string-identical, always return true.
- if got == want:
- return True
-
- # The values True and False replaced 1 and 0 as the return
- # value for boolean comparisons in Python 2.3.
- if not (optionflags & DONT_ACCEPT_TRUE_FOR_1):
- if (got,want) == ("True\n", "1\n"):
- return True
- if (got,want) == ("False\n", "0\n"):
- return True
-
- # <BLANKLINE> can be used as a special sequence to signify a
- # blank line, unless the DONT_ACCEPT_BLANKLINE flag is used.
- if not (optionflags & DONT_ACCEPT_BLANKLINE):
- # Replace <BLANKLINE> in want with a blank line.
- want = re.sub('(?m)^%s\s*?$' % re.escape(BLANKLINE_MARKER),
- '', want)
- # If a line in got contains only spaces, then remove the
- # spaces.
- got = re.sub('(?m)^\s*?$', '', got)
- if got == want:
- return True
-
- # This flag causes doctest to ignore any differences in the
- # contents of whitespace strings. Note that this can be used
- # in conjunction with the ELLIPSIS flag.
- if optionflags & NORMALIZE_WHITESPACE:
- got = ' '.join(got.split())
- want = ' '.join(want.split())
- if got == want:
- return True
-
- # The ELLIPSIS flag says to let the sequence "..." in `want`
- # match any substring in `got`.
- if optionflags & ELLIPSIS:
- if _ellipsis_match(want, got):
- return True
-
- # We didn't find any match; return false.
- return False
-
- # Should we do a fancy diff?
- def _do_a_fancy_diff(self, want, got, optionflags):
- # Not unless they asked for a fancy diff.
- if not optionflags & (REPORT_UDIFF |
- REPORT_CDIFF |
- REPORT_NDIFF):
- return False
-
- # If expected output uses ellipsis, a meaningful fancy diff is
- # too hard ... or maybe not. In two real-life failures Tim saw,
- # a diff was a major help anyway, so this is commented out.
- # [todo] _ellipsis_match() knows which pieces do and don't match,
- # and could be the basis for a kick-ass diff in this case.
- ##if optionflags & ELLIPSIS and ELLIPSIS_MARKER in want:
- ## return False
-
- # ndiff does intraline difference marking, so can be useful even
- # for 1-line differences.
- if optionflags & REPORT_NDIFF:
- return True
-
- # The other diff types need at least a few lines to be helpful.
- return want.count('\n') > 2 and got.count('\n') > 2
-
- def output_difference(self, example, got, optionflags):
- """
- Return a string describing the differences between the
- expected output for a given example (`example`) and the actual
- output (`got`). `optionflags` is the set of option flags used
- to compare `want` and `got`.
- """
- want = example.want
- # If <BLANKLINE>s are being used, then replace blank lines
- # with <BLANKLINE> in the actual output string.
- if not (optionflags & DONT_ACCEPT_BLANKLINE):
- got = re.sub('(?m)^[ ]*(?=\n)', BLANKLINE_MARKER, got)
-
- # Check if we should use diff.
- if self._do_a_fancy_diff(want, got, optionflags):
- # Split want & got into lines.
- want_lines = want.splitlines(True) # True == keep line ends
- got_lines = got.splitlines(True)
- # Use difflib to find their differences.
- if optionflags & REPORT_UDIFF:
- diff = difflib.unified_diff(want_lines, got_lines, n=2)
- diff = list(diff)[2:] # strip the diff header
- kind = 'unified diff with -expected +actual'
- elif optionflags & REPORT_CDIFF:
- diff = difflib.context_diff(want_lines, got_lines, n=2)
- diff = list(diff)[2:] # strip the diff header
- kind = 'context diff with expected followed by actual'
- elif optionflags & REPORT_NDIFF:
- engine = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK)
- diff = list(engine.compare(want_lines, got_lines))
- kind = 'ndiff with -expected +actual'
- else:
- assert 0, 'Bad diff option'
- # Remove trailing whitespace on diff output.
- diff = [line.rstrip() + '\n' for line in diff]
- return 'Differences (%s):\n' % kind + _indent(''.join(diff))
-
- # If we're not using diff, then simply list the expected
- # output followed by the actual output.
- if want and got:
- return 'Expected:\n%sGot:\n%s' % (_indent(want), _indent(got))
- elif want:
- return 'Expected:\n%sGot nothing\n' % _indent(want)
- elif got:
- return 'Expected nothing\nGot:\n%s' % _indent(got)
- else:
- return 'Expected nothing\nGot nothing\n'
-
-class DocTestFailure(Exception):
- """A DocTest example has failed in debugging mode.
-
- The exception instance has variables:
-
- - test: the DocTest object being run
-
- - excample: the Example object that failed
-
- - got: the actual output
- """
- def __init__(self, test, example, got):
- self.test = test
- self.example = example
- self.got = got
-
- def __str__(self):
- return str(self.test)
-
-class UnexpectedException(Exception):
- """A DocTest example has encountered an unexpected exception
-
- The exception instance has variables:
-
- - test: the DocTest object being run
-
- - excample: the Example object that failed
-
- - exc_info: the exception info
- """
- def __init__(self, test, example, exc_info):
- self.test = test
- self.example = example
- self.exc_info = exc_info
-
- def __str__(self):
- return str(self.test)
-
-class DebugRunner(DocTestRunner):
-
- def run(self, test, compileflags=None, out=None, clear_globs=True):
- r = DocTestRunner.run(self, test, compileflags, out, False)
- if clear_globs:
- test.globs.clear()
- return r
-
- def report_unexpected_exception(self, out, test, example, exc_info):
- raise UnexpectedException(test, example, exc_info)
-
- def report_failure(self, out, test, example, got):
- raise DocTestFailure(test, example, got)
-
-######################################################################
-## 6. Test Functions
-######################################################################
-# These should be backwards compatible.
-
-# For backward compatibility, a global instance of a DocTestRunner
-# class, updated by testmod.
-master = None
-
-def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
- report=True, optionflags=0, extraglobs=None,
- raise_on_error=False, exclude_empty=False):
- """m=None, name=None, globs=None, verbose=None, isprivate=None,
- report=True, optionflags=0, extraglobs=None, raise_on_error=False,
- exclude_empty=False
-
- Test examples in docstrings in functions and classes reachable
- from module m (or the current module if m is not supplied), starting
- with m.__doc__. Unless isprivate is specified, private names
- are not skipped.
-
- Also test examples reachable from dict m.__test__ if it exists and is
- not None. m.__test__ maps names to functions, classes and strings;
- function and class docstrings are tested even if the name is private;
- strings are tested directly, as if they were docstrings.
-
- Return (#failures, #tests).
-
- See doctest.__doc__ for an overview.
-
- Optional keyword arg "name" gives the name of the module; by default
- use m.__name__.
-
- Optional keyword arg "globs" gives a dict to be used as the globals
- when executing examples; by default, use m.__dict__. A copy of this
- dict is actually used for each docstring, so that each docstring's
- examples start with a clean slate.
-
- Optional keyword arg "extraglobs" gives a dictionary that should be
- merged into the globals that are used to execute examples. By
- default, no extra globals are used. This is new in 2.4.
-
- Optional keyword arg "verbose" prints lots of stuff if true, prints
- only failures if false; by default, it's true iff "-v" is in sys.argv.
-
- Optional keyword arg "report" prints a summary at the end when true,
- else prints nothing at the end. In verbose mode, the summary is
- detailed, else very brief (in fact, empty if all tests passed).
-
- Optional keyword arg "optionflags" or's together module constants,
- and defaults to 0. This is new in 2.3. Possible values (see the
- docs for details):
-
- DONT_ACCEPT_TRUE_FOR_1
- DONT_ACCEPT_BLANKLINE
- NORMALIZE_WHITESPACE
- ELLIPSIS
- IGNORE_EXCEPTION_DETAIL
- REPORT_UDIFF
- REPORT_CDIFF
- REPORT_NDIFF
- REPORT_ONLY_FIRST_FAILURE
-
- Optional keyword arg "raise_on_error" raises an exception on the
- first unexpected exception or failure. This allows failures to be
- post-mortem debugged.
-
- Deprecated in Python 2.4:
- Optional keyword arg "isprivate" specifies a function used to
- determine whether a name is private. The default function is
- treat all functions as public. Optionally, "isprivate" can be
- set to doctest.is_private to skip over functions marked as private
- using the underscore naming convention; see its docs for details.
-
- Advanced tomfoolery: testmod runs methods of a local instance of
- class doctest.Tester, then merges the results into (or creates)
- global Tester instance doctest.master. Methods of doctest.master
- can be called directly too, if you want to do something unusual.
- Passing report=0 to testmod is especially useful then, to delay
- displaying a summary. Invoke doctest.master.summarize(verbose)
- when you're done fiddling.
- """
- global master
-
- if isprivate is not None:
- warnings.warn("the isprivate argument is deprecated; "
- "examine DocTestFinder.find() lists instead",
- DeprecationWarning)
-
- # If no module was given, then use __main__.
- if m is None:
- # DWA - m will still be None if this wasn't invoked from the command
- # line, in which case the following TypeError is about as good an error
- # as we should expect
- m = sys.modules.get('__main__')
-
- # Check that we were actually given a module.
- if not inspect.ismodule(m):
- raise TypeError("testmod: module required; %r" % (m,))
-
- # If no name was given, then use the module's name.
- if name is None:
- name = m.__name__
-
- # Find, parse, and run all tests in the given module.
- finder = DocTestFinder(_namefilter=isprivate, exclude_empty=exclude_empty)
-
- if raise_on_error:
- runner = DebugRunner(verbose=verbose, optionflags=optionflags)
- else:
- runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
-
- for test in finder.find(m, name, globs=globs, extraglobs=extraglobs):
- runner.run(test)
-
- if report:
- runner.summarize()
-
- if master is None:
- master = runner
- else:
- master.merge(runner)
-
- return runner.failures, runner.tries
-
-def testfile(filename, module_relative=True, name=None, package=None,
- globs=None, verbose=None, report=True, optionflags=0,
- extraglobs=None, raise_on_error=False, parser=DocTestParser()):
- """
- Test examples in the given file. Return (#failures, #tests).
-
- Optional keyword arg "module_relative" specifies how filenames
- should be interpreted:
-
- - If "module_relative" is True (the default), then "filename"
- specifies a module-relative path. By default, this path is
- relative to the calling module's directory; but if the
- "package" argument is specified, then it is relative to that
- package. To ensure os-independence, "filename" should use
- "/" characters to separate path segments, and should not
- be an absolute path (i.e., it may not begin with "/").
-
- - If "module_relative" is False, then "filename" specifies an
- os-specific path. The path may be absolute or relative (to
- the current working directory).
-
- Optional keyword arg "name" gives the name of the test; by default
- use the file's basename.
-
- Optional keyword argument "package" is a Python package or the
- name of a Python package whose directory should be used as the
- base directory for a module relative filename. If no package is
- specified, then the calling module's directory is used as the base
- directory for module relative filenames. It is an error to
- specify "package" if "module_relative" is False.
-
- Optional keyword arg "globs" gives a dict to be used as the globals
- when executing examples; by default, use {}. A copy of this dict
- is actually used for each docstring, so that each docstring's
- examples start with a clean slate.
-
- Optional keyword arg "extraglobs" gives a dictionary that should be
- merged into the globals that are used to execute examples. By
- default, no extra globals are used.
-
- Optional keyword arg "verbose" prints lots of stuff if true, prints
- only failures if false; by default, it's true iff "-v" is in sys.argv.
-
- Optional keyword arg "report" prints a summary at the end when true,
- else prints nothing at the end. In verbose mode, the summary is
- detailed, else very brief (in fact, empty if all tests passed).
-
- Optional keyword arg "optionflags" or's together module constants,
- and defaults to 0. Possible values (see the docs for details):
-
- DONT_ACCEPT_TRUE_FOR_1
- DONT_ACCEPT_BLANKLINE
- NORMALIZE_WHITESPACE
- ELLIPSIS
- IGNORE_EXCEPTION_DETAIL
- REPORT_UDIFF
- REPORT_CDIFF
- REPORT_NDIFF
- REPORT_ONLY_FIRST_FAILURE
-
- Optional keyword arg "raise_on_error" raises an exception on the
- first unexpected exception or failure. This allows failures to be
- post-mortem debugged.
-
- Optional keyword arg "parser" specifies a DocTestParser (or
- subclass) that should be used to extract tests from the files.
-
- Advanced tomfoolery: testmod runs methods of a local instance of
- class doctest.Tester, then merges the results into (or creates)
- global Tester instance doctest.master. Methods of doctest.master
- can be called directly too, if you want to do something unusual.
- Passing report=0 to testmod is especially useful then, to delay
- displaying a summary. Invoke doctest.master.summarize(verbose)
- when you're done fiddling.
- """
- global master
-
- if package and not module_relative:
- raise ValueError("Package may only be specified for module-"
- "relative paths.")
-
- # Relativize the path
- if module_relative:
- package = _normalize_module(package)
- filename = _module_relative_path(package, filename)
-
- # If no name was given, then use the file's name.
- if name is None:
- name = os.path.basename(filename)
-
- # Assemble the globals.
- if globs is None:
- globs = {}
- else:
- globs = globs.copy()
- if extraglobs is not None:
- globs.update(extraglobs)
-
- if raise_on_error:
- runner = DebugRunner(verbose=verbose, optionflags=optionflags)
- else:
- runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
-
- # Read the file, convert it to a test, and run it.
- s = open(filename).read()
- test = parser.get_doctest(s, globs, name, filename, 0)
- runner.run(test)
-
- if report:
- runner.summarize()
-
- if master is None:
- master = runner
- else:
- master.merge(runner)
-
- return runner.failures, runner.tries
-
-def run_docstring_examples(f, globs, verbose=False, name="NoName",
- compileflags=None, optionflags=0):
- """
- Test examples in the given object's docstring (`f`), using `globs`
- as globals. Optional argument `name` is used in failure messages.
- If the optional argument `verbose` is true, then generate output
- even if there are no failures.
-
- `compileflags` gives the set of flags that should be used by the
- Python compiler when running the examples. If not specified, then
- it will default to the set of future-import flags that apply to
- `globs`.
-
- Optional keyword arg `optionflags` specifies options for the
- testing and output. See the documentation for `testmod` for more
- information.
- """
- # Find, parse, and run all tests in the given module.
- finder = DocTestFinder(verbose=verbose, recurse=False)
- runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
- for test in finder.find(f, name, globs=globs):
- runner.run(test, compileflags=compileflags)
-
-######################################################################
-## 7. Tester
-######################################################################
-# This is provided only for backwards compatibility. It's not
-# actually used in any way.
-
-class Tester:
- def __init__(self, mod=None, globs=None, verbose=None,
- isprivate=None, optionflags=0):
-
- warnings.warn("class Tester is deprecated; "
- "use class doctest.DocTestRunner instead",
- DeprecationWarning, stacklevel=2)
- if mod is None and globs is None:
- raise TypeError("Tester.__init__: must specify mod or globs")
- if mod is not None and not inspect.ismodule(mod):
- raise TypeError("Tester.__init__: mod must be a module; %r" %
- (mod,))
- if globs is None:
- globs = mod.__dict__
- self.globs = globs
-
- self.verbose = verbose
- self.isprivate = isprivate
- self.optionflags = optionflags
- self.testfinder = DocTestFinder(_namefilter=isprivate)
- self.testrunner = DocTestRunner(verbose=verbose,
- optionflags=optionflags)
-
- def runstring(self, s, name):
- test = DocTestParser().get_doctest(s, self.globs, name, None, None)
- if self.verbose:
- print "Running string", name
- (f,t) = self.testrunner.run(test)
- if self.verbose:
- print f, "of", t, "examples failed in string", name
- return (f,t)
-
- def rundoc(self, object, name=None, module=None):
- f = t = 0
- tests = self.testfinder.find(object, name, module=module,
- globs=self.globs)
- for test in tests:
- (f2, t2) = self.testrunner.run(test)
- (f,t) = (f+f2, t+t2)
- return (f,t)
-
- def rundict(self, d, name, module=None):
- import new
- m = new.module(name)
- m.__dict__.update(d)
- if module is None:
- module = False
- return self.rundoc(m, name, module)
-
- def run__test__(self, d, name):
- import new
- m = new.module(name)
- m.__test__ = d
- return self.rundoc(m, name)
-
- def summarize(self, verbose=None):
- return self.testrunner.summarize(verbose)
-
- def merge(self, other):
- self.testrunner.merge(other.testrunner)
-
-######################################################################
-## 8. Unittest Support
-######################################################################
-
-_unittest_reportflags = 0
-
-def set_unittest_reportflags(flags):
- global _unittest_reportflags
-
- if (flags & REPORTING_FLAGS) != flags:
- raise ValueError("Only reporting flags allowed", flags)
- old = _unittest_reportflags
- _unittest_reportflags = flags
- return old
-
-
-class DocTestCase(unittest.TestCase):
-
- def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
- checker=None):
-
- unittest.TestCase.__init__(self)
- self._dt_optionflags = optionflags
- self._dt_checker = checker
- self._dt_test = test
- self._dt_setUp = setUp
- self._dt_tearDown = tearDown
-
- def setUp(self):
- test = self._dt_test
-
- if self._dt_setUp is not None:
- self._dt_setUp(test)
-
- def tearDown(self):
- test = self._dt_test
-
- if self._dt_tearDown is not None:
- self._dt_tearDown(test)
-
- test.globs.clear()
-
- def runTest(self):
- test = self._dt_test
- old = sys.stdout
- new = StringIO()
- optionflags = self._dt_optionflags
-
- if not (optionflags & REPORTING_FLAGS):
- # The option flags don't include any reporting flags,
- # so add the default reporting flags
- optionflags |= _unittest_reportflags
-
- runner = DocTestRunner(optionflags=optionflags,
- checker=self._dt_checker, verbose=False)
-
- try:
- runner.DIVIDER = "-"*70
- failures, tries = runner.run(
- test, out=new.write, clear_globs=False)
- finally:
- sys.stdout = old
-
- if failures:
- raise self.failureException(self.format_failure(new.getvalue()))
-
- def format_failure(self, err):
- test = self._dt_test
- if test.lineno is None:
- lineno = 'unknown line number'
- else:
- lineno = '%s' % test.lineno
- lname = '.'.join(test.name.split('.')[-1:])
- return ('Failed doctest test for %s\n'
- ' File "%s", line %s, in %s\n\n%s'
- % (test.name, test.filename, lineno, lname, err)
- )
-
- def debug(self):
- self.setUp()
- runner = DebugRunner(optionflags=self._dt_optionflags,
- checker=self._dt_checker, verbose=False)
- runner.run(self._dt_test)
- self.tearDown()
-
- def id(self):
- return self._dt_test.name
-
- def __repr__(self):
- name = self._dt_test.name.split('.')
- return "%s (%s)" % (name[-1], '.'.join(name[:-1]))
-
- __str__ = __repr__
-
- def shortDescription(self):
- return "Doctest: " + self._dt_test.name
-
-def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
- **options):
- """
- Convert doctest tests for a module to a unittest test suite.
-
- This converts each documentation string in a module that
- contains doctest tests to a unittest test case. If any of the
- tests in a doc string fail, then the test case fails. An exception
- is raised showing the name of the file containing the test and a
- (sometimes approximate) line number.
-
- The `module` argument provides the module to be tested. The argument
- can be either a module or a module name.
-
- If no argument is given, the calling module is used.
-
- A number of options may be provided as keyword arguments:
-
- setUp
- A set-up function. This is called before running the
- tests in each file. The setUp function will be passed a DocTest
- object. The setUp function can access the test globals as the
- globs attribute of the test passed.
-
- tearDown
- A tear-down function. This is called after running the
- tests in each file. The tearDown function will be passed a DocTest
- object. The tearDown function can access the test globals as the
- globs attribute of the test passed.
-
- globs
- A dictionary containing initial global variables for the tests.
-
- optionflags
- A set of doctest option flags expressed as an integer.
- """
-
- if test_finder is None:
- test_finder = DocTestFinder()
-
- module = _normalize_module(module)
- tests = test_finder.find(module, globs=globs, extraglobs=extraglobs)
- if globs is None:
- globs = module.__dict__
- if not tests:
- # Why do we want to do this? Because it reveals a bug that might
- # otherwise be hidden.
- raise ValueError(module, "has no tests")
-
- tests.sort()
- suite = unittest.TestSuite()
- for test in tests:
- if len(test.examples) == 0:
- continue
- if not test.filename:
- filename = module.__file__
- if filename[-4:] in (".pyc", ".pyo"):
- filename = filename[:-1]
- elif sys.platform.startswith('java') and \
- filename.endswith('$py.class'):
- filename = '%s.py' % filename[:-9]
- test.filename = filename
- suite.addTest(DocTestCase(test, **options))
-
- return suite
-
-class DocFileCase(DocTestCase):
-
- def id(self):
- return '_'.join(self._dt_test.name.split('.'))
-
- def __repr__(self):
- return self._dt_test.filename
- __str__ = __repr__
-
- def format_failure(self, err):
- return ('Failed doctest test for %s\n File "%s", line 0\n\n%s'
- % (self._dt_test.name, self._dt_test.filename, err)
- )
-
-def DocFileTest(path, module_relative=True, package=None,
- globs=None, parser=DocTestParser(), **options):
- if globs is None:
- globs = {}
-
- if package and not module_relative:
- raise ValueError("Package may only be specified for module-"
- "relative paths.")
-
- # Relativize the path.
- if module_relative:
- package = _normalize_module(package)
- path = _module_relative_path(package, path)
-
- # Find the file and read it.
- name = os.path.basename(path)
- doc = open(path).read()
-
- # Convert it to a test, and wrap it in a DocFileCase.
- test = parser.get_doctest(doc, globs, name, path, 0)
- return DocFileCase(test, **options)
-
-def DocFileSuite(*paths, **kw):
- """A unittest suite for one or more doctest files.
-
- The path to each doctest file is given as a string; the
- interpretation of that string depends on the keyword argument
- "module_relative".
-
- A number of options may be provided as keyword arguments:
-
- module_relative
- If "module_relative" is True, then the given file paths are
- interpreted as os-independent module-relative paths. By
- default, these paths are relative to the calling module's
- directory; but if the "package" argument is specified, then
- they are relative to that package. To ensure os-independence,
- "filename" should use "/" characters to separate path
- segments, and may not be an absolute path (i.e., it may not
- begin with "/").
-
- If "module_relative" is False, then the given file paths are
- interpreted as os-specific paths. These paths may be absolute
- or relative (to the current working directory).
-
- package
- A Python package or the name of a Python package whose directory
- should be used as the base directory for module relative paths.
- If "package" is not specified, then the calling module's
- directory is used as the base directory for module relative
- filenames. It is an error to specify "package" if
- "module_relative" is False.
-
- setUp
- A set-up function. This is called before running the
- tests in each file. The setUp function will be passed a DocTest
- object. The setUp function can access the test globals as the
- globs attribute of the test passed.
-
- tearDown
- A tear-down function. This is called after running the
- tests in each file. The tearDown function will be passed a DocTest
- object. The tearDown function can access the test globals as the
- globs attribute of the test passed.
-
- globs
- A dictionary containing initial global variables for the tests.
-
- optionflags
- A set of doctest option flags expressed as an integer.
-
- parser
- A DocTestParser (or subclass) that should be used to extract
- tests from the files.
- """
- suite = unittest.TestSuite()
-
- # We do this here so that _normalize_module is called at the right
- # level. If it were called in DocFileTest, then this function
- # would be the caller and we might guess the package incorrectly.
- if kw.get('module_relative', True):
- kw['package'] = _normalize_module(kw.get('package'))
-
- for path in paths:
- suite.addTest(DocFileTest(path, **kw))
-
- return suite
-
-######################################################################
-## 9. Debugging Support
-######################################################################
-
-def script_from_examples(s):
- output = []
- for piece in DocTestParser().parse(s):
- if isinstance(piece, Example):
- # Add the example's source code (strip trailing NL)
- output.append(piece.source[:-1])
- # Add the expected output:
- want = piece.want
- if want:
- output.append('# Expected:')
- output += ['## '+l for l in want.split('\n')[:-1]]
- else:
- # Add non-example text.
- output += [_comment_line(l)
- for l in piece.split('\n')[:-1]]
-
- # Trim junk on both ends.
- while output and output[-1] == '#':
- output.pop()
- while output and output[0] == '#':
- output.pop(0)
- # Combine the output, and return it.
- # Add a courtesy newline to prevent exec from choking (see bug #1172785)
- return '\n'.join(output) + '\n'
-
-def testsource(module, name):
- """Extract the test sources from a doctest docstring as a script.
-
- Provide the module (or dotted name of the module) containing the
- test to be debugged and the name (within the module) of the object
- with the doc string with tests to be debugged.
- """
- module = _normalize_module(module)
- tests = DocTestFinder().find(module)
- test = [t for t in tests if t.name == name]
- if not test:
- raise ValueError(name, "not found in tests")
- test = test[0]
- testsrc = script_from_examples(test.docstring)
- return testsrc
-
-def debug_src(src, pm=False, globs=None):
- """Debug a single doctest docstring, in argument `src`'"""
- testsrc = script_from_examples(src)
- debug_script(testsrc, pm, globs)
-
-def debug_script(src, pm=False, globs=None):
- "Debug a test script. `src` is the script, as a string."
- import pdb
-
- # Note that tempfile.NameTemporaryFile() cannot be used. As the
- # docs say, a file so created cannot be opened by name a second time
- # on modern Windows boxes, and execfile() needs to open it.
- srcfilename = tempfile.mktemp(".py", "doctestdebug")
- f = open(srcfilename, 'w')
- f.write(src)
- f.close()
-
- try:
- if globs:
- globs = globs.copy()
- else:
- globs = {}
-
- if pm:
- try:
- execfile(srcfilename, globs, globs)
- except:
- print sys.exc_info()[1]
- pdb.post_mortem(sys.exc_info()[2])
- else:
- # Note that %r is vital here. '%s' instead can, e.g., cause
- # backslashes to get treated as metacharacters on Windows.
- pdb.run("execfile(%r)" % srcfilename, globs, globs)
-
- finally:
- os.remove(srcfilename)
-
-def debug(module, name, pm=False):
- """Debug a single doctest docstring.
-
- Provide the module (or dotted name of the module) containing the
- test to be debugged and the name (within the module) of the object
- with the docstring with tests to be debugged.
- """
- module = _normalize_module(module)
- testsrc = testsource(module, name)
- debug_script(testsrc, pm, module.__dict__)
-
-
-__test__ = {}
diff --git a/lib/spack/external/nose/failure.py b/lib/spack/external/nose/failure.py
deleted file mode 100644
index c5fabfda5e..0000000000
--- a/lib/spack/external/nose/failure.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import logging
-import unittest
-from traceback import format_tb
-from nose.pyversion import is_base_exception
-
-log = logging.getLogger(__name__)
-
-
-__all__ = ['Failure']
-
-
-class Failure(unittest.TestCase):
- """Unloadable or unexecutable test.
-
- A Failure case is placed in a test suite to indicate the presence of a
- test that could not be loaded or executed. A common example is a test
- module that fails to import.
-
- """
- __test__ = False # do not collect
- def __init__(self, exc_class, exc_val, tb=None, address=None):
- log.debug("A failure! %s %s %s", exc_class, exc_val, format_tb(tb))
- self.exc_class = exc_class
- self.exc_val = exc_val
- self.tb = tb
- self._address = address
- unittest.TestCase.__init__(self)
-
- def __str__(self):
- return "Failure: %s (%s)" % (
- getattr(self.exc_class, '__name__', self.exc_class), self.exc_val)
-
- def address(self):
- return self._address
-
- def runTest(self):
- if self.tb is not None:
- if is_base_exception(self.exc_val):
- raise self.exc_val, None, self.tb
- raise self.exc_class, self.exc_val, self.tb
- else:
- raise self.exc_class(self.exc_val)
diff --git a/lib/spack/external/nose/importer.py b/lib/spack/external/nose/importer.py
deleted file mode 100644
index e677658ce6..0000000000
--- a/lib/spack/external/nose/importer.py
+++ /dev/null
@@ -1,167 +0,0 @@
-"""Implements an importer that looks only in specific path (ignoring
-sys.path), and uses a per-path cache in addition to sys.modules. This is
-necessary because test modules in different directories frequently have the
-same names, which means that the first loaded would mask the rest when using
-the builtin importer.
-"""
-import logging
-import os
-import sys
-from nose.config import Config
-
-from imp import find_module, load_module, acquire_lock, release_lock
-
-log = logging.getLogger(__name__)
-
-try:
- _samefile = os.path.samefile
-except AttributeError:
- def _samefile(src, dst):
- return (os.path.normcase(os.path.realpath(src)) ==
- os.path.normcase(os.path.realpath(dst)))
-
-
-class Importer(object):
- """An importer class that does only path-specific imports. That
- is, the given module is not searched for on sys.path, but only at
- the path or in the directory specified.
- """
- def __init__(self, config=None):
- if config is None:
- config = Config()
- self.config = config
-
- def importFromPath(self, path, fqname):
- """Import a dotted-name package whose tail is at path. In other words,
- given foo.bar and path/to/foo/bar.py, import foo from path/to/foo then
- bar from path/to/foo/bar, returning bar.
- """
- # find the base dir of the package
- path_parts = os.path.normpath(os.path.abspath(path)).split(os.sep)
- name_parts = fqname.split('.')
- if path_parts[-1] == '__init__.py':
- path_parts.pop()
- path_parts = path_parts[:-(len(name_parts))]
- dir_path = os.sep.join(path_parts)
- # then import fqname starting from that dir
- return self.importFromDir(dir_path, fqname)
-
- def importFromDir(self, dir, fqname):
- """Import a module *only* from path, ignoring sys.path and
- reloading if the version in sys.modules is not the one we want.
- """
- dir = os.path.normpath(os.path.abspath(dir))
- log.debug("Import %s from %s", fqname, dir)
-
- # FIXME reimplement local per-dir cache?
-
- # special case for __main__
- if fqname == '__main__':
- return sys.modules[fqname]
-
- if self.config.addPaths:
- add_path(dir, self.config)
-
- path = [dir]
- parts = fqname.split('.')
- part_fqname = ''
- mod = parent = fh = None
-
- for part in parts:
- if part_fqname == '':
- part_fqname = part
- else:
- part_fqname = "%s.%s" % (part_fqname, part)
- try:
- acquire_lock()
- log.debug("find module part %s (%s) in %s",
- part, part_fqname, path)
- fh, filename, desc = find_module(part, path)
- old = sys.modules.get(part_fqname)
- if old is not None:
- # test modules frequently have name overlap; make sure
- # we get a fresh copy of anything we are trying to load
- # from a new path
- log.debug("sys.modules has %s as %s", part_fqname, old)
- if (self.sameModule(old, filename)
- or (self.config.firstPackageWins and
- getattr(old, '__path__', None))):
- mod = old
- else:
- del sys.modules[part_fqname]
- mod = load_module(part_fqname, fh, filename, desc)
- else:
- mod = load_module(part_fqname, fh, filename, desc)
- finally:
- if fh:
- fh.close()
- release_lock()
- if parent:
- setattr(parent, part, mod)
- if hasattr(mod, '__path__'):
- path = mod.__path__
- parent = mod
- return mod
-
- def _dirname_if_file(self, filename):
- # We only take the dirname if we have a path to a non-dir,
- # because taking the dirname of a symlink to a directory does not
- # give the actual directory parent.
- if os.path.isdir(filename):
- return filename
- else:
- return os.path.dirname(filename)
-
- def sameModule(self, mod, filename):
- mod_paths = []
- if hasattr(mod, '__path__'):
- for path in mod.__path__:
- mod_paths.append(self._dirname_if_file(path))
- elif hasattr(mod, '__file__'):
- mod_paths.append(self._dirname_if_file(mod.__file__))
- else:
- # builtin or other module-like object that
- # doesn't have __file__; must be new
- return False
- new_path = self._dirname_if_file(filename)
- for mod_path in mod_paths:
- log.debug(
- "module already loaded? mod: %s new: %s",
- mod_path, new_path)
- if _samefile(mod_path, new_path):
- return True
- return False
-
-
-def add_path(path, config=None):
- """Ensure that the path, or the root of the current package (if
- path is in a package), is in sys.path.
- """
-
- # FIXME add any src-looking dirs seen too... need to get config for that
-
- log.debug('Add path %s' % path)
- if not path:
- return []
- added = []
- parent = os.path.dirname(path)
- if (parent
- and os.path.exists(os.path.join(path, '__init__.py'))):
- added.extend(add_path(parent, config))
- elif not path in sys.path:
- log.debug("insert %s into sys.path", path)
- sys.path.insert(0, path)
- added.append(path)
- if config and config.srcDirs:
- for dirname in config.srcDirs:
- dirpath = os.path.join(path, dirname)
- if os.path.isdir(dirpath):
- sys.path.insert(0, dirpath)
- added.append(dirpath)
- return added
-
-
-def remove_path(path):
- log.debug('Remove path %s' % path)
- if path in sys.path:
- sys.path.remove(path)
diff --git a/lib/spack/external/nose/inspector.py b/lib/spack/external/nose/inspector.py
deleted file mode 100644
index a6c4a3e3b6..0000000000
--- a/lib/spack/external/nose/inspector.py
+++ /dev/null
@@ -1,207 +0,0 @@
-"""Simple traceback introspection. Used to add additional information to
-AssertionErrors in tests, so that failure messages may be more informative.
-"""
-import inspect
-import logging
-import re
-import sys
-import textwrap
-import tokenize
-
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-
-log = logging.getLogger(__name__)
-
-def inspect_traceback(tb):
- """Inspect a traceback and its frame, returning source for the expression
- where the exception was raised, with simple variable replacement performed
- and the line on which the exception was raised marked with '>>'
- """
- log.debug('inspect traceback %s', tb)
-
- # we only want the innermost frame, where the exception was raised
- while tb.tb_next:
- tb = tb.tb_next
-
- frame = tb.tb_frame
- lines, exc_line = tbsource(tb)
-
- # figure out the set of lines to grab.
- inspect_lines, mark_line = find_inspectable_lines(lines, exc_line)
- src = StringIO(textwrap.dedent(''.join(inspect_lines)))
- exp = Expander(frame.f_locals, frame.f_globals)
-
- while inspect_lines:
- try:
- for tok in tokenize.generate_tokens(src.readline):
- exp(*tok)
- except tokenize.TokenError, e:
- # this can happen if our inspectable region happens to butt up
- # against the end of a construct like a docstring with the closing
- # """ on separate line
- log.debug("Tokenizer error: %s", e)
- inspect_lines.pop(0)
- mark_line -= 1
- src = StringIO(textwrap.dedent(''.join(inspect_lines)))
- exp = Expander(frame.f_locals, frame.f_globals)
- continue
- break
- padded = []
- if exp.expanded_source:
- exp_lines = exp.expanded_source.split('\n')
- ep = 0
- for line in exp_lines:
- if ep == mark_line:
- padded.append('>> ' + line)
- else:
- padded.append(' ' + line)
- ep += 1
- return '\n'.join(padded)
-
-
-def tbsource(tb, context=6):
- """Get source from a traceback object.
-
- A tuple of two things is returned: a list of lines of context from
- the source code, and the index of the current line within that list.
- The optional second argument specifies the number of lines of context
- to return, which are centered around the current line.
-
- .. Note ::
- This is adapted from inspect.py in the python 2.4 standard library,
- since a bug in the 2.3 version of inspect prevents it from correctly
- locating source lines in a traceback frame.
- """
-
- lineno = tb.tb_lineno
- frame = tb.tb_frame
-
- if context > 0:
- start = lineno - 1 - context//2
- log.debug("lineno: %s start: %s", lineno, start)
-
- try:
- lines, dummy = inspect.findsource(frame)
- except IOError:
- lines, index = [''], 0
- else:
- all_lines = lines
- start = max(start, 1)
- start = max(0, min(start, len(lines) - context))
- lines = lines[start:start+context]
- index = lineno - 1 - start
-
- # python 2.5 compat: if previous line ends in a continuation,
- # decrement start by 1 to match 2.4 behavior
- if sys.version_info >= (2, 5) and index > 0:
- while lines[index-1].strip().endswith('\\'):
- start -= 1
- lines = all_lines[start:start+context]
- else:
- lines, index = [''], 0
- log.debug("tbsource lines '''%s''' around index %s", lines, index)
- return (lines, index)
-
-
-def find_inspectable_lines(lines, pos):
- """Find lines in home that are inspectable.
-
- Walk back from the err line up to 3 lines, but don't walk back over
- changes in indent level.
-
- Walk forward up to 3 lines, counting \ separated lines as 1. Don't walk
- over changes in indent level (unless part of an extended line)
- """
- cnt = re.compile(r'\\[\s\n]*$')
- df = re.compile(r':[\s\n]*$')
- ind = re.compile(r'^(\s*)')
- toinspect = []
- home = lines[pos]
- home_indent = ind.match(home).groups()[0]
-
- before = lines[max(pos-3, 0):pos]
- before.reverse()
- after = lines[pos+1:min(pos+4, len(lines))]
-
- for line in before:
- if ind.match(line).groups()[0] == home_indent:
- toinspect.append(line)
- else:
- break
- toinspect.reverse()
- toinspect.append(home)
- home_pos = len(toinspect)-1
- continued = cnt.search(home)
- for line in after:
- if ((continued or ind.match(line).groups()[0] == home_indent)
- and not df.search(line)):
- toinspect.append(line)
- continued = cnt.search(line)
- else:
- break
- log.debug("Inspecting lines '''%s''' around %s", toinspect, home_pos)
- return toinspect, home_pos
-
-
-class Expander:
- """Simple expression expander. Uses tokenize to find the names and
- expands any that can be looked up in the frame.
- """
- def __init__(self, locals, globals):
- self.locals = locals
- self.globals = globals
- self.lpos = None
- self.expanded_source = ''
-
- def __call__(self, ttype, tok, start, end, line):
- # TODO
- # deal with unicode properly
-
- # TODO
- # Dealing with instance members
- # always keep the last thing seen
- # if the current token is a dot,
- # get ready to getattr(lastthing, this thing) on the
- # next call.
-
- if self.lpos is not None:
- if start[1] >= self.lpos:
- self.expanded_source += ' ' * (start[1]-self.lpos)
- elif start[1] < self.lpos:
- # newline, indent correctly
- self.expanded_source += ' ' * start[1]
- self.lpos = end[1]
-
- if ttype == tokenize.INDENT:
- pass
- elif ttype == tokenize.NAME:
- # Clean this junk up
- try:
- val = self.locals[tok]
- if callable(val):
- val = tok
- else:
- val = repr(val)
- except KeyError:
- try:
- val = self.globals[tok]
- if callable(val):
- val = tok
- else:
- val = repr(val)
-
- except KeyError:
- val = tok
- # FIXME... not sure how to handle things like funcs, classes
- # FIXME this is broken for some unicode strings
- self.expanded_source += val
- else:
- self.expanded_source += tok
- # if this is the end of the line and the line ends with
- # \, then tack a \ and newline onto the output
- # print line[end[1]:]
- if re.match(r'\s+\\\n', line[end[1]:]):
- self.expanded_source += ' \\\n'
diff --git a/lib/spack/external/nose/loader.py b/lib/spack/external/nose/loader.py
deleted file mode 100644
index 3744e54ff6..0000000000
--- a/lib/spack/external/nose/loader.py
+++ /dev/null
@@ -1,623 +0,0 @@
-"""
-Test Loader
------------
-
-nose's test loader implements the same basic functionality as its
-superclass, unittest.TestLoader, but extends it by more liberal
-interpretations of what may be a test and how a test may be named.
-"""
-from __future__ import generators
-
-import logging
-import os
-import sys
-import unittest
-import types
-from inspect import isfunction
-from nose.pyversion import unbound_method, ismethod
-from nose.case import FunctionTestCase, MethodTestCase
-from nose.failure import Failure
-from nose.config import Config
-from nose.importer import Importer, add_path, remove_path
-from nose.selector import defaultSelector, TestAddress
-from nose.util import func_lineno, getpackage, isclass, isgenerator, \
- ispackage, regex_last_key, resolve_name, transplant_func, \
- transplant_class, test_address
-from nose.suite import ContextSuiteFactory, ContextList, LazySuite
-from nose.pyversion import sort_list, cmp_to_key
-
-
-log = logging.getLogger(__name__)
-#log.setLevel(logging.DEBUG)
-
-# for efficiency and easier mocking
-op_normpath = os.path.normpath
-op_abspath = os.path.abspath
-op_join = os.path.join
-op_isdir = os.path.isdir
-op_isfile = os.path.isfile
-
-
-__all__ = ['TestLoader', 'defaultTestLoader']
-
-
-class TestLoader(unittest.TestLoader):
- """Test loader that extends unittest.TestLoader to:
-
- * Load tests from test-like functions and classes that are not
- unittest.TestCase subclasses
- * Find and load test modules in a directory
- * Support tests that are generators
- * Support easy extensions of or changes to that behavior through plugins
- """
- config = None
- importer = None
- workingDir = None
- selector = None
- suiteClass = None
-
- def __init__(self, config=None, importer=None, workingDir=None,
- selector=None):
- """Initialize a test loader.
-
- Parameters (all optional):
-
- * config: provide a `nose.config.Config`_ or other config class
- instance; if not provided a `nose.config.Config`_ with
- default values is used.
- * importer: provide an importer instance that implements
- `importFromPath`. If not provided, a
- `nose.importer.Importer`_ is used.
- * workingDir: the directory to which file and module names are
- relative. If not provided, assumed to be the current working
- directory.
- * selector: a selector class or instance. If a class is
- provided, it will be instantiated with one argument, the
- current config. If not provided, a `nose.selector.Selector`_
- is used.
- """
- if config is None:
- config = Config()
- if importer is None:
- importer = Importer(config=config)
- if workingDir is None:
- workingDir = config.workingDir
- if selector is None:
- selector = defaultSelector(config)
- elif isclass(selector):
- selector = selector(config)
- self.config = config
- self.importer = importer
- self.workingDir = op_normpath(op_abspath(workingDir))
- self.selector = selector
- if config.addPaths:
- add_path(workingDir, config)
- self.suiteClass = ContextSuiteFactory(config=config)
-
- self._visitedPaths = set([])
-
- unittest.TestLoader.__init__(self)
-
- def getTestCaseNames(self, testCaseClass):
- """Override to select with selector, unless
- config.getTestCaseNamesCompat is True
- """
- if self.config.getTestCaseNamesCompat:
- return unittest.TestLoader.getTestCaseNames(self, testCaseClass)
-
- def wanted(attr, cls=testCaseClass, sel=self.selector):
- item = getattr(cls, attr, None)
- if isfunction(item):
- item = unbound_method(cls, item)
- elif not ismethod(item):
- return False
- return sel.wantMethod(item)
-
- cases = filter(wanted, dir(testCaseClass))
-
- # add runTest if nothing else picked
- if not cases and hasattr(testCaseClass, 'runTest'):
- cases = ['runTest']
- if self.sortTestMethodsUsing:
- sort_list(cases, cmp_to_key(self.sortTestMethodsUsing))
- return cases
-
- def _haveVisited(self, path):
- # For cases where path is None, we always pretend we haven't visited
- # them.
- if path is None:
- return False
-
- return path in self._visitedPaths
-
- def _addVisitedPath(self, path):
- if path is not None:
- self._visitedPaths.add(path)
-
- def loadTestsFromDir(self, path):
- """Load tests from the directory at path. This is a generator
- -- each suite of tests from a module or other file is yielded
- and is expected to be executed before the next file is
- examined.
- """
- log.debug("load from dir %s", path)
- plugins = self.config.plugins
- plugins.beforeDirectory(path)
- if self.config.addPaths:
- paths_added = add_path(path, self.config)
-
- entries = os.listdir(path)
- sort_list(entries, regex_last_key(self.config.testMatch))
- for entry in entries:
- # this hard-coded initial-dot test will be removed:
- # http://code.google.com/p/python-nose/issues/detail?id=82
- if entry.startswith('.'):
- continue
- entry_path = op_abspath(op_join(path, entry))
- is_file = op_isfile(entry_path)
- wanted = False
- if is_file:
- is_dir = False
- wanted = self.selector.wantFile(entry_path)
- else:
- is_dir = op_isdir(entry_path)
- if is_dir:
- # this hard-coded initial-underscore test will be removed:
- # http://code.google.com/p/python-nose/issues/detail?id=82
- if entry.startswith('_'):
- continue
- wanted = self.selector.wantDirectory(entry_path)
- is_package = ispackage(entry_path)
-
- # Python 3.3 now implements PEP 420: Implicit Namespace Packages.
- # As a result, it's now possible that parent paths that have a
- # segment with the same basename as our package ends up
- # in module.__path__. So we have to keep track of what we've
- # visited, and not-revisit them again.
- if wanted and not self._haveVisited(entry_path):
- self._addVisitedPath(entry_path)
- if is_file:
- plugins.beforeContext()
- if entry.endswith('.py'):
- yield self.loadTestsFromName(
- entry_path, discovered=True)
- else:
- yield self.loadTestsFromFile(entry_path)
- plugins.afterContext()
- elif is_package:
- # Load the entry as a package: given the full path,
- # loadTestsFromName() will figure it out
- yield self.loadTestsFromName(
- entry_path, discovered=True)
- else:
- # Another test dir in this one: recurse lazily
- yield self.suiteClass(
- lambda: self.loadTestsFromDir(entry_path))
- tests = []
- for test in plugins.loadTestsFromDir(path):
- tests.append(test)
- # TODO: is this try/except needed?
- try:
- if tests:
- yield self.suiteClass(tests)
- except (KeyboardInterrupt, SystemExit):
- raise
- except:
- yield self.suiteClass([Failure(*sys.exc_info())])
-
- # pop paths
- if self.config.addPaths:
- for p in paths_added:
- remove_path(p)
- plugins.afterDirectory(path)
-
- def loadTestsFromFile(self, filename):
- """Load tests from a non-module file. Default is to raise a
- ValueError; plugins may implement `loadTestsFromFile` to
- provide a list of tests loaded from the file.
- """
- log.debug("Load from non-module file %s", filename)
- try:
- tests = [test for test in
- self.config.plugins.loadTestsFromFile(filename)]
- if tests:
- # Plugins can yield False to indicate that they were
- # unable to load tests from a file, but it was not an
- # error -- the file just had no tests to load.
- tests = filter(None, tests)
- return self.suiteClass(tests)
- else:
- # Nothing was able to even try to load from this file
- open(filename, 'r').close() # trigger os error
- raise ValueError("Unable to load tests from file %s"
- % filename)
- except (KeyboardInterrupt, SystemExit):
- raise
- except:
- exc = sys.exc_info()
- return self.suiteClass(
- [Failure(exc[0], exc[1], exc[2],
- address=(filename, None, None))])
-
- def loadTestsFromGenerator(self, generator, module):
- """Lazy-load tests from a generator function. The generator function
- may yield either:
-
- * a callable, or
- * a function name resolvable within the same module
- """
- def generate(g=generator, m=module):
- try:
- for test in g():
- test_func, arg = self.parseGeneratedTest(test)
- if not callable(test_func):
- test_func = getattr(m, test_func)
- yield FunctionTestCase(test_func, arg=arg, descriptor=g)
- except KeyboardInterrupt:
- raise
- except:
- exc = sys.exc_info()
- yield Failure(exc[0], exc[1], exc[2],
- address=test_address(generator))
- return self.suiteClass(generate, context=generator, can_split=False)
-
- def loadTestsFromGeneratorMethod(self, generator, cls):
- """Lazy-load tests from a generator method.
-
- This is more complicated than loading from a generator function,
- since a generator method may yield:
-
- * a function
- * a bound or unbound method, or
- * a method name
- """
- # convert the unbound generator method
- # into a bound method so it can be called below
- if hasattr(generator, 'im_class'):
- cls = generator.im_class
- inst = cls()
- method = generator.__name__
- generator = getattr(inst, method)
-
- def generate(g=generator, c=cls):
- try:
- for test in g():
- test_func, arg = self.parseGeneratedTest(test)
- if not callable(test_func):
- test_func = unbound_method(c, getattr(c, test_func))
- if ismethod(test_func):
- yield MethodTestCase(test_func, arg=arg, descriptor=g)
- elif callable(test_func):
- # In this case we're forcing the 'MethodTestCase'
- # to run the inline function as its test call,
- # but using the generator method as the 'method of
- # record' (so no need to pass it as the descriptor)
- yield MethodTestCase(g, test=test_func, arg=arg)
- else:
- yield Failure(
- TypeError,
- "%s is not a callable or method" % test_func)
- except KeyboardInterrupt:
- raise
- except:
- exc = sys.exc_info()
- yield Failure(exc[0], exc[1], exc[2],
- address=test_address(generator))
- return self.suiteClass(generate, context=generator, can_split=False)
-
- def loadTestsFromModule(self, module, path=None, discovered=False):
- """Load all tests from module and return a suite containing
- them. If the module has been discovered and is not test-like,
- the suite will be empty by default, though plugins may add
- their own tests.
- """
- log.debug("Load from module %s", module)
- tests = []
- test_classes = []
- test_funcs = []
- # For *discovered* modules, we only load tests when the module looks
- # testlike. For modules we've been directed to load, we always
- # look for tests. (discovered is set to True by loadTestsFromDir)
- if not discovered or self.selector.wantModule(module):
- for item in dir(module):
- test = getattr(module, item, None)
- # print "Check %s (%s) in %s" % (item, test, module.__name__)
- if isclass(test):
- if self.selector.wantClass(test):
- test_classes.append(test)
- elif isfunction(test) and self.selector.wantFunction(test):
- test_funcs.append(test)
- sort_list(test_classes, lambda x: x.__name__)
- sort_list(test_funcs, func_lineno)
- tests = map(lambda t: self.makeTest(t, parent=module),
- test_classes + test_funcs)
-
- # Now, descend into packages
- # FIXME can or should this be lazy?
- # is this syntax 2.2 compatible?
- module_paths = getattr(module, '__path__', [])
-
- if path:
- path = os.path.normcase(os.path.realpath(path))
-
- for module_path in module_paths:
- log.debug("Load tests from module path %s?", module_path)
- log.debug("path: %s os.path.realpath(%s): %s",
- path, os.path.normcase(module_path),
- os.path.realpath(os.path.normcase(module_path)))
- if (self.config.traverseNamespace or not path) or \
- os.path.realpath(
- os.path.normcase(module_path)).startswith(path):
- # Egg files can be on sys.path, so make sure the path is a
- # directory before trying to load from it.
- if os.path.isdir(module_path):
- tests.extend(self.loadTestsFromDir(module_path))
-
- for test in self.config.plugins.loadTestsFromModule(module, path):
- tests.append(test)
-
- return self.suiteClass(ContextList(tests, context=module))
-
- def loadTestsFromName(self, name, module=None, discovered=False):
- """Load tests from the entity with the given name.
-
- The name may indicate a file, directory, module, or any object
- within a module. See `nose.util.split_test_name` for details on
- test name parsing.
- """
- # FIXME refactor this method into little bites?
- log.debug("load from %s (%s)", name, module)
-
- suite = self.suiteClass
-
- # give plugins first crack
- plug_tests = self.config.plugins.loadTestsFromName(name, module)
- if plug_tests:
- return suite(plug_tests)
-
- addr = TestAddress(name, workingDir=self.workingDir)
- if module:
- # Two cases:
- # name is class.foo
- # The addr will be incorrect, since it thinks class.foo is
- # a dotted module name. It's actually a dotted attribute
- # name. In this case we want to use the full submitted
- # name as the name to load from the module.
- # name is module:class.foo
- # The addr will be correct. The part we want is the part after
- # the :, which is in addr.call.
- if addr.call:
- name = addr.call
- parent, obj = self.resolve(name, module)
- if (isclass(parent)
- and getattr(parent, '__module__', None) != module.__name__
- and not isinstance(obj, Failure)):
- parent = transplant_class(parent, module.__name__)
- obj = getattr(parent, obj.__name__)
- log.debug("parent %s obj %s module %s", parent, obj, module)
- if isinstance(obj, Failure):
- return suite([obj])
- else:
- return suite(ContextList([self.makeTest(obj, parent)],
- context=parent))
- else:
- if addr.module:
- try:
- if addr.filename is None:
- module = resolve_name(addr.module)
- else:
- self.config.plugins.beforeImport(
- addr.filename, addr.module)
- # FIXME: to support module.name names,
- # do what resolve-name does and keep trying to
- # import, popping tail of module into addr.call,
- # until we either get an import or run out of
- # module parts
- try:
- module = self.importer.importFromPath(
- addr.filename, addr.module)
- finally:
- self.config.plugins.afterImport(
- addr.filename, addr.module)
- except (KeyboardInterrupt, SystemExit):
- raise
- except:
- exc = sys.exc_info()
- return suite([Failure(exc[0], exc[1], exc[2],
- address=addr.totuple())])
- if addr.call:
- return self.loadTestsFromName(addr.call, module)
- else:
- return self.loadTestsFromModule(
- module, addr.filename,
- discovered=discovered)
- elif addr.filename:
- path = addr.filename
- if addr.call:
- package = getpackage(path)
- if package is None:
- return suite([
- Failure(ValueError,
- "Can't find callable %s in file %s: "
- "file is not a python module" %
- (addr.call, path),
- address=addr.totuple())])
- return self.loadTestsFromName(addr.call, module=package)
- else:
- if op_isdir(path):
- # In this case we *can* be lazy since we know
- # that each module in the dir will be fully
- # loaded before its tests are executed; we
- # also know that we're not going to be asked
- # to load from . and ./some_module.py *as part
- # of this named test load*
- return LazySuite(
- lambda: self.loadTestsFromDir(path))
- elif op_isfile(path):
- return self.loadTestsFromFile(path)
- else:
- return suite([
- Failure(OSError, "No such file %s" % path,
- address=addr.totuple())])
- else:
- # just a function? what to do? I think it can only be
- # handled when module is not None
- return suite([
- Failure(ValueError, "Unresolvable test name %s" % name,
- address=addr.totuple())])
-
- def loadTestsFromNames(self, names, module=None):
- """Load tests from all names, returning a suite containing all
- tests.
- """
- plug_res = self.config.plugins.loadTestsFromNames(names, module)
- if plug_res:
- suite, names = plug_res
- if suite:
- return self.suiteClass([
- self.suiteClass(suite),
- unittest.TestLoader.loadTestsFromNames(self, names, module)
- ])
- return unittest.TestLoader.loadTestsFromNames(self, names, module)
-
- def loadTestsFromTestCase(self, testCaseClass):
- """Load tests from a unittest.TestCase subclass.
- """
- cases = []
- plugins = self.config.plugins
- for case in plugins.loadTestsFromTestCase(testCaseClass):
- cases.append(case)
- # For efficiency in the most common case, just call and return from
- # super. This avoids having to extract cases and rebuild a context
- # suite when there are no plugin-contributed cases.
- if not cases:
- return super(TestLoader, self).loadTestsFromTestCase(testCaseClass)
- cases.extend(
- [case for case in
- super(TestLoader, self).loadTestsFromTestCase(testCaseClass)])
- return self.suiteClass(cases)
-
- def loadTestsFromTestClass(self, cls):
- """Load tests from a test class that is *not* a unittest.TestCase
- subclass.
-
- In this case, we can't depend on the class's `__init__` taking method
- name arguments, so we have to compose a MethodTestCase for each
- method in the class that looks testlike.
- """
- def wanted(attr, cls=cls, sel=self.selector):
- item = getattr(cls, attr, None)
- if isfunction(item):
- item = unbound_method(cls, item)
- elif not ismethod(item):
- return False
- return sel.wantMethod(item)
- cases = [self.makeTest(getattr(cls, case), cls)
- for case in filter(wanted, dir(cls))]
- for test in self.config.plugins.loadTestsFromTestClass(cls):
- cases.append(test)
- return self.suiteClass(ContextList(cases, context=cls))
-
- def makeTest(self, obj, parent=None):
- try:
- return self._makeTest(obj, parent)
- except (KeyboardInterrupt, SystemExit):
- raise
- except:
- exc = sys.exc_info()
- try:
- addr = test_address(obj)
- except KeyboardInterrupt:
- raise
- except:
- addr = None
- return Failure(exc[0], exc[1], exc[2], address=addr)
-
- def _makeTest(self, obj, parent=None):
- """Given a test object and its parent, return a test case
- or test suite.
- """
- plug_tests = []
- try:
- addr = test_address(obj)
- except KeyboardInterrupt:
- raise
- except:
- addr = None
- for test in self.config.plugins.makeTest(obj, parent):
- plug_tests.append(test)
- # TODO: is this try/except needed?
- try:
- if plug_tests:
- return self.suiteClass(plug_tests)
- except (KeyboardInterrupt, SystemExit):
- raise
- except:
- exc = sys.exc_info()
- return Failure(exc[0], exc[1], exc[2], address=addr)
-
- if isfunction(obj) and parent and not isinstance(parent, types.ModuleType):
- # This is a Python 3.x 'unbound method'. Wrap it with its
- # associated class..
- obj = unbound_method(parent, obj)
-
- if isinstance(obj, unittest.TestCase):
- return obj
- elif isclass(obj):
- if parent and obj.__module__ != parent.__name__:
- obj = transplant_class(obj, parent.__name__)
- if issubclass(obj, unittest.TestCase):
- return self.loadTestsFromTestCase(obj)
- else:
- return self.loadTestsFromTestClass(obj)
- elif ismethod(obj):
- if parent is None:
- parent = obj.__class__
- if issubclass(parent, unittest.TestCase):
- return parent(obj.__name__)
- else:
- if isgenerator(obj):
- return self.loadTestsFromGeneratorMethod(obj, parent)
- else:
- return MethodTestCase(obj)
- elif isfunction(obj):
- if parent and obj.__module__ != parent.__name__:
- obj = transplant_func(obj, parent.__name__)
- if isgenerator(obj):
- return self.loadTestsFromGenerator(obj, parent)
- else:
- return FunctionTestCase(obj)
- else:
- return Failure(TypeError,
- "Can't make a test from %s" % obj,
- address=addr)
-
- def resolve(self, name, module):
- """Resolve name within module
- """
- obj = module
- parts = name.split('.')
- for part in parts:
- parent, obj = obj, getattr(obj, part, None)
- if obj is None:
- # no such test
- obj = Failure(ValueError, "No such test %s" % name)
- return parent, obj
-
- def parseGeneratedTest(self, test):
- """Given the yield value of a test generator, return a func and args.
-
- This is used in the two loadTestsFromGenerator* methods.
-
- """
- if not isinstance(test, tuple): # yield test
- test_func, arg = (test, tuple())
- elif len(test) == 1: # yield (test,)
- test_func, arg = (test[0], tuple())
- else: # yield test, foo, bar, ...
- assert len(test) > 1 # sanity check
- test_func, arg = (test[0], test[1:])
- return test_func, arg
-
-defaultTestLoader = TestLoader
-
diff --git a/lib/spack/external/nose/plugins/__init__.py b/lib/spack/external/nose/plugins/__init__.py
deleted file mode 100644
index 08ee8f3230..0000000000
--- a/lib/spack/external/nose/plugins/__init__.py
+++ /dev/null
@@ -1,190 +0,0 @@
-"""
-Writing Plugins
----------------
-
-nose supports plugins for test collection, selection, observation and
-reporting. There are two basic rules for plugins:
-
-* Plugin classes should subclass :class:`nose.plugins.Plugin`.
-
-* Plugins may implement any of the methods described in the class
- :doc:`IPluginInterface <interface>` in nose.plugins.base. Please note that
- this class is for documentary purposes only; plugins may not subclass
- IPluginInterface.
-
-Hello World
-===========
-
-Here's a basic plugin. It doesn't do much so read on for more ideas or dive
-into the :doc:`IPluginInterface <interface>` to see all available hooks.
-
-.. code-block:: python
-
- import logging
- import os
-
- from nose.plugins import Plugin
-
- log = logging.getLogger('nose.plugins.helloworld')
-
- class HelloWorld(Plugin):
- name = 'helloworld'
-
- def options(self, parser, env=os.environ):
- super(HelloWorld, self).options(parser, env=env)
-
- def configure(self, options, conf):
- super(HelloWorld, self).configure(options, conf)
- if not self.enabled:
- return
-
- def finalize(self, result):
- log.info('Hello pluginized world!')
-
-Registering
-===========
-
-.. Note::
- Important note: the following applies only to the default
- plugin manager. Other plugin managers may use different means to
- locate and load plugins.
-
-For nose to find a plugin, it must be part of a package that uses
-setuptools_, and the plugin must be included in the entry points defined
-in the setup.py for the package:
-
-.. code-block:: python
-
- setup(name='Some plugin',
- # ...
- entry_points = {
- 'nose.plugins.0.10': [
- 'someplugin = someplugin:SomePlugin'
- ]
- },
- # ...
- )
-
-Once the package is installed with install or develop, nose will be able
-to load the plugin.
-
-.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools
-
-Registering a plugin without setuptools
-=======================================
-
-It is currently possible to register a plugin programmatically by
-creating a custom nose runner like this :
-
-.. code-block:: python
-
- import nose
- from yourplugin import YourPlugin
-
- if __name__ == '__main__':
- nose.main(addplugins=[YourPlugin()])
-
-Defining options
-================
-
-All plugins must implement the methods ``options(self, parser, env)``
-and ``configure(self, options, conf)``. Subclasses of nose.plugins.Plugin
-that want the standard options should call the superclass methods.
-
-nose uses optparse.OptionParser from the standard library to parse
-arguments. A plugin's ``options()`` method receives a parser
-instance. It's good form for a plugin to use that instance only to add
-additional arguments that take only long arguments (--like-this). Most
-of nose's built-in arguments get their default value from an environment
-variable.
-
-A plugin's ``configure()`` method receives the parsed ``OptionParser`` options
-object, as well as the current config object. Plugins should configure their
-behavior based on the user-selected settings, and may raise exceptions
-if the configured behavior is nonsensical.
-
-Logging
-=======
-
-nose uses the logging classes from the standard library. To enable users
-to view debug messages easily, plugins should use ``logging.getLogger()`` to
-acquire a logger in the ``nose.plugins`` namespace.
-
-Recipes
-=======
-
-* Writing a plugin that monitors or controls test result output
-
- Implement any or all of ``addError``, ``addFailure``, etc., to monitor test
- results. If you also want to monitor output, implement
- ``setOutputStream`` and keep a reference to the output stream. If you
- want to prevent the builtin ``TextTestResult`` output, implement
- ``setOutputSteam`` and *return a dummy stream*. The default output will go
- to the dummy stream, while you send your desired output to the real stream.
-
- Example: `examples/html_plugin/htmlplug.py`_
-
-* Writing a plugin that handles exceptions
-
- Subclass :doc:`ErrorClassPlugin <errorclasses>`.
-
- Examples: :doc:`nose.plugins.deprecated <deprecated>`,
- :doc:`nose.plugins.skip <skip>`
-
-* Writing a plugin that adds detail to error reports
-
- Implement ``formatError`` and/or ``formatFailure``. The error tuple
- you return (error class, error message, traceback) will replace the
- original error tuple.
-
- Examples: :doc:`nose.plugins.capture <capture>`,
- :doc:`nose.plugins.failuredetail <failuredetail>`
-
-* Writing a plugin that loads tests from files other than python modules
-
- Implement ``wantFile`` and ``loadTestsFromFile``. In ``wantFile``,
- return True for files that you want to examine for tests. In
- ``loadTestsFromFile``, for those files, return an iterable
- containing TestCases (or yield them as you find them;
- ``loadTestsFromFile`` may also be a generator).
-
- Example: :doc:`nose.plugins.doctests <doctests>`
-
-* Writing a plugin that prints a report
-
- Implement ``begin`` if you need to perform setup before testing
- begins. Implement ``report`` and output your report to the provided stream.
-
- Examples: :doc:`nose.plugins.cover <cover>`, :doc:`nose.plugins.prof <prof>`
-
-* Writing a plugin that selects or rejects tests
-
- Implement any or all ``want*`` methods. Return False to reject the test
- candidate, True to accept it -- which means that the test candidate
- will pass through the rest of the system, so you must be prepared to
- load tests from it if tests can't be loaded by the core loader or
- another plugin -- and None if you don't care.
-
- Examples: :doc:`nose.plugins.attrib <attrib>`,
- :doc:`nose.plugins.doctests <doctests>`, :doc:`nose.plugins.testid <testid>`
-
-
-More Examples
-=============
-
-See any builtin plugin or example plugin in the examples_ directory in
-the nose source distribution. There is a list of third-party plugins
-`on jottit`_.
-
-.. _examples/html_plugin/htmlplug.py: http://python-nose.googlecode.com/svn/trunk/examples/html_plugin/htmlplug.py
-.. _examples: http://python-nose.googlecode.com/svn/trunk/examples
-.. _on jottit: http://nose-plugins.jottit.com/
-
-"""
-from nose.plugins.base import Plugin
-from nose.plugins.manager import *
-from nose.plugins.plugintest import PluginTester
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
diff --git a/lib/spack/external/nose/plugins/allmodules.py b/lib/spack/external/nose/plugins/allmodules.py
deleted file mode 100644
index 1ccd7773a7..0000000000
--- a/lib/spack/external/nose/plugins/allmodules.py
+++ /dev/null
@@ -1,45 +0,0 @@
-"""Use the AllModules plugin by passing ``--all-modules`` or setting the
-NOSE_ALL_MODULES environment variable to enable collection and execution of
-tests in all python modules. Normal nose behavior is to look for tests only in
-modules that match testMatch.
-
-More information: :doc:`../doc_tests/test_allmodules/test_allmodules`
-
-.. warning ::
-
- This plugin can have surprising interactions with plugins that load tests
- from what nose normally considers non-test modules, such as
- the :doc:`doctest plugin <doctests>`. This is because any given
- object in a module can't be loaded both by a plugin and the normal nose
- :class:`test loader <nose.loader.TestLoader>`. Also, if you have functions
- or classes in non-test modules that look like tests but aren't, you will
- likely see errors as nose attempts to run them as tests.
-
-"""
-
-import os
-from nose.plugins.base import Plugin
-
-class AllModules(Plugin):
- """Collect tests from all python modules.
- """
- def options(self, parser, env):
- """Register commandline options.
- """
- env_opt = 'NOSE_ALL_MODULES'
- parser.add_option('--all-modules',
- action="store_true",
- dest=self.enableOpt,
- default=env.get(env_opt),
- help="Enable plugin %s: %s [%s]" %
- (self.__class__.__name__, self.help(), env_opt))
-
- def wantFile(self, file):
- """Override to return True for all files ending with .py"""
- # always want .py files
- if file.endswith('.py'):
- return True
-
- def wantModule(self, module):
- """Override return True for all modules"""
- return True
diff --git a/lib/spack/external/nose/plugins/attrib.py b/lib/spack/external/nose/plugins/attrib.py
deleted file mode 100644
index 3d4422a23a..0000000000
--- a/lib/spack/external/nose/plugins/attrib.py
+++ /dev/null
@@ -1,286 +0,0 @@
-"""Attribute selector plugin.
-
-Oftentimes when testing you will want to select tests based on
-criteria rather then simply by filename. For example, you might want
-to run all tests except for the slow ones. You can do this with the
-Attribute selector plugin by setting attributes on your test methods.
-Here is an example:
-
-.. code-block:: python
-
- def test_big_download():
- import urllib
- # commence slowness...
-
- test_big_download.slow = 1
-
-Once you've assigned an attribute ``slow = 1`` you can exclude that
-test and all other tests having the slow attribute by running ::
-
- $ nosetests -a '!slow'
-
-There is also a decorator available for you that will set attributes.
-Here's how to set ``slow=1`` like above with the decorator:
-
-.. code-block:: python
-
- from nose.plugins.attrib import attr
- @attr('slow')
- def test_big_download():
- import urllib
- # commence slowness...
-
-And here's how to set an attribute with a specific value:
-
-.. code-block:: python
-
- from nose.plugins.attrib import attr
- @attr(speed='slow')
- def test_big_download():
- import urllib
- # commence slowness...
-
-This test could be run with ::
-
- $ nosetests -a speed=slow
-
-In Python 2.6 and higher, ``@attr`` can be used on a class to set attributes
-on all its test methods at once. For example:
-
-.. code-block:: python
-
- from nose.plugins.attrib import attr
- @attr(speed='slow')
- class MyTestCase:
- def test_long_integration(self):
- pass
- def test_end_to_end_something(self):
- pass
-
-Below is a reference to the different syntaxes available.
-
-Simple syntax
--------------
-
-Examples of using the ``-a`` and ``--attr`` options:
-
-* ``nosetests -a status=stable``
- Only runs tests with attribute "status" having value "stable"
-
-* ``nosetests -a priority=2,status=stable``
- Runs tests having both attributes and values
-
-* ``nosetests -a priority=2 -a slow``
- Runs tests that match either attribute
-
-* ``nosetests -a tags=http``
- If a test's ``tags`` attribute was a list and it contained the value
- ``http`` then it would be run
-
-* ``nosetests -a slow``
- Runs tests with the attribute ``slow`` if its value does not equal False
- (False, [], "", etc...)
-
-* ``nosetests -a '!slow'``
- Runs tests that do NOT have the attribute ``slow`` or have a ``slow``
- attribute that is equal to False
- **NOTE**:
- if your shell (like bash) interprets '!' as a special character make sure to
- put single quotes around it.
-
-Expression Evaluation
----------------------
-
-Examples using the ``-A`` and ``--eval-attr`` options:
-
-* ``nosetests -A "not slow"``
- Evaluates the Python expression "not slow" and runs the test if True
-
-* ``nosetests -A "(priority > 5) and not slow"``
- Evaluates a complex Python expression and runs the test if True
-
-"""
-import inspect
-import logging
-import os
-import sys
-from inspect import isfunction
-from nose.plugins.base import Plugin
-from nose.util import tolist
-
-log = logging.getLogger('nose.plugins.attrib')
-compat_24 = sys.version_info >= (2, 4)
-
-def attr(*args, **kwargs):
- """Decorator that adds attributes to classes or functions
- for use with the Attribute (-a) plugin.
- """
- def wrap_ob(ob):
- for name in args:
- setattr(ob, name, True)
- for name, value in kwargs.iteritems():
- setattr(ob, name, value)
- return ob
- return wrap_ob
-
-def get_method_attr(method, cls, attr_name, default = False):
- """Look up an attribute on a method/ function.
- If the attribute isn't found there, looking it up in the
- method's class, if any.
- """
- Missing = object()
- value = getattr(method, attr_name, Missing)
- if value is Missing and cls is not None:
- value = getattr(cls, attr_name, Missing)
- if value is Missing:
- return default
- return value
-
-
-class ContextHelper:
- """Object that can act as context dictionary for eval and looks up
- names as attributes on a method/ function and its class.
- """
- def __init__(self, method, cls):
- self.method = method
- self.cls = cls
-
- def __getitem__(self, name):
- return get_method_attr(self.method, self.cls, name)
-
-
-class AttributeSelector(Plugin):
- """Selects test cases to be run based on their attributes.
- """
-
- def __init__(self):
- Plugin.__init__(self)
- self.attribs = []
-
- def options(self, parser, env):
- """Register command line options"""
- parser.add_option("-a", "--attr",
- dest="attr", action="append",
- default=env.get('NOSE_ATTR'),
- metavar="ATTR",
- help="Run only tests that have attributes "
- "specified by ATTR [NOSE_ATTR]")
- # disable in < 2.4: eval can't take needed args
- if compat_24:
- parser.add_option("-A", "--eval-attr",
- dest="eval_attr", metavar="EXPR", action="append",
- default=env.get('NOSE_EVAL_ATTR'),
- help="Run only tests for whose attributes "
- "the Python expression EXPR evaluates "
- "to True [NOSE_EVAL_ATTR]")
-
- def configure(self, options, config):
- """Configure the plugin and system, based on selected options.
-
- attr and eval_attr may each be lists.
-
- self.attribs will be a list of lists of tuples. In that list, each
- list is a group of attributes, all of which must match for the rule to
- match.
- """
- self.attribs = []
-
- # handle python eval-expression parameter
- if compat_24 and options.eval_attr:
- eval_attr = tolist(options.eval_attr)
- for attr in eval_attr:
- # "<python expression>"
- # -> eval(expr) in attribute context must be True
- def eval_in_context(expr, obj, cls):
- return eval(expr, None, ContextHelper(obj, cls))
- self.attribs.append([(attr, eval_in_context)])
-
- # attribute requirements are a comma separated list of
- # 'key=value' pairs
- if options.attr:
- std_attr = tolist(options.attr)
- for attr in std_attr:
- # all attributes within an attribute group must match
- attr_group = []
- for attrib in attr.strip().split(","):
- # don't die on trailing comma
- if not attrib:
- continue
- items = attrib.split("=", 1)
- if len(items) > 1:
- # "name=value"
- # -> 'str(obj.name) == value' must be True
- key, value = items
- else:
- key = items[0]
- if key[0] == "!":
- # "!name"
- # 'bool(obj.name)' must be False
- key = key[1:]
- value = False
- else:
- # "name"
- # -> 'bool(obj.name)' must be True
- value = True
- attr_group.append((key, value))
- self.attribs.append(attr_group)
- if self.attribs:
- self.enabled = True
-
- def validateAttrib(self, method, cls = None):
- """Verify whether a method has the required attributes
- The method is considered a match if it matches all attributes
- for any attribute group.
- ."""
- # TODO: is there a need for case-sensitive value comparison?
- any = False
- for group in self.attribs:
- match = True
- for key, value in group:
- attr = get_method_attr(method, cls, key)
- if callable(value):
- if not value(key, method, cls):
- match = False
- break
- elif value is True:
- # value must exist and be True
- if not bool(attr):
- match = False
- break
- elif value is False:
- # value must not exist or be False
- if bool(attr):
- match = False
- break
- elif type(attr) in (list, tuple):
- # value must be found in the list attribute
- if not str(value).lower() in [str(x).lower()
- for x in attr]:
- match = False
- break
- else:
- # value must match, convert to string and compare
- if (value != attr
- and str(value).lower() != str(attr).lower()):
- match = False
- break
- any = any or match
- if any:
- # not True because we don't want to FORCE the selection of the
- # item, only say that it is acceptable
- return None
- return False
-
- def wantFunction(self, function):
- """Accept the function if its attributes match.
- """
- return self.validateAttrib(function)
-
- def wantMethod(self, method):
- """Accept the method if its attributes match.
- """
- try:
- cls = method.im_class
- except AttributeError:
- return False
- return self.validateAttrib(method, cls)
diff --git a/lib/spack/external/nose/plugins/base.py b/lib/spack/external/nose/plugins/base.py
deleted file mode 100644
index f09beb696f..0000000000
--- a/lib/spack/external/nose/plugins/base.py
+++ /dev/null
@@ -1,725 +0,0 @@
-import os
-import textwrap
-from optparse import OptionConflictError
-from warnings import warn
-from nose.util import tolist
-
-class Plugin(object):
- """Base class for nose plugins. It's recommended but not *necessary* to
- subclass this class to create a plugin, but all plugins *must* implement
- `options(self, parser, env)` and `configure(self, options, conf)`, and
- must have the attributes `enabled`, `name` and `score`. The `name`
- attribute may contain hyphens ('-').
-
- Plugins should not be enabled by default.
-
- Subclassing Plugin (and calling the superclass methods in
- __init__, configure, and options, if you override them) will give
- your plugin some friendly default behavior:
-
- * A --with-$name option will be added to the command line interface
- to enable the plugin, and a corresponding environment variable
- will be used as the default value. The plugin class's docstring
- will be used as the help for this option.
- * The plugin will not be enabled unless this option is selected by
- the user.
- """
- can_configure = False
- enabled = False
- enableOpt = None
- name = None
- score = 100
-
- def __init__(self):
- if self.name is None:
- self.name = self.__class__.__name__.lower()
- if self.enableOpt is None:
- self.enableOpt = "enable_plugin_%s" % self.name.replace('-', '_')
-
- def addOptions(self, parser, env=None):
- """Add command-line options for this plugin.
-
- The base plugin class adds --with-$name by default, used to enable the
- plugin.
-
- .. warning :: Don't implement addOptions unless you want to override
- all default option handling behavior, including
- warnings for conflicting options. Implement
- :meth:`options
- <nose.plugins.base.IPluginInterface.options>`
- instead.
- """
- self.add_options(parser, env)
-
- def add_options(self, parser, env=None):
- """Non-camel-case version of func name for backwards compatibility.
-
- .. warning ::
-
- DEPRECATED: Do not use this method,
- use :meth:`options <nose.plugins.base.IPluginInterface.options>`
- instead.
-
- """
- # FIXME raise deprecation warning if wasn't called by wrapper
- if env is None:
- env = os.environ
- try:
- self.options(parser, env)
- self.can_configure = True
- except OptionConflictError, e:
- warn("Plugin %s has conflicting option string: %s and will "
- "be disabled" % (self, e), RuntimeWarning)
- self.enabled = False
- self.can_configure = False
-
- def options(self, parser, env):
- """Register commandline options.
-
- Implement this method for normal options behavior with protection from
- OptionConflictErrors. If you override this method and want the default
- --with-$name option to be registered, be sure to call super().
- """
- env_opt = 'NOSE_WITH_%s' % self.name.upper()
- env_opt = env_opt.replace('-', '_')
- parser.add_option("--with-%s" % self.name,
- action="store_true",
- dest=self.enableOpt,
- default=env.get(env_opt),
- help="Enable plugin %s: %s [%s]" %
- (self.__class__.__name__, self.help(), env_opt))
-
- def configure(self, options, conf):
- """Configure the plugin and system, based on selected options.
-
- The base plugin class sets the plugin to enabled if the enable option
- for the plugin (self.enableOpt) is true.
- """
- if not self.can_configure:
- return
- self.conf = conf
- if hasattr(options, self.enableOpt):
- self.enabled = getattr(options, self.enableOpt)
-
- def help(self):
- """Return help for this plugin. This will be output as the help
- section of the --with-$name option that enables the plugin.
- """
- if self.__class__.__doc__:
- # doc sections are often indented; compress the spaces
- return textwrap.dedent(self.__class__.__doc__)
- return "(no help available)"
-
- # Compatiblity shim
- def tolist(self, val):
- warn("Plugin.tolist is deprecated. Use nose.util.tolist instead",
- DeprecationWarning)
- return tolist(val)
-
-
-class IPluginInterface(object):
- """
- IPluginInterface describes the plugin API. Do not subclass or use this
- class directly.
- """
- def __new__(cls, *arg, **kw):
- raise TypeError("IPluginInterface class is for documentation only")
-
- def addOptions(self, parser, env):
- """Called to allow plugin to register command-line options with the
- parser. DO NOT return a value from this method unless you want to stop
- all other plugins from setting their options.
-
- .. warning ::
-
- DEPRECATED -- implement
- :meth:`options <nose.plugins.base.IPluginInterface.options>` instead.
- """
- pass
- add_options = addOptions
- add_options.deprecated = True
-
- def addDeprecated(self, test):
- """Called when a deprecated test is seen. DO NOT return a value
- unless you want to stop other plugins from seeing the deprecated
- test.
-
- .. warning :: DEPRECATED -- check error class in addError instead
- """
- pass
- addDeprecated.deprecated = True
-
- def addError(self, test, err):
- """Called when a test raises an uncaught exception. DO NOT return a
- value unless you want to stop other plugins from seeing that the
- test has raised an error.
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- :param err: sys.exc_info() tuple
- :type err: 3-tuple
- """
- pass
- addError.changed = True
-
- def addFailure(self, test, err):
- """Called when a test fails. DO NOT return a value unless you
- want to stop other plugins from seeing that the test has failed.
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- :param err: 3-tuple
- :type err: sys.exc_info() tuple
- """
- pass
- addFailure.changed = True
-
- def addSkip(self, test):
- """Called when a test is skipped. DO NOT return a value unless
- you want to stop other plugins from seeing the skipped test.
-
- .. warning:: DEPRECATED -- check error class in addError instead
- """
- pass
- addSkip.deprecated = True
-
- def addSuccess(self, test):
- """Called when a test passes. DO NOT return a value unless you
- want to stop other plugins from seeing the passing test.
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- """
- pass
- addSuccess.changed = True
-
- def afterContext(self):
- """Called after a context (generally a module) has been
- lazy-loaded, imported, setup, had its tests loaded and
- executed, and torn down.
- """
- pass
- afterContext._new = True
-
- def afterDirectory(self, path):
- """Called after all tests have been loaded from directory at path
- and run.
-
- :param path: the directory that has finished processing
- :type path: string
- """
- pass
- afterDirectory._new = True
-
- def afterImport(self, filename, module):
- """Called after module is imported from filename. afterImport
- is called even if the import failed.
-
- :param filename: The file that was loaded
- :type filename: string
- :param module: The name of the module
- :type module: string
- """
- pass
- afterImport._new = True
-
- def afterTest(self, test):
- """Called after the test has been run and the result recorded
- (after stopTest).
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- """
- pass
- afterTest._new = True
-
- def beforeContext(self):
- """Called before a context (generally a module) is
- examined. Because the context is not yet loaded, plugins don't
- get to know what the context is; so any context operations
- should use a stack that is pushed in `beforeContext` and popped
- in `afterContext` to ensure they operate symmetrically.
-
- `beforeContext` and `afterContext` are mainly useful for tracking
- and restoring global state around possible changes from within a
- context, whatever the context may be. If you need to operate on
- contexts themselves, see `startContext` and `stopContext`, which
- are passed the context in question, but are called after
- it has been loaded (imported in the module case).
- """
- pass
- beforeContext._new = True
-
- def beforeDirectory(self, path):
- """Called before tests are loaded from directory at path.
-
- :param path: the directory that is about to be processed
- """
- pass
- beforeDirectory._new = True
-
- def beforeImport(self, filename, module):
- """Called before module is imported from filename.
-
- :param filename: The file that will be loaded
- :param module: The name of the module found in file
- :type module: string
- """
- beforeImport._new = True
-
- def beforeTest(self, test):
- """Called before the test is run (before startTest).
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- """
- pass
- beforeTest._new = True
-
- def begin(self):
- """Called before any tests are collected or run. Use this to
- perform any setup needed before testing begins.
- """
- pass
-
- def configure(self, options, conf):
- """Called after the command line has been parsed, with the
- parsed options and the config container. Here, implement any
- config storage or changes to state or operation that are set
- by command line options.
-
- DO NOT return a value from this method unless you want to
- stop all other plugins from being configured.
- """
- pass
-
- def finalize(self, result):
- """Called after all report output, including output from all
- plugins, has been sent to the stream. Use this to print final
- test results or perform final cleanup. Return None to allow
- other plugins to continue printing, or any other value to stop
- them.
-
- :param result: test result object
-
- .. Note:: When tests are run under a test runner other than
- :class:`nose.core.TextTestRunner`, such as
- via ``python setup.py test``, this method may be called
- **before** the default report output is sent.
- """
- pass
-
- def describeTest(self, test):
- """Return a test description.
-
- Called by :meth:`nose.case.Test.shortDescription`.
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- """
- pass
- describeTest._new = True
-
- def formatError(self, test, err):
- """Called in result.addError, before plugin.addError. If you
- want to replace or modify the error tuple, return a new error
- tuple, otherwise return err, the original error tuple.
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- :param err: sys.exc_info() tuple
- :type err: 3-tuple
- """
- pass
- formatError._new = True
- formatError.chainable = True
- # test arg is not chainable
- formatError.static_args = (True, False)
-
- def formatFailure(self, test, err):
- """Called in result.addFailure, before plugin.addFailure. If you
- want to replace or modify the error tuple, return a new error
- tuple, otherwise return err, the original error tuple.
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- :param err: sys.exc_info() tuple
- :type err: 3-tuple
- """
- pass
- formatFailure._new = True
- formatFailure.chainable = True
- # test arg is not chainable
- formatFailure.static_args = (True, False)
-
- def handleError(self, test, err):
- """Called on addError. To handle the error yourself and prevent normal
- error processing, return a true value.
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- :param err: sys.exc_info() tuple
- :type err: 3-tuple
- """
- pass
- handleError._new = True
-
- def handleFailure(self, test, err):
- """Called on addFailure. To handle the failure yourself and
- prevent normal failure processing, return a true value.
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- :param err: sys.exc_info() tuple
- :type err: 3-tuple
- """
- pass
- handleFailure._new = True
-
- def loadTestsFromDir(self, path):
- """Return iterable of tests from a directory. May be a
- generator. Each item returned must be a runnable
- unittest.TestCase (or subclass) instance or suite instance.
- Return None if your plugin cannot collect any tests from
- directory.
-
- :param path: The path to the directory.
- """
- pass
- loadTestsFromDir.generative = True
- loadTestsFromDir._new = True
-
- def loadTestsFromModule(self, module, path=None):
- """Return iterable of tests in a module. May be a
- generator. Each item returned must be a runnable
- unittest.TestCase (or subclass) instance.
- Return None if your plugin cannot
- collect any tests from module.
-
- :param module: The module object
- :type module: python module
- :param path: the path of the module to search, to distinguish from
- namespace package modules
-
- .. note::
-
- NEW. The ``path`` parameter will only be passed by nose 0.11
- or above.
- """
- pass
- loadTestsFromModule.generative = True
-
- def loadTestsFromName(self, name, module=None, importPath=None):
- """Return tests in this file or module. Return None if you are not able
- to load any tests, or an iterable if you are. May be a
- generator.
-
- :param name: The test name. May be a file or module name plus a test
- callable. Use split_test_name to split into parts. Or it might
- be some crazy name of your own devising, in which case, do
- whatever you want.
- :param module: Module from which the name is to be loaded
- :param importPath: Path from which file (must be a python module) was
- found
-
- .. warning:: DEPRECATED: this argument will NOT be passed.
- """
- pass
- loadTestsFromName.generative = True
-
- def loadTestsFromNames(self, names, module=None):
- """Return a tuple of (tests loaded, remaining names). Return
- None if you are not able to load any tests. Multiple plugins
- may implement loadTestsFromNames; the remaining name list from
- each will be passed to the next as input.
-
- :param names: List of test names.
- :type names: iterable
- :param module: Module from which the names are to be loaded
- """
- pass
- loadTestsFromNames._new = True
- loadTestsFromNames.chainable = True
-
- def loadTestsFromFile(self, filename):
- """Return tests in this file. Return None if you are not
- interested in loading any tests, or an iterable if you are and
- can load some. May be a generator. *If you are interested in
- loading tests from the file and encounter no errors, but find
- no tests, yield False or return [False].*
-
- .. Note:: This method replaces loadTestsFromPath from the 0.9
- API.
-
- :param filename: The full path to the file or directory.
- """
- pass
- loadTestsFromFile.generative = True
- loadTestsFromFile._new = True
-
- def loadTestsFromPath(self, path):
- """
- .. warning:: DEPRECATED -- use loadTestsFromFile instead
- """
- pass
- loadTestsFromPath.deprecated = True
-
- def loadTestsFromTestCase(self, cls):
- """Return tests in this test case class. Return None if you are
- not able to load any tests, or an iterable if you are. May be a
- generator.
-
- :param cls: The test case class. Must be subclass of
- :class:`unittest.TestCase`.
- """
- pass
- loadTestsFromTestCase.generative = True
-
- def loadTestsFromTestClass(self, cls):
- """Return tests in this test class. Class will *not* be a
- unittest.TestCase subclass. Return None if you are not able to
- load any tests, an iterable if you are. May be a generator.
-
- :param cls: The test case class. Must be **not** be subclass of
- :class:`unittest.TestCase`.
- """
- pass
- loadTestsFromTestClass._new = True
- loadTestsFromTestClass.generative = True
-
- def makeTest(self, obj, parent):
- """Given an object and its parent, return or yield one or more
- test cases. Each test must be a unittest.TestCase (or subclass)
- instance. This is called before default test loading to allow
- plugins to load an alternate test case or cases for an
- object. May be a generator.
-
- :param obj: The object to be made into a test
- :param parent: The parent of obj (eg, for a method, the class)
- """
- pass
- makeTest._new = True
- makeTest.generative = True
-
- def options(self, parser, env):
- """Called to allow plugin to register command line
- options with the parser.
-
- DO NOT return a value from this method unless you want to stop
- all other plugins from setting their options.
-
- :param parser: options parser instance
- :type parser: :class:`ConfigParser.ConfigParser`
- :param env: environment, default is os.environ
- """
- pass
- options._new = True
-
- def prepareTest(self, test):
- """Called before the test is run by the test runner. Please
- note the article *the* in the previous sentence: prepareTest
- is called *only once*, and is passed the test case or test
- suite that the test runner will execute. It is *not* called
- for each individual test case. If you return a non-None value,
- that return value will be run as the test. Use this hook to
- wrap or decorate the test with another function. If you need
- to modify or wrap individual test cases, use `prepareTestCase`
- instead.
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- """
- pass
-
- def prepareTestCase(self, test):
- """Prepare or wrap an individual test case. Called before
- execution of the test. The test passed here is a
- nose.case.Test instance; the case to be executed is in the
- test attribute of the passed case. To modify the test to be
- run, you should return a callable that takes one argument (the
- test result object) -- it is recommended that you *do not*
- side-effect the nose.case.Test instance you have been passed.
-
- Keep in mind that when you replace the test callable you are
- replacing the run() method of the test case -- including the
- exception handling and result calls, etc.
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- """
- pass
- prepareTestCase._new = True
-
- def prepareTestLoader(self, loader):
- """Called before tests are loaded. To replace the test loader,
- return a test loader. To allow other plugins to process the
- test loader, return None. Only one plugin may replace the test
- loader. Only valid when using nose.TestProgram.
-
- :param loader: :class:`nose.loader.TestLoader`
- (or other loader) instance
- """
- pass
- prepareTestLoader._new = True
-
- def prepareTestResult(self, result):
- """Called before the first test is run. To use a different
- test result handler for all tests than the given result,
- return a test result handler. NOTE however that this handler
- will only be seen by tests, that is, inside of the result
- proxy system. The TestRunner and TestProgram -- whether nose's
- or other -- will continue to see the original result
- handler. For this reason, it is usually better to monkeypatch
- the result (for instance, if you want to handle some
- exceptions in a unique way). Only one plugin may replace the
- result, but many may monkeypatch it. If you want to
- monkeypatch and stop other plugins from doing so, monkeypatch
- and return the patched result.
-
- :param result: :class:`nose.result.TextTestResult`
- (or other result) instance
- """
- pass
- prepareTestResult._new = True
-
- def prepareTestRunner(self, runner):
- """Called before tests are run. To replace the test runner,
- return a test runner. To allow other plugins to process the
- test runner, return None. Only valid when using nose.TestProgram.
-
- :param runner: :class:`nose.core.TextTestRunner`
- (or other runner) instance
- """
- pass
- prepareTestRunner._new = True
-
- def report(self, stream):
- """Called after all error output has been printed. Print your
- plugin's report to the provided stream. Return None to allow
- other plugins to print reports, any other value to stop them.
-
- :param stream: stream object; send your output here
- :type stream: file-like object
- """
- pass
-
- def setOutputStream(self, stream):
- """Called before test output begins. To direct test output to a
- new stream, return a stream object, which must implement a
- `write(msg)` method. If you only want to note the stream, not
- capture or redirect it, then return None.
-
- :param stream: stream object; send your output here
- :type stream: file-like object
- """
-
- def startContext(self, context):
- """Called before context setup and the running of tests in the
- context. Note that tests have already been *loaded* from the
- context before this call.
-
- :param context: the context about to be setup. May be a module or
- class, or any other object that contains tests.
- """
- pass
- startContext._new = True
-
- def startTest(self, test):
- """Called before each test is run. DO NOT return a value unless
- you want to stop other plugins from seeing the test start.
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- """
- pass
-
- def stopContext(self, context):
- """Called after the tests in a context have run and the
- context has been torn down.
-
- :param context: the context that has been torn down. May be a module or
- class, or any other object that contains tests.
- """
- pass
- stopContext._new = True
-
- def stopTest(self, test):
- """Called after each test is run. DO NOT return a value unless
- you want to stop other plugins from seeing that the test has stopped.
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- """
- pass
-
- def testName(self, test):
- """Return a short test name. Called by `nose.case.Test.__str__`.
-
- :param test: the test case
- :type test: :class:`nose.case.Test`
- """
- pass
- testName._new = True
-
- def wantClass(self, cls):
- """Return true if you want the main test selector to collect
- tests from this class, false if you don't, and None if you don't
- care.
-
- :param cls: The class being examined by the selector
- """
- pass
-
- def wantDirectory(self, dirname):
- """Return true if you want test collection to descend into this
- directory, false if you do not, and None if you don't care.
-
- :param dirname: Full path to directory being examined by the selector
- """
- pass
-
- def wantFile(self, file):
- """Return true if you want to collect tests from this file,
- false if you do not and None if you don't care.
-
- Change from 0.9: The optional package parameter is no longer passed.
-
- :param file: Full path to file being examined by the selector
- """
- pass
-
- def wantFunction(self, function):
- """Return true to collect this function as a test, false to
- prevent it from being collected, and None if you don't care.
-
- :param function: The function object being examined by the selector
- """
- pass
-
- def wantMethod(self, method):
- """Return true to collect this method as a test, false to
- prevent it from being collected, and None if you don't care.
-
- :param method: The method object being examined by the selector
- :type method: unbound method
- """
- pass
-
- def wantModule(self, module):
- """Return true if you want to collection to descend into this
- module, false to prevent the collector from descending into the
- module, and None if you don't care.
-
- :param module: The module object being examined by the selector
- :type module: python module
- """
- pass
-
- def wantModuleTests(self, module):
- """
- .. warning:: DEPRECATED -- this method will not be called, it has
- been folded into wantModule.
- """
- pass
- wantModuleTests.deprecated = True
-
diff --git a/lib/spack/external/nose/plugins/builtin.py b/lib/spack/external/nose/plugins/builtin.py
deleted file mode 100644
index 4fcc0018ad..0000000000
--- a/lib/spack/external/nose/plugins/builtin.py
+++ /dev/null
@@ -1,34 +0,0 @@
-"""
-Lists builtin plugins.
-"""
-plugins = []
-builtins = (
- ('nose.plugins.attrib', 'AttributeSelector'),
- ('nose.plugins.capture', 'Capture'),
- ('nose.plugins.logcapture', 'LogCapture'),
- ('nose.plugins.cover', 'Coverage'),
- ('nose.plugins.debug', 'Pdb'),
- ('nose.plugins.deprecated', 'Deprecated'),
- ('nose.plugins.doctests', 'Doctest'),
- ('nose.plugins.isolate', 'IsolationPlugin'),
- ('nose.plugins.failuredetail', 'FailureDetail'),
- ('nose.plugins.prof', 'Profile'),
- ('nose.plugins.skip', 'Skip'),
- ('nose.plugins.testid', 'TestId'),
- ('nose.plugins.multiprocess', 'MultiProcess'),
- ('nose.plugins.xunit', 'Xunit'),
- ('nose.plugins.allmodules', 'AllModules'),
- ('nose.plugins.collect', 'CollectOnly'),
- )
-
-for module, cls in builtins:
- try:
- plugmod = __import__(module, globals(), locals(), [cls])
- except KeyboardInterrupt:
- raise
- except:
- continue
- plug = getattr(plugmod, cls)
- plugins.append(plug)
- globals()[cls] = plug
-
diff --git a/lib/spack/external/nose/plugins/capture.py b/lib/spack/external/nose/plugins/capture.py
deleted file mode 100644
index fa4e5dcaaf..0000000000
--- a/lib/spack/external/nose/plugins/capture.py
+++ /dev/null
@@ -1,115 +0,0 @@
-"""
-This plugin captures stdout during test execution. If the test fails
-or raises an error, the captured output will be appended to the error
-or failure output. It is enabled by default but can be disabled with
-the options ``-s`` or ``--nocapture``.
-
-:Options:
- ``--nocapture``
- Don't capture stdout (any stdout output will be printed immediately)
-
-"""
-import logging
-import os
-import sys
-from nose.plugins.base import Plugin
-from nose.pyversion import exc_to_unicode, force_unicode
-from nose.util import ln
-from StringIO import StringIO
-
-
-log = logging.getLogger(__name__)
-
-class Capture(Plugin):
- """
- Output capture plugin. Enabled by default. Disable with ``-s`` or
- ``--nocapture``. This plugin captures stdout during test execution,
- appending any output captured to the error or failure output,
- should the test fail or raise an error.
- """
- enabled = True
- env_opt = 'NOSE_NOCAPTURE'
- name = 'capture'
- score = 1600
-
- def __init__(self):
- self.stdout = []
- self._buf = None
-
- def options(self, parser, env):
- """Register commandline options
- """
- parser.add_option(
- "-s", "--nocapture", action="store_false",
- default=not env.get(self.env_opt), dest="capture",
- help="Don't capture stdout (any stdout output "
- "will be printed immediately) [NOSE_NOCAPTURE]")
-
- def configure(self, options, conf):
- """Configure plugin. Plugin is enabled by default.
- """
- self.conf = conf
- if not options.capture:
- self.enabled = False
-
- def afterTest(self, test):
- """Clear capture buffer.
- """
- self.end()
- self._buf = None
-
- def begin(self):
- """Replace sys.stdout with capture buffer.
- """
- self.start() # get an early handle on sys.stdout
-
- def beforeTest(self, test):
- """Flush capture buffer.
- """
- self.start()
-
- def formatError(self, test, err):
- """Add captured output to error report.
- """
- test.capturedOutput = output = self.buffer
- self._buf = None
- if not output:
- # Don't return None as that will prevent other
- # formatters from formatting and remove earlier formatters
- # formats, instead return the err we got
- return err
- ec, ev, tb = err
- return (ec, self.addCaptureToErr(ev, output), tb)
-
- def formatFailure(self, test, err):
- """Add captured output to failure report.
- """
- return self.formatError(test, err)
-
- def addCaptureToErr(self, ev, output):
- ev = exc_to_unicode(ev)
- output = force_unicode(output)
- return u'\n'.join([ev, ln(u'>> begin captured stdout <<'),
- output, ln(u'>> end captured stdout <<')])
-
- def start(self):
- self.stdout.append(sys.stdout)
- self._buf = StringIO()
- sys.stdout = self._buf
-
- def end(self):
- if self.stdout:
- sys.stdout = self.stdout.pop()
-
- def finalize(self, result):
- """Restore stdout.
- """
- while self.stdout:
- self.end()
-
- def _get_buffer(self):
- if self._buf is not None:
- return self._buf.getvalue()
-
- buffer = property(_get_buffer, None, None,
- """Captured stdout output.""")
diff --git a/lib/spack/external/nose/plugins/collect.py b/lib/spack/external/nose/plugins/collect.py
deleted file mode 100644
index 6f9f0faa77..0000000000
--- a/lib/spack/external/nose/plugins/collect.py
+++ /dev/null
@@ -1,94 +0,0 @@
-"""
-This plugin bypasses the actual execution of tests, and instead just collects
-test names. Fixtures are also bypassed, so running nosetests with the
-collection plugin enabled should be very quick.
-
-This plugin is useful in combination with the testid plugin (``--with-id``).
-Run both together to get an indexed list of all tests, which will enable you to
-run individual tests by index number.
-
-This plugin is also useful for counting tests in a test suite, and making
-people watching your demo think all of your tests pass.
-"""
-from nose.plugins.base import Plugin
-from nose.case import Test
-import logging
-import unittest
-
-log = logging.getLogger(__name__)
-
-
-class CollectOnly(Plugin):
- """
- Collect and output test names only, don't run any tests.
- """
- name = "collect-only"
- enableOpt = 'collect_only'
-
- def options(self, parser, env):
- """Register commandline options.
- """
- parser.add_option('--collect-only',
- action='store_true',
- dest=self.enableOpt,
- default=env.get('NOSE_COLLECT_ONLY'),
- help="Enable collect-only: %s [COLLECT_ONLY]" %
- (self.help()))
-
- def prepareTestLoader(self, loader):
- """Install collect-only suite class in TestLoader.
- """
- # Disable context awareness
- log.debug("Preparing test loader")
- loader.suiteClass = TestSuiteFactory(self.conf)
-
- def prepareTestCase(self, test):
- """Replace actual test with dummy that always passes.
- """
- # Return something that always passes
- log.debug("Preparing test case %s", test)
- if not isinstance(test, Test):
- return
- def run(result):
- # We need to make these plugin calls because there won't be
- # a result proxy, due to using a stripped-down test suite
- self.conf.plugins.startTest(test)
- result.startTest(test)
- self.conf.plugins.addSuccess(test)
- result.addSuccess(test)
- self.conf.plugins.stopTest(test)
- result.stopTest(test)
- return run
-
-
-class TestSuiteFactory:
- """
- Factory for producing configured test suites.
- """
- def __init__(self, conf):
- self.conf = conf
-
- def __call__(self, tests=(), **kw):
- return TestSuite(tests, conf=self.conf)
-
-
-class TestSuite(unittest.TestSuite):
- """
- Basic test suite that bypasses most proxy and plugin calls, but does
- wrap tests in a nose.case.Test so prepareTestCase will be called.
- """
- def __init__(self, tests=(), conf=None):
- self.conf = conf
- # Exec lazy suites: makes discovery depth-first
- if callable(tests):
- tests = tests()
- log.debug("TestSuite(%r)", tests)
- unittest.TestSuite.__init__(self, tests)
-
- def addTest(self, test):
- log.debug("Add test %s", test)
- if isinstance(test, unittest.TestSuite):
- self._tests.append(test)
- else:
- self._tests.append(Test(test, config=self.conf))
-
diff --git a/lib/spack/external/nose/plugins/cover.py b/lib/spack/external/nose/plugins/cover.py
deleted file mode 100644
index fbe2e30dcd..0000000000
--- a/lib/spack/external/nose/plugins/cover.py
+++ /dev/null
@@ -1,271 +0,0 @@
-"""If you have Ned Batchelder's coverage_ module installed, you may activate a
-coverage report with the ``--with-coverage`` switch or NOSE_WITH_COVERAGE
-environment variable. The coverage report will cover any python source module
-imported after the start of the test run, excluding modules that match
-testMatch. If you want to include those modules too, use the ``--cover-tests``
-switch, or set the NOSE_COVER_TESTS environment variable to a true value. To
-restrict the coverage report to modules from a particular package or packages,
-use the ``--cover-package`` switch or the NOSE_COVER_PACKAGE environment
-variable.
-
-.. _coverage: http://www.nedbatchelder.com/code/modules/coverage.html
-"""
-import logging
-import re
-import sys
-import StringIO
-from nose.plugins.base import Plugin
-from nose.util import src, tolist
-
-log = logging.getLogger(__name__)
-
-
-class Coverage(Plugin):
- """
- Activate a coverage report using Ned Batchelder's coverage module.
- """
- coverTests = False
- coverPackages = None
- coverInstance = None
- coverErase = False
- coverMinPercentage = None
- score = 200
- status = {}
-
- def options(self, parser, env):
- """
- Add options to command line.
- """
- super(Coverage, self).options(parser, env)
- parser.add_option("--cover-package", action="append",
- default=env.get('NOSE_COVER_PACKAGE'),
- metavar="PACKAGE",
- dest="cover_packages",
- help="Restrict coverage output to selected packages "
- "[NOSE_COVER_PACKAGE]")
- parser.add_option("--cover-erase", action="store_true",
- default=env.get('NOSE_COVER_ERASE'),
- dest="cover_erase",
- help="Erase previously collected coverage "
- "statistics before run")
- parser.add_option("--cover-tests", action="store_true",
- dest="cover_tests",
- default=env.get('NOSE_COVER_TESTS'),
- help="Include test modules in coverage report "
- "[NOSE_COVER_TESTS]")
- parser.add_option("--cover-min-percentage", action="store",
- dest="cover_min_percentage",
- default=env.get('NOSE_COVER_MIN_PERCENTAGE'),
- help="Minimum percentage of coverage for tests "
- "to pass [NOSE_COVER_MIN_PERCENTAGE]")
- parser.add_option("--cover-inclusive", action="store_true",
- dest="cover_inclusive",
- default=env.get('NOSE_COVER_INCLUSIVE'),
- help="Include all python files under working "
- "directory in coverage report. Useful for "
- "discovering holes in test coverage if not all "
- "files are imported by the test suite. "
- "[NOSE_COVER_INCLUSIVE]")
- parser.add_option("--cover-html", action="store_true",
- default=env.get('NOSE_COVER_HTML'),
- dest='cover_html',
- help="Produce HTML coverage information")
- parser.add_option('--cover-html-dir', action='store',
- default=env.get('NOSE_COVER_HTML_DIR', 'cover'),
- dest='cover_html_dir',
- metavar='DIR',
- help='Produce HTML coverage information in dir')
- parser.add_option("--cover-branches", action="store_true",
- default=env.get('NOSE_COVER_BRANCHES'),
- dest="cover_branches",
- help="Include branch coverage in coverage report "
- "[NOSE_COVER_BRANCHES]")
- parser.add_option("--cover-xml", action="store_true",
- default=env.get('NOSE_COVER_XML'),
- dest="cover_xml",
- help="Produce XML coverage information")
- parser.add_option("--cover-xml-file", action="store",
- default=env.get('NOSE_COVER_XML_FILE', 'coverage.xml'),
- dest="cover_xml_file",
- metavar="FILE",
- help="Produce XML coverage information in file")
-
- def configure(self, options, conf):
- """
- Configure plugin.
- """
- try:
- self.status.pop('active')
- except KeyError:
- pass
- super(Coverage, self).configure(options, conf)
- if self.enabled:
- try:
- import coverage
- if not hasattr(coverage, 'coverage'):
- raise ImportError("Unable to import coverage module")
- except ImportError:
- log.error("Coverage not available: "
- "unable to import coverage module")
- self.enabled = False
- return
- self.conf = conf
- self.coverErase = options.cover_erase
- self.coverTests = options.cover_tests
- self.coverPackages = []
- if options.cover_packages:
- if isinstance(options.cover_packages, (list, tuple)):
- cover_packages = options.cover_packages
- else:
- cover_packages = [options.cover_packages]
- for pkgs in [tolist(x) for x in cover_packages]:
- self.coverPackages.extend(pkgs)
- self.coverInclusive = options.cover_inclusive
- if self.coverPackages:
- log.info("Coverage report will include only packages: %s",
- self.coverPackages)
- self.coverHtmlDir = None
- if options.cover_html:
- self.coverHtmlDir = options.cover_html_dir
- log.debug('Will put HTML coverage report in %s', self.coverHtmlDir)
- self.coverBranches = options.cover_branches
- self.coverXmlFile = None
- if options.cover_min_percentage:
- self.coverMinPercentage = int(options.cover_min_percentage.rstrip('%'))
- if options.cover_xml:
- self.coverXmlFile = options.cover_xml_file
- log.debug('Will put XML coverage report in %s', self.coverXmlFile)
- if self.enabled:
- self.status['active'] = True
- self.coverInstance = coverage.coverage(auto_data=False,
- branch=self.coverBranches, data_suffix=conf.worker,
- source=self.coverPackages)
- self.coverInstance._warn_no_data = False
- self.coverInstance.is_worker = conf.worker
- self.coverInstance.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
-
- log.debug("Coverage begin")
- self.skipModules = sys.modules.keys()[:]
- if self.coverErase:
- log.debug("Clearing previously collected coverage statistics")
- self.coverInstance.combine()
- self.coverInstance.erase()
-
- if not self.coverInstance.is_worker:
- self.coverInstance.load()
- self.coverInstance.start()
-
-
- def beforeTest(self, *args, **kwargs):
- """
- Begin recording coverage information.
- """
-
- if self.coverInstance.is_worker:
- self.coverInstance.load()
- self.coverInstance.start()
-
- def afterTest(self, *args, **kwargs):
- """
- Stop recording coverage information.
- """
-
- if self.coverInstance.is_worker:
- self.coverInstance.stop()
- self.coverInstance.save()
-
-
- def report(self, stream):
- """
- Output code coverage report.
- """
- log.debug("Coverage report")
- self.coverInstance.stop()
- self.coverInstance.combine()
- self.coverInstance.save()
- modules = [module
- for name, module in sys.modules.items()
- if self.wantModuleCoverage(name, module)]
- log.debug("Coverage report will cover modules: %s", modules)
- self.coverInstance.report(modules, file=stream)
-
- import coverage
- if self.coverHtmlDir:
- log.debug("Generating HTML coverage report")
- try:
- self.coverInstance.html_report(modules, self.coverHtmlDir)
- except coverage.misc.CoverageException, e:
- log.warning("Failed to generate HTML report: %s" % str(e))
-
- if self.coverXmlFile:
- log.debug("Generating XML coverage report")
- try:
- self.coverInstance.xml_report(modules, self.coverXmlFile)
- except coverage.misc.CoverageException, e:
- log.warning("Failed to generate XML report: %s" % str(e))
-
- # make sure we have minimum required coverage
- if self.coverMinPercentage:
- f = StringIO.StringIO()
- self.coverInstance.report(modules, file=f)
-
- multiPackageRe = (r'-------\s\w+\s+\d+\s+\d+(?:\s+\d+\s+\d+)?'
- r'\s+(\d+)%\s+\d*\s{0,1}$')
- singlePackageRe = (r'-------\s[\w./]+\s+\d+\s+\d+(?:\s+\d+\s+\d+)?'
- r'\s+(\d+)%(?:\s+[-\d, ]+)\s{0,1}$')
-
- m = re.search(multiPackageRe, f.getvalue())
- if m is None:
- m = re.search(singlePackageRe, f.getvalue())
-
- if m:
- percentage = int(m.groups()[0])
- if percentage < self.coverMinPercentage:
- log.error('TOTAL Coverage did not reach minimum '
- 'required: %d%%' % self.coverMinPercentage)
- sys.exit(1)
- else:
- log.error("No total percentage was found in coverage output, "
- "something went wrong.")
-
-
- def wantModuleCoverage(self, name, module):
- if not hasattr(module, '__file__'):
- log.debug("no coverage of %s: no __file__", name)
- return False
- module_file = src(module.__file__)
- if not module_file or not module_file.endswith('.py'):
- log.debug("no coverage of %s: not a python file", name)
- return False
- if self.coverPackages:
- for package in self.coverPackages:
- if (re.findall(r'^%s\b' % re.escape(package), name)
- and (self.coverTests
- or not self.conf.testMatch.search(name))):
- log.debug("coverage for %s", name)
- return True
- if name in self.skipModules:
- log.debug("no coverage for %s: loaded before coverage start",
- name)
- return False
- if self.conf.testMatch.search(name) and not self.coverTests:
- log.debug("no coverage for %s: is a test", name)
- return False
- # accept any package that passed the previous tests, unless
- # coverPackages is on -- in that case, if we wanted this
- # module, we would have already returned True
- return not self.coverPackages
-
- def wantFile(self, file, package=None):
- """If inclusive coverage enabled, return true for all source files
- in wanted packages.
- """
- if self.coverInclusive:
- if file.endswith(".py"):
- if package and self.coverPackages:
- for want in self.coverPackages:
- if package.startswith(want):
- return True
- else:
- return True
- return None
diff --git a/lib/spack/external/nose/plugins/debug.py b/lib/spack/external/nose/plugins/debug.py
deleted file mode 100644
index 78243e60d0..0000000000
--- a/lib/spack/external/nose/plugins/debug.py
+++ /dev/null
@@ -1,67 +0,0 @@
-"""
-This plugin provides ``--pdb`` and ``--pdb-failures`` options. The ``--pdb``
-option will drop the test runner into pdb when it encounters an error. To
-drop into pdb on failure, use ``--pdb-failures``.
-"""
-
-import pdb
-from nose.plugins.base import Plugin
-
-class Pdb(Plugin):
- """
- Provides --pdb and --pdb-failures options that cause the test runner to
- drop into pdb if it encounters an error or failure, respectively.
- """
- enabled_for_errors = False
- enabled_for_failures = False
- score = 5 # run last, among builtins
-
- def options(self, parser, env):
- """Register commandline options.
- """
- parser.add_option(
- "--pdb", action="store_true", dest="debugBoth",
- default=env.get('NOSE_PDB', False),
- help="Drop into debugger on failures or errors")
- parser.add_option(
- "--pdb-failures", action="store_true",
- dest="debugFailures",
- default=env.get('NOSE_PDB_FAILURES', False),
- help="Drop into debugger on failures")
- parser.add_option(
- "--pdb-errors", action="store_true",
- dest="debugErrors",
- default=env.get('NOSE_PDB_ERRORS', False),
- help="Drop into debugger on errors")
-
- def configure(self, options, conf):
- """Configure which kinds of exceptions trigger plugin.
- """
- self.conf = conf
- self.enabled_for_errors = options.debugErrors or options.debugBoth
- self.enabled_for_failures = options.debugFailures or options.debugBoth
- self.enabled = self.enabled_for_failures or self.enabled_for_errors
-
- def addError(self, test, err):
- """Enter pdb if configured to debug errors.
- """
- if not self.enabled_for_errors:
- return
- self.debug(err)
-
- def addFailure(self, test, err):
- """Enter pdb if configured to debug failures.
- """
- if not self.enabled_for_failures:
- return
- self.debug(err)
-
- def debug(self, err):
- import sys # FIXME why is this import here?
- ec, ev, tb = err
- stdout = sys.stdout
- sys.stdout = sys.__stdout__
- try:
- pdb.post_mortem(tb)
- finally:
- sys.stdout = stdout
diff --git a/lib/spack/external/nose/plugins/deprecated.py b/lib/spack/external/nose/plugins/deprecated.py
deleted file mode 100644
index 461a26be63..0000000000
--- a/lib/spack/external/nose/plugins/deprecated.py
+++ /dev/null
@@ -1,45 +0,0 @@
-"""
-This plugin installs a DEPRECATED error class for the :class:`DeprecatedTest`
-exception. When :class:`DeprecatedTest` is raised, the exception will be logged
-in the deprecated attribute of the result, ``D`` or ``DEPRECATED`` (verbose)
-will be output, and the exception will not be counted as an error or failure.
-It is enabled by default, but can be turned off by using ``--no-deprecated``.
-"""
-
-from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin
-
-
-class DeprecatedTest(Exception):
- """Raise this exception to mark a test as deprecated.
- """
- pass
-
-
-class Deprecated(ErrorClassPlugin):
- """
- Installs a DEPRECATED error class for the DeprecatedTest exception. Enabled
- by default.
- """
- enabled = True
- deprecated = ErrorClass(DeprecatedTest,
- label='DEPRECATED',
- isfailure=False)
-
- def options(self, parser, env):
- """Register commandline options.
- """
- env_opt = 'NOSE_WITHOUT_DEPRECATED'
- parser.add_option('--no-deprecated', action='store_true',
- dest='noDeprecated', default=env.get(env_opt, False),
- help="Disable special handling of DeprecatedTest "
- "exceptions.")
-
- def configure(self, options, conf):
- """Configure plugin.
- """
- if not self.can_configure:
- return
- self.conf = conf
- disable = getattr(options, 'noDeprecated', False)
- if disable:
- self.enabled = False
diff --git a/lib/spack/external/nose/plugins/doctests.py b/lib/spack/external/nose/plugins/doctests.py
deleted file mode 100644
index 5ef65799f3..0000000000
--- a/lib/spack/external/nose/plugins/doctests.py
+++ /dev/null
@@ -1,455 +0,0 @@
-"""Use the Doctest plugin with ``--with-doctest`` or the NOSE_WITH_DOCTEST
-environment variable to enable collection and execution of :mod:`doctests
-<doctest>`. Because doctests are usually included in the tested package
-(instead of being grouped into packages or modules of their own), nose only
-looks for them in the non-test packages it discovers in the working directory.
-
-Doctests may also be placed into files other than python modules, in which
-case they can be collected and executed by using the ``--doctest-extension``
-switch or NOSE_DOCTEST_EXTENSION environment variable to indicate which file
-extension(s) to load.
-
-When loading doctests from non-module files, use the ``--doctest-fixtures``
-switch to specify how to find modules containing fixtures for the tests. A
-module name will be produced by appending the value of that switch to the base
-name of each doctest file loaded. For example, a doctest file "widgets.rst"
-with the switch ``--doctest_fixtures=_fixt`` will load fixtures from the module
-``widgets_fixt.py``.
-
-A fixtures module may define any or all of the following functions:
-
-* setup([module]) or setup_module([module])
-
- Called before the test runs. You may raise SkipTest to skip all tests.
-
-* teardown([module]) or teardown_module([module])
-
- Called after the test runs, if setup/setup_module did not raise an
- unhandled exception.
-
-* setup_test(test)
-
- Called before the test. NOTE: the argument passed is a
- doctest.DocTest instance, *not* a unittest.TestCase.
-
-* teardown_test(test)
-
- Called after the test, if setup_test did not raise an exception. NOTE: the
- argument passed is a doctest.DocTest instance, *not* a unittest.TestCase.
-
-Doctests are run like any other test, with the exception that output
-capture does not work; doctest does its own output capture while running a
-test.
-
-.. note ::
-
- See :doc:`../doc_tests/test_doctest_fixtures/doctest_fixtures` for
- additional documentation and examples.
-
-"""
-from __future__ import generators
-
-import logging
-import os
-import sys
-import unittest
-from inspect import getmodule
-from nose.plugins.base import Plugin
-from nose.suite import ContextList
-from nose.util import anyp, getpackage, test_address, resolve_name, \
- src, tolist, isproperty
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-import sys
-import __builtin__ as builtin_mod
-
-log = logging.getLogger(__name__)
-
-try:
- import doctest
- doctest.DocTestCase
- # system version of doctest is acceptable, but needs a monkeypatch
-except (ImportError, AttributeError):
- # system version is too old
- import nose.ext.dtcompat as doctest
-
-
-#
-# Doctest and coverage don't get along, so we need to create
-# a monkeypatch that will replace the part of doctest that
-# interferes with coverage reports.
-#
-# The monkeypatch is based on this zope patch:
-# http://svn.zope.org/Zope3/trunk/src/zope/testing/doctest.py?rev=28679&r1=28703&r2=28705
-#
-_orp = doctest._OutputRedirectingPdb
-
-class NoseOutputRedirectingPdb(_orp):
- def __init__(self, out):
- self.__debugger_used = False
- _orp.__init__(self, out)
-
- def set_trace(self):
- self.__debugger_used = True
- _orp.set_trace(self, sys._getframe().f_back)
-
- def set_continue(self):
- # Calling set_continue unconditionally would break unit test
- # coverage reporting, as Bdb.set_continue calls sys.settrace(None).
- if self.__debugger_used:
- _orp.set_continue(self)
-doctest._OutputRedirectingPdb = NoseOutputRedirectingPdb
-
-
-class DoctestSuite(unittest.TestSuite):
- """
- Doctest suites are parallelizable at the module or file level only,
- since they may be attached to objects that are not individually
- addressable (like properties). This suite subclass is used when
- loading doctests from a module to ensure that behavior.
-
- This class is used only if the plugin is not fully prepared;
- in normal use, the loader's suiteClass is used.
-
- """
- can_split = False
-
- def __init__(self, tests=(), context=None, can_split=False):
- self.context = context
- self.can_split = can_split
- unittest.TestSuite.__init__(self, tests=tests)
-
- def address(self):
- return test_address(self.context)
-
- def __iter__(self):
- # 2.3 compat
- return iter(self._tests)
-
- def __str__(self):
- return str(self._tests)
-
-
-class Doctest(Plugin):
- """
- Activate doctest plugin to find and run doctests in non-test modules.
- """
- extension = None
- suiteClass = DoctestSuite
-
- def options(self, parser, env):
- """Register commmandline options.
- """
- Plugin.options(self, parser, env)
- parser.add_option('--doctest-tests', action='store_true',
- dest='doctest_tests',
- default=env.get('NOSE_DOCTEST_TESTS'),
- help="Also look for doctests in test modules. "
- "Note that classes, methods and functions should "
- "have either doctests or non-doctest tests, "
- "not both. [NOSE_DOCTEST_TESTS]")
- parser.add_option('--doctest-extension', action="append",
- dest="doctestExtension",
- metavar="EXT",
- help="Also look for doctests in files with "
- "this extension [NOSE_DOCTEST_EXTENSION]")
- parser.add_option('--doctest-result-variable',
- dest='doctest_result_var',
- default=env.get('NOSE_DOCTEST_RESULT_VAR'),
- metavar="VAR",
- help="Change the variable name set to the result of "
- "the last interpreter command from the default '_'. "
- "Can be used to avoid conflicts with the _() "
- "function used for text translation. "
- "[NOSE_DOCTEST_RESULT_VAR]")
- parser.add_option('--doctest-fixtures', action="store",
- dest="doctestFixtures",
- metavar="SUFFIX",
- help="Find fixtures for a doctest file in module "
- "with this name appended to the base name "
- "of the doctest file")
- parser.add_option('--doctest-options', action="append",
- dest="doctestOptions",
- metavar="OPTIONS",
- help="Specify options to pass to doctest. " +
- "Eg. '+ELLIPSIS,+NORMALIZE_WHITESPACE'")
- # Set the default as a list, if given in env; otherwise
- # an additional value set on the command line will cause
- # an error.
- env_setting = env.get('NOSE_DOCTEST_EXTENSION')
- if env_setting is not None:
- parser.set_defaults(doctestExtension=tolist(env_setting))
-
- def configure(self, options, config):
- """Configure plugin.
- """
- Plugin.configure(self, options, config)
- self.doctest_result_var = options.doctest_result_var
- self.doctest_tests = options.doctest_tests
- self.extension = tolist(options.doctestExtension)
- self.fixtures = options.doctestFixtures
- self.finder = doctest.DocTestFinder()
- self.optionflags = 0
- if options.doctestOptions:
- flags = ",".join(options.doctestOptions).split(',')
- for flag in flags:
- if not flag or flag[0] not in '+-':
- raise ValueError(
- "Must specify doctest options with starting " +
- "'+' or '-'. Got %s" % (flag,))
- mode, option_name = flag[0], flag[1:]
- option_flag = doctest.OPTIONFLAGS_BY_NAME.get(option_name)
- if not option_flag:
- raise ValueError("Unknown doctest option %s" %
- (option_name,))
- if mode == '+':
- self.optionflags |= option_flag
- elif mode == '-':
- self.optionflags &= ~option_flag
-
- def prepareTestLoader(self, loader):
- """Capture loader's suiteClass.
-
- This is used to create test suites from doctest files.
-
- """
- self.suiteClass = loader.suiteClass
-
- def loadTestsFromModule(self, module):
- """Load doctests from the module.
- """
- log.debug("loading from %s", module)
- if not self.matches(module.__name__):
- log.debug("Doctest doesn't want module %s", module)
- return
- try:
- tests = self.finder.find(module)
- except AttributeError:
- log.exception("Attribute error loading from %s", module)
- # nose allows module.__test__ = False; doctest does not and throws
- # AttributeError
- return
- if not tests:
- log.debug("No tests found in %s", module)
- return
- tests.sort()
- module_file = src(module.__file__)
- # FIXME this breaks the id plugin somehow (tests probably don't
- # get wrapped in result proxy or something)
- cases = []
- for test in tests:
- if not test.examples:
- continue
- if not test.filename:
- test.filename = module_file
- cases.append(DocTestCase(test,
- optionflags=self.optionflags,
- result_var=self.doctest_result_var))
- if cases:
- yield self.suiteClass(cases, context=module, can_split=False)
-
- def loadTestsFromFile(self, filename):
- """Load doctests from the file.
-
- Tests are loaded only if filename's extension matches
- configured doctest extension.
-
- """
- if self.extension and anyp(filename.endswith, self.extension):
- name = os.path.basename(filename)
- dh = open(filename)
- try:
- doc = dh.read()
- finally:
- dh.close()
-
- fixture_context = None
- globs = {'__file__': filename}
- if self.fixtures:
- base, ext = os.path.splitext(name)
- dirname = os.path.dirname(filename)
- sys.path.append(dirname)
- fixt_mod = base + self.fixtures
- try:
- fixture_context = __import__(
- fixt_mod, globals(), locals(), ["nop"])
- except ImportError, e:
- log.debug(
- "Could not import %s: %s (%s)", fixt_mod, e, sys.path)
- log.debug("Fixture module %s resolved to %s",
- fixt_mod, fixture_context)
- if hasattr(fixture_context, 'globs'):
- globs = fixture_context.globs(globs)
- parser = doctest.DocTestParser()
- test = parser.get_doctest(
- doc, globs=globs, name=name,
- filename=filename, lineno=0)
- if test.examples:
- case = DocFileCase(
- test,
- optionflags=self.optionflags,
- setUp=getattr(fixture_context, 'setup_test', None),
- tearDown=getattr(fixture_context, 'teardown_test', None),
- result_var=self.doctest_result_var)
- if fixture_context:
- yield ContextList((case,), context=fixture_context)
- else:
- yield case
- else:
- yield False # no tests to load
-
- def makeTest(self, obj, parent):
- """Look for doctests in the given object, which will be a
- function, method or class.
- """
- name = getattr(obj, '__name__', 'Unnammed %s' % type(obj))
- doctests = self.finder.find(obj, module=getmodule(parent), name=name)
- if doctests:
- for test in doctests:
- if len(test.examples) == 0:
- continue
- yield DocTestCase(test, obj=obj, optionflags=self.optionflags,
- result_var=self.doctest_result_var)
-
- def matches(self, name):
- # FIXME this seems wrong -- nothing is ever going to
- # fail this test, since we're given a module NAME not FILE
- if name == '__init__.py':
- return False
- # FIXME don't think we need include/exclude checks here?
- return ((self.doctest_tests or not self.conf.testMatch.search(name)
- or (self.conf.include
- and filter(None,
- [inc.search(name)
- for inc in self.conf.include])))
- and (not self.conf.exclude
- or not filter(None,
- [exc.search(name)
- for exc in self.conf.exclude])))
-
- def wantFile(self, file):
- """Override to select all modules and any file ending with
- configured doctest extension.
- """
- # always want .py files
- if file.endswith('.py'):
- return True
- # also want files that match my extension
- if (self.extension
- and anyp(file.endswith, self.extension)
- and (not self.conf.exclude
- or not filter(None,
- [exc.search(file)
- for exc in self.conf.exclude]))):
- return True
- return None
-
-
-class DocTestCase(doctest.DocTestCase):
- """Overrides DocTestCase to
- provide an address() method that returns the correct address for
- the doctest case. To provide hints for address(), an obj may also
- be passed -- this will be used as the test object for purposes of
- determining the test address, if it is provided.
- """
- def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
- checker=None, obj=None, result_var='_'):
- self._result_var = result_var
- self._nose_obj = obj
- super(DocTestCase, self).__init__(
- test, optionflags=optionflags, setUp=setUp, tearDown=tearDown,
- checker=checker)
-
- def address(self):
- if self._nose_obj is not None:
- return test_address(self._nose_obj)
- obj = resolve_name(self._dt_test.name)
-
- if isproperty(obj):
- # properties have no connection to the class they are in
- # so we can't just look 'em up, we have to first look up
- # the class, then stick the prop on the end
- parts = self._dt_test.name.split('.')
- class_name = '.'.join(parts[:-1])
- cls = resolve_name(class_name)
- base_addr = test_address(cls)
- return (base_addr[0], base_addr[1],
- '.'.join([base_addr[2], parts[-1]]))
- else:
- return test_address(obj)
-
- # doctests loaded via find(obj) omit the module name
- # so we need to override id, __repr__ and shortDescription
- # bonus: this will squash a 2.3 vs 2.4 incompatiblity
- def id(self):
- name = self._dt_test.name
- filename = self._dt_test.filename
- if filename is not None:
- pk = getpackage(filename)
- if pk is None:
- return name
- if not name.startswith(pk):
- name = "%s.%s" % (pk, name)
- return name
-
- def __repr__(self):
- name = self.id()
- name = name.split('.')
- return "%s (%s)" % (name[-1], '.'.join(name[:-1]))
- __str__ = __repr__
-
- def shortDescription(self):
- return 'Doctest: %s' % self.id()
-
- def setUp(self):
- if self._result_var is not None:
- self._old_displayhook = sys.displayhook
- sys.displayhook = self._displayhook
- super(DocTestCase, self).setUp()
-
- def _displayhook(self, value):
- if value is None:
- return
- setattr(builtin_mod, self._result_var, value)
- print repr(value)
-
- def tearDown(self):
- super(DocTestCase, self).tearDown()
- if self._result_var is not None:
- sys.displayhook = self._old_displayhook
- delattr(builtin_mod, self._result_var)
-
-
-class DocFileCase(doctest.DocFileCase):
- """Overrides to provide address() method that returns the correct
- address for the doc file case.
- """
- def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
- checker=None, result_var='_'):
- self._result_var = result_var
- super(DocFileCase, self).__init__(
- test, optionflags=optionflags, setUp=setUp, tearDown=tearDown,
- checker=None)
-
- def address(self):
- return (self._dt_test.filename, None, None)
-
- def setUp(self):
- if self._result_var is not None:
- self._old_displayhook = sys.displayhook
- sys.displayhook = self._displayhook
- super(DocFileCase, self).setUp()
-
- def _displayhook(self, value):
- if value is None:
- return
- setattr(builtin_mod, self._result_var, value)
- print repr(value)
-
- def tearDown(self):
- super(DocFileCase, self).tearDown()
- if self._result_var is not None:
- sys.displayhook = self._old_displayhook
- delattr(builtin_mod, self._result_var)
diff --git a/lib/spack/external/nose/plugins/errorclass.py b/lib/spack/external/nose/plugins/errorclass.py
deleted file mode 100644
index d1540e0070..0000000000
--- a/lib/spack/external/nose/plugins/errorclass.py
+++ /dev/null
@@ -1,210 +0,0 @@
-"""
-ErrorClass Plugins
-------------------
-
-ErrorClass plugins provide an easy way to add support for custom
-handling of particular classes of exceptions.
-
-An ErrorClass plugin defines one or more ErrorClasses and how each is
-handled and reported on. Each error class is stored in a different
-attribute on the result, and reported separately. Each error class must
-indicate the exceptions that fall under that class, the label to use
-for reporting, and whether exceptions of the class should be
-considered as failures for the whole test run.
-
-ErrorClasses use a declarative syntax. Assign an ErrorClass to the
-attribute you wish to add to the result object, defining the
-exceptions, label and isfailure attributes. For example, to declare an
-ErrorClassPlugin that defines TodoErrors (and subclasses of TodoError)
-as an error class with the label 'TODO' that is considered a failure,
-do this:
-
- >>> class Todo(Exception):
- ... pass
- >>> class TodoError(ErrorClassPlugin):
- ... todo = ErrorClass(Todo, label='TODO', isfailure=True)
-
-The MetaErrorClass metaclass translates the ErrorClass declarations
-into the tuples used by the error handling and reporting functions in
-the result. This is an internal format and subject to change; you
-should always use the declarative syntax for attaching ErrorClasses to
-an ErrorClass plugin.
-
- >>> TodoError.errorClasses # doctest: +ELLIPSIS
- ((<class ...Todo...>, ('todo', 'TODO', True)),)
-
-Let's see the plugin in action. First some boilerplate.
-
- >>> import sys
- >>> import unittest
- >>> try:
- ... # 2.7+
- ... from unittest.runner import _WritelnDecorator
- ... except ImportError:
- ... from unittest import _WritelnDecorator
- ...
- >>> buf = _WritelnDecorator(sys.stdout)
-
-Now define a test case that raises a Todo.
-
- >>> class TestTodo(unittest.TestCase):
- ... def runTest(self):
- ... raise Todo("I need to test something")
- >>> case = TestTodo()
-
-Prepare the result using our plugin. Normally this happens during the
-course of test execution within nose -- you won't be doing this
-yourself. For the purposes of this testing document, I'm stepping
-through the internal process of nose so you can see what happens at
-each step.
-
- >>> plugin = TodoError()
- >>> from nose.result import _TextTestResult
- >>> result = _TextTestResult(stream=buf, descriptions=0, verbosity=2)
- >>> plugin.prepareTestResult(result)
-
-Now run the test. TODO is printed.
-
- >>> _ = case(result) # doctest: +ELLIPSIS
- runTest (....TestTodo) ... TODO: I need to test something
-
-Errors and failures are empty, but todo has our test:
-
- >>> result.errors
- []
- >>> result.failures
- []
- >>> result.todo # doctest: +ELLIPSIS
- [(<....TestTodo testMethod=runTest>, '...Todo: I need to test something\\n')]
- >>> result.printErrors() # doctest: +ELLIPSIS
- <BLANKLINE>
- ======================================================================
- TODO: runTest (....TestTodo)
- ----------------------------------------------------------------------
- Traceback (most recent call last):
- ...
- ...Todo: I need to test something
- <BLANKLINE>
-
-Since we defined a Todo as a failure, the run was not successful.
-
- >>> result.wasSuccessful()
- False
-"""
-
-from nose.pyversion import make_instancemethod
-from nose.plugins.base import Plugin
-from nose.result import TextTestResult
-from nose.util import isclass
-
-class MetaErrorClass(type):
- """Metaclass for ErrorClassPlugins that allows error classes to be
- set up in a declarative manner.
- """
- def __init__(self, name, bases, attr):
- errorClasses = []
- for name, detail in attr.items():
- if isinstance(detail, ErrorClass):
- attr.pop(name)
- for cls in detail:
- errorClasses.append(
- (cls, (name, detail.label, detail.isfailure)))
- super(MetaErrorClass, self).__init__(name, bases, attr)
- self.errorClasses = tuple(errorClasses)
-
-
-class ErrorClass(object):
- def __init__(self, *errorClasses, **kw):
- self.errorClasses = errorClasses
- try:
- for key in ('label', 'isfailure'):
- setattr(self, key, kw.pop(key))
- except KeyError:
- raise TypeError("%r is a required named argument for ErrorClass"
- % key)
-
- def __iter__(self):
- return iter(self.errorClasses)
-
-
-class ErrorClassPlugin(Plugin):
- """
- Base class for ErrorClass plugins. Subclass this class and declare the
- exceptions that you wish to handle as attributes of the subclass.
- """
- __metaclass__ = MetaErrorClass
- score = 1000
- errorClasses = ()
-
- def addError(self, test, err):
- err_cls, a, b = err
- if not isclass(err_cls):
- return
- classes = [e[0] for e in self.errorClasses]
- if filter(lambda c: issubclass(err_cls, c), classes):
- return True
-
- def prepareTestResult(self, result):
- if not hasattr(result, 'errorClasses'):
- self.patchResult(result)
- for cls, (storage_attr, label, isfail) in self.errorClasses:
- if cls not in result.errorClasses:
- storage = getattr(result, storage_attr, [])
- setattr(result, storage_attr, storage)
- result.errorClasses[cls] = (storage, label, isfail)
-
- def patchResult(self, result):
- result.printLabel = print_label_patch(result)
- result._orig_addError, result.addError = \
- result.addError, add_error_patch(result)
- result._orig_wasSuccessful, result.wasSuccessful = \
- result.wasSuccessful, wassuccessful_patch(result)
- if hasattr(result, 'printErrors'):
- result._orig_printErrors, result.printErrors = \
- result.printErrors, print_errors_patch(result)
- if hasattr(result, 'addSkip'):
- result._orig_addSkip, result.addSkip = \
- result.addSkip, add_skip_patch(result)
- result.errorClasses = {}
-
-
-def add_error_patch(result):
- """Create a new addError method to patch into a result instance
- that recognizes the errorClasses attribute and deals with
- errorclasses correctly.
- """
- return make_instancemethod(TextTestResult.addError, result)
-
-
-def print_errors_patch(result):
- """Create a new printErrors method that prints errorClasses items
- as well.
- """
- return make_instancemethod(TextTestResult.printErrors, result)
-
-
-def print_label_patch(result):
- """Create a new printLabel method that prints errorClasses items
- as well.
- """
- return make_instancemethod(TextTestResult.printLabel, result)
-
-
-def wassuccessful_patch(result):
- """Create a new wasSuccessful method that checks errorClasses for
- exceptions that were put into other slots than error or failure
- but that still count as not success.
- """
- return make_instancemethod(TextTestResult.wasSuccessful, result)
-
-
-def add_skip_patch(result):
- """Create a new addSkip method to patch into a result instance
- that delegates to addError.
- """
- return make_instancemethod(TextTestResult.addSkip, result)
-
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
diff --git a/lib/spack/external/nose/plugins/failuredetail.py b/lib/spack/external/nose/plugins/failuredetail.py
deleted file mode 100644
index 6462865dd0..0000000000
--- a/lib/spack/external/nose/plugins/failuredetail.py
+++ /dev/null
@@ -1,49 +0,0 @@
-"""
-This plugin provides assert introspection. When the plugin is enabled
-and a test failure occurs, the traceback is displayed with extra context
-around the line in which the exception was raised. Simple variable
-substitution is also performed in the context output to provide more
-debugging information.
-"""
-
-from nose.plugins import Plugin
-from nose.pyversion import exc_to_unicode, force_unicode
-from nose.inspector import inspect_traceback
-
-class FailureDetail(Plugin):
- """
- Plugin that provides extra information in tracebacks of test failures.
- """
- score = 1600 # before capture
-
- def options(self, parser, env):
- """Register commmandline options.
- """
- parser.add_option(
- "-d", "--detailed-errors", "--failure-detail",
- action="store_true",
- default=env.get('NOSE_DETAILED_ERRORS'),
- dest="detailedErrors", help="Add detail to error"
- " output by attempting to evaluate failed"
- " asserts [NOSE_DETAILED_ERRORS]")
-
- def configure(self, options, conf):
- """Configure plugin.
- """
- if not self.can_configure:
- return
- self.enabled = options.detailedErrors
- self.conf = conf
-
- def formatFailure(self, test, err):
- """Add detail from traceback inspection to error message of a failure.
- """
- ec, ev, tb = err
- tbinfo, str_ev = None, exc_to_unicode(ev)
-
- if tb:
- tbinfo = force_unicode(inspect_traceback(tb))
- str_ev = '\n'.join([str_ev, tbinfo])
- test.tbinfo = tbinfo
- return (ec, str_ev, tb)
-
diff --git a/lib/spack/external/nose/plugins/isolate.py b/lib/spack/external/nose/plugins/isolate.py
deleted file mode 100644
index 13235dfbd1..0000000000
--- a/lib/spack/external/nose/plugins/isolate.py
+++ /dev/null
@@ -1,103 +0,0 @@
-"""The isolation plugin resets the contents of sys.modules after running
-each test module or package. Use it by setting ``--with-isolation`` or the
-NOSE_WITH_ISOLATION environment variable.
-
-The effects are similar to wrapping the following functions around the
-import and execution of each test module::
-
- def setup(module):
- module._mods = sys.modules.copy()
-
- def teardown(module):
- to_del = [ m for m in sys.modules.keys() if m not in
- module._mods ]
- for mod in to_del:
- del sys.modules[mod]
- sys.modules.update(module._mods)
-
-Isolation works only during lazy loading. In normal use, this is only
-during discovery of modules within a directory, where the process of
-importing, loading tests and running tests from each module is
-encapsulated in a single loadTestsFromName call. This plugin
-implements loadTestsFromNames to force the same lazy-loading there,
-which allows isolation to work in directed mode as well as discovery,
-at the cost of some efficiency: lazy-loading names forces full context
-setup and teardown to run for each name, defeating the grouping that
-is normally used to ensure that context setup and teardown are run the
-fewest possible times for a given set of names.
-
-.. warning ::
-
- This plugin should not be used in conjunction with other plugins
- that assume that modules, once imported, will stay imported; for
- instance, it may cause very odd results when used with the coverage
- plugin.
-
-"""
-
-import logging
-import sys
-
-from nose.plugins import Plugin
-
-
-log = logging.getLogger('nose.plugins.isolation')
-
-class IsolationPlugin(Plugin):
- """
- Activate the isolation plugin to isolate changes to external
- modules to a single test module or package. The isolation plugin
- resets the contents of sys.modules after each test module or
- package runs to its state before the test. PLEASE NOTE that this
- plugin should not be used with the coverage plugin, or in any other case
- where module reloading may produce undesirable side-effects.
- """
- score = 10 # I want to be last
- name = 'isolation'
-
- def configure(self, options, conf):
- """Configure plugin.
- """
- Plugin.configure(self, options, conf)
- self._mod_stack = []
-
- def beforeContext(self):
- """Copy sys.modules onto my mod stack
- """
- mods = sys.modules.copy()
- self._mod_stack.append(mods)
-
- def afterContext(self):
- """Pop my mod stack and restore sys.modules to the state
- it was in when mod stack was pushed.
- """
- mods = self._mod_stack.pop()
- to_del = [ m for m in sys.modules.keys() if m not in mods ]
- if to_del:
- log.debug('removing sys modules entries: %s', to_del)
- for mod in to_del:
- del sys.modules[mod]
- sys.modules.update(mods)
-
- def loadTestsFromNames(self, names, module=None):
- """Create a lazy suite that calls beforeContext and afterContext
- around each name. The side-effect of this is that full context
- fixtures will be set up and torn down around each test named.
- """
- # Fast path for when we don't care
- if not names or len(names) == 1:
- return
- loader = self.loader
- plugins = self.conf.plugins
- def lazy():
- for name in names:
- plugins.beforeContext()
- yield loader.loadTestsFromName(name, module=module)
- plugins.afterContext()
- return (loader.suiteClass(lazy), [])
-
- def prepareTestLoader(self, loader):
- """Get handle on test loader so we can use it in loadTestsFromNames.
- """
- self.loader = loader
-
diff --git a/lib/spack/external/nose/plugins/logcapture.py b/lib/spack/external/nose/plugins/logcapture.py
deleted file mode 100644
index 4c9a79f6fd..0000000000
--- a/lib/spack/external/nose/plugins/logcapture.py
+++ /dev/null
@@ -1,245 +0,0 @@
-"""
-This plugin captures logging statements issued during test execution. When an
-error or failure occurs, the captured log messages are attached to the running
-test in the test.capturedLogging attribute, and displayed with the error failure
-output. It is enabled by default but can be turned off with the option
-``--nologcapture``.
-
-You can filter captured logging statements with the ``--logging-filter`` option.
-If set, it specifies which logger(s) will be captured; loggers that do not match
-will be passed. Example: specifying ``--logging-filter=sqlalchemy,myapp``
-will ensure that only statements logged via sqlalchemy.engine, myapp
-or myapp.foo.bar logger will be logged.
-
-You can remove other installed logging handlers with the
-``--logging-clear-handlers`` option.
-"""
-
-import logging
-from logging import Handler
-import threading
-
-from nose.plugins.base import Plugin
-from nose.util import anyp, ln, safe_str
-
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-
-log = logging.getLogger(__name__)
-
-class FilterSet(object):
- def __init__(self, filter_components):
- self.inclusive, self.exclusive = self._partition(filter_components)
-
- # @staticmethod
- def _partition(components):
- inclusive, exclusive = [], []
- for component in components:
- if component.startswith('-'):
- exclusive.append(component[1:])
- else:
- inclusive.append(component)
- return inclusive, exclusive
- _partition = staticmethod(_partition)
-
- def allow(self, record):
- """returns whether this record should be printed"""
- if not self:
- # nothing to filter
- return True
- return self._allow(record) and not self._deny(record)
-
- # @staticmethod
- def _any_match(matchers, record):
- """return the bool of whether `record` starts with
- any item in `matchers`"""
- def record_matches_key(key):
- return record == key or record.startswith(key + '.')
- return anyp(bool, map(record_matches_key, matchers))
- _any_match = staticmethod(_any_match)
-
- def _allow(self, record):
- if not self.inclusive:
- return True
- return self._any_match(self.inclusive, record)
-
- def _deny(self, record):
- if not self.exclusive:
- return False
- return self._any_match(self.exclusive, record)
-
-
-class MyMemoryHandler(Handler):
- def __init__(self, logformat, logdatefmt, filters):
- Handler.__init__(self)
- fmt = logging.Formatter(logformat, logdatefmt)
- self.setFormatter(fmt)
- self.filterset = FilterSet(filters)
- self.buffer = []
- def emit(self, record):
- self.buffer.append(self.format(record))
- def flush(self):
- pass # do nothing
- def truncate(self):
- self.buffer = []
- def filter(self, record):
- if self.filterset.allow(record.name):
- return Handler.filter(self, record)
- def __getstate__(self):
- state = self.__dict__.copy()
- del state['lock']
- return state
- def __setstate__(self, state):
- self.__dict__.update(state)
- self.lock = threading.RLock()
-
-
-class LogCapture(Plugin):
- """
- Log capture plugin. Enabled by default. Disable with --nologcapture.
- This plugin captures logging statements issued during test execution,
- appending any output captured to the error or failure output,
- should the test fail or raise an error.
- """
- enabled = True
- env_opt = 'NOSE_NOLOGCAPTURE'
- name = 'logcapture'
- score = 500
- logformat = '%(name)s: %(levelname)s: %(message)s'
- logdatefmt = None
- clear = False
- filters = ['-nose']
-
- def options(self, parser, env):
- """Register commandline options.
- """
- parser.add_option(
- "--nologcapture", action="store_false",
- default=not env.get(self.env_opt), dest="logcapture",
- help="Disable logging capture plugin. "
- "Logging configuration will be left intact."
- " [NOSE_NOLOGCAPTURE]")
- parser.add_option(
- "--logging-format", action="store", dest="logcapture_format",
- default=env.get('NOSE_LOGFORMAT') or self.logformat,
- metavar="FORMAT",
- help="Specify custom format to print statements. "
- "Uses the same format as used by standard logging handlers."
- " [NOSE_LOGFORMAT]")
- parser.add_option(
- "--logging-datefmt", action="store", dest="logcapture_datefmt",
- default=env.get('NOSE_LOGDATEFMT') or self.logdatefmt,
- metavar="FORMAT",
- help="Specify custom date/time format to print statements. "
- "Uses the same format as used by standard logging handlers."
- " [NOSE_LOGDATEFMT]")
- parser.add_option(
- "--logging-filter", action="store", dest="logcapture_filters",
- default=env.get('NOSE_LOGFILTER'),
- metavar="FILTER",
- help="Specify which statements to filter in/out. "
- "By default, everything is captured. If the output is too"
- " verbose,\nuse this option to filter out needless output.\n"
- "Example: filter=foo will capture statements issued ONLY to\n"
- " foo or foo.what.ever.sub but not foobar or other logger.\n"
- "Specify multiple loggers with comma: filter=foo,bar,baz.\n"
- "If any logger name is prefixed with a minus, eg filter=-foo,\n"
- "it will be excluded rather than included. Default: "
- "exclude logging messages from nose itself (-nose)."
- " [NOSE_LOGFILTER]\n")
- parser.add_option(
- "--logging-clear-handlers", action="store_true",
- default=False, dest="logcapture_clear",
- help="Clear all other logging handlers")
- parser.add_option(
- "--logging-level", action="store",
- default='NOTSET', dest="logcapture_level",
- help="Set the log level to capture")
-
- def configure(self, options, conf):
- """Configure plugin.
- """
- self.conf = conf
- # Disable if explicitly disabled, or if logging is
- # configured via logging config file
- if not options.logcapture or conf.loggingConfig:
- self.enabled = False
- self.logformat = options.logcapture_format
- self.logdatefmt = options.logcapture_datefmt
- self.clear = options.logcapture_clear
- self.loglevel = options.logcapture_level
- if options.logcapture_filters:
- self.filters = options.logcapture_filters.split(',')
-
- def setupLoghandler(self):
- # setup our handler with root logger
- root_logger = logging.getLogger()
- if self.clear:
- if hasattr(root_logger, "handlers"):
- for handler in root_logger.handlers:
- root_logger.removeHandler(handler)
- for logger in logging.Logger.manager.loggerDict.values():
- if hasattr(logger, "handlers"):
- for handler in logger.handlers:
- logger.removeHandler(handler)
- # make sure there isn't one already
- # you can't simply use "if self.handler not in root_logger.handlers"
- # since at least in unit tests this doesn't work --
- # LogCapture() is instantiated for each test case while root_logger
- # is module global
- # so we always add new MyMemoryHandler instance
- for handler in root_logger.handlers[:]:
- if isinstance(handler, MyMemoryHandler):
- root_logger.handlers.remove(handler)
- root_logger.addHandler(self.handler)
- # to make sure everything gets captured
- loglevel = getattr(self, "loglevel", "NOTSET")
- root_logger.setLevel(getattr(logging, loglevel))
-
- def begin(self):
- """Set up logging handler before test run begins.
- """
- self.start()
-
- def start(self):
- self.handler = MyMemoryHandler(self.logformat, self.logdatefmt,
- self.filters)
- self.setupLoghandler()
-
- def end(self):
- pass
-
- def beforeTest(self, test):
- """Clear buffers and handlers before test.
- """
- self.setupLoghandler()
-
- def afterTest(self, test):
- """Clear buffers after test.
- """
- self.handler.truncate()
-
- def formatFailure(self, test, err):
- """Add captured log messages to failure output.
- """
- return self.formatError(test, err)
-
- def formatError(self, test, err):
- """Add captured log messages to error output.
- """
- # logic flow copied from Capture.formatError
- test.capturedLogging = records = self.formatLogRecords()
- if not records:
- return err
- ec, ev, tb = err
- return (ec, self.addCaptureToErr(ev, records), tb)
-
- def formatLogRecords(self):
- return map(safe_str, self.handler.buffer)
-
- def addCaptureToErr(self, ev, records):
- return '\n'.join([safe_str(ev), ln('>> begin captured logging <<')] + \
- records + \
- [ln('>> end captured logging <<')])
diff --git a/lib/spack/external/nose/plugins/manager.py b/lib/spack/external/nose/plugins/manager.py
deleted file mode 100644
index 4d2ed22b6f..0000000000
--- a/lib/spack/external/nose/plugins/manager.py
+++ /dev/null
@@ -1,460 +0,0 @@
-"""
-Plugin Manager
---------------
-
-A plugin manager class is used to load plugins, manage the list of
-loaded plugins, and proxy calls to those plugins.
-
-The plugin managers provided with nose are:
-
-:class:`PluginManager`
- This manager doesn't implement loadPlugins, so it can only work
- with a static list of plugins.
-
-:class:`BuiltinPluginManager`
- This manager loads plugins referenced in ``nose.plugins.builtin``.
-
-:class:`EntryPointPluginManager`
- This manager uses setuptools entrypoints to load plugins.
-
-:class:`ExtraPluginsPluginManager`
- This manager loads extra plugins specified with the keyword
- `addplugins`.
-
-:class:`DefaultPluginMananger`
- This is the manager class that will be used by default. If
- setuptools is installed, it is a subclass of
- :class:`EntryPointPluginManager` and :class:`BuiltinPluginManager`;
- otherwise, an alias to :class:`BuiltinPluginManager`.
-
-:class:`RestrictedPluginManager`
- This manager is for use in test runs where some plugin calls are
- not available, such as runs started with ``python setup.py test``,
- where the test runner is the default unittest :class:`TextTestRunner`. It
- is a subclass of :class:`DefaultPluginManager`.
-
-Writing a plugin manager
-========================
-
-If you want to load plugins via some other means, you can write a
-plugin manager and pass an instance of your plugin manager class when
-instantiating the :class:`nose.config.Config` instance that you pass to
-:class:`TestProgram` (or :func:`main` or :func:`run`).
-
-To implement your plugin loading scheme, implement ``loadPlugins()``,
-and in that method, call ``addPlugin()`` with an instance of each plugin
-you wish to make available. Make sure to call
-``super(self).loadPlugins()`` as well if have subclassed a manager
-other than ``PluginManager``.
-
-"""
-import inspect
-import logging
-import os
-import sys
-from itertools import chain as iterchain
-from warnings import warn
-import nose.config
-from nose.failure import Failure
-from nose.plugins.base import IPluginInterface
-from nose.pyversion import sort_list
-
-try:
- import cPickle as pickle
-except:
- import pickle
-try:
- from cStringIO import StringIO
-except:
- from StringIO import StringIO
-
-
-__all__ = ['DefaultPluginManager', 'PluginManager', 'EntryPointPluginManager',
- 'BuiltinPluginManager', 'RestrictedPluginManager']
-
-log = logging.getLogger(__name__)
-
-
-class PluginProxy(object):
- """Proxy for plugin calls. Essentially a closure bound to the
- given call and plugin list.
-
- The plugin proxy also must be bound to a particular plugin
- interface specification, so that it knows what calls are available
- and any special handling that is required for each call.
- """
- interface = IPluginInterface
- def __init__(self, call, plugins):
- try:
- self.method = getattr(self.interface, call)
- except AttributeError:
- raise AttributeError("%s is not a valid %s method"
- % (call, self.interface.__name__))
- self.call = self.makeCall(call)
- self.plugins = []
- for p in plugins:
- self.addPlugin(p, call)
-
- def __call__(self, *arg, **kw):
- return self.call(*arg, **kw)
-
- def addPlugin(self, plugin, call):
- """Add plugin to my list of plugins to call, if it has the attribute
- I'm bound to.
- """
- meth = getattr(plugin, call, None)
- if meth is not None:
- if call == 'loadTestsFromModule' and \
- len(inspect.getargspec(meth)[0]) == 2:
- orig_meth = meth
- meth = lambda module, path, **kwargs: orig_meth(module)
- self.plugins.append((plugin, meth))
-
- def makeCall(self, call):
- if call == 'loadTestsFromNames':
- # special case -- load tests from names behaves somewhat differently
- # from other chainable calls, because plugins return a tuple, only
- # part of which can be chained to the next plugin.
- return self._loadTestsFromNames
-
- meth = self.method
- if getattr(meth, 'generative', False):
- # call all plugins and yield a flattened iterator of their results
- return lambda *arg, **kw: list(self.generate(*arg, **kw))
- elif getattr(meth, 'chainable', False):
- return self.chain
- else:
- # return a value from the first plugin that returns non-None
- return self.simple
-
- def chain(self, *arg, **kw):
- """Call plugins in a chain, where the result of each plugin call is
- sent to the next plugin as input. The final output result is returned.
- """
- result = None
- # extract the static arguments (if any) from arg so they can
- # be passed to each plugin call in the chain
- static = [a for (static, a)
- in zip(getattr(self.method, 'static_args', []), arg)
- if static]
- for p, meth in self.plugins:
- result = meth(*arg, **kw)
- arg = static[:]
- arg.append(result)
- return result
-
- def generate(self, *arg, **kw):
- """Call all plugins, yielding each item in each non-None result.
- """
- for p, meth in self.plugins:
- result = None
- try:
- result = meth(*arg, **kw)
- if result is not None:
- for r in result:
- yield r
- except (KeyboardInterrupt, SystemExit):
- raise
- except:
- exc = sys.exc_info()
- yield Failure(*exc)
- continue
-
- def simple(self, *arg, **kw):
- """Call all plugins, returning the first non-None result.
- """
- for p, meth in self.plugins:
- result = meth(*arg, **kw)
- if result is not None:
- return result
-
- def _loadTestsFromNames(self, names, module=None):
- """Chainable but not quite normal. Plugins return a tuple of
- (tests, names) after processing the names. The tests are added
- to a suite that is accumulated throughout the full call, while
- names are input for the next plugin in the chain.
- """
- suite = []
- for p, meth in self.plugins:
- result = meth(names, module=module)
- if result is not None:
- suite_part, names = result
- if suite_part:
- suite.extend(suite_part)
- return suite, names
-
-
-class NoPlugins(object):
- """Null Plugin manager that has no plugins."""
- interface = IPluginInterface
- def __init__(self):
- self._plugins = self.plugins = ()
-
- def __iter__(self):
- return ()
-
- def _doNothing(self, *args, **kwds):
- pass
-
- def _emptyIterator(self, *args, **kwds):
- return ()
-
- def __getattr__(self, call):
- method = getattr(self.interface, call)
- if getattr(method, "generative", False):
- return self._emptyIterator
- else:
- return self._doNothing
-
- def addPlugin(self, plug):
- raise NotImplementedError()
-
- def addPlugins(self, plugins):
- raise NotImplementedError()
-
- def configure(self, options, config):
- pass
-
- def loadPlugins(self):
- pass
-
- def sort(self):
- pass
-
-
-class PluginManager(object):
- """Base class for plugin managers. PluginManager is intended to be
- used only with a static list of plugins. The loadPlugins() implementation
- only reloads plugins from _extraplugins to prevent those from being
- overridden by a subclass.
-
- The basic functionality of a plugin manager is to proxy all unknown
- attributes through a ``PluginProxy`` to a list of plugins.
-
- Note that the list of plugins *may not* be changed after the first plugin
- call.
- """
- proxyClass = PluginProxy
-
- def __init__(self, plugins=(), proxyClass=None):
- self._plugins = []
- self._extraplugins = ()
- self._proxies = {}
- if plugins:
- self.addPlugins(plugins)
- if proxyClass is not None:
- self.proxyClass = proxyClass
-
- def __getattr__(self, call):
- try:
- return self._proxies[call]
- except KeyError:
- proxy = self.proxyClass(call, self._plugins)
- self._proxies[call] = proxy
- return proxy
-
- def __iter__(self):
- return iter(self.plugins)
-
- def addPlugin(self, plug):
- # allow, for instance, plugins loaded via entry points to
- # supplant builtin plugins.
- new_name = getattr(plug, 'name', object())
- self._plugins[:] = [p for p in self._plugins
- if getattr(p, 'name', None) != new_name]
- self._plugins.append(plug)
-
- def addPlugins(self, plugins=(), extraplugins=()):
- """extraplugins are maintained in a separate list and
- re-added by loadPlugins() to prevent their being overwritten
- by plugins added by a subclass of PluginManager
- """
- self._extraplugins = extraplugins
- for plug in iterchain(plugins, extraplugins):
- self.addPlugin(plug)
-
- def configure(self, options, config):
- """Configure the set of plugins with the given options
- and config instance. After configuration, disabled plugins
- are removed from the plugins list.
- """
- log.debug("Configuring plugins")
- self.config = config
- cfg = PluginProxy('configure', self._plugins)
- cfg(options, config)
- enabled = [plug for plug in self._plugins if plug.enabled]
- self.plugins = enabled
- self.sort()
- log.debug("Plugins enabled: %s", enabled)
-
- def loadPlugins(self):
- for plug in self._extraplugins:
- self.addPlugin(plug)
-
- def sort(self):
- return sort_list(self._plugins, lambda x: getattr(x, 'score', 1), reverse=True)
-
- def _get_plugins(self):
- return self._plugins
-
- def _set_plugins(self, plugins):
- self._plugins = []
- self.addPlugins(plugins)
-
- plugins = property(_get_plugins, _set_plugins, None,
- """Access the list of plugins managed by
- this plugin manager""")
-
-
-class ZeroNinePlugin:
- """Proxy for 0.9 plugins, adapts 0.10 calls to 0.9 standard.
- """
- def __init__(self, plugin):
- self.plugin = plugin
-
- def options(self, parser, env=os.environ):
- self.plugin.add_options(parser, env)
-
- def addError(self, test, err):
- if not hasattr(self.plugin, 'addError'):
- return
- # switch off to addSkip, addDeprecated if those types
- from nose.exc import SkipTest, DeprecatedTest
- ec, ev, tb = err
- if issubclass(ec, SkipTest):
- if not hasattr(self.plugin, 'addSkip'):
- return
- return self.plugin.addSkip(test.test)
- elif issubclass(ec, DeprecatedTest):
- if not hasattr(self.plugin, 'addDeprecated'):
- return
- return self.plugin.addDeprecated(test.test)
- # add capt
- capt = test.capturedOutput
- return self.plugin.addError(test.test, err, capt)
-
- def loadTestsFromFile(self, filename):
- if hasattr(self.plugin, 'loadTestsFromPath'):
- return self.plugin.loadTestsFromPath(filename)
-
- def addFailure(self, test, err):
- if not hasattr(self.plugin, 'addFailure'):
- return
- # add capt and tbinfo
- capt = test.capturedOutput
- tbinfo = test.tbinfo
- return self.plugin.addFailure(test.test, err, capt, tbinfo)
-
- def addSuccess(self, test):
- if not hasattr(self.plugin, 'addSuccess'):
- return
- capt = test.capturedOutput
- self.plugin.addSuccess(test.test, capt)
-
- def startTest(self, test):
- if not hasattr(self.plugin, 'startTest'):
- return
- return self.plugin.startTest(test.test)
-
- def stopTest(self, test):
- if not hasattr(self.plugin, 'stopTest'):
- return
- return self.plugin.stopTest(test.test)
-
- def __getattr__(self, val):
- return getattr(self.plugin, val)
-
-
-class EntryPointPluginManager(PluginManager):
- """Plugin manager that loads plugins from the `nose.plugins` and
- `nose.plugins.0.10` entry points.
- """
- entry_points = (('nose.plugins.0.10', None),
- ('nose.plugins', ZeroNinePlugin))
-
- def loadPlugins(self):
- """Load plugins by iterating the `nose.plugins` entry point.
- """
- from pkg_resources import iter_entry_points
- loaded = {}
- for entry_point, adapt in self.entry_points:
- for ep in iter_entry_points(entry_point):
- if ep.name in loaded:
- continue
- loaded[ep.name] = True
- log.debug('%s load plugin %s', self.__class__.__name__, ep)
- try:
- plugcls = ep.load()
- except KeyboardInterrupt:
- raise
- except Exception, e:
- # never want a plugin load to kill the test run
- # but we can't log here because the logger is not yet
- # configured
- warn("Unable to load plugin %s: %s" % (ep, e),
- RuntimeWarning)
- continue
- if adapt:
- plug = adapt(plugcls())
- else:
- plug = plugcls()
- self.addPlugin(plug)
- super(EntryPointPluginManager, self).loadPlugins()
-
-
-class BuiltinPluginManager(PluginManager):
- """Plugin manager that loads plugins from the list in
- `nose.plugins.builtin`.
- """
- def loadPlugins(self):
- """Load plugins in nose.plugins.builtin
- """
- from nose.plugins import builtin
- for plug in builtin.plugins:
- self.addPlugin(plug())
- super(BuiltinPluginManager, self).loadPlugins()
-
-try:
- import pkg_resources
- class DefaultPluginManager(EntryPointPluginManager, BuiltinPluginManager):
- pass
-
-except ImportError:
- class DefaultPluginManager(BuiltinPluginManager):
- pass
-
-class RestrictedPluginManager(DefaultPluginManager):
- """Plugin manager that restricts the plugin list to those not
- excluded by a list of exclude methods. Any plugin that implements
- an excluded method will be removed from the manager's plugin list
- after plugins are loaded.
- """
- def __init__(self, plugins=(), exclude=(), load=True):
- DefaultPluginManager.__init__(self, plugins)
- self.load = load
- self.exclude = exclude
- self.excluded = []
- self._excludedOpts = None
-
- def excludedOption(self, name):
- if self._excludedOpts is None:
- from optparse import OptionParser
- self._excludedOpts = OptionParser(add_help_option=False)
- for plugin in self.excluded:
- plugin.options(self._excludedOpts, env={})
- return self._excludedOpts.get_option('--' + name)
-
- def loadPlugins(self):
- if self.load:
- DefaultPluginManager.loadPlugins(self)
- allow = []
- for plugin in self.plugins:
- ok = True
- for method in self.exclude:
- if hasattr(plugin, method):
- ok = False
- self.excluded.append(plugin)
- break
- if ok:
- allow.append(plugin)
- self.plugins = allow
diff --git a/lib/spack/external/nose/plugins/multiprocess.py b/lib/spack/external/nose/plugins/multiprocess.py
deleted file mode 100644
index 2cae744a11..0000000000
--- a/lib/spack/external/nose/plugins/multiprocess.py
+++ /dev/null
@@ -1,835 +0,0 @@
-"""
-Overview
-========
-
-The multiprocess plugin enables you to distribute your test run among a set of
-worker processes that run tests in parallel. This can speed up CPU-bound test
-runs (as long as the number of work processeses is around the number of
-processors or cores available), but is mainly useful for IO-bound tests that
-spend most of their time waiting for data to arrive from someplace else.
-
-.. note ::
-
- See :doc:`../doc_tests/test_multiprocess/multiprocess` for
- additional documentation and examples. Use of this plugin on python
- 2.5 or earlier requires the multiprocessing_ module, also available
- from PyPI.
-
-.. _multiprocessing : http://code.google.com/p/python-multiprocessing/
-
-How tests are distributed
-=========================
-
-The ideal case would be to dispatch each test to a worker process
-separately. This ideal is not attainable in all cases, however, because many
-test suites depend on context (class, module or package) fixtures.
-
-The plugin can't know (unless you tell it -- see below!) if a context fixture
-can be called many times concurrently (is re-entrant), or if it can be shared
-among tests running in different processes. Therefore, if a context has
-fixtures, the default behavior is to dispatch the entire suite to a worker as
-a unit.
-
-Controlling distribution
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-There are two context-level variables that you can use to control this default
-behavior.
-
-If a context's fixtures are re-entrant, set ``_multiprocess_can_split_ = True``
-in the context, and the plugin will dispatch tests in suites bound to that
-context as if the context had no fixtures. This means that the fixtures will
-execute concurrently and multiple times, typically once per test.
-
-If a context's fixtures can be shared by tests running in different processes
--- such as a package-level fixture that starts an external http server or
-initializes a shared database -- then set ``_multiprocess_shared_ = True`` in
-the context. These fixtures will then execute in the primary nose process, and
-tests in those contexts will be individually dispatched to run in parallel.
-
-How results are collected and reported
-======================================
-
-As each test or suite executes in a worker process, results (failures, errors,
-and specially handled exceptions like SkipTest) are collected in that
-process. When the worker process finishes, it returns results to the main
-nose process. There, any progress output is printed (dots!), and the
-results from the test run are combined into a consolidated result
-set. When results have been received for all dispatched tests, or all
-workers have died, the result summary is output as normal.
-
-Beware!
-=======
-
-Not all test suites will benefit from, or even operate correctly using, this
-plugin. For example, CPU-bound tests will run more slowly if you don't have
-multiple processors. There are also some differences in plugin
-interactions and behaviors due to the way in which tests are dispatched and
-loaded. In general, test loading under this plugin operates as if it were
-always in directed mode instead of discovered mode. For instance, doctests
-in test modules will always be found when using this plugin with the doctest
-plugin.
-
-But the biggest issue you will face is probably concurrency. Unless you
-have kept your tests as religiously pure unit tests, with no side-effects, no
-ordering issues, and no external dependencies, chances are you will experience
-odd, intermittent and unexplainable failures and errors when using this
-plugin. This doesn't necessarily mean the plugin is broken; it may mean that
-your test suite is not safe for concurrency.
-
-New Features in 1.1.0
-=====================
-
-* functions generated by test generators are now added to the worker queue
- making them multi-threaded.
-* fixed timeout functionality, now functions will be terminated with a
- TimedOutException exception when they exceed their execution time. The
- worker processes are not terminated.
-* added ``--process-restartworker`` option to restart workers once they are
- done, this helps control memory usage. Sometimes memory leaks can accumulate
- making long runs very difficult.
-* added global _instantiate_plugins to configure which plugins are started
- on the worker processes.
-
-"""
-
-import logging
-import os
-import sys
-import time
-import traceback
-import unittest
-import pickle
-import signal
-import nose.case
-from nose.core import TextTestRunner
-from nose import failure
-from nose import loader
-from nose.plugins.base import Plugin
-from nose.pyversion import bytes_
-from nose.result import TextTestResult
-from nose.suite import ContextSuite
-from nose.util import test_address
-try:
- # 2.7+
- from unittest.runner import _WritelnDecorator
-except ImportError:
- from unittest import _WritelnDecorator
-from Queue import Empty
-from warnings import warn
-try:
- from cStringIO import StringIO
-except ImportError:
- import StringIO
-
-# this is a list of plugin classes that will be checked for and created inside
-# each worker process
-_instantiate_plugins = None
-
-log = logging.getLogger(__name__)
-
-Process = Queue = Pool = Event = Value = Array = None
-
-# have to inherit KeyboardInterrupt to it will interrupt process properly
-class TimedOutException(KeyboardInterrupt):
- def __init__(self, value = "Timed Out"):
- self.value = value
- def __str__(self):
- return repr(self.value)
-
-def _import_mp():
- global Process, Queue, Pool, Event, Value, Array
- try:
- from multiprocessing import Manager, Process
- #prevent the server process created in the manager which holds Python
- #objects and allows other processes to manipulate them using proxies
- #to interrupt on SIGINT (keyboardinterrupt) so that the communication
- #channel between subprocesses and main process is still usable after
- #ctrl+C is received in the main process.
- old=signal.signal(signal.SIGINT, signal.SIG_IGN)
- m = Manager()
- #reset it back so main process will receive a KeyboardInterrupt
- #exception on ctrl+c
- signal.signal(signal.SIGINT, old)
- Queue, Pool, Event, Value, Array = (
- m.Queue, m.Pool, m.Event, m.Value, m.Array
- )
- except ImportError:
- warn("multiprocessing module is not available, multiprocess plugin "
- "cannot be used", RuntimeWarning)
-
-
-class TestLet:
- def __init__(self, case):
- try:
- self._id = case.id()
- except AttributeError:
- pass
- self._short_description = case.shortDescription()
- self._str = str(case)
-
- def id(self):
- return self._id
-
- def shortDescription(self):
- return self._short_description
-
- def __str__(self):
- return self._str
-
-class MultiProcess(Plugin):
- """
- Run tests in multiple processes. Requires processing module.
- """
- score = 1000
- status = {}
-
- def options(self, parser, env):
- """
- Register command-line options.
- """
- parser.add_option("--processes", action="store",
- default=env.get('NOSE_PROCESSES', 0),
- dest="multiprocess_workers",
- metavar="NUM",
- help="Spread test run among this many processes. "
- "Set a number equal to the number of processors "
- "or cores in your machine for best results. "
- "Pass a negative number to have the number of "
- "processes automatically set to the number of "
- "cores. Passing 0 means to disable parallel "
- "testing. Default is 0 unless NOSE_PROCESSES is "
- "set. "
- "[NOSE_PROCESSES]")
- parser.add_option("--process-timeout", action="store",
- default=env.get('NOSE_PROCESS_TIMEOUT', 10),
- dest="multiprocess_timeout",
- metavar="SECONDS",
- help="Set timeout for return of results from each "
- "test runner process. Default is 10. "
- "[NOSE_PROCESS_TIMEOUT]")
- parser.add_option("--process-restartworker", action="store_true",
- default=env.get('NOSE_PROCESS_RESTARTWORKER', False),
- dest="multiprocess_restartworker",
- help="If set, will restart each worker process once"
- " their tests are done, this helps control memory "
- "leaks from killing the system. "
- "[NOSE_PROCESS_RESTARTWORKER]")
-
- def configure(self, options, config):
- """
- Configure plugin.
- """
- try:
- self.status.pop('active')
- except KeyError:
- pass
- if not hasattr(options, 'multiprocess_workers'):
- self.enabled = False
- return
- # don't start inside of a worker process
- if config.worker:
- return
- self.config = config
- try:
- workers = int(options.multiprocess_workers)
- except (TypeError, ValueError):
- workers = 0
- if workers:
- _import_mp()
- if Process is None:
- self.enabled = False
- return
- # Negative number of workers will cause multiprocessing to hang.
- # Set the number of workers to the CPU count to avoid this.
- if workers < 0:
- try:
- import multiprocessing
- workers = multiprocessing.cpu_count()
- except NotImplementedError:
- self.enabled = False
- return
- self.enabled = True
- self.config.multiprocess_workers = workers
- t = float(options.multiprocess_timeout)
- self.config.multiprocess_timeout = t
- r = int(options.multiprocess_restartworker)
- self.config.multiprocess_restartworker = r
- self.status['active'] = True
-
- def prepareTestLoader(self, loader):
- """Remember loader class so MultiProcessTestRunner can instantiate
- the right loader.
- """
- self.loaderClass = loader.__class__
-
- def prepareTestRunner(self, runner):
- """Replace test runner with MultiProcessTestRunner.
- """
- # replace with our runner class
- return MultiProcessTestRunner(stream=runner.stream,
- verbosity=self.config.verbosity,
- config=self.config,
- loaderClass=self.loaderClass)
-
-def signalhandler(sig, frame):
- raise TimedOutException()
-
-class MultiProcessTestRunner(TextTestRunner):
- waitkilltime = 5.0 # max time to wait to terminate a process that does not
- # respond to SIGILL
- def __init__(self, **kw):
- self.loaderClass = kw.pop('loaderClass', loader.defaultTestLoader)
- super(MultiProcessTestRunner, self).__init__(**kw)
-
- def collect(self, test, testQueue, tasks, to_teardown, result):
- # dispatch and collect results
- # put indexes only on queue because tests aren't picklable
- for case in self.nextBatch(test):
- log.debug("Next batch %s (%s)", case, type(case))
- if (isinstance(case, nose.case.Test) and
- isinstance(case.test, failure.Failure)):
- log.debug("Case is a Failure")
- case(result) # run here to capture the failure
- continue
- # handle shared fixtures
- if isinstance(case, ContextSuite) and case.context is failure.Failure:
- log.debug("Case is a Failure")
- case(result) # run here to capture the failure
- continue
- elif isinstance(case, ContextSuite) and self.sharedFixtures(case):
- log.debug("%s has shared fixtures", case)
- try:
- case.setUp()
- except (KeyboardInterrupt, SystemExit):
- raise
- except:
- log.debug("%s setup failed", sys.exc_info())
- result.addError(case, sys.exc_info())
- else:
- to_teardown.append(case)
- if case.factory:
- ancestors=case.factory.context.get(case, [])
- for an in ancestors[:2]:
- #log.debug('reset ancestor %s', an)
- if getattr(an, '_multiprocess_shared_', False):
- an._multiprocess_can_split_=True
- #an._multiprocess_shared_=False
- self.collect(case, testQueue, tasks, to_teardown, result)
-
- else:
- test_addr = self.addtask(testQueue,tasks,case)
- log.debug("Queued test %s (%s) to %s",
- len(tasks), test_addr, testQueue)
-
- def startProcess(self, iworker, testQueue, resultQueue, shouldStop, result):
- currentaddr = Value('c',bytes_(''))
- currentstart = Value('d',time.time())
- keyboardCaught = Event()
- p = Process(target=runner,
- args=(iworker, testQueue,
- resultQueue,
- currentaddr,
- currentstart,
- keyboardCaught,
- shouldStop,
- self.loaderClass,
- result.__class__,
- pickle.dumps(self.config)))
- p.currentaddr = currentaddr
- p.currentstart = currentstart
- p.keyboardCaught = keyboardCaught
- old = signal.signal(signal.SIGILL, signalhandler)
- p.start()
- signal.signal(signal.SIGILL, old)
- return p
-
- def run(self, test):
- """
- Execute the test (which may be a test suite). If the test is a suite,
- distribute it out among as many processes as have been configured, at
- as fine a level as is possible given the context fixtures defined in
- the suite or any sub-suites.
-
- """
- log.debug("%s.run(%s) (%s)", self, test, os.getpid())
- wrapper = self.config.plugins.prepareTest(test)
- if wrapper is not None:
- test = wrapper
-
- # plugins can decorate or capture the output stream
- wrapped = self.config.plugins.setOutputStream(self.stream)
- if wrapped is not None:
- self.stream = wrapped
-
- testQueue = Queue()
- resultQueue = Queue()
- tasks = []
- completed = []
- workers = []
- to_teardown = []
- shouldStop = Event()
-
- result = self._makeResult()
- start = time.time()
-
- self.collect(test, testQueue, tasks, to_teardown, result)
-
- log.debug("Starting %s workers", self.config.multiprocess_workers)
- for i in range(self.config.multiprocess_workers):
- p = self.startProcess(i, testQueue, resultQueue, shouldStop, result)
- workers.append(p)
- log.debug("Started worker process %s", i+1)
-
- total_tasks = len(tasks)
- # need to keep track of the next time to check for timeouts in case
- # more than one process times out at the same time.
- nexttimeout=self.config.multiprocess_timeout
- thrownError = None
-
- try:
- while tasks:
- log.debug("Waiting for results (%s/%s tasks), next timeout=%.3fs",
- len(completed), total_tasks,nexttimeout)
- try:
- iworker, addr, newtask_addrs, batch_result = resultQueue.get(
- timeout=nexttimeout)
- log.debug('Results received for worker %d, %s, new tasks: %d',
- iworker,addr,len(newtask_addrs))
- try:
- try:
- tasks.remove(addr)
- except ValueError:
- log.warn('worker %s failed to remove from tasks: %s',
- iworker,addr)
- total_tasks += len(newtask_addrs)
- tasks.extend(newtask_addrs)
- except KeyError:
- log.debug("Got result for unknown task? %s", addr)
- log.debug("current: %s",str(list(tasks)[0]))
- else:
- completed.append([addr,batch_result])
- self.consolidate(result, batch_result)
- if (self.config.stopOnError
- and not result.wasSuccessful()):
- # set the stop condition
- shouldStop.set()
- break
- if self.config.multiprocess_restartworker:
- log.debug('joining worker %s',iworker)
- # wait for working, but not that important if worker
- # cannot be joined in fact, for workers that add to
- # testQueue, they will not terminate until all their
- # items are read
- workers[iworker].join(timeout=1)
- if not shouldStop.is_set() and not testQueue.empty():
- log.debug('starting new process on worker %s',iworker)
- workers[iworker] = self.startProcess(iworker, testQueue, resultQueue, shouldStop, result)
- except Empty:
- log.debug("Timed out with %s tasks pending "
- "(empty testQueue=%r): %s",
- len(tasks),testQueue.empty(),str(tasks))
- any_alive = False
- for iworker, w in enumerate(workers):
- if w.is_alive():
- worker_addr = bytes_(w.currentaddr.value,'ascii')
- timeprocessing = time.time() - w.currentstart.value
- if ( len(worker_addr) == 0
- and timeprocessing > self.config.multiprocess_timeout-0.1):
- log.debug('worker %d has finished its work item, '
- 'but is not exiting? do we wait for it?',
- iworker)
- else:
- any_alive = True
- if (len(worker_addr) > 0
- and timeprocessing > self.config.multiprocess_timeout-0.1):
- log.debug('timed out worker %s: %s',
- iworker,worker_addr)
- w.currentaddr.value = bytes_('')
- # If the process is in C++ code, sending a SIGILL
- # might not send a python KeybordInterrupt exception
- # therefore, send multiple signals until an
- # exception is caught. If this takes too long, then
- # terminate the process
- w.keyboardCaught.clear()
- startkilltime = time.time()
- while not w.keyboardCaught.is_set() and w.is_alive():
- if time.time()-startkilltime > self.waitkilltime:
- # have to terminate...
- log.error("terminating worker %s",iworker)
- w.terminate()
- # there is a small probability that the
- # terminated process might send a result,
- # which has to be specially handled or
- # else processes might get orphaned.
- workers[iworker] = w = self.startProcess(iworker, testQueue, resultQueue, shouldStop, result)
- break
- os.kill(w.pid, signal.SIGILL)
- time.sleep(0.1)
- if not any_alive and testQueue.empty():
- log.debug("All workers dead")
- break
- nexttimeout=self.config.multiprocess_timeout
- for w in workers:
- if w.is_alive() and len(w.currentaddr.value) > 0:
- timeprocessing = time.time()-w.currentstart.value
- if timeprocessing <= self.config.multiprocess_timeout:
- nexttimeout = min(nexttimeout,
- self.config.multiprocess_timeout-timeprocessing)
- log.debug("Completed %s tasks (%s remain)", len(completed), len(tasks))
-
- except (KeyboardInterrupt, SystemExit), e:
- log.info('parent received ctrl-c when waiting for test results')
- thrownError = e
- #resultQueue.get(False)
-
- result.addError(test, sys.exc_info())
-
- try:
- for case in to_teardown:
- log.debug("Tearing down shared fixtures for %s", case)
- try:
- case.tearDown()
- except (KeyboardInterrupt, SystemExit):
- raise
- except:
- result.addError(case, sys.exc_info())
-
- stop = time.time()
-
- # first write since can freeze on shutting down processes
- result.printErrors()
- result.printSummary(start, stop)
- self.config.plugins.finalize(result)
-
- if thrownError is None:
- log.debug("Tell all workers to stop")
- for w in workers:
- if w.is_alive():
- testQueue.put('STOP', block=False)
-
- # wait for the workers to end
- for iworker,worker in enumerate(workers):
- if worker.is_alive():
- log.debug('joining worker %s',iworker)
- worker.join()
- if worker.is_alive():
- log.debug('failed to join worker %s',iworker)
- except (KeyboardInterrupt, SystemExit):
- log.info('parent received ctrl-c when shutting down: stop all processes')
- for worker in workers:
- if worker.is_alive():
- worker.terminate()
-
- if thrownError: raise thrownError
- else: raise
-
- return result
-
- def addtask(testQueue,tasks,case):
- arg = None
- if isinstance(case,nose.case.Test) and hasattr(case.test,'arg'):
- # this removes the top level descriptor and allows real function
- # name to be returned
- case.test.descriptor = None
- arg = case.test.arg
- test_addr = MultiProcessTestRunner.address(case)
- testQueue.put((test_addr,arg), block=False)
- if arg is not None:
- test_addr += str(arg)
- if tasks is not None:
- tasks.append(test_addr)
- return test_addr
- addtask = staticmethod(addtask)
-
- def address(case):
- if hasattr(case, 'address'):
- file, mod, call = case.address()
- elif hasattr(case, 'context'):
- file, mod, call = test_address(case.context)
- else:
- raise Exception("Unable to convert %s to address" % case)
- parts = []
- if file is None:
- if mod is None:
- raise Exception("Unaddressable case %s" % case)
- else:
- parts.append(mod)
- else:
- # strip __init__.py(c) from end of file part
- # if present, having it there confuses loader
- dirname, basename = os.path.split(file)
- if basename.startswith('__init__'):
- file = dirname
- parts.append(file)
- if call is not None:
- parts.append(call)
- return ':'.join(map(str, parts))
- address = staticmethod(address)
-
- def nextBatch(self, test):
- # allows tests or suites to mark themselves as not safe
- # for multiprocess execution
- if hasattr(test, 'context'):
- if not getattr(test.context, '_multiprocess_', True):
- return
-
- if ((isinstance(test, ContextSuite)
- and test.hasFixtures(self.checkCanSplit))
- or not getattr(test, 'can_split', True)
- or not isinstance(test, unittest.TestSuite)):
- # regular test case, or a suite with context fixtures
-
- # special case: when run like nosetests path/to/module.py
- # the top-level suite has only one item, and it shares
- # the same context as that item. In that case, we want the
- # item, not the top-level suite
- if isinstance(test, ContextSuite):
- contained = list(test)
- if (len(contained) == 1
- and getattr(contained[0],
- 'context', None) == test.context):
- test = contained[0]
- yield test
- else:
- # Suite is without fixtures at this level; but it may have
- # fixtures at any deeper level, so we need to examine it all
- # the way down to the case level
- for case in test:
- for batch in self.nextBatch(case):
- yield batch
-
- def checkCanSplit(context, fixt):
- """
- Callback that we use to check whether the fixtures found in a
- context or ancestor are ones we care about.
-
- Contexts can tell us that their fixtures are reentrant by setting
- _multiprocess_can_split_. So if we see that, we return False to
- disregard those fixtures.
- """
- if not fixt:
- return False
- if getattr(context, '_multiprocess_can_split_', False):
- return False
- return True
- checkCanSplit = staticmethod(checkCanSplit)
-
- def sharedFixtures(self, case):
- context = getattr(case, 'context', None)
- if not context:
- return False
- return getattr(context, '_multiprocess_shared_', False)
-
- def consolidate(self, result, batch_result):
- log.debug("batch result is %s" , batch_result)
- try:
- output, testsRun, failures, errors, errorClasses = batch_result
- except ValueError:
- log.debug("result in unexpected format %s", batch_result)
- failure.Failure(*sys.exc_info())(result)
- return
- self.stream.write(output)
- result.testsRun += testsRun
- result.failures.extend(failures)
- result.errors.extend(errors)
- for key, (storage, label, isfail) in errorClasses.items():
- if key not in result.errorClasses:
- # Ordinarily storage is result attribute
- # but it's only processed through the errorClasses
- # dict, so it's ok to fake it here
- result.errorClasses[key] = ([], label, isfail)
- mystorage, _junk, _junk = result.errorClasses[key]
- mystorage.extend(storage)
- log.debug("Ran %s tests (total: %s)", testsRun, result.testsRun)
-
-
-def runner(ix, testQueue, resultQueue, currentaddr, currentstart,
- keyboardCaught, shouldStop, loaderClass, resultClass, config):
- try:
- try:
- return __runner(ix, testQueue, resultQueue, currentaddr, currentstart,
- keyboardCaught, shouldStop, loaderClass, resultClass, config)
- except KeyboardInterrupt:
- log.debug('Worker %s keyboard interrupt, stopping',ix)
- except Empty:
- log.debug("Worker %s timed out waiting for tasks", ix)
-
-def __runner(ix, testQueue, resultQueue, currentaddr, currentstart,
- keyboardCaught, shouldStop, loaderClass, resultClass, config):
-
- config = pickle.loads(config)
- dummy_parser = config.parserClass()
- if _instantiate_plugins is not None:
- for pluginclass in _instantiate_plugins:
- plugin = pluginclass()
- plugin.addOptions(dummy_parser,{})
- config.plugins.addPlugin(plugin)
- config.plugins.configure(config.options,config)
- config.plugins.begin()
- log.debug("Worker %s executing, pid=%d", ix,os.getpid())
- loader = loaderClass(config=config)
- loader.suiteClass.suiteClass = NoSharedFixtureContextSuite
-
- def get():
- return testQueue.get(timeout=config.multiprocess_timeout)
-
- def makeResult():
- stream = _WritelnDecorator(StringIO())
- result = resultClass(stream, descriptions=1,
- verbosity=config.verbosity,
- config=config)
- plug_result = config.plugins.prepareTestResult(result)
- if plug_result:
- return plug_result
- return result
-
- def batch(result):
- failures = [(TestLet(c), err) for c, err in result.failures]
- errors = [(TestLet(c), err) for c, err in result.errors]
- errorClasses = {}
- for key, (storage, label, isfail) in result.errorClasses.items():
- errorClasses[key] = ([(TestLet(c), err) for c, err in storage],
- label, isfail)
- return (
- result.stream.getvalue(),
- result.testsRun,
- failures,
- errors,
- errorClasses)
- for test_addr, arg in iter(get, 'STOP'):
- if shouldStop.is_set():
- log.exception('Worker %d STOPPED',ix)
- break
- result = makeResult()
- test = loader.loadTestsFromNames([test_addr])
- test.testQueue = testQueue
- test.tasks = []
- test.arg = arg
- log.debug("Worker %s Test is %s (%s)", ix, test_addr, test)
- try:
- if arg is not None:
- test_addr = test_addr + str(arg)
- currentaddr.value = bytes_(test_addr)
- currentstart.value = time.time()
- test(result)
- currentaddr.value = bytes_('')
- resultQueue.put((ix, test_addr, test.tasks, batch(result)))
- except KeyboardInterrupt, e: #TimedOutException:
- timeout = isinstance(e, TimedOutException)
- if timeout:
- keyboardCaught.set()
- if len(currentaddr.value):
- if timeout:
- msg = 'Worker %s timed out, failing current test %s'
- else:
- msg = 'Worker %s keyboard interrupt, failing current test %s'
- log.exception(msg,ix,test_addr)
- currentaddr.value = bytes_('')
- failure.Failure(*sys.exc_info())(result)
- resultQueue.put((ix, test_addr, test.tasks, batch(result)))
- else:
- if timeout:
- msg = 'Worker %s test %s timed out'
- else:
- msg = 'Worker %s test %s keyboard interrupt'
- log.debug(msg,ix,test_addr)
- resultQueue.put((ix, test_addr, test.tasks, batch(result)))
- if not timeout:
- raise
- except SystemExit:
- currentaddr.value = bytes_('')
- log.exception('Worker %s system exit',ix)
- raise
- except:
- currentaddr.value = bytes_('')
- log.exception("Worker %s error running test or returning "
- "results",ix)
- failure.Failure(*sys.exc_info())(result)
- resultQueue.put((ix, test_addr, test.tasks, batch(result)))
- if config.multiprocess_restartworker:
- break
- log.debug("Worker %s ending", ix)
-
-
-class NoSharedFixtureContextSuite(ContextSuite):
- """
- Context suite that never fires shared fixtures.
-
- When a context sets _multiprocess_shared_, fixtures in that context
- are executed by the main process. Using this suite class prevents them
- from executing in the runner process as well.
-
- """
- testQueue = None
- tasks = None
- arg = None
- def setupContext(self, context):
- if getattr(context, '_multiprocess_shared_', False):
- return
- super(NoSharedFixtureContextSuite, self).setupContext(context)
-
- def teardownContext(self, context):
- if getattr(context, '_multiprocess_shared_', False):
- return
- super(NoSharedFixtureContextSuite, self).teardownContext(context)
- def run(self, result):
- """Run tests in suite inside of suite fixtures.
- """
- # proxy the result for myself
- log.debug("suite %s (%s) run called, tests: %s",
- id(self), self, self._tests)
- if self.resultProxy:
- result, orig = self.resultProxy(result, self), result
- else:
- result, orig = result, result
- try:
- #log.debug('setUp for %s', id(self));
- self.setUp()
- except KeyboardInterrupt:
- raise
- except:
- self.error_context = 'setup'
- result.addError(self, self._exc_info())
- return
- try:
- for test in self._tests:
- if (isinstance(test,nose.case.Test)
- and self.arg is not None):
- test.test.arg = self.arg
- else:
- test.arg = self.arg
- test.testQueue = self.testQueue
- test.tasks = self.tasks
- if result.shouldStop:
- log.debug("stopping")
- break
- # each nose.case.Test will create its own result proxy
- # so the cases need the original result, to avoid proxy
- # chains
- #log.debug('running test %s in suite %s', test, self);
- try:
- test(orig)
- except KeyboardInterrupt, e:
- timeout = isinstance(e, TimedOutException)
- if timeout:
- msg = 'Timeout when running test %s in suite %s'
- else:
- msg = 'KeyboardInterrupt when running test %s in suite %s'
- log.debug(msg, test, self)
- err = (TimedOutException,TimedOutException(str(test)),
- sys.exc_info()[2])
- test.config.plugins.addError(test,err)
- orig.addError(test,err)
- if not timeout:
- raise
- finally:
- self.has_run = True
- try:
- #log.debug('tearDown for %s', id(self));
- self.tearDown()
- except KeyboardInterrupt:
- raise
- except:
- self.error_context = 'teardown'
- result.addError(self, self._exc_info())
diff --git a/lib/spack/external/nose/plugins/plugintest.py b/lib/spack/external/nose/plugins/plugintest.py
deleted file mode 100644
index 76d0d2c48c..0000000000
--- a/lib/spack/external/nose/plugins/plugintest.py
+++ /dev/null
@@ -1,416 +0,0 @@
-"""
-Testing Plugins
-===============
-
-The plugin interface is well-tested enough to safely unit test your
-use of its hooks with some level of confidence. However, there is also
-a mixin for unittest.TestCase called PluginTester that's designed to
-test plugins in their native runtime environment.
-
-Here's a simple example with a do-nothing plugin and a composed suite.
-
- >>> import unittest
- >>> from nose.plugins import Plugin, PluginTester
- >>> class FooPlugin(Plugin):
- ... pass
- >>> class TestPluginFoo(PluginTester, unittest.TestCase):
- ... activate = '--with-foo'
- ... plugins = [FooPlugin()]
- ... def test_foo(self):
- ... for line in self.output:
- ... # i.e. check for patterns
- ... pass
- ...
- ... # or check for a line containing ...
- ... assert "ValueError" in self.output
- ... def makeSuite(self):
- ... class TC(unittest.TestCase):
- ... def runTest(self):
- ... raise ValueError("I hate foo")
- ... return [TC('runTest')]
- ...
- >>> res = unittest.TestResult()
- >>> case = TestPluginFoo('test_foo')
- >>> _ = case(res)
- >>> res.errors
- []
- >>> res.failures
- []
- >>> res.wasSuccessful()
- True
- >>> res.testsRun
- 1
-
-And here is a more complex example of testing a plugin that has extra
-arguments and reads environment variables.
-
- >>> import unittest, os
- >>> from nose.plugins import Plugin, PluginTester
- >>> class FancyOutputter(Plugin):
- ... name = "fancy"
- ... def configure(self, options, conf):
- ... Plugin.configure(self, options, conf)
- ... if not self.enabled:
- ... return
- ... self.fanciness = 1
- ... if options.more_fancy:
- ... self.fanciness = 2
- ... if 'EVEN_FANCIER' in self.env:
- ... self.fanciness = 3
- ...
- ... def options(self, parser, env=os.environ):
- ... self.env = env
- ... parser.add_option('--more-fancy', action='store_true')
- ... Plugin.options(self, parser, env=env)
- ...
- ... def report(self, stream):
- ... stream.write("FANCY " * self.fanciness)
- ...
- >>> class TestFancyOutputter(PluginTester, unittest.TestCase):
- ... activate = '--with-fancy' # enables the plugin
- ... plugins = [FancyOutputter()]
- ... args = ['--more-fancy']
- ... env = {'EVEN_FANCIER': '1'}
- ...
- ... def test_fancy_output(self):
- ... assert "FANCY FANCY FANCY" in self.output, (
- ... "got: %s" % self.output)
- ... def makeSuite(self):
- ... class TC(unittest.TestCase):
- ... def runTest(self):
- ... raise ValueError("I hate fancy stuff")
- ... return [TC('runTest')]
- ...
- >>> res = unittest.TestResult()
- >>> case = TestFancyOutputter('test_fancy_output')
- >>> _ = case(res)
- >>> res.errors
- []
- >>> res.failures
- []
- >>> res.wasSuccessful()
- True
- >>> res.testsRun
- 1
-
-"""
-
-import re
-import sys
-from warnings import warn
-
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-
-__all__ = ['PluginTester', 'run']
-
-from os import getpid
-class MultiProcessFile(object):
- """
- helper for testing multiprocessing
-
- multiprocessing poses a problem for doctests, since the strategy
- of replacing sys.stdout/stderr with file-like objects then
- inspecting the results won't work: the child processes will
- write to the objects, but the data will not be reflected
- in the parent doctest-ing process.
-
- The solution is to create file-like objects which will interact with
- multiprocessing in a more desirable way.
-
- All processes can write to this object, but only the creator can read.
- This allows the testing system to see a unified picture of I/O.
- """
- def __init__(self):
- # per advice at:
- # http://docs.python.org/library/multiprocessing.html#all-platforms
- self.__master = getpid()
- self.__queue = Manager().Queue()
- self.__buffer = StringIO()
- self.softspace = 0
-
- def buffer(self):
- if getpid() != self.__master:
- return
-
- from Queue import Empty
- from collections import defaultdict
- cache = defaultdict(str)
- while True:
- try:
- pid, data = self.__queue.get_nowait()
- except Empty:
- break
- if pid == ():
- #show parent output after children
- #this is what users see, usually
- pid = ( 1e100, ) # googol!
- cache[pid] += data
- for pid in sorted(cache):
- #self.__buffer.write( '%s wrote: %r\n' % (pid, cache[pid]) ) #DEBUG
- self.__buffer.write( cache[pid] )
- def write(self, data):
- # note that these pids are in the form of current_process()._identity
- # rather than OS pids
- from multiprocessing import current_process
- pid = current_process()._identity
- self.__queue.put((pid, data))
- def __iter__(self):
- "getattr doesn't work for iter()"
- self.buffer()
- return self.__buffer
- def seek(self, offset, whence=0):
- self.buffer()
- return self.__buffer.seek(offset, whence)
- def getvalue(self):
- self.buffer()
- return self.__buffer.getvalue()
- def __getattr__(self, attr):
- return getattr(self.__buffer, attr)
-
-try:
- from multiprocessing import Manager
- Buffer = MultiProcessFile
-except ImportError:
- Buffer = StringIO
-
-class PluginTester(object):
- """A mixin for testing nose plugins in their runtime environment.
-
- Subclass this and mix in unittest.TestCase to run integration/functional
- tests on your plugin. When setUp() is called, the stub test suite is
- executed with your plugin so that during an actual test you can inspect the
- artifacts of how your plugin interacted with the stub test suite.
-
- - activate
-
- - the argument to send nosetests to activate the plugin
-
- - suitepath
-
- - if set, this is the path of the suite to test. Otherwise, you
- will need to use the hook, makeSuite()
-
- - plugins
-
- - the list of plugins to make available during the run. Note
- that this does not mean these plugins will be *enabled* during
- the run -- only the plugins enabled by the activate argument
- or other settings in argv or env will be enabled.
-
- - args
-
- - a list of arguments to add to the nosetests command, in addition to
- the activate argument
-
- - env
-
- - optional dict of environment variables to send nosetests
-
- """
- activate = None
- suitepath = None
- args = None
- env = {}
- argv = None
- plugins = []
- ignoreFiles = None
-
- def makeSuite(self):
- """returns a suite object of tests to run (unittest.TestSuite())
-
- If self.suitepath is None, this must be implemented. The returned suite
- object will be executed with all plugins activated. It may return
- None.
-
- Here is an example of a basic suite object you can return ::
-
- >>> import unittest
- >>> class SomeTest(unittest.TestCase):
- ... def runTest(self):
- ... raise ValueError("Now do something, plugin!")
- ...
- >>> unittest.TestSuite([SomeTest()]) # doctest: +ELLIPSIS
- <unittest...TestSuite tests=[<...SomeTest testMethod=runTest>]>
-
- """
- raise NotImplementedError
-
- def _execPlugin(self):
- """execute the plugin on the internal test suite.
- """
- from nose.config import Config
- from nose.core import TestProgram
- from nose.plugins.manager import PluginManager
-
- suite = None
- stream = Buffer()
- conf = Config(env=self.env,
- stream=stream,
- plugins=PluginManager(plugins=self.plugins))
- if self.ignoreFiles is not None:
- conf.ignoreFiles = self.ignoreFiles
- if not self.suitepath:
- suite = self.makeSuite()
-
- self.nose = TestProgram(argv=self.argv, config=conf, suite=suite,
- exit=False)
- self.output = AccessDecorator(stream)
-
- def setUp(self):
- """runs nosetests with the specified test suite, all plugins
- activated.
- """
- self.argv = ['nosetests', self.activate]
- if self.args:
- self.argv.extend(self.args)
- if self.suitepath:
- self.argv.append(self.suitepath)
-
- self._execPlugin()
-
-
-class AccessDecorator(object):
- stream = None
- _buf = None
- def __init__(self, stream):
- self.stream = stream
- stream.seek(0)
- self._buf = stream.read()
- stream.seek(0)
- def __contains__(self, val):
- return val in self._buf
- def __iter__(self):
- return iter(self.stream)
- def __str__(self):
- return self._buf
-
-
-def blankline_separated_blocks(text):
- "a bunch of === characters is also considered a blank line"
- block = []
- for line in text.splitlines(True):
- block.append(line)
- line = line.strip()
- if not line or line.startswith('===') and not line.strip('='):
- yield "".join(block)
- block = []
- if block:
- yield "".join(block)
-
-
-def remove_stack_traces(out):
- # this regexp taken from Python 2.5's doctest
- traceback_re = re.compile(r"""
- # Grab the traceback header. Different versions of Python have
- # said different things on the first traceback line.
- ^(?P<hdr> Traceback\ \(
- (?: most\ recent\ call\ last
- | innermost\ last
- ) \) :
- )
- \s* $ # toss trailing whitespace on the header.
- (?P<stack> .*?) # don't blink: absorb stuff until...
- ^(?=\w) # a line *starts* with alphanum.
- .*?(?P<exception> \w+ ) # exception name
- (?P<msg> [:\n] .*) # the rest
- """, re.VERBOSE | re.MULTILINE | re.DOTALL)
- blocks = []
- for block in blankline_separated_blocks(out):
- blocks.append(traceback_re.sub(r"\g<hdr>\n...\n\g<exception>\g<msg>", block))
- return "".join(blocks)
-
-
-def simplify_warnings(out):
- warn_re = re.compile(r"""
- # Cut the file and line no, up to the warning name
- ^.*:\d+:\s
- (?P<category>\w+): \s+ # warning category
- (?P<detail>.+) $ \n? # warning message
- ^ .* $ # stack frame
- """, re.VERBOSE | re.MULTILINE)
- return warn_re.sub(r"\g<category>: \g<detail>", out)
-
-
-def remove_timings(out):
- return re.sub(
- r"Ran (\d+ tests?) in [0-9.]+s", r"Ran \1 in ...s", out)
-
-
-def munge_nose_output_for_doctest(out):
- """Modify nose output to make it easy to use in doctests."""
- out = remove_stack_traces(out)
- out = simplify_warnings(out)
- out = remove_timings(out)
- return out.strip()
-
-
-def run(*arg, **kw):
- """
- Specialized version of nose.run for use inside of doctests that
- test test runs.
-
- This version of run() prints the result output to stdout. Before
- printing, the output is processed by replacing the timing
- information with an ellipsis (...), removing traceback stacks, and
- removing trailing whitespace.
-
- Use this version of run wherever you are writing a doctest that
- tests nose (or unittest) test result output.
-
- Note: do not use doctest: +ELLIPSIS when testing nose output,
- since ellipses ("test_foo ... ok") in your expected test runner
- output may match multiple lines of output, causing spurious test
- passes!
- """
- from nose import run
- from nose.config import Config
- from nose.plugins.manager import PluginManager
-
- buffer = Buffer()
- if 'config' not in kw:
- plugins = kw.pop('plugins', [])
- if isinstance(plugins, list):
- plugins = PluginManager(plugins=plugins)
- env = kw.pop('env', {})
- kw['config'] = Config(env=env, plugins=plugins)
- if 'argv' not in kw:
- kw['argv'] = ['nosetests', '-v']
- kw['config'].stream = buffer
-
- # Set up buffering so that all output goes to our buffer,
- # or warn user if deprecated behavior is active. If this is not
- # done, prints and warnings will either be out of place or
- # disappear.
- stderr = sys.stderr
- stdout = sys.stdout
- if kw.pop('buffer_all', False):
- sys.stdout = sys.stderr = buffer
- restore = True
- else:
- restore = False
- warn("The behavior of nose.plugins.plugintest.run() will change in "
- "the next release of nose. The current behavior does not "
- "correctly account for output to stdout and stderr. To enable "
- "correct behavior, use run_buffered() instead, or pass "
- "the keyword argument buffer_all=True to run().",
- DeprecationWarning, stacklevel=2)
- try:
- run(*arg, **kw)
- finally:
- if restore:
- sys.stderr = stderr
- sys.stdout = stdout
- out = buffer.getvalue()
- print munge_nose_output_for_doctest(out)
-
-
-def run_buffered(*arg, **kw):
- kw['buffer_all'] = True
- run(*arg, **kw)
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
diff --git a/lib/spack/external/nose/plugins/prof.py b/lib/spack/external/nose/plugins/prof.py
deleted file mode 100644
index 4d304a934b..0000000000
--- a/lib/spack/external/nose/plugins/prof.py
+++ /dev/null
@@ -1,154 +0,0 @@
-"""This plugin will run tests using the hotshot profiler, which is part
-of the standard library. To turn it on, use the ``--with-profile`` option
-or set the NOSE_WITH_PROFILE environment variable. Profiler output can be
-controlled with the ``--profile-sort`` and ``--profile-restrict`` options,
-and the profiler output file may be changed with ``--profile-stats-file``.
-
-See the `hotshot documentation`_ in the standard library documentation for
-more details on the various output options.
-
-.. _hotshot documentation: http://docs.python.org/library/hotshot.html
-"""
-
-try:
- import hotshot
- from hotshot import stats
-except ImportError:
- hotshot, stats = None, None
-import logging
-import os
-import sys
-import tempfile
-from nose.plugins.base import Plugin
-from nose.util import tolist
-
-log = logging.getLogger('nose.plugins')
-
-class Profile(Plugin):
- """
- Use this plugin to run tests using the hotshot profiler.
- """
- pfile = None
- clean_stats_file = False
- def options(self, parser, env):
- """Register commandline options.
- """
- if not self.available():
- return
- Plugin.options(self, parser, env)
- parser.add_option('--profile-sort', action='store', dest='profile_sort',
- default=env.get('NOSE_PROFILE_SORT', 'cumulative'),
- metavar="SORT",
- help="Set sort order for profiler output")
- parser.add_option('--profile-stats-file', action='store',
- dest='profile_stats_file',
- metavar="FILE",
- default=env.get('NOSE_PROFILE_STATS_FILE'),
- help='Profiler stats file; default is a new '
- 'temp file on each run')
- parser.add_option('--profile-restrict', action='append',
- dest='profile_restrict',
- metavar="RESTRICT",
- default=env.get('NOSE_PROFILE_RESTRICT'),
- help="Restrict profiler output. See help for "
- "pstats.Stats for details")
-
- def available(cls):
- return hotshot is not None
- available = classmethod(available)
-
- def begin(self):
- """Create profile stats file and load profiler.
- """
- if not self.available():
- return
- self._create_pfile()
- self.prof = hotshot.Profile(self.pfile)
-
- def configure(self, options, conf):
- """Configure plugin.
- """
- if not self.available():
- self.enabled = False
- return
- Plugin.configure(self, options, conf)
- self.conf = conf
- if options.profile_stats_file:
- self.pfile = options.profile_stats_file
- self.clean_stats_file = False
- else:
- self.pfile = None
- self.clean_stats_file = True
- self.fileno = None
- self.sort = options.profile_sort
- self.restrict = tolist(options.profile_restrict)
-
- def prepareTest(self, test):
- """Wrap entire test run in :func:`prof.runcall`.
- """
- if not self.available():
- return
- log.debug('preparing test %s' % test)
- def run_and_profile(result, prof=self.prof, test=test):
- self._create_pfile()
- prof.runcall(test, result)
- return run_and_profile
-
- def report(self, stream):
- """Output profiler report.
- """
- log.debug('printing profiler report')
- self.prof.close()
- prof_stats = stats.load(self.pfile)
- prof_stats.sort_stats(self.sort)
-
- # 2.5 has completely different stream handling from 2.4 and earlier.
- # Before 2.5, stats objects have no stream attribute; in 2.5 and later
- # a reference sys.stdout is stored before we can tweak it.
- compat_25 = hasattr(prof_stats, 'stream')
- if compat_25:
- tmp = prof_stats.stream
- prof_stats.stream = stream
- else:
- tmp = sys.stdout
- sys.stdout = stream
- try:
- if self.restrict:
- log.debug('setting profiler restriction to %s', self.restrict)
- prof_stats.print_stats(*self.restrict)
- else:
- prof_stats.print_stats()
- finally:
- if compat_25:
- prof_stats.stream = tmp
- else:
- sys.stdout = tmp
-
- def finalize(self, result):
- """Clean up stats file, if configured to do so.
- """
- if not self.available():
- return
- try:
- self.prof.close()
- except AttributeError:
- # TODO: is this trying to catch just the case where not
- # hasattr(self.prof, "close")? If so, the function call should be
- # moved out of the try: suite.
- pass
- if self.clean_stats_file:
- if self.fileno:
- try:
- os.close(self.fileno)
- except OSError:
- pass
- try:
- os.unlink(self.pfile)
- except OSError:
- pass
- return None
-
- def _create_pfile(self):
- if not self.pfile:
- self.fileno, self.pfile = tempfile.mkstemp()
- self.clean_stats_file = True
diff --git a/lib/spack/external/nose/plugins/skip.py b/lib/spack/external/nose/plugins/skip.py
deleted file mode 100644
index 9d1ac8f604..0000000000
--- a/lib/spack/external/nose/plugins/skip.py
+++ /dev/null
@@ -1,63 +0,0 @@
-"""
-This plugin installs a SKIP error class for the SkipTest exception.
-When SkipTest is raised, the exception will be logged in the skipped
-attribute of the result, 'S' or 'SKIP' (verbose) will be output, and
-the exception will not be counted as an error or failure. This plugin
-is enabled by default but may be disabled with the ``--no-skip`` option.
-"""
-
-from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin
-
-
-# on SkipTest:
-# - unittest SkipTest is first preference, but it's only available
-# for >= 2.7
-# - unittest2 SkipTest is second preference for older pythons. This
-# mirrors logic for choosing SkipTest exception in testtools
-# - if none of the above, provide custom class
-try:
- from unittest.case import SkipTest
-except ImportError:
- try:
- from unittest2.case import SkipTest
- except ImportError:
- class SkipTest(Exception):
- """Raise this exception to mark a test as skipped.
- """
- pass
-
-
-class Skip(ErrorClassPlugin):
- """
- Plugin that installs a SKIP error class for the SkipTest
- exception. When SkipTest is raised, the exception will be logged
- in the skipped attribute of the result, 'S' or 'SKIP' (verbose)
- will be output, and the exception will not be counted as an error
- or failure.
- """
- enabled = True
- skipped = ErrorClass(SkipTest,
- label='SKIP',
- isfailure=False)
-
- def options(self, parser, env):
- """
- Add my options to command line.
- """
- env_opt = 'NOSE_WITHOUT_SKIP'
- parser.add_option('--no-skip', action='store_true',
- dest='noSkip', default=env.get(env_opt, False),
- help="Disable special handling of SkipTest "
- "exceptions.")
-
- def configure(self, options, conf):
- """
- Configure plugin. Skip plugin is enabled by default.
- """
- if not self.can_configure:
- return
- self.conf = conf
- disable = getattr(options, 'noSkip', False)
- if disable:
- self.enabled = False
-
diff --git a/lib/spack/external/nose/plugins/testid.py b/lib/spack/external/nose/plugins/testid.py
deleted file mode 100644
index ae8119bd01..0000000000
--- a/lib/spack/external/nose/plugins/testid.py
+++ /dev/null
@@ -1,311 +0,0 @@
-"""
-This plugin adds a test id (like #1) to each test name output. After
-you've run once to generate test ids, you can re-run individual
-tests by activating the plugin and passing the ids (with or
-without the # prefix) instead of test names.
-
-For example, if your normal test run looks like::
-
- % nosetests -v
- tests.test_a ... ok
- tests.test_b ... ok
- tests.test_c ... ok
-
-When adding ``--with-id`` you'll see::
-
- % nosetests -v --with-id
- #1 tests.test_a ... ok
- #2 tests.test_b ... ok
- #3 tests.test_c ... ok
-
-Then you can re-run individual tests by supplying just an id number::
-
- % nosetests -v --with-id 2
- #2 tests.test_b ... ok
-
-You can also pass multiple id numbers::
-
- % nosetests -v --with-id 2 3
- #2 tests.test_b ... ok
- #3 tests.test_c ... ok
-
-Since most shells consider '#' a special character, you can leave it out when
-specifying a test id.
-
-Note that when run without the -v switch, no special output is displayed, but
-the ids file is still written.
-
-Looping over failed tests
--------------------------
-
-This plugin also adds a mode that will direct the test runner to record
-failed tests. Subsequent test runs will then run only the tests that failed
-last time. Activate this mode with the ``--failed`` switch::
-
- % nosetests -v --failed
- #1 test.test_a ... ok
- #2 test.test_b ... ERROR
- #3 test.test_c ... FAILED
- #4 test.test_d ... ok
-
-On the second run, only tests #2 and #3 will run::
-
- % nosetests -v --failed
- #2 test.test_b ... ERROR
- #3 test.test_c ... FAILED
-
-As you correct errors and tests pass, they'll drop out of subsequent runs.
-
-First::
-
- % nosetests -v --failed
- #2 test.test_b ... ok
- #3 test.test_c ... FAILED
-
-Second::
-
- % nosetests -v --failed
- #3 test.test_c ... FAILED
-
-When all tests pass, the full set will run on the next invocation.
-
-First::
-
- % nosetests -v --failed
- #3 test.test_c ... ok
-
-Second::
-
- % nosetests -v --failed
- #1 test.test_a ... ok
- #2 test.test_b ... ok
- #3 test.test_c ... ok
- #4 test.test_d ... ok
-
-.. note ::
-
- If you expect to use ``--failed`` regularly, it's a good idea to always run
- using the ``--with-id`` option. This will ensure that an id file is always
- created, allowing you to add ``--failed`` to the command line as soon as
- you have failing tests. Otherwise, your first run using ``--failed`` will
- (perhaps surprisingly) run *all* tests, because there won't be an id file
- containing the record of failed tests from your previous run.
-
-"""
-__test__ = False
-
-import logging
-import os
-from nose.plugins import Plugin
-from nose.util import src, set
-
-try:
- from cPickle import dump, load
-except ImportError:
- from pickle import dump, load
-
-log = logging.getLogger(__name__)
-
-
-class TestId(Plugin):
- """
- Activate to add a test id (like #1) to each test name output. Activate
- with --failed to rerun failing tests only.
- """
- name = 'id'
- idfile = None
- collecting = True
- loopOnFailed = False
-
- def options(self, parser, env):
- """Register commandline options.
- """
- Plugin.options(self, parser, env)
- parser.add_option('--id-file', action='store', dest='testIdFile',
- default='.noseids', metavar="FILE",
- help="Store test ids found in test runs in this "
- "file. Default is the file .noseids in the "
- "working directory.")
- parser.add_option('--failed', action='store_true',
- dest='failed', default=False,
- help="Run the tests that failed in the last "
- "test run.")
-
- def configure(self, options, conf):
- """Configure plugin.
- """
- Plugin.configure(self, options, conf)
- if options.failed:
- self.enabled = True
- self.loopOnFailed = True
- log.debug("Looping on failed tests")
- self.idfile = os.path.expanduser(options.testIdFile)
- if not os.path.isabs(self.idfile):
- self.idfile = os.path.join(conf.workingDir, self.idfile)
- self.id = 1
- # Ids and tests are mirror images: ids are {id: test address} and
- # tests are {test address: id}
- self.ids = {}
- self.tests = {}
- self.failed = []
- self.source_names = []
- # used to track ids seen when tests is filled from
- # loaded ids file
- self._seen = {}
- self._write_hashes = conf.verbosity >= 2
-
- def finalize(self, result):
- """Save new ids file, if needed.
- """
- if result.wasSuccessful():
- self.failed = []
- if self.collecting:
- ids = dict(list(zip(list(self.tests.values()), list(self.tests.keys()))))
- else:
- ids = self.ids
- fh = open(self.idfile, 'wb')
- dump({'ids': ids,
- 'failed': self.failed,
- 'source_names': self.source_names}, fh)
- fh.close()
- log.debug('Saved test ids: %s, failed %s to %s',
- ids, self.failed, self.idfile)
-
- def loadTestsFromNames(self, names, module=None):
- """Translate ids in the list of requested names into their
- test addresses, if they are found in my dict of tests.
- """
- log.debug('ltfn %s %s', names, module)
- try:
- fh = open(self.idfile, 'rb')
- data = load(fh)
- if 'ids' in data:
- self.ids = data['ids']
- self.failed = data['failed']
- self.source_names = data['source_names']
- else:
- # old ids field
- self.ids = data
- self.failed = []
- self.source_names = names
- if self.ids:
- self.id = max(self.ids) + 1
- self.tests = dict(list(zip(list(self.ids.values()), list(self.ids.keys()))))
- else:
- self.id = 1
- log.debug(
- 'Loaded test ids %s tests %s failed %s sources %s from %s',
- self.ids, self.tests, self.failed, self.source_names,
- self.idfile)
- fh.close()
- except ValueError, e:
- # load() may throw a ValueError when reading the ids file, if it
- # was generated with a newer version of Python than we are currently
- # running.
- log.debug('Error loading %s : %s', self.idfile, str(e))
- except IOError:
- log.debug('IO error reading %s', self.idfile)
-
- if self.loopOnFailed and self.failed:
- self.collecting = False
- names = self.failed
- self.failed = []
- # I don't load any tests myself, only translate names like '#2'
- # into the associated test addresses
- translated = []
- new_source = []
- really_new = []
- for name in names:
- trans = self.tr(name)
- if trans != name:
- translated.append(trans)
- else:
- new_source.append(name)
- # names that are not ids and that are not in the current
- # list of source names go into the list for next time
- if new_source:
- new_set = set(new_source)
- old_set = set(self.source_names)
- log.debug("old: %s new: %s", old_set, new_set)
- really_new = [s for s in new_source
- if not s in old_set]
- if really_new:
- # remember new sources
- self.source_names.extend(really_new)
- if not translated:
- # new set of source names, no translations
- # means "run the requested tests"
- names = new_source
- else:
- # no new names to translate and add to id set
- self.collecting = False
- log.debug("translated: %s new sources %s names %s",
- translated, really_new, names)
- return (None, translated + really_new or names)
-
- def makeName(self, addr):
- log.debug("Make name %s", addr)
- filename, module, call = addr
- if filename is not None:
- head = src(filename)
- else:
- head = module
- if call is not None:
- return "%s:%s" % (head, call)
- return head
-
- def setOutputStream(self, stream):
- """Get handle on output stream so the plugin can print id #s
- """
- self.stream = stream
-
- def startTest(self, test):
- """Maybe output an id # before the test name.
-
- Example output::
-
- #1 test.test ... ok
- #2 test.test_two ... ok
-
- """
- adr = test.address()
- log.debug('start test %s (%s)', adr, adr in self.tests)
- if adr in self.tests:
- if adr in self._seen:
- self.write(' ')
- else:
- self.write('#%s ' % self.tests[adr])
- self._seen[adr] = 1
- return
- self.tests[adr] = self.id
- self.write('#%s ' % self.id)
- self.id += 1
-
- def afterTest(self, test):
- # None means test never ran, False means failed/err
- if test.passed is False:
- try:
- key = str(self.tests[test.address()])
- except KeyError:
- # never saw this test -- startTest didn't run
- pass
- else:
- if key not in self.failed:
- self.failed.append(key)
-
- def tr(self, name):
- log.debug("tr '%s'", name)
- try:
- key = int(name.replace('#', ''))
- except ValueError:
- return name
- log.debug("Got key %s", key)
- # I'm running tests mapped from the ids file,
- # not collecting new ones
- if key in self.ids:
- return self.makeName(self.ids[key])
- return name
-
- def write(self, output):
- if self._write_hashes:
- self.stream.write(output)
diff --git a/lib/spack/external/nose/plugins/xunit.py b/lib/spack/external/nose/plugins/xunit.py
deleted file mode 100644
index 90b52f5f61..0000000000
--- a/lib/spack/external/nose/plugins/xunit.py
+++ /dev/null
@@ -1,341 +0,0 @@
-"""This plugin provides test results in the standard XUnit XML format.
-
-It's designed for the `Jenkins`_ (previously Hudson) continuous build
-system, but will probably work for anything else that understands an
-XUnit-formatted XML representation of test results.
-
-Add this shell command to your builder ::
-
- nosetests --with-xunit
-
-And by default a file named nosetests.xml will be written to the
-working directory.
-
-In a Jenkins builder, tick the box named "Publish JUnit test result report"
-under the Post-build Actions and enter this value for Test report XMLs::
-
- **/nosetests.xml
-
-If you need to change the name or location of the file, you can set the
-``--xunit-file`` option.
-
-If you need to change the name of the test suite, you can set the
-``--xunit-testsuite-name`` option.
-
-Here is an abbreviated version of what an XML test report might look like::
-
- <?xml version="1.0" encoding="UTF-8"?>
- <testsuite name="nosetests" tests="1" errors="1" failures="0" skip="0">
- <testcase classname="path_to_test_suite.TestSomething"
- name="test_it" time="0">
- <error type="exceptions.TypeError" message="oops, wrong type">
- Traceback (most recent call last):
- ...
- TypeError: oops, wrong type
- </error>
- </testcase>
- </testsuite>
-
-.. _Jenkins: http://jenkins-ci.org/
-
-"""
-import codecs
-import doctest
-import os
-import sys
-import traceback
-import re
-import inspect
-from StringIO import StringIO
-from time import time
-from xml.sax import saxutils
-
-from nose.plugins.base import Plugin
-from nose.exc import SkipTest
-from nose.pyversion import force_unicode, format_exception
-
-# Invalid XML characters, control characters 0-31 sans \t, \n and \r
-CONTROL_CHARACTERS = re.compile(r"[\000-\010\013\014\016-\037]")
-
-TEST_ID = re.compile(r'^(.*?)(\(.*\))$')
-
-def xml_safe(value):
- """Replaces invalid XML characters with '?'."""
- return CONTROL_CHARACTERS.sub('?', value)
-
-def escape_cdata(cdata):
- """Escape a string for an XML CDATA section."""
- return xml_safe(cdata).replace(']]>', ']]>]]&gt;<![CDATA[')
-
-def id_split(idval):
- m = TEST_ID.match(idval)
- if m:
- name, fargs = m.groups()
- head, tail = name.rsplit(".", 1)
- return [head, tail+fargs]
- else:
- return idval.rsplit(".", 1)
-
-def nice_classname(obj):
- """Returns a nice name for class object or class instance.
-
- >>> nice_classname(Exception()) # doctest: +ELLIPSIS
- '...Exception'
- >>> nice_classname(Exception) # doctest: +ELLIPSIS
- '...Exception'
-
- """
- if inspect.isclass(obj):
- cls_name = obj.__name__
- else:
- cls_name = obj.__class__.__name__
- mod = inspect.getmodule(obj)
- if mod:
- name = mod.__name__
- # jython
- if name.startswith('org.python.core.'):
- name = name[len('org.python.core.'):]
- return "%s.%s" % (name, cls_name)
- else:
- return cls_name
-
-def exc_message(exc_info):
- """Return the exception's message."""
- exc = exc_info[1]
- if exc is None:
- # str exception
- result = exc_info[0]
- else:
- try:
- result = str(exc)
- except UnicodeEncodeError:
- try:
- result = unicode(exc)
- except UnicodeError:
- # Fallback to args as neither str nor
- # unicode(Exception(u'\xe6')) work in Python < 2.6
- result = exc.args[0]
- result = force_unicode(result, 'UTF-8')
- return xml_safe(result)
-
-class Tee(object):
- def __init__(self, encoding, *args):
- self._encoding = encoding
- self._streams = args
-
- def write(self, data):
- data = force_unicode(data, self._encoding)
- for s in self._streams:
- s.write(data)
-
- def writelines(self, lines):
- for line in lines:
- self.write(line)
-
- def flush(self):
- for s in self._streams:
- s.flush()
-
- def isatty(self):
- return False
-
-
-class Xunit(Plugin):
- """This plugin provides test results in the standard XUnit XML format."""
- name = 'xunit'
- score = 1500
- encoding = 'UTF-8'
- error_report_file = None
-
- def __init__(self):
- super(Xunit, self).__init__()
- self._capture_stack = []
- self._currentStdout = None
- self._currentStderr = None
-
- def _timeTaken(self):
- if hasattr(self, '_timer'):
- taken = time() - self._timer
- else:
- # test died before it ran (probably error in setup())
- # or success/failure added before test started probably
- # due to custom TestResult munging
- taken = 0.0
- return taken
-
- def _quoteattr(self, attr):
- """Escape an XML attribute. Value can be unicode."""
- attr = xml_safe(attr)
- return saxutils.quoteattr(attr)
-
- def options(self, parser, env):
- """Sets additional command line options."""
- Plugin.options(self, parser, env)
- parser.add_option(
- '--xunit-file', action='store',
- dest='xunit_file', metavar="FILE",
- default=env.get('NOSE_XUNIT_FILE', 'nosetests.xml'),
- help=("Path to xml file to store the xunit report in. "
- "Default is nosetests.xml in the working directory "
- "[NOSE_XUNIT_FILE]"))
-
- parser.add_option(
- '--xunit-testsuite-name', action='store',
- dest='xunit_testsuite_name', metavar="PACKAGE",
- default=env.get('NOSE_XUNIT_TESTSUITE_NAME', 'nosetests'),
- help=("Name of the testsuite in the xunit xml, generated by plugin. "
- "Default test suite name is nosetests."))
-
- def configure(self, options, config):
- """Configures the xunit plugin."""
- Plugin.configure(self, options, config)
- self.config = config
- if self.enabled:
- self.stats = {'errors': 0,
- 'failures': 0,
- 'passes': 0,
- 'skipped': 0
- }
- self.errorlist = []
- self.error_report_file_name = os.path.realpath(options.xunit_file)
- self.xunit_testsuite_name = options.xunit_testsuite_name
-
- def report(self, stream):
- """Writes an Xunit-formatted XML file
-
- The file includes a report of test errors and failures.
-
- """
- self.error_report_file = codecs.open(self.error_report_file_name, 'w',
- self.encoding, 'replace')
- self.stats['encoding'] = self.encoding
- self.stats['testsuite_name'] = self.xunit_testsuite_name
- self.stats['total'] = (self.stats['errors'] + self.stats['failures']
- + self.stats['passes'] + self.stats['skipped'])
- self.error_report_file.write(
- u'<?xml version="1.0" encoding="%(encoding)s"?>'
- u'<testsuite name="%(testsuite_name)s" tests="%(total)d" '
- u'errors="%(errors)d" failures="%(failures)d" '
- u'skip="%(skipped)d">' % self.stats)
- self.error_report_file.write(u''.join([force_unicode(e, self.encoding)
- for e in self.errorlist]))
- self.error_report_file.write(u'</testsuite>')
- self.error_report_file.close()
- if self.config.verbosity > 1:
- stream.writeln("-" * 70)
- stream.writeln("XML: %s" % self.error_report_file.name)
-
- def _startCapture(self):
- self._capture_stack.append((sys.stdout, sys.stderr))
- self._currentStdout = StringIO()
- self._currentStderr = StringIO()
- sys.stdout = Tee(self.encoding, self._currentStdout, sys.stdout)
- sys.stderr = Tee(self.encoding, self._currentStderr, sys.stderr)
-
- def startContext(self, context):
- self._startCapture()
-
- def stopContext(self, context):
- self._endCapture()
-
- def beforeTest(self, test):
- """Initializes a timer before starting a test."""
- self._timer = time()
- self._startCapture()
-
- def _endCapture(self):
- if self._capture_stack:
- sys.stdout, sys.stderr = self._capture_stack.pop()
-
- def afterTest(self, test):
- self._endCapture()
- self._currentStdout = None
- self._currentStderr = None
-
- def finalize(self, test):
- while self._capture_stack:
- self._endCapture()
-
- def _getCapturedStdout(self):
- if self._currentStdout:
- value = self._currentStdout.getvalue()
- if value:
- return '<system-out><![CDATA[%s]]></system-out>' % escape_cdata(
- value)
- return ''
-
- def _getCapturedStderr(self):
- if self._currentStderr:
- value = self._currentStderr.getvalue()
- if value:
- return '<system-err><![CDATA[%s]]></system-err>' % escape_cdata(
- value)
- return ''
-
- def addError(self, test, err, capt=None):
- """Add error output to Xunit report.
- """
- taken = self._timeTaken()
-
- if issubclass(err[0], SkipTest):
- type = 'skipped'
- self.stats['skipped'] += 1
- else:
- type = 'error'
- self.stats['errors'] += 1
-
- tb = format_exception(err, self.encoding)
- id = test.id()
-
- self.errorlist.append(
- u'<testcase classname=%(cls)s name=%(name)s time="%(taken).3f">'
- u'<%(type)s type=%(errtype)s message=%(message)s><![CDATA[%(tb)s]]>'
- u'</%(type)s>%(systemout)s%(systemerr)s</testcase>' %
- {'cls': self._quoteattr(id_split(id)[0]),
- 'name': self._quoteattr(id_split(id)[-1]),
- 'taken': taken,
- 'type': type,
- 'errtype': self._quoteattr(nice_classname(err[0])),
- 'message': self._quoteattr(exc_message(err)),
- 'tb': escape_cdata(tb),
- 'systemout': self._getCapturedStdout(),
- 'systemerr': self._getCapturedStderr(),
- })
-
- def addFailure(self, test, err, capt=None, tb_info=None):
- """Add failure output to Xunit report.
- """
- taken = self._timeTaken()
- tb = format_exception(err, self.encoding)
- self.stats['failures'] += 1
- id = test.id()
-
- self.errorlist.append(
- u'<testcase classname=%(cls)s name=%(name)s time="%(taken).3f">'
- u'<failure type=%(errtype)s message=%(message)s><![CDATA[%(tb)s]]>'
- u'</failure>%(systemout)s%(systemerr)s</testcase>' %
- {'cls': self._quoteattr(id_split(id)[0]),
- 'name': self._quoteattr(id_split(id)[-1]),
- 'taken': taken,
- 'errtype': self._quoteattr(nice_classname(err[0])),
- 'message': self._quoteattr(exc_message(err)),
- 'tb': escape_cdata(tb),
- 'systemout': self._getCapturedStdout(),
- 'systemerr': self._getCapturedStderr(),
- })
-
- def addSuccess(self, test, capt=None):
- """Add success output to Xunit report.
- """
- taken = self._timeTaken()
- self.stats['passes'] += 1
- id = test.id()
- self.errorlist.append(
- '<testcase classname=%(cls)s name=%(name)s '
- 'time="%(taken).3f">%(systemout)s%(systemerr)s</testcase>' %
- {'cls': self._quoteattr(id_split(id)[0]),
- 'name': self._quoteattr(id_split(id)[-1]),
- 'taken': taken,
- 'systemout': self._getCapturedStdout(),
- 'systemerr': self._getCapturedStderr(),
- })
diff --git a/lib/spack/external/nose/proxy.py b/lib/spack/external/nose/proxy.py
deleted file mode 100644
index c2676cb195..0000000000
--- a/lib/spack/external/nose/proxy.py
+++ /dev/null
@@ -1,188 +0,0 @@
-"""
-Result Proxy
-------------
-
-The result proxy wraps the result instance given to each test. It
-performs two functions: enabling extended error/failure reporting
-and calling plugins.
-
-As each result event is fired, plugins are called with the same event;
-however, plugins are called with the nose.case.Test instance that
-wraps the actual test. So when a test fails and calls
-result.addFailure(self, err), the result proxy calls
-addFailure(self.test, err) for each plugin. This allows plugins to
-have a single stable interface for all test types, and also to
-manipulate the test object itself by setting the `test` attribute of
-the nose.case.Test that they receive.
-"""
-import logging
-from nose.config import Config
-
-
-log = logging.getLogger(__name__)
-
-
-def proxied_attribute(local_attr, proxied_attr, doc):
- """Create a property that proxies attribute ``proxied_attr`` through
- the local attribute ``local_attr``.
- """
- def fget(self):
- return getattr(getattr(self, local_attr), proxied_attr)
- def fset(self, value):
- setattr(getattr(self, local_attr), proxied_attr, value)
- def fdel(self):
- delattr(getattr(self, local_attr), proxied_attr)
- return property(fget, fset, fdel, doc)
-
-
-class ResultProxyFactory(object):
- """Factory for result proxies. Generates a ResultProxy bound to each test
- and the result passed to the test.
- """
- def __init__(self, config=None):
- if config is None:
- config = Config()
- self.config = config
- self.__prepared = False
- self.__result = None
-
- def __call__(self, result, test):
- """Return a ResultProxy for the current test.
-
- On first call, plugins are given a chance to replace the
- result used for the remaining tests. If a plugin returns a
- value from prepareTestResult, that object will be used as the
- result for all tests.
- """
- if not self.__prepared:
- self.__prepared = True
- plug_result = self.config.plugins.prepareTestResult(result)
- if plug_result is not None:
- self.__result = result = plug_result
- if self.__result is not None:
- result = self.__result
- return ResultProxy(result, test, config=self.config)
-
-
-class ResultProxy(object):
- """Proxy to TestResults (or other results handler).
-
- One ResultProxy is created for each nose.case.Test. The result
- proxy calls plugins with the nose.case.Test instance (instead of
- the wrapped test case) as each result call is made. Finally, the
- real result method is called, also with the nose.case.Test
- instance as the test parameter.
-
- """
- def __init__(self, result, test, config=None):
- if config is None:
- config = Config()
- self.config = config
- self.plugins = config.plugins
- self.result = result
- self.test = test
-
- def __repr__(self):
- return repr(self.result)
-
- def _prepareErr(self, err):
- if not isinstance(err[1], Exception) and isinstance(err[0], type):
- # Turn value back into an Exception (required in Python 3.x).
- # Plugins do all sorts of crazy things with exception values.
- # Convert it to a custom subclass of Exception with the same
- # name as the actual exception to make it print correctly.
- value = type(err[0].__name__, (Exception,), {})(err[1])
- err = (err[0], value, err[2])
- return err
-
- def assertMyTest(self, test):
- # The test I was called with must be my .test or my
- # .test's .test. or my .test.test's .case
-
- case = getattr(self.test, 'test', None)
- assert (test is self.test
- or test is case
- or test is getattr(case, '_nose_case', None)), (
- "ResultProxy for %r (%s) was called with test %r (%s)"
- % (self.test, id(self.test), test, id(test)))
-
- def afterTest(self, test):
- self.assertMyTest(test)
- self.plugins.afterTest(self.test)
- if hasattr(self.result, "afterTest"):
- self.result.afterTest(self.test)
-
- def beforeTest(self, test):
- self.assertMyTest(test)
- self.plugins.beforeTest(self.test)
- if hasattr(self.result, "beforeTest"):
- self.result.beforeTest(self.test)
-
- def addError(self, test, err):
- self.assertMyTest(test)
- plugins = self.plugins
- plugin_handled = plugins.handleError(self.test, err)
- if plugin_handled:
- return
- # test.passed is set in result, to account for error classes
- formatted = plugins.formatError(self.test, err)
- if formatted is not None:
- err = formatted
- plugins.addError(self.test, err)
- self.result.addError(self.test, self._prepareErr(err))
- if not self.result.wasSuccessful() and self.config.stopOnError:
- self.shouldStop = True
-
- def addFailure(self, test, err):
- self.assertMyTest(test)
- plugins = self.plugins
- plugin_handled = plugins.handleFailure(self.test, err)
- if plugin_handled:
- return
- self.test.passed = False
- formatted = plugins.formatFailure(self.test, err)
- if formatted is not None:
- err = formatted
- plugins.addFailure(self.test, err)
- self.result.addFailure(self.test, self._prepareErr(err))
- if self.config.stopOnError:
- self.shouldStop = True
-
- def addSkip(self, test, reason):
- # 2.7 compat shim
- from nose.plugins.skip import SkipTest
- self.assertMyTest(test)
- plugins = self.plugins
- if not isinstance(reason, Exception):
- # for Python 3.2+
- reason = Exception(reason)
- plugins.addError(self.test, (SkipTest, reason, None))
- self.result.addSkip(self.test, reason)
-
- def addSuccess(self, test):
- self.assertMyTest(test)
- self.plugins.addSuccess(self.test)
- self.result.addSuccess(self.test)
-
- def startTest(self, test):
- self.assertMyTest(test)
- self.plugins.startTest(self.test)
- self.result.startTest(self.test)
-
- def stop(self):
- self.result.stop()
-
- def stopTest(self, test):
- self.assertMyTest(test)
- self.plugins.stopTest(self.test)
- self.result.stopTest(self.test)
-
- # proxied attributes
- shouldStop = proxied_attribute('result', 'shouldStop',
- """Should the test run stop?""")
- errors = proxied_attribute('result', 'errors',
- """Tests that raised an exception""")
- failures = proxied_attribute('result', 'failures',
- """Tests that failed""")
- testsRun = proxied_attribute('result', 'testsRun',
- """Number of tests run""")
diff --git a/lib/spack/external/nose/pyversion.py b/lib/spack/external/nose/pyversion.py
deleted file mode 100644
index 091238da75..0000000000
--- a/lib/spack/external/nose/pyversion.py
+++ /dev/null
@@ -1,215 +0,0 @@
-"""
-This module contains fixups for using nose under different versions of Python.
-"""
-import sys
-import os
-import traceback
-import types
-import inspect
-import nose.util
-
-__all__ = ['make_instancemethod', 'cmp_to_key', 'sort_list', 'ClassType',
- 'TypeType', 'UNICODE_STRINGS', 'unbound_method', 'ismethod',
- 'bytes_', 'is_base_exception', 'force_unicode', 'exc_to_unicode',
- 'format_exception']
-
-# In Python 3.x, all strings are unicode (the call to 'unicode()' in the 2.x
-# source will be replaced with 'str()' when running 2to3, so this test will
-# then become true)
-UNICODE_STRINGS = (type(unicode()) == type(str()))
-
-if sys.version_info[:2] < (3, 0):
- def force_unicode(s, encoding='UTF-8'):
- try:
- s = unicode(s)
- except UnicodeDecodeError:
- s = str(s).decode(encoding, 'replace')
-
- return s
-else:
- def force_unicode(s, encoding='UTF-8'):
- return str(s)
-
-# new.instancemethod() is obsolete for new-style classes (Python 3.x)
-# We need to use descriptor methods instead.
-try:
- import new
- def make_instancemethod(function, instance):
- return new.instancemethod(function.im_func, instance,
- instance.__class__)
-except ImportError:
- def make_instancemethod(function, instance):
- return function.__get__(instance, instance.__class__)
-
-# To be forward-compatible, we do all list sorts using keys instead of cmp
-# functions. However, part of the unittest.TestLoader API involves a
-# user-provideable cmp function, so we need some way to convert that.
-def cmp_to_key(mycmp):
- 'Convert a cmp= function into a key= function'
- class Key(object):
- def __init__(self, obj):
- self.obj = obj
- def __lt__(self, other):
- return mycmp(self.obj, other.obj) < 0
- def __gt__(self, other):
- return mycmp(self.obj, other.obj) > 0
- def __eq__(self, other):
- return mycmp(self.obj, other.obj) == 0
- return Key
-
-# Python 2.3 also does not support list-sorting by key, so we need to convert
-# keys to cmp functions if we're running on old Python..
-if sys.version_info < (2, 4):
- def sort_list(l, key, reverse=False):
- if reverse:
- return l.sort(lambda a, b: cmp(key(b), key(a)))
- else:
- return l.sort(lambda a, b: cmp(key(a), key(b)))
-else:
- def sort_list(l, key, reverse=False):
- return l.sort(key=key, reverse=reverse)
-
-# In Python 3.x, all objects are "new style" objects descended from 'type', and
-# thus types.ClassType and types.TypeType don't exist anymore. For
-# compatibility, we make sure they still work.
-if hasattr(types, 'ClassType'):
- ClassType = types.ClassType
- TypeType = types.TypeType
-else:
- ClassType = type
- TypeType = type
-
-# The following emulates the behavior (we need) of an 'unbound method' under
-# Python 3.x (namely, the ability to have a class associated with a function
-# definition so that things can do stuff based on its associated class)
-class UnboundMethod:
- def __init__(self, cls, func):
- # Make sure we have all the same attributes as the original function,
- # so that the AttributeSelector plugin will work correctly...
- self.__dict__ = func.__dict__.copy()
- self._func = func
- self.__self__ = UnboundSelf(cls)
- if sys.version_info < (3, 0):
- self.im_class = cls
- self.__doc__ = getattr(func, '__doc__', None)
-
- def address(self):
- cls = self.__self__.cls
- modname = cls.__module__
- module = sys.modules[modname]
- filename = getattr(module, '__file__', None)
- if filename is not None:
- filename = os.path.abspath(filename)
- return (nose.util.src(filename), modname, "%s.%s" % (cls.__name__,
- self._func.__name__))
-
- def __call__(self, *args, **kwargs):
- return self._func(*args, **kwargs)
-
- def __getattr__(self, attr):
- return getattr(self._func, attr)
-
- def __repr__(self):
- return '<unbound method %s.%s>' % (self.__self__.cls.__name__,
- self._func.__name__)
-
-class UnboundSelf:
- def __init__(self, cls):
- self.cls = cls
-
- # We have to do this hackery because Python won't let us override the
- # __class__ attribute...
- def __getattribute__(self, attr):
- if attr == '__class__':
- return self.cls
- else:
- return object.__getattribute__(self, attr)
-
-def unbound_method(cls, func):
- if inspect.ismethod(func):
- return func
- if not inspect.isfunction(func):
- raise TypeError('%s is not a function' % (repr(func),))
- return UnboundMethod(cls, func)
-
-def ismethod(obj):
- return inspect.ismethod(obj) or isinstance(obj, UnboundMethod)
-
-
-# Make a pseudo-bytes function that can be called without the encoding arg:
-if sys.version_info >= (3, 0):
- def bytes_(s, encoding='utf8'):
- if isinstance(s, bytes):
- return s
- return bytes(s, encoding)
-else:
- def bytes_(s, encoding=None):
- return str(s)
-
-
-if sys.version_info[:2] >= (2, 6):
- def isgenerator(o):
- if isinstance(o, UnboundMethod):
- o = o._func
- return inspect.isgeneratorfunction(o) or inspect.isgenerator(o)
-else:
- try:
- from compiler.consts import CO_GENERATOR
- except ImportError:
- # IronPython doesn't have a complier module
- CO_GENERATOR=0x20
-
- def isgenerator(func):
- try:
- return func.func_code.co_flags & CO_GENERATOR != 0
- except AttributeError:
- return False
-
-# Make a function to help check if an exception is derived from BaseException.
-# In Python 2.4, we just use Exception instead.
-if sys.version_info[:2] < (2, 5):
- def is_base_exception(exc):
- return isinstance(exc, Exception)
-else:
- def is_base_exception(exc):
- return isinstance(exc, BaseException)
-
-if sys.version_info[:2] < (3, 0):
- def exc_to_unicode(ev, encoding='utf-8'):
- if is_base_exception(ev):
- if not hasattr(ev, '__unicode__'):
- # 2.5-
- if not hasattr(ev, 'message'):
- # 2.4
- msg = len(ev.args) and ev.args[0] or ''
- else:
- msg = ev.message
- msg = force_unicode(msg, encoding=encoding)
- clsname = force_unicode(ev.__class__.__name__,
- encoding=encoding)
- ev = u'%s: %s' % (clsname, msg)
- elif not isinstance(ev, unicode):
- ev = repr(ev)
-
- return force_unicode(ev, encoding=encoding)
-else:
- def exc_to_unicode(ev, encoding='utf-8'):
- return str(ev)
-
-def format_exception(exc_info, encoding='UTF-8'):
- ec, ev, tb = exc_info
-
- # Our exception object may have been turned into a string, and Python 3's
- # traceback.format_exception() doesn't take kindly to that (it expects an
- # actual exception object). So we work around it, by doing the work
- # ourselves if ev is not an exception object.
- if not is_base_exception(ev):
- tb_data = force_unicode(
- ''.join(traceback.format_tb(tb)),
- encoding)
- ev = exc_to_unicode(ev)
- return tb_data + ev
- else:
- return force_unicode(
- ''.join(traceback.format_exception(*exc_info)),
- encoding)
diff --git a/lib/spack/external/nose/result.py b/lib/spack/external/nose/result.py
deleted file mode 100644
index f974a14ae2..0000000000
--- a/lib/spack/external/nose/result.py
+++ /dev/null
@@ -1,200 +0,0 @@
-"""
-Test Result
------------
-
-Provides a TextTestResult that extends unittest's _TextTestResult to
-provide support for error classes (such as the builtin skip and
-deprecated classes), and hooks for plugins to take over or extend
-reporting.
-"""
-
-import logging
-try:
- # 2.7+
- from unittest.runner import _TextTestResult
-except ImportError:
- from unittest import _TextTestResult
-from nose.config import Config
-from nose.util import isclass, ln as _ln # backwards compat
-
-log = logging.getLogger('nose.result')
-
-
-def _exception_detail(exc):
- # this is what stdlib module traceback does
- try:
- return str(exc)
- except:
- return '<unprintable %s object>' % type(exc).__name__
-
-
-class TextTestResult(_TextTestResult):
- """Text test result that extends unittest's default test result
- support for a configurable set of errorClasses (eg, Skip,
- Deprecated, TODO) that extend the errors/failures/success triad.
- """
- def __init__(self, stream, descriptions, verbosity, config=None,
- errorClasses=None):
- if errorClasses is None:
- errorClasses = {}
- self.errorClasses = errorClasses
- if config is None:
- config = Config()
- self.config = config
- _TextTestResult.__init__(self, stream, descriptions, verbosity)
-
- def addSkip(self, test, reason):
- # 2.7 skip compat
- from nose.plugins.skip import SkipTest
- if SkipTest in self.errorClasses:
- storage, label, isfail = self.errorClasses[SkipTest]
- storage.append((test, reason))
- self.printLabel(label, (SkipTest, reason, None))
-
- def addError(self, test, err):
- """Overrides normal addError to add support for
- errorClasses. If the exception is a registered class, the
- error will be added to the list for that class, not errors.
- """
- ec, ev, tb = err
- try:
- exc_info = self._exc_info_to_string(err, test)
- except TypeError:
- # 2.3 compat
- exc_info = self._exc_info_to_string(err)
- for cls, (storage, label, isfail) in self.errorClasses.items():
- #if 'Skip' in cls.__name__ or 'Skip' in ec.__name__:
- # from nose.tools import set_trace
- # set_trace()
- if isclass(ec) and issubclass(ec, cls):
- if isfail:
- test.passed = False
- storage.append((test, exc_info))
- self.printLabel(label, err)
- return
- self.errors.append((test, exc_info))
- test.passed = False
- self.printLabel('ERROR')
-
- # override to bypass changes in 2.7
- def getDescription(self, test):
- if self.descriptions:
- return test.shortDescription() or str(test)
- else:
- return str(test)
-
- def printLabel(self, label, err=None):
- # Might get patched into a streamless result
- stream = getattr(self, 'stream', None)
- if stream is not None:
- if self.showAll:
- message = [label]
- if err:
- detail = _exception_detail(err[1])
- if detail:
- message.append(detail)
- stream.writeln(": ".join(message))
- elif self.dots:
- stream.write(label[:1])
-
- def printErrors(self):
- """Overrides to print all errorClasses errors as well.
- """
- _TextTestResult.printErrors(self)
- for cls in self.errorClasses.keys():
- storage, label, isfail = self.errorClasses[cls]
- if isfail:
- self.printErrorList(label, storage)
- # Might get patched into a result with no config
- if hasattr(self, 'config'):
- self.config.plugins.report(self.stream)
-
- def printSummary(self, start, stop):
- """Called by the test runner to print the final summary of test
- run results.
- """
- write = self.stream.write
- writeln = self.stream.writeln
- taken = float(stop - start)
- run = self.testsRun
- plural = run != 1 and "s" or ""
-
- writeln(self.separator2)
- writeln("Ran %s test%s in %.3fs" % (run, plural, taken))
- writeln()
-
- summary = {}
- eckeys = self.errorClasses.keys()
- for cls in eckeys:
- storage, label, isfail = self.errorClasses[cls]
- count = len(storage)
- if not count:
- continue
- summary[label] = count
- if len(self.failures):
- summary['failures'] = len(self.failures)
- if len(self.errors):
- summary['errors'] = len(self.errors)
-
- if not self.wasSuccessful():
- write("FAILED")
- else:
- write("OK")
- items = summary.items()
- if items:
- items.sort()
- write(" (")
- write(", ".join(["%s=%s" % (label, count) for
- label, count in items]))
- writeln(")")
- else:
- writeln()
-
- def wasSuccessful(self):
- """Overrides to check that there are no errors in errorClasses
- lists that are marked as errors and should cause a run to
- fail.
- """
- if self.errors or self.failures:
- return False
- for cls in self.errorClasses.keys():
- storage, label, isfail = self.errorClasses[cls]
- if not isfail:
- continue
- if storage:
- return False
- return True
-
- def _addError(self, test, err):
- try:
- exc_info = self._exc_info_to_string(err, test)
- except TypeError:
- # 2.3: does not take test arg
- exc_info = self._exc_info_to_string(err)
- self.errors.append((test, exc_info))
- if self.showAll:
- self.stream.write('ERROR')
- elif self.dots:
- self.stream.write('E')
-
- def _exc_info_to_string(self, err, test=None):
- # 2.7 skip compat
- from nose.plugins.skip import SkipTest
- if isclass(err[0]) and issubclass(err[0], SkipTest):
- return str(err[1])
- # 2.3/2.4 -- 2.4 passes test, 2.3 does not
- try:
- return _TextTestResult._exc_info_to_string(self, err, test)
- except TypeError:
- # 2.3: does not take test arg
- return _TextTestResult._exc_info_to_string(self, err)
-
-
-def ln(*arg, **kw):
- from warnings import warn
- warn("ln() has moved to nose.util from nose.result and will be removed "
- "from nose.result in a future release. Please update your imports ",
- DeprecationWarning)
- return _ln(*arg, **kw)
-
-
diff --git a/lib/spack/external/nose/selector.py b/lib/spack/external/nose/selector.py
deleted file mode 100644
index b63f7af0b1..0000000000
--- a/lib/spack/external/nose/selector.py
+++ /dev/null
@@ -1,251 +0,0 @@
-"""
-Test Selection
---------------
-
-Test selection is handled by a Selector. The test loader calls the
-appropriate selector method for each object it encounters that it
-thinks may be a test.
-"""
-import logging
-import os
-import unittest
-from nose.config import Config
-from nose.util import split_test_name, src, getfilename, getpackage, ispackage, is_executable
-
-log = logging.getLogger(__name__)
-
-__all__ = ['Selector', 'defaultSelector', 'TestAddress']
-
-
-# for efficiency and easier mocking
-op_join = os.path.join
-op_basename = os.path.basename
-op_exists = os.path.exists
-op_splitext = os.path.splitext
-op_isabs = os.path.isabs
-op_abspath = os.path.abspath
-
-
-class Selector(object):
- """Core test selector. Examines test candidates and determines whether,
- given the specified configuration, the test candidate should be selected
- as a test.
- """
- def __init__(self, config):
- if config is None:
- config = Config()
- self.configure(config)
-
- def configure(self, config):
- self.config = config
- self.exclude = config.exclude
- self.ignoreFiles = config.ignoreFiles
- self.include = config.include
- self.plugins = config.plugins
- self.match = config.testMatch
-
- def matches(self, name):
- """Does the name match my requirements?
-
- To match, a name must match config.testMatch OR config.include
- and it must not match config.exclude
- """
- return ((self.match.search(name)
- or (self.include and
- filter(None,
- [inc.search(name) for inc in self.include])))
- and ((not self.exclude)
- or not filter(None,
- [exc.search(name) for exc in self.exclude])
- ))
-
- def wantClass(self, cls):
- """Is the class a wanted test class?
-
- A class must be a unittest.TestCase subclass, or match test name
- requirements. Classes that start with _ are always excluded.
- """
- declared = getattr(cls, '__test__', None)
- if declared is not None:
- wanted = declared
- else:
- wanted = (not cls.__name__.startswith('_')
- and (issubclass(cls, unittest.TestCase)
- or self.matches(cls.__name__)))
-
- plug_wants = self.plugins.wantClass(cls)
- if plug_wants is not None:
- log.debug("Plugin setting selection of %s to %s", cls, plug_wants)
- wanted = plug_wants
- log.debug("wantClass %s? %s", cls, wanted)
- return wanted
-
- def wantDirectory(self, dirname):
- """Is the directory a wanted test directory?
-
- All package directories match, so long as they do not match exclude.
- All other directories must match test requirements.
- """
- tail = op_basename(dirname)
- if ispackage(dirname):
- wanted = (not self.exclude
- or not filter(None,
- [exc.search(tail) for exc in self.exclude]
- ))
- else:
- wanted = (self.matches(tail)
- or (self.config.srcDirs
- and tail in self.config.srcDirs))
- plug_wants = self.plugins.wantDirectory(dirname)
- if plug_wants is not None:
- log.debug("Plugin setting selection of %s to %s",
- dirname, plug_wants)
- wanted = plug_wants
- log.debug("wantDirectory %s? %s", dirname, wanted)
- return wanted
-
- def wantFile(self, file):
- """Is the file a wanted test file?
-
- The file must be a python source file and match testMatch or
- include, and not match exclude. Files that match ignore are *never*
- wanted, regardless of plugin, testMatch, include or exclude settings.
- """
- # never, ever load files that match anything in ignore
- # (.* _* and *setup*.py by default)
- base = op_basename(file)
- ignore_matches = [ ignore_this for ignore_this in self.ignoreFiles
- if ignore_this.search(base) ]
- if ignore_matches:
- log.debug('%s matches ignoreFiles pattern; skipped',
- base)
- return False
- if not self.config.includeExe and is_executable(file):
- log.info('%s is executable; skipped', file)
- return False
- dummy, ext = op_splitext(base)
- pysrc = ext == '.py'
-
- wanted = pysrc and self.matches(base)
- plug_wants = self.plugins.wantFile(file)
- if plug_wants is not None:
- log.debug("plugin setting want %s to %s", file, plug_wants)
- wanted = plug_wants
- log.debug("wantFile %s? %s", file, wanted)
- return wanted
-
- def wantFunction(self, function):
- """Is the function a test function?
- """
- try:
- if hasattr(function, 'compat_func_name'):
- funcname = function.compat_func_name
- else:
- funcname = function.__name__
- except AttributeError:
- # not a function
- return False
- declared = getattr(function, '__test__', None)
- if declared is not None:
- wanted = declared
- else:
- wanted = not funcname.startswith('_') and self.matches(funcname)
- plug_wants = self.plugins.wantFunction(function)
- if plug_wants is not None:
- wanted = plug_wants
- log.debug("wantFunction %s? %s", function, wanted)
- return wanted
-
- def wantMethod(self, method):
- """Is the method a test method?
- """
- try:
- method_name = method.__name__
- except AttributeError:
- # not a method
- return False
- if method_name.startswith('_'):
- # never collect 'private' methods
- return False
- declared = getattr(method, '__test__', None)
- if declared is not None:
- wanted = declared
- else:
- wanted = self.matches(method_name)
- plug_wants = self.plugins.wantMethod(method)
- if plug_wants is not None:
- wanted = plug_wants
- log.debug("wantMethod %s? %s", method, wanted)
- return wanted
-
- def wantModule(self, module):
- """Is the module a test module?
-
- The tail of the module name must match test requirements. One exception:
- we always want __main__.
- """
- declared = getattr(module, '__test__', None)
- if declared is not None:
- wanted = declared
- else:
- wanted = self.matches(module.__name__.split('.')[-1]) \
- or module.__name__ == '__main__'
- plug_wants = self.plugins.wantModule(module)
- if plug_wants is not None:
- wanted = plug_wants
- log.debug("wantModule %s? %s", module, wanted)
- return wanted
-
-defaultSelector = Selector
-
-
-class TestAddress(object):
- """A test address represents a user's request to run a particular
- test. The user may specify a filename or module (or neither),
- and/or a callable (a class, function, or method). The naming
- format for test addresses is:
-
- filename_or_module:callable
-
- Filenames that are not absolute will be made absolute relative to
- the working dir.
-
- The filename or module part will be considered a module name if it
- doesn't look like a file, that is, if it doesn't exist on the file
- system and it doesn't contain any directory separators and it
- doesn't end in .py.
-
- Callables may be a class name, function name, method name, or
- class.method specification.
- """
- def __init__(self, name, workingDir=None):
- if workingDir is None:
- workingDir = os.getcwd()
- self.name = name
- self.workingDir = workingDir
- self.filename, self.module, self.call = split_test_name(name)
- log.debug('Test name %s resolved to file %s, module %s, call %s',
- name, self.filename, self.module, self.call)
- if self.filename is None:
- if self.module is not None:
- self.filename = getfilename(self.module, self.workingDir)
- if self.filename:
- self.filename = src(self.filename)
- if not op_isabs(self.filename):
- self.filename = op_abspath(op_join(workingDir,
- self.filename))
- if self.module is None:
- self.module = getpackage(self.filename)
- log.debug(
- 'Final resolution of test name %s: file %s module %s call %s',
- name, self.filename, self.module, self.call)
-
- def totuple(self):
- return (self.filename, self.module, self.call)
-
- def __str__(self):
- return self.name
-
- def __repr__(self):
- return "%s: (%s, %s, %s)" % (self.name, self.filename,
- self.module, self.call)
diff --git a/lib/spack/external/nose/sphinx/__init__.py b/lib/spack/external/nose/sphinx/__init__.py
deleted file mode 100644
index 2ae28399f5..0000000000
--- a/lib/spack/external/nose/sphinx/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-pass
diff --git a/lib/spack/external/nose/sphinx/pluginopts.py b/lib/spack/external/nose/sphinx/pluginopts.py
deleted file mode 100644
index d2b284ab27..0000000000
--- a/lib/spack/external/nose/sphinx/pluginopts.py
+++ /dev/null
@@ -1,189 +0,0 @@
-"""
-Adds a sphinx directive that can be used to automatically document a plugin.
-
-this::
-
- .. autoplugin :: nose.plugins.foo
- :plugin: Pluggy
-
-produces::
-
- .. automodule :: nose.plugins.foo
-
- Options
- -------
-
- .. cmdoption :: --foo=BAR, --fooble=BAR
-
- Do the foo thing to the new thing.
-
- Plugin
- ------
-
- .. autoclass :: nose.plugins.foo.Pluggy
- :members:
-
- Source
- ------
-
- .. include :: path/to/nose/plugins/foo.py
- :literal:
-
-"""
-import os
-try:
- from docutils import nodes, utils
- from docutils.statemachine import ViewList
- from docutils.parsers.rst import directives
-except ImportError:
- pass # won't run anyway
-
-from nose.util import resolve_name
-from nose.plugins.base import Plugin
-from nose.plugins.manager import BuiltinPluginManager
-from nose.config import Config
-from nose.core import TestProgram
-from inspect import isclass
-
-
-def autoplugin_directive(dirname, arguments, options, content, lineno,
- content_offset, block_text, state, state_machine):
- mod_name = arguments[0]
- mod = resolve_name(mod_name)
- plug_name = options.get('plugin', None)
- if plug_name:
- obj = getattr(mod, plug_name)
- else:
- for entry in dir(mod):
- obj = getattr(mod, entry)
- if isclass(obj) and issubclass(obj, Plugin) and obj is not Plugin:
- plug_name = '%s.%s' % (mod_name, entry)
- break
-
- # mod docstring
- rst = ViewList()
- rst.append('.. automodule :: %s\n' % mod_name, '<autodoc>')
- rst.append('', '<autodoc>')
-
- # options
- rst.append('Options', '<autodoc>')
- rst.append('-------', '<autodoc>')
- rst.append('', '<autodoc>')
-
- plug = obj()
- opts = OptBucket()
- plug.options(opts, {})
- for opt in opts:
- rst.append(opt.options(), '<autodoc>')
- rst.append(' \n', '<autodoc>')
- rst.append(' ' + opt.help + '\n', '<autodoc>')
- rst.append('\n', '<autodoc>')
-
- # plugin class
- rst.append('Plugin', '<autodoc>')
- rst.append('------', '<autodoc>')
- rst.append('', '<autodoc>')
-
- rst.append('.. autoclass :: %s\n' % plug_name, '<autodoc>')
- rst.append(' :members:\n', '<autodoc>')
- rst.append(' :show-inheritance:\n', '<autodoc>')
- rst.append('', '<autodoc>')
-
- # source
- rst.append('Source', '<autodoc>')
- rst.append('------', '<autodoc>')
- rst.append(
- '.. include :: %s\n' % utils.relative_path(
- state_machine.document['source'],
- os.path.abspath(mod.__file__.replace('.pyc', '.py'))),
- '<autodoc>')
- rst.append(' :literal:\n', '<autodoc>')
- rst.append('', '<autodoc>')
-
- node = nodes.section()
- node.document = state.document
- surrounding_title_styles = state.memo.title_styles
- surrounding_section_level = state.memo.section_level
- state.memo.title_styles = []
- state.memo.section_level = 0
- state.nested_parse(rst, 0, node, match_titles=1)
- state.memo.title_styles = surrounding_title_styles
- state.memo.section_level = surrounding_section_level
-
- return node.children
-
-
-def autohelp_directive(dirname, arguments, options, content, lineno,
- content_offset, block_text, state, state_machine):
- """produces rst from nose help"""
- config = Config(parserClass=OptBucket,
- plugins=BuiltinPluginManager())
- parser = config.getParser(TestProgram.usage())
- rst = ViewList()
- for line in parser.format_help().split('\n'):
- rst.append(line, '<autodoc>')
-
- rst.append('Options', '<autodoc>')
- rst.append('-------', '<autodoc>')
- rst.append('', '<autodoc>')
- for opt in parser:
- rst.append(opt.options(), '<autodoc>')
- rst.append(' \n', '<autodoc>')
- rst.append(' ' + opt.help + '\n', '<autodoc>')
- rst.append('\n', '<autodoc>')
- node = nodes.section()
- node.document = state.document
- surrounding_title_styles = state.memo.title_styles
- surrounding_section_level = state.memo.section_level
- state.memo.title_styles = []
- state.memo.section_level = 0
- state.nested_parse(rst, 0, node, match_titles=1)
- state.memo.title_styles = surrounding_title_styles
- state.memo.section_level = surrounding_section_level
-
- return node.children
-
-
-class OptBucket(object):
- def __init__(self, doc=None, prog='nosetests'):
- self.opts = []
- self.doc = doc
- self.prog = prog
-
- def __iter__(self):
- return iter(self.opts)
-
- def format_help(self):
- return self.doc.replace('%prog', self.prog).replace(':\n', '::\n')
-
- def add_option(self, *arg, **kw):
- self.opts.append(Opt(*arg, **kw))
-
-
-class Opt(object):
- def __init__(self, *arg, **kw):
- self.opts = arg
- self.action = kw.pop('action', None)
- self.default = kw.pop('default', None)
- self.metavar = kw.pop('metavar', None)
- self.help = kw.pop('help', None)
-
- def options(self):
- buf = []
- for optstring in self.opts:
- desc = optstring
- if self.action not in ('store_true', 'store_false'):
- desc += '=%s' % self.meta(optstring)
- buf.append(desc)
- return '.. cmdoption :: ' + ', '.join(buf)
-
- def meta(self, optstring):
- # FIXME optparser default metavar?
- return self.metavar or 'DEFAULT'
-
-
-def setup(app):
- app.add_directive('autoplugin',
- autoplugin_directive, 1, (1, 0, 1),
- plugin=directives.unchanged)
- app.add_directive('autohelp', autohelp_directive, 0, (0, 0, 1))
diff --git a/lib/spack/external/nose/suite.py b/lib/spack/external/nose/suite.py
deleted file mode 100644
index a831105e34..0000000000
--- a/lib/spack/external/nose/suite.py
+++ /dev/null
@@ -1,609 +0,0 @@
-"""
-Test Suites
------------
-
-Provides a LazySuite, which is a suite whose test list is a generator
-function, and ContextSuite,which can run fixtures (setup/teardown
-functions or methods) for the context that contains its tests.
-
-"""
-from __future__ import generators
-
-import logging
-import sys
-import unittest
-from nose.case import Test
-from nose.config import Config
-from nose.proxy import ResultProxyFactory
-from nose.util import isclass, resolve_name, try_run
-
-if sys.platform == 'cli':
- if sys.version_info[:2] < (2, 6):
- import clr
- clr.AddReference("IronPython")
- from IronPython.Runtime.Exceptions import StringException
- else:
- class StringException(Exception):
- pass
-
-log = logging.getLogger(__name__)
-#log.setLevel(logging.DEBUG)
-
-# Singleton for default value -- see ContextSuite.__init__ below
-_def = object()
-
-
-def _strclass(cls):
- return "%s.%s" % (cls.__module__, cls.__name__)
-
-class MixedContextError(Exception):
- """Error raised when a context suite sees tests from more than
- one context.
- """
- pass
-
-
-class LazySuite(unittest.TestSuite):
- """A suite that may use a generator as its list of tests
- """
- def __init__(self, tests=()):
- """Initialize the suite. tests may be an iterable or a generator
- """
- super(LazySuite, self).__init__()
- self._set_tests(tests)
-
- def __iter__(self):
- return iter(self._tests)
-
- def __repr__(self):
- return "<%s tests=generator (%s)>" % (
- _strclass(self.__class__), id(self))
-
- def __hash__(self):
- return object.__hash__(self)
-
- __str__ = __repr__
-
- def addTest(self, test):
- self._precache.append(test)
-
- # added to bypass run changes in 2.7's unittest
- def run(self, result):
- for test in self._tests:
- if result.shouldStop:
- break
- test(result)
- return result
-
- def __nonzero__(self):
- log.debug("tests in %s?", id(self))
- if self._precache:
- return True
- if self.test_generator is None:
- return False
- try:
- test = self.test_generator.next()
- if test is not None:
- self._precache.append(test)
- return True
- except StopIteration:
- pass
- return False
-
- def _get_tests(self):
- log.debug("precache is %s", self._precache)
- for test in self._precache:
- yield test
- if self.test_generator is None:
- return
- for test in self.test_generator:
- yield test
-
- def _set_tests(self, tests):
- self._precache = []
- is_suite = isinstance(tests, unittest.TestSuite)
- if callable(tests) and not is_suite:
- self.test_generator = tests()
- elif is_suite:
- # Suites need special treatment: they must be called like
- # tests for their setup/teardown to run (if any)
- self.addTests([tests])
- self.test_generator = None
- else:
- self.addTests(tests)
- self.test_generator = None
-
- _tests = property(_get_tests, _set_tests, None,
- "Access the tests in this suite. Access is through a "
- "generator, so iteration may not be repeatable.")
-
-
-class ContextSuite(LazySuite):
- """A suite with context.
-
- A ContextSuite executes fixtures (setup and teardown functions or
- methods) for the context containing its tests.
-
- The context may be explicitly passed. If it is not, a context (or
- nested set of contexts) will be constructed by examining the tests
- in the suite.
- """
- failureException = unittest.TestCase.failureException
- was_setup = False
- was_torndown = False
- classSetup = ('setup_class', 'setup_all', 'setupClass', 'setupAll',
- 'setUpClass', 'setUpAll')
- classTeardown = ('teardown_class', 'teardown_all', 'teardownClass',
- 'teardownAll', 'tearDownClass', 'tearDownAll')
- moduleSetup = ('setup_module', 'setupModule', 'setUpModule', 'setup',
- 'setUp')
- moduleTeardown = ('teardown_module', 'teardownModule', 'tearDownModule',
- 'teardown', 'tearDown')
- packageSetup = ('setup_package', 'setupPackage', 'setUpPackage')
- packageTeardown = ('teardown_package', 'teardownPackage',
- 'tearDownPackage')
-
- def __init__(self, tests=(), context=None, factory=None,
- config=None, resultProxy=None, can_split=True):
- log.debug("Context suite for %s (%s) (%s)", tests, context, id(self))
- self.context = context
- self.factory = factory
- if config is None:
- config = Config()
- self.config = config
- self.resultProxy = resultProxy
- self.has_run = False
- self.can_split = can_split
- self.error_context = None
- super(ContextSuite, self).__init__(tests)
-
- def __repr__(self):
- return "<%s context=%s>" % (
- _strclass(self.__class__),
- getattr(self.context, '__name__', self.context))
- __str__ = __repr__
-
- def id(self):
- if self.error_context:
- return '%s:%s' % (repr(self), self.error_context)
- else:
- return repr(self)
-
- def __hash__(self):
- return object.__hash__(self)
-
- # 2.3 compat -- force 2.4 call sequence
- def __call__(self, *arg, **kw):
- return self.run(*arg, **kw)
-
- def exc_info(self):
- """Hook for replacing error tuple output
- """
- return sys.exc_info()
-
- def _exc_info(self):
- """Bottleneck to fix up IronPython string exceptions
- """
- e = self.exc_info()
- if sys.platform == 'cli':
- if isinstance(e[0], StringException):
- # IronPython throws these StringExceptions, but
- # traceback checks type(etype) == str. Make a real
- # string here.
- e = (str(e[0]), e[1], e[2])
-
- return e
-
- def run(self, result):
- """Run tests in suite inside of suite fixtures.
- """
- # proxy the result for myself
- log.debug("suite %s (%s) run called, tests: %s", id(self), self, self._tests)
- #import pdb
- #pdb.set_trace()
- if self.resultProxy:
- result, orig = self.resultProxy(result, self), result
- else:
- result, orig = result, result
- try:
- self.setUp()
- except KeyboardInterrupt:
- raise
- except:
- self.error_context = 'setup'
- result.addError(self, self._exc_info())
- return
- try:
- for test in self._tests:
- if result.shouldStop:
- log.debug("stopping")
- break
- # each nose.case.Test will create its own result proxy
- # so the cases need the original result, to avoid proxy
- # chains
- test(orig)
- finally:
- self.has_run = True
- try:
- self.tearDown()
- except KeyboardInterrupt:
- raise
- except:
- self.error_context = 'teardown'
- result.addError(self, self._exc_info())
-
- def hasFixtures(self, ctx_callback=None):
- context = self.context
- if context is None:
- return False
- if self.implementsAnyFixture(context, ctx_callback=ctx_callback):
- return True
- # My context doesn't have any, but its ancestors might
- factory = self.factory
- if factory:
- ancestors = factory.context.get(self, [])
- for ancestor in ancestors:
- if self.implementsAnyFixture(
- ancestor, ctx_callback=ctx_callback):
- return True
- return False
-
- def implementsAnyFixture(self, context, ctx_callback):
- if isclass(context):
- names = self.classSetup + self.classTeardown
- else:
- names = self.moduleSetup + self.moduleTeardown
- if hasattr(context, '__path__'):
- names += self.packageSetup + self.packageTeardown
- # If my context has any fixture attribute, I have fixtures
- fixt = False
- for m in names:
- if hasattr(context, m):
- fixt = True
- break
- if ctx_callback is None:
- return fixt
- return ctx_callback(context, fixt)
-
- def setUp(self):
- log.debug("suite %s setUp called, tests: %s", id(self), self._tests)
- if not self:
- # I have no tests
- log.debug("suite %s has no tests", id(self))
- return
- if self.was_setup:
- log.debug("suite %s already set up", id(self))
- return
- context = self.context
- if context is None:
- return
- # before running my own context's setup, I need to
- # ask the factory if my context's contexts' setups have been run
- factory = self.factory
- if factory:
- # get a copy, since we'll be destroying it as we go
- ancestors = factory.context.get(self, [])[:]
- while ancestors:
- ancestor = ancestors.pop()
- log.debug("ancestor %s may need setup", ancestor)
- if ancestor in factory.was_setup:
- continue
- log.debug("ancestor %s does need setup", ancestor)
- self.setupContext(ancestor)
- if not context in factory.was_setup:
- self.setupContext(context)
- else:
- self.setupContext(context)
- self.was_setup = True
- log.debug("completed suite setup")
-
- def setupContext(self, context):
- self.config.plugins.startContext(context)
- log.debug("%s setup context %s", self, context)
- if self.factory:
- if context in self.factory.was_setup:
- return
- # note that I ran the setup for this context, so that I'll run
- # the teardown in my teardown
- self.factory.was_setup[context] = self
- if isclass(context):
- names = self.classSetup
- else:
- names = self.moduleSetup
- if hasattr(context, '__path__'):
- names = self.packageSetup + names
- try_run(context, names)
-
- def shortDescription(self):
- if self.context is None:
- return "test suite"
- return "test suite for %s" % self.context
-
- def tearDown(self):
- log.debug('context teardown')
- if not self.was_setup or self.was_torndown:
- log.debug(
- "No reason to teardown (was_setup? %s was_torndown? %s)"
- % (self.was_setup, self.was_torndown))
- return
- self.was_torndown = True
- context = self.context
- if context is None:
- log.debug("No context to tear down")
- return
-
- # for each ancestor... if the ancestor was setup
- # and I did the setup, I can do teardown
- factory = self.factory
- if factory:
- ancestors = factory.context.get(self, []) + [context]
- for ancestor in ancestors:
- log.debug('ancestor %s may need teardown', ancestor)
- if not ancestor in factory.was_setup:
- log.debug('ancestor %s was not setup', ancestor)
- continue
- if ancestor in factory.was_torndown:
- log.debug('ancestor %s already torn down', ancestor)
- continue
- setup = factory.was_setup[ancestor]
- log.debug("%s setup ancestor %s", setup, ancestor)
- if setup is self:
- self.teardownContext(ancestor)
- else:
- self.teardownContext(context)
-
- def teardownContext(self, context):
- log.debug("%s teardown context %s", self, context)
- if self.factory:
- if context in self.factory.was_torndown:
- return
- self.factory.was_torndown[context] = self
- if isclass(context):
- names = self.classTeardown
- else:
- names = self.moduleTeardown
- if hasattr(context, '__path__'):
- names = self.packageTeardown + names
- try_run(context, names)
- self.config.plugins.stopContext(context)
-
- # FIXME the wrapping has to move to the factory?
- def _get_wrapped_tests(self):
- for test in self._get_tests():
- if isinstance(test, Test) or isinstance(test, unittest.TestSuite):
- yield test
- else:
- yield Test(test,
- config=self.config,
- resultProxy=self.resultProxy)
-
- _tests = property(_get_wrapped_tests, LazySuite._set_tests, None,
- "Access the tests in this suite. Tests are returned "
- "inside of a context wrapper.")
-
-
-class ContextSuiteFactory(object):
- """Factory for ContextSuites. Called with a collection of tests,
- the factory decides on a hierarchy of contexts by introspecting
- the collection or the tests themselves to find the objects
- containing the test objects. It always returns one suite, but that
- suite may consist of a hierarchy of nested suites.
- """
- suiteClass = ContextSuite
- def __init__(self, config=None, suiteClass=None, resultProxy=_def):
- if config is None:
- config = Config()
- self.config = config
- if suiteClass is not None:
- self.suiteClass = suiteClass
- # Using a singleton to represent default instead of None allows
- # passing resultProxy=None to turn proxying off.
- if resultProxy is _def:
- resultProxy = ResultProxyFactory(config=config)
- self.resultProxy = resultProxy
- self.suites = {}
- self.context = {}
- self.was_setup = {}
- self.was_torndown = {}
-
- def __call__(self, tests, **kw):
- """Return ``ContextSuite`` for tests. ``tests`` may either
- be a callable (in which case the resulting ContextSuite will
- have no parent context and be evaluated lazily) or an
- iterable. In that case the tests will wrapped in
- nose.case.Test, be examined and the context of each found and a
- suite of suites returned, organized into a stack with the
- outermost suites belonging to the outermost contexts.
- """
- log.debug("Create suite for %s", tests)
- context = kw.pop('context', getattr(tests, 'context', None))
- log.debug("tests %s context %s", tests, context)
- if context is None:
- tests = self.wrapTests(tests)
- try:
- context = self.findContext(tests)
- except MixedContextError:
- return self.makeSuite(self.mixedSuites(tests), None, **kw)
- return self.makeSuite(tests, context, **kw)
-
- def ancestry(self, context):
- """Return the ancestry of the context (that is, all of the
- packages and modules containing the context), in order of
- descent with the outermost ancestor last.
- This method is a generator.
- """
- log.debug("get ancestry %s", context)
- if context is None:
- return
- # Methods include reference to module they are defined in, we
- # don't want that, instead want the module the class is in now
- # (classes are re-ancestored elsewhere).
- if hasattr(context, 'im_class'):
- context = context.im_class
- elif hasattr(context, '__self__'):
- context = context.__self__.__class__
- if hasattr(context, '__module__'):
- ancestors = context.__module__.split('.')
- elif hasattr(context, '__name__'):
- ancestors = context.__name__.split('.')[:-1]
- else:
- raise TypeError("%s has no ancestors?" % context)
- while ancestors:
- log.debug(" %s ancestors %s", context, ancestors)
- yield resolve_name('.'.join(ancestors))
- ancestors.pop()
-
- def findContext(self, tests):
- if callable(tests) or isinstance(tests, unittest.TestSuite):
- return None
- context = None
- for test in tests:
- # Don't look at suites for contexts, only tests
- ctx = getattr(test, 'context', None)
- if ctx is None:
- continue
- if context is None:
- context = ctx
- elif context != ctx:
- raise MixedContextError(
- "Tests with different contexts in same suite! %s != %s"
- % (context, ctx))
- return context
-
- def makeSuite(self, tests, context, **kw):
- suite = self.suiteClass(
- tests, context=context, config=self.config, factory=self,
- resultProxy=self.resultProxy, **kw)
- if context is not None:
- self.suites.setdefault(context, []).append(suite)
- self.context.setdefault(suite, []).append(context)
- log.debug("suite %s has context %s", suite,
- getattr(context, '__name__', None))
- for ancestor in self.ancestry(context):
- self.suites.setdefault(ancestor, []).append(suite)
- self.context[suite].append(ancestor)
- log.debug("suite %s has ancestor %s", suite, ancestor.__name__)
- return suite
-
- def mixedSuites(self, tests):
- """The complex case where there are tests that don't all share
- the same context. Groups tests into suites with common ancestors,
- according to the following (essentially tail-recursive) procedure:
-
- Starting with the context of the first test, if it is not
- None, look for tests in the remaining tests that share that
- ancestor. If any are found, group into a suite with that
- ancestor as the context, and replace the current suite with
- that suite. Continue this process for each ancestor of the
- first test, until all ancestors have been processed. At this
- point if any tests remain, recurse with those tests as the
- input, returning a list of the common suite (which may be the
- suite or test we started with, if no common tests were found)
- plus the results of recursion.
- """
- if not tests:
- return []
- head = tests.pop(0)
- if not tests:
- return [head] # short circuit when none are left to combine
- suite = head # the common ancestry suite, so far
- tail = tests[:]
- context = getattr(head, 'context', None)
- if context is not None:
- ancestors = [context] + [a for a in self.ancestry(context)]
- for ancestor in ancestors:
- common = [suite] # tests with ancestor in common, so far
- remain = [] # tests that remain to be processed
- for test in tail:
- found_common = False
- test_ctx = getattr(test, 'context', None)
- if test_ctx is None:
- remain.append(test)
- continue
- if test_ctx is ancestor:
- common.append(test)
- continue
- for test_ancestor in self.ancestry(test_ctx):
- if test_ancestor is ancestor:
- common.append(test)
- found_common = True
- break
- if not found_common:
- remain.append(test)
- if common:
- suite = self.makeSuite(common, ancestor)
- tail = self.mixedSuites(remain)
- return [suite] + tail
-
- def wrapTests(self, tests):
- log.debug("wrap %s", tests)
- if callable(tests) or isinstance(tests, unittest.TestSuite):
- log.debug("I won't wrap")
- return tests
- wrapped = []
- for test in tests:
- log.debug("wrapping %s", test)
- if isinstance(test, Test) or isinstance(test, unittest.TestSuite):
- wrapped.append(test)
- elif isinstance(test, ContextList):
- wrapped.append(self.makeSuite(test, context=test.context))
- else:
- wrapped.append(
- Test(test, config=self.config, resultProxy=self.resultProxy)
- )
- return wrapped
-
-
-class ContextList(object):
- """Not quite a suite -- a group of tests in a context. This is used
- to hint the ContextSuiteFactory about what context the tests
- belong to, in cases where it may be ambiguous or missing.
- """
- def __init__(self, tests, context=None):
- self.tests = tests
- self.context = context
-
- def __iter__(self):
- return iter(self.tests)
-
-
-class FinalizingSuiteWrapper(unittest.TestSuite):
- """Wraps suite and calls final function after suite has
- executed. Used to call final functions in cases (like running in
- the standard test runner) where test running is not under nose's
- control.
- """
- def __init__(self, suite, finalize):
- super(FinalizingSuiteWrapper, self).__init__()
- self.suite = suite
- self.finalize = finalize
-
- def __call__(self, *arg, **kw):
- return self.run(*arg, **kw)
-
- # 2.7 compat
- def __iter__(self):
- return iter(self.suite)
-
- def run(self, *arg, **kw):
- try:
- return self.suite(*arg, **kw)
- finally:
- self.finalize(*arg, **kw)
-
-
-# backwards compat -- sort of
-class TestDir:
- def __init__(*arg, **kw):
- raise NotImplementedError(
- "TestDir is not usable with nose 0.10. The class is present "
- "in nose.suite for backwards compatibility purposes but it "
- "may not be used.")
-
-
-class TestModule:
- def __init__(*arg, **kw):
- raise NotImplementedError(
- "TestModule is not usable with nose 0.10. The class is present "
- "in nose.suite for backwards compatibility purposes but it "
- "may not be used.")
diff --git a/lib/spack/external/nose/tools/__init__.py b/lib/spack/external/nose/tools/__init__.py
deleted file mode 100644
index 74dab16a74..0000000000
--- a/lib/spack/external/nose/tools/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
-Tools for testing
------------------
-
-nose.tools provides a few convenience functions to make writing tests
-easier. You don't have to use them; nothing in the rest of nose depends
-on any of these methods.
-
-"""
-from nose.tools.nontrivial import *
-from nose.tools.nontrivial import __all__ as nontrivial_all
-from nose.tools.trivial import *
-from nose.tools.trivial import __all__ as trivial_all
-
-__all__ = trivial_all + nontrivial_all
diff --git a/lib/spack/external/nose/tools/nontrivial.py b/lib/spack/external/nose/tools/nontrivial.py
deleted file mode 100644
index 283973245b..0000000000
--- a/lib/spack/external/nose/tools/nontrivial.py
+++ /dev/null
@@ -1,151 +0,0 @@
-"""Tools not exempt from being descended into in tracebacks"""
-
-import time
-
-
-__all__ = ['make_decorator', 'raises', 'set_trace', 'timed', 'with_setup',
- 'TimeExpired', 'istest', 'nottest']
-
-
-class TimeExpired(AssertionError):
- pass
-
-
-def make_decorator(func):
- """
- Wraps a test decorator so as to properly replicate metadata
- of the decorated function, including nose's additional stuff
- (namely, setup and teardown).
- """
- def decorate(newfunc):
- if hasattr(func, 'compat_func_name'):
- name = func.compat_func_name
- else:
- name = func.__name__
- newfunc.__dict__ = func.__dict__
- newfunc.__doc__ = func.__doc__
- newfunc.__module__ = func.__module__
- if not hasattr(newfunc, 'compat_co_firstlineno'):
- newfunc.compat_co_firstlineno = func.func_code.co_firstlineno
- try:
- newfunc.__name__ = name
- except TypeError:
- # can't set func name in 2.3
- newfunc.compat_func_name = name
- return newfunc
- return decorate
-
-
-def raises(*exceptions):
- """Test must raise one of expected exceptions to pass.
-
- Example use::
-
- @raises(TypeError, ValueError)
- def test_raises_type_error():
- raise TypeError("This test passes")
-
- @raises(Exception)
- def test_that_fails_by_passing():
- pass
-
- If you want to test many assertions about exceptions in a single test,
- you may want to use `assert_raises` instead.
- """
- valid = ' or '.join([e.__name__ for e in exceptions])
- def decorate(func):
- name = func.__name__
- def newfunc(*arg, **kw):
- try:
- func(*arg, **kw)
- except exceptions:
- pass
- except:
- raise
- else:
- message = "%s() did not raise %s" % (name, valid)
- raise AssertionError(message)
- newfunc = make_decorator(func)(newfunc)
- return newfunc
- return decorate
-
-
-def set_trace():
- """Call pdb.set_trace in the calling frame, first restoring
- sys.stdout to the real output stream. Note that sys.stdout is NOT
- reset to whatever it was before the call once pdb is done!
- """
- import pdb
- import sys
- stdout = sys.stdout
- sys.stdout = sys.__stdout__
- pdb.Pdb().set_trace(sys._getframe().f_back)
-
-
-def timed(limit):
- """Test must finish within specified time limit to pass.
-
- Example use::
-
- @timed(.1)
- def test_that_fails():
- time.sleep(.2)
- """
- def decorate(func):
- def newfunc(*arg, **kw):
- start = time.time()
- result = func(*arg, **kw)
- end = time.time()
- if end - start > limit:
- raise TimeExpired("Time limit (%s) exceeded" % limit)
- return result
- newfunc = make_decorator(func)(newfunc)
- return newfunc
- return decorate
-
-
-def with_setup(setup=None, teardown=None):
- """Decorator to add setup and/or teardown methods to a test function::
-
- @with_setup(setup, teardown)
- def test_something():
- " ... "
-
- Note that `with_setup` is useful *only* for test functions, not for test
- methods or inside of TestCase subclasses.
- """
- def decorate(func, setup=setup, teardown=teardown):
- if setup:
- if hasattr(func, 'setup'):
- _old_s = func.setup
- def _s():
- setup()
- _old_s()
- func.setup = _s
- else:
- func.setup = setup
- if teardown:
- if hasattr(func, 'teardown'):
- _old_t = func.teardown
- def _t():
- _old_t()
- teardown()
- func.teardown = _t
- else:
- func.teardown = teardown
- return func
- return decorate
-
-
-def istest(func):
- """Decorator to mark a function or method as a test
- """
- func.__test__ = True
- return func
-
-
-def nottest(func):
- """Decorator to mark a function or method as *not* a test
- """
- func.__test__ = False
- return func
diff --git a/lib/spack/external/nose/tools/trivial.py b/lib/spack/external/nose/tools/trivial.py
deleted file mode 100644
index cf83efeda5..0000000000
--- a/lib/spack/external/nose/tools/trivial.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""Tools so trivial that tracebacks should not descend into them
-
-We define the ``__unittest`` symbol in their module namespace so unittest will
-skip them when printing tracebacks, just as it does for their corresponding
-methods in ``unittest`` proper.
-
-"""
-import re
-import unittest
-
-
-__all__ = ['ok_', 'eq_']
-
-# Use the same flag as unittest itself to prevent descent into these functions:
-__unittest = 1
-
-
-def ok_(expr, msg=None):
- """Shorthand for assert. Saves 3 whole characters!
- """
- if not expr:
- raise AssertionError(msg)
-
-
-def eq_(a, b, msg=None):
- """Shorthand for 'assert a == b, "%r != %r" % (a, b)
- """
- if not a == b:
- raise AssertionError(msg or "%r != %r" % (a, b))
-
-
-#
-# Expose assert* from unittest.TestCase
-# - give them pep8 style names
-#
-caps = re.compile('([A-Z])')
-
-def pep8(name):
- return caps.sub(lambda m: '_' + m.groups()[0].lower(), name)
-
-class Dummy(unittest.TestCase):
- def nop():
- pass
-_t = Dummy('nop')
-
-for at in [ at for at in dir(_t)
- if at.startswith('assert') and not '_' in at ]:
- pepd = pep8(at)
- vars()[pepd] = getattr(_t, at)
- __all__.append(pepd)
-
-del Dummy
-del _t
-del pep8
diff --git a/lib/spack/external/nose/twistedtools.py b/lib/spack/external/nose/twistedtools.py
deleted file mode 100644
index 8d9c6ffe9b..0000000000
--- a/lib/spack/external/nose/twistedtools.py
+++ /dev/null
@@ -1,173 +0,0 @@
-"""
-Twisted integration
--------------------
-
-This module provides a very simple way to integrate your tests with the
-Twisted_ event loop.
-
-You must import this module *before* importing anything from Twisted itself!
-
-Example::
-
- from nose.twistedtools import reactor, deferred
-
- @deferred()
- def test_resolve():
- return reactor.resolve("www.python.org")
-
-Or, more realistically::
-
- @deferred(timeout=5.0)
- def test_resolve():
- d = reactor.resolve("www.python.org")
- def check_ip(ip):
- assert ip == "67.15.36.43"
- d.addCallback(check_ip)
- return d
-
-.. _Twisted: http://twistedmatrix.com/trac/
-"""
-
-import sys
-from Queue import Queue, Empty
-from nose.tools import make_decorator, TimeExpired
-
-__all__ = [
- 'threaded_reactor', 'reactor', 'deferred', 'TimeExpired',
- 'stop_reactor'
-]
-
-_twisted_thread = None
-
-def threaded_reactor():
- """
- Start the Twisted reactor in a separate thread, if not already done.
- Returns the reactor.
- The thread will automatically be destroyed when all the tests are done.
- """
- global _twisted_thread
- try:
- from twisted.internet import reactor
- except ImportError:
- return None, None
- if not _twisted_thread:
- from twisted.python import threadable
- from threading import Thread
- _twisted_thread = Thread(target=lambda: reactor.run( \
- installSignalHandlers=False))
- _twisted_thread.setDaemon(True)
- _twisted_thread.start()
- return reactor, _twisted_thread
-
-# Export global reactor variable, as Twisted does
-reactor, reactor_thread = threaded_reactor()
-
-
-def stop_reactor():
- """Stop the reactor and join the reactor thread until it stops.
- Call this function in teardown at the module or package level to
- reset the twisted system after your tests. You *must* do this if
- you mix tests using these tools and tests using twisted.trial.
- """
- global _twisted_thread
-
- def stop_reactor():
- '''Helper for calling stop from withing the thread.'''
- reactor.stop()
-
- reactor.callFromThread(stop_reactor)
- reactor_thread.join()
- for p in reactor.getDelayedCalls():
- if p.active():
- p.cancel()
- _twisted_thread = None
-
-
-def deferred(timeout=None):
- """
- By wrapping a test function with this decorator, you can return a
- twisted Deferred and the test will wait for the deferred to be triggered.
- The whole test function will run inside the Twisted event loop.
-
- The optional timeout parameter specifies the maximum duration of the test.
- The difference with timed() is that timed() will still wait for the test
- to end, while deferred() will stop the test when its timeout has expired.
- The latter is more desireable when dealing with network tests, because
- the result may actually never arrive.
-
- If the callback is triggered, the test has passed.
- If the errback is triggered or the timeout expires, the test has failed.
-
- Example::
-
- @deferred(timeout=5.0)
- def test_resolve():
- return reactor.resolve("www.python.org")
-
- Attention! If you combine this decorator with other decorators (like
- "raises"), deferred() must be called *first*!
-
- In other words, this is good::
-
- @raises(DNSLookupError)
- @deferred()
- def test_error():
- return reactor.resolve("xxxjhjhj.biz")
-
- and this is bad::
-
- @deferred()
- @raises(DNSLookupError)
- def test_error():
- return reactor.resolve("xxxjhjhj.biz")
- """
- reactor, reactor_thread = threaded_reactor()
- if reactor is None:
- raise ImportError("twisted is not available or could not be imported")
- # Check for common syntax mistake
- # (otherwise, tests can be silently ignored
- # if one writes "@deferred" instead of "@deferred()")
- try:
- timeout is None or timeout + 0
- except TypeError:
- raise TypeError("'timeout' argument must be a number or None")
-
- def decorate(func):
- def wrapper(*args, **kargs):
- q = Queue()
- def callback(value):
- q.put(None)
- def errback(failure):
- # Retrieve and save full exception info
- try:
- failure.raiseException()
- except:
- q.put(sys.exc_info())
- def g():
- try:
- d = func(*args, **kargs)
- try:
- d.addCallbacks(callback, errback)
- # Check for a common mistake and display a nice error
- # message
- except AttributeError:
- raise TypeError("you must return a twisted Deferred "
- "from your test case!")
- # Catch exceptions raised in the test body (from the
- # Twisted thread)
- except:
- q.put(sys.exc_info())
- reactor.callFromThread(g)
- try:
- error = q.get(timeout=timeout)
- except Empty:
- raise TimeExpired("timeout expired before end of test (%f s.)"
- % timeout)
- # Re-raise all exceptions
- if error is not None:
- exc_type, exc_value, tb = error
- raise exc_type, exc_value, tb
- wrapper = make_decorator(func)(wrapper)
- return wrapper
- return decorate
-
diff --git a/lib/spack/external/nose/usage.txt b/lib/spack/external/nose/usage.txt
deleted file mode 100644
index bc96894ab7..0000000000
--- a/lib/spack/external/nose/usage.txt
+++ /dev/null
@@ -1,115 +0,0 @@
-nose collects tests automatically from python source files,
-directories and packages found in its working directory (which
-defaults to the current working directory). Any python source file,
-directory or package that matches the testMatch regular expression
-(by default: `(?:^|[\b_\.-])[Tt]est)` will be collected as a test (or
-source for collection of tests). In addition, all other packages
-found in the working directory will be examined for python source files
-or directories that match testMatch. Package discovery descends all
-the way down the tree, so package.tests and package.sub.tests and
-package.sub.sub2.tests will all be collected.
-
-Within a test directory or package, any python source file matching
-testMatch will be examined for test cases. Within a test module,
-functions and classes whose names match testMatch and TestCase
-subclasses with any name will be loaded and executed as tests. Tests
-may use the assert keyword or raise AssertionErrors to indicate test
-failure. TestCase subclasses may do the same or use the various
-TestCase methods available.
-
-**It is important to note that the default behavior of nose is to
-not include tests from files which are executable.** To include
-tests from such files, remove their executable bit or use
-the --exe flag (see 'Options' section below).
-
-Selecting Tests
----------------
-
-To specify which tests to run, pass test names on the command line:
-
- %prog only_test_this.py
-
-Test names specified may be file or module names, and may optionally
-indicate the test case to run by separating the module or file name
-from the test case name with a colon. Filenames may be relative or
-absolute. Examples:
-
- %prog test.module
- %prog another.test:TestCase.test_method
- %prog a.test:TestCase
- %prog /path/to/test/file.py:test_function
-
-You may also change the working directory where nose looks for tests
-by using the -w switch:
-
- %prog -w /path/to/tests
-
-Note, however, that support for multiple -w arguments is now deprecated
-and will be removed in a future release. As of nose 0.10, you can get
-the same behavior by specifying the target directories *without*
-the -w switch:
-
- %prog /path/to/tests /another/path/to/tests
-
-Further customization of test selection and loading is possible
-through the use of plugins.
-
-Test result output is identical to that of unittest, except for
-the additional features (error classes, and plugin-supplied
-features such as output capture and assert introspection) detailed
-in the options below.
-
-Configuration
--------------
-
-In addition to passing command-line options, you may also put
-configuration options in your project's *setup.cfg* file, or a .noserc
-or nose.cfg file in your home directory. In any of these standard
-ini-style config files, you put your nosetests configuration in a
-``[nosetests]`` section. Options are the same as on the command line,
-with the -- prefix removed. For options that are simple switches, you
-must supply a value:
-
- [nosetests]
- verbosity=3
- with-doctest=1
-
-All configuration files that are found will be loaded and their
-options combined. You can override the standard config file loading
-with the ``-c`` option.
-
-Using Plugins
--------------
-
-There are numerous nose plugins available via easy_install and
-elsewhere. To use a plugin, just install it. The plugin will add
-command line options to nosetests. To verify that the plugin is installed,
-run:
-
- nosetests --plugins
-
-You can add -v or -vv to that command to show more information
-about each plugin.
-
-If you are running nose.main() or nose.run() from a script, you
-can specify a list of plugins to use by passing a list of plugins
-with the plugins keyword argument.
-
-0.9 plugins
------------
-
-nose 1.0 can use SOME plugins that were written for nose 0.9. The
-default plugin manager inserts a compatibility wrapper around 0.9
-plugins that adapts the changed plugin api calls. However, plugins
-that access nose internals are likely to fail, especially if they
-attempt to access test case or test suite classes. For example,
-plugins that try to determine if a test passed to startTest is an
-individual test or a suite will fail, partly because suites are no
-longer passed to startTest and partly because it's likely that the
-plugin is trying to find out if the test is an instance of a class
-that no longer exists.
-
-0.10 and 0.11 plugins
----------------------
-
-All plugins written for nose 0.10 and 0.11 should work with nose 1.0.
diff --git a/lib/spack/external/nose/util.py b/lib/spack/external/nose/util.py
deleted file mode 100644
index bfe16589ea..0000000000
--- a/lib/spack/external/nose/util.py
+++ /dev/null
@@ -1,668 +0,0 @@
-"""Utility functions and classes used by nose internally.
-"""
-import inspect
-import itertools
-import logging
-import stat
-import os
-import re
-import sys
-import types
-import unittest
-from nose.pyversion import ClassType, TypeType, isgenerator, ismethod
-
-
-log = logging.getLogger('nose')
-
-ident_re = re.compile(r'^[A-Za-z_][A-Za-z0-9_.]*$')
-class_types = (ClassType, TypeType)
-skip_pattern = r"(?:\.svn)|(?:[^.]+\.py[co])|(?:.*~)|(?:.*\$py\.class)|(?:__pycache__)"
-
-try:
- set()
- set = set # make from nose.util import set happy
-except NameError:
- try:
- from sets import Set as set
- except ImportError:
- pass
-
-
-def ls_tree(dir_path="",
- skip_pattern=skip_pattern,
- indent="|-- ", branch_indent="| ",
- last_indent="`-- ", last_branch_indent=" "):
- # TODO: empty directories look like non-directory files
- return "\n".join(_ls_tree_lines(dir_path, skip_pattern,
- indent, branch_indent,
- last_indent, last_branch_indent))
-
-
-def _ls_tree_lines(dir_path, skip_pattern,
- indent, branch_indent, last_indent, last_branch_indent):
- if dir_path == "":
- dir_path = os.getcwd()
-
- lines = []
-
- names = os.listdir(dir_path)
- names.sort()
- dirs, nondirs = [], []
- for name in names:
- if re.match(skip_pattern, name):
- continue
- if os.path.isdir(os.path.join(dir_path, name)):
- dirs.append(name)
- else:
- nondirs.append(name)
-
- # list non-directories first
- entries = list(itertools.chain([(name, False) for name in nondirs],
- [(name, True) for name in dirs]))
- def ls_entry(name, is_dir, ind, branch_ind):
- if not is_dir:
- yield ind + name
- else:
- path = os.path.join(dir_path, name)
- if not os.path.islink(path):
- yield ind + name
- subtree = _ls_tree_lines(path, skip_pattern,
- indent, branch_indent,
- last_indent, last_branch_indent)
- for x in subtree:
- yield branch_ind + x
- for name, is_dir in entries[:-1]:
- for line in ls_entry(name, is_dir, indent, branch_indent):
- yield line
- if entries:
- name, is_dir = entries[-1]
- for line in ls_entry(name, is_dir, last_indent, last_branch_indent):
- yield line
-
-
-def absdir(path):
- """Return absolute, normalized path to directory, if it exists; None
- otherwise.
- """
- if not os.path.isabs(path):
- path = os.path.normpath(os.path.abspath(os.path.join(os.getcwd(),
- path)))
- if path is None or not os.path.isdir(path):
- return None
- return path
-
-
-def absfile(path, where=None):
- """Return absolute, normalized path to file (optionally in directory
- where), or None if the file can't be found either in where or the current
- working directory.
- """
- orig = path
- if where is None:
- where = os.getcwd()
- if isinstance(where, list) or isinstance(where, tuple):
- for maybe_path in where:
- maybe_abs = absfile(path, maybe_path)
- if maybe_abs is not None:
- return maybe_abs
- return None
- if not os.path.isabs(path):
- path = os.path.normpath(os.path.abspath(os.path.join(where, path)))
- if path is None or not os.path.exists(path):
- if where != os.getcwd():
- # try the cwd instead
- path = os.path.normpath(os.path.abspath(os.path.join(os.getcwd(),
- orig)))
- if path is None or not os.path.exists(path):
- return None
- if os.path.isdir(path):
- # might want an __init__.py from pacakge
- init = os.path.join(path,'__init__.py')
- if os.path.isfile(init):
- return init
- elif os.path.isfile(path):
- return path
- return None
-
-
-def anyp(predicate, iterable):
- for item in iterable:
- if predicate(item):
- return True
- return False
-
-
-def file_like(name):
- """A name is file-like if it is a path that exists, or it has a
- directory part, or it ends in .py, or it isn't a legal python
- identifier.
- """
- return (os.path.exists(name)
- or os.path.dirname(name)
- or name.endswith('.py')
- or not ident_re.match(os.path.splitext(name)[0]))
-
-
-def func_lineno(func):
- """Get the line number of a function. First looks for
- compat_co_firstlineno, then func_code.co_first_lineno.
- """
- try:
- return func.compat_co_firstlineno
- except AttributeError:
- try:
- return func.func_code.co_firstlineno
- except AttributeError:
- return -1
-
-
-def isclass(obj):
- """Is obj a class? Inspect's isclass is too liberal and returns True
- for objects that can't be subclasses of anything.
- """
- obj_type = type(obj)
- return obj_type in class_types or issubclass(obj_type, type)
-
-
-# backwards compat (issue #64)
-is_generator = isgenerator
-
-
-def ispackage(path):
- """
- Is this path a package directory?
-
- >>> ispackage('nose')
- True
- >>> ispackage('unit_tests')
- False
- >>> ispackage('nose/plugins')
- True
- >>> ispackage('nose/loader.py')
- False
- """
- if os.path.isdir(path):
- # at least the end of the path must be a legal python identifier
- # and __init__.py[co] must exist
- end = os.path.basename(path)
- if ident_re.match(end):
- for init in ('__init__.py', '__init__.pyc', '__init__.pyo'):
- if os.path.isfile(os.path.join(path, init)):
- return True
- if sys.platform.startswith('java') and \
- os.path.isfile(os.path.join(path, '__init__$py.class')):
- return True
- return False
-
-
-def isproperty(obj):
- """
- Is this a property?
-
- >>> class Foo:
- ... def got(self):
- ... return 2
- ... def get(self):
- ... return 1
- ... get = property(get)
-
- >>> isproperty(Foo.got)
- False
- >>> isproperty(Foo.get)
- True
- """
- return type(obj) == property
-
-
-def getfilename(package, relativeTo=None):
- """Find the python source file for a package, relative to a
- particular directory (defaults to current working directory if not
- given).
- """
- if relativeTo is None:
- relativeTo = os.getcwd()
- path = os.path.join(relativeTo, os.sep.join(package.split('.')))
- if os.path.exists(path + '/__init__.py'):
- return path
- filename = path + '.py'
- if os.path.exists(filename):
- return filename
- return None
-
-
-def getpackage(filename):
- """
- Find the full dotted package name for a given python source file
- name. Returns None if the file is not a python source file.
-
- >>> getpackage('foo.py')
- 'foo'
- >>> getpackage('biff/baf.py')
- 'baf'
- >>> getpackage('nose/util.py')
- 'nose.util'
-
- Works for directories too.
-
- >>> getpackage('nose')
- 'nose'
- >>> getpackage('nose/plugins')
- 'nose.plugins'
-
- And __init__ files stuck onto directories
-
- >>> getpackage('nose/plugins/__init__.py')
- 'nose.plugins'
-
- Absolute paths also work.
-
- >>> path = os.path.abspath(os.path.join('nose', 'plugins'))
- >>> getpackage(path)
- 'nose.plugins'
- """
- src_file = src(filename)
- if (os.path.isdir(src_file) or not src_file.endswith('.py')) and not ispackage(src_file):
- return None
- base, ext = os.path.splitext(os.path.basename(src_file))
- if base == '__init__':
- mod_parts = []
- else:
- mod_parts = [base]
- path, part = os.path.split(os.path.split(src_file)[0])
- while part:
- if ispackage(os.path.join(path, part)):
- mod_parts.append(part)
- else:
- break
- path, part = os.path.split(path)
- mod_parts.reverse()
- return '.'.join(mod_parts)
-
-
-def ln(label):
- """Draw a 70-char-wide divider, with label in the middle.
-
- >>> ln('hello there')
- '---------------------------- hello there -----------------------------'
- """
- label_len = len(label) + 2
- chunk = (70 - label_len) // 2
- out = '%s %s %s' % ('-' * chunk, label, '-' * chunk)
- pad = 70 - len(out)
- if pad > 0:
- out = out + ('-' * pad)
- return out
-
-
-def resolve_name(name, module=None):
- """Resolve a dotted name to a module and its parts. This is stolen
- wholesale from unittest.TestLoader.loadTestByName.
-
- >>> resolve_name('nose.util') #doctest: +ELLIPSIS
- <module 'nose.util' from...>
- >>> resolve_name('nose.util.resolve_name') #doctest: +ELLIPSIS
- <function resolve_name at...>
- """
- parts = name.split('.')
- parts_copy = parts[:]
- if module is None:
- while parts_copy:
- try:
- log.debug("__import__ %s", name)
- module = __import__('.'.join(parts_copy))
- break
- except ImportError:
- del parts_copy[-1]
- if not parts_copy:
- raise
- parts = parts[1:]
- obj = module
- log.debug("resolve: %s, %s, %s, %s", parts, name, obj, module)
- for part in parts:
- obj = getattr(obj, part)
- return obj
-
-
-def split_test_name(test):
- """Split a test name into a 3-tuple containing file, module, and callable
- names, any of which (but not all) may be blank.
-
- Test names are in the form:
-
- file_or_module:callable
-
- Either side of the : may be dotted. To change the splitting behavior, you
- can alter nose.util.split_test_re.
- """
- norm = os.path.normpath
- file_or_mod = test
- fn = None
- if not ':' in test:
- # only a file or mod part
- if file_like(test):
- return (norm(test), None, None)
- else:
- return (None, test, None)
-
- # could be path|mod:callable, or a : in the file path someplace
- head, tail = os.path.split(test)
- if not head:
- # this is a case like 'foo:bar' -- generally a module
- # name followed by a callable, but also may be a windows
- # drive letter followed by a path
- try:
- file_or_mod, fn = test.split(':')
- if file_like(fn):
- # must be a funny path
- file_or_mod, fn = test, None
- except ValueError:
- # more than one : in the test
- # this is a case like c:\some\path.py:a_test
- parts = test.split(':')
- if len(parts[0]) == 1:
- file_or_mod, fn = ':'.join(parts[:-1]), parts[-1]
- else:
- # nonsense like foo:bar:baz
- raise ValueError("Test name '%s' could not be parsed. Please "
- "format test names as path:callable or "
- "module:callable." % (test,))
- elif not tail:
- # this is a case like 'foo:bar/'
- # : must be part of the file path, so ignore it
- file_or_mod = test
- else:
- if ':' in tail:
- file_part, fn = tail.split(':')
- else:
- file_part = tail
- file_or_mod = os.sep.join([head, file_part])
- if file_or_mod:
- if file_like(file_or_mod):
- return (norm(file_or_mod), None, fn)
- else:
- return (None, file_or_mod, fn)
- else:
- return (None, None, fn)
-split_test_name.__test__ = False # do not collect
-
-
-def test_address(test):
- """Find the test address for a test, which may be a module, filename,
- class, method or function.
- """
- if hasattr(test, "address"):
- return test.address()
- # type-based polymorphism sucks in general, but I believe is
- # appropriate here
- t = type(test)
- file = module = call = None
- if t == types.ModuleType:
- file = getattr(test, '__file__', None)
- module = getattr(test, '__name__', None)
- return (src(file), module, call)
- if t == types.FunctionType or issubclass(t, type) or t == types.ClassType:
- module = getattr(test, '__module__', None)
- if module is not None:
- m = sys.modules[module]
- file = getattr(m, '__file__', None)
- if file is not None:
- file = os.path.abspath(file)
- call = getattr(test, '__name__', None)
- return (src(file), module, call)
- if t == types.MethodType:
- cls_adr = test_address(test.im_class)
- return (src(cls_adr[0]), cls_adr[1],
- "%s.%s" % (cls_adr[2], test.__name__))
- # handle unittest.TestCase instances
- if isinstance(test, unittest.TestCase):
- if (hasattr(test, '_FunctionTestCase__testFunc') # pre 2.7
- or hasattr(test, '_testFunc')): # 2.7
- # unittest FunctionTestCase
- try:
- return test_address(test._FunctionTestCase__testFunc)
- except AttributeError:
- return test_address(test._testFunc)
- # regular unittest.TestCase
- cls_adr = test_address(test.__class__)
- # 2.5 compat: __testMethodName changed to _testMethodName
- try:
- method_name = test._TestCase__testMethodName
- except AttributeError:
- method_name = test._testMethodName
- return (src(cls_adr[0]), cls_adr[1],
- "%s.%s" % (cls_adr[2], method_name))
- if (hasattr(test, '__class__') and
- test.__class__.__module__ not in ('__builtin__', 'builtins')):
- return test_address(test.__class__)
- raise TypeError("I don't know what %s is (%s)" % (test, t))
-test_address.__test__ = False # do not collect
-
-
-def try_run(obj, names):
- """Given a list of possible method names, try to run them with the
- provided object. Keep going until something works. Used to run
- setup/teardown methods for module, package, and function tests.
- """
- for name in names:
- func = getattr(obj, name, None)
- if func is not None:
- if type(obj) == types.ModuleType:
- # py.test compatibility
- if isinstance(func, types.FunctionType):
- args, varargs, varkw, defaults = \
- inspect.getargspec(func)
- else:
- # Not a function. If it's callable, call it anyway
- if hasattr(func, '__call__') and not inspect.ismethod(func):
- func = func.__call__
- try:
- args, varargs, varkw, defaults = \
- inspect.getargspec(func)
- args.pop(0) # pop the self off
- except TypeError:
- raise TypeError("Attribute %s of %r is not a python "
- "function. Only functions or callables"
- " may be used as fixtures." %
- (name, obj))
- if len(args):
- log.debug("call fixture %s.%s(%s)", obj, name, obj)
- return func(obj)
- log.debug("call fixture %s.%s", obj, name)
- return func()
-
-
-def src(filename):
- """Find the python source file for a .pyc, .pyo or $py.class file on
- jython. Returns the filename provided if it is not a python source
- file.
- """
- if filename is None:
- return filename
- if sys.platform.startswith('java') and filename.endswith('$py.class'):
- return '.'.join((filename[:-9], 'py'))
- base, ext = os.path.splitext(filename)
- if ext in ('.pyc', '.pyo', '.py'):
- return '.'.join((base, 'py'))
- return filename
-
-
-def regex_last_key(regex):
- """Sort key function factory that puts items that match a
- regular expression last.
-
- >>> from nose.config import Config
- >>> from nose.pyversion import sort_list
- >>> c = Config()
- >>> regex = c.testMatch
- >>> entries = ['.', '..', 'a_test', 'src', 'lib', 'test', 'foo.py']
- >>> sort_list(entries, regex_last_key(regex))
- >>> entries
- ['.', '..', 'foo.py', 'lib', 'src', 'a_test', 'test']
- """
- def k(obj):
- if regex.search(obj):
- return (1, obj)
- return (0, obj)
- return k
-
-
-def tolist(val):
- """Convert a value that may be a list or a (possibly comma-separated)
- string into a list. The exception: None is returned as None, not [None].
-
- >>> tolist(["one", "two"])
- ['one', 'two']
- >>> tolist("hello")
- ['hello']
- >>> tolist("separate,values, with, commas, spaces , are ,ok")
- ['separate', 'values', 'with', 'commas', 'spaces', 'are', 'ok']
- """
- if val is None:
- return None
- try:
- # might already be a list
- val.extend([])
- return val
- except AttributeError:
- pass
- # might be a string
- try:
- return re.split(r'\s*,\s*', val)
- except TypeError:
- # who knows...
- return list(val)
-
-
-class odict(dict):
- """Simple ordered dict implementation, based on:
-
- http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747
- """
- def __init__(self, *arg, **kw):
- self._keys = []
- super(odict, self).__init__(*arg, **kw)
-
- def __delitem__(self, key):
- super(odict, self).__delitem__(key)
- self._keys.remove(key)
-
- def __setitem__(self, key, item):
- super(odict, self).__setitem__(key, item)
- if key not in self._keys:
- self._keys.append(key)
-
- def __str__(self):
- return "{%s}" % ', '.join(["%r: %r" % (k, v) for k, v in self.items()])
-
- def clear(self):
- super(odict, self).clear()
- self._keys = []
-
- def copy(self):
- d = super(odict, self).copy()
- d._keys = self._keys[:]
- return d
-
- def items(self):
- return zip(self._keys, self.values())
-
- def keys(self):
- return self._keys[:]
-
- def setdefault(self, key, failobj=None):
- item = super(odict, self).setdefault(key, failobj)
- if key not in self._keys:
- self._keys.append(key)
- return item
-
- def update(self, dict):
- super(odict, self).update(dict)
- for key in dict.keys():
- if key not in self._keys:
- self._keys.append(key)
-
- def values(self):
- return map(self.get, self._keys)
-
-
-def transplant_func(func, module):
- """
- Make a function imported from module A appear as if it is located
- in module B.
-
- >>> from pprint import pprint
- >>> pprint.__module__
- 'pprint'
- >>> pp = transplant_func(pprint, __name__)
- >>> pp.__module__
- 'nose.util'
-
- The original function is not modified.
-
- >>> pprint.__module__
- 'pprint'
-
- Calling the transplanted function calls the original.
-
- >>> pp([1, 2])
- [1, 2]
- >>> pprint([1,2])
- [1, 2]
-
- """
- from nose.tools import make_decorator
- if isgenerator(func):
- def newfunc(*arg, **kw):
- for v in func(*arg, **kw):
- yield v
- else:
- def newfunc(*arg, **kw):
- return func(*arg, **kw)
-
- newfunc = make_decorator(func)(newfunc)
- newfunc.__module__ = module
- return newfunc
-
-
-def transplant_class(cls, module):
- """
- Make a class appear to reside in `module`, rather than the module in which
- it is actually defined.
-
- >>> from nose.failure import Failure
- >>> Failure.__module__
- 'nose.failure'
- >>> Nf = transplant_class(Failure, __name__)
- >>> Nf.__module__
- 'nose.util'
- >>> Nf.__name__
- 'Failure'
-
- """
- class C(cls):
- pass
- C.__module__ = module
- C.__name__ = cls.__name__
- return C
-
-
-def safe_str(val, encoding='utf-8'):
- try:
- return str(val)
- except UnicodeEncodeError:
- if isinstance(val, Exception):
- return ' '.join([safe_str(arg, encoding)
- for arg in val])
- return unicode(val).encode(encoding)
-
-
-def is_executable(file):
- if not os.path.exists(file):
- return False
- st = os.stat(file)
- return bool(st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH))
-
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
diff --git a/lib/spack/external/pyqver2.py b/lib/spack/external/pyqver2.py
index 4690239748..571e005524 100755
--- a/lib/spack/external/pyqver2.py
+++ b/lib/spack/external/pyqver2.py
@@ -57,7 +57,11 @@ StandardModules = {
"hmac": (2, 2),
"hotshot": (2, 2),
"HTMLParser": (2, 2),
- "importlib": (2, 7),
+# skip importlib until we can conditionally skip for pytest.
+# pytest tries to import this and catches the exception, but
+# the test will still fail.
+# TODO: can we excelude with a comment like '# flake: noqa?'
+# "importlib": (2, 7),
"inspect": (2, 1),
"io": (2, 6),
"itertools": (2, 3),
diff --git a/lib/spack/external/pytest.py b/lib/spack/external/pytest.py
new file mode 100644
index 0000000000..e376e417e8
--- /dev/null
+++ b/lib/spack/external/pytest.py
@@ -0,0 +1,28 @@
+# PYTHON_ARGCOMPLETE_OK
+"""
+pytest: unit and functional testing with Python.
+"""
+__all__ = [
+ 'main',
+ 'UsageError',
+ 'cmdline',
+ 'hookspec',
+ 'hookimpl',
+ '__version__',
+]
+
+if __name__ == '__main__': # if run as a script or by 'python -m pytest'
+ # we trigger the below "else" condition by the following import
+ import pytest
+ raise SystemExit(pytest.main())
+
+# else we are imported
+
+from _pytest.config import (
+ main, UsageError, _preloadplugins, cmdline,
+ hookspec, hookimpl
+)
+from _pytest import __version__
+
+_preloadplugins() # to populate pytest.* namespace so help(pytest) works
+
diff --git a/lib/spack/llnl/util/lang.py b/lib/spack/llnl/util/lang.py
index 637d18cd63..331cf2b3c5 100644
--- a/lib/spack/llnl/util/lang.py
+++ b/lib/spack/llnl/util/lang.py
@@ -362,3 +362,15 @@ class RequiredAttributeError(ValueError):
def __init__(self, message):
super(RequiredAttributeError, self).__init__(message)
+
+
+def duplicate_stream(original):
+ """Duplicates a stream at the os level.
+
+ :param stream original: original stream to be duplicated. Must have a
+ `fileno` callable attribute.
+
+ :return: duplicate of the original stream
+ :rtype: file like object
+ """
+ return os.fdopen(os.dup(original.fileno()))
diff --git a/lib/spack/llnl/util/tty/log.py b/lib/spack/llnl/util/tty/log.py
index 3d4972b3ae..b1d45214ab 100644
--- a/lib/spack/llnl/util/tty/log.py
+++ b/lib/spack/llnl/util/tty/log.py
@@ -30,6 +30,7 @@ import re
import select
import sys
+import llnl.util.lang as lang
import llnl.util.tty as tty
import llnl.util.tty.color as color
@@ -147,9 +148,7 @@ class log_output(object):
def __enter__(self):
# Sets a daemon that writes to file what it reads from a pipe
try:
- fwd_input_stream = os.fdopen(
- os.dup(self.input_stream.fileno())
- )
+ fwd_input_stream = lang.duplicate_stream(self.input_stream)
self.p = multiprocessing.Process(
target=self._spawn_writing_daemon,
args=(self.read, fwd_input_stream),
diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py
index 0646f5cb32..fcf140617e 100644
--- a/lib/spack/spack/__init__.py
+++ b/lib/spack/spack/__init__.py
@@ -41,6 +41,7 @@ spack_file = join_path(spack_root, "bin", "spack")
# spack directory hierarchy
lib_path = join_path(spack_root, "lib", "spack")
+external_path = join_path(lib_path, "external")
build_env_path = join_path(lib_path, "env")
module_path = join_path(lib_path, "spack")
platform_path = join_path(module_path, 'platforms')
@@ -196,3 +197,8 @@ from spack.package import \
__all__ += [
'install_dependency_symlinks', 'flatten_dependencies',
'DependencyConflictError', 'InstallError', 'ExternalPackageError']
+
+# Add default values for attributes that would otherwise be modified from
+# Spack main script
+debug = True
+spack_working_dir = None
diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py
index 501ace29b1..d83288860b 100644
--- a/lib/spack/spack/build_environment.py
+++ b/lib/spack/spack/build_environment.py
@@ -51,13 +51,14 @@ There are two parts to the build environment:
Skimming this module is a nice way to get acquainted with the types of
calls you can make from within the install() function.
"""
+import inspect
+import multiprocessing
import os
+import shutil
import sys
-import multiprocessing
import traceback
-import inspect
-import shutil
+import llnl.util.lang as lang
import llnl.util.tty as tty
import spack
import spack.store
@@ -579,7 +580,7 @@ def fork(pkg, function, dirty=False):
try:
# Forward sys.stdin to be able to activate / deactivate
# verbosity pressing a key at run-time
- input_stream = os.fdopen(os.dup(sys.stdin.fileno()))
+ input_stream = lang.duplicate_stream(sys.stdin)
p = multiprocessing.Process(
target=child_execution,
args=(child_connection, input_stream)
diff --git a/lib/spack/spack/cmd/__init__.py b/lib/spack/spack/cmd/__init__.py
index e712ba8e1d..bcc4524b4f 100644
--- a/lib/spack/spack/cmd/__init__.py
+++ b/lib/spack/spack/cmd/__init__.py
@@ -61,9 +61,19 @@ for file in os.listdir(command_path):
if file.endswith(".py") and not re.search(ignore_files, file):
cmd = re.sub(r'.py$', '', file)
commands.append(cmd)
+commands.append('test')
commands.sort()
+def remove_options(parser, *options):
+ """Remove some options from a parser."""
+ for option in options:
+ for action in parser._actions:
+ if vars(action)['option_strings'][0] == option:
+ parser._handle_conflict_resolve(None, [(option, action)])
+ break
+
+
def get_cmd_function_name(name):
return name.replace("-", "_")
diff --git a/lib/spack/spack/cmd/flake8.py b/lib/spack/spack/cmd/flake8.py
index a4c607a640..b8e28b0860 100644
--- a/lib/spack/spack/cmd/flake8.py
+++ b/lib/spack/spack/cmd/flake8.py
@@ -35,17 +35,18 @@ import spack
from spack.util.executable import *
description = "Runs source code style checks on Spack. Requires flake8."
-
-changed_files_path = os.path.join(spack.share_path, 'qa', 'changed_files')
-changed_files = Executable(changed_files_path)
flake8 = None
+include_untracked = True
-#
-# This is a dict that maps:
-# filename pattern ->
-# a flake8 exemption code ->
-# list of patterns, for which matching lines should have codes applied.
-#
+"""List of directories to exclude from checks."""
+exclude_directories = [spack.external_path]
+
+"""
+This is a dict that maps:
+ filename pattern ->
+ a flake8 exemption code ->
+ list of patterns, for which matching lines should have codes applied.
+"""
exemptions = {
# exemptions applied only to package.py files.
r'package.py$': {
@@ -77,6 +78,37 @@ exemptions = dict((re.compile(file_pattern),
for file_pattern, error_dict in exemptions.items())
+def changed_files():
+ """Get list of changed files in the Spack repository."""
+
+ git = which('git', required=True)
+
+ git_args = [
+ # Add changed files committed since branching off of develop
+ ['diff', '--name-only', '--diff-filter=ACMR', 'develop'],
+ # Add changed files that have been staged but not yet committed
+ ['diff', '--name-only', '--diff-filter=ACMR', '--cached'],
+ # Add changed files that are unstaged
+ ['diff', '--name-only', '--diff-filter=ACMR']]
+
+ # Add new files that are untracked
+ if include_untracked:
+ git_args.append(['ls-files', '--exclude-standard', '--other'])
+
+ excludes = [os.path.realpath(f) for f in exclude_directories]
+ changed = set()
+ for git_arg_list in git_args:
+ arg_list = git_arg_list + ['--', '*.py']
+
+ files = [f for f in git(*arg_list, output=str).split('\n') if f]
+ for f in files:
+ # don't look at files that are in the exclude locations
+ if any(os.path.realpath(f).startswith(e) for e in excludes):
+ continue
+ changed.add(f)
+ return sorted(changed)
+
+
def filter_file(source, dest, output=False):
"""Filter a single file through all the patterns in exemptions."""
with open(source) as infile:
@@ -115,13 +147,17 @@ def setup_parser(subparser):
'-r', '--root-relative', action='store_true', default=False,
help="print root-relative paths (default is cwd-relative)")
subparser.add_argument(
+ '-U', '--no-untracked', dest='untracked', action='store_false',
+ default=True, help="Exclude untracked files from checks.")
+ subparser.add_argument(
'files', nargs=argparse.REMAINDER, help="specific files to check")
def flake8(parser, args):
# Just use this to check for flake8 -- we actually execute it with Popen.
- global flake8
+ global flake8, include_untracked
flake8 = which('flake8', required=True)
+ include_untracked = args.untracked
temp = tempfile.mkdtemp()
try:
@@ -135,9 +171,7 @@ def flake8(parser, args):
with working_dir(spack.prefix):
if not file_list:
- file_list = changed_files('*.py', output=str)
- file_list = [x for x in file_list.split('\n') if x]
-
+ file_list = changed_files()
shutil.copy('.flake8', os.path.join(temp, '.flake8'))
print '======================================================='
diff --git a/lib/spack/spack/cmd/test.py b/lib/spack/spack/cmd/test.py
index 52c2a06778..2e0ab8b49e 100644
--- a/lib/spack/spack/cmd/test.py
+++ b/lib/spack/spack/cmd/test.py
@@ -22,71 +22,86 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
+import sys
import os
+import re
+import argparse
+import pytest
+from StringIO import StringIO
-from llnl.util.filesystem import join_path, mkdirp
+from llnl.util.filesystem import *
from llnl.util.tty.colify import colify
import spack
-import spack.test
-from spack.fetch_strategy import FetchError
-description = "Run unit tests"
+description = "A thin wrapper around the pytest command."
def setup_parser(subparser):
subparser.add_argument(
- 'names', nargs='*', help="Names of tests to run.")
+ '-H', '--pytest-help', action='store_true', default=False,
+ help="print full pytest help message, showing advanced options.")
+
+ list_group = subparser.add_mutually_exclusive_group()
+ list_group.add_argument(
+ '-l', '--list', action='store_true', default=False,
+ help="list basic test names.")
+ list_group.add_argument(
+ '-L', '--long-list', action='store_true', default=False,
+ help="list the entire hierarchy of tests.")
subparser.add_argument(
- '-l', '--list', action='store_true', dest='list',
- help="Show available tests")
- subparser.add_argument(
- '--createXmlOutput', action='store_true', dest='createXmlOutput',
- help="Create JUnit XML from test results")
- subparser.add_argument(
- '--xmlOutputDir', dest='xmlOutputDir',
- help="Nose creates XML files in this directory")
- subparser.add_argument(
- '-v', '--verbose', action='store_true', dest='verbose',
- help="verbose output")
-
-
-class MockCache(object):
-
- def store(self, copyCmd, relativeDst):
- pass
-
- def fetcher(self, targetPath, digest, **kwargs):
- return MockCacheFetcher()
-
-
-class MockCacheFetcher(object):
-
- def set_stage(self, stage):
- pass
-
- def fetch(self):
- raise FetchError("Mock cache always fails for tests")
-
- def __str__(self):
- return "[mock fetcher]"
-
+ 'tests', nargs=argparse.REMAINDER,
+ help="list of tests to run (will be passed to pytest -k).")
+
+
+def do_list(args, unknown_args):
+ """Print a lists of tests than what pytest offers."""
+ # Run test collection and get the tree out.
+ old_output = sys.stdout
+ try:
+ sys.stdout = output = StringIO()
+ pytest.main(['--collect-only'])
+ finally:
+ sys.stdout = old_output
+
+ # put the output in a more readable tree format.
+ lines = output.getvalue().split('\n')
+ output_lines = []
+ for line in lines:
+ match = re.match(r"(\s*)<([^ ]*) '([^']*)'", line)
+ if not match:
+ continue
+ indent, nodetype, name = match.groups()
+
+ # only print top-level for short list
+ if args.list:
+ if not indent:
+ output_lines.append(
+ os.path.basename(name).replace('.py', ''))
+ else:
+ print indent + name
-def test(parser, args):
if args.list:
- print "Available tests:"
- colify(spack.test.list_tests(), indent=2)
-
- else:
- if not args.createXmlOutput:
- outputDir = None
+ colify(output_lines)
+
+
+def test(parser, args, unknown_args):
+ if args.pytest_help:
+ # make the pytest.main help output more accurate
+ sys.argv[0] = 'spack test'
+ pytest.main(['-h'])
+ return
+
+ # pytest.ini lives in the root of the sapck repository.
+ with working_dir(spack.prefix):
+ # --list and --long-list print the test output better.
+ if args.list or args.long_list:
+ do_list(args, unknown_args)
+ return
+
+ if args.tests and not any(arg.startswith('-') for arg in args.tests):
+ # Allow keyword search without -k if no options are specified
+ return pytest.main(['-k'] + args.tests)
else:
- if not args.xmlOutputDir:
- outputDir = join_path(os.getcwd(), "test-output")
- else:
- outputDir = os.path.abspath(args.xmlOutputDir)
-
- if not os.path.exists(outputDir):
- mkdirp(outputDir)
- spack.fetch_cache = MockCache()
- spack.test.run(args.names, outputDir, args.verbose)
+ # Just run the pytest command.
+ return pytest.main(unknown_args + args.tests)
diff --git a/lib/spack/spack/repository.py b/lib/spack/spack/repository.py
index 94b79accdb..d77700c01f 100644
--- a/lib/spack/spack/repository.py
+++ b/lib/spack/spack/repository.py
@@ -133,7 +133,7 @@ class RepoPath(object):
" spack repo rm %s" % root)
def swap(self, other):
- """Convenience function to make swapping repostiories easier.
+ """Convenience function to make swapping repositories easier.
This is currently used by mock tests.
TODO: Maybe there is a cleaner way.
diff --git a/lib/spack/spack/test/__init__.py b/lib/spack/spack/test/__init__.py
index 79122cc1de..ed1ec23bca 100644
--- a/lib/spack/spack/test/__init__.py
+++ b/lib/spack/spack/test/__init__.py
@@ -22,132 +22,3 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-import sys
-import os
-
-import llnl.util.tty as tty
-import nose
-import spack
-import spack.architecture
-from llnl.util.filesystem import join_path
-from llnl.util.tty.colify import colify
-from spack.test.tally_plugin import Tally
-from spack.platforms.test import Test as TestPlatform
-"""Names of tests to be included in Spack's test suite"""
-
-# All the tests Spack knows about.
-# Keep these one per line so that it's easy to see changes in diffs.
-test_names = [
- 'architecture',
- 'build_system_guess',
- 'cc',
- 'cmd.find',
- 'cmd.module',
- 'cmd.install',
- 'cmd.uninstall',
- 'concretize',
- 'concretize_preferences',
- 'config',
- 'database',
- 'directory_layout',
- 'environment',
- 'file_cache',
- 'git_fetch',
- 'hg_fetch',
- 'install',
- 'library_list',
- 'link_tree',
- 'lock',
- 'make_executable',
- 'mirror',
- 'modules',
- 'multimethod',
- 'namespace_trie',
- 'optional_deps',
- 'package_sanity',
- 'packages',
- 'pattern',
- 'python_version',
- 'sbang',
- 'spec_dag',
- 'spec_semantics',
- 'spec_syntax',
- 'spec_yaml',
- 'stage',
- 'svn_fetch',
- 'url_extrapolate',
- 'url_parse',
- 'url_substitution',
- 'versions',
- 'provider_index',
- 'spack_yaml',
- # This test needs to be last until global compiler cache is fixed.
- 'cmd.test_compiler_cmd',
-]
-
-
-def setup_tests():
- """Prepare the environment for the Spack tests to be run."""
- test_platform = TestPlatform()
- spack.architecture.real_platform = spack.architecture.platform
- spack.architecture.platform = lambda: test_platform
-
-
-def list_tests():
- """Return names of all tests that can be run for Spack."""
- return test_names
-
-
-def run(names, outputDir, verbose=False):
- """Run tests with the supplied names. Names should be a list. If
- it's empty, run ALL of Spack's tests."""
- # Print output to stdout if verbose is 1.
- if verbose:
- os.environ['NOSE_NOCAPTURE'] = '1'
-
- if not names:
- names = test_names
- else:
- for test in names:
- if test not in test_names:
- tty.error("%s is not a valid spack test name." % test,
- "Valid names are:")
- colify(sorted(test_names), indent=4)
- sys.exit(1)
-
- tally = Tally()
-
- modules = ['spack.test.' + test for test in names]
- runOpts = ["--with-%s" % spack.test.tally_plugin.Tally.name]
-
- if outputDir:
- xmlOutputFname = "unittests-{0}.xml".format(test)
- xmlOutputPath = join_path(outputDir, xmlOutputFname)
- runOpts += ["--with-xunit",
- "--xunit-file={0}".format(xmlOutputPath)]
- argv = [""] + runOpts + modules
-
- setup_tests()
- nose.run(argv=argv, addplugins=[tally])
-
- succeeded = not tally.failCount and not tally.errorCount
- tty.msg(
- "Tests Complete.",
- "%5d tests run" % tally.numberOfTestsRun,
- "%5d failures" % tally.failCount,
- "%5d errors" % tally.errorCount
- )
-
- if tally.fail_list:
- items = [x for x in tally.fail_list]
- tty.msg('List of failing tests:', *items)
-
- if tally.error_list:
- items = [x for x in tally.error_list]
- tty.msg('List of tests with errors:', *items)
-
- if succeeded:
- tty.info("OK", format='g')
- else:
- tty.info("FAIL", format='r')
- sys.exit(1)
diff --git a/lib/spack/spack/test/architecture.py b/lib/spack/spack/test/architecture.py
index 0ce583c6ea..fb4113361c 100644
--- a/lib/spack/spack/test/architecture.py
+++ b/lib/spack/spack/test/architecture.py
@@ -30,48 +30,33 @@ import os
import platform as py_platform
import spack
import spack.architecture
-from spack.spec import *
+from spack.spec import Spec
from spack.platforms.cray import Cray
from spack.platforms.linux import Linux
from spack.platforms.bgq import Bgq
from spack.platforms.darwin import Darwin
-from spack.test.mock_packages_test import *
+def test_dict_functions_for_architecture():
+ arch = spack.architecture.Arch()
+ arch.platform = spack.architecture.platform()
+ arch.platform_os = arch.platform.operating_system('default_os')
+ arch.target = arch.platform.target('default_target')
-class ArchitectureTest(MockPackagesTest):
+ new_arch = spack.architecture.Arch.from_dict(arch.to_dict())
- def setUp(self):
- super(ArchitectureTest, self).setUp()
- self.platform = spack.architecture.platform()
+ assert arch == new_arch
+ assert isinstance(arch, spack.architecture.Arch)
+ assert isinstance(arch.platform, spack.architecture.Platform)
+ assert isinstance(arch.platform_os, spack.architecture.OperatingSystem)
+ assert isinstance(arch.target, spack.architecture.Target)
+ assert isinstance(new_arch, spack.architecture.Arch)
+ assert isinstance(new_arch.platform, spack.architecture.Platform)
+ assert isinstance(new_arch.platform_os, spack.architecture.OperatingSystem)
+ assert isinstance(new_arch.target, spack.architecture.Target)
- def tearDown(self):
- super(ArchitectureTest, self).tearDown()
- def test_dict_functions_for_architecture(self):
- arch = spack.architecture.Arch()
- arch.platform = spack.architecture.platform()
- arch.platform_os = arch.platform.operating_system('default_os')
- arch.target = arch.platform.target('default_target')
-
- new_arch = spack.architecture.Arch.from_dict(arch.to_dict())
- self.assertEqual(arch, new_arch)
-
- self.assertTrue(isinstance(arch, spack.architecture.Arch))
- self.assertTrue(isinstance(arch.platform, spack.architecture.Platform))
- self.assertTrue(isinstance(arch.platform_os,
- spack.architecture.OperatingSystem))
- self.assertTrue(isinstance(arch.target,
- spack.architecture.Target))
- self.assertTrue(isinstance(new_arch, spack.architecture.Arch))
- self.assertTrue(isinstance(new_arch.platform,
- spack.architecture.Platform))
- self.assertTrue(isinstance(new_arch.platform_os,
- spack.architecture.OperatingSystem))
- self.assertTrue(isinstance(new_arch.target,
- spack.architecture.Target))
-
- def test_platform(self):
+def test_platform():
output_platform_class = spack.architecture.real_platform()
if os.path.exists('/opt/cray/craype'):
my_platform_class = Cray()
@@ -82,85 +67,95 @@ class ArchitectureTest(MockPackagesTest):
elif 'Darwin' in py_platform.system():
my_platform_class = Darwin()
- self.assertEqual(str(output_platform_class), str(my_platform_class))
-
- def test_boolness(self):
- # Make sure architecture reports that it's False when nothing's set.
- arch = spack.architecture.Arch()
- self.assertFalse(arch)
-
- # Dummy architecture parts
- plat = spack.architecture.platform()
- plat_os = plat.operating_system('default_os')
- plat_target = plat.target('default_target')
-
- # Make sure architecture reports that it's True when anything is set.
- arch = spack.architecture.Arch()
- arch.platform = plat
- self.assertTrue(arch)
-
- arch = spack.architecture.Arch()
- arch.platform_os = plat_os
- self.assertTrue(arch)
-
- arch = spack.architecture.Arch()
- arch.target = plat_target
- self.assertTrue(arch)
-
- def test_user_front_end_input(self):
- """Test when user inputs just frontend that both the frontend target
- and frontend operating system match
- """
- frontend_os = str(self.platform.operating_system("frontend"))
- frontend_target = str(self.platform.target("frontend"))
-
- frontend_spec = Spec("libelf os=frontend target=frontend")
- frontend_spec.concretize()
-
- self.assertEqual(frontend_os, frontend_spec.architecture.platform_os)
- self.assertEqual(frontend_target, frontend_spec.architecture.target)
-
- def test_user_back_end_input(self):
- """Test when user inputs backend that both the backend target and
- backend operating system match
- """
- backend_os = str(self.platform.operating_system("backend"))
- backend_target = str(self.platform.target("backend"))
-
- backend_spec = Spec("libelf os=backend target=backend")
- backend_spec.concretize()
-
- self.assertEqual(backend_os, backend_spec.architecture.platform_os)
- self.assertEqual(backend_target, backend_spec.architecture.target)
-
- def test_user_defaults(self):
- default_os = str(self.platform.operating_system("default_os"))
- default_target = str(self.platform.target("default_target"))
-
- default_spec = Spec("libelf") # default is no args
- default_spec.concretize()
-
- self.assertEqual(default_os, default_spec.architecture.platform_os)
- self.assertEqual(default_target, default_spec.architecture.target)
-
- def test_user_input_combination(self):
- os_list = self.platform.operating_sys.keys()
- target_list = self.platform.targets.keys()
- additional = ["fe", "be", "frontend", "backend"]
-
- os_list.extend(additional)
- target_list.extend(additional)
-
- combinations = itertools.product(os_list, target_list)
- results = []
- for arch in combinations:
- o, t = arch
- spec = Spec("libelf os=%s target=%s" % (o, t))
- spec.concretize()
- results.append(spec.architecture.platform_os ==
- str(self.platform.operating_system(o)))
- results.append(spec.architecture.target ==
- str(self.platform.target(t)))
- res = all(results)
-
- self.assertTrue(res)
+ assert str(output_platform_class) == str(my_platform_class)
+
+
+def test_boolness():
+ # Make sure architecture reports that it's False when nothing's set.
+ arch = spack.architecture.Arch()
+ assert not arch
+
+ # Dummy architecture parts
+ plat = spack.architecture.platform()
+ plat_os = plat.operating_system('default_os')
+ plat_target = plat.target('default_target')
+
+ # Make sure architecture reports that it's True when anything is set.
+ arch = spack.architecture.Arch()
+ arch.platform = plat
+ assert arch
+
+ arch = spack.architecture.Arch()
+ arch.platform_os = plat_os
+ assert arch
+
+ arch = spack.architecture.Arch()
+ arch.target = plat_target
+ assert arch
+
+
+def test_user_front_end_input(config):
+ """Test when user inputs just frontend that both the frontend target
+ and frontend operating system match
+ """
+ platform = spack.architecture.platform()
+ frontend_os = str(platform.operating_system('frontend'))
+ frontend_target = str(platform.target('frontend'))
+
+ frontend_spec = Spec('libelf os=frontend target=frontend')
+ frontend_spec.concretize()
+
+ assert frontend_os == frontend_spec.architecture.platform_os
+ assert frontend_target == frontend_spec.architecture.target
+
+
+def test_user_back_end_input(config):
+ """Test when user inputs backend that both the backend target and
+ backend operating system match
+ """
+ platform = spack.architecture.platform()
+ backend_os = str(platform.operating_system("backend"))
+ backend_target = str(platform.target("backend"))
+
+ backend_spec = Spec("libelf os=backend target=backend")
+ backend_spec.concretize()
+
+ assert backend_os == backend_spec.architecture.platform_os
+ assert backend_target == backend_spec.architecture.target
+
+
+def test_user_defaults(config):
+ platform = spack.architecture.platform()
+ default_os = str(platform.operating_system("default_os"))
+ default_target = str(platform.target("default_target"))
+
+ default_spec = Spec("libelf") # default is no args
+ default_spec.concretize()
+
+ assert default_os == default_spec.architecture.platform_os
+ assert default_target == default_spec.architecture.target
+
+
+def test_user_input_combination(config):
+ platform = spack.architecture.platform()
+ os_list = platform.operating_sys.keys()
+ target_list = platform.targets.keys()
+ additional = ["fe", "be", "frontend", "backend"]
+
+ os_list.extend(additional)
+ target_list.extend(additional)
+
+ combinations = itertools.product(os_list, target_list)
+ results = []
+ for arch in combinations:
+ o, t = arch
+ spec = Spec("libelf os=%s target=%s" % (o, t))
+ spec.concretize()
+ results.append(
+ spec.architecture.platform_os == str(platform.operating_system(o))
+ )
+ results.append(
+ spec.architecture.target == str(platform.target(t))
+ )
+ res = all(results)
+ assert res
diff --git a/lib/spack/spack/test/build_system_guess.py b/lib/spack/spack/test/build_system_guess.py
index e728a47cf4..97a9d67b47 100644
--- a/lib/spack/spack/test/build_system_guess.py
+++ b/lib/spack/spack/test/build_system_guess.py
@@ -22,60 +22,43 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-import os
-import shutil
-import tempfile
-import unittest
-from llnl.util.filesystem import *
-from spack.cmd.create import BuildSystemGuesser
-from spack.stage import Stage
-from spack.test.mock_packages_test import *
-from spack.util.executable import which
+import pytest
+import spack.cmd.create
+import spack.util.executable
+import spack.stage
-class InstallTest(unittest.TestCase):
- """Tests the build system guesser in spack create"""
+@pytest.fixture(
+ scope='function',
+ params=[
+ ('configure', 'autotools'),
+ ('CMakeLists.txt', 'cmake'),
+ ('SConstruct', 'scons'),
+ ('setup.py', 'python'),
+ ('NAMESPACE', 'R'),
+ ('foobar', 'unknown')
+ ]
+)
+def url_and_build_system(request, tmpdir):
+ """Sets up the resources to be pulled by the stage with
+ the appropriate file name and returns their url along with
+ the correct build-system guess
+ """
+ tar = spack.util.executable.which('tar')
+ orig_dir = tmpdir.chdir()
+ filename, system = request.param
+ tmpdir.ensure('archive', filename)
+ tar('czf', 'archive.tar.gz', 'archive')
+ url = 'file://' + str(tmpdir.join('archive.tar.gz'))
+ yield url, system
+ orig_dir.chdir()
- def setUp(self):
- self.tar = which('tar')
- self.tmpdir = tempfile.mkdtemp()
- self.orig_dir = os.getcwd()
- os.chdir(self.tmpdir)
- self.stage = None
- def tearDown(self):
- shutil.rmtree(self.tmpdir, ignore_errors=True)
- os.chdir(self.orig_dir)
-
- def check_archive(self, filename, system):
- mkdirp('archive')
- touch(join_path('archive', filename))
- self.tar('czf', 'archive.tar.gz', 'archive')
-
- url = 'file://' + join_path(os.getcwd(), 'archive.tar.gz')
- print url
- with Stage(url) as stage:
- stage.fetch()
-
- guesser = BuildSystemGuesser()
- guesser(stage, url)
- self.assertEqual(system, guesser.build_system)
-
- def test_autotools(self):
- self.check_archive('configure', 'autotools')
-
- def test_cmake(self):
- self.check_archive('CMakeLists.txt', 'cmake')
-
- def test_scons(self):
- self.check_archive('SConstruct', 'scons')
-
- def test_python(self):
- self.check_archive('setup.py', 'python')
-
- def test_R(self):
- self.check_archive('NAMESPACE', 'R')
-
- def test_unknown(self):
- self.check_archive('foobar', 'unknown')
+def test_build_systems(url_and_build_system):
+ url, build_system = url_and_build_system
+ with spack.stage.Stage(url) as stage:
+ stage.fetch()
+ guesser = spack.cmd.create.BuildSystemGuesser()
+ guesser(stage, url)
+ assert build_system == guesser.build_system
diff --git a/lib/spack/spack/test/cmd/find.py b/lib/spack/spack/test/cmd/find.py
index 4788da8ec6..dcd123d46e 100644
--- a/lib/spack/spack/test/cmd/find.py
+++ b/lib/spack/spack/test/cmd/find.py
@@ -22,33 +22,32 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-
-
import spack.cmd.find
-import unittest
-
from spack.util.pattern import Bunch
-class FindTest(unittest.TestCase):
-
- def test_query_arguments(self):
- query_arguments = spack.cmd.find.query_arguments
- # Default arguments
- args = Bunch(only_missing=False, missing=False,
- unknown=False, explicit=False, implicit=False)
- q_args = query_arguments(args)
- self.assertTrue('installed' in q_args)
- self.assertTrue('known' in q_args)
- self.assertTrue('explicit' in q_args)
- self.assertEqual(q_args['installed'], True)
- self.assertEqual(q_args['known'], any)
- self.assertEqual(q_args['explicit'], any)
- # Check that explicit works correctly
- args.explicit = True
- q_args = query_arguments(args)
- self.assertEqual(q_args['explicit'], True)
- args.explicit = False
- args.implicit = True
- q_args = query_arguments(args)
- self.assertEqual(q_args['explicit'], False)
+def test_query_arguments():
+ query_arguments = spack.cmd.find.query_arguments
+ # Default arguments
+ args = Bunch(
+ only_missing=False,
+ missing=False,
+ unknown=False,
+ explicit=False,
+ implicit=False
+ )
+ q_args = query_arguments(args)
+ assert 'installed' in q_args
+ assert 'known' in q_args
+ assert 'explicit' in q_args
+ assert q_args['installed'] is True
+ assert q_args['known'] is any
+ assert q_args['explicit'] is any
+ # Check that explicit works correctly
+ args.explicit = True
+ q_args = query_arguments(args)
+ assert q_args['explicit'] is True
+ args.explicit = False
+ args.implicit = True
+ q_args = query_arguments(args)
+ assert q_args['explicit'] is False
diff --git a/lib/spack/spack/test/cmd/module.py b/lib/spack/spack/test/cmd/module.py
index 39f9c5649f..03ce1ef206 100644
--- a/lib/spack/spack/test/cmd/module.py
+++ b/lib/spack/spack/test/cmd/module.py
@@ -25,67 +25,82 @@
import argparse
import os.path
+import pytest
import spack.cmd.module as module
import spack.modules as modules
-import spack.test.mock_database
-class TestModule(spack.test.mock_database.MockDatabase):
+def _get_module_files(args):
+ return [modules.module_types[args.module_type](spec).file_name
+ for spec in args.specs()]
- def _get_module_files(self, args):
- return [modules.module_types[args.module_type](spec).file_name
- for spec in args.specs()]
- def test_module_common_operations(self):
- parser = argparse.ArgumentParser()
- module.setup_parser(parser)
+@pytest.fixture(scope='module')
+def parser():
+ """Returns the parser for the module command"""
+ parser = argparse.ArgumentParser()
+ module.setup_parser(parser)
+ return parser
- # Try to remove a non existing module [tcl]
- args = parser.parse_args(['rm', 'doesnotexist'])
- self.assertRaises(SystemExit, module.module, parser, args)
- # Remove existing modules [tcl]
- args = parser.parse_args(['rm', '-y', 'mpileaks'])
- module_files = self._get_module_files(args)
- for item in module_files:
- self.assertTrue(os.path.exists(item))
- module.module(parser, args)
- for item in module_files:
- self.assertFalse(os.path.exists(item))
+@pytest.fixture(
+ params=[
+ ['rm', 'doesnotexist'], # Try to remove a non existing module [tcl]
+ ['find', 'mpileaks'], # Try to find a module with multiple matches
+ ['find', 'doesnotexist'], # Try to find a module with no matches
+ ]
+)
+def failure_args(request):
+ """A list of arguments that will cause a failure"""
+ return request.param
+
+
+# TODO : test the --delete-tree option
+# TODO : this requires having a separate directory for test modules
+# TODO : add tests for loads and find to check the prompt format
- # Add them back [tcl]
- args = parser.parse_args(['refresh', '-y', 'mpileaks'])
+
+def test_exit_with_failure(database, parser, failure_args):
+ args = parser.parse_args(failure_args)
+ with pytest.raises(SystemExit):
module.module(parser, args)
- for item in module_files:
- self.assertTrue(os.path.exists(item))
- # TODO : test the --delete-tree option
- # TODO : this requires having a separate directory for test modules
- # Try to find a module with multiple matches
- args = parser.parse_args(['find', 'mpileaks'])
- self.assertRaises(SystemExit, module.module, parser, args)
+def test_remove_and_add_tcl(database, parser):
+ # Remove existing modules [tcl]
+ args = parser.parse_args(['rm', '-y', 'mpileaks'])
+ module_files = _get_module_files(args)
+ for item in module_files:
+ assert os.path.exists(item)
+ module.module(parser, args)
+ for item in module_files:
+ assert not os.path.exists(item)
- # Try to find a module with no matches
- args = parser.parse_args(['find', 'doesnotexist'])
- self.assertRaises(SystemExit, module.module, parser, args)
+ # Add them back [tcl]
+ args = parser.parse_args(['refresh', '-y', 'mpileaks'])
+ module.module(parser, args)
+ for item in module_files:
+ assert os.path.exists(item)
- # Try to find a module
- args = parser.parse_args(['find', 'libelf'])
- module.module(parser, args)
- # Remove existing modules [dotkit]
- args = parser.parse_args(['rm', '-y', '-m', 'dotkit', 'mpileaks'])
- module_files = self._get_module_files(args)
- for item in module_files:
- self.assertTrue(os.path.exists(item))
- module.module(parser, args)
- for item in module_files:
- self.assertFalse(os.path.exists(item))
+def test_find(database, parser):
+ # Try to find a module
+ args = parser.parse_args(['find', 'libelf'])
+ module.module(parser, args)
- # Add them back [dotkit]
- args = parser.parse_args(['refresh', '-y', '-m', 'dotkit', 'mpileaks'])
- module.module(parser, args)
- for item in module_files:
- self.assertTrue(os.path.exists(item))
- # TODO : add tests for loads and find to check the prompt format
+
+def test_remove_and_add_dotkit(database, parser):
+ # Remove existing modules [dotkit]
+ args = parser.parse_args(['rm', '-y', '-m', 'dotkit', 'mpileaks'])
+ module_files = _get_module_files(args)
+ for item in module_files:
+ assert os.path.exists(item)
+ module.module(parser, args)
+ for item in module_files:
+ assert not os.path.exists(item)
+
+ # Add them back [dotkit]
+ args = parser.parse_args(['refresh', '-y', '-m', 'dotkit', 'mpileaks'])
+ module.module(parser, args)
+ for item in module_files:
+ assert os.path.exists(item)
diff --git a/lib/spack/spack/test/cmd/test_compiler_cmd.py b/lib/spack/spack/test/cmd/test_compiler_cmd.py
index f6e7cdeb64..647404e6da 100644
--- a/lib/spack/spack/test/cmd/test_compiler_cmd.py
+++ b/lib/spack/spack/test/cmd/test_compiler_cmd.py
@@ -22,42 +22,30 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-import os
-import shutil
-from tempfile import mkdtemp
+import pytest
+import llnl.util.filesystem
-from llnl.util.filesystem import set_executable, mkdirp
-
-import spack.spec
import spack.cmd.compiler
import spack.compilers
+import spack.spec
+import spack.util.pattern
from spack.version import Version
-from spack.test.mock_packages_test import *
test_version = '4.5-spacktest'
-class MockArgs(object):
-
- def __init__(self, add_paths=[], scope=None, compiler_spec=None, all=None):
- self.add_paths = add_paths
- self.scope = scope
- self.compiler_spec = compiler_spec
- self.all = all
+@pytest.fixture()
+def mock_compiler_dir(tmpdir):
+ """Return a directory containing a fake, but detectable compiler."""
+ tmpdir.ensure('bin', dir=True)
+ bin_dir = tmpdir.join('bin')
-def make_mock_compiler():
- """Make a directory containing a fake, but detectable compiler."""
- mock_compiler_dir = mkdtemp()
- bin_dir = os.path.join(mock_compiler_dir, 'bin')
- mkdirp(bin_dir)
+ gcc_path = bin_dir.join('gcc')
+ gxx_path = bin_dir.join('g++')
+ gfortran_path = bin_dir.join('gfortran')
- gcc_path = os.path.join(bin_dir, 'gcc')
- gxx_path = os.path.join(bin_dir, 'g++')
- gfortran_path = os.path.join(bin_dir, 'gfortran')
-
- with open(gcc_path, 'w') as f:
- f.write("""\
+ gcc_path.write("""\
#!/bin/sh
for arg in "$@"; do
@@ -68,39 +56,39 @@ done
""" % test_version)
# Create some mock compilers in the temporary directory
- set_executable(gcc_path)
- shutil.copy(gcc_path, gxx_path)
- shutil.copy(gcc_path, gfortran_path)
+ llnl.util.filesystem.set_executable(str(gcc_path))
+ gcc_path.copy(gxx_path, mode=True)
+ gcc_path.copy(gfortran_path, mode=True)
- return mock_compiler_dir
+ return str(tmpdir)
-class CompilerCmdTest(MockPackagesTest):
- """ Test compiler commands for add and remove """
+@pytest.mark.usefixtures('config', 'builtin_mock')
+class TestCompilerCommand(object):
def test_compiler_remove(self):
- args = MockArgs(all=True, compiler_spec='gcc@4.5.0')
+ args = spack.util.pattern.Bunch(
+ all=True, compiler_spec='gcc@4.5.0', add_paths=[], scope=None
+ )
spack.cmd.compiler.compiler_remove(args)
compilers = spack.compilers.all_compilers()
- self.assertTrue(spack.spec.CompilerSpec("gcc@4.5.0") not in compilers)
+ assert spack.spec.CompilerSpec("gcc@4.5.0") not in compilers
- def test_compiler_add(self):
- # compilers available by default.
+ def test_compiler_add(self, mock_compiler_dir):
+ # Compilers available by default.
old_compilers = set(spack.compilers.all_compilers())
- # add our new compiler and find again.
- compiler_dir = make_mock_compiler()
-
- try:
- args = MockArgs(add_paths=[compiler_dir])
- spack.cmd.compiler.compiler_find(args)
-
- # ensure new compiler is in there
- new_compilers = set(spack.compilers.all_compilers())
- new_compiler = new_compilers - old_compilers
- self.assertTrue(new_compiler)
- self.assertTrue(new_compiler.pop().version ==
- Version(test_version))
-
- finally:
- shutil.rmtree(compiler_dir, ignore_errors=True)
+ args = spack.util.pattern.Bunch(
+ all=None,
+ compiler_spec=None,
+ add_paths=[mock_compiler_dir],
+ scope=None
+ )
+ spack.cmd.compiler.compiler_find(args)
+
+ # Ensure new compiler is in there
+ new_compilers = set(spack.compilers.all_compilers())
+ new_compiler = new_compilers - old_compilers
+ assert new_compiler
+ c = new_compiler.pop()
+ assert c.version == Version(test_version)
diff --git a/lib/spack/spack/test/cmd/uninstall.py b/lib/spack/spack/test/cmd/uninstall.py
index 6a86a1543f..bfbb9b8148 100644
--- a/lib/spack/spack/test/cmd/uninstall.py
+++ b/lib/spack/spack/test/cmd/uninstall.py
@@ -22,9 +22,9 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-import spack.test.mock_database
+import pytest
import spack.store
-from spack.cmd.uninstall import uninstall
+import spack.cmd.uninstall
class MockArgs(object):
@@ -37,27 +37,28 @@ class MockArgs(object):
self.yes_to_all = True
-class TestUninstall(spack.test.mock_database.MockDatabase):
-
- def test_uninstall(self):
- parser = None
- # Multiple matches
- args = MockArgs(['mpileaks'])
- self.assertRaises(SystemExit, uninstall, parser, args)
- # Installed dependents
- args = MockArgs(['libelf'])
- self.assertRaises(SystemExit, uninstall, parser, args)
- # Recursive uninstall
- args = MockArgs(['callpath'], all=True, dependents=True)
+def test_uninstall(database):
+ parser = None
+ uninstall = spack.cmd.uninstall.uninstall
+ # Multiple matches
+ args = MockArgs(['mpileaks'])
+ with pytest.raises(SystemExit):
+ uninstall(parser, args)
+ # Installed dependents
+ args = MockArgs(['libelf'])
+ with pytest.raises(SystemExit):
uninstall(parser, args)
+ # Recursive uninstall
+ args = MockArgs(['callpath'], all=True, dependents=True)
+ uninstall(parser, args)
- all_specs = spack.store.layout.all_specs()
- self.assertEqual(len(all_specs), 7)
- # query specs with multiple configurations
- mpileaks_specs = [s for s in all_specs if s.satisfies('mpileaks')]
- callpath_specs = [s for s in all_specs if s.satisfies('callpath')]
- mpi_specs = [s for s in all_specs if s.satisfies('mpi')]
+ all_specs = spack.store.layout.all_specs()
+ assert len(all_specs) == 7
+ # query specs with multiple configurations
+ mpileaks_specs = [s for s in all_specs if s.satisfies('mpileaks')]
+ callpath_specs = [s for s in all_specs if s.satisfies('callpath')]
+ mpi_specs = [s for s in all_specs if s.satisfies('mpi')]
- self.assertEqual(len(mpileaks_specs), 0)
- self.assertEqual(len(callpath_specs), 0)
- self.assertEqual(len(mpi_specs), 3)
+ assert len(mpileaks_specs) == 0
+ assert len(callpath_specs) == 0
+ assert len(mpi_specs) == 3
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
index 42ae9aa18e..1f8eeaa29e 100644
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -22,160 +22,152 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
+import pytest
import spack
import spack.architecture
+from spack.concretize import find_spec
from spack.spec import Spec, CompilerSpec
from spack.version import ver
-from spack.concretize import find_spec
-from spack.test.mock_packages_test import *
-
-
-class ConcretizeTest(MockPackagesTest):
-
- def check_spec(self, abstract, concrete):
- if abstract.versions.concrete:
- self.assertEqual(abstract.versions, concrete.versions)
-
- if abstract.variants:
- for name in abstract.variants:
- avariant = abstract.variants[name]
- cvariant = concrete.variants[name]
- self.assertEqual(avariant.value, cvariant.value)
-
- if abstract.compiler_flags:
- for flag in abstract.compiler_flags:
- aflag = abstract.compiler_flags[flag]
- cflag = concrete.compiler_flags[flag]
- self.assertTrue(set(aflag) <= set(cflag))
-
- for name in abstract.package.variants:
- self.assertTrue(name in concrete.variants)
- for flag in concrete.compiler_flags.valid_compiler_flags():
- self.assertTrue(flag in concrete.compiler_flags)
- if abstract.compiler and abstract.compiler.concrete:
- self.assertEqual(abstract.compiler, concrete.compiler)
-
- if abstract.architecture and abstract.architecture.concrete:
- self.assertEqual(abstract.architecture, concrete.architecture)
-
- def check_concretize(self, abstract_spec):
- abstract = Spec(abstract_spec)
- concrete = abstract.concretized()
-
- self.assertFalse(abstract.concrete)
- self.assertTrue(concrete.concrete)
- self.check_spec(abstract, concrete)
-
- return concrete
-
- def test_concretize_no_deps(self):
- self.check_concretize('libelf')
- self.check_concretize('libelf@0.8.13')
-
- def test_concretize_dag(self):
- self.check_concretize('callpath')
- self.check_concretize('mpileaks')
- self.check_concretize('libelf')
+def check_spec(abstract, concrete):
+ if abstract.versions.concrete:
+ assert abstract.versions == concrete.versions
+
+ if abstract.variants:
+ for name in abstract.variants:
+ avariant = abstract.variants[name]
+ cvariant = concrete.variants[name]
+ assert avariant.value == cvariant.value
+
+ if abstract.compiler_flags:
+ for flag in abstract.compiler_flags:
+ aflag = abstract.compiler_flags[flag]
+ cflag = concrete.compiler_flags[flag]
+ assert set(aflag) <= set(cflag)
+
+ for name in abstract.package.variants:
+ assert name in concrete.variants
+
+ for flag in concrete.compiler_flags.valid_compiler_flags():
+ assert flag in concrete.compiler_flags
+
+ if abstract.compiler and abstract.compiler.concrete:
+ assert abstract.compiler == concrete.compiler
+
+ if abstract.architecture and abstract.architecture.concrete:
+ assert abstract.architecture == concrete.architecture
+
+
+def check_concretize(abstract_spec):
+ abstract = Spec(abstract_spec)
+ concrete = abstract.concretized()
+ assert not abstract.concrete
+ assert concrete.concrete
+ check_spec(abstract, concrete)
+ return concrete
+
+
+@pytest.fixture(
+ params=[
+ # no_deps
+ 'libelf', 'libelf@0.8.13',
+ # dag
+ 'callpath', 'mpileaks', 'libelf',
+ # variant
+ 'mpich+debug', 'mpich~debug', 'mpich debug=2', 'mpich',
+ # compiler flags
+ 'mpich cppflags="-O3"',
+ # with virtual
+ 'mpileaks ^mpi', 'mpileaks ^mpi@:1.1', 'mpileaks ^mpi@2:',
+ 'mpileaks ^mpi@2.1', 'mpileaks ^mpi@2.2', 'mpileaks ^mpi@2.2',
+ 'mpileaks ^mpi@:1', 'mpileaks ^mpi@1.2:2'
+ ]
+)
+def spec(request):
+ """Spec to be concretized"""
+ return request.param
+
+
+@pytest.mark.usefixtures('config', 'builtin_mock')
+class TestConcretize(object):
+ def test_concretize(self, spec):
+ check_concretize(spec)
def test_concretize_mention_build_dep(self):
- spec = self.check_concretize('cmake-client ^cmake@3.4.3')
-
+ spec = check_concretize('cmake-client ^cmake@3.4.3')
# Check parent's perspective of child
dependency = spec.dependencies_dict()['cmake']
- self.assertEqual(set(dependency.deptypes), set(['build']))
-
+ assert set(dependency.deptypes) == set(['build'])
# Check child's perspective of parent
cmake = spec['cmake']
dependent = cmake.dependents_dict()['cmake-client']
- self.assertEqual(set(dependent.deptypes), set(['build']))
-
- def test_concretize_variant(self):
- self.check_concretize('mpich+debug')
- self.check_concretize('mpich~debug')
- self.check_concretize('mpich debug=2')
- self.check_concretize('mpich')
-
- def test_conretize_compiler_flags(self):
- self.check_concretize('mpich cppflags="-O3"')
+ assert set(dependent.deptypes) == set(['build'])
def test_concretize_preferred_version(self):
- spec = self.check_concretize('python')
- self.assertEqual(spec.versions, ver('2.7.11'))
-
- spec = self.check_concretize('python@3.5.1')
- self.assertEqual(spec.versions, ver('3.5.1'))
-
- def test_concretize_with_virtual(self):
- self.check_concretize('mpileaks ^mpi')
- self.check_concretize('mpileaks ^mpi@:1.1')
- self.check_concretize('mpileaks ^mpi@2:')
- self.check_concretize('mpileaks ^mpi@2.1')
- self.check_concretize('mpileaks ^mpi@2.2')
- self.check_concretize('mpileaks ^mpi@2.2')
- self.check_concretize('mpileaks ^mpi@:1')
- self.check_concretize('mpileaks ^mpi@1.2:2')
+ spec = check_concretize('python')
+ assert spec.versions == ver('2.7.11')
+ spec = check_concretize('python@3.5.1')
+ assert spec.versions == ver('3.5.1')
def test_concretize_with_restricted_virtual(self):
- self.check_concretize('mpileaks ^mpich2')
+ check_concretize('mpileaks ^mpich2')
- concrete = self.check_concretize('mpileaks ^mpich2@1.1')
- self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.1'))
+ concrete = check_concretize('mpileaks ^mpich2@1.1')
+ assert concrete['mpich2'].satisfies('mpich2@1.1')
- concrete = self.check_concretize('mpileaks ^mpich2@1.2')
- self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.2'))
+ concrete = check_concretize('mpileaks ^mpich2@1.2')
+ assert concrete['mpich2'].satisfies('mpich2@1.2')
- concrete = self.check_concretize('mpileaks ^mpich2@:1.5')
- self.assertTrue(concrete['mpich2'].satisfies('mpich2@:1.5'))
+ concrete = check_concretize('mpileaks ^mpich2@:1.5')
+ assert concrete['mpich2'].satisfies('mpich2@:1.5')
- concrete = self.check_concretize('mpileaks ^mpich2@:1.3')
- self.assertTrue(concrete['mpich2'].satisfies('mpich2@:1.3'))
+ concrete = check_concretize('mpileaks ^mpich2@:1.3')
+ assert concrete['mpich2'].satisfies('mpich2@:1.3')
- concrete = self.check_concretize('mpileaks ^mpich2@:1.2')
- self.assertTrue(concrete['mpich2'].satisfies('mpich2@:1.2'))
+ concrete = check_concretize('mpileaks ^mpich2@:1.2')
+ assert concrete['mpich2'].satisfies('mpich2@:1.2')
- concrete = self.check_concretize('mpileaks ^mpich2@:1.1')
- self.assertTrue(concrete['mpich2'].satisfies('mpich2@:1.1'))
+ concrete = check_concretize('mpileaks ^mpich2@:1.1')
+ assert concrete['mpich2'].satisfies('mpich2@:1.1')
- concrete = self.check_concretize('mpileaks ^mpich2@1.1:')
- self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.1:'))
+ concrete = check_concretize('mpileaks ^mpich2@1.1:')
+ assert concrete['mpich2'].satisfies('mpich2@1.1:')
- concrete = self.check_concretize('mpileaks ^mpich2@1.5:')
- self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.5:'))
+ concrete = check_concretize('mpileaks ^mpich2@1.5:')
+ assert concrete['mpich2'].satisfies('mpich2@1.5:')
- concrete = self.check_concretize('mpileaks ^mpich2@1.3.1:1.4')
- self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.3.1:1.4'))
+ concrete = check_concretize('mpileaks ^mpich2@1.3.1:1.4')
+ assert concrete['mpich2'].satisfies('mpich2@1.3.1:1.4')
def test_concretize_with_provides_when(self):
"""Make sure insufficient versions of MPI are not in providers list when
- we ask for some advanced version.
+ we ask for some advanced version.
"""
- self.assertTrue(
- not any(spec.satisfies('mpich2@:1.0')
- for spec in spack.repo.providers_for('mpi@2.1')))
-
- self.assertTrue(
- not any(spec.satisfies('mpich2@:1.1')
- for spec in spack.repo.providers_for('mpi@2.2')))
-
- self.assertTrue(
- not any(spec.satisfies('mpich@:1')
- for spec in spack.repo.providers_for('mpi@2')))
-
- self.assertTrue(
- not any(spec.satisfies('mpich@:1')
- for spec in spack.repo.providers_for('mpi@3')))
-
- self.assertTrue(
- not any(spec.satisfies('mpich2')
- for spec in spack.repo.providers_for('mpi@3')))
+ repo = spack.repo
+ assert not any(
+ s.satisfies('mpich2@:1.0') for s in repo.providers_for('mpi@2.1')
+ )
+ assert not any(
+ s.satisfies('mpich2@:1.1') for s in repo.providers_for('mpi@2.2')
+ )
+ assert not any(
+ s.satisfies('mpich@:1') for s in repo.providers_for('mpi@2')
+ )
+ assert not any(
+ s.satisfies('mpich@:1') for s in repo.providers_for('mpi@3')
+ )
+ assert not any(
+ s.satisfies('mpich2') for s in repo.providers_for('mpi@3')
+ )
def test_concretize_two_virtuals(self):
"""Test a package with multiple virtual dependencies."""
Spec('hypre').concretize()
- def test_concretize_two_virtuals_with_one_bound(self):
+ def test_concretize_two_virtuals_with_one_bound(
+ self, refresh_builtin_mock
+ ):
"""Test a package with multiple virtual dependencies and one preset."""
Spec('hypre ^openblas').concretize()
@@ -185,54 +177,48 @@ class ConcretizeTest(MockPackagesTest):
def test_concretize_two_virtuals_with_dual_provider(self):
"""Test a package with multiple virtual dependencies and force a provider
- that provides both."""
+ that provides both.
+ """
Spec('hypre ^openblas-with-lapack').concretize()
- def test_concretize_two_virtuals_with_dual_provider_and_a_conflict(self):
+ def test_concretize_two_virtuals_with_dual_provider_and_a_conflict(
+ self
+ ):
"""Test a package with multiple virtual dependencies and force a
- provider that provides both, and another conflicting package that
- provides one.
+ provider that provides both, and another conflicting package that
+ provides one.
"""
s = Spec('hypre ^openblas-with-lapack ^netlib-lapack')
- self.assertRaises(spack.spec.MultipleProviderError, s.concretize)
+ with pytest.raises(spack.spec.MultipleProviderError):
+ s.concretize()
def test_virtual_is_fully_expanded_for_callpath(self):
# force dependence on fake "zmpi" by asking for MPI 10.0
spec = Spec('callpath ^mpi@10.0')
- self.assertTrue('mpi' in spec._dependencies)
- self.assertFalse('fake' in spec)
-
+ assert 'mpi' in spec._dependencies
+ assert 'fake' not in spec
spec.concretize()
-
- self.assertTrue('zmpi' in spec._dependencies)
- self.assertTrue(all('mpi' not in d._dependencies
- for d in spec.traverse()))
- self.assertTrue('zmpi' in spec)
- self.assertTrue('mpi' in spec)
-
- self.assertTrue('fake' in spec._dependencies['zmpi'].spec)
-
- def test_virtual_is_fully_expanded_for_mpileaks(self):
+ assert 'zmpi' in spec._dependencies
+ assert all('mpi' not in d._dependencies for d in spec.traverse())
+ assert 'zmpi' in spec
+ assert 'mpi' in spec
+ assert 'fake' in spec._dependencies['zmpi'].spec
+
+ def test_virtual_is_fully_expanded_for_mpileaks(
+ self
+ ):
spec = Spec('mpileaks ^mpi@10.0')
- self.assertTrue('mpi' in spec._dependencies)
- self.assertFalse('fake' in spec)
-
+ assert 'mpi' in spec._dependencies
+ assert 'fake' not in spec
spec.concretize()
-
- self.assertTrue('zmpi' in spec._dependencies)
- self.assertTrue('callpath' in spec._dependencies)
- self.assertTrue(
- 'zmpi' in spec._dependencies['callpath']
- .spec._dependencies)
- self.assertTrue(
- 'fake' in spec._dependencies['callpath']
- .spec._dependencies['zmpi']
- .spec._dependencies)
-
- self.assertTrue(
- all('mpi' not in d._dependencies for d in spec.traverse()))
- self.assertTrue('zmpi' in spec)
- self.assertTrue('mpi' in spec)
+ assert 'zmpi' in spec._dependencies
+ assert 'callpath' in spec._dependencies
+ assert 'zmpi' in spec._dependencies['callpath'].spec._dependencies
+ assert 'fake' in spec._dependencies['callpath'].spec._dependencies[
+ 'zmpi'].spec._dependencies # NOQA: ignore=E501
+ assert all('mpi' not in d._dependencies for d in spec.traverse())
+ assert 'zmpi' in spec
+ assert 'mpi' in spec
def test_my_dep_depends_on_provider_of_my_virtual_dep(self):
spec = Spec('indirect_mpich')
@@ -242,36 +228,31 @@ class ConcretizeTest(MockPackagesTest):
def test_compiler_inheritance(self):
spec = Spec('mpileaks')
spec.normalize()
-
spec['dyninst'].compiler = CompilerSpec('clang')
spec.concretize()
-
# TODO: not exactly the syntax I would like.
- self.assertTrue(spec['libdwarf'].compiler.satisfies('clang'))
- self.assertTrue(spec['libelf'].compiler.satisfies('clang'))
+ assert spec['libdwarf'].compiler.satisfies('clang')
+ assert spec['libelf'].compiler.satisfies('clang')
def test_external_package(self):
spec = Spec('externaltool%gcc')
spec.concretize()
-
- self.assertEqual(
- spec['externaltool'].external, '/path/to/external_tool')
- self.assertFalse('externalprereq' in spec)
- self.assertTrue(spec['externaltool'].compiler.satisfies('gcc'))
+ assert spec['externaltool'].external == '/path/to/external_tool'
+ assert 'externalprereq' not in spec
+ assert spec['externaltool'].compiler.satisfies('gcc')
def test_external_package_module(self):
# No tcl modules on darwin/linux machines
# TODO: improved way to check for this.
platform = spack.architecture.real_platform().name
- if (platform == 'darwin' or platform == 'linux'):
+ if platform == 'darwin' or platform == 'linux':
return
spec = Spec('externalmodule')
spec.concretize()
- self.assertEqual(
- spec['externalmodule'].external_module, 'external-module')
- self.assertFalse('externalprereq' in spec)
- self.assertTrue(spec['externalmodule'].compiler.satisfies('gcc'))
+ assert spec['externalmodule'].external_module == 'external-module'
+ assert 'externalprereq' not in spec
+ assert spec['externalmodule'].compiler.satisfies('gcc')
def test_nobuild_package(self):
got_error = False
@@ -280,17 +261,15 @@ class ConcretizeTest(MockPackagesTest):
spec.concretize()
except spack.concretize.NoBuildError:
got_error = True
- self.assertTrue(got_error)
+ assert got_error
def test_external_and_virtual(self):
spec = Spec('externaltest')
spec.concretize()
- self.assertEqual(
- spec['externaltool'].external, '/path/to/external_tool')
- self.assertEqual(
- spec['stuff'].external, '/path/to/external_virtual_gcc')
- self.assertTrue(spec['externaltool'].compiler.satisfies('gcc'))
- self.assertTrue(spec['stuff'].compiler.satisfies('gcc'))
+ assert spec['externaltool'].external == '/path/to/external_tool'
+ assert spec['stuff'].external == '/path/to/external_virtual_gcc'
+ assert spec['externaltool'].compiler.satisfies('gcc')
+ assert spec['stuff'].compiler.satisfies('gcc')
def test_find_spec_parents(self):
"""Tests the spec finding logic used by concretization. """
@@ -300,7 +279,7 @@ class ConcretizeTest(MockPackagesTest):
Spec('d +foo')),
Spec('e +foo'))
- self.assertEqual('a', find_spec(s['b'], lambda s: '+foo' in s).name)
+ assert 'a' == find_spec(s['b'], lambda s: '+foo' in s).name
def test_find_spec_children(self):
s = Spec('a',
@@ -308,13 +287,13 @@ class ConcretizeTest(MockPackagesTest):
Spec('c'),
Spec('d +foo')),
Spec('e +foo'))
- self.assertEqual('d', find_spec(s['b'], lambda s: '+foo' in s).name)
+ assert 'd' == find_spec(s['b'], lambda s: '+foo' in s).name
s = Spec('a',
Spec('b +foo',
Spec('c +foo'),
Spec('d')),
Spec('e +foo'))
- self.assertEqual('c', find_spec(s['b'], lambda s: '+foo' in s).name)
+ assert 'c' == find_spec(s['b'], lambda s: '+foo' in s).name
def test_find_spec_sibling(self):
s = Spec('a',
@@ -322,8 +301,8 @@ class ConcretizeTest(MockPackagesTest):
Spec('c'),
Spec('d')),
Spec('e +foo'))
- self.assertEqual('e', find_spec(s['b'], lambda s: '+foo' in s).name)
- self.assertEqual('b', find_spec(s['e'], lambda s: '+foo' in s).name)
+ assert 'e' == find_spec(s['b'], lambda s: '+foo' in s).name
+ assert 'b' == find_spec(s['e'], lambda s: '+foo' in s).name
s = Spec('a',
Spec('b +foo',
@@ -331,7 +310,7 @@ class ConcretizeTest(MockPackagesTest):
Spec('d')),
Spec('e',
Spec('f +foo')))
- self.assertEqual('f', find_spec(s['b'], lambda s: '+foo' in s).name)
+ assert 'f' == find_spec(s['b'], lambda s: '+foo' in s).name
def test_find_spec_self(self):
s = Spec('a',
@@ -339,7 +318,7 @@ class ConcretizeTest(MockPackagesTest):
Spec('c'),
Spec('d')),
Spec('e'))
- self.assertEqual('b', find_spec(s['b'], lambda s: '+foo' in s).name)
+ assert 'b' == find_spec(s['b'], lambda s: '+foo' in s).name
def test_find_spec_none(self):
s = Spec('a',
@@ -347,10 +326,10 @@ class ConcretizeTest(MockPackagesTest):
Spec('c'),
Spec('d')),
Spec('e'))
- self.assertEqual(None, find_spec(s['b'], lambda s: '+foo' in s))
+ assert find_spec(s['b'], lambda s: '+foo' in s) is None
def test_compiler_child(self):
s = Spec('mpileaks%clang ^dyninst%gcc')
s.concretize()
- self.assertTrue(s['mpileaks'].satisfies('%clang'))
- self.assertTrue(s['dyninst'].satisfies('%gcc'))
+ assert s['mpileaks'].satisfies('%clang')
+ assert s['dyninst'].satisfies('%gcc')
diff --git a/lib/spack/spack/test/concretize_preferences.py b/lib/spack/spack/test/concretize_preferences.py
index 575e912609..21d457d2e0 100644
--- a/lib/spack/spack/test/concretize_preferences.py
+++ b/lib/spack/spack/test/concretize_preferences.py
@@ -22,92 +22,95 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
+import pytest
+
import spack
-import spack.architecture
-from spack.test.mock_packages_test import *
-from tempfile import mkdtemp
+from spack.spec import Spec
-class ConcretizePreferencesTest(MockPackagesTest):
- """Test concretization preferences are being applied correctly.
- """
+@pytest.fixture()
+def concretize_scope(config, tmpdir):
+ """Adds a scope for concretization preferences"""
+ tmpdir.ensure_dir('concretize')
+ spack.config.ConfigScope(
+ 'concretize', str(tmpdir.join('concretize'))
+ )
+ yield
+ # This is kind of weird, but that's how config scopes are
+ # set in ConfigScope.__init__
+ spack.config.config_scopes.pop('concretize')
+ spack.pkgsort = spack.PreferredPackages()
+
+
+def concretize(abstract_spec):
+ return Spec(abstract_spec).concretized()
+
+
+def update_packages(pkgname, section, value):
+ """Update config and reread package list"""
+ conf = {pkgname: {section: value}}
+ spack.config.update_config('packages', conf, 'concretize')
+ spack.pkgsort = spack.PreferredPackages()
- def setUp(self):
- """Create config section to store concretization preferences
- """
- super(ConcretizePreferencesTest, self).setUp()
- self.tmp_dir = mkdtemp('.tmp', 'spack-config-test-')
- spack.config.ConfigScope('concretize',
- os.path.join(self.tmp_dir, 'concretize'))
-
- def tearDown(self):
- super(ConcretizePreferencesTest, self).tearDown()
- shutil.rmtree(self.tmp_dir, True)
- spack.pkgsort = spack.PreferredPackages()
-
- def concretize(self, abstract_spec):
- return Spec(abstract_spec).concretized()
-
- def update_packages(self, pkgname, section, value):
- """Update config and reread package list"""
- conf = {pkgname: {section: value}}
- spack.config.update_config('packages', conf, 'concretize')
- spack.pkgsort = spack.PreferredPackages()
-
- def assert_variant_values(self, spec, **variants):
- concrete = self.concretize(spec)
- for variant, value in variants.items():
- self.assertEqual(concrete.variants[variant].value, value)
+def assert_variant_values(spec, **variants):
+ concrete = concretize(spec)
+ for variant, value in variants.items():
+ assert concrete.variants[variant].value == value
+
+
+@pytest.mark.usefixtures('concretize_scope', 'builtin_mock')
+class TestConcretizePreferences(object):
def test_preferred_variants(self):
"""Test preferred variants are applied correctly
"""
- self.update_packages('mpileaks', 'variants',
- '~debug~opt+shared+static')
- self.assert_variant_values('mpileaks', debug=False, opt=False,
- shared=True, static=True)
-
- self.update_packages('mpileaks', 'variants',
- ['+debug', '+opt', '~shared', '-static'])
- self.assert_variant_values('mpileaks', debug=True, opt=True,
- shared=False, static=False)
-
- def test_preferred_compilers(self):
+ update_packages('mpileaks', 'variants', '~debug~opt+shared+static')
+ assert_variant_values(
+ 'mpileaks', debug=False, opt=False, shared=True, static=True
+ )
+ update_packages(
+ 'mpileaks', 'variants', ['+debug', '+opt', '~shared', '-static']
+ )
+ assert_variant_values(
+ 'mpileaks', debug=True, opt=True, shared=False, static=False
+ )
+
+ def test_preferred_compilers(self, refresh_builtin_mock):
"""Test preferred compilers are applied correctly
"""
- self.update_packages('mpileaks', 'compiler', ['clang@3.3'])
- spec = self.concretize('mpileaks')
- self.assertEqual(spec.compiler, spack.spec.CompilerSpec('clang@3.3'))
+ update_packages('mpileaks', 'compiler', ['clang@3.3'])
+ spec = concretize('mpileaks')
+ assert spec.compiler == spack.spec.CompilerSpec('clang@3.3')
- self.update_packages('mpileaks', 'compiler', ['gcc@4.5.0'])
- spec = self.concretize('mpileaks')
- self.assertEqual(spec.compiler, spack.spec.CompilerSpec('gcc@4.5.0'))
+ update_packages('mpileaks', 'compiler', ['gcc@4.5.0'])
+ spec = concretize('mpileaks')
+ assert spec.compiler == spack.spec.CompilerSpec('gcc@4.5.0')
def test_preferred_versions(self):
"""Test preferred package versions are applied correctly
"""
- self.update_packages('mpileaks', 'version', ['2.3'])
- spec = self.concretize('mpileaks')
- self.assertEqual(spec.version, spack.spec.Version('2.3'))
+ update_packages('mpileaks', 'version', ['2.3'])
+ spec = concretize('mpileaks')
+ assert spec.version == spack.spec.Version('2.3')
- self.update_packages('mpileaks', 'version', ['2.2'])
- spec = self.concretize('mpileaks')
- self.assertEqual(spec.version, spack.spec.Version('2.2'))
+ update_packages('mpileaks', 'version', ['2.2'])
+ spec = concretize('mpileaks')
+ assert spec.version == spack.spec.Version('2.2')
def test_preferred_providers(self):
- """Test preferred providers of virtual packages are applied correctly
+ """Test preferred providers of virtual packages are
+ applied correctly
"""
- self.update_packages('all', 'providers', {'mpi': ['mpich']})
- spec = self.concretize('mpileaks')
- self.assertTrue('mpich' in spec)
+ update_packages('all', 'providers', {'mpi': ['mpich']})
+ spec = concretize('mpileaks')
+ assert 'mpich' in spec
- self.update_packages('all', 'providers', {'mpi': ['zmpi']})
- spec = self.concretize('mpileaks')
- self.assertTrue('zmpi', spec)
+ update_packages('all', 'providers', {'mpi': ['zmpi']})
+ spec = concretize('mpileaks')
+ assert 'zmpi' in spec
def test_develop(self):
- """Test conretization with develop version
- """
+ """Test concretization with develop version"""
spec = Spec('builtin.mock.develop-test')
spec.concretize()
- self.assertEqual(spec.version, spack.spec.Version('0.2.15'))
+ assert spec.version == spack.spec.Version('0.2.15')
diff --git a/lib/spack/spack/test/config.py b/lib/spack/spack/test/config.py
index adc0795916..ed8f78ceb4 100644
--- a/lib/spack/spack/test/config.py
+++ b/lib/spack/spack/test/config.py
@@ -22,17 +22,17 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-import os
-import shutil
+import collections
import getpass
-import yaml
-from tempfile import mkdtemp
+import os
+import tempfile
+import ordereddict_backport
+import pytest
import spack
import spack.config
+import yaml
from spack.util.path import canonicalize_path
-from ordereddict_backport import OrderedDict
-from spack.test.mock_packages_test import *
# Some sample compiler config data
a_comps = {
@@ -167,104 +167,115 @@ config_override_list = {
'build_stage:': ['patha', 'pathb']}}
-class ConfigTest(MockPackagesTest):
-
- def setUp(self):
- super(ConfigTest, self).setUp()
- self.tmp_dir = mkdtemp('.tmp', 'spack-config-test-')
- self.a_comp_specs = [
- ac['compiler']['spec'] for ac in a_comps['compilers']]
- self.b_comp_specs = [
- bc['compiler']['spec'] for bc in b_comps['compilers']]
+def check_compiler_config(comps, *compiler_names):
+ """Check that named compilers in comps match Spack's config."""
+ config = spack.config.get_config('compilers')
+ compiler_list = ['cc', 'cxx', 'f77', 'fc']
+ flag_list = ['cflags', 'cxxflags', 'fflags', 'cppflags',
+ 'ldflags', 'ldlibs']
+ param_list = ['modules', 'paths', 'spec', 'operating_system']
+ for compiler in config:
+ conf = compiler['compiler']
+ if conf['spec'] in compiler_names:
+ comp = next((c['compiler'] for c in comps if
+ c['compiler']['spec'] == conf['spec']), None)
+ if not comp:
+ raise ValueError('Bad config spec')
+ for p in param_list:
+ assert conf[p] == comp[p]
+ for f in flag_list:
+ expected = comp.get('flags', {}).get(f, None)
+ actual = conf.get('flags', {}).get(f, None)
+ assert expected == actual
+ for c in compiler_list:
+ expected = comp['paths'][c]
+ actual = conf['paths'][c]
+ assert expected == actual
+
+
+@pytest.fixture()
+def config(tmpdir):
+ """Mocks the configuration scope."""
+ spack.config.clear_config_caches()
+ real_scope = spack.config.config_scopes
+ spack.config.config_scopes = ordereddict_backport.OrderedDict()
+ for priority in ['low', 'high']:
+ spack.config.ConfigScope(priority, str(tmpdir.join(priority)))
+ Config = collections.namedtuple('Config', ['real', 'mock'])
+ yield Config(real=real_scope, mock=spack.config.config_scopes)
+ spack.config.config_scopes = real_scope
+ spack.config.clear_config_caches()
+
+
+@pytest.fixture()
+def write_config_file(tmpdir):
+ """Returns a function that writes a config file."""
+ def _write(config, data, scope):
+ config_yaml = tmpdir.join(scope, config + '.yaml')
+ config_yaml.ensure()
+ with config_yaml.open('w') as f:
+ yaml.dump(data, f)
+ return _write
- spack.config.config_scopes = OrderedDict()
- for priority in ['low', 'high']:
- scope_dir = os.path.join(self.tmp_dir, priority)
- spack.config.ConfigScope(priority, scope_dir)
- def tearDown(self):
- super(ConfigTest, self).tearDown()
- shutil.rmtree(self.tmp_dir, True)
+@pytest.fixture()
+def compiler_specs():
+ """Returns a couple of compiler specs needed for the tests"""
+ a = [ac['compiler']['spec'] for ac in a_comps['compilers']]
+ b = [bc['compiler']['spec'] for bc in b_comps['compilers']]
+ CompilerSpecs = collections.namedtuple('CompilerSpecs', ['a', 'b'])
+ return CompilerSpecs(a=a, b=b)
- def write_config_file(self, config, data, scope):
- scope_dir = os.path.join(self.tmp_dir, scope)
- mkdirp(scope_dir)
- path = os.path.join(scope_dir, config + '.yaml')
- with open(path, 'w') as f:
- print yaml
- yaml.dump(data, f)
-
- def check_compiler_config(self, comps, *compiler_names):
- """Check that named compilers in comps match Spack's config."""
- config = spack.config.get_config('compilers')
- compiler_list = ['cc', 'cxx', 'f77', 'fc']
- flag_list = ['cflags', 'cxxflags', 'fflags', 'cppflags',
- 'ldflags', 'ldlibs']
- param_list = ['modules', 'paths', 'spec', 'operating_system']
- for compiler in config:
- conf = compiler['compiler']
- if conf['spec'] in compiler_names:
- comp = next((c['compiler'] for c in comps if
- c['compiler']['spec'] == conf['spec']), None)
- if not comp:
- self.fail('Bad config spec')
- for p in param_list:
- self.assertEqual(conf[p], comp[p])
- for f in flag_list:
- expected = comp.get('flags', {}).get(f, None)
- actual = conf.get('flags', {}).get(f, None)
- self.assertEqual(expected, actual)
- for c in compiler_list:
- expected = comp['paths'][c]
- actual = conf['paths'][c]
- self.assertEqual(expected, actual)
+@pytest.mark.usefixtures('config')
+class TestConfig(object):
def test_write_list_in_memory(self):
spack.config.update_config('repos', repos_low['repos'], scope='low')
spack.config.update_config('repos', repos_high['repos'], scope='high')
config = spack.config.get_config('repos')
- self.assertEqual(config, repos_high['repos'] + repos_low['repos'])
+ assert config == repos_high['repos'] + repos_low['repos']
- def test_write_key_in_memory(self):
+ def test_write_key_in_memory(self, compiler_specs):
# Write b_comps "on top of" a_comps.
spack.config.update_config(
- 'compilers', a_comps['compilers'], scope='low')
+ 'compilers', a_comps['compilers'], scope='low'
+ )
spack.config.update_config(
- 'compilers', b_comps['compilers'], scope='high')
-
+ 'compilers', b_comps['compilers'], scope='high'
+ )
# Make sure the config looks how we expect.
- self.check_compiler_config(a_comps['compilers'], *self.a_comp_specs)
- self.check_compiler_config(b_comps['compilers'], *self.b_comp_specs)
+ check_compiler_config(a_comps['compilers'], *compiler_specs.a)
+ check_compiler_config(b_comps['compilers'], *compiler_specs.b)
- def test_write_key_to_disk(self):
+ def test_write_key_to_disk(self, compiler_specs):
# Write b_comps "on top of" a_comps.
spack.config.update_config(
- 'compilers', a_comps['compilers'], scope='low')
+ 'compilers', a_comps['compilers'], scope='low'
+ )
spack.config.update_config(
- 'compilers', b_comps['compilers'], scope='high')
-
+ 'compilers', b_comps['compilers'], scope='high'
+ )
# Clear caches so we're forced to read from disk.
spack.config.clear_config_caches()
-
# Same check again, to ensure consistency.
- self.check_compiler_config(a_comps['compilers'], *self.a_comp_specs)
- self.check_compiler_config(b_comps['compilers'], *self.b_comp_specs)
+ check_compiler_config(a_comps['compilers'], *compiler_specs.a)
+ check_compiler_config(b_comps['compilers'], *compiler_specs.b)
- def test_write_to_same_priority_file(self):
+ def test_write_to_same_priority_file(self, compiler_specs):
# Write b_comps in the same file as a_comps.
spack.config.update_config(
- 'compilers', a_comps['compilers'], scope='low')
+ 'compilers', a_comps['compilers'], scope='low'
+ )
spack.config.update_config(
- 'compilers', b_comps['compilers'], scope='low')
-
+ 'compilers', b_comps['compilers'], scope='low'
+ )
# Clear caches so we're forced to read from disk.
spack.config.clear_config_caches()
-
# Same check again, to ensure consistency.
- self.check_compiler_config(a_comps['compilers'], *self.a_comp_specs)
- self.check_compiler_config(b_comps['compilers'], *self.b_comp_specs)
+ check_compiler_config(a_comps['compilers'], *compiler_specs.a)
+ check_compiler_config(b_comps['compilers'], *compiler_specs.b)
def check_canonical(self, var, expected):
"""Ensure that <expected> is substituted properly for <var> in strings
@@ -283,72 +294,78 @@ class ConfigTest(MockPackagesTest):
def test_substitute_config_variables(self):
prefix = spack.prefix.lstrip('/')
- self.assertEqual(os.path.join('/foo/bar/baz', prefix),
- canonicalize_path('/foo/bar/baz/$spack'))
+ assert os.path.join(
+ '/foo/bar/baz', prefix
+ ) == canonicalize_path('/foo/bar/baz/$spack')
- self.assertEqual(os.path.join(spack.prefix, 'foo/bar/baz'),
- canonicalize_path('$spack/foo/bar/baz/'))
+ assert os.path.join(
+ spack.prefix, 'foo/bar/baz'
+ ) == canonicalize_path('$spack/foo/bar/baz/')
- self.assertEqual(os.path.join('/foo/bar/baz', prefix, 'foo/bar/baz'),
- canonicalize_path('/foo/bar/baz/$spack/foo/bar/baz/'))
+ assert os.path.join(
+ '/foo/bar/baz', prefix, 'foo/bar/baz'
+ ) == canonicalize_path('/foo/bar/baz/$spack/foo/bar/baz/')
- self.assertEqual(os.path.join('/foo/bar/baz', prefix),
- canonicalize_path('/foo/bar/baz/${spack}'))
+ assert os.path.join(
+ '/foo/bar/baz', prefix
+ ) == canonicalize_path('/foo/bar/baz/${spack}')
- self.assertEqual(os.path.join(spack.prefix, 'foo/bar/baz'),
- canonicalize_path('${spack}/foo/bar/baz/'))
+ assert os.path.join(
+ spack.prefix, 'foo/bar/baz'
+ ) == canonicalize_path('${spack}/foo/bar/baz/')
- self.assertEqual(
- os.path.join('/foo/bar/baz', prefix, 'foo/bar/baz'),
- canonicalize_path('/foo/bar/baz/${spack}/foo/bar/baz/'))
+ assert os.path.join(
+ '/foo/bar/baz', prefix, 'foo/bar/baz'
+ ) == canonicalize_path('/foo/bar/baz/${spack}/foo/bar/baz/')
- self.assertNotEqual(
- os.path.join('/foo/bar/baz', prefix, 'foo/bar/baz'),
- canonicalize_path('/foo/bar/baz/${spack/foo/bar/baz/'))
+ assert os.path.join(
+ '/foo/bar/baz', prefix, 'foo/bar/baz'
+ ) != canonicalize_path('/foo/bar/baz/${spack/foo/bar/baz/')
def test_substitute_user(self):
user = getpass.getuser()
- self.assertEqual('/foo/bar/' + user + '/baz',
- canonicalize_path('/foo/bar/$user/baz'))
+ assert '/foo/bar/' + user + '/baz' == canonicalize_path(
+ '/foo/bar/$user/baz'
+ )
def test_substitute_tempdir(self):
tempdir = tempfile.gettempdir()
- self.assertEqual(tempdir, canonicalize_path('$tempdir'))
- self.assertEqual(tempdir + '/foo/bar/baz',
- canonicalize_path('$tempdir/foo/bar/baz'))
-
- def test_read_config(self):
- self.write_config_file('config', config_low, 'low')
- self.assertEqual(spack.config.get_config('config'),
- config_low['config'])
-
- def test_read_config_override_all(self):
- self.write_config_file('config', config_low, 'low')
- self.write_config_file('config', config_override_all, 'high')
- self.assertEqual(spack.config.get_config('config'), {
+ assert tempdir == canonicalize_path('$tempdir')
+ assert tempdir + '/foo/bar/baz' == canonicalize_path(
+ '$tempdir/foo/bar/baz'
+ )
+
+ def test_read_config(self, write_config_file):
+ write_config_file('config', config_low, 'low')
+ assert spack.config.get_config('config') == config_low['config']
+
+ def test_read_config_override_all(self, write_config_file):
+ write_config_file('config', config_low, 'low')
+ write_config_file('config', config_override_all, 'high')
+ assert spack.config.get_config('config') == {
'install_tree': 'override_all'
- })
+ }
- def test_read_config_override_key(self):
- self.write_config_file('config', config_low, 'low')
- self.write_config_file('config', config_override_key, 'high')
- self.assertEqual(spack.config.get_config('config'), {
+ def test_read_config_override_key(self, write_config_file):
+ write_config_file('config', config_low, 'low')
+ write_config_file('config', config_override_key, 'high')
+ assert spack.config.get_config('config') == {
'install_tree': 'override_key',
'build_stage': ['path1', 'path2', 'path3']
- })
+ }
- def test_read_config_merge_list(self):
- self.write_config_file('config', config_low, 'low')
- self.write_config_file('config', config_merge_list, 'high')
- self.assertEqual(spack.config.get_config('config'), {
+ def test_read_config_merge_list(self, write_config_file):
+ write_config_file('config', config_low, 'low')
+ write_config_file('config', config_merge_list, 'high')
+ assert spack.config.get_config('config') == {
'install_tree': 'install_tree_path',
'build_stage': ['patha', 'pathb', 'path1', 'path2', 'path3']
- })
+ }
- def test_read_config_override_list(self):
- self.write_config_file('config', config_low, 'low')
- self.write_config_file('config', config_override_list, 'high')
- self.assertEqual(spack.config.get_config('config'), {
+ def test_read_config_override_list(self, write_config_file):
+ write_config_file('config', config_low, 'low')
+ write_config_file('config', config_override_list, 'high')
+ assert spack.config.get_config('config') == {
'install_tree': 'install_tree_path',
'build_stage': ['patha', 'pathb']
- })
+ }
diff --git a/lib/spack/spack/test/conftest.py b/lib/spack/spack/test/conftest.py
new file mode 100644
index 0000000000..11127d8735
--- /dev/null
+++ b/lib/spack/spack/test/conftest.py
@@ -0,0 +1,515 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+import collections
+import copy
+import os
+import re
+import shutil
+
+import cStringIO
+import llnl.util.filesystem
+import llnl.util.lang
+import ordereddict_backport
+import py
+import pytest
+import spack
+import spack.architecture
+import spack.database
+import spack.directory_layout
+import spack.fetch_strategy
+import spack.platforms.test
+import spack.repository
+import spack.stage
+import spack.util.executable
+import spack.util.pattern
+
+
+##########
+# Monkey-patching that is applied to all tests
+##########
+
+
+@pytest.fixture(autouse=True)
+def no_stdin_duplication(monkeypatch):
+ """Duplicating stdin (or any other stream) returns an empty
+ cStringIO object.
+ """
+ monkeypatch.setattr(
+ llnl.util.lang,
+ 'duplicate_stream',
+ lambda x: cStringIO.StringIO()
+ )
+
+
+@pytest.fixture(autouse=True)
+def mock_fetch_cache(monkeypatch):
+ """Substitutes spack.fetch_cache with a mock object that does nothing
+ and raises on fetch.
+ """
+ class MockCache(object):
+ def store(self, copyCmd, relativeDst):
+ pass
+
+ def fetcher(self, targetPath, digest, **kwargs):
+ return MockCacheFetcher()
+
+ class MockCacheFetcher(object):
+ def set_stage(self, stage):
+ pass
+
+ def fetch(self):
+ raise spack.fetch_strategy.FetchError(
+ 'Mock cache always fails for tests'
+ )
+
+ def __str__(self):
+ return "[mock fetcher]"
+
+ monkeypatch.setattr(spack, 'fetch_cache', MockCache())
+
+
+# FIXME: The lines below should better be added to a fixture with
+# FIXME: session-scope. Anyhow doing it is not easy, as it seems
+# FIXME: there's some weird interaction with compilers during concretization.
+spack.architecture.real_platform = spack.architecture.platform
+spack.architecture.platform = lambda: spack.platforms.test.Test()
+
+##########
+# Test-specific fixtures
+##########
+
+
+@pytest.fixture(scope='session')
+def repo_path():
+ """Session scoped RepoPath object pointing to the mock repository"""
+ return spack.repository.RepoPath(spack.mock_packages_path)
+
+
+@pytest.fixture(scope='module')
+def builtin_mock(repo_path):
+ """Uses the 'builtin.mock' repository instead of 'builtin'"""
+ mock_repo = copy.deepcopy(repo_path)
+ spack.repo.swap(mock_repo)
+ BuiltinMock = collections.namedtuple('BuiltinMock', ['real', 'mock'])
+ # Confusing, but we swapped above
+ yield BuiltinMock(real=mock_repo, mock=spack.repo)
+ spack.repo.swap(mock_repo)
+
+
+@pytest.fixture()
+def refresh_builtin_mock(builtin_mock, repo_path):
+ """Refreshes the state of spack.repo"""
+ # Get back the real repository
+ spack.repo.swap(builtin_mock.real)
+ mock_repo = copy.deepcopy(repo_path)
+ spack.repo.swap(mock_repo)
+ return builtin_mock
+
+
+@pytest.fixture(scope='session')
+def linux_os():
+ """Returns a named tuple with attributes 'name' and 'version'
+ representing the OS.
+ """
+ platform = spack.architecture.platform()
+ name, version = 'debian', '6'
+ if platform.name == 'linux':
+ platform = spack.architecture.platform()
+ current_os = platform.operating_system('default_os')
+ name, version = current_os.name, current_os.version
+ LinuxOS = collections.namedtuple('LinuxOS', ['name', 'version'])
+ return LinuxOS(name=name, version=version)
+
+
+@pytest.fixture(scope='session')
+def configuration_dir(tmpdir_factory, linux_os):
+ """Copies mock configuration files in a temporary directory. Returns the
+ directory path.
+ """
+ tmpdir = tmpdir_factory.mktemp('configurations')
+ # Name of the yaml files in the test/data folder
+ test_path = py.path.local(spack.test_path)
+ compilers_yaml = test_path.join('data', 'compilers.yaml')
+ packages_yaml = test_path.join('data', 'packages.yaml')
+ config_yaml = test_path.join('data', 'config.yaml')
+ # Create temporary 'site' and 'user' folders
+ tmpdir.ensure('site', dir=True)
+ tmpdir.ensure('user', dir=True)
+ # Copy the configurations that don't need further work
+ packages_yaml.copy(tmpdir.join('site', 'packages.yaml'))
+ config_yaml.copy(tmpdir.join('site', 'config.yaml'))
+ # Write the one that needs modifications
+ content = ''.join(compilers_yaml.read()).format(linux_os)
+ t = tmpdir.join('site', 'compilers.yaml')
+ t.write(content)
+ return tmpdir
+
+
+@pytest.fixture(scope='module')
+def config(configuration_dir):
+ """Hooks the mock configuration files into spack.config"""
+ # Set up a mock config scope
+ spack.config.clear_config_caches()
+ real_scope = spack.config.config_scopes
+ spack.config.config_scopes = ordereddict_backport.OrderedDict()
+ spack.config.ConfigScope('site', str(configuration_dir.join('site')))
+ spack.config.ConfigScope('user', str(configuration_dir.join('user')))
+ Config = collections.namedtuple('Config', ['real', 'mock'])
+ yield Config(real=real_scope, mock=spack.config.config_scopes)
+ spack.config.config_scopes = real_scope
+ spack.config.clear_config_caches()
+
+
+@pytest.fixture(scope='module')
+def database(tmpdir_factory, builtin_mock, config):
+ """Creates a mock database with some packages installed note that
+ the ref count for dyninst here will be 3, as it's recycled
+ across each install.
+ """
+
+ # Here is what the mock DB looks like:
+ #
+ # o mpileaks o mpileaks' o mpileaks''
+ # |\ |\ |\
+ # | o callpath | o callpath' | o callpath''
+ # |/| |/| |/|
+ # o | mpich o | mpich2 o | zmpi
+ # | | o | fake
+ # | | |
+ # | |______________/
+ # | .____________/
+ # |/
+ # o dyninst
+ # |\
+ # | o libdwarf
+ # |/
+ # o libelf
+
+ # Make a fake install directory
+ install_path = tmpdir_factory.mktemp('install_for_database')
+ spack_install_path = py.path.local(spack.store.root)
+ spack.store.root = str(install_path)
+
+ install_layout = spack.directory_layout.YamlDirectoryLayout(
+ str(install_path)
+ )
+ spack_install_layout = spack.store.layout
+ spack.store.layout = install_layout
+
+ # Make fake database and fake install directory.
+ install_db = spack.database.Database(str(install_path))
+ spack_install_db = spack.store.db
+ spack.store.db = install_db
+
+ Entry = collections.namedtuple('Entry', ['path', 'layout', 'db'])
+ Database = collections.namedtuple(
+ 'Database', ['real', 'mock', 'install', 'uninstall', 'refresh']
+ )
+
+ real = Entry(
+ path=spack_install_path,
+ layout=spack_install_layout,
+ db=spack_install_db
+ )
+ mock = Entry(path=install_path, layout=install_layout, db=install_db)
+
+ def _install(spec):
+ s = spack.spec.Spec(spec)
+ s.concretize()
+ pkg = spack.repo.get(s)
+ pkg.do_install(fake=True)
+
+ def _uninstall(spec):
+ spec.package.do_uninstall(spec)
+
+ def _refresh():
+ with spack.store.db.write_transaction():
+ for spec in spack.store.db.query():
+ _uninstall(spec)
+ _install('mpileaks ^mpich')
+ _install('mpileaks ^mpich2')
+ _install('mpileaks ^zmpi')
+
+ t = Database(
+ real=real,
+ mock=mock,
+ install=_install,
+ uninstall=_uninstall,
+ refresh=_refresh
+ )
+ # Transaction used to avoid repeated writes.
+ with spack.store.db.write_transaction():
+ t.install('mpileaks ^mpich')
+ t.install('mpileaks ^mpich2')
+ t.install('mpileaks ^zmpi')
+
+ yield t
+
+ with spack.store.db.write_transaction():
+ for spec in spack.store.db.query():
+ t.uninstall(spec)
+
+ install_path.remove(rec=1)
+ spack.store.root = str(spack_install_path)
+ spack.store.layout = spack_install_layout
+ spack.store.db = spack_install_db
+
+
+@pytest.fixture()
+def refresh_db_on_exit(database):
+ """"Restores the state of the database after a test."""
+ yield
+ database.refresh()
+
+##########
+# Fake archives and repositories
+##########
+
+
+@pytest.fixture(scope='session')
+def mock_archive():
+ """Creates a very simple archive directory with a configure script and a
+ makefile that installs to a prefix. Tars it up into an archive.
+ """
+ tar = spack.util.executable.which('tar', required=True)
+ stage = spack.stage.Stage('mock-archive-stage')
+ tmpdir = py.path.local(stage.path)
+ repo_name = 'mock-archive-repo'
+ tmpdir.ensure(repo_name, dir=True)
+ repodir = tmpdir.join(repo_name)
+ # Create the configure script
+ configure_path = str(tmpdir.join(repo_name, 'configure'))
+ with open(configure_path, 'w') as f:
+ f.write(
+ "#!/bin/sh\n"
+ "prefix=$(echo $1 | sed 's/--prefix=//')\n"
+ "cat > Makefile <<EOF\n"
+ "all:\n"
+ "\techo Building...\n\n"
+ "install:\n"
+ "\tmkdir -p $prefix\n"
+ "\ttouch $prefix/dummy_file\n"
+ "EOF\n"
+ )
+ os.chmod(configure_path, 0755)
+ # Archive it
+ current = tmpdir.chdir()
+ archive_name = '{0}.tar.gz'.format(repo_name)
+ tar('-czf', archive_name, repo_name)
+ current.chdir()
+ Archive = collections.namedtuple('Archive', ['url', 'path'])
+ url = 'file://' + str(tmpdir.join(archive_name))
+ # Return the url
+ yield Archive(url=url, path=str(repodir))
+ stage.destroy()
+
+
+@pytest.fixture(scope='session')
+def mock_git_repository():
+ """Creates a very simple git repository with two branches and
+ two commits.
+ """
+ git = spack.util.executable.which('git', required=True)
+ stage = spack.stage.Stage('mock-git-stage')
+ tmpdir = py.path.local(stage.path)
+ repo_name = 'mock-git-repo'
+ tmpdir.ensure(repo_name, dir=True)
+ repodir = tmpdir.join(repo_name)
+
+ # Initialize the repository
+ current = repodir.chdir()
+ git('init')
+ url = 'file://' + str(repodir)
+
+ # r0 is just the first commit
+ r0_file = 'r0_file'
+ repodir.ensure(r0_file)
+ git('add', r0_file)
+ git('commit', '-m', 'mock-git-repo r0')
+
+ branch = 'test-branch'
+ branch_file = 'branch_file'
+ git('branch', branch)
+
+ tag_branch = 'tag-branch'
+ tag_file = 'tag_file'
+ git('branch', tag_branch)
+
+ # Check out first branch
+ git('checkout', branch)
+ repodir.ensure(branch_file)
+ git('add', branch_file)
+ git('commit', '-m' 'r1 test branch')
+
+ # Check out a second branch and tag it
+ git('checkout', tag_branch)
+ repodir.ensure(tag_file)
+ git('add', tag_file)
+ git('commit', '-m' 'tag test branch')
+
+ tag = 'test-tag'
+ git('tag', tag)
+
+ git('checkout', 'master')
+
+ # R1 test is the same as test for branch
+ rev_hash = lambda x: git('rev-parse', x, output=str).strip()
+ r1 = rev_hash(branch)
+ r1_file = branch_file
+ current.chdir()
+
+ Bunch = spack.util.pattern.Bunch
+
+ checks = {
+ 'master': Bunch(
+ revision='master', file=r0_file, args={'git': str(repodir)}
+ ),
+ 'branch': Bunch(
+ revision=branch, file=branch_file, args={
+ 'git': str(repodir), 'branch': branch
+ }
+ ),
+ 'tag': Bunch(
+ revision=tag, file=tag_file, args={'git': str(repodir), 'tag': tag}
+ ),
+ 'commit': Bunch(
+ revision=r1, file=r1_file, args={'git': str(repodir), 'commit': r1}
+ )
+ }
+
+ t = Bunch(checks=checks, url=url, hash=rev_hash, path=str(repodir))
+ yield t
+ stage.destroy()
+
+
+@pytest.fixture(scope='session')
+def mock_hg_repository():
+ """Creates a very simple hg repository with two commits."""
+ hg = spack.util.executable.which('hg', required=True)
+ stage = spack.stage.Stage('mock-hg-stage')
+ tmpdir = py.path.local(stage.path)
+ repo_name = 'mock-hg-repo'
+ tmpdir.ensure(repo_name, dir=True)
+ repodir = tmpdir.join(repo_name)
+
+ get_rev = lambda: hg('id', '-i', output=str).strip()
+
+ # Initialize the repository
+ current = repodir.chdir()
+ url = 'file://' + str(repodir)
+ hg('init')
+ # Commit file r0
+ r0_file = 'r0_file'
+ repodir.ensure(r0_file)
+ hg('add', r0_file)
+ hg('commit', '-m', 'revision 0', '-u', 'test')
+ r0 = get_rev()
+ # Commit file r1
+ r1_file = 'r1_file'
+ repodir.ensure(r1_file)
+ hg('add', r1_file)
+ hg('commit', '-m' 'revision 1', '-u', 'test')
+ r1 = get_rev()
+ current.chdir()
+
+ Bunch = spack.util.pattern.Bunch
+
+ checks = {
+ 'default': Bunch(
+ revision=r1, file=r1_file, args={'hg': str(repodir)}
+ ),
+ 'rev0': Bunch(
+ revision=r0, file=r0_file, args={
+ 'hg': str(repodir), 'revision': r0
+ }
+ )
+ }
+ t = Bunch(checks=checks, url=url, hash=get_rev, path=str(repodir))
+ yield t
+ stage.destroy()
+
+
+@pytest.fixture(scope='session')
+def mock_svn_repository():
+ """Creates a very simple svn repository with two commits."""
+ svn = spack.util.executable.which('svn', required=True)
+ svnadmin = spack.util.executable.which('svnadmin', required=True)
+ stage = spack.stage.Stage('mock-svn-stage')
+ tmpdir = py.path.local(stage.path)
+ repo_name = 'mock-svn-repo'
+ tmpdir.ensure(repo_name, dir=True)
+ repodir = tmpdir.join(repo_name)
+ url = 'file://' + str(repodir)
+ # Initialize the repository
+ current = repodir.chdir()
+ svnadmin('create', str(repodir))
+
+ # Import a structure (first commit)
+ r0_file = 'r0_file'
+ tmpdir.ensure('tmp-path', r0_file)
+ svn(
+ 'import',
+ str(tmpdir.join('tmp-path')),
+ url,
+ '-m',
+ 'Initial import r0'
+ )
+ shutil.rmtree(str(tmpdir.join('tmp-path')))
+ # Second commit
+ r1_file = 'r1_file'
+ svn('checkout', url, str(tmpdir.join('tmp-path')))
+ tmpdir.ensure('tmp-path', r1_file)
+ tmpdir.join('tmp-path').chdir()
+ svn('add', str(tmpdir.ensure('tmp-path', r1_file)))
+ svn('ci', '-m', 'second revision r1')
+ repodir.chdir()
+ shutil.rmtree(str(tmpdir.join('tmp-path')))
+ r0 = '1'
+ r1 = '2'
+
+ Bunch = spack.util.pattern.Bunch
+
+ checks = {
+ 'default': Bunch(
+ revision=r1, file=r1_file, args={'svn': url}
+ ),
+ 'rev0': Bunch(
+ revision=r0, file=r0_file, args={
+ 'svn': url, 'revision': r0
+ }
+ )
+ }
+
+ def get_rev():
+ output = svn('info', output=str)
+ assert "Revision" in output
+ for line in output.split('\n'):
+ match = re.match(r'Revision: (\d+)', line)
+ if match:
+ return match.group(1)
+
+ t = Bunch(checks=checks, url=url, hash=get_rev, path=str(repodir))
+ yield t
+ current.chdir() \ No newline at end of file
diff --git a/lib/spack/spack/test/data/compilers.yaml b/lib/spack/spack/test/data/compilers.yaml
new file mode 100644
index 0000000000..ebba6a601d
--- /dev/null
+++ b/lib/spack/spack/test/data/compilers.yaml
@@ -0,0 +1,116 @@
+compilers:
+- compiler:
+ spec: clang@3.3
+ operating_system: {0.name}{0.version}
+ paths:
+ cc: /path/to/clang
+ cxx: /path/to/clang++
+ f77: None
+ fc: None
+ modules: 'None'
+- compiler:
+ spec: gcc@4.5.0
+ operating_system: {0.name}{0.version}
+ paths:
+ cc: /path/to/gcc
+ cxx: /path/to/g++
+ f77: None
+ fc: None
+ modules: 'None'
+- compiler:
+ spec: clang@3.3
+ operating_system: CNL
+ paths:
+ cc: /path/to/clang
+ cxx: /path/to/clang++
+ f77: None
+ fc: None
+ modules: 'None'
+- compiler:
+ spec: clang@3.3
+ operating_system: SuSE11
+ paths:
+ cc: /path/to/clang
+ cxx: /path/to/clang++
+ f77: None
+ fc: None
+ modules: 'None'
+- compiler:
+ spec: clang@3.3
+ operating_system: yosemite
+ paths:
+ cc: /path/to/clang
+ cxx: /path/to/clang++
+ f77: None
+ fc: None
+ modules: 'None'
+- compiler:
+ paths:
+ cc: /path/to/gcc
+ cxx: /path/to/g++
+ f77: /path/to/gfortran
+ fc: /path/to/gfortran
+ operating_system: CNL
+ spec: gcc@4.5.0
+ modules: 'None'
+- compiler:
+ paths:
+ cc: /path/to/gcc
+ cxx: /path/to/g++
+ f77: /path/to/gfortran
+ fc: /path/to/gfortran
+ operating_system: SuSE11
+ spec: gcc@4.5.0
+ modules: 'None'
+- compiler:
+ paths:
+ cc: /path/to/gcc
+ cxx: /path/to/g++
+ f77: /path/to/gfortran
+ fc: /path/to/gfortran
+ operating_system: yosemite
+ spec: gcc@4.5.0
+ modules: 'None'
+- compiler:
+ paths:
+ cc: /path/to/gcc
+ cxx: /path/to/g++
+ f77: /path/to/gfortran
+ fc: /path/to/gfortran
+ operating_system: elcapitan
+ spec: gcc@4.5.0
+ modules: 'None'
+- compiler:
+ spec: clang@3.3
+ operating_system: elcapitan
+ paths:
+ cc: /path/to/clang
+ cxx: /path/to/clang++
+ f77: None
+ fc: None
+ modules: 'None'
+- compiler:
+ spec: gcc@4.7.2
+ operating_system: redhat6
+ paths:
+ cc: /path/to/gcc472
+ cxx: /path/to/g++472
+ f77: /path/to/gfortran472
+ fc: /path/to/gfortran472
+ flags:
+ cflags: -O0
+ cxxflags: -O0
+ fflags: -O0
+ modules: 'None'
+- compiler:
+ spec: clang@3.5
+ operating_system: redhat6
+ paths:
+ cc: /path/to/clang35
+ cxx: /path/to/clang++35
+ f77: None
+ fc: None
+ flags:
+ cflags: -O3
+ cxxflags: -O3
+ modules: 'None'
diff --git a/lib/spack/spack/test/data/config.yaml b/lib/spack/spack/test/data/config.yaml
new file mode 100644
index 0000000000..d1758e9c16
--- /dev/null
+++ b/lib/spack/spack/test/data/config.yaml
@@ -0,0 +1,11 @@
+config:
+ install_tree: $spack/opt/spack
+ build_stage:
+ - $tempdir
+ - /nfs/tmp2/$user
+ - $spack/var/spack/stage
+ source_cache: $spack/var/spack/cache
+ misc_cache: ~/.spack/cache
+ verify_ssl: true
+ checksum: true
+ dirty: True
diff --git a/lib/spack/spack/test/data/packages.yaml b/lib/spack/spack/test/data/packages.yaml
new file mode 100644
index 0000000000..923d63173a
--- /dev/null
+++ b/lib/spack/spack/test/data/packages.yaml
@@ -0,0 +1,14 @@
+packages:
+ externaltool:
+ buildable: False
+ paths:
+ externaltool@1.0%gcc@4.5.0: /path/to/external_tool
+ externalvirtual:
+ buildable: False
+ paths:
+ externalvirtual@2.0%clang@3.3: /path/to/external_virtual_clang
+ externalvirtual@1.0%gcc@4.5.0: /path/to/external_virtual_gcc
+ externalmodule:
+ buildable: False
+ modules:
+ externalmodule@1.0%gcc@4.5.0: external-module
diff --git a/lib/spack/spack/test/database.py b/lib/spack/spack/test/database.py
index 55a1d84e20..bbaa88b91d 100644
--- a/lib/spack/spack/test/database.py
+++ b/lib/spack/spack/test/database.py
@@ -29,11 +29,10 @@ both in memory and in its file
import multiprocessing
import os.path
+import pytest
import spack
import spack.store
-from llnl.util.filesystem import join_path
from llnl.util.tty.colify import colify
-from spack.test.mock_database import MockDatabase
def _print_ref_counts():
@@ -71,264 +70,286 @@ def _print_ref_counts():
colify(recs, cols=3)
-class DatabaseTest(MockDatabase):
-
- def test_005_db_exists(self):
- """Make sure db cache file exists after creating."""
- index_file = join_path(self.install_path, '.spack-db', 'index.json')
- lock_file = join_path(self.install_path, '.spack-db', 'lock')
-
- self.assertTrue(os.path.exists(index_file))
- self.assertTrue(os.path.exists(lock_file))
-
- def test_010_all_install_sanity(self):
- """Ensure that the install layout reflects what we think it does."""
- all_specs = spack.store.layout.all_specs()
- self.assertEqual(len(all_specs), 13)
-
- # query specs with multiple configurations
- mpileaks_specs = [s for s in all_specs if s.satisfies('mpileaks')]
- callpath_specs = [s for s in all_specs if s.satisfies('callpath')]
- mpi_specs = [s for s in all_specs if s.satisfies('mpi')]
-
- self.assertEqual(len(mpileaks_specs), 3)
- self.assertEqual(len(callpath_specs), 3)
- self.assertEqual(len(mpi_specs), 3)
-
- # query specs with single configurations
- dyninst_specs = [s for s in all_specs if s.satisfies('dyninst')]
- libdwarf_specs = [s for s in all_specs if s.satisfies('libdwarf')]
- libelf_specs = [s for s in all_specs if s.satisfies('libelf')]
-
- self.assertEqual(len(dyninst_specs), 1)
- self.assertEqual(len(libdwarf_specs), 1)
- self.assertEqual(len(libelf_specs), 1)
-
- # Query by dependency
- self.assertEqual(
- len([s for s in all_specs if s.satisfies('mpileaks ^mpich')]), 1)
- self.assertEqual(
- len([s for s in all_specs if s.satisfies('mpileaks ^mpich2')]), 1)
- self.assertEqual(
- len([s for s in all_specs if s.satisfies('mpileaks ^zmpi')]), 1)
-
- def test_015_write_and_read(self):
- # write and read DB
- with spack.store.db.write_transaction():
- specs = spack.store.db.query()
- recs = [spack.store.db.get_record(s) for s in specs]
-
- for spec, rec in zip(specs, recs):
- new_rec = spack.store.db.get_record(spec)
- self.assertEqual(new_rec.ref_count, rec.ref_count)
- self.assertEqual(new_rec.spec, rec.spec)
- self.assertEqual(new_rec.path, rec.path)
- self.assertEqual(new_rec.installed, rec.installed)
-
- def _check_merkleiness(self):
- """Ensure the spack database is a valid merkle graph."""
- all_specs = spack.store.db.query(installed=any)
-
- seen = {}
- for spec in all_specs:
- for dep in spec.dependencies():
- hash_key = dep.dag_hash()
- if hash_key not in seen:
- seen[hash_key] = id(dep)
- else:
- self.assertEqual(seen[hash_key], id(dep))
-
- def _check_db_sanity(self):
- """Utiilty function to check db against install layout."""
- expected = sorted(spack.store.layout.all_specs())
- actual = sorted(self.install_db.query())
-
- self.assertEqual(len(expected), len(actual))
- for e, a in zip(expected, actual):
- self.assertEqual(e, a)
-
- self._check_merkleiness()
-
- def test_020_db_sanity(self):
- """Make sure query() returns what's actually in the db."""
- self._check_db_sanity()
-
- def test_025_reindex(self):
- """Make sure reindex works and ref counts are valid."""
- spack.store.db.reindex(spack.store.layout)
- self._check_db_sanity()
-
- def test_030_db_sanity_from_another_process(self):
- def read_and_modify():
- self._check_db_sanity() # check that other process can read DB
- with self.install_db.write_transaction():
- self._mock_remove('mpileaks ^zmpi')
-
- p = multiprocessing.Process(target=read_and_modify, args=())
- p.start()
- p.join()
-
- # ensure child process change is visible in parent process
- with self.install_db.read_transaction():
- self.assertEqual(len(self.install_db.query('mpileaks ^zmpi')), 0)
-
- def test_040_ref_counts(self):
- """Ensure that we got ref counts right when we read the DB."""
- self.install_db._check_ref_counts()
-
- def test_050_basic_query(self):
- """Ensure querying database is consistent with what is installed."""
- # query everything
- self.assertEqual(len(spack.store.db.query()), 13)
-
- # query specs with multiple configurations
- mpileaks_specs = self.install_db.query('mpileaks')
- callpath_specs = self.install_db.query('callpath')
- mpi_specs = self.install_db.query('mpi')
-
- self.assertEqual(len(mpileaks_specs), 3)
- self.assertEqual(len(callpath_specs), 3)
- self.assertEqual(len(mpi_specs), 3)
-
- # query specs with single configurations
- dyninst_specs = self.install_db.query('dyninst')
- libdwarf_specs = self.install_db.query('libdwarf')
- libelf_specs = self.install_db.query('libelf')
-
- self.assertEqual(len(dyninst_specs), 1)
- self.assertEqual(len(libdwarf_specs), 1)
- self.assertEqual(len(libelf_specs), 1)
-
- # Query by dependency
- self.assertEqual(len(self.install_db.query('mpileaks ^mpich')), 1)
- self.assertEqual(len(self.install_db.query('mpileaks ^mpich2')), 1)
- self.assertEqual(len(self.install_db.query('mpileaks ^zmpi')), 1)
-
- def _check_remove_and_add_package(self, spec):
- """Remove a spec from the DB, then add it and make sure everything's
- still ok once it is added. This checks that it was
- removed, that it's back when added again, and that ref
- counts are consistent.
- """
- original = self.install_db.query()
- self.install_db._check_ref_counts()
-
- # Remove spec
- concrete_spec = self.install_db.remove(spec)
- self.install_db._check_ref_counts()
- remaining = self.install_db.query()
-
- # ensure spec we removed is gone
- self.assertEqual(len(original) - 1, len(remaining))
- self.assertTrue(all(s in original for s in remaining))
- self.assertTrue(concrete_spec not in remaining)
-
- # add it back and make sure everything is ok.
- self.install_db.add(concrete_spec, spack.store.layout)
- installed = self.install_db.query()
- self.assertTrue(concrete_spec in installed)
- self.assertEqual(installed, original)
-
- # sanity check against direcory layout and check ref counts.
- self._check_db_sanity()
- self.install_db._check_ref_counts()
-
- def test_060_remove_and_add_root_package(self):
- self._check_remove_and_add_package('mpileaks ^mpich')
-
- def test_070_remove_and_add_dependency_package(self):
- self._check_remove_and_add_package('dyninst')
-
- def test_080_root_ref_counts(self):
- rec = self.install_db.get_record('mpileaks ^mpich')
-
- # Remove a top-level spec from the DB
- self.install_db.remove('mpileaks ^mpich')
-
- # record no longer in DB
- self.assertEqual(
- self.install_db.query('mpileaks ^mpich', installed=any), [])
-
- # record's deps have updated ref_counts
- self.assertEqual(
- self.install_db.get_record('callpath ^mpich').ref_count, 0)
- self.assertEqual(self.install_db.get_record('mpich').ref_count, 1)
-
- # Put the spec back
- self.install_db.add(rec.spec, spack.store.layout)
-
- # record is present again
- self.assertEqual(
- len(self.install_db.query('mpileaks ^mpich', installed=any)), 1)
-
- # dependencies have ref counts updated
- self.assertEqual(
- self.install_db.get_record('callpath ^mpich').ref_count, 1)
- self.assertEqual(self.install_db.get_record('mpich').ref_count, 2)
-
- def test_090_non_root_ref_counts(self):
- self.install_db.get_record('mpileaks ^mpich')
- self.install_db.get_record('callpath ^mpich')
-
- # "force remove" a non-root spec from the DB
- self.install_db.remove('callpath ^mpich')
-
- # record still in DB but marked uninstalled
- self.assertEqual(
- self.install_db.query('callpath ^mpich', installed=True), [])
- self.assertEqual(
- len(self.install_db.query('callpath ^mpich', installed=any)), 1)
-
- # record and its deps have same ref_counts
- self.assertEqual(self.install_db.get_record(
- 'callpath ^mpich', installed=any).ref_count, 1)
- self.assertEqual(self.install_db.get_record('mpich').ref_count, 2)
-
- # remove only dependent of uninstalled callpath record
- self.install_db.remove('mpileaks ^mpich')
-
- # record and parent are completely gone.
- self.assertEqual(
- self.install_db.query('mpileaks ^mpich', installed=any), [])
- self.assertEqual(
- self.install_db.query('callpath ^mpich', installed=any), [])
-
- # mpich ref count updated properly.
- mpich_rec = self.install_db.get_record('mpich')
- self.assertEqual(mpich_rec.ref_count, 0)
-
- def test_100_no_write_with_exception_on_remove(self):
- def fail_while_writing():
- with self.install_db.write_transaction():
- self._mock_remove('mpileaks ^zmpi')
- raise Exception()
-
- with self.install_db.read_transaction():
- self.assertEqual(
- len(self.install_db.query('mpileaks ^zmpi', installed=any)),
- 1)
-
- self.assertRaises(Exception, fail_while_writing)
-
- # reload DB and make sure zmpi is still there.
- with self.install_db.read_transaction():
- self.assertEqual(
- len(self.install_db.query('mpileaks ^zmpi', installed=any)),
- 1)
-
- def test_110_no_write_with_exception_on_install(self):
- def fail_while_writing():
- with self.install_db.write_transaction():
- self._mock_install('cmake')
- raise Exception()
-
- with self.install_db.read_transaction():
- self.assertEqual(
- self.install_db.query('cmake', installed=any), [])
-
- self.assertRaises(Exception, fail_while_writing)
-
- # reload DB and make sure cmake was not written.
- with self.install_db.read_transaction():
- self.assertEqual(
- self.install_db.query('cmake', installed=any), [])
+def _check_merkleiness():
+ """Ensure the spack database is a valid merkle graph."""
+ all_specs = spack.store.db.query(installed=any)
+
+ seen = {}
+ for spec in all_specs:
+ for dep in spec.dependencies():
+ hash_key = dep.dag_hash()
+ if hash_key not in seen:
+ seen[hash_key] = id(dep)
+ else:
+ assert seen[hash_key] == id(dep)
+
+
+def _check_db_sanity(install_db):
+ """Utiilty function to check db against install layout."""
+ expected = sorted(spack.store.layout.all_specs())
+ actual = sorted(install_db.query())
+
+ assert len(expected) == len(actual)
+ for e, a in zip(expected, actual):
+ assert e == a
+
+ _check_merkleiness()
+
+
+def _mock_remove(spec):
+ specs = spack.store.db.query(spec)
+ assert len(specs) == 1
+ spec = specs[0]
+ spec.package.do_uninstall(spec)
+
+
+def test_005_db_exists(database):
+ """Make sure db cache file exists after creating."""
+ install_path = database.mock.path
+ index_file = install_path.join('.spack-db', 'index.json')
+ lock_file = install_path.join('.spack-db', 'lock')
+ assert os.path.exists(str(index_file))
+ assert os.path.exists(str(lock_file))
+
+
+def test_010_all_install_sanity(database):
+ """Ensure that the install layout reflects what we think it does."""
+ all_specs = spack.store.layout.all_specs()
+ assert len(all_specs) == 13
+
+ # Query specs with multiple configurations
+ mpileaks_specs = [s for s in all_specs if s.satisfies('mpileaks')]
+ callpath_specs = [s for s in all_specs if s.satisfies('callpath')]
+ mpi_specs = [s for s in all_specs if s.satisfies('mpi')]
+
+ assert len(mpileaks_specs) == 3
+ assert len(callpath_specs) == 3
+ assert len(mpi_specs) == 3
+
+ # Query specs with single configurations
+ dyninst_specs = [s for s in all_specs if s.satisfies('dyninst')]
+ libdwarf_specs = [s for s in all_specs if s.satisfies('libdwarf')]
+ libelf_specs = [s for s in all_specs if s.satisfies('libelf')]
+
+ assert len(dyninst_specs) == 1
+ assert len(libdwarf_specs) == 1
+ assert len(libelf_specs) == 1
+
+ # Query by dependency
+ assert len([s for s in all_specs if s.satisfies('mpileaks ^mpich')]) == 1
+ assert len([s for s in all_specs if s.satisfies('mpileaks ^mpich2')]) == 1
+ assert len([s for s in all_specs if s.satisfies('mpileaks ^zmpi')]) == 1
+
+
+def test_015_write_and_read(database):
+ # write and read DB
+ with spack.store.db.write_transaction():
+ specs = spack.store.db.query()
+ recs = [spack.store.db.get_record(s) for s in specs]
+
+ for spec, rec in zip(specs, recs):
+ new_rec = spack.store.db.get_record(spec)
+ assert new_rec.ref_count == rec.ref_count
+ assert new_rec.spec == rec.spec
+ assert new_rec.path == rec.path
+ assert new_rec.installed == rec.installed
+
+
+def test_020_db_sanity(database):
+ """Make sure query() returns what's actually in the db."""
+ install_db = database.mock.db
+ _check_db_sanity(install_db)
+
+
+def test_025_reindex(database):
+ """Make sure reindex works and ref counts are valid."""
+ install_db = database.mock.db
+ spack.store.db.reindex(spack.store.layout)
+ _check_db_sanity(install_db)
+
+
+def test_030_db_sanity_from_another_process(database, refresh_db_on_exit):
+ install_db = database.mock.db
+
+ def read_and_modify():
+ _check_db_sanity(install_db) # check that other process can read DB
+ with install_db.write_transaction():
+ _mock_remove('mpileaks ^zmpi')
+
+ p = multiprocessing.Process(target=read_and_modify, args=())
+ p.start()
+ p.join()
+
+ # ensure child process change is visible in parent process
+ with install_db.read_transaction():
+ assert len(install_db.query('mpileaks ^zmpi')) == 0
+
+
+def test_040_ref_counts(database):
+ """Ensure that we got ref counts right when we read the DB."""
+ install_db = database.mock.db
+ install_db._check_ref_counts()
+
+
+def test_050_basic_query(database):
+ """Ensure querying database is consistent with what is installed."""
+ install_db = database.mock.db
+ # query everything
+ assert len(spack.store.db.query()) == 13
+
+ # query specs with multiple configurations
+ mpileaks_specs = install_db.query('mpileaks')
+ callpath_specs = install_db.query('callpath')
+ mpi_specs = install_db.query('mpi')
+
+ assert len(mpileaks_specs) == 3
+ assert len(callpath_specs) == 3
+ assert len(mpi_specs) == 3
+
+ # query specs with single configurations
+ dyninst_specs = install_db.query('dyninst')
+ libdwarf_specs = install_db.query('libdwarf')
+ libelf_specs = install_db.query('libelf')
+
+ assert len(dyninst_specs) == 1
+ assert len(libdwarf_specs) == 1
+ assert len(libelf_specs) == 1
+
+ # Query by dependency
+ assert len(install_db.query('mpileaks ^mpich')) == 1
+ assert len(install_db.query('mpileaks ^mpich2')) == 1
+ assert len(install_db.query('mpileaks ^zmpi')) == 1
+
+
+def _check_remove_and_add_package(install_db, spec):
+ """Remove a spec from the DB, then add it and make sure everything's
+ still ok once it is added. This checks that it was
+ removed, that it's back when added again, and that ref
+ counts are consistent.
+ """
+ original = install_db.query()
+ install_db._check_ref_counts()
+
+ # Remove spec
+ concrete_spec = install_db.remove(spec)
+ install_db._check_ref_counts()
+ remaining = install_db.query()
+
+ # ensure spec we removed is gone
+ assert len(original) - 1 == len(remaining)
+ assert all(s in original for s in remaining)
+ assert concrete_spec not in remaining
+
+ # add it back and make sure everything is ok.
+ install_db.add(concrete_spec, spack.store.layout)
+ installed = install_db.query()
+ assert concrete_spec in installed
+ assert installed == original
+
+ # sanity check against direcory layout and check ref counts.
+ _check_db_sanity(install_db)
+ install_db._check_ref_counts()
+
+
+def test_060_remove_and_add_root_package(database):
+ install_db = database.mock.db
+ _check_remove_and_add_package(install_db, 'mpileaks ^mpich')
+
+
+def test_070_remove_and_add_dependency_package(database):
+ install_db = database.mock.db
+ _check_remove_and_add_package(install_db, 'dyninst')
+
+
+def test_080_root_ref_counts(database):
+ install_db = database.mock.db
+ rec = install_db.get_record('mpileaks ^mpich')
+
+ # Remove a top-level spec from the DB
+ install_db.remove('mpileaks ^mpich')
+
+ # record no longer in DB
+ assert install_db.query('mpileaks ^mpich', installed=any) == []
+
+ # record's deps have updated ref_counts
+ assert install_db.get_record('callpath ^mpich').ref_count == 0
+ assert install_db.get_record('mpich').ref_count == 1
+
+ # Put the spec back
+ install_db.add(rec.spec, spack.store.layout)
+
+ # record is present again
+ assert len(install_db.query('mpileaks ^mpich', installed=any)) == 1
+
+ # dependencies have ref counts updated
+ assert install_db.get_record('callpath ^mpich').ref_count == 1
+ assert install_db.get_record('mpich').ref_count == 2
+
+
+def test_090_non_root_ref_counts(database):
+ install_db = database.mock.db
+
+ install_db.get_record('mpileaks ^mpich')
+ install_db.get_record('callpath ^mpich')
+
+ # "force remove" a non-root spec from the DB
+ install_db.remove('callpath ^mpich')
+
+ # record still in DB but marked uninstalled
+ assert install_db.query('callpath ^mpich', installed=True) == []
+ assert len(install_db.query('callpath ^mpich', installed=any)) == 1
+
+ # record and its deps have same ref_counts
+ assert install_db.get_record(
+ 'callpath ^mpich', installed=any
+ ).ref_count == 1
+ assert install_db.get_record('mpich').ref_count == 2
+
+ # remove only dependent of uninstalled callpath record
+ install_db.remove('mpileaks ^mpich')
+
+ # record and parent are completely gone.
+ assert install_db.query('mpileaks ^mpich', installed=any) == []
+ assert install_db.query('callpath ^mpich', installed=any) == []
+
+ # mpich ref count updated properly.
+ mpich_rec = install_db.get_record('mpich')
+ assert mpich_rec.ref_count == 0
+
+
+def test_100_no_write_with_exception_on_remove(database):
+ install_db = database.mock.db
+
+ def fail_while_writing():
+ with install_db.write_transaction():
+ _mock_remove('mpileaks ^zmpi')
+ raise Exception()
+
+ with install_db.read_transaction():
+ assert len(install_db.query('mpileaks ^zmpi', installed=any)) == 1
+
+ with pytest.raises(Exception):
+ fail_while_writing()
+
+ # reload DB and make sure zmpi is still there.
+ with install_db.read_transaction():
+ assert len(install_db.query('mpileaks ^zmpi', installed=any)) == 1
+
+
+def test_110_no_write_with_exception_on_install(database):
+ install_db = database.mock.db
+
+ def fail_while_writing():
+ with install_db.write_transaction():
+ _mock_install('cmake')
+ raise Exception()
+
+ with install_db.read_transaction():
+ assert install_db.query('cmake', installed=any) == []
+
+ with pytest.raises(Exception):
+ fail_while_writing()
+
+ # reload DB and make sure cmake was not written.
+ with install_db.read_transaction():
+ assert install_db.query('cmake', installed=any) == []
diff --git a/lib/spack/spack/test/directory_layout.py b/lib/spack/spack/test/directory_layout.py
index fdaa43464b..2caadad0fe 100644
--- a/lib/spack/spack/test/directory_layout.py
+++ b/lib/spack/spack/test/directory_layout.py
@@ -22,175 +22,173 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-"""\
+"""
This test verifies that the Spack directory layout works properly.
"""
import os
-import shutil
-import tempfile
+import pytest
import spack
-from llnl.util.filesystem import *
from spack.directory_layout import YamlDirectoryLayout
from spack.repository import RepoPath
from spack.spec import Spec
-from spack.test.mock_packages_test import *
# number of packages to test (to reduce test time)
max_packages = 10
-class DirectoryLayoutTest(MockPackagesTest):
- """Tests that a directory layout works correctly and produces a
- consistent install path."""
-
- def setUp(self):
- super(DirectoryLayoutTest, self).setUp()
- self.tmpdir = tempfile.mkdtemp()
- self.layout = YamlDirectoryLayout(self.tmpdir)
-
- def tearDown(self):
- super(DirectoryLayoutTest, self).tearDown()
- shutil.rmtree(self.tmpdir, ignore_errors=True)
- self.layout = None
-
- def test_read_and_write_spec(self):
- """This goes through each package in spack and creates a directory for
- it. It then ensures that the spec for the directory's
- installed package can be read back in consistently, and
- finally that the directory can be removed by the directory
- layout.
- """
- packages = list(spack.repo.all_packages())[:max_packages]
-
- for pkg in packages:
- if pkg.name.startswith('external'):
- # External package tests cannot be installed
- continue
- spec = pkg.spec
-
- # If a spec fails to concretize, just skip it. If it is a
- # real error, it will be caught by concretization tests.
- try:
- spec.concretize()
- except:
- continue
-
- self.layout.create_install_directory(spec)
-
- install_dir = self.layout.path_for_spec(spec)
- spec_path = self.layout.spec_file_path(spec)
-
- # Ensure directory has been created in right place.
- self.assertTrue(os.path.isdir(install_dir))
- self.assertTrue(install_dir.startswith(self.tmpdir))
-
- # Ensure spec file exists when directory is created
- self.assertTrue(os.path.isfile(spec_path))
- self.assertTrue(spec_path.startswith(install_dir))
-
- # Make sure spec file can be read back in to get the original spec
- spec_from_file = self.layout.read_spec(spec_path)
-
- # currently we don't store build dependency information when
- # we write out specs to the filesystem.
-
- # TODO: fix this when we can concretize more loosely based on
- # TODO: what is installed. We currently omit these to
- # TODO: increase reuse of build dependencies.
- stored_deptypes = ('link', 'run')
- expected = spec.copy(deps=stored_deptypes)
- self.assertEqual(expected, spec_from_file)
- self.assertTrue(expected.eq_dag, spec_from_file)
- self.assertTrue(spec_from_file.concrete)
-
- # Ensure that specs that come out "normal" are really normal.
- with open(spec_path) as spec_file:
- read_separately = Spec.from_yaml(spec_file.read())
-
- # TODO: revise this when build deps are in dag_hash
- norm = read_separately.normalized().copy(deps=stored_deptypes)
- self.assertEqual(norm, spec_from_file)
-
- # TODO: revise this when build deps are in dag_hash
- conc = read_separately.concretized().copy(deps=stored_deptypes)
- self.assertEqual(conc, spec_from_file)
-
- # Make sure the hash of the read-in spec is the same
- self.assertEqual(expected.dag_hash(), spec_from_file.dag_hash())
-
- # Ensure directories are properly removed
- self.layout.remove_install_directory(spec)
- self.assertFalse(os.path.isdir(install_dir))
- self.assertFalse(os.path.exists(install_dir))
-
- def test_handle_unknown_package(self):
- """This test ensures that spack can at least do *some*
- operations with packages that are installed but that it
- does not know about. This is actually not such an uncommon
- scenario with spack; it can happen when you switch from a
- git branch where you're working on a new package.
-
- This test ensures that the directory layout stores enough
- information about installed packages' specs to uninstall
- or query them again if the package goes away.
- """
- mock_db = RepoPath(spack.mock_packages_path)
-
- not_in_mock = set.difference(
- set(spack.repo.all_package_names()),
- set(mock_db.all_package_names()))
- packages = list(not_in_mock)[:max_packages]
-
- # Create all the packages that are not in mock.
- installed_specs = {}
- for pkg_name in packages:
- spec = spack.repo.get(pkg_name).spec
-
- # If a spec fails to concretize, just skip it. If it is a
- # real error, it will be caught by concretization tests.
- try:
- spec.concretize()
- except:
- continue
-
- self.layout.create_install_directory(spec)
- installed_specs[spec] = self.layout.path_for_spec(spec)
-
- spack.repo.swap(mock_db)
-
- # Now check that even without the package files, we know
- # enough to read a spec from the spec file.
- for spec, path in installed_specs.items():
- spec_from_file = self.layout.read_spec(
- join_path(path, '.spack', 'spec.yaml'))
-
- # To satisfy these conditions, directory layouts need to
- # read in concrete specs from their install dirs somehow.
- self.assertEqual(path, self.layout.path_for_spec(spec_from_file))
- self.assertEqual(spec, spec_from_file)
- self.assertTrue(spec.eq_dag(spec_from_file))
- self.assertEqual(spec.dag_hash(), spec_from_file.dag_hash())
-
- spack.repo.swap(mock_db)
-
- def test_find(self):
- """Test that finding specs within an install layout works."""
- packages = list(spack.repo.all_packages())[:max_packages]
-
- # Create install prefixes for all packages in the list
- installed_specs = {}
- for pkg in packages:
- if pkg.name.startswith('external'):
- # External package tests cannot be installed
- continue
- spec = pkg.spec.concretized()
- installed_specs[spec.name] = spec
- self.layout.create_install_directory(spec)
-
- # Make sure all the installed specs appear in
- # DirectoryLayout.all_specs()
- found_specs = dict((s.name, s) for s in self.layout.all_specs())
- for name, spec in found_specs.items():
- self.assertTrue(name in found_specs)
- self.assertTrue(found_specs[name].eq_dag(spec))
+@pytest.fixture()
+def layout_and_dir(tmpdir):
+ """Returns a directory layout and the corresponding directory."""
+ yield YamlDirectoryLayout(str(tmpdir)), str(tmpdir)
+
+
+def test_read_and_write_spec(
+ layout_and_dir, config, builtin_mock
+):
+ """This goes through each package in spack and creates a directory for
+ it. It then ensures that the spec for the directory's
+ installed package can be read back in consistently, and
+ finally that the directory can be removed by the directory
+ layout.
+ """
+ layout, tmpdir = layout_and_dir
+ packages = list(spack.repo.all_packages())[:max_packages]
+
+ for pkg in packages:
+ if pkg.name.startswith('external'):
+ # External package tests cannot be installed
+ continue
+ spec = pkg.spec
+
+ # If a spec fails to concretize, just skip it. If it is a
+ # real error, it will be caught by concretization tests.
+ try:
+ spec.concretize()
+ except Exception:
+ continue
+
+ layout.create_install_directory(spec)
+
+ install_dir = layout.path_for_spec(spec)
+ spec_path = layout.spec_file_path(spec)
+
+ # Ensure directory has been created in right place.
+ assert os.path.isdir(install_dir)
+ assert install_dir.startswith(str(tmpdir))
+
+ # Ensure spec file exists when directory is created
+ assert os.path.isfile(spec_path)
+ assert spec_path.startswith(install_dir)
+
+ # Make sure spec file can be read back in to get the original spec
+ spec_from_file = layout.read_spec(spec_path)
+
+ # currently we don't store build dependency information when
+ # we write out specs to the filesystem.
+
+ # TODO: fix this when we can concretize more loosely based on
+ # TODO: what is installed. We currently omit these to
+ # TODO: increase reuse of build dependencies.
+ stored_deptypes = ('link', 'run')
+ expected = spec.copy(deps=stored_deptypes)
+ assert expected == spec_from_file
+ assert expected.eq_dag # msg , spec_from_file
+ assert spec_from_file.concrete
+
+ # Ensure that specs that come out "normal" are really normal.
+ with open(spec_path) as spec_file:
+ read_separately = Spec.from_yaml(spec_file.read())
+
+ # TODO: revise this when build deps are in dag_hash
+ norm = read_separately.normalized().copy(deps=stored_deptypes)
+ assert norm == spec_from_file
+
+ # TODO: revise this when build deps are in dag_hash
+ conc = read_separately.concretized().copy(deps=stored_deptypes)
+ assert conc == spec_from_file
+
+ # Make sure the hash of the read-in spec is the same
+ assert expected.dag_hash() == spec_from_file.dag_hash()
+
+ # Ensure directories are properly removed
+ layout.remove_install_directory(spec)
+ assert not os.path.isdir(install_dir)
+ assert not os.path.exists(install_dir)
+
+
+def test_handle_unknown_package(
+ layout_and_dir, config, builtin_mock
+):
+ """This test ensures that spack can at least do *some*
+ operations with packages that are installed but that it
+ does not know about. This is actually not such an uncommon
+ scenario with spack; it can happen when you switch from a
+ git branch where you're working on a new package.
+
+ This test ensures that the directory layout stores enough
+ information about installed packages' specs to uninstall
+ or query them again if the package goes away.
+ """
+ layout, _ = layout_and_dir
+ mock_db = RepoPath(spack.mock_packages_path)
+
+ not_in_mock = set.difference(
+ set(spack.repo.all_package_names()),
+ set(mock_db.all_package_names()))
+ packages = list(not_in_mock)[:max_packages]
+
+ # Create all the packages that are not in mock.
+ installed_specs = {}
+ for pkg_name in packages:
+ spec = spack.repo.get(pkg_name).spec
+
+ # If a spec fails to concretize, just skip it. If it is a
+ # real error, it will be caught by concretization tests.
+ try:
+ spec.concretize()
+ except Exception:
+ continue
+
+ layout.create_install_directory(spec)
+ installed_specs[spec] = layout.path_for_spec(spec)
+
+ spack.repo.swap(mock_db)
+
+ # Now check that even without the package files, we know
+ # enough to read a spec from the spec file.
+ for spec, path in installed_specs.items():
+ spec_from_file = layout.read_spec(
+ join_path(path, '.spack', 'spec.yaml')
+ )
+ # To satisfy these conditions, directory layouts need to
+ # read in concrete specs from their install dirs somehow.
+ assert path == layout.path_for_spec(spec_from_file)
+ assert spec == spec_from_file
+ assert spec.eq_dag(spec_from_file)
+ assert spec.dag_hash() == spec_from_file.dag_hash()
+
+ spack.repo.swap(mock_db)
+
+
+def test_find(layout_and_dir, config, builtin_mock):
+ """Test that finding specs within an install layout works."""
+ layout, _ = layout_and_dir
+ packages = list(spack.repo.all_packages())[:max_packages]
+
+ # Create install prefixes for all packages in the list
+ installed_specs = {}
+ for pkg in packages:
+ if pkg.name.startswith('external'):
+ # External package tests cannot be installed
+ continue
+ spec = pkg.spec.concretized()
+ installed_specs[spec.name] = spec
+ layout.create_install_directory(spec)
+
+ # Make sure all the installed specs appear in
+ # DirectoryLayout.all_specs()
+ found_specs = dict((s.name, s) for s in layout.all_specs())
+ for name, spec in found_specs.items():
+ assert name in found_specs
+ assert found_specs[name].eq_dag(spec)
diff --git a/lib/spack/spack/test/git_fetch.py b/lib/spack/spack/test/git_fetch.py
index 7aff98cc54..3bd998c5c2 100644
--- a/lib/spack/spack/test/git_fetch.py
+++ b/lib/spack/spack/test/git_fetch.py
@@ -24,93 +24,61 @@
##############################################################################
import os
+import pytest
import spack
from llnl.util.filesystem import *
-from spack.test.mock_packages_test import *
-from spack.test.mock_repo import MockGitRepo
+from spack.spec import Spec
from spack.version import ver
-class GitFetchTest(MockPackagesTest):
- """Tests fetching from a dummy git repository."""
-
- def setUp(self):
- """Create a git repository with master and two other branches,
- and one tag, so that we can experiment on it."""
- super(GitFetchTest, self).setUp()
-
- self.repo = MockGitRepo()
-
- spec = Spec('git-test')
- spec.concretize()
- self.pkg = spack.repo.get(spec, new=True)
-
- def tearDown(self):
- """Destroy the stage space used by this test."""
- super(GitFetchTest, self).tearDown()
- self.repo.destroy()
-
- def assert_rev(self, rev):
- """Check that the current git revision is equal to the supplied rev."""
- self.assertEqual(self.repo.rev_hash('HEAD'), self.repo.rev_hash(rev))
-
- def try_fetch(self, rev, test_file, args):
- """Tries to:
-
- 1. Fetch the repo using a fetch strategy constructed with
- supplied args.
- 2. Check if the test_file is in the checked out repository.
- 3. Assert that the repository is at the revision supplied.
- 4. Add and remove some files, then reset the repo, and
- ensure it's all there again.
- """
- self.pkg.versions[ver('git')] = args
-
- with self.pkg.stage:
- self.pkg.do_stage()
- self.assert_rev(rev)
-
- file_path = join_path(self.pkg.stage.source_path, test_file)
- self.assertTrue(os.path.isdir(self.pkg.stage.source_path))
- self.assertTrue(os.path.isfile(file_path))
-
- os.unlink(file_path)
- self.assertFalse(os.path.isfile(file_path))
-
- untracked_file = 'foobarbaz'
- touch(untracked_file)
- self.assertTrue(os.path.isfile(untracked_file))
- self.pkg.do_restage()
- self.assertFalse(os.path.isfile(untracked_file))
-
- self.assertTrue(os.path.isdir(self.pkg.stage.source_path))
- self.assertTrue(os.path.isfile(file_path))
-
- self.assert_rev(rev)
-
- def test_fetch_master(self):
- """Test a default git checkout with no commit or tag specified."""
- self.try_fetch('master', self.repo.r0_file, {
- 'git': self.repo.path
- })
-
- def test_fetch_branch(self):
- """Test fetching a branch."""
- self.try_fetch(self.repo.branch, self.repo.branch_file, {
- 'git': self.repo.path,
- 'branch': self.repo.branch
- })
-
- def test_fetch_tag(self):
- """Test fetching a tag."""
- self.try_fetch(self.repo.tag, self.repo.tag_file, {
- 'git': self.repo.path,
- 'tag': self.repo.tag
- })
-
- def test_fetch_commit(self):
- """Test fetching a particular commit."""
- self.try_fetch(self.repo.r1, self.repo.r1_file, {
- 'git': self.repo.path,
- 'commit': self.repo.r1
- })
+@pytest.fixture(params=['master', 'branch', 'tag', 'commit'])
+def type_of_test(request):
+ """Returns one of the test type available for the mock_git_repository"""
+ return request.param
+
+
+def test_fetch(
+ type_of_test,
+ mock_git_repository,
+ config,
+ refresh_builtin_mock
+):
+ """Tries to:
+
+ 1. Fetch the repo using a fetch strategy constructed with
+ supplied args (they depend on type_of_test).
+ 2. Check if the test_file is in the checked out repository.
+ 3. Assert that the repository is at the revision supplied.
+ 4. Add and remove some files, then reset the repo, and
+ ensure it's all there again.
+ """
+ # Retrieve the right test parameters
+ t = mock_git_repository.checks[type_of_test]
+ h = mock_git_repository.hash
+ # Construct the package under test
+ spec = Spec('git-test')
+ spec.concretize()
+ pkg = spack.repo.get(spec, new=True)
+ pkg.versions[ver('git')] = t.args
+ # Enter the stage directory and check some properties
+ with pkg.stage:
+ pkg.do_stage()
+ assert h('HEAD') == h(t.revision)
+
+ file_path = join_path(pkg.stage.source_path, t.file)
+ assert os.path.isdir(pkg.stage.source_path)
+ assert os.path.isfile(file_path)
+
+ os.unlink(file_path)
+ assert not os.path.isfile(file_path)
+
+ untracked_file = 'foobarbaz'
+ touch(untracked_file)
+ assert os.path.isfile(untracked_file)
+ pkg.do_restage()
+ assert not os.path.isfile(untracked_file)
+
+ assert os.path.isdir(pkg.stage.source_path)
+ assert os.path.isfile(file_path)
+
+ assert h('HEAD') == h(t.revision)
diff --git a/lib/spack/spack/test/hg_fetch.py b/lib/spack/spack/test/hg_fetch.py
index 03e35ea093..71e4693c56 100644
--- a/lib/spack/spack/test/hg_fetch.py
+++ b/lib/spack/spack/test/hg_fetch.py
@@ -23,76 +23,62 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import os
-import spack
-from spack.version import ver
-from spack.test.mock_repo import MockHgRepo
+import pytest
+import spack
from llnl.util.filesystem import *
-from spack.test.mock_packages_test import *
-
-
-class HgFetchTest(MockPackagesTest):
- """Tests fetching from a dummy hg repository."""
-
- def setUp(self):
- """Create a hg repository with master and two other branches,
- and one tag, so that we can experiment on it."""
- super(HgFetchTest, self).setUp()
-
- self.repo = MockHgRepo()
-
- spec = Spec('hg-test')
- spec.concretize()
- self.pkg = spack.repo.get(spec, new=True)
-
- def tearDown(self):
- """Destroy the stage space used by this test."""
- super(HgFetchTest, self).tearDown()
- self.repo.destroy()
-
- def try_fetch(self, rev, test_file, args):
- """Tries to:
-
- 1. Fetch the repo using a fetch strategy constructed with
- supplied args.
- 2. Check if the test_file is in the checked out repository.
- 3. Assert that the repository is at the revision supplied.
- 4. Add and remove some files, then reset the repo, and
- ensure it's all there again.
- """
- self.pkg.versions[ver('hg')] = args
-
- with self.pkg.stage:
- self.pkg.do_stage()
- self.assertEqual(self.repo.get_rev(), rev)
-
- file_path = join_path(self.pkg.stage.source_path, test_file)
- self.assertTrue(os.path.isdir(self.pkg.stage.source_path))
- self.assertTrue(os.path.isfile(file_path))
-
- os.unlink(file_path)
- self.assertFalse(os.path.isfile(file_path))
-
- untracked = 'foobarbaz'
- touch(untracked)
- self.assertTrue(os.path.isfile(untracked))
- self.pkg.do_restage()
- self.assertFalse(os.path.isfile(untracked))
-
- self.assertTrue(os.path.isdir(self.pkg.stage.source_path))
- self.assertTrue(os.path.isfile(file_path))
-
- self.assertEqual(self.repo.get_rev(), rev)
+from spack.spec import Spec
+from spack.version import ver
- def test_fetch_default(self):
- """Test a default hg checkout with no commit or tag specified."""
- self.try_fetch(self.repo.r1, self.repo.r1_file, {
- 'hg': self.repo.path
- })
- def test_fetch_rev0(self):
- """Test fetching a branch."""
- self.try_fetch(self.repo.r0, self.repo.r0_file, {
- 'hg': self.repo.path,
- 'revision': self.repo.r0
- })
+@pytest.fixture(params=['default', 'rev0'])
+def type_of_test(request):
+ """Returns one of the test type available for the mock_hg_repository"""
+ return request.param
+
+
+def test_fetch(
+ type_of_test,
+ mock_hg_repository,
+ config,
+ refresh_builtin_mock
+):
+ """Tries to:
+
+ 1. Fetch the repo using a fetch strategy constructed with
+ supplied args (they depend on type_of_test).
+ 2. Check if the test_file is in the checked out repository.
+ 3. Assert that the repository is at the revision supplied.
+ 4. Add and remove some files, then reset the repo, and
+ ensure it's all there again.
+ """
+ # Retrieve the right test parameters
+ t = mock_hg_repository.checks[type_of_test]
+ h = mock_hg_repository.hash
+ # Construct the package under test
+ spec = Spec('hg-test')
+ spec.concretize()
+ pkg = spack.repo.get(spec, new=True)
+ pkg.versions[ver('hg')] = t.args
+ # Enter the stage directory and check some properties
+ with pkg.stage:
+ pkg.do_stage()
+ assert h() == t.revision
+
+ file_path = join_path(pkg.stage.source_path, t.file)
+ assert os.path.isdir(pkg.stage.source_path)
+ assert os.path.isfile(file_path)
+
+ os.unlink(file_path)
+ assert not os.path.isfile(file_path)
+
+ untracked_file = 'foobarbaz'
+ touch(untracked_file)
+ assert os.path.isfile(untracked_file)
+ pkg.do_restage()
+ assert not os.path.isfile(untracked_file)
+
+ assert os.path.isdir(pkg.stage.source_path)
+ assert os.path.isfile(file_path)
+
+ assert h() == t.revision
diff --git a/lib/spack/spack/test/install.py b/lib/spack/spack/test/install.py
index 0524c14cfd..3a83280c6f 100644
--- a/lib/spack/spack/test/install.py
+++ b/lib/spack/spack/test/install.py
@@ -22,85 +22,71 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-import shutil
-import tempfile
-
+import pytest
import spack
import spack.store
-from llnl.util.filesystem import *
-from spack.directory_layout import YamlDirectoryLayout
from spack.database import Database
+from spack.directory_layout import YamlDirectoryLayout
from spack.fetch_strategy import URLFetchStrategy, FetchStrategyComposite
-from spack.test.mock_packages_test import *
-from spack.test.mock_repo import MockArchive
-
-
-class InstallTest(MockPackagesTest):
- """Tests install and uninstall on a trivial package."""
-
- def setUp(self):
- super(InstallTest, self).setUp()
-
- # create a simple installable package directory and tarball
- self.repo = MockArchive()
-
- # We use a fake package, so skip the checksum.
- spack.do_checksum = False
-
- # Use a fake install directory to avoid conflicts bt/w
- # installed pkgs and mock packages.
- self.tmpdir = tempfile.mkdtemp()
- self.orig_layout = spack.store.layout
- self.orig_db = spack.store.db
-
- spack.store.layout = YamlDirectoryLayout(self.tmpdir)
- spack.store.db = Database(self.tmpdir)
-
- def tearDown(self):
- super(InstallTest, self).tearDown()
- self.repo.destroy()
-
- # Turn checksumming back on
- spack.do_checksum = True
-
- # restore spack's layout.
- spack.store.layout = self.orig_layout
- spack.store.db = self.orig_db
- shutil.rmtree(self.tmpdir, ignore_errors=True)
-
- def fake_fetchify(self, pkg):
- """Fake the URL for a package so it downloads from a file."""
- fetcher = FetchStrategyComposite()
- fetcher.append(URLFetchStrategy(self.repo.url))
- pkg.fetcher = fetcher
-
- def test_install_and_uninstall(self):
- # Get a basic concrete spec for the trivial install package.
- spec = Spec('trivial_install_test_package')
- spec.concretize()
- self.assertTrue(spec.concrete)
-
- # Get the package
- pkg = spack.repo.get(spec)
-
- self.fake_fetchify(pkg)
-
- try:
- pkg.do_install()
- pkg.do_uninstall()
- except Exception:
- pkg.remove_prefix()
- raise
-
- def test_store(self):
- spec = Spec('cmake-client').concretized()
-
- for s in spec.traverse():
- self.fake_fetchify(s.package)
-
- pkg = spec.package
- try:
- pkg.do_install()
- except Exception:
- pkg.remove_prefix()
- raise
+from spack.spec import Spec
+
+
+@pytest.fixture()
+def install_mockery(tmpdir, config, builtin_mock):
+ """Hooks a fake install directory and a fake db into Spack."""
+ layout = spack.store.layout
+ db = spack.store.db
+ # Use a fake install directory to avoid conflicts bt/w
+ # installed pkgs and mock packages.
+ spack.store.layout = YamlDirectoryLayout(str(tmpdir))
+ spack.store.db = Database(str(tmpdir))
+ # We use a fake package, so skip the checksum.
+ spack.do_checksum = False
+ yield
+ # Turn checksumming back on
+ spack.do_checksum = True
+ # Restore Spack's layout.
+ spack.store.layout = layout
+ spack.store.db = db
+
+
+def fake_fetchify(url, pkg):
+ """Fake the URL for a package so it downloads from a file."""
+ fetcher = FetchStrategyComposite()
+ fetcher.append(URLFetchStrategy(url))
+ pkg.fetcher = fetcher
+
+
+@pytest.mark.usefixtures('install_mockery')
+def test_install_and_uninstall(mock_archive):
+ # Get a basic concrete spec for the trivial install package.
+ spec = Spec('trivial_install_test_package')
+ spec.concretize()
+ assert spec.concrete
+
+ # Get the package
+ pkg = spack.repo.get(spec)
+
+ fake_fetchify(mock_archive.url, pkg)
+
+ try:
+ pkg.do_install()
+ pkg.do_uninstall()
+ except Exception:
+ pkg.remove_prefix()
+ raise
+
+
+@pytest.mark.usefixtures('install_mockery')
+def test_store(mock_archive):
+ spec = Spec('cmake-client').concretized()
+
+ for s in spec.traverse():
+ fake_fetchify(mock_archive.url, s.package)
+
+ pkg = spec.package
+ try:
+ pkg.do_install()
+ except Exception:
+ pkg.remove_prefix()
+ raise
diff --git a/lib/spack/spack/test/mirror.py b/lib/spack/spack/test/mirror.py
index f9ff190468..13219ef878 100644
--- a/lib/spack/spack/test/mirror.py
+++ b/lib/spack/spack/test/mirror.py
@@ -22,123 +22,127 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
+import filecmp
import os
+import pytest
+
import spack
import spack.mirror
-
-from filecmp import dircmp
-from spack.test.mock_packages_test import *
-from spack.test.mock_repo import *
+import spack.util.executable
+from llnl.util.filesystem import join_path
+from spack.spec import Spec
+from spack.stage import Stage
# paths in repos that shouldn't be in the mirror tarballs.
exclude = ['.hg', '.git', '.svn']
-
-class MirrorTest(MockPackagesTest):
-
- def setUp(self):
- """Sets up a mock package and a mock repo for each fetch strategy, to
- ensure that the mirror can create archives for each of them.
- """
- super(MirrorTest, self).setUp()
- self.repos = {}
-
- def tearDown(self):
- """Destroy all the stages created by the repos in setup."""
- super(MirrorTest, self).tearDown()
- for repo in self.repos.values():
- repo.destroy()
- self.repos.clear()
-
- def set_up_package(self, name, MockRepoClass, url_attr):
- """Set up a mock package to be mirrored.
- Each package needs us to:
-
- 1. Set up a mock repo/archive to fetch from.
- 2. Point the package's version args at that repo.
- """
- # Set up packages to point at mock repos.
- spec = Spec(name)
- spec.concretize()
-
- # Get the package and fix its fetch args to point to a mock repo
- pkg = spack.repo.get(spec)
- repo = MockRepoClass()
- self.repos[name] = repo
-
- # change the fetch args of the first (only) version.
- assert(len(pkg.versions) == 1)
- v = next(iter(pkg.versions))
- pkg.versions[v][url_attr] = repo.url
-
- def check_mirror(self):
- with Stage('spack-mirror-test') as stage:
- mirror_root = join_path(stage.path, 'test-mirror')
-
- # register mirror with spack config
- mirrors = {'spack-mirror-test': 'file://' + mirror_root}
- spack.config.update_config('mirrors', mirrors)
-
- os.chdir(stage.path)
- spack.mirror.create(
- mirror_root, self.repos, no_checksum=True)
-
- # Stage directory exists
- self.assertTrue(os.path.isdir(mirror_root))
-
- # check that there are subdirs for each package
- for name in self.repos:
- subdir = join_path(mirror_root, name)
- self.assertTrue(os.path.isdir(subdir))
-
- files = os.listdir(subdir)
- self.assertEqual(len(files), 1)
-
- # Now try to fetch each package.
- for name, mock_repo in self.repos.items():
- spec = Spec(name).concretized()
- pkg = spec.package
-
- saved_checksum_setting = spack.do_checksum
- with pkg.stage:
- # Stage the archive from the mirror and cd to it.
- spack.do_checksum = False
- pkg.do_stage(mirror_only=True)
- # Compare the original repo with the expanded archive
- original_path = mock_repo.path
- if 'svn' in name:
- # have to check out the svn repo to compare.
- original_path = join_path(
- mock_repo.path, 'checked_out')
- svn('checkout', mock_repo.url, original_path)
- dcmp = dircmp(original_path, pkg.stage.source_path)
- # make sure there are no new files in the expanded
- # tarball
- self.assertFalse(dcmp.right_only)
- # and that all original files are present.
- self.assertTrue(
- all(l in exclude for l in dcmp.left_only))
- spack.do_checksum = saved_checksum_setting
-
- def test_git_mirror(self):
- self.set_up_package('git-test', MockGitRepo, 'git')
- self.check_mirror()
-
- def test_svn_mirror(self):
- self.set_up_package('svn-test', MockSvnRepo, 'svn')
- self.check_mirror()
-
- def test_hg_mirror(self):
- self.set_up_package('hg-test', MockHgRepo, 'hg')
- self.check_mirror()
-
- def test_url_mirror(self):
- self.set_up_package('trivial_install_test_package', MockArchive, 'url')
- self.check_mirror()
-
- def test_all_mirror(self):
- self.set_up_package('git-test', MockGitRepo, 'git')
- self.set_up_package('svn-test', MockSvnRepo, 'svn')
- self.set_up_package('hg-test', MockHgRepo, 'hg')
- self.set_up_package('trivial_install_test_package', MockArchive, 'url')
- self.check_mirror()
+repos = {}
+svn = spack.util.executable.which('svn', required=True)
+
+
+def set_up_package(name, repository, url_attr):
+ """Set up a mock package to be mirrored.
+ Each package needs us to:
+
+ 1. Set up a mock repo/archive to fetch from.
+ 2. Point the package's version args at that repo.
+ """
+ # Set up packages to point at mock repos.
+ spec = Spec(name)
+ spec.concretize()
+ # Get the package and fix its fetch args to point to a mock repo
+ pkg = spack.repo.get(spec)
+
+ repos[name] = repository
+
+ # change the fetch args of the first (only) version.
+ assert len(pkg.versions) == 1
+ v = next(iter(pkg.versions))
+
+ pkg.versions[v][url_attr] = repository.url
+
+
+def check_mirror():
+ with Stage('spack-mirror-test') as stage:
+ mirror_root = join_path(stage.path, 'test-mirror')
+ # register mirror with spack config
+ mirrors = {'spack-mirror-test': 'file://' + mirror_root}
+ spack.config.update_config('mirrors', mirrors)
+
+ os.chdir(stage.path)
+ spack.mirror.create(
+ mirror_root, repos, no_checksum=True
+ )
+
+ # Stage directory exists
+ assert os.path.isdir(mirror_root)
+
+ # check that there are subdirs for each package
+ for name in repos:
+ subdir = join_path(mirror_root, name)
+ assert os.path.isdir(subdir)
+
+ files = os.listdir(subdir)
+ assert len(files) == 1
+
+ # Now try to fetch each package.
+ for name, mock_repo in repos.items():
+ spec = Spec(name).concretized()
+ pkg = spec.package
+
+ saved_checksum_setting = spack.do_checksum
+ with pkg.stage:
+ # Stage the archive from the mirror and cd to it.
+ spack.do_checksum = False
+ pkg.do_stage(mirror_only=True)
+ # Compare the original repo with the expanded archive
+ original_path = mock_repo.path
+ if 'svn' in name:
+ # have to check out the svn repo to compare.
+ original_path = join_path(
+ mock_repo.path, 'checked_out')
+ svn('checkout', mock_repo.url, original_path)
+ dcmp = filecmp.dircmp(original_path, pkg.stage.source_path)
+ # make sure there are no new files in the expanded
+ # tarball
+ assert not dcmp.right_only
+ # and that all original files are present.
+ assert all(l in exclude for l in dcmp.left_only)
+ spack.do_checksum = saved_checksum_setting
+
+
+@pytest.mark.usefixtures('config', 'refresh_builtin_mock')
+class TestMirror(object):
+ def test_git_mirror(self, mock_git_repository):
+ set_up_package('git-test', mock_git_repository, 'git')
+ check_mirror()
+ repos.clear()
+
+ def test_svn_mirror(self, mock_svn_repository):
+ set_up_package('svn-test', mock_svn_repository, 'svn')
+ check_mirror()
+ repos.clear()
+
+ def test_hg_mirror(self, mock_hg_repository):
+ set_up_package('hg-test', mock_hg_repository, 'hg')
+ check_mirror()
+ repos.clear()
+
+ def test_url_mirror(self, mock_archive):
+ set_up_package('trivial_install_test_package', mock_archive, 'url')
+ check_mirror()
+ repos.clear()
+
+ def test_all_mirror(
+ self,
+ mock_git_repository,
+ mock_svn_repository,
+ mock_hg_repository,
+ mock_archive,
+ ):
+ set_up_package('git-test', mock_git_repository, 'git')
+ set_up_package('svn-test', mock_svn_repository, 'svn')
+ set_up_package('hg-test', mock_hg_repository, 'hg')
+ set_up_package('trivial_install_test_package', mock_archive, 'url')
+ check_mirror()
+ repos.clear()
diff --git a/lib/spack/spack/test/mock_database.py b/lib/spack/spack/test/mock_database.py
deleted file mode 100644
index 1eaec2b598..0000000000
--- a/lib/spack/spack/test/mock_database.py
+++ /dev/null
@@ -1,108 +0,0 @@
-##############################################################################
-# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
-# LLNL-CODE-647188
-#
-# For details, see https://github.com/llnl/spack
-# Please also see the LICENSE file for our notice and the LGPL.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License (as
-# published by the Free Software Foundation) version 2.1, February 1999.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
-# conditions of the GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##############################################################################
-import shutil
-import tempfile
-
-import spack
-import spack.store
-from spack.spec import Spec
-from spack.database import Database
-from spack.directory_layout import YamlDirectoryLayout
-from spack.test.mock_packages_test import MockPackagesTest
-
-
-class MockDatabase(MockPackagesTest):
-
- def _mock_install(self, spec):
- s = Spec(spec)
- s.concretize()
- pkg = spack.repo.get(s)
- pkg.do_install(fake=True)
-
- def _mock_remove(self, spec):
- specs = spack.store.db.query(spec)
- assert len(specs) == 1
- spec = specs[0]
- spec.package.do_uninstall(spec)
-
- def setUp(self):
- super(MockDatabase, self).setUp()
- #
- # TODO: make the mockup below easier.
- #
-
- # Make a fake install directory
- self.install_path = tempfile.mkdtemp()
- self.spack_install_path = spack.store.root
- spack.store.root = self.install_path
-
- self.install_layout = YamlDirectoryLayout(self.install_path)
- self.spack_install_layout = spack.store.layout
- spack.store.layout = self.install_layout
-
- # Make fake database and fake install directory.
- self.install_db = Database(self.install_path)
- self.spack_install_db = spack.store.db
- spack.store.db = self.install_db
-
- # make a mock database with some packages installed note that
- # the ref count for dyninst here will be 3, as it's recycled
- # across each install.
- #
- # Here is what the mock DB looks like:
- #
- # o mpileaks o mpileaks' o mpileaks''
- # |\ |\ |\
- # | o callpath | o callpath' | o callpath''
- # |/| |/| |/|
- # o | mpich o | mpich2 o | zmpi
- # | | o | fake
- # | | |
- # | |______________/
- # | .____________/
- # |/
- # o dyninst
- # |\
- # | o libdwarf
- # |/
- # o libelf
- #
-
- # Transaction used to avoid repeated writes.
- with spack.store.db.write_transaction():
- self._mock_install('mpileaks ^mpich')
- self._mock_install('mpileaks ^mpich2')
- self._mock_install('mpileaks ^zmpi')
-
- def tearDown(self):
- with spack.store.db.write_transaction():
- for spec in spack.store.db.query():
- spec.package.do_uninstall(spec)
-
- super(MockDatabase, self).tearDown()
- shutil.rmtree(self.install_path)
- spack.store.root = self.spack_install_path
- spack.store.layout = self.spack_install_layout
- spack.store.db = self.spack_install_db
diff --git a/lib/spack/spack/test/mock_packages_test.py b/lib/spack/spack/test/mock_packages_test.py
deleted file mode 100644
index 69c52c2f53..0000000000
--- a/lib/spack/spack/test/mock_packages_test.py
+++ /dev/null
@@ -1,281 +0,0 @@
-##############################################################################
-# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
-# LLNL-CODE-647188
-#
-# For details, see https://github.com/llnl/spack
-# Please also see the LICENSE file for our notice and the LGPL.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License (as
-# published by the Free Software Foundation) version 2.1, February 1999.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
-# conditions of the GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##############################################################################
-import os
-import shutil
-import tempfile
-import unittest
-
-import spack
-import spack.config
-from llnl.util.filesystem import mkdirp
-from ordereddict_backport import OrderedDict
-from spack.repository import RepoPath
-from spack.spec import Spec
-
-platform = spack.architecture.platform()
-
-linux_os_name = 'debian'
-linux_os_version = '6'
-
-if platform.name == 'linux':
- linux_os = platform.operating_system("default_os")
- linux_os_name = linux_os.name
- linux_os_version = linux_os.version
-
-mock_compiler_config = """\
-compilers:
-- compiler:
- spec: clang@3.3
- operating_system: {0}{1}
- paths:
- cc: /path/to/clang
- cxx: /path/to/clang++
- f77: None
- fc: None
- modules: 'None'
-- compiler:
- spec: gcc@4.5.0
- operating_system: {0}{1}
- paths:
- cc: /path/to/gcc
- cxx: /path/to/g++
- f77: None
- fc: None
- modules: 'None'
-- compiler:
- spec: clang@3.3
- operating_system: CNL
- paths:
- cc: /path/to/clang
- cxx: /path/to/clang++
- f77: None
- fc: None
- modules: 'None'
-- compiler:
- spec: clang@3.3
- operating_system: SuSE11
- paths:
- cc: /path/to/clang
- cxx: /path/to/clang++
- f77: None
- fc: None
- modules: 'None'
-- compiler:
- spec: clang@3.3
- operating_system: yosemite
- paths:
- cc: /path/to/clang
- cxx: /path/to/clang++
- f77: None
- fc: None
- modules: 'None'
-- compiler:
- paths:
- cc: /path/to/gcc
- cxx: /path/to/g++
- f77: /path/to/gfortran
- fc: /path/to/gfortran
- operating_system: CNL
- spec: gcc@4.5.0
- modules: 'None'
-- compiler:
- paths:
- cc: /path/to/gcc
- cxx: /path/to/g++
- f77: /path/to/gfortran
- fc: /path/to/gfortran
- operating_system: SuSE11
- spec: gcc@4.5.0
- modules: 'None'
-- compiler:
- paths:
- cc: /path/to/gcc
- cxx: /path/to/g++
- f77: /path/to/gfortran
- fc: /path/to/gfortran
- operating_system: yosemite
- spec: gcc@4.5.0
- modules: 'None'
-- compiler:
- paths:
- cc: /path/to/gcc
- cxx: /path/to/g++
- f77: /path/to/gfortran
- fc: /path/to/gfortran
- operating_system: elcapitan
- spec: gcc@4.5.0
- modules: 'None'
-- compiler:
- spec: clang@3.3
- operating_system: elcapitan
- paths:
- cc: /path/to/clang
- cxx: /path/to/clang++
- f77: None
- fc: None
- modules: 'None'
-- compiler:
- spec: gcc@4.7.2
- operating_system: redhat6
- paths:
- cc: /path/to/gcc472
- cxx: /path/to/g++472
- f77: /path/to/gfortran472
- fc: /path/to/gfortran472
- flags:
- cflags: -O0
- cxxflags: -O0
- fflags: -O0
- modules: 'None'
-- compiler:
- spec: clang@3.5
- operating_system: redhat6
- paths:
- cc: /path/to/clang35
- cxx: /path/to/clang++35
- f77: None
- fc: None
- flags:
- cflags: -O3
- cxxflags: -O3
- modules: 'None'
-""".format(linux_os_name, linux_os_version)
-
-mock_packages_config = """\
-packages:
- externaltool:
- buildable: False
- paths:
- externaltool@1.0%gcc@4.5.0: /path/to/external_tool
- externalvirtual:
- buildable: False
- paths:
- externalvirtual@2.0%clang@3.3: /path/to/external_virtual_clang
- externalvirtual@1.0%gcc@4.5.0: /path/to/external_virtual_gcc
- externalmodule:
- buildable: False
- modules:
- externalmodule@1.0%gcc@4.5.0: external-module
-"""
-
-mock_config = """\
-config:
- install_tree: $spack/opt/spack
- build_stage:
- - $tempdir
- - /nfs/tmp2/$user
- - $spack/var/spack/stage
- source_cache: $spack/var/spack/cache
- misc_cache: ~/.spack/cache
- verify_ssl: true
- checksum: true
- dirty: True
-"""
-
-# these are written out to mock config files.
-mock_configs = {
- 'config.yaml': mock_config,
- 'compilers.yaml': mock_compiler_config,
- 'packages.yaml': mock_packages_config,
-}
-
-
-class MockPackagesTest(unittest.TestCase):
-
- def initmock(self):
- # Use the mock packages database for these tests. This allows
- # us to set up contrived packages that don't interfere with
- # real ones.
- self.db = RepoPath(spack.mock_packages_path)
- spack.repo.swap(self.db)
-
- # Mock up temporary configuration directories
- self.temp_config = tempfile.mkdtemp()
- self.mock_site_config = os.path.join(self.temp_config, 'site')
- self.mock_user_config = os.path.join(self.temp_config, 'user')
- mkdirp(self.mock_site_config)
- mkdirp(self.mock_user_config)
- for filename, data in mock_configs.items():
- conf_yaml = os.path.join(self.mock_site_config, filename)
- with open(conf_yaml, 'w') as f:
- f.write(data)
-
- # TODO: Mocking this up is kind of brittle b/c ConfigScope
- # TODO: constructor modifies config_scopes. Make it cleaner.
- spack.config.clear_config_caches()
- self.real_scopes = spack.config.config_scopes
-
- spack.config.config_scopes = OrderedDict()
- spack.config.ConfigScope('site', self.mock_site_config)
- spack.config.ConfigScope('user', self.mock_user_config)
-
- # Keep tests from interfering with the actual module path.
- self.real_share_path = spack.share_path
- spack.share_path = tempfile.mkdtemp()
-
- # Store changes to the package's dependencies so we can
- # restore later.
- self.saved_deps = {}
-
- def set_pkg_dep(self, pkg_name, spec, deptypes=spack.alldeps):
- """Alters dependence information for a package.
-
- Adds a dependency on <spec> to pkg.
- Use this to mock up constraints.
- """
- spec = Spec(spec)
-
- # Save original dependencies before making any changes.
- pkg = spack.repo.get(pkg_name)
- if pkg_name not in self.saved_deps:
- self.saved_deps[pkg_name] = (pkg, pkg.dependencies.copy())
-
- # Change dep spec
- # XXX(deptype): handle deptypes.
- pkg.dependencies[spec.name] = {Spec(pkg_name): spec}
- pkg.dependency_types[spec.name] = set(deptypes)
-
- def cleanmock(self):
- """Restore the real packages path after any test."""
- spack.repo.swap(self.db)
- spack.config.config_scopes = self.real_scopes
-
- shutil.rmtree(self.temp_config, ignore_errors=True)
- spack.config.clear_config_caches()
-
- # XXX(deptype): handle deptypes.
- # Restore dependency changes that happened during the test
- for pkg_name, (pkg, deps) in self.saved_deps.items():
- pkg.dependencies.clear()
- pkg.dependencies.update(deps)
-
- shutil.rmtree(spack.share_path, ignore_errors=True)
- spack.share_path = self.real_share_path
-
- def setUp(self):
- self.initmock()
-
- def tearDown(self):
- self.cleanmock()
diff --git a/lib/spack/spack/test/mock_repo.py b/lib/spack/spack/test/mock_repo.py
deleted file mode 100644
index 0ae7dbd516..0000000000
--- a/lib/spack/spack/test/mock_repo.py
+++ /dev/null
@@ -1,202 +0,0 @@
-##############################################################################
-# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
-# LLNL-CODE-647188
-#
-# For details, see https://github.com/llnl/spack
-# Please also see the LICENSE file for our notice and the LGPL.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License (as
-# published by the Free Software Foundation) version 2.1, February 1999.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
-# conditions of the GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##############################################################################
-import os
-import shutil
-
-from llnl.util.filesystem import *
-from spack.stage import Stage
-from spack.util.executable import which
-
-#
-# VCS Systems used by mock repo code.
-#
-git = which('git', required=True)
-svn = which('svn', required=True)
-svnadmin = which('svnadmin', required=True)
-hg = which('hg', required=True)
-tar = which('tar', required=True)
-
-
-class MockRepo(object):
-
- def __init__(self, stage_name, repo_name):
- """This creates a stage where some archive/repo files can be staged
- for testing spack's fetch strategies."""
- # Stage where this repo has been created
- self.stage = Stage(stage_name)
-
- # Full path to the repo within the stage.
- self.path = join_path(self.stage.path, repo_name)
- mkdirp(self.path)
-
- def destroy(self):
- """Destroy resources associated with this mock repo."""
- if self.stage:
- self.stage.destroy()
-
-
-class MockArchive(MockRepo):
- """Creates a very simple archive directory with a configure script and a
- makefile that installs to a prefix. Tars it up into an archive."""
-
- def __init__(self):
- repo_name = 'mock-archive-repo'
- super(MockArchive, self).__init__('mock-archive-stage', repo_name)
-
- with working_dir(self.path):
- configure = join_path(self.path, 'configure')
-
- with open(configure, 'w') as cfg_file:
- cfg_file.write(
- "#!/bin/sh\n"
- "prefix=$(echo $1 | sed 's/--prefix=//')\n"
- "cat > Makefile <<EOF\n"
- "all:\n"
- "\techo Building...\n\n"
- "install:\n"
- "\tmkdir -p $prefix\n"
- "\ttouch $prefix/dummy_file\n"
- "EOF\n")
- os.chmod(configure, 0755)
-
- with working_dir(self.stage.path):
- archive_name = "%s.tar.gz" % repo_name
- tar('-czf', archive_name, repo_name)
-
- self.archive_path = join_path(self.stage.path, archive_name)
- self.url = 'file://' + self.archive_path
-
-
-class MockVCSRepo(MockRepo):
-
- def __init__(self, stage_name, repo_name):
- """This creates a stage and a repo directory within the stage."""
- super(MockVCSRepo, self).__init__(stage_name, repo_name)
-
- # Name for rev0 & rev1 files in the repo to be
- self.r0_file = 'r0_file'
- self.r1_file = 'r1_file'
-
-
-class MockGitRepo(MockVCSRepo):
-
- def __init__(self):
- super(MockGitRepo, self).__init__('mock-git-stage', 'mock-git-repo')
-
- self.url = 'file://' + self.path
-
- with working_dir(self.path):
- git('init')
-
- # r0 is just the first commit
- touch(self.r0_file)
- git('add', self.r0_file)
- git('commit', '-m', 'mock-git-repo r0')
-
- self.branch = 'test-branch'
- self.branch_file = 'branch_file'
- git('branch', self.branch)
-
- self.tag_branch = 'tag-branch'
- self.tag_file = 'tag_file'
- git('branch', self.tag_branch)
-
- # Check out first branch
- git('checkout', self.branch)
- touch(self.branch_file)
- git('add', self.branch_file)
- git('commit', '-m' 'r1 test branch')
-
- # Check out a second branch and tag it
- git('checkout', self.tag_branch)
- touch(self.tag_file)
- git('add', self.tag_file)
- git('commit', '-m' 'tag test branch')
-
- self.tag = 'test-tag'
- git('tag', self.tag)
-
- git('checkout', 'master')
-
- # R1 test is the same as test for branch
- self.r1 = self.rev_hash(self.branch)
- self.r1_file = self.branch_file
-
- def rev_hash(self, rev):
- return git('rev-parse', rev, output=str).strip()
-
-
-class MockSvnRepo(MockVCSRepo):
-
- def __init__(self):
- super(MockSvnRepo, self).__init__('mock-svn-stage', 'mock-svn-repo')
-
- self.url = 'file://' + self.path
-
- with working_dir(self.stage.path):
- svnadmin('create', self.path)
-
- tmp_path = join_path(self.stage.path, 'tmp-path')
- mkdirp(tmp_path)
- with working_dir(tmp_path):
- touch(self.r0_file)
-
- svn('import', tmp_path, self.url, '-m', 'Initial import r0')
-
- shutil.rmtree(tmp_path)
- svn('checkout', self.url, tmp_path)
- with working_dir(tmp_path):
- touch(self.r1_file)
- svn('add', self.r1_file)
- svn('ci', '-m', 'second revision r1')
-
- shutil.rmtree(tmp_path)
-
- self.r0 = '1'
- self.r1 = '2'
-
-
-class MockHgRepo(MockVCSRepo):
-
- def __init__(self):
- super(MockHgRepo, self).__init__('mock-hg-stage', 'mock-hg-repo')
- self.url = 'file://' + self.path
-
- with working_dir(self.path):
- hg('init')
-
- touch(self.r0_file)
- hg('add', self.r0_file)
- hg('commit', '-m', 'revision 0', '-u', 'test')
- self.r0 = self.get_rev()
-
- touch(self.r1_file)
- hg('add', self.r1_file)
- hg('commit', '-m' 'revision 1', '-u', 'test')
- self.r1 = self.get_rev()
-
- def get_rev(self):
- """Get current mercurial revision."""
- return hg('id', '-i', output=str).strip()
diff --git a/lib/spack/spack/test/modules.py b/lib/spack/spack/test/modules.py
index 42f072debb..4f35df1982 100644
--- a/lib/spack/spack/test/modules.py
+++ b/lib/spack/spack/test/modules.py
@@ -23,112 +23,124 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import collections
-from contextlib import contextmanager
+import contextlib
-import StringIO
+import cStringIO
+import pytest
import spack.modules
import spack.spec
-import llnl.util.filesystem
-from spack.test.mock_packages_test import MockPackagesTest
-FILE_REGISTRY = collections.defaultdict(StringIO.StringIO)
+# Our "filesystem" for the tests below
+FILE_REGISTRY = collections.defaultdict(cStringIO.StringIO)
+# Spec strings that will be used throughout the tests
+mpich_spec_string = 'mpich@3.0.4'
+mpileaks_spec_string = 'mpileaks'
+libdwarf_spec_string = 'libdwarf arch=x64-linux'
-# Monkey-patch open to write module files to a StringIO instance
-@contextmanager
-def mock_open(filename, mode):
- if not mode == 'w':
- raise RuntimeError(
- 'test.modules : unexpected opening mode for monkey-patched open')
+@pytest.fixture()
+def stringio_open(monkeypatch):
+ """Overrides the `open` builtin in spack.modules with an implementation
+ that writes on a StringIO instance.
+ """
+ @contextlib.contextmanager
+ def _mock(filename, mode):
+ if not mode == 'w':
+ raise RuntimeError('unexpected opening mode for stringio_open')
- FILE_REGISTRY[filename] = StringIO.StringIO()
+ FILE_REGISTRY[filename] = cStringIO.StringIO()
- try:
- yield FILE_REGISTRY[filename]
- finally:
- handle = FILE_REGISTRY[filename]
- FILE_REGISTRY[filename] = handle.getvalue()
- handle.close()
+ try:
+ yield FILE_REGISTRY[filename]
+ finally:
+ handle = FILE_REGISTRY[filename]
+ FILE_REGISTRY[filename] = handle.getvalue()
+ handle.close()
+ monkeypatch.setattr(spack.modules, 'open', _mock, raising=False)
-# Spec strings that will be used throughout the tests
-mpich_spec_string = 'mpich@3.0.4'
-mpileaks_spec_string = 'mpileaks'
-libdwarf_spec_string = 'libdwarf arch=x64-linux'
+def get_modulefile_content(factory, spec):
+ """Writes the module file and returns the content as a string.
-class HelperFunctionsTests(MockPackagesTest):
-
- def test_update_dictionary_extending_list(self):
- target = {
- 'foo': {
- 'a': 1,
- 'b': 2,
- 'd': 4
- },
- 'bar': [1, 2, 4],
- 'baz': 'foobar'
- }
- update = {
- 'foo': {
- 'c': 3,
- },
- 'bar': [3],
- 'baz': 'foobaz',
- 'newkey': {
- 'd': 4
- }
- }
- spack.modules.update_dictionary_extending_lists(target, update)
- self.assertTrue(len(target) == 4)
- self.assertTrue(len(target['foo']) == 4)
- self.assertTrue(len(target['bar']) == 4)
- self.assertEqual(target['baz'], 'foobaz')
-
- def test_inspect_path(self):
- env = spack.modules.inspect_path('/usr')
- names = [item.name for item in env]
- self.assertTrue('PATH' in names)
- self.assertTrue('LIBRARY_PATH' in names)
- self.assertTrue('LD_LIBRARY_PATH' in names)
- self.assertTrue('CPATH' in names)
-
-
-class ModuleFileGeneratorTests(MockPackagesTest):
- """
- Base class to test module file generators. Relies on child having defined
- a 'factory' attribute to create an instance of the generator to be tested.
+ :param factory: module file factory
+ :param spec: spec of the module file to be written
+ :return: content of the module file
+ :rtype: str
"""
+ spec.concretize()
+ generator = factory(spec)
+ generator.write()
+ content = FILE_REGISTRY[generator.file_name].split('\n')
+ generator.remove()
+ return content
+
+
+def test_update_dictionary_extending_list():
+ target = {
+ 'foo': {
+ 'a': 1,
+ 'b': 2,
+ 'd': 4
+ },
+ 'bar': [1, 2, 4],
+ 'baz': 'foobar'
+ }
+ update = {
+ 'foo': {
+ 'c': 3,
+ },
+ 'bar': [3],
+ 'baz': 'foobaz',
+ 'newkey': {
+ 'd': 4
+ }
+ }
+ spack.modules.update_dictionary_extending_lists(target, update)
+ assert len(target) == 4
+ assert len(target['foo']) == 4
+ assert len(target['bar']) == 4
+ assert target['baz'] == 'foobaz'
- def setUp(self):
- super(ModuleFileGeneratorTests, self).setUp()
- self.configuration_instance = spack.modules._module_config
- self.module_types_instance = spack.modules.module_types
- spack.modules.open = mock_open
- spack.modules.mkdirp = lambda x: None
- # Make sure that a non-mocked configuration will trigger an error
- spack.modules._module_config = None
- spack.modules.module_types = {self.factory.name: self.factory}
-
- def tearDown(self):
- del spack.modules.open
- spack.modules.module_types = self.module_types_instance
- spack.modules._module_config = self.configuration_instance
- spack.modules.mkdirp = llnl.util.filesystem.mkdirp
- super(ModuleFileGeneratorTests, self).tearDown()
-
- def get_modulefile_content(self, spec):
- spec.concretize()
- generator = self.factory(spec)
- generator.write()
- content = FILE_REGISTRY[generator.file_name].split('\n')
- generator.remove()
- return content
+def test_inspect_path():
+ env = spack.modules.inspect_path('/usr')
+ names = [item.name for item in env]
+ assert 'PATH' in names
+ assert 'LIBRARY_PATH' in names
+ assert 'LD_LIBRARY_PATH' in names
+ assert 'CPATH' in names
-class TclTests(ModuleFileGeneratorTests):
+@pytest.fixture()
+def tcl_factory(tmpdir, monkeypatch):
+ """Returns a factory that writes non-hierarchical TCL module files."""
factory = spack.modules.TclModule
+ monkeypatch.setattr(factory, 'path', str(tmpdir))
+ monkeypatch.setattr(spack.modules, 'module_types', {factory.name: factory})
+ return factory
+
+
+@pytest.fixture()
+def lmod_factory(tmpdir, monkeypatch):
+ """Returns a factory that writes hierarchical LUA module files."""
+ factory = spack.modules.LmodModule
+ monkeypatch.setattr(factory, 'path', str(tmpdir))
+ monkeypatch.setattr(spack.modules, 'module_types', {factory.name: factory})
+ return factory
+
+
+@pytest.fixture()
+def dotkit_factory(tmpdir, monkeypatch):
+ """Returns a factory that writes DotKit module files."""
+ factory = spack.modules.Dotkit
+ monkeypatch.setattr(factory, 'path', str(tmpdir))
+ monkeypatch.setattr(spack.modules, 'module_types', {factory.name: factory})
+ return factory
+
+
+@pytest.mark.usefixtures('config', 'builtin_mock', 'stringio_open')
+class TestTcl(object):
configuration_autoload_direct = {
'enable': ['tcl'],
@@ -230,26 +242,26 @@ class TclTests(ModuleFileGeneratorTests):
}
}
- def test_simple_case(self):
+ def test_simple_case(self, tcl_factory):
spack.modules._module_config = self.configuration_autoload_direct
spec = spack.spec.Spec(mpich_spec_string)
- content = self.get_modulefile_content(spec)
- self.assertTrue('module-whatis "mpich @3.0.4"' in content)
- self.assertRaises(TypeError, spack.modules.dependencies,
- spec, 'non-existing-tag')
+ content = get_modulefile_content(tcl_factory, spec)
+ assert 'module-whatis "mpich @3.0.4"' in content
+ with pytest.raises(TypeError):
+ spack.modules.dependencies(spec, 'non-existing-tag')
- def test_autoload(self):
+ def test_autoload(self, tcl_factory):
spack.modules._module_config = self.configuration_autoload_direct
spec = spack.spec.Spec(mpileaks_spec_string)
- content = self.get_modulefile_content(spec)
- self.assertEqual(len([x for x in content if 'is-loaded' in x]), 2)
- self.assertEqual(len([x for x in content if 'module load ' in x]), 2)
+ content = get_modulefile_content(tcl_factory, spec)
+ assert len([x for x in content if 'is-loaded' in x]) == 2
+ assert len([x for x in content if 'module load ' in x]) == 2
spack.modules._module_config = self.configuration_autoload_all
spec = spack.spec.Spec(mpileaks_spec_string)
- content = self.get_modulefile_content(spec)
- self.assertEqual(len([x for x in content if 'is-loaded' in x]), 5)
- self.assertEqual(len([x for x in content if 'module load ' in x]), 5)
+ content = get_modulefile_content(tcl_factory, spec)
+ assert len([x for x in content if 'is-loaded' in x]) == 5
+ assert len([x for x in content if 'module load ' in x]) == 5
# dtbuild1 has
# - 1 ('run',) dependency
@@ -258,9 +270,9 @@ class TclTests(ModuleFileGeneratorTests):
# Just make sure the 'build' dependency is not there
spack.modules._module_config = self.configuration_autoload_direct
spec = spack.spec.Spec('dtbuild1')
- content = self.get_modulefile_content(spec)
- self.assertEqual(len([x for x in content if 'is-loaded' in x]), 2)
- self.assertEqual(len([x for x in content if 'module load ' in x]), 2)
+ content = get_modulefile_content(tcl_factory, spec)
+ assert len([x for x in content if 'is-loaded' in x]) == 2
+ assert len([x for x in content if 'module load ' in x]) == 2
# dtbuild1 has
# - 1 ('run',) dependency
@@ -269,95 +281,85 @@ class TclTests(ModuleFileGeneratorTests):
# Just make sure the 'build' dependency is not there
spack.modules._module_config = self.configuration_autoload_all
spec = spack.spec.Spec('dtbuild1')
- content = self.get_modulefile_content(spec)
- self.assertEqual(len([x for x in content if 'is-loaded' in x]), 2)
- self.assertEqual(len([x for x in content if 'module load ' in x]), 2)
+ content = get_modulefile_content(tcl_factory, spec)
+ assert len([x for x in content if 'is-loaded' in x]) == 2
+ assert len([x for x in content if 'module load ' in x]) == 2
- def test_prerequisites(self):
+ def test_prerequisites(self, tcl_factory):
spack.modules._module_config = self.configuration_prerequisites_direct
spec = spack.spec.Spec('mpileaks arch=x86-linux')
- content = self.get_modulefile_content(spec)
- self.assertEqual(len([x for x in content if 'prereq' in x]), 2)
+ content = get_modulefile_content(tcl_factory, spec)
+ assert len([x for x in content if 'prereq' in x]) == 2
spack.modules._module_config = self.configuration_prerequisites_all
spec = spack.spec.Spec('mpileaks arch=x86-linux')
- content = self.get_modulefile_content(spec)
- self.assertEqual(len([x for x in content if 'prereq' in x]), 5)
+ content = get_modulefile_content(tcl_factory, spec)
+ assert len([x for x in content if 'prereq' in x]) == 5
- def test_alter_environment(self):
+ def test_alter_environment(self, tcl_factory):
spack.modules._module_config = self.configuration_alter_environment
spec = spack.spec.Spec('mpileaks platform=test target=x86_64')
- content = self.get_modulefile_content(spec)
- self.assertEqual(
- len([x
- for x in content
- if x.startswith('prepend-path CMAKE_PREFIX_PATH')]), 0)
- self.assertEqual(
- len([x for x in content if 'setenv FOO "foo"' in x]), 1)
- self.assertEqual(len([x for x in content if 'unsetenv BAR' in x]), 1)
- self.assertEqual(
- len([x for x in content if 'setenv MPILEAKS_ROOT' in x]), 1)
+ content = get_modulefile_content(tcl_factory, spec)
+ assert len([x for x in content
+ if x.startswith('prepend-path CMAKE_PREFIX_PATH')
+ ]) == 0
+ assert len([x for x in content if 'setenv FOO "foo"' in x]) == 1
+ assert len([x for x in content if 'unsetenv BAR' in x]) == 1
+ assert len([x for x in content if 'setenv MPILEAKS_ROOT' in x]) == 1
spec = spack.spec.Spec('libdwarf %clang platform=test target=x86_32')
- content = self.get_modulefile_content(spec)
- self.assertEqual(
- len([x
- for x in content
- if x.startswith('prepend-path CMAKE_PREFIX_PATH')]), 0)
- self.assertEqual(
- len([x for x in content if 'setenv FOO "foo"' in x]), 0)
- self.assertEqual(len([x for x in content if 'unsetenv BAR' in x]), 0)
- self.assertEqual(
- len([x for x in content if 'is-loaded foo/bar' in x]), 1)
- self.assertEqual(
- len([x for x in content if 'module load foo/bar' in x]), 1)
- self.assertEqual(
- len([x for x in content if 'setenv LIBDWARF_ROOT' in x]), 1)
-
- def test_blacklist(self):
+ content = get_modulefile_content(tcl_factory, spec)
+ assert len(
+ [x for x in content if x.startswith('prepend-path CMAKE_PREFIX_PATH')] # NOQA: ignore=E501
+ ) == 0
+ assert len([x for x in content if 'setenv FOO "foo"' in x]) == 0
+ assert len([x for x in content if 'unsetenv BAR' in x]) == 0
+ assert len([x for x in content if 'is-loaded foo/bar' in x]) == 1
+ assert len([x for x in content if 'module load foo/bar' in x]) == 1
+ assert len([x for x in content if 'setenv LIBDWARF_ROOT' in x]) == 1
+
+ def test_blacklist(self, tcl_factory):
spack.modules._module_config = self.configuration_blacklist
spec = spack.spec.Spec('mpileaks ^zmpi')
- content = self.get_modulefile_content(spec)
- self.assertEqual(len([x for x in content if 'is-loaded' in x]), 1)
- self.assertEqual(len([x for x in content if 'module load ' in x]), 1)
+ content = get_modulefile_content(tcl_factory, spec)
+ assert len([x for x in content if 'is-loaded' in x]) == 1
+ assert len([x for x in content if 'module load ' in x]) == 1
spec = spack.spec.Spec('callpath arch=x86-linux')
# Returns a StringIO instead of a string as no module file was written
- self.assertRaises(AttributeError, self.get_modulefile_content, spec)
+ with pytest.raises(AttributeError):
+ get_modulefile_content(tcl_factory, spec)
spec = spack.spec.Spec('zmpi arch=x86-linux')
- content = self.get_modulefile_content(spec)
- self.assertEqual(len([x for x in content if 'is-loaded' in x]), 1)
- self.assertEqual(len([x for x in content if 'module load ' in x]), 1)
+ content = get_modulefile_content(tcl_factory, spec)
+ assert len([x for x in content if 'is-loaded' in x]) == 1
+ assert len([x for x in content if 'module load ' in x]) == 1
- def test_conflicts(self):
+ def test_conflicts(self, tcl_factory):
spack.modules._module_config = self.configuration_conflicts
spec = spack.spec.Spec('mpileaks')
- content = self.get_modulefile_content(spec)
- self.assertEqual(
- len([x for x in content if x.startswith('conflict')]), 2)
- self.assertEqual(
- len([x for x in content if x == 'conflict mpileaks']), 1)
- self.assertEqual(
- len([x for x in content if x == 'conflict intel/14.0.1']), 1)
+ content = get_modulefile_content(tcl_factory, spec)
+ assert len([x for x in content if x.startswith('conflict')]) == 2
+ assert len([x for x in content if x == 'conflict mpileaks']) == 1
+ assert len([x for x in content if x == 'conflict intel/14.0.1']) == 1
spack.modules._module_config = self.configuration_wrong_conflicts
- self.assertRaises(SystemExit, self.get_modulefile_content, spec)
+ with pytest.raises(SystemExit):
+ get_modulefile_content(tcl_factory, spec)
- def test_suffixes(self):
+ def test_suffixes(self, tcl_factory):
spack.modules._module_config = self.configuration_suffix
spec = spack.spec.Spec('mpileaks+debug arch=x86-linux')
spec.concretize()
- generator = spack.modules.TclModule(spec)
- self.assertTrue('foo' in generator.use_name)
+ generator = tcl_factory(spec)
+ assert 'foo' in generator.use_name
spec = spack.spec.Spec('mpileaks~debug arch=x86-linux')
spec.concretize()
- generator = spack.modules.TclModule(spec)
- self.assertTrue('bar' in generator.use_name)
-
+ generator = tcl_factory(spec)
+ assert 'bar' in generator.use_name
-class LmodTests(ModuleFileGeneratorTests):
- factory = spack.modules.LmodModule
+@pytest.mark.usefixtures('config', 'builtin_mock', 'stringio_open')
+class TestLmod(object):
configuration_autoload_direct = {
'enable': ['lmod'],
'lmod': {
@@ -411,83 +413,74 @@ class LmodTests(ModuleFileGeneratorTests):
}
}
- def test_simple_case(self):
+ def test_simple_case(self, lmod_factory):
spack.modules._module_config = self.configuration_autoload_direct
spec = spack.spec.Spec(mpich_spec_string)
- content = self.get_modulefile_content(spec)
- self.assertTrue('-- -*- lua -*-' in content)
- self.assertTrue('whatis([[Name : mpich]])' in content)
- self.assertTrue('whatis([[Version : 3.0.4]])' in content)
+ content = get_modulefile_content(lmod_factory, spec)
+ assert '-- -*- lua -*-' in content
+ assert 'whatis([[Name : mpich]])' in content
+ assert 'whatis([[Version : 3.0.4]])' in content
- def test_autoload(self):
+ def test_autoload(self, lmod_factory):
spack.modules._module_config = self.configuration_autoload_direct
spec = spack.spec.Spec(mpileaks_spec_string)
- content = self.get_modulefile_content(spec)
- self.assertEqual(
- len([x for x in content if 'if not isloaded(' in x]), 2)
- self.assertEqual(len([x for x in content if 'load(' in x]), 2)
+ content = get_modulefile_content(lmod_factory, spec)
+ assert len([x for x in content if 'if not isloaded(' in x]) == 2
+ assert len([x for x in content if 'load(' in x]) == 2
spack.modules._module_config = self.configuration_autoload_all
spec = spack.spec.Spec(mpileaks_spec_string)
- content = self.get_modulefile_content(spec)
- self.assertEqual(
- len([x for x in content if 'if not isloaded(' in x]), 5)
- self.assertEqual(len([x for x in content if 'load(' in x]), 5)
+ content = get_modulefile_content(lmod_factory, spec)
+ assert len([x for x in content if 'if not isloaded(' in x]) == 5
+ assert len([x for x in content if 'load(' in x]) == 5
- def test_alter_environment(self):
+ def test_alter_environment(self, lmod_factory):
spack.modules._module_config = self.configuration_alter_environment
spec = spack.spec.Spec('mpileaks platform=test target=x86_64')
- content = self.get_modulefile_content(spec)
- self.assertEqual(
- len([x
- for x in content
- if x.startswith('prepend_path("CMAKE_PREFIX_PATH"')]), 0)
- self.assertEqual(
- len([x for x in content if 'setenv("FOO", "foo")' in x]), 1)
- self.assertEqual(
- len([x for x in content if 'unsetenv("BAR")' in x]), 1)
+ content = get_modulefile_content(lmod_factory, spec)
+ assert len(
+ [x for x in content if x.startswith('prepend_path("CMAKE_PREFIX_PATH"')] # NOQA: ignore=E501
+ ) == 0
+ assert len([x for x in content if 'setenv("FOO", "foo")' in x]) == 1
+ assert len([x for x in content if 'unsetenv("BAR")' in x]) == 1
spec = spack.spec.Spec('libdwarf %clang platform=test target=x86_32')
- content = self.get_modulefile_content(spec)
- print('\n'.join(content))
- self.assertEqual(
- len([x
- for x in content
- if x.startswith('prepend-path("CMAKE_PREFIX_PATH"')]), 0)
- self.assertEqual(
- len([x for x in content if 'setenv("FOO", "foo")' in x]), 0)
- self.assertEqual(
- len([x for x in content if 'unsetenv("BAR")' in x]), 0)
-
- def test_blacklist(self):
+ content = get_modulefile_content(lmod_factory, spec)
+ assert len(
+ [x for x in content if x.startswith('prepend-path("CMAKE_PREFIX_PATH"')] # NOQA: ignore=E501
+ ) == 0
+ assert len([x for x in content if 'setenv("FOO", "foo")' in x]) == 0
+ assert len([x for x in content if 'unsetenv("BAR")' in x]) == 0
+
+ def test_blacklist(self, lmod_factory):
spack.modules._module_config = self.configuration_blacklist
spec = spack.spec.Spec(mpileaks_spec_string)
- content = self.get_modulefile_content(spec)
- self.assertEqual(
- len([x for x in content if 'if not isloaded(' in x]), 1)
- self.assertEqual(len([x for x in content if 'load(' in x]), 1)
+ content = get_modulefile_content(lmod_factory, spec)
+ assert len([x for x in content if 'if not isloaded(' in x]) == 1
+ assert len([x for x in content if 'load(' in x]) == 1
- def test_no_hash(self):
+ def test_no_hash(self, lmod_factory):
# Make sure that virtual providers (in the hierarchy) always
# include a hash. Make sure that the module file for the spec
# does not include a hash if hash_length is 0.
spack.modules._module_config = self.configuration_no_hash
spec = spack.spec.Spec(mpileaks_spec_string)
spec.concretize()
- module = spack.modules.LmodModule(spec)
+ module = lmod_factory(spec)
path = module.file_name
- mpiSpec = spec['mpi']
+ mpi_spec = spec['mpi']
mpiElement = "{0}/{1}-{2}/".format(
- mpiSpec.name, mpiSpec.version, mpiSpec.dag_hash(length=7))
- self.assertTrue(mpiElement in path)
- mpileaksSpec = spec
- mpileaksElement = "{0}/{1}.lua".format(
- mpileaksSpec.name, mpileaksSpec.version)
- self.assertTrue(path.endswith(mpileaksElement))
-
+ mpi_spec.name, mpi_spec.version, mpi_spec.dag_hash(length=7)
+ )
+ assert mpiElement in path
+ mpileaks_spec = spec
+ mpileaks_element = "{0}/{1}.lua".format(
+ mpileaks_spec.name, mpileaks_spec.version)
+ assert path.endswith(mpileaks_element)
-class DotkitTests(MockPackagesTest):
+@pytest.mark.usefixtures('config', 'builtin_mock', 'stringio_open')
+class TestDotkit(object):
configuration_dotkit = {
'enable': ['dotkit'],
'dotkit': {
@@ -497,28 +490,9 @@ class DotkitTests(MockPackagesTest):
}
}
- def setUp(self):
- super(DotkitTests, self).setUp()
- self.configuration_obj = spack.modules._module_config
- spack.modules.open = mock_open
- # Make sure that a non-mocked configuration will trigger an error
- spack.modules._module_config = None
-
- def tearDown(self):
- del spack.modules.open
- spack.modules._module_config = self.configuration_obj
- super(DotkitTests, self).tearDown()
-
- def get_modulefile_content(self, spec):
- spec.concretize()
- generator = spack.modules.Dotkit(spec)
- generator.write()
- content = FILE_REGISTRY[generator.file_name].split('\n')
- return content
-
- def test_dotkit(self):
+ def test_dotkit(self, dotkit_factory):
spack.modules._module_config = self.configuration_dotkit
spec = spack.spec.Spec('mpileaks arch=x86-linux')
- content = self.get_modulefile_content(spec)
- self.assertTrue('#c spack' in content)
- self.assertTrue('#d mpileaks @2.3' in content)
+ content = get_modulefile_content(dotkit_factory, spec)
+ assert '#c spack' in content
+ assert '#d mpileaks @2.3' in content
diff --git a/lib/spack/spack/test/multimethod.py b/lib/spack/spack/test/multimethod.py
index a885374080..90948f010c 100644
--- a/lib/spack/spack/test/multimethod.py
+++ b/lib/spack/spack/test/multimethod.py
@@ -22,93 +22,99 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-"""
-Test for multi_method dispatch.
-"""
+"""Test for multi_method dispatch."""
import spack
+import pytest
from spack.multimethod import *
from spack.version import *
-from spack.test.mock_packages_test import *
-class MultiMethodTest(MockPackagesTest):
+def test_no_version_match(builtin_mock):
+ pkg = spack.repo.get('multimethod@2.0')
+ with pytest.raises(NoSuchMethodError):
+ pkg.no_version_2()
- def test_no_version_match(self):
- pkg = spack.repo.get('multimethod@2.0')
- self.assertRaises(NoSuchMethodError, pkg.no_version_2)
- def test_one_version_match(self):
- pkg = spack.repo.get('multimethod@1.0')
- self.assertEqual(pkg.no_version_2(), 1)
+def test_one_version_match(builtin_mock):
+ pkg = spack.repo.get('multimethod@1.0')
+ assert pkg.no_version_2() == 1
- pkg = spack.repo.get('multimethod@3.0')
- self.assertEqual(pkg.no_version_2(), 3)
+ pkg = spack.repo.get('multimethod@3.0')
+ assert pkg.no_version_2() == 3
- pkg = spack.repo.get('multimethod@4.0')
- self.assertEqual(pkg.no_version_2(), 4)
+ pkg = spack.repo.get('multimethod@4.0')
+ assert pkg.no_version_2() == 4
- def test_version_overlap(self):
- pkg = spack.repo.get('multimethod@2.0')
- self.assertEqual(pkg.version_overlap(), 1)
- pkg = spack.repo.get('multimethod@5.0')
- self.assertEqual(pkg.version_overlap(), 2)
+def test_version_overlap(builtin_mock):
+ pkg = spack.repo.get('multimethod@2.0')
+ assert pkg.version_overlap() == 1
- def test_mpi_version(self):
- pkg = spack.repo.get('multimethod^mpich@3.0.4')
- self.assertEqual(pkg.mpi_version(), 3)
+ pkg = spack.repo.get('multimethod@5.0')
+ assert pkg.version_overlap() == 2
- pkg = spack.repo.get('multimethod^mpich2@1.2')
- self.assertEqual(pkg.mpi_version(), 2)
- pkg = spack.repo.get('multimethod^mpich@1.0')
- self.assertEqual(pkg.mpi_version(), 1)
+def test_mpi_version(builtin_mock):
+ pkg = spack.repo.get('multimethod^mpich@3.0.4')
+ assert pkg.mpi_version() == 3
- def test_undefined_mpi_version(self):
- pkg = spack.repo.get('multimethod^mpich@0.4')
- self.assertEqual(pkg.mpi_version(), 1)
+ pkg = spack.repo.get('multimethod^mpich2@1.2')
+ assert pkg.mpi_version() == 2
- pkg = spack.repo.get('multimethod^mpich@1.4')
- self.assertEqual(pkg.mpi_version(), 1)
+ pkg = spack.repo.get('multimethod^mpich@1.0')
+ assert pkg.mpi_version() == 1
- def test_default_works(self):
- pkg = spack.repo.get('multimethod%gcc')
- self.assertEqual(pkg.has_a_default(), 'gcc')
- pkg = spack.repo.get('multimethod%intel')
- self.assertEqual(pkg.has_a_default(), 'intel')
+def test_undefined_mpi_version(builtin_mock):
+ pkg = spack.repo.get('multimethod^mpich@0.4')
+ assert pkg.mpi_version() == 1
- pkg = spack.repo.get('multimethod%pgi')
- self.assertEqual(pkg.has_a_default(), 'default')
+ pkg = spack.repo.get('multimethod^mpich@1.4')
+ assert pkg.mpi_version() == 1
- def test_target_match(self):
- platform = spack.architecture.platform()
- targets = platform.targets.values()
- for target in targets[:-1]:
- pkg = spack.repo.get('multimethod target=' + target.name)
- self.assertEqual(pkg.different_by_target(), target.name)
- pkg = spack.repo.get('multimethod target=' + targets[-1].name)
- if len(targets) == 1:
- self.assertEqual(pkg.different_by_target(), targets[-1].name)
- else:
- self.assertRaises(NoSuchMethodError, pkg.different_by_target)
+def test_default_works(builtin_mock):
+ pkg = spack.repo.get('multimethod%gcc')
+ assert pkg.has_a_default() == 'gcc'
- def test_dependency_match(self):
- pkg = spack.repo.get('multimethod^zmpi')
- self.assertEqual(pkg.different_by_dep(), 'zmpi')
+ pkg = spack.repo.get('multimethod%intel')
+ assert pkg.has_a_default() == 'intel'
- pkg = spack.repo.get('multimethod^mpich')
- self.assertEqual(pkg.different_by_dep(), 'mpich')
+ pkg = spack.repo.get('multimethod%pgi')
+ assert pkg.has_a_default() == 'default'
- # If we try to switch on some entirely different dep, it's ambiguous,
- # but should take the first option
- pkg = spack.repo.get('multimethod^foobar')
- self.assertEqual(pkg.different_by_dep(), 'mpich')
- def test_virtual_dep_match(self):
- pkg = spack.repo.get('multimethod^mpich2')
- self.assertEqual(pkg.different_by_virtual_dep(), 2)
+def test_target_match(builtin_mock):
+ platform = spack.architecture.platform()
+ targets = platform.targets.values()
+ for target in targets[:-1]:
+ pkg = spack.repo.get('multimethod target=' + target.name)
+ assert pkg.different_by_target() == target.name
- pkg = spack.repo.get('multimethod^mpich@1.0')
- self.assertEqual(pkg.different_by_virtual_dep(), 1)
+ pkg = spack.repo.get('multimethod target=' + targets[-1].name)
+ if len(targets) == 1:
+ assert pkg.different_by_target() == targets[-1].name
+ else:
+ with pytest.raises(NoSuchMethodError):
+ pkg.different_by_target()
+
+
+def test_dependency_match(builtin_mock):
+ pkg = spack.repo.get('multimethod^zmpi')
+ assert pkg.different_by_dep() == 'zmpi'
+
+ pkg = spack.repo.get('multimethod^mpich')
+ assert pkg.different_by_dep() == 'mpich'
+
+ # If we try to switch on some entirely different dep, it's ambiguous,
+ # but should take the first option
+ pkg = spack.repo.get('multimethod^foobar')
+ assert pkg.different_by_dep() == 'mpich'
+
+
+def test_virtual_dep_match(builtin_mock):
+ pkg = spack.repo.get('multimethod^mpich2')
+ assert pkg.different_by_virtual_dep() == 2
+
+ pkg = spack.repo.get('multimethod^mpich@1.0')
+ assert pkg.different_by_virtual_dep() == 1
diff --git a/lib/spack/spack/test/optional_deps.py b/lib/spack/spack/test/optional_deps.py
index a9a2b9abf5..f013817b6f 100644
--- a/lib/spack/spack/test/optional_deps.py
+++ b/lib/spack/spack/test/optional_deps.py
@@ -22,93 +22,91 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
+import pytest
from spack.spec import Spec
-from spack.test.mock_packages_test import *
-class ConcretizeTest(MockPackagesTest):
-
- def check_normalize(self, spec_string, expected):
- spec = Spec(spec_string)
- spec.normalize()
- self.assertEqual(spec, expected)
- self.assertTrue(spec.eq_dag(expected))
-
- def test_normalize_simple_conditionals(self):
- self.check_normalize('optional-dep-test', Spec('optional-dep-test'))
- self.check_normalize('optional-dep-test~a',
- Spec('optional-dep-test~a'))
-
- self.check_normalize('optional-dep-test+a',
- Spec('optional-dep-test+a', Spec('a')))
-
- self.check_normalize('optional-dep-test a=true',
- Spec('optional-dep-test a=true', Spec('a')))
-
- self.check_normalize('optional-dep-test a=true',
- Spec('optional-dep-test+a', Spec('a')))
-
- self.check_normalize('optional-dep-test@1.1',
- Spec('optional-dep-test@1.1', Spec('b')))
-
- self.check_normalize('optional-dep-test%intel',
- Spec('optional-dep-test%intel', Spec('c')))
-
- self.check_normalize(
- 'optional-dep-test%intel@64.1',
- Spec('optional-dep-test%intel@64.1', Spec('c'), Spec('d')))
-
- self.check_normalize(
- 'optional-dep-test%intel@64.1.2',
- Spec('optional-dep-test%intel@64.1.2', Spec('c'), Spec('d')))
-
- self.check_normalize('optional-dep-test%clang@35',
- Spec('optional-dep-test%clang@35', Spec('e')))
-
- def test_multiple_conditionals(self):
- self.check_normalize(
- 'optional-dep-test+a@1.1',
- Spec('optional-dep-test+a@1.1', Spec('a'), Spec('b')))
-
- self.check_normalize(
- 'optional-dep-test+a%intel',
- Spec('optional-dep-test+a%intel', Spec('a'), Spec('c')))
-
- self.check_normalize(
- 'optional-dep-test@1.1%intel',
- Spec('optional-dep-test@1.1%intel', Spec('b'), Spec('c')))
-
- self.check_normalize('optional-dep-test@1.1%intel@64.1.2+a',
- Spec('optional-dep-test@1.1%intel@64.1.2+a',
- Spec('b'), Spec('a'), Spec('c'), Spec('d')))
-
- self.check_normalize('optional-dep-test@1.1%clang@36.5+a',
- Spec('optional-dep-test@1.1%clang@36.5+a',
- Spec('b'), Spec('a'), Spec('e')))
-
- def test_chained_mpi(self):
- self.check_normalize('optional-dep-test-2+mpi',
- Spec('optional-dep-test-2+mpi',
- Spec('optional-dep-test+mpi',
- Spec('mpi'))))
-
- def test_default_variant(self):
- spec = Spec('optional-dep-test-3')
- spec.concretize()
- self.assertTrue('a' in spec)
-
- spec = Spec('optional-dep-test-3~var')
- spec.concretize()
- self.assertTrue('a' in spec)
-
- spec = Spec('optional-dep-test-3+var')
- spec.concretize()
- self.assertTrue('b' in spec)
-
- def test_transitive_chain(self):
+@pytest.fixture(
+ params=[
+ # Normalize simple conditionals
+ ('optional-dep-test', Spec('optional-dep-test')),
+ ('optional-dep-test~a', Spec('optional-dep-test~a')),
+ ('optional-dep-test+a', Spec('optional-dep-test+a', Spec('a'))),
+ ('optional-dep-test a=true', Spec(
+ 'optional-dep-test a=true', Spec('a')
+ )),
+ ('optional-dep-test a=true', Spec('optional-dep-test+a', Spec('a'))),
+ ('optional-dep-test@1.1', Spec('optional-dep-test@1.1', Spec('b'))),
+ ('optional-dep-test%intel', Spec(
+ 'optional-dep-test%intel', Spec('c')
+ )),
+ ('optional-dep-test%intel@64.1', Spec(
+ 'optional-dep-test%intel@64.1', Spec('c'), Spec('d')
+ )),
+ ('optional-dep-test%intel@64.1.2', Spec(
+ 'optional-dep-test%intel@64.1.2', Spec('c'), Spec('d')
+ )),
+ ('optional-dep-test%clang@35', Spec(
+ 'optional-dep-test%clang@35', Spec('e')
+ )),
+ # Normalize multiple conditionals
+ ('optional-dep-test+a@1.1', Spec(
+ 'optional-dep-test+a@1.1', Spec('a'), Spec('b')
+ )),
+ ('optional-dep-test+a%intel', Spec(
+ 'optional-dep-test+a%intel', Spec('a'), Spec('c')
+ )),
+ ('optional-dep-test@1.1%intel', Spec(
+ 'optional-dep-test@1.1%intel', Spec('b'), Spec('c')
+ )),
+ ('optional-dep-test@1.1%intel@64.1.2+a', Spec(
+ 'optional-dep-test@1.1%intel@64.1.2+a',
+ Spec('b'),
+ Spec('a'),
+ Spec('c'),
+ Spec('d')
+ )),
+ ('optional-dep-test@1.1%clang@36.5+a', Spec(
+ 'optional-dep-test@1.1%clang@36.5+a',
+ Spec('b'),
+ Spec('a'),
+ Spec('e')
+ )),
+ # Chained MPI
+ ('optional-dep-test-2+mpi', Spec(
+ 'optional-dep-test-2+mpi',
+ Spec('optional-dep-test+mpi', Spec('mpi'))
+ )),
# Each of these dependencies comes from a conditional
# dependency on another. This requires iterating to evaluate
# the whole chain.
- self.check_normalize(
- 'optional-dep-test+f',
- Spec('optional-dep-test+f', Spec('f'), Spec('g'), Spec('mpi')))
+ ('optional-dep-test+f', Spec(
+ 'optional-dep-test+f', Spec('f'), Spec('g'), Spec('mpi')
+ ))
+ ]
+)
+def spec_and_expected(request):
+ """Parameters for te normalization test."""
+ return request.param
+
+
+def test_normalize(spec_and_expected, config, builtin_mock):
+ spec, expected = spec_and_expected
+ spec = Spec(spec)
+ spec.normalize()
+ assert spec == expected
+ assert spec.eq_dag(expected)
+
+
+def test_default_variant(config, builtin_mock):
+ spec = Spec('optional-dep-test-3')
+ spec.concretize()
+ assert 'a' in spec
+
+ spec = Spec('optional-dep-test-3~var')
+ spec.concretize()
+ assert 'a' in spec
+
+ spec = Spec('optional-dep-test-3+var')
+ spec.concretize()
+ assert 'b' in spec
diff --git a/lib/spack/spack/test/packages.py b/lib/spack/spack/test/packages.py
index 1217568c9c..39bbe4a954 100644
--- a/lib/spack/spack/test/packages.py
+++ b/lib/spack/spack/test/packages.py
@@ -25,93 +25,106 @@
import spack
from llnl.util.filesystem import join_path
from spack.repository import Repo
-from spack.test.mock_packages_test import *
from spack.util.naming import mod_to_class
from spack.spec import *
-class PackagesTest(MockPackagesTest):
-
- def test_load_package(self):
- spack.repo.get('mpich')
-
- def test_package_name(self):
- pkg = spack.repo.get('mpich')
- self.assertEqual(pkg.name, 'mpich')
-
- def test_package_filename(self):
- repo = Repo(spack.mock_packages_path)
- filename = repo.filename_for_package_name('mpich')
- self.assertEqual(filename,
- join_path(spack.mock_packages_path,
- 'packages', 'mpich', 'package.py'))
-
- def test_nonexisting_package_filename(self):
- repo = Repo(spack.mock_packages_path)
- filename = repo.filename_for_package_name('some-nonexisting-package')
- self.assertEqual(
- filename,
- join_path(spack.mock_packages_path,
- 'packages', 'some-nonexisting-package', 'package.py'))
-
- def test_package_class_names(self):
- self.assertEqual('Mpich', mod_to_class('mpich'))
- self.assertEqual('PmgrCollective', mod_to_class('pmgr_collective'))
- self.assertEqual('PmgrCollective', mod_to_class('pmgr-collective'))
- self.assertEqual('Pmgrcollective', mod_to_class('PmgrCollective'))
- self.assertEqual('_3db', mod_to_class('3db'))
-
- #
- # Below tests target direct imports of spack packages from the
- # spack.pkg namespace
- #
-
- def test_import_package(self):
- import spack.pkg.builtin.mock.mpich # noqa
-
- def test_import_package_as(self):
- import spack.pkg.builtin.mock.mpich as mp # noqa
-
- def test_import_class_from_package(self):
- from spack.pkg.builtin.mock.mpich import Mpich # noqa
-
- def test_import_module_from_package(self):
- from spack.pkg.builtin.mock import mpich # noqa
-
- def test_import_namespace_container_modules(self):
- import spack.pkg # noqa
- import spack.pkg as p # noqa
- from spack import pkg # noqa
-
- import spack.pkg.builtin # noqa
- import spack.pkg.builtin as b # noqa
- from spack.pkg import builtin # noqa
-
- import spack.pkg.builtin.mock # noqa
- import spack.pkg.builtin.mock as m # noqa
- from spack.pkg.builtin import mock # noqa
-
- def test_inheritance_of_diretives(self):
- p = spack.repo.get('simple_inheritance')
-
- # Check dictionaries that should have been filled by directives
- self.assertEqual(len(p.dependencies), 3)
- self.assertTrue('cmake' in p.dependencies)
- self.assertTrue('openblas' in p.dependencies)
- self.assertTrue('mpi' in p.dependencies)
- self.assertEqual(len(p.provided), 2)
-
- # Check that Spec instantiation behaves as we expect
- s = Spec('simple_inheritance')
- s.concretize()
- self.assertTrue('^cmake' in s)
- self.assertTrue('^openblas' in s)
- self.assertTrue('+openblas' in s)
- self.assertTrue('mpi' in s)
-
- s = Spec('simple_inheritance~openblas')
- s.concretize()
- self.assertTrue('^cmake' in s)
- self.assertTrue('^openblas' not in s)
- self.assertTrue('~openblas' in s)
- self.assertTrue('mpi' in s)
+def test_load_package(builtin_mock):
+ spack.repo.get('mpich')
+
+
+def test_package_name(builtin_mock):
+ pkg = spack.repo.get('mpich')
+ assert pkg.name == 'mpich'
+
+
+def test_package_filename(builtin_mock):
+ repo = Repo(spack.mock_packages_path)
+ filename = repo.filename_for_package_name('mpich')
+ assert filename == join_path(
+ spack.mock_packages_path,
+ 'packages',
+ 'mpich',
+ 'package.py'
+ )
+
+
+def test_nonexisting_package_filename():
+ repo = Repo(spack.mock_packages_path)
+ filename = repo.filename_for_package_name('some-nonexisting-package')
+ assert filename == join_path(
+ spack.mock_packages_path,
+ 'packages',
+ 'some-nonexisting-package',
+ 'package.py'
+ )
+
+
+def test_package_class_names():
+ assert 'Mpich' == mod_to_class('mpich')
+ assert 'PmgrCollective' == mod_to_class('pmgr_collective')
+ assert 'PmgrCollective' == mod_to_class('pmgr-collective')
+ assert 'Pmgrcollective' == mod_to_class('PmgrCollective')
+ assert '_3db' == mod_to_class('3db')
+
+
+# Below tests target direct imports of spack packages from the
+# spack.pkg namespace
+def test_import_package(builtin_mock):
+ import spack.pkg.builtin.mock.mpich # noqa
+
+
+def test_import_package_as(builtin_mock):
+ import spack.pkg.builtin.mock.mpich as mp # noqa
+
+ import spack.pkg.builtin.mock # noqa
+ import spack.pkg.builtin.mock as m # noqa
+ from spack.pkg.builtin import mock # noqa
+
+
+def test_inheritance_of_diretives():
+ p = spack.repo.get('simple_inheritance')
+
+ # Check dictionaries that should have been filled by directives
+ assert len(p.dependencies) == 3
+ assert 'cmake' in p.dependencies
+ assert 'openblas' in p.dependencies
+ assert 'mpi' in p.dependencies
+ assert len(p.provided) == 2
+
+ # Check that Spec instantiation behaves as we expect
+ s = Spec('simple_inheritance')
+ s.concretize()
+ assert '^cmake' in s
+ assert '^openblas' in s
+ assert '+openblas' in s
+ assert 'mpi' in s
+
+ s = Spec('simple_inheritance~openblas')
+ s.concretize()
+ assert '^cmake' in s
+ assert '^openblas' not in s
+ assert '~openblas' in s
+ assert 'mpi' in s
+
+
+def test_import_class_from_package(builtin_mock):
+ from spack.pkg.builtin.mock.mpich import Mpich # noqa
+
+
+def test_import_module_from_package(builtin_mock):
+ from spack.pkg.builtin.mock import mpich # noqa
+
+
+def test_import_namespace_container_modules(builtin_mock):
+ import spack.pkg # noqa
+ import spack.pkg as p # noqa
+ from spack import pkg # noqa
+
+ import spack.pkg.builtin # noqa
+ import spack.pkg.builtin as b # noqa
+ from spack.pkg import builtin # noqa
+
+ import spack.pkg.builtin.mock # noqa
+ import spack.pkg.builtin.mock as m # noqa
+ from spack.pkg.builtin import mock # noqa
diff --git a/lib/spack/spack/test/provider_index.py b/lib/spack/spack/test/provider_index.py
index d785038899..a176d0c315 100644
--- a/lib/spack/spack/test/provider_index.py
+++ b/lib/spack/spack/test/provider_index.py
@@ -37,57 +37,57 @@ Tests assume that mock packages provide this::
mpi@:10.0: set([zmpi])},
'stuff': {stuff: set([externalvirtual])}}
"""
-from StringIO import StringIO
-
+import StringIO
import spack
-from spack.spec import Spec
from spack.provider_index import ProviderIndex
-from spack.test.mock_packages_test import *
+from spack.spec import Spec
+
+
+def test_yaml_round_trip(builtin_mock):
+ p = ProviderIndex(spack.repo.all_package_names())
+
+ ostream = StringIO.StringIO()
+ p.to_yaml(ostream)
+ istream = StringIO.StringIO(ostream.getvalue())
+ q = ProviderIndex.from_yaml(istream)
-class ProviderIndexTest(MockPackagesTest):
+ assert p == q
- def test_yaml_round_trip(self):
- p = ProviderIndex(spack.repo.all_package_names())
- ostream = StringIO()
- p.to_yaml(ostream)
+def test_providers_for_simple(builtin_mock):
+ p = ProviderIndex(spack.repo.all_package_names())
- istream = StringIO(ostream.getvalue())
- q = ProviderIndex.from_yaml(istream)
+ blas_providers = p.providers_for('blas')
+ assert Spec('netlib-blas') in blas_providers
+ assert Spec('openblas') in blas_providers
+ assert Spec('openblas-with-lapack') in blas_providers
- self.assertEqual(p, q)
+ lapack_providers = p.providers_for('lapack')
+ assert Spec('netlib-lapack') in lapack_providers
+ assert Spec('openblas-with-lapack') in lapack_providers
- def test_providers_for_simple(self):
- p = ProviderIndex(spack.repo.all_package_names())
- blas_providers = p.providers_for('blas')
- self.assertTrue(Spec('netlib-blas') in blas_providers)
- self.assertTrue(Spec('openblas') in blas_providers)
- self.assertTrue(Spec('openblas-with-lapack') in blas_providers)
+def test_mpi_providers(builtin_mock):
+ p = ProviderIndex(spack.repo.all_package_names())
- lapack_providers = p.providers_for('lapack')
- self.assertTrue(Spec('netlib-lapack') in lapack_providers)
- self.assertTrue(Spec('openblas-with-lapack') in lapack_providers)
+ mpi_2_providers = p.providers_for('mpi@2')
+ assert Spec('mpich2') in mpi_2_providers
+ assert Spec('mpich@3:') in mpi_2_providers
- def test_mpi_providers(self):
- p = ProviderIndex(spack.repo.all_package_names())
+ mpi_3_providers = p.providers_for('mpi@3')
+ assert Spec('mpich2') not in mpi_3_providers
+ assert Spec('mpich@3:') in mpi_3_providers
+ assert Spec('zmpi') in mpi_3_providers
- mpi_2_providers = p.providers_for('mpi@2')
- self.assertTrue(Spec('mpich2') in mpi_2_providers)
- self.assertTrue(Spec('mpich@3:') in mpi_2_providers)
- mpi_3_providers = p.providers_for('mpi@3')
- self.assertTrue(Spec('mpich2') not in mpi_3_providers)
- self.assertTrue(Spec('mpich@3:') in mpi_3_providers)
- self.assertTrue(Spec('zmpi') in mpi_3_providers)
+def test_equal(builtin_mock):
+ p = ProviderIndex(spack.repo.all_package_names())
+ q = ProviderIndex(spack.repo.all_package_names())
+ assert p == q
- def test_equal(self):
- p = ProviderIndex(spack.repo.all_package_names())
- q = ProviderIndex(spack.repo.all_package_names())
- self.assertEqual(p, q)
- def test_copy(self):
- p = ProviderIndex(spack.repo.all_package_names())
- q = p.copy()
- self.assertEqual(p, q)
+def test_copy(builtin_mock):
+ p = ProviderIndex(spack.repo.all_package_names())
+ q = p.copy()
+ assert p == q
diff --git a/lib/spack/spack/test/spec_dag.py b/lib/spack/spack/test/spec_dag.py
index dd536f945c..e1d3f24e07 100644
--- a/lib/spack/spack/test/spec_dag.py
+++ b/lib/spack/spack/test/spec_dag.py
@@ -28,26 +28,64 @@ You can find the dummy packages here::
spack/lib/spack/spack/test/mock_packages
"""
+import pytest
import spack
import spack.architecture
import spack.package
from spack.spec import Spec
-from spack.test.mock_packages_test import *
-class SpecDagTest(MockPackagesTest):
+def check_links(spec_to_check):
+ for spec in spec_to_check.traverse():
+ for dependent in spec.dependents():
+ assert spec.name in dependent.dependencies_dict()
- def test_conflicting_package_constraints(self):
- self.set_pkg_dep('mpileaks', 'mpich@1.0')
- self.set_pkg_dep('callpath', 'mpich@2.0')
+ for dependency in spec.dependencies():
+ assert spec.name in dependency.dependents_dict()
+
+
+@pytest.fixture()
+def saved_deps():
+ """Returns a dictionary to save the dependencies."""
+ return {}
+
+
+@pytest.fixture()
+def set_dependency(saved_deps):
+ """Returns a function that alters the dependency information
+ for a package.
+ """
+ def _mock(pkg_name, spec, deptypes=spack.alldeps):
+ """Alters dependence information for a package.
+
+ Adds a dependency on <spec> to pkg. Use this to mock up constraints.
+ """
+ spec = Spec(spec)
+ # Save original dependencies before making any changes.
+ pkg = spack.repo.get(pkg_name)
+ if pkg_name not in saved_deps:
+ saved_deps[pkg_name] = (pkg, pkg.dependencies.copy())
+ # Change dep spec
+ # XXX(deptype): handle deptypes.
+ pkg.dependencies[spec.name] = {Spec(pkg_name): spec}
+ pkg.dependency_types[spec.name] = set(deptypes)
+ return _mock
+
+
+@pytest.mark.usefixtures('refresh_builtin_mock')
+class TestSpecDag(object):
+
+ def test_conflicting_package_constraints(self, set_dependency):
+ set_dependency('mpileaks', 'mpich@1.0')
+ set_dependency('callpath', 'mpich@2.0')
spec = Spec('mpileaks ^mpich ^callpath ^dyninst ^libelf ^libdwarf')
- # TODO: try to do something to showt that the issue was with
+ # TODO: try to do something to show that the issue was with
# TODO: the user's input or with package inconsistencies.
- self.assertRaises(spack.spec.UnsatisfiableVersionSpecError,
- spec.normalize)
+ with pytest.raises(spack.spec.UnsatisfiableVersionSpecError):
+ spec.normalize()
def test_preorder_node_traversal(self):
dag = Spec('mpileaks ^zmpi')
@@ -58,10 +96,10 @@ class SpecDagTest(MockPackagesTest):
pairs = zip([0, 1, 2, 3, 4, 2, 3], names)
traversal = dag.traverse()
- self.assertEqual([x.name for x in traversal], names)
+ assert [x.name for x in traversal] == names
traversal = dag.traverse(depth=True)
- self.assertEqual([(x, y.name) for x, y in traversal], pairs)
+ assert [(x, y.name) for x, y in traversal] == pairs
def test_preorder_edge_traversal(self):
dag = Spec('mpileaks ^zmpi')
@@ -72,10 +110,10 @@ class SpecDagTest(MockPackagesTest):
pairs = zip([0, 1, 2, 3, 4, 3, 2, 3, 1], names)
traversal = dag.traverse(cover='edges')
- self.assertEqual([x.name for x in traversal], names)
+ assert [x.name for x in traversal] == names
traversal = dag.traverse(cover='edges', depth=True)
- self.assertEqual([(x, y.name) for x, y in traversal], pairs)
+ assert [(x, y.name) for x, y in traversal] == pairs
def test_preorder_path_traversal(self):
dag = Spec('mpileaks ^zmpi')
@@ -86,10 +124,10 @@ class SpecDagTest(MockPackagesTest):
pairs = zip([0, 1, 2, 3, 4, 3, 2, 3, 1, 2], names)
traversal = dag.traverse(cover='paths')
- self.assertEqual([x.name for x in traversal], names)
+ assert [x.name for x in traversal] == names
traversal = dag.traverse(cover='paths', depth=True)
- self.assertEqual([(x, y.name) for x, y in traversal], pairs)
+ assert [(x, y.name) for x, y in traversal] == pairs
def test_postorder_node_traversal(self):
dag = Spec('mpileaks ^zmpi')
@@ -100,10 +138,10 @@ class SpecDagTest(MockPackagesTest):
pairs = zip([4, 3, 2, 3, 2, 1, 0], names)
traversal = dag.traverse(order='post')
- self.assertEqual([x.name for x in traversal], names)
+ assert [x.name for x in traversal] == names
traversal = dag.traverse(depth=True, order='post')
- self.assertEqual([(x, y.name) for x, y in traversal], pairs)
+ assert [(x, y.name) for x, y in traversal] == pairs
def test_postorder_edge_traversal(self):
dag = Spec('mpileaks ^zmpi')
@@ -114,10 +152,10 @@ class SpecDagTest(MockPackagesTest):
pairs = zip([4, 3, 3, 2, 3, 2, 1, 1, 0], names)
traversal = dag.traverse(cover='edges', order='post')
- self.assertEqual([x.name for x in traversal], names)
+ assert [x.name for x in traversal] == names
traversal = dag.traverse(cover='edges', depth=True, order='post')
- self.assertEqual([(x, y.name) for x, y in traversal], pairs)
+ assert [(x, y.name) for x, y in traversal] == pairs
def test_postorder_path_traversal(self):
dag = Spec('mpileaks ^zmpi')
@@ -128,10 +166,10 @@ class SpecDagTest(MockPackagesTest):
pairs = zip([4, 3, 3, 2, 3, 2, 1, 2, 1, 0], names)
traversal = dag.traverse(cover='paths', order='post')
- self.assertEqual([x.name for x in traversal], names)
+ assert [x.name for x in traversal] == names
traversal = dag.traverse(cover='paths', depth=True, order='post')
- self.assertEqual([(x, y.name) for x, y in traversal], pairs)
+ assert [(x, y.name) for x, y in traversal] == pairs
def test_conflicting_spec_constraints(self):
mpileaks = Spec('mpileaks ^mpich ^callpath ^dyninst ^libelf ^libdwarf')
@@ -143,8 +181,8 @@ class SpecDagTest(MockPackagesTest):
mpileaks._dependencies['callpath']. \
spec._dependencies['mpich'].spec = Spec('mpich@2.0')
- self.assertRaises(spack.spec.InconsistentSpecError,
- lambda: mpileaks.flat_dependencies(copy=False))
+ with pytest.raises(spack.spec.InconsistentSpecError):
+ mpileaks.flat_dependencies(copy=False)
def test_normalize_twice(self):
"""Make sure normalize can be run twice on the same spec,
@@ -154,7 +192,7 @@ class SpecDagTest(MockPackagesTest):
n1 = spec.copy()
spec.normalize()
- self.assertEqual(n1, spec)
+ assert n1 == spec
def test_normalize_a_lot(self):
spec = Spec('mpileaks')
@@ -182,21 +220,7 @@ class SpecDagTest(MockPackagesTest):
counts[spec.name] += 1
for name in counts:
- self.assertEqual(counts[name], 1, "Count for %s was not 1!" % name)
-
- def check_links(self, spec_to_check):
- for spec in spec_to_check.traverse():
- for dependent in spec.dependents():
- self.assertTrue(
- spec.name in dependent.dependencies_dict(),
- "%s not in dependencies of %s" %
- (spec.name, dependent.name))
-
- for dependency in spec.dependencies():
- self.assertTrue(
- spec.name in dependency.dependents_dict(),
- "%s not in dependents of %s" %
- (spec.name, dependency.name))
+ assert counts[name] == 1
def test_dependents_and_dependencies_are_correct(self):
spec = Spec('mpileaks',
@@ -208,49 +232,49 @@ class SpecDagTest(MockPackagesTest):
Spec('mpi')),
Spec('mpi'))
- self.check_links(spec)
+ check_links(spec)
spec.normalize()
- self.check_links(spec)
+ check_links(spec)
- def test_unsatisfiable_version(self):
- self.set_pkg_dep('mpileaks', 'mpich@1.0')
+ def test_unsatisfiable_version(self, set_dependency):
+ set_dependency('mpileaks', 'mpich@1.0')
spec = Spec('mpileaks ^mpich@2.0 ^callpath ^dyninst ^libelf ^libdwarf')
- self.assertRaises(spack.spec.UnsatisfiableVersionSpecError,
- spec.normalize)
+ with pytest.raises(spack.spec.UnsatisfiableVersionSpecError):
+ spec.normalize()
- def test_unsatisfiable_compiler(self):
- self.set_pkg_dep('mpileaks', 'mpich%gcc')
+ def test_unsatisfiable_compiler(self, set_dependency):
+ set_dependency('mpileaks', 'mpich%gcc')
spec = Spec('mpileaks ^mpich%intel ^callpath ^dyninst ^libelf'
' ^libdwarf')
- self.assertRaises(spack.spec.UnsatisfiableCompilerSpecError,
- spec.normalize)
+ with pytest.raises(spack.spec.UnsatisfiableCompilerSpecError):
+ spec.normalize()
- def test_unsatisfiable_compiler_version(self):
- self.set_pkg_dep('mpileaks', 'mpich%gcc@4.6')
+ def test_unsatisfiable_compiler_version(self, set_dependency):
+ set_dependency('mpileaks', 'mpich%gcc@4.6')
spec = Spec('mpileaks ^mpich%gcc@4.5 ^callpath ^dyninst ^libelf'
' ^libdwarf')
- self.assertRaises(spack.spec.UnsatisfiableCompilerSpecError,
- spec.normalize)
+ with pytest.raises(spack.spec.UnsatisfiableCompilerSpecError):
+ spec.normalize()
- def test_unsatisfiable_architecture(self):
- self.set_pkg_dep('mpileaks', 'mpich platform=test target=be')
+ def test_unsatisfiable_architecture(self, set_dependency):
+ set_dependency('mpileaks', 'mpich platform=test target=be')
spec = Spec('mpileaks ^mpich platform=test target=fe ^callpath'
' ^dyninst ^libelf ^libdwarf')
- self.assertRaises(spack.spec.UnsatisfiableArchitectureSpecError,
- spec.normalize)
+ with pytest.raises(spack.spec.UnsatisfiableArchitectureSpecError):
+ spec.normalize()
def test_invalid_dep(self):
spec = Spec('libelf ^mpich')
- self.assertRaises(spack.spec.InvalidDependencyError,
- spec.normalize)
+ with pytest.raises(spack.spec.InvalidDependencyError):
+ spec.normalize()
spec = Spec('libelf ^libdwarf')
- self.assertRaises(spack.spec.InvalidDependencyError,
- spec.normalize)
+ with pytest.raises(spack.spec.InvalidDependencyError):
+ spec.normalize()
spec = Spec('mpich ^dyninst ^libelf')
- self.assertRaises(spack.spec.InvalidDependencyError,
- spec.normalize)
+ with pytest.raises(spack.spec.InvalidDependencyError):
+ spec.normalize()
def test_equal(self):
# Different spec structures to test for equality
@@ -273,21 +297,21 @@ class SpecDagTest(MockPackagesTest):
# All these are equal to each other with regular ==
specs = (flat, flat_init, flip_flat, dag, flip_dag)
for lhs, rhs in zip(specs, specs):
- self.assertEqual(lhs, rhs)
- self.assertEqual(str(lhs), str(rhs))
+ assert lhs == rhs
+ assert str(lhs) == str(rhs)
# Same DAGs constructed different ways are equal
- self.assertTrue(flat.eq_dag(flat_init))
+ assert flat.eq_dag(flat_init)
# order at same level does not matter -- (dep on same parent)
- self.assertTrue(flat.eq_dag(flip_flat))
+ assert flat.eq_dag(flip_flat)
# DAGs should be unequal if nesting is different
- self.assertFalse(flat.eq_dag(dag))
- self.assertFalse(flat.eq_dag(flip_dag))
- self.assertFalse(flip_flat.eq_dag(dag))
- self.assertFalse(flip_flat.eq_dag(flip_dag))
- self.assertFalse(dag.eq_dag(flip_dag))
+ assert not flat.eq_dag(dag)
+ assert not flat.eq_dag(flip_dag)
+ assert not flip_flat.eq_dag(dag)
+ assert not flip_flat.eq_dag(flip_dag)
+ assert not dag.eq_dag(flip_dag)
def test_normalize_mpileaks(self):
# Spec parsed in from a string
@@ -328,32 +352,32 @@ class SpecDagTest(MockPackagesTest):
# All specs here should be equal under regular equality
specs = (spec, expected_flat, expected_normalized, non_unique_nodes)
for lhs, rhs in zip(specs, specs):
- self.assertEqual(lhs, rhs)
- self.assertEqual(str(lhs), str(rhs))
+ assert lhs == rhs
+ assert str(lhs) == str(rhs)
# Test that equal and equal_dag are doing the right thing
- self.assertEqual(spec, expected_flat)
- self.assertTrue(spec.eq_dag(expected_flat))
+ assert spec == expected_flat
+ assert spec.eq_dag(expected_flat)
# Normalized has different DAG structure, so NOT equal.
- self.assertNotEqual(spec, expected_normalized)
- self.assertFalse(spec.eq_dag(expected_normalized))
+ assert spec != expected_normalized
+ assert not spec.eq_dag(expected_normalized)
# Again, different DAG structure so not equal.
- self.assertNotEqual(spec, non_unique_nodes)
- self.assertFalse(spec.eq_dag(non_unique_nodes))
+ assert spec != non_unique_nodes
+ assert not spec.eq_dag(non_unique_nodes)
spec.normalize()
# After normalizing, spec_dag_equal should match the normalized spec.
- self.assertNotEqual(spec, expected_flat)
- self.assertFalse(spec.eq_dag(expected_flat))
+ assert spec != expected_flat
+ assert not spec.eq_dag(expected_flat)
- self.assertEqual(spec, expected_normalized)
- self.assertTrue(spec.eq_dag(expected_normalized))
+ assert spec == expected_normalized
+ assert spec.eq_dag(expected_normalized)
- self.assertEqual(spec, non_unique_nodes)
- self.assertFalse(spec.eq_dag(non_unique_nodes))
+ assert spec == non_unique_nodes
+ assert not spec.eq_dag(non_unique_nodes)
def test_normalize_with_virtual_package(self):
spec = Spec('mpileaks ^mpi ^libelf@1.8.11 ^libdwarf')
@@ -368,67 +392,66 @@ class SpecDagTest(MockPackagesTest):
Spec('libelf@1.8.11')),
Spec('mpi')), Spec('mpi'))
- self.assertEqual(str(spec), str(expected_normalized))
+ assert str(spec) == str(expected_normalized)
def test_contains(self):
spec = Spec('mpileaks ^mpi ^libelf@1.8.11 ^libdwarf')
- self.assertTrue(Spec('mpi') in spec)
- self.assertTrue(Spec('libelf') in spec)
- self.assertTrue(Spec('libelf@1.8.11') in spec)
- self.assertFalse(Spec('libelf@1.8.12') in spec)
- self.assertTrue(Spec('libdwarf') in spec)
- self.assertFalse(Spec('libgoblin') in spec)
- self.assertTrue(Spec('mpileaks') in spec)
+ assert Spec('mpi') in spec
+ assert Spec('libelf') in spec
+ assert Spec('libelf@1.8.11') in spec
+ assert Spec('libelf@1.8.12') not in spec
+ assert Spec('libdwarf') in spec
+ assert Spec('libgoblin') not in spec
+ assert Spec('mpileaks') in spec
def test_copy_simple(self):
orig = Spec('mpileaks')
copy = orig.copy()
+ check_links(copy)
- self.check_links(copy)
-
- self.assertEqual(orig, copy)
- self.assertTrue(orig.eq_dag(copy))
- self.assertEqual(orig._normal, copy._normal)
- self.assertEqual(orig._concrete, copy._concrete)
+ assert orig == copy
+ assert orig.eq_dag(copy)
+ assert orig._normal == copy._normal
+ assert orig._concrete == copy._concrete
# ensure no shared nodes bt/w orig and copy.
orig_ids = set(id(s) for s in orig.traverse())
copy_ids = set(id(s) for s in copy.traverse())
- self.assertFalse(orig_ids.intersection(copy_ids))
+ assert not orig_ids.intersection(copy_ids)
def test_copy_normalized(self):
orig = Spec('mpileaks')
orig.normalize()
copy = orig.copy()
+ check_links(copy)
- self.check_links(copy)
-
- self.assertEqual(orig, copy)
- self.assertTrue(orig.eq_dag(copy))
- self.assertEqual(orig._normal, copy._normal)
- self.assertEqual(orig._concrete, copy._concrete)
+ assert orig == copy
+ assert orig.eq_dag(copy)
+ assert orig._normal == copy._normal
+ assert orig._concrete == copy._concrete
# ensure no shared nodes bt/w orig and copy.
orig_ids = set(id(s) for s in orig.traverse())
copy_ids = set(id(s) for s in copy.traverse())
- self.assertFalse(orig_ids.intersection(copy_ids))
+ assert not orig_ids.intersection(copy_ids)
+ @pytest.mark.usefixtures('config')
def test_copy_concretized(self):
orig = Spec('mpileaks')
orig.concretize()
copy = orig.copy()
- self.check_links(copy)
+ check_links(copy)
- self.assertEqual(orig, copy)
- self.assertTrue(orig.eq_dag(copy))
- self.assertEqual(orig._normal, copy._normal)
- self.assertEqual(orig._concrete, copy._concrete)
+ assert orig == copy
+ assert orig.eq_dag(copy)
+ assert orig._normal == copy._normal
+ assert orig._concrete == copy._concrete
# ensure no shared nodes bt/w orig and copy.
orig_ids = set(id(s) for s in orig.traverse())
copy_ids = set(id(s) for s in copy.traverse())
- self.assertFalse(orig_ids.intersection(copy_ids))
+ assert not orig_ids.intersection(copy_ids)
"""
Here is the graph with deptypes labeled (assume all packages have a 'dt'
@@ -464,7 +487,7 @@ class SpecDagTest(MockPackagesTest):
'dtlink1', 'dtlink3', 'dtlink4']
traversal = dag.traverse(deptype=('build', 'link'))
- self.assertEqual([x.name for x in traversal], names)
+ assert [x.name for x in traversal] == names
def test_deptype_traversal_with_builddeps(self):
dag = Spec('dttop')
@@ -474,7 +497,7 @@ class SpecDagTest(MockPackagesTest):
'dtlink1', 'dtlink3', 'dtlink4']
traversal = dag.traverse(deptype=('build', 'link'))
- self.assertEqual([x.name for x in traversal], names)
+ assert [x.name for x in traversal] == names
def test_deptype_traversal_full(self):
dag = Spec('dttop')
@@ -485,7 +508,7 @@ class SpecDagTest(MockPackagesTest):
'dtrun3', 'dtbuild3']
traversal = dag.traverse(deptype=spack.alldeps)
- self.assertEqual([x.name for x in traversal], names)
+ assert [x.name for x in traversal] == names
def test_deptype_traversal_run(self):
dag = Spec('dttop')
@@ -494,7 +517,7 @@ class SpecDagTest(MockPackagesTest):
names = ['dttop', 'dtrun1', 'dtrun3']
traversal = dag.traverse(deptype='run')
- self.assertEqual([x.name for x in traversal], names)
+ assert [x.name for x in traversal] == names
def test_hash_bits(self):
"""Ensure getting first n bits of a base32-encoded DAG hash works."""
@@ -522,10 +545,10 @@ class SpecDagTest(MockPackagesTest):
fmt = "#0%sb" % (bits + 2)
actual = format(actual_int, fmt).replace('0b', '')
- self.assertEqual(expected[:bits], actual)
+ assert expected[:bits] == actual
- self.assertRaises(
- ValueError, spack.spec.base32_prefix_bits, test_hash, 161)
+ with pytest.raises(ValueError):
+ spack.spec.base32_prefix_bits(test_hash, 161)
- self.assertRaises(
- ValueError, spack.spec.base32_prefix_bits, test_hash, 256)
+ with pytest.raises(ValueError):
+ spack.spec.base32_prefix_bits(test_hash, 256)
diff --git a/lib/spack/spack/test/spec_semantics.py b/lib/spack/spack/test/spec_semantics.py
index 16d6121dea..84c8650f15 100644
--- a/lib/spack/spack/test/spec_semantics.py
+++ b/lib/spack/spack/test/spec_semantics.py
@@ -23,340 +23,344 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import spack.architecture
+import pytest
from spack.spec import *
-from spack.test.mock_packages_test import *
-class SpecSematicsTest(MockPackagesTest):
- """This tests satisfies(), constrain() and other semantic operations
- on specs."""
+def check_satisfies(spec, anon_spec, concrete=False):
+ left = Spec(spec, concrete=concrete)
+ try:
+ right = Spec(anon_spec) # if it's not anonymous, allow it.
+ except Exception:
+ right = parse_anonymous_spec(anon_spec, left.name)
- # ========================================================================
- # Utility functions to set everything up.
- # ========================================================================
- def check_satisfies(self, spec, anon_spec, concrete=False):
- left = Spec(spec, concrete=concrete)
- try:
- right = Spec(anon_spec) # if it's not anonymous, allow it.
- except:
- right = parse_anonymous_spec(anon_spec, left.name)
-
- # Satisfies is one-directional.
- self.assertTrue(left.satisfies(right))
- self.assertTrue(left.satisfies(anon_spec))
-
- # if left satisfies right, then we should be able to consrain
- # right by left. Reverse is not always true.
+ # Satisfies is one-directional.
+ assert left.satisfies(right)
+ assert left.satisfies(anon_spec)
+
+ # if left satisfies right, then we should be able to consrain
+ # right by left. Reverse is not always true.
+ right.copy().constrain(left)
+
+
+def check_unsatisfiable(spec, anon_spec, concrete=False):
+ left = Spec(spec, concrete=concrete)
+ try:
+ right = Spec(anon_spec) # if it's not anonymous, allow it.
+ except Exception:
+ right = parse_anonymous_spec(anon_spec, left.name)
+
+ assert not left.satisfies(right)
+ assert not left.satisfies(anon_spec)
+
+ with pytest.raises(UnsatisfiableSpecError):
right.copy().constrain(left)
- def check_unsatisfiable(self, spec, anon_spec, concrete=False):
- left = Spec(spec, concrete=concrete)
- try:
- right = Spec(anon_spec) # if it's not anonymous, allow it.
- except:
- right = parse_anonymous_spec(anon_spec, left.name)
- self.assertFalse(left.satisfies(right))
- self.assertFalse(left.satisfies(anon_spec))
+def check_constrain(expected, spec, constraint):
+ exp = Spec(expected)
+ spec = Spec(spec)
+ constraint = Spec(constraint)
+ spec.constrain(constraint)
+ assert exp == spec
- self.assertRaises(UnsatisfiableSpecError, right.copy().constrain, left)
- def check_constrain(self, expected, spec, constraint):
- exp = Spec(expected)
- spec = Spec(spec)
- constraint = Spec(constraint)
- spec.constrain(constraint)
- self.assertEqual(exp, spec)
+def check_constrain_changed(spec, constraint):
+ spec = Spec(spec)
+ assert spec.constrain(constraint)
- def check_constrain_changed(self, spec, constraint):
- spec = Spec(spec)
- self.assertTrue(spec.constrain(constraint))
- def check_constrain_not_changed(self, spec, constraint):
- spec = Spec(spec)
- self.assertFalse(spec.constrain(constraint))
+def check_constrain_not_changed(spec, constraint):
+ spec = Spec(spec)
+ assert not spec.constrain(constraint)
- def check_invalid_constraint(self, spec, constraint):
- spec = Spec(spec)
- constraint = Spec(constraint)
- self.assertRaises(UnsatisfiableSpecError, spec.constrain, constraint)
- # ========================================================================
- # Satisfiability
- # ========================================================================
+def check_invalid_constraint(spec, constraint):
+ spec = Spec(spec)
+ constraint = Spec(constraint)
+ with pytest.raises(UnsatisfiableSpecError):
+ spec.constrain(constraint)
+
+
+@pytest.mark.usefixtures('config', 'builtin_mock')
+class TestSpecSematics(object):
+ """This tests satisfies(), constrain() and other semantic operations
+ on specs.
+ """
def test_satisfies(self):
- self.check_satisfies('libelf@0.8.13', '@0:1')
- self.check_satisfies('libdwarf^libelf@0.8.13', '^libelf@0:1')
+ check_satisfies('libelf@0.8.13', '@0:1')
+ check_satisfies('libdwarf^libelf@0.8.13', '^libelf@0:1')
def test_satisfies_namespace(self):
- self.check_satisfies('builtin.mpich', 'mpich')
- self.check_satisfies('builtin.mock.mpich', 'mpich')
+ check_satisfies('builtin.mpich', 'mpich')
+ check_satisfies('builtin.mock.mpich', 'mpich')
# TODO: only works for deps now, but shouldn't we allow for root spec?
- # self.check_satisfies('builtin.mock.mpich', 'mpi')
+ # check_satisfies('builtin.mock.mpich', 'mpi')
- self.check_satisfies('builtin.mock.mpich', 'builtin.mock.mpich')
+ check_satisfies('builtin.mock.mpich', 'builtin.mock.mpich')
- self.check_unsatisfiable('builtin.mock.mpich', 'builtin.mpich')
+ check_unsatisfiable('builtin.mock.mpich', 'builtin.mpich')
def test_satisfies_namespaced_dep(self):
"""Ensure spec from same or unspecified namespace satisfies namespace
constraint."""
- self.check_satisfies('mpileaks ^builtin.mock.mpich', '^mpich')
+ check_satisfies('mpileaks ^builtin.mock.mpich', '^mpich')
- self.check_satisfies('mpileaks ^builtin.mock.mpich', '^mpi')
- self.check_satisfies(
+ check_satisfies('mpileaks ^builtin.mock.mpich', '^mpi')
+ check_satisfies(
'mpileaks ^builtin.mock.mpich', '^builtin.mock.mpich')
- self.check_unsatisfiable(
+ check_unsatisfiable(
'mpileaks ^builtin.mock.mpich', '^builtin.mpich')
def test_satisfies_compiler(self):
- self.check_satisfies('foo%gcc', '%gcc')
- self.check_satisfies('foo%intel', '%intel')
- self.check_unsatisfiable('foo%intel', '%gcc')
- self.check_unsatisfiable('foo%intel', '%pgi')
+ check_satisfies('foo%gcc', '%gcc')
+ check_satisfies('foo%intel', '%intel')
+ check_unsatisfiable('foo%intel', '%gcc')
+ check_unsatisfiable('foo%intel', '%pgi')
def test_satisfies_compiler_version(self):
- self.check_satisfies('foo%gcc', '%gcc@4.7.2')
- self.check_satisfies('foo%intel', '%intel@4.7.2')
+ check_satisfies('foo%gcc', '%gcc@4.7.2')
+ check_satisfies('foo%intel', '%intel@4.7.2')
- self.check_satisfies('foo%pgi@4.5', '%pgi@4.4:4.6')
- self.check_satisfies('foo@2.0%pgi@4.5', '@1:3%pgi@4.4:4.6')
+ check_satisfies('foo%pgi@4.5', '%pgi@4.4:4.6')
+ check_satisfies('foo@2.0%pgi@4.5', '@1:3%pgi@4.4:4.6')
- self.check_unsatisfiable('foo%pgi@4.3', '%pgi@4.4:4.6')
- self.check_unsatisfiable('foo@4.0%pgi', '@1:3%pgi')
- self.check_unsatisfiable('foo@4.0%pgi@4.5', '@1:3%pgi@4.4:4.6')
+ check_unsatisfiable('foo%pgi@4.3', '%pgi@4.4:4.6')
+ check_unsatisfiable('foo@4.0%pgi', '@1:3%pgi')
+ check_unsatisfiable('foo@4.0%pgi@4.5', '@1:3%pgi@4.4:4.6')
- self.check_satisfies('foo %gcc@4.7.3', '%gcc@4.7')
- self.check_unsatisfiable('foo %gcc@4.7', '%gcc@4.7.3')
+ check_satisfies('foo %gcc@4.7.3', '%gcc@4.7')
+ check_unsatisfiable('foo %gcc@4.7', '%gcc@4.7.3')
def test_satisfies_architecture(self):
- self.check_satisfies(
+ check_satisfies(
'foo platform=test',
'platform=test')
- self.check_satisfies(
+ check_satisfies(
'foo platform=linux',
'platform=linux')
- self.check_satisfies(
+ check_satisfies(
'foo platform=test',
'platform=test target=frontend')
- self.check_satisfies(
+ check_satisfies(
'foo platform=test',
'platform=test os=frontend target=frontend')
- self.check_satisfies(
+ check_satisfies(
'foo platform=test os=frontend target=frontend',
'platform=test')
- self.check_unsatisfiable(
+ check_unsatisfiable(
'foo platform=linux',
'platform=test os=redhat6 target=x86_32')
- self.check_unsatisfiable(
+ check_unsatisfiable(
'foo os=redhat6',
'platform=test os=debian6 target=x86_64')
- self.check_unsatisfiable(
+ check_unsatisfiable(
'foo target=x86_64',
'platform=test os=redhat6 target=x86_32')
- self.check_satisfies(
+ check_satisfies(
'foo arch=test-None-None',
'platform=test')
- self.check_satisfies(
+ check_satisfies(
'foo arch=test-None-frontend',
'platform=test target=frontend')
- self.check_satisfies(
+ check_satisfies(
'foo arch=test-frontend-frontend',
'platform=test os=frontend target=frontend')
- self.check_satisfies(
+ check_satisfies(
'foo arch=test-frontend-frontend',
'platform=test')
- self.check_unsatisfiable(
+ check_unsatisfiable(
'foo arch=test-frontend-frontend',
'platform=test os=frontend target=backend')
- self.check_satisfies(
+ check_satisfies(
'foo platform=test target=frontend os=frontend',
'platform=test target=frontend os=frontend')
- self.check_satisfies(
+ check_satisfies(
'foo platform=test target=backend os=backend',
'platform=test target=backend os=backend')
- self.check_satisfies(
+ check_satisfies(
'foo platform=test target=default_target os=default_os',
'platform=test os=default_os')
- self.check_unsatisfiable(
+ check_unsatisfiable(
'foo platform=test target=x86_32 os=redhat6',
'platform=linux target=x86_32 os=redhat6')
def test_satisfies_dependencies(self):
- self.check_satisfies('mpileaks^mpich', '^mpich')
- self.check_satisfies('mpileaks^zmpi', '^zmpi')
+ check_satisfies('mpileaks^mpich', '^mpich')
+ check_satisfies('mpileaks^zmpi', '^zmpi')
- self.check_unsatisfiable('mpileaks^mpich', '^zmpi')
- self.check_unsatisfiable('mpileaks^zmpi', '^mpich')
+ check_unsatisfiable('mpileaks^mpich', '^zmpi')
+ check_unsatisfiable('mpileaks^zmpi', '^mpich')
def test_satisfies_dependency_versions(self):
- self.check_satisfies('mpileaks^mpich@2.0', '^mpich@1:3')
- self.check_unsatisfiable('mpileaks^mpich@1.2', '^mpich@2.0')
+ check_satisfies('mpileaks^mpich@2.0', '^mpich@1:3')
+ check_unsatisfiable('mpileaks^mpich@1.2', '^mpich@2.0')
- self.check_satisfies(
+ check_satisfies(
'mpileaks^mpich@2.0^callpath@1.5', '^mpich@1:3^callpath@1.4:1.6')
- self.check_unsatisfiable(
+ check_unsatisfiable(
'mpileaks^mpich@4.0^callpath@1.5', '^mpich@1:3^callpath@1.4:1.6')
- self.check_unsatisfiable(
+ check_unsatisfiable(
'mpileaks^mpich@2.0^callpath@1.7', '^mpich@1:3^callpath@1.4:1.6')
- self.check_unsatisfiable(
+ check_unsatisfiable(
'mpileaks^mpich@4.0^callpath@1.7', '^mpich@1:3^callpath@1.4:1.6')
def test_satisfies_virtual_dependencies(self):
- self.check_satisfies('mpileaks^mpi', '^mpi')
- self.check_satisfies('mpileaks^mpi', '^mpich')
+ check_satisfies('mpileaks^mpi', '^mpi')
+ check_satisfies('mpileaks^mpi', '^mpich')
- self.check_satisfies('mpileaks^mpi', '^zmpi')
- self.check_unsatisfiable('mpileaks^mpich', '^zmpi')
+ check_satisfies('mpileaks^mpi', '^zmpi')
+ check_unsatisfiable('mpileaks^mpich', '^zmpi')
def test_satisfies_virtual_dependency_versions(self):
- self.check_satisfies('mpileaks^mpi@1.5', '^mpi@1.2:1.6')
- self.check_unsatisfiable('mpileaks^mpi@3', '^mpi@1.2:1.6')
+ check_satisfies('mpileaks^mpi@1.5', '^mpi@1.2:1.6')
+ check_unsatisfiable('mpileaks^mpi@3', '^mpi@1.2:1.6')
- self.check_satisfies('mpileaks^mpi@2:', '^mpich')
- self.check_satisfies('mpileaks^mpi@2:', '^mpich@3.0.4')
- self.check_satisfies('mpileaks^mpi@2:', '^mpich2@1.4')
+ check_satisfies('mpileaks^mpi@2:', '^mpich')
+ check_satisfies('mpileaks^mpi@2:', '^mpich@3.0.4')
+ check_satisfies('mpileaks^mpi@2:', '^mpich2@1.4')
- self.check_satisfies('mpileaks^mpi@1:', '^mpich2')
- self.check_satisfies('mpileaks^mpi@2:', '^mpich2')
+ check_satisfies('mpileaks^mpi@1:', '^mpich2')
+ check_satisfies('mpileaks^mpi@2:', '^mpich2')
- self.check_unsatisfiable('mpileaks^mpi@3:', '^mpich2@1.4')
- self.check_unsatisfiable('mpileaks^mpi@3:', '^mpich2')
- self.check_unsatisfiable('mpileaks^mpi@3:', '^mpich@1.0')
+ check_unsatisfiable('mpileaks^mpi@3:', '^mpich2@1.4')
+ check_unsatisfiable('mpileaks^mpi@3:', '^mpich2')
+ check_unsatisfiable('mpileaks^mpi@3:', '^mpich@1.0')
def test_satisfies_matching_variant(self):
- self.check_satisfies('mpich+foo', 'mpich+foo')
- self.check_satisfies('mpich~foo', 'mpich~foo')
- self.check_satisfies('mpich foo=1', 'mpich foo=1')
+ check_satisfies('mpich+foo', 'mpich+foo')
+ check_satisfies('mpich~foo', 'mpich~foo')
+ check_satisfies('mpich foo=1', 'mpich foo=1')
# confirm that synonymous syntax works correctly
- self.check_satisfies('mpich+foo', 'mpich foo=True')
- self.check_satisfies('mpich foo=true', 'mpich+foo')
- self.check_satisfies('mpich~foo', 'mpich foo=FALSE')
- self.check_satisfies('mpich foo=False', 'mpich~foo')
+ check_satisfies('mpich+foo', 'mpich foo=True')
+ check_satisfies('mpich foo=true', 'mpich+foo')
+ check_satisfies('mpich~foo', 'mpich foo=FALSE')
+ check_satisfies('mpich foo=False', 'mpich~foo')
def test_satisfies_unconstrained_variant(self):
# only asked for mpich, no constraints. Either will do.
- self.check_satisfies('mpich+foo', 'mpich')
- self.check_satisfies('mpich~foo', 'mpich')
- self.check_satisfies('mpich foo=1', 'mpich')
+ check_satisfies('mpich+foo', 'mpich')
+ check_satisfies('mpich~foo', 'mpich')
+ check_satisfies('mpich foo=1', 'mpich')
def test_unsatisfiable_variants(self):
# This case is different depending on whether the specs are concrete.
# 'mpich' is not concrete:
- self.check_satisfies('mpich', 'mpich+foo', False)
- self.check_satisfies('mpich', 'mpich~foo', False)
- self.check_satisfies('mpich', 'mpich foo=1', False)
+ check_satisfies('mpich', 'mpich+foo', False)
+ check_satisfies('mpich', 'mpich~foo', False)
+ check_satisfies('mpich', 'mpich foo=1', False)
# 'mpich' is concrete:
- self.check_unsatisfiable('mpich', 'mpich+foo', True)
- self.check_unsatisfiable('mpich', 'mpich~foo', True)
- self.check_unsatisfiable('mpich', 'mpich foo=1', True)
+ check_unsatisfiable('mpich', 'mpich+foo', True)
+ check_unsatisfiable('mpich', 'mpich~foo', True)
+ check_unsatisfiable('mpich', 'mpich foo=1', True)
def test_unsatisfiable_variant_mismatch(self):
# No matchi in specs
- self.check_unsatisfiable('mpich~foo', 'mpich+foo')
- self.check_unsatisfiable('mpich+foo', 'mpich~foo')
- self.check_unsatisfiable('mpich foo=1', 'mpich foo=2')
+ check_unsatisfiable('mpich~foo', 'mpich+foo')
+ check_unsatisfiable('mpich+foo', 'mpich~foo')
+ check_unsatisfiable('mpich foo=1', 'mpich foo=2')
def test_satisfies_matching_compiler_flag(self):
- self.check_satisfies('mpich cppflags="-O3"', 'mpich cppflags="-O3"')
- self.check_satisfies('mpich cppflags="-O3 -Wall"',
- 'mpich cppflags="-O3 -Wall"')
+ check_satisfies('mpich cppflags="-O3"', 'mpich cppflags="-O3"')
+ check_satisfies(
+ 'mpich cppflags="-O3 -Wall"', 'mpich cppflags="-O3 -Wall"'
+ )
def test_satisfies_unconstrained_compiler_flag(self):
# only asked for mpich, no constraints. Any will do.
- self.check_satisfies('mpich cppflags="-O3"', 'mpich')
+ check_satisfies('mpich cppflags="-O3"', 'mpich')
def test_unsatisfiable_compiler_flag(self):
# This case is different depending on whether the specs are concrete.
# 'mpich' is not concrete:
- self.check_satisfies('mpich', 'mpich cppflags="-O3"', False)
+ check_satisfies('mpich', 'mpich cppflags="-O3"', False)
# 'mpich' is concrete:
- self.check_unsatisfiable('mpich', 'mpich cppflags="-O3"', True)
+ check_unsatisfiable('mpich', 'mpich cppflags="-O3"', True)
def test_unsatisfiable_compiler_flag_mismatch(self):
# No matchi in specs
- self.check_unsatisfiable(
+ check_unsatisfiable(
'mpich cppflags="-O3"', 'mpich cppflags="-O2"')
def test_satisfies_virtual(self):
# Don't use check_satisfies: it checks constrain() too, and
# you can't constrain a non-virtual by a virtual.
- self.assertTrue(Spec('mpich').satisfies(Spec('mpi')))
- self.assertTrue(Spec('mpich2').satisfies(Spec('mpi')))
- self.assertTrue(Spec('zmpi').satisfies(Spec('mpi')))
+ assert Spec('mpich').satisfies(Spec('mpi'))
+ assert Spec('mpich2').satisfies(Spec('mpi'))
+ assert Spec('zmpi').satisfies(Spec('mpi'))
def test_satisfies_virtual_dep_with_virtual_constraint(self):
"""Ensure we can satisfy virtual constraints when there are multiple
vdep providers in the specs."""
- self.assertTrue(
- Spec('netlib-lapack ^openblas').satisfies(
- 'netlib-lapack ^openblas'))
- self.assertFalse(
- Spec('netlib-lapack ^netlib-blas').satisfies(
- 'netlib-lapack ^openblas'))
-
- self.assertFalse(
- Spec('netlib-lapack ^openblas').satisfies(
- 'netlib-lapack ^netlib-blas'))
- self.assertTrue(
- Spec('netlib-lapack ^netlib-blas').satisfies(
- 'netlib-lapack ^netlib-blas'))
+ assert Spec('netlib-lapack ^openblas').satisfies(
+ 'netlib-lapack ^openblas'
+ )
+ assert not Spec('netlib-lapack ^netlib-blas').satisfies(
+ 'netlib-lapack ^openblas'
+ )
+ assert not Spec('netlib-lapack ^openblas').satisfies(
+ 'netlib-lapack ^netlib-blas'
+ )
+ assert Spec('netlib-lapack ^netlib-blas').satisfies(
+ 'netlib-lapack ^netlib-blas'
+ )
def test_satisfies_same_spec_with_different_hash(self):
"""Ensure that concrete specs are matched *exactly* by hash."""
s1 = Spec('mpileaks').concretized()
s2 = s1.copy()
- self.assertTrue(s1.satisfies(s2))
- self.assertTrue(s2.satisfies(s1))
+ assert s1.satisfies(s2)
+ assert s2.satisfies(s1)
# Simulate specs that were installed before and after a change to
# Spack's hashing algorithm. This just reverses s2's hash.
s2._hash = s1.dag_hash()[-1::-1]
- self.assertFalse(s1.satisfies(s2))
- self.assertFalse(s2.satisfies(s1))
+ assert not s1.satisfies(s2)
+ assert not s2.satisfies(s1)
# ========================================================================
# Indexing specs
# ========================================================================
def test_self_index(self):
s = Spec('callpath')
- self.assertTrue(s['callpath'] == s)
+ assert s['callpath'] == s
def test_dep_index(self):
s = Spec('callpath')
s.normalize()
- self.assertTrue(s['callpath'] == s)
- self.assertTrue(type(s['dyninst']) == Spec)
- self.assertTrue(type(s['libdwarf']) == Spec)
- self.assertTrue(type(s['libelf']) == Spec)
- self.assertTrue(type(s['mpi']) == Spec)
+ assert s['callpath'] == s
+ assert type(s['dyninst']) == Spec
+ assert type(s['libdwarf']) == Spec
+ assert type(s['libelf']) == Spec
+ assert type(s['mpi']) == Spec
- self.assertTrue(s['dyninst'].name == 'dyninst')
- self.assertTrue(s['libdwarf'].name == 'libdwarf')
- self.assertTrue(s['libelf'].name == 'libelf')
- self.assertTrue(s['mpi'].name == 'mpi')
+ assert s['dyninst'].name == 'dyninst'
+ assert s['libdwarf'].name == 'libdwarf'
+ assert s['libelf'].name == 'libelf'
+ assert s['mpi'].name == 'mpi'
def test_spec_contains_deps(self):
s = Spec('callpath')
s.normalize()
- self.assertTrue('dyninst' in s)
- self.assertTrue('libdwarf' in s)
- self.assertTrue('libelf' in s)
- self.assertTrue('mpi' in s)
+ assert 'dyninst' in s
+ assert 'libdwarf' in s
+ assert 'libelf' in s
+ assert 'mpi' in s
+ @pytest.mark.usefixtures('config')
def test_virtual_index(self):
s = Spec('callpath')
s.concretize()
@@ -370,133 +374,149 @@ class SpecSematicsTest(MockPackagesTest):
s_zmpi = Spec('callpath ^zmpi')
s_zmpi.concretize()
- self.assertTrue(s['mpi'].name != 'mpi')
- self.assertTrue(s_mpich['mpi'].name == 'mpich')
- self.assertTrue(s_mpich2['mpi'].name == 'mpich2')
- self.assertTrue(s_zmpi['zmpi'].name == 'zmpi')
+ assert s['mpi'].name != 'mpi'
+ assert s_mpich['mpi'].name == 'mpich'
+ assert s_mpich2['mpi'].name == 'mpich2'
+ assert s_zmpi['zmpi'].name == 'zmpi'
for spec in [s, s_mpich, s_mpich2, s_zmpi]:
- self.assertTrue('mpi' in spec)
+ assert 'mpi' in spec
# ========================================================================
# Constraints
# ========================================================================
def test_constrain_variants(self):
- self.check_constrain('libelf@2.1:2.5', 'libelf@0:2.5', 'libelf@2.1:3')
- self.check_constrain('libelf@2.1:2.5%gcc@4.5:4.6',
- 'libelf@0:2.5%gcc@2:4.6',
- 'libelf@2.1:3%gcc@4.5:4.7')
-
- self.check_constrain('libelf+debug+foo', 'libelf+debug', 'libelf+foo')
- self.check_constrain('libelf+debug+foo',
- 'libelf+debug', 'libelf+debug+foo')
-
- self.check_constrain('libelf debug=2 foo=1',
- 'libelf debug=2', 'libelf foo=1')
- self.check_constrain('libelf debug=2 foo=1',
- 'libelf debug=2', 'libelf debug=2 foo=1')
-
- self.check_constrain('libelf+debug~foo', 'libelf+debug', 'libelf~foo')
- self.check_constrain('libelf+debug~foo',
- 'libelf+debug', 'libelf+debug~foo')
+ check_constrain('libelf@2.1:2.5', 'libelf@0:2.5', 'libelf@2.1:3')
+ check_constrain(
+ 'libelf@2.1:2.5%gcc@4.5:4.6',
+ 'libelf@0:2.5%gcc@2:4.6',
+ 'libelf@2.1:3%gcc@4.5:4.7'
+ )
+ check_constrain('libelf+debug+foo', 'libelf+debug', 'libelf+foo')
+ check_constrain(
+ 'libelf+debug+foo', 'libelf+debug', 'libelf+debug+foo'
+ )
+ check_constrain(
+ 'libelf debug=2 foo=1', 'libelf debug=2', 'libelf foo=1'
+ )
+ check_constrain(
+ 'libelf debug=2 foo=1', 'libelf debug=2', 'libelf debug=2 foo=1'
+ )
+
+ check_constrain('libelf+debug~foo', 'libelf+debug', 'libelf~foo')
+ check_constrain(
+ 'libelf+debug~foo', 'libelf+debug', 'libelf+debug~foo'
+ )
def test_constrain_compiler_flags(self):
- self.check_constrain('libelf cflags="-O3" cppflags="-Wall"',
- 'libelf cflags="-O3"', 'libelf cppflags="-Wall"')
- self.check_constrain('libelf cflags="-O3" cppflags="-Wall"',
- 'libelf cflags="-O3"',
- 'libelf cflags="-O3" cppflags="-Wall"')
+ check_constrain(
+ 'libelf cflags="-O3" cppflags="-Wall"',
+ 'libelf cflags="-O3"',
+ 'libelf cppflags="-Wall"'
+ )
+ check_constrain(
+ 'libelf cflags="-O3" cppflags="-Wall"',
+ 'libelf cflags="-O3"',
+ 'libelf cflags="-O3" cppflags="-Wall"'
+ )
def test_constrain_architecture(self):
- self.check_constrain('libelf target=default_target os=default_os',
- 'libelf target=default_target os=default_os',
- 'libelf target=default_target os=default_os')
- self.check_constrain('libelf target=default_target os=default_os',
- 'libelf',
- 'libelf target=default_target os=default_os')
+ check_constrain(
+ 'libelf target=default_target os=default_os',
+ 'libelf target=default_target os=default_os',
+ 'libelf target=default_target os=default_os'
+ )
+ check_constrain(
+ 'libelf target=default_target os=default_os',
+ 'libelf',
+ 'libelf target=default_target os=default_os'
+ )
def test_constrain_compiler(self):
- self.check_constrain('libelf %gcc@4.4.7',
- 'libelf %gcc@4.4.7', 'libelf %gcc@4.4.7')
- self.check_constrain('libelf %gcc@4.4.7',
- 'libelf', 'libelf %gcc@4.4.7')
+ check_constrain(
+ 'libelf %gcc@4.4.7', 'libelf %gcc@4.4.7', 'libelf %gcc@4.4.7'
+ )
+ check_constrain(
+ 'libelf %gcc@4.4.7', 'libelf', 'libelf %gcc@4.4.7'
+ )
def test_invalid_constraint(self):
- self.check_invalid_constraint('libelf@0:2.0', 'libelf@2.1:3')
- self.check_invalid_constraint(
+ check_invalid_constraint('libelf@0:2.0', 'libelf@2.1:3')
+ check_invalid_constraint(
'libelf@0:2.5%gcc@4.8:4.9', 'libelf@2.1:3%gcc@4.5:4.7')
- self.check_invalid_constraint('libelf+debug', 'libelf~debug')
- self.check_invalid_constraint('libelf+debug~foo', 'libelf+debug+foo')
- self.check_invalid_constraint('libelf debug=2', 'libelf debug=1')
+ check_invalid_constraint('libelf+debug', 'libelf~debug')
+ check_invalid_constraint('libelf+debug~foo', 'libelf+debug+foo')
+ check_invalid_constraint('libelf debug=2', 'libelf debug=1')
- self.check_invalid_constraint(
+ check_invalid_constraint(
'libelf cppflags="-O3"', 'libelf cppflags="-O2"')
- self.check_invalid_constraint('libelf platform=test target=be os=be',
- 'libelf target=fe os=fe')
+ check_invalid_constraint(
+ 'libelf platform=test target=be os=be', 'libelf target=fe os=fe'
+ )
def test_constrain_changed(self):
- self.check_constrain_changed('libelf', '@1.0')
- self.check_constrain_changed('libelf', '@1.0:5.0')
- self.check_constrain_changed('libelf', '%gcc')
- self.check_constrain_changed('libelf%gcc', '%gcc@4.5')
- self.check_constrain_changed('libelf', '+debug')
- self.check_constrain_changed('libelf', '~debug')
- self.check_constrain_changed('libelf', 'debug=2')
- self.check_constrain_changed('libelf', 'cppflags="-O3"')
+ check_constrain_changed('libelf', '@1.0')
+ check_constrain_changed('libelf', '@1.0:5.0')
+ check_constrain_changed('libelf', '%gcc')
+ check_constrain_changed('libelf%gcc', '%gcc@4.5')
+ check_constrain_changed('libelf', '+debug')
+ check_constrain_changed('libelf', '~debug')
+ check_constrain_changed('libelf', 'debug=2')
+ check_constrain_changed('libelf', 'cppflags="-O3"')
platform = spack.architecture.platform()
- self.check_constrain_changed(
+ check_constrain_changed(
'libelf', 'target=' + platform.target('default_target').name)
- self.check_constrain_changed(
+ check_constrain_changed(
'libelf', 'os=' + platform.operating_system('default_os').name)
def test_constrain_not_changed(self):
- self.check_constrain_not_changed('libelf', 'libelf')
- self.check_constrain_not_changed('libelf@1.0', '@1.0')
- self.check_constrain_not_changed('libelf@1.0:5.0', '@1.0:5.0')
- self.check_constrain_not_changed('libelf%gcc', '%gcc')
- self.check_constrain_not_changed('libelf%gcc@4.5', '%gcc@4.5')
- self.check_constrain_not_changed('libelf+debug', '+debug')
- self.check_constrain_not_changed('libelf~debug', '~debug')
- self.check_constrain_not_changed('libelf debug=2', 'debug=2')
- self.check_constrain_not_changed(
+ check_constrain_not_changed('libelf', 'libelf')
+ check_constrain_not_changed('libelf@1.0', '@1.0')
+ check_constrain_not_changed('libelf@1.0:5.0', '@1.0:5.0')
+ check_constrain_not_changed('libelf%gcc', '%gcc')
+ check_constrain_not_changed('libelf%gcc@4.5', '%gcc@4.5')
+ check_constrain_not_changed('libelf+debug', '+debug')
+ check_constrain_not_changed('libelf~debug', '~debug')
+ check_constrain_not_changed('libelf debug=2', 'debug=2')
+ check_constrain_not_changed(
'libelf cppflags="-O3"', 'cppflags="-O3"')
platform = spack.architecture.platform()
default_target = platform.target('default_target').name
- self.check_constrain_not_changed(
+ check_constrain_not_changed(
'libelf target=' + default_target, 'target=' + default_target)
def test_constrain_dependency_changed(self):
- self.check_constrain_changed('libelf^foo', 'libelf^foo@1.0')
- self.check_constrain_changed('libelf^foo', 'libelf^foo@1.0:5.0')
- self.check_constrain_changed('libelf^foo', 'libelf^foo%gcc')
- self.check_constrain_changed('libelf^foo%gcc', 'libelf^foo%gcc@4.5')
- self.check_constrain_changed('libelf^foo', 'libelf^foo+debug')
- self.check_constrain_changed('libelf^foo', 'libelf^foo~debug')
+ check_constrain_changed('libelf^foo', 'libelf^foo@1.0')
+ check_constrain_changed('libelf^foo', 'libelf^foo@1.0:5.0')
+ check_constrain_changed('libelf^foo', 'libelf^foo%gcc')
+ check_constrain_changed('libelf^foo%gcc', 'libelf^foo%gcc@4.5')
+ check_constrain_changed('libelf^foo', 'libelf^foo+debug')
+ check_constrain_changed('libelf^foo', 'libelf^foo~debug')
platform = spack.architecture.platform()
default_target = platform.target('default_target').name
- self.check_constrain_changed(
+ check_constrain_changed(
'libelf^foo', 'libelf^foo target=' + default_target)
def test_constrain_dependency_not_changed(self):
- self.check_constrain_not_changed('libelf^foo@1.0', 'libelf^foo@1.0')
- self.check_constrain_not_changed(
+ check_constrain_not_changed('libelf^foo@1.0', 'libelf^foo@1.0')
+ check_constrain_not_changed(
'libelf^foo@1.0:5.0', 'libelf^foo@1.0:5.0')
- self.check_constrain_not_changed('libelf^foo%gcc', 'libelf^foo%gcc')
- self.check_constrain_not_changed(
+ check_constrain_not_changed('libelf^foo%gcc', 'libelf^foo%gcc')
+ check_constrain_not_changed(
'libelf^foo%gcc@4.5', 'libelf^foo%gcc@4.5')
- self.check_constrain_not_changed(
+ check_constrain_not_changed(
'libelf^foo+debug', 'libelf^foo+debug')
- self.check_constrain_not_changed(
+ check_constrain_not_changed(
'libelf^foo~debug', 'libelf^foo~debug')
- self.check_constrain_not_changed(
+ check_constrain_not_changed(
'libelf^foo cppflags="-O3"', 'libelf^foo cppflags="-O3"')
platform = spack.architecture.platform()
default_target = platform.target('default_target').name
- self.check_constrain_not_changed(
+ check_constrain_not_changed(
'libelf^foo target=' + default_target,
'libelf^foo target=' + default_target)
diff --git a/lib/spack/spack/test/spec_syntax.py b/lib/spack/spack/test/spec_syntax.py
index 1e072fe970..3cf094f25a 100644
--- a/lib/spack/spack/test/spec_syntax.py
+++ b/lib/spack/spack/test/spec_syntax.py
@@ -22,7 +22,7 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-import unittest
+import pytest
import spack.spec as sp
from spack.parse import Token
@@ -54,7 +54,7 @@ complex_lex = [Token(sp.ID, 'mvapich_foo'),
Token(sp.ID, '8.1_1e')]
-class SpecSyntaxTest(unittest.TestCase):
+class TestSpecSyntax(object):
# ========================================================================
# Parse checks
# ========================================================================
@@ -77,17 +77,22 @@ class SpecSyntaxTest(unittest.TestCase):
output = sp.parse(spec)
parsed = (" ".join(str(spec) for spec in output))
- self.assertEqual(expected, parsed)
+ assert expected == parsed
def check_lex(self, tokens, spec):
"""Check that the provided spec parses to the provided token list."""
lex_output = sp.SpecLexer().lex(spec)
for tok, spec_tok in zip(tokens, lex_output):
if tok.type == sp.ID:
- self.assertEqual(tok, spec_tok)
+ assert tok == spec_tok
else:
# Only check the type for non-identifiers.
- self.assertEqual(tok.type, spec_tok.type)
+ assert tok.type == spec_tok.type
+
+ def _check_raises(self, exc_type, items):
+ for item in items:
+ with pytest.raises(exc_type):
+ self.check_parse(item)
# ========================================================================
# Parse checks
@@ -107,6 +112,10 @@ class SpecSyntaxTest(unittest.TestCase):
self.check_parse("openmpi^hwloc@:1.4b7-rc3")
self.check_parse("openmpi^hwloc@1.2e6:1.4b7-rc3")
+ @pytest.mark.xfail
+ def test_multiple_specs(self):
+ self.check_parse("mvapich emacs")
+
def test_full_specs(self):
self.check_parse(
"mvapich_foo"
@@ -167,88 +176,53 @@ class SpecSyntaxTest(unittest.TestCase):
self.check_parse("x^y", "x@: ^y@:")
def test_parse_errors(self):
- self.assertRaises(SpecParseError, self.check_parse, "x@@1.2")
- self.assertRaises(SpecParseError, self.check_parse, "x ^y@@1.2")
- self.assertRaises(SpecParseError, self.check_parse, "x@1.2::")
- self.assertRaises(SpecParseError, self.check_parse, "x::")
+ errors = ['x@@1.2', 'x ^y@@1.2', 'x@1.2::', 'x::']
+ self._check_raises(SpecParseError, errors)
def test_duplicate_variant(self):
- self.assertRaises(DuplicateVariantError,
- self.check_parse, "x@1.2+debug+debug")
- self.assertRaises(DuplicateVariantError,
- self.check_parse, "x ^y@1.2+debug debug=true")
- self.assertRaises(DuplicateVariantError, self.check_parse,
- "x ^y@1.2 debug=false debug=true")
- self.assertRaises(DuplicateVariantError,
- self.check_parse, "x ^y@1.2 debug=false~debug")
-
- def test_duplicate_depdendence(self):
- self.assertRaises(DuplicateDependencyError,
- self.check_parse, "x ^y ^y")
-
- def test_duplicate_compiler(self):
- self.assertRaises(DuplicateCompilerSpecError,
- self.check_parse, "x%intel%intel")
+ duplicates = [
+ 'x@1.2+debug+debug',
+ 'x ^y@1.2+debug debug=true',
+ 'x ^y@1.2 debug=false debug=true',
+ 'x ^y@1.2 debug=false~debug'
+ ]
+ self._check_raises(DuplicateVariantError, duplicates)
- self.assertRaises(DuplicateCompilerSpecError,
- self.check_parse, "x%intel%gcc")
- self.assertRaises(DuplicateCompilerSpecError,
- self.check_parse, "x%gcc%intel")
+ def test_duplicate_dependency(self):
+ self._check_raises(DuplicateDependencyError, ["x ^y ^y"])
- self.assertRaises(DuplicateCompilerSpecError,
- self.check_parse, "x ^y%intel%intel")
- self.assertRaises(DuplicateCompilerSpecError,
- self.check_parse, "x ^y%intel%gcc")
- self.assertRaises(DuplicateCompilerSpecError,
- self.check_parse, "x ^y%gcc%intel")
+ def test_duplicate_compiler(self):
+ duplicates = [
+ "x%intel%intel",
+ "x%intel%gcc",
+ "x%gcc%intel",
+ "x ^y%intel%intel",
+ "x ^y%intel%gcc",
+ "x ^y%gcc%intel"
+ ]
+ self._check_raises(DuplicateCompilerSpecError, duplicates)
def test_duplicate_architecture(self):
- self.assertRaises(
- DuplicateArchitectureError, self.check_parse,
- "x arch=linux-rhel7-x86_64 arch=linux-rhel7-x86_64")
-
- self.assertRaises(
- DuplicateArchitectureError, self.check_parse,
- "x arch=linux-rhel7-x86_64 arch=linux-rhel7-ppc64le")
- self.assertRaises(
- DuplicateArchitectureError, self.check_parse,
- "x arch=linux-rhel7-ppc64le arch=linux-rhel7-x86_64")
-
- self.assertRaises(
- DuplicateArchitectureError, self.check_parse,
- "y ^x arch=linux-rhel7-x86_64 arch=linux-rhel7-x86_64")
- self.assertRaises(
- DuplicateArchitectureError, self.check_parse,
- "y ^x arch=linux-rhel7-x86_64 arch=linux-rhel7-ppc64le")
+ duplicates = [
+ "x arch=linux-rhel7-x86_64 arch=linux-rhel7-x86_64",
+ "x arch=linux-rhel7-x86_64 arch=linux-rhel7-ppc64le",
+ "x arch=linux-rhel7-ppc64le arch=linux-rhel7-x86_64",
+ "y ^x arch=linux-rhel7-x86_64 arch=linux-rhel7-x86_64",
+ "y ^x arch=linux-rhel7-x86_64 arch=linux-rhel7-ppc64le"
+ ]
+ self._check_raises(DuplicateArchitectureError, duplicates)
def test_duplicate_architecture_component(self):
- self.assertRaises(
- DuplicateArchitectureError, self.check_parse,
- "x os=fe os=fe")
- self.assertRaises(
- DuplicateArchitectureError, self.check_parse,
- "x os=fe os=be")
-
- self.assertRaises(
- DuplicateArchitectureError, self.check_parse,
- "x target=fe target=fe")
- self.assertRaises(
- DuplicateArchitectureError, self.check_parse,
- "x target=fe target=be")
-
- self.assertRaises(
- DuplicateArchitectureError, self.check_parse,
- "x platform=test platform=test")
- self.assertRaises(
- DuplicateArchitectureError, self.check_parse,
- "x platform=test platform=test")
-
- self.assertRaises(
- DuplicateArchitectureError, self.check_parse,
- "x os=fe platform=test target=fe os=fe")
- self.assertRaises(
- DuplicateArchitectureError, self.check_parse,
- "x target=be platform=test os=be os=fe")
+ duplicates = [
+ "x os=fe os=fe",
+ "x os=fe os=be",
+ "x target=fe target=fe",
+ "x target=fe target=be",
+ "x platform=test platform=test",
+ "x os=fe platform=test target=fe os=fe",
+ "x target=be platform=test os=be os=fe"
+ ]
+ self._check_raises(DuplicateArchitectureError, duplicates)
# ========================================================================
# Lex checks
@@ -256,11 +230,13 @@ class SpecSyntaxTest(unittest.TestCase):
def test_ambiguous(self):
# This first one is ambiguous because - can be in an identifier AND
# indicate disabling an option.
- self.assertRaises(
- AssertionError, self.check_lex, complex_lex,
- "mvapich_foo"
- "^_openmpi@1.2:1.4,1.6%intel@12.1:12.6+debug-qt_4"
- "^stackwalker@8.1_1e")
+ with pytest.raises(AssertionError):
+ self.check_lex(
+ complex_lex,
+ "mvapich_foo"
+ "^_openmpi@1.2:1.4,1.6%intel@12.1:12.6+debug-qt_4"
+ "^stackwalker@8.1_1e"
+ )
# The following lexes are non-ambiguous (add a space before -qt_4)
# and should all result in the tokens in complex_lex
diff --git a/lib/spack/spack/test/spec_yaml.py b/lib/spack/spack/test/spec_yaml.py
index 442c6e6e81..e913dc8412 100644
--- a/lib/spack/spack/test/spec_yaml.py
+++ b/lib/spack/spack/test/spec_yaml.py
@@ -27,155 +27,153 @@
YAML format preserves DAG informatoin in the spec.
"""
-import spack.util.spack_yaml as syaml
import spack.util.spack_json as sjson
+import spack.util.spack_yaml as syaml
+from spack.spec import Spec
from spack.util.spack_yaml import syaml_dict
-from spack.spec import Spec
-from spack.test.mock_packages_test import *
+def check_yaml_round_trip(spec):
+ yaml_text = spec.to_yaml()
+ spec_from_yaml = Spec.from_yaml(yaml_text)
+ assert spec.eq_dag(spec_from_yaml)
-class SpecYamlTest(MockPackagesTest):
- def check_yaml_round_trip(self, spec):
- yaml_text = spec.to_yaml()
- spec_from_yaml = Spec.from_yaml(yaml_text)
- self.assertTrue(spec.eq_dag(spec_from_yaml))
+def test_simple_spec():
+ spec = Spec('mpileaks')
+ check_yaml_round_trip(spec)
- def test_simple_spec(self):
- spec = Spec('mpileaks')
- self.check_yaml_round_trip(spec)
- def test_normal_spec(self):
- spec = Spec('mpileaks+debug~opt')
- spec.normalize()
- self.check_yaml_round_trip(spec)
+def test_normal_spec(builtin_mock):
+ spec = Spec('mpileaks+debug~opt')
+ spec.normalize()
+ check_yaml_round_trip(spec)
- def test_ambiguous_version_spec(self):
- spec = Spec('mpileaks@1.0:5.0,6.1,7.3+debug~opt')
- spec.normalize()
- self.check_yaml_round_trip(spec)
- def test_concrete_spec(self):
- spec = Spec('mpileaks+debug~opt')
- spec.concretize()
- self.check_yaml_round_trip(spec)
+def test_ambiguous_version_spec(builtin_mock):
+ spec = Spec('mpileaks@1.0:5.0,6.1,7.3+debug~opt')
+ spec.normalize()
+ check_yaml_round_trip(spec)
+
+
+def test_concrete_spec(config, builtin_mock):
+ spec = Spec('mpileaks+debug~opt')
+ spec.concretize()
+ check_yaml_round_trip(spec)
+
+
+def test_yaml_subdag(config, builtin_mock):
+ spec = Spec('mpileaks^mpich+debug')
+ spec.concretize()
+ yaml_spec = Spec.from_yaml(spec.to_yaml())
- def test_yaml_subdag(self):
- spec = Spec('mpileaks^mpich+debug')
+ for dep in ('callpath', 'mpich', 'dyninst', 'libdwarf', 'libelf'):
+ assert spec[dep].eq_dag(yaml_spec[dep])
+
+
+def test_using_ordered_dict(builtin_mock):
+ """ Checks that dicts are ordered
+
+ Necessary to make sure that dag_hash is stable across python
+ versions and processes.
+ """
+ def descend_and_check(iterable, level=0):
+ from spack.util.spack_yaml import syaml_dict
+ from collections import Iterable, Mapping
+ if isinstance(iterable, Mapping):
+ assert isinstance(iterable, syaml_dict)
+ return descend_and_check(iterable.values(), level=level + 1)
+ max_level = level
+ for value in iterable:
+ if isinstance(value, Iterable) and not isinstance(value, str):
+ nlevel = descend_and_check(value, level=level + 1)
+ if nlevel > max_level:
+ max_level = nlevel
+ return max_level
+
+ specs = ['mpileaks ^zmpi', 'dttop', 'dtuse']
+ for spec in specs:
+ dag = Spec(spec)
+ dag.normalize()
+ level = descend_and_check(dag.to_node_dict())
+ # level just makes sure we are doing something here
+ assert level >= 5
+
+
+def test_ordered_read_not_required_for_consistent_dag_hash(
+ config, builtin_mock
+):
+ """Make sure ordered serialization isn't required to preserve hashes.
+
+ For consistent hashes, we require that YAML and json documents
+ have their keys serialized in a deterministic order. However, we
+ don't want to require them to be serialized in order. This
+ ensures that is not required.
+ """
+ specs = ['mpileaks ^zmpi', 'dttop', 'dtuse']
+ for spec in specs:
+ spec = Spec(spec)
spec.concretize()
- yaml_spec = Spec.from_yaml(spec.to_yaml())
-
- for dep in ('callpath', 'mpich', 'dyninst', 'libdwarf', 'libelf'):
- self.assertTrue(spec[dep].eq_dag(yaml_spec[dep]))
-
- def test_using_ordered_dict(self):
- """ Checks that dicts are ordered
-
- Necessary to make sure that dag_hash is stable across python
- versions and processes.
- """
- def descend_and_check(iterable, level=0):
- from spack.util.spack_yaml import syaml_dict
- from collections import Iterable, Mapping
- if isinstance(iterable, Mapping):
- self.assertTrue(isinstance(iterable, syaml_dict))
- return descend_and_check(iterable.values(), level=level + 1)
- max_level = level
- for value in iterable:
- if isinstance(value, Iterable) and not isinstance(value, str):
- nlevel = descend_and_check(value, level=level + 1)
- if nlevel > max_level:
- max_level = nlevel
- return max_level
-
- specs = ['mpileaks ^zmpi', 'dttop', 'dtuse']
- for spec in specs:
- dag = Spec(spec)
- dag.normalize()
- level = descend_and_check(dag.to_node_dict())
- # level just makes sure we are doing something here
- self.assertTrue(level >= 5)
-
- def test_ordered_read_not_required_for_consistent_dag_hash(self):
- """Make sure ordered serialization isn't required to preserve hashes.
-
- For consistent hashes, we require that YAML and json documents
- have their keys serialized in a deterministic order. However, we
- don't want to require them to be serialized in order. This
- ensures that is not reauired.
-
- """
- specs = ['mpileaks ^zmpi', 'dttop', 'dtuse']
- for spec in specs:
- spec = Spec(spec)
- spec.concretize()
-
- #
- # Dict & corresponding YAML & JSON from the original spec.
- #
- spec_dict = spec.to_dict()
- spec_yaml = spec.to_yaml()
- spec_json = spec.to_json()
-
- #
- # Make a spec with reversed OrderedDicts for every
- # OrderedDict in the original.
- #
- reversed_spec_dict = reverse_all_dicts(spec.to_dict())
-
- #
- # Dump to YAML and JSON
- #
- yaml_string = syaml.dump(spec_dict, default_flow_style=False)
- reversed_yaml_string = syaml.dump(reversed_spec_dict,
- default_flow_style=False)
- json_string = sjson.dump(spec_dict)
- reversed_json_string = sjson.dump(reversed_spec_dict)
-
- #
- # Do many consistency checks
- #
-
- # spec yaml is ordered like the spec dict
- self.assertEqual(yaml_string, spec_yaml)
- self.assertEqual(json_string, spec_json)
-
- # reversed string is different from the original, so it
- # *would* generate a different hash
- self.assertNotEqual(yaml_string, reversed_yaml_string)
- self.assertNotEqual(json_string, reversed_json_string)
-
- # build specs from the "wrongly" ordered data
- round_trip_yaml_spec = Spec.from_yaml(yaml_string)
- round_trip_json_spec = Spec.from_json(json_string)
- round_trip_reversed_yaml_spec = Spec.from_yaml(
- reversed_yaml_string)
- round_trip_reversed_json_spec = Spec.from_yaml(
- reversed_json_string)
-
- # TODO: remove this when build deps are in provenance.
- spec = spec.copy(deps=('link', 'run'))
-
- # specs are equal to the original
- self.assertEqual(spec, round_trip_yaml_spec)
- self.assertEqual(spec, round_trip_json_spec)
- self.assertEqual(spec, round_trip_reversed_yaml_spec)
- self.assertEqual(spec, round_trip_reversed_json_spec)
- self.assertEqual(round_trip_yaml_spec,
- round_trip_reversed_yaml_spec)
- self.assertEqual(round_trip_json_spec,
- round_trip_reversed_json_spec)
-
- # dag_hashes are equal
- self.assertEqual(
- spec.dag_hash(), round_trip_yaml_spec.dag_hash())
- self.assertEqual(
- spec.dag_hash(), round_trip_json_spec.dag_hash())
- self.assertEqual(
- spec.dag_hash(), round_trip_reversed_yaml_spec.dag_hash())
- self.assertEqual(
- spec.dag_hash(), round_trip_reversed_json_spec.dag_hash())
+
+ #
+ # Dict & corresponding YAML & JSON from the original spec.
+ #
+ spec_dict = spec.to_dict()
+ spec_yaml = spec.to_yaml()
+ spec_json = spec.to_json()
+
+ #
+ # Make a spec with reversed OrderedDicts for every
+ # OrderedDict in the original.
+ #
+ reversed_spec_dict = reverse_all_dicts(spec.to_dict())
+
+ #
+ # Dump to YAML and JSON
+ #
+ yaml_string = syaml.dump(spec_dict, default_flow_style=False)
+ reversed_yaml_string = syaml.dump(reversed_spec_dict,
+ default_flow_style=False)
+ json_string = sjson.dump(spec_dict)
+ reversed_json_string = sjson.dump(reversed_spec_dict)
+
+ #
+ # Do many consistency checks
+ #
+
+ # spec yaml is ordered like the spec dict
+ assert yaml_string == spec_yaml
+ assert json_string == spec_json
+
+ # reversed string is different from the original, so it
+ # *would* generate a different hash
+ assert yaml_string != reversed_yaml_string
+ assert json_string != reversed_json_string
+
+ # build specs from the "wrongly" ordered data
+ round_trip_yaml_spec = Spec.from_yaml(yaml_string)
+ round_trip_json_spec = Spec.from_json(json_string)
+ round_trip_reversed_yaml_spec = Spec.from_yaml(
+ reversed_yaml_string
+ )
+ round_trip_reversed_json_spec = Spec.from_yaml(
+ reversed_json_string
+ )
+
+ # TODO: remove this when build deps are in provenance.
+ spec = spec.copy(deps=('link', 'run'))
+ # specs are equal to the original
+ assert spec == round_trip_yaml_spec
+ assert spec == round_trip_json_spec
+ assert spec == round_trip_reversed_yaml_spec
+ assert spec == round_trip_reversed_json_spec
+ assert round_trip_yaml_spec == round_trip_reversed_yaml_spec
+ assert round_trip_json_spec == round_trip_reversed_json_spec
+ # dag_hashes are equal
+ assert spec.dag_hash() == round_trip_yaml_spec.dag_hash()
+ assert spec.dag_hash() == round_trip_json_spec.dag_hash()
+ assert spec.dag_hash() == round_trip_reversed_yaml_spec.dag_hash()
+ assert spec.dag_hash() == round_trip_reversed_json_spec.dag_hash()
def reverse_all_dicts(data):
diff --git a/lib/spack/spack/test/stage.py b/lib/spack/spack/test/stage.py
index cfeb80dd35..5b4c46e0bf 100644
--- a/lib/spack/spack/test/stage.py
+++ b/lib/spack/spack/test/stage.py
@@ -22,355 +22,360 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-"""\
-Test that the Stage class works correctly.
-"""
+"""Test that the Stage class works correctly."""
+import collections
import os
-import shutil
-import tempfile
-from contextlib import *
+import pytest
import spack
import spack.stage
-from llnl.util.filesystem import *
+import spack.util.executable
+from llnl.util.filesystem import join_path
from spack.stage import Stage
-from spack.util.executable import which
-from spack.test.mock_packages_test import *
-_test_tmp_path = None
+def check_chdir_to_source(stage, stage_name):
+ stage_path = get_stage_path(stage, stage_name)
+ archive_dir = 'test-files'
+ assert join_path(
+ os.path.realpath(stage_path), archive_dir
+ ) == os.getcwd()
-@contextmanager
-def use_tmp(use_tmp):
- """Allow some test code to be executed such that spack will either use or
- not use temporary space for stages.
- """
- # mock up config
- assert(_test_tmp_path is not None)
- if use_tmp:
- path = _test_tmp_path # use temporary stage
+def check_expand_archive(stage, stage_name, mock_archive):
+ stage_path = get_stage_path(stage, stage_name)
+ archive_name = 'test-files.tar.gz'
+ archive_dir = 'test-files'
+ assert archive_name in os.listdir(stage_path)
+ assert archive_dir in os.listdir(stage_path)
+
+ assert join_path(stage_path, archive_dir) == stage.source_path
+
+ readme = join_path(stage_path, archive_dir, 'README.txt')
+ assert os.path.isfile(readme)
+ with open(readme) as file:
+ 'hello world!\n' == file.read()
+
+
+def check_fetch(stage, stage_name):
+ archive_name = 'test-files.tar.gz'
+ stage_path = get_stage_path(stage, stage_name)
+ assert archive_name in os.listdir(stage_path)
+ assert join_path(stage_path, archive_name) == stage.fetcher.archive_file
+
+
+def check_chdir(stage, stage_name):
+ stage_path = get_stage_path(stage, stage_name)
+ assert os.path.realpath(stage_path) == os.getcwd()
+
+
+def check_destroy(stage, stage_name):
+ """Figure out whether a stage was destroyed correctly."""
+ stage_path = get_stage_path(stage, stage_name)
+
+ # check that the stage dir/link was removed.
+ assert not os.path.exists(stage_path)
+
+ # tmp stage needs to remove tmp dir too.
+ if spack.stage._use_tmp_stage:
+ target = os.path.realpath(stage_path)
+ assert not os.path.exists(target)
+
+
+def check_setup(stage, stage_name, archive):
+ """Figure out whether a stage was set up correctly."""
+ stage_path = get_stage_path(stage, stage_name)
+
+ # Ensure stage was created in the spack stage directory
+ assert os.path.isdir(stage_path)
+
+ if spack.stage.get_tmp_root():
+ # Check that the stage dir is really a symlink.
+ assert os.path.islink(stage_path)
+
+ # Make sure it points to a valid directory
+ target = os.path.realpath(stage_path)
+ assert os.path.isdir(target)
+ assert not os.path.islink(target)
+
+ # Make sure the directory is in the place we asked it to
+ # be (see setUp, tearDown, and use_tmp)
+ assert target.startswith(str(archive.test_tmp_dir))
+
else:
- path = spack.stage_path # Use Spack's stage dir (no links)
+ # Make sure the stage path is NOT a link for a non-tmp stage
+ assert os.path.islink(stage_path)
- spack.config.update_config(
- 'config', {'build_stage': [path]}, scope='user')
- yield
+def get_stage_path(stage, stage_name):
+ """Figure out where a stage should be living. This depends on
+ whether it's named.
+ """
+ if stage_name is not None:
+ # If it is a named stage, we know where the stage should be
+ return join_path(spack.stage_path, stage_name)
+ else:
+ # If it's unnamed, ensure that we ran mkdtemp in the right spot.
+ assert stage.path is not None
+ assert stage.path.startswith(spack.stage_path)
+ return stage.path
-def fail_search_fn():
- raise Exception("This should not have been called")
-
-
-class FailingFetchStrategy(spack.fetch_strategy.FetchStrategy):
- def fetch(self):
- raise spack.fetch_strategy.FailedDownloadError(
- "<non-existent URL>",
- "This implementation of FetchStrategy always fails")
-
-
-class MockSearchFunction(object):
- def __init__(self):
- self.performed_search = False
-
- def __call__(self):
- self.performed_search = True
- return []
-
-
-class StageTest(MockPackagesTest):
-
- def setUp(self):
- """This sets up a mock archive to fetch, and a mock temp space for use
- by the Stage class. It doesn't actually create the Stage -- that
- is done by individual tests.
- """
- super(StageTest, self).setUp()
-
- global _test_tmp_path
-
- #
- # Mock up a stage area that looks like this:
- #
- # TMPDIR/ test_files_dir
- # tmp/ test_tmp_path (where stage should be)
- # test-files/ archive_dir_path
- # README.txt test_readme (contains "hello world!\n")
- # test-files.tar.gz archive_url = file:///path/to/this
- #
- self.test_files_dir = tempfile.mkdtemp()
- self.test_tmp_path = os.path.realpath(
- os.path.join(self.test_files_dir, 'tmp'))
- _test_tmp_path = self.test_tmp_path
-
- # set _test_tmp_path as the default test directory to use for stages.
- spack.config.update_config(
- 'config', {'build_stage': [_test_tmp_path]}, scope='user')
-
- self.archive_dir = 'test-files'
- self.archive_name = self.archive_dir + '.tar.gz'
- archive_dir_path = os.path.join(self.test_files_dir,
- self.archive_dir)
- self.archive_url = 'file://' + os.path.join(self.test_files_dir,
- self.archive_name)
- test_readme = join_path(archive_dir_path, 'README.txt')
- self.readme_text = "hello world!\n"
-
- self.stage_name = 'spack-test-stage'
-
- mkdirp(archive_dir_path)
- mkdirp(self.test_tmp_path)
-
- with open(test_readme, 'w') as readme:
- readme.write(self.readme_text)
-
- with working_dir(self.test_files_dir):
- tar = which('tar', required=True)
- tar('czf', self.archive_name, self.archive_dir)
-
- # Make spack use the test environment for tmp stuff.
- self._old_tmp_root = spack.stage._tmp_root
- self._old_use_tmp_stage = spack.stage._use_tmp_stage
- spack.stage._tmp_root = None
- spack.stage._use_tmp_stage = True
-
- # record this since this test changes to directories that will
- # be removed.
- self.working_dir = os.getcwd()
-
- def tearDown(self):
- """Blows away the test environment directory."""
- super(StageTest, self).tearDown()
-
- shutil.rmtree(self.test_files_dir, ignore_errors=True)
-
- # chdir back to original working dir
- os.chdir(self.working_dir)
-
- # restore spack's original tmp environment
- spack.stage._tmp_root = self._old_tmp_root
- spack.stage._use_tmp_stage = self._old_use_tmp_stage
-
- def get_stage_path(self, stage, stage_name):
- """Figure out where a stage should be living. This depends on
- whether it's named.
- """
- if stage_name is not None:
- # If it is a named stage, we know where the stage should be
- return join_path(spack.stage_path, stage_name)
- else:
- # If it's unnamed, ensure that we ran mkdtemp in the right spot.
- self.assertTrue(stage.path is not None)
- self.assertTrue(stage.path.startswith(spack.stage_path))
- return stage.path
-
- def check_setup(self, stage, stage_name):
- """Figure out whether a stage was set up correctly."""
- stage_path = self.get_stage_path(stage, stage_name)
-
- # Ensure stage was created in the spack stage directory
- self.assertTrue(os.path.isdir(stage_path))
-
- if spack.stage.get_tmp_root():
- # Check that the stage dir is really a symlink.
- self.assertTrue(os.path.islink(stage_path))
-
- # Make sure it points to a valid directory
- target = os.path.realpath(stage_path)
- self.assertTrue(os.path.isdir(target))
- self.assertFalse(os.path.islink(target))
-
- # Make sure the directory is in the place we asked it to
- # be (see setUp, tearDown, and use_tmp)
- self.assertTrue(target.startswith(self.test_tmp_path))
-
- else:
- # Make sure the stage path is NOT a link for a non-tmp stage
- self.assertFalse(os.path.islink(stage_path))
-
- def check_fetch(self, stage, stage_name):
- stage_path = self.get_stage_path(stage, stage_name)
- self.assertTrue(self.archive_name in os.listdir(stage_path))
- self.assertEqual(join_path(stage_path, self.archive_name),
- stage.fetcher.archive_file)
-
- def check_expand_archive(self, stage, stage_name):
- stage_path = self.get_stage_path(stage, stage_name)
- self.assertTrue(self.archive_name in os.listdir(stage_path))
- self.assertTrue(self.archive_dir in os.listdir(stage_path))
-
- self.assertEqual(
- join_path(stage_path, self.archive_dir),
- stage.source_path)
-
- readme = join_path(stage_path, self.archive_dir, 'README.txt')
- self.assertTrue(os.path.isfile(readme))
-
- with open(readme) as file:
- self.assertEqual(self.readme_text, file.read())
-
- def check_chdir(self, stage, stage_name):
- stage_path = self.get_stage_path(stage, stage_name)
- self.assertEqual(os.path.realpath(stage_path), os.getcwd())
-
- def check_chdir_to_source(self, stage, stage_name):
- stage_path = self.get_stage_path(stage, stage_name)
- self.assertEqual(
- join_path(os.path.realpath(stage_path), self.archive_dir),
- os.getcwd())
-
- def check_destroy(self, stage, stage_name):
- """Figure out whether a stage was destroyed correctly."""
- stage_path = self.get_stage_path(stage, stage_name)
-
- # check that the stage dir/link was removed.
- self.assertFalse(os.path.exists(stage_path))
-
- # tmp stage needs to remove tmp dir too.
- if spack.stage._use_tmp_stage:
- target = os.path.realpath(stage_path)
- self.assertFalse(os.path.exists(target))
-
- def test_setup_and_destroy_name_with_tmp(self):
- with use_tmp(True):
- with Stage(self.archive_url, name=self.stage_name) as stage:
- self.check_setup(stage, self.stage_name)
- self.check_destroy(stage, self.stage_name)
-
- def test_setup_and_destroy_name_without_tmp(self):
- with use_tmp(False):
- with Stage(self.archive_url, name=self.stage_name) as stage:
- self.check_setup(stage, self.stage_name)
- self.check_destroy(stage, self.stage_name)
-
- def test_setup_and_destroy_no_name_with_tmp(self):
- with use_tmp(True):
- with Stage(self.archive_url) as stage:
- self.check_setup(stage, None)
- self.check_destroy(stage, None)
-
- def test_setup_and_destroy_no_name_without_tmp(self):
- with use_tmp(False):
- with Stage(self.archive_url) as stage:
- self.check_setup(stage, None)
- self.check_destroy(stage, None)
-
- def test_chdir(self):
- with Stage(self.archive_url, name=self.stage_name) as stage:
+@pytest.fixture()
+def tmpdir_for_stage(mock_archive):
+ """Uses a temporary directory for staging"""
+ current = spack.stage_path
+ spack.config.update_config(
+ 'config',
+ {'build_stage': [str(mock_archive.test_tmp_dir)]},
+ scope='user'
+ )
+ yield
+ spack.config.update_config(
+ 'config', {'build_stage': [current]}, scope='user'
+ )
+
+
+@pytest.fixture()
+def mock_archive(tmpdir, monkeypatch):
+ """Creates a mock archive with the structure expected by the tests"""
+ # Mock up a stage area that looks like this:
+ #
+ # TMPDIR/ test_files_dir
+ # tmp/ test_tmp_path (where stage should be)
+ # test-files/ archive_dir_path
+ # README.txt test_readme (contains "hello world!\n")
+ # test-files.tar.gz archive_url = file:///path/to/this
+ #
+ test_tmp_path = tmpdir.join('tmp')
+ # set _test_tmp_path as the default test directory to use for stages.
+ spack.config.update_config(
+ 'config', {'build_stage': [str(test_tmp_path)]}, scope='user'
+ )
+
+ archive_dir = tmpdir.join('test-files')
+ archive_name = 'test-files.tar.gz'
+ archive = tmpdir.join(archive_name)
+ archive_url = 'file://' + str(archive)
+ test_readme = archive_dir.join('README.txt')
+ archive_dir.ensure(dir=True)
+ test_tmp_path.ensure(dir=True)
+ test_readme.write('hello world!\n')
+
+ current = tmpdir.chdir()
+ tar = spack.util.executable.which('tar', required=True)
+ tar('czf', str(archive_name), 'test-files')
+ current.chdir()
+
+ # Make spack use the test environment for tmp stuff.
+ monkeypatch.setattr(spack.stage, '_tmp_root', None)
+ monkeypatch.setattr(spack.stage, '_use_tmp_stage', True)
+
+ Archive = collections.namedtuple(
+ 'Archive', ['url', 'tmpdir', 'test_tmp_dir', 'archive_dir']
+ )
+ yield Archive(
+ url=archive_url,
+ tmpdir=tmpdir,
+ test_tmp_dir=test_tmp_path,
+ archive_dir=archive_dir
+ )
+ # record this since this test changes to directories that will
+ # be removed.
+ current.chdir()
+
+
+@pytest.fixture()
+def failing_search_fn():
+ """Returns a search function that fails! Always!"""
+ def _mock():
+ raise Exception("This should not have been called")
+ return _mock
+
+
+@pytest.fixture()
+def failing_fetch_strategy():
+ """Returns a fetch strategy that fails."""
+ class FailingFetchStrategy(spack.fetch_strategy.FetchStrategy):
+ def fetch(self):
+ raise spack.fetch_strategy.FailedDownloadError(
+ "<non-existent URL>",
+ "This implementation of FetchStrategy always fails"
+ )
+ return FailingFetchStrategy()
+
+
+@pytest.fixture()
+def search_fn():
+ """Returns a search function that always succeeds."""
+ class _Mock(object):
+ performed_search = False
+
+ def __call__(self):
+ self.performed_search = True
+ return []
+
+ return _Mock()
+
+
+@pytest.mark.usefixtures('builtin_mock')
+class TestStage(object):
+
+ stage_name = 'spack-test-stage'
+
+ @pytest.mark.usefixtures('tmpdir_for_stage')
+ def test_setup_and_destroy_name_with_tmp(self, mock_archive):
+ with Stage(mock_archive.url, name=self.stage_name) as stage:
+ check_setup(stage, self.stage_name, mock_archive)
+ check_destroy(stage, self.stage_name)
+
+ def test_setup_and_destroy_name_without_tmp(self, mock_archive):
+ with Stage(mock_archive.url, name=self.stage_name) as stage:
+ check_setup(stage, self.stage_name, mock_archive)
+ check_destroy(stage, self.stage_name)
+
+ @pytest.mark.usefixtures('tmpdir_for_stage')
+ def test_setup_and_destroy_no_name_with_tmp(self, mock_archive):
+ with Stage(mock_archive.url) as stage:
+ check_setup(stage, None, mock_archive)
+ check_destroy(stage, None)
+
+ def test_setup_and_destroy_no_name_without_tmp(self, mock_archive):
+ with Stage(mock_archive.url) as stage:
+ check_setup(stage, None, mock_archive)
+ check_destroy(stage, None)
+
+ def test_chdir(self, mock_archive):
+ with Stage(mock_archive.url, name=self.stage_name) as stage:
stage.chdir()
- self.check_setup(stage, self.stage_name)
- self.check_chdir(stage, self.stage_name)
- self.check_destroy(stage, self.stage_name)
+ check_setup(stage, self.stage_name, mock_archive)
+ check_chdir(stage, self.stage_name)
+ check_destroy(stage, self.stage_name)
- def test_fetch(self):
- with Stage(self.archive_url, name=self.stage_name) as stage:
+ def test_fetch(self, mock_archive):
+ with Stage(mock_archive.url, name=self.stage_name) as stage:
stage.fetch()
- self.check_setup(stage, self.stage_name)
- self.check_chdir(stage, self.stage_name)
- self.check_fetch(stage, self.stage_name)
- self.check_destroy(stage, self.stage_name)
-
- def test_no_search_if_default_succeeds(self):
- with Stage(self.archive_url, name=self.stage_name,
- search_fn=fail_search_fn) as stage:
+ check_setup(stage, self.stage_name, mock_archive)
+ check_chdir(stage, self.stage_name)
+ check_fetch(stage, self.stage_name)
+ check_destroy(stage, self.stage_name)
+
+ def test_no_search_if_default_succeeds(
+ self, mock_archive, failing_search_fn
+ ):
+ with Stage(
+ mock_archive.url,
+ name=self.stage_name,
+ search_fn=failing_search_fn
+ ) as stage:
stage.fetch()
- self.check_destroy(stage, self.stage_name)
-
- def test_no_search_mirror_only(self):
- with Stage(FailingFetchStrategy(), name=self.stage_name,
- search_fn=fail_search_fn) as stage:
+ check_destroy(stage, self.stage_name)
+
+ def test_no_search_mirror_only(
+ self, failing_fetch_strategy, failing_search_fn
+ ):
+ with Stage(
+ failing_fetch_strategy,
+ name=self.stage_name,
+ search_fn=failing_search_fn
+ ) as stage:
try:
stage.fetch(mirror_only=True)
except spack.fetch_strategy.FetchError:
pass
- self.check_destroy(stage, self.stage_name)
-
- def test_search_if_default_fails(self):
- test_search = MockSearchFunction()
- with Stage(FailingFetchStrategy(), name=self.stage_name,
- search_fn=test_search) as stage:
+ check_destroy(stage, self.stage_name)
+
+ def test_search_if_default_fails(self, failing_fetch_strategy, search_fn):
+ with Stage(
+ failing_fetch_strategy,
+ name=self.stage_name,
+ search_fn=search_fn
+ ) as stage:
try:
stage.fetch(mirror_only=False)
except spack.fetch_strategy.FetchError:
pass
- self.check_destroy(stage, self.stage_name)
- self.assertTrue(test_search.performed_search)
+ check_destroy(stage, self.stage_name)
+ assert search_fn.performed_search
- def test_expand_archive(self):
- with Stage(self.archive_url, name=self.stage_name) as stage:
+ def test_expand_archive(self, mock_archive):
+ with Stage(mock_archive.url, name=self.stage_name) as stage:
stage.fetch()
- self.check_setup(stage, self.stage_name)
- self.check_fetch(stage, self.stage_name)
+ check_setup(stage, self.stage_name, mock_archive)
+ check_fetch(stage, self.stage_name)
stage.expand_archive()
- self.check_expand_archive(stage, self.stage_name)
- self.check_destroy(stage, self.stage_name)
+ check_expand_archive(stage, self.stage_name, mock_archive)
+ check_destroy(stage, self.stage_name)
- def test_expand_archive_with_chdir(self):
- with Stage(self.archive_url, name=self.stage_name) as stage:
+ def test_expand_archive_with_chdir(self, mock_archive):
+ with Stage(mock_archive.url, name=self.stage_name) as stage:
stage.fetch()
- self.check_setup(stage, self.stage_name)
- self.check_fetch(stage, self.stage_name)
+ check_setup(stage, self.stage_name, mock_archive)
+ check_fetch(stage, self.stage_name)
stage.expand_archive()
stage.chdir_to_source()
- self.check_expand_archive(stage, self.stage_name)
- self.check_chdir_to_source(stage, self.stage_name)
- self.check_destroy(stage, self.stage_name)
+ check_expand_archive(stage, self.stage_name, mock_archive)
+ check_chdir_to_source(stage, self.stage_name)
+ check_destroy(stage, self.stage_name)
- def test_restage(self):
- with Stage(self.archive_url, name=self.stage_name) as stage:
+ def test_restage(self, mock_archive):
+ with Stage(mock_archive.url, name=self.stage_name) as stage:
stage.fetch()
stage.expand_archive()
stage.chdir_to_source()
- self.check_expand_archive(stage, self.stage_name)
- self.check_chdir_to_source(stage, self.stage_name)
+ check_expand_archive(stage, self.stage_name, mock_archive)
+ check_chdir_to_source(stage, self.stage_name)
# Try to make a file in the old archive dir
with open('foobar', 'w') as file:
file.write("this file is to be destroyed.")
- self.assertTrue('foobar' in os.listdir(stage.source_path))
+ assert 'foobar' in os.listdir(stage.source_path)
# Make sure the file is not there after restage.
stage.restage()
- self.check_chdir(stage, self.stage_name)
- self.check_fetch(stage, self.stage_name)
+ check_chdir(stage, self.stage_name)
+ check_fetch(stage, self.stage_name)
stage.chdir_to_source()
- self.check_chdir_to_source(stage, self.stage_name)
- self.assertFalse('foobar' in os.listdir(stage.source_path))
- self.check_destroy(stage, self.stage_name)
-
- def test_no_keep_without_exceptions(self):
- with Stage(self.archive_url,
- name=self.stage_name, keep=False) as stage:
+ check_chdir_to_source(stage, self.stage_name)
+ assert 'foobar' not in os.listdir(stage.source_path)
+ check_destroy(stage, self.stage_name)
+
+ def test_no_keep_without_exceptions(self, mock_archive):
+ with Stage(
+ mock_archive.url, name=self.stage_name, keep=False
+ ) as stage:
pass
- self.check_destroy(stage, self.stage_name)
+ check_destroy(stage, self.stage_name)
- def test_keep_without_exceptions(self):
- with Stage(self.archive_url,
- name=self.stage_name, keep=True) as stage:
+ def test_keep_without_exceptions(self, mock_archive):
+ with Stage(
+ mock_archive.url, name=self.stage_name, keep=True
+ ) as stage:
pass
- path = self.get_stage_path(stage, self.stage_name)
- self.assertTrue(os.path.isdir(path))
+ path = get_stage_path(stage, self.stage_name)
+ assert os.path.isdir(path)
- def test_no_keep_with_exceptions(self):
+ def test_no_keep_with_exceptions(self, mock_archive):
+ class ThisMustFailHere(Exception):
+ pass
try:
- with Stage(self.archive_url,
- name=self.stage_name, keep=False) as stage:
- raise Exception()
-
- path = self.get_stage_path(stage, self.stage_name)
- self.assertTrue(os.path.isdir(path))
- except:
- pass # ignore here.
-
- def test_keep_exceptions(self):
+ with Stage(
+ mock_archive.url, name=self.stage_name, keep=False
+ ) as stage:
+ raise ThisMustFailHere()
+ except ThisMustFailHere:
+ path = get_stage_path(stage, self.stage_name)
+ assert os.path.isdir(path)
+
+ def test_keep_exceptions(self, mock_archive):
+ class ThisMustFailHere(Exception):
+ pass
try:
- with Stage(self.archive_url,
- name=self.stage_name, keep=True) as stage:
- raise Exception()
-
- path = self.get_stage_path(stage, self.stage_name)
- self.assertTrue(os.path.isdir(path))
- except:
- pass # ignore here.
+ with Stage(
+ mock_archive.url, name=self.stage_name, keep=True
+ ) as stage:
+ raise ThisMustFailHere()
+ except ThisMustFailHere:
+ path = get_stage_path(stage, self.stage_name)
+ assert os.path.isdir(path)
diff --git a/lib/spack/spack/test/svn_fetch.py b/lib/spack/spack/test/svn_fetch.py
index 01ffc488a7..962a150909 100644
--- a/lib/spack/spack/test/svn_fetch.py
+++ b/lib/spack/spack/test/svn_fetch.py
@@ -23,87 +23,62 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import os
-import re
-import spack
-from spack.test.mock_repo import svn, MockSvnRepo
-from spack.version import ver
-from spack.test.mock_packages_test import *
+import pytest
+import spack
from llnl.util.filesystem import *
+from spack.spec import Spec
+from spack.version import ver
-class SvnFetchTest(MockPackagesTest):
- """Tests fetching from a dummy git repository."""
-
- def setUp(self):
- """Create an svn repository with two revisions."""
- super(SvnFetchTest, self).setUp()
-
- self.repo = MockSvnRepo()
-
- spec = Spec('svn-test')
- spec.concretize()
- self.pkg = spack.repo.get(spec, new=True)
-
- def tearDown(self):
- """Destroy the stage space used by this test."""
- super(SvnFetchTest, self).tearDown()
- self.repo.destroy()
-
- def assert_rev(self, rev):
- """Check that the current revision is equal to the supplied rev."""
- def get_rev():
- output = svn('info', output=str)
- self.assertTrue("Revision" in output)
- for line in output.split('\n'):
- match = re.match(r'Revision: (\d+)', line)
- if match:
- return match.group(1)
- self.assertEqual(get_rev(), rev)
-
- def try_fetch(self, rev, test_file, args):
- """Tries to:
-
- 1. Fetch the repo using a fetch strategy constructed with
- supplied args.
- 2. Check if the test_file is in the checked out repository.
- 3. Assert that the repository is at the revision supplied.
- 4. Add and remove some files, then reset the repo, and
- ensure it's all there again.
- """
- self.pkg.versions[ver('svn')] = args
-
- with self.pkg.stage:
- self.pkg.do_stage()
- self.assert_rev(rev)
-
- file_path = join_path(self.pkg.stage.source_path, test_file)
- self.assertTrue(os.path.isdir(self.pkg.stage.source_path))
- self.assertTrue(os.path.isfile(file_path))
-
- os.unlink(file_path)
- self.assertFalse(os.path.isfile(file_path))
-
- untracked = 'foobarbaz'
- touch(untracked)
- self.assertTrue(os.path.isfile(untracked))
- self.pkg.do_restage()
- self.assertFalse(os.path.isfile(untracked))
-
- self.assertTrue(os.path.isdir(self.pkg.stage.source_path))
- self.assertTrue(os.path.isfile(file_path))
-
- self.assert_rev(rev)
-
- def test_fetch_default(self):
- """Test a default checkout and make sure it's on rev 1"""
- self.try_fetch(self.repo.r1, self.repo.r1_file, {
- 'svn': self.repo.url
- })
-
- def test_fetch_r1(self):
- """Test fetching an older revision (0)."""
- self.try_fetch(self.repo.r0, self.repo.r0_file, {
- 'svn': self.repo.url,
- 'revision': self.repo.r0
- })
+@pytest.fixture(params=['default', 'rev0'])
+def type_of_test(request):
+ """Returns one of the test type available for the mock_hg_repository"""
+ return request.param
+
+
+def test_fetch(
+ type_of_test,
+ mock_svn_repository,
+ config,
+ refresh_builtin_mock
+):
+ """Tries to:
+
+ 1. Fetch the repo using a fetch strategy constructed with
+ supplied args (they depend on type_of_test).
+ 2. Check if the test_file is in the checked out repository.
+ 3. Assert that the repository is at the revision supplied.
+ 4. Add and remove some files, then reset the repo, and
+ ensure it's all there again.
+ """
+ # Retrieve the right test parameters
+ t = mock_svn_repository.checks[type_of_test]
+ h = mock_svn_repository.hash
+ # Construct the package under test
+ spec = Spec('hg-test')
+ spec.concretize()
+ pkg = spack.repo.get(spec, new=True)
+ pkg.versions[ver('hg')] = t.args
+ # Enter the stage directory and check some properties
+ with pkg.stage:
+ pkg.do_stage()
+ assert h() == t.revision
+
+ file_path = join_path(pkg.stage.source_path, t.file)
+ assert os.path.isdir(pkg.stage.source_path)
+ assert os.path.isfile(file_path)
+
+ os.unlink(file_path)
+ assert not os.path.isfile(file_path)
+
+ untracked_file = 'foobarbaz'
+ touch(untracked_file)
+ assert os.path.isfile(untracked_file)
+ pkg.do_restage()
+ assert not os.path.isfile(untracked_file)
+
+ assert os.path.isdir(pkg.stage.source_path)
+ assert os.path.isfile(file_path)
+
+ assert h() == t.revision
diff --git a/lib/spack/spack/test/tally_plugin.py b/lib/spack/spack/test/tally_plugin.py
deleted file mode 100644
index d848f2cb9f..0000000000
--- a/lib/spack/spack/test/tally_plugin.py
+++ /dev/null
@@ -1,64 +0,0 @@
-##############################################################################
-# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
-# LLNL-CODE-647188
-#
-# For details, see https://github.com/llnl/spack
-# Please also see the LICENSE file for our notice and the LGPL.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License (as
-# published by the Free Software Foundation) version 2.1, February 1999.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
-# conditions of the GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##############################################################################
-import os
-
-from nose.plugins import Plugin
-
-
-class Tally(Plugin):
- name = 'tally'
-
- def __init__(self):
- super(Tally, self).__init__()
- self.successCount = 0
- self.failCount = 0
- self.errorCount = 0
- self.error_list = []
- self.fail_list = []
-
- @property
- def numberOfTestsRun(self):
- """Excludes skipped tests"""
- return self.errorCount + self.failCount + self.successCount
-
- def options(self, parser, env=os.environ):
- super(Tally, self).options(parser, env=env)
-
- def configure(self, options, conf):
- super(Tally, self).configure(options, conf)
-
- def addSuccess(self, test):
- self.successCount += 1
-
- def addError(self, test, err):
- self.errorCount += 1
- self.error_list.append(test)
-
- def addFailure(self, test, err):
- self.failCount += 1
- self.fail_list.append(test)
-
- def finalize(self, result):
- pass
diff --git a/lib/spack/spack/test/url_extrapolate.py b/lib/spack/spack/test/url_extrapolate.py
index ca14dab958..5f5cf555ae 100644
--- a/lib/spack/spack/test/url_extrapolate.py
+++ b/lib/spack/spack/test/url_extrapolate.py
@@ -22,11 +22,12 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-"""\
-Tests ability of spack to extrapolate URL versions from existing versions.
+"""Tests ability of spack to extrapolate URL versions from
+existing versions.
"""
+import unittest
+
import spack.url as url
-from spack.test.mock_packages_test import *
class UrlExtrapolateTest(unittest.TestCase):