Skip to content

test: harden entity recycling with stress and edge-case tests#20

Open
gauvainw wants to merge 1 commit into
mainfrom
test/entity-recycling-hardening
Open

test: harden entity recycling with stress and edge-case tests#20
gauvainw wants to merge 1 commit into
mainfrom
test/entity-recycling-hardening

Conversation

@gauvainw

Copy link
Copy Markdown
Owner

What changed and why

Stress tests and edge-case tests for the entity recycling (generation-based) system.

New entity pool tests (entity_test.go)

  • StressCreateDestroy — 1M alloc/free cycles, verifying alive/dead state each iteration
  • StressGenerationIncrement — 10K recycles of slot 0, asserting generation counter increments correctly
  • StressBatchCreateDestroy — 100 batches of 1K entities, verifying count after create and destroy
  • StaleAfterMultipleRecycles — 10 generations of stale refs, all must fail IsAlive/Free
  • MixedAliveAndDead — 100 entities, destroy evens, verify odds alive / evens dead
  • FreeListOrder — Verifies LIFO reuse order of the free list
  • MaxGeneration / MaxIndex / MaxBoth — Entity encoding at uint32 boundaries

New world-level recycling tests (world_test.go)

  • RecyclingStress — 10K create/destroy cycles with component data verification
  • RecyclingStaleAccess — 50 stale refs all fail IsAlive/Has/Get
  • RecycledEntityCleanComponents — Recycled slot has no inherited components
  • BatchRecyclingQueryConsistency — 1K entities, destroy half, recreate, verify query counts

All tests pass with -race enabled.

How to test

go test -race -count=1 -run 'Recycl|Stress|Stale|Max|Mixed|FreeList' ./internal/ecs/

Related issue

Closes #13

Add stress tests for entity pool (1M create/destroy cycles, generation
increment validation, batch create/destroy) and entity encoding limits
(max index, max generation).

Add world-level recycling tests: stress create/destroy with components,
stale reference access after multiple recycles, clean component state
on recycled entities, batch recycling with query consistency checks.

All tests pass with -race enabled.

Closes #13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds stress and edge-case test coverage for the ECS entity recycling (generation-based) mechanism at both the entity pool and world levels, aligned with the recycling hardening work in internal/ecs/.

Changes:

  • Add high-iteration stress tests for entity pool alloc/free recycling, generation increments, stale references, free-list order, and uint32 boundary encoding.
  • Add world-level recycling tests validating stale entity access, component cleanup on recycle, and query consistency through destroy/recreate cycles.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
internal/ecs/entity_test.go Adds entity pool stress and edge-case tests (recycling, generations, free-list order, boundary encoding).
internal/ecs/world_test.go Adds world-level recycling stress/stale access/component cleanup/query consistency tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +100 to +112
const cycles = 1_000_000
for i := range cycles {
e := pool.Alloc()
if !pool.IsAlive(e) {
t.Fatalf("cycle %d: freshly allocated entity should be alive", i)
}
if !pool.Free(e) {
t.Fatalf("cycle %d: free of alive entity should succeed", i)
}
if pool.IsAlive(e) {
t.Fatalf("cycle %d: freed entity should be dead", i)
}
}

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestEntityPool_StressCreateDestroy runs 1,000,000 alloc/free cycles unconditionally. In CI this repo runs go test -race for all packages, and this kind of stress loop can significantly slow or time out the pipeline. Consider gating the high iteration count behind testing.Short() (skip or reduce cycles), or lowering the default cycles and making the full stress run opt-in (e.g., via a build tag).

Copilot uses AI. Check for mistakes.
t.Fatalf("cycle %d: destroyed entity should be dead", i)
}

// Get on dead entity must return zero value and false.

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment says “Get on dead entity must return zero value and false”, but Get[T] actually returns nil, false when the entity is not alive. Updating the comment to match the API behavior will avoid confusion for future readers.

Suggested change
// Get on dead entity must return zero value and false.
// Get on dead entity must return nil and false.

Copilot uses AI. Check for mistakes.
if e.Generation() != uint32(i) {
t.Fatalf("cycle %d: expected generation %d, got %d", i, i, e.Generation())
}
pool.Free(e)

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pool.Free(e) return value is ignored here. While the next iteration will likely fail if Free doesn’t succeed, asserting the return value directly makes the test failure clearer and pins down the exact cycle where freeing broke.

Suggested change
pool.Free(e)
if !pool.Free(e) {
t.Fatalf("cycle %d: free of entity should succeed", i)
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test: entity recycling hardening

2 participants