From ae668512e1fdef167a9f8f76f86a391ff68f8ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 18 Jan 2024 14:22:26 +0100 Subject: [PATCH 1/2] Terminal logger fixes --- .../Tasks/VSTestTask2.cs | 17 ++- src/vstest.console/Internal/MSBuildLogger.cs | 127 ++++++++++-------- .../DotnetTestMSBuildOutputTests.cs | 2 + 3 files changed, 91 insertions(+), 55 deletions(-) diff --git a/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask2.cs b/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask2.cs index d99c3e89d0..3a550968ad 100644 --- a/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask2.cs +++ b/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask2.cs @@ -40,6 +40,8 @@ public class VSTestTask2 : ToolTask, ITestTask public string? VSTestArtifactsProcessingMode { get; set; } public string? VSTestSessionCorrelationId { get; set; } + private readonly string _testResultSplitter = "++++"; + private readonly string[] _testResultSplitterArray = new[] { "++++" }; private readonly string _errorSplitter = "||||"; private readonly string[] _errorSplitterArray = new[] { "||||" }; @@ -91,8 +93,7 @@ protected override void LogEventsFromTextOutput(string singleLine, MessageImport return; } } - - if (singleLine.StartsWith(_fullErrorSplitter)) + else if (singleLine.StartsWith(_fullErrorSplitter)) { var parts = singleLine.Split(_fullErrorSplitterArray, StringSplitOptions.None); if (parts.Length > 1) @@ -119,6 +120,18 @@ protected override void LogEventsFromTextOutput(string singleLine, MessageImport return; } } + else if (singleLine.StartsWith(_testResultSplitter)) + { + var parts = singleLine.Split(_testResultSplitterArray, StringSplitOptions.None); + if (parts.Length == 3) + { + var outcome = parts[1]; + var testName = parts[2]; + + Log.LogMessage(MessageImportance.Low, $"{outcome} {testName}"); + return; + } + } base.LogEventsFromTextOutput(singleLine, messageImportance); } diff --git a/src/vstest.console/Internal/MSBuildLogger.cs b/src/vstest.console/Internal/MSBuildLogger.cs index 9eb5a820af..421c5356c4 100644 --- a/src/vstest.console/Internal/MSBuildLogger.cs +++ b/src/vstest.console/Internal/MSBuildLogger.cs @@ -18,7 +18,6 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; -// Not using FriendlyName because it [ExtensionUri(ExtensionUri)] [FriendlyName(FriendlyName)] internal class MSBuildLogger : ITestLoggerWithParameters @@ -100,72 +99,83 @@ private void TestResultHandler(object? sender, TestResultEventArgs e) TPDebug.Assert(Output != null, "Initialize should have been called."); switch (e.Result.Outcome) { + case TestOutcome.Passed: + case TestOutcome.Skipped: + + var test = e.Result.TestCase.DisplayName; + var outcome = e.Result.Outcome == TestOutcome.Passed + ? CommandLineResources.PassedTestIndicator + : CommandLineResources.SkippedTestIndicator; + var info = $"++++{outcome}++++{ReplacePlusSeparator(test)}"; + + Debug.WriteLine(">>>>MESSAGE:" + info); + Output.Information(false, info); + break; case TestOutcome.Failed: + + var result = e.Result; + if (!StringUtils.IsNullOrWhiteSpace(result.ErrorStackTrace)) { - var result = e.Result; - if (!StringUtils.IsNullOrWhiteSpace(result.ErrorStackTrace)) + var maxLength = 1000; + string? error = null; + if (result.ErrorMessage != null) { - var maxLength = 1000; - string? error = null; - if (result.ErrorMessage != null) - { - var oneLineMessage = result.ErrorMessage.Replace(Environment.NewLine, " "); - error = oneLineMessage.Length > maxLength ? oneLineMessage.Substring(0, maxLength) : oneLineMessage; - } + // Do not use environment.newline here, we want to replace also \n on Windows. + var oneLineMessage = result.ErrorMessage.Replace("\n", " ").Replace("\r", " "); + error = oneLineMessage.Length > maxLength ? oneLineMessage.Substring(0, maxLength) : oneLineMessage; + } - string? stackFrame = null; - var stackFrames = Regex.Split(result.ErrorStackTrace, Environment.NewLine); - string? line = null; - string? file = null; - string? place = null; - if (stackFrames.Length > 0) + string? stackFrame = null; + var stackFrames = Regex.Split(result.ErrorStackTrace, Environment.NewLine); + string? line = null; + string? file = null; + string? place = null; + if (stackFrames.Length > 0) + { + foreach (var frame in stackFrames.Take(20)) { - foreach (var frame in stackFrames.Take(20)) + if (TryGetStackFrameLocation(frame, out line, out file, out place)) { - if (TryGetStackFrameLocation(frame, out line, out file, out place)) - { - break; - } + break; } } + } - // We did not find any stack frame with location in the first 20 frames. - // Try getting location of the test. - if (file == null) + // We did not find any stack frame with location in the first 20 frames. + // Try getting location of the test. + if (file == null) + { + if (!StringUtils.IsNullOrEmpty(result.TestCase.CodeFilePath)) { - if (!StringUtils.IsNullOrEmpty(result.TestCase.CodeFilePath)) - { - // if there are no symbols but we collect source info, us the source info. - file = result.TestCase.CodeFilePath; - line = result.TestCase.LineNumber > 0 ? result.TestCase.LineNumber.ToString(CultureInfo.InvariantCulture) : null; - place = stackFrame; - } - else - { - // if there are no symbols and no source info use the dll - place = result.TestCase.DisplayName; - file = result.TestCase.Source; - } + // if there are no symbols but we collect source info, us the source info. + file = result.TestCase.CodeFilePath; + line = result.TestCase.LineNumber > 0 ? result.TestCase.LineNumber.ToString(CultureInfo.InvariantCulture) : null; + place = stackFrame; + } + else + { + // if there are no symbols and no source info use the dll + place = result.TestCase.DisplayName; + file = result.TestCase.Source; } - - place = $"({result.TestCase.DisplayName}) {place}"; - var message = $"||||{ReplacePipeSeparator(file)}||||{line}||||{ReplacePipeSeparator(place)}||||{ReplacePipeSeparator(error)}"; - - Trace.WriteLine(">>>>MESSAGE:" + message); - Output.Error(false, message); - - var fullError = $"~~~~{ReplaceTildaSeparator(result.ErrorMessage)}~~~~{ReplaceTildaSeparator(result.ErrorStackTrace)}"; - Output.Information(false, fullError); - return; - } - else - { - Output.Error(false, result.DisplayName?.Replace(Environment.NewLine, " ") ?? string.Empty); } + place = $"({result.TestCase.DisplayName}) {place}"; + var message = $"||||{ReplacePipeSeparator(file)}||||{line}||||{ReplacePipeSeparator(place)}||||{ReplacePipeSeparator(error)}"; - break; + Debug.WriteLine(">>>>MESSAGE:" + message); + Output.Error(false, message); + + var fullError = $"~~~~{ReplaceTildaSeparator(result.ErrorMessage)}~~~~{ReplaceTildaSeparator(result.ErrorStackTrace)}"; + Output.Information(false, fullError); + return; + } + else + { + Output.Error(false, result.DisplayName?.Replace(Environment.NewLine, " ") ?? string.Empty); } + + break; } } @@ -186,7 +196,7 @@ private static bool TryGetStackFrameLocation(string stackFrame, out string? line line = match.Groups["line"].Value; } - Trace.WriteLine($">>>> {(match.Success ? "MATCH" : "NOMATCH")} {stackFrame}"); + Debug.WriteLine($">>>> {(match.Success ? "MATCH" : "NOMATCH")} {stackFrame}"); return match.Success; } @@ -203,6 +213,17 @@ private static bool TryGetStackFrameLocation(string stackFrame, out string? line return text.Replace("||||", "____"); } + private static string? ReplacePlusSeparator(string? text) + { + if (text == null) + { + return null; + } + + // Remove any occurrence of message splitter. + return text.Replace("++++", "____"); + } + private static string? ReplaceTildaSeparator(string? text) { if (text == null) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs index 831356bd73..f4d5e5e404 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs @@ -32,6 +32,8 @@ public void RunDotnetTestWithCsproj(RunnerInfo runnerInfo) // C:\Users\nohwnd\AppData\Local\Temp\vstest\xvoVt\UnitTest1.cs(41): error VSTEST1: (FailingTest) SampleUnitTestProject.UnitTest1.FailingTest() Assert.AreEqual failed. Expected:<2>. Actual:<3>. [C:\Users\nohwnd\AppData\Local\Temp\vstest\xvoVt\SimpleTestProject.csproj::TargetFramework=netcoreapp3.1] StdOutputContains("error VSTEST1: (FailingTest) SampleUnitTestProject.UnitTest1.FailingTest() Assert.AreEqual failed. Expected:<2>. Actual:<3>."); + StdOutputContains("passed PassingTest"); + StdOutputContains("skipped SkippingTest"); ExitCodeEquals(1); } } From 78d49d7193a4bd35e4cfe009318e3cb5d59686ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Fri, 19 Jan 2024 10:08:37 +0100 Subject: [PATCH 2/2] Fix test --- .../DotnetTestMSBuildOutputTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs index f4d5e5e404..03abefbcc6 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs @@ -32,8 +32,9 @@ public void RunDotnetTestWithCsproj(RunnerInfo runnerInfo) // C:\Users\nohwnd\AppData\Local\Temp\vstest\xvoVt\UnitTest1.cs(41): error VSTEST1: (FailingTest) SampleUnitTestProject.UnitTest1.FailingTest() Assert.AreEqual failed. Expected:<2>. Actual:<3>. [C:\Users\nohwnd\AppData\Local\Temp\vstest\xvoVt\SimpleTestProject.csproj::TargetFramework=netcoreapp3.1] StdOutputContains("error VSTEST1: (FailingTest) SampleUnitTestProject.UnitTest1.FailingTest() Assert.AreEqual failed. Expected:<2>. Actual:<3>."); - StdOutputContains("passed PassingTest"); - StdOutputContains("skipped SkippingTest"); + // We are sending those as low prio messages, they won't show up on screen but will be in binlog. + //StdOutputContains("passed PassingTest"); + //StdOutputContains("skipped SkippingTest"); ExitCodeEquals(1); } }