diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/MSTestAdapterSettings.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/MSTestAdapterSettings.cs index a9c9bf0192..4e2a109300 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/MSTestAdapterSettings.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/MSTestAdapterSettings.cs @@ -189,9 +189,11 @@ public static bool IsAppDomainCreationDisabled(string? settingsXml) bool disableAppDomain = true; // HACK: When running VSTest, and VSTest didn't create TestHostAppDomain (default behavior), we must be enabling appdomain in MSTest. // Otherwise, we will not merge app.config properly, nor we will have correct BaseDirectory of current domain. + // This detects if we run in testhost.*.exe or in vstest.console.exe.This covers all: running with vstest.console.exe because there we can run in both modes, running with dotnet test or VS, because there we can run only in testhost(in isolation). #if NETFRAMEWORK if (AppDomain.CurrentDomain.Id == 1 && - AppDomain.CurrentDomain.FriendlyName.StartsWith("testhost.", StringComparison.Ordinal) && + (AppDomain.CurrentDomain.FriendlyName.StartsWith("testhost.", StringComparison.Ordinal) || + AppDomain.CurrentDomain.FriendlyName.StartsWith("vstest.console.", StringComparison.Ordinal)) && AppDomain.CurrentDomain.FriendlyName.EndsWith(".exe", StringComparison.Ordinal)) { disableAppDomain = false; diff --git a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/AppDomainTests.cs b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/AppDomainTests.cs index 14ed18e665..cac5b3ed9d 100644 --- a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/AppDomainTests.cs +++ b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/AppDomainTests.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.MSTestV2.CLIAutomation; using Microsoft.Testing.Platform.Acceptance.IntegrationTests; using Microsoft.Testing.Platform.Acceptance.IntegrationTests.Helpers; +using Microsoft.Testing.TestInfrastructure; namespace MSTest.Acceptance.IntegrationTests; @@ -90,7 +92,7 @@ public async Task RunTests_With_VSTest(bool? disableAppDomain) string disableAppDomainCommand = disableAppDomain switch { true => " -- RunConfiguration.DisableAppDomain=true", - false => " -- RunConfiguration.EnableAppDomain=false", + false => " -- RunConfiguration.DisableAppDomain=false", null => string.Empty, }; @@ -115,7 +117,7 @@ public async Task DiscoverTests_With_VSTest(bool? disableAppDomain) string disableAppDomainCommand = disableAppDomain switch { true => " -- RunConfiguration.DisableAppDomain=true", - false => " -- RunConfiguration.EnableAppDomain=false", + false => " -- RunConfiguration.DisableAppDomain=false", null => string.Empty, }; @@ -123,5 +125,89 @@ public async Task DiscoverTests_With_VSTest(bool? disableAppDomain) Assert.AreEqual(0, compilationResult.ExitCode); } + [TestMethod] + [DataRow(true)] + [DataRow(false)] + [DataRow(null)] + public async Task RunTests_With_VSTestConsole_Directly(bool? disableAppDomain) + { + using TestAsset testAsset = await TestAsset.GenerateAssetAsync( + AssetName, + SingleTestSourceCode + .PatchCodeWithReplace("$MSTestVersion$", MSTestVersion) + .PatchCodeWithReplace("$TargetFramework$", TargetFrameworks.NetFramework[0])); + + // Build the test project + DotnetMuxerResult buildResult = await DotnetCli.RunAsync( + $"build {testAsset.TargetAssetPath} -c Debug", + AcceptanceFixture.NuGetGlobalPackagesFolder.Path, + workingDirectory: testAsset.TargetAssetPath, + cancellationToken: TestContext.CancellationToken); + Assert.AreEqual(0, buildResult.ExitCode, $"Build failed: {buildResult.StandardOutput}"); + + // Get the DLL path + string dllPath = GetTestDllPath(testAsset.TargetAssetPath, TargetFrameworks.NetFramework[0]); + Assert.IsTrue(File.Exists(dllPath), $"Test DLL not found at {dllPath}"); + + // Run tests using vstest.console.exe directly + string vstestConsolePath = VSTestConsoleLocator.GetConsoleRunnerPath(); + string disableAppDomainCommand = disableAppDomain switch + { + true => " -- RunConfiguration.DisableAppDomain=true", + false => " -- RunConfiguration.DisableAppDomain=false", + null => string.Empty, + }; + + string arguments = $"\"{dllPath}\"{disableAppDomainCommand}"; + + using var commandLine = new CommandLine(); + await commandLine.RunAsync( + $"\"{vstestConsolePath}\" {arguments}", + cancellationToken: TestContext.CancellationToken); + } + + [TestMethod] + [DataRow(true)] + [DataRow(false)] + [DataRow(null)] + public async Task DiscoverTests_With_VSTestConsole_Directly(bool? disableAppDomain) + { + using TestAsset testAsset = await TestAsset.GenerateAssetAsync( + AssetName, + SingleTestSourceCode + .PatchCodeWithReplace("$MSTestVersion$", MSTestVersion) + .PatchCodeWithReplace("$TargetFramework$", TargetFrameworks.NetFramework[0])); + + // Build the test project + DotnetMuxerResult buildResult = await DotnetCli.RunAsync( + $"build {testAsset.TargetAssetPath} -c Debug", + AcceptanceFixture.NuGetGlobalPackagesFolder.Path, + workingDirectory: testAsset.TargetAssetPath, + cancellationToken: TestContext.CancellationToken); + Assert.AreEqual(0, buildResult.ExitCode, $"Build failed: {buildResult.StandardOutput}"); + + // Get the DLL path + string dllPath = GetTestDllPath(testAsset.TargetAssetPath, TargetFrameworks.NetFramework[0]); + Assert.IsTrue(File.Exists(dllPath), $"Test DLL not found at {dllPath}"); + + // Run discovery using vstest.console.exe directly + string vstestConsolePath = VSTestConsoleLocator.GetConsoleRunnerPath(); + string disableAppDomainCommand = disableAppDomain switch + { + true => " -- RunConfiguration.DisableAppDomain=true", + false => " -- RunConfiguration.DisableAppDomain=false", + null => string.Empty, + }; + string arguments = $"\"{dllPath}\" /ListTests{disableAppDomainCommand}"; + + using var commandLine = new CommandLine(); + await commandLine.RunAsync( + $"\"{vstestConsolePath}\" {arguments}", + cancellationToken: TestContext.CancellationToken); + } + + private static string GetTestDllPath(string assetPath, string targetFramework) => + Path.Combine(assetPath, "bin", "Debug", targetFramework, $"{AssetName}.dll"); + public TestContext TestContext { get; set; } } diff --git a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj index 7fb66b0acc..c086fd8b9b 100644 --- a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj +++ b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj @@ -13,13 +13,15 @@ + + - + diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests.csproj b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests.csproj index 8da7dd00bb..b6ba49f6aa 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests.csproj +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests.csproj @@ -13,6 +13,10 @@ + + + + diff --git a/test/Performance/MSTest.Performance.Runner/MSTest.Performance.Runner.csproj b/test/Performance/MSTest.Performance.Runner/MSTest.Performance.Runner.csproj index eb2306c412..686b71999d 100644 --- a/test/Performance/MSTest.Performance.Runner/MSTest.Performance.Runner.csproj +++ b/test/Performance/MSTest.Performance.Runner/MSTest.Performance.Runner.csproj @@ -15,4 +15,8 @@ + + + + diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.csproj b/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.csproj index 069388fa30..4b32407f1f 100644 --- a/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.csproj +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.csproj @@ -44,4 +44,8 @@ + + + + diff --git a/test/UnitTests/MSTest.SourceGeneration.UnitTests/MSTest.SourceGeneration.UnitTests.csproj b/test/UnitTests/MSTest.SourceGeneration.UnitTests/MSTest.SourceGeneration.UnitTests.csproj index fcdc1a7ebe..014e756668 100644 --- a/test/UnitTests/MSTest.SourceGeneration.UnitTests/MSTest.SourceGeneration.UnitTests.csproj +++ b/test/UnitTests/MSTest.SourceGeneration.UnitTests/MSTest.SourceGeneration.UnitTests.csproj @@ -14,6 +14,10 @@ + + + + PreserveNewest diff --git a/test/Utilities/Automation.CLI/Automation.CLI.csproj b/test/Utilities/Automation.CLI/Automation.CLI.csproj index 6b62895628..cbb4fe349f 100644 --- a/test/Utilities/Automation.CLI/Automation.CLI.csproj +++ b/test/Utilities/Automation.CLI/Automation.CLI.csproj @@ -14,4 +14,8 @@ + + + + diff --git a/test/Utilities/Automation.CLI/CLITestBase.common.cs b/test/Utilities/Automation.CLI/CLITestBase.common.cs index 8a59fa515c..9b6fccddb4 100644 --- a/test/Utilities/Automation.CLI/CLITestBase.common.cs +++ b/test/Utilities/Automation.CLI/CLITestBase.common.cs @@ -30,14 +30,6 @@ protected static XmlDocument ReadCPMFile() return versionPropsXml; } - protected static string GetTestPlatformVersion() - { - XmlDocument cpmXml = ReadCPMFile(); - XmlNode testSdkVersion = cpmXml.DocumentElement.SelectSingleNode("PropertyGroup/MicrosoftNETTestSdkVersion"); - - return testSdkVersion.InnerText; - } - protected static string GetArtifactsBinFolderPath() { string assemblyLocation = Assembly.GetExecutingAssembly().Location; diff --git a/test/Utilities/Automation.CLI/CLITestBase.e2e.cs b/test/Utilities/Automation.CLI/CLITestBase.e2e.cs index 76d01fb92b..8e3725549c 100644 --- a/test/Utilities/Automation.CLI/CLITestBase.e2e.cs +++ b/test/Utilities/Automation.CLI/CLITestBase.e2e.cs @@ -15,7 +15,7 @@ public abstract partial class CLITestBase protected CLITestBase() { s_vsTestConsoleWrapper = new( - GetConsoleRunnerPath(), + VSTestConsoleLocator.GetConsoleRunnerPath(), new() { EnvironmentVariables = new() @@ -65,52 +65,6 @@ public void InvokeVsTestForExecution(string[] sources, string runSettings = "", } } - public static string GetNugetPackageFolder() - { - string nugetPackagesFolderPath = Environment.GetEnvironmentVariable("NUGET_PACKAGES"); - if (!string.IsNullOrEmpty(nugetPackagesFolderPath)) - { - Assert.IsTrue(Directory.Exists(nugetPackagesFolderPath), $"Found environment variable 'NUGET_PACKAGES' and NuGet package folder '{nugetPackagesFolderPath}' should exist"); - - return nugetPackagesFolderPath; - } - - string userProfile = Environment.GetEnvironmentVariable("USERPROFILE"); - nugetPackagesFolderPath = Path.Combine(userProfile, ".nuget", "packages"); - Assert.IsTrue(Directory.Exists(nugetPackagesFolderPath), $"NuGet package folder '{nugetPackagesFolderPath}' should exist"); - - return nugetPackagesFolderPath; - } - - /// - /// Gets the path to vstest.console.exe. - /// - /// Full path to vstest.console.exe. - public static string GetConsoleRunnerPath() - { - string testPlatformNuGetPackageFolder = Path.Combine( - GetNugetPackageFolder(), - TestPlatformCLIPackageName, - GetTestPlatformVersion()); - if (!Directory.Exists(testPlatformNuGetPackageFolder)) - { - throw new DirectoryNotFoundException($"Test platform NuGet package folder '{testPlatformNuGetPackageFolder}' does not exist"); - } - - string vstestConsolePath = Path.Combine( - testPlatformNuGetPackageFolder, - "tools", - "net462", - "Common7", - "IDE", - "Extensions", - "TestPlatform", - "vstest.console.exe"); - return !File.Exists(vstestConsolePath) - ? throw new InvalidOperationException($"Could not find vstest.console.exe in {vstestConsolePath}") - : vstestConsolePath; - } - /// /// Validate if the discovered tests list contains provided tests. /// diff --git a/test/Utilities/Automation.CLI/VSTestConsoleLocator.cs b/test/Utilities/Automation.CLI/VSTestConsoleLocator.cs new file mode 100644 index 0000000000..3966fa7f8e --- /dev/null +++ b/test/Utilities/Automation.CLI/VSTestConsoleLocator.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Testing.TestInfrastructure; + +using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert; + +namespace Microsoft.MSTestV2.CLIAutomation; + +/// +/// Helper class to locate vstest.console.exe. +/// +public static class VSTestConsoleLocator +{ + private const string TestPlatformPackageName = "Microsoft.TestPlatform"; + + /// + /// Gets the path to vstest.console.exe. + /// + /// Full path to vstest.console.exe. + public static string GetConsoleRunnerPath() + { + string testPlatformNuGetPackageFolder = Path.Combine( + GetNugetPackageFolder(), + TestPlatformPackageName, + GetTestPlatformVersion()); + if (!Directory.Exists(testPlatformNuGetPackageFolder)) + { + throw new DirectoryNotFoundException($"Test platform NuGet package folder '{testPlatformNuGetPackageFolder}' does not exist"); + } + + string vstestConsolePath = Path.Combine( + testPlatformNuGetPackageFolder, + "tools", + "net462", + "Common7", + "IDE", + "Extensions", + "TestPlatform", + "vstest.console.exe"); + return !File.Exists(vstestConsolePath) + ? throw GetExceptionForVSTestConsoleNotFound(vstestConsolePath) + : vstestConsolePath; + + InvalidOperationException GetExceptionForVSTestConsoleNotFound(string expectedPath) + { + string[] files = Directory.GetFiles(testPlatformNuGetPackageFolder, "vstest.console.exe", SearchOption.AllDirectories); + return files.Length == 0 + ? new InvalidOperationException($"Could not find vstest.console.exe in {vstestConsolePath}") + : new InvalidOperationException($"Could not find vstest.console.exe in {vstestConsolePath}. Found in:{Environment.NewLine}{string.Join(Environment.NewLine, files)}"); + } + } + + private static string GetNugetPackageFolder() + { + string? nugetPackagesFolderPath = Environment.GetEnvironmentVariable("NUGET_PACKAGES"); + if (!string.IsNullOrEmpty(nugetPackagesFolderPath)) + { + Assert.IsTrue(Directory.Exists(nugetPackagesFolderPath), $"Found environment variable 'NUGET_PACKAGES' and NuGet package folder '{nugetPackagesFolderPath}' should exist"); + + return nugetPackagesFolderPath; + } + + string? userProfile = Environment.GetEnvironmentVariable("USERPROFILE"); + if (string.IsNullOrEmpty(userProfile)) + { + throw new InvalidOperationException("USERPROFILE environment variable is not set"); + } + + nugetPackagesFolderPath = Path.Combine(userProfile, ".nuget", "packages"); + Assert.IsTrue(Directory.Exists(nugetPackagesFolderPath), $"NuGet package folder '{nugetPackagesFolderPath}' should exist"); + + return nugetPackagesFolderPath; + } + + private static string GetTestPlatformVersion() + { + string cpmFilePath = Path.Combine(RootFinder.Find(), "Directory.Packages.props"); + using FileStream fileStream = File.OpenRead(cpmFilePath); +#pragma warning disable CA3075 // Insecure DTD processing in XML + using var xmlTextReader = new XmlTextReader(fileStream) { Namespaces = false }; +#pragma warning restore CA3075 // Insecure DTD processing in XML + var cpmXml = new XmlDocument(); + cpmXml.Load(xmlTextReader); + + return cpmXml.DocumentElement?.SelectSingleNode("PropertyGroup/MicrosoftNETTestSdkVersion")?.InnerText + ?? throw new InvalidOperationException($"Could not find MicrosoftNETTestSdkVersion in {cpmFilePath}"); + } +} diff --git a/test/Utilities/Microsoft.Testing.TestInfrastructure/Microsoft.Testing.TestInfrastructure.csproj b/test/Utilities/Microsoft.Testing.TestInfrastructure/Microsoft.Testing.TestInfrastructure.csproj index 6a8963eaa5..97bb9f850f 100644 --- a/test/Utilities/Microsoft.Testing.TestInfrastructure/Microsoft.Testing.TestInfrastructure.csproj +++ b/test/Utilities/Microsoft.Testing.TestInfrastructure/Microsoft.Testing.TestInfrastructure.csproj @@ -4,7 +4,6 @@ $(SupportedNetFrameworks);netstandard2.0 enable $(DefineConstants);SKIP_INTERMEDIATE_TARGET_FRAMEWORKS - $(DefineConstants);ROOT_FINDER_PUBLIC diff --git a/test/Utilities/Microsoft.Testing.TestInfrastructure/RootFinder.cs b/test/Utilities/Microsoft.Testing.TestInfrastructure/RootFinder.cs index 404f930f6a..53d2ac7be5 100644 --- a/test/Utilities/Microsoft.Testing.TestInfrastructure/RootFinder.cs +++ b/test/Utilities/Microsoft.Testing.TestInfrastructure/RootFinder.cs @@ -9,12 +9,7 @@ namespace Microsoft.Testing.TestInfrastructure; /// The class is used to find the root directory of a Git repository by /// searching for a ".git" directory or file starting from the application's base directory and moving up the directory /// hierarchy. This is useful for applications that need to determine the root of a project or repository. -#if ROOT_FINDER_PUBLIC -public -#else -internal -#endif - static class RootFinder +internal static class RootFinder { private static string? s_root;