diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index ececb87..0000000 --- a/.editorconfig +++ /dev/null @@ -1,14 +0,0 @@ -root = true - -[*] -indent_style = space -charset = utf-8 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true - -[*.nix] -indent_size = 2 - -[*.{fs,fsi,fsx}] -indent_size = 4 diff --git a/Example.Tests/CalculatorTests.fs b/Example.Tests/CalculatorTests.fs index 0a96e10..40f9b66 100644 --- a/Example.Tests/CalculatorTests.fs +++ b/Example.Tests/CalculatorTests.fs @@ -6,22 +6,17 @@ open Xunit type CalculatorTests() = [] - [] + member _.AddOne_increments() = + Assert.Equal(42, Calculator.addOne 41) - module Calculator = - - let addOne value = value + 1 - + let addOne value = value - 1 + [] + [] + member _.AbsoluteDifference_preserves_order() = + Assert.Equal(7, Calculator.absoluteDifference 10 3) - let absoluteDifference left right = - if left >= right then left - right else right - left - - member _.AddOne_increments() = - Assert.Equal(42, Calculator.addOne 41) - """)>] - member _.AddOne_increments() = Assert.Equal(42, Calculator.addOne 41) + [] + [ 0", "year % 100 = 0")>] + member _.LeapYear_handles_centuries() = + Assert.True(Calculator.isLeapYear 2000) + Assert.False(Calculator.isLeapYear 1900) diff --git a/Example.Tests/MutationCaseAttribute.fs b/Example.Tests/MutationCaseAttribute.fs index f1175e4..12e6475 100644 --- a/Example.Tests/MutationCaseAttribute.fs +++ b/Example.Tests/MutationCaseAttribute.fs @@ -3,7 +3,11 @@ namespace Mutannot open System [] -type MutationCaseAttribute(patch: string) = +type MutationCaseAttribute(id: string, file: string, line: int, find: string, replace: string) = inherit Attribute() - member _.Patch = patch + member _.Id = id + member _.File = file + member _.Line = line + member _.Find = find + member _.Replace = replace diff --git a/Mutannot/Program.fs b/Mutannot/Program.fs index 13666be..e7195fc 100644 --- a/Mutannot/Program.fs +++ b/Mutannot/Program.fs @@ -1,12 +1,7 @@ -open System -open System.IO -open System.Reflection -open System.Runtime.InteropServices open Fli -type MutationCase = { TestName: string; Patch: string } - -let ensureCleanWorkingDirectory () = +[] +let main argv = let gitState = cli { Exec "git" @@ -17,123 +12,6 @@ let ensureCleanWorkingDirectory () = if gitState.Text <> None then eprintfn "Uncommitted changes. Refusing to run." - exit 2 - -let applyPatch patch = - cli { - Exec "git" - Arguments [ "apply"; "-" ] - Input patch - } - |> Command.execute - |> Output.throwIfErrored - |> ignore - -let restore () = - cli { - Exec "git" - Arguments [ "restore"; "--staged"; "--worktree"; "." ] - } - |> Command.execute - |> Output.throwIfErrored - |> ignore - -let ensureBuilt projectPath = - cli { - Exec "dotnet" - Arguments [ "build"; projectPath ] - Output(new StreamWriter(Console.OpenStandardOutput())) - } - |> Command.execute - |> Output.throwIfErrored - |> ignore - -let runTest projectPath testName = - cli { - Exec "dotnet" - Arguments [ "test"; projectPath; "--filter"; $"FullyQualifiedName={testName}" ] - Output(new StreamWriter(Console.OpenStandardOutput())) - } - |> Command.execute - |> ignore - -let getAssemblyPath projectPath = - cli { - Exec "dotnet" - Arguments [ "msbuild"; projectPath; "--getProperty:TargetPath" ] - } - |> Command.execute - |> Output.toText - -let getMetadataLoadContext (assemblyPath: string) = - // This allows us to inspect assemblies regardless of the platform that they were built for - // https://learn.microsoft.com/en-us/dotnet/standard/assembly/inspect-contents-using-metadataloadcontext - let assemblyDir = Path.GetDirectoryName assemblyPath - - let pathAssemblyResolver = - [ yield assemblyPath - yield! Directory.EnumerateFiles(assemblyDir, "*.dll") - yield! Directory.EnumerateFiles(assemblyDir, "*.exe") - yield! Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll") ] - |> PathAssemblyResolver - - new MetadataLoadContext(pathAssemblyResolver, typeof.Assembly.GetName().Name) - -let unindented (s: string) = - let lines = s.Split([| "\r\n"; "\n" |], StringSplitOptions.None) - - let indexOfFirstNonEmptyLine = - lines |> Array.findIndex (not << String.IsNullOrWhiteSpace) - - let identantionOfFirstNonEmptyLine = - lines[indexOfFirstNonEmptyLine] |> Seq.takeWhile Char.IsWhiteSpace |> Seq.length - - lines[indexOfFirstNonEmptyLine..] - |> Seq.map (fun line -> line.Substring(min identantionOfFirstNonEmptyLine line.Length)) - |> String.concat Environment.NewLine - -let getMutationCases projectPath = - ensureBuilt projectPath - - let assemblyPath = getAssemblyPath projectPath - - use metadataLoadContext = getMetadataLoadContext assemblyPath - - let assemblyTypes = - assemblyPath |> metadataLoadContext.LoadFromAssemblyPath |> _.GetTypes() - - let assemblyMethods = - assemblyTypes - |> Seq.collect _.GetMethods(BindingFlags.Public ||| BindingFlags.Instance) - - assemblyMethods - |> Seq.collect (fun m -> - m.GetCustomAttributesData() - |> Seq.choose (fun attr -> - match attr.AttributeType.FullName with - | "Mutannot.MutationCaseAttribute" -> - Some - { TestName = $"{m.DeclaringType.FullName}.{m.Name}" - Patch = attr.ConstructorArguments[0].Value :?> string |> unindented } - | _ -> None)) - |> Seq.toList - -[] -let main argv = - if argv.Length <> 1 then - eprintfn "Usage: mutannot " exit 1 - ensureCleanWorkingDirectory () - - AppDomain.CurrentDomain.ProcessExit.Add(fun _ -> restore ()) - - let projectPath = argv[0] - - for mutationCase in getMutationCases projectPath do - printfn "MUTATION\n\n%s" <| mutationCase.Patch - applyPatch mutationCase.Patch - runTest projectPath mutationCase.TestName - restore () - 0 diff --git a/flake.lock b/flake.lock index 8244c7c..6908e25 100644 --- a/flake.lock +++ b/flake.lock @@ -1,47 +1,5 @@ { "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "git-temp-commit": { - "inputs": { - "flake-utils": [ - "flake-utils" - ], - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1778481212, - "narHash": "sha256-Vy0ufQ51CHkamX+XB8hhgohBHJesKli0jF503NuSY20=", - "ref": "main", - "rev": "622b553f46920a2f3cc92f26c1f49cabb612de5f", - "revCount": 2, - "type": "git", - "url": "https://codeberg.org/svenvanheugten/git-temp-commit.git" - }, - "original": { - "ref": "main", - "type": "git", - "url": "https://codeberg.org/svenvanheugten/git-temp-commit.git" - } - }, "nixpkgs": { "locked": { "lastModified": 1776877367, @@ -60,25 +18,8 @@ }, "root": { "inputs": { - "flake-utils": "flake-utils", - "git-temp-commit": "git-temp-commit", "nixpkgs": "nixpkgs" } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index f3fe06f..16f31af 100644 --- a/flake.nix +++ b/flake.nix @@ -1,50 +1,57 @@ { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - flake-utils.url = "github:numtide/flake-utils"; - git-temp-commit = { - url = "git+https://codeberg.org/svenvanheugten/git-temp-commit.git?ref=main"; - inputs.nixpkgs.follows = "nixpkgs"; - inputs.flake-utils.follows = "flake-utils"; - }; }; outputs = + { self, nixpkgs }: + let + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + forEachSystem = + f: nixpkgs.lib.genAttrs systems (system: f system (import nixpkgs { inherit system; })); + in { - self, - nixpkgs, - flake-utils, - git-temp-commit, - }: - flake-utils.lib.eachDefaultSystem ( - system: - let - pkgs = nixpkgs.legacyPackages.${system}; - in - { - packages.default = pkgs.buildDotnetModule { - pname = "mutannot"; - version = "0.1.0"; - src = ./Mutannot; - projectFile = "Mutannot.fsproj"; - nugetDeps = ./Mutannot/deps.nix; - executables = [ "mutannot" ]; - dotnet-sdk = pkgs.dotnet-sdk_10; - dotnet-runtime = pkgs.dotnet-sdk_10; - useDotnetFromEnv = true; + formatter = forEachSystem (_system: pkgs: pkgs.nixfmt-rfc-style); - meta = { - mainProgram = "mutannot"; + packages = forEachSystem ( + _system: pkgs: + let + mutannot = pkgs.buildDotnetModule { + pname = "mutannot"; + version = "0.1.0"; + src = ./Mutannot; + projectFile = "Mutannot.fsproj"; + nugetDeps = ./Mutannot/deps.nix; + executables = [ "mutannot" ]; + dotnet-sdk = pkgs.dotnet-sdk_10; + dotnet-runtime = pkgs.dotnet-sdk_10; + useDotnetFromEnv = true; + + meta = { + mainProgram = "mutannot"; + }; }; - }; + in + { + default = mutannot; + mutannot = mutannot; + } + ); - devShells.default = pkgs.mkShell { - packages = [ - pkgs.git - pkgs.dotnet-sdk_10 - git-temp-commit.packages.${system}.default - ]; - }; - } - ); + devShells = forEachSystem ( + _system: pkgs: { + default = pkgs.mkShell { + packages = [ + pkgs.git + pkgs.dotnet-sdk_10 + ]; + }; + } + ); + }; }