Skip to content

Commit dd957c1

Browse files
authored
Merge pull request #251 from tonyhallett/project-settings-from-msbuild-imports
fallback to IVsBuildPropertyStorage with new FineCodeCoverage xml ele…
2 parents 96da821 + 359e303 commit dd957c1

11 files changed

+464
-183
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using FineCodeCoverage.Engine.Model;
2+
using FineCodeCoverage.Options;
3+
using Moq;
4+
using NUnit.Framework;
5+
using System;
6+
using System.Threading.Tasks;
7+
using System.Xml.Linq;
8+
9+
namespace Test
10+
{
11+
public class CoverageProject_Settings_Tests
12+
{
13+
//[Test]
14+
public void Should_Get_Settings_From_CoverageProjectSettingsManager()
15+
{
16+
17+
}
18+
19+
}
20+
21+
public class CoverageProjectSettingsManager_Tests
22+
{
23+
[Test]
24+
public async Task Should_Use_Global_Settings_If_No_Project_Level()
25+
{
26+
var mockAppOptionsProvider = new Mock<IAppOptionsProvider>();
27+
var mockAppOptions = new Mock<IAppOptions>(MockBehavior.Strict);
28+
var appOptions = mockAppOptions.Object;
29+
mockAppOptionsProvider.Setup(appOptionsProvider => appOptionsProvider.Get()).Returns(appOptions);
30+
31+
var coverageProjectSettingsManager = new CoverageProjectSettingsManager(
32+
mockAppOptionsProvider.Object,
33+
null,
34+
new Mock<IVsBuildFCCSettingsProvider>().Object
35+
);
36+
37+
var mockCoverageProject = new Mock<ICoverageProject>();
38+
mockCoverageProject.Setup(cp => cp.ProjectFileXElement).Returns(new XElement("Project"));
39+
var coverageProject = mockCoverageProject.Object;
40+
var coverageProjectSettings = await coverageProjectSettingsManager.GetSettingsAsync(coverageProject);
41+
Assert.AreSame(appOptions, coverageProjectSettings);
42+
}
43+
44+
[Test]
45+
public async Task Should_Prefer_ProjectLevel_From_FCC_Labelled_PropertyGroup_Over_Global()
46+
{
47+
var mockAppOptionsProvider = new Mock<IAppOptionsProvider>();
48+
var mockAppOptions = new Mock<IAppOptions>(MockBehavior.Strict);
49+
mockAppOptions.SetupSet(o => o.ThresholdForCrapScore = 123); // int type
50+
mockAppOptions.SetupSet(o => o.CoverletCollectorDirectoryPath = "CoverletCollectorDirectoryPath"); // string type
51+
mockAppOptions.SetupSet(o => o.IncludeReferencedProjects = true); // bool type
52+
mockAppOptions.SetupSet(o => o.Exclude = new string[] { "1","2"}); // string array
53+
var appOptions = mockAppOptions.Object;
54+
mockAppOptionsProvider.Setup(appOptionsProvider => appOptionsProvider.Get()).Returns(appOptions);
55+
56+
var coverageProjectSettingsManager = new CoverageProjectSettingsManager(
57+
mockAppOptionsProvider.Object,
58+
null,
59+
// does not use if has FineCodeCoverage PropertyGroup with label
60+
new Mock<IVsBuildFCCSettingsProvider>(MockBehavior.Strict).Object
61+
);
62+
63+
var mockCoverageProject = new Mock<ICoverageProject>();
64+
var projectFileElement = XElement.Parse(@"
65+
<Project>
66+
67+
<PropertyGroup Label='FineCodeCoverage'>
68+
<ThresholdForCrapScore>123</ThresholdForCrapScore>
69+
<CoverletCollectorDirectoryPath>CoverletCollectorDirectoryPath</CoverletCollectorDirectoryPath>
70+
<IncludeReferencedProjects>true</IncludeReferencedProjects>
71+
<Exclude>
72+
1
73+
2
74+
</Exclude>
75+
</PropertyGroup>
76+
</Project>
77+
");
78+
mockCoverageProject.Setup(cp => cp.ProjectFileXElement).Returns(projectFileElement);
79+
var coverageProject = mockCoverageProject.Object;
80+
var coverageProjectSettings = await coverageProjectSettingsManager.GetSettingsAsync(coverageProject);
81+
Assert.AreSame(appOptions, coverageProjectSettings);
82+
mockAppOptions.VerifyAll();
83+
}
84+
85+
[Test]
86+
public async Task Should_Prefer_ProjectLevel_From_Vs_Build_When_No_FCC_Labelled_PropertyGroup_Over_Global()
87+
{
88+
var mockCoverageProject = new Mock<ICoverageProject>();
89+
var projectId = Guid.NewGuid();
90+
mockCoverageProject.Setup(cp => cp.Id).Returns(projectId);
91+
92+
var mockAppOptionsProvider = new Mock<IAppOptionsProvider>();
93+
var mockAppOptions = new Mock<IAppOptions>(MockBehavior.Strict);
94+
mockAppOptions.SetupSet(o => o.HideFullyCovered = true);
95+
var appOptions = mockAppOptions.Object;
96+
mockAppOptionsProvider.Setup(appOptionsProvider => appOptionsProvider.Get()).Returns(appOptions);
97+
98+
var mockBuildVsBuildFCCSettingsProvider = new Mock<IVsBuildFCCSettingsProvider>();
99+
mockBuildVsBuildFCCSettingsProvider.Setup(vsBuildFCCSettingsProvider => vsBuildFCCSettingsProvider.GetSettingsAsync(projectId)).ReturnsAsync(
100+
XElement.Parse(@"
101+
<Container>
102+
<HideFullyCovered>true</HideFullyCovered>
103+
</Container>
104+
")
105+
);
106+
var coverageProjectSettingsManager = new CoverageProjectSettingsManager(
107+
mockAppOptionsProvider.Object,
108+
null,
109+
mockBuildVsBuildFCCSettingsProvider.Object
110+
);
111+
112+
113+
var projectFileElement = XElement.Parse(@"
114+
<Project>
115+
116+
<PropertyGroup Label='NotFineCodeCoverage'>
117+
<ThresholdForCrapScore>123</ThresholdForCrapScore>
118+
</PropertyGroup>
119+
</Project>
120+
");
121+
mockCoverageProject.Setup(cp => cp.ProjectFileXElement).Returns(projectFileElement);
122+
var coverageProject = mockCoverageProject.Object;
123+
var coverageProjectSettings = await coverageProjectSettingsManager.GetSettingsAsync(coverageProject);
124+
Assert.AreSame(appOptions, coverageProjectSettings);
125+
mockAppOptions.VerifyAll();
126+
}
127+
}
128+
}

FineCodeCoverageTests/CoverageProject_Tests.cs

Lines changed: 0 additions & 7 deletions
This file was deleted.

FineCodeCoverageTests/FineCodeCoverageTests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@
141141
<Compile Include="ProcessResponseProcessor_Tests.cs" />
142142
<Compile Include="Properties\AssemblyInfo.cs" />
143143
<Compile Include="FCCEngine_Tests.cs" />
144-
<Compile Include="CoverageProject_Tests.cs" />
144+
<Compile Include="CoverageProject_Settings_Tests.cs" />
145145
<Compile Include="Initializer_Tests.cs" />
146146
<Compile Include="CoverletUtil_Tests.cs" />
147147
<Compile Include="CoverletConsole_Tests.cs" />

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,18 @@ Run a(some) unit test(s) and ...
137137
<ModulePathsExclude>
138138
.*Fabrikam.Math.UnitTest.dll
139139
</ModulePathsExclude>
140+
<!-- and more -->
141+
</PropertyGroup>
142+
```
143+
or **( necessary if storing project settings outside your project file and using msbuild Import )**
144+
```
145+
<PropertyGroup>
146+
<FineCodeCoverage>
147+
<Enabled>
148+
True
149+
</Enabled>
150+
<!-- and more -->
151+
</FineCodeCoverage>
140152
</PropertyGroup>
141153
```
142154

SharedProject/Core/Model/CoverageProject.cs

Lines changed: 12 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ internal class CoverageProject : ICoverageProject
2424
private readonly IFileSynchronizationUtil fileSynchronizationUtil;
2525
private readonly ILogger logger;
2626
private readonly DTE2 dte;
27+
private readonly ICoverageProjectSettingsManager settingsManager;
2728
private readonly bool canUseMsBuildWorkspace;
2829
private XElement projectFileXElement;
2930
private IAppOptions settings;
@@ -58,12 +59,19 @@ private string BuildOutputPath
5859
}
5960
private readonly string coverageToolOutputFolderName = "coverage-tool-output";
6061

61-
public CoverageProject(IAppOptionsProvider appOptionsProvider, IFileSynchronizationUtil fileSynchronizationUtil, ILogger logger, DTE2 dte, bool canUseMsBuildWorkspace)
62+
public CoverageProject(
63+
IAppOptionsProvider appOptionsProvider,
64+
IFileSynchronizationUtil fileSynchronizationUtil,
65+
ILogger logger,
66+
DTE2 dte,
67+
ICoverageProjectSettingsManager settingsManager,
68+
bool canUseMsBuildWorkspace)
6269
{
6370
this.appOptionsProvider = appOptionsProvider;
6471
this.fileSynchronizationUtil = fileSynchronizationUtil;
6572
this.logger = logger;
6673
this.dte = dte;
74+
this.settingsManager = settingsManager;
6775
this.canUseMsBuildWorkspace = canUseMsBuildWorkspace;
6876
}
6977

@@ -158,184 +166,16 @@ public bool IsDotNetSdkStyle()
158166
public string ProjectName { get; set; }
159167
public string CoverageOutputFile => Path.Combine(CoverageOutputFolder, $"{ProjectName}.coverage.xml");
160168

161-
private bool TypeMatch(Type type, params Type[] otherTypes)
162-
{
163-
return (otherTypes ?? new Type[0]).Any(ot => type == ot);
164-
}
165-
166-
167169
public IAppOptions Settings
168170
{
169171
get
170172
{
171173
if (settings == null)
172174
{
173-
// get global settings
174-
175-
settings = appOptionsProvider.Get();
176-
177-
/*
178-
========================================
179-
Process PropertyGroup settings
180-
========================================
181-
<PropertyGroup Label="FineCodeCoverage">
182-
...
183-
</PropertyGroup>
184-
*/
185-
186-
var settingsPropertyGroup = ProjectFileXElement.XPathSelectElement($"/PropertyGroup[@Label='{Vsix.Code}']");
187-
188-
if (settingsPropertyGroup != null)
175+
ThreadHelper.JoinableTaskFactory.Run(async () =>
189176
{
190-
foreach (var property in settings.GetType().GetProperties())
191-
{
192-
try
193-
{
194-
var xproperty = settingsPropertyGroup.Descendants().FirstOrDefault(x => x.Name.LocalName.Equals(property.Name, StringComparison.OrdinalIgnoreCase));
195-
196-
if (xproperty == null)
197-
{
198-
continue;
199-
}
200-
201-
var strValue = xproperty.Value;
202-
203-
if (string.IsNullOrWhiteSpace(strValue))
204-
{
205-
continue;
206-
}
207-
208-
var strValueArr = strValue.Split('\n', '\r').Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).ToArray();
209-
210-
if (!strValue.Any())
211-
{
212-
continue;
213-
}
214-
215-
if (TypeMatch(property.PropertyType, typeof(string)))
216-
{
217-
property.SetValue(settings, strValueArr.FirstOrDefault());
218-
}
219-
else if (TypeMatch(property.PropertyType, typeof(string[])))
220-
{
221-
property.SetValue(settings, strValueArr);
222-
}
223-
224-
else if (TypeMatch(property.PropertyType, typeof(bool), typeof(bool?)))
225-
{
226-
if (bool.TryParse(strValueArr.FirstOrDefault(), out bool value))
227-
{
228-
property.SetValue(settings, value);
229-
}
230-
}
231-
else if (TypeMatch(property.PropertyType, typeof(bool[]), typeof(bool?[])))
232-
{
233-
var arr = strValueArr.Where(x => bool.TryParse(x, out var _)).Select(x => bool.Parse(x));
234-
if (arr.Any()) property.SetValue(settings, arr);
235-
}
236-
237-
else if (TypeMatch(property.PropertyType, typeof(int), typeof(int?)))
238-
{
239-
if (int.TryParse(strValueArr.FirstOrDefault(), out var value))
240-
{
241-
property.SetValue(settings, value);
242-
}
243-
}
244-
else if (TypeMatch(property.PropertyType, typeof(int[]), typeof(int?[])))
245-
{
246-
var arr = strValueArr.Where(x => int.TryParse(x, out var _)).Select(x => int.Parse(x));
247-
if (arr.Any()) property.SetValue(settings, arr);
248-
}
249-
250-
else if (TypeMatch(property.PropertyType, typeof(short), typeof(short?)))
251-
{
252-
if (short.TryParse(strValueArr.FirstOrDefault(), out var vaue))
253-
{
254-
property.SetValue(settings, vaue);
255-
}
256-
}
257-
else if (TypeMatch(property.PropertyType, typeof(short[]), typeof(short?[])))
258-
{
259-
var arr = strValueArr.Where(x => short.TryParse(x, out var _)).Select(x => short.Parse(x));
260-
if (arr.Any()) property.SetValue(settings, arr);
261-
}
262-
263-
else if (TypeMatch(property.PropertyType, typeof(long), typeof(long?)))
264-
{
265-
if (long.TryParse(strValueArr.FirstOrDefault(), out var value))
266-
{
267-
property.SetValue(settings, value);
268-
}
269-
}
270-
else if (TypeMatch(property.PropertyType, typeof(long[]), typeof(long?[])))
271-
{
272-
var arr = strValueArr.Where(x => long.TryParse(x, out var _)).Select(x => long.Parse(x));
273-
if (arr.Any()) property.SetValue(settings, arr);
274-
}
275-
276-
else if (TypeMatch(property.PropertyType, typeof(decimal), typeof(decimal?)))
277-
{
278-
if (decimal.TryParse(strValueArr.FirstOrDefault(), out var value))
279-
{
280-
property.SetValue(settings, value);
281-
}
282-
}
283-
else if (TypeMatch(property.PropertyType, typeof(decimal[]), typeof(decimal?[])))
284-
{
285-
var arr = strValueArr.Where(x => decimal.TryParse(x, out var _)).Select(x => decimal.Parse(x));
286-
if (arr.Any()) property.SetValue(settings, arr);
287-
}
288-
289-
else if (TypeMatch(property.PropertyType, typeof(double), typeof(double?)))
290-
{
291-
if (double.TryParse(strValueArr.FirstOrDefault(), out var value))
292-
{
293-
property.SetValue(settings, value);
294-
}
295-
}
296-
else if (TypeMatch(property.PropertyType, typeof(double[]), typeof(double?[])))
297-
{
298-
var arr = strValueArr.Where(x => double.TryParse(x, out var _)).Select(x => double.Parse(x));
299-
if (arr.Any()) property.SetValue(settings, arr);
300-
}
301-
302-
else if (TypeMatch(property.PropertyType, typeof(float), typeof(float?)))
303-
{
304-
if (float.TryParse(strValueArr.FirstOrDefault(), out var value))
305-
{
306-
property.SetValue(settings, value);
307-
}
308-
}
309-
else if (TypeMatch(property.PropertyType, typeof(float[]), typeof(float?[])))
310-
{
311-
var arr = strValueArr.Where(x => float.TryParse(x, out var _)).Select(x => float.Parse(x));
312-
if (arr.Any()) property.SetValue(settings, arr);
313-
}
314-
315-
else if (TypeMatch(property.PropertyType, typeof(char), typeof(char?)))
316-
{
317-
if (char.TryParse(strValueArr.FirstOrDefault(), out var value))
318-
{
319-
property.SetValue(settings, value);
320-
}
321-
}
322-
else if (TypeMatch(property.PropertyType, typeof(char[]), typeof(char?[])))
323-
{
324-
var arr = strValueArr.Where(x => char.TryParse(x, out var _)).Select(x => char.Parse(x));
325-
if (arr.Any()) property.SetValue(settings, arr);
326-
}
327-
328-
else
329-
{
330-
throw new Exception($"Cannot handle '{property.PropertyType.Name}' yet");
331-
}
332-
}
333-
catch (Exception exception)
334-
{
335-
logger.Log($"Failed to override '{property.Name}' setting", exception);
336-
}
337-
}
338-
}
177+
settings = await settingsManager.GetSettingsAsync(this);
178+
});
339179
}
340180
return settings;
341181
}

0 commit comments

Comments
 (0)