diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 79a4131..3233b22 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,25 +64,6 @@ jobs: ./Artifacts/* ./TestResults/*.trx - mutation-tests: - name: "Mutation tests" - runs-on: ubuntu-latest - env: - STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - DOTNET_NOLOGO: true - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - name: Setup .NET SDKs - uses: actions/setup-dotnet@v5 - with: - dotnet-version: | - 8.0.x - 10.0.x - - name: Run mutation tests - run: ./build.sh MutationTests MutationTestDashboard - benchmarks: name: "Benchmarks (${{ matrix.benchmark }})" strategy: @@ -229,7 +210,7 @@ jobs: if: ${{ startsWith(github.ref, 'refs/tags/v') }} runs-on: macos-latest environment: production - needs: [ pack, mutation-tests, publish-test-results, static-code-analysis, benchmarks ] + needs: [ pack, publish-test-results, static-code-analysis, benchmarks ] permissions: contents: write id-token: write diff --git a/.github/workflows/ci-analysis.yml b/.github/workflows/ci-analysis.yml index bc3221a..2664fee 100644 --- a/.github/workflows/ci-analysis.yml +++ b/.github/workflows/ci-analysis.yml @@ -8,29 +8,6 @@ on: - completed jobs: - mutation-tests: - name: "Mutation tests" - runs-on: ubuntu-latest - permissions: - pull-requests: write - env: - STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - name: Setup .NET SDKs - uses: actions/setup-dotnet@v5 - with: - dotnet-version: | - 8.0.x - 10.0.x - - name: Upload mutation dashboard and create comment - run: ./build.sh MutationTestDashboard - env: - GithubToken: ${{ secrets.GITHUB_TOKEN }} - WorkflowRunId: ${{ github.event.workflow_run.id }} - benchmarks: name: "Benchmarks" runs-on: ubuntu-latest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e35d15f..7048cd8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,31 +63,6 @@ jobs: ./Artifacts/* ./TestResults/*.trx - mutation-tests: - name: "Mutation tests" - runs-on: ubuntu-latest - env: - DOTNET_NOLOGO: true - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - name: Setup .NET SDKs - uses: actions/setup-dotnet@v5 - with: - dotnet-version: | - 8.0.x - 10.0.x - - name: Run mutation tests - run: ./build.sh MutationTests - - name: Upload artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: MutationTests - path: | - ./Artifacts/* - benchmarks: name: "Benchmarks (${{ matrix.benchmark }})" strategy: diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index ba72bd1..0d9a32b 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -38,10 +38,6 @@ "CodeCoverage", "Compile", "DotNetUnitTests", - "MutationComment", - "MutationTestDashboard", - "MutationTestExecution", - "MutationTests", "Pack", "PublishBenchmarkReport", "Restore", diff --git a/Pipeline/Build.MutationTests.cs b/Pipeline/Build.MutationTests.cs deleted file mode 100644 index f5805f4..0000000 --- a/Pipeline/Build.MutationTests.cs +++ /dev/null @@ -1,245 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using Nuke.Common; -using Nuke.Common.IO; -using Nuke.Common.Tooling; -using Nuke.Common.Tools.DotNet; -using Octokit; -using Serilog; -using static Nuke.Common.Tools.DotNet.DotNetTasks; -using ProductHeaderValue = Octokit.ProductHeaderValue; -using Project = Nuke.Common.ProjectModel.Project; - -// ReSharper disable UnusedMember.Local -// ReSharper disable AllUnderscoreLocalParameterName - -namespace Build; - -partial class Build -{ - static string MutationCommentBody = ""; - - Target MutationTests => _ => _ - .DependsOn(MutationTestExecution) - .DependsOn(MutationComment); - - Target MutationTestExecution => _ => _ - .DependsOn(Compile) - .Executes(() => - { - AbsolutePath toolPath = TestResultsDirectory / "dotnet-stryker"; - AbsolutePath configFile = toolPath / "Stryker.Config.json"; - AbsolutePath strykerOutputDirectory = ArtifactsDirectory / "Stryker"; - strykerOutputDirectory.CreateOrCleanDirectory(); - toolPath.CreateOrCleanDirectory(); - - DotNetToolInstall(_ => _ - .SetPackageName("dotnet-stryker") - .SetToolInstallationPath(toolPath)); - - Dictionary projects = new() - { - { - Solution.Awaiten, [Solution.Tests.Awaiten_Tests,] - }, - }; - - foreach (KeyValuePair project in projects) - { - string branchName = BranchName; - if (GitHubActions?.Ref.StartsWith("refs/tags/", StringComparison.OrdinalIgnoreCase) == true) - { - string version = GitHubActions.Ref.Substring("refs/tags/".Length); - branchName = "release/" + version; - Log.Information("Use release branch analysis for '{BranchName}'", branchName); - } - File.WriteAllText(ArtifactsDirectory / "BranchName.txt", branchName); - - // coverage-analysis is "off" (not the "perTest" default) because C# 14 extension member - // blocks (e.g. AwaitenResolverExtensions) lower into [CompilerGenerated] methods that - // per-test coverage cannot attribute, so their mutants were wrongly scored as uncovered. - // Running every test per mutant restores a real score. - string configText = $$""" - { - "stryker-config": { - "project-info": { - "name": "github.com/{{BuildExtensions.Owner}}/{{BuildExtensions.Repo}}", - "module": "{{project.Key.Name}}", - "version": "{{branchName}}" - }, - "test-projects": [ - {{string.Join(",\n\t\t\t", project.Value.Select(PathForJson))}} - ], - "project": {{PathForJson(project.Key)}}, - "target-framework": "net8.0", - "coverage-analysis": "off", - "since": { - "target": "main", - "enabled": {{(BranchName != "main").ToString().ToLowerInvariant()}}, - "ignore-changes-in": [ - "**/.github/**/*.*" - ] - }, - "mutation-level": "Advanced" - } - } - """; - File.WriteAllText(configFile, configText); - Log.Debug($"Created '{configFile}':{Environment.NewLine}{configText}"); - - string arguments = - $"-f \"{configFile}\" -O \"{strykerOutputDirectory}\" -r \"Markdown\" -r \"cleartext\" -r \"json\""; - - string executable = EnvironmentInfo.IsWin ? "dotnet-stryker.exe" : "dotnet-stryker"; - IProcess process = ProcessTasks.StartProcess( - Path.Combine(toolPath, executable), - arguments, - Solution.Directory) - .AssertWaitForExit(); - if (process.ExitCode != 0) - { - Assert.Fail( - $"Stryker did not execute successfully for {project.Key.Name}: (exit code {process.ExitCode})."); - } - - MutationCommentBody += Environment.NewLine + CreateMutationCommentBody(project.Key.Name); - } - }); - - Target MutationComment => _ => _ - .After(MutationTestExecution) - .OnlyWhenDynamic(() => GitHubActions.IsPullRequest) - .Executes(() => - { - int? prId = GitHubActions.PullRequestNumber; - Log.Debug("Pull request number: {PullRequestId}", prId); - if (string.IsNullOrWhiteSpace(MutationCommentBody)) - { - return; - } - - string body = "## :alien: Mutation Results" - + Environment.NewLine - + $"[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2F{BuildExtensions.Owner}%2F{BuildExtensions.Repo}%2Fpull/{prId}/merge)](https://dashboard.stryker-mutator.io/reports/github.com/{BuildExtensions.Owner}/{BuildExtensions.Repo}/pull/{prId}/merge)" - + Environment.NewLine - + MutationCommentBody; - File.WriteAllText(ArtifactsDirectory / "PR_Comment.md", body); - - if (prId != null) - { File.WriteAllText(ArtifactsDirectory / "PR.txt", prId.Value.ToString()); - } - }); - - Target MutationTestDashboard => _ => _ - .After(MutationTestExecution) - .Executes(async () => - { - await "MutationTests".DownloadArtifactTo(ArtifactsDirectory, GithubToken); - - Dictionary projects = new() - { - { - Solution.Awaiten, [Solution.Tests.Awaiten_Tests,] - }, - }; - string apiKey = Environment.GetEnvironmentVariable("STRYKER_DASHBOARD_API_KEY"); - string branchName = File.ReadAllText(ArtifactsDirectory / "BranchName.txt"); - foreach (KeyValuePair project in projects) - { - string reportComment = - File.ReadAllText(ArtifactsDirectory / "Stryker" / "reports" / "mutation-report.json"); - using HttpClient client = new(); - client.DefaultRequestHeaders.Add("X-Api-Key", apiKey); - // https://stryker-mutator.io/docs/General/dashboard/#send-a-report-via-curl - await client.PutAsync( - $"https://dashboard.stryker-mutator.io/api/reports/github.com/{BuildExtensions.Owner}/{BuildExtensions.Repo}/{branchName}?module={project.Key.Name}", - new StringContent(reportComment, new MediaTypeHeaderValue("application/json"))); - } - - if (File.Exists(ArtifactsDirectory / "PR.txt")) - { - string prNumber = File.ReadAllText(ArtifactsDirectory / "PR.txt"); - Log.Debug("Pull request number: {PullRequestId}", prNumber); - string body = File.ReadAllText(ArtifactsDirectory / "PR_Comment.md"); - if (int.TryParse(prNumber, out int prId)) - { - GitHubClient gitHubClient = new(new ProductHeaderValue("Nuke")); - Credentials tokenAuth = new(GithubToken); - gitHubClient.Credentials = tokenAuth; - IReadOnlyList comments = - await gitHubClient.Issue.Comment.GetAllForIssue(BuildExtensions.Owner, BuildExtensions.Repo, prId); - long? commentId = null; - Log.Information($"Found {comments.Count} comments"); - foreach (IssueComment comment in comments) - { - if (comment.Body.Contains("## :alien: Mutation Results")) - { - Log.Information($"Found comment: {comment.Body}"); - commentId = comment.Id; - } - } - - if (commentId == null) - { - Log.Information($"Create comment:\n{body}"); - await gitHubClient.Issue.Comment.Create(BuildExtensions.Owner, BuildExtensions.Repo, prId, body); - } - else - { - Log.Information($"Update comment:\n{body}"); - await gitHubClient.Issue.Comment.Update(BuildExtensions.Owner, BuildExtensions.Repo, commentId.Value, body); - } - } - } - }); - - string CreateMutationCommentBody(string projectName) - { - string[] fileContent = File.ReadAllLines(ArtifactsDirectory / "Stryker" / "reports" / "mutation-report.md"); - StringBuilder sb = new(); - sb.AppendLine($"### {projectName}"); - sb.AppendLine("
"); - sb.AppendLine("Details"); - sb.AppendLine(); - int count = 0; - foreach (string line in fileContent.Skip(1)) - { - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - if (line.StartsWith("#")) - { - if (++count == 1) - { - sb.AppendLine(); - sb.AppendLine("
"); - sb.AppendLine(); - } - - sb.AppendLine("##" + line); - continue; - } - - if (count == 0 && - line.StartsWith("|") && - line.Contains("| N\\/A")) - { - continue; - } - - sb.AppendLine(line); - } - - string body = sb.ToString(); - return body; - } - - static string PathForJson(Project project) => $"\"{project.Path.ToString().Replace(@"\", @"\\")}\""; -} diff --git a/Pipeline/Build.Pack.cs b/Pipeline/Build.Pack.cs index d322c18..2711308 100644 --- a/Pipeline/Build.Pack.cs +++ b/Pipeline/Build.Pack.cs @@ -45,14 +45,6 @@ partial class Build continue; } - if (line.StartsWith("[![Mutation testing badge](https://img.shields.io/endpoint")) - { - sb.AppendLine(line - .Replace("%2Fmain)", $"%2Frelease%2Fv{version})") - .Replace("/main)", $"/release/v{version})")); - continue; - } - sb.AppendLine(line); } diff --git a/Pipeline/Build.csproj b/Pipeline/Build.csproj index 67ab6bc..2bc8081 100644 --- a/Pipeline/Build.csproj +++ b/Pipeline/Build.csproj @@ -29,7 +29,6 @@ - diff --git a/README.md b/README.md index 8217fba..4b6ccb8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ [![Build](https://github.com/Testably/Awaiten/actions/workflows/build.yml/badge.svg)](https://github.com/Testably/Awaiten/actions/workflows/build.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Testably_Awaiten&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Testably_Awaiten) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Testably_Awaiten&metric=coverage)](https://sonarcloud.io/summary/overall?id=Testably_Awaiten) -[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2FTestably%2FAwaiten%2Fmain)](https://dashboard.stryker-mutator.io/reports/github.com/Testably/Awaiten/main) **The async-first dependency injection container for .NET.**