#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later """Synthetic tests for Phase-1.0AB nonblocking lifecycle traces.""" from __future__ import annotations import argparse import importlib.util from pathlib import Path import sys def load(path: Path): spec = importlib.util.spec_from_file_location("phase10ab_trace", path) assert spec and spec.loader module = importlib.util.module_from_spec(spec); sys.modules[spec.name] = module spec.loader.exec_module(module); return module def main() -> int: parser = argparse.ArgumentParser(); parser.add_argument("--root", type=Path, required=True) args = parser.parse_args(); module = load(args.root.resolve() / "tools/phase10ab_nonblocking_trace_model.py") def event(op, at, value=0): return module.TraceEvent(op, at, value) def valid(pending=True, sends=(2, 3), waits=()): values = [event(module.RECEIPT_CREATED, 0), event(module.SOCKET_CREATED, 0), event(module.SET_NONBLOCKING, 0)] if pending: values += [event(module.CONNECT_PENDING, .1), event(module.READY_WRITE, .2), event(module.SO_ERROR_ZERO, .2)] else: values += [event(module.CONNECT_IMMEDIATE, .1)] at = .3 for count in sends: values += [event(module.READY_WRITE, at), event(module.SEND_BYTES, at, count)]; at += .1 for op in waits: values.append(event(op, at)); at += .1 values += [event(module.READY_READ, at), event(module.RECV_BYTES, at, 100), event(module.DEADLINE_REACHED, 10), event(module.SANITIZER_ACCEPTED, 10), event(module.LOCAL_CLOSE, 10), event(module.OUTPUT_CREATED, 10)] return tuple(values) def expect_failure(function): try: function() except module.TraceModelError: return raise RuntimeError("invalid trace accepted") cases = [] def case(name): def register(function): cases.append((name, function)); return function return register @case("01 pending connect complete trace succeeds") def _(): result = module.assess_nonblocking_trace(5, 10, valid()); assert result.batch_bytes_sent == 5 @case("02 immediate connect complete trace succeeds") def _(): assert module.assess_nonblocking_trace(5, 10, valid(False)).classification == "OFFLINE_NONBLOCKING_SEQUENCE_FEASIBLE" @case("03 partial sends require repeated readiness") def _(): assert module.assess_nonblocking_trace(5, 10, valid(sends=(1, 1, 3))).complete_send_loop @case("04 interrupted waits are recomputed") def _(): assert module.assess_nonblocking_trace(5, 10, valid(waits=(module.WAIT_INTERRUPTED,))).deadline_only_completion @case("05 timeout before deadline is not completion") def _(): assert module.assess_nonblocking_trace(5, 10, valid(waits=(module.WAIT_TIMEOUT,))).local_close_observed @case("06 receipt must precede creation") def _(): expect_failure(lambda: module.assess_nonblocking_trace(5, 10, valid()[1:])) @case("07 nonblocking must precede connect") def _(): trace = tuple(e for e in valid() if e.operation != module.SET_NONBLOCKING); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace)) @case("08 pending connect requires readiness") def _(): trace = tuple(e for e in valid() if e.operation != module.READY_WRITE or e.at_seconds != .2); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace)) @case("09 pending connect requires SO_ERROR") def _(): trace = tuple(e for e in valid() if e.operation != module.SO_ERROR_ZERO); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace)) @case("10 send requires write readiness") def _(): trace = tuple(e for e in valid() if not (e.operation == module.READY_WRITE and e.at_seconds == .3)); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace)) @case("11 send cannot exceed exact batch") def _(): expect_failure(lambda: module.assess_nonblocking_trace(5, 10, valid(sends=(6,)))) @case("12 incomplete send fails") def _(): expect_failure(lambda: module.assess_nonblocking_trace(5, 10, valid(sends=(2, 2)))) @case("13 receive requires read readiness") def _(): trace = tuple(e for e in valid() if e.operation != module.READY_READ); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace)) @case("14 receive bound is enforced") def _(): trace = list(valid()); index = next(i for i,e in enumerate(trace) if e.operation == module.RECV_BYTES); trace[index] = event(module.RECV_BYTES, trace[index].at_seconds, 65537); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, tuple(trace))) @case("15 EOF never completes") def _(): trace = list(valid()); index = next(i for i,e in enumerate(trace) if e.operation == module.RECV_BYTES); trace[index] = event(module.RECV_EOF, trace[index].at_seconds); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, tuple(trace))) @case("16 early deadline fails") def _(): trace = tuple(event(e.operation, 9 if e.operation in {module.DEADLINE_REACHED,module.SANITIZER_ACCEPTED,module.LOCAL_CLOSE,module.OUTPUT_CREATED} else e.at_seconds, e.value) for e in valid()); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace)) @case("17 data at deadline fails") def _(): trace = list(valid()); index = next(i for i,e in enumerate(trace) if e.operation == module.RECV_BYTES); trace[index] = event(module.RECV_BYTES, 10, 100); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, tuple(trace))) @case("18 deadline requires received data") def _(): trace = tuple(e for e in valid() if e.operation not in {module.READY_READ,module.RECV_BYTES}); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace)) @case("19 sanitizer must follow deadline") def _(): trace = tuple(e for e in valid() if e.operation != module.DEADLINE_REACHED); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace)) @case("20 close must follow sanitizer") def _(): trace = tuple(e for e in valid() if e.operation != module.SANITIZER_ACCEPTED); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace)) @case("21 output must follow close") def _(): trace = tuple(e for e in valid() if e.operation != module.LOCAL_CLOSE); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace)) @case("22 backward time fails") def _(): trace = list(valid()); trace[2] = event(module.SET_NONBLOCKING, -.0); trace[1] = event(module.SOCKET_CREATED, .1); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, tuple(trace))) @case("23 trace event bound is enforced") def _(): expect_failure(lambda: module.assess_nonblocking_trace(5, 10, tuple(event(module.RECEIPT_CREATED, 0) for _ in range(513)))) @case("24 result never claims device proof") def _(): result = module.assess_nonblocking_trace(5, 10, valid()); assert not result.device_behavior_proven and not result.live_transport_present @case("25 no live API exists") def _(): assert not ({"connect", "send", "recv", "main"} & set(dir(module))) failures=[] for name,function in cases: try: function(); print(f"PASS {name}") except Exception as error: failures.append(f"{name}: {error}"); print(f"FAIL {name}: {error}") if failures: return 1 print(f"Phase-1.0AB nonblocking-trace tests passed: {len(cases)}"); return 0 if __name__ == "__main__": raise SystemExit(main())