Skip to content

Commit

Permalink
Add mutation commit message
Browse files Browse the repository at this point in the history
  • Loading branch information
vbreuss committed Dec 19, 2024
1 parent 9123628 commit c50354a
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 23 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ jobs:
env:
STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
DOTNET_NOLOGO: true
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -85,6 +87,8 @@ jobs:
8.0.x
- name: Run mutation tests
run: ./build.sh MutationTestsLinux
env:
GithubToken: ${{ secrets.GITHUB_TOKEN }}

mutation-tests-windows:
name: "Mutation tests (Windows)"
Expand All @@ -93,6 +97,8 @@ jobs:
env:
STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
DOTNET_NOLOGO: true
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -106,6 +112,8 @@ jobs:
8.0.x
- name: Run mutation tests
run: ./build.ps1 MutationTestsWindows
env:
GithubToken: ${{ secrets.GITHUB_TOKEN }}

static-code-analysis:
name: "Static code analysis"
Expand Down
171 changes: 148 additions & 23 deletions Pipeline/Build.MutationTests.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,50 @@
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 System.Text;
using static Nuke.Common.Tools.DotNet.DotNetTasks;
using Project = Nuke.Common.ProjectModel.Project;

// ReSharper disable AllUnderscoreLocalParameterName

namespace Build;

partial class Build
{
static Dictionary<string, string> MutationCommentBody = new();
AbsolutePath StrykerToolPath => TestResultsDirectory / "dotnet-stryker";
AbsolutePath StrykerOutputDirectory => ArtifactsDirectory / "Stryker";

Target MutationTests => _ => _
.DependsOn(MutationTestsWindows)
.DependsOn(MutationTestsLinux);
.DependsOn(MutationTestsLinux)
.DependsOn(MutationComment);

Target MutationTestsWindows => _ => _
.DependsOn(Compile)
Target MutationTestPreparation => _ => _
.Executes(() =>
{
AbsolutePath toolPath = TestResultsDirectory / "dotnet-stryker";
AbsolutePath configFile = toolPath / "Stryker.Config.json";
toolPath.CreateOrCleanDirectory();
StrykerToolPath.CreateOrCleanDirectory();

DotNetToolInstall(_ => _
.SetPackageName("dotnet-stryker")
.SetToolInstallationPath(toolPath));
.SetToolInstallationPath(StrykerToolPath));

StrykerOutputDirectory.CreateOrCleanDirectory();
});

Target MutationTestsWindows => _ => _
.DependsOn(Compile)
.DependsOn(MutationTestPreparation)
.Executes(() =>
{
AbsolutePath configFile = StrykerToolPath / "Stryker.Config.json";
Dictionary<Project, Project[]> projects = new()
{
{ Solution.Testably_Abstractions_AccessControl, [Solution.Tests.Testably_Abstractions_AccessControl_Tests] },
Expand Down Expand Up @@ -82,12 +95,12 @@ 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(
Path.Combine(toolPath, executable),
Path.Combine(StrykerToolPath, executable),
arguments,
Solution.Directory)
.AssertWaitForExit();
Expand All @@ -96,21 +109,17 @@ partial class Build
Assert.Fail(
$"Stryker did not execute successfully for {project.Key.Name}: (exit code {process.ExitCode}).");
}

MutationCommentBody.Add(project.Key.Name, CreateMutationCommentBody(project.Key.Name));
}
});

Target MutationTestsLinux => _ => _
.DependsOn(Compile)
.DependsOn(MutationTestPreparation)
.Executes(() =>
{
AbsolutePath toolPath = TestResultsDirectory / "dotnet-stryker";
AbsolutePath configFile = toolPath / "Stryker.Config.json";
toolPath.CreateOrCleanDirectory();

DotNetToolInstall(_ => _
.SetPackageName("dotnet-stryker")
.SetToolInstallationPath(toolPath));

AbsolutePath configFile = StrykerToolPath / "Stryker.Config.json";
Dictionary<Project, Project[]> projects = new()
{
{ Solution.Testably_Abstractions_Testing, [ Solution.Tests.Testably_Abstractions_Testing_Tests, Solution.Tests.Testably_Abstractions_Tests ] },
Expand Down Expand Up @@ -187,12 +196,12 @@ 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(
Path.Combine(toolPath, executable),
Path.Combine(StrykerToolPath, executable),
arguments,
Solution.Directory)
.AssertWaitForExit();
Expand All @@ -201,8 +210,124 @@ partial class Build
Assert.Fail(
$"Stryker did not execute successfully for {project.Key.Name}: (exit code {process.ExitCode}).");
}

MutationCommentBody.Add(project.Key.Name, CreateMutationCommentBody(project.Key.Name));
}
});


