package windows import ( "context" "errors" "path/filepath" "testing" "time" ) func TestPlatformStubContract(t *testing.T) { platform := New() if platform.OS() == "" { t.Fatal("OS() should not be empty") } if platform.Edition() != EditionModern { t.Fatalf("Edition() = %q, want %q", platform.Edition(), EditionModern) } } func TestEntrypointIsRunningUsesFullPathIdentity(t *testing.T) { root := t.TempDir() target := filepath.Join(root, "one", "App.exe") collision := filepath.Join(root, "two", "App.exe") running, err := entrypointIsRunning(target, func() (processSnapshot, error) { return &scriptedSnapshot{items: []snapshotItem{{path: collision}}}, nil }) if err != nil { t.Fatalf("entrypointIsRunning() error = %v", err) } if running { t.Fatal("entrypointIsRunning() matched a same-basename executable in another directory") } running, err = entrypointIsRunning(target, func() (processSnapshot, error) { return &scriptedSnapshot{items: []snapshotItem{{path: collision}, {path: target}}}, nil }) if err != nil || !running { t.Fatalf("entrypointIsRunning() = (%v, %v), want (true, nil)", running, err) } } func TestEntrypointIsRunningFailsClosedForSnapshotErrors(t *testing.T) { expected := errors.New("snapshot failed") _, err := entrypointIsRunning("App.exe", func() (processSnapshot, error) { return nil, expected }) if !errors.Is(err, expected) { t.Fatalf("entrypointIsRunning() error = %v, want %v", err, expected) } _, err = entrypointIsRunning("App.exe", func() (processSnapshot, error) { return &scriptedSnapshot{items: []snapshotItem{{err: expected}}}, nil }) if !errors.Is(err, expected) { t.Fatalf("entrypointIsRunning() error = %v, want %v", err, expected) } } func TestVersionSupports(t *testing.T) { cases := []struct { minOS string major, minor, build, service uint32 want bool wantErr bool }{ {minOS: "windows-7-sp1", major: 6, minor: 1, service: 1, want: true}, {minOS: "windows-7-sp1", major: 6, minor: 1, service: 0, want: false}, {minOS: "windows-10", major: 10, want: true}, {minOS: "windows-11", major: 10, build: 19045, want: false}, {minOS: "windows-11", major: 10, build: 22000, want: true}, {minOS: "unknown", wantErr: true}, } for _, test := range cases { got, err := versionSupports(test.minOS, test.major, test.minor, test.build, test.service) if (err != nil) != test.wantErr || got != test.want { t.Fatalf("versionSupports(%q, %d, %d, %d, %d) = (%v, %v), want (%v, error=%v)", test.minOS, test.major, test.minor, test.build, test.service, got, err, test.want, test.wantErr) } } } func TestWaitForExit(t *testing.T) { initial := time.Date(2026, 7, 19, 0, 0, 0, 0, time.UTC) errSnapshot := errors.New("snapshot failed") tests := []struct { name string ctx context.Context running []bool runErr error timeout time.Duration wantErr error wantSleeps int }{ {name: "already stopped", ctx: context.Background(), running: []bool{false}, timeout: time.Second}, {name: "stops after one poll", ctx: context.Background(), running: []bool{true, false}, timeout: time.Second, wantSleeps: 1}, {name: "timeout", ctx: context.Background(), running: []bool{true, true, true, true, true}, timeout: time.Second, wantErr: context.DeadlineExceeded, wantSleeps: 4}, {name: "snapshot failure", ctx: context.Background(), runErr: errSnapshot, timeout: time.Second, wantErr: errSnapshot}, {name: "canceled", ctx: canceledWaitContext(), running: []bool{true}, timeout: time.Second, wantErr: context.Canceled}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { clock := &fakeExitWaitClock{now: initial} index := 0 err := waitForExit(test.ctx, test.timeout, func() (bool, error) { if test.runErr != nil { return false, test.runErr } if index >= len(test.running) { return test.running[len(test.running)-1], nil } running := test.running[index] index++ return running, nil }, clock) if !errors.Is(err, test.wantErr) { t.Fatalf("waitForExit() error = %v, want %v", err, test.wantErr) } if clock.sleeps != test.wantSleeps { t.Fatalf("sleeps = %d, want %d", clock.sleeps, test.wantSleeps) } }) } } func canceledWaitContext() context.Context { ctx, cancel := context.WithCancel(context.Background()) cancel() return ctx } type fakeExitWaitClock struct { now time.Time sleeps int } func (clock *fakeExitWaitClock) Now() time.Time { return clock.now } func (clock *fakeExitWaitClock) Wait(ctx context.Context, duration time.Duration) error { if err := ctx.Err(); err != nil { return err } clock.sleeps++ clock.now = clock.now.Add(duration) return nil } type snapshotItem struct { path string err error } type scriptedSnapshot struct { items []snapshotItem next int } func (snapshot *scriptedSnapshot) NextImagePath() (string, bool, error) { if snapshot.next == len(snapshot.items) { return "", false, nil } item := snapshot.items[snapshot.next] snapshot.next++ return item.path, true, item.err } func (*scriptedSnapshot) Close() error { return nil }