Skip to content

Commit 99d2bd8

Browse files
authored
Merge pull request #11 from ademanuele/feature/runSettingsSupport
Feature/run settings support
2 parents ac6ced0 + 6098532 commit 99d2bd8

20 files changed

+395
-97
lines changed

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ You can access the Coverage pad through `View -> Pads -> Coverage`.
2727
Select any test project that is currently open in the workspace using the dropdown menu and hit `Gather Coverage`.
2828
Your test project should start running. When complete, line and branch coverage results for each covered project are shown on the pad as well as in margins for any editors that you have open.
2929

30-
## Planned Features
30+
### Configuring Coverage Collection
31+
This extension supports most of the coverage configuration options currently provided by coverlet.
3132

32-
- Keyboard shortcuts
33-
- Configurable editor margin colors
34-
- Coverage graphs
33+
These can be set by creating a file with the extension `.runsettings` in the directory alongside your solution file.
34+
35+
You can use this [sample runsettings file](doc/example.runsettings) as a starting point.
36+
37+
More info can be found on [coverlet's runsettings documentation.](https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/VSTestIntegration.md#advanced-options-supported-via-runsettings)
3538

3639
## Reporting Issues
3740

@@ -53,7 +56,6 @@ If you like this tool and would like to support its development, you can...
5356

5457
Big thanks to the [coverlet](https://github.com/tonerdo/coverlet) project, which made this extension possible.
5558

56-
5759
Big thanks to [David Karlas](https://developercommunity.visualstudio.com/users/25964/06b25657-7e73-4eef-bfae-8a6c57e7e6c9.html) for resolving an [issue](https://developercommunity.visualstudio.com/content/problem/907691/unable-to-create-custom-vs-for-mac-editor-margin.html) with the editor margin functionality.
5860

5961
## License

VSMac-CodeCoverage/VSMac-CodeCoverage/Coverage/CoverageResultsRepository.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ public ICoverageResults ResultsFor(Project testProject, ConfigurationSelector co
3737

3838
string resultsFilePath = CoverageFilePathForProject(testProject, configuration);
3939
if (!File.Exists(resultsFilePath)) return null;
40-
using (var stream = new FileStream(resultsFilePath, FileMode.Open))
41-
return parser.ParseFrom(stream);
40+
using FileStream stream = new FileStream(resultsFilePath, FileMode.Open);
41+
return parser.ParseFrom(stream);
4242
}
4343

4444
public void SaveResults(ICoverageResults results, Project testProject, ConfigurationSelector configuration)

VSMac-CodeCoverage/VSMac-CodeCoverage/Coverage/CoverageService.cs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
using System;
2+
using System.IO;
3+
using System.Linq;
24
using System.Threading.Tasks;
5+
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
6+
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
37
using MonoDevelop.Core.Execution;
48
using MonoDevelop.Ide;
59
using MonoDevelop.Projects;
@@ -14,7 +18,8 @@ public interface ICoverageService
1418

1519
interface ICoverageProvider
1620
{
17-
void Prepare(Project testProject, ConfigurationSelector configuration);
21+
string RunSettingsDataCollectorFriendlyName { get; }
22+
void Prepare(Project testProject, ConfigurationSelector configuration, DataCollectorSettings coverageSettings);
1823
ICoverageResults GetCoverage(Project testProject, ConfigurationSelector configuration);
1924
}
2025

@@ -41,7 +46,7 @@ public async Task CollectCoverageForTestProject(Project testProject)
4146
}
4247

4348
protected virtual async Task RunTests(Project testProject)
44-
{
49+
{
4550
IExecutionHandler mode = null;
4651
ExecutionContext context = new ExecutionContext(mode, IdeApp.Workbench.ProgressMonitors.ConsoleFactory, null);
4752
var firstRootTest = UnitTestService.FindRootTest(testProject);
@@ -52,16 +57,43 @@ protected virtual async Task RunTests(Project testProject)
5257

5358
private async void UnitTestService_TestSessionStarting(object sender, TestSessionEventArgs e)
5459
{
55-
if (coverageCollectionCompletion == null || !(e.Test.OwnerObject is Project testProject)) return;
60+
if (coverageCollectionCompletion == null || e.Test.OwnerObject is not Project testProject) return;
5661

5762
var configuration = IdeApp.Workspace.ActiveConfiguration;
58-
provider.Prepare(testProject, configuration);
63+
DataCollectorSettings coverageSettings = GetRunSettings(testProject);
64+
provider.Prepare(testProject, configuration, coverageSettings);
5965
await e.Session.Task;
6066
var results = provider.GetCoverage(testProject, configuration);
6167
if (results != null) SaveResults(results, testProject, configuration);
6268
coverageCollectionCompletion.SetResult(true);
6369
}
6470

71+
protected DataCollectorSettings GetRunSettings(Project testProject)
72+
{
73+
string solutionDirectoryPath = testProject.ParentSolution.BaseDirectory.ToString();
74+
string[] runSettingsFiles = Directory.GetFiles(solutionDirectoryPath, "*.runsettings");
75+
string runSettingsFile = runSettingsFiles.FirstOrDefault();
76+
if (runSettingsFile == null) return null;
77+
78+
return ParseRunSettings(runSettingsFile);
79+
}
80+
81+
protected virtual DataCollectorSettings ParseRunSettings(string runSettingsFile)
82+
{
83+
try
84+
{
85+
using FileStream settingsFileStream = new FileStream(runSettingsFile, FileMode.Open);
86+
using StreamReader reader = new StreamReader(settingsFileStream);
87+
string xml = reader.ReadToEnd();
88+
DataCollectionRunSettings runSettings = XmlRunSettingsUtilities.GetDataCollectionRunSettings(xml);
89+
return runSettings.DataCollectorSettingsList.FirstOrDefault(
90+
s => s.FriendlyName == provider.RunSettingsDataCollectorFriendlyName && s.IsEnabled);
91+
} catch
92+
{
93+
return null;
94+
}
95+
}
96+
6597
protected virtual void SaveResults(ICoverageResults results, Project testProject, ConfigurationSelector configuration)
6698
{
6799
repository.SaveResults(results, testProject, configuration);
@@ -72,4 +104,4 @@ public void Dispose()
72104
UnitTestService.TestSessionStarting -= UnitTestService_TestSessionStarting;
73105
}
74106
}
75-
}
107+
}

VSMac-CodeCoverage/VSMac-CodeCoverage/Coverage/LoggedCoverageService.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Threading.Tasks;
3+
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
34
using MonoDevelop.Projects;
45

56
namespace CodeCoverage.Coverage
@@ -33,39 +34,54 @@ public enum LogLevel
3334
class LoggedCoverageService : CoverageService, ILoggedCoverageService
3435
{
3536
IProgress<Log> progress;
37+
readonly ILoggingService loggingService;
3638

37-
public LoggedCoverageService(ICoverageProvider provider, ICoverageResultsRepository repository) : base(provider, repository) { }
39+
public LoggedCoverageService(ICoverageProvider provider, ICoverageResultsRepository repository, ILoggingService loggingService) : base(provider, repository) {
40+
this.loggingService = loggingService;
41+
}
3842

3943
public async Task CollectCoverageForTestProject(Project testProject, IProgress<Log> progress)
4044
{
4145
this.progress = progress;
42-
46+
loggingService.Info($"\n----\n");
47+
loggingService.Info($"Starting coverage gathering: {testProject.Name}");
4348
try
4449
{
4550
await CollectCoverageForTestProject(testProject);
4651
}
4752
catch (Exception e)
4853
{
4954
progress.Report(new Log("Failed to gather coverage. See log for details.", LogLevel.Error, e));
55+
loggingService.Error("Failed to gather coverage.");
5056
}
5157
}
5258

5359
protected override async Task RunTests(Project testProject)
5460
{
5561
progress.Report(new Log("Running unit tests...", LogLevel.Info));
62+
loggingService.Info($"Running unit tests: {testProject.Name}");
5663
await base.RunTests(testProject);
5764
}
5865

66+
protected override DataCollectorSettings ParseRunSettings(string runSettingsFile)
67+
{
68+
loggingService.Info($"Using run settings file: {runSettingsFile}");
69+
return base.ParseRunSettings(runSettingsFile);
70+
}
71+
5972
protected override void SaveResults(ICoverageResults results, Project testProject, ConfigurationSelector configuration)
6073
{
6174
progress.Report(new Log("Saving coverage results...", LogLevel.Info));
75+
loggingService.Info($"Saving coverage results...");
6276
base.SaveResults(results, testProject, configuration);
6377
FinishedGatheringCoveage();
6478
}
6579

6680
void FinishedGatheringCoveage()
6781
{
6882
progress.Report(new Log("Done gathering coverage.", LogLevel.Info));
83+
loggingService.Info("Done gathering coverage.");
84+
loggingService.Info($"\n----\n");
6985
}
7086
}
7187
}

