diff --git a/.travis.yml b/.travis.yml index 00abca0b2f0..72731b3d072 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ sudo: false language: python +dist: xenial stages: - baseline - name: test @@ -7,7 +8,7 @@ stages: - name: deploy if: repo = pytest-dev/pytest AND tag IS present python: - - '3.6' + - '3.7' install: - pip install --upgrade --pre tox env: @@ -17,24 +18,21 @@ env: - 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 + # Specialized factors for py37. + - TOXENV=py37-pexpect,py37-trial,py37-numpy + - TOXENV=py37-xdist + - TOXENV=py37-pluggymaster PYTEST_NO_COVERAGE=1 + - TOXENV=py37-freeze PYTEST_NO_COVERAGE=1 jobs: include: # Coverage tracking is slow with pypy, skip it. - env: TOXENV=pypy PYTEST_NO_COVERAGE=1 python: 'pypy-5.4' + dist: trusty - env: TOXENV=py35 python: '3.5' - - 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 @@ -54,8 +52,11 @@ jobs: - stage: baseline env: TOXENV=py27 - env: TOXENV=py34 + python: '3.4' - env: TOXENV=py36 - - env: TOXENV=linting,docs,doctesting PYTEST_NO_COVERAGE=1 + python: '3.6' + - env: TOXENV=linting,docs,doctesting + python: '3.7' - stage: deploy python: '3.6' @@ -88,7 +89,8 @@ after_success: - | if [[ "$PYTEST_NO_COVERAGE" != 1 ]]; then set -e - pip install coverage + # Add last TOXENV to $PATH. + PATH="$PWD/.tox/${TOXENV##*,}/bin:$PATH" coverage combine coverage xml --ignore-errors coverage report -m --ignore-errors diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 64a955198ce..c537d6b7926 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,40 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 3.10.1 (2018-11-11) +========================== + +Bug Fixes +--------- + +- `#4287 `_: Fix nested usage of debugging plugin (pdb), e.g. with pytester's ``testdir.runpytest``. + + +- `#4304 `_: Block the ``stepwise`` plugin if ``cacheprovider`` is also blocked, as one depends on the other. + + +- `#4306 `_: Parse ``minversion`` as an actual version and not as dot-separated strings. + + +- `#4310 `_: Fix duplicate collection due to multiple args matching the same packages. + + +- `#4321 `_: Fix ``item.nodeid`` with resolved symlinks. + + +- `#4325 `_: Fix collection of direct symlinked files, where the target does not match ``python_files``. + + +- `#4329 `_: Fix TypeError in report_collect with _collect_report_last_write. + + + +Trivial/Internal Changes +------------------------ + +- `#4305 `_: Replace byte/unicode helpers in test_capture with python level syntax. + + pytest 3.10.0 (2018-11-03) ========================== diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d3202f7c835..ea778149c60 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -169,7 +169,7 @@ Short version #. Follow **PEP-8** for naming and `black `_ for formatting. #. Tests are run using ``tox``:: - tox -e linting,py27,py36 + tox -e linting,py27,py37 The test environments above are usually enough to cover most cases locally. @@ -237,12 +237,12 @@ Here is a simple overview, with pytest-specific bits: #. Run all the tests - You need to have Python 2.7 and 3.6 available in your system. Now + You need to have Python 2.7 and 3.7 available in your system. Now running tests is as simple as issuing this command:: - $ tox -e linting,py27,py36 + $ tox -e linting,py27,py37 - This command will run tests via the "tox" tool against Python 2.7 and 3.6 + This command will run tests via the "tox" tool against Python 2.7 and 3.7 and also perform "lint" coding-style checks. #. You can now edit your local working copy and run the tests again as necessary. Please follow PEP-8 for naming. @@ -252,9 +252,9 @@ Here is a simple overview, with pytest-specific bits: $ tox -e py27 -- --pdb - Or to only run tests in a particular test module on Python 3.6:: + Or to only run tests in a particular test module on Python 3.7:: - $ tox -e py36 -- testing/test_config.py + $ tox -e py37 -- testing/test_config.py When committing, ``pre-commit`` will re-format the files if necessary. diff --git a/appveyor.yml b/appveyor.yml index abe4319841c..47f5db9e68a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,6 @@ environment: matrix: - TOXENV: "py27" - TOXENV: "py37" - PYTEST_NO_COVERAGE: "1" - TOXENV: "linting,docs,doctesting" - TOXENV: "py36" - TOXENV: "py35" @@ -14,13 +13,13 @@ environment: - TOXENV: "py27-pluggymaster" PYTEST_NO_COVERAGE: "1" - TOXENV: "py27-xdist" - # Specialized factors for py36. - - TOXENV: "py36-trial,py36-numpy" - - TOXENV: "py36-pluggymaster" + # Specialized factors for py37. + - TOXENV: "py37-trial,py37-numpy" + - TOXENV: "py37-pluggymaster" PYTEST_NO_COVERAGE: "1" - - TOXENV: "py36-freeze" + - TOXENV: "py37-freeze" PYTEST_NO_COVERAGE: "1" - - TOXENV: "py36-xdist" + - TOXENV: "py37-xdist" matrix: fast_finish: true diff --git a/changelog/4287.bugfix.rst b/changelog/4287.bugfix.rst deleted file mode 100644 index 5ba5fbb7af9..00000000000 --- a/changelog/4287.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix nested usage of debugging plugin (pdb), e.g. with pytester's ``testdir.runpytest``. diff --git a/changelog/4304.bugfix.rst b/changelog/4304.bugfix.rst deleted file mode 100644 index 7d4dc033e40..00000000000 --- a/changelog/4304.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Block the ``stepwise`` plugin if ``cacheprovider`` is also blocked, as one depends on the other. diff --git a/changelog/4305.trivial.rst b/changelog/4305.trivial.rst deleted file mode 100644 index 2430a5f91d4..00000000000 --- a/changelog/4305.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Replace byte/unicode helpers in test_capture with python level syntax. diff --git a/changelog/4306.bugfix.rst b/changelog/4306.bugfix.rst deleted file mode 100644 index cb2872d3ff8..00000000000 --- a/changelog/4306.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Parse ``minversion`` as an actual version and not as dot-separated strings. diff --git a/changelog/4310.bugfix.rst b/changelog/4310.bugfix.rst deleted file mode 100644 index 24e177c2ea1..00000000000 --- a/changelog/4310.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix duplicate collection due to multiple args matching the same packages. diff --git a/changelog/4329.bugfix.rst b/changelog/4329.bugfix.rst deleted file mode 100644 index 6acfe7e61b5..00000000000 --- a/changelog/4329.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix TypeError in report_collect with _collect_report_last_write. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 8f583c5f527..e0df8eb1dd7 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-3.10.1 release-3.10.0 release-3.9.3 release-3.9.2 diff --git a/doc/en/announce/release-3.10.1.rst b/doc/en/announce/release-3.10.1.rst new file mode 100644 index 00000000000..556b24ae15b --- /dev/null +++ b/doc/en/announce/release-3.10.1.rst @@ -0,0 +1,24 @@ +pytest-3.10.1 +======================================= + +pytest 3.10.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: + +* Anthony Sottile +* Boris Feld +* Bruno Oliveira +* Daniel Hahler +* Fabien ZARIFIAN +* Jon Dufresne +* Ronny Pfannschmidt + + +Happy testing, +The pytest Development Team diff --git a/doc/en/example/nonpython.rst b/doc/en/example/nonpython.rst index 8bcb75b4385..bda15065ae7 100644 --- a/doc/en/example/nonpython.rst +++ b/doc/en/example/nonpython.rst @@ -85,9 +85,8 @@ interesting to just look at the collection tree:: rootdir: $REGENDOC_TMPDIR/nonpython, inifile: collected 2 items - - - - + + + ======================= no tests ran in 0.12 seconds ======================= diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 0a4cbab0eba..df4f1c8fbf5 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -398,6 +398,7 @@ def __init__(self, config): # Keep track of any collected nodes in here, so we don't duplicate fixtures self._node_cache = {} self._bestrelpathcache = _bestrelpath_cache(config.rootdir) + # Dirnames of pkgs with dunder-init files. self._pkg_roots = {} self.config.pluginmanager.register(self, name="session") @@ -504,7 +505,7 @@ def _collect(self, arg): from _pytest.python import Package names = self._parsearg(arg) - argpath = names.pop(0).realpath() + argpath = names.pop(0) # Start with a Session root, and delve to argpath item (dir or file) # and stack all Packages found on the way. @@ -551,8 +552,7 @@ def filter_(f): seen_dirs.add(dirpath) pkginit = dirpath.join("__init__.py") if pkginit.exists(): - collect_root = self._pkg_roots.get(dirpath, self) - for x in collect_root._collectfile(pkginit): + for x in self._collectfile(pkginit): yield x if isinstance(x, Package): self._pkg_roots[dirpath] = x @@ -652,7 +652,7 @@ def _parsearg(self, arg): "file or package not found: " + arg + " (missing __init__.py?)" ) raise UsageError("file not found: " + arg) - parts[0] = path + parts[0] = path.realpath() return parts def matchnodes(self, matching, names): diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index a559b55b538..86e541152d7 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -449,7 +449,7 @@ def _prunetraceback(self, excinfo): def _check_initialpaths_for_relpath(session, fspath): for initial_path in session._initialpaths: if fspath.common(initial_path) == initial_path: - return fspath.relto(initial_path.dirname) + return fspath.relto(initial_path) class FSCollector(Collector): diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 7c94fda6a43..37ec6d84df1 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -666,11 +666,11 @@ def test_cmdline_python_namespace_package(self, testdir, monkeypatch): assert result.ret == 0 result.stdout.fnmatch_lines( [ - "*test_hello.py::test_hello*PASSED*", - "*test_hello.py::test_other*PASSED*", - "*test_world.py::test_world*PASSED*", - "*test_world.py::test_other*PASSED*", - "*4 passed*", + "test_hello.py::test_hello*PASSED*", + "test_hello.py::test_other*PASSED*", + "ns_pkg/world/test_world.py::test_world*PASSED*", + "ns_pkg/world/test_world.py::test_other*PASSED*", + "*4 passed in*", ] ) diff --git a/testing/test_argcomplete.py b/testing/test_argcomplete.py index 060cc2b4962..046d69aa03a 100644 --- a/testing/test_argcomplete.py +++ b/testing/test_argcomplete.py @@ -15,7 +15,7 @@ def equal_with_bash(prefix, ffc, fc, out=None): res_bash = set(fc(prefix)) retval = set(res) == res_bash if out: - out.write("equal_with_bash {} {}\n".format(retval, res)) + out.write("equal_with_bash({}) {} {}\n".format(prefix, retval, res)) if not retval: out.write(" python - bash: %s\n" % (set(res) - res_bash)) out.write(" bash - python: %s\n" % (res_bash - set(res))) @@ -91,13 +91,19 @@ def __call__(self, prefix, **kwargs): class TestArgComplete(object): @pytest.mark.skipif("sys.platform in ('win32', 'darwin')") - def test_compare_with_compgen(self): + def test_compare_with_compgen(self, tmpdir): from _pytest._argcomplete import FastFilesCompleter ffc = FastFilesCompleter() fc = FilesCompleter() - for x in ["/", "/d", "/data", "qqq", ""]: - assert equal_with_bash(x, ffc, fc, out=sys.stdout) + + with tmpdir.as_cwd(): + assert equal_with_bash("", ffc, fc, out=sys.stdout) + + tmpdir.ensure("data") + + for x in ["d", "data", "doesnotexist", ""]: + assert equal_with_bash(x, ffc, fc, out=sys.stdout) @pytest.mark.skipif("sys.platform in ('win32', 'darwin')") def test_remove_dir_prefix(self): diff --git a/testing/test_collection.py b/testing/test_collection.py index db2497c0eb2..9385f546118 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -6,6 +6,8 @@ import sys import textwrap +import py + import pytest from _pytest.main import _in_venv from _pytest.main import EXIT_NOTESTSCOLLECTED @@ -1082,4 +1084,55 @@ def test_1(): with testdir.tmpdir.as_cwd(): result = testdir.runpytest("--collect-only") result.stdout.fnmatch_lines(["collected 1 item"]) + + +@pytest.mark.skipif( + not hasattr(py.path.local, "mksymlinkto"), + reason="symlink not available on this platform", +) +def test_collect_symlink_file_arg(testdir): + """Test that collecting a direct symlink, where the target does not match python_files works (#4325).""" + real = testdir.makepyfile( + real=""" + def test_nodeid(request): + assert request.node.nodeid == "real.py::test_nodeid" + """ + ) + symlink = testdir.tmpdir.join("symlink.py") + symlink.mksymlinkto(real) + result = testdir.runpytest("-v", symlink) + result.stdout.fnmatch_lines(["real.py::test_nodeid PASSED*", "*1 passed in*"]) + assert result.ret == 0 + + +@pytest.mark.skipif( + not hasattr(py.path.local, "mksymlinkto"), + reason="symlink not available on this platform", +) +def test_collect_symlink_out_of_tree(testdir): + """Test collection of symlink via out-of-tree rootdir.""" + sub = testdir.tmpdir.join("sub") + real = sub.join("test_real.py") + real.write( + textwrap.dedent( + """ + def test_nodeid(request): + # Should not contain sub/ prefix. + assert request.node.nodeid == "test_real.py::test_nodeid" + """ + ), + ensure=True, + ) + + out_of_tree = testdir.tmpdir.join("out_of_tree").ensure(dir=True) + symlink_to_sub = out_of_tree.join("symlink_to_sub") + symlink_to_sub.mksymlinkto(sub) + sub.chdir() + result = testdir.runpytest("-vs", "--rootdir=%s" % sub, symlink_to_sub) + result.stdout.fnmatch_lines( + [ + # Should not contain "sub/"! + "test_real.py::test_nodeid PASSED" + ] + ) assert result.ret == 0 diff --git a/testing/test_nodes.py b/testing/test_nodes.py index cc1ee1583ec..b13ce1fe604 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -1,3 +1,5 @@ +import py + import pytest from _pytest import nodes @@ -29,3 +31,23 @@ def test(): ) with pytest.raises(ValueError, match=".*instance of PytestWarning.*"): items[0].warn(UserWarning("some warning")) + + +def test__check_initialpaths_for_relpath(): + """Ensure that it handles dirs, and does not always use dirname.""" + cwd = py.path.local() + + class FakeSession: + _initialpaths = [cwd] + + assert nodes._check_initialpaths_for_relpath(FakeSession, cwd) == "" + + sub = cwd.join("file") + + class FakeSession: + _initialpaths = [cwd] + + assert nodes._check_initialpaths_for_relpath(FakeSession, sub) == "file" + + outside = py.path.local("/outside") + assert nodes._check_initialpaths_for_relpath(FakeSession, outside) is None diff --git a/testing/test_session.py b/testing/test_session.py index fed39195cf5..e2c3701da65 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -333,7 +333,11 @@ def test_one(): result = testdir.runpytest("--rootdir={}".format(path)) result.stdout.fnmatch_lines( - ["*rootdir: {}/root, inifile:*".format(testdir.tmpdir), "*1 passed*"] + [ + "*rootdir: {}/root, inifile:*".format(testdir.tmpdir), + "root/test_rootdir_option_arg.py *", + "*1 passed*", + ] ) diff --git a/tox.ini b/tox.ini index 6088aa6e688..8fe0124f328 100644 --- a/tox.ini +++ b/tox.ini @@ -10,10 +10,10 @@ envlist = py36 py37 pypy - {py27,py36}-{pexpect,xdist,trial,numpy,pluggymaster} + {py27,py37}-{pexpect,xdist,trial,numpy,pluggymaster} py27-nobyte doctesting - py36-freeze + py37-freeze docs [testenv] @@ -23,7 +23,7 @@ commands = coverage: coverage report passenv = USER USERNAME COVERAGE_* TRAVIS setenv = - # configuration if a user runs tox with a "coverage" factor, for example "tox -e py36-coverage" + # configuration if a user runs tox with a "coverage" factor, for example "tox -e py37-coverage" coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m coverage: _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess coverage: COVERAGE_FILE={toxinidir}/.coverage @@ -46,7 +46,7 @@ commands = [testenv:linting] skip_install = True -basepython = python3.6 +basepython = python3 deps = pre-commit>=1.11.0 commands = pre-commit run --all-files --show-diff-on-failure @@ -60,7 +60,7 @@ deps = commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs} -[testenv:py36-xdist] +[testenv:py37-xdist] # NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706. deps = pytest-xdist>=1.13 @@ -78,7 +78,7 @@ deps = commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest testing/test_pdb.py testing/test_terminal.py testing/test_unittest.py {posargs} -[testenv:py36-pexpect] +[testenv:py37-pexpect] platform = {[testenv:py27-pexpect]platform} deps = {[testenv:py27-pexpect]deps} commands = {[testenv:py27-pexpect]commands} @@ -103,7 +103,7 @@ deps = commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/test_unittest.py} -[testenv:py36-trial] +[testenv:py37-trial] deps = {[testenv:py27-trial]deps} commands = {[testenv:py27-trial]commands} @@ -114,7 +114,7 @@ deps = commands= {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/python/approx.py} -[testenv:py36-numpy] +[testenv:py37-numpy] deps = {[testenv:py27-numpy]deps} commands = {[testenv:py27-numpy]commands} @@ -125,7 +125,7 @@ setenv= # NOTE: using env instead of "{[testenv]deps}", because of https://github.com/tox-dev/tox/issues/706. _PYTEST_TOX_EXTRA_DEP=git+https://github.com/pytest-dev/pluggy.git@master -[testenv:py36-pluggymaster] +[testenv:py37-pluggymaster] setenv = {[testenv:py27-pluggymaster]setenv} [testenv:docs] @@ -170,7 +170,7 @@ changedir = testing commands = {envpython} {envbindir}/py.test-jython {posargs} -[testenv:py36-freeze] +[testenv:py37-freeze] changedir = testing/freeze deps = pyinstaller