diff --git a/src/Cli/dotnet/commands/dotnet-test/MSBuildUtility.cs b/src/Cli/dotnet/commands/dotnet-test/MSBuildUtility.cs index 6420e35bfe1c..cdcc7d9b192d 100644 --- a/src/Cli/dotnet/commands/dotnet-test/MSBuildUtility.cs +++ b/src/Cli/dotnet/commands/dotnet-test/MSBuildUtility.cs @@ -36,7 +36,7 @@ public static (IEnumerable Projects, bool IsBuiltOrRestored) GetProj { bool isBuiltOrRestored = BuildOrRestoreProjectOrSolution(projectFilePath, buildOptions); - IEnumerable projects = SolutionAndProjectUtility.GetProjectProperties(projectFilePath, GetGlobalProperties(buildOptions.BuildProperties), new ProjectCollection()); + IEnumerable projects = SolutionAndProjectUtility.GetProjectProperties(projectFilePath, GetGlobalProperties(buildOptions), new ProjectCollection()); isBuiltOrRestored |= projects.Any(); @@ -45,15 +45,12 @@ public static (IEnumerable Projects, bool IsBuiltOrRestored) GetProj public static BuildOptions GetBuildOptions(ParseResult parseResult, int degreeOfParallelism) { - IEnumerable propertyTokens = GetPropertyTokens(parseResult.UnmatchedTokens); IEnumerable binaryLoggerTokens = GetBinaryLoggerTokens(parseResult.UnmatchedTokens); var msbuildArgs = parseResult.OptionValuesToBeForwarded(TestCommandParser.GetCommand()) - .Concat(propertyTokens) .Concat(binaryLoggerTokens); List unmatchedTokens = [.. parseResult.UnmatchedTokens]; - unmatchedTokens.RemoveAll(arg => propertyTokens.Contains(arg)); unmatchedTokens.RemoveAll(arg => binaryLoggerTokens.Contains(arg)); PathOptions pathOptions = new(parseResult.GetValue( @@ -73,6 +70,7 @@ public static BuildOptions GetBuildOptions(ParseResult parseResult, int degreeOf parseResult.GetValue(TestingPlatformOptions.NoBuildOption), parseResult.HasOption(CommonOptions.VerbosityOption) ? parseResult.GetValue(CommonOptions.VerbosityOption) : null, degreeOfParallelism, + parseResult.GetValue(CommonOptions.PropertiesOption), unmatchedTokens, msbuildArgs); } @@ -92,16 +90,7 @@ private static string ResolveRuntimeIdentifier(ParseResult parseResult) return CommonOptions.ResolveRidShorthandOptionsToRuntimeIdentifier(parseResult.GetValue(CommonOptions.OperatingSystemOption), parseResult.GetValue(CommonOptions.ArchitectureOption)); } - public static IEnumerable GetPropertyTokens(IEnumerable unmatchedTokens) - { - return unmatchedTokens.Where(token => - token.StartsWith("--property:", StringComparison.OrdinalIgnoreCase) || - token.StartsWith("/property:", StringComparison.OrdinalIgnoreCase) || - token.StartsWith("-p:", StringComparison.OrdinalIgnoreCase) || - token.StartsWith("/p:", StringComparison.OrdinalIgnoreCase)); - } - - public static IEnumerable GetBinaryLoggerTokens(IEnumerable args) + private static IEnumerable GetBinaryLoggerTokens(IEnumerable args) { return args.Where(arg => arg.StartsWith("/bl:", StringComparison.OrdinalIgnoreCase) || arg.Equals("/bl", StringComparison.OrdinalIgnoreCase) || @@ -135,7 +124,7 @@ private static ConcurrentBag GetProjectsProperties(ProjectCollection new ParallelOptions { MaxDegreeOfParallelism = buildOptions.DegreeOfParallelism }, (project) => { - IEnumerable projectsMetadata = SolutionAndProjectUtility.GetProjectProperties(project, GetGlobalProperties(buildOptions.BuildProperties), projectCollection); + IEnumerable projectsMetadata = SolutionAndProjectUtility.GetProjectProperties(project, GetGlobalProperties(buildOptions), projectCollection); foreach (var projectMetadata in projectsMetadata) { allProjects.Add(projectMetadata); @@ -145,9 +134,25 @@ private static ConcurrentBag GetProjectsProperties(ProjectCollection return allProjects; } - private static Dictionary GetGlobalProperties(BuildProperties buildProperties) + private static Dictionary GetGlobalProperties(BuildOptions buildOptions) { var globalProperties = new Dictionary(); + var buildProperties = buildOptions.BuildProperties; + + foreach (var property in buildOptions.UserSpecifiedProperties) + { + foreach (var (key, value) in MSBuildPropertyParser.ParseProperties(property)) + { + if (globalProperties.TryGetValue(key, out var existingValues)) + { + globalProperties[key] = $"{existingValues};{value}"; + } + else + { + globalProperties[key] = value; + } + } + } if (!string.IsNullOrEmpty(buildProperties.Configuration)) { diff --git a/src/Cli/dotnet/commands/dotnet-test/Options.cs b/src/Cli/dotnet/commands/dotnet-test/Options.cs index 92df3a52dbf9..11d892ccf1d9 100644 --- a/src/Cli/dotnet/commands/dotnet-test/Options.cs +++ b/src/Cli/dotnet/commands/dotnet-test/Options.cs @@ -9,5 +9,5 @@ internal record PathOptions(string ProjectPath, string SolutionPath, string Dire internal record BuildProperties(string Configuration, string RuntimeIdentifier, string TargetFramework); - internal record BuildOptions(PathOptions PathOptions, BuildProperties BuildProperties, bool HasNoRestore, bool HasNoBuild, VerbosityOptions? Verbosity, int DegreeOfParallelism, List UnmatchedTokens, IEnumerable MSBuildArgs); + internal record BuildOptions(PathOptions PathOptions, BuildProperties BuildProperties, bool HasNoRestore, bool HasNoBuild, VerbosityOptions? Verbosity, int DegreeOfParallelism, string[] UserSpecifiedProperties, List UnmatchedTokens, IEnumerable MSBuildArgs); } diff --git a/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs b/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs index d8e249accceb..9d36f85678d5 100644 --- a/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs +++ b/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs @@ -234,6 +234,7 @@ private static CliCommand GetTestingPlatformCliCommand() command.Options.Add(TestingPlatformOptions.TestModulesRootDirectoryOption); command.Options.Add(TestingPlatformOptions.MaxParallelTestModulesOption); command.Options.Add(CommonOptions.ArchitectureOption); + command.Options.Add(CommonOptions.PropertiesOption); command.Options.Add(TestingPlatformOptions.ConfigurationOption); command.Options.Add(TestingPlatformOptions.FrameworkOption); command.Options.Add(CommonOptions.OperatingSystemOption); diff --git a/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/OtherTestProject/OtherTestProject.csproj b/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/OtherTestProject/OtherTestProject.csproj new file mode 100644 index 000000000000..0a7bf1ded69e --- /dev/null +++ b/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/OtherTestProject/OtherTestProject.csproj @@ -0,0 +1,19 @@ + + + + + $(CurrentTargetFramework) + Exe + + enable + enable + + false + false + true + + + + + + diff --git a/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/OtherTestProject/Program.cs b/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/OtherTestProject/Program.cs new file mode 100644 index 000000000000..480d1dc9e9db --- /dev/null +++ b/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/OtherTestProject/Program.cs @@ -0,0 +1,46 @@ +using Microsoft.Testing.Platform.Builder; +using Microsoft.Testing.Platform.Capabilities.TestFramework; +using Microsoft.Testing.Platform.Extensions.Messages; +using Microsoft.Testing.Platform.Extensions.TestFramework; + +var testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); + +testApplicationBuilder.RegisterTestFramework(_ => new TestFrameworkCapabilities(), (_, __) => new DummyTestAdapter()); + +using var testApplication = await testApplicationBuilder.BuildAsync(); +return await testApplication.RunAsync(); + +public class DummyTestAdapter : ITestFramework, IDataProducer +{ + public string Uid => nameof(DummyTestAdapter); + + public string Version => "2.0.0"; + + public string DisplayName => nameof(DummyTestAdapter); + + public string Description => nameof(DummyTestAdapter); + + public Task IsEnabledAsync() => Task.FromResult(true); + + public Type[] DataTypesProduced => new[] { + typeof(TestNodeUpdateMessage) + }; + + public Task CreateTestSessionAsync(CreateTestSessionContext context) + => Task.FromResult(new CreateTestSessionResult() { IsSuccess = true }); + + public Task CloseTestSessionAsync(CloseTestSessionContext context) + => Task.FromResult(new CloseTestSessionResult() { IsSuccess = true }); + + public async Task ExecuteRequestAsync(ExecuteRequestContext context) + { + await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode() + { + Uid = "Test1", + DisplayName = "Test1", + Properties = new PropertyBag(new PassedTestNodeStateProperty("OK")), + })); + + context.Complete(); + } +} \ No newline at end of file diff --git a/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/TestProject/Program.cs b/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/TestProject/Program.cs new file mode 100644 index 000000000000..206c4eb6698d --- /dev/null +++ b/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/TestProject/Program.cs @@ -0,0 +1,43 @@ +using Microsoft.Testing.Platform.Builder; +using Microsoft.Testing.Platform.Capabilities.TestFramework; +using Microsoft.Testing.Platform.Extensions.Messages; +using Microsoft.Testing.Platform.Extensions.TestFramework; + +ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); +testApplicationBuilder.RegisterTestFramework(_ => new TestFrameworkCapabilities(), (_, __) => new DummyTestAdapter()); + +ITestApplication testApplication = await testApplicationBuilder.BuildAsync(); +return await testApplication.RunAsync(); + +public class DummyTestAdapter : ITestFramework, IDataProducer +{ + public string Uid => nameof(DummyTestAdapter); + + public string Version => "2.0.0"; + + public string DisplayName => nameof(DummyTestAdapter); + + public string Description => nameof(DummyTestAdapter); + + public Task IsEnabledAsync() => Task.FromResult(true); + + public Type[] DataTypesProduced => new[] { typeof(TestNodeUpdateMessage) }; + + public Task CreateTestSessionAsync(CreateTestSessionContext context) + => Task.FromResult(new CreateTestSessionResult() { IsSuccess = true }); + + public Task CloseTestSessionAsync(CloseTestSessionContext context) + => Task.FromResult(new CloseTestSessionResult() { IsSuccess = true }); + + public async Task ExecuteRequestAsync(ExecuteRequestContext context) + { + await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode() + { + Uid = "Test0", + DisplayName = "Test0", + Properties = new PropertyBag(new PassedTestNodeStateProperty("OK")), + })); + + context.Complete(); + } +} diff --git a/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/TestProject/TestProject.csproj b/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/TestProject/TestProject.csproj new file mode 100644 index 000000000000..50b5a3c912c4 --- /dev/null +++ b/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/TestProject/TestProject.csproj @@ -0,0 +1,19 @@ + + + + + Exe + $(CurrentTargetFramework) + enable + enable + false + latest + false + true + + + + + + + \ No newline at end of file diff --git a/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/TestProjectWithConditionOnGlobalProperty.sln b/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/TestProjectWithConditionOnGlobalProperty.sln new file mode 100644 index 000000000000..c1e19dacdca6 --- /dev/null +++ b/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/TestProjectWithConditionOnGlobalProperty.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35505.181 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject", "TestProject\TestProject.csproj", "{D2E321F2-3513-99DE-C37E-6D48D15F404D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OtherTestProject", "OtherTestProject/OtherTestProject.csproj", "{6171FC1F-E2F2-4F66-A8DE-EC4765081661}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D2E321F2-3513-99DE-C37E-6D48D15F404D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D2E321F2-3513-99DE-C37E-6D48D15F404D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D2E321F2-3513-99DE-C37E-6D48D15F404D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D2E321F2-3513-99DE-C37E-6D48D15F404D}.Release|Any CPU.Build.0 = Release|Any CPU + {6171FC1F-E2F2-4F66-A8DE-EC4765081661}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6171FC1F-E2F2-4F66-A8DE-EC4765081661}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6171FC1F-E2F2-4F66-A8DE-EC4765081661}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6171FC1F-E2F2-4F66-A8DE-EC4765081661}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3D7914D1-7D03-4FFB-8D0F-7FFA6B6BA6A0} + EndGlobalSection +EndGlobal diff --git a/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/dotnet.config b/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/dotnet.config new file mode 100644 index 000000000000..fcc92b88398e --- /dev/null +++ b/test/TestAssets/TestProjects/TestProjectWithConditionOnGlobalProperty/dotnet.config @@ -0,0 +1,2 @@ +[dotnet.test:runner] +name= "Microsoft.Testing.Platform" \ No newline at end of file diff --git a/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTests.cs b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTests.cs index d85a163766ba..12ef5ff4515b 100644 --- a/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTests.cs +++ b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTests.cs @@ -256,5 +256,34 @@ public void RunOnProjectWithClassLibrary_ShouldReturnExitCodeSuccess(string conf result.ExitCode.Should().Be(ExitCode.Success); } + + [InlineData(TestingConstants.Debug)] + [InlineData(TestingConstants.Release)] + [Theory] + public void RunningWithGlobalPropertyShouldProperlyPropagate(string configuration) + { + TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithConditionOnGlobalProperty", Guid.NewGuid().ToString()) + .WithSource(); + testInstance.WithTargetFramework($"{DotnetVersionHelper.GetPreviousDotnetVersion()}", "TestProject"); + + CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) + .WithWorkingDirectory(testInstance.Path) + .WithEnableTestingPlatform() + .Execute( + TestingPlatformOptions.ConfigurationOption.Name, configuration, + CommonOptions.PropertiesOption.Name, "PROPERTY_TO_ENABLE_MTP=1"); + + if (!TestContext.IsLocalized()) + { + result.StdOut + .Should().Contain("Test run summary: Passed!") + .And.Contain("total: 2") + .And.Contain("succeeded: 2") + .And.Contain("failed: 0") + .And.Contain("skipped: 0"); + } + + result.ExitCode.Should().Be(ExitCode.Success); + } } }