Skip to content

Testing

Three layers of confidence: a broad base of fast unit tests, a build-tagged integration suite that runs whole experiments, and scaling benchmarks for the columnar store. On top sits a benchmarking surface — the on-demand benchmarking suite, which drives the benchmarks into per-commit run folders and answers capacity questions (rows/second to disk, entities per GB, cache residency). All of it is fed by minimal, single-purpose fixtures. Two generated reports — code coverage and a capability domains — summarize test health from complementary angles.

The testing layers Three test categories — unit, integration, and benchmark — all built on a shared base of fixtures and helpers. Unit 689 tests · 154 files table-driven round-trip · statistical per-file, colocated go test ./internal/engine/... Integration 22 scenarios build tag: integration auto-discovered, end-to-end 10 turns · 20 seeds -tags=integration Benchmark 20 benchmarks store hot path + sim loop SimLoop · Export · scaling throughput · b.ReportAllocs go test -bench=. Shared fixtures & helpers scenarios/* · internal/engine/testdata/Fixture*/ · yamlPath(t) · loadTestConfig(t) · slotParents(...) writeSeedCSV(t) · buildMinimalConfig() · splitJSONL([]byte)
Counts are exact at time of writing (go test function counts across internal/engine).
Terminal window
# All unit tests (the ... wildcard covers every engine subpackage)
go test ./internal/engine/... -v
# A single test by name (runtime package)
go test ./internal/engine/runtime/ -run TestEvaluateConditions -v
# The build-tagged integration suite (every auto-discovered scenario, run end-to-end)
go test -tags=integration ./internal/engine/enginetest/ -run TestSimulationScenarios -v
# Benchmarks (store hot path + sim-loop / scaling)
go test ./internal/engine/... -bench=. -benchmem
# The on-demand benchmarking suite — topline, profiles, scaling, throughput (see Benchmarking)
go run ./tools/bench/suite
# An end-to-end experiment smoke test
go run main.go -run 50 scenarios/PaymentProcessor/PaymentProcessor.yaml

Running the suite tells you it passes; two generated HTML reports tell you what it covers, from complementary angles. Both write to the project root.

ReportGenerated byWhat it shows
Code coveragego tool coverWhich lines and functions the suite executed — a per-function breakdown and an interactive heatmap. Output: coverage.html.
Capability domainstools/capabilitiesWhich capabilities are validated — a curated inventory mapping feature domains to their tests, fixtures, and scenarios, surfacing gaps that line coverage cannot. Output: capability-domains.html.

Go’s built-in coverage tooling shows which lines and functions the suite exercised. Generate the profile and the HTML heatmap:

Terminal window
go test ./internal/engine/ -coverprofile=cov.out
go tool cover -html=cov.out -o coverage.html

Open coverage.html: covered lines are green, uncovered red, with a file-by-file dropdown. For a quick read in the terminal:

CommandOutput
go test ./internal/engine/ -coverOne-line percentage per package
go tool cover -func=cov.outPer-function coverage table
go tool cover -func=cov.out | sort -t: -k3 -nSorted by percentage (gaps first)

Line coverage shows which code ran; the capability domains report shows which capabilities are validated. go run ./tools/capabilities (also wired into go generate ./...) renders it from a curated inventory — capabilities.yaml for capabilities and validations.yaml for the validation-rule catalog — cross-referenced against the tests, fixtures, and scenarios that exercise each domain:

Terminal window
go run ./tools/capabilities # writes capability-domains.html in the project root
go run ./tools/capabilities --check # CI / pre-commit drift gate (fails on orphan or stale refs)

The report has four sections:

SectionWhat it shows
Capability DomainsEach domain (Scheduling, Taxonomy, Partitioning, …) with test / fixture / scenario counts and a strength rating; expand a domain to see its capabilities and the exact test names.
Scenario × Domain MatrixWhich scenarios exercise which capability domains.
ScenariosEach scenario config with its description and the domains it covers.
Known GapsCapabilities with no or insufficient coverage.

Each domain’s strength rating is derived from its inventory:

RatingCriteria
Strong≥ 3 tests, plus at least one fixture or scenario
Adequate≥ 3 tests
Weak1–2 tests
Untestedno tests

The inventory lives in tools/capabilities/capabilities.yaml. When you add tests, fixtures, or capabilities, update it and regenerate. It has three top-level keys:

