diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 23a9f8c56..7054f063d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,7 @@ Thanks for submitting a PR, your contribution is really appreciated! Here's a quick checklist that should be present in PRs (you can delete this text from the final description, this is just a guideline): -- [ ] Create a new changelog file in the `changelog` folder, with a name like `..rst`. See [changelog/README.rst](/changelog/README.rst) for details. +- [ ] Create a new changelog file in the `changelog` folder, with a name like `..rst`. See [changelog/README.rst](https://github.com/pytest-dev/pytest/blob/master/changelog/README.rst) for details. - [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes. - [ ] Target the `features` branch for new features and removals/deprecations. - [ ] Include documentation when adding new features. diff --git a/.travis.yml b/.travis.yml index 3936e599b..00abca0b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,19 +12,15 @@ install: - pip install --upgrade --pre tox env: matrix: - # note: please use "tox --listenvs" to populate the build matrix below - # please remove the linting env in all cases - - TOXENV=py27-pexpect - - TOXENV=py27-xdist - - TOXENV=py27-trial - - TOXENV=py27-numpy - - TOXENV=py27-pluggymaster PYTEST_NO_COVERAGE=1 - - TOXENV=py36-pexpect - - TOXENV=py36-xdist - - TOXENV=py36-trial - - TOXENV=py36-numpy - - TOXENV=py36-pluggymaster PYTEST_NO_COVERAGE=1 + # Specialized factors for py27. + - TOXENV=py27-pexpect,py27-trial,py27-numpy - TOXENV=py27-nobyte + - TOXENV=py27-xdist + - TOXENV=py27-pluggymaster PYTEST_NO_COVERAGE=1 + # Specialized factors for py36. + - TOXENV=py36-pexpect,py36-trial,py36-numpy + - TOXENV=py36-xdist + - TOXENV=py36-pluggymaster PYTEST_NO_COVERAGE=1 jobs: include: @@ -97,12 +93,6 @@ after_success: coverage xml --ignore-errors coverage report -m --ignore-errors bash <(curl -s https://codecov.io/bash) -Z -X gcov -X coveragepy -X search -X xcode -X gcovout -X fix -f coverage.xml -F "${TOXENV//-/,},linux" - - # Coveralls does not support merged reports. - if [[ "$TOXENV" = py37 ]]; then - pip install coveralls - coveralls - fi fi notifications: diff --git a/AUTHORS b/AUTHORS index ae375228b..dabeb1c06 100644 --- a/AUTHORS +++ b/AUTHORS @@ -204,6 +204,7 @@ Stefan Zimmermann Stefano Taschini Steffen Allner Stephan Obermann +Sven-Hendrik Haase Tadek Teleżyński Tarcisio Fischer Tareq Alayan @@ -213,6 +214,7 @@ Thomas Hisch Tim Strazny Tom Dalton Tom Viner +Tomer Keren Trevor Bekolay Tyler Goodlet Tzu-ping Chung diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 26bc28af1..14881e373 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,163 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 3.9.1 (2018-10-16) +========================= + +Features +-------- + +- `#4159 `_: For test-suites containing test classes, the information about the subclassed + module is now output only if a higher verbosity level is specified (at least + "-vv"). + + +pytest 3.9.0 (2018-10-15 - not published due to a release automation bug) +========================================================================= + +Deprecations +------------ + +- `#3616 `_: The following accesses have been documented as deprecated for years, but are now actually emitting deprecation warnings. + + * Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances. Now + users will this warning:: + + usage of Function.Module is deprecated, please use pytest.Module instead + + Users should just ``import pytest`` and access those objects using the ``pytest`` module. + + * ``request.cached_setup``, this was the precursor of the setup/teardown mechanism available to fixtures. You can + consult `funcarg comparison section in the docs `_. + + * Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector`` + subclasses has been deprecated. Users instead should use ``pytest_collect_make_item`` to customize node types during + collection. + + This issue should affect only advanced plugins who create new collection types, so if you see this warning + message please contact the authors so they can change the code. + + * The warning that produces the message below has changed to ``RemovedInPytest4Warning``:: + + getfuncargvalue is deprecated, use getfixturevalue + + +- `#3988 `_: Add a Deprecation warning for pytest.ensuretemp as it was deprecated since a while. + + + +Features +-------- + +- `#2293 `_: Improve usage errors messages by hiding internal details which can be distracting and noisy. + + This has the side effect that some error conditions that previously raised generic errors (such as + ``ValueError`` for unregistered marks) are now raising ``Failed`` exceptions. + + +- `#3332 `_: Improve the error displayed when a ``conftest.py`` file could not be imported. + + In order to implement this, a new ``chain`` parameter was added to ``ExceptionInfo.getrepr`` + to show or hide chained tracebacks in Python 3 (defaults to ``True``). + + +- `#3849 `_: Add ``empty_parameter_set_mark=fail_at_collect`` ini option for raising an exception when parametrize collects an empty set. + + +- `#3964 `_: Log messages generated in the collection phase are shown when + live-logging is enabled and/or when they are logged to a file. + + +- `#3985 `_: Introduce ``tmp_path`` as a fixture providing a Path object. + + +- `#4013 `_: Deprecation warnings are now shown even if you customize the warnings filters yourself. In the previous version + any customization would override pytest's filters and deprecation warnings would fall back to being hidden by default. + + +- `#4073 `_: Allow specification of timeout for ``Testdir.runpytest_subprocess()`` and ``Testdir.run()``. + + +- `#4098 `_: Add returncode argument to pytest.exit() to exit pytest with a specific return code. + + +- `#4102 `_: Reimplement ``pytest.deprecated_call`` using ``pytest.warns`` so it supports the ``match='...'`` keyword argument. + + This has the side effect that ``pytest.deprecated_call`` now raises ``pytest.fail.Exception`` instead + of ``AssertionError``. + + +- `#4149 `_: Require setuptools>=30.3 and move most of the metadata to ``setup.cfg``. + + + +Bug Fixes +--------- + +- `#2535 `_: Improve error message when test functions of ``unittest.TestCase`` subclasses use a parametrized fixture. + + +- `#3057 `_: ``request.fixturenames`` now correctly returns the name of fixtures created by ``request.getfixturevalue()``. + + +- `#3946 `_: Warning filters passed as command line options using ``-W`` now take precedence over filters defined in ``ini`` + configuration files. + + +- `#4066 `_: Fix source reindenting by using ``textwrap.dedent`` directly. + + +- `#4102 `_: ``pytest.warn`` will capture previously-warned warnings in Python 2. Previously they were never raised. + + +- `#4108 `_: Resolve symbolic links for args. + + This fixes running ``pytest tests/test_foo.py::test_bar``, where ``tests`` + is a symlink to ``project/app/tests``: + previously ``project/app/conftest.py`` would be ignored for fixtures then. + + +- `#4132 `_: Fix duplicate printing of internal errors when using ``--pdb``. + + +- `#4135 `_: pathlib based tmpdir cleanup now correctly handles symlinks in the folder. + + +- `#4152 `_: Display the filename when encountering ``SyntaxWarning``. + + + +Improved Documentation +---------------------- + +- `#3713 `_: Update usefixtures documentation to clarify that it can't be used with fixture functions. + + +- `#4058 `_: Update fixture documentation to specify that a fixture can be invoked twice in the scope it's defined for. + + +- `#4064 `_: According to unittest.rst, setUpModule and tearDownModule were not implemented, but it turns out they are. So updated the documentation for unittest. + + +- `#4151 `_: Add tempir testing example to CONTRIBUTING.rst guide + + + +Trivial/Internal Changes +------------------------ + +- `#2293 `_: The internal ``MarkerError`` exception has been removed. + + +- `#3988 `_: Port the implementation of tmpdir to pathlib. + + +- `#4063 `_: Exclude 0.00 second entries from ``--duration`` output unless ``-vv`` is passed on the command-line. + + +- `#4093 `_: Fixed formatting of string literals in internal tests. + + pytest 3.8.2 (2018-10-02) ========================= diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index c005c2fb2..d3202f7c8 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -280,6 +280,47 @@ Here is a simple overview, with pytest-specific bits: base: features # if it's a feature +Writing Tests +---------------------------- + +Writing tests for plugins or for pytest itself is often done using the `testdir fixture `_, as a "black-box" test. + +For example, to ensure a simple test passes you can write: + +.. code-block:: python + + def test_true_assertion(testdir): + testdir.makepyfile( + """ + def test_foo(): + assert True + """ + ) + result = testdir.runpytest() + result.assert_outcomes(failed=0, passed=1) + + +Alternatively, it is possible to make checks based on the actual output of the termal using +*glob-like* expressions: + +.. code-block:: python + + def test_true_assertion(testdir): + testdir.makepyfile( + """ + def test_foo(): + assert False + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*assert False*", "*1 failed*"]) + +When choosing a file where to write a new test, take a look at the existing files and see if there's +one file which looks like a good fit. For example, a regression test about a bug in the ``--lf`` option +should go into ``test_cacheprovider.py``, given that this option is implemented in ``cacheprovider.py``. +If in doubt, go ahead and open a PR with your best guess and we can discuss this over the code. + + Joining the Development Team ---------------------------- diff --git a/appveyor.yml b/appveyor.yml index efc880638..abe431984 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,27 +1,29 @@ environment: matrix: - - TOXENV: "linting,docs,doctesting" - PYTEST_NO_COVERAGE: "1" - TOXENV: "py27" - - TOXENV: "py34" - - TOXENV: "py35" - - TOXENV: "py36" - TOXENV: "py37" + PYTEST_NO_COVERAGE: "1" + - TOXENV: "linting,docs,doctesting" + - TOXENV: "py36" + - TOXENV: "py35" + - TOXENV: "py34" - TOXENV: "pypy" PYTEST_NO_COVERAGE: "1" - - TOXENV: "py27-xdist" - - TOXENV: "py27-trial" - - TOXENV: "py27-numpy" + # Specialized factors for py27. + - TOXENV: "py27-trial,py27-numpy,py27-nobyte" - TOXENV: "py27-pluggymaster" PYTEST_NO_COVERAGE: "1" - - TOXENV: "py36-xdist" - - TOXENV: "py36-trial" - - TOXENV: "py36-numpy" + - TOXENV: "py27-xdist" + # Specialized factors for py36. + - TOXENV: "py36-trial,py36-numpy" - TOXENV: "py36-pluggymaster" PYTEST_NO_COVERAGE: "1" - - TOXENV: "py27-nobyte" - TOXENV: "py36-freeze" PYTEST_NO_COVERAGE: "1" + - TOXENV: "py36-xdist" + +matrix: + fast_finish: true install: - echo Installed Pythons diff --git a/changelog/2293.feature.rst b/changelog/2293.feature.rst deleted file mode 100644 index 5e56ba321..000000000 --- a/changelog/2293.feature.rst +++ /dev/null @@ -1,4 +0,0 @@ -Improve usage errors messages by hiding internal details which can be distracting and noisy. - -This has the side effect that some error conditions that previously raised generic errors (such as -``ValueError`` for unregistered marks) are now raising ``Failed`` exceptions. diff --git a/changelog/2293.trivial.rst b/changelog/2293.trivial.rst deleted file mode 100644 index a11245127..000000000 --- a/changelog/2293.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -The internal ``MarkerError`` exception has been removed. diff --git a/changelog/2535.bugfix.rst b/changelog/2535.bugfix.rst deleted file mode 100644 index ec16e81ea..000000000 --- a/changelog/2535.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Improve error message when test functions of ``unittest.TestCase`` subclasses use a parametrized fixture. diff --git a/changelog/3057.bugfix.rst b/changelog/3057.bugfix.rst deleted file mode 100644 index 8cc22f278..000000000 --- a/changelog/3057.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -``request.fixturenames`` now correctly returns the name of fixtures created by ``request.getfixturevalue()``. diff --git a/changelog/3332.feature.rst b/changelog/3332.feature.rst deleted file mode 100644 index e0110c451..000000000 --- a/changelog/3332.feature.rst +++ /dev/null @@ -1,4 +0,0 @@ -Improve the error displayed when a ``conftest.py`` file could not be imported. - -In order to implement this, a new ``chain`` parameter was added to ``ExceptionInfo.getrepr`` -to show or hide chained tracebacks in Python 3 (defaults to ``True``). diff --git a/changelog/3533.bugfix.rst b/changelog/3533.bugfix.rst new file mode 100644 index 000000000..89f136458 --- /dev/null +++ b/changelog/3533.bugfix.rst @@ -0,0 +1 @@ +Fix unescaped XML raw objects in JUnit report for skipped tests diff --git a/changelog/3616.deprecation.rst b/changelog/3616.deprecation.rst deleted file mode 100644 index 8ea1b4d3d..000000000 --- a/changelog/3616.deprecation.rst +++ /dev/null @@ -1,22 +0,0 @@ -The following accesses have been documented as deprecated for years, but are now actually emitting deprecation warnings. - -* Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances. Now - users will this warning:: - - usage of Function.Module is deprecated, please use pytest.Module instead - - Users should just ``import pytest`` and access those objects using the ``pytest`` module. - -* ``request.cached_setup``, this was the precursor of the setup/teardown mechanism available to fixtures. You can - consult `funcarg comparision section in the docs `_. - -* Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector`` - subclasses has been deprecated. Users instead should use ``pytest_collect_make_item`` to customize node types during - collection. - - This issue should affect only advanced plugins who create new collection types, so if you see this warning - message please contact the authors so they can change the code. - -* The warning that produces the message below has changed to ``RemovedInPytest4Warning``:: - - getfuncargvalue is deprecated, use getfixturevalue diff --git a/changelog/3713.doc.rst b/changelog/3713.doc.rst deleted file mode 100644 index c1d6b51b8..000000000 --- a/changelog/3713.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Update usefixtures documentation to clarify that it can't be used with fixture functions. diff --git a/changelog/3849.feature.rst b/changelog/3849.feature.rst deleted file mode 100644 index 26cbfe7b4..000000000 --- a/changelog/3849.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Add ``empty_parameter_set_mark=fail_at_collect`` ini option for raising an exception when parametrize collects an empty set. diff --git a/changelog/3946.bugfix.rst b/changelog/3946.bugfix.rst deleted file mode 100644 index 1b5248b4c..000000000 --- a/changelog/3946.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Warning filters passed as command line options using ``-W`` now take precedence over filters defined in ``ini`` -configuration files. diff --git a/changelog/3964.feature.rst b/changelog/3964.feature.rst deleted file mode 100644 index 37788c6f7..000000000 --- a/changelog/3964.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -Log messages generated in the collection phase are shown when -live-logging is enabled and/or when they are logged to a file. diff --git a/changelog/3985.feature.rst b/changelog/3985.feature.rst deleted file mode 100644 index 19070cad0..000000000 --- a/changelog/3985.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Introduce ``tmp_path`` as a fixture providing a Path object. diff --git a/changelog/3988.deprecation.rst b/changelog/3988.deprecation.rst deleted file mode 100644 index b731112e4..000000000 --- a/changelog/3988.deprecation.rst +++ /dev/null @@ -1 +0,0 @@ -Add a Deprecation warning for pytest.ensuretemp as it was deprecated since a while. diff --git a/changelog/3988.trivial.rst b/changelog/3988.trivial.rst deleted file mode 100644 index 876db9798..000000000 --- a/changelog/3988.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Port the implementation of tmpdir to pathlib. diff --git a/changelog/4013.feature.rst b/changelog/4013.feature.rst deleted file mode 100644 index 84c3ab79d..000000000 --- a/changelog/4013.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -Deprecation warnings are now shown even if you customize the warnings filters yourself. In the previous version -any customization would override pytest's filters and deprecation warnings would fall back to being hidden by default. diff --git a/changelog/4058.doc.rst b/changelog/4058.doc.rst deleted file mode 100644 index 51d568f54..000000000 --- a/changelog/4058.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Update fixture documentation to specify that a fixture can be invoked twice in the scope it's defined for. diff --git a/changelog/4064.doc.rst b/changelog/4064.doc.rst deleted file mode 100644 index 7b34fe43e..000000000 --- a/changelog/4064.doc.rst +++ /dev/null @@ -1 +0,0 @@ -According to unittest.rst, setUpModule and tearDownModule were not implemented, but it turns out they are. So updated the documentation for unittest. diff --git a/changelog/4066.bugfix.rst b/changelog/4066.bugfix.rst deleted file mode 100644 index 64980d6e8..000000000 --- a/changelog/4066.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix source reindenting by using ``textwrap.dedent`` directly. diff --git a/changelog/4073.feature.rst b/changelog/4073.feature.rst deleted file mode 100644 index 5b0ed5927..000000000 --- a/changelog/4073.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Allow specification of timeout for ``Testdir.runpytest_subprocess()`` and ``Testdir.run()``. diff --git a/changelog/4093.trivial.rst b/changelog/4093.trivial.rst deleted file mode 100644 index cbfbeb00d..000000000 --- a/changelog/4093.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed formatting of string literals in internal tests. diff --git a/changelog/4098.feature.rst b/changelog/4098.feature.rst deleted file mode 100644 index 1a53de759..000000000 --- a/changelog/4098.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Add returncode argument to pytest.exit() to exit pytest with a specific return code. diff --git a/changelog/4102.bugfix.rst b/changelog/4102.bugfix.rst deleted file mode 100644 index dd066c38d..000000000 --- a/changelog/4102.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -``pytest.warn`` will capture previously-warned warnings in Python 2. Previously they were never raised. diff --git a/changelog/4102.feature.rst b/changelog/4102.feature.rst deleted file mode 100644 index ee43ddc24..000000000 --- a/changelog/4102.feature.rst +++ /dev/null @@ -1,4 +0,0 @@ -Reimplement ``pytest.deprecated_call`` using ``pytest.warns`` so it supports the ``match='...'`` keyword argument. - -This has the side effect that ``pytest.deprecated_call`` now raises ``pytest.fail.Exception`` instead -of ``AssertionError``. diff --git a/changelog/4108.bugfix.rst b/changelog/4108.bugfix.rst deleted file mode 100644 index d136d5996..000000000 --- a/changelog/4108.bugfix.rst +++ /dev/null @@ -1,5 +0,0 @@ -Resolve symbolic links for args. - -This fixes running ``pytest tests/test_foo.py::test_bar``, where ``tests`` -is a symlink to ``project/app/tests``: -previously ``project/app/conftest.py`` would be ignored for fixtures then. diff --git a/changelog/4135.bugfix.rst b/changelog/4135.bugfix.rst deleted file mode 100644 index d4f8a851d..000000000 --- a/changelog/4135.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -pathlib based tmpdir cleanup now correctly handles symlinks in the folder. diff --git a/changelog/4149.feature.rst b/changelog/4149.feature.rst deleted file mode 100644 index 7f9908b15..000000000 --- a/changelog/4149.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Require setuptools>=30.3 and move most of the metadata to ``setup.cfg``. diff --git a/changelog/4177.bugfix.rst b/changelog/4177.bugfix.rst new file mode 100644 index 000000000..b26ad4bad --- /dev/null +++ b/changelog/4177.bugfix.rst @@ -0,0 +1 @@ +Pin ``setuptools>=40.0`` to support ``py_modules`` in ``setup.cfg`` diff --git a/changelog/4179.bugfix.rst b/changelog/4179.bugfix.rst new file mode 100644 index 000000000..6f7467f50 --- /dev/null +++ b/changelog/4179.bugfix.rst @@ -0,0 +1 @@ +Restore the tmpdir behaviour of symlinking the current test run. diff --git a/changelog/4188.feature.rst b/changelog/4188.feature.rst new file mode 100644 index 000000000..d3169efc0 --- /dev/null +++ b/changelog/4188.feature.rst @@ -0,0 +1 @@ +Make ``--color`` emit colorful dots when not running in verbose mode. Earlier, it would only colorize the test-by-test output if ``--verbose`` was also passed. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index e6c712120..a692eee15 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,8 @@ Release announcements :maxdepth: 2 + release-3.9.1 + release-3.9.0 release-3.8.2 release-3.8.1 release-3.8.0 diff --git a/doc/en/announce/release-3.9.0.rst b/doc/en/announce/release-3.9.0.rst new file mode 100644 index 000000000..14cfbe903 --- /dev/null +++ b/doc/en/announce/release-3.9.0.rst @@ -0,0 +1,43 @@ +pytest-3.9.0 +======================================= + +The pytest team is proud to announce the 3.9.0 release! + +pytest is a mature Python testing tool with more than a 2000 tests +against itself, passing on many different interpreters and platforms. + +This release contains a number of bugs fixes and improvements, so users are encouraged +to take a look at the CHANGELOG: + + https://docs.pytest.org/en/latest/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/latest/ + +As usual, you can upgrade from pypi via: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + +* Andrea Cimatoribus +* Ankit Goel +* Anthony Sottile +* Ben Eyal +* Bruno Oliveira +* Daniel Hahler +* Jeffrey Rackauckas +* Jose Carlos Menezes +* Kyle Altendorf +* Niklas JQ +* Palash Chatterjee +* Ronny Pfannschmidt +* Thomas Hess +* Thomas Hisch +* Tomer Keren +* Victor Maryama + + +Happy testing, +The Pytest Development Team diff --git a/doc/en/announce/release-3.9.1.rst b/doc/en/announce/release-3.9.1.rst new file mode 100644 index 000000000..f050e4653 --- /dev/null +++ b/doc/en/announce/release-3.9.1.rst @@ -0,0 +1,20 @@ +pytest-3.9.1 +======================================= + +pytest 3.9.1 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Bruno Oliveira +* Ronny Pfannschmidt +* Thomas Hisch + + +Happy testing, +The pytest Development Team diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index e52151a1b..f921b5d64 100644 --- a/doc/en/builtin.rst +++ b/doc/en/builtin.rst @@ -104,7 +104,9 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a See http://docs.python.org/library/warnings.html for information on warning categories. tmpdir_factory - Return a TempdirFactory instance for the test session. + Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session. + tmp_path_factory + Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session. tmpdir Return a temporary directory path object which is unique to each test function invocation, @@ -113,6 +115,16 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a path object. .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html + tmp_path + 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 :class:`pathlib.Path` + object. + + .. note:: + + in python < 3.6 this is a pathlib2.Path no tests ran in 0.12 seconds diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 268786d58..3398c92a2 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -56,7 +56,7 @@ This should be updated to make use of standard fixture mechanisms: session.close() -You can consult `funcarg comparision section in the docs `_ for +You can consult `funcarg comparison section in the docs `_ for more information. This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings. @@ -68,7 +68,7 @@ Using ``Class`` in custom Collectors .. deprecated:: 3.9 Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector`` -subclasses has been deprecated. Users instead should use ``pytest_collect_make_item`` to customize node types during +subclasses has been deprecated. Users instead should use ``pytest_pycollect_makeitem`` to customize node types during collection. This issue should affect only advanced plugins who create new collection types, so if you see this warning @@ -304,7 +304,7 @@ This form of test function doesn't support fixtures properly, and users should s .. code-block:: python @pytest.mark.parametrize("x, y", [(2, 4), (3, 9)]) - def test_squared(): + def test_squared(x, y): assert x ** x == y diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 7ec27d547..df83ec97e 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -574,7 +574,7 @@ We can run this:: file $REGENDOC_TMPDIR/b/test_error.py, line 1 def test_root(db): # no db here, will error out E fixture 'db' not found - > available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, record_xml_property, recwarn, tmpdir, tmpdir_factory + > available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, record_xml_property, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory > use 'pytest --fixtures [testpath]' for help on them. $REGENDOC_TMPDIR/b/test_error.py:1 diff --git a/doc/en/tmpdir.rst b/doc/en/tmpdir.rst index 728621152..d8cd8b705 100644 --- a/doc/en/tmpdir.rst +++ b/doc/en/tmpdir.rst @@ -31,16 +31,37 @@ created in the `base temporary directory`_. p = d / "hello.txt" p.write_text(CONTENT) assert p.read_text() == CONTENT - assert len(tmpdir.listdir()) == 1 + assert len(list(tmp_path.iterdir())) == 1 assert 0 Running this would result in a passed test except for the last ``assert 0`` line which we use to look at values:: $ pytest test_tmp_path.py - ... #fill fom regendoc + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + rootdir: $REGENDOC_TMPDIR, inifile: + collected 1 item + test_tmp_path.py F [100%] + ================================= FAILURES ================================= + _____________________________ test_create_file _____________________________ + + tmp_path = PosixPath('PYTEST_TMPDIR/test_create_file0') + + def test_create_file(tmp_path): + d = tmp_path / "sub" + d.mkdir() + p = d / "hello.txt" + p.write_text(CONTENT) + assert p.read_text() == CONTENT + assert len(list(tmp_path.iterdir())) == 1 + > assert 0 + E assert 0 + + test_tmp_path.py:13: AssertionError + ========================= 1 failed in 0.12 seconds ========================= The ``tmp_path_factory`` fixture -------------------------------- diff --git a/doc/en/usage.rst b/doc/en/usage.rst index e5521bba5..cc118fad8 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -269,6 +269,7 @@ To get a list of the slowest 10 test durations:: pytest --durations=10 +By default, pytest will not show test durations that are too small (<0.01s) unless ``-vv`` is passed on the command-line. Creating JUnitXML format files ---------------------------------------------------- diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index 060057d91..f83377cf6 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -75,60 +75,6 @@ Both ``-W`` command-line option and ``filterwarnings`` ini option are based on P `-W option`_ and `warnings.simplefilter`_, so please refer to those sections in the Python documentation for other examples and advanced usage. -Disabling warning summary -------------------------- - -Although not recommended, you can use the ``--disable-warnings`` command-line option to suppress the -warning summary entirely from the test run output. - -Disabling warning capture entirely ----------------------------------- - -This plugin is enabled by default but can be disabled entirely in your ``pytest.ini`` file with: - - .. code-block:: ini - - [pytest] - addopts = -p no:warnings - -Or passing ``-p no:warnings`` in the command-line. This might be useful if your test suites handles warnings -using an external system. - - -.. _`deprecation-warnings`: - -DeprecationWarning and PendingDeprecationWarning ------------------------------------------------- - -.. versionadded:: 3.8 -.. versionchanged:: 3.9 - -By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning``. - -Sometimes it is useful to hide some specific deprecation warnings that happen in code that you have no control over -(such as third-party libraries), in which case you might use the standard warning filters options (ini or marks). -For example: - -.. code-block:: ini - - [pytest] - filterwarnings = - ignore:.*U.*mode is deprecated:DeprecationWarning - - -.. note:: - If warnings are configured at the interpreter level, using - the `PYTHONWARNINGS `_ environment variable or the - ``-W`` command-line option, pytest will not configure any filters by default. - -.. note:: - This feature makes pytest more compliant with `PEP-0506 `_ which suggests that those warnings should - be shown by default by test runners, but pytest doesn't follow ``PEP-0506`` completely because resetting all - warning filters like suggested in the PEP will break existing test suites that configure warning filters themselves - by calling ``warnings.simplefilter`` (see issue `#2430 `_ - for an example of that). - - .. _`filterwarnings`: ``@pytest.mark.filterwarnings`` @@ -167,24 +113,6 @@ decorator or to all tests in a module by setting the ``pytestmark`` variable: pytestmark = pytest.mark.filterwarnings("error") -.. note:: - - Except for these features, pytest does not change the python warning filter; it only captures - and displays the warnings which are issued with respect to the currently configured filter, - including changes to the filter made by test functions or by the system under test. - -.. note:: - - ``DeprecationWarning`` and ``PendingDeprecationWarning`` are hidden by the standard library - by default so you have to explicitly configure them to be displayed in your ``pytest.ini``: - - .. code-block:: ini - - [pytest] - filterwarnings = - once::DeprecationWarning - once::PendingDeprecationWarning - *Credits go to Florian Schulze for the reference implementation in the* `pytest-warnings`_ *plugin.* @@ -193,6 +121,102 @@ decorator or to all tests in a module by setting the ``pytestmark`` variable: .. _warnings.simplefilter: https://docs.python.org/3/library/warnings.html#warnings.simplefilter .. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings +Disabling warnings summary +-------------------------- + +Although not recommended, you can use the ``--disable-warnings`` command-line option to suppress the +warning summary entirely from the test run output. + +Disabling warning capture entirely +---------------------------------- + +This plugin is enabled by default but can be disabled entirely in your ``pytest.ini`` file with: + + .. code-block:: ini + + [pytest] + addopts = -p no:warnings + +Or passing ``-p no:warnings`` in the command-line. This might be useful if your test suites handles warnings +using an external system. + + +.. _`deprecation-warnings`: + +DeprecationWarning and PendingDeprecationWarning +------------------------------------------------ + +.. versionadded:: 3.8 +.. versionchanged:: 3.9 + +By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning`` warnings from +user code and third-party libraries, as recommended by `PEP-0506 `_. +This helps users keep their code modern and avoid breakages when deprecated warnings are effectively removed. + +Sometimes it is useful to hide some specific deprecation warnings that happen in code that you have no control over +(such as third-party libraries), in which case you might use the warning filters options (ini or marks) to ignore +those warnings. + +For example: + +.. code-block:: ini + + [pytest] + filterwarnings = + ignore:.*U.*mode is deprecated:DeprecationWarning + + +This will ignore all warnings of type ``DeprecationWarning`` where the start of the message matches +the regular expression ``".*U.*mode is deprecated"``. + +.. note:: + If warnings are configured at the interpreter level, using + the `PYTHONWARNINGS `_ environment variable or the + ``-W`` command-line option, pytest will not configure any filters by default. + + Also pytest doesn't follow ``PEP-0506`` suggestion of resetting all warning filters because + it might break test suites that configure warning filters themselves + by calling ``warnings.simplefilter`` (see issue `#2430 `_ + for an example of that). + + +.. _`ensuring a function triggers a deprecation warning`: + +.. _ensuring_function_triggers: + +Ensuring code triggers a deprecation warning +-------------------------------------------- + +You can also call a global helper for checking +that a certain function call triggers a ``DeprecationWarning`` or +``PendingDeprecationWarning``:: + + import pytest + + def test_global(): + pytest.deprecated_call(myfunction, 17) + +By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be +caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide +them. If you wish to record them in your own code, use the +command ``warnings.simplefilter('always')``:: + + import warnings + import pytest + + def test_deprecation(recwarn): + warnings.simplefilter('always') + warnings.warn("deprecated", DeprecationWarning) + assert len(recwarn) == 1 + assert recwarn.pop(DeprecationWarning) + +You can also use it as a contextmanager:: + + def test_global(): + with pytest.deprecated_call(): + myobject.deprecated_method() + + .. _`asserting warnings`: @@ -299,43 +323,6 @@ warnings, or index into it to get a particular recorded warning. Full API: :class:`WarningsRecorder`. -.. _`ensuring a function triggers a deprecation warning`: - -.. _ensuring_function_triggers: - -Ensuring a function triggers a deprecation warning -------------------------------------------------------- - -You can also call a global helper for checking -that a certain function call triggers a ``DeprecationWarning`` or -``PendingDeprecationWarning``:: - - import pytest - - def test_global(): - pytest.deprecated_call(myfunction, 17) - -By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be -caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide -them. If you wish to record them in your own code, use the -command ``warnings.simplefilter('always')``:: - - import warnings - import pytest - - def test_deprecation(recwarn): - warnings.simplefilter('always') - warnings.warn("deprecated", DeprecationWarning) - assert len(recwarn) == 1 - assert recwarn.pop(DeprecationWarning) - -You can also use it as a contextmanager:: - - def test_global(): - with pytest.deprecated_call(): - myobject.deprecated_method() - - .. _internal-warnings: diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 70e48f817..2cb1caefb 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -420,9 +420,21 @@ additionally it is possible to copy examples for a example folder before running ============================= warnings summary ============================= $REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time testdir.copy_example("test_example.py") + $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Class is deprecated, please use pytest.Class instead + return getattr(object, name, default) + $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.File is deprecated, please use pytest.File instead + return getattr(object, name, default) + $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Function is deprecated, please use pytest.Function instead + return getattr(object, name, default) + $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Instance is deprecated, please use pytest.Instance instead + return getattr(object, name, default) + $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Item is deprecated, please use pytest.Item instead + return getattr(object, name, default) + $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Module is deprecated, please use pytest.Module instead + return getattr(object, name, default) -- Docs: https://docs.pytest.org/en/latest/warnings.html - =================== 2 passed, 1 warnings in 0.12 seconds =================== + =================== 2 passed, 7 warnings in 0.12 seconds =================== For more information about the result object that ``runpytest()`` returns, and the methods that it provides please check out the :py:class:`RunResult diff --git a/pyproject.toml b/pyproject.toml index c83bd853d..d17b936c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [build-system] requires = [ # sync with setup.py until we discard non-pep-517/518 - "setuptools>=30.3", + "setuptools>=40.0", "setuptools-scm", "wheel", ] diff --git a/setup.cfg b/setup.cfg index a898b66c6..8cd3858fd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ name = pytest description = pytest: simple powerful testing with Python long_description = file: README.rst -url = "https://docs.pytest.org/en/latest/" +url = https://docs.pytest.org/en/latest/ project_urls = Source=https://github.com/pytest-dev/pytest Tracker=https://github.com/pytest-dev/pytest/issues diff --git a/setup.py b/setup.py index 6bab5312d..ac0739455 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ if "_PYTEST_SETUP_SKIP_PLUGGY_DEP" not in os.environ: def main(): setup( use_scm_version={"write_to": "src/_pytest/_version.py"}, - setup_requires=["setuptools-scm", "setuptools>=30.3"], + setup_requires=["setuptools-scm", "setuptools>=40.0"], package_dir={"": "src"}, install_requires=INSTALL_REQUIRES, ) diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 88331dd4b..5e76563d9 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -399,7 +399,7 @@ def _rewrite_test(config, fn): finally: del state._indecode try: - tree = ast.parse(source) + tree = ast.parse(source, filename=fn.strpath) except SyntaxError: # Let this pop up again in the real import. state.trace("failed to parse: %r" % (fn,)) diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index f51dff373..cc9bf5c2a 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -1,10 +1,12 @@ """ interactive debugging with PDB, the Python Debugger. """ from __future__ import absolute_import, division, print_function + +import os import pdb import sys -import os from doctest import UnexpectedException +from _pytest import outcomes from _pytest.config import hookimpl try: @@ -109,9 +111,6 @@ class PdbInvoke(object): _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) @@ -164,8 +163,9 @@ def _enter_pdb(node, excinfo, rep): rep.toterminal(tw) tw.sep(">", "entering PDB") tb = _postmortem_traceback(excinfo) - post_mortem(tb) rep._pdbshown = True + if post_mortem(tb): + outcomes.exit("Quitting debugger") return rep @@ -196,3 +196,4 @@ def post_mortem(t): p = Pdb() p.reset() p.interaction(None, t) + return p.quitting diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 1829cf411..29eda351f 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1020,7 +1020,7 @@ class FixtureFunctionMarker(object): def __call__(self, function): if isclass(function): - raise ValueError("class fixtures not supported (may be in the future)") + raise ValueError("class fixtures not supported (maybe in the future)") if getattr(function, "_pytestfixturefunction", False): raise ValueError( @@ -1371,8 +1371,7 @@ class FixtureManager(object): fixturedefs = self._arg2fixturedefs[argname] except KeyError: return None - else: - return tuple(self._matchfactories(fixturedefs, nodeid)) + return tuple(self._matchfactories(fixturedefs, nodeid)) def _matchfactories(self, fixturedefs, nodeid): for fixturedef in fixturedefs: diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index ac00c772a..a39c94c13 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -221,12 +221,14 @@ class _NodeReporter(object): else: filename, lineno, skipreason = report.longrepr if skipreason.startswith("Skipped: "): - skipreason = bin_xml_escape(skipreason[9:]) + skipreason = skipreason[9:] + details = "%s:%s: %s" % (filename, lineno, skipreason) + self.append( Junit.skipped( - "%s:%s: %s" % (filename, lineno, skipreason), + bin_xml_escape(details), type="pytest.skip", - message=skipreason, + message=bin_xml_escape(skipreason), ) ) self.write_captured_output(report) diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index fe6711ac7..ee6795250 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -279,7 +279,7 @@ class LogCaptureFixture(object): Unlike 'records', which contains the format string and parameters for interpolation, log messages in this list are all interpolated. Unlike 'text', which contains the output from the handler, log messages in this list are unadorned with - levels, timestamps, etc, making exact comparisions more reliable. + levels, timestamps, etc, making exact comparisons more reliable. Note that traceback or stack info (from :func:`logging.exception` or the `exc_info` or `stack_info` arguments to the logging functions) is not included, as this is added by the formatter in the handler. diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 2d6cea668..8d4176aea 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -570,9 +570,7 @@ class Session(nodes.FSCollector): return True def _tryconvertpyarg(self, x): - """Convert a dotted module name to path. - - """ + """Convert a dotted module name to path.""" try: with _patched_find_module(): loader = pkgutil.find_loader(x) @@ -604,8 +602,7 @@ class Session(nodes.FSCollector): raise UsageError( "file or package not found: " + arg + " (missing __init__.py?)" ) - else: - raise UsageError("file not found: " + arg) + raise UsageError("file not found: " + arg) parts[0] = path return parts diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index cda5e9947..081fce904 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -100,6 +100,26 @@ else: _max = max +def _force_symlink(root, target, link_to): + """helper to create the current symlink + + its full of race conditions that are reasonably ok to ignore + for the contex of best effort linking to the latest testrun + + the presumption being thatin case of much parallelism + the inaccuracy is going to be acceptable + """ + current_symlink = root.joinpath(target) + try: + current_symlink.unlink() + except OSError: + pass + try: + current_symlink.symlink_to(link_to) + except Exception: + pass + + def make_numbered_dir(root, prefix): """create a directory with a increased number as suffix for the given prefix""" for i in range(10): @@ -112,6 +132,7 @@ def make_numbered_dir(root, prefix): except Exception: pass else: + _force_symlink(root, prefix + "current", new_path) return new_path else: raise EnvironmentError( diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 956e00087..8782a30ba 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -17,7 +17,7 @@ from weakref import WeakKeyDictionary from _pytest.capture import MultiCapture, SysCapture from _pytest._code import Source -from _pytest.main import Session, EXIT_OK +from _pytest.main import Session, EXIT_INTERRUPTED, EXIT_OK from _pytest.assertion.rewrite import AssertionRewritingHook from _pytest.pathlib import Path from _pytest.compat import safe_str @@ -857,7 +857,7 @@ class Testdir(object): # 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"): + if ret == EXIT_INTERRUPTED and not kwargs.get("no_reraise_ctrlc"): calls = reprec.getcalls("pytest_keyboard_interrupt") if calls and calls[-1].excinfo.type == KeyboardInterrupt: raise KeyboardInterrupt() diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 1ba9ff310..057317993 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -30,6 +30,7 @@ def pytest_addoption(parser): def pytest_terminal_summary(terminalreporter): durations = terminalreporter.config.option.durations + verbose = terminalreporter.config.getvalue("verbose") if durations is None: return tr = terminalreporter @@ -49,6 +50,10 @@ def pytest_terminal_summary(terminalreporter): dlist = dlist[:durations] for rep in dlist: + if verbose < 2 and rep.duration < 0.005: + tr.write_line("") + tr.write_line("(0.00 durations hidden. Use -vv to show these durations.)") + break nodeid = rep.nodeid.replace("::()::", "::") tr.write_line("%02.2fs %-8s %s" % (rep.duration, rep.when, nodeid)) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index f4dbbe61a..d207dd785 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -263,7 +263,7 @@ class TerminalReporter(object): char = {"xfailed": "x", "skipped": "s"}.get(char, char) return char in self.reportchars - def write_fspath_result(self, nodeid, res): + def write_fspath_result(self, nodeid, res, **markup): fspath = self.config.rootdir.join(nodeid.split("::")[0]) if fspath != self.currentfspath: if self.currentfspath is not None and self._show_progress_info: @@ -272,7 +272,7 @@ class TerminalReporter(object): fspath = self.startdir.bestrelpath(fspath) self._tw.line() self._tw.write(fspath + " ") - self._tw.write(res) + self._tw.write(res, **markup) def write_ensure_prefix(self, prefix, extra="", **kwargs): if self.currentfspath != prefix: @@ -386,22 +386,22 @@ class TerminalReporter(object): # probably passed setup/teardown return running_xdist = hasattr(rep, "node") + if markup is None: + if rep.passed: + markup = {"green": True} + elif rep.failed: + markup = {"red": True} + elif rep.skipped: + markup = {"yellow": True} + else: + markup = {} if self.verbosity <= 0: if not running_xdist and self.showfspath: - self.write_fspath_result(rep.nodeid, letter) + self.write_fspath_result(rep.nodeid, letter, **markup) else: - self._tw.write(letter) + self._tw.write(letter, **markup) else: self._progress_nodeids_reported.add(rep.nodeid) - if markup is None: - if rep.passed: - markup = {"green": True} - elif rep.failed: - markup = {"red": True} - elif rep.skipped: - markup = {"yellow": True} - else: - markup = {} line = self._locationline(rep.nodeid, *rep.location) if not running_xdist: self.write_ensure_prefix(line, word, **markup) @@ -676,7 +676,9 @@ class TerminalReporter(object): if fspath: res = mkrel(nodeid).replace("::()", "") # parens-normalization - if nodeid.split("::")[0] != fspath.replace("\\", nodes.SEP): + if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace( + "\\", nodes.SEP + ): res += " <- " + self.startdir.bestrelpath(fspath) else: res = "[location]" diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 2a6c75740..0a7eb2e03 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -12,6 +12,13 @@ import pytest from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR +def prepend_pythonpath(*dirs): + cur = os.getenv("PYTHONPATH") + if cur: + dirs += (cur,) + return os.pathsep.join(str(p) for p in dirs) + + class TestGeneralUsage(object): def test_config_error(self, testdir): testdir.copy_example("conftest_usageerror/conftest.py") @@ -590,14 +597,8 @@ class TestInvocationVariants(object): assert result.ret == 0 result.stdout.fnmatch_lines(["*1 passed*"]) - def join_pythonpath(what): - cur = os.environ.get("PYTHONPATH") - if cur: - return str(what) + os.pathsep + cur - return what - empty_package = testdir.mkpydir("empty_package") - monkeypatch.setenv("PYTHONPATH", str(join_pythonpath(empty_package))) + monkeypatch.setenv("PYTHONPATH", str(empty_package), prepend=os.pathsep) # the path which is not a package raises a warning on pypy; # no idea why only pypy and not normal python warn about it here with warnings.catch_warnings(): @@ -606,7 +607,7 @@ class TestInvocationVariants(object): assert result.ret == 0 result.stdout.fnmatch_lines(["*2 passed*"]) - monkeypatch.setenv("PYTHONPATH", str(join_pythonpath(testdir))) + monkeypatch.setenv("PYTHONPATH", str(testdir), prepend=os.pathsep) result = testdir.runpytest("--pyargs", "tpkg.test_missing", syspathinsert=True) assert result.ret != 0 result.stderr.fnmatch_lines(["*not*found*test_missing*"]) @@ -646,18 +647,13 @@ class TestInvocationVariants(object): # ├── __init__.py # └── test_world.py - def join_pythonpath(*dirs): - cur = os.environ.get("PYTHONPATH") - if cur: - dirs += (cur,) - return os.pathsep.join(str(p) for p in dirs) - - monkeypatch.setenv("PYTHONPATH", join_pythonpath(*search_path)) + # NOTE: the different/reversed ordering is intentional here. + monkeypatch.setenv("PYTHONPATH", prepend_pythonpath(*search_path)) for p in search_path: monkeypatch.syspath_prepend(p) # mixed module and filenames: - os.chdir("world") + monkeypatch.chdir("world") result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world") assert result.ret == 0 result.stdout.fnmatch_lines( @@ -708,8 +704,6 @@ class TestInvocationVariants(object): pytest.skip(six.text_type(e.args[0])) monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False) - search_path = ["lib", os.path.join("local", "lib")] - dirname = "lib" d = testdir.mkdir(dirname) foo = d.mkdir("foo") @@ -742,13 +736,9 @@ class TestInvocationVariants(object): # ├── conftest.py # └── test_bar.py - def join_pythonpath(*dirs): - cur = os.getenv("PYTHONPATH") - if cur: - dirs += (cur,) - return os.pathsep.join(str(p) for p in dirs) - - monkeypatch.setenv("PYTHONPATH", join_pythonpath(*search_path)) + # NOTE: the different/reversed ordering is intentional here. + search_path = ["lib", os.path.join("local", "lib")] + monkeypatch.setenv("PYTHONPATH", prepend_pythonpath(*search_path)) for p in search_path: monkeypatch.syspath_prepend(p) @@ -760,16 +750,16 @@ class TestInvocationVariants(object): if hasattr(py.path.local, "mksymlinkto"): result.stdout.fnmatch_lines( [ - "lib/foo/bar/test_bar.py::test_bar <- local/lib/foo/bar/test_bar.py PASSED*", - "lib/foo/bar/test_bar.py::test_other <- local/lib/foo/bar/test_bar.py PASSED*", + "lib/foo/bar/test_bar.py::test_bar PASSED*", + "lib/foo/bar/test_bar.py::test_other PASSED*", "*2 passed*", ] ) else: result.stdout.fnmatch_lines( [ - "local/lib/foo/bar/test_bar.py::test_bar PASSED*", - "local/lib/foo/bar/test_bar.py::test_other PASSED*", + "*lib/foo/bar/test_bar.py::test_bar PASSED*", + "*lib/foo/bar/test_bar.py::test_other PASSED*", "*2 passed*", ] ) @@ -846,7 +836,10 @@ class TestDurations(object): result = testdir.runpytest("--durations=10") assert result.ret == 0 result.stdout.fnmatch_lines_random( - ["*durations*", "*call*test_3*", "*call*test_2*", "*call*test_1*"] + ["*durations*", "*call*test_3*", "*call*test_2*"] + ) + result.stdout.fnmatch_lines( + ["(0.00 durations hidden. Use -vv to show these durations.)"] ) def test_calls_show_2(self, testdir): @@ -860,6 +853,18 @@ class TestDurations(object): testdir.makepyfile(self.source) result = testdir.runpytest("--durations=0") assert result.ret == 0 + for x in "23": + for y in ("call",): # 'setup', 'call', 'teardown': + for line in result.stdout.lines: + if ("test_%s" % x) in line and y in line: + break + else: + raise AssertionError("not found {} {}".format(x, y)) + + def test_calls_showall_verbose(self, testdir): + testdir.makepyfile(self.source) + result = testdir.runpytest("--durations=0", "-vv") + assert result.ret == 0 for x in "123": for y in ("call",): # 'setup', 'call', 'teardown': for line in result.stdout.lines: @@ -870,9 +875,9 @@ class TestDurations(object): def test_with_deselected(self, testdir): testdir.makepyfile(self.source) - result = testdir.runpytest("--durations=2", "-k test_1") + result = testdir.runpytest("--durations=2", "-k test_2") assert result.ret == 0 - result.stdout.fnmatch_lines(["*durations*", "*call*test_1*"]) + result.stdout.fnmatch_lines(["*durations*", "*call*test_2*"]) def test_with_failing_collection(self, testdir): testdir.makepyfile(self.source) @@ -892,13 +897,15 @@ class TestDurations(object): class TestDurationWithFixture(object): source = """ + import pytest import time - frag = 0.001 - def setup_function(func): - time.sleep(frag * 3) - def test_1(): - time.sleep(frag*2) - def test_2(): + frag = 0.01 + + @pytest.fixture + def setup_fixt(): + time.sleep(frag) + + def test_1(setup_fixt): time.sleep(frag) """ diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 97be7ec02..f21f7a861 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -494,6 +494,12 @@ class TestRequestBasic(object): reason="this method of test doesn't work on pypy", ) def test_request_garbage(self, testdir): + try: + import xdist # noqa + except ImportError: + pass + else: + pytest.xfail("this test is flaky when executed with xdist") testdir.makepyfile( """ import sys diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 29d3f7f6e..079b01f32 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1222,3 +1222,19 @@ def test_set_suite_name(testdir, suite_name): assert result.ret == 0 node = dom.find_first_by_tag("testsuite") node.assert_attr(name=expected) + + +def test_escaped_skipreason_issue3533(testdir): + testdir.makepyfile( + """ + import pytest + @pytest.mark.skip(reason='1 <> 2') + def test_skip(): + pass + """ + ) + _, dom = runandparse(testdir) + node = dom.find_first_by_tag("testcase") + snode = node.find_first_by_tag("skipped") + assert "1 <> 2" in snode.text + snode.assert_attr(message="1 <> 2") diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 4739f0e2d..57a6cb9a3 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -25,6 +25,8 @@ def custom_pdb_calls(): # install dummy debugger class and track which methods were called on it class _CustomPdb(object): + quitting = False + def __init__(self, *args, **kwargs): called.append("init") @@ -142,6 +144,9 @@ class TestPDB(object): def test_1(): i = 0 assert i == 1 + + def test_not_called_due_to_quit(): + pass """ ) child = testdir.spawn_pytest("--pdb %s" % p1) @@ -150,8 +155,9 @@ class TestPDB(object): child.expect("Pdb") child.sendeof() rest = child.read().decode("utf8") - assert "1 failed" in rest + assert "= 1 failed in" in rest assert "def test_1" not in rest + assert "Exit: Quitting debugger" in rest self.flush(child) @staticmethod @@ -321,7 +327,7 @@ class TestPDB(object): child = testdir.spawn_pytest("--pdb %s" % p1) # child.expect(".*import pytest.*") child.expect("Pdb") - child.sendeof() + child.sendline("c") child.expect("1 error") self.flush(child) @@ -334,8 +340,20 @@ class TestPDB(object): ) p1 = testdir.makepyfile("def test_func(): pass") child = testdir.spawn_pytest("--pdb %s" % p1) - # child.expect(".*import pytest.*") child.expect("Pdb") + + # INTERNALERROR is only displayed once via terminal reporter. + assert ( + len( + [ + x + for x in child.before.decode().splitlines() + if x.startswith("INTERNALERROR> Traceback") + ] + ) + == 1 + ) + child.sendeof() self.flush(child) @@ -345,7 +363,7 @@ class TestPDB(object): import pytest def test_1(): i = 0 - print ("hello17") + print("hello17") pytest.set_trace() x = 3 """ @@ -376,6 +394,7 @@ class TestPDB(object): rest = child.read().decode("utf8") assert "1 failed" in rest assert "reading from stdin while output" not in rest + assert "BdbQuit" in rest self.flush(child) def test_pdb_and_capsys(self, testdir): @@ -383,7 +402,7 @@ class TestPDB(object): """ import pytest def test_1(capsys): - print ("hello1") + print("hello1") pytest.set_trace() """ ) @@ -420,7 +439,7 @@ class TestPDB(object): def test_1(): pdb.set_trace() def test_2(): - print ("hello") + print("hello") assert 0 """ ) @@ -461,10 +480,10 @@ class TestPDB(object): import pytest def test_1(): i = 0 - print ("hello17") + print("hello17") pytest.set_trace() x = 3 - print ("hello18") + print("hello18") pytest.set_trace() x = 4 """ @@ -518,14 +537,16 @@ class TestPDB(object): def test_pdb_collection_failure_is_shown(self, testdir): p1 = testdir.makepyfile("xxx") result = testdir.runpytest_subprocess("--pdb", p1) - result.stdout.fnmatch_lines(["*NameError*xxx*", "*1 error*"]) + result.stdout.fnmatch_lines( + ["E NameError: *xxx*", "*! *Exit: Quitting debugger !*"] # due to EOF + ) def test_enter_pdb_hook_is_called(self, testdir): testdir.makeconftest( """ def pytest_enter_pdb(config): assert config.testing_verification == 'configured' - print 'enter_pdb_hook' + print('enter_pdb_hook') def pytest_configure(config): config.testing_verification = 'configured' @@ -562,7 +583,7 @@ class TestPDB(object): custom_pdb=""" class CustomPdb(object): def set_trace(*args, **kwargs): - print 'custom set_trace>' + print('custom set_trace>') """ ) p1 = testdir.makepyfile( diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 7651f3ab3..af2dc2f00 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -154,7 +154,7 @@ class TestTerminal(object): ) result = testdir.runpytest(p2) result.stdout.fnmatch_lines(["*test_p2.py .*", "*1 passed*"]) - result = testdir.runpytest("-v", p2) + result = testdir.runpytest("-vv", p2) result.stdout.fnmatch_lines( ["*test_p2.py::TestMore::test_p1* <- *test_p1.py*PASSED*"] ) @@ -170,7 +170,7 @@ class TestTerminal(object): """ ) ) - result = testdir.runpytest("-v") + result = testdir.runpytest("-vv") assert result.ret == 0 result.stdout.fnmatch_lines(["*a123/test_hello123.py*PASS*"]) assert " <- " not in result.stdout.str() diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 9f4158eb7..3c413e7c2 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -196,6 +196,12 @@ class TestNumberedDir(object): assert d.name.startswith(self.PREFIX) assert d.name.endswith(str(i)) + symlink = tmp_path.joinpath(self.PREFIX + "current") + if symlink.exists(): + # unix + assert symlink.is_symlink() + assert symlink.resolve() == d.resolve() + def test_cleanup_lock_create(self, tmp_path): d = tmp_path.joinpath("test") d.mkdir() @@ -244,7 +250,7 @@ class TestNumberedDir(object): def test_cleanup_keep(self, tmp_path): self._do_cleanup(tmp_path) - a, b = tmp_path.iterdir() + a, b = (x for x in tmp_path.iterdir() if not x.is_symlink()) print(a, b) def test_cleanup_locked(self, tmp_path): diff --git a/tox.ini b/tox.ini index 1985942d0..86b3b9458 100644 --- a/tox.ini +++ b/tox.ini @@ -18,10 +18,10 @@ envlist = [testenv] commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof -ra {posargs:testing} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof coverage: coverage combine coverage: coverage report -passenv = USER USERNAME +passenv = USER USERNAME COVERAGE_* TRAVIS setenv = # configuration if a user runs tox with a "coverage" factor, for example "tox -e py36-coverage" coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m @@ -36,14 +36,12 @@ deps = {env:_PYTEST_TOX_EXTRA_DEP:} [testenv:py27-subprocess] -changedir = . deps = pytest-xdist>=1.13 py27: mock nose -passenv = USER USERNAME TRAVIS commands = - pytest -n auto -ra --runpytest=subprocess {posargs:testing} + pytest -n auto --runpytest=subprocess [testenv:linting] @@ -59,9 +57,8 @@ deps = nose hypothesis>=3.56 {env:_PYTEST_TOX_EXTRA_DEP:} -passenv = USER USERNAME TRAVIS commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto -ra {posargs:testing} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto [testenv:py36-xdist] # NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706. @@ -74,16 +71,14 @@ deps = commands = {[testenv:py27-xdist]commands} [testenv:py27-pexpect] -changedir = testing platform = linux|darwin deps = pexpect {env:_PYTEST_TOX_EXTRA_DEP:} commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra test_pdb.py test_terminal.py test_unittest.py + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest testing/test_pdb.py testing/test_terminal.py testing/test_unittest.py {posargs} [testenv:py36-pexpect] -changedir = {[testenv:py27-pexpect]changedir} platform = {[testenv:py27-pexpect]platform} deps = {[testenv:py27-pexpect]deps} commands = {[testenv:py27-pexpect]commands} @@ -95,20 +90,18 @@ deps = py27: mock {env:_PYTEST_TOX_EXTRA_DEP:} distribute = true -changedir=testing setenv = {[testenv]setenv} PYTHONDONTWRITEBYTECODE=1 -passenv = USER USERNAME TRAVIS commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto -ra {posargs:.} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs} [testenv:py27-trial] deps = twisted {env:_PYTEST_TOX_EXTRA_DEP:} commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra {posargs:testing/test_unittest.py} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/test_unittest.py} [testenv:py36-trial] deps = {[testenv:py27-trial]deps} @@ -119,7 +112,7 @@ deps = numpy {env:_PYTEST_TOX_EXTRA_DEP:} commands= - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra {posargs:testing/python/approx.py} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/python/approx.py} [testenv:py36-numpy] deps = {[testenv:py27-numpy]deps} @@ -154,7 +147,7 @@ deps = PyYAML {env:_PYTEST_TOX_EXTRA_DEP:} commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra doc/en + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest doc/en {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest [testenv:regen] @@ -175,7 +168,7 @@ commands = [testenv:jython] changedir = testing commands = - {envpython} {envbindir}/py.test-jython -ra {posargs} + {envpython} {envbindir}/py.test-jython {posargs} [testenv:py36-freeze] changedir = testing/freeze