From 2246edcc6d26022af30152cbc7ff0bcb49c4fdb6 Mon Sep 17 00:00:00 2001 From: Jeroen Bloemscheer Date: Sat, 9 May 2026 06:57:06 +0200 Subject: [PATCH 1/3] arch: scaffold Allure.TestingPlatform package Adds an empty packable library `Allure.TestingPlatform` targeting `net8.0` and referencing `Microsoft.Testing.Platform`. This is the foundation for a framework-agnostic Allure adapter that consumes MTP messages and drives `AllureLifecycle`. Framework-specific layers (xUnit.v3, etc.) will sit on top. Co-Authored-By: Claude Opus 4.7 (1M context) --- allure-csharp.slnx | 3 ++ .../Allure.TestingPlatform.csproj | 27 +++++++++++++++ src/Allure.TestingPlatform/README.md | 34 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 src/Allure.TestingPlatform/Allure.TestingPlatform.csproj create mode 100644 src/Allure.TestingPlatform/README.md diff --git a/allure-csharp.slnx b/allure-csharp.slnx index 1aabca56..6c72c7f1 100644 --- a/allure-csharp.slnx +++ b/allure-csharp.slnx @@ -60,6 +60,9 @@ + + + diff --git a/src/Allure.TestingPlatform/Allure.TestingPlatform.csproj b/src/Allure.TestingPlatform/Allure.TestingPlatform.csproj new file mode 100644 index 00000000..43a3ed99 --- /dev/null +++ b/src/Allure.TestingPlatform/Allure.TestingPlatform.csproj @@ -0,0 +1,27 @@ + + + + Allure.TestingPlatform + Allure.TestingPlatform + net8.0 + enable + Allure Framework Contributors + Framework-agnostic Allure adapter for Microsoft.Testing.Platform. Consumes MTP messages and drives Allure.Net.Commons; intended as the foundation for framework-specific adapters (xUnit.v3, etc.). + Allure-Color.png + $(PackageTags) microsoft-testing-platform mtp + + + + + + + + + + + + + + + + diff --git a/src/Allure.TestingPlatform/README.md b/src/Allure.TestingPlatform/README.md new file mode 100644 index 00000000..b80d45e6 --- /dev/null +++ b/src/Allure.TestingPlatform/README.md @@ -0,0 +1,34 @@ +# Allure.TestingPlatform + +Framework-agnostic Allure adapter for [Microsoft.Testing.Platform][mtp]. It +consumes MTP messages (`TestNodeUpdateMessage`) and drives `AllureLifecycle` +from `Allure.Net.Commons`, producing standard Allure result files. + +This package is the foundation for framework-specific adapters (xUnit.v3, +NUnit-on-MTP, etc.); those layers add framework-specific enrichment (traits → +labels, parameter formatting, fixture mapping) on top of the lifecycle handled +here. + +## Status + +**Minimal viable scaffold.** Currently maps the test happy path +(`InProgress` / `Passed` / `Failed` / `Skipped`) to Allure test results. +Steps, fixtures, attachments, parameters and labels will follow in subsequent +PRs. + +## Usage + +In your MTP-based test project's `Program.cs`: + +```csharp +using Allure.TestingPlatform; +using Microsoft.Testing.Platform.Builder; + +var builder = await TestApplication.CreateBuilderAsync(args); +// register your test framework's MTP integration first, e.g. builder.AddXunit() +builder.AddAllure(); +using var app = await builder.BuildAsync(); +return await app.RunAsync(); +``` + +[mtp]: https://learn.microsoft.com/dotnet/core/testing/microsoft-testing-platform-intro From a4bbac1933471ef716075b51961ce336dbb9ad6e Mon Sep 17 00:00:00 2001 From: Jeroen Bloemscheer Date: Sat, 9 May 2026 06:58:16 +0200 Subject: [PATCH 2/3] feat: map MTP TestNodeUpdateMessage to AllureLifecycle Implements `AllureDataConsumer`, an `IDataConsumer` that handles terminal test states (Passed / Failed / Skipped / Error / Timeout / Cancelled) by issuing a single `Schedule -> Start -> Update -> Stop -> Write` sequence on `AllureLifecycle.Instance` under a per-consumer lock. `InProgress` is a no-op for now; timing is therefore approximate and will be addressed by a follow-up that reads `TimingProperty`. `AddAllure()` is provided as an `ITestApplicationBuilder` extension so test hosts opt in with `builder.AddAllure()`. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../AllureDataConsumer.cs | 110 ++++++++++++++++++ .../TestApplicationBuilderExtensions.cs | 25 ++++ 2 files changed, 135 insertions(+) create mode 100644 src/Allure.TestingPlatform/AllureDataConsumer.cs create mode 100644 src/Allure.TestingPlatform/TestApplicationBuilderExtensions.cs diff --git a/src/Allure.TestingPlatform/AllureDataConsumer.cs b/src/Allure.TestingPlatform/AllureDataConsumer.cs new file mode 100644 index 00000000..29e8fd19 --- /dev/null +++ b/src/Allure.TestingPlatform/AllureDataConsumer.cs @@ -0,0 +1,110 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Allure.Net.Commons; +using Allure.Net.Commons.Functions; +using Microsoft.Testing.Platform.Extensions.Messages; +using Microsoft.Testing.Platform.Extensions.TestHost; + +namespace Allure.TestingPlatform; + +/// +/// Consumes Microsoft.Testing.Platform s +/// and projects them onto Allure's . +/// +/// +/// Minimal viable scope: maps terminal test states (Passed / Failed / Skipped +/// / Error / Timeout / Cancelled) to a single Schedule → Start → Update → +/// Stop → Write sequence per test. InProgress updates are ignored — +/// timing therefore reflects when the consumer processed the terminal +/// message, not the test's actual wall-clock duration. This will be addressed +/// in a follow-up PR by reading . +/// +public sealed class AllureDataConsumer : IDataConsumer +{ + static readonly Type[] s_consumed = [typeof(TestNodeUpdateMessage)]; + + readonly AllureLifecycle _lifecycle; + readonly object _sequenceLock = new(); + + public AllureDataConsumer() : this(AllureLifecycle.Instance) { } + + internal AllureDataConsumer(AllureLifecycle lifecycle) + { + _lifecycle = lifecycle; + } + + public string Uid => "Allure.TestingPlatform"; + + public string Version => typeof(AllureDataConsumer).Assembly.GetName().Version?.ToString() ?? "0.0.0"; + + public string DisplayName => "Allure Report"; + + public string Description => "Writes Allure result files for tests reported through Microsoft.Testing.Platform."; + + public Type[] DataTypesConsumed => s_consumed; + + public Task IsEnabledAsync() => Task.FromResult(true); + + public Task ConsumeAsync( + IDataProducer dataProducer, + IData value, + CancellationToken cancellationToken) + { + if (value is TestNodeUpdateMessage update) + { + HandleUpdate(update); + } + return Task.CompletedTask; + } + + void HandleUpdate(TestNodeUpdateMessage update) + { + var node = update.TestNode; + var state = node.Properties.OfType().LastOrDefault(); + if (state is null || state is InProgressTestNodeStateProperty) + { + return; + } + + var (status, message, trace) = MapState(state); + + var testResult = new TestResult + { + uuid = IdFunctions.CreateUUID(), + name = node.DisplayName, + fullName = node.Uid.Value, + historyId = node.Uid.Value, + }; + + lock (_sequenceLock) + { + _lifecycle.StartTestCase(testResult); + _lifecycle.UpdateTestCase(tr => + { + tr.status = status; + if (message is not null || trace is not null) + { + tr.statusDetails ??= new StatusDetails(); + tr.statusDetails.message = message; + tr.statusDetails.trace = trace; + } + }); + _lifecycle.StopTestCase(); + _lifecycle.WriteTestCase(); + } + } + + static (Status status, string? message, string? trace) MapState(TestNodeStateProperty state) => + state switch + { + PassedTestNodeStateProperty => (Status.passed, null, null), + SkippedTestNodeStateProperty s => (Status.skipped, s.Explanation, null), + FailedTestNodeStateProperty f => (Status.failed, f.Exception?.Message, f.Exception?.StackTrace), + ErrorTestNodeStateProperty e => (Status.broken, e.Exception?.Message, e.Exception?.StackTrace), + TimeoutTestNodeStateProperty t => (Status.broken, t.Exception?.Message ?? "Test timed out", t.Exception?.StackTrace), + CancelledTestNodeStateProperty c => (Status.broken, c.Exception?.Message ?? "Test cancelled", c.Exception?.StackTrace), + _ => (Status.none, null, null), + }; +} diff --git a/src/Allure.TestingPlatform/TestApplicationBuilderExtensions.cs b/src/Allure.TestingPlatform/TestApplicationBuilderExtensions.cs new file mode 100644 index 00000000..a0140dab --- /dev/null +++ b/src/Allure.TestingPlatform/TestApplicationBuilderExtensions.cs @@ -0,0 +1,25 @@ +using System; +using Microsoft.Testing.Platform.Builder; + +namespace Allure.TestingPlatform; + +/// +/// Wires the Allure data consumer into a Microsoft.Testing.Platform host. +/// +public static class TestApplicationBuilderExtensions +{ + /// + /// Registers the Allure data consumer so the host writes Allure result + /// files for every reported test. + /// + public static ITestApplicationBuilder AddAllure(this ITestApplicationBuilder builder) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.TestHost.AddDataConsumer(_ => new AllureDataConsumer()); + return builder; + } +} From 83204d86c104e8ff84ba8abec9f22fb40d4cd6e3 Mon Sep 17 00:00:00 2001 From: Jeroen Bloemscheer Date: Sat, 9 May 2026 06:58:58 +0200 Subject: [PATCH 3/3] test: add smoke coverage for AllureDataConsumer Three NUnit tests pin the consumer's IDataConsumer/IExtension surface so refactors of the contract show up as test failures: DataTypesConsumed advertises TestNodeUpdateMessage, IsEnabledAsync returns true, and the extension metadata (Uid / DisplayName / Description / Version) is populated. End-to-end lifecycle assertions will follow once the consumer's mapping grows beyond the current happy path. Co-Authored-By: Claude Opus 4.7 (1M context) --- allure-csharp.slnx | 3 ++ .../Allure.TestingPlatform.Tests.csproj | 13 ++++++ .../AllureDataConsumerTests.cs | 40 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 tests/Allure.TestingPlatform.Tests/Allure.TestingPlatform.Tests.csproj create mode 100644 tests/Allure.TestingPlatform.Tests/AllureDataConsumerTests.cs diff --git a/allure-csharp.slnx b/allure-csharp.slnx index 6c72c7f1..1b8ce46f 100644 --- a/allure-csharp.slnx +++ b/allure-csharp.slnx @@ -83,6 +83,9 @@ + + + diff --git a/tests/Allure.TestingPlatform.Tests/Allure.TestingPlatform.Tests.csproj b/tests/Allure.TestingPlatform.Tests/Allure.TestingPlatform.Tests.csproj new file mode 100644 index 00000000..8c2364d4 --- /dev/null +++ b/tests/Allure.TestingPlatform.Tests/Allure.TestingPlatform.Tests.csproj @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/tests/Allure.TestingPlatform.Tests/AllureDataConsumerTests.cs b/tests/Allure.TestingPlatform.Tests/AllureDataConsumerTests.cs new file mode 100644 index 00000000..f4edfb12 --- /dev/null +++ b/tests/Allure.TestingPlatform.Tests/AllureDataConsumerTests.cs @@ -0,0 +1,40 @@ +using System.Threading.Tasks; +using Allure.TestingPlatform; +using Microsoft.Testing.Platform.Extensions.Messages; +using NUnit.Framework; + +namespace Allure.TestingPlatform.Tests; + +[TestFixture] +public class AllureDataConsumerTests +{ + [Test] + public void DataTypesConsumed_includes_TestNodeUpdateMessage() + { + var consumer = new AllureDataConsumer(); + + Assert.That(consumer.DataTypesConsumed, Has.Member(typeof(TestNodeUpdateMessage))); + } + + [Test] + public async Task IsEnabledAsync_returns_true() + { + var consumer = new AllureDataConsumer(); + + Assert.That(await consumer.IsEnabledAsync(), Is.True); + } + + [Test] + public void Extension_metadata_is_populated() + { + var consumer = new AllureDataConsumer(); + + Assert.Multiple(() => + { + Assert.That(consumer.Uid, Is.EqualTo("Allure.TestingPlatform")); + Assert.That(consumer.DisplayName, Is.Not.Empty); + Assert.That(consumer.Description, Is.Not.Empty); + Assert.That(consumer.Version, Is.Not.Empty); + }); + } +}