Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0e8493c
[MTP] Add initial support for OTel
Evangelink Sep 3, 2025
0adb084
Apply suggestion from @Evangelink
Evangelink Sep 4, 2025
73b1d5b
Apply suggestion from @Evangelink
Evangelink Sep 4, 2025
d0f172c
Apply suggestion from @Evangelink
Evangelink Sep 4, 2025
509f306
Apply suggestion from @Evangelink
Evangelink Sep 4, 2025
1ba53c7
Apply suggestion from @Evangelink
Evangelink Sep 4, 2025
856f380
Apply suggestion from @Evangelink
Evangelink Sep 4, 2025
2a8b2a0
Merge branch 'rel/4.0' into dev/amauryleve/otel-mtp
Evangelink Sep 4, 2025
1495423
Fixes
Evangelink Sep 4, 2025
ed4fc44
Add more sub-activities
Evangelink Sep 5, 2025
eaead9b
Merge branch 'main' into dev/amauryleve/otel-mtp
Evangelink Oct 6, 2025
e47bac4
Merge branch 'main' into dev/amauryleve/otel-mtp
Evangelink Oct 7, 2025
573a081
Fix public APIs
Evangelink Oct 7, 2025
32f2fa0
Apply suggestion from @Evangelink
Evangelink Oct 7, 2025
d216378
Apply suggestion from @Evangelink
Evangelink Oct 7, 2025
c47dc25
Update Directory.Packages.props
Evangelink Oct 20, 2025
4d2a70f
Update samples/Playground/Playground.csproj
Evangelink Oct 20, 2025
059b84b
Updates
Evangelink Oct 21, 2025
b41d8f6
Merge branch 'main' into dev/amauryleve/otel-mtp
Evangelink Oct 21, 2025
476777b
More fixes
Evangelink Oct 21, 2025
195c2d7
More feedback
Evangelink Oct 21, 2025
9d066f6
Fix polyfill issues
Evangelink Oct 22, 2025
5daf9c1
Merge branch 'main' into dev/amauryleve/otel-mtp
Evangelink Oct 28, 2025
33f307e
Add MTP otel sample
Evangelink Oct 28, 2025
0c1a745
Fix playground build
Evangelink Oct 28, 2025
d0abceb
Apply suggestion from @Evangelink
Evangelink Oct 31, 2025
625eff9
Merge branch 'main' into dev/amauryleve/otel-mtp
Evangelink Dec 12, 2025
fe2778a
Fix conflicts + update versions
Evangelink Dec 12, 2025
2f6998a
Fix obsolete warning
Evangelink Dec 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
<PackageVersion Include="Microsoft.TestPlatform.TranslationLayer" Version="$(MicrosoftNETTestSdkVersion)" />
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.251003001" />
<PackageVersion Include="OpenTelemetry" Version="1.14.0" />
<PackageVersion Include="Polyfill" Version="9.0.0-beta.19" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsVersion)" />
</ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions Microsoft.Testing.Platform.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"src\\Platform\\Microsoft.Testing.Extensions.HangDump\\Microsoft.Testing.Extensions.HangDump.csproj",
"src\\Platform\\Microsoft.Testing.Extensions.HotReload\\Microsoft.Testing.Extensions.HotReload.csproj",
"src\\Platform\\Microsoft.Testing.Extensions.MSBuild\\Microsoft.Testing.Extensions.MSBuild.csproj",
"src\\Platform\\Microsoft.Testing.Extensions.OpenTelemetry\\Microsoft.Testing.Extensions.OpenTelemetry.csproj",
"src\\Platform\\Microsoft.Testing.Extensions.Retry\\Microsoft.Testing.Extensions.Retry.csproj",
"src\\Platform\\Microsoft.Testing.Extensions.Telemetry\\Microsoft.Testing.Extensions.Telemetry.csproj",
"src\\Platform\\Microsoft.Testing.Extensions.TrxReport.Abstractions\\Microsoft.Testing.Extensions.TrxReport.Abstractions.csproj",
Expand Down
1 change: 1 addition & 0 deletions TestFx.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<Project Path="src/Platform/Microsoft.Testing.Extensions.HangDump/Microsoft.Testing.Extensions.HangDump.csproj" />
<Project Path="src/Platform/Microsoft.Testing.Extensions.HotReload/Microsoft.Testing.Extensions.HotReload.csproj" />
<Project Path="src/Platform/Microsoft.Testing.Extensions.MSBuild/Microsoft.Testing.Extensions.MSBuild.csproj" />
<Project Path="src/Platform/Microsoft.Testing.Extensions.OpenTelemetry/Microsoft.Testing.Extensions.OpenTelemetry.csproj" />
<Project Path="src/Platform/Microsoft.Testing.Extensions.Retry/Microsoft.Testing.Extensions.Retry.csproj" />
<Project Path="src/Platform/Microsoft.Testing.Extensions.Telemetry/Microsoft.Testing.Extensions.Telemetry.csproj" />
<Project Path="src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/Microsoft.Testing.Extensions.TrxReport.Abstractions.csproj" />
Expand Down
2 changes: 2 additions & 0 deletions samples/Playground/Playground.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
<ProjectReference Include="$(RepoRoot)src\Analyzers\MSTest.Analyzers\MSTest.Analyzers.csproj" PrivateAssets="all" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
<PackageReference Include="StreamJsonRpc" />
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Extensions.Telemetry\Microsoft.Testing.Extensions.Telemetry.csproj" />
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Extensions.OpenTelemetry\Microsoft.Testing.Extensions.OpenTelemetry.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" VersionOverride="$(MicrosoftNETTestSdkVersion)" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" VersionOverride="1.14.0" />
</ItemGroup>

