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

update fork #1

Merged
merged 8 commits into from
Aug 17, 2019
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
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Clone this repo:
Building, testing, and packing use all the standard dotnet commands:

dotnet build
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Include=[coverlet.*]*"
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Include="[coverlet.*]*"
dotnet pack

## Performance testing
Expand Down
26 changes: 26 additions & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project>
<ItemGroup>
<PackageReference Update="McMaster.Extensions.CommandLineUtils" Version="2.3.4" />
<PackageReference Update="Microsoft.Build.Utilities.Core" Version="15.5.180"/>
<PackageReference Update="Microsoft.CodeAnalysis.CSharp" Version="2.10.0" />
<PackageReference Update="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
<PackageReference Update="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
<PackageReference Update="Microsoft.Extensions.FileSystemGlobbing" Version="2.0.1" />
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Update="Microsoft.TestPlatform.ObjectModel" Version="16.1.0" />
<PackageReference Update="Mono.Cecil" Version="0.10.4" />
<PackageReference Update="Moq" Version="4.10.1" />
<!-- Do not upgrade this version or we won't support old SDK -->
<PackageReference Update="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Update="ReportGenerator.Core" Version="4.2.15" />
<!--
Do not change System.Reflection.Metadata version since we need to support VSTest DataCollectors. Goto https://www.nuget.org/packages/System.Reflection.Metadata to check versions.
We need to load assembly version 1.4.2.0 to properly work
We can check minimum supported package version here https://github.com/Microsoft/vstest/blob/master/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj#L37
-->
<PackageReference Update="System.Reflection.Metadata" Version="1.5.0" />
<PackageReference Update="xunit" Version="2.4.1" />
<PackageReference Update="xunit.assert" Version="2.4.1" />
<PackageReference Update="xunit.runner.visualstudio" Version="2.4.1"/>
</ItemGroup>
</Project>
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ Coverlet is a cross platform code coverage framework for .NET, with support for
```bash
dotnet add package coverlet.collector
```
N.B. You **MUST** add package only to test projects

**MSBuild Integration**:

```bash
dotnet add package coverlet.msbuild
```
N.B. You **MUST** add package only to test projects

**Global Tool**:

Expand Down
13 changes: 10 additions & 3 deletions coverlet.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
# Visual Studio Version 16
VisualStudioVersion = 16.0.28902.138
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E877EBA4-E78B-4F7D-A2D3-1E070FED04CD}"
EndProject
Expand All @@ -21,7 +21,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.core.performancete
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.collector", "src\coverlet.collector\coverlet.collector.csproj", "{F5B2C45B-274B-43D6-9565-8B50659CFE56}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.collector.tests", "test\coverlet.collector.tests\coverlet.collector.tests.csproj", "{5ED4FA81-8F8C-4211-BA88-7573BD63262E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.collector.tests", "test\coverlet.collector.tests\coverlet.collector.tests.csproj", "{5ED4FA81-8F8C-4211-BA88-7573BD63262E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.tests.remoteexecutor", "test\coverlet.tests.remoteexecutor\coverlet.tests.remoteexecutor.csproj", "{3E0F9E47-A1D7-4DF5-841D-A633486E2475}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -61,6 +63,10 @@ Global
{5ED4FA81-8F8C-4211-BA88-7573BD63262E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5ED4FA81-8F8C-4211-BA88-7573BD63262E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5ED4FA81-8F8C-4211-BA88-7573BD63262E}.Release|Any CPU.Build.0 = Release|Any CPU
{3E0F9E47-A1D7-4DF5-841D-A633486E2475}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3E0F9E47-A1D7-4DF5-841D-A633486E2475}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E0F9E47-A1D7-4DF5-841D-A633486E2475}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E0F9E47-A1D7-4DF5-841D-A633486E2475}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -74,6 +80,7 @@ Global
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
{F5B2C45B-274B-43D6-9565-8B50659CFE56} = {E877EBA4-E78B-4F7D-A2D3-1E070FED04CD}
{5ED4FA81-8F8C-4211-BA88-7573BD63262E} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
{3E0F9E47-A1D7-4DF5-841D-A633486E2475} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9CA57C02-97B0-4C38-A027-EA61E8741F10}
Expand Down
2 changes: 1 addition & 1 deletion src/coverlet.collector/coverlet.collector.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" Version="16.1.0" />
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" />
</ItemGroup>

<ItemGroup>
Expand Down
8 changes: 7 additions & 1 deletion src/coverlet.console/Program.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;

