diff --git a/Directory.Packages.props b/Directory.Packages.props index 37f12c935a34..56cc3862dd6e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -63,7 +63,7 @@ - + diff --git a/eng/Versions.props b/eng/Versions.props index f3b1c74614e4..d550850f2213 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -28,7 +28,6 @@ true 6.0.1 true - 1.6.0-preview.25056.11 30 @@ -161,6 +160,10 @@ 17.13.0-preview-25055-01 17.13.0-preview-25055-01 + + + 1.5.0-preview.24604.7 + 10.0.0-preview.24629.1 diff --git a/src/Cli/dotnet/commands/dotnet-test/BuiltInOptions.cs b/src/Cli/dotnet/commands/dotnet-test/BuiltInOptions.cs index f3bc4c1419c5..0462c6c7486d 100644 --- a/src/Cli/dotnet/commands/dotnet-test/BuiltInOptions.cs +++ b/src/Cli/dotnet/commands/dotnet-test/BuiltInOptions.cs @@ -3,5 +3,5 @@ namespace Microsoft.DotNet.Cli { - internal record BuiltInOptions(bool HasNoRestore, bool HasNoBuild, string Configuration, string Architecture); + internal record BuiltInOptions(bool HasNoRestore, bool HasNoBuild, bool HasListTests, string Configuration, string Architecture); } diff --git a/src/Cli/dotnet/commands/dotnet-test/IPC/Models/TestResultMessages.cs b/src/Cli/dotnet/commands/dotnet-test/IPC/Models/TestResultMessages.cs index 5882717301ff..af9ee626604c 100644 --- a/src/Cli/dotnet/commands/dotnet-test/IPC/Models/TestResultMessages.cs +++ b/src/Cli/dotnet/commands/dotnet-test/IPC/Models/TestResultMessages.cs @@ -5,7 +5,9 @@ namespace Microsoft.DotNet.Tools.Test { internal sealed record SuccessfulTestResultMessage(string? Uid, string? DisplayName, byte? State, long? Duration, string? Reason, string? StandardOutput, string? ErrorOutput, string? SessionUid); - internal sealed record FailedTestResultMessage(string? Uid, string? DisplayName, byte? State, long? Duration, string? Reason, string? ErrorMessage, string? ErrorStackTrace, string? StandardOutput, string? ErrorOutput, string? SessionUid); + internal sealed record FailedTestResultMessage(string? Uid, string? DisplayName, byte? State, long? Duration, string? Reason, ExceptionMessage[]? Exceptions, string? StandardOutput, string? ErrorOutput, string? SessionUid); + + internal sealed record ExceptionMessage(string? ErrorMessage, string? ErrorType, string? StackTrace); internal sealed record TestResultMessages(string? ExecutionId, SuccessfulTestResultMessage[] SuccessfulTestMessages, FailedTestResultMessage[] FailedTestMessages) : IRequest; } diff --git a/src/Cli/dotnet/commands/dotnet-test/IPC/ObjectFieldIds.cs b/src/Cli/dotnet/commands/dotnet-test/IPC/ObjectFieldIds.cs index e7f720c96fbb..01d38f3d9ec2 100644 --- a/src/Cli/dotnet/commands/dotnet-test/IPC/ObjectFieldIds.cs +++ b/src/Cli/dotnet/commands/dotnet-test/IPC/ObjectFieldIds.cs @@ -85,13 +85,19 @@ internal static class FailedTestResultMessageFieldsId public const ushort State = 3; public const ushort Duration = 4; public const ushort Reason = 5; - public const ushort ErrorMessage = 6; - public const ushort ErrorStackTrace = 7; + public const ushort ExceptionMessageList = 6; public const ushort StandardOutput = 8; public const ushort ErrorOutput = 9; public const ushort SessionUid = 10; } + internal static class ExceptionMessageFieldsId + { + public const ushort ErrorMessage = 1; + public const ushort ErrorType = 2; + public const ushort StackTrace = 3; + } + internal static class FileArtifactMessagesFieldsId { public const int MessagesSerializerId = 7; diff --git a/src/Cli/dotnet/commands/dotnet-test/IPC/Serializers/TestResultMessagesSerializer.cs b/src/Cli/dotnet/commands/dotnet-test/IPC/Serializers/TestResultMessagesSerializer.cs index 9726bec16ff7..329c54e23b1c 100644 --- a/src/Cli/dotnet/commands/dotnet-test/IPC/Serializers/TestResultMessagesSerializer.cs +++ b/src/Cli/dotnet/commands/dotnet-test/IPC/Serializers/TestResultMessagesSerializer.cs @@ -217,8 +217,8 @@ private static List ReadFailedTestMessagesPayload(Strea int length = ReadInt(stream); for (int i = 0; i < length; i++) { - string? uid = null, displayName = null, reason = null, sessionUid = null, - errorMessage = null, errorStackTrace = null, standardOutput = null, errorOutput = null; + string? uid = null, displayName = null, reason = null, sessionUid = null, standardOutput = null, errorOutput = null; + List exceptionMessages = []; byte? state = null; long? duration = null; @@ -251,13 +251,44 @@ private static List ReadFailedTestMessagesPayload(Strea reason = ReadStringValue(stream, fieldSize); break; - case FailedTestResultMessageFieldsId.ErrorMessage: - errorMessage = ReadStringValue(stream, fieldSize); - break; + case FailedTestResultMessageFieldsId.ExceptionMessageList: + { + int length2 = ReadInt(stream); + for (int k = 0; k < length2; k++) + { - case FailedTestResultMessageFieldsId.ErrorStackTrace: - errorStackTrace = ReadStringValue(stream, fieldSize); - break; + int fieldCount2 = ReadShort(stream); + + string? errorMessage = null; + string? errorType = null; + string? stackTrace = null; + + for (int l = 0; l < fieldCount2; l++) + { + int fieldId2 = ReadShort(stream); + int fieldSize2 = ReadInt(stream); + + switch (fieldId2) + { + case ExceptionMessageFieldsId.ErrorMessage: + errorMessage = ReadStringValue(stream, fieldSize2); + break; + + case ExceptionMessageFieldsId.ErrorType: + errorType = ReadStringValue(stream, fieldSize2); + break; + + case ExceptionMessageFieldsId.StackTrace: + stackTrace = ReadStringValue(stream, fieldSize2); + break; + } + } + + exceptionMessages.Add(new ExceptionMessage(errorMessage, errorType, stackTrace)); + } + + break; + } case FailedTestResultMessageFieldsId.StandardOutput: standardOutput = ReadStringValue(stream, fieldSize); @@ -277,7 +308,7 @@ private static List ReadFailedTestMessagesPayload(Strea } } - failedTestResultMessages.Add(new FailedTestResultMessage(uid, displayName, state, duration, reason, errorMessage, errorStackTrace, standardOutput, errorOutput, sessionUid)); + failedTestResultMessages.Add(new FailedTestResultMessage(uid, displayName, state, duration, reason, exceptionMessages.ToArray(), standardOutput, errorOutput, sessionUid)); } return failedTestResultMessages; @@ -354,8 +385,7 @@ private static void WriteFailedTestMessagesPayload(Stream stream, FailedTestResu WriteField(stream, FailedTestResultMessageFieldsId.State, failedTestResultMessage.State); WriteField(stream, FailedTestResultMessageFieldsId.Duration, failedTestResultMessage.Duration); WriteField(stream, FailedTestResultMessageFieldsId.Reason, failedTestResultMessage.Reason); - WriteField(stream, FailedTestResultMessageFieldsId.ErrorMessage, failedTestResultMessage.ErrorMessage); - WriteField(stream, FailedTestResultMessageFieldsId.ErrorStackTrace, failedTestResultMessage.ErrorStackTrace); + WriteExceptionMessagesPayload(stream, failedTestResultMessage.Exceptions); WriteField(stream, FailedTestResultMessageFieldsId.StandardOutput, failedTestResultMessage.StandardOutput); WriteField(stream, FailedTestResultMessageFieldsId.ErrorOutput, failedTestResultMessage.ErrorOutput); WriteField(stream, FailedTestResultMessageFieldsId.SessionUid, failedTestResultMessage.SessionUid); @@ -366,6 +396,35 @@ private static void WriteFailedTestMessagesPayload(Stream stream, FailedTestResu WriteAtPosition(stream, (int)(stream.Position - before), before - sizeof(int)); } + private static void WriteExceptionMessagesPayload(Stream stream, ExceptionMessage[]? exceptionMessages) + { + if (exceptionMessages is null || exceptionMessages.Length == 0) + { + return; + } + + WriteShort(stream, FailedTestResultMessageFieldsId.ExceptionMessageList); + + // We will reserve an int (4 bytes) + // so that we fill the size later, once we write the payload + WriteInt(stream, 0); + + long before = stream.Position; + WriteInt(stream, exceptionMessages.Length); + foreach (ExceptionMessage exceptionMessage in exceptionMessages) + { + WriteShort(stream, GetFieldCount(exceptionMessage)); + + WriteField(stream, ExceptionMessageFieldsId.ErrorMessage, exceptionMessage.ErrorMessage); + WriteField(stream, ExceptionMessageFieldsId.ErrorType, exceptionMessage.ErrorType); + WriteField(stream, ExceptionMessageFieldsId.StackTrace, exceptionMessage.StackTrace); + } + + // NOTE: We are able to seek only if we are using a MemoryStream + // thus, the seek operation is fast as we are only changing the value of a property + WriteAtPosition(stream, (int)(stream.Position - before), before - sizeof(int)); + } + private static ushort GetFieldCount(TestResultMessages testResultMessages) => (ushort)((testResultMessages.ExecutionId is null ? 0 : 1) + (IsNullOrEmpty(testResultMessages.SuccessfulTestMessages) ? 0 : 1) + @@ -387,10 +446,14 @@ private static ushort GetFieldCount(FailedTestResultMessage failedTestResultMess (failedTestResultMessage.State is null ? 0 : 1) + (failedTestResultMessage.Duration is null ? 0 : 1) + (failedTestResultMessage.Reason is null ? 0 : 1) + - (failedTestResultMessage.ErrorMessage is null ? 0 : 1) + - (failedTestResultMessage.ErrorStackTrace is null ? 0 : 1) + + (IsNullOrEmpty(failedTestResultMessage.Exceptions) ? 0 : 1) + (failedTestResultMessage.StandardOutput is null ? 0 : 1) + (failedTestResultMessage.ErrorOutput is null ? 0 : 1) + (failedTestResultMessage.SessionUid is null ? 0 : 1)); + + private static ushort GetFieldCount(ExceptionMessage exceptionMessage) => + (ushort)((exceptionMessage.ErrorMessage is null ? 0 : 1) + + (exceptionMessage.ErrorType is null ? 0 : 1) + + (exceptionMessage.StackTrace is null ? 0 : 1)); } } diff --git a/src/Cli/dotnet/commands/dotnet-test/Models.cs b/src/Cli/dotnet/commands/dotnet-test/Models.cs index 8484f819f38d..d022955e8c1c 100644 --- a/src/Cli/dotnet/commands/dotnet-test/Models.cs +++ b/src/Cli/dotnet/commands/dotnet-test/Models.cs @@ -13,7 +13,9 @@ internal sealed record DiscoveredTest(string? Uid, string? DisplayName); internal sealed record SuccessfulTestResult(string? Uid, string? DisplayName, byte? State, long? Duration, string? Reason, string? StandardOutput, string? ErrorOutput, string? SessionUid); - internal sealed record FailedTestResult(string? Uid, string? DisplayName, byte? State, long? Duration, string? Reason, string? ErrorMessage, string? ErrorStackTrace, string? StandardOutput, string? ErrorOutput, string? SessionUid); + internal sealed record FailedTestResult(string? Uid, string? DisplayName, byte? State, long? Duration, string? Reason, FlatException[]? Exceptions, string? StandardOutput, string? ErrorOutput, string? SessionUid); + + internal sealed record FlatException(string? ErrorMessage, string? ErrorType, string? StackTrace); internal sealed record FileArtifact(string? FullPath, string? DisplayName, string? Description, string? TestUid, string? TestDisplayName, string? SessionUid); diff --git a/src/Cli/dotnet/commands/dotnet-test/TestApplication.cs b/src/Cli/dotnet/commands/dotnet-test/TestApplication.cs index c727ffd4fe3b..27d564a4bebd 100644 --- a/src/Cli/dotnet/commands/dotnet-test/TestApplication.cs +++ b/src/Cli/dotnet/commands/dotnet-test/TestApplication.cs @@ -260,6 +260,11 @@ private string BuildArgsWithDotnetRun(bool hasHelp, BuiltInOptions builtInOption builder.Append($" {TestingPlatformOptions.NoBuildOption.Name}"); } + if (builtInOptions.HasListTests) + { + builder.Append($" {TestingPlatformOptions.ListTestsOption.Name}"); + } + if (!string.IsNullOrEmpty(builtInOptions.Architecture)) { builder.Append($" {TestingPlatformOptions.ArchitectureOption.Name} {builtInOptions.Architecture}"); @@ -339,7 +344,7 @@ internal void OnTestResultMessages(TestResultMessages testResultMessage) { ExecutionId = testResultMessage.ExecutionId, SuccessfulTestResults = testResultMessage.SuccessfulTestMessages.Select(message => new SuccessfulTestResult(message.Uid, message.DisplayName, message.State, message.Duration, message.Reason, message.StandardOutput, message.ErrorOutput, message.SessionUid)).ToArray(), - FailedTestResults = testResultMessage.FailedTestMessages.Select(message => new FailedTestResult(message.Uid, message.DisplayName, message.State, message.Duration, message.Reason, message.ErrorMessage, message.ErrorStackTrace, message.StandardOutput, message.ErrorOutput, message.SessionUid)).ToArray() + FailedTestResults = testResultMessage.FailedTestMessages.Select(message => new FailedTestResult(message.Uid, message.DisplayName, message.State, message.Duration, message.Reason, message.Exceptions.Select(e => new FlatException(e.ErrorMessage, e.ErrorType, e.StackTrace)).ToArray(), message.StandardOutput, message.ErrorOutput, message.SessionUid)).ToArray() }); } diff --git a/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs b/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs index 5196981af9ae..c308cd2185fe 100644 --- a/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs +++ b/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs @@ -200,6 +200,7 @@ private static CliCommand GetTestingPlatformCliCommand() command.Options.Add(TestingPlatformOptions.ArchitectureOption); command.Options.Add(TestingPlatformOptions.ConfigurationOption); command.Options.Add(TestingPlatformOptions.ProjectOption); + command.Options.Add(TestingPlatformOptions.ListTestsOption); return command; } diff --git a/src/Cli/dotnet/commands/dotnet-test/TestingPlatformCommand.cs b/src/Cli/dotnet/commands/dotnet-test/TestingPlatformCommand.cs index b947031fdca2..026900ec1e12 100644 --- a/src/Cli/dotnet/commands/dotnet-test/TestingPlatformCommand.cs +++ b/src/Cli/dotnet/commands/dotnet-test/TestingPlatformCommand.cs @@ -1,10 +1,14 @@ // 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; using Microsoft.TemplateEngine.Cli.Commands; +using Microsoft.Testing.Platform.Helpers; +using Microsoft.Testing.Platform.OutputDevice; +using Microsoft.Testing.Platform.OutputDevice.Terminal; namespace Microsoft.DotNet.Cli { @@ -15,9 +19,13 @@ internal partial class TestingPlatformCommand : CliCommand, ICustomHelp private MSBuildConnectionHandler _msBuildConnectionHandler; private TestModulesFilterHandler _testModulesFilterHandler; + private TerminalTestReporter _output; private TestApplicationActionQueue _actionQueue; private Task _namedPipeConnectionLoop; private List _args; + private ConcurrentDictionary _executions = new(); + private byte _cancelled; + private bool _isDiscovery; public TestingPlatformCommand(string name, string description = null) : base(name, description) { @@ -29,6 +37,12 @@ public int Run(ParseResult parseResult) bool hasFailed = false; try { + Console.CancelKeyPress += (s, e) => + { + _output?.StartCancelling(); + CompleteRun(); + }; + // User can decide what the degree of parallelism should be // If not specified, we will default to the number of processors if (!int.TryParse(parseResult.GetValue(TestingPlatformOptions.MaxParallelTestModulesOption), out int degreeOfParallelism)) @@ -41,12 +55,30 @@ public int Run(ParseResult parseResult) VSTestTrace.SafeWriteTrace(() => $"The --arch option is not supported yet."); } + if (parseResult.HasOption(TestingPlatformOptions.ListTestsOption)) + { + _isDiscovery = true; + } + BuiltInOptions builtInOptions = new( parseResult.HasOption(TestingPlatformOptions.NoRestoreOption), parseResult.HasOption(TestingPlatformOptions.NoBuildOption), + parseResult.HasOption(TestingPlatformOptions.ListTestsOption), parseResult.GetValue(TestingPlatformOptions.ConfigurationOption), parseResult.GetValue(TestingPlatformOptions.ArchitectureOption)); + 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; + _output.TestExecutionStarted(DateTimeOffset.Now, degreeOfParallelism, _isDiscovery); + if (ContainsHelpOption(parseResult.GetArguments())) { _actionQueue = new(degreeOfParallelism, async (TestApplication testApp) => @@ -57,7 +89,9 @@ public int Run(ParseResult parseResult) testApp.Run += OnTestApplicationRun; testApp.ExecutionIdReceived += OnExecutionIdReceived; - return await testApp.RunAsync(filterModeEnabled, enableHelp: true, builtInOptions); + var result = await testApp.RunAsync(filterModeEnabled, enableHelp: true, builtInOptions); + CompleteRun(); + return result; }); } else @@ -87,6 +121,7 @@ public int Run(ParseResult parseResult) { if (!_testModulesFilterHandler.RunWithTestModulesFilter(parseResult)) { + CompleteRun(); return ExitCodes.GenericFailure; } } @@ -97,6 +132,7 @@ public int Run(ParseResult parseResult) if (msbuildResult != 0) { VSTestTrace.SafeWriteTrace(() => $"MSBuild task _GetTestsProject didn't execute properly with exit code: {msbuildResult}."); + CompleteRun(); return ExitCodes.GenericFailure; } @@ -104,6 +140,7 @@ public int Run(ParseResult parseResult) if (!_msBuildConnectionHandler.EnqueueTestApplications()) { VSTestTrace.SafeWriteTrace(() => LocalizableStrings.CmdUnsupportedVSTestTestApplicationsDescription); + CompleteRun(); return ExitCodes.GenericFailure; } } @@ -120,9 +157,18 @@ public int Run(ParseResult parseResult) CleanUp(); } + CompleteRun(); return hasFailed ? ExitCodes.GenericFailure : ExitCodes.Success; } + private void CompleteRun() + { + if (Interlocked.CompareExchange(ref _cancelled, 1, 0) == 0) + { + _output?.TestExecutionCompleted(DateTimeOffset.Now); + } + } + private void WaitOnMSBuildHandlerPipeConnectionLoop() { _cancellationToken.Cancel(); @@ -140,6 +186,14 @@ private void CleanUp() private void OnHandshakeReceived(object sender, HandshakeArgs args) { + var testApplication = (TestApplication)sender; + var executionId = args.Handshake.Properties[HandshakeMessagePropertyNames.ExecutionId]; + var arch = args.Handshake.Properties[HandshakeMessagePropertyNames.Architecture]?.ToLower(); + var tfm = TargetFrameworkParser.GetShortTargetFramework(args.Handshake.Properties[HandshakeMessagePropertyNames.Framework]); + (string ModulePath, string TargetFramework, string Architecture, string ExecutionId) appInfo = new(testApplication.Module.DllOrExePath, tfm, arch, executionId); + _executions[testApplication] = appInfo; + _output.AssemblyRunStarted(appInfo.ModulePath, appInfo.TargetFramework, appInfo.Architecture, appInfo.ExecutionId); + if (!VSTestTrace.TraceEnabled) { return; @@ -155,13 +209,22 @@ private void OnHandshakeReceived(object sender, HandshakeArgs args) private void OnDiscoveredTestsReceived(object sender, DiscoveredTestEventArgs args) { + var testApp = (TestApplication)sender; + var appInfo = _executions[testApp]; + + foreach (var test in args.DiscoveredTests) + { + _output.TestDiscovered(appInfo.ModulePath, appInfo.TargetFramework, appInfo.Architecture, appInfo.ExecutionId, + test.DisplayName, + test.Uid); + } + if (!VSTestTrace.TraceEnabled) { return; } var discoveredTestMessages = args.DiscoveredTests; - VSTestTrace.SafeWriteTrace(() => $"DiscoveredTests Execution Id: {args.ExecutionId}"); foreach (DiscoveredTest discoveredTestMessage in discoveredTestMessages) { @@ -171,6 +234,40 @@ private void OnDiscoveredTestsReceived(object sender, DiscoveredTestEventArgs ar private void OnTestResultsReceived(object sender, TestResultEventArgs args) { + foreach (var testResult in args.SuccessfulTestResults) + { + var testApp = (TestApplication)sender; + var appInfo = _executions[testApp]; + _output.TestCompleted(appInfo.ModulePath, appInfo.TargetFramework, appInfo.Architecture, appInfo.ExecutionId, + testResult.Uid, + testResult.DisplayName, + ToOutcome(testResult.State), + TimeSpan.FromTicks(testResult.Duration ?? 0), + exceptions: null, + expected: null, + actual: null, + standardOutput: null, + errorOutput: null); + } + + foreach (var testResult in args.FailedTestResults) + { + var testApp = (TestApplication)sender; + // TODO: expected + // TODO: actual + var appInfo = _executions[testApp]; + _output.TestCompleted(appInfo.ModulePath, appInfo.TargetFramework, appInfo.Architecture, appInfo.ExecutionId, + testResult.Uid, + testResult.DisplayName, + ToOutcome(testResult.State), + TimeSpan.FromTicks(testResult.Duration ?? 0), + exceptions: testResult.Exceptions.Select(fe => new Microsoft.Testing.Platform.OutputDevice.Terminal.FlatException(fe.ErrorMessage, fe.ErrorType, fe.StackTrace)).ToArray(), + expected: null, + actual: null, + standardOutput: null, + errorOutput: null); + } + if (!VSTestTrace.TraceEnabled) { return; @@ -188,13 +285,39 @@ private void OnTestResultsReceived(object sender, TestResultEventArgs args) foreach (FailedTestResult failedTestResult in args.FailedTestResults) { VSTestTrace.SafeWriteTrace(() => $"FailedTestResult: {failedTestResult.Uid}, {failedTestResult.DisplayName}, " + - $"{failedTestResult.State}, {failedTestResult.Duration}, {failedTestResult.Reason}, {failedTestResult.ErrorMessage}," + - $"{failedTestResult.ErrorStackTrace}, {failedTestResult.StandardOutput}, {failedTestResult.ErrorOutput}, {failedTestResult.SessionUid}"); + $"{failedTestResult.State}, {failedTestResult.Duration}, {failedTestResult.Reason}, {string.Join(", ", failedTestResult.Exceptions?.Select(e => $"{e.ErrorMessage}, {e.ErrorType}, {e.StackTrace}"))}" + + $"{failedTestResult.StandardOutput}, {failedTestResult.ErrorOutput}, {failedTestResult.SessionUid}"); } } + public static TestOutcome ToOutcome(byte? testState) + { + return testState switch + { + TestStates.Passed => TestOutcome.Passed, + TestStates.Skipped => TestOutcome.Skipped, + TestStates.Failed => TestOutcome.Fail, + TestStates.Error => TestOutcome.Error, + TestStates.Timeout => TestOutcome.Timeout, + TestStates.Cancelled => TestOutcome.Canceled, + _ => throw new ArgumentOutOfRangeException(nameof(testState), $"Invalid test state value {testState}") + }; + } + private void OnFileArtifactsReceived(object sender, FileArtifactEventArgs args) { + var testApp = (TestApplication)sender; + var appInfo = _executions[testApp]; + + foreach (var artifact in args.FileArtifacts) + { + // TODO: Is artifact out of process + _output.ArtifactAdded( + outOfProcess: false, + appInfo.ModulePath, appInfo.TargetFramework, appInfo.Architecture, appInfo.ExecutionId, + artifact.TestDisplayName, artifact.FullPath); + } + if (!VSTestTrace.TraceEnabled) { return; @@ -233,6 +356,15 @@ private void OnErrorReceived(object sender, ErrorEventArgs args) private void OnTestProcessExited(object sender, TestProcessExitEventArgs args) { + var testApplication = (TestApplication)sender; + + // If the application exits too early we might not start the execution, + // e.g. if the parameter is incorrect. + if (_executions.TryGetValue(testApplication, out var appInfo)) + { + _output.AssemblyRunCompleted(appInfo.ModulePath, appInfo.TargetFramework, appInfo.Architecture, appInfo.ExecutionId, args.ExitCode, string.Join(Environment.NewLine, args.OutputData), string.Join(Environment.NewLine, args.ErrorData)); + } + if (!VSTestTrace.TraceEnabled) { return; diff --git a/src/Cli/dotnet/commands/dotnet-test/TestingPlatformOptions.cs b/src/Cli/dotnet/commands/dotnet-test/TestingPlatformOptions.cs index f894c4cab66f..cd0a128146ce 100644 --- a/src/Cli/dotnet/commands/dotnet-test/TestingPlatformOptions.cs +++ b/src/Cli/dotnet/commands/dotnet-test/TestingPlatformOptions.cs @@ -57,5 +57,11 @@ internal static class TestingPlatformOptions Description = LocalizableStrings.CmdProjectDescription, Arity = ArgumentArity.ExactlyOne }; + + public static readonly CliOption ListTestsOption = new("--list-tests") + { + Description = LocalizableStrings.CmdListTestsDescription, + Arity = ArgumentArity.Zero + }; } } diff --git a/src/Cli/dotnet/dotnet.csproj b/src/Cli/dotnet/dotnet.csproj index 82db3621e7fc..f19ef88ebd53 100644 --- a/src/Cli/dotnet/dotnet.csproj +++ b/src/Cli/dotnet/dotnet.csproj @@ -100,6 +100,7 @@ +