<ItemGroup>
Expand Down
19 changes: 15 additions & 4 deletions samples/Playground/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@
using Microsoft.Testing.Platform.Extensions.TestFramework;
using Microsoft.Testing.Platform.Extensions.TestHostControllers;
using Microsoft.Testing.Platform.Messages;

#if NETCOREAPP
using Microsoft.Testing.Platform.ServerMode.IntegrationTests.Messages.V100;

using MSTest.Acceptance.IntegrationTests.Messages.V100;

#endif

using Microsoft.Extensions.AI;
using Microsoft.Testing.Extensions.AzureFoundry;
using Microsoft.Testing.Platform.AI;
using Microsoft.Testing.Platform.Capabilities.TestFramework;
using Microsoft.Testing.Platform.TestHost;
using Microsoft.VisualStudio.TestTools.UnitTesting;

Expand Down Expand Up @@ -54,6 +52,19 @@ public static async Task<int> Main(string[] args)

// Enable Telemetry
// testApplicationBuilder.AddAppInsightsTelemetryProvider();

// Enable OTel
// testApplicationBuilder.AddOpenTelemetryProvider(
// tracing =>
// {
// tracing.AddTestingPlatformInstrumentation();
// tracing.AddOtlpExporter();
// },
// metrics =>
// {
// metrics.AddTestingPlatformInstrumentation();
// metrics.AddOtlpExporter();
// });
using ITestApplication testApplication = await testApplicationBuilder.BuildAsync();
return await testApplication.RunAsync();
}
Expand Down
2 changes: 2 additions & 0 deletions samples/Playground/Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ public class TestClass
[TestMethod]
public void Test1()
{
Thread.Sleep(5000);
Assert.IsPositive(1);
}
}
31 changes: 31 additions & 0 deletions samples/public/MTPOTel/MTPOTel.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<GenerateProgramFile>false</GenerateProgramFile>
<NoWarn>$(NoWarn);NETSDK1023;SA0001;EnableGenerateDocumentationFile</NoWarn>

<IsTestingPlatformApplication>true</IsTestingPlatformApplication>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<RepoRoot>$(MSBuildThisFileDirectory)..\..\..\</RepoRoot>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform\Microsoft.Testing.Platform.csproj" />
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Extensions.OpenTelemetry\Microsoft.Testing.Extensions.OpenTelemetry.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="OpenTelemetry.Exporter.Console" VersionOverride="1.13.1" />
</ItemGroup>

<ItemGroup>
<ProjectCapability Include="TestingPlatformServer" />
<ProjectCapability Include="TestContainer" />
</ItemGroup>

