{"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.","language":"python","status":"active","last_verified":"Fri May 15","install":{"commands":["pip install python-subunit"],"cli":{"name":"subunit","version":"sh: 1: subunit: not found"}},"imports":["from subunit import TestProtocolClient","from subunit import ProtocolTestCase","from subunit.test_results import StreamResult"],"auth":{"required":false,"env_vars":[]},"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.","tag":null,"tag_description":null,"last_tested":null,"results":[]},"compatibility":{"tag":null,"tag_description":null,"last_tested":"2026-05-15","installed_version":"1.4.4","pypi_latest":"1.4.6","is_stale":true,"summary":{"python_range":"3.10–3.9","success_rate":100,"avg_install_s":1.9,"avg_import_s":0.33,"wheel_type":"wheel"},"results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"python-subunit","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":null,"import_time_s":0.29,"mem_mb":7.4,"disk_size":"21.4M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"python-subunit","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":1.8,"import_time_s":0.19,"mem_mb":7.4,"disk_size":"23M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"python-subunit","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":null,"import_time_s":0.42,"mem_mb":8.3,"disk_size":"23.8M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"python-subunit","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":1.9,"import_time_s":0.34,"mem_mb":8.3,"disk_size":"25M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"python-subunit","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":null,"import_time_s":0.39,"mem_mb":8.3,"disk_size":"15.6M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"python-subunit","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":1.7,"import_time_s":0.4,"mem_mb":8.3,"disk_size":"17M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"python-subunit","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":null,"import_time_s":0.4,"mem_mb":9.5,"disk_size":"15.3M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"python-subunit","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":1.8,"import_time_s":0.37,"mem_mb":9.5,"disk_size":"17M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"python-subunit","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":null,"import_time_s":0.29,"mem_mb":6.8,"disk_size":"20.0M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"python-subunit","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":2.1,"import_time_s":0.26,"mem_mb":6.8,"disk_size":"20M"}]}}