Write, run, review, and debug end-to-end tests for rtp2httpd. ALWAYS use this skill when the user: (1) wants to write or optimize e2e/integration tests, (2) asks to run tests or mentions run-e2e.sh, uv, pytest, collect-only, xdist, markers, fixtures, or parallelism, (3) needs to debug failing, flaky, slow, or hanging e2e tests, (4) mentions any file under e2e/ or scripts/run-e2e.sh, (5) mentions MockRTSP*, MockHTTP*, MockFCC*, MockSTUN*, R2HProcess, MulticastSender, helper APIs, or test fixtures, (6) asks about multicast, RTSP, HTTP proxy, FCC, STUN, M3U, EPG, URL template, or zerocopy test coverage in rtp2httpd, or (7) uses Chinese phrases such as "端到端测试", "跑测试", or "e2e 测试" in this repo.
77
96%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
rtp2httpd e2e tests live in e2e/. They run the real build/rtp2httpd binary against mock
servers and assert behavior over HTTP, RTSP, UDP, multicast, Unix sockets, and generated playlists.
Run commands from the project root. Prefer ./scripts/run-e2e.sh over invoking pytest directly.
The wrapper uses uv run pytest, resolves bare test filenames to e2e/<name>, and defaults to
xdist with --dist loadscope.
./scripts/run-e2e.sh
./scripts/run-e2e.sh -p 1
./scripts/run-e2e.sh --parallel=4
./scripts/run-e2e.sh test_m3u.py
./scripts/run-e2e.sh e2e/test_multicast.py
./scripts/run-e2e.sh -k "etag"
./scripts/run-e2e.sh -m "not multicast"
./scripts/run-e2e.sh -x
./scripts/run-e2e.sh --co
./scripts/run-e2e.sh --collect-onlyBuild build/rtp2httpd before running real tests:
cmake -B build -DCMAKE_BUILD_TYPE=Release -DENABLE_AGGRESSIVE_OPT=ON
cmake --build build -j$(getconf _NPROCESSORS_ONLN)Collect-only mode is the exception: ./scripts/run-e2e.sh --co and --collect-only must work
without build/rtp2httpd.
Use direct pytest only for static or collection checks:
uv run ruff check e2e
uv run pytest e2e --collect-only -qe2e/
├── conftest.py
├── test_m3u.py
├── test_epg.py / test_pages.py / test_auth.py / test_config.py / test_error.py
├── test_multicast.py / test_fcc.py / test_zerocopy.py
├── test_http_proxy*.py
├── test_rtsp_*.py
├── test_url_template_http.py
├── test_url_template_rtsp.py
├── test_url_template_m3u.py
├── test_url_template_placeholders.py
└── helpers/
├── __init__.py
├── config.py
├── http.py
├── ports.py
├── r2h_process.py
├── mock_http.py / mock_rtsp.py / mock_fcc.py / mock_stun.py
└── rtp.pyKeep URL template coverage split by resolver responsibility. Do not recreate a monolithic
test_url_template.py; place new cases in the matching HTTP, RTSP, M3U, or placeholder file.
helpers; update both imports and __all__ in helpers/__init__.py
when adding a helper.find_free_port(), find_free_udp_port(), or
find_free_udp_port_pair().R2HProcess fixtures when tests share config and args.R2HProcess only for mutually exclusive configs, port-range behavior, timeout or
log-capture cases, Unix socket cases, or tests whose process state must be isolated.--dist loadscope avoid one-worker tail latency.@pytest.mark.parametrize for input/expected matrices. Keep separate tests only for genuinely
different workflows or complex end-to-end paths.e2e/helpers/.Use accurate markers so filtered runs mean what they say. Keep pyproject.toml marker registration
in sync with tests.
@pytest.mark.multicast
@pytest.mark.rtsp
@pytest.mark.fcc
@pytest.mark.http_proxy
@pytest.mark.slowUse module-level pytestmark = pytest.mark.<marker> when the whole file has one requirement.
Use class-level markers when only one class needs the capability.
Port and process helpers:
find_free_port()find_free_udp_port()find_free_udp_port_pair()wait_for_port(port, host="127.0.0.1", timeout=5.0)wait_for_unix_socket(path, timeout=5.0)R2HProcess(binary, port, extra_args=None, config_content=None, capture_log=False, listen=None)make_m3u_rtsp_config(port, rtsp_port, service_name="Test RTSP")Config and file helpers:
build_config(port, global_lines=None, services_content=None) -> strbuild_single_service_config(port, service_name, service_url, global_lines=None, extinf_attrs=None) -> strwrite_temp_file(data, suffix="", prefix="r2h_test_") -> strHTTP helpers:
http_get(host, port, path, timeout=5.0, headers=None)http_request(host, port, method, path, timeout=5.0, headers=None, body=None)stream_get(host, port, path, read_bytes=8192, timeout=10.0, headers=None)unix_http_get(socket_path, path, timeout=5.0, headers=None)unix_http_request(socket_path, method, path, timeout=5.0, headers=None, body=None)get_header(headers, name, default="")assert_etag_cache_behavior(host, port, path)get_upstream_path(upstream)extract_catchup_source(playlist_text, channel_name)Mock servers:
MockHTTPUpstream(routes={...})MockHTTPUpstreamSilent()MockRTSPServer(...)MockRTSPServerUDP()MockRTSPServerSilent()MockRTSPServerNoMedia()MockRTSPServerNoTeardownResponse()MockFCCServer()MockSTUNServer(port=0, mapped_port=0, mapped_ip="1.2.3.4", silent=False)MulticastSender(...)make_rtp_packet(...)Preferred shared process fixture:
@pytest.fixture(scope="module")
def shared_r2h(r2h_binary):
port = find_free_port()
config = build_single_service_config(
port,
"Channel One",
"rtp://239.0.0.1:1234",
global_lines=["maxclients = 10"],
)
r2h = R2HProcess(r2h_binary, port, config_content=config)
r2h.start()
yield r2h
r2h.stop()Per-test process when isolation is required:
def test_custom_config(r2h_binary):
port = find_free_port()
config = build_config(port, global_lines=["maxclients = 1"])
r2h = R2HProcess(r2h_binary, port, config_content=config)
try:
r2h.start()
status, _, _ = http_get("127.0.0.1", port, "/status")
assert status == 200
finally:
r2h.stop()HTTP upstream path assertion:
upstream = MockHTTPUpstream(routes={"/archive/1.ts": {"status": 200, "body": b"ok"}})
upstream.start()
try:
status, _, _ = http_get("127.0.0.1", shared_r2h.port, f"/http/127.0.0.1:{upstream.port}/archive/1.ts")
assert status == 200
assert get_upstream_path(upstream) == "/archive/1.ts"
finally:
upstream.stop()ETag behavior:
assert_etag_cache_behavior("127.0.0.1", shared_r2h.port, "/playlist.m3u")uv run pytest e2e/ -v -n auto --dist loadscope.-p 1 disables xdist and is best for debugging logs, hangs, and order-sensitive failures.loadscope less effective. Split by functional area and keep fixtures
scoped to the smallest stable shared config.-p 1 -x, then inspect fixture
scope, ports, shared mock state, and filesystem temp paths.Reproduce narrowly:
./scripts/run-e2e.sh -p 1 -k "test_name" -xConfirm binary and collection:
ls -la build/rtp2httpd
./scripts/run-e2e.sh --coCheck common failure causes:
pyproject.toml.helpers/__init__.py.stream_get() timeout too short for streaming or multicast.-M http://... or -M file://... needs a short async wait.Run at least:
uv run ruff check e2e
uv run pytest e2e --collect-only -q
./scripts/run-e2e.sh --coFor URL template changes, run the focused split files:
./scripts/run-e2e.sh -p 1 test_url_template_http.py
./scripts/run-e2e.sh -p 1 test_url_template_rtsp.py
./scripts/run-e2e.sh -p 1 test_url_template_m3u.py
./scripts/run-e2e.sh -p 1 test_url_template_placeholders.pyFor marker or scheduling changes, run:
./scripts/run-e2e.sh -m "not multicast and not slow"
./scripts/run-e2e.sh -m "not multicast"For broad e2e changes, build the binary and run the full suite:
./scripts/run-e2e.shf63adb0
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.