Compare commits
No commits in common. "91ab96815460c403bbf4c1a22d2e338b2bcf35e9" and "b05c2dc0a260d749f4d84c699d07e6366e80b621" have entirely different histories.
91ab968154
...
b05c2dc0a2
5 changed files with 25 additions and 64 deletions
|
|
@ -6,17 +6,17 @@ open Xunit
|
||||||
|
|
||||||
type CalculatorTests() =
|
type CalculatorTests() =
|
||||||
[<Fact>]
|
[<Fact>]
|
||||||
[<MutationCase("calc-add-one", "Example.Tests/Calculator.fs", 4, "value + 1", "value - 1")>]
|
[<MutationCase("calc-add-one", "example-tests/Example.Tests/Calculator.fs", 4, "value + 1", "value - 1")>]
|
||||||
member _.AddOne_increments() =
|
member _.AddOne_increments() =
|
||||||
Assert.Equal(42, Calculator.addOne 41)
|
Assert.Equal(42, Calculator.addOne 41)
|
||||||
|
|
||||||
[<Fact>]
|
[<Fact>]
|
||||||
[<MutationCase("calc-abs-diff-branch", "Example.Tests/Calculator.fs", 7, "left - right", "right - left")>]
|
[<MutationCase("calc-abs-diff-branch", "example-tests/Example.Tests/Calculator.fs", 7, "left - right", "right - left")>]
|
||||||
member _.AbsoluteDifference_preserves_order() =
|
member _.AbsoluteDifference_preserves_order() =
|
||||||
Assert.Equal(7, Calculator.absoluteDifference 10 3)
|
Assert.Equal(7, Calculator.absoluteDifference 10 3)
|
||||||
|
|
||||||
[<Fact>]
|
[<Fact>]
|
||||||
[<MutationCase("calc-leap-year-century", "Example.Tests/Calculator.fs", 10, "year % 100 <> 0", "year % 100 = 0")>]
|
[<MutationCase("calc-leap-year-century", "example-tests/Example.Tests/Calculator.fs", 10, "year % 100 <> 0", "year % 100 = 0")>]
|
||||||
member _.LeapYear_handles_centuries() =
|
member _.LeapYear_handles_centuries() =
|
||||||
Assert.True(Calculator.isLeapYear 2000)
|
Assert.True(Calculator.isLeapYear 2000)
|
||||||
Assert.False(Calculator.isLeapYear 1900)
|
Assert.False(Calculator.isLeapYear 1900)
|
||||||
83
verify-coverage-mutants.fsx
Executable file → Normal file
83
verify-coverage-mutants.fsx
Executable file → Normal file
|
|
@ -1,4 +1,3 @@
|
||||||
#!/usr/bin/env -S dotnet fsi
|
|
||||||
open System
|
open System
|
||||||
open System.IO
|
open System.IO
|
||||||
open System.Reflection
|
open System.Reflection
|
||||||
|
|
@ -25,14 +24,13 @@ type Command =
|
||||||
|
|
||||||
type Options =
|
type Options =
|
||||||
{ Configuration: string
|
{ Configuration: string
|
||||||
ProjectPath: string
|
|
||||||
BuildArgs: string list
|
|
||||||
NoBuild: bool
|
NoBuild: bool
|
||||||
Command: Command }
|
Command: Command }
|
||||||
|
|
||||||
type ProjectInfo =
|
let projectPath = "Example.Tests"
|
||||||
{ RelativeProjectPath: string
|
let targetFramework = "net10.0"
|
||||||
AbsoluteProjectPath: string }
|
let projectDirectory = "example-tests"
|
||||||
|
let testProjectPath = Path.Combine(projectDirectory, "Example.Tests", "Example.Tests.fsproj")
|
||||||
|
|
||||||
let fail message =
|
let fail message =
|
||||||
eprintfn "%s" message
|
eprintfn "%s" message
|
||||||
|
|
@ -78,67 +76,30 @@ let repoRoot =
|
||||||
if exitCode <> 0 then fail stderr
|
if exitCode <> 0 then fail stderr
|
||||||
stdout.Trim()
|
stdout.Trim()
|
||||||
|
|
||||||
|
let makeRelativePath (path: string) =
|
||||||
|
if Path.IsPathRooted path then Path.GetRelativePath(repoRoot, path) else path
|
||||||
|
|
||||||
let parseArgs (args: string list) =
|
let parseArgs (args: string list) =
|
||||||
let usage () =
|
let rec loop configuration noBuild remaining =
|
||||||
fail "Usage: verify-coverage-mutants.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 =
|
|
||||||
match remaining with
|
match remaining with
|
||||||
| [] -> { Configuration = configuration; ProjectPath = projectPath; BuildArgs = List.rev buildArgs; NoBuild = noBuild; Command = Run [] }
|
| [] -> { Configuration = configuration; NoBuild = noBuild; Command = Run [] }
|
||||||
| "--configuration" :: value :: tail -> loop value projectPath buildArgs noBuild tail
|
| "--configuration" :: value :: tail -> loop value noBuild tail
|
||||||
| "--build-arg" :: value :: tail -> loop configuration projectPath (value :: buildArgs) noBuild tail
|
| "--no-build" :: tail -> loop configuration true tail
|
||||||
| "--no-build" :: tail -> loop configuration projectPath buildArgs true tail
|
| "--list" :: tail when tail.IsEmpty -> { Configuration = configuration; NoBuild = noBuild; Command = List }
|
||||||
| "--list" :: tail when tail.IsEmpty -> { Configuration = configuration; ProjectPath = projectPath; BuildArgs = List.rev buildArgs; NoBuild = noBuild; Command = List }
|
| "--show" :: id :: tail when tail.IsEmpty -> { Configuration = configuration; NoBuild = noBuild; Command = Show id }
|
||||||
| "--show" :: id :: tail when tail.IsEmpty -> { Configuration = configuration; ProjectPath = projectPath; BuildArgs = List.rev buildArgs; NoBuild = noBuild; Command = Show id }
|
| "--run" :: tail -> { Configuration = configuration; NoBuild = noBuild; Command = Run tail }
|
||||||
| "--run" :: tail -> { Configuration = configuration; ProjectPath = projectPath; BuildArgs = List.rev buildArgs; NoBuild = noBuild; Command = Run tail }
|
| value :: tail when not (value.StartsWith "--") -> { Configuration = configuration; NoBuild = noBuild; Command = Run (value :: tail) }
|
||||||
| value :: tail when not (value.StartsWith "--") -> { Configuration = configuration; ProjectPath = projectPath; BuildArgs = List.rev buildArgs; NoBuild = noBuild; Command = Run (value :: tail) }
|
| _ -> fail "Usage: dotnet fsi verify-coverage-mutants.fsx [--configuration Debug|Release] [--no-build] [--list | --show <id> | --run [id...]]"
|
||||||
| _ -> usage ()
|
loop "Debug" false args
|
||||||
|
|
||||||
match args with
|
|
||||||
| projectPath :: tail when not (projectPath.StartsWith "--") -> loop "Debug" projectPath [] false tail
|
|
||||||
| _ -> usage ()
|
|
||||||
|
|
||||||
let options = parseArgs (fsi.CommandLineArgs |> Array.skip 1 |> Array.toList)
|
let options = parseArgs (fsi.CommandLineArgs |> Array.skip 1 |> Array.toList)
|
||||||
|
|
||||||
let ensureWithinRepo (path: string) =
|
let assemblyPath =
|
||||||
let relativePath = Path.GetRelativePath(repoRoot, path)
|
Path.Combine(repoRoot, projectDirectory, "Example.Tests", "bin", options.Configuration, targetFramework, "Example.Tests.dll")
|
||||||
if relativePath = ".." || relativePath.StartsWith($"..{Path.DirectorySeparatorChar}") then
|
|
||||||
fail $"Project path must be inside the repository: {path}"
|
|
||||||
relativePath
|
|
||||||
|
|
||||||
let loadProjectInfo (projectPath: string) =
|
|
||||||
let absoluteProjectPath =
|
|
||||||
if Path.IsPathRooted projectPath then projectPath
|
|
||||||
else Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, projectPath))
|
|
||||||
|
|
||||||
if not (File.Exists absoluteProjectPath) then
|
|
||||||
fail $"Project file not found: {absoluteProjectPath}"
|
|
||||||
|
|
||||||
let relativeProjectPath = ensureWithinRepo absoluteProjectPath
|
|
||||||
{ RelativeProjectPath = relativeProjectPath
|
|
||||||
AbsoluteProjectPath = absoluteProjectPath }
|
|
||||||
|
|
||||||
let project =
|
|
||||||
loadProjectInfo options.ProjectPath
|
|
||||||
|
|
||||||
let targetPathForProject (workingDirectory: string) (projectPath: string) =
|
|
||||||
let exitCode, stdout, stderr =
|
|
||||||
captureProcess workingDirectory "dotnet" [ "msbuild"; projectPath; "--getProperty:TargetPath"; $"-property:Configuration={options.Configuration}" ]
|
|
||||||
if exitCode <> 0 then fail stderr
|
|
||||||
let targetPath = stdout.Trim()
|
|
||||||
if String.IsNullOrWhiteSpace targetPath then
|
|
||||||
fail $"MSBuild did not return a TargetPath for {projectPath}."
|
|
||||||
targetPath
|
|
||||||
|
|
||||||
let assemblyPath = targetPathForProject repoRoot project.AbsoluteProjectPath
|
|
||||||
|
|
||||||
let buildArgs projectPath =
|
|
||||||
[ "build"; projectPath; "--configuration"; options.Configuration; "--nologo" ]
|
|
||||||
@ options.BuildArgs
|
|
||||||
|
|
||||||
let ensureBuilt () =
|
let ensureBuilt () =
|
||||||
if not options.NoBuild then
|
if not options.NoBuild then
|
||||||
let exitCode = runProcess repoRoot "dotnet" (buildArgs project.RelativeProjectPath)
|
let exitCode = runProcess repoRoot "dotnet" [ "build"; testProjectPath; "--configuration"; options.Configuration; "--nologo" ]
|
||||||
if exitCode <> 0 then fail "dotnet build failed."
|
if exitCode <> 0 then fail "dotnet build failed."
|
||||||
if not (File.Exists assemblyPath) then
|
if not (File.Exists assemblyPath) then
|
||||||
fail $"Compiled test assembly not found at {assemblyPath}."
|
fail $"Compiled test assembly not found at {assemblyPath}."
|
||||||
|
|
@ -248,7 +209,7 @@ let runMutation (mutation: MutationCase) =
|
||||||
|
|
||||||
printfn "==> %s: %s" mutation.Id mutation.TestName
|
printfn "==> %s: %s" mutation.Id mutation.TestName
|
||||||
let buildExitCode =
|
let buildExitCode =
|
||||||
runProcess worktreePath "dotnet" (buildArgs project.RelativeProjectPath)
|
runProcess worktreePath "dotnet" [ "build"; testProjectPath; "--configuration"; options.Configuration; "--nologo" ]
|
||||||
|
|
||||||
let outcome =
|
let outcome =
|
||||||
if buildExitCode <> 0 then
|
if buildExitCode <> 0 then
|
||||||
|
|
@ -256,7 +217,7 @@ let runMutation (mutation: MutationCase) =
|
||||||
BuildFailed
|
BuildFailed
|
||||||
else
|
else
|
||||||
let testExitCode =
|
let testExitCode =
|
||||||
runProcess worktreePath "dotnet" [ "test"; project.RelativeProjectPath; "--configuration"; options.Configuration; "--filter"; testFilter mutation; "--no-build"; "--nologo" ]
|
runProcess worktreePath "dotnet" [ "test"; testProjectPath; "--configuration"; options.Configuration; "--filter"; testFilter mutation; "--no-build"; "--nologo" ]
|
||||||
|
|
||||||
if testExitCode = 0 then
|
if testExitCode = 0 then
|
||||||
printfn "SURVIVED %s" mutation.Id
|
printfn "SURVIVED %s" mutation.Id
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue