[py-svn] py-trunk commit 91cf219b3d46: fix sessionstart/sessionfinish handling at the slave side, set "session.nodeid" to id of the slave and make sure "final" teardown failures are reported nicely. fixes issue66.

commits-noreply at bitbucket.org commits-noreply at bitbucket.org
Mon Jan 11 17:16:56 CET 2010


# HG changeset patch -- Bitbucket.org
# Project py-trunk
# URL http://bitbucket.org/hpk42/py-trunk/overview/
# User holger krekel <holger at merlinux.eu>
# Date 1263226147 -3600
# Node ID 91cf219b3d4659119d7e7df3162c1de0e6332b69
# Parent 0d2090ccd84c7180d64e051c2c23a2f8ee00261d
fix sessionstart/sessionfinish handling at the slave side, set "session.nodeid" to id of the slave and make sure "final" teardown failures are reported nicely.  fixes issue66.

--- a/ISSUES.txt
+++ b/ISSUES.txt
@@ -42,16 +42,6 @@ test: testing/pytest/dist/test_dsession.
 Call gateway group termination with a small timeout if available. 
 Should make dist-testing less likely to leave lost processes.
 
-dist-testing: fix session hook / setup calling
------------------------------------------------------
-tags: bug 1.2
-
-Currently pytest_sessionstart and finish are called 
-on the master node and not on the slaves.  Call
-it on slaves and provide a session.nodeid which defaults
-to None for the master and contains the gateway id 
-for slaves. 
-
 have --report=xfailed[-detail] report the actual tracebacks 
 ------------------------------------------------------------------
 tags: feature

--- a/py/impl/test/dist/txnode.py
+++ b/py/impl/test/dist/txnode.py
@@ -3,6 +3,7 @@
 """
 import py
 from py.impl.test.dist.mypickle import PickleChannel
+from py.impl.test import outcome
 
 class TXNode(object):
     """ Represents a Test Execution environment in the controlling process. 
@@ -55,12 +56,12 @@ class TXNode(object):
             elif eventname == "slavefinished":
                 self._down = True
                 self.notify("pytest_testnodedown", error=None, node=self)
-            elif eventname == "pytest_runtest_logreport":
-                rep = kwargs['report']
-                rep.node = self
-                self.notify("pytest_runtest_logreport", report=rep)
+            elif eventname in ("pytest_runtest_logreport", 
+                               "pytest__teardown_final_logerror"):
+                kwargs['report'].node = self
+                self.notify(eventname, **kwargs)
             else:
-                self.notify(eventname, *args, **kwargs)
+                self.notify(eventname, **kwargs)
         except KeyboardInterrupt: 
             # should not land in receiver-thread
             raise 
@@ -99,7 +100,7 @@ def install_slave(gateway, config):
         basetemp = py.path.local.make_numbered_dir(prefix="slave-", 
             keep=0, rootdir=popenbase)
         basetemp = str(basetemp)
-    channel.send((config, basetemp))
+    channel.send((config, basetemp, gateway.id))
     return channel
 
 class SlaveNode(object):
@@ -115,9 +116,12 @@ class SlaveNode(object):
     def pytest_runtest_logreport(self, report):
         self.sendevent("pytest_runtest_logreport", report=report)
 
+    def pytest__teardown_final_logerror(self, report):
+        self.sendevent("pytest__teardown_final_logerror", report=report)
+
     def run(self):
         channel = self.channel
-        self.config, basetemp = channel.receive()
+        self.config, basetemp, self.nodeid = channel.receive()
         if basetemp:
             self.config.basetemp = py.path.local(basetemp)
         self.config.pluginmanager.do_configure(self.config)
@@ -125,22 +129,27 @@ class SlaveNode(object):
         self.runner = self.config.pluginmanager.getplugin("pytest_runner")
         self.sendevent("slaveready")
         try:
+            self.config.hook.pytest_sessionstart(session=self)
             while 1:
                 task = channel.receive()
                 if task is None: 
-                    self.sendevent("slavefinished")
                     break
                 if isinstance(task, list):
                     for item in task:
                         self.run_single(item=item)
                 else:
                     self.run_single(item=task)
+            self.config.hook.pytest_sessionfinish(
+                session=self, 
+                exitstatus=outcome.EXIT_OK)
         except KeyboardInterrupt:
             raise
         except:
             er = py.code.ExceptionInfo().getrepr(funcargs=True, showlocals=True)
             self.sendevent("pytest_internalerror", excrepr=er)
             raise
+        else:
+            self.sendevent("slavefinished")
 
     def run_single(self, item):
         call = self.runner.CallInfo(item._checkcollectable, when='setup')

