feat: add scoped browser stop and job control

This commit is contained in:
QiuSW
2026-07-22 15:03:21 +08:00
parent d6f6bb0f2c
commit 1ce02bcf3b
8 changed files with 262 additions and 14 deletions
+33 -4
View File
@@ -6,6 +6,7 @@ import (
"path/filepath"
"sync"
"testing"
"time"
"chub/internal/domain"
)
@@ -42,6 +43,7 @@ func TestLauncherSeparatesProfilesBlocksDuplicatesAndReleasesAfterExit(t *testin
if err == nil && !use.Occupied {
break
}
time.Sleep(time.Millisecond)
}
if use.Occupied {
t.Fatalf("profile remained occupied after exit: %+v", use)
@@ -70,6 +72,24 @@ func TestProcessWaitCancellationDoesNotTerminateBrowser(t *testing.T) {
}
}
func TestLauncherStopProfileTargetsOnlyRegisteredProfile(t *testing.T) {
runner := &fakeRunner{}
launcher := NewLauncher(runner)
profile := filepath.Join(t.TempDir(), "profile")
if _, err := launcher.Start(context.Background(), launchSpec(profile)); err != nil {
t.Fatalf("Start() error = %v", err)
}
if err := launcher.StopProfile(context.Background(), profile, false); err != nil {
t.Fatalf("StopProfile() error = %v", err)
}
if runner.processAt(0).stopCount != 1 || runner.processAt(0).stopForce {
t.Fatalf("stop state = count %d force %v", runner.processAt(0).stopCount, runner.processAt(0).stopForce)
}
if err := launcher.StopProfile(context.Background(), filepath.Join(t.TempDir(), "other"), true); !errors.Is(err, domain.ErrInstanceNotFound) {
t.Fatalf("unknown profile error = %v", err)
}
}
func launchSpec(profile string) domain.LaunchSpec {
return domain.LaunchSpec{Kind: domain.BrowserChrome, Executable: `C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe`, UserDataDir: profile, TargetURL: "https://example.com"}
}
@@ -93,10 +113,12 @@ func (r *fakeRunner) processAt(index int) *fakeProcess {
}
type fakeProcess struct {
pid int
done chan processResult
mu sync.Mutex
finished bool
pid int
done chan processResult
mu sync.Mutex
finished bool
stopCount int
stopForce bool
}
type processResult struct {
code int
@@ -108,6 +130,13 @@ func newFakeProcess(pid int) *fakeProcess {
}
func (p *fakeProcess) PID() int { return p.pid }
func (p *fakeProcess) Wait() (int, error) { result := <-p.done; return result.code, result.err }
func (p *fakeProcess) Stop(_ context.Context, force bool) error {
p.mu.Lock()
defer p.mu.Unlock()
p.stopCount++
p.stopForce = force
return nil
}
func (p *fakeProcess) finish(code int, err error) {
p.mu.Lock()
defer p.mu.Unlock()