VSMac-CodeCoverage/VSMac-CodeCoverage/Coverlet/CoverletCoverageProvider.cs

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
using System;
22
using System.Collections.Generic;
3-
using Coverlet.Core.Abstracts;
3+
using System.Diagnostics;
4+
using System.IO;
5+
using System.Xml.Serialization;
6+
using Coverlet.Core;
7+
using Coverlet.Core.Abstractions;
48
using Coverlet.Core.Helpers;
9+
using Coverlet.Core.Symbols;
10+
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
511
using MonoDevelop.Projects;
612
using CoverletCoverage = Coverlet.Core.Coverage;
713

@@ -11,7 +17,22 @@ class CoverletCoverageProvider : ICoverageProvider
1117
{
1218
readonly Dictionary<Tuple<Project, ConfigurationSelector>, CoverletCoverage> projectCoverageMap;
1319
readonly ILogger logger;
14-
readonly FileSystem fileSystem;
20+
readonly FileSystem fileSystem;
21+
22+
public string RunSettingsDataCollectorFriendlyName => "XPlat code coverage";
23+
24+
private static CoverageParameters defaultCoverageParameters = new CoverageParameters
25+
{
26+
IncludeFilters = new string[0],
27+
IncludeDirectories = new string[0],
28+
ExcludeFilters = new string[0],
29+
ExcludedSourceFiles = new string[0],
30+
ExcludeAttributes = new string[0],
31+
IncludeTestAssembly = false,
32+
SingleHit = false,
33+
MergeWith = null,
34+
UseSourceLink = false,
35+
};
1536

1637
public CoverletCoverageProvider(ILoggingService log)
1738
{
@@ -20,27 +41,48 @@ public CoverletCoverageProvider(ILoggingService log)
2041
projectCoverageMap = new Dictionary<Tuple<Project, ConfigurationSelector>, CoverletCoverage>();
2142
}
2243

23-
public void Prepare(Project testProject, ConfigurationSelector configuration)
44+
public void Prepare(Project testProject, ConfigurationSelector configuration, DataCollectorSettings coverageSettings)
2445
{
2546
var unitTestDll = testProject.GetOutputFileName(configuration).ToString();
26-
var instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), fileSystem);
27-
var coverage = new CoverletCoverage(unitTestDll,
28-
new string[0], // Include Filters
29-
new string[0], // Include directories
30-
new string[0], // Exclude Filters
31-
new string[0], // Excluded Source Files
32-
new string[0], // Exclude Attributes
33-
false, // Include test assembly
34-
false, // Single hit
35-
null, // Merge with
36-
false, // Use source link
47+
var sourceRootTranslator = new SourceRootTranslator(logger, fileSystem);
48+
var cecilSymbolHelper = new CecilSymbolHelper();
49+
var instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), fileSystem, logger, sourceRootTranslator);
50+
var coverageParameters = GetCoverageParameters(coverageSettings);
51+
52+
var coverage = new CoverletCoverage(unitTestDll,
53+
coverageParameters,
3754
logger,
3855
instrumentationHelper,
39-
fileSystem);
56+
fileSystem,
57+
sourceRootTranslator,
58+
cecilSymbolHelper);
4059
coverage.PrepareModules();
4160
projectCoverageMap[new Tuple<Project, ConfigurationSelector>(testProject, configuration)] = coverage;
4261
}
4362

