diff --git a/.coveragerc b/.coveragerc index 61ff66749..a0a7a02f2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,4 +1,3 @@ [run] -omit = - # standlonetemplate is read dynamically and tested by test_genscript - *standalonetemplate.py +source = _pytest,testing +parallel = 1 diff --git a/.travis.yml b/.travis.yml index 373b79289..89874f5a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,9 @@ sudo: false language: python stages: -- linting -- test +- baseline +- name: test + if: repo = pytest-dev/pytest AND tag IS NOT present - name: deploy if: repo = pytest-dev/pytest AND tag IS present python: @@ -11,13 +12,8 @@ install: - pip install --upgrade --pre tox env: matrix: - # coveralls is not listed in tox's envlist, but should run in travis - - TOXENV=coveralls # note: please use "tox --listenvs" to populate the build matrix below # please remove the linting env in all cases - - TOXENV=py27 - - TOXENV=py34 - - TOXENV=py36 - TOXENV=py27-pexpect - TOXENV=py27-xdist - TOXENV=py27-trial @@ -30,20 +26,42 @@ env: - TOXENV=py36-pluggymaster - TOXENV=py27-nobyte - TOXENV=doctesting - - TOXENV=docs + - TOXENV=docs PYTEST_NO_COVERAGE=1 jobs: include: - - env: TOXENV=pypy + # Coverage tracking is slow with pypy, skip it. + - env: TOXENV=pypy PYTEST_NO_COVERAGE=1 python: 'pypy-5.4' - env: TOXENV=py35 python: '3.5' - - env: TOXENV=py36-freeze + - env: TOXENV=py36-freeze PYTEST_NO_COVERAGE=1 python: '3.6' - env: TOXENV=py37 python: '3.7' sudo: required dist: xenial + - &test-macos + language: generic + os: osx + osx_image: xcode9.4 + sudo: required + install: + - python -m pip install --pre tox + env: TOXENV=py27 + - <<: *test-macos + env: TOXENV=py37 + before_install: + - brew update + - brew upgrade python + - brew unlink python + - brew link python + + - stage: baseline + env: TOXENV=py27 + - env: TOXENV=py34 + - env: TOXENV=py36 + - env: TOXENV=linting PYTEST_NO_COVERAGE=1 - stage: deploy python: '3.6' @@ -60,12 +78,33 @@ jobs: on: tags: true repo: pytest-dev/pytest - - stage: linting - python: '3.6' - env: TOXENV=linting + +before_script: + - | + if [[ "$PYTEST_NO_COVERAGE" != 1 ]]; then + export _PYTEST_TOX_COVERAGE_RUN="env COVERAGE_FILE=$PWD/.coverage COVERAGE_PROCESS_START=$PWD/.coveragerc coverage run --source {envsitepackagesdir}/_pytest/,$PWD/testing -m" + export _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess + fi script: tox --recreate +after_success: + - | + if [[ "$PYTEST_NO_COVERAGE" != 1 ]]; then + set -e + pip install codecov + coverage combine + coverage xml + coverage report -m + codecov --required -X gcov pycov search -f coverage.xml --flags ${TOXENV//-/ } + + # Coveralls does not support merged reports. + if [[ "$TOXENV" = py37 ]]; then + pip install coveralls + coveralls + fi + fi + notifications: irc: channels: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0d6c9d412..78f2156e8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,31 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 3.7.4 (2018-08-29) +========================= + +Bug Fixes +--------- + +- `#3506 `_: Fix possible infinite recursion when writing ``.pyc`` files. + + +- `#3853 `_: Cache plugin now obeys the ``-q`` flag when ``--last-failed`` and ``--failed-first`` flags are used. + + +- `#3883 `_: Fix bad console output when using ``console_output_style=classic``. + + +- `#3888 `_: Fix macOS specific code using ``capturemanager`` plugin in doctests. + + + +Improved Documentation +---------------------- + +- `#3902 `_: Fix pytest.org links + + pytest 3.7.3 (2018-08-26) ========================= diff --git a/HOWTORELEASE.rst b/HOWTORELEASE.rst index 3d2d6a769..435edb550 100644 --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -28,10 +28,13 @@ taking a lot of time to make a new one. #. After all tests pass and the PR has been approved, publish to PyPI by pushing the tag:: + git tag git push git@github.com:pytest-dev/pytest.git Wait for the deploy to complete, then make sure it is `available on PyPI `_. +#. Merge the PR into ``master``. + #. Send an email announcement with the contents from:: doc/en/announce/release-.rst diff --git a/README.rst b/README.rst index 97b21898e..97ab784cd 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ -.. image:: http://docs.pytest.org/en/latest/_static/pytest1.png - :target: http://docs.pytest.org +.. image:: https://docs.pytest.org/en/latest/_static/pytest1.png + :target: https://docs.pytest.org/en/latest/ :align: center :alt: pytest @@ -66,23 +66,23 @@ To execute it:: ========================== 1 failed in 0.04 seconds =========================== -Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started `_ for more examples. +Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started `_ for more examples. Features -------- -- Detailed info on failing `assert statements `_ (no need to remember ``self.assert*`` names); +- Detailed info on failing `assert statements `_ (no need to remember ``self.assert*`` names); - `Auto-discovery - `_ + `_ of test modules and functions; -- `Modular fixtures `_ for +- `Modular fixtures `_ for managing small or parametrized long-lived test resources; -- Can run `unittest `_ (or trial), - `nose `_ test suites out of the box; +- Can run `unittest `_ (or trial), + `nose `_ test suites out of the box; - Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested); @@ -92,7 +92,7 @@ Features Documentation ------------- -For full documentation, including installation, tutorials and PDF documents, please see http://docs.pytest.org. +For full documentation, including installation, tutorials and PDF documents, please see https://docs.pytest.org/en/latest/. Bugs/Requests @@ -104,7 +104,7 @@ Please use the `GitHub issue tracker `__ page for fixes and enhancements of each version. +Consult the `Changelog `__ page for fixes and enhancements of each version. License diff --git a/changelog/3907.doc.rst b/changelog/3907.doc.rst new file mode 100644 index 000000000..c556344f4 --- /dev/null +++ b/changelog/3907.doc.rst @@ -0,0 +1 @@ +Corrected type of the exceptions collection passed to ``xfail``: ``raises`` argument accepts a ``tuple`` instead of ``list``. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 8a97baa5a..f4814ac7d 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-3.7.4 release-3.7.3 release-3.7.2 release-3.7.1 diff --git a/doc/en/announce/release-3.7.4.rst b/doc/en/announce/release-3.7.4.rst new file mode 100644 index 000000000..0ab8938f4 --- /dev/null +++ b/doc/en/announce/release-3.7.4.rst @@ -0,0 +1,22 @@ +pytest-3.7.4 +======================================= + +pytest 3.7.4 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: + +* Anthony Sottile +* Bruno Oliveira +* Daniel Hahler +* Jiri Kuncar +* Steve Piercy + + +Happy testing, +The pytest Development Team diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 1ae99436d..cb6368a64 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -200,17 +200,17 @@ You can ask which markers exist for your test suite - the list includes our just $ pytest --markers @pytest.mark.webtest: mark a test as a webtest. - @pytest.mark.filterwarnings(warning): add a warning filter to the given test. see http://pytest.org/latest/warnings.html#pytest-mark-filterwarnings + @pytest.mark.filterwarnings(warning): add a warning filter to the given test. see https://docs.pytest.org/en/latest/warnings.html#pytest-mark-filterwarnings @pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test. - @pytest.mark.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 + @pytest.mark.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 https://docs.pytest.org/en/latest/skipping.html - @pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark 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 + @pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark 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 https://docs.pytest.org/en/latest/skipping.html - @pytest.mark.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. + @pytest.mark.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 https://docs.pytest.org/en/latest/parametrize.html for more info and examples. - @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures + @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/latest/fixture.html#usefixtures @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. @@ -376,17 +376,17 @@ The ``--markers`` option always gives you a list of available markers:: $ pytest --markers @pytest.mark.env(name): mark test to run only on named environment - @pytest.mark.filterwarnings(warning): add a warning filter to the given test. see http://pytest.org/latest/warnings.html#pytest-mark-filterwarnings + @pytest.mark.filterwarnings(warning): add a warning filter to the given test. see https://docs.pytest.org/en/latest/warnings.html#pytest-mark-filterwarnings @pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test. - @pytest.mark.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 + @pytest.mark.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 https://docs.pytest.org/en/latest/skipping.html - @pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark 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 + @pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark 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 https://docs.pytest.org/en/latest/skipping.html - @pytest.mark.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. + @pytest.mark.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 https://docs.pytest.org/en/latest/parametrize.html for more info and examples. - @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures + @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/latest/fixture.html#usefixtures @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index 7e1acea4e..61891eebd 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -617,5 +617,5 @@ get on the terminal - we are working on that):: Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0. Please use Metafunc.parametrize instead. - -- Docs: http://doc.pytest.org/en/latest/warnings.html + -- Docs: https://docs.pytest.org/en/latest/warnings.html ================== 42 failed, 1 warnings in 0.12 seconds =================== diff --git a/doc/en/funcarg_compare.rst b/doc/en/funcarg_compare.rst index c29ba1f3c..5403da2f2 100644 --- a/doc/en/funcarg_compare.rst +++ b/doc/en/funcarg_compare.rst @@ -7,7 +7,7 @@ pytest-2.3: reasoning for fixture/funcarg evolution **Target audience**: Reading this document requires basic knowledge of python testing, xUnit setup methods and the (previous) basic pytest -funcarg mechanism, see http://pytest.org/2.2.4/funcargs.html +funcarg mechanism, see https://docs.pytest.org/en/latest/historical-notes.html#funcargs-and-pytest-funcarg. If you are new to pytest, then you can simply ignore this section and read the other sections. diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index efdf008fb..dabc8a90f 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -277,7 +277,7 @@ on a particular platform:: ~~~~~~~~~~~~~~~~~~~~ If you want to be more specific as to why the test is failing, you can specify -a single exception, or a list of exceptions, in the ``raises`` argument. +a single exception, or a tuple of exceptions, in the ``raises`` argument. .. code-block:: python diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index df93a02b5..d1c927dd0 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -33,7 +33,7 @@ Running pytest now produces this output:: $REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2 warnings.warn(UserWarning("api v1, should use functions from v2")) - -- Docs: http://doc.pytest.org/en/latest/warnings.html + -- Docs: https://docs.pytest.org/en/latest/warnings.html =================== 1 passed, 1 warnings in 0.12 seconds =================== Pytest by default catches all warnings except for ``DeprecationWarning`` and ``PendingDeprecationWarning``. diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index aa361799b..27e13d932 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -422,7 +422,7 @@ additionally it is possible to copy examples for a example folder before running $REGENDOC_TMPDIR/test_example.py:4: PytestExerimentalApiWarning: testdir.copy_example is an experimental api that may change over time testdir.copy_example("test_example.py") - -- Docs: http://doc.pytest.org/en/latest/warnings.html + -- Docs: https://docs.pytest.org/en/latest/warnings.html =================== 2 passed, 1 warnings in 0.12 seconds =================== For more information about the result object that ``runpytest()`` returns, and diff --git a/scripts/release.minor.rst b/scripts/release.minor.rst index bdd8282cf..9a488edbc 100644 --- a/scripts/release.minor.rst +++ b/scripts/release.minor.rst @@ -9,11 +9,11 @@ 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: - http://doc.pytest.org/en/latest/changelog.html + https://docs.pytest.org/en/latest/changelog.html For complete documentation, please visit: - http://docs.pytest.org + https://docs.pytest.org/en/latest/ As usual, you can upgrade from pypi via: diff --git a/scripts/release.patch.rst b/scripts/release.patch.rst index 1982dc353..b1ad2dbd7 100644 --- a/scripts/release.patch.rst +++ b/scripts/release.patch.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/setup.py b/setup.py index 7039ae604..6207ad09b 100644 --- a/setup.py +++ b/setup.py @@ -92,7 +92,7 @@ def main(): description="pytest: simple powerful testing with Python", long_description=long_description, use_scm_version={"write_to": "src/_pytest/_version.py"}, - url="http://pytest.org", + 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/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 5cf63a063..a48a931ac 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -64,11 +64,16 @@ class AssertionRewritingHook(object): self._rewritten_names = set() self._register_with_pkg_resources() self._must_rewrite = set() + # flag to guard against trying to rewrite a pyc file while we are already writing another pyc file, + # which might result in infinite recursion (#3506) + self._writing_pyc = False def set_session(self, session): self.session = session def find_module(self, name, path=None): + if self._writing_pyc: + return None state = self.config._assertstate state.trace("find_module called for: %s" % name) names = name.rsplit(".", 1) @@ -151,7 +156,11 @@ class AssertionRewritingHook(object): # Probably a SyntaxError in the test. return None if write: - _write_pyc(state, co, source_stat, pyc) + self._writing_pyc = True + try: + _write_pyc(state, co, source_stat, pyc) + finally: + self._writing_pyc = False else: state.trace("found cached rewritten pyc for %r" % (fn,)) self.modules[name] = co, pyc diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index f601d9bec..7cad246c8 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -132,7 +132,7 @@ class LFPlugin(object): self._no_failures_behavior = self.config.getoption("last_failed_no_failures") def pytest_report_collectionfinish(self): - if self.active: + if self.active and self.config.getoption("verbose") >= 0: if not self._previously_failed_count: return None noun = "failure" if self._previously_failed_count == 1 else "failures" diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 57d3367e4..12b871f9f 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -203,7 +203,8 @@ class DoctestItem(pytest.Item): return capman = self.config.pluginmanager.getplugin("capturemanager") if capman: - out, err = capman.suspend_global_capture(in_=True) + capman.suspend_global_capture(in_=True) + out, err = capman.read_global_capture() sys.stdout.write(out) sys.stderr.write(err) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 977b07442..f175394a8 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -173,13 +173,14 @@ def pytest_configure(config): "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.", + "see https://docs.pytest.org/en/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 ", + "all of the specified fixtures. see " + "https://docs.pytest.org/en/latest/fixture.html#usefixtures ", ) diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index 64bc770ae..90afd6de8 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -51,7 +51,7 @@ def pytest_configure(config): "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", + "https://docs.pytest.org/en/latest/skipping.html", ) config.addinivalue_line( "markers", @@ -61,7 +61,7 @@ def pytest_configure(config): "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", + "a true failure. See https://docs.pytest.org/en/latest/skipping.html", ) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 09a4d5e0e..cc83959fd 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -263,7 +263,7 @@ class TerminalReporter(object): def write_fspath_result(self, nodeid, res): fspath = self.config.rootdir.join(nodeid.split("::")[0]) if fspath != self.currentfspath: - if self.currentfspath is not None: + if self.currentfspath is not None and self._show_progress_info: self._write_progress_information_filling_space() self.currentfspath = fspath fspath = self.startdir.bestrelpath(fspath) @@ -358,12 +358,12 @@ class TerminalReporter(object): def pytest_runtest_logreport(self, report): rep = report res = self.config.hook.pytest_report_teststatus(report=rep) - cat, letter, word = res + category, letter, word = res if isinstance(word, tuple): word, markup = word else: markup = None - self.stats.setdefault(cat, []).append(rep) + self.stats.setdefault(category, []).append(rep) self._tests_ran = True if not letter and not word: # probably passed setup/teardown @@ -703,7 +703,7 @@ class TerminalReporter(object): indented = "\n".join(" " + x for x in lines) self._tw.line(indented) self._tw.line() - self._tw.line("-- Docs: http://doc.pytest.org/en/latest/warnings.html") + self._tw.line("-- Docs: https://docs.pytest.org/en/latest/warnings.html") def summary_passes(self): if self.config.option.tbstyle != "no": diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index f2f23a6e2..3a93f92f3 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -53,7 +53,7 @@ def pytest_configure(config): config.addinivalue_line( "markers", "filterwarnings(warning): add a warning filter to the given test. " - "see http://pytest.org/latest/warnings.html#pytest-mark-filterwarnings ", + "see https://docs.pytest.org/en/latest/warnings.html#pytest-mark-filterwarnings ", ) diff --git a/testing/code/test_code.py b/testing/code/test_code.py index f7a8a4dbd..d1ae648c8 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -32,10 +32,8 @@ def test_code_with_class(): pytest.raises(TypeError, "_pytest._code.Code(A)") -if True: - - def x(): - pass +def x(): + raise NotImplementedError() def test_code_fullsource(): @@ -48,7 +46,7 @@ def test_code_source(): code = _pytest._code.Code(x) src = code.source() expected = """def x(): - pass""" + raise NotImplementedError()""" assert str(src) == expected @@ -85,9 +83,9 @@ def test_unicode_handling(): raise Exception(value) excinfo = pytest.raises(Exception, f) - str(excinfo) - if sys.version_info[0] < 3: - text_type(excinfo) + text_type(excinfo) + if sys.version_info < (3,): + bytes(excinfo) @pytest.mark.skipif(sys.version_info[0] >= 3, reason="python 2 only issue") @@ -105,25 +103,25 @@ def test_unicode_handling_syntax_error(): def test_code_getargs(): def f1(x): - pass + raise NotImplementedError() c1 = _pytest._code.Code(f1) assert c1.getargs(var=True) == ("x",) def f2(x, *y): - pass + raise NotImplementedError() c2 = _pytest._code.Code(f2) assert c2.getargs(var=True) == ("x", "y") def f3(x, **z): - pass + raise NotImplementedError() c3 = _pytest._code.Code(f3) assert c3.getargs(var=True) == ("x", "z") def f4(x, *y, **z): - pass + raise NotImplementedError() c4 = _pytest._code.Code(f4) assert c4.getargs(var=True) == ("x", "y", "z") @@ -188,11 +186,14 @@ class TestReprFuncArgs(object): tw = TWMock() - args = [("unicode_string", u"São Paulo"), ("utf8_string", "S\xc3\xa3o Paulo")] + args = [("unicode_string", u"São Paulo"), ("utf8_string", b"S\xc3\xa3o Paulo")] r = ReprFuncArgs(args) r.toterminal(tw) if sys.version_info[0] >= 3: - assert tw.lines[0] == "unicode_string = São Paulo, utf8_string = São Paulo" + assert ( + tw.lines[0] + == r"unicode_string = São Paulo, utf8_string = b'S\xc3\xa3o Paulo'" + ) else: assert tw.lines[0] == "unicode_string = São Paulo, utf8_string = São Paulo" diff --git a/testing/code/test_source.py b/testing/code/test_source.py index d7e8fe422..14f06acd0 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # flake8: noqa # disable flake check on this file because some constructs are strange # or redundant on purpose and can't be disable on a line-by-line basis @@ -41,15 +42,11 @@ def test_source_str_function(): def test_unicode(): - try: - unicode - except NameError: - return - x = Source(unicode("4")) + x = Source(u"4") assert str(x) == "4" - co = _pytest._code.compile(unicode('u"\xc3\xa5"', "utf8"), mode="eval") + co = _pytest._code.compile(u'u"å"', mode="eval") val = eval(co) - assert isinstance(val, unicode) + assert isinstance(val, six.text_type) def test_source_from_function(): @@ -632,7 +629,7 @@ def test_issue55(): assert str(s) == ' round_trip("""\n""")' -def XXXtest_multiline(): +def test_multiline(): source = getstatement( 0, """\ diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 41907503e..966de66b2 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -116,7 +116,7 @@ def test_resultlog_is_deprecated(testdir): result.stdout.fnmatch_lines( [ "*--result-log is deprecated and scheduled for removal in pytest 4.0*", - "*See https://docs.pytest.org/*/usage.html#creating-resultlog-format-files for more information*", + "*See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information*", ] ) diff --git a/testing/python/fixture.py b/testing/python/fixture.py index f8f5eb54e..fc3eee42b 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -1584,6 +1584,7 @@ class TestFixtureManagerParseFactories(object): values = [] """ ) + testdir.syspathinsert(testdir.tmpdir.dirname) package = testdir.mkdir("package") package.join("__init__.py").write("") package.join("conftest.py").write( diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 79e7cf0e3..c436ab0de 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -1124,3 +1124,32 @@ def test_simple_failure(): result = testdir.runpytest() result.stdout.fnmatch_lines("*E*assert (1 + 1) == 3") + + +def test_rewrite_infinite_recursion(testdir, pytestconfig, monkeypatch): + """Fix infinite recursion when writing pyc files: if an import happens to be triggered when writing the pyc + file, this would cause another call to the hook, which would trigger another pyc writing, which could + trigger another import, and so on. (#3506)""" + from _pytest.assertion import rewrite + + testdir.syspathinsert() + testdir.makepyfile(test_foo="def test_foo(): pass") + testdir.makepyfile(test_bar="def test_bar(): pass") + + original_write_pyc = rewrite._write_pyc + + write_pyc_called = [] + + def spy_write_pyc(*args, **kwargs): + # make a note that we have called _write_pyc + write_pyc_called.append(True) + # try to import a module at this point: we should not try to rewrite this module + assert hook.find_module("test_bar") is None + return original_write_pyc(*args, **kwargs) + + monkeypatch.setattr(rewrite, "_write_pyc", spy_write_pyc) + monkeypatch.setattr(sys, "dont_write_bytecode", False) + + hook = AssertionRewritingHook(pytestconfig) + assert hook.find_module("test_foo") is not None + assert len(write_pyc_called) == 1 diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index ba3d6f87a..86bd65810 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -553,6 +553,19 @@ class TestLastFailed(object): testdir.runpytest("-q", "--lf") assert os.path.exists(".pytest_cache/v/cache/lastfailed") + @pytest.mark.parametrize("quiet", [True, False]) + @pytest.mark.parametrize("opt", ["--ff", "--lf"]) + def test_lf_and_ff_obey_verbosity(self, quiet, opt, testdir): + testdir.makepyfile("def test(): pass") + args = [opt] + if quiet: + args.append("-q") + result = testdir.runpytest(*args) + if quiet: + assert "run all" not in result.stdout.str() + else: + assert "run all" in result.stdout.str() + def test_xfail_not_considered_failure(self, testdir): testdir.makepyfile( """ diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 2331620d5..02e2824d9 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -3,6 +3,7 @@ terminal reporting of the full testing process. """ from __future__ import absolute_import, division, print_function import collections +import os import sys import textwrap @@ -472,7 +473,7 @@ class TestTerminalFunctional(object): def test_show_deselected_items_using_markexpr_before_test_execution(self, testdir): testdir.makepyfile( - """ + test_show_deselected=""" import pytest @pytest.mark.foo @@ -491,7 +492,7 @@ class TestTerminalFunctional(object): result.stdout.fnmatch_lines( [ "collected 3 items / 1 deselected", - "*test_show_des*.py ..*", + "*test_show_deselected.py ..*", "*= 2 passed, 1 deselected in * =*", ] ) @@ -1134,7 +1135,53 @@ def test_no_trailing_whitespace_after_inifile_word(testdir): assert "inifile: tox.ini\n" in result.stdout.str() -class TestProgress(object): +class TestClassicOutputStyle(object): + """Ensure classic output style works as expected (#3883)""" + + @pytest.fixture + def test_files(self, testdir): + testdir.makepyfile( + **{ + "test_one.py": "def test_one(): pass", + "test_two.py": "def test_two(): assert 0", + "sub/test_three.py": """ + def test_three_1(): pass + def test_three_2(): assert 0 + def test_three_3(): pass + """, + } + ) + + def test_normal_verbosity(self, testdir, test_files): + result = testdir.runpytest("-o", "console_output_style=classic") + result.stdout.fnmatch_lines( + [ + "test_one.py .", + "test_two.py F", + "sub{}test_three.py .F.".format(os.sep), + "*2 failed, 3 passed in*", + ] + ) + + def test_verbose(self, testdir, test_files): + result = testdir.runpytest("-o", "console_output_style=classic", "-v") + result.stdout.fnmatch_lines( + [ + "test_one.py::test_one PASSED", + "test_two.py::test_two FAILED", + "sub{}test_three.py::test_three_1 PASSED".format(os.sep), + "sub{}test_three.py::test_three_2 FAILED".format(os.sep), + "sub{}test_three.py::test_three_3 PASSED".format(os.sep), + "*2 failed, 3 passed in*", + ] + ) + + def test_quiet(self, testdir, test_files): + result = testdir.runpytest("-o", "console_output_style=classic", "-q") + result.stdout.fnmatch_lines([".F.F.", "*2 failed, 3 passed in*"]) + + +class TestProgressOutputStyle(object): @pytest.fixture def many_tests_files(self, testdir): testdir.makepyfile( diff --git a/tox.ini b/tox.ini index 1ca17370f..fbc5d4779 100644 --- a/tox.ini +++ b/tox.ini @@ -17,13 +17,21 @@ envlist = docs [testenv] -commands = pytest --lsof -ra {posargs:testing} +commands = + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof -ra {env:_PYTEST_TEST_OPTS:} {posargs:testing} + coverage: coverage report -m --skip-covered +setenv = + coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m + coverage: _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess + coverage: COVERAGE_FILE={toxinidir}/.coverage + coverage: COVERAGE_PROCESS_START={toxinidir}/.coveragerc passenv = USER USERNAME deps = hypothesis>=3.56 nose mock requests + {env:_PYTEST_TOX_EXTRA_DEP:} [testenv:py27-subprocess] changedir = . @@ -47,9 +55,10 @@ deps = mock nose hypothesis>=3.56 + {env:_PYTEST_TOX_EXTRA_DEP:} changedir=testing commands = - pytest -n8 -ra {posargs:.} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n8 -ra {posargs:.} [testenv:py36-xdist] deps = {[testenv:py27-xdist]deps} @@ -58,9 +67,11 @@ commands = {[testenv:py27-xdist]commands} [testenv:py27-pexpect] changedir = testing platform = linux|darwin -deps = pexpect +deps = + pexpect + {env:_PYTEST_TOX_EXTRA_DEP:} commands = - pytest -ra test_pdb.py test_terminal.py test_unittest.py + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra test_pdb.py test_terminal.py test_unittest.py [testenv:py36-pexpect] changedir = {[testenv:py27-pexpect]changedir} @@ -73,26 +84,32 @@ deps = pytest-xdist>=1.13 hypothesis>=3.56 mock + {env:_PYTEST_TOX_EXTRA_DEP:} distribute = true changedir=testing setenv = + {[testenv]setenv} PYTHONDONTWRITEBYTECODE=1 commands = - pytest -n3 -ra {posargs:.} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n3 -ra {posargs:.} [testenv:py27-trial] -deps = twisted +deps = + twisted + {env:_PYTEST_TOX_EXTRA_DEP:} commands = - pytest -ra {posargs:testing/test_unittest.py} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra {posargs:testing/test_unittest.py} [testenv:py36-trial] deps = {[testenv:py27-trial]deps} commands = {[testenv:py27-trial]commands} [testenv:py27-numpy] -deps = numpy +deps = + numpy + {env:_PYTEST_TOX_EXTRA_DEP:} commands= - pytest -ra {posargs:testing/python/approx.py} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra {posargs:testing/python/approx.py} [testenv:py36-numpy] deps = {[testenv:py27-numpy]deps} @@ -100,6 +117,7 @@ commands = {[testenv:py27-numpy]commands} [testenv:py27-pluggymaster] setenv= + {[testenv]setenv} _PYTEST_SETUP_SKIP_PLUGGY_DEP=1 deps = {[testenv]deps} @@ -123,15 +141,13 @@ commands = [testenv:doctesting] basepython = python -usedevelop = True skipsdist = True -# ensure the given pyargs can't mean anything else -changedir = doc/ deps = PyYAML + {env:_PYTEST_TOX_EXTRA_DEP:} commands = - pytest -ra en - pytest --doctest-modules --pyargs _pytest + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra doc/en + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest [testenv:regen] changedir = doc/en @@ -155,7 +171,8 @@ commands = [testenv:py36-freeze] changedir = testing/freeze -deps = pyinstaller +deps = + pyinstaller commands = {envpython} create_executable.py {envpython} tox_run.py @@ -170,7 +187,7 @@ deps = coveralls codecov commands = - coverage run --source=_pytest -m pytest testing + coverage run -m pytest testing coverage report -m coveralls codecov