diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.FilterDiscovery.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.FilterDiscovery.cs new file mode 100644 index 0000000000..6d1413fd88 --- /dev/null +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.FilterDiscovery.cs @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; + +internal sealed partial class TypeCache +{ + // Single filter instance cached per test assembly source path. Computed lazily on the first + // request for that source so the cost is paid at most once per run, even when many tests + // target the same assembly. Stored as a TestFilterBox so the dictionary can cache the + // "no filter" answer alongside real filter instances. + private readonly ConcurrentDictionary _testFilterBySource = + new(StringComparer.Ordinal); + + /// + /// Returns the cached instance registered via + /// on the given test assembly, or + /// if the assembly does not register one. + /// + /// The test assembly source path (typically TestMethod.AssemblyName). + /// + /// Discovery is metadata-only for the probe step and never forces the test types of the + /// assembly to load. The filter type is loaded the first time the filter for a + /// given source is requested. Only the test assembly itself is inspected — registering a + /// in a referenced library has no effect. + /// + internal ITestFilter? GetOrLoadTestFilter(string assemblySource) + => _testFilterBySource + .GetOrAdd(assemblySource, static src => new TestFilterBox(LoadTestFilterForSource(src))) + .Filter; + + private static ITestFilter? LoadTestFilterForSource(string assemblySource) + { + Assembly assembly; + try + { + assembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(assemblySource); + } + catch (Exception ex) + { + if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsWarningEnabled) + { + PlatformServiceProvider.Instance.AdapterTraceLogger.Warning( + "TypeCache: Could not load test assembly {0} for TestFilterProvider discovery. {1}", + assemblySource, + ex); + } + + return null; + } + + return DiscoverTestFilterFromProvider(assembly); + } + + private static ITestFilter? DiscoverTestFilterFromProvider(Assembly testAssembly) + { + // Cheap metadata-only probe first: avoid loading the filter's Type unless the attribute is + // actually present. Mirrors the AssemblyFixtureProvider probe pattern. + if (!HasTestFilterProviderMarker(testAssembly)) + { + return null; + } + + object[] markers; + try + { + markers = PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributes(testAssembly, typeof(TestFilterProviderAttribute)); + } + catch (Exception ex) + { + // Marker is present (CustomAttributeData saw it) but the attribute cannot be + // instantiated. This typically means the type referenced by typeof(...) cannot be + // loaded. [TestFilterProvider] is explicit opt-in: silently dropping the marker + // would let the user's filter logic disappear at runtime, which is a more + // dangerous failure mode than a clear diagnostic. + string message = string.Format( + CultureInfo.CurrentCulture, + Resource.UTA_TestFilterProviderLoadFailed, + SafeGetAssemblyName(testAssembly) ?? "", + ex.Message); + throw new TypeInspectionException(message, ex); + } + + if (markers is null || markers.Length == 0) + { + return null; + } + + if (markers.Length > 1) + { + string message = string.Format( + CultureInfo.CurrentCulture, + Resource.UTA_TestFilterProviderMultipleDeclared, + SafeGetAssemblyName(testAssembly) ?? ""); + throw new TypeInspectionException(message); + } + + return markers[0] is TestFilterProviderAttribute { FilterType: { } filterType } + ? InstantiateTestFilter(filterType) + : null; + } + + internal static ITestFilter InstantiateTestFilter(Type filterType) + { + if (filterType.IsGenericType) + { + string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_TestFilterProviderTypeIsGeneric, filterType.FullName); + throw new TypeInspectionException(message); + } + + if (filterType.IsAbstract || filterType.IsInterface) + { + string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_TestFilterProviderTypeIsNotInstantiable, filterType.FullName); + throw new TypeInspectionException(message); + } + + if (!typeof(ITestFilter).IsAssignableFrom(filterType)) + { + string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_TestFilterProviderTypeDoesNotImplementInterface, filterType.FullName, typeof(ITestFilter).FullName); + throw new TypeInspectionException(message); + } + + try + { + return (ITestFilter)Activator.CreateInstance(filterType)!; + } + catch (Exception ex) + { + string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_TestFilterProviderInstantiationFailed, filterType.FullName, ex.Message); + throw new TypeInspectionException(message, ex); + } + } + + private static bool HasTestFilterProviderMarker(Assembly assembly) + { + // Compare on the attribute type's FullName so we don't trigger attribute construction, + // mirroring the AssemblyFixtureProvider probe. + string markerFullName = typeof(TestFilterProviderAttribute).FullName!; + foreach (CustomAttributeData data in assembly.GetCustomAttributesData()) + { + if (string.Equals(data.AttributeType.FullName, markerFullName, StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } + + // Tiny holder so the cache can distinguish "not computed yet" (missing key) from + // "computed and result is no filter" (present key with Filter = null). + private sealed class TestFilterBox + { + public TestFilterBox(ITestFilter? filter) => Filter = filter; + + public ITestFilter? Filter { get; } + } +} diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs index 5e391fb043..6636ac745e 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs @@ -10,6 +10,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -36,6 +37,12 @@ internal sealed class UnitTestRunner // Used to attach assembly cleanup failures to the right test. private UnitTestElement? _lastRunnableTestInWholeAssembly; + // Tracks whether at least one test in this runner's lifetime triggered AssemblyInitialize. + // Needed so that end-of-assembly cleanup still runs when the very last test of the assembly + // was filtered out by a [TestFilterProvider] (in which case testMethodInfo is null for the + // cleanup decision in RunSingleTestAsync). + private bool _assemblyInitializeWasExecuted; + /// /// Initializes a new instance of the class. /// @@ -133,6 +140,21 @@ internal async Task RunSingleTestAsync(UnitTestElement unitTestEle { testContextForTestExecution = PlatformServiceProvider.Instance.GetTestContext(testMethod, null, testContextProperties, messageLogger, UnitTestOutcome.InProgress); + // Apply user-supplied [TestFilterProvider] filter BEFORE loading the test type, BEFORE + // running [AssemblyInitialize] and BEFORE [ClassInitialize]. This is the whole point of + // the feature: a Drop or Skip here pays none of those costs. See + // https://github.com/microsoft/testfx/issues/8894 for the design. + TestResult[]? filterResult = ApplyTestFilter(unitTestElement); + if (filterResult is not null) + { + return await FinishFilteredOutTestAsync( + testMethod, + testContextProperties, + messageLogger, + filterResult, + testContextForTestExecution).ConfigureAwait(false); + } + // Get the testMethod TestMethodInfo? testMethodInfo = _typeCache.GetTestMethodInfo(testMethod); @@ -148,6 +170,7 @@ internal async Task RunSingleTestAsync(UnitTestElement unitTestEle testContextForAssemblyInit = PlatformServiceProvider.Instance.GetTestContext(testMethod: null, null, testContextProperties, messageLogger, testContextForTestExecution.Context.CurrentTestOutcome); TestResult assemblyInitializeResult = await RunAssemblyInitializeIfNeededAsync(testMethodInfo, testContextForAssemblyInit).ConfigureAwait(false); + _assemblyInitializeWasExecuted |= testMethodInfo.Parent.Parent.IsAssemblyInitializeExecuted; if (assemblyInitializeResult.Outcome != UnitTestOutcome.Passed) { @@ -390,4 +413,171 @@ private static bool IsTestMethodRunnable( } internal void ForceCleanup(IDictionary sourceLevelParameters, IMessageLogger logger) => ClassCleanupManager.ForceCleanup(_typeCache, sourceLevelParameters, logger); + + /// + /// Invokes the user-supplied registered via + /// for the test assembly, if any. Returns + /// if no filter is registered or the filter returned + /// (test should run normally), an empty array if the + /// filter returned , or a single Skipped + /// if the filter returned . + /// + /// + /// A filter exception is surfaced as an Error test result so the failure is visible to the + /// user instead of silently affecting test selection. + /// is single-per-assembly by design: callers that want to combine multiple strategies should + /// compose them explicitly inside their implementation. + /// + private TestResult[]? ApplyTestFilter(UnitTestElement unitTestElement) + { + ITestFilter? filter = _typeCache.GetOrLoadTestFilter(unitTestElement.TestMethod.AssemblyName); + if (filter is null) + { + return null; + } + + TestFilterContext context = CreateFilterContext(unitTestElement); + + TestFilterResult result; + try + { + result = filter.Filter(context); + } + catch (Exception ex) + { + string message = string.Format( + CultureInfo.CurrentCulture, + Resource.UTA_TestFilterProviderThrew, + filter.GetType().FullName, + context.FullyQualifiedName, + ex.Message); + return + [ + new TestResult + { + Outcome = UnitTestOutcome.Error, + TestFailureException = new TestFailedException(UnitTestOutcome.Error, message, ex.TryGetStackTraceInformation()), + } + ]; + } + + return result.Action switch + { + TestFilterAction.Drop => [], + TestFilterAction.Skip => [TestResult.CreateIgnoredResult(result.SkipReason)], + _ => null, + }; + } + + private static TestFilterContext CreateFilterContext(UnitTestElement element) + { + TestMethod testMethod = element.TestMethod; + string[] categories = element.TestCategory ?? []; + + KeyValuePair[] traits; + if (element.Traits is { Length: > 0 } source) + { + traits = new KeyValuePair[source.Length]; + for (int i = 0; i < source.Length; i++) + { + traits[i] = new KeyValuePair(source[i].Name, source[i].Value); + } + } + else + { + traits = []; + } + + // Pull namespace + simple class name from the hierarchy when available — this is the + // same source the IDE / Test Explorer uses, so it correctly handles nested types and + // generic classes (where naïve FullClassName splitting would lie). + string? hierarchyNamespace = null; + string? hierarchyClassName = null; + if (testMethod.Hierarchy is IReadOnlyList hierarchy && hierarchy.Count > HierarchyConstants.Levels.ClassIndex) + { + hierarchyNamespace = hierarchy[HierarchyConstants.Levels.NamespaceIndex]; + hierarchyClassName = hierarchy[HierarchyConstants.Levels.ClassIndex]; + } + + // ManagedMethodName is an ECMA-335 string like `MyMethod`1(System.Int32)` — parse it + // cheaply (no MethodInfo reflection) to surface arity and parameter type names. + int? methodArity = null; + IReadOnlyList? parameterTypeFullNames = null; + if (testMethod.ManagedMethodName is { } managedMethod) + { + try + { + ManagedNameParser.ParseManagedMethodName(managedMethod, out _, out int arity, out string[]? parameterTypes); + methodArity = arity; + parameterTypeFullNames = parameterTypes ?? (IReadOnlyList)[]; + } + catch + { + // Defensive: if the managed name is malformed for any reason, surface what we + // can via the flat strings rather than failing the filter. + } + } + + return new TestFilterContext + { + FullyQualifiedName = $"{testMethod.FullClassName}.{testMethod.Name}", + DisplayName = testMethod.DisplayName, + MethodName = testMethod.Name, + Source = testMethod.AssemblyName, + Namespace = hierarchyNamespace, + ClassName = hierarchyClassName, + ManagedTypeName = testMethod.ManagedTypeName, + ManagedMethodName = testMethod.ManagedMethodName, + MethodArity = methodArity, + ParameterTypeFullNames = parameterTypeFullNames, + Categories = categories, + Traits = traits, + Priority = element.Priority, + }; + } + + /// + /// Handles the bookkeeping (class-cleanup countdown, end-of-assembly cleanup) for a test that + /// was filtered out by a . Mirrors the tail of + /// without ever requiring testMethodInfo, since the filter ran before any type was loaded. + /// + private async Task FinishFilteredOutTestAsync( + TestMethod testMethod, + IDictionary testContextProperties, + IMessageLogger messageLogger, + TestResult[] filterResult, + ITestContext testContextForTestExecution) + { + _classCleanupManager.MarkTestComplete(testMethod, out bool isLastTestInClass); + if (isLastTestInClass) + { + // No class cleanup possible: we never loaded the test type, so there's nothing to + // execute. Still mark the class as complete so end-of-assembly cleanup is gated correctly. + _classCleanupManager.MarkClassComplete(testMethod.FullClassName); + } + + if (_assemblyInitializeWasExecuted && _classCleanupManager.ShouldRunEndOfAssemblyCleanup) + { + ITestContext? testContextForAssemblyCleanup = null; + try + { + testContextForAssemblyCleanup = PlatformServiceProvider.Instance.GetTestContext(testMethod: null, null, testContextProperties, messageLogger, testContextForTestExecution.Context.CurrentTestOutcome); + + TestResult? assemblyCleanupResult = await RunAssemblyCleanupAsync(testContextForAssemblyCleanup, _typeCache, filterResult).ConfigureAwait(false); + if (assemblyCleanupResult is not null) + { + // Current test was filtered (no result), so an assembly cleanup failure needs to + // be associated with the last real test that ran in the assembly. + assemblyCleanupResult.AssociatedUnitTestElement = _lastRunnableTestInWholeAssembly; + filterResult = [.. filterResult, assemblyCleanupResult]; + } + } + finally + { + (testContextForAssemblyCleanup as IDisposable)?.Dispose(); + } + } + + return filterResult; + } } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/Resource.resx b/src/Adapter/MSTestAdapter.PlatformServices/Resources/Resource.resx index 3380abb157..e893ac5d3d 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/Resource.resx +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/Resource.resx @@ -379,6 +379,27 @@ but received {4} argument(s), with types '{5}'. UTA072: Failed to load [AssemblyFixtureProvider] marker from assembly '{0}'. {1} + + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + + + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + + + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + + + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + + + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + + + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + + + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + UTA026: {0}: Cannot define more than one method with the ClassCleanup attribute inside a class. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.cs.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.cs.xlf index 5ca4624317..ec4cec5aa1 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.cs.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.cs.xlf @@ -522,6 +522,41 @@ byl však přijat tento počet argumentů: {4} s typy {5}. {0}.TestContext má nesprávný typ. + + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + + + + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + + + + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + + + + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + + + + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + + + + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + + + + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + + Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be 'Task' or 'ValueTask'. Metoda {0}.{1} má špatný podpis. Metoda nesmí být static nebo public, nevrací hodnotu a nesmí přijímat žádný parametr. Pokud navíc v metodě používáte operátor async-await, musí být návratový typ Task nebo ValueTask. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.de.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.de.xlf index ebbffd3709..c2092af92c 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.de.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.de.xlf @@ -522,6 +522,41 @@ aber empfing {4} Argument(e) mit den Typen „{5}“. "{0}.TestContext" weist einen falschen Typ auf. + + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + + + + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + + + + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + + + + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + + + + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + + + + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + + + + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + + Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be 'Task' or 'ValueTask'. Die Methode „{0}.{1}“ weist eine falsche Signatur auf. Die Methode muss nicht statisch und öffentlich sein, und sie darf keinen Wert zurückgeben und keinen Parameter annehmen. Wenn Sie außerdem in der Methode „async-await“ verwenden, muss der Rückgabetyp „Task“ oder „ValueTask“ sein. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.es.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.es.xlf index afe7ab8315..4b4e5ec262 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.es.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.es.xlf @@ -522,6 +522,41 @@ pero recibió {4} argumentos, con los tipos '{5}'. Tipo {0}.TestContext no es correcto. + + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + + + + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + + + + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + + + + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + + + + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + + + + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + + + + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + + Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be 'Task' or 'ValueTask'. El método {0}.{1} tiene una firma incorrecta. Debe ser un método no estático, público, no devolver ningún valor y no debe aceptar parámetros. Además, si está usando async-await en el método entonces el tipo de valor devuelto debe ser 'Task' o 'ValueTask'. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.fr.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.fr.xlf index 0e67f56036..21f98e5167 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.fr.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.fr.xlf @@ -522,6 +522,41 @@ mais a reçu {4} argument(s), avec les types « {5} ». {0}.TestContext possède un type incorrect. + + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + + + + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + + + + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + + + + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + + + + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + + + + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + + + + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + + Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be 'Task' or 'ValueTask'. La méthode {0}.{1} présente une signature incorrecte. La méthode doit être non statique, publique et ne doit retourner aucune valeur ni accepter aucun paramètre. En outre, si vous utilisez async-await dans la méthode, return-type doit être « Task » ou « ValueTask ». diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.it.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.it.xlf index fc92844e1f..6c356dd203 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.it.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.it.xlf @@ -522,6 +522,41 @@ ma ha ricevuto {4} argomenti, con tipi '{5}'. Il tipo di {0}.TestContext non è corretto. + + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + + + + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + + + + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + + + + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + + + + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + + + + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + + + + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + + Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be 'Task' or 'ValueTask'. La firma del metodo {0}.{1}non è corretta. Il metodo deve essere non statico e pubblico, non deve restituire un valore né accettare parametri. Se inoltre si usa async-await nel metodo di test, il tipo restituito deve essere 'Task' o 'ValueTask'. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ja.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ja.xlf index a849fd11cc..ec3659efb3 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ja.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ja.xlf @@ -523,6 +523,41 @@ but received {4} argument(s), with types '{5}'. {0}.TestContext は不適切な型を含んでいます。 + + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + + + + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + + + + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + + + + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + + + + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + + + + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + + + + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + + Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be 'Task' or 'ValueTask'. メソッド {0}。{1} は不適切なシグネチャを含んでいます。メソッドは non-static および public である必要があり、値を返しません。また、パラメーターを受け取ることはできません。また、メソッドで async-await を使用している場合、戻り値の型は 'Task' または 'ValueTask' である必要があります。 diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ko.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ko.xlf index 74f8f366ca..47abada2b9 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ko.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ko.xlf @@ -522,6 +522,41 @@ but received {4} argument(s), with types '{5}'. {0}.TestContext의 형식이 잘못되었습니다. + + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + + + + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + + + + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + + + + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + + + + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + + + + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + + + + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + + Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be 'Task' or 'ValueTask'. {0}.{1} 메서드의 서명이 잘못되었습니다. 메서드는 정적이 아니고 공용이어야 하며, 값을 반환하거나 매개 변수를 사용할 수 없습니다. 또한 메서드에서 비동기 대기를 사용하는 경우 반환 형식은 'Task' 또는 'ValueTask'여야 합니다. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pl.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pl.xlf index 1af348a901..a74f7198a4 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pl.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pl.xlf @@ -522,6 +522,41 @@ ale odebrał argumenty {4} z typami „{5}”. Element {0}.TestContext ma niepoprawny typ. + + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + + + + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + + + + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + + + + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + + + + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + + + + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + + + + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + + Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be 'Task' or 'ValueTask'. Metoda {0}.{1}ma nieprawidłową sygnaturę. Metoda musi być niestatyczna, publiczna, nie może zwracać wartości i nie powinna przyjmować żadnego parametru. Ponadto jeśli w metodzie jest używane oczekiwanie asynchroniczne, wtedy zwracanym typem musi być typ „Task” lub „ValueTask”. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pt-BR.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pt-BR.xlf index ea757352ae..adede11bfa 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pt-BR.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pt-BR.xlf @@ -522,6 +522,41 @@ mas {4} argumentos recebidos, com tipos '{5}'. O {0}.TestContext é do tipo incorreto. + + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + + + + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + + + + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + + + + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + + + + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + + + + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + + + + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + + Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be 'Task' or 'ValueTask'. O método {0}.{1} tem a assinatura incorreta. O método deve ser não estático, público, não deve retornar um valor e não deve receber nenhum parâmetro. Além disso, se você estiver usando async-await no método, o return-type deverá ser "Task" ou "ValueTask". diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ru.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ru.xlf index 11a224d39f..57b6dece45 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ru.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ru.xlf @@ -522,6 +522,41 @@ but received {4} argument(s), with types '{5}'. Для свойства {0}.TestContext указан неправильный тип. + + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + + + + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + + + + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + + + + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + + + + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + + + + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + + + + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + + Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be 'Task' or 'ValueTask'. Метод {0}.{1} имеет неправильную сигнатуру. Метод должен быть нестатическим и открытым, не должен возвращать значение и принимать параметры. Кроме того, при использовании async-await в методе возвращаемое значение должно иметь тип "Task" или "ValueTask". diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.tr.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.tr.xlf index b85acecf72..94d6e1c8ec 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.tr.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.tr.xlf @@ -522,6 +522,41 @@ ancak, '{5}' türünde {4} bağımsız değişken aldı. {0}.TestContext yanlış türe sahip. + + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + + + + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + + + + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + + + + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + + + + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + + + + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + + + + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + + Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be 'Task' or 'ValueTask'. {0}.{1} yönteminin imzası yanlış. Yöntem statik olmayan, genel, değer döndürmeyen bir yöntem olmalı, hiçbir parametre almamalıdır. Bunlara ek olarak, yöntemde async-await kullanıyorsanız return-type değeri 'Task' veya 'ValueTask' olmalıdır. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hans.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hans.xlf index e9a8319a66..86ff98d509 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hans.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hans.xlf @@ -522,6 +522,41 @@ but received {4} argument(s), with types '{5}'. {0}.TestContext 的类型不正确。 + + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + + + + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + + + + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + + + + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + + + + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + + + + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + + + + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + + Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be 'Task' or 'ValueTask'. 方法 {0}。{1}的签名错误。该方法必须是非静态的公共方法、不返回值并且不应采用任何参数。此外,如果在方法中使用同步等待,则返回类型必须为“Task”或“Value Task”。 diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hant.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hant.xlf index 227de99936..b63baf4c25 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hant.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hant.xlf @@ -522,6 +522,41 @@ but received {4} argument(s), with types '{5}'. {0}.TestContext 有不正確的類型。 + + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + UTA077: Failed to instantiate type '{0}' referenced by [TestFilterProvider]. The type must declare a public parameterless constructor. {1} + + + + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + UTA073: Failed to load [TestFilterProvider] marker from assembly '{0}'. {1} + + + + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + UTA079: Assembly '{0}' declares more than one [TestFilterProvider] attribute. At most one is allowed per test assembly; compose multiple filtering strategies inside a single ITestFilter implementation. + + + + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + UTA078: The [TestFilterProvider] filter '{0}' threw an exception while evaluating test '{1}'. {2} + + + + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + UTA076: Type '{0}' referenced by [TestFilterProvider] must implement '{1}'. + + + + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + UTA074: Type '{0}' referenced by [TestFilterProvider] must not be generic. + + + + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + UTA075: Type '{0}' referenced by [TestFilterProvider] must be a concrete class, not an interface or abstract class. + + Method {0}.{1} has wrong signature. The method must be non-static, public, does not return a value and should not take any parameter. Additionally, if you are using async-await in method then return-type must be 'Task' or 'ValueTask'. 方法 {0}.{1} 有錯誤的簽章。方法必須為非靜態、公用、不傳回值,並且不應該接受任何參數。此外,如果您在方法中使用 async-await,則傳回類型必須是 'Task' 或 'ValueTask'。 diff --git a/src/TestFramework/TestFramework/Attributes/Lifecycle/TestFilterProviderAttribute.cs b/src/TestFramework/TestFramework/Attributes/Lifecycle/TestFilterProviderAttribute.cs new file mode 100644 index 0000000000..02e3a5dc0c --- /dev/null +++ b/src/TestFramework/TestFramework/Attributes/Lifecycle/TestFilterProviderAttribute.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestTools.UnitTesting; + +/// +/// Registers a user-supplied implementation that the MSTest adapter +/// invokes for every test it is about to run, after any command-line filter (--filter, +/// Test Explorer selection, etc.) has been applied. +/// +/// +/// +/// Apply this attribute at the assembly level on the test assembly itself. At most one +/// may be applied per test assembly; this is intentional +/// so that filter ordering is not part of the public API. If multiple filtering strategies are +/// needed, compose them explicitly inside a single implementation. +/// +/// +/// The filter type must be a non-generic class with a public parameterless constructor that +/// implements . A single instance is created per test assembly per test +/// run and reused for every test of that assembly. +/// +/// +/// The filter runs before the test type is loaded, before [AssemblyInitialize], +/// before [ClassInitialize], and before the test constructor is invoked, so dropping or +/// skipping a test through avoids paying any of those costs. +/// +/// +/// +/// +/// [assembly: TestFilterProvider(typeof(NightlyFilter))] +/// +/// public sealed class NightlyFilter : ITestFilter +/// { +/// public TestFilterResult Filter(TestFilterContext context) +/// => context.Categories.Contains("Nightly") +/// && Environment.GetEnvironmentVariable("RUN_NIGHTLY") != "1" +/// ? TestFilterResult.Skip("Set RUN_NIGHTLY=1 to run nightly tests.") +/// : TestFilterResult.Run; +/// } +/// +/// +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)] +public sealed class TestFilterProviderAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + /// + /// The implementation to instantiate and invoke for every test in + /// the consuming test assembly. Must be a non-generic class with a public parameterless + /// constructor. + /// + public TestFilterProviderAttribute(Type filterType) + => FilterType = filterType ?? throw new ArgumentNullException(nameof(filterType)); + + /// + /// Gets the implementation registered by this attribute. + /// + public Type FilterType { get; } +} diff --git a/src/TestFramework/TestFramework/Filtering/ITestFilter.cs b/src/TestFramework/TestFramework/Filtering/ITestFilter.cs new file mode 100644 index 0000000000..aee36cd975 --- /dev/null +++ b/src/TestFramework/TestFramework/Filtering/ITestFilter.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestTools.UnitTesting; + +/// +/// Implemented by a user-supplied test filter that decides, on a per-test basis, whether the test +/// should run, be silently dropped, or be reported as skipped. +/// +/// +/// +/// Implementations are registered via at the assembly +/// level. The MSTest adapter materializes the filter lazily on the first test invocation for that +/// assembly using the public parameterless constructor; the same instance is then reused for +/// every test in the assembly. +/// +/// +/// Implementations should be allocation-free and thread-safe; may be +/// invoked concurrently for tests in different classes. +/// +/// +/// Ordering with built-in filtering: is composed with — +/// not a replacement for — the adapter's default filtering. Adapter-level filters such as the +/// VSTest --filter command line or test-explorer selection run before +/// , so only ever sees tests that already survived +/// those gates. By contrast, [Ignore] is evaluated after +/// (it requires loading the declaring type, which is specifically designed +/// to avoid). Returning does not override a later +/// [Ignore]; an ignored test is still ignored. +/// +/// +public interface ITestFilter +{ + /// + /// Decides whether the test described by should run. + /// + /// Metadata describing the test under consideration. + /// + /// to let the test execute normally, + /// to silently drop the test (no result emitted), or + /// to report the test as Skipped with a reason. + /// + /// + /// If this method throws, the test is reported as Error with diagnostic UTA078; the + /// exception is never silently swallowed. Implementations that want to opt the test in to + /// running on failure should catch their own exceptions and return . + /// + TestFilterResult Filter(TestFilterContext context); +} diff --git a/src/TestFramework/TestFramework/Filtering/TestFilterAction.cs b/src/TestFramework/TestFramework/Filtering/TestFilterAction.cs new file mode 100644 index 0000000000..235dc230ef --- /dev/null +++ b/src/TestFramework/TestFramework/Filtering/TestFilterAction.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestTools.UnitTesting; + +/// +/// Indicates how the MSTest adapter should treat a test for which an +/// returned a particular . +/// +public enum TestFilterAction +{ + /// + /// Run the test normally. This is the default action so that default(TestFilterResult) + /// is the safe choice when an implementation forgets to set a result explicitly. + /// + Run = 0, + + /// + /// Silently drop the test. No is emitted, the test does not appear in + /// the test count, and the declaring class's [ClassInitialize] is not invoked unless + /// another (non-dropped) test in the same class still has to run. This matches the semantics + /// of the platform --filter command-line option. + /// + Drop, + + /// + /// Report the test as Skipped, with the reason supplied to . + /// The test appears in the test count, the TRX/console output, and IDE test explorers. + /// + Skip, +} diff --git a/src/TestFramework/TestFramework/Filtering/TestFilterContext.cs b/src/TestFramework/TestFramework/Filtering/TestFilterContext.cs new file mode 100644 index 0000000000..9d15f457a7 --- /dev/null +++ b/src/TestFramework/TestFramework/Filtering/TestFilterContext.cs @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestTools.UnitTesting; + +/// +/// Read-only snapshot of the metadata MSTest exposes to an for a single +/// test under consideration. +/// +/// +/// +/// Only metadata that is available without loading the test type is exposed; +/// must be able to decide using strings, categories, traits, and priority alone. This is what allows +/// the filter to drop tests before their declaring type is loaded and before +/// [AssemblyInitialize] / [ClassInitialize] run. +/// +/// +/// Each property describes a separate facet of the test. The flat string properties +/// (, ) are convenience views and are always +/// populated. The structured properties (, , +/// , , , +/// ) may be when the metadata is not +/// available — typically for tests discovered through code paths that don't surface ECMA-335 +/// managed names. +/// +/// +/// The type is designed to be extended over time: new properties can be added without breaking +/// existing implementations or their unit tests. Consumers construct +/// instances using an object initializer (e.g. new TestFilterContext { FullyQualifiedName = "…" }); +/// no positional constructor needs to be updated when new properties land. +/// +/// +public sealed class TestFilterContext +{ + /// + /// Gets or sets the fully qualified test name in Namespace.Class.Method form. + /// + /// + /// This mirrors the historical VSTest TestCase.FullyQualifiedName shape and is intended + /// for filters that want a single string to match against. + /// + public string FullyQualifiedName { get; set; } = string.Empty; + + /// + /// Gets or sets the display name of the test, as reported to the runner / IDE. + /// + /// + /// Often equal to ; differs for data-driven tests, tests with a + /// custom display name, or attributes that override it. + /// + public string DisplayName { get; set; } = string.Empty; + + /// + /// Gets or sets the unqualified test method name (i.e. without class or namespace). + /// + public string MethodName { get; set; } = string.Empty; + + /// + /// Gets or sets the path to the test assembly file containing this test. + /// + /// + /// Matches VSTest TestCase.Source: the absolute path to the .dll being run. + /// This is not a simple assembly name; see if you + /// need an ECMA-335-style identifier. + /// + public string Source { get; set; } = string.Empty; + + /// + /// Gets or sets the namespace of the declaring test class, or when + /// no managed metadata is available or the class is in the global namespace. + /// + public string? Namespace { get; set; } + + /// + /// Gets or sets the simple class name (without namespace), or when + /// no managed metadata is available. Nested types are surfaced using their managed metadata + /// form (e.g. Outer+Inner); see for the fully escaped + /// ECMA-335 representation. + /// + public string? ClassName { get; set; } + + /// + /// Gets or sets the declaring type name in ECMA-335 metadata form, or + /// when no managed metadata is available. + /// + /// + /// Includes the namespace, uses + for nested types, and uses backtick + arity for + /// generics (e.g. Acme.MyOuter+MyInner`1). Matches the format defined by the + /// + /// Managed TestCase Properties RFC. + /// + public string? ManagedTypeName { get; set; } + + /// + /// Gets or sets the method name in ECMA-335 metadata form, or when no + /// managed metadata is available. + /// + /// + /// Includes the method name, generic arity suffix, and parameter type list + /// (e.g. MyMethod`1(System.Int32)). Matches the format defined by the + /// + /// Managed TestCase Properties RFC. + /// + public string? ManagedMethodName { get; set; } + + /// + /// Gets or sets the number of generic type parameters declared on the test method, + /// or when no managed metadata is available. Zero indicates a + /// non-generic method. + /// + public int? MethodArity { get; set; } + + /// + /// Gets or sets the ECMA-335-style fully qualified parameter type names of the test method, + /// or when no managed metadata is available. An empty list indicates + /// a parameterless method. + /// + public IReadOnlyList? ParameterTypeFullNames { get; set; } + + /// + /// Gets or sets the test categories declared via on the + /// method or its declaring class. Defaults to an empty list. + /// + public IReadOnlyList Categories { get; set; } = []; + + /// + /// Gets or sets the traits attached to this test. Multiple traits can share the same key; + /// consumers must therefore not assume the collection behaves like a dictionary. Defaults + /// to an empty list. + /// + public IReadOnlyList> Traits { get; set; } = []; + + /// + /// Gets or sets the value declared on the test, or + /// when no priority was declared. + /// + public int? Priority { get; set; } +} diff --git a/src/TestFramework/TestFramework/Filtering/TestFilterResult.cs b/src/TestFramework/TestFramework/Filtering/TestFilterResult.cs new file mode 100644 index 0000000000..5ed341cd8b --- /dev/null +++ b/src/TestFramework/TestFramework/Filtering/TestFilterResult.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestTools.UnitTesting; + +/// +/// The decision returned by an for a single test. +/// +/// +/// Designed as a so the filter hot path stays +/// allocation-free. The static / properties (one shared value +/// each) and the parameterized factory are the only ways to construct +/// a result; this keeps the surface evolvable. +/// +public readonly struct TestFilterResult : IEquatable +{ + private TestFilterResult(TestFilterAction action, string? skipReason) + { + Action = action; + SkipReason = skipReason; + } + + /// + /// Gets the action MSTest should apply for the test that produced this result. + /// + public TestFilterAction Action { get; } + + /// + /// Gets the reason supplied to . Non- only when + /// is . + /// + public string? SkipReason { get; } + + /// + /// Gets the result indicating that the test should run normally. + /// + /// + /// Equivalent to default(TestFilterResult); is the + /// default enum value so a filter that forgets to assign a result still defaults to running + /// the test. + /// + public static TestFilterResult Run { get; } = new(TestFilterAction.Run, null); + + /// + /// Gets the result indicating that the test should be silently dropped. Matches the semantics + /// of the command-line --filter option: no test result is emitted and the test is not + /// counted. + /// + public static TestFilterResult Drop { get; } = new(TestFilterAction.Drop, null); + + /// + /// Creates a result indicating that the test should be reported as Skipped with the given reason. + /// + /// A non-empty human-readable explanation surfaced in TRX / console / IDE output. + /// A with equal to . + /// Thrown when is . + /// Thrown when is empty or whitespace only. + public static TestFilterResult Skip(string reason) + => reason is null + ? throw new ArgumentNullException(nameof(reason)) + : string.IsNullOrWhiteSpace(reason) + ? throw new ArgumentException("Value cannot be empty or whitespace.", nameof(reason)) + : new TestFilterResult(TestFilterAction.Skip, reason); + + /// + public bool Equals(TestFilterResult other) + => Action == other.Action && string.Equals(SkipReason, other.SkipReason, StringComparison.Ordinal); + + /// + public override bool Equals(object? obj) + => obj is TestFilterResult other && Equals(other); + + /// + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = (hash * 31) + (int)Action; + hash = (hash * 31) + (SkipReason?.GetHashCode() ?? 0); + return hash; + } + } + + /// Equality operator. + public static bool operator ==(TestFilterResult left, TestFilterResult right) => left.Equals(right); + + /// Inequality operator. + public static bool operator !=(TestFilterResult left, TestFilterResult right) => !left.Equals(right); +} diff --git a/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt b/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt index ea572077cb..f214542bd4 100644 --- a/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt @@ -5,9 +5,58 @@ Microsoft.VisualStudio.TestTools.UnitTesting.AssemblyFixtureProviderAttribute.As Microsoft.VisualStudio.TestTools.UnitTesting.AssemblyFixtureProviderAttribute.FixtureType.get -> System.Type! Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException.ActualText.get -> string? Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException.ExpectedText.get -> string? +Microsoft.VisualStudio.TestTools.UnitTesting.ITestFilter +Microsoft.VisualStudio.TestTools.UnitTesting.ITestFilter.Filter(Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext! context) -> Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult Microsoft.VisualStudio.TestTools.UnitTesting.SequenceOrder Microsoft.VisualStudio.TestTools.UnitTesting.SequenceOrder.InAnyOrder = 1 -> Microsoft.VisualStudio.TestTools.UnitTesting.SequenceOrder Microsoft.VisualStudio.TestTools.UnitTesting.SequenceOrder.InOrder = 0 -> Microsoft.VisualStudio.TestTools.UnitTesting.SequenceOrder +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterAction +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterAction.Drop = 1 -> Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterAction +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterAction.Run = 0 -> Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterAction +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterAction.Skip = 2 -> Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterAction +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.Categories.get -> System.Collections.Generic.IReadOnlyList! +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.Categories.set -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.ClassName.get -> string? +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.ClassName.set -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.DisplayName.get -> string! +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.DisplayName.set -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.FullyQualifiedName.get -> string! +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.FullyQualifiedName.set -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.ManagedMethodName.get -> string? +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.ManagedMethodName.set -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.ManagedTypeName.get -> string? +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.ManagedTypeName.set -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.MethodArity.get -> int? +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.MethodArity.set -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.MethodName.get -> string! +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.MethodName.set -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.Namespace.get -> string? +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.Namespace.set -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.ParameterTypeFullNames.get -> System.Collections.Generic.IReadOnlyList? +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.ParameterTypeFullNames.set -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.Priority.get -> int? +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.Priority.set -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.Source.get -> string! +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.Source.set -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.TestFilterContext() -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.Traits.get -> System.Collections.Generic.IReadOnlyList>! +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterContext.Traits.set -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterProviderAttribute +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterProviderAttribute.FilterType.get -> System.Type! +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterProviderAttribute.TestFilterProviderAttribute(System.Type! filterType) -> void +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult.Action.get -> Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterAction +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult.Equals(Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult other) -> bool +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult.SkipReason.get -> string? +Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult.TestFilterResult() -> void +override Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult.Equals(object? obj) -> bool +override Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult.GetHashCode() -> int +static Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult.Drop.get -> Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult +static Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult.operator !=(Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult left, Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult right) -> bool +static Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult.operator ==(Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult left, Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult right) -> bool +static Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult.Run.get -> Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult +static Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult.Skip(string! reason) -> Microsoft.VisualStudio.TestTools.UnitTesting.TestFilterResult static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreAllDistinct(System.Collections.IEnumerable? collection, System.Collections.IEqualityComparer? comparer, string? message = "", string! collectionExpression = "") -> void static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreAllDistinct(System.Collections.IEnumerable? collection, string? message = "", string! collectionExpression = "") -> void static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreAllDistinct(System.Collections.Generic.IEnumerable? collection, System.Collections.Generic.IEqualityComparer? comparer, string? message = "", string! collectionExpression = "") -> void diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTestFilterProviderTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTestFilterProviderTests.cs new file mode 100644 index 0000000000..4b73c89918 --- /dev/null +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTestFilterProviderTests.cs @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using AwesomeAssertions; + +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; + +using TestFramework.ForTestingMSTest; + +namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Execution; + +public class TypeCacheTestFilterProviderTests : TestContainer +{ + private readonly TestablePlatformServiceProvider _testablePlatformServiceProvider; + + public TypeCacheTestFilterProviderTests() + { + _testablePlatformServiceProvider = new TestablePlatformServiceProvider(); + PlatformServiceProvider.Instance = _testablePlatformServiceProvider; + } + + protected override void Dispose(bool disposing) + { + if (!IsDisposed) + { + base.Dispose(disposing); + PlatformServiceProvider.Instance = null; + MSTestSettings.Reset(); + } + } + + // Direct unit tests for the validation branches of InstantiateTestFilter. Driving these + // through GetOrLoadTestFilter would require polluting AssemblyAttributes.cs with broken + // markers that would then run for every test in this assembly. The helper is scoped, + // internal, and self-contained, so testing it directly is both safer and clearer. + public void InstantiateTestFilter_WhenTypeIsOpenGeneric_ThrowsUTA074() + { + Action act = () => TypeCache.InstantiateTestFilter(typeof(GenericFilter<>)); + act.Should().Throw().WithMessage("*UTA074*"); + } + + public void InstantiateTestFilter_WhenTypeIsClosedGeneric_ThrowsUTA074() + { + Action act = () => TypeCache.InstantiateTestFilter(typeof(GenericFilter)); + act.Should().Throw().WithMessage("*UTA074*"); + } + + public void InstantiateTestFilter_WhenTypeIsAbstract_ThrowsUTA075() + { + Action act = () => TypeCache.InstantiateTestFilter(typeof(AbstractFilter)); + act.Should().Throw().WithMessage("*UTA075*"); + } + + public void InstantiateTestFilter_WhenTypeIsInterface_ThrowsUTA075() + { + Action act = () => TypeCache.InstantiateTestFilter(typeof(IFilterInterface)); + act.Should().Throw().WithMessage("*UTA075*"); + } + + public void InstantiateTestFilter_WhenTypeDoesNotImplementITestFilter_ThrowsUTA076() + { + Action act = () => TypeCache.InstantiateTestFilter(typeof(NotAFilter)); + act.Should().Throw().WithMessage("*UTA076*"); + } + + public void InstantiateTestFilter_WhenConstructorThrows_ThrowsUTA077() + { + // Activator.CreateInstance wraps the constructor exception in TargetInvocationException; + // the diagnostic surfaces UTA077 either way and preserves the inner exception. + Action act = () => TypeCache.InstantiateTestFilter(typeof(ThrowingFilter)); + act.Should().Throw().WithMessage("*UTA077*"); + } + + public void InstantiateTestFilter_WhenTypeIsMissingPublicParameterlessConstructor_ThrowsUTA077() + { + Action act = () => TypeCache.InstantiateTestFilter(typeof(FilterWithoutPublicCtor)); + act.Should().Throw().WithMessage("*UTA077*"); + } + + public void InstantiateTestFilter_WhenTypeIsValid_ReturnsInstance() + { + ITestFilter filter = TypeCache.InstantiateTestFilter(typeof(NoOpFilter)); + filter.Should().NotBeNull().And.BeOfType(); + } + + // ----- test types ----- + public sealed class NoOpFilter : ITestFilter + { + public TestFilterResult Filter(TestFilterContext context) => TestFilterResult.Run; + } + + public sealed class GenericFilter : ITestFilter + { + public TestFilterResult Filter(TestFilterContext context) => TestFilterResult.Run; + } + + public abstract class AbstractFilter : ITestFilter + { + public abstract TestFilterResult Filter(TestFilterContext context); + } + + public interface IFilterInterface : ITestFilter + { + } + + // Intentionally does not implement ITestFilter. + public sealed class NotAFilter + { + } + + public sealed class ThrowingFilter : ITestFilter + { + public ThrowingFilter() => throw new InvalidOperationException("filter ctor blew up"); + + public TestFilterResult Filter(TestFilterContext context) => TestFilterResult.Run; + } + + public sealed class FilterWithoutPublicCtor : ITestFilter + { + private FilterWithoutPublicCtor() + { + } + + public TestFilterResult Filter(TestFilterContext context) => TestFilterResult.Run; + } +} diff --git a/test/UnitTests/TestFramework.UnitTests/Filtering/TestFilterResultTests.cs b/test/UnitTests/TestFramework.UnitTests/Filtering/TestFilterResultTests.cs new file mode 100644 index 0000000000..0548f66150 --- /dev/null +++ b/test/UnitTests/TestFramework.UnitTests/Filtering/TestFilterResultTests.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using AwesomeAssertions; + +using TestFramework.ForTestingMSTest; + +namespace Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests.Filtering; + +public class TestFilterResultTests : TestContainer +{ + public void Run_HasActionRunAndNullReason() + { + TestFilterResult result = TestFilterResult.Run; + result.Action.Should().Be(TestFilterAction.Run); + result.SkipReason.Should().BeNull(); + } + + public void Drop_HasActionDropAndNullReason() + { + TestFilterResult result = TestFilterResult.Drop; + result.Action.Should().Be(TestFilterAction.Drop); + result.SkipReason.Should().BeNull(); + } + + public void Default_DefaultsToRunAction() + { + // The XML doc on TestFilterResult.Run promises that a default(TestFilterResult) value + // behaves like Run. A filter that forgets to assign a result still runs the test. + var result = default(TestFilterResult); + result.Action.Should().Be(TestFilterAction.Run); + result.SkipReason.Should().BeNull(); + } + + public void Skip_WithValidReason_SetsActionAndReason() + { + var result = TestFilterResult.Skip("Skipped because of CI policy."); + result.Action.Should().Be(TestFilterAction.Skip); + result.SkipReason.Should().Be("Skipped because of CI policy."); + } + + public void Skip_WhenReasonIsNull_ThrowsArgumentNullException() + { + Action act = static () => TestFilterResult.Skip(null!); + act.Should().Throw().WithParameterName("reason"); + } + + public void Skip_WhenReasonIsEmpty_ThrowsArgumentException() + { + // The XML doc promises a "non-empty human-readable explanation" — empty strings would + // surface as unactionable skipped tests in TRX / IDE output, so they are rejected. + Action act = static () => TestFilterResult.Skip(string.Empty); + act.Should().Throw().WithParameterName("reason"); + } + + public void Skip_WhenReasonIsWhitespace_ThrowsArgumentException() + { + Action act = static () => TestFilterResult.Skip(" "); + act.Should().Throw().WithParameterName("reason"); + } + + public void Equality_TwoSkipResultsWithSameReasonAreEqual() + { + var first = TestFilterResult.Skip("reason"); + var second = TestFilterResult.Skip("reason"); + + first.Equals(second).Should().BeTrue(); + (first == second).Should().BeTrue(); + (first != second).Should().BeFalse(); + first.GetHashCode().Should().Be(second.GetHashCode()); + } + + public void Equality_TwoSkipResultsWithDifferentReasonsAreNotEqual() + { + var first = TestFilterResult.Skip("reason A"); + var second = TestFilterResult.Skip("reason B"); + + first.Equals(second).Should().BeFalse(); + (first == second).Should().BeFalse(); + (first != second).Should().BeTrue(); + } + + public void Equality_RunAndDropAreNotEqual() + { + (TestFilterResult.Run == TestFilterResult.Drop).Should().BeFalse(); + TestFilterResult.Run.Equals(TestFilterResult.Drop).Should().BeFalse(); + } + + public void Equality_BoxedComparisonAgainstNonResultIsFalse() + { + TestFilterResult.Run.Equals("not a result").Should().BeFalse(); + TestFilterResult.Run.Equals(null).Should().BeFalse(); + } +}