diff --git a/appveyor.yml b/appveyor.yml index 9018b78c..63af1ac4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,7 +8,7 @@ environment: - TOXENV: 'py27-t310-c45,py27-t40-c45,py27-t41-c45' - TOXENV: 'py34-t310-c45,py34-t40-c45,py34-t41-c45' - TOXENV: 'py35-t310-c45,py35-t40-c45,py35-t41-c45' - - TOXENV: 'pypy-t310-c45,pypy-t40-c45,pypy-t41-c45,pypy3-t310-c45,pypy3-t40-c45,pypy3-t41-c45' + - TOXENV: 'pypy-t310-c45,pypy-t40-c45,pypy-t41-c45' init: - ps: echo $env:TOXENV diff --git a/ci/bootstrap.py b/ci/bootstrap.py index 885f20bc..d380392f 100755 --- a/ci/bootstrap.py +++ b/ci/bootstrap.py @@ -52,7 +52,7 @@ template_vars = {'tox_environments': tox_environments} for py_ver in '27 34 35 py'.split(): - template_vars['py%s_environments' % py_ver] = [x for x in tox_environments if x.startswith('py' + py_ver)] + template_vars['py%s_environments' % py_ver] = [x for x in tox_environments if x.startswith('py' + py_ver + '-')] for name in os.listdir(join("ci", "templates")): with open(join(base_path, name), "w") as fh: diff --git a/docs/index.rst b/docs/index.rst index 155784d8..eb27e7a0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,7 +11,7 @@ Contents: reporting debuggers xdist - mp + subprocess-support plugins markers-fixtures changelog diff --git a/docs/mp.rst b/docs/mp.rst deleted file mode 100644 index 6298cb80..00000000 --- a/docs/mp.rst +++ /dev/null @@ -1,70 +0,0 @@ -======================= -Multiprocessing support -======================= - -Although pytest-cov supports multiprocessing there are few pitfalls that need to be explained. - -Abusing ``Process.terminate`` -============================= - -It appears that many people are using the ``terminate`` method and then get unreliable coverage results. - -On Linux usually that means a SIGTERM gets sent to the process. Unfortunately Python don't have a default handler for SIGTERM -so you need to install your own. Because ``pytest-cov`` doesn't want to second-guess (not yet, add your thoughts on the issue -tracker if you disagree) it doesn't install a handler by default, but you can activate it by doing anything like: - -.. code-block:: python - - from pytest_cov.embed import cleanup_on_sigterm - cleanup_on_sigterm() - - # alternatively you can do this - - from pytest_cov.embed import cleanup - - def my_handler(signum, frame): - cleanup() - # custom cleanup - signal.signal(signal.SIGTERM, my_handler) - -On Windows there's no nice way to do cleanup (no signal handlers) so you're left to your own devices. - -Ungraceful Pool shutdown -======================== - -Another problem is when using the ``Pool`` object. If you run some work on a pool in a test you're not guaranteed to get all -the coverage data unless you use the ``join`` method. - -Eg: - -.. code-block:: python - - from multiprocessing import Pool - - def f(x): - return x*x - - if __name__ == '__main__': - with Pool(5) as p: - print(p.map(f, [1, 2, 3])) - - p.join() # <= THIS IS ESSENTIAL - - -Ungraceful Process shutdown -=========================== - -There's an identical issue when using the ``Process`` objects. Don't forget to use ``.join()``: - -.. code-block:: python - - from multiprocessing import Process - - def f(name): - print('hello', name) - - if __name__ == '__main__': - p = Process(target=f, args=('bob',)) - p.start() - - p.join() # <= THIS IS ESSENTIAL diff --git a/docs/plugins.rst b/docs/plugins.rst index 12269ced..c152dec4 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -14,9 +14,9 @@ Alternatively you can have this in ``tox.ini`` (if you're using `Tox `_ being +roughly: + +* you need to deliver ``signal.CTRL_BREAK_EVENT`` +* it gets delivered to the whole process group, and that can have unforeseen consequences diff --git a/src/pytest_cov/embed.py b/src/pytest_cov/embed.py index df28ae67..025622f7 100644 --- a/src/pytest_cov/embed.py +++ b/src/pytest_cov/embed.py @@ -13,16 +13,19 @@ that code coverage is being collected we activate coverage based on info passed via env vars. """ +import atexit import os import signal -active_cov = None +_active_cov = None def multiprocessing_start(_): + global _active_cov cov = init() if cov: - multiprocessing.util.Finalize(None, cleanup, args=(cov,), exitpriority=1000) + _active_cov = cov + multiprocessing.util.Finalize(None, cleanup, exitpriority=1000) try: @@ -36,7 +39,7 @@ def multiprocessing_start(_): def init(): # Only continue if ancestor process has set everything needed in # the env. - global active_cov + global _active_cov cov_source = os.environ.get('COV_CORE_SOURCE') cov_config = os.environ.get('COV_CORE_CONFIG') @@ -44,11 +47,13 @@ def init(): cov_branch = True if os.environ.get('COV_CORE_BRANCH') == 'enabled' else None if cov_datafile: + if _active_cov: + cleanup() # Import what we need to activate coverage. import coverage # Determine all source roots. - if cov_source == os.pathsep: + if cov_source in os.pathsep: cov_source = None else: cov_source = cov_source.split(os.pathsep) @@ -56,7 +61,7 @@ def init(): cov_config = True # Activate coverage for this process. - cov = active_cov = coverage.Coverage( + cov = _active_cov = coverage.Coverage( source=cov_source, branch=cov_branch, data_suffix=True, @@ -75,28 +80,44 @@ def _cleanup(cov): if cov is not None: cov.stop() cov.save() + cov._auto_save = False # prevent autosaving from cov._atexit in case the interpreter lacks atexit.unregister + try: + atexit.unregister(cov._atexit) + except Exception: + pass -def cleanup(cov=None): - global active_cov +def cleanup(): + global _active_cov + global _cleanup_in_progress + global _pending_signal - _cleanup(cov) - if active_cov is not cov: - _cleanup(active_cov) - active_cov = None + _cleanup_in_progress = True + _cleanup(_active_cov) + _active_cov = None + _cleanup_in_progress = False + if _pending_signal: + _signal_cleanup_handler(*_pending_signal) + _pending_signal = None multiprocessing_finish = cleanup # in case someone dared to use this internal _previous_handlers = {} +_pending_signal = None +_cleanup_in_progress = False def _signal_cleanup_handler(signum, frame): + global _pending_signal + if _cleanup_in_progress: + _pending_signal = signum, frame + return cleanup() _previous_handler = _previous_handlers.get(signum) if _previous_handler == signal.SIG_IGN: return - elif _previous_handler: + elif _previous_handler and _previous_handler is not _signal_cleanup_handler: _previous_handler(signum, frame) elif signum == signal.SIGTERM: os._exit(128 + signum) @@ -105,8 +126,10 @@ def _signal_cleanup_handler(signum, frame): def cleanup_on_signal(signum): - _previous_handlers[signum] = signal.getsignal(signum) - signal.signal(signum, _signal_cleanup_handler) + previous = signal.getsignal(signum) + if previous is not _signal_cleanup_handler: + _previous_handlers[signum] = previous + signal.signal(signum, _signal_cleanup_handler) def cleanup_on_sigterm(): diff --git a/src/pytest_cov/engine.py b/src/pytest_cov/engine.py index abfb7452..afb98c53 100644 --- a/src/pytest_cov/engine.py +++ b/src/pytest_cov/engine.py @@ -8,6 +8,7 @@ import coverage from coverage.data import CoverageData +from .embed import cleanup from .compat import StringIO @@ -145,7 +146,8 @@ class Central(CovController): """Implementation for centralised operation.""" def start(self): - """Erase any previous coverage data and start coverage.""" + cleanup() + self.cov = coverage.Coverage(source=self.cov_source, branch=self.cov_branch, config_file=self.cov_config) @@ -153,6 +155,8 @@ def start(self): branch=self.cov_branch, data_file=os.path.abspath(self.cov.config.data_file), config_file=self.cov_config) + + # Erase or load any previous coverage data and start coverage. if self.cov_append: self.cov.load() else: @@ -180,8 +184,9 @@ class DistMaster(CovController): """Implementation for distributed master.""" def start(self): - """Ensure coverage rc file rsynced if appropriate.""" + cleanup() + # Ensure coverage rc file rsynced if appropriate. if self.cov_config and os.path.exists(self.cov_config): self.config.option.rsyncdir.append(self.cov_config) @@ -258,7 +263,7 @@ class DistSlave(CovController): """Implementation for distributed slaves.""" def start(self): - """Determine what data file and suffix to contribute to and start coverage.""" + cleanup() # Determine whether we are collocated with master. self.is_collocated = (socket.gethostname() == self.config.slaveinput['cov_master_host'] and diff --git a/src/pytest_cov/plugin.py b/src/pytest_cov/plugin.py index 619b9a38..8e69fd10 100644 --- a/src/pytest_cov/plugin.py +++ b/src/pytest_cov/plugin.py @@ -118,7 +118,6 @@ def __init__(self, options, pluginmanager, start=True): # Our implementation is unknown at this time. self.pid = None - self.cov = None self.cov_controller = None self.cov_report = compat.StringIO() self.cov_total = None @@ -286,12 +285,10 @@ def pytest_runtest_setup(self, item): if os.getpid() != self.pid: # test is run in another process than session, run # coverage manually - self.cov = embed.init() + embed.init() def pytest_runtest_teardown(self, item): - if self.cov is not None: - embed.cleanup(self.cov) - self.cov = None + embed.cleanup() @compat.hookwrapper def pytest_runtest_call(self, item): diff --git a/tests/test_pytest_cov.py b/tests/test_pytest_cov.py index 8809eaed..6e21c300 100644 --- a/tests/test_pytest_cov.py +++ b/tests/test_pytest_cov.py @@ -1,5 +1,6 @@ import glob import os +import platform import subprocess import sys from distutils.version import StrictVersion @@ -23,7 +24,7 @@ import pytest_cov.plugin -coverage, StrictVersion # required for skipif mark on test_cov_min_from_coveragerc +coverage, platform, StrictVersion # required for skipif mark on test_cov_min_from_coveragerc SCRIPT = ''' import sys, helper @@ -149,7 +150,10 @@ def test_foo(cov): DEST_DIR = 'cov_dest' REPORT_NAME = 'cov.xml' -xdist_params = pytest.mark.parametrize('opts', ['', '-n 1'], ids=['nodist', 'xdist']) +xdist_params = pytest.mark.parametrize('opts', [ + '', + pytest.param('-n 1', marks=pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')) +], ids=['nodist', 'xdist']) @pytest.fixture(params=[ @@ -545,6 +549,7 @@ def test_fail(p): ]) +@pytest.mark.skipif('sys.platform == "win32" or platform.python_implementation() == "PyPy"') def test_dist_combine_racecondition(testdir): script = testdir.makepyfile(""" import pytest @@ -573,6 +578,7 @@ def test_foo(foo): assert result.ret == 0 +@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_dist_collocated(testdir, prop): script = testdir.makepyfile(prop.code) testdir.tmpdir.join('.coveragerc').write(prop.fullconf) @@ -592,6 +598,7 @@ def test_dist_collocated(testdir, prop): assert result.ret == 0 +@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_dist_not_collocated(testdir, prop): script = testdir.makepyfile(prop.code) dir1 = testdir.mkdir('dir1') @@ -624,6 +631,7 @@ def test_dist_not_collocated(testdir, prop): assert result.ret == 0 +@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_dist_not_collocated_coveragerc_source(testdir, prop): script = testdir.makepyfile(prop.code) dir1 = testdir.mkdir('dir1') @@ -747,6 +755,7 @@ def test_foo(): assert result.ret == 0 +@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_dist_subprocess_collocated(testdir): scripts = testdir.makepyfile(parent_script=SCRIPT_PARENT, child_script=SCRIPT_CHILD) @@ -768,6 +777,7 @@ def test_dist_subprocess_collocated(testdir): assert result.ret == 0 +@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_dist_subprocess_not_collocated(testdir, tmpdir): scripts = testdir.makepyfile(parent_script=SCRIPT_PARENT, child_script=SCRIPT_CHILD) @@ -829,11 +839,15 @@ def test_invalid_coverage_source(testdir): @pytest.mark.skipif("'dev' in pytest.__version__") +@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_dist_missing_data(testdir): venv_path = os.path.join(str(testdir.tmpdir), 'venv') virtualenv.create_environment(venv_path) if sys.platform == 'win32': - exe = os.path.join(venv_path, 'Scripts', 'python.exe') + if platform.python_implementation() == "PyPy": + exe = os.path.join(venv_path, 'bin', 'python.exe') + else: + exe = os.path.join(venv_path, 'Scripts', 'python.exe') else: exe = os.path.join(venv_path, 'bin', 'python') subprocess.check_call([ @@ -889,8 +903,131 @@ def test_funcarg_not_active(testdir): assert result.ret == 0 -def test_multiprocessing_subprocess(testdir): - py.test.importorskip('multiprocessing.util') +@pytest.mark.skipif("sys.version_info[0] < 3", reason="no context manager api on Python 2") +@pytest.mark.skipif('sys.platform == "win32"', reason="multiprocessing support is broken on Windows") +@pytest.mark.skipif('platform.python_implementation() == "PyPy"', reason="often deadlocks on PyPy") +def test_multiprocessing_pool(testdir): + pytest.importorskip('multiprocessing.util') + + script = testdir.makepyfile(''' +import multiprocessing + +def target_fn(a): + %sse: # pragma: nocover + return None + +def test_run_target(): + from pytest_cov.embed import cleanup_on_sigterm + cleanup_on_sigterm() + + for i in range(33): + with multiprocessing.Pool(3) as p: + p.map(target_fn, [i * 3 + j for j in range(3)]) + p.join() +''' % ''.join('''if a == %r: + return a + el''' % i for i in range(99))) + + result = testdir.runpytest('-v', + '--cov=%s' % script.dirpath(), + '--cov-report=term-missing', + script) + + assert "Doesn't seem to be a coverage.py data file" not in result.stdout.str() + assert "Doesn't seem to be a coverage.py data file" not in result.stderr.str() + assert not testdir.tmpdir.listdir(".coverage.*") + result.stdout.fnmatch_lines([ + '*- coverage: platform *, python * -*', + 'test_multiprocessing_pool* 100%*', + '*1 passed*' + ]) + assert result.ret == 0 + + +@pytest.mark.skipif('sys.platform == "win32"', reason="multiprocessing support is broken on Windows") +@pytest.mark.skipif('platform.python_implementation() == "PyPy"', reason="often deadlocks on PyPy") +def test_multiprocessing_pool_terminate(testdir): + pytest.importorskip('multiprocessing.util') + + script = testdir.makepyfile(''' +import multiprocessing + +def target_fn(a): + %sse: # pragma: nocover + return None + +def test_run_target(): + from pytest_cov.embed import cleanup_on_sigterm + cleanup_on_sigterm() + + for i in range(33): + p = multiprocessing.Pool(3) + try: + p.map(target_fn, [i * 3 + j for j in range(3)]) + finally: + p.terminate() + p.join() +''' % ''.join('''if a == %r: + return a + el''' % i for i in range(99))) + + result = testdir.runpytest('-v', + '--cov=%s' % script.dirpath(), + '--cov-report=term-missing', + script) + + assert "Doesn't seem to be a coverage.py data file" not in result.stdout.str() + assert "Doesn't seem to be a coverage.py data file" not in result.stderr.str() + assert not testdir.tmpdir.listdir(".coverage.*") + result.stdout.fnmatch_lines([ + '*- coverage: platform *, python * -*', + 'test_multiprocessing_pool* 100%*', + '*1 passed*' + ]) + assert result.ret == 0 + + +@pytest.mark.skipif('sys.platform == "win32"', reason="multiprocessing support is broken on Windows") +def test_multiprocessing_pool_close(testdir): + pytest.importorskip('multiprocessing.util') + + script = testdir.makepyfile(''' +import multiprocessing + +def target_fn(a): + %sse: # pragma: nocover + return None + +def test_run_target(): + for i in range(33): + p = multiprocessing.Pool(3) + try: + p.map(target_fn, [i * 3 + j for j in range(3)]) + finally: + p.close() + p.join() +''' % ''.join('''if a == %r: + return a + el''' % i for i in range(99))) + + result = testdir.runpytest('-v', + '--cov=%s' % script.dirpath(), + '--cov-report=term-missing', + script) + assert "Doesn't seem to be a coverage.py data file" not in result.stdout.str() + assert "Doesn't seem to be a coverage.py data file" not in result.stderr.str() + assert not testdir.tmpdir.listdir(".coverage.*") + result.stdout.fnmatch_lines([ + '*- coverage: platform *, python * -*', + 'test_multiprocessing_pool* 100%*', + '*1 passed*' + ]) + assert result.ret == 0 + + +@pytest.mark.skipif('sys.platform == "win32"', reason="multiprocessing support is broken on Windows") +def test_multiprocessing_process(testdir): + pytest.importorskip('multiprocessing.util') script = testdir.makepyfile(''' import multiprocessing @@ -912,14 +1049,15 @@ def test_run_target(): result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', - 'test_multiprocessing_subprocess* 8 * 100%*', + 'test_multiprocessing_process* 8 * 100%*', '*1 passed*' ]) assert result.ret == 0 -def test_multiprocessing_subprocess_no_source(testdir): - py.test.importorskip('multiprocessing.util') +@pytest.mark.skipif('sys.platform == "win32"', reason="multiprocessing support is broken on Windows") +def test_multiprocessing_process_no_source(testdir): + pytest.importorskip('multiprocessing.util') script = testdir.makepyfile(''' import multiprocessing @@ -941,16 +1079,15 @@ def test_run_target(): result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', - 'test_multiprocessing_subprocess* 8 * 100%*', + 'test_multiprocessing_process* 8 * 100%*', '*1 passed*' ]) assert result.ret == 0 -@pytest.mark.skipif('sys.platform == "win32"', - reason="multiprocessing don't support clean process temination on Windows") -def test_multiprocessing_subprocess_with_terminate(testdir): - py.test.importorskip('multiprocessing.util') +@pytest.mark.skipif('sys.platform == "win32"', reason="multiprocessing support is broken on Windows") +def test_multiprocessing_process_with_terminate(testdir): + pytest.importorskip('multiprocessing.util') script = testdir.makepyfile(''' import multiprocessing @@ -981,14 +1118,13 @@ def test_run_target(): result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', - 'test_multiprocessing_subprocess* 16 * 100%*', + 'test_multiprocessing_process* 16 * 100%*', '*1 passed*' ]) assert result.ret == 0 -@pytest.mark.skipif('sys.platform == "win32"', - reason="fork not available on Windows") +@pytest.mark.skipif('sys.platform == "win32"', reason="SIGTERM isn't really supported on Windows") def test_cleanup_on_sigterm(testdir): script = testdir.makepyfile(''' import os, signal, subprocess, sys, time @@ -1033,7 +1169,53 @@ def test_run(): assert result.ret == 0 -@pytest.mark.skipif('sys.platform == "win32"', reason="fork not available on Windows") +@pytest.mark.skipif('sys.platform != "win32"') +@pytest.mark.parametrize('setup', [ + ('signal.signal(signal.SIGBREAK, signal.SIG_DFL); cleanup_on_signal(signal.SIGBREAK)', '87% 21-22'), + ('cleanup_on_signal(signal.SIGBREAK)', '87% 21-22'), + ('cleanup()', '73% 19-22'), +]) +def test_cleanup_on_sigterm_sig_break(testdir, setup): + # worth a read: https://stefan.sofa-rockers.org/2013/08/15/handling-sub-process-hierarchies-python-linux-os-x/ + script = testdir.makepyfile(''' +import os, signal, subprocess, sys, time + +def test_run(): + proc = subprocess.Popen( + [sys.executable, __file__], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, shell=True + ) + time.sleep(1) + proc.send_signal(signal.CTRL_BREAK_EVENT) + stdout, stderr = proc.communicate() + assert not stderr + assert stdout in [b"^C", b"", b"captured IOError(4, 'Interrupted function call')\\n"] + +if __name__ == "__main__": + from pytest_cov.embed import cleanup_on_signal, cleanup + ''' + setup[0] + ''' + + try: + time.sleep(10) + except BaseException as exc: + print("captured %r" % exc) +''') + + result = testdir.runpytest('-vv', + '--cov=%s' % script.dirpath(), + '--cov-report=term-missing', + script) + + result.stdout.fnmatch_lines([ + '*- coverage: platform *, python * -*', + 'test_cleanup_on_sigterm* %s' % setup[1], + '*1 passed*' + ]) + assert result.ret == 0 + + +@pytest.mark.skipif('sys.platform == "win32"', reason="SIGTERM isn't really supported on Windows") @pytest.mark.parametrize('setup', [ ('signal.signal(signal.SIGTERM, signal.SIG_DFL); cleanup_on_sigterm()', '88% 18-19'), ('cleanup_on_sigterm()', '88% 18-19'), @@ -1054,13 +1236,13 @@ def test_run(): if __name__ == "__main__": from pytest_cov.embed import cleanup_on_sigterm, cleanup - {0} + ''' + setup[0] + ''' try: time.sleep(10) except BaseException as exc: print("captured %r" % exc) -'''.format(setup[0])) +''') result = testdir.runpytest('-vv', '--cov=%s' % script.dirpath(), @@ -1075,7 +1257,7 @@ def test_run(): assert result.ret == 0 -@pytest.mark.skipif('sys.platform == "win32"', reason="fork not available on Windows") +@pytest.mark.skipif('sys.platform == "win32"', reason="SIGINT is subtly broken on Windows") def test_cleanup_on_sigterm_sig_dfl_sigint(testdir): script = testdir.makepyfile(''' import os, signal, subprocess, sys, time @@ -1112,6 +1294,7 @@ def test_run(): ]) assert result.ret == 0 + @pytest.mark.skipif('sys.platform == "win32"', reason="fork not available on Windows") def test_cleanup_on_sigterm_sig_ign(testdir): script = testdir.makepyfile(''' @@ -1190,6 +1373,7 @@ def test_cover_conftest(testdir): result.stdout.fnmatch_lines([CONF_RESULT]) +@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_cover_looponfail(testdir, monkeypatch): testdir.makepyfile(mod=MODULE) testdir.makeconftest(CONFTEST) @@ -1209,6 +1393,7 @@ def test_cover_looponfail(testdir, monkeypatch): ) +@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_cover_conftest_dist(testdir): testdir.makepyfile(mod=MODULE) testdir.makeconftest(CONFTEST) @@ -1235,7 +1420,7 @@ def test_no_cover_marker(testdir): @pytest.mark.no_cover def test_basic(): mod.func() - subprocess.check_call([sys.executable, '-c', 'from mod import func; func()']) + subprocess.check_call([sys.executable, '-c', 'from mod import func; func()']) ''') result = testdir.runpytest('-v', '-ra', '--strict', '--cov=%s' % script.dirpath(), @@ -1254,7 +1439,7 @@ def test_no_cover_fixture(testdir): def test_basic(no_cover): mod.func() - subprocess.check_call([sys.executable, '-c', 'from mod import func; func()']) + subprocess.check_call([sys.executable, '-c', 'from mod import func; func()']) ''') result = testdir.runpytest('-v', '-ra', '--strict', '--cov=%s' % script.dirpath(), @@ -1298,6 +1483,7 @@ def test_coveragerc(testdir): result.stdout.fnmatch_lines(['test_coveragerc* %s' % EXCLUDED_RESULT]) +@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_coveragerc_dist(testdir): testdir.makefile('', coveragerc=COVERAGERC) script = testdir.makepyfile(EXCLUDED_TEST) @@ -1486,6 +1672,7 @@ def test_external_data_file(testdir): assert glob.glob(str(testdir.tmpdir.join('some/special/place/coverage-data*'))) +@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_external_data_file_xdist(testdir): script = testdir.makepyfile(SCRIPT) testdir.tmpdir.join('.coveragerc').write(""" @@ -1609,6 +1796,7 @@ def test_double_cov2(testdir): assert result.ret == 0 +@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_cov_and_no_cov(testdir): script = testdir.makepyfile(SCRIPT_SIMPLE) result = testdir.runpytest('-v',