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)
88 lines
3.6 KiB
Swift
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", "组合配置后台解析应最终完成")
|
|
}
|
|
}
|