Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0d3e8db
fix: carry compiler output in BuildError.compilationFailed
ericodx Apr 1, 2026
94d725a
test: update BuildStageTests for compilationFailed output
ericodx Apr 1, 2026
a1cc2da
feat: add createClean to SandboxFactory
ericodx Apr 1, 2026
b481012
feat: add SPM build path to FallbackExecutor
ericodx Apr 1, 2026
0be9f61
feat: add retryExcludingErrors, canonicalPath and validateSPMBaseline…
ericodx Apr 1, 2026
7e6b58a
test: add spmRetryExcludingErrors tests and update spmBuildFailure in…
ericodx Apr 1, 2026
d16a6fb
feat: use shared sandbox with incremental builds for SPM in Incompati…
ericodx Apr 1, 2026
7ef507d
test: update IncompatibleMutantExecutorTests for shared SPM sandbox
ericodx Apr 1, 2026
5a47f94
fix: log unviable mutant diagnostics in TestExecutionStage
ericodx Apr 1, 2026
3b363a6
fix: kill process group after normal process exit in ProcessLauncher
ericodx Apr 1, 2026
e9d1aa7
feat: narrow exclusion per problematic mutant on schematized build fa…
ericodx Apr 1, 2026
9ec7109
test: add narrow exclusion test for per-mutant schematized build error
ericodx Apr 1, 2026
3fc328a
fix: classify empty output as crash in SPMResultParser
ericodx Apr 3, 2026
fcca6ff
feat: add CapturedOutput and launchCapturingDeferred to ProcessLaunching
ericodx Apr 3, 2026
0f7df87
feat: implement launchCapturingDeferred and escaped child cleanup in …
ericodx Apr 3, 2026
09bc9fe
test: add launchCapturingDeferred to MockProcessLauncher
ericodx Apr 3, 2026
aa79e44
feat: add TestResultResolver for centralized result parsing
ericodx Apr 3, 2026
979f7a7
refactor: unify launch result types and use deferred cleanup in execu…
ericodx Apr 3, 2026
003761f
feat: reroute schema-excluded mutants and guarantee SimulatorPool tea…
ericodx Apr 3, 2026
008b97c
test: update MutantExecutorTests for rerouted mutant expectations
ericodx Apr 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Sources/SwiftMutationTesting/Build/BuildError.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
enum BuildError: Error, Equatable {
case compilationFailed
case compilationFailed(output: String)
case xctestrunNotFound

static func == (lhs: BuildError, rhs: BuildError) -> Bool {
switch (lhs, rhs) {
case (.compilationFailed, .compilationFailed): return true
case (.xctestrunNotFound, .xctestrunNotFound): return true
default: return false
}
}
}
18 changes: 9 additions & 9 deletions Sources/SwiftMutationTesting/Build/BuildStage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@ struct BuildStage: Sendable {
arguments += ["-project", projectURL.path]
}

let exitCode = try await launcher.launch(
let (exitCode, buildOutput) = try await launcher.launchCapturing(
executableURL: URL(fileURLWithPath: "/usr/bin/xcodebuild"),
arguments: arguments,
environment: nil,
additionalEnvironment: [:],
workingDirectoryURL: sandbox.rootURL,
timeout: timeout
)

guard exitCode == 0 else {
throw BuildError.compilationFailed
throw BuildError.compilationFailed(output: buildOutput)
}

let productsURL = derivedDataURL.appendingPathComponent("Build/Products")
Expand All @@ -59,20 +61,18 @@ struct BuildStage: Sendable {
testTarget: String?,
timeout: Double
) async throws -> BuildArtifact {
var arguments = ["build", "--build-tests"]
let arguments = ["build", "--build-tests"]

if let testTarget {
arguments += ["--target", testTarget]
}

let exitCode = try await launcher.launch(
let (exitCode, buildOutput) = try await launcher.launchCapturing(
executableURL: URL(fileURLWithPath: "/usr/bin/swift"),
arguments: arguments,
environment: nil,
additionalEnvironment: [:],
workingDirectoryURL: sandbox.rootURL,
timeout: timeout
)

guard exitCode == 0 else { throw BuildError.compilationFailed }
guard exitCode == 0 else { throw BuildError.compilationFailed(output: buildOutput) }

return BuildArtifact(
derivedDataPath: sandbox.rootURL.appendingPathComponent(".build").path,
Expand Down
46 changes: 29 additions & 17 deletions Sources/SwiftMutationTesting/Execution/FallbackExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,36 @@ struct FallbackExecutor: Sendable {

await deps.reporter.report(.fallbackBuildStarted(filePath: file.originalPath))

guard case .xcode(let scheme, let destination) = configuration.build.projectType else {
try? sandbox.cleanup()
return await markUnviable(mutants: fileMutants, testFilesHash: testFilesHash)
}

let artifact: BuildArtifact
do {
artifact = try await BuildStage(launcher: deps.launcher).build(
sandbox: sandbox,
scheme: scheme,
destination: destination,
timeout: configuration.build.timeout
)
await deps.reporter.report(.fallbackBuildFinished(filePath: file.originalPath, success: true))
} catch {
await deps.reporter.report(.fallbackBuildFinished(filePath: file.originalPath, success: false))
try? sandbox.cleanup()
return await markUnviable(mutants: fileMutants, testFilesHash: testFilesHash)
switch configuration.build.projectType {
case .xcode(let scheme, let destination):
do {
artifact = try await BuildStage(launcher: deps.launcher).build(
sandbox: sandbox,
scheme: scheme,
destination: destination,
timeout: configuration.build.timeout
)
await deps.reporter.report(.fallbackBuildFinished(filePath: file.originalPath, success: true))
} catch {
await deps.reporter.report(.fallbackBuildFinished(filePath: file.originalPath, success: false))
try? sandbox.cleanup()
return await markUnviable(mutants: fileMutants, testFilesHash: testFilesHash)
}

case .spm:
do {
artifact = try await BuildStage(launcher: deps.launcher).buildSPM(
sandbox: sandbox,
testTarget: configuration.build.testTarget,
timeout: configuration.build.timeout
)
await deps.reporter.report(.fallbackBuildFinished(filePath: file.originalPath, success: true))
} catch {
await deps.reporter.report(.fallbackBuildFinished(filePath: file.originalPath, success: false))
try? sandbox.cleanup()
return await markUnviable(mutants: fileMutants, testFilesHash: testFilesHash)
}
}

let context = TestExecutionContext(
Expand Down
Loading