compat

package
v0.0.1-alpha.30 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 24 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.

Two rules CI enforces, documented in full at AGENTS.md § Baseline & uniformity policy:

  1. No new failures. baseline.json records every test's expected status; a result that gets worse, or a new failing test, fails the build. Improvements are promoted automatically on main.
  2. Every SDK/CLI suite tests the same operations. Add to suites/registry.json first, then implement in all of them. Gaps must be declared in parity-debt.json, and that file only shrinks.

Check both locally before pushing:

go run ./cmd/compat --compare-baseline --results-file compat-results.json
go run ./cmd/compat --check-parity --results-file compat-results.json

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

The runner manages its own Overcast instance. Unless you pin --endpoint, cmd/compat starts a throwaway emulator on a free port, waits for /_health, runs against it, and stops it on exit. Ports 4566 (API) and 4567 (web UI) are never bound — those belong to your own instance (AGENTS.md § Reserved ports) — so a compat run never disturbs whatever you have running, and two runs can go at once. The dashboard port is probed the same way.

Dashboard with a hot-reloading UI, opened in your browser:

go run ./cmd/compat --dev

Same thing through whichever entry point you prefer — all four are the same code path, so they behave identically on Windows, macOS, and Linux:

Entry point Command
Go directly go run ./cmd/compat --dev
Task (any OS) task compat-dev
Make make compat-dev
Shell wrapper compat/dev.sh · compat\dev.ps1

For a stable, pre-built (non-HMR) dashboard, swap --dev for --serve --interactive --build-ui --open — or use task compat-serve, make compat-serve, compat/run.sh, compat\run.ps1.

Headless runs, no UI:

go run ./cmd/compat --format agent
go run ./cmd/compat --suite go-sdk --format json

Target an instance you are already running (nothing is started or stopped for you):

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

Run via Docker — no host Go or Node required:

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

The second command serves the dashboard at http://localhost:7777; set COMPAT_PORT if that port is taken (a published container port is the one thing compat cannot pick for you). Add --wait to block until it is actually serving. A cold start — no image layers, no caches — takes about a minute; afterwards a restart is under ten seconds, because the Go module and build caches, the suites' node_modules, and the UI build all live in named volumes rather than in your working tree. docker compose -f compat/docker-compose.yml down -v discards those caches and returns you to a cold start.

All eight suites run in the containerised dashboard, including the four that build and run their own images (java-sdk, dotnet-sdk, rust-sdk) and the Lambda tests. Both the emulator and the runner mount the host Docker socket, so those images are siblings on the host daemon rather than a nested daemon; each suite runner already detects it is inside a container and joins the runner's network namespace so the sibling can reach overcast. Without the socket the emulator's Lambda, ECS, RDS, ElastiCache and MSK support is metadata-only, and a compat run measures the stub instead of Overcast — which is why OVERCAST_COMPAT_SKIP_DOCKER now defaults to 0. Set it to 1 to skip the Docker-dependent tests, and COMPAT_DOCKER_SOCK if your socket is not at /var/run/docker.sock.

Mounting the socket gives those containers control of your Docker daemon. That is the same trust the dev container and make docker-run already assume, but it is worth knowing; the compose file is for local development, not CI on shared infrastructure.

First build of the Java, .NET and Rust suite images takes a few minutes; they are cached on the host daemon afterwards and start in seconds.

Useful flags
Flag Default What it does
--dev off --serve --interactive --ui-dev --open in one switch
--endpoint Target your own instance; disables instance management
--start-overcast auto auto | always | never
--port-base 4570 First port considered when scanning (never 4566/4567)
--port :7777 Preferred dashboard port; a free one is picked if taken
--overcast-bin Binary to run (default: bin/overcast, then PATH)
--overcast-image ghcr.io/neaox/overcast:alpha Image used when no binary is found
--overcast-host localhost Hostname the suites use — e.g. localhost.overcast.sh for virtual-host-style S3
--overcast-ui off Also expose the managed instance's own web UI
--build-ui off Build the dashboard UI before serving it
--ui-dir Serve the dashboard UI from a directory instead of the embedded build

A managed instance runs with OVERCAST_STATE=memory and its own web UI disabled, so it leaves nothing behind.

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.

An interactive session has no single end — the dashboard submits work whenever you ask it to — so the report is finalised each time the queue drains: every batch done, nothing running. At that point results are merged into the last report (a scoped re-run replaces just those suites), written to --results-file, and summarised into --agent-report-file. So --report and the results file reflect dashboard-triggered runs exactly as they do batch ones.

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 dashboard UI. Where it comes from depends on how you started:

Mode Source of the UI
default the compat/ui/dist/ build embedded in the binary at compile time
--build-ui a fresh build, served from compat/ui/dist/ on disk
--ui-dir DIR DIR on disk
--ui-dev / --dev an external Vite dev server, with HMR

Compat UI

compat/ui/ is 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.
  • In --ui-dev, proxies its API calls to the compat server named by COMPAT_SERVER_URL (the CLI sets it, since both ports are chosen at runtime).

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

	// OnIdle is called with an aggregated report each time the last
	// outstanding batch completes. Interactive runs have no single end — the
	// dashboard submits work whenever the user asks — so "everything queued
	// has finished" is the point at which results are worth persisting.
	// Optional; set before Start.
	OnIdle func(*RunReport)
	// 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) Report

func (o *Orchestrator) Report() *RunReport

Report aggregates every result seen so far into a RunReport — the same shape the batch runner produces, so GET /results, the saved results file, and `--report` all work for dashboard-triggered runs too.

Results accumulate across batches, so this is the full picture, not just the most recent batch. Ordering is deterministic (suite, then group, then test) so a saved file does not churn between identical runs.

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