Harden the interop capture analysis and smppsim capture readiness (#89)

* Fail an interop run on an empty capture or a missing bind/bind_resp, and always tear down on analysis failure

* smppsim: wait for the capture sidecar to actually be capturing before node starts

* Clear the stale textdlr capture file too, and tidy the bind-check ordering

* Split the unlink comment so each line matches the unlink it explains
This commit is contained in:
2026-09-06 17:38:14 +02:00
committed by GitHub
parent ad094b3897
commit 9a312e5126
2 changed files with 40 additions and 7 deletions
+14 -2
View File
@@ -86,6 +86,13 @@ services:
smppsim:
condition: service_started
command: ["dumpcap", "-i", "any", "-f", "tcp port 2775", "-w", "/captures/smppsim.pcapng"]
# dumpcap writes the pcapng section header as soon as it opens the interface, before any
# packet arrives, so a non-empty file means the capture is actually running.
healthcheck:
test: ["CMD-SHELL", "test -s /captures/smppsim.pcapng"]
interval: 1s
retries: 30
timeout: 2s
volumes:
- ./interop-tests/captures:/captures
@@ -101,15 +108,20 @@ services:
smppsim-textdlr:
condition: service_started
command: ["dumpcap", "-i", "any", "-f", "tcp port 2775", "-w", "/captures/smppsim-textdlr.pcapng"]
healthcheck:
test: ["CMD-SHELL", "test -s /captures/smppsim-textdlr.pcapng"]
interval: 1s
retries: 30
timeout: 2s
volumes:
- ./interop-tests/captures:/captures
node:
depends_on:
capture:
condition: service_started
condition: service_healthy
capture-textdlr:
condition: service_started
condition: service_healthy
smppsim:
condition: service_healthy
smppsim-textdlr:
+23 -2
View File
@@ -21,6 +21,10 @@ PORT_BY_PEER = {
"smscsim": 2775,
}
# Every scenario in every peer's test file binds before doing anything else, so a capture missing
# either side of that handshake never saw real traffic - the same signal as an empty capture.
BIND_COMMANDS = ("bind_receiver", "bind_transceiver", "bind_transmitter")
# The 33 SMPP commands (SMPP 3.4), by numeric command_id, for the tshark histogram.
COMMAND_NAMES = {
0x00000001: "bind_receiver",
@@ -135,6 +139,9 @@ def analyse_capture(peer: str, port: int) -> tuple[int, dict[str, object]]:
1 for severity in collect(frame, "_ws.expert.severity") if severity == EXPERT_SEVERITY_ERROR
)
has_bind = any(histogram[name] for name in BIND_COMMANDS)
has_bind_resp = any(histogram[f"{name}_resp"] for name in BIND_COMMANDS)
print(f"frames: {len(frames)}")
print("commands:")
for name, count in sorted(histogram.items()):
@@ -142,7 +149,18 @@ def analyse_capture(peer: str, port: int) -> tuple[int, dict[str, object]]:
print(f"malformed: {malformed}")
print(f"expert errors: {expert_errors}")
status = 1 if malformed > 0 or expert_errors > 0 else 0
if not frames:
print("empty capture: no frames decoded", file=sys.stderr)
if not has_bind or not has_bind_resp:
print("no bind/bind_resp pair in capture", file=sys.stderr)
status = 1 if any((
not frames,
not has_bind,
not has_bind_resp,
malformed > 0,
expert_errors > 0,
)) else 0
return status, {
"commands": dict(histogram),
@@ -169,6 +187,8 @@ def main() -> int:
# root; overwriting one from a previous run then fails, since root here has no DAC override
# either - so the stale file has to go before a fresh one can be written in its place.
(CAPTURES_DIR / f"{args.peer}.pcapng").unlink(missing_ok=True)
# Also clears smppsim's textdlr capture so its healthcheck can't pass against a stale leftover.
(CAPTURES_DIR / f"{args.peer}-textdlr.pcapng").unlink(missing_ok=True)
tests = sh(compose_cmd(
args.peer, "run", "--rm", "--use-aliases", "node",
@@ -177,8 +197,9 @@ def main() -> int:
sh(compose_cmd(args.peer, "stop", "capture"))
fix_capture_ownership()
try:
analysis_status, _ = analyse_capture(args.peer, port)
finally:
if not args.keep:
sh(compose_cmd(args.peer, "down", "-v"))