Skip to content

Commit ef60caf

Browse files
authored
Merge pull request #253 from tonyhallett/fcc-options
added finecodecoverage-settings.xml searching, top level and string[]…
2 parents dd957c1 + 75de11e commit ef60caf

14 files changed

+1118
-277
lines changed

FineCodeCoverageTests/AppOptionsProvider_Tests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ internal void Should_Use_Deseralized_String_From_Store_For_AppOption_Property(Fu
293293

294294
var appOptions = act();
295295

296-
var appOptionsPropertyInfos = typeof(IAppOptions).GetInterfacePropertyInfos();
296+
var appOptionsPropertyInfos = typeof(IAppOptions).GetPublicProperties();
297297
foreach(var appOptionsPropertyInfo in appOptionsPropertyInfos)
298298
{
299299
if (appOptionsPropertyInfo.PropertyType.IsValueType)
@@ -329,7 +329,7 @@ public void Should_Log_Exception_Thrown_In_LoadSettingsFromStorage()
329329
[Test]
330330
public void IAppOptions_Should_Have_A_Getter_And_Setter_For_Each_Property()
331331
{
332-
var propertyInfos = typeof(IAppOptions).GetInterfacePropertyInfos();
332+
var propertyInfos = typeof(IAppOptions).GetPublicProperties();
333333
Assert.True(propertyInfos.All(pi => pi.GetMethod != null && pi.SetMethod != null));
334334
}
335335

FineCodeCoverageTests/CoverageProject_Settings_Tests.cs

Lines changed: 520 additions & 73 deletions
Large diffs are not rendered by default.

SharedProject/Core/Model/CoverageProjectSettingsManager.cs

Lines changed: 14 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -1,218 +1,38 @@
11
using FineCodeCoverage.Options;
2-
using System;
32
using System.ComponentModel.Composition;
4-
using System.Linq;
3+
using System.IO;
54
using System.Threading.Tasks;
6-
using System.Xml.Linq;
7-
using System.Xml.XPath;
85

96
namespace FineCodeCoverage.Engine.Model
107
{
118
[Export(typeof(ICoverageProjectSettingsManager))]
129
internal class CoverageProjectSettingsManager : ICoverageProjectSettingsManager
1310
{
1411
private readonly IAppOptionsProvider appOptionsProvider;
15-
private readonly ILogger logger;
16-
private readonly IVsBuildFCCSettingsProvider vsBuildFCCSettingsProvider;
12+
private readonly ICoverageProjectSettingsProvider coverageProjectSettingsProvider;
13+
private readonly IFCCSettingsFilesProvider fccSettingsFilesProvider;
14+
private readonly ISettingsMerger settingsMerger;
1715

1816
[ImportingConstructor]
1917
public CoverageProjectSettingsManager(
2018
IAppOptionsProvider appOptionsProvider,
21-
ILogger logger,
22-
IVsBuildFCCSettingsProvider vsBuildFCCSettingsProvider
19+
ICoverageProjectSettingsProvider coverageProjectSettingsProvider,
20+
IFCCSettingsFilesProvider fccSettingsFilesProvider,
21+
ISettingsMerger settingsMerger
2322
)
2423
{
2524
this.appOptionsProvider = appOptionsProvider;
26-
this.logger = logger;
27-
this.vsBuildFCCSettingsProvider = vsBuildFCCSettingsProvider;
28-
}
29-
30-
private bool TypeMatch(Type type, params Type[] otherTypes)
31-
{
32-
return (otherTypes ?? new Type[0]).Any(ot => type == ot);
33-
}
34-
35-
private async Task<XElement> GetSettingsElementAsync(ICoverageProject coverageProject)
36-
{
37-
var settingsElement = SettingsElementFromFCCLabelledPropertyGroup(coverageProject);
38-
if (settingsElement == null)
39-
{
40-
settingsElement = await vsBuildFCCSettingsProvider.GetSettingsAsync(coverageProject.Id);
41-
}
42-
return settingsElement;
43-
}
44-
45-
private XElement SettingsElementFromFCCLabelledPropertyGroup(ICoverageProject coverageProject)
46-
{
47-
/*
48-
<PropertyGroup Label="FineCodeCoverage">
49-
...
50-
</PropertyGroup>
51-
*/
52-
return coverageProject.ProjectFileXElement.XPathSelectElement($"/PropertyGroup[@Label='{Vsix.Code}']");
25+
this.coverageProjectSettingsProvider = coverageProjectSettingsProvider;
26+
this.fccSettingsFilesProvider = fccSettingsFilesProvider;
27+
this.settingsMerger = settingsMerger;
5328
}
5429

5530
public async Task<IAppOptions> GetSettingsAsync(ICoverageProject coverageProject)
5631
{
57-
// get global settings
58-
59-
var settings = appOptionsProvider.Get();
60-
61-
var settingsElement = await GetSettingsElementAsync(coverageProject);
62-
63-
if (settingsElement != null)
64-
{
65-
foreach (var property in settings.GetType().GetProperties())
66-
{
67-
try
68-
{
69-
var xproperty = settingsElement.Descendants().FirstOrDefault(x => x.Name.LocalName.Equals(property.Name, StringComparison.OrdinalIgnoreCase));
70-
71-
if (xproperty == null)
72-
{
73-
continue;
74-
}
75-
76-
var strValue = xproperty.Value;
77-
78-
if (string.IsNullOrWhiteSpace(strValue))
79-
{
80-
continue;
81-
}
82-
83-
var strValueArr = strValue.Split('\n', '\r').Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).ToArray();
84-
85-
if (!strValue.Any())
86-
{
87-
continue;
88-
}
89-
90-
if (TypeMatch(property.PropertyType, typeof(string)))
91-
{
92-
property.SetValue(settings, strValueArr.FirstOrDefault());
93-
}
94-
else if (TypeMatch(property.PropertyType, typeof(string[])))
95-
{
96-
property.SetValue(settings, strValueArr);
97-
}
98-
99-
else if (TypeMatch(property.PropertyType, typeof(bool), typeof(bool?)))
100-
{
101-
if (bool.TryParse(strValueArr.FirstOrDefault(), out bool value))
102-
{
103-
property.SetValue(settings, value);
104-
}
105-
}
106-
else if (TypeMatch(property.PropertyType, typeof(bool[]), typeof(bool?[])))
107-
{
108-
var arr = strValueArr.Where(x => bool.TryParse(x, out var _)).Select(x => bool.Parse(x));
109-
if (arr.Any()) property.SetValue(settings, arr);
110-
}
111-
112-
else if (TypeMatch(property.PropertyType, typeof(int), typeof(int?)))
113-
{
114-
if (int.TryParse(strValueArr.FirstOrDefault(), out var value))
115-
{
116-
property.SetValue(settings, value);
117-
}
118-
}
119-
else if (TypeMatch(property.PropertyType, typeof(int[]), typeof(int?[])))
120-
{
121-
var arr = strValueArr.Where(x => int.TryParse(x, out var _)).Select(x => int.Parse(x));
122-
if (arr.Any()) property.SetValue(settings, arr);
123-
}
124-
125-
else if (TypeMatch(property.PropertyType, typeof(short), typeof(short?)))
126-
{
127-
if (short.TryParse(strValueArr.FirstOrDefault(), out var vaue))
128-
{
129-
property.SetValue(settings, vaue);
130-
}
131-
}
132-
else if (TypeMatch(property.PropertyType, typeof(short[]), typeof(short?[])))
133-
{
134-
var arr = strValueArr.Where(x => short.TryParse(x, out var _)).Select(x => short.Parse(x));
135-
if (arr.Any()) property.SetValue(settings, arr);
136-
}
137-
138-
else if (TypeMatch(property.PropertyType, typeof(long), typeof(long?)))
139-
{
140-
if (long.TryParse(strValueArr.FirstOrDefault(), out var value))
141-
{
142-
property.SetValue(settings, value);
143-
}
144-
}
145-
else if (TypeMatch(property.PropertyType, typeof(long[]), typeof(long?[])))
146-
{
147-
var arr = strValueArr.Where(x => long.TryParse(x, out var _)).Select(x => long.Parse(x));
148-
if (arr.Any()) property.SetValue(settings, arr);
149-
}
150-
151-
else if (TypeMatch(property.PropertyType, typeof(decimal), typeof(decimal?)))
152-
{
153-
if (decimal.TryParse(strValueArr.FirstOrDefault(), out var value))
154-
{
155-
property.SetValue(settings, value);
156-
}
157-
}
158-
else if (TypeMatch(property.PropertyType, typeof(decimal[]), typeof(decimal?[])))
159-
{
160-
var arr = strValueArr.Where(x => decimal.TryParse(x, out var _)).Select(x => decimal.Parse(x));
161-
if (arr.Any()) property.SetValue(settings, arr);
162-
}
163-
164-
else if (TypeMatch(property.PropertyType, typeof(double), typeof(double?)))
165-
{
166-
if (double.TryParse(strValueArr.FirstOrDefault(), out var value))
167-
{
168-
property.SetValue(settings, value);
169-
}
170-
}
171-
else if (TypeMatch(property.PropertyType, typeof(double[]), typeof(double?[])))
172-
{
173-
var arr = strValueArr.Where(x => double.TryParse(x, out var _)).Select(x => double.Parse(x));
174-
if (arr.Any()) property.SetValue(settings, arr);
175-
}
176-
177-
else if (TypeMatch(property.PropertyType, typeof(float), typeof(float?)))
178-
{
179-
if (float.TryParse(strValueArr.FirstOrDefault(), out var value))
180-
{
181-
property.SetValue(settings, value);
182-
}
183-
}
184-
else if (TypeMatch(property.PropertyType, typeof(float[]), typeof(float?[])))
185-
{
186-
var arr = strValueArr.Where(x => float.TryParse(x, out var _)).Select(x => float.Parse(x));
187-
if (arr.Any()) property.SetValue(settings, arr);
188-
}
189-
190-
else if (TypeMatch(property.PropertyType, typeof(char), typeof(char?)))
191-
{
192-
if (char.TryParse(strValueArr.FirstOrDefault(), out var value))
193-
{
194-
property.SetValue(settings, value);
195-
}
196-
}
197-
else if (TypeMatch(property.PropertyType, typeof(char[]), typeof(char?[])))
198-
{
199-
var arr = strValueArr.Where(x => char.TryParse(x, out var _)).Select(x => char.Parse(x));
200-
if (arr.Any()) property.SetValue(settings, arr);
201-
}
202-
203-
else
204-
{
205-
throw new Exception($"Cannot handle '{property.PropertyType.Name}' yet");
206-
}
207-
}
208-
catch (Exception exception)
209-
{
210-
logger.Log($"Failed to override '{property.Name}' setting", exception);
211-
}
212-
}
213-
}
214-
215-
return settings;
32+
var projectDirectory = Path.GetDirectoryName(coverageProject.ProjectFile);
33+
var settingsFilesElements = fccSettingsFilesProvider.Provide(projectDirectory);
34+
var projectSettingsElement = await coverageProjectSettingsProvider.ProvideAsync(coverageProject);
35+
return settingsMerger.Merge(appOptionsProvider.Get(), settingsFilesElements, projectSettingsElement);
21636
}
21737
}
21838

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.ComponentModel.Composition;
2+
using System.Threading.Tasks;
3+
using System.Xml.Linq;
4+
using System.Xml.XPath;
5+
6+
namespace FineCodeCoverage.Engine.Model
7+
{
8+
[Export(typeof(ICoverageProjectSettingsProvider))]
9+
internal class CoverageProjectSettingsProvider : ICoverageProjectSettingsProvider
10+
{
11+
private readonly IVsBuildFCCSettingsProvider vsBuildFCCSettingsProvider;
12+
13+
[ImportingConstructor]
14+
public CoverageProjectSettingsProvider(
15+
IVsBuildFCCSettingsProvider vsBuildFCCSettingsProvider
16+
)
17+
{
18+
this.vsBuildFCCSettingsProvider = vsBuildFCCSettingsProvider;
19+
}
20+
public async Task<XElement> ProvideAsync(ICoverageProject coverageProject)
21+
{
22+
var settingsElement = ProjectSettingsElementFromFCCLabelledPropertyGroup(coverageProject);
23+
if (settingsElement == null)
24+
{
25+
settingsElement = await vsBuildFCCSettingsProvider.GetSettingsAsync(coverageProject.Id);
26+
}
27+
return settingsElement;
28+
}
29+
30+
private XElement ProjectSettingsElementFromFCCLabelledPropertyGroup(ICoverageProject coverageProject)
31+
{
32+
/*
33+
<PropertyGroup Label="FineCodeCoverage">
34+
...
35+
</PropertyGroup>
36+
*/
37+
return coverageProject.ProjectFileXElement.XPathSelectElement($"/PropertyGroup[@Label='{Vsix.Code}']");
38+
}
39+
}
40+
41+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using FineCodeCoverage.Core.Utilities;
2+
using System.Collections.Generic;
3+
using System.ComponentModel.Composition;
4+
using System.IO;
5+
using System.Xml.Linq;
6+
7+
namespace FineCodeCoverage.Engine.Model
8+
{
9+
[Export(typeof(IFCCSettingsFilesProvider))]
10+
internal class FCCSettingsFilesProvider : IFCCSettingsFilesProvider
11+
{
12+
internal const string fccOptionsFileName = "finecodecoverage-settings.xml";
13+
private const string topLevelAttributeName = "topLevel";
14+
private readonly IFileUtil fileUtil;
15+
16+
[ImportingConstructor]
17+
public FCCSettingsFilesProvider(
18+
IFileUtil fileUtil
19+
)
20+
{
21+
this.fileUtil = fileUtil;
22+
}
23+
24+
public List<XElement> Provide(string projectPath)
25+
{
26+
var fccOptionsElements = new List<XElement>();
27+
var directoryPath = projectPath;
28+
var ascend = true;
29+
while (ascend)
30+
{
31+
ascend = AddFromDirectory(fccOptionsElements, directoryPath);
32+
if (ascend)
33+
{
34+
directoryPath = fileUtil.DirectoryParentPath(directoryPath);
35+
if (directoryPath == null)
36+
{
37+
ascend = false;
38+
}
39+
}
40+
41+
}
42+
43+
fccOptionsElements.Reverse();
44+
return fccOptionsElements;
45+
46+
}
47+
48+
private bool AddFromDirectory(List<XElement> fccOptionsElements, string directory)
49+
{
50+
var ascend = true;
51+
var fccOptionsPath = GetFCCOptionsPath(directory);
52+
if (fileUtil.Exists(fccOptionsPath))
53+
{
54+
var fccOptions = fileUtil.ReadAllText(fccOptionsPath);
55+
try
56+
{
57+
var element = XElement.Parse(fccOptions);
58+
fccOptionsElements.Add(element);
59+
ascend = !IsTopLevel(element);
60+
}
61+
catch
62+
{
63+
64+
}
65+
}
66+
67+
return ascend;
68+
}
69+
70+
71+
private bool IsTopLevel(XElement root)
72+
{
73+
var topLevel = false;
74+
var topLevelAttribute = root.Attribute(topLevelAttributeName);
75+
if (topLevelAttribute != null && topLevelAttribute.Value.ToLower() == "true")
76+
{
77+
topLevel = true;
78+
}
79+
return topLevel;
80+
}
81+
82+
private string GetFCCOptionsPath(string directory)
83+
{
84+
return Path.Combine(directory, fccOptionsFileName);
85+
}
86+
87+
}
88+
89+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Threading.Tasks;
2+
using System.Xml.Linq;
3+
4+
namespace FineCodeCoverage.Engine.Model
5+
{
6+
internal interface ICoverageProjectSettingsProvider
7+
{
8+
Task<XElement> ProvideAsync(ICoverageProject coverageProject);
9+
}
10+
11+
}

0 commit comments

Comments
 (0)