Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve project analysis step #3184

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using Moq;
using Stryker.Core.Initialisation.Buildalyzer;
using Stryker.Core.Testing;
using Stryker.Core.UnitTest;

namespace Stryker.Core.UnitTest.Initialisation;

Expand Down Expand Up @@ -79,12 +78,12 @@ public static Dictionary<string, string> GetSourceProjectDefaultProperties()
/// <param name="success"></param>
/// <returns>a mock project analyzer</returns>
/// <remarks>the test project references the production code project and contains no source file</remarks>
protected Mock<IProjectAnalyzer> TestProjectAnalyzerMock(string testCsprojPathName, string csProj, IEnumerable<string> frameworks = null, bool success = true)
protected Mock<IProjectAnalyzer> TestProjectAnalyzerMock(string testCsprojPathName, string csProj, IEnumerable<string> frameworks = null, bool success = true, bool dontGenerateProjectReference= false)
{
frameworks??=[DefaultFramework];
var properties = new Dictionary<string, string>{ { "IsTestProject", "True" }, { "Language", "C#" } };
var projectReferences = string.IsNullOrEmpty(csProj) ? [] : GetProjectResult(csProj, frameworks.First()).ProjectReferences.Append(csProj).ToList();
return BuildProjectAnalyzerMock(testCsprojPathName, [], properties, projectReferences, frameworks, () => success);
return BuildProjectAnalyzerMock(testCsprojPathName, [], properties, projectReferences, frameworks, () => success, [], dontGenerateProjectReference);
}

private IAnalyzerResult GetProjectResult(string projectFile, string expectedFramework, bool returnDefaultIfNotFound = true)
Expand Down Expand Up @@ -199,7 +198,8 @@ internal Mock<IProjectAnalyzer> BuildProjectAnalyzerMock(string csprojPathName,
IEnumerable<string> projectReferences= null,
IEnumerable<string> frameworks = null,
Func<bool> success = null,
IEnumerable<string> rawReferences = null)
IEnumerable<string> rawReferences = null,
bool dontResolveProjectReference = false)
{
var projectFileMock = new Mock<IProjectFile>(MockBehavior.Strict);
success ??= () => true;
Expand All @@ -226,9 +226,17 @@ internal Mock<IProjectAnalyzer> BuildProjectAnalyzerMock(string csprojPathName,
FileSystem.AddFile(FileSystem.Path.Combine(projectUnderTestBin, projectBin), new MockFileData(""));
var projectAnalyzerResultMock = new Mock<IAnalyzerResult>(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());
if (dontResolveProjectReference)
{
projectAnalyzerResultMock.Setup(x => x.References).Returns([]);
}
else
{
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ public void ProjectAnalyzerShouldDecodeFramework(string tfm, string framework, s
}

[TestMethod]
[DataRow("")]
[DataRow("nxt")]
[DataRow("mono4.6")]
[DataRow("netcoreapp1.2.3.4.5")]
Expand Down Expand Up @@ -1274,6 +1273,33 @@ public void ShouldThrowWhenTheNameMatchesNone()

}

[TestMethod]
public void ShouldFallbackToProjectReferenceIfDependencyNotFound()
{
var fileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ _sourceProjectPath, new MockFileData(_defaultTestProjectFileContents)},
{ Path.Combine(_sourcePath, "source.cs"), new MockFileData(_sourceFile)},
{ _testProjectPath, new MockFileData(_defaultTestProjectFileContents)},
});

var sourceProjectManagerMock = SourceProjectAnalyzerMock(_sourceProjectPath, fileSystem.AllFiles.Where(s => s.EndsWith(".cs")).ToArray());
var testProjectManagerMock = TestProjectAnalyzerMock(_testProjectPath, _sourceProjectPath, ["netcoreapp2.1"], dontGenerateProjectReference: true);

var analyzerResults = new Dictionary<string, IProjectAnalyzer>
{
{ "MyProject", sourceProjectManagerMock.Object },
{ "MyProject.UnitTests", testProjectManagerMock.Object }
};
BuildBuildAnalyzerMock(analyzerResults);

var target = new InputFileResolver(fileSystem, BuildalyzerProviderMock.Object, _nugetMock.Object);

var result = target.ResolveSourceProjectInfos(_options).First();

result.AnalyzerResult.ProjectFilePath.ShouldBe(_sourceProjectPath);
}

