diff --git a/Directory.Packages.props b/Directory.Packages.props index f1bab54f5f..4b44ce9907 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -58,6 +58,8 @@ + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index ee90b9bb5b..c98b566633 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -13,17 +13,17 @@ https://github.com/dotnet/arcade d7f6456959c71512dd68e03021e0aadd8e27d025 - + https://dev.azure.com/devdiv/DevDiv/_git/vs-code-coverage - aaf54e61f7eebd65bb437a4a9643bf7976311f2b + 43d6af3682f80c1fb77f0c3a7223c84bd49f7708 - + https://github.com/microsoft/testanywhere - 2e0c875aad0f0fbf9a99332da2cc00f6b33e30e5 + 08fc3f8b5646702164ea2cb975988d8653ce37ae - + https://github.com/microsoft/testanywhere - 2e0c875aad0f0fbf9a99332da2cc00f6b33e30e5 + 08fc3f8b5646702164ea2cb975988d8653ce37ae diff --git a/eng/Versions.props b/eng/Versions.props index f8f9523f4c..8f06427ad7 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -1,16 +1,16 @@ - 3.6.1 + 3.6.2 - 1.4.1 + 1.4.2 preview 10.0.0-beta.24429.6 - 17.12.5 + 17.12.6 - 1.4.1 - 1.0.0-alpha.24503.4 + 1.4.2 + 1.0.0-alpha.24530.4 diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TestRunCancellationToken.cs b/src/Adapter/MSTest.TestAdapter/Execution/TestRunCancellationToken.cs index eebf71446c..7faaabe606 100644 --- a/src/Adapter/MSTest.TestAdapter/Execution/TestRunCancellationToken.cs +++ b/src/Adapter/MSTest.TestAdapter/Execution/TestRunCancellationToken.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Collections.Concurrent; + namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; /// @@ -10,8 +12,9 @@ public class TestRunCancellationToken { /// /// Callbacks to be invoked when canceled. + /// Needs to be a concurrent collection, see https://github.com/microsoft/testfx/issues/3953. /// - private readonly List _registeredCallbacks = new(); + private readonly ConcurrentBag _registeredCallbacks = new(); /// /// Stores whether the test run is canceled or not. @@ -63,7 +66,17 @@ private set /// /// Unregister the callback method. /// - public void Unregister() => _registeredCallbacks.Clear(); + public void Unregister() +#if NETCOREAPP || WINDOWS_UWP + => _registeredCallbacks.Clear(); +#else + { + while (!_registeredCallbacks.IsEmpty) + { + _ = _registeredCallbacks.TryTake(out _); + } + } +#endif internal void ThrowIfCancellationRequested() { diff --git a/src/Adapter/MSTest.TestAdapter/build/net/MSTest.TestAdapter.targets b/src/Adapter/MSTest.TestAdapter/build/net/MSTest.TestAdapter.targets index 05548ac62e..f3ef87d399 100644 --- a/src/Adapter/MSTest.TestAdapter/build/net/MSTest.TestAdapter.targets +++ b/src/Adapter/MSTest.TestAdapter/build/net/MSTest.TestAdapter.targets @@ -73,18 +73,18 @@ - - %(CurrentUICultureHierarchy.Identity) - + - + + %(CurrentUICultureHierarchy.Identity) + - - $(CurrentUICultureHierarchy)\%(FileName).resources.dll + + + %(MSTestV2Files.UICulture)\%(FileName).dll PreserveNewest %(FullPath) False diff --git a/src/Adapter/MSTest.TestAdapter/build/netfx-netcore-netstandard/MSTest.TestAdapter.targets b/src/Adapter/MSTest.TestAdapter/build/netfx-netcore-netstandard/MSTest.TestAdapter.targets index 106ac5a5da..64fb79bf30 100644 --- a/src/Adapter/MSTest.TestAdapter/build/netfx-netcore-netstandard/MSTest.TestAdapter.targets +++ b/src/Adapter/MSTest.TestAdapter/build/netfx-netcore-netstandard/MSTest.TestAdapter.targets @@ -66,14 +66,20 @@ + + - - %(CurrentUICultureHierarchy.Identity) - + + %(CurrentUICultureHierarchy.Identity) + + - - %(MSTestV2ResourceFiles.CultureString)\%(Filename)%(Extension) + + + + %(MSTestV2Files.UICulture)\%(FileName).dll PreserveNewest + %(FullPath) False diff --git a/src/Adapter/MSTest.TestAdapter/build/uwp/MSTest.TestAdapter.targets b/src/Adapter/MSTest.TestAdapter/build/uwp/MSTest.TestAdapter.targets index d8baf21f26..c33b561416 100644 --- a/src/Adapter/MSTest.TestAdapter/build/uwp/MSTest.TestAdapter.targets +++ b/src/Adapter/MSTest.TestAdapter/build/uwp/MSTest.TestAdapter.targets @@ -29,19 +29,20 @@ - - %(CurrentUICultureHierarchy.Identity) - - + - - + + %(CurrentUICultureHierarchy.Identity) + + + %(CurrentUICultureHierarchy.Identity) + - - $(CurrentUICultureHierarchy)\%(FileName).resources.dll + + + %(MSTestV2Files.UICulture)\%(FileName).dll PreserveNewest %(FullPath) False diff --git a/src/Adapter/MSTestAdapter.PlatformServices/AssemblyResolver.cs b/src/Adapter/MSTestAdapter.PlatformServices/AssemblyResolver.cs index 06d9a359a4..53a2a392b1 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/AssemblyResolver.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/AssemblyResolver.cs @@ -376,7 +376,15 @@ protected virtual // the ResourceHelper's currentlyLoading stack to null if an exception occurs. if (s_currentlyLoading != null && s_currentlyLoading.Count > 0 && s_currentlyLoading.LastIndexOf(assemblyPath) != -1) { - EqtTrace.Info("MSTest.AssemblyResolver.OnResolve: Assembly '{0}' is searching for itself recursively '{1}', returning as not found.", name, assemblyPath); + SafeLog( + name, + () => + { + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("MSTest.AssemblyResolver.OnResolve: Assembly '{0}' is searching for itself recursively '{1}', returning as not found.", name, assemblyPath); + } + }); _resolvedAssemblies[name] = null; return null; } diff --git a/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs index 115600daca..c92572ebd9 100644 --- a/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/DynamicDataShouldBeValidAnalyzer.cs @@ -220,23 +220,29 @@ private static void AnalyzeDataSource(SymbolAnalysisContext context, AttributeDa } // Validate member return type. - if (member.GetMemberType() is not INamedTypeSymbol memberType) + ITypeSymbol? memberTypeSymbol = member.GetMemberType(); + if (memberTypeSymbol is INamedTypeSymbol memberNamedType) { - return; - } + if (!SymbolEqualityComparer.Default.Equals(memberNamedType.ConstructedFrom, ienumerableTypeSymbol) + || memberNamedType.TypeArguments.Length != 1) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberTypeRule, declaringType.Name, memberName)); + return; + } - if (!SymbolEqualityComparer.Default.Equals(memberType.ConstructedFrom, ienumerableTypeSymbol) - || memberType.TypeArguments.Length != 1) - { - context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberTypeRule, declaringType.Name, memberName)); - return; + ITypeSymbol collectionBoundType = memberNamedType.TypeArguments[0]; + if (!collectionBoundType.Inherits(itupleTypeSymbol) + && collectionBoundType is not IArrayTypeSymbol) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberTypeRule, declaringType.Name, memberName)); + } } - - ITypeSymbol collectionBoundType = memberType.TypeArguments[0]; - if (!collectionBoundType.Inherits(itupleTypeSymbol) - && (collectionBoundType is not IArrayTypeSymbol arrayTypeSymbol || arrayTypeSymbol.ElementType.SpecialType != SpecialType.System_Object)) + else if (memberTypeSymbol is IArrayTypeSymbol arrayType) { - context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberTypeRule, declaringType.Name, memberName)); + if (arrayType.ElementType is not IArrayTypeSymbol) + { + context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(MemberTypeRule, declaringType.Name, memberName)); + } } } diff --git a/src/Analyzers/MSTest.Analyzers/TypeContainingTestMethodShouldBeATestClassAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/TypeContainingTestMethodShouldBeATestClassAnalyzer.cs index f89aa288f8..da57e4d3cc 100644 --- a/src/Analyzers/MSTest.Analyzers/TypeContainingTestMethodShouldBeATestClassAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/TypeContainingTestMethodShouldBeATestClassAnalyzer.cs @@ -77,7 +77,7 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbo bool hasTestMethod = false; INamedTypeSymbol? currentType = namedTypeSymbol; - do + while (currentType is not null && !hasTestMethod) { foreach (ISymbol classMember in currentType.GetMembers()) { @@ -94,16 +94,10 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbo break; } } - - if (!hasTestMethod) - { - break; - } } currentType = currentType.BaseType; } - while (currentType is not null && !hasTestMethod); if (!hasTestMethod) { diff --git a/src/Platform/Microsoft.Testing.Platform.MSBuild/buildMultiTargeting/Microsoft.Testing.Platform.MSBuild.targets b/src/Platform/Microsoft.Testing.Platform.MSBuild/buildMultiTargeting/Microsoft.Testing.Platform.MSBuild.targets index 37c0a13b02..a8ff84d21d 100644 --- a/src/Platform/Microsoft.Testing.Platform.MSBuild/buildMultiTargeting/Microsoft.Testing.Platform.MSBuild.targets +++ b/src/Platform/Microsoft.Testing.Platform.MSBuild/buildMultiTargeting/Microsoft.Testing.Platform.MSBuild.targets @@ -330,4 +330,18 @@ + + + + + + + diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs index c61803c262..0b1de6c275 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs @@ -373,7 +373,7 @@ internal void TestCompleted( TestOutcome outcome, TimeSpan duration, string? errorMessage, - string? errorStackTrace, + Exception? exception, string? expected, string? actual) { @@ -410,7 +410,7 @@ internal void TestCompleted( outcome, duration, errorMessage, - errorStackTrace, + exception, expected, actual)); } @@ -425,7 +425,7 @@ internal void TestCompleted( TestOutcome outcome, TimeSpan duration, string? errorMessage, - string? errorStackTrace, + Exception? exception, string? expected, string? actual) { @@ -469,9 +469,10 @@ internal void TestCompleted( terminal.AppendLine(); - FormatErrorMessage(terminal, errorMessage); + FormatErrorMessage(terminal, errorMessage, exception, outcome); FormatExpectedAndActual(terminal, expected, actual); - FormatStackTrace(terminal, errorStackTrace); + FormatStackTrace(terminal, exception); + FormatInnerExceptions(terminal, exception); } private static void AppendAssemblyLinkTargetFrameworkAndArchitecture(ITerminal terminal, string assembly, string? targetFramework, string? architecture) @@ -495,25 +496,16 @@ private static void AppendAssemblyLinkTargetFrameworkAndArchitecture(ITerminal t } } - private static void FormatStackTrace(ITerminal terminal, string? errorStackTrace) + private static void FormatStackTrace(ITerminal terminal, Exception? exception) { - if (RoslynString.IsNullOrWhiteSpace(errorStackTrace)) + if (exception?.StackTrace is not { } stackTrace) { return; } - terminal.SetColor(TerminalColor.Red); - terminal.Append(SingleIndentation); - terminal.Append(PlatformResources.StackTrace); - terminal.AppendLine(":"); - - if (!errorStackTrace.Contains('\n')) - { - AppendStackFrame(terminal, errorStackTrace); - return; - } + terminal.SetColor(TerminalColor.DarkGray); - string[] lines = errorStackTrace.Split(NewLineStrings, StringSplitOptions.None); + string[] lines = stackTrace.Split(NewLineStrings, StringSplitOptions.None); foreach (string line in lines) { AppendStackFrame(terminal, line); @@ -581,18 +573,57 @@ private static void FormatExpectedAndActual(ITerminal terminal, string? expected terminal.ResetColor(); } - private static void FormatErrorMessage(ITerminal terminal, string? errorMessage) + private static void FormatErrorMessage(ITerminal terminal, string? errorMessage, Exception? exception, TestOutcome outcome) { - if (RoslynString.IsNullOrWhiteSpace(errorMessage)) + if (RoslynString.IsNullOrWhiteSpace(errorMessage) && exception is null) { return; } terminal.SetColor(TerminalColor.Red); - AppendIndentedLine(terminal, errorMessage, SingleIndentation); + + if (exception is null) + { + AppendIndentedLine(terminal, errorMessage, SingleIndentation); + } + else if (outcome == TestOutcome.Fail) + { + // For failed tests, we don't prefix the message with the exception type because it is most likely an assertion specific exception like AssertionFailedException, and we prefer to show that without the exception type to avoid additional noise. + AppendIndentedLine(terminal, errorMessage ?? exception.Message, SingleIndentation); + } + else + { + AppendIndentedLine(terminal, $"{exception.GetType().FullName}: {errorMessage ?? exception.Message}", SingleIndentation); + } + terminal.ResetColor(); } + private static void FormatInnerExceptions(ITerminal terminal, Exception? exception) + { + IEnumerable aggregateExceptions = exception switch + { + AggregateException aggregate => aggregate.Flatten().InnerExceptions, + _ => [exception?.InnerException], + }; + + foreach (Exception? aggregate in aggregateExceptions) + { + Exception? currentException = aggregate; + while (currentException is not null) + { + terminal.SetColor(TerminalColor.Red); + terminal.Append(SingleIndentation); + terminal.Append("--->"); + FormatErrorMessage(terminal, null, currentException, TestOutcome.Error); + + FormatStackTrace(terminal, currentException); + + currentException = currentException.InnerException; + } + } + } + private static void AppendIndentedLine(ITerminal terminal, string? message, string indent) { if (RoslynString.IsNullOrWhiteSpace(message)) diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs index 5792660438..774eda95ad 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs @@ -411,8 +411,8 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella testNodeStateChanged.TestNode.DisplayName, TestOutcome.Error, duration, - errorMessage: errorState.Exception?.Message ?? errorState.Explanation, - errorState.Exception?.StackTrace, + errorState.Explanation, + errorState.Exception, expected: null, actual: null); break; @@ -425,8 +425,8 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella testNodeStateChanged.TestNode.DisplayName, TestOutcome.Fail, duration, - errorMessage: failedState.Exception?.Message ?? failedState.Explanation, - failedState.Exception?.StackTrace, + failedState.Explanation, + failedState.Exception, expected: failedState.Exception?.Data["assert.expected"] as string, actual: failedState.Exception?.Data["assert.actual"] as string); break; @@ -439,8 +439,8 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella testNodeStateChanged.TestNode.DisplayName, TestOutcome.Timeout, duration, - errorMessage: timeoutState.Exception?.Message ?? timeoutState.Explanation, - timeoutState.Exception?.StackTrace, + timeoutState.Explanation, + timeoutState.Exception, expected: null, actual: null); break; @@ -453,8 +453,8 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella testNodeStateChanged.TestNode.DisplayName, TestOutcome.Canceled, duration, - errorMessage: cancelledState.Exception?.Message ?? cancelledState.Explanation, - cancelledState.Exception?.StackTrace, + cancelledState.Explanation, + cancelledState.Exception, expected: null, actual: null); break; @@ -468,7 +468,7 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella outcome: TestOutcome.Passed, duration: duration, errorMessage: null, - errorStackTrace: null, + exception: null, expected: null, actual: null); break; @@ -482,7 +482,7 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella TestOutcome.Skipped, duration, errorMessage: null, - errorStackTrace: null, + exception: null, expected: null, actual: null); break; diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/JsonRpcMethods.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/JsonRpcMethods.cs index e0a988755e..cf8b52f5df 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/JsonRpcMethods.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/JsonRpcMethods.cs @@ -60,7 +60,7 @@ internal static class JsonRpcStrings public const string Message = "message"; // Telemetry - public const string EventName = "EventName"; + public const string EventName = "eventName"; public const string Metrics = "metrics"; // Process diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs index ec3b1d5491..b15c8c2c9d 100644 --- a/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/DynamicDataShouldBeValidAnalyzerTests.cs @@ -164,12 +164,112 @@ public void TestMethod214(int i, string s) { } + [DynamicData("DataJaggedArray")] + [TestMethod] + public void TestMethod301(MyTestClass[] testClasses) + { + } + + [DynamicData("SomeDataJaggedArray", typeof(SomeClass))] + [TestMethod] + public void TestMethod302(MyTestClass[] testClasses) + { + } + + [DynamicData(dynamicDataSourceName: "DataJaggedArray")] + [TestMethod] + public void TestMethod303(MyTestClass[] testClasses) + { + } + + [DynamicData(dynamicDataDeclaringType: typeof(SomeClass), dynamicDataSourceName: "SomeDataJaggedArray")] + [TestMethod] + public void TestMethod304(MyTestClass[] testClasses) + { + } + + [DynamicData("GetDataJaggedArray", DynamicDataSourceType.Method)] + [TestMethod] + public void TestMethod311(MyTestClass[] testClasses) + { + } + + [DynamicData("GetSomeDataJaggedArray", typeof(SomeClass), DynamicDataSourceType.Method)] + [TestMethod] + public void TestMethod312(MyTestClass[] testClasses) + { + } + + [DynamicData(dynamicDataSourceType: DynamicDataSourceType.Method, dynamicDataSourceName: "GetDataJaggedArray")] + [TestMethod] + public void TestMethod313(MyTestClass[] testClasses) + { + } + + [DynamicData(dynamicDataDeclaringType: typeof(SomeClass), dynamicDataSourceType: DynamicDataSourceType.Method, dynamicDataSourceName: "GetSomeDataJaggedArray")] + [TestMethod] + public void TestMethod314(MyTestClass[] testClasses) + { + } + + [DynamicData("DataNonObjectTypeArray")] + [TestMethod] + public void TestMethod401(MyTestClass[] testClasses) + { + } + + [DynamicData("SomeDataNonObjectTypeArray", typeof(SomeClass))] + [TestMethod] + public void TestMethod402(MyTestClass[] testClasses) + { + } + + [DynamicData(dynamicDataSourceName: "DataNonObjectTypeArray")] + [TestMethod] + public void TestMethod403(MyTestClass[] testClasses) + { + } + + [DynamicData(dynamicDataDeclaringType: typeof(SomeClass), dynamicDataSourceName: "SomeDataNonObjectTypeArray")] + [TestMethod] + public void TestMethod404(MyTestClass[] testClasses) + { + } + + [DynamicData("GetDataNonObjectTypeArray", DynamicDataSourceType.Method)] + [TestMethod] + public void TestMethod411(MyTestClass[] testClasses) + { + } + + [DynamicData("GetSomeDataNonObjectTypeArray", typeof(SomeClass), DynamicDataSourceType.Method)] + [TestMethod] + public void TestMethod412(MyTestClass[] testClasses) + { + } + + [DynamicData(dynamicDataSourceType: DynamicDataSourceType.Method, dynamicDataSourceName: "GetDataNonObjectTypeArray")] + [TestMethod] + public void TestMethod413(MyTestClass[] testClasses) + { + } + + [DynamicData(dynamicDataDeclaringType: typeof(SomeClass), dynamicDataSourceType: DynamicDataSourceType.Method, dynamicDataSourceName: "GetSomeDataNonObjectTypeArray")] + [TestMethod] + public void TestMethod414(MyTestClass[] testClasses) + { + } + public static IEnumerable Data => new List(); public static IEnumerable> DataTuple => new List>(); public static IEnumerable<(int, string)> DataValueTuple => new List<(int, string)>(); + public static MyTestClass[][] DataJaggedArray => System.Array.Empty(); + public static IEnumerable DataNonObjectTypeArray => new List(); public static IEnumerable GetData() => new List(); public static IEnumerable> GetDataTuple() => new List>(); public static IEnumerable<(int, string)> GetDataValueTuple() => new List<(int, string)>(); + public static MyTestClass[][] GetDataJaggedArray() => System.Array.Empty(); + public static IEnumerable GetDataNonObjectTypeArray() => new List(); } public class SomeClass @@ -177,9 +277,13 @@ public class SomeClass public static IEnumerable SomeData => new List(); public static IEnumerable> SomeDataTuple => new List>(); public static IEnumerable<(int, string)> SomeDataValueTuple => new List<(int, string)>(); + public static MyTestClass[][] SomeDataJaggedArray => System.Array.Empty(); + public static IEnumerable SomeDataNonObjectTypeArray => new List(); public static IEnumerable GetSomeData() => new List(); public static IEnumerable> GetSomeDataTuple() => new List>(); public static IEnumerable<(int, string)> GetSomeDataValueTuple() => new List<(int, string)>(); + public static MyTestClass[][] GetSomeDataJaggedArray() => System.Array.Empty(); + public static IEnumerable GetSomeDataNonObjectTypeArray() => new List(); } """; @@ -469,36 +573,88 @@ public void TestMethod4(object[] o) [{|#4:DynamicData("GetData", DynamicDataSourceType.Method)|}] [TestMethod] - public void TestMethod11(object[] o) + public void TestMethod5(object[] o) { } [{|#5:DynamicData("GetSomeData", typeof(SomeClass), DynamicDataSourceType.Method)|}] [TestMethod] - public void TestMethod12(object[] o) + public void TestMethod6(object[] o) { } [{|#6:DynamicData(dynamicDataSourceType: DynamicDataSourceType.Method, dynamicDataSourceName: "GetData")|}] [TestMethod] - public void TestMethod13(object[] o) + public void TestMethod7(object[] o) { } [{|#7:DynamicData(dynamicDataDeclaringType: typeof(SomeClass), dynamicDataSourceType: DynamicDataSourceType.Method, dynamicDataSourceName: "GetSomeData")|}] [TestMethod] - public void TestMethod14(object[] o) + public void TestMethod8(object[] o) + { + } + + [{|#8:DynamicData("DataArray")|}] + [TestMethod] + public void TestMethod9(MyTestClass[] o) + { + } + + [{|#9:DynamicData("SomeDataArray", typeof(SomeClass))|}] + [TestMethod] + public void TestMethod10(MyTestClass[] o) + { + } + + [{|#10:DynamicData(dynamicDataSourceName: "DataArray")|}] + [TestMethod] + public void TestMethod11(MyTestClass[] o) + { + } + + [{|#11:DynamicData(dynamicDataDeclaringType: typeof(SomeClass), dynamicDataSourceName: "SomeDataArray")|}] + [TestMethod] + public void TestMethod12(MyTestClass[] o) + { + } + + [{|#12:DynamicData("GetDataArray", DynamicDataSourceType.Method)|}] + [TestMethod] + public void TestMethod13(MyTestClass[] o) + { + } + + [{|#13:DynamicData("GetSomeDataArray", typeof(SomeClass), DynamicDataSourceType.Method)|}] + [TestMethod] + public void TestMethod14(MyTestClass[] o) + { + } + + [{|#14:DynamicData(dynamicDataSourceType: DynamicDataSourceType.Method, dynamicDataSourceName: "GetDataArray")|}] + [TestMethod] + public void TestMethod15(MyTestClass[] o) + { + } + + [{|#15:DynamicData(dynamicDataDeclaringType: typeof(SomeClass), dynamicDataSourceType: DynamicDataSourceType.Method, dynamicDataSourceName: "GetSomeDataArray")|}] + [TestMethod] + public void TestMethod16(MyTestClass[] o) { } public static IEnumerable Data => new List(); + public static MyTestClass[] DataArray => System.Array.Empty(); public static IEnumerable GetData() => new List(); + public static MyTestClass[] GetDataArray() => System.Array.Empty(); } public class SomeClass { public static IEnumerable SomeData => new List(); + public static MyTestClass[] SomeDataArray => System.Array.Empty(); public static IEnumerable GetSomeData() => new List(); + public static MyTestClass[] GetSomeDataArray() => System.Array.Empty(); } """; @@ -511,7 +667,15 @@ await VerifyCS.VerifyAnalyzerAsync( VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberTypeRule).WithLocation(4).WithArguments("MyTestClass", "GetData"), VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberTypeRule).WithLocation(5).WithArguments("SomeClass", "GetSomeData"), VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberTypeRule).WithLocation(6).WithArguments("MyTestClass", "GetData"), - VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberTypeRule).WithLocation(7).WithArguments("SomeClass", "GetSomeData")); + VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberTypeRule).WithLocation(7).WithArguments("SomeClass", "GetSomeData"), + VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberTypeRule).WithLocation(8).WithArguments("MyTestClass", "DataArray"), + VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberTypeRule).WithLocation(9).WithArguments("SomeClass", "SomeDataArray"), + VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberTypeRule).WithLocation(10).WithArguments("MyTestClass", "DataArray"), + VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberTypeRule).WithLocation(11).WithArguments("SomeClass", "SomeDataArray"), + VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberTypeRule).WithLocation(12).WithArguments("MyTestClass", "GetDataArray"), + VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberTypeRule).WithLocation(13).WithArguments("SomeClass", "GetSomeDataArray"), + VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberTypeRule).WithLocation(14).WithArguments("MyTestClass", "GetDataArray"), + VerifyCS.Diagnostic(DynamicDataShouldBeValidAnalyzer.MemberTypeRule).WithLocation(15).WithArguments("SomeClass", "GetSomeDataArray")); } public async Task MemberIsNotStatic_Diagnostic() diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/TypeContainingTestMethodShouldBeATestClassAnalyzer.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/TypeContainingTestMethodShouldBeATestClassAnalyzer.cs index cad0ffead4..a743894786 100644 --- a/test/UnitTests/MSTest.Analyzers.UnitTests/TypeContainingTestMethodShouldBeATestClassAnalyzer.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/TypeContainingTestMethodShouldBeATestClassAnalyzer.cs @@ -162,4 +162,28 @@ public void TestMethod1() await VerifyCS.VerifyAnalyzerAsync(code); } + + public async Task WhenClassHasTestInitializeAndThenTestMethod_Diagnostic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public class [|TestClass|] + { + [TestInitialize] + public void Initialize() + { + + } + + [TestMethod] + public void TestMethod1() + { + + } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } } diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.cs index a4ca874c51..ced3ec2eb6 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.cs @@ -70,16 +70,16 @@ public void OutputFormattingIsCorrect() terminalReporter.AssemblyRunStarted(assembly, targetFramework, architecture); terminalReporter.TestCompleted(assembly, targetFramework, architecture, "PassedTest1", TestOutcome.Passed, TimeSpan.FromSeconds(10), - errorMessage: null, errorStackTrace: null, expected: null, actual: null); + errorMessage: null, exception: null, expected: null, actual: null); terminalReporter.TestCompleted(assembly, targetFramework, architecture, "SkippedTest1", TestOutcome.Skipped, TimeSpan.FromSeconds(10), - errorMessage: null, errorStackTrace: null, expected: null, actual: null); + errorMessage: null, exception: null, expected: null, actual: null); // timed out + cancelled + failed should all report as failed in summary terminalReporter.TestCompleted(assembly, targetFramework, architecture, "TimedoutTest1", TestOutcome.Timeout, TimeSpan.FromSeconds(10), - errorMessage: null, errorStackTrace: null, expected: null, actual: null); + errorMessage: null, exception: null, expected: null, actual: null); terminalReporter.TestCompleted(assembly, targetFramework, architecture, "CanceledTest1", TestOutcome.Canceled, TimeSpan.FromSeconds(10), - errorMessage: null, errorStackTrace: null, expected: null, actual: null); + errorMessage: null, exception: null, expected: null, actual: null); terminalReporter.TestCompleted(assembly, targetFramework, architecture, "FailedTest1", TestOutcome.Fail, TimeSpan.FromSeconds(10), - errorMessage: "Tests failed", errorStackTrace: @$" at FailingTest() in {folder}codefile.cs:line 10", expected: "ABC", actual: "DEF"); + errorMessage: "Tests failed", exception: new StackTraceException(@$" at FailingTest() in {folder}codefile.cs:line 10"), expected: "ABC", actual: "DEF"); terminalReporter.ArtifactAdded(outOfProcess: true, assembly, targetFramework, architecture, testName: null, @$"{folder}artifact1.txt"); terminalReporter.ArtifactAdded(outOfProcess: false, assembly, targetFramework, architecture, testName: null, @$"{folder}artifact2.txt"); terminalReporter.AssemblyRunCompleted(assembly, targetFramework, architecture); @@ -98,9 +98,8 @@ public void OutputFormattingIsCorrect() ABC Actual DEF - ␛[m␛[91m Stack Trace: - ␛[90mat ␛[m␛[91mFailingTest()␛[90m in ␛[90m␛]8;;file:///{folderLink}codefile.cs␛\{folder}codefile.cs:10␛]8;;␛\␛[m - + ␛[m␛[90m ␛[90mat ␛[m␛[91mFailingTest()␛[90m in ␛[90m␛]8;;file:///{folderLink}codefile.cs␛\{folder}codefile.cs:10␛]8;;␛\␛[m + ␛[m Out of process file artifacts produced: - ␛[90m␛]8;;file:///{folderLink}artifact1.txt␛\{folder}artifact1.txt␛]8;;␛\␛[m @@ -115,13 +114,10 @@ public void OutputFormattingIsCorrect() """; - EnsureAnsiMatch(expected, output); + Assert.AreEqual(expected, ShowEscape(output)); } - private void EnsureAnsiMatch(string expected, string actual) - => Assert.AreEqual(expected, ShowEscape(actual)); - - private string? ShowEscape(string? text) + private static string? ShowEscape(string? text) { string visibleEsc = "\x241b"; return text?.Replace(AnsiCodes.Esc, visibleEsc); @@ -229,4 +225,11 @@ public void SetColor(TerminalColor color) public void StopUpdate() => throw new NotImplementedException(); } + + private class StackTraceException : Exception + { + public StackTraceException(string stackTrace) => StackTrace = stackTrace; + + public override string? StackTrace { get; } + } } diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/FormatterUtilitiesTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/FormatterUtilitiesTests.cs index c62ae65727..ce6d52bf0d 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/FormatterUtilitiesTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/FormatterUtilitiesTests.cs @@ -152,7 +152,7 @@ private static void AssertSerialize(Type type, string instanceSerialized) if (type == typeof(TelemetryEventArgs)) { - Assert.AreEqual("""{"EventName":"eventName","metrics":{"key":1}}""".Replace(" ", string.Empty), instanceSerialized, because); + Assert.AreEqual("""{"eventName":"eventName","metrics":{"key":1}}""".Replace(" ", string.Empty), instanceSerialized, because); return; }