From c60a68d1cf319eca9b6dfe71c0f57818dc5bf309 Mon Sep 17 00:00:00 2001 From: cabbage Date: Wed, 2 Sep 2026 06:14:16 +0000 Subject: [PATCH] test: add server session integration harness --- docs/testing.md | 16 ++++++++++---- test/harness/harness.go | 46 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/docs/testing.md b/docs/testing.md index 425111e..696a543 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -15,10 +15,12 @@ Run focused unit tests with: scripts/test-unit --package ./internal/domain --run UUIDv7 --race ``` -The integration harness provides the Phase 0 `sample` suite and the incremental -Phase 2 `store` suite. The latter uses a real temporary SQLite database in WAL -mode and a real segment/audit filesystem; it does not mock the persistence -boundary: +The integration harness provides the Phase 0 `sample` suite, the incremental +Phase 2 `store` suite, and the incremental Phase 3 `server-session` suite. +The storage suite uses a real temporary SQLite database in WAL mode and a real +segment/audit filesystem. The session suite uses a real HTTP/WebSocket listener, +binary protobuf frames, SQLite fencing, and the race detector; neither mocks its +respective durability or wire boundary: ```sh scripts/test-env doctor @@ -36,6 +38,12 @@ scripts/test-env logs --run-id store-smoke scripts/test-env collect --run-id store-smoke scripts/test-env reset --run-id store-smoke scripts/test-env purge --run-id store-smoke + +scripts/test-integration --suite server-session --run-id session-smoke +scripts/test-env logs --run-id session-smoke +scripts/test-env collect --run-id session-smoke +scripts/test-env reset --run-id session-smoke +scripts/test-env purge --run-id session-smoke ``` Suite output is capped at 1 MiB and stored as `artifacts/suite.log`. A failed diff --git a/test/harness/harness.go b/test/harness/harness.go index da19743..78be4c9 100644 --- a/test/harness/harness.go +++ b/test/harness/harness.go @@ -126,8 +126,8 @@ func (h *harness) integration(ctx context.Context, args []string) error { if err := flags.Parse(args); err != nil { return err } - if *suite != "sample" && *suite != "store" { - return fmt.Errorf("suite %q is not implemented yet; available: sample, store", *suite) + if *suite != "sample" && *suite != "store" && *suite != "server-session" { + return fmt.Errorf("suite %q is not implemented yet; available: sample, store, server-session", *suite) } var current *manifest @@ -166,9 +166,18 @@ func (h *harness) integration(ctx context.Context, args []string) error { if err := h.appendJournal(current.RunID, journalEntry{At: h.now(), Step: "invariant", Status: "passed", Detail: "manifest ownership and journal durability verified"}); err != nil { return err } - } else if err := h.runStoreSuite(ctx, current); err != nil { - _ = h.transition(current, "failed", "store-failed", err.Error()) - return err + } else { + var suiteErr error + switch *suite { + case "store": + suiteErr = h.runStoreSuite(ctx, current) + case "server-session": + suiteErr = h.runServerSessionSuite(ctx, current) + } + if suiteErr != nil { + _ = h.transition(current, "failed", *suite+"-failed", suiteErr.Error()) + return suiteErr + } } return h.transition(current, "completed", *suite+"-complete", *suite+" integration run completed") } @@ -196,6 +205,33 @@ func (h *harness) runStoreSuite(ctx context.Context, current *manifest) error { return h.appendJournal(current.RunID, journalEntry{At: h.now(), Step: "store-real-sqlite", Status: "passed", Detail: "real SQLite/WAL and filesystem cases passed"}) } +func (h *harness) runServerSessionSuite(ctx context.Context, current *manifest) error { + return h.runGoSuite(ctx, current, "server-session-real-websocket", "running real HTTP/WebSocket, protobuf, SQLite, and fencing cases", "./internal/server/session") +} + +func (h *harness) runGoSuite(ctx context.Context, current *manifest, step, detail, packagePath string) error { + if err := h.appendJournal(current.RunID, journalEntry{At: h.now(), Step: step, Status: "running", Detail: detail}); err != nil { + return err + } + artifactDir := filepath.Join(h.runDir(current.RunID), "artifacts") + if err := os.MkdirAll(artifactDir, 0o700); err != nil { + return err + } + capture := &limitedCapture{limit: maxSuiteLogSize} + command := exec.CommandContext(ctx, "go", "test", "-race", "-count=1", "-shuffle="+strconv.FormatInt(current.Seed, 10), "-timeout=2m", packagePath) + command.Stdout = capture + command.Stderr = capture + err := command.Run() + logPath := filepath.Join(artifactDir, "suite.log") + if writeErr := atomicWrite(logPath, capture.Bytes(), 0o600); writeErr != nil { + return writeErr + } + if err != nil { + return fmt.Errorf("%s suite failed (bounded log %s): %w", current.Suite, logPath, err) + } + return h.appendJournal(current.RunID, journalEntry{At: h.now(), Step: step, Status: "passed", Detail: detail + " passed"}) +} + func (h *harness) environmentCommand(command string, args []string) error { flags := flag.NewFlagSet(command, flag.ContinueOnError) flags.SetOutput(io.Discard)