Skip to content

Commit bf49b7d

Browse files
Copilotbrianrob
andcommitted
Add test using MutableTraceEventStackSource with real nettrace file
Co-authored-by: brianrob <[email protected]>
1 parent 16daf4c commit bf49b7d

File tree

1 file changed

+93
-0
lines changed

1 file changed

+93
-0
lines changed

src/TraceEvent/TraceEvent.Tests/Regression/RecursiveCallTest.cs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.IO;
4+
using System.IO.Compression;
35
using System.Linq;
46
using Microsoft.Diagnostics.Tracing;
7+
using Microsoft.Diagnostics.Tracing.Etlx;
8+
using Microsoft.Diagnostics.Tracing.EventPipe;
59
using Microsoft.Diagnostics.Tracing.Stacks;
610
using Xunit;
711

@@ -143,6 +147,95 @@ public void ThreeLevelRecursiveCallsShowCorrectMetrics()
143147
Assert.True(xCallee.InclusiveMetric > 0, $"Recursive callee 'X' should have positive metric, got {xCallee.InclusiveMetric}");
144148
}
145149

150+
/// <summary>
151+
/// Regression test using MutableTraceEventStackSource to reproduce the exact issue scenario.
152+
/// This test uses an existing test nettrace file, converts it to a TraceLog, and then uses
153+
/// MutableTraceEventStackSource to add recursive frames, matching the original issue repro code.
154+
/// </summary>
155+
[Fact]
156+
public void RecursiveCallsWithMutableTraceEventStackSource()
157+
{
158+
// Use an existing test nettrace file
159+
string testDataDir = Path.Combine(
160+
Path.GetDirectoryName(typeof(RecursiveCallTest).Assembly.Location),
161+
"..", "..", "..", "inputs");
162+
string zipFile = Path.Combine(testDataDir, "eventpipe-dotnetcore6.0-win-x64-executioncheckpoints.nettrace.zip");
163+
164+
// Skip test if file doesn't exist (CI environments may not have test data)
165+
if (!File.Exists(zipFile))
166+
{
167+
return; // Skip test
168+
}
169+
170+
string unzippedFile = Path.Combine(Path.GetTempPath(), $"test_recursive_{Guid.NewGuid()}.nettrace");
171+
string tempEtlxFile = null;
172+
173+
try
174+
{
175+
// Extract the nettrace file
176+
using (var archive = System.IO.Compression.ZipFile.OpenRead(zipFile))
177+
{
178+
var entry = archive.Entries.First(e => e.Name.EndsWith(".nettrace"));
179+
entry.ExtractToFile(unzippedFile, true);
180+
}
181+
182+
// Create TraceLog from nettrace
183+
tempEtlxFile = TraceLog.CreateFromEventPipeDataFile(unzippedFile, null, new TraceLogOptions() { ContinueOnError = true });
184+
using (var traceLog = new TraceLog(tempEtlxFile))
185+
{
186+
// Create MutableTraceEventStackSource and reproduce the exact issue scenario
187+
var stackSource = new MutableTraceEventStackSource(traceLog);
188+
189+
// This reproduces the exact code from the issue:
190+
var sample = new StackSourceSample(stackSource);
191+
sample.StackIndex = stackSource.Interner.CallStackIntern(
192+
stackSource.Interner.FrameIntern("X"), sample.StackIndex);
193+
sample.StackIndex = stackSource.Interner.CallStackIntern(
194+
stackSource.Interner.FrameIntern("X"), sample.StackIndex);
195+
sample.TimeRelativeMSec = 1.0;
196+
sample.Metric = 1.0f;
197+
sample.Count = 1;
198+
stackSource.AddSample(sample);
199+
stackSource.DoneAddingSamples();
200+
201+
// Build CallTree and verify structure
202+
var callTree = new CallTree(ScalingPolicyKind.ScaleToData);
203+
callTree.StackSource = stackSource;
204+
205+
var root = callTree.Root;
206+
Assert.NotNull(root);
207+
208+
// Test CallerCalleeNode for "X" - this is the main test
209+
// The CallerCalleeNode should correctly identify recursive relationships
210+
var callerCalleeNode = new CallerCalleeNode("X", callTree);
211+
212+
// The key assertions: X should appear as both caller and callee
213+
// This tests that the recursive call (X -> X) is properly represented
214+
var xCaller = callerCalleeNode.Callers.FirstOrDefault(c => c.Name == "X");
215+
Assert.NotNull(xCaller);
216+
Assert.True(xCaller.InclusiveMetric > 0,
217+
$"Recursive caller 'X' should have positive metric, got {xCaller.InclusiveMetric}");
218+
Assert.False(float.IsNaN(xCaller.InclusiveMetric),
219+
"Recursive caller 'X' has NaN inclusive metric");
220+
221+
var xCallee = callerCalleeNode.Callees.FirstOrDefault(c => c.Name == "X");
222+
Assert.NotNull(xCallee);
223+
Assert.True(xCallee.InclusiveMetric > 0,
224+
$"Recursive callee 'X' should have positive metric, got {xCallee.InclusiveMetric}");
225+
Assert.False(float.IsNaN(xCallee.InclusiveMetric),
226+
"Recursive callee 'X' has NaN inclusive metric");
227+
}
228+
}
229+
finally
230+
{
231+
// Clean up temp files
232+
if (File.Exists(unzippedFile))
233+
File.Delete(unzippedFile);
234+
if (tempEtlxFile != null && File.Exists(tempEtlxFile))
235+
File.Delete(tempEtlxFile);
236+
}
237+
}
238+
146239
/// <summary>
147240
/// Simple StackSource implementation for testing recursive call scenarios.
148241
/// This minimal implementation allows creating call stacks with interned frames

0 commit comments

Comments
 (0)