[TestMethod]
[DataRow("ExampleProject/ExampleProject.csproj")]
[DataRow("ExampleProject\\ExampleProject.csproj")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ public Assembly LoadFromPath(string fullPath)

internal static NuGetFramework GetNuGetFramework(this IAnalyzerResult analyzerResult)
{
if (string.IsNullOrEmpty(analyzerResult.TargetFramework))
return null;
var framework = NuGetFramework.Parse(analyzerResult.TargetFramework);
if (framework != NuGetFramework.UnsupportedFramework)
{
Expand All @@ -160,7 +162,7 @@ internal static NuGetFramework GetNuGetFramework(this IAnalyzerResult analyzerRe
throw new InputException(message);
}

internal static bool TargetsFullFramework(this IAnalyzerResult analyzerResult) => analyzerResult.GetNuGetFramework().IsDesktop();
internal static bool TargetsFullFramework(this IAnalyzerResult analyzerResult) => analyzerResult.GetNuGetFramework()?.IsDesktop() == true;

public static Language GetLanguage(this IAnalyzerResult analyzerResult) =>
analyzerResult.GetPropertyOrDefault("Language") switch
Expand Down
60 changes: 45 additions & 15 deletions src/Stryker.Core/Stryker.Core/Initialisation/InputFileResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -400,35 +400,65 @@ IAnalyzerResult PickFrameworkVersion()
// checks if an analyzer result is valid
private static bool IsValid(IAnalyzerResult br) => br.Succeeded || (br.SourceFiles.Length > 0 && br.References.Length > 0);

private static Dictionary<IAnalyzerResult, List<IAnalyzerResult>> FindMutableAnalyzerResults(ConcurrentBag<(IEnumerable<IAnalyzerResult> result, bool isTest)> mutableProjectsAnalyzerResults)
private Dictionary<IAnalyzerResult, List<IAnalyzerResult>> FindMutableAnalyzerResults(ConcurrentBag<(IEnumerable<IAnalyzerResult> result, bool isTest)> mutableProjectsAnalyzerResults)
{
var mutableToTestMap = new Dictionary<IAnalyzerResult, List<IAnalyzerResult>>();
var analyzerTestProjects = mutableProjectsAnalyzerResults.Where(p => p.isTest).SelectMany(p => p.result).Where(p => p.BuildsAnAssembly());
var mutableProjects = mutableProjectsAnalyzerResults.Where(p => !p.isTest).SelectMany(p => p.result).Where(p => p.BuildsAnAssembly()).ToArray();
// for each test project
foreach (var testProject in analyzerTestProjects)
{
// we identify which project are referenced by it
foreach (var mutableProject in mutableProjects)
if (!ScanAssemblyReferences(mutableToTestMap, mutableProjects, testProject))
{
if (Array.TrueForAll(testProject.References, r =>
!r.Equals(mutableProject.GetAssemblyPath(), StringComparison.OrdinalIgnoreCase) &&
!r.Equals(mutableProject.GetReferenceAssemblyPath(), StringComparison.OrdinalIgnoreCase)))
{
continue;
}
if (!mutableToTestMap.TryGetValue(mutableProject, out var dependencies))
{
dependencies = [];
mutableToTestMap[mutableProject] = dependencies;
}
dependencies.Add(testProject);
_logger.LogInformation("Could not find an assembly reference to a mutable assembly for project {0}. Will look into project references.", testProject.ProjectFilePath);
// we try to find a project reference
ScanProjectReferences(mutableToTestMap, mutableProjects, testProject);
}
}

return mutableToTestMap;
}

private static void ScanProjectReferences(Dictionary<IAnalyzerResult, List<IAnalyzerResult>> mutableToTestMap, IAnalyzerResult[] mutableProjects, IAnalyzerResult testProject)
{
var mutableProject = mutableProjects.FirstOrDefault(p => testProject.ProjectReferences.Contains(p.ProjectFilePath));
if (mutableProject == null)
{
return;
}
if (!mutableToTestMap.TryGetValue(mutableProject, out var dependencies))
{
mutableToTestMap[mutableProject] = dependencies = [];
}

dependencies.Add(testProject);
}

private static bool ScanAssemblyReferences(Dictionary<IAnalyzerResult, List<IAnalyzerResult>> mutableToTestMap, IAnalyzerResult[] mutableProjects, IAnalyzerResult testProject)
{
var foundOneProject = false;
// we identify which project are referenced by it
foreach (var mutableProject in mutableProjects)
{
var assemblyPath = mutableProject.GetAssemblyPath();
var refAssemblyPath = mutableProject.GetReferenceAssemblyPath();

if (Array.TrueForAll(testProject.References, r => !r.Equals(assemblyPath, StringComparison.OrdinalIgnoreCase) &&
!r.Equals(refAssemblyPath, StringComparison.OrdinalIgnoreCase)))
{
continue;
}
if (!mutableToTestMap.TryGetValue(mutableProject, out var dependencies))
{
mutableToTestMap[mutableProject] = dependencies = [];
}
dependencies.Add(testProject);
foundOneProject = true;
}

return foundOneProject;
}

/// <summary>
/// Builds a <see cref="SourceProjectInfo"/> instance describing a project its associated test project(s)
/// </summary>
Expand Down
Loading