Skip to content

Commit

Permalink
fix: Dashboard baseline project name and version are not set (#1710)
Browse files Browse the repository at this point in the history
* Fix DashboardBaselineProvider

The automatic detection of project name and version introduced in #1663 broke the DashboardBaselineProvider because the project name would not be set and thus the report would be sent to an invalid URL: `/api/reports//version`

This commit addresses this issue by setting the project name and version on the StrykerOptions rather than on the DashboardClient and DashboardReporter (like it was originally done in #1532).

* Fix uploading the report

1. Unlike `GetFromJsonAsync`, `PutAsJsonAsync` does not ensure a success status code so we call it to make sure the request was successful.
2. The description field of a mutant can not be null else we get an HTTP 400 error with this description:
> Invalid report. data.files['IndentationSettings.cs'].mutants[0].description should be string

* Fix NullReferenceException in InitialisationProcessTests

* Don't try to read project name and project version if not required

This should fix the integration error on Azure Pipelines:
> Failed to retrieve the RepositoryUrl from the AssemblyMetadataAttribute of /home/vsts/work/1/s/integrationtest/TargetProjects/TargetProject/bin/Debug/netcoreapp3.0/TargetProject.dll

Co-authored-by: Rouke Broersma <mobrockers@gmail.com>
  • Loading branch information
0xced and rouke-broersma authored Oct 5, 2021
1 parent 8f90d76 commit e2523e4
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,13 @@ public void InitialisationProcess_ShouldCallNeededResolvers()
testRunnerMock.Object,
assemblyReferenceResolverMock.Object);

var options = new StrykerOptions();
var options = new StrykerOptions
{
ProjectName = "TheProjectName",
ProjectVersion = "TheProjectVersion"
};

var result = target.Initialize(options, dashboardReporter: null);
var result = target.Initialize(options);

inputFileResolverMock.Verify(x => x.ResolveInput(It.IsAny<StrykerOptions>()), Times.Once);
}
Expand Down Expand Up @@ -104,9 +108,13 @@ public void InitialisationProcess_ShouldThrowOnFailedInitialTestRun()
initialTestProcessMock.Object,
testRunnerMock.Object,
assemblyReferenceResolverMock.Object);
var options = new StrykerOptions();
var options = new StrykerOptions
{
ProjectName = "TheProjectName",
ProjectVersion = "TheProjectVersion"
};

target.Initialize(options, dashboardReporter: null);
target.Initialize(options);
Assert.Throws<InputException>(() => target.InitialTest(options));

inputFileResolverMock.Verify(x => x.ResolveInput(It.IsAny<StrykerOptions>()), Times.Once);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public void ShouldInitializeEachProjectInSolution()
var options = new StrykerOptions();
var target = new ProjectMutator(_initialisationProcessProviderMock.Object, _mutationTestProcessProviderMock.Object);

_initialisationProcessMock.Setup(x => x.Initialize(It.IsAny<StrykerOptions>(), It.IsAny<DashboardReporter>())).Returns(_mutationTestInput);
_initialisationProcessMock.Setup(x => x.Initialize(It.IsAny<StrykerOptions>())).Returns(_mutationTestInput);
_initialisationProcessMock.Setup(x => x.InitialTest(options))
.Returns(new InitialTestRun(new TestRunResult(true), new TimeoutValueCalculator(500)));
// act
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void ShouldInitializeEachProjectInSolution()
};
var target = new ProjectOrchestrator(_buildalyzerProviderMock.Object, _projectMutatorMock.Object);

_initialisationProcessMock.Setup(x => x.Initialize(It.IsAny<StrykerOptions>(), It.IsAny<DashboardReporter>())).Returns(_mutationTestInput);
_initialisationProcessMock.Setup(x => x.Initialize(It.IsAny<StrykerOptions>())).Returns(_mutationTestInput);
_initialisationProcessMock.Setup(x => x.InitialTest(It.IsAny<StrykerOptions>())).Returns(new InitialTestRun(new TestRunResult(true), new TimeoutValueCalculator(5)));
_buildalyzerProviderMock.Setup(x => x.Provide(It.IsAny<string>(), It.IsAny<AnalyzerManagerOptions>())).Returns(buildalyzerAnalyzerManagerMock.Object);
// The analyzer finds two projects
Expand Down
7 changes: 2 additions & 5 deletions src/Stryker.Core/Stryker.Core/Clients/DashboardClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ namespace Stryker.Core.Clients
{
public interface IDashboardClient
{
string ProjectName { get; set; }
Task<string> PublishReport(JsonReport json, string version);
Task<JsonReport> PullReport(string version);
}
Expand All @@ -35,11 +34,8 @@ public DashboardClient(StrykerOptions options, HttpClient httpClient = null, ILo
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("X-Api-Key", _options.DashboardApiKey);
}
ProjectName = _options.ProjectName;
}

public string ProjectName { get; set; }

