compat

package
v0.0.1-alpha.24 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 25, 2026 License: MIT Imports: 22 Imported by: 0

README

Overcast Compatibility Tests

Compatibility test suites that verify standard AWS tooling (SDKs, CLI, CDK, IaC) works correctly against Overcast without modification.

Tests are used as a coverage metric: every service and operation is tested, including those not yet implemented. Failures on unimplemented features are expected and tracked — this is how we measure what's left to build, and how we guard against regressions in what's already working.

Separation boundary: everything in compat/ is a black-box external observer of Overcast. Each suite uses its SDK, CLI, or CDK tool without modification — the only difference from talking to real AWS is the endpoint URL. Nothing in compat/ imports from internal/, routes, middleware, or any other part of the Overcast server. The emulator has no knowledge that compat exists. This boundary must never be crossed.


Quick start

# Build and start the compat dashboard on :7777
make compat-serve

# Then open http://localhost:7777 in your browser.
# The dashboard streams live test results as they arrive.

Run a single suite from the CLI without the UI:

go run ./cmd/compat --endpoint http://localhost:4566

# JSON output (CI / dashboards)
go run ./cmd/compat --endpoint http://localhost:4566 --format json

Run via Docker — no local toolchain required:

docker compose -f compat/docker-compose.yml run --rm compat

For GitHub Actions, a dedicated workflow is provided at .github/workflows/compat.yml. It runs on every push to main, every PR to main, on release creation, and on manual dispatch. It uses the native ubuntu-latest runner (no Docker image builds) for fast startup and standard GHA caching. Results are uploaded as a build artifact and written to the job summary.

The compose file starts Overcast, health-checks it, then runs the Go CLI which spawns each suite subprocess. Suite failures are expected; the CLI exits 0. Only infrastructure failures (Overcast failed to start, subprocess crashed) produce a non-zero exit code.


Build Performance Notes

Test harness optimization: Rust suite builds use the dev profile (no optimization) for fast test builds, not release profile. Test code prioritizes build speed over runtime performance.

First build vs. subsequent builds:

Suite First Build Cached Build Profile Why slow on first build?
node-js-sdk ~30s ~5s default npm ci + TypeScript check
python-sdk ~20s ~3s default pip install + fast lang
rust-sdk ~3-5 min ~15-30s dev LLVM compilation + large deps
java-sdk ~2-3 min ~10-20s default Maven + JVM startup
dotnet-sdk ~2-3 min ~15-30s default NuGet restore + .NET runtime

Why Rust is slower than Go/Node on first build:

  1. Compiler design: Rust uses LLVM (powerful, slow backend). Go has a simple, fast compiler optimized for speed.
  2. Type system: Rust's trait system, lifetime checking, and generic specialization add heavy compile-time work that Go doesn't have.
  3. Dependencies: AWS SDK Rust has macro-heavy transitive deps (syn, proc-macro2, quote). Each macro expansion adds compilation overhead.
  4. Even with opt-level=0: Rust still performs complex borrow checking, trait resolution, and type inference at compile time.

Optimizations in place:

  • Dev profile for test code (no optimization, fastest possible build)
  • BuildKit cache mounts (persists cargo registry + build artifacts across rebuilds)
  • .dockerignore excludes unnecessary files from build context
  • Dependency caching (first install is slow, subsequent builds reuse cached deps)

To rebuild faster:

# BuildKit is auto-enabled in recent Docker versions; explicit enable if needed:
DOCKER_BUILDKIT=1 docker build -f compat/suites/rust-sdk/Dockerfile -t oc-rust-sdk:latest compat/suites

# Subsequent builds reuse cached dependencies — only recompile changed code (~15-30s)

Why subsequent builds are much faster:

  1. Cargo registry cache is persisted across builds
  2. Compiled dependency binaries are cached
  3. cargo build detects unchanged dependencies and reuses their builds
  4. Only source changes (src/) trigger recompilation

Running Stable Tests Without Rebuilds

Once test code is stable (not changing frequently), you can run tests directly without Docker rebuilds. This is ideal for CI/testing scenarios where only Overcast changes, not the test code:

Option 1: Run pre-built Docker image (recommended for CI)

# Build once
docker build -f compat/suites/rust-sdk/Dockerfile -t oc-rust-sdk:stable compat/suites

# Run many times — uses cached image (instant, no rebuild)
docker run --rm --network host \
  -e OVERCAST_ENDPOINT=http://localhost:4566 \
  oc-rust-sdk:stable

Option 2: Run host binary directly (recommended for local dev)

Build locally once, then run against Overcast without Docker overhead:

# Build once (with cargo caching, ~1m 20s on first build, ~0.5s on subsequent)
cd compat/suites/rust-sdk && cargo build

