Skip to content

Commit 8ac9034

Browse files
committed
tests
1 parent bab2daf commit 8ac9034

File tree

9 files changed

+460
-5
lines changed

9 files changed

+460
-5
lines changed

src/Components/Components/src/RenderTree/Renderer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,7 @@ private void RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
932932
{
933933
var componentState = renderQueueEntry.ComponentState;
934934
Log.RenderingComponent(_logger, componentState);
935-
var startTime = ((bool)_renderingMetrics?.IsDurationEnabled()) ? Stopwatch.GetTimestamp() : 0;
935+
var startTime = (_renderingMetrics != null && _renderingMetrics.IsDurationEnabled()) ? Stopwatch.GetTimestamp() : 0;
936936
_renderingMetrics?.RenderStart(componentState.Component.GetType().FullName);
937937
componentState.RenderIntoBatch(_batchBuilder, renderQueueEntry.RenderFragment, out var renderFragmentException);
938938
if (renderFragmentException != null)

src/Components/Components/src/Rendering/RenderingMetrics.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ internal sealed class RenderingMetrics : IDisposable
1818

1919
public RenderingMetrics(IMeterFactory meterFactory)
2020
{
21+
Debug.Assert(meterFactory != null);
22+
2123
_meter = meterFactory.Create(MeterName);
2224

2325
_renderTotalCounter = _meter.CreateCounter<long>(

src/Components/Components/test/Microsoft.AspNetCore.Components.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
<ItemGroup>
99
<Reference Include="Microsoft.AspNetCore.Components" />
1010
<Reference Include="Microsoft.Extensions.DependencyInjection" />
11+
<Reference Include="Microsoft.Extensions.Diagnostics.Testing" />
1112
</ItemGroup>
1213

1314
<ItemGroup>
15+
<Compile Include="$(SharedSourceRoot)Metrics\TestMeterFactory.cs" LinkBase="shared" />
1416
<Compile Include="$(ComponentsSharedSourceRoot)test\**\*.cs" LinkBase="Helpers" />
1517
</ItemGroup>
1618

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Diagnostics.Metrics;
6+
using Microsoft.Extensions.Diagnostics.Metrics.Testing;
7+
using Microsoft.Extensions.Logging.Abstractions;
8+
using Microsoft.Extensions.Options;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.AspNetCore.InternalTesting;
11+
using Moq;
12+
13+
namespace Microsoft.AspNetCore.Components.Rendering;
14+
15+
public class RenderingMetricsTest
16+
{
17+
private readonly TestMeterFactory _meterFactory;
18+
19+
public RenderingMetricsTest()
20+
{
21+
_meterFactory = new TestMeterFactory();
22+
}
23+
24+
[Fact]
25+
public void Constructor_CreatesMetersCorrectly()
26+
{
27+
// Arrange & Act
28+
var renderingMetrics = new RenderingMetrics(_meterFactory);
29+
30+
// Assert
31+
Assert.Single(_meterFactory.Meters);
32+
Assert.Equal(RenderingMetrics.MeterName, _meterFactory.Meters[0].Name);
33+
}
34+
35+
[Fact]
36+
public void RenderStart_IncreasesCounters()
37+
{
38+
// Arrange
39+
var renderingMetrics = new RenderingMetrics(_meterFactory);
40+
using var totalCounter = new MetricCollector<long>(_meterFactory,
41+
RenderingMetrics.MeterName, "aspnetcore.components.rendering.count");
42+
using var activeCounter = new MetricCollector<long>(_meterFactory,
43+
RenderingMetrics.MeterName, "aspnetcore.components.rendering.active_renders");
44+
45+
var componentType = "TestComponent";
46+
47+
// Act
48+
renderingMetrics.RenderStart(componentType);
49+
50+
// Assert
51+
var totalMeasurements = totalCounter.GetMeasurementSnapshot();
52+
var activeMeasurements = activeCounter.GetMeasurementSnapshot();
53+
54+
Assert.Single(totalMeasurements);
55+
Assert.Equal(1, totalMeasurements[0].Value);
56+
Assert.Equal(componentType, totalMeasurements[0].Tags["component.type"]);
57+
58+
Assert.Single(activeMeasurements);
59+
Assert.Equal(1, activeMeasurements[0].Value);
60+
Assert.Equal(componentType, activeMeasurements[0].Tags["component.type"]);
61+
}
62+
63+
[Fact]
64+
public void RenderEnd_DecreasesActiveCounterAndRecordsDuration()
65+
{
66+
// Arrange
67+
var renderingMetrics = new RenderingMetrics(_meterFactory);
68+
using var activeCounter = new MetricCollector<long>(_meterFactory,
69+
RenderingMetrics.MeterName, "aspnetcore.components.rendering.active_renders");
70+
using var durationCollector = new MetricCollector<double>(_meterFactory,
71+
RenderingMetrics.MeterName, "aspnetcore.components.rendering.duration");
72+
73+
var componentType = "TestComponent";
74+
75+
// Act
76+
var startTime = Stopwatch.GetTimestamp();
77+
Thread.Sleep(10); // Add a small delay to ensure a measurable duration
78+
var endTime = Stopwatch.GetTimestamp();
79+
renderingMetrics.RenderEnd(componentType, null, startTime, endTime);
80+
81+
// Assert
82+
var activeMeasurements = activeCounter.GetMeasurementSnapshot();
83+
var durationMeasurements = durationCollector.GetMeasurementSnapshot();
84+
85+
Assert.Single(activeMeasurements);
86+
Assert.Equal(-1, activeMeasurements[0].Value);
87+
Assert.Equal(componentType, activeMeasurements[0].Tags["component.type"]);
88+
89+
Assert.Single(durationMeasurements);
90+
Assert.True(durationMeasurements[0].Value > 0);
91+
Assert.Equal(componentType, durationMeasurements[0].Tags["component.type"]);
92+
}
93+
94+
[Fact]
95+
public void RenderEnd_AddsErrorTypeTag_WhenExceptionIsProvided()
96+
{
97+
// Arrange
98+
var renderingMetrics = new RenderingMetrics(_meterFactory);
99+
using var durationCollector = new MetricCollector<double>(_meterFactory,
100+
RenderingMetrics.MeterName, "aspnetcore.components.rendering.duration");
101+
102+
var componentType = "TestComponent";
103+
var exception = new InvalidOperationException("Test exception");
104+
105+
// Act
106+
var startTime = Stopwatch.GetTimestamp();
107+
Thread.Sleep(10);
108+
var endTime = Stopwatch.GetTimestamp();
109+
renderingMetrics.RenderEnd(componentType, exception, startTime, endTime);
110+
111+
// Assert
112+
var durationMeasurements = durationCollector.GetMeasurementSnapshot();
113+
114+
Assert.Single(durationMeasurements);
115+
Assert.True(durationMeasurements[0].Value > 0);
116+
Assert.Equal(componentType, durationMeasurements[0].Tags["component.type"]);
117+
Assert.Equal(exception.GetType().FullName, durationMeasurements[0].Tags["error.type"]);
118+
}
119+
120+
[Fact]
121+
public void IsDurationEnabled_ReturnsMeterEnabledState()
122+
{
123+
// Arrange
124+
var renderingMetrics = new RenderingMetrics(_meterFactory);
125+
126+
// Create a collector to ensure the meter is enabled
127+
using var durationCollector = new MetricCollector<double>(_meterFactory,
128+
RenderingMetrics.MeterName, "aspnetcore.components.rendering.duration");
129+
130+
// Act & Assert
131+
Assert.True(renderingMetrics.IsDurationEnabled());
132+
}
133+
134+
[Fact]
135+
public void FullRenderingLifecycle_RecordsAllMetricsCorrectly()
136+
{
137+
// Arrange
138+
var renderingMetrics = new RenderingMetrics(_meterFactory);
139+
using var totalCounter = new MetricCollector<long>(_meterFactory,
140+
RenderingMetrics.MeterName, "aspnetcore.components.rendering.count");
141+
using var activeCounter = new MetricCollector<long>(_meterFactory,
142+
RenderingMetrics.MeterName, "aspnetcore.components.rendering.active_renders");
143+
using var durationCollector = new MetricCollector<double>(_meterFactory,
144+
RenderingMetrics.MeterName, "aspnetcore.components.rendering.duration");
145+
146+
var componentType = "TestComponent";
147+
148+
// Act - Simulating a full rendering lifecycle
149+
var startTime = Stopwatch.GetTimestamp();
150+
151+
// 1. Component render starts
152+
renderingMetrics.RenderStart(componentType);
153+
154+
// 2. Component render ends
155+
Thread.Sleep(10); // Add a small delay to ensure a measurable duration
156+
var endTime = Stopwatch.GetTimestamp();
157+
renderingMetrics.RenderEnd(componentType, null, startTime, endTime);
158+
159+
// Assert
160+
var totalMeasurements = totalCounter.GetMeasurementSnapshot();
161+
var activeMeasurements = activeCounter.GetMeasurementSnapshot();
162+
var durationMeasurements = durationCollector.GetMeasurementSnapshot();
163+
164+
// Total render count should have 1 measurement with value 1
165+
Assert.Single(totalMeasurements);
166+
Assert.Equal(1, totalMeasurements[0].Value);
167+
Assert.Equal(componentType, totalMeasurements[0].Tags["component.type"]);
168+
169+
// Active render count should have 2 measurements (1 for start, -1 for end)
170+
Assert.Equal(2, activeMeasurements.Count);
171+
Assert.Equal(1, activeMeasurements[0].Value);
172+
Assert.Equal(-1, activeMeasurements[1].Value);
173+
Assert.Equal(componentType, activeMeasurements[0].Tags["component.type"]);
174+
Assert.Equal(componentType, activeMeasurements[1].Tags["component.type"]);
175+
176+
// Duration should have 1 measurement with a positive value
177+
Assert.Single(durationMeasurements);
178+
Assert.True(durationMeasurements[0].Value > 0);
179+
Assert.Equal(componentType, durationMeasurements[0].Tags["component.type"]);
180+
}
181+
182+
[Fact]
183+
public void MultipleRenders_TracksMetricsIndependently()
184+
{
185+
// Arrange
186+
var renderingMetrics = new RenderingMetrics(_meterFactory);
187+
using var totalCounter = new MetricCollector<long>(_meterFactory,
188+
RenderingMetrics.MeterName, "aspnetcore.components.rendering.count");
189+
using var activeCounter = new MetricCollector<long>(_meterFactory,
190+
RenderingMetrics.MeterName, "aspnetcore.components.rendering.active_renders");
191+
using var durationCollector = new MetricCollector<double>(_meterFactory,
192+
RenderingMetrics.MeterName, "aspnetcore.components.rendering.duration");
193+
194+
var componentType1 = "TestComponent1";
195+
var componentType2 = "TestComponent2";
196+
197+
// Act
198+
// First component render
199+
var startTime1 = Stopwatch.GetTimestamp();
200+
renderingMetrics.RenderStart(componentType1);
201+
202+
// Second component render starts while first is still rendering
203+
var startTime2 = Stopwatch.GetTimestamp();
204+
renderingMetrics.RenderStart(componentType2);
205+
206+
// First component render ends
207+
Thread.Sleep(5);
208+
var endTime1 = Stopwatch.GetTimestamp();
209+
renderingMetrics.RenderEnd(componentType1, null, startTime1, endTime1);
210+
211+
// Second component render ends
212+
Thread.Sleep(5);
213+
var endTime2 = Stopwatch.GetTimestamp();
214+
renderingMetrics.RenderEnd(componentType2, null, startTime2, endTime2);
215+
216+
// Assert
217+
var totalMeasurements = totalCounter.GetMeasurementSnapshot();
218+
var activeMeasurements = activeCounter.GetMeasurementSnapshot();
219+
var durationMeasurements = durationCollector.GetMeasurementSnapshot();
220+
221+
// Should have 2 total render counts (one for each component)
222+
Assert.Equal(2, totalMeasurements.Count);
223+
Assert.Contains(totalMeasurements, m => m.Value == 1 && m.Tags["component.type"] as string == componentType1);
224+
Assert.Contains(totalMeasurements, m => m.Value == 1 && m.Tags["component.type"] as string == componentType2);
225+
226+
// Should have 4 active render counts (start and end for each component)
227+
Assert.Equal(4, activeMeasurements.Count);
228+
Assert.Contains(activeMeasurements, m => m.Value == 1 && m.Tags["component.type"] as string == componentType1);
229+
Assert.Contains(activeMeasurements, m => m.Value == 1 && m.Tags["component.type"] as string == componentType2);
230+
Assert.Contains(activeMeasurements, m => m.Value == -1 && m.Tags["component.type"] as string == componentType1);
231+
Assert.Contains(activeMeasurements, m => m.Value == -1 && m.Tags["component.type"] as string == componentType2);
232+
233+
// Should have 2 duration measurements (one for each component)
234+
Assert.Equal(2, durationMeasurements.Count);
235+
Assert.Contains(durationMeasurements, m => m.Value > 0 && m.Tags["component.type"] as string == componentType1);
236+
Assert.Contains(durationMeasurements, m => m.Value > 0 && m.Tags["component.type"] as string == componentType2);
237+
}
238+
}

src/Components/Server/src/Circuits/CircuitHost.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public CircuitHost(
5050
RemoteJSRuntime jsRuntime,
5151
RemoteNavigationManager navigationManager,
5252
CircuitHandler[] circuitHandlers,
53-
CircuitMetrics circuitMetrics,
53+
CircuitMetrics? circuitMetrics,
5454
ILogger logger)
5555
{
5656
CircuitId = circuitId;
@@ -235,7 +235,7 @@ await Renderer.Dispatcher.InvokeAsync(async () =>
235235
private async Task OnCircuitOpenedAsync(CancellationToken cancellationToken)
236236
{
237237
Log.CircuitOpened(_logger, CircuitId);
238-
_startTime = ((bool)_circuitMetrics?.IsDurationEnabled()) ? Stopwatch.GetTimestamp() : 0;
238+
_startTime = (_circuitMetrics != null && _circuitMetrics.IsDurationEnabled()) ? Stopwatch.GetTimestamp() : 0;
239239
_circuitMetrics?.OnCircuitOpened();
240240

241241
Renderer.Dispatcher.AssertAccess();

src/Components/Server/src/Circuits/CircuitMetrics.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ internal sealed class CircuitMetrics : IDisposable
1919

2020
public CircuitMetrics(IMeterFactory meterFactory)
2121
{
22+
Debug.Assert(meterFactory != null);
23+
2224
_meter = meterFactory.Create(MeterName);
2325

2426
_circuitTotalCounter = _meter.CreateCounter<long>(

0 commit comments

Comments
 (0)