python-subunit
Advanced tools
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| # Please see the documentation for all configuration options: | ||
| # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates | ||
| version: 2 | ||
| updates: | ||
| - package-ecosystem: "github-actions" | ||
| directory: "/" | ||
| schedule: | ||
| interval: weekly | ||
| - package-ecosystem: "pip" | ||
| directory: "/" | ||
| schedule: | ||
| interval: weekly |
| name: Tests | ||
| on: | ||
| push: | ||
| branches: [master] | ||
| pull_request: | ||
| branches: [master] | ||
| jobs: | ||
| tests: | ||
| name: tests-python${{ matrix.python-version }}-${{ matrix.os }} | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| matrix: | ||
| python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "pypy3.10"] | ||
| os: ["ubuntu-latest"] | ||
| steps: | ||
| - uses: actions/checkout@v5 | ||
| - name: Set up Python ${{ matrix.python-version }} | ||
| uses: actions/setup-python@v6 | ||
| with: | ||
| python-version: ${{ matrix.python-version }} | ||
| - name: Install Deps | ||
| run: sudo apt-get install check libcppunit-dev | ||
| - name: Install package | ||
| run: | | ||
| python -m pip install ruff | ||
| python -m pip install -U '.[test,docs]' | ||
| - name: Build | ||
| run: autoreconf -fi && ./configure && make | ||
| - name: Run ruff check | ||
| run: ruff check python | ||
| - name: Format | ||
| run: ruff format --check . | ||
| - name: Run make check | ||
| run: make check | ||
| - name: Run make distcheck | ||
| run: make distcheck | ||
| # Disabled; needs a tuit or two | ||
| # - name: Docs build | ||
| # run: md2html.py README.md README.html | ||
| # if: "matrix.python-version == '3.8'" | ||
| # - name: Docs build | ||
| # run: md2html README.md README.html | ||
| # if: "matrix.python-version != '3.8'" |
+35
| # | ||
| # subunit: extensions to Python unittest to get test results from subprocesses. | ||
| # Copyright (C) 2013 Robert Collins <robertc@robertcollins.net> | ||
| # | ||
| # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause | ||
| # license at the users choice. A copy of both licenses are available in the | ||
| # project source as Apache-2.0 and BSD. You may not use this file except in | ||
| # compliance with one of these two licences. | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
| # license you chose for the specific language governing permissions and | ||
| # limitations under that license. | ||
| # | ||
| import unittest | ||
| import subunit | ||
| class ShellTests(subunit.ExecTestCase): | ||
| def test_sourcing(self): | ||
| """./shell/tests/test_source_library.sh""" | ||
| def test_functions(self): | ||
| """./shell/tests/test_function_output.sh""" | ||
| def test_suite(): | ||
| result = unittest.TestSuite() | ||
| result.addTest(subunit.test_suite()) | ||
| result.addTest(ShellTests("test_sourcing")) | ||
| result.addTest(ShellTests("test_functions")) | ||
| return result |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| tag-name = "$VERSION" | ||
| news-file = "NEWS" | ||
| verify-command = "autoreconf -i && ./configure && make distcheck" | ||
| tarball-location = [] | ||
| release-timeout = 5 | ||
| [[update_version]] | ||
| path = "configure.ac" | ||
| new-line = "m4_define([SUBUNIT_MAJOR_VERSION], [$MAJOR_VERSION])" | ||
| [[update_version]] | ||
| path = "configure.ac" | ||
| new-line = "m4_define([SUBUNIT_MINOR_VERSION], [$MINOR_VERSION])" | ||
| [[update_version]] | ||
| path = "configure.ac" | ||
| new-line = "m4_define([SUBUNIT_MICRO_VERSION], [$MICRO_VERSION])" | ||
| [[update_version]] | ||
| path = "python/subunit/__init__.py" | ||
| new-line = "__version__ = $STATUS_TUPLED_VERSION" | ||
| [launchpad] | ||
| project = "subunit" |
| [console_scripts] | ||
| subunit-1to2 = subunit.filter_scripts.subunit_1to2:main | ||
| subunit-2to1 = subunit.filter_scripts.subunit_2to1:main | ||
| subunit-filter = subunit.filter_scripts.subunit_filter:main | ||
| subunit-ls = subunit.filter_scripts.subunit_ls:main | ||
| subunit-notify = subunit.filter_scripts.subunit_notify:main | ||
| subunit-output = subunit.filter_scripts.subunit_output:main | ||
| subunit-stats = subunit.filter_scripts.subunit_stats:main | ||
| subunit-tags = subunit.filter_scripts.subunit_tags:main | ||
| subunit2csv = subunit.filter_scripts.subunit2csv:main | ||
| subunit2disk = subunit.filter_scripts.subunit2disk:main | ||
| subunit2gtk = subunit.filter_scripts.subunit2gtk:main | ||
| subunit2junitxml = subunit.filter_scripts.subunit2junitxml:main | ||
| subunit2pyunit = subunit.filter_scripts.subunit2pyunit:main | ||
| tap2subunit = subunit.filter_scripts.tap2subunit:main |
| Metadata-Version: 2.4 | ||
| Name: python-subunit | ||
| Version: 1.4.5 | ||
| Summary: Python implementation of subunit test streaming protocol | ||
| Author-email: Robert Collins <subunit-dev@lists.launchpad.net> | ||
| License: Apache-2.0 or BSD | ||
| Project-URL: Bug Tracker, https://bugs.launchpad.net/subunit | ||
| Project-URL: Homepage, http://launchpad.net/subunit | ||
| Project-URL: Source Code, https://github.com/testing-cabal/subunit/ | ||
| Keywords: python,streaming,test | ||
| Classifier: Intended Audience :: Developers | ||
| Classifier: Operating System :: OS Independent | ||
| Classifier: Programming Language :: Python :: 3.10 | ||
| Classifier: Programming Language :: Python :: 3.11 | ||
| Classifier: Programming Language :: Python :: 3.12 | ||
| Classifier: Programming Language :: Python :: 3.13 | ||
| Classifier: Programming Language :: Python :: 3.14 | ||
| Classifier: Programming Language :: Python | ||
| Classifier: Topic :: Software Development :: Testing | ||
| Requires-Python: >=3.10 | ||
| Description-Content-Type: text/markdown | ||
| License-File: COPYING | ||
| Requires-Dist: iso8601 | ||
| Requires-Dist: testtools>=2.7 | ||
| Provides-Extra: docs | ||
| Requires-Dist: docutils; extra == "docs" | ||
| Provides-Extra: test | ||
| Requires-Dist: fixtures; extra == "test" | ||
| Requires-Dist: hypothesis; extra == "test" | ||
| Requires-Dist: testscenarios; extra == "test" | ||
| Dynamic: license-file | ||
| subunit: A streaming protocol for test results | ||
| Copyright (C) 2005-2013 Robert Collins <robertc@robertcollins.net> | ||
| Licensed under either the Apache License, Version 2.0 or the BSD 3-clause | ||
| license at the users choice. A copy of both licenses are available in the | ||
| project source as Apache-2.0 and BSD. You may not use this file except in | ||
| compliance with one of these two licences. | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
| license you chose for the specific language governing permissions and | ||
| limitations under that license. | ||
| See the COPYING file for full details on the licensing of Subunit. | ||
| # Subunit | ||
| Subunit is a streaming protocol for test results. | ||
| There are two major revisions of the protocol. Version 1 was trivially human | ||
| readable but had significant defects as far as highly parallel testing was | ||
| concerned - it had no room for doing discovery and execution in parallel, | ||
| required substantial buffering when multiplexing and was fragile - a corrupt | ||
| byte could cause an entire stream to be misparsed. Version 1.1 added | ||
| encapsulation of binary streams which mitigated some of the issues but the | ||
| core remained. | ||
| Version 2 shares many of the good characteristics of Version 1 - it can be | ||
| embedded into a regular text stream (e.g. from a build system) and it still | ||
| models xUnit style test execution. It also fixes many of the issues with | ||
| Version 1 - Version 2 can be multiplexed without excessive buffering (in | ||
| time or space), it has a well defined recovery mechanism for dealing with | ||
| corrupted streams (e.g. where two processes write to the same stream | ||
| concurrently, or where the stream generator suffers a bug). | ||
| More details on both protocol version s can be found in the 'Protocol' section | ||
| of this document. | ||
| Subunit comes with command line filters to process a subunit stream and | ||
| language bindings for python, C, C++ and shell. Bindings are easy to write | ||
| for other languages. | ||
| A number of useful things can be done easily with subunit: | ||
| * Test aggregation: Tests run separately can be combined and then | ||
| reported/displayed together. For instance, tests from different languages | ||
| can be shown as a seamless whole, and tests running on multiple machines | ||
| can be aggregated into a single stream through a multiplexer. | ||
| * Test archiving: A test run may be recorded and replayed later. | ||
| * Test isolation: Tests that may crash or otherwise interact badly with each | ||
| other can be run separately and then aggregated, rather than interfering | ||
| with each other or requiring an adhoc test->runner reporting protocol. | ||
| * Grid testing: subunit can act as the necessary serialisation and | ||
| deserialiation to get test runs on distributed machines to be reported in | ||
| real time. | ||
| Subunit supplies the following filters: | ||
| * tap2subunit - convert perl's TestAnythingProtocol to subunit. | ||
| * subunit2csv - convert a subunit stream to csv. | ||
| * subunit2disk - export a subunit stream to files on disk. | ||
| * subunit2pyunit - convert a subunit stream to pyunit test results. | ||
| * subunit2gtk - show a subunit stream in GTK. | ||
| * subunit2junitxml - convert a subunit stream to JUnit's XML format. | ||
| * subunit-diff - compare two subunit streams. | ||
| * subunit-filter - filter out tests from a subunit stream. | ||
| * subunit-ls - list info about tests present in a subunit stream. | ||
| * subunit-stats - generate a summary of a subunit stream. | ||
| * subunit-tags - add or remove tags from a stream. | ||
| ## Integration with other tools | ||
| Subunit's language bindings act as integration with various test runners like | ||
| 'check', 'cppunit', Python's 'unittest'. Beyond that a small amount of glue | ||
| (typically a few lines) will allow Subunit to be used in more sophisticated | ||
| ways. | ||
| ### Python | ||
| Subunit has excellent Python support: most of the filters and tools are written | ||
| in python and there are facilities for using Subunit to increase test isolation | ||
| seamlessly within a test suite. | ||
| The most common way is to run an existing python test suite and have it output | ||
| subunit via the ``subunit.run`` module:: | ||
| $ python -m subunit.run mypackage.tests.test_suite | ||
| For more information on the Python support Subunit offers , please see | ||
| ``pydoc subunit``, or the source in ``python/subunit/`` | ||
| ### Rust | ||
| There is a rust crate [subunit](https://crates.io/crates/subunit) that can be used to parse v1 and v1, and emit v2 subunit streams. | ||
| ### C | ||
| Subunit has C bindings to emit the protocol. The 'check' C unit testing project | ||
| has included subunit support in their project for some years now. See | ||
| 'c/README' for more details. | ||
| ### C++ | ||
| The C library is includable and usable directly from C++. A TestListener for | ||
| CPPUnit is included in the Subunit distribution. See 'c++/README' for details. | ||
| ### shell | ||
| There are two sets of shell tools. There are filters, which accept a subunit | ||
| stream on stdin and output processed data (or a transformed stream) on stdout. | ||
| Then there are unittest facilities similar to those for C : shell bindings | ||
| consisting of simple functions to output protocol elements, and a patch for | ||
| adding subunit output to the 'ShUnit' shell test runner. See 'shell/README' for | ||
| details. | ||
| #### Filter recipes | ||
| To ignore some failing tests whose root cause is already known:: | ||
| subunit-filter --without 'AttributeError.*flavor' | ||
| ## The xUnit test model | ||
| Subunit implements a slightly modified xUnit test model. The stock standard | ||
| model is that there are tests, which have an id(), can be run, and when run | ||
| start, emit an outcome (like success or failure) and then finish. | ||
| Subunit extends this with the idea of test enumeration (find out about tests | ||
| a runner has without running them), tags (allow users to describe tests in | ||
| ways the test framework doesn't apply any semantic value to), file attachments | ||
| (allow arbitrary data to make analysing a failure easy) and timestamps. | ||
| ## The protocol | ||
| Version 2, or v2 is new and still under development, but is intended to | ||
| supersede version 1 in the very near future. Subunit's bundled tools accept | ||
| only version 2 and only emit version 2, but the new filters subunit-1to2 and | ||
| subunit-2to1 can be used to interoperate with older third party libraries. | ||
| ### Version 2 | ||
| Version 2 is a binary protocol consisting of independent packets that can be | ||
| embedded in the output from tools like make - as long as each packet has no | ||
| other bytes mixed in with it (which 'make -j N>1' has a tendency of doing). | ||
| Version 2 is currently in draft form, and early adopters should be willing | ||
| to either discard stored results (if protocol changes are made), or bulk | ||
| convert them back to v1 and then to a newer edition of v2. | ||
| The protocol synchronises at the start of the stream, after a packet, or | ||
| after any 0x0A byte. That is, a subunit v2 packet starts after a newline or | ||
| directly after the end of the prior packet. | ||
| Subunit is intended to be transported over a reliable streaming protocol such | ||
| as TCP. As such it does not concern itself with out of order delivery of | ||
| packets. However, because of the possibility of corruption due to either | ||
| bugs in the sender, or due to mixed up data from concurrent writes to the same | ||
| fd when being embedded, subunit strives to recover reasonably gracefully from | ||
| damaged data. | ||
| A key design goal for Subunit version 2 is to allow processing and multiplexing | ||
| without forcing buffering for semantic correctness, as buffering tends to hide | ||
| hung or otherwise misbehaving tests. That said, limited time based buffering | ||
| for network efficiency is a good idea - this is ultimately implementer | ||
| choice. Line buffering is also discouraged for subunit streams, as dropping | ||
| into a debugger or other tool may require interactive traffic even if line | ||
| buffering would not otherwise be a problem. | ||
| In version two there are two conceptual events - a test status event and a file | ||
| attachment event. Events may have timestamps, and the path of multiplexers that | ||
| an event is routed through is recorded to permit sending actions back to the | ||
| source (such as new tests to run or stdin for driving debuggers and other | ||
| interactive input). Test status events are used to enumerate tests, to report | ||
| tests and test helpers as they run. Tests may have tags, used to allow | ||
| tunnelling extra meanings through subunit without requiring parsing of | ||
| arbitrary file attachments. Things that are not standalone tests get marked | ||
| as such by setting the 'Runnable' flag to false. (For instance, individual | ||
| assertions in TAP are not runnable tests, only the top level TAP test script | ||
| is runnable). | ||
| File attachments are used to provide rich detail about the nature of a failure. | ||
| File attachments can also be used to encapsulate stdout and stderr both during | ||
| and outside tests. | ||
| Most numbers are stored in network byte order - Most Significant Byte first | ||
| encoded using a variation of http://www.dlugosz.com/ZIP2/VLI.html. The first | ||
| byte's top 2 high order bits encode the total number of octets in the number. | ||
| This encoding can encode values from 0 to 2**30-1, enough to encode a | ||
| nanosecond. Numbers that are not variable length encoded are still stored in | ||
| MSB order. | ||
| +--------+--------+---------+------------+ | ||
| | prefix | octets | max | max | | ||
| +========+========+=========+============+ | ||
| | 00 | 1 | 2**6-1 | 63 | | ||
| +--------+--------+---------+------------+ | ||
| | 01 | 2 | 2**14-1 | 16383 | | ||
| +--------+--------+---------+------------+ | ||
| | 10 | 3 | 2**22-1 | 4194303 | | ||
| +--------+--------+---------+------------+ | ||
| | 11 | 4 | 2**30-1 | 1073741823 | | ||
| +--------+--------+---------+------------+ | ||
| All variable length elements of the packet are stored with a length prefix | ||
| number allowing them to be skipped over for consumers that don't need to | ||
| interpret them. | ||
| UTF-8 strings are with no terminating NUL and should not have any embedded NULs | ||
| (implementations SHOULD validate any such strings that they process and take | ||
| some remedial action (such as discarding the packet as corrupt). | ||
| In short the structure of a packet is: | ||
| PACKET := SIGNATURE FLAGS PACKET_LENGTH TIMESTAMP? TESTID? TAGS? MIME? | ||
| FILECONTENT? ROUTING_CODE? CRC32 | ||
| In more detail... | ||
| Packets are identified by a single byte signature - 0xB3, which is never legal | ||
| in a UTF-8 stream as the first byte of a character. 0xB3 starts with the first | ||
| bit set and the second not, which is the UTF-8 signature for a continuation | ||
| byte. 0xB3 was chosen as 0x73 ('s' in ASCII') with the top two bits replaced by | ||
| the 1 and 0 for a continuation byte. | ||
| If subunit packets are being embedded in a non-UTF-8 text stream, where 0x73 is | ||
| a legal character, consider either recoding the text to UTF-8, or using | ||
| subunit's 'file' packets to embed the text stream in subunit, rather than the | ||
| other way around. | ||
| Following the signature byte comes a 16-bit flags field, which includes a | ||
| 4-bit version field - if the version is not 0x2 then the packet cannot be | ||
| read. It is recommended to signal an error at this point (e.g. by emitting | ||
| a synthetic error packet and returning to the top level loop to look for | ||
| new packets, or exiting with an error). If recovery is desired, treat the | ||
| packet signature as an opaque byte and scan for a new synchronisation point. | ||
| NB: Subunit V1 and V2 packets may legitimately included 0xB3 internally, | ||
| as they are an 8-bit safe container format, so recovery from this situation | ||
| may involve an arbitrary number of false positives until an actual packet | ||
| is encountered : and even then it may still be false, failing after passing | ||
| the version check due to coincidence. | ||
| Flags are stored in network byte order too. | ||
| ```asciiart | ||
| +------------+------------+------------------------+ | ||
| | High byte | Low byte | | ||
| +------------+------------+------------------------+ | ||
| | 15 14 13 12 11 10 9 8 | 7 6 5 4 3 2 1 0 | | ||
| +------------+------------+------------------------+ | ||
| | VERSION | feature bits | | ||
| +------------+-------------------------------------+ | ||
| ``` | ||
| Valid version values are: | ||
| `0x2` - version 2 | ||
| Feature bits: | ||
| | Bit position | Mask | Description| | ||
| |---------|-------------|---------------------------| | ||
| | Bit 11 | `0x0800` | Test id present. | | ||
| | Bit 10 | `0x0400` | Routing code present. | | ||
| | Bit 9 | `0x0200` | Timestamp present. | | ||
| | Bit 8 | `0x0100` | Test is 'runnable'. | | ||
| | Bit 7 | `0x0080` | Tags are present. | | ||
| | Bit 6 | `0x0040` | File content is present. | | ||
| | Bit 5 | `0x0020` | File MIME type is present.| | ||
| | Bit 4 | `0x0010` | EOF marker. | | ||
| | Bit 3 | `0x0008` | Must be zero in version 2.| | ||
| Test status gets three bits: | ||
| Bit 2 | Bit 1 | Bit 0 - mask 0x0007 - A test status enum lookup: | ||
| * 000 - undefined / no test | ||
| * 001 - Enumeration / existence | ||
| * 002 - In progress | ||
| * 003 - Success | ||
| * 004 - Unexpected Success | ||
| * 005 - Skipped | ||
| * 006 - Failed | ||
| * 007 - Expected failure | ||
| After the flags field is a number field giving the length in bytes for the | ||
| entire packet including the signature and the checksum. This length must | ||
| be less than 4MiB - 4194303 bytes. The encoding can obviously record a larger | ||
| number but one of the goals is to avoid requiring large buffers, or causing | ||
| large latency in the packet forward/processing pipeline. Larger file | ||
| attachments can be communicated in multiple packets, and the overhead in such a | ||
| 4MiB packet is approximately 0.2%. | ||
| The rest of the packet is a series of optional features as specified by the set | ||
| feature bits in the flags field. When absent they are entirely absent. | ||
| Forwarding and multiplexing of packets can be done without interpreting the | ||
| remainder of the packet until the routing code and checksum (which are both at | ||
| the end of the packet). Additionally, routers can often avoid copying or moving | ||
| the bulk of the packet, as long as the routing code size increase doesn't force | ||
| the length encoding to take up a new byte (which will only happen to packets | ||
| less than or equal to 16KiB in length) - large packets are very efficient to | ||
| route. | ||
| Timestamp when present is a 32 bit unsigned integer for seconds, and a variable | ||
| length number for nanoseconds, representing UTC time since Unix Epoch in | ||
| seconds and nanoseconds. | ||
| Test id when present is a UTF-8 string. The test id should uniquely identify | ||
| runnable tests such that they can be selected individually. For tests and other | ||
| actions which cannot be individually run (such as test | ||
| fixtures/layers/subtests) uniqueness is not required (though being human | ||
| meaningful is highly recommended). | ||
| Tags when present is a length prefixed vector of UTF-8 strings, one per tag. | ||
| There are no restrictions on tag content (other than the restrictions on UTF-8 | ||
| strings in subunit in general). Tags have no ordering. | ||
| When a MIME type is present, it defines the MIME type for the file across all | ||
| packets same file (routing code + testid + name uniquely identifies a file, | ||
| reset when EOF is flagged). If a file never has a MIME type set, it should be | ||
| treated as application/octet-stream. | ||
| File content when present is a UTF-8 string for the name followed by the length | ||
| in bytes of the content, and then the content octets. | ||
| If present routing code is a UTF-8 string. The routing code is used to | ||
| determine which test backend a test was running on when doing data analysis, | ||
| and to route stdin to the test process if interaction is required. | ||
| Multiplexers SHOULD add a routing code if none is present, and prefix any | ||
| existing routing code with a routing code ('/' separated) if one is already | ||
| present. For example, a multiplexer might label each stream it is multiplexing | ||
| with a simple ordinal ('0', '1' etc), and given an incoming packet with route | ||
| code '3' from stream '0' would adjust the route code when forwarding the packet | ||
| to be '0/3'. | ||
| Following the end of the packet is a CRC-32 checksum of the contents of the | ||
| packet including the signature. | ||
| #### Example packets | ||
| Trivial test "foo" enumeration packet, with test id, runnable set, | ||
| status=enumeration. Spaces below are to visually break up signature / flags / | ||
| length / testid / crc32 | ||
| b3 2901 0c 03666f6f 08555f1b | ||
| ### Version 1 (and 1.1) | ||
| Version 1 (and 1.1) are mostly human readable protocols. | ||
| #### Sample subunit wire contents | ||
| The following: | ||
| ```subunit | ||
| test: test foo works | ||
| success: test foo works | ||
| test: tar a file. | ||
| failure: tar a file. [ | ||
| .. | ||
| ].. space is eaten. | ||
| foo.c:34 WARNING foo is not defined. | ||
| ] | ||
| a writeln to stdout | ||
| When run through subunit2pyunit:: | ||
| .F | ||
| a writeln to stdout | ||
| ======================== | ||
| FAILURE: tar a file. | ||
| ------------------- | ||
| .. | ||
| ].. space is eaten. | ||
| foo.c:34 WARNING foo is not defined. | ||
| ``` | ||
| #### Subunit v1 protocol description | ||
| This description is being ported to an EBNF style. Currently its only partly in | ||
| that style, but should be fairly clear all the same. When in doubt, refer the | ||
| source (and ideally help fix up the description!). Generally the protocol is | ||
| line orientated and consists of either directives and their parameters, or | ||
| when outside a DETAILS region unexpected lines which are not interpreted by | ||
| the parser - they should be forwarded unaltered: | ||
| ```ebnf | ||
| test|testing|test:|testing: test LABEL | ||
| success|success:|successful|successful: test LABEL | ||
| success|success:|successful|successful: test LABEL DETAILS | ||
| failure: test LABEL | ||
| failure: test LABEL DETAILS | ||
| error: test LABEL | ||
| error: test LABEL DETAILS | ||
| skip[:] test LABEL | ||
| skip[:] test LABEL DETAILS | ||
| xfail[:] test LABEL | ||
| xfail[:] test LABEL DETAILS | ||
| uxsuccess[:] test LABEL | ||
| uxsuccess[:] test LABEL DETAILS | ||
| progress: [+|-]X | ||
| progress: push | ||
| progress: pop | ||
| tags: [-]TAG ... | ||
| time: YYYY-MM-DD HH:MM:SSZ | ||
| LABEL: UTF8* | ||
| NAME: UTF8* | ||
| DETAILS ::= BRACKETED | MULTIPART | ||
| BRACKETED ::= '[' LF UTF8-lines ']' LF | ||
| MULTIPART ::= '[ multipart' LF PART* ']' LF | ||
| PART ::= PART_TYPE LF NAME LF PART_BYTES LF | ||
| PART_TYPE ::= Content-Type: type/sub-type(;parameter=value,parameter=value) | ||
| PART_BYTES ::= (DIGITS CR LF BYTE{DIGITS})* '0' CR LF | ||
| LF ::= '\n' | ||
| CR ::= '\r' | ||
| ``` | ||
| unexpected output on stdout -> stdout. | ||
| exit w/0 or last test completing -> error | ||
| Tags given outside a test are applied to all following tests | ||
| Tags given after a test: line and before the result line for the same test | ||
| apply only to that test, and inherit the current global tags. | ||
| A '-' before a tag is used to remove tags - e.g. to prevent a global tag | ||
| applying to a single test, or to cancel a global tag. | ||
| The progress directive is used to provide progress information about a stream | ||
| so that stream consumer can provide completion estimates, progress bars and so | ||
| on. Stream generators that know how many tests will be present in the stream | ||
| should output "progress: COUNT". Stream filters that add tests should output | ||
| "progress: +COUNT", and those that remove tests should output | ||
| "progress: -COUNT". An absolute count should reset the progress indicators in | ||
| use - it indicates that two separate streams from different generators have | ||
| been trivially concatenated together, and there is no knowledge of how many | ||
| more complete streams are incoming. Smart concatenation could scan each stream | ||
| for their count and sum them, or alternatively translate absolute counts into | ||
| relative counts inline. It is recommended that outputters avoid absolute counts | ||
| unless necessary. The push and pop directives are used to provide local regions | ||
| for progress reporting. This fits with hierarchically operating test | ||
| environments - such as those that organise tests into suites - the top-most | ||
| runner can report on the number of suites, and each suite surround its output | ||
| with a (push, pop) pair. Interpreters should interpret a pop as also advancing | ||
| the progress of the restored level by one step. Encountering progress | ||
| directives between the start and end of a test pair indicates that a previous | ||
| test was interrupted and did not cleanly terminate: it should be implicitly | ||
| closed with an error (the same as when a stream ends with no closing test | ||
| directive for the most recently started test). | ||
| The time directive acts as a clock event - it sets the time for all future | ||
| events. The value should be a valid ISO8601 time. | ||
| The skip, xfail and uxsuccess outcomes are not supported by all testing | ||
| environments. In Python the testttools (https://launchpad.net/testtools) | ||
| library is used to translate these automatically if an older Python version | ||
| that does not support them is in use. See the testtools documentation for the | ||
| translation policy. | ||
| skip is used to indicate a test was discovered but not executed. xfail is used | ||
| to indicate a test that errored in some expected fashion (also know as "TODO" | ||
| tests in some frameworks). uxsuccess is used to indicate and unexpected success | ||
| where a test though to be failing actually passes. It is complementary to | ||
| xfail. | ||
| ## Hacking on subunit | ||
| ### Releases | ||
| * Update versions in configure.ac and python/subunit/__init__.py. | ||
| * Update NEWS. | ||
| * Do a make distcheck, which will update Makefile etc. | ||
| * Do a PyPI release: PYTHONPATH=python python setup.py sdist bdist_wheel; twine upload -s dist/* | ||
| * Upload the regular one to LP. | ||
| * Push a tagged commit. | ||
| git push -t origin master:master |
| iso8601 | ||
| testtools>=2.7 | ||
| [docs] | ||
| docutils | ||
| [test] | ||
| fixtures | ||
| hypothesis | ||
| testscenarios |
| .codespellrc | ||
| .git-blame-ignore-revs | ||
| Apache-2.0 | ||
| BSD | ||
| COPYING | ||
| MANIFEST.in | ||
| NEWS | ||
| README.md | ||
| all_tests.py | ||
| disperse.toml | ||
| pyproject.toml | ||
| setup.py | ||
| .github/dependabot.yaml | ||
| .github/workflows/main.yml | ||
| python/python_subunit.egg-info/PKG-INFO | ||
| python/python_subunit.egg-info/SOURCES.txt | ||
| python/python_subunit.egg-info/dependency_links.txt | ||
| python/python_subunit.egg-info/entry_points.txt | ||
| python/python_subunit.egg-info/requires.txt | ||
| python/python_subunit.egg-info/top_level.txt | ||
| python/subunit/__init__.py | ||
| python/subunit/_output.py | ||
| python/subunit/_to_disk.py | ||
| python/subunit/chunked.py | ||
| python/subunit/details.py | ||
| python/subunit/filters.py | ||
| python/subunit/progress_model.py | ||
| python/subunit/run.py | ||
| python/subunit/test_results.py | ||
| python/subunit/v2.py | ||
| python/subunit/filter_scripts/__init__.py | ||
| python/subunit/filter_scripts/subunit2csv.py | ||
| python/subunit/filter_scripts/subunit2disk.py | ||
| python/subunit/filter_scripts/subunit2gtk.py | ||
| python/subunit/filter_scripts/subunit2junitxml.py | ||
| python/subunit/filter_scripts/subunit2pyunit.py | ||
| python/subunit/filter_scripts/subunit_1to2.py | ||
| python/subunit/filter_scripts/subunit_2to1.py | ||
| python/subunit/filter_scripts/subunit_filter.py | ||
| python/subunit/filter_scripts/subunit_ls.py | ||
| python/subunit/filter_scripts/subunit_notify.py | ||
| python/subunit/filter_scripts/subunit_output.py | ||
| python/subunit/filter_scripts/subunit_stats.py | ||
| python/subunit/filter_scripts/subunit_tags.py | ||
| python/subunit/filter_scripts/tap2subunit.py | ||
| python/subunit/tests/__init__.py | ||
| python/subunit/tests/sample-script.py | ||
| python/subunit/tests/sample-two-script.py | ||
| python/subunit/tests/test_chunked.py | ||
| python/subunit/tests/test_details.py | ||
| python/subunit/tests/test_filter_to_disk.py | ||
| python/subunit/tests/test_filters.py | ||
| python/subunit/tests/test_output_filter.py | ||
| python/subunit/tests/test_progress_model.py | ||
| python/subunit/tests/test_run.py | ||
| python/subunit/tests/test_subunit_filter.py | ||
| python/subunit/tests/test_subunit_stats.py | ||
| python/subunit/tests/test_subunit_tags.py | ||
| python/subunit/tests/test_tap2subunit.py | ||
| python/subunit/tests/test_test_protocol.py | ||
| python/subunit/tests/test_test_protocol2.py | ||
| python/subunit/tests/test_test_results.py | ||
| vim/README.md | ||
| vim/ftdetect/subunit.vim | ||
| vim/syntax/subunit.vim |
+482
| subunit: A streaming protocol for test results | ||
| Copyright (C) 2005-2013 Robert Collins <robertc@robertcollins.net> | ||
| Licensed under either the Apache License, Version 2.0 or the BSD 3-clause | ||
| license at the users choice. A copy of both licenses are available in the | ||
| project source as Apache-2.0 and BSD. You may not use this file except in | ||
| compliance with one of these two licences. | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
| license you chose for the specific language governing permissions and | ||
| limitations under that license. | ||
| See the COPYING file for full details on the licensing of Subunit. | ||
| # Subunit | ||
| Subunit is a streaming protocol for test results. | ||
| There are two major revisions of the protocol. Version 1 was trivially human | ||
| readable but had significant defects as far as highly parallel testing was | ||
| concerned - it had no room for doing discovery and execution in parallel, | ||
| required substantial buffering when multiplexing and was fragile - a corrupt | ||
| byte could cause an entire stream to be misparsed. Version 1.1 added | ||
| encapsulation of binary streams which mitigated some of the issues but the | ||
| core remained. | ||
| Version 2 shares many of the good characteristics of Version 1 - it can be | ||
| embedded into a regular text stream (e.g. from a build system) and it still | ||
| models xUnit style test execution. It also fixes many of the issues with | ||
| Version 1 - Version 2 can be multiplexed without excessive buffering (in | ||
| time or space), it has a well defined recovery mechanism for dealing with | ||
| corrupted streams (e.g. where two processes write to the same stream | ||
| concurrently, or where the stream generator suffers a bug). | ||
| More details on both protocol version s can be found in the 'Protocol' section | ||
| of this document. | ||
| Subunit comes with command line filters to process a subunit stream and | ||
| language bindings for python, C, C++ and shell. Bindings are easy to write | ||
| for other languages. | ||
| A number of useful things can be done easily with subunit: | ||
| * Test aggregation: Tests run separately can be combined and then | ||
| reported/displayed together. For instance, tests from different languages | ||
| can be shown as a seamless whole, and tests running on multiple machines | ||
| can be aggregated into a single stream through a multiplexer. | ||
| * Test archiving: A test run may be recorded and replayed later. | ||
| * Test isolation: Tests that may crash or otherwise interact badly with each | ||
| other can be run separately and then aggregated, rather than interfering | ||
| with each other or requiring an adhoc test->runner reporting protocol. | ||
| * Grid testing: subunit can act as the necessary serialisation and | ||
| deserialiation to get test runs on distributed machines to be reported in | ||
| real time. | ||
| Subunit supplies the following filters: | ||
| * tap2subunit - convert perl's TestAnythingProtocol to subunit. | ||
| * subunit2csv - convert a subunit stream to csv. | ||
| * subunit2disk - export a subunit stream to files on disk. | ||
| * subunit2pyunit - convert a subunit stream to pyunit test results. | ||
| * subunit2gtk - show a subunit stream in GTK. | ||
| * subunit2junitxml - convert a subunit stream to JUnit's XML format. | ||
| * subunit-diff - compare two subunit streams. | ||
| * subunit-filter - filter out tests from a subunit stream. | ||
| * subunit-ls - list info about tests present in a subunit stream. | ||
| * subunit-stats - generate a summary of a subunit stream. | ||
| * subunit-tags - add or remove tags from a stream. | ||
| ## Integration with other tools | ||
| Subunit's language bindings act as integration with various test runners like | ||
| 'check', 'cppunit', Python's 'unittest'. Beyond that a small amount of glue | ||
| (typically a few lines) will allow Subunit to be used in more sophisticated | ||
| ways. | ||
| ### Python | ||
| Subunit has excellent Python support: most of the filters and tools are written | ||
| in python and there are facilities for using Subunit to increase test isolation | ||
| seamlessly within a test suite. | ||
| The most common way is to run an existing python test suite and have it output | ||
| subunit via the ``subunit.run`` module:: | ||
| $ python -m subunit.run mypackage.tests.test_suite | ||
| For more information on the Python support Subunit offers , please see | ||
| ``pydoc subunit``, or the source in ``python/subunit/`` | ||
| ### Rust | ||
| There is a rust crate [subunit](https://crates.io/crates/subunit) that can be used to parse v1 and v1, and emit v2 subunit streams. | ||
| ### C | ||
| Subunit has C bindings to emit the protocol. The 'check' C unit testing project | ||
| has included subunit support in their project for some years now. See | ||
| 'c/README' for more details. | ||
| ### C++ | ||
| The C library is includable and usable directly from C++. A TestListener for | ||
| CPPUnit is included in the Subunit distribution. See 'c++/README' for details. | ||
| ### shell | ||
| There are two sets of shell tools. There are filters, which accept a subunit | ||
| stream on stdin and output processed data (or a transformed stream) on stdout. | ||
| Then there are unittest facilities similar to those for C : shell bindings | ||
| consisting of simple functions to output protocol elements, and a patch for | ||
| adding subunit output to the 'ShUnit' shell test runner. See 'shell/README' for | ||
| details. | ||
| #### Filter recipes | ||
| To ignore some failing tests whose root cause is already known:: | ||
| subunit-filter --without 'AttributeError.*flavor' | ||
| ## The xUnit test model | ||
| Subunit implements a slightly modified xUnit test model. The stock standard | ||
| model is that there are tests, which have an id(), can be run, and when run | ||
| start, emit an outcome (like success or failure) and then finish. | ||
| Subunit extends this with the idea of test enumeration (find out about tests | ||
| a runner has without running them), tags (allow users to describe tests in | ||
| ways the test framework doesn't apply any semantic value to), file attachments | ||
| (allow arbitrary data to make analysing a failure easy) and timestamps. | ||
| ## The protocol | ||
| Version 2, or v2 is new and still under development, but is intended to | ||
| supersede version 1 in the very near future. Subunit's bundled tools accept | ||
| only version 2 and only emit version 2, but the new filters subunit-1to2 and | ||
| subunit-2to1 can be used to interoperate with older third party libraries. | ||
| ### Version 2 | ||
| Version 2 is a binary protocol consisting of independent packets that can be | ||
| embedded in the output from tools like make - as long as each packet has no | ||
| other bytes mixed in with it (which 'make -j N>1' has a tendency of doing). | ||
| Version 2 is currently in draft form, and early adopters should be willing | ||
| to either discard stored results (if protocol changes are made), or bulk | ||
| convert them back to v1 and then to a newer edition of v2. | ||
| The protocol synchronises at the start of the stream, after a packet, or | ||
| after any 0x0A byte. That is, a subunit v2 packet starts after a newline or | ||
| directly after the end of the prior packet. | ||
| Subunit is intended to be transported over a reliable streaming protocol such | ||
| as TCP. As such it does not concern itself with out of order delivery of | ||
| packets. However, because of the possibility of corruption due to either | ||
| bugs in the sender, or due to mixed up data from concurrent writes to the same | ||
| fd when being embedded, subunit strives to recover reasonably gracefully from | ||
| damaged data. | ||
| A key design goal for Subunit version 2 is to allow processing and multiplexing | ||
| without forcing buffering for semantic correctness, as buffering tends to hide | ||
| hung or otherwise misbehaving tests. That said, limited time based buffering | ||
| for network efficiency is a good idea - this is ultimately implementer | ||
| choice. Line buffering is also discouraged for subunit streams, as dropping | ||
| into a debugger or other tool may require interactive traffic even if line | ||
| buffering would not otherwise be a problem. | ||
| In version two there are two conceptual events - a test status event and a file | ||
| attachment event. Events may have timestamps, and the path of multiplexers that | ||
| an event is routed through is recorded to permit sending actions back to the | ||
| source (such as new tests to run or stdin for driving debuggers and other | ||
| interactive input). Test status events are used to enumerate tests, to report | ||
| tests and test helpers as they run. Tests may have tags, used to allow | ||
| tunnelling extra meanings through subunit without requiring parsing of | ||
| arbitrary file attachments. Things that are not standalone tests get marked | ||
| as such by setting the 'Runnable' flag to false. (For instance, individual | ||
| assertions in TAP are not runnable tests, only the top level TAP test script | ||
| is runnable). | ||
| File attachments are used to provide rich detail about the nature of a failure. | ||
| File attachments can also be used to encapsulate stdout and stderr both during | ||
| and outside tests. | ||
| Most numbers are stored in network byte order - Most Significant Byte first | ||
| encoded using a variation of http://www.dlugosz.com/ZIP2/VLI.html. The first | ||
| byte's top 2 high order bits encode the total number of octets in the number. | ||
| This encoding can encode values from 0 to 2**30-1, enough to encode a | ||
| nanosecond. Numbers that are not variable length encoded are still stored in | ||
| MSB order. | ||
| +--------+--------+---------+------------+ | ||
| | prefix | octets | max | max | | ||
| +========+========+=========+============+ | ||
| | 00 | 1 | 2**6-1 | 63 | | ||
| +--------+--------+---------+------------+ | ||
| | 01 | 2 | 2**14-1 | 16383 | | ||
| +--------+--------+---------+------------+ | ||
| | 10 | 3 | 2**22-1 | 4194303 | | ||
| +--------+--------+---------+------------+ | ||
| | 11 | 4 | 2**30-1 | 1073741823 | | ||
| +--------+--------+---------+------------+ | ||
| All variable length elements of the packet are stored with a length prefix | ||
| number allowing them to be skipped over for consumers that don't need to | ||
| interpret them. | ||
| UTF-8 strings are with no terminating NUL and should not have any embedded NULs | ||
| (implementations SHOULD validate any such strings that they process and take | ||
| some remedial action (such as discarding the packet as corrupt). | ||
| In short the structure of a packet is: | ||
| PACKET := SIGNATURE FLAGS PACKET_LENGTH TIMESTAMP? TESTID? TAGS? MIME? | ||
| FILECONTENT? ROUTING_CODE? CRC32 | ||
| In more detail... | ||
| Packets are identified by a single byte signature - 0xB3, which is never legal | ||
| in a UTF-8 stream as the first byte of a character. 0xB3 starts with the first | ||
| bit set and the second not, which is the UTF-8 signature for a continuation | ||
| byte. 0xB3 was chosen as 0x73 ('s' in ASCII') with the top two bits replaced by | ||
| the 1 and 0 for a continuation byte. | ||
| If subunit packets are being embedded in a non-UTF-8 text stream, where 0x73 is | ||
| a legal character, consider either recoding the text to UTF-8, or using | ||
| subunit's 'file' packets to embed the text stream in subunit, rather than the | ||
| other way around. | ||
| Following the signature byte comes a 16-bit flags field, which includes a | ||
| 4-bit version field - if the version is not 0x2 then the packet cannot be | ||
| read. It is recommended to signal an error at this point (e.g. by emitting | ||
| a synthetic error packet and returning to the top level loop to look for | ||
| new packets, or exiting with an error). If recovery is desired, treat the | ||
| packet signature as an opaque byte and scan for a new synchronisation point. | ||
| NB: Subunit V1 and V2 packets may legitimately included 0xB3 internally, | ||
| as they are an 8-bit safe container format, so recovery from this situation | ||
| may involve an arbitrary number of false positives until an actual packet | ||
| is encountered : and even then it may still be false, failing after passing | ||
| the version check due to coincidence. | ||
| Flags are stored in network byte order too. | ||
| ```asciiart | ||
| +------------+------------+------------------------+ | ||
| | High byte | Low byte | | ||
| +------------+------------+------------------------+ | ||
| | 15 14 13 12 11 10 9 8 | 7 6 5 4 3 2 1 0 | | ||
| +------------+------------+------------------------+ | ||
| | VERSION | feature bits | | ||
| +------------+-------------------------------------+ | ||
| ``` | ||
| Valid version values are: | ||
| `0x2` - version 2 | ||
| Feature bits: | ||
| | Bit position | Mask | Description| | ||
| |---------|-------------|---------------------------| | ||
| | Bit 11 | `0x0800` | Test id present. | | ||
| | Bit 10 | `0x0400` | Routing code present. | | ||
| | Bit 9 | `0x0200` | Timestamp present. | | ||
| | Bit 8 | `0x0100` | Test is 'runnable'. | | ||
| | Bit 7 | `0x0080` | Tags are present. | | ||
| | Bit 6 | `0x0040` | File content is present. | | ||
| | Bit 5 | `0x0020` | File MIME type is present.| | ||
| | Bit 4 | `0x0010` | EOF marker. | | ||
| | Bit 3 | `0x0008` | Must be zero in version 2.| | ||
| Test status gets three bits: | ||
| Bit 2 | Bit 1 | Bit 0 - mask 0x0007 - A test status enum lookup: | ||
| * 000 - undefined / no test | ||
| * 001 - Enumeration / existence | ||
| * 002 - In progress | ||
| * 003 - Success | ||
| * 004 - Unexpected Success | ||
| * 005 - Skipped | ||
| * 006 - Failed | ||
| * 007 - Expected failure | ||
| After the flags field is a number field giving the length in bytes for the | ||
| entire packet including the signature and the checksum. This length must | ||
| be less than 4MiB - 4194303 bytes. The encoding can obviously record a larger | ||
| number but one of the goals is to avoid requiring large buffers, or causing | ||
| large latency in the packet forward/processing pipeline. Larger file | ||
| attachments can be communicated in multiple packets, and the overhead in such a | ||
| 4MiB packet is approximately 0.2%. | ||
| The rest of the packet is a series of optional features as specified by the set | ||
| feature bits in the flags field. When absent they are entirely absent. | ||
| Forwarding and multiplexing of packets can be done without interpreting the | ||
| remainder of the packet until the routing code and checksum (which are both at | ||
| the end of the packet). Additionally, routers can often avoid copying or moving | ||
| the bulk of the packet, as long as the routing code size increase doesn't force | ||
| the length encoding to take up a new byte (which will only happen to packets | ||
| less than or equal to 16KiB in length) - large packets are very efficient to | ||
| route. | ||
| Timestamp when present is a 32 bit unsigned integer for seconds, and a variable | ||
| length number for nanoseconds, representing UTC time since Unix Epoch in | ||
| seconds and nanoseconds. | ||
| Test id when present is a UTF-8 string. The test id should uniquely identify | ||
| runnable tests such that they can be selected individually. For tests and other | ||
| actions which cannot be individually run (such as test | ||
| fixtures/layers/subtests) uniqueness is not required (though being human | ||
| meaningful is highly recommended). | ||
| Tags when present is a length prefixed vector of UTF-8 strings, one per tag. | ||
| There are no restrictions on tag content (other than the restrictions on UTF-8 | ||
| strings in subunit in general). Tags have no ordering. | ||
| When a MIME type is present, it defines the MIME type for the file across all | ||
| packets same file (routing code + testid + name uniquely identifies a file, | ||
| reset when EOF is flagged). If a file never has a MIME type set, it should be | ||
| treated as application/octet-stream. | ||
| File content when present is a UTF-8 string for the name followed by the length | ||
| in bytes of the content, and then the content octets. | ||
| If present routing code is a UTF-8 string. The routing code is used to | ||
| determine which test backend a test was running on when doing data analysis, | ||
| and to route stdin to the test process if interaction is required. | ||
| Multiplexers SHOULD add a routing code if none is present, and prefix any | ||
| existing routing code with a routing code ('/' separated) if one is already | ||
| present. For example, a multiplexer might label each stream it is multiplexing | ||
| with a simple ordinal ('0', '1' etc), and given an incoming packet with route | ||
| code '3' from stream '0' would adjust the route code when forwarding the packet | ||
| to be '0/3'. | ||
| Following the end of the packet is a CRC-32 checksum of the contents of the | ||
| packet including the signature. | ||
| #### Example packets | ||
| Trivial test "foo" enumeration packet, with test id, runnable set, | ||
| status=enumeration. Spaces below are to visually break up signature / flags / | ||
| length / testid / crc32 | ||
| b3 2901 0c 03666f6f 08555f1b | ||
| ### Version 1 (and 1.1) | ||
| Version 1 (and 1.1) are mostly human readable protocols. | ||
| #### Sample subunit wire contents | ||
| The following: | ||
| ```subunit | ||
| test: test foo works | ||
| success: test foo works | ||
| test: tar a file. | ||
| failure: tar a file. [ | ||
| .. | ||
| ].. space is eaten. | ||
| foo.c:34 WARNING foo is not defined. | ||
| ] | ||
| a writeln to stdout | ||
| When run through subunit2pyunit:: | ||
| .F | ||
| a writeln to stdout | ||
| ======================== | ||
| FAILURE: tar a file. | ||
| ------------------- | ||
| .. | ||
| ].. space is eaten. | ||
| foo.c:34 WARNING foo is not defined. | ||
| ``` | ||
| #### Subunit v1 protocol description | ||
| This description is being ported to an EBNF style. Currently its only partly in | ||
| that style, but should be fairly clear all the same. When in doubt, refer the | ||
| source (and ideally help fix up the description!). Generally the protocol is | ||
| line orientated and consists of either directives and their parameters, or | ||
| when outside a DETAILS region unexpected lines which are not interpreted by | ||
| the parser - they should be forwarded unaltered: | ||
| ```ebnf | ||
| test|testing|test:|testing: test LABEL | ||
| success|success:|successful|successful: test LABEL | ||
| success|success:|successful|successful: test LABEL DETAILS | ||
| failure: test LABEL | ||
| failure: test LABEL DETAILS | ||
| error: test LABEL | ||
| error: test LABEL DETAILS | ||
| skip[:] test LABEL | ||
| skip[:] test LABEL DETAILS | ||
| xfail[:] test LABEL | ||
| xfail[:] test LABEL DETAILS | ||
| uxsuccess[:] test LABEL | ||
| uxsuccess[:] test LABEL DETAILS | ||
| progress: [+|-]X | ||
| progress: push | ||
| progress: pop | ||
| tags: [-]TAG ... | ||
| time: YYYY-MM-DD HH:MM:SSZ | ||
| LABEL: UTF8* | ||
| NAME: UTF8* | ||
| DETAILS ::= BRACKETED | MULTIPART | ||
| BRACKETED ::= '[' LF UTF8-lines ']' LF | ||
| MULTIPART ::= '[ multipart' LF PART* ']' LF | ||
| PART ::= PART_TYPE LF NAME LF PART_BYTES LF | ||
| PART_TYPE ::= Content-Type: type/sub-type(;parameter=value,parameter=value) | ||
| PART_BYTES ::= (DIGITS CR LF BYTE{DIGITS})* '0' CR LF | ||
| LF ::= '\n' | ||
| CR ::= '\r' | ||
| ``` | ||
| unexpected output on stdout -> stdout. | ||
| exit w/0 or last test completing -> error | ||
| Tags given outside a test are applied to all following tests | ||
| Tags given after a test: line and before the result line for the same test | ||
| apply only to that test, and inherit the current global tags. | ||
| A '-' before a tag is used to remove tags - e.g. to prevent a global tag | ||
| applying to a single test, or to cancel a global tag. | ||
| The progress directive is used to provide progress information about a stream | ||
| so that stream consumer can provide completion estimates, progress bars and so | ||
| on. Stream generators that know how many tests will be present in the stream | ||
| should output "progress: COUNT". Stream filters that add tests should output | ||
| "progress: +COUNT", and those that remove tests should output | ||
| "progress: -COUNT". An absolute count should reset the progress indicators in | ||
| use - it indicates that two separate streams from different generators have | ||
| been trivially concatenated together, and there is no knowledge of how many | ||
| more complete streams are incoming. Smart concatenation could scan each stream | ||
| for their count and sum them, or alternatively translate absolute counts into | ||
| relative counts inline. It is recommended that outputters avoid absolute counts | ||
| unless necessary. The push and pop directives are used to provide local regions | ||
| for progress reporting. This fits with hierarchically operating test | ||
| environments - such as those that organise tests into suites - the top-most | ||
| runner can report on the number of suites, and each suite surround its output | ||
| with a (push, pop) pair. Interpreters should interpret a pop as also advancing | ||
| the progress of the restored level by one step. Encountering progress | ||
| directives between the start and end of a test pair indicates that a previous | ||
| test was interrupted and did not cleanly terminate: it should be implicitly | ||
| closed with an error (the same as when a stream ends with no closing test | ||
| directive for the most recently started test). | ||
| The time directive acts as a clock event - it sets the time for all future | ||
| events. The value should be a valid ISO8601 time. | ||
| The skip, xfail and uxsuccess outcomes are not supported by all testing | ||
| environments. In Python the testttools (https://launchpad.net/testtools) | ||
| library is used to translate these automatically if an older Python version | ||
| that does not support them is in use. See the testtools documentation for the | ||
| translation policy. | ||
| skip is used to indicate a test was discovered but not executed. xfail is used | ||
| to indicate a test that errored in some expected fashion (also know as "TODO" | ||
| tests in some frameworks). uxsuccess is used to indicate and unexpected success | ||
| where a test though to be failing actually passes. It is complementary to | ||
| xfail. | ||
| ## Hacking on subunit | ||
| ### Releases | ||
| * Update versions in configure.ac and python/subunit/__init__.py. | ||
| * Update NEWS. | ||
| * Do a make distcheck, which will update Makefile etc. | ||
| * Do a PyPI release: PYTHONPATH=python python setup.py sdist bdist_wheel; twine upload -s dist/* | ||
| * Upload the regular one to LP. | ||
| * Push a tagged commit. | ||
| git push -t origin master:master |
Sorry, the diff of this file is not supported yet
| # Subunit Vim Syntax Highlighting | ||
| This directory contains Vim syntax highlighting for Subunit v1 test protocol streams. | ||
| ## Installation | ||
| ### Option 1: Manual Installation | ||
| Copy the files to your Vim configuration directory: | ||
| ```bash | ||
| cp -r syntax ~/.vim/ | ||
| cp -r ftdetect ~/.vim/ | ||
| ``` | ||
| ### Option 2: Using a Plugin Manager | ||
| If you use a Vim plugin manager like vim-plug, add to your `.vimrc`: | ||
| ```vim | ||
| Plug 'path/to/subunit/vim' | ||
| ``` | ||
| ### Option 3: Pathogen | ||
| If you use Pathogen: | ||
| ```bash | ||
| cd ~/.vim/bundle | ||
| ln -s /path/to/subunit/vim subunit | ||
| ``` |
Sorry, the diff of this file is not supported yet
+20
-2
@@ -5,5 +5,21 @@ --------------------- | ||
| NEXT (In development) | ||
| 1.4.5 (2025-11-10) | ||
| --------------------- | ||
| BUG FIXES | ||
| ~~~~~~~~~ | ||
| * Drop support for Python 3.9. | ||
| (Jelmer Vernooij) | ||
| * Drop use of Python26 and Python27 test cases from testtools. | ||
| These are no longer relevant and no longer provided by | ||
| modern versions of testtools. (Jelmer Vernooij) | ||
| IMPROVEMENTS | ||
| ~~~~~~~~~~~~ | ||
| * Add support for Python 3.14. | ||
| (Jelmer Vernooij) | ||
| 1.4.4 (2023-11-17) | ||
@@ -27,2 +43,4 @@ ------------------ | ||
| * Drop support for Python 3.7 (Jelmer Vernooij) | ||
| 1.4.3 (2023-09-17) | ||
@@ -338,3 +356,3 @@ --------------------- | ||
| With the ability to encapsulate multiple non-test streams, another significant | ||
| cange is that filters which emit subunit now encapsulate any non-subunit they | ||
| change is that filters which emit subunit now encapsulate any non-subunit they | ||
| encounter, labelling it 'stdout'. This permits multiplexing such streams and | ||
@@ -341,0 +359,0 @@ detangling the stdout streams from each input. |
+71
-74
@@ -1,27 +0,32 @@ | ||
| Metadata-Version: 2.1 | ||
| Metadata-Version: 2.4 | ||
| Name: python-subunit | ||
| Version: 1.4.4 | ||
| Version: 1.4.5 | ||
| Summary: Python implementation of subunit test streaming protocol | ||
| Home-page: http://launchpad.net/subunit | ||
| Author: Robert Collins | ||
| Author-email: subunit-dev@lists.launchpad.net | ||
| Author-email: Robert Collins <subunit-dev@lists.launchpad.net> | ||
| License: Apache-2.0 or BSD | ||
| Project-URL: Bug Tracker, https://bugs.launchpad.net/subunit | ||
| Project-URL: Homepage, http://launchpad.net/subunit | ||
| Project-URL: Source Code, https://github.com/testing-cabal/subunit/ | ||
| Keywords: python test streaming | ||
| Keywords: python,streaming,test | ||
| Classifier: Intended Audience :: Developers | ||
| Classifier: Operating System :: OS Independent | ||
| Classifier: Programming Language :: Python | ||
| Classifier: Programming Language :: Python :: 3 | ||
| Classifier: Programming Language :: Python :: 3.7 | ||
| Classifier: Programming Language :: Python :: 3.8 | ||
| Classifier: Programming Language :: Python :: 3.9 | ||
| Classifier: Programming Language :: Python :: 3.10 | ||
| Classifier: Programming Language :: Python :: 3.11 | ||
| Classifier: Programming Language :: Python :: 3.12 | ||
| Classifier: Programming Language :: Python :: 3.13 | ||
| Classifier: Programming Language :: Python :: 3.14 | ||
| Classifier: Programming Language :: Python | ||
| Classifier: Topic :: Software Development :: Testing | ||
| Requires-Python: >=3.7 | ||
| Requires-Python: >=3.10 | ||
| Description-Content-Type: text/markdown | ||
| License-File: COPYING | ||
| Requires-Dist: iso8601 | ||
| Requires-Dist: testtools>=2.7 | ||
| Provides-Extra: docs | ||
| Requires-Dist: docutils; extra == "docs" | ||
| Provides-Extra: test | ||
| License-File: COPYING | ||
| Requires-Dist: fixtures; extra == "test" | ||
| Requires-Dist: hypothesis; extra == "test" | ||
| Requires-Dist: testscenarios; extra == "test" | ||
| Dynamic: license-file | ||
@@ -45,4 +50,3 @@ | ||
| Subunit | ||
| ------- | ||
| # Subunit | ||
@@ -81,3 +85,3 @@ Subunit is a streaming protocol for test results. | ||
| * Test isolation: Tests that may crash or otherwise interact badly with each | ||
| other can be run seperately and then aggregated, rather than interfering | ||
| other can be run separately and then aggregated, rather than interfering | ||
| with each other or requiring an adhoc test->runner reporting protocol. | ||
@@ -101,5 +105,5 @@ * Grid testing: subunit can act as the necessary serialisation and | ||
| Integration with other tools | ||
| ---------------------------- | ||
| ## Integration with other tools | ||
| Subunit's language bindings act as integration with various test runners like | ||
@@ -110,4 +114,3 @@ 'check', 'cppunit', Python's 'unittest'. Beyond that a small amount of glue | ||
| Python | ||
| ====== | ||
| ### Python | ||
@@ -126,5 +129,9 @@ Subunit has excellent Python support: most of the filters and tools are written | ||
| C | ||
| = | ||
| ### Rust | ||
| There is a rust crate [subunit](https://crates.io/crates/subunit) that can be used to parse v1 and v1, and emit v2 subunit streams. | ||
| ### C | ||
| Subunit has C bindings to emit the protocol. The 'check' C unit testing project | ||
@@ -134,10 +141,9 @@ has included subunit support in their project for some years now. See | ||
| C++ | ||
| === | ||
| ### C++ | ||
| The C library is includable and usable directly from C++. A TestListener for | ||
| CPPUnit is included in the Subunit distribution. See 'c++/README' for details. | ||
| shell | ||
| ===== | ||
| ### shell | ||
@@ -152,4 +158,3 @@ There are two sets of shell tools. There are filters, which accept a subunit | ||
| Filter recipes | ||
| -------------- | ||
| #### Filter recipes | ||
@@ -160,5 +165,4 @@ To ignore some failing tests whose root cause is already known:: | ||
| ## The xUnit test model | ||
| The xUnit test model | ||
| -------------------- | ||
@@ -174,12 +178,11 @@ Subunit implements a slightly modified xUnit test model. The stock standard | ||
| The protocol | ||
| ------------ | ||
| ## The protocol | ||
| Version 2, or v2 is new and still under development, but is intended to | ||
| supercede version 1 in the very near future. Subunit's bundled tools accept | ||
| supersede version 1 in the very near future. Subunit's bundled tools accept | ||
| only version 2 and only emit version 2, but the new filters subunit-1to2 and | ||
| subunit-2to1 can be used to interoperate with older third party libraries. | ||
| Version 2 | ||
| ========= | ||
| ### Version 2 | ||
@@ -207,3 +210,3 @@ Version 2 is a binary protocol consisting of independent packets that can be | ||
| hung or otherwise misbehaving tests. That said, limited time based buffering | ||
| for network efficiency is a good idea - this is ultimately implementator | ||
| for network efficiency is a good idea - this is ultimately implementer | ||
| choice. Line buffering is also discouraged for subunit streams, as dropping | ||
@@ -288,2 +291,3 @@ into a debugger or other tool may require interactive traffic even if line | ||
| ```asciiart | ||
| +------------+------------+------------------------+ | ||
@@ -296,27 +300,20 @@ | High byte | Low byte | | ||
| +------------+-------------------------------------+ | ||
| ``` | ||
| Valid version values are: | ||
| 0x2 - version 2 | ||
| `0x2` - version 2 | ||
| Feature bits: | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 11 | mask 0x0800 | Test id present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 10 | mask 0x0400 | Routing code present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 9 | mask 0x0200 | Timestamp present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 8 | mask 0x0100 | Test is 'runnable'. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 7 | mask 0x0080 | Tags are present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 6 | mask 0x0040 | File content is present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 5 | mask 0x0020 | File MIME type is present.| | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 4 | mask 0x0010 | EOF marker. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 3 | mask 0x0008 | Must be zero in version 2.| | ||
| +---------+-------------+---------------------------+ | ||
| | Bit position | Mask | Description| | ||
| |---------|-------------|---------------------------| | ||
| | Bit 11 | `0x0800` | Test id present. | | ||
| | Bit 10 | `0x0400` | Routing code present. | | ||
| | Bit 9 | `0x0200` | Timestamp present. | | ||
| | Bit 8 | `0x0100` | Test is 'runnable'. | | ||
| | Bit 7 | `0x0080` | Tags are present. | | ||
| | Bit 6 | `0x0040` | File content is present. | | ||
| | Bit 5 | `0x0020` | File MIME type is present.| | ||
| | Bit 4 | `0x0010` | EOF marker. | | ||
| | Bit 3 | `0x0008` | Must be zero in version 2.| | ||
@@ -390,4 +387,3 @@ Test status gets three bits: | ||
| Example packets | ||
| ~~~~~~~~~~~~~~~ | ||
| #### Example packets | ||
@@ -401,12 +397,12 @@ Trivial test "foo" enumeration packet, with test id, runnable set, | ||
| Version 1 (and 1.1) | ||
| =================== | ||
| ### Version 1 (and 1.1) | ||
| Version 1 (and 1.1) are mostly human readable protocols. | ||
| Sample subunit wire contents | ||
| ---------------------------- | ||
| #### Sample subunit wire contents | ||
| The following:: | ||
| The following: | ||
| ```subunit | ||
| test: test foo works | ||
@@ -433,7 +429,6 @@ success: test foo works | ||
| foo.c:34 WARNING foo is not defined. | ||
| ``` | ||
| #### Subunit v1 protocol description | ||
| Subunit v1 protocol description | ||
| =============================== | ||
| This description is being ported to an EBNF style. Currently its only partly in | ||
@@ -444,4 +439,5 @@ that style, but should be fairly clear all the same. When in doubt, refer the | ||
| when outside a DETAILS region unexpected lines which are not interpreted by | ||
| the parser - they should be forwarded unaltered:: | ||
| the parser - they should be forwarded unaltered: | ||
| ```ebnf | ||
| test|testing|test:|testing: test LABEL | ||
@@ -469,7 +465,10 @@ success|success:|successful|successful: test LABEL | ||
| DETAILS ::= BRACKETED | MULTIPART | ||
| BRACKETED ::= '[' CR UTF8-lines ']' CR | ||
| MULTIPART ::= '[ multipart' CR PART* ']' CR | ||
| PART ::= PART_TYPE CR NAME CR PART_BYTES CR | ||
| BRACKETED ::= '[' LF UTF8-lines ']' LF | ||
| MULTIPART ::= '[ multipart' LF PART* ']' LF | ||
| PART ::= PART_TYPE LF NAME LF PART_BYTES LF | ||
| PART_TYPE ::= Content-Type: type/sub-type(;parameter=value,parameter=value) | ||
| PART_BYTES ::= (DIGITS CR LF BYTE{DIGITS})* '0' CR LF | ||
| LF ::= '\n' | ||
| CR ::= '\r' | ||
| ``` | ||
@@ -522,7 +521,5 @@ unexpected output on stdout -> stdout. | ||
| Hacking on subunit | ||
| ------------------ | ||
| ## Hacking on subunit | ||
| Releases | ||
| ======== | ||
| ### Releases | ||
@@ -529,0 +526,0 @@ * Update versions in configure.ac and python/subunit/__init__.py. |
+60
-2
| [build-system] | ||
| requires = ["setuptools>=43.0.0"] | ||
| build-backend = "setuptools.build_meta" | ||
| requires = ["iso8601", "setuptools>=61.2", "testtools"] | ||
| [tool.ruff] | ||
| line-length = 120 | ||
| target-version = "py37" | ||
| [project] | ||
| authors = [ | ||
| { name = "Robert Collins", email = "subunit-dev@lists.launchpad.net" }, | ||
| ] | ||
| classifiers = [ | ||
| "Intended Audience :: Developers", | ||
| "Operating System :: OS Independent", | ||
| "Programming Language :: Python :: 3.10", | ||
| "Programming Language :: Python :: 3.11", | ||
| "Programming Language :: Python :: 3.12", | ||
| "Programming Language :: Python :: 3.13", | ||
| "Programming Language :: Python :: 3.14", | ||
| "Programming Language :: Python", | ||
| "Topic :: Software Development :: Testing", | ||
| ] | ||
| dependencies = ["iso8601", "testtools>=2.7"] | ||
| description = "Python implementation of subunit test streaming protocol" | ||
| dynamic = ["version"] | ||
| keywords = ["python", "streaming", "test"] | ||
| license = { text = "Apache-2.0 or BSD" } | ||
| name = "python-subunit" | ||
| readme = "README.md" | ||
| requires-python = ">=3.10" | ||
| [project.urls] | ||
| "Bug Tracker" = "https://bugs.launchpad.net/subunit" | ||
| Homepage = "http://launchpad.net/subunit" | ||
| "Source Code" = "https://github.com/testing-cabal/subunit/" | ||
| [project.optional-dependencies] | ||
| "docs" = ["docutils"] | ||
| "test" = ["fixtures", "hypothesis", "testscenarios"] | ||
| [tool.setuptools.packages.find] | ||
| include = ["subunit*"] | ||
| where = ["python"] | ||
| [project.scripts] | ||
| "subunit-1to2" = "subunit.filter_scripts.subunit_1to2:main" | ||
| "subunit-2to1" = "subunit.filter_scripts.subunit_2to1:main" | ||
| "subunit-filter" = "subunit.filter_scripts.subunit_filter:main" | ||
| "subunit-ls" = "subunit.filter_scripts.subunit_ls:main" | ||
| "subunit-notify" = "subunit.filter_scripts.subunit_notify:main" | ||
| "subunit-output" = "subunit.filter_scripts.subunit_output:main" | ||
| "subunit-stats" = "subunit.filter_scripts.subunit_stats:main" | ||
| "subunit-tags" = "subunit.filter_scripts.subunit_tags:main" | ||
| "subunit2csv" = "subunit.filter_scripts.subunit2csv:main" | ||
| "subunit2disk" = "subunit.filter_scripts.subunit2disk:main" | ||
| "subunit2gtk" = "subunit.filter_scripts.subunit2gtk:main" | ||
| "subunit2junitxml" = "subunit.filter_scripts.subunit2junitxml:main" | ||
| "subunit2pyunit" = "subunit.filter_scripts.subunit2pyunit:main" | ||
| "tap2subunit" = "subunit.filter_scripts.tap2subunit:main" | ||
| [tool.setuptools.dynamic] | ||
| version = { attr = "subunit.version_string" } | ||
| [tool.setuptools] | ||
| license-files = ["COPYING"] |
+192
-186
@@ -52,7 +52,4 @@ # | ||
| When used the value of details should be a dict from ``string`` to | ||
| ``testtools.content.Content`` objects. This is a draft API being worked on with | ||
| the Python Testing In Python mail list, with the goal of permitting a common | ||
| way to provide additional data beyond a traceback, such as captured data from | ||
| disk, logging messages etc. The reference for this API is in testtools (0.9.0 | ||
| and newer). | ||
| ``testtools.content.Content`` objects. This API permits providing additional | ||
| data beyond a traceback, such as captured data from disk, logging messages etc. | ||
@@ -128,12 +125,8 @@ The ``tags(new_tags, gone_tags)`` method is called (if present) to add or | ||
| import iso8601 | ||
| from testtools import ExtendedToOriginalDecorator, content, content_type | ||
| from testtools.compat import _b, _u | ||
| from testtools.content import TracebackContent | ||
| try: | ||
| from testtools.testresult.real import _StringException | ||
| RemoteException = _StringException | ||
| except ImportError: | ||
| raise ImportError ("testtools.testresult.real does not contain " | ||
| "_StringException, check your version.") | ||
| from testtools.testresult.real import _StringException as RemoteException | ||
| from testtools import CopyStreamResult, testresult | ||
@@ -156,22 +149,25 @@ | ||
| __version__ = (1, 4, 4, 'final', 0) | ||
| __version__ = (1, 4, 5, "final", 0) | ||
| version_string = ".".join(map(str, __version__[:3])) | ||
| __all__ = [ | ||
| 'join_dir', | ||
| 'tags_to_new_gone', | ||
| 'content', | ||
| 'content_type', | ||
| 'TestProtocolServer', | ||
| 'TestProtocolClient', | ||
| 'RemoteError', | ||
| 'RemotedTestCase', | ||
| 'IsolatedTestCase', | ||
| 'IsolatedTestSuite', | ||
| 'run_isolated', | ||
| 'TAP2SubUnit', | ||
| 'tag_stream', | ||
| 'ProtocolTestCase', | ||
| 'make_stream_binary', | ||
| 'read_test_list', | ||
| 'TestResultStats', | ||
| "join_dir", | ||
| "tags_to_new_gone", | ||
| "content", | ||
| "content_type", | ||
| "TestProtocolServer", | ||
| "TestProtocolClient", | ||
| "RemoteError", | ||
| "RemotedTestCase", | ||
| "IsolatedTestCase", | ||
| "IsolatedTestSuite", | ||
| "run_isolated", | ||
| "TAP2SubUnit", | ||
| "tag_stream", | ||
| "ProtocolTestCase", | ||
| "make_stream_binary", | ||
| "read_test_list", | ||
| "TestResultStats", | ||
| ] | ||
@@ -187,2 +183,3 @@ | ||
| import subunit.tests | ||
| return subunit.tests.test_suite() | ||
@@ -210,3 +207,3 @@ | ||
| for tag in tags: | ||
| if tag[0] == '-': | ||
| if tag[0] == "-": | ||
| gone_tags.add(tag[1:]) | ||
@@ -228,3 +225,3 @@ else: | ||
| def read(self, len=0): | ||
| return _b('') | ||
| return b"" | ||
@@ -237,15 +234,15 @@ | ||
| self.parser = parser | ||
| self._test_sym = (_b('test'), _b('testing')) | ||
| self._colon_sym = _b(':') | ||
| self._error_sym = (_b('error'),) | ||
| self._failure_sym = (_b('failure'),) | ||
| self._progress_sym = (_b('progress'),) | ||
| self._skip_sym = _b('skip') | ||
| self._success_sym = (_b('success'), _b('successful')) | ||
| self._tags_sym = (_b('tags'),) | ||
| self._time_sym = (_b('time'),) | ||
| self._xfail_sym = (_b('xfail'),) | ||
| self._uxsuccess_sym = (_b('uxsuccess'),) | ||
| self._start_simple = _u(" [") | ||
| self._start_multipart = _u(" [ multipart") | ||
| self._test_sym = (b"test", b"testing") | ||
| self._colon_sym = b":" | ||
| self._error_sym = (b"error",) | ||
| self._failure_sym = (b"failure",) | ||
| self._progress_sym = (b"progress",) | ||
| self._skip_sym = b"skip" | ||
| self._success_sym = (b"success", b"successful") | ||
| self._tags_sym = (b"tags",) | ||
| self._time_sym = (b"time",) | ||
| self._xfail_sym = (b"xfail",) | ||
| self._uxsuccess_sym = (b"uxsuccess",) | ||
| self._start_simple = " [" | ||
| self._start_multipart = " [ multipart" | ||
@@ -308,3 +305,3 @@ def addError(self, offset, line): | ||
| """Connection lost.""" | ||
| self.parser._lostConnectionInTest(_u('unknown state of ')) | ||
| self.parser._lostConnectionInTest("unknown state of ") | ||
@@ -326,3 +323,3 @@ def startTest(self, offset, line): | ||
| """ | ||
| test_name = line[offset:-1].decode('utf8') | ||
| test_name = line[offset:-1].decode("utf8") | ||
| if self.parser.current_test_description == test_name: | ||
@@ -335,9 +332,7 @@ self.parser._state = self.parser._outside_test | ||
| self.parser.subunitLineReceived(line) | ||
| elif self.parser.current_test_description + self._start_simple == \ | ||
| test_name: | ||
| elif self.parser.current_test_description + self._start_simple == test_name: | ||
| self.parser._state = details_state | ||
| details_state.set_simple() | ||
| self.parser.subunitLineReceived(line) | ||
| elif self.parser.current_test_description + self._start_multipart == \ | ||
| test_name: | ||
| elif self.parser.current_test_description + self._start_multipart == test_name: | ||
| self.parser._state = details_state | ||
@@ -350,18 +345,14 @@ details_state.set_multipart() | ||
| def _error(self): | ||
| self.parser.client.addError(self.parser._current_test, | ||
| details={}) | ||
| self.parser.client.addError(self.parser._current_test, details={}) | ||
| def addError(self, offset, line): | ||
| """An 'error:' directive has been read.""" | ||
| self._outcome(offset, line, self._error, | ||
| self.parser._reading_error_details) | ||
| self._outcome(offset, line, self._error, self.parser._reading_error_details) | ||
| def _xfail(self): | ||
| self.parser.client.addExpectedFailure(self.parser._current_test, | ||
| details={}) | ||
| self.parser.client.addExpectedFailure(self.parser._current_test, details={}) | ||
| def addExpectedFail(self, offset, line): | ||
| """An 'xfail:' directive has been read.""" | ||
| self._outcome(offset, line, self._xfail, | ||
| self.parser._reading_xfail_details) | ||
| self._outcome(offset, line, self._xfail, self.parser._reading_xfail_details) | ||
@@ -373,4 +364,3 @@ def _uxsuccess(self): | ||
| """A 'uxsuccess:' directive has been read.""" | ||
| self._outcome(offset, line, self._uxsuccess, | ||
| self.parser._reading_uxsuccess_details) | ||
| self._outcome(offset, line, self._uxsuccess, self.parser._reading_uxsuccess_details) | ||
@@ -382,4 +372,3 @@ def _failure(self): | ||
| """A 'failure:' directive has been read.""" | ||
| self._outcome(offset, line, self._failure, | ||
| self.parser._reading_failure_details) | ||
| self._outcome(offset, line, self._failure, self.parser._reading_failure_details) | ||
@@ -391,4 +380,3 @@ def _skip(self): | ||
| """A 'skip:' directive has been read.""" | ||
| self._outcome(offset, line, self._skip, | ||
| self.parser._reading_skip_details) | ||
| self._outcome(offset, line, self._skip, self.parser._reading_skip_details) | ||
@@ -400,8 +388,7 @@ def _succeed(self): | ||
| """A 'success:' directive has been read.""" | ||
| self._outcome(offset, line, self._succeed, | ||
| self.parser._reading_success_details) | ||
| self._outcome(offset, line, self._succeed, self.parser._reading_success_details) | ||
| def lostConnection(self): | ||
| """Connection lost.""" | ||
| self.parser._lostConnectionInTest(_u('')) | ||
| self.parser._lostConnectionInTest("") | ||
@@ -418,3 +405,3 @@ | ||
| self.parser._state = self.parser._in_test | ||
| test_name = line[offset:-1].decode('utf8') | ||
| test_name = line[offset:-1].decode("utf8") | ||
| self.parser._current_test = RemotedTestCase(test_name) | ||
@@ -443,4 +430,3 @@ self.parser.current_test_description = test_name | ||
| """Connection lost.""" | ||
| self.parser._lostConnectionInTest(_u('%s report of ') % | ||
| self._outcome_label()) | ||
| self.parser._lostConnectionInTest("%s report of " % self._outcome_label()) | ||
@@ -464,4 +450,3 @@ def _outcome_label(self): | ||
| def _report_outcome(self): | ||
| self.parser.client.addFailure(self.parser._current_test, | ||
| details=self.details_parser.get_details()) | ||
| self.parser.client.addFailure(self.parser._current_test, details=self.details_parser.get_details()) | ||
@@ -476,4 +461,3 @@ def _outcome_label(self): | ||
| def _report_outcome(self): | ||
| self.parser.client.addError(self.parser._current_test, | ||
| details=self.details_parser.get_details()) | ||
| self.parser.client.addError(self.parser._current_test, details=self.details_parser.get_details()) | ||
@@ -488,4 +472,3 @@ def _outcome_label(self): | ||
| def _report_outcome(self): | ||
| self.parser.client.addExpectedFailure(self.parser._current_test, | ||
| details=self.details_parser.get_details()) | ||
| self.parser.client.addExpectedFailure(self.parser._current_test, details=self.details_parser.get_details()) | ||
@@ -500,4 +483,3 @@ def _outcome_label(self): | ||
| def _report_outcome(self): | ||
| self.parser.client.addUnexpectedSuccess(self.parser._current_test, | ||
| details=self.details_parser.get_details()) | ||
| self.parser.client.addUnexpectedSuccess(self.parser._current_test, details=self.details_parser.get_details()) | ||
@@ -512,4 +494,3 @@ def _outcome_label(self): | ||
| def _report_outcome(self): | ||
| self.parser.client.addSkip(self.parser._current_test, | ||
| details=self.details_parser.get_details("skip")) | ||
| self.parser.client.addSkip(self.parser._current_test, details=self.details_parser.get_details("skip")) | ||
@@ -524,4 +505,3 @@ def _outcome_label(self): | ||
| def _report_outcome(self): | ||
| self.parser.client.addSuccess(self.parser._current_test, | ||
| details=self.details_parser.get_details("success")) | ||
| self.parser.client.addSuccess(self.parser._current_test, details=self.details_parser.get_details("success")) | ||
@@ -568,5 +548,5 @@ def _outcome_label(self): | ||
| # Avoid casts on every call | ||
| self._plusminus = _b('+-') | ||
| self._push_sym = _b('push') | ||
| self._pop_sym = _b('pop') | ||
| self._plusminus = b"+-" | ||
| self._push_sym = b"push" | ||
| self._pop_sym = b"pop" | ||
@@ -592,3 +572,3 @@ def _handleProgress(self, offset, line): | ||
| """Process a tags command.""" | ||
| tags = line[offset:].decode('utf8').split() | ||
| tags = line[offset:].decode("utf8").split() | ||
| new_tags, gone_tags = tags_to_new_gone(tags) | ||
@@ -602,4 +582,3 @@ self.client.tags(new_tags, gone_tags) | ||
| except TypeError: | ||
| raise TypeError(_u("Failed to parse %r, got %r") | ||
| % (line, sys.exc_info()[1])) | ||
| raise TypeError("Failed to parse %r, got %r" % (line, sys.exc_info()[1])) | ||
| self.client.time(event_time) | ||
@@ -612,4 +591,3 @@ | ||
| def _lostConnectionInTest(self, state_string): | ||
| error_string = _u("lost connection during %stest '%s'") % ( | ||
| state_string, self.current_test_description) | ||
| error_string = "lost connection during %stest '%s'" % (state_string, self.current_test_description) | ||
| self.client.addError(self._current_test, RemoteError(error_string)) | ||
@@ -666,10 +644,10 @@ self.client.stopTest(self._current_test) | ||
| self._stream = stream | ||
| self._progress_fmt = _b("progress: ") | ||
| self._bytes_eol = _b("\n") | ||
| self._progress_plus = _b("+") | ||
| self._progress_push = _b("push") | ||
| self._progress_pop = _b("pop") | ||
| self._empty_bytes = _b("") | ||
| self._start_simple = _b(" [\n") | ||
| self._end_simple = _b("]\n") | ||
| self._progress_fmt = b"progress: " | ||
| self._bytes_eol = b"\n" | ||
| self._progress_plus = b"+" | ||
| self._progress_push = b"push" | ||
| self._progress_pop = b"pop" | ||
| self._empty_bytes = b"" | ||
| self._start_simple = b" [\n" | ||
| self._end_simple = b"]\n" | ||
@@ -725,4 +703,3 @@ def addError(self, test, error=None, details=None): | ||
| def _addOutcome(self, outcome, test, error=None, details=None, | ||
| error_permitted=True): | ||
| def _addOutcome(self, outcome, test, error=None, details=None, error_permitted=True): | ||
| """Report a failure in test test. | ||
@@ -743,4 +720,4 @@ | ||
| details must be supplied. If False then error must not be supplied | ||
| and details is still optional. """ | ||
| self._stream.write(_b("%s: " % outcome) + self._test_id(test)) | ||
| and details is still optional.""" | ||
| self._stream.write(("%s: " % outcome).encode() + self._test_id(test)) | ||
| if error_permitted: | ||
@@ -760,3 +737,3 @@ if error is None and details is None: | ||
| else: | ||
| self._stream.write(_b("\n")) | ||
| self._stream.write(b"\n") | ||
| if details is not None or error is not None: | ||
@@ -770,4 +747,4 @@ self._stream.write(self._end_simple) | ||
| else: | ||
| self._stream.write(_b("skip: %s [\n" % test.id())) | ||
| self._stream.write(_b("%s\n" % reason)) | ||
| self._stream.write(("skip: %s [\n" % test.id()).encode()) | ||
| self._stream.write(("%s\n" % reason).encode()) | ||
| self._stream.write(self._end_simple) | ||
@@ -790,4 +767,3 @@ | ||
| """ | ||
| self._addOutcome("uxsuccess", test, details=details, | ||
| error_permitted=False) | ||
| self._addOutcome("uxsuccess", test, details=details, error_permitted=False) | ||
| if self.failfast: | ||
@@ -798,4 +774,4 @@ self.stop() | ||
| result = test.id() | ||
| if type(result) is not bytes: | ||
| result = result.encode('utf8') | ||
| if not isinstance(result, bytes): | ||
| result = result.encode("utf8") | ||
| return result | ||
@@ -806,3 +782,3 @@ | ||
| super(TestProtocolClient, self).startTest(test) | ||
| self._stream.write(_b("test: ") + self._test_id(test) + _b("\n")) | ||
| self._stream.write(b"test: " + self._test_id(test) + b"\n") | ||
| self._stream.flush() | ||
@@ -826,3 +802,3 @@ | ||
| prefix = self._progress_plus | ||
| offset = _b(str(offset)) | ||
| offset = str(offset).encode() | ||
| elif whence == PROGRESS_PUSH: | ||
@@ -836,5 +812,4 @@ prefix = self._empty_bytes | ||
| prefix = self._empty_bytes | ||
| offset = _b(str(offset)) | ||
| self._stream.write(self._progress_fmt + prefix + offset + | ||
| self._bytes_eol) | ||
| offset = str(offset).encode() | ||
| self._stream.write(self._progress_fmt + prefix + offset + self._bytes_eol) | ||
@@ -845,5 +820,5 @@ def tags(self, new_tags, gone_tags): | ||
| return | ||
| tags = set([tag.encode('utf8') for tag in new_tags]) | ||
| tags.update([_b("-") + tag.encode('utf8') for tag in gone_tags]) | ||
| tag_line = _b("tags: ") + _b(" ").join(tags) + _b("\n") | ||
| tags = set([tag.encode("utf8") for tag in new_tags]) | ||
| tags.update([b"-" + tag.encode("utf8") for tag in gone_tags]) | ||
| tag_line = b"tags: " + b" ".join(tags) + b"\n" | ||
| self._stream.write(tag_line) | ||
@@ -857,5 +832,6 @@ | ||
| time = a_datetime.astimezone(iso8601.UTC) | ||
| self._stream.write(_b("time: %04d-%02d-%02d %02d:%02d:%02d.%06dZ\n" % ( | ||
| time.year, time.month, time.day, time.hour, time.minute, | ||
| time.second, time.microsecond))) | ||
| self._stream.write( | ||
| b"time: %04d-%02d-%02d %02d:%02d:%02d.%06dZ\n" | ||
| % (time.year, time.month, time.day, time.hour, time.minute, time.second, time.microsecond) | ||
| ) | ||
@@ -867,14 +843,15 @@ def _write_details(self, details): | ||
| """ | ||
| self._stream.write(_b(" [ multipart\n")) | ||
| self._stream.write(b" [ multipart\n") | ||
| for name, content in sorted(details.items()): # noqa: F402 | ||
| self._stream.write(_b("Content-Type: %s/%s" % | ||
| (content.content_type.type, content.content_type.subtype))) | ||
| self._stream.write( | ||
| ("Content-Type: %s/%s" % (content.content_type.type, content.content_type.subtype)).encode() | ||
| ) | ||
| parameters = content.content_type.parameters | ||
| if parameters: | ||
| self._stream.write(_b(";")) | ||
| self._stream.write(b";") | ||
| param_strs = [] | ||
| for param, value in sorted(parameters.items()): | ||
| param_strs.append("%s=%s" % (param, value)) | ||
| self._stream.write(_b(",".join(param_strs))) | ||
| self._stream.write(_b("\n%s\n" % name)) | ||
| self._stream.write(",".join(param_strs).encode()) | ||
| self._stream.write(b"\n" + name.encode() + b"\n") | ||
| encoder = chunked.Encoder(self._stream) | ||
@@ -887,7 +864,15 @@ list(map(encoder.write, content.iter_bytes())) | ||
| def addDuration(self, test, duration): | ||
| """Called to add a test duration. | ||
| def RemoteError(description=_u("")): | ||
| return (_StringException, _StringException(description), None) | ||
| :param test: The test that completed. | ||
| :param duration: The duration of the test as a float in seconds. | ||
| """ | ||
| pass | ||
| def RemoteError(description=""): | ||
| return (RemoteException, RemoteException(description), None) | ||
| class RemotedTestCase(unittest.TestCase): | ||
@@ -903,3 +888,3 @@ """A class to represent test cases run in child processes. | ||
| def __eq__ (self, other): | ||
| def __eq__(self, other): | ||
| try: | ||
@@ -911,8 +896,7 @@ return self.__description == other.__description | ||
| def __init__(self, description): | ||
| """Create a psuedo test case with description description.""" | ||
| """Create a pseudo test case with description description.""" | ||
| self.__description = description | ||
| def error(self, label): | ||
| raise NotImplementedError("%s on RemotedTestCases is not permitted." % | ||
| label) | ||
| raise NotImplementedError("%s on RemotedTestCases is not permitted." % label) | ||
@@ -935,4 +919,3 @@ def setUp(self): | ||
| def __repr__(self): | ||
| return "<%s description='%s'>" % \ | ||
| (self._strclass(), self.__description) | ||
| return "<%s description='%s'>" % (self._strclass(), self.__description) | ||
@@ -943,3 +926,3 @@ def run(self, result=None): | ||
| result.startTest(self) | ||
| result.addError(self, RemoteError(_u("Cannot run RemotedTestCases.\n"))) | ||
| result.addError(self, RemoteError("Cannot run RemotedTestCases.\n")) | ||
| result.stopTest(self) | ||
@@ -955,11 +938,10 @@ | ||
| def __init__(self, methodName='runTest'): | ||
| def __init__(self, methodName="runTest"): | ||
| """Create an instance of the class that will use the named test | ||
| method when executed. Raises a ValueError if the instance does | ||
| not have a method with the specified name. | ||
| method when executed. Raises a ValueError if the instance does | ||
| not have a method with the specified name. | ||
| """ | ||
| unittest.TestCase.__init__(self, methodName) | ||
| testMethod = getattr(self, methodName) | ||
| self.script = join_dir(sys.modules[self.__class__.__module__].__file__, | ||
| testMethod.__doc__) | ||
| self.script = join_dir(sys.modules[self.__class__.__module__].__file__, testMethod.__doc__) | ||
@@ -980,4 +962,3 @@ def countTestCases(self): | ||
| protocol = TestProtocolServer(result) | ||
| process = subprocess.Popen(self.script, shell=True, | ||
| stdout=subprocess.PIPE) | ||
| process = subprocess.Popen(self.script, shell=True, stdout=subprocess.PIPE) | ||
| make_stream_binary(process.stdout) | ||
@@ -1018,4 +999,3 @@ output = process.communicate()[0] | ||
| def run_isolated(klass, self, result): | ||
| """Run a test suite or case in a subprocess, using the run method on klass. | ||
| """ | ||
| """Run a test suite or case in a subprocess, using the run method on klass.""" | ||
| c2pread, c2pwrite = os.pipe() | ||
@@ -1036,4 +1016,3 @@ # fixme - error -> result | ||
| # to filter it to escape ]'s. | ||
| ### XXX: test and write that bit. | ||
| stream = os.fdopen(1, 'wb') | ||
| stream = os.fdopen(1, "wb") | ||
| result = TestProtocolClient(stream) | ||
@@ -1051,6 +1030,5 @@ klass.run(self, result) | ||
| protocol = TestProtocolServer(result) | ||
| fileobj = os.fdopen(c2pread, 'rb') | ||
| fileobj = os.fdopen(c2pread, "rb") | ||
| protocol.readFrom(fileobj) | ||
| os.waitpid(pid, 0) | ||
| # TODO return code evaluation. | ||
| return result | ||
@@ -1070,3 +1048,3 @@ | ||
| output = StreamResultToBytes(output_stream) | ||
| UTF8_TEXT = 'text/plain; charset=UTF8' | ||
| UTF8_TEXT = "text/plain; charset=UTF8" | ||
| BEFORE_PLAN = 0 | ||
@@ -1082,7 +1060,14 @@ AFTER_PLAN = 1 | ||
| result = None | ||
| def missing_test(plan_start): | ||
| output.status(test_id='test %d' % plan_start, | ||
| test_status='fail', runnable=False, | ||
| mime_type=UTF8_TEXT, eof=True, file_name="tap meta", | ||
| file_bytes=b"test missing from TAP output") | ||
| output.status( | ||
| test_id="test %d" % plan_start, | ||
| test_status="fail", | ||
| runnable=False, | ||
| mime_type=UTF8_TEXT, | ||
| eof=True, | ||
| file_name="tap meta", | ||
| file_bytes=b"test missing from TAP output", | ||
| ) | ||
| def _emit_test(): | ||
@@ -1093,5 +1078,5 @@ "write out a test" | ||
| if log: | ||
| log_bytes = b'\n'.join(log_line.encode('utf8') for log_line in log) | ||
| log_bytes = b"\n".join(log_line.encode("utf8") for log_line in log) | ||
| mime_type = UTF8_TEXT | ||
| file_name = 'tap comment' | ||
| file_name = "tap comment" | ||
| eof = True | ||
@@ -1104,5 +1089,12 @@ else: | ||
| del log[:] | ||
| output.status(test_id=test_name, test_status=result, | ||
| file_bytes=log_bytes, mime_type=mime_type, eof=eof, | ||
| file_name=file_name, runnable=False) | ||
| output.status( | ||
| test_id=test_name, | ||
| test_status=result, | ||
| file_bytes=log_bytes, | ||
| mime_type=mime_type, | ||
| eof=eof, | ||
| file_name=file_name, | ||
| runnable=False, | ||
| ) | ||
| for line in tap: | ||
@@ -1118,10 +1110,15 @@ if state == BEFORE_PLAN: | ||
| state = SKIP_STREAM | ||
| output.status(test_id='file skip', test_status='skip', | ||
| file_bytes=comment.encode('utf8'), eof=True, | ||
| file_name='tap comment') | ||
| output.status( | ||
| test_id="file skip", | ||
| test_status="skip", | ||
| file_bytes=comment.encode("utf8"), | ||
| eof=True, | ||
| file_name="tap comment", | ||
| ) | ||
| continue | ||
| # not a plan line, or have seen one before | ||
| match = re.match( | ||
| r"(ok|not ok)(?:\s+(\d+)?)?(?:\s+([^#]*[^#\s]+)\s*)?" | ||
| r"(?:\s+#\s+(TODO|SKIP|skip|todo)(?:\s+(.*))?)?\n", line) | ||
| r"(ok|not ok)(?:\s+(\d+)?)?(?:\s+([^#]*[^#\s]+)\s*)?" r"(?:\s+#\s+(TODO|SKIP|skip|todo)(?:\s+(.*))?)?\n", | ||
| line, | ||
| ) | ||
| if match: | ||
@@ -1131,15 +1128,15 @@ # new test, emit current one. | ||
| status, number, description, directive, directive_comment = match.groups() | ||
| if status == 'ok': | ||
| result = 'success' | ||
| if status == "ok": | ||
| result = "success" | ||
| else: | ||
| result = "fail" | ||
| if description is None: | ||
| description = '' | ||
| description = "" | ||
| else: | ||
| description = ' ' + description | ||
| description = " " + description | ||
| if directive is not None: | ||
| if directive.upper() == 'TODO': | ||
| result = 'xfail' | ||
| elif directive.upper() == 'SKIP': | ||
| result = 'skip' | ||
| if directive.upper() == "TODO": | ||
| result = "xfail" | ||
| elif directive.upper() == "SKIP": | ||
| result = "skip" | ||
| if directive_comment is not None: | ||
@@ -1157,7 +1154,7 @@ log.append(directive_comment) | ||
| if match: | ||
| reason, = match.groups() | ||
| (reason,) = match.groups() | ||
| if reason is None: | ||
| extra = '' | ||
| extra = "" | ||
| else: | ||
| extra = ' %s' % reason | ||
| extra = " %s" % reason | ||
| _emit_test() | ||
@@ -1173,4 +1170,3 @@ test_name = "Bail out!%s" % extra | ||
| # Should look at buffering status and binding this to the prior result. | ||
| output.status(file_bytes=line.encode('utf8'), file_name='stdout', | ||
| mime_type=UTF8_TEXT) | ||
| output.status(file_bytes=line.encode("utf8"), file_name="stdout", mime_type=UTF8_TEXT) | ||
| _emit_test() | ||
@@ -1208,6 +1204,7 @@ while plan_start <= plan_stop: | ||
| new_tags, gone_tags = tags_to_new_gone(tags) | ||
| source = ByteStreamToStreamResult(original, non_subunit_name='stdout') | ||
| source = ByteStreamToStreamResult(original, non_subunit_name="stdout") | ||
| class Tagger(CopyStreamResult): | ||
| def status(self, **kwargs): | ||
| tags = kwargs.get('test_tags') | ||
| tags = kwargs.get("test_tags") | ||
| if not tags: | ||
@@ -1218,6 +1215,7 @@ tags = set() | ||
| if tags: | ||
| kwargs['test_tags'] = tags | ||
| kwargs["test_tags"] = tags | ||
| else: | ||
| kwargs['test_tags'] = None | ||
| kwargs["test_tags"] = None | ||
| super(Tagger, self).status(**kwargs) | ||
| output = Tagger([StreamResultToBytes(filtered)]) | ||
@@ -1334,3 +1332,11 @@ source.run(output) | ||
| def addDuration(self, test, duration): | ||
| """Called to add a test duration. | ||
| :param test: The test that completed. | ||
| :param duration: The duration of the test as a float in seconds. | ||
| """ | ||
| pass | ||
| def read_test_list(path): | ||
@@ -1342,5 +1348,4 @@ """Read a list of test ids from a file on disk. | ||
| """ | ||
| with open(path, 'r') as f: | ||
| return [line.split('#')[0].rstrip() for line in f.readlines() | ||
| if line.split('#')[0]] | ||
| with open(path, "r") as f: | ||
| return [line.split("#")[0].rstrip() for line in f.readlines() if line.split("#")[0]] | ||
@@ -1350,3 +1355,3 @@ | ||
| """Ensure that a stream will be binary safe. See _make_binary_on_windows. | ||
| :return: A binary version of the same stream (some streams cannot be | ||
@@ -1368,2 +1373,3 @@ 'fixed' but can be unwrapped). | ||
| import msvcrt | ||
| msvcrt.setmode(fileno, os.O_BINARY) | ||
@@ -1383,5 +1389,5 @@ | ||
| try: | ||
| stream.write(_b('')) | ||
| stream.write(b"") | ||
| except TypeError: | ||
| return stream.buffer | ||
| return stream |
@@ -25,12 +25,14 @@ # subunit: extensions to python unittest to get test results from subprocesses. | ||
| _FINAL_ACTIONS = frozenset([ | ||
| 'exists', | ||
| 'fail', | ||
| 'skip', | ||
| 'success', | ||
| 'uxsuccess', | ||
| 'xfail', | ||
| ]) | ||
| _ALL_ACTIONS = _FINAL_ACTIONS.union(['inprogress']) | ||
| _CHUNK_SIZE=3670016 # 3.5 MiB | ||
| _FINAL_ACTIONS = frozenset( | ||
| [ | ||
| "exists", | ||
| "fail", | ||
| "skip", | ||
| "success", | ||
| "uxsuccess", | ||
| "xfail", | ||
| ] | ||
| ) | ||
| _ALL_ACTIONS = _FINAL_ACTIONS.union(["inprogress"]) | ||
| _CHUNK_SIZE = 3670016 # 3.5 MiB | ||
@@ -58,4 +60,4 @@ | ||
| ) | ||
| parser.set_default('tags', None) | ||
| parser.set_default('test_id', None) | ||
| parser.set_default("tags", None) | ||
| parser.set_default("test_id", None) | ||
@@ -65,4 +67,3 @@ status_commands = OptionGroup( | ||
| "Status Commands", | ||
| "These options report the status of a test. TEST_ID must be a string " | ||
| "that uniquely identifies the test." | ||
| "These options report the status of a test. TEST_ID must be a string that uniquely identifies the test.", | ||
| ) | ||
@@ -78,3 +79,3 @@ for action_name in _ALL_ACTIONS: | ||
| metavar="TEST_ID", | ||
| help="Report a test status." | ||
| help="Report a test status.", | ||
| ) | ||
@@ -87,6 +88,6 @@ parser.add_option_group(status_commands) | ||
| "These options control attaching data to a result stream. They can " | ||
| "either be specified with a status command, in which case the file " | ||
| "is attached to the test status, or by themselves, in which case " | ||
| "the file is attached to the stream (and not associated with any " | ||
| "test id)." | ||
| "either be specified with a status command, in which case the file " | ||
| "is attached to the test status, or by themselves, in which case " | ||
| "the file is attached to the stream (and not associated with any " | ||
| "test id).", | ||
| ) | ||
@@ -96,5 +97,5 @@ file_commands.add_option( | ||
| help="Attach a file to the result stream for this test. If '-' is " | ||
| "specified, stdin will be read instead. In this case, the file " | ||
| "name will be set to 'stdin' (but can still be overridden with " | ||
| "the --file-name option)." | ||
| "specified, stdin will be read instead. In this case, the file " | ||
| "name will be set to 'stdin' (but can still be overridden with " | ||
| "the --file-name option).", | ||
| ) | ||
@@ -104,13 +105,13 @@ file_commands.add_option( | ||
| help="The name to give this file attachment. If not specified, the " | ||
| "name of the file on disk will be used, or 'stdin' in the case " | ||
| "where '-' was passed to the '--attach-file' argument. This option" | ||
| " may only be specified when '--attach-file' is specified.", | ||
| ) | ||
| "name of the file on disk will be used, or 'stdin' in the case " | ||
| "where '-' was passed to the '--attach-file' argument. This option" | ||
| " may only be specified when '--attach-file' is specified.", | ||
| ) | ||
| file_commands.add_option( | ||
| "--mimetype", | ||
| help="The mime type to send with this file. This is only used if the " | ||
| "--attach-file argument is used. This argument is optional. If it " | ||
| "is not specified, the file will be sent without a mime type. This " | ||
| "option may only be specified when '--attach-file' is specified.", | ||
| default=None | ||
| "--attach-file argument is used. This argument is optional. If it " | ||
| "is not specified, the file will be sent without a mime type. This " | ||
| "option may only be specified when '--attach-file' is specified.", | ||
| default=None, | ||
| ) | ||
@@ -120,7 +121,3 @@ parser.add_option_group(file_commands) | ||
| parser.add_option( | ||
| "--tag", | ||
| help="Specifies a tag. May be used multiple times", | ||
| action="append", | ||
| dest="tags", | ||
| default=[] | ||
| "--tag", help="Specifies a tag. May be used multiple times", action="append", dest="tags", default=[] | ||
| ) | ||
@@ -134,9 +131,9 @@ | ||
| if options.attach_file: | ||
| if options.attach_file == '-': | ||
| if options.attach_file == "-": | ||
| if not options.file_name: | ||
| options.file_name = 'stdin' | ||
| options.file_name = "stdin" | ||
| options.attach_file = make_stream_binary(sys.stdin) | ||
| else: | ||
| try: | ||
| options.attach_file = open(options.attach_file, 'rb') | ||
| options.attach_file = open(options.attach_file, "rb") | ||
| except IOError as e: | ||
@@ -185,3 +182,3 @@ parser.error("Cannot open %s (%s)" % (options.attach_file, e.strerror)) | ||
| write_status = partial(write_status, file_name=filename, file_bytes=this_file_hunk) | ||
| if next_file_hunk == b'': | ||
| if next_file_hunk == b"": | ||
| write_status = partial(write_status, eof=True) | ||
@@ -188,0 +185,0 @@ is_last_packet = True |
@@ -8,3 +8,3 @@ # subunit: extensions to python unittest to get test results from subprocesses. | ||
| # compliance with one of these two licences. | ||
| # | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
@@ -43,3 +43,3 @@ # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| if not candidate.startswith(realroot): | ||
| sub = sub.replace('/', '_').replace('\\', '_') | ||
| sub = sub.replace("/", "_").replace("\\", "_") | ||
| return _allocate_path(root, sub) | ||
@@ -51,3 +51,3 @@ | ||
| attempt += 1 | ||
| probe = '%s-%s' % (candidate, attempt) | ||
| probe = "%s-%s" % (candidate, attempt) | ||
| return probe | ||
@@ -63,3 +63,3 @@ | ||
| raise | ||
| return io.open(name, 'wb') | ||
| return io.open(name, "wb") | ||
@@ -80,20 +80,19 @@ | ||
| def export(self, test_dict): | ||
| id = test_dict['id'] | ||
| tags = sorted(test_dict['tags']) | ||
| details = test_dict['details'] | ||
| status = test_dict['status'] | ||
| start, stop = test_dict['timestamps'] | ||
| id = test_dict["id"] | ||
| tags = sorted(test_dict["tags"]) | ||
| details = test_dict["details"] | ||
| status = test_dict["status"] | ||
| start, stop = test_dict["timestamps"] | ||
| test_summary = {} | ||
| test_summary['id'] = id | ||
| test_summary['tags'] = tags | ||
| test_summary['status'] = status | ||
| test_summary['details'] = sorted(details.keys()) | ||
| test_summary['start'] = _json_time(start) | ||
| test_summary['stop'] = _json_time(stop) | ||
| test_summary["id"] = id | ||
| test_summary["tags"] = tags | ||
| test_summary["status"] = status | ||
| test_summary["details"] = sorted(details.keys()) | ||
| test_summary["start"] = _json_time(start) | ||
| test_summary["stop"] = _json_time(stop) | ||
| root = _allocate_path(self._directory, id) | ||
| with _open_path(root, 'test.json') as f: | ||
| maybe_str = json.dumps( | ||
| test_summary, sort_keys=True, ensure_ascii=False) | ||
| with _open_path(root, "test.json") as f: | ||
| maybe_str = json.dumps(test_summary, sort_keys=True, ensure_ascii=False) | ||
| if not isinstance(maybe_str, bytes): | ||
| maybe_str = maybe_str.encode('utf-8') | ||
| maybe_str = maybe_str.encode("utf-8") | ||
| f.write(maybe_str) | ||
@@ -113,3 +112,4 @@ for name, detail in details.items(): | ||
| description="Export a subunit stream to files on disk.", | ||
| epilog=dedent("""\ | ||
| epilog=dedent( | ||
| """\ | ||
| Creates a directory per test id, a JSON file with test | ||
@@ -122,6 +122,6 @@ metadata within that directory, and each attachment | ||
| Exits 0 if the export was completed, or non-zero otherwise. | ||
| """)) | ||
| parser.add_option( | ||
| "-d", "--directory", help="Root directory to export to.", | ||
| default=".") | ||
| """ | ||
| ), | ||
| ) | ||
| parser.add_option("-d", "--directory", help="Root directory to export to.", default=".") | ||
| options, args = parser.parse_args(argv) | ||
@@ -131,3 +131,3 @@ if len(args) > 1: | ||
| if len(args): | ||
| source = io.open(args[0], 'rb') | ||
| source = io.open(args[0], "rb") | ||
| else: | ||
@@ -139,2 +139,1 @@ source = stdin | ||
| return 0 | ||
@@ -20,5 +20,4 @@ # | ||
| from testtools.compat import _b | ||
| empty = b"" | ||
| empty = _b('') | ||
@@ -50,7 +49,7 @@ class Decoder(object): | ||
| self.strict = strict | ||
| self._match_chars = _b("0123456789abcdefABCDEF\r\n") | ||
| self._slash_n = _b('\n') | ||
| self._slash_r = _b('\r') | ||
| self._slash_rn = _b('\r\n') | ||
| self._slash_nr = _b('\n\r') | ||
| self._match_chars = b"0123456789abcdefABCDEF\r\n" | ||
| self._slash_n = b"\n" | ||
| self._slash_r = b"\r" | ||
| self._slash_rn = b"\r\n" | ||
| self._slash_nr = b"\n\r" | ||
@@ -85,5 +84,4 @@ def close(self): | ||
| else: | ||
| self.output.write(self.buffered_bytes[0][:self.body_length]) | ||
| self.buffered_bytes[0] = \ | ||
| self.buffered_bytes[0][self.body_length:] | ||
| self.output.write(self.buffered_bytes[0][: self.body_length]) | ||
| self.buffered_bytes[0] = self.buffered_bytes[0][self.body_length :] | ||
| self.body_length = 0 | ||
@@ -98,3 +96,3 @@ self.state = self._read_length | ||
| for pos in range(len(bytes)): | ||
| byte = bytes[pos:pos+1] | ||
| byte = bytes[pos : pos + 1] | ||
| if byte not in self._match_chars: | ||
@@ -171,3 +169,3 @@ break | ||
| self.buffer_size = 0 | ||
| self.output.write(_b("%X\r\n" % (buffer_size + extra_len))) | ||
| self.output.write(b"%X\r\n" % (buffer_size + extra_len)) | ||
| if buffer_size: | ||
@@ -190,2 +188,2 @@ self.output.write(empty.join(buffered_bytes)) | ||
| self.flush() | ||
| self.output.write(_b("0\r\n")) | ||
| self.output.write(b"0\r\n") |
@@ -22,9 +22,8 @@ # | ||
| from testtools import content, content_type | ||
| from testtools.compat import _b | ||
| from subunit import chunked | ||
| end_marker = _b("]\n") | ||
| quoted_marker = _b(" ]") | ||
| empty = _b('') | ||
| end_marker = b"]\n" | ||
| quoted_marker = b" ]" | ||
| empty = b"" | ||
@@ -40,3 +39,3 @@ | ||
| def __init__(self, state): | ||
| self._message = _b("") | ||
| self._message = b"" | ||
| self._state = state | ||
@@ -60,14 +59,11 @@ | ||
| # or something like that. | ||
| result['traceback'] = content.Content( | ||
| content_type.ContentType("text", "x-traceback", | ||
| {"charset": "utf8"}), | ||
| lambda:[self._message]) | ||
| result["traceback"] = content.Content( | ||
| content_type.ContentType("text", "x-traceback", {"charset": "utf8"}), lambda: [self._message] | ||
| ) | ||
| else: | ||
| if style == 'skip': | ||
| name = 'reason' | ||
| if style == "skip": | ||
| name = "reason" | ||
| else: | ||
| name = 'message' | ||
| result[name] = content.Content( | ||
| content_type.ContentType("text", "plain"), | ||
| lambda:[self._message]) | ||
| name = "message" | ||
| result[name] = content.Content(content_type.ContentType("text", "plain"), lambda: [self._message]) | ||
| return result | ||
@@ -92,5 +88,5 @@ | ||
| # TODO error handling | ||
| field, value = line[:-1].decode('utf8').split(' ', 1) | ||
| field, value = line[:-1].decode("utf8").split(" ", 1) | ||
| try: | ||
| main, sub = value.split('/') | ||
| main, sub = value.split("/") | ||
| except ValueError: | ||
@@ -102,3 +98,3 @@ raise ValueError("Invalid MIME type %r" % value) | ||
| def _get_name(self, line): | ||
| self._name = line[:-1].decode('utf8') | ||
| self._name = line[:-1].decode("utf8") | ||
| self._body = BytesIO() | ||
@@ -112,6 +108,5 @@ self._chunk_parser = chunked.Decoder(self._body) | ||
| # Line based use always ends on no residue. | ||
| assert residue == empty, 'residue: %r' % (residue,) | ||
| assert residue == empty, "residue: %r" % (residue,) | ||
| body = self._body | ||
| self._details[self._name] = content.Content( | ||
| self._content_type, lambda:[body.getvalue()]) | ||
| self._details[self._name] = content.Content(self._content_type, lambda: [body.getvalue()]) | ||
| self._chunk_parser.close() | ||
@@ -118,0 +113,0 @@ self._parse_state = self._look_for_content |
@@ -36,8 +36,7 @@ #!/usr/bin/env python3 | ||
| (options, args) = parser.parse_args() | ||
| run_tests_from_stream(find_stream(sys.stdin, args), | ||
| ExtendedToStreamDecorator(StreamResultToBytes(sys.stdout))) | ||
| run_tests_from_stream(find_stream(sys.stdin, args), ExtendedToStreamDecorator(StreamResultToBytes(sys.stdout))) | ||
| sys.exit(0) | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -37,8 +37,7 @@ #!/usr/bin/env python3 | ||
| (options, args) = parser.parse_args() | ||
| case = ByteStreamToStreamResult( | ||
| find_stream(sys.stdin, args), non_subunit_name='stdout') | ||
| case = ByteStreamToStreamResult(find_stream(sys.stdin, args), non_subunit_name="stdout") | ||
| result = StreamToExtendedDecorator(TestProtocolClient(sys.stdout)) | ||
| result = StreamResultRouter(result) | ||
| cat = CatFiles(sys.stdout) | ||
| result.add_rule(cat, 'test_id', test_id=None) | ||
| result.add_rule(cat, "test_id", test_id=None) | ||
| result.startTestRun() | ||
@@ -50,3 +49,3 @@ case.run(result) | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -37,4 +37,3 @@ #!/usr/bin/env python3 | ||
| from subunit.filters import filter_by_result, find_stream | ||
| from subunit.test_results import (TestResultFilter, and_predicates, | ||
| make_tag_filter) | ||
| from subunit.test_results import TestResultFilter, and_predicates, make_tag_filter | ||
@@ -44,48 +43,68 @@ | ||
| parser = OptionParser(description=__doc__) | ||
| parser.add_option("--error", action="store_false", | ||
| help="include errors", default=False, dest="error") | ||
| parser.add_option("-e", "--no-error", action="store_true", | ||
| help="exclude errors", dest="error") | ||
| parser.add_option("--failure", action="store_false", | ||
| help="include failures", default=False, dest="failure") | ||
| parser.add_option("-f", "--no-failure", action="store_true", | ||
| help="exclude failures", dest="failure") | ||
| parser.add_option("--passthrough", action="store_false", | ||
| help="Forward non-subunit input as 'stdout'.", default=False, | ||
| dest="no_passthrough") | ||
| parser.add_option("--no-passthrough", action="store_true", | ||
| help="Discard all non subunit input.", default=False, | ||
| dest="no_passthrough") | ||
| parser.add_option("-s", "--success", action="store_false", | ||
| help="include successes", dest="success") | ||
| parser.add_option("--no-success", action="store_true", | ||
| help="exclude successes", default=True, dest="success") | ||
| parser.add_option("--no-skip", action="store_true", | ||
| help="exclude skips", dest="skip") | ||
| parser.add_option("--xfail", action="store_false", | ||
| help="include expected failures", default=True, dest="xfail") | ||
| parser.add_option("--no-xfail", action="store_true", | ||
| help="exclude expected failures", default=True, dest="xfail") | ||
| parser.add_option("--error", action="store_false", help="include errors", default=False, dest="error") | ||
| parser.add_option("-e", "--no-error", action="store_true", help="exclude errors", dest="error") | ||
| parser.add_option("--failure", action="store_false", help="include failures", default=False, dest="failure") | ||
| parser.add_option("-f", "--no-failure", action="store_true", help="exclude failures", dest="failure") | ||
| parser.add_option( | ||
| "--with-tag", type=str, | ||
| help="include tests with these tags", action="append", dest="with_tags") | ||
| "--passthrough", | ||
| action="store_false", | ||
| help="Forward non-subunit input as 'stdout'.", | ||
| default=False, | ||
| dest="no_passthrough", | ||
| ) | ||
| parser.add_option( | ||
| "--without-tag", type=str, | ||
| help="exclude tests with these tags", action="append", dest="without_tags") | ||
| parser.add_option("-m", "--with", type=str, | ||
| "--no-passthrough", | ||
| action="store_true", | ||
| help="Discard all non subunit input.", | ||
| default=False, | ||
| dest="no_passthrough", | ||
| ) | ||
| parser.add_option("-s", "--success", action="store_false", help="include successes", dest="success") | ||
| parser.add_option("--no-success", action="store_true", help="exclude successes", default=True, dest="success") | ||
| parser.add_option("--no-skip", action="store_true", help="exclude skips", dest="skip") | ||
| parser.add_option("--xfail", action="store_false", help="include expected failures", default=True, dest="xfail") | ||
| parser.add_option("--no-xfail", action="store_true", help="exclude expected failures", default=True, dest="xfail") | ||
| parser.add_option("--with-tag", type=str, help="include tests with these tags", action="append", dest="with_tags") | ||
| parser.add_option( | ||
| "--without-tag", type=str, help="exclude tests with these tags", action="append", dest="without_tags" | ||
| ) | ||
| parser.add_option( | ||
| "-m", | ||
| "--with", | ||
| type=str, | ||
| help="regexp to include (case-sensitive by default)", | ||
| action="append", dest="with_regexps") | ||
| parser.add_option("--fixup-expected-failures", type=str, | ||
| action="append", | ||
| dest="with_regexps", | ||
| ) | ||
| parser.add_option( | ||
| "--fixup-expected-failures", | ||
| type=str, | ||
| help="File with list of test ids that are expected to fail; on failure " | ||
| "their result will be changed to xfail; on success they will be " | ||
| "changed to error.", dest="fixup_expected_failures", action="append") | ||
| parser.add_option("--without", type=str, | ||
| "their result will be changed to xfail; on success they will be " | ||
| "changed to error.", | ||
| dest="fixup_expected_failures", | ||
| action="append", | ||
| ) | ||
| parser.add_option( | ||
| "--without", | ||
| type=str, | ||
| help="regexp to exclude (case-sensitive by default)", | ||
| action="append", dest="without_regexps") | ||
| parser.add_option("-F", "--only-genuine-failures", action="callback", | ||
| action="append", | ||
| dest="without_regexps", | ||
| ) | ||
| parser.add_option( | ||
| "-F", | ||
| "--only-genuine-failures", | ||
| action="callback", | ||
| callback=only_genuine_failures_callback, | ||
| help="Only pass through failures and exceptions.") | ||
| parser.add_option("--rename", action="append", nargs=2, | ||
| help="Apply specified regex subsitutions to test names.", | ||
| dest="renames", default=[]) | ||
| help="Only pass through failures and exceptions.", | ||
| ) | ||
| parser.add_option( | ||
| "--rename", | ||
| action="append", | ||
| nargs=2, | ||
| help="Apply specified regex substitutions to test names.", | ||
| dest="renames", | ||
| default=[], | ||
| ) | ||
| return parser | ||
@@ -95,6 +114,6 @@ | ||
| def only_genuine_failures_callback(option, opt, value, parser): | ||
| parser.rargs.insert(0, '--no-passthrough') | ||
| parser.rargs.insert(0, '--no-xfail') | ||
| parser.rargs.insert(0, '--no-skip') | ||
| parser.rargs.insert(0, '--no-success') | ||
| parser.rargs.insert(0, "--no-passthrough") | ||
| parser.rargs.insert(0, "--no-xfail") | ||
| parser.rargs.insert(0, "--no-skip") | ||
| parser.rargs.insert(0, "--no-success") | ||
@@ -123,2 +142,3 @@ | ||
| return True | ||
| return check_regexps | ||
@@ -129,5 +149,6 @@ | ||
| def rename(name): | ||
| for (from_pattern, to_pattern) in patterns: | ||
| for from_pattern, to_pattern in patterns: | ||
| name = re.sub(from_pattern, to_pattern, name) | ||
| return name | ||
| return rename | ||
@@ -141,13 +162,15 @@ | ||
| fixup_expected_failures.update(read_test_list(path)) | ||
| return StreamToExtendedDecorator(TestResultFilter( | ||
| ExtendedToStreamDecorator( | ||
| StreamResultToBytes(output)), | ||
| filter_error=options.error, | ||
| filter_failure=options.failure, | ||
| filter_success=options.success, | ||
| filter_skip=options.skip, | ||
| filter_xfail=options.xfail, | ||
| filter_predicate=predicate, | ||
| fixup_expected_failures=fixup_expected_failures, | ||
| rename=_compile_rename(options.renames))) | ||
| return StreamToExtendedDecorator( | ||
| TestResultFilter( | ||
| ExtendedToStreamDecorator(StreamResultToBytes(output)), | ||
| filter_error=options.error, | ||
| filter_failure=options.failure, | ||
| filter_success=options.success, | ||
| filter_skip=options.skip, | ||
| filter_xfail=options.xfail, | ||
| filter_predicate=predicate, | ||
| fixup_expected_failures=fixup_expected_failures, | ||
| rename=_compile_rename(options.renames), | ||
| ) | ||
| ) | ||
@@ -159,4 +182,3 @@ | ||
| regexp_filter = _make_regexp_filter( | ||
| options.with_regexps, options.without_regexps) | ||
| regexp_filter = _make_regexp_filter(options.with_regexps, options.without_regexps) | ||
| tag_filter = make_tag_filter(options.with_tags, options.without_tags) | ||
@@ -171,7 +193,8 @@ filter_predicate = and_predicates([regexp_filter, tag_filter]) | ||
| protocol_version=2, | ||
| input_stream=find_stream(sys.stdin, args)) | ||
| input_stream=find_stream(sys.stdin, args), | ||
| ) | ||
| sys.exit(0) | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -31,13 +31,20 @@ #!/usr/bin/env python | ||
| parser = OptionParser(description=__doc__) | ||
| parser.add_option("--times", action="store_true", | ||
| parser.add_option( | ||
| "--times", | ||
| action="store_true", | ||
| help="list the time each test took (requires a timestamped stream)", | ||
| default=False) | ||
| parser.add_option("--exists", action="store_true", | ||
| help="list tests that are reported as existing (as well as ran)", | ||
| default=False) | ||
| parser.add_option("--no-passthrough", action="store_true", | ||
| help="Hide all non subunit input.", default=False, dest="no_passthrough") | ||
| default=False, | ||
| ) | ||
| parser.add_option( | ||
| "--exists", action="store_true", help="list tests that are reported as existing (as well as ran)", default=False | ||
| ) | ||
| parser.add_option( | ||
| "--no-passthrough", | ||
| action="store_true", | ||
| help="Hide all non subunit input.", | ||
| default=False, | ||
| dest="no_passthrough", | ||
| ) | ||
| (options, args) = parser.parse_args() | ||
| test = ByteStreamToStreamResult( | ||
| find_stream(sys.stdin, args), non_subunit_name="stdout") | ||
| test = ByteStreamToStreamResult(find_stream(sys.stdin, args), non_subunit_name="stdout") | ||
| result = TestIdPrintingResult(sys.stdout, options.times, options.exists) | ||
@@ -47,3 +54,3 @@ if not options.no_passthrough: | ||
| cat = CatFiles(sys.stdout) | ||
| result.add_rule(cat, 'test_id', test_id=None) | ||
| result.add_rule(cat, "test_id", test_id=None) | ||
| summary = StreamSummary() | ||
@@ -61,3 +68,3 @@ result = CopyStreamResult([result, summary]) | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -9,3 +9,3 @@ #!/usr/bin/env python3 | ||
| # compliance with one of these two licences. | ||
| # | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
@@ -23,3 +23,3 @@ # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| gi.require_version('Gtk', '3.0') | ||
| gi.require_version("Gtk", "3.0") | ||
| from gi.repository import Notify # noqa: E402 | ||
@@ -52,7 +52,7 @@ from testtools import StreamToExtendedDecorator # noqa: E402 | ||
| run_filter_script( | ||
| lambda output:StreamToExtendedDecorator(TestResultStats(output)), | ||
| __doc__, notify_of_result, protocol_version=2) | ||
| lambda output: StreamToExtendedDecorator(TestResultStats(output)), __doc__, notify_of_result, protocol_version=2 | ||
| ) | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -28,3 +28,3 @@ #!/usr/bin/env python3 | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -9,3 +9,3 @@ #!/usr/bin/env python3 | ||
| # compliance with one of these two licences. | ||
| # | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
@@ -33,8 +33,13 @@ # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| r.decorated.formatStats() | ||
| run_filter_script( | ||
| lambda output:StreamToExtendedDecorator(result), | ||
| __doc__, show_stats, protocol_version=2, passthrough_subunit=False) | ||
| lambda output: StreamToExtendedDecorator(result), | ||
| __doc__, | ||
| show_stats, | ||
| protocol_version=2, | ||
| passthrough_subunit=False, | ||
| ) | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -9,3 +9,3 @@ #!/usr/bin/env python3 | ||
| # compliance with one of these two licences. | ||
| # | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
@@ -33,3 +33,3 @@ # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -26,7 +26,6 @@ #!/usr/bin/env python3 | ||
| def main(): | ||
| run_filter_script(lambda output: StreamToExtendedDecorator( | ||
| CsvResult(output)), __doc__, protocol_version=2) | ||
| run_filter_script(lambda output: StreamToExtendedDecorator(CsvResult(output)), __doc__, protocol_version=2) | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -28,3 +28,3 @@ #!/usr/bin/env python3 | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -54,8 +54,12 @@ #!/usr/bin/env python3 | ||
| gi.require_version('Gtk', '3.0') | ||
| from gi.repository import GObject, Gtk # noqa: E402 | ||
| gi.require_version("Gtk", "3.0") | ||
| from gi.repository import GObject, Gtk # noqa: E402 | ||
| from testtools import StreamToExtendedDecorator # noqa: E402 | ||
| from subunit import (PROGRESS_POP, PROGRESS_PUSH, PROGRESS_SET, # noqa: E402 | ||
| ByteStreamToStreamResult) | ||
| from subunit import ( # noqa: E402 | ||
| PROGRESS_POP, | ||
| PROGRESS_PUSH, | ||
| PROGRESS_SET, | ||
| ByteStreamToStreamResult, | ||
| ) | ||
| from subunit.progress_model import ProgressModel # noqa: E402 | ||
@@ -65,3 +69,2 @@ | ||
| class GTKTestResult(unittest.TestResult): | ||
| def __init__(self): | ||
@@ -110,26 +113,80 @@ super(GTKTestResult, self).__init__() | ||
| label = Gtk.Label(label="Run:") | ||
| table.attach(label, 0, 1, 1, 2, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 5, 5) | ||
| table.attach( | ||
| label, | ||
| 0, | ||
| 1, | ||
| 1, | ||
| 2, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| 5, | ||
| 5, | ||
| ) | ||
| label.show() | ||
| self.run_label = Gtk.Label(label="N/A") | ||
| table.attach(self.run_label, 1, 2, 1, 2, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 5, 5) | ||
| table.attach( | ||
| self.run_label, | ||
| 1, | ||
| 2, | ||
| 1, | ||
| 2, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| 5, | ||
| 5, | ||
| ) | ||
| self.run_label.show() | ||
| label = Gtk.Label(label="OK:") | ||
| table.attach(label, 0, 1, 2, 3, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 5, 5) | ||
| table.attach( | ||
| label, | ||
| 0, | ||
| 1, | ||
| 2, | ||
| 3, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| 5, | ||
| 5, | ||
| ) | ||
| label.show() | ||
| self.ok_label = Gtk.Label(label="N/A") | ||
| table.attach(self.ok_label, 1, 2, 2, 3, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 5, 5) | ||
| table.attach( | ||
| self.ok_label, | ||
| 1, | ||
| 2, | ||
| 2, | ||
| 3, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| 5, | ||
| 5, | ||
| ) | ||
| self.ok_label.show() | ||
| label = Gtk.Label(label="Not OK:") | ||
| table.attach(label, 0, 1, 3, 4, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 5, 5) | ||
| table.attach( | ||
| label, | ||
| 0, | ||
| 1, | ||
| 3, | ||
| 4, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| 5, | ||
| 5, | ||
| ) | ||
| label.show() | ||
| self.not_ok_label = Gtk.Label(label="N/A") | ||
| table.attach(self.not_ok_label, 1, 2, 3, 4, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 5, 5) | ||
| table.attach( | ||
| self.not_ok_label, | ||
| 1, | ||
| 2, | ||
| 3, | ||
| 4, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, | ||
| 5, | ||
| 5, | ||
| ) | ||
| self.not_ok_label.show() | ||
@@ -153,11 +210,8 @@ | ||
| width = self.progress_model.width() | ||
| percentage = (pos / float(width)) | ||
| percentage = pos / float(width) | ||
| self.pbar.set_fraction(percentage) | ||
| def stopTestRun(self): | ||
| try: | ||
| super(GTKTestResult, self).stopTestRun() | ||
| except AttributeError: | ||
| pass | ||
| GObject.idle_add(self.pbar.set_text, 'Finished') | ||
| super(GTKTestResult, self).stopTestRun() | ||
| GObject.idle_add(self.pbar.set_text, "Finished") | ||
@@ -204,2 +258,10 @@ def addError(self, test, err): | ||
| def addDuration(self, test, duration): | ||
| """Called to add a test duration. | ||
| :param test: The test that completed. | ||
| :param duration: The duration of the test as a float in seconds. | ||
| """ | ||
| pass | ||
| def update_counts(self): | ||
@@ -215,3 +277,3 @@ self.run_label.set_text(str(self.testsRun)) | ||
| result = StreamToExtendedDecorator(GTKTestResult()) | ||
| test = ByteStreamToStreamResult(sys.stdin, non_subunit_name='stdout') | ||
| test = ByteStreamToStreamResult(sys.stdin, non_subunit_name="stdout") | ||
| # Get setup | ||
@@ -225,2 +287,3 @@ while Gtk.events_pending(): | ||
| result.stopTestRun() | ||
| t = threading.Thread(target=run_and_finish) | ||
@@ -238,3 +301,3 @@ t.daemon = True | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -19,3 +19,2 @@ #!/usr/bin/env python3 | ||
| import sys | ||
@@ -30,4 +29,6 @@ | ||
| except ImportError: | ||
| sys.stderr.write("python-junitxml (https://launchpad.net/pyjunitxml or " | ||
| "http://pypi.python.org/pypi/junitxml) is required for this filter.") | ||
| sys.stderr.write( | ||
| "python-junitxml (https://launchpad.net/pyjunitxml or " | ||
| "http://pypi.python.org/pypi/junitxml) is required for this filter." | ||
| ) | ||
| raise | ||
@@ -37,8 +38,6 @@ | ||
| def main(): | ||
| run_filter_script( | ||
| lambda output: StreamToExtendedDecorator( | ||
| JUnitXmlResult(output)), __doc__, protocol_version=2) | ||
| run_filter_script(lambda output: StreamToExtendedDecorator(JUnitXmlResult(output)), __doc__, protocol_version=2) | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -24,4 +24,3 @@ #!/usr/bin/env python | ||
| from testtools import (DecorateTestCaseResult, StreamResultRouter, | ||
| StreamToExtendedDecorator) | ||
| from testtools import DecorateTestCaseResult, StreamResultRouter, StreamToExtendedDecorator | ||
@@ -35,11 +34,14 @@ from subunit import ByteStreamToStreamResult | ||
| parser = OptionParser(description=__doc__) | ||
| parser.add_option("--no-passthrough", action="store_true", | ||
| help="Hide all non subunit input.", | ||
| default=False, dest="no_passthrough") | ||
| parser.add_option("--progress", action="store_true", | ||
| help="Use bzrlib's test reporter (requires bzrlib)", | ||
| default=False) | ||
| parser.add_option( | ||
| "--no-passthrough", | ||
| action="store_true", | ||
| help="Hide all non subunit input.", | ||
| default=False, | ||
| dest="no_passthrough", | ||
| ) | ||
| parser.add_option( | ||
| "--progress", action="store_true", help="Use breezy 's test reporter (requires breezy)", default=False | ||
| ) | ||
| (options, args) = parser.parse_args() | ||
| test = ByteStreamToStreamResult( | ||
| find_stream(sys.stdin, args), non_subunit_name='stdout') | ||
| test = ByteStreamToStreamResult(find_stream(sys.stdin, args), non_subunit_name="stdout") | ||
@@ -50,10 +52,12 @@ def wrap_result(result): | ||
| result = StreamResultRouter(result) | ||
| result.add_rule(CatFiles(sys.stdout), 'test_id', test_id=None) | ||
| result.add_rule(CatFiles(sys.stdout), "test_id", test_id=None) | ||
| return result | ||
| test = DecorateTestCaseResult(test, wrap_result, | ||
| before_run=methodcaller('startTestRun'), | ||
| after_run=methodcaller('stopTestRun')) | ||
| test = DecorateTestCaseResult( | ||
| test, wrap_result, before_run=methodcaller("startTestRun"), after_run=methodcaller("stopTestRun") | ||
| ) | ||
| if options.progress: | ||
| from bzrlib import ui | ||
| from bzrlib.tests import TextTestRunner | ||
| from breezy import ui | ||
| from breezy.tests import TextTestRunner | ||
| ui.ui_factory = ui.make_ui_for_terminal(None, sys.stdout, sys.stderr) | ||
@@ -70,3 +74,3 @@ runner = TextTestRunner() | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -9,3 +9,3 @@ #!/usr/bin/env python3 | ||
| # compliance with one of these two licences. | ||
| # | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
@@ -33,3 +33,3 @@ # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -22,4 +22,3 @@ # subunit: extensions to python unittest to get test results from subprocesses. | ||
| from subunit import (ByteStreamToStreamResult, DiscardStream, ProtocolTestCase, | ||
| StreamResultToBytes) | ||
| from subunit import ByteStreamToStreamResult, DiscardStream, ProtocolTestCase, StreamResultToBytes | ||
| from subunit.test_results import CatFiles | ||
@@ -31,17 +30,22 @@ | ||
| parser.add_option( | ||
| "--no-passthrough", action="store_true", | ||
| help="Hide all non subunit input.", default=False, | ||
| dest="no_passthrough") | ||
| "--no-passthrough", | ||
| action="store_true", | ||
| help="Hide all non subunit input.", | ||
| default=False, | ||
| dest="no_passthrough", | ||
| ) | ||
| parser.add_option("-o", "--output-to", help="Send the output to this path rather than stdout.") | ||
| parser.add_option( | ||
| "-o", "--output-to", | ||
| help="Send the output to this path rather than stdout.") | ||
| parser.add_option( | ||
| "-f", "--forward", action="store_true", default=False, | ||
| help="Forward subunit stream on stdout. When set, received " | ||
| "non-subunit output will be encapsulated in subunit.") | ||
| "-f", | ||
| "--forward", | ||
| action="store_true", | ||
| default=False, | ||
| help="Forward subunit stream on stdout. When set, received non-subunit output will be encapsulated in subunit.", | ||
| ) | ||
| return parser | ||
| def run_tests_from_stream(input_stream, result, passthrough_stream=None, | ||
| forward_stream=None, protocol_version=1, passthrough_subunit=True): | ||
| def run_tests_from_stream( | ||
| input_stream, result, passthrough_stream=None, forward_stream=None, protocol_version=1, passthrough_subunit=True | ||
| ): | ||
| """Run tests from a subunit input stream through 'result'. | ||
@@ -71,7 +75,5 @@ | ||
| """ | ||
| if 1==protocol_version: | ||
| test = ProtocolTestCase( | ||
| input_stream, passthrough=passthrough_stream, | ||
| forward=forward_stream) | ||
| elif 2==protocol_version: | ||
| if 1 == protocol_version: | ||
| test = ProtocolTestCase(input_stream, passthrough=passthrough_stream, forward=forward_stream) | ||
| elif 2 == protocol_version: | ||
| # In all cases we encapsulate unknown inputs. | ||
@@ -85,3 +87,3 @@ if forward_stream is not None: | ||
| router = StreamResultRouter(forward_result) | ||
| router.add_rule(StreamResult(), 'test_id', test_id=None) | ||
| router.add_rule(StreamResult(), "test_id", test_id=None) | ||
| result = CopyStreamResult([router, result]) | ||
@@ -99,5 +101,4 @@ else: | ||
| result = StreamResultRouter(result) | ||
| result.add_rule(passthrough_result, 'test_id', test_id=None) | ||
| test = ByteStreamToStreamResult(input_stream, | ||
| non_subunit_name='stdout') | ||
| result.add_rule(passthrough_result, "test_id", test_id=None) | ||
| test = ByteStreamToStreamResult(input_stream, non_subunit_name="stdout") | ||
| else: | ||
@@ -110,5 +111,11 @@ raise Exception("Unknown protocol version.") | ||
| def filter_by_result(result_factory, output_path, passthrough, forward, | ||
| input_stream=sys.stdin, protocol_version=1, | ||
| passthrough_subunit=True): | ||
| def filter_by_result( | ||
| result_factory, | ||
| output_path, | ||
| passthrough, | ||
| forward, | ||
| input_stream=sys.stdin, | ||
| protocol_version=1, | ||
| passthrough_subunit=True, | ||
| ): | ||
| """Filter an input stream using a test result. | ||
@@ -134,3 +141,3 @@ | ||
| else: | ||
| if 1==protocol_version: | ||
| if 1 == protocol_version: | ||
| passthrough_stream = DiscardStream() | ||
@@ -142,3 +149,3 @@ else: | ||
| forward_stream = sys.stdout | ||
| elif 1==protocol_version: | ||
| elif 1 == protocol_version: | ||
| forward_stream = DiscardStream() | ||
@@ -151,3 +158,3 @@ else: | ||
| else: | ||
| output_to = open(output_path, 'w') | ||
| output_to = open(output_path, "w") | ||
@@ -157,5 +164,9 @@ try: | ||
| run_tests_from_stream( | ||
| input_stream, result, passthrough_stream, forward_stream, | ||
| input_stream, | ||
| result, | ||
| passthrough_stream, | ||
| forward_stream, | ||
| protocol_version=protocol_version, | ||
| passthrough_subunit=passthrough_subunit) | ||
| passthrough_subunit=passthrough_subunit, | ||
| ) | ||
| finally: | ||
@@ -167,4 +178,3 @@ if output_path: | ||
| def run_filter_script(result_factory, description, post_run_hook=None, | ||
| protocol_version=1, passthrough_subunit=True): | ||
| def run_filter_script(result_factory, description, post_run_hook=None, protocol_version=1, passthrough_subunit=True): | ||
| """Main function for simple subunit filter scripts. | ||
@@ -188,10 +198,12 @@ | ||
| result = filter_by_result( | ||
| result_factory, options.output_to, not options.no_passthrough, | ||
| options.forward, protocol_version=protocol_version, | ||
| result_factory, | ||
| options.output_to, | ||
| not options.no_passthrough, | ||
| options.forward, | ||
| protocol_version=protocol_version, | ||
| passthrough_subunit=passthrough_subunit, | ||
| input_stream=find_stream(sys.stdin, args)) | ||
| input_stream=find_stream(sys.stdin, args), | ||
| ) | ||
| if post_run_hook: | ||
| post_run_hook(result) | ||
| if not hasattr(result, 'wasSuccessful'): | ||
| result = result.decorated | ||
| if result.wasSuccessful(): | ||
@@ -213,4 +225,4 @@ sys.exit(0) | ||
| if argv: | ||
| return open(argv[0], 'rb') | ||
| return open(argv[0], "rb") | ||
| else: | ||
| return stdin |
@@ -9,3 +9,3 @@ # | ||
| # compliance with one of these two licences. | ||
| # | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
@@ -20,5 +20,6 @@ # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| class ProgressModel(object): | ||
| """A model of progress indicators as subunit defines it. | ||
| Instances of this class represent a single logical operation that is | ||
@@ -45,3 +46,3 @@ progressing. The operation may have many steps, and some of those steps may | ||
| """Create a ProgressModel. | ||
| The new model has no progress data at all - it will claim a summary | ||
@@ -109,2 +110,1 @@ width of zero and position of 0. | ||
| return task[1] | ||
+22
-26
@@ -5,3 +5,3 @@ #!/usr/bin/python3 | ||
| # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007 | ||
| # | ||
| # | ||
| # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause | ||
@@ -11,3 +11,3 @@ # license at the users choice. A copy of both licenses are available in the | ||
| # compliance with one of these two licences. | ||
| # | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
@@ -22,3 +22,3 @@ # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| $ python -m subunit.run mylib.tests.test_suite | ||
| $ python -m subunit.run mylib.tests.test_suite | ||
| """ | ||
@@ -31,4 +31,3 @@ | ||
| from testtools import ExtendedToStreamDecorator | ||
| from testtools.run import (BUFFEROUTPUT, CATCHBREAK, FAILFAST, USAGE_AS_MAIN, | ||
| TestProgram, list_test) | ||
| from testtools.run import BUFFEROUTPUT, CATCHBREAK, FAILFAST, USAGE_AS_MAIN, TestProgram, list_test | ||
@@ -40,4 +39,3 @@ from subunit import StreamResultToBytes | ||
| class SubunitTestRunner(object): | ||
| def __init__(self, verbosity=None, failfast=None, buffer=None, stream=None, | ||
| stdout=None, tb_locals=False): | ||
| def __init__(self, verbosity=None, failfast=None, buffer=None, stream=None, stdout=None, tb_locals=False): | ||
| """Create a TestToolsTestRunner. | ||
@@ -82,5 +80,6 @@ | ||
| if errors: | ||
| failed_descr = '\n'.join(errors).encode('utf8') | ||
| result.status(file_name="import errors", runnable=False, | ||
| file_bytes=failed_descr, mime_type="text/plain;charset=utf8") | ||
| failed_descr = "\n".join(errors).encode("utf8") | ||
| result.status( | ||
| file_name="import errors", runnable=False, file_bytes=failed_descr, mime_type="text/plain;charset=utf8" | ||
| ) | ||
| sys.exit(2) | ||
@@ -95,3 +94,3 @@ | ||
| if fileno is not None: | ||
| stream = os.fdopen(fileno, 'wb', 0) | ||
| stream = os.fdopen(fileno, "wb", 0) | ||
| else: | ||
@@ -101,3 +100,3 @@ stream = self.stream | ||
| for test_id in test_ids: | ||
| result.status(test_id=test_id, test_status='exists') | ||
| result.status(test_id=test_id, test_status="exists") | ||
| return result, errors | ||
@@ -107,3 +106,2 @@ | ||
| class SubunitTestProgram(TestProgram): | ||
| USAGE = USAGE_AS_MAIN | ||
@@ -113,16 +111,15 @@ | ||
| if msg: | ||
| print (msg) | ||
| usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '', | ||
| 'buffer': ''} | ||
| print(msg) | ||
| usage = {"progName": self.progName, "catchbreak": "", "failfast": "", "buffer": ""} | ||
| if self.failfast is not False: | ||
| usage['failfast'] = FAILFAST | ||
| usage["failfast"] = FAILFAST | ||
| if self.catchbreak is not False: | ||
| usage['catchbreak'] = CATCHBREAK | ||
| usage["catchbreak"] = CATCHBREAK | ||
| if self.buffer is not False: | ||
| usage['buffer'] = BUFFEROUTPUT | ||
| usage["buffer"] = BUFFEROUTPUT | ||
| usage_text = self.USAGE % usage | ||
| usage_lines = usage_text.split('\n') | ||
| usage_lines = usage_text.split("\n") | ||
| usage_lines.insert(2, "Run a test suite with a subunit reporter.") | ||
| usage_lines.insert(3, "") | ||
| print('\n'.join(usage_lines)) | ||
| print("\n".join(usage_lines)) | ||
| sys.exit(2) | ||
@@ -140,12 +137,11 @@ | ||
| # on non-ttys. | ||
| if hasattr(stdout, 'fileno'): | ||
| if hasattr(stdout, "fileno"): | ||
| # Patch stdout to be unbuffered, so that pdb works well on 2.6/2.7. | ||
| binstdout = io.open(stdout.fileno(), 'wb', 0) | ||
| binstdout = io.open(stdout.fileno(), "wb", 0) | ||
| sys.stdout = io.TextIOWrapper(binstdout, encoding=sys.stdout.encoding) | ||
| stdout = sys.stdout | ||
| SubunitTestProgram(module=None, argv=argv, testRunner=runner, | ||
| stdout=stdout, exit=False) | ||
| SubunitTestProgram(module=None, argv=argv, testRunner=runner, stdout=stdout, exit=False) | ||
| if __name__ == '__main__': | ||
| if __name__ == "__main__": | ||
| main() |
@@ -23,4 +23,3 @@ # | ||
| import testtools | ||
| from testtools import StreamResult | ||
| from testtools.content import TracebackContent, text_content | ||
| from testtools import StreamResult, TestResultDecorator, TestByTestResult | ||
@@ -31,82 +30,2 @@ import iso8601 | ||
| # NOT a TestResult, because we are implementing the interface, not inheriting | ||
| # it. | ||
| class TestResultDecorator: | ||
| """General pass-through decorator. | ||
| This provides a base that other TestResults can inherit from to | ||
| gain basic forwarding functionality. It also takes care of | ||
| handling the case where the target doesn't support newer methods | ||
| or features by degrading them. | ||
| """ | ||
| # XXX: Since lp:testtools r250, this is in testtools. Once it's released, | ||
| # we should gut this and just use that. | ||
| def __init__(self, decorated): | ||
| """Create a TestResultDecorator forwarding to decorated.""" | ||
| # Make every decorator degrade gracefully. | ||
| self.decorated = testtools.ExtendedToOriginalDecorator(decorated) | ||
| def startTest(self, test): | ||
| return self.decorated.startTest(test) | ||
| def startTestRun(self): | ||
| return self.decorated.startTestRun() | ||
| def stopTest(self, test): | ||
| return self.decorated.stopTest(test) | ||
| def stopTestRun(self): | ||
| return self.decorated.stopTestRun() | ||
| def addError(self, test, err=None, details=None): | ||
| return self.decorated.addError(test, err, details=details) | ||
| def addFailure(self, test, err=None, details=None): | ||
| return self.decorated.addFailure(test, err, details=details) | ||
| def addSuccess(self, test, details=None): | ||
| return self.decorated.addSuccess(test, details=details) | ||
| def addSkip(self, test, reason=None, details=None): | ||
| return self.decorated.addSkip(test, reason, details=details) | ||
| def addExpectedFailure(self, test, err=None, details=None): | ||
| return self.decorated.addExpectedFailure(test, err, details=details) | ||
| def addUnexpectedSuccess(self, test, details=None): | ||
| return self.decorated.addUnexpectedSuccess(test, details=details) | ||
| def _get_failfast(self): | ||
| return getattr(self.decorated, 'failfast', False) | ||
| def _set_failfast(self, value): | ||
| self.decorated.failfast = value | ||
| failfast = property(_get_failfast, _set_failfast) | ||
| def progress(self, offset, whence): | ||
| return self.decorated.progress(offset, whence) | ||
| def wasSuccessful(self): | ||
| return self.decorated.wasSuccessful() | ||
| @property | ||
| def shouldStop(self): | ||
| return self.decorated.shouldStop | ||
| def stop(self): | ||
| return self.decorated.stop() | ||
| @property | ||
| def testsRun(self): | ||
| return self.decorated.testsRun | ||
| def tags(self, new_tags, gone_tags): | ||
| return self.decorated.tags(new_tags, gone_tags) | ||
| def time(self, a_datetime): | ||
| return self.decorated.time(a_datetime) | ||
| class HookedTestResultDecorator(TestResultDecorator): | ||
@@ -116,2 +35,4 @@ """A TestResult which calls a hook on every event.""" | ||
| def __init__(self, decorated): | ||
| # Wrap with ExtendedToOriginalDecorator to handle unittest TestResults | ||
| decorated = testtools.ExtendedToOriginalDecorator(decorated) | ||
| self.super = super() | ||
@@ -181,3 +102,16 @@ self.super.__init__(decorated) | ||
| def addDuration(self, test, duration): | ||
| self._before_event() | ||
| if hasattr(self.decorated, "addDuration"): | ||
| return self.decorated.addDuration(test, duration) | ||
| def _get_failfast(self): | ||
| return getattr(self.decorated, "failfast", False) | ||
| def _set_failfast(self, value): | ||
| self.decorated.failfast = value | ||
| failfast = property(_get_failfast, _set_failfast) | ||
| class AutoTimingTestResultDecorator(HookedTestResultDecorator): | ||
@@ -222,3 +156,2 @@ """Decorate a TestResult to add time events to a test run. | ||
| class TagsMixin: | ||
| def __init__(self): | ||
@@ -318,3 +251,2 @@ self._clear_tags() | ||
| """Return a predicate that is true iff all predicates are true.""" | ||
| # XXX: Should probably be in testtools to be better used by matchers. jml | ||
| return lambda *args, **kwargs: all(p(*args, **kwargs) for p in predicates) | ||
@@ -340,8 +272,6 @@ | ||
| class _PredicateFilter(TestResultDecorator, TagsMixin): | ||
| def __init__(self, result, predicate): | ||
| super().__init__(result) | ||
| self._clear_tags() | ||
| self.decorated = TimeCollapsingDecorator( | ||
| TagCollapsingDecorator(self.decorated)) | ||
| self.decorated = TimeCollapsingDecorator(TagCollapsingDecorator(self.decorated)) | ||
| self._predicate = predicate | ||
@@ -356,9 +286,7 @@ # The current test (for filtering tags) | ||
| def filter_predicate(self, test, outcome, error, details): | ||
| return self._predicate( | ||
| test, outcome, error, details, self._get_active_tags()) | ||
| return self._predicate(test, outcome, error, details, self._get_active_tags()) | ||
| def addError(self, test, err=None, details=None): | ||
| if (self.filter_predicate(test, 'error', err, details)): | ||
| self._buffered_calls.append( | ||
| ('addError', [test, err], {'details': details})) | ||
| if self.filter_predicate(test, "error", err, details): | ||
| self._buffered_calls.append(("addError", [test, err], {"details": details})) | ||
| else: | ||
@@ -368,5 +296,4 @@ self._filtered() | ||
| def addFailure(self, test, err=None, details=None): | ||
| if (self.filter_predicate(test, 'failure', err, details)): | ||
| self._buffered_calls.append( | ||
| ('addFailure', [test, err], {'details': details})) | ||
| if self.filter_predicate(test, "failure", err, details): | ||
| self._buffered_calls.append(("addFailure", [test, err], {"details": details})) | ||
| else: | ||
@@ -376,5 +303,4 @@ self._filtered() | ||
| def addSkip(self, test, reason=None, details=None): | ||
| if (self.filter_predicate(test, 'skip', reason, details)): | ||
| self._buffered_calls.append( | ||
| ('addSkip', [test, reason], {'details': details})) | ||
| if self.filter_predicate(test, "skip", reason, details): | ||
| self._buffered_calls.append(("addSkip", [test, reason], {"details": details})) | ||
| else: | ||
@@ -384,5 +310,4 @@ self._filtered() | ||
| def addExpectedFailure(self, test, err=None, details=None): | ||
| if self.filter_predicate(test, 'expectedfailure', err, details): | ||
| self._buffered_calls.append( | ||
| ('addExpectedFailure', [test, err], {'details': details})) | ||
| if self.filter_predicate(test, "expectedfailure", err, details): | ||
| self._buffered_calls.append(("addExpectedFailure", [test, err], {"details": details})) | ||
| else: | ||
@@ -392,9 +317,7 @@ self._filtered() | ||
| def addUnexpectedSuccess(self, test, details=None): | ||
| self._buffered_calls.append( | ||
| ('addUnexpectedSuccess', [test], {'details': details})) | ||
| self._buffered_calls.append(("addUnexpectedSuccess", [test], {"details": details})) | ||
| def addSuccess(self, test, details=None): | ||
| if (self.filter_predicate(test, 'success', None, details)): | ||
| self._buffered_calls.append( | ||
| ('addSuccess', [test], {'details': details})) | ||
| if self.filter_predicate(test, "success", None, details): | ||
| self._buffered_calls.append(("addSuccess", [test], {"details": details})) | ||
| else: | ||
@@ -415,3 +338,3 @@ self._filtered() | ||
| self._current_test_filtered = False | ||
| self._buffered_calls.append(('startTest', [test], {})) | ||
| self._buffered_calls.append(("startTest", [test], {})) | ||
@@ -436,3 +359,3 @@ def stopTest(self, test): | ||
| if self._current_test is not None: | ||
| self._buffered_calls.append(('tags', [new_tags, gone_tags], {})) | ||
| self._buffered_calls.append(("tags", [new_tags, gone_tags], {})) | ||
| else: | ||
@@ -446,3 +369,3 @@ return super().tags(new_tags, gone_tags) | ||
| if id.startswith("subunit.RemotedTestCase."): | ||
| return id[len("subunit.RemotedTestCase."):] | ||
| return id[len("subunit.RemotedTestCase.") :] | ||
| return id | ||
@@ -463,6 +386,14 @@ | ||
| def __init__(self, result, filter_error=False, filter_failure=False, | ||
| filter_success=True, filter_skip=False, filter_xfail=False, | ||
| filter_predicate=None, fixup_expected_failures=None, | ||
| rename=None): | ||
| def __init__( | ||
| self, | ||
| result, | ||
| filter_error=False, | ||
| filter_failure=False, | ||
| filter_success=True, | ||
| filter_skip=False, | ||
| filter_xfail=False, | ||
| filter_predicate=None, | ||
| fixup_expected_failures=None, | ||
| rename=None, | ||
| ): | ||
| """Create a FilterResult object filtering to result. | ||
@@ -488,28 +419,23 @@ | ||
| if filter_error: | ||
| predicates.append( | ||
| lambda t, outcome, e, d, tags: outcome != 'error') | ||
| predicates.append(lambda t, outcome, e, d, tags: outcome != "error") | ||
| if filter_failure: | ||
| predicates.append( | ||
| lambda t, outcome, e, d, tags: outcome != 'failure') | ||
| predicates.append(lambda t, outcome, e, d, tags: outcome != "failure") | ||
| if filter_success: | ||
| predicates.append( | ||
| lambda t, outcome, e, d, tags: outcome != 'success') | ||
| predicates.append(lambda t, outcome, e, d, tags: outcome != "success") | ||
| if filter_skip: | ||
| predicates.append( | ||
| lambda t, outcome, e, d, tags: outcome != 'skip') | ||
| predicates.append(lambda t, outcome, e, d, tags: outcome != "skip") | ||
| if filter_xfail: | ||
| predicates.append( | ||
| lambda t, outcome, e, d, tags: outcome != 'expectedfailure') | ||
| predicates.append(lambda t, outcome, e, d, tags: outcome != "expectedfailure") | ||
| if filter_predicate is not None: | ||
| def compat(test, outcome, error, details, tags): | ||
| # 0.0.7 and earlier did not support the 'tags' parameter. | ||
| try: | ||
| return filter_predicate( | ||
| test, outcome, error, details, tags) | ||
| return filter_predicate(test, outcome, error, details, tags) | ||
| except TypeError: | ||
| return filter_predicate(test, outcome, error, details) | ||
| predicates.append(compat) | ||
| predicate = and_predicates(predicates) | ||
| super().__init__( | ||
| _PredicateFilter(result, predicate)) | ||
| super().__init__(_PredicateFilter(result, predicate)) | ||
| if fixup_expected_failures is None: | ||
@@ -526,4 +452,3 @@ self._fixup_expected_failures = frozenset() | ||
| else: | ||
| super().addError( | ||
| test, err=err, details=details) | ||
| super().addError(test, err=err, details=details) | ||
@@ -535,4 +460,3 @@ def addFailure(self, test, err=None, details=None): | ||
| else: | ||
| super().addFailure( | ||
| test, err=err, details=details) | ||
| super().addFailure(test, err=err, details=details) | ||
@@ -547,3 +471,3 @@ def addSuccess(self, test, details=None): | ||
| def _failure_expected(self, test): | ||
| return (test.id() in self._fixup_expected_failures) | ||
| return test.id() in self._fixup_expected_failures | ||
@@ -554,3 +478,2 @@ def _apply_renames(self, test): | ||
| new_id = self._rename_fn(test.id()) | ||
| # TODO(jelmer): Isn't there a cleaner way of doing this? | ||
| setattr(test, "id", lambda: new_id) | ||
@@ -606,5 +529,5 @@ return test | ||
| seconds += duration.microseconds / 1000000.0 | ||
| self._stream.write(test_id + ' %0.3f\n' % seconds) | ||
| self._stream.write(test_id + " %0.3f\n" % seconds) | ||
| else: | ||
| self._stream.write(test_id + '\n') | ||
| self._stream.write(test_id + "\n") | ||
@@ -614,5 +537,15 @@ def startTest(self, test): | ||
| def status(self, test_id=None, test_status=None, test_tags=None, | ||
| runnable=True, file_name=None, file_bytes=None, eof=False, | ||
| mime_type=None, route_code=None, timestamp=None): | ||
| def status( | ||
| self, | ||
| test_id=None, | ||
| test_status=None, | ||
| test_tags=None, | ||
| runnable=True, | ||
| file_name=None, | ||
| file_bytes=None, | ||
| eof=False, | ||
| mime_type=None, | ||
| route_code=None, | ||
| timestamp=None, | ||
| ): | ||
| if not test_id: | ||
@@ -622,6 +555,6 @@ return | ||
| self.time(timestamp) | ||
| if test_status=='exists': | ||
| if test_status == "exists": | ||
| if self.show_exists: | ||
| self.reportTest(test_id, 0) | ||
| elif test_status in ('inprogress', None): | ||
| elif test_status in ("inprogress", None): | ||
| self._active_tests[test_id] = self._time() | ||
@@ -653,2 +586,10 @@ else: | ||
| def addDuration(self, test, duration): | ||
| """Called to add a test duration. | ||
| :param test: The test that completed. | ||
| :param duration: The duration of the test as a float in seconds. | ||
| """ | ||
| pass | ||
| def stopTestRun(self): | ||
@@ -659,84 +600,3 @@ for test_id in list(self._active_tests.keys()): | ||
| class TestByTestResult(testtools.TestResult): | ||
| """Call something every time a test completes.""" | ||
| # XXX: In testtools since lp:testtools r249. Once that's released, just | ||
| # import that. | ||
| def __init__(self, on_test): | ||
| """Construct a ``TestByTestResult``. | ||
| :param on_test: A callable that take a test case, a status (one of | ||
| "success", "failure", "error", "skip", or "xfail"), a start time | ||
| (a ``datetime`` with timezone), a stop time, an iterable of tags, | ||
| and a details dict. Is called at the end of each test (i.e. on | ||
| ``stopTest``) with the accumulated values for that test. | ||
| """ | ||
| super().__init__() | ||
| self._on_test = on_test | ||
| def startTest(self, test): | ||
| super().startTest(test) | ||
| self._start_time = self._now() | ||
| # There's no supported (i.e. tested) behaviour that relies on these | ||
| # being set, but it makes me more comfortable all the same. -- jml | ||
| self._status = None | ||
| self._details = None | ||
| self._stop_time = None | ||
| def stopTest(self, test): | ||
| self._stop_time = self._now() | ||
| super().stopTest(test) | ||
| self._on_test( | ||
| test=test, | ||
| status=self._status, | ||
| start_time=self._start_time, | ||
| stop_time=self._stop_time, | ||
| # current_tags is new in testtools 0.9.13. | ||
| tags=getattr(self, 'current_tags', None), | ||
| details=self._details) | ||
| def _err_to_details(self, test, err, details): | ||
| if details: | ||
| return details | ||
| return {'traceback': TracebackContent(err, test)} | ||
| def addSuccess(self, test, details=None): | ||
| super().addSuccess(test) | ||
| self._status = 'success' | ||
| self._details = details | ||
| def addFailure(self, test, err=None, details=None): | ||
| super().addFailure(test, err, details) | ||
| self._status = 'failure' | ||
| self._details = self._err_to_details(test, err, details) | ||
| def addError(self, test, err=None, details=None): | ||
| super().addError(test, err, details) | ||
| self._status = 'error' | ||
| self._details = self._err_to_details(test, err, details) | ||
| def addSkip(self, test, reason=None, details=None): | ||
| super().addSkip(test, reason, details) | ||
| self._status = 'skip' | ||
| if details is None: | ||
| details = {'reason': text_content(reason)} | ||
| elif reason: | ||
| # XXX: What if details already has 'reason' key? | ||
| details['reason'] = text_content(reason) | ||
| self._details = details | ||
| def addExpectedFailure(self, test, err=None, details=None): | ||
| super().addExpectedFailure(test, err, details) | ||
| self._status = 'xfail' | ||
| self._details = self._err_to_details(test, err, details) | ||
| def addUnexpectedSuccess(self, test, details=None): | ||
| super().addUnexpectedSuccess(test, details) | ||
| self._status = 'success' | ||
| self._details = details | ||
| class CsvResult(TestByTestResult): | ||
| def __init__(self, stream): | ||
@@ -751,3 +611,3 @@ super().__init__(self._on_test) | ||
| super().startTestRun() | ||
| self._write_row(['test', 'status', 'start_time', 'stop_time']) | ||
| self._write_row(["test", "status", "start_time", "stop_time"]) | ||
@@ -761,7 +621,17 @@ | ||
| def status(self, test_id=None, test_status=None, test_tags=None, | ||
| runnable=True, file_name=None, file_bytes=None, eof=False, | ||
| mime_type=None, route_code=None, timestamp=None): | ||
| def status( | ||
| self, | ||
| test_id=None, | ||
| test_status=None, | ||
| test_tags=None, | ||
| runnable=True, | ||
| file_name=None, | ||
| file_bytes=None, | ||
| eof=False, | ||
| mime_type=None, | ||
| route_code=None, | ||
| timestamp=None, | ||
| ): | ||
| if file_name is not None: | ||
| self.stream.write(file_bytes) | ||
| self.stream.flush() |
@@ -29,8 +29,18 @@ # | ||
| from subunit.tests import (test_chunked, test_details, test_filter_to_disk, # noqa: E402 | ||
| test_filters, test_output_filter, | ||
| test_progress_model, test_run, test_subunit_filter, | ||
| test_subunit_stats, test_subunit_tags, | ||
| test_tap2subunit, test_test_protocol, | ||
| test_test_protocol2, test_test_results) | ||
| from subunit.tests import ( # noqa: E402 | ||
| test_chunked, | ||
| test_details, | ||
| test_filter_to_disk, | ||
| test_filters, | ||
| test_output_filter, | ||
| test_progress_model, | ||
| test_run, | ||
| test_subunit_filter, | ||
| test_subunit_stats, | ||
| test_subunit_tags, | ||
| test_tap2subunit, | ||
| test_test_protocol, | ||
| test_test_protocol2, | ||
| test_test_results, | ||
| ) | ||
@@ -53,5 +63,3 @@ | ||
| result.addTest(loader.loadTestsFromModule(test_run)) | ||
| result.addTests( | ||
| generate_scenarios(loader.loadTestsFromModule(test_output_filter)) | ||
| ) | ||
| result.addTests(generate_scenarios(loader.loadTestsFromModule(test_output_filter))) | ||
| return result |
@@ -7,5 +7,6 @@ #!/usr/bin/env python3 | ||
| import os | ||
| msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) | ||
| if len(sys.argv) == 2: | ||
| # subunit.tests.test_test_protocol.TestExecTestCase.test_sample_method_args | ||
| # subunit.tests.test_test_protocol.TestExecTestCase.test_sample_method_args | ||
| # uses this code path to be sure that the arguments were passed to | ||
@@ -12,0 +13,0 @@ # sample-script.py |
@@ -21,3 +21,2 @@ # | ||
| from testtools.compat import _b | ||
@@ -28,3 +27,2 @@ import subunit.chunked | ||
| class TestDecode(unittest.TestCase): | ||
| def setUp(self): | ||
@@ -39,50 +37,50 @@ unittest.TestCase.setUp(self) | ||
| def test_close_body_short_errors(self): | ||
| self.assertEqual(None, self.decoder.write(_b('2\r\na'))) | ||
| self.assertEqual(None, self.decoder.write(b"2\r\na")) | ||
| self.assertRaises(ValueError, self.decoder.close) | ||
| def test_close_body_buffered_data_errors(self): | ||
| self.assertEqual(None, self.decoder.write(_b('2\r'))) | ||
| self.assertEqual(None, self.decoder.write(b"2\r")) | ||
| self.assertRaises(ValueError, self.decoder.close) | ||
| def test_close_after_finished_stream_safe(self): | ||
| self.assertEqual(None, self.decoder.write(_b('2\r\nab'))) | ||
| self.assertEqual(_b(''), self.decoder.write(_b('0\r\n'))) | ||
| self.assertEqual(None, self.decoder.write(b"2\r\nab")) | ||
| self.assertEqual(b"", self.decoder.write(b"0\r\n")) | ||
| self.decoder.close() | ||
| def test_decode_nothing(self): | ||
| self.assertEqual(_b(''), self.decoder.write(_b('0\r\n'))) | ||
| self.assertEqual(_b(''), self.output.getvalue()) | ||
| self.assertEqual(b"", self.decoder.write(b"0\r\n")) | ||
| self.assertEqual(b"", self.output.getvalue()) | ||
| def test_decode_serialised_form(self): | ||
| self.assertEqual(None, self.decoder.write(_b("F\r\n"))) | ||
| self.assertEqual(None, self.decoder.write(_b("serialised\n"))) | ||
| self.assertEqual(_b(''), self.decoder.write(_b("form0\r\n"))) | ||
| self.assertEqual(None, self.decoder.write(b"F\r\n")) | ||
| self.assertEqual(None, self.decoder.write(b"serialised\n")) | ||
| self.assertEqual(b"", self.decoder.write(b"form0\r\n")) | ||
| def test_decode_short(self): | ||
| self.assertEqual(_b(''), self.decoder.write(_b('3\r\nabc0\r\n'))) | ||
| self.assertEqual(_b('abc'), self.output.getvalue()) | ||
| self.assertEqual(b"", self.decoder.write(b"3\r\nabc0\r\n")) | ||
| self.assertEqual(b"abc", self.output.getvalue()) | ||
| def test_decode_combines_short(self): | ||
| self.assertEqual(_b(''), self.decoder.write(_b('6\r\nabcdef0\r\n'))) | ||
| self.assertEqual(_b('abcdef'), self.output.getvalue()) | ||
| self.assertEqual(b"", self.decoder.write(b"6\r\nabcdef0\r\n")) | ||
| self.assertEqual(b"abcdef", self.output.getvalue()) | ||
| def test_decode_excess_bytes_from_write(self): | ||
| self.assertEqual(_b('1234'), self.decoder.write(_b('3\r\nabc0\r\n1234'))) | ||
| self.assertEqual(_b('abc'), self.output.getvalue()) | ||
| self.assertEqual(b"1234", self.decoder.write(b"3\r\nabc0\r\n1234")) | ||
| self.assertEqual(b"abc", self.output.getvalue()) | ||
| def test_decode_write_after_finished_errors(self): | ||
| self.assertEqual(_b('1234'), self.decoder.write(_b('3\r\nabc0\r\n1234'))) | ||
| self.assertRaises(ValueError, self.decoder.write, _b('')) | ||
| self.assertEqual(b"1234", self.decoder.write(b"3\r\nabc0\r\n1234")) | ||
| self.assertRaises(ValueError, self.decoder.write, b"") | ||
| def test_decode_hex(self): | ||
| self.assertEqual(_b(''), self.decoder.write(_b('A\r\n12345678900\r\n'))) | ||
| self.assertEqual(_b('1234567890'), self.output.getvalue()) | ||
| self.assertEqual(b"", self.decoder.write(b"A\r\n12345678900\r\n")) | ||
| self.assertEqual(b"1234567890", self.output.getvalue()) | ||
| def test_decode_long_ranges(self): | ||
| self.assertEqual(None, self.decoder.write(_b('10000\r\n'))) | ||
| self.assertEqual(None, self.decoder.write(_b('1' * 65536))) | ||
| self.assertEqual(None, self.decoder.write(_b('10000\r\n'))) | ||
| self.assertEqual(None, self.decoder.write(_b('2' * 65536))) | ||
| self.assertEqual(_b(''), self.decoder.write(_b('0\r\n'))) | ||
| self.assertEqual(_b('1' * 65536 + '2' * 65536), self.output.getvalue()) | ||
| self.assertEqual(None, self.decoder.write(b"10000\r\n")) | ||
| self.assertEqual(None, self.decoder.write(b"1" * 65536)) | ||
| self.assertEqual(None, self.decoder.write(b"10000\r\n")) | ||
| self.assertEqual(None, self.decoder.write(b"2" * 65536)) | ||
| self.assertEqual(b"", self.decoder.write(b"0\r\n")) | ||
| self.assertEqual(b"1" * 65536 + b"2" * 65536, self.output.getvalue()) | ||
@@ -93,6 +91,6 @@ def test_decode_newline_nonstrict(self): | ||
| self.decoder = subunit.chunked.Decoder(self.output, strict=False) | ||
| self.assertEqual(None, self.decoder.write(_b('a\n'))) | ||
| self.assertEqual(None, self.decoder.write(_b('abcdeabcde'))) | ||
| self.assertEqual(_b(''), self.decoder.write(_b('0\n'))) | ||
| self.assertEqual(_b('abcdeabcde'), self.output.getvalue()) | ||
| self.assertEqual(None, self.decoder.write(b"a\n")) | ||
| self.assertEqual(None, self.decoder.write(b"abcdeabcde")) | ||
| self.assertEqual(b"", self.decoder.write(b"0\n")) | ||
| self.assertEqual(b"abcdeabcde", self.output.getvalue()) | ||
@@ -102,16 +100,12 @@ def test_decode_strict_newline_only(self): | ||
| # From <http://pad.lv/505078> | ||
| self.assertRaises(ValueError, | ||
| self.decoder.write, _b('a\n')) | ||
| self.assertRaises(ValueError, self.decoder.write, b"a\n") | ||
| def test_decode_strict_multiple_crs(self): | ||
| self.assertRaises(ValueError, | ||
| self.decoder.write, _b('a\r\r\n')) | ||
| self.assertRaises(ValueError, self.decoder.write, b"a\r\r\n") | ||
| def test_decode_short_header(self): | ||
| self.assertRaises(ValueError, | ||
| self.decoder.write, _b('\n')) | ||
| self.assertRaises(ValueError, self.decoder.write, b"\n") | ||
| class TestEncode(unittest.TestCase): | ||
| def setUp(self): | ||
@@ -124,30 +118,29 @@ unittest.TestCase.setUp(self) | ||
| self.encoder.close() | ||
| self.assertEqual(_b('0\r\n'), self.output.getvalue()) | ||
| self.assertEqual(b"0\r\n", self.output.getvalue()) | ||
| def test_encode_empty(self): | ||
| self.encoder.write(_b('')) | ||
| self.encoder.write(b"") | ||
| self.encoder.close() | ||
| self.assertEqual(_b('0\r\n'), self.output.getvalue()) | ||
| self.assertEqual(b"0\r\n", self.output.getvalue()) | ||
| def test_encode_short(self): | ||
| self.encoder.write(_b('abc')) | ||
| self.encoder.write(b"abc") | ||
| self.encoder.close() | ||
| self.assertEqual(_b('3\r\nabc0\r\n'), self.output.getvalue()) | ||
| self.assertEqual(b"3\r\nabc0\r\n", self.output.getvalue()) | ||
| def test_encode_combines_short(self): | ||
| self.encoder.write(_b('abc')) | ||
| self.encoder.write(_b('def')) | ||
| self.encoder.write(b"abc") | ||
| self.encoder.write(b"def") | ||
| self.encoder.close() | ||
| self.assertEqual(_b('6\r\nabcdef0\r\n'), self.output.getvalue()) | ||
| self.assertEqual(b"6\r\nabcdef0\r\n", self.output.getvalue()) | ||
| def test_encode_over_9_is_in_hex(self): | ||
| self.encoder.write(_b('1234567890')) | ||
| self.encoder.write(b"1234567890") | ||
| self.encoder.close() | ||
| self.assertEqual(_b('A\r\n12345678900\r\n'), self.output.getvalue()) | ||
| self.assertEqual(b"A\r\n12345678900\r\n", self.output.getvalue()) | ||
| def test_encode_long_ranges_not_combined(self): | ||
| self.encoder.write(_b('1' * 65536)) | ||
| self.encoder.write(_b('2' * 65536)) | ||
| self.encoder.write(b"1" * 65536) | ||
| self.encoder.write(b"2" * 65536) | ||
| self.encoder.close() | ||
| self.assertEqual(_b('10000\r\n' + '1' * 65536 + '10000\r\n' + | ||
| '2' * 65536 + '0\r\n'), self.output.getvalue()) | ||
| self.assertEqual(b"10000\r\n" + b"1" * 65536 + b"10000\r\n" + b"2" * 65536 + b"0\r\n", self.output.getvalue()) |
@@ -9,3 +9,3 @@ # | ||
| # compliance with one of these two licences. | ||
| # | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
@@ -20,3 +20,2 @@ # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| from testtools.compat import _b | ||
@@ -27,19 +26,18 @@ from subunit import content, content_type, details | ||
| class TestSimpleDetails(unittest.TestCase): | ||
| def test_lineReceived(self): | ||
| parser = details.SimpleDetailsParser(None) | ||
| parser.lineReceived(_b("foo\n")) | ||
| parser.lineReceived(_b("bar\n")) | ||
| self.assertEqual(_b("foo\nbar\n"), parser._message) | ||
| parser.lineReceived(b"foo\n") | ||
| parser.lineReceived(b"bar\n") | ||
| self.assertEqual(b"foo\nbar\n", parser._message) | ||
| def test_lineReceived_escaped_bracket(self): | ||
| parser = details.SimpleDetailsParser(None) | ||
| parser.lineReceived(_b("foo\n")) | ||
| parser.lineReceived(_b(" ]are\n")) | ||
| parser.lineReceived(_b("bar\n")) | ||
| self.assertEqual(_b("foo\n]are\nbar\n"), parser._message) | ||
| parser.lineReceived(b"foo\n") | ||
| parser.lineReceived(b" ]are\n") | ||
| parser.lineReceived(b"bar\n") | ||
| self.assertEqual(b"foo\n]are\nbar\n", parser._message) | ||
| def test_get_message(self): | ||
| parser = details.SimpleDetailsParser(None) | ||
| self.assertEqual(_b(""), parser.get_message()) | ||
| self.assertEqual(b"", parser.get_message()) | ||
@@ -49,12 +47,9 @@ def test_get_details(self): | ||
| expected = {} | ||
| expected['traceback'] = content.Content( | ||
| content_type.ContentType("text", "x-traceback", | ||
| {'charset': 'utf8'}), | ||
| lambda:[_b("")]) | ||
| expected["traceback"] = content.Content( | ||
| content_type.ContentType("text", "x-traceback", {"charset": "utf8"}), lambda: [b""] | ||
| ) | ||
| found = parser.get_details() | ||
| self.assertEqual(expected.keys(), found.keys()) | ||
| self.assertEqual(expected['traceback'].content_type, | ||
| found['traceback'].content_type) | ||
| self.assertEqual(_b('').join(expected['traceback'].iter_bytes()), | ||
| _b('').join(found['traceback'].iter_bytes())) | ||
| self.assertEqual(expected["traceback"].content_type, found["traceback"].content_type) | ||
| self.assertEqual(b"".join(expected["traceback"].iter_bytes()), b"".join(found["traceback"].iter_bytes())) | ||
@@ -64,5 +59,3 @@ def test_get_details_skip(self): | ||
| expected = {} | ||
| expected['reason'] = content.Content( | ||
| content_type.ContentType("text", "plain"), | ||
| lambda:[_b("")]) | ||
| expected["reason"] = content.Content(content_type.ContentType("text", "plain"), lambda: [b""]) | ||
| found = parser.get_details("skip") | ||
@@ -74,5 +67,3 @@ self.assertEqual(expected, found) | ||
| expected = {} | ||
| expected['message'] = content.Content( | ||
| content_type.ContentType("text", "plain"), | ||
| lambda:[_b("")]) | ||
| expected["message"] = content.Content(content_type.ContentType("text", "plain"), lambda: [b""]) | ||
| found = parser.get_details("success") | ||
@@ -83,3 +74,2 @@ self.assertEqual(expected, found) | ||
| class TestMultipartDetails(unittest.TestCase): | ||
| def test_get_message_is_None(self): | ||
@@ -95,16 +85,14 @@ parser = details.MultipartDetailsParser(None) | ||
| parser = details.MultipartDetailsParser(None) | ||
| parser.lineReceived(_b("Content-Type: text/plain\n")) | ||
| parser.lineReceived(_b("something\n")) | ||
| parser.lineReceived(_b("F\r\n")) | ||
| parser.lineReceived(_b("serialised\n")) | ||
| parser.lineReceived(_b("form0\r\n")) | ||
| parser.lineReceived(b"Content-Type: text/plain\n") | ||
| parser.lineReceived(b"something\n") | ||
| parser.lineReceived(b"F\r\n") | ||
| parser.lineReceived(b"serialised\n") | ||
| parser.lineReceived(b"form0\r\n") | ||
| expected = {} | ||
| expected['something'] = content.Content( | ||
| content_type.ContentType("text", "plain"), | ||
| lambda:[_b("serialised\nform")]) | ||
| expected["something"] = content.Content( | ||
| content_type.ContentType("text", "plain"), lambda: [b"serialised\nform"] | ||
| ) | ||
| found = parser.get_details() | ||
| self.assertEqual(expected.keys(), found.keys()) | ||
| self.assertEqual(expected['something'].content_type, | ||
| found['something'].content_type) | ||
| self.assertEqual(_b('').join(expected['something'].iter_bytes()), | ||
| _b('').join(found['something'].iter_bytes())) | ||
| self.assertEqual(expected["something"].content_type, found["something"].content_type) | ||
| self.assertEqual(b"".join(expected["something"].iter_bytes()), b"".join(found["something"].iter_bytes())) |
@@ -28,5 +28,4 @@ # | ||
| class SmokeTest(TestCase): | ||
| def test_smoke(self): | ||
| output = os.path.join(self.useFixture(TempDir()).path, 'output') | ||
| output = os.path.join(self.useFixture(TempDir()).path, "output") | ||
| stdin = io.BytesIO() | ||
@@ -37,14 +36,13 @@ stdout = io.StringIO() | ||
| writer.status( | ||
| 'foo', 'success', {'tag'}, file_name='fred', | ||
| file_bytes=b'abcdefg', eof=True, mime_type='text/plain') | ||
| "foo", "success", {"tag"}, file_name="fred", file_bytes=b"abcdefg", eof=True, mime_type="text/plain" | ||
| ) | ||
| writer.stopTestRun() | ||
| stdin.seek(0) | ||
| _to_disk.to_disk(['-d', output], stdin=stdin, stdout=stdout) | ||
| _to_disk.to_disk(["-d", output], stdin=stdin, stdout=stdout) | ||
| self.expectThat( | ||
| os.path.join(output, 'foo/test.json'), | ||
| os.path.join(output, "foo/test.json"), | ||
| FileContains( | ||
| '{"details": ["fred"], "id": "foo", "start": null, ' | ||
| '"status": "success", "stop": null, "tags": ["tag"]}')) | ||
| self.expectThat( | ||
| os.path.join(output, 'foo/fred'), | ||
| FileContains('abcdefg')) | ||
| '{"details": ["fred"], "id": "foo", "start": null, "status": "success", "stop": null, "tags": ["tag"]}' | ||
| ), | ||
| ) | ||
| self.expectThat(os.path.join(output, "foo/fred"), FileContains("abcdefg")) |
@@ -26,20 +26,18 @@ # | ||
| class TestReadTestList(TestCase): | ||
| def test_read_list(self): | ||
| with NamedTemporaryFile() as f: | ||
| f.write(b'foo\nbar\n# comment\nother # comment\n') | ||
| f.write(b"foo\nbar\n# comment\nother # comment\n") | ||
| f.flush() | ||
| self.assertEqual(read_test_list(f.name), ['foo', 'bar', 'other']) | ||
| self.assertEqual(read_test_list(f.name), ["foo", "bar", "other"]) | ||
| class TestFindStream(TestCase): | ||
| def test_no_argv(self): | ||
| self.assertEqual('foo', find_stream('foo', [])) | ||
| self.assertEqual("foo", find_stream("foo", [])) | ||
| def test_opens_file(self): | ||
| f = NamedTemporaryFile() | ||
| f.write(b'foo') | ||
| f.write(b"foo") | ||
| f.flush() | ||
| stream = find_stream('bar', [f.name]) | ||
| self.assertEqual(b'foo', stream.read()) | ||
| stream = find_stream("bar", [f.name]) | ||
| self.assertEqual(b"foo", stream.read()) |
@@ -27,9 +27,7 @@ # | ||
| from testtools import TestCase | ||
| from testtools.matchers import (Equals, Matcher, MatchesAny, MatchesListwise, | ||
| Mismatch, raises) | ||
| from testtools.matchers import Equals, Matcher, MatchesAny, MatchesListwise, Mismatch, raises | ||
| from testtools.testresult.doubles import StreamResult | ||
| import subunit._output as _o | ||
| from subunit._output import (_ALL_ACTIONS, _FINAL_ACTIONS, | ||
| generate_stream_results, parse_arguments) | ||
| from subunit._output import _ALL_ACTIONS, _FINAL_ACTIONS, generate_stream_results, parse_arguments | ||
@@ -51,7 +49,4 @@ | ||
| class TestStatusArgParserTests(TestCase): | ||
| scenarios = [(cmd, dict(command=cmd, option="--" + cmd)) for cmd in _ALL_ACTIONS] | ||
| scenarios = [ | ||
| (cmd, dict(command=cmd, option='--' + cmd)) for cmd in _ALL_ACTIONS | ||
| ] | ||
| def test_can_parse_all_commands_with_test_id(self): | ||
@@ -66,5 +61,3 @@ test_id = self.getUniqueString() | ||
| with NamedTemporaryFile() as tmp_file: | ||
| args = safe_parse_arguments( | ||
| args=[self.option, 'foo', '--attach-file', tmp_file.name] | ||
| ) | ||
| args = safe_parse_arguments(args=[self.option, "foo", "--attach-file", tmp_file.name]) | ||
| self.assertThat(args.attach_file.name, Equals(tmp_file.name)) | ||
@@ -75,3 +68,3 @@ | ||
| args = safe_parse_arguments( | ||
| args=[self.option, 'foo', '--attach-file', tmp_file.name, '--mimetype', "text/plain"] | ||
| args=[self.option, "foo", "--attach-file", tmp_file.name, "--mimetype", "text/plain"] | ||
| ) | ||
@@ -82,18 +75,12 @@ self.assertThat(args.mimetype, Equals("text/plain")) | ||
| with NamedTemporaryFile() as tmp_file: | ||
| args = safe_parse_arguments( | ||
| args=[self.option, 'foo', '--attach-file', tmp_file.name, '--file-name', "foo"] | ||
| ) | ||
| args = safe_parse_arguments(args=[self.option, "foo", "--attach-file", tmp_file.name, "--file-name", "foo"]) | ||
| self.assertThat(args.file_name, Equals("foo")) | ||
| def test_all_commands_accept_tags_argument(self): | ||
| args = safe_parse_arguments( | ||
| args=[self.option, 'foo', '--tag', "foo", "--tag", "bar", "--tag", "baz"] | ||
| ) | ||
| args = safe_parse_arguments(args=[self.option, "foo", "--tag", "foo", "--tag", "bar", "--tag", "baz"]) | ||
| self.assertThat(args.tags, Equals(["foo", "bar", "baz"])) | ||
| def test_attach_file_with_hyphen_opens_stdin(self): | ||
| self.patch(_o.sys, 'stdin', TextIOWrapper(BytesIO(b"Hello"))) | ||
| args = safe_parse_arguments( | ||
| args=[self.option, "foo", "--attach-file", "-"] | ||
| ) | ||
| self.patch(_o.sys, "stdin", TextIOWrapper(BytesIO(b"Hello"))) | ||
| args = safe_parse_arguments(args=[self.option, "foo", "--attach-file", "-"]) | ||
@@ -103,5 +90,3 @@ self.assertThat(args.attach_file.read(), Equals(b"Hello")) | ||
| def test_attach_file_with_hyphen_sets_filename_to_stdin(self): | ||
| args = safe_parse_arguments( | ||
| args=[self.option, "foo", "--attach-file", "-"] | ||
| ) | ||
| args = safe_parse_arguments(args=[self.option, "foo", "--attach-file", "-"]) | ||
@@ -111,5 +96,3 @@ self.assertThat(args.file_name, Equals("stdin")) | ||
| def test_can_override_stdin_filename(self): | ||
| args = safe_parse_arguments( | ||
| args=[self.option, "foo", "--attach-file", "-", '--file-name', 'foo'] | ||
| ) | ||
| args = safe_parse_arguments(args=[self.option, "foo", "--attach-file", "-", "--file-name", "foo"]) | ||
@@ -121,15 +104,10 @@ self.assertThat(args.file_name, Equals("foo")) | ||
| return safe_parse_arguments(args=[self.option]) | ||
| self.assertThat( | ||
| fn, | ||
| raises(RuntimeError('argument %s: must specify a single TEST_ID.' % self.option)) | ||
| ) | ||
| self.assertThat(fn, raises(RuntimeError("argument %s: must specify a single TEST_ID." % self.option))) | ||
| class ArgParserTests(TestCase): | ||
| def test_can_parse_attach_file_without_test_id(self): | ||
| with NamedTemporaryFile() as tmp_file: | ||
| args = safe_parse_arguments( | ||
| args=["--attach-file", tmp_file.name] | ||
| ) | ||
| args = safe_parse_arguments(args=["--attach-file", tmp_file.name]) | ||
| self.assertThat(args.attach_file.name, Equals(tmp_file.name)) | ||
@@ -143,26 +121,20 @@ | ||
| return safe_parse_arguments(["--fail", "foo", "--skip", "bar"]) | ||
| self.assertThat( | ||
| fn, | ||
| raises(RuntimeError('argument --skip: Only one status may be specified at once.')) | ||
| ) | ||
| self.assertThat(fn, raises(RuntimeError("argument --skip: Only one status may be specified at once."))) | ||
| def test_cannot_specify_mimetype_without_attach_file(self): | ||
| def fn(): | ||
| return safe_parse_arguments(["--mimetype", "foo"]) | ||
| self.assertThat( | ||
| fn, | ||
| raises(RuntimeError('Cannot specify --mimetype without --attach-file')) | ||
| ) | ||
| self.assertThat(fn, raises(RuntimeError("Cannot specify --mimetype without --attach-file"))) | ||
| def test_cannot_specify_filename_without_attach_file(self): | ||
| def fn(): | ||
| return safe_parse_arguments(["--file-name", "foo"]) | ||
| self.assertThat( | ||
| fn, | ||
| raises(RuntimeError('Cannot specify --file-name without --attach-file')) | ||
| ) | ||
| self.assertThat(fn, raises(RuntimeError("Cannot specify --file-name without --attach-file"))) | ||
| def test_can_specify_tags_without_status_command(self): | ||
| args = safe_parse_arguments(['--tag', 'foo']) | ||
| self.assertEqual(['foo'], args.tags) | ||
| args = safe_parse_arguments(["--tag", "foo"]) | ||
| self.assertEqual(["foo"], args.tags) | ||
@@ -172,10 +144,12 @@ def test_must_specify_tags_with_tags_options(self): | ||
| return safe_parse_arguments(["--fail", "foo", "--tag"]) | ||
| self.assertThat( | ||
| fn, | ||
| MatchesAny( | ||
| raises(RuntimeError('--tag option requires 1 argument')), | ||
| raises(RuntimeError('--tag option requires an argument')), | ||
| ) | ||
| raises(RuntimeError("--tag option requires 1 argument")), | ||
| raises(RuntimeError("--tag option requires an argument")), | ||
| ), | ||
| ) | ||
| def get_result_for(commands): | ||
@@ -205,7 +179,4 @@ """Get a result object from *commands. | ||
| class StatusStreamResultTests(TestCase): | ||
| scenarios = [(s, dict(status=s, option="--" + s)) for s in _ALL_ACTIONS] | ||
| scenarios = [ | ||
| (s, dict(status=s, option='--' + s)) for s in _ALL_ACTIONS | ||
| ] | ||
| _dummy_timestamp = datetime.datetime(2013, 1, 1, 0, 0, 0, 0, UTC) | ||
@@ -215,3 +186,3 @@ | ||
| super().setUp() | ||
| self.patch(_o, 'create_timestamp', lambda: self._dummy_timestamp) | ||
| self.patch(_o, "create_timestamp", lambda: self._dummy_timestamp) | ||
| self.test_id = self.getUniqueString() | ||
@@ -223,3 +194,3 @@ | ||
| len(result._events), | ||
| Equals(3) # startTestRun and stopTestRun are also called, making 3 total. | ||
| Equals(3), # startTestRun and stopTestRun are also called, making 3 total. | ||
| ) | ||
@@ -230,13 +201,7 @@ | ||
| self.assertThat( | ||
| result._events[1], | ||
| MatchesStatusCall(test_status=self.status) | ||
| ) | ||
| self.assertThat(result._events[1], MatchesStatusCall(test_status=self.status)) | ||
| def test_all_commands_generate_tags(self): | ||
| result = get_result_for([self.option, self.test_id, '--tag', 'hello', '--tag', 'world']) | ||
| self.assertThat( | ||
| result._events[1], | ||
| MatchesStatusCall(test_tags={'hello', 'world'}) | ||
| ) | ||
| result = get_result_for([self.option, self.test_id, "--tag", "hello", "--tag", "world"]) | ||
| self.assertThat(result._events[1], MatchesStatusCall(test_tags={"hello", "world"})) | ||
@@ -246,6 +211,3 @@ def test_all_commands_generate_timestamp(self): | ||
| self.assertThat( | ||
| result._events[1], | ||
| MatchesStatusCall(timestamp=self._dummy_timestamp) | ||
| ) | ||
| self.assertThat(result._events[1], MatchesStatusCall(timestamp=self._dummy_timestamp)) | ||
@@ -255,31 +217,32 @@ def test_all_commands_generate_correct_test_id(self): | ||
| self.assertThat( | ||
| result._events[1], | ||
| MatchesStatusCall(test_id=self.test_id) | ||
| ) | ||
| self.assertThat(result._events[1], MatchesStatusCall(test_id=self.test_id)) | ||
| def test_file_is_sent_in_single_packet(self): | ||
| with temp_file_contents(b"Hello") as f: | ||
| result = get_result_for([self.option, self.test_id, '--attach-file', f.name]) | ||
| result = get_result_for([self.option, self.test_id, "--attach-file", f.name]) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(file_bytes=b'Hello', eof=True), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(file_bytes=b"Hello", eof=True), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
| def test_can_read_binary_files(self): | ||
| with temp_file_contents(b"\xDE\xAD\xBE\xEF") as f: | ||
| result = get_result_for([self.option, self.test_id, '--attach-file', f.name]) | ||
| with temp_file_contents(b"\xde\xad\xbe\xef") as f: | ||
| result = get_result_for([self.option, self.test_id, "--attach-file", f.name]) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(file_bytes=b"\xDE\xAD\xBE\xEF", eof=True), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(file_bytes=b"\xde\xad\xbe\xef", eof=True), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -289,24 +252,28 @@ | ||
| with temp_file_contents(b"") as f: | ||
| result = get_result_for([self.option, self.test_id, '--attach-file', f.name]) | ||
| result = get_result_for([self.option, self.test_id, "--attach-file", f.name]) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(file_bytes=b"", file_name=f.name, eof=True), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(file_bytes=b"", file_name=f.name, eof=True), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
| def test_can_read_stdin(self): | ||
| self.patch(_o.sys, 'stdin', TextIOWrapper(BytesIO(b"\xFE\xED\xFA\xCE"))) | ||
| result = get_result_for([self.option, self.test_id, '--attach-file', '-']) | ||
| self.patch(_o.sys, "stdin", TextIOWrapper(BytesIO(b"\xfe\xed\xfa\xce"))) | ||
| result = get_result_for([self.option, self.test_id, "--attach-file", "-"]) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(file_bytes=b"\xFE\xED\xFA\xCE", file_name='stdin', eof=True), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(file_bytes=b"\xfe\xed\xfa\xce", file_name="stdin", eof=True), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -316,11 +283,13 @@ | ||
| with temp_file_contents(b"Hello") as f: | ||
| result = get_result_for([self.option, self.test_id, '--attach-file', f.name]) | ||
| result = get_result_for([self.option, self.test_id, "--attach-file", f.name]) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(test_id=self.test_id, file_bytes=b'Hello', eof=True), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(test_id=self.test_id, file_bytes=b"Hello", eof=True), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -330,11 +299,13 @@ | ||
| with temp_file_contents(b"Hello") as f: | ||
| result = get_result_for([self.option, self.test_id, '--attach-file', f.name]) | ||
| result = get_result_for([self.option, self.test_id, "--attach-file", f.name]) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(test_status=self.status, file_bytes=b'Hello', eof=True), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(test_status=self.status, file_bytes=b"Hello", eof=True), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -344,16 +315,18 @@ | ||
| with temp_file_contents(b"Hello") as f: | ||
| self.patch(_o, '_CHUNK_SIZE', 1) | ||
| result = get_result_for([self.option, self.test_id, '--attach-file', f.name]) | ||
| self.patch(_o, "_CHUNK_SIZE", 1) | ||
| result = get_result_for([self.option, self.test_id, "--attach-file", f.name]) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(test_id=self.test_id, file_bytes=b'H', eof=False), | ||
| MatchesStatusCall(test_id=self.test_id, file_bytes=b'e', eof=False), | ||
| MatchesStatusCall(test_id=self.test_id, file_bytes=b'l', eof=False), | ||
| MatchesStatusCall(test_id=self.test_id, file_bytes=b'l', eof=False), | ||
| MatchesStatusCall(test_id=self.test_id, file_bytes=b'o', eof=True), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(test_id=self.test_id, file_bytes=b"H", eof=False), | ||
| MatchesStatusCall(test_id=self.test_id, file_bytes=b"e", eof=False), | ||
| MatchesStatusCall(test_id=self.test_id, file_bytes=b"l", eof=False), | ||
| MatchesStatusCall(test_id=self.test_id, file_bytes=b"l", eof=False), | ||
| MatchesStatusCall(test_id=self.test_id, file_bytes=b"o", eof=True), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -363,20 +336,24 @@ | ||
| with temp_file_contents(b"Hi") as f: | ||
| self.patch(_o, '_CHUNK_SIZE', 1) | ||
| result = get_result_for([ | ||
| self.option, | ||
| self.test_id, | ||
| '--attach-file', | ||
| f.name, | ||
| '--mimetype', | ||
| 'text/plain', | ||
| ]) | ||
| self.patch(_o, "_CHUNK_SIZE", 1) | ||
| result = get_result_for( | ||
| [ | ||
| self.option, | ||
| self.test_id, | ||
| "--attach-file", | ||
| f.name, | ||
| "--mimetype", | ||
| "text/plain", | ||
| ] | ||
| ) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(test_id=self.test_id, mime_type='text/plain', file_bytes=b'H', eof=False), | ||
| MatchesStatusCall(test_id=self.test_id, mime_type=None, file_bytes=b'i', eof=True), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(test_id=self.test_id, mime_type="text/plain", file_bytes=b"H", eof=False), | ||
| MatchesStatusCall(test_id=self.test_id, mime_type=None, file_bytes=b"i", eof=True), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -386,22 +363,26 @@ | ||
| with temp_file_contents(b"Hi") as f: | ||
| self.patch(_o, '_CHUNK_SIZE', 1) | ||
| result = get_result_for([ | ||
| self.option, | ||
| self.test_id, | ||
| '--attach-file', | ||
| f.name, | ||
| '--tag', | ||
| 'foo', | ||
| '--tag', | ||
| 'bar', | ||
| ]) | ||
| self.patch(_o, "_CHUNK_SIZE", 1) | ||
| result = get_result_for( | ||
| [ | ||
| self.option, | ||
| self.test_id, | ||
| "--attach-file", | ||
| f.name, | ||
| "--tag", | ||
| "foo", | ||
| "--tag", | ||
| "bar", | ||
| ] | ||
| ) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(test_id=self.test_id, test_tags={'foo', 'bar'}), | ||
| MatchesStatusCall(test_id=self.test_id, test_tags=None), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(test_id=self.test_id, test_tags={"foo", "bar"}), | ||
| MatchesStatusCall(test_id=self.test_id, test_tags=None), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -411,18 +392,22 @@ | ||
| with temp_file_contents(b"Hi") as f: | ||
| self.patch(_o, '_CHUNK_SIZE', 1) | ||
| result = get_result_for([ | ||
| self.option, | ||
| self.test_id, | ||
| '--attach-file', | ||
| f.name, | ||
| ]) | ||
| self.patch(_o, "_CHUNK_SIZE", 1) | ||
| result = get_result_for( | ||
| [ | ||
| self.option, | ||
| self.test_id, | ||
| "--attach-file", | ||
| f.name, | ||
| ] | ||
| ) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(test_id=self.test_id, timestamp=self._dummy_timestamp), | ||
| MatchesStatusCall(test_id=self.test_id, timestamp=None), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(test_id=self.test_id, timestamp=self._dummy_timestamp), | ||
| MatchesStatusCall(test_id=self.test_id, timestamp=None), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -432,9 +417,11 @@ | ||
| with temp_file_contents(b"Hi") as f: | ||
| self.patch(_o, '_CHUNK_SIZE', 1) | ||
| result = get_result_for([ | ||
| self.option, | ||
| self.test_id, | ||
| '--attach-file', | ||
| f.name, | ||
| ]) | ||
| self.patch(_o, "_CHUNK_SIZE", 1) | ||
| result = get_result_for( | ||
| [ | ||
| self.option, | ||
| self.test_id, | ||
| "--attach-file", | ||
| f.name, | ||
| ] | ||
| ) | ||
@@ -451,8 +438,10 @@ # 'inprogress' status should be on the first packet only, all other | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| first_call, | ||
| last_call, | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| first_call, | ||
| last_call, | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -463,17 +452,15 @@ | ||
| specified_file_name = self.getUniqueString() | ||
| result = get_result_for([ | ||
| self.option, | ||
| self.test_id, | ||
| '--attach-file', | ||
| f.name, | ||
| '--file-name', | ||
| specified_file_name]) | ||
| result = get_result_for( | ||
| [self.option, self.test_id, "--attach-file", f.name, "--file-name", specified_file_name] | ||
| ) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(file_name=specified_file_name, file_bytes=b'Hello'), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(file_name=specified_file_name, file_bytes=b"Hello"), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -483,11 +470,13 @@ | ||
| with temp_file_contents(b"Hello") as f: | ||
| result = get_result_for([self.option, self.test_id, '--attach-file', f.name]) | ||
| result = get_result_for([self.option, self.test_id, "--attach-file", f.name]) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(file_name=f.name, file_bytes=b'Hello', eof=True), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(file_name=f.name, file_bytes=b"Hello", eof=True), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -497,14 +486,15 @@ | ||
| class FileDataTests(TestCase): | ||
| def test_can_attach_file_without_test_id(self): | ||
| with temp_file_contents(b"Hello") as f: | ||
| result = get_result_for(['--attach-file', f.name]) | ||
| result = get_result_for(["--attach-file", f.name]) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(test_id=None, file_bytes=b'Hello', eof=True), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(test_id=None, file_bytes=b"Hello", eof=True), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -514,11 +504,13 @@ | ||
| with temp_file_contents(b"Hello") as f: | ||
| result = get_result_for(['--attach-file', f.name]) | ||
| result = get_result_for(["--attach-file", f.name]) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(file_name=f.name, file_bytes=b'Hello', eof=True), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(file_name=f.name, file_bytes=b"Hello", eof=True), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -529,16 +521,13 @@ | ||
| specified_file_name = self.getUniqueString() | ||
| result = get_result_for([ | ||
| '--attach-file', | ||
| f.name, | ||
| '--file-name', | ||
| specified_file_name | ||
| ]) | ||
| result = get_result_for(["--attach-file", f.name, "--file-name", specified_file_name]) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(file_name=specified_file_name, file_bytes=b'Hello'), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(file_name=specified_file_name, file_bytes=b"Hello"), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -548,33 +537,41 @@ | ||
| _dummy_timestamp = datetime.datetime(2013, 1, 1, 0, 0, 0, 0, UTC) | ||
| self.patch(_o, 'create_timestamp', lambda: _dummy_timestamp) | ||
| self.patch(_o, "create_timestamp", lambda: _dummy_timestamp) | ||
| with temp_file_contents(b"Hello") as f: | ||
| self.getUniqueString() | ||
| result = get_result_for([ | ||
| '--attach-file', | ||
| f.name, | ||
| ]) | ||
| result = get_result_for( | ||
| [ | ||
| "--attach-file", | ||
| f.name, | ||
| ] | ||
| ) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(file_bytes=b'Hello', timestamp=_dummy_timestamp), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(file_bytes=b"Hello", timestamp=_dummy_timestamp), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
| def test_can_specify_tags_without_test_status(self): | ||
| result = get_result_for([ | ||
| '--tag', | ||
| 'foo', | ||
| ]) | ||
| result = get_result_for( | ||
| [ | ||
| "--tag", | ||
| "foo", | ||
| ] | ||
| ) | ||
| self.assertThat( | ||
| result._events, | ||
| MatchesListwise([ | ||
| MatchesStatusCall(call='startTestRun'), | ||
| MatchesStatusCall(test_tags={'foo'}), | ||
| MatchesStatusCall(call='stopTestRun'), | ||
| ]) | ||
| MatchesListwise( | ||
| [ | ||
| MatchesStatusCall(call="startTestRun"), | ||
| MatchesStatusCall(test_tags={"foo"}), | ||
| MatchesStatusCall(call="stopTestRun"), | ||
| ] | ||
| ), | ||
| ) | ||
@@ -584,24 +581,20 @@ | ||
| class MatchesStatusCall(Matcher): | ||
| _position_lookup = { | ||
| 'call': 0, | ||
| 'test_id': 1, | ||
| 'test_status': 2, | ||
| 'test_tags': 3, | ||
| 'runnable': 4, | ||
| 'file_name': 5, | ||
| 'file_bytes': 6, | ||
| 'eof': 7, | ||
| 'mime_type': 8, | ||
| 'route_code': 9, | ||
| 'timestamp': 10, | ||
| "call": 0, | ||
| "test_id": 1, | ||
| "test_status": 2, | ||
| "test_tags": 3, | ||
| "runnable": 4, | ||
| "file_name": 5, | ||
| "file_bytes": 6, | ||
| "eof": 7, | ||
| "mime_type": 8, | ||
| "route_code": 9, | ||
| "timestamp": 10, | ||
| } | ||
| def __init__(self, **kwargs): | ||
| unknown_kwargs = list(filter( | ||
| lambda k: k not in self._position_lookup, | ||
| kwargs | ||
| )) | ||
| unknown_kwargs = list(filter(lambda k: k not in self._position_lookup, kwargs)) | ||
| if unknown_kwargs: | ||
| raise ValueError("Unknown keywords: %s" % ','.join(unknown_kwargs)) | ||
| raise ValueError("Unknown keywords: %s" % ",".join(unknown_kwargs)) | ||
| self._filters = kwargs | ||
@@ -614,5 +607,3 @@ | ||
| if call_tuple[pos] != v: | ||
| return Mismatch( | ||
| "Value for key is {!r}, not {!r}".format(call_tuple[pos], v) | ||
| ) | ||
| return Mismatch("Value for key is {!r}, not {!r}".format(call_tuple[pos], v)) | ||
| except IndexError: | ||
@@ -619,0 +610,0 @@ return Mismatch("Key %s is not present." % k) |
@@ -9,3 +9,3 @@ # | ||
| # compliance with one of these two licences. | ||
| # | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
@@ -24,3 +24,2 @@ # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| class TestProgressModel(unittest.TestCase): | ||
| def assertProgressSummary(self, pos, total, progress): | ||
@@ -27,0 +26,0 @@ """Assert that a progress model has reached a particular point.""" |
@@ -9,3 +9,3 @@ # | ||
| # compliance with one of these two licences. | ||
| # | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
@@ -22,3 +22,3 @@ # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| from testtools import PlaceHolder, TestCase | ||
| from testtools.compat import _b | ||
| from testtools.matchers import StartsWith | ||
@@ -33,7 +33,6 @@ from testtools.testresult.doubles import StreamResult | ||
| class TestSubunitTestRunner(TestCase): | ||
| def test_includes_timing_output(self): | ||
| bytestream = io.BytesIO() | ||
| runner = SubunitTestRunner(stream=bytestream) | ||
| test = PlaceHolder('name') | ||
| test = PlaceHolder("name") | ||
| runner.run(test) | ||
@@ -43,4 +42,3 @@ bytestream.seek(0) | ||
| subunit.ByteStreamToStreamResult(bytestream).run(eventstream) | ||
| timestamps = [event[-1] for event in eventstream._events | ||
| if event is not None] | ||
| timestamps = [event[-1] for event in eventstream._events if event is not None] | ||
| self.assertNotEqual([], timestamps) | ||
@@ -51,4 +49,4 @@ | ||
| runner = SubunitTestRunner(stream=bytestream) | ||
| test1 = PlaceHolder('name1') | ||
| test2 = PlaceHolder('name2') | ||
| test1 = PlaceHolder("name1") | ||
| test2 = PlaceHolder("name2") | ||
| case = unittest.TestSuite([test1, test2]) | ||
@@ -59,6 +57,9 @@ runner.run(case) | ||
| subunit.ByteStreamToStreamResult(bytestream).run(eventstream) | ||
| self.assertEqual([ | ||
| ('status', 'name1', 'exists'), | ||
| ('status', 'name2', 'exists'), | ||
| ], [event[:3] for event in eventstream._events[:2]]) | ||
| self.assertEqual( | ||
| [ | ||
| ("status", "name1", "exists"), | ||
| ("status", "name2", "exists"), | ||
| ], | ||
| [event[:3] for event in eventstream._events[:2]], | ||
| ) | ||
@@ -68,5 +69,7 @@ def test_list_errors_if_errors_from_list_test(self): | ||
| runner = SubunitTestRunner(stream=bytestream) | ||
| def list_test(test): | ||
| return [], ['failed import'] | ||
| self.patch(run, 'list_test', list_test) | ||
| return [], ["failed import"] | ||
| self.patch(run, "list_test", list_test) | ||
| exc = self.assertRaises(SystemExit, runner.list, None) | ||
@@ -78,8 +81,11 @@ self.assertEqual((2,), exc.args) | ||
| runner = SubunitTestRunner(stream=bytestream) | ||
| def list_test(test): | ||
| return [], [] | ||
| class Loader: | ||
| errors = ['failed import'] | ||
| errors = ["failed import"] | ||
| loader = Loader() | ||
| self.patch(run, 'list_test', list_test) | ||
| self.patch(run, "list_test", list_test) | ||
| exc = self.assertRaises(SystemExit, runner.list, None, loader=loader) | ||
@@ -90,3 +96,3 @@ self.assertEqual((2,), exc.args) | ||
| def test_fail(self): | ||
| 1/0 | ||
| 1 / 0 | ||
@@ -97,8 +103,9 @@ def test_exits_zero_when_tests_fail(self): | ||
| try: | ||
| self.assertEqual(None, run.main( | ||
| argv=["progName", "subunit.tests.test_run.TestSubunitTestRunner.FailingTest"], | ||
| stdout=stream)) | ||
| self.assertEqual( | ||
| None, | ||
| run.main(argv=["progName", "subunit.tests.test_run.TestSubunitTestRunner.FailingTest"], stdout=stream), | ||
| ) | ||
| except SystemExit: | ||
| self.fail("SystemExit raised") | ||
| self.assertThat(bytestream.getvalue(), StartsWith(_b('\xb3'))) | ||
| self.assertThat(bytestream.getvalue(), StartsWith(b"\xb3")) | ||
@@ -112,5 +119,8 @@ class ExitingTest(TestCase): | ||
| stream = io.TextIOWrapper(bytestream, encoding="utf8") | ||
| exc = self.assertRaises(SystemExit, run.main, | ||
| argv=["progName", "subunit.tests.test_run.TestSubunitTestRunner.ExitingTest"], | ||
| stdout=stream) | ||
| exc = self.assertRaises( | ||
| SystemExit, | ||
| run.main, | ||
| argv=["progName", "subunit.tests.test_run.TestSubunitTestRunner.ExitingTest"], | ||
| stdout=stream, | ||
| ) | ||
| self.assertEqual(0, exc.args[0]) |
@@ -26,3 +26,3 @@ # | ||
| from testtools import TestCase | ||
| from testtools.compat import _b | ||
| from testtools.testresult.doubles import ExtendedTestResult, StreamResult | ||
@@ -43,3 +43,3 @@ | ||
| # also has the benefit of detecting any interface skew issues. | ||
| example_subunit_stream = _b("""\ | ||
| example_subunit_stream = b"""\ | ||
| tags: global | ||
@@ -59,3 +59,3 @@ test passed | ||
| xfail todo | ||
| """) | ||
| """ | ||
@@ -80,42 +80,30 @@ def run_tests(self, result_filter, input_stream=None): | ||
| # skips are seen as success by default python TestResult. | ||
| self.assertEqual(['error'], | ||
| [error[0].id() for error in filtered_result.errors]) | ||
| self.assertEqual(['failed'], | ||
| [failure[0].id() for failure in | ||
| filtered_result.failures]) | ||
| self.assertEqual(["error"], [error[0].id() for error in filtered_result.errors]) | ||
| self.assertEqual(["failed"], [failure[0].id() for failure in filtered_result.failures]) | ||
| self.assertEqual(4, filtered_result.testsRun) | ||
| def test_tag_filter(self): | ||
| tag_filter = make_tag_filter(['global'], ['local']) | ||
| tag_filter = make_tag_filter(["global"], ["local"]) | ||
| result = ExtendedTestResult() | ||
| result_filter = TestResultFilter( | ||
| result, filter_success=False, filter_predicate=tag_filter) | ||
| result_filter = TestResultFilter(result, filter_success=False, filter_predicate=tag_filter) | ||
| self.run_tests(result_filter) | ||
| tests_included = [ | ||
| event[1] for event in result._events if event[0] == 'startTest'] | ||
| tests_expected = list(map( | ||
| subunit.RemotedTestCase, | ||
| ['passed', 'error', 'skipped', 'todo'])) | ||
| tests_included = [event[1] for event in result._events if event[0] == "startTest"] | ||
| tests_expected = list(map(subunit.RemotedTestCase, ["passed", "error", "skipped", "todo"])) | ||
| self.assertEqual(tests_expected, tests_included) | ||
| def test_tags_tracked_correctly(self): | ||
| tag_filter = make_tag_filter(['a'], []) | ||
| tag_filter = make_tag_filter(["a"], []) | ||
| result = ExtendedTestResult() | ||
| result_filter = TestResultFilter( | ||
| result, filter_success=False, filter_predicate=tag_filter) | ||
| input_stream = _b( | ||
| "test: foo\n" | ||
| "tags: a\n" | ||
| "successful: foo\n" | ||
| "test: bar\n" | ||
| "successful: bar\n") | ||
| result_filter = TestResultFilter(result, filter_success=False, filter_predicate=tag_filter) | ||
| input_stream = b"test: foo\ntags: a\nsuccessful: foo\ntest: bar\nsuccessful: bar\n" | ||
| self.run_tests(result_filter, input_stream) | ||
| foo = subunit.RemotedTestCase('foo') | ||
| foo = subunit.RemotedTestCase("foo") | ||
| self.assertEqual( | ||
| [('startTest', foo), | ||
| ('tags', {'a'}, set()), | ||
| ('addSuccess', foo), | ||
| ('stopTest', foo), | ||
| ], | ||
| result._events | ||
| [ | ||
| ("startTest", foo), | ||
| ("tags", {"a"}, set()), | ||
| ("addSuccess", foo), | ||
| ("stopTest", foo), | ||
| ], | ||
| result._events, | ||
| ) | ||
@@ -129,5 +117,3 @@ | ||
| self.assertEqual([], filtered_result.errors) | ||
| self.assertEqual(['failed'], | ||
| [failure[0].id() for failure in | ||
| filtered_result.failures]) | ||
| self.assertEqual(["failed"], [failure[0].id() for failure in filtered_result.failures]) | ||
| self.assertEqual(3, filtered_result.testsRun) | ||
@@ -137,7 +123,5 @@ | ||
| filtered_result = unittest.TestResult() | ||
| result_filter = TestResultFilter(filtered_result, | ||
| fixup_expected_failures={"failed"}) | ||
| result_filter = TestResultFilter(filtered_result, fixup_expected_failures={"failed"}) | ||
| self.run_tests(result_filter) | ||
| self.assertEqual(['failed', 'todo'], | ||
| [failure[0].id() for failure in filtered_result.expectedFailures]) | ||
| self.assertEqual(["failed", "todo"], [failure[0].id() for failure in filtered_result.expectedFailures]) | ||
| self.assertEqual([], filtered_result.failures) | ||
@@ -148,7 +132,5 @@ self.assertEqual(4, filtered_result.testsRun) | ||
| filtered_result = unittest.TestResult() | ||
| result_filter = TestResultFilter(filtered_result, | ||
| fixup_expected_failures={"error"}) | ||
| result_filter = TestResultFilter(filtered_result, fixup_expected_failures={"error"}) | ||
| self.run_tests(result_filter) | ||
| self.assertEqual(['error', 'todo'], | ||
| [failure[0].id() for failure in filtered_result.expectedFailures]) | ||
| self.assertEqual(["error", "todo"], [failure[0].id() for failure in filtered_result.expectedFailures]) | ||
| self.assertEqual([], filtered_result.errors) | ||
@@ -159,7 +141,5 @@ self.assertEqual(4, filtered_result.testsRun) | ||
| filtered_result = unittest.TestResult() | ||
| result_filter = TestResultFilter(filtered_result, filter_success=False, | ||
| fixup_expected_failures={"passed"}) | ||
| result_filter = TestResultFilter(filtered_result, filter_success=False, fixup_expected_failures={"passed"}) | ||
| self.run_tests(result_filter) | ||
| self.assertEqual(['passed'], | ||
| [passed.id() for passed in filtered_result.unexpectedSuccesses]) | ||
| self.assertEqual(["passed"], [passed.id() for passed in filtered_result.unexpectedSuccesses]) | ||
| self.assertEqual(5, filtered_result.testsRun) | ||
@@ -171,7 +151,4 @@ | ||
| self.run_tests(result_filter) | ||
| self.assertEqual(['error'], | ||
| [error[0].id() for error in filtered_result.errors]) | ||
| self.assertEqual([], | ||
| [failure[0].id() for failure in | ||
| filtered_result.failures]) | ||
| self.assertEqual(["error"], [error[0].id() for error in filtered_result.errors]) | ||
| self.assertEqual([], [failure[0].id() for failure in filtered_result.failures]) | ||
| self.assertEqual(3, filtered_result.testsRun) | ||
@@ -190,10 +167,6 @@ | ||
| filtered_result = unittest.TestResult() | ||
| result_filter = TestResultFilter(filtered_result, | ||
| filter_success=False) | ||
| result_filter = TestResultFilter(filtered_result, filter_success=False) | ||
| self.run_tests(result_filter) | ||
| self.assertEqual(['error'], | ||
| [error[0].id() for error in filtered_result.errors]) | ||
| self.assertEqual(['failed'], | ||
| [failure[0].id() for failure in | ||
| filtered_result.failures]) | ||
| self.assertEqual(["error"], [error[0].id() for error in filtered_result.errors]) | ||
| self.assertEqual(["failed"], [failure[0].id() for failure in filtered_result.failures]) | ||
| self.assertEqual(5, filtered_result.testsRun) | ||
@@ -206,7 +179,7 @@ | ||
| filtered_result = unittest.TestResult() | ||
| def filter_cb(test, outcome, err, details): | ||
| return outcome == 'success' | ||
| result_filter = TestResultFilter(filtered_result, | ||
| filter_predicate=filter_cb, | ||
| filter_success=False) | ||
| return outcome == "success" | ||
| result_filter = TestResultFilter(filtered_result, filter_predicate=filter_cb, filter_success=False) | ||
| self.run_tests(result_filter) | ||
@@ -219,7 +192,7 @@ # Only success should pass | ||
| filtered_result = unittest.TestResult() | ||
| def filter_cb(test, outcome, err, details, tags): | ||
| return outcome == 'success' | ||
| result_filter = TestResultFilter(filtered_result, | ||
| filter_predicate=filter_cb, | ||
| filter_success=False) | ||
| return outcome == "success" | ||
| result_filter = TestResultFilter(filtered_result, filter_predicate=filter_cb, filter_success=False) | ||
| self.run_tests(result_filter) | ||
@@ -236,21 +209,21 @@ # Only success should pass | ||
| date_c = datetime(year=2000, month=1, day=3, tzinfo=iso8601.UTC) | ||
| subunit_stream = _b('\n'.join([ | ||
| "time: %s", | ||
| "test: foo", | ||
| "time: %s", | ||
| "error: foo", | ||
| "time: %s", | ||
| ""]) % (date_a, date_b, date_c)) | ||
| subunit_stream = ( | ||
| "\n".join(["time: %s", "test: foo", "time: %s", "error: foo", "time: %s", ""]) % (date_a, date_b, date_c) | ||
| ).encode() | ||
| result = ExtendedTestResult() | ||
| result_filter = TestResultFilter(result) | ||
| self.run_tests(result_filter, subunit_stream) | ||
| foo = subunit.RemotedTestCase('foo') | ||
| foo = subunit.RemotedTestCase("foo") | ||
| self.maxDiff = None | ||
| self.assertEqual( | ||
| [('time', date_a), | ||
| ('time', date_b), | ||
| ('startTest', foo), | ||
| ('addError', foo, {}), | ||
| ('stopTest', foo), | ||
| ('time', date_c)], result._events) | ||
| [ | ||
| ("time", date_a), | ||
| ("time", date_b), | ||
| ("startTest", foo), | ||
| ("addError", foo, {}), | ||
| ("stopTest", foo), | ||
| ("time", date_c), | ||
| ], | ||
| result._events, | ||
| ) | ||
@@ -263,9 +236,5 @@ def test_time_passes_through_filtered_tests(self): | ||
| date_c = datetime(year=2000, month=1, day=3, tzinfo=iso8601.UTC) | ||
| subunit_stream = _b('\n'.join([ | ||
| "time: %s", | ||
| "test: foo", | ||
| "time: %s", | ||
| "success: foo", | ||
| "time: %s", | ||
| ""]) % (date_a, date_b, date_c)) | ||
| subunit_stream = ( | ||
| "\n".join(["time: %s", "test: foo", "time: %s", "success: foo", "time: %s", ""]) % (date_a, date_b, date_c) | ||
| ).encode() | ||
| result = ExtendedTestResult() | ||
@@ -276,24 +245,27 @@ result_filter = TestResultFilter(result) | ||
| result_filter.stopTestRun() | ||
| subunit.RemotedTestCase('foo') | ||
| subunit.RemotedTestCase("foo") | ||
| self.maxDiff = None | ||
| self.assertEqual( | ||
| [('startTestRun',), | ||
| ('time', date_a), | ||
| ('time', date_c), | ||
| ('stopTestRun',),], result._events) | ||
| [ | ||
| ("startTestRun",), | ||
| ("time", date_a), | ||
| ("time", date_c), | ||
| ("stopTestRun",), | ||
| ], | ||
| result._events, | ||
| ) | ||
| def test_skip_preserved(self): | ||
| subunit_stream = _b('\n'.join([ | ||
| "test: foo", | ||
| "skip: foo", | ||
| ""])) | ||
| subunit_stream = "\n".join(["test: foo", "skip: foo", ""]).encode() | ||
| result = ExtendedTestResult() | ||
| result_filter = TestResultFilter(result) | ||
| self.run_tests(result_filter, subunit_stream) | ||
| foo = subunit.RemotedTestCase('foo') | ||
| foo = subunit.RemotedTestCase("foo") | ||
| self.assertEqual( | ||
| [('startTest', foo), | ||
| ('addSkip', foo, {}), | ||
| ('stopTest', foo), ], | ||
| result._events | ||
| [ | ||
| ("startTest", foo), | ||
| ("addSkip", foo, {}), | ||
| ("stopTest", foo), | ||
| ], | ||
| result._events, | ||
| ) | ||
@@ -304,14 +276,10 @@ | ||
| return name + " - renamed" | ||
| result = ExtendedTestResult() | ||
| result_filter = TestResultFilter( | ||
| result, filter_success=False, rename=rename) | ||
| input_stream = _b( | ||
| "test: foo\n" | ||
| "successful: foo\n") | ||
| result_filter = TestResultFilter(result, filter_success=False, rename=rename) | ||
| input_stream = b"test: foo\nsuccessful: foo\n" | ||
| self.run_tests(result_filter, input_stream) | ||
| self.assertEqual( | ||
| [('startTest', 'foo - renamed'), | ||
| ('addSuccess', 'foo - renamed'), | ||
| ('stopTest', 'foo - renamed')], | ||
| [(ev[0], ev[1].id()) for ev in result._events] | ||
| [("startTest", "foo - renamed"), ("addSuccess", "foo - renamed"), ("stopTest", "foo - renamed")], | ||
| [(ev[0], ev[1].id()) for ev in result._events], | ||
| ) | ||
@@ -321,8 +289,5 @@ | ||
| class TestFilterCommand(TestCase): | ||
| def run_command(self, args, stream): | ||
| command = [sys.executable, '-m', 'subunit.filter_scripts.subunit_filter'] + list(args) | ||
| ps = subprocess.Popen( | ||
| command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, | ||
| stderr=subprocess.PIPE) | ||
| command = [sys.executable, "-m", "subunit.filter_scripts.subunit_filter"] + list(args) | ||
| ps = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||
| out, err = ps.communicate(stream) | ||
@@ -342,6 +307,9 @@ if ps.returncode != 0: | ||
| {event[1] for event in events._events} | ||
| self.assertEqual([ | ||
| ('status', 'foo', 'inprogress'), | ||
| ('status', 'foo', 'skip'), | ||
| ], [event[:3] for event in events._events]) | ||
| self.assertEqual( | ||
| [ | ||
| ("status", "foo", "inprogress"), | ||
| ("status", "foo", "skip"), | ||
| ], | ||
| [event[:3] for event in events._events], | ||
| ) | ||
@@ -351,28 +319,23 @@ def test_tags(self): | ||
| stream = StreamResultToBytes(byte_stream) | ||
| stream.status( | ||
| test_id="foo", test_status="inprogress", test_tags={"a"}) | ||
| stream.status( | ||
| test_id="foo", test_status="success", test_tags={"a"}) | ||
| stream.status(test_id="foo", test_status="inprogress", test_tags={"a"}) | ||
| stream.status(test_id="foo", test_status="success", test_tags={"a"}) | ||
| stream.status(test_id="bar", test_status="inprogress") | ||
| stream.status(test_id="bar", test_status="inprogress") | ||
| stream.status( | ||
| test_id="baz", test_status="inprogress", test_tags={"a"}) | ||
| stream.status( | ||
| test_id="baz", test_status="success", test_tags={"a"}) | ||
| output = self.run_command( | ||
| ['-s', '--with-tag', 'a'], byte_stream.getvalue()) | ||
| stream.status(test_id="baz", test_status="inprogress", test_tags={"a"}) | ||
| stream.status(test_id="baz", test_status="success", test_tags={"a"}) | ||
| output = self.run_command(["-s", "--with-tag", "a"], byte_stream.getvalue()) | ||
| events = StreamResult() | ||
| ByteStreamToStreamResult(BytesIO(output)).run(events) | ||
| ids = {event[1] for event in events._events} | ||
| self.assertEqual({'foo', 'baz'}, ids) | ||
| self.assertEqual({"foo", "baz"}, ids) | ||
| def test_no_passthrough(self): | ||
| output = self.run_command(['--no-passthrough'], b'hi thar') | ||
| self.assertEqual(b'', output) | ||
| output = self.run_command(["--no-passthrough"], b"hi thar") | ||
| self.assertEqual(b"", output) | ||
| def test_passthrough(self): | ||
| output = self.run_command([], b'hi thar') | ||
| output = self.run_command([], b"hi thar") | ||
| byte_stream = BytesIO() | ||
| stream = StreamResultToBytes(byte_stream) | ||
| stream.status(file_name="stdout", file_bytes=b'hi thar') | ||
| stream.status(file_name="stdout", file_bytes=b"hi thar") | ||
| self.assertEqual(byte_stream.getvalue(), output) |
@@ -22,3 +22,2 @@ # | ||
| from testtools.compat import _b | ||
@@ -45,3 +44,4 @@ import subunit | ||
| def setUpUsedStream(self): | ||
| self.input_stream.write(_b("""tags: global | ||
| self.input_stream.write( | ||
| b"""tags: global | ||
| test passed | ||
@@ -58,6 +58,7 @@ success passed | ||
| xfail todo | ||
| """)) | ||
| """ | ||
| ) | ||
| self.input_stream.seek(0) | ||
| self.test.run(self.result) | ||
| def test_stats_smoke_everything(self): | ||
@@ -73,3 +74,4 @@ # Statistics are calculated usefully. | ||
| def test_stat_formatting(self): | ||
| expected = (""" | ||
| expected = ( | ||
| """ | ||
| Total tests: 5 | ||
@@ -80,5 +82,6 @@ Passed tests: 2 | ||
| Seen tags: global, local | ||
| """)[1:] | ||
| """ | ||
| )[1:] | ||
| self.setUpUsedStream() | ||
| self.result.formatStats() | ||
| self.assertEqual(expected, self.output.getvalue()) |
@@ -9,3 +9,3 @@ # | ||
| # compliance with one of these two licences. | ||
| # | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
@@ -30,3 +30,2 @@ # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| class TestSubUnitTags(testtools.TestCase): | ||
| def setUp(self): | ||
@@ -47,27 +46,26 @@ super().setUp() | ||
| reference = [ | ||
| b'\xb3)\x82\x17\x04test\x02\x04quux\x03foo\x05\x97n\x86\xb3)' | ||
| b'\x83\x1b\x04test\x03\x03bar\x04quux\x03fooqn\xab)', | ||
| b'\xb3)\x82\x17\x04test\x02\x04quux\x03foo\x05\x97n\x86\xb3)' | ||
| b'\x83\x1b\x04test\x03\x04quux\x03foo\x03bar\xaf\xbd\x9d\xd6', | ||
| b'\xb3)\x82\x17\x04test\x02\x04quux\x03foo\x05\x97n\x86\xb3)' | ||
| b'\x83\x1b\x04test\x03\x04quux\x03bar\x03foo\x03\x04b\r', | ||
| b'\xb3)\x82\x17\x04test\x02\x04quux\x03foo\x05\x97n\x86\xb3)' | ||
| b'\x83\x1b\x04test\x03\x03bar\x03foo\x04quux\xd2\x18\x1bC', | ||
| b'\xb3)\x82\x17\x04test\x02\x03foo\x04quux\xa6\xe1\xde\xec\xb3)' | ||
| b'\x83\x1b\x04test\x03\x03foo\x04quux\x03bar\x08\xc2X\x83', | ||
| b'\xb3)\x82\x17\x04test\x02\x03foo\x04quux\xa6\xe1\xde\xec\xb3)' | ||
| b'\x83\x1b\x04test\x03\x03bar\x03foo\x04quux\xd2\x18\x1bC', | ||
| b'\xb3)\x82\x17\x04test\x02\x03foo\x04quux\xa6\xe1\xde\xec\xb3)' | ||
| b'\x83\x1b\x04test\x03\x03foo\x03bar\x04quux:\x05e\x80', | ||
| b'\xb3)\x82\x17\x04test\x02\x03foo\x04quux\xa6\xe1\xde\xec\xb3)' | ||
| b'\x83\x1b\x04test\x03\x04quux\x03foo\x03bar\xaf\xbd\x9d\xd6', | ||
| ] | ||
| b"\xb3)\x82\x17\x04test\x02\x04quux\x03foo\x05\x97n\x86" | ||
| b"\xb3)\x83\x1b\x04test\x03\x03bar\x04quux\x03fooqn\xab)", | ||
| b"\xb3)\x82\x17\x04test\x02\x04quux\x03foo\x05\x97n\x86" | ||
| b"\xb3)\x83\x1b\x04test\x03\x04quux\x03foo\x03bar\xaf\xbd\x9d\xd6", | ||
| b"\xb3)\x82\x17\x04test\x02\x04quux\x03foo\x05\x97n\x86" | ||
| b"\xb3)\x83\x1b\x04test\x03\x04quux\x03bar\x03foo\x03\x04b\r", | ||
| b"\xb3)\x82\x17\x04test\x02\x04quux\x03foo\x05\x97n\x86" | ||
| b"\xb3)\x83\x1b\x04test\x03\x03bar\x03foo\x04quux\xd2\x18\x1bC", | ||
| b"\xb3)\x82\x17\x04test\x02\x04quux\x03foo\x05\x97n\x86" | ||
| b"\xb3)\x83\x1b\x04test\x03\x03foo\x04quux\x03bar\x08\xc2X\x83", | ||
| b"\xb3)\x82\x17\x04test\x02\x03foo\x04quux\xa6\xe1\xde\xec" | ||
| b"\xb3)\x83\x1b\x04test\x03\x03foo\x04quux\x03bar\x08\xc2X\x83", | ||
| b"\xb3)\x82\x17\x04test\x02\x03foo\x04quux\xa6\xe1\xde\xec" | ||
| b"\xb3)\x83\x1b\x04test\x03\x03bar\x03foo\x04quux\xd2\x18\x1bC", | ||
| b"\xb3)\x82\x17\x04test\x02\x03foo\x04quux\xa6\xe1\xde\xec" | ||
| b"\xb3)\x83\x1b\x04test\x03\x03foo\x03bar\x04quux:\x05e\x80", | ||
| b"\xb3)\x82\x17\x04test\x02\x03foo\x04quux\xa6\xe1\xde\xec" | ||
| b"\xb3)\x83\x1b\x04test\x03\x04quux\x03foo\x03bar\xaf\xbd\x9d\xd6", | ||
| ] | ||
| stream = subunit.StreamResultToBytes(self.original) | ||
| stream.status( | ||
| test_id='test', test_status='inprogress', test_tags={'foo'}) | ||
| stream.status( | ||
| test_id='test', test_status='success', test_tags={'foo', 'bar'}) | ||
| stream.status(test_id="test", test_status="inprogress", test_tags={"foo"}) | ||
| stream.status(test_id="test", test_status="success", test_tags={"foo", "bar"}) | ||
| self.original.seek(0) | ||
| self.assertEqual( | ||
| 0, subunit.tag_stream(self.original, self.filtered, ["quux"])) | ||
| self.assertEqual(0, subunit.tag_stream(self.original, self.filtered, ["quux"])) | ||
| self.assertThat(reference, Contains(bytes(self.filtered.getvalue()))) | ||
@@ -78,14 +76,9 @@ | ||
| stream = subunit.StreamResultToBytes(reference) | ||
| stream.status( | ||
| test_id='test', test_status='inprogress', test_tags={'foo'}) | ||
| stream.status( | ||
| test_id='test', test_status='success', test_tags={'foo'}) | ||
| stream.status(test_id="test", test_status="inprogress", test_tags={"foo"}) | ||
| stream.status(test_id="test", test_status="success", test_tags={"foo"}) | ||
| stream = subunit.StreamResultToBytes(self.original) | ||
| stream.status( | ||
| test_id='test', test_status='inprogress', test_tags={'foo'}) | ||
| stream.status( | ||
| test_id='test', test_status='success', test_tags={'foo', 'bar'}) | ||
| stream.status(test_id="test", test_status="inprogress", test_tags={"foo"}) | ||
| stream.status(test_id="test", test_status="success", test_tags={"foo", "bar"}) | ||
| self.original.seek(0) | ||
| self.assertEqual( | ||
| 0, subunit.tag_stream(self.original, self.filtered, ["-bar"])) | ||
| self.assertEqual(0, subunit.tag_stream(self.original, self.filtered, ["-bar"])) | ||
| self.assertEqual(reference.getvalue(), self.filtered.getvalue()) |
@@ -9,3 +9,3 @@ # | ||
| # compliance with one of these two licences. | ||
| # | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
@@ -23,3 +23,3 @@ # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| from testtools import TestCase | ||
| from testtools.compat import _u | ||
| from testtools.testresult.doubles import StreamResult | ||
@@ -29,3 +29,3 @@ | ||
| UTF8_TEXT = 'text/plain; charset=UTF8' | ||
| UTF8_TEXT = "text/plain; charset=UTF8" | ||
@@ -52,9 +52,23 @@ | ||
| # results in a single skipped test. | ||
| self.tap.write(_u("1..0 # Skipped: entire file skipped\n")) | ||
| self.tap.write("1..0 # Skipped: entire file skipped\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([('status', 'file skip', 'skip', None, True, | ||
| 'tap comment', b'Skipped: entire file skipped', True, None, None, | ||
| None)]) | ||
| self.check_events( | ||
| [ | ||
| ( | ||
| "status", | ||
| "file skip", | ||
| "skip", | ||
| None, | ||
| True, | ||
| "tap comment", | ||
| b"Skipped: entire file skipped", | ||
| True, | ||
| None, | ||
| None, | ||
| None, | ||
| ) | ||
| ] | ||
| ) | ||
@@ -67,8 +81,7 @@ def test_ok_test_pass(self): | ||
| # stream). | ||
| self.tap.write(_u("ok\n")) | ||
| self.tap.write("ok\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([('status', 'test 1', 'success', None, False, None, | ||
| None, True, None, None, None)]) | ||
| self.check_events([("status", "test 1", "success", None, False, None, None, True, None, None, None)]) | ||
@@ -79,8 +92,7 @@ def test_ok_test_number_pass(self): | ||
| # results in a passed test with name 'test 1' | ||
| self.tap.write(_u("ok 1\n")) | ||
| self.tap.write("ok 1\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([('status', 'test 1', 'success', None, False, None, | ||
| None, True, None, None, None)]) | ||
| self.check_events([("status", "test 1", "success", None, False, None, None, True, None, None, None)]) | ||
@@ -91,8 +103,9 @@ def test_ok_test_number_description_pass(self): | ||
| # results in a passed test with name 'test 1 - There is a description' | ||
| self.tap.write(_u("ok 1 - There is a description\n")) | ||
| self.tap.write("ok 1 - There is a description\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([('status', 'test 1 - There is a description', | ||
| 'success', None, False, None, None, True, None, None, None)]) | ||
| self.check_events( | ||
| [("status", "test 1 - There is a description", "success", None, False, None, None, True, None, None, None)] | ||
| ) | ||
@@ -103,8 +116,9 @@ def test_ok_test_description_pass(self): | ||
| # results in a passed test with name 'test 1 There is a description' | ||
| self.tap.write(_u("ok There is a description\n")) | ||
| self.tap.write("ok There is a description\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([('status', 'test 1 There is a description', | ||
| 'success', None, False, None, None, True, None, None, None)]) | ||
| self.check_events( | ||
| [("status", "test 1 There is a description", "success", None, False, None, None, True, None, None, None)] | ||
| ) | ||
@@ -115,17 +129,30 @@ def test_ok_SKIP_skip(self): | ||
| # results in a skkip test with name 'test 1' | ||
| self.tap.write(_u("ok # SKIP\n")) | ||
| self.tap.write("ok # SKIP\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([('status', 'test 1', 'skip', None, False, None, | ||
| None, True, None, None, None)]) | ||
| self.check_events([("status", "test 1", "skip", None, False, None, None, True, None, None, None)]) | ||
| def test_ok_skip_number_comment_lowercase(self): | ||
| self.tap.write(_u("ok 1 # skip no samba environment available, skipping compilation\n")) | ||
| self.tap.write("ok 1 # skip no samba environment available, skipping compilation\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([('status', 'test 1', 'skip', None, False, 'tap comment', | ||
| b'no samba environment available, skipping compilation', True, | ||
| 'text/plain; charset=UTF8', None, None)]) | ||
| self.check_events( | ||
| [ | ||
| ( | ||
| "status", | ||
| "test 1", | ||
| "skip", | ||
| None, | ||
| False, | ||
| "tap comment", | ||
| b"no samba environment available, skipping compilation", | ||
| True, | ||
| "text/plain; charset=UTF8", | ||
| None, | ||
| None, | ||
| ) | ||
| ] | ||
| ) | ||
@@ -137,9 +164,23 @@ def test_ok_number_description_SKIP_skip_comment(self): | ||
| # Not done yet | ||
| self.tap.write(_u("ok 1 foo # SKIP Not done yet\n")) | ||
| self.tap.write("ok 1 foo # SKIP Not done yet\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([('status', 'test 1 foo', 'skip', None, False, | ||
| 'tap comment', b'Not done yet', True, 'text/plain; charset=UTF8', | ||
| None, None)]) | ||
| self.check_events( | ||
| [ | ||
| ( | ||
| "status", | ||
| "test 1 foo", | ||
| "skip", | ||
| None, | ||
| False, | ||
| "tap comment", | ||
| b"Not done yet", | ||
| True, | ||
| "text/plain; charset=UTF8", | ||
| None, | ||
| None, | ||
| ) | ||
| ] | ||
| ) | ||
@@ -150,9 +191,23 @@ def test_ok_SKIP_skip_comment(self): | ||
| # results in a skip test with name 'test 1' and a log of Not done yet | ||
| self.tap.write(_u("ok # SKIP Not done yet\n")) | ||
| self.tap.write("ok # SKIP Not done yet\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([('status', 'test 1', 'skip', None, False, | ||
| 'tap comment', b'Not done yet', True, 'text/plain; charset=UTF8', | ||
| None, None)]) | ||
| self.check_events( | ||
| [ | ||
| ( | ||
| "status", | ||
| "test 1", | ||
| "skip", | ||
| None, | ||
| False, | ||
| "tap comment", | ||
| b"Not done yet", | ||
| True, | ||
| "text/plain; charset=UTF8", | ||
| None, | ||
| None, | ||
| ) | ||
| ] | ||
| ) | ||
@@ -163,8 +218,7 @@ def test_ok_TODO_xfail(self): | ||
| # results in a xfail test with name 'test 1' | ||
| self.tap.write(_u("ok # TODO\n")) | ||
| self.tap.write("ok # TODO\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([('status', 'test 1', 'xfail', None, False, None, | ||
| None, True, None, None, None)]) | ||
| self.check_events([("status", "test 1", "xfail", None, False, None, None, True, None, None, None)]) | ||
@@ -175,9 +229,23 @@ def test_ok_TODO_xfail_comment(self): | ||
| # results in a xfail test with name 'test 1' and a log of Not done yet | ||
| self.tap.write(_u("ok # TODO Not done yet\n")) | ||
| self.tap.write("ok # TODO Not done yet\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([('status', 'test 1', 'xfail', None, False, | ||
| 'tap comment', b'Not done yet', True, 'text/plain; charset=UTF8', | ||
| None, None)]) | ||
| self.check_events( | ||
| [ | ||
| ( | ||
| "status", | ||
| "test 1", | ||
| "xfail", | ||
| None, | ||
| False, | ||
| "tap comment", | ||
| b"Not done yet", | ||
| True, | ||
| "text/plain; charset=UTF8", | ||
| None, | ||
| None, | ||
| ) | ||
| ] | ||
| ) | ||
@@ -188,12 +256,13 @@ def test_bail_out_errors(self): | ||
| # is treated as an error | ||
| self.tap.write(_u("ok 1 foo\n")) | ||
| self.tap.write(_u("Bail out! Lifejacket engaged\n")) | ||
| self.tap.write("ok 1 foo\n") | ||
| self.tap.write("Bail out! Lifejacket engaged\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([ | ||
| ('status', 'test 1 foo', 'success', None, False, None, None, True, | ||
| None, None, None), | ||
| ('status', 'Bail out! Lifejacket engaged', 'fail', None, False, | ||
| None, None, True, None, None, None)]) | ||
| self.check_events( | ||
| [ | ||
| ("status", "test 1 foo", "success", None, False, None, None, True, None, None, None), | ||
| ("status", "Bail out! Lifejacket engaged", "fail", None, False, None, None, True, None, None, None), | ||
| ] | ||
| ) | ||
@@ -206,16 +275,27 @@ def test_missing_test_at_end_with_plan_adds_error(self): | ||
| # results in three tests, with the third being created | ||
| self.tap.write(_u('1..3\n')) | ||
| self.tap.write(_u('ok first test\n')) | ||
| self.tap.write(_u('not ok second test\n')) | ||
| self.tap.write("1..3\n") | ||
| self.tap.write("ok first test\n") | ||
| self.tap.write("not ok second test\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([ | ||
| ('status', 'test 1 first test', 'success', None, False, None, | ||
| None, True, None, None, None), | ||
| ('status', 'test 2 second test', 'fail', None, False, None, None, | ||
| True, None, None, None), | ||
| ('status', 'test 3', 'fail', None, False, 'tap meta', | ||
| b'test missing from TAP output', True, 'text/plain; charset=UTF8', | ||
| None, None)]) | ||
| self.check_events( | ||
| [ | ||
| ("status", "test 1 first test", "success", None, False, None, None, True, None, None, None), | ||
| ("status", "test 2 second test", "fail", None, False, None, None, True, None, None, None), | ||
| ( | ||
| "status", | ||
| "test 3", | ||
| "fail", | ||
| None, | ||
| False, | ||
| "tap meta", | ||
| b"test missing from TAP output", | ||
| True, | ||
| "text/plain; charset=UTF8", | ||
| None, | ||
| None, | ||
| ), | ||
| ] | ||
| ) | ||
@@ -228,16 +308,27 @@ def test_missing_test_with_plan_adds_error(self): | ||
| # results in three tests, with the second being created | ||
| self.tap.write(_u('1..3\n')) | ||
| self.tap.write(_u('ok first test\n')) | ||
| self.tap.write(_u('not ok 3 third test\n')) | ||
| self.tap.write("1..3\n") | ||
| self.tap.write("ok first test\n") | ||
| self.tap.write("not ok 3 third test\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([ | ||
| ('status', 'test 1 first test', 'success', None, False, None, None, | ||
| True, None, None, None), | ||
| ('status', 'test 2', 'fail', None, False, 'tap meta', | ||
| b'test missing from TAP output', True, 'text/plain; charset=UTF8', | ||
| None, None), | ||
| ('status', 'test 3 third test', 'fail', None, False, None, None, | ||
| True, None, None, None)]) | ||
| self.check_events( | ||
| [ | ||
| ("status", "test 1 first test", "success", None, False, None, None, True, None, None, None), | ||
| ( | ||
| "status", | ||
| "test 2", | ||
| "fail", | ||
| None, | ||
| False, | ||
| "tap meta", | ||
| b"test missing from TAP output", | ||
| True, | ||
| "text/plain; charset=UTF8", | ||
| None, | ||
| None, | ||
| ), | ||
| ("status", "test 3 third test", "fail", None, False, None, None, True, None, None, None), | ||
| ] | ||
| ) | ||
@@ -249,15 +340,26 @@ def test_missing_test_no_plan_adds_error(self): | ||
| # results in three tests, with the second being created | ||
| self.tap.write(_u('ok first test\n')) | ||
| self.tap.write(_u('not ok 3 third test\n')) | ||
| self.tap.write("ok first test\n") | ||
| self.tap.write("not ok 3 third test\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([ | ||
| ('status', 'test 1 first test', 'success', None, False, None, None, | ||
| True, None, None, None), | ||
| ('status', 'test 2', 'fail', None, False, 'tap meta', | ||
| b'test missing from TAP output', True, 'text/plain; charset=UTF8', | ||
| None, None), | ||
| ('status', 'test 3 third test', 'fail', None, False, None, None, | ||
| True, None, None, None)]) | ||
| self.check_events( | ||
| [ | ||
| ("status", "test 1 first test", "success", None, False, None, None, True, None, None, None), | ||
| ( | ||
| "status", | ||
| "test 2", | ||
| "fail", | ||
| None, | ||
| False, | ||
| "tap meta", | ||
| b"test missing from TAP output", | ||
| True, | ||
| "text/plain; charset=UTF8", | ||
| None, | ||
| None, | ||
| ), | ||
| ("status", "test 3 third test", "fail", None, False, None, None, True, None, None, None), | ||
| ] | ||
| ) | ||
@@ -272,19 +374,30 @@ def test_four_tests_in_a_row_trailing_plan(self): | ||
| # results in four tests numbered and named | ||
| self.tap.write(_u('ok 1 - first test in a script with trailing plan\n')) | ||
| self.tap.write(_u('not ok 2 - second\n')) | ||
| self.tap.write(_u('ok 3 - third\n')) | ||
| self.tap.write(_u('not ok 4 - fourth\n')) | ||
| self.tap.write(_u('1..4\n')) | ||
| self.tap.write("ok 1 - first test in a script with trailing plan\n") | ||
| self.tap.write("not ok 2 - second\n") | ||
| self.tap.write("ok 3 - third\n") | ||
| self.tap.write("not ok 4 - fourth\n") | ||
| self.tap.write("1..4\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([ | ||
| ('status', 'test 1 - first test in a script with trailing plan', | ||
| 'success', None, False, None, None, True, None, None, None), | ||
| ('status', 'test 2 - second', 'fail', None, False, None, None, | ||
| True, None, None, None), | ||
| ('status', 'test 3 - third', 'success', None, False, None, None, | ||
| True, None, None, None), | ||
| ('status', 'test 4 - fourth', 'fail', None, False, None, None, | ||
| True, None, None, None)]) | ||
| self.check_events( | ||
| [ | ||
| ( | ||
| "status", | ||
| "test 1 - first test in a script with trailing plan", | ||
| "success", | ||
| None, | ||
| False, | ||
| None, | ||
| None, | ||
| True, | ||
| None, | ||
| None, | ||
| None, | ||
| ), | ||
| ("status", "test 2 - second", "fail", None, False, None, None, True, None, None, None), | ||
| ("status", "test 3 - third", "success", None, False, None, None, True, None, None, None), | ||
| ("status", "test 4 - fourth", "fail", None, False, None, None, True, None, None, None), | ||
| ] | ||
| ) | ||
@@ -299,19 +412,30 @@ def test_four_tests_in_a_row_with_plan(self): | ||
| # results in four tests numbered and named | ||
| self.tap.write(_u('1..4\n')) | ||
| self.tap.write(_u('ok 1 - first test in a script with a plan\n')) | ||
| self.tap.write(_u('not ok 2 - second\n')) | ||
| self.tap.write(_u('ok 3 - third\n')) | ||
| self.tap.write(_u('not ok 4 - fourth\n')) | ||
| self.tap.write("1..4\n") | ||
| self.tap.write("ok 1 - first test in a script with a plan\n") | ||
| self.tap.write("not ok 2 - second\n") | ||
| self.tap.write("ok 3 - third\n") | ||
| self.tap.write("not ok 4 - fourth\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([ | ||
| ('status', 'test 1 - first test in a script with a plan', | ||
| 'success', None, False, None, None, True, None, None, None), | ||
| ('status', 'test 2 - second', 'fail', None, False, None, None, | ||
| True, None, None, None), | ||
| ('status', 'test 3 - third', 'success', None, False, None, None, | ||
| True, None, None, None), | ||
| ('status', 'test 4 - fourth', 'fail', None, False, None, None, | ||
| True, None, None, None)]) | ||
| self.check_events( | ||
| [ | ||
| ( | ||
| "status", | ||
| "test 1 - first test in a script with a plan", | ||
| "success", | ||
| None, | ||
| False, | ||
| None, | ||
| None, | ||
| True, | ||
| None, | ||
| None, | ||
| None, | ||
| ), | ||
| ("status", "test 2 - second", "fail", None, False, None, None, True, None, None, None), | ||
| ("status", "test 3 - third", "success", None, False, None, None, True, None, None, None), | ||
| ("status", "test 4 - fourth", "fail", None, False, None, None, True, None, None, None), | ||
| ] | ||
| ) | ||
@@ -325,18 +449,29 @@ def test_four_tests_in_a_row_no_plan(self): | ||
| # results in four tests numbered and named | ||
| self.tap.write(_u('ok 1 - first test in a script with no plan at all\n')) | ||
| self.tap.write(_u('not ok 2 - second\n')) | ||
| self.tap.write(_u('ok 3 - third\n')) | ||
| self.tap.write(_u('not ok 4 - fourth\n')) | ||
| self.tap.write("ok 1 - first test in a script with no plan at all\n") | ||
| self.tap.write("not ok 2 - second\n") | ||
| self.tap.write("ok 3 - third\n") | ||
| self.tap.write("not ok 4 - fourth\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([ | ||
| ('status', 'test 1 - first test in a script with no plan at all', | ||
| 'success', None, False, None, None, True, None, None, None), | ||
| ('status', 'test 2 - second', 'fail', None, False, None, None, | ||
| True, None, None, None), | ||
| ('status', 'test 3 - third', 'success', None, False, None, None, | ||
| True, None, None, None), | ||
| ('status', 'test 4 - fourth', 'fail', None, False, None, None, | ||
| True, None, None, None)]) | ||
| self.check_events( | ||
| [ | ||
| ( | ||
| "status", | ||
| "test 1 - first test in a script with no plan at all", | ||
| "success", | ||
| None, | ||
| False, | ||
| None, | ||
| None, | ||
| True, | ||
| None, | ||
| None, | ||
| None, | ||
| ), | ||
| ("status", "test 2 - second", "fail", None, False, None, None, True, None, None, None), | ||
| ("status", "test 3 - third", "success", None, False, None, None, True, None, None, None), | ||
| ("status", "test 4 - fourth", "fail", None, False, None, None, True, None, None, None), | ||
| ] | ||
| ) | ||
@@ -348,4 +483,4 @@ def test_todo_and_skip(self): | ||
| # results in two tests, numbered and commented. | ||
| self.tap.write(_u("not ok 1 - a fail but # TODO but is TODO\n")) | ||
| self.tap.write(_u("not ok 2 - another fail # SKIP instead\n")) | ||
| self.tap.write("not ok 1 - a fail but # TODO but is TODO\n") | ||
| self.tap.write("not ok 2 - another fail # SKIP instead\n") | ||
| self.tap.seek(0) | ||
@@ -357,9 +492,32 @@ result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| subunit.ByteStreamToStreamResult(self.subunit).run(events) | ||
| self.check_events([ | ||
| ('status', 'test 1 - a fail but', 'xfail', None, False, | ||
| 'tap comment', b'but is TODO', True, 'text/plain; charset=UTF8', | ||
| None, None), | ||
| ('status', 'test 2 - another fail', 'skip', None, False, | ||
| 'tap comment', b'instead', True, 'text/plain; charset=UTF8', | ||
| None, None)]) | ||
| self.check_events( | ||
| [ | ||
| ( | ||
| "status", | ||
| "test 1 - a fail but", | ||
| "xfail", | ||
| None, | ||
| False, | ||
| "tap comment", | ||
| b"but is TODO", | ||
| True, | ||
| "text/plain; charset=UTF8", | ||
| None, | ||
| None, | ||
| ), | ||
| ( | ||
| "status", | ||
| "test 2 - another fail", | ||
| "skip", | ||
| None, | ||
| False, | ||
| "tap comment", | ||
| b"instead", | ||
| True, | ||
| "text/plain; charset=UTF8", | ||
| None, | ||
| None, | ||
| ), | ||
| ] | ||
| ) | ||
@@ -369,18 +527,31 @@ def test_leading_comments_add_to_next_test_log(self): | ||
| # # comment | ||
| # ok | ||
| # ok | ||
| # ok | ||
| # results in a single test with the comment included | ||
| # in the first test and not the second. | ||
| self.tap.write(_u("# comment\n")) | ||
| self.tap.write(_u("ok\n")) | ||
| self.tap.write(_u("ok\n")) | ||
| self.tap.write("# comment\n") | ||
| self.tap.write("ok\n") | ||
| self.tap.write("ok\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([ | ||
| ('status', 'test 1', 'success', None, False, 'tap comment', | ||
| b'# comment', True, 'text/plain; charset=UTF8', None, None), | ||
| ('status', 'test 2', 'success', None, False, None, None, True, | ||
| None, None, None)]) | ||
| self.check_events( | ||
| [ | ||
| ( | ||
| "status", | ||
| "test 1", | ||
| "success", | ||
| None, | ||
| False, | ||
| "tap comment", | ||
| b"# comment", | ||
| True, | ||
| "text/plain; charset=UTF8", | ||
| None, | ||
| None, | ||
| ), | ||
| ("status", "test 2", "success", None, False, None, None, True, None, None, None), | ||
| ] | ||
| ) | ||
| def test_trailing_comments_are_included_in_last_test_log(self): | ||
@@ -393,13 +564,26 @@ # A file | ||
| # attached to its log. | ||
| self.tap.write(_u("ok\n")) | ||
| self.tap.write(_u("ok\n")) | ||
| self.tap.write(_u("# comment\n")) | ||
| self.tap.write("ok\n") | ||
| self.tap.write("ok\n") | ||
| self.tap.write("# comment\n") | ||
| self.tap.seek(0) | ||
| result = subunit.TAP2SubUnit(self.tap, self.subunit) | ||
| self.assertEqual(0, result) | ||
| self.check_events([ | ||
| ('status', 'test 1', 'success', None, False, None, None, True, | ||
| None, None, None), | ||
| ('status', 'test 2', 'success', None, False, 'tap comment', | ||
| b'# comment', True, 'text/plain; charset=UTF8', None, None)]) | ||
| self.check_events( | ||
| [ | ||
| ("status", "test 1", "success", None, False, None, None, True, None, None, None), | ||
| ( | ||
| "status", | ||
| "test 2", | ||
| "success", | ||
| None, | ||
| False, | ||
| "tap comment", | ||
| b"# comment", | ||
| True, | ||
| "text/plain; charset=UTF8", | ||
| None, | ||
| None, | ||
| ), | ||
| ] | ||
| ) | ||
@@ -406,0 +590,0 @@ def check_events(self, events): |
@@ -25,16 +25,6 @@ # | ||
| from testtools import PlaceHolder, TestCase, TestResult, skipIf | ||
| from testtools.compat import _b, _u | ||
| from testtools.content import Content, TracebackContent, text_content | ||
| from testtools.content_type import ContentType | ||
| try: | ||
| from testtools.testresult.doubles import (ExtendedTestResult, | ||
| Python26TestResult, | ||
| Python27TestResult) | ||
| except ImportError: | ||
| from testtools.tests.helpers import ( | ||
| Python26TestResult, | ||
| Python27TestResult, | ||
| ExtendedTestResult, | ||
| ) | ||
| from testtools.testresult.doubles import ExtendedTestResult | ||
@@ -46,6 +36,8 @@ from testtools.matchers import Contains, Equals, MatchesAny | ||
| import subunit | ||
| from subunit.tests import (_remote_exception_repr, | ||
| _remote_exception_repr_chunked, | ||
| _remote_exception_str, | ||
| _remote_exception_str_chunked) | ||
| from subunit.tests import ( | ||
| _remote_exception_repr, | ||
| _remote_exception_repr_chunked, | ||
| _remote_exception_str, | ||
| _remote_exception_str_chunked, | ||
| ) | ||
@@ -63,3 +55,3 @@ tb_prelude = "Traceback (most recent call last):\n" | ||
| self.addCleanup(os.remove, file_path) | ||
| fake_file = os.fdopen(fd, 'r') | ||
| fake_file = os.fdopen(fd, "r") | ||
| self.assertEqual(fake_file.buffer, subunit._unwrap_text(fake_file)) | ||
@@ -70,3 +62,3 @@ | ||
| self.addCleanup(os.remove, file_path) | ||
| fake_file = os.fdopen(fd, 'w') | ||
| fake_file = os.fdopen(fd, "w") | ||
| self.assertEqual(fake_file.buffer, subunit._unwrap_text(fake_file)) | ||
@@ -77,3 +69,3 @@ | ||
| self.addCleanup(os.remove, file_path) | ||
| fake_file = io.FileIO(file_path, 'r') | ||
| fake_file = io.FileIO(file_path, "r") | ||
| self.assertEqual(fake_file, subunit._unwrap_text(fake_file)) | ||
@@ -84,3 +76,3 @@ | ||
| self.addCleanup(os.remove, file_path) | ||
| fake_file = io.FileIO(file_path, 'w') | ||
| fake_file = io.FileIO(file_path, "w") | ||
| self.assertEqual(fake_file, subunit._unwrap_text(fake_file)) | ||
@@ -94,11 +86,16 @@ | ||
| class TestTestImports(unittest.TestCase): | ||
| def test_imports(self): | ||
| from subunit import (DiscardStream, ExecTestCase, IsolatedTestCase, # noqa: F401 | ||
| ProtocolTestCase, RemotedTestCase, RemoteError, # noqa: F401 | ||
| TestProtocolClient, TestProtocolServer) # noqa: F401 | ||
| from subunit import ( # noqa: F401 | ||
| DiscardStream, | ||
| ExecTestCase, | ||
| IsolatedTestCase, | ||
| ProtocolTestCase, | ||
| RemotedTestCase, | ||
| RemoteError, | ||
| TestProtocolClient, | ||
| TestProtocolServer, | ||
| ) # noqa: F401 | ||
| class TestDiscardStream(unittest.TestCase): | ||
| def test_write(self): | ||
@@ -109,3 +106,2 @@ subunit.DiscardStream().write("content") | ||
| class TestProtocolServerForward(unittest.TestCase): | ||
| def test_story(self): | ||
@@ -115,4 +111,3 @@ client = unittest.TestResult() | ||
| protocol = subunit.TestProtocolServer(client, forward_stream=out) | ||
| pipe = BytesIO(_b("test old mcdonald\n" | ||
| "success old mcdonald\n")) | ||
| pipe = BytesIO(b"test old mcdonald\nsuccess old mcdonald\n") | ||
| protocol.readFrom(pipe) | ||
@@ -125,12 +120,10 @@ self.assertEqual(client.testsRun, 1) | ||
| out = BytesIO() | ||
| protocol = subunit.TestProtocolServer(client, | ||
| stream=subunit.DiscardStream(), forward_stream=out) | ||
| pipe = BytesIO(_b("success old mcdonald\n")) | ||
| protocol = subunit.TestProtocolServer(client, stream=subunit.DiscardStream(), forward_stream=out) | ||
| pipe = BytesIO(b"success old mcdonald\n") | ||
| protocol.readFrom(pipe) | ||
| self.assertEqual(client.testsRun, 0) | ||
| self.assertEqual(_b(""), out.getvalue()) | ||
| self.assertEqual(b"", out.getvalue()) | ||
| class TestTestProtocolServerPipe(unittest.TestCase): | ||
| def test_story(self): | ||
@@ -140,10 +133,10 @@ client = unittest.TestResult() | ||
| traceback = "foo.c:53:ERROR invalid state\n" | ||
| pipe = BytesIO(_b("test old mcdonald\n" | ||
| "success old mcdonald\n" | ||
| "test bing crosby\n" | ||
| "failure bing crosby [\n" | ||
| + traceback + | ||
| "]\n" | ||
| "test an error\n" | ||
| "error an error\n")) | ||
| pipe = BytesIO( | ||
| b"test old mcdonald\n" | ||
| b"success old mcdonald\n" | ||
| b"test bing crosby\n" | ||
| b"failure bing crosby [\n" + traceback.encode() + b"]\n" | ||
| b"test an error\n" | ||
| b"error an error\n" | ||
| ) | ||
| protocol.readFrom(pipe) | ||
@@ -153,8 +146,8 @@ bing = subunit.RemotedTestCase("bing crosby") | ||
| self.assertEqual( | ||
| client.errors, [(an_error, _remote_exception_repr + '\n')], | ||
| client.errors, | ||
| [(an_error, _remote_exception_repr + "\n")], | ||
| ) | ||
| self.assertEqual( | ||
| client.failures, | ||
| [(bing, _remote_exception_repr + ": " | ||
| + details_to_str({'traceback': text_content(traceback)}) + "\n")], | ||
| [(bing, _remote_exception_repr + ": " + details_to_str({"traceback": text_content(traceback)}) + "\n")], | ||
| ) | ||
@@ -168,5 +161,4 @@ self.assertEqual(client.testsRun, 3) | ||
| class TestTestProtocolServerStartTest(unittest.TestCase): | ||
| def setUp(self): | ||
| self.client = Python26TestResult() | ||
| self.client = ExtendedTestResult() | ||
| self.stream = BytesIO() | ||
@@ -176,18 +168,15 @@ self.protocol = subunit.TestProtocolServer(self.client, self.stream) | ||
| def test_start_test(self): | ||
| self.protocol.lineReceived(_b("test old mcdonald\n")) | ||
| self.assertEqual(self.client._events, | ||
| [('startTest', subunit.RemotedTestCase("old mcdonald"))]) | ||
| self.protocol.lineReceived(b"test old mcdonald\n") | ||
| self.assertEqual(self.client._events, [("startTest", subunit.RemotedTestCase("old mcdonald"))]) | ||
| def test_start_testing(self): | ||
| self.protocol.lineReceived(_b("testing old mcdonald\n")) | ||
| self.assertEqual(self.client._events, | ||
| [('startTest', subunit.RemotedTestCase("old mcdonald"))]) | ||
| self.protocol.lineReceived(b"testing old mcdonald\n") | ||
| self.assertEqual(self.client._events, [("startTest", subunit.RemotedTestCase("old mcdonald"))]) | ||
| def test_start_test_colon(self): | ||
| self.protocol.lineReceived(_b("test: old mcdonald\n")) | ||
| self.assertEqual(self.client._events, | ||
| [('startTest', subunit.RemotedTestCase("old mcdonald"))]) | ||
| self.protocol.lineReceived(b"test: old mcdonald\n") | ||
| self.assertEqual(self.client._events, [("startTest", subunit.RemotedTestCase("old mcdonald"))]) | ||
| def test_indented_test_colon_ignored(self): | ||
| ignored_line = _b(" test: old mcdonald\n") | ||
| ignored_line = b" test: old mcdonald\n" | ||
| self.protocol.lineReceived(ignored_line) | ||
@@ -198,9 +187,7 @@ self.assertEqual([], self.client._events) | ||
| def test_start_testing_colon(self): | ||
| self.protocol.lineReceived(_b("testing: old mcdonald\n")) | ||
| self.assertEqual(self.client._events, | ||
| [('startTest', subunit.RemotedTestCase("old mcdonald"))]) | ||
| self.protocol.lineReceived(b"testing: old mcdonald\n") | ||
| self.assertEqual(self.client._events, [("startTest", subunit.RemotedTestCase("old mcdonald"))]) | ||
| class TestTestProtocolServerPassThrough(unittest.TestCase): | ||
| def setUp(self): | ||
@@ -213,20 +200,15 @@ self.stdout = BytesIO() | ||
| def keywords_before_test(self): | ||
| self.protocol.lineReceived(_b("failure a\n")) | ||
| self.protocol.lineReceived(_b("failure: a\n")) | ||
| self.protocol.lineReceived(_b("error a\n")) | ||
| self.protocol.lineReceived(_b("error: a\n")) | ||
| self.protocol.lineReceived(_b("success a\n")) | ||
| self.protocol.lineReceived(_b("success: a\n")) | ||
| self.protocol.lineReceived(_b("successful a\n")) | ||
| self.protocol.lineReceived(_b("successful: a\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.assertEqual(self.stdout.getvalue(), _b("failure a\n" | ||
| "failure: a\n" | ||
| "error a\n" | ||
| "error: a\n" | ||
| "success a\n" | ||
| "success: a\n" | ||
| "successful a\n" | ||
| "successful: a\n" | ||
| "]\n")) | ||
| self.protocol.lineReceived(b"failure a\n") | ||
| self.protocol.lineReceived(b"failure: a\n") | ||
| self.protocol.lineReceived(b"error a\n") | ||
| self.protocol.lineReceived(b"error: a\n") | ||
| self.protocol.lineReceived(b"success a\n") | ||
| self.protocol.lineReceived(b"success: a\n") | ||
| self.protocol.lineReceived(b"successful a\n") | ||
| self.protocol.lineReceived(b"successful: a\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| self.assertEqual( | ||
| self.stdout.getvalue(), | ||
| b"failure a\nfailure: a\nerror a\nerror: a\nsuccess a\nsuccess: a\nsuccessful a\nsuccessful: a\n]\n", | ||
| ) | ||
@@ -238,59 +220,74 @@ def test_keywords_before_test(self): | ||
| def test_keywords_after_error(self): | ||
| self.protocol.lineReceived(_b("test old mcdonald\n")) | ||
| self.protocol.lineReceived(_b("error old mcdonald\n")) | ||
| self.protocol.lineReceived(b"test old mcdonald\n") | ||
| self.protocol.lineReceived(b"error old mcdonald\n") | ||
| self.keywords_before_test() | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addError', self.test, {}), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addError", self.test, {}), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
| def test_keywords_after_failure(self): | ||
| self.protocol.lineReceived(_b("test old mcdonald\n")) | ||
| self.protocol.lineReceived(_b("failure old mcdonald\n")) | ||
| self.protocol.lineReceived(b"test old mcdonald\n") | ||
| self.protocol.lineReceived(b"failure old mcdonald\n") | ||
| self.keywords_before_test() | ||
| self.assertEqual(self.client._events, [ | ||
| ('startTest', self.test), | ||
| ('addFailure', self.test, {}), | ||
| ('stopTest', self.test), | ||
| ]) | ||
| self.assertEqual( | ||
| self.client._events, | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addFailure", self.test, {}), | ||
| ("stopTest", self.test), | ||
| ], | ||
| ) | ||
| def test_keywords_after_success(self): | ||
| self.protocol.lineReceived(_b("test old mcdonald\n")) | ||
| self.protocol.lineReceived(_b("success old mcdonald\n")) | ||
| self.protocol.lineReceived(b"test old mcdonald\n") | ||
| self.protocol.lineReceived(b"success old mcdonald\n") | ||
| self.keywords_before_test() | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addSuccess', self.test), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addSuccess", self.test), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
| def test_keywords_after_test(self): | ||
| self.protocol.lineReceived(_b("test old mcdonald\n")) | ||
| self.protocol.lineReceived(_b("test old mcdonald\n")) | ||
| self.protocol.lineReceived(_b("failure a\n")) | ||
| self.protocol.lineReceived(_b("failure: a\n")) | ||
| self.protocol.lineReceived(_b("error a\n")) | ||
| self.protocol.lineReceived(_b("error: a\n")) | ||
| self.protocol.lineReceived(_b("success a\n")) | ||
| self.protocol.lineReceived(_b("success: a\n")) | ||
| self.protocol.lineReceived(_b("successful a\n")) | ||
| self.protocol.lineReceived(_b("successful: a\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.protocol.lineReceived(_b("failure old mcdonald\n")) | ||
| self.assertEqual(self.stdout.getvalue(), _b("test old mcdonald\n" | ||
| "failure a\n" | ||
| "failure: a\n" | ||
| "error a\n" | ||
| "error: a\n" | ||
| "success a\n" | ||
| "success: a\n" | ||
| "successful a\n" | ||
| "successful: a\n" | ||
| "]\n")) | ||
| self.assertEqual(self.client._events, [ | ||
| ('startTest', self.test), | ||
| ('addFailure', self.test, {}), | ||
| ('stopTest', self.test), | ||
| ]) | ||
| self.protocol.lineReceived(b"test old mcdonald\n") | ||
| self.protocol.lineReceived(b"test old mcdonald\n") | ||
| self.protocol.lineReceived(b"failure a\n") | ||
| self.protocol.lineReceived(b"failure: a\n") | ||
| self.protocol.lineReceived(b"error a\n") | ||
| self.protocol.lineReceived(b"error: a\n") | ||
| self.protocol.lineReceived(b"success a\n") | ||
| self.protocol.lineReceived(b"success: a\n") | ||
| self.protocol.lineReceived(b"successful a\n") | ||
| self.protocol.lineReceived(b"successful: a\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| self.protocol.lineReceived(b"failure old mcdonald\n") | ||
| self.assertEqual( | ||
| self.stdout.getvalue(), | ||
| b"test old mcdonald\n" | ||
| b"failure a\n" | ||
| b"failure: a\n" | ||
| b"error a\n" | ||
| b"error: a\n" | ||
| b"success a\n" | ||
| b"success: a\n" | ||
| b"successful a\n" | ||
| b"successful: a\n" | ||
| b"]\n", | ||
| ) | ||
| self.assertEqual( | ||
| self.client._events, | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addFailure", self.test, {}), | ||
| ("stopTest", self.test), | ||
| ], | ||
| ) | ||
@@ -300,35 +297,40 @@ def test_keywords_during_failure(self): | ||
| # appropriately. | ||
| self.protocol.lineReceived(_b("test old mcdonald\n")) | ||
| self.protocol.lineReceived(_b("failure: old mcdonald [\n")) | ||
| self.protocol.lineReceived(_b("test old mcdonald\n")) | ||
| self.protocol.lineReceived(_b("failure a\n")) | ||
| self.protocol.lineReceived(_b("failure: a\n")) | ||
| self.protocol.lineReceived(_b("error a\n")) | ||
| self.protocol.lineReceived(_b("error: a\n")) | ||
| self.protocol.lineReceived(_b("success a\n")) | ||
| self.protocol.lineReceived(_b("success: a\n")) | ||
| self.protocol.lineReceived(_b("successful a\n")) | ||
| self.protocol.lineReceived(_b("successful: a\n")) | ||
| self.protocol.lineReceived(_b(" ]\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.assertEqual(self.stdout.getvalue(), _b("")) | ||
| self.protocol.lineReceived(b"test old mcdonald\n") | ||
| self.protocol.lineReceived(b"failure: old mcdonald [\n") | ||
| self.protocol.lineReceived(b"test old mcdonald\n") | ||
| self.protocol.lineReceived(b"failure a\n") | ||
| self.protocol.lineReceived(b"failure: a\n") | ||
| self.protocol.lineReceived(b"error a\n") | ||
| self.protocol.lineReceived(b"error: a\n") | ||
| self.protocol.lineReceived(b"success a\n") | ||
| self.protocol.lineReceived(b"success: a\n") | ||
| self.protocol.lineReceived(b"successful a\n") | ||
| self.protocol.lineReceived(b"successful: a\n") | ||
| self.protocol.lineReceived(b" ]\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| self.assertEqual(self.stdout.getvalue(), b"") | ||
| details = {} | ||
| details['traceback'] = Content(ContentType("text", "x-traceback", | ||
| {'charset': 'utf8'}), | ||
| lambda:[_b( | ||
| "test old mcdonald\n" | ||
| "failure a\n" | ||
| "failure: a\n" | ||
| "error a\n" | ||
| "error: a\n" | ||
| "success a\n" | ||
| "success: a\n" | ||
| "successful a\n" | ||
| "successful: a\n" | ||
| "]\n")]) | ||
| self.assertEqual(self.client._events, [ | ||
| ('startTest', self.test), | ||
| ('addFailure', self.test, details), | ||
| ('stopTest', self.test), | ||
| ]) | ||
| details["traceback"] = Content( | ||
| ContentType("text", "x-traceback", {"charset": "utf8"}), | ||
| lambda: [ | ||
| b"test old mcdonald\n" | ||
| b"failure a\n" | ||
| b"failure: a\n" | ||
| b"error a\n" | ||
| b"error: a\n" | ||
| b"success a\n" | ||
| b"success: a\n" | ||
| b"successful a\n" | ||
| b"successful: a\n" | ||
| b"]\n" | ||
| ], | ||
| ) | ||
| self.assertEqual( | ||
| self.client._events, | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addFailure", self.test, details), | ||
| ("stopTest", self.test), | ||
| ], | ||
| ) | ||
@@ -339,3 +341,3 @@ def test_stdout_passthrough(self): | ||
| """ | ||
| bytes = _b("randombytes\n") | ||
| bytes = b"randombytes\n" | ||
| self.protocol.lineReceived(bytes) | ||
@@ -346,5 +348,4 @@ self.assertEqual(self.stdout.getvalue(), bytes) | ||
| class TestTestProtocolServerLostConnection(unittest.TestCase): | ||
| def setUp(self): | ||
| self.client = Python26TestResult() | ||
| self.client = ExtendedTestResult() | ||
| self.protocol = subunit.TestProtocolServer(self.client) | ||
@@ -358,34 +359,40 @@ self.test = subunit.RemotedTestCase("old mcdonald") | ||
| def test_lost_connection_after_start(self): | ||
| self.protocol.lineReceived(_b("test old mcdonald\n")) | ||
| self.protocol.lineReceived(b"test old mcdonald\n") | ||
| self.protocol.lostConnection() | ||
| failure = subunit.RemoteError( | ||
| _u("lost connection during test 'old mcdonald'")) | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addError', self.test, failure), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| failure = subunit.RemoteError("lost connection during test 'old mcdonald'") | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addError", self.test, failure), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
| def test_lost_connected_after_error(self): | ||
| self.protocol.lineReceived(_b("test old mcdonald\n")) | ||
| self.protocol.lineReceived(_b("error old mcdonald\n")) | ||
| self.protocol.lineReceived(b"test old mcdonald\n") | ||
| self.protocol.lineReceived(b"error old mcdonald\n") | ||
| self.protocol.lostConnection() | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addError', self.test, subunit.RemoteError(_u(""))), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addError", self.test, {}), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
| def do_connection_lost(self, outcome, opening): | ||
| self.protocol.lineReceived(_b("test old mcdonald\n")) | ||
| self.protocol.lineReceived(_b("{} old mcdonald {}".format(outcome, opening))) | ||
| self.protocol.lineReceived(b"test old mcdonald\n") | ||
| self.protocol.lineReceived("{} old mcdonald {}".format(outcome, opening).encode()) | ||
| self.protocol.lostConnection() | ||
| failure = subunit.RemoteError( | ||
| _u("lost connection during %s report of test 'old mcdonald'") % | ||
| outcome) | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addError', self.test, failure), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| failure = subunit.RemoteError("lost connection during %s report of test 'old mcdonald'" % outcome) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addError", self.test, failure), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
@@ -399,10 +406,13 @@ def test_lost_connection_during_error(self): | ||
| def test_lost_connected_after_failure(self): | ||
| self.protocol.lineReceived(_b("test old mcdonald\n")) | ||
| self.protocol.lineReceived(_b("failure old mcdonald\n")) | ||
| self.protocol.lineReceived(b"test old mcdonald\n") | ||
| self.protocol.lineReceived(b"failure old mcdonald\n") | ||
| self.protocol.lostConnection() | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addFailure', self.test, subunit.RemoteError(_u(""))), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addFailure", self.test, {}), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
@@ -416,10 +426,13 @@ def test_lost_connection_during_failure(self): | ||
| def test_lost_connection_after_success(self): | ||
| self.protocol.lineReceived(_b("test old mcdonald\n")) | ||
| self.protocol.lineReceived(_b("success old mcdonald\n")) | ||
| self.protocol.lineReceived(b"test old mcdonald\n") | ||
| self.protocol.lineReceived(b"success old mcdonald\n") | ||
| self.protocol.lostConnection() | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addSuccess', self.test), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addSuccess", self.test), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
@@ -452,37 +465,34 @@ def test_lost_connection_during_success(self): | ||
| class TestInTestMultipart(unittest.TestCase): | ||
| def setUp(self): | ||
| self.client = ExtendedTestResult() | ||
| self.protocol = subunit.TestProtocolServer(self.client) | ||
| self.protocol.lineReceived(_b("test mcdonalds farm\n")) | ||
| self.test = subunit.RemotedTestCase(_u("mcdonalds farm")) | ||
| self.protocol.lineReceived(b"test mcdonalds farm\n") | ||
| self.test = subunit.RemotedTestCase("mcdonalds farm") | ||
| def test__outcome_sets_details_parser(self): | ||
| self.protocol._reading_success_details.details_parser = None | ||
| self.protocol._state._outcome(0, _b("mcdonalds farm [ multipart\n"), | ||
| None, self.protocol._reading_success_details) | ||
| self.protocol._state._outcome(0, b"mcdonalds farm [ multipart\n", None, self.protocol._reading_success_details) | ||
| parser = self.protocol._reading_success_details.details_parser | ||
| self.assertNotEqual(None, parser) | ||
| self.assertIsInstance( | ||
| parser, | ||
| subunit.details.MultipartDetailsParser | ||
| ) | ||
| self.assertIsInstance(parser, subunit.details.MultipartDetailsParser) | ||
| class TestTestProtocolServerAddError(unittest.TestCase): | ||
| def setUp(self): | ||
| self.client = ExtendedTestResult() | ||
| self.protocol = subunit.TestProtocolServer(self.client) | ||
| self.protocol.lineReceived(_b("test mcdonalds farm\n")) | ||
| self.protocol.lineReceived(b"test mcdonalds farm\n") | ||
| self.test = subunit.RemotedTestCase("mcdonalds farm") | ||
| def simple_error_keyword(self, keyword): | ||
| self.protocol.lineReceived(_b("%s mcdonalds farm\n" % keyword)) | ||
| self.protocol.lineReceived(("%s mcdonalds farm\n" % keyword).encode()) | ||
| details = {} | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addError', self.test, details), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addError", self.test, details), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
@@ -496,25 +506,29 @@ def test_simple_error(self): | ||
| def test_error_empty_message(self): | ||
| self.protocol.lineReceived(_b("error mcdonalds farm [\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.protocol.lineReceived(b"error mcdonalds farm [\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| details = {} | ||
| details['traceback'] = Content(ContentType("text", "x-traceback", | ||
| {'charset': 'utf8'}), lambda:[_b("")]) | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addError', self.test, details), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| details["traceback"] = Content(ContentType("text", "x-traceback", {"charset": "utf8"}), lambda: [b""]) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addError", self.test, details), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
| def error_quoted_bracket(self, keyword): | ||
| self.protocol.lineReceived(_b("%s mcdonalds farm [\n" % keyword)) | ||
| self.protocol.lineReceived(_b(" ]\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.protocol.lineReceived(("%s mcdonalds farm [\n" % keyword).encode()) | ||
| self.protocol.lineReceived(b" ]\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| details = {} | ||
| details['traceback'] = Content(ContentType("text", "x-traceback", | ||
| {'charset': 'utf8'}), lambda:[_b("]\n")]) | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addError', self.test, details), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| details["traceback"] = Content(ContentType("text", "x-traceback", {"charset": "utf8"}), lambda: [b"]\n"]) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addError", self.test, details), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
@@ -529,18 +543,20 @@ def test_error_quoted_bracket(self): | ||
| class TestTestProtocolServerAddFailure(unittest.TestCase): | ||
| def setUp(self): | ||
| self.client = ExtendedTestResult() | ||
| self.protocol = subunit.TestProtocolServer(self.client) | ||
| self.protocol.lineReceived(_b("test mcdonalds farm\n")) | ||
| self.protocol.lineReceived(b"test mcdonalds farm\n") | ||
| self.test = subunit.RemotedTestCase("mcdonalds farm") | ||
| def assertFailure(self, details): | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addFailure', self.test, details), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addFailure", self.test, details), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
| def simple_failure_keyword(self, keyword): | ||
| self.protocol.lineReceived(_b("%s mcdonalds farm\n" % keyword)) | ||
| self.protocol.lineReceived(("%s mcdonalds farm\n" % keyword).encode()) | ||
| details = {} | ||
@@ -556,16 +572,14 @@ self.assertFailure(details) | ||
| def test_failure_empty_message(self): | ||
| self.protocol.lineReceived(_b("failure mcdonalds farm [\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.protocol.lineReceived(b"failure mcdonalds farm [\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| details = {} | ||
| details['traceback'] = Content(ContentType("text", "x-traceback", | ||
| {'charset': 'utf8'}), lambda:[_b("")]) | ||
| details["traceback"] = Content(ContentType("text", "x-traceback", {"charset": "utf8"}), lambda: [b""]) | ||
| self.assertFailure(details) | ||
| def failure_quoted_bracket(self, keyword): | ||
| self.protocol.lineReceived(_b("%s mcdonalds farm [\n" % keyword)) | ||
| self.protocol.lineReceived(_b(" ]\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.protocol.lineReceived(("%s mcdonalds farm [\n" % keyword).encode()) | ||
| self.protocol.lineReceived(b" ]\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| details = {} | ||
| details['traceback'] = Content(ContentType("text", "x-traceback", | ||
| {'charset': 'utf8'}), lambda:[_b("]\n")]) | ||
| details["traceback"] = Content(ContentType("text", "x-traceback", {"charset": "utf8"}), lambda: [b"]\n"]) | ||
| self.assertFailure(details) | ||
@@ -581,114 +595,52 @@ | ||
| class TestTestProtocolServerAddxFail(unittest.TestCase): | ||
| """Tests for the xfail keyword. | ||
| """Tests for the xfail keyword.""" | ||
| In Python this can thunk through to Success due to stdlib limitations (see | ||
| README). | ||
| """ | ||
| def capture_expected_failure(self, test, err): | ||
| self._events.append((test, err)) | ||
| def setup_python26(self): | ||
| """Setup a test object ready to be xfailed and thunk to success.""" | ||
| self.client = Python26TestResult() | ||
| self.setup_protocol() | ||
| def setup_python27(self): | ||
| """Setup a test object ready to be xfailed.""" | ||
| self.client = Python27TestResult() | ||
| self.setup_protocol() | ||
| def setup_python_ex(self): | ||
| def setUp(self): | ||
| """Setup a test object ready to be xfailed with details.""" | ||
| self.client = ExtendedTestResult() | ||
| self.setup_protocol() | ||
| def setup_protocol(self): | ||
| """Setup the protocol based on self.client.""" | ||
| self.protocol = subunit.TestProtocolServer(self.client) | ||
| self.protocol.lineReceived(_b("test mcdonalds farm\n")) | ||
| self.protocol.lineReceived(b"test mcdonalds farm\n") | ||
| self.test = self.client._events[-1][-1] | ||
| def simple_xfail_keyword(self, keyword, as_success): | ||
| self.protocol.lineReceived(_b("%s mcdonalds farm\n" % keyword)) | ||
| self.check_success_or_xfail(as_success) | ||
| def simple_xfail_keyword(self, keyword): | ||
| self.protocol.lineReceived(("%s mcdonalds farm\n" % keyword).encode()) | ||
| self.check_xfail() | ||
| def check_success_or_xfail(self, as_success, error_message=None): | ||
| if as_success: | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addSuccess', self.test), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| else: | ||
| details = {} | ||
| if error_message is not None: | ||
| details['traceback'] = Content( | ||
| ContentType("text", "x-traceback", {'charset': 'utf8'}), | ||
| lambda:[_b(error_message)]) | ||
| if isinstance(self.client, ExtendedTestResult): | ||
| value = details | ||
| else: | ||
| if error_message is not None: | ||
| value = subunit.RemoteError(details_to_str(details)) | ||
| else: | ||
| value = subunit.RemoteError() | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addExpectedFailure', self.test, value), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| def check_xfail(self, error_message=None): | ||
| details = {} | ||
| if error_message is not None: | ||
| details["traceback"] = Content( | ||
| ContentType("text", "x-traceback", {"charset": "utf8"}), lambda: [error_message.encode()] | ||
| ) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addExpectedFailure", self.test, details), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
| def test_simple_xfail(self): | ||
| self.setup_python26() | ||
| self.simple_xfail_keyword("xfail", True) | ||
| self.setup_python27() | ||
| self.simple_xfail_keyword("xfail", False) | ||
| self.setup_python_ex() | ||
| self.simple_xfail_keyword("xfail", False) | ||
| self.simple_xfail_keyword("xfail") | ||
| def test_simple_xfail_colon(self): | ||
| self.setup_python26() | ||
| self.simple_xfail_keyword("xfail:", True) | ||
| self.setup_python27() | ||
| self.simple_xfail_keyword("xfail:", False) | ||
| self.setup_python_ex() | ||
| self.simple_xfail_keyword("xfail:", False) | ||
| self.simple_xfail_keyword("xfail:") | ||
| def test_xfail_empty_message(self): | ||
| self.setup_python26() | ||
| self.empty_message(True) | ||
| self.setup_python27() | ||
| self.empty_message(False) | ||
| self.setup_python_ex() | ||
| self.empty_message(False, error_message="") | ||
| self.protocol.lineReceived(b"xfail mcdonalds farm [\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| self.check_xfail(error_message="") | ||
| def empty_message(self, as_success, error_message="\n"): | ||
| self.protocol.lineReceived(_b("xfail mcdonalds farm [\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.check_success_or_xfail(as_success, error_message) | ||
| def xfail_quoted_bracket(self, keyword, as_success): | ||
| # This tests it is accepted, but cannot test it is used today, because | ||
| # of not having a way to expose it in Python so far. | ||
| self.protocol.lineReceived(_b("%s mcdonalds farm [\n" % keyword)) | ||
| self.protocol.lineReceived(_b(" ]\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.check_success_or_xfail(as_success, "]\n") | ||
| def test_xfail_quoted_bracket(self): | ||
| self.setup_python26() | ||
| self.xfail_quoted_bracket("xfail", True) | ||
| self.setup_python27() | ||
| self.xfail_quoted_bracket("xfail", False) | ||
| self.setup_python_ex() | ||
| self.xfail_quoted_bracket("xfail", False) | ||
| self.protocol.lineReceived(b"xfail mcdonalds farm [\n") | ||
| self.protocol.lineReceived(b" ]\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| self.check_xfail("]\n") | ||
| def test_xfail_colon_quoted_bracket(self): | ||
| self.setup_python26() | ||
| self.xfail_quoted_bracket("xfail:", True) | ||
| self.setup_python27() | ||
| self.xfail_quoted_bracket("xfail:", False) | ||
| self.setup_python_ex() | ||
| self.xfail_quoted_bracket("xfail:", False) | ||
| self.protocol.lineReceived(b"xfail: mcdonalds farm [\n") | ||
| self.protocol.lineReceived(b" ]\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| self.check_xfail("]\n") | ||
@@ -699,112 +651,55 @@ | ||
| def capture_expected_failure(self, test, err): | ||
| self._events.append((test, err)) | ||
| def setup_python26(self): | ||
| """Setup a test object ready to be xfailed and thunk to success.""" | ||
| self.client = Python26TestResult() | ||
| self.setup_protocol() | ||
| def setup_python27(self): | ||
| """Setup a test object ready to be xfailed.""" | ||
| self.client = Python27TestResult() | ||
| self.setup_protocol() | ||
| def setup_python_ex(self): | ||
| """Setup a test object ready to be xfailed with details.""" | ||
| def setUp(self): | ||
| """Setup a test object ready for uxsuccess with details.""" | ||
| super().setUp() | ||
| self.client = ExtendedTestResult() | ||
| self.setup_protocol() | ||
| def setup_protocol(self): | ||
| """Setup the protocol based on self.client.""" | ||
| self.protocol = subunit.TestProtocolServer(self.client) | ||
| self.protocol.lineReceived(_b("test mcdonalds farm\n")) | ||
| self.protocol.lineReceived(b"test mcdonalds farm\n") | ||
| self.test = self.client._events[-1][-1] | ||
| def simple_uxsuccess_keyword(self, keyword, as_fail): | ||
| self.protocol.lineReceived(_b("%s mcdonalds farm\n" % keyword)) | ||
| self.check_fail_or_uxsuccess(as_fail) | ||
| def simple_uxsuccess_keyword(self, keyword): | ||
| self.protocol.lineReceived(("%s mcdonalds farm\n" % keyword).encode()) | ||
| self.check_uxsuccess() | ||
| def check_fail_or_uxsuccess(self, as_fail, error_message=None): | ||
| details = {} | ||
| def check_uxsuccess(self, error_message=None): | ||
| if error_message is not None: | ||
| details['traceback'] = Content( | ||
| ContentType("text", "x-traceback", {'charset': 'utf8'}), | ||
| lambda:[_b(error_message)]) | ||
| if isinstance(self.client, ExtendedTestResult): | ||
| value = details | ||
| details = {} | ||
| details["traceback"] = Content( | ||
| ContentType("text", "x-traceback", {"charset": "utf8"}), lambda: [error_message.encode()] | ||
| ) | ||
| expected_events = [ | ||
| ("startTest", self.test), | ||
| ("addUnexpectedSuccess", self.test, details), | ||
| ("stopTest", self.test), | ||
| ] | ||
| else: | ||
| value = None | ||
| if as_fail: | ||
| self.client._events[1] = self.client._events[1][:2] | ||
| # The value is generated within the extended to original decorator: | ||
| # todo use the testtools matcher to check on this. | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addFailure', self.test), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| elif value: | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addUnexpectedSuccess', self.test, value), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| else: | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addUnexpectedSuccess', self.test), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| expected_events = [ | ||
| ("startTest", self.test), | ||
| ("addUnexpectedSuccess", self.test), | ||
| ("stopTest", self.test), | ||
| ] | ||
| self.assertEqual(expected_events, self.client._events) | ||
| def test_simple_uxsuccess(self): | ||
| self.setup_python26() | ||
| self.simple_uxsuccess_keyword("uxsuccess", True) | ||
| self.setup_python27() | ||
| self.simple_uxsuccess_keyword("uxsuccess", False) | ||
| self.setup_python_ex() | ||
| self.simple_uxsuccess_keyword("uxsuccess", False) | ||
| self.simple_uxsuccess_keyword("uxsuccess") | ||
| def test_simple_uxsuccess_colon(self): | ||
| self.setup_python26() | ||
| self.simple_uxsuccess_keyword("uxsuccess:", True) | ||
| self.setup_python27() | ||
| self.simple_uxsuccess_keyword("uxsuccess:", False) | ||
| self.setup_python_ex() | ||
| self.simple_uxsuccess_keyword("uxsuccess:", False) | ||
| self.simple_uxsuccess_keyword("uxsuccess:") | ||
| def test_uxsuccess_empty_message(self): | ||
| self.setup_python26() | ||
| self.empty_message(True) | ||
| self.setup_python27() | ||
| self.empty_message(False) | ||
| self.setup_python_ex() | ||
| self.empty_message(False, error_message="") | ||
| self.protocol.lineReceived(b"uxsuccess mcdonalds farm [\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| self.check_uxsuccess(error_message="") | ||
| def empty_message(self, as_fail, error_message="\n"): | ||
| self.protocol.lineReceived(_b("uxsuccess mcdonalds farm [\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.check_fail_or_uxsuccess(as_fail, error_message) | ||
| def uxsuccess_quoted_bracket(self, keyword, as_fail): | ||
| self.protocol.lineReceived(_b("%s mcdonalds farm [\n" % keyword)) | ||
| self.protocol.lineReceived(_b(" ]\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.check_fail_or_uxsuccess(as_fail, "]\n") | ||
| def test_uxsuccess_quoted_bracket(self): | ||
| self.setup_python26() | ||
| self.uxsuccess_quoted_bracket("uxsuccess", True) | ||
| self.setup_python27() | ||
| self.uxsuccess_quoted_bracket("uxsuccess", False) | ||
| self.setup_python_ex() | ||
| self.uxsuccess_quoted_bracket("uxsuccess", False) | ||
| self.protocol.lineReceived(b"uxsuccess mcdonalds farm [\n") | ||
| self.protocol.lineReceived(b" ]\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| self.check_uxsuccess("]\n") | ||
| def test_uxsuccess_colon_quoted_bracket(self): | ||
| self.setup_python26() | ||
| self.uxsuccess_quoted_bracket("uxsuccess:", True) | ||
| self.setup_python27() | ||
| self.uxsuccess_quoted_bracket("uxsuccess:", False) | ||
| self.setup_python_ex() | ||
| self.uxsuccess_quoted_bracket("uxsuccess:", False) | ||
| self.protocol.lineReceived(b"uxsuccess: mcdonalds farm [\n") | ||
| self.protocol.lineReceived(b" ]\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| self.check_uxsuccess("]\n") | ||
@@ -823,3 +718,3 @@ | ||
| self.protocol = subunit.TestProtocolServer(self.client) | ||
| self.protocol.lineReceived(_b("test mcdonalds farm\n")) | ||
| self.protocol.lineReceived(b"test mcdonalds farm\n") | ||
| self.test = self.client._events[-1][-1] | ||
@@ -830,12 +725,14 @@ | ||
| if reason is not None: | ||
| details['reason'] = Content( | ||
| ContentType("text", "plain"), lambda:[reason]) | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addSkip', self.test, details), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| details["reason"] = Content(ContentType("text", "plain"), lambda: [reason]) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addSkip", self.test, details), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
| def simple_skip_keyword(self, keyword): | ||
| self.protocol.lineReceived(_b("%s mcdonalds farm\n" % keyword)) | ||
| self.protocol.lineReceived(("%s mcdonalds farm\n" % keyword).encode()) | ||
| self.assertSkip(None) | ||
@@ -850,5 +747,5 @@ | ||
| def test_skip_empty_message(self): | ||
| self.protocol.lineReceived(_b("skip mcdonalds farm [\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.assertSkip(_b("")) | ||
| self.protocol.lineReceived(b"skip mcdonalds farm [\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| self.assertSkip(b"") | ||
@@ -858,6 +755,6 @@ def skip_quoted_bracket(self, keyword): | ||
| # of not having a way to expose it in Python so far. | ||
| self.protocol.lineReceived(_b("%s mcdonalds farm [\n" % keyword)) | ||
| self.protocol.lineReceived(_b(" ]\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.assertSkip(_b("]\n")) | ||
| self.protocol.lineReceived(("%s mcdonalds farm [\n" % keyword).encode()) | ||
| self.protocol.lineReceived(b" ]\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| self.assertSkip(b"]\n") | ||
@@ -872,16 +769,18 @@ def test_skip_quoted_bracket(self): | ||
| class TestTestProtocolServerAddSuccess(unittest.TestCase): | ||
| def setUp(self): | ||
| self.client = ExtendedTestResult() | ||
| self.protocol = subunit.TestProtocolServer(self.client) | ||
| self.protocol.lineReceived(_b("test mcdonalds farm\n")) | ||
| self.protocol.lineReceived(b"test mcdonalds farm\n") | ||
| self.test = subunit.RemotedTestCase("mcdonalds farm") | ||
| def simple_success_keyword(self, keyword): | ||
| self.protocol.lineReceived(_b("%s mcdonalds farm\n" % keyword)) | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addSuccess', self.test), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| self.protocol.lineReceived(("%s mcdonalds farm\n" % keyword).encode()) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addSuccess", self.test), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
@@ -895,14 +794,16 @@ def test_simple_success(self): | ||
| def assertSuccess(self, details): | ||
| self.assertEqual([ | ||
| ('startTest', self.test), | ||
| ('addSuccess', self.test, details), | ||
| ('stopTest', self.test), | ||
| ], self.client._events) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", self.test), | ||
| ("addSuccess", self.test, details), | ||
| ("stopTest", self.test), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
| def test_success_empty_message(self): | ||
| self.protocol.lineReceived(_b("success mcdonalds farm [\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.protocol.lineReceived(b"success mcdonalds farm [\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| details = {} | ||
| details['message'] = Content(ContentType("text", "plain"), | ||
| lambda:[_b("")]) | ||
| details["message"] = Content(ContentType("text", "plain"), lambda: [b""]) | ||
| self.assertSuccess(details) | ||
@@ -913,8 +814,7 @@ | ||
| # of not having a way to expose it in Python so far. | ||
| self.protocol.lineReceived(_b("%s mcdonalds farm [\n" % keyword)) | ||
| self.protocol.lineReceived(_b(" ]\n")) | ||
| self.protocol.lineReceived(_b("]\n")) | ||
| self.protocol.lineReceived(("%s mcdonalds farm [\n" % keyword).encode()) | ||
| self.protocol.lineReceived(b" ]\n") | ||
| self.protocol.lineReceived(b"]\n") | ||
| details = {} | ||
| details['message'] = Content(ContentType("text", "plain"), | ||
| lambda:[_b("]\n")]) | ||
| details["message"] = Content(ContentType("text", "plain"), lambda: [b"]\n"]) | ||
| self.assertSuccess(details) | ||
@@ -933,10 +833,9 @@ | ||
| def test_progress_accepted_stdlib(self): | ||
| self.result = Python26TestResult() | ||
| self.result = ExtendedTestResult() | ||
| self.stream = BytesIO() | ||
| self.protocol = subunit.TestProtocolServer(self.result, | ||
| stream=self.stream) | ||
| self.protocol.lineReceived(_b("progress: 23")) | ||
| self.protocol.lineReceived(_b("progress: -2")) | ||
| self.protocol.lineReceived(_b("progress: +4")) | ||
| self.assertEqual(_b(""), self.stream.getvalue()) | ||
| self.protocol = subunit.TestProtocolServer(self.result, stream=self.stream) | ||
| self.protocol.lineReceived(b"progress: 23") | ||
| self.protocol.lineReceived(b"progress: -2") | ||
| self.protocol.lineReceived(b"progress: +4") | ||
| self.assertEqual(b"", self.stream.getvalue()) | ||
@@ -947,17 +846,19 @@ def test_progress_accepted_extended(self): | ||
| self.stream = BytesIO() | ||
| self.protocol = subunit.TestProtocolServer(self.result, | ||
| stream=self.stream) | ||
| self.protocol.lineReceived(_b("progress: 23")) | ||
| self.protocol.lineReceived(_b("progress: push")) | ||
| self.protocol.lineReceived(_b("progress: -2")) | ||
| self.protocol.lineReceived(_b("progress: pop")) | ||
| self.protocol.lineReceived(_b("progress: +4")) | ||
| self.assertEqual(_b(""), self.stream.getvalue()) | ||
| self.assertEqual([ | ||
| ('progress', 23, subunit.PROGRESS_SET), | ||
| ('progress', None, subunit.PROGRESS_PUSH), | ||
| ('progress', -2, subunit.PROGRESS_CUR), | ||
| ('progress', None, subunit.PROGRESS_POP), | ||
| ('progress', 4, subunit.PROGRESS_CUR), | ||
| ], self.result._events) | ||
| self.protocol = subunit.TestProtocolServer(self.result, stream=self.stream) | ||
| self.protocol.lineReceived(b"progress: 23") | ||
| self.protocol.lineReceived(b"progress: push") | ||
| self.protocol.lineReceived(b"progress: -2") | ||
| self.protocol.lineReceived(b"progress: pop") | ||
| self.protocol.lineReceived(b"progress: +4") | ||
| self.assertEqual(b"", self.stream.getvalue()) | ||
| self.assertEqual( | ||
| [ | ||
| ("progress", 23, subunit.PROGRESS_SET), | ||
| ("progress", None, subunit.PROGRESS_PUSH), | ||
| ("progress", -2, subunit.PROGRESS_CUR), | ||
| ("progress", None, subunit.PROGRESS_POP), | ||
| ("progress", 4, subunit.PROGRESS_CUR), | ||
| ], | ||
| self.result._events, | ||
| ) | ||
@@ -973,30 +874,36 @@ | ||
| def test_initial_tags(self): | ||
| self.protocol.lineReceived(_b("tags: foo bar:baz quux\n")) | ||
| self.assertEqual([ | ||
| ('tags', {"foo", "bar:baz", "quux"}, set()), | ||
| ], self.client._events) | ||
| self.protocol.lineReceived(b"tags: foo bar:baz quux\n") | ||
| self.assertEqual( | ||
| [ | ||
| ("tags", {"foo", "bar:baz", "quux"}, set()), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
| def test_minus_removes_tags(self): | ||
| self.protocol.lineReceived(_b("tags: -bar quux\n")) | ||
| self.assertEqual([ | ||
| ('tags', {"quux"}, {"bar"}), | ||
| ], self.client._events) | ||
| self.protocol.lineReceived(b"tags: -bar quux\n") | ||
| self.assertEqual( | ||
| [ | ||
| ("tags", {"quux"}, {"bar"}), | ||
| ], | ||
| self.client._events, | ||
| ) | ||
| def test_tags_do_not_get_set_on_test(self): | ||
| self.protocol.lineReceived(_b("test mcdonalds farm\n")) | ||
| self.protocol.lineReceived(b"test mcdonalds farm\n") | ||
| test = self.client._events[0][-1] | ||
| self.assertEqual(None, getattr(test, 'tags', None)) | ||
| self.assertEqual(None, getattr(test, "tags", None)) | ||
| def test_tags_do_not_get_set_on_global_tags(self): | ||
| self.protocol.lineReceived(_b("tags: foo bar\n")) | ||
| self.protocol.lineReceived(_b("test mcdonalds farm\n")) | ||
| self.protocol.lineReceived(b"tags: foo bar\n") | ||
| self.protocol.lineReceived(b"test mcdonalds farm\n") | ||
| test = self.client._events[-1][-1] | ||
| self.assertEqual(None, getattr(test, 'tags', None)) | ||
| self.assertEqual(None, getattr(test, "tags", None)) | ||
| def test_tags_get_set_on_test_tags(self): | ||
| self.protocol.lineReceived(_b("test mcdonalds farm\n")) | ||
| self.protocol.lineReceived(b"test mcdonalds farm\n") | ||
| test = self.client._events[-1][-1] | ||
| self.protocol.lineReceived(_b("tags: foo bar\n")) | ||
| self.protocol.lineReceived(_b("success mcdonalds farm\n")) | ||
| self.assertEqual(None, getattr(test, 'tags', None)) | ||
| self.protocol.lineReceived(b"tags: foo bar\n") | ||
| self.protocol.lineReceived(b"success mcdonalds farm\n") | ||
| self.assertEqual(None, getattr(test, "tags", None)) | ||
@@ -1008,8 +915,7 @@ | ||
| def test_time_accepted_stdlib(self): | ||
| self.result = Python26TestResult() | ||
| self.result = ExtendedTestResult() | ||
| self.stream = BytesIO() | ||
| self.protocol = subunit.TestProtocolServer(self.result, | ||
| stream=self.stream) | ||
| self.protocol.lineReceived(_b("time: 2001-12-12 12:59:59Z\n")) | ||
| self.assertEqual(_b(""), self.stream.getvalue()) | ||
| self.protocol = subunit.TestProtocolServer(self.result, stream=self.stream) | ||
| self.protocol.lineReceived(b"time: 2001-12-12 12:59:59Z\n") | ||
| self.assertEqual(b"", self.stream.getvalue()) | ||
@@ -1019,14 +925,9 @@ def test_time_accepted_extended(self): | ||
| self.stream = BytesIO() | ||
| self.protocol = subunit.TestProtocolServer(self.result, | ||
| stream=self.stream) | ||
| self.protocol.lineReceived(_b("time: 2001-12-12 12:59:59Z\n")) | ||
| self.assertEqual(_b(""), self.stream.getvalue()) | ||
| self.assertEqual([ | ||
| ('time', datetime.datetime(2001, 12, 12, 12, 59, 59, 0, | ||
| iso8601.UTC)) | ||
| ], self.result._events) | ||
| self.protocol = subunit.TestProtocolServer(self.result, stream=self.stream) | ||
| self.protocol.lineReceived(b"time: 2001-12-12 12:59:59Z\n") | ||
| self.assertEqual(b"", self.stream.getvalue()) | ||
| self.assertEqual([("time", datetime.datetime(2001, 12, 12, 12, 59, 59, 0, iso8601.UTC))], self.result._events) | ||
| class TestRemotedTestCase(unittest.TestCase): | ||
| def test_simple(self): | ||
@@ -1036,14 +937,9 @@ test = subunit.RemotedTestCase("A test description") | ||
| self.assertRaises(NotImplementedError, test.tearDown) | ||
| self.assertEqual("A test description", | ||
| test.shortDescription()) | ||
| self.assertEqual("A test description", | ||
| test.id()) | ||
| self.assertEqual("A test description", test.shortDescription()) | ||
| self.assertEqual("A test description", test.id()) | ||
| self.assertEqual("A test description (subunit.RemotedTestCase)", "%s" % test) | ||
| self.assertEqual("<subunit.RemotedTestCase description=" | ||
| "'A test description'>", "%r" % test) | ||
| self.assertEqual("<subunit.RemotedTestCase description='A test description'>", "%r" % test) | ||
| result = unittest.TestResult() | ||
| test.run(result) | ||
| self.assertEqual([(test, _remote_exception_repr + ': ' + | ||
| "Cannot run RemotedTestCases.\n\n")], | ||
| result.errors) | ||
| self.assertEqual([(test, _remote_exception_repr + ": " + "Cannot run RemotedTestCases.\n\n")], result.errors) | ||
| self.assertEqual(1, result.testsRun) | ||
@@ -1058,7 +954,6 @@ another_test = subunit.RemotedTestCase("A test description") | ||
| class TestRemoteError(unittest.TestCase): | ||
| def test_eq(self): | ||
| error = subunit.RemoteError(_u("Something went wrong")) | ||
| another_error = subunit.RemoteError(_u("Something went wrong")) | ||
| different_error = subunit.RemoteError(_u("boo!")) | ||
| error = subunit.RemoteError("Something went wrong") | ||
| another_error = subunit.RemoteError("Something went wrong") | ||
| different_error = subunit.RemoteError("boo!") | ||
| self.assertEqual(error, another_error) | ||
@@ -1069,9 +964,7 @@ self.assertNotEqual(error, different_error) | ||
| def test_empty_constructor(self): | ||
| self.assertEqual(subunit.RemoteError(), subunit.RemoteError(_u(""))) | ||
| self.assertEqual(subunit.RemoteError(), subunit.RemoteError("")) | ||
| class TestExecTestCase(unittest.TestCase): | ||
| class SampleExecTestCase(subunit.ExecTestCase): | ||
| def test_sample_method(self): | ||
@@ -1088,4 +981,3 @@ """sample-script.py""" | ||
| test = self.SampleExecTestCase("test_sample_method") | ||
| self.assertEqual(test.script, | ||
| subunit.join_dir(__file__, 'sample-script.py')) | ||
| self.assertEqual(test.script, subunit.join_dir(__file__, "sample-script.py")) | ||
@@ -1105,17 +997,21 @@ def test_args(self): | ||
| bing_details = {} | ||
| bing_details['traceback'] = Content(ContentType("text", "x-traceback", | ||
| {'charset': 'utf8'}), lambda:[_b("foo.c:53:ERROR invalid state\n")]) | ||
| bing_details["traceback"] = Content( | ||
| ContentType("text", "x-traceback", {"charset": "utf8"}), lambda: [b"foo.c:53:ERROR invalid state\n"] | ||
| ) | ||
| an_error = subunit.RemotedTestCase("an error") | ||
| error_details = {} | ||
| self.assertEqual([ | ||
| ('startTest', mcdonald), | ||
| ('addSuccess', mcdonald), | ||
| ('stopTest', mcdonald), | ||
| ('startTest', bing), | ||
| ('addFailure', bing, bing_details), | ||
| ('stopTest', bing), | ||
| ('startTest', an_error), | ||
| ('addError', an_error, error_details), | ||
| ('stopTest', an_error), | ||
| ], result._events) | ||
| self.assertEqual( | ||
| [ | ||
| ("startTest", mcdonald), | ||
| ("addSuccess", mcdonald), | ||
| ("stopTest", mcdonald), | ||
| ("startTest", bing), | ||
| ("addFailure", bing, bing_details), | ||
| ("stopTest", bing), | ||
| ("startTest", an_error), | ||
| ("addError", an_error, error_details), | ||
| ("stopTest", an_error), | ||
| ], | ||
| result._events, | ||
| ) | ||
@@ -1127,8 +1023,8 @@ def test_debug(self): | ||
| def test_count_test_cases(self): | ||
| """TODO run the child process and count responses to determine the count.""" | ||
| """Run the child process and count responses to determine the count.""" | ||
| def test_join_dir(self): | ||
| sibling = subunit.join_dir(__file__, 'foo') | ||
| sibling = subunit.join_dir(__file__, "foo") | ||
| filedir = os.path.abspath(os.path.dirname(__file__)) | ||
| expected = os.path.join(filedir, 'foo') | ||
| expected = os.path.join(filedir, "foo") | ||
| self.assertEqual(sibling, expected) | ||
@@ -1138,3 +1034,2 @@ | ||
| class DoExecTestCase(subunit.ExecTestCase): | ||
| def test_working_script(self): | ||
@@ -1145,5 +1040,3 @@ """sample-two-script.py""" | ||
| class TestIsolatedTestCase(TestCase): | ||
| class SampleIsolatedTestCase(subunit.IsolatedTestCase): | ||
| SETUP = False | ||
@@ -1162,3 +1055,2 @@ TEARDOWN = False | ||
| def test_construct(self): | ||
@@ -1179,10 +1071,8 @@ self.SampleIsolatedTestCase("test_sets_global_state") | ||
| pass | ||
| #test = self.SampleExecTestCase("test_sample_method") | ||
| #test.debug() | ||
| # test = self.SampleExecTestCase("test_sample_method") | ||
| # test.debug() | ||
| class TestIsolatedTestSuite(TestCase): | ||
| class SampleTestToIsolate(unittest.TestCase): | ||
| SETUP = False | ||
@@ -1201,3 +1091,2 @@ TEARDOWN = False | ||
| def test_construct(self): | ||
@@ -1228,3 +1117,2 @@ subunit.IsolatedTestSuite() | ||
| class TestTestProtocolClient(TestCase): | ||
| def setUp(self): | ||
@@ -1234,9 +1122,7 @@ super().setUp() | ||
| self.protocol = subunit.TestProtocolClient(self.io) | ||
| self.unicode_test = PlaceHolder(_u('\u2603')) | ||
| self.unicode_test = PlaceHolder("\u2603") | ||
| self.test = TestTestProtocolClient("test_start_test") | ||
| self.sample_details = {'something':Content( | ||
| ContentType('text', 'plain'), lambda:[_b('serialised\nform')])} | ||
| self.sample_details = {"something": Content(ContentType("text", "plain"), lambda: [b"serialised\nform"])} | ||
| self.sample_tb_details = dict(self.sample_details) | ||
| self.sample_tb_details['traceback'] = TracebackContent( | ||
| subunit.RemoteError(_u("boo qux")), self.test) | ||
| self.sample_tb_details["traceback"] = TracebackContent(subunit.RemoteError("boo qux"), self.test) | ||
@@ -1246,3 +1132,3 @@ def test_start_test(self): | ||
| self.protocol.startTest(self.test) | ||
| self.assertEqual(self.io.getvalue(), _b("test: %s\n" % self.test.id())) | ||
| self.assertEqual(self.io.getvalue(), ("test: %s\n" % self.test.id()).encode()) | ||
@@ -1252,3 +1138,3 @@ def test_start_test_unicode_id(self): | ||
| self.protocol.startTest(self.unicode_test) | ||
| expected = _b("test: ") + _u('\u2603').encode('utf8') + _b("\n") | ||
| expected = b"test: " + "\u2603".encode("utf8") + b"\n" | ||
| self.assertEqual(expected, self.io.getvalue()) | ||
@@ -1259,3 +1145,3 @@ | ||
| self.protocol.stopTest(self.test) | ||
| self.assertEqual(self.io.getvalue(), _b("")) | ||
| self.assertEqual(self.io.getvalue(), b"") | ||
@@ -1265,4 +1151,3 @@ def test_add_success(self): | ||
| self.protocol.addSuccess(self.test) | ||
| self.assertEqual( | ||
| self.io.getvalue(), _b("successful: %s\n" % self.test.id())) | ||
| self.assertEqual(self.io.getvalue(), ("successful: %s\n" % self.test.id()).encode()) | ||
@@ -1272,3 +1157,3 @@ def test_add_outcome_unicode_id(self): | ||
| self.protocol.addSuccess(self.unicode_test) | ||
| expected = _b("successful: ") + _u('\u2603').encode('utf8') + _b("\n") | ||
| expected = b"successful: " + "\u2603".encode("utf8") + b"\n" | ||
| self.assertEqual(expected, self.io.getvalue()) | ||
@@ -1280,175 +1165,203 @@ | ||
| self.assertEqual( | ||
| self.io.getvalue(), _b("successful: %s [ multipart\n" | ||
| self.io.getvalue(), | ||
| ( | ||
| "successful: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "something\n" | ||
| "F\r\nserialised\nform0\r\n]\n" % self.test.id())) | ||
| "F\r\nserialised\nform0\r\n]\n" % self.test.id() | ||
| ).encode(), | ||
| ) | ||
| def test_add_failure(self): | ||
| """Test addFailure on a TestProtocolClient.""" | ||
| self.protocol.addFailure( | ||
| self.test, subunit.RemoteError(_u("boo qux"))) | ||
| self.assertThat(self.io.getvalue(), MatchesAny( | ||
| # testtools < 2.5.0 | ||
| Equals(_b(( | ||
| 'failure: %s [\n' + | ||
| _remote_exception_str + ': boo qux\n' + | ||
| ']\n') % self.test.id())), | ||
| # testtools >= 2.5.0 | ||
| Equals(_b(( | ||
| 'failure: %s [\n' + | ||
| _remote_exception_repr + ': boo qux\n' + | ||
| ']\n') % self.test.id())))) | ||
| self.protocol.addFailure(self.test, subunit.RemoteError("boo qux")) | ||
| self.assertThat( | ||
| self.io.getvalue(), | ||
| MatchesAny( | ||
| # testtools < 2.5.0 | ||
| Equals((("failure: %s [\n" + _remote_exception_str + ": boo qux\n" + "]\n") % self.test.id()).encode()), | ||
| # testtools >= 2.5.0 | ||
| Equals( | ||
| (("failure: %s [\n" + _remote_exception_repr + ": boo qux\n" + "]\n") % self.test.id()).encode() | ||
| ), | ||
| ), | ||
| ) | ||
| def test_add_failure_details(self): | ||
| """Test addFailure on a TestProtocolClient with details.""" | ||
| self.protocol.addFailure( | ||
| self.test, details=self.sample_tb_details) | ||
| self.assertThat(self.io.getvalue(), MatchesAny( | ||
| # testtools < 2.5.0 | ||
| Equals(_b(( | ||
| "failure: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "something\n" | ||
| "F\r\nserialised\nform0\r\n" | ||
| "Content-Type: text/x-traceback;charset=utf8,language=python\n" | ||
| "traceback\n" + _remote_exception_str_chunked + | ||
| "]\n") % self.test.id())), | ||
| # testtools >= 2.5.0 | ||
| Equals(_b(( | ||
| "failure: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "something\n" | ||
| "F\r\nserialised\nform0\r\n" | ||
| "Content-Type: text/x-traceback;charset=utf8,language=python\n" | ||
| "traceback\n" + _remote_exception_repr_chunked + | ||
| "]\n") % self.test.id())))) | ||
| self.protocol.addFailure(self.test, details=self.sample_tb_details) | ||
| self.assertThat( | ||
| self.io.getvalue(), | ||
| MatchesAny( | ||
| # testtools < 2.5.0 | ||
| Equals( | ||
| ( | ||
| ( | ||
| "failure: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "something\n" | ||
| "F\r\nserialised\nform0\r\n" | ||
| "Content-Type: text/x-traceback;charset=utf8,language=python\n" | ||
| "traceback\n" + _remote_exception_str_chunked + "]\n" | ||
| ) | ||
| % self.test.id() | ||
| ).encode() | ||
| ), | ||
| # testtools >= 2.5.0 | ||
| Equals( | ||
| ( | ||
| ( | ||
| "failure: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "something\n" | ||
| "F\r\nserialised\nform0\r\n" | ||
| "Content-Type: text/x-traceback;charset=utf8,language=python\n" | ||
| "traceback\n" + _remote_exception_repr_chunked + "]\n" | ||
| ) | ||
| % self.test.id() | ||
| ).encode() | ||
| ), | ||
| ), | ||
| ) | ||
| def test_add_error(self): | ||
| """Test stopTest on a TestProtocolClient.""" | ||
| self.protocol.addError( | ||
| self.test, subunit.RemoteError(_u("phwoar crikey"))) | ||
| self.assertThat(self.io.getvalue(), MatchesAny( | ||
| # testtools < 2.5.0 | ||
| Equals(_b(( | ||
| 'error: %s [\n' + | ||
| _remote_exception_str + ": phwoar crikey\n" | ||
| "]\n") % self.test.id())), | ||
| # testtools >= 2.5.0 | ||
| Equals(_b(( | ||
| 'error: %s [\n' + | ||
| _remote_exception_repr + ": phwoar crikey\n" | ||
| "]\n") % self.test.id())))) | ||
| self.protocol.addError(self.test, subunit.RemoteError("phwoar crikey")) | ||
| self.assertThat( | ||
| self.io.getvalue(), | ||
| MatchesAny( | ||
| # testtools < 2.5.0 | ||
| Equals((("error: %s [\n" + _remote_exception_str + ": phwoar crikey\n]\n") % self.test.id()).encode()), | ||
| # testtools >= 2.5.0 | ||
| Equals((("error: %s [\n" + _remote_exception_repr + ": phwoar crikey\n]\n") % self.test.id()).encode()), | ||
| ), | ||
| ) | ||
| def test_add_error_details(self): | ||
| """Test stopTest on a TestProtocolClient with details.""" | ||
| self.protocol.addError( | ||
| self.test, details=self.sample_tb_details) | ||
| self.assertThat(self.io.getvalue(), MatchesAny( | ||
| # testtools < 2.5.0 | ||
| Equals(_b(( | ||
| "error: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "something\n" | ||
| "F\r\nserialised\nform0\r\n" | ||
| "Content-Type: text/x-traceback;charset=utf8,language=python\n" | ||
| "traceback\n" + _remote_exception_str_chunked + | ||
| "]\n") % self.test.id())), | ||
| # testtools >= 2.5.0 | ||
| Equals(_b(( | ||
| "error: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "something\n" | ||
| "F\r\nserialised\nform0\r\n" | ||
| "Content-Type: text/x-traceback;charset=utf8,language=python\n" | ||
| "traceback\n" + _remote_exception_repr_chunked + | ||
| "]\n") % self.test.id())))) | ||
| self.protocol.addError(self.test, details=self.sample_tb_details) | ||
| self.assertThat( | ||
| self.io.getvalue(), | ||
| MatchesAny( | ||
| # testtools < 2.5.0 | ||
| Equals( | ||
| ( | ||
| ( | ||
| "error: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "something\n" | ||
| "F\r\nserialised\nform0\r\n" | ||
| "Content-Type: text/x-traceback;charset=utf8,language=python\n" | ||
| "traceback\n" + _remote_exception_str_chunked + "]\n" | ||
| ) | ||
| % self.test.id() | ||
| ).encode() | ||
| ), | ||
| # testtools >= 2.5.0 | ||
| Equals( | ||
| ( | ||
| ( | ||
| "error: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "something\n" | ||
| "F\r\nserialised\nform0\r\n" | ||
| "Content-Type: text/x-traceback;charset=utf8,language=python\n" | ||
| "traceback\n" + _remote_exception_repr_chunked + "]\n" | ||
| ) | ||
| % self.test.id() | ||
| ).encode() | ||
| ), | ||
| ), | ||
| ) | ||
| def test_add_expected_failure(self): | ||
| """Test addExpectedFailure on a TestProtocolClient.""" | ||
| self.protocol.addExpectedFailure( | ||
| self.test, subunit.RemoteError(_u("phwoar crikey"))) | ||
| self.assertThat(self.io.getvalue(), MatchesAny( | ||
| # testtools < 2.5.0 | ||
| Equals(_b(( | ||
| 'xfail: %s [\n' + | ||
| _remote_exception_str + ": phwoar crikey\n" | ||
| "]\n") % self.test.id())), | ||
| # testtools >= 2.5.0 | ||
| Equals(_b(( | ||
| 'xfail: %s [\n' + | ||
| _remote_exception_repr + ": phwoar crikey\n" | ||
| "]\n") % self.test.id())))) | ||
| self.protocol.addExpectedFailure(self.test, subunit.RemoteError("phwoar crikey")) | ||
| self.assertThat( | ||
| self.io.getvalue(), | ||
| MatchesAny( | ||
| # testtools < 2.5.0 | ||
| Equals((("xfail: %s [\n" + _remote_exception_str + ": phwoar crikey\n]\n") % self.test.id()).encode()), | ||
| # testtools >= 2.5.0 | ||
| Equals((("xfail: %s [\n" + _remote_exception_repr + ": phwoar crikey\n]\n") % self.test.id()).encode()), | ||
| ), | ||
| ) | ||
| def test_add_expected_failure_details(self): | ||
| """Test addExpectedFailure on a TestProtocolClient with details.""" | ||
| self.protocol.addExpectedFailure( | ||
| self.test, details=self.sample_tb_details) | ||
| self.assertThat(self.io.getvalue(), MatchesAny( | ||
| # testtools < 2.5.0 | ||
| Equals(_b(( | ||
| "xfail: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "something\n" | ||
| "F\r\nserialised\nform0\r\n" | ||
| "Content-Type: text/x-traceback;charset=utf8,language=python\n" | ||
| "traceback\n" + _remote_exception_str_chunked + | ||
| "]\n") % self.test.id())), | ||
| # testtools >= 2.5.0 | ||
| Equals(_b(( | ||
| "xfail: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "something\n" | ||
| "F\r\nserialised\nform0\r\n" | ||
| "Content-Type: text/x-traceback;charset=utf8,language=python\n" | ||
| "traceback\n" + _remote_exception_repr_chunked + | ||
| "]\n") % self.test.id())))) | ||
| self.protocol.addExpectedFailure(self.test, details=self.sample_tb_details) | ||
| self.assertThat( | ||
| self.io.getvalue(), | ||
| MatchesAny( | ||
| # testtools < 2.5.0 | ||
| Equals( | ||
| ( | ||
| ( | ||
| "xfail: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "something\n" | ||
| "F\r\nserialised\nform0\r\n" | ||
| "Content-Type: text/x-traceback;charset=utf8,language=python\n" | ||
| "traceback\n" + _remote_exception_str_chunked + "]\n" | ||
| ) | ||
| % self.test.id() | ||
| ).encode() | ||
| ), | ||
| # testtools >= 2.5.0 | ||
| Equals( | ||
| ( | ||
| ( | ||
| "xfail: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "something\n" | ||
| "F\r\nserialised\nform0\r\n" | ||
| "Content-Type: text/x-traceback;charset=utf8,language=python\n" | ||
| "traceback\n" + _remote_exception_repr_chunked + "]\n" | ||
| ) | ||
| % self.test.id() | ||
| ).encode() | ||
| ), | ||
| ), | ||
| ) | ||
| def test_add_skip(self): | ||
| """Test addSkip on a TestProtocolClient.""" | ||
| self.protocol.addSkip( | ||
| self.test, "Has it really?") | ||
| self.assertEqual( | ||
| self.io.getvalue(), | ||
| _b('skip: %s [\nHas it really?\n]\n' % self.test.id())) | ||
| self.protocol.addSkip(self.test, "Has it really?") | ||
| self.assertEqual(self.io.getvalue(), ("skip: %s [\nHas it really?\n]\n" % self.test.id()).encode()) | ||
| def test_add_skip_details(self): | ||
| """Test addSkip on a TestProtocolClient with details.""" | ||
| details = {'reason':Content( | ||
| ContentType('text', 'plain'), lambda:[_b('Has it really?')])} | ||
| details = {"reason": Content(ContentType("text", "plain"), lambda: [b"Has it really?"])} | ||
| self.protocol.addSkip(self.test, details=details) | ||
| self.assertEqual( | ||
| self.io.getvalue(), | ||
| _b("skip: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "reason\n" | ||
| "E\r\nHas it really?0\r\n" | ||
| "]\n" % self.test.id())) | ||
| ( | ||
| "skip: %s [ multipart\nContent-Type: text/plain\nreason\nE\r\nHas it really?0\r\n]\n" % self.test.id() | ||
| ).encode(), | ||
| ) | ||
| def test_progress_set(self): | ||
| self.protocol.progress(23, subunit.PROGRESS_SET) | ||
| self.assertEqual(self.io.getvalue(), _b('progress: 23\n')) | ||
| self.assertEqual(self.io.getvalue(), b"progress: 23\n") | ||
| def test_progress_neg_cur(self): | ||
| self.protocol.progress(-23, subunit.PROGRESS_CUR) | ||
| self.assertEqual(self.io.getvalue(), _b('progress: -23\n')) | ||
| self.assertEqual(self.io.getvalue(), b"progress: -23\n") | ||
| def test_progress_pos_cur(self): | ||
| self.protocol.progress(23, subunit.PROGRESS_CUR) | ||
| self.assertEqual(self.io.getvalue(), _b('progress: +23\n')) | ||
| self.assertEqual(self.io.getvalue(), b"progress: +23\n") | ||
| def test_progress_pop(self): | ||
| self.protocol.progress(1234, subunit.PROGRESS_POP) | ||
| self.assertEqual(self.io.getvalue(), _b('progress: pop\n')) | ||
| self.assertEqual(self.io.getvalue(), b"progress: pop\n") | ||
| def test_progress_push(self): | ||
| self.protocol.progress(1234, subunit.PROGRESS_PUSH) | ||
| self.assertEqual(self.io.getvalue(), _b('progress: push\n')) | ||
| self.assertEqual(self.io.getvalue(), b"progress: push\n") | ||
| def test_time(self): | ||
| # Calling time() outputs a time signal immediately. | ||
| self.protocol.time( | ||
| datetime.datetime(2009,10,11,12,13,14,15, iso8601.UTC)) | ||
| self.assertEqual( | ||
| _b("time: 2009-10-11 12:13:14.000015Z\n"), | ||
| self.io.getvalue()) | ||
| self.protocol.time(datetime.datetime(2009, 10, 11, 12, 13, 14, 15, iso8601.UTC)) | ||
| self.assertEqual(b"time: 2009-10-11 12:13:14.000015Z\n", self.io.getvalue()) | ||
@@ -1458,4 +1371,3 @@ def test_add_unexpected_success(self): | ||
| self.protocol.addUnexpectedSuccess(self.test) | ||
| self.assertEqual( | ||
| self.io.getvalue(), _b("uxsuccess: %s\n" % self.test.id())) | ||
| self.assertEqual(self.io.getvalue(), ("uxsuccess: %s\n" % self.test.id()).encode()) | ||
@@ -1466,23 +1378,25 @@ def test_add_unexpected_success_details(self): | ||
| self.assertEqual( | ||
| self.io.getvalue(), _b("uxsuccess: %s [ multipart\n" | ||
| self.io.getvalue(), | ||
| ( | ||
| "uxsuccess: %s [ multipart\n" | ||
| "Content-Type: text/plain\n" | ||
| "something\n" | ||
| "F\r\nserialised\nform0\r\n]\n" % self.test.id())) | ||
| "F\r\nserialised\nform0\r\n]\n" % self.test.id() | ||
| ).encode(), | ||
| ) | ||
| def test_tags_empty(self): | ||
| self.protocol.tags(set(), set()) | ||
| self.assertEqual(_b(""), self.io.getvalue()) | ||
| self.assertEqual(b"", self.io.getvalue()) | ||
| def test_tags_add(self): | ||
| self.protocol.tags({'foo'}, set()) | ||
| self.assertEqual(_b("tags: foo\n"), self.io.getvalue()) | ||
| self.protocol.tags({"foo"}, set()) | ||
| self.assertEqual(b"tags: foo\n", self.io.getvalue()) | ||
| def test_tags_both(self): | ||
| self.protocol.tags({'quux'}, {'bar'}) | ||
| self.assertThat( | ||
| [b"tags: quux -bar\n", b"tags: -bar quux\n"], | ||
| Contains(self.io.getvalue())) | ||
| self.protocol.tags({"quux"}, {"bar"}) | ||
| self.assertThat([b"tags: quux -bar\n", b"tags: -bar quux\n"], Contains(self.io.getvalue())) | ||
| def test_tags_gone(self): | ||
| self.protocol.tags(set(), {'bar'}) | ||
| self.assertEqual(_b("tags: -bar\n"), self.io.getvalue()) | ||
| self.protocol.tags(set(), {"bar"}) | ||
| self.assertEqual(b"tags: -bar\n", self.io.getvalue()) |
@@ -22,2 +22,3 @@ # | ||
| from hypothesis import given | ||
| # To debug hypothesis | ||
@@ -38,19 +39,19 @@ # from hypothesis import Settings, Verbosity | ||
| CONSTANT_ENUM = b'\xb3)\x01\x0c\x03foo\x08U_\x1b' | ||
| CONSTANT_INPROGRESS = b'\xb3)\x02\x0c\x03foo\x8e\xc1-\xb5' | ||
| CONSTANT_SUCCESS = b'\xb3)\x03\x0c\x03fooE\x9d\xfe\x10' | ||
| CONSTANT_UXSUCCESS = b'\xb3)\x04\x0c\x03fooX\x98\xce\xa8' | ||
| CONSTANT_SKIP = b'\xb3)\x05\x0c\x03foo\x93\xc4\x1d\r' | ||
| CONSTANT_FAIL = b'\xb3)\x06\x0c\x03foo\x15Po\xa3' | ||
| CONSTANT_XFAIL = b'\xb3)\x07\x0c\x03foo\xde\x0c\xbc\x06' | ||
| CONSTANT_EOF = b'\xb3!\x10\x08S\x15\x88\xdc' | ||
| CONSTANT_FILE_CONTENT = b'\xb3!@\x13\x06barney\x03wooA5\xe3\x8c' | ||
| CONSTANT_MIME = b'\xb3! #\x1aapplication/foo; charset=1x3Q\x15' | ||
| CONSTANT_TIMESTAMP = b'\xb3+\x03\x13<\x17T\xcf\x80\xaf\xc8\x03barI\x96>-' | ||
| CONSTANT_ROUTE_CODE = b'\xb3-\x03\x13\x03bar\x06source\x9cY9\x19' | ||
| CONSTANT_RUNNABLE = b'\xb3(\x03\x0c\x03foo\xe3\xea\xf5\xa4' | ||
| CONSTANT_ENUM = b"\xb3)\x01\x0c\x03foo\x08U_\x1b" | ||
| CONSTANT_INPROGRESS = b"\xb3)\x02\x0c\x03foo\x8e\xc1-\xb5" | ||
| CONSTANT_SUCCESS = b"\xb3)\x03\x0c\x03fooE\x9d\xfe\x10" | ||
| CONSTANT_UXSUCCESS = b"\xb3)\x04\x0c\x03fooX\x98\xce\xa8" | ||
| CONSTANT_SKIP = b"\xb3)\x05\x0c\x03foo\x93\xc4\x1d\r" | ||
| CONSTANT_FAIL = b"\xb3)\x06\x0c\x03foo\x15Po\xa3" | ||
| CONSTANT_XFAIL = b"\xb3)\x07\x0c\x03foo\xde\x0c\xbc\x06" | ||
| CONSTANT_EOF = b"\xb3!\x10\x08S\x15\x88\xdc" | ||
| CONSTANT_FILE_CONTENT = b"\xb3!@\x13\x06barney\x03wooA5\xe3\x8c" | ||
| CONSTANT_MIME = b"\xb3! #\x1aapplication/foo; charset=1x3Q\x15" | ||
| CONSTANT_TIMESTAMP = b"\xb3+\x03\x13<\x17T\xcf\x80\xaf\xc8\x03barI\x96>-" | ||
| CONSTANT_ROUTE_CODE = b"\xb3-\x03\x13\x03bar\x06source\x9cY9\x19" | ||
| CONSTANT_RUNNABLE = b"\xb3(\x03\x0c\x03foo\xe3\xea\xf5\xa4" | ||
| CONSTANT_TAGS = [ | ||
| b'\xb3)\x80\x15\x03bar\x02\x03foo\x03barTHn\xb4', | ||
| b'\xb3)\x80\x15\x03bar\x02\x03bar\x03foo\xf8\xf1\x91o', | ||
| ] | ||
| b"\xb3)\x80\x15\x03bar\x02\x03foo\x03barTHn\xb4", | ||
| b"\xb3)\x80\x15\x03bar\x02\x03bar\x03foo\xf8\xf1\x91o", | ||
| ] | ||
@@ -66,3 +67,2 @@ | ||
| class TestStreamResultToBytes(TestCase): | ||
| def _make_result(self): | ||
@@ -78,24 +78,24 @@ output = BytesIO() | ||
| result._write_number(0, packet) | ||
| self.assertEqual([b'\x00'], packet) | ||
| self.assertEqual([b"\x00"], packet) | ||
| del packet[:] | ||
| result._write_number(63, packet) | ||
| self.assertEqual([b'\x3f'], packet) | ||
| self.assertEqual([b"\x3f"], packet) | ||
| del packet[:] | ||
| result._write_number(64, packet) | ||
| self.assertEqual([b'\x40\x40'], packet) | ||
| self.assertEqual([b"\x40\x40"], packet) | ||
| del packet[:] | ||
| result._write_number(16383, packet) | ||
| self.assertEqual([b'\x7f\xff'], packet) | ||
| self.assertEqual([b"\x7f\xff"], packet) | ||
| del packet[:] | ||
| result._write_number(16384, packet) | ||
| self.assertEqual([b'\x80\x40', b'\x00'], packet) | ||
| self.assertEqual([b"\x80\x40", b"\x00"], packet) | ||
| del packet[:] | ||
| result._write_number(4194303, packet) | ||
| self.assertEqual([b'\xbf\xff', b'\xff'], packet) | ||
| self.assertEqual([b"\xbf\xff", b"\xff"], packet) | ||
| del packet[:] | ||
| result._write_number(4194304, packet) | ||
| self.assertEqual([b'\xc0\x40\x00\x00'], packet) | ||
| self.assertEqual([b"\xc0\x40\x00\x00"], packet) | ||
| del packet[:] | ||
| result._write_number(1073741823, packet) | ||
| self.assertEqual([b'\xff\xff\xff\xff'], packet) | ||
| self.assertEqual([b"\xff\xff\xff\xff"], packet) | ||
| del packet[:] | ||
@@ -114,43 +114,42 @@ self.assertRaises(Exception, result._write_number, 1073741824, packet) | ||
| # 1 byte short: | ||
| result.status(file_name="", file_bytes=b'\xff'*0) | ||
| result.status(file_name="", file_bytes=b"\xff" * 0) | ||
| self.assertThat(output.getvalue(), HasLength(10)) | ||
| self.assertEqual(b'\x0a', output.getvalue()[3:4]) | ||
| self.assertEqual(b"\x0a", output.getvalue()[3:4]) | ||
| output.seek(0) | ||
| output.truncate() | ||
| # 1 byte long: | ||
| result.status(file_name="", file_bytes=b'\xff'*53) | ||
| result.status(file_name="", file_bytes=b"\xff" * 53) | ||
| self.assertThat(output.getvalue(), HasLength(63)) | ||
| self.assertEqual(b'\x3f', output.getvalue()[3:4]) | ||
| self.assertEqual(b"\x3f", output.getvalue()[3:4]) | ||
| output.seek(0) | ||
| output.truncate() | ||
| # 2 bytes short | ||
| result.status(file_name="", file_bytes=b'\xff'*54) | ||
| result.status(file_name="", file_bytes=b"\xff" * 54) | ||
| self.assertThat(output.getvalue(), HasLength(65)) | ||
| self.assertEqual(b'\x40\x41', output.getvalue()[3:5]) | ||
| self.assertEqual(b"\x40\x41", output.getvalue()[3:5]) | ||
| output.seek(0) | ||
| output.truncate() | ||
| # 2 bytes long | ||
| result.status(file_name="", file_bytes=b'\xff'*16371) | ||
| result.status(file_name="", file_bytes=b"\xff" * 16371) | ||
| self.assertThat(output.getvalue(), HasLength(16383)) | ||
| self.assertEqual(b'\x7f\xff', output.getvalue()[3:5]) | ||
| self.assertEqual(b"\x7f\xff", output.getvalue()[3:5]) | ||
| output.seek(0) | ||
| output.truncate() | ||
| # 3 bytes short | ||
| result.status(file_name="", file_bytes=b'\xff'*16372) | ||
| result.status(file_name="", file_bytes=b"\xff" * 16372) | ||
| self.assertThat(output.getvalue(), HasLength(16385)) | ||
| self.assertEqual(b'\x80\x40\x01', output.getvalue()[3:6]) | ||
| self.assertEqual(b"\x80\x40\x01", output.getvalue()[3:6]) | ||
| output.seek(0) | ||
| output.truncate() | ||
| # 3 bytes long | ||
| result.status(file_name="", file_bytes=b'\xff'*4194289) | ||
| result.status(file_name="", file_bytes=b"\xff" * 4194289) | ||
| self.assertThat(output.getvalue(), HasLength(4194303)) | ||
| self.assertEqual(b'\xbf\xff\xff', output.getvalue()[3:6]) | ||
| self.assertEqual(b"\xbf\xff\xff", output.getvalue()[3:6]) | ||
| output.seek(0) | ||
| output.truncate() | ||
| self.assertRaises(Exception, result.status, file_name="", | ||
| file_bytes=b'\xff'*4194290) | ||
| self.assertRaises(Exception, result.status, file_name="", file_bytes=b"\xff" * 4194290) | ||
| def test_trivial_enumeration(self): | ||
| result, output = self._make_result() | ||
| result.status("foo", 'exists') | ||
| result.status("foo", "exists") | ||
| self.assertEqual(CONSTANT_ENUM, output.getvalue()) | ||
@@ -160,3 +159,3 @@ | ||
| result, output = self._make_result() | ||
| result.status("foo", 'inprogress') | ||
| result.status("foo", "inprogress") | ||
| self.assertEqual(CONSTANT_INPROGRESS, output.getvalue()) | ||
@@ -166,3 +165,3 @@ | ||
| result, output = self._make_result() | ||
| result.status("foo", 'success') | ||
| result.status("foo", "success") | ||
| self.assertEqual(CONSTANT_SUCCESS, output.getvalue()) | ||
@@ -172,3 +171,3 @@ | ||
| result, output = self._make_result() | ||
| result.status("foo", 'uxsuccess') | ||
| result.status("foo", "uxsuccess") | ||
| self.assertEqual(CONSTANT_UXSUCCESS, output.getvalue()) | ||
@@ -178,3 +177,3 @@ | ||
| result, output = self._make_result() | ||
| result.status("foo", 'skip') | ||
| result.status("foo", "skip") | ||
| self.assertEqual(CONSTANT_SKIP, output.getvalue()) | ||
@@ -184,3 +183,3 @@ | ||
| result, output = self._make_result() | ||
| result.status("foo", 'fail') | ||
| result.status("foo", "fail") | ||
| self.assertEqual(CONSTANT_FAIL, output.getvalue()) | ||
@@ -190,3 +189,3 @@ | ||
| result, output = self._make_result() | ||
| result.status("foo", 'xfail') | ||
| result.status("foo", "xfail") | ||
| self.assertEqual(CONSTANT_XFAIL, output.getvalue()) | ||
@@ -196,4 +195,4 @@ | ||
| result, output = self._make_result() | ||
| self.assertRaises(Exception, result.status, "foo", 'boo') | ||
| self.assertEqual(b'', output.getvalue()) | ||
| self.assertRaises(Exception, result.status, "foo", "boo") | ||
| self.assertEqual(b"", output.getvalue()) | ||
@@ -217,4 +216,3 @@ def test_eof(self): | ||
| result, output = self._make_result() | ||
| result.status(test_id="bar", test_status='success', | ||
| route_code="source") | ||
| result.status(test_id="bar", test_status="success", route_code="source") | ||
| self.assertEqual(CONSTANT_ROUTE_CODE, output.getvalue()) | ||
@@ -224,3 +222,3 @@ | ||
| result, output = self._make_result() | ||
| result.status("foo", 'success', runnable=False) | ||
| result.status("foo", "success", runnable=False) | ||
| self.assertEqual(CONSTANT_RUNNABLE, output.getvalue()) | ||
@@ -230,10 +228,9 @@ | ||
| result, output = self._make_result() | ||
| result.status(test_id="bar", test_tags={'foo', 'bar'}) | ||
| result.status(test_id="bar", test_tags={"foo", "bar"}) | ||
| self.assertThat(CONSTANT_TAGS, Contains(output.getvalue())) | ||
| def test_timestamp(self): | ||
| timestamp = datetime.datetime(2001, 12, 12, 12, 59, 59, 45, | ||
| iso8601.UTC) | ||
| timestamp = datetime.datetime(2001, 12, 12, 12, 59, 59, 45, iso8601.UTC) | ||
| result, output = self._make_result() | ||
| result.status(test_id="bar", test_status='success', timestamp=timestamp) | ||
| result.status(test_id="bar", test_status="success", timestamp=timestamp) | ||
| self.assertEqual(CONSTANT_TIMESTAMP, output.getvalue()) | ||
@@ -243,33 +240,35 @@ | ||
| class TestByteStreamToStreamResult(TestCase): | ||
| def test_non_subunit_encapsulated(self): | ||
| source = BytesIO(b"foo\nbar\n") | ||
| result = StreamResult() | ||
| subunit.ByteStreamToStreamResult( | ||
| source, non_subunit_name="stdout").run(result) | ||
| self.assertEqual([ | ||
| ('status', None, None, None, True, 'stdout', b'f', False, None, None, None), | ||
| ('status', None, None, None, True, 'stdout', b'o', False, None, None, None), | ||
| ('status', None, None, None, True, 'stdout', b'o', False, None, None, None), | ||
| ('status', None, None, None, True, 'stdout', b'\n', False, None, None, None), | ||
| ('status', None, None, None, True, 'stdout', b'b', False, None, None, None), | ||
| ('status', None, None, None, True, 'stdout', b'a', False, None, None, None), | ||
| ('status', None, None, None, True, 'stdout', b'r', False, None, None, None), | ||
| ('status', None, None, None, True, 'stdout', b'\n', False, None, None, None), | ||
| ], result._events) | ||
| self.assertEqual(b'', source.read()) | ||
| subunit.ByteStreamToStreamResult(source, non_subunit_name="stdout").run(result) | ||
| self.assertEqual( | ||
| [ | ||
| ("status", None, None, None, True, "stdout", b"f", False, None, None, None), | ||
| ("status", None, None, None, True, "stdout", b"o", False, None, None, None), | ||
| ("status", None, None, None, True, "stdout", b"o", False, None, None, None), | ||
| ("status", None, None, None, True, "stdout", b"\n", False, None, None, None), | ||
| ("status", None, None, None, True, "stdout", b"b", False, None, None, None), | ||
| ("status", None, None, None, True, "stdout", b"a", False, None, None, None), | ||
| ("status", None, None, None, True, "stdout", b"r", False, None, None, None), | ||
| ("status", None, None, None, True, "stdout", b"\n", False, None, None, None), | ||
| ], | ||
| result._events, | ||
| ) | ||
| self.assertEqual(b"", source.read()) | ||
| def test_signature_middle_utf8_char(self): | ||
| utf8_bytes = b'\xe3\xb3\x8a' | ||
| utf8_bytes = b"\xe3\xb3\x8a" | ||
| source = BytesIO(utf8_bytes) | ||
| # Should be treated as one character (it is u'\u3cca') and wrapped | ||
| result = StreamResult() | ||
| subunit.ByteStreamToStreamResult( | ||
| source, non_subunit_name="stdout").run( | ||
| result) | ||
| self.assertEqual([ | ||
| ('status', None, None, None, True, 'stdout', b'\xe3', False, None, None, None), | ||
| ('status', None, None, None, True, 'stdout', b'\xb3', False, None, None, None), | ||
| ('status', None, None, None, True, 'stdout', b'\x8a', False, None, None, None), | ||
| ], result._events) | ||
| subunit.ByteStreamToStreamResult(source, non_subunit_name="stdout").run(result) | ||
| self.assertEqual( | ||
| [ | ||
| ("status", None, None, None, True, "stdout", b"\xe3", False, None, None, None), | ||
| ("status", None, None, None, True, "stdout", b"\xb3", False, None, None, None), | ||
| ("status", None, None, None, True, "stdout", b"\x8a", False, None, None, None), | ||
| ], | ||
| result._events, | ||
| ) | ||
@@ -281,4 +280,4 @@ def test_non_subunit_disabled_raises(self): | ||
| e = self.assertRaises(Exception, case.run, result) | ||
| self.assertEqual(b'f', e.args[1]) | ||
| self.assertEqual(b'oo\nbar\n', source.read()) | ||
| self.assertEqual(b"f", e.args[1]) | ||
| self.assertEqual(b"oo\nbar\n", source.read()) | ||
| self.assertEqual([], result._events) | ||
@@ -289,8 +288,10 @@ | ||
| result = StreamResult() | ||
| subunit.ByteStreamToStreamResult( | ||
| source, non_subunit_name="stdout").run(result) | ||
| self.assertEqual(b'', source.read()) | ||
| self.assertEqual([ | ||
| ('status', 'foo', 'exists', None, True, None, None, False, None, None, None), | ||
| ], result._events) | ||
| subunit.ByteStreamToStreamResult(source, non_subunit_name="stdout").run(result) | ||
| self.assertEqual(b"", source.read()) | ||
| self.assertEqual( | ||
| [ | ||
| ("status", "foo", "exists", None, True, None, None, False, None, None, None), | ||
| ], | ||
| result._events, | ||
| ) | ||
@@ -300,27 +301,29 @@ def test_multiple_events(self): | ||
| result = StreamResult() | ||
| subunit.ByteStreamToStreamResult( | ||
| source, non_subunit_name="stdout").run(result) | ||
| self.assertEqual(b'', source.read()) | ||
| self.assertEqual([ | ||
| ('status', 'foo', 'exists', None, True, None, None, False, None, None, None), | ||
| ('status', 'foo', 'exists', None, True, None, None, False, None, None, None), | ||
| ], result._events) | ||
| subunit.ByteStreamToStreamResult(source, non_subunit_name="stdout").run(result) | ||
| self.assertEqual(b"", source.read()) | ||
| self.assertEqual( | ||
| [ | ||
| ("status", "foo", "exists", None, True, None, None, False, None, None, None), | ||
| ("status", "foo", "exists", None, True, None, None, False, None, None, None), | ||
| ], | ||
| result._events, | ||
| ) | ||
| def test_inprogress(self): | ||
| self.check_event(CONSTANT_INPROGRESS, 'inprogress') | ||
| self.check_event(CONSTANT_INPROGRESS, "inprogress") | ||
| def test_success(self): | ||
| self.check_event(CONSTANT_SUCCESS, 'success') | ||
| self.check_event(CONSTANT_SUCCESS, "success") | ||
| def test_uxsuccess(self): | ||
| self.check_event(CONSTANT_UXSUCCESS, 'uxsuccess') | ||
| self.check_event(CONSTANT_UXSUCCESS, "uxsuccess") | ||
| def test_skip(self): | ||
| self.check_event(CONSTANT_SKIP, 'skip') | ||
| self.check_event(CONSTANT_SKIP, "skip") | ||
| def test_fail(self): | ||
| self.check_event(CONSTANT_FAIL, 'fail') | ||
| self.check_event(CONSTANT_FAIL, "fail") | ||
| def test_xfail(self): | ||
| self.check_event(CONSTANT_XFAIL, 'xfail') | ||
| self.check_event(CONSTANT_XFAIL, "xfail") | ||
@@ -330,7 +333,6 @@ def check_events(self, source_bytes, events): | ||
| result = StreamResult() | ||
| subunit.ByteStreamToStreamResult( | ||
| source, non_subunit_name="stdout").run(result) | ||
| self.assertEqual(b'', source.read()) | ||
| subunit.ByteStreamToStreamResult(source, non_subunit_name="stdout").run(result) | ||
| self.assertEqual(b"", source.read()) | ||
| self.assertEqual(events, result._events) | ||
| #- any file attachments should be bytes equivalent [as users assume that]. | ||
| # - any file attachments should be bytes equivalent [as users assume that]. | ||
| for event in result._events: | ||
@@ -340,16 +342,56 @@ if event[5] is not None: | ||
| def check_event(self, source_bytes, test_status=None, test_id="foo", | ||
| route_code=None, timestamp=None, tags=None, mime_type=None, | ||
| file_name=None, file_bytes=None, eof=False, runnable=True): | ||
| event = self._event(test_id=test_id, test_status=test_status, | ||
| tags=tags, runnable=runnable, file_name=file_name, | ||
| file_bytes=file_bytes, eof=eof, mime_type=mime_type, | ||
| route_code=route_code, timestamp=timestamp) | ||
| def check_event( | ||
| self, | ||
| source_bytes, | ||
| test_status=None, | ||
| test_id="foo", | ||
| route_code=None, | ||
| timestamp=None, | ||
| tags=None, | ||
| mime_type=None, | ||
| file_name=None, | ||
| file_bytes=None, | ||
| eof=False, | ||
| runnable=True, | ||
| ): | ||
| event = self._event( | ||
| test_id=test_id, | ||
| test_status=test_status, | ||
| tags=tags, | ||
| runnable=runnable, | ||
| file_name=file_name, | ||
| file_bytes=file_bytes, | ||
| eof=eof, | ||
| mime_type=mime_type, | ||
| route_code=route_code, | ||
| timestamp=timestamp, | ||
| ) | ||
| self.check_events(source_bytes, [event]) | ||
| def _event(self, test_status=None, test_id=None, route_code=None, | ||
| timestamp=None, tags=None, mime_type=None, file_name=None, | ||
| file_bytes=None, eof=False, runnable=True): | ||
| return ('status', test_id, test_status, tags, runnable, file_name, | ||
| file_bytes, eof, mime_type, route_code, timestamp) | ||
| def _event( | ||
| self, | ||
| test_status=None, | ||
| test_id=None, | ||
| route_code=None, | ||
| timestamp=None, | ||
| tags=None, | ||
| mime_type=None, | ||
| file_name=None, | ||
| file_bytes=None, | ||
| eof=False, | ||
| runnable=True, | ||
| ): | ||
| return ( | ||
| "status", | ||
| test_id, | ||
| test_status, | ||
| tags, | ||
| runnable, | ||
| file_name, | ||
| file_bytes, | ||
| eof, | ||
| mime_type, | ||
| route_code, | ||
| timestamp, | ||
| ) | ||
@@ -360,101 +402,158 @@ def test_eof(self): | ||
| def test_file_content(self): | ||
| self.check_event(CONSTANT_FILE_CONTENT, | ||
| test_id=None, file_name="barney", file_bytes=b"woo") | ||
| self.check_event(CONSTANT_FILE_CONTENT, test_id=None, file_name="barney", file_bytes=b"woo") | ||
| def test_file_content_length_into_checksum(self): | ||
| # A bad file content length which creeps into the checksum. | ||
| bad_file_length_content = b'\xb3!@\x13\x06barney\x04woo\xdc\xe2\xdb\x35' | ||
| self.check_events(bad_file_length_content, [ | ||
| self._event(test_id="subunit.parser", eof=True, | ||
| file_name="Packet data", file_bytes=bad_file_length_content, | ||
| mime_type="application/octet-stream"), | ||
| self._event(test_id="subunit.parser", test_status="fail", eof=True, | ||
| file_name="Parser Error", | ||
| file_bytes=b"File content extends past end of packet: claimed 4 bytes, 3 available", | ||
| mime_type="text/plain;charset=utf8"), | ||
| ]) | ||
| bad_file_length_content = b"\xb3!@\x13\x06barney\x04woo\xdc\xe2\xdb\x35" | ||
| self.check_events( | ||
| bad_file_length_content, | ||
| [ | ||
| self._event( | ||
| test_id="subunit.parser", | ||
| eof=True, | ||
| file_name="Packet data", | ||
| file_bytes=bad_file_length_content, | ||
| mime_type="application/octet-stream", | ||
| ), | ||
| self._event( | ||
| test_id="subunit.parser", | ||
| test_status="fail", | ||
| eof=True, | ||
| file_name="Parser Error", | ||
| file_bytes=b"File content extends past end of packet: claimed 4 bytes, 3 available", | ||
| mime_type="text/plain;charset=utf8", | ||
| ), | ||
| ], | ||
| ) | ||
| def test_packet_length_4_word_varint(self): | ||
| packet_data = b'\xb3!@\xc0\x00\x11' | ||
| self.check_events(packet_data, [ | ||
| self._event(test_id="subunit.parser", eof=True, | ||
| file_name="Packet data", file_bytes=packet_data, | ||
| mime_type="application/octet-stream"), | ||
| self._event(test_id="subunit.parser", test_status="fail", eof=True, | ||
| file_name="Parser Error", | ||
| file_bytes=b"3 byte maximum given but 4 byte value found.", | ||
| mime_type="text/plain;charset=utf8"), | ||
| ]) | ||
| packet_data = b"\xb3!@\xc0\x00\x11" | ||
| self.check_events( | ||
| packet_data, | ||
| [ | ||
| self._event( | ||
| test_id="subunit.parser", | ||
| eof=True, | ||
| file_name="Packet data", | ||
| file_bytes=packet_data, | ||
| mime_type="application/octet-stream", | ||
| ), | ||
| self._event( | ||
| test_id="subunit.parser", | ||
| test_status="fail", | ||
| eof=True, | ||
| file_name="Parser Error", | ||
| file_bytes=b"3 byte maximum given but 4 byte value found.", | ||
| mime_type="text/plain;charset=utf8", | ||
| ), | ||
| ], | ||
| ) | ||
| def test_mime(self): | ||
| self.check_event(CONSTANT_MIME, | ||
| test_id=None, mime_type='application/foo; charset=1') | ||
| self.check_event(CONSTANT_MIME, test_id=None, mime_type="application/foo; charset=1") | ||
| def test_route_code(self): | ||
| self.check_event(CONSTANT_ROUTE_CODE, | ||
| 'success', route_code="source", test_id="bar") | ||
| self.check_event(CONSTANT_ROUTE_CODE, "success", route_code="source", test_id="bar") | ||
| def test_runnable(self): | ||
| self.check_event(CONSTANT_RUNNABLE, | ||
| test_status='success', runnable=False) | ||
| self.check_event(CONSTANT_RUNNABLE, test_status="success", runnable=False) | ||
| def test_tags(self): | ||
| self.check_event(CONSTANT_TAGS[0], | ||
| None, tags={'foo', 'bar'}, test_id="bar") | ||
| self.check_event(CONSTANT_TAGS[0], None, tags={"foo", "bar"}, test_id="bar") | ||
| def test_timestamp(self): | ||
| timestamp = datetime.datetime(2001, 12, 12, 12, 59, 59, 45, | ||
| iso8601.UTC) | ||
| self.check_event(CONSTANT_TIMESTAMP, | ||
| 'success', test_id='bar', timestamp=timestamp) | ||
| timestamp = datetime.datetime(2001, 12, 12, 12, 59, 59, 45, iso8601.UTC) | ||
| self.check_event(CONSTANT_TIMESTAMP, "success", test_id="bar", timestamp=timestamp) | ||
| def test_bad_crc_errors_via_status(self): | ||
| file_bytes = CONSTANT_MIME[:-1] + b'\x00' | ||
| self.check_events( file_bytes, [ | ||
| self._event(test_id="subunit.parser", eof=True, | ||
| file_name="Packet data", file_bytes=file_bytes, | ||
| mime_type="application/octet-stream"), | ||
| self._event(test_id="subunit.parser", test_status="fail", eof=True, | ||
| file_name="Parser Error", | ||
| file_bytes=b'Bad checksum - calculated (0x78335115), ' | ||
| b'stored (0x78335100)', | ||
| mime_type="text/plain;charset=utf8"), | ||
| ]) | ||
| file_bytes = CONSTANT_MIME[:-1] + b"\x00" | ||
| self.check_events( | ||
| file_bytes, | ||
| [ | ||
| self._event( | ||
| test_id="subunit.parser", | ||
| eof=True, | ||
| file_name="Packet data", | ||
| file_bytes=file_bytes, | ||
| mime_type="application/octet-stream", | ||
| ), | ||
| self._event( | ||
| test_id="subunit.parser", | ||
| test_status="fail", | ||
| eof=True, | ||
| file_name="Parser Error", | ||
| file_bytes=b"Bad checksum - calculated (0x78335115), stored (0x78335100)", | ||
| mime_type="text/plain;charset=utf8", | ||
| ), | ||
| ], | ||
| ) | ||
| def test_not_utf8_in_string(self): | ||
| file_bytes = CONSTANT_ROUTE_CODE[:5] + b'\xb4' + CONSTANT_ROUTE_CODE[6:-4] + b'\xce\x56\xc6\x17' | ||
| self.check_events(file_bytes, [ | ||
| self._event(test_id="subunit.parser", eof=True, | ||
| file_name="Packet data", file_bytes=file_bytes, | ||
| mime_type="application/octet-stream"), | ||
| self._event(test_id="subunit.parser", test_status="fail", eof=True, | ||
| file_name="Parser Error", | ||
| file_bytes=b'UTF8 string at offset 2 is not UTF8', | ||
| mime_type="text/plain;charset=utf8"), | ||
| ]) | ||
| file_bytes = CONSTANT_ROUTE_CODE[:5] + b"\xb4" + CONSTANT_ROUTE_CODE[6:-4] + b"\xce\x56\xc6\x17" | ||
| self.check_events( | ||
| file_bytes, | ||
| [ | ||
| self._event( | ||
| test_id="subunit.parser", | ||
| eof=True, | ||
| file_name="Packet data", | ||
| file_bytes=file_bytes, | ||
| mime_type="application/octet-stream", | ||
| ), | ||
| self._event( | ||
| test_id="subunit.parser", | ||
| test_status="fail", | ||
| eof=True, | ||
| file_name="Parser Error", | ||
| file_bytes=b"UTF8 string at offset 2 is not UTF8", | ||
| mime_type="text/plain;charset=utf8", | ||
| ), | ||
| ], | ||
| ) | ||
| def test_NULL_in_string(self): | ||
| file_bytes = CONSTANT_ROUTE_CODE[:6] + b'\x00' + CONSTANT_ROUTE_CODE[7:-4] + b'\xd7\x41\xac\xfe' | ||
| self.check_events(file_bytes, [ | ||
| self._event(test_id="subunit.parser", eof=True, | ||
| file_name="Packet data", file_bytes=file_bytes, | ||
| mime_type="application/octet-stream"), | ||
| self._event(test_id="subunit.parser", test_status="fail", eof=True, | ||
| file_name="Parser Error", | ||
| file_bytes=b'UTF8 string at offset 2 contains NUL byte', | ||
| mime_type="text/plain;charset=utf8"), | ||
| ]) | ||
| file_bytes = CONSTANT_ROUTE_CODE[:6] + b"\x00" + CONSTANT_ROUTE_CODE[7:-4] + b"\xd7\x41\xac\xfe" | ||
| self.check_events( | ||
| file_bytes, | ||
| [ | ||
| self._event( | ||
| test_id="subunit.parser", | ||
| eof=True, | ||
| file_name="Packet data", | ||
| file_bytes=file_bytes, | ||
| mime_type="application/octet-stream", | ||
| ), | ||
| self._event( | ||
| test_id="subunit.parser", | ||
| test_status="fail", | ||
| eof=True, | ||
| file_name="Parser Error", | ||
| file_bytes=b"UTF8 string at offset 2 contains NUL byte", | ||
| mime_type="text/plain;charset=utf8", | ||
| ), | ||
| ], | ||
| ) | ||
| def test_bad_utf8_stringlength(self): | ||
| file_bytes = CONSTANT_ROUTE_CODE[:4] + b'\x3f' + CONSTANT_ROUTE_CODE[5:-4] + b'\xbe\x29\xe0\xc2' | ||
| self.check_events(file_bytes, [ | ||
| self._event(test_id="subunit.parser", eof=True, | ||
| file_name="Packet data", file_bytes=file_bytes, | ||
| mime_type="application/octet-stream"), | ||
| self._event(test_id="subunit.parser", test_status="fail", eof=True, | ||
| file_name="Parser Error", | ||
| file_bytes=b'UTF8 string at offset 2 extends past end of ' | ||
| b'packet: claimed 63 bytes, 10 available', | ||
| mime_type="text/plain;charset=utf8"), | ||
| ]) | ||
| file_bytes = CONSTANT_ROUTE_CODE[:4] + b"\x3f" + CONSTANT_ROUTE_CODE[5:-4] + b"\xbe\x29\xe0\xc2" | ||
| self.check_events( | ||
| file_bytes, | ||
| [ | ||
| self._event( | ||
| test_id="subunit.parser", | ||
| eof=True, | ||
| file_name="Packet data", | ||
| file_bytes=file_bytes, | ||
| mime_type="application/octet-stream", | ||
| ), | ||
| self._event( | ||
| test_id="subunit.parser", | ||
| test_status="fail", | ||
| eof=True, | ||
| file_name="Parser Error", | ||
| file_bytes=b"UTF8 string at offset 2 extends past end of packet: claimed 63 bytes, 10 available", | ||
| mime_type="text/plain;charset=utf8", | ||
| ), | ||
| ], | ||
| ) | ||
@@ -464,8 +563,10 @@ def test_route_code_and_file_content(self): | ||
| subunit.StreamResultToBytes(content).status( | ||
| route_code='0', mime_type='text/plain', file_name='bar', | ||
| file_bytes=b'foo') | ||
| self.check_event(content.getvalue(), test_id=None, file_name='bar', | ||
| route_code='0', mime_type='text/plain', file_bytes=b'foo') | ||
| route_code="0", mime_type="text/plain", file_name="bar", file_bytes=b"foo" | ||
| ) | ||
| self.check_event( | ||
| content.getvalue(), test_id=None, file_name="bar", route_code="0", mime_type="text/plain", file_bytes=b"foo" | ||
| ) | ||
| if st is not None: | ||
| @given(st.binary()) | ||
@@ -475,5 +576,4 @@ def test_hypothesis_decoding(self, code_bytes): | ||
| result = StreamResult() | ||
| stream = subunit.ByteStreamToStreamResult( | ||
| source, non_subunit_name="stdout") | ||
| stream = subunit.ByteStreamToStreamResult(source, non_subunit_name="stdout") | ||
| stream.run(result) | ||
| self.assertEqual(b'', source.read()) | ||
| self.assertEqual(b"", source.read()) |
@@ -34,3 +34,2 @@ # | ||
| class LoggingDecorator(subunit.test_results.HookedTestResultDecorator): | ||
| def __init__(self, decorated): | ||
@@ -57,3 +56,2 @@ self._calls = 0 | ||
| class TimeCapturingResult(unittest.TestResult): | ||
| def __init__(self): | ||
@@ -67,5 +65,15 @@ super().__init__() | ||
| def progress(self, offset, whence): | ||
| pass | ||
| def addDuration(self, test, duration): | ||
| """Called to add a test duration. | ||
| :param test: The test that completed. | ||
| :param duration: The duration of the test as a float in seconds. | ||
| """ | ||
| pass | ||
| class TestHookedTestResultDecorator(unittest.TestCase): | ||
| def setUp(self): | ||
@@ -152,5 +160,7 @@ # An end to the chain | ||
| def test_addDuration(self): | ||
| self.result.addDuration(self, 1.5) | ||
| class TestAutoTimingTestResultDecorator(unittest.TestCase): | ||
| def setUp(self): | ||
@@ -160,4 +170,3 @@ # And end to the chain which captures time events. | ||
| # The result object under test. | ||
| self.result = subunit.test_results.AutoTimingTestResultDecorator( | ||
| terminal) | ||
| self.result = subunit.test_results.AutoTimingTestResultDecorator(terminal) | ||
| self.decorated = terminal | ||
@@ -182,3 +191,3 @@ | ||
| # automatically adding one when other methods are called. | ||
| time = datetime.datetime(2009,10,11,12,13,14,15, iso8601.UTC) | ||
| time = datetime.datetime(2009, 10, 11, 12, 13, 14, 15, iso8601.UTC) | ||
| self.result.time(time) | ||
@@ -191,3 +200,3 @@ self.result.startTest(self) | ||
| def test_calling_time_None_enables_automatic_time(self): | ||
| time = datetime.datetime(2009,10,11,12,13,14,15, iso8601.UTC) | ||
| time = datetime.datetime(2009, 10, 11, 12, 13, 14, 15, iso8601.UTC) | ||
| self.result.time(time) | ||
@@ -212,14 +221,14 @@ self.assertEqual(1, len(self.decorated._calls)) | ||
| class TestTagCollapsingDecorator(TestCase): | ||
| def test_tags_collapsed_outside_of_tests(self): | ||
| result = ExtendedTestResult() | ||
| tag_collapser = subunit.test_results.TagCollapsingDecorator(result) | ||
| tag_collapser.tags({'a'}, set()) | ||
| tag_collapser.tags({'b'}, set()) | ||
| tag_collapser.tags({"a"}, set()) | ||
| tag_collapser.tags({"b"}, set()) | ||
| tag_collapser.startTest(self) | ||
| self.assertEqual( | ||
| [('tags', {'a', 'b'}, set()), | ||
| ('startTest', self), | ||
| ], | ||
| result._events | ||
| [ | ||
| ("tags", {"a", "b"}, set()), | ||
| ("startTest", self), | ||
| ], | ||
| result._events, | ||
| ) | ||
@@ -231,4 +240,4 @@ | ||
| tag_collapser.startTestRun() | ||
| tag_collapser.tags({'a'}, set()) | ||
| tag_collapser.tags({'b'}, set()) | ||
| tag_collapser.tags({"a"}, set()) | ||
| tag_collapser.tags({"b"}, set()) | ||
| tag_collapser.startTest(self) | ||
@@ -239,14 +248,15 @@ tag_collapser.addSuccess(self) | ||
| self.assertEqual( | ||
| [('startTestRun',), | ||
| ('tags', {'a', 'b'}, set()), | ||
| ('startTest', self), | ||
| ('addSuccess', self), | ||
| ('stopTest', self), | ||
| ('stopTestRun',), | ||
| ], | ||
| result._events | ||
| [ | ||
| ("startTestRun",), | ||
| ("tags", {"a", "b"}, set()), | ||
| ("startTest", self), | ||
| ("addSuccess", self), | ||
| ("stopTest", self), | ||
| ("stopTestRun",), | ||
| ], | ||
| result._events, | ||
| ) | ||
| def test_tags_forwarded_after_tests(self): | ||
| test = subunit.RemotedTestCase('foo') | ||
| test = subunit.RemotedTestCase("foo") | ||
| result = ExtendedTestResult() | ||
@@ -258,13 +268,15 @@ tag_collapser = subunit.test_results.TagCollapsingDecorator(result) | ||
| tag_collapser.stopTest(test) | ||
| tag_collapser.tags({'a'}, {'b'}) | ||
| tag_collapser.tags({"a"}, {"b"}) | ||
| tag_collapser.stopTestRun() | ||
| self.assertEqual( | ||
| [('startTestRun',), | ||
| ('startTest', test), | ||
| ('addSuccess', test), | ||
| ('stopTest', test), | ||
| ('tags', {'a'}, {'b'}), | ||
| ('stopTestRun',), | ||
| ], | ||
| result._events) | ||
| [ | ||
| ("startTestRun",), | ||
| ("startTest", test), | ||
| ("addSuccess", test), | ||
| ("stopTest", test), | ||
| ("tags", {"a"}, {"b"}), | ||
| ("stopTestRun",), | ||
| ], | ||
| result._events, | ||
| ) | ||
@@ -274,14 +286,9 @@ def test_tags_collapsed_inside_of_tests(self): | ||
| tag_collapser = subunit.test_results.TagCollapsingDecorator(result) | ||
| test = subunit.RemotedTestCase('foo') | ||
| test = subunit.RemotedTestCase("foo") | ||
| tag_collapser.startTest(test) | ||
| tag_collapser.tags({'a'}, set()) | ||
| tag_collapser.tags({'b'}, {'a'}) | ||
| tag_collapser.tags({'c'}, set()) | ||
| tag_collapser.tags({"a"}, set()) | ||
| tag_collapser.tags({"b"}, {"a"}) | ||
| tag_collapser.tags({"c"}, set()) | ||
| tag_collapser.stopTest(test) | ||
| self.assertEqual( | ||
| [('startTest', test), | ||
| ('tags', {'b', 'c'}, {'a'}), | ||
| ('stopTest', test)], | ||
| result._events | ||
| ) | ||
| self.assertEqual([("startTest", test), ("tags", {"b", "c"}, {"a"}), ("stopTest", test)], result._events) | ||
@@ -291,14 +298,9 @@ def test_tags_collapsed_inside_of_tests_different_ordering(self): | ||
| tag_collapser = subunit.test_results.TagCollapsingDecorator(result) | ||
| test = subunit.RemotedTestCase('foo') | ||
| test = subunit.RemotedTestCase("foo") | ||
| tag_collapser.startTest(test) | ||
| tag_collapser.tags(set(), {'a'}) | ||
| tag_collapser.tags({'a', 'b'}, set()) | ||
| tag_collapser.tags({'c'}, set()) | ||
| tag_collapser.tags(set(), {"a"}) | ||
| tag_collapser.tags({"a", "b"}, set()) | ||
| tag_collapser.tags({"c"}, set()) | ||
| tag_collapser.stopTest(test) | ||
| self.assertEqual( | ||
| [('startTest', test), | ||
| ('tags', {'a', 'b', 'c'}, set()), | ||
| ('stopTest', test)], | ||
| result._events | ||
| ) | ||
| self.assertEqual([("startTest", test), ("tags", {"a", "b", "c"}, set()), ("stopTest", test)], result._events) | ||
@@ -312,13 +314,9 @@ def test_tags_sent_before_result(self): | ||
| tag_collapser = subunit.test_results.TagCollapsingDecorator(result) | ||
| test = subunit.RemotedTestCase('foo') | ||
| test = subunit.RemotedTestCase("foo") | ||
| tag_collapser.startTest(test) | ||
| tag_collapser.tags({'a'}, set()) | ||
| tag_collapser.tags({"a"}, set()) | ||
| tag_collapser.addSuccess(test) | ||
| tag_collapser.stopTest(test) | ||
| self.assertEqual( | ||
| [('startTest', test), | ||
| ('tags', {'a'}, set()), | ||
| ('addSuccess', test), | ||
| ('stopTest', test)], | ||
| result._events | ||
| [("startTest", test), ("tags", {"a"}, set()), ("addSuccess", test), ("stopTest", test)], result._events | ||
| ) | ||
@@ -328,7 +326,5 @@ | ||
| class TestTimeCollapsingDecorator(TestCase): | ||
| def make_time(self): | ||
| # Heh heh. | ||
| return datetime.datetime( | ||
| 2000, 1, self.getUniqueInteger(), tzinfo=iso8601.UTC) | ||
| return datetime.datetime(2000, 1, self.getUniqueInteger(), tzinfo=iso8601.UTC) | ||
@@ -341,3 +337,3 @@ def test_initial_time_forwarded(self): | ||
| tag_collapser.time(a_time) | ||
| self.assertEqual([('time', a_time)], result._events) | ||
| self.assertEqual([("time", a_time)], result._events) | ||
@@ -352,7 +348,4 @@ def test_time_collapsed_to_first_and_last(self): | ||
| tag_collapser.time(a_time) | ||
| tag_collapser.startTest(subunit.RemotedTestCase('foo')) | ||
| self.assertEqual( | ||
| [('time', times[0]), ('time', times[-1])], | ||
| result._events[:-1] | ||
| ) | ||
| tag_collapser.startTest(subunit.RemotedTestCase("foo")) | ||
| self.assertEqual([("time", times[0]), ("time", times[-1])], result._events[:-1]) | ||
@@ -366,4 +359,4 @@ def test_only_one_time_sent(self): | ||
| tag_collapser.time(a_time) | ||
| tag_collapser.startTest(subunit.RemotedTestCase('foo')) | ||
| self.assertEqual([('time', a_time)], result._events[:-1]) | ||
| tag_collapser.startTest(subunit.RemotedTestCase("foo")) | ||
| self.assertEqual([("time", a_time)], result._events[:-1]) | ||
@@ -378,4 +371,4 @@ def test_duplicate_times_not_sent(self): | ||
| tag_collapser.time(a_time) | ||
| tag_collapser.startTest(subunit.RemotedTestCase('foo')) | ||
| self.assertEqual([('time', a_time)], result._events[:-1]) | ||
| tag_collapser.startTest(subunit.RemotedTestCase("foo")) | ||
| self.assertEqual([("time", a_time)], result._events[:-1]) | ||
@@ -387,17 +380,10 @@ def test_no_times_inserted(self): | ||
| tag_collapser.time(a_time) | ||
| foo = subunit.RemotedTestCase('foo') | ||
| foo = subunit.RemotedTestCase("foo") | ||
| tag_collapser.startTest(foo) | ||
| tag_collapser.addSuccess(foo) | ||
| tag_collapser.stopTest(foo) | ||
| self.assertEqual( | ||
| [('time', a_time), | ||
| ('startTest', foo), | ||
| ('addSuccess', foo), | ||
| ('stopTest', foo)], | ||
| result._events | ||
| ) | ||
| self.assertEqual([("time", a_time), ("startTest", foo), ("addSuccess", foo), ("stopTest", foo)], result._events) | ||
| class TestByTestResultTests(testtools.TestCase): | ||
| def setUp(self): | ||
@@ -411,8 +397,8 @@ super().setUp() | ||
| defaults = { | ||
| 'test': self, | ||
| 'tags': set(), | ||
| 'details': None, | ||
| 'start_time': 0, | ||
| 'stop_time': 1, | ||
| } | ||
| "test": self, | ||
| "tags": set(), | ||
| "details": None, | ||
| "start_time": 0, | ||
| "stop_time": 1, | ||
| } | ||
| defaults.update(kwargs) | ||
@@ -433,19 +419,19 @@ self.assertEqual([defaults], self.log) | ||
| self.result.stopTest(self) | ||
| self.assertCalled(status='success') | ||
| self.assertCalled(status="success") | ||
| def test_add_success_details(self): | ||
| self.result.startTest(self) | ||
| details = {'foo': 'bar'} | ||
| details = {"foo": "bar"} | ||
| self.result.addSuccess(self, details=details) | ||
| self.result.stopTest(self) | ||
| self.assertCalled(status='success', details=details) | ||
| self.assertCalled(status="success", details=details) | ||
| def test_tags(self): | ||
| if not getattr(self.result, 'tags', None): | ||
| if not getattr(self.result, "tags", None): | ||
| self.skipTest("No tags in testtools") | ||
| self.result.tags(['foo'], []) | ||
| self.result.tags(["foo"], []) | ||
| self.result.startTest(self) | ||
| self.result.addSuccess(self) | ||
| self.result.stopTest(self) | ||
| self.assertCalled(status='success', tags={'foo'}) | ||
| self.assertCalled(status="success", tags={"foo"}) | ||
@@ -455,3 +441,3 @@ def test_add_error(self): | ||
| try: | ||
| 1/0 | ||
| 1 / 0 | ||
| except ZeroDivisionError: | ||
@@ -461,5 +447,3 @@ error = sys.exc_info() | ||
| self.result.stopTest(self) | ||
| self.assertCalled( | ||
| status='error', | ||
| details={'traceback': TracebackContent(error, self)}) | ||
| self.assertCalled(status="error", details={"traceback": TracebackContent(error, self)}) | ||
@@ -471,3 +455,3 @@ def test_add_error_details(self): | ||
| self.result.stopTest(self) | ||
| self.assertCalled(status='error', details=details) | ||
| self.assertCalled(status="error", details=details) | ||
@@ -482,5 +466,3 @@ def test_add_failure(self): | ||
| self.result.stopTest(self) | ||
| self.assertCalled( | ||
| status='failure', | ||
| details={'traceback': TracebackContent(failure, self)}) | ||
| self.assertCalled(status="failure", details={"traceback": TracebackContent(failure, self)}) | ||
@@ -492,3 +474,3 @@ def test_add_failure_details(self): | ||
| self.result.stopTest(self) | ||
| self.assertCalled(status='failure', details=details) | ||
| self.assertCalled(status="failure", details=details) | ||
@@ -498,3 +480,3 @@ def test_add_xfail(self): | ||
| try: | ||
| 1/0 | ||
| 1 / 0 | ||
| except ZeroDivisionError: | ||
@@ -504,5 +486,3 @@ error = sys.exc_info() | ||
| self.result.stopTest(self) | ||
| self.assertCalled( | ||
| status='xfail', | ||
| details={'traceback': TracebackContent(error, self)}) | ||
| self.assertCalled(status="xfail", details={"traceback": TracebackContent(error, self)}) | ||
@@ -514,10 +494,10 @@ def test_add_xfail_details(self): | ||
| self.result.stopTest(self) | ||
| self.assertCalled(status='xfail', details=details) | ||
| self.assertCalled(status="xfail", details=details) | ||
| def test_add_unexpected_success(self): | ||
| self.result.startTest(self) | ||
| details = {'foo': 'bar'} | ||
| details = {"foo": "bar"} | ||
| self.result.addUnexpectedSuccess(self, details=details) | ||
| self.result.stopTest(self) | ||
| self.assertCalled(status='success', details=details) | ||
| self.assertCalled(status="success", details=details) | ||
@@ -529,15 +509,14 @@ def test_add_skip_reason(self): | ||
| self.result.stopTest(self) | ||
| self.assertCalled( | ||
| status='skip', details={'reason': text_content(reason)}) | ||
| self.assertCalled(status="skip", details={"reason": text_content(reason)}) | ||
| def test_add_skip_details(self): | ||
| self.result.startTest(self) | ||
| details = {'foo': 'bar'} | ||
| details = {"foo": "bar"} | ||
| self.result.addSkip(self, details=details) | ||
| self.result.stopTest(self) | ||
| self.assertCalled(status='skip', details=details) | ||
| self.assertCalled(status="skip", details=details) | ||
| def test_twice(self): | ||
| self.result.startTest(self) | ||
| self.result.addSuccess(self, details={'foo': 'bar'}) | ||
| self.result.addSuccess(self, details={"foo": "bar"}) | ||
| self.result.stopTest(self) | ||
@@ -548,20 +527,18 @@ self.result.startTest(self) | ||
| self.assertEqual( | ||
| [{'test': self, | ||
| 'status': 'success', | ||
| 'start_time': 0, | ||
| 'stop_time': 1, | ||
| 'tags': set(), | ||
| 'details': {'foo': 'bar'}}, | ||
| {'test': self, | ||
| 'status': 'success', | ||
| 'start_time': 2, | ||
| 'stop_time': 3, | ||
| 'tags': set(), | ||
| 'details': None}, | ||
| ], | ||
| self.log) | ||
| [ | ||
| { | ||
| "test": self, | ||
| "status": "success", | ||
| "start_time": 0, | ||
| "stop_time": 1, | ||
| "tags": set(), | ||
| "details": {"foo": "bar"}, | ||
| }, | ||
| {"test": self, "status": "success", "start_time": 2, "stop_time": 3, "tags": set(), "details": None}, | ||
| ], | ||
| self.log, | ||
| ) | ||
| class TestCsvResult(testtools.TestCase): | ||
| def parse_stream(self, stream): | ||
@@ -582,6 +559,8 @@ stream.seek(0) | ||
| self.assertEqual( | ||
| [['test', 'status', 'start_time', 'stop_time'], | ||
| [self.id(), 'success', '0', '1'], | ||
| ], | ||
| self.parse_stream(stream)) | ||
| [ | ||
| ["test", "status", "start_time", "stop_time"], | ||
| [self.id(), "success", "0", "1"], | ||
| ], | ||
| self.parse_stream(stream), | ||
| ) | ||
@@ -593,5 +572,3 @@ def test_just_header_when_no_tests(self): | ||
| result.stopTestRun() | ||
| self.assertEqual( | ||
| [['test', 'status', 'start_time', 'stop_time']], | ||
| self.parse_stream(stream)) | ||
| self.assertEqual([["test", "status", "start_time", "stop_time"]], self.parse_stream(stream)) | ||
@@ -602,1 +579,15 @@ def test_no_output_before_events(self): | ||
| self.assertEqual([], self.parse_stream(stream)) | ||
| class TestTestIdPrintingResult(testtools.TestCase): | ||
| def setUp(self): | ||
| super().setUp() | ||
| self.stream = StringIO() | ||
| self.result = subunit.test_results.TestIdPrintingResult(self.stream) | ||
| def test_addDuration(self): | ||
| # addDuration should not raise an exception | ||
| test = subunit.RemotedTestCase("foo") | ||
| self.result.addDuration(test, 2.5) | ||
| # TestIdPrintingResult doesn't output anything for addDuration | ||
| self.assertEqual("", self.stream.getvalue()) |
+138
-105
@@ -17,3 +17,2 @@ # | ||
| import builtins | ||
| import codecs | ||
@@ -32,12 +31,12 @@ import datetime | ||
| __all__ = [ | ||
| 'ByteStreamToStreamResult', | ||
| 'StreamResultToBytes', | ||
| ] | ||
| "ByteStreamToStreamResult", | ||
| "StreamResultToBytes", | ||
| ] | ||
| SIGNATURE = b'\xb3' | ||
| FMT_8 = '>B' | ||
| FMT_16 = '>H' | ||
| FMT_24 = '>HB' | ||
| FMT_32 = '>I' | ||
| FMT_TIMESTAMP = '>II' | ||
| SIGNATURE = b"\xb3" | ||
| FMT_8 = ">B" | ||
| FMT_16 = ">H" | ||
| FMT_24 = ">HB" | ||
| FMT_32 = ">I" | ||
| FMT_TIMESTAMP = ">II" | ||
| FLAG_TEST_ID = 0x0800 | ||
@@ -52,3 +51,3 @@ FLAG_ROUTE_CODE = 0x0400 | ||
| EPOCH = datetime.datetime.fromtimestamp(0, tz=iso8601.UTC) | ||
| NUL_ELEMENT = b'\0'[0] | ||
| NUL_ELEMENT = b"\0"[0] | ||
| # Contains True for types for which 'nul in thing' falsely returns false. | ||
@@ -65,3 +64,3 @@ _nul_test_broken = {} | ||
| """ | ||
| data = b'' | ||
| data = b"" | ||
| remaining = size | ||
@@ -71,4 +70,3 @@ while remaining: | ||
| if len(read) == 0: | ||
| raise ParseError('Short read - got %d bytes, wanted %d bytes' % ( | ||
| len(data), size)) | ||
| raise ParseError("Short read - got %d bytes, wanted %d bytes" % (len(data), size)) | ||
| data += read | ||
@@ -91,12 +89,12 @@ remaining -= len(read) | ||
| None: 0, | ||
| 'exists': 0x1, | ||
| 'inprogress': 0x2, | ||
| 'success': 0x3, | ||
| 'uxsuccess': 0x4, | ||
| 'skip': 0x5, | ||
| 'fail': 0x6, | ||
| 'xfail': 0x7, | ||
| } | ||
| "exists": 0x1, | ||
| "inprogress": 0x2, | ||
| "success": 0x3, | ||
| "uxsuccess": 0x4, | ||
| "skip": 0x5, | ||
| "fail": 0x6, | ||
| "xfail": 0x7, | ||
| } | ||
| zero_b = b'\0'[0] | ||
| zero_b = b"\0"[0] | ||
@@ -119,12 +117,30 @@ def __init__(self, output_stream): | ||
| def status(self, test_id=None, test_status=None, test_tags=None, | ||
| runnable=True, file_name=None, file_bytes=None, eof=False, | ||
| mime_type=None, route_code=None, timestamp=None): | ||
| self._write_packet(test_id=test_id, test_status=test_status, | ||
| test_tags=test_tags, runnable=runnable, file_name=file_name, | ||
| file_bytes=file_bytes, eof=eof, mime_type=mime_type, | ||
| route_code=route_code, timestamp=timestamp) | ||
| def status( | ||
| self, | ||
| test_id=None, | ||
| test_status=None, | ||
| test_tags=None, | ||
| runnable=True, | ||
| file_name=None, | ||
| file_bytes=None, | ||
| eof=False, | ||
| mime_type=None, | ||
| route_code=None, | ||
| timestamp=None, | ||
| ): | ||
| self._write_packet( | ||
| test_id=test_id, | ||
| test_status=test_status, | ||
| test_tags=test_tags, | ||
| runnable=runnable, | ||
| file_name=file_name, | ||
| file_bytes=file_bytes, | ||
| eof=eof, | ||
| mime_type=mime_type, | ||
| route_code=route_code, | ||
| timestamp=timestamp, | ||
| ) | ||
| def _write_utf8(self, a_string, packet): | ||
| utf8 = a_string.encode('utf-8') | ||
| utf8 = a_string.encode("utf-8") | ||
| self._write_number(len(utf8), packet) | ||
@@ -149,18 +165,27 @@ packet.append(utf8) | ||
| value = value | 0x800000 | ||
| return [struct.pack(FMT_16, value >> 8), | ||
| struct.pack(FMT_8, value & 0xff)] | ||
| return [struct.pack(FMT_16, value >> 8), struct.pack(FMT_8, value & 0xFF)] | ||
| elif value < 1073741824: | ||
| value = value | 0xc0000000 | ||
| value = value | 0xC0000000 | ||
| return [struct.pack(FMT_32, value)] | ||
| else: | ||
| raise ValueError('value too large to encode: %r' % (value,)) | ||
| raise ValueError("value too large to encode: %r" % (value,)) | ||
| def _write_packet(self, test_id=None, test_status=None, test_tags=None, | ||
| runnable=True, file_name=None, file_bytes=None, eof=False, | ||
| mime_type=None, route_code=None, timestamp=None): | ||
| def _write_packet( | ||
| self, | ||
| test_id=None, | ||
| test_status=None, | ||
| test_tags=None, | ||
| runnable=True, | ||
| file_name=None, | ||
| file_bytes=None, | ||
| eof=False, | ||
| mime_type=None, | ||
| route_code=None, | ||
| timestamp=None, | ||
| ): | ||
| packet = [SIGNATURE] | ||
| packet.append(b'FF') # placeholder for flags | ||
| packet.append(b"FF") # placeholder for flags | ||
| # placeholder for length, but see below as length is variable. | ||
| packet.append(b'') | ||
| flags = 0x2000 # Version 0x2 | ||
| packet.append(b"") | ||
| flags = 0x2000 # Version 0x2 | ||
| if timestamp is not None: | ||
@@ -170,3 +195,3 @@ flags = flags | FLAG_TIMESTAMP | ||
| nanoseconds = since_epoch.microseconds * 1000 | ||
| seconds = (since_epoch.seconds + since_epoch.days * 24 * 3600) | ||
| seconds = since_epoch.seconds + since_epoch.days * 24 * 3600 | ||
| packet.append(struct.pack(FMT_32, seconds)) | ||
@@ -192,4 +217,4 @@ self._write_number(nanoseconds, packet) | ||
| packet.append(file_bytes) | ||
| if eof: | ||
| flags = flags | FLAG_EOF | ||
| if eof: | ||
| flags = flags | FLAG_EOF | ||
| if route_code is not None: | ||
@@ -223,4 +248,4 @@ flags = flags | FLAG_ROUTE_CODE | ||
| # For now, simplest code: join, crc32, join, output | ||
| content = b''.join(packet) | ||
| data = content + struct.pack(FMT_32, zlib.crc32(content) & 0xffffffff) | ||
| content = b"".join(packet) | ||
| data = content + struct.pack(FMT_32, zlib.crc32(content) & 0xFFFFFFFF) | ||
| # On eventlet 0.17.3, GreenIO.write() can make partial write. | ||
@@ -245,3 +270,3 @@ # Use a loop to ensure that all bytes are written. | ||
| Mixed streams that contain non-subunit content is supported when a | ||
| non_subunit_name is passed to the contructor. The default is to raise an | ||
| non_subunit_name is passed to the constructor. The default is to raise an | ||
| error containing the non-subunit byte after it has been read from the | ||
@@ -261,10 +286,10 @@ stream. | ||
| 0x0: None, | ||
| 0x1: 'exists', | ||
| 0x2: 'inprogress', | ||
| 0x3: 'success', | ||
| 0x4: 'uxsuccess', | ||
| 0x5: 'skip', | ||
| 0x6: 'fail', | ||
| 0x7: 'xfail', | ||
| } | ||
| 0x1: "exists", | ||
| 0x2: "inprogress", | ||
| 0x3: "success", | ||
| 0x4: "uxsuccess", | ||
| 0x5: "skip", | ||
| 0x6: "fail", | ||
| 0x7: "xfail", | ||
| } | ||
@@ -284,3 +309,3 @@ def __init__(self, source, non_subunit_name=None): | ||
| self.source = subunit.make_stream_binary(source) | ||
| self.codec = codecs.lookup('utf8').incrementaldecoder() | ||
| self.codec = codecs.lookup("utf8").incrementaldecoder() | ||
@@ -326,3 +351,3 @@ def run(self, result): | ||
| # select.select. fallback to one-byte-at-a-time. | ||
| if sys.platform == 'win32': | ||
| if sys.platform == "win32": | ||
| break | ||
@@ -365,5 +390,3 @@ | ||
| break | ||
| result.status( | ||
| file_name=self.non_subunit_name, | ||
| file_bytes=b''.join(buffered)) | ||
| result.status(file_name=self.non_subunit_name, file_bytes=b"".join(buffered)) | ||
| if mid_character or not len(content) or content[0] != SIGNATURE[0]: | ||
@@ -379,9 +402,17 @@ continue | ||
| except ParseError as error: | ||
| result.status(test_id="subunit.parser", eof=True, | ||
| file_name="Packet data", file_bytes=b''.join(packet), | ||
| mime_type="application/octet-stream") | ||
| result.status(test_id="subunit.parser", test_status='fail', | ||
| eof=True, file_name="Parser Error", | ||
| file_bytes=(error.args[0]).encode('utf8'), | ||
| mime_type="text/plain;charset=utf8") | ||
| result.status( | ||
| test_id="subunit.parser", | ||
| eof=True, | ||
| file_name="Packet data", | ||
| file_bytes=b"".join(packet), | ||
| mime_type="application/octet-stream", | ||
| ) | ||
| result.status( | ||
| test_id="subunit.parser", | ||
| test_status="fail", | ||
| eof=True, | ||
| file_name="Parser Error", | ||
| file_bytes=(error.args[0]).encode("utf8"), | ||
| mime_type="text/plain;charset=utf8", | ||
| ) | ||
@@ -393,17 +424,17 @@ def _parse_varint(self, data, pos, max_3_bytes=False): | ||
| # error not a normal situation. | ||
| data_0 = struct.unpack(FMT_8, data[pos:pos+1])[0] | ||
| typeenum = data_0 & 0xc0 | ||
| value_0 = data_0 & 0x3f | ||
| data_0 = struct.unpack(FMT_8, data[pos : pos + 1])[0] | ||
| typeenum = data_0 & 0xC0 | ||
| value_0 = data_0 & 0x3F | ||
| if typeenum == 0x00: | ||
| return value_0, 1 | ||
| elif typeenum == 0x40: | ||
| data_1 = struct.unpack(FMT_8, data[pos+1:pos+2])[0] | ||
| data_1 = struct.unpack(FMT_8, data[pos + 1 : pos + 2])[0] | ||
| return (value_0 << 8) | data_1, 2 | ||
| elif typeenum == 0x80: | ||
| data_1 = struct.unpack(FMT_16, data[pos+1:pos+3])[0] | ||
| data_1 = struct.unpack(FMT_16, data[pos + 1 : pos + 3])[0] | ||
| return (value_0 << 16) | data_1, 3 | ||
| else: | ||
| if max_3_bytes: | ||
| raise ParseError('3 byte maximum given but 4 byte value found.') | ||
| data_1, data_2 = struct.unpack(FMT_24, data[pos+1:pos+4]) | ||
| raise ParseError("3 byte maximum given but 4 byte value found.") | ||
| data_1, data_2 = struct.unpack(FMT_24, data[pos + 1 : pos + 4]) | ||
| result = (value_0 << 24) | data_1 << 8 | data_2 | ||
@@ -433,3 +464,3 @@ return result, 4 | ||
| crc = zlib.crc32(packet[-1][:-4], crc) & 0xffffffff | ||
| crc = zlib.crc32(packet[-1][:-4], crc) & 0xFFFFFFFF | ||
| packet_crc = struct.unpack(FMT_32, packet[-1][-4:])[0] | ||
@@ -439,10 +470,5 @@ | ||
| # Bad CRC, report it and stop parsing the packet. | ||
| raise ParseError( | ||
| 'Bad checksum - calculated (0x%x), stored (0x%x)' % ( | ||
| crc, packet_crc)) | ||
| raise ParseError("Bad checksum - calculated (0x%x), stored (0x%x)" % (crc, packet_crc)) | ||
| if hasattr(builtins, 'memoryview'): | ||
| body = memoryview(packet[-1]) | ||
| else: | ||
| body = packet[-1] | ||
| body = memoryview(packet[-1]) | ||
@@ -455,7 +481,6 @@ # Discard CRC-32 | ||
| if flags & FLAG_TIMESTAMP: | ||
| seconds = struct.unpack(FMT_32, body[pos:pos+4])[0] | ||
| nanoseconds, consumed = self._parse_varint(body, pos+4) | ||
| seconds = struct.unpack(FMT_32, body[pos : pos + 4])[0] | ||
| nanoseconds, consumed = self._parse_varint(body, pos + 4) | ||
| pos = pos + 4 + consumed | ||
| timestamp = EPOCH + datetime.timedelta( | ||
| seconds=seconds, microseconds=nanoseconds/1000) | ||
| timestamp = EPOCH + datetime.timedelta(seconds=seconds, microseconds=nanoseconds / 1000) | ||
| else: | ||
@@ -488,7 +513,8 @@ timestamp = None | ||
| pos += consumed | ||
| file_bytes = body[pos:pos+content_length] | ||
| file_bytes = body[pos : pos + content_length] | ||
| if len(file_bytes) != content_length: | ||
| raise ParseError('File content extends past end of packet: ' | ||
| 'claimed %d bytes, %d available' % ( | ||
| content_length, len(file_bytes))) | ||
| raise ParseError( | ||
| "File content extends past end of packet: " | ||
| "claimed %d bytes, %d available" % (content_length, len(file_bytes)) | ||
| ) | ||
| pos += content_length | ||
@@ -508,6 +534,13 @@ else: | ||
| result.status( | ||
| test_id=test_id, test_status=test_status, | ||
| test_tags=test_tags, runnable=runnable, mime_type=mime_type, | ||
| eof=eof, file_name=file_name, file_bytes=file_bytes, | ||
| route_code=route_code, timestamp=timestamp) | ||
| test_id=test_id, | ||
| test_status=test_status, | ||
| test_tags=test_tags, | ||
| runnable=runnable, | ||
| mime_type=mime_type, | ||
| eof=eof, | ||
| file_name=file_name, | ||
| file_bytes=file_bytes, | ||
| route_code=route_code, | ||
| timestamp=timestamp, | ||
| ) | ||
@@ -519,19 +552,19 @@ __call__ = run | ||
| pos += consumed | ||
| utf8_bytes = buf[pos:pos+length] | ||
| utf8_bytes = buf[pos : pos + length] | ||
| if length != len(utf8_bytes): | ||
| raise ParseError( | ||
| 'UTF8 string at offset %d extends past end of packet: ' | ||
| 'claimed %d bytes, %d available' % (pos - 2, length, | ||
| len(utf8_bytes))) | ||
| "UTF8 string at offset %d extends past end of packet: " | ||
| "claimed %d bytes, %d available" % (pos - 2, length, len(utf8_bytes)) | ||
| ) | ||
| if NUL_ELEMENT in utf8_bytes: | ||
| raise ParseError('UTF8 string at offset %d contains NUL byte' % ( | ||
| pos-2,)) | ||
| raise ParseError("UTF8 string at offset %d contains NUL byte" % (pos - 2,)) | ||
| try: | ||
| utf8, decoded_bytes = utf_8_decode(utf8_bytes) | ||
| if decoded_bytes != length: | ||
| raise ParseError("Invalid (partially decodable) string at " | ||
| "offset %d, %d undecoded bytes" % ( | ||
| pos-2, length - decoded_bytes)) | ||
| return utf8, length+pos | ||
| raise ParseError( | ||
| "Invalid (partially decodable) string at " | ||
| "offset %d, %d undecoded bytes" % (pos - 2, length - decoded_bytes) | ||
| ) | ||
| return utf8, length + pos | ||
| except UnicodeDecodeError: | ||
| raise ParseError('UTF8 string at offset %d is not UTF8' % (pos-2,)) | ||
| raise ParseError("UTF8 string at offset %d is not UTF8" % (pos - 2,)) |
+1
-89
| #!/usr/bin/env python3 | ||
| import os.path | ||
| from setuptools import setup | ||
| def _get_version_from_file(filename, start_of_line, split_marker): | ||
| """Extract version from file, giving last matching value or None""" | ||
| try: | ||
| return [ | ||
| x for x in open(filename) if x.startswith(start_of_line) | ||
| ][-1].split(split_marker)[1].strip() | ||
| except (IOError, IndexError): | ||
| return None | ||
| VERSION = ( | ||
| # Assume we are in a distribution, which has PKG-INFO | ||
| _get_version_from_file('PKG-INFO', 'Version:', ':') | ||
| # Must be a development checkout, so use the Makefile | ||
| or _get_version_from_file('Makefile', 'VERSION', '=') | ||
| or "0.0" | ||
| ) | ||
| relpath = os.path.dirname(__file__) | ||
| if relpath: | ||
| os.chdir(relpath) | ||
| setup( | ||
| name='python-subunit', | ||
| version=VERSION, | ||
| description=('Python implementation of subunit test streaming protocol'), | ||
| long_description=open('README.rst').read(), | ||
| classifiers=[ | ||
| 'Intended Audience :: Developers', | ||
| 'Operating System :: OS Independent', | ||
| 'Programming Language :: Python', | ||
| 'Programming Language :: Python :: 3', | ||
| 'Programming Language :: Python :: 3.7', | ||
| 'Programming Language :: Python :: 3.8', | ||
| 'Programming Language :: Python :: 3.9', | ||
| 'Programming Language :: Python :: 3.10', | ||
| 'Programming Language :: Python :: 3.11', | ||
| 'Programming Language :: Python :: 3.12', | ||
| 'Topic :: Software Development :: Testing', | ||
| ], | ||
| keywords='python test streaming', | ||
| author='Robert Collins', | ||
| author_email='subunit-dev@lists.launchpad.net', | ||
| url='http://launchpad.net/subunit', | ||
| license='Apache-2.0 or BSD', | ||
| project_urls={ | ||
| "Bug Tracker": "https://bugs.launchpad.net/subunit", | ||
| "Source Code": "https://github.com/testing-cabal/subunit/", | ||
| }, | ||
| packages=['subunit', 'subunit.tests', 'subunit.filter_scripts'], | ||
| package_dir={'subunit': 'python/subunit'}, | ||
| python_requires=">=3.7", | ||
| install_requires=[ | ||
| 'iso8601', | ||
| 'testtools>=0.9.34', | ||
| ], | ||
| entry_points={ | ||
| 'console_scripts': [ | ||
| 'subunit-1to2=subunit.filter_scripts.subunit_1to2:main', | ||
| 'subunit-2to1=subunit.filter_scripts.subunit_2to1:main', | ||
| 'subunit-filter=subunit.filter_scripts.subunit_filter:main', | ||
| 'subunit-ls=subunit.filter_scripts.subunit_ls:main', | ||
| 'subunit-notify=subunit.filter_scripts.subunit_notify:main', | ||
| 'subunit-output=subunit.filter_scripts.subunit_output:main', | ||
| 'subunit-stats=subunit.filter_scripts.subunit_stats:main', | ||
| 'subunit-tags=subunit.filter_scripts.subunit_tags:main', | ||
| 'subunit2csv=subunit.filter_scripts.subunit2csv:main', | ||
| 'subunit2disk=subunit.filter_scripts.subunit2disk:main', | ||
| 'subunit2gtk=subunit.filter_scripts.subunit2gtk:main', | ||
| 'subunit2junitxml=subunit.filter_scripts.subunit2junitxml:main', | ||
| 'subunit2pyunit=subunit.filter_scripts.subunit2pyunit:main', | ||
| 'tap2subunit=subunit.filter_scripts.tap2subunit:main', | ||
| ] | ||
| }, | ||
| tests_require=[ | ||
| 'fixtures', | ||
| 'hypothesis', | ||
| 'testscenarios', | ||
| ], | ||
| extras_require={ | ||
| 'docs': ['docutils'], | ||
| 'test': ['fixtures', 'testscenarios', 'hypothesis'], | ||
| }, | ||
| ) | ||
| setup() |
| [console_scripts] | ||
| subunit-1to2 = subunit.filter_scripts.subunit_1to2:main | ||
| subunit-2to1 = subunit.filter_scripts.subunit_2to1:main | ||
| subunit-filter = subunit.filter_scripts.subunit_filter:main | ||
| subunit-ls = subunit.filter_scripts.subunit_ls:main | ||
| subunit-notify = subunit.filter_scripts.subunit_notify:main | ||
| subunit-output = subunit.filter_scripts.subunit_output:main | ||
| subunit-stats = subunit.filter_scripts.subunit_stats:main | ||
| subunit-tags = subunit.filter_scripts.subunit_tags:main | ||
| subunit2csv = subunit.filter_scripts.subunit2csv:main | ||
| subunit2disk = subunit.filter_scripts.subunit2disk:main | ||
| subunit2gtk = subunit.filter_scripts.subunit2gtk:main | ||
| subunit2junitxml = subunit.filter_scripts.subunit2junitxml:main | ||
| subunit2pyunit = subunit.filter_scripts.subunit2pyunit:main | ||
| tap2subunit = subunit.filter_scripts.tap2subunit:main |
| Metadata-Version: 2.1 | ||
| Name: python-subunit | ||
| Version: 1.4.4 | ||
| Summary: Python implementation of subunit test streaming protocol | ||
| Home-page: http://launchpad.net/subunit | ||
| Author: Robert Collins | ||
| Author-email: subunit-dev@lists.launchpad.net | ||
| License: Apache-2.0 or BSD | ||
| Project-URL: Bug Tracker, https://bugs.launchpad.net/subunit | ||
| Project-URL: Source Code, https://github.com/testing-cabal/subunit/ | ||
| Keywords: python test streaming | ||
| Classifier: Intended Audience :: Developers | ||
| Classifier: Operating System :: OS Independent | ||
| Classifier: Programming Language :: Python | ||
| Classifier: Programming Language :: Python :: 3 | ||
| Classifier: Programming Language :: Python :: 3.7 | ||
| Classifier: Programming Language :: Python :: 3.8 | ||
| Classifier: Programming Language :: Python :: 3.9 | ||
| Classifier: Programming Language :: Python :: 3.10 | ||
| Classifier: Programming Language :: Python :: 3.11 | ||
| Classifier: Programming Language :: Python :: 3.12 | ||
| Classifier: Topic :: Software Development :: Testing | ||
| Requires-Python: >=3.7 | ||
| Provides-Extra: docs | ||
| Provides-Extra: test | ||
| License-File: COPYING | ||
| subunit: A streaming protocol for test results | ||
| Copyright (C) 2005-2013 Robert Collins <robertc@robertcollins.net> | ||
| Licensed under either the Apache License, Version 2.0 or the BSD 3-clause | ||
| license at the users choice. A copy of both licenses are available in the | ||
| project source as Apache-2.0 and BSD. You may not use this file except in | ||
| compliance with one of these two licences. | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
| license you chose for the specific language governing permissions and | ||
| limitations under that license. | ||
| See the COPYING file for full details on the licensing of Subunit. | ||
| Subunit | ||
| ------- | ||
| Subunit is a streaming protocol for test results. | ||
| There are two major revisions of the protocol. Version 1 was trivially human | ||
| readable but had significant defects as far as highly parallel testing was | ||
| concerned - it had no room for doing discovery and execution in parallel, | ||
| required substantial buffering when multiplexing and was fragile - a corrupt | ||
| byte could cause an entire stream to be misparsed. Version 1.1 added | ||
| encapsulation of binary streams which mitigated some of the issues but the | ||
| core remained. | ||
| Version 2 shares many of the good characteristics of Version 1 - it can be | ||
| embedded into a regular text stream (e.g. from a build system) and it still | ||
| models xUnit style test execution. It also fixes many of the issues with | ||
| Version 1 - Version 2 can be multiplexed without excessive buffering (in | ||
| time or space), it has a well defined recovery mechanism for dealing with | ||
| corrupted streams (e.g. where two processes write to the same stream | ||
| concurrently, or where the stream generator suffers a bug). | ||
| More details on both protocol version s can be found in the 'Protocol' section | ||
| of this document. | ||
| Subunit comes with command line filters to process a subunit stream and | ||
| language bindings for python, C, C++ and shell. Bindings are easy to write | ||
| for other languages. | ||
| A number of useful things can be done easily with subunit: | ||
| * Test aggregation: Tests run separately can be combined and then | ||
| reported/displayed together. For instance, tests from different languages | ||
| can be shown as a seamless whole, and tests running on multiple machines | ||
| can be aggregated into a single stream through a multiplexer. | ||
| * Test archiving: A test run may be recorded and replayed later. | ||
| * Test isolation: Tests that may crash or otherwise interact badly with each | ||
| other can be run seperately and then aggregated, rather than interfering | ||
| with each other or requiring an adhoc test->runner reporting protocol. | ||
| * Grid testing: subunit can act as the necessary serialisation and | ||
| deserialiation to get test runs on distributed machines to be reported in | ||
| real time. | ||
| Subunit supplies the following filters: | ||
| * tap2subunit - convert perl's TestAnythingProtocol to subunit. | ||
| * subunit2csv - convert a subunit stream to csv. | ||
| * subunit2disk - export a subunit stream to files on disk. | ||
| * subunit2pyunit - convert a subunit stream to pyunit test results. | ||
| * subunit2gtk - show a subunit stream in GTK. | ||
| * subunit2junitxml - convert a subunit stream to JUnit's XML format. | ||
| * subunit-diff - compare two subunit streams. | ||
| * subunit-filter - filter out tests from a subunit stream. | ||
| * subunit-ls - list info about tests present in a subunit stream. | ||
| * subunit-stats - generate a summary of a subunit stream. | ||
| * subunit-tags - add or remove tags from a stream. | ||
| Integration with other tools | ||
| ---------------------------- | ||
| Subunit's language bindings act as integration with various test runners like | ||
| 'check', 'cppunit', Python's 'unittest'. Beyond that a small amount of glue | ||
| (typically a few lines) will allow Subunit to be used in more sophisticated | ||
| ways. | ||
| Python | ||
| ====== | ||
| Subunit has excellent Python support: most of the filters and tools are written | ||
| in python and there are facilities for using Subunit to increase test isolation | ||
| seamlessly within a test suite. | ||
| The most common way is to run an existing python test suite and have it output | ||
| subunit via the ``subunit.run`` module:: | ||
| $ python -m subunit.run mypackage.tests.test_suite | ||
| For more information on the Python support Subunit offers , please see | ||
| ``pydoc subunit``, or the source in ``python/subunit/`` | ||
| C | ||
| = | ||
| Subunit has C bindings to emit the protocol. The 'check' C unit testing project | ||
| has included subunit support in their project for some years now. See | ||
| 'c/README' for more details. | ||
| C++ | ||
| === | ||
| The C library is includable and usable directly from C++. A TestListener for | ||
| CPPUnit is included in the Subunit distribution. See 'c++/README' for details. | ||
| shell | ||
| ===== | ||
| There are two sets of shell tools. There are filters, which accept a subunit | ||
| stream on stdin and output processed data (or a transformed stream) on stdout. | ||
| Then there are unittest facilities similar to those for C : shell bindings | ||
| consisting of simple functions to output protocol elements, and a patch for | ||
| adding subunit output to the 'ShUnit' shell test runner. See 'shell/README' for | ||
| details. | ||
| Filter recipes | ||
| -------------- | ||
| To ignore some failing tests whose root cause is already known:: | ||
| subunit-filter --without 'AttributeError.*flavor' | ||
| The xUnit test model | ||
| -------------------- | ||
| Subunit implements a slightly modified xUnit test model. The stock standard | ||
| model is that there are tests, which have an id(), can be run, and when run | ||
| start, emit an outcome (like success or failure) and then finish. | ||
| Subunit extends this with the idea of test enumeration (find out about tests | ||
| a runner has without running them), tags (allow users to describe tests in | ||
| ways the test framework doesn't apply any semantic value to), file attachments | ||
| (allow arbitrary data to make analysing a failure easy) and timestamps. | ||
| The protocol | ||
| ------------ | ||
| Version 2, or v2 is new and still under development, but is intended to | ||
| supercede version 1 in the very near future. Subunit's bundled tools accept | ||
| only version 2 and only emit version 2, but the new filters subunit-1to2 and | ||
| subunit-2to1 can be used to interoperate with older third party libraries. | ||
| Version 2 | ||
| ========= | ||
| Version 2 is a binary protocol consisting of independent packets that can be | ||
| embedded in the output from tools like make - as long as each packet has no | ||
| other bytes mixed in with it (which 'make -j N>1' has a tendency of doing). | ||
| Version 2 is currently in draft form, and early adopters should be willing | ||
| to either discard stored results (if protocol changes are made), or bulk | ||
| convert them back to v1 and then to a newer edition of v2. | ||
| The protocol synchronises at the start of the stream, after a packet, or | ||
| after any 0x0A byte. That is, a subunit v2 packet starts after a newline or | ||
| directly after the end of the prior packet. | ||
| Subunit is intended to be transported over a reliable streaming protocol such | ||
| as TCP. As such it does not concern itself with out of order delivery of | ||
| packets. However, because of the possibility of corruption due to either | ||
| bugs in the sender, or due to mixed up data from concurrent writes to the same | ||
| fd when being embedded, subunit strives to recover reasonably gracefully from | ||
| damaged data. | ||
| A key design goal for Subunit version 2 is to allow processing and multiplexing | ||
| without forcing buffering for semantic correctness, as buffering tends to hide | ||
| hung or otherwise misbehaving tests. That said, limited time based buffering | ||
| for network efficiency is a good idea - this is ultimately implementator | ||
| choice. Line buffering is also discouraged for subunit streams, as dropping | ||
| into a debugger or other tool may require interactive traffic even if line | ||
| buffering would not otherwise be a problem. | ||
| In version two there are two conceptual events - a test status event and a file | ||
| attachment event. Events may have timestamps, and the path of multiplexers that | ||
| an event is routed through is recorded to permit sending actions back to the | ||
| source (such as new tests to run or stdin for driving debuggers and other | ||
| interactive input). Test status events are used to enumerate tests, to report | ||
| tests and test helpers as they run. Tests may have tags, used to allow | ||
| tunnelling extra meanings through subunit without requiring parsing of | ||
| arbitrary file attachments. Things that are not standalone tests get marked | ||
| as such by setting the 'Runnable' flag to false. (For instance, individual | ||
| assertions in TAP are not runnable tests, only the top level TAP test script | ||
| is runnable). | ||
| File attachments are used to provide rich detail about the nature of a failure. | ||
| File attachments can also be used to encapsulate stdout and stderr both during | ||
| and outside tests. | ||
| Most numbers are stored in network byte order - Most Significant Byte first | ||
| encoded using a variation of http://www.dlugosz.com/ZIP2/VLI.html. The first | ||
| byte's top 2 high order bits encode the total number of octets in the number. | ||
| This encoding can encode values from 0 to 2**30-1, enough to encode a | ||
| nanosecond. Numbers that are not variable length encoded are still stored in | ||
| MSB order. | ||
| +--------+--------+---------+------------+ | ||
| | prefix | octets | max | max | | ||
| +========+========+=========+============+ | ||
| | 00 | 1 | 2**6-1 | 63 | | ||
| +--------+--------+---------+------------+ | ||
| | 01 | 2 | 2**14-1 | 16383 | | ||
| +--------+--------+---------+------------+ | ||
| | 10 | 3 | 2**22-1 | 4194303 | | ||
| +--------+--------+---------+------------+ | ||
| | 11 | 4 | 2**30-1 | 1073741823 | | ||
| +--------+--------+---------+------------+ | ||
| All variable length elements of the packet are stored with a length prefix | ||
| number allowing them to be skipped over for consumers that don't need to | ||
| interpret them. | ||
| UTF-8 strings are with no terminating NUL and should not have any embedded NULs | ||
| (implementations SHOULD validate any such strings that they process and take | ||
| some remedial action (such as discarding the packet as corrupt). | ||
| In short the structure of a packet is: | ||
| PACKET := SIGNATURE FLAGS PACKET_LENGTH TIMESTAMP? TESTID? TAGS? MIME? | ||
| FILECONTENT? ROUTING_CODE? CRC32 | ||
| In more detail... | ||
| Packets are identified by a single byte signature - 0xB3, which is never legal | ||
| in a UTF-8 stream as the first byte of a character. 0xB3 starts with the first | ||
| bit set and the second not, which is the UTF-8 signature for a continuation | ||
| byte. 0xB3 was chosen as 0x73 ('s' in ASCII') with the top two bits replaced by | ||
| the 1 and 0 for a continuation byte. | ||
| If subunit packets are being embedded in a non-UTF-8 text stream, where 0x73 is | ||
| a legal character, consider either recoding the text to UTF-8, or using | ||
| subunit's 'file' packets to embed the text stream in subunit, rather than the | ||
| other way around. | ||
| Following the signature byte comes a 16-bit flags field, which includes a | ||
| 4-bit version field - if the version is not 0x2 then the packet cannot be | ||
| read. It is recommended to signal an error at this point (e.g. by emitting | ||
| a synthetic error packet and returning to the top level loop to look for | ||
| new packets, or exiting with an error). If recovery is desired, treat the | ||
| packet signature as an opaque byte and scan for a new synchronisation point. | ||
| NB: Subunit V1 and V2 packets may legitimately included 0xB3 internally, | ||
| as they are an 8-bit safe container format, so recovery from this situation | ||
| may involve an arbitrary number of false positives until an actual packet | ||
| is encountered : and even then it may still be false, failing after passing | ||
| the version check due to coincidence. | ||
| Flags are stored in network byte order too. | ||
| +------------+------------+------------------------+ | ||
| | High byte | Low byte | | ||
| +------------+------------+------------------------+ | ||
| | 15 14 13 12 11 10 9 8 | 7 6 5 4 3 2 1 0 | | ||
| +------------+------------+------------------------+ | ||
| | VERSION | feature bits | | ||
| +------------+-------------------------------------+ | ||
| Valid version values are: | ||
| 0x2 - version 2 | ||
| Feature bits: | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 11 | mask 0x0800 | Test id present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 10 | mask 0x0400 | Routing code present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 9 | mask 0x0200 | Timestamp present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 8 | mask 0x0100 | Test is 'runnable'. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 7 | mask 0x0080 | Tags are present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 6 | mask 0x0040 | File content is present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 5 | mask 0x0020 | File MIME type is present.| | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 4 | mask 0x0010 | EOF marker. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 3 | mask 0x0008 | Must be zero in version 2.| | ||
| +---------+-------------+---------------------------+ | ||
| Test status gets three bits: | ||
| Bit 2 | Bit 1 | Bit 0 - mask 0x0007 - A test status enum lookup: | ||
| * 000 - undefined / no test | ||
| * 001 - Enumeration / existence | ||
| * 002 - In progress | ||
| * 003 - Success | ||
| * 004 - Unexpected Success | ||
| * 005 - Skipped | ||
| * 006 - Failed | ||
| * 007 - Expected failure | ||
| After the flags field is a number field giving the length in bytes for the | ||
| entire packet including the signature and the checksum. This length must | ||
| be less than 4MiB - 4194303 bytes. The encoding can obviously record a larger | ||
| number but one of the goals is to avoid requiring large buffers, or causing | ||
| large latency in the packet forward/processing pipeline. Larger file | ||
| attachments can be communicated in multiple packets, and the overhead in such a | ||
| 4MiB packet is approximately 0.2%. | ||
| The rest of the packet is a series of optional features as specified by the set | ||
| feature bits in the flags field. When absent they are entirely absent. | ||
| Forwarding and multiplexing of packets can be done without interpreting the | ||
| remainder of the packet until the routing code and checksum (which are both at | ||
| the end of the packet). Additionally, routers can often avoid copying or moving | ||
| the bulk of the packet, as long as the routing code size increase doesn't force | ||
| the length encoding to take up a new byte (which will only happen to packets | ||
| less than or equal to 16KiB in length) - large packets are very efficient to | ||
| route. | ||
| Timestamp when present is a 32 bit unsigned integer for seconds, and a variable | ||
| length number for nanoseconds, representing UTC time since Unix Epoch in | ||
| seconds and nanoseconds. | ||
| Test id when present is a UTF-8 string. The test id should uniquely identify | ||
| runnable tests such that they can be selected individually. For tests and other | ||
| actions which cannot be individually run (such as test | ||
| fixtures/layers/subtests) uniqueness is not required (though being human | ||
| meaningful is highly recommended). | ||
| Tags when present is a length prefixed vector of UTF-8 strings, one per tag. | ||
| There are no restrictions on tag content (other than the restrictions on UTF-8 | ||
| strings in subunit in general). Tags have no ordering. | ||
| When a MIME type is present, it defines the MIME type for the file across all | ||
| packets same file (routing code + testid + name uniquely identifies a file, | ||
| reset when EOF is flagged). If a file never has a MIME type set, it should be | ||
| treated as application/octet-stream. | ||
| File content when present is a UTF-8 string for the name followed by the length | ||
| in bytes of the content, and then the content octets. | ||
| If present routing code is a UTF-8 string. The routing code is used to | ||
| determine which test backend a test was running on when doing data analysis, | ||
| and to route stdin to the test process if interaction is required. | ||
| Multiplexers SHOULD add a routing code if none is present, and prefix any | ||
| existing routing code with a routing code ('/' separated) if one is already | ||
| present. For example, a multiplexer might label each stream it is multiplexing | ||
| with a simple ordinal ('0', '1' etc), and given an incoming packet with route | ||
| code '3' from stream '0' would adjust the route code when forwarding the packet | ||
| to be '0/3'. | ||
| Following the end of the packet is a CRC-32 checksum of the contents of the | ||
| packet including the signature. | ||
| Example packets | ||
| ~~~~~~~~~~~~~~~ | ||
| Trivial test "foo" enumeration packet, with test id, runnable set, | ||
| status=enumeration. Spaces below are to visually break up signature / flags / | ||
| length / testid / crc32 | ||
| b3 2901 0c 03666f6f 08555f1b | ||
| Version 1 (and 1.1) | ||
| =================== | ||
| Version 1 (and 1.1) are mostly human readable protocols. | ||
| Sample subunit wire contents | ||
| ---------------------------- | ||
| The following:: | ||
| test: test foo works | ||
| success: test foo works | ||
| test: tar a file. | ||
| failure: tar a file. [ | ||
| .. | ||
| ].. space is eaten. | ||
| foo.c:34 WARNING foo is not defined. | ||
| ] | ||
| a writeln to stdout | ||
| When run through subunit2pyunit:: | ||
| .F | ||
| a writeln to stdout | ||
| ======================== | ||
| FAILURE: tar a file. | ||
| ------------------- | ||
| .. | ||
| ].. space is eaten. | ||
| foo.c:34 WARNING foo is not defined. | ||
| Subunit v1 protocol description | ||
| =============================== | ||
| This description is being ported to an EBNF style. Currently its only partly in | ||
| that style, but should be fairly clear all the same. When in doubt, refer the | ||
| source (and ideally help fix up the description!). Generally the protocol is | ||
| line orientated and consists of either directives and their parameters, or | ||
| when outside a DETAILS region unexpected lines which are not interpreted by | ||
| the parser - they should be forwarded unaltered:: | ||
| test|testing|test:|testing: test LABEL | ||
| success|success:|successful|successful: test LABEL | ||
| success|success:|successful|successful: test LABEL DETAILS | ||
| failure: test LABEL | ||
| failure: test LABEL DETAILS | ||
| error: test LABEL | ||
| error: test LABEL DETAILS | ||
| skip[:] test LABEL | ||
| skip[:] test LABEL DETAILS | ||
| xfail[:] test LABEL | ||
| xfail[:] test LABEL DETAILS | ||
| uxsuccess[:] test LABEL | ||
| uxsuccess[:] test LABEL DETAILS | ||
| progress: [+|-]X | ||
| progress: push | ||
| progress: pop | ||
| tags: [-]TAG ... | ||
| time: YYYY-MM-DD HH:MM:SSZ | ||
| LABEL: UTF8* | ||
| NAME: UTF8* | ||
| DETAILS ::= BRACKETED | MULTIPART | ||
| BRACKETED ::= '[' CR UTF8-lines ']' CR | ||
| MULTIPART ::= '[ multipart' CR PART* ']' CR | ||
| PART ::= PART_TYPE CR NAME CR PART_BYTES CR | ||
| PART_TYPE ::= Content-Type: type/sub-type(;parameter=value,parameter=value) | ||
| PART_BYTES ::= (DIGITS CR LF BYTE{DIGITS})* '0' CR LF | ||
| unexpected output on stdout -> stdout. | ||
| exit w/0 or last test completing -> error | ||
| Tags given outside a test are applied to all following tests | ||
| Tags given after a test: line and before the result line for the same test | ||
| apply only to that test, and inherit the current global tags. | ||
| A '-' before a tag is used to remove tags - e.g. to prevent a global tag | ||
| applying to a single test, or to cancel a global tag. | ||
| The progress directive is used to provide progress information about a stream | ||
| so that stream consumer can provide completion estimates, progress bars and so | ||
| on. Stream generators that know how many tests will be present in the stream | ||
| should output "progress: COUNT". Stream filters that add tests should output | ||
| "progress: +COUNT", and those that remove tests should output | ||
| "progress: -COUNT". An absolute count should reset the progress indicators in | ||
| use - it indicates that two separate streams from different generators have | ||
| been trivially concatenated together, and there is no knowledge of how many | ||
| more complete streams are incoming. Smart concatenation could scan each stream | ||
| for their count and sum them, or alternatively translate absolute counts into | ||
| relative counts inline. It is recommended that outputters avoid absolute counts | ||
| unless necessary. The push and pop directives are used to provide local regions | ||
| for progress reporting. This fits with hierarchically operating test | ||
| environments - such as those that organise tests into suites - the top-most | ||
| runner can report on the number of suites, and each suite surround its output | ||
| with a (push, pop) pair. Interpreters should interpret a pop as also advancing | ||
| the progress of the restored level by one step. Encountering progress | ||
| directives between the start and end of a test pair indicates that a previous | ||
| test was interrupted and did not cleanly terminate: it should be implicitly | ||
| closed with an error (the same as when a stream ends with no closing test | ||
| directive for the most recently started test). | ||
| The time directive acts as a clock event - it sets the time for all future | ||
| events. The value should be a valid ISO8601 time. | ||
| The skip, xfail and uxsuccess outcomes are not supported by all testing | ||
| environments. In Python the testttools (https://launchpad.net/testtools) | ||
| library is used to translate these automatically if an older Python version | ||
| that does not support them is in use. See the testtools documentation for the | ||
| translation policy. | ||
| skip is used to indicate a test was discovered but not executed. xfail is used | ||
| to indicate a test that errored in some expected fashion (also know as "TODO" | ||
| tests in some frameworks). uxsuccess is used to indicate and unexpected success | ||
| where a test though to be failing actually passes. It is complementary to | ||
| xfail. | ||
| Hacking on subunit | ||
| ------------------ | ||
| Releases | ||
| ======== | ||
| * Update versions in configure.ac and python/subunit/__init__.py. | ||
| * Update NEWS. | ||
| * Do a make distcheck, which will update Makefile etc. | ||
| * Do a PyPI release: PYTHONPATH=python python setup.py sdist bdist_wheel; twine upload -s dist/* | ||
| * Upload the regular one to LP. | ||
| * Push a tagged commit. | ||
| git push -t origin master:master |
| iso8601 | ||
| testtools>=0.9.34 | ||
| [docs] | ||
| docutils | ||
| [test] | ||
| fixtures | ||
| testscenarios | ||
| hypothesis |
| COPYING | ||
| MANIFEST.in | ||
| NEWS | ||
| README.rst | ||
| pyproject.toml | ||
| setup.py | ||
| python/subunit/__init__.py | ||
| python/subunit/_output.py | ||
| python/subunit/_to_disk.py | ||
| python/subunit/chunked.py | ||
| python/subunit/details.py | ||
| python/subunit/filters.py | ||
| python/subunit/progress_model.py | ||
| python/subunit/run.py | ||
| python/subunit/test_results.py | ||
| python/subunit/v2.py | ||
| python/subunit/filter_scripts/__init__.py | ||
| python/subunit/filter_scripts/subunit2csv.py | ||
| python/subunit/filter_scripts/subunit2disk.py | ||
| python/subunit/filter_scripts/subunit2gtk.py | ||
| python/subunit/filter_scripts/subunit2junitxml.py | ||
| python/subunit/filter_scripts/subunit2pyunit.py | ||
| python/subunit/filter_scripts/subunit_1to2.py | ||
| python/subunit/filter_scripts/subunit_2to1.py | ||
| python/subunit/filter_scripts/subunit_filter.py | ||
| python/subunit/filter_scripts/subunit_ls.py | ||
| python/subunit/filter_scripts/subunit_notify.py | ||
| python/subunit/filter_scripts/subunit_output.py | ||
| python/subunit/filter_scripts/subunit_stats.py | ||
| python/subunit/filter_scripts/subunit_tags.py | ||
| python/subunit/filter_scripts/tap2subunit.py | ||
| python/subunit/tests/__init__.py | ||
| python/subunit/tests/sample-script.py | ||
| python/subunit/tests/sample-two-script.py | ||
| python/subunit/tests/test_chunked.py | ||
| python/subunit/tests/test_details.py | ||
| python/subunit/tests/test_filter_to_disk.py | ||
| python/subunit/tests/test_filters.py | ||
| python/subunit/tests/test_output_filter.py | ||
| python/subunit/tests/test_progress_model.py | ||
| python/subunit/tests/test_run.py | ||
| python/subunit/tests/test_subunit_filter.py | ||
| python/subunit/tests/test_subunit_stats.py | ||
| python/subunit/tests/test_subunit_tags.py | ||
| python/subunit/tests/test_tap2subunit.py | ||
| python/subunit/tests/test_test_protocol.py | ||
| python/subunit/tests/test_test_protocol2.py | ||
| python/subunit/tests/test_test_results.py | ||
| python_subunit.egg-info/PKG-INFO | ||
| python_subunit.egg-info/SOURCES.txt | ||
| python_subunit.egg-info/dependency_links.txt | ||
| python_subunit.egg-info/entry_points.txt | ||
| python_subunit.egg-info/requires.txt | ||
| python_subunit.egg-info/top_level.txt |
-490
| subunit: A streaming protocol for test results | ||
| Copyright (C) 2005-2013 Robert Collins <robertc@robertcollins.net> | ||
| Licensed under either the Apache License, Version 2.0 or the BSD 3-clause | ||
| license at the users choice. A copy of both licenses are available in the | ||
| project source as Apache-2.0 and BSD. You may not use this file except in | ||
| compliance with one of these two licences. | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT | ||
| WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
| license you chose for the specific language governing permissions and | ||
| limitations under that license. | ||
| See the COPYING file for full details on the licensing of Subunit. | ||
| Subunit | ||
| ------- | ||
| Subunit is a streaming protocol for test results. | ||
| There are two major revisions of the protocol. Version 1 was trivially human | ||
| readable but had significant defects as far as highly parallel testing was | ||
| concerned - it had no room for doing discovery and execution in parallel, | ||
| required substantial buffering when multiplexing and was fragile - a corrupt | ||
| byte could cause an entire stream to be misparsed. Version 1.1 added | ||
| encapsulation of binary streams which mitigated some of the issues but the | ||
| core remained. | ||
| Version 2 shares many of the good characteristics of Version 1 - it can be | ||
| embedded into a regular text stream (e.g. from a build system) and it still | ||
| models xUnit style test execution. It also fixes many of the issues with | ||
| Version 1 - Version 2 can be multiplexed without excessive buffering (in | ||
| time or space), it has a well defined recovery mechanism for dealing with | ||
| corrupted streams (e.g. where two processes write to the same stream | ||
| concurrently, or where the stream generator suffers a bug). | ||
| More details on both protocol version s can be found in the 'Protocol' section | ||
| of this document. | ||
| Subunit comes with command line filters to process a subunit stream and | ||
| language bindings for python, C, C++ and shell. Bindings are easy to write | ||
| for other languages. | ||
| A number of useful things can be done easily with subunit: | ||
| * Test aggregation: Tests run separately can be combined and then | ||
| reported/displayed together. For instance, tests from different languages | ||
| can be shown as a seamless whole, and tests running on multiple machines | ||
| can be aggregated into a single stream through a multiplexer. | ||
| * Test archiving: A test run may be recorded and replayed later. | ||
| * Test isolation: Tests that may crash or otherwise interact badly with each | ||
| other can be run seperately and then aggregated, rather than interfering | ||
| with each other or requiring an adhoc test->runner reporting protocol. | ||
| * Grid testing: subunit can act as the necessary serialisation and | ||
| deserialiation to get test runs on distributed machines to be reported in | ||
| real time. | ||
| Subunit supplies the following filters: | ||
| * tap2subunit - convert perl's TestAnythingProtocol to subunit. | ||
| * subunit2csv - convert a subunit stream to csv. | ||
| * subunit2disk - export a subunit stream to files on disk. | ||
| * subunit2pyunit - convert a subunit stream to pyunit test results. | ||
| * subunit2gtk - show a subunit stream in GTK. | ||
| * subunit2junitxml - convert a subunit stream to JUnit's XML format. | ||
| * subunit-diff - compare two subunit streams. | ||
| * subunit-filter - filter out tests from a subunit stream. | ||
| * subunit-ls - list info about tests present in a subunit stream. | ||
| * subunit-stats - generate a summary of a subunit stream. | ||
| * subunit-tags - add or remove tags from a stream. | ||
| Integration with other tools | ||
| ---------------------------- | ||
| Subunit's language bindings act as integration with various test runners like | ||
| 'check', 'cppunit', Python's 'unittest'. Beyond that a small amount of glue | ||
| (typically a few lines) will allow Subunit to be used in more sophisticated | ||
| ways. | ||
| Python | ||
| ====== | ||
| Subunit has excellent Python support: most of the filters and tools are written | ||
| in python and there are facilities for using Subunit to increase test isolation | ||
| seamlessly within a test suite. | ||
| The most common way is to run an existing python test suite and have it output | ||
| subunit via the ``subunit.run`` module:: | ||
| $ python -m subunit.run mypackage.tests.test_suite | ||
| For more information on the Python support Subunit offers , please see | ||
| ``pydoc subunit``, or the source in ``python/subunit/`` | ||
| C | ||
| = | ||
| Subunit has C bindings to emit the protocol. The 'check' C unit testing project | ||
| has included subunit support in their project for some years now. See | ||
| 'c/README' for more details. | ||
| C++ | ||
| === | ||
| The C library is includable and usable directly from C++. A TestListener for | ||
| CPPUnit is included in the Subunit distribution. See 'c++/README' for details. | ||
| shell | ||
| ===== | ||
| There are two sets of shell tools. There are filters, which accept a subunit | ||
| stream on stdin and output processed data (or a transformed stream) on stdout. | ||
| Then there are unittest facilities similar to those for C : shell bindings | ||
| consisting of simple functions to output protocol elements, and a patch for | ||
| adding subunit output to the 'ShUnit' shell test runner. See 'shell/README' for | ||
| details. | ||
| Filter recipes | ||
| -------------- | ||
| To ignore some failing tests whose root cause is already known:: | ||
| subunit-filter --without 'AttributeError.*flavor' | ||
| The xUnit test model | ||
| -------------------- | ||
| Subunit implements a slightly modified xUnit test model. The stock standard | ||
| model is that there are tests, which have an id(), can be run, and when run | ||
| start, emit an outcome (like success or failure) and then finish. | ||
| Subunit extends this with the idea of test enumeration (find out about tests | ||
| a runner has without running them), tags (allow users to describe tests in | ||
| ways the test framework doesn't apply any semantic value to), file attachments | ||
| (allow arbitrary data to make analysing a failure easy) and timestamps. | ||
| The protocol | ||
| ------------ | ||
| Version 2, or v2 is new and still under development, but is intended to | ||
| supercede version 1 in the very near future. Subunit's bundled tools accept | ||
| only version 2 and only emit version 2, but the new filters subunit-1to2 and | ||
| subunit-2to1 can be used to interoperate with older third party libraries. | ||
| Version 2 | ||
| ========= | ||
| Version 2 is a binary protocol consisting of independent packets that can be | ||
| embedded in the output from tools like make - as long as each packet has no | ||
| other bytes mixed in with it (which 'make -j N>1' has a tendency of doing). | ||
| Version 2 is currently in draft form, and early adopters should be willing | ||
| to either discard stored results (if protocol changes are made), or bulk | ||
| convert them back to v1 and then to a newer edition of v2. | ||
| The protocol synchronises at the start of the stream, after a packet, or | ||
| after any 0x0A byte. That is, a subunit v2 packet starts after a newline or | ||
| directly after the end of the prior packet. | ||
| Subunit is intended to be transported over a reliable streaming protocol such | ||
| as TCP. As such it does not concern itself with out of order delivery of | ||
| packets. However, because of the possibility of corruption due to either | ||
| bugs in the sender, or due to mixed up data from concurrent writes to the same | ||
| fd when being embedded, subunit strives to recover reasonably gracefully from | ||
| damaged data. | ||
| A key design goal for Subunit version 2 is to allow processing and multiplexing | ||
| without forcing buffering for semantic correctness, as buffering tends to hide | ||
| hung or otherwise misbehaving tests. That said, limited time based buffering | ||
| for network efficiency is a good idea - this is ultimately implementator | ||
| choice. Line buffering is also discouraged for subunit streams, as dropping | ||
| into a debugger or other tool may require interactive traffic even if line | ||
| buffering would not otherwise be a problem. | ||
| In version two there are two conceptual events - a test status event and a file | ||
| attachment event. Events may have timestamps, and the path of multiplexers that | ||
| an event is routed through is recorded to permit sending actions back to the | ||
| source (such as new tests to run or stdin for driving debuggers and other | ||
| interactive input). Test status events are used to enumerate tests, to report | ||
| tests and test helpers as they run. Tests may have tags, used to allow | ||
| tunnelling extra meanings through subunit without requiring parsing of | ||
| arbitrary file attachments. Things that are not standalone tests get marked | ||
| as such by setting the 'Runnable' flag to false. (For instance, individual | ||
| assertions in TAP are not runnable tests, only the top level TAP test script | ||
| is runnable). | ||
| File attachments are used to provide rich detail about the nature of a failure. | ||
| File attachments can also be used to encapsulate stdout and stderr both during | ||
| and outside tests. | ||
| Most numbers are stored in network byte order - Most Significant Byte first | ||
| encoded using a variation of http://www.dlugosz.com/ZIP2/VLI.html. The first | ||
| byte's top 2 high order bits encode the total number of octets in the number. | ||
| This encoding can encode values from 0 to 2**30-1, enough to encode a | ||
| nanosecond. Numbers that are not variable length encoded are still stored in | ||
| MSB order. | ||
| +--------+--------+---------+------------+ | ||
| | prefix | octets | max | max | | ||
| +========+========+=========+============+ | ||
| | 00 | 1 | 2**6-1 | 63 | | ||
| +--------+--------+---------+------------+ | ||
| | 01 | 2 | 2**14-1 | 16383 | | ||
| +--------+--------+---------+------------+ | ||
| | 10 | 3 | 2**22-1 | 4194303 | | ||
| +--------+--------+---------+------------+ | ||
| | 11 | 4 | 2**30-1 | 1073741823 | | ||
| +--------+--------+---------+------------+ | ||
| All variable length elements of the packet are stored with a length prefix | ||
| number allowing them to be skipped over for consumers that don't need to | ||
| interpret them. | ||
| UTF-8 strings are with no terminating NUL and should not have any embedded NULs | ||
| (implementations SHOULD validate any such strings that they process and take | ||
| some remedial action (such as discarding the packet as corrupt). | ||
| In short the structure of a packet is: | ||
| PACKET := SIGNATURE FLAGS PACKET_LENGTH TIMESTAMP? TESTID? TAGS? MIME? | ||
| FILECONTENT? ROUTING_CODE? CRC32 | ||
| In more detail... | ||
| Packets are identified by a single byte signature - 0xB3, which is never legal | ||
| in a UTF-8 stream as the first byte of a character. 0xB3 starts with the first | ||
| bit set and the second not, which is the UTF-8 signature for a continuation | ||
| byte. 0xB3 was chosen as 0x73 ('s' in ASCII') with the top two bits replaced by | ||
| the 1 and 0 for a continuation byte. | ||
| If subunit packets are being embedded in a non-UTF-8 text stream, where 0x73 is | ||
| a legal character, consider either recoding the text to UTF-8, or using | ||
| subunit's 'file' packets to embed the text stream in subunit, rather than the | ||
| other way around. | ||
| Following the signature byte comes a 16-bit flags field, which includes a | ||
| 4-bit version field - if the version is not 0x2 then the packet cannot be | ||
| read. It is recommended to signal an error at this point (e.g. by emitting | ||
| a synthetic error packet and returning to the top level loop to look for | ||
| new packets, or exiting with an error). If recovery is desired, treat the | ||
| packet signature as an opaque byte and scan for a new synchronisation point. | ||
| NB: Subunit V1 and V2 packets may legitimately included 0xB3 internally, | ||
| as they are an 8-bit safe container format, so recovery from this situation | ||
| may involve an arbitrary number of false positives until an actual packet | ||
| is encountered : and even then it may still be false, failing after passing | ||
| the version check due to coincidence. | ||
| Flags are stored in network byte order too. | ||
| +------------+------------+------------------------+ | ||
| | High byte | Low byte | | ||
| +------------+------------+------------------------+ | ||
| | 15 14 13 12 11 10 9 8 | 7 6 5 4 3 2 1 0 | | ||
| +------------+------------+------------------------+ | ||
| | VERSION | feature bits | | ||
| +------------+-------------------------------------+ | ||
| Valid version values are: | ||
| 0x2 - version 2 | ||
| Feature bits: | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 11 | mask 0x0800 | Test id present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 10 | mask 0x0400 | Routing code present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 9 | mask 0x0200 | Timestamp present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 8 | mask 0x0100 | Test is 'runnable'. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 7 | mask 0x0080 | Tags are present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 6 | mask 0x0040 | File content is present. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 5 | mask 0x0020 | File MIME type is present.| | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 4 | mask 0x0010 | EOF marker. | | ||
| +---------+-------------+---------------------------+ | ||
| | Bit 3 | mask 0x0008 | Must be zero in version 2.| | ||
| +---------+-------------+---------------------------+ | ||
| Test status gets three bits: | ||
| Bit 2 | Bit 1 | Bit 0 - mask 0x0007 - A test status enum lookup: | ||
| * 000 - undefined / no test | ||
| * 001 - Enumeration / existence | ||
| * 002 - In progress | ||
| * 003 - Success | ||
| * 004 - Unexpected Success | ||
| * 005 - Skipped | ||
| * 006 - Failed | ||
| * 007 - Expected failure | ||
| After the flags field is a number field giving the length in bytes for the | ||
| entire packet including the signature and the checksum. This length must | ||
| be less than 4MiB - 4194303 bytes. The encoding can obviously record a larger | ||
| number but one of the goals is to avoid requiring large buffers, or causing | ||
| large latency in the packet forward/processing pipeline. Larger file | ||
| attachments can be communicated in multiple packets, and the overhead in such a | ||
| 4MiB packet is approximately 0.2%. | ||
| The rest of the packet is a series of optional features as specified by the set | ||
| feature bits in the flags field. When absent they are entirely absent. | ||
| Forwarding and multiplexing of packets can be done without interpreting the | ||
| remainder of the packet until the routing code and checksum (which are both at | ||
| the end of the packet). Additionally, routers can often avoid copying or moving | ||
| the bulk of the packet, as long as the routing code size increase doesn't force | ||
| the length encoding to take up a new byte (which will only happen to packets | ||
| less than or equal to 16KiB in length) - large packets are very efficient to | ||
| route. | ||
| Timestamp when present is a 32 bit unsigned integer for seconds, and a variable | ||
| length number for nanoseconds, representing UTC time since Unix Epoch in | ||
| seconds and nanoseconds. | ||
| Test id when present is a UTF-8 string. The test id should uniquely identify | ||
| runnable tests such that they can be selected individually. For tests and other | ||
| actions which cannot be individually run (such as test | ||
| fixtures/layers/subtests) uniqueness is not required (though being human | ||
| meaningful is highly recommended). | ||
| Tags when present is a length prefixed vector of UTF-8 strings, one per tag. | ||
| There are no restrictions on tag content (other than the restrictions on UTF-8 | ||
| strings in subunit in general). Tags have no ordering. | ||
| When a MIME type is present, it defines the MIME type for the file across all | ||
| packets same file (routing code + testid + name uniquely identifies a file, | ||
| reset when EOF is flagged). If a file never has a MIME type set, it should be | ||
| treated as application/octet-stream. | ||
| File content when present is a UTF-8 string for the name followed by the length | ||
| in bytes of the content, and then the content octets. | ||
| If present routing code is a UTF-8 string. The routing code is used to | ||
| determine which test backend a test was running on when doing data analysis, | ||
| and to route stdin to the test process if interaction is required. | ||
| Multiplexers SHOULD add a routing code if none is present, and prefix any | ||
| existing routing code with a routing code ('/' separated) if one is already | ||
| present. For example, a multiplexer might label each stream it is multiplexing | ||
| with a simple ordinal ('0', '1' etc), and given an incoming packet with route | ||
| code '3' from stream '0' would adjust the route code when forwarding the packet | ||
| to be '0/3'. | ||
| Following the end of the packet is a CRC-32 checksum of the contents of the | ||
| packet including the signature. | ||
| Example packets | ||
| ~~~~~~~~~~~~~~~ | ||
| Trivial test "foo" enumeration packet, with test id, runnable set, | ||
| status=enumeration. Spaces below are to visually break up signature / flags / | ||
| length / testid / crc32 | ||
| b3 2901 0c 03666f6f 08555f1b | ||
| Version 1 (and 1.1) | ||
| =================== | ||
| Version 1 (and 1.1) are mostly human readable protocols. | ||
| Sample subunit wire contents | ||
| ---------------------------- | ||
| The following:: | ||
| test: test foo works | ||
| success: test foo works | ||
| test: tar a file. | ||
| failure: tar a file. [ | ||
| .. | ||
| ].. space is eaten. | ||
| foo.c:34 WARNING foo is not defined. | ||
| ] | ||
| a writeln to stdout | ||
| When run through subunit2pyunit:: | ||
| .F | ||
| a writeln to stdout | ||
| ======================== | ||
| FAILURE: tar a file. | ||
| ------------------- | ||
| .. | ||
| ].. space is eaten. | ||
| foo.c:34 WARNING foo is not defined. | ||
| Subunit v1 protocol description | ||
| =============================== | ||
| This description is being ported to an EBNF style. Currently its only partly in | ||
| that style, but should be fairly clear all the same. When in doubt, refer the | ||
| source (and ideally help fix up the description!). Generally the protocol is | ||
| line orientated and consists of either directives and their parameters, or | ||
| when outside a DETAILS region unexpected lines which are not interpreted by | ||
| the parser - they should be forwarded unaltered:: | ||
| test|testing|test:|testing: test LABEL | ||
| success|success:|successful|successful: test LABEL | ||
| success|success:|successful|successful: test LABEL DETAILS | ||
| failure: test LABEL | ||
| failure: test LABEL DETAILS | ||
| error: test LABEL | ||
| error: test LABEL DETAILS | ||
| skip[:] test LABEL | ||
| skip[:] test LABEL DETAILS | ||
| xfail[:] test LABEL | ||
| xfail[:] test LABEL DETAILS | ||
| uxsuccess[:] test LABEL | ||
| uxsuccess[:] test LABEL DETAILS | ||
| progress: [+|-]X | ||
| progress: push | ||
| progress: pop | ||
| tags: [-]TAG ... | ||
| time: YYYY-MM-DD HH:MM:SSZ | ||
| LABEL: UTF8* | ||
| NAME: UTF8* | ||
| DETAILS ::= BRACKETED | MULTIPART | ||
| BRACKETED ::= '[' CR UTF8-lines ']' CR | ||
| MULTIPART ::= '[ multipart' CR PART* ']' CR | ||
| PART ::= PART_TYPE CR NAME CR PART_BYTES CR | ||
| PART_TYPE ::= Content-Type: type/sub-type(;parameter=value,parameter=value) | ||
| PART_BYTES ::= (DIGITS CR LF BYTE{DIGITS})* '0' CR LF | ||
| unexpected output on stdout -> stdout. | ||
| exit w/0 or last test completing -> error | ||
| Tags given outside a test are applied to all following tests | ||
| Tags given after a test: line and before the result line for the same test | ||
| apply only to that test, and inherit the current global tags. | ||
| A '-' before a tag is used to remove tags - e.g. to prevent a global tag | ||
| applying to a single test, or to cancel a global tag. | ||
| The progress directive is used to provide progress information about a stream | ||
| so that stream consumer can provide completion estimates, progress bars and so | ||
| on. Stream generators that know how many tests will be present in the stream | ||
| should output "progress: COUNT". Stream filters that add tests should output | ||
| "progress: +COUNT", and those that remove tests should output | ||
| "progress: -COUNT". An absolute count should reset the progress indicators in | ||
| use - it indicates that two separate streams from different generators have | ||
| been trivially concatenated together, and there is no knowledge of how many | ||
| more complete streams are incoming. Smart concatenation could scan each stream | ||
| for their count and sum them, or alternatively translate absolute counts into | ||
| relative counts inline. It is recommended that outputters avoid absolute counts | ||
| unless necessary. The push and pop directives are used to provide local regions | ||
| for progress reporting. This fits with hierarchically operating test | ||
| environments - such as those that organise tests into suites - the top-most | ||
| runner can report on the number of suites, and each suite surround its output | ||
| with a (push, pop) pair. Interpreters should interpret a pop as also advancing | ||
| the progress of the restored level by one step. Encountering progress | ||
| directives between the start and end of a test pair indicates that a previous | ||
| test was interrupted and did not cleanly terminate: it should be implicitly | ||
| closed with an error (the same as when a stream ends with no closing test | ||
| directive for the most recently started test). | ||
| The time directive acts as a clock event - it sets the time for all future | ||
| events. The value should be a valid ISO8601 time. | ||
| The skip, xfail and uxsuccess outcomes are not supported by all testing | ||
| environments. In Python the testttools (https://launchpad.net/testtools) | ||
| library is used to translate these automatically if an older Python version | ||
| that does not support them is in use. See the testtools documentation for the | ||
| translation policy. | ||
| skip is used to indicate a test was discovered but not executed. xfail is used | ||
| to indicate a test that errored in some expected fashion (also know as "TODO" | ||
| tests in some frameworks). uxsuccess is used to indicate and unexpected success | ||
| where a test though to be failing actually passes. It is complementary to | ||
| xfail. | ||
| Hacking on subunit | ||
| ------------------ | ||
| Releases | ||
| ======== | ||
| * Update versions in configure.ac and python/subunit/__init__.py. | ||
| * Update NEWS. | ||
| * Do a make distcheck, which will update Makefile etc. | ||
| * Do a PyPI release: PYTHONPATH=python python setup.py sdist bdist_wheel; twine upload -s dist/* | ||
| * Upload the regular one to LP. | ||
| * Push a tagged commit. | ||
| git push -t origin master:master |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
454808
3.41%67
19.64%7940
1.7%