# Run many times — cargo detects no changes, instant startup
OVERCAST_ENDPOINT=http://localhost:4566 ./target/debug/rust_sdk_compat

Performance comparison:

Scenario Build Time Run Time Use Case
CI: Docker image + tests 1-2m (1×) ~10s Reproducible, shared
Docker: pre-built image 0 (cached) ~10s Stability testing
Host: direct binary 0 (cached) ~1s Local dev, fast loop
Host: cargo check unchanged 0.5s ~1s Frequent test runs

Key insight: Once test code stabilizes, subsequent test runs are instant or sub-second because:

  • Cargo detects no source changes and skips recompilation
  • Docker layer cache skips rebuilds
  • Test failures are due to Overcast changes, not test code issues

This breaks the edit-compile-test loop for test harnesses: edit once, test many times.


Suites

SDK Tests
Suite Language SDK / Tool Status
node-js-sdk TypeScript AWS SDK JS v3 ✅ active
python-sdk Python 3 boto3 ✅ active
go-sdk Go 1.24 AWS SDK Go v2 ✅ active
java-sdk Java 17 AWS SDK Java v2 🔜 planned
dotnet-sdk C# AWS SDK .NET v3 ✅ active
rust-sdk Rust AWS SDK Rust ✅ active
cli Bash AWS CLI v2 ✅ active
Infrastructure as Code
Suite Tool Status
cdk AWS CDK v2 (TypeScript) 🔜 planned
tofu OpenTofu + AWS provider 🔜 planned
terraform Terraform + AWS provider v6 🔜 planned
pulumi Pulumi AWS provider 🔜 planned

What to test

Every suite should cover all services implemented in Overcast at a minimum. The table below shows what each suite currently covers. ✅ = tests exist (may include expected failures for unimplemented ops), 🔜 = planned, — = out of scope.

Service node-js-sdk python go java rust cli cdk tofu terraform
S3 🔜 🔜 🔜 🔜 🔜
SQS 🔜 🔜 🔜 🔜 🔜
DynamoDB 🔜 🔜 🔜 🔜 🔜
SNS 🔜 🔜 🔜 🔜 🔜
Lambda 🔜 🔜 🔜
CloudWatch Logs 🔜
SES 🔜
Secrets Manager 🔜 🔜 🔜 🔜 🔜
IAM 🔜 🔜 🔜 🔜
STS 🔜 🔜
KMS 🔜 🔜 🔜 🔜
SSM 🔜 🔜 🔜 🔜
EventBridge 🔜 🔜
Kinesis 🔜

Architecture

[ Overcast emulator ]  ← the system under test; knows nothing about compat
        ↑ HTTP (port 4566)
        │
[ compat runner ]      ← spawns suite subprocesses, reads NDJSON from stdout
        │
  ┌─────┴──────┐
  │  suites    │  ← each suite is an independent subprocess / Docker image
  │ node-js-sdk│     that speaks only to the emulator via the AWS SDK
  │  python …  │
  └────────────┘
        │ aggregated RunReport (JSON)
        ↓
[ compat server ]      ← small HTTP service inside cmd/compat
        │               serves last run result + streams live NDJSON events
        ↓
[ compat-ui ]          ← Vite/React dashboard (compat/ui/)
                          reads from compat server only; never from Overcast
compat/
  README.md          ← you are here
  AGENTS.md          ← coding conventions for contributors and AI agents
  Makefile           ← make run / make ci / make json / make serve
  docker-compose.yml
  result.go          ← Go types for the NDJSON wire format
  runner.go          ← orchestrates suite subprocesses, aggregates results
  server.go          ← HTTP server: GET /events (SSE), GET /results, GET /

  suites/
    node-js-sdk/     ← TypeScript / AWS SDK JS v3 (active)
    python-sdk/      ← Python 3 / boto3 (planned)
    go-sdk/          ← Go / AWS SDK Go v2 (planned)
    java-sdk/        ← Java 17 / AWS SDK Java v2 (planned)
    dotnet-sdk/      ← C# / AWS SDK .NET v3 (planned)
    rust-sdk/        ← Rust / AWS SDK Rust (planned)
    cli/             ← Bash / AWS CLI v2 (planned)
    cdk/             ← TypeScript / AWS CDK v2 (planned)
    tofu/            ← HCL / OpenTofu (planned)
    terraform/       ← HCL / Terraform (planned)
    pulumi/          ← TypeScript / Pulumi AWS provider (planned)

  ui/                ← Vite + React dashboard
    package.json
    src/

cmd/compat/
  main.go            ← CLI: run suites and/or start the compat server

Wire format (NDJSON)

Every suite runner emits newline-delimited JSON to stdout — one object per line, four event types:

