Skip to content

Commit

Permalink
Fix [Explicit] attribute not being honoured when running from IDE Tes…
Browse files Browse the repository at this point in the history
…t Explorers (#1739)
  • Loading branch information
thomhurst authored Feb 1, 2025
1 parent 9e12836 commit f5bda1e
Show file tree
Hide file tree
Showing 8 changed files with 34 additions and 54 deletions.
4 changes: 1 addition & 3 deletions TUnit.Engine/Framework/TUnitServiceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,12 @@ public TUnitServiceProvider(IExtension extension,
Disposer = Register(new Disposer(Logger));

var testInvoker = Register(new TestInvoker(testHookOrchestrator, Logger, Disposer));
var explicitFilterService = Register(new ExplicitFilterService());
var parallelLimitProvider = Register(new ParallelLimitLockProvider());

// TODO
Register(new HookMessagePublisher(extension, messageBus));

var singleTestExecutor = Register(new SingleTestExecutor(extension, instanceTracker, testInvoker,
explicitFilterService, parallelLimitProvider, AssemblyHookOrchestrator, classHookOrchestrator, TestFinder, TUnitMessageBus, Logger, EngineCancellationToken, testRegistrar));
var singleTestExecutor = Register(new SingleTestExecutor(extension, instanceTracker, testInvoker, parallelLimitProvider, AssemblyHookOrchestrator, classHookOrchestrator, TestFinder, TUnitMessageBus, Logger, EngineCancellationToken, testRegistrar));

TestsExecutor = Register(new TestsExecutor(singleTestExecutor, Logger, CommandLineOptions, EngineCancellationToken));

Expand Down
2 changes: 1 addition & 1 deletion TUnit.Engine/Models/GroupedTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace TUnit.Engine.Models;

internal record GroupedTests
{
public required DiscoveredTest[] AllValidTests { get; init; }
public required IReadOnlyCollection<DiscoveredTest> AllValidTests { get; init; }
public required PriorityQueue<DiscoveredTest, int> NotInParallel { get; init; }
public required IDictionary<ConstraintKeysCollection, PriorityQueue<DiscoveredTest, int>> KeyedNotInParallel { get; init; }
public required IList<DiscoveredTest> Parallel { get; init; }
Expand Down
29 changes: 0 additions & 29 deletions TUnit.Engine/Services/ExplicitFilterService.cs

This file was deleted.

6 changes: 0 additions & 6 deletions TUnit.Engine/Services/SingleTestExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ internal class SingleTestExecutor(
IExtension extension,
InstanceTracker instanceTracker,
TestInvoker testInvoker,
ExplicitFilterService explicitFilterService,
ParallelLimitLockProvider parallelLimitLockProvider,
AssemblyHookOrchestrator assemblyHookOrchestrator,
ClassHookOrchestrator classHookOrchestrator,
Expand Down Expand Up @@ -79,11 +78,6 @@ private async Task ExecuteTestInternalAsync(DiscoveredTest test, ITestExecutionF

await RegisterIfNotAlready(testContext);

if (!explicitFilterService.CanRun(test.TestDetails, filter))
{
throw new SkipTestException("Test with ExplicitAttribute was not explicitly run.");
}

if (testContext.SkipReason != null)
{
throw new SkipTestException(testContext.SkipReason);
Expand Down
4 changes: 2 additions & 2 deletions TUnit.Engine/Services/TUnitTestDiscoverer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ public async Task<GroupedTests> FilterTests(ExecuteRequestContext context, strin

var executionRequest = context.Request as TestExecutionRequest;

var filteredTests = testFilterService.FilterTests(executionRequest?.Filter, allDiscoveredTests).ToArray();
var filteredTests = testFilterService.FilterTests(executionRequest, allDiscoveredTests);

await logger.LogTraceAsync($"Found {filteredTests.Length} tests after filtering.");
await logger.LogTraceAsync($"Found {filteredTests.Count} tests after filtering.");

var organisedTests = testGrouper.OrganiseTests(filteredTests);

Expand Down
32 changes: 26 additions & 6 deletions TUnit.Engine/Services/TestFilterService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Microsoft.Testing.Platform.Extensions.Messages;
#pragma warning disable TPEXP

using Microsoft.Testing.Platform.Extensions.Messages;
using Microsoft.Testing.Platform.Logging;
using Microsoft.Testing.Platform.Requests;
using TUnit.Core;
Expand All @@ -9,20 +11,38 @@ internal class TestFilterService(ILoggerFactory loggerFactory)
{
private readonly ILogger<TestFilterService> _logger = loggerFactory.CreateLogger<TestFilterService>();

public IEnumerable<DiscoveredTest> FilterTests(ITestExecutionFilter? testExecutionFilter, IEnumerable<DiscoveredTest> testNodes)
public IReadOnlyCollection<DiscoveredTest> FilterTests(TestExecutionRequest? testExecutionRequest, IReadOnlyCollection<DiscoveredTest> testNodes)
{
#pragma warning disable TPEXP
var testExecutionFilter = testExecutionRequest?.Filter;

if (testExecutionFilter is null or NopFilter)
#pragma warning restore TPEXP
{
_logger.LogTrace("No test filter found.");


if (testExecutionRequest is RunTestExecutionRequest)
{
return testNodes
.Where(x => !x.TestDetails.Attributes.OfType<ExplicitAttribute>().Any())
.ToArray();
}

return testNodes;
}

_logger.LogTrace($"Test filter is: {testExecutionFilter.GetType().Name}");

return testNodes.Where(x => MatchesTest(testExecutionFilter, x));
var filteredTests = testNodes.Where(x => MatchesTest(testExecutionFilter, x)).ToArray();

var testsWithExplicitAttributeCount = filteredTests.Count(x => x.TestDetails.Attributes.OfType<ExplicitAttribute>().Any());

if (testsWithExplicitAttributeCount > 0 && testsWithExplicitAttributeCount < filteredTests.Length)
{
return testNodes
.Where(x => !x.TestDetails.Attributes.OfType<ExplicitAttribute>().Any())
.ToArray();
}

return filteredTests;
}

public bool MatchesTest(ITestExecutionFilter? testExecutionFilter, DiscoveredTest discoveredTest)
Expand Down
2 changes: 1 addition & 1 deletion TUnit.Engine/Services/TestGrouper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace TUnit.Engine.Services;

internal class TestGrouper
{
public GroupedTests OrganiseTests(DiscoveredTest[] testCases)
public GroupedTests OrganiseTests(IReadOnlyCollection<DiscoveredTest> testCases)
{
var notInParallel = new PriorityQueue<DiscoveredTest, int>();
var keyedNotInParallel = new ConcurrentDictionary<ConstraintKeysCollection, PriorityQueue<DiscoveredTest, int>>();
Expand Down
9 changes: 3 additions & 6 deletions docs/docs/tutorial-extras/explicit.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@ If you want a test to only be run explicitly (and not part of all general tests)

This can be added to a test method or a test class.

If added to a test method, then all tests generated from that test method (different test cases generated from different test data) will only be run if that method has been explicitly run.
If added to a test class, then all tests generated within that class will only be run if only tests within that class have been explicitly run.
A test is considered 'explicitly' run when all filtered tests have an explicit attribute on them.

A test is considered 'explicitly' run when:
- A test filter was used and matches your test
e.g. `dotnet run --treenode-filter "/*/*/*/*"`
- You ran your test from within the test explorer in your IDE specifically
That means that you could run all tests in a class with an `[Explicit]` attribute. Or you could run a single method with an `[Explicit]` attribute. But if you try to run a mix of explicit and non-explicit tests, then the ones with an `[Explicit]` attribute will be excluded from the run.

This can be useful for 'Tests' that make sense in a local environment, and maybe not part of your CI builds. Or they could be helpers that ping things to warm them up, and by making them explicit tests, they are easily runnable, but don't affect your overall test suite.

```csharp
using TUnit.Core;
Expand Down

0 comments on commit f5bda1e

Please sign in to comment.