From c325e2b49d8cc7b996c8eef9cf36c945f6027a2b Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Sat, 21 Mar 2026 02:22:00 -0700 Subject: [PATCH] Add tmux shell environment regression tests --- cmuxTests/GhosttyConfigTests.swift | 116 ++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 2 deletions(-) diff --git a/cmuxTests/GhosttyConfigTests.swift b/cmuxTests/GhosttyConfigTests.swift index a2c8ccf6..4eff2aa6 100644 --- a/cmuxTests/GhosttyConfigTests.swift +++ b/cmuxTests/GhosttyConfigTests.swift @@ -2388,6 +2388,98 @@ final class ZshShellIntegrationHandoffTests: XCTestCase { XCTAssertEqual(output, "BEFORE\nAFTER", output) } + func testShellIntegrationPublishesCmuxEnvironmentToTmuxServerAutomatically() throws { + let fileManager = FileManager.default + let root = fileManager.temporaryDirectory + .appendingPathComponent("cmux-zsh-tmux-publish-\(UUID().uuidString)") + let binDir = root.appendingPathComponent("bin", isDirectory: true) + let logPath = root.appendingPathComponent("tmux.log", isDirectory: false) + + try fileManager.createDirectory(at: binDir, withIntermediateDirectories: true) + defer { try? fileManager.removeItem(at: root) } + + try writeExecutableScript( + at: binDir.appendingPathComponent("tmux", isDirectory: false), + contents: """ + #!/bin/sh + if [ "$1" = "show-environment" ] && [ "$2" = "-g" ]; then + exit 0 + fi + printf '%s\\n' "$*" >> "\(logPath.path)" + exit 0 + """ + ) + + _ = try runInteractiveZsh( + cmuxLoadGhosttyIntegration: false, + cmuxLoadShellIntegration: true, + command: "_cmux_preexec tmux; print -r -- READY", + extraEnvironment: [ + "PATH": "\(binDir.path):/usr/bin:/bin:/usr/sbin:/sbin", + "CMUX_SOCKET_PATH": "/tmp/cmux-current.sock", + "CMUX_TAG": "feat-tmux-notification-attention-state", + "CMUX_WORKSPACE_ID": "11111111-1111-1111-1111-111111111111", + "CMUX_SURFACE_ID": "22222222-2222-2222-2222-222222222222", + "CMUX_TAB_ID": "11111111-1111-1111-1111-111111111111", + "CMUX_PANEL_ID": "22222222-2222-2222-2222-222222222222", + ] + ) + + let log = (try? String(contentsOf: logPath, encoding: .utf8)) ?? "" + XCTAssertTrue(log.contains("set-environment -g CMUX_TAG feat-tmux-notification-attention-state"), log) + XCTAssertTrue(log.contains("set-environment -g CMUX_SOCKET_PATH /tmp/cmux-current.sock"), log) + XCTAssertTrue(log.contains("set-environment -g CMUX_WORKSPACE_ID 11111111-1111-1111-1111-111111111111"), log) + XCTAssertTrue(log.contains("set-environment -g CMUX_SURFACE_ID 22222222-2222-2222-2222-222222222222"), log) + } + + func testShellIntegrationRefreshesStaleCmuxEnvironmentFromTmuxAutomatically() throws { + let fileManager = FileManager.default + let root = fileManager.temporaryDirectory + .appendingPathComponent("cmux-zsh-tmux-refresh-\(UUID().uuidString)") + let binDir = root.appendingPathComponent("bin", isDirectory: true) + + try fileManager.createDirectory(at: binDir, withIntermediateDirectories: true) + defer { try? fileManager.removeItem(at: root) } + + try writeExecutableScript( + at: binDir.appendingPathComponent("tmux", isDirectory: false), + contents: """ + #!/bin/sh + if [ "$1" = "show-environment" ] && [ "$2" = "-g" ]; then + printf '%s\\n' 'CMUX_SOCKET_PATH=/tmp/cmux-current.sock' + printf '%s\\n' 'CMUX_TAG=feat-tmux-notification-attention-state' + printf '%s\\n' 'CMUX_WORKSPACE_ID=11111111-1111-1111-1111-111111111111' + printf '%s\\n' 'CMUX_SURFACE_ID=22222222-2222-2222-2222-222222222222' + printf '%s\\n' 'CMUX_TAB_ID=11111111-1111-1111-1111-111111111111' + printf '%s\\n' 'CMUX_PANEL_ID=22222222-2222-2222-2222-222222222222' + exit 0 + fi + exit 0 + """ + ) + + let output = try runInteractiveZsh( + cmuxLoadGhosttyIntegration: false, + cmuxLoadShellIntegration: true, + command: "_cmux_precmd; print -r -- \"$CMUX_TAG|$CMUX_SOCKET_PATH|$CMUX_WORKSPACE_ID|$CMUX_SURFACE_ID\"", + extraEnvironment: [ + "PATH": "\(binDir.path):/usr/bin:/bin:/usr/sbin:/sbin", + "TMUX": "/tmp/tmux-stale,123,0", + "CMUX_SOCKET_PATH": "/tmp/cmux-stale.sock", + "CMUX_TAG": "feat-tmux-integration-experiments", + "CMUX_WORKSPACE_ID": "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", + "CMUX_SURFACE_ID": "BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB", + "CMUX_TAB_ID": "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", + "CMUX_PANEL_ID": "BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB", + ] + ) + + XCTAssertEqual( + output, + "feat-tmux-notification-attention-state|/tmp/cmux-current.sock|11111111-1111-1111-1111-111111111111|22222222-2222-2222-2222-222222222222" + ) + } + private func runInteractiveZsh(cmuxLoadGhosttyIntegration: Bool) throws -> String { try runInteractiveZsh( cmuxLoadGhosttyIntegration: cmuxLoadGhosttyIntegration, @@ -2401,7 +2493,8 @@ final class ZshShellIntegrationHandoffTests: XCTestCase { private func runInteractiveZsh( cmuxLoadGhosttyIntegration: Bool, cmuxLoadShellIntegration: Bool, - command: String + command: String, + extraEnvironment: [String: String] = [:] ) throws -> String { let fileManager = FileManager.default let root = fileManager.temporaryDirectory @@ -2411,7 +2504,18 @@ final class ZshShellIntegrationHandoffTests: XCTestCase { let userZdotdir = root.appendingPathComponent("zdotdir") try fileManager.createDirectory(at: userZdotdir, withIntermediateDirectories: true) - try "\n".write(to: userZdotdir.appendingPathComponent(".zshenv"), atomically: true, encoding: .utf8) + let userZshEnvContents: String = { + if let path = extraEnvironment["PATH"] { + let escaped = path.replacingOccurrences(of: "\"", with: "\\\"") + return "export PATH=\"\(escaped)\"\n" + } + return "\n" + }() + try userZshEnvContents.write( + to: userZdotdir.appendingPathComponent(".zshenv"), + atomically: true, + encoding: .utf8 + ) let repoRoot = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() @@ -2445,6 +2549,9 @@ final class ZshShellIntegrationHandoffTests: XCTestCase { process.environment?["CMUX_TAB_ID"] = "tab-test" process.environment?["CMUX_PANEL_ID"] = "panel-test" } + for (key, value) in extraEnvironment { + process.environment?[key] = value + } let stdout = Pipe() let stderr = Pipe() @@ -2468,6 +2575,11 @@ final class ZshShellIntegrationHandoffTests: XCTestCase { XCTAssertEqual(process.terminationStatus, 0, error) return output.trimmingCharacters(in: .whitespacesAndNewlines) } + + private func writeExecutableScript(at url: URL, contents: String) throws { + try contents.write(to: url, atomically: true, encoding: .utf8) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: url.path) + } } final class BrowserInstallDetectorTests: XCTestCase {