Skip to content

Commit 930bead

Browse files
committed
CSHARP-5798: Implement test to track if any UnobservedTaskExceptions were raised while test run
1 parent 8c51ba0 commit 930bead

File tree

6 files changed

+157
-5
lines changed

6 files changed

+157
-5
lines changed

src/MongoDB.Driver/Core/Clusters/Cluster.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ private sealed class ServerSelectionWaitQueue : IDisposable
445445
private readonly InterlockedInt32 _rapidHeartbeatTimerCallbackState;
446446

447447
private int _serverSelectionWaitQueueSize;
448+
private bool _disposed;
448449

449450
public ServerSelectionWaitQueue(Cluster cluster)
450451
{
@@ -455,6 +456,7 @@ public ServerSelectionWaitQueue(Cluster cluster)
455456

456457
public void Dispose()
457458
{
459+
_disposed = true;
458460
_rapidHeartbeatTimer.Dispose();
459461
}
460462

@@ -489,6 +491,11 @@ private void ExitServerSelectionWaitQueue()
489491
{
490492
if (--_serverSelectionWaitQueueSize == 0)
491493
{
494+
if (_disposed)
495+
{
496+
return;
497+
}
498+
492499
_rapidHeartbeatTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
493500
}
494501
}

tests/MongoDB.Driver.Tests/Core/Jira/CSharp3302Tests.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,15 @@ public async Task RapidHeartbeatTimerCallback_should_ignore_reentrant_calls()
9696
cluster.Initialize();
9797

9898
// Trigger Cluster._rapidHeartbeatTimer
99-
_ = cluster.SelectServerAsync(OperationContext.NoTimeout, CreateWritableServerAndEndPointSelector(__endPoint1));
99+
using var cancellationTokenSource = new CancellationTokenSource();
100+
var operationContext = new OperationContext(Timeout.InfiniteTimeSpan, cancellationTokenSource.Token);
101+
cluster.SelectServerAsync(operationContext, CreateWritableServerAndEndPointSelector(__endPoint1))
102+
.IgnoreExceptions();
100103

101104
// Wait for all heartbeats to complete
102105
await Task.WhenAny(allHeartbeatsReceived.Task, Task.Delay(1000));
106+
107+
cancellationTokenSource.Cancel();
103108
}
104109

105110
allHeartbeatsReceived.Task.Status.Should().Be(TaskStatus.RanToCompletion);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using FluentAssertions;
17+
using MongoDB.TestHelpers.XunitExtensions.TimeoutEnforcing;
18+
19+
namespace MongoDB.Driver.Tests;
20+
21+
public class UnobservedTaskExceptionTracking
22+
{
23+
[UnobservedExceptionTrackingFact]
24+
public void EnsureNoUnobservedTaskException()
25+
{
26+
UnobservedExceptionTrackingTestCase.__unobservedExceptions.Should().BeEmpty();
27+
}
28+
}
29+

tests/MongoDB.TestHelpers/XunitExtensions/TimeoutEnforcing/TimeoutEnforcingXunitTestAssemblyRunner.cs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
using System.Collections.Generic;
1717
using System.Diagnostics;
18+
using System.Linq;
1819
using System.Threading;
1920
using System.Threading.Tasks;
2021
using Xunit.Abstractions;
@@ -25,20 +26,50 @@ namespace MongoDB.TestHelpers.XunitExtensions.TimeoutEnforcing
2526
[DebuggerStepThrough]
2627
internal sealed class TimeoutEnforcingXunitTestAssemblyRunner : XunitTestAssemblyRunner
2728
{
29+
private readonly UnobservedExceptionTrackingTestCase _unobservedExceptionTrackingTestCase;
30+
2831
public TimeoutEnforcingXunitTestAssemblyRunner(
2932
ITestAssembly testAssembly,
3033
IEnumerable<IXunitTestCase> testCases,
3134
IMessageSink diagnosticMessageSink,
3235
IMessageSink executionMessageSink,
3336
ITestFrameworkExecutionOptions executionOptions)
34-
: base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions)
35-
{ }
37+
: base(testAssembly, testCases.Where(t => t is not UnobservedExceptionTrackingTestCase), diagnosticMessageSink, executionMessageSink, executionOptions)
38+
{
39+
_unobservedExceptionTrackingTestCase = (UnobservedExceptionTrackingTestCase)testCases.SingleOrDefault(t => t is UnobservedExceptionTrackingTestCase);
40+
}
3641

