Skip to content
Merged
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
7 changes: 7 additions & 0 deletions src/coverlet.core/Abstracts/IConsole.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Coverlet.Core.Abstracts
{
internal interface IConsole
{
public void WriteLine(string value);
}
}
1 change: 1 addition & 0 deletions src/coverlet.core/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ private static IServiceProvider InitDefaultServices()
serviceCollection.AddTransient<IRetryHelper, RetryHelper>();
serviceCollection.AddTransient<IProcessExitHandler, ProcessExitHandler>();
serviceCollection.AddTransient<IFileSystem, FileSystem>();
serviceCollection.AddTransient<IConsole, SystemConsole>();

// We need to keep singleton/static semantics
serviceCollection.AddSingleton<IInstrumentationHelper, InstrumentationHelper>();
Expand Down
14 changes: 14 additions & 0 deletions src/coverlet.core/Helpers/Console.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

using Coverlet.Core.Abstracts;

namespace Coverlet.Core.Helpers
{
public class SystemConsole : IConsole
{
public void WriteLine(string value)
{
Console.WriteLine(value);
}
}
}
23 changes: 15 additions & 8 deletions src/coverlet.msbuild.tasks/CoverageResultTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class CoverageResultTask : Task
private double _threshold;
private string _thresholdType;
private string _thresholdStat;
private string _coverletMultiTargetFrameworksCurrentTFM;
private ITaskItem _instrumenterState;
private MSBuildLogger _logger;

Expand Down Expand Up @@ -65,6 +66,12 @@ public ITaskItem InstrumenterState
set { _instrumenterState = value; }
}

public string CoverletMultiTargetFrameworksCurrentTFM
{
get { return _coverletMultiTargetFrameworksCurrentTFM; }
set { _coverletMultiTargetFrameworksCurrentTFM = value; }
}

public CoverageResultTask()
{
_logger = new MSBuildLogger(Log);
Expand Down Expand Up @@ -128,14 +135,14 @@ public override bool Execute()
}
else
{
// Output to file
var filename = Path.GetFileName(_output);
filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename;
filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}";

var report = Path.Combine(directory, filename);
Console.WriteLine($" Generating report '{report}'");
fileSystem.WriteAllText(report, reporter.Report(result));
ReportWriter writer = new ReportWriter(_coverletMultiTargetFrameworksCurrentTFM,
directory,
_output,
reporter,
fileSystem,
DependencyInjection.Current.GetService<IConsole>(),
result);
writer.WriteReport();
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/coverlet.msbuild.tasks/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
[assembly: System.Reflection.AssemblyKeyFileAttribute("coverlet.msbuild.tasks.snk")]
using System.Reflection;
using System.Runtime.CompilerServices;

[assembly: AssemblyKeyFile("coverlet.msbuild.tasks.snk")]
[assembly: InternalsVisibleTo("coverlet.core.tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100757cf9291d78a82e5bb58a827a3c46c2f959318327ad30d1b52e918321ffbd847fb21565b8576d2a3a24562a93e86c77a298b564a0f1b98f63d7a1441a3a8bcc206da3ed09d5dacc76e122a109a9d3ac608e21a054d667a2bae98510a1f0f653c0e6f58f42b4b3934f6012f5ec4a09b3dfd3e14d437ede1424bdb722aead64ad")]
53 changes: 53 additions & 0 deletions src/coverlet.msbuild.tasks/ReportWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.IO;

using Coverlet.Core;
using Coverlet.Core.Abstracts;
using Coverlet.Core.Reporters;

