feat: add preferred debug port planning
This commit is contained in:
@@ -25,11 +25,12 @@ type Settings struct {
|
||||
}
|
||||
|
||||
type Instance struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ProxyID string `json:"proxyId,omitempty"`
|
||||
Launch domain.LaunchSpec `json:"launch"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ProxyID string `json:"proxyId,omitempty"`
|
||||
PreferredRemoteDebugPort int `json:"preferredRemoteDebugPort,omitempty"`
|
||||
Launch domain.LaunchSpec `json:"launch"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// ProxyProfile is deliberately limited to a display name and a validated,
|
||||
@@ -88,6 +89,9 @@ func (s *Store) Load() (File, error) {
|
||||
if result.Settings.RemoteDebugStartPort == 0 {
|
||||
result.Settings.RemoteDebugStartPort = domain.DefaultRemoteDebugPort
|
||||
}
|
||||
if err := normalizeInstanceRemoteDebugPorts(&result); err != nil {
|
||||
return File{}, err
|
||||
}
|
||||
if err := normalizeProxyConfig(&result); err != nil {
|
||||
return File{}, err
|
||||
}
|
||||
@@ -107,12 +111,18 @@ func DefaultSettings() Settings {
|
||||
|
||||
func (s *Store) Save(value File) error {
|
||||
value.Version = currentVersion
|
||||
if value.Settings.RemoteDebugStartPort == 0 {
|
||||
value.Settings.RemoteDebugStartPort = domain.DefaultRemoteDebugPort
|
||||
}
|
||||
if value.Instances == nil {
|
||||
value.Instances = []Instance{}
|
||||
}
|
||||
if value.Proxies == nil {
|
||||
value.Proxies = []ProxyProfile{}
|
||||
}
|
||||
if err := normalizeInstanceRemoteDebugPorts(&value); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := normalizeProxyConfig(&value); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -150,6 +160,51 @@ func (s *Store) Save(value File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeInstanceRemoteDebugPorts(value *File) error {
|
||||
if value == nil {
|
||||
return errors.New("config is required")
|
||||
}
|
||||
start := value.Settings.RemoteDebugStartPort
|
||||
if !domain.ValidRemoteDebugPort(start) {
|
||||
return fmt.Errorf("invalid remote debug start port %d", start)
|
||||
}
|
||||
used := make(map[int]struct{}, len(value.Instances))
|
||||
for _, instance := range value.Instances {
|
||||
port := instance.PreferredRemoteDebugPort
|
||||
if port == 0 {
|
||||
continue
|
||||
}
|
||||
if !domain.ValidRemoteDebugPort(port) {
|
||||
return fmt.Errorf("invalid preferred remote debug port %d", port)
|
||||
}
|
||||
if _, exists := used[port]; exists {
|
||||
return fmt.Errorf("duplicate preferred remote debug port %d", port)
|
||||
}
|
||||
used[port] = struct{}{}
|
||||
}
|
||||
for index := range value.Instances {
|
||||
if value.Instances[index].PreferredRemoteDebugPort != 0 {
|
||||
continue
|
||||
}
|
||||
port, err := nextPreferredRemoteDebugPort(start, used)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value.Instances[index].PreferredRemoteDebugPort = port
|
||||
used[port] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nextPreferredRemoteDebugPort(start int, used map[int]struct{}) (int, error) {
|
||||
for port := start; port <= domain.MaxRemoteDebugPort; port++ {
|
||||
if _, exists := used[port]; !exists {
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
return 0, errors.New("no preferred remote debug port is available")
|
||||
}
|
||||
|
||||
func normalizeProxyConfig(value *File) error {
|
||||
if value == nil {
|
||||
return errors.New("config is required")
|
||||
|
||||
@@ -42,6 +42,50 @@ func TestStoreAddsDefaultRemoteDebugPortForExistingConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreAssignsPreferredPortsForLegacyInstancesInStableOrder(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "config.json")
|
||||
contents := `{"version":1,"settings":{"remoteDebugStartPort":9777},"instances":[{"id":"first","launch":{"Kind":"chrome","UserDataDir":"C:\\profiles\\first"}},{"id":"second","launch":{"Kind":"edge","UserDataDir":"C:\\profiles\\second"}}]}`
|
||||
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
store, err := New(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := store.Load()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Instances[0].PreferredRemoteDebugPort != 9777 || got.Instances[1].PreferredRemoteDebugPort != 9778 {
|
||||
t.Fatalf("legacy preferred ports = %#v", got.Instances)
|
||||
}
|
||||
if err := store.Save(got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
reloaded, err := store.Load()
|
||||
if err != nil || reloaded.Instances[0].PreferredRemoteDebugPort != 9777 || reloaded.Instances[1].PreferredRemoteDebugPort != 9778 {
|
||||
t.Fatalf("persisted preferred ports = %#v, error = %v", reloaded.Instances, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreRejectsInvalidOrDuplicatePreferredPorts(t *testing.T) {
|
||||
store, err := New(filepath.Join(t.TempDir(), "config.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
duplicate := File{Instances: []Instance{
|
||||
{ID: "one", PreferredRemoteDebugPort: 9666},
|
||||
{ID: "two", PreferredRemoteDebugPort: 9666},
|
||||
}}
|
||||
if err := store.Save(duplicate); err == nil {
|
||||
t.Fatal("duplicate preferred port was accepted")
|
||||
}
|
||||
invalid := File{Instances: []Instance{{ID: "one", PreferredRemoteDebugPort: domain.MinRemoteDebugPort - 1}}}
|
||||
if err := store.Save(invalid); err == nil {
|
||||
t.Fatal("invalid preferred port was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreMissingFileReturnsDefaults(t *testing.T) {
|
||||
store, err := New(filepath.Join(t.TempDir(), "config.json"))
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user