Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,19 @@ struct IncompatibleMutantExecutor: Sendable {
let duration = launched.duration
await pool.release(slot)

let outcome = try await ResultParser(launcher: launcher).parse(
exitCode: launched.exitCode,
output: launched.output,
xcresultPath: launched.xcresultPath,
timeout: configuration.build.timeout
)
let outcome: TestRunOutcome
switch configuration.build.projectType {
case .xcode:
outcome = try await ResultParser(launcher: launcher).parse(
exitCode: launched.exitCode,
output: launched.output,
xcresultPath: launched.xcresultPath,
timeout: configuration.build.timeout
)

case .spm:
outcome = spmOutcome(exitCode: launched.exitCode, output: launched.output)
}

try? sandbox.cleanup()

Expand All @@ -85,17 +92,25 @@ struct IncompatibleMutantExecutor: Sendable {
sandbox: Sandbox,
configuration: RunnerConfiguration
) async throws -> IncompatibleTestLaunchResult {
let derivedDataPath = sandbox.rootURL
.appendingPathComponent(".derived-data").path
switch configuration.build.projectType {
case .xcode(let scheme, _):
return try await launchXcode(
scheme: scheme, slot: slot, sandbox: sandbox, configuration: configuration)
case .spm:
return try await launchSPM(sandbox: sandbox, configuration: configuration)
}
}

private func launchXcode(
scheme: String,
slot: SimulatorSlot,
sandbox: Sandbox,
configuration: RunnerConfiguration
) async throws -> IncompatibleTestLaunchResult {
let derivedDataPath = sandbox.rootURL.appendingPathComponent(".derived-data").path
let xcresultPath = sandbox.rootURL
.appendingPathComponent("\(UUID().uuidString).xcresult").path

guard case .xcode(let scheme, _) = configuration.build.projectType else {
return IncompatibleTestLaunchResult(
exitCode: 0, output: "", xcresultPath: xcresultPath, duration: 0
)
}

var arguments = [
"test",
"-scheme", scheme,
Expand Down Expand Up @@ -126,6 +141,44 @@ struct IncompatibleMutantExecutor: Sendable {
)
}

private func launchSPM(
sandbox: Sandbox,
configuration: RunnerConfiguration
) async throws -> IncompatibleTestLaunchResult {
var arguments = ["test"]

if let testTarget = configuration.build.testTarget {
arguments += ["--filter", testTarget]
}

let start = Date()
let captured = try await launcher.launchCapturing(
executableURL: URL(fileURLWithPath: "/usr/bin/swift"),
arguments: arguments,
environment: nil,
workingDirectoryURL: sandbox.rootURL,
timeout: configuration.build.timeout
)

return IncompatibleTestLaunchResult(
exitCode: captured.exitCode,
output: captured.output,
xcresultPath: "",
duration: Date().timeIntervalSince(start)
)
}

private func spmOutcome(exitCode: Int32, output: String) -> TestRunOutcome {
if exitCode == -1 { return .timedOut }
if exitCode == 0 { return .testsSucceeded }

switch TestOutputParser().parse(output) {
case .killed(let name): return .testsFailed(failingTest: name)
case .crashed: return .crashed
case .unviable: return .unviable
}
}

private func storeAndReport(
mutant: MutantDescriptor,
key: MutantCacheKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,68 @@ struct IncompatibleMutantExecutorTests {
}
}

@Test("Given SPM project type and exit code 0, when execute called, then mutant survived")
func spmExitCodeZeroProducesSurvivedStatus() async throws {
let dir = try FileHelpers.makeTemporaryDirectory()
defer { FileHelpers.cleanup(dir) }

let executor = makeExecutorSPM(in: dir, exitCode: 0)
let pool = makePool()
try await pool.setUp()

let results = try await executor.execute(
[makeMutant(id: "m0", content: "let x = 1")],
configuration: makeConfigurationSPM(projectPath: dir.path),
pool: pool,
testFilesHash: "hash"
)

#expect(results.first?.status == .survived)
}

@Test("Given SPM project type and exit code 1 with failure output, when execute called, then mutant is killed")
func spmExitCodeOneWithFailureOutputProducesKilledStatus() async throws {
let dir = try FileHelpers.makeTemporaryDirectory()
defer { FileHelpers.cleanup(dir) }

let output = #"Test "myTest" failed after 0.001 seconds."#
let executor = makeExecutorSPM(in: dir, exitCode: 1, output: output)
let pool = makePool()
try await pool.setUp()

let results = try await executor.execute(
[makeMutant(id: "m0", content: "let x = 1")],
configuration: makeConfigurationSPM(projectPath: dir.path),
pool: pool,
testFilesHash: "hash"
)

#expect(results.first?.status == .killed(by: "myTest"))
}

private func makeExecutorSPM(
in dir: URL,
exitCode: Int32,
output: String = ""
) -> IncompatibleMutantExecutor {
IncompatibleMutantExecutor(
launcher: MockProcessLauncher(exitCode: exitCode, output: output),
sandboxFactory: SandboxFactory(),
cacheStore: CacheStore(storePath: dir.appendingPathComponent("cache.json").path),
reporter: MockProgressReporter(),
counter: MutationCounter(total: 1)
)
}

private func makeConfigurationSPM(projectPath: String) -> RunnerConfiguration {
RunnerConfiguration(
projectPath: projectPath,
build: .init(projectType: .spm, timeout: 60, concurrency: 1, noCache: false),
reporting: .init(quiet: true),
filter: .init(excludePatterns: [], operators: [])
)
}

private func makeExecutor(in dir: URL, exitCode: Int32) -> IncompatibleMutantExecutor {
IncompatibleMutantExecutor(
launcher: MockProcessLauncher(exitCode: exitCode),
Expand Down