{"event":"run_start","suite":"node-js-sdk","started_at":"…","endpoint":"…","version":"1"}
{"event":"test_start","suite":"node-js-sdk","service":"s3","group":"s3-crud","test":"CreateBucket"}
{"event":"test_result","suite":"node-js-sdk","service":"s3","group":"s3-crud","test":"CreateBucket","status":"pass","duration_ms":42}
{"event":"test_result","suite":"node-js-sdk","service":"iam","group":"iam-users","test":"CreateUser","status":"unimplemented","duration_ms":120,"error":"NotImplemented: Unknown action: CreateUser"}
{"event":"run_end","suite":"node-js-sdk","passed":45,"failed":12,"skipped":2,"unimplemented":31,"duration_ms":5432}
Field Type Description
event string run_start | test_start | test_result | run_end
suite string Suite name, e.g. "node-js-sdk"
service string AWS service, e.g. "s3", "iam"
group string Group within suite, e.g. "s3-crud"
test string Test name
status string "pass" | "fail" | "skip" | "unimplemented"
duration_ms number Wall-clock milliseconds
error string Error message (on fail and unimplemented)

Status semantics:

Status Meaning
pass Test passed against the emulator
fail Test failed — the emulator returned a wrong response or an error
unimplemented Emulator returned 501 or UnknownOperationException / NotImplemented
skip Test skipped (e.g. Docker not available)

Rules: emit to stdout only; one line per event; exit 0 always (suites must not fail the process for expected test failures).


Adding a new suite

  1. Create compat/suites/<name>/ — see the stub README in each planned suite directory for language-specific setup notes.
  2. Emit the NDJSON wire format above to stdout.
  3. Register the suite in compat/runner.go.
  4. Update the Suites table above from 🔜 to ✅.

Compat server

The Go CLI starts a small HTTP server (--serve, default port 7777).

GET /events — SSE stream

Uses Server-Sent Events to push individual result objects to connected clients as they arrive from the suite subprocesses. Clients that connect mid-run receive all buffered events since the run started.

GET /results — last completed run

Returns the latest RunReport as a single JSON object. Useful for CI badge generation and one-shot queries.

POST /run — trigger a run

Starts a new run (returns 202 Accepted or 409 Conflict if already running). Accepts a JSON filter body:

{ "service": "s3" }                          // re-run one service
{ "suite": "node-js-sdk" }                   // re-run one suite
{ "statuses": ["fail", "skip"] }             // re-run non-passing tests
{ "service": "s3", "group": "s3-crud" }      // re-run one group
GET / — compat dashboard

Serves the embedded compat/ui/dist/ build. Run make compat-serve to build and start everything.

Compatibility test suites that verify standard AWS tooling (SDKs, CLI, CDK, IaC) works correctly against Overcast without modification.

Tests are used as a coverage metric: every service and operation is tested, including those not yet implemented. Failures on unimplemented features are expected and tracked — this is how we measure what's left to build, and how we guard against regressions in what's already working.

Separation boundary: everything in compat/ is a black-box external observer of Overcast. Each suite uses its SDK, CLI, or CDK tool without modification — the only difference from talking to real AWS is the endpoint URL. Nothing in compat/ imports from internal/, routes, middleware, or any other part of the Overcast server. The emulator has no knowledge that compat exists. This boundary must never be crossed.


Quick start

# Run all suites and print a summary (no Overcast changes required)
go run ./cmd/compat --endpoint http://localhost:4566

# JSON output (CI / dashboards / pipe to the compat server)
go run ./cmd/compat --endpoint http://localhost:4566 --format json

# Run via Docker — no local toolchain required
docker compose -f compat/docker-compose.yml run --rm compat

# Start the compat server so the UI can connect (planned)
go run ./cmd/compat --serve --endpoint http://localhost:4566
# then open http://localhost:7777 in the compat-ui

For GitHub Actions:

- name: Run compat tests
  run: docker compose -f compat/docker-compose.yml run --rm compat

The compose file starts Overcast, health-checks it, then runs the Go CLI which spawns each suite subprocess. Suite failures are expected; the CLI exits 0. Only infrastructure failures (Overcast failed to start, subprocess crashed) produce a non-zero exit code.


Suites

Suite Language SDK / Tool README Status
node-js-sdk TypeScript AWS SDK v3 (JS) suites/node-js-sdk/README.md ✅ initial
python Python 3 boto3 🔜 planned
go Go 1.24 AWS SDK Go v2 🔜 planned
cli Bash AWS CLI v2 🔜 planned
cdk TypeScript AWS CDK v2 🔜 planned
tofu HCL OpenTofu + AWS prov. 🔜 planned

Architecture

[ Overcast emulator ]  ← the system under test; knows nothing about compat
        ↑ HTTP (port 4566)
        │
