2
This plugin adds a test id (like #1) to each test name output. After
3
you've run once to generate test ids, you can re-run individual
4
tests by activating the plugin and passing the ids (with or
5
without the # prefix) instead of test names.
7
For example, if your normal test run looks like::
14
When adding ``--with-id`` you'll see::
16
% nosetests -v --with-id
17
#1 tests.test_a ... ok
18
#2 tests.test_b ... ok
19
#2 tests.test_c ... ok
21
Then you can re-run individual tests by supplying just an id number::
23
% nosetests -v --with-id 2
24
#2 tests.test_b ... ok
26
You can also pass multiple id numbers::
28
% nosetests -v --with-id 2 3
29
#2 tests.test_b ... ok
30
#3 tests.test_c ... ok
32
Since most shells consider '#' a special character, you can leave it out when
35
Note that when run without the -v switch, no special output is displayed, but
36
the ids file is still written.
38
Looping over failed tests
39
-------------------------
41
This plugin also adds a mode that will direct the test runner to record
42
failed tests. Subsequent test runs will then run only the tests that failed
43
last time. Activate this mode with the ``--failed`` switch::
45
% nosetests -v --failed
47
#2 test.test_b ... ERROR
48
#3 test.test_c ... FAILED
51
On the second run, only tests #2 and #3 will run::
53
% nosetests -v --failed
54
#2 test.test_b ... ERROR
55
#3 test.test_c ... FAILED
57
As you correct errors and tests pass, they'll drop out of subsequent runs.
61
% nosetests -v --failed
63
#3 test.test_c ... FAILED
67
% nosetests -v --failed
68
#3 test.test_c ... FAILED
70
When all tests pass, the full set will run on the next invocation.
74
% nosetests -v --failed
79
% nosetests -v --failed
87
If you expect to use ``--failed`` regularly, it's a good idea to always run
88
run using the ``--with-id`` option. This will ensure that an id file is
89
always created, allowing you to add ``--failed`` to the command line as soon
90
as you have failing tests. Otherwise, your first run using ``--failed`` will
91
(perhaps surprisingly) run *all* tests, because there won't be an id file
92
containing the record of failed tests from your previous run.
99
from nose.plugins import Plugin
100
from nose.util import src, set
103
from cPickle import dump, load
105
from pickle import dump, load
107
log = logging.getLogger(__name__)
110
class TestId(Plugin):
112
Activate to add a test id (like #1) to each test name output. Activate
113
with --failed to rerun failing tests only.
120
def options(self, parser, env):
121
"""Register commandline options.
123
Plugin.options(self, parser, env)
124
parser.add_option('--id-file', action='store', dest='testIdFile',
125
default='.noseids', metavar="FILE",
126
help="Store test ids found in test runs in this "
127
"file. Default is the file .noseids in the "
128
"working directory.")
129
parser.add_option('--failed', action='store_true',
130
dest='failed', default=False,
131
help="Run the tests that failed in the last "
134
def configure(self, options, conf):
137
Plugin.configure(self, options, conf)
140
self.loopOnFailed = True
141
log.debug("Looping on failed tests")
142
self.idfile = os.path.expanduser(options.testIdFile)
143
if not os.path.isabs(self.idfile):
144
self.idfile = os.path.join(conf.workingDir, self.idfile)
146
# Ids and tests are mirror images: ids are {id: test address} and
147
# tests are {test address: id}
151
self.source_names = []
152
# used to track ids seen when tests is filled from
155
self._write_hashes = options.verbosity >= 2
157
def finalize(self, result):
158
"""Save new ids file, if needed.
160
if result.wasSuccessful():
163
ids = dict(zip(self.tests.values(), self.tests.keys()))
166
fh = open(self.idfile, 'w')
168
'failed': self.failed,
169
'source_names': self.source_names}, fh)
171
log.debug('Saved test ids: %s, failed %s to %s',
172
ids, self.failed, self.idfile)
174
def loadTestsFromNames(self, names, module=None):
175
"""Translate ids in the list of requested names into their
176
test addresses, if they are found in my dict of tests.
178
log.debug('ltfn %s %s', names, module)
180
fh = open(self.idfile, 'r')
183
self.ids = data['ids']
184
self.failed = data['failed']
185
self.source_names = data['source_names']
190
self.source_names = names
192
self.id = max(self.ids) + 1
193
self.tests = dict(zip(self.ids.values(), self.ids.keys()))
197
'Loaded test ids %s tests %s failed %s sources %s from %s',
198
self.ids, self.tests, self.failed, self.source_names,
202
log.debug('IO error reading %s', self.idfile)
204
if self.loopOnFailed and self.failed:
205
self.collecting = False
208
# I don't load any tests myself, only translate names like '#2'
209
# into the associated test addresses
214
trans = self.tr(name)
216
translated.append(trans)
218
new_source.append(name)
219
# names that are not ids and that are not in the current
220
# list of source names go into the list for next time
222
new_set = set(new_source)
223
old_set = set(self.source_names)
224
log.debug("old: %s new: %s", old_set, new_set)
225
really_new = [s for s in new_source
228
# remember new sources
229
self.source_names.extend(really_new)
231
# new set of source names, no translations
232
# means "run the requested tests"
235
# no new names to translate and add to id set
236
self.collecting = False
237
log.debug("translated: %s new sources %s names %s",
238
translated, really_new, names)
239
return (None, translated + really_new or names)
241
def makeName(self, addr):
242
log.debug("Make name %s", addr)
243
filename, module, call = addr
244
if filename is not None:
249
return "%s:%s" % (head, call)
252
def setOutputStream(self, stream):
253
"""Get handle on output stream so the plugin can print id #s
257
def startTest(self, test):
258
"""Maybe output an id # before the test name.
263
#2 test.test_two ... ok
267
log.debug('start test %s (%s)', adr, adr in self.tests)
268
if adr in self.tests:
269
if adr in self._seen:
272
self.write('#%s ' % self.tests[adr])
275
self.tests[adr] = self.id
276
self.write('#%s ' % self.id)
279
def afterTest(self, test):
280
# None means test never ran, False means failed/err
281
if test.passed is False:
283
key = str(self.tests[test.address()])
285
# never saw this test -- startTest didn't run
288
if key not in self.failed:
289
self.failed.append(key)
292
log.debug("tr '%s'", name)
294
key = int(name.replace('#', ''))
297
log.debug("Got key %s", key)
298
# I'm running tests mapped from the ids file,
299
# not collecting new ones
301
return self.makeName(self.ids[key])
304
def write(self, output):
305
if self._write_hashes:
306
self.stream.write(output)