Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Testing
@testable import CmuxCore

@Suite("Bounded port snapshot retirement")
struct PortScanAuthoritativeRetirementRegressionTests {
private enum Scope: Hashable, Sendable {
case panel
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
let scopes: Set<Scope> = [.panel, .workspace]
var reconciler = PortScanSnapshotReconciler<Scope>()

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)
}
}
}
}
4 changes: 4 additions & 0 deletions cmux.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1772,6 +1772,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 */; };
Expand Down Expand Up @@ -4772,6 +4773,7 @@ B8B056D80000000000000002 /* MobileHostIdentityTests.swift */ = {isa = PBXFileRef
D0C658660000000000000001 /* PortalSplitDividerRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortalSplitDividerRegion.swift; sourceTree = "<group>"; };
D0B10009A1B2C3D4E5F60001 /* PortalTabDragRoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortalTabDragRoutingTests.swift; sourceTree = "<group>"; };
B79500310000000000000002 /* PortLsofScanResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortLsofScanResult.swift; sourceTree = "<group>"; };
F11294000000000000000002 /* PortRetirementPublicationRegressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortRetirementPublicationRegressionTests.swift; sourceTree = "<group>"; };
B79500030000000000000002 /* PortScanCoordination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortScanCoordination.swift; sourceTree = "<group>"; };
B79500070000000000000002 /* PortScanner+PanelKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PortScanner+PanelKey.swift"; sourceTree = "<group>"; };
B79500010000000000000002 /* PortScanner+Process.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PortScanner+Process.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -8441,6 +8443,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 */,
Expand Down Expand Up @@ -12275,6 +12278,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 */,
Expand Down
83 changes: 83 additions & 0 deletions cmuxTests/PortRetirementPublicationRegressionTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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 {
/// 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
let workspaceID = UUID()
let panelKey = PortScanner.PanelKey(workspaceId: workspaceID, panelId: UUID())
var panelSnapshot = PortScanSnapshotReconciler<PortScanner.PanelKey>()
var workspaceSnapshot = PortScanSnapshotReconciler<UUID>()
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 pendingBatch = buffer.takePendingBatch()
let batch = try #require(pendingBatch)
#expect(batch.panelPublicationsByKey[panelKey]?.ports.isEmpty == true)
#expect(batch.agentPublicationsByWorkspace[workspaceID]?.ports.isEmpty == true)
}
}