Files
Termind/TFITests/MobileBackgroundControllerTests.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)
}
}