diff --git a/Example.Tests/Calculator.fs b/example-tests/Example.Tests/Calculator.fs similarity index 100% rename from Example.Tests/Calculator.fs rename to example-tests/Example.Tests/Calculator.fs diff --git a/Example.Tests/CalculatorTests.fs b/example-tests/Example.Tests/CalculatorTests.fs similarity index 55% rename from Example.Tests/CalculatorTests.fs rename to example-tests/Example.Tests/CalculatorTests.fs index 40f9b66..f05ee54 100644 --- a/Example.Tests/CalculatorTests.fs +++ b/example-tests/Example.Tests/CalculatorTests.fs @@ -6,17 +6,17 @@ open Xunit type CalculatorTests() = [] - [] + [] member _.AddOne_increments() = Assert.Equal(42, Calculator.addOne 41) [] - [] + [] member _.AbsoluteDifference_preserves_order() = Assert.Equal(7, Calculator.absoluteDifference 10 3) [] - [ 0", "year % 100 = 0")>] + [ 0", "year % 100 = 0")>] member _.LeapYear_handles_centuries() = Assert.True(Calculator.isLeapYear 2000) Assert.False(Calculator.isLeapYear 1900) diff --git a/Example.Tests/Example.Tests.fsproj b/example-tests/Example.Tests/Example.Tests.fsproj similarity index 100% rename from Example.Tests/Example.Tests.fsproj rename to example-tests/Example.Tests/Example.Tests.fsproj diff --git a/Example.Tests/MutationCaseAttribute.fs b/example-tests/Example.Tests/MutationCaseAttribute.fs similarity index 100% rename from Example.Tests/MutationCaseAttribute.fs rename to example-tests/Example.Tests/MutationCaseAttribute.fs diff --git a/verify-coverage-mutants.fsx b/verify-coverage-mutants.fsx old mode 100755 new mode 100644 index 0c68461..ed5f988 --- a/verify-coverage-mutants.fsx +++ b/verify-coverage-mutants.fsx @@ -1,4 +1,3 @@ -#!/usr/bin/env -S dotnet fsi open System open System.IO open System.Reflection @@ -25,14 +24,13 @@ type Command = type Options = { Configuration: string - ProjectPath: string - BuildArgs: string list NoBuild: bool Command: Command } -type ProjectInfo = - { RelativeProjectPath: string - AbsoluteProjectPath: string } +let projectPath = "Example.Tests" +let targetFramework = "net10.0" +let projectDirectory = "example-tests" +let testProjectPath = Path.Combine(projectDirectory, "Example.Tests", "Example.Tests.fsproj") let fail message = eprintfn "%s" message @@ -78,67 +76,30 @@ let repoRoot = if exitCode <> 0 then fail stderr stdout.Trim() +let makeRelativePath (path: string) = + if Path.IsPathRooted path then Path.GetRelativePath(repoRoot, path) else path + let parseArgs (args: string list) = - let usage () = - fail "Usage: verify-coverage-mutants.fsx [--configuration Debug|Release] [--build-arg ...] [--no-build] [--list | --show | --run [id...]]" - - let rec loop configuration projectPath buildArgs noBuild remaining = + let rec loop configuration noBuild remaining = match remaining with - | [] -> { Configuration = configuration; ProjectPath = projectPath; BuildArgs = List.rev buildArgs; NoBuild = noBuild; Command = Run [] } - | "--configuration" :: value :: tail -> loop value projectPath buildArgs noBuild tail - | "--build-arg" :: value :: tail -> loop configuration projectPath (value :: buildArgs) noBuild tail - | "--no-build" :: tail -> loop configuration projectPath buildArgs true tail - | "--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; ProjectPath = projectPath; BuildArgs = List.rev buildArgs; NoBuild = noBuild; Command = Show id } - | "--run" :: tail -> { Configuration = configuration; ProjectPath = projectPath; BuildArgs = List.rev buildArgs; NoBuild = noBuild; Command = Run tail } - | value :: tail when not (value.StartsWith "--") -> { Configuration = configuration; ProjectPath = projectPath; BuildArgs = List.rev buildArgs; NoBuild = noBuild; Command = Run (value :: tail) } - | _ -> usage () - - match args with - | projectPath :: tail when not (projectPath.StartsWith "--") -> loop "Debug" projectPath [] false tail - | _ -> usage () + | [] -> { Configuration = configuration; NoBuild = noBuild; Command = Run [] } + | "--configuration" :: value :: tail -> loop value noBuild tail + | "--no-build" :: tail -> loop configuration true tail + | "--list" :: tail when tail.IsEmpty -> { Configuration = configuration; NoBuild = noBuild; Command = List } + | "--show" :: id :: tail when tail.IsEmpty -> { Configuration = configuration; NoBuild = noBuild; Command = Show id } + | "--run" :: tail -> { Configuration = configuration; NoBuild = noBuild; Command = Run tail } + | value :: tail when not (value.StartsWith "--") -> { Configuration = configuration; NoBuild = noBuild; Command = Run (value :: tail) } + | _ -> fail "Usage: dotnet fsi verify-coverage-mutants.fsx [--configuration Debug|Release] [--no-build] [--list | --show | --run [id...]]" + loop "Debug" false args let options = parseArgs (fsi.CommandLineArgs |> Array.skip 1 |> Array.toList) -let ensureWithinRepo (path: string) = - let relativePath = Path.GetRelativePath(repoRoot, path) - 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 assemblyPath = + Path.Combine(repoRoot, projectDirectory, "Example.Tests", "bin", options.Configuration, targetFramework, "Example.Tests.dll") let ensureBuilt () = 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 not (File.Exists assemblyPath) then fail $"Compiled test assembly not found at {assemblyPath}." @@ -248,7 +209,7 @@ let runMutation (mutation: MutationCase) = printfn "==> %s: %s" mutation.Id mutation.TestName let buildExitCode = - runProcess worktreePath "dotnet" (buildArgs project.RelativeProjectPath) + runProcess worktreePath "dotnet" [ "build"; testProjectPath; "--configuration"; options.Configuration; "--nologo" ] let outcome = if buildExitCode <> 0 then @@ -256,7 +217,7 @@ let runMutation (mutation: MutationCase) = BuildFailed else 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 printfn "SURVIVED %s" mutation.Id