Skip to content

Commit

Permalink
feat: add Stryker.NET comment in pull requests (#19)
Browse files Browse the repository at this point in the history
Add a PR comment with the mutation test results.
  • Loading branch information
vbreuss authored Dec 31, 2024
1 parent 0960362 commit b5464ab
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 13 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ jobs:
name: "Mutation tests"
if: ${{ github.actor != 'dependabot[bot]' }}
runs-on: ubuntu-latest
permissions:
pull-requests: write
env:
STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
DOTNET_NOLOGO: true
Expand All @@ -79,6 +81,8 @@ jobs:
8.0.x
- name: Run mutation tests
run: ./build.sh MutationTests
env:
GithubToken: ${{ secrets.GITHUB_TOKEN }}

static-code-analysis:
name: "Static code analysis"
Expand Down
125 changes: 112 additions & 13 deletions Pipeline/Build.MutationTests.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Nuke.Common;
using Nuke.Common.IO;
using Nuke.Common.ProjectModel;
using Nuke.Common.Tooling;
using Nuke.Common.Tools.DotNet;
using Octokit;
using Serilog;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using static Nuke.Common.Tools.DotNet.DotNetTasks;
using Project = Nuke.Common.ProjectModel.Project;

// 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(_ => _
Expand All @@ -42,7 +52,7 @@ partial class Build
branchName = "release/" + version;
Log.Information("Use release branch analysis for '{BranchName}'", branchName);
}

string configText = $$"""
{
"stryker-config": {
Expand All @@ -63,11 +73,6 @@ partial class Build
"**/.github/**/*.*"
]
},
"reporters": [
"html",
"progress",
"cleartext"
],
"mutation-level": "Advanced"
}
}
Expand All @@ -76,8 +81,8 @@ partial class Build
Log.Debug($"Created '{configFile}':{Environment.NewLine}{configText}");

string arguments = IsServerBuild
? $"-f \"{configFile}\" -r \"Dashboard\" -r \"cleartext\""
: $"-f \"{configFile}\" -r \"cleartext\"";
? $"-f \"{configFile}\" -O \"{strykerOutputDirectory}\" -r \"Markdown\" -r \"Dashboard\" -r \"cleartext\""
: $"-f \"{configFile}\" -O \"{strykerOutputDirectory}\" -r \"Markdown\" -r \"cleartext\"";

string executable = EnvironmentInfo.IsWin ? "dotnet-stryker.exe" : "dotnet-stryker";
IProcess process = ProcessTasks.StartProcess(
Expand All @@ -90,8 +95,102 @@ partial class Build
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(async () =>
{
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%2FaweXpect%2FaweXpect.Chronology%2Fpull/{prId}/merge)](https://dashboard.stryker-mutator.io/reports/github.com/aweXpect/aweXpect.Chronology/pull/{prId}/merge)"
+ Environment.NewLine
+ MutationCommentBody;

if (prId != null)
{
GitHubClient gitHubClient = new(new ProductHeaderValue("Nuke"));
Credentials tokenAuth = new(GithubToken);
gitHubClient.Credentials = tokenAuth;
IReadOnlyList<IssueComment> comments =
await gitHubClient.Issue.Comment.GetAllForIssue("aweXpect", "aweXpect.Chronology", prId.Value);
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("aweXpect", "aweXpect.Chronology", prId.Value, body);
}
else
{
Log.Information($"Update comment:\n{body}");
await gitHubClient.Issue.Comment.Update("aweXpect", "aweXpect.Chronology", 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("<details>");
sb.AppendLine("<summary>Details</summary>");
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("</details>");
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(@"\", @"\\")}\"";
}

0 comments on commit b5464ab

Please sign in to comment.