mirror of
https://github.com/apple/container.git
synced 2026-09-22 23:55:34 +00:00
Simplify the serial suite trait (#1497)
## Motivation and Context The `SuiteGate` actor introduced for the serial test trait in https://github.com/apple/container/pull/1489 could be simplified using containerization's [AsyncLock](https://github.com/apple/containerization/blob/f2c42402e744df992fecb84eb3a82935c6fa01d3/Sources/ContainerizationExtras/AsyncLock.swift#L23). ## Testing - [ ] Tested locally Note: working on A/B performance testing to validate this approach does not significantly impact test run performance. Signed-off-by: Kathryn Baldauf <k_baldauf@apple.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ContainerizationExtras
|
||||
import Testing
|
||||
|
||||
/// Integration test suites share mutable system state (apiserver, containers, networks, volumes).
|
||||
@@ -22,20 +23,18 @@ import Testing
|
||||
/// each suite still run in parallel.
|
||||
struct SerialSuiteTrait: SuiteTrait, TestScoping {
|
||||
var isRecursive: Bool { false }
|
||||
private static let lock = AsyncLock()
|
||||
|
||||
func provideScope(
|
||||
for test: Test,
|
||||
testCase: Test.Case?,
|
||||
performing function: @Sendable () async throws -> Void
|
||||
) async throws {
|
||||
await SuiteGate.shared.enter()
|
||||
do {
|
||||
try await function()
|
||||
} catch {
|
||||
await SuiteGate.shared.leave()
|
||||
throw error
|
||||
try await withoutActuallyEscaping(function) { escapingFunction in
|
||||
try await Self.lock.withLock { _ in
|
||||
try await escapingFunction()
|
||||
}
|
||||
}
|
||||
await SuiteGate.shared.leave()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-75
@@ -16,26 +16,6 @@
|
||||
|
||||
import Testing
|
||||
|
||||
/// Helper actor that tracks how many callers are active simultaneously.
|
||||
actor ConcurrencyTracker {
|
||||
private var current = 0
|
||||
private var peak = 0
|
||||
|
||||
func enterAndGetCount() -> Int {
|
||||
current += 1
|
||||
if current > peak { peak = current }
|
||||
return current
|
||||
}
|
||||
|
||||
func leave() {
|
||||
current -= 1
|
||||
}
|
||||
|
||||
func peakConcurrency() -> Int {
|
||||
peak
|
||||
}
|
||||
}
|
||||
|
||||
/// Async countdown latch. N callers suspend on arriveAndWait(); the Nth arrival
|
||||
/// resumes them all simultaneously, proving they were running concurrently.
|
||||
actor Barrier {
|
||||
@@ -62,61 +42,6 @@ actor Barrier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper actor that records the order in which tasks complete.
|
||||
private actor CompletionRecorder {
|
||||
private var order: [Int] = []
|
||||
|
||||
func record(_ id: Int) {
|
||||
order.append(id)
|
||||
}
|
||||
|
||||
func completedCount() -> Int {
|
||||
order.count
|
||||
}
|
||||
}
|
||||
|
||||
@Suite struct SuiteGateTests {
|
||||
@Test func mutualExclusion() async {
|
||||
let gate = SuiteGate()
|
||||
let tracker = ConcurrencyTracker()
|
||||
let barrier = Barrier(count: 5)
|
||||
|
||||
await withTaskGroup(of: Void.self) { group in
|
||||
for _ in 0..<5 {
|
||||
group.addTask {
|
||||
// Barrier ensures all 5 tasks are live before any enters the gate
|
||||
await barrier.arriveAndWait()
|
||||
await gate.enter()
|
||||
let count = await tracker.enterAndGetCount()
|
||||
#expect(count == 1, "Only 1 task should be inside the gate at a time")
|
||||
await tracker.leave()
|
||||
await gate.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test func multipleWaiters() async {
|
||||
let gate = SuiteGate()
|
||||
let recorder = CompletionRecorder()
|
||||
let barrier = Barrier(count: 3)
|
||||
|
||||
await withTaskGroup(of: Void.self) { group in
|
||||
for i in 0..<3 {
|
||||
group.addTask {
|
||||
await barrier.arriveAndWait()
|
||||
await gate.enter()
|
||||
await recorder.record(i)
|
||||
await gate.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let count = await recorder.completedCount()
|
||||
#expect(count == 3, "All 3 tasks should have completed")
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies that tests within a `.serialSuites`-annotated suite still run in parallel.
|
||||
/// All 3 tests rendezvous at a shared barrier. If they run concurrently, the barrier
|
||||
/// opens and tests pass instantly. If serialized, the first test deadlocks waiting for
|
||||
@@ -1,46 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2026 Apple Inc. and the container project authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// Async semaphore with capacity 1. Used by SerialSuiteTrait to ensure only one
|
||||
/// integration test suite executes at a time.
|
||||
actor SuiteGate {
|
||||
static let shared = SuiteGate()
|
||||
|
||||
private var isOccupied = false
|
||||
private var waiters: [CheckedContinuation<Void, Never>] = []
|
||||
|
||||
func enter() async {
|
||||
if !isOccupied {
|
||||
isOccupied = true
|
||||
return
|
||||
}
|
||||
await withCheckedContinuation { continuation in
|
||||
waiters.append(continuation)
|
||||
}
|
||||
}
|
||||
|
||||
// Must remain synchronous (non-async body). This guarantees the call
|
||||
// completes even if the calling task is cancelled, because actor hops
|
||||
// for synchronous methods are not cancellation points.
|
||||
func leave() {
|
||||
if let next = waiters.first {
|
||||
waiters.removeFirst()
|
||||
next.resume()
|
||||
} else {
|
||||
isOccupied = false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user