Imported Upstream version 1.1.2
[platform/upstream/python-nose.git] / doc / doc_tests / test_multiprocess / multiprocess.rst
1 Parallel Testing with nose
2 --------------------------
3
4 .. Note ::
5
6    Use of the multiprocess plugin on python 2.5 or earlier requires
7    the multiprocessing_ module, available from PyPI and at
8    http://code.google.com/p/python-multiprocessing/.
9
10 ..
11
12 Using the `nose.plugin.multiprocess` plugin, you can parallelize a
13 test run across a configurable number of worker processes. While this can
14 speed up CPU-bound test runs, it is mainly useful for IO-bound tests
15 that spend most of their time waiting for data to arrive from someplace
16 else and can benefit from parallelization.
17
18 .. _multiprocessing : http://code.google.com/p/python-multiprocessing/
19
20 How tests are distributed
21 =========================
22
23 The ideal case would be to dispatch each test to a worker process separately,
24 and to have enough worker processes that the entire test run takes only as
25 long as the slowest test. This ideal is not attainable in all cases, however,
26 because many test suites depend on context (class, module or package)
27 fixtures.
28
29 Some context fixtures are re-entrant -- that is, they can be called many times
30 concurrently. Other context fixtures can be shared among tests running in
31 different processes. Still others must be run once and only once for a given
32 set of tests, and must be in the same process as the tests themselves.
33
34 The plugin can't know the difference between these types of context fixtures
35 unless you tell it, so the default behavior is to dispatch the entire context
36 suite to a worker as a unit. This way, the fixtures are run once, in the same
37 process as the tests. (That, of course, is how they are run when the plugin
38 is not active: All tests are run in a single process.)
39
40 Controlling distribution
41 ^^^^^^^^^^^^^^^^^^^^^^^^
42
43 There are two context-level variables that you can use to control this default
44 behavior.
45
46 If a context's fixtures are re-entrant, set ``_multiprocess_can_split_ = True``
47 in the context, and the plugin will dispatch tests in suites bound to that
48 context as if the context had no fixtures. This means that the fixtures will
49 execute multiple times, typically once per test, and concurrently.
50
51 For example, a module that contains re-entrant fixtures might look like::
52
53   _multiprocess_can_split_ = True
54
55   def setup():
56       ...
57
58 A class might look like::
59
60   class TestClass:
61       _multiprocess_can_split_ = True
62
63       @classmethod
64       def setup_class(cls):
65           ...
66
67 Alternatively, if a context's fixtures may only be run once, or may not run
68 concurrently, but *may* be shared by tests running in different processes
69 -- for instance a package-level fixture that starts an external http server or
70 initializes a shared database -- then set ``_multiprocess_shared_ = True`` in
71 the context. Fixtures for contexts so marked will execute in the primary nose
72 process, and tests in those contexts will be individually dispatched to run in
73 parallel.
74
75 A module with shareable fixtures might look like::
76
77   _multiprocess_shared_ = True
78
79   def setup():
80       ...
81
82 A class might look like::
83
84   class TestClass:
85       _multiprocess_shared_ = True
86
87       @classmethod
88       def setup_class(cls):
89           ...
90
91 These options are mutually exclusive: you can't mark a context as both
92 splittable and shareable.
93
94 Example
95 ~~~~~~~
96
97 Consider three versions of the same test suite. One
98 is marked ``_multiprocess_shared_``, another ``_multiprocess_can_split_``,
99 and the third is unmarked. They all define the same fixtures:
100
101     called = []
102
103     def setup():
104         print "setup called"
105         called.append('setup')
106
107     def teardown():
108         print "teardown called"
109         called.append('teardown')
110
111 And each has two tests that just test that ``setup()`` has been called
112 once and only once.
113
114 When run without the multiprocess plugin, fixtures for the shared,
115 can-split and not-shared test suites execute at the same times, and
116 all tests pass.
117
118 .. Note ::
119
120    The run() function in :mod:`nose.plugins.plugintest` reformats test result
121    output to remove timings, which will vary from run to run, and
122    redirects the output to stdout.
123
124     >>> from nose.plugins.plugintest import run_buffered as run
125
126 ..
127
128     >>> import os
129     >>> support = os.path.join(os.path.dirname(__file__), 'support')
130     >>> test_not_shared = os.path.join(support, 'test_not_shared.py')
131     >>> test_shared = os.path.join(support, 'test_shared.py')
132     >>> test_can_split = os.path.join(support, 'test_can_split.py')
133
134 The module with shared fixtures passes.
135
136     >>> run(argv=['nosetests', '-v', test_shared]) #doctest: +REPORT_NDIFF
137     setup called
138     test_shared.TestMe.test_one ... ok
139     test_shared.test_a ... ok
140     test_shared.test_b ... ok
141     teardown called
142     <BLANKLINE>
143     ----------------------------------------------------------------------
144     Ran 3 tests in ...s
145     <BLANKLINE>
146     OK
147
148 As does the module with no fixture annotations.
149
150     >>> run(argv=['nosetests', '-v', test_not_shared]) #doctest: +REPORT_NDIFF
151     setup called
152     test_not_shared.TestMe.test_one ... ok
153     test_not_shared.test_a ... ok
154     test_not_shared.test_b ... ok
155     teardown called
156     <BLANKLINE>
157     ----------------------------------------------------------------------
158     Ran 3 tests in ...s
159     <BLANKLINE>
160     OK
161
162 And the module that marks its fixtures as re-entrant.
163
164     >>> run(argv=['nosetests', '-v', test_can_split]) #doctest: +REPORT_NDIFF
165     setup called
166     test_can_split.TestMe.test_one ... ok
167     test_can_split.test_a ... ok
168     test_can_split.test_b ... ok
169     teardown called
170     <BLANKLINE>
171     ----------------------------------------------------------------------
172     Ran 3 tests in ...s
173     <BLANKLINE>
174     OK
175
176 However, when run with the ``--processes=2`` switch, each test module
177 behaves differently.
178
179     >>> from nose.plugins.multiprocess import MultiProcess
180
181 The module marked ``_multiprocess_shared_`` executes correctly, although as with
182 any use of the multiprocess plugin, the order in which the tests execute is
183 indeterminate.
184
185 First we have to reset all of the test modules.
186
187     >>> import sys
188     >>> sys.modules['test_not_shared'].called[:] = []
189     >>> sys.modules['test_can_split'].called[:] = []
190
191 Then we can run the tests again with the multiprocess plugin active.
192     
193     >>> run(argv=['nosetests', '-v', '--processes=2', test_shared],
194     ...     plugins=[MultiProcess()]) #doctest: +ELLIPSIS
195     setup called
196     test_shared.... ok
197     teardown called
198     <BLANKLINE>
199     ----------------------------------------------------------------------
200     Ran 3 tests in ...s
201     <BLANKLINE>
202     OK
203
204 As does the one not marked -- however in this case, ``--processes=2``
205 will do *nothing at all*: since the tests are in a module with
206 unmarked fixtures, the entire test module will be dispatched to a
207 single runner process.
208
209 However, the module marked ``_multiprocess_can_split_`` will fail, since
210 the fixtures *are not reentrant*. A module such as this *must not* be
211 marked ``_multiprocess_can_split_``, or tests will fail in one or more
212 runner processes as fixtures are re-executed.
213
214 We have to reset all of the test modules again.
215
216     >>> import sys
217     >>> sys.modules['test_not_shared'].called[:] = []
218     >>> sys.modules['test_can_split'].called[:] = []
219
220 Then we can run again and see the failures.
221
222     >>> run(argv=['nosetests', '-v', '--processes=2', test_can_split],
223     ...     plugins=[MultiProcess()]) #doctest: +ELLIPSIS
224     setup called
225     teardown called
226     test_can_split....
227     ...
228     FAILED (failures=...)
229
230 Other differences in test running
231 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
232
233 The main difference between using the multiprocess plugin and not doing so
234 is obviously that tests run concurrently under multiprocess. However, there
235 are a few other differences that may impact your test suite:
236
237 * More tests may be found
238
239   Because tests are dispatched to worker processes by name, a worker
240   process may find and run tests in a module that would not be found during a
241   normal test run. For instance, if a non-test module contains a test-like
242   function, that function would be discovered as a test in a worker process
243   if the entire module is dispatched to the worker. This is because worker
244   processes load tests in *directed* mode -- the same way that nose loads
245   tests when you explicitly name a module -- rather than in *discovered* mode,
246   the mode nose uses when looking for tests in a directory.
247
248 * Out-of-order output
249
250   Test results are collected by workers and returned to the master process for
251   output. Since different processes may complete their tests at different
252   times, test result output order is not determinate.
253
254 * Plugin interaction warning
255
256   The multiprocess plugin does not work well with other plugins that expect to
257   wrap or gain control of the test-running process. Examples from nose's 
258   builtin plugins include coverage and profiling: a test run using
259   both multiprocess and either of those is likely to fail in some
260   confusing and spectacular way.
261
262 * Python 2.6 warning
263
264   This is unlikely to impact you unless you are writing tests for nose itself,
265   but be aware that under python 2.6, the multiprocess plugin is not
266   re-entrant. For example, when running nose with the plugin active, you can't
267   use subprocess to launch another copy of nose that also uses the
268   multiprocess plugin. This is why this test is skipped under python 2.6 when
269   run with the ``--processes`` switch.