diff --git a/Sources/XCLogParser/parser/ParserBuildSteps.swift b/Sources/XCLogParser/parser/ParserBuildSteps.swift index cdea461..912c8bd 100644 --- a/Sources/XCLogParser/parser/ParserBuildSteps.swift +++ b/Sources/XCLogParser/parser/ParserBuildSteps.swift @@ -374,7 +374,8 @@ public final class ParserBuildSteps { private func addCompilationTimesToTarget(_ target: BuildStep) -> BuildStep { let lastCompilationStep = target.subSteps - .filter { $0.isCompilationStep() && $0.fetchedFromCache == false } + .filter { $0.isCompilationStep() && $0.fetchedFromCache == false && + $0.compilationEndTimestamp >= target.startTimestamp } .max { $0.compilationEndTimestamp < $1.compilationEndTimestamp } guard let lastStep = lastCompilationStep else { return target.with(newCompilationEndTimestamp: target.startTimestamp, andCompilationDuration: 0.0) @@ -385,7 +386,8 @@ public final class ParserBuildSteps { private func addCompilationTimesToApp(_ app: BuildStep) -> BuildStep { let lastCompilationStep = app.subSteps - .filter { $0.compilationDuration > 0 && $0.fetchedFromCache == false } + .filter { $0.compilationDuration > 0 && $0.fetchedFromCache == false && + $0.compilationEndTimestamp >= app.startTimestamp } .max { $0.compilationEndTimestamp < $1.compilationEndTimestamp } guard let lastStep = lastCompilationStep else { return app.with(newCompilationEndTimestamp: app.startTimestamp, diff --git a/Tests/XCLogParserTests/ParserTests.swift b/Tests/XCLogParserTests/ParserTests.swift index a0e978a..5c01c54 100644 --- a/Tests/XCLogParserTests/ParserTests.swift +++ b/Tests/XCLogParserTests/ParserTests.swift @@ -346,6 +346,31 @@ note: use 'updatedDoSomething' instead\r doSomething()\r ^~~~~~~~~~~\r XCTAssertEqual(expectedCompilationDuration, targetStep.compilationDuration) } + func testParseTargetCompilationTimesIgnoresStaleSubsteps() { + // For incremental builds, Xcode reuses substep entries from previous build sessions for + // files that didn't need recompilation. Those substeps keep their original (older) + // timestamps and are not flagged `wasFetchedFromCache`. The target's compilationDuration + // must remain non-negative; substeps that ended before the target started belong to a + // prior session and should be ignored. + let now = Date().timeIntervalSince1970 + let staleCompileEnd = now - 1000 + let staleStep = makeFakeBuildStep(title: "Stale Compile", + type: .detail, + detailStepType: .cCompilation, + startTimestamp: now - 1010, + fetchedFromCache: false) + .with(newCompilationEndTimestamp: staleCompileEnd, andCompilationDuration: 10) + var targetStep = makeFakeBuildStep(title: "Build Target", + type: .target, + detailStepType: .none, + startTimestamp: now, + fetchedFromCache: false).with(subSteps: [staleStep]) + + targetStep = parser.addCompilationTimes(step: targetStep) + XCTAssertEqual(targetStep.startTimestamp, targetStep.compilationEndTimestamp) + XCTAssertEqual(0.0, targetStep.compilationDuration) + } + func testParseAppCompilationTimes() { let expectedCompilationDuration = 50.0 let now = Date().timeIntervalSince1970