From 0c0b7242216a201f0fe518ecb8f8c462e552d5f7 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Fri, 28 Feb 2025 15:43:00 +0000 Subject: [PATCH] Check output type for exe for Azure function projects that do not produce a dll. Note that still have to include dll. Improved referenced projects, VcProject and removed Msbuild locator. ms regex now escapes dots. --- FineCodeCoverage/FineCodeCoverage.csproj | 6 +- .../FineCodeCoverage2022.csproj | 6 +- .../CoverageProject_Tests.cs | 2 +- .../CoverletConsole_Tests.cs | 5 +- ...overletDataCollectorUtil_RunAsync_Tests.cs | 8 +- FineCodeCoverageTests/Initializer_Tests.cs | 13 +- ...geRunSettingsService_IsCollecting_Tests.cs | 8 +- ...ttingsTemplateReplacementsFactory_Tests.cs | 78 ++++---- .../OpenCoverExeArgumentsProvider_Tests.cs | 8 +- .../ReferencedProject_Tests.cs | 4 +- .../Coverlet/Console/CoverletConsoleUtil.cs | 4 +- .../CoverletDataCollectorUtil.cs | 4 +- .../Core/Initialization/Initializer.cs | 5 - SharedProject/Core/Model/CoverageProject.cs | 181 +----------------- .../Core/Model/CoverageProjectFactory.cs | 45 +---- SharedProject/Core/Model/ICoverageProject.cs | 4 +- .../Core/Model/ICoverageProjectFactory.cs | 7 +- .../CPPReferencedProjectsHelper.cs | 73 +++++++ .../DotNetReferencedProjectsHelper.cs | 52 +++++ .../ICPPReferencedProjectsHelper.cs | 12 ++ .../IDotNetReferencedProjectsHelper.cs | 11 ++ .../IExcludableReferencedProject.cs | 7 + .../IProjectFileReferencedProjectsHelper.cs | 10 + .../ReferencedProjects/IReferencedProject.cs | 8 + .../IReferencedProjectsHelper.cs | 15 ++ .../IVsApiReferencedProjectsHelper.cs | 10 + .../ProjectFileReferencedProjectsHelper.cs | 64 +++++++ .../ReferencedProject.cs | 26 ++- .../ReferencedProjectsHelper.cs | 51 +++++ .../VsApiReferencedProjectsHelper.cs | 84 ++++++++ .../IUserRunSettingsProjectDetails.cs | 7 +- .../CodeCoverage/MsCodeCoverageRegex.cs | 10 +- .../MsCodeCoverageRunSettingsService.cs | 4 +- .../RunSettingsTemplateReplacementsFactory.cs | 7 +- ...erverCapabilityGlobalPropertiesProvider.cs | 2 +- .../OpenCoverExeArgumentsProvider.cs | 4 +- .../TestContainerDiscovery/TestOperation.cs | 2 +- SharedProject/Output/Directory.Build.props | 6 - SharedProject/SharedProject.projitems | 16 +- 39 files changed, 543 insertions(+), 326 deletions(-) create mode 100644 SharedProject/Core/Model/ReferencedProjects/CPPReferencedProjectsHelper.cs create mode 100644 SharedProject/Core/Model/ReferencedProjects/DotNetReferencedProjectsHelper.cs create mode 100644 SharedProject/Core/Model/ReferencedProjects/ICPPReferencedProjectsHelper.cs create mode 100644 SharedProject/Core/Model/ReferencedProjects/IDotNetReferencedProjectsHelper.cs create mode 100644 SharedProject/Core/Model/ReferencedProjects/IExcludableReferencedProject.cs create mode 100644 SharedProject/Core/Model/ReferencedProjects/IProjectFileReferencedProjectsHelper.cs create mode 100644 SharedProject/Core/Model/ReferencedProjects/IReferencedProject.cs create mode 100644 SharedProject/Core/Model/ReferencedProjects/IReferencedProjectsHelper.cs create mode 100644 SharedProject/Core/Model/ReferencedProjects/IVsApiReferencedProjectsHelper.cs create mode 100644 SharedProject/Core/Model/ReferencedProjects/ProjectFileReferencedProjectsHelper.cs rename SharedProject/Core/Model/{ => ReferencedProjects}/ReferencedProject.cs (68%) create mode 100644 SharedProject/Core/Model/ReferencedProjects/ReferencedProjectsHelper.cs create mode 100644 SharedProject/Core/Model/ReferencedProjects/VsApiReferencedProjectsHelper.cs delete mode 100644 SharedProject/Output/Directory.Build.props diff --git a/FineCodeCoverage/FineCodeCoverage.csproj b/FineCodeCoverage/FineCodeCoverage.csproj index 49d4574d..1ff524ce 100644 --- a/FineCodeCoverage/FineCodeCoverage.csproj +++ b/FineCodeCoverage/FineCodeCoverage.csproj @@ -147,9 +147,6 @@ 4.1.1 - - 1.4.1 - 3.11.0 @@ -182,6 +179,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + + 16.10.31320.204 + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/FineCodeCoverage2022/FineCodeCoverage2022.csproj b/FineCodeCoverage2022/FineCodeCoverage2022.csproj index a74d9eb6..d734cd3e 100644 --- a/FineCodeCoverage2022/FineCodeCoverage2022.csproj +++ b/FineCodeCoverage2022/FineCodeCoverage2022.csproj @@ -146,9 +146,6 @@ 4.1.1 - - 1.4.1 - 4.10.0 @@ -181,6 +178,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + + 17.10.40170 + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/FineCodeCoverageTests/CoverageProject_Tests.cs b/FineCodeCoverageTests/CoverageProject_Tests.cs index 7cc1016b..48b96888 100644 --- a/FineCodeCoverageTests/CoverageProject_Tests.cs +++ b/FineCodeCoverageTests/CoverageProject_Tests.cs @@ -12,7 +12,7 @@ public class CoverageProject_Tests [SetUp] public void SetUp() { - coverageProject = new CoverageProject(null, null, null, null, null, false); + coverageProject = new CoverageProject(null, null, null, null); tempProjectFilePath = Path.Combine(Path.GetTempPath(), "testproject.csproj"); coverageProject.ProjectFile = tempProjectFilePath; } diff --git a/FineCodeCoverageTests/CoverletConsole_Tests.cs b/FineCodeCoverageTests/CoverletConsole_Tests.cs index 2cbe7342..62a6b734 100644 --- a/FineCodeCoverageTests/CoverletConsole_Tests.cs +++ b/FineCodeCoverageTests/CoverletConsole_Tests.cs @@ -8,7 +8,6 @@ using FineCodeCoverage.Core.Utilities; using FineCodeCoverage.Engine.Coverlet; using FineCodeCoverage.Engine.Model; -using FineCodeCoverage.Engine.OpenCover; using FineCodeCoverage.Options; using Moq; using NUnit.Framework; @@ -46,8 +45,8 @@ public void Should_Unqualified_Qualified_ExcludeByAttribute() private Mock SafeMockCoverageProject() { var mockCoverageProject = new Mock(); - mockCoverageProject.SetupGet(coverageProject => coverageProject.IncludedReferencedProjects).Returns(new List()); - mockCoverageProject.SetupGet(coverageProject => coverageProject.ExcludedReferencedProjects).Returns(new List()); + mockCoverageProject.SetupGet(coverageProject => coverageProject.IncludedReferencedProjects).Returns(new List()); + mockCoverageProject.SetupGet(coverageProject => coverageProject.ExcludedReferencedProjects).Returns(new List()); mockCoverageProject.SetupGet(coverageProject => coverageProject.Settings).Returns(new Mock().Object); return mockCoverageProject; } diff --git a/FineCodeCoverageTests/CoverletDataCollectorUtil_RunAsync_Tests.cs b/FineCodeCoverageTests/CoverletDataCollectorUtil_RunAsync_Tests.cs index 0aaa407e..537d6192 100644 --- a/FineCodeCoverageTests/CoverletDataCollectorUtil_RunAsync_Tests.cs +++ b/FineCodeCoverageTests/CoverletDataCollectorUtil_RunAsync_Tests.cs @@ -36,8 +36,8 @@ public void SetUp() mockCoverageProject = new Mock(); mockCoverageProject.Setup(cp => cp.Settings).Returns(new Mock().Object); mockCoverageProject.Setup(cp => cp.CoverageOutputFolder).Returns(""); - mockCoverageProject.Setup(cp => cp.ExcludedReferencedProjects).Returns(new List()); - mockCoverageProject.Setup(cp => cp.IncludedReferencedProjects).Returns(new List()); + mockCoverageProject.Setup(cp => cp.ExcludedReferencedProjects).Returns(new List()); + mockCoverageProject.Setup(cp => cp.IncludedReferencedProjects).Returns(new List()); mockRunSettingsCoverletConfiguration = new Mock(); coverletDataCollectorUtil.runSettingsCoverletConfiguration = mockRunSettingsCoverletConfiguration.Object; coverletDataCollectorUtil.coverageProject = mockCoverageProject.Object; @@ -76,7 +76,7 @@ public async Task Should_Get_Settings_With_Exclude_From_CoverageProject_And_RunS var projectExclude = new string[] { "excluded" }; mockCoverageProject.Setup(cp => cp.Settings.Exclude).Returns(projectExclude); mockCoverageProject.Setup(cp => cp.CoverageOutputFolder).Returns(""); - var referencedExcluded = new List { "referencedExcluded" }; + var referencedExcluded = new List { new ReferencedProject("","referencedExcluded",true) }; mockCoverageProject.Setup(cp => cp.ExcludedReferencedProjects).Returns(referencedExcluded); mockRunSettingsCoverletConfiguration.Setup(rsc => rsc.Exclude).Returns("rsexclude"); await coverletDataCollectorUtil.RunAsync(CancellationToken.None); @@ -86,7 +86,7 @@ public async Task Should_Get_Settings_With_Exclude_From_CoverageProject_And_RunS [Test] public async Task Should_Not_Throw_When_Project_Setttings_Exclude_Is_Null_Async() { - var referencedExcluded = new List { "referencedExcluded" }; + var referencedExcluded = new List { new ReferencedProject("", "referencedExcluded", true) }; mockCoverageProject.Setup(cp => cp.ExcludedReferencedProjects).Returns(referencedExcluded); mockCoverageProject.Setup(cp => cp.CoverageOutputFolder).Returns(""); mockRunSettingsCoverletConfiguration.Setup(rsc => rsc.Exclude).Returns("rsexclude"); diff --git a/FineCodeCoverageTests/Initializer_Tests.cs b/FineCodeCoverageTests/Initializer_Tests.cs index 08cf04e8..613d081f 100644 --- a/FineCodeCoverageTests/Initializer_Tests.cs +++ b/FineCodeCoverageTests/Initializer_Tests.cs @@ -8,6 +8,7 @@ using FineCodeCoverage.Core.Initialization; using FineCodeCoverage.Engine; using FineCodeCoverage.Engine.Model; +using Moq; using NUnit.Framework; namespace Test @@ -50,7 +51,7 @@ public async Task Should_Log_Initializing_When_Initialize_Async() private async Task InitializeWithExceptionAsync(Action callback = null) { var initializeException = new Exception("initialize exception"); - mocker.Setup(a => a.Initialize()).Throws(initializeException); + mocker.Setup(fccEngine => fccEngine.Initialize(It.IsAny())).Throws(initializeException); await initializer.InitializeAsync(CancellationToken.None); callback?.Invoke(initializeException); @@ -97,22 +98,18 @@ public async Task Should_Initialize_Dependencies_In_Order_Async() { var disposalToken = CancellationToken.None; List callOrder = new List(); - mocker.GetMock().Setup(cp => cp.Initialize()).Callback(() => - { - callOrder.Add(1); - }); mocker.GetMock().Setup(engine => engine.Initialize(disposalToken)).Callback(() => { - callOrder.Add(2); + callOrder.Add(1); }); mocker.GetMock().Setup(firstTimeToolWindowOpener => firstTimeToolWindowOpener.OpenIfFirstTimeAsync(disposalToken)).Callback(() => { - callOrder.Add(3); + callOrder.Add(2); }); await initializer.InitializeAsync(disposalToken); - Assert.AreEqual(new List { 1, 2, 3 }, callOrder); + Assert.AreEqual(new List { 1, 2 }, callOrder); } } } \ No newline at end of file diff --git a/FineCodeCoverageTests/MsCodeCoverage/MsCodeCoverageRunSettingsService_IsCollecting_Tests.cs b/FineCodeCoverageTests/MsCodeCoverage/MsCodeCoverageRunSettingsService_IsCollecting_Tests.cs index 423c1140..9573963b 100644 --- a/FineCodeCoverageTests/MsCodeCoverage/MsCodeCoverageRunSettingsService_IsCollecting_Tests.cs +++ b/FineCodeCoverageTests/MsCodeCoverage/MsCodeCoverageRunSettingsService_IsCollecting_Tests.cs @@ -215,8 +215,8 @@ public async Task Should_Set_UserRunSettingsProjectDetailsLookup_For_IRunSetting SetupAppOptionsProvider(RunMsCodeCoverage.IfInRunSettings); var projectSettings = new Mock().Object; - var excludedReferencedProjects = new List(); - var includedReferencedProjects = new List(); + var excludedReferencedProjects = new List(); + var includedReferencedProjects = new List(); var coverageProjects = new List { CreateCoverageProject(null), @@ -531,8 +531,8 @@ private ICoverageProject CreateCoverageProject( IAppOptions settings = null, string coverageOutputFolder = "", string testDllFile = "", - List excludedReferencedProjects = null, - List includedReferencedProjects = null, + List excludedReferencedProjects = null, + List includedReferencedProjects = null, string projectFile = "" ) { diff --git a/FineCodeCoverageTests/MsCodeCoverage/RunSettingsTemplateReplacementsFactory_Tests.cs b/FineCodeCoverageTests/MsCodeCoverage/RunSettingsTemplateReplacementsFactory_Tests.cs index 9feac54e..f3315a49 100644 --- a/FineCodeCoverageTests/MsCodeCoverage/RunSettingsTemplateReplacementsFactory_Tests.cs +++ b/FineCodeCoverageTests/MsCodeCoverage/RunSettingsTemplateReplacementsFactory_Tests.cs @@ -61,8 +61,8 @@ internal class RunSettingsTemplateReplacementsFactory_UserRunSettings_Tests private class TestUserRunSettingsProjectDetails : IUserRunSettingsProjectDetails { - public List ExcludedReferencedProjects { get; set; } - public List IncludedReferencedProjects { get; set; } + public List ExcludedReferencedProjects { get; set; } + public List IncludedReferencedProjects { get; set; } public string CoverageOutputFolder { get; set; } public IMsCodeCoverageOptions Settings { get; set; } public string TestDllFile { get; set; } @@ -91,8 +91,8 @@ public void Should_Set_The_TestAdapter() CoverageOutputFolder = "", TestDllFile = "", Settings = new TestMsCodeCoverageOptions{ IncludeTestAssembly = true}, - ExcludedReferencedProjects = new List(), - IncludedReferencedProjects = new List(), + ExcludedReferencedProjects = new List(), + IncludedReferencedProjects = new List(), } }, }; @@ -119,8 +119,8 @@ public void Should_Set_The_ResultsDirectory_To_The_First_OutputFolder(string out CoverageOutputFolder = outputFolder1, TestDllFile = "", Settings = new TestMsCodeCoverageOptions{ IncludeTestAssembly = true}, - ExcludedReferencedProjects = new List(), - IncludedReferencedProjects = new List(), + ExcludedReferencedProjects = new List(), + IncludedReferencedProjects = new List(), } }, { @@ -130,8 +130,8 @@ public void Should_Set_The_ResultsDirectory_To_The_First_OutputFolder(string out CoverageOutputFolder = outputFolder2, TestDllFile = "", Settings = new TestMsCodeCoverageOptions{ IncludeTestAssembly = true}, - ExcludedReferencedProjects = new List(), - IncludedReferencedProjects = new List(), + ExcludedReferencedProjects = new List(), + IncludedReferencedProjects = new List(), } }, { @@ -182,8 +182,8 @@ TestMsCodeCoverageOptions CreateSettings(string id) CoverageOutputFolder = "", TestDllFile = "", Settings = CreateSettings("1"), - ExcludedReferencedProjects = new List(), - IncludedReferencedProjects = new List(), + ExcludedReferencedProjects = new List(), + IncludedReferencedProjects = new List(), } }, { @@ -193,8 +193,8 @@ TestMsCodeCoverageOptions CreateSettings(string id) CoverageOutputFolder = "", TestDllFile = "", Settings = CreateSettings("2"), - ExcludedReferencedProjects = new List(), - IncludedReferencedProjects = new List(), + ExcludedReferencedProjects = new List(), + IncludedReferencedProjects = new List(), } }, { @@ -247,8 +247,8 @@ public void Should_Add_The_Test_Assembly_Regex_Escaped_To_Module_Excludes_When_I IncludeTestAssembly = includeTestAssembly1, ModulePathsExclude = new string[]{ "ModulePathExclude"} }, - ExcludedReferencedProjects = new List(), - IncludedReferencedProjects = new List(), + ExcludedReferencedProjects = new List(), + IncludedReferencedProjects = new List(), TestDllFile = @"Some\Path1" } }, @@ -258,8 +258,8 @@ public void Should_Add_The_Test_Assembly_Regex_Escaped_To_Module_Excludes_When_I { CoverageOutputFolder = "", Settings = new TestMsCodeCoverageOptions{ IncludeTestAssembly = includeTestAssembly2}, - ExcludedReferencedProjects = new List(), - IncludedReferencedProjects = new List(), + ExcludedReferencedProjects = new List(), + IncludedReferencedProjects = new List(), TestDllFile = @"Some\Path2" } }, @@ -304,8 +304,8 @@ public void Should_Add_Regexed_IncludedExcluded_Referenced_Projects_To_ModulePat ModulePathsExclude = new string[] { "ModulePathExclude" }, ModulePathsInclude = new string[] { "ModulePathInclude" } }, - ExcludedReferencedProjects = new List { "ExcludedReferenced1" }, - IncludedReferencedProjects = new List { "IncludedReferenced1" }, + ExcludedReferencedProjects = new List { new ReferencedProject("", "ExcludedReferenced1", true) }, + IncludedReferencedProjects = new List { new ReferencedProject("", "IncludedReferenced1", true) }, } }, { @@ -315,18 +315,18 @@ public void Should_Add_Regexed_IncludedExcluded_Referenced_Projects_To_ModulePat CoverageOutputFolder = "", TestDllFile = "", Settings = new TestMsCodeCoverageOptions { IncludeTestAssembly = !included }, - ExcludedReferencedProjects = new List { "ExcludedReferenced2" }, - IncludedReferencedProjects = new List { "IncludedReferenced2" }, + ExcludedReferencedProjects = new List { new ReferencedProject("", "ExcludedReferenced2", true) }, + IncludedReferencedProjects = new List { new ReferencedProject("", "IncludedReferenced2", true) }, } }, }; var projectDetails = userRunSettingsProjectDetailsLookup.Select(kvp => kvp.Value).ToList(); - IEnumerable allReferencedProjects = projectDetails.SelectMany(pd => included ? pd.IncludedReferencedProjects : pd.ExcludedReferencedProjects); + IEnumerable allReferencedProjects = projectDetails.SelectMany(pd => included ? pd.IncludedReferencedProjects : pd.ExcludedReferencedProjects); - string GetExpectedExcludedOrIncludedEscaped(IEnumerable excludedOrIncludedReferenced) + string GetExpectedExcludedOrIncludedEscaped(IEnumerable excludedOrIncludedReferenced) { - return string.Join("", excludedOrIncludedReferenced.Select(referenced => ModulePathElement(MsCodeCoverageRegex.RegexModuleName(referenced)))); + return string.Join("", excludedOrIncludedReferenced.Select(referenced => ModulePathElement(MsCodeCoverageRegex.RegexModuleName(referenced.AssemblyName,true)))); } var expectedExcludes = GetExpectedExcludedOrIncludedEscaped(allReferencedProjects) + ModulePathElement(included ? "ModulePathInclude" : "ModulePathExclude"); @@ -352,8 +352,8 @@ public void Should_Be_Null_TestAdapter_Replacement_When_Null() CoverageOutputFolder = "", TestDllFile = "", Settings = new TestMsCodeCoverageOptions{ IncludeTestAssembly = true}, - ExcludedReferencedProjects = new List(), - IncludedReferencedProjects = new List(), + ExcludedReferencedProjects = new List(), + IncludedReferencedProjects = new List(), } }, { @@ -363,8 +363,8 @@ public void Should_Be_Null_TestAdapter_Replacement_When_Null() CoverageOutputFolder = "", TestDllFile = "", Settings = new TestMsCodeCoverageOptions{ IncludeTestAssembly = true}, - ExcludedReferencedProjects = new List(), - IncludedReferencedProjects = new List(), + ExcludedReferencedProjects = new List(), + IncludedReferencedProjects = new List(), } } }; @@ -394,8 +394,8 @@ public void Should_Be_Disabled_When_All_Projects_Are_Disabled(bool project1Enabl CoverageOutputFolder = "", TestDllFile = "", Settings = new TestMsCodeCoverageOptions{ Enabled = project1Enabled, IncludeTestAssembly = true}, - ExcludedReferencedProjects = new List(), - IncludedReferencedProjects = new List(), + ExcludedReferencedProjects = new List(), + IncludedReferencedProjects = new List(), } }, { @@ -405,8 +405,8 @@ public void Should_Be_Disabled_When_All_Projects_Are_Disabled(bool project1Enabl CoverageOutputFolder = "", TestDllFile = "", Settings = new TestMsCodeCoverageOptions{ Enabled = project2Enabled, IncludeTestAssembly = true}, - ExcludedReferencedProjects = new List(), - IncludedReferencedProjects = new List(), + ExcludedReferencedProjects = new List(), + IncludedReferencedProjects = new List(), } } }; @@ -451,8 +451,8 @@ private ICoverageProject CreateCoverageProject(Action> fu var mockSettings = new Mock(); mockSettings.Setup(settings => settings.IncludeTestAssembly).Returns(includeTestAssembly); var mockCoverageProject = new Mock(); - mockCoverageProject.Setup(cp => cp.ExcludedReferencedProjects).Returns(new List()); - mockCoverageProject.Setup(cp => cp.IncludedReferencedProjects).Returns(new List()); + mockCoverageProject.Setup(cp => cp.ExcludedReferencedProjects).Returns(new List()); + mockCoverageProject.Setup(cp => cp.IncludedReferencedProjects).Returns(new List()); mockCoverageProject.Setup(cp => cp.TestDllFile).Returns(""); mockCoverageProject.Setup(cp => cp.Settings).Returns(mockSettings.Object); furtherSetup?.Invoke(mockCoverageProject); @@ -587,15 +587,15 @@ public void Should_Have_ModulePathsExclude_Replacements_From_ExcludedReferencedP var coverageProject = CreateCoverageProject(mock => { mock.Setup(cp => cp.Settings).Returns(msCodeCoverageOptions); - mock.Setup(cp => cp.ExcludedReferencedProjects).Returns(new List + mock.Setup(cp => cp.ExcludedReferencedProjects).Returns(new List { - "ModuleName" + new ReferencedProject("","ModuleName",true) }); mock.Setup(cp => cp.TestDllFile).Returns(@"Path\To\Test.dll"); }); var replacements = runSettingsTemplateReplacementsFactory.Create(coverageProject, null); - var expectedModulePathsExclude = $"{ModulePathElement(MsCodeCoverageRegex.RegexModuleName("ModuleName"))}{ModulePathElement(MsCodeCoverageRegex.RegexEscapePath(@"Path\To\Test.dll"))}{ModulePathElement("FromSettings")}"; + var expectedModulePathsExclude = $"{ModulePathElement(MsCodeCoverageRegex.RegexModuleName("ModuleName", true))}{ModulePathElement(MsCodeCoverageRegex.RegexEscapePath(@"Path\To\Test.dll"))}{ModulePathElement("FromSettings")}"; Assert.AreEqual(expectedModulePathsExclude, replacements.ModulePathsExclude); } @@ -611,15 +611,15 @@ public void Should_Have_ModulePathsInclude_Replacements_From_IncludedReferencedP var coverageProject = CreateCoverageProject(mock => { mock.Setup(cp => cp.Settings).Returns(msCodeCoverageOptions); - mock.Setup(cp => cp.IncludedReferencedProjects).Returns(new List + mock.Setup(cp => cp.IncludedReferencedProjects).Returns(new List { - "ModuleName" + new ReferencedProject("", "ModuleName", true) }); mock.Setup(cp => cp.TestDllFile).Returns(@"Path\To\Test.dll"); }); var replacements = runSettingsTemplateReplacementsFactory.Create(coverageProject, null); - var expectedModulePathsInclude = $"{ModulePathElement(MsCodeCoverageRegex.RegexModuleName("ModuleName"))}{ModulePathElement(MsCodeCoverageRegex.RegexEscapePath(@"Path\To\Test.dll"))}{ModulePathElement("FromSettings")}"; + var expectedModulePathsInclude = $"{ModulePathElement(MsCodeCoverageRegex.RegexModuleName("ModuleName",true))}{ModulePathElement(MsCodeCoverageRegex.RegexEscapePath(@"Path\To\Test.dll"))}{ModulePathElement("FromSettings")}"; Assert.AreEqual(expectedModulePathsInclude, replacements.ModulePathsInclude); } diff --git a/FineCodeCoverageTests/OpenCoverExeArgumentsProvider_Tests.cs b/FineCodeCoverageTests/OpenCoverExeArgumentsProvider_Tests.cs index 8e242cd3..127eabb4 100644 --- a/FineCodeCoverageTests/OpenCoverExeArgumentsProvider_Tests.cs +++ b/FineCodeCoverageTests/OpenCoverExeArgumentsProvider_Tests.cs @@ -222,7 +222,7 @@ public void Should_Safely_Filter_Include_Project_IncludedReferencedProjects_Spac var openCoverExeArgumentsProvider = new OpenCoverExeArgumentsProvider(); var mockCoverageProject = SafeMockCoverageProject(); mockCoverageProject.Setup(coverageProject => coverageProject.IncludedReferencedProjects) - .Returns(new List { "Referenced1", "Referenced2" }); + .Returns(new List { new ReferencedProject("", "Referenced1", true), new ReferencedProject("", "Referenced2", true) }); var arguments = openCoverExeArgumentsProvider.Provide(mockCoverageProject.Object, ""); @@ -241,7 +241,7 @@ public void Should_Safely_Filter_Exclude_Project_ExcludedReferencedProjects_Spac var openCoverExeArgumentsProvider = new OpenCoverExeArgumentsProvider(); var mockCoverageProject = SafeMockCoverageProject(); mockCoverageProject.Setup(coverageProject => coverageProject.ExcludedReferencedProjects) - .Returns(new List { "Referenced1", "Referenced2" }); + .Returns(new List { new ReferencedProject("", "Referenced1", true), new ReferencedProject("", "Referenced2", true) }); var arguments = openCoverExeArgumentsProvider.Provide(mockCoverageProject.Object, ""); @@ -264,8 +264,8 @@ public void Should_Safely_Filter_Exclude_Trimmed_Project_Excludes_Trimmed_Of_Spa private Mock SafeMockCoverageProject() { var mockCoverageProject = new Mock(); - mockCoverageProject.SetupGet(coverageProject => coverageProject.IncludedReferencedProjects).Returns(new List()); - mockCoverageProject.SetupGet(coverageProject => coverageProject.ExcludedReferencedProjects).Returns(new List()); + mockCoverageProject.SetupGet(coverageProject => coverageProject.IncludedReferencedProjects).Returns(new List()); + mockCoverageProject.SetupGet(coverageProject => coverageProject.ExcludedReferencedProjects).Returns(new List()); mockCoverageProject.SetupGet(coverageProject => coverageProject.Settings).Returns(new Mock().Object); return mockCoverageProject; } diff --git a/FineCodeCoverageTests/ReferencedProject_Tests.cs b/FineCodeCoverageTests/ReferencedProject_Tests.cs index 74e5d3bd..9b0c152f 100644 --- a/FineCodeCoverageTests/ReferencedProject_Tests.cs +++ b/FineCodeCoverageTests/ReferencedProject_Tests.cs @@ -1,5 +1,5 @@ using System.IO; -using FineCodeCoverage.Core.Model; +using FineCodeCoverage.Engine.Model; using NUnit.Framework; namespace Test @@ -24,7 +24,7 @@ public void Should_ExcludeFromCodeCoverage_If_Has_Project_Property_FCCExcludeFro { var property = addProperty ? $"<{ReferencedProject.excludeFromCodeCoveragePropertyName}/>" : ""; WriteProperty(property); - var referencedProject = new ReferencedProject(tempProjectFilePath, ""); + var referencedProject = new ReferencedProject(tempProjectFilePath, "",true); Assert.AreEqual(addProperty, referencedProject.ExcludeFromCodeCoverage); } diff --git a/SharedProject/Core/Coverlet/Console/CoverletConsoleUtil.cs b/SharedProject/Core/Coverlet/Console/CoverletConsoleUtil.cs index 148032c4..fe2239c5 100644 --- a/SharedProject/Core/Coverlet/Console/CoverletConsoleUtil.cs +++ b/SharedProject/Core/Coverlet/Console/CoverletConsoleUtil.cs @@ -37,7 +37,7 @@ public List GetArguments(ICoverageProject project) coverletSettings.Add($@"--exclude ""{value.Replace("\"", "\\\"").Trim(' ', '\'')}"""); } - foreach (var referencedProjectExcludedFromCodeCoverage in project.ExcludedReferencedProjects) + foreach (var referencedProjectExcludedFromCodeCoverage in project.ExcludedReferencedProjects.Select(rp => rp.AssemblyName)) { coverletSettings.Add($@"--exclude ""[{referencedProjectExcludedFromCodeCoverage}]*"""); } @@ -47,7 +47,7 @@ public List GetArguments(ICoverageProject project) coverletSettings.Add($@"--include ""{value.Replace("\"", "\\\"").Trim(' ', '\'')}"""); } - foreach (var includedReferencedProject in project.IncludedReferencedProjects) + foreach (var includedReferencedProject in project.IncludedReferencedProjects.Select(rp => rp.AssemblyName)) { coverletSettings.Add($@"--include ""[{includedReferencedProject}]*"""); } diff --git a/SharedProject/Core/Coverlet/DataCollector/CoverletDataCollectorUtil.cs b/SharedProject/Core/Coverlet/DataCollector/CoverletDataCollectorUtil.cs index 44a7a3fd..296b80d9 100644 --- a/SharedProject/Core/Coverlet/DataCollector/CoverletDataCollectorUtil.cs +++ b/SharedProject/Core/Coverlet/DataCollector/CoverletDataCollectorUtil.cs @@ -172,7 +172,7 @@ private string GetSettings() dataCollectorSettingsBuilder .WithResultsDirectory(coverageProject.CoverageOutputFolder); - string[] projectExcludes = coverageProject.ExcludedReferencedProjects.Select(erp => $"[{erp}]*").ToArray(); + string[] projectExcludes = coverageProject.ExcludedReferencedProjects.Select(erp => $"[{erp.AssemblyName}]*").ToArray(); if(coverageProject.Settings.Exclude != null) { projectExcludes = projectExcludes.Concat(SanitizeExcludesOrIncludes(coverageProject.Settings.Exclude)).ToArray(); @@ -190,7 +190,7 @@ private string GetSettings() SanitizeExcludesOrIncludes(coverageProject.Settings.ExcludeByAttribute), runSettingsCoverletConfiguration.ExcludeByAttribute); - string[] projectIncludes = coverageProject.IncludedReferencedProjects.Select(irp => $"[{irp}]*").ToArray(); + string[] projectIncludes = coverageProject.IncludedReferencedProjects.Select(irp => $"[{irp.AssemblyName}]*").ToArray(); if(coverageProject.Settings.Include != null) { projectIncludes = projectIncludes.Concat(SanitizeExcludesOrIncludes(coverageProject.Settings.Include)).ToArray(); diff --git a/SharedProject/Core/Initialization/Initializer.cs b/SharedProject/Core/Initialization/Initializer.cs index 5ed0fb54..341b421c 100644 --- a/SharedProject/Core/Initialization/Initializer.cs +++ b/SharedProject/Core/Initialization/Initializer.cs @@ -3,7 +3,6 @@ using System.Threading; using System.Threading.Tasks; using FineCodeCoverage.Engine; -using FineCodeCoverage.Engine.Model; namespace FineCodeCoverage.Core.Initialization { @@ -15,7 +14,6 @@ internal class Initializer : IInitializer { private readonly IFCCEngine fccEngine; private readonly ILogger logger; - private readonly ICoverageProjectFactory coverageProjectFactory; private readonly IFirstTimeToolWindowOpener firstTimeToolWindowOpener; public InitializeStatus InitializeStatus { get; set; } = InitializeStatus.Initializing; @@ -25,7 +23,6 @@ internal class Initializer : IInitializer public Initializer( IFCCEngine fccEngine, ILogger logger, - ICoverageProjectFactory coverageProjectFactory, IFirstTimeToolWindowOpener firstTimeToolWindowOpener, [ImportMany] IInitializable[] initializables @@ -33,7 +30,6 @@ IInitializable[] initializables { this.fccEngine = fccEngine; this.logger = logger; - this.coverageProjectFactory = coverageProjectFactory; this.firstTimeToolWindowOpener = firstTimeToolWindowOpener; } public async Task InitializeAsync(CancellationToken cancellationToken) @@ -44,7 +40,6 @@ public async Task InitializeAsync(CancellationToken cancellationToken) logger.Log($"Initializing"); cancellationToken.ThrowIfCancellationRequested(); - coverageProjectFactory.Initialize(); fccEngine.Initialize(cancellationToken); diff --git a/SharedProject/Core/Model/CoverageProject.cs b/SharedProject/Core/Model/CoverageProject.cs index d867bf58..cdc025b3 100644 --- a/SharedProject/Core/Model/CoverageProject.cs +++ b/SharedProject/Core/Model/CoverageProject.cs @@ -5,15 +5,10 @@ using System.Threading.Tasks; using Task = System.Threading.Tasks.Task; using System.Xml.Linq; -using System.Xml.XPath; -using EnvDTE; -using FineCodeCoverage.Core.Model; using FineCodeCoverage.Core.Utilities; using FineCodeCoverage.Engine.FileSynchronization; using FineCodeCoverage.Options; using Microsoft.VisualStudio.Shell; -using Microsoft.CodeAnalysis.MSBuild; -using EnvDTE80; using System.Threading; namespace FineCodeCoverage.Engine.Model @@ -22,10 +17,8 @@ internal class CoverageProject : ICoverageProject { private readonly IAppOptionsProvider appOptionsProvider; private readonly IFileSynchronizationUtil fileSynchronizationUtil; - private readonly ILogger logger; - private readonly DTE2 dte; private readonly ICoverageProjectSettingsManager settingsManager; - private readonly bool canUseMsBuildWorkspace; + private readonly IReferencedProjectsHelper referencedProjectsHelper; private XElement projectFileXElement; private IAppOptions settings; private string targetFramework; @@ -63,17 +56,13 @@ private string BuildOutputPath public CoverageProject( IAppOptionsProvider appOptionsProvider, IFileSynchronizationUtil fileSynchronizationUtil, - ILogger logger, - DTE2 dte, ICoverageProjectSettingsManager settingsManager, - bool canUseMsBuildWorkspace) + IReferencedProjectsHelper referencedProjectsHelper) { this.appOptionsProvider = appOptionsProvider; this.fileSynchronizationUtil = fileSynchronizationUtil; - this.logger = logger; - this.dte = dte; this.settingsManager = settingsManager; - this.canUseMsBuildWorkspace = canUseMsBuildWorkspace; + this.referencedProjectsHelper = referencedProjectsHelper; } public string FCCOutputFolder => Path.Combine(ProjectOutputFolder, fccFolderName); @@ -164,8 +153,8 @@ public XElement ProjectFileXElement } } - public List ExcludedReferencedProjects { get; } = new List(); - public List IncludedReferencedProjects { get; set; } = new List(); + public List ExcludedReferencedProjects { get; } = new List(); + public List IncludedReferencedProjects { get; set; } = new List(); public bool Is64Bit { get; set; } public string RunSettingsFile { get; set; } public bool IsDotNetFramework { get; private set; } @@ -234,180 +223,30 @@ public async Task PrepareForCoverageA private async Task SetIncludedExcludedReferencedProjectsAsync() { - List referencedProjects = await GetReferencedProjectsAsync(); + var referencedProjects = await referencedProjectsHelper.GetReferencedProjectsAsync(ProjectFile,() => ProjectFileXElement); SetExcludedReferencedProjects(referencedProjects); SetIncludedReferencedProjects(referencedProjects); } - private void SetIncludedReferencedProjects(List referencedProjects) + private void SetIncludedReferencedProjects(List referencedProjects) { if (Settings.IncludeReferencedProjects) { - IncludedReferencedProjects = referencedProjects.Select(referencedProject => referencedProject.AssemblyName).ToList(); + IncludedReferencedProjects = new List(referencedProjects); } } - private void SetExcludedReferencedProjects(List referencedProjects) + private void SetExcludedReferencedProjects(List referencedProjects) { foreach (var referencedProject in referencedProjects) { if (referencedProject.ExcludeFromCodeCoverage) { - ExcludedReferencedProjects.Add(referencedProject.AssemblyName); + ExcludedReferencedProjects.Add(referencedProject); } } } - private async Task> GetReferencedProjectsAsync() - { - List referencedProjects = await SafeGetReferencedProjectsFromDteAsync() ?? await GetReferencedProjectsFromProjectFileAsync(); - return referencedProjects; - } - - private async Task> SafeGetReferencedProjectsFromDteAsync() - { - try - { - return await GetReferencedProjectsFromDteAsync(); - } - catch (Exception) { } - return null; - } - - private async Task GetProjectAsync() - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - var project = dte.Solution.Projects.Cast().FirstOrDefault(p => - { - ThreadHelper.ThrowIfNotOnUIThread(); - //have to try here as unloaded projects will throw - var projectFullName = ""; - try - { - projectFullName = p.FullName; - } - catch { } - return projectFullName == ProjectFile; - }); - - if (project == null) - { - return null; - } - - return project.Object as VSLangProj.VSProject; - } - - private IEnumerable GetReferencedSourceProjects(VSLangProj.VSProject vsproject) - { - return vsproject.References.Cast().Where(r => r.SourceProject != null) - .Select(r => r.SourceProject); - } - - private async Task GetAssemblyNameAsync(Project project) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - var assNameProperty = project.Properties.Cast().FirstOrDefault(p => - { -#pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread - return p.Name == "AssemblyName"; -#pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread - }); - return assNameProperty?.Value.ToString() ?? project.Name; - } - private async Task GetReferencedProjectAsync(Project project) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - var assemblyName = await GetAssemblyNameAsync(project); - return new ReferencedProject(project.FullName, assemblyName); - } - - private async Task> GetReferencedProjectsFromDteAsync() - { - var vsproject = await GetProjectAsync(); - - if (vsproject == null) - { - return null; - } - return (await Task.WhenAll(GetReferencedSourceProjects(vsproject).Select(GetReferencedProjectAsync))).ToList(); - - } - - private async Task> SafeGetReferencedProjectsWithDesignTimeBuildAsync() - { - try - { - return await GetReferencedProjectsWithDesignTimeBuildWorkerAsync(); - } - catch (Exception exception) - { - logger.Log("Unable to get referenced projects with design time build", exception); - } - return new List(); - } - - private async Task> GetReferencedProjectsWithDesignTimeBuildWorkerAsync() - { - var msBuildWorkspace = MSBuildWorkspace.Create(); - var project = await msBuildWorkspace.OpenProjectAsync(ProjectFile); - var solution = msBuildWorkspace.CurrentSolution; - return project.ProjectReferences.Select( - pr => solution.Projects.First(p => p.Id == pr.ProjectId).FilePath) - .Where(path => path != null) - .Select(path => new ReferencedProject(path)).ToList(); - } - - private async Task> GetReferencedProjectsFromProjectFileAsync() - { - /* - - - - - */ - - - var xprojectReferences = ProjectFileXElement.XPathSelectElements($"/ItemGroup/ProjectReference[@Include]"); - var requiresDesignTimeBuild = false; - List referencedProjectFiles = new List(); - foreach (var xprojectReference in xprojectReferences) - { - var referencedProjectProjectFile = xprojectReference.Attribute("Include").Value; - if (referencedProjectProjectFile.Contains("$(")) - { - if (canUseMsBuildWorkspace) - { - requiresDesignTimeBuild = true; - break; - } - else - { - logger.Log($"Cannot exclude referenced project {referencedProjectProjectFile} of {ProjectFile} with {ReferencedProject.excludeFromCodeCoveragePropertyName}. Cannot use MSBuildWorkspace"); - } - - } - else - { - if (!Path.IsPathRooted(referencedProjectProjectFile)) - { - referencedProjectProjectFile = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(ProjectFile), referencedProjectProjectFile)); - } - referencedProjectFiles.Add(referencedProjectProjectFile); - } - - } - - if (requiresDesignTimeBuild) - { - var referencedProjects = await SafeGetReferencedProjectsWithDesignTimeBuildAsync(); - return referencedProjects; - - } - - return referencedProjectFiles.Select(referencedProjectProjectFile => new ReferencedProject(referencedProjectProjectFile)).ToList(); - } - private void EnsureDirectories() { EnsureFccDirectory(); diff --git a/SharedProject/Core/Model/CoverageProjectFactory.cs b/SharedProject/Core/Model/CoverageProjectFactory.cs index 6d958cdd..f0b1eb77 100644 --- a/SharedProject/Core/Model/CoverageProjectFactory.cs +++ b/SharedProject/Core/Model/CoverageProjectFactory.cs @@ -1,13 +1,6 @@ -using System; -using System.ComponentModel.Composition; -using System.Threading.Tasks; -using EnvDTE; -using EnvDTE80; +using System.ComponentModel.Composition; using FineCodeCoverage.Engine.FileSynchronization; using FineCodeCoverage.Options; -using Microsoft.Build.Locator; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Threading; namespace FineCodeCoverage.Engine.Model { @@ -16,54 +9,30 @@ internal class CoverageProjectFactory : ICoverageProjectFactory { private readonly IAppOptionsProvider appOptionsProvider; private readonly IFileSynchronizationUtil fileSynchronizationUtil; - private readonly ILogger logger; private readonly ICoverageProjectSettingsManager coverageProjectSettingsManager; - private bool canUseMsBuildWorkspace = true; - private readonly AsyncLazy lazyDTE2; + private readonly IReferencedProjectsHelper referencedProjectsHelper; [ImportingConstructor] public CoverageProjectFactory( IAppOptionsProvider appOptionsProvider, IFileSynchronizationUtil fileSynchronizationUtil, - ILogger logger, ICoverageProjectSettingsManager coverageProjectSettingsManager, - [Import(typeof(SVsServiceProvider))] - IServiceProvider serviceProvider) + IReferencedProjectsHelper referencedProjectsHelper + ) { this.appOptionsProvider = appOptionsProvider; this.fileSynchronizationUtil = fileSynchronizationUtil; - this.logger = logger; this.coverageProjectSettingsManager = coverageProjectSettingsManager; - - lazyDTE2 = new AsyncLazy(async () => - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - return (DTE2)serviceProvider.GetService(typeof(DTE)); - },ThreadHelper.JoinableTaskFactory); + this.referencedProjectsHelper = referencedProjectsHelper; } - public void Initialize() - { - try - { - MSBuildLocator.RegisterDefaults(); - } - catch - { - canUseMsBuildWorkspace = false; - } - } - public async Task CreateAsync() + public ICoverageProject Create() { - var dte2 = await lazyDTE2.GetValueAsync(); - return new CoverageProject( appOptionsProvider, fileSynchronizationUtil, - logger, - dte2, coverageProjectSettingsManager, - canUseMsBuildWorkspace); + referencedProjectsHelper); } } } diff --git a/SharedProject/Core/Model/ICoverageProject.cs b/SharedProject/Core/Model/ICoverageProject.cs index 67bad4cc..01821f3d 100644 --- a/SharedProject/Core/Model/ICoverageProject.cs +++ b/SharedProject/Core/Model/ICoverageProject.cs @@ -12,8 +12,8 @@ internal interface ICoverageProject string CoverageOutputFile { get; } string CoverageOutputFolder { get; set; } string DefaultCoverageOutputFolder { get; } - List ExcludedReferencedProjects { get; } - List IncludedReferencedProjects { get; } + List ExcludedReferencedProjects { get; } + List IncludedReferencedProjects { get; } string FailureDescription { get; set; } string FailureStage { get; set; } bool HasFailed { get; } diff --git a/SharedProject/Core/Model/ICoverageProjectFactory.cs b/SharedProject/Core/Model/ICoverageProjectFactory.cs index ef54b62c..a9328ccb 100644 --- a/SharedProject/Core/Model/ICoverageProjectFactory.cs +++ b/SharedProject/Core/Model/ICoverageProjectFactory.cs @@ -1,10 +1,7 @@ -using System.Threading.Tasks; - -namespace FineCodeCoverage.Engine.Model +namespace FineCodeCoverage.Engine.Model { internal interface ICoverageProjectFactory { - Task CreateAsync(); - void Initialize(); + ICoverageProject Create(); } } diff --git a/SharedProject/Core/Model/ReferencedProjects/CPPReferencedProjectsHelper.cs b/SharedProject/Core/Model/ReferencedProjects/CPPReferencedProjectsHelper.cs new file mode 100644 index 00000000..35ea4f7d --- /dev/null +++ b/SharedProject/Core/Model/ReferencedProjects/CPPReferencedProjectsHelper.cs @@ -0,0 +1,73 @@ +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.VCProjectEngine; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace FineCodeCoverage.Engine.Model +{ + [Export(typeof(ICPPReferencedProjectsHelper))] + internal class CPPReferencedProjectsHelper : ICPPReferencedProjectsHelper + { + private VCProject GetReferencedVCProject(VCProjectReference projectReference) + { + ThreadHelper.ThrowIfNotOnUIThread(); + return projectReference.ReferencedProject as VCProject + ?? (projectReference.ReferencedProject as EnvDTE.Project)?.Object as VCProject; + } + + private bool? IsDll(VCProject vcProject) + { + if (!(vcProject.Configurations is IEnumerable configurations)) + return null; + + var configuration = configurations.Cast().FirstOrDefault(); + if (configuration == null) + return null; + + bool isDll = configuration.ConfigurationType == ConfigurationTypes.typeDynamicLibrary; + bool isApplication = configuration.ConfigurationType == ConfigurationTypes.typeApplication; + if (!isDll && !isApplication) + return null; + return isDll; + } + + private string GetCPPProjectReferenceProjectFilePath(VCProjectReference reference) + { + ThreadHelper.ThrowIfNotOnUIThread(); + var vsReference = reference.Reference as VSLangProj.Reference; + var sourceProject = vsReference.SourceProject; + return sourceProject.FileName; + } + + public async Task> GetInstrumentableReferencedProjectsAsync(VCProject cppProject) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + if (!(cppProject.VCReferences is IEnumerable vcReferences)) + return null; + + return vcReferences + .OfType() + .Select(reference => + { + var referencedProject = GetReferencedVCProject(reference); + + var isDll = IsDll(referencedProject); + return isDll.HasValue ?(IExcludableReferencedProject) new ReferencedProject( + GetCPPProjectReferenceProjectFilePath(reference), + Path.GetFileNameWithoutExtension(reference.FullPath), + isDll.Value + ) + : null; + }) + .Where(p => p != null) + .ToList(); + } + + } + +} diff --git a/SharedProject/Core/Model/ReferencedProjects/DotNetReferencedProjectsHelper.cs b/SharedProject/Core/Model/ReferencedProjects/DotNetReferencedProjectsHelper.cs new file mode 100644 index 00000000..18933374 --- /dev/null +++ b/SharedProject/Core/Model/ReferencedProjects/DotNetReferencedProjectsHelper.cs @@ -0,0 +1,52 @@ +using EnvDTE; +using Microsoft.VisualStudio.Shell; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Threading.Tasks; +using VSLangProj; +using VSLangProj80; + +namespace FineCodeCoverage.Engine.Model +{ + [Export(typeof(IDotNetReferencedProjectsHelper))] + internal class DotNetReferencedProjectsHelper : IDotNetReferencedProjectsHelper + { + public async Task> GetReferencedProjectsAsync(VSProject vsProject) + { + var referencedProjects = (await System.Threading.Tasks.Task.WhenAll(GetReferencedSourceProjects(vsProject).Select(GetReferencedProjectAsync))).ToList(); + return new List(referencedProjects); + } + + private IEnumerable GetReferencedSourceProjects(VSProject vsproject) + { + return vsproject.References.Cast().Where(r => r.SourceProject != null) + .Select(r => r.SourceProject); + } + + private async Task GetReferencedProjectAsync(Project project) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var (assemblyName, isDll) = await GetAssemblyNameIsDllAsync(project); + return new ReferencedProject(project.FullName, assemblyName, isDll); + } + + private async Task<(string, bool)> GetAssemblyNameIsDllAsync(Project project) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var assemblyNameProperty = project.Properties.Item(nameof(ProjectProperties3.AssemblyName)); + var assemblyName = assemblyNameProperty?.Value.ToString() ?? project.Name; + var outputTypeProperty = project.Properties.Item(nameof(ProjectProperties3.OutputType)); + var isDll = true; + if (outputTypeProperty != null) + { + prjOutputType po = (prjOutputType)Enum.Parse(typeof(prjOutputType), outputTypeProperty.Value.ToString()); + isDll = po == prjOutputType.prjOutputTypeLibrary; + } + + return (assemblyName, isDll); + } + } + +} diff --git a/SharedProject/Core/Model/ReferencedProjects/ICPPReferencedProjectsHelper.cs b/SharedProject/Core/Model/ReferencedProjects/ICPPReferencedProjectsHelper.cs new file mode 100644 index 00000000..d2020811 --- /dev/null +++ b/SharedProject/Core/Model/ReferencedProjects/ICPPReferencedProjectsHelper.cs @@ -0,0 +1,12 @@ +using Microsoft.VisualStudio.VCProjectEngine; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace FineCodeCoverage.Engine.Model +{ + internal interface ICPPReferencedProjectsHelper + { + Task> GetInstrumentableReferencedProjectsAsync(VCProject cppProject); + } + +} diff --git a/SharedProject/Core/Model/ReferencedProjects/IDotNetReferencedProjectsHelper.cs b/SharedProject/Core/Model/ReferencedProjects/IDotNetReferencedProjectsHelper.cs new file mode 100644 index 00000000..fe7e57af --- /dev/null +++ b/SharedProject/Core/Model/ReferencedProjects/IDotNetReferencedProjectsHelper.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using VSLangProj; + +namespace FineCodeCoverage.Engine.Model +{ + internal interface IDotNetReferencedProjectsHelper + { + Task> GetReferencedProjectsAsync(VSProject vsProject); + } +} diff --git a/SharedProject/Core/Model/ReferencedProjects/IExcludableReferencedProject.cs b/SharedProject/Core/Model/ReferencedProjects/IExcludableReferencedProject.cs new file mode 100644 index 00000000..6bd33618 --- /dev/null +++ b/SharedProject/Core/Model/ReferencedProjects/IExcludableReferencedProject.cs @@ -0,0 +1,7 @@ +namespace FineCodeCoverage.Engine.Model +{ + interface IExcludableReferencedProject : IReferencedProject + { + bool ExcludeFromCodeCoverage { get; } + } +} diff --git a/SharedProject/Core/Model/ReferencedProjects/IProjectFileReferencedProjectsHelper.cs b/SharedProject/Core/Model/ReferencedProjects/IProjectFileReferencedProjectsHelper.cs new file mode 100644 index 00000000..068d4c84 --- /dev/null +++ b/SharedProject/Core/Model/ReferencedProjects/IProjectFileReferencedProjectsHelper.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace FineCodeCoverage.Engine.Model +{ + interface IProjectFileReferencedProjectsHelper + { + List GetReferencedProjects(string projectFile, XElement projectFileXElement); + } +} diff --git a/SharedProject/Core/Model/ReferencedProjects/IReferencedProject.cs b/SharedProject/Core/Model/ReferencedProjects/IReferencedProject.cs new file mode 100644 index 00000000..42172682 --- /dev/null +++ b/SharedProject/Core/Model/ReferencedProjects/IReferencedProject.cs @@ -0,0 +1,8 @@ +namespace FineCodeCoverage.Engine.Model +{ + internal interface IReferencedProject + { + string AssemblyName { get; } + bool IsDll { get; } + } +} diff --git a/SharedProject/Core/Model/ReferencedProjects/IReferencedProjectsHelper.cs b/SharedProject/Core/Model/ReferencedProjects/IReferencedProjectsHelper.cs new file mode 100644 index 00000000..446b6902 --- /dev/null +++ b/SharedProject/Core/Model/ReferencedProjects/IReferencedProjectsHelper.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace FineCodeCoverage.Engine.Model +{ + internal interface IReferencedProjectsHelper + { + + // todo - should not need ms build workspaces or parsing the project file + Task> GetReferencedProjectsAsync( + string projectFile, Func projectFileXElementProvider); + } +} diff --git a/SharedProject/Core/Model/ReferencedProjects/IVsApiReferencedProjectsHelper.cs b/SharedProject/Core/Model/ReferencedProjects/IVsApiReferencedProjectsHelper.cs new file mode 100644 index 00000000..5f894195 --- /dev/null +++ b/SharedProject/Core/Model/ReferencedProjects/IVsApiReferencedProjectsHelper.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace FineCodeCoverage.Engine.Model +{ + internal interface IVsApiReferencedProjectsHelper + { + Task> GetReferencedProjectsAsync(string projectFile); + } +} diff --git a/SharedProject/Core/Model/ReferencedProjects/ProjectFileReferencedProjectsHelper.cs b/SharedProject/Core/Model/ReferencedProjects/ProjectFileReferencedProjectsHelper.cs new file mode 100644 index 00000000..567a3082 --- /dev/null +++ b/SharedProject/Core/Model/ReferencedProjects/ProjectFileReferencedProjectsHelper.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using System.Xml.XPath; + +namespace FineCodeCoverage.Engine.Model +{ + // todo - remove this ? Should not be necessary + [Export(typeof(IProjectFileReferencedProjectsHelper))] + internal class ProjectFileReferencedProjectsHelper : IProjectFileReferencedProjectsHelper + + { + private readonly ILogger logger; + + [ImportingConstructor] + public ProjectFileReferencedProjectsHelper(ILogger logger) + { + this.logger = logger; + } + + public List GetReferencedProjects( + string projectFile, XElement projectFileXElement + ) + { + /* + + + + + */ + + var xprojectReferences = projectFileXElement.XPathSelectElements($"/ItemGroup/ProjectReference[@Include]"); + var requiresDesignTimeBuild = false; + List referencedProjectFiles = new List(); + foreach (var xprojectReference in xprojectReferences) + { + var referencedProjectProjectFile = xprojectReference.Attribute("Include").Value; + if (referencedProjectProjectFile.Contains("$(")) + { + logger.Log($"Cannot exclude referenced project {referencedProjectProjectFile} of {projectFile} with {ReferencedProject.excludeFromCodeCoveragePropertyName}. Cannot use MSBuildWorkspace"); + } + else + { + if (!Path.IsPathRooted(referencedProjectProjectFile)) + { + referencedProjectProjectFile = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(projectFile), referencedProjectProjectFile)); + } + referencedProjectFiles.Add(referencedProjectProjectFile); + } + + } + + if (requiresDesignTimeBuild) + { + return new List(); + + } + + return referencedProjectFiles.Select(referencedProjectProjectFile => (IExcludableReferencedProject)new ReferencedProject(referencedProjectProjectFile)).ToList(); + } + } +} diff --git a/SharedProject/Core/Model/ReferencedProject.cs b/SharedProject/Core/Model/ReferencedProjects/ReferencedProject.cs similarity index 68% rename from SharedProject/Core/Model/ReferencedProject.cs rename to SharedProject/Core/Model/ReferencedProjects/ReferencedProject.cs index 882e3010..32eaeb74 100644 --- a/SharedProject/Core/Model/ReferencedProject.cs +++ b/SharedProject/Core/Model/ReferencedProjects/ReferencedProject.cs @@ -3,18 +3,21 @@ using System.Xml.XPath; using FineCodeCoverage.Core.Utilities; -namespace FineCodeCoverage.Core.Model +namespace FineCodeCoverage.Engine.Model { - internal class ReferencedProject - { + internal class ReferencedProject : IExcludableReferencedProject + { internal const string excludeFromCodeCoveragePropertyName = "FCCExcludeFromCodeCoverage"; private readonly string projectPath; + - public ReferencedProject(string projectPath,string assemblyName) + public ReferencedProject(string projectPath,string assemblyName,bool isDll) { - AssemblyName = assemblyName; this.projectPath = projectPath; + AssemblyName = assemblyName; + IsDll = isDll; } + public ReferencedProject(string projectPath) { this.projectPath = projectPath; @@ -43,8 +46,17 @@ private string GetAssemblyName(XElement projectFileXElement, string fallbackName return result; } - public string AssemblyName { get; private set; } - public bool ExcludeFromCodeCoverage + public string AssemblyName { get; } + + public bool IsDll { get; } = true; + + /* + Annoyingly by allowing and not true + it is not possible to use IVsBuildPropertyStorage. + Todo - consider breaking change to true + Given that purpose is for dotnet framework..... + */ + public bool ExcludeFromCodeCoverage { get { diff --git a/SharedProject/Core/Model/ReferencedProjects/ReferencedProjectsHelper.cs b/SharedProject/Core/Model/ReferencedProjects/ReferencedProjectsHelper.cs new file mode 100644 index 00000000..134fa97d --- /dev/null +++ b/SharedProject/Core/Model/ReferencedProjects/ReferencedProjectsHelper.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace FineCodeCoverage.Engine.Model +{ + [Export(typeof(IReferencedProjectsHelper))] + internal class ReferencedProjectsHelper : IReferencedProjectsHelper + { + private readonly IVsApiReferencedProjectsHelper vsApiReferencedProjectsHelper; + private readonly IProjectFileReferencedProjectsHelper projectFileReferencedProjectsHelper; + private string projectFile { get; set; } + private Func projectFileXElementProvider; + + [ImportingConstructor] + public ReferencedProjectsHelper( + IVsApiReferencedProjectsHelper vsApiReferencedProjectsHelper, + IProjectFileReferencedProjectsHelper projectFileReferencedProjectsHelper + ) + { + this.vsApiReferencedProjectsHelper = vsApiReferencedProjectsHelper; + this.projectFileReferencedProjectsHelper = projectFileReferencedProjectsHelper; + } + + public async Task> GetReferencedProjectsAsync(string projectFile, Func projectFileXElementProvider) + { + this.projectFileXElementProvider = projectFileXElementProvider; + this.projectFile = projectFile; + var referencedProjects = await GetReferencedProjectsAsync(); + return new List(referencedProjects); + } + + private async Task> GetReferencedProjectsAsync() + { + return await SafeGetReferencedProjectsFromVSApiAsync() ?? projectFileReferencedProjectsHelper.GetReferencedProjects(projectFile, projectFileXElementProvider()); + } + + private async Task> SafeGetReferencedProjectsFromVSApiAsync() + { + try + { + return await vsApiReferencedProjectsHelper.GetReferencedProjectsAsync(projectFile); + } + catch (Exception) { } + return null; + } + } + +} diff --git a/SharedProject/Core/Model/ReferencedProjects/VsApiReferencedProjectsHelper.cs b/SharedProject/Core/Model/ReferencedProjects/VsApiReferencedProjectsHelper.cs new file mode 100644 index 00000000..f0991049 --- /dev/null +++ b/SharedProject/Core/Model/ReferencedProjects/VsApiReferencedProjectsHelper.cs @@ -0,0 +1,84 @@ +using EnvDTE; +using EnvDTE80; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; +using Microsoft.VisualStudio.VCProjectEngine; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Threading.Tasks; +using VSLangProj; + +namespace FineCodeCoverage.Engine.Model +{ + + [Export(typeof(IVsApiReferencedProjectsHelper))] + internal class VsApiReferencedProjectsHelper : IVsApiReferencedProjectsHelper + { + private readonly ICPPReferencedProjectsHelper cppReferencedProjectsHelper; + private readonly IDotNetReferencedProjectsHelper dotNetReferencedProjectsHelper; + private AsyncLazy lazyDTE2; + + [ImportingConstructor] + public VsApiReferencedProjectsHelper( + [Import(typeof(SVsServiceProvider))] + IServiceProvider serviceProvider, + ICPPReferencedProjectsHelper cppReferencedProjectsHelper, + IDotNetReferencedProjectsHelper dotNetReferencedProjectsHelper + ) + { + lazyDTE2 = new AsyncLazy(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + return (DTE2)serviceProvider.GetService(typeof(DTE)); + }, ThreadHelper.JoinableTaskFactory); + this.cppReferencedProjectsHelper = cppReferencedProjectsHelper; + this.dotNetReferencedProjectsHelper = dotNetReferencedProjectsHelper; + } + public async Task> GetReferencedProjectsAsync(string projectFile) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var project = await GetProjectAsync(projectFile); + + if (project == null) + { + return null; + } + + var cppProject = project.Object as VCProject; + if (cppProject != null) + { + return await cppReferencedProjectsHelper.GetInstrumentableReferencedProjectsAsync(cppProject); + } + + var vsProject = project.Object as VSProject; + if (vsProject != null) + { + return await dotNetReferencedProjectsHelper.GetReferencedProjectsAsync(vsProject); + } + + return null; + } + + private async Task GetProjectAsync(string projectFile) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var dte2 = await lazyDTE2.GetValueAsync(); + // note that cannot do dte.Solution.Projects.Item(ProjectFile) - fails when dots in path + return dte2.Solution.Projects.Cast().FirstOrDefault(p => + { + ThreadHelper.ThrowIfNotOnUIThread(); + //have to try here as unloaded projects will throw + var projectFullName = ""; + try + { + projectFullName = p.FullName; + } + catch { } + return projectFullName == projectFile; + }); + } + } + +} diff --git a/SharedProject/Core/MsTestPlatform/CodeCoverage/IUserRunSettingsProjectDetails.cs b/SharedProject/Core/MsTestPlatform/CodeCoverage/IUserRunSettingsProjectDetails.cs index a4df082e..a39925b1 100644 --- a/SharedProject/Core/MsTestPlatform/CodeCoverage/IUserRunSettingsProjectDetails.cs +++ b/SharedProject/Core/MsTestPlatform/CodeCoverage/IUserRunSettingsProjectDetails.cs @@ -1,12 +1,13 @@ -using FineCodeCoverage.Options; +using FineCodeCoverage.Engine.Model; +using FineCodeCoverage.Options; using System.Collections.Generic; namespace FineCodeCoverage.Engine.MsTestPlatform.CodeCoverage { internal interface IUserRunSettingsProjectDetails { - List ExcludedReferencedProjects { get; set; } - List IncludedReferencedProjects { get; set; } + List ExcludedReferencedProjects { get; set; } + List IncludedReferencedProjects { get; set; } string CoverageOutputFolder { get; set; } IMsCodeCoverageOptions Settings { get; set; } string TestDllFile { get; set; } diff --git a/SharedProject/Core/MsTestPlatform/CodeCoverage/MsCodeCoverageRegex.cs b/SharedProject/Core/MsTestPlatform/CodeCoverage/MsCodeCoverageRegex.cs index 2301c13c..41c44ae0 100644 --- a/SharedProject/Core/MsTestPlatform/CodeCoverage/MsCodeCoverageRegex.cs +++ b/SharedProject/Core/MsTestPlatform/CodeCoverage/MsCodeCoverageRegex.cs @@ -7,9 +7,15 @@ public static string RegexEscapePath(string path) return path.Replace(@"\", @"\\"); } - public static string RegexModuleName(string moduleName) + public static string RegexModuleName(string moduleName, bool isDll) + { + var extensionMatch = isDll ? "dll" : "(dll|exe)"; + return $".*\\\\{EscapeDots(moduleName)}\\.{extensionMatch}$"; + } + + private static string EscapeDots(string moduleName) { - return $".*\\\\{moduleName}.dll$"; + return moduleName.Replace(".", @"\."); } } diff --git a/SharedProject/Core/MsTestPlatform/CodeCoverage/MsCodeCoverageRunSettingsService.cs b/SharedProject/Core/MsTestPlatform/CodeCoverage/MsCodeCoverageRunSettingsService.cs index cf137bce..5729169b 100644 --- a/SharedProject/Core/MsTestPlatform/CodeCoverage/MsCodeCoverageRunSettingsService.cs +++ b/SharedProject/Core/MsTestPlatform/CodeCoverage/MsCodeCoverageRunSettingsService.cs @@ -29,8 +29,8 @@ private class UserRunSettingsProjectDetails : IUserRunSettingsProjectDetails public IMsCodeCoverageOptions Settings { get; set; } public string CoverageOutputFolder { get; set; } public string TestDllFile { get; set; } - public List ExcludedReferencedProjects { get; set; } - public List IncludedReferencedProjects { get; set; } + public List ExcludedReferencedProjects { get; set; } + public List IncludedReferencedProjects { get; set; } } private class CoverageProjectsByType { diff --git a/SharedProject/Core/MsTestPlatform/CodeCoverage/RunSettingsTemplateReplacementsFactory.cs b/SharedProject/Core/MsTestPlatform/CodeCoverage/RunSettingsTemplateReplacementsFactory.cs index 30bf0633..84aecedf 100644 --- a/SharedProject/Core/MsTestPlatform/CodeCoverage/RunSettingsTemplateReplacementsFactory.cs +++ b/SharedProject/Core/MsTestPlatform/CodeCoverage/RunSettingsTemplateReplacementsFactory.cs @@ -1,5 +1,4 @@ -using FineCodeCoverage.Core.Model; -using FineCodeCoverage.Engine.Model; +using FineCodeCoverage.Engine.Model; using FineCodeCoverage.Options; using Microsoft.VisualStudio.TestWindow.Extensibility; using System; @@ -160,14 +159,14 @@ public IRunSettingsTemplateReplacements Create( } private IEnumerable GetAdditionalModulePaths( - IEnumerable referencedProjects, + IEnumerable referencedProjects, string testDllFile, bool includeTestAssembly, bool isInclude ) { var additionalReferenced = referencedProjects.Select( - rp => MsCodeCoverageRegex.RegexModuleName(rp)); + rp => MsCodeCoverageRegex.RegexModuleName(rp.AssemblyName,rp.IsDll)); if(includeTestAssembly == isInclude) { additionalReferenced = additionalReferenced.Append(MsCodeCoverageRegex.RegexEscapePath(testDllFile)); diff --git a/SharedProject/Core/MsTestPlatform/TestingPlatform/DisableTestingPlatformServerCapabilityGlobalPropertiesProvider.cs b/SharedProject/Core/MsTestPlatform/TestingPlatform/DisableTestingPlatformServerCapabilityGlobalPropertiesProvider.cs index 32aedfdf..1c74e4a1 100644 --- a/SharedProject/Core/MsTestPlatform/TestingPlatform/DisableTestingPlatformServerCapabilityGlobalPropertiesProvider.cs +++ b/SharedProject/Core/MsTestPlatform/TestingPlatform/DisableTestingPlatformServerCapabilityGlobalPropertiesProvider.cs @@ -55,7 +55,7 @@ ICoverageProjectSettingsManager coverageProjectSettingsManager { // todo - ICoverageProjectSettingsManager.GetSettingsAsync parameter // to change to what it actually needs - coverageProject = new CoverageProject(appOptionsProvider, null, null, null, coverageProjectSettingsManager, false) + coverageProject = new CoverageProject(appOptionsProvider, null, coverageProjectSettingsManager, null) { Id = projectGuid, ProjectFile = unconfiguredProject.FullPath diff --git a/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs b/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs index 55a06918..785e2531 100644 --- a/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs +++ b/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs @@ -25,13 +25,13 @@ internal class OpenCoverExeArgumentsProvider : IOpenCoverExeArgumentsProvider private enum Delimiter { Semicolon, Space} private void AddFilter(ICoverageProject project, List opencoverSettings) { - var includedModules = project.IncludedReferencedProjects.ToList(); + var includedModules = project.IncludedReferencedProjects.Select(rp => rp.AssemblyName).ToList(); if (project.Settings.IncludeTestAssembly) { includedModules.Add(project.ProjectName); } var includeFilters = GetExcludesOrIncludes(project.Settings.Include, includedModules, true); - var excludeFilters = GetExcludesOrIncludes(project.Settings.Exclude, project.ExcludedReferencedProjects,false); + var excludeFilters = GetExcludesOrIncludes(project.Settings.Exclude, project.ExcludedReferencedProjects.Select(rp => rp.AssemblyName), false); AddIncludeAllIfExcludingWithoutIncludes(); var filters = includeFilters.Concat(excludeFilters).ToList(); SafeAddToSettingsDelimitedIfAny(opencoverSettings, "filter", filters, Delimiter.Space); diff --git a/SharedProject/Impl/TestContainerDiscovery/TestOperation.cs b/SharedProject/Impl/TestContainerDiscovery/TestOperation.cs index 3a16d71b..3b061be8 100644 --- a/SharedProject/Impl/TestContainerDiscovery/TestOperation.cs +++ b/SharedProject/Impl/TestContainerDiscovery/TestOperation.cs @@ -34,7 +34,7 @@ private async Task> GetCoverageProjectsAsync(TestConfigur List coverageProjects = new List(); foreach (var container in testContainers) { - var project = await coverageProjectFactory.CreateAsync(); + var project = coverageProjectFactory.Create(); coverageProjects.Add(project); project.ProjectName = container.ProjectName; project.TestDllFile = container.Source; diff --git a/SharedProject/Output/Directory.Build.props b/SharedProject/Output/Directory.Build.props deleted file mode 100644 index 2df4bf32..00000000 --- a/SharedProject/Output/Directory.Build.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/SharedProject/SharedProject.projitems b/SharedProject/SharedProject.projitems index 582ce406..99505aa7 100644 --- a/SharedProject/SharedProject.projitems +++ b/SharedProject/SharedProject.projitems @@ -79,18 +79,30 @@ + + + + + + + + + + - + + + @@ -443,10 +455,10 @@ - +