Hello community, here is the log from the commit of package python-pytest-xdist for openSUSE:Factory checked in at 2019-02-24 17:04:27 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pytest-xdist (Old) and /work/SRC/openSUSE:Factory/.python-pytest-xdist.new.28833 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pytest-xdist" Sun Feb 24 17:04:27 2019 rev:8 rq:674153 version:1.26.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pytest-xdist/python-pytest-xdist.changes 2018-11-08 09:39:23.725736040 +0100 +++ /work/SRC/openSUSE:Factory/.python-pytest-xdist.new.28833/python-pytest-xdist.changes 2019-02-24 17:04:34.976646573 +0100 @@ -1,0 +2,13 @@ +Tue Feb 12 12:57:15 UTC 2019 - Tomáš Chvátal <tchva...@suse.com> + +- Update to 1.26.1: + * #406: Do not implement deprecated pytest_logwarning hook in pytest versions where it is deprecated. + * #376: The current directory is no longer added sys.path for local workers, only for remote connections. + * #379: Warning attributes are checked to make sure they can be dumped prior to serializing the warning for submission to the master node. + * #372: Pytest versions older than 3.6 are no longer supported. + * #373: Node setup information is hidden when pytest is run in quiet mode to reduce noise on many-core machines. + * #388: mainargv is made available in workerinput from the host's sys.argv. + * #384: pytest 4.1 support: ExceptionInfo API changes. + * #390: pytest 4.1 support: pytest_logwarning hook removed. + +------------------------------------------------------------------- Old: ---- pytest-xdist-1.23.2.tar.gz New: ---- pytest-xdist-1.26.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pytest-xdist.spec ++++++ --- /var/tmp/diff_new_pack.m7q3p1/_old 2019-02-24 17:04:35.768646196 +0100 +++ /var/tmp/diff_new_pack.m7q3p1/_new 2019-02-24 17:04:35.768646196 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-pytest-xdist # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,18 +18,23 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-pytest-xdist -Version: 1.23.2 +Version: 1.26.1 Release: 0 Summary: Distributed testing and loop-on-failing for py.test License: MIT Group: Development/Languages/Python -Url: https://bitbucket.org/pytest-dev/pytest-xdist +Url: https://github.com/pytest-dev/pytest-xdist Source0: https://files.pythonhosted.org/packages/source/p/pytest-xdist/pytest-xdist-%{version}.tar.gz +BuildRequires: %{python_module execnet >= 1.1} +BuildRequires: %{python_module filelock} +BuildRequires: %{python_module pytest >= 3.6.0} +BuildRequires: %{python_module pytest-forked} BuildRequires: %{python_module setuptools_scm} +BuildRequires: %{python_module six} BuildRequires: fdupes BuildRequires: python-rpm-macros Requires: python-execnet >= 1.1 -Requires: python-pytest >= 3.0.0 +Requires: python-pytest >= 3.6.0 Requires: python-pytest-forked Requires: python-six BuildArch: noarch @@ -70,6 +75,9 @@ %python_install %python_expand %fdupes -s %{buildroot}%{$python_sitelib} +%check +%python_expand PYTHONPATH=%{buildroot}%{$python_sitelib} py.test-%{$python_bin_suffix} -v + %files %{python_files} %doc CHANGELOG.rst ISSUES.txt README.rst %license LICENSE ++++++ pytest-xdist-1.23.2.tar.gz -> pytest-xdist-1.26.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/.travis.yml new/pytest-xdist-1.26.1/.travis.yml --- old/pytest-xdist-1.23.2/.travis.yml 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/.travis.yml 2019-01-28 21:48:04.000000000 +0100 @@ -11,36 +11,36 @@ email: - pytest-com...@python.org -python: -- '2.7' -- '3.4' -- '3.5' -- '3.6' -env: -- TOXENV=py-pytest30 -- TOXENV=py-pytest31 -- TOXENV=py-pytest32 -- TOXENV=py-pytest33 -- TOXENV=py-pytest36 -- TOXENV=py-pytest38 - -install: pip install tox setuptools_scm +install: + - pip install -U pip + - pip install tox setuptools_scm script: tox stages: -- linting +- baseline - test - name: deploy if: repo = pytest-dev/pytest-xdist AND tag IS present jobs: include: - - stage: linting + - stage: baseline python: '3.6' - script: - - tox -e linting + env: TOXENV=linting + - python: '3.6' + env: TOXENV=py36-pytestlatest + - python: '2.7' + env: TOXENV=py27-pytestlatest + - stage: test - # python x env above are already included into this stage + python: "3.4" + env: TOXENV=py34-pytestlatest + - python: "3.5" + env: TOXENV=py35-pytestlatest + - python: "3.7" + env: TOXENV=py37-pytestlatest + sudo: required + dist: xenial - python: "2.7" env: TOXENV=py27-pytestmaster - python: "2.7" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/CHANGELOG.rst new/pytest-xdist-1.26.1/CHANGELOG.rst --- old/pytest-xdist-1.23.2/CHANGELOG.rst 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/CHANGELOG.rst 2019-01-28 21:48:04.000000000 +0100 @@ -1,3 +1,85 @@ +pytest-xdist 1.26.1 (2019-01-28) +================================ + +Bug Fixes +--------- + +- `#406 <https://github.com/pytest-dev/pytest-xdist/issues/406>`_: Do not implement deprecated ``pytest_logwarning`` hook in pytest versions where it is deprecated. + + +pytest-xdist 1.26.0 (2019-01-11) +================================ + +Features +-------- + +- `#376 <https://github.com/pytest-dev/pytest-xdist/issues/376>`_: The current directory is no longer added ``sys.path`` for local workers, only for remote connections. + + This behavior is surprising because it makes xdist runs and non-xdist runs to potentially behave differently. + + +Bug Fixes +--------- + +- `#379 <https://github.com/pytest-dev/pytest-xdist/issues/379>`_: Warning attributes are checked to make sure they can be dumped prior to + serializing the warning for submission to the master node. + + +pytest-xdist 1.25.0 (2018-12-12) +================================ + +Deprecations and Removals +------------------------- + +- `#372 <https://github.com/pytest-dev/pytest-xdist/issues/372>`_: Pytest versions older than 3.6 are no longer supported. + + +Features +-------- + +- `#373 <https://github.com/pytest-dev/pytest-xdist/issues/373>`_: Node setup information is hidden when pytest is run in quiet mode to reduce noise on many-core machines. + +- `#388 <https://github.com/pytest-dev/pytest-xdist/issues/388>`_: ``mainargv`` is made available in ``workerinput`` from the host's ``sys.argv``. + + This can be used via ``request.config.workerinput["mainargv"]``. + + +Bug Fixes +--------- + +- `#332 <https://github.com/pytest-dev/pytest-xdist/issues/332>`_: Fix report of module-level skips (``pytest.skip(reason, allow_module_level=True)``). + +- `#378 <https://github.com/pytest-dev/pytest-xdist/issues/378>`_: Fix support for gevent monkeypatching + +- `#384 <https://github.com/pytest-dev/pytest-xdist/issues/384>`_: pytest 4.1 support: ``ExceptionInfo`` API changes. + +- `#390 <https://github.com/pytest-dev/pytest-xdist/issues/390>`_: pytest 4.1 support: ``pytest_logwarning`` hook removed. + + +pytest-xdist 1.24.1 (2018-11-09) +================================ + +Bug Fixes +--------- + +- `#349 <https://github.com/pytest-dev/pytest-xdist/issues/349>`_: Correctly handle warnings created with arguments that can't be serialized during the transfer from workers to master node. + + +pytest-xdist 1.24.0 (2018-10-18) +================================ + +Features +-------- + +- `#337 <https://github.com/pytest-dev/pytest-xdist/issues/337>`_: New ``--maxprocesses`` command-line option that limits the maximum number of workers when using ``--numprocesses=auto``. + + +Bug Fixes +--------- + +- `#351 <https://github.com/pytest-dev/pytest-xdist/issues/351>`_: Fix scheduling deadlock in case of inter-test locking. + + pytest-xdist 1.23.2 (2018-09-28) ================================ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/PKG-INFO new/pytest-xdist-1.26.1/PKG-INFO --- old/pytest-xdist-1.23.2/PKG-INFO 2018-09-28 12:29:23.000000000 +0200 +++ new/pytest-xdist-1.26.1/PKG-INFO 2019-01-28 21:48:22.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: pytest-xdist -Version: 1.23.2 +Version: 1.26.1 Summary: pytest xdist plugin for distributed testing and loop-on-failing modes Home-page: https://github.com/pytest-dev/pytest-xdist Author: holger krekel and contributors @@ -231,6 +231,12 @@ The information about the worker_id in a test is stored in the ``TestReport`` as well, under the ``worker_id`` attribute. + Acessing ``sys.argv`` from the master node in workers + ----------------------------------------------------- + + To access the ``sys.argv`` passed to the command-line of the master node, use + ``request.config.workerinput["mainargv"]``. + Specifying test exec environments in an ini file ------------------------------------------------ @@ -296,4 +302,5 @@ Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/README.rst new/pytest-xdist-1.26.1/README.rst --- old/pytest-xdist-1.23.2/README.rst 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/README.rst 2019-01-28 21:48:04.000000000 +0100 @@ -223,6 +223,12 @@ The information about the worker_id in a test is stored in the ``TestReport`` as well, under the ``worker_id`` attribute. +Acessing ``sys.argv`` from the master node in workers +----------------------------------------------------- + +To access the ``sys.argv`` passed to the command-line of the master node, use +``request.config.workerinput["mainargv"]``. + Specifying test exec environments in an ini file ------------------------------------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/appveyor.yml new/pytest-xdist-1.26.1/appveyor.yml --- old/pytest-xdist-1.23.2/appveyor.yml 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/appveyor.yml 2019-01-28 21:48:04.000000000 +0100 @@ -1,22 +1,23 @@ environment: matrix: - # note: please use "tox --listenvs" to populate the build matrix - - TOXENV: "py27-pytest33" - - TOXENV: "py34-pytest33" - - TOXENV: "py35-pytest33" - - TOXENV: "py36-pytest33" - - TOXENV: "py36-pytest36" - - TOXENV: "py36-pytest38" - - TOXENV: "py27-pytest33-pexpect" - - TOXENV: "py36-pytest33-pexpect" + - TOXENV: "py27-pytestlatest" + - TOXENV: "py34-pytestlatest" + - TOXENV: "py35-pytestlatest" + - TOXENV: "py36-pytestlatest" + - TOXENV: "py37-pytestlatest" + - TOXENV: "py27-pytestmaster" + - TOXENV: "py36-pytestmaster" + - TOXENV: "py27-pytestfeatures" + - TOXENV: "py36-pytestfeatures" install: - - C:\Python36\python -m pip install -U tox setuptools_scm pip + - C:\Python37\python -m pip install -U pip + - C:\Python37\python -m pip install -U tox setuptools_scm build: false # Not a C# project, build stuff at the test step instead. test_script: - - C:\Python36\python -m tox + - C:\Python37\python -m tox # We don't deploy anything on tags with AppVeyor, we use Travis instead, so we # might as well save resources diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/pytest_xdist.egg-info/PKG-INFO new/pytest-xdist-1.26.1/pytest_xdist.egg-info/PKG-INFO --- old/pytest-xdist-1.23.2/pytest_xdist.egg-info/PKG-INFO 2018-09-28 12:29:22.000000000 +0200 +++ new/pytest-xdist-1.26.1/pytest_xdist.egg-info/PKG-INFO 2019-01-28 21:48:22.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: pytest-xdist -Version: 1.23.2 +Version: 1.26.1 Summary: pytest xdist plugin for distributed testing and loop-on-failing modes Home-page: https://github.com/pytest-dev/pytest-xdist Author: holger krekel and contributors @@ -231,6 +231,12 @@ The information about the worker_id in a test is stored in the ``TestReport`` as well, under the ``worker_id`` attribute. + Acessing ``sys.argv`` from the master node in workers + ----------------------------------------------------- + + To access the ``sys.argv`` passed to the command-line of the master node, use + ``request.config.workerinput["mainargv"]``. + Specifying test exec environments in an ini file ------------------------------------------------ @@ -296,4 +302,5 @@ Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/pytest_xdist.egg-info/SOURCES.txt new/pytest-xdist-1.26.1/pytest_xdist.egg-info/SOURCES.txt --- old/pytest-xdist-1.23.2/pytest_xdist.egg-info/SOURCES.txt 2018-09-28 12:29:22.000000000 +0200 +++ new/pytest-xdist-1.26.1/pytest_xdist.egg-info/SOURCES.txt 2019-01-28 21:48:22.000000000 +0100 @@ -48,6 +48,6 @@ xdist/workermanage.py xdist/scheduler/__init__.py xdist/scheduler/each.py -xdist/scheduler/filescope.py xdist/scheduler/load.py +xdist/scheduler/loadfile.py xdist/scheduler/loadscope.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/pytest_xdist.egg-info/requires.txt new/pytest-xdist-1.26.1/pytest_xdist.egg-info/requires.txt --- old/pytest-xdist-1.23.2/pytest_xdist.egg-info/requires.txt 2018-09-28 12:29:22.000000000 +0200 +++ new/pytest-xdist-1.26.1/pytest_xdist.egg-info/requires.txt 2019-01-28 21:48:22.000000000 +0100 @@ -1,4 +1,4 @@ execnet>=1.1 -pytest>=3.0.0 +pytest>=3.6.0 pytest-forked six diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/setup.py new/pytest-xdist-1.26.1/setup.py --- old/pytest-xdist-1.23.2/setup.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/setup.py 2019-01-28 21:48:04.000000000 +0100 @@ -1,14 +1,17 @@ from setuptools import setup, find_packages -install_requires = ["execnet>=1.1", "pytest>=3.0.0", "pytest-forked", "six"] +install_requires = ["execnet>=1.1", "pytest>=3.6.0", "pytest-forked", "six"] +with open("README.rst") as f: + long_description = f.read() + setup( name="pytest-xdist", use_scm_version={"write_to": "xdist/_version.py"}, description="pytest xdist plugin for distributed testing" " and loop-on-failing modes", - long_description=open("README.rst").read(), + long_description=long_description, license="MIT", author="holger krekel and contributors", author_email="pytest-...@python.org,hol...@merlinux.eu", @@ -40,5 +43,6 @@ "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", ], ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/testing/acceptance_test.py new/pytest-xdist-1.26.1/testing/acceptance_test.py --- old/pytest-xdist-1.23.2/testing/acceptance_test.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/testing/acceptance_test.py 2019-01-28 21:48:04.000000000 +0100 @@ -1,5 +1,6 @@ import os import re +import sys import textwrap import py @@ -358,6 +359,30 @@ class TestTerminalReporting: + @pytest.mark.parametrize("verbosity", ["", "-q", "-v"]) + def test_output_verbosity(self, testdir, verbosity): + testdir.makepyfile( + """ + def test_ok(): + pass + """ + ) + args = ["-n1"] + if verbosity: + args.append(verbosity) + result = testdir.runpytest(*args) + out = result.stdout.str() + if verbosity == "-v": + assert "scheduling tests" in out + assert "gw" in out + elif verbosity == "-q": + assert "scheduling tests" not in out + assert "gw" not in out + assert "bringing up nodes..." in out + else: + assert "scheduling tests" not in out + assert "gw" in out + def test_pass_skip_fail(self, testdir): testdir.makepyfile( """ @@ -400,64 +425,6 @@ ] ) - @pytest.mark.parametrize("n", ["-n0", "-n1"]) - @pytest.mark.parametrize("warn_type", ["pytest", "builtin"]) - def test_warnings(self, testdir, n, warn_type): - from pkg_resources import parse_version - - if parse_version(pytest.__version__) < parse_version("3.1"): - pytest.skip("pytest warnings requires >= 3.1") - - if warn_type == "builtin": - warn_code = """warnings.warn(UserWarning('this is a warning'))""" - elif warn_type == "pytest": - warn_code = """request.config.warn('', 'this is a warning', - fslocation=py.path.local())""" - else: - assert False - testdir.makepyfile( - """ - import warnings, py, pytest - - @pytest.mark.filterwarnings('ignore:config.warn has been deprecated') - def test_func(request): - {warn_code} - """.format( - warn_code=warn_code - ) - ) - result = testdir.runpytest(n) - result.stdout.fnmatch_lines(["*this is a warning*", "*1 passed, 1 warnings*"]) - - @pytest.mark.parametrize("n", ["-n0", "-n1"]) - def test_custom_subclass(self, testdir, n): - """Check that warning subclasses that don't honor the args attribute don't break - pytest-xdist (#344) - """ - from pkg_resources import parse_version - - if parse_version(pytest.__version__) < parse_version("3.1"): - pytest.skip("pytest warnings requires >= 3.1") - - testdir.makepyfile( - """ - import warnings, py, pytest - - class MyWarning(UserWarning): - - def __init__(self, p1, p2): - self.p1 = p1 - self.p2 = p2 - self.args = () - - def test_func(request): - warnings.warn(MyWarning("foo", 1)) - """ - ) - testdir.syspathinsert() - result = testdir.runpytest(n) - result.stdout.fnmatch_lines(["*MyWarning*", "*1 passed, 1 warnings*"]) - def test_logfinish_hook(self, testdir): """Ensure the pytest_runtest_logfinish hook is being properly handled""" from _pytest import hookspec @@ -523,9 +490,8 @@ name = "worker" else: name = "master" - f = open(name, "w") - f.write("xy") - f.close() + with open(name, "w") as f: + f.write("xy") # let's fail on the worker if name == "worker": raise ValueError(42) @@ -575,16 +541,15 @@ assert collected_file.read() == "collected = 3" -def test_funcarg_teardown_failure(testdir): +def test_fixture_teardown_failure(testdir): p = testdir.makepyfile( """ import pytest - @pytest.fixture + @pytest.fixture(scope="module") def myarg(request): - def teardown(val): - raise ValueError(val) - return request.cached_setup(setup=lambda: 42, teardown=teardown, - scope="module") + yield 42 + raise ValueError(42) + def test_hello(myarg): pass """ @@ -670,6 +635,11 @@ def test_issue34_pluginloading_in_subprocess(testdir): + import _pytest.hookspec + + if not hasattr(_pytest.hookspec, "pytest_namespace"): + pytest.skip("this pytest version no longer supports pytest_namespace()") + testdir.tmpdir.join("plugin123.py").write( textwrap.dedent( """ @@ -765,6 +735,108 @@ result.stdout.fnmatch_lines("*1 passed*") +class TestWarnings: + @pytest.mark.parametrize("n", ["-n0", "-n1"]) + @pytest.mark.parametrize("warn_type", ["pytest", "builtin"]) + def test_warnings(self, testdir, n, request, warn_type): + if warn_type == "builtin": + warn_code = """warnings.warn(UserWarning('this is a warning'))""" + elif warn_type == "pytest": + if not hasattr(request.config, "warn"): + pytest.skip("config.warn has been removed in pytest 4.1") + warn_code = """request.config.warn('', 'this is a warning', + fslocation=py.path.local())""" + else: + assert False + testdir.makepyfile( + """ + import warnings, py, pytest + + @pytest.mark.filterwarnings('ignore:config.warn has been deprecated') + def test_func(request): + {warn_code} + """.format( + warn_code=warn_code + ) + ) + result = testdir.runpytest(n) + result.stdout.fnmatch_lines(["*this is a warning*", "*1 passed, 1 warnings*"]) + + @pytest.mark.parametrize("n", ["-n0", "-n1"]) + def test_custom_subclass(self, testdir, n): + """Check that warning subclasses that don't honor the args attribute don't break + pytest-xdist (#344) + """ + testdir.makepyfile( + """ + import warnings, py, pytest + + class MyWarning(UserWarning): + + def __init__(self, p1, p2): + self.p1 = p1 + self.p2 = p2 + self.args = () + + def test_func(request): + warnings.warn(MyWarning("foo", 1)) + """ + ) + testdir.syspathinsert() + result = testdir.runpytest(n) + result.stdout.fnmatch_lines(["*MyWarning*", "*1 passed, 1 warnings*"]) + + @pytest.mark.parametrize("n", ["-n0", "-n1"]) + def test_unserializable_arguments(self, testdir, n): + """Check that warnings with unserializable arguments are handled correctly (#349).""" + testdir.makepyfile( + """ + import warnings, pytest + + def test_func(tmpdir): + fn = (tmpdir / 'foo.txt').ensure(file=1) + with fn.open('r') as f: + warnings.warn(UserWarning("foo", f)) + """ + ) + testdir.syspathinsert() + result = testdir.runpytest(n) + result.stdout.fnmatch_lines(["*UserWarning*foo.txt*", "*1 passed, 1 warnings*"]) + + @pytest.mark.parametrize("n", ["-n0", "-n1"]) + def test_unserializable_warning_details(self, testdir, n): + """Check that warnings with unserializable _WARNING_DETAILS are + handled correctly (#379). + """ + if sys.version_info[0] < 3: + # The issue is only present in Python 3 warnings + return + testdir.makepyfile( + """ + import warnings, pytest + import socket + import gc + def abuse_socket(): + s = socket.socket() + del s + + # Deliberately provoke a ResourceWarning for an unclosed socket. + # The socket itself will end up attached as a value in + # _WARNING_DETAIL. We need to test that it is not serialized + # (it can't be, so the test will fail if we try to). + @pytest.mark.filterwarnings('always') + def test_func(tmpdir): + abuse_socket() + gc.collect() + """ + ) + testdir.syspathinsert() + result = testdir.runpytest(n) + result.stdout.fnmatch_lines( + ["*ResourceWarning*unclosed*", "*1 passed, 1 warnings*"] + ) + + class TestNodeFailure: def test_load_single(self, testdir): f = testdir.makepyfile( @@ -1132,6 +1204,56 @@ assert c1 == c2 +class TestLocking: + _test_content = """ + class TestClassName%s(object): + + @classmethod + def setup_class(cls): + FILE_LOCK.acquire() + + @classmethod + def teardown_class(cls): + FILE_LOCK.release() + + def test_a(self): + pass + + def test_b(self): + pass + + def test_c(self): + pass + + """ + + test_file1 = """ + import filelock + + FILE_LOCK = filelock.FileLock("test.lock") + + """ + ( + (_test_content * 4) % ("A", "B", "C", "D") + ) + + @pytest.mark.parametrize("scope", ["each", "load", "loadscope", "loadfile", "no"]) + def test_single_file(self, testdir, scope): + testdir.makepyfile(test_a=self.test_file1) + result = testdir.runpytest("-n2", "--dist=%s" % scope, "-v") + result.assert_outcomes(passed=(12 if scope != "each" else 12 * 2)) + + @pytest.mark.parametrize("scope", ["each", "load", "loadscope", "loadfile", "no"]) + def test_multi_file(self, testdir, scope): + testdir.makepyfile( + test_a=self.test_file1, + test_b=self.test_file1, + test_c=self.test_file1, + test_d=self.test_file1, + ) + result = testdir.runpytest("-n2", "--dist=%s" % scope, "-v") + result.assert_outcomes(passed=(48 if scope != "each" else 48 * 2)) + + def parse_tests_and_workers_from_output(lines): result = [] for line in lines: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/testing/test_looponfail.py new/pytest-xdist-1.26.1/testing/test_looponfail.py --- old/pytest-xdist-1.23.2/testing/test_looponfail.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/testing/test_looponfail.py 2019-01-28 21:48:04.000000000 +0100 @@ -1,4 +1,7 @@ import py +import pytest +from pkg_resources import parse_version + from xdist.looponfail import RemoteControl from xdist.looponfail import StatRecorder @@ -214,7 +217,11 @@ assert "test_one" not in remotecontrol.failures[0] assert "test_two" in remotecontrol.failures[0] - @py.test.mark.xfail(py.test.__version__ >= "3.1", reason="broken by pytest 3.1+") + @pytest.mark.xfail( + parse_version(pytest.__version__) >= parse_version("3.1"), + reason="broken by pytest 3.1+", + strict=True, + ) def test_looponfail_removed_test(self, testdir): modcol = testdir.getmodulecol( """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/testing/test_plugin.py new/pytest-xdist-1.26.1/testing/test_plugin.py --- old/pytest-xdist-1.23.2/testing/test_plugin.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/testing/test_plugin.py 2019-01-28 21:48:04.000000000 +0100 @@ -25,6 +25,10 @@ check_options(config) assert config.option.dist == "load" assert config.option.tx == ["popen"] * 2 + config = testdir.parseconfigure("--numprocesses", "3", "--maxprocesses", "2") + check_options(config) + assert config.option.dist == "load" + assert config.option.tx == ["popen"] * 2 config = testdir.parseconfigure("-d") check_options(config) assert config.option.dist == "load" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/testing/test_remote.py new/pytest-xdist-1.26.1/testing/test_remote.py --- old/pytest-xdist-1.23.2/testing/test_remote.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/testing/test_remote.py 2019-01-28 21:48:04.000000000 +0100 @@ -1,6 +1,8 @@ import py import pprint import pytest +import sys + from xdist.workermanage import WorkerController, unserialize_report from xdist.remote import serialize_report import execnet @@ -151,7 +153,6 @@ for i in range(len(a_entries)): assert isinstance(rep_entries[i], ReprEntry) assert rep_entries[i].lines == a_entries[i].lines - assert rep_entries[i].localssep == a_entries[i].localssep assert rep_entries[i].reprfileloc.lineno == a_entries[i].reprfileloc.lineno assert ( rep_entries[i].reprfileloc.message == a_entries[i].reprfileloc.message @@ -292,14 +293,11 @@ ev = worker.popevent("workerfinished") assert "workeroutput" in ev.kwargs - @pytest.mark.skipif( - pytest.__version__ >= "3.0", reason="skip at module level illegal in pytest 3.0" - ) def test_remote_collect_skip(self, worker): worker.testdir.makepyfile( """ - import py - py.test.skip("hello") + import pytest + pytest.skip("hello", allow_module_level=True) """ ) worker.setup() @@ -307,10 +305,9 @@ assert not ev.kwargs ev = worker.popevent() assert ev.name == "collectreport" - ev = worker.popevent() - assert ev.name == "collectreport" rep = unserialize_report(ev.name, ev.kwargs["data"]) assert rep.skipped + assert rep.longrepr[2] == "Skipped: hello" ev = worker.popevent("collectionfinish") assert not ev.kwargs["ids"] @@ -400,3 +397,66 @@ ) result = testdir.runpytest("-n2", "--max-worker-restart=0") assert result.ret == 0 + + +def test_remote_inner_argv(testdir): + """Test/document the behavior due to execnet using `python -c`.""" + testdir.makepyfile( + """ + import sys + + def test_argv(): + assert sys.argv == ["-c"] + """ + ) + result = testdir.runpytest("-n1") + assert result.ret == 0 + + +def test_remote_mainargv(testdir): + outer_argv = sys.argv + + testdir.makepyfile( + """ + def test_mainargv(request): + assert request.config.workerinput["mainargv"] == {!r} + """.format( + outer_argv + ) + ) + result = testdir.runpytest("-n1") + assert result.ret == 0 + + +def test_remote_usage_prog(testdir, request): + if not hasattr(request.config._parser, "prog"): + pytest.skip("prog not available in config parser") + testdir.makeconftest( + """ + import pytest + + config_parser = None + + @pytest.fixture + def get_config_parser(): + return config_parser + + def pytest_configure(config): + global config_parser + config_parser = config._parser + """ + ) + testdir.makepyfile( + """ + import sys + + def test(get_config_parser, request): + get_config_parser._getparser().error("my_usage_error") + """ + ) + + result = testdir.runpytest_subprocess("-n1") + assert result.ret == 1 + result.stdout.fnmatch_lines( + ["usage: pytest.py *", "pytest.py: error: my_usage_error"] + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/tox.ini new/pytest-xdist-1.26.1/tox.ini --- old/pytest-xdist-1.23.2/tox.ini 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/tox.ini 2019-01-28 21:48:04.000000000 +0100 @@ -1,33 +1,18 @@ [tox] -# if you change the envlist, please update .travis.yml file as well envlist= linting - py{27,34,35,36}-pytest{30,31,32,33,36,38} - py{27,36}-pytest36-pexpect + py{27,34,35,36,37}-pytestlatest py{27,36}-pytest{master,features} - [testenv] changedir=testing passenv = USER USERNAME deps = - pycmd - # to avoid .eggs - setuptools_scm - pytest30: pytest~=3.0.5 - pytest31: pytest~=3.1.0 - pytest32: pytest~=3.2.0 - pytest33: pytest~=3.3.0 - pytest36: pytest~=3.6.0 - pytest38: pytest~=3.8.0 + pytestlatest: pytest pytestmaster: git+https://github.com/pytest-dev/pytest.git@master pytestfeatures: git+https://github.com/pytest-dev/pytest.git@features - pexpect: pexpect -platform= - pexpect: linux|darwin + filelock commands= - # always clean to avoid code unmarshal mismatch on old python/pytest - py.cleanup -aq pytest {posargs} [testenv:linting] @@ -50,7 +35,7 @@ towncrier --version {posargs} --yes [pytest] -addopts = -rsfxX +addopts = -ra [flake8] max-line-length = 120 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/xdist/_version.py new/pytest-xdist-1.26.1/xdist/_version.py --- old/pytest-xdist-1.23.2/xdist/_version.py 2018-09-28 12:29:22.000000000 +0200 +++ new/pytest-xdist-1.26.1/xdist/_version.py 2019-01-28 21:48:22.000000000 +0100 @@ -1,4 +1,4 @@ # coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control -version = '1.23.2' +version = '1.26.1' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/xdist/dsession.py new/pytest-xdist-1.26.1/xdist/dsession.py --- old/pytest-xdist-1.23.2/xdist/dsession.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/xdist/dsession.py 2019-01-28 21:48:04.000000000 +0100 @@ -229,9 +229,10 @@ if self.terminal and not self.sched.has_pending: self.trdist.ensure_show_status() self.terminal.write_line("") - self.terminal.write_line( - "scheduling tests via %s" % (self.sched.__class__.__name__) - ) + if self.config.option.verbose > 0: + self.terminal.write_line( + "scheduling tests via %s" % (self.sched.__class__.__name__) + ) self.sched.schedule() def worker_logstart(self, node, nodeid, location): @@ -259,10 +260,10 @@ def worker_collectreport(self, node, rep): """Emitted when a node calls the pytest_collectreport hook. - Because we only need the report when there's a failure, as optimization - we only expect to receive failed reports from workers (#330). + Because we only need the report when there's a failure/skip, as optimization + we only expect to receive failed/skipped reports from workers (#330). """ - assert rep.failed + assert not rep.passed self._failed_worker_collectreport(node, rep) def worker_logwarning(self, message, code, nodeid, fslocation): @@ -344,8 +345,11 @@ self.rewrite(self.getstatus()) def getstatus(self): - parts = ["%s %s" % (spec.id, self._status[spec.id]) for spec in self._specs] - return " / ".join(parts) + if self.config.option.verbose >= 0: + parts = ["%s %s" % (spec.id, self._status[spec.id]) for spec in self._specs] + return " / ".join(parts) + else: + return "bringing up nodes..." def rewrite(self, line, newline=False): pline = line + " " * max(self._lastlen - len(line), 0) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/xdist/looponfail.py new/pytest-xdist-1.26.1/xdist/looponfail.py --- old/pytest-xdist-1.23.2/xdist/looponfail.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/xdist/looponfail.py 2019-01-28 21:48:04.000000000 +0100 @@ -32,7 +32,7 @@ if config.getoption("looponfail"): usepdb = config.getoption("usepdb") # a core option if usepdb: - raise pytest.UsageError("--pdb incompatible with --looponfail.") + raise pytest.UsageError("--pdb is incompatible with --looponfail.") looponfail_main(config) return 2 # looponfail only can get stop with ctrl-C anyway diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/xdist/plugin.py new/pytest-xdist-1.26.1/xdist/plugin.py --- old/pytest-xdist-1.23.2/xdist/plugin.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/xdist/plugin.py 2019-01-28 21:48:04.000000000 +0100 @@ -48,6 +48,14 @@ "host system", ) group.addoption( + "--maxprocesses", + dest="maxprocesses", + metavar="maxprocesses", + action="store", + type=int, + help="limit the maximum number of workers to process the tests when using --numprocesses=auto", + ) + group.addoption( "--max-worker-restart", "--max-slave-restart", action="store", @@ -118,12 +126,12 @@ ) parser.addini( "rsyncdirs", - "list of (relative) paths to be rsynced for" " remote distributed testing.", + "list of (relative) paths to be rsynced for remote distributed testing.", type="pathlist", ) parser.addini( "rsyncignore", - "list of (relative) glob-style paths to be ignored " "for rsyncing.", + "list of (relative) glob-style paths to be ignored for rsyncing.", type="pathlist", ) parser.addini( @@ -172,7 +180,10 @@ if config.option.numprocesses: if config.option.dist == "no": config.option.dist = "load" - config.option.tx = ["popen"] * config.option.numprocesses + numprocesses = config.option.numprocesses + if config.option.maxprocesses: + numprocesses = min(numprocesses, config.option.maxprocesses) + config.option.tx = ["popen"] * numprocesses if config.option.distload: config.option.dist = "load" val = config.getvalue diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/xdist/remote.py new/pytest-xdist-1.26.1/xdist/remote.py --- old/pytest-xdist-1.23.2/xdist/remote.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/xdist/remote.py 2019-01-28 21:48:04.000000000 +0100 @@ -12,6 +12,7 @@ import _pytest.hookspec import pytest +from execnet.gateway_base import dumps, DumpError class WorkerInteractor(object): @@ -109,19 +110,24 @@ self.sendevent("testreport", data=data) def pytest_collectreport(self, report): - # master only needs reports that failed, as optimization send only them instead (#330) - if report.failed: + # send only reports that have not passed to master as optimization (#330) + if not report.passed: data = serialize_report(report) self.sendevent("collectreport", data=data) - def pytest_logwarning(self, message, code, nodeid, fslocation): - self.sendevent( - "logwarning", - message=message, - code=code, - nodeid=nodeid, - fslocation=str(fslocation), - ) + # the pytest_logwarning hook was deprecated since pytest 4.0 + if hasattr( + _pytest.hookspec, "pytest_logwarning" + ) and not _pytest.hookspec.pytest_logwarning.pytest_spec.get("warn_on_impl"): + + def pytest_logwarning(self, message, code, nodeid, fslocation): + self.sendevent( + "logwarning", + message=message, + code=code, + nodeid=nodeid, + fslocation=str(fslocation), + ) # the pytest_warning_captured hook was introduced in pytest 3.8 if hasattr(_pytest.hookspec, "pytest_warning_captured"): @@ -181,8 +187,15 @@ if isinstance(warning_message.message, Warning): message_module = type(warning_message.message).__module__ message_class_name = type(warning_message.message).__name__ - message_args = warning_message.message.args message_str = str(warning_message.message) + # check now if we can serialize the warning arguments (#349) + # if not, we will just use the exception message on the master node + try: + dumps(warning_message.message.args) + except DumpError: + message_args = None + else: + message_args = warning_message.message.args else: message_str = warning_message.message message_module = None @@ -207,7 +220,15 @@ for attr_name in warning_message._WARNING_DETAILS: if attr_name in ("message", "category"): continue - result[attr_name] = getattr(warning_message, attr_name) + attr = getattr(warning_message, attr_name) + # Check if we can serialize the warning detail, marking `None` otherwise + # Note that we need to define the attr (even as `None`) to allow deserializing + try: + dumps(attr) + except DumpError: + result[attr_name] = repr(attr) + else: + result[attr_name] = attr return result @@ -234,24 +255,29 @@ config.option.dist = "no" config.option.distload = False config.option.numprocesses = None + config.option.maxprocesses = None config.args = args return config if __name__ == "__channelexec__": + import py + channel = channel # noqa - workerinput, args, option_dict = channel.receive() - importpath = os.getcwd() - sys.path.insert(0, importpath) # XXX only for remote situations - os.environ["PYTHONPATH"] = ( - importpath + os.pathsep + os.environ.get("PYTHONPATH", "") - ) + workerinput, args, option_dict, change_sys_path = channel.receive() + + if change_sys_path: + importpath = os.getcwd() + sys.path.insert(0, importpath) + os.environ["PYTHONPATH"] = ( + importpath + os.pathsep + os.environ.get("PYTHONPATH", "") + ) + os.environ["PYTEST_XDIST_WORKER"] = workerinput["workerid"] os.environ["PYTEST_XDIST_WORKER_COUNT"] = str(workerinput["workercount"]) - # os.environ['PYTHONPATH'] = importpath - import py config = remote_initconfig(option_dict, args) + config._parser.prog = os.path.basename(workerinput["mainargv"][0]) config.workerinput = workerinput config.workeroutput = {} # TODO: deprecated name, backward compatibility only. Remove it in future diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/xdist/scheduler/__init__.py new/pytest-xdist-1.26.1/xdist/scheduler/__init__.py --- old/pytest-xdist-1.23.2/xdist/scheduler/__init__.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/xdist/scheduler/__init__.py 2019-01-28 21:48:04.000000000 +0100 @@ -1,4 +1,4 @@ from xdist.scheduler.each import EachScheduling # noqa from xdist.scheduler.load import LoadScheduling # noqa +from xdist.scheduler.loadfile import LoadFileScheduling # noqa from xdist.scheduler.loadscope import LoadScopeScheduling # noqa -from xdist.scheduler.filescope import LoadFileScheduling # noqa diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/xdist/scheduler/each.py new/pytest-xdist-1.26.1/xdist/scheduler/each.py --- old/pytest-xdist-1.23.2/xdist/scheduler/each.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/xdist/scheduler/each.py 2019-01-28 21:48:04.000000000 +0100 @@ -126,6 +126,7 @@ if not pending: pending[:] = range(len(self.node2collection[node])) node.send_runtest_all() + node.shutdown() else: node.send_runtest_some(pending) self._started.append(node) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/xdist/scheduler/filescope.py new/pytest-xdist-1.26.1/xdist/scheduler/filescope.py --- old/pytest-xdist-1.23.2/xdist/scheduler/filescope.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/xdist/scheduler/filescope.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,52 +0,0 @@ -from . import LoadScopeScheduling -from py.log import Producer - - -class LoadFileScheduling(LoadScopeScheduling): - """Implement load scheduling across nodes, but grouping test test file. - - This distributes the tests collected across all nodes so each test is run - just once. All nodes collect and submit the list of tests and when all - collections are received it is verified they are identical collections. - Then the collection gets divided up in work units, grouped by test file, - and those work units get submitted to nodes. Whenever a node finishes an - item, it calls ``.mark_test_complete()`` which will trigger the scheduler - to assign more work units if the number of pending tests for the node falls - below a low-watermark. - - When created, ``numnodes`` defines how many nodes are expected to submit a - collection. This is used to know when all nodes have finished collection. - - This class behaves very much like LoadScopeScheduling, but with a file-level scope. - """ - - def __init__(self, config, log=None): - super(LoadFileScheduling, self).__init__(config, log) - if log is None: - self.log = Producer("loadfilesched") - else: - self.log = log.loadfilesched - - def _split_scope(self, nodeid): - """Determine the scope (grouping) of a nodeid. - - There are usually 3 cases for a nodeid:: - - example/loadsuite/test/test_beta.py::test_beta0 - example/loadsuite/test/test_delta.py::Delta1::test_delta0 - example/loadsuite/epsilon/__init__.py::epsilon.epsilon - - #. Function in a test module. - #. Method of a class in a test module. - #. Doctest in a function in a package. - - This function will group tests with the scope determined by splitting - the first ``::`` from the left. That is, test will be grouped in a - single work unit when they reside in the same file. - In the above example, scopes will be:: - - example/loadsuite/test/test_beta.py - example/loadsuite/test/test_delta.py - example/loadsuite/epsilon/__init__.py - """ - return nodeid.split("::", 1)[0] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/xdist/scheduler/load.py new/pytest-xdist-1.26.1/xdist/scheduler/load.py --- old/pytest-xdist-1.23.2/xdist/scheduler/load.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/xdist/scheduler/load.py 2019-01-28 21:48:04.000000000 +0100 @@ -178,6 +178,9 @@ return num_send = items_per_node_max - len(node_pending) self._send_tests(node, num_send) + else: + node.shutdown() + self.log("num items waiting for node:", len(self.pending)) def remove_node(self, node): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/xdist/scheduler/loadfile.py new/pytest-xdist-1.26.1/xdist/scheduler/loadfile.py --- old/pytest-xdist-1.23.2/xdist/scheduler/loadfile.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pytest-xdist-1.26.1/xdist/scheduler/loadfile.py 2019-01-28 21:48:04.000000000 +0100 @@ -0,0 +1,52 @@ +from .loadscope import LoadScopeScheduling +from py.log import Producer + + +class LoadFileScheduling(LoadScopeScheduling): + """Implement load scheduling across nodes, but grouping test test file. + + This distributes the tests collected across all nodes so each test is run + just once. All nodes collect and submit the list of tests and when all + collections are received it is verified they are identical collections. + Then the collection gets divided up in work units, grouped by test file, + and those work units get submitted to nodes. Whenever a node finishes an + item, it calls ``.mark_test_complete()`` which will trigger the scheduler + to assign more work units if the number of pending tests for the node falls + below a low-watermark. + + When created, ``numnodes`` defines how many nodes are expected to submit a + collection. This is used to know when all nodes have finished collection. + + This class behaves very much like LoadScopeScheduling, but with a file-level scope. + """ + + def __init__(self, config, log=None): + super(LoadFileScheduling, self).__init__(config, log) + if log is None: + self.log = Producer("loadfilesched") + else: + self.log = log.loadfilesched + + def _split_scope(self, nodeid): + """Determine the scope (grouping) of a nodeid. + + There are usually 3 cases for a nodeid:: + + example/loadsuite/test/test_beta.py::test_beta0 + example/loadsuite/test/test_delta.py::Delta1::test_delta0 + example/loadsuite/epsilon/__init__.py::epsilon.epsilon + + #. Function in a test module. + #. Method of a class in a test module. + #. Doctest in a function in a package. + + This function will group tests with the scope determined by splitting + the first ``::`` from the left. That is, test will be grouped in a + single work unit when they reside in the same file. + In the above example, scopes will be:: + + example/loadsuite/test/test_beta.py + example/loadsuite/test/test_delta.py + example/loadsuite/epsilon/__init__.py + """ + return nodeid.split("::", 1)[0] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/xdist/scheduler/loadscope.py new/pytest-xdist-1.26.1/xdist/scheduler/loadscope.py --- old/pytest-xdist-1.23.2/xdist/scheduler/loadscope.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/xdist/scheduler/loadscope.py 2019-01-28 21:48:04.000000000 +0100 @@ -187,7 +187,7 @@ break else: raise RuntimeError( - "Unable to identify crashitem on a workload with " "pending items" + "Unable to identify crashitem on a workload with pending items" ) # Made uncompleted work unit available again @@ -306,6 +306,7 @@ # Check that more work is available if not self.workqueue: + node.shutdown() return self.log("Number of units waiting for node:", len(self.workqueue)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-xdist-1.23.2/xdist/workermanage.py new/pytest-xdist-1.26.1/xdist/workermanage.py --- old/pytest-xdist-1.23.2/xdist/workermanage.py 2018-09-28 12:28:58.000000000 +0200 +++ new/pytest-xdist-1.26.1/xdist/workermanage.py 2019-01-28 21:48:04.000000000 +0100 @@ -2,6 +2,7 @@ import fnmatch import os import re +import sys import threading import py @@ -213,6 +214,7 @@ "workercount": len(nodemanager.specs), "slaveid": gateway.id, "slavecount": len(nodemanager.specs), + "mainargv": sys.argv, } # TODO: deprecated name, backward compatibility only. Remove it in future self.slaveinput = self.workerinput @@ -243,7 +245,9 @@ option_dict["basetemp"] = str(basetemp.join(name)) self.config.hook.pytest_configure_node(node=self) self.channel = self.gateway.remote_exec(xdist.remote) - self.channel.send((self.workerinput, args, option_dict)) + # change sys.path only for remote workers + change_sys_path = not self.gateway.spec.popen + self.channel.send((self.workerinput, args, option_dict, change_sys_path)) if self.putevent: self.channel.setcallback(self.process_from_remote, endmarker=self.ENDMARK) @@ -268,7 +272,7 @@ if not self._down: try: self.sendcommand("shutdown") - except IOError: + except (IOError, OSError): pass self._shutdown_sent = True @@ -345,7 +349,11 @@ except: # noqa from _pytest._code import ExceptionInfo - excinfo = ExceptionInfo() + # ExceptionInfo API changed in pytest 4.1 + if hasattr(ExceptionInfo, "from_current"): + excinfo = ExceptionInfo.from_current() + else: + excinfo = ExceptionInfo() print("!" * 20, excinfo) self.config.notify_exception(excinfo) self.shutdown() @@ -426,9 +434,16 @@ if data["message_module"]: mod = importlib.import_module(data["message_module"]) cls = getattr(mod, data["message_class_name"]) - try: - message = cls(*data["message_args"]) - except TypeError: + message = None + if data["message_args"] is not None: + try: + message = cls(*data["message_args"]) + except TypeError: + pass + if message is None: + # could not recreate the original warning instance; + # create a generic Warning instance with the original + # message at least message_text = "{mod}.{cls}: {msg}".format( mod=data["message_module"], cls=data["message_class_name"],