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 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 TestDataSourceIgnoreReasonProperty = TestProperty.Register("MSTest.TestDataSourceIgnoreReasonProperty", "TestDataSourceIgnoreReasonProperty", 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? testDataSourceIgnoreReason = (dataSource as ITestDataSourceIgnoreCapability)?.Ignore;

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.TestDataSourceIgnoreReason = testDataSourceIgnoreReason;
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.TestDataSourceIgnoreReason = testDataSourceIgnoreReason;
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.Ignore 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.Ignore 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
19 changes: 19 additions & 0 deletions src/Adapter/MSTest.TestAdapter/Execution/TestMethodRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ internal UnitTestResult[] RunTestMethod()
{
if (_test.DataType == DynamicDataType.ITestDataSource)
{
if (_test.TestDataSourceIgnoreReason is not null)
{
return [new(UnitTestOutcome.Ignored, _test.TestDataSourceIgnoreReason)];
}

object?[]? data = DataSerializationHelper.Deserialize(_test.SerializedData);
TestResult[] testResults = ExecuteTestWithDataSource(null, data);
results.AddRange(testResults);
Expand Down Expand Up @@ -306,6 +311,20 @@ private bool ExecuteDataSourceBasedTests(List<TestResult> results)
foreach (UTF.ITestDataSource testDataSource in testDataSources)
{
isDataDriven = true;

if (testDataSource is ITestDataSourceIgnoreCapability { Ignore: { } ignoreReason })
{
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,
Copy link
Member

Choose a reason for hiding this comment

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

It's probably good to have more states but I will have a closer look later.

IgnoreReason = ignoreReason,
});
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.TestMethodOptions.Executor?.Ignore ?? testMethodInfo.Parent.ClassAttribute.Ignore;
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.TestDataSourceIgnoreReason = testCase.GetPropertyValue(Constants.TestDataSourceIgnoreReasonProperty) 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? TestDataSourceIgnoreReason { 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; }
Comment on lines -36 to -39
Copy link
Member Author

Choose a reason for hiding this comment

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

This was never read (except in a unit test) and is adding noise to the code. The class is internal so I removed it altogether


/// <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.TestDataSourceIgnoreReasonProperty, TestMethod.TestDataSourceIgnoreReason);
}

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? Ignore { 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 />

public string? Ignore { 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? Ignore { 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? Ignore { 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?

{
/// <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? Ignore { 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.Ignore.get -> string?
Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute.Ignore.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.Ignore.get -> string?
Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataAttribute.Ignore.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
Copy link
Member Author

Choose a reason for hiding this comment

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

TODO: Discuss the naming. Possible options:

  • Ignore
  • IgnoreReason
  • IgnoreMessage

Note that the property on the existing IgnoreAttribute is named IgnoreMessage. So we may want to keep the consistency.

Copy link
Member

Choose a reason for hiding this comment

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

I like the IgnoreMessage in case we start adding more "options" linked to ignoring a test (e.g. I like NUnit concept of Until on the [Ignore] attribute). I guess we could also think about creating a IgnoreOptions type (I never recall the limitations of types in attributes).

Microsoft.VisualStudio.TestTools.UnitTesting.ITestDataSourceIgnoreCapability.Ignore.get -> string?
Microsoft.VisualStudio.TestTools.UnitTesting.ITestDataSourceIgnoreCapability.Ignore.set -> void
Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute.Ignore.get -> string?
Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute.Ignore.set -> void
Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute.Ignore.get -> string?
Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute.Ignore.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
Original file line number Diff line number Diff line change
Expand Up @@ -291,18 +291,6 @@ public void GetTestFromMethodShouldInitializeAsyncTypeNameCorrectly()
Verify(expectedAsyncTaskName == testElement.AsyncTypeName);
}

public void GetTestFromMethodShouldSetIgnoredPropertyToFalseIfNotSetOnTestClassAndTestMethod()
{
SetupTestClassAndTestMethods(isValidTestClass: true, isValidTestMethod: true, isMethodFromSameAssembly: true);
TypeEnumerator typeEnumerator = GetTypeEnumeratorInstance(typeof(DummyTestClass), "DummyAssemblyName");
MethodInfo methodInfo = typeof(DummyTestClass).GetMethod("MethodWithVoidReturnType");

MSTest.TestAdapter.ObjectModel.UnitTestElement testElement = typeEnumerator.GetTestFromMethod(methodInfo, true, _warnings);

Verify(testElement is not null);
Verify(!testElement.Ignored);
}

public void GetTestFromMethodShouldSetTestCategory()
{
SetupTestClassAndTestMethods(isValidTestClass: true, isValidTestMethod: true, isMethodFromSameAssembly: true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public void RunTestsForTestWithFilterShouldSendResultsForFilteredTests()

public void RunTestsForIgnoredTestShouldSendResultsMarkingIgnoredTestsAsSkipped()
{
TestCase testCase = GetTestCase(typeof(DummyTestClass), "IgnoredTest", ignore: true);
TestCase testCase = GetTestCase(typeof(DummyTestClass), "IgnoredTest");
TestCase[] tests = [testCase];

_testExecutionManager.RunTests(tests, _runContext, _frameworkHandle, _cancellationToken);
Expand Down Expand Up @@ -814,14 +814,11 @@ private void RunTestsForTestShouldRunTestsInTheParentDomainsApartmentState()

#region private methods

private static TestCase GetTestCase(Type typeOfClass, string testName, bool ignore = false)
private static TestCase GetTestCase(Type typeOfClass, string testName)
{
MethodInfo methodInfo = typeOfClass.GetMethod(testName);
var testMethod = new TestMethod(methodInfo.Name, typeOfClass.FullName, Assembly.GetExecutingAssembly().Location, isAsync: false);
UnitTestElement element = new(testMethod)
{
Ignored = ignore,
};
UnitTestElement element = new(testMethod);
return element.ToTestCase();
}

Expand Down