Compare commits

...

2 commits

View file

@ -80,7 +80,7 @@ let repoRoot =
let parseArgs (args: string list) = let parseArgs (args: string list) =
let usage () = let usage () =
fail "Usage: verify-coverage-mutants.fsx <path/to/project.fsproj> [--configuration Debug|Release] [--build-arg <value> ...] [--no-build] [--list | --show <id> | --run [id...]]" fail "Usage: mutannot.fsx <path/to/project.fsproj> [--configuration Debug|Release] [--build-arg <value> ...] [--no-build] [--list | --show <id> | --run [id...]]"
let rec loop configuration projectPath buildArgs noBuild remaining = let rec loop configuration projectPath buildArgs noBuild remaining =
match remaining with match remaining with
@ -178,27 +178,40 @@ let findMutation id mutations =
|> Array.tryFind (fun mutation -> mutation.Id = id) |> Array.tryFind (fun mutation -> mutation.Id = id)
|> Option.defaultWith (fun () -> fail $"Unknown mutation id: {id}") |> Option.defaultWith (fun () -> fail $"Unknown mutation id: {id}")
let lineNumberAt (text: string) index = let lineSpan (filePath: string) (text: string) (lineNumber: int) =
let mutable line = 1 if lineNumber < 1 then
for i in 0 .. index - 1 do fail $"Line number must be positive: {lineNumber}"
if text[i] = '\n' then
line <- line + 1
line
let replaceNearestOccurrence (mutation: MutationCase) (text: string) = let mutable currentLine = 1
let rec collect fromIndex acc = let mutable lineStart = 0
let idx = text.IndexOf(mutation.Find, fromIndex, StringComparison.Ordinal) let mutable index = 0
if idx < 0 then List.rev acc
else collect (idx + 1) (idx :: acc)
let matches = collect 0 [] while index < text.Length && currentLine < lineNumber do
match matches with if text[index] = '\n' then
| [] -> fail $"Could not find '{mutation.Find}' in {mutation.File}" currentLine <- currentLine + 1
| _ -> lineStart <- index + 1
let chosen =
matches index <- index + 1
|> List.minBy (fun idx -> abs (lineNumberAt text idx - mutation.Line))
text.Remove(chosen, mutation.Find.Length).Insert(chosen, mutation.Replace) if currentLine <> lineNumber then
fail $"Line {lineNumber} does not exist in {filePath}"
let mutable lineEnd = lineStart
while lineEnd < text.Length && text[lineEnd] <> '\n' && text[lineEnd] <> '\r' do
lineEnd <- lineEnd + 1
lineStart, lineEnd
let replaceOccurrenceOnLine (mutation: MutationCase) (text: string) =
let lineStart, lineEnd = lineSpan mutation.File text mutation.Line
let lineText = text.Substring(lineStart, lineEnd - lineStart)
let lineOffset = lineText.IndexOf(mutation.Find, StringComparison.Ordinal)
if lineOffset < 0 then
fail $"Line {mutation.Line} in {mutation.File} does not contain '{mutation.Find}'."
let absoluteOffset = lineStart + lineOffset
text.Remove(absoluteOffset, mutation.Find.Length).Insert(absoluteOffset, mutation.Replace)
let printMutation mutation = let printMutation mutation =
printfn "id: %s" mutation.Id printfn "id: %s" mutation.Id
@ -243,7 +256,7 @@ let runMutation (mutation: MutationCase) =
fail $"Target file does not exist in worktree: {targetFile}" fail $"Target file does not exist in worktree: {targetFile}"
let originalText = File.ReadAllText targetFile let originalText = File.ReadAllText targetFile
let mutatedText = replaceNearestOccurrence mutation originalText let mutatedText = replaceOccurrenceOnLine mutation originalText
File.WriteAllText(targetFile, mutatedText) File.WriteAllText(targetFile, mutatedText)
printfn "==> %s: %s" mutation.Id mutation.TestName printfn "==> %s: %s" mutation.Id mutation.TestName