test framework: bulk reformat
[scons.git] / testing / framework / TestUnit / taprunner.py
blobc4e6f417752c56ede9bf64b215d8352953a827c3
1 """
2 Format unittest results in Test Anything Protocol (TAP).
3 https://testanything.org/tap-version-13-specification.html
5 Public domain work by:
6 anatoly techtonik <techtonik@gmail.com>
8 Changes:
9 0.3 - fixed used imports that failed on Python 2.6
10 0.2 - removed unused import that failed on Python 2.6
11 0.1 - initial release
12 """
14 __version__ = "0.3"
17 from unittest import TextTestRunner
19 try:
20 from unittest import TextTestResult
21 except ImportError:
22 # Python 2.6
23 from unittest import _TextTestResult as TextTestResult
26 class TAPTestResult(TextTestResult):
27 def _process(self, test, msg, failtype=None, directive=None) -> None:
28 """increase the counter, format and output TAP info"""
29 # counterhack: increase test counter
30 test.suite.tap_counter += 1
31 msg = "%s %d" % (msg, test.suite.tap_counter)
32 if "not" not in msg:
33 msg += " " # justify
34 self.stream.write(f"{msg} - ")
35 if failtype:
36 self.stream.write(f"{failtype} - ")
37 self.stream.write(f"{test.__class__.__name__}")
38 self.stream.write(f".{test._testMethodName}")
39 if directive:
40 self.stream.write(directive)
41 self.stream.write("\n")
42 # [ ] write test __doc__ (if exists) in comment
43 self.stream.flush()
45 def addSuccess(self, test) -> None:
46 super().addSuccess(test)
47 self._process(test, "ok")
49 def addFailure(self, test, err) -> None:
50 super().addFailure(test, err)
51 self._process(test, "not ok", "FAIL")
52 # [ ] add structured data about assertion
54 def addError(self, test, err) -> None:
55 super().addError(test, err)
56 self._process(test, "not ok", "ERROR")
57 # [ ] add structured data about exception
59 def addSkip(self, test, reason) -> None:
60 super().addSkip(test, reason)
61 self._process(test, "ok", directive=f" # SKIP {reason}")
63 def addExpectedFailure(self, test, err) -> None:
64 super().addExpectedFailure(test, err)
65 self._process(test, "not ok", directive=" # TODO")
67 def addUnexpectedSuccess(self, test) -> None:
68 super().addUnexpectedSuccess(test)
69 self._process(test, "not ok", "FAIL (unexpected success)")
71 """
72 def printErrors(self):
73 def printErrorList(self, flavour, errors):
74 """
77 class TAPTestRunner(TextTestRunner):
78 resultclass = TAPTestResult
80 def run(self, test):
81 self.stream.write("TAP version 13\n")
82 # [ ] add commented block with test suite __doc__
83 # [ ] check call with a single test
84 # if isinstance(test, suite.TestSuite):
85 self.stream.write(f"1..{len(list(test))}\n")
87 # counterhack: inject test counter into test suite
88 test.tap_counter = 0
89 # counterhack: inject reference to suite into each test case
90 for case in test:
91 case.suite = test
93 return super().run(test)
96 if __name__ == "__main__":
97 import sys
98 import unittest
100 class Test(unittest.TestCase):
101 def test_ok(self) -> None:
102 pass
104 def test_fail(self) -> None:
105 self.assertTrue(False)
107 def test_error(self) -> None:
108 bad_symbol
110 @unittest.skip("skipin'")
111 def test_skip(self) -> None:
112 pass
114 @unittest.expectedFailure
115 def test_not_ready(self) -> None:
116 self.fail()
118 @unittest.expectedFailure
119 def test_invalid_fail_mark(self) -> None:
120 pass
122 def test_another_ok(self) -> None:
123 pass
125 suite = unittest.TestSuite(
127 Test('test_ok'),
128 Test('test_fail'),
129 Test('test_error'),
130 Test('test_skip'),
131 Test('test_not_ready'),
132 Test('test_invalid_fail_mark'),
133 Test('test_another_ok'),
136 if not TAPTestRunner().run(suite).wasSuccessful():
137 sys.exit(1)