diff --git a/.coveragerc b/.coveragerc index 61ff66749dc..a0a7a02f2f0 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 373b79289b5..89874f5a0e4 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 0d6c9d41238..78f2156e803 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 3d2d6a7691d..435edb55046 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 97b21898ed8..97ab784cde4 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 00000000000..c556344f4b7 --- /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 8a97baa5a61..f4814ac7d1a 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 00000000000..0ab8938f4f6 --- /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 1ae99436d84..cb6368a6443 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 7e1acea4e73..61891eebd8b 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 c29ba1f3c35..5403da2f2de 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 efdf008fb69..dabc8a90f43 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 df93a02b540..d1c927dd067 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 aa361799b3b..27e13d93208 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 bdd8282cfad..9a488edbc52 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 1982dc353c4..b1ad2dbd775 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 7039ae6049e..6207ad09b86 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 5cf63a0639a..a48a931ac1f 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -64,11 +64,16 @@ def __init__(self, config): 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 @@ def find_module(self, name, path=None): # 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 f601d9bec08..7cad246c86c 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -132,7 +132,7 @@ def __init__(self, config): 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 57d3367e403..12b871f9fa3 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -203,7 +203,8 @@ def _disable_output_capturing_for_darwin(self): 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 977b07442e2..f175394a8be 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 64bc770aea5..90afd6de873 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -51,7 +51,7 @@ def nop(*args, **kwargs): "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 nop(*args, **kwargs): "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 09a4d5e0e4f..cc83959fd84 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -263,7 +263,7 @@ def hasopt(self, char): 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 @@ def pytest_runtest_logstart(self, nodeid, location): 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 @@ def summary_warnings(self): 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 f2f23a6e2be..3a93f92f3c4 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 f7a8a4dbd55..d1ae648c848 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -32,10 +32,8 @@ class A(object): 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 f(): 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 f(): 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 @@ def test_not_raise_exception_with_mixed_encoding(self): 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 d7e8fe42221..14f06acd071 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 41907503e8a..966de66b205 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -116,7 +116,7 @@ def test(): 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 f8f5eb54e34..fc3eee42b5f 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -1584,6 +1584,7 @@ def test_package_fixture_complex(self, testdir): 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 79e7cf0e354..c436ab0decd 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 ba3d6f87ada..23ec7359906 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -615,13 +615,19 @@ def test(): @pytest.mark.parametrize("opt", ["--ff", "--lf"]) def test_lf_and_ff_prints_no_needless_message(self, quiet, opt, testdir): # Issue 3853 - testdir.makepyfile("def test(): pass") + testdir.makepyfile("def test(): assert 0") args = [opt] if quiet: args.append("-q") result = testdir.runpytest(*args) assert "run all" not in result.stdout.str() + result = testdir.runpytest(*args) + if quiet: + assert "run all" not in result.stdout.str() + else: + assert "rerun previous" in result.stdout.str() + def get_cached_last_failed(self, testdir): config = testdir.parseconfigure() return sorted(config.cache.get("cache/lastfailed", {})) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 2331620d597..02e2824d97a 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -3,6 +3,7 @@ """ from __future__ import absolute_import, division, print_function import collections +import os import sys import textwrap @@ -472,7 +473,7 @@ def test_three(): 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 @@ def test_pass(): 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 1ca17370fb7..fbc5d4779d6 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