<!-- Import the capabilities for the Microsoft.Testing.Platform -->
<Import Project="$(RepoRoot)src\Platform\Microsoft.Testing.Platform\buildMultiTargeting\Microsoft.Testing.Platform.props" />
</Project>
47 changes: 47 additions & 0 deletions samples/public/MTPOTel/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Threading.Tasks;
using Microsoft.Testing.Extensions;
using Microsoft.Testing.Platform.Builder;
using Microsoft.Testing.Platform.Extensions.Messages;
using Microsoft.Testing.Platform.Extensions.TestFramework;
using Microsoft.Testing.Platform.Messages;
using Microsoft.Testing.Platform.Capabilities.TestFramework;
using Microsoft.Testing.Platform.Services;
using Microsoft.Testing.Platform.TestHost;

using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

namespace MTPOTel;

public class Program
{
public static async Task<int> Main(string[] args)
{
ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args);

// Register our simple test framework
testApplicationBuilder.RegisterTestFramework(
_ => new TestFrameworkCapabilities(),
(capabilities, serviceProvider) => new SimpleTestFramework(serviceProvider));

// Enable OpenTelemetry with console exporter
testApplicationBuilder.AddOpenTelemetryProvider(
tracing =>
{
tracing.AddTestingPlatformInstrumentation();
tracing.AddConsoleExporter();
},
metrics =>
{
metrics.AddTestingPlatformInstrumentation();
metrics.AddConsoleExporter();
});

using ITestApplication testApplication = await testApplicationBuilder.BuildAsync();
return await testApplication.RunAsync();
}
}
87 changes: 87 additions & 0 deletions samples/public/MTPOTel/SimpleTestFramework.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Threading.Tasks;
using Microsoft.Testing.Platform.Extensions.Messages;
using Microsoft.Testing.Platform.Extensions.TestFramework;
using Microsoft.Testing.Platform.TestHost;

namespace MTPOTel;

