Skip to content

Commit af6a42f

Browse files
committed
test: parallelize and stabilize suite
1 parent 867f706 commit af6a42f

19 files changed

+328
-466
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,5 @@ jobs:
2828
- name: Build
2929
run: swift build --enable-experimental-prebuilts
3030

31-
- name: Test
31+
- name: Test (Parallel)
3232
run: bash test.sh --enable-experimental-prebuilts

AGENT.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,26 @@
1515

1616
## Test Guidance
1717

18-
- Command-line full test runs should use serial execution:
19-
- `swift test --no-parallel`
18+
- Command-line full test runs should use parallel execution:
19+
- `swift test --parallel`
2020
- or `./test.sh`
2121
- `./test.sh` is the preferred full-suite command because it enables Core Data concurrency assertions via `com.apple.CoreData.ConcurrencyDebug=1`.
22-
- In Xcode, disable parallel testing for this package before running the full suite.
2322
- When debugging failures, prefer running a single test file or a filtered suite first.
24-
- This repository's tests are currently treated as serial-only at the full-suite level.
23+
- Swift Testing parallel runs should be treated as same-process concurrency, not process isolation per test case.
24+
- Do not assume `static` state is isolated per test. Static helpers in the test target are shared across concurrently running suites.
2525
- For new Core Data tests, do not write directly through `viewContext` or a raw background context from the test body.
2626
- Prefer `Tests/PersistentHistoryTrackingKitTests/TestAppDataHandler.swift` and actor-isolated helper methods for creating, updating, deleting, and reading test data.
2727
- If a test needs direct inspection of a handler-owned context, use the handler's `withContext` API rather than `context.perform` from the test body.
28+
- Prefer real test data and the actual transaction-processing path over synthetic Core Data artifacts. Do not hand-build `HookContext`, `NSManagedObjectID`, or similar fake inputs when a test can create data through `TestAppDataHandler` and trigger processing through the library.
2829

2930
## Known Test Constraints
3031

31-
- Full-suite parallel execution in Xcode can hang before any individual test completes.
32-
- Serial full-suite execution has been verified to pass repeatedly.
32+
- Full-suite parallel execution has been revalidated after test-infrastructure fixes.
33+
- Repeated parallel runs have been observed to pass after serializing test container creation.
3334
- Repeated Xcode runs of `cleanTransactionsByTimestampAndAuthors` have been observed to pass.
34-
- Repeated Xcode serial full-suite runs have also been observed to pass.
35+
- `TestModelBuilder.createContainer` is intentionally serialized with a global lock.
36+
- Reason: concurrent `NSPersistentContainer` creation and `loadPersistentStores` can crash inside Core Data with `EXC_BAD_ACCESS`, even when each test uses a unique SQLite store URL.
37+
- The container creation lock is required for test stability; do not remove it unless the Core Data initialization path is reworked and revalidated under repeated parallel runs.
3538

3639
## Failure Triage
3740

@@ -46,7 +49,7 @@ If a crash or hang appears again, collect the following before changing code:
4649
## Build and Cache Notes
4750

4851
- If SwiftPM reports compiler/module version mismatches, clear `.build` and rerun.
49-
- If Xcode behaves inconsistently while command-line serial runs are stable, suspect DerivedData or Xcode test execution settings before changing repository code.
52+
- If Xcode behaves inconsistently while command-line parallel runs are stable, suspect DerivedData or Xcode test execution settings before changing repository code.
5053

5154
## Version Tag Rule
5255

README.md

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Persistent History Tracking Kit
1+
# Persistent History Tracking Kit 2
22

33
**Swift 6 ready****Actor-based****Fully concurrent****Type-safe**
44

@@ -503,33 +503,31 @@ let hookB = await kit.registerMergeHook(before: hookA) { _ in
503503

504504
## Testing
505505

506-
**⚠️ Important: Tests Must Run Serially**
507-
508-
Due to Core Data's singleton nature and shared persistent stores, **tests must run serially**, not in parallel. Running tests in parallel will cause race conditions and failures.
506+
Tests are validated under parallel execution. The test infrastructure serializes `NSPersistentContainer` creation internally to avoid Core Data store-loading crashes while preserving parallel suite execution.
509507

510508
### Recommended: Use the test script
511509

512510
```bash
513-
# Run all tests serially (recommended)
511+
# Run all tests in parallel (recommended)
514512
./test.sh
515513
```
516514

517515
The test script ensures:
518516

519-
-All tests run sequentially
520-
-Proper cleanup between test suites
517+
-Full-suite parallel execution
518+
-Core Data concurrency assertions enabled
521519
- ✅ Reliable results
522520

523521
### Alternative: Manual testing (caution required)
524522

525-
If you run tests manually, use filters with caution:
523+
If you run tests manually, prefer the same parallel settings:
526524

527525
```bash
528-
# ⚠️ Only use this for individual test suites
529-
swift test --filter HookRegistryActorTests
526+
# Run the full suite in parallel
527+
swift test --parallel
530528

531-
# ❌ AVOID: Running all tests may cause failures due to Core Data conflicts
532-
swift test # May fail - use test.sh instead
529+
# Or run an individual suite
530+
swift test --filter HookRegistryActorTests
533531
```
534532

535533
Test suites include:

READMECN.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Persistent History Tracking Kit
1+
# Persistent History Tracking Kit 2
22

33
**适配 Swift 6****Actor 架构****并发安全****类型安全**
44

@@ -385,13 +385,18 @@ let hookB = await kit.registerMergeHook(before: hookA) { _ in print("Hook B"); r
385385

386386
## 测试
387387

388-
> **重要:测试必须串行执行**Core Data 共享同一存储,若并发运行测试可能发生竞争导致失败
388+
> 测试现已验证可并行执行。测试基础设施会在内部串行化 `NSPersistentContainer` 的创建,以规避 Core Data 在并发加载存储时的崩溃,同时保留 suite 级并行执行
389389
390390
```bash
391-
./test.sh # 推荐脚本,自动串行化
391+
./test.sh # 推荐脚本,自动启用并行测试
392392
```
393393

394-
如需手动运行,请仅针对单个测试集使用 `swift test --filter ...`
394+
如需手动运行,建议使用:
395+
396+
```bash
397+
swift test --parallel
398+
swift test --filter HookRegistryActorTests
399+
```
395400

396401
---
397402

Tests/PersistentHistoryTrackingKitTests/CleanStrategyTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Testing
1010

1111
@testable import PersistentHistoryTrackingKit
1212

13-
@Suite("Clean Strategy Tests", .serialized)
13+
@Suite("Clean Strategy Tests")
1414
struct CleanStrategyTests {
1515
@Test("None strategy disables automatic cleanup")
1616
func noneStrategyDisablesAutomaticCleanup() async throws {

Tests/PersistentHistoryTrackingKitTests/ConcurrencyTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Testing
1010

1111
@testable import PersistentHistoryTrackingKit
1212

13-
@Suite("Concurrency Safety Tests", .serialized)
13+
@Suite("Concurrency Safety Tests")
1414
struct ConcurrencyTests {
1515
@Test("Multithreaded concurrent writes")
1616
func concurrentWrites() async throws {

Tests/PersistentHistoryTrackingKitTests/CoreDataEvolutionDataHandlerTests.swift

Lines changed: 0 additions & 91 deletions
This file was deleted.

0 commit comments

Comments
 (0)