Merge pull request #410 from vagisha-nidhi/coverletDatacollectors
Coverlet Intergration with VSTest
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
<RemoveDir Directories="$(OutputPath)" Condition="Exists('$(MSBuildThisFileDirectory)\build')" />
|
||||
<Exec Command="dotnet build "$(MSBuildThisFileDirectory)src\coverlet.msbuild.tasks\coverlet.msbuild.tasks.csproj" -c $(Configuration)" />
|
||||
<Exec Command="dotnet build "$(MSBuildThisFileDirectory)src\coverlet.console\coverlet.console.csproj" -c $(Configuration)" />
|
||||
<Exec Command="dotnet build "$(MSBuildThisFileDirectory)src\coverlet.collector\coverlet.collector.csproj" -c $(Configuration)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PublishMSBuildTaskProject" AfterTargets="BuildAllProjects">
|
||||
@@ -25,11 +26,13 @@
|
||||
|
||||
<Target Name="RunTests" AfterTargets="CopyMSBuildScripts">
|
||||
<Exec Command="dotnet test "$(MSBuildThisFileDirectory)test\coverlet.core.tests\coverlet.core.tests.csproj" -c $(Configuration) /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Include=[coverlet.*]*"/>
|
||||
<Exec Command="dotnet test "$(MSBuildThisFileDirectory)test\coverlet.collector.tests\coverlet.collector.tests.csproj" -c $(Configuration) /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Include=[coverlet.*]*"/>
|
||||
</Target>
|
||||
|
||||
<Target Name="CreateNuGetPackage" AfterTargets="RunTests" Condition="$(Configuration) == 'Release'">
|
||||
<Exec Command="dotnet pack "$(MSBuildThisFileDirectory)src\coverlet.msbuild.tasks\coverlet.msbuild.tasks.csproj" -c $(Configuration) -o $(OutputPath)" />
|
||||
<Exec Command="dotnet pack "$(MSBuildThisFileDirectory)src\coverlet.console\coverlet.console.csproj" -c $(Configuration) -o $(OutputPath)" />
|
||||
<Exec Command="dotnet pack "$(MSBuildThisFileDirectory)src\coverlet.collector\coverlet.collector.csproj" -c $(Configuration) -p:NuspecFile="$(MSBuildThisFileDirectory)src\coverlet.collector\coverlet.collector.nuspec" -o $(OutputPath)" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -19,6 +19,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.testsubject", "tes
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.core.performancetest", "test\coverlet.core.performancetest\coverlet.core.performancetest.csproj", "{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}"
|
||||
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}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -49,6 +53,14 @@ Global
|
||||
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F5B2C45B-274B-43D6-9565-8B50659CFE56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F5B2C45B-274B-43D6-9565-8B50659CFE56}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F5B2C45B-274B-43D6-9565-8B50659CFE56}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F5B2C45B-274B-43D6-9565-8B50659CFE56}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5ED4FA81-8F8C-4211-BA88-7573BD63262E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{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
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -60,6 +72,8 @@ Global
|
||||
{F3DBE7C3-ABBB-4B8B-A6CB-A1D3D607163E} = {E877EBA4-E78B-4F7D-A2D3-1E070FED04CD}
|
||||
{AE117FAA-C21D-4F23-917E-0C8050614750} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
|
||||
{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}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {9CA57C02-97B0-4C38-A027-EA61E8741F10}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using coverlet.collector.Resources;
|
||||
using Coverlet.Collector.Utilities;
|
||||
using Coverlet.Collector.Utilities.Interfaces;
|
||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection;
|
||||
|
||||
namespace Coverlet.Collector.DataCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages coverage report attachments
|
||||
/// </summary>
|
||||
internal class AttachmentManager : IDisposable
|
||||
{
|
||||
private readonly DataCollectionSink _dataSink;
|
||||
private readonly TestPlatformEqtTrace _eqtTrace;
|
||||
private readonly TestPlatformLogger _logger;
|
||||
private readonly DataCollectionContext _dataCollectionContext;
|
||||
private readonly IFileHelper _fileHelper;
|
||||
private readonly IDirectoryHelper _directoryHelper;
|
||||
private readonly string _reportFileName;
|
||||
private readonly string _reportDirectory;
|
||||
|
||||
public AttachmentManager(DataCollectionSink dataSink, DataCollectionContext dataCollectionContext, TestPlatformLogger logger, TestPlatformEqtTrace eqtTrace, string reportFileName)
|
||||
: this(dataSink,
|
||||
dataCollectionContext,
|
||||
logger,
|
||||
eqtTrace,
|
||||
reportFileName,
|
||||
Guid.NewGuid().ToString(),
|
||||
new FileHelper(),
|
||||
new DirectoryHelper())
|
||||
{
|
||||
}
|
||||
|
||||
public AttachmentManager(DataCollectionSink dataSink, DataCollectionContext dataCollectionContext, TestPlatformLogger logger, TestPlatformEqtTrace eqtTrace, string reportFileName, string reportDirectoryName, IFileHelper fileHelper, IDirectoryHelper directoryHelper)
|
||||
{
|
||||
// Store input variabless
|
||||
_dataSink = dataSink;
|
||||
_dataCollectionContext = dataCollectionContext;
|
||||
_logger = logger;
|
||||
_eqtTrace = eqtTrace;
|
||||
_reportFileName = reportFileName;
|
||||
_fileHelper = fileHelper;
|
||||
_directoryHelper = directoryHelper;
|
||||
|
||||
// Report directory to store the coverage reports.
|
||||
_reportDirectory = Path.Combine(Path.GetTempPath(), reportDirectoryName);
|
||||
|
||||
// Register events
|
||||
_dataSink.SendFileCompleted += this.OnSendFileCompleted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends coverage report to test platform
|
||||
/// </summary>
|
||||
/// <param name="coverageReport">Coverage report</param>
|
||||
public void SendCoverageReport(string coverageReport)
|
||||
{
|
||||
// Save coverage report to file
|
||||
string coverageReportPath = this.SaveCoverageReport(coverageReport);
|
||||
|
||||
// Send coverage attachment to test platform.
|
||||
this.SendAttachment(coverageReportPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes attachment manager
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
// Unregister events
|
||||
try
|
||||
{
|
||||
if (_dataSink != null)
|
||||
{
|
||||
_dataSink.SendFileCompleted -= this.OnSendFileCompleted;
|
||||
}
|
||||
this.CleanupReportDirectory();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves coverage report to file system
|
||||
/// </summary>
|
||||
/// <param name="report">Coverage report</param>
|
||||
/// <returns>Coverage report file path</returns>
|
||||
private string SaveCoverageReport(string report)
|
||||
{
|
||||
try
|
||||
{
|
||||
_directoryHelper.CreateDirectory(_reportDirectory);
|
||||
string filePath = Path.Combine(_reportDirectory, _reportFileName);
|
||||
_fileHelper.WriteAllText(filePath, report);
|
||||
_eqtTrace.Info("{0}: Saved coverage report to path: '{1}'", CoverletConstants.DataCollectorName, filePath);
|
||||
|
||||
return filePath;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string errorMessage = string.Format(Resources.FailedToSaveCoverageReport, CoverletConstants.DataCollectorName, _reportFileName, _reportDirectory);
|
||||
throw new CoverletDataCollectorException(errorMessage, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SendFileCompleted event handler
|
||||
/// </summary>
|
||||
/// <param name="sender">Sender</param>
|
||||
/// <param name="e">Event args</param>
|
||||
public void OnSendFileCompleted(object sender, AsyncCompletedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_eqtTrace.Verbose("{0}: SendFileCompleted received", CoverletConstants.DataCollectorName);
|
||||
this.CleanupReportDirectory();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex.ToString());
|
||||
this.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends attachment file to test platform
|
||||
/// </summary>
|
||||
/// <param name="attachmentPath">Attachment file path</param>
|
||||
private void SendAttachment(string attachmentPath)
|
||||
{
|
||||
if (_fileHelper.Exists(attachmentPath))
|
||||
{
|
||||
// Send coverage attachment to test platform.
|
||||
_eqtTrace.Verbose("{0}: Sending attachment to test platform", CoverletConstants.DataCollectorName);
|
||||
_dataSink.SendFileAsync(_dataCollectionContext, attachmentPath, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_eqtTrace.Warning("{0}: Attachment file does not exist", CoverletConstants.DataCollectorName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up coverage report directory
|
||||
/// </summary>
|
||||
private void CleanupReportDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_directoryHelper.Exists(_reportDirectory))
|
||||
{
|
||||
_directoryHelper.Delete(_reportDirectory, true);
|
||||
_eqtTrace.Verbose("{0}: Deleted report directory: '{1}'", CoverletConstants.DataCollectorName, _reportDirectory);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string errorMessage = string.Format(Resources.FailedToCleanupReportDirectory, CoverletConstants.DataCollectorName, _reportDirectory);
|
||||
throw new CoverletDataCollectorException(errorMessage, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using coverlet.collector.Resources;
|
||||
using Coverlet.Collector.Utilities;
|
||||
using Coverlet.Collector.Utilities.Interfaces;
|
||||
using Coverlet.Core;
|
||||
using Coverlet.Core.Logging;
|
||||
using Coverlet.Core.Reporters;
|
||||
|
||||
namespace Coverlet.Collector.DataCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages coverlet coverage
|
||||
/// </summary>
|
||||
internal class CoverageManager
|
||||
{
|
||||
private readonly Coverage _coverage;
|
||||
|
||||
private ICoverageWrapper _coverageWrapper;
|
||||
|
||||
public IReporter Reporter { get; }
|
||||
|
||||
public CoverageManager(CoverletSettings settings, TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, ICoverageWrapper coverageWrapper)
|
||||
: this(settings,
|
||||
new ReporterFactory(settings.ReportFormat).CreateReporter(),
|
||||
new CoverletLogger(eqtTrace, logger),
|
||||
coverageWrapper)
|
||||
{
|
||||
}
|
||||
|
||||
public CoverageManager(CoverletSettings settings, IReporter reporter, ILogger logger, ICoverageWrapper coverageWrapper)
|
||||
{
|
||||
// Store input vars
|
||||
Reporter = reporter;
|
||||
_coverageWrapper = coverageWrapper;
|
||||
|
||||
// Coverage object
|
||||
_coverage = _coverageWrapper.CreateCoverage(settings, logger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instrument modules
|
||||
/// </summary>
|
||||
public void InstrumentModules()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Instrument modules
|
||||
_coverageWrapper.PrepareModules(_coverage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string errorMessage = string.Format(Resources.InstrumentationException, CoverletConstants.DataCollectorName);
|
||||
throw new CoverletDataCollectorException(errorMessage, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets coverlet coverage report
|
||||
/// </summary>
|
||||
/// <returns>Coverage report</returns>
|
||||
public string GetCoverageReport()
|
||||
{
|
||||
// Get coverage result
|
||||
CoverageResult coverageResult = this.GetCoverageResult();
|
||||
|
||||
// Get coverage report in default format
|
||||
string coverageReport = this.GetCoverageReport(coverageResult);
|
||||
return coverageReport;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets coverlet coverage result
|
||||
/// </summary>
|
||||
/// <returns>Coverage result</returns>
|
||||
private CoverageResult GetCoverageResult()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _coverageWrapper.GetCoverageResult(_coverage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string errorMessage = string.Format(Resources.CoverageResultException, CoverletConstants.DataCollectorName);
|
||||
throw new CoverletDataCollectorException(errorMessage, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets coverage report from coverage result
|
||||
/// </summary>
|
||||
/// <param name="coverageResult">Coverage result</param>
|
||||
/// <returns>Coverage report</returns>
|
||||
private string GetCoverageReport(CoverageResult coverageResult)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Reporter.Report(coverageResult);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string errorMessage = string.Format(Resources.CoverageReportException, CoverletConstants.DataCollectorName);
|
||||
throw new CoverletDataCollectorException(errorMessage, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Coverlet.Collector.Utilities.Interfaces;
|
||||
using Coverlet.Core;
|
||||
using Coverlet.Core.Logging;
|
||||
|
||||
namespace Coverlet.Collector.DataCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation for wrapping over Coverage class in coverlet.core
|
||||
/// </summary>
|
||||
internal class CoverageWrapper : ICoverageWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a coverage object from given coverlet settings
|
||||
/// </summary>
|
||||
/// <param name="settings">Coverlet settings</param>
|
||||
/// <param name="coverletLogger">Coverlet logger</param>
|
||||
/// <returns>Coverage object</returns>
|
||||
public Coverage CreateCoverage(CoverletSettings settings, ILogger coverletLogger)
|
||||
{
|
||||
return new Coverage(
|
||||
settings.TestModule,
|
||||
settings.IncludeFilters,
|
||||
settings.IncludeDirectories,
|
||||
settings.ExcludeFilters,
|
||||
settings.ExcludeSourceFiles,
|
||||
settings.ExcludeAttributes,
|
||||
settings.IncludeTestAssembly,
|
||||
settings.SingleHit,
|
||||
settings.MergeWith,
|
||||
settings.UseSourceLink,
|
||||
coverletLogger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the coverage result from provided coverage object
|
||||
/// </summary>
|
||||
/// <param name="coverage">Coverage</param>
|
||||
/// <returns>The coverage result</returns>
|
||||
public CoverageResult GetCoverageResult(Coverage coverage)
|
||||
{
|
||||
return coverage.GetCoverageResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares modules for getting coverage.
|
||||
/// Wrapper over coverage.PrepareModules
|
||||
/// </summary>
|
||||
/// <param name="coverage"></param>
|
||||
public void PrepareModules(Coverage coverage)
|
||||
{
|
||||
coverage.PrepareModules();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using Coverlet.Collector.Utilities;
|
||||
using Coverlet.Collector.Utilities.Interfaces;
|
||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection;
|
||||
|
||||
namespace Coverlet.Collector.DataCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Coverlet coverage out-proc data collector.
|
||||
/// </summary>
|
||||
[DataCollectorTypeUri(CoverletConstants.DefaultUri)]
|
||||
[DataCollectorFriendlyName(CoverletConstants.FriendlyName)]
|
||||
public class CoverletCoverageCollector : DataCollector
|
||||
{
|
||||
private readonly TestPlatformEqtTrace _eqtTrace;
|
||||
private DataCollectionEvents _events;
|
||||
private TestPlatformLogger _logger;
|
||||
private XmlElement _configurationElement;
|
||||
private DataCollectionSink _dataSink;
|
||||
private DataCollectionContext _dataCollectionContext;
|
||||
private CoverageManager _coverageManager;
|
||||
private ICoverageWrapper _coverageWrapper;
|
||||
|
||||
public CoverletCoverageCollector() : this(new TestPlatformEqtTrace(), new CoverageWrapper())
|
||||
{
|
||||
}
|
||||
|
||||
internal CoverletCoverageCollector(TestPlatformEqtTrace eqtTrace, ICoverageWrapper coverageWrapper) : base()
|
||||
{
|
||||
_eqtTrace = eqtTrace;
|
||||
_coverageWrapper = coverageWrapper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes data collector
|
||||
/// </summary>
|
||||
/// <param name="configurationElement">Configuration element</param>
|
||||
/// <param name="events">Events to register on</param>
|
||||
/// <param name="dataSink">Data sink to send attachments to test platform</param>
|
||||
/// <param name="logger">Test platform logger</param>
|
||||
/// <param name="environmentContext">Environment context</param>
|
||||
public override void Initialize(
|
||||
XmlElement configurationElement,
|
||||
DataCollectionEvents events,
|
||||
DataCollectionSink dataSink,
|
||||
DataCollectionLogger logger,
|
||||
DataCollectionEnvironmentContext environmentContext)
|
||||
{
|
||||
if (_eqtTrace.IsInfoEnabled)
|
||||
{
|
||||
_eqtTrace.Info("Initializing {0} with configuration: '{1}'", CoverletConstants.DataCollectorName, configurationElement?.OuterXml);
|
||||
}
|
||||
|
||||
// Store input variables
|
||||
_events = events;
|
||||
_configurationElement = configurationElement;
|
||||
_dataSink = dataSink;
|
||||
_dataCollectionContext = environmentContext.SessionDataCollectionContext;
|
||||
_logger = new TestPlatformLogger(logger, _dataCollectionContext);
|
||||
|
||||
// Register events
|
||||
_events.SessionStart += OnSessionStart;
|
||||
_events.SessionEnd += OnSessionEnd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the data collector
|
||||
/// </summary>
|
||||
/// <param name="disposing">Disposing flag</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_eqtTrace.Verbose("{0}: Disposing", CoverletConstants.DataCollectorName);
|
||||
|
||||
// Unregister events
|
||||
if (_events != null)
|
||||
{
|
||||
_events.SessionStart -= OnSessionStart;
|
||||
_events.SessionEnd -= OnSessionEnd;
|
||||
}
|
||||
|
||||
// Remove vars
|
||||
_events = null;
|
||||
_dataSink = null;
|
||||
_coverageManager = null;
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SessionStart event handler
|
||||
/// </summary>
|
||||
/// <param name="sender">Sender</param>
|
||||
/// <param name="sessionStartEventArgs">Event args</param>
|
||||
private void OnSessionStart(object sender, SessionStartEventArgs sessionStartEventArgs)
|
||||
{
|
||||
_eqtTrace.Verbose("{0}: SessionStart received", CoverletConstants.DataCollectorName);
|
||||
|
||||
try
|
||||
{
|
||||
// Get coverlet settings
|
||||
IEnumerable<string> testModules = this.GetTestModules(sessionStartEventArgs);
|
||||
var coverletSettingsParser = new CoverletSettingsParser(_eqtTrace);
|
||||
CoverletSettings coverletSettings = coverletSettingsParser.Parse(_configurationElement, testModules);
|
||||
|
||||
// Get coverage and attachment managers
|
||||
_coverageManager = new CoverageManager(coverletSettings, _eqtTrace, _logger, _coverageWrapper);
|
||||
|
||||
// Instrument modules
|
||||
_coverageManager.InstrumentModules();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex.ToString());
|
||||
this.Dispose(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SessionEnd event handler
|
||||
/// </summary>
|
||||
/// <param name="sender">Sender</param>
|
||||
/// <param name="e">Event args</param>
|
||||
private void OnSessionEnd(object sender, SessionEndEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_eqtTrace.Verbose("{0}: SessionEnd received", CoverletConstants.DataCollectorName);
|
||||
|
||||
// Get coverage reports
|
||||
string coverageReport = _coverageManager?.GetCoverageReport();
|
||||
|
||||
// Send result attachments to test platform.
|
||||
var attachmentManager = new AttachmentManager(_dataSink, _dataCollectionContext, _logger, _eqtTrace, this.GetReportFileName());
|
||||
attachmentManager?.SendCoverageReport(coverageReport);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex.ToString());
|
||||
this.Dispose(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets coverage report file name
|
||||
/// </summary>
|
||||
/// <returns>Coverage report file name</returns>
|
||||
private string GetReportFileName()
|
||||
{
|
||||
string fileName = CoverletConstants.DefaultFileName;
|
||||
string extension = _coverageManager?.Reporter.Extension;
|
||||
|
||||
return extension == null ? fileName : $"{fileName}.{extension}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets test modules
|
||||
/// </summary>
|
||||
/// <param name="sessionStartEventArgs">Event args</param>
|
||||
/// <returns>Test modules list</returns>
|
||||
private IEnumerable<string> GetTestModules(SessionStartEventArgs sessionStartEventArgs)
|
||||
{
|
||||
var testModules = sessionStartEventArgs.GetPropertyValue<IEnumerable<string>>(CoverletConstants.TestSourcesPropertyName);
|
||||
if (_eqtTrace.IsInfoEnabled)
|
||||
{
|
||||
_eqtTrace.Info("{0}: TestModules: '{1}'",
|
||||
CoverletConstants.DataCollectorName,
|
||||
string.Join(",", testModules ?? Enumerable.Empty<string>()));
|
||||
}
|
||||
|
||||
return testModules;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using Coverlet.Collector.Utilities;
|
||||
using Coverlet.Core.Logging;
|
||||
|
||||
namespace Coverlet.Collector.DataCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Coverlet logger
|
||||
/// </summary>
|
||||
internal class CoverletLogger : ILogger
|
||||
{
|
||||
private readonly TestPlatformEqtTrace _eqtTrace;
|
||||
private readonly TestPlatformLogger _logger;
|
||||
|
||||
public CoverletLogger(TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger)
|
||||
{
|
||||
_eqtTrace = eqtTrace;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs error
|
||||
/// </summary>
|
||||
/// <param name="message">Error message</param>
|
||||
public void LogError(string message)
|
||||
{
|
||||
_logger.LogWarning(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs error
|
||||
/// </summary>
|
||||
/// <param name="exception">Exception to log</param>
|
||||
public void LogError(Exception exception)
|
||||
{
|
||||
_logger.LogWarning(exception.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs information
|
||||
/// </summary>
|
||||
/// <param name="message">Information message</param>
|
||||
/// <param name="important">importance</param>
|
||||
public void LogInformation(string message, bool important = false)
|
||||
{
|
||||
_eqtTrace.Info(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs verbose
|
||||
/// </summary>
|
||||
/// <param name="message">Verbose message</param>
|
||||
public void LogVerbose(string message)
|
||||
{
|
||||
_eqtTrace.Verbose(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs warning
|
||||
/// </summary>
|
||||
/// <param name="message">Warning message</param>
|
||||
public void LogWarning(string message)
|
||||
{
|
||||
_eqtTrace.Warning(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Coverlet.Collector.DataCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Coverlet settings
|
||||
/// </summary>
|
||||
internal class CoverletSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Test module
|
||||
/// </summary>
|
||||
public string TestModule { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Report format
|
||||
/// </summary>
|
||||
public string ReportFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Filters to include
|
||||
/// </summary>
|
||||
public string[] IncludeFilters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Directories to include
|
||||
/// </summary>
|
||||
public string[] IncludeDirectories { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Filters to exclude
|
||||
/// </summary>
|
||||
public string[] ExcludeFilters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Source files to exclude
|
||||
/// </summary>
|
||||
public string[] ExcludeSourceFiles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Attributes to exclude
|
||||
/// </summary>
|
||||
public string[] ExcludeAttributes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Coverate report path to merge with
|
||||
/// </summary>
|
||||
public string MergeWith { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Use source link flag
|
||||
/// </summary>
|
||||
public bool UseSourceLink { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Single hit flag
|
||||
/// </summary>
|
||||
public bool SingleHit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Includes test assembly
|
||||
/// </summary>
|
||||
public bool IncludeTestAssembly { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.AppendFormat("TestModule: '{0}', ", this.TestModule);
|
||||
builder.AppendFormat("IncludeFilters: '{0}', ", string.Join(",", this.IncludeFilters ?? Enumerable.Empty<string>()));
|
||||
builder.AppendFormat("IncludeDirectories: '{0}', ", string.Join(",", this.IncludeDirectories ?? Enumerable.Empty<string>()));
|
||||
builder.AppendFormat("ExcludeFilters: '{0}', ", string.Join(",", this.ExcludeFilters ?? Enumerable.Empty<string>()));
|
||||
builder.AppendFormat("ExcludeSourceFiles: '{0}', ", string.Join(",", this.ExcludeSourceFiles ?? Enumerable.Empty<string>()));
|
||||
builder.AppendFormat("ExcludeAttributes: '{0}', ", string.Join(",", this.ExcludeAttributes ?? Enumerable.Empty<string>()));
|
||||
builder.AppendFormat("MergeWith: '{0}', ", this.MergeWith);
|
||||
builder.AppendFormat("UseSourceLink: '{0}'", this.UseSourceLink);
|
||||
builder.AppendFormat("SingleHit: '{0}'", this.SingleHit);
|
||||
builder.AppendFormat("IncludeTestAssembly: '{0}'", this.IncludeTestAssembly);
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using coverlet.collector.Resources;
|
||||
using Coverlet.Collector.Utilities;
|
||||
|
||||
namespace Coverlet.Collector.DataCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Coverlet settings parser
|
||||
/// </summary>
|
||||
internal class CoverletSettingsParser
|
||||
{
|
||||
private readonly TestPlatformEqtTrace _eqtTrace;
|
||||
|
||||
public CoverletSettingsParser(TestPlatformEqtTrace eqtTrace)
|
||||
{
|
||||
_eqtTrace = eqtTrace;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parser coverlet settings
|
||||
/// </summary>
|
||||
/// <param name="configurationElement">Configuration element</param>
|
||||
/// <param name="testModules">Test modules</param>
|
||||
/// <returns>Coverlet settings</returns>
|
||||
public CoverletSettings Parse(XmlElement configurationElement, IEnumerable<string> testModules)
|
||||
{
|
||||
var coverletSettings = new CoverletSettings
|
||||
{
|
||||
TestModule = this.ParseTestModule(testModules)
|
||||
};
|
||||
|
||||
if (configurationElement != null)
|
||||
{
|
||||
coverletSettings.IncludeFilters = this.ParseIncludeFilters(configurationElement);
|
||||
coverletSettings.IncludeDirectories = this.ParseIncludeDirectories(configurationElement);
|
||||
coverletSettings.ExcludeAttributes = this.ParseExcludeAttributes(configurationElement);
|
||||
coverletSettings.ExcludeSourceFiles = this.ParseExcludeSourceFiles(configurationElement);
|
||||
coverletSettings.MergeWith = this.ParseMergeWith(configurationElement);
|
||||
coverletSettings.UseSourceLink = this.ParseUseSourceLink(configurationElement);
|
||||
coverletSettings.SingleHit = this.ParseSingleHit(configurationElement);
|
||||
coverletSettings.IncludeTestAssembly = this.ParseIncludeTestAssembly(configurationElement);
|
||||
}
|
||||
|
||||
coverletSettings.ReportFormat = this.ParseReportFormat(configurationElement);
|
||||
coverletSettings.ExcludeFilters = this.ParseExcludeFilters(configurationElement);
|
||||
|
||||
if (_eqtTrace.IsVerboseEnabled)
|
||||
{
|
||||
_eqtTrace.Verbose("{0}: Initializing coverlet process with settings: \"{1}\"", CoverletConstants.DataCollectorName, coverletSettings.ToString());
|
||||
}
|
||||
|
||||
return coverletSettings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses test module
|
||||
/// </summary>
|
||||
/// <param name="testModules">Test modules</param>
|
||||
/// <returns>Test module</returns>
|
||||
private string ParseTestModule(IEnumerable<string> testModules)
|
||||
{
|
||||
// Validate if at least one source present.
|
||||
if (testModules == null || !testModules.Any())
|
||||
{
|
||||
string errorMessage = string.Format(Resources.NoTestModulesFound, CoverletConstants.DataCollectorName);
|
||||
throw new CoverletDataCollectorException(errorMessage);
|
||||
}
|
||||
|
||||
// Note:
|
||||
// 1) .NET core test run supports one testModule per run. Coverlet also supports one testModule per run. So, we are using first testSource only and ignoring others.
|
||||
// 2) If and when .NET full is supported with coverlet OR .NET core starts supporting multiple testModules, revisit this code to use other testModules as well.
|
||||
return testModules.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse report format
|
||||
/// </summary>
|
||||
/// <param name="configurationElement">Configuration element</param>
|
||||
/// <returns>Report format</returns>
|
||||
private string ParseReportFormat(XmlElement configurationElement)
|
||||
{
|
||||
string format = string.Empty;
|
||||
if (configurationElement != null)
|
||||
{
|
||||
XmlElement reportFormatElement = configurationElement[CoverletConstants.ReportFormatElementName];
|
||||
format = reportFormatElement?.InnerText?.Split(',').FirstOrDefault();
|
||||
}
|
||||
return string.IsNullOrEmpty(format) ? CoverletConstants.DefaultReportFormat : format;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse filters to include
|
||||
/// </summary>
|
||||
/// <param name="configurationElement">Configuration element</param>
|
||||
/// <returns>Filters to include</returns>
|
||||
private string[] ParseIncludeFilters(XmlElement configurationElement)
|
||||
{
|
||||
XmlElement includeFiltersElement = configurationElement[CoverletConstants.IncludeFiltersElementName];
|
||||
return includeFiltersElement?.InnerText?.Split(',');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse directories to include
|
||||
/// </summary>
|
||||
/// <param name="configurationElement">Configuration element</param>
|
||||
/// <returns>Directories to include</returns>
|
||||
private string[] ParseIncludeDirectories(XmlElement configurationElement)
|
||||
{
|
||||
XmlElement includeDirectoriesElement = configurationElement[CoverletConstants.IncludeDirectoriesElementName];
|
||||
return includeDirectoriesElement?.InnerText?.Split(',');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse filters to exclude
|
||||
/// </summary>
|
||||
/// <param name="configurationElement">Configuration element</param>
|
||||
/// <returns>Filters to exclude</returns>
|
||||
private string[] ParseExcludeFilters(XmlElement configurationElement)
|
||||
{
|
||||
List<string> excludeFilters = new List<string> { CoverletConstants.DefaultExcludeFilter };
|
||||
|
||||
if (configurationElement != null)
|
||||
{
|
||||
XmlElement excludeFiltersElement = configurationElement[CoverletConstants.ExcludeFiltersElementName];
|
||||
string[] filters = excludeFiltersElement?.InnerText?.Split(',');
|
||||
if (filters != null)
|
||||
{
|
||||
excludeFilters.AddRange(filters);
|
||||
}
|
||||
}
|
||||
|
||||
return excludeFilters.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse source files to exclude
|
||||
/// </summary>
|
||||
/// <param name="configurationElement">Configuration element</param>
|
||||
/// <returns>Source files to exclude</returns>
|
||||
private string[] ParseExcludeSourceFiles(XmlElement configurationElement)
|
||||
{
|
||||
XmlElement excludeSourceFilesElement = configurationElement[CoverletConstants.ExcludeSourceFilesElementName];
|
||||
return excludeSourceFilesElement?.InnerText?.Split(',');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse attributes to exclude
|
||||
/// </summary>
|
||||
/// <param name="configurationElement">Configuration element</param>
|
||||
/// <returns>Attributes to exclude</returns>
|
||||
private string[] ParseExcludeAttributes(XmlElement configurationElement)
|
||||
{
|
||||
XmlElement excludeAttributesElement = configurationElement[CoverletConstants.ExcludeAttributesElementName];
|
||||
return excludeAttributesElement?.InnerText?.Split(',');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse merge with attribute
|
||||
/// </summary>
|
||||
/// <param name="configurationElement">Configuration element</param>
|
||||
/// <returns>Merge with attribute</returns>
|
||||
private string ParseMergeWith(XmlElement configurationElement)
|
||||
{
|
||||
XmlElement mergeWithElement = configurationElement[CoverletConstants.MergeWithElementName];
|
||||
return mergeWithElement?.InnerText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse use source link flag
|
||||
/// </summary>
|
||||
/// <param name="configurationElement">Configuration element</param>
|
||||
/// <returns>Use source link flag</returns>
|
||||
private bool ParseUseSourceLink(XmlElement configurationElement)
|
||||
{
|
||||
XmlElement useSourceLinkElement = configurationElement[CoverletConstants.UseSourceLinkElementName];
|
||||
bool.TryParse(useSourceLinkElement?.InnerText, out bool useSourceLink);
|
||||
return useSourceLink;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse single hit flag
|
||||
/// </summary>
|
||||
/// <param name="configurationElement">Configuration element</param>
|
||||
/// <returns>Single hit flag</returns>
|
||||
private bool ParseSingleHit(XmlElement configurationElement)
|
||||
{
|
||||
XmlElement singleHitElement = configurationElement[CoverletConstants.SingleHitElementName];
|
||||
bool.TryParse(singleHitElement?.InnerText, out bool singleHit);
|
||||
return singleHit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse include test assembly flag
|
||||
/// </summary>
|
||||
/// <param name="configurationElement">Configuration element</param>
|
||||
/// <returns>Include Test Assembly Flag</returns>
|
||||
private bool ParseIncludeTestAssembly(XmlElement configurationElement)
|
||||
{
|
||||
XmlElement includeTestAssemblyElement = configurationElement[CoverletConstants.IncludeTestAssemblyElementName];
|
||||
bool.TryParse(includeTestAssemblyElement?.InnerText, out bool includeTestAssembly);
|
||||
return includeTestAssembly;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#region Test Assemblies
|
||||
|
||||
[assembly: InternalsVisibleTo("coverlet.collector.tests")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using coverlet.collector.Resources;
|
||||
using Coverlet.Collector.Utilities;
|
||||
using Coverlet.Core.Instrumentation;
|
||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection;
|
||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollector.InProcDataCollector;
|
||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.InProcDataCollector;
|
||||
|
||||
namespace Coverlet.Collector.DataCollection
|
||||
{
|
||||
public class CoverletInProcDataCollector : InProcDataCollection
|
||||
{
|
||||
public void Initialize(IDataCollectionSink dataCollectionSink)
|
||||
{
|
||||
}
|
||||
|
||||
public void TestCaseEnd(TestCaseEndArgs testCaseEndArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public void TestCaseStart(TestCaseStartArgs testCaseStartArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public void TestSessionEnd(TestSessionEndArgs testSessionEndArgs)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
Type injectedInstrumentationClass = GetInstrumentationClass(assembly);
|
||||
if (injectedInstrumentationClass is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var unloadModule = injectedInstrumentationClass.GetMethod(nameof(ModuleTrackerTemplate.UnloadModule), new[] { typeof(object), typeof(EventArgs) });
|
||||
unloadModule.Invoke(null, new[] { null, EventArgs.Empty });
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
// Throw any exception if unload fails
|
||||
if (EqtTrace.IsErrorEnabled)
|
||||
{
|
||||
EqtTrace.Error("{0}: Failed to unload module with error: {1}", CoverletConstants.InProcDataCollectorName, ex);
|
||||
}
|
||||
|
||||
string errorMessage = string.Format(Resources.FailedToUnloadModule, CoverletConstants.InProcDataCollectorName);
|
||||
throw new CoverletDataCollectorException(errorMessage, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void TestSessionStart(TestSessionStartArgs testSessionStartArgs)
|
||||
{
|
||||
}
|
||||
|
||||
private static Type GetInstrumentationClass(Assembly assembly)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
if (type.Namespace == "Coverlet.Core.Instrumentation.Tracker"
|
||||
&& type.Name.StartsWith(assembly.GetName().Name + "_"))
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
// Avoid crashing if reflection fails.
|
||||
if (EqtTrace.IsWarningEnabled)
|
||||
{
|
||||
EqtTrace.Warning("{0}: Failed to get Instrumentation class with error: {1}", CoverletConstants.InProcDataCollectorName, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace coverlet.collector.Resources {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("coverlet.collector.Resources.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0}: Failed to get coverage report.
|
||||
/// </summary>
|
||||
internal static string CoverageReportException {
|
||||
get {
|
||||
return ResourceManager.GetString("CoverageReportException", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0}: Failed to get coverage result.
|
||||
/// </summary>
|
||||
internal static string CoverageResultException {
|
||||
get {
|
||||
return ResourceManager.GetString("CoverageResultException", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0}: Failed to cleanup report directory: '{1}'.
|
||||
/// </summary>
|
||||
internal static string FailedToCleanupReportDirectory {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedToCleanupReportDirectory", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0}: Failed to save coverage report '{1}' in directory '{2}'.
|
||||
/// </summary>
|
||||
internal static string FailedToSaveCoverageReport {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedToSaveCoverageReport", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0}: Failed to unload module.
|
||||
/// </summary>
|
||||
internal static string FailedToUnloadModule {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedToUnloadModule", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0}: Failed to instrument modules.
|
||||
/// </summary>
|
||||
internal static string InstrumentationException {
|
||||
get {
|
||||
return ResourceManager.GetString("InstrumentationException", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0}: No test modules found.
|
||||
/// </summary>
|
||||
internal static string NoTestModulesFound {
|
||||
get {
|
||||
return ResourceManager.GetString("NoTestModulesFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="CoverageReportException" xml:space="preserve">
|
||||
<value>{0}: Failed to get coverage report</value>
|
||||
</data>
|
||||
<data name="CoverageResultException" xml:space="preserve">
|
||||
<value>{0}: Failed to get coverage result</value>
|
||||
</data>
|
||||
<data name="FailedToCleanupReportDirectory" xml:space="preserve">
|
||||
<value>{0}: Failed to cleanup report directory: '{1}'</value>
|
||||
</data>
|
||||
<data name="FailedToSaveCoverageReport" xml:space="preserve">
|
||||
<value>{0}: Failed to save coverage report '{1}' in directory '{2}'</value>
|
||||
</data>
|
||||
<data name="FailedToUnloadModule" xml:space="preserve">
|
||||
<value>{0}: Failed to unload module</value>
|
||||
</data>
|
||||
<data name="InstrumentationException" xml:space="preserve">
|
||||
<value>{0}: Failed to instrument modules</value>
|
||||
</data>
|
||||
<data name="NoTestModulesFound" xml:space="preserve">
|
||||
<value>{0}: No test modules found</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Coverlet.Collector.Utilities
|
||||
{
|
||||
internal static class CoverletConstants
|
||||
{
|
||||
public const string FriendlyName = "XPlat code coverage";
|
||||
public const string DefaultUri = @"datacollector://Microsoft/CoverletCodeCoverage/1.0";
|
||||
public const string DataCollectorName = "CoverletCoverageDataCollector";
|
||||
public const string DefaultReportFormat = "cobertura";
|
||||
public const string DefaultFileName = "coverage";
|
||||
public const string IncludeFiltersElementName = "Include";
|
||||
public const string IncludeDirectoriesElementName = "IncludeDirectory";
|
||||
public const string ExcludeFiltersElementName = "Exclude";
|
||||
public const string ExcludeSourceFilesElementName = "ExcludeByFile";
|
||||
public const string ExcludeAttributesElementName = "ExcludeByAttribute";
|
||||
public const string MergeWithElementName = "MergeWith";
|
||||
public const string UseSourceLinkElementName = "UseSourceLink";
|
||||
public const string SingleHitElementName = "SingleHit";
|
||||
public const string IncludeTestAssemblyElementName = "IncludeTestAssembly";
|
||||
public const string TestSourcesPropertyName = "TestSources";
|
||||
public const string ReportFormatElementName = "Format";
|
||||
public const string DefaultExcludeFilter = "[coverlet.*]*";
|
||||
public const string InProcDataCollectorName = "CoverletInProcDataCollector";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Coverlet.Collector.Utilities
|
||||
{
|
||||
internal class CoverletDataCollectorException : Exception
|
||||
{
|
||||
public CoverletDataCollectorException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public CoverletDataCollectorException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.IO;
|
||||
using Coverlet.Collector.Utilities.Interfaces;
|
||||
|
||||
namespace Coverlet.Collector.Utilities
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal class DirectoryHelper : IDirectoryHelper
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Exists(string path)
|
||||
{
|
||||
return Directory.Exists(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CreateDirectory(string path)
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Delete(string path, bool recursive)
|
||||
{
|
||||
Directory.Delete(path, recursive);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.IO;
|
||||
using Coverlet.Collector.Utilities.Interfaces;
|
||||
|
||||
namespace Coverlet.Collector.Utilities
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal class FileHelper : IFileHelper
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Exists(string path)
|
||||
{
|
||||
return File.Exists(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteAllText(string path, string contents)
|
||||
{
|
||||
File.WriteAllText(path, contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Coverlet.Collector.DataCollection;
|
||||
using Coverlet.Core;
|
||||
using Coverlet.Core.Logging;
|
||||
|
||||
namespace Coverlet.Collector.Utilities.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper interface for Coverage class in coverlet.core
|
||||
/// Since the class is not testable, this interface is used to abstract methods for mocking in unit tests.
|
||||
/// </summary>
|
||||
internal interface ICoverageWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a coverage object from given coverlet settings
|
||||
/// </summary>
|
||||
/// <param name="settings">Coverlet settings</param>
|
||||
/// <param name="coverletLogger">Coverlet logger</param>
|
||||
/// <returns>Coverage object</returns>
|
||||
Coverage CreateCoverage(CoverletSettings settings, ILogger logger);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the coverage result from provided coverage object
|
||||
/// </summary>
|
||||
/// <param name="coverage">Coverage</param>
|
||||
/// <returns>The coverage result</returns>
|
||||
CoverageResult GetCoverageResult(Coverage coverage);
|
||||
|
||||
/// <summary>
|
||||
/// Prepares modules for getting coverage.
|
||||
/// Wrapper over coverage.PrepareModules
|
||||
/// </summary>
|
||||
/// <param name="coverage"></param>
|
||||
void PrepareModules(Coverage coverage);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Coverlet.Collector.Utilities.Interfaces
|
||||
{
|
||||
interface IDirectoryHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the specified directory exists.
|
||||
/// </summary>
|
||||
/// <param name="path">The directory to check.</param>
|
||||
/// <returns>true if the caller has the required permissions and path contains the name of an existing directory; otherwise, false.
|
||||
/// This method also returns false if path is null, an invalid path, or a zero-length string.
|
||||
/// If the caller does not have sufficient permissions to read the specified file,
|
||||
/// no exception is thrown and the method returns false regardless of the existence of path.</returns>
|
||||
bool Exists(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Creates all directories and subdirectories in the specified path unless they already exist.
|
||||
/// </summary>
|
||||
/// <param name="directory">The directory to create.</param>
|
||||
void CreateDirectory(string directory);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the specified directory and, if indicated, any subdirectories and files in the directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The name of the directory to remove.</param>
|
||||
/// <param name="recursive">true to remove directories, subdirectories, and files in path; otherwise, false.</param>
|
||||
void Delete(string path, bool recursive);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Coverlet.Collector.Utilities.Interfaces
|
||||
{
|
||||
internal interface IFileHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the specified file exists.
|
||||
/// </summary>
|
||||
/// <param name="path">The file to check.</param>
|
||||
/// <returns>true if the caller has the required permissions and path contains the name of an existing file; otherwise, false.
|
||||
/// This method also returns false if path is null, an invalid path, or a zero-length string.
|
||||
/// If the caller does not have sufficient permissions to read the specified file,
|
||||
/// no exception is thrown and the method returns false regardless of the existence of path.</returns>
|
||||
bool Exists(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new file, writes the specified string to the file, and then closes the file.
|
||||
/// If the target file already exists, it is overwritten.
|
||||
/// </summary>
|
||||
/// <param name="path">The file to write to.</param>
|
||||
/// <param name="contents">The string to write to the file.</param>
|
||||
void WriteAllText(string path, string contents);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||
|
||||
namespace Coverlet.Collector.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Test platform eqttrace
|
||||
/// </summary>
|
||||
internal class TestPlatformEqtTrace
|
||||
{
|
||||
public bool IsInfoEnabled => EqtTrace.IsInfoEnabled;
|
||||
public bool IsVerboseEnabled => EqtTrace.IsVerboseEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Verbose logger
|
||||
/// </summary>
|
||||
/// <param name="format">Format</param>
|
||||
/// <param name="args">Args</param>
|
||||
public void Verbose(string format, params object[] args)
|
||||
{
|
||||
EqtTrace.Verbose(format, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Warning logger
|
||||
/// </summary>
|
||||
/// <param name="format">Format</param>
|
||||
/// <param name="args">Args</param>
|
||||
public void Warning(string format, params object[] args)
|
||||
{
|
||||
EqtTrace.Warning(format, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Info logger
|
||||
/// </summary>
|
||||
/// <param name="format">Format</param>
|
||||
/// <param name="args">Args</param>
|
||||
public void Info(string format, params object[] args)
|
||||
{
|
||||
EqtTrace.Info(format, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection;
|
||||
|
||||
namespace Coverlet.Collector.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Test platform logger
|
||||
/// </summary>
|
||||
internal class TestPlatformLogger
|
||||
{
|
||||
private readonly DataCollectionLogger _logger;
|
||||
private readonly DataCollectionContext _dataCollectionContext;
|
||||
|
||||
public TestPlatformLogger(DataCollectionLogger logger, DataCollectionContext dataCollectionContext)
|
||||
{
|
||||
_logger = logger;
|
||||
_dataCollectionContext = dataCollectionContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log warning
|
||||
/// </summary>
|
||||
/// <param name="warning">Warning message</param>
|
||||
public void LogWarning(string warning)
|
||||
{
|
||||
_logger.LogWarning(_dataCollectionContext, warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<AssemblyTitle>coverlet.collector</AssemblyTitle>
|
||||
<AssemblyVersion>1.0.0</AssemblyVersion>
|
||||
|
||||
<PackageId>coverlet.collector</PackageId>
|
||||
<PackageVersion>$(AssemblyVersion)</PackageVersion>
|
||||
<Title>coverlet.collector</Title>
|
||||
<Authors>tonerdo</Authors>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageProjectUrl>http://github.com/tonerdo/coverlet</PackageProjectUrl>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/tonerdo/coverlet/master/_assets/coverlet-icon.svg?sanitize=true</PackageIconUrl>
|
||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||
<DevelopmentDependency>true</DevelopmentDependency>
|
||||
<Description>Coverlet is a cross platform code coverage library for .NET, with support for line, branch and method coverage.</Description>
|
||||
<PackageTags>coverage testing unit-test lcov opencover quality</PackageTags>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/tonerdo/coverlet</RepositoryUrl>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" Version="16.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="coverlet.collector.targets" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Resources\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Resources\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)..\coverlet.core\coverlet.core.csproj" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0"?>
|
||||
<package>
|
||||
<metadata>
|
||||
<id>coverlet.collector</id>
|
||||
<version>1.0.0</version>
|
||||
<title>coverlet.collector</title>
|
||||
<authors>tonerdo</authors>
|
||||
<owners>tonerdo</owners>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<iconUrl>https://raw.githubusercontent.com/tonerdo/coverlet/master/_assets/coverlet-icon.svg?sanitize=true</iconUrl>
|
||||
<description>Coverlet is a cross platform code coverage library for .NET, with support for line, branch and method coverage.</description>
|
||||
<projectUrl>http://github.com/tonerdo/coverlet</projectUrl>
|
||||
<tags>coverage testing unit-test lcov opencover quality</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="bin\Release\netcoreapp2.0\*.dll" target="build\netcoreapp2.0\" />
|
||||
<file src="bin\Release\netcoreapp2.0\coverlet.collector.deps.json" target="build\netcoreapp2.0\" />
|
||||
<file src="coverlet.collector.targets" target="build\netcoreapp2.0\" />
|
||||
</files>
|
||||
</package>
|
||||
@@ -0,0 +1,24 @@
|
||||
<!--
|
||||
***********************************************************************************************
|
||||
coverlet.collector.targets
|
||||
|
||||
WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
|
||||
created a backup copy. Incorrect changes to this file will make it
|
||||
impossible to load or build your projects from the command-line or the IDE.
|
||||
***********************************************************************************************
|
||||
-->
|
||||
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Target Name="CopyCoverletDataCollectorFiles" AfterTargets="ComputeFilesToPublish">
|
||||
<ItemGroup>
|
||||
<CoverletDataCollectorFiles Include="$(MSBuildThisFileDirectory)\*.*" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(CoverletDataCollectorFiles)" DestinationFolder="$(PublishDir)%(RecursiveDir)" />
|
||||
|
||||
</Target>
|
||||
<Target Name="SetXPlatDataCollectorPath" BeforeTargets="VSTest">
|
||||
<PropertyGroup>
|
||||
<VSTestTestAdapterPath>$(VSTestTestAdapterPath);$(MSBuildThisFileDirectory)</VSTestTestAdapterPath>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection;
|
||||
using Xunit;
|
||||
using Moq;
|
||||
using Coverlet.Collector.Utilities;
|
||||
using Coverlet.Collector.Utilities.Interfaces;
|
||||
using Coverlet.Collector.DataCollection;
|
||||
|
||||
namespace Coverlet.Collector.Tests
|
||||
{
|
||||
public class AttachmentManagerTests
|
||||
{
|
||||
private AttachmentManager _attachmentManager;
|
||||
private Mock<DataCollectionSink> _mockDataCollectionSink;
|
||||
private DataCollectionContext _dataCollectionContext;
|
||||
private TestPlatformLogger _testPlatformLogger;
|
||||
private TestPlatformEqtTrace _eqtTrace;
|
||||
private Mock<IFileHelper> _mockFileHelper;
|
||||
private Mock<IDirectoryHelper> _mockDirectoryHelper;
|
||||
private Mock<DataCollectionLogger> _mockDataCollectionLogger;
|
||||
|
||||
public AttachmentManagerTests()
|
||||
{
|
||||
_mockDataCollectionSink = new Mock<DataCollectionSink>();
|
||||
_mockDataCollectionLogger = new Mock<DataCollectionLogger>();
|
||||
var testcase = new TestCase { Id = Guid.NewGuid() };
|
||||
_dataCollectionContext = new DataCollectionContext(testcase);
|
||||
_testPlatformLogger = new TestPlatformLogger(_mockDataCollectionLogger.Object, _dataCollectionContext);
|
||||
_eqtTrace = new TestPlatformEqtTrace();
|
||||
_mockFileHelper = new Mock<IFileHelper>();
|
||||
_mockDirectoryHelper = new Mock<IDirectoryHelper>();
|
||||
|
||||
_attachmentManager = new AttachmentManager(_mockDataCollectionSink.Object, _dataCollectionContext, _testPlatformLogger,
|
||||
_eqtTrace, "report.cobertura.xml", @"E:\temp", _mockFileHelper.Object, _mockDirectoryHelper.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCoverageReportShouldSaveReportToFile()
|
||||
{
|
||||
string coverageReport = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
||||
+ "<coverage line-rate=\"1\" branch-rate=\"1\" version=\"1.9\" timestamp=\"1556263787\" lines-covered=\"0\" lines-valid=\"0\" branches-covered=\"0\" branches-valid=\"0\">"
|
||||
+ "<sources/>"
|
||||
+ "<packages/>"
|
||||
+ "</coverage>";
|
||||
|
||||
_attachmentManager.SendCoverageReport(coverageReport);
|
||||
_mockFileHelper.Verify(x => x.WriteAllText(It.Is<string>(y => y.Contains(@"report.cobertura.xml")), coverageReport), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCoverageReportShouldThrowExceptionWhenFailedToSaveReportToFile()
|
||||
{
|
||||
_attachmentManager = new AttachmentManager(_mockDataCollectionSink.Object, _dataCollectionContext, _testPlatformLogger,
|
||||
_eqtTrace, null, @"E:\temp", _mockFileHelper.Object, _mockDirectoryHelper.Object);
|
||||
|
||||
string coverageReport = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
||||
+ "<coverage line-rate=\"1\" branch-rate=\"1\" version=\"1.9\" timestamp=\"1556263787\" lines-covered=\"0\" lines-valid=\"0\" branches-covered=\"0\" branches-valid=\"0\">"
|
||||
+ "<sources/>"
|
||||
+ "<packages/>"
|
||||
+ "</coverage>";
|
||||
|
||||
string message = Assert.Throws<CoverletDataCollectorException>(() => _attachmentManager.SendCoverageReport(coverageReport)).Message;
|
||||
Assert.Contains("CoverletCoverageDataCollector: Failed to save coverage report", message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCoverageReportShouldSendAttachmentToTestPlatform()
|
||||
{
|
||||
var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()));
|
||||
_attachmentManager = new AttachmentManager(_mockDataCollectionSink.Object, _dataCollectionContext, _testPlatformLogger,
|
||||
_eqtTrace, "report.cobertura.xml", directory.ToString(), new FileHelper(), _mockDirectoryHelper.Object);
|
||||
|
||||
string coverageReport = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
||||
+ "<coverage line-rate=\"1\" branch-rate=\"1\" version=\"1.9\" timestamp=\"1556263787\" lines-covered=\"0\" lines-valid=\"0\" branches-covered=\"0\" branches-valid=\"0\">"
|
||||
+ "<sources/>"
|
||||
+ "<packages/>"
|
||||
+ "</coverage>";
|
||||
|
||||
_attachmentManager.SendCoverageReport(coverageReport);
|
||||
|
||||
_mockDataCollectionSink.Verify(x => x.SendFileAsync(It.IsAny<FileTransferInformation>()));
|
||||
|
||||
directory.Delete(true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnSendFileCompletedShouldCleanUpReportDirectory()
|
||||
{
|
||||
_mockDirectoryHelper.Setup(x => x.Exists(It.Is<string>(y => y.Contains(@"E:\temp")))).Returns(true);
|
||||
|
||||
_mockDataCollectionSink.Raise(x => x.SendFileCompleted += null, new AsyncCompletedEventArgs(null, false, null));
|
||||
|
||||
_mockDirectoryHelper.Verify(x => x.Delete(It.Is<string>(y => y.Contains(@"E:\temp")), true), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnSendFileCompletedShouldThrowCoverletDataCollectorExceptionIfUnableToCleanUpReportDirectory()
|
||||
{
|
||||
_mockDirectoryHelper.Setup(x => x.Exists(It.Is<string>(y => y.Contains(@"E:\temp")))).Returns(true);
|
||||
_mockDirectoryHelper.Setup(x => x.Delete(It.Is<string>(y => y.Contains(@"E:\temp")), true)).Throws(new FileNotFoundException());
|
||||
|
||||
_mockDataCollectionSink.Raise(x => x.SendFileCompleted += null, new AsyncCompletedEventArgs(null, false, null));
|
||||
_mockDataCollectionLogger.Verify(x => x.LogWarning(_dataCollectionContext,
|
||||
It.Is<string>(y => y.Contains("CoverletDataCollectorException: CoverletCoverageDataCollector: Failed to cleanup report directory"))), Times.AtLeastOnce);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection;
|
||||
using Moq;
|
||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||
using Coverlet.Core;
|
||||
using Coverlet.Core.Logging;
|
||||
using Coverlet.Collector.Utilities.Interfaces;
|
||||
using Coverlet.Collector.Utilities;
|
||||
using Xunit;
|
||||
using Coverlet.Collector.DataCollection;
|
||||
|
||||
namespace Coverlet.Collector.Tests
|
||||
{
|
||||
public class CoverletCoverageDataCollectorTests
|
||||
{
|
||||
private DataCollectionEnvironmentContext _context;
|
||||
private CoverletCoverageCollector _coverletCoverageDataCollector;
|
||||
private DataCollectionContext _dataCollectionContext;
|
||||
private Mock<DataCollectionEvents> _mockDataColectionEvents;
|
||||
private Mock<DataCollectionSink> _mockDataCollectionSink;
|
||||
private Mock<ICoverageWrapper> _mockCoverageWrapper;
|
||||
private XmlElement _configurationElement;
|
||||
private Mock<DataCollectionLogger> _mockLogger;
|
||||
|
||||
public CoverletCoverageDataCollectorTests()
|
||||
{
|
||||
_mockDataColectionEvents = new Mock<DataCollectionEvents>();
|
||||
_mockDataCollectionSink = new Mock<DataCollectionSink>();
|
||||
_mockLogger = new Mock<DataCollectionLogger>();
|
||||
_configurationElement = null;
|
||||
|
||||
TestCase testcase = new TestCase { Id = Guid.NewGuid() };
|
||||
_dataCollectionContext = new DataCollectionContext(testcase);
|
||||
_context = new DataCollectionEnvironmentContext(_dataCollectionContext);
|
||||
_mockCoverageWrapper = new Mock<ICoverageWrapper>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnSessionStartShouldInitializeCoverageWithCorrectCoverletSettings()
|
||||
{
|
||||
_coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), _mockCoverageWrapper.Object);
|
||||
_coverletCoverageDataCollector.Initialize(
|
||||
_configurationElement,
|
||||
_mockDataColectionEvents.Object,
|
||||
_mockDataCollectionSink.Object,
|
||||
_mockLogger.Object,
|
||||
_context);
|
||||
IDictionary<string, object> sessionStartProperties = new Dictionary<string, object>();
|
||||
|
||||
sessionStartProperties.Add("TestSources", new List<string> { "abc.dll" });
|
||||
|
||||
_mockDataColectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties));
|
||||
|
||||
_mockCoverageWrapper.Verify(x => x.CreateCoverage(It.Is<CoverletSettings>(y => string.Equals(y.TestModule, "abc.dll")), It.IsAny<ILogger>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnSessionStartShouldPrepareModulesForCoverage()
|
||||
{
|
||||
_coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), _mockCoverageWrapper.Object);
|
||||
_coverletCoverageDataCollector.Initialize(
|
||||
_configurationElement,
|
||||
_mockDataColectionEvents.Object,
|
||||
_mockDataCollectionSink.Object,
|
||||
null,
|
||||
_context);
|
||||
IDictionary<string, object> sessionStartProperties = new Dictionary<string, object>();
|
||||
Coverage coverage = new Coverage("abc.dll", null, null, null, null, null, true, true, "abc.json", true, It.IsAny<ILogger>());
|
||||
|
||||
sessionStartProperties.Add("TestSources", new List<string> { "abc.dll" });
|
||||
_mockCoverageWrapper.Setup(x => x.CreateCoverage(It.IsAny<CoverletSettings>(), It.IsAny<ILogger>())).Returns(coverage);
|
||||
|
||||
_mockDataColectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties));
|
||||
|
||||
_mockCoverageWrapper.Verify(x => x.CreateCoverage(It.Is<CoverletSettings>(y => y.TestModule.Contains("abc.dll")), It.IsAny<ILogger>()), Times.Once);
|
||||
_mockCoverageWrapper.Verify(x => x.PrepareModules(It.IsAny<Coverage>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnSessionEndShouldSendGetCoverageReportToTestPlatform()
|
||||
{
|
||||
_coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), new CoverageWrapper());
|
||||
_coverletCoverageDataCollector.Initialize(
|
||||
_configurationElement,
|
||||
_mockDataColectionEvents.Object,
|
||||
_mockDataCollectionSink.Object,
|
||||
_mockLogger.Object,
|
||||
_context);
|
||||
|
||||
string module = GetType().Assembly.Location;
|
||||
string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb");
|
||||
|
||||
var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()));
|
||||
|
||||
File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true);
|
||||
File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true);
|
||||
|
||||
IDictionary<string, object> sessionStartProperties = new Dictionary<string, object>();
|
||||
sessionStartProperties.Add("TestSources", new List<string> { Path.Combine(directory.FullName, Path.GetFileName(module)) });
|
||||
|
||||
_mockDataColectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties));
|
||||
_mockDataColectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs());
|
||||
|
||||
_mockDataCollectionSink.Verify(x => x.SendFileAsync(It.IsAny<FileTransferInformation>()), Times.Once);
|
||||
|
||||
directory.Delete(true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnSessionStartShouldLogWarningIfInstrumentationFailed()
|
||||
{
|
||||
_coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), _mockCoverageWrapper.Object);
|
||||
_coverletCoverageDataCollector.Initialize(
|
||||
_configurationElement,
|
||||
_mockDataColectionEvents.Object,
|
||||
_mockDataCollectionSink.Object,
|
||||
_mockLogger.Object,
|
||||
_context);
|
||||
IDictionary<string, object> sessionStartProperties = new Dictionary<string, object>();
|
||||
|
||||
sessionStartProperties.Add("TestSources", new List<string> { "abc.dll" });
|
||||
|
||||
_mockCoverageWrapper.Setup(x => x.PrepareModules(It.IsAny<Coverage>())).Throws(new FileNotFoundException());
|
||||
|
||||
_mockDataColectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties));
|
||||
|
||||
_mockLogger.Verify(x => x.LogWarning(_dataCollectionContext,
|
||||
It.Is<string>(y => y.Contains("CoverletDataCollectorException"))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using Coverlet.Collector.DataCollection;
|
||||
using Coverlet.Collector.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Coverlet.Collector.Tests
|
||||
{
|
||||
public class CoverletSettingsParserTests
|
||||
{
|
||||
private CoverletSettingsParser _coverletSettingsParser;
|
||||
|
||||
public CoverletSettingsParserTests()
|
||||
{
|
||||
_coverletSettingsParser = new CoverletSettingsParser(new TestPlatformEqtTrace());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseShouldThrowCoverletDataCollectorExceptionIfTestModulesIsNull()
|
||||
{
|
||||
string message = Assert.Throws<CoverletDataCollectorException>(() => _coverletSettingsParser.Parse(null, null)).Message;
|
||||
|
||||
Assert.Equal("CoverletCoverageDataCollector: No test modules found", message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseShouldThrowCoverletDataCollectorExceptionIfTestModulesIsEmpty()
|
||||
{
|
||||
string message = Assert.Throws<CoverletDataCollectorException>(() => _coverletSettingsParser.Parse(null, Enumerable.Empty<string>())).Message;
|
||||
|
||||
Assert.Equal("CoverletCoverageDataCollector: No test modules found", message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseShouldSelectFirstTestModuleFromTestModulesList()
|
||||
{
|
||||
var testModules = new List<string> { "module1.dll", "module2.dll", "module3.dll" };
|
||||
|
||||
CoverletSettings coverletSettings = _coverletSettingsParser.Parse(null, testModules);
|
||||
|
||||
Assert.Equal("module1.dll", coverletSettings.TestModule);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseShouldCorrectlyParseConfigurationElement()
|
||||
{
|
||||
var testModules = new List<string> { "abc.dll" };
|
||||
var doc = new XmlDocument();
|
||||
var configElement = doc.CreateElement("Configuration");
|
||||
this.CreateCoverletNodes(doc, configElement, CoverletConstants.IncludeFiltersElementName, "[*]*");
|
||||
this.CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeFiltersElementName, "[coverlet.*.tests?]*");
|
||||
this.CreateCoverletNodes(doc, configElement, CoverletConstants.IncludeDirectoriesElementName, @"E:\temp");
|
||||
this.CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeSourceFilesElementName, "module1.cs,module2.cs");
|
||||
this.CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeAttributesElementName, "Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute");
|
||||
this.CreateCoverletNodes(doc, configElement, CoverletConstants.MergeWithElementName, "/path/to/result.json");
|
||||
this.CreateCoverletNodes(doc, configElement, CoverletConstants.UseSourceLinkElementName, "false");
|
||||
this.CreateCoverletNodes(doc, configElement, CoverletConstants.SingleHitElementName, "true");
|
||||
|
||||
CoverletSettings coverletSettings = _coverletSettingsParser.Parse(configElement, testModules);
|
||||
|
||||
Assert.Equal("abc.dll", coverletSettings.TestModule);
|
||||
Assert.Equal("[*]*", coverletSettings.IncludeFilters[0]);
|
||||
Assert.Equal(@"E:\temp", coverletSettings.IncludeDirectories[0]);
|
||||
Assert.Equal("module1.cs", coverletSettings.ExcludeSourceFiles[0]);
|
||||
Assert.Equal("module2.cs", coverletSettings.ExcludeSourceFiles[1]);
|
||||
Assert.Equal("Obsolete", coverletSettings.ExcludeAttributes[0]);
|
||||
Assert.Equal("GeneratedCodeAttribute", coverletSettings.ExcludeAttributes[1]);
|
||||
Assert.Equal("/path/to/result.json", coverletSettings.MergeWith);
|
||||
Assert.Equal("[coverlet.*]*", coverletSettings.ExcludeFilters[0]);
|
||||
Assert.False(coverletSettings.UseSourceLink);
|
||||
Assert.True(coverletSettings.SingleHit);
|
||||
}
|
||||
|
||||
private void CreateCoverletNodes(XmlDocument doc, XmlElement configElement, string nodeSetting, string nodeValue)
|
||||
{
|
||||
var node = doc.CreateNode("element", nodeSetting, string.Empty);
|
||||
node.InnerText = nodeValue;
|
||||
configElement.AppendChild(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
|
||||
<PackageReference Include="Moq" Version="4.10.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\coverlet.core\coverlet.core.csproj" />
|
||||
<ProjectReference Include="..\..\src\coverlet.collector\coverlet.collector.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user