namespace Coverlet.MSbuild.Tasks
{
internal class ReportWriter
{
private readonly string _coverletMultiTargetFrameworksCurrentTFM;
private readonly string _directory;
private readonly string _output;
private readonly IReporter _reporter;
private readonly IFileSystem _fileSystem;
private readonly IConsole _console;
private readonly CoverageResult _result;

public ReportWriter(string coverletMultiTargetFrameworksCurrentTFM, string directory, string output, IReporter reporter, IFileSystem fileSystem, IConsole console, CoverageResult result)
=> (_coverletMultiTargetFrameworksCurrentTFM, _directory, _output, _reporter, _fileSystem, _console, _result) =
(coverletMultiTargetFrameworksCurrentTFM, directory, output, reporter, fileSystem, console, result);

public void WriteReport()
{
string filename = Path.GetFileName(_output);

string separatorPoint = string.IsNullOrEmpty(_coverletMultiTargetFrameworksCurrentTFM) ? "" : ".";

if (filename == string.Empty)
{
// empty filename for instance only directory is passed to CoverletOutput c:\reportpath
// c:\reportpath\coverage.reportedextension
filename = $"coverage.{_coverletMultiTargetFrameworksCurrentTFM}{separatorPoint}{_reporter.Extension}";
}
else if (Path.HasExtension(filename))
{
// filename with extension for instance c:\reportpath\file.ext
// c:\reportpath\file.ext.reportedextension
filename = $"{Path.GetFileNameWithoutExtension(filename)}{separatorPoint}{_coverletMultiTargetFrameworksCurrentTFM}{Path.GetExtension(filename)}.{_reporter.Extension}";
}
else
{
// filename without extension for instance c:\reportpath\file
// c:\reportpath\file.reportedextension
filename = $"{filename}{separatorPoint}{_coverletMultiTargetFrameworksCurrentTFM}.{_reporter.Extension}";
}

string report = Path.Combine(_directory, filename);
_console.WriteLine($" Generating report '{report}'");
_fileSystem.WriteAllText(report, _reporter.Report(_result));
}
}
}
6 changes: 5 additions & 1 deletion src/coverlet.msbuild.tasks/coverlet.msbuild.targets
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,17 @@
Condition="'$(VSTestNoBuild)' != 'true' and '$(CollectCoverage)' == 'true'" />

<Target Name="GenerateCoverageResult">
<PropertyGroup>
<_coverletMultiTargetFrameworksCurrentTFM Condition="'$(TargetFrameworks)' != ''" >$(TargetFramework)</_coverletMultiTargetFrameworksCurrentTFM>
</PropertyGroup>
<Coverlet.MSbuild.Tasks.CoverageResultTask
Output="$(CoverletOutput)"
OutputFormat="$(CoverletOutputFormat)"
Threshold="$(Threshold)"
ThresholdType="$(ThresholdType)"
ThresholdStat="$(ThresholdStat)"
InstrumenterState="$(InstrumenterState)"/>
InstrumenterState="$(InstrumenterState)"
CoverletMultiTargetFrameworksCurrentTFM="$(_coverletMultiTargetFrameworksCurrentTFM)" />
</Target>

<Target Name="GenerateCoverageResultAfterTest"
Expand Down
55 changes: 55 additions & 0 deletions test/coverlet.core.tests/Reporters/Reporters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.IO;

using Coverlet.Core.Abstracts;
using Coverlet.MSbuild.Tasks;
using Moq;
using Xunit;

