Show More
@@ -57,6 +57,7 b' import re' | |||
|
57 | 57 | import threading |
|
58 | 58 | import killdaemons as killmod |
|
59 | 59 | import Queue as queue |
|
60 | from xml.dom import minidom | |
|
60 | 61 | import unittest |
|
61 | 62 | |
|
62 | 63 | processlock = threading.Lock() |
@@ -190,6 +191,8 b' def getparser():' | |||
|
190 | 191 | " (implies --keep-tmpdir)") |
|
191 | 192 | parser.add_option("-v", "--verbose", action="store_true", |
|
192 | 193 | help="output verbose messages") |
|
194 | parser.add_option("--xunit", type="string", | |
|
195 | help="record xunit results at specified path") | |
|
193 | 196 | parser.add_option("--view", type="string", |
|
194 | 197 | help="external diff viewer") |
|
195 | 198 | parser.add_option("--with-hg", type="string", |
@@ -304,6 +307,20 b' def vlog(*msg):' | |||
|
304 | 307 | |
|
305 | 308 | return log(*msg) |
|
306 | 309 | |
|
310 | # Bytes that break XML even in a CDATA block: control characters 0-31 | |
|
311 | # sans \t, \n and \r | |
|
312 | CDATA_EVIL = re.compile(r"[\000-\010\013\014\016-\037]") | |
|
313 | ||
|
314 | def cdatasafe(data): | |
|
315 | """Make a string safe to include in a CDATA block. | |
|
316 | ||
|
317 | Certain control characters are illegal in a CDATA block, and | |
|
318 | there's no way to include a ]]> in a CDATA either. This function | |
|
319 | replaces illegal bytes with ? and adds a space between the ]] so | |
|
320 | that it won't break the CDATA block. | |
|
321 | """ | |
|
322 | return CDATA_EVIL.sub('?', data).replace(']]>', '] ]>') | |
|
323 | ||
|
307 | 324 | def log(*msg): |
|
308 | 325 | """Log something to stdout. |
|
309 | 326 | |
@@ -1085,6 +1102,9 b' class TestResult(unittest._TextTestResul' | |||
|
1085 | 1102 | self.times = [] |
|
1086 | 1103 | self._started = {} |
|
1087 | 1104 | self._stopped = {} |
|
1105 | # Data stored for the benefit of generating xunit reports. | |
|
1106 | self.successes = [] | |
|
1107 | self.faildata = {} | |
|
1088 | 1108 | |
|
1089 | 1109 | def addFailure(self, test, reason): |
|
1090 | 1110 | self.failures.append((test, reason)) |
@@ -1099,9 +1119,12 b' class TestResult(unittest._TextTestResul' | |||
|
1099 | 1119 | self.stream.write('!') |
|
1100 | 1120 | iolock.release() |
|
1101 | 1121 | |
|
1102 | def addError(self, *args, **kwargs): | |
|
1103 |
super(TestResult, self).add |
|
|
1122 | def addSuccess(self, test): | |
|
1123 | super(TestResult, self).addSuccess(test) | |
|
1124 | self.successes.append(test) | |
|
1104 | 1125 | |
|
1126 | def addError(self, test, err): | |
|
1127 | super(TestResult, self).addError(test, err) | |
|
1105 | 1128 | if self._options.first: |
|
1106 | 1129 | self.stop() |
|
1107 | 1130 | |
@@ -1141,6 +1164,8 b' class TestResult(unittest._TextTestResul' | |||
|
1141 | 1164 | """Record a mismatch in test output for a particular test.""" |
|
1142 | 1165 | |
|
1143 | 1166 | accepted = False |
|
1167 | failed = False | |
|
1168 | lines = [] | |
|
1144 | 1169 | |
|
1145 | 1170 | iolock.acquire() |
|
1146 | 1171 | if self._options.nodiff: |
@@ -1169,7 +1194,8 b' class TestResult(unittest._TextTestResul' | |||
|
1169 | 1194 | else: |
|
1170 | 1195 | rename(test.errpath, '%s.out' % test.path) |
|
1171 | 1196 | accepted = True |
|
1172 | ||
|
1197 | if not accepted and not failed: | |
|
1198 | self.faildata[test.name] = ''.join(lines) | |
|
1173 | 1199 | iolock.release() |
|
1174 | 1200 | |
|
1175 | 1201 | return accepted |
@@ -1344,6 +1370,35 b' class TextTestRunner(unittest.TextTestRu' | |||
|
1344 | 1370 | for test, msg in result.errors: |
|
1345 | 1371 | self.stream.writeln('Errored %s: %s' % (test.name, msg)) |
|
1346 | 1372 | |
|
1373 | if self._runner.options.xunit: | |
|
1374 | xuf = open(self._runner.options.xunit, 'wb') | |
|
1375 | try: | |
|
1376 | timesd = dict( | |
|
1377 | (test, real) for test, cuser, csys, real in result.times) | |
|
1378 | doc = minidom.Document() | |
|
1379 | s = doc.createElement('testsuite') | |
|
1380 | s.setAttribute('name', 'run-tests') | |
|
1381 | s.setAttribute('tests', str(result.testsRun)) | |
|
1382 | s.setAttribute('errors', "0") # TODO | |
|
1383 | s.setAttribute('failures', str(failed)) | |
|
1384 | s.setAttribute('skipped', str(skipped + ignored)) | |
|
1385 | doc.appendChild(s) | |
|
1386 | for tc in result.successes: | |
|
1387 | t = doc.createElement('testcase') | |
|
1388 | t.setAttribute('name', tc.name) | |
|
1389 | t.setAttribute('time', '%.3f' % timesd[tc.name]) | |
|
1390 | s.appendChild(t) | |
|
1391 | for tc, err in sorted(result.faildata.iteritems()): | |
|
1392 | t = doc.createElement('testcase') | |
|
1393 | t.setAttribute('name', tc) | |
|
1394 | t.setAttribute('time', '%.3f' % timesd[tc]) | |
|
1395 | cd = doc.createCDATASection(cdatasafe(err)) | |
|
1396 | t.appendChild(cd) | |
|
1397 | s.appendChild(t) | |
|
1398 | xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8')) | |
|
1399 | finally: | |
|
1400 | xuf.close() | |
|
1401 | ||
|
1347 | 1402 | self._runner._checkhglib('Tested') |
|
1348 | 1403 | |
|
1349 | 1404 | self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.' |
@@ -48,6 +48,39 b' failing test' | |||
|
48 | 48 | # Ran 2 tests, 0 skipped, 0 warned, 1 failed. |
|
49 | 49 | python hash seed: * (glob) |
|
50 | 50 | [1] |
|
51 | test --xunit support | |
|
52 | $ $TESTDIR/run-tests.py --with-hg=`which hg` --xunit=xunit.xml | |
|
53 | ||
|
54 | --- $TESTTMP/test-failure.t | |
|
55 | +++ $TESTTMP/test-failure.t.err | |
|
56 | @@ -1,4 +1,4 @@ | |
|
57 | $ echo babar | |
|
58 | - rataxes | |
|
59 | + babar | |
|
60 | This is a noop statement so that | |
|
61 | this test is still more bytes than success. | |
|
62 | ||
|
63 | ERROR: test-failure.t output changed | |
|
64 | !. | |
|
65 | Failed test-failure.t: output changed | |
|
66 | # Ran 2 tests, 0 skipped, 0 warned, 1 failed. | |
|
67 | python hash seed: * (glob) | |
|
68 | [1] | |
|
69 | $ cat xunit.xml | |
|
70 | <?xml version="1.0" encoding="utf-8"?> | |
|
71 | <testsuite errors="0" failures="1" name="run-tests" skipped="0" tests="2"> | |
|
72 | <testcase name="test-success.t" time="*"/> (glob) | |
|
73 | <testcase name="test-failure.t" time="*"> (glob) | |
|
74 | <![CDATA[--- $TESTTMP/test-failure.t | |
|
75 | +++ $TESTTMP/test-failure.t.err | |
|
76 | @@ -1,4 +1,4 @@ | |
|
77 | $ echo babar | |
|
78 | - rataxes | |
|
79 | + babar | |
|
80 | This is a noop statement so that | |
|
81 | this test is still more bytes than success. | |
|
82 | ]]> </testcase> | |
|
83 | </testsuite> | |
|
51 | 84 | |
|
52 | 85 | test for --retest |
|
53 | 86 | ==================== |
@@ -291,6 +324,18 b' Skips' | |||
|
291 | 324 | Skipped test-skip.t: irrelevant |
|
292 | 325 | # Ran 1 tests, 2 skipped, 0 warned, 0 failed. |
|
293 | 326 | |
|
327 | Skips with xml | |
|
328 | $ $TESTDIR/run-tests.py --with-hg=`which hg` --keyword xyzzy \ | |
|
329 | > --xunit=xunit.xml | |
|
330 | i.s | |
|
331 | Skipped test-skip.t: irrelevant | |
|
332 | # Ran 1 tests, 2 skipped, 0 warned, 0 failed. | |
|
333 | $ cat xunit.xml | |
|
334 | <?xml version="1.0" encoding="utf-8"?> | |
|
335 | <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="1"> | |
|
336 | <testcase name="test-success.t" time="*"/> (glob) | |
|
337 | </testsuite> | |
|
338 | ||
|
294 | 339 | Missing skips or blacklisted skips don't count as executed: |
|
295 | 340 | $ echo test-failure.t > blacklist |
|
296 | 341 | $ $TESTDIR/run-tests.py --with-hg=`which hg` --blacklist=blacklist \ |
General Comments 0
You need to be logged in to leave comments.
Login now