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)
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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", "组合配置后台解析应最终完成")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user