Show More
@@ -57,6 +57,7 b' import re' | |||||
57 | import threading |
|
57 | import threading | |
58 | import killdaemons as killmod |
|
58 | import killdaemons as killmod | |
59 | import Queue as queue |
|
59 | import Queue as queue | |
|
60 | from xml.dom import minidom | |||
60 | import unittest |
|
61 | import unittest | |
61 |
|
62 | |||
62 | processlock = threading.Lock() |
|
63 | processlock = threading.Lock() | |
@@ -190,6 +191,8 b' def getparser():' | |||||
190 | " (implies --keep-tmpdir)") |
|
191 | " (implies --keep-tmpdir)") | |
191 | parser.add_option("-v", "--verbose", action="store_true", |
|
192 | parser.add_option("-v", "--verbose", action="store_true", | |
192 | help="output verbose messages") |
|
193 | help="output verbose messages") | |
|
194 | parser.add_option("--xunit", type="string", | |||
|
195 | help="record xunit results at specified path") | |||
193 | parser.add_option("--view", type="string", |
|
196 | parser.add_option("--view", type="string", | |
194 | help="external diff viewer") |
|
197 | help="external diff viewer") | |
195 | parser.add_option("--with-hg", type="string", |
|
198 | parser.add_option("--with-hg", type="string", | |
@@ -304,6 +307,20 b' def vlog(*msg):' | |||||
304 |
|
307 | |||
305 | return log(*msg) |
|
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 | def log(*msg): |
|
324 | def log(*msg): | |
308 | """Log something to stdout. |
|
325 | """Log something to stdout. | |
309 |
|
326 | |||
@@ -1085,6 +1102,9 b' class TestResult(unittest._TextTestResul' | |||||
1085 | self.times = [] |
|
1102 | self.times = [] | |
1086 | self._started = {} |
|
1103 | self._started = {} | |
1087 | self._stopped = {} |
|
1104 | self._stopped = {} | |
|
1105 | # Data stored for the benefit of generating xunit reports. | |||
|
1106 | self.successes = [] | |||
|
1107 | self.faildata = {} | |||
1088 |
|
1108 | |||
1089 | def addFailure(self, test, reason): |
|
1109 | def addFailure(self, test, reason): | |
1090 | self.failures.append((test, reason)) |
|
1110 | self.failures.append((test, reason)) | |
@@ -1099,9 +1119,12 b' class TestResult(unittest._TextTestResul' | |||||
1099 | self.stream.write('!') |
|
1119 | self.stream.write('!') | |
1100 | iolock.release() |
|
1120 | iolock.release() | |
1101 |
|
1121 | |||
1102 | def addError(self, *args, **kwargs): |
|
1122 | def addSuccess(self, test): | |
1103 |
super(TestResult, self).add |
|
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 | if self._options.first: |
|
1128 | if self._options.first: | |
1106 | self.stop() |
|
1129 | self.stop() | |
1107 |
|
1130 | |||
@@ -1141,6 +1164,8 b' class TestResult(unittest._TextTestResul' | |||||
1141 | """Record a mismatch in test output for a particular test.""" |
|
1164 | """Record a mismatch in test output for a particular test.""" | |
1142 |
|
1165 | |||
1143 | accepted = False |
|
1166 | accepted = False | |
|
1167 | failed = False | |||
|
1168 | lines = [] | |||
1144 |
|
1169 | |||
1145 | iolock.acquire() |
|
1170 | iolock.acquire() | |
1146 | if self._options.nodiff: |
|
1171 | if self._options.nodiff: | |
@@ -1169,7 +1194,8 b' class TestResult(unittest._TextTestResul' | |||||
1169 | else: |
|
1194 | else: | |
1170 | rename(test.errpath, '%s.out' % test.path) |
|
1195 | rename(test.errpath, '%s.out' % test.path) | |
1171 | accepted = True |
|
1196 | accepted = True | |
1172 |
|
1197 | if not accepted and not failed: | ||
|
1198 | self.faildata[test.name] = ''.join(lines) | |||
1173 | iolock.release() |
|
1199 | iolock.release() | |
1174 |
|
1200 | |||
1175 | return accepted |
|
1201 | return accepted | |
@@ -1344,6 +1370,35 b' class TextTestRunner(unittest.TextTestRu' | |||||
1344 | for test, msg in result.errors: |
|
1370 | for test, msg in result.errors: | |
1345 | self.stream.writeln('Errored %s: %s' % (test.name, msg)) |
|
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 | self._runner._checkhglib('Tested') |
|
1402 | self._runner._checkhglib('Tested') | |
1348 |
|
1403 | |||
1349 | self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.' |
|
1404 | self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.' |
@@ -48,6 +48,39 b' failing test' | |||||
48 | # Ran 2 tests, 0 skipped, 0 warned, 1 failed. |
|
48 | # Ran 2 tests, 0 skipped, 0 warned, 1 failed. | |
49 | python hash seed: * (glob) |
|
49 | python hash seed: * (glob) | |
50 | [1] |
|
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 | test for --retest |
|
85 | test for --retest | |
53 | ==================== |
|
86 | ==================== | |
@@ -291,6 +324,18 b' Skips' | |||||
291 | Skipped test-skip.t: irrelevant |
|
324 | Skipped test-skip.t: irrelevant | |
292 | # Ran 1 tests, 2 skipped, 0 warned, 0 failed. |
|
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 | Missing skips or blacklisted skips don't count as executed: |
|
339 | Missing skips or blacklisted skips don't count as executed: | |
295 | $ echo test-failure.t > blacklist |
|
340 | $ echo test-failure.t > blacklist | |
296 | $ $TESTDIR/run-tests.py --with-hg=`which hg` --blacklist=blacklist \ |
|
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