feat: add portable directory safeguards

This commit is contained in:
QiuSW
2026-07-28 00:12:29 +08:00
parent 92091a6690
commit 733954ddc8
15 changed files with 914 additions and 95 deletions
+43 -3
View File
@@ -54,7 +54,7 @@ func TestInstanceStarterBuildsLaunchSpecFromInstanceAndSettings(t *testing.T) {
launcher := &fakeProcessLauncher{handle: fakeProcessHandle{pid: 4242}}
resolver := &fakeExecutableResolver{path: `C:\Browser\msedge.exe`}
remote := &fakeRemoteDebugInspector{endpoint: browser.RemoteDebugEndpoint{Port: 9666}, inspectErr: browser.ErrRemoteDebugEndpointUnavailable}
starter := instanceStarter{launcher: launcher, resolver: resolver, managedProfiles: fakeManagedProfileInspector{}, externalProfiles: fakeExternalProfileInspector{}, remoteDebug: remote, portAllocator: remote}
starter := instanceStarter{launcher: launcher, resolver: resolver, managedProfiles: fakeManagedProfileInspector{}, externalProfiles: fakeExternalProfileInspector{}, remoteDebug: remote, portAllocator: remote, dataPreparer: &fakeInstanceDataPreparer{}}
outcome, err := starter.Start(context.Background(), ui.InstanceRow{ID: "edge-a", Browser: "Edge", UserDataDir: `C:\profiles\edge-a`}, ui.SettingsState{EdgePath: `C:\Configured\msedge.exe`, RemoteDebugStartPort: 9666}, 1)
if err != nil || outcome.PID != 4242 || outcome.RemoteDebugPort != 9666 {
t.Fatalf("Start() = %#v, %v", outcome, err)
@@ -67,6 +67,28 @@ func TestInstanceStarterBuildsLaunchSpecFromInstanceAndSettings(t *testing.T) {
}
}
func TestInstanceStarterRejectsUnwritableDataDirectoryBeforeLaunch(t *testing.T) {
launcher := &fakeProcessLauncher{handle: fakeProcessHandle{pid: 4242}}
preparer := &fakeInstanceDataPreparer{err: files.ErrInstanceDataDirectoryUnavailable}
remote := &fakeRemoteDebugInspector{endpoint: browser.RemoteDebugEndpoint{Port: 9666}, inspectErr: browser.ErrRemoteDebugEndpointUnavailable}
starter := instanceStarter{
launcher: launcher,
resolver: &fakeExecutableResolver{path: `C:\Browser\chrome.exe`},
managedProfiles: fakeManagedProfileInspector{},
externalProfiles: fakeExternalProfileInspector{},
remoteDebug: remote,
portAllocator: remote,
dataPreparer: preparer,
}
err := func() error {
_, err := starter.Start(context.Background(), ui.InstanceRow{ID: "chrome-a", Browser: "Chrome", UserDataDir: `C:\profiles\chrome-a`}, ui.SettingsState{RemoteDebugStartPort: 9666}, 1)
return err
}()
if !errors.Is(err, files.ErrInstanceDataDirectoryUnavailable) || launcher.calls != 0 || preparer.calls != 1 {
t.Fatalf("Start() error = %v, launcher calls = %d, preparer calls = %d", err, launcher.calls, preparer.calls)
}
}
func TestInstanceStarterUsesPreferredPortAndSkipsOtherInstancesReservations(t *testing.T) {
launcher := &fakeProcessLauncher{handle: fakeProcessHandle{pid: 4242}}
remote := &fakeRemoteDebugInspector{
@@ -81,6 +103,7 @@ func TestInstanceStarterUsesPreferredPortAndSkipsOtherInstancesReservations(t *t
externalProfiles: fakeExternalProfileInspector{},
remoteDebug: remote,
portAllocator: remote,
dataPreparer: &fakeInstanceDataPreparer{},
}
outcome, err := starter.Start(context.Background(), ui.InstanceRow{ID: "chrome-a", Browser: "Chrome", UserDataDir: `C:\profiles\chrome-a`, PreferredRemoteDebugPort: 9666}, ui.SettingsState{RemoteDebugStartPort: 9777, ReservedRemoteDebugPorts: []int{9667}}, 1)
if err != nil || outcome.RemoteDebugPort != 9668 || launcher.spec.RemoteDebugPort != 9668 {
@@ -97,7 +120,7 @@ func TestInstanceStarterResolvesSelectedProxyAtLaunch(t *testing.T) {
remote := &fakeRemoteDebugInspector{endpoint: browser.RemoteDebugEndpoint{Port: 9666}, inspectErr: browser.ErrRemoteDebugEndpointUnavailable}
proxies := newProxyDirectory()
proxies.Set([]config.ProxyProfile{{ID: "proxy-sg", Name: "SG", Server: "http://127.0.0.1:8080"}})
starter := instanceStarter{launcher: launcher, resolver: resolver, managedProfiles: fakeManagedProfileInspector{}, externalProfiles: fakeExternalProfileInspector{}, remoteDebug: remote, portAllocator: remote, proxyResolver: proxies}
starter := instanceStarter{launcher: launcher, resolver: resolver, managedProfiles: fakeManagedProfileInspector{}, externalProfiles: fakeExternalProfileInspector{}, remoteDebug: remote, portAllocator: remote, proxyResolver: proxies, dataPreparer: &fakeInstanceDataPreparer{}}
_, err := starter.Start(context.Background(), ui.InstanceRow{ID: "chrome-a", Browser: "Chrome", UserDataDir: `C:\profiles\chrome-a`, ProxyID: "proxy-sg"}, ui.SettingsState{RemoteDebugStartPort: 9666}, 1)
if err != nil || launcher.spec.ProxyServer != "http://127.0.0.1:8080" {
t.Fatalf("proxy launch spec = %#v, error = %v", launcher.spec, err)
@@ -112,7 +135,7 @@ func TestInstanceStarterRegistersOnlyLaunchedProcessWithExitMonitor(t *testing.T
launcher := &fakeProcessLauncher{handle: fakeProcessHandle{pid: 4242}}
remote := &fakeRemoteDebugInspector{endpoint: browser.RemoteDebugEndpoint{Port: 9666}, inspectErr: browser.ErrRemoteDebugEndpointUnavailable}
monitor := &fakeManagedExitObserver{}
starter := instanceStarter{launcher: launcher, resolver: &fakeExecutableResolver{path: `C:\Browser\chrome.exe`}, managedProfiles: fakeManagedProfileInspector{}, externalProfiles: fakeExternalProfileInspector{}, remoteDebug: remote, portAllocator: remote, monitor: monitor}
starter := instanceStarter{launcher: launcher, resolver: &fakeExecutableResolver{path: `C:\Browser\chrome.exe`}, managedProfiles: fakeManagedProfileInspector{}, externalProfiles: fakeExternalProfileInspector{}, remoteDebug: remote, portAllocator: remote, monitor: monitor, dataPreparer: &fakeInstanceDataPreparer{}}
_, err := starter.Start(context.Background(), ui.InstanceRow{ID: "chrome-a", Browser: "Chrome", UserDataDir: `C:\profiles\chrome-a`}, ui.SettingsState{RemoteDebugStartPort: 9666}, 7)
if err != nil || monitor.watchID != "chrome-a" || monitor.watchGeneration != 7 || monitor.watchPID != 4242 {
t.Fatalf("monitor watch = %#v, error = %v", monitor, err)
@@ -190,6 +213,7 @@ func TestInstanceStarterAssociatesVerifiedExternalProfileWithoutLaunching(t *tes
externalProfiles: fakeExternalProfileInspector{use: browser.ProfileUse{Occupied: true, PID: 16108, Source: browser.ProfileSourceMessageWindow}},
remoteDebug: remote,
portAllocator: remote,
dataPreparer: &fakeInstanceDataPreparer{},
}
outcome, err := starter.Start(context.Background(), ui.InstanceRow{ID: "chrome-a", Browser: "Chrome", UserDataDir: `C:\profiles\chrome-a`}, ui.SettingsState{RemoteDebugStartPort: 9666}, 1)
if err != nil || !outcome.External || outcome.PID != 16108 || outcome.RemoteDebugPort != 9668 || outcome.Source != browser.ProfileSourceMessageWindow {
@@ -210,6 +234,7 @@ func TestInstanceStarterRejectsUnverifiedExternalProfile(t *testing.T) {
externalProfiles: fakeExternalProfileInspector{use: browser.ProfileUse{Occupied: true, PID: 16108, Source: browser.ProfileSourceLock}},
remoteDebug: remote,
portAllocator: remote,
dataPreparer: &fakeInstanceDataPreparer{},
}
_, err := starter.Start(context.Background(), ui.InstanceRow{ID: "chrome-a", Browser: "Chrome", UserDataDir: `C:\profiles\chrome-a`}, ui.SettingsState{RemoteDebugStartPort: 9666}, 1)
if !errors.Is(err, domain.ErrProfileOccupied) {
@@ -482,6 +507,21 @@ func (r *fakeInstanceDataRemover) RemoveInstanceData(ctx context.Context, userDa
return r.err
}
type fakeInstanceDataPreparer struct {
calls int
userDataDir string
err error
}
func (p *fakeInstanceDataPreparer) PrepareInstanceDataDir(ctx context.Context, userDataDir string) error {
if err := ctx.Err(); err != nil {
return err
}
p.calls++
p.userDataDir = userDataDir
return p.err
}
type fakeManagedStopper struct {
use browser.ProfileUse
stopCalls int