diff --git a/integrationtest/TargetProjects/NetCore/Library/Library.csproj b/integrationtest/TargetProjects/NetCore/Library/Library.csproj index 58990cd569..8268829b64 100644 --- a/integrationtest/TargetProjects/NetCore/Library/Library.csproj +++ b/integrationtest/TargetProjects/NetCore/Library/Library.csproj @@ -1,7 +1,7 @@ - net8.0 + net7.0 diff --git a/integrationtest/TargetProjects/NetCore/TargetProject/Constructs/Csharp12.cs b/integrationtest/TargetProjects/NetCore/TargetProject/Constructs/Csharp12.cs index 5f03bab0b7..1f7c1bba9e 100644 --- a/integrationtest/TargetProjects/NetCore/TargetProject/Constructs/Csharp12.cs +++ b/integrationtest/TargetProjects/NetCore/TargetProject/Constructs/Csharp12.cs @@ -72,6 +72,7 @@ public static void TuplesInLambda() // The set (2, 3, 4) doubled: (4, 6, 8) } + #if NET8_0_OR_GREATER // inline array [System.Runtime.CompilerServices.InlineArray(10)] public struct Buffer @@ -92,4 +93,5 @@ public static void InlineArray() Console.WriteLine(i); } } + #endif } diff --git a/integrationtest/TargetProjects/NetCore/TargetProject/TargetProject.csproj b/integrationtest/TargetProjects/NetCore/TargetProject/TargetProject.csproj index d5c39f1534..865c4f3ff7 100644 --- a/integrationtest/TargetProjects/NetCore/TargetProject/TargetProject.csproj +++ b/integrationtest/TargetProjects/NetCore/TargetProject/TargetProject.csproj @@ -1,7 +1,7 @@ - net8.0 + net7.0 true enable latest diff --git a/integrationtest/Validation/ValidationProject/ValidateStrykerResults.cs b/integrationtest/Validation/ValidationProject/ValidateStrykerResults.cs index ba0c30952e..63b211c6ab 100644 --- a/integrationtest/Validation/ValidationProject/ValidateStrykerResults.cs +++ b/integrationtest/Validation/ValidationProject/ValidateStrykerResults.cs @@ -82,7 +82,7 @@ public void CSharp_NetCore_SingleTestProject() var report = JsonConvert.DeserializeObject(strykerRunOutput); - CheckReportMutants(report, total: 596, ignored: 248, survived: 4, killed: 9, timeout: 2, nocoverage: 302); + CheckReportMutants(report, total: 589, ignored: 246, survived: 4, killed: 9, timeout: 2, nocoverage: 297); CheckReportTestCounts(report, total: 11); } @@ -121,7 +121,7 @@ public void CSharp_NetCore_WithTwoTestProjects() var report = JsonConvert.DeserializeObject(strykerRunOutput); - CheckReportMutants(report, total: 596, ignored: 107, survived: 5, killed: 11, timeout: 2, nocoverage: 440); + CheckReportMutants(report, total: 589, ignored: 105, survived: 5, killed: 11, timeout: 2, nocoverage: 435); CheckReportTestCounts(report, total: 21); } @@ -140,7 +140,7 @@ public void CSharp_NetCore_SolutionRun() var report = JsonConvert.DeserializeObject(strykerRunOutput); - CheckReportMutants(report, total: 596, ignored: 248, survived: 4, killed: 9, timeout: 2, nocoverage: 302); + CheckReportMutants(report, total: 589, ignored: 246, survived: 4, killed: 9, timeout: 2, nocoverage: 297); CheckReportTestCounts(report, total: 23); } diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/BuildAnalyzerTestsBase.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/BuildAnalyzerTestsBase.cs index 1fb0670ad0..ef3ea01194 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/BuildAnalyzerTestsBase.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/BuildAnalyzerTestsBase.cs @@ -17,7 +17,7 @@ public class BuildAnalyzerTestsBase : TestBase protected internal const string DefaultFramework = "net6.0"; protected readonly MockFileSystem FileSystem = new(); protected string ProjectPath; - private readonly Dictionary _projectCache = new(); + private readonly Dictionary> _projectCache = new(); protected readonly Mock BuildalyzerProviderMock = new(MockBehavior.Strict); public BuildAnalyzerTestsBase() @@ -42,7 +42,24 @@ protected Mock SourceProjectAnalyzerMock(string csprojPathName var properties = GetSourceProjectDefaultProperties(); projectReferences??= new List(); - return BuildProjectAnalyzerMock(csprojPathName, sourceFiles, properties, projectReferences, framework, success); + return BuildProjectAnalyzerMock(csprojPathName, sourceFiles, properties, projectReferences, [framework], success); + } + + /// + /// Build a simple production project + /// + /// project pathname + /// project source files + /// project references + /// framework version + /// Predicate to control when analysis is successful. Default is always success. + protected Mock SourceProjectAnalyzerMock(string csprojPathName, string[] sourceFiles, + IEnumerable projectReferences , IEnumerable frameworks, Func success = null) + { + var properties = GetSourceProjectDefaultProperties(); + projectReferences??= new List(); + + return BuildProjectAnalyzerMock(csprojPathName, sourceFiles, properties, projectReferences, frameworks, success); } public static Dictionary GetSourceProjectDefaultProperties() @@ -57,15 +74,109 @@ public static Dictionary GetSourceProjectDefaultProperties() /// /// test project pathname /// production code project pathname - /// + /// /// /// a mock project analyzer /// the test project references the production code project and contains no source file - protected Mock TestProjectAnalyzerMock(string testCsprojPathName, string csProj, string framework = DefaultFramework, bool success = true) + protected Mock TestProjectAnalyzerMock(string testCsprojPathName, string csProj, IEnumerable frameworks = null, bool success = true) { + frameworks??=new []{DefaultFramework}; var properties = new Dictionary{ { "IsTestProject", "True" }, { "Language", "C#" } }; - var projectReferences = string.IsNullOrEmpty(csProj) ? [] : _projectCache[csProj].ProjectReferences.Append(csProj).ToList(); - return BuildProjectAnalyzerMock(testCsprojPathName, [], properties, projectReferences, framework, () => success); + var projectReferences = string.IsNullOrEmpty(csProj) ? [] : GetProjectResult(csProj, frameworks.First()).ProjectReferences.Append(csProj).ToList(); + return BuildProjectAnalyzerMock(testCsprojPathName, [], properties, projectReferences, frameworks, () => success); + } + + private IAnalyzerResult GetProjectResult(string projectFile, string expectedFramework, bool returnDefaultIfNotFound = true) + { + if (!_projectCache.TryGetValue(projectFile, out var project)) + { + return null; + } + + var target= PickCompatibleFramework(expectedFramework, project.Keys); + if (target == null) + { + return returnDefaultIfNotFound ? project.Values.First() : null; + } + return project[target]; + } + + /// + /// Parse a net moniker. WARNING: use this for tests only, as it only supports netcore, netstandard and net monikers. + /// This is a very naive implementation which does not fully respect the official rules + /// + /// moniker to parse + /// a tuple with the framework kind first and the version next + protected static (FrameworkKind kind, decimal version) ParseFramework(string framework) + { + FrameworkKind kind; + decimal version; + + if (framework.StartsWith("netcoreapp")) + { + if (!decimal.TryParse(framework[10..], out version)) + { + version = 1.0m; + } + return (FrameworkKind.NetCore, version); + } + if (framework.StartsWith("netstandard")) + { + if (!decimal.TryParse(framework[11..], out version)) + { + version = 1.0m; + } + return (FrameworkKind.NetStandard, version); + } + if (framework.StartsWith("net") && decimal.TryParse(framework[3..], out version)) + { + return framework.Contains('.') ? (FrameworkKind.NetCore, version) : (FrameworkKind.Net, version/100); + } + return (FrameworkKind.Other, 1.1m); + } + + /// + /// Framework families + /// + protected enum FrameworkKind + { + Net, NetCore, NetStandard, Other + } + + /// + /// Picks the best compatible framework from a list. WARNING: use this for tests only, as it only supports netcore and net monikers. + /// netstandard is not properly implemented. This is a basic implementation + /// + /// target framework + /// list of available frameworks + /// if the framework is among the target, the best match if available, null otherwise. + protected static string PickCompatibleFramework(string framework, IEnumerable frameworks) + { + var parsed = ParseFramework(framework); + + string bestCandidate = null; + var bestVersion = 1.0m; + foreach(var candidate in frameworks) + { + if (candidate == framework) + { + return framework; + } + var parsedCandidate = ParseFramework(candidate); + if (parsedCandidate.kind != parsed.kind) + { + continue; + } + + if (parsedCandidate.version > parsed.version || parsedCandidate.version <= bestVersion) + { + continue; + } + + bestVersion = parsedCandidate.version; + bestCandidate = candidate; + } + return bestCandidate; } /// @@ -75,7 +186,7 @@ protected Mock TestProjectAnalyzerMock(string testCsprojPathNa /// source files to return /// project properties /// project references - /// + /// list of frameworks (multitargeting) /// analysis success /// assembly references /// a mock project analyzer @@ -84,16 +195,15 @@ protected Mock TestProjectAnalyzerMock(string testCsprojPathNa /// 2. the project analyzer mock returns a single project result internal Mock BuildProjectAnalyzerMock(string csprojPathName, string[] sourceFiles, Dictionary properties, - IEnumerable projectReferences = null, - string framework = DefaultFramework, + IEnumerable projectReferences= null, + IEnumerable frameworks = null, Func success = null, IEnumerable rawReferences = null) { - var sourceProjectAnalyzerMock = new Mock(MockBehavior.Strict); - var sourceProjectAnalyzerResultMock = new Mock(MockBehavior.Strict); - var sourceProjectFileMock = new Mock(MockBehavior.Strict); + var projectFileMock = new Mock(MockBehavior.Strict); success??= () => true; projectReferences??= []; + frameworks??=[DefaultFramework]; // create dummy project and source files FileSystem.AddFile(csprojPathName, new MockFileData("")); foreach (var file in sourceFiles) @@ -101,52 +211,65 @@ internal Mock BuildProjectAnalyzerMock(string csprojPathName, FileSystem.AddFile(file, new MockFileData("")); } rawReferences ??= ["System"]; - // create bin folder - var projectUnderTestBin = FileSystem.Path.Combine(ProjectPath, "bin"); - FileSystem.AddDirectory(projectUnderTestBin); - - var projectBin = - FileSystem.Path.Combine(projectUnderTestBin, FileSystem.Path.GetFileNameWithoutExtension(csprojPathName)+".dll"); - FileSystem.AddFile(FileSystem.Path.Combine(projectUnderTestBin, projectBin), new MockFileData("")); - sourceProjectAnalyzerResultMock.Setup(x => x.ProjectReferences).Returns(projectReferences); - sourceProjectAnalyzerResultMock.Setup(x => x.References).Returns(projectReferences. - Where (_projectCache.ContainsKey). - Select( iar => _projectCache[iar].GetAssemblyPath()).Union(rawReferences).ToArray()); - sourceProjectAnalyzerResultMock.Setup(x => x.SourceFiles).Returns(sourceFiles); - sourceProjectAnalyzerResultMock.Setup(x => x.PreprocessorSymbols).Returns(["NET"]); - properties.Add("TargetRefPath", projectBin); - properties.Add("TargetDir", projectUnderTestBin); - properties.Add("TargetFileName", projectBin); - - sourceProjectAnalyzerResultMock.Setup(x => x.Properties).Returns(properties); - sourceProjectAnalyzerResultMock.Setup(x => x.GetProperty(It.IsAny())).Returns((string p) => properties.GetValueOrDefault(p, null)); - sourceProjectAnalyzerResultMock.Setup(x => x.ProjectFilePath).Returns(csprojPathName); - sourceProjectAnalyzerResultMock.Setup(x => x.TargetFramework).Returns(framework); - sourceProjectAnalyzerResultMock.Setup(x => x.Succeeded).Returns(success); - - sourceProjectAnalyzerResultMock.Setup(x => x.Analyzer).Returns(null); - _projectCache[csprojPathName] = sourceProjectAnalyzerResultMock.Object; - - var sourceProjectAnalyzerResultsMock = BuildAnalyzerResultsMock(sourceProjectAnalyzerResultMock.Object); - sourceProjectAnalyzerMock.Setup(x => x.Build(It.IsAny())).Returns(sourceProjectAnalyzerResultsMock); - sourceProjectAnalyzerMock.Setup(x => x.Build(It.IsAny(), It.IsAny())).Returns(sourceProjectAnalyzerResultsMock); - - sourceProjectAnalyzerMock.Setup(x => x.ProjectFile).Returns(sourceProjectFileMock.Object); - sourceProjectAnalyzerMock.Setup(x => x.EnvironmentFactory).Returns(null); - - sourceProjectFileMock.Setup(x => x.Path).Returns(csprojPathName); - sourceProjectFileMock.Setup(x => x.Name).Returns(FileSystem.Path.GetFileName(csprojPathName)); - sourceProjectFileMock.Setup(x=> x.TargetFrameworks).Returns([framework]); - return sourceProjectAnalyzerMock; + + var projectAnalyzerResults = new Dictionary(); + foreach(var framework in frameworks) + { + var specificProperties = new Dictionary(properties); + // create bin folders + var projectUnderTestBin = FileSystem.Path.Combine(ProjectPath, "bin", framework); + FileSystem.AddDirectory(projectUnderTestBin); + + var projectBin = + FileSystem.Path.Combine(projectUnderTestBin, FileSystem.Path.GetFileNameWithoutExtension(csprojPathName)+".dll"); + FileSystem.AddFile(FileSystem.Path.Combine(projectUnderTestBin, projectBin), new MockFileData("")); + var projectAnalyzerResultMock = new Mock(MockBehavior.Strict); + projectAnalyzerResultMock.Setup(x => x.ProjectReferences).Returns(projectReferences); + projectAnalyzerResultMock.Setup(x => x.References).Returns(projectReferences. + Where ( p => p !=null && _projectCache.ContainsKey(p)). + Select( iar => GetProjectResult(iar, framework).GetAssemblyPath()).Union(rawReferences).ToArray()); + projectAnalyzerResultMock.Setup(x => x.SourceFiles).Returns(sourceFiles); + projectAnalyzerResultMock.Setup(x => x.PreprocessorSymbols).Returns(["NET"]); + specificProperties.Add("TargetRefPath", projectBin); + specificProperties.Add("TargetDir", projectUnderTestBin); + specificProperties.Add("TargetFileName", projectBin); + + projectAnalyzerResultMock.Setup(x => x.Properties).Returns(specificProperties); + projectAnalyzerResultMock.Setup(x => x.GetProperty(It.IsAny())).Returns((string p) => specificProperties.GetValueOrDefault(p, null)); + projectAnalyzerResultMock.Setup(x => x.ProjectFilePath).Returns(csprojPathName); + projectAnalyzerResultMock.Setup(x => x.TargetFramework).Returns(framework); + projectAnalyzerResultMock.Setup(x => x.Succeeded).Returns(success); + + projectAnalyzerResultMock.Setup(x => x.Analyzer).Returns(null); + projectAnalyzerResults[framework] = projectAnalyzerResultMock.Object; + } + + _projectCache[csprojPathName] = projectAnalyzerResults; + + var sourceProjectAnalyzerResultsMock = BuildAnalyzerResultsMock(projectAnalyzerResults); + + var projectAnalyzerMock = new Mock(MockBehavior.Strict); + projectAnalyzerMock.Setup(x => x.Build(It.IsAny())).Returns(sourceProjectAnalyzerResultsMock); + projectAnalyzerMock.Setup(x => x.Build(It.IsAny(), It.IsAny())).Returns(sourceProjectAnalyzerResultsMock); + projectAnalyzerMock.Setup(x => x.Build(It.IsAny())).Returns(sourceProjectAnalyzerResultsMock); + projectAnalyzerMock.Setup(x => x.Build()).Returns(sourceProjectAnalyzerResultsMock); + + projectAnalyzerMock.Setup(x => x.ProjectFile).Returns(projectFileMock.Object); + projectAnalyzerMock.Setup(x => x.EnvironmentFactory).Returns(null); + + projectFileMock.Setup(x => x.Path).Returns(csprojPathName); + projectFileMock.Setup(x => x.Name).Returns(FileSystem.Path.GetFileName(csprojPathName)); + projectFileMock.Setup(x=> x.TargetFrameworks).Returns(frameworks.ToArray() ); + return projectAnalyzerMock; } - internal static IAnalyzerResults BuildAnalyzerResultsMock(IAnalyzerResult sourceProjectAnalyzerResult) + private IAnalyzerResults BuildAnalyzerResultsMock(IDictionary projectAnalyzerResults) { - IEnumerable analyzerResults = [ sourceProjectAnalyzerResult]; + var analyzerResults = projectAnalyzerResults.Values.ToList(); var sourceProjectAnalyzerResultsMock = new Mock(MockBehavior.Strict); sourceProjectAnalyzerResultsMock.Setup(x => x.OverallSuccess).Returns(() => analyzerResults.All(r=> r.Succeeded)); sourceProjectAnalyzerResultsMock.Setup(x => x.Results).Returns(analyzerResults); - sourceProjectAnalyzerResultsMock.Setup(x => x.Count).Returns(analyzerResults.Count()); + sourceProjectAnalyzerResultsMock.Setup(x => x.Count).Returns(analyzerResults.Count); sourceProjectAnalyzerResultsMock.Setup(x => x.GetEnumerator()).Returns(() => analyzerResults.GetEnumerator()); return sourceProjectAnalyzerResultsMock.Object; } diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/InputFileResolverTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/InputFileResolverTests.cs index 81808e4592..f711a5772b 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/InputFileResolverTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/InputFileResolverTests.cs @@ -135,7 +135,7 @@ public void InitializeShouldFindFilesRecursively() }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, []); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -169,7 +169,7 @@ public void InitializeShouldUseBuildalyzerResult() }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, fileSystem.AllFiles.Where(s => s.EndsWith(".cs")).ToArray()); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -200,7 +200,7 @@ public void InitializeShouldNotSkipXamlFiles() { Path.Combine(_sourcePath, "obj", "Debug", "netcoreapp2.1", "ExampleProject.AssemblyInfo.cs"), new MockFileData("Bytecode") } }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, fileSystem.AllFiles.Where(s => s.EndsWith(".cs")).ToArray()); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -250,7 +250,7 @@ public void InitializeShouldMutateAssemblyInfo() }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, fileSystem.AllFiles.Where(s => s.EndsWith(".cs")).ToArray()); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -307,7 +307,7 @@ public void InitializeShouldNotMutateIncompleteAssemblyInfo() }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, fileSystem.AllFiles.Where(s => s.EndsWith(".cs")).ToArray()); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -336,7 +336,7 @@ public void InitializeShouldFindSpecifiedTestProjectFile() }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, fileSystem.AllFiles.Where(s => s.EndsWith(".cs")).ToArray()); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -393,7 +393,7 @@ public void InitializeShouldResolveImportedProject() }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, fileSystem.AllFiles.Where(s => s.EndsWith(".cs")).ToArray()); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -439,7 +439,7 @@ public void InitializeShouldNotResolveImportedPropsFile() }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, []); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -510,7 +510,7 @@ public void InitializeShouldResolveMultipleImportedProjects() }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, []); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -558,7 +558,7 @@ public void InitializeShouldThrowOnMissingSharedProject() }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, []); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -610,7 +610,7 @@ public void InitializeShouldResolvePropertiesInSharedProjectImports() var properties = new Dictionary { { "IsTestProject", "False" }, { "ProjectTypeGuids", "not testproject" }, { "Language", "C#" }, { "SharedDir", "SharedProject" } }; var sourceProjectManagerMock = BuildProjectAnalyzerMock(_sourceProjectPath, [], properties, new List()); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -663,7 +663,7 @@ public void InitializeShouldThrowIfImportPropertyCannotBeResolved() }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, []); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -694,7 +694,7 @@ public void InitializeShouldIgnoreBinFolder(string folderName) }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, []); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -892,6 +892,57 @@ public void ShouldSelectAvailableFramework_WhenDesiredNotFound(string targetFram result.AnalyzerResult.TargetFramework.ShouldBe(DefaultFramework); } + [TestMethod] + [DataRow("net3.0,net462", "net461,net2.0", null, "net3.0", "net2.0")] + [DataRow("net3.0,net2.0", "net2.0,net3.0", null, "net3.0", "net3.0")] + [DataRow("net3.0,net462", "net461,net2.0", "net2.0", "net3.0", "net2.0")] + [DataRow("net3.0,net462", "net461,net2.0", "net3.0", "net3.0", "net2.0")] + [DataRow("net3.0,net462", "net461,net2.0", "net461", "net3.0", "net2.0")] + [DataRow("net3.0,net462", "net461,net2.0", "net462", "net462", "net461")] + public void ShouldSelectFrameworkBasedOnTestProject(string testFrameworks, string projectFrameworks, string targetFramework, string expectedTestFramework,string expectedFramework) + { + // Arrange + var basePath = Path.Combine(_sourcePath, "ExampleProject"); + var testProjectPath = Path.Combine(_sourcePath, "TestProjectFolder", "TestProject.csproj"); + var sourceProjectPath = Path.Combine(_sourcePath, "ExampleProject", "ExampleProject.csproj"); + var sourceProjectNameFilter = "ExampleProject.csproj"; + + var fileSystem = new MockFileSystem(new Dictionary + { + { sourceProjectPath, new MockFileData(_defaultTestProjectFileContents)}, + { testProjectPath, new MockFileData(_defaultTestProjectFileContents)}, + { Path.Combine(_sourcePath, "Recursive.cs"), new MockFileData("content")} + }); + + var options = new StrykerOptions() + { + ProjectPath = basePath, + SourceProjectName = sourceProjectNameFilter, + TestProjects = new List { testProjectPath }, + TargetFramework = targetFramework + }; + + var sourceProjectManagerMock = SourceProjectAnalyzerMock(sourceProjectPath, + fileSystem.AllFiles.Where(s => s.EndsWith(".cs")).ToArray(), null, projectFrameworks.Split(',')); + var testProjectManagerMock = TestProjectAnalyzerMock(testProjectPath, sourceProjectPath, frameworks: testFrameworks.Split(',')); + + var analyzerResults = new Dictionary + { + { "MyProject", sourceProjectManagerMock.Object }, + { "MyProject.UnitTests", testProjectManagerMock.Object } + }; + BuildBuildAnalyzerMock(analyzerResults); + + var target = new InputFileResolver(fileSystem, BuildalyzerProviderMock.Object, _nugetMock.Object); + + // Act + var result = target.ResolveSourceProjectInfos(options).First(); + + // Assert + result.AnalyzerResult.TargetFramework.ShouldBe(expectedFramework); + result.TestProjectsInfo.AnalyzerResults.First().TargetFramework.ShouldBe(expectedTestFramework); + } + [TestMethod] public void ShouldThrowOnMsTestV1Detected() { @@ -929,7 +980,7 @@ public void ShouldSkipXamlFiles() { _testProjectPath, new MockFileData(_defaultTestProjectFileContents)} }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, []); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -971,8 +1022,8 @@ public void ShouldFindAllTestProjects() }; var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, []); - var testProjectManagerMock = TestProjectAnalyzerMock(testProject1, _sourceProjectPath, "netcoreapp2.1"); - var testProjectManagerMock2 = TestProjectAnalyzerMock(testProject2, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(testProject1, _sourceProjectPath, ["netcoreapp2.1"]); + var testProjectManagerMock2 = TestProjectAnalyzerMock(testProject2, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -1002,7 +1053,7 @@ public void ShouldFindSourceProjectWhenSingleProjectReferenceAndNoFilter() }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, fileSystem.AllFiles.Where(s => s.EndsWith(".cs")).ToArray()); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -1026,7 +1077,7 @@ public void ShouldThrowOnNoProjectReference() { _testProjectPath, new MockFileData(_defaultTestProjectFileContents)}, }); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, null, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, null, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -1056,7 +1107,7 @@ public void ShouldThrowOnMultipleProjectsWithoutFilter() var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, []); var sourceProject2ManagerMock = SourceProjectAnalyzerMock(project2, []); var properties = new Dictionary{ { "IsTestProject", "True" }, { "Language", "C#" } }; - var testProjectManagerMock = BuildProjectAnalyzerMock(_testProjectPath, [], properties, [_sourceProjectPath, project2], "netcore2.1"); + var testProjectManagerMock = BuildProjectAnalyzerMock(_testProjectPath, [], properties, [_sourceProjectPath, project2], ["netcore2.1"]); var analyzerResults = new Dictionary { @@ -1093,9 +1144,9 @@ public void ShouldNotThrowIfMultipleProjectButOneIsAlwaysReferenced() var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, []); var sourceProject2ManagerMock = SourceProjectAnalyzerMock(project2, []); var properties = new Dictionary{ { "IsTestProject", "True" }, { "Language", "C#" } }; - var testProjectManagerMock = BuildProjectAnalyzerMock(_testProjectPath, [], properties, [_sourceProjectPath, project2], "netcore2.1"); + var testProjectManagerMock = BuildProjectAnalyzerMock(_testProjectPath, [], properties, [_sourceProjectPath, project2], ["netcore2.1"]); var properties2 = new Dictionary{ { "IsTestProject", "True" }, { "Language", "C#" } }; - var testProjectManagerMock2 = BuildProjectAnalyzerMock(test2Path, [], properties2, [_sourceProjectPath], "netcore2.1"); + var testProjectManagerMock2 = BuildProjectAnalyzerMock(test2Path, [], properties2, [_sourceProjectPath], ["netcore2.1"]); var analyzerResults = new Dictionary { @@ -1139,7 +1190,7 @@ public void ShouldMatchFromMultipleProjectByName(string shouldMatch) var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, []); var sourceProject2ManagerMock = SourceProjectAnalyzerMock(project2, []); var properties = new Dictionary{ { "IsTestProject", "True" }, { "Language", "C#" } }; - var testProjectManagerMock = BuildProjectAnalyzerMock(_testProjectPath, [], properties, [_sourceProjectPath, project2], "netcore2.1"); + var testProjectManagerMock = BuildProjectAnalyzerMock(_testProjectPath, [], properties, [_sourceProjectPath, project2], ["netcore2.1"]); var analyzerResults = new Dictionary { @@ -1176,7 +1227,7 @@ public void ShouldThrowWhenTheNameMatchesMore(string shouldMatchMoreThanOne) var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, []); var sourceProject2ManagerMock = SourceProjectAnalyzerMock(project2, []); var properties = new Dictionary{ { "IsTestProject", "True" }, { "Language", "C#" } }; - var testProjectManagerMock = BuildProjectAnalyzerMock(_testProjectPath, [], properties, [_sourceProjectPath, project2], "netcore2.1"); + var testProjectManagerMock = BuildProjectAnalyzerMock(_testProjectPath, [], properties, [_sourceProjectPath, project2], ["netcore2.1"]); var analyzerResults = new Dictionary { @@ -1204,7 +1255,7 @@ public void ShouldThrowWhenTheNameMatchesNone() }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, fileSystem.AllFiles.Where(s => s.EndsWith(".cs")).ToArray()); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { @@ -1234,7 +1285,7 @@ public void ShouldMatchOnBothForwardAndBackwardsSlash(string shouldMatch) }); var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, fileSystem.AllFiles.Where(s => s.EndsWith(".cs")).ToArray()); - var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, "netcoreapp2.1"); + var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"]); var analyzerResults = new Dictionary { diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/ProjectOrchestratorTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/ProjectOrchestratorTests.cs index 8863ce1aa6..7f01c81a46 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/ProjectOrchestratorTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Initialisation/ProjectOrchestratorTests.cs @@ -184,9 +184,9 @@ public void ShouldRestoreWhenAnalysisFails() var success = false; IEnumerable projectReferences = []; var properties = GetSourceProjectDefaultProperties(); - var sourceProjectAnalyzerMock = BuildProjectAnalyzerMock(csprojPathName, sourceFiles, properties, null, "net4.5", () => success, projectReferences).Object; + var sourceProjectAnalyzerMock = BuildProjectAnalyzerMock(csprojPathName, sourceFiles, properties, null, ["net4.5"], () => success, projectReferences).Object; - var testProjectAnalyzerMock = TestProjectAnalyzerMock(testCsprojPathName, csprojPathName, "net4.5").Object; + var testProjectAnalyzerMock = TestProjectAnalyzerMock(testCsprojPathName, csprojPathName, ["net4.5"]).Object; // The analyzer finds two projects BuildBuildAnalyzerMock(new Dictionary { diff --git a/src/Stryker.Core/Stryker.Core/Initialisation/Buildalyzer/IAnalyzerResultExtensions.cs b/src/Stryker.Core/Stryker.Core/Initialisation/Buildalyzer/IAnalyzerResultExtensions.cs index 3c6e89f791..4ddf57c7a9 100644 --- a/src/Stryker.Core/Stryker.Core/Initialisation/Buildalyzer/IAnalyzerResultExtensions.cs +++ b/src/Stryker.Core/Stryker.Core/Initialisation/Buildalyzer/IAnalyzerResultExtensions.cs @@ -162,7 +162,7 @@ public static Language GetLanguage(this IAnalyzerResult analyzerResult) => private static readonly string[] knownTestPackages = ["MSTest.TestFramework", "xunit", "NUnit"]; - public static bool IsTestProject(this IAnalyzerResults analyzerResults) => analyzerResults.Any(x => x.IsTestProject()); + public static bool IsTestProject(this IEnumerable analyzerResults) => analyzerResults.Any(x => x.IsTestProject()); public static bool IsTestProject(this IAnalyzerResult analyzerResult) { diff --git a/src/Stryker.Core/Stryker.Core/Initialisation/InputFileResolver.cs b/src/Stryker.Core/Stryker.Core/Initialisation/InputFileResolver.cs index 684e238216..731cac0dcb 100644 --- a/src/Stryker.Core/Stryker.Core/Initialisation/InputFileResolver.cs +++ b/src/Stryker.Core/Stryker.Core/Initialisation/InputFileResolver.cs @@ -40,7 +40,6 @@ public class InputFileResolver : IInputFileResolver private readonly INugetRestoreProcess _nugetRestoreProcess; - private readonly StringWriter _buildalyzerLog = new(); public InputFileResolver(IFileSystem fileSystem, @@ -148,37 +147,24 @@ private List AnalyzeAndIdentifyProjects(List projectL return projectInfos; } - private ConcurrentBag<(IAnalyzerResults result, bool isTest)> AnalyzeAllNeededProjects(List projectList, StrykerOptions options, IAnalyzerManager manager, ScanMode mode) + private ConcurrentBag<(IEnumerable result, bool isTest)> AnalyzeAllNeededProjects(List projectList, StrykerOptions options, IAnalyzerManager manager, ScanMode mode) { - var mutableProjectsAnalyzerResults = new ConcurrentBag<(IAnalyzerResults result, bool isTest)>(); + var mutableProjectsAnalyzerResults = new ConcurrentBag<(IEnumerable result, bool isTest)>(); try { - var list = new DynamicEnumerableQueue(projectList); + var list = new DynamicEnumerableQueue<(string projectFile, string framework)>(projectList.Select(p => (p, options.TargetFramework))); var normalizedProjectUnderTestNameFilter = !string.IsNullOrEmpty(options.SourceProjectName) ? options.SourceProjectName.Replace("\\", "/") : null; while(!list.Empty) { Parallel.ForEach(list.Consume(), new ParallelOptions - { MaxDegreeOfParallelism = options.DevMode ? 1 : Math.Max(options.Concurrency, 1) }, projectFile => + { MaxDegreeOfParallelism = options.DevMode ? 1 : Math.Max(options.Concurrency, 1) }, entry => { - var project = manager.GetProject(projectFile); - var buildResult = AnalyzeSingleProject(project, options); - if (buildResult.Count == 0) - { - // analysis failed - return; - } - var isTestProject = buildResult.IsTestProject(); - var referencesToAdd = ScanReferences(mode, buildResult); - - var addProject = isTestProject || normalizedProjectUnderTestNameFilter == null || - project.ProjectFile.Path.Replace('\\', '/') - .Contains(normalizedProjectUnderTestNameFilter, StringComparison.InvariantCultureIgnoreCase); - if (addProject) - mutableProjectsAnalyzerResults.Add((buildResult, isTestProject)); - foreach (var reference in referencesToAdd) + var buildResult = GetProjectAndAddIt(options, manager, entry, normalizedProjectUnderTestNameFilter, mutableProjectsAnalyzerResults); + + foreach (var reference in ScanReferences(mode, buildResult)) { - list.Add(reference); + list.Add((reference, null)); } }); } @@ -191,13 +177,43 @@ private List AnalyzeAndIdentifyProjects(List projectL return mutableProjectsAnalyzerResults; } + private IEnumerable GetProjectAndAddIt(StrykerOptions options, IAnalyzerManager manager, + (string projectFile, string framework) entry, string normalizedProjectUnderTestNameFilter, + ConcurrentBag<(IEnumerable result, bool isTest)> mutableProjectsAnalyzerResults) + { + var project = manager.GetProject(entry.projectFile); + IEnumerable buildResult = AnalyzeSingleProject(project, options); + if (!buildResult.Any()) + { + // analysis failed + return buildResult; + } + var isTestProject = buildResult.IsTestProject(); + if (isTestProject) + { + buildResult = new List + { + SelectAnalyzerResult(buildResult, entry.framework) + }; + } + + if (isTestProject || normalizedProjectUnderTestNameFilter == null || + project.ProjectFile.Path.Replace('\\', '/') + .Contains(normalizedProjectUnderTestNameFilter, StringComparison.InvariantCultureIgnoreCase)) + { + mutableProjectsAnalyzerResults.Add((buildResult, isTestProject)); + } + + return buildResult; + } + /// /// Scan the references of a project and add them for analysis according to scan option /// /// scan mode /// analyzer results to parse /// A list of project to analyse - private List ScanReferences(ScanMode mode, IAnalyzerResults buildResult) + private List ScanReferences(ScanMode mode, IEnumerable buildResult) { var referencesToAdd = new List(); var isTestProject = buildResult.IsTestProject(); @@ -232,7 +248,7 @@ private IAnalyzerResults AnalyzeSingleProject(IProjectAnalyzer project, StrykerO } var projectLogName = Path.GetRelativePath(options.WorkingDirectory, project.ProjectFile.Path); _logger.LogDebug("Analyzing {ProjectFilePath}", projectLogName); - var buildResult = project.Build([options.TargetFramework]); + var buildResult = project.Build(); var buildResultOverallSuccess = buildResult.OverallSuccess || Array. TrueForAll(project.ProjectFile.TargetFrameworks,tf => @@ -256,7 +272,7 @@ private IAnalyzerResults AnalyzeSingleProject(IProjectAnalyzer project, StrykerO Restore = true }; // retry the analysis - buildResult = project.Build([options.TargetFramework], buildOptions); + buildResult = project.Build(buildOptions); // check the new status buildResultOverallSuccess = Array.TrueForAll(project.ProjectFile.TargetFrameworks,tf => @@ -307,12 +323,12 @@ private void LogAnalyzerResult(IAnalyzerResults analyzerResults, StrykerOptions { log.AppendLine($"Property {property}={properties.GetValueOrDefault(property)??"\"'undefined'\""}"); } - foreach (var sourceFile in analyzerResult.SourceFiles ?? Enumerable.Empty()) + foreach (var sourceFile in analyzerResult.SourceFiles) { log.AppendLine($"SourceFile {sourceFile}"); } - foreach (var reference in analyzerResult.References ?? Enumerable.Empty()) + foreach (var reference in analyzerResult.References) { log.AppendLine($"References: {Path.GetFileName(reference)} (in {Path.GetDirectoryName(reference)})"); } @@ -330,16 +346,17 @@ private void LogAnalyzerResult(IAnalyzerResults analyzerResults, StrykerOptions public IAnalyzerResult SelectAnalyzerResult(IEnumerable analyzerResults, string targetFramework) { - var validResults = analyzerResults.Where(a => a.TargetFramework is not null).ToList(); + var validResults = analyzerResults.ToList(); + var projectName = analyzerResults.First().ProjectFilePath; if (validResults.Count == 0) { - throw new InputException("No valid project analysis results could be found."); + throw new InputException($"No valid project analysis results could be found for '{projectName}'."); } if (targetFramework is null) { // we try to avoid desktop versions - return validResults.Find(a => a.Succeeded && !a.TargetsFullFramework()) ?? validResults[0]; + return PickFrameworkVersion(); } var resultForRequestedFramework = validResults.Find(a => a.TargetFramework == targetFramework); @@ -347,24 +364,38 @@ public IAnalyzerResult SelectAnalyzerResult(IEnumerable analyze { return resultForRequestedFramework; } + // if there is only one available framework version, we log an info + if (validResults.Count == 1) + { + var singleAnalyzerResult = validResults[0]; + _logger.LogInformation( + "Could not find a valid analysis for target {0} for project '{1}'. Selected version is {2}.", + targetFramework, projectName, singleAnalyzerResult.TargetFramework); + return singleAnalyzerResult; + } - var firstAnalyzerResult = validResults[0]; + var firstAnalyzerResult = PickFrameworkVersion(); var availableFrameworks = validResults.Select(a => a.TargetFramework).Distinct(); var firstFramework = firstAnalyzerResult.TargetFramework; _logger.LogWarning( """ - Could not find a project analysis for the chosen target framework {0}. - The available target frameworks are: {1}. - first available framework will be selected, which is {2}. - """, targetFramework, string.Join(',', availableFrameworks), firstFramework); + Could not find a valid analysis for target {0} for project '{1}'. + The available target frameworks are: {2}. + selected version is {3}. + """, targetFramework, projectName, string.Join(',', availableFrameworks), firstFramework); return firstAnalyzerResult; + + IAnalyzerResult PickFrameworkVersion() + { + return validResults.Find(a => a.Succeeded && !a.TargetsFullFramework()) ?? validResults[0]; + } } // checks if an analyzer result is valid - private static bool IsValid(IAnalyzerResult br) => br.Succeeded || (br.SourceFiles.Length > 0 && br.References.Length > 0 && br.TargetFramework !=null); + private static bool IsValid(IAnalyzerResult br) => br.Succeeded || (br.SourceFiles.Length > 0 && br.References.Length > 0); - private static Dictionary> FindMutableAnalyzerResults(ConcurrentBag<(IAnalyzerResults result, bool isTest)> mutableProjectsAnalyzerResults) + private static Dictionary> FindMutableAnalyzerResults(ConcurrentBag<(IEnumerable result, bool isTest)> mutableProjectsAnalyzerResults) { var mutableToTestMap = new Dictionary>(); var analyzerTestProjects = mutableProjectsAnalyzerResults.Where(p => p.isTest).SelectMany(p=>p.result).Where(p => p.BuildsAnAssembly()); @@ -493,7 +524,6 @@ private string FindProjectFile(string path) } } - private static StringBuilder BuildReferenceChoice(IEnumerable projectReferences) { var builder = new StringBuilder(); @@ -506,7 +536,6 @@ private static StringBuilder BuildReferenceChoice(IEnumerable projectRef return builder; } - private sealed class DynamicEnumerableQueue { private readonly Queue _queue; diff --git a/src/Stryker.Core/Stryker.Core/ToolHelpers/MsBuildHelper.cs b/src/Stryker.Core/Stryker.Core/ToolHelpers/MsBuildHelper.cs index fe69c3cdde..33ae641223 100644 --- a/src/Stryker.Core/Stryker.Core/ToolHelpers/MsBuildHelper.cs +++ b/src/Stryker.Core/Stryker.Core/ToolHelpers/MsBuildHelper.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.IO; using System.IO.Abstractions; -using System.Linq; using Microsoft.Extensions.Logging; using Stryker.Core.Logging; using Stryker.Core.Testing;