Skip to content

Commit bb39e15

Browse files
Copilotericstj
andauthored
Fix flaky DiagnosticTests.Session_TracksActivities by waiting for exported server activity
Agent-Logs-Url: https://github.com/modelcontextprotocol/csharp-sdk/sessions/267e60a2-c6e5-4d2d-96cb-830b16a80bd4 Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com>
1 parent 6baa64f commit bb39e15

File tree

1 file changed

+28
-13
lines changed

1 file changed

+28
-13
lines changed

tests/ModelContextProtocol.Tests/DiagnosticTests.cs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ public async Task Session_TracksActivities()
1818
var activities = new List<Activity>();
1919
var clientToServerLog = new List<string>();
2020

21+
// Predicate for the expected server tool-call activity, including all required tags.
22+
// Defined here so it can be reused for both the wait and the assertion below.
23+
Func<Activity, bool> isExpectedServerToolCall = a =>
24+
a.DisplayName == "tools/call DoubleValue" &&
25+
a.Kind == ActivityKind.Server &&
26+
a.Status == ActivityStatusCode.Unset &&
27+
a.Tags.Any(t => t.Key == "gen_ai.tool.name" && t.Value == "DoubleValue") &&
28+
a.Tags.Any(t => t.Key == "mcp.method.name" && t.Value == "tools/call") &&
29+
a.Tags.Any(t => t.Key == "gen_ai.operation.name" && t.Value == "execute_tool") &&
30+
a.Tags.Any(t => t.Key == "mcp.protocol.version" && !string.IsNullOrEmpty(t.Value)) &&
31+
a.Tags.Any(t => t.Key == "mcp.session.id" && !string.IsNullOrEmpty(t.Value));
32+
2133
using (var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
2234
.AddSource("Experimental.ModelContextProtocol")
2335
.AddInMemoryExporter(activities)
@@ -36,9 +48,11 @@ await RunConnected(async (client, server) =>
3648
// Wait for server-side activities to be exported. The server processes messages
3749
// via fire-and-forget tasks, so activities may not be immediately available
3850
// after the client operation completes. Wait for the specific activity we need
39-
// rather than a count, as other server activities may be exported first.
40-
await WaitForAsync(() => activities.Any(a =>
41-
a.DisplayName == "tools/call DoubleValue" && a.Kind == ActivityKind.Server));
51+
// (including required tags) rather than just the display name, so that we don't
52+
// assert before all tags have been populated.
53+
await WaitForAsync(
54+
() => activities.Any(isExpectedServerToolCall),
55+
failureMessage: "Timed out waiting for the expected server tool-call activity (tools/call DoubleValue) to be exported with required tags.");
4256
}
4357

4458
Assert.NotEmpty(activities);
@@ -54,13 +68,7 @@ await WaitForAsync(() => activities.Any(a =>
5468
// Per semantic conventions: mcp.protocol.version should be present after initialization
5569
Assert.Contains(clientToolCall.Tags, t => t.Key == "mcp.protocol.version" && !string.IsNullOrEmpty(t.Value));
5670

57-
var serverToolCall = Assert.Single(activities, a =>
58-
a.Tags.Any(t => t.Key == "gen_ai.tool.name" && t.Value == "DoubleValue") &&
59-
a.Tags.Any(t => t.Key == "mcp.method.name" && t.Value == "tools/call") &&
60-
a.Tags.Any(t => t.Key == "gen_ai.operation.name" && t.Value == "execute_tool") &&
61-
a.DisplayName == "tools/call DoubleValue" &&
62-
a.Kind == ActivityKind.Server &&
63-
a.Status == ActivityStatusCode.Unset);
71+
var serverToolCall = Assert.Single(activities, a => isExpectedServerToolCall(a));
6472

6573
// Per semantic conventions: mcp.protocol.version should be present after initialization
6674
Assert.Contains(serverToolCall.Tags, t => t.Key == "mcp.protocol.version" && !string.IsNullOrEmpty(t.Value));
@@ -245,12 +253,19 @@ private static async Task RunConnected(Func<McpClient, McpServer, Task> action,
245253
await serverTask;
246254
}
247255

248-
private static async Task WaitForAsync(Func<bool> condition, int timeoutMs = 10_000)
256+
private static async Task WaitForAsync(Func<bool> condition, int timeoutMs = 10_000, string? failureMessage = null)
249257
{
250258
using var cts = new CancellationTokenSource(timeoutMs);
251-
while (!condition())
259+
try
260+
{
261+
while (!condition())
262+
{
263+
await Task.Delay(10, cts.Token);
264+
}
265+
}
266+
catch (TaskCanceledException)
252267
{
253-
await Task.Delay(10, cts.Token);
268+
throw new Xunit.Sdk.XunitException(failureMessage ?? $"Condition was not met within {timeoutMs}ms.");
254269
}
255270
}
256271
}

0 commit comments

Comments
 (0)