ReadViewSDK/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/ConcurrentParsingTests.swift
shen d20196ee34 feat: configurable chapter window & parallel metadata parsing with benchmark
1. Configurable chapter window size (onDemandChapterWindowSize: 3-15)
   - Parameterized window radius in RDEPUBChapterRuntimeStore
   - Updated RDEPUBChapterWindowCoordinator to use configurable radius
   - RDEPUBChapterWindowSnapshot.from() accepts chapter array instead of fixed prev/next
   - Even numbers round up to odd (4→5), min 3, max 15

2. Configurable metadata parsing concurrency (metadataParsingConcurrency)
   - Default equals CPU core count
   - Parallel execution via OperationQueue in paginateMetadataOnly
   - Each worker creates independent builder instance
   - NSLock protects result aggregation

3. Per-chapter and total wall-clock timing instrumentation
   - Separated render vs I/O timing per chapter
   - Summary log with wallClockMs, renderTotalMs, writeTotalMs, avgRenderMs
   - Timing stored in RDEPUBReaderContext for test access

4. UI automation test infrastructure
   - Added --demo-window-size, --demo-concurrency, --demo-clear-cache launch args
   - DemoReaderState exposes windowSize, parseMs, parseConcurrency
   - ConfigurableWindowTests: 5 test cases for window size 3/5/15
   - ConcurrentParsingTests: 4 test cases for concurrency 2/4
   - MetadataParseBenchmarkTests: serial vs parallel benchmark

5. Bug fixes
   - Fixed page snap-back during background parsing (isUserInteracting check)
   - Reduced BookPageMap refresh frequency from 16 to 32 chapters
   - Moved waitForReadingInteractionToSettle outside operation loop

6. Design doc: dual-layer PageMap (estimated + precise mixed)
2026-06-03 23:38:11 +08:00

88 lines
3.6 KiB
Swift

import XCTest
final class ConcurrentParsingTests: XCTestCase {
private let app = XCUIApplication()
private let largeBookQuery = "凡人修仙传"
override func setUpWithError() throws {
continueAfterFailure = false
}
func testConcurrency2ParsingCompletes() throws {
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true, concurrency: 2)
app.waitForReader(timeout: 20)
_ = app.waitForDemoReaderState(timeout: 15, description: "concurrency=2 首开") { state in
state.mode == "bookPageMap" && (state.page ?? 0) >= 1
}
XCTAssertTrue(app.otherElements[IDs.readerContent].waitForExistence(timeout: 5), "concurrency=2 应可阅读")
let completed = app.waitForDemoReaderState(timeout: 90, description: "concurrency=2 后台解析完成") { state in
state.pagination == "full"
}
XCTAssertEqual(completed.pagination, "full", "concurrency=2 后台解析应最终完成")
}
func testConcurrency4ParsingCompletes() throws {
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true, concurrency: 4)
app.waitForReader(timeout: 20)
_ = app.waitForDemoReaderState(timeout: 15, description: "concurrency=4 首开") { state in
state.mode == "bookPageMap" && (state.page ?? 0) >= 1
}
XCTAssertTrue(app.otherElements[IDs.readerContent].waitForExistence(timeout: 5), "concurrency=4 应可阅读")
let completed = app.waitForDemoReaderState(timeout: 90, description: "concurrency=4 后台解析完成") { state in
state.pagination == "full"
}
XCTAssertEqual(completed.pagination, "full", "concurrency=4 后台解析应最终完成")
}
func testConcurrentParsingProgresses() throws {
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true, concurrency: 2)
app.waitForReader(timeout: 20)
let initialState = app.waitForDemoReaderState(timeout: 15, description: "获取初始进度") { state in
state.mode == "bookPageMap" && state.knownChapters != nil
}
let initialKnown = initialState.knownChapters ?? 0
let progressed = app.waitForDemoReaderState(timeout: 60, description: "concurrency=2 后台解析推进") { state in
guard state.mode == "bookPageMap" else { return false }
return state.pagination == "full" || (state.knownChapters ?? 0) > initialKnown
}
XCTAssertTrue(
progressed.pagination == "full" || (progressed.knownChapters ?? 0) > initialKnown,
"concurrency=2 后台解析应推进,初始=\(initialKnown) 当前=\(progressed.knownChapters ?? 0)"
)
}
func testConcurrentParsingWithWindowSize() throws {
app.launchAndOpenSampleBook(
bookTitleQuery: largeBookQuery,
resetsReaderState: true,
windowSize: 5,
concurrency: 2
)
app.waitForReader(timeout: 20)
let state = app.waitForDemoReaderState(timeout: 15, description: "window=5 + concurrency=2") { state in
state.mode == "bookPageMap" && state.knownChapters != nil
}
XCTAssertEqual(state.windowSize, 5, "windowSize 应为 5")
XCTAssertTrue(app.otherElements[IDs.readerContent].waitForExistence(timeout: 5), "组合配置应可阅读")
let completed = app.waitForDemoReaderState(timeout: 90, description: "组合配置后台解析完成") { state in
state.pagination == "full"
}
XCTAssertEqual(completed.pagination, "full", "组合配置后台解析应最终完成")
}
}