Download internal/instance/instance_test.go from AUXteam/pinch: direct link, hf CLI and curl.
- Browser
- Download file 9.9 kB
-
https://huggingface.co/spaces/AUXteam/pinch/resolve/main/internal/instance/instance_test.go
- Command line
-
hf download hf://spaces/AUXteam/pinch/internal/instance/instance_test.go
-
curl -L -o instance_test.go https://huggingface.co/spaces/AUXteam/pinch/resolve/main/internal/instance/instance_test.go
9.9 kB
| package instance_test | |
| import ( | |
| "fmt" | |
| "testing" | |
| "github.com/pinchtab/pinchtab/internal/allocation" | |
| bridgepkg "github.com/pinchtab/pinchtab/internal/bridge" | |
| "github.com/pinchtab/pinchtab/internal/instance" | |
| ) | |
| // --- Test doubles --- | |
| type mockLauncher struct { | |
| instances map[string]*bridgepkg.Instance | |
| nextID int | |
| stopErr error | |
| } | |
| func newMockLauncher() *mockLauncher { | |
| return &mockLauncher{instances: make(map[string]*bridgepkg.Instance)} | |
| } | |
| func (m *mockLauncher) Launch(name, port string, headless bool) (*bridgepkg.Instance, error) { | |
| m.nextID++ | |
| inst := &bridgepkg.Instance{ | |
| ID: fmt.Sprintf("inst_%d", m.nextID), | |
| ProfileName: name, | |
| Port: port, | |
| URL: "http://localhost:" + port, | |
| Headless: headless, | |
| Status: "running", | |
| } | |
| m.instances[inst.ID] = inst | |
| return inst, nil | |
| } | |
| func (m *mockLauncher) Stop(id string) error { | |
| if m.stopErr != nil { | |
| return m.stopErr | |
| } | |
| delete(m.instances, id) | |
| return nil | |
| } | |
| type mockFetcher struct { | |
| // tabsByURL maps instance URL β tabs | |
| tabsByURL map[string][]bridgepkg.InstanceTab | |
| } | |
| func newMockFetcher() *mockFetcher { | |
| return &mockFetcher{tabsByURL: make(map[string][]bridgepkg.InstanceTab)} | |
| } | |
| func (f *mockFetcher) FetchTabs(instanceURL string) ([]bridgepkg.InstanceTab, error) { | |
| tabs, ok := f.tabsByURL[instanceURL] | |
| if !ok { | |
| return nil, fmt.Errorf("instance %s not reachable", instanceURL) | |
| } | |
| return tabs, nil | |
| } | |
| func (f *mockFetcher) AddTab(instancePort, tabID, url string) { | |
| key := "http://localhost:" + instancePort | |
| f.AddTabForURL(key, tabID, url) | |
| } | |
| func (f *mockFetcher) AddTabForURL(instanceURL, tabID, url string) { | |
| key := instanceURL | |
| f.tabsByURL[key] = append(f.tabsByURL[key], bridgepkg.InstanceTab{ | |
| ID: tabID, | |
| URL: url, | |
| }) | |
| } | |
| // --- Repository tests --- | |
| func TestRepository_LaunchAndGet(t *testing.T) { | |
| launcher := newMockLauncher() | |
| repo := instance.NewRepository(launcher) | |
| inst, err := repo.Launch("default", "9868", true) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| got, ok := repo.Get(inst.ID) | |
| if !ok { | |
| t.Fatal("instance not found after launch") | |
| } | |
| if got.ProfileName != "default" { | |
| t.Errorf("expected profile default, got %s", got.ProfileName) | |
| } | |
| if repo.Count() != 1 { | |
| t.Errorf("expected count 1, got %d", repo.Count()) | |
| } | |
| } | |
| func TestRepository_StopRemovesInstance(t *testing.T) { | |
| launcher := newMockLauncher() | |
| repo := instance.NewRepository(launcher) | |
| inst, _ := repo.Launch("default", "9868", true) | |
| if err := repo.Stop(inst.ID); err != nil { | |
| t.Fatal(err) | |
| } | |
| if _, ok := repo.Get(inst.ID); ok { | |
| t.Error("instance should be gone after stop") | |
| } | |
| if repo.Count() != 0 { | |
| t.Errorf("expected count 0, got %d", repo.Count()) | |
| } | |
| } | |
| func TestRepository_Running_FiltersNonRunning(t *testing.T) { | |
| launcher := newMockLauncher() | |
| repo := instance.NewRepository(launcher) | |
| inst1, _ := repo.Launch("prof1", "9868", true) | |
| _, _ = repo.Launch("prof2", "9869", true) | |
| // Manually mark inst1 as stopped via Add. | |
| stopped := *inst1 | |
| stopped.Status = "stopped" | |
| repo.Add(&stopped) | |
| running := repo.Running() | |
| if len(running) != 1 { | |
| t.Errorf("expected 1 running, got %d", len(running)) | |
| } | |
| } | |
| // --- Locator tests --- | |
| func TestLocator_CacheHit(t *testing.T) { | |
| launcher := newMockLauncher() | |
| fetcher := newMockFetcher() | |
| repo := instance.NewRepository(launcher) | |
| locator := instance.NewLocator(repo, fetcher) | |
| inst, _ := repo.Launch("default", "9868", true) | |
| // Pre-register in cache. | |
| locator.Register("tab_abc", inst.ID) | |
| found, err := locator.FindInstanceByTabID("tab_abc") | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if found.ID != inst.ID { | |
| t.Errorf("expected %s, got %s", inst.ID, found.ID) | |
| } | |
| } | |
| func TestLocator_CacheMiss_QueriesBridges(t *testing.T) { | |
| launcher := newMockLauncher() | |
| fetcher := newMockFetcher() | |
| repo := instance.NewRepository(launcher) | |
| locator := instance.NewLocator(repo, fetcher) | |
| inst, _ := repo.Launch("default", "9868", true) | |
| // Set up fetcher to return tabs for this instance. | |
| fetcher.AddTab("9868", "tab_xyz", "https://pinchtab.com") | |
| found, err := locator.FindInstanceByTabID("tab_xyz") | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if found.ID != inst.ID { | |
| t.Errorf("expected %s, got %s", inst.ID, found.ID) | |
| } | |
| // Should now be cached. | |
| if locator.CacheSize() != 1 { | |
| t.Errorf("expected cache size 1, got %d", locator.CacheSize()) | |
| } | |
| } | |
| func TestLocator_TabNotFound(t *testing.T) { | |
| launcher := newMockLauncher() | |
| fetcher := newMockFetcher() | |
| repo := instance.NewRepository(launcher) | |
| locator := instance.NewLocator(repo, fetcher) | |
| _, _ = repo.Launch("default", "9868", true) | |
| fetcher.AddTab("9868", "tab_abc", "https://pinchtab.com") | |
| _, err := locator.FindInstanceByTabID("nonexistent") | |
| if err == nil { | |
| t.Error("expected error for nonexistent tab") | |
| } | |
| } | |
| func TestLocator_UsesInstanceURLWhenPresent(t *testing.T) { | |
| launcher := newMockLauncher() | |
| fetcher := newMockFetcher() | |
| repo := instance.NewRepository(launcher) | |
| locator := instance.NewLocator(repo, fetcher) | |
| inst, _ := repo.Launch("remote", "9868", true) | |
| inst.URL = "https://bridge.example.com:9868" | |
| repo.Add(inst) | |
| fetcher.AddTabForURL("https://bridge.example.com:9868", "tab_remote", "https://pinchtab.com") | |
| found, err := locator.FindInstanceByTabID("tab_remote") | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if found.ID != inst.ID { | |
| t.Fatalf("expected %s, got %s", inst.ID, found.ID) | |
| } | |
| } | |
| func TestLocator_InvalidateRemovesCacheEntry(t *testing.T) { | |
| launcher := newMockLauncher() | |
| fetcher := newMockFetcher() | |
| repo := instance.NewRepository(launcher) | |
| locator := instance.NewLocator(repo, fetcher) | |
| inst, _ := repo.Launch("default", "9868", true) | |
| locator.Register("tab_abc", inst.ID) | |
| if locator.CacheSize() != 1 { | |
| t.Fatal("expected cache size 1") | |
| } | |
| locator.Invalidate("tab_abc") | |
| if locator.CacheSize() != 0 { | |
| t.Error("expected cache size 0 after invalidate") | |
| } | |
| } | |
| func TestLocator_InvalidateInstance_RemovesAllTabs(t *testing.T) { | |
| launcher := newMockLauncher() | |
| fetcher := newMockFetcher() | |
| repo := instance.NewRepository(launcher) | |
| locator := instance.NewLocator(repo, fetcher) | |
| inst, _ := repo.Launch("default", "9868", true) | |
| locator.Register("tab_1", inst.ID) | |
| locator.Register("tab_2", inst.ID) | |
| locator.Register("tab_3", inst.ID) | |
| locator.InvalidateInstance(inst.ID) | |
| if locator.CacheSize() != 0 { | |
| t.Errorf("expected cache size 0, got %d", locator.CacheSize()) | |
| } | |
| } | |
| func TestLocator_StaleCache_InstanceGone(t *testing.T) { | |
| launcher := newMockLauncher() | |
| fetcher := newMockFetcher() | |
| repo := instance.NewRepository(launcher) | |
| locator := instance.NewLocator(repo, fetcher) | |
| inst, _ := repo.Launch("default", "9868", true) | |
| locator.Register("tab_abc", inst.ID) | |
| // Remove instance from repo (simulates crash/stop). | |
| repo.Remove(inst.ID) | |
| // Cache hit returns stale entry, but instance is gone β should fallback. | |
| _, err := locator.FindInstanceByTabID("tab_abc") | |
| if err == nil { | |
| t.Error("expected error when instance is gone") | |
| } | |
| } | |
| // --- Allocator tests --- | |
| func TestAllocator_FCFS(t *testing.T) { | |
| launcher := newMockLauncher() | |
| repo := instance.NewRepository(launcher) | |
| policy := &allocation.FCFS{} | |
| alloc := instance.NewAllocator(repo, policy) | |
| _, _ = repo.Launch("prof1", "9868", true) | |
| got, err := alloc.Allocate() | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if got.ProfileName != "prof1" { | |
| t.Errorf("expected prof1, got %s", got.ProfileName) | |
| } | |
| } | |
| func TestAllocator_RoundRobin(t *testing.T) { | |
| launcher := newMockLauncher() | |
| repo := instance.NewRepository(launcher) | |
| policy := allocation.NewRoundRobin() | |
| alloc := instance.NewAllocator(repo, policy) | |
| _, _ = repo.Launch("prof1", "9868", true) | |
| _, _ = repo.Launch("prof2", "9869", true) | |
| // RoundRobin should cycle. Exact order depends on map iteration, | |
| // but each allocation should succeed. | |
| for range 4 { | |
| _, err := alloc.Allocate() | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| } | |
| } | |
| func TestAllocator_NoRunningInstances(t *testing.T) { | |
| launcher := newMockLauncher() | |
| repo := instance.NewRepository(launcher) | |
| alloc := instance.NewAllocator(repo, &allocation.FCFS{}) | |
| _, err := alloc.Allocate() | |
| if err == nil { | |
| t.Error("expected error with no running instances") | |
| } | |
| } | |
| // --- Manager facade tests --- | |
| func TestManager_DelegatesToComponents(t *testing.T) { | |
| launcher := newMockLauncher() | |
| fetcher := newMockFetcher() | |
| mgr := instance.NewManager(launcher, fetcher, &allocation.FCFS{}) | |
| // Launch via manager β delegates to repo. | |
| inst, err := mgr.Launch("default", "9868", true) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| // Get via manager β delegates to repo. | |
| got, ok := mgr.Get(inst.ID) | |
| if !ok || got.ID != inst.ID { | |
| t.Error("Get should delegate to repo") | |
| } | |
| // List via manager β delegates to repo. | |
| list := mgr.List() | |
| if len(list) != 1 { | |
| t.Errorf("expected 1 instance, got %d", len(list)) | |
| } | |
| // RegisterTab + FindInstanceByTabID β delegates to locator. | |
| mgr.RegisterTab("tab_abc", inst.ID) | |
| found, err := mgr.FindInstanceByTabID("tab_abc") | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if found.ID != inst.ID { | |
| t.Error("FindInstanceByTabID should delegate to locator") | |
| } | |
| // Allocate β delegates to allocator. | |
| alloc, err := mgr.Allocate() | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if alloc.ProfileName != "default" { | |
| t.Error("Allocate should delegate to allocator") | |
| } | |
| // Stop β delegates to repo + invalidates cache. | |
| if err := mgr.Stop(inst.ID); err != nil { | |
| t.Fatal(err) | |
| } | |
| if _, ok := mgr.Get(inst.ID); ok { | |
| t.Error("instance should be gone after stop") | |
| } | |
| } | |
| func TestManager_StopInvalidatesTabCache(t *testing.T) { | |
| launcher := newMockLauncher() | |
| fetcher := newMockFetcher() | |
| mgr := instance.NewManager(launcher, fetcher, nil) | |
| inst, _ := mgr.Launch("default", "9868", true) | |
| mgr.RegisterTab("tab_1", inst.ID) | |
| mgr.RegisterTab("tab_2", inst.ID) | |
| _ = mgr.Stop(inst.ID) | |
| // Tabs should be invalidated. | |
| _, err := mgr.FindInstanceByTabID("tab_1") | |
| if err == nil { | |
| t.Error("expected error: tab cache should be invalidated after stop") | |
| } | |
| } | |