Skip to content
Open
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
3 changes: 2 additions & 1 deletion PerformanceSuite/PerformanceApp/MetricsConsumer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ class MetricsConsumer: PerformanceSuiteMetricsReceiver {

func fatalHangReceived(info: HangInfo) {
log("fatalHangReceived \(info)")
interop?.send(message: Message.fatalHang)
let message: Message = info.duringStartup ? .startupFatalHang : .fatalHang
interop?.send(message: message)
}

func nonFatalHangReceived(info: HangInfo) {
Expand Down
6 changes: 6 additions & 0 deletions PerformanceSuite/PerformanceApp/RootController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ class RootController: UIHostingController<MenuView> {

// simulate long startup time
Thread.sleep(forTimeInterval: 2)

// For UI tests: simulate a fatal hang during startup if requested
if ProcessInfo.processInfo.environment[startupFatalHangKey] != nil {
// Block the main thread indefinitely to simulate a fatal hang during startup
Thread.sleep(forTimeInterval: .infinity)
}
}

@MainActor @objc required dynamic init?(coder aDecoder: NSCoder) {
Expand Down
3 changes: 3 additions & 0 deletions PerformanceSuite/PerformanceApp/UITestsInterop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import GCDWebServer

public let inTestsKey = "UI_TESTS"
public let clearStorageKey = "CLEAR_STORAGE"
public let startupFatalHangKey = "STARTUP_FATAL_HANG"


/// Message which is sent from the app to UI tests target
Expand All @@ -23,6 +24,7 @@ public enum Message: Codable, Equatable {
case fragmentTTI(duration: Int, fragment: String)
case hangStarted
case fatalHang
case startupFatalHang
case nonFatalHang
case watchdogTermination
case memoryLeak
Expand All @@ -34,6 +36,7 @@ public enum Message: Codable, Equatable {
(.appFreezeTime, .appFreezeTime),
(.hangStarted, .hangStarted),
(.fatalHang, .fatalHang),
(.startupFatalHang, .startupFatalHang),
(.nonFatalHang, .nonFatalHang),
(.watchdogTermination, .watchdogTermination),
(.memoryLeak, .memoryLeak),
Expand Down
1 change: 1 addition & 0 deletions PerformanceSuite/Sources/Utils/Storage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,6 @@ extension UserDefaults: Storage {

public func write(domain: String, key: String, value: String?) {
set(value, forKey: defaultsKey(domain: domain, key: key))
synchronize()
}
}
27 changes: 27 additions & 0 deletions PerformanceSuite/UITests/TerminationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,31 @@ final class TerminationTests: BaseTests {
app.staticTexts["Memory Leak"].tap()
waitForMessage { $0 == .memoryLeak }
}

func testStartupFatalHang() throws {
// First launch: clear storage
performFirstLaunch()
assertNoMessages(.fatalHang, .startupFatalHang, .hangStarted)

// Second launch: trigger startup fatal hang
app.terminate()
app.launchEnvironment = [inTestsKey: "1", startupFatalHangKey: "1"]
app.launch()

// Wait for hang to be detected (hang detection takes time)
waitForTimeout(5)

// Kill the app during the hang (simulating system kill or user force quit)
// Note: We may or may not receive hangStarted before termination, so we don't check for it
app.terminate()

// Third launch: should receive startup fatal hang callback (duringStartup=true)
app.launchEnvironment = [inTestsKey: "1"]
app.launch()

waitForMessage { $0 == .startupFatalHang }
// The startup fatal hang was successfully detected and reported!
assertNoMessages(.crash, .nonFatalHang, .fatalHang)
// Note: watchdogTermination might also be reported, which is acceptable
}
}
Loading