3742
protected override Task<RunSummary> RunTestCollectionAsync(
3843
IMessageBus messageBus,
3944
ITestCollection testCollection,
4045
IEnumerable<IXunitTestCase> testCases,
41-
CancellationTokenSource cancellationTokenSource) =>
42-
new TimeoutEnforcingXunitTestCollectionRunner(testCollection, testCases,DiagnosticMessageSink, messageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), cancellationTokenSource).RunAsync();
46+
CancellationTokenSource cancellationTokenSource)
47+
{
48+
return new TimeoutEnforcingXunitTestCollectionRunner(testCollection, testCases, DiagnosticMessageSink, messageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), cancellationTokenSource).RunAsync();
49+
}
50+
51+
protected override async Task<RunSummary> RunTestCollectionsAsync(IMessageBus messageBus, CancellationTokenSource cancellationTokenSource)
52+
{
53+
var baseSummary = await base.RunTestCollectionsAsync(messageBus, cancellationTokenSource);
54+
55+
if (_unobservedExceptionTrackingTestCase == null)
56+
{
57+
return baseSummary;
58+
}
59+
60+
var unobservedExceptionTestCaseRunSummary = await RunTestCollectionAsync(
61+
messageBus,
62+
_unobservedExceptionTrackingTestCase.TestMethod.TestClass.TestCollection,
63+
[_unobservedExceptionTrackingTestCase],
64+
cancellationTokenSource);
65+
66+
return new RunSummary
67+
{
68+
Total = baseSummary.Total + unobservedExceptionTestCaseRunSummary.Total,
69+
Failed = baseSummary.Failed + unobservedExceptionTestCaseRunSummary.Failed,
70+
Skipped = baseSummary.Skipped + unobservedExceptionTestCaseRunSummary.Skipped,
71+
Time = baseSummary.Time + unobservedExceptionTestCaseRunSummary.Time
72+
};
73+
}
4374
}
4475
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System.Collections.Generic;
17+
using Xunit;
18+
using Xunit.Abstractions;
19+
using Xunit.Sdk;
20+
21+
namespace MongoDB.TestHelpers.XunitExtensions.TimeoutEnforcing;
22+
23+
[XunitTestCaseDiscoverer("MongoDB.TestHelpers.XunitExtensions.TimeoutEnforcing.UnobservedExceptionTestDiscoverer", "MongoDB.TestHelpers")]
24+
public class UnobservedExceptionTrackingFactAttribute: FactAttribute
25+
{}
26+
27+
public class UnobservedExceptionTestDiscoverer(IMessageSink DiagnosticsMessageSink) : IXunitTestCaseDiscoverer
28+
{
29+
public IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) =>
30+
[new UnobservedExceptionTrackingTestCase(DiagnosticsMessageSink, testMethod)];
31+
}
32+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System.Collections.Generic;
17+
using System.Threading.Tasks;
18+
using Xunit.Abstractions;
19+
using Xunit.Sdk;
20+
21+
namespace MongoDB.TestHelpers.XunitExtensions.TimeoutEnforcing;
22+
23+
public sealed class UnobservedExceptionTrackingTestCase : XunitTestCase
24+
{
25+
public static readonly List<string> __unobservedExceptions = new();
26+
27+
#pragma warning disable CS0618 // Type or member is obsolete
28+
public UnobservedExceptionTrackingTestCase()
29+
{
30+
}
31+
#pragma warning restore CS0618 // Type or member is obsolete
32+
33+
public UnobservedExceptionTrackingTestCase(IMessageSink diagnosticMessageSink, ITestMethod testMethod)
34+
: base(diagnosticMessageSink, TestMethodDisplay.Method, TestMethodDisplayOptions.All, testMethod)
35+
{
36+
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionEventHandler;
37+
}
38+
39+
public override void Dispose()
40+
{
41+
base.Dispose();
42+
TaskScheduler.UnobservedTaskException -= UnobservedTaskExceptionEventHandler;
43+
}
44+
45+
void UnobservedTaskExceptionEventHandler(object sender, UnobservedTaskExceptionEventArgs unobservedException) =>
46+
__unobservedExceptions.Add(unobservedException.Exception.ToString());
47+
}
48+

0 commit comments

Comments
 (0)