From 27b07c122ca03d4d3bc423b63edbe1921523900b Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Mon, 31 Aug 2026 18:09:15 -0700 Subject: [PATCH 1/3] test: cover bounded stale port retirement --- ...thoritativeRetirementRegressionTests.swift | 40 +++++++++ cmux.xcodeproj/project.pbxproj | 4 + ...RetirementPublicationRegressionTests.swift | 81 +++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 Packages/macOS/CmuxCore/Tests/CmuxCoreTests/PortScanAuthoritativeRetirementRegressionTests.swift create mode 100644 cmuxTests/PortRetirementPublicationRegressionTests.swift diff --git a/Packages/macOS/CmuxCore/Tests/CmuxCoreTests/PortScanAuthoritativeRetirementRegressionTests.swift b/Packages/macOS/CmuxCore/Tests/CmuxCoreTests/PortScanAuthoritativeRetirementRegressionTests.swift new file mode 100644 index 000000000000..d8e19a50e7cc --- /dev/null +++ b/Packages/macOS/CmuxCore/Tests/CmuxCoreTests/PortScanAuthoritativeRetirementRegressionTests.swift @@ -0,0 +1,40 @@ +import Testing +@testable import CmuxCore + +@Suite("Bounded port snapshot retirement") +struct PortScanAuthoritativeRetirementRegressionTests { + private enum Scope: Hashable, Sendable { + case panel + case workspace + } + + @Test("Complete scans retire a stopped listener within one burst in every scope") + func completeScansRetireStoppedListenerWithinBurst() { + let port = 48_123 + let scopes: Set = [.panel, .workspace] + var reconciler = PortScanSnapshotReconciler() + + let published = reconciler.reconcile( + scannedPorts: [.panel: [port], .workspace: [port]], + scannedKeys: scopes, + trackedKeys: scopes, + completeness: .complete + ) + #expect(published == [.panel: [port], .workspace: [port]]) + + for miss in 1...3 { + let snapshot = reconciler.reconcile( + scannedPorts: [.panel: [], .workspace: []], + scannedKeys: scopes, + trackedKeys: scopes, + completeness: .complete + ) + + if miss < 3 { + #expect(snapshot == [.panel: [port], .workspace: [port]]) + } else { + #expect(snapshot.isEmpty) + } + } + } +} diff --git a/cmux.xcodeproj/project.pbxproj b/cmux.xcodeproj/project.pbxproj index a9143341038f..1b89e8916737 100644 --- a/cmux.xcodeproj/project.pbxproj +++ b/cmux.xcodeproj/project.pbxproj @@ -1770,6 +1770,7 @@ D0C658660000000000000002 /* PortalSplitDividerRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C658660000000000000001 /* PortalSplitDividerRegion.swift */; }; D0B10008A1B2C3D4E5F60001 /* PortalTabDragRoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B10009A1B2C3D4E5F60001 /* PortalTabDragRoutingTests.swift */; }; B79500310000000000000001 /* PortLsofScanResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79500310000000000000002 /* PortLsofScanResult.swift */; }; + F11294000000000000000001 /* PortRetirementPublicationRegressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11294000000000000000002 /* PortRetirementPublicationRegressionTests.swift */; }; B79500030000000000000001 /* PortScanCoordination.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79500030000000000000002 /* PortScanCoordination.swift */; }; B79500070000000000000001 /* PortScanner+PanelKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79500070000000000000002 /* PortScanner+PanelKey.swift */; }; B79500010000000000000001 /* PortScanner+Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79500010000000000000002 /* PortScanner+Process.swift */; }; @@ -4768,6 +4769,7 @@ B8B056D80000000000000002 /* MobileHostIdentityTests.swift */ = {isa = PBXFileRef D0C658660000000000000001 /* PortalSplitDividerRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortalSplitDividerRegion.swift; sourceTree = ""; }; D0B10009A1B2C3D4E5F60001 /* PortalTabDragRoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortalTabDragRoutingTests.swift; sourceTree = ""; }; B79500310000000000000002 /* PortLsofScanResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortLsofScanResult.swift; sourceTree = ""; }; + F11294000000000000000002 /* PortRetirementPublicationRegressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortRetirementPublicationRegressionTests.swift; sourceTree = ""; }; B79500030000000000000002 /* PortScanCoordination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortScanCoordination.swift; sourceTree = ""; }; B79500070000000000000002 /* PortScanner+PanelKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PortScanner+PanelKey.swift"; sourceTree = ""; }; B79500010000000000000002 /* PortScanner+Process.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PortScanner+Process.swift"; sourceTree = ""; }; @@ -8437,6 +8439,7 @@ B8B056D80000000000000002 /* MobileHostIdentityTests.swift */ = {isa = PBXFileRef F4200001A1B2C3D4E5F60718 /* WindowAppearanceSnapshotTests.swift */, F4100001A1B2C3D4E5F60718 /* PortScannerTests.swift */, B79500300000000000000002 /* PortScannerPublicationTests.swift */, + F11294000000000000000002 /* PortRetirementPublicationRegressionTests.swift */, F83620010000000000000002 /* PortScannerTTYFreshnessTests.swift */, B79500320000000000000002 /* PortScannerIdentityContinuityTests.swift */, F6572003A1B2C3D4E5F60718 /* SessionPersistenceResumeBindingTests.swift */, @@ -12267,6 +12270,7 @@ B8B056D80000000000000002 /* MobileHostIdentityTests.swift */ = {isa = PBXFileRef 5B0C00010000000000000005 /* PortalDividerCursorOcclusionTests.swift in Sources */, B1D5CEE0407545B6B57E3B0E /* PortalHitTestingPerformanceTests.swift in Sources */, D0B10008A1B2C3D4E5F60001 /* PortalTabDragRoutingTests.swift in Sources */, + F11294000000000000000001 /* PortRetirementPublicationRegressionTests.swift in Sources */, B79500320000000000000001 /* PortScannerIdentityContinuityTests.swift in Sources */, B79500300000000000000001 /* PortScannerPublicationTests.swift in Sources */, F4100000A1B2C3D4E5F60718 /* PortScannerTests.swift in Sources */, diff --git a/cmuxTests/PortRetirementPublicationRegressionTests.swift b/cmuxTests/PortRetirementPublicationRegressionTests.swift new file mode 100644 index 000000000000..ebda05ef3e7b --- /dev/null +++ b/cmuxTests/PortRetirementPublicationRegressionTests.swift @@ -0,0 +1,81 @@ +import CmuxCore +import Foundation +import Testing + +#if canImport(cmux_DEV) +@testable import cmux_DEV +#elseif canImport(cmux) +@testable import cmux +#endif + +@Suite("Port retirement publication regression") +struct PortRetirementPublicationRegressionTests { + @Test("Listener removal retires panel and workspace publications within one burst") + func listenerRemovalRetiresPublicationsWithinBurst() throws { + let port = 48_123 + let workspaceID = UUID() + let panelKey = PortScanner.PanelKey(workspaceId: workspaceID, panelId: UUID()) + var panelSnapshot = PortScanSnapshotReconciler() + var workspaceSnapshot = PortScanSnapshotReconciler() + var buffer = PortScanPublicationBuffer() + + let initialPanelSnapshot = panelSnapshot.reconcile( + scannedPorts: [panelKey: [port]], + scannedKeys: [panelKey], + trackedKeys: [panelKey], + completeness: .complete + ) + let initialWorkspaceSnapshot = workspaceSnapshot.reconcile( + scannedPorts: [workspaceID: [port]], + scannedKeys: [workspaceID], + trackedKeys: [workspaceID], + completeness: .complete + ) + _ = buffer.enqueue(panelPublications: [PanelPortScanPublication( + key: panelKey, + ports: initialPanelSnapshot[panelKey] ?? [], + revision: 1 + )]) + _ = buffer.enqueue(agentPublications: [AgentPortScanPublication( + workspaceId: workspaceID, + ports: initialWorkspaceSnapshot[workspaceID] ?? [], + revision: 1, + requestID: 1, + removesLifecycle: false + )]) + + for miss in 1...3 { + let retiredPanelSnapshot = panelSnapshot.reconcile( + scannedPorts: [panelKey: []], + scannedKeys: [panelKey], + trackedKeys: [panelKey], + completeness: .complete + ) + let retiredWorkspaceSnapshot = workspaceSnapshot.reconcile( + scannedPorts: [workspaceID: []], + scannedKeys: [workspaceID], + trackedKeys: [workspaceID], + completeness: .complete + ) + let expectedPorts = miss < 3 ? [port] : [] + #expect(retiredPanelSnapshot[panelKey] ?? [] == expectedPorts) + #expect(retiredWorkspaceSnapshot[workspaceID] ?? [] == expectedPorts) + _ = buffer.enqueue(panelPublications: [PanelPortScanPublication( + key: panelKey, + ports: retiredPanelSnapshot[panelKey] ?? [], + revision: 1 + )]) + _ = buffer.enqueue(agentPublications: [AgentPortScanPublication( + workspaceId: workspaceID, + ports: retiredWorkspaceSnapshot[workspaceID] ?? [], + revision: 1, + requestID: UInt64(miss + 1), + removesLifecycle: false + )]) + } + + let batch = try #require(buffer.takePendingBatch()) + #expect(batch.panelPublicationsByKey[panelKey]?.ports.isEmpty == true) + #expect(batch.agentPublicationsByWorkspace[workspaceID]?.ports.isEmpty == true) + } +} From 2f8b45216621bb0a77092e5ec6f94175c6fcfdd4 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Tue, 1 Sep 2026 00:40:27 -0700 Subject: [PATCH 2/3] test: evaluate buffered publication before requiring --- cmuxTests/PortRetirementPublicationRegressionTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmuxTests/PortRetirementPublicationRegressionTests.swift b/cmuxTests/PortRetirementPublicationRegressionTests.swift index ebda05ef3e7b..321cf46175e1 100644 --- a/cmuxTests/PortRetirementPublicationRegressionTests.swift +++ b/cmuxTests/PortRetirementPublicationRegressionTests.swift @@ -74,7 +74,8 @@ struct PortRetirementPublicationRegressionTests { )]) } - let batch = try #require(buffer.takePendingBatch()) + let pendingBatch = buffer.takePendingBatch() + let batch = try #require(pendingBatch) #expect(batch.panelPublicationsByKey[panelKey]?.ports.isEmpty == true) #expect(batch.agentPublicationsByWorkspace[workspaceID]?.ports.isEmpty == true) } From d54e736d9348dfaedab617a7c279c9a3ef464309 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Tue, 1 Sep 2026 00:54:16 -0700 Subject: [PATCH 3/3] test: document port retirement regressions --- .../PortScanAuthoritativeRetirementRegressionTests.swift | 1 + cmuxTests/PortRetirementPublicationRegressionTests.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/Packages/macOS/CmuxCore/Tests/CmuxCoreTests/PortScanAuthoritativeRetirementRegressionTests.swift b/Packages/macOS/CmuxCore/Tests/CmuxCoreTests/PortScanAuthoritativeRetirementRegressionTests.swift index d8e19a50e7cc..1ffd5e12de4c 100644 --- a/Packages/macOS/CmuxCore/Tests/CmuxCoreTests/PortScanAuthoritativeRetirementRegressionTests.swift +++ b/Packages/macOS/CmuxCore/Tests/CmuxCoreTests/PortScanAuthoritativeRetirementRegressionTests.swift @@ -8,6 +8,7 @@ struct PortScanAuthoritativeRetirementRegressionTests { case workspace } + /// Verifies bounded listener retirement for every shared publication scope. @Test("Complete scans retire a stopped listener within one burst in every scope") func completeScansRetireStoppedListenerWithinBurst() { let port = 48_123 diff --git a/cmuxTests/PortRetirementPublicationRegressionTests.swift b/cmuxTests/PortRetirementPublicationRegressionTests.swift index 321cf46175e1..e99a6f67a841 100644 --- a/cmuxTests/PortRetirementPublicationRegressionTests.swift +++ b/cmuxTests/PortRetirementPublicationRegressionTests.swift @@ -10,6 +10,7 @@ import Testing @Suite("Port retirement publication regression") struct PortRetirementPublicationRegressionTests { + /// Verifies that buffered panel and workspace publications converge on retirement. @Test("Listener removal retires panel and workspace publications within one burst") func listenerRemovalRetiresPublicationsWithinBurst() throws { let port = 48_123