diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..33afe2c --- /dev/null +++ b/.gitignore @@ -0,0 +1,213 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# Build results +[Bb]in/ +[Oo]bj/ +[Ee]xt/ +[Ll]og/ +[Ll]ogs/ + +# MSBuild Binary and Structured Log +*.binlog + +# MSBuild response files +!MSBuild.rsp +!Directory.Build.rsp + +# Visual Studio 14+ cache/options directory +.vs/ + +# Visual Studio 15+ auto generated files +Generated\ Files/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*~.* +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# Microsoft Fakes +FakesAssemblies/ + +# CodeRush personal settings +.cr/personal + +# JetBrains Rider +*.sln.iml + +# Codex +.codex diff --git a/MutationCase.fs b/MutationCase.fs index 33891ef..12e6475 100644 --- a/MutationCase.fs +++ b/MutationCase.fs @@ -1,4 +1,4 @@ -namespace Mutation +namespace Mutannot open System diff --git a/example-tests/Example.Tests/Calculator.fs b/example-tests/Example.Tests/Calculator.fs new file mode 100644 index 0000000..cfcce3b --- /dev/null +++ b/example-tests/Example.Tests/Calculator.fs @@ -0,0 +1,10 @@ +namespace Example + +module Calculator = + let addOne value = value + 1 + + let absoluteDifference left right = + if left >= right then left - right else right - left + + let isLeapYear year = + year % 4 = 0 && (year % 100 <> 0 || year % 400 = 0) diff --git a/example-tests/Example.Tests/CalculatorTests.fs b/example-tests/Example.Tests/CalculatorTests.fs new file mode 100644 index 0000000..f05ee54 --- /dev/null +++ b/example-tests/Example.Tests/CalculatorTests.fs @@ -0,0 +1,22 @@ +namespace Example.Tests + +open Example +open Mutannot +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")>] + member _.LeapYear_handles_centuries() = + Assert.True(Calculator.isLeapYear 2000) + Assert.False(Calculator.isLeapYear 1900) diff --git a/example-tests/Example.Tests/Example.Tests.fsproj b/example-tests/Example.Tests/Example.Tests.fsproj new file mode 100644 index 0000000..c8c7dce --- /dev/null +++ b/example-tests/Example.Tests/Example.Tests.fsproj @@ -0,0 +1,21 @@ + + + net10.0 + false + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/example-tests/Example.Tests/MutationCaseAttribute.fs b/example-tests/Example.Tests/MutationCaseAttribute.fs new file mode 100644 index 0000000..12e6475 --- /dev/null +++ b/example-tests/Example.Tests/MutationCaseAttribute.fs @@ -0,0 +1,13 @@ +namespace Mutannot + +open System + +[] +type MutationCaseAttribute(id: string, file: string, line: int, find: string, replace: string) = + inherit Attribute() + + member _.Id = id + member _.File = file + member _.Line = line + member _.Find = find + member _.Replace = replace diff --git a/verify-coverage-mutants.fsx b/verify-coverage-mutants.fsx index 25c43b4..ed5f988 100644 --- a/verify-coverage-mutants.fsx +++ b/verify-coverage-mutants.fsx @@ -12,6 +12,11 @@ type MutationCase = TestName: string DeclaringType: string } +type MutationOutcome = + | Killed + | Survived + | BuildFailed + type Command = | List | Show of string @@ -115,7 +120,7 @@ let mutationCases () = t.GetMethods(BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Static ||| BindingFlags.Instance) |> Array.collect (fun m -> m.GetCustomAttributesData() - |> Seq.filter (fun attr -> attr.AttributeType.FullName = "FsCheck.Test.MutationCaseAttribute") + |> Seq.filter (fun attr -> attr.AttributeType.FullName = "Mutannot.MutationCaseAttribute") |> Seq.map (fun attr -> let args = attr.ConstructorArguments if args.Count <> 5 then failwithf "Unexpected MutationCaseAttribute shape on %s.%s" t.FullName m.Name @@ -203,15 +208,26 @@ let runMutation (mutation: MutationCase) = File.WriteAllText(targetFile, mutatedText) printfn "==> %s: %s" mutation.Id mutation.TestName - let exitCode = runProcess worktreePath "dotnet" [ "test"; testProjectPath; "--configuration"; options.Configuration; "--filter"; testFilter mutation; "--nologo" ] - File.WriteAllText(targetFile, originalText) + let buildExitCode = + runProcess worktreePath "dotnet" [ "build"; testProjectPath; "--configuration"; options.Configuration; "--nologo" ] - if exitCode = 0 then - printfn "SURVIVED %s" mutation.Id - false - else - printfn "KILLED %s" mutation.Id - true + let outcome = + if buildExitCode <> 0 then + printfn "BUILD FAILED %s" mutation.Id + BuildFailed + else + let testExitCode = + runProcess worktreePath "dotnet" [ "test"; testProjectPath; "--configuration"; options.Configuration; "--filter"; testFilter mutation; "--no-build"; "--nologo" ] + + if testExitCode = 0 then + printfn "SURVIVED %s" mutation.Id + Survived + else + printfn "KILLED %s" mutation.Id + Killed + + File.WriteAllText(targetFile, originalText) + outcome let mutations = mutationCases () @@ -234,13 +250,14 @@ match options.Command with try createWorktree () - let killed = + let outcomes = requested |> List.map runMutation - let survivors = killed |> List.filter not |> List.length - if survivors = 0 then + let survivors = outcomes |> List.filter ((=) Survived) |> List.length + let buildFailures = outcomes |> List.filter ((=) BuildFailed) |> List.length + if survivors = 0 && buildFailures = 0 then printfn "All requested mutants were killed." else - fail $"{survivors} mutant(s) survived." + fail $"{survivors} mutant(s) survived. {buildFailures} mutant(s) failed to build." finally cleanup ()