mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
The review flagged map-iteration nondeterminism as a possible cause of unstable bundle SHAs. Empirically esbuild's Define handling is order- independent (parallel substitution rules), so output is already stable. Add a regression test that builds 10x with multiple Defines and asserts SHA equality so any future change that introduces ordering surfaces.
131 lines
3.7 KiB
Go
131 lines
3.7 KiB
Go
package bundler
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const fakeRuntime = `
|
|
(globalThis as Record<string, unknown>).__sanderling__ = {
|
|
extract: (g: () => unknown) => ({ current: g(), previous: undefined }),
|
|
always: () => ({ __sanderlingFormula: true }),
|
|
now: () => ({ __sanderlingFormula: true }),
|
|
next: () => ({ __sanderlingFormula: true }),
|
|
eventually: () => ({ __sanderlingFormula: true }),
|
|
actions: (g: () => unknown) => ({ __sanderlingActionGenerator: true, generate: g }),
|
|
weighted: () => ({ __sanderlingActionGenerator: true }),
|
|
from: () => ({ generate: () => null }),
|
|
tap: () => null,
|
|
inputText: () => null,
|
|
swipe: () => null,
|
|
pressKey: () => null,
|
|
wait: () => null,
|
|
taps: {},
|
|
swipes: {},
|
|
waitOnce: {},
|
|
pressKeys: {},
|
|
};
|
|
(globalThis as Record<string, unknown>).__sanderlingExtractors__ = () => ({});
|
|
(globalThis as Record<string, unknown>).__sanderlingNextAction__ = () => null;
|
|
export {};
|
|
`
|
|
|
|
const fakeSpec = `
|
|
const handle = (globalThis as { __sanderling__: { extract: (g: () => unknown) => unknown } }).__sanderling__.extract(() => 42);
|
|
(globalThis as { actions?: unknown }).actions = handle;
|
|
export {};
|
|
`
|
|
|
|
func TestBundleWeb_RegistersExpectedGlobals(t *testing.T) {
|
|
directory := t.TempDir()
|
|
runtimePath := filepath.Join(directory, "web-runtime.ts")
|
|
specPath := filepath.Join(directory, "spec.ts")
|
|
if err := os.WriteFile(runtimePath, []byte(fakeRuntime), 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,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BundleWeb: %v", err)
|
|
}
|
|
source := string(result.JavaScript)
|
|
for _, expected := range []string{
|
|
"__sanderlingExtractors__",
|
|
"__sanderlingNextAction__",
|
|
"__sanderling__",
|
|
} {
|
|
if !strings.Contains(source, expected) {
|
|
t.Errorf("bundle missing %q\nsource head:\n%s", expected, head(source, 500))
|
|
}
|
|
}
|
|
if !strings.HasPrefix(strings.TrimSpace(source), "(()") {
|
|
t.Errorf("expected IIFE format, head:\n%s", head(source, 200))
|
|
}
|
|
if result.SHA256 == "" {
|
|
t.Errorf("expected non-empty sha256")
|
|
}
|
|
}
|
|
|
|
func TestBundleWeb_RejectsMissingEntry(t *testing.T) {
|
|
if _, err := BundleWeb(WebOptions{}); err == nil {
|
|
t.Error("expected error for empty options")
|
|
}
|
|
if _, err := BundleWeb(WebOptions{EntryFile: "x.ts"}); err == nil {
|
|
t.Error("expected error for missing runtime")
|
|
}
|
|
}
|
|
|
|
// TestBundleWeb_IsDeterministic builds the same source repeatedly with multiple
|
|
// Defines entries and asserts the SHA matches. This guards against any future
|
|
// change that introduces map-iteration ordering into the bundle output.
|
|
func TestBundleWeb_IsDeterministic(t *testing.T) {
|
|
directory := t.TempDir()
|
|
runtimePath := filepath.Join(directory, "web-runtime.ts")
|
|
specPath := filepath.Join(directory, "spec.ts")
|
|
if err := os.WriteFile(runtimePath, []byte(fakeRuntime), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(specPath, []byte(fakeSpec), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
options := WebOptions{
|
|
EntryFile: specPath,
|
|
WebRuntimeFile: runtimePath,
|
|
Defines: map[string]string{
|
|
"API_BASE": "https://example.com",
|
|
"FEATURE": "true",
|
|
"VERSION": "1.2.3",
|
|
"TENANT": "alpha",
|
|
},
|
|
}
|
|
first, err := BundleWeb(options)
|
|
if err != nil {
|
|
t.Fatalf("first build: %v", err)
|
|
}
|
|
for attempt := range 10 {
|
|
repeat, err := BundleWeb(options)
|
|
if err != nil {
|
|
t.Fatalf("attempt %d: %v", attempt, err)
|
|
}
|
|
if repeat.SHA256 != first.SHA256 {
|
|
t.Fatalf("attempt %d: SHA changed (%s vs %s)", attempt, repeat.SHA256, first.SHA256)
|
|
}
|
|
}
|
|
}
|
|
|
|
func head(text string, max int) string {
|
|
if len(text) <= max {
|
|
return text
|
|
}
|
|
return text[:max] + "..."
|
|
}
|