mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
test: cover web-runtime seeded PRNG, weighted pick, and seed define wiring
This commit is contained in:
1 parent
6fc1c971c7
commit
d4ffdce8f9
3 files changed
+123
No files matched your search
@@ -122,6 +122,38 @@ func TestBundleWeb_IsDeterministic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestBundleWeb_InjectsSeedDefine asserts that a SANDERLING_SEED define is
|
||||||
|
// inlined into the bundle so --seed reaches the web runtime PRNG. esbuild
|
||||||
|
// replaces process.env.SANDERLING_SEED with the quoted literal at build time.
|
||||||
|
func TestBundleWeb_InjectsSeedDefine(t *testing.T) {
|
||||||
|
directory := t.TempDir()
|
||||||
|
runtimePath := filepath.Join(directory, "web-runtime.ts")
|
||||||
|
specPath := filepath.Join(directory, "spec.ts")
|
||||||
|
runtime := fakeRuntime + "\n(globalThis as Record<string, unknown>).__seed = process.env.SANDERLING_SEED;\n"
|
||||||
|
if err := os.WriteFile(runtimePath, []byte(runtime), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(specPath, []byte(fakeSpec), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := BundleWeb(WebOptions{
|
||||||
|
EntryFile: specPath,
|
||||||
|
WebRuntimeFile: runtimePath,
|
||||||
|
Defines: map[string]string{"SANDERLING_SEED": "8675309"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BundleWeb: %v", err)
|
||||||
|
}
|
||||||
|
source := string(result.JavaScript)
|
||||||
|
if !strings.Contains(source, "8675309") {
|
||||||
|
t.Errorf("seed literal not inlined into bundle\nsource head:\n%s", head(source, 500))
|
||||||
|
}
|
||||||
|
if strings.Contains(source, "process.env.SANDERLING_SEED") {
|
||||||
|
t.Errorf("define left unsubstituted in bundle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func head(text string, max int) string {
|
func head(text string, max int) string {
|
||||||
if len(text) <= max {
|
if len(text) <= max {
|
||||||
return text
|
return text
|
||||||
|
|||||||
@@ -3,9 +3,31 @@ package testrun
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestResolveSeed_UsesConfiguredWhenNonZero(t *testing.T) {
|
||||||
|
if got := resolveSeed(42); got != 42 {
|
||||||
|
t.Fatalf("got %d, want 42", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveSeed_DerivesWhenZero(t *testing.T) {
|
||||||
|
if got := resolveSeed(0); got == 0 {
|
||||||
|
t.Fatal("expected a non-zero time-derived seed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSeedDefineFormatsAsDecimal guards the contract that Execute uses to put
|
||||||
|
// the seed into the web bundle: strconv.FormatInt base 10, which web-runtime
|
||||||
|
// folds to a 32-bit PRNG seed.
|
||||||
|
func TestSeedDefineFormatsAsDecimal(t *testing.T) {
|
||||||
|
if got := strconv.FormatInt(resolveSeed(8675309), 10); got != "8675309" {
|
||||||
|
t.Fatalf("got %q, want 8675309", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResolveSpecAPIPath_FindsUpwardSibling(t *testing.T) {
|
func TestResolveSpecAPIPath_FindsUpwardSibling(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
apiPath := filepath.Join(root, "pkg", "spec", "src", "index.ts")
|
apiPath := filepath.Join(root, "pkg", "spec", "src", "index.ts")
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { test } from "node:test";
|
||||||
|
|
||||||
|
import { __testing__ } from "../src/web-runtime.ts";
|
||||||
|
|
||||||
|
const { mulberry32, deriveSeed32, pickWeighted, WEB_PRESS_KEYS } = __testing__;
|
||||||
|
|
||||||
|
function entry(weight: number, kind: string) {
|
||||||
|
return [weight, { __sanderlingActionGenerator: true, __sanderlingKind: kind }] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
test("mulberry32 is deterministic for the same seed", () => {
|
||||||
|
const a = mulberry32(deriveSeed32("12345"));
|
||||||
|
const b = mulberry32(deriveSeed32("12345"));
|
||||||
|
const first = Array.from({ length: 8 }, () => a());
|
||||||
|
const second = Array.from({ length: 8 }, () => b());
|
||||||
|
assert.deepEqual(first, second);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("mulberry32 diverges for different seeds", () => {
|
||||||
|
const a = Array.from({ length: 8 }, mulberry32(deriveSeed32("1")));
|
||||||
|
const b = Array.from({ length: 8 }, mulberry32(deriveSeed32("2")));
|
||||||
|
assert.notDeepEqual(a, b);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("mulberry32 stays in [0, 1)", () => {
|
||||||
|
const next = mulberry32(deriveSeed32("seed"));
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
const value = next();
|
||||||
|
assert.ok(value >= 0 && value < 1, `value out of range: ${value}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deriveSeed32 folds a 64-bit seed without precision loss", () => {
|
||||||
|
// Two distinct 64-bit values that collide as a JS Number must not collide here.
|
||||||
|
const a = deriveSeed32("9007199254740993");
|
||||||
|
const b = deriveSeed32("9007199254740992");
|
||||||
|
assert.notEqual(a, b);
|
||||||
|
assert.equal(deriveSeed32(undefined), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("pickWeighted selects by cumulative ascending scan", () => {
|
||||||
|
const handle = { __sanderlingActionGenerator: true, __sanderlingKind: "weighted", entries: [entry(1, "a"), entry(3, "b")] } as const;
|
||||||
|
// total = 4. pick = 0.1 -> 0.4 lands in [0,1) -> first; pick = 0.5 -> 2.0 lands in [1,4) -> second.
|
||||||
|
assert.equal(pickWeighted(handle as never, () => 0.1)?.__sanderlingKind, "a");
|
||||||
|
assert.equal(pickWeighted(handle as never, () => 0.5)?.__sanderlingKind, "b");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("pickWeighted never selects a zero-weight entry when pick lands before it", () => {
|
||||||
|
const handle = { __sanderlingActionGenerator: true, __sanderlingKind: "weighted", entries: [entry(1, "a"), entry(0, "zero"), entry(1, "b")] } as const;
|
||||||
|
// total = 2. Any pick < 0.5 lands strictly inside the first entry's [0,1) band.
|
||||||
|
for (const r of [0, 0.1, 0.25, 0.49]) {
|
||||||
|
assert.equal(pickWeighted(handle as never, () => r)?.__sanderlingKind, "a");
|
||||||
|
}
|
||||||
|
// A zero-weight entry adds 0 to the cumulative, so it can never win.
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
const r = i / 100;
|
||||||
|
assert.notEqual(pickWeighted(handle as never, () => r)?.__sanderlingKind, "zero");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("seeded press-key draw is reproducible", () => {
|
||||||
|
const draw = (seed: string) => {
|
||||||
|
const next = mulberry32(deriveSeed32(seed));
|
||||||
|
return Array.from({ length: 12 }, () => WEB_PRESS_KEYS[Math.floor(next() * WEB_PRESS_KEYS.length)]);
|
||||||
|
};
|
||||||
|
assert.deepEqual(draw("777"), draw("777"));
|
||||||
|
assert.notDeepEqual(draw("777"), draw("778"));
|
||||||
|
});
|
||||||
Reference in new issue
Block a user