~crunch.io/ubuntu/precise/codespeak-lib/unstable

« back to all changes in this revision

Viewing changes to py/_plugin/pytest_junitxml.py

  • Committer: Bazaar Package Importer
  • Author(s): Chris Lamb
  • Date: 2010-08-01 16:24:01 UTC
  • mfrom: (1.1.8 upstream)
  • Revision ID: james.westby@ubuntu.com-20100801162401-g37v49d1p148alpm
Tags: 1.3.3-1
* New upstream release.
* Bump Standards-Version to 3.9.1.
* Fix typo in py.test manpage.
* Prefer Breaks: over Conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
"""
2
 
   logging of test results in JUnit-XML format, for use with Hudson 
 
2
   logging of test results in JUnit-XML format, for use with Hudson
3
3
   and build integration servers.  Based on initial code from Ross Lawley.
4
4
"""
5
5
 
8
8
 
9
9
def pytest_addoption(parser):
10
10
    group = parser.getgroup("terminal reporting")
11
 
    group.addoption('--junitxml', action="store", dest="xmlpath", 
 
11
    group.addoption('--junitxml', action="store", dest="xmlpath",
12
12
           metavar="path", default=None,
13
13
           help="create junit-xml style report file at given path.")
 
14
    group.addoption('--junitprefix', action="store", dest="junitprefix",
 
15
           metavar="str", default=None,
 
16
           help="prepend prefix to classnames in junit-xml output")
14
17
 
15
18
def pytest_configure(config):
16
19
    xmlpath = config.option.xmlpath
17
20
    if xmlpath:
18
 
        config._xml = LogXML(xmlpath)
 
21
        config._xml = LogXML(xmlpath, config.option.junitprefix)
19
22
        config.pluginmanager.register(config._xml)
20
23
 
21
24
def pytest_unconfigure(config):
22
25
    xml = getattr(config, '_xml', None)
23
26
    if xml:
24
 
        del config._xml 
 
27
        del config._xml
25
28
        config.pluginmanager.unregister(xml)
26
29
 
27
30
class LogXML(object):
28
 
    def __init__(self, logfile):
 
31
    def __init__(self, logfile, prefix):
29
32
        self.logfile = logfile
 
33
        self.prefix = prefix
30
34
        self.test_logs = []
31
35
        self.passed = self.skipped = 0
32
36
        self.failed = self.errors = 0
33
37
        self._durations = {}
34
 
  
 
38
 
35
39
    def _opentestcase(self, report):
36
 
        node = report.item 
37
 
        d = {'time': self._durations.pop(report.item, "0")}
 
40
        if hasattr(report, 'item'):
 
41
            node = report.item
 
42
        else:
 
43
            node = report.collector
 
44
        d = {'time': self._durations.pop(node, "0")}
38
45
        names = [x.replace(".py", "") for x in node.listnames() if x != "()"]
39
 
        d['classname'] = ".".join(names[:-1])
40
 
        d['name'] = names[-1]
 
46
        classnames = names[:-1]
 
47
        if self.prefix:
 
48
            classnames.insert(0, self.prefix)
 
49
        d['classname'] = ".".join(classnames)
 
50
        d['name'] = py.xml.escape(names[-1])
41
51
        attrs = ['%s="%s"' % item for item in sorted(d.items())]
42
52
        self.test_logs.append("\n<testcase %s>" % " ".join(attrs))
43
53
 
44
54
    def _closetestcase(self):
45
55
        self.test_logs.append("</testcase>")
46
 
         
 
56
 
 
57
    def appendlog(self, fmt, *args):
 
58
        args = tuple([py.xml.escape(arg) for arg in args])
 
59
        self.test_logs.append(fmt % args)
 
60
 
47
61
    def append_pass(self, report):
48
62
        self.passed += 1
49
63
        self._opentestcase(report)
51
65
 
52
66
    def append_failure(self, report):
53
67
        self._opentestcase(report)
54
 
        s = py.xml.escape(str(report.longrepr))
55
68
        #msg = str(report.longrepr.reprtraceback.extraline)
56
 
        self.test_logs.append(
57
 
            '<failure message="test failure">%s</failure>' % (s))
 
69
        if "xfail" in report.keywords:
 
70
            self.appendlog(
 
71
                '<skipped message="xfail-marked test passes unexpectedly"/>')
 
72
            self.skipped += 1
 
73
        else:
 
74
            self.appendlog('<failure message="test failure">%s</failure>',
 
75
                report.longrepr)
 
76
            self.failed += 1
58
77
        self._closetestcase()
59
 
        self.failed += 1
60
 
 
61
 
    def _opentestcase_collectfailure(self, report):
62
 
        node = report.collector
63
 
        d = {'time': '???'}
64
 
        names = [x.replace(".py", "") for x in node.listnames() if x != "()"]
65
 
        d['classname'] = ".".join(names[:-1])
66
 
        d['name'] = names[-1]
67
 
        attrs = ['%s="%s"' % item for item in sorted(d.items())]
68
 
        self.test_logs.append("\n<testcase %s>" % " ".join(attrs))
69
78
 
70
79
    def append_collect_failure(self, report):
71
 
        self._opentestcase_collectfailure(report)
72
 
        s = py.xml.escape(str(report.longrepr))
 
80
        self._opentestcase(report)
73
81
        #msg = str(report.longrepr.reprtraceback.extraline)
74
 
        self.test_logs.append(
75
 
            '<failure message="collection failure">%s</failure>' % (s))
 
82
        self.appendlog('<failure message="collection failure">%s</failure>',
 
83
            report.longrepr)
76
84
        self._closetestcase()
77
85
        self.errors += 1
78
86
 
79
87
    def append_collect_skipped(self, report):
80
 
        self._opentestcase_collectfailure(report)
81
 
        s = py.xml.escape(str(report.longrepr))
 
88
        self._opentestcase(report)
82
89
        #msg = str(report.longrepr.reprtraceback.extraline)
83
 
        self.test_logs.append(
84
 
            '<skipped message="collection skipped">%s</skipped>' % (s))
 
90
        self.appendlog('<skipped message="collection skipped">%s</skipped>',
 
91
            report.longrepr)
85
92
        self._closetestcase()
86
93
        self.skipped += 1
87
94
 
88
95
    def append_error(self, report):
89
96
        self._opentestcase(report)
90
 
        s = py.xml.escape(str(report.longrepr))
91
 
        self.test_logs.append(
92
 
            '<error message="test setup failure">%s</error>' % s)
 
97
        self.appendlog('<error message="test setup failure">%s</error>',
 
98
            report.longrepr)
93
99
        self._closetestcase()
94
100
        self.errors += 1
95
101
 
96
102
    def append_skipped(self, report):
97
103
        self._opentestcase(report)
98
 
        self.test_logs.append("<skipped/>")
 
104
        if "xfail" in report.keywords:
 
105
            self.appendlog(
 
106
                '<skipped message="expected test failure">%s</skipped>',
 
107
                report.keywords['xfail'])
 
108
        else:
 
109
            self.appendlog("<skipped/>")
99
110
        self._closetestcase()
100
111
        self.skipped += 1
101
112
 
109
120
                self.append_failure(report)
110
121
        elif report.skipped:
111
122
            self.append_skipped(report)
112
 
        
 
123
 
113
124
    def pytest_runtest_call(self, item, __multicall__):
114
125
        start = time.time()
115
126
        try:
116
127
            return __multicall__.execute()
117
128
        finally:
118
129
            self._durations[item] = time.time() - start
119
 
    
 
130
 
120
131
    def pytest_collectreport(self, report):
121
132
        if not report.passed:
122
133
            if report.failed:
126
137
 
127
138
    def pytest_internalerror(self, excrepr):
128
139
        self.errors += 1
129
 
        data = py.xml.escape(str(excrepr))
 
140
        data = py.xml.escape(excrepr)
130
141
        self.test_logs.append(
131
142
            '\n<testcase classname="pytest" name="internal">'
132
143
            '    <error message="internal error">'
136
147
        self.suite_start_time = time.time()
137
148
 
138
149
    def pytest_sessionfinish(self, session, exitstatus, __multicall__):
139
 
        logfile = open(self.logfile, 'w', 1) # line buffered
 
150
        if py.std.sys.version_info[0] < 3:
 
151
            logfile = py.std.codecs.open(self.logfile, 'w', encoding='utf-8')
 
152
        else:
 
153
            logfile = open(self.logfile, 'w', encoding='utf-8')
 
154
 
140
155
        suite_stop_time = time.time()
141
156
        suite_time_delta = suite_stop_time - self.suite_start_time
142
157
        numtests = self.passed + self.failed
 
158
        logfile.write('<?xml version="1.0" encoding="utf-8"?>')
143
159
        logfile.write('<testsuite ')
144
160
        logfile.write('name="" ')
145
161
        logfile.write('errors="%i" ' % self.errors)
151
167
        logfile.writelines(self.test_logs)
152
168
        logfile.write('</testsuite>')
153
169
        logfile.close()
154
 
        tw = session.config.pluginmanager.getplugin("terminalreporter")._tw
155
 
        tw.line()
156
 
        tw.sep("-", "generated xml file: %s" %(self.logfile))
 
170
 
 
171
    def pytest_terminal_summary(self, terminalreporter):
 
172
        tw = terminalreporter._tw
 
173
        terminalreporter.write_sep("-", "generated xml file: %s" %(self.logfile))