Fix flaky DiagnosticTests.Session_TracksActivities by waiting for full server activity predicate (#1495)
This commit is contained in:
parent
01dd43217e
commit
480979b1b5
1 changed files with 28 additions and 13 deletions
|
|
@ -18,6 +18,18 @@ public class DiagnosticTests
|
||||||
var activities = new List<Activity>();
|
var activities = new List<Activity>();
|
||||||
var clientToServerLog = new List<string>();
|
var clientToServerLog = new List<string>();
|
||||||
|
|
||||||
|
// Predicate for the expected server tool-call activity, including all required tags.
|
||||||
|
// Defined here so it can be reused for both the wait and the assertion below.
|
||||||
|
Func<Activity, bool> isExpectedServerToolCall = a =>
|
||||||
|
a.DisplayName == "tools/call DoubleValue" &&
|
||||||
|
a.Kind == ActivityKind.Server &&
|
||||||
|
a.Status == ActivityStatusCode.Unset &&
|
||||||
|
a.Tags.Any(t => t.Key == "gen_ai.tool.name" && t.Value == "DoubleValue") &&
|
||||||
|
a.Tags.Any(t => t.Key == "mcp.method.name" && t.Value == "tools/call") &&
|
||||||
|
a.Tags.Any(t => t.Key == "gen_ai.operation.name" && t.Value == "execute_tool") &&
|
||||||
|
a.Tags.Any(t => t.Key == "mcp.protocol.version" && !string.IsNullOrEmpty(t.Value)) &&
|
||||||
|
a.Tags.Any(t => t.Key == "mcp.session.id" && !string.IsNullOrEmpty(t.Value));
|
||||||
|
|
||||||
using (var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
|
using (var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
|
||||||
.AddSource("Experimental.ModelContextProtocol")
|
.AddSource("Experimental.ModelContextProtocol")
|
||||||
.AddInMemoryExporter(activities)
|
.AddInMemoryExporter(activities)
|
||||||
|
|
@ -36,9 +48,11 @@ public class DiagnosticTests
|
||||||
// Wait for server-side activities to be exported. The server processes messages
|
// Wait for server-side activities to be exported. The server processes messages
|
||||||
// via fire-and-forget tasks, so activities may not be immediately available
|
// via fire-and-forget tasks, so activities may not be immediately available
|
||||||
// after the client operation completes. Wait for the specific activity we need
|
// after the client operation completes. Wait for the specific activity we need
|
||||||
// rather than a count, as other server activities may be exported first.
|
// (including required tags) rather than just the display name, so that we don't
|
||||||
await WaitForAsync(() => activities.Any(a =>
|
// assert before all tags have been populated.
|
||||||
a.DisplayName == "tools/call DoubleValue" && a.Kind == ActivityKind.Server));
|
await WaitForAsync(
|
||||||
|
() => activities.Any(isExpectedServerToolCall),
|
||||||
|
failureMessage: "Timed out waiting for the expected server tool-call activity (tools/call DoubleValue) to be exported with required tags.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.NotEmpty(activities);
|
Assert.NotEmpty(activities);
|
||||||
|
|
@ -54,13 +68,7 @@ public class DiagnosticTests
|
||||||
// Per semantic conventions: mcp.protocol.version should be present after initialization
|
// Per semantic conventions: mcp.protocol.version should be present after initialization
|
||||||
Assert.Contains(clientToolCall.Tags, t => t.Key == "mcp.protocol.version" && !string.IsNullOrEmpty(t.Value));
|
Assert.Contains(clientToolCall.Tags, t => t.Key == "mcp.protocol.version" && !string.IsNullOrEmpty(t.Value));
|
||||||
|
|
||||||
var serverToolCall = Assert.Single(activities, a =>
|
var serverToolCall = Assert.Single(activities, a => isExpectedServerToolCall(a));
|
||||||
a.Tags.Any(t => t.Key == "gen_ai.tool.name" && t.Value == "DoubleValue") &&
|
|
||||||
a.Tags.Any(t => t.Key == "mcp.method.name" && t.Value == "tools/call") &&
|
|
||||||
a.Tags.Any(t => t.Key == "gen_ai.operation.name" && t.Value == "execute_tool") &&
|
|
||||||
a.DisplayName == "tools/call DoubleValue" &&
|
|
||||||
a.Kind == ActivityKind.Server &&
|
|
||||||
a.Status == ActivityStatusCode.Unset);
|
|
||||||
|
|
||||||
// Per semantic conventions: mcp.protocol.version should be present after initialization
|
// Per semantic conventions: mcp.protocol.version should be present after initialization
|
||||||
Assert.Contains(serverToolCall.Tags, t => t.Key == "mcp.protocol.version" && !string.IsNullOrEmpty(t.Value));
|
Assert.Contains(serverToolCall.Tags, t => t.Key == "mcp.protocol.version" && !string.IsNullOrEmpty(t.Value));
|
||||||
|
|
@ -245,12 +253,19 @@ public class DiagnosticTests
|
||||||
await serverTask;
|
await serverTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task WaitForAsync(Func<bool> condition, int timeoutMs = 10_000)
|
private static async Task WaitForAsync(Func<bool> condition, int timeoutMs = 10_000, string? failureMessage = null)
|
||||||
{
|
{
|
||||||
using var cts = new CancellationTokenSource(timeoutMs);
|
using var cts = new CancellationTokenSource(timeoutMs);
|
||||||
while (!condition())
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(10, cts.Token);
|
while (!condition())
|
||||||
|
{
|
||||||
|
await Task.Delay(10, cts.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
throw new Xunit.Sdk.XunitException(failureMessage ?? $"Condition was not met within {timeoutMs}ms.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue