Revision history for this message
Gavin Panella (allenap) wrote : | # |
1 | === modified file 'lib/lp/services/job/tests/test_runner.py' |
2 | --- lib/lp/services/job/tests/test_runner.py 2010-03-09 14:28:11 +0000 |
3 | +++ lib/lp/services/job/tests/test_runner.py 2010-03-11 16:36:43 +0000 |
4 | @@ -27,7 +27,7 @@ |
5 | from lp.services.job.model.job import Job |
6 | from lp.services.job.runner import ( |
7 | BaseRunnableJob, JobCronScript, JobRunner, TwistedJobRunner) |
8 | -from lp.testing import TestCaseWithFactory, ZopeIsolatedTest |
9 | +from lp.testing import TestCaseWithFactory, ZopeTestInSubProcess |
10 | from lp.testing.mail_helpers import pop_notifications |
11 | |
12 | |
13 | @@ -301,7 +301,7 @@ |
14 | self.entries.append(input) |
15 | |
16 | |
17 | -class TestTwistedJobRunner(ZopeIsolatedTest, TestCaseWithFactory): |
18 | +class TestTwistedJobRunner(ZopeTestInSubProcess, TestCaseWithFactory): |
19 | |
20 | layer = LaunchpadZopelessLayer |
21 | |
22 | @@ -326,7 +326,7 @@ |
23 | self.assertIn('Job ran too long.', oops.value) |
24 | |
25 | |
26 | -class TestJobCronScript(ZopeIsolatedTest, TestCaseWithFactory): |
27 | +class TestJobCronScript(ZopeTestInSubProcess, TestCaseWithFactory): |
28 | |
29 | layer = LaunchpadZopelessLayer |
30 | |
31 | |
32 | === modified file 'lib/lp/testing/__init__.py' |
33 | --- lib/lp/testing/__init__.py 2010-03-09 14:28:11 +0000 |
34 | +++ lib/lp/testing/__init__.py 2010-03-11 16:36:43 +0000 |
35 | @@ -29,7 +29,7 @@ |
36 | 'validate_mock_class', |
37 | 'WindmillTestCase', |
38 | 'with_anonymous_login', |
39 | - 'ZopeIsolatedTest', |
40 | + 'ZopeTestInSubProcess', |
41 | ] |
42 | |
43 | import copy |
44 | @@ -590,7 +590,7 @@ |
45 | self.client.open(url=u'http://launchpad.dev:8085') |
46 | |
47 | |
48 | -class ZopeIsolatedTest: |
49 | +class ZopeTestInSubProcess: |
50 | """Run tests in a sub-process, respecting Zope idiosyncrasies. |
51 | |
52 | Use this as a mixin with an interesting `TestCase` or `TestSuite` |
53 | @@ -631,7 +631,7 @@ |
54 | # obtain the result. |
55 | result = testtools.MultiTestResult( |
56 | result, subunit.TestProtocolClient(fdwrite)) |
57 | - super(ZopeIsolatedTest, self).run(result) |
58 | + super(ZopeTestInSubProcess, self).run(result) |
59 | fdwrite.flush() |
60 | sys.stdout.flush() |
61 | sys.stderr.flush() |
62 | |
63 | === added file 'lib/lp/testing/tests/test_zope_test_in_subprocess.py' |
64 | --- lib/lp/testing/tests/test_zope_test_in_subprocess.py 1970-01-01 00:00:00 +0000 |
65 | +++ lib/lp/testing/tests/test_zope_test_in_subprocess.py 2010-03-11 21:36:45 +0000 |
66 | @@ -0,0 +1,118 @@ |
67 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
68 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
69 | + |
70 | +"""Test `lp.testing.ZopeTestInSubProcess`.""" |
71 | + |
72 | +__metaclass__ = type |
73 | + |
74 | +import functools |
75 | +import os |
76 | +import unittest |
77 | + |
78 | +from lp.testing import ZopeTestInSubProcess |
79 | + |
80 | + |
81 | +def record_pid(method): |
82 | + """Decorator that records the pid at method invocation. |
83 | + |
84 | + Will probably only DTRT with class methods or bound instance |
85 | + methods. |
86 | + """ |
87 | + @functools.wraps(method) |
88 | + def wrapper(self, *args, **kwargs): |
89 | + setattr(self, 'pid_in_%s' % method.__name__, os.getpid()) |
90 | + return method(self, *args, **kwargs) |
91 | + return wrapper |
92 | + |
93 | + |
94 | +class TestZopeTestInSubProcessLayer: |
95 | + """Helper to test `ZopeTestInSubProcess`. |
96 | + |
97 | + Asserts that layers are set up and torn down in the expected way, |
98 | + namely that setUp() and tearDown() are called in the parent |
99 | + process, and testSetUp() and testTearDown() are called in the |
100 | + child process. |
101 | + |
102 | + The assertions for tearDown() and testTearDown() must be done here |
103 | + because the test case runs before these methods are called. In the |
104 | + interests of symmetry and clarity, the assertions for setUp() and |
105 | + testSetUp() are done here too. |
106 | + """ |
107 | + |
108 | + @record_pid |
109 | + def __init__(self, test): |
110 | + self.test = test |
111 | + # These are needed to satisfy the requirements of the |
112 | + # byzantine Zope layer machinery. |
113 | + self.__name__ = self.__class__.__name__ |
114 | + self.__bases__ = self.__class__.__bases__ |
115 | + |
116 | + @record_pid |
117 | + def setUp(self): |
118 | + # Runs in the parent process. |
119 | + assert self.pid_in___init__ == self.pid_in_setUp, ( |
120 | + "layer.setUp() not called in parent process.") |
121 | + |
122 | + @record_pid |
123 | + def tearDown(self): |
124 | + # Runs in the parent process. |
125 | + assert self.pid_in___init__ == self.pid_in_tearDown, ( |
126 | + "layer.tearDown() not called in parent process.") |
127 | + |
128 | + @record_pid |
129 | + def testSetUp(self): |
130 | + # Runs in the child process. |
131 | + assert self.pid_in___init__ != self.pid_in_testSetUp, ( |
132 | + "layer.testSetUp() called in parent process.") |
133 | + |
134 | + @record_pid |
135 | + def testTearDown(self): |
136 | + # Runs in the child process. |
137 | + assert self.pid_in_testSetUp == self.pid_in_testTearDown, ( |
138 | + "layer.testTearDown() not called in same process as testSetUp().") |
139 | + |
140 | + |
141 | +class TestZopeTestInSubProcess(ZopeTestInSubProcess, unittest.TestCase): |
142 | + """Test `ZopeTestInSubProcess`. |
143 | + |
144 | + Assert that setUp(), test() and tearDown() are called in the child |
145 | + process. |
146 | + """ |
147 | + |
148 | + @record_pid |
149 | + def __init__(self, method_name='runTest'): |
150 | + # Runs in the parent process. |
151 | + super(TestZopeTestInSubProcess, self).__init__(method_name) |
152 | + self.layer = TestZopeTestInSubProcessLayer(self) |
153 | + |
154 | + @record_pid |
155 | + def setUp(self): |
156 | + # Runs in the child process. |
157 | + super(TestZopeTestInSubProcess, self).setUp() |
158 | + self.failUnlessEqual( |
159 | + self.layer.pid_in_testSetUp, self.pid_in_setUp, |
160 | + "setUp() not called in same process as layer.testSetUp().") |
161 | + |
162 | + @record_pid |
163 | + def test(self): |
164 | + # Runs in the child process. |
165 | + self.failUnlessEqual( |
166 | + self.pid_in_setUp, self.pid_in_test, |
167 | + "test method not run in same process as setUp().") |
168 | + |
169 | + @record_pid |
170 | + def tearDown(self): |
171 | + # Runs in the child process. |
172 | + super(TestZopeTestInSubProcess, self).tearDown() |
173 | + self.failUnlessEqual( |
174 | + self.pid_in_setUp, self.pid_in_tearDown, |
175 | + "tearDown() not run in same process as setUp().") |
176 | + |
177 | + |
178 | +def test_suite(): |
179 | + suite = unittest.TestSuite() |
180 | + loader = unittest.TestLoader() |
181 | + suite.addTests( |
182 | + loader.loadTestsFromTestCase( |
183 | + TestZopeTestInSubProcess)) |
184 | + return suite |
On Wed, 10 Mar 2010 14:15:31 -0000
Björn Tillenius <email address hidden> wrote:
... tCase is now known as ZopeIsolatedTest. The original IsolatedTestCas e, which does a very
> > ZopeIsolatedTes
> > name form came from subunit.
> > similar job. I'm happy to rename it though.
>
> I would probably have SubProcess, or Fork, in the name, to make it more
> clear in what way it provides the isolation.
It's now ZopeTestInSubPr ocess.
...
> > > I'm a bit interested in how this works with layers. Would you run away
> > > screaming if I asked you to add some tests demonstrating this? :)
Have a look at the new unit test I've added.
Basically, layer setUp() and tearDown() get called in the parent,
testSetUp() and testTearDown() in the child. For the test case,
__init__() is called in the parent, setUp(), tearDown() and test
methods are called in the child.
> > > It'd be interesting to see in which process the setUp(), testSetUp(),
> > > tearDown(), and testTearDown() methods of the layer are executed. I'm
> > > wondering if we can see subtle errors due to doing the wrong things in
> > > there.
Yes, there probably will be incompatibilities with some layers :-/
...
> > If it's mixed-in with a TestSuite I think it gets more complicated.
I haven't written tests for this yet. I may not do so (and restrict ocess to just TestCase for now).
ZopeTestInSubPr
...
> > > Also, how this this TestClass related to TwistedLayer? Oh right, that
> > > leads me to another question. You say that the reactor can't be
> > > restarted easily. But can it be shared between tests, just like the
> > > component architecture?
> >
> > That's an interesting idea. reactor.run() blocks, but I don't know of
> > any reason why it couldn't be started in a thread. I might get sent to
> > hell for it, but at least it's warmer than here.
>
> If it could, than maybe it would be better to use a layer instead, since
> that what we use elsewhere.
I haven't thought about this yet, but I will.
> Some other questions popped into my mind. What happens if you specify
> -D, and a test in the forked process fails?
This works as normal; you get a pdb prompt in the right place in the
child process.
> How well does this play with profiling? Do you still get all profiling
> information if you specify --profile=cProfile?
Not tried this yet. Not actually sure how to test this; it's a very
long time since I did any profiling.
So, I still have some work to do!
Gavin.