--- a/py/impl/test/dist/dsession.py
+++ b/py/impl/test/dist/dsession.py
@@ -127,6 +127,12 @@ class DSession(Session):
         elif eventname == "pytest_runtest_logreport":
             # might be some teardown report
             self.config.hook.pytest_runtest_logreport(**kwargs)
+        elif eventname == "pytest_internalerror":
+            self.config.hook.pytest_internalerror(**kwargs)
+            loopstate.exitstatus = outcome.EXIT_INTERNALERROR
+        elif eventname == "pytest__teardown_final_logerror":
+            self.config.hook.pytest__teardown_final_logerror(**kwargs)
+            loopstate.exitstatus = outcome.EXIT_TESTSFAILED
         if not self.node2pending:
             # finished
             if loopstate.testsfailed:

--- a/py/plugin/pytest_runner.py
+++ b/py/plugin/pytest_runner.py
@@ -68,6 +68,8 @@ def pytest_runtest_teardown(item):
 def pytest__teardown_final(session):
     call = CallInfo(session.config._setupstate.teardown_all, when="teardown")
     if call.excinfo:
+        ntraceback = call.excinfo.traceback .cut(excludepath=py._pydir)
+        call.excinfo.traceback = ntraceback.filter()
         rep = TeardownErrorReport(call.excinfo)
         return rep 
 

--- a/py/impl/test/session.py
+++ b/py/impl/test/session.py
@@ -13,10 +13,7 @@ Item = py.test.collect.Item
 Collector = py.test.collect.Collector
 
 class Session(object): 
-    """ 
-        Session drives the collection and running of tests
-        and generates test events for reporters. 
-    """ 
+    nodeid = ""
     def __init__(self, config):
         self.config = config
         self.pluginmanager = config.pluginmanager # shortcut 

--- a/CHANGELOG
+++ b/CHANGELOG
@@ -64,6 +64,10 @@ Changes between 1.X and 1.1.1
 
 - fix assert reinterpreation that sees a call containing "keyword=..."
 
+- fix issue66: invoke pytest_sessionstart and pytest_sessionfinish 
+  hooks on slaves during dist-testing, report module/session teardown 
+  hooks correctly.
+
 - fix issue65: properly handle dist-testing if no 
   execnet/py lib installed remotely. 
 

--- a/testing/pytest/dist/test_dsession.py
+++ b/testing/pytest/dist/test_dsession.py
@@ -440,19 +440,66 @@ def test_teardownfails_one_function(test
         "*1 passed*1 error*"
     ])
 
- at py.test.mark.xfail 
+ at py.test.mark.xfail
 def test_terminate_on_hangingnode(testdir):
     p = testdir.makeconftest("""
         def pytest__teardown_final(session):
-            if session.nodeid: # running on slave
+            if session.nodeid == "my": # running on slave
                 import time
-                time.sleep(2)
+                time.sleep(3)
     """)
-    result = testdir.runpytest(p, '--dist=each', '--tx=popen')
+    result = testdir.runpytest(p, '--dist=each', '--tx=popen//id=my')
     assert result.duration < 2.0 
     result.stdout.fnmatch_lines([
-        "*0 passed*",
+        "*killed*my*",
     ])
 
 
 
+def test_session_hooks(testdir):
+    testdir.makeconftest("""
+        import sys
+        def pytest_sessionstart(session):
+            sys.pytestsessionhooks = session
+        def pytest_sessionfinish(session):
+            f = open(session.nodeid or "master", 'w')
+            f.write("xy")
+            f.close()
+            # let's fail on the slave
+            if session.nodeid: 
+                raise ValueError(42)
+    """) 
+    p = testdir.makepyfile("""
+        import sys
+        def test_hello():
+            assert hasattr(sys, 'pytestsessionhooks')
+    """)
+    result = testdir.runpytest(p, "--dist=each", "--tx=popen//id=my1")
+    result.stdout.fnmatch_lines([
+        "*ValueError*",
+        "*1 passed*",
+    ])
+    assert result.ret 
+    d = result.parseoutcomes()
+    assert d['passed'] == 1
+    assert testdir.tmpdir.join("my1").check()
+    assert testdir.tmpdir.join("master").check()
+
+def test_funcarg_teardown_failure(testdir):
+    p = testdir.makepyfile("""
+        def pytest_funcarg__myarg(request):
+            def teardown(val):
+                raise ValueError(val)
+            return request.cached_setup(setup=lambda: 42, teardown=teardown, 
+                scope="module")
+        def test_hello(myarg):
+            pass
+    """)
+    result = testdir.runpytest(p, "-n1")
+    assert result.ret
+    result.stdout.fnmatch_lines([
+        "*ValueError*42*",
+        "*1 passed*1 error*",
+    ])
+
+



More information about the pytest-commit mailing list