///|
pub(all) struct MutationExecution {
mutation : ProjectMutation
edit : TextEdit
restore_edit : TextEdit
commands : Array[RunnerCommand]
} derive(Debug, Eq, ToJson)
///|
pub(all) struct ExecutionPlan {
config : MutestConfig
project : ProjectMutationPlan
executions : Array[MutationExecution]
truncated : Bool
} derive(Debug, Eq, ToJson)
///|
pub fn build_execution_plan(
files : ArrayView[SourceFile],
config : MutestConfig,
) -> ExecutionPlan {
build_execution_plan_with_profile(files, config, BasicProfile)
}
///|
pub fn build_execution_plan_with_profile(
files : ArrayView[SourceFile],
config : MutestConfig,
profile : RuleProfile,
) -> ExecutionPlan {
let project = plan_project_with_rules(
files,
config.to_filter(),
rules_for_profile(profile),
)
let executions : Array[MutationExecution] = []
let limit = config.max_mutants
for mutation in project.mutations {
if within_execution_limit(executions.length(), limit) {
let edit = edit_for_project_mutation(mutation)
executions.push({
mutation,
edit,
restore_edit: invert_edit(edit),
commands: config.commands,
})
}
}
{
config,
project,
executions,
truncated: match limit {
Some(max) => project.mutations.length() > max
None => false
},
}
}
///|
pub fn plan_project_with_rules(
files : ArrayView[SourceFile],
filter : MutationFilter,
rules : ArrayView[MutationRule],
) -> ProjectMutationPlan {
let file_plans : Array[FileMutationPlan] = []
let mutations : Array[ProjectMutation] = []
let mut next_id = 0
for file_index, source in files {
let raw_candidates = discover_with_rules(
source.content,
rules,
file=source.path,
)
let candidates = filter_candidates(raw_candidates, filter)
let start_id = next_id
for candidate in candidates {
mutations.push({
global_id: next_id,
file_index,
file: source.path,
local_id: candidate.id,
candidate,
mutated_source: apply_mutation(source.content, candidate),
})
next_id += 1
}
file_plans.push({
file: source.path,
file_index,
start_id,
end_id: next_id,
summary: summarize_candidates(source.path, candidates),
candidates,
})
}
{
file_count: files.length(),
mutation_count: mutations.length(),
files: file_plans,
mutations,
}
}
///|
pub fn format_execution_plan(plan : ExecutionPlan) -> String {
let lines : Array[String] = [
"Mutation execution plan",
"files: \{plan.project.file_count}",
"mutations: \{plan.project.mutation_count}",
"executions: \{plan.executions.length()}",
"truncated: \{plan.truncated}",
"commands:",
]
for command in plan.config.commands {
lines.push("- " + format_runner_command(command))
}
lines.push("")
lines.push("mutants:")
for execution in plan.executions {
let mutation = execution.mutation
lines.push(
"- #\{mutation.global_id} \{mutation.file}:" +
"\{mutation.candidate.span.line} \{mutation.candidate.rule.label}",
)
}
lines.join("\n")
}
///|
pub fn execution_by_mutation_id(
plan : ExecutionPlan,
global_id : Int,
) -> MutationExecution? {
for execution in plan.executions {
if execution.mutation.global_id == global_id {
break Some(execution)
}
} nobreak {
None
}
}
///|
fn within_execution_limit(count : Int, limit : Int?) -> Bool {
match limit {
Some(max) => count < max
None => true
}
}