diff --git a/FineCodeCoverage/FineCodeCoverage.csproj b/FineCodeCoverage/FineCodeCoverage.csproj
index 40b67f14..49d4574d 100644
--- a/FineCodeCoverage/FineCodeCoverage.csproj
+++ b/FineCodeCoverage/FineCodeCoverage.csproj
@@ -171,6 +171,9 @@
16.9.20
+
+ 15.8.243
+
compile; build; native; contentfiles; analyzers; buildtransitive
diff --git a/FineCodeCoverage2022/FineCodeCoverage2022.csproj b/FineCodeCoverage2022/FineCodeCoverage2022.csproj
index 843ca610..a74d9eb6 100644
--- a/FineCodeCoverage2022/FineCodeCoverage2022.csproj
+++ b/FineCodeCoverage2022/FineCodeCoverage2022.csproj
@@ -170,6 +170,9 @@
17.10.37
+
+ 17.9.380
+
compile; build; native; contentfiles; analyzers; buildtransitive
diff --git a/FineCodeCoverageTests/CoverageProject_Tests.cs b/FineCodeCoverageTests/CoverageProject_Tests.cs
new file mode 100644
index 00000000..7cc1016b
--- /dev/null
+++ b/FineCodeCoverageTests/CoverageProject_Tests.cs
@@ -0,0 +1,152 @@
+using FineCodeCoverage.Engine.Model;
+using NUnit.Framework;
+using System.IO;
+
+namespace FineCodeCoverageTests
+{
+ public class CoverageProject_Tests
+ {
+ private string tempProjectFilePath;
+ private CoverageProject coverageProject;
+
+ [SetUp]
+ public void SetUp()
+ {
+ coverageProject = new CoverageProject(null, null, null, null, null, false);
+ tempProjectFilePath = Path.Combine(Path.GetTempPath(), "testproject.csproj");
+ coverageProject.ProjectFile = tempProjectFilePath;
+ }
+
+ [Test]
+ public void Should_Be_An_Sdk_Project_When_Project_Element_Has_Sdk_Attribute()
+ {
+ Test(@"", true);
+ }
+
+ [Test]
+ public void Should_Be_An_Sdk_Project_When_Project_Element_Has_Child_Sdk_Element()
+ {
+ Test(@"
+
+
+", true);
+ }
+
+ [Test]
+ public void Should_be_An_Sdk_Project_When_Project_Element_Has_Child_Import_With_Sdk_Attribute()
+ {
+ Test(@"
+
+ Value
+
+
+
+", true);
+ }
+
+ [Test]
+ public void Should_Not_Be_An_Sdk_Project_When_Is_Not()
+ {
+ Test(@"
+
+
+
+
+ Debug
+ AnyCPU
+ {F4B73E7F-F91D-4C95-A3CE-B8DA3A94BB90}
+ Library
+ Properties
+ NetFrameworkUnitTest
+ NetFrameworkUnitTest
+ v4.7.2
+ 512
+ {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 15.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
+ False
+ UnitTest
+
+
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Microsoft.CodeCoverage.17.12.0\lib\net462\Microsoft.VisualStudio.CodeCoverage.Shim.dll
+
+
+ ..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll
+
+
+ ..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {9ef61906-87e7-42ac-b977-058fc55f74d8}
+ DemoOpenCover
+
+
+ {fa82235f-9590-410b-9840-daa224abb17e}
+ Exclude
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
+
+
+
+", false);
+ }
+
+ private void Test(string project, bool expectedIsSdkStyle)
+ {
+ var xmlDeclaration = "\r\n";
+ File.WriteAllText(tempProjectFilePath, $"{xmlDeclaration}{project}");
+ Assert.That(coverageProject.IsDotNetSdkStyle(), Is.EqualTo(expectedIsSdkStyle));
+ }
+
+ [TearDown]
+ public void Delete_ProjectFile()
+ {
+ File.Delete(tempProjectFilePath);
+ }
+ }
+
+}
diff --git a/FineCodeCoverageTests/FineCodeCoverageTests.csproj b/FineCodeCoverageTests/FineCodeCoverageTests.csproj
index 53e88952..b4c2efbf 100644
--- a/FineCodeCoverageTests/FineCodeCoverageTests.csproj
+++ b/FineCodeCoverageTests/FineCodeCoverageTests.csproj
@@ -66,6 +66,7 @@
+
diff --git a/README.md b/README.md
index 4033a67b..263bd889 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,15 @@ or download from [releases](https://github.com/FortuneN/FineCodeCoverage/release
## Prerequisites
-For .Net - that the test adapters are nuget packages. For instance, the NUnit Test Adapter extension is not sufficient.
+For .Net
+
+FCC supports the new [Microsoft.Testing.Platform]( https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-platform-intro).
+FCC does not support the associated testing platform server mode for when using microsoft as a coverage provider.
+You can disable this feature - "Options / Environment / Preview Features / Use testing platform server mode"
+but it is not necessary as FCC adds a global msbuild property, DisableTestingPlatformServerCapability true, that removes
+the project capability. ([see Microsoft.Testing.Platform.MSBuild.targets](https://github.com/microsoft/testfx/blob/d141931b99fad0617d8435ce321fca0c45c9eb94/src/Platform/Microsoft.Testing.Platform.MSBuild/buildMultiTargeting/Microsoft.Testing.Platform.MSBuild.targets#L10)).
+
+When not using Microsoft.TestingPlatform you have added test adapters through nuget packages. For instance, the NUnit Test Adapter extension is not sufficient.
---
diff --git a/SharedProject/Core/Model/CoverageProject.cs b/SharedProject/Core/Model/CoverageProject.cs
index 7e648e9d..d867bf58 100644
--- a/SharedProject/Core/Model/CoverageProject.cs
+++ b/SharedProject/Core/Model/CoverageProject.cs
@@ -32,6 +32,7 @@ internal class CoverageProject : ICoverageProject
private readonly string fccFolderName = "fine-code-coverage";
private readonly string buildOutputFolderName = "build-output";
private string buildOutputPath;
+ private bool? isDotNetSdkStyle;
private string BuildOutputPath
{
get
@@ -78,84 +79,50 @@ public CoverageProject(
public string FCCOutputFolder => Path.Combine(ProjectOutputFolder, fccFolderName);
public bool IsDotNetSdkStyle()
{
- return ProjectFileXElement
+ if (isDotNetSdkStyle.HasValue)
+ {
+ return isDotNetSdkStyle.Value;
+ }
+
+ isDotNetSdkStyle = ProjectFileXElement
.DescendantsAndSelf()
.Where(x =>
{
//https://docs.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk?view=vs-2019
+ return IsRootProjectElementWithSdkAttribute(x) ||
+ IsRootProjectElementSdkElementChild(x) ||
+ IsRootImportElementWithSdkAttribute(x);
+ })
+ .Any();
- /*
-
- ...
-
-
- ...
-
- */
- if
- (
- x?.Name?.LocalName?.Equals("Project", StringComparison.OrdinalIgnoreCase) == true &&
- x?.Parent == null
- )
- {
- var sdkAttr = x?.Attributes()?.FirstOrDefault(attr => attr?.Name?.LocalName?.Equals("Sdk", StringComparison.OrdinalIgnoreCase) == true);
-
- if (sdkAttr?.Value?.Trim()?.StartsWith("Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase) == true)
- {
- return true;
- }
- }
+ return isDotNetSdkStyle.Value;
- /*
-
-
- ...
-
- */
- if
- (
- x?.Name?.LocalName?.Equals("Sdk", StringComparison.OrdinalIgnoreCase) == true &&
- x?.Parent?.Name?.LocalName?.Equals("Project", StringComparison.OrdinalIgnoreCase) == true &&
- x?.Parent?.Parent == null
- )
- {
- var nameAttr = x?.Attributes()?.FirstOrDefault(attr => attr?.Name?.LocalName?.Equals("Name", StringComparison.OrdinalIgnoreCase) == true);
+ bool HasSdkAttribute(XElement x)
+ {
+ return x?.Attributes()?.FirstOrDefault(attr => attr?.Name?.LocalName?.Equals("Sdk", StringComparison.OrdinalIgnoreCase) == true) != null;
+ }
- if (nameAttr?.Value?.Trim()?.StartsWith("Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase) == true)
- {
- return true;
- }
- }
+ bool IsRootProjectElementWithSdkAttribute(XElement x)
+ {
+ return x?.Name?.LocalName?.Equals("Project", StringComparison.OrdinalIgnoreCase) == true &&
+ x?.Parent == null && HasSdkAttribute(x);
+ }
- /*
-
-
- Value
-
-
- ...
-
-
- */
- if
- (
- x?.Name?.LocalName?.Equals("Import", StringComparison.OrdinalIgnoreCase) == true &&
+ bool IsRootProjectElementSdkElementChild(XElement x)
+ {
+ return x?.Name?.LocalName?.Equals("Sdk", StringComparison.OrdinalIgnoreCase) == true &&
x?.Parent?.Name?.LocalName?.Equals("Project", StringComparison.OrdinalIgnoreCase) == true &&
- x?.Parent?.Parent == null
- )
- {
- var sdkAttr = x?.Attributes()?.FirstOrDefault(attr => attr?.Name?.LocalName?.Equals("Sdk", StringComparison.OrdinalIgnoreCase) == true);
-
- if (sdkAttr?.Value?.Trim()?.StartsWith("Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase) == true)
- {
- return true;
- }
- }
+ x?.Parent?.Parent == null;
+ }
- return false;
- })
- .Any();
+ bool IsRootImportElementWithSdkAttribute(XElement x)
+ {
+ return x?.Name?.LocalName?.Equals("Import", StringComparison.OrdinalIgnoreCase) == true &&
+ x?.Parent?.Name?.LocalName?.Equals("Project", StringComparison.OrdinalIgnoreCase) == true &&
+ x?.Parent?.Parent == null && HasSdkAttribute(x);
+ }
}
+
public string TestDllFile { get; set; }
public string ProjectOutputFolder => Path.GetDirectoryName(TestDllFile);
public string FailureDescription { get; set; }
diff --git a/SharedProject/Core/MsTestPlatform/CodeCoverage/ProjectSaver.cs b/SharedProject/Core/MsTestPlatform/CodeCoverage/ProjectSaver.cs
index e5f03004..d7cf949d 100644
--- a/SharedProject/Core/MsTestPlatform/CodeCoverage/ProjectSaver.cs
+++ b/SharedProject/Core/MsTestPlatform/CodeCoverage/ProjectSaver.cs
@@ -9,7 +9,7 @@ namespace FineCodeCoverage.Core.MsTestPlatform.CodeCoverage
{
interface IProjectSaver
{
- void SaveProject(IVsHierarchy projectHierarchy);
+ System.Threading.Tasks.Task SaveProjectAsync(IVsHierarchy projectHierarchy);
}
[Export(typeof(IProjectSaver))]
@@ -26,9 +26,9 @@ IServiceProvider serviceProvider
this.serviceProvider = serviceProvider;
}
- public void SaveProject(IVsHierarchy projectHierarchy)
+ public async System.Threading.Tasks.Task SaveProjectAsync(IVsHierarchy projectHierarchy)
{
- ThreadHelper.ThrowIfNotOnUIThread();
+ await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
var _solution = (IVsSolution)serviceProvider.GetService(typeof(SVsSolution));
Assumes.Present(_solution);
int hr = _solution.SaveSolutionElement((uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_SaveIfDirty, projectHierarchy, 0);
diff --git a/SharedProject/Core/MsTestPlatform/CodeCoverage/VsRunSettingsWriter.cs b/SharedProject/Core/MsTestPlatform/CodeCoverage/VsRunSettingsWriter.cs
index fa603582..7a957f52 100644
--- a/SharedProject/Core/MsTestPlatform/CodeCoverage/VsRunSettingsWriter.cs
+++ b/SharedProject/Core/MsTestPlatform/CodeCoverage/VsRunSettingsWriter.cs
@@ -6,6 +6,7 @@
using System.ComponentModel.Composition;
using System.Threading.Tasks;
using FineCodeCoverage.Core.MsTestPlatform.CodeCoverage;
+using FineCodeCoverage.Core.Utilities;
namespace FineCodeCoverage.Engine.MsTestPlatform.CodeCoverage
{
@@ -15,16 +16,19 @@ internal class VsRunSettingsWriter : IVsRunSettingsWriter
private const string projectRunSettingsFilePathElementName = "RunSettingsFilePath";
private readonly IServiceProvider serviceProvider;
private readonly IProjectSaver projectSaver;
+ private readonly IProjectFilePropertyWriter projectFilePropertyWriter;
[ImportingConstructor]
public VsRunSettingsWriter(
[Import(typeof(SVsServiceProvider))]
IServiceProvider serviceProvider,
- IProjectSaver projectSaver
+ IProjectSaver projectSaver,
+ IProjectFilePropertyWriter projectFilePropertyWriter
)
{
this.serviceProvider = serviceProvider;
this.projectSaver = projectSaver;
+ this.projectFilePropertyWriter = projectFilePropertyWriter;
}
public async Task WriteRunSettingsFilePathAsync(Guid projectGuid, string projectRunSettingsFilePath)
@@ -35,31 +39,23 @@ public async Task WriteRunSettingsFilePathAsync(Guid projectGuid, string p
Assumes.Present(vsSolution);
if (vsSolution.GetProjectOfGuid(ref projectGuid, out var vsHierarchy) == VSConstants.S_OK)
{
- if (vsHierarchy is IVsBuildPropertyStorage vsBuildPropertyStorage)
- {
- success = vsBuildPropertyStorage.SetPropertyValue(projectRunSettingsFilePathElementName, string.Empty, (uint)_PersistStorageType.PST_PROJECT_FILE, projectRunSettingsFilePath) == VSConstants.S_OK;
- }
+ success = await projectFilePropertyWriter.WritePropertyAsync(vsHierarchy, projectRunSettingsFilePathElementName, projectRunSettingsFilePath);
}
return success;
}
public async Task RemoveRunSettingsFilePathAsync(Guid projectGuid)
{
-
var ok = false;
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
var vsSolution = serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution;
Assumes.Present(vsSolution);
if (vsSolution.GetProjectOfGuid(ref projectGuid, out var vsHierarchy) == VSConstants.S_OK)
{
- if (vsHierarchy is IVsBuildPropertyStorage vsBuildPropertyStorage)
+ ok = await projectFilePropertyWriter.RemovePropertyAsync(vsHierarchy, projectRunSettingsFilePathElementName);
+ if (ok)
{
- ok = vsBuildPropertyStorage.RemoveProperty(projectRunSettingsFilePathElementName, string.Empty, (uint)_PersistStorageType.PST_PROJECT_FILE) == VSConstants.S_OK;
-
- if (ok)
- {
- this.projectSaver.SaveProject(vsHierarchy);
- }
+ await this.projectSaver.SaveProjectAsync(vsHierarchy);
}
}
return ok;
diff --git a/SharedProject/Core/MsTestPlatform/TestingPlatform/DisableTestingPlatformServerCapabilityGlobalPropertiesProvider.cs b/SharedProject/Core/MsTestPlatform/TestingPlatform/DisableTestingPlatformServerCapabilityGlobalPropertiesProvider.cs
new file mode 100644
index 00000000..32aedfdf
--- /dev/null
+++ b/SharedProject/Core/MsTestPlatform/TestingPlatform/DisableTestingPlatformServerCapabilityGlobalPropertiesProvider.cs
@@ -0,0 +1,112 @@
+using FineCodeCoverage.Engine.Model;
+using FineCodeCoverage.Engine.MsTestPlatform.TestingPlatform;
+using FineCodeCoverage.Options;
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.ProjectSystem;
+using Microsoft.VisualStudio.ProjectSystem.Build;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Shell.Interop;
+using System;
+using System.Collections.Immutable;
+using System.ComponentModel.Composition;
+using System.Threading;
+
+namespace FineCodeCoverage.Core.MsTestPlatform.TestingPlatform
+{
+ // https://github.com/dotnet/project-system
+ // https://github.com/microsoft/VSProjectSystem/blob/master/doc/extensibility/IProjectGlobalPropertiesProvider.md
+ [Export(typeof(IProjectGlobalPropertiesProvider))]
+ // https://github.com/microsoft/testfx/blob/main/src/Platform/Microsoft.Testing.Platform/buildMultiTargeting/Microsoft.Testing.Platform.props
+ /*
+ https://github.com/microsoft/VSProjectSystem/blob/master/doc/overview/about_project_capabilities.md
+ Classes exported via MEF can declare the project capabilities under which they apply.
+
+ See https://learn.microsoft.com/en-gb/dotnet/api/microsoft.visualstudio.shell.interop.vsprojectcapabilityexpressionmatcher?view=visualstudiosdk-2022
+ For expression syntax
+ */
+ [AppliesTo("TestingPlatformServer.ExitOnProcessExitCapability | TestingPlatformServer.UseListTestsOptionForDiscoveryCapability")]
+ internal class DisableTestingPlatformServerCapabilityGlobalPropertiesProvider : StaticGlobalPropertiesProviderBase
+ {
+ private readonly IUseTestingPlatformProtocolFeatureService useTestingPlatformProtocolFeatureService;
+ private readonly IAppOptionsProvider appOptionsProvider;
+ private readonly ICoverageProjectSettingsManager coverageProjectSettingsManager;
+ private CoverageProject coverageProject;
+ [ImportingConstructor]
+ public DisableTestingPlatformServerCapabilityGlobalPropertiesProvider(
+ IUseTestingPlatformProtocolFeatureService useTestingPlatformProtocolFeatureService,
+ IProjectService projectService,
+ UnconfiguredProject unconfiguredProject,
+ IAppOptionsProvider appOptionsProvider,
+ ICoverageProjectSettingsManager coverageProjectSettingsManager
+ )
+ : base((IProjectCommonServices)projectService.Services)
+ {
+ ThreadHelper.JoinableTaskFactory.Run(async () =>
+ {
+ await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
+ var hostObject = unconfiguredProject.Services.HostObject;
+
+ var vsHierarchy = (IVsHierarchy)hostObject;
+ if (vsHierarchy != null)
+ {
+ var success = vsHierarchy.GetGuidProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_ProjectIDGuid, out Guid projectGuid) == VSConstants.S_OK;
+
+ if (success)
+ {
+ // todo - ICoverageProjectSettingsManager.GetSettingsAsync parameter
+ // to change to what it actually needs
+ coverageProject = new CoverageProject(appOptionsProvider, null, null, null, coverageProjectSettingsManager, false)
+ {
+ Id = projectGuid,
+ ProjectFile = unconfiguredProject.FullPath
+ };
+ }
+ }
+ });
+
+ this.useTestingPlatformProtocolFeatureService = useTestingPlatformProtocolFeatureService;
+ this.appOptionsProvider = appOptionsProvider;
+ this.coverageProjectSettingsManager = coverageProjectSettingsManager;
+ }
+
+ // visual studio options states that a restart is required. If this is true then could cache this value
+ private async System.Threading.Tasks.Task UsingTestingPlatformProtocolAsync()
+ {
+ var useTestingPlatformProtocolFeature = await useTestingPlatformProtocolFeatureService.GetAsync();
+ return useTestingPlatformProtocolFeature.HasValue && useTestingPlatformProtocolFeature.Value;
+ }
+
+ private bool AllProjectsDisabled()
+ {
+ var appOptions = appOptionsProvider.Get();
+ return !appOptions.Enabled && appOptions.DisabledNoCoverage;
+ }
+
+ private async System.Threading.Tasks.Task ProjectEnabledAsync()
+ {
+ if (coverageProject != null)
+ {
+ var projectSettings = await coverageProjectSettingsManager.GetSettingsAsync(coverageProject);
+ return projectSettings.Enabled;
+ }
+ return true;
+ }
+
+ public override async System.Threading.Tasks.Task> GetGlobalPropertiesAsync(CancellationToken cancellationToken)
+ {
+ /*
+ Note that it only matters for ms code coverage but not going to test for that
+ Main thing is that FCC does not turn off if user has Enterprise which does support
+ the new feature and has turned off FCC.
+ */
+ if (await UsingTestingPlatformProtocolAsync() && !AllProjectsDisabled() && await ProjectEnabledAsync())
+ {
+ // https://github.com/microsoft/testfx/blob/main/src/Platform/Microsoft.Testing.Platform.MSBuild/buildMultiTargeting/Microsoft.Testing.Platform.MSBuild.targets
+ return Empty.PropertiesMap.Add("DisableTestingPlatformServerCapability", "true");
+ }
+ return Empty.PropertiesMap;
+ }
+
+ }
+
+}
diff --git a/SharedProject/Core/MsTestPlatform/TestingPlatform/IUseTestingPlatformProtocolFeatureService.cs b/SharedProject/Core/MsTestPlatform/TestingPlatform/IUseTestingPlatformProtocolFeatureService.cs
new file mode 100644
index 00000000..a7b9e6bf
--- /dev/null
+++ b/SharedProject/Core/MsTestPlatform/TestingPlatform/IUseTestingPlatformProtocolFeatureService.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+
+namespace FineCodeCoverage.Engine.MsTestPlatform.TestingPlatform
+{
+ internal interface IUseTestingPlatformProtocolFeatureService
+ {
+ Task GetAsync();
+ }
+
+}
diff --git a/SharedProject/Core/MsTestPlatform/TestingPlatform/UseTestingPlatformProtocolFeatureService.cs b/SharedProject/Core/MsTestPlatform/TestingPlatform/UseTestingPlatformProtocolFeatureService.cs
new file mode 100644
index 00000000..45f6469f
--- /dev/null
+++ b/SharedProject/Core/MsTestPlatform/TestingPlatform/UseTestingPlatformProtocolFeatureService.cs
@@ -0,0 +1,33 @@
+using FineCodeCoverage.Options;
+using System.ComponentModel.Composition;
+
+namespace FineCodeCoverage.Engine.MsTestPlatform.TestingPlatform
+{
+ [Export(typeof(IUseTestingPlatformProtocolFeatureService))]
+ internal class UseTestingPlatformProtocolFeatureService : IUseTestingPlatformProtocolFeatureService
+ {
+ private readonly IReadOnlyUserSettingsStoreProvider readableUserSettingsStoreProvider;
+
+ [ImportingConstructor]
+ public UseTestingPlatformProtocolFeatureService(
+ IReadOnlyUserSettingsStoreProvider readableUserSettingsStoreProvider
+ )
+ {
+ this.readableUserSettingsStoreProvider = readableUserSettingsStoreProvider;
+ }
+ public async System.Threading.Tasks.Task GetAsync()
+ {
+ var store = await readableUserSettingsStoreProvider.ProvideAsync();
+
+ try
+ {
+ var value = store.GetInt32("FeatureFlags\\TestingTools\\UnitTesting\\UseTestingPlatformProtocol", "Value");
+ return value == 1;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ }
+}
diff --git a/SharedProject/Core/Utilities/IProjectFilePropertyWriter.cs b/SharedProject/Core/Utilities/IProjectFilePropertyWriter.cs
new file mode 100644
index 00000000..2c77d3c7
--- /dev/null
+++ b/SharedProject/Core/Utilities/IProjectFilePropertyWriter.cs
@@ -0,0 +1,11 @@
+using Microsoft.VisualStudio.Shell.Interop;
+using System.Threading.Tasks;
+
+namespace FineCodeCoverage.Core.Utilities
+{
+ public interface IProjectFilePropertyWriter
+ {
+ Task RemovePropertyAsync(IVsHierarchy pHierProj, string propertyName);
+ Task WritePropertyAsync(IVsHierarchy projectHierarchy, string propertyName, string value);
+ }
+}
diff --git a/SharedProject/Core/Utilities/ProjectFilePropertyWriter.cs b/SharedProject/Core/Utilities/ProjectFilePropertyWriter.cs
new file mode 100644
index 00000000..85acb35c
--- /dev/null
+++ b/SharedProject/Core/Utilities/ProjectFilePropertyWriter.cs
@@ -0,0 +1,40 @@
+using Microsoft.VisualStudio.Shell.Interop;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio;
+using System.ComponentModel.Composition;
+
+namespace FineCodeCoverage.Core.Utilities
+{
+ [Export(typeof(IProjectFilePropertyWriter))]
+ public class ProjectFilePropertyWriter : IProjectFilePropertyWriter
+ {
+ public async System.Threading.Tasks.Task WritePropertyAsync(IVsHierarchy projectHierarchy, string propertyName, string value)
+ {
+ await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
+ if (projectHierarchy is IVsBuildPropertyStorage vsBuildPropertyStorage)
+ {
+ if(vsBuildPropertyStorage.GetPropertyValue(propertyName, string.Empty, (uint)_PersistStorageType.PST_PROJECT_FILE, out var v) == VSConstants.S_OK)
+ {
+ if (v == value)
+ {
+ return true;
+ }
+ return vsBuildPropertyStorage.SetPropertyValue(propertyName, string.Empty, (uint)_PersistStorageType.PST_PROJECT_FILE, value) == VSConstants.S_OK;
+ }
+ }
+
+ return false;
+ }
+
+ public async System.Threading.Tasks.Task RemovePropertyAsync(IVsHierarchy pHierProj, string propertyName)
+ {
+ await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
+ if (pHierProj is IVsBuildPropertyStorage vsBuildPropertyStorage)
+ {
+ return vsBuildPropertyStorage.RemoveProperty(propertyName, string.Empty, (uint)_PersistStorageType.PST_PROJECT_FILE) == VSConstants.S_OK;
+ }
+ return false;
+ }
+ }
+
+}
diff --git a/SharedProject/Options/IReadOnlyUserSettingsStoreProvider.cs b/SharedProject/Options/IReadOnlyUserSettingsStoreProvider.cs
new file mode 100644
index 00000000..f6701724
--- /dev/null
+++ b/SharedProject/Options/IReadOnlyUserSettingsStoreProvider.cs
@@ -0,0 +1,9 @@
+using Microsoft.VisualStudio.Settings;
+
+namespace FineCodeCoverage.Options
+{
+ internal interface IReadOnlyUserSettingsStoreProvider
+ {
+ System.Threading.Tasks.Task ProvideAsync();
+ }
+}
diff --git a/SharedProject/Options/ReadOnlyUserSettingsStoreProvider.cs b/SharedProject/Options/ReadOnlyUserSettingsStoreProvider.cs
new file mode 100644
index 00000000..3f6f4d54
--- /dev/null
+++ b/SharedProject/Options/ReadOnlyUserSettingsStoreProvider.cs
@@ -0,0 +1,24 @@
+using Microsoft.VisualStudio.Settings;
+using Microsoft.VisualStudio.Shell.Settings;
+using Microsoft.VisualStudio.Shell;
+using System.Threading.Tasks;
+using System.ComponentModel.Composition;
+
+namespace FineCodeCoverage.Options
+{
+ [Export(typeof(IReadOnlyUserSettingsStoreProvider))]
+ internal class ReadOnlyUserSettingsStoreProvider : IReadOnlyUserSettingsStoreProvider
+ {
+ private SettingsStore settingsStore;
+ public async Task ProvideAsync()
+ {
+ if (settingsStore == null)
+ {
+ await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
+ var settingsManager = new ShellSettingsManager(ServiceProvider.GlobalProvider);
+ return settingsManager.GetReadOnlySettingsStore(SettingsScope.UserSettings);
+ }
+ return settingsStore;
+ }
+ }
+}
diff --git a/SharedProject/SharedProject.projitems b/SharedProject/SharedProject.projitems
index 23fbaf57..582ce406 100644
--- a/SharedProject/SharedProject.projitems
+++ b/SharedProject/SharedProject.projitems
@@ -122,6 +122,9 @@
+
+
+
@@ -159,6 +162,7 @@
+
@@ -168,6 +172,7 @@
+
@@ -391,9 +396,11 @@
+
+