63+
CoverageParameters GetCoverageParameters(DataCollectorSettings coverageSettings)
64+
{
65+
if (coverageSettings is null) return defaultCoverageParameters;
66+
return ParseSettings(coverageSettings);
67+
}
68+
69+
CoverageParameters ParseSettings(DataCollectorSettings coverageSettings)
70+
{
71+
try
72+
{
73+
string configurationXml = coverageSettings.Configuration.OuterXml;
74+
XmlSerializer serializer = new XmlSerializer(typeof(CoverletRunSettingsConfiguration));
75+
using StringReader reader = new StringReader(configurationXml);
76+
CoverletRunSettingsConfiguration settings = (CoverletRunSettingsConfiguration)serializer.Deserialize(reader);
77+
return settings.ToParameters();
78+
}
79+
catch (Exception e)
80+
{
81+
Debug.WriteLine(e);
82+
return defaultCoverageParameters;
83+
}
84+
}
85+
4486
public ICoverageResults GetCoverage(Project testProject, ConfigurationSelector configuration)
4587
{
4688
var key = new Tuple<Project, ConfigurationSelector>(testProject, configuration);

VSMac-CodeCoverage/VSMac-CodeCoverage/Coverlet/CoverletCoverageResults.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,30 @@ namespace CodeCoverage.Coverage
88
{
99
class CoverletCoverageResults : ICoverageResults
1010
{
11+
public Dictionary<string, CoverageSummary> ModuleCoverage { get; private set; }
12+
1113
readonly CoverletCoverageResult result;
1214

1315
public CoverletCoverageResults(CoverletCoverageResult result)
1416
{
1517
this.result = result;
18+
SetModuleCoverage();
1619
}
1720

18-
public Dictionary<string, CoverageSummary> ModuleCoverage
21+
void SetModuleCoverage()
1922
{
20-
get
21-
{
22-
var modulesCoverage = new Dictionary<string, CoverageSummary>();
23-
var summary = new CoverletCoverageSummary();
24-
25-
foreach (var moduleInfo in result.Modules)
26-
{
27-
var moduleLineCoverage = summary.CalculateLineCoverage(moduleInfo.Value);
28-
var moduleBranchCoverage = summary.CalculateBranchCoverage(moduleInfo.Value);
29-
var moduleName = moduleInfo.Key.Replace(".dll", "");
30-
modulesCoverage[moduleName] = new CoverageSummary(moduleLineCoverage.Percent, moduleBranchCoverage.Percent);
31-
}
23+
var modulesCoverage = new Dictionary<string, CoverageSummary>();
24+
var summary = new CoverletCoverageSummary();
3225

33-
return modulesCoverage;
26+
foreach (var moduleInfo in result.Modules)
27+
{
28+
var moduleLineCoverage = summary.CalculateLineCoverage(moduleInfo.Value);
29+
var moduleBranchCoverage = summary.CalculateBranchCoverage(moduleInfo.Value);
30+
var moduleName = moduleInfo.Key.Replace(".dll", "");
31+
modulesCoverage[moduleName] = new CoverageSummary(moduleLineCoverage.Percent, moduleBranchCoverage.Percent);
3432
}
33+
34+
ModuleCoverage = modulesCoverage;
3535
}
3636

3737
public Dictionary<int, int> CoverageForFile(string path)

VSMac-CodeCoverage/VSMac-CodeCoverage/Coverlet/CoverletResultsParser.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@ class CoverletResultsParser : ICoverageResultsParser
99

1010
public ICoverageResults ParseFrom(Stream stream)
1111
{
12-
using (StreamReader reader = new StreamReader(stream))
13-
using (JsonTextReader jsonReader = new JsonTextReader(reader))
14-
{
15-
JsonSerializer serializer = new JsonSerializer();
16-
var coverletResults = serializer.Deserialize<Coverlet.Core.CoverageResult>(jsonReader);
17-
return new CoverletCoverageResults(coverletResults);
18-
}
12+
using StreamReader reader = new StreamReader(stream);
13+
using JsonTextReader jsonReader = new JsonTextReader(reader);
14+
JsonSerializer serializer = new JsonSerializer();
15+
var coverletResults = serializer.Deserialize<Coverlet.Core.CoverageResult>(jsonReader);
16+
return new CoverletCoverageResults(coverletResults);
1917
}
2018
}
2119
}

0 commit comments

Comments
 (0)