Registry / testing / python-subunit

python-subunit

JSON →
library1.4.6pypypi✓ verified 25d ago

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.

pip install python-subunit
INSTALL
IMPORT
SIG · PYTHON-SUBUNIT
P
python-subunit
testingpythonv1.4.6
Install
1.8s avg
Import
332ms
Disk
20MB
Pass rate
10/ 10
Env Coverage10 / 10
glibc
3.9–3.13
musl
3.9–3.13
Install & Compatibility
Where this runs
tested against v1.4.6 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
musl
py 3.10–3.95 runs
installs and imports cleanly · install 0.0s · import 0.362s · 21.4MB
glibc
py 3.10–3.95 runs
installs and imports cleanly · install 1.8s · import 0.302s · 23MB
20MB installed
● package 20MB
Code
Verified usage

Verified import paths — ran on the pinned version, not inferred.

TestProtocolClient
✓ from subunit import TestProtocolClient
Used as a unittest.TestResult extension to convert test runs into a Subunit stream.
ProtocolTestCase
✓ from subunit import ProtocolTestCase
An adapter between the Subunit wire protocol and unittest.TestCase, used to translate a stream into a test run.
StreamResult
✓ from subunit.test_results import StreamResult
A base class for test results that can process Subunit streams.

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.

import unittest import io from subunit import TestProtocolClient, ProtocolTestCase from subunit.test_results import StreamResult # 1. Define a simple unittest.TestCase class MyTests(unittest.TestCase): def test_success(self): self.assertTrue(True) def test_failure(self): self.fail("This test explicitly failed") # 2. Capture a test run as a Subunit stream stream_buffer = io.BytesIO() # TestProtocolClient is a TestResult, so a TextTestRunner can use it result_client = TestProtocolClient(stream_buffer) runner = unittest.TextTestRunner(result=result_client) print("--- Running tests and capturing Subunit stream ---") suite = unittest.TestSuite() suite.addTest(MyTests('test_success')) suite.addTest(MyTests('test_failure')) runner.run(suite) subunit_stream_bytes = stream_buffer.getvalue() print("\n--- Captured Subunit Stream (raw bytes) ---") # For demonstration, decode and print the start of the stream if it's text-like try: print(subunit_stream_bytes.decode('utf-8')[:200] + '...' if len(subunit_stream_bytes) > 200 else subunit_stream_bytes.decode('utf-8')) except UnicodeDecodeError: print(subunit_stream_bytes[:200], '... (binary stream)') # 3. Parse the Subunit stream back into unittest results class MyStreamProcessor(StreamResult): def status(self, test_id=None, test_status=None, **kwargs): super().status(test_id=test_id, test_status=test_status, **kwargs) print(f"Processing status for {test_id}: {test_status}") def addSuccess(self, test): print(f"Test passed: {test.id()}") def addFailure(self, test, err): print(f"Test failed: {test.id()} - {err[0].__name__}: {err[1]}") print("\n--- Parsing Subunit stream back into results ---") parse_result = MyStreamProcessor() # ProtocolTestCase can read the stream and feed events to a TestResult ProtocolTestCase.run_with_stream(subunit_stream_bytes, parse_result)
subunit --version
Debug
Known issues
breakingSubunit 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.
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.
affects: <1.2.0 (primarily for v1 default emission), 1.2.0+
gotchaWhen 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.
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.
affects: All versions
Errors
Common errors & fixes
ModuleNotFoundError: No module named 'subunit'
The 'python-subunit' package is not installed in the current Python environment.
fix
pip install python-subunit
TypeError: TestRunner.__init__() missing 1 required positional argument: 'stream'
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.
fix
import sys
from subunit import TestRunner

# For binary subunit v2 output, use a binary stream like sys.stdout.buffer
runner = TestRunner(stream=sys.stdout.buffer)
TypeError: write() argument must be bytes, not str
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).
fix
import sys
from subunit import TestRunner

# Ensure the stream accepts bytes for subunit v2 output
runner = TestRunner(stream=sys.stdout.buffer)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xXX in position Y: invalid start byte
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.
fix
import io
import sys

# When reading a text-based stream, explicitly specify the correct encoding
stream = io.TextIOWrapper(sys.stdin.buffer, encoding='latin-1') # Or 'utf-8', 'cp1252', etc., based on source

# If consuming a raw binary subunit v2 stream, ensure no implicit text decoding happens
# Use a binary-mode stream like sys.stdin.buffer directly, without TextIOWrapper
Upgrade
Version history
1.4.6latest on PyPI · released May 4, 2026
Audit
Dependencies

No dependency data recorded yet.

Agent activity
16 hits · last 30 days
node
14
OpenAI (training)
1
Resources
python-subunit — pip install python-subunit · libregistry