[ compat runner ]      ← spawns suite subprocesses, reads NDJSON from stdout
        │
  ┌─────┴──────┐
  │  suites    │  ← each suite is an independent subprocess / Docker image
  │ node-js-sdk│     that speaks only to the emulator via the AWS SDK
  │  python …  │
  └────────────┘
        │ aggregated RunReport (JSON)
        ↓
[ compat server ]      ← small HTTP service inside cmd/compat (planned)
        │               serves last run result + streams live NDJSON events
        ↓
[ compat-ui ]          ← standalone Vite/React app (compat/ui/) (planned)
                          reads from compat server only; never from Overcast
compat/
  README.md          ← you are here
  AGENTS.md          ← coding conventions for contributors and AI agents
  Makefile           ← make run / make ci / make json / make serve
  docker-compose.yml
  result.go          ← Go types for the NDJSON wire format
  runner.go          ← orchestrates suite subprocesses, aggregates results
  server.go          ← HTTP server: GET /events (SSE), GET /results (planned)

  suites/
    node-js-sdk/     ← see suites/node-js-sdk/README.md

  ui/                ← standalone compat UI (planned)
    package.json
    src/

cmd/compat/
  main.go            ← CLI: run suites and/or start the compat server

Wire format (NDJSON)

Every suite runner emits newline-delimited JSON to stdout — one object per line, three event types:

{"event":"run_start","suite":"node-js-sdk","started_at":"…","endpoint":"…","version":"1"}
{"event":"test_result","suite":"node-js-sdk","service":"s3","group":"s3-crud","test":"CreateBucket","status":"pass","duration_ms":42}
{"event":"test_result","suite":"node-js-sdk","service":"iam","group":"iam-users","test":"CreateUser","status":"fail","duration_ms":120,"error":"501 Not Implemented"}
{"event":"run_end","suite":"node-js-sdk","passed":45,"failed":12,"skipped":2,"duration_ms":5432}
Field Type Description
event string run_start | test_result | run_end
suite string Suite name, e.g. "node-js-sdk"
service string AWS service, e.g. "s3", "iam"
group string Group within suite, e.g. "s3-crud"
test string Test name
status string "pass" | "fail" | "skip"
duration_ms number Wall-clock milliseconds
error string Error message (only on fail)

Rules: emit to stdout only; one line per event; exit 0 always (suites must not fail the process for expected test failures).


Adding a new suite

  1. Create compat/suites/<name>/ with a package manifest, source tree, and Dockerfile (for CI).
  2. Emit the NDJSON wire format above to stdout.
  3. Register the suite in compat/runner.go.
  4. Add a README.md in the suite directory.
  5. Add a row to the Suites table above.

Compat server (planned)

The Go CLI will optionally start a small HTTP server (--serve, default port 7777) that:

GET /events — SSE stream

The primary endpoint. Uses Server-Sent Events to push individual result objects to connected clients as they arrive from the suite subprocesses:

Content-Type: text/event-stream

data: {"event":"run_start","suite":"node-js-sdk","started_at":"…","endpoint":"…"}

data: {"event":"test_result","suite":"node-js-sdk","service":"s3","group":"s3-crud","test":"CreateBucket","status":"pass","duration_ms":42}

data: {"event":"test_result","suite":"node-js-sdk","service":"iam","group":"iam-users","test":"CreateUser","status":"fail","duration_ms":120,"error":"501 Not Implemented"}

data: {"event":"run_end","suite":"node-js-sdk","passed":45,"failed":12,"skipped":2,"duration_ms":5432}

Each data: line is one JSON object — the same event shape as the internal NDJSON wire format, re-emitted as SSE. The UI updates each cell in real time as results stream in. Clients that connect mid-run receive all buffered events since the run started (the server keeps them in memory), then continue to receive live events.

GET /results — last completed run

Returns the latest RunReport as a single JSON object once a run has finished. Useful for CI badge generation and one-shot queries without keeping a persistent SSE connection.

GET / — compat UI bundle

Serves the embedded compat/ui/ static build.

The compat server is entirely self-contained inside cmd/compat. It has no dependency on the Overcast emulator and does not import anything from internal/.

Compat UI

compat/ui/ will be a standalone Vite + React app. It:

  • Opens an EventSource to GET /events and updates the compatibility matrix in real time as each test_result event arrives — no polling, no page reload.
  • Falls back to GET /results to populate the matrix when loading a completed run.
  • Never connects to Overcast directly.
  • Is built and embedded into the cmd/compat binary as a static asset.

State management

Each test group creates and destroys its own resources using a runId prefix (format: oc-{8-hex}). Teardown always runs in a finally block.

Documentation

Overview

compat/embed.go — embeds the pre-built compatibility UI into the package.

The compat UI must be built before `go build ./cmd/compat`:

