Skip to content

Commit

Permalink
Misc: improve project analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
dupdob committed Feb 5, 2025
1 parent db3ebc4 commit 058f788
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 24 deletions.
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

0 comments on commit 058f788

Please sign in to comment.