From a14b64bbea21cb44c8bc9868c826a5344500e058 Mon Sep 17 00:00:00 2001 From: Cyrille DUPUYDAUBY Date: Fri, 1 Mar 2024 10:47:57 +0100 Subject: [PATCH] fix: Improve build, restore and test (#2845) * chore: log stryker version * feat: align project analysis between project and solution modes + imp. logging of analysis results * chore: drop use of obsolete VsTest async APIs * chore: update FullFrameworkApp dependencies * fix: force restore packages if msbuild fails * fix: add support for packages.config * fix: tests * fix: force running tests in isolation * revert useless dependency updates * fix: add some log and adjust vstest settings * chore: add tests * fix: fix first projet in solution always tested against all test projects --------- Co-authored-by: Rouke Broersma --- src/Stryker.CLI/Stryker.CLI/StrykerCLI.cs | 3 +- .../InitialBuildProcessTests.cs | 2 +- .../NugetRestoreProcessTests.cs | 203 +++++++++++++++--- .../ProjectOrchestratorTests.cs | 51 ++++- .../TestRunners/CoverageCollectorTests.cs | 1 - .../TestRunners/VsTestMockingHelper.cs | 82 ++++--- .../TestRunners/VsTestRunnerPoolTests.cs | 40 +++- .../VsTextContextInformationTests.cs | 4 +- .../Buildalyzer/IAnalyzerResultExtensions.cs | 7 +- .../Initialisation/InitialBuildProcess.cs | 8 +- .../Initialisation/InputFileResolver.cs | 8 +- .../Initialisation/NugetRestoreProcess.cs | 50 +++-- .../Initialisation/ProjectFileReader.cs | 48 +++-- .../InjectedHelpers/MutantControl.cs | 2 +- .../TestProjects/TestProjectsInfo.cs | 107 +++++---- .../Stryker.Core/StrykerRunner.cs | 2 + .../VsTest/VsTestContextInformation.cs | 9 +- .../TestRunners/VsTest/VsTestRunner.cs | 9 +- .../TestRunners/VsTest/VsTestRunnerPool.cs | 12 +- .../Stryker.Core/Testing/ProcessExecutor.cs | 12 +- .../CoverageCollector.cs | 14 +- 21 files changed, 462 insertions(+), 212 deletions(-) diff --git a/src/Stryker.CLI/Stryker.CLI/StrykerCLI.cs b/src/Stryker.CLI/Stryker.CLI/StrykerCLI.cs index 0eded7e438..93ec8f3c53 100644 --- a/src/Stryker.CLI/Stryker.CLI/StrykerCLI.cs +++ b/src/Stryker.CLI/Stryker.CLI/StrykerCLI.cs @@ -148,12 +148,12 @@ _____ _ _ _ _ ______ _______   [System.Diagnostics.CodeAnalysis.SuppressMessage("Major Bug", "S3168:\"async\" methods should not return \"void\"", Justification = "This method is fire and forget. Task.Run also doesn't work in unit tests")] private async void PrintStrykerVersionInformationAsync() { + var logger = ApplicationLogging.LoggerFactory.CreateLogger(); var assembly = Assembly.GetExecutingAssembly(); var version = assembly.GetCustomAttribute()?.InformationalVersion; if (!SemanticVersion.TryParse(version, out var currentVersion)) { - var logger = ApplicationLogging.LoggerFactory.CreateLogger(); if (string.IsNullOrEmpty(version)) { logger.LogWarning("{Attribute} is missing in {Assembly} at {AssemblyLocation}", nameof(AssemblyInformationalVersionAttribute), assembly, assembly.Location); @@ -166,6 +166,7 @@ private async void PrintStrykerVersionInformationAsync() } _console.MarkupLine($"Version: [Green]{currentVersion}[/]"); + logger.LogDebug("Stryker starting, version: {Version}", currentVersion); _console.WriteLine(); var latestVersion = await _nugetClient.GetLatestVersionAsync(); diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/InitialBuildProcessTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/InitialBuildProcessTests.cs index da0478edc5..add5c60c36 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/InitialBuildProcessTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/InitialBuildProcessTests.cs @@ -63,7 +63,7 @@ public void InitialBuildProcess_WithPathAsBuildCommand_TriesWithMsBuildIfDotnetF @"C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe" + "\" \"" + _cProjectsExampleCsproj + "\"\""); processMock.Verify(x =>x.Start(It.IsAny(), It.Is(app => app.Contains("dotnet")), It.IsAny(), It.IsAny>>(), 0), Times.Once()); - processMock.Verify(x =>x.Start(It.IsAny(), It.Is(app => app.Contains("MSBuild.exe")), It.IsAny(), It.IsAny>>(), 0), Times.Once()); + processMock.Verify(x =>x.Start(It.IsAny(), It.Is(app => app.Contains("MSBuild.exe")), It.IsAny(), It.IsAny>>(), 0), Times.Exactly(3)); } [Fact] diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/NugetRestoreProcessTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/NugetRestoreProcessTests.cs index 2834b5263a..176c742c12 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/NugetRestoreProcessTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/NugetRestoreProcessTests.cs @@ -12,20 +12,21 @@ namespace Stryker.Core.UnitTest.Initialisation { public class NugetRestoreProcessTests : TestBase { - private const string solutionPath = @"..\MySolution.sln"; + private const string SolutionPath = @"..\MySolution.sln"; private const string CProgramFilesX86MicrosoftVisualStudio = "C:\\Program Files (x86)\\Microsoft Visual Studio"; - private readonly string solutionDir = Path.GetDirectoryName(Path.GetFullPath(solutionPath)); + private readonly string _solutionDir = Path.GetDirectoryName(Path.GetFullPath(SolutionPath)); [SkippableFact] public void HappyFlow() { Skip.IfNot(Environment.OSVersion.Platform == PlatformID.Win32NT, "DotnetFramework does not run on Unix"); - string nugetPath = @"C:\choco\bin\NuGet.exe"; - string msBuildVersion = "16.0.0"; + var nugetPath = @"C:\choco\bin\NuGet.exe"; + var msBuildVersion = "16.0.0"; + var nugetDirectory = Path.GetDirectoryName(nugetPath); var processExecutorMock = new Mock(MockBehavior.Strict); - processExecutorMock.Setup(x => x.Start(solutionDir, It.Is((p) => p.EndsWith("where.exe")), "nuget.exe", null, It.IsAny())) + processExecutorMock.Setup(x => x.Start(_solutionDir, It.Is((p) => p.EndsWith("where.exe")), "nuget.exe", null, It.IsAny())) .Returns(new ProcessResult() { ExitCode = 0, @@ -47,20 +48,143 @@ public void HappyFlow() Output = CProgramFilesX86MicrosoftVisualStudio }); - processExecutorMock.Setup(x => x.Start(solutionDir, It.IsAny(), "-version /nologo", null, It.IsAny())) + processExecutorMock.Setup(x => x.Start(_solutionDir, It.IsAny(), "-version /nologo", null, It.IsAny())) .Returns(new ProcessResult() { ExitCode = 0, Output = msBuildVersion }); - processExecutorMock.Setup(x => x.Start(solutionDir, nugetPath, string.Format("restore \"{0}\" -MsBuildVersion \"{1}\"", Path.GetFullPath(solutionPath), msBuildVersion), null, It.IsAny())) + processExecutorMock.Setup(x => x.Start(nugetDirectory, nugetPath, + $"restore \"{Path.GetFullPath(SolutionPath)}\" -MsBuildVersion \"{msBuildVersion}\"", null, It.IsAny())) .Returns(new ProcessResult() { ExitCode = 0, Output = "Packages restored" }); - processExecutorMock.Setup(x => x.Start(It.Is(s => s.Contains("Microsoft Visual Studio")), It.Is(s => s.Contains("vswhere.exe")), @"-latest -requires Microsoft.Component.MSBuild -products * -find MSBuild\**\Bin\MSBuild.exe", null, It.IsAny())) + processExecutorMock.Setup(x => x.Start(It.Is(s => s.Contains("Microsoft Visual Studio")), It.Is(s => s.Contains("vswhere.exe")), + @"-latest -requires Microsoft.Component.MSBuild -products * -find MSBuild\**\Bin\MSBuild.exe", null, It.IsAny())) + .Returns(new ProcessResult() + { + ExitCode = 0, + Output = "Msbuild executable path found at " + }); + var target = new NugetRestoreProcess(processExecutorMock.Object); + + target.RestorePackages(SolutionPath); + + processExecutorMock.Verify( p => p.Start(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny>>(), 0), Times.Exactly(4)); + } + + [SkippableFact] + public void ThrowIfRestoreFails() + { + Skip.IfNot(Environment.OSVersion.Platform == PlatformID.Win32NT, "DotnetFramework does not run on Unix"); + + var nugetPath = @"C:\choco\bin\NuGet.exe"; + var msBuildVersion = "16.0.0"; + var nugetDirectory = Path.GetDirectoryName(nugetPath); + + var processExecutorMock = new Mock(MockBehavior.Strict); + processExecutorMock.Setup(x => x.Start(_solutionDir, It.Is((p) => p.EndsWith("where.exe")), "nuget.exe", null, It.IsAny())) + .Returns(new ProcessResult() + { + ExitCode = 0, + Output = nugetPath + }); + + processExecutorMock.Setup(x => x.Start(CProgramFilesX86MicrosoftVisualStudio, It.Is((p) => p.EndsWith("where.exe")), + "-latest -requires Microsoft.Component.MSBuild -products * -find MSBuild\\**\\Bin\\MSBuild.exe", null, It.IsAny())) + .Returns(new ProcessResult() + { + ExitCode = 0, + Output = CProgramFilesX86MicrosoftVisualStudio + }); + processExecutorMock.Setup(x => x.Start(CProgramFilesX86MicrosoftVisualStudio, It.Is((p) => p.EndsWith("where.exe")), + "-prerelease -requires Microsoft.Component.MSBuild -products * -find MSBuild\\**\\Bin\\MSBuild.exe", null, It.IsAny())) + .Returns(new ProcessResult() + { + ExitCode = 0, + Output = CProgramFilesX86MicrosoftVisualStudio + }); + + processExecutorMock.Setup(x => x.Start(_solutionDir, It.IsAny(), "-version /nologo", null, It.IsAny())) + .Returns(new ProcessResult() + { + ExitCode = 0, + Output = msBuildVersion + }); + + processExecutorMock.Setup(x => x.Start(nugetDirectory, nugetPath, + $"restore \"{Path.GetFullPath(SolutionPath)}\" -MsBuildVersion \"{msBuildVersion}\"", null, It.IsAny())) + .Returns(new ProcessResult() + { + ExitCode = 1, + Output = "Packages restore failed." + }); + processExecutorMock.Setup(x => x.Start(It.Is(s => s.Contains("Microsoft Visual Studio")), It.Is(s => s.Contains("vswhere.exe")), + @"-latest -requires Microsoft.Component.MSBuild -products * -find MSBuild\**\Bin\MSBuild.exe", null, It.IsAny())) + .Returns(new ProcessResult() + { + ExitCode = 0, + Output = "Msbuild executable path found at " + }); + var target = new NugetRestoreProcess(processExecutorMock.Object); + + var action = () => target.RestorePackages(SolutionPath); + + action.ShouldThrow("Packages restore failed."); + } + + [SkippableFact] + public void FailToGetMsBuildVersion() + { + Skip.IfNot(Environment.OSVersion.Platform == PlatformID.Win32NT, "DotnetFramework does not run on Unix"); + + var nugetPath = @"C:\choco\bin\NuGet.exe"; + var msBuildVersion = string.Empty; + var nugetDirectory = Path.GetDirectoryName(nugetPath); + + var processExecutorMock = new Mock(MockBehavior.Strict); + processExecutorMock.Setup(x => x.Start(_solutionDir, It.Is((p) => p.EndsWith("where.exe")), "nuget.exe", null, It.IsAny())) + .Returns(new ProcessResult() + { + ExitCode = 0, + Output = nugetPath + }); + + processExecutorMock.Setup(x => x.Start(CProgramFilesX86MicrosoftVisualStudio, It.Is((p) => p.EndsWith("where.exe")), + "-latest -requires Microsoft.Component.MSBuild -products * -find MSBuild\\**\\Bin\\MSBuild.exe", null, It.IsAny())) + .Returns(new ProcessResult() + { + ExitCode = 0, + Output = CProgramFilesX86MicrosoftVisualStudio + }); + processExecutorMock.Setup(x => x.Start(CProgramFilesX86MicrosoftVisualStudio, It.Is((p) => p.EndsWith("where.exe")), + "-prerelease -requires Microsoft.Component.MSBuild -products * -find MSBuild\\**\\Bin\\MSBuild.exe", null, It.IsAny())) + .Returns(new ProcessResult() + { + ExitCode = 0, + Output = CProgramFilesX86MicrosoftVisualStudio + }); + + processExecutorMock.Setup(x => x.Start(_solutionDir, It.IsAny(), "-version /nologo", null, It.IsAny())) + .Returns(new ProcessResult() + { + ExitCode = 1, + Output = msBuildVersion + }); + + processExecutorMock.Setup(x => x.Start(nugetDirectory, nugetPath, + $"restore \"{Path.GetFullPath(SolutionPath)}\"", null, It.IsAny())) + .Returns(new ProcessResult() + { + ExitCode = 0, + Output = "Packages restored" + }); + processExecutorMock.Setup(x => x.Start(It.Is(s => s.Contains("Microsoft Visual Studio")), It.Is(s => s.Contains("vswhere.exe")), + @"-latest -requires Microsoft.Component.MSBuild -products * -find MSBuild\**\Bin\MSBuild.exe", null, It.IsAny())) .Returns(new ProcessResult() { ExitCode = 0, @@ -68,7 +192,7 @@ public void HappyFlow() }); var target = new NugetRestoreProcess(processExecutorMock.Object); - target.RestorePackages(solutionPath); + target.RestorePackages(SolutionPath); processExecutorMock.Verify( p => p.Start(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>(), 0), Times.Exactly(4)); @@ -79,14 +203,15 @@ public void NugetIsUsingSuppliedMsBuild() { Skip.IfNot(Environment.OSVersion.Platform == PlatformID.Win32NT, "DotnetFramework does not run on Unix"); - string nugetPath = @"C:\choco\bin\NuGet.exe"; - string msBuildVersion = "16.0.0"; - string msbuildPath = @$"{CProgramFilesX86MicrosoftVisualStudio}\2019\MSBuild\16.0\Bin\MSBuild.exe"; + var nugetPath = @"C:\choco\bin\NuGet.exe"; + var msBuildVersion = "16.0.0"; + var msbuildPath = @$"{CProgramFilesX86MicrosoftVisualStudio}\2019\MSBuild\16.0\Bin\MSBuild.exe"; - string capturedMsBuildPath = ""; + var capturedMsBuildPath = ""; + var nugetDirectory = Path.GetDirectoryName(nugetPath); var processExecutorMock = new Mock(MockBehavior.Strict); - processExecutorMock.Setup(x => x.Start(solutionDir, It.Is((p) => p.EndsWith("where.exe")), "nuget.exe", null, It.IsAny())) + processExecutorMock.Setup(x => x.Start(_solutionDir, It.Is((p) => p.EndsWith("where.exe")), "nuget.exe", null, It.IsAny())) .Returns(new ProcessResult() { ExitCode = 0, @@ -100,7 +225,7 @@ public void NugetIsUsingSuppliedMsBuild() ExitCode = 0, Output = CProgramFilesX86MicrosoftVisualStudio }); - processExecutorMock.Setup(x => x.Start(solutionDir, It.IsAny(), "-version /nologo", null, It.IsAny())) + processExecutorMock.Setup(x => x.Start(_solutionDir, It.IsAny(), "-version /nologo", null, It.IsAny())) .Callback>, int>(( path, application, arguments, environmentVariables, timeoutMs) => { @@ -112,7 +237,8 @@ public void NugetIsUsingSuppliedMsBuild() Output = msBuildVersion }); - processExecutorMock.Setup(x => x.Start(solutionDir, nugetPath, string.Format("restore \"{0}\" -MsBuildVersion \"{1}\"", Path.GetFullPath(solutionPath), msBuildVersion), null, It.IsAny())) + processExecutorMock.Setup(x => x.Start(nugetDirectory, nugetPath, + $"restore \"{Path.GetFullPath(SolutionPath)}\" -MsBuildVersion \"{msBuildVersion}\"", null, It.IsAny())) .Returns(new ProcessResult() { ExitCode = 0, @@ -128,11 +254,11 @@ public void NugetIsUsingSuppliedMsBuild() var target = new NugetRestoreProcess(processExecutorMock.Object); - target.RestorePackages(solutionPath, msbuildPath); + target.RestorePackages(SolutionPath, msbuildPath); processExecutorMock.Verify(x => x.Start(CProgramFilesX86MicrosoftVisualStudio, It.Is((p) => p.EndsWith("where.exe")), "-latest -requires Microsoft.Component.MSBuild -products * -find MSBuild\\**\\Bin\\MSBuild.exe", null, It.IsAny()), Times.Never); - processExecutorMock.Verify(x => x.Start(solutionDir, It.IsAny(), "-version /nologo", null, It.IsAny()), Times.Once); - processExecutorMock.Verify(x => x.Start(solutionDir, nugetPath, string.Format("restore \"{0}\" -MsBuildVersion \"{1}\"", Path.GetFullPath(solutionPath), msBuildVersion), null, It.IsAny()), Times.Once); + processExecutorMock.Verify(x => x.Start(_solutionDir, It.IsAny(), "-version /nologo", null, It.IsAny()), Times.Once); + processExecutorMock.Verify(x => x.Start(nugetDirectory, nugetPath, string.Format("restore \"{0}\" -MsBuildVersion \"{1}\"", Path.GetFullPath(SolutionPath), msBuildVersion), null, It.IsAny()), Times.Once); processExecutorMock.Verify(x => x.Start(It.Is(s => s.Contains("Microsoft Visual Studio")), It.Is(s => s.Contains("vswhere.exe")), @"-latest -requires Microsoft.Component.MSBuild -products * -find MSBuild\**\Bin\MSBuild.exe", null, It.IsAny()), Times.Never); Assert.Equal(msbuildPath, capturedMsBuildPath); } @@ -143,7 +269,31 @@ public void ShouldThrowOnNugetNotInstalled() Skip.IfNot(Environment.OSVersion.Platform == PlatformID.Win32NT, "DotnetFramework does not run on Unix"); var processExecutorMock = new Mock(MockBehavior.Strict); - processExecutorMock.Setup(x => x.Start(solutionDir, "where.exe", "nuget.exe", null, It.IsAny())) + var msBuildVersion = "16.0.0"; + + var capturedMsBuildPath = ""; + + processExecutorMock.Setup(x => x.Start(CProgramFilesX86MicrosoftVisualStudio, It.Is((p) => p.EndsWith("where.exe")), + It.Is(s => s.EndsWith("-requires Microsoft.Component.MSBuild -products * -find MSBuild\\**\\Bin\\MSBuild.exe")), null, It.IsAny())) + .Returns(new ProcessResult() + { + ExitCode = 0, + Output = CProgramFilesX86MicrosoftVisualStudio + }); + processExecutorMock.Setup(x => x.Start(_solutionDir, It.IsAny(), "-version /nologo", null, It.IsAny())) + .Callback>, int>(( + path, application, arguments, environmentVariables, timeoutMs) => + { + capturedMsBuildPath = application; + }) + .Returns(new ProcessResult() + { + ExitCode = 0, + Output = msBuildVersion + }); + + processExecutorMock.Setup(x => x.Start(_solutionDir, "where.exe", It.Is( s => s.EndsWith("nuget.exe")), + null, It.IsAny())) .Returns(new ProcessResult() { ExitCode = 0, @@ -151,8 +301,7 @@ public void ShouldThrowOnNugetNotInstalled() }); var target = new NugetRestoreProcess(processExecutorMock.Object); - - Should.Throw(() => target.RestorePackages(solutionPath) ); + Should.Throw(() => target.RestorePackages(SolutionPath) ); } [SkippableFact] @@ -162,9 +311,10 @@ public void ShouldPickFirstNugetPath() string firstNugetPath = @"C:\choco\bin\NuGet.exe"; string msBuildVersion = "16.0.0"; + var nugetDirectory = Path.GetDirectoryName(firstNugetPath); var processExecutorMock = new Mock(MockBehavior.Strict); - processExecutorMock.Setup(x => x.Start(solutionDir, "where.exe", "nuget.exe", null, It.IsAny())) + processExecutorMock.Setup(x => x.Start(_solutionDir, "where.exe", "nuget.exe", null, It.IsAny())) .Returns(new ProcessResult() { ExitCode = 0, @@ -188,14 +338,15 @@ public void ShouldPickFirstNugetPath() Output = CProgramFilesX86MicrosoftVisualStudio }); - processExecutorMock.Setup(x => x.Start(solutionDir, It.IsAny(), "-version /nologo", null, It.IsAny())) + processExecutorMock.Setup(x => x.Start(_solutionDir, It.IsAny(), "-version /nologo", null, It.IsAny())) .Returns(new ProcessResult() { ExitCode = 0, Output = msBuildVersion }); - processExecutorMock.Setup(x => x.Start(solutionDir, firstNugetPath, string.Format("restore \"{0}\" -MsBuildVersion \"{1}\"", Path.GetFullPath(solutionPath), msBuildVersion), null, It.IsAny())) + processExecutorMock.Setup(x => x.Start(nugetDirectory, firstNugetPath, + $"restore \"{Path.GetFullPath(SolutionPath)}\" -MsBuildVersion \"{msBuildVersion}\"", null, It.IsAny())) .Returns(new ProcessResult() { ExitCode = 0, @@ -211,7 +362,7 @@ public void ShouldPickFirstNugetPath() var target = new NugetRestoreProcess(processExecutorMock.Object); - target.RestorePackages(solutionPath); + target.RestorePackages(SolutionPath); processExecutorMock.Verify( p => p.Start(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>(), 0), Times.Exactly(4)); } diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/ProjectOrchestratorTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/ProjectOrchestratorTests.cs index 139ae9d291..04dc291ad6 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/ProjectOrchestratorTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/ProjectOrchestratorTests.cs @@ -70,7 +70,7 @@ public void ShouldInitializeEachProjectInSolution() public void ShouldPassWhenProjectNameIsGiven() { // arrange - // when a solutionPath is given and it's inside the current directory (basePath) + // when a solutionPath is given, and it's inside the current directory (basePath) var csprojPathName = _fileSystem.Path.Combine(_projectPath, "sourceproject.csproj"); var testCsprojPathName = _fileSystem.Path.Combine(_projectPath, "test", "testproject.csproj"); var options = new StrykerOptions @@ -90,6 +90,31 @@ public void ShouldPassWhenProjectNameIsGiven() result.ShouldHaveSingleItem(); } + [Fact] + public void ShouldRestoreWhenAnalysisFails() + { + // arrange + // when a solutionPath is given, and it's inside the current directory (basePath) + var csprojPathName = _fileSystem.Path.Combine(_projectPath, "sourceproject.csproj"); + var testCsprojPathName = _fileSystem.Path.Combine(_projectPath, "test", "testproject.csproj"); + var options = new StrykerOptions + { + ProjectPath = _fileSystem.Path.GetFullPath(testCsprojPathName), + SourceProjectName = csprojPathName, + SolutionPath = _fileSystem.Path.Combine(_projectPath, "MySolution.sln") + }; + + var csPathName = _fileSystem.Path.Combine(_projectPath, "someFile.cs"); + var target = BuildProjectOrchestratorForSimpleProject(SourceProjectAnalyzerMock(csprojPathName, new[] { csPathName }).Object, + TestProjectAnalyzerMock(testCsprojPathName, csprojPathName, "net4.5", false).Object, out var mockRunner); + + // act + var result = target.MutateProjects(options, _reporterMock.Object, mockRunner.Object).ToList(); + + // assert + result.ShouldHaveSingleItem(); + } + [Fact] public void ShouldFailIfNoProjectFound() { @@ -330,7 +355,7 @@ private ProjectOrchestrator BuildProjectOrchestrator(Dictionary().Object, _buildalyzerProviderMock.Object))); var buildalyzerAnalyzerManagerMock = new Mock(MockBehavior.Strict); buildalyzerAnalyzerManagerMock.Setup(x => x.Projects) @@ -361,7 +386,7 @@ private ProjectOrchestrator BuildProjectOrchestrator(Dictionary /// project pathname /// project source files - /// project cross references + /// project references private Mock SourceProjectAnalyzerMock(string csprojPathName, string[] sourceFiles, IEnumerable projectReferences = null) { var properties = new Dictionary @@ -376,13 +401,15 @@ private Mock SourceProjectAnalyzerMock(string csprojPathName, /// /// test project pathname /// production code project pathname + /// + /// /// a mock project analyzer /// the test project references the production code project and contains no source file - private Mock TestProjectAnalyzerMock(string testCsprojPathName, string csprojPathName) + private Mock TestProjectAnalyzerMock(string testCsprojPathName, string csprojPathName, string framework = "net6.0", bool success = true) { var properties = new Dictionary{ { "IsTestProject", "True" }, { "Language", "C#" } }; - return BuildProjectAnalyzerMock(testCsprojPathName, Array.Empty(), properties, new List { csprojPathName }); + return BuildProjectAnalyzerMock(testCsprojPathName, Array.Empty(), properties, new List { csprojPathName }, framework, success); } /// @@ -391,13 +418,17 @@ private Mock TestProjectAnalyzerMock(string testCsprojPathName /// project file name /// source files to return /// project properties - /// project cross references + /// project references + /// analysis success /// a mock project analyzer /// /// 1. project and source files will be created (empty) in the file system /// 2. the project analyzer mock returns a single project result - - private Mock BuildProjectAnalyzerMock(string csprojPathName, string[] sourceFiles, Dictionary properties, IEnumerable projectReferences) + private Mock BuildProjectAnalyzerMock(string csprojPathName, + string[] sourceFiles, Dictionary properties, + IEnumerable projectReferences, + string framework = "net6.0", + bool success = true) { var sourceProjectAnalyzerMock = new Mock(MockBehavior.Strict); var sourceProjectAnalyzerResultsMock = new Mock(MockBehavior.Strict); @@ -428,8 +459,8 @@ private Mock BuildProjectAnalyzerMock(string csprojPathName, s sourceProjectAnalyzerResultMock.Setup(x => x.Properties).Returns(properties); sourceProjectAnalyzerResultMock.Setup(x => x.ProjectFilePath).Returns(csprojPathName); - sourceProjectAnalyzerResultMock.Setup(x => x.TargetFramework).Returns("net6.0"); - sourceProjectAnalyzerResultMock.Setup(x => x.Succeeded).Returns(true); + sourceProjectAnalyzerResultMock.Setup(x => x.TargetFramework).Returns(framework); + sourceProjectAnalyzerResultMock.Setup(x => x.Succeeded).Returns(success); IEnumerable analyzerResults = new[] { sourceProjectAnalyzerResultMock.Object }; sourceProjectAnalyzerResultsMock.Setup(x => x.Results).Returns(analyzerResults); diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/CoverageCollectorTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/CoverageCollectorTests.cs index 8539aa2519..6bb6fa889b 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/CoverageCollectorTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/CoverageCollectorTests.cs @@ -80,7 +80,6 @@ public void SelectMutantEarlyIfSingle() var collector = new CoverageCollector(); var testCase = new TestCase("theTest", new Uri("xunit://"), "source.cs"); - var nonCoveringTestCase = new TestCase("theOtherTest", new Uri("xunit://"), "source.cs"); var mutantMap = new List<(int, IEnumerable)> {(5, new List{testCase.Id})}; var start = new TestSessionStartArgs diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/VsTestMockingHelper.cs b/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/VsTestMockingHelper.cs index 4375f25645..5759f86233 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/VsTestMockingHelper.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/VsTestMockingHelper.cs @@ -16,7 +16,6 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollector.InProcDataCollector; using Moq; -using Moq.Language.Flow; using Stryker.Core.Initialisation; using Stryker.Core.Mutants; using Stryker.Core.MutationTest; @@ -130,18 +129,19 @@ protected TestProjectsInfo BuildTestProjectsInfo() => protected IReadOnlyList TestCases { get; } - private static Task DiscoverTests(ITestDiscoveryEventsHandler discoveryEventsHandler, IReadOnlyCollection tests, bool aborted) => - Task.Run(() => discoveryEventsHandler.HandleDiscoveredTests(tests)). - ContinueWith((_, u) => discoveryEventsHandler.HandleDiscoveryComplete((int)u, null, aborted), tests.Count); + private static void DiscoverTests(ITestDiscoveryEventsHandler discoveryEventsHandler, IReadOnlyCollection tests, bool aborted) + { + discoveryEventsHandler.HandleDiscoveredTests(tests); + discoveryEventsHandler.HandleDiscoveryComplete(tests.Count, null, aborted); + } protected Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase BuildCase(string name, TestFrameworks framework = TestFrameworks.xUnit, string displayName = null) => new(name, framework == TestFrameworks.xUnit ? _xUnitUri : _NUnitUri, _testAssemblyPath) { Id = new Guid(), DisplayName = displayName ?? name }; - private Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase FindOrBuildCase(string testResultId) => TestCases.FirstOrDefault(t => t.FullyQualifiedName == testResultId) ?? BuildCase(testResultId); + protected Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase FindOrBuildCase(string testResultId) => TestCases.FirstOrDefault(t => t.FullyQualifiedName == testResultId) ?? BuildCase(testResultId); - private static Task MockTestRun(ITestRunEventsHandler testRunEvents, IReadOnlyList testResults, - Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase timeOutTest = null) => - Task.Run(() => + private static void MockTestRun(ITestRunEventsHandler testRunEvents, IReadOnlyList testResults, + Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase timeOutTest = null) { if (testResults.Count == 0) { @@ -174,7 +174,7 @@ private static Task MockTestRun(ITestRunEventsHandler testRunEvents, IReadOnlyLi new TestRunChangedEventArgs(null, Array.Empty(), new List()), null, null); - }); + } protected void SetupMockTestRun(Mock mockVsTest, bool testResult, IReadOnlyList testCases) { @@ -200,25 +200,25 @@ protected void SetupMockTestRun(Mock mockVsTest, IEnumera SetupMockTestRun(mockVsTest, results); } - protected IReturnsResult SetupMockTestRun(Mock mockVsTest, IReadOnlyList results) => + protected void SetupMockTestRun(Mock mockVsTest, IReadOnlyList results) => mockVsTest.Setup(x => - x.RunTestsWithCustomTestHostAsync( + x.RunTestsWithCustomTestHost( It.Is>(t => t.Any(source => source == _testAssemblyPath)), It.Is(settings => !settings.Contains("(o => o != null && o.TestCaseFilter == null), It.IsAny(), - It.IsAny())).Returns( + It.IsAny())).Callback( (IEnumerable _, string _, TestPlatformOptions _, ITestRunEventsHandler testRunEvents, ITestHostLauncher _) => MockTestRun(testRunEvents, results)); protected void SetupFailingTestRun(Mock mockVsTest) => mockVsTest.Setup(x => - x.RunTestsWithCustomTestHostAsync( + x.RunTestsWithCustomTestHost( It.Is>(t => t.Any(source => source == _testAssemblyPath)), It.Is(settings => !settings.Contains("(o => o != null && o.TestCaseFilter == null), It.IsAny(), - It.IsAny())).Returns( + It.IsAny())).Callback( (IEnumerable _, string _, TestPlatformOptions _, ITestRunEventsHandler testRunEvents, ITestHostLauncher _) => // generate test results @@ -239,14 +239,15 @@ protected void SetupFailingTestRun(Mock mockVsTest) => null, null); })); + protected void SetupFrozenTestRun(Mock mockVsTest, int repeated = 1) => mockVsTest.Setup(x => - x.RunTestsWithCustomTestHostAsync( + x.RunTestsWithCustomTestHost( It.Is>(t => t.Any(source => source == _testAssemblyPath)), It.Is(settings => !settings.Contains("(o => o != null && o.TestCaseFilter == null), It.IsAny(), - It.IsAny())).Returns( + It.IsAny())).Callback( (IEnumerable _, string _, TestPlatformOptions _, ITestRunEventsHandler testRunEvents, ITestHostLauncher _) => // generate test results @@ -276,18 +277,18 @@ protected void SetupFrozenTestRun(Mock mockVsTest, int re null); } })); + protected void SetupFrozenVsTest(Mock mockVsTest, int repeated = 1) => mockVsTest.Setup(x => - x.RunTestsWithCustomTestHostAsync( + x.RunTestsWithCustomTestHost( It.Is>(t => t.Any(source => source == _testAssemblyPath)), It.Is(settings => !settings.Contains("(o => o != null && o.TestCaseFilter == null), It.IsAny(), - It.IsAny())).Returns( + It.IsAny())).Callback( (IEnumerable _, string _, TestPlatformOptions _, ITestRunEventsHandler testRunEvents, ITestHostLauncher _) => // generate test results - Task.Run(() => { testRunEvents.HandleTestRunStatsChange( new TestRunChangedEventArgs(new TestRunStatistics(0, null), null, null)); @@ -303,18 +304,18 @@ protected void SetupFrozenVsTest(Mock mockVsTest, int rep if (repeated-->0) Thread.Sleep(1000); - })); + }); protected void SetupMockCoverageRun(Mock mockVsTest, IReadOnlyDictionary coverageResults) => SetupMockCoverageRun(mockVsTest, GenerateCoverageTestResults(coverageResults)); protected void SetupMockCoverageRun(Mock mockVsTest, IReadOnlyList results) => mockVsTest.Setup(x => - x.RunTestsWithCustomTestHostAsync( + x.RunTestsWithCustomTestHost( It.Is>(t => t.Any(source => source == _testAssemblyPath)), It.Is(settings => settings.Contains("(o => o != null && o.TestCaseFilter == null), It.IsAny(), - It.IsAny())).Returns( + It.IsAny())).Callback( (IEnumerable _, string _, TestPlatformOptions _, ITestRunEventsHandler testRunEvents, ITestHostLauncher _) => MockTestRun(testRunEvents, results)); @@ -347,12 +348,12 @@ private List GenerateCoverageTestResults(IReadOnlyDictionary mockVsTest, IReadOnlyDictionary coverageResults) => mockVsTest.Setup(x => - x.RunTestsWithCustomTestHostAsync( + x.RunTestsWithCustomTestHost( It.Is>(t => t.Any()), It.Is(settings => settings.Contains("(o => o != null && o.TestCaseFilter == null), It.IsAny(), - It.IsAny())).Returns( + It.IsAny())).Callback( (IEnumerable testCases, string _, TestPlatformOptions _, ITestRunEventsHandler testRunEvents, ITestHostLauncher _) => { @@ -368,7 +369,7 @@ protected void SetupMockCoveragePerTestRun(Mock mockVsTes var result = BuildCoverageTestResult(key, coveredList); results.Add(result); } - return MockTestRun(testRunEvents, results); + MockTestRun(testRunEvents, results); }); protected TestResult BuildCoverageTestResult(string key, string[] coveredList) @@ -390,14 +391,14 @@ protected TestResult BuildCoverageTestResult(string key, string[] coveredList) protected static void SetupMockPartialTestRun(Mock mockVsTest, IReadOnlyDictionary results) => mockVsTest.Setup(x => - x.RunTestsWithCustomTestHostAsync( + x.RunTestsWithCustomTestHost( It.IsAny>(), It.Is(s => !s.Contains("(o => o != null && o.TestCaseFilter == null), It.IsAny(), - It.IsAny())).Returns( + It.IsAny())).Callback( (IEnumerable sources, string settings, TestPlatformOptions _, ITestRunEventsHandler testRunEvents, - ITestHostLauncher _) => Task.Run(()=> + ITestHostLauncher _) => { var collector = new CoverageCollector(); var start = new TestSessionStartArgs @@ -435,20 +436,20 @@ protected static void SetupMockPartialTestRun(Mock mockVs runResults.Add(result); } // setup a normal test run - MockTestRun(testRunEvents, runResults).Wait(); + MockTestRun(testRunEvents, runResults); collector.TestSessionEnd(new TestSessionEndArgs()); - })); + }); protected static void SetupMockTimeOutTestRun(Mock mockVsTest, IReadOnlyDictionary results, string timeoutTest) => mockVsTest.Setup(x => - x.RunTestsWithCustomTestHostAsync( + x.RunTestsWithCustomTestHost( It.IsAny>(), It.IsAny(), It.Is(o => o != null && o.TestCaseFilter == null), It.IsAny(), - It.IsAny())).Returns( + It.IsAny())).Callback( (IEnumerable sources, string settings, TestPlatformOptions _, ITestRunEventsHandler testRunEvents, - ITestHostLauncher _) => Task.Run(()=> + ITestHostLauncher _) => { var collector = new CoverageCollector(); var start = new TestSessionStartArgs @@ -490,10 +491,10 @@ protected static void SetupMockTimeOutTestRun(Mock mockVs runResults.Add(result); } // setup a normal test run - MockTestRun(testRunEvents, runResults, timeOutTestCase).Wait(); + MockTestRun(testRunEvents, runResults, timeOutTestCase); collector.TestSessionEnd(new TestSessionEndArgs()); - })); + }); protected Mock BuildVsTestRunnerPool(StrykerOptions options, out VsTestRunnerPool runner, IReadOnlyCollection testCases = null, TestProjectsInfo testProjectsInfo = null) @@ -504,14 +505,12 @@ protected Mock BuildVsTestRunnerPool(StrykerOptions optio mockedVsTestConsole.Setup(x => x.InitializeExtensions(It.IsAny>())); mockedVsTestConsole.Setup(x => x.AbortTestRun()); mockedVsTestConsole.Setup(x => x.EndSession()); - ITestDiscoveryEventsHandler discoveryHandler = null; + mockedVsTestConsole.Setup(x => - x.DiscoverTestsAsync(It.Is>(d => d.Any(e => e == _testAssemblyPath)), + x.DiscoverTests(It.Is>(d => d.Any(e => e == _testAssemblyPath)), It.IsAny(), - It.IsAny())).Callback( - (IEnumerable _, string _, ITestDiscoveryEventsHandler handler) => - discoveryHandler = handler).Returns(() => DiscoverTests(discoveryHandler, testCases, false)); - + It.IsAny())) + .Callback((IEnumerable _, string _, ITestDiscoveryEventsHandler handler) => DiscoverTests(handler, testCases, false)); var context = new VsTestContextInformation( options, new Mock().Object, @@ -561,5 +560,4 @@ private class MockStrykerTestHostLauncher : IStrykerTestHostLauncher public int ErrorCode { get; } } - } diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/VsTestRunnerPoolTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/VsTestRunnerPoolTests.cs index c72aa0406b..cd9792cb3c 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/VsTestRunnerPoolTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/VsTestRunnerPoolTests.cs @@ -10,6 +10,7 @@ using Stryker.Core.Initialisation; using Stryker.Core.Mutants; using Stryker.Core.Options; +using Stryker.Core.TestRunners; using Stryker.Core.TestRunners.VsTest; using Xunit; @@ -147,7 +148,7 @@ public void RecycleRunnerOnError() var action = () => runner.TestMultipleMutants(SourceProjectInfo, null, new[] { Mutant }, null); action.ShouldThrow(); // the test will always end in a crash, VsTestRunner should retry at least a few times - mockVsTest.Verify(m => m.RunTestsWithCustomTestHostAsync(It.IsAny>(), + mockVsTest.Verify(m => m.RunTestsWithCustomTestHost(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.AtLeast(3)); @@ -189,11 +190,11 @@ public void ShouldRetryFrozenSession() var mockVsTest = BuildVsTestRunnerPool(new StrykerOptions(), out var runner); var defaultTimeOut = VsTestRunner.VsTestExtraTimeOutInMs; VsTestRunner.VsTestExtraTimeOutInMs = 100; - // the test session will hung twice + // the test session will freeze twice SetupFrozenTestRun(mockVsTest, 2); runner.TestMultipleMutants(SourceProjectInfo, new TimeoutValueCalculator(0, 10,9), new[] { Mutant }, null); VsTestRunner.VsTestExtraTimeOutInMs = defaultTimeOut; - mockVsTest.Verify(m => m.RunTestsWithCustomTestHostAsync(It.IsAny>(), + mockVsTest.Verify(m => m.RunTestsWithCustomTestHost(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(3)); @@ -205,7 +206,7 @@ public void ShouldNotRetryFrozenVsTest() var mockVsTest = BuildVsTestRunnerPool(new StrykerOptions(), out var runner); var defaultTimeOut = VsTestRunner.VsTestExtraTimeOutInMs; // the test session will end properly, but VsTest will hang - // it will be recyled + // it will be recycled SetupFrozenVsTest(mockVsTest, 3); VsTestRunner.VsTestExtraTimeOutInMs = 100; runner.TestMultipleMutants(SourceProjectInfo, new TimeoutValueCalculator(0, 10,9), new[] { Mutant }, null); @@ -575,7 +576,7 @@ public void MarkSuspiciousCoverageInPresenceOfFailedTests() } // this verifies that tests missing any coverage information are - // flagged as to be tested used against every mutants + // flagged as to be tested used against every mutant [Fact] public void MarkSuspiciousTests() { @@ -619,6 +620,35 @@ public void HandleNonCoveringTests() Mutant.CoveringTests.Count.ShouldBe(1); } + // this verifies extra test results (without any coverage info) are properly handled + // are properly handled + [Fact] + public void HandleExtraTestResult() + { + var options = new StrykerOptions + { + OptimizationMode = OptimizationModes.CoverageBasedTest + }; + + var mockVsTest = BuildVsTestRunnerPool(options, out var runner); + + var testResult = BuildCoverageTestResult("T0", new[] { "0;", "" }); + var other = new TestResult(FindOrBuildCase("T0")) + { + DisplayName = "T0", + Outcome = TestOutcome.Passed, + ComputerName = "." + }; + SetupMockCoverageRun(mockVsTest, new[] { testResult, other }); + + + var analyzer = new CoverageAnalyser(options); + analyzer.DetermineTestCoverage(SourceProjectInfo, runner, new[] { Mutant, OtherMutant }, TestGuidsList.NoTest()); + + OtherMutant.CoveringTests.Count.ShouldBe(0); + Mutant.CoveringTests.Count.ShouldBe(1); + } + // this verifies that unexpected test case (i.e. unseen during test discovery and without coverage info) // are assumed to cover every mutant [Fact] diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/VsTextContextInformationTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/VsTextContextInformationTests.cs index 763eb71571..7c1ae1e745 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/VsTextContextInformationTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/TestRunners/VsTextContextInformationTests.cs @@ -111,11 +111,11 @@ private VsTestContextInformation BuildVsTextContext(StrykerOptions options, out mockedVsTestConsole.Setup(x => x.InitializeExtensions(It.IsAny>())); mockedVsTestConsole.Setup(x => x.EndSession()); mockedVsTestConsole.Setup(x => - x.DiscoverTestsAsync(It.Is>(d => d.Any(e => e == _testAssemblyPath)), + x.DiscoverTests(It.Is>(d => d.Any(e => e == _testAssemblyPath)), It.IsAny(), It.IsAny())).Callback( (IEnumerable _, string _, ITestDiscoveryEventsHandler discoveryEventsHandler) => - DiscoverTests(discoveryEventsHandler, TestCases, false)).Returns(Task.CompletedTask); + DiscoverTests(discoveryEventsHandler, TestCases, false)); var vsTestConsoleWrapper = mockedVsTestConsole.Object; return new VsTestContextInformation( diff --git a/src/Stryker.Core/Stryker.Core/Initialisation/Buildalyzer/IAnalyzerResultExtensions.cs b/src/Stryker.Core/Stryker.Core/Initialisation/Buildalyzer/IAnalyzerResultExtensions.cs index 3a410117c8..d491def546 100644 --- a/src/Stryker.Core/Stryker.Core/Initialisation/Buildalyzer/IAnalyzerResultExtensions.cs +++ b/src/Stryker.Core/Stryker.Core/Initialisation/Buildalyzer/IAnalyzerResultExtensions.cs @@ -125,7 +125,12 @@ internal static NuGetFramework GetNuGetFramework(this IAnalyzerResult analyzerRe throw new InputException(message); } - internal static bool TargetsFullFramework(this IAnalyzerResult analyzerResult) => GetNuGetFramework(analyzerResult).IsDesktop(); + internal static bool TargetsFullFramework(this IAnalyzerResult analyzerResult) + { + var nuGetFramework = GetNuGetFramework(analyzerResult); + + return nuGetFramework.IsDesktop() && (nuGetFramework.Version.Major<4 || nuGetFramework.Version is { Major: 4, Minor: < 8 }); + } public static Language GetLanguage(this IAnalyzerResult analyzerResult) => analyzerResult.GetPropertyOrDefault("Language") switch { diff --git a/src/Stryker.Core/Stryker.Core/Initialisation/InitialBuildProcess.cs b/src/Stryker.Core/Stryker.Core/Initialisation/InitialBuildProcess.cs index 7156581e5b..8afdb68b67 100644 --- a/src/Stryker.Core/Stryker.Core/Initialisation/InitialBuildProcess.cs +++ b/src/Stryker.Core/Stryker.Core/Initialisation/InitialBuildProcess.cs @@ -47,7 +47,7 @@ public void InitialBuild(bool fullFramework, string projectPath, string solution _logger.LogDebug("Initial build using path: {buildPath}", buildPath); // Build with dotnet build - result = _processExecutor.Start(projectPath, "dotnet", $"build \"{buildPath}\""); + result = _processExecutor.Start(Path.GetDirectoryName(projectPath), "dotnet", $"build \"{buildPath}\""); if (result.ExitCode!=ExitCodes.Success && !string.IsNullOrEmpty(solutionPath)) { _logger.LogWarning("Dotnet build failed, trying with MsBuild."); @@ -67,6 +67,12 @@ private ProcessResult BuildSolutionWithMsBuild(ref string solutionPath, ref stri // Build project with MSBuild.exe var result = _processExecutor.Start(solutionDir, msbuildPath, $"\"{solutionPath}\""); + if (result.ExitCode != ExitCodes.Success) + { + _logger.LogWarning("MsBuild failed to build the solution, trying to restore packages and build again."); + _processExecutor.Start(solutionDir, msbuildPath, $"\"{solutionPath}\" -t:restore -p:RestorePackagesConfig=true"); + result = _processExecutor.Start(solutionDir, msbuildPath, $"\"{solutionPath}\""); + } return result; } diff --git a/src/Stryker.Core/Stryker.Core/Initialisation/InputFileResolver.cs b/src/Stryker.Core/Stryker.Core/Initialisation/InputFileResolver.cs index 8b4d23a227..a16a3bb06a 100644 --- a/src/Stryker.Core/Stryker.Core/Initialisation/InputFileResolver.cs +++ b/src/Stryker.Core/Stryker.Core/Initialisation/InputFileResolver.cs @@ -60,7 +60,8 @@ public IReadOnlyCollection ResolveSourceProjectInfos(StrykerO testProjectFileNames = new List { FindTestProject(options.ProjectPath) }; } - var testProjects = testProjectFileNames.Select(testProjectFile => _projectFileReader.AnalyzeProject(testProjectFile, options.SolutionPath, options.TargetFramework)).ToList(); + var testProjects = testProjectFileNames.Select(testProjectFile => + _projectFileReader.AnalyzeProject(testProjectFile, options.SolutionPath, options.TargetFramework)).ToList(); var analyzerResult = _projectFileReader.AnalyzeProject(FindSourceProject(testProjects, options), options.SolutionPath, options.TargetFramework); @@ -147,12 +148,13 @@ private List AnalyzeSolution(StrykerOptions options) _logger.LogDebug("Analyzing {count} projects.", manager.Projects.Count); try { - Parallel.ForEach(manager.Projects.Values, project => + Parallel.ForEach(manager.Projects.Values, new ParallelOptions{MaxDegreeOfParallelism = options.DevMode ? 1 : Math.Max(options.Concurrency,1)}, project => { var projectLogName = Path.GetRelativePath(options.WorkingDirectory, project.ProjectFile.Path); _logger.LogDebug("Analyzing {projectFilePath}", projectLogName); var buildResult = project.Build(); - var projectAnalyzerResult = buildResult.Results.FirstOrDefault(a => a.TargetFramework is not null); + + var projectAnalyzerResult = _projectFileReader.GetAnalyzerResult(buildResult, options.TargetFramework); if (projectAnalyzerResult is not null) { projectsAnalyzerResults.Add(projectAnalyzerResult); diff --git a/src/Stryker.Core/Stryker.Core/Initialisation/NugetRestoreProcess.cs b/src/Stryker.Core/Stryker.Core/Initialisation/NugetRestoreProcess.cs index 3a54903273..740082a17d 100644 --- a/src/Stryker.Core/Stryker.Core/Initialisation/NugetRestoreProcess.cs +++ b/src/Stryker.Core/Stryker.Core/Initialisation/NugetRestoreProcess.cs @@ -19,12 +19,12 @@ public interface INugetRestoreProcess /// public class NugetRestoreProcess : INugetRestoreProcess { - private IProcessExecutor _processExecutor { get; set; } - private ILogger _logger { get; set; } + private IProcessExecutor ProcessExecutor { get; set; } + private readonly ILogger _logger; public NugetRestoreProcess(IProcessExecutor processExecutor = null) { - _processExecutor = processExecutor ?? new ProcessExecutor(); + ProcessExecutor = processExecutor ?? new ProcessExecutor(); _logger = ApplicationLogging.LoggerFactory.CreateLogger(); } @@ -36,36 +36,52 @@ public void RestorePackages(string solutionPath, string msbuildPath = null) throw new InputException("Solution path is required on .net framework projects. Please supply the solution path."); } solutionPath = Path.GetFullPath(solutionPath); - string solutionDir = Path.GetDirectoryName(solutionPath); + var solutionDir = Path.GetDirectoryName(solutionPath); + + + // Locate MSBuild.exe + msbuildPath ??= new MsBuildHelper().GetMsBuildPath(ProcessExecutor); + var msBuildVersionOutput = ProcessExecutor.Start(solutionDir, msbuildPath, "-version /nologo"); + string msBuildVersion; + if (msBuildVersionOutput.ExitCode != ExitCodes.Success) + { + msBuildVersion = string.Empty; + _logger.LogDebug("Auto detected msbuild at: {0}, but failed to get version.", msbuildPath); + } + else + { + msBuildVersion = msBuildVersionOutput.Output.Trim(); + _logger.LogDebug("Auto detected msbuild version {0} at: {1}", msBuildVersion, msbuildPath); + } // Validate nuget.exe is installed and included in path - var nugetWhereExeResult = _processExecutor.Start(solutionDir, "where.exe", "nuget.exe"); + var nugetWhereExeResult = ProcessExecutor.Start(solutionDir, "where.exe", "nuget.exe"); if (!nugetWhereExeResult.Output.ToLowerInvariant().Contains("nuget.exe")) { - throw new InputException("Nuget.exe should be installed to restore .net framework nuget packages. Install nuget.exe and make sure it's included in your path."); + // try to extend the search + nugetWhereExeResult = ProcessExecutor.Start(solutionDir, "where.exe", $"/R {Path.GetPathRoot(msbuildPath)} nuget.exe"); + + if (!nugetWhereExeResult.Output.ToLowerInvariant().Contains("nuget.exe")) + throw new InputException("Nuget.exe should be installed to restore .net framework nuget packages. Install nuget.exe and make sure it's included in your path."); } // Get the first nuget.exe path from the where.exe output var nugetPath = nugetWhereExeResult.Output.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).First().Trim(); - // Locate MSBuild.exe - msbuildPath ??= new MsBuildHelper().GetMsBuildPath(_processExecutor); - var msBuildVersionOutput = _processExecutor.Start(solutionDir, msbuildPath, "-version /nologo"); - if (msBuildVersionOutput.ExitCode != ExitCodes.Success) - { - _logger.LogError("Unable to detect msbuild version"); - } - var msBuildVersion = msBuildVersionOutput.Output.Trim(); - _logger.LogDebug("Auto detected msbuild version {0} at: {1}", msBuildVersion, msbuildPath); // Restore packages using nuget.exe - var nugetRestoreCommand = string.Format("restore \"{0}\" -MsBuildVersion \"{1}\"", solutionPath, msBuildVersion); + var nugetRestoreCommand = $"restore \"{solutionPath}\""; + if (!string.IsNullOrEmpty(msBuildVersion)) + { + nugetRestoreCommand += $" -MsBuildVersion \"{msBuildVersion}\""; + } _logger.LogDebug("Restoring packages using command: {0} {1}", nugetPath, nugetRestoreCommand); try { - var nugetRestoreResult = _processExecutor.Start(solutionDir, nugetPath, nugetRestoreCommand, timeoutMs: 120000); + var nugetRestoreResult = ProcessExecutor.Start(Path.GetDirectoryName(nugetPath), nugetPath, nugetRestoreCommand, timeoutMs: 120000); if (nugetRestoreResult.ExitCode != ExitCodes.Success) { + _logger.LogCritical($"Failed to restore nuget packages. Nuget error: {nugetRestoreResult.Error}"); throw new InputException("Nuget.exe failed to restore packages for your solution. Please review your nuget setup.", nugetRestoreResult.Output); } _logger.LogDebug("Restored packages using nuget.exe, output: {0}", nugetRestoreResult.Output); diff --git a/src/Stryker.Core/Stryker.Core/Initialisation/ProjectFileReader.cs b/src/Stryker.Core/Stryker.Core/Initialisation/ProjectFileReader.cs index c7c6e5e344..42685f003d 100644 --- a/src/Stryker.Core/Stryker.Core/Initialisation/ProjectFileReader.cs +++ b/src/Stryker.Core/Stryker.Core/Initialisation/ProjectFileReader.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -17,6 +18,8 @@ IAnalyzerResult AnalyzeProject(string projectFilePath, string targetFramework, string msBuildPath = null); IAnalyzerManager GetAnalyzerManager(string solutionFilePath = null); + + IAnalyzerResult GetAnalyzerResult(IAnalyzerResults results, string targetFramework); } /// @@ -51,8 +54,7 @@ public IAnalyzerResult AnalyzeProject(string projectFilePath, string msBuildPath = null) { _logger.LogDebug("Analyzing project file {0}", projectFilePath); - var analyzerResult = GetProjectInfo(projectFilePath, solutionFilePath, targetFramework); - LogAnalyzerResult(analyzerResult); + var analyzerResult = GetAnalyzerResult(GetAnalyzerManager(solutionFilePath).GetProject(projectFilePath).Build(), targetFramework); if (analyzerResult.Succeeded) { @@ -63,28 +65,21 @@ public IAnalyzerResult AnalyzeProject(string projectFilePath, // buildalyzer failed to find restored packages, retry after nuget restore _logger.LogDebug("Project analyzer result not successful, restoring packages"); _nugetRestoreProcess.RestorePackages(solutionFilePath, msBuildPath); - analyzerResult = GetProjectInfo(projectFilePath, solutionFilePath, targetFramework); - } - else - { - // buildalyzer failed, but seems to work anyway. - _logger.LogDebug("Project analyzer result not successful"); + analyzerResult = GetAnalyzerResult(GetAnalyzerManager(solutionFilePath).GetProject(projectFilePath).Build(), targetFramework); } return analyzerResult; } - /// - /// Checks if project info is already present in solution projects. If not, analyze here. - /// - /// - private IAnalyzerResult GetProjectInfo( - string projectFilePath, - string solutionFilePath, - string targetFramework) + public IAnalyzerResult GetAnalyzerResult(IAnalyzerResults results, string targetFramework) { - var analyzerResults = GetAnalyzerManager(solutionFilePath).GetProject(projectFilePath).Build(); - return SelectAnalyzerResult(analyzerResults, targetFramework); + var result = SelectAnalyzerResult(results, targetFramework); + if (!result.Succeeded) + { + _logger.LogDebug("Project analyzer result not successful"); + } + LogAnalyzerResult(result); + return result; } private IAnalyzerResult SelectAnalyzerResult(IAnalyzerResults analyzerResults, string targetFramework) @@ -119,6 +114,8 @@ private IAnalyzerResult SelectAnalyzerResult(IAnalyzerResults analyzerResults, s return firstAnalyzerResult; } + private static readonly HashSet ImportantProperties = new() {"Configuration", "Platform", "AssemblyName", "Configurations"}; + private void LogAnalyzerResult(IAnalyzerResult analyzerResult) { // dump all properties as it can help diagnosing build issues for user project. @@ -126,10 +123,12 @@ private void LogAnalyzerResult(IAnalyzerResult analyzerResult) _logger.LogTrace("Project: {0}", analyzerResult.ProjectFilePath); _logger.LogTrace("TargetFramework: {0}", analyzerResult.TargetFramework); + _logger.LogTrace("Succeeded: {0}", analyzerResult.Succeeded); - foreach (var property in analyzerResult?.Properties ?? new Dictionary()) + var properties = analyzerResult.Properties ?? new Dictionary(); + foreach (var property in ImportantProperties) { - _logger.LogTrace("Property {0}={1}", property.Key, property.Value); + _logger.LogTrace("Property {0}={1}", property, properties.GetValueOrDefault(property)??"'undefined'"); } foreach (var sourceFile in analyzerResult?.SourceFiles ?? Enumerable.Empty()) { @@ -137,9 +136,14 @@ private void LogAnalyzerResult(IAnalyzerResult analyzerResult) } foreach (var reference in analyzerResult?.References ?? Enumerable.Empty()) { - _logger.LogTrace("References: {0}", reference); + _logger.LogTrace("References: {0} (in {1})", Path.GetFileName(reference), Path.GetDirectoryName(reference)); + } + + foreach (var property in properties) + { + if (ImportantProperties.Contains(property.Key)) continue; // already logged + _logger.LogTrace("Property {0}={1}", property.Key, property.Value.Replace(Environment.NewLine, "\\n")); } - _logger.LogTrace("Succeeded: {0}", analyzerResult.Succeeded); _logger.LogTrace("**** Buildalyzer result ****"); } diff --git a/src/Stryker.Core/Stryker.Core/InjectedHelpers/MutantControl.cs b/src/Stryker.Core/Stryker.Core/InjectedHelpers/MutantControl.cs index 955c6f86fa..5562dfcd1a 100644 --- a/src/Stryker.Core/Stryker.Core/InjectedHelpers/MutantControl.cs +++ b/src/Stryker.Core/Stryker.Core/InjectedHelpers/MutantControl.cs @@ -1,6 +1,6 @@ namespace Stryker { - internal static class MutantControl + public static class MutantControl { private static System.Collections.Generic.List _coveredMutants; private static System.Collections.Generic.List _coveredStaticdMutants; diff --git a/src/Stryker.Core/Stryker.Core/ProjectComponents/TestProjects/TestProjectsInfo.cs b/src/Stryker.Core/Stryker.Core/ProjectComponents/TestProjects/TestProjectsInfo.cs index aee0f7970a..8c2db8a40e 100644 --- a/src/Stryker.Core/Stryker.Core/ProjectComponents/TestProjects/TestProjectsInfo.cs +++ b/src/Stryker.Core/Stryker.Core/ProjectComponents/TestProjects/TestProjectsInfo.cs @@ -8,80 +8,79 @@ using Stryker.Core.Initialisation.Buildalyzer; using Stryker.Core.Logging; -namespace Stryker.Core.ProjectComponents.TestProjects +namespace Stryker.Core.ProjectComponents.TestProjects; + +public class TestProjectsInfo { - public class TestProjectsInfo - { - private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; - public IEnumerable TestProjects { get; set; } - public IEnumerable TestFiles => TestProjects.SelectMany(testProject => testProject.TestFiles); - public IEnumerable AnalyzerResults => TestProjects.Select(testProject => testProject.AnalyzerResult); + public IEnumerable TestProjects { get; set; } + public IEnumerable TestFiles => TestProjects.SelectMany(testProject => testProject.TestFiles); + public IEnumerable AnalyzerResults => TestProjects.Select(testProject => testProject.AnalyzerResult); - public IReadOnlyList GetTestAssemblies() => - AnalyzerResults.Select(a => a.GetAssemblyPath()).ToList(); + public IReadOnlyList GetTestAssemblies() => + AnalyzerResults.Select(a => a.GetAssemblyPath()).ToList(); - public TestProjectsInfo(IFileSystem fileSystem, ILogger logger = null) - { - _fileSystem = fileSystem ?? new FileSystem(); - _logger = logger ?? ApplicationLogging.LoggerFactory.CreateLogger(); - TestProjects = Array.Empty(); - } + public TestProjectsInfo(IFileSystem fileSystem, ILogger logger = null) + { + _fileSystem = fileSystem ?? new FileSystem(); + _logger = logger ?? ApplicationLogging.LoggerFactory.CreateLogger(); + TestProjects = Array.Empty(); + } - public static TestProjectsInfo operator +(TestProjectsInfo a, TestProjectsInfo b) + public static TestProjectsInfo operator +(TestProjectsInfo a, TestProjectsInfo b) => + new(a._fileSystem, a._logger) { - a.TestProjects = a.TestProjects.Union(b.TestProjects); - return a; - } + TestProjects = a.TestProjects.Union(b.TestProjects) + }; - public void RestoreOriginalAssembly(IAnalyzerResult sourceProject) + public void RestoreOriginalAssembly(IAnalyzerResult sourceProject) + { + foreach (var testProject in AnalyzerResults) { - foreach (var testProject in AnalyzerResults) + var injectionPath = GetInjectionFilePath(testProject, sourceProject); + var backupFilePath = GetBackupName(injectionPath); + if (_fileSystem.File.Exists(backupFilePath)) { - var injectionPath = GetInjectionFilePath(testProject, sourceProject); - var backupFilePath = GetBackupName(injectionPath); - if (_fileSystem.File.Exists(backupFilePath)) + try { - try - { - _fileSystem.File.Copy(backupFilePath, injectionPath, true); - } - catch (IOException ex) - { - _logger.LogWarning(ex, "Failed to restore output assembly {Path}. Mutated assembly is still in place.", injectionPath); - } + _fileSystem.File.Copy(backupFilePath, injectionPath, true); + } + catch (IOException ex) + { + _logger.LogWarning(ex, "Failed to restore output assembly {Path}. Mutated assembly is still in place.", injectionPath); } } } + } - public void BackupOriginalAssembly(IAnalyzerResult sourceProject) + public void BackupOriginalAssembly(IAnalyzerResult sourceProject) + { + foreach (var testProject in AnalyzerResults) { - foreach (var testProject in AnalyzerResults) + var injectionPath = GetInjectionFilePath(testProject, sourceProject); + var backupFilePath = GetBackupName(injectionPath); + if (!_fileSystem.Directory.Exists(sourceProject.GetAssemblyDirectoryPath())) { - var injectionPath = GetInjectionFilePath(testProject, sourceProject); - var backupFilePath = GetBackupName(injectionPath); - if (!_fileSystem.Directory.Exists(sourceProject.GetAssemblyDirectoryPath())) - { - _fileSystem.Directory.CreateDirectory(sourceProject.GetAssemblyDirectoryPath()); - } - if (_fileSystem.File.Exists(injectionPath)) - { - // Only create backup if there isn't already a backup - if (!_fileSystem.File.Exists(backupFilePath)) - { - _fileSystem.File.Move(injectionPath, backupFilePath, false); - } - } - else + _fileSystem.Directory.CreateDirectory(sourceProject.GetAssemblyDirectoryPath()); + } + if (_fileSystem.File.Exists(injectionPath)) + { + // Only create backup if there isn't already a backup + if (!_fileSystem.File.Exists(backupFilePath)) { - _logger.LogWarning("Could not locate source assembly {injectionPath}", injectionPath); + _fileSystem.File.Move(injectionPath, backupFilePath, false); } } + else + { + _logger.LogWarning("Could not locate source assembly {injectionPath}", injectionPath); + } } + } - public static string GetInjectionFilePath(IAnalyzerResult testProject, IAnalyzerResult sourceProject) => Path.Combine(testProject.GetAssemblyDirectoryPath(), sourceProject.GetAssemblyFileName()); + public static string GetInjectionFilePath(IAnalyzerResult testProject, IAnalyzerResult sourceProject) => Path.Combine(testProject.GetAssemblyDirectoryPath(), sourceProject.GetAssemblyFileName()); - private static string GetBackupName(string injectionPath) => injectionPath + ".stryker-unchanged"; - } + private static string GetBackupName(string injectionPath) => injectionPath + ".stryker-unchanged"; } diff --git a/src/Stryker.Core/Stryker.Core/StrykerRunner.cs b/src/Stryker.Core/Stryker.Core/StrykerRunner.cs index 50a96cd20a..64891411e9 100644 --- a/src/Stryker.Core/Stryker.Core/StrykerRunner.cs +++ b/src/Stryker.Core/Stryker.Core/StrykerRunner.cs @@ -1,4 +1,6 @@ +#if !DEBUG using System; +#endif using System.Collections.Generic; using System.Diagnostics; using System.Linq; diff --git a/src/Stryker.Core/Stryker.Core/TestRunners/VsTest/VsTestContextInformation.cs b/src/Stryker.Core/Stryker.Core/TestRunners/VsTest/VsTestContextInformation.cs index 6dc750ab0d..b447bfe337 100644 --- a/src/Stryker.Core/Stryker.Core/TestRunners/VsTest/VsTestContextInformation.cs +++ b/src/Stryker.Core/Stryker.Core/TestRunners/VsTest/VsTestContextInformation.cs @@ -190,7 +190,7 @@ private void DiscoverTestsInSources(string newSource) var wrapper = BuildVsTestWrapper("TestDiscoverer"); var messages = new List(); var handler = new DiscoveryEventHandler(messages); - wrapper.DiscoverTestsAsync(new List { newSource }, GenerateRunSettingsForDiscovery(), handler); + wrapper.DiscoverTests(new List { newSource }, GenerateRunSettingsForDiscovery(), handler); handler.WaitEnd(); if (handler.Aborted) @@ -251,6 +251,8 @@ private string GenerateRunSettingsForDiscovery() return $@" {Math.Max(1, Options.Concurrency)} + true +true true {testCaseFilter} @@ -289,10 +291,11 @@ public string GenerateRunSettings(int? timeout, bool forCoverage, Dictionary false -{(isFullFramework ? @"true -" : string.Empty)} 1 +1 {timeoutSettings}{settingsForCoverage} false +true +true {testCaseFilter}{dataCollectorSettings} "; diff --git a/src/Stryker.Core/Stryker.Core/TestRunners/VsTest/VsTestRunner.cs b/src/Stryker.Core/Stryker.Core/TestRunners/VsTest/VsTestRunner.cs index b5bc586d05..5a0da642ab 100644 --- a/src/Stryker.Core/Stryker.Core/TestRunners/VsTest/VsTestRunner.cs +++ b/src/Stryker.Core/Stryker.Core/TestRunners/VsTest/VsTestRunner.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; @@ -258,16 +257,16 @@ private void RunVsTest(ITestGuids tests, string source, string runSettings, Test eventHandler.StartSession(); _currentSessionCancelled = false; - Task session; + Task session = Task.Run(()=>{ if (tests.IsEveryTest) { - session = _vsTestConsole.RunTestsWithCustomTestHostAsync(new []{source}, runSettings, options, eventHandler, strykerVsTestHostLauncher); + _vsTestConsole.RunTestsWithCustomTestHost(new []{source}, runSettings, options, eventHandler, strykerVsTestHostLauncher); } else { - session = _vsTestConsole.RunTestsWithCustomTestHostAsync(tests.GetGuids().Select(t => _context.VsTests[t].Case), + _vsTestConsole.RunTestsWithCustomTestHost(tests.GetGuids().Select(t => _context.VsTests[t].Case), runSettings, options, eventHandler, strykerVsTestHostLauncher); - } + } }); if (WaitForEnd(session, eventHandler, timeOut, ref attempt)) { diff --git a/src/Stryker.Core/Stryker.Core/TestRunners/VsTest/VsTestRunnerPool.cs b/src/Stryker.Core/Stryker.Core/TestRunners/VsTest/VsTestRunnerPool.cs index 948f24ccbf..4517fd10f2 100644 --- a/src/Stryker.Core/Stryker.Core/TestRunners/VsTest/VsTestRunnerPool.cs +++ b/src/Stryker.Core/Stryker.Core/TestRunners/VsTest/VsTestRunnerPool.cs @@ -139,14 +139,10 @@ private IEnumerable ConvertCoverageResult(IEnumerable seenTestCases var (key, value) = testResult.GetProperties().FirstOrDefault(x => x.Key.Id == CoverageCollector.PropertyName); var testCaseId = testResult.TestCase.Id; var unexpected = false; + var log = testResult.GetProperties().FirstOrDefault(x => x.Key.Id == CoverageCollector.Coveragelog).Value?.ToString(); + if (!string.IsNullOrEmpty(log)) + { + _logger.LogError($"VsTestRunner: Coverage collector error: {log}."); + } + if (!Context.VsTests.ContainsKey(testCaseId)) { _logger.LogWarning( diff --git a/src/Stryker.Core/Stryker.Core/Testing/ProcessExecutor.cs b/src/Stryker.Core/Stryker.Core/Testing/ProcessExecutor.cs index 3b02e691ce..75477fe8c3 100644 --- a/src/Stryker.Core/Stryker.Core/Testing/ProcessExecutor.cs +++ b/src/Stryker.Core/Stryker.Core/Testing/ProcessExecutor.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; using System.Text; @@ -27,15 +26,10 @@ public interface IProcessExecutor } [ExcludeFromCodeCoverage] - public class ProcessExecutor : IProcessExecutor + public class ProcessExecutor(bool redirectOutput = true) : IProcessExecutor { // when redirected, the output from the process will be kept in memory and not displayed to the console directly - private bool RedirectOutput { get; } - - public ProcessExecutor(bool redirectOutput = true) - { - RedirectOutput = redirectOutput; - } + private bool RedirectOutput { get; } = redirectOutput; public ProcessResult Start( string path, @@ -47,7 +41,7 @@ public ProcessResult Start( var info = new ProcessStartInfo(application, arguments) { UseShellExecute = false, - WorkingDirectory = Path.GetDirectoryName(FilePathUtils.NormalizePathSeparators(path)), + WorkingDirectory = FilePathUtils.NormalizePathSeparators(path), RedirectStandardOutput = RedirectOutput, RedirectStandardError = RedirectOutput }; diff --git a/src/Stryker.DataCollector/Stryker.DataCollector/CoverageCollector.cs b/src/Stryker.DataCollector/Stryker.DataCollector/CoverageCollector.cs index 8d1edac1da..9ce57e14dd 100644 --- a/src/Stryker.DataCollector/Stryker.DataCollector/CoverageCollector.cs +++ b/src/Stryker.DataCollector/Stryker.DataCollector/CoverageCollector.cs @@ -17,6 +17,8 @@ public class CoverageCollector : InProcDataCollection private IDataCollectionSink _dataSink; private bool _coverageOn; private int _activeMutation = -1; + private bool _reportFailure; + private Action _logger; private readonly IDictionary _mutantTestedBy = new Dictionary(); @@ -34,6 +36,7 @@ public class CoverageCollector : InProcDataCollection public const string PropertyName = "Stryker.Coverage"; public const string OutOfTestsPropertyName = "Stryker.Coverage.OutOfTests"; + public const string Coveragelog = "CoverageLog"; public string MutantList => string.Join(",", _mutantTestedBy.Values.Distinct()); @@ -168,12 +171,12 @@ private void ReadConfiguration(string configuration) private int GetActiveMutantForThisTest(string testId) { - if (_mutantTestedBy.ContainsKey(testId)) + if (_mutantTestedBy.TryGetValue(testId, out var test)) { - return _mutantTestedBy[testId]; + return test; } - return _mutantTestedBy.ContainsKey(AnyId) ? _mutantTestedBy[AnyId] : -1; + return _mutantTestedBy.TryGetValue(AnyId, out var value) ? value : -1; } private void ParseTestMapping(XmlNodeList testMapping) @@ -240,6 +243,11 @@ private void PublishCoverageData(DataCollectionContext dataCollectionContext) { // no test covered any mutations, so the controller was never properly initialized _dataSink.SendData(dataCollectionContext, PropertyName, ";"); + if (!_reportFailure) + { + _dataSink.SendData(dataCollectionContext, Coveragelog, $"Did not find type {_controlClassName}. This indicates Stryker failed to copy the mutated assembly for test."); + _reportFailure = true; + } return; }