diff --git a/eng/Versions.props b/eng/Versions.props
index 8e04bff1c351..bac5b3e88e2a 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -28,6 +28,7 @@
true
6.0.1
true
+
1.6.0-preview.25072.4
diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/PathUtility.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/PathUtility.cs
index 60069f1aa672..f4f74e5aae2f 100644
--- a/src/Cli/Microsoft.DotNet.Cli.Utils/PathUtility.cs
+++ b/src/Cli/Microsoft.DotNet.Cli.Utils/PathUtility.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Text.RegularExpressions;
using Microsoft.DotNet.Cli.Utils;
using NuGet.Configuration;
@@ -388,5 +389,15 @@ public static void EnsureAllPathsExist(
public static bool IsDirectory(this string path) =>
File.GetAttributes(path).HasFlag(FileAttributes.Directory);
+
+ public static string FixFilePath(string path)
+ {
+ return string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == '\\' ? path : path.Replace('\\', '/');
+ }
+
+ public static string GetDirectorySeparatorChar()
+ {
+ return Regex.Escape(Path.DirectorySeparatorChar.ToString());
+ }
}
}
diff --git a/src/Cli/dotnet/commands/dotnet-test/CliConstants.cs b/src/Cli/dotnet/commands/dotnet-test/CliConstants.cs
index 8f578b5f31dc..b0d32dd371e6 100644
--- a/src/Cli/dotnet/commands/dotnet-test/CliConstants.cs
+++ b/src/Cli/dotnet/commands/dotnet-test/CliConstants.cs
@@ -22,7 +22,10 @@ internal static class CliConstants
public const string TestSectionKey = "test";
- public const string RestoreCommand = "restore";
+ public const string RestoreCommand = "Restore";
+ public const string BuildCommand = "Build";
+ public const string Configuration = "Configuration";
+ public const string RuntimeIdentifier = "RuntimeIdentifier";
public static readonly string[] ProjectExtensions = { ".proj", ".csproj", ".vbproj", ".fsproj" };
public static readonly string[] SolutionExtensions = { ".sln", ".slnx" };
diff --git a/src/Cli/dotnet/commands/dotnet-test/MSBuildHandler.cs b/src/Cli/dotnet/commands/dotnet-test/MSBuildHandler.cs
index b1adead6ad14..6fc6fbaef65d 100644
--- a/src/Cli/dotnet/commands/dotnet-test/MSBuildHandler.cs
+++ b/src/Cli/dotnet/commands/dotnet-test/MSBuildHandler.cs
@@ -6,7 +6,10 @@
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
+using Microsoft.DotNet.Tools.Common;
using Microsoft.DotNet.Tools.Test;
+using Microsoft.Testing.Platform.OutputDevice.Terminal;
+
namespace Microsoft.DotNet.Cli
{
@@ -15,17 +18,17 @@ internal sealed class MSBuildHandler : IDisposable
private readonly List _args;
private readonly TestApplicationActionQueue _actionQueue;
private readonly int _degreeOfParallelism;
+ private TerminalTestReporter _output;
private readonly ConcurrentBag _testApplications = new();
private bool _areTestingPlatformApplications = true;
- private static readonly Lock buildLock = new();
-
- public MSBuildHandler(List args, TestApplicationActionQueue actionQueue, int degreeOfParallelism)
+ public MSBuildHandler(List args, TestApplicationActionQueue actionQueue, int degreeOfParallelism, TerminalTestReporter output)
{
_args = args;
_actionQueue = actionQueue;
_degreeOfParallelism = degreeOfParallelism;
+ _output = output;
}
public async Task RunMSBuild(BuildPathsOptions buildPathOptions)
@@ -36,23 +39,27 @@ public async Task RunMSBuild(BuildPathsOptions buildPathOptions)
}
int msbuildExitCode;
+ string path;
if (!string.IsNullOrEmpty(buildPathOptions.ProjectPath))
{
- msbuildExitCode = await RunBuild(buildPathOptions.ProjectPath, isSolution: false);
+ path = PathUtility.GetFullPath(buildPathOptions.ProjectPath);
+ msbuildExitCode = await RunBuild(path, isSolution: false, buildPathOptions);
}
else if (!string.IsNullOrEmpty(buildPathOptions.SolutionPath))
{
- msbuildExitCode = await RunBuild(buildPathOptions.SolutionPath, isSolution: true);
+ path = PathUtility.GetFullPath(buildPathOptions.SolutionPath);
+ msbuildExitCode = await RunBuild(path, isSolution: true, buildPathOptions);
}
else
{
- msbuildExitCode = await RunBuild(buildPathOptions.DirectoryPath ?? Directory.GetCurrentDirectory());
+ path = PathUtility.GetFullPath(buildPathOptions.DirectoryPath ?? Directory.GetCurrentDirectory());
+ msbuildExitCode = await RunBuild(path, buildPathOptions);
}
if (msbuildExitCode != ExitCodes.Success)
{
- VSTestTrace.SafeWriteTrace(() => string.Format(LocalizableStrings.CmdMSBuildProjectsPropertiesErrorDescription, msbuildExitCode));
+ _output.WriteMessage(string.Format(LocalizableStrings.CmdMSBuildProjectsPropertiesErrorDescription, msbuildExitCode));
return false;
}
@@ -65,7 +72,7 @@ private bool ValidateBuildPathOptions(BuildPathsOptions buildPathOptions)
(!string.IsNullOrEmpty(buildPathOptions.ProjectPath) && !string.IsNullOrEmpty(buildPathOptions.DirectoryPath)) ||
(!string.IsNullOrEmpty(buildPathOptions.SolutionPath) && !string.IsNullOrEmpty(buildPathOptions.DirectoryPath)))
{
- VSTestTrace.SafeWriteTrace(() => LocalizableStrings.CmdMultipleBuildPathOptionsErrorDescription);
+ _output.WriteMessage(LocalizableStrings.CmdMultipleBuildPathOptionsErrorDescription);
return false;
}
@@ -81,49 +88,50 @@ private bool ValidateBuildPathOptions(BuildPathsOptions buildPathOptions)
if (!string.IsNullOrEmpty(buildPathOptions.DirectoryPath) && !Directory.Exists(buildPathOptions.DirectoryPath))
{
- VSTestTrace.SafeWriteTrace(() => string.Format(LocalizableStrings.CmdNonExistentDirectoryErrorDescription, Path.GetFullPath(buildPathOptions.DirectoryPath)));
+ _output.WriteMessage(string.Format(LocalizableStrings.CmdNonExistentDirectoryErrorDescription, buildPathOptions.DirectoryPath));
return false;
}
return true;
}
- private static bool ValidateFilePath(string filePath, string[] validExtensions, string errorMessage)
+ private bool ValidateFilePath(string filePath, string[] validExtensions, string errorMessage)
{
if (!validExtensions.Contains(Path.GetExtension(filePath)))
{
- VSTestTrace.SafeWriteTrace(() => string.Format(errorMessage, filePath));
+ _output.WriteMessage(string.Format(errorMessage, filePath));
return false;
}
if (!File.Exists(filePath))
{
- VSTestTrace.SafeWriteTrace(() => string.Format(LocalizableStrings.CmdNonExistentFileErrorDescription, Path.GetFullPath(filePath)));
+ _output.WriteMessage(string.Format(LocalizableStrings.CmdNonExistentFileErrorDescription, Path.GetFullPath(filePath)));
return false;
}
return true;
}
- private async Task RunBuild(string directoryPath)
+ private async Task RunBuild(string directoryPath, BuildPathsOptions buildPathOptions)
{
- bool solutionOrProjectFileFound = SolutionAndProjectUtility.TryGetProjectOrSolutionFilePath(directoryPath, out string projectOrSolutionFilePath, out bool isSolution);
+ (bool solutionOrProjectFileFound, string message) = SolutionAndProjectUtility.TryGetProjectOrSolutionFilePath(directoryPath, out string projectOrSolutionFilePath, out bool isSolution);
if (!solutionOrProjectFileFound)
{
+ _output.WriteMessage(message);
return ExitCodes.GenericFailure;
}
- (IEnumerable modules, bool restored) = await GetProjectsProperties(projectOrSolutionFilePath, isSolution);
+ (IEnumerable modules, bool restored) = await GetProjectsProperties(projectOrSolutionFilePath, isSolution, buildPathOptions);
InitializeTestApplications(modules);
return restored ? ExitCodes.Success : ExitCodes.GenericFailure;
}
- private async Task RunBuild(string filePath, bool isSolution)
+ private async Task RunBuild(string filePath, bool isSolution, BuildPathsOptions buildPathOptions)
{
- (IEnumerable modules, bool restored) = await GetProjectsProperties(filePath, isSolution);
+ (IEnumerable modules, bool restored) = await GetProjectsProperties(filePath, isSolution, buildPathOptions);
InitializeTestApplications(modules);
@@ -166,10 +174,12 @@ public bool EnqueueTestApplications()
return true;
}
- private async Task<(IEnumerable, bool Restored)> GetProjectsProperties(string solutionOrProjectFilePath, bool isSolution)
+ private async Task<(IEnumerable, bool Restored)> GetProjectsProperties(string solutionOrProjectFilePath, bool isSolution, BuildPathsOptions buildPathOptions)
{
+ bool isBuiltOrRestored = true;
var allProjects = new ConcurrentBag();
- bool restored = true;
+ var projectCollection = new ProjectCollection();
+ bool allowBinLog = IsBinaryLoggerEnabled(_args, out string binLogFileName);
if (isSolution)
{
@@ -179,77 +189,141 @@ public bool EnqueueTestApplications()
: fileDirectory;
var projects = await SolutionAndProjectUtility.ParseSolution(solutionOrProjectFilePath, rootDirectory);
- ProcessProjectsInParallel(projects, allProjects, ref restored);
+
+ MSBuildBuildAndRestoreSettings msBuildBuildAndRestoreSettings = new(GetCommands(buildPathOptions.HasNoRestore, buildPathOptions.HasNoBuild), buildPathOptions.Configuration, buildPathOptions.RuntimeIdentifier, allowBinLog, binLogFileName);
+ isBuiltOrRestored = BuildOrRestoreProjectOrSolution(solutionOrProjectFilePath, projectCollection, msBuildBuildAndRestoreSettings);
+
+ ProcessProjectsInParallel(projectCollection, projects, allProjects);
}
else
{
- bool allowBinLog = IsBinaryLoggerEnabled(_args, out string binLogFileName);
+ if (!buildPathOptions.HasNoRestore)
+ {
+ MSBuildBuildAndRestoreSettings msBuildRestoreSettings = new([CliConstants.RestoreCommand], buildPathOptions.Configuration, buildPathOptions.RuntimeIdentifier, allowBinLog, binLogFileName);
+ isBuiltOrRestored = BuildOrRestoreProjectOrSolution(solutionOrProjectFilePath, projectCollection, msBuildRestoreSettings);
+ }
- var (relatedProjects, isProjectBuilt) = GetProjectPropertiesInternal(solutionOrProjectFilePath, allowBinLog, binLogFileName);
- foreach (var relatedProject in relatedProjects)
+ if (!buildPathOptions.HasNoBuild)
{
- allProjects.Add(relatedProject);
+ MSBuildBuildAndRestoreSettings msBuildBuildSettings = new([CliConstants.BuildCommand], buildPathOptions.Configuration, buildPathOptions.RuntimeIdentifier, allowBinLog, binLogFileName);
+ isBuiltOrRestored = isBuiltOrRestored && BuildOrRestoreProjectOrSolution(solutionOrProjectFilePath, projectCollection, msBuildBuildSettings);
}
- if (!isProjectBuilt)
+ IEnumerable relatedProjects = GetProjectPropertiesInternal(solutionOrProjectFilePath, projectCollection);
+ foreach (var relatedProject in relatedProjects)
{
- restored = false;
+ allProjects.Add(relatedProject);
}
}
- return (allProjects, restored);
+
+ LogProjectProperties(allProjects);
+
+ return (allProjects, isBuiltOrRestored);
}
- private void ProcessProjectsInParallel(IEnumerable projects, ConcurrentBag allProjects, ref bool restored)
+ public static string[] GetCommands(bool hasNoRestore, bool hasNoBuild)
{
- bool allProjectsRestored = true;
- bool allowBinLog = IsBinaryLoggerEnabled(_args, out string binLogFileName);
+ var commands = new List();
+
+ if (!hasNoRestore)
+ {
+ commands.Add(CliConstants.RestoreCommand);
+ }
+ if (!hasNoBuild)
+ {
+ commands.Add(CliConstants.BuildCommand);
+ }
+
+ return commands.ToArray();
+ }
+
+ private void ProcessProjectsInParallel(ProjectCollection projectCollection, IEnumerable projects, ConcurrentBag allProjects)
+ {
Parallel.ForEach(
projects,
new ParallelOptions { MaxDegreeOfParallelism = _degreeOfParallelism },
- () => true,
- (project, state, localRestored) =>
+ (project, state) =>
{
- var (relatedProjects, isRestored) = GetProjectPropertiesInternal(project, allowBinLog, binLogFileName);
+ IEnumerable relatedProjects = GetProjectPropertiesInternal(project, projectCollection);
foreach (var relatedProject in relatedProjects)
{
allProjects.Add(relatedProject);
}
+ });
+ }
- return localRestored && isRestored;
- },
- localRestored =>
- {
- if (!localRestored)
+ private static IEnumerable GetProjectPropertiesInternal(string projectFilePath, ProjectCollection projectCollection)
+ {
+ var project = projectCollection.LoadProject(projectFilePath);
+ return ExtractModulesFromProject(project);
+ }
+
+ private static bool BuildOrRestoreProjectOrSolution(string projectFilePath, ProjectCollection projectCollection, MSBuildBuildAndRestoreSettings msBuildBuildAndRestoreSettings)
+ {
+ var parameters = GetBuildParameters(projectCollection, msBuildBuildAndRestoreSettings);
+ var globalProperties = GetGlobalProperties(msBuildBuildAndRestoreSettings);
+
+ var buildRequestData = new BuildRequestData(projectFilePath, globalProperties, null, msBuildBuildAndRestoreSettings.Commands, null);
+
+ BuildResult buildResult = BuildManager.DefaultBuildManager.Build(parameters, buildRequestData);
+
+ return buildResult.OverallResult == BuildResultCode.Success;
+ }
+
+ private static BuildParameters GetBuildParameters(ProjectCollection projectCollection, MSBuildBuildAndRestoreSettings msBuildBuildAndRestoreSettings)
+ {
+ BuildParameters parameters = new(projectCollection)
+ {
+ Loggers = [new ConsoleLogger(LoggerVerbosity.Quiet)]
+ };
+
+ if (!msBuildBuildAndRestoreSettings.AllowBinLog)
+ return parameters;
+
+ parameters.Loggers =
+ [
+ .. parameters.Loggers,
+ .. new[]
+ {
+ new BinaryLogger
{
- allProjectsRestored = false;
+ Parameters = msBuildBuildAndRestoreSettings.BinLogFileName
}
- });
+ },
+ ];
- restored = allProjectsRestored;
+ return parameters;
}
- private static (IEnumerable Modules, bool Restored) GetProjectPropertiesInternal(string projectFilePath, bool allowBinLog, string binLogFileName)
+ private static Dictionary GetGlobalProperties(MSBuildBuildAndRestoreSettings msBuildBuildAndRestoreSettings)
{
- var projectCollection = new ProjectCollection();
- var project = projectCollection.LoadProject(projectFilePath);
- var buildResult = RestoreProject(projectFilePath, projectCollection, allowBinLog, binLogFileName);
+ var globalProperties = new Dictionary();
- bool restored = buildResult.OverallResult == BuildResultCode.Success;
+ if (!string.IsNullOrEmpty(msBuildBuildAndRestoreSettings.Configuration))
+ {
+ globalProperties[CliConstants.Configuration] = msBuildBuildAndRestoreSettings.Configuration;
+ }
- if (!restored)
+ if (!string.IsNullOrEmpty(msBuildBuildAndRestoreSettings.RuntimeIdentifier))
{
- return (Array.Empty(), restored);
+ globalProperties[CliConstants.RuntimeIdentifier] = msBuildBuildAndRestoreSettings.RuntimeIdentifier;
}
- return (ExtractModulesFromProject(project), restored);
+ return globalProperties;
}
private static IEnumerable ExtractModulesFromProject(Project project)
{
- _ = bool.TryParse(project.GetPropertyValue(ProjectProperties.IsTestingPlatformApplication), out bool isTestingPlatformApplication);
_ = bool.TryParse(project.GetPropertyValue(ProjectProperties.IsTestProject), out bool isTestProject);
+ if (!isTestProject)
+ {
+ return [];
+ }
+
+ _ = bool.TryParse(project.GetPropertyValue(ProjectProperties.IsTestingPlatformApplication), out bool isTestingPlatformApplication);
+
string targetFramework = project.GetPropertyValue(ProjectProperties.TargetFramework);
string targetFrameworks = project.GetPropertyValue(ProjectProperties.TargetFrameworks);
string targetPath = project.GetPropertyValue(ProjectProperties.TargetPath);
@@ -260,7 +334,7 @@ private static IEnumerable ExtractModulesFromProject(Project project)
if (string.IsNullOrEmpty(targetFrameworks))
{
- projects.Add(new Module(targetPath, projectFullPath, targetFramework, runSettingsFilePath, isTestingPlatformApplication, isTestProject));
+ projects.Add(new Module(targetPath, PathUtility.FixFilePath(projectFullPath), targetFramework, runSettingsFilePath, isTestingPlatformApplication, isTestProject));
}
else
{
@@ -271,7 +345,7 @@ private static IEnumerable ExtractModulesFromProject(Project project)
project.ReevaluateIfNecessary();
projects.Add(new Module(project.GetPropertyValue(ProjectProperties.TargetPath),
- projectFullPath,
+ PathUtility.FixFilePath(projectFullPath),
framework,
runSettingsFilePath,
isTestingPlatformApplication,
@@ -282,34 +356,29 @@ private static IEnumerable ExtractModulesFromProject(Project project)
return projects;
}
- private static BuildResult RestoreProject(string projectFilePath, ProjectCollection projectCollection, bool allowBinLog, string binLogFileName)
+ private void LogProjectProperties(IEnumerable modules)
{
- BuildParameters parameters = new(projectCollection)
+ if (!VSTestTrace.TraceEnabled)
{
- Loggers = [new ConsoleLogger(LoggerVerbosity.Quiet)]
- };
-
- if (allowBinLog)
- {
- parameters.Loggers = parameters.Loggers.Concat([
- new BinaryLogger
- {
- Parameters = binLogFileName
- }
- ]);
+ return;
}
- var buildRequestData = new BuildRequestData(projectFilePath, new Dictionary(), null, [CliConstants.RestoreCommand], null);
- BuildResult buildResult;
- lock (buildLock)
+ foreach (var module in modules)
{
- buildResult = BuildManager.DefaultBuildManager.Build(parameters, buildRequestData);
- }
+ Console.WriteLine();
- return buildResult;
+ VSTestTrace.SafeWriteTrace(() => $"{ProjectProperties.ProjectFullPath}: {module.ProjectPath}");
+ VSTestTrace.SafeWriteTrace(() => $"{ProjectProperties.IsTestProject}: {module.IsTestProject}");
+ VSTestTrace.SafeWriteTrace(() => $"{ProjectProperties.IsTestingPlatformApplication}: {module.IsTestingPlatformApplication}");
+ VSTestTrace.SafeWriteTrace(() => $"{ProjectProperties.TargetFramework}: {module.TargetFramework}");
+ VSTestTrace.SafeWriteTrace(() => $"{ProjectProperties.TargetPath}: {module.DllOrExePath}");
+ VSTestTrace.SafeWriteTrace(() => $"{ProjectProperties.RunSettingsFilePath}: {module.RunSettingsFilePath}");
+
+ Console.WriteLine();
+ }
}
- private static bool IsBinaryLoggerEnabled(List args, out string binLogFileName)
+ internal static bool IsBinaryLoggerEnabled(List args, out string binLogFileName)
{
binLogFileName = string.Empty;
var binLogArgs = new List();
diff --git a/src/Cli/dotnet/commands/dotnet-test/Models.cs b/src/Cli/dotnet/commands/dotnet-test/Models.cs
index 17ca6a4261ec..cd59dec9327c 100644
--- a/src/Cli/dotnet/commands/dotnet-test/Models.cs
+++ b/src/Cli/dotnet/commands/dotnet-test/Models.cs
@@ -20,4 +20,6 @@ internal sealed record FlatException(string? ErrorMessage, string? ErrorType, st
internal sealed record FileArtifact(string? FullPath, string? DisplayName, string? Description, string? TestUid, string? TestDisplayName, string? SessionUid);
internal sealed record TestSession(byte? SessionType, string? SessionUid, string? ExecutionId);
+
+ internal record MSBuildBuildAndRestoreSettings(string[] Commands, string Configuration, string RuntimeIdentifier, bool AllowBinLog, string BinLogFileName);
}
diff --git a/src/Cli/dotnet/commands/dotnet-test/Options.cs b/src/Cli/dotnet/commands/dotnet-test/Options.cs
index 598e3cb7d722..d87f185995f0 100644
--- a/src/Cli/dotnet/commands/dotnet-test/Options.cs
+++ b/src/Cli/dotnet/commands/dotnet-test/Options.cs
@@ -3,7 +3,7 @@
namespace Microsoft.DotNet.Cli
{
- internal record BuildConfigurationOptions(bool HasNoRestore, bool HasNoBuild, bool HasListTests, string Configuration, string Architecture);
+ internal record BuildConfigurationOptions(bool HasListTests, string Configuration, string Architecture);
- internal record BuildPathsOptions(string ProjectPath, string SolutionPath, string DirectoryPath);
+ internal record BuildPathsOptions(string ProjectPath, string SolutionPath, string DirectoryPath, bool HasNoRestore, bool HasNoBuild, string Configuration, string RuntimeIdentifier);
}
diff --git a/src/Cli/dotnet/commands/dotnet-test/SolutionAndProjectUtility.cs b/src/Cli/dotnet/commands/dotnet-test/SolutionAndProjectUtility.cs
index a3ca1af6b4c6..07799a885983 100644
--- a/src/Cli/dotnet/commands/dotnet-test/SolutionAndProjectUtility.cs
+++ b/src/Cli/dotnet/commands/dotnet-test/SolutionAndProjectUtility.cs
@@ -11,14 +11,14 @@ namespace Microsoft.DotNet.Cli
{
internal static class SolutionAndProjectUtility
{
- public static bool TryGetProjectOrSolutionFilePath(string directory, out string projectOrSolutionFilePath, out bool isSolution)
+ public static (bool SolutionOrProjectFileFound, string Message) TryGetProjectOrSolutionFilePath(string directory, out string projectOrSolutionFilePath, out bool isSolution)
{
projectOrSolutionFilePath = string.Empty;
isSolution = false;
if (!Directory.Exists(directory))
{
- return false;
+ return (false, string.Format(LocalizableStrings.CmdNonExistentDirectoryErrorDescription, directory));
}
var possibleSolutionPaths = GetSolutionFilePaths(directory);
@@ -26,8 +26,7 @@ public static bool TryGetProjectOrSolutionFilePath(string directory, out string
// If more than a single sln file is found, an error is thrown since we can't determine which one to choose.
if (possibleSolutionPaths.Length > 1)
{
- VSTestTrace.SafeWriteTrace(() => string.Format(CommonLocalizableStrings.MoreThanOneSolutionInDirectory, directory));
- return false;
+ return (false, string.Format(CommonLocalizableStrings.MoreThanOneSolutionInDirectory, directory));
}
if (possibleSolutionPaths.Length == 1)
@@ -38,11 +37,10 @@ public static bool TryGetProjectOrSolutionFilePath(string directory, out string
{
projectOrSolutionFilePath = possibleSolutionPaths[0];
isSolution = true;
- return true;
+ return (true, string.Empty);
}
- VSTestTrace.SafeWriteTrace(() => LocalizableStrings.CmdMultipleProjectOrSolutionFilesErrorDescription);
- return false;
+ return (false, LocalizableStrings.CmdMultipleProjectOrSolutionFilesErrorDescription);
}
else // If no solutions are found, look for a project file
{
@@ -50,19 +48,16 @@ public static bool TryGetProjectOrSolutionFilePath(string directory, out string
if (possibleProjectPath.Length == 0)
{
- VSTestTrace.SafeWriteTrace(() => LocalizableStrings.CmdNoProjectOrSolutionFileErrorDescription);
- return false;
+ return (false, LocalizableStrings.CmdNoProjectOrSolutionFileErrorDescription);
}
if (possibleProjectPath.Length == 1)
{
projectOrSolutionFilePath = possibleProjectPath[0];
- return true;
+ return (true, string.Empty);
}
- VSTestTrace.SafeWriteTrace(() => string.Format(CommonLocalizableStrings.MoreThanOneProjectInDirectory, directory));
-
- return false;
+ return (false, string.Format(CommonLocalizableStrings.MoreThanOneSolutionInDirectory, directory));
}
}
diff --git a/src/Cli/dotnet/commands/dotnet-test/TestApplication.cs b/src/Cli/dotnet/commands/dotnet-test/TestApplication.cs
index 6918d2bcc2a7..ca0a6cdd7dda 100644
--- a/src/Cli/dotnet/commands/dotnet-test/TestApplication.cs
+++ b/src/Cli/dotnet/commands/dotnet-test/TestApplication.cs
@@ -31,12 +31,8 @@ internal sealed class TestApplication : IDisposable
public event EventHandler SessionEventReceived;
public event EventHandler ErrorReceived;
public event EventHandler TestProcessExited;
- public event EventHandler Run;
public event EventHandler ExecutionIdReceived;
- private const string TestingPlatformVsTestBridgeRunSettingsFileEnvVar = "TESTINGPLATFORM_VSTESTBRIDGE_RUNSETTINGS_FILE";
- private const string DLLExtension = "dll";
-
public Module Module => _module;
public TestApplication(Module module, List args)
@@ -52,8 +48,6 @@ public void AddExecutionId(string executionId)
public async Task RunAsync(bool hasFilterMode, bool enableHelp, BuildConfigurationOptions buildConfigurationOptions)
{
- Run?.Invoke(this, EventArgs.Empty);
-
if (hasFilterMode && !ModulePathExists())
{
return 1;
@@ -89,11 +83,10 @@ private ProcessStartInfo CreateProcessStartInfo(bool hasFilterMode, bool isDll,
return processStartInfo;
}
-
private void WaitOnTestApplicationPipeConnectionLoop()
{
_cancellationToken.Cancel();
- _testAppPipeConnectionLoop.Wait((int)TimeSpan.FromSeconds(30).TotalMilliseconds);
+ _testAppPipeConnectionLoop?.Wait((int)TimeSpan.FromSeconds(30).TotalMilliseconds);
}
private async Task WaitConnectionAsync(CancellationToken token)
@@ -109,9 +102,14 @@ private async Task WaitConnectionAsync(CancellationToken token)
_testAppPipeConnections.Add(pipeConnection);
}
}
- catch (OperationCanceledException ex) when (ex.CancellationToken == token)
+ catch (OperationCanceledException ex)
{
// We are exiting
+ if (VSTestTrace.TraceEnabled)
+ {
+ string tokenType = ex.CancellationToken == token ? "internal token" : "external token";
+ VSTestTrace.SafeWriteTrace(() => $"WaitConnectionAsync() throws OperationCanceledException with {tokenType}");
+ }
}
catch (Exception ex)
{
@@ -212,7 +210,7 @@ private async Task StartProcess(ProcessStartInfo processStartInfo)
{
if (VSTestTrace.TraceEnabled)
{
- VSTestTrace.SafeWriteTrace(() => $"Updated args: {processStartInfo.Arguments}");
+ VSTestTrace.SafeWriteTrace(() => $"Test application arguments: {processStartInfo.Arguments}");
}
var process = Process.Start(processStartInfo);
@@ -262,15 +260,9 @@ private string BuildArgsWithDotnetRun(bool hasHelp, BuildConfigurationOptions bu
builder.Append($"{CliConstants.DotnetRunCommand} {TestingPlatformOptions.ProjectOption.Name} \"{_module.ProjectPath}\"");
- if (buildConfigurationOptions.HasNoRestore)
- {
- builder.Append($" {TestingPlatformOptions.NoRestoreOption.Name}");
- }
-
- if (buildConfigurationOptions.HasNoBuild)
- {
- builder.Append($" {TestingPlatformOptions.NoBuildOption.Name}");
- }
+ // Because we restored and built before in MSHandler, we will skip those with dotnet run
+ builder.Append($" {TestingPlatformOptions.NoRestoreOption.Name}");
+ builder.Append($" {TestingPlatformOptions.NoBuildOption.Name}");
if (buildConfigurationOptions.HasListTests)
{
@@ -382,12 +374,14 @@ public override string ToString()
if (!string.IsNullOrEmpty(_module.ProjectPath))
{
builder.Append($"Project: {_module.ProjectPath}");
- };
+ }
+ ;
if (!string.IsNullOrEmpty(_module.TargetFramework))
{
builder.Append($"Target Framework: {_module.TargetFramework}");
- };
+ }
+ ;
return builder.ToString();
}
diff --git a/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs b/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs
index df414cbfa4e9..34b6a0a2e865 100644
--- a/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs
+++ b/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs
@@ -168,9 +168,6 @@ private static bool IsTestingPlatformEnabled()
private static CliCommand ConstructCommand()
{
-#if RELEASE
- return GetVSTestCliCommand();
-#else
bool isTestingPlatformEnabled = IsTestingPlatformEnabled();
string testingSdkName = isTestingPlatformEnabled ? "testingplatform" : "vstest";
@@ -184,7 +181,6 @@ private static CliCommand ConstructCommand()
}
throw new InvalidOperationException($"Testing sdk not supported: {testingSdkName}");
-#endif
}
private static CliCommand GetTestingPlatformCliCommand()
diff --git a/src/Cli/dotnet/commands/dotnet-test/TestingPlatformCommand.cs b/src/Cli/dotnet/commands/dotnet-test/TestingPlatformCommand.cs
index e73dddebf244..42528d8b6f88 100644
--- a/src/Cli/dotnet/commands/dotnet-test/TestingPlatformCommand.cs
+++ b/src/Cli/dotnet/commands/dotnet-test/TestingPlatformCommand.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
using System.Collections.Concurrent;
using System.CommandLine;
using Microsoft.DotNet.Tools.Test;
@@ -14,8 +13,6 @@ namespace Microsoft.DotNet.Cli
{
internal partial class TestingPlatformCommand : CliCommand, ICustomHelp
{
- private readonly ConcurrentBag _testApplications = [];
-
private MSBuildHandler _msBuildHandler;
private TestModulesFilterHandler _testModulesFilterHandler;
private TerminalTestReporter _output;
@@ -36,91 +33,34 @@ public int Run(ParseResult parseResult)
bool hasFailed = false;
try
{
- Console.CancelKeyPress += (s, e) =>
- {
- _output?.StartCancelling();
- CompleteRun();
- };
+ SetupCancelKeyPressHandler();
int degreeOfParallelism = GetDegreeOfParallelism(parseResult);
BuildConfigurationOptions buildConfigurationOptions = GetBuildConfigurationOptions(parseResult);
- InitializeActionQueue(parseResult, degreeOfParallelism, buildConfigurationOptions);
-
- bool filterModeEnabled = parseResult.HasOption(TestingPlatformOptions.TestModulesFilterOption);
-
- if (filterModeEnabled && parseResult.HasOption(TestingPlatformOptions.ArchitectureOption))
- {
- VSTestTrace.SafeWriteTrace(() => $"The --arch option is not supported yet.");
- }
- if (parseResult.HasOption(TestingPlatformOptions.ListTestsOption))
- {
- _isDiscovery = true;
- }
+ _isDiscovery = parseResult.HasOption(TestingPlatformOptions.ListTestsOption);
+ _args = [.. parseResult.UnmatchedTokens];
+ _isHelp = ContainsHelpOption(parseResult.GetArguments());
- BuildConfigurationOptions builtInOptions = new(
- parseResult.HasOption(TestingPlatformOptions.NoRestoreOption),
- parseResult.HasOption(TestingPlatformOptions.NoBuildOption),
- parseResult.HasOption(TestingPlatformOptions.ListTestsOption),
- parseResult.GetValue(TestingPlatformOptions.ConfigurationOption),
- parseResult.GetValue(TestingPlatformOptions.ArchitectureOption));
+ InitializeOutput(degreeOfParallelism);
- var console = new SystemConsole();
- var output = new TerminalTestReporter(console, new TerminalTestReporterOptions()
- {
- ShowPassedTests = Environment.GetEnvironmentVariable("SHOW_PASSED") == "1" ? () => true : () => false,
- ShowProgress = () => Environment.GetEnvironmentVariable("NO_PROGRESS") != "1",
- UseAnsi = Environment.GetEnvironmentVariable("NO_ANSI") != "1",
- ShowAssembly = true,
- ShowAssemblyStartAndComplete = true,
- });
- _output = output;
-
- _isHelp = false;
- if (ContainsHelpOption(parseResult.GetArguments()))
+ bool filterModeEnabled = parseResult.HasOption(TestingPlatformOptions.TestModulesFilterOption);
+ if (_isHelp)
{
- _isHelp = true;
- _actionQueue = new(degreeOfParallelism, async (TestApplication testApp) =>
- {
- testApp.HelpRequested += OnHelpRequested;
- testApp.ErrorReceived += OnErrorReceived;
- testApp.TestProcessExited += OnTestProcessExited;
- testApp.Run += OnTestApplicationRun;
- testApp.ExecutionIdReceived += OnExecutionIdReceived;
-
- var result = await testApp.RunAsync(filterModeEnabled, enableHelp: true, builtInOptions);
- return result;
- });
+ InitializeHelpActionQueue(degreeOfParallelism, buildConfigurationOptions, filterModeEnabled);
}
else
{
- _output.TestExecutionStarted(DateTimeOffset.Now, degreeOfParallelism, _isDiscovery);
-
- _actionQueue = new(degreeOfParallelism, async (TestApplication testApp) =>
- {
- testApp.HandshakeReceived += OnHandshakeReceived;
- testApp.DiscoveredTestsReceived += OnDiscoveredTestsReceived;
- testApp.TestResultsReceived += OnTestResultsReceived;
- testApp.FileArtifactsReceived += OnFileArtifactsReceived;
- testApp.SessionEventReceived += OnSessionEventReceived;
- testApp.ErrorReceived += OnErrorReceived;
- testApp.TestProcessExited += OnTestProcessExited;
- testApp.Run += OnTestApplicationRun;
- testApp.ExecutionIdReceived += OnExecutionIdReceived;
-
- return await testApp.RunAsync(filterModeEnabled, enableHelp: false, builtInOptions);
- });
+ InitializeTestExecutionActionQueue(degreeOfParallelism, buildConfigurationOptions, filterModeEnabled);
}
- _args = [.. parseResult.UnmatchedTokens];
- _msBuildHandler = new(_args, _actionQueue, degreeOfParallelism);
+ _msBuildHandler = new(_args, _actionQueue, degreeOfParallelism, _output);
_testModulesFilterHandler = new(_args, _actionQueue);
- if (parseResult.HasOption(TestingPlatformOptions.TestModulesFilterOption))
+ if (filterModeEnabled)
{
if (!_testModulesFilterHandler.RunWithTestModulesFilter(parseResult))
{
- CompleteRun();
return ExitCodes.GenericFailure;
}
}
@@ -129,14 +69,12 @@ public int Run(ParseResult parseResult)
var buildPathOptions = GetBuildPathOptions(parseResult);
if (!_msBuildHandler.RunMSBuild(buildPathOptions).GetAwaiter().GetResult())
{
- CompleteRun();
return ExitCodes.GenericFailure;
}
if (!_msBuildHandler.EnqueueTestApplications())
{
- VSTestTrace.SafeWriteTrace(() => LocalizableStrings.CmdUnsupportedVSTestTestApplicationsDescription);
- CompleteRun();
+ _output.WriteMessage(LocalizableStrings.CmdUnsupportedVSTestTestApplicationsDescription);
return ExitCodes.GenericFailure;
}
}
@@ -146,13 +84,70 @@ public int Run(ParseResult parseResult)
}
finally
{
+ CompleteRun();
CleanUp();
}
- CompleteRun();
return hasFailed ? ExitCodes.GenericFailure : ExitCodes.Success;
}
+ private void SetupCancelKeyPressHandler()
+ {
+ Console.CancelKeyPress += (s, e) =>
+ {
+ _output?.StartCancelling();
+ CompleteRun();
+ };
+ }
+
+ private void InitializeOutput(int degreeOfParallelism)
+ {
+ var console = new SystemConsole();
+ _output = new TerminalTestReporter(console, new TerminalTestReporterOptions()
+ {
+ ShowPassedTests = Environment.GetEnvironmentVariable("SHOW_PASSED") == "1" ? () => true : () => false,
+ ShowProgress = () => Environment.GetEnvironmentVariable("NO_PROGRESS") != "1",
+ UseAnsi = Environment.GetEnvironmentVariable("NO_ANSI") != "1",
+ ShowAssembly = true,
+ ShowAssemblyStartAndComplete = true,
+ });
+
+ if (!_isHelp)
+ {
+ _output.TestExecutionStarted(DateTimeOffset.Now, degreeOfParallelism, _isDiscovery);
+ }
+ }
+
+ private void InitializeHelpActionQueue(int degreeOfParallelism, BuildConfigurationOptions buildConfigurationOptions, bool filterModeEnabled)
+ {
+ _actionQueue = new(degreeOfParallelism, async (TestApplication testApp) =>
+ {
+ testApp.HelpRequested += OnHelpRequested;
+ testApp.ErrorReceived += OnErrorReceived;
+ testApp.TestProcessExited += OnTestProcessExited;
+ testApp.ExecutionIdReceived += OnExecutionIdReceived;
+
+ return await testApp.RunAsync(filterModeEnabled, enableHelp: true, buildConfigurationOptions);
+ });
+ }
+
+ private void InitializeTestExecutionActionQueue(int degreeOfParallelism, BuildConfigurationOptions buildConfigurationOptions, bool filterModeEnabled)
+ {
+ _actionQueue = new(degreeOfParallelism, async (TestApplication testApp) =>
+ {
+ testApp.HandshakeReceived += OnHandshakeReceived;
+ testApp.DiscoveredTestsReceived += OnDiscoveredTestsReceived;
+ testApp.TestResultsReceived += OnTestResultsReceived;
+ testApp.FileArtifactsReceived += OnFileArtifactsReceived;
+ testApp.SessionEventReceived += OnSessionEventReceived;
+ testApp.ErrorReceived += OnErrorReceived;
+ testApp.TestProcessExited += OnTestProcessExited;
+ testApp.ExecutionIdReceived += OnExecutionIdReceived;
+
+ return await testApp.RunAsync(filterModeEnabled, enableHelp: false, buildConfigurationOptions);
+ });
+ }
+
private static int GetDegreeOfParallelism(ParseResult parseResult)
{
if (!int.TryParse(parseResult.GetValue(TestingPlatformOptions.MaxParallelTestModulesOption), out int degreeOfParallelism) || degreeOfParallelism <= 0)
@@ -161,50 +156,20 @@ private static int GetDegreeOfParallelism(ParseResult parseResult)
}
private static BuildConfigurationOptions GetBuildConfigurationOptions(ParseResult parseResult) =>
- new(parseResult.HasOption(TestingPlatformOptions.NoRestoreOption),
- parseResult.HasOption(TestingPlatformOptions.NoBuildOption),
- parseResult.HasOption(TestingPlatformOptions.ListTestsOption),
+ new(parseResult.HasOption(TestingPlatformOptions.ListTestsOption),
parseResult.GetValue(TestingPlatformOptions.ConfigurationOption),
parseResult.GetValue(TestingPlatformOptions.ArchitectureOption));
private static BuildPathsOptions GetBuildPathOptions(ParseResult parseResult) =>
new(parseResult.GetValue(TestingPlatformOptions.ProjectOption),
parseResult.GetValue(TestingPlatformOptions.SolutionOption),
- parseResult.GetValue(TestingPlatformOptions.DirectoryOption));
-
- private void InitializeActionQueue(ParseResult parseResult, int degreeOfParallelism, BuildConfigurationOptions buildConfigurationOptions)
- {
- if (!ContainsHelpOption(parseResult.GetArguments()))
- {
- _actionQueue = new(degreeOfParallelism, async (TestApplication testApp) =>
- {
- testApp.HandshakeReceived += OnHandshakeReceived;
- testApp.DiscoveredTestsReceived += OnDiscoveredTestsReceived;
- testApp.TestResultsReceived += OnTestResultsReceived;
- testApp.FileArtifactsReceived += OnFileArtifactsReceived;
- testApp.SessionEventReceived += OnSessionEventReceived;
- testApp.ErrorReceived += OnErrorReceived;
- testApp.TestProcessExited += OnTestProcessExited;
- testApp.Run += OnTestApplicationRun;
- testApp.ExecutionIdReceived += OnExecutionIdReceived;
-
- return await testApp.RunAsync(hasFilterMode: false, enableHelp: false, buildConfigurationOptions);
- });
- }
- else
- {
- _actionQueue = new(degreeOfParallelism, async (TestApplication testApp) =>
- {
- testApp.HelpRequested += OnHelpRequested;
- testApp.ErrorReceived += OnErrorReceived;
- testApp.TestProcessExited += OnTestProcessExited;
- testApp.Run += OnTestApplicationRun;
- testApp.ExecutionIdReceived += OnExecutionIdReceived;
-
- return await testApp.RunAsync(hasFilterMode: true, enableHelp: true, buildConfigurationOptions);
- });
- }
- }
+ parseResult.GetValue(TestingPlatformOptions.DirectoryOption),
+ parseResult.HasOption(TestingPlatformOptions.NoRestoreOption),
+ parseResult.HasOption(TestingPlatformOptions.NoBuildOption),
+ parseResult.GetValue(TestingPlatformOptions.ConfigurationOption),
+ parseResult.HasOption(TestingPlatformOptions.ArchitectureOption) ?
+ CommonOptions.ResolveRidShorthandOptionsToRuntimeIdentifier(string.Empty, parseResult.GetValue(TestingPlatformOptions.ArchitectureOption)) :
+ string.Empty);
private static bool ContainsHelpOption(IEnumerable args) => args.Contains(CliConstants.HelpOptionKey) || args.Contains(CliConstants.HelpOptionKey.Substring(0, 2));
@@ -222,9 +187,9 @@ private void CompleteRun()
private void CleanUp()
{
_msBuildHandler.Dispose();
- foreach (var testApplication in _testApplications)
+ foreach (var execution in _executions)
{
- testApplication.Dispose();
+ execution.Key.Dispose();
}
}
@@ -242,10 +207,24 @@ private void OnHandshakeReceived(object sender, HandshakeArgs args)
foreach (var property in args.Handshake.Properties)
{
- VSTestTrace.SafeWriteTrace(() => $"{property.Key}: {property.Value}");
+ VSTestTrace.SafeWriteTrace(() => $"{GetHandshakePropertyName(property.Key)}: {property.Value}");
}
}
+ private static string GetHandshakePropertyName(byte propertyId) =>
+ propertyId switch
+ {
+ HandshakeMessagePropertyNames.PID => nameof(HandshakeMessagePropertyNames.PID),
+ HandshakeMessagePropertyNames.Architecture => nameof(HandshakeMessagePropertyNames.Architecture),
+ HandshakeMessagePropertyNames.Framework => nameof(HandshakeMessagePropertyNames.Framework),
+ HandshakeMessagePropertyNames.OS => nameof(HandshakeMessagePropertyNames.OS),
+ HandshakeMessagePropertyNames.SupportedProtocolVersions => nameof(HandshakeMessagePropertyNames.SupportedProtocolVersions),
+ HandshakeMessagePropertyNames.HostType => nameof(HandshakeMessagePropertyNames.HostType),
+ HandshakeMessagePropertyNames.ModulePath => nameof(HandshakeMessagePropertyNames.ModulePath),
+ HandshakeMessagePropertyNames.ExecutionId => nameof(HandshakeMessagePropertyNames.ExecutionId),
+ _ => string.Empty,
+ };
+
private void OnDiscoveredTestsReceived(object sender, DiscoveredTestEventArgs args)
{
var testApp = (TestApplication)sender;
@@ -410,12 +389,6 @@ private void OnTestProcessExited(object sender, TestProcessExitEventArgs args)
}
}
- private void OnTestApplicationRun(object sender, EventArgs args)
- {
- TestApplication testApp = sender as TestApplication;
- _testApplications.Add(testApp);
- }
-
private void OnExecutionIdReceived(object sender, ExecutionEventArgs args)
{
}
diff --git a/test/Microsoft.NET.TestFramework/Commands/TestCommand.cs b/test/Microsoft.NET.TestFramework/Commands/TestCommand.cs
index 96d555bb293f..bd8efafeb284 100644
--- a/test/Microsoft.NET.TestFramework/Commands/TestCommand.cs
+++ b/test/Microsoft.NET.TestFramework/Commands/TestCommand.cs
@@ -61,6 +61,12 @@ public TestCommand WithTraceOutput()
return this;
}
+ public TestCommand WithEnableTestingPlatform()
+ {
+ WithEnvironmentVariable("DOTNET_CLI_TESTINGPLATFORM_ENABLE", "1");
+ return this;
+ }
+
private SdkCommandSpec CreateCommandSpec(IEnumerable args)
{
var commandSpec = CreateCommand(args);
diff --git a/test/TestAssets/TestProjects/EmptyFolder/file.txt b/test/TestAssets/TestProjects/EmptyFolder/file.txt
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/AnotherTestProject/AnotherTestProject.csproj b/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/AnotherTestProject/AnotherTestProject.csproj
new file mode 100644
index 000000000000..37286601726d
--- /dev/null
+++ b/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/AnotherTestProject/AnotherTestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ $(CurrentTargetFramework)
+ latest
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/AnotherTestProject/MSTestSettings.cs b/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/AnotherTestProject/MSTestSettings.cs
new file mode 100644
index 000000000000..aaf278c844f0
--- /dev/null
+++ b/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/AnotherTestProject/MSTestSettings.cs
@@ -0,0 +1 @@
+[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
diff --git a/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/AnotherTestProject/Test1.cs b/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/AnotherTestProject/Test1.cs
new file mode 100644
index 000000000000..46eeff4520ba
--- /dev/null
+++ b/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/AnotherTestProject/Test1.cs
@@ -0,0 +1,11 @@
+namespace AnotherTestProject
+{
+ [TestClass]
+ public sealed class Test1
+ {
+ [TestMethod]
+ public void TestMethod1()
+ {
+ }
+ }
+}
diff --git a/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/HybridTestRunnerTestProjects.sln b/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/HybridTestRunnerTestProjects.sln
new file mode 100644
index 000000000000..027f72f15fbe
--- /dev/null
+++ b/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/HybridTestRunnerTestProjects.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.13.35415.258
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OtherTestProject", "OtherTestProject\OtherTestProject.csproj", "{8683AE78-764B-46C2-98D4-8A3031CBA27E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnotherTestProject", "AnotherTestProject\AnotherTestProject.csproj", "{2604B2BA-D36A-461D-9BB3-207E4253051C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8683AE78-764B-46C2-98D4-8A3031CBA27E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8683AE78-764B-46C2-98D4-8A3031CBA27E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8683AE78-764B-46C2-98D4-8A3031CBA27E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8683AE78-764B-46C2-98D4-8A3031CBA27E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2604B2BA-D36A-461D-9BB3-207E4253051C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2604B2BA-D36A-461D-9BB3-207E4253051C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2604B2BA-D36A-461D-9BB3-207E4253051C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2604B2BA-D36A-461D-9BB3-207E4253051C}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/OtherTestProject/OtherTestProject.csproj b/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/OtherTestProject/OtherTestProject.csproj
new file mode 100644
index 000000000000..11c124c3ee7e
--- /dev/null
+++ b/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/OtherTestProject/OtherTestProject.csproj
@@ -0,0 +1,17 @@
+
+
+
+
+ Exe
+ $(CurrentTargetFramework)
+ enable
+ enable
+ true
+ true
+
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/OtherTestProject/Program.cs b/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/OtherTestProject/Program.cs
new file mode 100644
index 000000000000..f8fcbb648156
--- /dev/null
+++ b/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/OtherTestProject/Program.cs
@@ -0,0 +1,106 @@
+//See https://aka.ms/new-console-template for more information
+
+//Opt -out telemetry
+
+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.AddMSTest(() => new[] { Assembly.GetEntryAssembly()! });
+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),
+ typeof(SessionFileArtifact),
+ typeof(TestNodeFileArtifact),
+ typeof(FileArtifact), };
+
+ 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 DiscoveredTestNodeStateProperty()),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test0",
+ DisplayName = "Test0",
+ Properties = new PropertyBag(new PassedTestNodeStateProperty("OK")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test1",
+ DisplayName = "Test1",
+ Properties = new PropertyBag(new SkippedTestNodeStateProperty("OK skipped!")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test2",
+ DisplayName = "Test2",
+ Properties = new PropertyBag(new FailedTestNodeStateProperty(new Exception("this is a failed test"), "not OK")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test3",
+ DisplayName = "Test3",
+ Properties = new PropertyBag(new TimeoutTestNodeStateProperty(new Exception("this is a timeout exception"), "not OK")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test4",
+ DisplayName = "Test4",
+ Properties = new PropertyBag(new ErrorTestNodeStateProperty(new Exception("this is an exception"), "not OK")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test5",
+ DisplayName = "Test5",
+ Properties = new PropertyBag(new CancelledTestNodeStateProperty(new Exception("this is a cancelled exception"), "not OK")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new FileArtifact(new FileInfo("file.txt"), "file", "file description"));
+
+ await context.MessageBus.PublishAsync(this, new SessionFileArtifact(context.Request.Session.SessionUid, new FileInfo("sessionFile.txt"), "sessionFile", "description"));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeFileArtifact(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test6 id",
+ DisplayName = "Test6",
+ Properties = new PropertyBag(new PassedTestNodeStateProperty("OK")),
+ }, new FileInfo("testNodeFile.txt"), "testNodeFile", "description"));
+ //await Task.CompletedTask;
+ context.Complete();
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MSTestMetaPackageProjectWithMultipleTFMsSolution/MSTestMetaPackageProjectWithMultipleTFMsSolution.sln b/test/TestAssets/TestProjects/MSTestMetaPackageProjectWithMultipleTFMsSolution/MSTestMetaPackageProjectWithMultipleTFMsSolution.sln
new file mode 100644
index 000000000000..27a9cf62907c
--- /dev/null
+++ b/test/TestAssets/TestProjects/MSTestMetaPackageProjectWithMultipleTFMsSolution/MSTestMetaPackageProjectWithMultipleTFMsSolution.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.13.35415.258 main
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject", "TestProject\TestProject.csproj", "{0D246F24-651A-479B-AC3C-8D94F66A5E7E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {0D246F24-651A-479B-AC3C-8D94F66A5E7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0D246F24-651A-479B-AC3C-8D94F66A5E7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0D246F24-651A-479B-AC3C-8D94F66A5E7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0D246F24-651A-479B-AC3C-8D94F66A5E7E}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/test/TestAssets/TestProjects/MSTestMetaPackageProjectWithMultipleTFMsSolution/TestProject/TestProject.csproj b/test/TestAssets/TestProjects/MSTestMetaPackageProjectWithMultipleTFMsSolution/TestProject/TestProject.csproj
new file mode 100644
index 000000000000..7b590e68eec5
--- /dev/null
+++ b/test/TestAssets/TestProjects/MSTestMetaPackageProjectWithMultipleTFMsSolution/TestProject/TestProject.csproj
@@ -0,0 +1,36 @@
+
+
+
+
+
+ true
+
+ Exe
+
+ net8.0;net9.0
+ enable
+ enable
+
+ false
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MSTestMetaPackageProjectWithMultipleTFMsSolution/TestProject/UnitTest1.cs b/test/TestAssets/TestProjects/MSTestMetaPackageProjectWithMultipleTFMsSolution/TestProject/UnitTest1.cs
new file mode 100644
index 000000000000..19f247ea1459
--- /dev/null
+++ b/test/TestAssets/TestProjects/MSTestMetaPackageProjectWithMultipleTFMsSolution/TestProject/UnitTest1.cs
@@ -0,0 +1,25 @@
+namespace TestProject;
+
+[TestClass]
+public class UnitTest1
+{
+ [TestMethod]
+ public void TestMethod1()
+ {
+ Assert.AreEqual(1, 1);
+ }
+
+#if NET8_0
+ [TestMethod]
+ public void TestMethod2()
+ {
+ Assert.AreEqual(1, 1);
+ }
+#endif
+
+ [TestMethod]
+ public void TestMethod3()
+ {
+ Assert.AreEqual(1, 0);
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/AnotherTestProject/AnotherTestProject.csproj b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/AnotherTestProject/AnotherTestProject.csproj
new file mode 100644
index 000000000000..e78435c52b0b
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/AnotherTestProject/AnotherTestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/AnotherTestProject/Program.cs b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/AnotherTestProject/Program.cs
new file mode 100644
index 000000000000..28a9001705af
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/AnotherTestProject/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 = "Test0",
+ DisplayName = "Test0",
+ Properties = new PropertyBag(new PassedTestNodeStateProperty("OK")),
+ }));
+
+ context.Complete();
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/MultiTestProjectSolutionWithDifferentFailures.sln b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/MultiTestProjectSolutionWithDifferentFailures.sln
new file mode 100644
index 000000000000..46fb718cc0c1
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/MultiTestProjectSolutionWithDifferentFailures.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.13.35415.258 main
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject", "TestProject\TestProject.csproj", "{EB61ABDF-A476-4CD0-8F05-78C1CFB8D011}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OtherTestProject", "OtherTestProject\OtherTestProject.csproj", "{10324BA1-E676-44C6-AF77-5186CEF704FC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnotherTestProject", "AnotherTestProject\AnotherTestProject.csproj", "{7BCB85D3-DFED-4FAA-BCB5-5F0638331CC3}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {EB61ABDF-A476-4CD0-8F05-78C1CFB8D011}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EB61ABDF-A476-4CD0-8F05-78C1CFB8D011}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EB61ABDF-A476-4CD0-8F05-78C1CFB8D011}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EB61ABDF-A476-4CD0-8F05-78C1CFB8D011}.Release|Any CPU.Build.0 = Release|Any CPU
+ {10324BA1-E676-44C6-AF77-5186CEF704FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {10324BA1-E676-44C6-AF77-5186CEF704FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {10324BA1-E676-44C6-AF77-5186CEF704FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {10324BA1-E676-44C6-AF77-5186CEF704FC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7BCB85D3-DFED-4FAA-BCB5-5F0638331CC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7BCB85D3-DFED-4FAA-BCB5-5F0638331CC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7BCB85D3-DFED-4FAA-BCB5-5F0638331CC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7BCB85D3-DFED-4FAA-BCB5-5F0638331CC3}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/OtherTestProject/OtherTestProject.csproj b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/OtherTestProject/OtherTestProject.csproj
new file mode 100644
index 000000000000..e78435c52b0b
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/OtherTestProject/OtherTestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/OtherTestProject/Program.cs b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/OtherTestProject/Program.cs
new file mode 100644
index 000000000000..bba220658aa8
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/OtherTestProject/Program.cs
@@ -0,0 +1,60 @@
+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")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test2",
+ DisplayName = "Test2",
+ Properties = new PropertyBag(new SkippedTestNodeStateProperty("OK skipped!")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test3",
+ DisplayName = "Test3",
+ Properties = new PropertyBag(new FailedTestNodeStateProperty(new Exception("this is a failed test"), "not OK")),
+ }));
+
+ context.Complete();
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/TestProject/Program.cs b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/TestProject/Program.cs
new file mode 100644
index 000000000000..b8efb9c72be9
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/TestProject/Program.cs
@@ -0,0 +1,38 @@
+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 => [];
+
+ 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)
+ {
+ context.Complete();
+ await Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/TestProject/TestProject.csproj b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/TestProject/TestProject.csproj
new file mode 100644
index 000000000000..e78435c52b0b
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDifferentFailures/TestProject/TestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/MultiTestProjectSolutionWithDiscoveredTests.sln b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/MultiTestProjectSolutionWithDiscoveredTests.sln
new file mode 100644
index 000000000000..17275f0f8f30
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/MultiTestProjectSolutionWithDiscoveredTests.sln
@@ -0,0 +1,32 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35322.30 main
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject", "TestProject\TestProject.csproj", "{C43FCD94-028D-4DE8-96EC-A3663AE225B4}"
+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
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.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
+EndGlobal
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/OtherTestProject/OtherTestProject.csproj b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/OtherTestProject/OtherTestProject.csproj
new file mode 100644
index 000000000000..19a9d61c98f1
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/OtherTestProject/OtherTestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/OtherTestProject/Program.cs b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/OtherTestProject/Program.cs
new file mode 100644
index 000000000000..ea33026cd253
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/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 DiscoveredTestNodeStateProperty()),
+ }));
+
+ context.Complete();
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/TestProject/Program.cs b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/TestProject/Program.cs
new file mode 100644
index 000000000000..d6f6d84b2367
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/TestProject/Program.cs
@@ -0,0 +1,53 @@
+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 = "Test0",
+ DisplayName = "Test0",
+ Properties = new PropertyBag(new DiscoveredTestNodeStateProperty()),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test2",
+ DisplayName = "Test2",
+ Properties = new PropertyBag(new DiscoveredTestNodeStateProperty()),
+ }));
+
+ context.Complete();
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/TestProject/TestProject.csproj b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/TestProject/TestProject.csproj
new file mode 100644
index 000000000000..19a9d61c98f1
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/TestProject/TestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/global.json b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/global.json
new file mode 100644
index 000000000000..daa3ae1f149d
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithDiscoveredTests/global.json
@@ -0,0 +1,7 @@
+{
+ "test" : {
+ "runner": {
+ "name": "MicrosoftTestingPlatform"
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/MultiTestProjectSolutionWithTests.sln b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/MultiTestProjectSolutionWithTests.sln
new file mode 100644
index 000000000000..17275f0f8f30
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/MultiTestProjectSolutionWithTests.sln
@@ -0,0 +1,32 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35322.30 main
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject", "TestProject\TestProject.csproj", "{C43FCD94-028D-4DE8-96EC-A3663AE225B4}"
+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
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C43FCD94-028D-4DE8-96EC-A3663AE225B4}.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
+EndGlobal
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/OtherTestProject/OtherTestProject.csproj b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/OtherTestProject/OtherTestProject.csproj
new file mode 100644
index 000000000000..19a9d61c98f1
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/OtherTestProject/OtherTestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/OtherTestProject/Program.cs b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/OtherTestProject/Program.cs
new file mode 100644
index 000000000000..7f1ee5386c15
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/OtherTestProject/Program.cs
@@ -0,0 +1,53 @@
+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")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test2",
+ DisplayName = "Test2",
+ Properties = new PropertyBag(new SkippedTestNodeStateProperty("skipped")),
+ }));
+
+ context.Complete();
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/TestProject/Program.cs b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/TestProject/Program.cs
new file mode 100644
index 000000000000..cdecea10ffeb
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/TestProject/Program.cs
@@ -0,0 +1,60 @@
+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 = "Test0",
+ DisplayName = "Test0",
+ Properties = new PropertyBag(new PassedTestNodeStateProperty("OK")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test1",
+ DisplayName = "Test1",
+ Properties = new PropertyBag(new SkippedTestNodeStateProperty("OK skipped!")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test2",
+ DisplayName = "Test2",
+ Properties = new PropertyBag(new FailedTestNodeStateProperty(new Exception("this is a failed test"), "not OK")),
+ }));
+
+ context.Complete();
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/TestProject/TestProject.csproj b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/TestProject/TestProject.csproj
new file mode 100644
index 000000000000..19a9d61c98f1
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultiTestProjectSolutionWithTests/TestProject/TestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/MultipleTestProjectSolution/AnotherTestProject/AnotherTestProject.csproj b/test/TestAssets/TestProjects/MultipleTestProjectSolution/AnotherTestProject/AnotherTestProject.csproj
new file mode 100644
index 000000000000..19a9d61c98f1
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultipleTestProjectSolution/AnotherTestProject/AnotherTestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/MultipleTestProjectSolution/AnotherTestProject/Program.cs b/test/TestAssets/TestProjects/MultipleTestProjectSolution/AnotherTestProject/Program.cs
new file mode 100644
index 000000000000..b8efb9c72be9
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultipleTestProjectSolution/AnotherTestProject/Program.cs
@@ -0,0 +1,38 @@
+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 => [];
+
+ 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)
+ {
+ context.Complete();
+ await Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultipleTestProjectSolution/MultipleTestProjectSolution.sln b/test/TestAssets/TestProjects/MultipleTestProjectSolution/MultipleTestProjectSolution.sln
new file mode 100644
index 000000000000..e9eba761916e
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultipleTestProjectSolution/MultipleTestProjectSolution.sln
@@ -0,0 +1,33 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35322.30 main
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject", "TestProject\TestProject.csproj", "{C6D846BA-EFD0-44A5-A07C-6C50D264F63E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OtherTestProject", "OtherTestProject\OtherTestProject.csproj", "{0E527CEF-292C-4265-81EB-4C9F51072219}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnotherTestProject", "AnotherTestProject\AnotherTestProject.csproj", "{9C734AA0-998F-4CB8-BEB8-043D47AB9996}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C6D846BA-EFD0-44A5-A07C-6C50D264F63E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6D846BA-EFD0-44A5-A07C-6C50D264F63E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C6D846BA-EFD0-44A5-A07C-6C50D264F63E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C6D846BA-EFD0-44A5-A07C-6C50D264F63E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0E527CEF-292C-4265-81EB-4C9F51072219}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0E527CEF-292C-4265-81EB-4C9F51072219}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0E527CEF-292C-4265-81EB-4C9F51072219}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0E527CEF-292C-4265-81EB-4C9F51072219}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9C734AA0-998F-4CB8-BEB8-043D47AB9996}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9C734AA0-998F-4CB8-BEB8-043D47AB9996}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9C734AA0-998F-4CB8-BEB8-043D47AB9996}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9C734AA0-998F-4CB8-BEB8-043D47AB9996}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/test/TestAssets/TestProjects/MultipleTestProjectSolution/OtherTestProject/OtherTestProject.csproj b/test/TestAssets/TestProjects/MultipleTestProjectSolution/OtherTestProject/OtherTestProject.csproj
new file mode 100644
index 000000000000..1acd030edec2
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultipleTestProjectSolution/OtherTestProject/OtherTestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/MultipleTestProjectSolution/OtherTestProject/Program.cs b/test/TestAssets/TestProjects/MultipleTestProjectSolution/OtherTestProject/Program.cs
new file mode 100644
index 000000000000..b8efb9c72be9
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultipleTestProjectSolution/OtherTestProject/Program.cs
@@ -0,0 +1,38 @@
+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 => [];
+
+ 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)
+ {
+ context.Complete();
+ await Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultipleTestProjectSolution/TestProject/Program.cs b/test/TestAssets/TestProjects/MultipleTestProjectSolution/TestProject/Program.cs
new file mode 100644
index 000000000000..b8efb9c72be9
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultipleTestProjectSolution/TestProject/Program.cs
@@ -0,0 +1,38 @@
+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 => [];
+
+ 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)
+ {
+ context.Complete();
+ await Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultipleTestProjectSolution/TestProject/TestProject.csproj b/test/TestAssets/TestProjects/MultipleTestProjectSolution/TestProject/TestProject.csproj
new file mode 100644
index 000000000000..35acc103cb62
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultipleTestProjectSolution/TestProject/TestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/AnotherTestProject/AnotherTestProject.csproj b/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/AnotherTestProject/AnotherTestProject.csproj
new file mode 100644
index 000000000000..19a9d61c98f1
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/AnotherTestProject/AnotherTestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/AnotherTestProject/Program.cs b/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/AnotherTestProject/Program.cs
new file mode 100644
index 000000000000..b8efb9c72be9
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/AnotherTestProject/Program.cs
@@ -0,0 +1,38 @@
+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 => [];
+
+ 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)
+ {
+ context.Complete();
+ await Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/OtherTestProject/OtherTestProject.csproj b/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/OtherTestProject/OtherTestProject.csproj
new file mode 100644
index 000000000000..1acd030edec2
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/OtherTestProject/OtherTestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/OtherTestProject/Program.cs b/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/OtherTestProject/Program.cs
new file mode 100644
index 000000000000..b8efb9c72be9
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/OtherTestProject/Program.cs
@@ -0,0 +1,38 @@
+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 => [];
+
+ 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)
+ {
+ context.Complete();
+ await Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/TestProject/Program.cs b/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/TestProject/Program.cs
new file mode 100644
index 000000000000..b8efb9c72be9
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/TestProject/Program.cs
@@ -0,0 +1,38 @@
+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 => [];
+
+ 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)
+ {
+ context.Complete();
+ await Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/TestProject/TestProject.csproj b/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/TestProject/TestProject.csproj
new file mode 100644
index 000000000000..35acc103cb62
--- /dev/null
+++ b/test/TestAssets/TestProjects/MultipleTestProjectsWithoutSolution/TestProject/TestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/ProjectSolutionForMultipleTFMs.sln b/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/ProjectSolutionForMultipleTFMs.sln
new file mode 100644
index 000000000000..5ba28415f8a0
--- /dev/null
+++ b/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/ProjectSolutionForMultipleTFMs.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35322.30
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProjectWithNet9", "TestProjectWithNet9\TestProjectWithNet9.csproj", "{EF95A0BF-ADC3-4B37-B1EB-0436D24D615F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProjectWithNet8", "TestProjectWithNet8\TestProjectWithNet8.csproj", "{4B9F47C4-06A0-4A41-9F8D-CEC199C49CAA}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {EF95A0BF-ADC3-4B37-B1EB-0436D24D615F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EF95A0BF-ADC3-4B37-B1EB-0436D24D615F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EF95A0BF-ADC3-4B37-B1EB-0436D24D615F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EF95A0BF-ADC3-4B37-B1EB-0436D24D615F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4B9F47C4-06A0-4A41-9F8D-CEC199C49CAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4B9F47C4-06A0-4A41-9F8D-CEC199C49CAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4B9F47C4-06A0-4A41-9F8D-CEC199C49CAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4B9F47C4-06A0-4A41-9F8D-CEC199C49CAA}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/TestProjectWithNet8/Program.cs b/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/TestProjectWithNet8/Program.cs
new file mode 100644
index 000000000000..d32c5029df4e
--- /dev/null
+++ b/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/TestProjectWithNet8/Program.cs
@@ -0,0 +1,62 @@
+using Microsoft.Testing.Extensions;
+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());
+testApplicationBuilder.AddTrxReportProvider();
+
+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")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test2",
+ DisplayName = "Test2",
+ Properties = new PropertyBag(new SkippedTestNodeStateProperty("OK skipped!")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test3",
+ DisplayName = "Test3",
+ Properties = new PropertyBag(new FailedTestNodeStateProperty(new Exception("this is a failed test"), "not OK")),
+ }));
+
+ context.Complete();
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/TestProjectWithNet8/TestProjectWithNet8.csproj b/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/TestProjectWithNet8/TestProjectWithNet8.csproj
new file mode 100644
index 000000000000..9e3c61d9c8be
--- /dev/null
+++ b/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/TestProjectWithNet8/TestProjectWithNet8.csproj
@@ -0,0 +1,21 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/TestProjectWithNet9/Program.cs b/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/TestProjectWithNet9/Program.cs
new file mode 100644
index 000000000000..dd865efbf0b1
--- /dev/null
+++ b/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/TestProjectWithNet9/Program.cs
@@ -0,0 +1,53 @@
+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")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test2",
+ DisplayName = "Test2",
+ Properties = new PropertyBag(new SkippedTestNodeStateProperty("OK skipped!")),
+ }));
+
+ context.Complete();
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/TestProjectWithNet9/TestProjectWithNet9.csproj b/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/TestProjectWithNet9/TestProjectWithNet9.csproj
new file mode 100644
index 000000000000..c6644242edfd
--- /dev/null
+++ b/test/TestAssets/TestProjects/ProjectSolutionForMultipleTFMs/TestProjectWithNet9/TestProjectWithNet9.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net9.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/TestProjectFileAndSolutionFile/MultipleTestProjectSolution.sln b/test/TestAssets/TestProjects/TestProjectFileAndSolutionFile/MultipleTestProjectSolution.sln
new file mode 100644
index 000000000000..4be44315b612
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectFileAndSolutionFile/MultipleTestProjectSolution.sln
@@ -0,0 +1,21 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35322.30 main
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject", "TestProject.csproj", "{C6D846BA-EFD0-44A5-A07C-6C50D264F63E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C6D846BA-EFD0-44A5-A07C-6C50D264F63E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6D846BA-EFD0-44A5-A07C-6C50D264F63E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C6D846BA-EFD0-44A5-A07C-6C50D264F63E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C6D846BA-EFD0-44A5-A07C-6C50D264F63E}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/test/TestAssets/TestProjects/TestProjectFileAndSolutionFile/Program.cs b/test/TestAssets/TestProjects/TestProjectFileAndSolutionFile/Program.cs
new file mode 100644
index 000000000000..b8efb9c72be9
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectFileAndSolutionFile/Program.cs
@@ -0,0 +1,38 @@
+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 => [];
+
+ 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)
+ {
+ context.Complete();
+ await Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/TestProjectFileAndSolutionFile/TestProject.csproj b/test/TestAssets/TestProjects/TestProjectFileAndSolutionFile/TestProject.csproj
new file mode 100644
index 000000000000..35acc103cb62
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectFileAndSolutionFile/TestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/TestProjectSolution/TestProject/Program.cs b/test/TestAssets/TestProjects/TestProjectSolution/TestProject/Program.cs
new file mode 100644
index 000000000000..b8efb9c72be9
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectSolution/TestProject/Program.cs
@@ -0,0 +1,38 @@
+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 => [];
+
+ 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)
+ {
+ context.Complete();
+ await Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/TestProjectSolution/TestProject/TestProject.csproj b/test/TestAssets/TestProjects/TestProjectSolution/TestProject/TestProject.csproj
new file mode 100644
index 000000000000..19a9d61c98f1
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectSolution/TestProject/TestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/TestProjectSolution/TestProjectSolution.sln b/test/TestAssets/TestProjects/TestProjectSolution/TestProjectSolution.sln
new file mode 100644
index 000000000000..15ee9bf00d5d
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectSolution/TestProjectSolution.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35322.30
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject", "TestProject\TestProject.csproj", "{A3CB990F-B603-4911-A669-87C60B41DF10}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A3CB990F-B603-4911-A669-87C60B41DF10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A3CB990F-B603-4911-A669-87C60B41DF10}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A3CB990F-B603-4911-A669-87C60B41DF10}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A3CB990F-B603-4911-A669-87C60B41DF10}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/test/TestAssets/TestProjects/TestProjectSolution/global.json b/test/TestAssets/TestProjects/TestProjectSolution/global.json
new file mode 100644
index 000000000000..daa3ae1f149d
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectSolution/global.json
@@ -0,0 +1,7 @@
+{
+ "test" : {
+ "runner": {
+ "name": "MicrosoftTestingPlatform"
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProject/Program.cs b/test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProject/Program.cs
new file mode 100644
index 000000000000..07fc4f4a8c25
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProject/Program.cs
@@ -0,0 +1,15 @@
+namespace TestProject
+{
+ internal class Program
+ {
+ public static async global::System.Threading.Tasks.Task Main(string[] args)
+ {
+ Microsoft.Testing.Platform.Builder.ITestApplicationBuilder builder = await global::Microsoft.Testing.Platform.Builder.TestApplication.CreateBuilderAsync(args);
+ builder.AddSelfRegisteredExtensions(args);
+ using (global::Microsoft.Testing.Platform.Builder.ITestApplication app = await builder.BuildAsync())
+ {
+ return await app.RunAsync();
+ }
+ }
+ }
+}
diff --git a/test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProject/Test1.cs b/test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProject/Test1.cs
new file mode 100644
index 000000000000..8bbbb841f05f
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProject/Test1.cs
@@ -0,0 +1,20 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace TestProject
+{
+ [TestClass]
+ public sealed class Test1
+ {
+ [TestMethod]
+ public void TestMethod1()
+ {
+ Assert.AreEqual(1, 1);
+ }
+
+ [TestMethod]
+ public void TestMethod2()
+ {
+ Assert.AreEqual(1, 2);
+ }
+ }
+}
diff --git a/test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProject/TestProject.csproj b/test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProject/TestProject.csproj
new file mode 100644
index 000000000000..4b6416d5323e
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProject/TestProject.csproj
@@ -0,0 +1,13 @@
+
+
+
+
+ net8
+ False
+ 17.12.6
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProjectSolutionWithCodeCoverage.sln b/test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProjectSolutionWithCodeCoverage.sln
new file mode 100644
index 000000000000..13471530b1e4
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProjectSolutionWithCodeCoverage.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.13.35415.258 main
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject", "TestProject\TestProject.csproj", "{44DB7F03-7556-4EBA-BF32-C538B3A05742}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {44DB7F03-7556-4EBA-BF32-C538B3A05742}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {44DB7F03-7556-4EBA-BF32-C538B3A05742}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {44DB7F03-7556-4EBA-BF32-C538B3A05742}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {44DB7F03-7556-4EBA-BF32-C538B3A05742}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/test/TestAssets/TestProjects/TestProjectSolutionWithTestsAndArtifacts/TestProject/Program.cs b/test/TestAssets/TestProjects/TestProjectSolutionWithTestsAndArtifacts/TestProject/Program.cs
new file mode 100644
index 000000000000..db1b1ca08669
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectSolutionWithTestsAndArtifacts/TestProject/Program.cs
@@ -0,0 +1,103 @@
+//See https://aka.ms/new-console-template for more information
+
+//Opt -out telemetry
+
+using Microsoft.Testing.Extensions;
+using Microsoft.Testing.Platform.Builder;
+using Microsoft.Testing.Platform.Capabilities.TestFramework;
+using Microsoft.Testing.Platform.Extensions.Messages;
+using Microsoft.Testing.Platform.Extensions.TestFramework;
+
+Environment.SetEnvironmentVariable("DOTNET_CLI_TELEMETRY_OPTOUT", "1");
+
+var testApplicationBuilder = await TestApplication.CreateBuilderAsync(args);
+
+testApplicationBuilder.RegisterTestFramework(_ => new TestFrameworkCapabilities(), (_, __) => new DummyTestAdapter());
+
+// Enable Trx
+testApplicationBuilder.AddTrxReportProvider();
+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),
+ typeof(SessionFileArtifact),
+ typeof(TestNodeFileArtifact),
+ typeof(FileArtifact), };
+
+ 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")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test1",
+ DisplayName = "Test1",
+ Properties = new PropertyBag(new SkippedTestNodeStateProperty("OK skipped!")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test2",
+ DisplayName = "Test2",
+ Properties = new PropertyBag(new FailedTestNodeStateProperty(new Exception("this is a failed test"), "not OK")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test3",
+ DisplayName = "Test3",
+ Properties = new PropertyBag(new TimeoutTestNodeStateProperty(new Exception("this is a timeout exception"), "not OK")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Tes43",
+ DisplayName = "Test4",
+ Properties = new PropertyBag(new ErrorTestNodeStateProperty(new Exception("this is an exception"), "not OK")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test5",
+ DisplayName = "Test5",
+ Properties = new PropertyBag(new CancelledTestNodeStateProperty(new Exception("this is a cancelled exception"), "not OK")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new FileArtifact(new FileInfo("file.txt"), "file", "file description"));
+
+ await context.MessageBus.PublishAsync(this, new SessionFileArtifact(context.Request.Session.SessionUid, new FileInfo("sessionFile.txt"), "sessionFile", "description"));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeFileArtifact(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test6 id",
+ DisplayName = "Test6",
+ Properties = new PropertyBag(new PassedTestNodeStateProperty("OK")),
+ }, new FileInfo("testNodeFile.txt"), "testNodeFile", "description"));
+
+ context.Complete();
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/TestProjectSolutionWithTestsAndArtifacts/TestProject/TestProject.csproj b/test/TestAssets/TestProjects/TestProjectSolutionWithTestsAndArtifacts/TestProject/TestProject.csproj
new file mode 100644
index 000000000000..9e3c61d9c8be
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectSolutionWithTestsAndArtifacts/TestProject/TestProject.csproj
@@ -0,0 +1,21 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/TestProjectSolutionWithTestsAndArtifacts/TestProjectSolutionWithTestsAndArtifacts.sln b/test/TestAssets/TestProjects/TestProjectSolutionWithTestsAndArtifacts/TestProjectSolutionWithTestsAndArtifacts.sln
new file mode 100644
index 000000000000..f14170863d5a
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectSolutionWithTestsAndArtifacts/TestProjectSolutionWithTestsAndArtifacts.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.13.35415.258 main
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject", "TestProject\TestProject.csproj", "{7DF6A00E-4EBA-4845-95FF-9C567E6687F6}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {7DF6A00E-4EBA-4845-95FF-9C567E6687F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7DF6A00E-4EBA-4845-95FF-9C567E6687F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7DF6A00E-4EBA-4845-95FF-9C567E6687F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7DF6A00E-4EBA-4845-95FF-9C567E6687F6}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/test/TestAssets/TestProjects/TestProjectWithClassLibrary/ClassLibrary/Class1.cs b/test/TestAssets/TestProjects/TestProjectWithClassLibrary/ClassLibrary/Class1.cs
new file mode 100644
index 000000000000..534e4f348652
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectWithClassLibrary/ClassLibrary/Class1.cs
@@ -0,0 +1,6 @@
+namespace ClassLibrary
+{
+ public class Class1
+ {
+ }
+}
diff --git a/test/TestAssets/TestProjects/TestProjectWithClassLibrary/ClassLibrary/ClassLibrary.csproj b/test/TestAssets/TestProjects/TestProjectWithClassLibrary/ClassLibrary/ClassLibrary.csproj
new file mode 100644
index 000000000000..fa71b7ae6a34
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectWithClassLibrary/ClassLibrary/ClassLibrary.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/test/TestAssets/TestProjects/TestProjectWithClassLibrary/TestProject/Program.cs b/test/TestAssets/TestProjects/TestProjectWithClassLibrary/TestProject/Program.cs
new file mode 100644
index 000000000000..28a9001705af
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectWithClassLibrary/TestProject/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 = "Test0",
+ DisplayName = "Test0",
+ Properties = new PropertyBag(new PassedTestNodeStateProperty("OK")),
+ }));
+
+ context.Complete();
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/TestProjectWithClassLibrary/TestProject/TestProject.csproj b/test/TestAssets/TestProjects/TestProjectWithClassLibrary/TestProject/TestProject.csproj
new file mode 100644
index 000000000000..628b99807474
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectWithClassLibrary/TestProject/TestProject.csproj
@@ -0,0 +1,21 @@
+
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/TestProjectWithClassLibrary/TestProjectWithClassLibrary.sln b/test/TestAssets/TestProjects/TestProjectWithClassLibrary/TestProjectWithClassLibrary.sln
new file mode 100644
index 000000000000..c042974108d5
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectWithClassLibrary/TestProjectWithClassLibrary.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.35712.36 main
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject", "TestProject\TestProject.csproj", "{4868FD55-C56D-445C-9F83-A063302EC78F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary", "ClassLibrary\ClassLibrary.csproj", "{729A12AB-4D79-4756-A776-C7BB63D30A4F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4868FD55-C56D-445C-9F83-A063302EC78F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4868FD55-C56D-445C-9F83-A063302EC78F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4868FD55-C56D-445C-9F83-A063302EC78F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4868FD55-C56D-445C-9F83-A063302EC78F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {729A12AB-4D79-4756-A776-C7BB63D30A4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {729A12AB-4D79-4756-A776-C7BB63D30A4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {729A12AB-4D79-4756-A776-C7BB63D30A4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {729A12AB-4D79-4756-A776-C7BB63D30A4F}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {E2F590CA-47E0-4145-924B-3E398540BA45}
+ EndGlobalSection
+EndGlobal
diff --git a/test/TestAssets/TestProjects/TestProjectWithDiscoveredTests/Program.cs b/test/TestAssets/TestProjects/TestProjectWithDiscoveredTests/Program.cs
new file mode 100644
index 000000000000..444f5a1db21b
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectWithDiscoveredTests/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 = "Test0",
+ DisplayName = "Test0",
+ Properties = new PropertyBag(new DiscoveredTestNodeStateProperty()),
+ }));
+
+ context.Complete();
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/TestProjectWithDiscoveredTests/TestProject.csproj b/test/TestAssets/TestProjects/TestProjectWithDiscoveredTests/TestProject.csproj
new file mode 100644
index 000000000000..19a9d61c98f1
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectWithDiscoveredTests/TestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/TestProjectWithMultipleTFMsSolution/TestProject/Program.cs b/test/TestAssets/TestProjects/TestProjectWithMultipleTFMsSolution/TestProject/Program.cs
new file mode 100644
index 000000000000..99cfc02234a4
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectWithMultipleTFMsSolution/TestProject/Program.cs
@@ -0,0 +1,90 @@
+using Microsoft.Testing.Platform.Builder;
+using Microsoft.Testing.Platform.Capabilities.TestFramework;
+using Microsoft.Testing.Platform.Extensions.Messages;
+using Microsoft.Testing.Platform.Extensions.TestFramework;
+
+namespace TestProjectWithNetFM
+{
+ internal class Program
+ {
+ public static async Task Main(string[] args)
+ {
+ // To attach to the children
+ 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")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test1",
+ DisplayName = "Test1",
+ Properties = new PropertyBag(new SkippedTestNodeStateProperty("Skipped!")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test2",
+ DisplayName = "Test2",
+ Properties = new PropertyBag(new FailedTestNodeStateProperty(new Exception("this is a failed test"), "")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test3",
+ DisplayName = "Test3",
+ Properties = new PropertyBag(new TimeoutTestNodeStateProperty(new Exception("this is a timeout exception"), "")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test4",
+ DisplayName = "Test4",
+ Properties = new PropertyBag(new ErrorTestNodeStateProperty(new Exception("this is an exception"), "")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test5",
+ DisplayName = "Test5",
+ Properties = new PropertyBag(new CancelledTestNodeStateProperty(new Exception("this is a cancelled exception"), "")),
+ }));
+
+ context.Complete();
+ }
+ }
+}
diff --git a/test/TestAssets/TestProjects/TestProjectWithMultipleTFMsSolution/TestProject/TestProject.csproj b/test/TestAssets/TestProjects/TestProjectWithMultipleTFMsSolution/TestProject/TestProject.csproj
new file mode 100644
index 000000000000..d7de2c97ff87
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectWithMultipleTFMsSolution/TestProject/TestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ Exe
+ net8.0;net9.0
+ enable
+ enable
+ false
+ true
+ latest
+ false
+ true
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/TestProjectWithMultipleTFMsSolution/TestProjectWithMultipleTFMsSolution.sln b/test/TestAssets/TestProjects/TestProjectWithMultipleTFMsSolution/TestProjectWithMultipleTFMsSolution.sln
new file mode 100644
index 000000000000..62931ef6461b
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectWithMultipleTFMsSolution/TestProjectWithMultipleTFMsSolution.sln
@@ -0,0 +1,25 @@
+
+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
+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
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {3D7914D1-7D03-4FFB-8D0F-7FFA6B6BA6A0}
+ EndGlobalSection
+EndGlobal
diff --git a/test/TestAssets/TestProjects/TestProjectWithTests/Program.cs b/test/TestAssets/TestProjects/TestProjectWithTests/Program.cs
new file mode 100644
index 000000000000..e7d85d197aea
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectWithTests/Program.cs
@@ -0,0 +1,53 @@
+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 = "Test0",
+ DisplayName = "Test0",
+ Properties = new PropertyBag(new PassedTestNodeStateProperty("OK")),
+ }));
+
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
+ {
+ Uid = "Test1",
+ DisplayName = "Test1",
+ Properties = new PropertyBag(new SkippedTestNodeStateProperty("OK skipped!")),
+ }));
+
+ context.Complete();
+ }
+}
\ No newline at end of file
diff --git a/test/TestAssets/TestProjects/TestProjectWithTests/TestProject.csproj b/test/TestAssets/TestProjects/TestProjectWithTests/TestProject.csproj
new file mode 100644
index 000000000000..19a9d61c98f1
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestProjectWithTests/TestProject.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ net8.0
+ Exe
+
+ enable
+ enable
+ true
+
+ false
+ false
+ true
+
+
+
+
+
+
diff --git a/test/dotnet-test.Tests/GivenDotnetTestBuildsAndDiscoversTests.cs b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndDiscoversTests.cs
new file mode 100644
index 000000000000..140a734b9e3a
--- /dev/null
+++ b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndDiscoversTests.cs
@@ -0,0 +1,152 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using dotnet.Tests;
+using Microsoft.DotNet.Tools.Common;
+using CommandResult = Microsoft.DotNet.Cli.Utils.CommandResult;
+
+namespace Microsoft.DotNet.Cli.Test.Tests
+{
+ public class GivenDotnetTestBuildsAndDiscoversTests : SdkTest
+ {
+ public GivenDotnetTestBuildsAndDiscoversTests(ITestOutputHelper log) : base(log)
+ {
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void DiscoverTestProjectWithNoTests_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectSolution", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ListTestsOption.Name, TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ Assert.Matches($@"Discovered 0 tests.*{PathUtility.GetDirectorySeparatorChar()}TestProject.dll\s\(net8.0\|[a-zA-Z][0-9]+\)", result.StdOut);
+
+ result.StdOut
+ .Should().Contain("Discovered 0 tests.");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void DiscoverMultipleTestProjectsWithNoTests_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultipleTestProjectSolution", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ListTestsOption.Name, TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ Assert.Matches($@"Discovered 0 tests.*{PathUtility.GetDirectorySeparatorChar()}TestProject.dll\s\(net8.0\|[a-zA-Z][0-9]+\)", result.StdOut);
+ Assert.Matches($@"Discovered 0 tests.*{PathUtility.GetDirectorySeparatorChar()}OtherTestProject.dll\s\(net8.0\|[a-zA-Z][0-9]+\)", result.StdOut);
+ Assert.Matches($@"Discovered 0 tests.*{PathUtility.GetDirectorySeparatorChar()}AnotherTestProject.dll\s\(net8.0\|[a-zA-Z][0-9]+\)", result.StdOut);
+ Assert.Matches(@"Discovered 0 tests.*", result.StdOut);
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void DiscoverTestProjectWithTests_ShouldReturnZeroAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithDiscoveredTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ListTestsOption.Name, TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ Assert.Matches($@"Discovered 1 tests.*{PathUtility.GetDirectorySeparatorChar()}TestProject.dll\s\(net8.0\|[a-zA-Z][0-9]+\)\s+Test0", result.StdOut);
+ Assert.Matches(@"Discovered 1 tests.*", result.StdOut);
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.Success);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void DiscoverMultipleTestProjectsWithTests_ShouldReturnZeroAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithDiscoveredTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ListTestsOption.Name, TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ Assert.Matches($@"Discovered 2 tests.*{PathUtility.GetDirectorySeparatorChar()}TestProject.dll\s\(net8.0\|[a-zA-Z][0-9]+\)\s+Test0\s+Test2", result.StdOut);
+ Assert.Matches($@"Discovered 1 tests.*{PathUtility.GetDirectorySeparatorChar()}OtherTestProject.dll\s\(net8.0\|[a-zA-Z][0-9]+\)\s+Test1", result.StdOut);
+ Assert.Matches(@"Discovered 3 tests.*", result.StdOut);
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.Success);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void DiscoverProjectWithMSTestMetaPackageAndMultipleTFMsWithTests_ShouldReturnZeroAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MSTestMetaPackageProjectWithMultipleTFMsSolution", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ListTestsOption.Name, TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ Assert.Matches($@"Discovered 3 tests.*{PathUtility.GetDirectorySeparatorChar()}TestProject.dll\s\(net8.0\|[a-zA-Z][0-9]+\)\s+TestMethod1\s+TestMethod2\s+TestMethod3", result.StdOut);
+ Assert.Matches($@"Discovered 2 tests.*{PathUtility.GetDirectorySeparatorChar()}TestProject.dll\s\(net9.0\|[a-zA-Z][0-9]+\)\s+TestMethod1\s+TestMethod3", result.StdOut);
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.Success);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void DiscoverTestProjectsWithHybridModeTestRunners_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("HybridTestRunnerTestProjects", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ListTestsOption.Name, TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ result.StdOut.Should().Contain("Test application(s) that support VSTest are not supported.");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+ }
+}
diff --git a/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsHelp.cs b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsHelp.cs
new file mode 100644
index 000000000000..774632f30ec3
--- /dev/null
+++ b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsHelp.cs
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using dotnet.Tests;
+using CommandResult = Microsoft.DotNet.Cli.Utils.CommandResult;
+
+namespace Microsoft.DotNet.Cli.Test.Tests
+{
+ public class GivenDotnetTestBuildsAndRunsHelp : SdkTest
+ {
+ public GivenDotnetTestBuildsAndRunsHelp(ITestOutputHelper log) : base(log)
+ {
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunHelpOnTestProject_ShouldReturnZeroAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectSolutionWithTestsAndArtifacts", Guid.NewGuid().ToString()).WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(CliConstants.HelpOptionKey, TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ Assert.Matches(@"Extension options:\s+--[\s\S]*", result.StdOut);
+ Assert.Matches(@"Options:\s+--[\s\S]*", result.StdOut);
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.Success);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunHelpOnMultipleTestProjects_ShouldReturnZeroAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("ProjectSolutionForMultipleTFMs", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(CliConstants.HelpOptionKey, TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ Assert.Matches(@"Extension options:\s+--[\s\S]*", result.StdOut);
+ Assert.Matches(@"Options:\s+--[\s\S]*", result.StdOut);
+
+ string net9ProjectDllRegex = @"\s+.*\\net9\.0\\TestProjectWithNet9\.dll.*\s+--report-trx\s+--report-trx-filename";
+ string net48ProjectExeRegex = @"\s+.*\\net4\.8\\TestProjectWithNetFramework\.exe.*\s+--report-trx\s+--report-trx-filename";
+
+ Assert.Matches(@$"Unavailable extension options:(?:({net9ProjectDllRegex})|({net48ProjectExeRegex}))(?:({net48ProjectExeRegex})|({net9ProjectDllRegex}))", result.StdOut);
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.Success);
+ }
+ }
+}
diff --git a/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestBasedOnGlobbingFilter.cs b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestBasedOnGlobbingFilter.cs
new file mode 100644
index 000000000000..337b423afc3a
--- /dev/null
+++ b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestBasedOnGlobbingFilter.cs
@@ -0,0 +1,92 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text.RegularExpressions;
+using dotnet.Tests;
+using CommandResult = Microsoft.DotNet.Cli.Utils.CommandResult;
+
+namespace Microsoft.DotNet.Cli.Test.Tests
+{
+ public class GivenDotnetTestBuildsAndRunsTestBasedOnGlobbingFilter : SdkTest
+ {
+ private const string TestApplicationArgsPattern = @".*(Test application arguments).*";
+
+ public GivenDotnetTestBuildsAndRunsTestBasedOnGlobbingFilter(ITestOutputHelper log) : base(log)
+ {
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunTestProjectWithFilterOfDll_ShouldReturnZeroAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ new BuildCommand(testInstance)
+ .Execute()
+ .Should().Pass();
+
+ var binDirectory = new FileInfo($"{testInstance.Path}/bin").Directory;
+ var binDirectoryLastWriteTime = binDirectory?.LastWriteTime;
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.TestModulesFilterOption.Name, "**/bin/**/Debug/net8.0/TestProject.dll",
+ TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ // Assert that the bin folder hasn't been modified
+ Assert.Equal(binDirectoryLastWriteTime, binDirectory?.LastWriteTime);
+
+ if (!TestContext.IsLocalized())
+ {
+ result.StdOut
+ .Should().Contain("Test run summary: Passed!")
+ .And.Contain("total: 2")
+ .And.Contain("succeeded: 1")
+ .And.Contain("failed: 0")
+ .And.Contain("skipped: 1");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.Success);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunTestProjectWithFilterOfDllWithRootDirectory_ShouldReturnZeroAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ new BuildCommand(testInstance)
+ .Execute()
+ .Should().Pass();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .WithTraceOutput()
+ .Execute(TestingPlatformOptions.TestModulesFilterOption.Name, "**/bin/**/Debug/net8.0/TestProject.dll",
+ TestingPlatformOptions.TestModulesRootDirectoryOption.Name, testInstance.TestRoot,
+ TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+
+ var testAppArgs = Regex.Matches(result.StdOut!, TestApplicationArgsPattern);
+ Assert.Contains($"exec {testInstance.TestRoot}\\bin\\Debug\\net8.0\\TestProject.dll", testAppArgs.FirstOrDefault()?.Value);
+
+ if (!TestContext.IsLocalized())
+ {
+ result.StdOut
+ .Should().Contain("Test run summary: Passed!")
+ .And.Contain("total: 2")
+ .And.Contain("succeeded: 1")
+ .And.Contain("failed: 0")
+ .And.Contain("skipped: 1");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.Success);
+ }
+ }
+}
diff --git a/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTests.cs b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTests.cs
new file mode 100644
index 000000000000..275b4d4136f2
--- /dev/null
+++ b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTests.cs
@@ -0,0 +1,262 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using dotnet.Tests;
+using Microsoft.DotNet.Tools.Common;
+using CommandResult = Microsoft.DotNet.Cli.Utils.CommandResult;
+
+namespace Microsoft.DotNet.Cli.Test.Tests
+{
+ public class GivenDotnetTestBuildsAndRunsTests : SdkTest
+ {
+ public GivenDotnetTestBuildsAndRunsTests(ITestOutputHelper log) : base(log)
+ {
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunTestProjectWithNoTests_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectSolution", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ result.StdOut
+ .Should().Contain("Test run summary: Zero tests ran")
+ .And.Contain("total: 0")
+ .And.Contain("succeeded: 0")
+ .And.Contain("failed: 0")
+ .And.Contain("skipped: 0");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunMultipleTestProjectsWithNoTests_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultipleTestProjectSolution", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ result.StdOut
+ .Should().Contain("Test run summary: Zero tests ran")
+ .And.Contain("total: 0")
+ .And.Contain("succeeded: 0")
+ .And.Contain("failed: 0")
+ .And.Contain("skipped: 0");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunTestProjectWithTests_ShouldReturnZeroAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ result.StdOut
+ .Should().Contain("Test run summary: Passed!")
+ .And.Contain("skipped Test1")
+ .And.Contain("total: 2")
+ .And.Contain("succeeded: 1")
+ .And.Contain("failed: 0")
+ .And.Contain("skipped: 1");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.Success);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunMultipleTestProjectsWithFailingTests_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ result.StdOut
+ .Should().Contain("Test run summary: Failed!")
+ .And.Contain("total: 5")
+ .And.Contain("succeeded: 2")
+ .And.Contain("failed: 1")
+ .And.Contain("skipped: 2");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunMultipleTestProjectsWithDifferentFailures_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithDifferentFailures", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute("--minimum-expected-tests 2",
+ TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ Assert.Matches($@".+{PathUtility.GetDirectorySeparatorChar()}net8\.0{PathUtility.GetDirectorySeparatorChar()}TestProject\.dll\s+\(net8.0\|[a-zA-Z][1-9]+\)\sfailed.*\s+Exit code: 8", result.StdOut);
+ Assert.Matches($@".+{PathUtility.GetDirectorySeparatorChar()}net8\.0{PathUtility.GetDirectorySeparatorChar()}OtherTestProject\.dll\s+\(net8.0\|[a-zA-Z][1-9]+\)\sfailed.*\s+Exit code: 2", result.StdOut);
+ Assert.Matches($@".+{PathUtility.GetDirectorySeparatorChar()}net8\.0{PathUtility.GetDirectorySeparatorChar()}AnotherTestProject\.dll\s+\(net8.0\|[a-zA-Z][1-9]+\)\sfailed.*\s+Exit code: 9", result.StdOut);
+
+ result.StdOut
+ .Should().Contain("Test run summary: Failed!")
+ .And.Contain("total: 4")
+ .And.Contain("succeeded: 2")
+ .And.Contain("failed: 1")
+ .And.Contain("skipped: 1");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunTestProjectsWithHybridModeTestRunners_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("HybridTestRunnerTestProjects", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ result.StdOut.Should().Contain("Test application(s) that support VSTest are not supported.");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunOnEmptyFolder_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("EmptyFolder", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ result.StdOut.Should().Contain("Specify a project or solution file. The current working directory does not contain a project or solution file.");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunOnMultipleProjectFoldersWithoutSolutionFile_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultipleTestProjectsWithoutSolution", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ result.StdOut.Should().Contain("Specify a project or solution file. The current working directory does not contain a project or solution file.");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunOnProjectWithSolutionFile_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectFileAndSolutionFile", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ result.StdOut.Should().Contain("Specify which project or solution file to use because this folder contains more than one project or solution file.");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunOnProjectWithClassLibrary_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithClassLibrary", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ result.StdOut
+ .Should().Contain("Test run summary: Passed!")
+ .And.Contain("total: 1")
+ .And.Contain("succeeded: 1")
+ .And.Contain("failed: 0")
+ .And.Contain("skipped: 0");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.Success);
+ }
+ }
+}
diff --git a/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestsForMultipleTFMs.cs b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestsForMultipleTFMs.cs
new file mode 100644
index 000000000000..d624e1a09a82
--- /dev/null
+++ b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestsForMultipleTFMs.cs
@@ -0,0 +1,136 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text.RegularExpressions;
+using dotnet.Tests;
+using Microsoft.DotNet.Tools.Common;
+using CommandResult = Microsoft.DotNet.Cli.Utils.CommandResult;
+
+namespace Microsoft.DotNet.Cli.Test.Tests
+{
+ public class GivenDotnetTestBuildsAndRunsTestsForMultipleTFMs : SdkTest
+ {
+ public GivenDotnetTestBuildsAndRunsTestsForMultipleTFMs(ITestOutputHelper log) : base(log)
+ {
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunMultipleProjectWithDifferentTFMsWithFailingTests_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("ProjectSolutionForMultipleTFMs", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ MatchCollection net8ProjectMatches = Regex.Matches(result.StdOut!, $@".+{PathUtility.GetDirectorySeparatorChar()}net8\.0{PathUtility.GetDirectorySeparatorChar()}TestProjectWithNet8\.dll\s+\(net8.0\|[a-zA-Z][1-9]+\)\sfailed");
+ MatchCollection net9ProjectMatches = Regex.Matches(result.StdOut!, $@".+{PathUtility.GetDirectorySeparatorChar()}net9\.0{PathUtility.GetDirectorySeparatorChar()}TestProjectWithNet9\.dll\s+\(net9.0\|[a-zA-Z][1-9]+\)\spassed");
+
+ MatchCollection skippedTestsMatches = Regex.Matches(result.StdOut!, "skipped Test2");
+ MatchCollection failedTestsMatches = Regex.Matches(result.StdOut!, "failed Test3");
+
+ Assert.True(net8ProjectMatches.Count > 1);
+ Assert.True(net9ProjectMatches.Count > 1);
+
+ Assert.Single(failedTestsMatches);
+ Assert.Multiple(() => Assert.Equal(2, skippedTestsMatches.Count));
+
+ result.StdOut
+ .Should().Contain("Test run summary: Failed!")
+ .And.Contain("total: 5")
+ .And.Contain("succeeded: 2")
+ .And.Contain("failed: 1")
+ .And.Contain("skipped: 2");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunProjectWithMultipleTFMsWithFailingTests_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithMultipleTFMsSolution", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ var net8ProjectMatches = Regex.Matches(result.StdOut!, $@".+{PathUtility.GetDirectorySeparatorChar()}net8\.0{PathUtility.GetDirectorySeparatorChar()}TestProject\.dll\s+\(net8.0\|[a-zA-Z][1-9]+\)\sfailed");
+ var net9ProjectMatches = Regex.Matches(result.StdOut!, $@".+{PathUtility.GetDirectorySeparatorChar()}net9\.0{PathUtility.GetDirectorySeparatorChar()}TestProject\.dll\s+\(net9.0\|[a-zA-Z][1-9]+\)\sfailed");
+
+ MatchCollection skippedTestsMatches = Regex.Matches(result.StdOut!, "skipped Test1");
+ MatchCollection failedTestsMatches = Regex.Matches(result.StdOut!, "failed Test2");
+ MatchCollection timeoutTestsMatches = Regex.Matches(result.StdOut!, @"failed \(canceled\) Test3");
+ MatchCollection errorTestsMatches = Regex.Matches(result.StdOut!, "failed Test4");
+ MatchCollection canceledTestsMatches = Regex.Matches(result.StdOut!, @"failed \(canceled\) Test5");
+
+ Assert.True(net8ProjectMatches.Count > 1);
+ Assert.True(net9ProjectMatches.Count > 1);
+
+ Assert.Multiple(() => Assert.Equal(2, skippedTestsMatches.Count));
+ Assert.Multiple(() => Assert.Equal(2, failedTestsMatches.Count));
+ Assert.Multiple(() => Assert.Equal(2, timeoutTestsMatches.Count));
+ Assert.Multiple(() => Assert.Equal(2, errorTestsMatches.Count));
+ Assert.Multiple(() => Assert.Equal(2, skippedTestsMatches.Count));
+
+ result.StdOut
+ .Should().Contain("Test run summary: Failed!")
+ .And.Contain("total: 12")
+ .And.Contain("succeeded: 2")
+ .And.Contain("failed: 8")
+ .And.Contain("skipped: 2");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunProjectWithMSTestMetaPackageAndMultipleTFMsWithFailingTests_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MSTestMetaPackageProjectWithMultipleTFMsSolution", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ MatchCollection net8ProjectMatches = Regex.Matches(result.StdOut!, $@".+{PathUtility.GetDirectorySeparatorChar()}net8\.0{PathUtility.GetDirectorySeparatorChar()}TestProject\.dll\s+\(net8.0\|[a-zA-Z][1-9]+\)\sfailed");
+ MatchCollection net9ProjectMatches = Regex.Matches(result.StdOut!, $@".+{PathUtility.GetDirectorySeparatorChar()}net9\.0{PathUtility.GetDirectorySeparatorChar()}TestProject\.dll\s+\(net9.0\|[a-zA-Z][1-9]+\)\sfailed");
+
+ MatchCollection failedTestsMatches = Regex.Matches(result.StdOut!, "failed TestMethod3");
+
+ Assert.True(net8ProjectMatches.Count > 1);
+ Assert.True(net9ProjectMatches.Count > 1);
+
+ Assert.Multiple(() => Assert.Equal(2, failedTestsMatches.Count));
+
+ result.StdOut
+ .Should().Contain("Test run summary: Failed!")
+ .And.Contain("total: 5")
+ .And.Contain("succeeded: 3")
+ .And.Contain("failed: 2")
+ .And.Contain("skipped: 0");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+ }
+}
diff --git a/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestsWithArtifacts.cs b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestsWithArtifacts.cs
new file mode 100644
index 000000000000..d1818318bd22
--- /dev/null
+++ b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestsWithArtifacts.cs
@@ -0,0 +1,78 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using dotnet.Tests;
+using Microsoft.DotNet.Tools.Common;
+using CommandResult = Microsoft.DotNet.Cli.Utils.CommandResult;
+
+namespace Microsoft.DotNet.Cli.Test.Tests
+{
+ public class GivenDotnetTestBuildsAndRunsTestsWithArtifacts : SdkTest
+ {
+ public GivenDotnetTestBuildsAndRunsTestsWithArtifacts(ITestOutputHelper log) : base(log)
+ {
+ }
+
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunTestProjectWithFailingTestsAndFileArtifacts_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectSolutionWithTestsAndArtifacts", Guid.NewGuid().ToString()).WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ Assert.Matches(@".*Test6.*testNodeFile.txt", result.StdOut);
+
+ result.StdOut
+ .Should().Contain("In process file artifacts")
+ .And.Contain("file.txt")
+ .And.Contain("sessionFile.txt");
+
+ result.StdOut
+ .Should().Contain("Test run summary: Failed!")
+ .And.Contain("total: 6")
+ .And.Contain("succeeded: 1")
+ .And.Contain("failed: 4")
+ .And.Contain("skipped: 1");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunTestProjectWithCodeCoverage_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectSolutionWithCodeCoverage", Guid.NewGuid().ToString()).WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute("--coverage", TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ if (!TestContext.IsLocalized())
+ {
+ string pattern = $@"In\sprocess\sfile\sartifacts\sproduced:\s+.*{PathUtility.GetDirectorySeparatorChar()}TestResults{PathUtility.GetDirectorySeparatorChar()}.*\.coverage";
+
+ Assert.Matches(pattern, result.StdOut);
+
+ result.StdOut
+ .Should().Contain("Test run summary: Failed!")
+ .And.Contain("total: 2")
+ .And.Contain("succeeded: 1")
+ .And.Contain("failed: 1")
+ .And.Contain("skipped: 0");
+ }
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+ }
+}
diff --git a/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestsWithDifferentOptions.cs b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestsWithDifferentOptions.cs
new file mode 100644
index 000000000000..48ad4172ae00
--- /dev/null
+++ b/test/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestsWithDifferentOptions.cs
@@ -0,0 +1,298 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text.RegularExpressions;
+using dotnet.Tests;
+using CommandResult = Microsoft.DotNet.Cli.Utils.CommandResult;
+
+namespace Microsoft.DotNet.Cli.Test.Tests
+{
+ public class GivenDotnetTestBuildsAndRunsTestsWithDifferentOptions : SdkTest
+ {
+ private const string TestApplicationArgsSeparator = $" {CliConstants.ParametersSeparator} ";
+ private const string TestApplicationArgsPattern = @".*(Test application arguments).*";
+
+ public GivenDotnetTestBuildsAndRunsTestsWithDifferentOptions(ITestOutputHelper log) : base(log)
+ {
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunWithProjectPathWithFailingTests_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ string testProjectPath = $"TestProject{Path.DirectorySeparatorChar}TestProject.csproj";
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .WithTraceOutput()
+ .Execute(TestingPlatformOptions.ProjectOption.Name, testProjectPath,
+ TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ var testAppArgs = Regex.Matches(result.StdOut!, TestApplicationArgsPattern);
+ string fullProjectPath = $"{testInstance.TestRoot}{Path.DirectorySeparatorChar}{testProjectPath}";
+ Assert.Contains($"{TestingPlatformOptions.ProjectOption.Name} \"{fullProjectPath}\"", testAppArgs.FirstOrDefault()?.Value.Split(TestApplicationArgsSeparator)[0]);
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunWithSolutionPathWithFailingTests_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ string testSolutionPath = "MultiTestProjectSolutionWithTests.sln";
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .WithTraceOutput()
+ .Execute(TestingPlatformOptions.SolutionOption.Name, testSolutionPath,
+ TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+
+ var testAppArgs = Regex.Matches(result.StdOut!, TestApplicationArgsPattern)
+ .Select(match => match.Value.Split(TestApplicationArgsSeparator)[0])
+ .ToList();
+
+ string expectedProjectPath = $"{testInstance.TestRoot}{Path.DirectorySeparatorChar}TestProject{Path.DirectorySeparatorChar}TestProject.csproj";
+ string otherExpectedProjectPath = $"{testInstance.TestRoot}{Path.DirectorySeparatorChar}OtherTestProject{Path.DirectorySeparatorChar}OtherTestProject.csproj";
+
+ bool containsExpectedPath = testAppArgs.Any(arg => arg.Contains(expectedProjectPath) || arg.Contains(otherExpectedProjectPath));
+
+ Assert.True(containsExpectedPath,
+ $"Expected either '{expectedProjectPath}' or '{otherExpectedProjectPath}' to be present in the test application arguments.");
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunWithInvalidProjectExtension_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ string invalidProjectPath = $"TestProject{Path.DirectorySeparatorChar}TestProject.txt";
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ProjectOption.Name, invalidProjectPath,
+ TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ result.StdOut.Should().Contain($"The provided project file has an invalid extension: {invalidProjectPath}.");
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunWithInvalidSolutionExtension_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ string invalidSolutionPath = "TestProjects.txt";
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.SolutionOption.Name, invalidSolutionPath,
+ TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ result.StdOut.Should().Contain($"The provided solution file has an invalid extension: {invalidSolutionPath}.");
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunWithBothProjectAndSolutionAndDirectoryOptions_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ string testProjectPath = $"TestProject{Path.DirectorySeparatorChar}TestProject.csproj";
+ string testSolutionPath = "MultiTestProjectSolutionWithTests.sln";
+ string testDirectoryPath = "TestProjects";
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ProjectOption.Name, testProjectPath,
+ TestingPlatformOptions.SolutionOption.Name, testSolutionPath,
+ TestingPlatformOptions.DirectoryOption.Name, testDirectoryPath,
+ TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ result.StdOut?.Contains("Specify either the project, solution or directory option.");
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunWithBothProjectAndSolutionOptions_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ string testProjectPath = $"TestProject{Path.DirectorySeparatorChar}TestProject.csproj";
+ string testSolutionPath = "MultiTestProjectSolutionWithTests.sln";
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ProjectOption.Name, testProjectPath,
+ TestingPlatformOptions.SolutionOption.Name, testSolutionPath,
+ TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ result.StdOut?.Contains("Specify either the project or solution option.");
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunWithNonExistentProjectPath_ShouldReturnZeroAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ string testProjectPath = $"TestProject{Path.DirectorySeparatorChar}OtherTestProject.csproj";
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.ProjectOption.Name, testProjectPath,
+ TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ string fullProjectPath = $"{testInstance.TestRoot}{Path.DirectorySeparatorChar}{testProjectPath}";
+ result.StdOut?.Contains($"The provided file path does not exist: {fullProjectPath}.");
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunWithNonExistentSolutionPath_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ string solutionPath = "Solution.sln";
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.SolutionOption.Name, solutionPath,
+ TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ string fullSolutionPath = $"{testInstance.TestRoot}{Path.DirectorySeparatorChar}{solutionPath}";
+ result.StdOut.Should().Contain($"The provided file path does not exist: {fullSolutionPath}.");
+
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunWithNonExistentDirectoryPath_ShouldReturnOneAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ string directoryPath = "Directory";
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .Execute(TestingPlatformOptions.DirectoryOption.Name, directoryPath,
+ TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ result.StdOut.Should().Contain($"The provided directory path does not exist: {directoryPath}.");
+ result.ExitCode.Should().Be(ExitCodes.GenericFailure);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunTestProjectSolutionWithConfigurationOption_ShouldReturnZeroAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .WithTraceOutput()
+ .Execute(TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ var testAppArgs = Regex.Matches(result.StdOut!, TestApplicationArgsPattern);
+ Assert.Contains($"{TestingPlatformOptions.ConfigurationOption.Name} {configuration}", testAppArgs.FirstOrDefault()?.Value.Split(TestApplicationArgsSeparator)[0]);
+
+ result.ExitCode.Should().Be(ExitCodes.Success);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunTestProjectSolutionWithArchOption_ShouldReturnZeroAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithTests", Guid.NewGuid().ToString())
+ .WithSource();
+ string arch = "x64";
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .WithTraceOutput()
+ .Execute(TestingPlatformOptions.ArchitectureOption.Name, arch,
+ TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ var testAppArgs = Regex.Matches(result.StdOut!, TestApplicationArgsPattern);
+ Assert.Contains($"{TestingPlatformOptions.ArchitectureOption.Name} {arch}", testAppArgs.FirstOrDefault()?.Value.Split(TestApplicationArgsSeparator)[0]);
+
+ result.ExitCode.Should().Be(ExitCodes.Success);
+ }
+
+ [InlineData(Constants.Debug)]
+ [InlineData(Constants.Release)]
+ [Theory]
+ public void RunSpecificCSProjRunsWithNoBuildAndNoRestoreOptions_ShouldReturnZeroAsExitCode(string configuration)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithTests", Guid.NewGuid().ToString()).WithSource();
+
+ new BuildCommand(testInstance)
+ .Execute($"/p:Configuration={configuration}")
+ .Should().Pass();
+
+ var binDirectory = new FileInfo($"{testInstance.Path}{Path.DirectorySeparatorChar}bin").Directory;
+ var binDirectoryLastWriteTime = binDirectory?.LastWriteTime;
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .WithTraceOutput()
+ .Execute(TestingPlatformOptions.ProjectOption.Name, @"TestProject.csproj",
+ TestingPlatformOptions.ConfigurationOption.Name, configuration);
+
+ // Assert that the bin folder hasn't been modified
+ Assert.Equal(binDirectoryLastWriteTime, binDirectory?.LastWriteTime);
+
+ var testAppArgs = Regex.Matches(result.StdOut!, TestApplicationArgsPattern);
+ Assert.Contains($"{TestingPlatformOptions.NoRestoreOption.Name} {TestingPlatformOptions.NoBuildOption.Name}", testAppArgs.FirstOrDefault()?.Value.Split(TestApplicationArgsSeparator)[0]);
+
+ result.ExitCode.Should().Be(ExitCodes.Success);
+ }
+ }
+}
diff --git a/test/dotnet-test.Tests/GivenDotnetTestsRunsWithDifferentCultures.cs b/test/dotnet-test.Tests/GivenDotnetTestsRunsWithDifferentCultures.cs
new file mode 100644
index 000000000000..d12ebf18edf1
--- /dev/null
+++ b/test/dotnet-test.Tests/GivenDotnetTestsRunsWithDifferentCultures.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using CommandResult = Microsoft.DotNet.Cli.Utils.CommandResult;
+
+namespace Microsoft.DotNet.Cli.Test.Tests;
+
+public class GivenDotnetTestsRunsWithDifferentCultures : SdkTest
+{
+ public GivenDotnetTestsRunsWithDifferentCultures(ITestOutputHelper log) : base(log)
+ {
+ }
+
+ [InlineData("en-US")]
+ [InlineData("de-DE")]
+ [Theory]
+ public void CanRunTestsAgainstProjectInLocale(string locale)
+ {
+ TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithTests", Guid.NewGuid().ToString()).WithSource();
+
+ CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
+ .WithWorkingDirectory(testInstance.Path)
+ .WithEnableTestingPlatform()
+ .WithCulture(locale)
+ .Execute();
+
+ result.ExitCode.Should().Be(0);
+ }
+}
diff --git a/test/dotnet-test.Tests/MSBuildHandlerTests.cs b/test/dotnet-test.Tests/MSBuildHandlerTests.cs
new file mode 100644
index 000000000000..c13144c83357
--- /dev/null
+++ b/test/dotnet-test.Tests/MSBuildHandlerTests.cs
@@ -0,0 +1,111 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.DotNet.Cli.Test.Tests
+{
+ public class MSBuildHandlerTests
+ {
+ [Fact]
+ public void IsBinaryLoggerEnabled_ShouldReturnTrue_WhenBinaryLoggerArgumentIsPresent()
+ {
+ // Arrange
+ var args = new List { "--binaryLogger" };
+
+ // Act
+ var result = MSBuildHandler.IsBinaryLoggerEnabled(args, out string binLogFileName);
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal(CliConstants.BinLogFileName, binLogFileName);
+ }
+
+ [Fact]
+ public void IsBinaryLoggerEnabled_ShouldReturnTrue_WhenBinaryLoggerArgumentWithFileNameIsPresent()
+ {
+ // Arrange
+ var args = new List { "--binaryLogger:custom.binlog" };
+
+ // Act
+ var result = MSBuildHandler.IsBinaryLoggerEnabled(args, out string binLogFileName);
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal("custom.binlog", binLogFileName);
+ }
+
+ [Fact]
+ public void IsBinaryLoggerEnabled_ShouldReturnFalse_WhenBinaryLoggerArgumentIsNotPresent()
+ {
+ // Arrange
+ var args = new List { "--someOtherArg" };
+
+ // Act
+ var result = MSBuildHandler.IsBinaryLoggerEnabled(args, out string binLogFileName);
+
+ // Assert
+ Assert.False(result);
+ Assert.Equal(string.Empty, binLogFileName);
+ }
+
+ [Fact]
+ public void IsBinaryLoggerEnabled_ShouldRemoveBinaryLoggerArgumentsFromArgs()
+ {
+ // Arrange
+ var args = new List { "--binaryLogger", "--someOtherArg" };
+
+ // Act
+ var result = MSBuildHandler.IsBinaryLoggerEnabled(args, out string binLogFileName);
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal(CliConstants.BinLogFileName, binLogFileName);
+ Assert.DoesNotContain("--binaryLogger", args);
+ }
+
+ [Fact]
+ public void IsBinaryLoggerEnabled_ShouldHandleMultipleBinaryLoggerArguments()
+ {
+ // Arrange
+ var args = new List { "--binaryLogger", "--binaryLogger:custom1.binlog", "--binaryLogger:custom2.binlog" };
+
+ // Act
+ var result = MSBuildHandler.IsBinaryLoggerEnabled(args, out string binLogFileName);
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal("custom2.binlog", binLogFileName);
+ Assert.DoesNotContain("--binaryLogger", args);
+ Assert.DoesNotContain("--binaryLogger:custom1.binlog", args);
+ Assert.DoesNotContain("--binaryLogger:custom2.binlog", args);
+ }
+
+ [Fact]
+ public void IsBinaryLoggerEnabled_ShouldHandleInvalidBinaryLoggerArgumentFormat()
+ {
+ // Arrange
+ var args = new List { "--binaryLogger:" };
+
+ // Act
+ var result = MSBuildHandler.IsBinaryLoggerEnabled(args, out string binLogFileName);
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal(CliConstants.BinLogFileName, binLogFileName);
+ Assert.DoesNotContain("--binaryLogger:", args);
+ }
+
+ [Fact]
+ public void IsBinaryLoggerEnabled_ShouldHandleEmptyBinaryLoggerFilename()
+ {
+ // Arrange
+ var args = new List { "--binaryLogger:" };
+
+ // Act
+ var result = MSBuildHandler.IsBinaryLoggerEnabled(args, out string binLogFileName);
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal(CliConstants.BinLogFileName, binLogFileName);
+ }
+ }
+}
diff --git a/test/dotnet.Tests/Constants.cs b/test/dotnet.Tests/Constants.cs
new file mode 100644
index 000000000000..0617ee3af3cd
--- /dev/null
+++ b/test/dotnet.Tests/Constants.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace dotnet.Tests
+{
+ internal static class Constants
+ {
+ public const string Debug = "Debug";
+ public const string Release = "Release";
+ }
+}
diff --git a/test/dotnet.Tests/dotnet.Tests.csproj b/test/dotnet.Tests/dotnet.Tests.csproj
index 4bb8bfdc1df5..fe1345dfcd2d 100644
--- a/test/dotnet.Tests/dotnet.Tests.csproj
+++ b/test/dotnet.Tests/dotnet.Tests.csproj
@@ -56,6 +56,8 @@
+
+
PreserveNewest