| index dfe7251..a765d70 100644 |
| --- a/internal/core/core_internal_test.go |
| +++ b/internal/core/core_internal_test.go |
| @@ -54,6 +54,8 @@ import ( |
| "path/filepath" |
| "testing" |
| "time" |
| + |
| + "github.com/tetratelabs/wazero/api" |
| ) |
| |
| // rng and mutate below are a byte-for-byte port of |
| @@ -305,54 +307,60 @@ func TestSlowUpdateIsCutOffByGuestCallTimeout(t *testing.T) { |
| t.Fatalf("expected the default callTimeout (%v), got %v", defaultGuestCallTimeout, rt.callTimeout) |
| } |
| |
| - mod, err := rt.instantiate(ctx) |
| - if err != nil { |
| - t.Fatalf("instantiate: %v", err) |
| - } |
| + warm := func() (api.Module, uint64, []byte) { |
| + t.Helper() |
| + mod, err := rt.instantiate(ctx) |
| + if err != nil { |
| + t.Fatalf("instantiate: %v", err) |
| + } |
| |
| - fixtureNames := []string{"hello", "unicode", "formatted", "concurrent"} |
| - fixtures := make([][]byte, len(fixtureNames)) |
| - for i, name := range fixtureNames { |
| - fixtures[i] = loadFixtureBytes(t, name) |
| - } |
| + fixtureNames := []string{"hello", "unicode", "formatted", "concurrent"} |
| + fixtures := make([][]byte, len(fixtureNames)) |
| + for i, name := range fixtureNames { |
| + fixtures[i] = loadFixtureBytes(t, name) |
| + } |
| |
| - // Warm up: replay trials 0..1132 exactly as |
| - // wasm_trap_containment.rs's fuzz loop does (fixture pick, mutate, |
| - // doc_new, apply), ignoring the outcome of each — most are clean |
| - // oks or clean rejections, and this loop must not go through |
| - // Handle.Apply (which would auto-rebuild on any genuine trap here |
| - // and undo the accumulated memory growth this test depends on). |
| - r := &rng{x: 0xC0FFEE} |
| - for i := 0; i < 1133; i++ { |
| + // Warm up: replay trials 0..1132 exactly as |
| + // wasm_trap_containment.rs's fuzz loop does (fixture pick, mutate, |
| + // doc_new, apply), ignoring the outcome of each — most are clean |
| + // oks or clean rejections, and this loop must not go through |
| + // Handle.Apply (which would auto-rebuild on any genuine trap here |
| + // and undo the accumulated memory growth this test depends on). |
| + r := &rng{x: 0xC0FFEE} |
| + for i := 0; i < 1133; i++ { |
| + fi := r.nextUsize(len(fixtures)) |
| + mutated := mutate(r, fixtures[fi]) |
| + docHandle, err := createDoc(ctx, rt.callTimeout, mod, fmt.Sprintf("warmup-%d", i), uint64(2_000_000+i)) |
| + if err != nil { |
| + t.Fatalf("warm-up trial %d: doc_new failed unexpectedly: %v", i, err) |
| + } |
| + _ = applyOn(ctx, rt.callTimeout, mod, docHandle, mutated) // outcome irrelevant; only the memory growth matters |
| + } |
| + |
| + // Trial 1133 itself: the exact reproducer documented in |
| + // core/tests/wasm_trap_containment.rs ("a 3-byte-flipped `concurrent` |
| + // fixture ... ran for 163 seconds of real CPU time"). Confirm the RNG |
| + // sequence still lands on the documented bytes before relying on them. |
| fi := r.nextUsize(len(fixtures)) |
| mutated := mutate(r, fixtures[fi]) |
| - docHandle, err := createDoc(ctx, rt.callTimeout, mod, fmt.Sprintf("warmup-%d", i), uint64(2_000_000+i)) |
| - if err != nil { |
| - t.Fatalf("warm-up trial %d: doc_new failed unexpectedly: %v", i, err) |
| + const wantHex = "02010200040107636f6e6f656e7403424293010100040107636f6e74656e74034141418a" |
| + if got := hex.EncodeToString(mutated); got != wantHex { |
| + t.Fatalf("RNG/mutate drifted from core/tests/wasm_trap_containment.rs: trial 1133 produced "+ |
| + "fixture=%s bytes=%s, want the documented bytes=%s — this test no longer exercises the "+ |
| + "known-slow reproducer", fixtureNames[fi], got, wantHex) |
| + } |
| + if fixtureNames[fi] != "concurrent" { |
| + t.Fatalf("expected trial 1133 to mutate the concurrent fixture, got %s", fixtureNames[fi]) |
| } |
| - _ = applyOn(ctx, rt.callTimeout, mod, docHandle, mutated) // outcome irrelevant; only the memory growth matters |
| - } |
| |
| - // Trial 1133 itself: the exact reproducer documented in |
| - // core/tests/wasm_trap_containment.rs ("a 3-byte-flipped `concurrent` |
| - // fixture ... ran for 163 seconds of real CPU time"). Confirm the RNG |
| - // sequence still lands on the documented bytes before relying on them. |
| - fi := r.nextUsize(len(fixtures)) |
| - mutated := mutate(r, fixtures[fi]) |
| - const wantHex = "02010200040107636f6e6f656e7403424293010100040107636f6e74656e74034141418a" |
| - if got := hex.EncodeToString(mutated); got != wantHex { |
| - t.Fatalf("RNG/mutate drifted from core/tests/wasm_trap_containment.rs: trial 1133 produced "+ |
| - "fixture=%s bytes=%s, want the documented bytes=%s — this test no longer exercises the "+ |
| - "known-slow reproducer", fixtureNames[fi], got, wantHex) |
| - } |
| - if fixtureNames[fi] != "concurrent" { |
| - t.Fatalf("expected trial 1133 to mutate the concurrent fixture, got %s", fixtureNames[fi]) |
| - } |
| + docHandle, err := createDoc(ctx, rt.callTimeout, mod, "trial-1133", 2_000_000+1133) |
| + if err != nil { |
| + t.Fatalf("creating the document under test: %v", err) |
| + } |
| |
| - docHandle, err := createDoc(ctx, rt.callTimeout, mod, "trial-1133", 2_000_000+1133) |
| - if err != nil { |
| - t.Fatalf("creating the document under test: %v", err) |
| + return mod, docHandle, mutated |
| } |
| + mod, docHandle, mutated := warm() |
| |
| // Sanity: confirm this input is genuinely slow (not just a fast trap) |
| // under wazero's compiler engine at this memory-growth state, with a |
| @@ -375,31 +383,21 @@ func TestSlowUpdateIsCutOffByGuestCallTimeout(t *testing.T) { |
| "it returned in %v instead — the reproducer may have stopped reproducing, which would make "+ |
| "the capped assertion below meaningless", sanityElapsed) |
| } |
| - t.Logf("uncapped: pathological update ran %v before failing on its own (%v)", sanityElapsed, sanityErr) |
| - |
| - // The actual assertion: a fresh document on the same (already grown) |
| - // instance, wired through the exported Handle so this exercises the |
| - // real Apply()/rebuild() path, called with a plain context carrying no |
| - // deadline of its own — so what cuts this call off, if anything does, |
| - // is exactly rt.callTimeout (the default, 1s), not a contrived |
| - // caller-side budget. That is the point fix-round-1 finding 2 raised: |
| - // the previous 5s default was close enough to this input's ~4.34s |
| - // natural running time that the cap wasn't reliably doing anything for |
| - // its own motivating case. 1s should cut it off at roughly a quarter |
| - // of that. |
| - docHandle2, err := createDoc(ctx, rt.callTimeout, mod, "trial-1133b", 2_000_000+11330) |
| - if err != nil { |
| - t.Fatalf("creating the second document under test: %v", err) |
| - } |
| - handle := &Handle{rt: rt, guid: "trial-1133b", clientID: 2_000_000 + 11330, mod: mod, handle: docHandle2} |
| + t.Logf("sanity probe: pathological update failed or reached its 20s bound after %v (%v)", sanityElapsed, sanityErr) |
| + _ = mod.Close(ctx) |
| + |
| + // Repeat the exact warm-up independently: a timed-out sanity probe closes |
| + // its module, and a fresh but unwarmed module would lose the reproducer. |
| + mod, docHandle, mutated = warm() |
| + handle := &Handle{rt: rt, guid: "trial-1133", clientID: 2_000_000 + 1133, mod: mod, handle: docHandle} |
| oldMod := handle.mod |
| |
| start := time.Now() |
| applyErr := handle.Apply(ctx, mutated) |
| elapsed := time.Since(start) |
| |
| - if applyErr == nil { |
| - t.Fatalf("expected the capped Apply to fail (it should have been cut off before completing), got nil error after %v", elapsed) |
| + if !errors.Is(applyErr, context.DeadlineExceeded) { |
| + t.Fatalf("expected the capped Apply to reach its deadline, got %v after %v", applyErr, elapsed) |
| } |
| // Generous upper bound: the call must not have been allowed to run |
| // anywhere near its natural ~4.34s completion time. |