""" logging of test results in JUnit-XML format, for use with Hudson and build integration servers. Based on initial code from Ross Lawley. """ import py import time def pytest_addoption(parser): group = parser.getgroup("terminal reporting") group.addoption('--junitxml', action="store", dest="xmlpath", metavar="path", default=None, help="create junit-xml style report file at given path.") # probalby YAGNI, therefore commented out: #group.addoption('--junitprefix', action="store", dest="junitprefix", # metavar="str", default=None, # help="prepend prefix to classnames in junit-xml output") def pytest_configure(config): xmlpath = config.option.xmlpath if xmlpath: config._xml = LogXML(xmlpath, None) # config.option.junitprefix) config.pluginmanager.register(config._xml) def pytest_unconfigure(config): xml = getattr(config, '_xml', None) if xml: del config._xml config.pluginmanager.unregister(xml) class LogXML(object): def __init__(self, logfile, prefix): self.logfile = logfile self.prefix = prefix self.test_logs = [] self.passed = self.skipped = 0 self.failed = self.errors = 0 self._durations = {} def _opentestcase(self, report): if hasattr(report, 'item'): node = report.item else: node = report.collector d = {'time': self._durations.pop(node, "0")} names = [x.replace(".py", "") for x in node.listnames() if x != "()"] classnames = names[:-1] if self.prefix: classnames.insert(0, self.prefix) d['classname'] = ".".join(classnames) d['name'] = py.xml.escape(names[-1]) attrs = ['%s="%s"' % item for item in sorted(d.items())] self.test_logs.append("\n" % " ".join(attrs)) def _closetestcase(self): self.test_logs.append("") def appendlog(self, fmt, *args): args = tuple([py.xml.escape(arg) for arg in args]) self.test_logs.append(fmt % args) def append_pass(self, report): self.passed += 1 self._opentestcase(report) self._closetestcase() def append_failure(self, report): self._opentestcase(report) #msg = str(report.longrepr.reprtraceback.extraline) if "xfail" in report.keywords: self.appendlog( '') self.skipped += 1 else: self.appendlog('%s', report.longrepr) self.failed += 1 self._closetestcase() def append_collect_failure(self, report): self._opentestcase(report) #msg = str(report.longrepr.reprtraceback.extraline) self.appendlog('%s', report.longrepr) self._closetestcase() self.errors += 1 def append_collect_skipped(self, report): self._opentestcase(report) #msg = str(report.longrepr.reprtraceback.extraline) self.appendlog('%s', report.longrepr) self._closetestcase() self.skipped += 1 def append_error(self, report): self._opentestcase(report) self.appendlog('%s', report.longrepr) self._closetestcase() self.errors += 1 def append_skipped(self, report): self._opentestcase(report) if "xfail" in report.keywords: self.appendlog( '%s', report.keywords['xfail']) else: self.appendlog("") self._closetestcase() self.skipped += 1 def pytest_runtest_logreport(self, report): if report.passed: self.append_pass(report) elif report.failed: if report.when != "call": self.append_error(report) else: self.append_failure(report) elif report.skipped: self.append_skipped(report) def pytest_runtest_call(self, item, __multicall__): start = time.time() try: return __multicall__.execute() finally: self._durations[item] = time.time() - start def pytest_collectreport(self, report): if not report.passed: if report.failed: self.append_collect_failure(report) else: self.append_collect_skipped(report) def pytest_internalerror(self, excrepr): self.errors += 1 data = py.xml.escape(excrepr) self.test_logs.append( '\n' ' ' '%s' % data) def pytest_sessionstart(self, session): self.suite_start_time = time.time() def pytest_sessionfinish(self, session, exitstatus, __multicall__): if py.std.sys.version_info[0] < 3: logfile = py.std.codecs.open(self.logfile, 'w', encoding='utf-8') else: logfile = open(self.logfile, 'w', encoding='utf-8') suite_stop_time = time.time() suite_time_delta = suite_stop_time - self.suite_start_time numtests = self.passed + self.failed logfile.write('') logfile.write('') logfile.writelines(self.test_logs) logfile.write('') logfile.close() def pytest_terminal_summary(self, terminalreporter): tw = terminalreporter._tw terminalreporter.write_sep("-", "generated xml file: %s" %(self.logfile))