Skip to content

Commit 374f964

Browse files
authored
Release of new minor version v1.20
Release of new minor version v1.20
2 parents 69732f6 + 6961cf3 commit 374f964

File tree

17 files changed

+156
-35
lines changed

17 files changed

+156
-35
lines changed

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ All notable changes to **bUnit** will be documented in this file. The project ad
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- Added static `DefaultWaitTimeout` property to `TestContext` to enable overriding the default timeout of "wait for" methods like `WaitForAssertion` from 1 second to something else. By [@egil](https://github.com/egil).
12+
13+
### Fixed
14+
15+
- TestRenderer throws `ObjectDisposedException` if any methods is accessed after it has been disposed. It will also prevent changes to the internal render tree after it has been disposed. By [@egil](https://github.com/egil).
16+
17+
918
## [1.19.14] - 2023-04-26
1019

1120
### Fixed
@@ -33,7 +42,7 @@ All notable changes to **bUnit** will be documented in this file. The project ad
3342

3443
## [1.15.5] - 2023-02-04
3544

36-
- Upgrade AngleSharp.Diffing to 0.17.1.
45+
- Upgrade AngleSharp.Diffing to 0.17.1.
3746

3847
## [1.14.4] - 2023-01-11
3948

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
<!-- Shared code analyzers used for all projects in the solution -->
5151
<ItemGroup Label="Code Analyzers">
5252
<PackageReference Include="AsyncFixer" Version="1.6.0" PrivateAssets="All" />
53-
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.56.0.67649" PrivateAssets="All" />
53+
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.0.0.68202" PrivateAssets="All" />
5454
</ItemGroup>
5555

5656
<ItemGroup Label="Implicit usings"

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444
<ItemGroup>
4545
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
46-
<PackageReference Include="Nerdbank.GitVersioning" Version="3.5.119" PrivateAssets="All" />
46+
<PackageReference Include="Nerdbank.GitVersioning" Version="3.6.132" PrivateAssets="All" />
4747

4848
</ItemGroup>
4949

src/bunit.core/Extensions/WaitForHelpers/WaitForHelper.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ protected WaitForHelper(
5151
{
5252
this.renderedFragment = renderedFragment ?? throw new ArgumentNullException(nameof(renderedFragment));
5353
this.completeChecker = completeChecker ?? throw new ArgumentNullException(nameof(completeChecker));
54-
54+
5555
logger = renderedFragment.Services.CreateLogger<WaitForHelper<T>>();
5656
renderer = (TestRenderer)renderedFragment
5757
.Services
@@ -125,7 +125,7 @@ private void InitializeWaiting()
125125
}
126126

127127
private Task<T> CreateWaitTask()
128-
{
128+
{
129129
// Two to failure conditions, that the renderer captures an unhandled
130130
// exception from a component or itself, or that the timeout is reached,
131131
// are executed on the renderers scheduler, to ensure that OnAfterRender
@@ -194,6 +194,6 @@ private void SubscribeToOnAfterRender()
194194

195195
private static TimeSpan GetRuntimeTimeout(TimeSpan? timeout)
196196
{
197-
return timeout ?? TimeSpan.FromSeconds(1);
197+
return timeout ?? TestContextBase.DefaultWaitTimeout;
198198
}
199199
}

src/bunit.core/Rendering/TestRenderer.cs

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class TestRenderer : Renderer, ITestRenderer
1414
private readonly List<RootComponent> rootComponents = new();
1515
private readonly ILogger<TestRenderer> logger;
1616
private readonly IRenderedComponentActivator activator;
17+
private bool disposed;
1718
private TaskCompletionSource<Exception> unhandledExceptionTsc = new(TaskCreationOptions.RunContinuationsAsynchronously);
1819
private Exception? capturedUnhandledException;
1920

@@ -81,6 +82,9 @@ public Task DispatchEventAsync(
8182
if (fieldInfo is null)
8283
throw new ArgumentNullException(nameof(fieldInfo));
8384

85+
if (disposed)
86+
throw new ObjectDisposedException(nameof(TestRenderer));
87+
8488
// Calling base.DispatchEventAsync updates the render tree
8589
// if the event contains associated data.
8690
lock (renderTreeUpdateLock)
@@ -134,6 +138,9 @@ public IReadOnlyList<IRenderedComponentBase<TComponent>> FindComponents<TCompone
134138
/// <inheritdoc />
135139
public void DisposeComponents()
136140
{
141+
if (disposed)
142+
throw new ObjectDisposedException(nameof(TestRenderer));
143+
137144
// The dispatcher will always return a completed task,
138145
// when dealing with an IAsyncDisposable.
139146
// Therefore checking for a completed task and awaiting it
@@ -160,6 +167,12 @@ public void DisposeComponents()
160167
/// <inheritdoc/>
161168
protected override void ProcessPendingRender()
162169
{
170+
if (disposed)
171+
{
172+
logger.LogRenderCycleActiveAfterDispose();
173+
return;
174+
}
175+
163176
// Blocks updates to the renderers internal render tree
164177
// while the render tree is being read elsewhere.
165178
// base.ProcessPendingRender calls UpdateDisplayAsync,
@@ -173,6 +186,12 @@ protected override void ProcessPendingRender()
173186
/// <inheritdoc/>
174187
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
175188
{
189+
if (disposed)
190+
{
191+
logger.LogRenderCycleActiveAfterDispose();
192+
return Task.CompletedTask;
193+
}
194+
176195
if (usersSyncContext is not null && usersSyncContext != SynchronizationContext.Current)
177196
{
178197
// The users' sync context, typically one established by
@@ -207,6 +226,12 @@ private void UpdateDisplay(in RenderBatch renderBatch)
207226
RenderCount++;
208227
var renderEvent = new RenderEvent(renderBatch, new RenderTreeFrameDictionary());
209228

229+
if (disposed)
230+
{
231+
logger.LogRenderCycleActiveAfterDispose();
232+
return;
233+
}
234+
210235
// removes disposed components
211236
for (var i = 0; i < renderBatch.DisposedComponentIDs.Count; i++)
212237
{
@@ -243,23 +268,34 @@ private void UpdateDisplay(in RenderBatch renderBatch)
243268
/// <inheritdoc/>
244269
protected override void Dispose(bool disposing)
245270
{
246-
if (disposing)
271+
if (disposed)
272+
return;
273+
274+
disposed = true;
275+
276+
lock (renderTreeUpdateLock)
247277
{
248-
foreach (var rc in renderedComponents.Values)
278+
if (disposing)
249279
{
250-
rc.Dispose();
280+
foreach (var rc in renderedComponents.Values)
281+
{
282+
rc.Dispose();
283+
}
284+
285+
renderedComponents.Clear();
286+
unhandledExceptionTsc.TrySetCanceled();
251287
}
252288

253-
renderedComponents.Clear();
254-
unhandledExceptionTsc.TrySetCanceled();
289+
base.Dispose(disposing);
255290
}
256-
257-
base.Dispose(disposing);
258291
}
259292

260293
private TResult Render<TResult>(RenderFragment renderFragment, Func<int, TResult> activator)
261294
where TResult : IRenderedFragmentBase
262295
{
296+
if (disposed)
297+
throw new ObjectDisposedException(nameof(TestRenderer));
298+
263299
var renderTask = Dispatcher.InvokeAsync(() =>
264300
{
265301
ResetUnhandledException();
@@ -298,6 +334,9 @@ private IReadOnlyList<IRenderedComponentBase<TComponent>> FindComponents<TCompon
298334
if (parentComponent is null)
299335
throw new ArgumentNullException(nameof(parentComponent));
300336

337+
if (disposed)
338+
throw new ObjectDisposedException(nameof(TestRenderer));
339+
301340
var result = new List<IRenderedComponentBase<TComponent>>();
302341
var framesCollection = new RenderTreeFrameDictionary();
303342

@@ -387,7 +426,7 @@ private ArrayRange<RenderTreeFrame> GetOrLoadRenderTreeFrame(RenderTreeFrameDict
387426
/// <inheritdoc/>
388427
protected override void HandleException(Exception exception)
389428
{
390-
if (exception is null)
429+
if (exception is null || disposed)
391430
return;
392431

393432
logger.LogUnhandledException(exception);
@@ -411,7 +450,7 @@ private void ResetUnhandledException()
411450

412451
private void AssertNoUnhandledExceptions()
413452
{
414-
if (capturedUnhandledException is Exception unhandled)
453+
if (capturedUnhandledException is Exception unhandled && !disposed)
415454
{
416455
capturedUnhandledException = null;
417456

src/bunit.core/Rendering/TestRendererLoggerExtensions.cs

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,78 @@ internal static class TestRendererLoggerExtensions
2828
private static readonly Action<ILogger, string, string, Exception> UnhandledException
2929
= LoggerMessage.Define<string, string>(LogLevel.Error, new EventId(30, "LogUnhandledException"), "An unhandled exception happened during rendering: {Message}" + Environment.NewLine + "{StackTrace}");
3030

31+
private static readonly Action<ILogger, Exception?> RenderCycleActiveAfterDispose
32+
= LoggerMessage.Define(LogLevel.Warning, new EventId(31, "LogRenderCycleActiveAfterDispose"), "A component attempted to update the render tree after the renderer was disposed.");
33+
3134
internal static void LogProcessingPendingRenders(this ILogger<TestRenderer> logger)
32-
=> ProcessingPendingRenders(logger, null);
35+
{
36+
if (logger.IsEnabled(LogLevel.Debug))
37+
{
38+
ProcessingPendingRenders(logger, null);
39+
}
40+
}
3341

3442
internal static void LogNewRenderBatchReceived(this ILogger<TestRenderer> logger)
35-
=> NewRenderBatchReceived(logger, null);
43+
{
44+
if (logger.IsEnabled(LogLevel.Debug))
45+
{
46+
NewRenderBatchReceived(logger, null);
47+
}
48+
}
3649

3750
internal static void LogComponentDisposed(this ILogger<TestRenderer> logger, int componentId)
38-
=> ComponentDisposed(logger, componentId, null);
51+
{
52+
if (logger.IsEnabled(LogLevel.Debug))
53+
{
54+
ComponentDisposed(logger, componentId, null);
55+
}
56+
}
3957

4058
internal static void LogComponentRendered(this ILogger<TestRenderer> logger, int componentId)
41-
=> ComponentRendered(logger, componentId, null);
59+
{
60+
if (logger.IsEnabled(LogLevel.Debug))
61+
{
62+
ComponentRendered(logger, componentId, null);
63+
}
64+
}
4265

4366
internal static void LogChangedComponentsMarkupUpdated(this ILogger<TestRenderer> logger)
44-
=> ChangedComponentsMarkupUpdated(logger, null);
67+
{
68+
if (logger.IsEnabled(LogLevel.Debug))
69+
{
70+
ChangedComponentsMarkupUpdated(logger, null);
71+
}
72+
}
4573

4674
internal static void LogAsyncInitialRender(this ILogger<TestRenderer> logger)
47-
=> AsyncInitialRender(logger, null);
75+
{
76+
if (logger.IsEnabled(LogLevel.Debug))
77+
{
78+
AsyncInitialRender(logger, null);
79+
}
80+
}
4881

4982
internal static void LogInitialRenderCompleted(this ILogger<TestRenderer> logger, int componentId)
50-
=> InitialRenderCompleted(logger, componentId, null);
83+
{
84+
if (logger.IsEnabled(LogLevel.Debug))
85+
{
86+
InitialRenderCompleted(logger, componentId, null);
87+
}
88+
}
5189

5290
internal static void LogUnhandledException(this ILogger<TestRenderer> logger, Exception exception)
53-
=> UnhandledException(logger, exception.Message, exception.StackTrace ?? string.Empty, exception);
91+
{
92+
if (logger.IsEnabled(LogLevel.Error))
93+
{
94+
UnhandledException(logger, exception.Message, exception.StackTrace ?? string.Empty, exception);
95+
}
96+
}
97+
98+
internal static void LogRenderCycleActiveAfterDispose(this ILogger<TestRenderer> logger)
99+
{
100+
if (logger.IsEnabled(LogLevel.Warning))
101+
{
102+
RenderCycleActiveAfterDispose(logger, null);
103+
}
104+
}
54105
}

src/bunit.core/TestContextBase.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ public abstract class TestContextBase : IDisposable
1010
private bool disposed;
1111
private ITestRenderer? testRenderer;
1212

13+
/// <summary>
14+
/// Gets or sets the default wait timeout used by "WaitFor" operations, i.e. <see cref="RenderedFragmentWaitForHelperExtensions.WaitForAssertion(IRenderedFragmentBase, Action, TimeSpan?)"/>.
15+
/// </summary>
16+
/// <remarks>The default is 1 second.</remarks>
17+
public static TimeSpan DefaultWaitTimeout { get; set; } = TimeSpan.FromSeconds(1);
18+
1319
/// <summary>
1420
/// Gets the renderer used by the test context.
1521
/// </summary>

tests/Directory.Build.props

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@
88

99
<PropertyGroup Label="Compile settings" Condition="$(MSBuildProjectName) != 'bunit.testassets'">
1010
<Nullable>annotations</Nullable>
11-
<IsPackable>false</IsPackable>
11+
<IsPackable>false</IsPackable>
1212
<SonarQubeTestProject>true</SonarQubeTestProject>
1313
<IsTestProject>true</IsTestProject>
1414
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
1515
</PropertyGroup>
1616

17-
<ItemGroup Condition="$(MSBuildProjectName) != 'bunit.testassets'">
17+
<ItemGroup Condition="$(MSBuildProjectName) != 'bunit.testassets' and $(MSBuildProjectName) != 'bunit.web.testcomponents.tests'">
1818
<PackageReference Include="AutoFixture" Version="4.18.0" />
1919
<PackageReference Include="AutoFixture.Xunit2" Version="4.18.0" />
20-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
21-
<PackageReference Include="Moq" Version="4.18.4" />
22-
<PackageReference Include="Shouldly" Version="4.1.0" />
20+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
21+
<PackageReference Include="Moq" Version="4.18.4" />
22+
<PackageReference Include="Shouldly" Version="4.2.1" />
2323
<PackageReference Include="xunit" Version="2.4.2" />
2424
<PackageReference Include="Xunit.Combinatorial" Version="1.5.25" />
2525
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" PrivateAssets="All" />

tests/bunit.core.tests/Rendering/TestRendererTest.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public partial class TestRendererTest : TestContext
88
{
99
public TestRendererTest(ITestOutputHelper outputHelper)
1010
{
11+
TestContext.DefaultWaitTimeout = TimeSpan.FromSeconds(30);
1112
Services.AddXunitLogger(outputHelper);
1213
}
1314

tests/bunit.testassets/bunit.testassets.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<PackageReference Include="xunit.assert" Version="2.4.2" />
1818
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
1919
<PackageReference Include="Serilog" Version="2.12.0" />
20-
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
20+
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
2121
</ItemGroup>
2222

2323
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">

0 commit comments

Comments
 (0)