Add regression coverage for Cmd+N workspace creation crash (#2127)

* Add Cmd+N workspace snapshot regression coverage (#2017)

* Add dev flag to stress Cmd+N workspace creation
This commit is contained in:
Austin Wang 2026-03-25 01:50:57 -07:00 committed by GitHub
parent 0d8597caf9
commit da70f3fa47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 93 additions and 1 deletions

View file

@ -1142,6 +1142,48 @@ class TabManager: ObservableObject {
focusedBrowserPanel?.hideFind()
}
func makeWorkspaceForCreation(
title: String,
workingDirectory: String?,
portOrdinal: Int,
configTemplate: ghostty_surface_config_s?,
initialTerminalCommand: String?,
initialTerminalEnvironment: [String: String]
) -> Workspace {
Workspace(
title: title,
workingDirectory: workingDirectory,
portOrdinal: portOrdinal,
configTemplate: configTemplate,
initialTerminalCommand: initialTerminalCommand,
initialTerminalEnvironment: initialTerminalEnvironment
)
}
#if DEBUG
private func maybeMutateSelectionDuringWorkspaceCreationForDev(
snapshot: WorkspaceCreationSnapshot
) {
let env = ProcessInfo.processInfo.environment
let isEnabled: Bool = {
if let raw = env["CMUX_DEV_MUTATE_WORKSPACE_SELECTION_DURING_CREATION"] {
return raw == "1" || raw.caseInsensitiveCompare("true") == .orderedSame
}
return UserDefaults.standard.bool(forKey: "cmuxDevMutateWorkspaceSelectionDuringCreation")
}()
guard isEnabled,
let selectedTabId = snapshot.selectedTabId,
let target = snapshot.tabs.first(where: { $0.id != selectedTabId }) else {
return
}
dlog(
"workspace.create.devSelectionMutation from=\(selectedTabId.uuidString.prefix(5)) " +
"to=\(target.id.uuidString.prefix(5))"
)
self.selectedTabId = target.id
}
#endif
@discardableResult
func addWorkspace(
workingDirectory overrideWorkingDirectory: String? = nil,
@ -1155,6 +1197,9 @@ class TabManager: ObservableObject {
// Snapshot current published state once so workspace creation doesn't repeatedly
// bounce through Combine-backed accessors while we're preparing the new workspace.
let snapshot = workspaceCreationSnapshot()
#if DEBUG
maybeMutateSelectionDuringWorkspaceCreationForDev(snapshot: snapshot)
#endif
let nextTabCount = snapshot.tabs.count + 1
sentryBreadcrumb("workspace.create", data: ["tabCount": nextTabCount])
let explicitWorkingDirectory = normalizedWorkingDirectory(overrideWorkingDirectory)
@ -1166,7 +1211,7 @@ class TabManager: ObservableObject {
let insertIndex = newTabInsertIndex(snapshot: snapshot, placementOverride: placementOverride)
let ordinal = Self.nextPortOrdinal
Self.nextPortOrdinal += 1
let newWorkspace = Workspace(
let newWorkspace = makeWorkspaceForCreation(
title: "Terminal \(nextTabCount)",
workingDirectory: workingDirectory,
portOrdinal: ordinal,

View file

@ -294,6 +294,29 @@ final class WorkspacePlacementSettingsTests: XCTestCase {
@MainActor
final class WorkspaceCreationPlacementTests: XCTestCase {
private final class SnapshotMutatingTabManager: TabManager {
var beforeCreateWorkspace: (() -> Void)?
override func makeWorkspaceForCreation(
title: String,
workingDirectory: String?,
portOrdinal: Int,
configTemplate: ghostty_surface_config_s?,
initialTerminalCommand: String?,
initialTerminalEnvironment: [String: String]
) -> Workspace {
beforeCreateWorkspace?()
return super.makeWorkspaceForCreation(
title: title,
workingDirectory: workingDirectory,
portOrdinal: portOrdinal,
configTemplate: configTemplate,
initialTerminalCommand: initialTerminalCommand,
initialTerminalEnvironment: initialTerminalEnvironment
)
}
}
func testAddWorkspaceDefaultPlacementMatchesCurrentSetting() {
let currentPlacement = WorkspacePlacementSettings.current()
@ -352,6 +375,30 @@ final class WorkspaceCreationPlacementTests: XCTestCase {
XCTAssertEqual(manager.tabs.last?.id, inserted.id)
}
func testAddWorkspaceAfterCurrentUsesPrecreationSnapshotWhenSelectionMutatesDuringBootstrap() {
let manager = SnapshotMutatingTabManager()
guard let first = manager.tabs.first else {
XCTFail("Expected initial workspace")
return
}
manager.setPinned(first, pinned: true)
let second = manager.addWorkspace()
let third = manager.addWorkspace()
manager.selectWorkspace(third)
let baselineOrder = manager.tabs.map(\.id)
manager.beforeCreateWorkspace = {
manager.selectWorkspace(first)
}
let inserted = manager.addWorkspace(placementOverride: .afterCurrent)
XCTAssertEqual(manager.tabs.map(\.id).filter { $0 != inserted.id }, baselineOrder)
XCTAssertEqual(manager.tabs.map(\.id), [first.id, second.id, third.id, inserted.id])
XCTAssertEqual(manager.selectedTabId, inserted.id)
}
private func makeManagerWithThreeWorkspaces() -> TabManager {
let manager = TabManager()
_ = manager.addWorkspace()