public async Task<string> PublishReport(JsonReport report, string version)
{
var url = GetUrl(version);
Expand All @@ -49,6 +45,7 @@ public async Task<string> PublishReport(JsonReport report, string version)
try
{
using var response = await _httpClient.PutAsJsonAsync(url, report);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<DashboardResult>();
return result?.Href;
}
Expand Down Expand Up @@ -78,7 +75,7 @@ public async Task<JsonReport> PullReport(string version)

private Uri GetUrl(string version)
{
var url = new Uri($"{_options.DashboardUrl}/api/reports/{ProjectName}/{version}");
var url = new Uri($"{_options.DashboardUrl}/api/reports/{_options.ProjectName}/{version}");

if (_options.ModuleName != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using Microsoft.Extensions.Logging;
using Mono.Cecil;
using Stryker.Core.Baseline.Providers;
using Stryker.Core.Exceptions;
using Stryker.Core.Initialisation.Buildalyzer;
using Stryker.Core.Logging;
Expand All @@ -29,7 +30,7 @@ public class InitialisationProcessProvider : IInitialisationProcessProvider

public interface IInitialisationProcess
{
MutationTestInput Initialize(StrykerOptions options, DashboardReporter dashboardReporter);
MutationTestInput Initialize(StrykerOptions options);
InitialTestRun InitialTest(StrykerOptions options);
}

Expand Down Expand Up @@ -57,7 +58,7 @@ public InitialisationProcess(
_logger = ApplicationLogging.LoggerFactory.CreateLogger<InitialisationProcess>();
}

public MutationTestInput Initialize(StrykerOptions options, DashboardReporter dashboardReporter)
public MutationTestInput Initialize(StrykerOptions options)
{
// resolve project info
var projectInfo = _inputFileResolver.ResolveInput(options);
Expand All @@ -78,7 +79,7 @@ public MutationTestInput Initialize(StrykerOptions options, DashboardReporter da
options.MsBuildPath);
}

InitializeDashboardReporter(dashboardReporter, projectInfo);
InitializeDashboardProjectInformation(options, projectInfo);

if (_testRunner == null)
{
Expand All @@ -99,16 +100,19 @@ public InitialTestRun InitialTest(StrykerOptions options) =>
// initial test
_initialTestProcess.InitialTest(options, _testRunner);

private void InitializeDashboardReporter(DashboardReporter dashboardReporter, ProjectInfo projectInfo)
private void InitializeDashboardProjectInformation(StrykerOptions options, ProjectInfo projectInfo)
{
if (dashboardReporter == null)
var dashboardReporterEnabled = options.Reporters.Contains(Reporter.Dashboard) || options.Reporters.Contains(Reporter.All);
var dashboardBaselineEnabled = options.WithBaseline && options.BaselineProvider == BaselineProvider.Dashboard;
var requiresProjectInformation = dashboardReporterEnabled || dashboardBaselineEnabled;
if (!requiresProjectInformation)
{
return;
}

// try to read the repository URL + version for the dashboard report
var missingProjectName = string.IsNullOrEmpty(dashboardReporter.ProjectName);
var missingProjectVersion = string.IsNullOrEmpty(dashboardReporter.ProjectVersion);
// try to read the repository URL + version for the dashboard report or dashboard baseline
var missingProjectName = string.IsNullOrEmpty(options.ProjectName);
var missingProjectVersion = string.IsNullOrEmpty(options.ProjectVersion);
if (missingProjectName || missingProjectVersion)
{
var subject = missingProjectName switch
Expand All @@ -135,14 +139,14 @@ private void InitializeDashboardReporter(DashboardReporter dashboardReporter, Pr
var details = $"To solve this issue, either specify the {subject.ToLowerInvariant()} in the stryker configuration or configure [SourceLink](/~https://github.com/dotnet/sourcelink#readme) in {projectFilePath}";
if (missingProjectName)
{
dashboardReporter.ProjectName = ReadProjectName(module, details);
_logger.LogDebug("Using {ProjectName} as project name for the dashboard reporter. (Read from the AssemblyMetadata/RepositoryUrl assembly attribute of {TargetName})", dashboardReporter.ProjectName, targetName);
options.ProjectName = ReadProjectName(module, details);
_logger.LogDebug("Using {ProjectName} as project name for the dashboard reporter. (Read from the AssemblyMetadata/RepositoryUrl assembly attribute of {TargetName})", options.ProjectName, targetName);
}

if (missingProjectVersion)
{
dashboardReporter.ProjectVersion = ReadProjectVersion(module, details);
_logger.LogDebug("Using {ProjectVersion} as project version for the dashboard reporter. (Read from the AssemblyInformationalVersion assembly attribute of {TargetName})", dashboardReporter.ProjectVersion, targetName);
options.ProjectVersion = ReadProjectVersion(module, details);
_logger.LogDebug("Using {ProjectVersion} as project version for the dashboard reporter. (Read from the AssemblyInformationalVersion assembly attribute of {TargetName})", options.ProjectVersion, targetName);
}
}
catch (Exception e) when (e is not InputException)
Expand Down
13 changes: 1 addition & 12 deletions src/Stryker.Core/Stryker.Core/Initialisation/ProjectMutator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ public IMutationTestProcess MutateProject(StrykerOptions options, IReporter repo
// get a new instance of InitialisationProcess for each project
var initialisationProcess = _initialisationProcessProvider.Provide();
// initialize
var dashboardReporter = GetDashboardReporter(reporters);
var input = initialisationProcess.Initialize(options, dashboardReporter);
var input = initialisationProcess.Initialize(options);

var process = _mutationTestProcessProvider.Provide(
mutationTestInput: input,
Expand All @@ -44,15 +43,5 @@ public IMutationTestProcess MutateProject(StrykerOptions options, IReporter repo

return process;
}

private static DashboardReporter GetDashboardReporter(IReporter reporters)
{
if (reporters is BroadcastReporter broadcastReporter)
{
return broadcastReporter.Reporters.OfType<DashboardReporter>().FirstOrDefault();
}

return reporters as DashboardReporter;
}
}
}
34 changes: 32 additions & 2 deletions src/Stryker.Core/Stryker.Core/Options/StrykerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,12 @@ public class StrykerOptions

public string DashboardUrl { get; init; }
public string DashboardApiKey { get; init; }
public string ProjectName { get; init; }

public bool Since { get; init; }
public string SinceTarget { get; init; }
public IEnumerable<FilePattern> DiffIgnoreChanges { get; init; } = Enumerable.Empty<FilePattern>();

public string FallbackVersion { get; init; }
public string ProjectVersion { get; init; }
public string ModuleName { get; init; }

public IEnumerable<FilePattern> Mutate { get; init; } = new[] { FilePattern.Parse("**/*") };
Expand All @@ -56,8 +54,40 @@ public class StrykerOptions

public OptimizationModes OptimizationMode { get; init; }

private string _projectName;
public string ProjectName
{
get => _projectName;
set
{
_projectName = value;
if (_parentOptions is not null)
{
_parentOptions.ProjectName = value;
}
}
}

private string _projectVersion;
public string ProjectVersion
{
get => _projectVersion;
set
{
_projectVersion = value;
if (_parentOptions is not null)
{
_parentOptions.ProjectVersion = value;
}
}
}

// Keep a reference on the parent instance in order to flow get/set properties (ProjectName and ProjectVersion) up to the parent
private StrykerOptions _parentOptions;

public StrykerOptions Copy(string basePath, string projectUnderTest, IEnumerable<string> testProjects) => new()
{
_parentOptions = this,
AdditionalTimeout = AdditionalTimeout,
AzureFileStorageSas = AzureFileStorageSas,
AzureFileStorageUrl = AzureFileStorageUrl,
Expand Down
13 changes: 2 additions & 11 deletions src/Stryker.Core/Stryker.Core/Reporters/DashboardReporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace Stryker.Core.Reporters
{
public partial class DashboardReporter : IReporter
public class DashboardReporter : IReporter
{
private readonly StrykerOptions _options;
private readonly IDashboardClient _dashboardClient;
Expand All @@ -25,22 +25,13 @@ public DashboardReporter(StrykerOptions options, IDashboardClient dashboardClien
_dashboardClient = dashboardClient ?? new DashboardClient(options);
_logger = logger ?? ApplicationLogging.LoggerFactory.CreateLogger<DashboardReporter>();
_consoleWriter = consoleWriter ?? Console.Out;
ProjectVersion = _options.ProjectVersion;
}

public string ProjectName
{
get => _dashboardClient.ProjectName;
set => _dashboardClient.ProjectName = value;
}

public string ProjectVersion { get; set; }

public void OnAllMutantsTested(IReadOnlyProjectComponent reportComponent)
{
var mutationReport = JsonReport.Build(_options, reportComponent);

var reportUrl = _dashboardClient.PublishReport(mutationReport, ProjectVersion).Result;
var reportUrl = _dashboardClient.PublishReport(mutationReport, _options.ProjectVersion).Result;

if (reportUrl != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ public JsonReportFileComponent(ReadOnlyFileLeaf file, ILogger logger = null)
var jsonMutant = new JsonMutant
{
Id = mutant.Id.ToString(),
MutatorName = mutant.Mutation.DisplayName,
MutatorName = mutant.Mutation.DisplayName ?? "",
Replacement = mutant.Mutation.ReplacementNode.ToFullString(),
Location = new JsonMutantLocation(mutant.Mutation.OriginalNode.GetLocation().GetMappedLineSpan()),
Status = mutant.ResultStatus.ToString(),
Description = mutant.Mutation.Description
Description = mutant.Mutation.Description ?? ""
};

if (!Mutants.Add(jsonMutant))
Expand Down

0 comments on commit e2523e4

Please sign in to comment.