diff --git a/CycloneDX.Tests/CycloneDX.Tests.csproj b/CycloneDX.Tests/CycloneDX.Tests.csproj
index 4bbf0c19..c73bf001 100644
--- a/CycloneDX.Tests/CycloneDX.Tests.csproj
+++ b/CycloneDX.Tests/CycloneDX.Tests.csproj
@@ -51,6 +51,15 @@
Always
+
+ Always
+
+
+ Always
+
+
+ Always
+
Always
diff --git a/CycloneDX.Tests/FunctionalTests/Issue669-IsTestProjectEvaluation/Issue669.cs b/CycloneDX.Tests/FunctionalTests/Issue669-IsTestProjectEvaluation/Issue669.cs
new file mode 100644
index 00000000..85c6877f
--- /dev/null
+++ b/CycloneDX.Tests/FunctionalTests/Issue669-IsTestProjectEvaluation/Issue669.cs
@@ -0,0 +1,40 @@
+using System.IO;
+using System.IO.Abstractions;
+using System.Reflection;
+using CycloneDX.Interfaces;
+using CycloneDX.Models;
+using CycloneDX.Services;
+using Moq;
+using Xunit;
+
+namespace CycloneDX.Tests.FunctionalTests
+{
+ public sealed class Issue669
+ {
+ [Theory]
+ [InlineData("testproject.csproj.xml", true)]
+ [InlineData("nontestproject.csproj.xml", false)]
+ public void IsTestProjectTrueWithImportedPropsTargets(string projectFile, bool expectedIsTestProject)
+ {
+ var mockDotnetUtilsService = new Mock();
+ mockDotnetUtilsService
+ .Setup(s => s.Restore(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Returns(new DotnetUtilsResult());
+ mockDotnetUtilsService
+ .Setup(s => s.GetAssetsPath(It.IsAny()))
+ .Returns(new DotnetUtilsResult() { Result = "" });
+ var mockPackageFileService = new Mock();
+ var mockProjectAssetsFileService = new Mock();
+
+ var projectFileService = new ProjectFileService(
+ new FileSystem(),
+ mockDotnetUtilsService.Object,
+ mockPackageFileService.Object,
+ mockProjectAssetsFileService.Object);
+
+ string physicalProjectPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "FunctionalTests", "Issue669-IsTestProjectEvaluation", projectFile);
+
+ Assert.Equal(projectFileService.IsTestProject(physicalProjectPath), expectedIsTestProject);
+ }
+ }
+}
diff --git a/CycloneDX.Tests/FunctionalTests/Issue669-IsTestProjectEvaluation/makeMeTestProject.props b/CycloneDX.Tests/FunctionalTests/Issue669-IsTestProjectEvaluation/makeMeTestProject.props
new file mode 100644
index 00000000..d885d18d
--- /dev/null
+++ b/CycloneDX.Tests/FunctionalTests/Issue669-IsTestProjectEvaluation/makeMeTestProject.props
@@ -0,0 +1,6 @@
+
+
+
+ true
+
+
diff --git a/CycloneDX.Tests/FunctionalTests/Issue669-IsTestProjectEvaluation/nontestproject.csproj.xml b/CycloneDX.Tests/FunctionalTests/Issue669-IsTestProjectEvaluation/nontestproject.csproj.xml
new file mode 100644
index 00000000..8131dc5b
--- /dev/null
+++ b/CycloneDX.Tests/FunctionalTests/Issue669-IsTestProjectEvaluation/nontestproject.csproj.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+ false
+
+
+
diff --git a/CycloneDX.Tests/FunctionalTests/Issue669-IsTestProjectEvaluation/testproject.csproj.xml b/CycloneDX.Tests/FunctionalTests/Issue669-IsTestProjectEvaluation/testproject.csproj.xml
new file mode 100644
index 00000000..76fb3644
--- /dev/null
+++ b/CycloneDX.Tests/FunctionalTests/Issue669-IsTestProjectEvaluation/testproject.csproj.xml
@@ -0,0 +1,9 @@
+
+
+
+ false
+
+
+
+
+
diff --git a/CycloneDX/CycloneDX.csproj b/CycloneDX/CycloneDX.csproj
index 64240016..d64edaea 100644
--- a/CycloneDX/CycloneDX.csproj
+++ b/CycloneDX/CycloneDX.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -36,6 +36,8 @@
+
+
diff --git a/CycloneDX/Services/ProjectFileService.cs b/CycloneDX/Services/ProjectFileService.cs
index b6788ad2..0f801f89 100755
--- a/CycloneDX/Services/ProjectFileService.cs
+++ b/CycloneDX/Services/ProjectFileService.cs
@@ -17,15 +17,16 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Xml;
using System.IO;
using System.IO.Abstractions;
+using System.Linq;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
+using System.Xml;
using CycloneDX.Interfaces;
using CycloneDX.Models;
-using System.Text.RegularExpressions;
-using System.Reflection;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Locator;
namespace CycloneDX.Services
{
@@ -38,6 +39,12 @@ public DotnetRestoreException(string message, Exception innerException) : base(m
public class ProjectFileService : IProjectFileService
{
+ static ProjectFileService()
+ {
+ // Register the MSBuild instance (do this once per process)
+ MSBuildLocator.RegisterDefaults();
+ }
+
private XmlReaderSettings _xmlReaderSettings = new XmlReaderSettings
{
Async = true
@@ -67,18 +74,17 @@ public bool IsTestProject(string projectFilePath)
return false;
}
- XmlDocument xmldoc = new XmlDocument();
using var fileStream = _fileSystem.FileStream.New(projectFilePath, FileMode.Open, FileAccess.Read);
- xmldoc.Load(fileStream);
+ using var xmlReader = XmlReader.Create(fileStream);
- XmlElement testSdkReference = xmldoc.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.NET.Test.Sdk']") as XmlElement;
- if (testSdkReference != null)
- {
- return true;
- }
- // if this is meant for old csproj file format, then it's probably not working because there is no namespace given
- XmlElement testProjectPropertyGroup = xmldoc.SelectSingleNode("/Project/PropertyGroup[IsTestProject='true']") as XmlElement;
- return testProjectPropertyGroup != null;
+ // load the project (all imports and SDKs are processed)
+ var project = new Project(xmlReader);
+
+ // get property value after evaluation
+ string propertyValue = project.GetPropertyValue("IsTestProject");
+
+ //return is expected parsed boolean value, default false
+ return bool.TryParse(propertyValue, out bool isTestProject) && isTestProject;
}
private (string name, string version) GetAssemblyNameAndVersion(string projectFilePath)
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 6acade1f..7327e6cf 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -5,7 +5,7 @@
-
+