Commit | Line | Data |
---|---|---|
0235b0db MJ |
1 | # SPDX-License-Identifier: BSD-2-Clause |
2 | # | |
b85894a3 MJ |
3 | # Copyright (c) 2016, Matt Layman |
4 | ||
5 | import os | |
6 | from unittest import TextTestResult, TextTestRunner | |
7 | from unittest.runner import _WritelnDecorator | |
8 | import sys | |
9 | ||
10 | from tap import formatter | |
11 | from tap.i18n import _ | |
12 | from tap.tracker import Tracker | |
13 | ||
14 | ||
15 | class TAPTestResult(TextTestResult): | |
16 | ||
17 | FORMAT = None | |
18 | ||
19 | def __init__(self, stream, descriptions, verbosity): | |
20 | super(TAPTestResult, self).__init__(stream, descriptions, verbosity) | |
21 | ||
22 | def stopTestRun(self): | |
23 | """Once the test run is complete, generate each of the TAP files.""" | |
24 | super(TAPTestResult, self).stopTestRun() | |
25 | self.tracker.generate_tap_reports() | |
26 | ||
27 | def addError(self, test, err): | |
28 | super(TAPTestResult, self).addError(test, err) | |
29 | diagnostics = formatter.format_exception(err) | |
30 | self.tracker.add_not_ok( | |
768f9bcb MJ |
31 | self._cls_name(test), self._description(test), diagnostics=diagnostics |
32 | ) | |
b85894a3 MJ |
33 | |
34 | def addFailure(self, test, err): | |
35 | super(TAPTestResult, self).addFailure(test, err) | |
36 | diagnostics = formatter.format_exception(err) | |
37 | self.tracker.add_not_ok( | |
768f9bcb MJ |
38 | self._cls_name(test), self._description(test), diagnostics=diagnostics |
39 | ) | |
b85894a3 MJ |
40 | |
41 | def addSuccess(self, test): | |
42 | super(TAPTestResult, self).addSuccess(test) | |
43 | self.tracker.add_ok(self._cls_name(test), self._description(test)) | |
44 | ||
45 | def addSkip(self, test, reason): | |
46 | super(TAPTestResult, self).addSkip(test, reason) | |
768f9bcb | 47 | self.tracker.add_skip(self._cls_name(test), self._description(test), reason) |
b85894a3 MJ |
48 | |
49 | def addExpectedFailure(self, test, err): | |
50 | super(TAPTestResult, self).addExpectedFailure(test, err) | |
51 | diagnostics = formatter.format_exception(err) | |
52 | self.tracker.add_not_ok( | |
768f9bcb MJ |
53 | self._cls_name(test), |
54 | self._description(test), | |
55 | _('(expected failure)'), | |
56 | diagnostics=diagnostics, | |
57 | ) | |
b85894a3 MJ |
58 | |
59 | def addUnexpectedSuccess(self, test): | |
60 | super(TAPTestResult, self).addUnexpectedSuccess(test) | |
768f9bcb MJ |
61 | self.tracker.add_ok( |
62 | self._cls_name(test), self._description(test), _('(unexpected success)') | |
63 | ) | |
b85894a3 MJ |
64 | |
65 | def _cls_name(self, test): | |
66 | return test.__class__.__name__ | |
67 | ||
68 | def _description(self, test): | |
69 | if self.FORMAT: | |
70 | try: | |
71 | return self.FORMAT.format( | |
72 | method_name=str(test), | |
768f9bcb MJ |
73 | short_description=test.shortDescription() or '', |
74 | ) | |
b85894a3 | 75 | except KeyError: |
768f9bcb MJ |
76 | sys.exit( |
77 | _( | |
78 | 'Bad format string: {format}\n' | |
79 | 'Replacement options are: {{short_description}} and ' | |
80 | '{{method_name}}' | |
81 | ).format(format=self.FORMAT) | |
82 | ) | |
b85894a3 MJ |
83 | |
84 | return test.shortDescription() or str(test) | |
85 | ||
86 | ||
87 | # TODO: 2016-7-30 mblayman - Since the 2.6 signature is no longer relevant, | |
88 | # check the possibility of removing the module level scope. | |
89 | ||
90 | # Module level state stinks, but this is the only way to keep compatibility | |
91 | # with Python 2.6. The best place for the tracker is as an instance variable | |
92 | # on the runner, but __init__ is so different that it is not easy to create | |
93 | # a runner that satisfies every supported Python version. | |
94 | _tracker = Tracker() | |
95 | ||
96 | ||
97 | class TAPTestRunner(TextTestRunner): | |
98 | """A test runner that will behave exactly like TextTestRunner and will | |
99 | additionally generate TAP files for each test case""" | |
100 | ||
101 | resultclass = TAPTestResult | |
102 | ||
103 | def set_stream(self, streaming): | |
104 | """Set the streaming boolean option to stream TAP directly to stdout. | |
105 | ||
106 | The test runner default output will be suppressed in favor of TAP. | |
107 | """ | |
108 | self.stream = _WritelnDecorator(open(os.devnull, 'w')) | |
109 | _tracker.streaming = streaming | |
110 | _tracker.stream = sys.stdout | |
111 | ||
112 | def _makeResult(self): | |
768f9bcb | 113 | result = self.resultclass(self.stream, self.descriptions, self.verbosity) |
b85894a3 MJ |
114 | result.tracker = _tracker |
115 | return result | |
116 | ||
117 | @classmethod | |
118 | def set_outdir(cls, outdir): | |
119 | """Set the output directory so that TAP files are written to the | |
120 | specified outdir location. | |
121 | """ | |
122 | # Blame the lack of unittest extensibility for this hacky method. | |
123 | _tracker.outdir = outdir | |
124 | ||
125 | @classmethod | |
126 | def set_combined(cls, combined): | |
127 | """Set the tracker to use a single output file.""" | |
128 | _tracker.combined = combined | |
129 | ||
130 | @classmethod | |
131 | def set_header(cls, header): | |
132 | """Set the header display flag.""" | |
133 | _tracker.header = header | |
134 | ||
135 | @classmethod | |
136 | def set_format(cls, fmt): | |
137 | """Set the format of each test line. | |
138 | ||
139 | The format string can use: | |
140 | * {method_name}: The test method name | |
141 | * {short_description}: The test's docstring short description | |
142 | """ | |
143 | TAPTestResult.FORMAT = fmt |