cd compat/ui && npm install && npm run build

The resulting compat/ui/dist/ tree is embedded here and exported as UIFS. The compat server serves it at GET /.

Package compat provides shared Go types for the NDJSON wire format emitted by all per-language test suite runners.

Every runner (Node.js, Python, Go, CLI, …) writes one JSON line per event to stdout. The Go runner in runner.go reads these lines, aggregates them, and builds a RunReport for display or further processing.

Package compat provides the runner that orchestrates per-language test suite subprocesses and aggregates their NDJSON output into a RunReport.

Each suite is an executable (or docker image) that writes NDJSON events to stdout. The runner starts each suite subprocess, reads its stdout line by line, and builds a live RunReport. Suite stderr is forwarded to the runner's own stderr as log lines.

Usage:

r := compat.NewRunner(cfg)
report, err := r.Run(ctx)

Package compat — HTTP server for the compatibility test dashboard.

The server exposes three endpoints:

GET /events   — Server-Sent Events stream of NDJSON test events.
               New clients receive a full replay of the current run so
               far, then live events as they arrive.
GET /results  — The last completed RunReport as indented JSON.
GET /         — Embedded static UI files.

Calls Broadcast(raw) for each NDJSON line from the test runner. Call FinishRun(report) once the whole run is done. Call ResetRun() to clear the replay buffer at the start of a new run.

Index

Constants

This section is empty.

Variables

View Source
var UIFS, _ = fs.Sub(rawUIFS, "ui/dist")

UIFS is the embedded compat UI sub-tree, rooted at the dist build output. Served at / by the compat HTTP server.

Functions

func NewMCPServer

func NewMCPServer(orch *Orchestrator, registryPath, workspaceRoot string, logger *slog.Logger) *intmcp.Server

NewMCPServer creates an MCP server that combines generic repo tools with compat-specific orchestration tools.

Types

type EventType

type EventType string

EventType identifies the kind of NDJSON event.

const (
	EventRunStart      EventType = "run_start"
	EventSuiteStarting EventType = "suite_starting" // emitted by the runner before a suite subprocess starts
	EventSuiteError    EventType = "suite_error"    // emitted when a suite subprocess fails to start or crashes
	EventTestStart     EventType = "test_start"
	EventTestResult    EventType = "test_result"
	EventRunEnd        EventType = "run_end"
)

type GroupReport

type GroupReport struct {
	Suite         string
	Service       string
	Name          string
	Tests         []TestResultEvent
	Passed        int
	Failed        int
	Skipped       int
	Unimplemented int
}

GroupReport is the aggregated result of one test group within a suite.

type Orchestrator

type Orchestrator struct {

	// Endpoint and Region are injected into suite subprocess environments.
	Endpoint string
	Region   string
	// contains filtered or unexported fields
}

Orchestrator manages all suite processes for interactive compat testing.

func NewOrchestrator

func NewOrchestrator(ctx context.Context, configs []SuiteConfig, onEvent func([]byte), logger *slog.Logger) *Orchestrator

NewOrchestrator creates a new orchestrator for the given suite configs. onEvent is called with each raw NDJSON event line for SSE broadcast.

func (*Orchestrator) CancelTests

func (o *Orchestrator) CancelTests(batchID, suite, group, test string, all bool) []QueueEntry

CancelTests cancels matching queued/running tests. Supports cancellation by batchID, suite+group+test, or all.

func (*Orchestrator) QueueState

func (o *Orchestrator) QueueState() []QueueEntry

QueueState returns all queued/running items across all suites.

func (*Orchestrator) RegisterSSEClient

func (o *Orchestrator) RegisterSSEClient(ch chan []byte)

RegisterSSEClient adds a channel that will receive copies of raw NDJSON event lines. Used by the MCP SSE endpoint.

func (*Orchestrator) ReloadSuite

func (o *Orchestrator) ReloadSuite(name string) error

ReloadSuite restarts a specific suite process (hot-swap).

func (*Orchestrator) Results

func (o *Orchestrator) Results(suite, service, group, test, status string) []TestResultEvent

Results returns the latest test results, optionally filtered. Pass empty strings to skip a filter dimension.

func (*Orchestrator) Shutdown

func (o *Orchestrator) Shutdown()

Shutdown gracefully stops all suite processes.

func (*Orchestrator) Start

func (o *Orchestrator) Start() error

Start spawns all suite processes, begins reading their stdout, and launches a watchdog goroutine that detects stalled suites.

func (*Orchestrator) SubmitFailingTests

func (o *Orchestrator) SubmitFailingTests(suiteFilter, serviceFilter string, statuses ...Status) (batchID string, queued []QueueEntry)

SubmitFailingTests re-queues all tests whose last result matched one of the given statuses (e.g. "fail", "skip", "unimplemented"). If statuses is empty it defaults to StatusFail. Returns the queued entries so callers can relay them to clients.

func (*Orchestrator) SubmitTests

func (o *Orchestrator) SubmitTests(suites []string, tests []TestRef) (batchID string, queued []QueueEntry, skippedDups int)

SubmitTests queues tests for execution across specified suites. If suites is nil/empty, submits to all suites. Returns batch ID, list of queued items, and count of skipped duplicates.

func (*Orchestrator) SuiteStates

func (o *Orchestrator) SuiteStates() []SuiteStatus

SuiteStates returns the current state of all suites.

func (*Orchestrator) UnregisterSSEClient

func (o *Orchestrator) UnregisterSSEClient(ch chan []byte)

UnregisterSSEClient removes a previously registered SSE channel.

type QueueEntry

type QueueEntry struct {
	BatchID string `json:"batch_id"`
	Suite   string `json:"suite"`
	Group   string `json:"group"`
	Test    string `json:"test,omitempty"`
	State   string `json:"state"` // "queued" or "running"
}

QueueEntry represents a single item in the queue (for API responses).

type QueuedBatch

type QueuedBatch struct {
	ID        string    `json:"batch_id"`
	Tests     []TestRef `json:"tests"`
	CreatedAt time.Time `json:"created_at"`
}

QueuedBatch is a batch of tests waiting to be sent to a suite.

type RawEvent

type RawEvent struct {
	Event EventType `json:"event"`
}

RawEvent is used to peek at the "event" field before full unmarshalling.

type RunConfig

type RunConfig struct {
	// Endpoint is the Overcast base URL, e.g. "http://localhost:4566".
	Endpoint string
	// Region is the AWS region to advertise to suite clients.
	Region string
	// Suites lists which suites to run. An empty slice runs all registered suites.
	Suites []string
	// Service filters runs to a single AWS service (e.g. "s3"). Empty = all.
	Service string
	// Group filters runs to a single test group (e.g. "s3-crud"). Empty = all.
	Group string
	// Test filters runs to a single test within a group. Empty = all.
	// Only meaningful when Group is also set.
	Test string
	// TestPairs restricts the run to specific (group, test) pairs.
	// Format: ["groupName:testName", ...]. When set, Service/Group/Test filters
	// are ignored — the pairs are the authoritative list.
	TestPairs []string
	// RunID is the unique identifier for this run, injected into all suite
	// subprocesses as OVERCAST_COMPAT_RUN_ID. All test resources must be
	// prefixed with this ID so the post-run orphan sweep can detect leaks.
	// If empty, a random ID is generated in Run().
	RunID string
	// OnEvent is an optional callback invoked with each raw NDJSON event line
	// as it is received from a suite subprocess. The byte slice is a stable
	// copy and may be retained by the caller. Invoked from a single goroutine.
	OnEvent func(raw []byte)
}

RunConfig controls how the runner executes suites.

type RunEndEvent

type RunEndEvent struct {
	Event         EventType `json:"event"`
	Suite         string    `json:"suite"`
	Passed        int       `json:"passed"`
	Failed        int       `json:"failed"`
	Skipped       int       `json:"skipped"`
	Unimplemented int       `json:"unimplemented"`
	DurationMS    int64     `json:"duration_ms"`
}

RunEndEvent is the last line emitted by a suite runner.

type RunFilter

type RunFilter struct {
	Suite   string `json:"suite,omitempty"`
	Service string `json:"service,omitempty"`
	Group   string `json:"group,omitempty"`
	Test    string `json:"test,omitempty"`
	// Statuses, when non-empty, restricts the run to tests whose result in the
	// last run matched one of the given statuses (e.g. "fail", "skip",
	// "unimplemented"). The server expands this to TestPairs before calling
	// the run function.
	Statuses []string `json:"statuses,omitempty"`
	// TestPairs is set internally (not decoded from JSON) when Statuses is
	// expanded. Format: ["groupName:testName", ...].
	TestPairs []string `json:"-"`
}

RunFilter scopes a re-run to a subset of tests. All fields are optional; zero value means "run everything".

type RunReport

type RunReport struct {
	Endpoint   string
	StartedAt  time.Time
	FinishedAt time.Time
	Suites     []*SuiteReport
}

RunReport is the aggregated result of one or more suite runs. Built by runner.go from the streamed NDJSON events.

type RunStartEvent

type RunStartEvent struct {
	Event      EventType `json:"event"`
	Suite      string    `json:"suite"`
	StartedAt  time.Time `json:"started_at"`
	Endpoint   string    `json:"endpoint"`
	Version    string    `json:"version"`
	TotalTests int       `json:"total_tests,omitempty"`
}

