this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: Address cursor review comments

- Add UDID validation in StreamVideo command to prevent empty/nil UDID issues
- Fix async cleanup resource leak in cancellation handler using synchronous cleanup
- Add proper UDID validation in all test files to prevent spurious failures
- Use semaphore to ensure cleanup completes before process termination
- Fix dangerous force unwrap in TypeTests.swift
- Add timeout to prevent cleanup from hanging indefinitely

All tests pass with proper error handling for missing SIMULATOR_UDID environment variable.

Pedro 5e69aa43 6b21bf40

+39 -25
+27 -22
Sources/AXe/Commands/StreamVideo.swift
··· 85 85 try await setup(logger: logger) 86 86 try await performGlobalSetup(logger: logger) 87 87 88 + // Validate UDID is not empty 89 + guard !simulatorUDID.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { 90 + throw CLIError(errorDescription: "Simulator UDID cannot be empty. Use --udid to specify a simulator.") 91 + } 92 + 88 93 // Get simulator set 89 94 let simulatorSet = try await getSimulatorSet(deviceSetPath: nil, logger: logger, reporter: EmptyEventReporter.shared) 90 95 ··· 152 157 153 158 FileHandle.standardError.write(Data("Stream is now running...\n".utf8)) 154 159 155 - // Set up cancellation handler 160 + // Set up cancellation handler with proper synchronous cleanup 156 161 await withTaskCancellationHandler { 157 162 // Keep the stream running until cancelled 158 163 while !Task.isCancelled { 159 164 try? await Task.sleep(nanoseconds: 100_000_000) // 100ms 160 165 } 161 166 } onCancel: { 162 - Task { 163 - FileHandle.standardError.write(Data("\nStopping video stream...\n".utf8)) 167 + // Synchronous cleanup - stop the stream immediately 168 + FileHandle.standardError.write(Data("\nStopping video stream...\n".utf8)) 169 + 170 + // Use a semaphore to synchronously wait for cleanup 171 + let semaphore = DispatchSemaphore(value: 0) 172 + 173 + BridgeQueues.videoStreamQueue.async { 174 + let stopFuture = videoStream.stopStreaming() 164 175 165 - // Stop the video stream on the same queue 166 - do { 167 - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in 168 - BridgeQueues.videoStreamQueue.async { 169 - let stopFuture = videoStream.stopStreaming() 170 - 171 - stopFuture.onQueue(BridgeQueues.videoStreamQueue, notifyOfCompletion: { future in 172 - if let error = future.error { 173 - FileHandle.standardError.write(Data("Stream stop error: \(error)\n".utf8)) 174 - continuation.resume(throwing: error) 175 - } else { 176 - FileHandle.standardError.write(Data("Stream stopped successfully\n".utf8)) 177 - continuation.resume() 178 - } 179 - }) 180 - } 176 + stopFuture.onQueue(BridgeQueues.videoStreamQueue, notifyOfCompletion: { future in 177 + if let error = future.error { 178 + FileHandle.standardError.write(Data("Stream stop error: \(error)\n".utf8)) 179 + } else { 180 + FileHandle.standardError.write(Data("Stream stopped successfully\n".utf8)) 181 181 } 182 - } catch { 183 - FileHandle.standardError.write(Data("Error stopping stream: \(error)\n".utf8)) 184 - } 182 + semaphore.signal() 183 + }) 184 + } 185 + 186 + // Wait for cleanup to complete (with timeout to prevent hanging) 187 + let timeoutResult = semaphore.wait(timeout: .now() + .seconds(5)) 188 + if timeoutResult == .timedOut { 189 + FileHandle.standardError.write(Data("Warning: Stream cleanup timed out\n".utf8)) 185 190 } 186 191 } 187 192
+5 -1
Tests/StreamVideoTests.swift
··· 119 119 120 120 // Run command directly with timeout since stream-video outputs to stdout 121 121 // and TestHelpers.runAxeCommand doesn't separate stdout/stderr 122 + guard let udid = defaultSimulatorUDID else { 123 + throw TestError.commandError("No simulator UDID specified in SIMULATOR_UDID environment variable") 124 + } 125 + 122 126 let axePath = try TestHelpers.getAxePath() 123 - let fullCommand = "\(axePath) \(command) --udid \(defaultSimulatorUDID ?? "")" 127 + let fullCommand = "\(axePath) \(command) --udid \(udid)" 124 128 125 129 let process = Process() 126 130 process.executableURL = URL(fileURLWithPath: "/bin/bash")
+3 -1
Tests/TestUtilities.swift
··· 177 177 } 178 178 179 179 static func getUIState(simulatorUDID: String? = nil) async throws -> UIElement { 180 - let udid = simulatorUDID ?? defaultSimulatorUDID 180 + guard let udid = simulatorUDID ?? defaultSimulatorUDID else { 181 + throw TestError.commandError("No simulator UDID specified") 182 + } 181 183 let result = try await runAxeCommand("describe-ui", simulatorUDID: udid) 182 184 183 185 // Check if the command failed
+4 -1
Tests/TypeTests.swift
··· 188 188 let textToType = "Text from stdin" 189 189 190 190 // Act - Use echo to pipe text to stdin 191 + guard let udid = defaultSimulatorUDID else { 192 + throw TestError.commandError("No simulator UDID specified in SIMULATOR_UDID environment variable") 193 + } 191 194 let axePath = try TestHelpers.getAxePath() 192 - let command = "echo '\(textToType)' | \(axePath) type --stdin --udid \(defaultSimulatorUDID!)" 195 + let command = "echo '\(textToType)' | \(axePath) type --stdin --udid \(udid)" 193 196 let result = try await CommandRunner.run(command) 194 197 #expect(result.exitCode == 0, "Command should succeed") 195 198 try await Task.sleep(nanoseconds: 1_000_000_000)