Merge lp:~lifeless/subunit/streamresult into lp:~subunit/subunit/trunk
- streamresult
- Merge into trunk
Proposed by
Robert Collins
Status: | Merged |
---|---|
Merged at revision: | 205 |
Proposed branch: | lp:~lifeless/subunit/streamresult |
Merge into: | lp:~subunit/subunit/trunk |
Diff against target: |
3262 lines (+1848/-501) 25 files modified
Makefile.am (+2/-0) NEWS (+35/-0) README (+252/-14) filters/subunit-1to2 (+42/-0) filters/subunit-2to1 (+46/-0) filters/subunit-filter (+14/-8) filters/subunit-ls (+21/-10) filters/subunit-notify (+5/-1) filters/subunit-stats (+12/-21) filters/subunit2csv (+4/-1) filters/subunit2gtk (+29/-48) filters/subunit2junitxml (+6/-1) filters/subunit2pyunit (+15/-6) python/subunit/__init__.py (+57/-47) python/subunit/filters.py (+75/-15) python/subunit/run.py (+39/-3) python/subunit/test_results.py (+58/-7) python/subunit/tests/__init__.py (+2/-0) python/subunit/tests/test_run.py (+22/-14) python/subunit/tests/test_subunit_filter.py (+43/-61) python/subunit/tests/test_subunit_tags.py (+31/-30) python/subunit/tests/test_tap2subunit.py (+162/-214) python/subunit/tests/test_test_protocol2.py (+415/-0) python/subunit/v2.py (+458/-0) setup.py (+3/-0) |
To merge this branch: | bzr merge lp:~lifeless/subunit/streamresult |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Subunit Developers | Pending | ||
Review via email: mp+151442@code.launchpad.net |
Commit message
Description of the change
Woo! Overlapping concurrent tests with built in enumeration and twice as fast parsing.
To post a comment you must log in.
- 203. By Robert Collins
-
Fixes from getting testrepository running with v2.
- 204. By Robert Collins
-
* ``subunit.run`` now replaces sys.stdout to ensure that stdout is unbuffered
- without this pdb output is not reliably visible when stdout is a pipe
as it usually is. (Robert Collins) - 205. By Robert Collins
-
Switch to variable length encoded integers.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile.am' | |||
2 | --- Makefile.am 2012-12-17 07:32:51 +0000 | |||
3 | +++ Makefile.am 2013-03-31 05:51:20 +0000 | |||
4 | @@ -47,6 +47,8 @@ | |||
5 | 47 | include_subunitdir = $(includedir)/subunit | 47 | include_subunitdir = $(includedir)/subunit |
6 | 48 | 48 | ||
7 | 49 | dist_bin_SCRIPTS = \ | 49 | dist_bin_SCRIPTS = \ |
8 | 50 | filters/subunit-1to2 \ | ||
9 | 51 | filters/subunit-2to1 \ | ||
10 | 50 | filters/subunit-filter \ | 52 | filters/subunit-filter \ |
11 | 51 | filters/subunit-ls \ | 53 | filters/subunit-ls \ |
12 | 52 | filters/subunit-notify \ | 54 | filters/subunit-notify \ |
13 | 53 | 55 | ||
14 | === modified file 'NEWS' | |||
15 | --- NEWS 2013-02-07 11:33:11 +0000 | |||
16 | +++ NEWS 2013-03-31 05:51:20 +0000 | |||
17 | @@ -5,6 +5,41 @@ | |||
18 | 5 | NEXT (In development) | 5 | NEXT (In development) |
19 | 6 | --------------------- | 6 | --------------------- |
20 | 7 | 7 | ||
21 | 8 | v2 protocol draft included in this release. The v2 protocol trades off human | ||
22 | 9 | readability for a massive improvement in robustness, the ability to represent | ||
23 | 10 | concurrent tests in a single stream, cheaper parsing, and that provides | ||
24 | 11 | significantly better in-line debugging support and structured forwarding | ||
25 | 12 | of non-test data (such as stdout or stdin data). | ||
26 | 13 | |||
27 | 14 | This change includes two new filters (subunit-1to2 and subunit-2to1). Use | ||
28 | 15 | these filters to convert old streams to v2 and convert v2 streams to v1. | ||
29 | 16 | |||
30 | 17 | All the other filters now only parse and emit v2 streams. V2 is still in | ||
31 | 18 | draft format, so if you want to delay and wait for v2 to be finalised, you | ||
32 | 19 | should use subunit-2to1 before any serialisation steps take place. | ||
33 | 20 | With the ability to encapsulate multiple non-test streams, another significant | ||
34 | 21 | cange is that filters which emit subunit now encapsulate any non-subunit they | ||
35 | 22 | encounter, labelling it 'stdout'. This permits multiplexing such streams and | ||
36 | 23 | detangling the stdout streams from each input. | ||
37 | 24 | |||
38 | 25 | The subunit libraries (Python etc) have not changed their behaviour: they | ||
39 | 26 | still emit v1 from their existing API calls. New API's are being added | ||
40 | 27 | and applications should migrate once their language has those API's available. | ||
41 | 28 | |||
42 | 29 | IMPROVEMENTS | ||
43 | 30 | ~~~~~~~~~~~~ | ||
44 | 31 | |||
45 | 32 | * ``subunit.run`` now replaces sys.stdout to ensure that stdout is unbuffered | ||
46 | 33 | - without this pdb output is not reliably visible when stdout is a pipe | ||
47 | 34 | as it usually is. (Robert Collins) | ||
48 | 35 | |||
49 | 36 | * v2 protocol draft included in this release. | ||
50 | 37 | (Robert Collins) | ||
51 | 38 | |||
52 | 39 | * Two new Python classes -- ``StreamResultToBytes`` and | ||
53 | 40 | ``ByteStreamToStreamResult`` handle v2 generation and parsing. | ||
54 | 41 | (Robert Collins) | ||
55 | 42 | |||
56 | 8 | 0.0.10 | 43 | 0.0.10 |
57 | 9 | ------ | 44 | ------ |
58 | 10 | 45 | ||
59 | 11 | 46 | ||
60 | === modified file 'README' | |||
61 | --- README 2013-02-07 11:33:11 +0000 | |||
62 | +++ README 2013-03-31 05:51:20 +0000 | |||
63 | @@ -21,9 +21,26 @@ | |||
64 | 21 | Subunit | 21 | Subunit |
65 | 22 | ------- | 22 | ------- |
66 | 23 | 23 | ||
70 | 24 | Subunit is a streaming protocol for test results. The protocol is human | 24 | Subunit is a streaming protocol for test results. |
71 | 25 | readable and easily generated and parsed. By design all the components of | 25 | |
72 | 26 | the protocol conceptually fit into the xUnit TestCase->TestResult interaction. | 26 | There are two major revisions of the protocol. Version 1 was trivially human |
73 | 27 | readable but had significant defects as far as highly parallel testing was | ||
74 | 28 | concerned - it had no room for doing discovery and execution in parallel, | ||
75 | 29 | required substantial buffering when multiplexing and was fragile - a corrupt | ||
76 | 30 | byte could cause an entire stream to be misparsed. Version 1.1 added | ||
77 | 31 | encapsulation of binary streams which mitigated some of the issues but the | ||
78 | 32 | core remained. | ||
79 | 33 | |||
80 | 34 | Version 2 shares many of the good characteristics of Version 1 - it can be | ||
81 | 35 | embedded into a regular text stream (e.g. from a build system) and it still | ||
82 | 36 | models xUnit style test execution. It also fixes many of the issues with | ||
83 | 37 | Version 1 - Version 2 can be multiplexed without excessive buffering (in | ||
84 | 38 | time or space), it has a well defined recovery mechanism for dealing with | ||
85 | 39 | corrupted streams (e.g. where two processes write to the same stream | ||
86 | 40 | concurrently, or where the stream generator suffers a bug). | ||
87 | 41 | |||
88 | 42 | More details on both protocol version s can be found in the 'Protocol' section | ||
89 | 43 | of this document. | ||
90 | 27 | 44 | ||
91 | 28 | Subunit comes with command line filters to process a subunit stream and | 45 | Subunit comes with command line filters to process a subunit stream and |
92 | 29 | language bindings for python, C, C++ and shell. Bindings are easy to write | 46 | language bindings for python, C, C++ and shell. Bindings are easy to write |
93 | @@ -32,11 +49,12 @@ | |||
94 | 32 | A number of useful things can be done easily with subunit: | 49 | A number of useful things can be done easily with subunit: |
95 | 33 | * Test aggregation: Tests run separately can be combined and then | 50 | * Test aggregation: Tests run separately can be combined and then |
96 | 34 | reported/displayed together. For instance, tests from different languages | 51 | reported/displayed together. For instance, tests from different languages |
98 | 35 | can be shown as a seamless whole. | 52 | can be shown as a seamless whole, and tests running on multiple machines |
99 | 53 | can be aggregated into a single stream through a multiplexer. | ||
100 | 36 | * Test archiving: A test run may be recorded and replayed later. | 54 | * Test archiving: A test run may be recorded and replayed later. |
101 | 37 | * Test isolation: Tests that may crash or otherwise interact badly with each | 55 | * Test isolation: Tests that may crash or otherwise interact badly with each |
102 | 38 | other can be run seperately and then aggregated, rather than interfering | 56 | other can be run seperately and then aggregated, rather than interfering |
104 | 39 | with each other. | 57 | with each other or requiring an adhoc test->runner reporting protocol. |
105 | 40 | * Grid testing: subunit can act as the necessary serialisation and | 58 | * Grid testing: subunit can act as the necessary serialisation and |
106 | 41 | deserialiation to get test runs on distributed machines to be reported in | 59 | deserialiation to get test runs on distributed machines to be reported in |
107 | 42 | real time. | 60 | real time. |
108 | @@ -68,20 +86,20 @@ | |||
109 | 68 | in python and there are facilities for using Subunit to increase test isolation | 86 | in python and there are facilities for using Subunit to increase test isolation |
110 | 69 | seamlessly within a test suite. | 87 | seamlessly within a test suite. |
111 | 70 | 88 | ||
114 | 71 | One simple way to run an existing python test suite and have it output subunit | 89 | The most common way is to run an existing python test suite and have it output |
115 | 72 | is the module ``subunit.run``:: | 90 | subunit via the ``subunit.run`` module:: |
116 | 73 | 91 | ||
117 | 74 | $ python -m subunit.run mypackage.tests.test_suite | 92 | $ python -m subunit.run mypackage.tests.test_suite |
118 | 75 | 93 | ||
119 | 76 | For more information on the Python support Subunit offers , please see | 94 | For more information on the Python support Subunit offers , please see |
121 | 77 | ``pydoc subunit``, or the source in ``python/subunit/__init__.py`` | 95 | ``pydoc subunit``, or the source in ``python/subunit/`` |
122 | 78 | 96 | ||
123 | 79 | C | 97 | C |
124 | 80 | = | 98 | = |
125 | 81 | 99 | ||
129 | 82 | Subunit has C bindings to emit the protocol, and comes with a patch for 'check' | 100 | Subunit has C bindings to emit the protocol. The 'check' C unit testing project |
130 | 83 | which has been nominally accepted by the 'check' developers. See 'c/README' for | 101 | has included subunit support in their project for some years now. See |
131 | 84 | more details. | 102 | 'c/README' for more details. |
132 | 85 | 103 | ||
133 | 86 | C++ | 104 | C++ |
134 | 87 | === | 105 | === |
135 | @@ -92,9 +110,13 @@ | |||
136 | 92 | shell | 110 | shell |
137 | 93 | ===== | 111 | ===== |
138 | 94 | 112 | ||
142 | 95 | Similar to C, the shell bindings consist of simple functions to output protocol | 113 | There are two sets of shell tools. There are filters, which accept a subunit |
143 | 96 | elements, and a patch for adding subunit output to the 'ShUnit' shell test | 114 | stream on stdin and output processed data (or a transformed stream) on stdout. |
144 | 97 | runner. See 'shell/README' for details. | 115 | |
145 | 116 | Then there are unittest facilities similar to those for C : shell bindings | ||
146 | 117 | consisting of simple functions to output protocol elements, and a patch for | ||
147 | 118 | adding subunit output to the 'ShUnit' shell test runner. See 'shell/README' for | ||
148 | 119 | details. | ||
149 | 98 | 120 | ||
150 | 99 | Filter recipes | 121 | Filter recipes |
151 | 100 | -------------- | 122 | -------------- |
152 | @@ -104,9 +126,225 @@ | |||
153 | 104 | subunit-filter --without 'AttributeError.*flavor' | 126 | subunit-filter --without 'AttributeError.*flavor' |
154 | 105 | 127 | ||
155 | 106 | 128 | ||
156 | 129 | The xUnit test model | ||
157 | 130 | -------------------- | ||
158 | 131 | |||
159 | 132 | Subunit implements a slightly modified xUnit test model. The stock standard | ||
160 | 133 | model is that there are tests, which have an id(), can be run, and when run | ||
161 | 134 | start, emit an outcome (like success or failure) and then finish. | ||
162 | 135 | |||
163 | 136 | Subunit extends this with the idea of test enumeration (find out about tests | ||
164 | 137 | a runner has without running them), tags (allow users to describe tests in | ||
165 | 138 | ways the test framework doesn't apply any semantic value to), file attachments | ||
166 | 139 | (allow arbitrary data to make analysing a failure easy) and timestamps. | ||
167 | 140 | |||
168 | 107 | The protocol | 141 | The protocol |
169 | 108 | ------------ | 142 | ------------ |
170 | 109 | 143 | ||
171 | 144 | Version 2, or v2 is new and still under development, but is intended to | ||
172 | 145 | supercede version 1 in the very near future. Subunit's bundled tools accept | ||
173 | 146 | only version 2 and only emit version 2, but the new filters subunit-1to2 and | ||
174 | 147 | subunit-2to1 can be used to interoperate with older third party libraries. | ||
175 | 148 | |||
176 | 149 | Version 2 | ||
177 | 150 | ========= | ||
178 | 151 | |||
179 | 152 | Version 2 is a binary protocol consisting of independent packets that can be | ||
180 | 153 | embedded in the output from tools like make - as long as each packet has no | ||
181 | 154 | other bytes mixed in with it (which 'make -j N>1' has a tendency of doing). | ||
182 | 155 | Version 2 is currently in draft form, and early adopters should be willing | ||
183 | 156 | to either discard stored results (if protocol changes are made), or bulk | ||
184 | 157 | convert them back to v1 and then to a newer edition of v2. | ||
185 | 158 | |||
186 | 159 | The protocol synchronises at the start of the stream, after a packet, or | ||
187 | 160 | after any 0x0A byte. That is, a subunit v2 packet starts after a newline or | ||
188 | 161 | directly after the end of the prior packet. | ||
189 | 162 | |||
190 | 163 | Subunit is intended to be transported over a reliable streaming protocol such | ||
191 | 164 | as TCP. As such it does not concern itself with out of order delivery of | ||
192 | 165 | packets. However, because of the possibility of corruption due to either | ||
193 | 166 | bugs in the sender, or due to mixed up data from concurrent writes to the same | ||
194 | 167 | fd when being embedded, subunit strives to recover reasonably gracefully from | ||
195 | 168 | damaged data. | ||
196 | 169 | |||
197 | 170 | A key design goal for Subunit version 2 is to allow processing and multiplexing | ||
198 | 171 | without forcing buffering for semantic correctness, as buffering tends to hide | ||
199 | 172 | hung or otherwise misbehaving tests. That said, limited time based buffering | ||
200 | 173 | for network efficiency is a good idea - this is ultimately implementator | ||
201 | 174 | choice. Line buffering is also discouraged for subunit streams, as dropping | ||
202 | 175 | into a debugger or other tool may require interactive traffic even if line | ||
203 | 176 | buffering would not otherwise be a problem. | ||
204 | 177 | |||
205 | 178 | In version two there are two conceptual events - a test status event and a file | ||
206 | 179 | attachment event. Events may have timestamps, and the path of multiplexers that | ||
207 | 180 | an event is routed through is recorded to permit sending actions back to the | ||
208 | 181 | source (such as new tests to run or stdin for driving debuggers and other | ||
209 | 182 | interactive input). Test status events are used to enumerate tests, to report | ||
210 | 183 | tests and test helpers as they run. Tests may have tags, used to allow | ||
211 | 184 | tunnelling extra meanings through subunit without requiring parsing of | ||
212 | 185 | arbitrary file attachments. Things that are not standalone tests get marked | ||
213 | 186 | as such by setting the 'Runnable' flag to false. (For instance, individual | ||
214 | 187 | assertions in TAP are not runnable tests, only the top level TAP test script | ||
215 | 188 | is runnable). | ||
216 | 189 | |||
217 | 190 | File attachments are used to provide rich detail about the nature of a failure. | ||
218 | 191 | File attachments can also be used to encapsulate stdout and stderr both during | ||
219 | 192 | and outside tests. | ||
220 | 193 | |||
221 | 194 | Most numbers are stored in network byte order - Most Significant Byte first | ||
222 | 195 | encoded using a variation of http://www.dlugosz.com/ZIP2/VLI.html. The first | ||
223 | 196 | byte's top 2 high order bits encode the total number of octets in the number. | ||
224 | 197 | This encoding can encode values from 0 to 2**30-1, enough to encode a | ||
225 | 198 | nanosecond. Numbers that are not variable length encoded are still stored in | ||
226 | 199 | MSB order. | ||
227 | 200 | |||
228 | 201 | prefix octets max max | ||
229 | 202 | +-------+--------+---------+------------+ | ||
230 | 203 | | 00 | 1 | 2**6-1 | 63 | | ||
231 | 204 | | 01 | 2 | 2**14-1 | 16383 | | ||
232 | 205 | | 10 | 3 | 2**22-1 | 4194303 | | ||
233 | 206 | | 11 | 4 | 2**30-1 | 1073741823 | | ||
234 | 207 | +-------+--------+---------+------------+ | ||
235 | 208 | |||
236 | 209 | All variable length elements of the packet are stored with a length prefix | ||
237 | 210 | number allowing them to be skipped over for consumers that don't need to | ||
238 | 211 | interpret them. | ||
239 | 212 | |||
240 | 213 | UTF-8 strings are with no terminating NUL and should not have any embedded NULs | ||
241 | 214 | (implementations SHOULD validate any such strings that they process and take | ||
242 | 215 | some remedial action (such as discarding the packet as corrupt). | ||
243 | 216 | |||
244 | 217 | In short the structure of a packet is: | ||
245 | 218 | PACKET := SIGNATURE FLAGS PACKET_LENGTH TIMESTAMP? TESTID? TAGS? MIME? | ||
246 | 219 | FILECONTENT? ROUTING_CODE? CRC32 | ||
247 | 220 | |||
248 | 221 | In more detail... | ||
249 | 222 | |||
250 | 223 | Packets are identified by a single byte signature - 0xB3, which is never legal | ||
251 | 224 | in a UTF-8 stream as the first byte of a character. 0xB3 starts with the first | ||
252 | 225 | bit set and the second not, which is the UTF-8 signature for a continuation | ||
253 | 226 | byte. 0xB3 was chosen as 0x73 ('s' in ASCII') with the top two bits replaced by | ||
254 | 227 | the 1 and 0 for a continuation byte. | ||
255 | 228 | |||
256 | 229 | If subunit packets are being embedded in a non-UTF-8 text stream, where 0x73 is | ||
257 | 230 | a legal character, consider either recoding the text to UTF-8, or using | ||
258 | 231 | subunit's 'file' packets to embed the text stream in subunit, rather than the | ||
259 | 232 | other way around. | ||
260 | 233 | |||
261 | 234 | Following the signature byte comes a 16-bit flags field, which includes a | ||
262 | 235 | 4-bit version field - if the version is not 0x2 then the packet cannot be | ||
263 | 236 | read. It is recommended to signal an error at this point (e.g. by emitting | ||
264 | 237 | a synthetic error packet and returning to the top level loop to look for | ||
265 | 238 | new packets, or exiting with an error). If recovery is desired, treat the | ||
266 | 239 | packet signature as an opaque byte and scan for a new synchronisation point. | ||
267 | 240 | NB: Subunit V1 and V2 packets may legitimately included 0xB3 internally, | ||
268 | 241 | as they are an 8-bit safe container format, so recovery from this situation | ||
269 | 242 | may involve an arbitrary number of false positives until an actual packet | ||
270 | 243 | is encountered : and even then it may still be false, failing after passing | ||
271 | 244 | the version check due to coincidence. | ||
272 | 245 | |||
273 | 246 | Flags are stored in network byte order too. | ||
274 | 247 | +-------------------------+------------------------+ | ||
275 | 248 | | High byte | Low byte | | ||
276 | 249 | | 15 14 13 12 11 10 9 8 | 7 6 5 4 3 2 1 0 | | ||
277 | 250 | | VERSION |feature bits| | | ||
278 | 251 | +------------+------------+------------------------+ | ||
279 | 252 | |||
280 | 253 | Valid version values are: | ||
281 | 254 | 0x2 - version 2 | ||
282 | 255 | |||
283 | 256 | Feature bits: | ||
284 | 257 | Bit 11 - mask 0x0800 - Test id present. | ||
285 | 258 | Bit 10 - mask 0x0400 - Routing code present. | ||
286 | 259 | Bit 9 - mask 0x0200 - Timestamp present. | ||
287 | 260 | Bit 8 - mask 0x0100 - Test is 'runnable'. | ||
288 | 261 | Bit 7 - mask 0x0080 - Tags are present. | ||
289 | 262 | Bit 6 - mask 0x0040 - File content is present. | ||
290 | 263 | Bit 5 - mask 0x0020 - File MIME type is present. | ||
291 | 264 | Bit 4 - mask 0x0010 - EOF marker. | ||
292 | 265 | Bit 3 - mask 0x0008 - Must be zero in version 2. | ||
293 | 266 | |||
294 | 267 | Test status gets three bits: | ||
295 | 268 | Bit 2 | Bit 1 | Bit 0 - mask 0x0007 - A test status enum lookup: | ||
296 | 269 | 000 - undefined / no test | ||
297 | 270 | 001 - Enumeration / existence | ||
298 | 271 | 002 - In progress | ||
299 | 272 | 003 - Success | ||
300 | 273 | 004 - Unexpected Success | ||
301 | 274 | 005 - Skipped | ||
302 | 275 | 006 - Failed | ||
303 | 276 | 007 - Expected failure | ||
304 | 277 | |||
305 | 278 | After the flags field is a number field giving the length in bytes for the | ||
306 | 279 | entire packet including the signature and the checksum. This length must | ||
307 | 280 | be less than 4MiB - 4194303 bytes. The encoding can obviously record a larger | ||
308 | 281 | number but one of the goals is to avoid requiring large buffers, or causing | ||
309 | 282 | large latency in the packet forward/processing pipeline. Larger file | ||
310 | 283 | attachments can be communicated in multiple packets, and the overhead in such a | ||
311 | 284 | 4MiB packet is approximately 0.2%. | ||
312 | 285 | |||
313 | 286 | The rest of the packet is a series of optional features as specified by the set | ||
314 | 287 | feature bits in the flags field. When absent they are entirely absent. | ||
315 | 288 | |||
316 | 289 | Forwarding and multiplexing of packets can be done without interpreting the | ||
317 | 290 | remainder of the packet until the routing code and checksum (which are both at | ||
318 | 291 | the end of the packet). Additionally, routers can often avoid copying or moving | ||
319 | 292 | the bulk of the packet, as long as the routing code size increase doesn't force | ||
320 | 293 | the length encoding to take up a new byte (which will only happen to packets | ||
321 | 294 | less than or equal to 16KiB in length) - large packets are very efficient to | ||
322 | 295 | route. | ||
323 | 296 | |||
324 | 297 | Timestamp when present is a 32 bit unsigned integer for secnods, and a variable | ||
325 | 298 | length number for nanoseconds, representing UTC time since Unix Epoch in | ||
326 | 299 | seconds and nanoseconds. | ||
327 | 300 | |||
328 | 301 | Test id when present is a UTF-8 string. The test id should uniquely identify | ||
329 | 302 | runnable tests such that they can be selected individually. For tests and other | ||
330 | 303 | actions which cannot be individually run (such as test | ||
331 | 304 | fixtures/layers/subtests) uniqueness is not required (though being human | ||
332 | 305 | meaningful is highly recommended). | ||
333 | 306 | |||
334 | 307 | Tags when present is a length prefixed vector of UTF-8 strings, one per tag. | ||
335 | 308 | There are no restrictions on tag content (other than the restrictions on UTF-8 | ||
336 | 309 | strings in subunit in general). Tags have no ordering. | ||
337 | 310 | |||
338 | 311 | When a MIME type is present, it defines the MIME type for the file across all | ||
339 | 312 | packets same file (routing code + testid + name uniquely identifies a file, | ||
340 | 313 | reset when EOF is flagged). If a file never has a MIME type set, it should be | ||
341 | 314 | treated as application/octet-stream. | ||
342 | 315 | |||
343 | 316 | File content when present is a UTF-8 string for the name followed by the length | ||
344 | 317 | in bytes of the content, and then the content octets. | ||
345 | 318 | |||
346 | 319 | If present routing code is a UTF-8 string. The routing code is used to | ||
347 | 320 | determine which test backend a test was running on when doing data analysis, | ||
348 | 321 | and to route stdin to the test process if interaction is required. | ||
349 | 322 | |||
350 | 323 | Multiplexers SHOULD add a routing code if none is present, and prefix any | ||
351 | 324 | existing routing code with a routing code ('/' separated) if one is already | ||
352 | 325 | present. For example, a multiplexer might label each stream it is multiplexing | ||
353 | 326 | with a simple ordinal ('0', '1' etc), and given an incoming packet with route | ||
354 | 327 | code '3' from stream '0' would adjust the route code when forwarding the packet | ||
355 | 328 | to be '0/3'. | ||
356 | 329 | |||
357 | 330 | Following the end of the packet is a CRC-32 checksum of the contents of the | ||
358 | 331 | packet including the signature. | ||
359 | 332 | |||
360 | 333 | Example packets | ||
361 | 334 | ~~~~~~~~~~~~~~~ | ||
362 | 335 | |||
363 | 336 | Trivial test "foo" enumeration packet, with test id, runnable set, | ||
364 | 337 | status=enumeration. Spaces below are to visually break up signature / flags / | ||
365 | 338 | length / testid / crc32 | ||
366 | 339 | |||
367 | 340 | b3 2901 0c 03666f6f 08555f1b | ||
368 | 341 | |||
369 | 342 | |||
370 | 343 | Version 1 (and 1.1) | ||
371 | 344 | =================== | ||
372 | 345 | |||
373 | 346 | Version 1 (and 1.1) are mostly human readable protocols. | ||
374 | 347 | |||
375 | 110 | Sample subunit wire contents | 348 | Sample subunit wire contents |
376 | 111 | ---------------------------- | 349 | ---------------------------- |
377 | 112 | 350 | ||
378 | 113 | 351 | ||
379 | === added file 'filters/subunit-1to2' | |||
380 | --- filters/subunit-1to2 1970-01-01 00:00:00 +0000 | |||
381 | +++ filters/subunit-1to2 2013-03-31 05:51:20 +0000 | |||
382 | @@ -0,0 +1,42 @@ | |||
383 | 1 | #!/usr/bin/env python | ||
384 | 2 | # subunit: extensions to python unittest to get test results from subprocesses. | ||
385 | 3 | # Copyright (C) 2013 Robert Collins <robertc@robertcollins.net> | ||
386 | 4 | # | ||
387 | 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause | ||
388 | 6 | # license at the users choice. A copy of both licenses are available in the | ||
389 | 7 | # project source as Apache-2.0 and BSD. You may not use this file except in | ||
390 | 8 | # compliance with one of these two licences. | ||
391 | 9 | # | ||
392 | 10 | # Unless required by applicable law or agreed to in writing, software | ||
393 | 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
394 | 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
395 | 13 | # license you chose for the specific language governing permissions and | ||
396 | 14 | # limitations under that license. | ||
397 | 15 | # | ||
398 | 16 | |||
399 | 17 | """Convert a version 1 subunit stream to version 2 stream.""" | ||
400 | 18 | |||
401 | 19 | from optparse import OptionParser | ||
402 | 20 | import sys | ||
403 | 21 | |||
404 | 22 | from testtools import ExtendedToStreamDecorator | ||
405 | 23 | |||
406 | 24 | from subunit import StreamResultToBytes | ||
407 | 25 | from subunit.filters import run_tests_from_stream | ||
408 | 26 | |||
409 | 27 | |||
410 | 28 | def make_options(description): | ||
411 | 29 | parser = OptionParser(description=__doc__) | ||
412 | 30 | return parser | ||
413 | 31 | |||
414 | 32 | |||
415 | 33 | def main(): | ||
416 | 34 | parser = make_options(__doc__) | ||
417 | 35 | (options, args) = parser.parse_args() | ||
418 | 36 | run_tests_from_stream(sys.stdin, | ||
419 | 37 | ExtendedToStreamDecorator(StreamResultToBytes(sys.stdout))) | ||
420 | 38 | sys.exit(0) | ||
421 | 39 | |||
422 | 40 | |||
423 | 41 | if __name__ == '__main__': | ||
424 | 42 | main() | ||
425 | 0 | 43 | ||
426 | === added file 'filters/subunit-2to1' | |||
427 | --- filters/subunit-2to1 1970-01-01 00:00:00 +0000 | |||
428 | +++ filters/subunit-2to1 2013-03-31 05:51:20 +0000 | |||
429 | @@ -0,0 +1,46 @@ | |||
430 | 1 | #!/usr/bin/env python | ||
431 | 2 | # subunit: extensions to python unittest to get test results from subprocesses. | ||
432 | 3 | # Copyright (C) 2013 Robert Collins <robertc@robertcollins.net> | ||
433 | 4 | # | ||
434 | 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause | ||
435 | 6 | # license at the users choice. A copy of both licenses are available in the | ||
436 | 7 | # project source as Apache-2.0 and BSD. You may not use this file except in | ||
437 | 8 | # compliance with one of these two licences. | ||
438 | 9 | # | ||
439 | 10 | # Unless required by applicable law or agreed to in writing, software | ||
440 | 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
441 | 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
442 | 13 | # license you chose for the specific language governing permissions and | ||
443 | 14 | # limitations under that license. | ||
444 | 15 | # | ||
445 | 16 | |||
446 | 17 | """Convert a version 2 subunit stream to a version 1 stream.""" | ||
447 | 18 | |||
448 | 19 | from optparse import OptionParser | ||
449 | 20 | import sys | ||
450 | 21 | |||
451 | 22 | from testtools import StreamToExtendedDecorator | ||
452 | 23 | |||
453 | 24 | from subunit import ByteStreamToStreamResult, TestProtocolClient | ||
454 | 25 | from subunit.filters import run_tests_from_stream | ||
455 | 26 | |||
456 | 27 | |||
457 | 28 | def make_options(description): | ||
458 | 29 | parser = OptionParser(description=__doc__) | ||
459 | 30 | return parser | ||
460 | 31 | |||
461 | 32 | |||
462 | 33 | def main(): | ||
463 | 34 | parser = make_options(__doc__) | ||
464 | 35 | (options, args) = parser.parse_args() | ||
465 | 36 | case = ByteStreamToStreamResult(sys.stdin, non_subunit_name='stdout') | ||
466 | 37 | result = StreamToExtendedDecorator(TestProtocolClient(sys.stdout)) | ||
467 | 38 | # What about stdout chunks? | ||
468 | 39 | result.startTestRun() | ||
469 | 40 | case.run(result) | ||
470 | 41 | result.stopTestRun() | ||
471 | 42 | sys.exit(0) | ||
472 | 43 | |||
473 | 44 | |||
474 | 45 | if __name__ == '__main__': | ||
475 | 46 | main() | ||
476 | 0 | 47 | ||
477 | === modified file 'filters/subunit-filter' | |||
478 | --- filters/subunit-filter 2012-05-03 08:18:01 +0000 | |||
479 | +++ filters/subunit-filter 2013-03-31 05:51:20 +0000 | |||
480 | @@ -1,6 +1,6 @@ | |||
481 | 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
482 | 2 | # subunit: extensions to python unittest to get test results from subprocesses. | 2 | # subunit: extensions to python unittest to get test results from subprocesses. |
484 | 3 | # Copyright (C) 2008 Robert Collins <robertc@robertcollins.net> | 3 | # Copyright (C) 200-2013 Robert Collins <robertc@robertcollins.net> |
485 | 4 | # (C) 2009 Martin Pool | 4 | # (C) 2009 Martin Pool |
486 | 5 | # | 5 | # |
487 | 6 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause | 6 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause |
488 | @@ -30,10 +30,12 @@ | |||
489 | 30 | import sys | 30 | import sys |
490 | 31 | import re | 31 | import re |
491 | 32 | 32 | ||
492 | 33 | from testtools import ExtendedToStreamDecorator, StreamToExtendedDecorator | ||
493 | 34 | |||
494 | 33 | from subunit import ( | 35 | from subunit import ( |
495 | 34 | DiscardStream, | 36 | DiscardStream, |
496 | 35 | ProtocolTestCase, | 37 | ProtocolTestCase, |
498 | 36 | TestProtocolClient, | 38 | StreamResultToBytes, |
499 | 37 | read_test_list, | 39 | read_test_list, |
500 | 38 | ) | 40 | ) |
501 | 39 | from subunit.filters import filter_by_result | 41 | from subunit.filters import filter_by_result |
502 | @@ -55,9 +57,11 @@ | |||
503 | 55 | parser.add_option("-f", "--no-failure", action="store_true", | 57 | parser.add_option("-f", "--no-failure", action="store_true", |
504 | 56 | help="exclude failures", dest="failure") | 58 | help="exclude failures", dest="failure") |
505 | 57 | parser.add_option("--passthrough", action="store_false", | 59 | parser.add_option("--passthrough", action="store_false", |
507 | 58 | help="Show all non subunit input.", default=False, dest="no_passthrough") | 60 | help="Forward non-subunit input as 'stdout'.", default=False, |
508 | 61 | dest="no_passthrough") | ||
509 | 59 | parser.add_option("--no-passthrough", action="store_true", | 62 | parser.add_option("--no-passthrough", action="store_true", |
511 | 60 | help="Hide all non subunit input.", default=False, dest="no_passthrough") | 63 | help="Discard all non subunit input.", default=False, |
512 | 64 | dest="no_passthrough") | ||
513 | 61 | parser.add_option("-s", "--success", action="store_false", | 65 | parser.add_option("-s", "--success", action="store_false", |
514 | 62 | help="include successes", dest="success") | 66 | help="include successes", dest="success") |
515 | 63 | parser.add_option("--no-success", action="store_true", | 67 | parser.add_option("--no-success", action="store_true", |
516 | @@ -126,15 +130,16 @@ | |||
517 | 126 | fixup_expected_failures = set() | 130 | fixup_expected_failures = set() |
518 | 127 | for path in options.fixup_expected_failures or (): | 131 | for path in options.fixup_expected_failures or (): |
519 | 128 | fixup_expected_failures.update(read_test_list(path)) | 132 | fixup_expected_failures.update(read_test_list(path)) |
522 | 129 | return TestResultFilter( | 133 | return StreamToExtendedDecorator(TestResultFilter( |
523 | 130 | TestProtocolClient(output), | 134 | ExtendedToStreamDecorator( |
524 | 135 | StreamResultToBytes(output)), | ||
525 | 131 | filter_error=options.error, | 136 | filter_error=options.error, |
526 | 132 | filter_failure=options.failure, | 137 | filter_failure=options.failure, |
527 | 133 | filter_success=options.success, | 138 | filter_success=options.success, |
528 | 134 | filter_skip=options.skip, | 139 | filter_skip=options.skip, |
529 | 135 | filter_xfail=options.xfail, | 140 | filter_xfail=options.xfail, |
530 | 136 | filter_predicate=predicate, | 141 | filter_predicate=predicate, |
532 | 137 | fixup_expected_failures=fixup_expected_failures) | 142 | fixup_expected_failures=fixup_expected_failures)) |
533 | 138 | 143 | ||
534 | 139 | 144 | ||
535 | 140 | def main(): | 145 | def main(): |
536 | @@ -150,7 +155,8 @@ | |||
537 | 150 | lambda output_to: _make_result(sys.stdout, options, filter_predicate), | 155 | lambda output_to: _make_result(sys.stdout, options, filter_predicate), |
538 | 151 | output_path=None, | 156 | output_path=None, |
539 | 152 | passthrough=(not options.no_passthrough), | 157 | passthrough=(not options.no_passthrough), |
541 | 153 | forward=False) | 158 | forward=False, |
542 | 159 | protocol_version=2) | ||
543 | 154 | sys.exit(0) | 160 | sys.exit(0) |
544 | 155 | 161 | ||
545 | 156 | 162 | ||
546 | 157 | 163 | ||
547 | === modified file 'filters/subunit-ls' | |||
548 | --- filters/subunit-ls 2011-05-23 09:57:58 +0000 | |||
549 | +++ filters/subunit-ls 2013-03-31 05:51:20 +0000 | |||
550 | @@ -19,9 +19,14 @@ | |||
551 | 19 | from optparse import OptionParser | 19 | from optparse import OptionParser |
552 | 20 | import sys | 20 | import sys |
553 | 21 | 21 | ||
555 | 22 | from subunit import DiscardStream, ProtocolTestCase | 22 | from testtools import ( |
556 | 23 | CopyStreamResult, StreamToExtendedDecorator, StreamResultRouter, | ||
557 | 24 | StreamSummary) | ||
558 | 25 | |||
559 | 26 | from subunit import ByteStreamToStreamResult | ||
560 | 27 | from subunit.filters import run_tests_from_stream | ||
561 | 23 | from subunit.test_results import ( | 28 | from subunit.test_results import ( |
563 | 24 | AutoTimingTestResultDecorator, | 29 | CatFiles, |
564 | 25 | TestIdPrintingResult, | 30 | TestIdPrintingResult, |
565 | 26 | ) | 31 | ) |
566 | 27 | 32 | ||
567 | @@ -30,18 +35,24 @@ | |||
568 | 30 | parser.add_option("--times", action="store_true", | 35 | parser.add_option("--times", action="store_true", |
569 | 31 | help="list the time each test took (requires a timestamped stream)", | 36 | help="list the time each test took (requires a timestamped stream)", |
570 | 32 | default=False) | 37 | default=False) |
571 | 38 | parser.add_option("--exists", action="store_true", | ||
572 | 39 | help="list tests that are reported as existing (as well as ran)", | ||
573 | 40 | default=False) | ||
574 | 33 | parser.add_option("--no-passthrough", action="store_true", | 41 | parser.add_option("--no-passthrough", action="store_true", |
575 | 34 | help="Hide all non subunit input.", default=False, dest="no_passthrough") | 42 | help="Hide all non subunit input.", default=False, dest="no_passthrough") |
576 | 35 | (options, args) = parser.parse_args() | 43 | (options, args) = parser.parse_args() |
584 | 36 | result = AutoTimingTestResultDecorator( | 44 | test = ByteStreamToStreamResult(sys.stdin, non_subunit_name="stdout") |
585 | 37 | TestIdPrintingResult(sys.stdout, options.times)) | 45 | result = TestIdPrintingResult(sys.stdout, options.times, options.exists) |
586 | 38 | if options.no_passthrough: | 46 | if not options.no_passthrough: |
587 | 39 | passthrough_stream = DiscardStream() | 47 | result = StreamResultRouter(result) |
588 | 40 | else: | 48 | cat = CatFiles(sys.stdout) |
589 | 41 | passthrough_stream = None | 49 | result.map(cat, 'test_id', test_id=None) |
590 | 42 | test = ProtocolTestCase(sys.stdin, passthrough=passthrough_stream) | 50 | summary = StreamSummary() |
591 | 51 | result = CopyStreamResult([result, summary]) | ||
592 | 52 | result.startTestRun() | ||
593 | 43 | test.run(result) | 53 | test.run(result) |
595 | 44 | if result.wasSuccessful(): | 54 | result.stopTestRun() |
596 | 55 | if summary.wasSuccessful(): | ||
597 | 45 | exit_code = 0 | 56 | exit_code = 0 |
598 | 46 | else: | 57 | else: |
599 | 47 | exit_code = 1 | 58 | exit_code = 1 |
600 | 48 | 59 | ||
601 | === modified file 'filters/subunit-notify' | |||
602 | --- filters/subunit-notify 2012-03-27 11:17:37 +0000 | |||
603 | +++ filters/subunit-notify 2013-03-31 05:51:20 +0000 | |||
604 | @@ -19,6 +19,7 @@ | |||
605 | 19 | import pygtk | 19 | import pygtk |
606 | 20 | pygtk.require('2.0') | 20 | pygtk.require('2.0') |
607 | 21 | import pynotify | 21 | import pynotify |
608 | 22 | from testtools import StreamToExtendedDecorator | ||
609 | 22 | 23 | ||
610 | 23 | from subunit import TestResultStats | 24 | from subunit import TestResultStats |
611 | 24 | from subunit.filters import run_filter_script | 25 | from subunit.filters import run_filter_script |
612 | @@ -28,6 +29,7 @@ | |||
613 | 28 | 29 | ||
614 | 29 | 30 | ||
615 | 30 | def notify_of_result(result): | 31 | def notify_of_result(result): |
616 | 32 | result = result.decorated | ||
617 | 31 | if result.failed_tests > 0: | 33 | if result.failed_tests > 0: |
618 | 32 | summary = "Test run failed" | 34 | summary = "Test run failed" |
619 | 33 | else: | 35 | else: |
620 | @@ -41,4 +43,6 @@ | |||
621 | 41 | nw.show() | 43 | nw.show() |
622 | 42 | 44 | ||
623 | 43 | 45 | ||
625 | 44 | run_filter_script(TestResultStats, __doc__, notify_of_result) | 46 | run_filter_script( |
626 | 47 | lambda output:StreamToExtendedDecorator(TestResultStats(output)), | ||
627 | 48 | __doc__, notify_of_result, protocol_version=2) | ||
628 | 45 | 49 | ||
629 | === modified file 'filters/subunit-stats' | |||
630 | --- filters/subunit-stats 2009-09-30 12:04:18 +0000 | |||
631 | +++ filters/subunit-stats 2013-03-31 05:51:20 +0000 | |||
632 | @@ -16,26 +16,17 @@ | |||
633 | 16 | 16 | ||
634 | 17 | """Filter a subunit stream to get aggregate statistics.""" | 17 | """Filter a subunit stream to get aggregate statistics.""" |
635 | 18 | 18 | ||
636 | 19 | from optparse import OptionParser | ||
637 | 20 | import sys | 19 | import sys |
646 | 21 | import unittest | 20 | |
647 | 22 | 21 | from testtools import StreamToExtendedDecorator | |
648 | 23 | from subunit import DiscardStream, ProtocolTestCase, TestResultStats | 22 | |
649 | 24 | 23 | from subunit import TestResultStats | |
650 | 25 | parser = OptionParser(description=__doc__) | 24 | from subunit.filters import run_filter_script |
651 | 26 | parser.add_option("--no-passthrough", action="store_true", | 25 | |
652 | 27 | help="Hide all non subunit input.", default=False, dest="no_passthrough") | 26 | |
645 | 28 | (options, args) = parser.parse_args() | ||
653 | 29 | result = TestResultStats(sys.stdout) | 27 | result = TestResultStats(sys.stdout) |
666 | 30 | if options.no_passthrough: | 28 | def show_stats(r): |
667 | 31 | passthrough_stream = DiscardStream() | 29 | r.decorated.formatStats() |
668 | 32 | else: | 30 | run_filter_script( |
669 | 33 | passthrough_stream = None | 31 | lambda output:StreamToExtendedDecorator(result), |
670 | 34 | test = ProtocolTestCase(sys.stdin, passthrough=passthrough_stream) | 32 | __doc__, show_stats, protocol_version=2) |
659 | 35 | test.run(result) | ||
660 | 36 | result.formatStats() | ||
661 | 37 | if result.wasSuccessful(): | ||
662 | 38 | exit_code = 0 | ||
663 | 39 | else: | ||
664 | 40 | exit_code = 1 | ||
665 | 41 | sys.exit(exit_code) | ||
671 | 42 | 33 | ||
672 | === modified file 'filters/subunit2csv' | |||
673 | --- filters/subunit2csv 2012-03-27 10:57:51 +0000 | |||
674 | +++ filters/subunit2csv 2013-03-31 05:51:20 +0000 | |||
675 | @@ -16,8 +16,11 @@ | |||
676 | 16 | 16 | ||
677 | 17 | """Turn a subunit stream into a CSV""" | 17 | """Turn a subunit stream into a CSV""" |
678 | 18 | 18 | ||
679 | 19 | from testtools import StreamToExtendedDecorator | ||
680 | 20 | |||
681 | 19 | from subunit.filters import run_filter_script | 21 | from subunit.filters import run_filter_script |
682 | 20 | from subunit.test_results import CsvResult | 22 | from subunit.test_results import CsvResult |
683 | 21 | 23 | ||
684 | 22 | 24 | ||
686 | 23 | run_filter_script(CsvResult, __doc__) | 25 | run_filter_script(lambda output:StreamToExtendedDecorator(CsvResult(output)), |
687 | 26 | __doc__, protocol_version=2) | ||
688 | 24 | 27 | ||
689 | === modified file 'filters/subunit2gtk' | |||
690 | --- filters/subunit2gtk 2010-01-25 15:45:45 +0000 | |||
691 | +++ filters/subunit2gtk 2013-03-31 05:51:20 +0000 | |||
692 | @@ -46,17 +46,20 @@ | |||
693 | 46 | """Display a subunit stream in a gtk progress window.""" | 46 | """Display a subunit stream in a gtk progress window.""" |
694 | 47 | 47 | ||
695 | 48 | import sys | 48 | import sys |
696 | 49 | import threading | ||
697 | 49 | import unittest | 50 | import unittest |
698 | 50 | 51 | ||
699 | 51 | import pygtk | 52 | import pygtk |
700 | 52 | pygtk.require('2.0') | 53 | pygtk.require('2.0') |
701 | 53 | import gtk, gtk.gdk, gobject | 54 | import gtk, gtk.gdk, gobject |
702 | 54 | 55 | ||
703 | 56 | from testtools import StreamToExtendedDecorator | ||
704 | 57 | |||
705 | 55 | from subunit import ( | 58 | from subunit import ( |
706 | 56 | PROGRESS_POP, | 59 | PROGRESS_POP, |
707 | 57 | PROGRESS_PUSH, | 60 | PROGRESS_PUSH, |
708 | 58 | PROGRESS_SET, | 61 | PROGRESS_SET, |
710 | 59 | TestProtocolServer, | 62 | ByteStreamToStreamResult, |
711 | 60 | ) | 63 | ) |
712 | 61 | from subunit.progress_model import ProgressModel | 64 | from subunit.progress_model import ProgressModel |
713 | 62 | 65 | ||
714 | @@ -139,6 +142,9 @@ | |||
715 | 139 | 142 | ||
716 | 140 | def stopTest(self, test): | 143 | def stopTest(self, test): |
717 | 141 | super(GTKTestResult, self).stopTest(test) | 144 | super(GTKTestResult, self).stopTest(test) |
718 | 145 | gobject.idle_add(self._stopTest) | ||
719 | 146 | |||
720 | 147 | def _stopTest(self): | ||
721 | 142 | self.progress_model.advance() | 148 | self.progress_model.advance() |
722 | 143 | if self.progress_model.width() == 0: | 149 | if self.progress_model.width() == 0: |
723 | 144 | self.pbar.pulse() | 150 | self.pbar.pulse() |
724 | @@ -153,26 +159,26 @@ | |||
725 | 153 | super(GTKTestResult, self).stopTestRun() | 159 | super(GTKTestResult, self).stopTestRun() |
726 | 154 | except AttributeError: | 160 | except AttributeError: |
727 | 155 | pass | 161 | pass |
729 | 156 | self.pbar.set_text('Finished') | 162 | gobject.idle_add(self.pbar.set_text, 'Finished') |
730 | 157 | 163 | ||
731 | 158 | def addError(self, test, err): | 164 | def addError(self, test, err): |
732 | 159 | super(GTKTestResult, self).addError(test, err) | 165 | super(GTKTestResult, self).addError(test, err) |
734 | 160 | self.update_counts() | 166 | gobject.idle_add(self.update_counts) |
735 | 161 | 167 | ||
736 | 162 | def addFailure(self, test, err): | 168 | def addFailure(self, test, err): |
737 | 163 | super(GTKTestResult, self).addFailure(test, err) | 169 | super(GTKTestResult, self).addFailure(test, err) |
739 | 164 | self.update_counts() | 170 | gobject.idle_add(self.update_counts) |
740 | 165 | 171 | ||
741 | 166 | def addSuccess(self, test): | 172 | def addSuccess(self, test): |
742 | 167 | super(GTKTestResult, self).addSuccess(test) | 173 | super(GTKTestResult, self).addSuccess(test) |
744 | 168 | self.update_counts() | 174 | gobject.idle_add(self.update_counts) |
745 | 169 | 175 | ||
746 | 170 | def addSkip(self, test, reason): | 176 | def addSkip(self, test, reason): |
747 | 171 | # addSkip is new in Python 2.7/3.1 | 177 | # addSkip is new in Python 2.7/3.1 |
748 | 172 | addSkip = getattr(super(GTKTestResult, self), 'addSkip', None) | 178 | addSkip = getattr(super(GTKTestResult, self), 'addSkip', None) |
749 | 173 | if callable(addSkip): | 179 | if callable(addSkip): |
750 | 174 | addSkip(test, reason) | 180 | addSkip(test, reason) |
752 | 175 | self.update_counts() | 181 | gobject.idle_add(self.update_counts) |
753 | 176 | 182 | ||
754 | 177 | def addExpectedFailure(self, test, err): | 183 | def addExpectedFailure(self, test, err): |
755 | 178 | # addExpectedFailure is new in Python 2.7/3.1 | 184 | # addExpectedFailure is new in Python 2.7/3.1 |
756 | @@ -180,7 +186,7 @@ | |||
757 | 180 | 'addExpectedFailure', None) | 186 | 'addExpectedFailure', None) |
758 | 181 | if callable(addExpectedFailure): | 187 | if callable(addExpectedFailure): |
759 | 182 | addExpectedFailure(test, err) | 188 | addExpectedFailure(test, err) |
761 | 183 | self.update_counts() | 189 | gobject.idle_add(self.update_counts) |
762 | 184 | 190 | ||
763 | 185 | def addUnexpectedSuccess(self, test): | 191 | def addUnexpectedSuccess(self, test): |
764 | 186 | # addUnexpectedSuccess is new in Python 2.7/3.1 | 192 | # addUnexpectedSuccess is new in Python 2.7/3.1 |
765 | @@ -188,7 +194,7 @@ | |||
766 | 188 | 'addUnexpectedSuccess', None) | 194 | 'addUnexpectedSuccess', None) |
767 | 189 | if callable(addUnexpectedSuccess): | 195 | if callable(addUnexpectedSuccess): |
768 | 190 | addUnexpectedSuccess(test) | 196 | addUnexpectedSuccess(test) |
770 | 191 | self.update_counts() | 197 | gobject.idle_add(self.update_counts) |
771 | 192 | 198 | ||
772 | 193 | def progress(self, offset, whence): | 199 | def progress(self, offset, whence): |
773 | 194 | if whence == PROGRESS_PUSH: | 200 | if whence == PROGRESS_PUSH: |
774 | @@ -212,47 +218,22 @@ | |||
775 | 212 | self.ok_label.set_text(str(self.testsRun - bad)) | 218 | self.ok_label.set_text(str(self.testsRun - bad)) |
776 | 213 | self.not_ok_label.set_text(str(bad)) | 219 | self.not_ok_label.set_text(str(bad)) |
777 | 214 | 220 | ||
817 | 215 | 221 | gobject.threads_init() | |
818 | 216 | class GIOProtocolTestCase(object): | 222 | result = StreamToExtendedDecorator(GTKTestResult()) |
819 | 217 | 223 | test = ByteStreamToStreamResult(sys.stdin, non_subunit_name='stdout') | |
820 | 218 | def __init__(self, stream, result, on_finish): | 224 | # Get setup |
821 | 219 | self.stream = stream | 225 | while gtk.events_pending(): |
822 | 220 | self.schedule_read() | 226 | gtk.main_iteration() |
823 | 221 | self.hup_id = gobject.io_add_watch(stream, gobject.IO_HUP, self.hup) | 227 | # Start IO |
824 | 222 | self.protocol = TestProtocolServer(result) | 228 | def run_and_finish(): |
825 | 223 | self.on_finish = on_finish | 229 | test.run(result) |
826 | 224 | 230 | result.stopTestRun() | |
827 | 225 | def read(self, source, condition, all=False): | 231 | t = threading.Thread(target=run_and_finish) |
828 | 226 | #NB: \o/ actually blocks | 232 | t.daemon = True |
829 | 227 | line = source.readline() | 233 | result.startTestRun() |
830 | 228 | if not line: | 234 | t.start() |
792 | 229 | self.protocol.lostConnection() | ||
793 | 230 | self.on_finish() | ||
794 | 231 | return False | ||
795 | 232 | self.protocol.lineReceived(line) | ||
796 | 233 | # schedule more IO shortly - if we say we're willing to do it | ||
797 | 234 | # immediately we starve things. | ||
798 | 235 | if not all: | ||
799 | 236 | source_id = gobject.timeout_add(1, self.schedule_read) | ||
800 | 237 | return False | ||
801 | 238 | else: | ||
802 | 239 | return True | ||
803 | 240 | |||
804 | 241 | def schedule_read(self): | ||
805 | 242 | self.read_id = gobject.io_add_watch(self.stream, gobject.IO_IN, self.read) | ||
806 | 243 | |||
807 | 244 | def hup(self, source, condition): | ||
808 | 245 | while self.read(source, condition, all=True): pass | ||
809 | 246 | self.protocol.lostConnection() | ||
810 | 247 | gobject.source_remove(self.read_id) | ||
811 | 248 | self.on_finish() | ||
812 | 249 | return False | ||
813 | 250 | |||
814 | 251 | |||
815 | 252 | result = GTKTestResult() | ||
816 | 253 | test = GIOProtocolTestCase(sys.stdin, result, result.stopTestRun) | ||
831 | 254 | gtk.main() | 235 | gtk.main() |
833 | 255 | if result.wasSuccessful(): | 236 | if result.decorated.wasSuccessful(): |
834 | 256 | exit_code = 0 | 237 | exit_code = 0 |
835 | 257 | else: | 238 | else: |
836 | 258 | exit_code = 1 | 239 | exit_code = 1 |
837 | 259 | 240 | ||
838 | === modified file 'filters/subunit2junitxml' | |||
839 | --- filters/subunit2junitxml 2012-03-27 10:57:51 +0000 | |||
840 | +++ filters/subunit2junitxml 2013-03-31 05:51:20 +0000 | |||
841 | @@ -18,6 +18,9 @@ | |||
842 | 18 | 18 | ||
843 | 19 | 19 | ||
844 | 20 | import sys | 20 | import sys |
845 | 21 | |||
846 | 22 | from testtools import StreamToExtendedDecorator | ||
847 | 23 | |||
848 | 21 | from subunit.filters import run_filter_script | 24 | from subunit.filters import run_filter_script |
849 | 22 | 25 | ||
850 | 23 | try: | 26 | try: |
851 | @@ -28,4 +31,6 @@ | |||
852 | 28 | raise | 31 | raise |
853 | 29 | 32 | ||
854 | 30 | 33 | ||
856 | 31 | run_filter_script(JUnitXmlResult, __doc__) | 34 | run_filter_script( |
857 | 35 | lambda output:StreamToExtendedDecorator(JUnitXmlResult(output)), __doc__, | ||
858 | 36 | protocol_version=2) | ||
859 | 32 | 37 | ||
860 | === modified file 'filters/subunit2pyunit' | |||
861 | --- filters/subunit2pyunit 2009-09-30 12:04:18 +0000 | |||
862 | +++ filters/subunit2pyunit 2013-03-31 05:51:20 +0000 | |||
863 | @@ -16,11 +16,15 @@ | |||
864 | 16 | 16 | ||
865 | 17 | """Display a subunit stream through python's unittest test runner.""" | 17 | """Display a subunit stream through python's unittest test runner.""" |
866 | 18 | 18 | ||
867 | 19 | from operator import methodcaller | ||
868 | 19 | from optparse import OptionParser | 20 | from optparse import OptionParser |
869 | 20 | import sys | 21 | import sys |
870 | 21 | import unittest | 22 | import unittest |
871 | 22 | 23 | ||
873 | 23 | from subunit import DiscardStream, ProtocolTestCase, TestProtocolServer | 24 | from testtools import StreamToExtendedDecorator, DecorateTestCaseResult, StreamResultRouter |
874 | 25 | |||
875 | 26 | from subunit import ByteStreamToStreamResult | ||
876 | 27 | from subunit.test_results import CatFiles | ||
877 | 24 | 28 | ||
878 | 25 | parser = OptionParser(description=__doc__) | 29 | parser = OptionParser(description=__doc__) |
879 | 26 | parser.add_option("--no-passthrough", action="store_true", | 30 | parser.add_option("--no-passthrough", action="store_true", |
880 | @@ -29,11 +33,16 @@ | |||
881 | 29 | help="Use bzrlib's test reporter (requires bzrlib)", | 33 | help="Use bzrlib's test reporter (requires bzrlib)", |
882 | 30 | default=False) | 34 | default=False) |
883 | 31 | (options, args) = parser.parse_args() | 35 | (options, args) = parser.parse_args() |
889 | 32 | if options.no_passthrough: | 36 | test = ByteStreamToStreamResult(sys.stdin, non_subunit_name='stdout') |
890 | 33 | passthrough_stream = DiscardStream() | 37 | def wrap_result(result): |
891 | 34 | else: | 38 | result = StreamToExtendedDecorator(result) |
892 | 35 | passthrough_stream = None | 39 | if not options.no_passthrough: |
893 | 36 | test = ProtocolTestCase(sys.stdin, passthrough=passthrough_stream) | 40 | result = StreamResultRouter(result) |
894 | 41 | result.map(CatFiles(sys.stdout), 'test_id', test_id=None) | ||
895 | 42 | return result | ||
896 | 43 | test = DecorateTestCaseResult(test, wrap_result, | ||
897 | 44 | before_run=methodcaller('startTestRun'), | ||
898 | 45 | after_run=methodcaller('stopTestRun')) | ||
899 | 37 | if options.progress: | 46 | if options.progress: |
900 | 38 | from bzrlib.tests import TextTestRunner | 47 | from bzrlib.tests import TextTestRunner |
901 | 39 | from bzrlib import ui | 48 | from bzrlib import ui |
902 | 40 | 49 | ||
903 | === modified file 'python/subunit/__init__.py' | |||
904 | --- python/subunit/__init__.py 2013-02-07 11:33:11 +0000 | |||
905 | +++ python/subunit/__init__.py 2013-03-31 05:51:20 +0000 | |||
906 | @@ -126,7 +126,7 @@ | |||
907 | 126 | except ImportError: | 126 | except ImportError: |
908 | 127 | _UnsupportedOperation = AttributeError | 127 | _UnsupportedOperation = AttributeError |
909 | 128 | 128 | ||
911 | 129 | 129 | from extras import safe_hasattr | |
912 | 130 | from testtools import content, content_type, ExtendedToOriginalDecorator | 130 | from testtools import content, content_type, ExtendedToOriginalDecorator |
913 | 131 | from testtools.content import TracebackContent | 131 | from testtools.content import TracebackContent |
914 | 132 | from testtools.compat import _b, _u, BytesIO, StringIO | 132 | from testtools.compat import _b, _u, BytesIO, StringIO |
915 | @@ -143,9 +143,10 @@ | |||
916 | 143 | except ImportError: | 143 | except ImportError: |
917 | 144 | raise ImportError ("testtools.testresult.real does not contain " | 144 | raise ImportError ("testtools.testresult.real does not contain " |
918 | 145 | "_StringException, check your version.") | 145 | "_StringException, check your version.") |
920 | 146 | from testtools import testresult | 146 | from testtools import testresult, CopyStreamResult |
921 | 147 | 147 | ||
922 | 148 | from subunit import chunked, details, iso8601, test_results | 148 | from subunit import chunked, details, iso8601, test_results |
923 | 149 | from subunit.v2 import ByteStreamToStreamResult, StreamResultToBytes | ||
924 | 149 | 150 | ||
925 | 150 | # same format as sys.version_info: "A tuple containing the five components of | 151 | # same format as sys.version_info: "A tuple containing the five components of |
926 | 151 | # the version number: major, minor, micro, releaselevel, and serial. All | 152 | # the version number: major, minor, micro, releaselevel, and serial. All |
927 | @@ -992,44 +993,51 @@ | |||
928 | 992 | return result | 993 | return result |
929 | 993 | 994 | ||
930 | 994 | 995 | ||
932 | 995 | def TAP2SubUnit(tap, subunit): | 996 | def TAP2SubUnit(tap, output_stream): |
933 | 996 | """Filter a TAP pipe into a subunit pipe. | 997 | """Filter a TAP pipe into a subunit pipe. |
934 | 997 | 998 | ||
936 | 998 | :param tap: A tap pipe/stream/file object. | 999 | This should be invoked once per TAP script, as TAP scripts get |
937 | 1000 | mapped to a single runnable case with multiple components. | ||
938 | 1001 | |||
939 | 1002 | :param tap: A tap pipe/stream/file object - should emit unicode strings. | ||
940 | 999 | :param subunit: A pipe/stream/file object to write subunit results to. | 1003 | :param subunit: A pipe/stream/file object to write subunit results to. |
941 | 1000 | :return: The exit code to exit with. | 1004 | :return: The exit code to exit with. |
942 | 1001 | """ | 1005 | """ |
943 | 1006 | output = StreamResultToBytes(output_stream) | ||
944 | 1007 | UTF8_TEXT = 'text/plain; charset=UTF8' | ||
945 | 1002 | BEFORE_PLAN = 0 | 1008 | BEFORE_PLAN = 0 |
946 | 1003 | AFTER_PLAN = 1 | 1009 | AFTER_PLAN = 1 |
947 | 1004 | SKIP_STREAM = 2 | 1010 | SKIP_STREAM = 2 |
948 | 1005 | state = BEFORE_PLAN | 1011 | state = BEFORE_PLAN |
949 | 1006 | plan_start = 1 | 1012 | plan_start = 1 |
950 | 1007 | plan_stop = 0 | 1013 | plan_stop = 0 |
951 | 1008 | def _skipped_test(subunit, plan_start): | ||
952 | 1009 | # Some tests were skipped. | ||
953 | 1010 | subunit.write('test test %d\n' % plan_start) | ||
954 | 1011 | subunit.write('error test %d [\n' % plan_start) | ||
955 | 1012 | subunit.write('test missing from TAP output\n') | ||
956 | 1013 | subunit.write(']\n') | ||
957 | 1014 | return plan_start + 1 | ||
958 | 1015 | # Test data for the next test to emit | 1014 | # Test data for the next test to emit |
959 | 1016 | test_name = None | 1015 | test_name = None |
960 | 1017 | log = [] | 1016 | log = [] |
961 | 1018 | result = None | 1017 | result = None |
962 | 1018 | def missing_test(plan_start): | ||
963 | 1019 | output.status(test_id='test %d' % plan_start, | ||
964 | 1020 | test_status='fail', runnable=False, | ||
965 | 1021 | mime_type=UTF8_TEXT, eof=True, file_name="tap meta", | ||
966 | 1022 | file_bytes=b"test missing from TAP output") | ||
967 | 1019 | def _emit_test(): | 1023 | def _emit_test(): |
968 | 1020 | "write out a test" | 1024 | "write out a test" |
969 | 1021 | if test_name is None: | 1025 | if test_name is None: |
970 | 1022 | return | 1026 | return |
971 | 1023 | subunit.write("test %s\n" % test_name) | ||
972 | 1024 | if not log: | ||
973 | 1025 | subunit.write("%s %s\n" % (result, test_name)) | ||
974 | 1026 | else: | ||
975 | 1027 | subunit.write("%s %s [\n" % (result, test_name)) | ||
976 | 1028 | if log: | 1027 | if log: |
980 | 1029 | for line in log: | 1028 | log_bytes = b'\n'.join(log_line.encode('utf8') for log_line in log) |
981 | 1030 | subunit.write("%s\n" % line) | 1029 | mime_type = UTF8_TEXT |
982 | 1031 | subunit.write("]\n") | 1030 | file_name = 'tap comment' |
983 | 1031 | eof = True | ||
984 | 1032 | else: | ||
985 | 1033 | log_bytes = None | ||
986 | 1034 | mime_type = None | ||
987 | 1035 | file_name = None | ||
988 | 1036 | eof = True | ||
989 | 1032 | del log[:] | 1037 | del log[:] |
990 | 1038 | output.status(test_id=test_name, test_status=result, | ||
991 | 1039 | file_bytes=log_bytes, mime_type=mime_type, eof=eof, | ||
992 | 1040 | file_name=file_name, runnable=False) | ||
993 | 1033 | for line in tap: | 1041 | for line in tap: |
994 | 1034 | if state == BEFORE_PLAN: | 1042 | if state == BEFORE_PLAN: |
995 | 1035 | match = re.match("(\d+)\.\.(\d+)\s*(?:\#\s+(.*))?\n", line) | 1043 | match = re.match("(\d+)\.\.(\d+)\s*(?:\#\s+(.*))?\n", line) |
996 | @@ -1040,10 +1048,9 @@ | |||
997 | 1040 | if plan_start > plan_stop and plan_stop == 0: | 1048 | if plan_start > plan_stop and plan_stop == 0: |
998 | 1041 | # skipped file | 1049 | # skipped file |
999 | 1042 | state = SKIP_STREAM | 1050 | state = SKIP_STREAM |
1004 | 1043 | subunit.write("test file skip\n") | 1051 | output.status(test_id='file skip', test_status='skip', |
1005 | 1044 | subunit.write("skip file skip [\n") | 1052 | file_bytes=comment.encode('utf8'), eof=True, |
1006 | 1045 | subunit.write("%s\n" % comment) | 1053 | file_name='tap comment') |
1003 | 1046 | subunit.write("]\n") | ||
1007 | 1047 | continue | 1054 | continue |
1008 | 1048 | # not a plan line, or have seen one before | 1055 | # not a plan line, or have seen one before |
1009 | 1049 | match = re.match("(ok|not ok)(?:\s+(\d+)?)?(?:\s+([^#]*[^#\s]+)\s*)?(?:\s+#\s+(TODO|SKIP|skip|todo)(?:\s+(.*))?)?\n", line) | 1056 | match = re.match("(ok|not ok)(?:\s+(\d+)?)?(?:\s+([^#]*[^#\s]+)\s*)?(?:\s+#\s+(TODO|SKIP|skip|todo)(?:\s+(.*))?)?\n", line) |
1010 | @@ -1054,7 +1061,7 @@ | |||
1011 | 1054 | if status == 'ok': | 1061 | if status == 'ok': |
1012 | 1055 | result = 'success' | 1062 | result = 'success' |
1013 | 1056 | else: | 1063 | else: |
1015 | 1057 | result = "failure" | 1064 | result = "fail" |
1016 | 1058 | if description is None: | 1065 | if description is None: |
1017 | 1059 | description = '' | 1066 | description = '' |
1018 | 1060 | else: | 1067 | else: |
1019 | @@ -1069,7 +1076,8 @@ | |||
1020 | 1069 | if number is not None: | 1076 | if number is not None: |
1021 | 1070 | number = int(number) | 1077 | number = int(number) |
1022 | 1071 | while plan_start < number: | 1078 | while plan_start < number: |
1024 | 1072 | plan_start = _skipped_test(subunit, plan_start) | 1079 | missing_test(plan_start) |
1025 | 1080 | plan_start += 1 | ||
1026 | 1073 | test_name = "test %d%s" % (plan_start, description) | 1081 | test_name = "test %d%s" % (plan_start, description) |
1027 | 1074 | plan_start += 1 | 1082 | plan_start += 1 |
1028 | 1075 | continue | 1083 | continue |
1029 | @@ -1082,18 +1090,21 @@ | |||
1030 | 1082 | extra = ' %s' % reason | 1090 | extra = ' %s' % reason |
1031 | 1083 | _emit_test() | 1091 | _emit_test() |
1032 | 1084 | test_name = "Bail out!%s" % extra | 1092 | test_name = "Bail out!%s" % extra |
1034 | 1085 | result = "error" | 1093 | result = "fail" |
1035 | 1086 | state = SKIP_STREAM | 1094 | state = SKIP_STREAM |
1036 | 1087 | continue | 1095 | continue |
1037 | 1088 | match = re.match("\#.*\n", line) | 1096 | match = re.match("\#.*\n", line) |
1038 | 1089 | if match: | 1097 | if match: |
1039 | 1090 | log.append(line[:-1]) | 1098 | log.append(line[:-1]) |
1040 | 1091 | continue | 1099 | continue |
1042 | 1092 | subunit.write(line) | 1100 | # Should look at buffering status and binding this to the prior result. |
1043 | 1101 | output.status(file_bytes=line.encode('utf8'), file_name='stdout', | ||
1044 | 1102 | mime_type=UTF8_TEXT) | ||
1045 | 1093 | _emit_test() | 1103 | _emit_test() |
1046 | 1094 | while plan_start <= plan_stop: | 1104 | while plan_start <= plan_stop: |
1047 | 1095 | # record missed tests | 1105 | # record missed tests |
1049 | 1096 | plan_start = _skipped_test(subunit, plan_start) | 1106 | missing_test(plan_start) |
1050 | 1107 | plan_start += 1 | ||
1051 | 1097 | return 0 | 1108 | return 0 |
1052 | 1098 | 1109 | ||
1053 | 1099 | 1110 | ||
1054 | @@ -1121,24 +1132,21 @@ | |||
1055 | 1121 | :return: 0 | 1132 | :return: 0 |
1056 | 1122 | """ | 1133 | """ |
1057 | 1123 | new_tags, gone_tags = tags_to_new_gone(tags) | 1134 | new_tags, gone_tags = tags_to_new_gone(tags) |
1076 | 1124 | def write_tags(new_tags, gone_tags): | 1135 | source = ByteStreamToStreamResult(original, non_subunit_name='stdout') |
1077 | 1125 | if new_tags or gone_tags: | 1136 | class Tagger(CopyStreamResult): |
1078 | 1126 | filtered.write("tags: " + ' '.join(new_tags)) | 1137 | def status(self, **kwargs): |
1079 | 1127 | if gone_tags: | 1138 | tags = kwargs.get('test_tags') |
1080 | 1128 | for tag in gone_tags: | 1139 | if not tags: |
1081 | 1129 | filtered.write("-" + tag) | 1140 | tags = set() |
1082 | 1130 | filtered.write("\n") | 1141 | tags.update(new_tags) |
1083 | 1131 | write_tags(new_tags, gone_tags) | 1142 | tags.difference_update(gone_tags) |
1084 | 1132 | # TODO: use the protocol parser and thus don't mangle test comments. | 1143 | if tags: |
1085 | 1133 | for line in original: | 1144 | kwargs['test_tags'] = tags |
1086 | 1134 | if line.startswith("tags:"): | 1145 | else: |
1087 | 1135 | line_tags = line[5:].split() | 1146 | kwargs['test_tags'] = None |
1088 | 1136 | line_new, line_gone = tags_to_new_gone(line_tags) | 1147 | super(Tagger, self).status(**kwargs) |
1089 | 1137 | line_new = line_new - gone_tags | 1148 | output = Tagger([StreamResultToBytes(filtered)]) |
1090 | 1138 | line_gone = line_gone - new_tags | 1149 | source.run(output) |
1073 | 1139 | write_tags(line_new, line_gone) | ||
1074 | 1140 | else: | ||
1075 | 1141 | filtered.write(line) | ||
1091 | 1142 | return 0 | 1150 | return 0 |
1092 | 1143 | 1151 | ||
1093 | 1144 | 1152 | ||
1094 | @@ -1260,7 +1268,8 @@ | |||
1095 | 1260 | else: | 1268 | else: |
1096 | 1261 | stream = sys.stdout | 1269 | stream = sys.stdout |
1097 | 1262 | if sys.version_info > (3, 0): | 1270 | if sys.version_info > (3, 0): |
1099 | 1263 | stream = stream.buffer | 1271 | if safe_hasattr(stream, 'buffer'): |
1100 | 1272 | stream = stream.buffer | ||
1101 | 1264 | return stream | 1273 | return stream |
1102 | 1265 | 1274 | ||
1103 | 1266 | 1275 | ||
1104 | @@ -1291,6 +1300,7 @@ | |||
1105 | 1291 | _make_binary_on_windows(fileno) | 1300 | _make_binary_on_windows(fileno) |
1106 | 1292 | return _unwrap_text(stream) | 1301 | return _unwrap_text(stream) |
1107 | 1293 | 1302 | ||
1108 | 1303 | |||
1109 | 1294 | def _make_binary_on_windows(fileno): | 1304 | def _make_binary_on_windows(fileno): |
1110 | 1295 | """Win32 mangles \r\n to \n and that breaks streams. See bug lp:505078.""" | 1305 | """Win32 mangles \r\n to \n and that breaks streams. See bug lp:505078.""" |
1111 | 1296 | if sys.platform == "win32": | 1306 | if sys.platform == "win32": |
1112 | 1297 | 1307 | ||
1113 | === modified file 'python/subunit/filters.py' | |||
1114 | --- python/subunit/filters.py 2012-03-27 11:17:37 +0000 | |||
1115 | +++ python/subunit/filters.py 2013-03-31 05:51:20 +0000 | |||
1116 | @@ -17,7 +17,14 @@ | |||
1117 | 17 | from optparse import OptionParser | 17 | from optparse import OptionParser |
1118 | 18 | import sys | 18 | import sys |
1119 | 19 | 19 | ||
1121 | 20 | from subunit import DiscardStream, ProtocolTestCase | 20 | from extras import safe_hasattr |
1122 | 21 | from testtools import CopyStreamResult, StreamResult, StreamResultRouter | ||
1123 | 22 | |||
1124 | 23 | from subunit import ( | ||
1125 | 24 | DiscardStream, ProtocolTestCase, ByteStreamToStreamResult, | ||
1126 | 25 | StreamResultToBytes, | ||
1127 | 26 | ) | ||
1128 | 27 | from subunit.test_results import CatFiles | ||
1129 | 21 | 28 | ||
1130 | 22 | 29 | ||
1131 | 23 | def make_options(description): | 30 | def make_options(description): |
1132 | @@ -31,33 +38,75 @@ | |||
1133 | 31 | help="Send the output to this path rather than stdout.") | 38 | help="Send the output to this path rather than stdout.") |
1134 | 32 | parser.add_option( | 39 | parser.add_option( |
1135 | 33 | "-f", "--forward", action="store_true", default=False, | 40 | "-f", "--forward", action="store_true", default=False, |
1137 | 34 | help="Forward subunit stream on stdout.") | 41 | help="Forward subunit stream on stdout. When set, received " |
1138 | 42 | "non-subunit output will be encapsulated in subunit.") | ||
1139 | 35 | return parser | 43 | return parser |
1140 | 36 | 44 | ||
1141 | 37 | 45 | ||
1142 | 38 | def run_tests_from_stream(input_stream, result, passthrough_stream=None, | 46 | def run_tests_from_stream(input_stream, result, passthrough_stream=None, |
1144 | 39 | forward_stream=None): | 47 | forward_stream=None, protocol_version=1, passthrough_subunit=True): |
1145 | 40 | """Run tests from a subunit input stream through 'result'. | 48 | """Run tests from a subunit input stream through 'result'. |
1146 | 41 | 49 | ||
1147 | 50 | Non-test events - top level file attachments - are expected to be | ||
1148 | 51 | dropped by v2 StreamResults at the present time (as all the analysis code | ||
1149 | 52 | is in ExtendedTestResult API's), so to implement passthrough_stream they | ||
1150 | 53 | are diverted and copied directly when that is set. | ||
1151 | 54 | |||
1152 | 42 | :param input_stream: A stream containing subunit input. | 55 | :param input_stream: A stream containing subunit input. |
1153 | 43 | :param result: A TestResult that will receive the test events. | 56 | :param result: A TestResult that will receive the test events. |
1154 | 57 | NB: This should be an ExtendedTestResult for v1 and a StreamResult for | ||
1155 | 58 | v2. | ||
1156 | 44 | :param passthrough_stream: All non-subunit input received will be | 59 | :param passthrough_stream: All non-subunit input received will be |
1157 | 45 | sent to this stream. If not provided, uses the ``TestProtocolServer`` | 60 | sent to this stream. If not provided, uses the ``TestProtocolServer`` |
1158 | 46 | default, which is ``sys.stdout``. | 61 | default, which is ``sys.stdout``. |
1159 | 47 | :param forward_stream: All subunit input received will be forwarded | 62 | :param forward_stream: All subunit input received will be forwarded |
1162 | 48 | to this stream. If not provided, uses the ``TestProtocolServer`` | 63 | to this stream. If not provided, uses the ``TestProtocolServer`` |
1163 | 49 | default, which is to not forward any input. | 64 | default, which is to not forward any input. Do not set this when |
1164 | 65 | transforming the stream - items would be double-reported. | ||
1165 | 66 | :param protocol_version: What version of the subunit protocol to expect. | ||
1166 | 67 | :param passthrough_subunit: If True, passthrough should be as subunit | ||
1167 | 68 | otherwise unwrap it. Only has effect when forward_stream is None. | ||
1168 | 69 | (when forwarding as subunit non-subunit input is always turned into | ||
1169 | 70 | subunit) | ||
1170 | 50 | """ | 71 | """ |
1174 | 51 | test = ProtocolTestCase( | 72 | if 1==protocol_version: |
1175 | 52 | input_stream, passthrough=passthrough_stream, | 73 | test = ProtocolTestCase( |
1176 | 53 | forward=forward_stream) | 74 | input_stream, passthrough=passthrough_stream, |
1177 | 75 | forward=forward_stream) | ||
1178 | 76 | elif 2==protocol_version: | ||
1179 | 77 | # In all cases we encapsulate unknown inputs. | ||
1180 | 78 | if forward_stream is not None: | ||
1181 | 79 | # Send events to forward_stream as subunit. | ||
1182 | 80 | forward_result = StreamResultToBytes(forward_stream) | ||
1183 | 81 | # If we're passing non-subunit through, copy: | ||
1184 | 82 | if passthrough_stream is None: | ||
1185 | 83 | # Not passing non-test events - split them off to nothing. | ||
1186 | 84 | router = StreamResultRouter(forward_result) | ||
1187 | 85 | router.map(StreamResult(), 'test_id', test_id=None) | ||
1188 | 86 | result = CopyStreamResult([router, result]) | ||
1189 | 87 | else: | ||
1190 | 88 | # otherwise, copy all events to forward_result | ||
1191 | 89 | result = CopyStreamResult([forward_result, result]) | ||
1192 | 90 | elif passthrough_stream is not None: | ||
1193 | 91 | if not passthrough_subunit: | ||
1194 | 92 | # Route non-test events to passthrough_stream, unwrapping them for | ||
1195 | 93 | # display. | ||
1196 | 94 | passthrough_result = CatFiles(passthrough_stream) | ||
1197 | 95 | else: | ||
1198 | 96 | passthrough_result = StreamResultToBytes(passthrough_stream) | ||
1199 | 97 | result = StreamResultRouter(result) | ||
1200 | 98 | result.map(passthrough_result, 'test_id', test_id=None) | ||
1201 | 99 | test = ByteStreamToStreamResult(input_stream, | ||
1202 | 100 | non_subunit_name='stdout') | ||
1203 | 101 | else: | ||
1204 | 102 | raise Exception("Unknown protocol version.") | ||
1205 | 54 | result.startTestRun() | 103 | result.startTestRun() |
1206 | 55 | test.run(result) | 104 | test.run(result) |
1207 | 56 | result.stopTestRun() | 105 | result.stopTestRun() |
1208 | 57 | 106 | ||
1209 | 58 | 107 | ||
1210 | 59 | def filter_by_result(result_factory, output_path, passthrough, forward, | 108 | def filter_by_result(result_factory, output_path, passthrough, forward, |
1212 | 60 | input_stream=sys.stdin): | 109 | input_stream=sys.stdin, protocol_version=1): |
1213 | 61 | """Filter an input stream using a test result. | 110 | """Filter an input stream using a test result. |
1214 | 62 | 111 | ||
1215 | 63 | :param result_factory: A callable that when passed an output stream | 112 | :param result_factory: A callable that when passed an output stream |
1216 | @@ -71,17 +120,23 @@ | |||
1217 | 71 | ``sys.stdout`` as well as to the ``TestResult``. | 120 | ``sys.stdout`` as well as to the ``TestResult``. |
1218 | 72 | :param input_stream: The source of subunit input. Defaults to | 121 | :param input_stream: The source of subunit input. Defaults to |
1219 | 73 | ``sys.stdin``. | 122 | ``sys.stdin``. |
1221 | 74 | :return: A test result with the resultts of the run. | 123 | :param protocol_version: The subunit protocol version to expect. |
1222 | 124 | :return: A test result with the results of the run. | ||
1223 | 75 | """ | 125 | """ |
1224 | 76 | if passthrough: | 126 | if passthrough: |
1225 | 77 | passthrough_stream = sys.stdout | 127 | passthrough_stream = sys.stdout |
1226 | 78 | else: | 128 | else: |
1228 | 79 | passthrough_stream = DiscardStream() | 129 | if 1==protocol_version: |
1229 | 130 | passthrough_stream = DiscardStream() | ||
1230 | 131 | else: | ||
1231 | 132 | passthrough_stream = None | ||
1232 | 80 | 133 | ||
1233 | 81 | if forward: | 134 | if forward: |
1234 | 82 | forward_stream = sys.stdout | 135 | forward_stream = sys.stdout |
1236 | 83 | else: | 136 | elif 1==protocol_version: |
1237 | 84 | forward_stream = DiscardStream() | 137 | forward_stream = DiscardStream() |
1238 | 138 | else: | ||
1239 | 139 | forward_stream = None | ||
1240 | 85 | 140 | ||
1241 | 86 | if output_path is None: | 141 | if output_path is None: |
1242 | 87 | output_to = sys.stdout | 142 | output_to = sys.stdout |
1243 | @@ -91,14 +146,16 @@ | |||
1244 | 91 | try: | 146 | try: |
1245 | 92 | result = result_factory(output_to) | 147 | result = result_factory(output_to) |
1246 | 93 | run_tests_from_stream( | 148 | run_tests_from_stream( |
1248 | 94 | input_stream, result, passthrough_stream, forward_stream) | 149 | input_stream, result, passthrough_stream, forward_stream, |
1249 | 150 | protocol_version=protocol_version) | ||
1250 | 95 | finally: | 151 | finally: |
1251 | 96 | if output_path: | 152 | if output_path: |
1252 | 97 | output_to.close() | 153 | output_to.close() |
1253 | 98 | return result | 154 | return result |
1254 | 99 | 155 | ||
1255 | 100 | 156 | ||
1257 | 101 | def run_filter_script(result_factory, description, post_run_hook=None): | 157 | def run_filter_script(result_factory, description, post_run_hook=None, |
1258 | 158 | protocol_version=1): | ||
1259 | 102 | """Main function for simple subunit filter scripts. | 159 | """Main function for simple subunit filter scripts. |
1260 | 103 | 160 | ||
1261 | 104 | Many subunit filter scripts take a stream of subunit input and use a | 161 | Many subunit filter scripts take a stream of subunit input and use a |
1262 | @@ -111,14 +168,17 @@ | |||
1263 | 111 | :param result_factory: A callable that takes an output stream and returns | 168 | :param result_factory: A callable that takes an output stream and returns |
1264 | 112 | a test result that outputs to that stream. | 169 | a test result that outputs to that stream. |
1265 | 113 | :param description: A description of the filter script. | 170 | :param description: A description of the filter script. |
1266 | 171 | :param protocol_version: What protocol version to consume/emit. | ||
1267 | 114 | """ | 172 | """ |
1268 | 115 | parser = make_options(description) | 173 | parser = make_options(description) |
1269 | 116 | (options, args) = parser.parse_args() | 174 | (options, args) = parser.parse_args() |
1270 | 117 | result = filter_by_result( | 175 | result = filter_by_result( |
1271 | 118 | result_factory, options.output_to, not options.no_passthrough, | 176 | result_factory, options.output_to, not options.no_passthrough, |
1273 | 119 | options.forward) | 177 | options.forward, protocol_version=protocol_version) |
1274 | 120 | if post_run_hook: | 178 | if post_run_hook: |
1275 | 121 | post_run_hook(result) | 179 | post_run_hook(result) |
1276 | 180 | if not safe_hasattr(result, 'wasSuccessful'): | ||
1277 | 181 | result = result.decorated | ||
1278 | 122 | if result.wasSuccessful(): | 182 | if result.wasSuccessful(): |
1279 | 123 | sys.exit(0) | 183 | sys.exit(0) |
1280 | 124 | else: | 184 | else: |
1281 | 125 | 185 | ||
1282 | === modified file 'python/subunit/run.py' | |||
1283 | --- python/subunit/run.py 2012-12-17 07:24:28 +0000 | |||
1284 | +++ python/subunit/run.py 2013-03-31 05:51:20 +0000 | |||
1285 | @@ -20,9 +20,14 @@ | |||
1286 | 20 | $ python -m subunit.run mylib.tests.test_suite | 20 | $ python -m subunit.run mylib.tests.test_suite |
1287 | 21 | """ | 21 | """ |
1288 | 22 | 22 | ||
1289 | 23 | import io | ||
1290 | 24 | import os | ||
1291 | 23 | import sys | 25 | import sys |
1292 | 24 | 26 | ||
1294 | 25 | from subunit import TestProtocolClient, get_default_formatter | 27 | from testtools import ExtendedToStreamDecorator |
1295 | 28 | from testtools.testsuite import iterate_tests | ||
1296 | 29 | |||
1297 | 30 | from subunit import StreamResultToBytes, get_default_formatter | ||
1298 | 26 | from subunit.test_results import AutoTimingTestResultDecorator | 31 | from subunit.test_results import AutoTimingTestResultDecorator |
1299 | 27 | from testtools.run import ( | 32 | from testtools.run import ( |
1300 | 28 | BUFFEROUTPUT, | 33 | BUFFEROUTPUT, |
1301 | @@ -46,11 +51,34 @@ | |||
1302 | 46 | 51 | ||
1303 | 47 | def run(self, test): | 52 | def run(self, test): |
1304 | 48 | "Run the given test case or test suite." | 53 | "Run the given test case or test suite." |
1306 | 49 | result = TestProtocolClient(self.stream) | 54 | result = self._list(test) |
1307 | 55 | result = ExtendedToStreamDecorator(result) | ||
1308 | 50 | result = AutoTimingTestResultDecorator(result) | 56 | result = AutoTimingTestResultDecorator(result) |
1309 | 51 | if self.failfast is not None: | 57 | if self.failfast is not None: |
1310 | 52 | result.failfast = self.failfast | 58 | result.failfast = self.failfast |
1312 | 53 | test(result) | 59 | result.startTestRun() |
1313 | 60 | try: | ||
1314 | 61 | test(result) | ||
1315 | 62 | finally: | ||
1316 | 63 | result.stopTestRun() | ||
1317 | 64 | return result | ||
1318 | 65 | |||
1319 | 66 | def list(self, test): | ||
1320 | 67 | "List the test." | ||
1321 | 68 | self._list(test) | ||
1322 | 69 | |||
1323 | 70 | def _list(self, test): | ||
1324 | 71 | try: | ||
1325 | 72 | fileno = self.stream.fileno() | ||
1326 | 73 | except: | ||
1327 | 74 | fileno = None | ||
1328 | 75 | if fileno is not None: | ||
1329 | 76 | stream = os.fdopen(fileno, 'wb', 0) | ||
1330 | 77 | else: | ||
1331 | 78 | stream = self.stream | ||
1332 | 79 | result = StreamResultToBytes(stream) | ||
1333 | 80 | for case in iterate_tests(test): | ||
1334 | 81 | result.status(test_id=case.id(), test_status='exists') | ||
1335 | 54 | return result | 82 | return result |
1336 | 55 | 83 | ||
1337 | 56 | 84 | ||
1338 | @@ -78,7 +106,15 @@ | |||
1339 | 78 | 106 | ||
1340 | 79 | 107 | ||
1341 | 80 | if __name__ == '__main__': | 108 | if __name__ == '__main__': |
1342 | 109 | # Disable the default buffering, for Python 2.x where pdb doesn't do it | ||
1343 | 110 | # on non-ttys. | ||
1344 | 81 | stream = get_default_formatter() | 111 | stream = get_default_formatter() |
1345 | 82 | runner = SubunitTestRunner | 112 | runner = SubunitTestRunner |
1346 | 113 | # Patch stdout to be unbuffered, so that pdb works well on 2.6/2.7. | ||
1347 | 114 | binstdout = io.open(sys.stdout.fileno(), 'wb', 0) | ||
1348 | 115 | if sys.version_info[0] > 2: | ||
1349 | 116 | sys.stdout = io.TextIOWrapper(binstdout, encoding=sys.stdout.encoding) | ||
1350 | 117 | else: | ||
1351 | 118 | sys.stdout = binstdout | ||
1352 | 83 | SubunitTestProgram(module=None, argv=sys.argv, testRunner=runner, | 119 | SubunitTestProgram(module=None, argv=sys.argv, testRunner=runner, |
1353 | 84 | stdout=sys.stdout) | 120 | stdout=sys.stdout) |
1354 | 85 | 121 | ||
1355 | === modified file 'python/subunit/test_results.py' | |||
1356 | --- python/subunit/test_results.py 2012-12-17 07:24:28 +0000 | |||
1357 | +++ python/subunit/test_results.py 2013-03-31 05:51:20 +0000 | |||
1358 | @@ -25,8 +25,10 @@ | |||
1359 | 25 | text_content, | 25 | text_content, |
1360 | 26 | TracebackContent, | 26 | TracebackContent, |
1361 | 27 | ) | 27 | ) |
1362 | 28 | from testtools import StreamResult | ||
1363 | 28 | 29 | ||
1364 | 29 | from subunit import iso8601 | 30 | from subunit import iso8601 |
1365 | 31 | import subunit | ||
1366 | 30 | 32 | ||
1367 | 31 | 33 | ||
1368 | 32 | # NOT a TestResult, because we are implementing the interface, not inheriting | 34 | # NOT a TestResult, because we are implementing the interface, not inheriting |
1369 | @@ -525,16 +527,24 @@ | |||
1370 | 525 | 527 | ||
1371 | 526 | 528 | ||
1372 | 527 | class TestIdPrintingResult(testtools.TestResult): | 529 | class TestIdPrintingResult(testtools.TestResult): |
1375 | 528 | 530 | """Print test ids to a stream. | |
1376 | 529 | def __init__(self, stream, show_times=False): | 531 | |
1377 | 532 | Implements both TestResult and StreamResult, for compatibility. | ||
1378 | 533 | """ | ||
1379 | 534 | |||
1380 | 535 | def __init__(self, stream, show_times=False, show_exists=False): | ||
1381 | 530 | """Create a FilterResult object outputting to stream.""" | 536 | """Create a FilterResult object outputting to stream.""" |
1382 | 531 | super(TestIdPrintingResult, self).__init__() | 537 | super(TestIdPrintingResult, self).__init__() |
1383 | 532 | self._stream = stream | 538 | self._stream = stream |
1384 | 539 | self.show_exists = show_exists | ||
1385 | 540 | self.show_times = show_times | ||
1386 | 541 | |||
1387 | 542 | def startTestRun(self): | ||
1388 | 533 | self.failed_tests = 0 | 543 | self.failed_tests = 0 |
1389 | 534 | self.__time = None | 544 | self.__time = None |
1390 | 535 | self.show_times = show_times | ||
1391 | 536 | self._test = None | 545 | self._test = None |
1392 | 537 | self._test_duration = 0 | 546 | self._test_duration = 0 |
1393 | 547 | self._active_tests = {} | ||
1394 | 538 | 548 | ||
1395 | 539 | def addError(self, test, err): | 549 | def addError(self, test, err): |
1396 | 540 | self.failed_tests += 1 | 550 | self.failed_tests += 1 |
1397 | @@ -557,21 +567,44 @@ | |||
1398 | 557 | def addExpectedFailure(self, test, err=None, details=None): | 567 | def addExpectedFailure(self, test, err=None, details=None): |
1399 | 558 | self._test = test | 568 | self._test = test |
1400 | 559 | 569 | ||
1402 | 560 | def reportTest(self, test, duration): | 570 | def reportTest(self, test_id, duration): |
1403 | 561 | if self.show_times: | 571 | if self.show_times: |
1404 | 562 | seconds = duration.seconds | 572 | seconds = duration.seconds |
1405 | 563 | seconds += duration.days * 3600 * 24 | 573 | seconds += duration.days * 3600 * 24 |
1406 | 564 | seconds += duration.microseconds / 1000000.0 | 574 | seconds += duration.microseconds / 1000000.0 |
1408 | 565 | self._stream.write(test.id() + ' %0.3f\n' % seconds) | 575 | self._stream.write(test_id + ' %0.3f\n' % seconds) |
1409 | 566 | else: | 576 | else: |
1411 | 567 | self._stream.write(test.id() + '\n') | 577 | self._stream.write(test_id + '\n') |
1412 | 568 | 578 | ||
1413 | 569 | def startTest(self, test): | 579 | def startTest(self, test): |
1414 | 570 | self._start_time = self._time() | 580 | self._start_time = self._time() |
1415 | 571 | 581 | ||
1416 | 582 | def status(self, test_id=None, test_status=None, test_tags=None, | ||
1417 | 583 | runnable=True, file_name=None, file_bytes=None, eof=False, | ||
1418 | 584 | mime_type=None, route_code=None, timestamp=None): | ||
1419 | 585 | if not test_id: | ||
1420 | 586 | return | ||
1421 | 587 | if timestamp is not None: | ||
1422 | 588 | self.time(timestamp) | ||
1423 | 589 | if test_status=='exists': | ||
1424 | 590 | if self.show_exists: | ||
1425 | 591 | self.reportTest(test_id, 0) | ||
1426 | 592 | elif test_status in ('inprogress', None): | ||
1427 | 593 | self._active_tests[test_id] = self._time() | ||
1428 | 594 | else: | ||
1429 | 595 | self._end_test(test_id) | ||
1430 | 596 | |||
1431 | 597 | def _end_test(self, test_id): | ||
1432 | 598 | test_start = self._active_tests.pop(test_id, None) | ||
1433 | 599 | if not test_start: | ||
1434 | 600 | test_duration = 0 | ||
1435 | 601 | else: | ||
1436 | 602 | test_duration = self._time() - test_start | ||
1437 | 603 | self.reportTest(test_id, test_duration) | ||
1438 | 604 | |||
1439 | 572 | def stopTest(self, test): | 605 | def stopTest(self, test): |
1440 | 573 | test_duration = self._time() - self._start_time | 606 | test_duration = self._time() - self._start_time |
1442 | 574 | self.reportTest(self._test, test_duration) | 607 | self.reportTest(self._test.id(), test_duration) |
1443 | 575 | 608 | ||
1444 | 576 | def time(self, time): | 609 | def time(self, time): |
1445 | 577 | self.__time = time | 610 | self.__time = time |
1446 | @@ -583,6 +616,10 @@ | |||
1447 | 583 | "Tells whether or not this result was a success" | 616 | "Tells whether or not this result was a success" |
1448 | 584 | return self.failed_tests == 0 | 617 | return self.failed_tests == 0 |
1449 | 585 | 618 | ||
1450 | 619 | def stopTestRun(self): | ||
1451 | 620 | for test_id in list(self._active_tests.keys()): | ||
1452 | 621 | self._end_test(test_id) | ||
1453 | 622 | |||
1454 | 586 | 623 | ||
1455 | 587 | class TestByTestResult(testtools.TestResult): | 624 | class TestByTestResult(testtools.TestResult): |
1456 | 588 | """Call something every time a test completes.""" | 625 | """Call something every time a test completes.""" |
1457 | @@ -676,3 +713,17 @@ | |||
1458 | 676 | def startTestRun(self): | 713 | def startTestRun(self): |
1459 | 677 | super(CsvResult, self).startTestRun() | 714 | super(CsvResult, self).startTestRun() |
1460 | 678 | self._write_row(['test', 'status', 'start_time', 'stop_time']) | 715 | self._write_row(['test', 'status', 'start_time', 'stop_time']) |
1461 | 716 | |||
1462 | 717 | |||
1463 | 718 | class CatFiles(StreamResult): | ||
1464 | 719 | """Cat file attachments received to a stream.""" | ||
1465 | 720 | |||
1466 | 721 | def __init__(self, byte_stream): | ||
1467 | 722 | self.stream = subunit.make_stream_binary(byte_stream) | ||
1468 | 723 | |||
1469 | 724 | def status(self, test_id=None, test_status=None, test_tags=None, | ||
1470 | 725 | runnable=True, file_name=None, file_bytes=None, eof=False, | ||
1471 | 726 | mime_type=None, route_code=None, timestamp=None): | ||
1472 | 727 | if file_name is not None: | ||
1473 | 728 | self.stream.write(file_bytes) | ||
1474 | 729 | self.stream.flush() | ||
1475 | 679 | 730 | ||
1476 | === modified file 'python/subunit/tests/__init__.py' | |||
1477 | --- python/subunit/tests/__init__.py 2011-11-01 15:59:58 +0000 | |||
1478 | +++ python/subunit/tests/__init__.py 2013-03-31 05:51:20 +0000 | |||
1479 | @@ -25,6 +25,7 @@ | |||
1480 | 25 | test_subunit_tags, | 25 | test_subunit_tags, |
1481 | 26 | test_tap2subunit, | 26 | test_tap2subunit, |
1482 | 27 | test_test_protocol, | 27 | test_test_protocol, |
1483 | 28 | test_test_protocol2, | ||
1484 | 28 | test_test_results, | 29 | test_test_results, |
1485 | 29 | ) | 30 | ) |
1486 | 30 | 31 | ||
1487 | @@ -35,6 +36,7 @@ | |||
1488 | 35 | result.addTest(test_progress_model.test_suite()) | 36 | result.addTest(test_progress_model.test_suite()) |
1489 | 36 | result.addTest(test_test_results.test_suite()) | 37 | result.addTest(test_test_results.test_suite()) |
1490 | 37 | result.addTest(test_test_protocol.test_suite()) | 38 | result.addTest(test_test_protocol.test_suite()) |
1491 | 39 | result.addTest(test_test_protocol2.test_suite()) | ||
1492 | 38 | result.addTest(test_tap2subunit.test_suite()) | 40 | result.addTest(test_tap2subunit.test_suite()) |
1493 | 39 | result.addTest(test_subunit_filter.test_suite()) | 41 | result.addTest(test_subunit_filter.test_suite()) |
1494 | 40 | result.addTest(test_subunit_tags.test_suite()) | 42 | result.addTest(test_subunit_tags.test_suite()) |
1495 | 41 | 43 | ||
1496 | === modified file 'python/subunit/tests/test_run.py' | |||
1497 | --- python/subunit/tests/test_run.py 2012-05-07 19:36:05 +0000 | |||
1498 | +++ python/subunit/tests/test_run.py 2013-03-31 05:51:20 +0000 | |||
1499 | @@ -18,6 +18,7 @@ | |||
1500 | 18 | import unittest | 18 | import unittest |
1501 | 19 | 19 | ||
1502 | 20 | from testtools import PlaceHolder | 20 | from testtools import PlaceHolder |
1503 | 21 | from testtools.testresult.doubles import StreamResult | ||
1504 | 21 | 22 | ||
1505 | 22 | import subunit | 23 | import subunit |
1506 | 23 | from subunit.run import SubunitTestRunner | 24 | from subunit.run import SubunitTestRunner |
1507 | @@ -29,16 +30,6 @@ | |||
1508 | 29 | return result | 30 | return result |
1509 | 30 | 31 | ||
1510 | 31 | 32 | ||
1511 | 32 | class TimeCollectingTestResult(unittest.TestResult): | ||
1512 | 33 | |||
1513 | 34 | def __init__(self, *args, **kwargs): | ||
1514 | 35 | super(TimeCollectingTestResult, self).__init__(*args, **kwargs) | ||
1515 | 36 | self.time_called = [] | ||
1516 | 37 | |||
1517 | 38 | def time(self, a_time): | ||
1518 | 39 | self.time_called.append(a_time) | ||
1519 | 40 | |||
1520 | 41 | |||
1521 | 42 | class TestSubunitTestRunner(unittest.TestCase): | 33 | class TestSubunitTestRunner(unittest.TestCase): |
1522 | 43 | 34 | ||
1523 | 44 | def test_includes_timing_output(self): | 35 | def test_includes_timing_output(self): |
1524 | @@ -46,7 +37,24 @@ | |||
1525 | 46 | runner = SubunitTestRunner(stream=io) | 37 | runner = SubunitTestRunner(stream=io) |
1526 | 47 | test = PlaceHolder('name') | 38 | test = PlaceHolder('name') |
1527 | 48 | runner.run(test) | 39 | runner.run(test) |
1532 | 49 | client = TimeCollectingTestResult() | 40 | io.seek(0) |
1533 | 50 | io.seek(0) | 41 | eventstream = StreamResult() |
1534 | 51 | subunit.TestProtocolServer(client).readFrom(io) | 42 | subunit.ByteStreamToStreamResult(io).run(eventstream) |
1535 | 52 | self.assertTrue(len(client.time_called) > 0) | 43 | timestamps = [event[-1] for event in eventstream._events |
1536 | 44 | if event is not None] | ||
1537 | 45 | self.assertNotEqual([], timestamps) | ||
1538 | 46 | |||
1539 | 47 | def test_enumerates_tests_before_run(self): | ||
1540 | 48 | io = BytesIO() | ||
1541 | 49 | runner = SubunitTestRunner(stream=io) | ||
1542 | 50 | test1 = PlaceHolder('name1') | ||
1543 | 51 | test2 = PlaceHolder('name2') | ||
1544 | 52 | case = unittest.TestSuite([test1, test2]) | ||
1545 | 53 | runner.run(case) | ||
1546 | 54 | io.seek(0) | ||
1547 | 55 | eventstream = StreamResult() | ||
1548 | 56 | subunit.ByteStreamToStreamResult(io).run(eventstream) | ||
1549 | 57 | self.assertEqual([ | ||
1550 | 58 | ('status', 'name1', 'exists'), | ||
1551 | 59 | ('status', 'name2', 'exists'), | ||
1552 | 60 | ], [event[:3] for event in eventstream._events[:2]]) | ||
1553 | 53 | 61 | ||
1554 | === modified file 'python/subunit/tests/test_subunit_filter.py' | |||
1555 | --- python/subunit/tests/test_subunit_filter.py 2012-05-07 22:53:53 +0000 | |||
1556 | +++ python/subunit/tests/test_subunit_filter.py 2013-03-31 05:51:20 +0000 | |||
1557 | @@ -25,10 +25,11 @@ | |||
1558 | 25 | 25 | ||
1559 | 26 | from testtools import TestCase | 26 | from testtools import TestCase |
1560 | 27 | from testtools.compat import _b, BytesIO | 27 | from testtools.compat import _b, BytesIO |
1562 | 28 | from testtools.testresult.doubles import ExtendedTestResult | 28 | from testtools.testresult.doubles import ExtendedTestResult, StreamResult |
1563 | 29 | 29 | ||
1564 | 30 | import subunit | 30 | import subunit |
1565 | 31 | from subunit.test_results import make_tag_filter, TestResultFilter | 31 | from subunit.test_results import make_tag_filter, TestResultFilter |
1566 | 32 | from subunit import ByteStreamToStreamResult, StreamResultToBytes | ||
1567 | 32 | 33 | ||
1568 | 33 | 34 | ||
1569 | 34 | class TestTestResultFilter(TestCase): | 35 | class TestTestResultFilter(TestCase): |
1570 | @@ -286,23 +287,6 @@ | |||
1571 | 286 | 287 | ||
1572 | 287 | class TestFilterCommand(TestCase): | 288 | class TestFilterCommand(TestCase): |
1573 | 288 | 289 | ||
1574 | 289 | example_subunit_stream = _b("""\ | ||
1575 | 290 | tags: global | ||
1576 | 291 | test passed | ||
1577 | 292 | success passed | ||
1578 | 293 | test failed | ||
1579 | 294 | tags: local | ||
1580 | 295 | failure failed | ||
1581 | 296 | test error | ||
1582 | 297 | error error [ | ||
1583 | 298 | error details | ||
1584 | 299 | ] | ||
1585 | 300 | test skipped | ||
1586 | 301 | skip skipped | ||
1587 | 302 | test todo | ||
1588 | 303 | xfail todo | ||
1589 | 304 | """) | ||
1590 | 305 | |||
1591 | 306 | def run_command(self, args, stream): | 290 | def run_command(self, args, stream): |
1592 | 307 | root = os.path.dirname( | 291 | root = os.path.dirname( |
1593 | 308 | os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) | 292 | os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) |
1594 | @@ -316,52 +300,50 @@ | |||
1595 | 316 | raise RuntimeError("%s failed: %s" % (command, err)) | 300 | raise RuntimeError("%s failed: %s" % (command, err)) |
1596 | 317 | return out | 301 | return out |
1597 | 318 | 302 | ||
1598 | 319 | def to_events(self, stream): | ||
1599 | 320 | test = subunit.ProtocolTestCase(BytesIO(stream)) | ||
1600 | 321 | result = ExtendedTestResult() | ||
1601 | 322 | test.run(result) | ||
1602 | 323 | return result._events | ||
1603 | 324 | |||
1604 | 325 | def test_default(self): | 303 | def test_default(self): |
1616 | 326 | output = self.run_command([], _b( | 304 | byte_stream = BytesIO() |
1617 | 327 | "test: foo\n" | 305 | stream = StreamResultToBytes(byte_stream) |
1618 | 328 | "skip: foo\n" | 306 | stream.status(test_id="foo", test_status="inprogress") |
1619 | 329 | )) | 307 | stream.status(test_id="foo", test_status="skip") |
1620 | 330 | events = self.to_events(output) | 308 | output = self.run_command([], byte_stream.getvalue()) |
1621 | 331 | foo = subunit.RemotedTestCase('foo') | 309 | events = StreamResult() |
1622 | 332 | self.assertEqual( | 310 | ByteStreamToStreamResult(BytesIO(output)).run(events) |
1623 | 333 | [('startTest', foo), | 311 | ids = set(event[1] for event in events._events) |
1624 | 334 | ('addSkip', foo, {}), | 312 | self.assertEqual([ |
1625 | 335 | ('stopTest', foo)], | 313 | ('status', 'foo', 'inprogress'), |
1626 | 336 | events) | 314 | ('status', 'foo', 'skip'), |
1627 | 315 | ], [event[:3] for event in events._events]) | ||
1628 | 337 | 316 | ||
1629 | 338 | def test_tags(self): | 317 | def test_tags(self): |
1656 | 339 | output = self.run_command(['-s', '--with-tag', 'a'], _b( | 318 | byte_stream = BytesIO() |
1657 | 340 | "tags: a\n" | 319 | stream = StreamResultToBytes(byte_stream) |
1658 | 341 | "test: foo\n" | 320 | stream.status( |
1659 | 342 | "success: foo\n" | 321 | test_id="foo", test_status="inprogress", test_tags=set(["a"])) |
1660 | 343 | "tags: -a\n" | 322 | stream.status( |
1661 | 344 | "test: bar\n" | 323 | test_id="foo", test_status="success", test_tags=set(["a"])) |
1662 | 345 | "success: bar\n" | 324 | stream.status(test_id="bar", test_status="inprogress") |
1663 | 346 | "test: baz\n" | 325 | stream.status(test_id="bar", test_status="inprogress") |
1664 | 347 | "tags: a\n" | 326 | stream.status( |
1665 | 348 | "success: baz\n" | 327 | test_id="baz", test_status="inprogress", test_tags=set(["a"])) |
1666 | 349 | )) | 328 | stream.status( |
1667 | 350 | events = self.to_events(output) | 329 | test_id="baz", test_status="success", test_tags=set(["a"])) |
1668 | 351 | foo = subunit.RemotedTestCase('foo') | 330 | output = self.run_command( |
1669 | 352 | baz = subunit.RemotedTestCase('baz') | 331 | ['-s', '--with-tag', 'a'], byte_stream.getvalue()) |
1670 | 353 | self.assertEqual( | 332 | events = StreamResult() |
1671 | 354 | [('tags', set(['a']), set()), | 333 | ByteStreamToStreamResult(BytesIO(output)).run(events) |
1672 | 355 | ('startTest', foo), | 334 | ids = set(event[1] for event in events._events) |
1673 | 356 | ('addSuccess', foo), | 335 | self.assertEqual(set(['foo', 'baz']), ids) |
1674 | 357 | ('stopTest', foo), | 336 | |
1675 | 358 | ('tags', set(), set(['a'])), | 337 | def test_no_passthrough(self): |
1676 | 359 | ('startTest', baz), | 338 | output = self.run_command(['--no-passthrough'], b'hi thar') |
1677 | 360 | ('tags', set(['a']), set()), | 339 | self.assertEqual(b'', output) |
1678 | 361 | ('addSuccess', baz), | 340 | |
1679 | 362 | ('stopTest', baz), | 341 | def test_passthrough(self): |
1680 | 363 | ], | 342 | output = self.run_command([], b'hi thar') |
1681 | 364 | events) | 343 | byte_stream = BytesIO() |
1682 | 344 | stream = StreamResultToBytes(byte_stream) | ||
1683 | 345 | stream.status(file_name="stdout", file_bytes=b'hi thar') | ||
1684 | 346 | self.assertEqual(byte_stream.getvalue(), output) | ||
1685 | 365 | 347 | ||
1686 | 366 | 348 | ||
1687 | 367 | def test_suite(): | 349 | def test_suite(): |
1688 | 368 | 350 | ||
1689 | === modified file 'python/subunit/tests/test_subunit_tags.py' | |||
1690 | --- python/subunit/tests/test_subunit_tags.py 2011-04-24 21:40:52 +0000 | |||
1691 | +++ python/subunit/tests/test_subunit_tags.py 2013-03-31 05:51:20 +0000 | |||
1692 | @@ -16,10 +16,9 @@ | |||
1693 | 16 | 16 | ||
1694 | 17 | """Tests for subunit.tag_stream.""" | 17 | """Tests for subunit.tag_stream.""" |
1695 | 18 | 18 | ||
1696 | 19 | from io import BytesIO | ||
1697 | 19 | import unittest | 20 | import unittest |
1698 | 20 | 21 | ||
1699 | 21 | from testtools.compat import StringIO | ||
1700 | 22 | |||
1701 | 23 | import subunit | 22 | import subunit |
1702 | 24 | import subunit.test_results | 23 | import subunit.test_results |
1703 | 25 | 24 | ||
1704 | @@ -27,40 +26,42 @@ | |||
1705 | 27 | class TestSubUnitTags(unittest.TestCase): | 26 | class TestSubUnitTags(unittest.TestCase): |
1706 | 28 | 27 | ||
1707 | 29 | def setUp(self): | 28 | def setUp(self): |
1710 | 30 | self.original = StringIO() | 29 | self.original = BytesIO() |
1711 | 31 | self.filtered = StringIO() | 30 | self.filtered = BytesIO() |
1712 | 32 | 31 | ||
1713 | 33 | def test_add_tag(self): | 32 | def test_add_tag(self): |
1718 | 34 | self.original.write("tags: foo\n") | 33 | reference = BytesIO() |
1719 | 35 | self.original.write("test: test\n") | 34 | stream = subunit.StreamResultToBytes(reference) |
1720 | 36 | self.original.write("tags: bar -quux\n") | 35 | stream.status( |
1721 | 37 | self.original.write("success: test\n") | 36 | test_id='test', test_status='inprogress', test_tags=set(['quux', 'foo'])) |
1722 | 37 | stream.status( | ||
1723 | 38 | test_id='test', test_status='success', test_tags=set(['bar', 'quux', 'foo'])) | ||
1724 | 39 | stream = subunit.StreamResultToBytes(self.original) | ||
1725 | 40 | stream.status( | ||
1726 | 41 | test_id='test', test_status='inprogress', test_tags=set(['foo'])) | ||
1727 | 42 | stream.status( | ||
1728 | 43 | test_id='test', test_status='success', test_tags=set(['foo', 'bar'])) | ||
1729 | 38 | self.original.seek(0) | 44 | self.original.seek(0) |
1739 | 39 | result = subunit.tag_stream(self.original, self.filtered, ["quux"]) | 45 | self.assertEqual( |
1740 | 40 | self.assertEqual([ | 46 | 0, subunit.tag_stream(self.original, self.filtered, ["quux"])) |
1741 | 41 | "tags: quux", | 47 | self.assertEqual(reference.getvalue(), self.filtered.getvalue()) |
1733 | 42 | "tags: foo", | ||
1734 | 43 | "test: test", | ||
1735 | 44 | "tags: bar", | ||
1736 | 45 | "success: test", | ||
1737 | 46 | ], | ||
1738 | 47 | self.filtered.getvalue().splitlines()) | ||
1742 | 48 | 48 | ||
1743 | 49 | def test_remove_tag(self): | 49 | def test_remove_tag(self): |
1748 | 50 | self.original.write("tags: foo\n") | 50 | reference = BytesIO() |
1749 | 51 | self.original.write("test: test\n") | 51 | stream = subunit.StreamResultToBytes(reference) |
1750 | 52 | self.original.write("tags: bar -quux\n") | 52 | stream.status( |
1751 | 53 | self.original.write("success: test\n") | 53 | test_id='test', test_status='inprogress', test_tags=set(['foo'])) |
1752 | 54 | stream.status( | ||
1753 | 55 | test_id='test', test_status='success', test_tags=set(['foo'])) | ||
1754 | 56 | stream = subunit.StreamResultToBytes(self.original) | ||
1755 | 57 | stream.status( | ||
1756 | 58 | test_id='test', test_status='inprogress', test_tags=set(['foo'])) | ||
1757 | 59 | stream.status( | ||
1758 | 60 | test_id='test', test_status='success', test_tags=set(['foo', 'bar'])) | ||
1759 | 54 | self.original.seek(0) | 61 | self.original.seek(0) |
1769 | 55 | result = subunit.tag_stream(self.original, self.filtered, ["-bar"]) | 62 | self.assertEqual( |
1770 | 56 | self.assertEqual([ | 63 | 0, subunit.tag_stream(self.original, self.filtered, ["-bar"])) |
1771 | 57 | "tags: -bar", | 64 | self.assertEqual(reference.getvalue(), self.filtered.getvalue()) |
1763 | 58 | "tags: foo", | ||
1764 | 59 | "test: test", | ||
1765 | 60 | "tags: -quux", | ||
1766 | 61 | "success: test", | ||
1767 | 62 | ], | ||
1768 | 63 | self.filtered.getvalue().splitlines()) | ||
1772 | 64 | 65 | ||
1773 | 65 | 66 | ||
1774 | 66 | def test_suite(): | 67 | def test_suite(): |
1775 | 67 | 68 | ||
1776 | === modified file 'python/subunit/tests/test_tap2subunit.py' | |||
1777 | --- python/subunit/tests/test_tap2subunit.py 2011-04-24 21:40:52 +0000 | |||
1778 | +++ python/subunit/tests/test_tap2subunit.py 2013-03-31 05:51:20 +0000 | |||
1779 | @@ -16,14 +16,19 @@ | |||
1780 | 16 | 16 | ||
1781 | 17 | """Tests for TAP2SubUnit.""" | 17 | """Tests for TAP2SubUnit.""" |
1782 | 18 | 18 | ||
1783 | 19 | from io import BytesIO, StringIO | ||
1784 | 19 | import unittest | 20 | import unittest |
1785 | 20 | 21 | ||
1787 | 21 | from testtools.compat import StringIO | 22 | from testtools import TestCase |
1788 | 23 | from testtools.compat import _u | ||
1789 | 24 | from testtools.testresult.doubles import StreamResult | ||
1790 | 22 | 25 | ||
1791 | 23 | import subunit | 26 | import subunit |
1792 | 24 | 27 | ||
1795 | 25 | 28 | UTF8_TEXT = 'text/plain; charset=UTF8' | |
1796 | 26 | class TestTAP2SubUnit(unittest.TestCase): | 29 | |
1797 | 30 | |||
1798 | 31 | class TestTAP2SubUnit(TestCase): | ||
1799 | 27 | """Tests for TAP2SubUnit. | 32 | """Tests for TAP2SubUnit. |
1800 | 28 | 33 | ||
1801 | 29 | These tests test TAP string data in, and subunit string data out. | 34 | These tests test TAP string data in, and subunit string data out. |
1802 | @@ -34,24 +39,21 @@ | |||
1803 | 34 | """ | 39 | """ |
1804 | 35 | 40 | ||
1805 | 36 | def setUp(self): | 41 | def setUp(self): |
1806 | 42 | super(TestTAP2SubUnit, self).setUp() | ||
1807 | 37 | self.tap = StringIO() | 43 | self.tap = StringIO() |
1809 | 38 | self.subunit = StringIO() | 44 | self.subunit = BytesIO() |
1810 | 39 | 45 | ||
1811 | 40 | def test_skip_entire_file(self): | 46 | def test_skip_entire_file(self): |
1812 | 41 | # A file | 47 | # A file |
1813 | 42 | # 1..- # Skipped: comment | 48 | # 1..- # Skipped: comment |
1814 | 43 | # results in a single skipped test. | 49 | # results in a single skipped test. |
1816 | 44 | self.tap.write("1..0 # Skipped: entire file skipped\n") | 50 | self.tap.write(_u("1..0 # Skipped: entire file skipped\n")) |
1817 | 45 | self.tap.seek(0) | 51 | self.tap.seek(0) |
1818 | 46 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 52 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
1819 | 47 | self.assertEqual(0, result) | 53 | self.assertEqual(0, result) |
1827 | 48 | self.assertEqual([ | 54 | self.check_events([('status', 'file skip', 'skip', None, True, |
1828 | 49 | "test file skip", | 55 | 'tap comment', b'Skipped: entire file skipped', True, None, None, |
1829 | 50 | "skip file skip [", | 56 | None)]) |
1823 | 51 | "Skipped: entire file skipped", | ||
1824 | 52 | "]", | ||
1825 | 53 | ], | ||
1826 | 54 | self.subunit.getvalue().splitlines()) | ||
1830 | 55 | 57 | ||
1831 | 56 | def test_ok_test_pass(self): | 58 | def test_ok_test_pass(self): |
1832 | 57 | # A file | 59 | # A file |
1833 | @@ -59,164 +61,128 @@ | |||
1834 | 59 | # results in a passed test with name 'test 1' (a synthetic name as tap | 61 | # results in a passed test with name 'test 1' (a synthetic name as tap |
1835 | 60 | # does not require named fixtures - it is the first test in the tap | 62 | # does not require named fixtures - it is the first test in the tap |
1836 | 61 | # stream). | 63 | # stream). |
1838 | 62 | self.tap.write("ok\n") | 64 | self.tap.write(_u("ok\n")) |
1839 | 63 | self.tap.seek(0) | 65 | self.tap.seek(0) |
1840 | 64 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 66 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
1841 | 65 | self.assertEqual(0, result) | 67 | self.assertEqual(0, result) |
1847 | 66 | self.assertEqual([ | 68 | self.check_events([('status', 'test 1', 'success', None, False, None, |
1848 | 67 | "test test 1", | 69 | None, True, None, None, None)]) |
1844 | 68 | "success test 1", | ||
1845 | 69 | ], | ||
1846 | 70 | self.subunit.getvalue().splitlines()) | ||
1849 | 71 | 70 | ||
1850 | 72 | def test_ok_test_number_pass(self): | 71 | def test_ok_test_number_pass(self): |
1851 | 73 | # A file | 72 | # A file |
1852 | 74 | # ok 1 | 73 | # ok 1 |
1853 | 75 | # results in a passed test with name 'test 1' | 74 | # results in a passed test with name 'test 1' |
1855 | 76 | self.tap.write("ok 1\n") | 75 | self.tap.write(_u("ok 1\n")) |
1856 | 77 | self.tap.seek(0) | 76 | self.tap.seek(0) |
1857 | 78 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 77 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
1858 | 79 | self.assertEqual(0, result) | 78 | self.assertEqual(0, result) |
1864 | 80 | self.assertEqual([ | 79 | self.check_events([('status', 'test 1', 'success', None, False, None, |
1865 | 81 | "test test 1", | 80 | None, True, None, None, None)]) |
1861 | 82 | "success test 1", | ||
1862 | 83 | ], | ||
1863 | 84 | self.subunit.getvalue().splitlines()) | ||
1866 | 85 | 81 | ||
1867 | 86 | def test_ok_test_number_description_pass(self): | 82 | def test_ok_test_number_description_pass(self): |
1868 | 87 | # A file | 83 | # A file |
1869 | 88 | # ok 1 - There is a description | 84 | # ok 1 - There is a description |
1870 | 89 | # results in a passed test with name 'test 1 - There is a description' | 85 | # results in a passed test with name 'test 1 - There is a description' |
1872 | 90 | self.tap.write("ok 1 - There is a description\n") | 86 | self.tap.write(_u("ok 1 - There is a description\n")) |
1873 | 91 | self.tap.seek(0) | 87 | self.tap.seek(0) |
1874 | 92 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 88 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
1875 | 93 | self.assertEqual(0, result) | 89 | self.assertEqual(0, result) |
1881 | 94 | self.assertEqual([ | 90 | self.check_events([('status', 'test 1 - There is a description', |
1882 | 95 | "test test 1 - There is a description", | 91 | 'success', None, False, None, None, True, None, None, None)]) |
1878 | 96 | "success test 1 - There is a description", | ||
1879 | 97 | ], | ||
1880 | 98 | self.subunit.getvalue().splitlines()) | ||
1883 | 99 | 92 | ||
1884 | 100 | def test_ok_test_description_pass(self): | 93 | def test_ok_test_description_pass(self): |
1885 | 101 | # A file | 94 | # A file |
1886 | 102 | # ok There is a description | 95 | # ok There is a description |
1887 | 103 | # results in a passed test with name 'test 1 There is a description' | 96 | # results in a passed test with name 'test 1 There is a description' |
1889 | 104 | self.tap.write("ok There is a description\n") | 97 | self.tap.write(_u("ok There is a description\n")) |
1890 | 105 | self.tap.seek(0) | 98 | self.tap.seek(0) |
1891 | 106 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 99 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
1892 | 107 | self.assertEqual(0, result) | 100 | self.assertEqual(0, result) |
1898 | 108 | self.assertEqual([ | 101 | self.check_events([('status', 'test 1 There is a description', |
1899 | 109 | "test test 1 There is a description", | 102 | 'success', None, False, None, None, True, None, None, None)]) |
1895 | 110 | "success test 1 There is a description", | ||
1896 | 111 | ], | ||
1897 | 112 | self.subunit.getvalue().splitlines()) | ||
1900 | 113 | 103 | ||
1901 | 114 | def test_ok_SKIP_skip(self): | 104 | def test_ok_SKIP_skip(self): |
1902 | 115 | # A file | 105 | # A file |
1903 | 116 | # ok # SKIP | 106 | # ok # SKIP |
1904 | 117 | # results in a skkip test with name 'test 1' | 107 | # results in a skkip test with name 'test 1' |
1906 | 118 | self.tap.write("ok # SKIP\n") | 108 | self.tap.write(_u("ok # SKIP\n")) |
1907 | 119 | self.tap.seek(0) | 109 | self.tap.seek(0) |
1908 | 120 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 110 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
1909 | 121 | self.assertEqual(0, result) | 111 | self.assertEqual(0, result) |
1915 | 122 | self.assertEqual([ | 112 | self.check_events([('status', 'test 1', 'skip', None, False, None, |
1916 | 123 | "test test 1", | 113 | None, True, None, None, None)]) |
1912 | 124 | "skip test 1", | ||
1913 | 125 | ], | ||
1914 | 126 | self.subunit.getvalue().splitlines()) | ||
1917 | 127 | 114 | ||
1918 | 128 | def test_ok_skip_number_comment_lowercase(self): | 115 | def test_ok_skip_number_comment_lowercase(self): |
1920 | 129 | self.tap.write("ok 1 # skip no samba environment available, skipping compilation\n") | 116 | self.tap.write(_u("ok 1 # skip no samba environment available, skipping compilation\n")) |
1921 | 130 | self.tap.seek(0) | 117 | self.tap.seek(0) |
1922 | 131 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 118 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
1923 | 132 | self.assertEqual(0, result) | 119 | self.assertEqual(0, result) |
1931 | 133 | self.assertEqual([ | 120 | self.check_events([('status', 'test 1', 'skip', None, False, 'tap comment', |
1932 | 134 | "test test 1", | 121 | b'no samba environment available, skipping compilation', True, |
1933 | 135 | "skip test 1 [", | 122 | 'text/plain; charset=UTF8', None, None)]) |
1927 | 136 | "no samba environment available, skipping compilation", | ||
1928 | 137 | "]" | ||
1929 | 138 | ], | ||
1930 | 139 | self.subunit.getvalue().splitlines()) | ||
1934 | 140 | 123 | ||
1935 | 141 | def test_ok_number_description_SKIP_skip_comment(self): | 124 | def test_ok_number_description_SKIP_skip_comment(self): |
1936 | 142 | # A file | 125 | # A file |
1937 | 143 | # ok 1 foo # SKIP Not done yet | 126 | # ok 1 foo # SKIP Not done yet |
1938 | 144 | # results in a skip test with name 'test 1 foo' and a log of | 127 | # results in a skip test with name 'test 1 foo' and a log of |
1939 | 145 | # Not done yet | 128 | # Not done yet |
1941 | 146 | self.tap.write("ok 1 foo # SKIP Not done yet\n") | 129 | self.tap.write(_u("ok 1 foo # SKIP Not done yet\n")) |
1942 | 147 | self.tap.seek(0) | 130 | self.tap.seek(0) |
1943 | 148 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 131 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
1944 | 149 | self.assertEqual(0, result) | 132 | self.assertEqual(0, result) |
1952 | 150 | self.assertEqual([ | 133 | self.check_events([('status', 'test 1 foo', 'skip', None, False, |
1953 | 151 | "test test 1 foo", | 134 | 'tap comment', b'Not done yet', True, 'text/plain; charset=UTF8', |
1954 | 152 | "skip test 1 foo [", | 135 | None, None)]) |
1948 | 153 | "Not done yet", | ||
1949 | 154 | "]", | ||
1950 | 155 | ], | ||
1951 | 156 | self.subunit.getvalue().splitlines()) | ||
1955 | 157 | 136 | ||
1956 | 158 | def test_ok_SKIP_skip_comment(self): | 137 | def test_ok_SKIP_skip_comment(self): |
1957 | 159 | # A file | 138 | # A file |
1958 | 160 | # ok # SKIP Not done yet | 139 | # ok # SKIP Not done yet |
1959 | 161 | # results in a skip test with name 'test 1' and a log of Not done yet | 140 | # results in a skip test with name 'test 1' and a log of Not done yet |
1961 | 162 | self.tap.write("ok # SKIP Not done yet\n") | 141 | self.tap.write(_u("ok # SKIP Not done yet\n")) |
1962 | 163 | self.tap.seek(0) | 142 | self.tap.seek(0) |
1963 | 164 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 143 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
1964 | 165 | self.assertEqual(0, result) | 144 | self.assertEqual(0, result) |
1972 | 166 | self.assertEqual([ | 145 | self.check_events([('status', 'test 1', 'skip', None, False, |
1973 | 167 | "test test 1", | 146 | 'tap comment', b'Not done yet', True, 'text/plain; charset=UTF8', |
1974 | 168 | "skip test 1 [", | 147 | None, None)]) |
1968 | 169 | "Not done yet", | ||
1969 | 170 | "]", | ||
1970 | 171 | ], | ||
1971 | 172 | self.subunit.getvalue().splitlines()) | ||
1975 | 173 | 148 | ||
1976 | 174 | def test_ok_TODO_xfail(self): | 149 | def test_ok_TODO_xfail(self): |
1977 | 175 | # A file | 150 | # A file |
1978 | 176 | # ok # TODO | 151 | # ok # TODO |
1979 | 177 | # results in a xfail test with name 'test 1' | 152 | # results in a xfail test with name 'test 1' |
1981 | 178 | self.tap.write("ok # TODO\n") | 153 | self.tap.write(_u("ok # TODO\n")) |
1982 | 179 | self.tap.seek(0) | 154 | self.tap.seek(0) |
1983 | 180 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 155 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
1984 | 181 | self.assertEqual(0, result) | 156 | self.assertEqual(0, result) |
1990 | 182 | self.assertEqual([ | 157 | self.check_events([('status', 'test 1', 'xfail', None, False, None, |
1991 | 183 | "test test 1", | 158 | None, True, None, None, None)]) |
1987 | 184 | "xfail test 1", | ||
1988 | 185 | ], | ||
1989 | 186 | self.subunit.getvalue().splitlines()) | ||
1992 | 187 | 159 | ||
1993 | 188 | def test_ok_TODO_xfail_comment(self): | 160 | def test_ok_TODO_xfail_comment(self): |
1994 | 189 | # A file | 161 | # A file |
1995 | 190 | # ok # TODO Not done yet | 162 | # ok # TODO Not done yet |
1996 | 191 | # results in a xfail test with name 'test 1' and a log of Not done yet | 163 | # results in a xfail test with name 'test 1' and a log of Not done yet |
1998 | 192 | self.tap.write("ok # TODO Not done yet\n") | 164 | self.tap.write(_u("ok # TODO Not done yet\n")) |
1999 | 193 | self.tap.seek(0) | 165 | self.tap.seek(0) |
2000 | 194 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 166 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
2001 | 195 | self.assertEqual(0, result) | 167 | self.assertEqual(0, result) |
2009 | 196 | self.assertEqual([ | 168 | self.check_events([('status', 'test 1', 'xfail', None, False, |
2010 | 197 | "test test 1", | 169 | 'tap comment', b'Not done yet', True, 'text/plain; charset=UTF8', |
2011 | 198 | "xfail test 1 [", | 170 | None, None)]) |
2005 | 199 | "Not done yet", | ||
2006 | 200 | "]", | ||
2007 | 201 | ], | ||
2008 | 202 | self.subunit.getvalue().splitlines()) | ||
2012 | 203 | 171 | ||
2013 | 204 | def test_bail_out_errors(self): | 172 | def test_bail_out_errors(self): |
2014 | 205 | # A file with line in it | 173 | # A file with line in it |
2015 | 206 | # Bail out! COMMENT | 174 | # Bail out! COMMENT |
2016 | 207 | # is treated as an error | 175 | # is treated as an error |
2019 | 208 | self.tap.write("ok 1 foo\n") | 176 | self.tap.write(_u("ok 1 foo\n")) |
2020 | 209 | self.tap.write("Bail out! Lifejacket engaged\n") | 177 | self.tap.write(_u("Bail out! Lifejacket engaged\n")) |
2021 | 210 | self.tap.seek(0) | 178 | self.tap.seek(0) |
2022 | 211 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 179 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
2023 | 212 | self.assertEqual(0, result) | 180 | self.assertEqual(0, result) |
2031 | 213 | self.assertEqual([ | 181 | self.check_events([ |
2032 | 214 | "test test 1 foo", | 182 | ('status', 'test 1 foo', 'success', None, False, None, None, True, |
2033 | 215 | "success test 1 foo", | 183 | None, None, None), |
2034 | 216 | "test Bail out! Lifejacket engaged", | 184 | ('status', 'Bail out! Lifejacket engaged', 'fail', None, False, |
2035 | 217 | "error Bail out! Lifejacket engaged", | 185 | None, None, True, None, None, None)]) |
2029 | 218 | ], | ||
2030 | 219 | self.subunit.getvalue().splitlines()) | ||
2036 | 220 | 186 | ||
2037 | 221 | def test_missing_test_at_end_with_plan_adds_error(self): | 187 | def test_missing_test_at_end_with_plan_adds_error(self): |
2038 | 222 | # A file | 188 | # A file |
2039 | @@ -224,23 +190,20 @@ | |||
2040 | 224 | # ok first test | 190 | # ok first test |
2041 | 225 | # not ok third test | 191 | # not ok third test |
2042 | 226 | # results in three tests, with the third being created | 192 | # results in three tests, with the third being created |
2046 | 227 | self.tap.write('1..3\n') | 193 | self.tap.write(_u('1..3\n')) |
2047 | 228 | self.tap.write('ok first test\n') | 194 | self.tap.write(_u('ok first test\n')) |
2048 | 229 | self.tap.write('not ok second test\n') | 195 | self.tap.write(_u('not ok second test\n')) |
2049 | 230 | self.tap.seek(0) | 196 | self.tap.seek(0) |
2050 | 231 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 197 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
2051 | 232 | self.assertEqual(0, result) | 198 | self.assertEqual(0, result) |
2063 | 233 | self.assertEqual([ | 199 | self.check_events([ |
2064 | 234 | 'test test 1 first test', | 200 | ('status', 'test 1 first test', 'success', None, False, None, |
2065 | 235 | 'success test 1 first test', | 201 | None, True, None, None, None), |
2066 | 236 | 'test test 2 second test', | 202 | ('status', 'test 2 second test', 'fail', None, False, None, None, |
2067 | 237 | 'failure test 2 second test', | 203 | True, None, None, None), |
2068 | 238 | 'test test 3', | 204 | ('status', 'test 3', 'fail', None, False, 'tap meta', |
2069 | 239 | 'error test 3 [', | 205 | b'test missing from TAP output', True, 'text/plain; charset=UTF8', |
2070 | 240 | 'test missing from TAP output', | 206 | None, None)]) |
2060 | 241 | ']', | ||
2061 | 242 | ], | ||
2062 | 243 | self.subunit.getvalue().splitlines()) | ||
2071 | 244 | 207 | ||
2072 | 245 | def test_missing_test_with_plan_adds_error(self): | 208 | def test_missing_test_with_plan_adds_error(self): |
2073 | 246 | # A file | 209 | # A file |
2074 | @@ -248,45 +211,39 @@ | |||
2075 | 248 | # ok first test | 211 | # ok first test |
2076 | 249 | # not ok 3 third test | 212 | # not ok 3 third test |
2077 | 250 | # results in three tests, with the second being created | 213 | # results in three tests, with the second being created |
2081 | 251 | self.tap.write('1..3\n') | 214 | self.tap.write(_u('1..3\n')) |
2082 | 252 | self.tap.write('ok first test\n') | 215 | self.tap.write(_u('ok first test\n')) |
2083 | 253 | self.tap.write('not ok 3 third test\n') | 216 | self.tap.write(_u('not ok 3 third test\n')) |
2084 | 254 | self.tap.seek(0) | 217 | self.tap.seek(0) |
2085 | 255 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 218 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
2086 | 256 | self.assertEqual(0, result) | 219 | self.assertEqual(0, result) |
2098 | 257 | self.assertEqual([ | 220 | self.check_events([ |
2099 | 258 | 'test test 1 first test', | 221 | ('status', 'test 1 first test', 'success', None, False, None, None, |
2100 | 259 | 'success test 1 first test', | 222 | True, None, None, None), |
2101 | 260 | 'test test 2', | 223 | ('status', 'test 2', 'fail', None, False, 'tap meta', |
2102 | 261 | 'error test 2 [', | 224 | b'test missing from TAP output', True, 'text/plain; charset=UTF8', |
2103 | 262 | 'test missing from TAP output', | 225 | None, None), |
2104 | 263 | ']', | 226 | ('status', 'test 3 third test', 'fail', None, False, None, None, |
2105 | 264 | 'test test 3 third test', | 227 | True, None, None, None)]) |
2095 | 265 | 'failure test 3 third test', | ||
2096 | 266 | ], | ||
2097 | 267 | self.subunit.getvalue().splitlines()) | ||
2106 | 268 | 228 | ||
2107 | 269 | def test_missing_test_no_plan_adds_error(self): | 229 | def test_missing_test_no_plan_adds_error(self): |
2108 | 270 | # A file | 230 | # A file |
2109 | 271 | # ok first test | 231 | # ok first test |
2110 | 272 | # not ok 3 third test | 232 | # not ok 3 third test |
2111 | 273 | # results in three tests, with the second being created | 233 | # results in three tests, with the second being created |
2114 | 274 | self.tap.write('ok first test\n') | 234 | self.tap.write(_u('ok first test\n')) |
2115 | 275 | self.tap.write('not ok 3 third test\n') | 235 | self.tap.write(_u('not ok 3 third test\n')) |
2116 | 276 | self.tap.seek(0) | 236 | self.tap.seek(0) |
2117 | 277 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 237 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
2118 | 278 | self.assertEqual(0, result) | 238 | self.assertEqual(0, result) |
2130 | 279 | self.assertEqual([ | 239 | self.check_events([ |
2131 | 280 | 'test test 1 first test', | 240 | ('status', 'test 1 first test', 'success', None, False, None, None, |
2132 | 281 | 'success test 1 first test', | 241 | True, None, None, None), |
2133 | 282 | 'test test 2', | 242 | ('status', 'test 2', 'fail', None, False, 'tap meta', |
2134 | 283 | 'error test 2 [', | 243 | b'test missing from TAP output', True, 'text/plain; charset=UTF8', |
2135 | 284 | 'test missing from TAP output', | 244 | None, None), |
2136 | 285 | ']', | 245 | ('status', 'test 3 third test', 'fail', None, False, None, None, |
2137 | 286 | 'test test 3 third test', | 246 | True, None, None, None)]) |
2127 | 287 | 'failure test 3 third test', | ||
2128 | 288 | ], | ||
2129 | 289 | self.subunit.getvalue().splitlines()) | ||
2138 | 290 | 247 | ||
2139 | 291 | def test_four_tests_in_a_row_trailing_plan(self): | 248 | def test_four_tests_in_a_row_trailing_plan(self): |
2140 | 292 | # A file | 249 | # A file |
2141 | @@ -296,25 +253,23 @@ | |||
2142 | 296 | # not ok 4 - fourth | 253 | # not ok 4 - fourth |
2143 | 297 | # 1..4 | 254 | # 1..4 |
2144 | 298 | # results in four tests numbered and named | 255 | # results in four tests numbered and named |
2150 | 299 | self.tap.write('ok 1 - first test in a script with trailing plan\n') | 256 | self.tap.write(_u('ok 1 - first test in a script with trailing plan\n')) |
2151 | 300 | self.tap.write('not ok 2 - second\n') | 257 | self.tap.write(_u('not ok 2 - second\n')) |
2152 | 301 | self.tap.write('ok 3 - third\n') | 258 | self.tap.write(_u('ok 3 - third\n')) |
2153 | 302 | self.tap.write('not ok 4 - fourth\n') | 259 | self.tap.write(_u('not ok 4 - fourth\n')) |
2154 | 303 | self.tap.write('1..4\n') | 260 | self.tap.write(_u('1..4\n')) |
2155 | 304 | self.tap.seek(0) | 261 | self.tap.seek(0) |
2156 | 305 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 262 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
2157 | 306 | self.assertEqual(0, result) | 263 | self.assertEqual(0, result) |
2169 | 307 | self.assertEqual([ | 264 | self.check_events([ |
2170 | 308 | 'test test 1 - first test in a script with trailing plan', | 265 | ('status', 'test 1 - first test in a script with trailing plan', |
2171 | 309 | 'success test 1 - first test in a script with trailing plan', | 266 | 'success', None, False, None, None, True, None, None, None), |
2172 | 310 | 'test test 2 - second', | 267 | ('status', 'test 2 - second', 'fail', None, False, None, None, |
2173 | 311 | 'failure test 2 - second', | 268 | True, None, None, None), |
2174 | 312 | 'test test 3 - third', | 269 | ('status', 'test 3 - third', 'success', None, False, None, None, |
2175 | 313 | 'success test 3 - third', | 270 | True, None, None, None), |
2176 | 314 | 'test test 4 - fourth', | 271 | ('status', 'test 4 - fourth', 'fail', None, False, None, None, |
2177 | 315 | 'failure test 4 - fourth' | 272 | True, None, None, None)]) |
2167 | 316 | ], | ||
2168 | 317 | self.subunit.getvalue().splitlines()) | ||
2178 | 318 | 273 | ||
2179 | 319 | def test_four_tests_in_a_row_with_plan(self): | 274 | def test_four_tests_in_a_row_with_plan(self): |
2180 | 320 | # A file | 275 | # A file |
2181 | @@ -324,25 +279,23 @@ | |||
2182 | 324 | # ok 3 - third | 279 | # ok 3 - third |
2183 | 325 | # not ok 4 - fourth | 280 | # not ok 4 - fourth |
2184 | 326 | # results in four tests numbered and named | 281 | # results in four tests numbered and named |
2190 | 327 | self.tap.write('1..4\n') | 282 | self.tap.write(_u('1..4\n')) |
2191 | 328 | self.tap.write('ok 1 - first test in a script with a plan\n') | 283 | self.tap.write(_u('ok 1 - first test in a script with a plan\n')) |
2192 | 329 | self.tap.write('not ok 2 - second\n') | 284 | self.tap.write(_u('not ok 2 - second\n')) |
2193 | 330 | self.tap.write('ok 3 - third\n') | 285 | self.tap.write(_u('ok 3 - third\n')) |
2194 | 331 | self.tap.write('not ok 4 - fourth\n') | 286 | self.tap.write(_u('not ok 4 - fourth\n')) |
2195 | 332 | self.tap.seek(0) | 287 | self.tap.seek(0) |
2196 | 333 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 288 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
2197 | 334 | self.assertEqual(0, result) | 289 | self.assertEqual(0, result) |
2209 | 335 | self.assertEqual([ | 290 | self.check_events([ |
2210 | 336 | 'test test 1 - first test in a script with a plan', | 291 | ('status', 'test 1 - first test in a script with a plan', |
2211 | 337 | 'success test 1 - first test in a script with a plan', | 292 | 'success', None, False, None, None, True, None, None, None), |
2212 | 338 | 'test test 2 - second', | 293 | ('status', 'test 2 - second', 'fail', None, False, None, None, |
2213 | 339 | 'failure test 2 - second', | 294 | True, None, None, None), |
2214 | 340 | 'test test 3 - third', | 295 | ('status', 'test 3 - third', 'success', None, False, None, None, |
2215 | 341 | 'success test 3 - third', | 296 | True, None, None, None), |
2216 | 342 | 'test test 4 - fourth', | 297 | ('status', 'test 4 - fourth', 'fail', None, False, None, None, |
2217 | 343 | 'failure test 4 - fourth' | 298 | True, None, None, None)]) |
2207 | 344 | ], | ||
2208 | 345 | self.subunit.getvalue().splitlines()) | ||
2218 | 346 | 299 | ||
2219 | 347 | def test_four_tests_in_a_row_no_plan(self): | 300 | def test_four_tests_in_a_row_no_plan(self): |
2220 | 348 | # A file | 301 | # A file |
2221 | @@ -351,46 +304,43 @@ | |||
2222 | 351 | # ok 3 - third | 304 | # ok 3 - third |
2223 | 352 | # not ok 4 - fourth | 305 | # not ok 4 - fourth |
2224 | 353 | # results in four tests numbered and named | 306 | # results in four tests numbered and named |
2229 | 354 | self.tap.write('ok 1 - first test in a script with no plan at all\n') | 307 | self.tap.write(_u('ok 1 - first test in a script with no plan at all\n')) |
2230 | 355 | self.tap.write('not ok 2 - second\n') | 308 | self.tap.write(_u('not ok 2 - second\n')) |
2231 | 356 | self.tap.write('ok 3 - third\n') | 309 | self.tap.write(_u('ok 3 - third\n')) |
2232 | 357 | self.tap.write('not ok 4 - fourth\n') | 310 | self.tap.write(_u('not ok 4 - fourth\n')) |
2233 | 358 | self.tap.seek(0) | 311 | self.tap.seek(0) |
2234 | 359 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 312 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
2235 | 360 | self.assertEqual(0, result) | 313 | self.assertEqual(0, result) |
2247 | 361 | self.assertEqual([ | 314 | self.check_events([ |
2248 | 362 | 'test test 1 - first test in a script with no plan at all', | 315 | ('status', 'test 1 - first test in a script with no plan at all', |
2249 | 363 | 'success test 1 - first test in a script with no plan at all', | 316 | 'success', None, False, None, None, True, None, None, None), |
2250 | 364 | 'test test 2 - second', | 317 | ('status', 'test 2 - second', 'fail', None, False, None, None, |
2251 | 365 | 'failure test 2 - second', | 318 | True, None, None, None), |
2252 | 366 | 'test test 3 - third', | 319 | ('status', 'test 3 - third', 'success', None, False, None, None, |
2253 | 367 | 'success test 3 - third', | 320 | True, None, None, None), |
2254 | 368 | 'test test 4 - fourth', | 321 | ('status', 'test 4 - fourth', 'fail', None, False, None, None, |
2255 | 369 | 'failure test 4 - fourth' | 322 | True, None, None, None)]) |
2245 | 370 | ], | ||
2246 | 371 | self.subunit.getvalue().splitlines()) | ||
2256 | 372 | 323 | ||
2257 | 373 | def test_todo_and_skip(self): | 324 | def test_todo_and_skip(self): |
2258 | 374 | # A file | 325 | # A file |
2259 | 375 | # not ok 1 - a fail but # TODO but is TODO | 326 | # not ok 1 - a fail but # TODO but is TODO |
2260 | 376 | # not ok 2 - another fail # SKIP instead | 327 | # not ok 2 - another fail # SKIP instead |
2261 | 377 | # results in two tests, numbered and commented. | 328 | # results in two tests, numbered and commented. |
2264 | 378 | self.tap.write("not ok 1 - a fail but # TODO but is TODO\n") | 329 | self.tap.write(_u("not ok 1 - a fail but # TODO but is TODO\n")) |
2265 | 379 | self.tap.write("not ok 2 - another fail # SKIP instead\n") | 330 | self.tap.write(_u("not ok 2 - another fail # SKIP instead\n")) |
2266 | 380 | self.tap.seek(0) | 331 | self.tap.seek(0) |
2267 | 381 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 332 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
2268 | 382 | self.assertEqual(0, result) | 333 | self.assertEqual(0, result) |
2280 | 383 | self.assertEqual([ | 334 | self.subunit.seek(0) |
2281 | 384 | 'test test 1 - a fail but', | 335 | events = StreamResult() |
2282 | 385 | 'xfail test 1 - a fail but [', | 336 | subunit.ByteStreamToStreamResult(self.subunit).run(events) |
2283 | 386 | 'but is TODO', | 337 | self.check_events([ |
2284 | 387 | ']', | 338 | ('status', 'test 1 - a fail but', 'xfail', None, False, |
2285 | 388 | 'test test 2 - another fail', | 339 | 'tap comment', b'but is TODO', True, 'text/plain; charset=UTF8', |
2286 | 389 | 'skip test 2 - another fail [', | 340 | None, None), |
2287 | 390 | 'instead', | 341 | ('status', 'test 2 - another fail', 'skip', None, False, |
2288 | 391 | ']', | 342 | 'tap comment', b'instead', True, 'text/plain; charset=UTF8', |
2289 | 392 | ], | 343 | None, None)]) |
2279 | 393 | self.subunit.getvalue().splitlines()) | ||
2290 | 394 | 344 | ||
2291 | 395 | def test_leading_comments_add_to_next_test_log(self): | 345 | def test_leading_comments_add_to_next_test_log(self): |
2292 | 396 | # A file | 346 | # A file |
2293 | @@ -399,21 +349,17 @@ | |||
2294 | 399 | # ok | 349 | # ok |
2295 | 400 | # results in a single test with the comment included | 350 | # results in a single test with the comment included |
2296 | 401 | # in the first test and not the second. | 351 | # in the first test and not the second. |
2300 | 402 | self.tap.write("# comment\n") | 352 | self.tap.write(_u("# comment\n")) |
2301 | 403 | self.tap.write("ok\n") | 353 | self.tap.write(_u("ok\n")) |
2302 | 404 | self.tap.write("ok\n") | 354 | self.tap.write(_u("ok\n")) |
2303 | 405 | self.tap.seek(0) | 355 | self.tap.seek(0) |
2304 | 406 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 356 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
2305 | 407 | self.assertEqual(0, result) | 357 | self.assertEqual(0, result) |
2315 | 408 | self.assertEqual([ | 358 | self.check_events([ |
2316 | 409 | 'test test 1', | 359 | ('status', 'test 1', 'success', None, False, 'tap comment', |
2317 | 410 | 'success test 1 [', | 360 | b'# comment', True, 'text/plain; charset=UTF8', None, None), |
2318 | 411 | '# comment', | 361 | ('status', 'test 2', 'success', None, False, None, None, True, |
2319 | 412 | ']', | 362 | None, None, None)]) |
2311 | 413 | 'test test 2', | ||
2312 | 414 | 'success test 2', | ||
2313 | 415 | ], | ||
2314 | 416 | self.subunit.getvalue().splitlines()) | ||
2320 | 417 | 363 | ||
2321 | 418 | def test_trailing_comments_are_included_in_last_test_log(self): | 364 | def test_trailing_comments_are_included_in_last_test_log(self): |
2322 | 419 | # A file | 365 | # A file |
2323 | @@ -422,21 +368,23 @@ | |||
2324 | 422 | # # comment | 368 | # # comment |
2325 | 423 | # results in a two tests, with the second having the comment | 369 | # results in a two tests, with the second having the comment |
2326 | 424 | # attached to its log. | 370 | # attached to its log. |
2330 | 425 | self.tap.write("ok\n") | 371 | self.tap.write(_u("ok\n")) |
2331 | 426 | self.tap.write("ok\n") | 372 | self.tap.write(_u("ok\n")) |
2332 | 427 | self.tap.write("# comment\n") | 373 | self.tap.write(_u("# comment\n")) |
2333 | 428 | self.tap.seek(0) | 374 | self.tap.seek(0) |
2334 | 429 | result = subunit.TAP2SubUnit(self.tap, self.subunit) | 375 | result = subunit.TAP2SubUnit(self.tap, self.subunit) |
2335 | 430 | self.assertEqual(0, result) | 376 | self.assertEqual(0, result) |
2345 | 431 | self.assertEqual([ | 377 | self.check_events([ |
2346 | 432 | 'test test 1', | 378 | ('status', 'test 1', 'success', None, False, None, None, True, |
2347 | 433 | 'success test 1', | 379 | None, None, None), |
2348 | 434 | 'test test 2', | 380 | ('status', 'test 2', 'success', None, False, 'tap comment', |
2349 | 435 | 'success test 2 [', | 381 | b'# comment', True, 'text/plain; charset=UTF8', None, None)]) |
2350 | 436 | '# comment', | 382 | |
2351 | 437 | ']', | 383 | def check_events(self, events): |
2352 | 438 | ], | 384 | self.subunit.seek(0) |
2353 | 439 | self.subunit.getvalue().splitlines()) | 385 | eventstream = StreamResult() |
2354 | 386 | subunit.ByteStreamToStreamResult(self.subunit).run(eventstream) | ||
2355 | 387 | self.assertEqual(events, eventstream._events) | ||
2356 | 440 | 388 | ||
2357 | 441 | 389 | ||
2358 | 442 | def test_suite(): | 390 | def test_suite(): |
2359 | 443 | 391 | ||
2360 | === added file 'python/subunit/tests/test_test_protocol2.py' | |||
2361 | --- python/subunit/tests/test_test_protocol2.py 1970-01-01 00:00:00 +0000 | |||
2362 | +++ python/subunit/tests/test_test_protocol2.py 2013-03-31 05:51:20 +0000 | |||
2363 | @@ -0,0 +1,415 @@ | |||
2364 | 1 | # | ||
2365 | 2 | # subunit: extensions to Python unittest to get test results from subprocesses. | ||
2366 | 3 | # Copyright (C) 2013 Robert Collins <robertc@robertcollins.net> | ||
2367 | 4 | # | ||
2368 | 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause | ||
2369 | 6 | # license at the users choice. A copy of both licenses are available in the | ||
2370 | 7 | # project source as Apache-2.0 and BSD. You may not use this file except in | ||
2371 | 8 | # compliance with one of these two licences. | ||
2372 | 9 | # | ||
2373 | 10 | # Unless required by applicable law or agreed to in writing, software | ||
2374 | 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
2375 | 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
2376 | 13 | # license you chose for the specific language governing permissions and | ||
2377 | 14 | # limitations under that license. | ||
2378 | 15 | # | ||
2379 | 16 | |||
2380 | 17 | from io import BytesIO | ||
2381 | 18 | import datetime | ||
2382 | 19 | |||
2383 | 20 | from testtools import TestCase | ||
2384 | 21 | from testtools.matchers import HasLength | ||
2385 | 22 | from testtools.tests.test_testresult import TestStreamResultContract | ||
2386 | 23 | from testtools.testresult.doubles import StreamResult | ||
2387 | 24 | |||
2388 | 25 | import subunit | ||
2389 | 26 | import subunit.iso8601 as iso8601 | ||
2390 | 27 | |||
2391 | 28 | CONSTANT_ENUM = b'\xb3)\x01\x0c\x03foo\x08U_\x1b' | ||
2392 | 29 | CONSTANT_INPROGRESS = b'\xb3)\x02\x0c\x03foo\x8e\xc1-\xb5' | ||
2393 | 30 | CONSTANT_SUCCESS = b'\xb3)\x03\x0c\x03fooE\x9d\xfe\x10' | ||
2394 | 31 | CONSTANT_UXSUCCESS = b'\xb3)\x04\x0c\x03fooX\x98\xce\xa8' | ||
2395 | 32 | CONSTANT_SKIP = b'\xb3)\x05\x0c\x03foo\x93\xc4\x1d\r' | ||
2396 | 33 | CONSTANT_FAIL = b'\xb3)\x06\x0c\x03foo\x15Po\xa3' | ||
2397 | 34 | CONSTANT_XFAIL = b'\xb3)\x07\x0c\x03foo\xde\x0c\xbc\x06' | ||
2398 | 35 | CONSTANT_EOF = b'\xb3!\x10\x08S\x15\x88\xdc' | ||
2399 | 36 | CONSTANT_FILE_CONTENT = b'\xb3!@\x13\x06barney\x03wooA5\xe3\x8c' | ||
2400 | 37 | CONSTANT_MIME = b'\xb3! #\x1aapplication/foo; charset=1x3Q\x15' | ||
2401 | 38 | CONSTANT_TIMESTAMP = b'\xb3+\x03\x13<\x17T\xcf\x80\xaf\xc8\x03barI\x96>-' | ||
2402 | 39 | CONSTANT_ROUTE_CODE = b'\xb3-\x03\x13\x03bar\x06source\x9cY9\x19' | ||
2403 | 40 | CONSTANT_RUNNABLE = b'\xb3(\x03\x0c\x03foo\xe3\xea\xf5\xa4' | ||
2404 | 41 | CONSTANT_TAGS = b'\xb3)\x80\x15\x03bar\x02\x03foo\x03barTHn\xb4' | ||
2405 | 42 | |||
2406 | 43 | |||
2407 | 44 | class TestStreamResultToBytesContract(TestCase, TestStreamResultContract): | ||
2408 | 45 | """Check that StreamResult behaves as testtools expects.""" | ||
2409 | 46 | |||
2410 | 47 | def _make_result(self): | ||
2411 | 48 | return subunit.StreamResultToBytes(BytesIO()) | ||
2412 | 49 | |||
2413 | 50 | |||
2414 | 51 | class TestStreamResultToBytes(TestCase): | ||
2415 | 52 | |||
2416 | 53 | def _make_result(self): | ||
2417 | 54 | output = BytesIO() | ||
2418 | 55 | return subunit.StreamResultToBytes(output), output | ||
2419 | 56 | |||
2420 | 57 | def test_numbers(self): | ||
2421 | 58 | result = subunit.StreamResultToBytes(BytesIO()) | ||
2422 | 59 | packet = [] | ||
2423 | 60 | self.assertRaises(Exception, result._write_number, -1, packet) | ||
2424 | 61 | self.assertEqual([], packet) | ||
2425 | 62 | result._write_number(0, packet) | ||
2426 | 63 | self.assertEqual([b'\x00'], packet) | ||
2427 | 64 | del packet[:] | ||
2428 | 65 | result._write_number(63, packet) | ||
2429 | 66 | self.assertEqual([b'\x3f'], packet) | ||
2430 | 67 | del packet[:] | ||
2431 | 68 | result._write_number(64, packet) | ||
2432 | 69 | self.assertEqual([b'\x40\x40'], packet) | ||
2433 | 70 | del packet[:] | ||
2434 | 71 | result._write_number(16383, packet) | ||
2435 | 72 | self.assertEqual([b'\x7f\xff'], packet) | ||
2436 | 73 | del packet[:] | ||
2437 | 74 | result._write_number(16384, packet) | ||
2438 | 75 | self.assertEqual([b'\x80\x40', b'\x00'], packet) | ||
2439 | 76 | del packet[:] | ||
2440 | 77 | result._write_number(4194303, packet) | ||
2441 | 78 | self.assertEqual([b'\xbf\xff', b'\xff'], packet) | ||
2442 | 79 | del packet[:] | ||
2443 | 80 | result._write_number(4194304, packet) | ||
2444 | 81 | self.assertEqual([b'\xc0\x40\x00\x00'], packet) | ||
2445 | 82 | del packet[:] | ||
2446 | 83 | result._write_number(1073741823, packet) | ||
2447 | 84 | self.assertEqual([b'\xff\xff\xff\xff'], packet) | ||
2448 | 85 | del packet[:] | ||
2449 | 86 | self.assertRaises(Exception, result._write_number, 1073741824, packet) | ||
2450 | 87 | self.assertEqual([], packet) | ||
2451 | 88 | |||
2452 | 89 | def test_volatile_length(self): | ||
2453 | 90 | # if the length of the packet data before the length itself is | ||
2454 | 91 | # considered is right on the boundary for length's variable length | ||
2455 | 92 | # encoding, it is easy to get the length wrong by not accounting for | ||
2456 | 93 | # length itself. | ||
2457 | 94 | # that is, the encoder has to ensure that length == sum (length_of_rest | ||
2458 | 95 | # + length_of_length) | ||
2459 | 96 | result, output = self._make_result() | ||
2460 | 97 | # 1 byte short: | ||
2461 | 98 | result.status(file_name="", file_bytes=b'\xff'*0) | ||
2462 | 99 | self.assertThat(output.getvalue(), HasLength(10)) | ||
2463 | 100 | self.assertEqual(b'\x0a', output.getvalue()[3:4]) | ||
2464 | 101 | output.seek(0) | ||
2465 | 102 | output.truncate() | ||
2466 | 103 | # 1 byte long: | ||
2467 | 104 | result.status(file_name="", file_bytes=b'\xff'*53) | ||
2468 | 105 | self.assertThat(output.getvalue(), HasLength(63)) | ||
2469 | 106 | self.assertEqual(b'\x3f', output.getvalue()[3:4]) | ||
2470 | 107 | output.seek(0) | ||
2471 | 108 | output.truncate() | ||
2472 | 109 | # 2 bytes short | ||
2473 | 110 | result.status(file_name="", file_bytes=b'\xff'*54) | ||
2474 | 111 | self.assertThat(output.getvalue(), HasLength(65)) | ||
2475 | 112 | self.assertEqual(b'\x40\x41', output.getvalue()[3:5]) | ||
2476 | 113 | output.seek(0) | ||
2477 | 114 | output.truncate() | ||
2478 | 115 | # 2 bytes long | ||
2479 | 116 | result.status(file_name="", file_bytes=b'\xff'*16371) | ||
2480 | 117 | self.assertThat(output.getvalue(), HasLength(16383)) | ||
2481 | 118 | self.assertEqual(b'\x7f\xff', output.getvalue()[3:5]) | ||
2482 | 119 | output.seek(0) | ||
2483 | 120 | output.truncate() | ||
2484 | 121 | # 3 bytes short | ||
2485 | 122 | result.status(file_name="", file_bytes=b'\xff'*16372) | ||
2486 | 123 | self.assertThat(output.getvalue(), HasLength(16385)) | ||
2487 | 124 | self.assertEqual(b'\x80\x40\x01', output.getvalue()[3:6]) | ||
2488 | 125 | output.seek(0) | ||
2489 | 126 | output.truncate() | ||
2490 | 127 | # 3 bytes long | ||
2491 | 128 | result.status(file_name="", file_bytes=b'\xff'*4194289) | ||
2492 | 129 | self.assertThat(output.getvalue(), HasLength(4194303)) | ||
2493 | 130 | self.assertEqual(b'\xbf\xff\xff', output.getvalue()[3:6]) | ||
2494 | 131 | output.seek(0) | ||
2495 | 132 | output.truncate() | ||
2496 | 133 | self.assertRaises(Exception, result.status, file_name="", | ||
2497 | 134 | file_bytes=b'\xff'*4194290) | ||
2498 | 135 | |||
2499 | 136 | def test_trivial_enumeration(self): | ||
2500 | 137 | result, output = self._make_result() | ||
2501 | 138 | result.status("foo", 'exists') | ||
2502 | 139 | self.assertEqual(CONSTANT_ENUM, output.getvalue()) | ||
2503 | 140 | |||
2504 | 141 | def test_inprogress(self): | ||
2505 | 142 | result, output = self._make_result() | ||
2506 | 143 | result.status("foo", 'inprogress') | ||
2507 | 144 | self.assertEqual(CONSTANT_INPROGRESS, output.getvalue()) | ||
2508 | 145 | |||
2509 | 146 | def test_success(self): | ||
2510 | 147 | result, output = self._make_result() | ||
2511 | 148 | result.status("foo", 'success') | ||
2512 | 149 | self.assertEqual(CONSTANT_SUCCESS, output.getvalue()) | ||
2513 | 150 | |||
2514 | 151 | def test_uxsuccess(self): | ||
2515 | 152 | result, output = self._make_result() | ||
2516 | 153 | result.status("foo", 'uxsuccess') | ||
2517 | 154 | self.assertEqual(CONSTANT_UXSUCCESS, output.getvalue()) | ||
2518 | 155 | |||
2519 | 156 | def test_skip(self): | ||
2520 | 157 | result, output = self._make_result() | ||
2521 | 158 | result.status("foo", 'skip') | ||
2522 | 159 | self.assertEqual(CONSTANT_SKIP, output.getvalue()) | ||
2523 | 160 | |||
2524 | 161 | def test_fail(self): | ||
2525 | 162 | result, output = self._make_result() | ||
2526 | 163 | result.status("foo", 'fail') | ||
2527 | 164 | self.assertEqual(CONSTANT_FAIL, output.getvalue()) | ||
2528 | 165 | |||
2529 | 166 | def test_xfail(self): | ||
2530 | 167 | result, output = self._make_result() | ||
2531 | 168 | result.status("foo", 'xfail') | ||
2532 | 169 | self.assertEqual(CONSTANT_XFAIL, output.getvalue()) | ||
2533 | 170 | |||
2534 | 171 | def test_unknown_status(self): | ||
2535 | 172 | result, output = self._make_result() | ||
2536 | 173 | self.assertRaises(Exception, result.status, "foo", 'boo') | ||
2537 | 174 | self.assertEqual(b'', output.getvalue()) | ||
2538 | 175 | |||
2539 | 176 | def test_eof(self): | ||
2540 | 177 | result, output = self._make_result() | ||
2541 | 178 | result.status(eof=True) | ||
2542 | 179 | self.assertEqual(CONSTANT_EOF, output.getvalue()) | ||
2543 | 180 | |||
2544 | 181 | def test_file_content(self): | ||
2545 | 182 | result, output = self._make_result() | ||
2546 | 183 | result.status(file_name="barney", file_bytes=b"woo") | ||
2547 | 184 | self.assertEqual(CONSTANT_FILE_CONTENT, output.getvalue()) | ||
2548 | 185 | |||
2549 | 186 | def test_mime(self): | ||
2550 | 187 | result, output = self._make_result() | ||
2551 | 188 | result.status(mime_type="application/foo; charset=1") | ||
2552 | 189 | self.assertEqual(CONSTANT_MIME, output.getvalue()) | ||
2553 | 190 | |||
2554 | 191 | def test_route_code(self): | ||
2555 | 192 | result, output = self._make_result() | ||
2556 | 193 | result.status(test_id="bar", test_status='success', | ||
2557 | 194 | route_code="source") | ||
2558 | 195 | self.assertEqual(CONSTANT_ROUTE_CODE, output.getvalue()) | ||
2559 | 196 | |||
2560 | 197 | def test_runnable(self): | ||
2561 | 198 | result, output = self._make_result() | ||
2562 | 199 | result.status("foo", 'success', runnable=False) | ||
2563 | 200 | self.assertEqual(CONSTANT_RUNNABLE, output.getvalue()) | ||
2564 | 201 | |||
2565 | 202 | def test_tags(self): | ||
2566 | 203 | result, output = self._make_result() | ||
2567 | 204 | result.status(test_id="bar", test_tags=set(['foo', 'bar'])) | ||
2568 | 205 | self.assertEqual(CONSTANT_TAGS, output.getvalue()) | ||
2569 | 206 | |||
2570 | 207 | def test_timestamp(self): | ||
2571 | 208 | timestamp = datetime.datetime(2001, 12, 12, 12, 59, 59, 45, | ||
2572 | 209 | iso8601.Utc()) | ||
2573 | 210 | result, output = self._make_result() | ||
2574 | 211 | result.status(test_id="bar", test_status='success', timestamp=timestamp) | ||
2575 | 212 | self.assertEqual(CONSTANT_TIMESTAMP, output.getvalue()) | ||
2576 | 213 | |||
2577 | 214 | |||
2578 | 215 | class TestByteStreamToStreamResult(TestCase): | ||
2579 | 216 | |||
2580 | 217 | def test_non_subunit_encapsulated(self): | ||
2581 | 218 | source = BytesIO(b"foo\nbar\n") | ||
2582 | 219 | result = StreamResult() | ||
2583 | 220 | subunit.ByteStreamToStreamResult( | ||
2584 | 221 | source, non_subunit_name="stdout").run(result) | ||
2585 | 222 | self.assertEqual([ | ||
2586 | 223 | ('status', None, None, None, True, 'stdout', b'f', False, None, None, None), | ||
2587 | 224 | ('status', None, None, None, True, 'stdout', b'o', False, None, None, None), | ||
2588 | 225 | ('status', None, None, None, True, 'stdout', b'o', False, None, None, None), | ||
2589 | 226 | ('status', None, None, None, True, 'stdout', b'\n', False, None, None, None), | ||
2590 | 227 | ('status', None, None, None, True, 'stdout', b'b', False, None, None, None), | ||
2591 | 228 | ('status', None, None, None, True, 'stdout', b'a', False, None, None, None), | ||
2592 | 229 | ('status', None, None, None, True, 'stdout', b'r', False, None, None, None), | ||
2593 | 230 | ('status', None, None, None, True, 'stdout', b'\n', False, None, None, None), | ||
2594 | 231 | ], result._events) | ||
2595 | 232 | self.assertEqual(b'', source.read()) | ||
2596 | 233 | |||
2597 | 234 | def test_signature_middle_utf8_char(self): | ||
2598 | 235 | utf8_bytes = b'\xe3\xb3\x8a' | ||
2599 | 236 | source = BytesIO(utf8_bytes) | ||
2600 | 237 | # Should be treated as one character (it is u'\u3cca') and wrapped | ||
2601 | 238 | result = StreamResult() | ||
2602 | 239 | subunit.ByteStreamToStreamResult( | ||
2603 | 240 | source, non_subunit_name="stdout").run( | ||
2604 | 241 | result) | ||
2605 | 242 | self.assertEqual([ | ||
2606 | 243 | ('status', None, None, None, True, 'stdout', b'\xe3', False, None, None, None), | ||
2607 | 244 | ('status', None, None, None, True, 'stdout', b'\xb3', False, None, None, None), | ||
2608 | 245 | ('status', None, None, None, True, 'stdout', b'\x8a', False, None, None, None), | ||
2609 | 246 | ], result._events) | ||
2610 | 247 | |||
2611 | 248 | def test_non_subunit_disabled_raises(self): | ||
2612 | 249 | source = BytesIO(b"foo\nbar\n") | ||
2613 | 250 | result = StreamResult() | ||
2614 | 251 | case = subunit.ByteStreamToStreamResult(source) | ||
2615 | 252 | e = self.assertRaises(Exception, case.run, result) | ||
2616 | 253 | self.assertEqual(b'f', e.args[1]) | ||
2617 | 254 | self.assertEqual(b'oo\nbar\n', source.read()) | ||
2618 | 255 | self.assertEqual([], result._events) | ||
2619 | 256 | |||
2620 | 257 | def test_trivial_enumeration(self): | ||
2621 | 258 | source = BytesIO(CONSTANT_ENUM) | ||
2622 | 259 | result = StreamResult() | ||
2623 | 260 | subunit.ByteStreamToStreamResult( | ||
2624 | 261 | source, non_subunit_name="stdout").run(result) | ||
2625 | 262 | self.assertEqual(b'', source.read()) | ||
2626 | 263 | self.assertEqual([ | ||
2627 | 264 | ('status', 'foo', 'exists', None, True, None, None, False, None, None, None), | ||
2628 | 265 | ], result._events) | ||
2629 | 266 | |||
2630 | 267 | def test_multiple_events(self): | ||
2631 | 268 | source = BytesIO(CONSTANT_ENUM + CONSTANT_ENUM) | ||
2632 | 269 | result = StreamResult() | ||
2633 | 270 | subunit.ByteStreamToStreamResult( | ||
2634 | 271 | source, non_subunit_name="stdout").run(result) | ||
2635 | 272 | self.assertEqual(b'', source.read()) | ||
2636 | 273 | self.assertEqual([ | ||
2637 | 274 | ('status', 'foo', 'exists', None, True, None, None, False, None, None, None), | ||
2638 | 275 | ('status', 'foo', 'exists', None, True, None, None, False, None, None, None), | ||
2639 | 276 | ], result._events) | ||
2640 | 277 | |||
2641 | 278 | def test_inprogress(self): | ||
2642 | 279 | self.check_event(CONSTANT_INPROGRESS, 'inprogress') | ||
2643 | 280 | |||
2644 | 281 | def test_success(self): | ||
2645 | 282 | self.check_event(CONSTANT_SUCCESS, 'success') | ||
2646 | 283 | |||
2647 | 284 | def test_uxsuccess(self): | ||
2648 | 285 | self.check_event(CONSTANT_UXSUCCESS, 'uxsuccess') | ||
2649 | 286 | |||
2650 | 287 | def test_skip(self): | ||
2651 | 288 | self.check_event(CONSTANT_SKIP, 'skip') | ||
2652 | 289 | |||
2653 | 290 | def test_fail(self): | ||
2654 | 291 | self.check_event(CONSTANT_FAIL, 'fail') | ||
2655 | 292 | |||
2656 | 293 | def test_xfail(self): | ||
2657 | 294 | self.check_event(CONSTANT_XFAIL, 'xfail') | ||
2658 | 295 | |||
2659 | 296 | def check_events(self, source_bytes, events): | ||
2660 | 297 | source = BytesIO(source_bytes) | ||
2661 | 298 | result = StreamResult() | ||
2662 | 299 | subunit.ByteStreamToStreamResult( | ||
2663 | 300 | source, non_subunit_name="stdout").run(result) | ||
2664 | 301 | self.assertEqual(b'', source.read()) | ||
2665 | 302 | self.assertEqual(events, result._events) | ||
2666 | 303 | |||
2667 | 304 | def check_event(self, source_bytes, test_status=None, test_id="foo", | ||
2668 | 305 | route_code=None, timestamp=None, tags=None, mime_type=None, | ||
2669 | 306 | file_name=None, file_bytes=None, eof=False, runnable=True): | ||
2670 | 307 | event = self._event(test_id=test_id, test_status=test_status, | ||
2671 | 308 | tags=tags, runnable=runnable, file_name=file_name, | ||
2672 | 309 | file_bytes=file_bytes, eof=eof, mime_type=mime_type, | ||
2673 | 310 | route_code=route_code, timestamp=timestamp) | ||
2674 | 311 | self.check_events(source_bytes, [event]) | ||
2675 | 312 | |||
2676 | 313 | def _event(self, test_status=None, test_id=None, route_code=None, | ||
2677 | 314 | timestamp=None, tags=None, mime_type=None, file_name=None, | ||
2678 | 315 | file_bytes=None, eof=False, runnable=True): | ||
2679 | 316 | return ('status', test_id, test_status, tags, runnable, file_name, | ||
2680 | 317 | file_bytes, eof, mime_type, route_code, timestamp) | ||
2681 | 318 | |||
2682 | 319 | def test_eof(self): | ||
2683 | 320 | self.check_event(CONSTANT_EOF, test_id=None, eof=True) | ||
2684 | 321 | |||
2685 | 322 | def test_file_content(self): | ||
2686 | 323 | self.check_event(CONSTANT_FILE_CONTENT, | ||
2687 | 324 | test_id=None, file_name="barney", file_bytes=b"woo") | ||
2688 | 325 | |||
2689 | 326 | def test_file_content_length_into_checksum(self): | ||
2690 | 327 | # A bad file content length which creeps into the checksum. | ||
2691 | 328 | bad_file_length_content = b'\xb3!@\x13\x06barney\x04woo\xdc\xe2\xdb\x35' | ||
2692 | 329 | self.check_events(bad_file_length_content, [ | ||
2693 | 330 | self._event(test_id="subunit.parser", eof=True, | ||
2694 | 331 | file_name="Packet data", file_bytes=bad_file_length_content), | ||
2695 | 332 | self._event(test_id="subunit.parser", test_status="fail", eof=True, | ||
2696 | 333 | file_name="Parser Error", | ||
2697 | 334 | file_bytes=b"File content extends past end of packet: claimed 4 bytes, 3 available"), | ||
2698 | 335 | ]) | ||
2699 | 336 | |||
2700 | 337 | def test_packet_length_4_word_varint(self): | ||
2701 | 338 | packet_data = b'\xb3!@\xc0\x00\x11' | ||
2702 | 339 | self.check_events(packet_data, [ | ||
2703 | 340 | self._event(test_id="subunit.parser", eof=True, | ||
2704 | 341 | file_name="Packet data", file_bytes=packet_data), | ||
2705 | 342 | self._event(test_id="subunit.parser", test_status="fail", eof=True, | ||
2706 | 343 | file_name="Parser Error", | ||
2707 | 344 | file_bytes=b"3 byte maximum given but 4 byte value found."), | ||
2708 | 345 | ]) | ||
2709 | 346 | |||
2710 | 347 | def test_mime(self): | ||
2711 | 348 | self.check_event(CONSTANT_MIME, | ||
2712 | 349 | test_id=None, mime_type='application/foo; charset=1') | ||
2713 | 350 | |||
2714 | 351 | def test_route_code(self): | ||
2715 | 352 | self.check_event(CONSTANT_ROUTE_CODE, | ||
2716 | 353 | 'success', route_code="source", test_id="bar") | ||
2717 | 354 | |||
2718 | 355 | def test_runnable(self): | ||
2719 | 356 | self.check_event(CONSTANT_RUNNABLE, | ||
2720 | 357 | test_status='success', runnable=False) | ||
2721 | 358 | |||
2722 | 359 | def test_tags(self): | ||
2723 | 360 | self.check_event(CONSTANT_TAGS, | ||
2724 | 361 | None, tags=set(['foo', 'bar']), test_id="bar") | ||
2725 | 362 | |||
2726 | 363 | def test_timestamp(self): | ||
2727 | 364 | timestamp = datetime.datetime(2001, 12, 12, 12, 59, 59, 45, | ||
2728 | 365 | iso8601.Utc()) | ||
2729 | 366 | self.check_event(CONSTANT_TIMESTAMP, | ||
2730 | 367 | 'success', test_id='bar', timestamp=timestamp) | ||
2731 | 368 | |||
2732 | 369 | def test_bad_crc_errors_via_status(self): | ||
2733 | 370 | file_bytes = CONSTANT_MIME[:-1] + b'\x00' | ||
2734 | 371 | self.check_events( file_bytes, [ | ||
2735 | 372 | self._event(test_id="subunit.parser", eof=True, | ||
2736 | 373 | file_name="Packet data", file_bytes=file_bytes), | ||
2737 | 374 | self._event(test_id="subunit.parser", test_status="fail", eof=True, | ||
2738 | 375 | file_name="Parser Error", | ||
2739 | 376 | file_bytes=b'Bad checksum - calculated (0x78335115), ' | ||
2740 | 377 | b'stored (0x78335100)'), | ||
2741 | 378 | ]) | ||
2742 | 379 | |||
2743 | 380 | def test_not_utf8_in_string(self): | ||
2744 | 381 | file_bytes = CONSTANT_ROUTE_CODE[:5] + b'\xb4' + CONSTANT_ROUTE_CODE[6:-4] + b'\xce\x56\xc6\x17' | ||
2745 | 382 | self.check_events(file_bytes, [ | ||
2746 | 383 | self._event(test_id="subunit.parser", eof=True, | ||
2747 | 384 | file_name="Packet data", file_bytes=file_bytes), | ||
2748 | 385 | self._event(test_id="subunit.parser", test_status="fail", eof=True, | ||
2749 | 386 | file_name="Parser Error", | ||
2750 | 387 | file_bytes=b'UTF8 string at offset 2 is not UTF8'), | ||
2751 | 388 | ]) | ||
2752 | 389 | |||
2753 | 390 | def test_NULL_in_string(self): | ||
2754 | 391 | file_bytes = CONSTANT_ROUTE_CODE[:6] + b'\x00' + CONSTANT_ROUTE_CODE[7:-4] + b'\xd7\x41\xac\xfe' | ||
2755 | 392 | self.check_events(file_bytes, [ | ||
2756 | 393 | self._event(test_id="subunit.parser", eof=True, | ||
2757 | 394 | file_name="Packet data", file_bytes=file_bytes), | ||
2758 | 395 | self._event(test_id="subunit.parser", test_status="fail", eof=True, | ||
2759 | 396 | file_name="Parser Error", | ||
2760 | 397 | file_bytes=b'UTF8 string at offset 2 contains NUL byte'), | ||
2761 | 398 | ]) | ||
2762 | 399 | |||
2763 | 400 | def test_bad_utf8_stringlength(self): | ||
2764 | 401 | file_bytes = CONSTANT_ROUTE_CODE[:4] + b'\x3f' + CONSTANT_ROUTE_CODE[5:-4] + b'\xbe\x29\xe0\xc2' | ||
2765 | 402 | self.check_events(file_bytes, [ | ||
2766 | 403 | self._event(test_id="subunit.parser", eof=True, | ||
2767 | 404 | file_name="Packet data", file_bytes=file_bytes), | ||
2768 | 405 | self._event(test_id="subunit.parser", test_status="fail", eof=True, | ||
2769 | 406 | file_name="Parser Error", | ||
2770 | 407 | file_bytes=b'UTF8 string at offset 2 extends past end of ' | ||
2771 | 408 | b'packet: claimed 63 bytes, 10 available'), | ||
2772 | 409 | ]) | ||
2773 | 410 | |||
2774 | 411 | |||
2775 | 412 | def test_suite(): | ||
2776 | 413 | loader = subunit.tests.TestUtil.TestLoader() | ||
2777 | 414 | result = loader.loadTestsFromName(__name__) | ||
2778 | 415 | return result | ||
2779 | 0 | 416 | ||
2780 | === added file 'python/subunit/v2.py' | |||
2781 | --- python/subunit/v2.py 1970-01-01 00:00:00 +0000 | |||
2782 | +++ python/subunit/v2.py 2013-03-31 05:51:20 +0000 | |||
2783 | @@ -0,0 +1,458 @@ | |||
2784 | 1 | # | ||
2785 | 2 | # subunit: extensions to Python unittest to get test results from subprocesses. | ||
2786 | 3 | # Copyright (C) 2013 Robert Collins <robertc@robertcollins.net> | ||
2787 | 4 | # | ||
2788 | 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause | ||
2789 | 6 | # license at the users choice. A copy of both licenses are available in the | ||
2790 | 7 | # project source as Apache-2.0 and BSD. You may not use this file except in | ||
2791 | 8 | # compliance with one of these two licences. | ||
2792 | 9 | # | ||
2793 | 10 | # Unless required by applicable law or agreed to in writing, software | ||
2794 | 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
2795 | 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
2796 | 13 | # license you chose for the specific language governing permissions and | ||
2797 | 14 | # limitations under that license. | ||
2798 | 15 | # | ||
2799 | 16 | |||
2800 | 17 | import codecs | ||
2801 | 18 | import datetime | ||
2802 | 19 | from io import UnsupportedOperation | ||
2803 | 20 | import os | ||
2804 | 21 | import select | ||
2805 | 22 | import struct | ||
2806 | 23 | import zlib | ||
2807 | 24 | |||
2808 | 25 | from extras import safe_hasattr | ||
2809 | 26 | |||
2810 | 27 | import subunit | ||
2811 | 28 | import subunit.iso8601 as iso8601 | ||
2812 | 29 | |||
2813 | 30 | __all__ = [ | ||
2814 | 31 | 'ByteStreamToStreamResult', | ||
2815 | 32 | 'StreamResultToBytes', | ||
2816 | 33 | ] | ||
2817 | 34 | |||
2818 | 35 | SIGNATURE = b'\xb3' | ||
2819 | 36 | FMT_8 = '>B' | ||
2820 | 37 | FMT_16 = '>H' | ||
2821 | 38 | FMT_24 = '>HB' | ||
2822 | 39 | FMT_32 = '>I' | ||
2823 | 40 | FMT_TIMESTAMP = '>II' | ||
2824 | 41 | FLAG_TEST_ID = 0x0800 | ||
2825 | 42 | FLAG_ROUTE_CODE = 0x0400 | ||
2826 | 43 | FLAG_TIMESTAMP = 0x0200 | ||
2827 | 44 | FLAG_RUNNABLE = 0x0100 | ||
2828 | 45 | FLAG_TAGS = 0x0080 | ||
2829 | 46 | FLAG_MIME_TYPE = 0x0020 | ||
2830 | 47 | FLAG_EOF = 0x0010 | ||
2831 | 48 | FLAG_FILE_CONTENT = 0x0040 | ||
2832 | 49 | EPOCH = datetime.datetime.utcfromtimestamp(0).replace(tzinfo=iso8601.Utc()) | ||
2833 | 50 | NUL_ELEMENT = b'\0'[0] | ||
2834 | 51 | |||
2835 | 52 | |||
2836 | 53 | class ParseError(Exception): | ||
2837 | 54 | """Used to pass error messages within the parser.""" | ||
2838 | 55 | |||
2839 | 56 | |||
2840 | 57 | class StreamResultToBytes(object): | ||
2841 | 58 | """Convert StreamResult API calls to bytes. | ||
2842 | 59 | |||
2843 | 60 | The StreamResult API is defined by testtools.StreamResult. | ||
2844 | 61 | """ | ||
2845 | 62 | |||
2846 | 63 | status_mask = { | ||
2847 | 64 | None: 0, | ||
2848 | 65 | 'exists': 0x1, | ||
2849 | 66 | 'inprogress': 0x2, | ||
2850 | 67 | 'success': 0x3, | ||
2851 | 68 | 'uxsuccess': 0x4, | ||
2852 | 69 | 'skip': 0x5, | ||
2853 | 70 | 'fail': 0x6, | ||
2854 | 71 | 'xfail': 0x7, | ||
2855 | 72 | } | ||
2856 | 73 | |||
2857 | 74 | zero_b = b'\0'[0] | ||
2858 | 75 | |||
2859 | 76 | def __init__(self, output_stream): | ||
2860 | 77 | """Create a StreamResultToBytes with output written to output_stream. | ||
2861 | 78 | |||
2862 | 79 | :param output_stream: A file-like object. Must support write(bytes) | ||
2863 | 80 | and flush() methods. Flush will be called after each write. | ||
2864 | 81 | The stream will be passed through subunit.make_stream_binary, | ||
2865 | 82 | to handle regular cases such as stdout. | ||
2866 | 83 | """ | ||
2867 | 84 | self.output_stream = subunit.make_stream_binary(output_stream) | ||
2868 | 85 | |||
2869 | 86 | def startTestRun(self): | ||
2870 | 87 | pass | ||
2871 | 88 | |||
2872 | 89 | def stopTestRun(self): | ||
2873 | 90 | pass | ||
2874 | 91 | |||
2875 | 92 | def status(self, test_id=None, test_status=None, test_tags=None, | ||
2876 | 93 | runnable=True, file_name=None, file_bytes=None, eof=False, | ||
2877 | 94 | mime_type=None, route_code=None, timestamp=None): | ||
2878 | 95 | self._write_packet(test_id=test_id, test_status=test_status, | ||
2879 | 96 | test_tags=test_tags, runnable=runnable, file_name=file_name, | ||
2880 | 97 | file_bytes=file_bytes, eof=eof, mime_type=mime_type, | ||
2881 | 98 | route_code=route_code, timestamp=timestamp) | ||
2882 | 99 | |||
2883 | 100 | def _write_utf8(self, a_string, packet): | ||
2884 | 101 | utf8 = a_string.encode('utf-8') | ||
2885 | 102 | self._write_number(len(utf8), packet) | ||
2886 | 103 | packet.append(utf8) | ||
2887 | 104 | |||
2888 | 105 | def _write_len16(self, length, packet): | ||
2889 | 106 | assert length < 65536 | ||
2890 | 107 | packet.append(struct.pack(FMT_16, length)) | ||
2891 | 108 | |||
2892 | 109 | def _write_number(self, value, packet): | ||
2893 | 110 | packet.extend(self._encode_number(value)) | ||
2894 | 111 | |||
2895 | 112 | def _encode_number(self, value): | ||
2896 | 113 | assert value >= 0 | ||
2897 | 114 | if value < 64: | ||
2898 | 115 | return [struct.pack(FMT_8, value)] | ||
2899 | 116 | elif value < 16384: | ||
2900 | 117 | value = value | 0x4000 | ||
2901 | 118 | return [struct.pack(FMT_16, value)] | ||
2902 | 119 | elif value < 4194304: | ||
2903 | 120 | value = value | 0x800000 | ||
2904 | 121 | return [struct.pack(FMT_16, value >> 8), | ||
2905 | 122 | struct.pack(FMT_8, value & 0xff)] | ||
2906 | 123 | elif value < 1073741824: | ||
2907 | 124 | value = value | 0xc0000000 | ||
2908 | 125 | return [struct.pack(FMT_32, value)] | ||
2909 | 126 | else: | ||
2910 | 127 | raise ValueError('value too large to encode: %r' % (value,)) | ||
2911 | 128 | |||
2912 | 129 | def _write_packet(self, test_id=None, test_status=None, test_tags=None, | ||
2913 | 130 | runnable=True, file_name=None, file_bytes=None, eof=False, | ||
2914 | 131 | mime_type=None, route_code=None, timestamp=None): | ||
2915 | 132 | packet = [SIGNATURE] | ||
2916 | 133 | packet.append(b'FF') # placeholder for flags | ||
2917 | 134 | # placeholder for length, but see below as length is variable. | ||
2918 | 135 | packet.append(b'') | ||
2919 | 136 | flags = 0x2000 # Version 0x2 | ||
2920 | 137 | if timestamp is not None: | ||
2921 | 138 | flags = flags | FLAG_TIMESTAMP | ||
2922 | 139 | since_epoch = timestamp - EPOCH | ||
2923 | 140 | nanoseconds = since_epoch.microseconds * 1000 | ||
2924 | 141 | seconds = (since_epoch.seconds + since_epoch.days * 24 * 3600) | ||
2925 | 142 | packet.append(struct.pack(FMT_32, seconds)) | ||
2926 | 143 | self._write_number(nanoseconds, packet) | ||
2927 | 144 | if test_id is not None: | ||
2928 | 145 | flags = flags | FLAG_TEST_ID | ||
2929 | 146 | self._write_utf8(test_id, packet) | ||
2930 | 147 | if test_tags: | ||
2931 | 148 | flags = flags | FLAG_TAGS | ||
2932 | 149 | self._write_number(len(test_tags), packet) | ||
2933 | 150 | for tag in test_tags: | ||
2934 | 151 | self._write_utf8(tag, packet) | ||
2935 | 152 | if runnable: | ||
2936 | 153 | flags = flags | FLAG_RUNNABLE | ||
2937 | 154 | if mime_type: | ||
2938 | 155 | flags = flags | FLAG_MIME_TYPE | ||
2939 | 156 | self._write_utf8(mime_type, packet) | ||
2940 | 157 | if file_name is not None: | ||
2941 | 158 | flags = flags | FLAG_FILE_CONTENT | ||
2942 | 159 | self._write_utf8(file_name, packet) | ||
2943 | 160 | self._write_number(len(file_bytes), packet) | ||
2944 | 161 | packet.append(file_bytes) | ||
2945 | 162 | if eof: | ||
2946 | 163 | flags = flags | FLAG_EOF | ||
2947 | 164 | if route_code is not None: | ||
2948 | 165 | flags = flags | FLAG_ROUTE_CODE | ||
2949 | 166 | self._write_utf8(route_code, packet) | ||
2950 | 167 | # 0x0008 - not used in v2. | ||
2951 | 168 | flags = flags | self.status_mask[test_status] | ||
2952 | 169 | packet[1] = struct.pack(FMT_16, flags) | ||
2953 | 170 | base_length = sum(map(len, packet)) + 4 | ||
2954 | 171 | if base_length <= 62: | ||
2955 | 172 | # one byte to encode length, 62+1 = 63 | ||
2956 | 173 | length_length = 1 | ||
2957 | 174 | elif base_length <= 16381: | ||
2958 | 175 | # two bytes to encode length, 16381+2 = 16383 | ||
2959 | 176 | length_length = 2 | ||
2960 | 177 | elif base_length <= 4194300: | ||
2961 | 178 | # three bytes to encode length, 419430+3=4194303 | ||
2962 | 179 | length_length = 3 | ||
2963 | 180 | else: | ||
2964 | 181 | # Longer than policy: | ||
2965 | 182 | # TODO: chunk the packet automatically? | ||
2966 | 183 | # - strip all but file data | ||
2967 | 184 | # - do 4M chunks of that till done | ||
2968 | 185 | # - include original data in final chunk. | ||
2969 | 186 | raise ValueError("Length too long: %r" % base_length) | ||
2970 | 187 | packet[2:3] = self._encode_number(base_length + length_length) | ||
2971 | 188 | # We could either do a partial application of crc32 over each chunk | ||
2972 | 189 | # or a single join to a temp variable then a final join | ||
2973 | 190 | # or two writes (that python might then split). | ||
2974 | 191 | # For now, simplest code: join, crc32, join, output | ||
2975 | 192 | content = b''.join(packet) | ||
2976 | 193 | self.output_stream.write(content + struct.pack( | ||
2977 | 194 | FMT_32, zlib.crc32(content) & 0xffffffff)) | ||
2978 | 195 | self.output_stream.flush() | ||
2979 | 196 | |||
2980 | 197 | |||
2981 | 198 | class ByteStreamToStreamResult(object): | ||
2982 | 199 | """Parse a subunit byte stream. | ||
2983 | 200 | |||
2984 | 201 | Mixed streams that contain non-subunit content is supported when a | ||
2985 | 202 | non_subunit_name is passed to the contructor. The default is to raise an | ||
2986 | 203 | error containing the non-subunit byte after it has been read from the | ||
2987 | 204 | stream. | ||
2988 | 205 | |||
2989 | 206 | Typical use: | ||
2990 | 207 | |||
2991 | 208 | >>> case = ByteStreamToStreamResult(sys.stdin.buffer) | ||
2992 | 209 | >>> result = StreamResult() | ||
2993 | 210 | >>> result.startTestRun() | ||
2994 | 211 | >>> case.run(result) | ||
2995 | 212 | >>> result.stopTestRun() | ||
2996 | 213 | """ | ||
2997 | 214 | |||
2998 | 215 | status_lookup = { | ||
2999 | 216 | 0x0: None, | ||
3000 | 217 | 0x1: 'exists', | ||
3001 | 218 | 0x2: 'inprogress', | ||
3002 | 219 | 0x3: 'success', | ||
3003 | 220 | 0x4: 'uxsuccess', | ||
3004 | 221 | 0x5: 'skip', | ||
3005 | 222 | 0x6: 'fail', | ||
3006 | 223 | 0x7: 'xfail', | ||
3007 | 224 | } | ||
3008 | 225 | |||
3009 | 226 | def __init__(self, source, non_subunit_name=None): | ||
3010 | 227 | """Create a ByteStreamToStreamResult. | ||
3011 | 228 | |||
3012 | 229 | :param source: A file like object to read bytes from. Must support | ||
3013 | 230 | read(<count>) and return bytes. The file is not closed by | ||
3014 | 231 | ByteStreamToStreamResult. subunit.make_stream_binary() is | ||
3015 | 232 | called on the stream to get it into bytes mode. | ||
3016 | 233 | :param non_subunit_name: If set to non-None, non subunit content | ||
3017 | 234 | encountered in the stream will be converted into file packets | ||
3018 | 235 | labelled with this name. | ||
3019 | 236 | """ | ||
3020 | 237 | self.non_subunit_name = non_subunit_name | ||
3021 | 238 | self.source = subunit.make_stream_binary(source) | ||
3022 | 239 | self.codec = codecs.lookup('utf8').incrementaldecoder() | ||
3023 | 240 | |||
3024 | 241 | def run(self, result): | ||
3025 | 242 | """Parse source and emit events to result. | ||
3026 | 243 | |||
3027 | 244 | This is a blocking call: it will run until EOF is detected on source. | ||
3028 | 245 | """ | ||
3029 | 246 | self.codec.reset() | ||
3030 | 247 | mid_character = False | ||
3031 | 248 | while True: | ||
3032 | 249 | # We're in blocking mode; read one char | ||
3033 | 250 | content = self.source.read(1) | ||
3034 | 251 | if not content: | ||
3035 | 252 | # EOF | ||
3036 | 253 | return | ||
3037 | 254 | if not mid_character and content[0] == SIGNATURE[0]: | ||
3038 | 255 | self._parse_packet(result) | ||
3039 | 256 | continue | ||
3040 | 257 | if self.non_subunit_name is None: | ||
3041 | 258 | raise Exception("Non subunit content", content) | ||
3042 | 259 | try: | ||
3043 | 260 | if self.codec.decode(content): | ||
3044 | 261 | # End of a character | ||
3045 | 262 | mid_character = False | ||
3046 | 263 | else: | ||
3047 | 264 | mid_character = True | ||
3048 | 265 | except UnicodeDecodeError: | ||
3049 | 266 | # Bad unicode, not our concern. | ||
3050 | 267 | mid_character = False | ||
3051 | 268 | # Aggregate all content that is not subunit until either | ||
3052 | 269 | # 1MiB is accumulated or 50ms has passed with no input. | ||
3053 | 270 | # Both are arbitrary amounts intended to give a simple | ||
3054 | 271 | # balance between efficiency (avoiding death by a thousand | ||
3055 | 272 | # one-byte packets), buffering (avoiding overlarge state | ||
3056 | 273 | # being hidden on intermediary nodes) and interactivity | ||
3057 | 274 | # (when driving a debugger, slow response to typing is | ||
3058 | 275 | # annoying). | ||
3059 | 276 | buffered = [content] | ||
3060 | 277 | while len(buffered[-1]): | ||
3061 | 278 | try: | ||
3062 | 279 | self.source.fileno() | ||
3063 | 280 | except: | ||
3064 | 281 | # Won't be able to select, fallback to | ||
3065 | 282 | # one-byte-at-a-time. | ||
3066 | 283 | break | ||
3067 | 284 | # Note: this has a very low timeout because with stdin, the | ||
3068 | 285 | # BufferedIO layer typically has all the content available | ||
3069 | 286 | # from the stream when e.g. pdb is dropped into, leading to | ||
3070 | 287 | # select always timing out when in fact we could have read | ||
3071 | 288 | # (from the buffer layer) - we typically fail to aggregate | ||
3072 | 289 | # any content on 3.x Pythons. | ||
3073 | 290 | readable = select.select([self.source], [], [], 0.000001)[0] | ||
3074 | 291 | if readable: | ||
3075 | 292 | content = self.source.read(1) | ||
3076 | 293 | if not len(content): | ||
3077 | 294 | # EOF - break and emit buffered. | ||
3078 | 295 | break | ||
3079 | 296 | if not mid_character and content[0] == SIGNATURE[0]: | ||
3080 | 297 | # New packet, break, emit buffered, then parse. | ||
3081 | 298 | break | ||
3082 | 299 | buffered.append(content) | ||
3083 | 300 | # Feed into the codec. | ||
3084 | 301 | try: | ||
3085 | 302 | if self.codec.decode(content): | ||
3086 | 303 | # End of a character | ||
3087 | 304 | mid_character = False | ||
3088 | 305 | else: | ||
3089 | 306 | mid_character = True | ||
3090 | 307 | except UnicodeDecodeError: | ||
3091 | 308 | # Bad unicode, not our concern. | ||
3092 | 309 | mid_character = False | ||
3093 | 310 | if not readable or len(buffered) >= 1048576: | ||
3094 | 311 | # timeout or too much data, emit what we have. | ||
3095 | 312 | break | ||
3096 | 313 | result.status( | ||
3097 | 314 | file_name=self.non_subunit_name, | ||
3098 | 315 | file_bytes=b''.join(buffered)) | ||
3099 | 316 | if mid_character or not len(content) or content[0] != SIGNATURE[0]: | ||
3100 | 317 | continue | ||
3101 | 318 | # Otherwise, parse a data packet. | ||
3102 | 319 | self._parse_packet(result) | ||
3103 | 320 | |||
3104 | 321 | def _parse_packet(self, result): | ||
3105 | 322 | try: | ||
3106 | 323 | packet = [SIGNATURE] | ||
3107 | 324 | self._parse(packet, result) | ||
3108 | 325 | except ParseError as error: | ||
3109 | 326 | result.status(test_id="subunit.parser", eof=True, | ||
3110 | 327 | file_name="Packet data", file_bytes=b''.join(packet)) | ||
3111 | 328 | result.status(test_id="subunit.parser", test_status='fail', | ||
3112 | 329 | eof=True, file_name="Parser Error", | ||
3113 | 330 | file_bytes=(error.args[0]).encode('utf8')) | ||
3114 | 331 | |||
3115 | 332 | def _parse_varint(self, data, pos, max_3_bytes=False): | ||
3116 | 333 | # because the only incremental IO we do is at the start, and the 32 bit | ||
3117 | 334 | # CRC means we can always safely read enough to cover any varint, we | ||
3118 | 335 | # can be sure that there should be enough data - and if not it is an | ||
3119 | 336 | # error not a normal situation. | ||
3120 | 337 | data_0 = struct.unpack(FMT_8, data[pos:pos+1])[0] | ||
3121 | 338 | typeenum = data_0 & 0xc0 | ||
3122 | 339 | value_0 = data_0 & 0x3f | ||
3123 | 340 | if typeenum == 0x00: | ||
3124 | 341 | return value_0, 1 | ||
3125 | 342 | elif typeenum == 0x40: | ||
3126 | 343 | data_1 = struct.unpack(FMT_8, data[pos+1:pos+2])[0] | ||
3127 | 344 | return (value_0 << 8) | data_1, 2 | ||
3128 | 345 | elif typeenum == 0x80: | ||
3129 | 346 | data_1 = struct.unpack(FMT_16, data[pos+1:pos+3])[0] | ||
3130 | 347 | return (value_0 << 16) | data_1, 3 | ||
3131 | 348 | else: | ||
3132 | 349 | if max_3_bytes: | ||
3133 | 350 | raise ParseError('3 byte maximum given but 4 byte value found.') | ||
3134 | 351 | data_1, data_2 = struct.unpack(FMT_24, data[pos+1:pos+4]) | ||
3135 | 352 | result = (value_0 << 24) | data_1 << 8 | data_2 | ||
3136 | 353 | return result, 4 | ||
3137 | 354 | |||
3138 | 355 | def _parse(self, packet, result): | ||
3139 | 356 | # 2 bytes flags, at most 3 bytes length. | ||
3140 | 357 | packet.append(self.source.read(5)) | ||
3141 | 358 | flags = struct.unpack(FMT_16, packet[-1][:2])[0] | ||
3142 | 359 | length, consumed = self._parse_varint( | ||
3143 | 360 | packet[-1], 2, max_3_bytes=True) | ||
3144 | 361 | remainder = self.source.read(length - 6) | ||
3145 | 362 | if len(remainder) != length - 6: | ||
3146 | 363 | raise ParseError( | ||
3147 | 364 | 'Short read - got %d bytes, wanted %d bytes' % ( | ||
3148 | 365 | len(remainder), length - 6)) | ||
3149 | 366 | if consumed != 3: | ||
3150 | 367 | # Avoid having to parse torn values | ||
3151 | 368 | packet[-1] += remainder | ||
3152 | 369 | pos = 2 + consumed | ||
3153 | 370 | else: | ||
3154 | 371 | # Avoid copying potentially lots of data. | ||
3155 | 372 | packet.append(remainder) | ||
3156 | 373 | pos = 0 | ||
3157 | 374 | crc = zlib.crc32(packet[0]) | ||
3158 | 375 | for fragment in packet[1:-1]: | ||
3159 | 376 | crc = zlib.crc32(fragment, crc) | ||
3160 | 377 | crc = zlib.crc32(packet[-1][:-4], crc) & 0xffffffff | ||
3161 | 378 | packet_crc = struct.unpack(FMT_32, packet[-1][-4:])[0] | ||
3162 | 379 | if crc != packet_crc: | ||
3163 | 380 | # Bad CRC, report it and stop parsing the packet. | ||
3164 | 381 | raise ParseError( | ||
3165 | 382 | 'Bad checksum - calculated (0x%x), stored (0x%x)' | ||
3166 | 383 | % (crc, packet_crc)) | ||
3167 | 384 | if safe_hasattr(__builtins__, 'memoryview'): | ||
3168 | 385 | body = memoryview(packet[-1]) | ||
3169 | 386 | else: | ||
3170 | 387 | body = packet[-1] | ||
3171 | 388 | # Discard CRC-32 | ||
3172 | 389 | body = body[:-4] | ||
3173 | 390 | # One packet could have both file and status data; the Python API | ||
3174 | 391 | # presents these separately (perhaps it shouldn't?) | ||
3175 | 392 | if flags & FLAG_TIMESTAMP: | ||
3176 | 393 | seconds = struct.unpack(FMT_32, body[pos:pos+4])[0] | ||
3177 | 394 | nanoseconds, consumed = self._parse_varint(body, pos+4) | ||
3178 | 395 | pos = pos + 4 + consumed | ||
3179 | 396 | timestamp = EPOCH + datetime.timedelta( | ||
3180 | 397 | seconds=seconds, microseconds=nanoseconds/1000) | ||
3181 | 398 | else: | ||
3182 | 399 | timestamp = None | ||
3183 | 400 | if flags & FLAG_TEST_ID: | ||
3184 | 401 | test_id, pos = self._read_utf8(body, pos) | ||
3185 | 402 | else: | ||
3186 | 403 | test_id = None | ||
3187 | 404 | if flags & FLAG_TAGS: | ||
3188 | 405 | tag_count, consumed = self._parse_varint(body, pos) | ||
3189 | 406 | pos += consumed | ||
3190 | 407 | test_tags = set() | ||
3191 | 408 | for _ in range(tag_count): | ||
3192 | 409 | tag, pos = self._read_utf8(body, pos) | ||
3193 | 410 | test_tags.add(tag) | ||
3194 | 411 | else: | ||
3195 | 412 | test_tags = None | ||
3196 | 413 | if flags & FLAG_MIME_TYPE: | ||
3197 | 414 | mime_type, pos = self._read_utf8(body, pos) | ||
3198 | 415 | else: | ||
3199 | 416 | mime_type = None | ||
3200 | 417 | if flags & FLAG_FILE_CONTENT: | ||
3201 | 418 | file_name, pos = self._read_utf8(body, pos) | ||
3202 | 419 | content_length, consumed = self._parse_varint(body, pos) | ||
3203 | 420 | pos += consumed | ||
3204 | 421 | file_bytes = body[pos:pos+content_length] | ||
3205 | 422 | if len(file_bytes) != content_length: | ||
3206 | 423 | raise ParseError('File content extends past end of packet: ' | ||
3207 | 424 | 'claimed %d bytes, %d available' % ( | ||
3208 | 425 | content_length, len(file_bytes))) | ||
3209 | 426 | else: | ||
3210 | 427 | file_name = None | ||
3211 | 428 | file_bytes = None | ||
3212 | 429 | if flags & FLAG_ROUTE_CODE: | ||
3213 | 430 | route_code, pos = self._read_utf8(body, pos) | ||
3214 | 431 | else: | ||
3215 | 432 | route_code = None | ||
3216 | 433 | runnable = bool(flags & FLAG_RUNNABLE) | ||
3217 | 434 | eof = bool(flags & FLAG_EOF) | ||
3218 | 435 | test_status = self.status_lookup[flags & 0x0007] | ||
3219 | 436 | result.status(test_id=test_id, test_status=test_status, | ||
3220 | 437 | test_tags=test_tags, runnable=runnable, mime_type=mime_type, | ||
3221 | 438 | eof=eof, file_name=file_name, file_bytes=file_bytes, | ||
3222 | 439 | route_code=route_code, timestamp=timestamp) | ||
3223 | 440 | __call__ = run | ||
3224 | 441 | |||
3225 | 442 | def _read_utf8(self, buf, pos): | ||
3226 | 443 | length, consumed = self._parse_varint(buf, pos) | ||
3227 | 444 | pos += consumed | ||
3228 | 445 | utf8_bytes = buf[pos:pos+length] | ||
3229 | 446 | if length != len(utf8_bytes): | ||
3230 | 447 | raise ParseError( | ||
3231 | 448 | 'UTF8 string at offset %d extends past end of packet: ' | ||
3232 | 449 | 'claimed %d bytes, %d available' % (pos - 2, length, | ||
3233 | 450 | len(utf8_bytes))) | ||
3234 | 451 | if NUL_ELEMENT in utf8_bytes: | ||
3235 | 452 | raise ParseError('UTF8 string at offset %d contains NUL byte' % ( | ||
3236 | 453 | pos-2,)) | ||
3237 | 454 | try: | ||
3238 | 455 | return utf8_bytes.decode('utf-8'), length+pos | ||
3239 | 456 | except UnicodeDecodeError: | ||
3240 | 457 | raise ParseError('UTF8 string at offset %d is not UTF8' % (pos-2,)) | ||
3241 | 458 | |||
3242 | 0 | 459 | ||
3243 | === modified file 'setup.py' | |||
3244 | --- setup.py 2012-12-17 08:12:44 +0000 | |||
3245 | +++ setup.py 2013-03-31 05:51:20 +0000 | |||
3246 | @@ -9,6 +9,7 @@ | |||
3247 | 9 | else: | 9 | else: |
3248 | 10 | extra = { | 10 | extra = { |
3249 | 11 | 'install_requires': [ | 11 | 'install_requires': [ |
3250 | 12 | 'extras', | ||
3251 | 12 | 'testtools>=0.9.23', | 13 | 'testtools>=0.9.23', |
3252 | 13 | ] | 14 | ] |
3253 | 14 | } | 15 | } |
3254 | @@ -49,6 +50,8 @@ | |||
3255 | 49 | packages=['subunit', 'subunit.tests'], | 50 | packages=['subunit', 'subunit.tests'], |
3256 | 50 | package_dir={'subunit': 'python/subunit'}, | 51 | package_dir={'subunit': 'python/subunit'}, |
3257 | 51 | scripts = [ | 52 | scripts = [ |
3258 | 53 | 'filters/subunit-1to2', | ||
3259 | 54 | 'filters/subunit-2to1', | ||
3260 | 52 | 'filters/subunit2gtk', | 55 | 'filters/subunit2gtk', |
3261 | 53 | 'filters/subunit2junitxml', | 56 | 'filters/subunit2junitxml', |
3262 | 54 | 'filters/subunit2pyunit', | 57 | 'filters/subunit2pyunit', |