Fix display regression UI test launch gating
This commit is contained in:
parent
54ec524a63
commit
10fd323bb0
1 changed files with 20 additions and 138 deletions
|
|
@ -11,9 +11,7 @@ final class DisplayResolutionRegressionUITests: XCTestCase {
|
|||
private var displayDonePath = ""
|
||||
private var helperBinaryPath = ""
|
||||
private var helperLogPath = ""
|
||||
private var appLogPath = ""
|
||||
private var launchedAppProcess: Process?
|
||||
private var launchedAppBundlePath = ""
|
||||
private var launchedApp: XCUIApplication?
|
||||
private var helperProcess: Process?
|
||||
|
||||
override func setUp() {
|
||||
|
|
@ -32,7 +30,6 @@ final class DisplayResolutionRegressionUITests: XCTestCase {
|
|||
displayDonePath = "\(tempPrefix).done"
|
||||
helperBinaryPath = "\(tempPrefix)-helper"
|
||||
helperLogPath = "\(tempPrefix)-helper.log"
|
||||
appLogPath = "\(tempPrefix)-app.log"
|
||||
|
||||
removeTestArtifacts()
|
||||
}
|
||||
|
|
@ -243,38 +240,18 @@ final class DisplayResolutionRegressionUITests: XCTestCase {
|
|||
}
|
||||
|
||||
private func launchAppProcess(targetDisplayID: String) throws {
|
||||
let executablePath = try resolveAppExecutablePath()
|
||||
launchedAppBundlePath = URL(fileURLWithPath: executablePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.path
|
||||
|
||||
let proc = Process()
|
||||
proc.executableURL = URL(fileURLWithPath: executablePath)
|
||||
var environment = ProcessInfo.processInfo.environment
|
||||
let app = XCUIApplication()
|
||||
for (key, value) in launchEnvironment(targetDisplayID: targetDisplayID) {
|
||||
environment[key] = value
|
||||
app.launchEnvironment[key] = value
|
||||
}
|
||||
proc.environment = environment
|
||||
app.launch()
|
||||
launchedApp = app
|
||||
|
||||
let logHandle = FileHandle(forWritingAtPath: appLogPath) ?? {
|
||||
FileManager.default.createFile(atPath: appLogPath, contents: nil)
|
||||
return FileHandle(forWritingAtPath: appLogPath)
|
||||
}()
|
||||
proc.standardOutput = logHandle
|
||||
proc.standardError = logHandle
|
||||
|
||||
try proc.run()
|
||||
launchedAppProcess = proc
|
||||
|
||||
if !waitForAppLaunchDiagnostics(pid: proc.processIdentifier, timeout: 12.0) {
|
||||
if !waitForAppLaunchDiagnostics(timeout: 12.0) {
|
||||
throw NSError(domain: "DisplayResolutionRegressionUITests", code: 2, userInfo: [
|
||||
NSLocalizedDescriptionKey: "App process failed to write launch diagnostics. pid=\(proc.processIdentifier) log=\(readTrimmedFile(atPath: appLogPath) ?? "<missing>")"
|
||||
NSLocalizedDescriptionKey: "App failed to write launch diagnostics. state=\(app.state.rawValue) diagnostics=\(loadDiagnostics() ?? [:])"
|
||||
])
|
||||
}
|
||||
|
||||
reopenAppBundleIfNeeded()
|
||||
}
|
||||
|
||||
private func launchEnvironment(targetDisplayID: String) -> [String: String] {
|
||||
|
|
@ -288,21 +265,20 @@ final class DisplayResolutionRegressionUITests: XCTestCase {
|
|||
}
|
||||
|
||||
private func terminateLaunchedAppIfNeeded() {
|
||||
guard let launchedAppProcess else { return }
|
||||
defer {
|
||||
self.launchedAppProcess = nil
|
||||
self.launchedAppBundlePath = ""
|
||||
guard let launchedApp else { return }
|
||||
defer { self.launchedApp = nil }
|
||||
|
||||
if launchedApp.state == .notRunning {
|
||||
return
|
||||
}
|
||||
|
||||
if launchedAppProcess.isRunning {
|
||||
launchedAppProcess.terminate()
|
||||
launchedAppProcess.waitUntilExit()
|
||||
}
|
||||
launchedApp.terminate()
|
||||
_ = launchedApp.wait(for: .notRunning, timeout: 5.0)
|
||||
}
|
||||
|
||||
private func launchedAppDiagnostics() -> String {
|
||||
guard let launchedAppProcess else { return "not-launched" }
|
||||
return "pid=\(launchedAppProcess.processIdentifier) running=\(launchedAppProcess.isRunning) bundle=\(launchedAppBundlePath)"
|
||||
guard let launchedApp else { return "not-launched" }
|
||||
return "state=\(launchedApp.state.rawValue)"
|
||||
}
|
||||
|
||||
private func waitForTargetDisplayMove(targetDisplayID: String, timeout: TimeInterval) -> Bool {
|
||||
|
|
@ -369,108 +345,15 @@ final class DisplayResolutionRegressionUITests: XCTestCase {
|
|||
.deletingLastPathComponent()
|
||||
}
|
||||
|
||||
private func resolveAppExecutablePath() throws -> String {
|
||||
let fileManager = FileManager.default
|
||||
let env = ProcessInfo.processInfo.environment
|
||||
var productDirectories: [String] = []
|
||||
|
||||
if let builtProductsDir = env["BUILT_PRODUCTS_DIR"], !builtProductsDir.isEmpty {
|
||||
productDirectories.append(builtProductsDir)
|
||||
}
|
||||
|
||||
if let hostPath = env["TEST_HOST"], !hostPath.isEmpty {
|
||||
let hostURL = URL(fileURLWithPath: hostPath)
|
||||
let productsDir = hostURL
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.path
|
||||
productDirectories.append(productsDir)
|
||||
}
|
||||
|
||||
productDirectories.append(contentsOf: inferredBuildProductsDirectories())
|
||||
|
||||
var candidates: [String] = []
|
||||
for productsDir in uniquePaths(productDirectories) {
|
||||
appendAppExecutableCandidates(fromProductsDirectory: productsDir, to: &candidates)
|
||||
}
|
||||
|
||||
for path in uniquePaths(candidates) where fileManager.isExecutableFile(atPath: path) {
|
||||
return URL(fileURLWithPath: path).resolvingSymlinksInPath().path
|
||||
}
|
||||
|
||||
throw NSError(domain: "DisplayResolutionRegressionUITests", code: 4, userInfo: [
|
||||
NSLocalizedDescriptionKey: "Unable to locate cmux app executable. searched=\(uniquePaths(candidates))"
|
||||
])
|
||||
}
|
||||
|
||||
private func inferredBuildProductsDirectories() -> [String] {
|
||||
let bundleURLs = [
|
||||
Bundle.main.bundleURL,
|
||||
Bundle(for: Self.self).bundleURL,
|
||||
]
|
||||
|
||||
return bundleURLs.compactMap { bundleURL in
|
||||
let standardizedPath = bundleURL.standardizedFileURL.path
|
||||
let components = standardizedPath.split(separator: "/")
|
||||
guard let productsIndex = components.firstIndex(of: "Products"),
|
||||
productsIndex + 1 < components.count else {
|
||||
return nil
|
||||
}
|
||||
let prefixComponents = components.prefix(productsIndex + 2)
|
||||
return "/" + prefixComponents.joined(separator: "/")
|
||||
}
|
||||
}
|
||||
|
||||
private func appendAppExecutableCandidates(fromProductsDirectory productsDir: String, to candidates: inout [String]) {
|
||||
candidates.append("\(productsDir)/cmux DEV.app/Contents/MacOS/cmux DEV")
|
||||
candidates.append("\(productsDir)/cmux.app/Contents/MacOS/cmux")
|
||||
|
||||
guard let entries = try? FileManager.default.contentsOfDirectory(atPath: productsDir) else {
|
||||
return
|
||||
}
|
||||
|
||||
for entry in entries.sorted() where entry.hasSuffix(".app") {
|
||||
let executableName = (entry as NSString).deletingPathExtension
|
||||
let executablePath = URL(fileURLWithPath: productsDir)
|
||||
.appendingPathComponent(entry)
|
||||
.appendingPathComponent("Contents/MacOS")
|
||||
.appendingPathComponent(executableName)
|
||||
.path
|
||||
candidates.append(executablePath)
|
||||
}
|
||||
}
|
||||
|
||||
private func uniquePaths(_ paths: [String]) -> [String] {
|
||||
var seen = Set<String>()
|
||||
var ordered: [String] = []
|
||||
for path in paths {
|
||||
let trimmed = path.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty else { continue }
|
||||
if seen.insert(trimmed).inserted {
|
||||
ordered.append(trimmed)
|
||||
}
|
||||
}
|
||||
return ordered
|
||||
}
|
||||
|
||||
private func waitForAppLaunchDiagnostics(pid: Int32, timeout: TimeInterval) -> Bool {
|
||||
private func waitForAppLaunchDiagnostics(timeout: TimeInterval) -> Bool {
|
||||
waitForCondition(timeout: timeout) {
|
||||
guard let diagnostics = self.loadDiagnostics() else { return false }
|
||||
return diagnostics["pid"] == String(pid)
|
||||
guard let pid = diagnostics["pid"], !pid.isEmpty else { return false }
|
||||
guard let stage = diagnostics["stage"], !stage.isEmpty else { return false }
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private func reopenAppBundleIfNeeded() {
|
||||
guard !launchedAppBundlePath.isEmpty else { return }
|
||||
|
||||
let proc = Process()
|
||||
proc.executableURL = URL(fileURLWithPath: "/usr/bin/open")
|
||||
proc.arguments = [launchedAppBundlePath]
|
||||
try? proc.run()
|
||||
}
|
||||
|
||||
private func removeTestArtifacts() {
|
||||
for path in [
|
||||
diagnosticsPath,
|
||||
|
|
@ -480,7 +363,6 @@ final class DisplayResolutionRegressionUITests: XCTestCase {
|
|||
displayDonePath,
|
||||
helperBinaryPath,
|
||||
helperLogPath,
|
||||
appLogPath,
|
||||
] {
|
||||
guard !path.isEmpty else { continue }
|
||||
try? FileManager.default.removeItem(atPath: path)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue