diff --git a/docs/RELEASE_RESOURCE_EVIDENCE.md b/docs/RELEASE_RESOURCE_EVIDENCE.md index a84b615a4..7c20b5749 100644 --- a/docs/RELEASE_RESOURCE_EVIDENCE.md +++ b/docs/RELEASE_RESOURCE_EVIDENCE.md @@ -42,7 +42,7 @@ python3 -m unittest discover -s scripts/release_control/internal -p 'release_pre bash -n scripts/release-preflight-worker.sh ``` -## Stress-test event window +## Bounded API test event windows Rehearsal backend Go output is streamed with `-json` and decoded by `release-go-test-events.py`. Every Output string is retained verbatim (including @@ -50,7 +50,8 @@ verbose test logs, skips and package summaries); stderr remains on stderr. Shell pipefail retains a failing Go exit. Invalid event input is drained, retained and fails the reader rather than quietly losing diagnostics. -Only the exact API `TestMultiTenant_ConcurrentAPIStress` lifecycle receives +Only the exact API `TestMultiTenant_ConcurrentAPIStress` and +`TestSLO_MetricsHistoryStore` lifecycles receive `RELEASE_GO_TEST_EVENT` records with Go's event Time, a separately labelled receipt timestamp and the same allowlisted resource snapshot. Resource time is collection time, not the Go event time. Sampling and verbose streaming add @@ -67,3 +68,10 @@ terminal events. Cgroup ancestry remains shared context, not per-test CPU usage. ```sh python3 -m unittest discover -s scripts/release_control/internal -p 'release_go_test_events_test.py' -v ``` + +The store-history SLO marker addresses a separate timing gap in failed rehearsal +20260908T221214Z: its completed latency assertion was buffered until package +output, so launcher resource samples cannot locate the actual test interval. +Neither parent throttling over the backend window nor a later passing isolated +sample would establish the cause. The marker observes test lifecycle only; it +adds no database inspection, replay or threshold change. diff --git a/scripts/release-go-test-events.py b/scripts/release-go-test-events.py index 6d9dd46c7..6ef21a2f4 100644 --- a/scripts/release-go-test-events.py +++ b/scripts/release-go-test-events.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Render streamed go test -json output and retain bounded stress-test timing. +"""Render streamed go test -json output and retain bounded API timing. Go's event Time is distinct from receipt time and the resource sample time. Samples share the worker cgroup; they do not establish per-test CPU usage or @@ -18,7 +18,10 @@ spec = importlib.util.spec_from_file_location( resources = importlib.util.module_from_spec(spec) spec.loader.exec_module(resources) -TARGET = "TestMultiTenant_ConcurrentAPIStress" +TARGETS = frozenset({ + "TestMultiTenant_ConcurrentAPIStress", + "TestSLO_MetricsHistoryStore", +}) PACKAGE = "github.com/rcourtman/pulse-go-rewrite/internal/api" ACTIONS = {"run", "pause", "cont", "pass", "fail", "skip"} @@ -40,7 +43,7 @@ def render(source, destination): invalid = True continue destination.write(output) - if (event.get("Package") == PACKAGE and event.get("Test") == TARGET + if (event.get("Package") == PACKAGE and event.get("Test") in TARGETS and event.get("Action") in ACTIONS): evidence = {key: event[key] for key in ("Time", "Action", "Package", "Test", "Elapsed") if key in event} diff --git a/scripts/release_control/internal/release_go_test_events_test.py b/scripts/release_control/internal/release_go_test_events_test.py index d8c552bd5..33fdee9e4 100644 --- a/scripts/release_control/internal/release_go_test_events_test.py +++ b/scripts/release_control/internal/release_go_test_events_test.py @@ -19,10 +19,10 @@ spec.loader.exec_module(events) class EventsTest(unittest.TestCase): def test_output_and_bounded_evidence(self): data = [dict(Time='2026-09-08T19:00:00Z', Action=action, - Package=events.PACKAGE, Test=events.TARGET, Elapsed=2.0) + Package=events.PACKAGE, Test="TestMultiTenant_ConcurrentAPIStress", Elapsed=2.0) for action in ('run', 'pause', 'cont', 'pass', 'fail', 'skip')] data += [dict(Action='output', Output='FAIL\tpackage\t2s\n'), - dict(Action='run', Package='other', Test=events.TARGET), + dict(Action='run', Package='other', Test="TestMultiTenant_ConcurrentAPIStress"), dict(Action='run', Package=events.PACKAGE, Test='TestOther')] output = io.StringIO() with patch.object(events.resources, 'snapshot', return_value={'unix_time_ns': 456}) as sample: @@ -36,8 +36,29 @@ class EventsTest(unittest.TestCase): self.assertGreater(evidence['received_unix_time_ns'], 456) self.assertEqual(evidence['resources'], {'unix_time_ns': 456}) + def test_slo_exact_target_lifecycle_and_exclusions(self): + data = [dict(Action=action, Package=events.PACKAGE, + Test='TestSLO_MetricsHistoryStore') + for action in ('run', 'pause', 'cont', 'pass', 'fail', 'skip')] + data += [dict(Action='run', Package=package, Test=test) + for package, test in ( + ('other', 'TestSLO_MetricsHistoryStore'), + (events.PACKAGE, 'TestSLO_MetricsHistoryStore/subtest'), + (events.PACKAGE, 'TestSLO_MetricsHistoryStoreExtra'), + (events.PACKAGE, 'TestSLO_MetricsHistoryMemory'))] + output = io.StringIO() + with patch.object(events.resources, 'snapshot', return_value={}) as sample: + self.assertEqual(events.render(io.StringIO(''.join( + json.dumps(x) + '\n' for x in data)), output), 0) + self.assertEqual(sample.call_count, 6) + records = [json.loads(line.removeprefix('RELEASE_GO_TEST_EVENT ')) + for line in output.getvalue().splitlines()] + self.assertEqual([r['Action'] for r in records], + ['run', 'pause', 'cont', 'pass', 'fail', 'skip']) + self.assertTrue(all(r['Test'] == 'TestSLO_MetricsHistoryStore' for r in records)) + def test_unavailable_snapshot_does_not_change_verdict(self): - event = dict(Action='fail', Package=events.PACKAGE, Test=events.TARGET) + event = dict(Action='fail', Package=events.PACKAGE, Test="TestMultiTenant_ConcurrentAPIStress") output = io.StringIO() with patch.object(events.resources, 'snapshot', side_effect=OSError): self.assertEqual(events.render(io.StringIO(json.dumps(event)+'\n'), output), 0) @@ -59,6 +80,7 @@ func TestMultiTenant_ConcurrentAPIStress(t *testing.T) { t.Log("synthetic log") if os.Getenv("FIXTURE_FAIL") == "1" { t.Fatal("synthetic failure") } } +func TestSLO_MetricsHistoryStore(t *testing.T) { t.Log("synthetic SLO marker") } func TestSkipped(t *testing.T) { t.Skip("synthetic skip") } ''') for fail in ('0', '1'): @@ -72,7 +94,10 @@ func TestSkipped(t *testing.T) { t.Skip("synthetic skip") } for line in result.stdout.splitlines() if line.startswith('RELEASE_GO_TEST_EVENT ')] self.assertEqual([r['Action'] for r in records], - ['run', 'fail' if fail == '1' else 'pass']) + ['run', 'fail' if fail == '1' else 'pass', 'run', 'pass']) + self.assertEqual([r['Test'] for r in records], + ['TestMultiTenant_ConcurrentAPIStress'] * 2 + + ['TestSLO_MetricsHistoryStore'] * 2) self.assertTrue(all(r.get('Time') for r in records)) self.assertIn('synthetic log', result.stdout) self.assertIn('--- SKIP: TestSkipped', result.stdout)