flake8 cleanup

This commit is contained in:
Ronny Pfannschmidt
2015-09-01 22:37:51 +02:00
parent fa1ec6742a
commit 0f5ef95be8
18 changed files with 298 additions and 223 deletions

View File

@@ -25,7 +25,7 @@ env:
- TESTENV=py27-pytest27-pexpect - TESTENV=py27-pytest27-pexpect
- TESTENV=py34-pytest27-pexpect - TESTENV=py34-pytest27-pexpect
- TESTENV=py35-pytest27 # - TESTENV=py35-pytest27
- TESTENV=pypy-pytest27 - TESTENV=pypy-pytest27
script: tox --recreate -e $TESTENV script: tox --recreate -e $TESTENV

View File

@@ -3,15 +3,16 @@ from setuptools import setup
setup( setup(
name="pytest-xdist", name="pytest-xdist",
use_scm_version={'write_to': 'xdist/_version.py'}, use_scm_version={'write_to': 'xdist/_version.py'},
description='py.test xdist plugin for distributed testing and loop-on-failing modes', description='py.test xdist plugin for distributed testing'
' and loop-on-failing modes',
long_description=open('README.rst').read(), long_description=open('README.rst').read(),
license='MIT', license='MIT',
author='holger krekel and contributors', author='holger krekel and contributors',
author_email='pytest-dev@python.org,holger@merlinux.eu', author_email='pytest-dev@python.org,holger@merlinux.eu',
url='http://bitbucket.org/hpk42/pytest-xdist', url='http://bitbucket.org/hpk42/pytest-xdist',
platforms=['linux', 'osx', 'win32'], platforms=['linux', 'osx', 'win32'],
packages = ['xdist'], packages=['xdist'],
entry_points = { entry_points={
'pytest11': [ 'pytest11': [
'xdist = xdist.plugin', 'xdist = xdist.plugin',
'xdist.looponfail = xdist.looponfail', 'xdist.looponfail = xdist.looponfail',

View File

@@ -10,9 +10,7 @@ class TestDistribution:
""") """)
result = testdir.runpytest(p1, "-n1") result = testdir.runpytest(p1, "-n1")
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines(["*1 passed*", ])
"*1 passed*",
])
def test_n1_fail(self, testdir): def test_n1_fail(self, testdir):
p1 = testdir.makepyfile(""" p1 = testdir.makepyfile("""
@@ -21,9 +19,7 @@ class TestDistribution:
""") """)
result = testdir.runpytest(p1, "-n1") result = testdir.runpytest(p1, "-n1")
assert result.ret == 1 assert result.ret == 1
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines(["*1 failed*", ])
"*1 failed*",
])
def test_n1_import_error(self, testdir): def test_n1_import_error(self, testdir):
p1 = testdir.makepyfile(""" p1 = testdir.makepyfile("""
@@ -57,9 +53,7 @@ class TestDistribution:
""") """)
result = testdir.runpytest(p1, "-n1") result = testdir.runpytest(p1, "-n1")
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines(["*1 skipped*", ])
"*1 skipped*",
])
def test_manytests_to_one_import_error(self, testdir): def test_manytests_to_one_import_error(self, testdir):
p1 = testdir.makepyfile(""" p1 = testdir.makepyfile("""
@@ -84,8 +78,7 @@ class TestDistribution:
pass pass
def test_skip(): def test_skip():
py.test.skip("hello") py.test.skip("hello")
""", """, )
)
result = testdir.runpytest(p1, "-v", '-d', '--tx=popen', '--tx=popen') result = testdir.runpytest(p1, "-v", '-d', '--tx=popen', '--tx=popen')
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*1*Python*", "*1*Python*",
@@ -115,9 +108,7 @@ class TestDistribution:
""" % str(testdir.tmpdir)) """ % str(testdir.tmpdir))
result = testdir.runpytest_subprocess(p1, "-n1") result = testdir.runpytest_subprocess(p1, "-n1")
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines(["*1 passed*", ])
"*1 passed*",
])
def test_dist_ini_specified(self, testdir): def test_dist_ini_specified(self, testdir):
p1 = testdir.makepyfile(""" p1 = testdir.makepyfile("""
@@ -130,8 +121,7 @@ class TestDistribution:
pass pass
def test_skip(): def test_skip():
py.test.skip("hello") py.test.skip("hello")
""", """, )
)
testdir.makeini(""" testdir.makeini("""
[pytest] [pytest]
addopts = --tx=3*popen addopts = --tx=3*popen
@@ -163,13 +153,10 @@ class TestDistribution:
import os import os
time.sleep(0.5) time.sleep(0.5)
os.kill(os.getpid(), 15) os.kill(os.getpid(), 15)
""" """)
)
result = testdir.runpytest(p1, "-v", '-d', '-n1') result = testdir.runpytest(p1, "-v", '-d', '-n1')
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*Python*", "*Python*", "*PASS**test_ok*", "*node*down*",
"*PASS**test_ok*",
"*node*down*",
"*3 failed, 1 passed, 1 skipped*" "*3 failed, 1 passed, 1 skipped*"
]) ])
assert result.ret == 1 assert result.ret == 1
@@ -182,13 +169,13 @@ class TestDistribution:
p = subdir.join("test_one.py") p = subdir.join("test_one.py")
p.write("def test_5():\n assert not __file__.startswith(%r)" % str(p)) p.write("def test_5():\n assert not __file__.startswith(%r)" % str(p))
result = testdir.runpytest("-v", "-d", result = testdir.runpytest("-v", "-d",
"--rsyncdir=%(subdir)s" % locals(), "--rsyncdir=%(subdir)s" % locals(),
"--tx=popen//chdir=%(dest)s" % locals(), p) "--tx=popen//chdir=%(dest)s" % locals(), p)
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*0* *cwd*", "*0* *cwd*",
#"RSyncStart: [G1]", # "RSyncStart: [G1]",
#"RSyncFinished: [G1]", # "RSyncFinished: [G1]",
"*1 passed*" "*1 passed*"
]) ])
assert dest.join(subdir.basename).check(dir=1) assert dest.join(subdir.basename).check(dir=1)
@@ -221,14 +208,13 @@ class TestDistribution:
p1 = testdir.makepyfile("def test_func(): pass") p1 = testdir.makepyfile("def test_func(): pass")
result = testdir.runpytest("-v", p1, '-d', '--tx=popen') result = testdir.runpytest("-v", p1, '-d', '--tx=popen')
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*0*Python*", "*0*Python*", "*calculated result is 49*", "*1 passed*"
"*calculated result is 49*",
"*1 passed*"
]) ])
assert result.ret == 0 assert result.ret == 0
def test_keyboardinterrupt_hooks_issue79(self, testdir): def test_keyboardinterrupt_hooks_issue79(self, testdir):
testdir.makepyfile(__init__="", test_one=""" testdir.makepyfile(__init__="",
test_one="""
def test_hello(): def test_hello():
raise KeyboardInterrupt() raise KeyboardInterrupt()
""") """)
@@ -258,11 +244,12 @@ class TestDistribution:
""") """)
child = testdir.spawn_pytest("-n1 -v") child = testdir.spawn_pytest("-n1 -v")
child.expect(".*test_sleep.*") child.expect(".*test_sleep.*")
child.kill(2) # keyboard interrupt child.kill(2) # keyboard interrupt
child.expect(".*KeyboardInterrupt.*") child.expect(".*KeyboardInterrupt.*")
#child.expect(".*seconds.*") # child.expect(".*seconds.*")
child.close() child.close()
#assert ret == 2 # assert ret == 2
class TestDistEach: class TestDistEach:
def test_simple(self, testdir): def test_simple(self, testdir):
@@ -270,11 +257,13 @@ class TestDistEach:
def test_hello(): def test_hello():
pass pass
""") """)
result = testdir.runpytest_subprocess("--debug", "--dist=each", "--tx=2*popen") result = testdir.runpytest_subprocess("--debug", "--dist=each",
"--tx=2*popen")
assert not result.ret assert not result.ret
result.stdout.fnmatch_lines(["*2 pass*"]) result.stdout.fnmatch_lines(["*2 pass*"])
@py.test.mark.xfail(run=False, @py.test.mark.xfail(
run=False,
reason="other python versions might not have py.test installed") reason="other python versions might not have py.test installed")
def test_simple_diffoutput(self, testdir): def test_simple_diffoutput(self, testdir):
interpreters = [] interpreters = []
@@ -284,7 +273,8 @@ class TestDistEach:
py.test.skip("%s not found" % name) py.test.skip("%s not found" % name)
interpreters.append(interp) interpreters.append(interp)
testdir.makepyfile(__init__="", test_one=""" testdir.makepyfile(__init__="",
test_one="""
import sys import sys
def test_hello(): def test_hello():
print("%s...%s" % sys.version_info[:2]) print("%s...%s" % sys.version_info[:2])
@@ -298,6 +288,7 @@ class TestDistEach:
assert "2...5" in s assert "2...5" in s
assert "2...6" in s assert "2...6" in s
class TestTerminalReporting: class TestTerminalReporting:
def test_pass_skip_fail(self, testdir): def test_pass_skip_fail(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
@@ -335,6 +326,7 @@ class TestTerminalReporting:
"E assert 0", "E assert 0",
]) ])
def test_teardownfails_one_function(testdir): def test_teardownfails_one_function(testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
def test_func(): def test_func():
@@ -344,10 +336,10 @@ def test_teardownfails_one_function(testdir):
""") """)
result = testdir.runpytest(p, '-n1', '--tx=popen') result = testdir.runpytest(p, '-n1', '--tx=popen')
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*def teardown_function(function):*", "*def teardown_function(function):*", "*1 passed*1 error*"
"*1 passed*1 error*"
]) ])
@py.test.mark.xfail @py.test.mark.xfail
def test_terminate_on_hangingnode(testdir): def test_terminate_on_hangingnode(testdir):
p = testdir.makeconftest(""" p = testdir.makeconftest("""
@@ -358,9 +350,7 @@ def test_terminate_on_hangingnode(testdir):
""") """)
result = testdir.runpytest(p, '--dist=each', '--tx=popen//id=my') result = testdir.runpytest(p, '--dist=each', '--tx=popen//id=my')
assert result.duration < 2.0 assert result.duration < 2.0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines(["*killed*my*", ])
"*killed*my*",
])
def test_auto_detect_cpus(testdir, monkeypatch): def test_auto_detect_cpus(testdir, monkeypatch):
@@ -400,10 +390,7 @@ def test_session_hooks(testdir):
assert hasattr(sys, 'pytestsessionhooks') assert hasattr(sys, 'pytestsessionhooks')
""") """)
result = testdir.runpytest(p, "--dist=each", "--tx=popen") result = testdir.runpytest(p, "--dist=each", "--tx=popen")
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines(["*ValueError*", "*1 passed*", ])
"*ValueError*",
"*1 passed*",
])
assert not result.ret assert not result.ret
d = result.parseoutcomes() d = result.parseoutcomes()
assert d['passed'] == 1 assert d['passed'] == 1
@@ -445,13 +432,11 @@ def test_funcarg_teardown_failure(testdir):
def test_hello(myarg): def test_hello(myarg):
pass pass
""") """)
result = testdir.runpytest_subprocess("--debug", p) # , "-n1") result = testdir.runpytest_subprocess("--debug", p) # , "-n1")
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines(["*ValueError*42*", "*1 passed*1 error*", ])
"*ValueError*42*",
"*1 passed*1 error*",
])
assert result.ret assert result.ret
def test_crashing_item(testdir): def test_crashing_item(testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
import py import py
@@ -463,12 +448,10 @@ def test_crashing_item(testdir):
""") """)
result = testdir.runpytest("-n2", p) result = testdir.runpytest("-n2", p)
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*crashed*test_crash*", "*crashed*test_crash*", "*1 failed*1 passed*"
"*1 failed*1 passed*"
]) ])
def test_skipping(testdir): def test_skipping(testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
import pytest import pytest
@@ -477,10 +460,8 @@ def test_skipping(testdir):
""") """)
result = testdir.runpytest("-n1", '-rs', p) result = testdir.runpytest("-n1", '-rs', p)
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines(["*hello*", "*1 skipped*"])
"*hello*",
"*1 skipped*"
])
def test_issue34_pluginloading_in_subprocess(testdir): def test_issue34_pluginloading_in_subprocess(testdir):
testdir.tmpdir.join("plugin123.py").write(py.code.Source(""" testdir.tmpdir.join("plugin123.py").write(py.code.Source("""
@@ -494,9 +475,7 @@ def test_issue34_pluginloading_in_subprocess(testdir):
""") """)
result = testdir.runpytest_subprocess("-n1", "-p", "plugin123") result = testdir.runpytest_subprocess("-n1", "-p", "plugin123")
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines(["*1 passed*", ])
"*1 passed*",
])
def test_fixture_scope_caching_issue503(testdir): def test_fixture_scope_caching_issue503(testdir):
@@ -505,7 +484,8 @@ def test_fixture_scope_caching_issue503(testdir):
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def fix(): def fix():
assert fix.counter == 0, 'session fixture was invoked multiple times' assert fix.counter == 0, \
'session fixture was invoked multiple times'
fix.counter += 1 fix.counter += 1
fix.counter = 0 fix.counter = 0
@@ -517,9 +497,7 @@ def test_fixture_scope_caching_issue503(testdir):
""") """)
result = testdir.runpytest(p1, '-v', '-n1') result = testdir.runpytest(p1, '-v', '-n1')
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines(["*2 passed*", ])
"*2 passed*",
])
def test_issue_594_random_parametrize(testdir): def test_issue_594_random_parametrize(testdir):
@@ -545,7 +523,6 @@ def test_issue_594_random_parametrize(testdir):
class TestNodeFailure: class TestNodeFailure:
def test_load_single(self, testdir): def test_load_single(self, testdir):
f = testdir.makepyfile(""" f = testdir.makepyfile("""
import os import os
@@ -617,7 +594,6 @@ class TestNodeFailure:
"*2 failed*2 passed*", "*2 failed*2 passed*",
]) ])
def test_disable_restart(self, testdir): def test_disable_restart(self, testdir):
f = testdir.makepyfile(""" f = testdir.makepyfile("""
import os import os

View File

@@ -2,6 +2,7 @@ import py
import pytest import pytest
import execnet import execnet
@pytest.fixture(scope="session", autouse=True) @pytest.fixture(scope="session", autouse=True)
def _ensure_imports(): def _ensure_imports():
# we import some modules because pytest-2.8's testdir fixture # we import some modules because pytest-2.8's testdir fixture
@@ -10,28 +11,36 @@ def _ensure_imports():
execnet.Group execnet.Group
execnet.makegateway execnet.makegateway
pytest_plugins = "pytester" pytest_plugins = "pytester"
#rsyncdirs = ['.', '../xdist', py.path.local(execnet.__file__).dirpath()] # rsyncdirs = ['.', '../xdist', py.path.local(execnet.__file__).dirpath()]
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def _divert_atexit(request, monkeypatch): def _divert_atexit(request, monkeypatch):
import atexit import atexit
l = [] l = []
def finish(): def finish():
while l: while l:
l.pop()() l.pop()()
monkeypatch.setattr(atexit, "register", l.append) monkeypatch.setattr(atexit, "register", l.append)
request.addfinalizer(finish) request.addfinalizer(finish)
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addoption('--gx', parser.addoption('--gx',
action="append", dest="gspecs", action="append",
help=("add a global test environment, XSpec-syntax. ")) dest="gspecs",
help=("add a global test environment, XSpec-syntax. "))
def pytest_funcarg__specssh(request): def pytest_funcarg__specssh(request):
return getspecssh(request.config) return getspecssh(request.config)
@pytest.fixture @pytest.fixture
def testdir(testdir): def testdir(testdir):
# pytest before 2.8 did not have a runpytest_subprocess # pytest before 2.8 did not have a runpytest_subprocess
@@ -39,10 +48,11 @@ def testdir(testdir):
testdir.runpytest_subprocess = testdir.runpytest testdir.runpytest_subprocess = testdir.runpytest
return testdir return testdir
# configuration information for tests # configuration information for tests
def getgspecs(config): def getgspecs(config):
return [execnet.XSpec(spec) return [execnet.XSpec(spec) for spec in config.getvalueorskip("gspecs")]
for spec in config.getvalueorskip("gspecs")]
def getspecssh(config): def getspecssh(config):
xspecs = getgspecs(config) xspecs = getgspecs(config)
@@ -53,10 +63,10 @@ def getspecssh(config):
return str(spec) return str(spec)
py.test.skip("need '--gx ssh=...'") py.test.skip("need '--gx ssh=...'")
def getsocketspec(config): def getsocketspec(config):
xspecs = getgspecs(config) xspecs = getgspecs(config)
for spec in xspecs: for spec in xspecs:
if spec.socket: if spec.socket:
return spec return spec
py.test.skip("need '--gx socket=...'") py.test.skip("need '--gx socket=...'")

View File

@@ -4,6 +4,7 @@ import os
needsfork = pytest.mark.skipif(not hasattr(os, "fork"), needsfork = pytest.mark.skipif(not hasattr(os, "fork"),
reason="os.fork required") reason="os.fork required")
@needsfork @needsfork
def test_functional_boxed(testdir): def test_functional_boxed(testdir):
p1 = testdir.makepyfile(""" p1 = testdir.makepyfile("""
@@ -17,6 +18,7 @@ def test_functional_boxed(testdir):
"*1 failed*" "*1 failed*"
]) ])
@needsfork @needsfork
@pytest.mark.parametrize("capmode", [ @pytest.mark.parametrize("capmode", [
"no", "no",
@@ -41,6 +43,7 @@ def test_functional_boxed_capturing(testdir, capmode):
*1 failed* *1 failed*
""") """)
class TestOptionEffects: class TestOptionEffects:
def test_boxed_option_default(self, testdir): def test_boxed_option_default(self, testdir):
tmpdir = testdir.tmpdir.ensure("subdir", dir=1) tmpdir = testdir.tmpdir.ensure("subdir", dir=1)
@@ -53,4 +56,3 @@ class TestOptionEffects:
def test_is_not_boxed_by_default(self, testdir): def test_is_not_boxed_by_default(self, testdir):
config = testdir.parseconfig(testdir.tmpdir) config = testdir.parseconfig(testdir.tmpdir)
assert not config.option.boxed assert not config.option.boxed

View File

@@ -1,8 +1,5 @@
from xdist.dsession import ( from xdist.dsession import (
DSession, DSession, LoadScheduling, EachScheduling, report_collection_diff,
LoadScheduling,
EachScheduling,
report_collection_diff,
) )
import py import py
import pytest import pytest
@@ -10,19 +7,22 @@ import execnet
XSpec = execnet.XSpec XSpec = execnet.XSpec
def run(item, node, excinfo=None): def run(item, node, excinfo=None):
runner = item.config.pluginmanager.getplugin("runner") runner = item.config.pluginmanager.getplugin("runner")
rep = runner.ItemTestReport(item=item, rep = runner.ItemTestReport(item=item, excinfo=excinfo, when="call")
excinfo=excinfo, when="call")
rep.node = node rep.node = node
return rep return rep
class MockGateway: class MockGateway:
_count = 0 _count = 0
def __init__(self): def __init__(self):
self.id = str(self._count) self.id = str(self._count)
self._count += 1 self._count += 1
class MockNode: class MockNode:
def __init__(self): def __init__(self):
self.sent = [] self.sent = []
@@ -35,12 +35,14 @@ class MockNode:
self.sent.append("ALL") self.sent.append("ALL")
def shutdown(self): def shutdown(self):
self._shutdown=True self._shutdown = True
def dumpqueue(queue): def dumpqueue(queue):
while queue.qsize(): while queue.qsize():
print(queue.get()) print(queue.get())
class TestEachScheduling: class TestEachScheduling:
def test_schedule_load_simple(self): def test_schedule_load_simple(self):
node1 = MockNode() node1 = MockNode()
@@ -81,6 +83,7 @@ class TestEachScheduling:
assert sched.tests_finished() assert sched.tests_finished()
assert not sched.hasnodes() assert not sched.hasnodes()
class TestLoadScheduling: class TestLoadScheduling:
def test_schedule_load_simple(self): def test_schedule_load_simple(self):
sched = LoadScheduling(2) sched = LoadScheduling(2)
@@ -115,7 +118,7 @@ class TestLoadScheduling:
sched.addnode_collection(node1, col) sched.addnode_collection(node1, col)
sched.addnode_collection(node2, col) sched.addnode_collection(node2, col)
sched.init_distribute() sched.init_distribute()
#assert not sched.tests_finished() # assert not sched.tests_finished()
sent1 = node1.sent sent1 = node1.sent
sent2 = node2.sent sent2 = node2.sent
assert sent1 == [0, 1] assert sent1 == [0, 1]
@@ -149,6 +152,7 @@ class TestLoadScheduling:
Test that LoadScheduling is reporting collection errors when Test that LoadScheduling is reporting collection errors when
different test ids are collected by slaves. different test ids are collected by slaves.
""" """
class CollectHook(object): class CollectHook(object):
""" """
Dummy hook that stores collection reports. Dummy hook that stores collection reports.
@@ -177,7 +181,6 @@ class TestLoadScheduling:
class TestDistReporter: class TestDistReporter:
@py.test.mark.xfail @py.test.mark.xfail
def test_rsync_printing(self, testdir, linecomp): def test_rsync_printing(self, testdir, linecomp):
config = testdir.parseconfig() config = testdir.parseconfig()
@@ -185,26 +188,26 @@ class TestDistReporter:
rep = TerminalReporter(config, file=linecomp.stringio) rep = TerminalReporter(config, file=linecomp.stringio)
config.pluginmanager.register(rep, "terminalreporter") config.pluginmanager.register(rep, "terminalreporter")
dsession = DSession(config) dsession = DSession(config)
class gw1: class gw1:
id = "X1" id = "X1"
spec = execnet.XSpec("popen") spec = execnet.XSpec("popen")
class gw2: class gw2:
id = "X2" id = "X2"
spec = execnet.XSpec("popen") spec = execnet.XSpec("popen")
#class rinfo: # class rinfo:
# version_info = (2, 5, 1, 'final', 0) # version_info = (2, 5, 1, 'final', 0)
# executable = "hello" # executable = "hello"
# platform = "xyz" # platform = "xyz"
# cwd = "qwe" # cwd = "qwe"
#dsession.pytest_xdist_newgateway(gw1, rinfo) # dsession.pytest_xdist_newgateway(gw1, rinfo)
#linecomp.assert_contains_lines([ # linecomp.assert_contains_lines([
# "*X1*popen*xyz*2.5*" # "*X1*popen*xyz*2.5*"
#]) # ])
dsession.pytest_xdist_rsyncstart(source="hello", gateways=[gw1, gw2]) dsession.pytest_xdist_rsyncstart(source="hello", gateways=[gw1, gw2])
linecomp.assert_contains_lines([ linecomp.assert_contains_lines(["[X1,X2] rsyncing: hello", ])
"[X1,X2] rsyncing: hello",
])
def test_report_collection_diff_equal(): def test_report_collection_diff_equal():
@@ -230,12 +233,12 @@ def test_report_collection_diff_different():
' bbb\n' ' bbb\n'
'+XXX\n' '+XXX\n'
' ccc\n' ' ccc\n'
'-YYY' '-YYY')
)
msg = report_collection_diff(from_collection, to_collection, 1, 2) msg = report_collection_diff(from_collection, to_collection, 1, 2)
assert msg == error_message assert msg == error_message
@pytest.mark.xfail(reason="duplicate test ids not supported yet") @pytest.mark.xfail(reason="duplicate test ids not supported yet")
def test_pytest_issue419(testdir): def test_pytest_issue419(testdir):
testdir.makepyfile(""" testdir.makepyfile("""

View File

@@ -2,6 +2,7 @@ import py
from xdist.looponfail import RemoteControl from xdist.looponfail import RemoteControl
from xdist.looponfail import StatRecorder from xdist.looponfail import StatRecorder
class TestStatRecorder: class TestStatRecorder:
def test_filechange(self, tmpdir): def test_filechange(self, tmpdir):
tmp = tmpdir tmp = tmpdir
@@ -87,6 +88,7 @@ class TestStatRecorder:
sd.waitonchange(checkinterval=0.2) sd.waitonchange(checkinterval=0.2)
assert not l assert not l
class TestRemoteControl: class TestRemoteControl:
def test_nofailures(self, testdir): def test_nofailures(self, testdir):
item = testdir.getitem("def test_func(): pass\n") item = testdir.getitem("def test_func(): pass\n")
@@ -136,13 +138,14 @@ class TestRemoteControl:
parent = modcol.fspath.dirpath().dirpath() parent = modcol.fspath.dirpath().dirpath()
parent.chdir() parent.chdir()
modcol.config.args = [py.path.local(x).relto(parent) modcol.config.args = [py.path.local(x).relto(parent)
for x in modcol.config.args] for x in modcol.config.args]
control = RemoteControl(modcol.config) control = RemoteControl(modcol.config)
control.loop_once() control.loop_once()
assert control.failures assert control.failures
control.loop_once() control.loop_once()
assert control.failures assert control.failures
class TestLooponFailing: class TestLooponFailing:
def test_looponfail_from_fail_to_ok(self, testdir): def test_looponfail_from_fail_to_ok(self, testdir):
modcol = testdir.getmodulecol(""" modcol = testdir.getmodulecol("""
@@ -225,7 +228,7 @@ class TestLooponFailing:
def runsession_dups(): def runsession_dups():
# twisted.trial test cases may report multiple errors. # twisted.trial test cases may report multiple errors.
failures, reports, collection_failed = orig_runsession() failures, reports, collection_failed = orig_runsession()
print (failures) print(failures)
return failures * 2, reports, collection_failed return failures * 2, reports, collection_failed
monkeypatch.setattr(remotecontrol, 'runsession', runsession_dups) monkeypatch.setattr(remotecontrol, 'runsession', runsession_dups)
@@ -240,8 +243,8 @@ class TestFunctional:
x = 0 x = 0
assert x == 1 assert x == 1
""") """)
#p = testdir.mkdir("sub").join(p1.basename) # p = testdir.mkdir("sub").join(p1.basename)
#p1.move(p) # p1.move(p)
child = testdir.spawn_pytest("-f %s --traceconfig" % p) child = testdir.spawn_pytest("-f %s --traceconfig" % p)
child.expect("def test_one") child.expect("def test_one")
child.expect("x == 1") child.expect("x == 1")
@@ -269,6 +272,7 @@ class TestFunctional:
child.expect("waiting for changes") child.expect("waiting for changes")
child.kill(15) child.kill(15)
def removepyc(path): def removepyc(path):
# XXX damn those pyc files # XXX damn those pyc files
pyc = path + "c" pyc = path + "c"
@@ -277,4 +281,3 @@ def removepyc(path):
c = path.dirpath("__pycache__") c = path.dirpath("__pycache__")
if c.check(): if c.check():
c.remove() c.remove()

View File

@@ -2,6 +2,7 @@ import py
import execnet import execnet
from xdist.slavemanage import NodeManager from xdist.slavemanage import NodeManager
def test_dist_incompatibility_messages(testdir): def test_dist_incompatibility_messages(testdir):
result = testdir.runpytest("--pdb", "--looponfail") result = testdir.runpytest("--pdb", "--looponfail")
assert result.ret != 0 assert result.ret != 0
@@ -12,6 +13,7 @@ def test_dist_incompatibility_messages(testdir):
assert result.ret != 0 assert result.ret != 0
assert "incompatible" in result.stderr.str() assert "incompatible" in result.stderr.str()
def test_dist_options(testdir): def test_dist_options(testdir):
from xdist.plugin import pytest_cmdline_main as check_options from xdist.plugin import pytest_cmdline_main as check_options
config = testdir.parseconfigure("-n 2") config = testdir.parseconfigure("-n 2")
@@ -22,6 +24,7 @@ def test_dist_options(testdir):
check_options(config) check_options(config)
assert config.option.dist == "load" assert config.option.dist == "load"
class TestDistOptions: class TestDistOptions:
def test_getxspecs(self, testdir): def test_getxspecs(self, testdir):
config = testdir.parseconfigure("--tx=popen", "--tx", "ssh=xyz") config = testdir.parseconfigure("--tx=popen", "--tx", "ssh=xyz")
@@ -33,7 +36,7 @@ class TestDistOptions:
assert xspecs[1].ssh == "xyz" assert xspecs[1].ssh == "xyz"
def test_xspecs_multiplied(self, testdir): def test_xspecs_multiplied(self, testdir):
config = testdir.parseconfigure("--tx=3*popen",) config = testdir.parseconfigure("--tx=3*popen", )
xspecs = NodeManager(config)._getxspecs() xspecs = NodeManager(config)._getxspecs()
assert len(xspecs) == 3 assert len(xspecs) == 3
assert xspecs[1].popen assert xspecs[1].popen
@@ -60,10 +63,10 @@ class TestDistOptions:
rsyncdirs= x rsyncdirs= x
""") """)
config = testdir.parseconfigure( config = testdir.parseconfigure(
testdir.tmpdir, '--rsyncdir=y', '--rsyncdir=z') testdir.tmpdir, '--rsyncdir=y', '--rsyncdir=z')
nm = NodeManager(config, specs=[execnet.XSpec("popen//chdir=xyz")]) nm = NodeManager(config, specs=[execnet.XSpec("popen//chdir=xyz")])
roots = nm._getrsyncdirs() roots = nm._getrsyncdirs()
#assert len(roots) == 3 + 1 # pylib # assert len(roots) == 3 + 1 # pylib
assert py.path.local('y') in roots assert py.path.local('y') in roots
assert py.path.local('z') in roots assert py.path.local('z') in roots
assert testdir.tmpdir.join('x') in roots assert testdir.tmpdir.join('x') in roots

View File

@@ -7,6 +7,7 @@ import marshal
WAIT_TIMEOUT = 10.0 WAIT_TIMEOUT = 10.0
def check_marshallable(d): def check_marshallable(d):
try: try:
marshal.dumps(d) marshal.dumps(d)
@@ -14,12 +15,14 @@ def check_marshallable(d):
py.std.pprint.pprint(d) py.std.pprint.pprint(d)
raise ValueError("not marshallable") raise ValueError("not marshallable")
class EventCall: class EventCall:
def __init__(self, eventcall): def __init__(self, eventcall):
self.name, self.kwargs = eventcall self.name, self.kwargs = eventcall
def __str__(self): def __str__(self):
return "<EventCall %s(**%s)>" %(self.name, self.kwargs) return "<EventCall %s(**%s)>" % (self.name, self.kwargs)
class SlaveSetup: class SlaveSetup:
use_callback = False use_callback = False
@@ -31,7 +34,7 @@ class SlaveSetup:
def setup(self, ): def setup(self, ):
self.testdir.chdir() self.testdir.chdir()
#import os ; os.environ['EXECNET_DEBUG'] = "2" # import os ; os.environ['EXECNET_DEBUG'] = "2"
self.gateway = execnet.makegateway() self.gateway = execnet.makegateway()
self.config = config = self.testdir.parseconfigure() self.config = config = self.testdir.parseconfigure()
putevent = self.use_callback and self.events.put or None putevent = self.use_callback and self.events.put or None
@@ -48,14 +51,16 @@ class SlaveSetup:
ev = EventCall(data) ev = EventCall(data)
if name is None or ev.name == name: if name is None or ev.name == name:
return ev return ev
print("skipping %s" % (ev,)) print("skipping %s" % (ev, ))
def sendcommand(self, name, **kwargs): def sendcommand(self, name, **kwargs):
self.slp.sendcommand(name, **kwargs) self.slp.sendcommand(name, **kwargs)
def pytest_funcarg__slave(request): def pytest_funcarg__slave(request):
return SlaveSetup(request) return SlaveSetup(request)
def test_remoteinitconfig(testdir): def test_remoteinitconfig(testdir):
from xdist.remote import remote_initconfig from xdist.remote import remote_initconfig
config1 = testdir.parseconfig() config1 = testdir.parseconfig()
@@ -63,6 +68,7 @@ def test_remoteinitconfig(testdir):
assert config2.option.__dict__ == config1.option.__dict__ assert config2.option.__dict__ == config1.option.__dict__
assert config2.pluginmanager.getplugin("terminal") in (-1, None) assert config2.pluginmanager.getplugin("terminal") in (-1, None)
class TestReportSerialization: class TestReportSerialization:
def test_itemreport_outcomes(self, testdir): def test_itemreport_outcomes(self, testdir):
reprec = testdir.inline_runsource(""" reprec = testdir.inline_runsource("""
@@ -79,7 +85,7 @@ class TestReportSerialization:
py.test.xfail("hello") py.test.xfail("hello")
""") """)
reports = reprec.getreports("pytest_runtest_logreport") reports = reprec.getreports("pytest_runtest_logreport")
assert len(reports) == 17 # with setup/teardown "passed" reports assert len(reports) == 17 # with setup/teardown "passed" reports
for rep in reports: for rep in reports:
d = serialize_report(rep) d = serialize_report(rep)
check_marshallable(d) check_marshallable(d)
@@ -158,7 +164,7 @@ class TestSlaveInteractor:
ev = slave.popevent("logstart") ev = slave.popevent("logstart")
assert ev.kwargs["nodeid"].endswith("test_func") assert ev.kwargs["nodeid"].endswith("test_func")
assert len(ev.kwargs["location"]) == 3 assert len(ev.kwargs["location"]) == 3
ev = slave.popevent("testreport") # setup ev = slave.popevent("testreport") # setup
ev = slave.popevent("testreport") ev = slave.popevent("testreport")
assert ev.name == "testreport" assert ev.name == "testreport"
rep = unserialize_report(ev.name, ev.kwargs['data']) rep = unserialize_report(ev.name, ev.kwargs['data'])
@@ -245,4 +251,3 @@ class TestSlaveInteractor:
("pytest_pycollect_makeitem", "name == 'test_func'"), ("pytest_pycollect_makeitem", "name == 'test_func'"),
("pytest_collectreport", "report.collector.fspath == bbb"), ("pytest_collectreport", "report.collector.fspath == bbb"),
]) ])

View File

@@ -7,6 +7,7 @@ from xdist.slavemanage import HostRSync, NodeManager
pytest_plugins = "pytester" pytest_plugins = "pytester"
def pytest_funcarg__hookrecorder(request, config): def pytest_funcarg__hookrecorder(request, config):
hookrecorder = HookRecorder(config.pluginmanager) hookrecorder = HookRecorder(config.pluginmanager)
if hasattr(hookrecorder, "start_recording"): if hasattr(hookrecorder, "start_recording"):
@@ -14,23 +15,32 @@ def pytest_funcarg__hookrecorder(request, config):
request.addfinalizer(hookrecorder.finish_recording) request.addfinalizer(hookrecorder.finish_recording)
return hookrecorder return hookrecorder
def pytest_funcarg__config(testdir): def pytest_funcarg__config(testdir):
return testdir.parseconfig() return testdir.parseconfig()
def pytest_funcarg__mysetup(tmpdir): def pytest_funcarg__mysetup(tmpdir):
class mysetup: class mysetup:
source = tmpdir.mkdir("source") source = tmpdir.mkdir("source")
dest = tmpdir.mkdir("dest") dest = tmpdir.mkdir("dest")
return mysetup() return mysetup()
@pytest.fixture @pytest.fixture
def slavecontroller(monkeypatch): def slavecontroller(monkeypatch):
class MockController(object): class MockController(object):
def __init__(self, *args): pass def __init__(self, *args):
def setup(self): pass pass
def setup(self):
pass
monkeypatch.setattr(slavemanage, 'SlaveController', MockController) monkeypatch.setattr(slavemanage, 'SlaveController', MockController)
return MockController return MockController
class TestNodeManagerPopen: class TestNodeManagerPopen:
def test_popen_no_default_chdir(self, config): def test_popen_no_default_chdir(self, config):
gm = NodeManager(config, ["popen"]) gm = NodeManager(config, ["popen"])
@@ -43,7 +53,8 @@ class TestNodeManagerPopen:
for spec in NodeManager(config, l, defaultchdir="abc").specs: for spec in NodeManager(config, l, defaultchdir="abc").specs:
assert spec.chdir == "abc" assert spec.chdir == "abc"
def test_popen_makegateway_events(self, config, hookrecorder, slavecontroller): def test_popen_makegateway_events(self, config, hookrecorder,
slavecontroller):
hm = NodeManager(config, ["popen"] * 2) hm = NodeManager(config, ["popen"] * 2)
hm.setup_nodes(None) hm.setup_nodes(None)
call = hookrecorder.popcall("pytest_xdist_setupnodes") call = hookrecorder.popcall("pytest_xdist_setupnodes")
@@ -64,12 +75,16 @@ class TestNodeManagerPopen:
hm.setup_nodes(None) hm.setup_nodes(None)
assert len(hm.group) == 2 assert len(hm.group) == 2
for gw in hm.group: for gw in hm.group:
class pseudoexec: class pseudoexec:
args = [] args = []
def __init__(self, *args): def __init__(self, *args):
self.args.extend(args) self.args.extend(args)
def waitclose(self): def waitclose(self):
pass pass
gw.remote_exec = pseudoexec gw.remote_exec = pseudoexec
l = [] l = []
for gw in hm.group: for gw in hm.group:
@@ -95,8 +110,8 @@ class TestNodeManagerPopen:
assert dest.join("dir1", "dir2").check() assert dest.join("dir1", "dir2").check()
assert dest.join("dir1", "dir2", 'hello').check() assert dest.join("dir1", "dir2", 'hello').check()
def test_rsync_same_popen_twice(self, config, mysetup, def test_rsync_same_popen_twice(self, config, mysetup, hookrecorder,
hookrecorder, slavecontroller): slavecontroller):
source, dest = mysetup.source, mysetup.dest source, dest = mysetup.source, mysetup.dest
hm = NodeManager(config, ["popen//chdir=%s" % dest] * 2) hm = NodeManager(config, ["popen//chdir=%s" % dest] * 2)
hm.roots = [] hm.roots = []
@@ -110,6 +125,7 @@ class TestNodeManagerPopen:
assert call.gateways[0] in hm.group assert call.gateways[0] in hm.group
call = hookrecorder.popcall("pytest_xdist_rsyncfinish") call = hookrecorder.popcall("pytest_xdist_rsyncfinish")
class TestHRSync: class TestHRSync:
def test_hrsync_filter(self, mysetup): def test_hrsync_filter(self, mysetup):
source, _ = mysetup.source, mysetup.dest # noqa source, _ = mysetup.source, mysetup.dest # noqa
@@ -118,8 +134,7 @@ class TestHRSync:
source.ensure(".somedotfile", "moreentries") source.ensure(".somedotfile", "moreentries")
source.ensure("somedir", "editfile~") source.ensure("somedir", "editfile~")
syncer = HostRSync(source, ignores=NodeManager.DEFAULT_IGNORES) syncer = HostRSync(source, ignores=NodeManager.DEFAULT_IGNORES)
l = list(source.visit(rec=syncer.filter, l = list(source.visit(rec=syncer.filter, fil=syncer.filter))
fil=syncer.filter))
assert len(l) == 3 assert len(l) == 3
basenames = [x.basename for x in l] basenames = [x.basename for x in l]
assert 'dir' in basenames assert 'dir' in basenames
@@ -145,7 +160,7 @@ class TestNodeManager:
mysetup.source.ensure("dir1", "file1").write("hello") mysetup.source.ensure("dir1", "file1").write("hello")
config = testdir.parseconfig(mysetup.source) config = testdir.parseconfig(mysetup.source)
nodemanager = NodeManager(config, ["popen//chdir=%s" % mysetup.dest]) nodemanager = NodeManager(config, ["popen//chdir=%s" % mysetup.dest])
#assert nodemanager.config.topdir == source == config.topdir # assert nodemanager.config.topdir == source == config.topdir
nodemanager.makegateways() nodemanager.makegateways()
nodemanager.rsync_roots() nodemanager.rsync_roots()
p, = nodemanager.gwmanager.multi_exec( p, = nodemanager.gwmanager.multi_exec(
@@ -164,10 +179,8 @@ class TestNodeManager:
for rsyncroot in (dir1, source): for rsyncroot in (dir1, source):
dest.remove() dest.remove()
nodemanager = NodeManager(testdir.parseconfig( nodemanager = NodeManager(testdir.parseconfig(
"--tx", "popen//chdir=%s" % dest, "--tx", "popen//chdir=%s" % dest, "--rsyncdir", rsyncroot,
"--rsyncdir", rsyncroot, source, ))
source,
))
nodemanager.setup_nodes(None) # calls .rsync_roots() nodemanager.setup_nodes(None) # calls .rsync_roots()
if rsyncroot == source: if rsyncroot == source:
dest = dest.join("source") dest = dest.join("source")
@@ -230,7 +243,8 @@ class TestNodeManager:
assert not gwspec.chdir assert not gwspec.chdir
def test_ssh_setup_nodes(self, specssh, testdir): def test_ssh_setup_nodes(self, specssh, testdir):
testdir.makepyfile(__init__="", test_x=""" testdir.makepyfile(__init__="",
test_x="""
def test_one(): def test_one():
pass pass
""") """)

View File

@@ -21,8 +21,8 @@ commands=
[testenv:flakes] [testenv:flakes]
changedir= changedir=
deps = pytest-flakes>=0.2 deps = flake8
commands = py.test --flakes -m flakes testing xdist commands = flake8 setup.py testing xdist
[testenv:readme] [testenv:readme]
changedir = changedir =

View File

@@ -4,9 +4,10 @@ import py
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup("xdist", "distributed and subprocess testing") group = parser.getgroup("xdist", "distributed and subprocess testing")
group.addoption('--boxed', group.addoption(
action="store_true", dest="boxed", default=False, '--boxed',
help="box each test run in a separate process (unix)") action="store_true", dest="boxed", default=False,
help="box each test run in a separate process (unix)")
def pytest_runtest_protocol(item): def pytest_runtest_protocol(item):
@@ -16,6 +17,7 @@ def pytest_runtest_protocol(item):
item.ihook.pytest_runtest_logreport(report=rep) item.ihook.pytest_runtest_logreport(report=rep)
return True return True
def forked_run_report(item): def forked_run_report(item):
# for now, we run setup/teardown in the subprocess # for now, we run setup/teardown in the subprocess
# XXX optionally allow sharing of setup/teardown # XXX optionally allow sharing of setup/teardown
@@ -24,6 +26,7 @@ def forked_run_report(item):
import marshal import marshal
from xdist.remote import serialize_report from xdist.remote import serialize_report
from xdist.slavemanage import unserialize_report from xdist.slavemanage import unserialize_report
def runforked(): def runforked():
try: try:
reports = runtestprotocol(item, log=False) reports = runtestprotocol(item, log=False)
@@ -38,9 +41,10 @@ def forked_run_report(item):
return [unserialize_report("testreport", x) for x in report_dumps] return [unserialize_report("testreport", x) for x in report_dumps]
else: else:
if result.exitstatus == EXITSTATUS_TESTEXIT: if result.exitstatus == EXITSTATUS_TESTEXIT:
py.test.exit("forked test item %s raised Exit" %(item,)) py.test.exit("forked test item %s raised Exit" % (item,))
return [report_process_crash(item, result)] return [report_process_crash(item, result)]
def report_process_crash(item, result): def report_process_crash(item, result):
path, lineno = item._getfslineno() path, lineno = item._getfslineno()
info = ("%s:%s: running the test CRASHED with signal %d" % info = ("%s:%s: running the test CRASHED with signal %d" %

View File

@@ -257,8 +257,9 @@ class LoadScheduling:
assert node in self.node2pending assert node in self.node2pending
if self.collection_is_completed: if self.collection_is_completed:
# A new node has been added later, perhaps an original one died. # A new node has been added later, perhaps an original one died.
assert self.collection # .init_distribute() should have # .init_distribute() should have
# been called by now # been called by now
assert self.collection
if collection != self.collection: if collection != self.collection:
other_node = next(iter(self.node2collection.keys())) other_node = next(iter(self.node2collection.keys()))
msg = report_collection_diff(self.collection, msg = report_collection_diff(self.collection,
@@ -398,8 +399,9 @@ class LoadScheduling:
same_collection = False same_collection = False
self.log(msg) self.log(msg)
if self.config is not None: if self.config is not None:
rep = CollectReport(node.gateway.id, 'failed', longrepr=msg, rep = CollectReport(
result=[]) node.gateway.id, 'failed',
longrepr=msg, result=[])
self.config.hook.pytest_collectreport(report=rep) self.config.hook.pytest_collectreport(report=rep)
return same_collection return same_collection
@@ -641,7 +643,7 @@ class DSession:
""" """
if rep.when == "call" or (rep.when == "setup" and not rep.passed): if rep.when == "call" or (rep.when == "setup" and not rep.passed):
self.sched.remove_item(node, rep.item_index, rep.duration) self.sched.remove_item(node, rep.item_index, rep.duration)
#self.report_line("testreport %s: %s" %(rep.id, rep.status)) # self.report_line("testreport %s: %s" %(rep.id, rep.status))
rep.node = node rep.node = node
self.config.hook.pytest_runtest_logreport(report=rep) self.config.hook.pytest_runtest_logreport(report=rep)
self._handlefailures(rep) self._handlefailures(rep)
@@ -719,8 +721,8 @@ class TerminalDistReporter:
self.rewrite(self.getstatus()) self.rewrite(self.getstatus())
def getstatus(self): def getstatus(self):
parts = ["%s %s" %(spec.id, self._status[spec.id]) parts = ["%s %s" % (spec.id, self._status[spec.id])
for spec in self._specs] for spec in self._specs]
return " / ".join(parts) return " / ".join(parts)
def rewrite(self, line, newline=False): def rewrite(self, line, newline=False):
@@ -751,7 +753,7 @@ class TerminalDistReporter:
def pytest_testnodeready(self, node): def pytest_testnodeready(self, node):
if self.config.option.verbose > 0: if self.config.option.verbose > 0:
d = node.slaveinfo d = node.slaveinfo
infoline = "[%s] Python %s" %( infoline = "[%s] Python %s" % (
d['id'], d['id'],
d['version'].replace('\n', ' -- '),) d['version'].replace('\n', ' -- '),)
self.rewrite(infoline, newline=True) self.rewrite(infoline, newline=True)
@@ -760,13 +762,12 @@ class TerminalDistReporter:
def pytest_testnodedown(self, node, error): def pytest_testnodedown(self, node, error):
if not error: if not error:
return return
self.write_line("[%s] node down: %s" %(node.gateway.id, error)) self.write_line("[%s] node down: %s" % (node.gateway.id, error))
#def pytest_xdist_rsyncstart(self, source, gateways): # def pytest_xdist_rsyncstart(self, source, gateways):
# targets = ",".join([gw.id for gw in gateways]) # targets = ",".join([gw.id for gw in gateways])
# msg = "[%s] rsyncing: %s" %(targets, source) # msg = "[%s] rsyncing: %s" %(targets, source)
# self.write_line(msg) # self.write_line(msg)
#def pytest_xdist_rsyncfinish(self, source, gateways): # def pytest_xdist_rsyncfinish(self, source, gateways):
# targets = ", ".join(["[%s]" % gw.id for gw in gateways]) # targets = ", ".join(["[%s]" % gw.id for gw in gateways])
# self.write_line("rsyncfinish: %s -> %s" %(source, targets)) # self.write_line("rsyncfinish: %s -> %s" %(source, targets))

View File

@@ -7,23 +7,26 @@
the controlling process which should best never happen. the controlling process which should best never happen.
""" """
import py, pytest import py
import pytest
import sys import sys
import execnet import execnet
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup("xdist", "distributed and subprocess testing") group = parser.getgroup("xdist", "distributed and subprocess testing")
group._addoption('-f', '--looponfail', group._addoption(
action="store_true", dest="looponfail", default=False, '-f', '--looponfail',
help="run tests in subprocess, wait for modified files " action="store_true", dest="looponfail", default=False,
"and re-run failing test set until all pass.") help="run tests in subprocess, wait for modified files "
"and re-run failing test set until all pass.")
def pytest_cmdline_main(config): def pytest_cmdline_main(config):
if config.getoption("looponfail"): if config.getoption("looponfail"):
looponfail_main(config) looponfail_main(config)
return 2 # looponfail only can get stop with ctrl-C anyway return 2 # looponfail only can get stop with ctrl-C anyway
def looponfail_main(config): def looponfail_main(config):
@@ -34,7 +37,8 @@ def looponfail_main(config):
while 1: while 1:
remotecontrol.loop_once() remotecontrol.loop_once()
if not remotecontrol.failures and remotecontrol.wasfailing: if not remotecontrol.failures and remotecontrol.wasfailing:
continue # the last failures passed, let's immediately rerun all # the last failures passed, let's immediately rerun all
continue
repr_pytest_looponfailinfo( repr_pytest_looponfailinfo(
failreports=remotecontrol.failures, failreports=remotecontrol.failures,
rootdirs=rootdirs) rootdirs=rootdirs)
@@ -42,6 +46,7 @@ def looponfail_main(config):
except KeyboardInterrupt: except KeyboardInterrupt:
print() print()
class RemoteControl(object): class RemoteControl(object):
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
@@ -62,11 +67,13 @@ class RemoteControl(object):
raise ValueError("already have gateway %r" % self.gateway) raise ValueError("already have gateway %r" % self.gateway)
self.trace("setting up slave session") self.trace("setting up slave session")
self.gateway = self.initgateway() self.gateway = self.initgateway()
self.channel = channel = self.gateway.remote_exec(init_slave_session, self.channel = channel = self.gateway.remote_exec(
init_slave_session,
args=self.config.args, args=self.config.args,
option_dict=vars(self.config.option), option_dict=vars(self.config.option),
) )
remote_outchannel = channel.receive() remote_outchannel = channel.receive()
def write(s): def write(s):
out._file.write(s) out._file.write(s)
out._file.flush() out._file.flush()
@@ -102,7 +109,7 @@ class RemoteControl(object):
result = self.runsession() result = self.runsession()
failures, reports, collection_failed = result failures, reports, collection_failed = result
if collection_failed: if collection_failed:
pass # "Collection failed, keeping previous failure set" pass # "Collection failed, keeping previous failure set"
else: else:
uniq_failures = [] uniq_failures = []
for failure in failures: for failure in failures:
@@ -110,6 +117,7 @@ class RemoteControl(object):
uniq_failures.append(failure) uniq_failures.append(failure)
self.failures = uniq_failures self.failures = uniq_failures
def repr_pytest_looponfailinfo(failreports, rootdirs): def repr_pytest_looponfailinfo(failreports, rootdirs):
tr = py.io.TerminalWriter() tr = py.io.TerminalWriter()
if failreports: if failreports:
@@ -119,11 +127,12 @@ def repr_pytest_looponfailinfo(failreports, rootdirs):
tr.line(report, red=True) tr.line(report, red=True)
tr.sep("#", "waiting for changes", bold=True) tr.sep("#", "waiting for changes", bold=True)
for rootdir in rootdirs: for rootdir in rootdirs:
tr.line("### Watching: %s" %(rootdir,), bold=True) tr.line("### Watching: %s" % (rootdir,), bold=True)
def init_slave_session(channel, args, option_dict): def init_slave_session(channel, args, option_dict):
import os, sys import os
import sys
outchannel = channel.gateway.newchannel() outchannel = channel.gateway.newchannel()
sys.stdout = sys.stderr = outchannel.makefile('w') sys.stdout = sys.stderr = outchannel.makefile('w')
channel.send(outchannel) channel.send(outchannel)
@@ -136,13 +145,14 @@ def init_slave_session(channel, args, option_dict):
newpaths.append(p) newpaths.append(p)
sys.path[:] = newpaths sys.path[:] = newpaths
#fullwidth, hasmarkup = channel.receive() # fullwidth, hasmarkup = channel.receive()
from _pytest.config import Config from _pytest.config import Config
config = Config.fromdictargs(option_dict, list(args)) config = Config.fromdictargs(option_dict, list(args))
config.args = args config.args = args
from xdist.looponfail import SlaveFailSession from xdist.looponfail import SlaveFailSession
SlaveFailSession(config, channel).main() SlaveFailSession(config, channel).main()
class SlaveFailSession: class SlaveFailSession:
def __init__(self, config, channel): def __init__(self, config, channel):
self.config = config self.config = config
@@ -165,7 +175,8 @@ class SlaveFailSession:
items = session.perform_collect(self.trails or None) items = session.perform_collect(self.trails or None)
except pytest.UsageError: except pytest.UsageError:
items = session.perform_collect(None) items = session.perform_collect(None)
hook.pytest_collection_modifyitems(session=session, config=session.config, items=items) hook.pytest_collection_modifyitems(
session=session, config=session.config, items=items)
hook.pytest_collection_finish(session=session) hook.pytest_collection_finish(session=session)
return True return True
@@ -183,7 +194,7 @@ class SlaveFailSession:
try: try:
command = self.channel.receive() command = self.channel.receive()
except KeyboardInterrupt: except KeyboardInterrupt:
return # in the slave we can't do much about this return # in the slave we can't do much about this
self.DEBUG("received", command) self.DEBUG("received", command)
self.current_command = command self.current_command = command
self.config.hook.pytest_cmdline_main(config=self.config) self.config.hook.pytest_cmdline_main(config=self.config)
@@ -195,14 +206,16 @@ class SlaveFailSession:
failreports.append(loc) failreports.append(loc)
self.channel.send((trails, failreports, self.collection_failed)) self.channel.send((trails, failreports, self.collection_failed))
class StatRecorder: class StatRecorder:
def __init__(self, rootdirlist): def __init__(self, rootdirlist):
self.rootdirlist = rootdirlist self.rootdirlist = rootdirlist
self.statcache = {} self.statcache = {}
self.check() # snapshot state self.check() # snapshot state
def fil(self, p): def fil(self, p):
return p.check(file=1, dotfile=0) and p.ext != ".pyc" return p.check(file=1, dotfile=0) and p.ext != ".pyc"
def rec(self, p): def rec(self, p):
return p.check(dotfile=0) return p.check(dotfile=0)
@@ -213,7 +226,7 @@ class StatRecorder:
return return
py.std.time.sleep(checkinterval) py.std.time.sleep(checkinterval)
def check(self, removepycfiles=True): def check(self, removepycfiles=True): # noqa, too complex
changed = False changed = False
statcache = self.statcache statcache = self.statcache
newstat = {} newstat = {}
@@ -227,8 +240,8 @@ class StatRecorder:
changed = True changed = True
else: else:
if oldstat: if oldstat:
if oldstat.mtime != curstat.mtime or \ if oldstat.mtime != curstat.mtime or \
oldstat.size != curstat.size: oldstat.size != curstat.size:
changed = True changed = True
py.builtin.print_("# MODIFIED", path) py.builtin.print_("# MODIFIED", path)
if removepycfiles and path.ext == ".py": if removepycfiles and path.ext == ".py":

View File

@@ -2,20 +2,26 @@
def pytest_xdist_setupnodes(config, specs): def pytest_xdist_setupnodes(config, specs):
""" called before any remote node is set up. """ """ called before any remote node is set up. """
def pytest_xdist_newgateway(gateway): def pytest_xdist_newgateway(gateway):
""" called on new raw gateway creation. """ """ called on new raw gateway creation. """
def pytest_xdist_rsyncstart(source, gateways): def pytest_xdist_rsyncstart(source, gateways):
""" called before rsyncing a directory to remote gateways takes place. """ """ called before rsyncing a directory to remote gateways takes place. """
def pytest_xdist_rsyncfinish(source, gateways): def pytest_xdist_rsyncfinish(source, gateways):
""" called after rsyncing a directory to remote gateways takes place. """ """ called after rsyncing a directory to remote gateways takes place. """
def pytest_configure_node(node): def pytest_configure_node(node):
""" configure node information before it gets instantiated. """ """ configure node information before it gets instantiated. """
def pytest_testnodeready(node): def pytest_testnodeready(node):
""" Test Node is ready to operate. """ """ Test Node is ready to operate. """
def pytest_testnodedown(node, error): def pytest_testnodedown(node, error):
""" Test Node is down. """ """ Test Node is down. """

View File

@@ -12,45 +12,56 @@ def parse_numprocesses(s):
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup("xdist", "distributed and subprocess testing") group = parser.getgroup("xdist", "distributed and subprocess testing")
group._addoption('-n', dest="numprocesses", metavar="numprocesses", group._addoption(
action="store", '-n', dest="numprocesses", metavar="numprocesses",
type=parse_numprocesses, action="store",
help="shortcut for '--dist=load --tx=NUM*popen', " type=parse_numprocesses,
"you can use 'auto' here for auto detection CPUs number on " help="shortcut for '--dist=load --tx=NUM*popen', "
"host system") "you can use 'auto' here for auto detection CPUs number on "
"host system")
group._addoption('--max-slave-restart', action="store", default=None, group._addoption('--max-slave-restart', action="store", default=None,
help="maximum number of slaves that can be restarted " help="maximum number of slaves that can be restarted "
"when crashed (set to zero to disable this feature)") "when crashed (set to zero to disable this feature)")
group._addoption('--dist', metavar="distmode", group._addoption(
action="store", choices=['load', 'each', 'no'], '--dist', metavar="distmode",
type="choice", dest="dist", default="no", action="store", choices=['load', 'each', 'no'],
help=("set mode for distributing tests to exec environments.\n\n" type="choice", dest="dist", default="no",
"each: send each test to each available environment.\n\n" help=("set mode for distributing tests to exec environments.\n\n"
"load: send each test to available environment.\n\n" "each: send each test to each available environment.\n\n"
"(default) no: run tests inprocess, don't distribute.")) "load: send each test to available environment.\n\n"
group._addoption('--tx', dest="tx", action="append", default=[], "(default) no: run tests inprocess, don't distribute."))
metavar="xspec", group._addoption(
help=("add a test execution environment. some examples: " '--tx', dest="tx", action="append", default=[],
"--tx popen//python=python2.5 --tx socket=192.168.1.102:8888 " metavar="xspec",
"--tx ssh=user@codespeak.net//chdir=testcache")) help=("add a test execution environment. some examples: "
group._addoption('-d', "--tx popen//python=python2.5 --tx socket=192.168.1.102:8888 "
action="store_true", dest="distload", default=False, "--tx ssh=user@codespeak.net//chdir=testcache"))
help="load-balance tests. shortcut for '--dist=load'") group._addoption(
group.addoption('--rsyncdir', action="append", default=[], metavar="DIR", '-d',
help="add directory for rsyncing to remote tx nodes.") action="store_true", dest="distload", default=False,
group.addoption('--rsyncignore', action="append", default=[], metavar="GLOB", help="load-balance tests. shortcut for '--dist=load'")
help="add expression for ignores when rsyncing to remote tx nodes.") group.addoption(
'--rsyncdir', action="append", default=[], metavar="DIR",
help="add directory for rsyncing to remote tx nodes.")
group.addoption(
'--rsyncignore', action="append", default=[], metavar="GLOB",
help="add expression for ignores when rsyncing to remote tx nodes.")
parser.addini('rsyncdirs', 'list of (relative) paths to be rsynced for' parser.addini(
' remote distributed testing.', type="pathlist") 'rsyncdirs', 'list of (relative) paths to be rsynced for'
parser.addini('rsyncignore', 'list of (relative) glob-style paths to be ignored ' ' remote distributed testing.', type="pathlist")
'for rsyncing.', type="pathlist") parser.addini(
parser.addini("looponfailroots", type="pathlist", 'rsyncignore', 'list of (relative) glob-style paths to be ignored '
'for rsyncing.', type="pathlist")
parser.addini(
"looponfailroots", type="pathlist",
help="directories to check for changes", default=[py.path.local()]) help="directories to check for changes", default=[py.path.local()])
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# distributed testing hooks # distributed testing hooks
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
def pytest_addhooks(pluginmanager): def pytest_addhooks(pluginmanager):
from xdist import newhooks from xdist import newhooks
# avoid warnings with pytest-2.8 # avoid warnings with pytest-2.8
@@ -73,6 +84,7 @@ def pytest_configure(config):
tr = config.pluginmanager.getplugin("terminalreporter") tr = config.pluginmanager.getplugin("terminalreporter")
tr.showfspath = False tr.showfspath = False
@pytest.mark.tryfirst @pytest.mark.tryfirst
def pytest_cmdline_main(config): def pytest_cmdline_main(config):
if config.option.numprocesses: if config.option.numprocesses:
@@ -85,7 +97,9 @@ def pytest_cmdline_main(config):
usepdb = config.option.usepdb # a core option usepdb = config.option.usepdb # a core option
if val("looponfail"): if val("looponfail"):
if usepdb: if usepdb:
raise pytest.UsageError("--pdb incompatible with --looponfail.") raise pytest.UsageError(
"--pdb incompatible with --looponfail.")
elif val("dist") != "no": elif val("dist") != "no":
if usepdb: if usepdb:
raise pytest.UsageError("--pdb incompatible with distributing tests.") raise pytest.UsageError(
"--pdb incompatible with distributing tests.")

View File

@@ -6,7 +6,9 @@
needs not to be installed in remote environments. needs not to be installed in remote environments.
""" """
import sys, os import sys
import os
class SlaveInteractor: class SlaveInteractor:
def __init__(self, config, channel): def __init__(self, config, channel):
@@ -72,7 +74,8 @@ class SlaveInteractor:
nextitem=nextitem) nextitem=nextitem)
def pytest_collection_finish(self, session): def pytest_collection_finish(self, session):
self.sendevent("collectionfinish", self.sendevent(
"collectionfinish",
topdir=str(session.fspath), topdir=str(session.fspath),
ids=[item.nodeid for item in session.items]) ids=[item.nodeid for item in session.items])
@@ -89,6 +92,7 @@ class SlaveInteractor:
data = serialize_report(report) data = serialize_report(report)
self.sendevent("collectreport", data=data) self.sendevent("collectreport", data=data)
def serialize_report(rep): def serialize_report(rep):
import py import py
d = rep.__dict__.copy() d = rep.__dict__.copy()
@@ -100,20 +104,22 @@ def serialize_report(rep):
if isinstance(d[name], py.path.local): if isinstance(d[name], py.path.local):
d[name] = str(d[name]) d[name] = str(d[name])
elif name == "result": elif name == "result":
d[name] = None # for now d[name] = None # for now
return d return d
def getinfodict(): def getinfodict():
import platform import platform
return dict( return dict(
version = sys.version, version=sys.version,
version_info = tuple(sys.version_info), version_info=tuple(sys.version_info),
sysplatform = sys.platform, sysplatform=sys.platform,
platform = platform.platform(), platform=platform.platform(),
executable = sys.executable, executable=sys.executable,
cwd = os.getcwd(), cwd=os.getcwd(),
) )
def remote_initconfig(option_dict, args): def remote_initconfig(option_dict, args):
from _pytest.config import Config from _pytest.config import Config
option_dict['plugins'].append("no:terminal") option_dict['plugins'].append("no:terminal")
@@ -131,14 +137,15 @@ if __name__ == '__channelexec__':
channel = channel # noqa channel = channel # noqa
# python3.2 is not concurrent import safe, so let's play it safe # python3.2 is not concurrent import safe, so let's play it safe
# https://bitbucket.org/hpk42/pytest/issue/347/pytest-xdist-and-python-32 # https://bitbucket.org/hpk42/pytest/issue/347/pytest-xdist-and-python-32
if sys.version_info[:2] == (3,2): if sys.version_info[:2] == (3, 2):
os.environ["PYTHONDONTWRITEBYTECODE"] = "1" os.environ["PYTHONDONTWRITEBYTECODE"] = "1"
slaveinput,args,option_dict = channel.receive() slaveinput, args, option_dict = channel.receive()
importpath = os.getcwd() importpath = os.getcwd()
sys.path.insert(0, importpath) # XXX only for remote situations sys.path.insert(0, importpath) # XXX only for remote situations
os.environ['PYTHONPATH'] = (importpath + os.pathsep + os.environ['PYTHONPATH'] = (
importpath + os.pathsep +
os.environ.get('PYTHONPATH', '')) os.environ.get('PYTHONPATH', ''))
#os.environ['PYTHONPATH'] = importpath # os.environ['PYTHONPATH'] = importpath
import py import py
config = remote_initconfig(option_dict, args) config = remote_initconfig(option_dict, args)
config.slaveinput = slaveinput config.slaveinput = slaveinput

View File

@@ -1,16 +1,19 @@
import fnmatch import fnmatch
import os import os
import re
import py import py
import pytest import pytest
import execnet import execnet
import xdist.remote import xdist.remote
from _pytest import runner # XXX load dynamically from _pytest import runner # XXX load dynamically
class NodeManager(object): class NodeManager(object):
EXIT_TIMEOUT = 10 EXIT_TIMEOUT = 10
DEFAULT_IGNORES = ['.*', '*.pyc', '*.pyo', '*~'] DEFAULT_IGNORES = ['.*', '*.pyc', '*.pyo', '*~']
def __init__(self, config, specs=None, defaultchdir="pyexecnetcache"): def __init__(self, config, specs=None, defaultchdir="pyexecnetcache"):
self.config = config self.config = config
self._nodesready = py.std.threading.Event() self._nodesready = py.std.threading.Event()
@@ -79,11 +82,12 @@ class NodeManager(object):
break break
else: else:
return [] return []
import pytest, _pytest import pytest
import _pytest
pytestpath = pytest.__file__.rstrip("co") pytestpath = pytest.__file__.rstrip("co")
pytestdir = py.path.local(_pytest.__file__).dirpath() pytestdir = py.path.local(_pytest.__file__).dirpath()
config = self.config config = self.config
candidates = [py._pydir,pytestpath,pytestdir] candidates = [py._pydir, pytestpath, pytestdir]
candidates += config.option.rsyncdir candidates += config.option.rsyncdir
rsyncroots = config.getini("rsyncdirs") rsyncroots = config.getini("rsyncdirs")
if rsyncroots: if rsyncroots:
@@ -92,7 +96,7 @@ class NodeManager(object):
for root in candidates: for root in candidates:
root = py.path.local(root).realpath() root = py.path.local(root).realpath()
if not root.check(): if not root.check():
raise pytest.UsageError("rsyncdir doesn't exist: %r" %(root,)) raise pytest.UsageError("rsyncdir doesn't exist: %r" % (root,))
if root not in roots: if root not in roots:
roots.append(root) roots.append(root)
return roots return roots
@@ -124,6 +128,7 @@ class NodeManager(object):
return return
if (spec, source) in self._rsynced_specs: if (spec, source) in self._rsynced_specs:
return return
def finished(): def finished():
if notify: if notify:
notify("rsyncrootready", spec, source) notify("rsyncrootready", spec, source)
@@ -139,19 +144,23 @@ class NodeManager(object):
gateways=[gateway], gateways=[gateway],
) )
class HostRSync(execnet.RSync): class HostRSync(execnet.RSync):
""" RSyncer that filters out common files """ RSyncer that filters out common files
""" """
def __init__(self, sourcedir, *args, **kwargs): def __init__(self, sourcedir, *args, **kwargs):
self._synced = {} self._synced = {}
self._ignores = kwargs.pop('ignores', None) or [] self._ignores = []
ignores = kwargs.pop('ignores', None) or []
for x in ignores:
x = getattr(x, 'strpath', x)
self.ignores.append(re.compile(fnmatch.translate(x)))
super(HostRSync, self).__init__(sourcedir=sourcedir, **kwargs) super(HostRSync, self).__init__(sourcedir=sourcedir, **kwargs)
def filter(self, path): def filter(self, path):
path = py.path.local(path) path = py.path.local(path)
for x in self._ignores: for check in self._ignores:
x = getattr(x, 'strpath', x) if check(path.basename) or check(path.strpath):
if fnmatch.fnmatch(path.basename, x) or fnmatch.fnmatch(path.strpath, x):
return False return False
else: else:
return True return True
@@ -187,6 +196,7 @@ def make_reltoroot(roots, args):
l.append(splitcode.join(parts)) l.append(splitcode.join(parts))
return l return l
class SlaveController(object): class SlaveController(object):
ENDMARK = -1 ENDMARK = -1
@@ -202,7 +212,7 @@ class SlaveController(object):
py.log.setconsumer(self.log._keywords, None) py.log.setconsumer(self.log._keywords, None)
def __repr__(self): def __repr__(self):
return "<%s %s>" %(self.__class__.__name__, self.gateway.id,) return "<%s %s>" % (self.__class__.__name__, self.gateway.id,)
def setup(self): def setup(self):
self.log("setting up slave session") self.log("setting up slave session")
@@ -219,7 +229,8 @@ class SlaveController(object):
self.channel = self.gateway.remote_exec(xdist.remote) self.channel = self.gateway.remote_exec(xdist.remote)
self.channel.send((self.slaveinput, args, option_dict)) self.channel.send((self.slaveinput, args, option_dict))
if self.putevent: if self.putevent:
self.channel.setcallback(self.process_from_remote, self.channel.setcallback(
self.process_from_remote,
endmarker=self.ENDMARK) endmarker=self.ENDMARK)
def ensure_teardown(self): def ensure_teardown(self):
@@ -227,11 +238,11 @@ class SlaveController(object):
if not self.channel.isclosed(): if not self.channel.isclosed():
self.log("closing", self.channel) self.log("closing", self.channel)
self.channel.close() self.channel.close()
#del self.channel # del self.channel
if hasattr(self, 'gateway'): if hasattr(self, 'gateway'):
self.log("exiting", self.gateway) self.log("exiting", self.gateway)
self.gateway.exit() self.gateway.exit()
#del self.gateway # del self.gateway
def send_runtest_some(self, indices): def send_runtest_some(self, indices):
self.sendcommand("runtests", indices=indices) self.sendcommand("runtests", indices=indices)
@@ -255,7 +266,7 @@ class SlaveController(object):
self.log("queuing %s(**%s)" % (eventname, kwargs)) self.log("queuing %s(**%s)" % (eventname, kwargs))
self.putevent((eventname, kwargs)) self.putevent((eventname, kwargs))
def process_from_remote(self, eventcall): def process_from_remote(self, eventcall): # noqa too complex
""" this gets called for each object we receive from """ this gets called for each object we receive from
the other side and if the channel closes. the other side and if the channel closes.
@@ -268,13 +279,13 @@ class SlaveController(object):
err = self.channel._getremoteerror() err = self.channel._getremoteerror()
if not self._down: if not self._down:
if not err or isinstance(err, EOFError): if not err or isinstance(err, EOFError):
err = "Not properly terminated" # lost connection? err = "Not properly terminated" # lost connection?
self.notify_inproc("errordown", node=self, error=err) self.notify_inproc("errordown", node=self, error=err)
self._down = True self._down = True
return return
eventname, kwargs = eventcall eventname, kwargs = eventcall
if eventname in ("collectionstart"): if eventname in ("collectionstart"):
self.log("ignoring %s(%s)" %(eventname, kwargs)) self.log("ignoring %s(%s)" % (eventname, kwargs))
elif eventname == "slaveready": elif eventname == "slaveready":
self.notify_inproc(eventname, node=self, **kwargs) self.notify_inproc(eventname, node=self, **kwargs)
elif eventname == "slavefinished": elif eventname == "slavefinished":
@@ -283,7 +294,8 @@ class SlaveController(object):
self.notify_inproc("slavefinished", node=self) self.notify_inproc("slavefinished", node=self)
elif eventname == "logstart": elif eventname == "logstart":
self.notify_inproc(eventname, node=self, **kwargs) self.notify_inproc(eventname, node=self, **kwargs)
elif eventname in ("testreport", "collectreport", "teardownreport"): elif eventname in (
"testreport", "collectreport", "teardownreport"):
item_index = kwargs.pop("item_index", None) item_index = kwargs.pop("item_index", None)
rep = unserialize_report(eventname, kwargs['data']) rep = unserialize_report(eventname, kwargs['data'])
if item_index is not None: if item_index is not None:
@@ -292,7 +304,7 @@ class SlaveController(object):
elif eventname == "collectionfinish": elif eventname == "collectionfinish":
self.notify_inproc(eventname, node=self, ids=kwargs['ids']) self.notify_inproc(eventname, node=self, ids=kwargs['ids'])
else: else:
raise ValueError("unknown event: %s" %(eventname,)) raise ValueError("unknown event: %s" % (eventname,))
except KeyboardInterrupt: except KeyboardInterrupt:
# should not land in receiver-thread # should not land in receiver-thread
raise raise
@@ -301,6 +313,7 @@ class SlaveController(object):
py.builtin.print_("!" * 20, excinfo) py.builtin.print_("!" * 20, excinfo)
self.config.pluginmanager.notify_exception(excinfo) self.config.pluginmanager.notify_exception(excinfo)
def unserialize_report(name, reportdict): def unserialize_report(name, reportdict):
if name == "testreport": if name == "testreport":
return runner.TestReport(**reportdict) return runner.TestReport(**reportdict)