KeyPurpose
domainsCapability domains — each with a name, slug, description, and capabilities; each capability lists its tests, fixtures, and scenarios.
scenariosScenario configs with the domain slugs they exercise.
gapsKnown coverage gaps with explanatory notes.

The binding from a capability (or a validation rule) to the test or fixture that validates it lives in code as a // @capability domain=… name=… annotation (# @capability … in a fixture’s YAML), so it travels with the code and survives renames. Validation rules work the same way: the rule catalog is validations.yaml, and each enforcement site carries a // @validation rule=… annotation. A catalog entry with no annotation renders as a gap; an annotation naming an unknown id is an orphan that --check rejects.

Terminal window
$EDITOR tools/capabilities/capabilities.yaml # edit the inventory
go run ./tools/capabilities # regenerate capability-domains.html
go run ./tools/capabilities --check # fail on drift (orphans / stale refs)

Tests are colocated with their source across the engine’s subpackages (runtime/scheduler_test.go beside runtime/scheduler.go, store/store_archetype_test.go beside store/store_archetype.go, and so on) and lean on the stdlib testing package — table-driven where inputs vary, round-trip where there’s a serializer, statistical where there’s sampling. The main functional domains are below; the full authoritative inventory — including OSI, checkpoint/resume, collections, history, timestamps, the REST API, and MCP — lives in capabilities.yaml.

