Compare commits
2 commits
4349310cab
...
10a460d2c3
| Author | SHA1 | Date | |
|---|---|---|---|
| 10a460d2c3 | |||
| ae04034028 |
2 changed files with 35 additions and 22 deletions
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
This allows you to annotate Xunit test cases with a mutation that should cause the test to fail.
|
This allows you to annotate Xunit test cases with a mutation that should cause the test to fail.
|
||||||
|
|
||||||
`verify-coverage-mutants.fsx` will apply each mutation and verify that the test actually fails.
|
`mutannot.fsx` will apply each mutation and verify that the test actually fails.
|
||||||
|
|
||||||
Current state: LLM-generated prototype
|
Current state: LLM-generated prototype
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue