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

feat: use buildalyzer to identify source files #898

Merged
merged 13 commits into from
Jan 27, 2020
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -1097,7 +1097,7 @@
"targetproject": {
"type": "Project",
"dependencies": {
"Library": "0.15.0"
"Library": "1.0.0"
}
}
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,45 @@ public void InitializeShouldFindFilesRecursively()
result.ProjectContents.GetAllFiles().Count().ShouldBe(2);
}

[Fact]
public void InitializeShouldUseBuildAlyzerResult()
richardwerkman marked this conversation as resolved.
Show resolved Hide resolved
{
var fileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ projectUnderTestPath, new MockFileData(defaultTestProjectFileContents)},
{ Path.Combine(_filesystemRoot, "ExampleProject", "Recursive.cs"), new MockFileData(sourceFile)},
{ Path.Combine(_filesystemRoot, "ExampleProject", "OneFolderDeeper", "Recursive.cs"), new MockFileData(sourceFile)},
{ testProjectPath, new MockFileData(defaultTestProjectFileContents)},
{ Path.Combine(_filesystemRoot, "ExampleProject", "bin", "Debug", "netcoreapp2.1"), new MockFileData("Bytecode") }, // bin should be excluded
{ Path.Combine(_filesystemRoot, "ExampleProject", "obj", "Debug", "netcoreapp2.1", "ExampleProject.AssemblyInfo.cs"), new MockFileData("Bytecode") }, // obj should be excluded
{ Path.Combine(_filesystemRoot, "ExampleProject", "obj", "Release", "netcoreapp2.1"), new MockFileData("Bytecode") }, // obj should be excluded
{ Path.Combine(_filesystemRoot, "ExampleProject", "node_modules", "Some package"), new MockFileData("bla") }, // node_modules should be excluded
});

var projectFileReaderMock = new Mock<IProjectFileReader>(MockBehavior.Strict);
projectFileReaderMock.Setup(x => x.AnalyzeProject(testProjectPath, null))
.Returns(new ProjectAnalyzerResult(null, null)
{
ProjectReferences = new List<string>() { projectUnderTestPath },
TargetFrameworkVersionString = "netcoreapp2.1",
ProjectFilePath = testProjectPath,
References = new string[] { "" }
});
projectFileReaderMock.Setup(x => x.AnalyzeProject(projectUnderTestPath, null))
.Returns(new ProjectAnalyzerResult(null, null)
{
ProjectReferences = new List<string>(),
TargetFrameworkVersionString = "netcoreapp2.1",
ProjectFilePath = projectUnderTestPath,
SourceFiles = fileSystem.AllFiles.Where(s => s.EndsWith(".cs"))
});
var target = new InputFileResolver(fileSystem, projectFileReaderMock.Object);

var result = target.ResolveInput(new StrykerOptions(fileSystem: fileSystem, basePath: _basePath));

result.ProjectContents.GetAllFiles().Count().ShouldBe(2);
}

[Fact]
public void InitializeShouldFindSpecifiedTestProjectFile()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,24 @@ public void FilesToExclude_should_be_converted_to_file_patterns(string fileToExc
[Fact]
public void ShouldValidateApiKey()
{
var options = new StrykerOptions();

var ex = Assert.Throws<StrykerInputException>(() =>
const string strykerDashboardApiKey = "STRYKER_DASHBOARD_API_KEY";
var key = Environment.GetEnvironmentVariable(strykerDashboardApiKey);
try
{
new StrykerOptions(reporters: new string[] { "Dashboard" });
});
ex.Message.ShouldContain($"An API key is required when the {Reporter.Dashboard} reporter is turned on! You can get an API key at {options.DashboardUrl}");
ex.Message.ShouldContain($"A project name is required when the {Reporter.Dashboard} reporter is turned on!");
var options = new StrykerOptions();
Environment.SetEnvironmentVariable(strykerDashboardApiKey, string.Empty);

var ex = Assert.Throws<StrykerInputException>(() =>
{
new StrykerOptions(reporters: new string[] { "Dashboard" });
});
ex.Message.ShouldContain($"An API key is required when the {Reporter.Dashboard} reporter is turned on! You can get an API key at {options.DashboardUrl}");
ex.Message.ShouldContain($"A project name is required when the {Reporter.Dashboard} reporter is turned on!");
}
finally
{
Environment.SetEnvironmentVariable(strykerDashboardApiKey, key);
}
}

