{"id":4733,"library":"python-subunit","title":"Python Subunit","description":"Subunit is a streaming protocol for test results, designed to be easily generated and parsed. The `python-subunit` library provides extensions to Python's `unittest` framework, enabling the generation and consumption of Subunit streams. It facilitates test aggregation, archiving, isolation, and grid testing across different languages and machines. The library supports both Version 1 (human-readable) and Version 2 (binary) of the protocol, with a focus on Version 2 for improved robustness and multiplexing. It is currently at version 1.4.5 and maintained with a moderate release cadence.","status":"active","version":"1.4.5","language":"python","source_language":"en","source_url":"https://github.com/testing-cabal/subunit","tags":["testing","unittest","protocol","test-runner","stream"],"install":[{"cmd":"pip install python-subunit","lang":"bash","label":"Install latest version"}],"dependencies":[],"imports":[{"note":"Used as a unittest.TestResult extension to convert test runs into a Subunit stream.","symbol":"TestProtocolClient","correct":"from subunit import TestProtocolClient"},{"note":"An adapter between the Subunit wire protocol and unittest.TestCase, used to translate a stream into a test run.","symbol":"ProtocolTestCase","correct":"from subunit import ProtocolTestCase"},{"note":"A base class for test results that can process Subunit streams.","symbol":"StreamResult","correct":"from subunit.test_results import StreamResult"}],"quickstart":{"code":"import unittest\nimport io\nfrom subunit import TestProtocolClient, ProtocolTestCase\nfrom subunit.test_results import StreamResult\n\n# 1. Define a simple unittest.TestCase\nclass MyTests(unittest.TestCase):\n    def test_success(self):\n        self.assertTrue(True)\n\n    def test_failure(self):\n        self.fail(\"This test explicitly failed\")\n\n# 2. Capture a test run as a Subunit stream\nstream_buffer = io.BytesIO()\n# TestProtocolClient is a TestResult, so a TextTestRunner can use it\nresult_client = TestProtocolClient(stream_buffer)\nrunner = unittest.TextTestRunner(result=result_client)\n\nprint(\"--- Running tests and capturing Subunit stream ---\")\nsuite = unittest.TestSuite()\nsuite.addTest(MyTests('test_success'))\nsuite.addTest(MyTests('test_failure'))\nrunner.run(suite)\n\nsubunit_stream_bytes = stream_buffer.getvalue()\nprint(\"\\n--- Captured Subunit Stream (raw bytes) ---\")\n# For demonstration, decode and print the start of the stream if it's text-like\ntry:\n    print(subunit_stream_bytes.decode('utf-8')[:200] + '...' if len(subunit_stream_bytes) > 200 else subunit_stream_bytes.decode('utf-8'))\nexcept UnicodeDecodeError:\n    print(subunit_stream_bytes[:200], '... (binary stream)')\n\n# 3. Parse the Subunit stream back into unittest results\nclass MyStreamProcessor(StreamResult):\n    def status(self, test_id=None, test_status=None, **kwargs):\n        super().status(test_id=test_id, test_status=test_status, **kwargs)\n        print(f\"Processing status for {test_id}: {test_status}\")\n\n    def addSuccess(self, test):\n        print(f\"Test passed: {test.id()}\")\n\n    def addFailure(self, test, err):\n        print(f\"Test failed: {test.id()} - {err[0].__name__}: {err[1]}\")\n\n\nprint(\"\\n--- Parsing Subunit stream back into results ---\")\nparse_result = MyStreamProcessor()\n# ProtocolTestCase can read the stream and feed events to a TestResult\nProtocolTestCase.run_with_stream(subunit_stream_bytes, parse_result)\n","lang":"python","description":"This quickstart demonstrates how to use `python-subunit` to both generate and consume a Subunit stream. It defines a standard `unittest.TestCase`, captures its execution output using `subunit.TestProtocolClient` into a byte stream, and then uses `subunit.ProtocolTestCase` with a custom `StreamResult` to parse and report on that stream. This illustrates the core functionality of converting `unittest` results to Subunit and back."},"warnings":[{"fix":"Use the provided `subunit-1to2` and `subunit-2to1` command-line filters to convert streams when interacting with systems that expect a different protocol version. Ensure all components in your testing pipeline are using compatible protocol versions or implement conversion steps.","message":"Subunit has two major protocol versions: v1 (human-readable) and v2 (binary). While v2 is more robust and intended to supersede v1, `python-subunit`'s bundled tools *only* accept and emit v2. Interoperating with older third-party libraries that use v1 requires explicit conversion filters (`subunit-1to2` and `subunit-2to1`). This can cause compatibility issues if not handled.","severity":"breaking","affected_versions":"<1.2.0 (primarily for v1 default emission), 1.2.0+"},{"fix":"Ensure that any custom `unittest.TestResult` implementations or third-party test runners are either compatible with `subunit`'s extensions or are explicitly configured to handle the additional data. Review the `subunit.__init__.py` source for details on the `TestResult` extensions.","message":"When extending `unittest.TestResult` objects with `python-subunit`'s extensions (e.g., for tags, extra details, timestamps), `TestResult` objects that do *not* implement these extension methods will either lose fidelity or discard the extended data without raising an error. This can lead to silent data loss if the consuming `TestResult` is not fully compatible with the `subunit` extensions.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"search_vec":"'1':57 '1.4.5':84 '2':63,73 'across':47 'aggreg':41 'archiv':42 'binari':64 'cadenc':91 'consumpt':34 'current':81 'design':11 'differ':48 'easili':14 'enabl':30 'extens':24 'facilit':39 'focus':70 'framework':29 'generat':15,32 'grid':45 'human':59 'human-read':58 'improv':75 'isol':43 'languag':49 'librari':22,53 'machin':51 'maintain':86 'moder':89 'multiplex':78 'pars':17 'protocol':7,67,94 'provid':23 'python':1,20,26 'python-subunit':19 'readabl':60 'releas':90 'result':10 'robust':76 'runner':97 'stream':6,37,98 'subunit':2,3,21,36 'support':54 'test':9,40,46,92,96 'test-runn':95 'unittest':28,93 'version':56,62,72,83","created_at":"2026-04-12T14:05:17.279822+00:00","updated_at":"2026-04-16T20:29:47.288448+00:00","problems":[{"fix":"pip install python-subunit","cause":"The 'python-subunit' package is not installed in the current Python environment.","error":"ModuleNotFoundError: No module named 'subunit'"},{"fix":"import sys\nfrom subunit import TestRunner\n\n# For binary subunit v2 output, use a binary stream like sys.stdout.buffer\nrunner = TestRunner(stream=sys.stdout.buffer)","cause":"The `subunit.TestRunner` class requires a `stream` object (e.g., a file-like object) to be passed during initialization, specifying where to write the test results.","error":"TypeError: TestRunner.__init__() missing 1 required positional argument: 'stream'"},{"fix":"import sys\nfrom subunit import TestRunner\n\n# Ensure the stream accepts bytes for subunit v2 output\nrunner = TestRunner(stream=sys.stdout.buffer)","cause":"This error occurs when a `python-subunit` component, such as `TestRunner` (which produces binary subunit v2 output), attempts to write bytes to a stream that is configured to only accept string data (e.g., `sys.stdout` without `.buffer`, or a `io.StringIO` object).","error":"TypeError: write() argument must be bytes, not str"},{"fix":"import io\nimport sys\n\n# When reading a text-based stream, explicitly specify the correct encoding\nstream = io.TextIOWrapper(sys.stdin.buffer, encoding='latin-1') # Or 'utf-8', 'cp1252', etc., based on source\n\n# If consuming a raw binary subunit v2 stream, ensure no implicit text decoding happens\n# Use a binary-mode stream like sys.stdin.buffer directly, without TextIOWrapper","cause":"This error typically occurs when `python-subunit` attempts to interpret a byte stream as UTF-8 text but encounters bytes that are not valid in UTF-8, often when processing a subunit v1 (text-based) stream, a mixed-encoding stream, or if the system's default encoding differs from the stream's actual encoding.","error":"UnicodeDecodeError: 'utf-8' codec can't decode byte 0xXX in position Y: invalid start byte"}],"ecosystem":"pypi","meta_description":null,"install_score":null,"quickstart_score":null,"quickstart_tag":null,"pypi_latest":"1.4.6","cli_name":"subunit","cli_version":"sh: 1: subunit: not found","type":"library","homepage":"http://launchpad.net/subunit","github":"https://github.com/testing-cabal/subunit","docs":null,"changelog":null,"pypi":"https://pypi.org/project/python-subunit/","npm":null,"openapi_spec":null,"status_page":null,"smithery":null,"categories":["testing","serialization","communication"],"base_url":null,"auth_type":null,"provenance":{"verified_status":"passing","verified_at":"2026-06-28","last_verified":"2026-08-30","next_check":"2026-07-28","install_tag":null}}