Files
XStats/Packages/XStatsKit/Tests/CleanerTests/ScanCancellationTests.swift
T
ysicingandClaude Opus 5.5 036a4a6598 refactor(uninstaller): 移除无调用方的应用列表接口并放宽取消测试
- 删除吞错的 installedApps(home:excluding:),统一使用可取消的 scanInstalledApps
- 取消测试改为按完整扫描检查点数在中途取消,不再绑定具体检查次数

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:20:08 +08:00

71 lines
3.2 KiB
Swift

// Copyright (C) 2026 ysicing
// SPDX-License-Identifier: AGPL-3.0-or-later
import Foundation
import Testing
@testable import Cleaner
struct ScanCancellationTests {
@Test func cancellationInterruptsNestedApplicationListingWithoutPartialResults() throws {
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
let nested = root.appendingPathComponent("Utilities")
try FileManager.default.createDirectory(at: nested, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: root) }
for index in 0..<20 {
let app = nested.appendingPathComponent("Fixture-\(index).app/Contents")
try FileManager.default.createDirectory(at: app, withIntermediateDirectories: true)
let info = ["CFBundleIdentifier": "test.list.\(index)", "CFBundleName": "Fixture-\(index)"]
try PropertyListSerialization.data(fromPropertyList: info, format: .xml, options: 0)
.write(to: app.appendingPathComponent("Info.plist"))
}
// 先统计完整扫描的检查点,再在中途取消;不依赖检查点的具体位置。
var total = 0
_ = try AppUninstaller.installedApps(in: [root], excluding: []) { total += 1 }
#expect(total > 20, "每个嵌套候选都应检查取消,实际检查 \(total) 次")
let cancelAt = total / 2
var checks = 0
#expect(throws: CancellationError.self) {
try AppUninstaller.installedApps(in: [root], excluding: []) {
checks += 1
if checks == cancelAt { throw CancellationError() }
}
}
#expect(checks == cancelAt, "取消后不应继续遍历")
let apps = try AppUninstaller.installedApps(in: [root], excluding: ["test.list.0"], checkCancellation: {})
#expect(apps.count == 19 && !apps.contains { $0.bundleIdentifier == "test.list.0" })
}
@Test func cancelledTaskDoesNotStartApplicationListing() async {
let scan = Task.detached {
withUnsafeCurrentTask { $0?.cancel() }
do {
_ = try AppUninstaller.scanInstalledApps()
return false
} catch is CancellationError {
return true
} catch {
return false
}
}
#expect(await scan.value)
}
@Test func cancellationInterruptsDirectoryTraversalWithoutReturningPartialSize() throws {
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: root) }
for index in 0..<20 {
try Data(repeating: 1, count: 4096).write(to: root.appendingPathComponent("\(index)"))
}
var checks = 0
#expect(throws: CancellationError.self) {
try CleanEngine.scanAllocatedSize(of: root) {
checks += 1
if checks == 4 { throw CancellationError() }
}
}
#expect(checks == 4)
#expect(try CleanEngine.scanAllocatedSize(of: root) == CleanEngine.allocatedSize(of: root))
}
}