RunStartEvent is the first line emitted by a suite runner.

type Runner

type Runner struct {
	// contains filtered or unexported fields
}

Runner orchestrates suite subprocesses.

func NewRunner

func NewRunner(cfg RunConfig) *Runner

NewRunner creates a Runner pre-loaded with the default suite set.

func (*Runner) Run

func (r *Runner) Run(ctx context.Context) (*RunReport, error)

Run starts each configured suite subprocess in parallel, reads their NDJSON output, and returns an aggregated RunReport. Suites are independent OS processes with no shared state so concurrent execution is safe. OnEvent is called under a mutex so callers receive events from all suites on a single goroutine (same contract as the previous sequential Run).

func (*Runner) Suites

func (r *Runner) Suites() []string

Suites returns the names of the suites that will be executed by this runner, applying any name filter from RunConfig.Suites. Useful for callers that need to know the resolved suite list before calling Run (e.g. to call ResetRun).

func (*Runner) WithLogWriter

func (r *Runner) WithLogWriter(w io.Writer) *Runner

WithLogWriter redirects runner log output (default: os.Stderr).

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is the compatibility test HTTP server. Create via NewServer; all methods are safe for concurrent use.

func NewServer

func NewServer(uiFS fs.FS) *Server

NewServer creates a Server backed by optional embedded UI files. Pass nil for uiFS to disable static file serving (useful in tests).

func (*Server) Broadcast

func (s *Server) Broadcast(raw []byte)

Broadcast delivers a raw NDJSON event line to all connected SSE clients and appends it to the replay buffer for clients that connect later. Safe to call from any goroutine.

func (*Server) FinishRun

func (s *Server) FinishRun(report *RunReport)

FinishRun stores the completed RunReport for GET /results responses and broadcasts a run_complete event to all connected SSE clients.

When report only covers a subset of suites (partial re-run), FinishRun merges those results into the existing last report so that GET /results always returns the full picture across all suites, not just the ones that were just re-run.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the HTTP handler for the server.

func (*Server) LoadResultsFile

func (s *Server) LoadResultsFile(path string) error

LoadResultsFile reads a previously saved results file and pre-populates the GET /results response so the dashboard shows the last run immediately after a server restart, before any new run has been performed.

func (*Server) ResetRun

func (s *Server) ResetRun(suites ...string)

ResetRun prepares for a new test run.

suites lists which suite names are about to be re-run. If empty, all suites are reset (full re-run). For a partial re-run (e.g. just "node-js-sdk"), pass only those suite names so the results for other suites are preserved in the replay buffer and remain visible in the UI while the new run proceeds.

ResetRun broadcasts a run_reset event to all live clients so the UI can mark the affected suites' data as stale while preserving the rest.

func (*Server) SaveResultsFile

func (s *Server) SaveResultsFile(path string) error

SaveResultsFile writes the last completed RunReport to path so it survives a server restart. The file is written atomically via a temp-file rename.

func (*Server) SetOrchestrator

func (s *Server) SetOrchestrator(o *Orchestrator)

SetOrchestrator attaches the interactive-mode orchestrator to the server. When set, POST /run delegates to the orchestrator instead of the legacy runFn, and the new /suites, /queue, /cancel, /registry endpoints become available.

func (*Server) SetRunFunc

func (s *Server) SetRunFunc(fn func(filter RunFilter) error)

SetRunFunc registers the function the server calls when POST /run is received. fn is invoked in a new goroutine. It must call ResetRun(), Broadcast(), and FinishRun() itself (the main run loop does this naturally). Only one run at a time is allowed; POST /run returns 409 if one is already in progress.

func (*Server) SetRunning

func (s *Server) SetRunning(v bool)

SetRunning marks the server as running or idle. Called by the run function before and after a run so POST /run can enforce single-concurrency.

type Status

type Status string

Status is the outcome of a single test.

const (
	StatusPass Status = "pass"
	StatusFail Status = "fail"
	StatusSkip Status = "skip"
	// StatusUnimplemented indicates the endpoint returned HTTP 501.
	// The feature gap is known and expected; this is distinct from a real
	// failure (wrong response, assertion error, SDK crash).
	StatusUnimplemented Status = "unimplemented"
	// StatusNA indicates the AWS SDK client used by this suite does not yet
	// expose this operation.  It is NOT an Overcast gap and NOT a suite
	// authoring gap — simply that the SDK library has no API for it yet.
	// NA results are excluded from all pass-rate calculations.
	StatusNA Status = "na"
)

type StdinCommand

type StdinCommand struct {
	Command string    `json:"command"`
	BatchID string    `json:"batch_id,omitempty"`
	Tests   []TestRef `json:"tests,omitempty"`
	Group   string    `json:"group,omitempty"`
	Test    string    `json:"test,omitempty"`
}