namespace Coverlet.Core.Reporters.Tests
{
public class Reporters
{
// we use lcov with extension .info and cobertura with extension .cobertura.xml
// to have all possible extension format
// empty coverletOutput is not possible thank's to props default
[Theory]
// single tfm
[InlineData("", "/folder/reportFolder/", "lcov", "/folder/reportFolder/coverage.info")]
[InlineData(null, "/folder/reportFolder/", "cobertura", "/folder/reportFolder/coverage.cobertura.xml")]
[InlineData(null, "/folder/reportFolder/file.ext", "cobertura", "/folder/reportFolder/file.ext.cobertura.xml")]
[InlineData(null, "/folder/reportFolder/file.ext1.ext2", "cobertura", "/folder/reportFolder/file.ext1.ext2.cobertura.xml")]
[InlineData(null, "/folder/reportFolder/file", "cobertura", "/folder/reportFolder/file.cobertura.xml")]
[InlineData(null, "file", "cobertura", "file.cobertura.xml")]
// multiple tfm
[InlineData("netcoreapp2.2", "/folder/reportFolder/", "lcov", "/folder/reportFolder/coverage.netcoreapp2.2.info")]
[InlineData("netcoreapp2.2", "/folder/reportFolder/", "cobertura", "/folder/reportFolder/coverage.netcoreapp2.2.cobertura.xml")]
[InlineData("net472", "/folder/reportFolder/file.ext", "cobertura", "/folder/reportFolder/file.net472.ext.cobertura.xml")]
[InlineData("net472", "/folder/reportFolder/file.ext1.ext2", "cobertura", "/folder/reportFolder/file.ext1.net472.ext2.cobertura.xml")]
[InlineData("netcoreapp2.2", "/folder/reportFolder/file", "cobertura", "/folder/reportFolder/file.netcoreapp2.2.cobertura.xml")]
[InlineData("netcoreapp2.2", "file", "cobertura", "file.netcoreapp2.2.cobertura.xml")]
public void Msbuild_ReportWriter(string coverletMultiTargetFrameworksCurrentTFM, string coverletOutput, string reportFormat, string expectedFileName)
{
Mock<IFileSystem> fileSystem = new Mock<IFileSystem>();
fileSystem.Setup(f => f.WriteAllText(It.IsAny<string>(), It.IsAny<string>()))
.Callback((string path, string contents) =>
{
// Path.Combine depends on OS so we can change only win side to avoid duplication
Assert.Equal(path.Replace('/', Path.DirectorySeparatorChar), expectedFileName.Replace('/', Path.DirectorySeparatorChar));
});

Mock<IConsole> console = new Mock<IConsole>();

ReportWriter reportWriter = new ReportWriter(
coverletMultiTargetFrameworksCurrentTFM,
// mimic code inside CoverageResultTask.cs
Path.GetDirectoryName(coverletOutput),
coverletOutput,
new ReporterFactory(reportFormat).CreateReporter(),
fileSystem.Object,
console.Object,
new CoverageResult() { Modules = new Modules() });

reportWriter.WriteReport();
}
}
}
1 change: 1 addition & 0 deletions test/coverlet.core.tests/coverlet.core.tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

<ItemGroup>
<ProjectReference Include="$(RepoRoot)src\coverlet.core\coverlet.core.csproj" />
<ProjectReference Include="$(RepoRoot)src\coverlet.msbuild.tasks\coverlet.msbuild.tasks.csproj" />
<ProjectReference Include="$(RepoRoot)test\coverlet.tests.remoteexecutor\coverlet.tests.remoteexecutor.csproj" />
<ProjectReference Include="$(RepoRoot)test\coverlet.tests.projectsample.empty\coverlet.tests.projectsample.empty.csproj" />
<ProjectReference Include="$(RepoRoot)test\coverlet.tests.projectsample.excludedbyattribute\coverlet.tests.projectsample.excludedbyattribute.csproj" />
Expand Down
92 changes: 80 additions & 12 deletions test/coverlet.integration.tests/BaseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Coverlet.Core;
using Newtonsoft.Json;
using NuGet.Packaging;
using Xunit;
using Xunit.Sdk;

namespace Coverlet.Integration.Tests
Expand Down Expand Up @@ -52,7 +53,7 @@ private protected string GetPackageVersion(string filter)
return manifest.Metadata.Version.OriginalVersion;
}