using ConsoleTables;
using Coverlet.Console.Logging;
using Coverlet.Core;
Expand Down Expand Up @@ -240,6 +241,11 @@ static int Main(string[] args)
app.ShowHelp();
return (int)CommandExitCodes.CommandParsingException;
}
catch (Win32Exception we) when (we.Source == "System.Diagnostics.Process")
{
logger.LogError($"Start process '{target.Value()}' failed with '{we.Message}'");
return exitCode > 0 ? exitCode : (int)CommandExitCodes.Exception;
}
catch (Exception ex)
{
logger.LogError(ex.Message);
Expand Down
2 changes: 1 addition & 1 deletion src/coverlet.console/coverlet.console.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.3.4" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" />
</ItemGroup>

<ItemGroup>
Expand Down
9 changes: 9 additions & 0 deletions src/coverlet.core/Abstracts/IProcessExitHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace Coverlet.Core.Abstracts
{
internal interface IProcessExitHandler
{
void Add(EventHandler handler);
}
}
10 changes: 10 additions & 0 deletions src/coverlet.core/Abstracts/IRetryHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace Coverlet.Core.Abstracts
{
internal interface IRetryHelper
{
void Retry(Action action, Func<TimeSpan> backoffStrategy, int maxAttemptCount = 3);
T Do<T>(Func<T> action, Func<TimeSpan> backoffStrategy, int maxAttemptCount = 3);
}
}
35 changes: 35 additions & 0 deletions src/coverlet.core/DependencyInjection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;

using Coverlet.Core.Abstracts;
using Coverlet.Core.Helpers;
using Microsoft.Extensions.DependencyInjection;

namespace Coverlet.Core
{
internal static class DependencyInjection
{
private static Lazy<IServiceProvider> _serviceProvider = new Lazy<IServiceProvider>(() => InitDefaultServices(), true);

public static IServiceProvider Current
{
get
{
return _serviceProvider.Value;
}
}

public static void Set(IServiceProvider serviceProvider)
{
_serviceProvider = new Lazy<IServiceProvider>(() => serviceProvider);
}

private static IServiceProvider InitDefaultServices()
{
IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IRetryHelper, RetryHelper>();
serviceCollection.AddTransient<IProcessExitHandler, ProcessExitHandler>();
return serviceCollection.BuildServiceProvider();
}

}
}
53 changes: 46 additions & 7 deletions src/coverlet.core/Helpers/InstrumentationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Text.RegularExpressions;

using Coverlet.Core.Abstracts;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;

namespace Coverlet.Core.Helpers
Expand All @@ -19,7 +21,7 @@ internal static class InstrumentationHelper

static InstrumentationHelper()
{
AppDomain.CurrentDomain.ProcessExit += (s, e) => RestoreOriginalModules();
DependencyInjection.Current.GetService<IProcessExitHandler>().Add((s, e) => RestoreOriginalModules());
}

public static string[] GetCoverableModules(string module, string[] directories, bool includeTestAssembly)
Expand Down Expand Up @@ -66,8 +68,9 @@ public static string[] GetCoverableModules(string module, string[] directories,
.ToArray();
}

public static bool HasPdb(string module)
public static bool HasPdb(string module, out bool embedded)
{
embedded = false;
using (var moduleStream = File.OpenRead(module))
using (var peReader = new PEReader(moduleStream))
{
Expand All @@ -79,6 +82,7 @@ public static bool HasPdb(string module)
if (codeViewData.Path == $"{Path.GetFileNameWithoutExtension(module)}.pdb")
{
// PDB is embedded
embedded = true;
return true;
}

Expand All @@ -90,6 +94,41 @@ public static bool HasPdb(string module)
}
}

public static bool EmbeddedPortablePdbHasLocalSource(string module)
{
using (FileStream moduleStream = File.OpenRead(module))
using (var peReader = new PEReader(moduleStream))
{
foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory())
{
if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb)
{
using (MetadataReaderProvider embeddedMetadataProvider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(entry))
{
MetadataReader metadataReader = embeddedMetadataProvider.GetMetadataReader();
foreach (DocumentHandle docHandle in metadataReader.Documents)
{
Document document = metadataReader.GetDocument(docHandle);
string docName = metadataReader.GetString(document.Name);

// We verify all docs and return false if not all are present in local
// We could have false negative if doc is not a source
// Btw check for all possible extension could be weak approach
if (!File.Exists(docName))
{
return false;
}
}
}
}
}
}

// If we don't have EmbeddedPortablePdb entry return true, for instance empty dll
// We should call this method only on embedded pdb module
return true;
}

public static void BackupOriginalModule(string module, string identifier)
{
var backupPath = GetBackupPath(module, identifier);
Expand Down Expand Up @@ -120,14 +159,14 @@ public static void RestoreOriginalModule(string module, string identifier)
// See: https://github.com/tonerdo/coverlet/issues/25
var retryStrategy = CreateRetryStrategy();

RetryHelper.Retry(() =>
DependencyInjection.Current.GetService<IRetryHelper>().Retry(() =>
{
File.Copy(backupPath, module, true);
File.Delete(backupPath);
_backupList.TryRemove(module, out string _);
}, retryStrategy, 10);

RetryHelper.Retry(() =>
DependencyInjection.Current.GetService<IRetryHelper>().Retry(() =>
{
if (File.Exists(backupSymbolPath))
{
Expand All @@ -148,7 +187,7 @@ public static void RestoreOriginalModules()
foreach (string key in _backupList.Keys.ToList())
{
string backupPath = _backupList[key];
RetryHelper.Retry(() =>
DependencyInjection.Current.GetService<IRetryHelper>().Retry(() =>
{
File.Copy(backupPath, key, true);
File.Delete(backupPath);
Expand All @@ -162,7 +201,7 @@ public static void DeleteHitsFile(string path)
// Retry hitting the hits file - retry up to 10 times, since the file could be locked
// See: https://github.com/tonerdo/coverlet/issues/25
var retryStrategy = CreateRetryStrategy();
RetryHelper.Retry(() => File.Delete(path), retryStrategy, 10);
DependencyInjection.Current.GetService<IRetryHelper>().Retry(() => File.Delete(path), retryStrategy, 10);
}

public static bool IsValidFilterExpression(string filter)
Expand Down
13 changes: 13 additions & 0 deletions src/coverlet.core/Helpers/ProcessExitHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using Coverlet.Core.Abstracts;

namespace Coverlet.Core.Helpers
{
internal class ProcessExitHandler : IProcessExitHandler
{
public void Add(EventHandler handler)
{
AppDomain.CurrentDomain.ProcessExit += handler;
}
}
}
7 changes: 4 additions & 3 deletions src/coverlet.core/Helpers/RetryHelper.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Coverlet.Core.Abstracts;
using System;
using System.Collections.Generic;
using System.Threading;
Expand All @@ -6,15 +7,15 @@ namespace Coverlet.Core.Helpers
{
// A slightly amended version of the code found here: https://stackoverflow.com/a/1563234/186184
// This code allows for varying backoff strategies through the use of Func<TimeSpan>.
public static class RetryHelper
public class RetryHelper : IRetryHelper
{
/// <summary>
/// Retry a void method.
/// </summary>
/// <param name="action">The action to perform</param>
/// <param name="backoffStrategy">A function returning a Timespan defining the backoff strategy to use.</param>
/// <param name="maxAttemptCount">The maximum number of retries before bailing out. Defaults to 3.</param>
public static void Retry(
public void Retry(
Action action,
Func<TimeSpan> backoffStrategy,
int maxAttemptCount = 3)
Expand All @@ -33,7 +34,7 @@ public static void Retry(
/// <param name="action">The action to perform</param>
/// <param name="backoffStrategy">A function returning a Timespan defining the backoff strategy to use.</param>
/// <param name="maxAttemptCount">The maximum number of retries before bailing out. Defaults to 3.</param>
public static T Do<T>(
public T Do<T>(
Func<T> action,
Func<TimeSpan> backoffStrategy,
int maxAttemptCount = 3)
Expand Down
24 changes: 23 additions & 1 deletion src/coverlet.core/Instrumentation/Instrumenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,29 @@ public bool CanInstrument()
{
try
{
return InstrumentationHelper.HasPdb(_module);
if (InstrumentationHelper.HasPdb(_module, out bool embeddedPdb))
{
if (embeddedPdb)
{
if (InstrumentationHelper.EmbeddedPortablePdbHasLocalSource(_module))
{
return true;
}
else
{
_logger.LogWarning($"Unable to instrument module: {_module}, embedded pdb without local source files");
return false;
}
}
else
{
return true;
}
}
else
{
return false;
}
}
catch (Exception ex)
{
Expand Down
Loading