internal sealed class SimpleTestFramework : ITestFramework, IDataProducer
{
private readonly IServiceProvider _serviceProvider;

public SimpleTestFramework(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider;

public string Uid => nameof(SimpleTestFramework);

public string Version => "1.0.0";

public string DisplayName => "Simple Test Framework";

public string Description => "A simple test framework that returns 5 tests run sequentially";

public Type[] DataTypesProduced => [typeof(TestNodeUpdateMessage)];

public Task<bool> IsEnabledAsync() => Task.FromResult(true);

public Task<CreateTestSessionResult> CreateTestSessionAsync(CreateTestSessionContext context)
=> Task.FromResult(new CreateTestSessionResult { IsSuccess = true });

public Task<CloseTestSessionResult> CloseTestSessionAsync(CloseTestSessionContext context)
=> Task.FromResult(new CloseTestSessionResult { IsSuccess = true });

public async Task ExecuteRequestAsync(ExecuteRequestContext context)
{
var sessionUid = new SessionUid("SimpleTestSession");

// Create 5 tests to run sequentially
for (int i = 1; i <= 5; i++)
{
string testId = $"Test{i}";
string testName = $"Simple Test {i}";

// Publish test node as in-progress
await context.MessageBus.PublishAsync(
this,
new TestNodeUpdateMessage(
sessionUid,
new TestNode
{
Uid = testId,
DisplayName = testName,
Properties = new PropertyBag(new InProgressTestNodeStateProperty()),
}));

// Simulate test execution
Console.WriteLine($"Executing {testName}...");
await Task.Delay(500); // Simulate some work

// Determine test result (all pass for now)
bool testPassed = true;

// Publish test node as passed or failed
IProperty resultProperty = testPassed
? new PassedTestNodeStateProperty()
: new FailedTestNodeStateProperty(new InvalidOperationException($"{testName} failed"));

await context.MessageBus.PublishAsync(
this,
new TestNodeUpdateMessage(
sessionUid,
new TestNode
{
Uid = testId,
DisplayName = testName,
Properties = new PropertyBag(resultProperty),
}));

Console.WriteLine($"{testName} completed: {(testPassed ? "Passed" : "Failed")}");
}

context.Complete();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.Testing.Platform.OutputDevice;
using Microsoft.Testing.Platform.Requests;
using Microsoft.Testing.Platform.Services;
using Microsoft.Testing.Platform.Telemetry;

namespace Microsoft.Testing.Extensions.Hosting;

Expand All @@ -28,12 +29,12 @@ private static bool IsHotReloadEnabled(IEnvironment environment)
=> environment.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_WATCH) == "1"
|| environment.GetEnvironmentVariable(EnvironmentVariableConstants.TESTINGPLATFORM_HOTRELOAD_ENABLED) == "1";

public override async Task ExecuteRequestAsync(ITestFramework testFrameworkAdapter, TestExecutionRequest request,
public override async Task ExecuteRequestAsync(ITestFramework testFramework, TestExecutionRequest request,
IMessageBus messageBus, CancellationToken cancellationToken)
{
if (!_isHotReloadEnabled)
{
await base.ExecuteRequestAsync(testFrameworkAdapter, request, messageBus, cancellationToken).ConfigureAwait(false);
await base.ExecuteRequestAsync(testFramework, request, messageBus, cancellationToken).ConfigureAwait(false);
return;
}

Expand All @@ -44,16 +45,21 @@ public override async Task ExecuteRequestAsync(ITestFramework testFrameworkAdapt
while (await hotReloadHandler.ShouldRunAsync(executionCompleted?.Task, cancellationToken).ConfigureAwait(false))
{
executionCompleted = new();
using SemaphoreSlim requestSemaphore = new(1);
var hotReloadOutputDevice = ServiceProvider.GetPlatformOutputDevice() as IHotReloadPlatformOutputDevice;
if (hotReloadOutputDevice is not null)
{
await hotReloadOutputDevice.DisplayBeforeHotReloadSessionStartAsync(cancellationToken).ConfigureAwait(false);
}

await testFrameworkAdapter.ExecuteRequestAsync(new(request, messageBus, new SemaphoreSlimRequestCompleteNotifier(requestSemaphore), cancellationToken)).ConfigureAwait(false);
IPlatformOpenTelemetryService? otelService = ServiceProvider.GetPlatformOTelService();
using (IPlatformActivity? testFrameworkActivity = otelService?.StartActivity("TestFramework", testFramework.ToOTelTags()))
{
using SemaphoreSlim requestSemaphore = new(1);
otelService?.TestFrameworkActivity = testFrameworkActivity;
await testFramework.ExecuteRequestAsync(new(request, messageBus, new SemaphoreSlimRequestCompleteNotifier(requestSemaphore), cancellationToken)).ConfigureAwait(false);
await requestSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
}

await requestSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
await ServiceProvider.GetBaseMessageBus().DrainDataAsync().ConfigureAwait(false);
if (hotReloadOutputDevice is not null)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Testing.Platform.Telemetry;

namespace Microsoft.Testing.Extensions.OpenTelemetry;

internal sealed class ActivityWrapper(Activity activity) : IPlatformActivity
{
public string? Id => activity.Id;

public IPlatformActivity SetTag(string key, object? value)
{
activity.SetTag(key, value);
return this;
}

public void Dispose() => activity.Dispose();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
M:System.String.IsNullOrEmpty(System.String); Use 'TAString.IsNullOrEmpty' instead
M:System.String.IsNullOrWhiteSpace(System.String); Use 'TAString.IsNullOrWhiteSpace' instead
M:System.Diagnostics.Debug.Assert(System.Boolean); Use 'RoslynDebug.Assert' instead
M:System.Diagnostics.Debug.Assert(System.Boolean,System.String); Use 'RoslynDebug.Assert' instead
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics.Metrics;

using Microsoft.Testing.Platform.Telemetry;

namespace Microsoft.Testing.Extensions.OpenTelemetry;

internal sealed class CounterWrapper<T> : ICounter<T>
where T : struct
{
private readonly Counter<T> _counter;

public CounterWrapper(Counter<T> counter)
=> _counter = counter
?? throw new ArgumentNullException(nameof(counter));

public void Add(T delta) => _counter.Add(delta);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics.Metrics;

using Microsoft.Testing.Platform.Telemetry;

namespace Microsoft.Testing.Extensions.OpenTelemetry;

internal sealed class HistogramWrapper<T> : IHistogram<T>
where T : struct
{
private readonly Histogram<T> _histogram;

public HistogramWrapper(Histogram<T> histogram)
=> _histogram = histogram
?? throw new ArgumentNullException(nameof(histogram));

public void Record(T value)
=> _histogram.Record(value);
}
Loading
Loading