private protected ClonedTemplateProject CloneTemplateProject()
private protected ClonedTemplateProject CloneTemplateProject(bool cleanupOnDispose = true)
{
DirectoryInfo finalRoot = Directory.CreateDirectory(Guid.NewGuid().ToString("N"));
foreach (string file in (Directory.GetFiles($"../../../../coverlet.integration.template", "*.cs")
Expand All @@ -75,7 +76,7 @@ private protected ClonedTemplateProject CloneTemplateProject()

AddMicrosoftNETTestSdkRef(finalRoot.FullName);

return new ClonedTemplateProject() { Path = finalRoot.FullName };
return new ClonedTemplateProject(finalRoot.FullName, cleanupOnDispose);
}

private protected bool RunCommand(string command, string arguments, out string standardOutput, out string standardError, string workingDirectory = "")
Expand Down Expand Up @@ -205,34 +206,101 @@ private protected string AddCollectorRunsettingsFile(string projectPath)
return runsettingsPath;
}

private protected void AssertCoverage(ClonedTemplateProject clonedTemplateProject)
private protected void AssertCoverage(ClonedTemplateProject clonedTemplateProject, string filter = "coverage.json")
{
Modules modules = JsonConvert.DeserializeObject<Modules>(File.ReadAllText(clonedTemplateProject.GetFiles("coverage.json").Single()));
modules
.Document("DeepThought.cs")
.Class("Coverlet.Integration.Template.DeepThought")
.Method("System.Int32 Coverlet.Integration.Template.DeepThought::AnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything()")
.AssertLinesCovered((6, 1), (7, 1), (8, 1));
bool coverageChecked = false;
foreach (string coverageFile in clonedTemplateProject.GetFiles(filter))
{
JsonConvert.DeserializeObject<Modules>(File.ReadAllText(coverageFile))
.Document("DeepThought.cs")
.Class("Coverlet.Integration.Template.DeepThought")
.Method("System.Int32 Coverlet.Integration.Template.DeepThought::AnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything()")
.AssertLinesCovered((6, 1), (7, 1), (8, 1));
coverageChecked = true;
}

Assert.True(coverageChecked, "Coverage check fail");
}

private protected void UpdateProjectTargetFramework(ClonedTemplateProject project, params string[] targetFrameworks)
{
if (targetFrameworks is null || targetFrameworks.Length == 0)
{
throw new ArgumentException("Invalid targetFrameworks", nameof(targetFrameworks));
}

if (!File.Exists(project.ProjectFileNamePath))
{
throw new FileNotFoundException("coverlet.integration.template.csproj not found", "coverlet.integration.template.csproj");
}
XDocument xml;
using (var csprojStream = File.OpenRead(project.ProjectFileNamePath))
{
xml = XDocument.Load(csprojStream);
}

xml.Element("Project")
.Element("PropertyGroup")
.Element("TargetFramework")
.Remove();

XElement targetFrameworkElement;

if (targetFrameworks.Length == 1)
{
targetFrameworkElement = new XElement("TargetFramework", targetFrameworks[0]);
}
else
{
targetFrameworkElement = new XElement("TargetFrameworks", string.Join(';', targetFrameworks));
}

xml.Element("Project").Element("PropertyGroup").Add(targetFrameworkElement);
xml.Save(project.ProjectFileNamePath);
}

private protected void PinSDK(ClonedTemplateProject project, string sdkVersion)
{
if (string.IsNullOrEmpty(sdkVersion))
{
throw new ArgumentException("Invalid sdkVersion", nameof(sdkVersion));
}

if (!File.Exists(project.ProjectFileNamePath))
{
throw new FileNotFoundException("coverlet.integration.template.csproj not found", "coverlet.integration.template.csproj");
}

File.WriteAllText(Path.Combine(project.ProjectRootPath, "global.json"), $"{{ \"sdk\": {{ \"version\": \"{sdkVersion}\" }} }}");
}
}

class ClonedTemplateProject : IDisposable
{
public string Path { get; set; }
public string? ProjectRootPath { get; private set; }
public bool _cleanupOnDispose { get; set; }

// We need to have a different asm name to avoid issue with collectors, we filter [coverlet.*]* by default
// https://github.com/tonerdo/coverlet/pull/410#discussion_r284526728
public static string AssemblyName { get; } = "coverletsamplelib.integration.template";
public static string ProjectFileName { get; } = "coverlet.integration.template.csproj";
public string ProjectFileNamePath => Path.Combine(ProjectRootPath, "coverlet.integration.template.csproj");

public ClonedTemplateProject(string projectRootPath, bool cleanupOnDispose) => (ProjectRootPath, _cleanupOnDispose) = (projectRootPath, cleanupOnDispose);



public string[] GetFiles(string filter)
{
return Directory.GetFiles(this.Path, filter, SearchOption.AllDirectories);
return Directory.GetFiles(ProjectRootPath, filter, SearchOption.AllDirectories);
}

public void Dispose()
{
Directory.Delete(Path, true);
if (_cleanupOnDispose)
{
Directory.Delete(ProjectRootPath, true);
}
}
}
}
Loading