[Fact]
Expand Down
124 changes: 117 additions & 7 deletions src/Stryker.Core/Stryker.Core/Initialisation/InputFileResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ public InputFileResolver(IFileSystem fileSystem, IProjectFileReader projectFileR
public ProjectInfo ResolveInput(StrykerOptions options)
{
var result = new ProjectInfo();

var testProjectFile = FindProjectFile(options.BasePath, options.TestProjectNameFilter);

// Analyze the test project
Expand All @@ -56,15 +55,32 @@ public ProjectInfo ResolveInput(StrykerOptions options)
// Determine project under test
var reader = new ProjectFileReader();
var projectUnderTest = reader.DetermineProjectUnderTest(result.TestProjectAnalyzerResult.ProjectReferences, options.ProjectUnderTestNameFilter);

_logger.LogInformation("The project {0} will be mutated", projectUnderTest);

// Analyze project under test
result.ProjectUnderTestAnalyzerResult = _projectFileReader.AnalyzeProject(projectUnderTest, options.SolutionPath);

FolderComposite inputFiles;
if (result.ProjectUnderTestAnalyzerResult.SourceFiles!=null && result.ProjectUnderTestAnalyzerResult.SourceFiles.Any())
{
inputFiles = FindProjectFilesUsingBuildAlyzer(result.ProjectUnderTestAnalyzerResult);
}
else
{
inputFiles = FindProjectFilesScanningProjectFolders(result.ProjectUnderTestAnalyzerResult);
}
result.ProjectContents = inputFiles;

ValidateResult(result, options);

return result;
}

private FolderComposite FindProjectFilesScanningProjectFolders(ProjectAnalyzerResult analyzerResult)
{
var inputFiles = new FolderComposite();
var projectUnderTestDir = Path.GetDirectoryName(result.ProjectUnderTestAnalyzerResult.ProjectFilePath);
foreach (var dir in ExtractProjectFolders(result.ProjectUnderTestAnalyzerResult))
var projectUnderTestDir = Path.GetDirectoryName(analyzerResult.ProjectFilePath);
foreach (var dir in ExtractProjectFolders(analyzerResult))
{
var folder = _fileSystem.Path.Combine(Path.GetDirectoryName(projectUnderTestDir), dir);

Expand All @@ -73,13 +89,107 @@ public ProjectInfo ResolveInput(StrykerOptions options)
{
throw new DirectoryNotFoundException($"Can't find {folder}");
}

inputFiles.Add(FindInputFiles(folder, projectUnderTestDir));
}
result.ProjectContents = inputFiles;

ValidateResult(result, options);
return inputFiles;
}

return result;
private FolderComposite FindProjectFilesUsingBuildAlyzer(ProjectAnalyzerResult analyzerResult)
{
var inputFiles = new FolderComposite();
var projectUnderTestDir = Path.GetDirectoryName(analyzerResult.ProjectFilePath);
var projectRoot = Path.GetDirectoryName(projectUnderTestDir);
var generatedAssemblyInfo =
(_fileSystem.Path.GetFileNameWithoutExtension(analyzerResult.ProjectFilePath) + ".AssemblyInfo.cs").ToLowerInvariant();
var rootFolderComposite = new FolderComposite()
{
Name = string.Empty,
FullPath = projectRoot,
RelativePath = string.Empty,
RelativePathToProjectFile =
Path.GetRelativePath(projectUnderTestDir, projectUnderTestDir)
};
var cache = new Dictionary<string, FolderComposite> {[string.Empty] = rootFolderComposite};
inputFiles.Add(rootFolderComposite);
foreach (var sourceFile in analyzerResult.SourceFiles)
{
if (sourceFile.EndsWith(".xaml.cs"))
{
continue;
}

if (_fileSystem.Path.GetFileName(sourceFile).ToLowerInvariant() == generatedAssemblyInfo)
{
continue;
}

var relativePath = Path.GetRelativePath(projectUnderTestDir, sourceFile);
var folderComposite = GetOrBuildFolderComposite(cache, Path.GetDirectoryName(relativePath), projectUnderTestDir,
projectRoot, inputFiles);
var fileName = Path.GetFileName(sourceFile);
folderComposite.Add(new FileLeaf()
{
SourceCode = _fileSystem.File.ReadAllText(sourceFile),
Name = _fileSystem.Path.GetFileName(sourceFile),
RelativePath = _fileSystem.Path.Combine(folderComposite.RelativePath, fileName),
FullPath = sourceFile,
RelativePathToProjectFile = Path.GetRelativePath(projectUnderTestDir, sourceFile)
});
}

return inputFiles;
}

// get the FolderComposite object representing the the project's folder 'targetFolder'. Build the needed FolderComposite(s) for a complete path
private FolderComposite GetOrBuildFolderComposite(IDictionary<string, FolderComposite> cache, string targetFolder, string projectUnderTestDir,
string projectRoot, ProjectComponent inputFiles)
{
if (cache.ContainsKey(targetFolder))
{
return cache[targetFolder];
}

var folder = targetFolder;
FolderComposite subDir = null;
while (!string.IsNullOrEmpty(folder))
{
if (!cache.ContainsKey(folder))
{
// we have not scanned this folder yet
var sub = Path.GetFileName(folder);
var fullPath = _fileSystem.Path.Combine(projectUnderTestDir, sub);
var newComposite = new FolderComposite
{
Name = sub,
FullPath = fullPath,
RelativePath = Path.GetRelativePath(projectRoot, fullPath),
RelativePathToProjectFile =
Path.GetRelativePath(projectUnderTestDir, fullPath)
};
if (subDir != null)
{
newComposite.Add(subDir);
}

cache.Add(folder, newComposite);
subDir = newComposite;
folder = Path.GetDirectoryName(folder);
if (string.IsNullOrEmpty(folder))
{
// we are at root
inputFiles.Add(subDir);
}
}
else
{
cache[folder].Add(subDir);
break;
}
}

return cache[targetFolder];
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
using Stryker.Core.Exceptions;
using Stryker.Core.Logging;
using Stryker.Core.Testing;
using Stryker.Core.ToolHelpers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
Expand Down
9 changes: 9 additions & 0 deletions src/Stryker.Core/Stryker.Core/Initialisation/ProjectInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ public IEnumerable<string> ProjectReferences
set => _projectReferences = value;
}

private IEnumerable<string> _sourceFiles;

public IEnumerable<string> SourceFiles
{
get => _sourceFiles ?? _analyzerResult?.SourceFiles;
set => _sourceFiles = value;
}

private IReadOnlyDictionary<string, string> _properties;

public IReadOnlyDictionary<string, string> Properties
Expand Down Expand Up @@ -126,6 +134,7 @@ public Version TargetFrameworkVersion
}

private IList<string> _defineConstants;

public IList<string> DefineConstants
{
get => _defineConstants ?? BuildDefineConstants();
Expand Down
4 changes: 4 additions & 0 deletions src/Stryker.Core/Stryker.Core/Stryker.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
<Compile Remove="InjectedHelpers\MutantControl.cs" />
</ItemGroup>

<ItemGroup>
<None Remove="Stryker.Core.csproj.DotSettings" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Stryker.DataCollector\Stryker.DataCollector\Stryker.DataCollector.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyVersion>0.15.0.0</AssemblyVersion>
<FileVersion>0.15.0.0</FileVersion>
<Version>0.15.0</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Expand Down