DomainFile(s)What it covers
Finite State Machinesruntime/fsm_test.go, runtime/eval/selector_test.goState transitions, SelectEvent probability sampling (stay, always-fire, age-clamp, empty-state), pre-selection condition masking + renormalization, age reset on transition.
Condition Evaluationruntime/eval/selector_test.go, enginetest/evaluators_test.goType-aware comparisons (int is/atLeast/atMost, float with epsilon, is/is_not), AND logic, per-condition diagnostic results.
Probability & Distributionsmodel/distributions_test.goNormalization (epsilon scaling, largest-gets-remainder, scale-down, immutability), YAML unmarshaling of mixed int/float values.
Taxonomy Samplingenginetest/taxonomy_test.go, compile/load/value_distribution_test.goAll five sampling strategies (distribution, conditional, lookups, weights, options — 10k-sample frequency checks), dependency-order DAG, circular-dep detection, inherit from parent.
Entity Spawning & Referencesruntime/spawn_policy_test.go, runtime/rt_spawn_integrity_test.go, compile/validate/validate_spawn_test.goChild entity creation (new_entity), referential integrity (child → parent), spawn-policy population sizing, spawn-drop logging, unused reference slot detection.
Message Passingruntime/messages_accepted_test.go, runtime/cascade_fanin_test.go, runtime/scheduler_test.goImmediate delivery (cascade queue), delayed delivery (time-wheel ordering/drain), cascade-loop detection, the unified handler-selection model.
Scheduling & Clock Modesruntime/scheduler_test.go, compile/load/clock_override_test.go, model/dwell_test.goTick and event clock advancement, the wake queue, dwell sampling, regime validation (valid/invalid combos), --clock override, event-log timestamp enrichment.
Synchronous Updatesruntime/scheduler_test.go, runtime/partial_turn_test.goSnapshot reads (int, float, string), deferred state/reference updates, delta accumulation (AddInt), commit-then-reveal end-to-end.
Partitioning & Parallelismcompile/plan/planner_test.go, runtime/rt_isolation_test.go, runtime/rt_dimension_partition_test.goHazard detection (spawn, message, reference edges), independent-machine identification, partition building, cross-partition routing, partition isolation, concurrent executor correctness.
Entity Storagestore/store_archetype_test.go, store/segment_test.goEntity creation and ID generation, creation with references, pending lifecycle, iteration/lookup, state/field access, snapshot/commit, segmented (flushed) stores.
Configuration Loadingcompile/load/loader_test.go, compile/load/convert_test.go, compile/load/schema_test.goType coercion (int/float/string), field flattening and mapping, condition/action linking, indirect path compilation, cycle detection (spawn/message).
Validationcompile/validate/validate_test.go, compile/validate/validate_reachability_test.goThe rule families — messaging, reachability, spawn, taxonomy, passivation, action-type, and payload-condition validators.
Event & Decision Loggingruntime/eventlog_test.go, runtime/decisionlog_test.goEntry builders for every action type, JSONL round-trip, full-experiment integration, partition-aware logging, debug-gated decision traces.
Seeding & Import/Exportenginetest/seed_test.go, runtime/import_test.go, store/dataset/export_test.goSeed reference parsing, seedCount fallback, CSV/Parquet round-trip (write → load with references and nextID), seed-schema validation.
Diagnostics & Dumpcompile/diagnostics/dump_test.go, compile/diagnostics/report_data_test.goThe --dump JSON (pointer-to-name marshaling, matrices labelled) and the --analyze report (parallelism verdict, hazards, scalability prediction).
Checkpoint & Resumestore/checkpoint/roundtrip_test.go, runtime/checkpoint_test.goPer-machine Arrow IPC + manifest round-trip across every column type, segmented-store handling, resume rehydration and history preservation.
OSI semantic layerstore/dataset/osi_test.goConfig → OSI generation rules, curated-overlay merge, and metric validation (bind/execute tiers + literal cross-check).
Collections & aggregationruntime/collections_test.go, runtime/aggregate_test.goCollection membership via back-references, and the aggregate fold (count/sum/max/min) with its type rules.
REST APIinternal/api/*_test.goScenario deploy/list/get/delete, experiment lifecycle, async run + targeted requests, entity/event/dataset reads, export, checkpoints, CORS, and admin gating.

Scheduling and partitioning behaviours are tested against dedicated, minimal configs — each isolating one concern — under internal/engine/testdata/, following the Fixture<Scenario> naming convention. A fixture directory holds a mock-machine.yaml and any seed CSVs (named by machine type). The richer scenarios/* configs are reserved for end-to-end coverage.

FixtureConcern it isolates
FixtureImmediateMessageSame-turn message delivery via the cascade queue.
FixtureDelayedMessageA delay defers delivery via the time wheel (proves the multi-tick lag).
FixtureCascadeLoopA request/reply loop trips the cascade-loop guard.
FixtureEventClockMulti-rate entities under the event clock (Fast.csv / Slow.csv).
FixtureIndependentPartitionsDisjoint machines partition into independent groups.
FixtureConnectedGroupMachines linked by an edge stay in one partition (serialized).
FixtureMultiPartitionMessagesMessaging across multiple partitions (Ping/Pong/Click/Clack).
FixtureScanEdgeA set_reference population scan creates a reference edge.

The integration test file internal/engine/enginetest/simulations_integration_test.go is gated behind //go:build integration, so the default go test skips it. TestSimulationScenarios auto-discovers every scenario via mock_machine.DiscoverScenarios over scenarios/ — adding a new scenarios/<Name>/<Name>.yaml enrols it automatically, no edit to the test — and runs each whole experiment end-to-end: load → seed from files (integrationSimCount = 20) → run (integrationTurns = 10) → WriteCSV to a temp dir → assert the total entity count is greater than zero.

The discovered set is whatever lives under scenarios/ — currently twenty-two. A representative sample:

ScenarioExercises
PaymentProcessorTaxonomy-heavy seeding, references, multi-stage spawn chains.
ProgrammaticAdsDeep spawn fan-out (campaigns → ad slots → bid requests → impressions → clicks).
ServiceQueueQueueing with spawned tellers/tickets/sessions.
OnlineShoppingCatalog, field families, message handlers, set_reference scans.
PrisonersDilemmaSelf-referential players under the sync regime (commit-then-reveal).
RefusalDemoRequested events masked by failing conditions (a refusal is a no-op, not a fallback).
CliffWalking, PageRank, SyncRevealReinforcement-learning grid walk (4×12 cliff grid), iterative web-graph ranking, and synchronous update revealed through StepTurn.
JaffleShop, LondonUnderground, PacMan, GameOfLifeTimestamp fields (a dbt-style orders dataset), transit-graph routing, a reactive-policy grid agent, and a generated cellular automaton.

Benchmarks and performance/capacity testing — the on-demand benchmarking suite, profiling, scaling sweeps, and cloud-scale generation — now live on the Benchmarking page.

HelperRole
yamlPath(t)Resolves scenarios/PaymentProcessor/PaymentProcessor.yaml relative to the test file via runtime.Caller. Variants exist for other fixtures (delayedEchoPath, syncRevealPath, dumpYamlPath, the bench paths).
loadTestConfig(t)Loads and fatals on error — the common setup for engine tests.
slotParents(machine, parents)Converts a readable map[string]EntityHandle of parents into the slot-indexed array CreateEntityWithRefs expects.
writeSeedCSV(t, …) / buildMinimalConfig()Construct temporary seed files and minimal configs for seed/import tests.
splitJSONL(data)Splits a JSONL log into lines for assertion.