99 lines
3.4 KiB
Swift
99 lines
3.4 KiB
Swift
// SPDX-FileCopyrightText: 2026 Kaede Akino
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import Testing
|
|
import UIKit
|
|
|
|
@testable import TermindIOS
|
|
|
|
@Suite @MainActor
|
|
struct MobileBackgroundControllerTests {
|
|
@Test func expirationRunsSynchronouslyAndEndsEachLeaseExactlyOnce() throws {
|
|
let first = UIBackgroundTaskIdentifier(rawValue: 1)
|
|
let second = UIBackgroundTaskIdentifier(rawValue: 2)
|
|
let platform = MobileBackgroundTaskPlatformSpy(tasks: [first, second])
|
|
var events: [String] = []
|
|
platform.didEnd = { _ in events.append("end") }
|
|
let controller = MobileBackgroundController(platform: platform)
|
|
|
|
controller.begin { events.append("expire-first") }
|
|
let stale = try #require(platform.expirations.first)
|
|
controller.finish()
|
|
stale()
|
|
stale()
|
|
|
|
controller.begin { events.append("expire-second") }
|
|
let current = try #require(platform.expirations.last)
|
|
current()
|
|
current()
|
|
controller.finish()
|
|
|
|
#expect(events == ["end", "expire-second", "end"])
|
|
#expect(platform.ended == [first, second])
|
|
}
|
|
|
|
@Test func synchronousExpirationDuringBeginRetiresReturnedTaskWithoutOverwritingReplacement() throws {
|
|
let expired = UIBackgroundTaskIdentifier(rawValue: 1)
|
|
let replacement = UIBackgroundTaskIdentifier(rawValue: 2)
|
|
let platform = MobileBackgroundTaskPlatformSpy(tasks: [expired, replacement])
|
|
var expirationCount = 0
|
|
var replacementExpirationCount = 0
|
|
var controller: MobileBackgroundController!
|
|
platform.expireSynchronouslyOnNextBegin = true
|
|
controller = MobileBackgroundController(platform: platform)
|
|
|
|
controller.begin {
|
|
expirationCount += 1
|
|
controller.begin { replacementExpirationCount += 1 }
|
|
}
|
|
|
|
#expect(expirationCount == 1)
|
|
#expect(replacementExpirationCount == 0)
|
|
#expect(platform.ended == [expired])
|
|
let replacementExpiration = try #require(platform.expirations.last)
|
|
replacementExpiration()
|
|
#expect(replacementExpirationCount == 1)
|
|
#expect(platform.ended == [expired, replacement])
|
|
}
|
|
|
|
@Test func invalidBeginExpiresOnceWithoutEndingAnInvalidTask() {
|
|
var expirationCount = 0
|
|
let platform = MobileBackgroundTaskPlatformSpy(tasks: [.invalid])
|
|
let controller = MobileBackgroundController(platform: platform)
|
|
|
|
controller.begin { expirationCount += 1 }
|
|
controller.finish()
|
|
|
|
#expect(expirationCount == 1)
|
|
#expect(platform.ended.isEmpty)
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private final class MobileBackgroundTaskPlatformSpy: MobileBackgroundTaskPlatform {
|
|
var expirations: [@MainActor () -> Void] = []
|
|
var ended: [UIBackgroundTaskIdentifier] = []
|
|
var expireSynchronouslyOnNextBegin = false
|
|
var didEnd: ((UIBackgroundTaskIdentifier) -> Void)?
|
|
private var tasks: [UIBackgroundTaskIdentifier]
|
|
|
|
init(tasks: [UIBackgroundTaskIdentifier]) {
|
|
self.tasks = tasks
|
|
}
|
|
|
|
func begin(expiration: @escaping @MainActor () -> Void) -> UIBackgroundTaskIdentifier {
|
|
let task = tasks.removeFirst()
|
|
expirations.append(expiration)
|
|
if expireSynchronouslyOnNextBegin {
|
|
expireSynchronouslyOnNextBegin = false
|
|
expiration()
|
|
}
|
|
return task
|
|
}
|
|
|
|
func end(_ task: UIBackgroundTaskIdentifier) {
|
|
ended.append(task)
|
|
didEnd?(task)
|
|
}
|
|
}
|