StdinCommand is a JSON command sent to a suite process via stdin.

type SuiteConfig

type SuiteConfig struct {
	// Name is the suite identifier, e.g. "node-js-sdk".
	Name string
	// Argv is the command + arguments to run.
	// The first element is the executable; the rest are arguments.
	// The executable is looked up on PATH.
	Argv []string
	// Env is additional environment variables (KEY=VALUE).
	// OVERCAST_ENDPOINT and OVERCAST_DEFAULT_REGION are always injected by the runner.
	Env []string
	// Dir is the working directory for the subprocess.
	// If empty, the runner's working directory is used.
	Dir string
	// Interactive indicates this suite supports the interactive NDJSON
	// stdin/stdout protocol (building → ready → run commands).
	// Suites without this flag are skipped by the orchestrator.
	Interactive bool
}

SuiteConfig describes a single test suite subprocess.

func DefaultSuiteConfigs

func DefaultSuiteConfigs(endpoint, region string) []SuiteConfig

DefaultSuiteConfigs returns the built-in suite configuration list with endpoint and region injected into each suite's environment. This is the public entry point for callers (e.g. cmd/compat interactive mode) that need to construct suite configs without creating a full Runner.

func FilterSuiteConfigs

func FilterSuiteConfigs(all []SuiteConfig, names []string) []SuiteConfig

FilterSuiteConfigs filters a list of suite configs to only those whose Name appears in names. Used by cmd/compat to narrow the default configs by --suite.

type SuiteProcess

type SuiteProcess struct {
	Name   string
	Config SuiteConfig
	State  SuiteState
	Cmd    *exec.Cmd

	Queue         []QueuedBatch
	ActiveBatch   *QueuedBatch
	RunningTest   string // "group:test" currently executing
	PendingBuffer []StdinCommand
	LastEventAt   time.Time // last time any stdout event was received
	PingSentAt    time.Time // last time a ping command was sent
	CancelSentAt  time.Time // last time a cancel command was sent for a stuck test
	Interactive   bool      // true if the suite emitted a 'ready' event
	// contains filtered or unexported fields
}

SuiteProcess manages a single long-lived suite runner process.

type SuiteReport

type SuiteReport struct {
	Suite         string
	Groups        []*GroupReport
	Passed        int
	Failed        int
	Skipped       int
	Unimplemented int
}

SuiteReport is the aggregated result of a single suite (e.g. node-js-sdk).

func (*SuiteReport) PassRate

func (s *SuiteReport) PassRate() float64

PassRate returns the pass rate as a value in [0, 1]. Returns 0 for empty suites. Unimplemented tests are excluded from both numerator and denominator — they represent known gaps, not implementation quality.

func (*SuiteReport) Services

func (s *SuiteReport) Services() []string

Services returns a deduplicated list of service names tested in this suite.

func (*SuiteReport) Total

func (s *SuiteReport) Total() int

Total returns the total number of tests in this suite.

type SuiteState

type SuiteState string

SuiteState represents the current lifecycle state of a suite process.

const (
	SuiteBuilding SuiteState = "building"
	SuiteReady    SuiteState = "ready"
	SuiteBusy     SuiteState = "busy"
	SuiteError    SuiteState = "error"
	SuiteStopped  SuiteState = "stopped"
)

type SuiteStatus

type SuiteStatus struct {
	Name        string     `json:"name"`
	State       SuiteState `json:"state"`
	QueuedCount int        `json:"queued_count"`
	RunningTest string     `json:"running_test,omitempty"`
}

SuiteStatus is the API response for suite state.

type TestRef

type TestRef struct {
	Group string   `json:"group"`
	Tests []string `json:"tests,omitempty"` // if nil, all tests in group
}

TestRef identifies a specific test or group of tests.

type TestResultEvent

type TestResultEvent struct {
	Event   EventType `json:"event"`
	Suite   string    `json:"suite"`
	Service string    `json:"service"`
	Group   string    `json:"group"`
	Test    string    `json:"test"`
	// Op is the AWS API operation name used for documentation links.
	// Empty string disables the doc link. When absent, Test is used.
	Op         string `json:"op,omitempty"`
	Status     Status `json:"status"`
	DurationMS int64  `json:"duration_ms"`
	Error      string `json:"error,omitempty"`
}

TestResultEvent is emitted once per test, immediately after it completes.

type TestStartEvent

type TestStartEvent struct {
	Event   EventType `json:"event"`
	Suite   string    `json:"suite"`
	Service string    `json:"service"`
	Group   string    `json:"group"`
	Test    string    `json:"test"`
}

TestStartEvent is emitted once per test, immediately before it begins executing. Consumers use this to show a test as "running" while awaiting the result.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL