Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to ignore specific test cases #4457

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions src/Adapter/MSTest.TestAdapter/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ internal static class Constants
internal static readonly TestProperty TestDynamicDataProperty = TestProperty.Register("MSTest.DynamicData", "DynamicData", typeof(string[]), TestPropertyAttributes.Hidden, typeof(TestCase));

internal static readonly TestProperty TestIdGenerationStrategyProperty = TestProperty.Register("MSTest.TestIdGenerationStrategy", "TestIdGenerationStrategy", typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));

internal static readonly TestProperty TestDataSourceIgnoreMessageProperty = TestProperty.Register("MSTest.TestDataSourceIgnoreMessageProperty", "TestDataSourceIgnoreMessageProperty", typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
#endregion

#region Private Constants
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,6 @@ static UnitTestElement GetFixtureTest(string classFullName, string assemblyLocat
return new UnitTestElement(method)
{
DisplayName = $"[{fixtureType}] {methodName}",
Ignored = true,
Traits = [new Trait(Constants.FixturesTestTrait, fixtureType)],
};
}
Expand Down Expand Up @@ -424,6 +423,7 @@ private static bool TryUnfoldITestDataSource(ITestDataSource dataSource, TestDat
// This code is to discover tests. To run the tests code is in TestMethodRunner.ExecuteDataSourceBasedTests.
// Any change made here should be reflected in TestMethodRunner.ExecuteDataSourceBasedTests as well.
data = dataSource.GetData(methodInfo);
string? testDataSourceIgnoreMessage = (dataSource as ITestDataSourceIgnoreCapability)?.IgnoreMessage;

if (!data.Any())
{
Expand All @@ -435,6 +435,7 @@ private static bool TryUnfoldITestDataSource(ITestDataSource dataSource, TestDat
UnitTestElement discoveredTest = test.Clone();
// Make the test not data driven, because it had no data.
discoveredTest.TestMethod.DataType = DynamicDataType.None;
discoveredTest.TestMethod.TestDataSourceIgnoreMessage = testDataSourceIgnoreMessage;
discoveredTest.DisplayName = dataSource.GetDisplayName(methodInfo, null) ?? discoveredTest.DisplayName;

tests.Add(discoveredTest);
Expand Down Expand Up @@ -468,6 +469,7 @@ private static bool TryUnfoldITestDataSource(ITestDataSource dataSource, TestDat
try
{
discoveredTest.TestMethod.SerializedData = DataSerializationHelper.Serialize(d);
discoveredTest.TestMethod.TestDataSourceIgnoreMessage = testDataSourceIgnoreMessage;
discoveredTest.TestMethod.DataType = DynamicDataType.ITestDataSource;
}
catch (SerializationException ex)
Expand Down
1 change: 0 additions & 1 deletion src/Adapter/MSTest.TestAdapter/Discovery/TypeEnumerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ internal UnitTestElement GetTestFromMethod(MethodInfo method, bool isDeclaredInT
TestCategory = _reflectHelper.GetTestCategories(method, _type),
DoNotParallelize = _reflectHelper.IsDoNotParallelizeSet(method, _type),
Priority = _reflectHelper.GetPriority(method),
Ignored = _reflectHelper.IsNonDerivedAttributeDefined<IgnoreAttribute>(method, inherit: false),
DeploymentItems = PlatformServiceProvider.Instance.TestDeployment.GetDeploymentItems(method, _type, warnings),
};

Expand Down
6 changes: 4 additions & 2 deletions src/Adapter/MSTest.TestAdapter/Execution/TestClassInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,8 @@ internal void ExecuteClassCleanup(TestContext testContext)
{
if (classCleanupMethod is not null)
{
if (!ReflectHelper.Instance.IsNonDerivedAttributeDefined<IgnoreAttribute>(classCleanupMethod.DeclaringType!, false))
if (ClassAttribute.IgnoreMessage is null &&
!ReflectHelper.Instance.IsNonDerivedAttributeDefined<IgnoreAttribute>(classCleanupMethod.DeclaringType!, false))
{
ClassCleanupException = InvokeCleanupMethod(classCleanupMethod, remainingCleanupCount: BaseClassCleanupMethods.Count, testContext);
}
Expand All @@ -579,7 +580,8 @@ internal void ExecuteClassCleanup(TestContext testContext)
for (int i = 0; i < BaseClassCleanupMethods.Count; i++)
{
classCleanupMethod = BaseClassCleanupMethods[i];
if (!ReflectHelper.Instance.IsNonDerivedAttributeDefined<IgnoreAttribute>(classCleanupMethod.DeclaringType!, false))
if (ClassAttribute.IgnoreMessage is null &&
!ReflectHelper.Instance.IsNonDerivedAttributeDefined<IgnoreAttribute>(classCleanupMethod.DeclaringType!, false))
{
ClassCleanupException = InvokeCleanupMethod(classCleanupMethod, remainingCleanupCount: BaseClassCleanupMethods.Count - 1 - i, testContext);
if (ClassCleanupException is not null)
Expand Down
18 changes: 18 additions & 0 deletions src/Adapter/MSTest.TestAdapter/Execution/TestMethodRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ internal UnitTestResult[] RunTestMethod()
var parentStopwatch = Stopwatch.StartNew();
if (_test.DataType == DynamicDataType.ITestDataSource)
{
if (_test.TestDataSourceIgnoreMessage is not null)
{
return [new(UnitTestOutcome.Ignored, _test.TestDataSourceIgnoreMessage)];
}

object?[]? data = DataSerializationHelper.Deserialize(_test.SerializedData);
TestResult[] testResults = ExecuteTestWithDataSource(null, data);
results.AddRange(testResults);
Expand Down Expand Up @@ -276,6 +281,19 @@ private bool TryExecuteFoldedDataDrivenTests(List<TestResult> results)

foreach (UTF.ITestDataSource testDataSource in testDataSources)
{
if (testDataSource is ITestDataSourceIgnoreCapability { IgnoreMessage: { } ignoreMessage })
{
results.Add(new()
{
// This is closest to ignore. This enum doesn't have a value specific to Ignore.
// It may be a better idea to add a value there, but the enum is public and we need to think more carefully before adding the API.
// For now, TestResultExtensions.ToUnitTestResults method will convert this to Ignored value of ObjectModel enum when IgnoreReason is non-null.
Outcome = UTF.UnitTestOutcome.NotRunnable,
IgnoreReason = ignoreMessage,
});
continue;
}

IEnumerable<object?[]>? dataSource;

// This code is to execute tests. To discover the tests code is in AssemblyEnumerator.ProcessTestDataSourceTests.
Expand Down
10 changes: 9 additions & 1 deletion src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -347,9 +347,17 @@ private bool IsTestMethodRunnable(
}
}

// TODO: Executor should never be null. Is it incorrectly annotated?
string? ignoreMessage = testMethodInfo.Parent.ClassAttribute.IgnoreMessage ?? testMethodInfo.TestMethodOptions.Executor?.IgnoreMessage;
if (ignoreMessage is not null)
{
notRunnableResult = [new UnitTestResult(UnitTestOutcome.Ignored, ignoreMessage)];
return false;
}

IgnoreAttribute? ignoreAttributeOnClass =
_reflectHelper.GetFirstNonDerivedAttributeOrDefault<IgnoreAttribute>(testMethodInfo.Parent.ClassType, inherit: false);
string? ignoreMessage = ignoreAttributeOnClass?.IgnoreMessage;
ignoreMessage = ignoreAttributeOnClass?.IgnoreMessage;

IgnoreAttribute? ignoreAttributeOnMethod =
_reflectHelper.GetFirstNonDerivedAttributeOrDefault<IgnoreAttribute>(testMethodInfo.TestMethod, inherit: false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ internal static UnitTestElement ToUnitTestElement(this TestCase testCase, string

testMethod.DataType = dataType;
testMethod.SerializedData = data;
testMethod.TestDataSourceIgnoreMessage = testCase.GetPropertyValue(Constants.TestDataSourceIgnoreMessageProperty) as string;
}

if (testCase.GetPropertyValue(Constants.DeclaringClassNameProperty) is string declaringClassName && declaringClassName != testClassName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ internal static UnitTestResult[] ToUnitTestResults(this IReadOnlyCollection<UTF.
int i = 0;
foreach (UTF.TestResult testResult in testResults)
{
var outcome = testResult.Outcome.ToUnitTestOutcome();
UnitTestOutcome outcome = testResult.IgnoreReason is not null
? UnitTestOutcome.Ignored
: testResult.Outcome.ToUnitTestOutcome();

UnitTestResult unitTestResult = testResult.TestFailureException is { } testFailureException
? new UnitTestResult(
Expand All @@ -40,6 +42,12 @@ testFailureException is TestFailedException testException
? testException.StackTraceInformation
: testFailureException.TryGetStackTraceInformation()))
: new UnitTestResult { Outcome = outcome };

if (testResult.IgnoreReason is not null)
{
unitTestResult.ErrorMessage = testResult.IgnoreReason;
}

unitTestResult.StandardOut = testResult.LogOutput;
unitTestResult.StandardError = testResult.LogError;
unitTestResult.DebugTrace = testResult.DebugTrace;
Expand Down
2 changes: 2 additions & 0 deletions src/Adapter/MSTest.TestAdapter/ObjectModel/TestMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ public string? DeclaringClassFullName
/// </summary>
internal string?[]? SerializedData { get; set; }

internal string? TestDataSourceIgnoreMessage { get; set; }

/// <summary>
/// Gets or sets the test group set during discovery.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@ public UnitTestElement(TestMethod testMethod)
/// </summary>
public TestMethod TestMethod { get; private set; }

/// <summary>
/// Gets or sets a value indicating whether the unit test should be ignored at run-time.
/// </summary>
public bool Ignored { get; set; }
Youssef1313 marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Gets or sets a value indicating whether it is a async test.
/// </summary>
Expand Down Expand Up @@ -211,6 +206,7 @@ internal TestCase ToTestCase()

testCase.SetPropertyValue(Constants.TestDynamicDataTypeProperty, (int)TestMethod.DataType);
testCase.SetPropertyValue(Constants.TestDynamicDataProperty, data);
testCase.SetPropertyValue(Constants.TestDataSourceIgnoreMessageProperty, TestMethod.TestDataSourceIgnoreMessage);
}

SetTestCaseId(testCase, testFullName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// Attribute to define in-line data for a test method.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class DataRowAttribute : Attribute, ITestDataSource, ITestDataSourceUnfoldingCapability
public class DataRowAttribute : Attribute, ITestDataSource, ITestDataSourceUnfoldingCapability, ITestDataSourceIgnoreCapability
{
/// <summary>
/// Initializes a new instance of the <see cref="DataRowAttribute"/> class.
Expand Down Expand Up @@ -53,6 +53,11 @@ public DataRowAttribute(string?[]? stringArrayData)
/// </summary>
public string? DisplayName { get; set; }

/// <summary>
/// Gets or sets a reason to ignore the specific test case. Setting the property to non-null value will ignore the test case.
/// </summary>
Comment on lines +56 to +58
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// <summary>
/// Gets or sets a reason to ignore the specific test case. Setting the property to non-null value will ignore the test case.
/// </summary>
/// <inheritdoc />

public string? IgnoreMessage { get; set; }

/// <inheritdoc />
public TestDataSourceUnfoldingStrategy UnfoldingStrategy { get; set; } = TestDataSourceUnfoldingStrategy.Auto;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public enum DynamicDataSourceType
/// Attribute to define dynamic data for a test method.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class DynamicDataAttribute : Attribute, ITestDataSource, ITestDataSourceEmptyDataSourceExceptionInfo, ITestDataSourceUnfoldingCapability
public sealed class DynamicDataAttribute : Attribute, ITestDataSource, ITestDataSourceEmptyDataSourceExceptionInfo, ITestDataSourceUnfoldingCapability, ITestDataSourceIgnoreCapability
{
private readonly string _dynamicDataSourceName;
private readonly DynamicDataSourceType _dynamicDataSourceType;
Expand Down Expand Up @@ -114,6 +114,11 @@ public DynamicDataAttribute(string dynamicDataSourceName, Type dynamicDataDeclar
/// <inheritdoc />
public TestDataSourceUnfoldingStrategy UnfoldingStrategy { get; set; } = TestDataSourceUnfoldingStrategy.Auto;

/// <summary>
/// Gets or sets a reason to ignore this dynamic data source. Setting the property to non-null value will ignore the dynamic data source.
/// </summary>
Comment on lines +117 to +119
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// <summary>
/// Gets or sets a reason to ignore this dynamic data source. Setting the property to non-null value will ignore the dynamic data source.
/// </summary>
/// <inheritdoc />

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was intentionally not inheriting as I'm adjusting the wording a little bit for the specific concrete implementation. It's not super important though as the wording from the interface should still be clear enough for the concrete implementations.

public string? IgnoreMessage { get; set; }

/// <inheritdoc />
public IEnumerable<object[]> GetData(MethodInfo methodInfo)
=> DynamicDataProvider.Instance.GetData(_dynamicDataDeclaringType, _dynamicDataSourceType, _dynamicDataSourceName, methodInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,9 @@ public class TestClassAttribute : Attribute
public virtual TestMethodAttribute? GetTestMethodAttribute(TestMethodAttribute? testMethodAttribute) =>
// If TestMethod is not extended by derived class then return back the original TestMethodAttribute
testMethodAttribute;

/// <summary>
/// Gets or sets a reason to ignore the test class. Setting the property to non-null value will ignore the test class.
/// </summary>
public string? IgnoreMessage { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public TestMethodAttribute()
/// </summary>
public string? DisplayName { get; }

/// <summary>
/// Gets or sets a reason to ignore the test method. Setting the property to non-null value will ignore the test method.
/// </summary>
public string? IgnoreMessage { get; set; }

/// <summary>
/// Executes a test method.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public class TestResult
/// </summary>
public UnitTestOutcome Outcome { get; set; }

internal string? IgnoreReason { get; set; }

/// <summary>
/// Gets or sets the exception thrown when test is failed.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 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;

/// <summary>
/// Specifies the capability of a test data source to be ignored and define the ignore reason.
/// </summary>
public interface ITestDataSourceIgnoreCapability
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be worth making this one more generic (not linked to data sources) so we can apply it to test methods and test classes too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Evangelink The impl is quite different, at least currently. For example, for specific test cases, we need to have a VSTest property for that to restore the associated ignore message. But we don't need that for when it's the whole test method or test class being ignored.

{
/// <summary>
/// Gets or sets a reason to ignore the test data source. Setting the property to non-null value will ignore the test data source.
/// </summary>
string? IgnoreMessage { get; set; }
}
11 changes: 11 additions & 0 deletions src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
#nullable enable
Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute.IgnoreMessage.get -> string?
Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute.IgnoreMessage.set -> void
Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataAttribute.DynamicDataAttribute(string! dynamicDataSourceName) -> void
Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataAttribute.DynamicDataAttribute(string! dynamicDataSourceName, Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataSourceType dynamicDataSourceType) -> void
Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataAttribute.DynamicDataAttribute(string! dynamicDataSourceName, System.Type! dynamicDataDeclaringType) -> void
Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataAttribute.DynamicDataAttribute(string! dynamicDataSourceName, System.Type! dynamicDataDeclaringType, Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataSourceType dynamicDataSourceType) -> void
Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataAttribute.IgnoreMessage.get -> string?
Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataAttribute.IgnoreMessage.set -> void
Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataSourceType.AutoDetect = 2 -> Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataSourceType
*REMOVED*Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataAttribute.DynamicDataAttribute(string! dynamicDataSourceName, Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataSourceType dynamicDataSourceType = Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataSourceType.Property) -> void
*REMOVED*Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataAttribute.DynamicDataAttribute(string! dynamicDataSourceName, System.Type! dynamicDataDeclaringType, Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataSourceType dynamicDataSourceType = Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataSourceType.Property) -> void
Microsoft.VisualStudio.TestTools.UnitTesting.ITestDataSourceIgnoreCapability
Youssef1313 marked this conversation as resolved.
Show resolved Hide resolved
Microsoft.VisualStudio.TestTools.UnitTesting.ITestDataSourceIgnoreCapability.IgnoreMessage.get -> string?
Microsoft.VisualStudio.TestTools.UnitTesting.ITestDataSourceIgnoreCapability.IgnoreMessage.set -> void
Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute.IgnoreMessage.get -> string?
Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute.IgnoreMessage.set -> void
Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute.IgnoreMessage.get -> string?
Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute.IgnoreMessage.set -> void
static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.Throws<TException>(System.Action! action, string! message = "", params object![]! messageArgs) -> TException!
static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.ThrowsAsync<TException>(System.Func<System.Threading.Tasks.Task!>! action, string! message = "", params object![]! messageArgs) -> System.Threading.Tasks.Task<TException!>!
static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.ThrowsExactly<TException>(System.Action! action, string! message = "", params object![]! messageArgs) -> TException!
Expand Down
Loading