diff --git a/.gitignore b/.gitignore index e2d59502c..a008b4363 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ coverage.xml .project .settings .vscode + +# generated by pip +pip-wheel-metadata/ diff --git a/.travis.yml b/.travis.yml index 6489a1647..66d06fccd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,10 +17,10 @@ env: # Specialized factors for py27. - TOXENV=py27-nobyte - TOXENV=py27-xdist - - TOXENV=py27-pluggymaster PYTEST_NO_COVERAGE=1 + - TOXENV=py27-pluggymaster # Specialized factors for py37. - TOXENV=py37-pexpect,py37-trial,py37-numpy - - TOXENV=py37-pluggymaster PYTEST_NO_COVERAGE=1 + - TOXENV=py37-pluggymaster - TOXENV=py37-freeze PYTEST_NO_COVERAGE=1 matrix: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ac168dfd3..b1791a6b9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,62 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 4.2.1 (2019-02-12) +========================= + +Bug Fixes +--------- + +- `#2895 `_: The ``pytest_report_collectionfinish`` hook now is also called with ``--collect-only``. + + +- `#3899 `_: Do not raise ``UsageError`` when an imported package has a ``pytest_plugins.py`` child module. + + +- `#4347 `_: Fix output capturing when using pdb++ with recursive debugging. + + +- `#4592 `_: Fix handling of ``collect_ignore`` via parent ``conftest.py``. + + +- `#4700 `_: Fix regression where ``setUpClass`` would always be called in subclasses even if all tests + were skipped by a ``unittest.skip()`` decorator applied in the subclass. + + +- `#4739 `_: Fix ``parametrize(... ids=)`` when the function returns non-strings. + + +- `#4745 `_: Fix/improve collection of args when passing in ``__init__.py`` and a test file. + + +- `#4770 `_: ``more_itertools`` is now constrained to <6.0.0 when required for Python 2.7 compatibility. + + +- `#526 `_: Fix "ValueError: Plugin already registered" exceptions when running in build directories that symlink to actual source. + + + +Improved Documentation +---------------------- + +- `#3899 `_: Add note to ``plugins.rst`` that ``pytest_plugins`` should not be used as a name for a user module containing plugins. + + +- `#4324 `_: Document how to use ``raises`` and ``does_not_raise`` to write parametrized tests with conditional raises. + + +- `#4709 `_: Document how to customize test failure messages when using + ``pytest.warns``. + + + +Trivial/Internal Changes +------------------------ + +- `#4741 `_: Some verbosity related attributes of the TerminalReporter plugin are now + read only properties. + + pytest 4.2.0 (2019-01-30) ========================= diff --git a/appveyor.yml b/appveyor.yml index c17c368a5..773e16b1b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,11 +11,9 @@ environment: # Specialized factors for py27. - TOXENV: "py27-trial,py27-numpy,py27-nobyte" - TOXENV: "py27-pluggymaster" - PYTEST_NO_COVERAGE: "1" # Specialized factors for py37. - TOXENV: "py37-trial,py37-numpy" - TOXENV: "py37-pluggymaster" - PYTEST_NO_COVERAGE: "1" - TOXENV: "py37-freeze" PYTEST_NO_COVERAGE: "1" diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..278b9015a --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,45 @@ +trigger: +- master +- features + +variables: + PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml" + +jobs: + +- job: 'Test' + pool: + vmImage: "vs2017-win2016" + strategy: + matrix: + py27: + python.version: '2.7' + tox.env: 'py27' + py35: + python.version: '3.5' + tox.env: 'py35' + py36: + python.version: '3.6' + tox.env: 'py36' + py37: + python.version: '3.7' + tox.env: 'py37' + maxParallel: 4 + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '$(python.version)' + architecture: 'x64' + + - script: python -m pip install --upgrade pip && pip install tox + displayName: 'Install tox' + + - script: python -m tox -e $(tox.env) + displayName: 'Run tests' + + - task: PublishTestResults@2 + inputs: + testResultsFiles: 'build/test-results/$(tox.env).xml' + testRunTitle: '$(tox.env)' + condition: succeededOrFailed() diff --git a/changelog/2895.bugfix.rst b/changelog/2895.bugfix.rst deleted file mode 100644 index 8e01e193c..000000000 --- a/changelog/2895.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -The ``pytest_report_collectionfinish`` hook now is also called with ``--collect-only``. diff --git a/changelog/3899.bugfix.rst b/changelog/3899.bugfix.rst deleted file mode 100644 index 8f117779e..000000000 --- a/changelog/3899.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Do not raise ``UsageError`` when an imported package has a ``pytest_plugins.py`` child module. diff --git a/changelog/3899.doc.rst b/changelog/3899.doc.rst deleted file mode 100644 index 675684a01..000000000 --- a/changelog/3899.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Add note to ``plugins.rst`` that ``pytest_plugins`` should not be used as a name for a user module containing plugins. diff --git a/changelog/4324.doc.rst b/changelog/4324.doc.rst deleted file mode 100644 index 5e37a91aa..000000000 --- a/changelog/4324.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Document how to use ``raises`` and ``does_not_raise`` to write parametrized tests with conditional raises. diff --git a/changelog/4592.bugfix.rst b/changelog/4592.bugfix.rst deleted file mode 100644 index f1eaae7eb..000000000 --- a/changelog/4592.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix handling of ``collect_ignore`` via parent ``conftest.py``. diff --git a/changelog/4700.bugfix.rst b/changelog/4700.bugfix.rst deleted file mode 100644 index 3f8acb876..000000000 --- a/changelog/4700.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix regression where ``setUpClass`` would always be called in subclasses even if all tests -were skipped by a ``unittest.skip()`` decorator applied in the subclass. diff --git a/changelog/4709.doc.rst b/changelog/4709.doc.rst deleted file mode 100644 index 5f21728f6..000000000 --- a/changelog/4709.doc.rst +++ /dev/null @@ -1,2 +0,0 @@ -Document how to customize test failure messages when using -``pytest.warns``. diff --git a/changelog/4739.bugfix.rst b/changelog/4739.bugfix.rst deleted file mode 100644 index dcd44d3fa..000000000 --- a/changelog/4739.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix ``parametrize(... ids=)`` when the function returns non-strings. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 96807c438..62cf5c783 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-4.2.1 release-4.2.0 release-4.1.1 release-4.1.0 diff --git a/doc/en/announce/release-4.2.1.rst b/doc/en/announce/release-4.2.1.rst new file mode 100644 index 000000000..5aec022df --- /dev/null +++ b/doc/en/announce/release-4.2.1.rst @@ -0,0 +1,30 @@ +pytest-4.2.1 +======================================= + +pytest 4.2.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 +* Arel Cordero +* Bruno Oliveira +* Daniel Hahler +* Holger Kohr +* Kevin J. Foley +* Nick Murphy +* Paweł Stradomski +* Raphael Pierzina +* Ronny Pfannschmidt +* Sam Brightman +* Thomas Hisch +* Zac Hatfield-Dodds + + +Happy testing, +The pytest Development Team diff --git a/doc/en/assert.rst b/doc/en/assert.rst index 110d00738..186e12853 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -205,8 +205,8 @@ Special comparisons are done for a number of cases: See the :ref:`reporting demo ` for many more examples. -Defining your own assertion comparison ----------------------------------------------- +Defining your own explanation for failed assertions +--------------------------------------------------- It is possible to add your own detailed explanations by implementing the ``pytest_assertrepr_compare`` hook. diff --git a/doc/en/reference.rst b/doc/en/reference.rst index c1e22f854..24d5f7975 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -1042,6 +1042,20 @@ passed multiple times. The expected format is ``name=value``. For example:: This tells pytest to ignore deprecation warnings and turn all other warnings into errors. For more information please refer to :ref:`warnings`. +.. confval:: junit_family + + .. versionadded:: 4.2 + + Configures the format of the generated JUnit XML file. The possible options are: + + * ``xunit1`` (or ``legacy``): produces old style output, compatible with the xunit 1.0 format. **This is the default**. + * ``xunit2``: produces `xunit 2.0 style output `__, + which should be more compatible with latest Jenkins versions. + + .. code-block:: ini + + [pytest] + junit_family = xunit2 .. confval:: junit_suite_name diff --git a/setup.py b/setup.py index 696fc4028..b286a4f20 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,8 @@ INSTALL_REQUIRES = [ "six>=1.10.0", "setuptools", "attrs>=17.4.0", - "more-itertools>=4.0.0", + 'more-itertools>=4.0.0,<6.0.0;python_version<="2.7"', + 'more-itertools>=4.0.0;python_version>"2.7"', "atomicwrites>=1.0", 'funcsigs;python_version<"3.0"', 'pathlib2>=2.2.0;python_version<"3.6"', diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index c6ebe8ad8..4258032b4 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -408,7 +408,10 @@ class PytestPluginManager(PluginManager): continue conftestpath = parent.join("conftest.py") if conftestpath.isfile(): - mod = self._importconftest(conftestpath) + # Use realpath to avoid loading the same conftest twice + # with build systems that create build directories containing + # symlinks to actual files. + mod = self._importconftest(conftestpath.realpath()) clist.append(mod) self._dirpath2confmods[directory] = clist return clist diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 7a1060ae0..6b401aa0b 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -75,6 +75,7 @@ class pytestPDB(object): _config = None _pdb_cls = pdb.Pdb _saved = [] + _recursive_debug = 0 @classmethod def _init_pdb(cls, *args, **kwargs): @@ -87,29 +88,37 @@ class pytestPDB(object): capman.suspend_global_capture(in_=True) tw = _pytest.config.create_terminal_writer(cls._config) tw.line() - # Handle header similar to pdb.set_trace in py37+. - header = kwargs.pop("header", None) - if header is not None: - tw.sep(">", header) - elif capman and capman.is_globally_capturing(): - tw.sep(">", "PDB set_trace (IO-capturing turned off)") - else: - tw.sep(">", "PDB set_trace") + if cls._recursive_debug == 0: + # Handle header similar to pdb.set_trace in py37+. + header = kwargs.pop("header", None) + if header is not None: + tw.sep(">", header) + elif capman and capman.is_globally_capturing(): + tw.sep(">", "PDB set_trace (IO-capturing turned off)") + else: + tw.sep(">", "PDB set_trace") class _PdbWrapper(cls._pdb_cls, object): _pytest_capman = capman _continued = False + def do_debug(self, arg): + cls._recursive_debug += 1 + ret = super(_PdbWrapper, self).do_debug(arg) + cls._recursive_debug -= 1 + return ret + def do_continue(self, arg): ret = super(_PdbWrapper, self).do_continue(arg) if self._pytest_capman: tw = _pytest.config.create_terminal_writer(cls._config) tw.line() - if self._pytest_capman.is_globally_capturing(): - tw.sep(">", "PDB continue (IO-capturing resumed)") - else: - tw.sep(">", "PDB continue") - self._pytest_capman.resume_global_capture() + if cls._recursive_debug == 0: + if self._pytest_capman.is_globally_capturing(): + tw.sep(">", "PDB continue (IO-capturing resumed)") + else: + tw.sep(">", "PDB continue") + self._pytest_capman.resume_global_capture() cls._pluginmanager.hook.pytest_leave_pdb( config=cls._config, pdb=self ) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 9da8f1609..b2ad9aae3 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -4,6 +4,7 @@ from __future__ import print_function import functools import inspect +import itertools import sys import warnings from collections import defaultdict @@ -13,7 +14,6 @@ from collections import OrderedDict import attr import py import six -from more_itertools import flatten import _pytest from _pytest import nodes @@ -1109,7 +1109,7 @@ class FixtureManager(object): argnames = getfuncargnames(func, cls=cls) else: argnames = () - usefixtures = flatten( + usefixtures = itertools.chain.from_iterable( mark.args for mark in node.iter_markers(name="usefixtures") ) initialnames = tuple(usefixtures) + argnames diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index ba0acd269..343d4dd1c 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -435,13 +435,6 @@ class LoggingPlugin(object): # terminal reporter is disabled e.g. by pytest-xdist. return - # FIXME don't set verbosity level and derived attributes of - # terminalwriter directly - terminal_reporter.verbosity = config.option.verbose - terminal_reporter.showheader = terminal_reporter.verbosity >= 0 - terminal_reporter.showfspath = terminal_reporter.verbosity >= 0 - terminal_reporter.showlongtestinfo = terminal_reporter.verbosity > 0 - capture_manager = config.pluginmanager.get_plugin("capturemanager") # if capturemanager plugin is disabled, live logging still works. log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index bf35a4823..b49de86fb 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -571,19 +571,9 @@ class Session(nodes.FSCollector): if argpath.check(dir=1): assert not names, "invalid arg %r" % (arg,) - if six.PY2: - - def filter_(f): - return f.check(file=1) and not f.strpath.endswith("*.pyc") - - else: - - def filter_(f): - return f.check(file=1) - seen_dirs = set() for path in argpath.visit( - fil=filter_, rec=self._recurse, bf=True, sort=True + fil=self._visit_filter, rec=self._recurse, bf=True, sort=True ): dirpath = path.dirpath() if dirpath not in seen_dirs: @@ -613,7 +603,7 @@ class Session(nodes.FSCollector): col = self._node_cache[argpath] else: collect_root = self._pkg_roots.get(argpath.dirname, self) - col = collect_root._collectfile(argpath) + col = collect_root._collectfile(argpath, handle_dupes=False) if col: self._node_cache[argpath] = col m = self.matchnodes(col, names) @@ -658,6 +648,18 @@ class Session(nodes.FSCollector): ihook.pytest_collect_directory(path=dirpath, parent=self) return True + if six.PY2: + + @staticmethod + def _visit_filter(f): + return f.check(file=1) and not f.strpath.endswith("*.pyc") + + else: + + @staticmethod + def _visit_filter(f): + return f.check(file=1) + def _tryconvertpyarg(self, x): """Convert a dotted module name to path.""" try: diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 26859e660..fae243a50 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -81,7 +81,11 @@ class LsofFdLeakChecker(object): def _exec_lsof(self): pid = os.getpid() - return subprocess.check_output(("lsof", "-Ffn0", "-p", str(pid))).decode() + # py3: use subprocess.DEVNULL directly. + with open(os.devnull, "wb") as devnull: + return subprocess.check_output( + ("lsof", "-Ffn0", "-p", str(pid)), stderr=devnull + ).decode() def _parse_lsof_output(self, out): def isopen(line): diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index f4f8b3dd2..b2e418030 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -222,12 +222,9 @@ class TerminalReporter(object): import _pytest.config self.config = config - self.verbosity = self.config.option.verbose - self.showheader = self.verbosity >= 0 - self.showfspath = self.verbosity >= 0 - self.showlongtestinfo = self.verbosity > 0 self._numcollected = 0 self._session = None + self._showfspath = None self.stats = {} self.startdir = py.path.local() @@ -255,6 +252,28 @@ class TerminalReporter(object): return False return self.config.getini("console_output_style") in ("progress", "count") + @property + def verbosity(self): + return self.config.option.verbose + + @property + def showheader(self): + return self.verbosity >= 0 + + @property + def showfspath(self): + if self._showfspath is None: + return self.verbosity >= 0 + return self._showfspath + + @showfspath.setter + def showfspath(self, value): + self._showfspath = value + + @property + def showlongtestinfo(self): + return self.verbosity > 0 + def hasopt(self, char): char = {"xfailed": "x", "skipped": "s"}.get(char, char) return char in self.reportchars diff --git a/testing/test_collection.py b/testing/test_collection.py index 3716cacd1..3eb398b9f 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1177,3 +1177,32 @@ def test_collectignore_via_conftest(testdir, monkeypatch): result = testdir.runpytest() assert result.ret == EXIT_NOTESTSCOLLECTED + + +def test_collect_pkg_init_and_file_in_args(testdir): + subdir = testdir.mkdir("sub") + init = subdir.ensure("__init__.py") + init.write("def test_init(): pass") + p = subdir.ensure("test_file.py") + p.write("def test_file(): pass") + + # NOTE: without "-o python_files=*.py" this collects test_file.py twice. + # This changed/broke with "Add package scoped fixtures #2283" (2b1410895) + # initially (causing a RecursionError). + result = testdir.runpytest("-v", str(init), str(p)) + result.stdout.fnmatch_lines( + [ + "sub/test_file.py::test_file PASSED*", + "sub/test_file.py::test_file PASSED*", + "*2 passed in*", + ] + ) + + result = testdir.runpytest("-v", "-o", "python_files=*.py", str(init), str(p)) + result.stdout.fnmatch_lines( + [ + "sub/__init__.py::test_init PASSED*", + "sub/test_file.py::test_file PASSED*", + "*2 passed in*", + ] + ) diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 2b66d8fa7..ac091fed8 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -244,6 +244,42 @@ def test_conftest_symlink(testdir): assert result.ret == EXIT_OK +@pytest.mark.skipif( + not hasattr(py.path.local, "mksymlinkto"), + reason="symlink not available on this platform", +) +def test_conftest_symlink_files(testdir): + """Check conftest.py loading when running in directory with symlinks.""" + real = testdir.tmpdir.mkdir("real") + source = { + "app/test_foo.py": "def test1(fixture): pass", + "app/__init__.py": "", + "app/conftest.py": textwrap.dedent( + """ + import pytest + + print("conftest_loaded") + + @pytest.fixture + def fixture(): + print("fixture_used") + """ + ), + } + testdir.makepyfile(**{"real/%s" % k: v for k, v in source.items()}) + + # Create a build directory that contains symlinks to actual files + # but doesn't symlink actual directories. + build = testdir.tmpdir.mkdir("build") + build.mkdir("app") + for f in source: + build.join(f).mksymlinkto(real.join(f)) + build.chdir() + result = testdir.runpytest("-vs", "app/test_foo.py") + result.stdout.fnmatch_lines(["*conftest_loaded*", "PASSED"]) + assert result.ret == EXIT_OK + + def test_no_conftest(testdir): testdir.makeconftest("assert 0") result = testdir.runpytest("--noconftest") diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 49fbbad72..43d640614 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -513,6 +513,76 @@ class TestPDB(object): assert "1 failed" in rest self.flush(child) + def test_pdb_interaction_continue_recursive(self, testdir): + p1 = testdir.makepyfile( + mytest=""" + import pdb + import pytest + + count_continue = 0 + + # Simulates pdbpp, which injects Pdb into do_debug, and uses + # self.__class__ in do_continue. + class CustomPdb(pdb.Pdb, object): + def do_debug(self, arg): + import sys + import types + + newglobals = { + 'Pdb': self.__class__, # NOTE: different with pdb.Pdb + 'sys': sys, + } + if sys.version_info < (3, ): + do_debug_func = pdb.Pdb.do_debug.im_func + else: + do_debug_func = pdb.Pdb.do_debug + + orig_do_debug = types.FunctionType( + do_debug_func.__code__, newglobals, + do_debug_func.__name__, do_debug_func.__defaults__, + ) + return orig_do_debug(self, arg) + do_debug.__doc__ = pdb.Pdb.do_debug.__doc__ + + def do_continue(self, *args, **kwargs): + global count_continue + count_continue += 1 + return super(CustomPdb, self).do_continue(*args, **kwargs) + + def foo(): + print("print_from_foo") + + def test_1(): + i = 0 + print("hello17") + pytest.set_trace() + x = 3 + print("hello18") + + assert count_continue == 2, "unexpected_failure: %d != 2" % count_continue + pytest.fail("expected_failure") + """ + ) + child = testdir.spawn_pytest("--pdbcls=mytest:CustomPdb %s" % str(p1)) + child.expect(r"PDB set_trace \(IO-capturing turned off\)") + child.expect(r"\n\(Pdb") + child.sendline("debug foo()") + child.expect("ENTERING RECURSIVE DEBUGGER") + child.expect(r"\n\(\(Pdb") + child.sendline("c") + child.expect("LEAVING RECURSIVE DEBUGGER") + assert b"PDB continue" not in child.before + assert b"print_from_foo" in child.before + child.sendline("c") + child.expect(r"PDB continue \(IO-capturing resumed\)") + rest = child.read().decode("utf8") + assert "hello17" in rest # out is captured + assert "hello18" in rest # out is captured + assert "1 failed" in rest + assert "Failed: expected_failure" in rest + assert "AssertionError: unexpected_failure" not in rest + self.flush(child) + def test_pdb_without_capture(self, testdir): p1 = testdir.makepyfile( """ diff --git a/tox.ini b/tox.ini index 487fe40b1..87d419890 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] isolated_build = True -minversion = 3.3 +minversion = 3.5.3 distshare = {homedir}/.tox/distshare # make sure to update environment list in travis.yml and appveyor.yml envlist = @@ -23,10 +23,11 @@ commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {env:_PYTEST_TOX_ARGS:} {posargs} coverage: coverage combine coverage: coverage report -passenv = USER USERNAME COVERAGE_* TRAVIS +passenv = USER USERNAME COVERAGE_* TRAVIS PYTEST_ADDOPTS setenv = _PYTEST_TOX_ARGS=--lsof - # configuration if a user runs tox with a "coverage" factor, for example "tox -e py37-coverage" + # Configuration to run with coverage similar to Travis/Appveyor, e.g. + # "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 @@ -55,8 +56,8 @@ commands = pre-commit run --all-files --show-diff-on-failure [testenv:py27-pexpect] platform = linux|darwin deps = + {[testenv]deps} pexpect - {env:_PYTEST_TOX_EXTRA_DEP:} commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/test_pdb.py testing/test_terminal.py testing/test_unittest.py} @@ -68,8 +69,8 @@ commands = {[testenv:py27-pexpect]commands} [testenv:py27-nobyte] extras = testing deps = + {[testenv]deps} pytest-xdist>=1.13 - {env:_PYTEST_TOX_EXTRA_DEP:} distribute = true setenv = {[testenv]setenv} @@ -79,8 +80,8 @@ commands = [testenv:py27-trial] deps = + {[testenv]deps} twisted - {env:_PYTEST_TOX_EXTRA_DEP:} commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/test_unittest.py} @@ -90,8 +91,8 @@ commands = {[testenv:py27-trial]commands} [testenv:py27-numpy] deps = + {[testenv]deps} numpy - {env:_PYTEST_TOX_EXTRA_DEP:} commands= {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/python/approx.py} @@ -103,11 +104,13 @@ commands = {[testenv:py27-numpy]commands} setenv= {[testenv]setenv} _PYTEST_SETUP_SKIP_PLUGGY_DEP=1 - # 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 +deps = + {[testenv]deps} + git+https://github.com/pytest-dev/pluggy.git@master [testenv:py37-pluggymaster] setenv = {[testenv:py27-pluggymaster]setenv} +deps = {[testenv:py27-pluggymaster]deps} [testenv:docs] basepython = python3 @@ -123,8 +126,8 @@ commands = basepython = python3 skipsdist = True deps = + {[testenv]deps} PyYAML - {env:_PYTEST_TOX_EXTRA_DEP:} commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest doc/en {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest