Skip to content

Commit

Permalink
Merge pull request #1 from tonerdo/master
Browse files Browse the repository at this point in the history
update fork
  • Loading branch information
daveMueller authored Aug 17, 2019
2 parents 83e93db + 428034d commit a6e0687
Show file tree
Hide file tree
Showing 31 changed files with 1,011 additions and 48 deletions.
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

0 comments on commit a6e0687

Please sign in to comment.