| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "testing" |
| 5 | ) |
| 6 | |
| 7 | // A tab whose controller build was blocked by a held session lease keeps |
| 8 | // Ctrl == nil and StartupErrLeaseHeld set. Every tab-scoped command resolves |
| 9 | // through tabAndCtrlByID, which retries that blocked startup before reporting |
| 10 | // a missing runtime; the local-tab resolver used by OpenSession must do the |
| 11 | // same, or opening a session on the only tab fails until the user finds |
| 12 | // another way to trigger a rebuild. |
| 13 | func TestActiveOrSingleLocalTabRecoversLeaseBlockedStartup(t *testing.T) { |
| 14 | app, ref := lifecycleFixture(t) |
| 15 | app.readyHook = func() {} |
| 16 | |
| 17 | release, err := app.beginProjectRuntimeAdmission("global", globalTabWorkspaceRoot()) |
| 18 | if err != nil { |
| 19 | t.Fatal(err) |
| 20 | } |
| 21 | tab := app.createTabEntry("global", globalTabWorkspaceRoot(), "") |
| 22 | tab.sink = &tabEventSink{tabID: tab.ID, app: app} |
| 23 | installNoopRuntimeEvents(app, tab.sink) |
| 24 | // The startup that would have built this tab's runtime was refused by a |
| 25 | // lease the holder has since released. |
| 26 | tab.StartupErr = (&sessionLeaseBusyError{}).Error() |
| 27 | tab.StartupErrLeaseHeld = true |
| 28 | tab.Ready = false |
| 29 | app.publishRestoredTab(tab, release) |
| 30 | t.Cleanup(func() { |
| 31 | if ctrl := app.controllerForTab(tab); ctrl != nil { |
| 32 | ctrl.Close() |
| 33 | } |
| 34 | tab.releaseSessionLease() |
| 35 | }) |
| 36 | |
| 37 | resolved, ctrl := app.activeOrSingleLocalTab() |
| 38 | if resolved != tab { |
| 39 | t.Fatalf("resolved tab = %+v, want the lease-blocked tab", resolved) |
| 40 | } |
| 41 | if ctrl == nil { |
| 42 | t.Fatal("lease-blocked startup was not recovered; the tab stayed without a runtime") |
| 43 | } |
| 44 | if _, err := app.OpenSession(ref); err != nil { |
| 45 | t.Fatalf("open session on the recovered tab: %v", err) |
| 46 | } |
| 47 | app.mu.RLock() |
| 48 | boundSessionID := app.tabs[tab.ID].SessionID |
| 49 | app.mu.RUnlock() |
| 50 | if boundSessionID != ref.SessionID { |
| 51 | t.Fatalf("tab session after open = %q, want %q", boundSessionID, ref.SessionID) |
| 52 | } |
| 53 | } |
| 54 |