Target MutationComment => _ => _
.After(MutationTestsLinux)
.After(MutationTestsWindows)
.OnlyWhenDynamic(() => GitHubActions.IsPullRequest)
.Executes(async () =>
{
int? prId = GitHubActions.PullRequestNumber;
Log.Debug("Pull request number: {PullRequestId}", prId);
if (MutationCommentBody.Count == 0)
{
return;
}

if (prId != null)
{
GitHubClient gitHubClient = new(new ProductHeaderValue("Nuke"));
Credentials tokenAuth = new(GithubToken);
gitHubClient.Credentials = tokenAuth;
IReadOnlyList<IssueComment> comments =
await gitHubClient.Issue.Comment.GetAllForIssue("Textably", "Textably.Abstractions", prId.Value);
IssueComment? existingComment = 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}");
existingComment = comment;
}
}

if (existingComment == null)
{
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%2Fgit.luolix.top%2FTextably%2FTextably.Abstractions%2Fpull/{prId}/merge)](https://dashboard.stryker-mutator.io/reports/github.com/Textably/Textably.Abstractions/pull/{prId}/merge)"
+ Environment.NewLine
+ string.Join(Environment.NewLine, MutationCommentBody.Values);

Log.Information($"Create comment:\n{body}");
await gitHubClient.Issue.Comment.Create("Textably", "Textably.Abstractions", prId.Value, body);
}
else
{
string body = existingComment.Body;
foreach ((var project, var value) in MutationCommentBody)
{
body = ReplaceProject(body, project, value);
}

Log.Information($"Update comment:\n{body}");
await gitHubClient.Issue.Comment.Update("Textably", "Textably.Abstractions", existingComment.Id, body);
}
}
});

string ReplaceProject(string body, string project, string value)
{
var startIndex = body.IndexOf($"<!-- START {project} -->", StringComparison.OrdinalIgnoreCase);
var endIndex = body.IndexOf($"<!-- END {project} -->", StringComparison.OrdinalIgnoreCase);
if (startIndex >= 0 && endIndex > startIndex)
{
var prefix = body.Substring(0, startIndex);
var suffix = body.Substring(endIndex + 1);
return prefix + value + suffix;
}
return body + Environment.NewLine + value

Check failure on line 284 in Pipeline/Build.MutationTests.cs

View workflow job for this annotation

GitHub Actions / Mutation tests (Linux)

; expected

Check failure on line 284 in Pipeline/Build.MutationTests.cs

View workflow job for this annotation

GitHub Actions / Static code analysis

; expected

Check failure on line 284 in Pipeline/Build.MutationTests.cs

View workflow job for this annotation

GitHub Actions / API tests

; expected

Check failure on line 284 in Pipeline/Build.MutationTests.cs

View workflow job for this annotation

GitHub Actions / Mutation tests (Windows)

; expected

Check failure on line 284 in Pipeline/Build.MutationTests.cs

View workflow job for this annotation

GitHub Actions / Unit tests (ubuntu-latest)

; expected

Check failure on line 284 in Pipeline/Build.MutationTests.cs

View workflow job for this annotation

GitHub Actions / Unit tests (macos-latest)

; expected
}

string CreateMutationCommentBody(string projectName)
{
string[] fileContent = File.ReadAllLines(ArtifactsDirectory / "Stryker" / "reports" / "mutation-report.md");
StringBuilder sb = new();
sb.AppendLine($"<!-- START {projectName} -->");
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);
}

sb.AppendLine($"<!-- END {projectName} -->");
string body = sb.ToString();
return body;
}

static string PathForJson(Project project) => $"\"{project.Path.ToString().Replace(@"\", @"\\")}\"";
}

0 comments on commit c50354a

Please sign in to comment.