""" This plugin adds a test id (like #1) to each test name output. After you've run once to generate test ids, you can re-run individual tests by activating the plugin and passing the ids (with or without the # prefix) instead of test names. For example, if your normal test run looks like:: % nosetests -v tests.test_a ... ok tests.test_b ... ok tests.test_c ... ok When adding --with-id you'll see:: % nosetests -v --with-id #1 tests.test_a ... ok #2 tests.test_b ... ok #2 tests.test_c ... ok Then you can rerun individual tests by supplying just the id numbers:: % nosetests -v --with-id 2 #2 tests.test_b ... ok Then you can rerun individual tests by supplying just the id numbers:: % nosetests -v --with-id 2 3 #2 tests.test_b ... ok #3 tests.test_c ... ok Since most shells consider '#' a special character, you can leave it out when specifying a test id. """ __test__ = False import logging import os from nose.plugins import Plugin from nose.util import src try: from cPickle import dump, load except ImportError: from pickle import dump, load log = logging.getLogger(__name__) class TestId(Plugin): """ Activate to add a test id (like #1) to each test name output. After you've run once to generate test ids, you can re-run individual tests by activating the plugin and passing the ids (with or without the # prefix) instead of test names. """ name = 'id' idfile = None shouldSave = True def options(self, parser, env): Plugin.options(self, parser, env) parser.add_option('--id-file', action='store', dest='testIdFile', default='.noseids', help="Store test ids found in test runs in this " "file. Default is the file .noseids in the " "working directory.") def configure(self, options, conf): Plugin.configure(self, options, conf) self.idfile = os.path.expanduser(options.testIdFile) if not os.path.isabs(self.idfile): self.idfile = os.path.join(conf.workingDir, self.idfile) self.id = 1 # Ids and tests are mirror images: ids are {id: test address} and # tests are {test address: id} self.ids = {} self.tests = {} # used to track ids seen when tests is filled from # loaded ids file self._seen = {} def finalize(self, result): if self.shouldSave: fh = open(self.idfile, 'w') # save as {id: test address} ids = dict(zip(self.tests.values(), self.tests.keys())) dump(ids, fh) fh.close() log.debug('Saved test ids: %s to %s', ids, self.idfile) def loadTestsFromNames(self, names, module=None): """Translate ids in the list of requested names into their test addresses, if they are found in my dict of tests. """ log.debug('ltfn %s %s', names, module) try: fh = open(self.idfile, 'r') self.ids = load(fh) log.debug('Loaded test ids %s from %s', self.ids, self.idfile) fh.close() except IOError: log.debug('IO error reading %s', self.idfile) return # I don't load any tests myself, only translate names like '#2' # into the associated test addresses result = (None, map(self.tr, names)) if not self.shouldSave: # got some ids in names, so make sure that the ids line # up in output with what I said they were last time self.tests = dict(zip(self.ids.values(), self.ids.keys())) return result def makeName(self, addr): log.debug("Make name %s", addr) filename, module, call = addr if filename is not None: head = src(filename) else: head = module if call is not None: return "%s:%s" % (head, call) return head def setOutputStream(self, stream): self.stream = stream def startTest(self, test): adr = test.address() if adr in self.tests: if self.shouldSave or adr in self._seen: self.stream.write(' ') else: self.stream.write('#%s ' % self.tests[adr]) self._seen[adr] = 1 return self.tests[adr] = self.id self.stream.write('#%s ' % self.id) self.id += 1 def tr(self, name): log.debug("tr '%s'", name) try: key = int(name.replace('#', '')) except ValueError: return name log.debug("Got key %s", key) self.shouldSave = False if key in self.ids: return self.makeName(self.ids[key]) return name