From d0e332ccde5b3258a67ff06f654c2534a7f92dd5 Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Wed, 12 Jul 2023 20:35:47 +0700
Subject: [PATCH 01/24] Add AllureContext lass to isolate allure state from
threads and async tasks
---
.../StorageTests/AllureContextTests.cs | 503 ++++++++++++++++++
Allure.Net.Commons/Allure.Net.Commons.csproj | 1 +
Allure.Net.Commons/Internal/IsExternalInit.cs | 20 +
Allure.Net.Commons/Storage/AllureContext.cs | 408 ++++++++++++++
4 files changed, 932 insertions(+)
create mode 100644 Allure.Net.Commons.Tests/StorageTests/AllureContextTests.cs
create mode 100644 Allure.Net.Commons/Internal/IsExternalInit.cs
create mode 100644 Allure.Net.Commons/Storage/AllureContext.cs
diff --git a/Allure.Net.Commons.Tests/StorageTests/AllureContextTests.cs b/Allure.Net.Commons.Tests/StorageTests/AllureContextTests.cs
new file mode 100644
index 00000000..f4755cc4
--- /dev/null
+++ b/Allure.Net.Commons.Tests/StorageTests/AllureContextTests.cs
@@ -0,0 +1,503 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Allure.Net.Commons.Storage;
+using NUnit.Framework;
+
+namespace Allure.Net.Commons.Tests.StorageTests
+{
+ class AllureContextTests
+ {
+ [Test]
+ public void TestEmptyContext()
+ {
+ var ctx = new AllureContext();
+
+ Assert.That(ctx.ContainerContext, Is.Empty);
+ Assert.That(ctx.FixtureContext, Is.Null);
+ Assert.That(ctx.TestContext, Is.Null);
+ Assert.That(ctx.StepContext, Is.Empty);
+
+ Assert.That(
+ () => ctx.CurrentContainer,
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "No container context has been set up."
+ )
+ );
+ Assert.That(
+ () => ctx.CurrentFixture,
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "No fixture context has been set up."
+ )
+ );
+ Assert.That(
+ () => ctx.CurrentTest,
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "No test context has been set up."
+ )
+ );
+ Assert.That(
+ () => ctx.CurrentStep,
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "No step context has been set up."
+ )
+ );
+ Assert.That(
+ () => ctx.CurrentStepContainer,
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "No fixture, test, or step context has been set up."
+ )
+ );
+ }
+
+ [Test]
+ public void TestContextOnly()
+ {
+ var test = new TestResult();
+
+ var ctx = new AllureContext().WithTestContext(test);
+
+ Assert.That(ctx.TestContext, Is.SameAs(test));
+ Assert.That(ctx.CurrentTest, Is.SameAs(test));
+ Assert.That(ctx.CurrentStepContainer, Is.SameAs(test));
+ }
+
+ [Test]
+ public void CanNotRemoveContainerIfTestIsSet()
+ {
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithTestContext(new());
+
+ Assert.That(
+ ctx.WithNoLastContainer,
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "Unable to exclude the latest container from the " +
+ "context because a test context exists."
+ )
+ );
+ }
+
+ [Test]
+ public void TestContextCanBeRemoved()
+ {
+ var test = new TestResult();
+
+ var ctx = new AllureContext()
+ .WithTestContext(test)
+ .WithNoTestContext();
+
+ Assert.That(ctx.TestContext, Is.Null);
+ Assert.That(
+ () => ctx.CurrentStepContainer,
+ Throws.InvalidOperationException
+ );
+ }
+
+ [Test]
+ public void ContainerCanNotBeNull()
+ {
+ var ctx = new AllureContext();
+
+ Assert.That(
+ () => ctx.WithContainer(null),
+ Throws.ArgumentNullException
+ );
+ }
+
+ [Test]
+ public void OneContainerInContainerContext()
+ {
+ var container = new TestResultContainer();
+
+ var ctx = new AllureContext().WithContainer(container);
+
+ Assert.That(ctx.ContainerContext, Is.EqualTo(new[] { container }));
+ Assert.That(ctx.CurrentContainer, Is.SameAs(container));
+ }
+
+ [Test]
+ public void SecondContainerIsPushedInFront()
+ {
+ var container1 = new TestResultContainer();
+ var container2 = new TestResultContainer();
+
+ var ctx = new AllureContext()
+ .WithContainer(container1)
+ .WithContainer(container2);
+
+ Assert.That(
+ ctx.ContainerContext,
+ Is.EqualTo(new[] { container2, container1 })
+ );
+ Assert.That(ctx.CurrentContainer, Is.SameAs(container2));
+ }
+
+ [Test]
+ public void CanNotRemoveContainerIfNoneExist()
+ {
+ var ctx = new AllureContext();
+
+ Assert.That(
+ ctx.WithNoLastContainer,
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "Unable to exclude the latest container from the " +
+ "context because no container context has been set up."
+ )
+ );
+ }
+
+ [Test]
+ public void LatestContainerCanBeRemoved()
+ {
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithNoLastContainer();
+
+ Assert.That(ctx.ContainerContext, Is.Empty);
+ }
+
+ [Test]
+ public void IfContainerIsRemovedThePreviousOneBecomesActive()
+ {
+ var container = new TestResultContainer();
+ var ctx = new AllureContext()
+ .WithContainer(container)
+ .WithContainer(new())
+ .WithNoLastContainer();
+
+ Assert.That(ctx.ContainerContext, Is.EqualTo(new[] { container }));
+ Assert.That(ctx.CurrentContainer, Is.SameAs(container));
+ }
+
+ [Test]
+ public void FixtureContextRequiresContainer()
+ {
+ var fixture = new FixtureResult();
+
+ var ctx = new AllureContext();
+
+ Assert.That(
+ () => ctx.WithFixtureContext(new()),
+ Throws.InvalidOperationException
+ .With.Message.EqualTo(
+ "Unable to set up the fixture context because there " +
+ "is no container context."
+ )
+ );
+ }
+
+ [Test]
+ public void FixtureCanNotBeNull()
+ {
+ var ctx = new AllureContext().WithContainer(new());
+
+ Assert.That(
+ () => ctx.WithFixtureContext(null),
+ Throws.ArgumentNullException
+ );
+ }
+
+ [Test]
+ public void FixtureContextIsSet()
+ {
+ var fixture = new FixtureResult();
+
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithFixtureContext(fixture);
+
+ Assert.That(ctx.FixtureContext, Is.SameAs(fixture));
+ Assert.That(ctx.CurrentFixture, Is.SameAs(fixture));
+ Assert.That(ctx.CurrentStepContainer, Is.SameAs(fixture));
+ }
+
+ [Test]
+ public void CanNotRemoveContainerIfFixtureIsSet()
+ {
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithFixtureContext(new());
+
+ Assert.That(
+ ctx.WithNoLastContainer,
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "Unable to exclude the latest container from the " +
+ "context because a fixture context exists."
+ )
+ );
+ }
+
+ [Test]
+ public void FixturesCanNotBeNested()
+ {
+ var fixture = new FixtureResult();
+
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithFixtureContext(fixture);
+
+ Assert.That(
+ () => ctx.WithFixtureContext(new()),
+ Throws.InvalidOperationException
+ .With.Message.EqualTo(
+ "Unable to set up the fixture context " +
+ "because another fixture context already exists."
+ )
+ );
+ }
+
+ [Test]
+ public void TestCanNotBeNull()
+ {
+ var ctx = new AllureContext();
+
+ Assert.That(
+ () => ctx.WithTestContext(null),
+ Throws.ArgumentNullException
+ );
+ }
+
+ [Test]
+ public void TestsCanNotBeNested()
+ {
+ var test = new TestResult();
+
+ var ctx = new AllureContext().WithTestContext(test);
+
+ Assert.That(
+ () => ctx.WithTestContext(new()),
+ Throws.InvalidOperationException
+ .With.Message.EqualTo(
+ "Unable to set up the test context " +
+ "because another test context already exists."
+ )
+ );
+ }
+
+ [Test]
+ public void CanNotSetTestContextIfFixtureContextIsActive()
+ {
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithFixtureContext(new());
+
+ Assert.That(
+ () => ctx.WithTestContext(new()),
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "Unable to set up the test context " +
+ "because a fixture context is currently active."
+ )
+ );
+ }
+
+ [Test]
+ public void ClearingTestContextClearsFixtureContext()
+ {
+ var test = new TestResult();
+
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithTestContext(test)
+ .WithFixtureContext(new())
+ .WithNoTestContext();
+
+ Assert.That(ctx.FixtureContext, Is.Null);
+ Assert.That(
+ () => ctx.CurrentStepContainer,
+ Throws.InvalidOperationException
+ );
+ }
+
+ [Test]
+ public void SettingFixtureContextAfterTestAffectsStepContainer()
+ {
+ var fixture = new FixtureResult();
+
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithTestContext(new())
+ .WithFixtureContext(fixture);
+
+ Assert.That(ctx.CurrentStepContainer, Is.SameAs(fixture));
+ }
+
+ [Test]
+ public void FixtureContextCanBeCleared()
+ {
+ var fixture = new FixtureResult();
+
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithFixtureContext(fixture)
+ .WithNoFixtureContext();
+
+ Assert.That(ctx.FixtureContext, Is.Null);
+ }
+
+ [Test]
+ public void StepCanNotBeNull()
+ {
+ var ctx = new AllureContext().WithTestContext(new());
+
+ Assert.That(
+ () => ctx.WithStep(null),
+ Throws.ArgumentNullException
+ );
+ }
+
+ [Test]
+ public void StepCanNotBeAddedIfNoTestOrFixtureExists()
+ {
+ var ctx = new AllureContext();
+
+ Assert.That(
+ () => ctx.WithStep(new()),
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "Unable to set up the step context because no test or" +
+ "fixture context exists."
+ )
+ );
+ }
+
+ [Test]
+ public void StepCanNotBeRemovedIfNoStepExists()
+ {
+ var ctx = new AllureContext();
+
+ Assert.That(
+ () => ctx.WithNoLastStep(),
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "Unable to exclude the latest step from the context " +
+ "because no step context has been set up."
+ )
+ );
+ }
+
+ [Test]
+ public void StepCanBeAddedIfFixtureExists()
+ {
+ var step = new StepResult();
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithFixtureContext(new())
+ .WithStep(step);
+
+ Assert.That(ctx.StepContext, Is.EqualTo(new[] { step }));
+ Assert.That(ctx.CurrentStepContainer, Is.SameAs(step));
+ }
+
+ [Test]
+ public void StepCanBeAddedIfTestExists()
+ {
+ var step = new StepResult();
+ var ctx = new AllureContext()
+ .WithTestContext(new())
+ .WithStep(step);
+
+ Assert.That(ctx.StepContext, Is.EqualTo(new[] { step }));
+ Assert.That(ctx.CurrentStep, Is.SameAs(step));
+ Assert.That(ctx.CurrentStepContainer, Is.SameAs(step));
+ }
+
+ [Test]
+ public void TwoStepsCanBeAdded()
+ {
+ var step1 = new StepResult();
+ var step2 = new StepResult();
+ var ctx = new AllureContext()
+ .WithTestContext(new())
+ .WithStep(step1)
+ .WithStep(step2);
+
+ Assert.That(ctx.StepContext, Is.EqualTo(new[] { step2, step1 }));
+ Assert.That(ctx.CurrentStep, Is.SameAs(step2));
+ Assert.That(ctx.CurrentStepContainer, Is.SameAs(step2));
+ }
+
+ [Test]
+ public void RemovingStepRestoresPreviousStepAsStepContainer()
+ {
+ var step1 = new StepResult();
+ var step2 = new StepResult();
+ var ctx = new AllureContext()
+ .WithTestContext(new())
+ .WithStep(step1)
+ .WithStep(step2)
+ .WithNoLastStep();
+
+ Assert.That(ctx.StepContext, Is.EqualTo(new[] { step1 }));
+ Assert.That(ctx.CurrentStep, Is.SameAs(step1));
+ Assert.That(ctx.CurrentStepContainer, Is.SameAs(step1));
+ }
+
+ [Test]
+ public void RemovingTheOnlyStepRestoresTestAsStepContainer()
+ {
+ var test = new TestResult();
+ var ctx = new AllureContext()
+ .WithTestContext(test)
+ .WithStep(new())
+ .WithNoLastStep();
+
+ Assert.That(ctx.StepContext, Is.Empty);
+ Assert.That(ctx.CurrentStepContainer, Is.SameAs(test));
+ }
+
+ [Test]
+ public void RemovingTheOnlyStepRestoresFixtureAsStepContainer()
+ {
+ var fixture = new FixtureResult();
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithFixtureContext(fixture)
+ .WithStep(new())
+ .WithNoLastStep();
+
+ Assert.That(ctx.StepContext, Is.Empty);
+ Assert.That(ctx.CurrentStepContainer, Is.SameAs(fixture));
+ }
+
+ [Test]
+ public void RemovingFixtureClearsStepContext()
+ {
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithFixtureContext(new())
+ .WithStep(new())
+ .WithNoFixtureContext();
+
+ Assert.That(ctx.StepContext, Is.Empty);
+ }
+
+ [Test]
+ public void RemovingTestClearsStepContext()
+ {
+ var ctx = new AllureContext()
+ .WithTestContext(new())
+ .WithStep(new())
+ .WithNoTestContext();
+
+ Assert.That(ctx.StepContext, Is.Empty);
+ }
+
+ [Test]
+ public void FixtureAfterTestClearsStepContext()
+ {
+ // It is typical for some tear down fixtures to overlap with a
+ // test. Once such a fixture is started, all steps left after the
+ // test should be removed from the context.
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithTestContext(new())
+ .WithStep(new())
+ .WithFixtureContext(new());
+
+ Assert.That(ctx.StepContext, Is.Empty);
+ }
+ }
+}
diff --git a/Allure.Net.Commons/Allure.Net.Commons.csproj b/Allure.Net.Commons/Allure.Net.Commons.csproj
index 4aaa813f..24d93e9d 100644
--- a/Allure.Net.Commons/Allure.Net.Commons.csproj
+++ b/Allure.Net.Commons/Allure.Net.Commons.csproj
@@ -37,6 +37,7 @@
+
diff --git a/Allure.Net.Commons/Internal/IsExternalInit.cs b/Allure.Net.Commons/Internal/IsExternalInit.cs
new file mode 100644
index 00000000..30cde23d
--- /dev/null
+++ b/Allure.Net.Commons/Internal/IsExternalInit.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel;
+
+namespace System.Runtime.CompilerServices
+{
+ ///
+ /// This class serves as an init-only setter modreq to make a library that
+ /// uses init only setters compile against pre-net5.0 TFMs (including .NET
+ /// Standard). See
+ ///
+ /// this article
+ ///
+ /// and
+ ///
+ /// this answer
+ ///
+ /// for more details.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal static class IsExternalInit { }
+}
diff --git a/Allure.Net.Commons/Storage/AllureContext.cs b/Allure.Net.Commons/Storage/AllureContext.cs
new file mode 100644
index 00000000..4c329031
--- /dev/null
+++ b/Allure.Net.Commons/Storage/AllureContext.cs
@@ -0,0 +1,408 @@
+using System;
+using System.Collections.Immutable;
+using System.Linq;
+
+#nullable enable
+
+namespace Allure.Net.Commons.Storage
+{
+ ///
+ /// Represents information related to a particular test at all stages of
+ /// its life cycle.
+ ///
+ ///
+ ///
+ /// Instances of this class are immutable to ensure proper isolation
+ /// between different tests and steps that may potentially be run
+ /// cuncurrently either by a test framework or by an end user.
+ ///
+ /// Methods in this class don't mutate allure model.
+ ///
+ public record class AllureContext
+ {
+ ///
+ /// A stack of fixture containers affecting subsequent tests.
+ ///
+ ///
+ /// Setting up this context allows operations on the current container
+ /// (including adding a fixture to or removing a fixture from the
+ /// current container).
+ ///
+ public IImmutableStack ContainerContext
+ {
+ get;
+ private init;
+ } = ImmutableStack.Empty;
+
+ ///
+ /// A fixture that is being currently executed.
+ ///
+ ///
+ /// Setting up this context allows operations on the current fixture
+ /// result.
+ /// This property differs from in that it
+ /// returns null if no fixture context exists instead of throwing.
+ ///
+ public FixtureResult? FixtureContext { get; private init; }
+
+ ///
+ /// A test that is being executed.
+ ///
+ ///
+ /// Setting up this context allows operations on the current test
+ /// result.
+ ///
+ /// This property differs from in that it
+ /// returns null if no test context exists instead of throwing.
+ ///
+ public TestResult? TestContext { get; private init; }
+
+ ///
+ /// A stack of nested steps that are being executed.
+ ///
+ ///
+ /// Setting up this context allows operations on the current step.
+ ///
+ public IImmutableStack StepContext
+ {
+ get;
+ private init;
+ } = ImmutableStack.Empty;
+
+ ///
+ /// The most recently added container from the container context.
+ ///
+ ///
+ /// It throws if there is no
+ /// container context.
+ ///
+ ///
+ public TestResultContainer CurrentContainer
+ {
+ get => this.ContainerContext.FirstOrDefault()
+ ?? throw new InvalidOperationException(
+ "No container context has been set up."
+ );
+ }
+
+ ///
+ /// A fixture that is being executed.
+ ///
+ ///
+ /// It throws if there is no
+ /// fixture context.
+ ///
+ ///
+ public FixtureResult CurrentFixture
+ {
+ get => this.FixtureContext ?? throw new InvalidOperationException(
+ "No fixture context has been set up."
+ );
+ }
+
+ ///
+ /// A test that is being executed.
+ ///
+ ///
+ /// It throws if there is no
+ /// test context.
+ ///
+ ///
+ public TestResult CurrentTest
+ {
+ get => this.TestContext ?? throw new InvalidOperationException(
+ "No test context has been set up."
+ );
+ }
+
+ ///
+ /// A step that is being executed.
+ ///
+ ///
+ /// It throws if there is no
+ /// step context.
+ ///
+ ///
+ public StepResult CurrentStep
+ {
+ get => this.StepContext.FirstOrDefault()
+ ?? throw new InvalidOperationException(
+ "No step context has been set up."
+ );
+ }
+
+ ///
+ /// A step container a next step should be put in.
+ ///
+ ///
+ /// A step container can be a fixture, a test of an another step.
+ /// It throws if neither
+ /// fixture nor test nor step context has been set up.
+ ///
+ ///
+ public ExecutableItem CurrentStepContainer
+ {
+ get => this.StepContext.FirstOrDefault() as ExecutableItem
+ ?? this.RootStepContainer
+ ?? throw new InvalidOperationException(
+ "No fixture, test, or step context has been set up."
+ );
+ }
+
+ ///
+ /// Creates a new with the specified
+ /// container pushed into the container context.
+ ///
+ ///
+ /// A container to push into the container context.
+ ///
+ ///
+ /// A new instance of with the modified
+ /// container context.
+ ///
+ ///
+ public AllureContext WithContainer(TestResultContainer container) =>
+ this with
+ {
+ ContainerContext = this.ContainerContext.Push(
+ container ?? throw new ArgumentNullException(
+ nameof(container)
+ )
+ )
+ };
+
+ ///
+ /// Creates a new without the most recently
+ /// added container in its container context. Requires a nonempty
+ /// container context.
+ ///
+ ///
+ /// Can't be called if a fixture or a test context exists.
+ ///
+ ///
+ /// A new instance of with the modified
+ /// container context.
+ ///
+ ///
+ public AllureContext WithNoLastContainer() =>
+ this with
+ {
+ ContainerContext = this.ValidateContainerCanBeRemoved()
+ .ContainerContext.Pop()
+ };
+
+ ///
+ /// Creates a new with the specified
+ /// fixture result as the fixture context. Requires at least one
+ /// container in the container context.
+ ///
+ ///
+ /// A new fixture context.
+ ///
+ ///
+ /// A new instance of with the modified
+ /// fixture context.
+ ///
+ ///
+ ///
+ public AllureContext WithFixtureContext(FixtureResult fixtureResult) =>
+ this with
+ {
+ FixtureContext = this.ValidateNewFixtureContext(
+ fixtureResult ?? throw new ArgumentNullException(
+ nameof(fixtureResult)
+ )
+ ),
+ StepContext = this.StepContext.Clear()
+ };
+
+ ///
+ /// Creates a new with no fixture and step
+ /// contexts.
+ ///
+ ///
+ /// A new instance of without a fixture or
+ /// a step contexts.
+ ///
+ public AllureContext WithNoFixtureContext() =>
+ this with
+ {
+ FixtureContext = null,
+ StepContext = this.StepContext.Clear()
+ };
+
+ ///
+ /// Creates a new with the specified
+ /// test result as the test context. Can't be used if a fixture context
+ /// exists.
+ ///
+ ///
+ /// A new test context.
+ ///
+ ///
+ /// A new instance of with the modified
+ /// test context.
+ ///
+ ///
+ ///
+ public AllureContext WithTestContext(TestResult testResult) =>
+ this with
+ {
+ TestContext = this.ValidateNewTestContext(
+ testResult ?? throw new ArgumentNullException(
+ nameof(testResult)
+ )
+ )
+ };
+
+ ///
+ /// Creates a new with no test, fixture
+ /// and step contexts.
+ ///
+ ///
+ /// A new instance of without a test,
+ /// a fixture and a step contexts.
+ ///
+ public AllureContext WithNoTestContext() =>
+ this with
+ {
+ FixtureContext = null,
+ TestContext = null,
+ StepContext = this.StepContext.Clear()
+ };
+
+ ///
+ /// Creates a new with the specified
+ /// step result pushed into the step context. Requires either a test or
+ /// a fixture context to exists.
+ ///
+ ///
+ /// A new step result to push into the step context.
+ ///
+ ///
+ /// A new instance of with the modified
+ /// step context.
+ ///
+ ///
+ ///
+ public AllureContext WithStep(StepResult stepResult) =>
+ this with
+ {
+ StepContext = this.StepContext.Push(
+ this.ValidateNewStep(
+ stepResult ?? throw new ArgumentNullException(
+ nameof(stepResult)
+ )
+ )
+ )
+ };
+
+ ///
+ /// Creates a new without the most recently
+ /// added step in its step context. Requires a nonempty step context.
+ ///
+ ///
+ /// A new instance of with the modified
+ /// step context.
+ ///
+ ///
+ public AllureContext WithNoLastStep() =>
+ this with
+ {
+ StepContext = this.StepContext.IsEmpty
+ ? throw new InvalidOperationException(
+ "Unable to exclude the latest step from the context " +
+ "because no step context has been set up."
+ ) : this.StepContext.Pop()
+ };
+
+ AllureContext ValidateContainerCanBeRemoved()
+ {
+ if (this.ContainerContext.IsEmpty)
+ {
+ throw new InvalidOperationException(
+ "Unable to exclude the latest container from the " +
+ "context because no container context has been set up."
+ );
+ }
+
+ if (this.FixtureContext is not null)
+ {
+ throw new InvalidOperationException(
+ "Unable to exclude the latest container from the " +
+ "context because a fixture context exists."
+ );
+ }
+
+ if (this.TestContext is not null)
+ {
+ throw new InvalidOperationException(
+ "Unable to exclude the latest container from the " +
+ "context because a test context exists."
+ );
+ }
+
+ return this;
+ }
+
+ ExecutableItem? RootStepContainer
+ {
+ get => this.FixtureContext as ExecutableItem ?? this.TestContext;
+ }
+
+ FixtureResult ValidateNewFixtureContext(FixtureResult fixture)
+ {
+ if (this.ContainerContext.IsEmpty)
+ {
+ throw new InvalidOperationException(
+ "Unable to set up the fixture context " +
+ "because there is no container context."
+ );
+ }
+
+ if (this.FixtureContext is not null)
+ {
+ throw new InvalidOperationException(
+ "Unable to set up the fixture context " +
+ "because another fixture context already exists."
+ );
+ }
+
+ return fixture;
+ }
+
+ TestResult ValidateNewTestContext(TestResult testResult)
+ {
+ if (this.FixtureContext is not null)
+ {
+ throw new InvalidOperationException(
+ "Unable to set up the test context " +
+ "because a fixture context is currently active."
+ );
+ }
+
+ if (this.TestContext is not null)
+ {
+ throw new InvalidOperationException(
+ "Unable to set up the test context " +
+ "because another test context already exists."
+ );
+ }
+
+ return testResult;
+ }
+
+ StepResult ValidateNewStep(StepResult stepResult)
+ {
+ if (this.RootStepContainer is null)
+ {
+ throw new InvalidOperationException(
+ "Unable to set up the step context because no test or" +
+ "fixture context exists."
+ );
+ }
+
+ return stepResult;
+ }
+ }
+}
From 03aa7d9e38a3ec2b82c74a4c719b337a7dcc6dbc Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Fri, 14 Jul 2023 21:09:04 +0700
Subject: [PATCH 02/24] Add concurrency tests. Reformulate context in terms of
active/inactive
---
.../{StorageTests => }/AllureContextTests.cs | 88 ++++---
.../AllureLifeCycleTest.cs | 43 ++++
Allure.Net.Commons.Tests/ConcurrencyTests.cs | 229 ++++++++++++++++++
.../InMemoryResultsWriter.cs | 34 +++
Allure.Net.Commons/AllureLifecycle.cs | 19 +-
Allure.Net.Commons/Storage/AllureContext.cs | 181 +++++++-------
6 files changed, 471 insertions(+), 123 deletions(-)
rename Allure.Net.Commons.Tests/{StorageTests => }/AllureContextTests.cs (84%)
create mode 100644 Allure.Net.Commons.Tests/ConcurrencyTests.cs
create mode 100644 Allure.Net.Commons.Tests/InMemoryResultsWriter.cs
diff --git a/Allure.Net.Commons.Tests/StorageTests/AllureContextTests.cs b/Allure.Net.Commons.Tests/AllureContextTests.cs
similarity index 84%
rename from Allure.Net.Commons.Tests/StorageTests/AllureContextTests.cs
rename to Allure.Net.Commons.Tests/AllureContextTests.cs
index f4755cc4..f0087344 100644
--- a/Allure.Net.Commons.Tests/StorageTests/AllureContextTests.cs
+++ b/Allure.Net.Commons.Tests/AllureContextTests.cs
@@ -1,14 +1,7 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Allure.Net.Commons.Storage;
+using Allure.Net.Commons.Storage;
using NUnit.Framework;
-namespace Allure.Net.Commons.Tests.StorageTests
+namespace Allure.Net.Commons.Tests
{
class AllureContextTests
{
@@ -25,31 +18,31 @@ public void TestEmptyContext()
Assert.That(
() => ctx.CurrentContainer,
Throws.InvalidOperationException.With.Message.EqualTo(
- "No container context has been set up."
+ "No container context is active."
)
);
Assert.That(
() => ctx.CurrentFixture,
Throws.InvalidOperationException.With.Message.EqualTo(
- "No fixture context has been set up."
+ "No fixture context is active."
)
);
Assert.That(
() => ctx.CurrentTest,
Throws.InvalidOperationException.With.Message.EqualTo(
- "No test context has been set up."
+ "No test context is active."
)
);
Assert.That(
() => ctx.CurrentStep,
Throws.InvalidOperationException.With.Message.EqualTo(
- "No step context has been set up."
+ "No step context is active."
)
);
Assert.That(
() => ctx.CurrentStepContainer,
Throws.InvalidOperationException.With.Message.EqualTo(
- "No fixture, test, or step context has been set up."
+ "No fixture, test, or step context is active."
)
);
}
@@ -66,6 +59,37 @@ public void TestContextOnly()
Assert.That(ctx.CurrentStepContainer, Is.SameAs(test));
}
+ [Test]
+ public void CanNotAddContainerIfTestIsSet()
+ {
+ var ctx = new AllureContext()
+ .WithTestContext(new());
+
+ Assert.That(
+ () => ctx.WithContainer(new()),
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "Unable to change a container context because a test " +
+ "context is active."
+ )
+ );
+ }
+
+ [Test]
+ public void CanNotAddContainerIfFixtureIsSet()
+ {
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithFixtureContext(new());
+
+ Assert.That(
+ () => ctx.WithContainer(new()),
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "Unable to change a container context because a fixture " +
+ "context is active."
+ )
+ );
+ }
+
[Test]
public void CanNotRemoveContainerIfTestIsSet()
{
@@ -76,8 +100,8 @@ public void CanNotRemoveContainerIfTestIsSet()
Assert.That(
ctx.WithNoLastContainer,
Throws.InvalidOperationException.With.Message.EqualTo(
- "Unable to exclude the latest container from the " +
- "context because a test context exists."
+ "Unable to change a container context because a test " +
+ "context is active."
)
);
}
@@ -145,8 +169,8 @@ public void CanNotRemoveContainerIfNoneExist()
Assert.That(
ctx.WithNoLastContainer,
Throws.InvalidOperationException.With.Message.EqualTo(
- "Unable to exclude the latest container from the " +
- "context because no container context has been set up."
+ "Unable to deactivate a container context " +
+ "because it's inactive."
)
);
}
@@ -185,8 +209,8 @@ public void FixtureContextRequiresContainer()
() => ctx.WithFixtureContext(new()),
Throws.InvalidOperationException
.With.Message.EqualTo(
- "Unable to set up the fixture context because there " +
- "is no container context."
+ "Unable to activate a fixture context because a " +
+ "container context is inactive."
)
);
}
@@ -226,8 +250,8 @@ public void CanNotRemoveContainerIfFixtureIsSet()
Assert.That(
ctx.WithNoLastContainer,
Throws.InvalidOperationException.With.Message.EqualTo(
- "Unable to exclude the latest container from the " +
- "context because a fixture context exists."
+ "Unable to change a container context because " +
+ "a fixture context is active."
)
);
}
@@ -245,8 +269,8 @@ public void FixturesCanNotBeNested()
() => ctx.WithFixtureContext(new()),
Throws.InvalidOperationException
.With.Message.EqualTo(
- "Unable to set up the fixture context " +
- "because another fixture context already exists."
+ "Unable to activate a fixture context because " +
+ "another fixture context is active."
)
);
}
@@ -273,8 +297,8 @@ public void TestsCanNotBeNested()
() => ctx.WithTestContext(new()),
Throws.InvalidOperationException
.With.Message.EqualTo(
- "Unable to set up the test context " +
- "because another test context already exists."
+ "Unable to activate a test context because another " +
+ "test context is active."
)
);
}
@@ -289,8 +313,8 @@ public void CanNotSetTestContextIfFixtureContextIsActive()
Assert.That(
() => ctx.WithTestContext(new()),
Throws.InvalidOperationException.With.Message.EqualTo(
- "Unable to set up the test context " +
- "because a fixture context is currently active."
+ "Unable to activate a test context because a fixture " +
+ "context is active."
)
);
}
@@ -358,8 +382,8 @@ public void StepCanNotBeAddedIfNoTestOrFixtureExists()
Assert.That(
() => ctx.WithStep(new()),
Throws.InvalidOperationException.With.Message.EqualTo(
- "Unable to set up the step context because no test or" +
- "fixture context exists."
+ "Unable to activate a step context because neither " +
+ "test, nor fixture context is active."
)
);
}
@@ -372,8 +396,8 @@ public void StepCanNotBeRemovedIfNoStepExists()
Assert.That(
() => ctx.WithNoLastStep(),
Throws.InvalidOperationException.With.Message.EqualTo(
- "Unable to exclude the latest step from the context " +
- "because no step context has been set up."
+ "Unable to deactivate a step context because it's " +
+ "already inactive."
)
);
}
diff --git a/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs b/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
index 6cd08fd5..08a100a0 100644
--- a/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
+++ b/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
@@ -1,4 +1,5 @@
using NUnit.Framework;
+using System;
using System.Threading.Tasks;
namespace Allure.Net.Commons.Tests
@@ -92,7 +93,49 @@ public void IntegrationTest()
.StopTestContainer(container.uuid)
.WriteTestContainer(container.uuid);
});
+ }
+ [Test, Description("Test step should be correctly added even if a " +
+ "before fixture overlaps with the test")]
+ public void BeforeFixtureMayOverlapsWithTest()
+ {
+ var writer = new InMemoryResultsWriter();
+ var lifecycle = new AllureLifecycle(_ => writer);
+ var container = new TestResultContainer
+ {
+ uuid = Guid.NewGuid().ToString()
+ };
+ var testResult = new TestResult
+ {
+ uuid = Guid.NewGuid().ToString()
+ };
+ var fixture = new FixtureResult { name = "fixture" };
+
+ lifecycle.StartTestContainer(container)
+ .StartTestCase(testResult)
+ .StartBeforeFixture(
+ container.uuid,
+ new(),
+ out var fixtureId
+ ).StopFixture(fixtureId)
+ .StartStep(new(), out var stepId)
+ .StopStep()
+ .StopTestCase(testResult.uuid)
+ .StopTestContainer(container.uuid)
+ .WriteTestCase(testResult.uuid)
+ .WriteTestContainer(container.uuid);
+
+ Assert.That(writer.testContainers.Count, Is.EqualTo(1));
+ Assert.That(writer.testContainers[0].uuid, Is.EqualTo(container.uuid));
+
+ Assert.That(writer.testContainers[0].befores.Count, Is.EqualTo(1));
+ Assert.That(writer.testContainers[0].befores[0].name, Is.EqualTo("fixture"));
+
+ Assert.That(writer.testContainers[0].children.Count, Is.EqualTo(1));
+ Assert.That(writer.testContainers[0].children[0], Is.EqualTo(testResult.uuid));
+
+ Assert.That(writer.testResults.Count, Is.EqualTo(1));
+ Assert.That(writer.testResults[0].uuid, Is.EqualTo(testResult.uuid));
}
}
}
diff --git a/Allure.Net.Commons.Tests/ConcurrencyTests.cs b/Allure.Net.Commons.Tests/ConcurrencyTests.cs
new file mode 100644
index 00000000..f1d4331f
--- /dev/null
+++ b/Allure.Net.Commons.Tests/ConcurrencyTests.cs
@@ -0,0 +1,229 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+
+namespace Allure.Net.Commons.Tests
+{
+ internal class ConcurrencyTests
+ {
+ InMemoryResultsWriter writer;
+ AllureLifecycle lifecycle;
+
+ [SetUp]
+ public void SetUp()
+ {
+ this.writer = new InMemoryResultsWriter();
+ this.lifecycle = new AllureLifecycle(_ => this.writer);
+ }
+
+ [Test]
+ public void ParallelTestsAreIsolated()
+ {
+ RunThreads(
+ () => this.AddTestWithSteps("test-1", "step-1-1", "step-1-2"),
+ () => this.AddTestWithSteps("test-2", "step-2-1", "step-2-2")
+ );
+
+ this.AssertTestWithSteps("test-1", "step-1-1", "step-1-2");
+ this.AssertTestWithSteps("test-2", "step-2-1", "step-2-2");
+ }
+
+ [Test]
+ public async Task AsyncTestsAreIsolated()
+ {
+ await Task.WhenAll(
+ this.AddTestWithStepsAsync("test-1", "step-1-1", "step-1-2"),
+ this.AddTestWithStepsAsync("test-2", "step-2-1", "step-2-2"),
+ this.AddTestWithStepsAsync("test-3", "step-3-1", "step-3-2")
+ );
+
+ this.AssertTestWithSteps("test-1", "step-1-1", "step-1-2");
+ this.AssertTestWithSteps("test-2", "step-2-1", "step-2-2");
+ this.AssertTestWithSteps("test-3", "step-3-1", "step-3-2");
+ }
+
+ [Test]
+ public void ParallelStepsOfTestAreIsolated()
+ {
+ this.WrapInTest("test-1", _ => RunThreads(
+ () => this.AddStep("step-1"),
+ () => this.AddStep("step-2")
+ ));
+
+ this.AssertTestWithSteps("test-1", "step-1", "step-2");
+ }
+
+ [Test]
+ public async Task AsyncStepsOfTestAreIsolated()
+ {
+ await this.WrapInTestAsync("test-1", async _ => await Task.WhenAll(
+ this.AddStepsAsync("step-1"),
+ this.AddStepsAsync("step-2"),
+ this.AddStepsAsync("step-3")
+ ));
+
+ this.AssertTestWithSteps("test-1", "step-1", "step-2", "step-3");
+ }
+
+ async Task AddTestWithStepsAsync(string name, params object[] steps)
+ {
+ var uuid = Guid.NewGuid().ToString();
+ this.lifecycle
+ .StartTestCase(new() { name = name, uuid = uuid });
+ await Task.Delay(1);
+ await this.AddStepsAsync(steps);
+ this.lifecycle
+ .StopTestCase(uuid)
+ .WriteTestCase(uuid);
+ }
+
+ void WrapInTest(string testName, Action action)
+ {
+ var uuid = Guid.NewGuid().ToString();
+ this.lifecycle.StartTestCase(
+ new() { name = testName, uuid = uuid }
+ );
+ action(uuid);
+ this.lifecycle
+ .StopTestCase(uuid)
+ .WriteTestCase(uuid);
+ }
+
+ async Task WrapInTestAsync(string testName, Func action)
+ {
+ var uuid = Guid.NewGuid().ToString();
+ this.lifecycle.StartTestCase(
+ new() { name = testName, uuid = uuid }
+ );
+ await Task.Delay(1);
+ await action(uuid);
+ this.lifecycle
+ .StopTestCase(uuid)
+ .WriteTestCase(uuid);
+ }
+
+ void AddTestWithSteps(string name, params object[] steps) =>
+ this.WrapInTest(name, _ => this.AddSteps(steps));
+
+ async Task AddStepsAsync(params object[] steps)
+ {
+ foreach (var step in steps)
+ {
+ if (step is string simpleStepName)
+ {
+ this.AddStep(simpleStepName);
+ }
+ else if (step is (string complexStepName, object[] substeps))
+ {
+ await this.AddStepWithSubstepsAsync(complexStepName, substeps);
+ }
+
+ await Task.Delay(1);
+ }
+ }
+
+ void AddSteps(params object[] steps)
+ {
+ foreach (var step in steps)
+ {
+ if (step is string simpleStepName)
+ {
+ this.AddStep(simpleStepName);
+ }
+ else if (step is (string complexStepName, object[] substeps))
+ {
+ this.AddStepWithSubsteps(complexStepName, substeps);
+ }
+ }
+ }
+
+ void AddStep(string name)
+ {
+ this.lifecycle.StartStep(
+ new() { name = name },
+ out var _
+ ).StopStep();
+ }
+
+ void AddStepWithSubsteps(string name, params object[] substeps)
+ {
+ this.lifecycle.StartStep(new() { name = name }, out var _);
+ this.AddSteps(substeps);
+ this.lifecycle.StopStep();
+ }
+
+ async Task AddStepWithSubstepsAsync(string name, params object[] substeps)
+ {
+ this.lifecycle.StartStep(new() { name = name }, out var _);
+ await this.AddStepsAsync(substeps);
+ this.lifecycle.StopStep();
+ }
+
+ void AssertTestWithSteps(string testName, params object[] steps)
+ {
+ Assert.That(
+ this.writer.testResults.Select(tr => tr.name),
+ Contains.Item(testName)
+ );
+ var test = this.writer.testResults.Single(tr => tr.name == testName);
+ this.AssertSteps(test.steps, steps);
+ }
+
+ void AssertSteps(List actualSteps, params object[] steps)
+ {
+ var expectedCount = steps.Count();
+ Assert.That(actualSteps.Count, Is.EqualTo(expectedCount));
+ for (var i = 0; i < expectedCount; i++)
+ {
+ var actualStep = actualSteps[i];
+ var step = steps.ElementAt(i);
+ if (!(step is (string expectedStepName, object[] substeps)))
+ {
+ expectedStepName = (string)step;
+ substeps = Array.Empty();
+ }
+ Assert.That(actualStep.name, Is.EqualTo(expectedStepName));
+ this.AssertSteps(actualStep.steps, substeps);
+ }
+ }
+
+ static void RunThreads(params Action[] jobs)
+ {
+ var errors = new List();
+ var threads = jobs.Select(
+ j => new Thread(
+ WrapThreadCallbackError(j, errors)
+ )
+ ).ToList();
+ foreach(var thread in threads)
+ {
+ thread.Start();
+ }
+ foreach (var thread in threads)
+ {
+ thread.Join();
+ }
+
+ Assert.That(errors, Is.Empty);
+ }
+
+ static ThreadStart WrapThreadCallbackError(
+ Action callback,
+ List errors
+ ) => () =>
+ {
+ try
+ {
+ callback();
+ }
+ catch (Exception ex)
+ {
+ errors.Add(ex);
+ }
+ };
+ }
+}
diff --git a/Allure.Net.Commons.Tests/InMemoryResultsWriter.cs b/Allure.Net.Commons.Tests/InMemoryResultsWriter.cs
new file mode 100644
index 00000000..ab619d1e
--- /dev/null
+++ b/Allure.Net.Commons.Tests/InMemoryResultsWriter.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using Allure.Net.Commons.Writer;
+
+namespace Allure.Net.Commons.Tests
+{
+ class InMemoryResultsWriter : IAllureResultsWriter
+ {
+ internal List testResults = new();
+ internal List testContainers = new();
+ internal List<(string Source, byte[] Content)> attachments = new();
+
+ public void CleanUp()
+ {
+ this.testResults.Clear();
+ this.testContainers.Clear();
+ this.attachments.Clear();
+ }
+
+ public void Write(TestResult testResult)
+ {
+ this.testResults.Add(testResult);
+ }
+
+ public void Write(TestResultContainer testResult)
+ {
+ this.testContainers.Add(testResult);
+ }
+
+ public void Write(string source, byte[] attachment)
+ {
+ this.attachments.Add((source, attachment));
+ }
+ }
+}
diff --git a/Allure.Net.Commons/AllureLifecycle.cs b/Allure.Net.Commons/AllureLifecycle.cs
index 8bd61249..97a1b295 100644
--- a/Allure.Net.Commons/AllureLifecycle.cs
+++ b/Allure.Net.Commons/AllureLifecycle.cs
@@ -33,12 +33,25 @@ public class AllureLifecycle
internal AllureLifecycle(): this(GetConfiguration())
{
}
-
- internal AllureLifecycle(JObject config)
+
+ internal AllureLifecycle(
+ Func writerFactory
+ ) : this(GetConfiguration(), writerFactory)
+ {
+ }
+
+ internal AllureLifecycle(JObject config): this(config, c => new FileSystemResultsWriter(c))
+ {
+ }
+
+ internal AllureLifecycle(
+ JObject config,
+ Func writerFactory
+ )
{
JsonConfiguration = config.ToString();
AllureConfiguration = AllureConfiguration.ReadFromJObject(config);
- writer = new FileSystemResultsWriter(AllureConfiguration);
+ writer = writerFactory(AllureConfiguration);
storage = new AllureStorage();
}
diff --git a/Allure.Net.Commons/Storage/AllureContext.cs b/Allure.Net.Commons/Storage/AllureContext.cs
index 4c329031..984d9bff 100644
--- a/Allure.Net.Commons/Storage/AllureContext.cs
+++ b/Allure.Net.Commons/Storage/AllureContext.cs
@@ -7,24 +7,23 @@
namespace Allure.Net.Commons.Storage
{
///
- /// Represents information related to a particular test at all stages of
- /// its life cycle.
+ /// Represents allure-related contextual information required to collect
+ /// the report data during a test execution. Comprises four contexts:
+ /// container, fxiture, test, and step, as well as methods to query and
+ /// modify them.
///
- ///
///
/// Instances of this class are immutable to ensure proper isolation
/// between different tests and steps that may potentially be run
- /// cuncurrently either by a test framework or by an end user.
- ///
- /// Methods in this class don't mutate allure model.
+ /// cuncurrently either by a test framework or by an end user.
///
- public record class AllureContext
+ internal record class AllureContext
{
///
/// A stack of fixture containers affecting subsequent tests.
///
///
- /// Setting up this context allows operations on the current container
+ /// Activating this context allows operations on the current container
/// (including adding a fixture to or removing a fixture from the
/// current container).
///
@@ -38,10 +37,11 @@ public IImmutableStack ContainerContext
/// A fixture that is being currently executed.
///
///
- /// Setting up this context allows operations on the current fixture
+ /// Activating this context allows operations on the current fixture
/// result.
- /// This property differs from in that it
- /// returns null if no fixture context exists instead of throwing.
+ /// This property differs from in that
+ /// instead of throwing it returns null if a fixture context isn't
+ /// active.
///
public FixtureResult? FixtureContext { get; private init; }
@@ -49,11 +49,11 @@ public IImmutableStack ContainerContext
/// A test that is being executed.
///
///
- /// Setting up this context allows operations on the current test
+ /// Activating this context allows operations on the current test
/// result.
///
- /// This property differs from in that it
- /// returns null if no test context exists instead of throwing.
+ /// This property differs from in that
+ /// instead of throwing it returns null if a test context isn't active.
///
public TestResult? TestContext { get; private init; }
@@ -61,7 +61,7 @@ public IImmutableStack ContainerContext
/// A stack of nested steps that are being executed.
///
///
- /// Setting up this context allows operations on the current step.
+ /// Activating this context allows operations on the current step.
///
public IImmutableStack StepContext
{
@@ -73,15 +73,15 @@ public IImmutableStack StepContext
/// The most recently added container from the container context.
///
///
- /// It throws if there is no
- /// container context.
+ /// It throws if a container
+ /// context isn't active.
///
///
public TestResultContainer CurrentContainer
{
get => this.ContainerContext.FirstOrDefault()
?? throw new InvalidOperationException(
- "No container context has been set up."
+ "No container context is active."
);
}
@@ -89,14 +89,14 @@ public TestResultContainer CurrentContainer
/// A fixture that is being executed.
///
///
- /// It throws if there is no
- /// fixture context.
+ /// It throws if a fixture
+ /// context isn't active.
///
///
public FixtureResult CurrentFixture
{
get => this.FixtureContext ?? throw new InvalidOperationException(
- "No fixture context has been set up."
+ "No fixture context is active."
);
}
@@ -104,14 +104,14 @@ public FixtureResult CurrentFixture
/// A test that is being executed.
///
///
- /// It throws if there is no
- /// test context.
+ /// It throws if a test context
+ /// isn't active.
///
///
public TestResult CurrentTest
{
get => this.TestContext ?? throw new InvalidOperationException(
- "No test context has been set up."
+ "No test context is active."
);
}
@@ -119,15 +119,15 @@ public TestResult CurrentTest
/// A step that is being executed.
///
///
- /// It throws if there is no
- /// step context.
+ /// It throws if a step context
+ /// isn't active.
///
///
public StepResult CurrentStep
{
get => this.StepContext.FirstOrDefault()
?? throw new InvalidOperationException(
- "No step context has been set up."
+ "No step context is active."
);
}
@@ -137,7 +137,7 @@ public StepResult CurrentStep
///
/// A step container can be a fixture, a test of an another step.
/// It throws if neither
- /// fixture nor test nor step context has been set up.
+ /// fixture, nor test, nor step context is active.
///
///
public ExecutableItem CurrentStepContainer
@@ -145,24 +145,27 @@ public ExecutableItem CurrentStepContainer
get => this.StepContext.FirstOrDefault() as ExecutableItem
?? this.RootStepContainer
?? throw new InvalidOperationException(
- "No fixture, test, or step context has been set up."
+ "No fixture, test, or step context is active."
);
}
///
- /// Creates a new with the specified
- /// container pushed into the container context.
+ /// Creates a new with the active container
+ /// context and the specified container pushed on top of it.
///
+ ///
+ /// Can't be called if a fixture or a test context is active.
+ ///
///
- /// A container to push into the container context.
+ /// A container to push on top of the container context.
///
///
/// A new instance of with the modified
- /// container context.
+ /// (always active) container context.
///
///
public AllureContext WithContainer(TestResultContainer container) =>
- this with
+ this.ValidateContainerContextCanBeModified() with
{
ContainerContext = this.ContainerContext.Push(
container ?? throw new ArgumentNullException(
@@ -173,15 +176,16 @@ this with
///
/// Creates a new without the most recently
- /// added container in its container context. Requires a nonempty
- /// container context.
+ /// added container in its container context. Requires an active
+ /// container context. Deactivates a container context if it consists
+ /// of one container only before the call.
///
///
- /// Can't be called if a fixture or a test context exists.
+ /// Can't be called if a fixture or a test context is active.
///
///
/// A new instance of with the modified
- /// container context.
+ /// (possibly inactive) container context.
///
///
public AllureContext WithNoLastContainer() =>
@@ -192,16 +196,16 @@ this with
};
///
- /// Creates a new with the specified
- /// fixture result as the fixture context. Requires at least one
- /// container in the container context.
+ /// Creates a new with the active fixture
+ /// context that is set to the specified fixture. Requires an active
+ /// container context.
///
///
/// A new fixture context.
///
///
/// A new instance of with the modified
- /// fixture context.
+ /// (always active) fixture context.
///
///
///
@@ -217,13 +221,9 @@ this with
};
///
- /// Creates a new with no fixture and step
- /// contexts.
+ /// Creates a new with inactive fixture and
+ /// step contexts.
///
- ///
- /// A new instance of without a fixture or
- /// a step contexts.
- ///
public AllureContext WithNoFixtureContext() =>
this with
{
@@ -232,16 +232,16 @@ this with
};
///
- /// Creates a new with the specified
- /// test result as the test context. Can't be used if a fixture context
- /// exists.
+ /// Creates a new with the active test
+ /// context that is set to the specified test result.
+ /// Can't be used if a fixture context is active.
///
///
/// A new test context.
///
///
/// A new instance of with the modified
- /// test context.
+ /// (always active) test context.
///
///
///
@@ -256,13 +256,9 @@ this with
};
///
- /// Creates a new with no test, fixture
- /// and step contexts.
+ /// Creates a new with inactive test,
+ /// fixture and step contexts.
///
- ///
- /// A new instance of without a test,
- /// a fixture and a step contexts.
- ///
public AllureContext WithNoTestContext() =>
this with
{
@@ -272,16 +268,18 @@ this with
};
///
- /// Creates a new with the specified
- /// step result pushed into the step context. Requires either a test or
- /// a fixture context to exists.
+ /// Creates a new with the active step
+ /// context and the specified step result pushed on top of it.
///
+ ///
+ /// Can't be called if neither fixture, nor test context is active.
+ ///
///
- /// A new step result to push into the step context.
+ /// A new step result to push on top of the step context.
///
///
/// A new instance of with the modified
- /// step context.
+ /// (always active) step context.
///
///
///
@@ -299,11 +297,13 @@ this with
///
/// Creates a new without the most recently
- /// added step in its step context. Requires a nonempty step context.
+ /// added step in its step context. Requires an active step context.
+ /// Deactivates a step context if it consists of one step only before
+ /// the call.
///
///
/// A new instance of with the modified
- /// step context.
+ /// (possibly inactive) step context.
///
///
public AllureContext WithNoLastStep() =>
@@ -311,38 +311,43 @@ this with
{
StepContext = this.StepContext.IsEmpty
? throw new InvalidOperationException(
- "Unable to exclude the latest step from the context " +
- "because no step context has been set up."
+ "Unable to deactivate a step context because it's " +
+ "already inactive."
) : this.StepContext.Pop()
};
- AllureContext ValidateContainerCanBeRemoved()
+ AllureContext ValidateContainerContextCanBeModified()
{
- if (this.ContainerContext.IsEmpty)
+ if (this.FixtureContext is not null)
{
throw new InvalidOperationException(
- "Unable to exclude the latest container from the " +
- "context because no container context has been set up."
+ "Unable to change a container context because a " +
+ "fixture context is active."
);
}
- if (this.FixtureContext is not null)
+ if (this.TestContext is not null)
{
throw new InvalidOperationException(
- "Unable to exclude the latest container from the " +
- "context because a fixture context exists."
+ "Unable to change a container context because a test " +
+ "context is active."
);
}
- if (this.TestContext is not null)
+ return this;
+ }
+
+ AllureContext ValidateContainerCanBeRemoved()
+ {
+ if (this.ContainerContext.IsEmpty)
{
throw new InvalidOperationException(
- "Unable to exclude the latest container from the " +
- "context because a test context exists."
+ "Unable to deactivate a container context because it's " +
+ "inactive."
);
}
- return this;
+ return this.ValidateContainerContextCanBeModified();
}
ExecutableItem? RootStepContainer
@@ -355,16 +360,16 @@ FixtureResult ValidateNewFixtureContext(FixtureResult fixture)
if (this.ContainerContext.IsEmpty)
{
throw new InvalidOperationException(
- "Unable to set up the fixture context " +
- "because there is no container context."
+ "Unable to activate a fixture context " +
+ "because a container context is inactive."
);
}
if (this.FixtureContext is not null)
{
throw new InvalidOperationException(
- "Unable to set up the fixture context " +
- "because another fixture context already exists."
+ "Unable to activate a fixture context " +
+ "because another fixture context is active."
);
}
@@ -376,16 +381,16 @@ TestResult ValidateNewTestContext(TestResult testResult)
if (this.FixtureContext is not null)
{
throw new InvalidOperationException(
- "Unable to set up the test context " +
- "because a fixture context is currently active."
+ "Unable to activate a test context " +
+ "because a fixture context is active."
);
}
if (this.TestContext is not null)
{
throw new InvalidOperationException(
- "Unable to set up the test context " +
- "because another test context already exists."
+ "Unable to activate a test context " +
+ "because another test context is active."
);
}
@@ -397,8 +402,8 @@ StepResult ValidateNewStep(StepResult stepResult)
if (this.RootStepContainer is null)
{
throw new InvalidOperationException(
- "Unable to set up the step context because no test or" +
- "fixture context exists."
+ "Unable to activate a step context because neither test, " +
+ "nor fixture context is active."
);
}
From 152b66c496c031818bc4c9f3d021086ac14b11c0 Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Sat, 15 Jul 2023 07:19:49 +0700
Subject: [PATCH 03/24] Add tests on the context capturing by child
threads/tasks
---
Allure.Net.Commons.Tests/ConcurrencyTests.cs | 99 ++++++++++++++++++++
1 file changed, 99 insertions(+)
diff --git a/Allure.Net.Commons.Tests/ConcurrencyTests.cs b/Allure.Net.Commons.Tests/ConcurrencyTests.cs
index f1d4331f..5c712fef 100644
--- a/Allure.Net.Commons.Tests/ConcurrencyTests.cs
+++ b/Allure.Net.Commons.Tests/ConcurrencyTests.cs
@@ -3,6 +3,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using System.Xml.Linq;
using NUnit.Framework;
using NUnit.Framework.Internal;
@@ -69,6 +70,82 @@ await this.WrapInTestAsync("test-1", async _ => await Task.WhenAll(
this.AssertTestWithSteps("test-1", "step-1", "step-2", "step-3");
}
+ [Test]
+ public void ContextCapturedBySubThreads()
+ {
+ /*
+ * test | Parent thread
+ * - outer | Parent thread
+ * - inner-1 | Child thread 1
+ * - inner-1-1 | Child thread 1
+ * - inner-1-2 | Child thread 1
+ * - inner-2 | Child thread 2
+ * - inner-2-1 | Child thread 2
+ * - inner-2-2 | Child thread 2
+ */
+ this.WrapInTest(
+ "test",
+ _ => this.WrapInStep(
+ "outer",
+ _ => RunThreads(
+ () => this.AddSteps(
+ ("inner-1", new object[] { "inner-1-1", "inner-1-2" })
+ ),
+ () => this.AddSteps(
+ ("inner-2", new object[] { "inner-2-1", "inner-2-2" })
+ )
+ )
+ )
+ );
+
+ this.AssertTestWithSteps(
+ "test",
+ ("outer", new object[]
+ {
+ ("inner-1", new object[] { "inner-1-1", "inner-1-2" }),
+ ("inner-2", new object[] { "inner-2-1", "inner-2-2" })
+ })
+ );
+ }
+
+ [Test]
+ public async Task ContextCapturedBySubTasks()
+ {
+ /*
+ * test | Parent task
+ * - outer | Parent task
+ * - inner-1 | Child task 1
+ * - inner-1-1 | Child task 1
+ * - inner-1-2 | Child task 1
+ * - inner-2 | Child task 2
+ * - inner-2-1 | Child task 2
+ * - inner-2-2 | Child task 2
+ */
+ await this.WrapInTestAsync(
+ "test",
+ async _ => await this.WrapInStepAsync(
+ "outer",
+ async _ => await Task.WhenAll(
+ this.AddStepsAsync(
+ ("inner-1", new object[] { "inner-1-1", "inner-1-2" })
+ ),
+ this.AddStepsAsync(
+ ("inner-2", new object[] { "inner-2-1", "inner-2-2" })
+ )
+ )
+ )
+ );
+
+ this.AssertTestWithSteps(
+ "test",
+ ("outer", new object[]
+ {
+ ("inner-1", new object[] { "inner-1-1", "inner-1-2" }),
+ ("inner-2", new object[] { "inner-2-1", "inner-2-2" })
+ })
+ );
+ }
+
async Task AddTestWithStepsAsync(string name, params object[] steps)
{
var uuid = Guid.NewGuid().ToString();
@@ -93,6 +170,28 @@ void WrapInTest(string testName, Action action)
.WriteTestCase(uuid);
}
+ void WrapInStep(string stepName, Action action)
+ {
+ this.lifecycle.StartStep(
+ new() { name = stepName },
+ out var stepId
+ );
+ action(stepId);
+ this.lifecycle
+ .StopStep();
+ }
+
+ async Task WrapInStepAsync(string stepName, Func action)
+ {
+ this.lifecycle.StartStep(
+ new() { name = stepName },
+ out var stepId
+ );
+ await action(stepId);
+ this.lifecycle
+ .StopStep();
+ }
+
async Task WrapInTestAsync(string testName, Func action)
{
var uuid = Guid.NewGuid().ToString();
From 5abf894820fcbcb8979b18cbc05741c15e32313e Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Wed, 19 Jul 2023 06:11:27 +0700
Subject: [PATCH 04/24] Modify lifecycle/storage to use new context model. Add
context get/set API
---
.../AllureLifeCycleTest.cs | 58 +++-
Allure.Net.Commons.Tests/ConcurrencyTests.cs | 59 ++--
Allure.Net.Commons/AllureLifecycle.cs | 276 +++++++++++++-----
Allure.Net.Commons/Storage/AllureContext.cs | 38 +--
Allure.Net.Commons/Storage/AllureStorage.cs | 213 ++++++++++++--
5 files changed, 483 insertions(+), 161 deletions(-)
diff --git a/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs b/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
index 08a100a0..085268f5 100644
--- a/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
+++ b/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
@@ -1,5 +1,7 @@
-using NUnit.Framework;
+using Allure.Net.Commons.Storage;
+using NUnit.Framework;
using System;
+using System.Threading;
using System.Threading.Tasks;
namespace Allure.Net.Commons.Tests
@@ -19,6 +21,19 @@ public void ShouldSetDefaultStateAsNone()
Assert.AreEqual(Status.none, new TestResult().status);
}
+ [Test]
+ public void Way()
+ {
+ AsyncLocal myContext = new();
+ myContext.Value = "Outer";
+ Parallel.For(0, 2, i =>
+ {
+ Console.WriteLine(myContext.Value);
+ myContext.Value = $"Inner {i}";
+ });
+ Console.WriteLine(myContext.Value);
+ }
+
[Test, Description("Integration Test")]
public void IntegrationTest()
{
@@ -95,7 +110,7 @@ public void IntegrationTest()
});
}
- [Test, Description("Test step should be correctly added even if a " +
+ [Test, Description("A test step should be correctly added even if a " +
"before fixture overlaps with the test")]
public void BeforeFixtureMayOverlapsWithTest()
{
@@ -113,17 +128,14 @@ public void BeforeFixtureMayOverlapsWithTest()
lifecycle.StartTestContainer(container)
.StartTestCase(testResult)
- .StartBeforeFixture(
- container.uuid,
- new(),
- out var fixtureId
- ).StopFixture(fixtureId)
- .StartStep(new(), out var stepId)
+ .StartBeforeFixture(fixture)
+ .StopFixture()
+ .StartStep(new())
.StopStep()
- .StopTestCase(testResult.uuid)
- .StopTestContainer(container.uuid)
- .WriteTestCase(testResult.uuid)
- .WriteTestContainer(container.uuid);
+ .StopTestCase()
+ .StopTestContainer()
+ .WriteTestCase()
+ .WriteTestContainer();
Assert.That(writer.testContainers.Count, Is.EqualTo(1));
Assert.That(writer.testContainers[0].uuid, Is.EqualTo(container.uuid));
@@ -137,5 +149,27 @@ out var fixtureId
Assert.That(writer.testResults.Count, Is.EqualTo(1));
Assert.That(writer.testResults[0].uuid, Is.EqualTo(testResult.uuid));
}
+
+ [Test]
+ public async Task AllureContextCouldBeAssigned()
+ {
+ var writer = new InMemoryResultsWriter();
+ var lifecycle = new AllureLifecycle(_ => writer);
+ AllureContext context = null;
+ await Task.Factory.StartNew(() =>
+ {
+ lifecycle.StartTestCase(new()
+ {
+ uuid = Guid.NewGuid().ToString()
+ });
+ context = lifecycle.Context;
+ });
+ lifecycle.Context = context;
+
+ lifecycle.StopTestCase();
+ lifecycle.WriteTestCase();
+
+ Assert.That(writer.testResults, Is.Not.Empty);
+ }
}
}
diff --git a/Allure.Net.Commons.Tests/ConcurrencyTests.cs b/Allure.Net.Commons.Tests/ConcurrencyTests.cs
index 5c712fef..0809edd2 100644
--- a/Allure.Net.Commons.Tests/ConcurrencyTests.cs
+++ b/Allure.Net.Commons.Tests/ConcurrencyTests.cs
@@ -50,7 +50,7 @@ await Task.WhenAll(
[Test]
public void ParallelStepsOfTestAreIsolated()
{
- this.WrapInTest("test-1", _ => RunThreads(
+ this.WrapInTest("test-1", () => RunThreads(
() => this.AddStep("step-1"),
() => this.AddStep("step-2")
));
@@ -61,7 +61,7 @@ public void ParallelStepsOfTestAreIsolated()
[Test]
public async Task AsyncStepsOfTestAreIsolated()
{
- await this.WrapInTestAsync("test-1", async _ => await Task.WhenAll(
+ await this.WrapInTestAsync("test-1", async () => await Task.WhenAll(
this.AddStepsAsync("step-1"),
this.AddStepsAsync("step-2"),
this.AddStepsAsync("step-3")
@@ -85,9 +85,9 @@ public void ContextCapturedBySubThreads()
*/
this.WrapInTest(
"test",
- _ => this.WrapInStep(
+ () => this.WrapInStep(
"outer",
- _ => RunThreads(
+ () => RunThreads(
() => this.AddSteps(
("inner-1", new object[] { "inner-1-1", "inner-1-2" })
),
@@ -123,9 +123,9 @@ public async Task ContextCapturedBySubTasks()
*/
await this.WrapInTestAsync(
"test",
- async _ => await this.WrapInStepAsync(
+ async () => await this.WrapInStepAsync(
"outer",
- async _ => await Task.WhenAll(
+ async () => await Task.WhenAll(
this.AddStepsAsync(
("inner-1", new object[] { "inner-1-1", "inner-1-2" })
),
@@ -158,55 +158,51 @@ async Task AddTestWithStepsAsync(string name, params object[] steps)
.WriteTestCase(uuid);
}
- void WrapInTest(string testName, Action action)
+ void WrapInTest(string testName, Action action)
{
- var uuid = Guid.NewGuid().ToString();
this.lifecycle.StartTestCase(
- new() { name = testName, uuid = uuid }
+ new() { name = testName, uuid = Guid.NewGuid().ToString() }
);
- action(uuid);
+ action();
this.lifecycle
- .StopTestCase(uuid)
- .WriteTestCase(uuid);
+ .StopTestCase()
+ .WriteTestCase();
}
- void WrapInStep(string stepName, Action action)
+ void WrapInStep(string stepName, Action action)
{
this.lifecycle.StartStep(
- new() { name = stepName },
- out var stepId
+ new() { name = stepName }
);
- action(stepId);
+ action();
this.lifecycle
.StopStep();
}
- async Task WrapInStepAsync(string stepName, Func action)
+ async Task WrapInStepAsync(string stepName, Func action)
{
this.lifecycle.StartStep(
- new() { name = stepName },
- out var stepId
+ new() { name = stepName }
);
- await action(stepId);
+ await action();
this.lifecycle
.StopStep();
}
- async Task WrapInTestAsync(string testName, Func action)
+ async Task WrapInTestAsync(string testName, Func action)
{
- var uuid = Guid.NewGuid().ToString();
this.lifecycle.StartTestCase(
- new() { name = testName, uuid = uuid }
+ new() { name = testName, uuid = Guid.NewGuid().ToString() }
);
await Task.Delay(1);
- await action(uuid);
+ await action();
this.lifecycle
- .StopTestCase(uuid)
- .WriteTestCase(uuid);
+ .StopTestCase()
+ .WriteTestCase();
}
void AddTestWithSteps(string name, params object[] steps) =>
- this.WrapInTest(name, _ => this.AddSteps(steps));
+ this.WrapInTest(name, () => this.AddSteps(steps));
async Task AddStepsAsync(params object[] steps)
{
@@ -243,21 +239,20 @@ void AddSteps(params object[] steps)
void AddStep(string name)
{
this.lifecycle.StartStep(
- new() { name = name },
- out var _
+ new() { name = name }
).StopStep();
}
void AddStepWithSubsteps(string name, params object[] substeps)
{
- this.lifecycle.StartStep(new() { name = name }, out var _);
+ this.lifecycle.StartStep(new() { name = name });
this.AddSteps(substeps);
this.lifecycle.StopStep();
}
async Task AddStepWithSubstepsAsync(string name, params object[] substeps)
{
- this.lifecycle.StartStep(new() { name = name }, out var _);
+ this.lifecycle.StartStep(new() { name = name });
await this.AddStepsAsync(substeps);
this.lifecycle.StopStep();
}
@@ -274,7 +269,7 @@ void AssertTestWithSteps(string testName, params object[] steps)
void AssertSteps(List actualSteps, params object[] steps)
{
- var expectedCount = steps.Count();
+ var expectedCount = steps.Length;
Assert.That(actualSteps.Count, Is.EqualTo(expectedCount));
for (var i = 0; i < expectedCount; i++)
{
diff --git a/Allure.Net.Commons/AllureLifecycle.cs b/Allure.Net.Commons/AllureLifecycle.cs
index 97a1b295..7c7d260d 100644
--- a/Allure.Net.Commons/AllureLifecycle.cs
+++ b/Allure.Net.Commons/AllureLifecycle.cs
@@ -5,12 +5,13 @@
using System.Runtime.CompilerServices;
using System.Threading;
using Allure.Net.Commons.Configuration;
-using Allure.Net.Commons.Helpers;
using Allure.Net.Commons.Storage;
using Allure.Net.Commons.Writer;
using HeyRed.Mime;
using Newtonsoft.Json.Linq;
+#nullable enable
+
[assembly: InternalsVisibleTo("Allure.Net.Commons.Tests")]
namespace Allure.Net.Commons
@@ -22,13 +23,30 @@ public class AllureLifecycle
public IReadOnlyDictionary TypeFormatters =>
new ReadOnlyDictionary(typeFormatters);
- private static readonly object Lockobj = new();
- private static AllureLifecycle instance;
+ private static readonly Lazy instance =
+ new(Initialize);
private readonly AllureStorage storage;
private readonly IAllureResultsWriter writer;
+
+ ///
+ /// Gets or sets an execution context of Allure. Use this property if
+ /// the context is set not in the same async domain where a
+ /// test/fixture function is executed.
+ ///
+ ///
+ /// This property is intended to be used by Allure integrations with
+ /// test frameworks, not by end user's code.
+ ///
+ public AllureContext Context
+ {
+ get => this.storage.CurrentContext;
+ set => this.storage.CurrentContext = value;
+ }
+
/// Method to get the key for separation the steps for different tests.
- public static Func CurrentTestIdGetter { get; set; } = () => Thread.CurrentThread.ManagedThreadId.ToString();
+ public static Func CurrentTestIdGetter { get; set; } =
+ () => Thread.CurrentThread.ManagedThreadId.ToString();
internal AllureLifecycle(): this(GetConfiguration())
{
@@ -56,29 +74,12 @@ Func writerFactory
}
public string JsonConfiguration { get; private set; }
+
public AllureConfiguration AllureConfiguration { get; }
public string ResultsDirectory => writer.ToString();
- public static AllureLifecycle Instance
- {
- get
- {
- if (instance == null)
- {
- lock (Lockobj)
- {
- if (instance == null)
- {
- var localInstance = new AllureLifecycle();
- Interlocked.Exchange(ref instance, localInstance);
- }
- }
- }
-
- return instance;
- }
- }
+ public static AllureLifecycle Instance { get => instance.Value; }
public void AddTypeFormatter(TypeFormatter typeFormatter) =>
AddTypeFormatterImpl(typeof(T), typeFormatter);
@@ -91,7 +92,10 @@ private void AddTypeFormatterImpl(Type type, ITypeFormatter formatter) =>
public virtual AllureLifecycle StartTestContainer(TestResultContainer container)
{
container.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- storage.Put(container.uuid, container);
+ this.storage.CurrentTestContainerOrNull?.children.Add(
+ container.uuid
+ );
+ storage.PutTestContainer(container);
return this;
}
@@ -102,21 +106,43 @@ public virtual AllureLifecycle StartTestContainer(string parentUuid, TestResultC
return this;
}
+ public virtual AllureLifecycle UpdateTestContainer(Action update)
+ {
+ update.Invoke(storage.CurrentTestContainer);
+ return this;
+ }
+
public virtual AllureLifecycle UpdateTestContainer(string uuid, Action update)
{
update.Invoke(storage.Get(uuid));
return this;
}
+ public virtual AllureLifecycle StopTestContainer()
+ {
+ UpdateTestContainer(stopContainer);
+ return this;
+ }
+
public virtual AllureLifecycle StopTestContainer(string uuid)
{
- UpdateTestContainer(uuid, c => c.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds());
+ UpdateTestContainer(uuid, stopContainer);
+ return this;
+ }
+
+ public virtual AllureLifecycle WriteTestContainer()
+ {
+ writer.Write(
+ storage.RemoveTestContainer()
+ );
return this;
}
public virtual AllureLifecycle WriteTestContainer(string uuid)
{
- writer.Write(storage.Remove(uuid));
+ writer.Write(
+ storage.RemoveTestContainer(uuid)
+ );
return this;
}
@@ -124,6 +150,27 @@ public virtual AllureLifecycle WriteTestContainer(string uuid)
#region Fixture
+ public virtual AllureLifecycle StartBeforeFixture(FixtureResult result)
+ {
+ UpdateTestContainer(container => container.befores.Add(result));
+ this.StartFixture(result);
+ return this;
+ }
+
+ public virtual AllureLifecycle StartBeforeFixture(FixtureResult result, out string uuid)
+ {
+ uuid = CreateUuid();
+ StartBeforeFixture(uuid, result);
+ return this;
+ }
+
+ public virtual AllureLifecycle StartBeforeFixture(string uuid, FixtureResult result)
+ {
+ UpdateTestContainer(container => container.befores.Add(result));
+ StartFixture(uuid, result);
+ return this;
+ }
+
public virtual AllureLifecycle StartBeforeFixture(string parentUuid, FixtureResult result, out string uuid)
{
uuid = Guid.NewGuid().ToString("N");
@@ -138,9 +185,16 @@ public virtual AllureLifecycle StartBeforeFixture(string parentUuid, string uuid
return this;
}
+ public virtual AllureLifecycle StartAfterFixture(FixtureResult result)
+ {
+ this.UpdateTestContainer(c => c.afters.Add(result));
+ this.StartFixture(result);
+ return this;
+ }
+
public virtual AllureLifecycle StartAfterFixture(string parentUuid, FixtureResult result, out string uuid)
{
- uuid = Guid.NewGuid().ToString("N");
+ uuid = CreateUuid();
StartAfterFixture(parentUuid, uuid, result);
return this;
}
@@ -154,7 +208,7 @@ public virtual AllureLifecycle StartAfterFixture(string parentUuid, string uuid,
public virtual AllureLifecycle UpdateFixture(Action update)
{
- UpdateFixture(storage.GetRootStep(), update);
+ update?.Invoke(storage.CurrentFixture);
return this;
}
@@ -167,13 +221,20 @@ public virtual AllureLifecycle UpdateFixture(string uuid, Action
public virtual AllureLifecycle StopFixture(Action beforeStop)
{
UpdateFixture(beforeStop);
- return StopFixture(storage.GetRootStep());
+ return this.StopFixture();
+ }
+
+ public virtual AllureLifecycle StopFixture()
+ {
+ var fixture = this.storage.RemoveFixture();
+ fixture.stage = Stage.finished;
+ fixture.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ return this;
}
public virtual AllureLifecycle StopFixture(string uuid)
{
- var fixture = storage.Remove(uuid);
- storage.ClearStepContext();
+ var fixture = this.storage.RemoveFixture(uuid);
fixture.stage = Stage.finished;
fixture.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
return this;
@@ -191,43 +252,62 @@ public virtual AllureLifecycle StartTestCase(string containerUuid, TestResult te
public virtual AllureLifecycle StartTestCase(TestResult testResult)
{
+ this.storage.CurrentTestContainerOrNull?.children.Add(testResult.uuid);
testResult.stage = Stage.running;
- testResult.start = testResult.start == 0L ? DateTimeOffset.Now.ToUnixTimeMilliseconds() : testResult.start;
- storage.Put(testResult.uuid, testResult);
- storage.ClearStepContext();
- storage.StartStep(testResult.uuid);
+ testResult.start = testResult.start == 0L
+ ? DateTimeOffset.Now.ToUnixTimeMilliseconds()
+ : testResult.start;
+ this.storage.PutTestCase(testResult);
return this;
}
- public virtual AllureLifecycle UpdateTestCase(string uuid, Action update)
+ public virtual AllureLifecycle UpdateTestCase(
+ string uuid,
+ Action update
+ )
{
- update.Invoke(storage.Get(uuid));
+ var testResult = this.storage.Get(uuid);
+ update(testResult);
return this;
}
- public virtual AllureLifecycle UpdateTestCase(Action update)
+ public virtual AllureLifecycle UpdateTestCase(
+ Action update
+ )
{
- return UpdateTestCase(storage.GetRootStep(), update);
+ update(this.storage.CurrentTest);
+ return this;
}
- public virtual AllureLifecycle StopTestCase(Action beforeStop)
+ public virtual AllureLifecycle StopTestCase(
+ Action beforeStop
+ )
{
- UpdateTestCase(beforeStop);
- return StopTestCase(storage.GetRootStep());
+ var testResult = this.storage.CurrentTest;
+ beforeStop(testResult);
+ stopTestCase(testResult);
+ return this;
}
- public virtual AllureLifecycle StopTestCase(string uuid)
+ public virtual AllureLifecycle StopTestCase() =>
+ this.UpdateTestCase(stopTestCase);
+
+ public virtual AllureLifecycle StopTestCase(string uuid) =>
+ this.UpdateTestCase(uuid, stopTestCase);
+
+ public virtual AllureLifecycle WriteTestCase()
{
- var testResult = storage.Get(uuid);
- testResult.stage = Stage.finished;
- testResult.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- storage.ClearStepContext();
+ this.writer.Write(
+ this.storage.RemoveTestCase()
+ );
return this;
}
public virtual AllureLifecycle WriteTestCase(string uuid)
{
- writer.Write(storage.Remove(uuid));
+ this.writer.Write(
+ this.storage.RemoveTestCase(uuid)
+ );
return this;
}
@@ -235,31 +315,40 @@ public virtual AllureLifecycle WriteTestCase(string uuid)
#region Step
- public virtual AllureLifecycle StartStep(StepResult result, out string uuid)
+ public virtual AllureLifecycle StartStep(StepResult result)
{
- uuid = Guid.NewGuid().ToString("N");
- StartStep(storage.GetCurrentStep(), uuid, result);
- return this;
- }
-
- public virtual AllureLifecycle StartStep(string uuid, StepResult result)
- {
- StartStep(storage.GetCurrentStep(), uuid, result);
+ result.stage = Stage.running;
+ result.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ this.storage.CurrentStepContainer.steps.Add(result);
+ this.storage.PutStep(result);
return this;
}
- public virtual AllureLifecycle StartStep(string parentUuid, string uuid, StepResult stepResult)
+ public virtual AllureLifecycle StartStep(StepResult result, out string uuid)
{
- stepResult.stage = Stage.running;
- stepResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- storage.StartStep(uuid);
- storage.AddStep(parentUuid, uuid, stepResult);
+ uuid = CreateUuid();
+ StartStep(this.storage.CurrentStepContainer, uuid, result);
return this;
}
+
+ public virtual AllureLifecycle StartStep(
+ string uuid,
+ StepResult result
+ ) => this.StartStep(this.storage.CurrentStepContainer, uuid, result);
+
+ public virtual AllureLifecycle StartStep(
+ string parentUuid,
+ string uuid,
+ StepResult stepResult
+ ) => this.StartStep(
+ this.storage.Get(parentUuid),
+ uuid,
+ stepResult
+ );
public virtual AllureLifecycle UpdateStep(Action update)
{
- update.Invoke(storage.Get(storage.GetCurrentStep()));
+ update.Invoke(this.storage.CurrentStep);
return this;
}
@@ -271,22 +360,23 @@ public virtual AllureLifecycle UpdateStep(string uuid, Action update
public virtual AllureLifecycle StopStep(Action beforeStop)
{
- UpdateStep(beforeStop);
- return StopStep(storage.GetCurrentStep());
+ this.UpdateStep(beforeStop);
+ return this.StopStep();
}
public virtual AllureLifecycle StopStep(string uuid)
{
- var step = storage.Remove(uuid);
+ var step = this.storage.RemoveStep(uuid);
step.stage = Stage.finished;
step.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- storage.StopStep();
return this;
}
public virtual AllureLifecycle StopStep()
{
- StopStep(storage.GetCurrentStep());
+ var step = this.storage.RemoveStep();
+ step.stage = Stage.finished;
+ step.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
return this;
}
@@ -304,7 +394,7 @@ public virtual AllureLifecycle AddAttachment(string name, string type, string pa
public virtual AllureLifecycle AddAttachment(string name, string type, byte[] content,
string fileExtension = "")
{
- var source = $"{Guid.NewGuid().ToString("N")}{AllureConstants.ATTACHMENT_FILE_SUFFIX}{fileExtension}";
+ var source = $"{CreateUuid()}{AllureConstants.ATTACHMENT_FILE_SUFFIX}{fileExtension}";
var attachment = new Attachment
{
name = name,
@@ -312,13 +402,13 @@ public virtual AllureLifecycle AddAttachment(string name, string type, byte[] co
source = source
};
writer.Write(source, content);
- storage.Get(storage.GetCurrentStep()).attachments.Add(attachment);
+ this.storage.CurrentStepContainer.attachments.Add(attachment);
return this;
}
- public virtual AllureLifecycle AddAttachment(string path, string name = null)
+ public virtual AllureLifecycle AddAttachment(string path, string? name = null)
{
- name = name ?? Path.GetFileName(path);
+ name ??= Path.GetFileName(path);
var type = MimeTypesMap.GetMimeType(path);
return AddAttachment(name, type, path);
}
@@ -348,6 +438,8 @@ public virtual AllureLifecycle AddScreenDiff(string testCaseUuid, string expecte
#region Privates
+ static AllureLifecycle Initialize() => new();
+
private static JObject GetConfiguration()
{
var jsonConfigPath = Environment.GetEnvironmentVariable(AllureConstants.ALLURE_CONFIG_ENV_VARIABLE);
@@ -368,15 +460,47 @@ private static JObject GetConfiguration()
return JObject.Parse("{}");
}
- private void StartFixture(string uuid, FixtureResult fixtureResult)
+ private void StartFixture(FixtureResult fixtureResult)
+ {
+ storage.PutFixture(fixtureResult);
+ fixtureResult.stage = Stage.running;
+ fixtureResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ }
+
+ void StartFixture(string uuid, FixtureResult fixtureResult)
{
- storage.Put(uuid, fixtureResult);
+ storage.PutFixture(uuid, fixtureResult);
fixtureResult.stage = Stage.running;
fixtureResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- storage.ClearStepContext();
- storage.StartStep(uuid);
}
+ AllureLifecycle StartStep(
+ ExecutableItem parent,
+ string uuid,
+ StepResult stepResult
+ )
+ {
+ stepResult.stage = Stage.running;
+ stepResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ parent.steps.Add(stepResult);
+ this.storage.PutStep(uuid, stepResult);
+ return this;
+ }
+
+ static readonly Action stopContainer =
+ c => c.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+
+ static readonly Action stopTestCase =
+ tr =>
+ {
+ tr.stage = Stage.finished;
+ tr.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ };
+
+
+ static string CreateUuid() =>
+ Guid.NewGuid().ToString("N");
+
#endregion
}
}
\ No newline at end of file
diff --git a/Allure.Net.Commons/Storage/AllureContext.cs b/Allure.Net.Commons/Storage/AllureContext.cs
index 984d9bff..a6b0c780 100644
--- a/Allure.Net.Commons/Storage/AllureContext.cs
+++ b/Allure.Net.Commons/Storage/AllureContext.cs
@@ -17,7 +17,7 @@ namespace Allure.Net.Commons.Storage
/// between different tests and steps that may potentially be run
/// cuncurrently either by a test framework or by an end user.
///
- internal record class AllureContext
+ public record class AllureContext
{
///
/// A stack of fixture containers affecting subsequent tests.
@@ -27,7 +27,7 @@ internal record class AllureContext
/// (including adding a fixture to or removing a fixture from the
/// current container).
///
- public IImmutableStack ContainerContext
+ internal IImmutableStack ContainerContext
{
get;
private init;
@@ -43,7 +43,7 @@ public IImmutableStack ContainerContext
/// instead of throwing it returns null if a fixture context isn't
/// active.
///
- public FixtureResult? FixtureContext { get; private init; }
+ internal FixtureResult? FixtureContext { get; private init; }
///
/// A test that is being executed.
@@ -55,7 +55,7 @@ public IImmutableStack ContainerContext
/// This property differs from in that
/// instead of throwing it returns null if a test context isn't active.
///
- public TestResult? TestContext { get; private init; }
+ internal TestResult? TestContext { get; private init; }
///
/// A stack of nested steps that are being executed.
@@ -63,7 +63,7 @@ public IImmutableStack ContainerContext
///
/// Activating this context allows operations on the current step.
///
- public IImmutableStack StepContext
+ internal IImmutableStack StepContext
{
get;
private init;
@@ -77,7 +77,7 @@ public IImmutableStack StepContext
/// context isn't active.
///
///
- public TestResultContainer CurrentContainer
+ internal TestResultContainer CurrentContainer
{
get => this.ContainerContext.FirstOrDefault()
?? throw new InvalidOperationException(
@@ -93,7 +93,7 @@ public TestResultContainer CurrentContainer
/// context isn't active.
///
///
- public FixtureResult CurrentFixture
+ internal FixtureResult CurrentFixture
{
get => this.FixtureContext ?? throw new InvalidOperationException(
"No fixture context is active."
@@ -108,7 +108,7 @@ public FixtureResult CurrentFixture
/// isn't active.
///
///
- public TestResult CurrentTest
+ internal TestResult CurrentTest
{
get => this.TestContext ?? throw new InvalidOperationException(
"No test context is active."
@@ -123,7 +123,7 @@ public TestResult CurrentTest
/// isn't active.
///
///
- public StepResult CurrentStep
+ internal StepResult CurrentStep
{
get => this.StepContext.FirstOrDefault()
?? throw new InvalidOperationException(
@@ -140,7 +140,7 @@ public StepResult CurrentStep
/// fixture, nor test, nor step context is active.
///
///
- public ExecutableItem CurrentStepContainer
+ internal ExecutableItem CurrentStepContainer
{
get => this.StepContext.FirstOrDefault() as ExecutableItem
?? this.RootStepContainer
@@ -164,7 +164,7 @@ public ExecutableItem CurrentStepContainer
/// (always active) container context.
///
///
- public AllureContext WithContainer(TestResultContainer container) =>
+ internal AllureContext WithContainer(TestResultContainer container) =>
this.ValidateContainerContextCanBeModified() with
{
ContainerContext = this.ContainerContext.Push(
@@ -188,7 +188,7 @@ public AllureContext WithContainer(TestResultContainer container) =>
/// (possibly inactive) container context.
///
///
- public AllureContext WithNoLastContainer() =>
+ internal AllureContext WithNoLastContainer() =>
this with
{
ContainerContext = this.ValidateContainerCanBeRemoved()
@@ -209,7 +209,7 @@ this with
///
///
///
- public AllureContext WithFixtureContext(FixtureResult fixtureResult) =>
+ internal AllureContext WithFixtureContext(FixtureResult fixtureResult) =>
this with
{
FixtureContext = this.ValidateNewFixtureContext(
@@ -224,7 +224,7 @@ this with
/// Creates a new with inactive fixture and
/// step contexts.
///
- public AllureContext WithNoFixtureContext() =>
+ internal AllureContext WithNoFixtureContext() =>
this with
{
FixtureContext = null,
@@ -245,7 +245,7 @@ this with
///
///
///
- public AllureContext WithTestContext(TestResult testResult) =>
+ internal AllureContext WithTestContext(TestResult testResult) =>
this with
{
TestContext = this.ValidateNewTestContext(
@@ -259,7 +259,7 @@ this with
/// Creates a new with inactive test,
/// fixture and step contexts.
///
- public AllureContext WithNoTestContext() =>
+ internal AllureContext WithNoTestContext() =>
this with
{
FixtureContext = null,
@@ -283,7 +283,7 @@ this with
///
///
///
- public AllureContext WithStep(StepResult stepResult) =>
+ internal AllureContext WithStep(StepResult stepResult) =>
this with
{
StepContext = this.StepContext.Push(
@@ -306,7 +306,7 @@ this with
/// (possibly inactive) step context.
///
///
- public AllureContext WithNoLastStep() =>
+ internal AllureContext WithNoLastStep() =>
this with
{
StepContext = this.StepContext.IsEmpty
@@ -330,7 +330,7 @@ AllureContext ValidateContainerContextCanBeModified()
{
throw new InvalidOperationException(
"Unable to change a container context because a test " +
- "context is active."
+ "context is active."
);
}
diff --git a/Allure.Net.Commons/Storage/AllureStorage.cs b/Allure.Net.Commons/Storage/AllureStorage.cs
index 2e03247d..741ca4e6 100644
--- a/Allure.Net.Commons/Storage/AllureStorage.cs
+++ b/Allure.Net.Commons/Storage/AllureStorage.cs
@@ -1,25 +1,65 @@
-using System.Collections.Concurrent;
+using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+#nullable enable
namespace Allure.Net.Commons.Storage
{
internal class AllureStorage
{
- private readonly ConcurrentDictionary> stepContext = new();
+ readonly ConcurrentDictionary storage = new();
+ readonly AsyncLocal context = new();
- private readonly ConcurrentDictionary storage = new();
+ public AllureContext CurrentContext
+ {
+ get => this.context.Value ??= new();
+ set => this.context.Value = value;
+ }
+
+ public AllureStorage()
+ {
+ this.CurrentContext = new();
+ }
+
+ public TestResultContainer? CurrentTestContainerOrNull
+ {
+ get => this.CurrentContext.ContainerContext.FirstOrDefault();
+ }
- private LinkedList Steps => stepContext.GetOrAdd(
- AllureLifecycle.CurrentTestIdGetter(),
- new LinkedList()
- );
+ public TestResultContainer CurrentTestContainer
+ {
+ get => this.CurrentContext.CurrentContainer;
+ }
+
+ public FixtureResult CurrentFixture
+ {
+ get => this.CurrentContext.CurrentFixture;
+ }
+
+ public TestResult CurrentTest
+ {
+ get => this.CurrentContext.CurrentTest;
+ }
+
+ public ExecutableItem CurrentStepContainer
+ {
+ get => this.CurrentContext.CurrentStepContainer;
+ }
+
+ public StepResult CurrentStep
+ {
+ get => this.CurrentContext.CurrentStep;
+ }
public T Get(string uuid)
{
return (T) storage[uuid];
}
- public T Put(string uuid, T item)
+ public T Put(string uuid, T item) where T: notnull
{
return (T) storage.GetOrAdd(uuid, item);
}
@@ -30,36 +70,165 @@ public T Remove(string uuid)
return (T) value;
}
- public void ClearStepContext()
+ public void PutTestContainer(TestResultContainer container) =>
+ this.PutAndUpdateContext(
+ container.uuid,
+ container,
+ c => c.WithContainer(container)
+ );
+
+ public TestResultContainer RemoveTestContainer() =>
+ this.RemoveAndUpdateContext(
+ this.CurrentTestContainer.uuid,
+ c => c.WithNoLastContainer()
+ );
+
+ public TestResultContainer RemoveTestContainer(string uuid) =>
+ this.RemoveAndUpdateContext(
+ uuid,
+ c => ContextWithNoContainer(c, uuid)
+ );
+
+ public void PutFixture(FixtureResult fixture) =>
+ this.UpdateContext(c => c.WithFixtureContext(fixture));
+
+ public void PutFixture(string uuid, FixtureResult fixture) =>
+ this.PutAndUpdateContext(
+ uuid,
+ fixture,
+ c => c.WithFixtureContext(fixture)
+ );
+
+ public FixtureResult RemoveFixture()
{
- Steps.Clear();
- stepContext.TryRemove(AllureLifecycle.CurrentTestIdGetter(), out _);
+ var fixture = this.CurrentFixture;
+ this.UpdateContext(c => c.WithNoFixtureContext());
+ return fixture;
}
- public void StartStep(string uuid)
+ public FixtureResult RemoveFixture(string uuid)
+ => this.RemoveAndUpdateContext(
+ uuid,
+ c => ReferenceEquals(
+ c.CurrentFixture,
+ this.Get(uuid)
+ ) ? c.WithNoFixtureContext() : c
+ );
+
+ public void PutTestCase(TestResult testResult) =>
+ this.PutAndUpdateContext(
+ testResult.uuid,
+ testResult,
+ c => c.WithTestContext(testResult)
+ );
+
+ public TestResult RemoveTestCase() =>
+ this.RemoveAndUpdateContext(
+ this.CurrentTest.uuid,
+ c => c.WithNoTestContext()
+ );
+
+ public TestResult RemoveTestCase(string uuid) =>
+ this.RemoveAndUpdateContext(
+ uuid,
+ c => c.CurrentTest.uuid == uuid ? c.WithNoTestContext() : c
+ );
+
+ public void PutStep(StepResult stepResult) =>
+ this.UpdateContext(
+ c => c.WithStep(stepResult)
+ );
+
+ public void PutStep(string uuid, StepResult stepResult) =>
+ this.PutAndUpdateContext(
+ uuid,
+ stepResult,
+ c => c.WithStep(stepResult)
+ );
+
+ public StepResult RemoveStep()
+ {
+ var step = this.CurrentStep;
+ this.UpdateContext(c => c.WithNoLastStep());
+ return step;
+ }
+
+ public StepResult RemoveStep(string uuid) =>
+ this.RemoveAndUpdateContext(
+ uuid,
+ c => this.ContextWithNoStep(c, uuid)
+ );
+
+ T PutAndUpdateContext(
+ string uuid,
+ T value,
+ Func updateFn
+ ) where T : notnull
{
- Steps.AddFirst(uuid);
+ var result = this.Put(uuid, value);
+ this.UpdateContext(updateFn);
+ return result;
}
- public void StopStep()
+ T RemoveAndUpdateContext(string uuid, Func updateFn)
{
- Steps.RemoveFirst();
+ this.UpdateContext(updateFn);
+ return this.Remove(uuid);
}
- public string GetRootStep()
+ void UpdateContext(Func updateFn)
{
- return Steps.Last?.Value;
+ this.CurrentContext = updateFn(this.CurrentContext);
}
- public string GetCurrentStep()
+ AllureContext ContextWithNoStep(AllureContext context, string uuid)
{
- return Steps.First?.Value;
+ var stepResult = this.Get(uuid);
+ var stepsToPushAgain = new Stack();
+ while (!ReferenceEquals(context.CurrentStep, stepResult))
+ {
+ stepsToPushAgain.Push(context.CurrentStep);
+ context = context.WithNoLastStep();
+ if (context.StepContext.IsEmpty)
+ {
+ throw new InvalidOperationException(
+ $"Step {stepResult.name} is not in the current context"
+ );
+ }
+ }
+ while (stepsToPushAgain.Any())
+ {
+ context = context.WithStep(
+ stepsToPushAgain.Pop()
+ );
+ }
+ return context;
}
- public void AddStep(string parentUuid, string uuid, StepResult stepResult)
+ static AllureContext ContextWithNoContainer(
+ AllureContext context,
+ string uuid
+ )
{
- Put(uuid, stepResult);
- Get(parentUuid).steps.Add(stepResult);
+ var containersToPushAgain = new Stack();
+ while (context.CurrentContainer.uuid != uuid)
+ {
+ containersToPushAgain.Push(context.CurrentContainer);
+ context = context.WithNoLastContainer();
+ if (context.ContainerContext.IsEmpty)
+ {
+ throw new InvalidOperationException(
+ $"Container {uuid} is not in the current context"
+ );
+ }
+ }
+ while (containersToPushAgain.Any())
+ {
+ context = context.WithContainer(
+ containersToPushAgain.Pop()
+ );
+ }
+ return context;
}
}
}
\ No newline at end of file
From ce1efb73502d9185c7a11fb5bc6a590b8317c801 Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Wed, 19 Jul 2023 10:16:45 +0700
Subject: [PATCH 05/24] Make CoreStepsHelper use AllureLifecycle's context
---
Allure.Net.Commons/AllureLifecycle.cs | 2 +-
Allure.Net.Commons/Steps/CoreStepsHelper.cs | 26 ++++++++++++++++-----
2 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/Allure.Net.Commons/AllureLifecycle.cs b/Allure.Net.Commons/AllureLifecycle.cs
index 7c7d260d..398dfe7c 100644
--- a/Allure.Net.Commons/AllureLifecycle.cs
+++ b/Allure.Net.Commons/AllureLifecycle.cs
@@ -173,7 +173,7 @@ public virtual AllureLifecycle StartBeforeFixture(string uuid, FixtureResult res
public virtual AllureLifecycle StartBeforeFixture(string parentUuid, FixtureResult result, out string uuid)
{
- uuid = Guid.NewGuid().ToString("N");
+ uuid = CreateUuid();
StartBeforeFixture(parentUuid, uuid, result);
return this;
}
diff --git a/Allure.Net.Commons/Steps/CoreStepsHelper.cs b/Allure.Net.Commons/Steps/CoreStepsHelper.cs
index 7f124ae2..0b1e14a4 100644
--- a/Allure.Net.Commons/Steps/CoreStepsHelper.cs
+++ b/Allure.Net.Commons/Steps/CoreStepsHelper.cs
@@ -16,7 +16,7 @@ public static ITestResultAccessor TestResultAccessor
get => TestResultAccessorAsyncLocal.Value;
set => TestResultAccessorAsyncLocal.Value = value;
}
-
+
#region Fixtures
public static string StartBeforeFixture(string name)
@@ -28,7 +28,11 @@ public static string StartBeforeFixture(string name)
start = DateTimeOffset.Now.ToUnixTimeMilliseconds()
};
- AllureLifecycle.Instance.StartBeforeFixture(TestResultAccessor.TestResultContainer.uuid, fixtureResult, out var uuid);
+ AllureLifecycle.Instance.StartBeforeFixture(
+ AllureLifecycle.Instance.Context.CurrentContainer.uuid,
+ fixtureResult,
+ out var uuid
+ );
StepLogger?.BeforeStarted?.Log(name);
return uuid;
}
@@ -42,7 +46,11 @@ public static string StartAfterFixture(string name)
start = DateTimeOffset.Now.ToUnixTimeMilliseconds()
};
- AllureLifecycle.Instance.StartAfterFixture(TestResultAccessor.TestResultContainer.uuid, fixtureResult, out var uuid);
+ AllureLifecycle.Instance.StartAfterFixture(
+ AllureLifecycle.Instance.Context.CurrentContainer.uuid,
+ fixtureResult,
+ out var uuid
+ );
StepLogger?.AfterStarted?.Log(name);
return uuid;
}
@@ -59,9 +67,12 @@ public static void StopFixture(Action updateResults = null)
public static void StopFixtureSuppressTestCase(Action updateResults = null)
{
- var newTestResult = TestResultAccessor.TestResult;
+ var newTestResult = AllureLifecycle.Instance.Context.CurrentTest;
StopFixture(updateResults);
- AllureLifecycle.Instance.StartTestCase(TestResultAccessor.TestResultContainer.uuid, newTestResult);
+ AllureLifecycle.Instance.StartTestCase(
+ AllureLifecycle.Instance.Context.CurrentContainer.uuid,
+ newTestResult
+ );
}
#endregion
@@ -152,7 +163,10 @@ public static void BrokeStep(string uuid, Action updateResults = nul
public static void UpdateTestResult(Action update)
{
- AllureLifecycle.Instance.UpdateTestCase(TestResultAccessor.TestResult.uuid, update);
+ AllureLifecycle.Instance.UpdateTestCase(
+ AllureLifecycle.Instance.Context.CurrentTest.uuid,
+ update
+ );
}
#endregion
From a08046dc16ee98c4957b6c1d4a25f980a22b880a Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Wed, 19 Jul 2023 13:37:19 +0700
Subject: [PATCH 06/24] Make lifecycle thread safe. Fix multithreading issues
in lifecycle tests
---
.../AllureLifeCycleTest.cs | 38 ++--
Allure.Net.Commons.Tests/ConcurrencyTests.cs | 47 ++++-
.../InMemoryResultsWriter.cs | 25 ++-
Allure.Net.Commons/AllureLifecycle.cs | 187 +++++++++++++-----
Allure.Net.Commons/Storage/AllureStorage.cs | 122 ++++++------
5 files changed, 271 insertions(+), 148 deletions(-)
diff --git a/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs b/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
index 085268f5..05c4a461 100644
--- a/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
+++ b/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
@@ -56,33 +56,33 @@ public void IntegrationTest()
cycle
.StartTestContainer(container)
- .StartBeforeFixture(container.uuid, beforeFeature.uuid, beforeFeature.fixture)
+ .StartBeforeFixture(beforeFeature.fixture)
- .StartStep(fixtureStep.uuid, fixtureStep.step)
+ .StartStep(fixtureStep.step)
.StopStep(x => x.status = Status.passed)
.AddAttachment("text file", "text/xml", txtAttach.path)
.AddAttachment(txtAttach.path)
- .UpdateFixture(beforeFeature.uuid, f => f.status = Status.passed)
- .StopFixture(beforeFeature.uuid)
+ .UpdateFixture(f => f.status = Status.passed)
+ .StopFixture()
- .StartBeforeFixture(container.uuid, beforeScenario.uuid, beforeScenario.fixture)
- .UpdateFixture(beforeScenario.uuid, f => f.status = Status.passed)
- .StopFixture(beforeScenario.uuid)
+ .StartBeforeFixture(beforeScenario.fixture)
+ .UpdateFixture(f => f.status = Status.passed)
+ .StopFixture()
- .StartTestCase(container.uuid, test)
+ .StartTestCase(test)
- .StartStep(step1.uuid, step1.step)
+ .StartStep(step1.step)
.StopStep(x => x.status = Status.passed)
- .StartStep(step2.uuid, step2.step)
+ .StartStep(step2.step)
.AddAttachment("unknown file", "text/xml", txtAttachWithNoExt.content)
.StopStep(x => x.status = Status.broken)
- .StartStep(step3.uuid, step3.step)
+ .StartStep(step3.step)
.StopStep(x => x.status = Status.skipped)
- .AddScreenDiff(test.uuid, "expected.png", "actual.png", "diff.png")
+ .AddScreenDiff("expected.png", "actual.png", "diff.png")
.StopTestCase(x =>
{
@@ -97,16 +97,16 @@ public void IntegrationTest()
};
})
- .StartAfterFixture(container.uuid, afterScenario.uuid, afterScenario.fixture)
- .UpdateFixture(afterScenario.uuid, f => f.status = Status.passed)
- .StopFixture(afterScenario.uuid)
+ .StartAfterFixture(afterScenario.fixture)
+ .UpdateFixture(f => f.status = Status.passed)
+ .StopFixture()
- .StartAfterFixture(container.uuid, afterFeature.uuid, afterFeature.fixture)
+ .StartAfterFixture(afterFeature.fixture)
.StopFixture(f => f.status = Status.passed)
- .WriteTestCase(test.uuid)
- .StopTestContainer(container.uuid)
- .WriteTestContainer(container.uuid);
+ .WriteTestCase()
+ .StopTestContainer()
+ .WriteTestContainer();
});
}
diff --git a/Allure.Net.Commons.Tests/ConcurrencyTests.cs b/Allure.Net.Commons.Tests/ConcurrencyTests.cs
index 0809edd2..f014eb20 100644
--- a/Allure.Net.Commons.Tests/ConcurrencyTests.cs
+++ b/Allure.Net.Commons.Tests/ConcurrencyTests.cs
@@ -13,6 +13,7 @@ internal class ConcurrencyTests
{
InMemoryResultsWriter writer;
AllureLifecycle lifecycle;
+ int writes = 0;
[SetUp]
public void SetUp()
@@ -83,17 +84,21 @@ public void ContextCapturedBySubThreads()
* - inner-2-1 | Child thread 2
* - inner-2-2 | Child thread 2
*/
+ var sync = new ManualResetEventSlim();
+
this.WrapInTest(
"test",
() => this.WrapInStep(
"outer",
() => RunThreads(
- () => this.AddSteps(
- ("inner-1", new object[] { "inner-1-1", "inner-1-2" })
- ),
- () => this.AddSteps(
- ("inner-2", new object[] { "inner-2-1", "inner-2-2" })
- )
+ BindEventSet(() => this.AddSteps((
+ "inner-1",
+ new object[] { "inner-1-1", "inner-1-2" }
+ )), sync),
+ BindEventWait (() => this.AddSteps((
+ "inner-2",
+ new object[] { "inner-2-1", "inner-2-2" }
+ )), sync)
)
)
);
@@ -146,16 +151,38 @@ await this.WrapInTestAsync(
);
}
+ static Action BindEventSet(Action fn, ManualResetEventSlim @event) => () =>
+ {
+ try
+ {
+ fn();
+ }
+ finally
+ {
+ @event.Set();
+ }
+ };
+
+ static Action BindEventWait(Action fn, ManualResetEventSlim @event) => () =>
+ {
+ @event.Wait();
+ fn();
+ };
+
async Task AddTestWithStepsAsync(string name, params object[] steps)
{
- var uuid = Guid.NewGuid().ToString();
this.lifecycle
- .StartTestCase(new() { name = name, uuid = uuid });
+ .StartTestCase(new()
+ {
+ name = name,
+ uuid = Guid.NewGuid().ToString()
+ });
await Task.Delay(1);
await this.AddStepsAsync(steps);
this.lifecycle
- .StopTestCase(uuid)
- .WriteTestCase(uuid);
+ .StopTestCase()
+ .WriteTestCase();
+ writes++;
}
void WrapInTest(string testName, Action action)
diff --git a/Allure.Net.Commons.Tests/InMemoryResultsWriter.cs b/Allure.Net.Commons.Tests/InMemoryResultsWriter.cs
index ab619d1e..29cd9223 100644
--- a/Allure.Net.Commons.Tests/InMemoryResultsWriter.cs
+++ b/Allure.Net.Commons.Tests/InMemoryResultsWriter.cs
@@ -5,30 +5,43 @@ namespace Allure.Net.Commons.Tests
{
class InMemoryResultsWriter : IAllureResultsWriter
{
+ readonly object monitor = new();
internal List testResults = new();
internal List testContainers = new();
internal List<(string Source, byte[] Content)> attachments = new();
public void CleanUp()
{
- this.testResults.Clear();
- this.testContainers.Clear();
- this.attachments.Clear();
+ lock (this.monitor)
+ {
+ this.testResults.Clear();
+ this.testContainers.Clear();
+ this.attachments.Clear();
+ }
}
public void Write(TestResult testResult)
{
- this.testResults.Add(testResult);
+ lock (this.monitor)
+ {
+ this.testResults.Add(testResult);
+ }
}
public void Write(TestResultContainer testResult)
{
- this.testContainers.Add(testResult);
+ lock (this.monitor)
+ {
+ this.testContainers.Add(testResult);
+ }
}
public void Write(string source, byte[] attachment)
{
- this.attachments.Add((source, attachment));
+ lock (this.monitor)
+ {
+ this.attachments.Add((source, attachment));
+ }
}
}
}
diff --git a/Allure.Net.Commons/AllureLifecycle.cs b/Allure.Net.Commons/AllureLifecycle.cs
index 398dfe7c..8c913cdc 100644
--- a/Allure.Net.Commons/AllureLifecycle.cs
+++ b/Allure.Net.Commons/AllureLifecycle.cs
@@ -28,11 +28,18 @@ public class AllureLifecycle
private readonly AllureStorage storage;
private readonly IAllureResultsWriter writer;
+ ///
+ /// Protects mutations of shared allure model objects against data
+ /// races that may otherwise occur because of multithreaded access to
+ /// the AllureLifecycle's singleton.
+ ///
+ readonly object monitor = new();
+
///
/// Gets or sets an execution context of Allure. Use this property if
- /// the context is set not in the same async domain where a
- /// test/fixture function is executed.
+ /// the context is set not in the same async domain where it should
+ /// later be accessed.
///
///
/// This property is intended to be used by Allure integrations with
@@ -92,9 +99,14 @@ private void AddTypeFormatterImpl(Type type, ITypeFormatter formatter) =>
public virtual AllureLifecycle StartTestContainer(TestResultContainer container)
{
container.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- this.storage.CurrentTestContainerOrNull?.children.Add(
- container.uuid
- );
+ var parent = this.storage.CurrentTestContainerOrNull;
+ if (parent is not null)
+ {
+ lock (this.monitor)
+ {
+ parent.children.Add(container.uuid);
+ }
+ }
storage.PutTestContainer(container);
return this;
}
@@ -108,13 +120,21 @@ public virtual AllureLifecycle StartTestContainer(string parentUuid, TestResultC
public virtual AllureLifecycle UpdateTestContainer(Action update)
{
- update.Invoke(storage.CurrentTestContainer);
+ var container = this.storage.CurrentTestContainer;
+ lock (this.monitor)
+ {
+ update.Invoke(container);
+ }
return this;
}
public virtual AllureLifecycle UpdateTestContainer(string uuid, Action update)
{
- update.Invoke(storage.Get(uuid));
+ var container = this.storage.Get(uuid);
+ lock (this.monitor)
+ {
+ update.Invoke(container);
+ }
return this;
}
@@ -132,17 +152,17 @@ public virtual AllureLifecycle StopTestContainer(string uuid)
public virtual AllureLifecycle WriteTestContainer()
{
- writer.Write(
- storage.RemoveTestContainer()
- );
+ var container = this.storage.CurrentTestContainer;
+ this.storage.RemoveTestContainer();
+ this.writer.Write(container);
return this;
}
public virtual AllureLifecycle WriteTestContainer(string uuid)
{
- writer.Write(
- storage.RemoveTestContainer(uuid)
- );
+ var container = this.storage.Get(uuid);
+ this.storage.RemoveTestContainer(uuid);
+ this.writer.Write(container);
return this;
}
@@ -208,35 +228,49 @@ public virtual AllureLifecycle StartAfterFixture(string parentUuid, string uuid,
public virtual AllureLifecycle UpdateFixture(Action update)
{
- update?.Invoke(storage.CurrentFixture);
+ var fixture = this.storage.CurrentFixture;
+ lock (this.monitor)
+ {
+ update.Invoke(fixture);
+ }
return this;
}
public virtual AllureLifecycle UpdateFixture(string uuid, Action update)
{
- update.Invoke(storage.Get(uuid));
+ var fixture = this.storage.Get(uuid);
+ lock (this.monitor)
+ {
+ update.Invoke(fixture);
+ }
return this;
}
public virtual AllureLifecycle StopFixture(Action beforeStop)
{
- UpdateFixture(beforeStop);
+ this.UpdateFixture(beforeStop);
return this.StopFixture();
}
public virtual AllureLifecycle StopFixture()
{
- var fixture = this.storage.RemoveFixture();
- fixture.stage = Stage.finished;
- fixture.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ this.UpdateFixture(fixture =>
+ {
+ fixture.stage = Stage.finished;
+ fixture.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ });
+ this.storage.RemoveFixture();
return this;
}
public virtual AllureLifecycle StopFixture(string uuid)
{
- var fixture = this.storage.RemoveFixture(uuid);
- fixture.stage = Stage.finished;
- fixture.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ this.UpdateFixture(fixture =>
+ {
+ fixture.stage = Stage.finished;
+ fixture.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ });
+ this.storage.RemoveFixture(uuid);
return this;
}
@@ -252,7 +286,14 @@ public virtual AllureLifecycle StartTestCase(string containerUuid, TestResult te
public virtual AllureLifecycle StartTestCase(TestResult testResult)
{
- this.storage.CurrentTestContainerOrNull?.children.Add(testResult.uuid);
+ var container = this.storage.CurrentTestContainerOrNull;
+ if (container is not null)
+ {
+ lock (this.monitor)
+ {
+ container.children.Add(testResult.uuid);
+ }
+ }
testResult.stage = Stage.running;
testResult.start = testResult.start == 0L
? DateTimeOffset.Now.ToUnixTimeMilliseconds()
@@ -267,7 +308,10 @@ Action update
)
{
var testResult = this.storage.Get(uuid);
- update(testResult);
+ lock (this.monitor)
+ {
+ update(testResult);
+ }
return this;
}
@@ -275,19 +319,21 @@ public virtual AllureLifecycle UpdateTestCase(
Action update
)
{
- update(this.storage.CurrentTest);
+ var testResult = this.storage.CurrentTest;
+ lock (this.monitor)
+ {
+ update(testResult);
+ }
return this;
}
public virtual AllureLifecycle StopTestCase(
Action beforeStop
- )
+ ) => this.UpdateTestCase(testResult =>
{
- var testResult = this.storage.CurrentTest;
beforeStop(testResult);
stopTestCase(testResult);
- return this;
- }
+ });
public virtual AllureLifecycle StopTestCase() =>
this.UpdateTestCase(stopTestCase);
@@ -297,17 +343,17 @@ public virtual AllureLifecycle StopTestCase(string uuid) =>
public virtual AllureLifecycle WriteTestCase()
{
- this.writer.Write(
- this.storage.RemoveTestCase()
- );
+ var testResult = this.storage.CurrentTest;
+ this.storage.RemoveTestCase();
+ this.writer.Write(testResult);
return this;
}
public virtual AllureLifecycle WriteTestCase(string uuid)
{
- this.writer.Write(
- this.storage.RemoveTestCase(uuid)
- );
+ var testResult = this.storage.Get(uuid);
+ this.storage.RemoveTestCase(uuid);
+ this.writer.Write(testResult);
return this;
}
@@ -319,7 +365,11 @@ public virtual AllureLifecycle StartStep(StepResult result)
{
result.stage = Stage.running;
result.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- this.storage.CurrentStepContainer.steps.Add(result);
+ var parent = this.storage.CurrentStepContainer;
+ lock (this.monitor)
+ {
+ parent.steps.Add(result);
+ }
this.storage.PutStep(result);
return this;
}
@@ -348,13 +398,21 @@ StepResult stepResult
public virtual AllureLifecycle UpdateStep(Action update)
{
- update.Invoke(this.storage.CurrentStep);
+ var stepResult = this.storage.CurrentStep;
+ lock (this.monitor)
+ {
+ update.Invoke(stepResult);
+ }
return this;
}
public virtual AllureLifecycle UpdateStep(string uuid, Action update)
{
- update.Invoke(storage.Get(uuid));
+ var stepResult = storage.Get(uuid);
+ lock (this.monitor)
+ {
+ update.Invoke(stepResult);
+ }
return this;
}
@@ -366,17 +424,23 @@ public virtual AllureLifecycle StopStep(Action beforeStop)
public virtual AllureLifecycle StopStep(string uuid)
{
- var step = this.storage.RemoveStep(uuid);
- step.stage = Stage.finished;
- step.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ this.UpdateStep(uuid, step =>
+ {
+ step.stage = Stage.finished;
+ step.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ });
+ this.storage.RemoveStep(uuid);
return this;
}
public virtual AllureLifecycle StopStep()
{
- var step = this.storage.RemoveStep();
- step.stage = Stage.finished;
- step.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ this.UpdateStep(step =>
+ {
+ step.stage = Stage.finished;
+ step.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ });
+ this.storage.RemoveStep();
return this;
}
@@ -401,8 +465,12 @@ public virtual AllureLifecycle AddAttachment(string name, string type, byte[] co
type = type,
source = source
};
- writer.Write(source, content);
- this.storage.CurrentStepContainer.attachments.Add(attachment);
+ this.writer.Write(source, content);
+ var target = this.storage.CurrentStepContainer;
+ lock (this.monitor)
+ {
+ target.attachments.Add(attachment);
+ }
return this;
}
@@ -433,6 +501,17 @@ public virtual AllureLifecycle AddScreenDiff(string testCaseUuid, string expecte
return this;
}
+ public virtual AllureLifecycle AddScreenDiff(
+ string expectedPng,
+ string actualPng,
+ string diffPng
+ ) => this.AddAttachment(expectedPng, "expected")
+ .AddAttachment(actualPng, "actual")
+ .AddAttachment(diffPng, "diff")
+ .UpdateTestCase(
+ x => x.labels.Add(Label.TestType("screenshotDiff"))
+ );
+
#endregion
@@ -462,16 +541,19 @@ private static JObject GetConfiguration()
private void StartFixture(FixtureResult fixtureResult)
{
- storage.PutFixture(fixtureResult);
+ this.storage.PutFixture(fixtureResult);
fixtureResult.stage = Stage.running;
fixtureResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
}
void StartFixture(string uuid, FixtureResult fixtureResult)
{
- storage.PutFixture(uuid, fixtureResult);
- fixtureResult.stage = Stage.running;
- fixtureResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ this.storage.PutFixture(uuid, fixtureResult);
+ lock (this.monitor)
+ {
+ fixtureResult.stage = Stage.running;
+ fixtureResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ }
}
AllureLifecycle StartStep(
@@ -482,7 +564,10 @@ StepResult stepResult
{
stepResult.stage = Stage.running;
stepResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- parent.steps.Add(stepResult);
+ lock (this.monitor)
+ {
+ parent.steps.Add(stepResult);
+ }
this.storage.PutStep(uuid, stepResult);
return this;
}
diff --git a/Allure.Net.Commons/Storage/AllureStorage.cs b/Allure.Net.Commons/Storage/AllureStorage.cs
index 741ca4e6..a0db2f80 100644
--- a/Allure.Net.Commons/Storage/AllureStorage.cs
+++ b/Allure.Net.Commons/Storage/AllureStorage.cs
@@ -71,19 +71,24 @@ public T Remove(string uuid)
}
public void PutTestContainer(TestResultContainer container) =>
- this.PutAndUpdateContext(
- container.uuid,
- container,
+ this.UpdateContext(
c => c.WithContainer(container)
);
- public TestResultContainer RemoveTestContainer() =>
- this.RemoveAndUpdateContext(
- this.CurrentTestContainer.uuid,
- c => c.WithNoLastContainer()
- );
-
- public TestResultContainer RemoveTestContainer(string uuid) =>
+ public void PutTestContainer(
+ string uuid,
+ TestResultContainer container
+ ) => this.PutAndUpdateContext(
+ uuid,
+ container,
+ c => c.WithContainer(container)
+ );
+
+ public void RemoveTestContainer() => this.UpdateContext(
+ c => c.WithNoLastContainer()
+ );
+
+ public void RemoveTestContainer(string uuid) =>
this.RemoveAndUpdateContext(
uuid,
c => ContextWithNoContainer(c, uuid)
@@ -99,39 +104,36 @@ public void PutFixture(string uuid, FixtureResult fixture) =>
c => c.WithFixtureContext(fixture)
);
- public FixtureResult RemoveFixture()
- {
- var fixture = this.CurrentFixture;
+ public void RemoveFixture() =>
this.UpdateContext(c => c.WithNoFixtureContext());
- return fixture;
- }
- public FixtureResult RemoveFixture(string uuid)
+ public void RemoveFixture(string uuid)
=> this.RemoveAndUpdateContext(
uuid,
- c => ReferenceEquals(
- c.CurrentFixture,
- this.Get(uuid)
- ) ? c.WithNoFixtureContext() : c
+ c => c.WithNoFixtureContext()
);
public void PutTestCase(TestResult testResult) =>
+ this.UpdateContext(
+ c => c.WithTestContext(testResult)
+ );
+
+ public void PutTestCase(string uuid, TestResult testResult) =>
this.PutAndUpdateContext(
- testResult.uuid,
+ uuid,
testResult,
c => c.WithTestContext(testResult)
);
- public TestResult RemoveTestCase() =>
- this.RemoveAndUpdateContext(
- this.CurrentTest.uuid,
+ public void RemoveTestCase() =>
+ this.UpdateContext(
c => c.WithNoTestContext()
);
- public TestResult RemoveTestCase(string uuid) =>
+ public void RemoveTestCase(string uuid) =>
this.RemoveAndUpdateContext(
uuid,
- c => c.CurrentTest.uuid == uuid ? c.WithNoTestContext() : c
+ c => c.WithNoTestContext()
);
public void PutStep(StepResult stepResult) =>
@@ -146,34 +148,30 @@ public void PutStep(string uuid, StepResult stepResult) =>
c => c.WithStep(stepResult)
);
- public StepResult RemoveStep()
- {
- var step = this.CurrentStep;
- this.UpdateContext(c => c.WithNoLastStep());
- return step;
- }
+ public void RemoveStep() => this.UpdateContext(
+ c => c.WithNoLastStep()
+ );
- public StepResult RemoveStep(string uuid) =>
+ public void RemoveStep(string uuid) =>
this.RemoveAndUpdateContext(
uuid,
c => this.ContextWithNoStep(c, uuid)
);
- T PutAndUpdateContext(
+ void PutAndUpdateContext(
string uuid,
T value,
Func updateFn
) where T : notnull
{
- var result = this.Put(uuid, value);
+ this.Put(uuid, value);
this.UpdateContext(updateFn);
- return result;
}
- T RemoveAndUpdateContext(string uuid, Func updateFn)
+ void RemoveAndUpdateContext(string uuid, Func updateFn)
{
this.UpdateContext(updateFn);
- return this.Remove(uuid);
+ this.Remove(uuid);
}
void UpdateContext(Func updateFn)
@@ -181,30 +179,6 @@ void UpdateContext(Func updateFn)
this.CurrentContext = updateFn(this.CurrentContext);
}
- AllureContext ContextWithNoStep(AllureContext context, string uuid)
- {
- var stepResult = this.Get(uuid);
- var stepsToPushAgain = new Stack();
- while (!ReferenceEquals(context.CurrentStep, stepResult))
- {
- stepsToPushAgain.Push(context.CurrentStep);
- context = context.WithNoLastStep();
- if (context.StepContext.IsEmpty)
- {
- throw new InvalidOperationException(
- $"Step {stepResult.name} is not in the current context"
- );
- }
- }
- while (stepsToPushAgain.Any())
- {
- context = context.WithStep(
- stepsToPushAgain.Pop()
- );
- }
- return context;
- }
-
static AllureContext ContextWithNoContainer(
AllureContext context,
string uuid
@@ -230,5 +204,29 @@ string uuid
}
return context;
}
+
+ AllureContext ContextWithNoStep(AllureContext context, string uuid)
+ {
+ var stepResult = this.Get(uuid);
+ var stepsToPushAgain = new Stack();
+ while (!ReferenceEquals(context.CurrentStep, stepResult))
+ {
+ stepsToPushAgain.Push(context.CurrentStep);
+ context = context.WithNoLastStep();
+ if (context.StepContext.IsEmpty)
+ {
+ throw new InvalidOperationException(
+ $"Step {stepResult.name} is not in the current context"
+ );
+ }
+ }
+ while (stepsToPushAgain.Any())
+ {
+ context = context.WithStep(
+ stepsToPushAgain.Pop()
+ );
+ }
+ return context;
+ }
}
}
\ No newline at end of file
From 7ffe0bc8e725d0c3fad1a743a5d5ff9b3a409976 Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Wed, 19 Jul 2023 18:16:55 +0700
Subject: [PATCH 07/24] Several improvements on context. Allure-xunit migration
- Add bool props to test context to context's public API
- Fix context string conversion
- Migrate allure-xunit's code to the new context implementation
---
.../AllureContextTests.cs | 67 +++++++
.../AllureLifeCycleTest.cs | 30 ++-
Allure.Net.Commons/AllureLifecycle.cs | 46 ++++-
Allure.Net.Commons/Steps/AllureStepAspect.cs | 105 ++++------
Allure.Net.Commons/Steps/CoreStepsHelper.cs | 147 +++++---------
Allure.Net.Commons/Storage/AllureContext.cs | 89 ++++++---
Allure.Net.Commons/Storage/AllureStorage.cs | 45 ++---
Allure.XUnit/AllureAfter.cs | 13 +-
Allure.XUnit/AllureBefore.cs | 13 +-
Allure.XUnit/AllureMessageSink.cs | 183 +++++++++++-------
Allure.XUnit/AllureStep.cs | 13 +-
Allure.XUnit/AllureStepBase.cs | 30 +--
Allure.XUnit/AllureXunitHelper.cs | 127 ++++++------
Allure.XUnit/AllureXunitTestData.cs | 10 +
Allure.XUnit/AllureXunitTestResultAccessor.cs | 14 --
15 files changed, 498 insertions(+), 434 deletions(-)
create mode 100644 Allure.XUnit/AllureXunitTestData.cs
delete mode 100644 Allure.XUnit/AllureXunitTestResultAccessor.cs
diff --git a/Allure.Net.Commons.Tests/AllureContextTests.cs b/Allure.Net.Commons.Tests/AllureContextTests.cs
index f0087344..668668c5 100644
--- a/Allure.Net.Commons.Tests/AllureContextTests.cs
+++ b/Allure.Net.Commons.Tests/AllureContextTests.cs
@@ -14,6 +14,10 @@ public void TestEmptyContext()
Assert.That(ctx.FixtureContext, Is.Null);
Assert.That(ctx.TestContext, Is.Null);
Assert.That(ctx.StepContext, Is.Empty);
+ Assert.That(ctx.HasContainer, Is.False);
+ Assert.That(ctx.HasFixture, Is.False);
+ Assert.That(ctx.HasTest, Is.False);
+ Assert.That(ctx.HasStep, Is.False);
Assert.That(
() => ctx.CurrentContainer,
@@ -54,6 +58,7 @@ public void TestContextOnly()
var ctx = new AllureContext().WithTestContext(test);
+ Assert.That(ctx.HasTest, Is.True);
Assert.That(ctx.TestContext, Is.SameAs(test));
Assert.That(ctx.CurrentTest, Is.SameAs(test));
Assert.That(ctx.CurrentStepContainer, Is.SameAs(test));
@@ -115,6 +120,7 @@ public void TestContextCanBeRemoved()
.WithTestContext(test)
.WithNoTestContext();
+ Assert.That(ctx.HasTest, Is.False);
Assert.That(ctx.TestContext, Is.Null);
Assert.That(
() => ctx.CurrentStepContainer,
@@ -140,6 +146,7 @@ public void OneContainerInContainerContext()
var ctx = new AllureContext().WithContainer(container);
+ Assert.That(ctx.HasContainer, Is.True);
Assert.That(ctx.ContainerContext, Is.EqualTo(new[] { container }));
Assert.That(ctx.CurrentContainer, Is.SameAs(container));
}
@@ -182,6 +189,7 @@ public void LatestContainerCanBeRemoved()
.WithContainer(new())
.WithNoLastContainer();
+ Assert.That(ctx.HasContainer, Is.False);
Assert.That(ctx.ContainerContext, Is.Empty);
}
@@ -235,6 +243,7 @@ public void FixtureContextIsSet()
.WithContainer(new())
.WithFixtureContext(fixture);
+ Assert.That(ctx.HasFixture, Is.True);
Assert.That(ctx.FixtureContext, Is.SameAs(fixture));
Assert.That(ctx.CurrentFixture, Is.SameAs(fixture));
Assert.That(ctx.CurrentStepContainer, Is.SameAs(fixture));
@@ -330,6 +339,7 @@ public void ClearingTestContextClearsFixtureContext()
.WithFixtureContext(new())
.WithNoTestContext();
+ Assert.That(ctx.HasFixture, Is.False);
Assert.That(ctx.FixtureContext, Is.Null);
Assert.That(
() => ctx.CurrentStepContainer,
@@ -360,6 +370,7 @@ public void FixtureContextCanBeCleared()
.WithFixtureContext(fixture)
.WithNoFixtureContext();
+ Assert.That(ctx.HasFixture, Is.False);
Assert.That(ctx.FixtureContext, Is.Null);
}
@@ -411,6 +422,7 @@ public void StepCanBeAddedIfFixtureExists()
.WithFixtureContext(new())
.WithStep(step);
+ Assert.That(ctx.HasStep, Is.True);
Assert.That(ctx.StepContext, Is.EqualTo(new[] { step }));
Assert.That(ctx.CurrentStepContainer, Is.SameAs(step));
}
@@ -423,6 +435,7 @@ public void StepCanBeAddedIfTestExists()
.WithTestContext(new())
.WithStep(step);
+ Assert.That(ctx.HasStep, Is.True);
Assert.That(ctx.StepContext, Is.EqualTo(new[] { step }));
Assert.That(ctx.CurrentStep, Is.SameAs(step));
Assert.That(ctx.CurrentStepContainer, Is.SameAs(step));
@@ -468,6 +481,7 @@ public void RemovingTheOnlyStepRestoresTestAsStepContainer()
.WithStep(new())
.WithNoLastStep();
+ Assert.That(ctx.HasStep, Is.False);
Assert.That(ctx.StepContext, Is.Empty);
Assert.That(ctx.CurrentStepContainer, Is.SameAs(test));
}
@@ -495,6 +509,7 @@ public void RemovingFixtureClearsStepContext()
.WithStep(new())
.WithNoFixtureContext();
+ Assert.That(ctx.HasStep, Is.False);
Assert.That(ctx.StepContext, Is.Empty);
}
@@ -506,6 +521,7 @@ public void RemovingTestClearsStepContext()
.WithStep(new())
.WithNoTestContext();
+ Assert.That(ctx.HasStep, Is.False);
Assert.That(ctx.StepContext, Is.Empty);
}
@@ -521,7 +537,58 @@ public void FixtureAfterTestClearsStepContext()
.WithStep(new())
.WithFixtureContext(new());
+ Assert.That(ctx.HasStep, Is.False);
Assert.That(ctx.StepContext, Is.Empty);
}
+
+ [Test]
+ public void ContextToString()
+ {
+ Assert.That(
+ new AllureContext().ToString(),
+ Is.EqualTo("AllureContext { Containers = [], Fixture = null, Test = null, Steps = [] }")
+ );
+ Assert.That(
+ new AllureContext()
+ .WithContainer(new() { name = "c" })
+ .ToString(),
+ Is.EqualTo("AllureContext { Containers = [c], Fixture = null, Test = null, Steps = [] }")
+ );
+ Assert.That(
+ new AllureContext()
+ .WithContainer(new() { name = "c1" })
+ .WithContainer(new() { name = "c2" })
+ .ToString(),
+ Is.EqualTo("AllureContext { Containers = [c2 <- c1], Fixture = null, Test = null, Steps = [] }")
+ );
+ Assert.That(
+ new AllureContext()
+ .WithTestContext(new() { name = "t" })
+ .ToString(),
+ Is.EqualTo("AllureContext { Containers = [], Fixture = null, Test = t, Steps = [] }")
+ );
+ Assert.That(
+ new AllureContext()
+ .WithContainer(new() { name = "c" })
+ .WithFixtureContext(new() { name = "f" })
+ .ToString(),
+ Is.EqualTo("AllureContext { Containers = [c], Fixture = f, Test = null, Steps = [] }")
+ );
+ Assert.That(
+ new AllureContext()
+ .WithTestContext(new() { name = "t" })
+ .WithStep(new() { name = "s" })
+ .ToString(),
+ Is.EqualTo("AllureContext { Containers = [], Fixture = null, Test = t, Steps = [s] }")
+ );
+ Assert.That(
+ new AllureContext()
+ .WithTestContext(new() { name = "t" })
+ .WithStep(new() { name = "s1" })
+ .WithStep(new() { name = "s2" })
+ .ToString(),
+ Is.EqualTo("AllureContext { Containers = [], Fixture = null, Test = t, Steps = [s2 <- s1] }")
+ );
+ }
}
}
diff --git a/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs b/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
index 05c4a461..c0394bad 100644
--- a/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
+++ b/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
@@ -151,7 +151,7 @@ public void BeforeFixtureMayOverlapsWithTest()
}
[Test]
- public async Task AllureContextCouldBeAssigned()
+ public async Task ContextCapturingTest()
{
var writer = new InMemoryResultsWriter();
var lifecycle = new AllureLifecycle(_ => writer);
@@ -164,12 +164,32 @@ await Task.Factory.StartNew(() =>
});
context = lifecycle.Context;
});
- lifecycle.Context = context;
-
- lifecycle.StopTestCase();
- lifecycle.WriteTestCase();
+ lifecycle.RunInContext(context, () =>
+ {
+ lifecycle.StopTestCase();
+ lifecycle.WriteTestCase();
+ });
Assert.That(writer.testResults, Is.Not.Empty);
}
+
+ [Test]
+ public async Task ContextCapturingHasNoEffectIfContextIsNull()
+ {
+ var writer = new InMemoryResultsWriter();
+ var lifecycle = new AllureLifecycle(_ => writer);
+ await Task.Factory.StartNew(() =>
+ {
+ lifecycle.StartTestCase(new()
+ {
+ uuid = Guid.NewGuid().ToString()
+ });
+ });
+
+ Assert.That(() => lifecycle.RunInContext(null, () =>
+ {
+ lifecycle.StopTestCase();
+ }), Throws.InvalidOperationException);
+ }
}
}
diff --git a/Allure.Net.Commons/AllureLifecycle.cs b/Allure.Net.Commons/AllureLifecycle.cs
index 8c913cdc..da1ea844 100644
--- a/Allure.Net.Commons/AllureLifecycle.cs
+++ b/Allure.Net.Commons/AllureLifecycle.cs
@@ -30,16 +30,13 @@ public class AllureLifecycle
///
/// Protects mutations of shared allure model objects against data
- /// races that may otherwise occur because of multithreaded access to
- /// the AllureLifecycle's singleton.
+ /// races that may otherwise occur because of multithreaded access.
///
readonly object monitor = new();
///
- /// Gets or sets an execution context of Allure. Use this property if
- /// the context is set not in the same async domain where it should
- /// later be accessed.
+ /// Captures the current context of Allure's execution.
///
///
/// This property is intended to be used by Allure integrations with
@@ -48,7 +45,44 @@ public class AllureLifecycle
public AllureContext Context
{
get => this.storage.CurrentContext;
- set => this.storage.CurrentContext = value;
+ private set => this.storage.CurrentContext = value;
+ }
+
+ ///
+ /// Runs the specified code in the specified context restoring it
+ /// before returning. Use this method if you need to access the context
+ /// somewhere outside the async execution context the allure context
+ /// has been set in.
+ ///
+ ///
+ /// This method is intended to be used by Allure integrations with
+ /// test frameworks, not by end user's code.
+ ///
+ ///
+ /// A context that was previously captured with .
+ ///
+ /// A code to run.
+ public void RunInContext(
+ AllureContext? context,
+ Action action
+ )
+ {
+ if (context is null || context == this.Context)
+ {
+ action(this.Context);
+ return;
+ }
+
+ var originalContext = this.Context;
+ try
+ {
+ this.Context = context;
+ action(context);
+ }
+ finally
+ {
+ this.Context = originalContext;
+ }
}
/// Method to get the key for separation the steps for different tests.
diff --git a/Allure.Net.Commons/Steps/AllureStepAspect.cs b/Allure.Net.Commons/Steps/AllureStepAspect.cs
index e5b6aaef..6faaed49 100644
--- a/Allure.Net.Commons/Steps/AllureStepAspect.cs
+++ b/Allure.Net.Commons/Steps/AllureStepAspect.cs
@@ -28,25 +28,23 @@ public abstract class AllureAbstractStepAspect
public static List ExceptionTypes { get; set; }
- private static string StartStep(MethodBase metadata, string stepName, List stepParameters)
+ private static void StartStep(MethodBase metadata, string stepName, List stepParameters)
{
if (metadata.GetCustomAttribute() != null)
{
- return CoreStepsHelper.StartStep(stepName, step => step.parameters = stepParameters);
+ CoreStepsHelper.StartStep(stepName, step => step.parameters = stepParameters);
}
-
- return null;
}
- private static void PassStep(string uuid, MethodBase metadata)
+ private static void PassStep(MethodBase metadata)
{
if (metadata.GetCustomAttribute() != null)
{
- CoreStepsHelper.PassStep(uuid);
+ CoreStepsHelper.PassStep();
}
}
- private static void ThrowStep(string uuid, MethodBase metadata, Exception e)
+ private static void ThrowStep(MethodBase metadata, Exception e)
{
if (metadata.GetCustomAttribute() != null)
{
@@ -58,25 +56,23 @@ private static void ThrowStep(string uuid, MethodBase metadata, Exception e)
if (ExceptionTypes.Any(exceptionType => exceptionType.IsInstanceOfType(e)))
{
- CoreStepsHelper.FailStep(uuid, result => result.statusDetails = exceptionStatusDetails);
+ CoreStepsHelper.FailStep(result => result.statusDetails = exceptionStatusDetails);
return;
}
- CoreStepsHelper.BrokeStep(uuid, result => result.statusDetails = exceptionStatusDetails);
+ CoreStepsHelper.BrokeStep(result => result.statusDetails = exceptionStatusDetails);
}
}
- private static void StartFixture(MethodBase metadata, string stepName)
+ private static void StartFixture(MethodBase metadata, string fixtureName)
{
if (metadata.GetCustomAttribute(inherit: true) != null)
{
- Console.Out.WriteLine("QWAQWA");
- // throw new Exception("BEFORE FIXTURE");
- CoreStepsHelper.StartBeforeFixture(stepName);
+ CoreStepsHelper.StartBeforeFixture(fixtureName);
}
if (metadata.GetCustomAttribute(inherit: true) != null)
{
- CoreStepsHelper.StartAfterFixture(stepName);
+ CoreStepsHelper.StartAfterFixture(fixtureName);
}
}
@@ -85,15 +81,8 @@ private static void PassFixture(MethodBase metadata)
if (metadata.GetCustomAttribute(inherit: true) != null ||
metadata.GetCustomAttribute(inherit: true) != null)
{
- if (metadata.Name == "InitializeAsync")
- {
- CoreStepsHelper.StopFixtureSuppressTestCase(result => result.status = Status.passed);
- }
- else
- {
- CoreStepsHelper.StopFixture(result => result.status = Status.passed);
- }
-
+ CoreStepsHelper.StopFixture(result => result.status = Status.passed);
+
// TODO: NUnit doing it this way: to be reviewed (!) DO NOT MERGE
// CoreStepsHelper.StopFixtureSuppressTestCase(result => result.status = Status.passed);
}
@@ -110,47 +99,33 @@ private static void ThrowFixture(MethodBase metadata, Exception e)
trace = e.StackTrace
};
- if (metadata.Name == "InitializeAsync")
+ CoreStepsHelper.StopFixture(result =>
{
- CoreStepsHelper.StopFixtureSuppressTestCase(result =>
- {
- result.status = ExceptionTypes.Any(exceptionType => exceptionType.IsInstanceOfType(e))
- ? Status.failed
- : Status.broken;
- result.statusDetails = exceptionStatusDetails;
- });
- }
- else
- {
- CoreStepsHelper.StopFixture(result =>
- {
- result.status = ExceptionTypes.Any(exceptionType => exceptionType.IsInstanceOfType(e))
- ? Status.failed
- : Status.broken;
- result.statusDetails = exceptionStatusDetails;
- });
- }
+ result.status = ExceptionTypes.Any(exceptionType => exceptionType.IsInstanceOfType(e))
+ ? Status.failed
+ : Status.broken;
+ result.statusDetails = exceptionStatusDetails;
+ });
}
}
// ------------------------------
- private static string BeforeTargetInvoke(MethodBase metadata, string stepName, List stepParameters)
+ private static void BeforeTargetInvoke(MethodBase metadata, string stepName, List stepParameters)
{
StartFixture(metadata, stepName);
- var stepUuid = StartStep(metadata, stepName, stepParameters);
- return stepUuid;
+ StartStep(metadata, stepName, stepParameters);
}
- private static void AfterTargetInvoke(string stepUuid, MethodBase metadata)
+ private static void AfterTargetInvoke(MethodBase metadata)
{
- PassStep(stepUuid, metadata);
+ PassStep(metadata);
PassFixture(metadata);
}
- private static void OnTargetInvokeException(string stepUuid, MethodBase metadata, Exception e)
+ private static void OnTargetInvokeException(MethodBase metadata, Exception e)
{
- ThrowStep(stepUuid, metadata, e);
+ ThrowStep(metadata, e);
ThrowFixture(metadata, e);
}
@@ -164,19 +139,17 @@ private static T WrapSync(
List stepParameters
)
{
- string stepUuid = null;
-
try
{
- stepUuid = BeforeTargetInvoke(metadata, stepName, stepParameters);
+ BeforeTargetInvoke(metadata, stepName, stepParameters);
var result = (T)target(args);
- AfterTargetInvoke(stepUuid, metadata);
+ AfterTargetInvoke(metadata);
return result;
}
catch (Exception e)
{
- OnTargetInvokeException(stepUuid, metadata, e);
+ OnTargetInvokeException(metadata, e);
throw;
}
}
@@ -189,17 +162,15 @@ private static void WrapSyncVoid(
List stepParameters
)
{
- string stepUuid = null;
-
try
{
- stepUuid = BeforeTargetInvoke(metadata, stepName, stepParameters);
+ BeforeTargetInvoke(metadata, stepName, stepParameters);
target(args);
- AfterTargetInvoke(stepUuid, metadata);
+ AfterTargetInvoke(metadata);
}
catch (Exception e)
{
- OnTargetInvokeException(stepUuid, metadata, e);
+ OnTargetInvokeException(metadata, e);
throw;
}
}
@@ -212,17 +183,15 @@ private static async Task WrapAsync(
List stepParameters
)
{
- string stepUuid = null;
-
try
{
- stepUuid = BeforeTargetInvoke(metadata, stepName, stepParameters);
+ BeforeTargetInvoke(metadata, stepName, stepParameters);
await ((Task)target(args)).ConfigureAwait(false);
- AfterTargetInvoke(stepUuid, metadata);
+ AfterTargetInvoke(metadata);
}
catch (Exception e)
{
- OnTargetInvokeException(stepUuid, metadata, e);
+ OnTargetInvokeException(metadata, e);
throw;
}
}
@@ -235,19 +204,17 @@ private static async Task WrapAsyncGeneric(
List stepParameters
)
{
- string stepUuid = null;
-
try
{
- stepUuid = BeforeTargetInvoke(metadata, stepName, stepParameters);
+ BeforeTargetInvoke(metadata, stepName, stepParameters);
var result = await ((Task)target(args)).ConfigureAwait(false);
- AfterTargetInvoke(stepUuid, metadata);
+ AfterTargetInvoke(metadata);
return result;
}
catch (Exception e)
{
- OnTargetInvokeException(stepUuid, metadata, e);
+ OnTargetInvokeException(metadata, e);
throw;
}
}
diff --git a/Allure.Net.Commons/Steps/CoreStepsHelper.cs b/Allure.Net.Commons/Steps/CoreStepsHelper.cs
index 0b1e14a4..6a51100e 100644
--- a/Allure.Net.Commons/Steps/CoreStepsHelper.cs
+++ b/Allure.Net.Commons/Steps/CoreStepsHelper.cs
@@ -19,155 +19,96 @@ public static ITestResultAccessor TestResultAccessor
#region Fixtures
- public static string StartBeforeFixture(string name)
+ public static void StartBeforeFixture(string name)
{
- var fixtureResult = new FixtureResult()
- {
- name = name,
- stage = Stage.running,
- start = DateTimeOffset.Now.ToUnixTimeMilliseconds()
- };
-
- AllureLifecycle.Instance.StartBeforeFixture(
- AllureLifecycle.Instance.Context.CurrentContainer.uuid,
- fixtureResult,
- out var uuid
- );
+ AllureLifecycle.Instance.StartBeforeFixture(new() { name = name });
StepLogger?.BeforeStarted?.Log(name);
- return uuid;
}
- public static string StartAfterFixture(string name)
+ public static void StartAfterFixture(string name)
{
- var fixtureResult = new FixtureResult()
- {
- name = name,
- stage = Stage.running,
- start = DateTimeOffset.Now.ToUnixTimeMilliseconds()
- };
-
- AllureLifecycle.Instance.StartAfterFixture(
- AllureLifecycle.Instance.Context.CurrentContainer.uuid,
- fixtureResult,
- out var uuid
- );
+ AllureLifecycle.Instance.StartAfterFixture(new() { name = name });
StepLogger?.AfterStarted?.Log(name);
- return uuid;
}
- public static void StopFixture(Action updateResults = null)
- {
- AllureLifecycle.Instance.StopFixture(result =>
- {
- result.stage = Stage.finished;
- result.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- updateResults?.Invoke(result);
- });
- }
-
+ public static void StopFixture(Action updateResults) =>
+ AllureLifecycle.Instance.StopFixture(updateResults);
+
+ public static void StopFixture() =>
+ AllureLifecycle.Instance.StopFixture();
+
+ [Obsolete]
public static void StopFixtureSuppressTestCase(Action updateResults = null)
{
var newTestResult = AllureLifecycle.Instance.Context.CurrentTest;
StopFixture(updateResults);
- AllureLifecycle.Instance.StartTestCase(
- AllureLifecycle.Instance.Context.CurrentContainer.uuid,
- newTestResult
- );
+ AllureLifecycle.Instance.StartTestCase(newTestResult);
}
#endregion
#region Steps
- public static string StartStep(string name, Action updateResults = null)
+ public static void StartStep(string name)
{
- var stepResult = new StepResult
- {
- name = name,
- stage = Stage.running,
- start = DateTimeOffset.Now.ToUnixTimeMilliseconds()
- };
- updateResults?.Invoke(stepResult);
-
- AllureLifecycle.Instance.StartStep(stepResult, out var uuid);
+ AllureLifecycle.Instance.StartStep(new() { name = name });
StepLogger?.StepStarted?.Log(name);
- return uuid;
}
- public static void PassStep(Action updateResults = null)
+ public static void StartStep(string name, Action updateResults)
{
- AllureLifecycle.Instance.StopStep(result =>
- {
- result.status = Status.passed;
- updateResults?.Invoke(result);
- StepLogger?.StepPassed?.Log(result.name);
- });
+ StartStep(name);
+ AllureLifecycle.Instance.UpdateStep(updateResults);
}
- public static void PassStep(string uuid, Action updateResults = null)
- {
- AllureLifecycle.Instance.UpdateStep(uuid, result =>
+ public static void PassStep() => AllureLifecycle.Instance.StopStep(
+ result =>
{
result.status = Status.passed;
- updateResults?.Invoke(result);
StepLogger?.StepPassed?.Log(result.name);
- });
- AllureLifecycle.Instance.StopStep(uuid);
- }
+ }
+ );
- public static void FailStep(Action updateResults = null)
+ public static void PassStep(Action updateResults)
{
- AllureLifecycle.Instance.StopStep(result =>
- {
- result.status = Status.failed;
- updateResults?.Invoke(result);
- StepLogger?.StepFailed?.Log(result.name);
- });
+ AllureLifecycle.Instance.UpdateStep(updateResults);
+ PassStep();
}
- public static void FailStep(string uuid, Action updateResults = null)
- {
- AllureLifecycle.Instance.UpdateStep(uuid, result =>
+ public static void FailStep() => AllureLifecycle.Instance.StopStep(
+ result =>
{
result.status = Status.failed;
- updateResults?.Invoke(result);
StepLogger?.StepFailed?.Log(result.name);
- });
- AllureLifecycle.Instance.StopStep(uuid);
- }
-
- public static void BrokeStep(Action updateResults = null)
+ }
+ );
+
+ public static void FailStep(Action updateResults)
{
- AllureLifecycle.Instance.StopStep(result =>
- {
- result.status = Status.broken;
- updateResults?.Invoke(result);
- StepLogger?.StepBroken?.Log(result.name);
- });
+ AllureLifecycle.Instance.UpdateStep(updateResults);
+ FailStep();
}
-
- public static void BrokeStep(string uuid, Action updateResults = null)
- {
- AllureLifecycle.Instance.UpdateStep(uuid, result =>
+
+ public static void BrokeStep() => AllureLifecycle.Instance.StopStep(
+ result =>
{
result.status = Status.broken;
- updateResults?.Invoke(result);
StepLogger?.StepBroken?.Log(result.name);
- });
- AllureLifecycle.Instance.StopStep(uuid);
+ }
+ );
+
+ public static void BrokeStep(Action updateResults)
+ {
+ AllureLifecycle.Instance.UpdateStep(updateResults);
+ BrokeStep();
}
#endregion
#region Misc
- public static void UpdateTestResult(Action update)
- {
- AllureLifecycle.Instance.UpdateTestCase(
- AllureLifecycle.Instance.Context.CurrentTest.uuid,
- update
- );
- }
+ public static void UpdateTestResult(Action update) =>
+ AllureLifecycle.Instance.UpdateTestCase(update);
#endregion
diff --git a/Allure.Net.Commons/Storage/AllureContext.cs b/Allure.Net.Commons/Storage/AllureContext.cs
index a6b0c780..54157cfc 100644
--- a/Allure.Net.Commons/Storage/AllureContext.cs
+++ b/Allure.Net.Commons/Storage/AllureContext.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Immutable;
using System.Linq;
+using System.Text;
#nullable enable
@@ -19,6 +20,26 @@ namespace Allure.Net.Commons.Storage
///
public record class AllureContext
{
+ ///
+ /// Returns true if a container context is active.
+ ///
+ public bool HasContainer => !this.ContainerContext.IsEmpty;
+
+ ///
+ /// Returns true if a fixture context is active.
+ ///
+ public bool HasFixture => this.FixtureContext is not null;
+
+ ///
+ /// Returns true if a test context is active.
+ ///
+ public bool HasTest => this.TestContext is not null;
+
+ ///
+ /// Returns true if a step context is active.
+ ///
+ public bool HasStep => !this.StepContext.IsEmpty;
+
///
/// A stack of fixture containers affecting subsequent tests.
///
@@ -93,12 +114,10 @@ internal TestResultContainer CurrentContainer
/// context isn't active.
///
///
- internal FixtureResult CurrentFixture
- {
- get => this.FixtureContext ?? throw new InvalidOperationException(
+ internal FixtureResult CurrentFixture =>
+ this.FixtureContext ?? throw new InvalidOperationException(
"No fixture context is active."
);
- }
///
/// A test that is being executed.
@@ -108,12 +127,10 @@ internal FixtureResult CurrentFixture
/// isn't active.
///
///
- internal TestResult CurrentTest
- {
- get => this.TestContext ?? throw new InvalidOperationException(
+ internal TestResult CurrentTest =>
+ this.TestContext ?? throw new InvalidOperationException(
"No test context is active."
);
- }
///
/// A step that is being executed.
@@ -123,13 +140,11 @@ internal TestResult CurrentTest
/// isn't active.
///
///
- internal StepResult CurrentStep
- {
- get => this.StepContext.FirstOrDefault()
+ internal StepResult CurrentStep =>
+ this.StepContext.FirstOrDefault()
?? throw new InvalidOperationException(
"No step context is active."
);
- }
///
/// A step container a next step should be put in.
@@ -140,13 +155,26 @@ internal StepResult CurrentStep
/// fixture, nor test, nor step context is active.
///
///
- internal ExecutableItem CurrentStepContainer
- {
- get => this.StepContext.FirstOrDefault() as ExecutableItem
+ internal ExecutableItem CurrentStepContainer =>
+ this.StepContext.FirstOrDefault() as ExecutableItem
?? this.RootStepContainer
?? throw new InvalidOperationException(
"No fixture, test, or step context is active."
);
+
+ protected virtual bool PrintMembers(StringBuilder stringBuilder)
+ {
+ var containers =
+ RepresentStack(this.ContainerContext, c => c.name);
+ var fixture = this.FixtureContext?.name ?? "null";
+ var test = this.TestContext?.name ?? "null";
+ var steps = RepresentStack(this.StepContext, s => s.name);
+
+ stringBuilder.AppendFormat("Containers = [{0}], ", containers);
+ stringBuilder.AppendFormat("Fixture = {0}, ", fixture);
+ stringBuilder.AppendFormat("Test = {0}, ", test);
+ stringBuilder.AppendFormat("Steps = [{0}]", steps);
+ return true;
}
///
@@ -309,11 +337,12 @@ this with
internal AllureContext WithNoLastStep() =>
this with
{
- StepContext = this.StepContext.IsEmpty
- ? throw new InvalidOperationException(
+ StepContext = this.HasStep
+ ? this.StepContext.Pop()
+ : throw new InvalidOperationException(
"Unable to deactivate a step context because it's " +
"already inactive."
- ) : this.StepContext.Pop()
+ )
};
AllureContext ValidateContainerContextCanBeModified()
@@ -339,7 +368,7 @@ AllureContext ValidateContainerContextCanBeModified()
AllureContext ValidateContainerCanBeRemoved()
{
- if (this.ContainerContext.IsEmpty)
+ if (!this.HasContainer)
{
throw new InvalidOperationException(
"Unable to deactivate a container context because it's " +
@@ -357,7 +386,7 @@ AllureContext ValidateContainerCanBeRemoved()
FixtureResult ValidateNewFixtureContext(FixtureResult fixture)
{
- if (this.ContainerContext.IsEmpty)
+ if (!this.HasContainer)
{
throw new InvalidOperationException(
"Unable to activate a fixture context " +
@@ -365,7 +394,7 @@ FixtureResult ValidateNewFixtureContext(FixtureResult fixture)
);
}
- if (this.FixtureContext is not null)
+ if (this.HasFixture)
{
throw new InvalidOperationException(
"Unable to activate a fixture context " +
@@ -378,7 +407,7 @@ FixtureResult ValidateNewFixtureContext(FixtureResult fixture)
TestResult ValidateNewTestContext(TestResult testResult)
{
- if (this.FixtureContext is not null)
+ if (this.HasFixture)
{
throw new InvalidOperationException(
"Unable to activate a test context " +
@@ -386,7 +415,7 @@ TestResult ValidateNewTestContext(TestResult testResult)
);
}
- if (this.TestContext is not null)
+ if (this.HasTest)
{
throw new InvalidOperationException(
"Unable to activate a test context " +
@@ -399,15 +428,23 @@ TestResult ValidateNewTestContext(TestResult testResult)
StepResult ValidateNewStep(StepResult stepResult)
{
- if (this.RootStepContainer is null)
+ if (!this.HasTest && !this.HasFixture)
{
throw new InvalidOperationException(
- "Unable to activate a step context because neither test, " +
- "nor fixture context is active."
+ "Unable to activate a step context because neither " +
+ "test, nor fixture context is active."
);
}
return stepResult;
}
+
+ static string RepresentStack(
+ IImmutableStack stack,
+ Func projection
+ ) => string.Join(
+ " <- ",
+ stack.Select(projection)
+ );
}
}
diff --git a/Allure.Net.Commons/Storage/AllureStorage.cs b/Allure.Net.Commons/Storage/AllureStorage.cs
index a0db2f80..042d824f 100644
--- a/Allure.Net.Commons/Storage/AllureStorage.cs
+++ b/Allure.Net.Commons/Storage/AllureStorage.cs
@@ -16,7 +16,8 @@ internal class AllureStorage
public AllureContext CurrentContext
{
get => this.context.Value ??= new();
- set => this.context.Value = value;
+ set => this.context.Value = value
+ ?? throw new ArgumentNullException(nameof(CurrentContext));
}
public AllureStorage()
@@ -24,35 +25,23 @@ public AllureStorage()
this.CurrentContext = new();
}
- public TestResultContainer? CurrentTestContainerOrNull
- {
- get => this.CurrentContext.ContainerContext.FirstOrDefault();
- }
+ public TestResultContainer? CurrentTestContainerOrNull =>
+ this.CurrentContext.ContainerContext.FirstOrDefault();
- public TestResultContainer CurrentTestContainer
- {
- get => this.CurrentContext.CurrentContainer;
- }
+ public TestResultContainer CurrentTestContainer =>
+ this.CurrentContext.CurrentContainer;
- public FixtureResult CurrentFixture
- {
- get => this.CurrentContext.CurrentFixture;
- }
-
- public TestResult CurrentTest
- {
- get => this.CurrentContext.CurrentTest;
- }
-
- public ExecutableItem CurrentStepContainer
- {
- get => this.CurrentContext.CurrentStepContainer;
- }
-
- public StepResult CurrentStep
- {
- get => this.CurrentContext.CurrentStep;
- }
+ public FixtureResult CurrentFixture =>
+ this.CurrentContext.CurrentFixture;
+
+ public TestResult CurrentTest =>
+ this.CurrentContext.CurrentTest;
+
+ public ExecutableItem CurrentStepContainer =>
+ this.CurrentContext.CurrentStepContainer;
+
+ public StepResult CurrentStep =>
+ this.CurrentContext.CurrentStep;
public T Get(string uuid)
{
diff --git a/Allure.XUnit/AllureAfter.cs b/Allure.XUnit/AllureAfter.cs
index 4162fb6b..66690605 100644
--- a/Allure.XUnit/AllureAfter.cs
+++ b/Allure.XUnit/AllureAfter.cs
@@ -6,18 +6,9 @@ namespace Allure.Xunit
public sealed class AllureAfter : AllureStepBase
{
[Obsolete("Use AllureAfterAttribute")]
- public AllureAfter(string name) : base(Init(name))
+ public AllureAfter(string name)
{
- }
-
- ///
- /// Starts After fixture and return it's UUID
- ///
- /// The name of created fixture
- /// string: UUID
- private static string Init(string name)
- {
- return CoreStepsHelper.StartAfterFixture(name);
+ CoreStepsHelper.StartAfterFixture(name);
}
}
}
\ No newline at end of file
diff --git a/Allure.XUnit/AllureBefore.cs b/Allure.XUnit/AllureBefore.cs
index caafb68c..72b75904 100644
--- a/Allure.XUnit/AllureBefore.cs
+++ b/Allure.XUnit/AllureBefore.cs
@@ -6,18 +6,9 @@ namespace Allure.Xunit
public sealed class AllureBefore : AllureStepBase
{
[Obsolete("Use AllureBeforeAttribute")]
- public AllureBefore(string name) : base(Init(name))
+ public AllureBefore(string name)
{
- }
-
- ///
- /// Starts Before fixture and return it's UUID
- ///
- /// The name of created fixture
- /// string: UUID
- private static string Init(string name)
- {
- return CoreStepsHelper.StartBeforeFixture(name);
+ CoreStepsHelper.StartBeforeFixture(name);
}
}
}
\ No newline at end of file
diff --git a/Allure.XUnit/AllureMessageSink.cs b/Allure.XUnit/AllureMessageSink.cs
index 248f2e7d..879178ff 100644
--- a/Allure.XUnit/AllureMessageSink.cs
+++ b/Allure.XUnit/AllureMessageSink.cs
@@ -1,9 +1,10 @@
-using Allure.Net.Commons;
-using Allure.Net.Commons.Steps;
-using Allure.Xunit;
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
+using Allure.Net.Commons;
+using Allure.Net.Commons.Storage;
+using Allure.Xunit;
using Xunit;
using Xunit.Abstractions;
@@ -11,8 +12,9 @@ namespace Allure.XUnit
{
public class AllureMessageSink : TestMessageSink
{
- IRunnerLogger logger;
- Dictionary allureTestData = new();
+ readonly IRunnerLogger logger;
+ readonly ConcurrentDictionary allureTestData
+ = new();
public AllureMessageSink(IRunnerLogger logger)
{
@@ -26,8 +28,36 @@ public AllureMessageSink(IRunnerLogger logger)
this.OnTestClassConstructionFinished;
this.Execution.TestFailedEvent += this.OnTestFailed;
this.Execution.TestPassedEvent += this.OnTestPassed;
+ this.Execution.TestSkippedEvent += this.OnTestSkipped;
this.Execution.TestFinishedEvent += this.OnTestFinished;
- this.Execution.TestCaseFinishedEvent+= this.OnTestCaseFinished;
+ }
+
+ public override bool OnMessageWithTypes(
+ IMessageSinkMessage message,
+ HashSet messageTypes
+ )
+ {
+ try
+ {
+ this.logger.LogMessage(message.GetType().Name);
+ return base.OnMessageWithTypes(message, messageTypes);
+ }
+ catch (Exception e)
+ {
+ if (message is ITestCaseMessage testCaseMessage)
+ {
+ this.logger.LogError(
+ "Error during execution of {0}: {1}",
+ testCaseMessage.TestCase.DisplayName,
+ e
+ );
+ }
+ else
+ {
+ this.logger.LogError(e.ToString());
+ }
+ return false;
+ }
}
void OnTestAssemblyExecutionStarting(
@@ -37,27 +67,22 @@ MessageHandlerArgs args
args.Message.ExecutionOptions.SetSynchronousMessageReporting(true);
}
- internal void OnTestArgumentsCreated(ITest test, object[] arguments)
- {
- var accessor = this.GetOrCreateAllureResultAccessor(test);
- accessor.Arguments = arguments;
- }
+ internal void OnTestArgumentsCreated(ITest test, object[] arguments) =>
+ this.GetOrCreateTestData(test).Arguments = arguments;
void OnTestStarting(MessageHandlerArgs args)
{
var message = args.Message;
var test = message.Test;
- var accessor = this.GetOrCreateAllureResultAccessor(test);
-
- CoreStepsHelper.TestResultAccessor = accessor;
- if (message.TestMethod.Method.IsStatic)
+ if (IsStaticTestMethod(message))
{
- accessor.TestResult = AllureXunitHelper.StartStaticAllureTestCase(test);
+ AllureXunitHelper.StartStaticAllureTestCase(test);
+ this.CaptureTestContext(test);
}
else
{
- accessor.TestResultContainer = AllureXunitHelper.StartNewAllureContainer(
+ AllureXunitHelper.StartNewAllureContainer(
message.TestClass.Class.Name
);
}
@@ -66,90 +91,77 @@ void OnTestStarting(MessageHandlerArgs args)
void OnTestClassConstructionFinished(
MessageHandlerArgs args
)
- {
- var test = args.Message.Test;
- var accessor = this.allureTestData[test];
- var container = accessor.TestResultContainer;
- if (accessor.TestResult is null && container is not null)
- {
- accessor.TestResult = AllureXunitHelper.StartAllureTestCase(
- test,
- container
- );
- }
- }
-
- void OnTestFailed(MessageHandlerArgs args)
{
var message = args.Message;
var test = message.Test;
- var testResult = this.allureTestData[test].TestResult;
-
- if (testResult is not null)
+ if (!IsStaticTestMethod(message))
{
- AllureXunitHelper.ApplyTestFailure(testResult, message);
+ AllureXunitHelper.StartAllureTestCase(test);
+ this.CaptureTestContext(test);
}
}
- void OnTestPassed(MessageHandlerArgs args)
+ void OnTestFailed(MessageHandlerArgs args) =>
+ this.RunInTestContext(
+ args.Message.Test,
+ _ => AllureXunitHelper.ApplyTestFailure(args.Message)
+ );
+
+ void OnTestPassed(MessageHandlerArgs args) =>
+ this.RunInTestContext(
+ args.Message.Test,
+ _ => AllureXunitHelper.ApplyTestSuccess(args.Message)
+ );
+
+ void OnTestSkipped(MessageHandlerArgs args)
{
var message = args.Message;
var test = message.Test;
- var testResult = this.allureTestData[test].TestResult;
-
- if (testResult is not null)
+ this.UpdateTestContext(test, ctx =>
{
- AllureXunitHelper.ApplyTestSuccess(testResult, message);
- }
+ if (!ctx.HasTest)
+ {
+ AllureXunitHelper.StartAllureTestCase(test);
+ }
+ AllureXunitHelper.ApplyTestSkip(message);
+ });
}
void OnTestFinished(MessageHandlerArgs args)
{
+ var message = args.Message;
var test = args.Message.Test;
- var accessor = this.allureTestData[test];
- this.allureTestData.Remove(test);
- var testResult = accessor.TestResult;
- if (testResult is not null)
- {
- this.AddAllureParameters(testResult, test, accessor.Arguments);
- AllureXunitHelper.ReportTestCase(testResult);
+ var arguments = this.allureTestData[test].Arguments;
- var container = accessor.TestResultContainer;
- if (container is not null)
+ this.RunInTestContext(test, _ =>
+ {
+ this.AddAllureParameters(test, arguments);
+ AllureXunitHelper.ReportCurrentTestCase();
+ if (!IsStaticTestMethod(message))
{
- AllureXunitHelper.ReportTestContainer(container);
+ AllureXunitHelper.ReportCurrentTestContainer();
}
- }
- }
+ });
- void OnTestCaseFinished(MessageHandlerArgs args)
- {
- var testCase = args.Message.TestCase;
- if (testCase.SkipReason != null)
- {
- AllureXunitHelper.ReportSkippedTestCase(testCase);
- }
+ this.allureTestData.Remove(test, out _);
}
- AllureXunitTestResultAccessor GetOrCreateAllureResultAccessor(ITest test)
+ AllureXunitTestData GetOrCreateTestData(ITest test)
{
- if (!this.allureTestData.TryGetValue(test, out var accessor))
+ if (!this.allureTestData.TryGetValue(test, out var data))
{
- accessor = new AllureXunitTestResultAccessor();
- this.allureTestData[test] = accessor;
+ data = new AllureXunitTestData();
+ this.allureTestData[test] = data;
}
- return accessor;
+ return data;
}
- void AddAllureParameters(
- TestResult testResult,
- ITest test,
- object[] arguments
- )
+ void AddAllureParameters(ITest test, object[] arguments)
{
var testCase = test.TestCase;
var parameters = testCase.TestMethod.Method.GetParameters();
- arguments ??= testCase.TestMethodArguments ?? Array.Empty();
+ arguments ??= testCase.TestMethodArguments
+ ?? Array.Empty();
if (parameters.Any() && !arguments.Any())
{
@@ -157,10 +169,34 @@ object[] arguments
}
else
{
- AllureXunitHelper.ApplyTestParameters(testResult, parameters, arguments);
+ AllureXunitHelper.ApplyTestParameters(parameters, arguments);
}
}
+ AllureContext GetTestContext(ITest test) =>
+ this.GetOrCreateTestData(test).Context
+ ?? AllureLifecycle.Instance.Context;
+
+ void CaptureTestContext(ITest test) =>
+ this.GetOrCreateTestData(test).Context =
+ AllureLifecycle.Instance.Context;
+
+ void RunInTestContext(ITest test, Action action) =>
+ AllureLifecycle.Instance.RunInContext(
+ this.GetOrCreateTestData(test).Context,
+ action
+ );
+
+ void UpdateTestContext(ITest test, Action action) =>
+ this.RunInTestContext(
+ test,
+ ctx =>
+ {
+ action(ctx);
+ this.CaptureTestContext(test);
+ }
+ );
+
void LogUnreportedTheoryArgs(string testName)
{
var message = $"Unable to attach arguments of {testName} to " +
@@ -171,5 +207,8 @@ void LogUnreportedTheoryArgs(string testName)
#endif
this.logger.LogWarning(message);
}
+
+ static bool IsStaticTestMethod(ITestMethodMessage message) =>
+ message.TestMethod.Method.IsStatic;
}
}
\ No newline at end of file
diff --git a/Allure.XUnit/AllureStep.cs b/Allure.XUnit/AllureStep.cs
index 6a75a427..828997fd 100644
--- a/Allure.XUnit/AllureStep.cs
+++ b/Allure.XUnit/AllureStep.cs
@@ -6,18 +6,9 @@ namespace Allure.Xunit
public sealed class AllureStep : AllureStepBase
{
[Obsolete("Use AllureStepAttribute")]
- public AllureStep(string name) : base(Init(name))
+ public AllureStep(string name)
{
- }
-
- ///
- /// Creates a new step and return it's UUID
- ///
- /// The name of created step
- /// string: UUID
- private static string Init(string name)
- {
- return CoreStepsHelper.StartStep(name);
+ CoreStepsHelper.StartStep(name);
}
}
}
\ No newline at end of file
diff --git a/Allure.XUnit/AllureStepBase.cs b/Allure.XUnit/AllureStepBase.cs
index e964f6f5..169547f6 100644
--- a/Allure.XUnit/AllureStepBase.cs
+++ b/Allure.XUnit/AllureStepBase.cs
@@ -12,12 +12,7 @@ namespace Allure.Xunit
{
public abstract class AllureStepBase : IDisposable where T : AllureStepBase
{
- protected AllureStepBase(string uuid)
- {
- UUID = uuid;
- }
-
- private string UUID { get; }
+ protected AllureStepBase() { }
public void Dispose()
{
@@ -28,37 +23,24 @@ public void Dispose()
#endif
if (failed)
{
- if (this is AllureBefore || this is AllureAfter)
- {
- CoreStepsHelper.StopFixtureSuppressTestCase(result => result.status = Status.failed);
- }
- else
- {
- CoreStepsHelper.FailStep(UUID);
- }
+ CoreStepsHelper.FailStep();
}
else
{
- if (this is AllureBefore || this is AllureAfter)
- {
- CoreStepsHelper.StopFixtureSuppressTestCase(result => result.status = Status.passed);
- }
- else
- {
- CoreStepsHelper.PassStep(UUID);
- }
+ CoreStepsHelper.PassStep();
}
}
[Obsolete("For named parameters use NameAttribute; For skipped parameters use SkipAttribute")]
public T SetParameter(string name, object value)
{
- AllureLifecycle.Instance.UpdateStep(UUID,
+ AllureLifecycle.Instance.UpdateStep(
result =>
{
result.parameters ??= new List();
result.parameters.Add(new Parameter { name = name, value = value?.ToString() });
- });
+ }
+ );
return (T) this;
}
diff --git a/Allure.XUnit/AllureXunitHelper.cs b/Allure.XUnit/AllureXunitHelper.cs
index 8148bfd2..d18085ce 100644
--- a/Allure.XUnit/AllureXunitHelper.cs
+++ b/Allure.XUnit/AllureXunitHelper.cs
@@ -45,13 +45,6 @@ static AllureXunitHelper()
);
}
- internal static TestResult StartStaticAllureTestCase(ITest test)
- {
- var testResult = CreateTestResultByTest(test);
- AllureLifecycle.Instance.StartTestCase(testResult);
- return testResult;
- }
-
internal static TestResultContainer StartNewAllureContainer(
string className
)
@@ -65,63 +58,93 @@ string className
return container;
}
- internal static TestResult StartAllureTestCase(
- ITest test,
- TestResultContainer container
- )
+ internal static TestResult StartStaticAllureTestCase(ITest test)
{
var testResult = CreateTestResultByTest(test);
- AllureLifecycle.Instance.StartTestCase(container.uuid, testResult);
+ AllureLifecycle.Instance.StartTestCase(testResult);
return testResult;
}
- internal static void ApplyTestFailure(
- TestResult testResult,
- IFailureInformation failure
- )
+ internal static TestResult StartAllureTestCase(ITest test)
{
- var statusDetails = testResult.statusDetails ??= new();
- statusDetails.trace = string.Join('\n', failure.StackTraces);
- statusDetails.message = string.Join('\n', failure.Messages);
+ var testResult = CreateTestResultByTest(test);
+ AllureLifecycle.Instance.StartTestCase(testResult);
+ return testResult;
+ }
- testResult.status = failure.ExceptionTypes.Any(
+ internal static void ApplyTestFailure(IFailureInformation failure)
+ {
+ var trace = string.Join('\n', failure.StackTraces);
+ var message = string.Join('\n', failure.Messages);
+ var status = failure.ExceptionTypes.Any(
exceptionType => !exceptionType.StartsWith("Xunit.Sdk.")
) ? Status.broken : Status.failed;
+
+ AllureLifecycle.Instance.UpdateTestCase(testResult =>
+ {
+ var statusDetails = testResult.statusDetails ??= new();
+ statusDetails.trace = trace;
+ statusDetails.message = message;
+ testResult.status = status;
+ });
}
- internal static void ApplyTestSuccess(
- TestResult testResult,
- ITestResultMessage message
- )
+ internal static void ApplyTestSuccess(ITestResultMessage success)
{
- var statusDetails = testResult.statusDetails ??= new();
- statusDetails.message = message.Output;
- testResult.status = Status.passed;
+ var message = success.Output;
+ var status = Status.passed;
+
+ AllureLifecycle.Instance.UpdateTestCase(testResult =>
+ {
+ var statusDetails = testResult.statusDetails ??= new();
+ statusDetails.message = message;
+ testResult.status = status;
+ });
+ }
+
+ internal static void ApplyTestSkip(ITestSkipped skip)
+ {
+ var message = skip.Reason;
+ var status = Status.skipped;
+
+ AllureLifecycle.Instance.UpdateTestCase(testResult =>
+ {
+ var statusDetails = testResult.statusDetails ??= new();
+ statusDetails.message = message;
+ testResult.status = status;
+ });
}
internal static void ApplyTestParameters(
- TestResult testResult,
IEnumerable parameters,
object[] arguments
- ) => testResult.parameters = parameters.Zip(
- arguments,
- (param, value) => new Parameter
+ )
+ {
+ var parametersList = parameters.Zip(
+ arguments,
+ (param, value) => new Parameter
+ {
+ name = param.Name,
+ value = value?.ToString() ?? "null"
+ }
+ ).ToList();
+
+ AllureLifecycle.Instance.UpdateTestCase(testResult =>
{
- name = param.Name,
- value = value?.ToString() ?? "null"
- }
- ).ToList();
+ testResult.parameters = parametersList;
+ });
+ }
- internal static void ReportTestCase(TestResult testResult)
+ internal static void ReportCurrentTestCase()
{
- AllureLifecycle.Instance.StopTestCase(testResult.uuid);
- AllureLifecycle.Instance.WriteTestCase(testResult.uuid);
+ AllureLifecycle.Instance.StopTestCase();
+ AllureLifecycle.Instance.WriteTestCase();
}
- internal static void ReportTestContainer(TestResultContainer container)
+ internal static void ReportCurrentTestContainer()
{
- AllureLifecycle.Instance.StopTestContainer(container.uuid);
- AllureLifecycle.Instance.WriteTestContainer(container.uuid);
+ AllureLifecycle.Instance.StopTestContainer();
+ AllureLifecycle.Instance.WriteTestContainer();
}
internal static void ReportSkippedTestCase(ITestCase testCase)
@@ -129,7 +152,7 @@ internal static void ReportSkippedTestCase(ITestCase testCase)
var testResult = CreateTestResultByTestCase(testCase);
ApplyTestSkip(testResult, testCase.SkipReason);
AllureLifecycle.Instance.StartTestCase(testResult);
- ReportTestCase(testResult);
+ ReportCurrentTestCase();
}
static TestResult CreateTestResultByTest(ITest test) =>
@@ -389,39 +412,35 @@ public static void StartTestCase(ITestCaseMessage testCaseMessage)
}
testResults.TestResult = CreateTestResultByTestCase(testCase);
+ AllureLifecycle.Instance.StartTestCase(testResults.TestResult);
ApplyTestParameters(
- testResults.TestResult,
testCase.TestMethod.Method.GetParameters(),
testCase.TestMethodArguments
);
- AllureLifecycle.Instance.StartTestCase(
- testResults.TestResultContainer.uuid,
- testResults.TestResult
- );
}
[Obsolete(OBS_MSG_UNINTENDED_PUBLIC)]
[EditorBrowsable(EditorBrowsableState.Never)]
public static void MarkTestCaseAsFailedOrBroken(ITestFailed testFailed)
{
- if (testFailed.TestCase is not ITestResultAccessor testResults)
+ if (testFailed.TestCase is not ITestResultAccessor)
{
return;
}
- ApplyTestFailure(testResults.TestResult, testFailed);
+ ApplyTestFailure(testFailed);
}
[Obsolete(OBS_MSG_UNINTENDED_PUBLIC)]
[EditorBrowsable(EditorBrowsableState.Never)]
public static void MarkTestCaseAsPassed(ITestPassed testPassed)
{
- if (testPassed.TestCase is not ITestResultAccessor testResults)
+ if (testPassed.TestCase is not ITestResultAccessor)
{
return;
}
- ApplyTestSuccess(testResults.TestResult, testPassed);
+ ApplyTestSuccess(testPassed);
}
[Obsolete(OBS_MSG_UNINTENDED_PUBLIC)]
@@ -444,13 +463,13 @@ ITestCaseMessage testCaseMessage
public static void FinishTestCase(ITestCaseMessage testCaseMessage)
{
var testCase = testCaseMessage.TestCase;
- if (testCase is not ITestResultAccessor testResults)
+ if (testCase is not ITestResultAccessor)
{
return;
}
- ReportTestCase(testResults.TestResult);
- ReportTestContainer(testResults.TestResultContainer);
+ ReportCurrentTestCase();
+ ReportCurrentTestContainer();
}
#endregion
}
diff --git a/Allure.XUnit/AllureXunitTestData.cs b/Allure.XUnit/AllureXunitTestData.cs
new file mode 100644
index 00000000..94f96073
--- /dev/null
+++ b/Allure.XUnit/AllureXunitTestData.cs
@@ -0,0 +1,10 @@
+using Allure.Net.Commons.Storage;
+
+namespace Allure.XUnit
+{
+ class AllureXunitTestData
+ {
+ public AllureContext Context { get; set; }
+ public object[] Arguments { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Allure.XUnit/AllureXunitTestResultAccessor.cs b/Allure.XUnit/AllureXunitTestResultAccessor.cs
deleted file mode 100644
index 760b9ea6..00000000
--- a/Allure.XUnit/AllureXunitTestResultAccessor.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Allure.Net.Commons;
-using Allure.Net.Commons.Storage;
-
-namespace Allure.XUnit
-{
- class AllureXunitTestResultAccessor : ITestResultAccessor
- {
- public TestResultContainer TestResultContainer { get; set; }
-
- public TestResult TestResult { get; set; }
-
- public object[] Arguments { get; set; }
- }
-}
\ No newline at end of file
From b89cb8bf6b57f7c54944ef2d30f340b0b7007dee Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Wed, 19 Jul 2023 18:26:33 +0700
Subject: [PATCH 08/24] Bump C# language version to 11 for all projects
---
Allure.Features/Allure.Features.csproj | 1 +
Allure.NUnit.Examples/Allure.NUnit.Examples.csproj | 1 +
Allure.NUnit/Allure.NUnit.csproj | 1 +
Allure.Net.Commons.Tests/Allure.Net.Commons.Tests.csproj | 2 +-
Allure.Net.Commons/Allure.Net.Commons.csproj | 1 +
.../Allure.SpecFlowPlugin.Tests.csproj | 1 +
Allure.SpecFlowPlugin/Allure.SpecFlowPlugin.csproj | 2 +-
Allure.XUnit.Examples/Allure.XUnit.Examples.csproj | 1 +
Allure.XUnit/Allure.XUnit.csproj | 6 +++---
9 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/Allure.Features/Allure.Features.csproj b/Allure.Features/Allure.Features.csproj
index b994f545..67af0001 100644
--- a/Allure.Features/Allure.Features.csproj
+++ b/Allure.Features/Allure.Features.csproj
@@ -1,6 +1,7 @@
netcoreapp3.1
+ 11
false
bin
diff --git a/Allure.NUnit.Examples/Allure.NUnit.Examples.csproj b/Allure.NUnit.Examples/Allure.NUnit.Examples.csproj
index 1f775846..370c9fb1 100644
--- a/Allure.NUnit.Examples/Allure.NUnit.Examples.csproj
+++ b/Allure.NUnit.Examples/Allure.NUnit.Examples.csproj
@@ -2,6 +2,7 @@
net6.0
+ 11
false
Library
diff --git a/Allure.NUnit/Allure.NUnit.csproj b/Allure.NUnit/Allure.NUnit.csproj
index 442ce783..2fce0102 100644
--- a/Allure.NUnit/Allure.NUnit.csproj
+++ b/Allure.NUnit/Allure.NUnit.csproj
@@ -2,6 +2,7 @@
netstandard2.0
+ 11
2.10-SNAPSHOT
false
Qameta Software
diff --git a/Allure.Net.Commons.Tests/Allure.Net.Commons.Tests.csproj b/Allure.Net.Commons.Tests/Allure.Net.Commons.Tests.csproj
index a595fb3c..88452e2a 100644
--- a/Allure.Net.Commons.Tests/Allure.Net.Commons.Tests.csproj
+++ b/Allure.Net.Commons.Tests/Allure.Net.Commons.Tests.csproj
@@ -3,7 +3,7 @@
net6.0
false
- default
+ 11
diff --git a/Allure.Net.Commons/Allure.Net.Commons.csproj b/Allure.Net.Commons/Allure.Net.Commons.csproj
index 24d93e9d..f988dae6 100644
--- a/Allure.Net.Commons/Allure.Net.Commons.csproj
+++ b/Allure.Net.Commons/Allure.Net.Commons.csproj
@@ -2,6 +2,7 @@
netstandard2.0
+ 11
2.10-SNAPSHOT
false
Alexander Bakanov, Nikolay Laptev
diff --git a/Allure.SpecFlowPlugin.Tests/Allure.SpecFlowPlugin.Tests.csproj b/Allure.SpecFlowPlugin.Tests/Allure.SpecFlowPlugin.Tests.csproj
index b232b746..7e56a367 100644
--- a/Allure.SpecFlowPlugin.Tests/Allure.SpecFlowPlugin.Tests.csproj
+++ b/Allure.SpecFlowPlugin.Tests/Allure.SpecFlowPlugin.Tests.csproj
@@ -2,6 +2,7 @@
netcoreapp3.1
+ 11
false
diff --git a/Allure.SpecFlowPlugin/Allure.SpecFlowPlugin.csproj b/Allure.SpecFlowPlugin/Allure.SpecFlowPlugin.csproj
index e2a3c73d..019ff73d 100644
--- a/Allure.SpecFlowPlugin/Allure.SpecFlowPlugin.csproj
+++ b/Allure.SpecFlowPlugin/Allure.SpecFlowPlugin.csproj
@@ -2,6 +2,7 @@
net462;netstandard2.0
+ 11
Allure.SpecFlow
2.10-SNAPSHOT
Alexander Bakanov
@@ -14,7 +15,6 @@
https://github.com/allure-framework/allure-csharp
specflow allure
false
- 8
true
true
snupkg
diff --git a/Allure.XUnit.Examples/Allure.XUnit.Examples.csproj b/Allure.XUnit.Examples/Allure.XUnit.Examples.csproj
index b021672b..13cf3e3a 100644
--- a/Allure.XUnit.Examples/Allure.XUnit.Examples.csproj
+++ b/Allure.XUnit.Examples/Allure.XUnit.Examples.csproj
@@ -3,6 +3,7 @@
false
net6.0
+ 11
diff --git a/Allure.XUnit/Allure.XUnit.csproj b/Allure.XUnit/Allure.XUnit.csproj
index c0252748..298c724e 100644
--- a/Allure.XUnit/Allure.XUnit.csproj
+++ b/Allure.XUnit/Allure.XUnit.csproj
@@ -1,8 +1,9 @@
-
+
- true
netcoreapp2.0;netstandard2.1;net5.0;net6.0
+ 11
+ true
Qameta Software
2.10-SNAPSHOT
Allure.XUnit
@@ -14,7 +15,6 @@
https://github.com/allure-framework/allure-csharp
allure xunit
false
- 9
true
true
true
From 50ef9ed32282eae1332b0a8bef4b997dbe8e8063 Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Wed, 19 Jul 2023 18:56:26 +0700
Subject: [PATCH 09/24] Enable null checks in some classes of allure-xunit
---
Allure.XUnit/AllureMessageSink.cs | 6 ++----
Allure.XUnit/AllureXunitHelper.cs | 2 ++
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/Allure.XUnit/AllureMessageSink.cs b/Allure.XUnit/AllureMessageSink.cs
index 879178ff..08b54b39 100644
--- a/Allure.XUnit/AllureMessageSink.cs
+++ b/Allure.XUnit/AllureMessageSink.cs
@@ -8,6 +8,8 @@
using Xunit;
using Xunit.Abstractions;
+#nullable enable
+
namespace Allure.XUnit
{
public class AllureMessageSink : TestMessageSink
@@ -173,10 +175,6 @@ void AddAllureParameters(ITest test, object[] arguments)
}
}
- AllureContext GetTestContext(ITest test) =>
- this.GetOrCreateTestData(test).Context
- ?? AllureLifecycle.Instance.Context;
-
void CaptureTestContext(ITest test) =>
this.GetOrCreateTestData(test).Context =
AllureLifecycle.Instance.Context;
diff --git a/Allure.XUnit/AllureXunitHelper.cs b/Allure.XUnit/AllureXunitHelper.cs
index d18085ce..98d3a3fa 100644
--- a/Allure.XUnit/AllureXunitHelper.cs
+++ b/Allure.XUnit/AllureXunitHelper.cs
@@ -12,6 +12,8 @@
using Xunit.Abstractions;
using Xunit.Sdk;
+#nullable enable
+
namespace Allure.Xunit
{
public static class AllureXunitHelper
From fbea57a5f3bf8fb63488eac7545e1dbc212ce38e Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Wed, 19 Jul 2023 20:57:18 +0700
Subject: [PATCH 10/24] Make lifecycle methods with explicit uuids obsoleted
---
Allure.Net.Commons/AllureLifecycle.cs | 595 +++++++++++++-------
Allure.Net.Commons/Steps/CoreStepsHelper.cs | 14 +-
Allure.Net.Commons/Storage/AllureStorage.cs | 153 +++--
3 files changed, 504 insertions(+), 258 deletions(-)
diff --git a/Allure.Net.Commons/AllureLifecycle.cs b/Allure.Net.Commons/AllureLifecycle.cs
index da1ea844..afaacea8 100644
--- a/Allure.Net.Commons/AllureLifecycle.cs
+++ b/Allure.Net.Commons/AllureLifecycle.cs
@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
-using System.Threading;
using Allure.Net.Commons.Configuration;
using Allure.Net.Commons.Storage;
using Allure.Net.Commons.Writer;
@@ -85,10 +85,6 @@ Action action
}
}
- /// Method to get the key for separation the steps for different tests.
- public static Func CurrentTestIdGetter { get; set; } =
- () => Thread.CurrentThread.ManagedThreadId.ToString();
-
internal AllureLifecycle(): this(GetConfiguration())
{
}
@@ -99,7 +95,8 @@ Func writerFactory
{
}
- internal AllureLifecycle(JObject config): this(config, c => new FileSystemResultsWriter(c))
+ internal AllureLifecycle(JObject config)
+ : this(config, c => new FileSystemResultsWriter(c))
{
}
@@ -130,7 +127,9 @@ private void AddTypeFormatterImpl(Type type, ITypeFormatter formatter) =>
#region TestContainer
- public virtual AllureLifecycle StartTestContainer(TestResultContainer container)
+ public virtual AllureLifecycle StartTestContainer(
+ TestResultContainer container
+ )
{
container.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
var parent = this.storage.CurrentTestContainerOrNull;
@@ -145,14 +144,9 @@ public virtual AllureLifecycle StartTestContainer(TestResultContainer container)
return this;
}
- public virtual AllureLifecycle StartTestContainer(string parentUuid, TestResultContainer container)
- {
- UpdateTestContainer(parentUuid, c => c.children.Add(container.uuid));
- StartTestContainer(container);
- return this;
- }
-
- public virtual AllureLifecycle UpdateTestContainer(Action update)
+ public virtual AllureLifecycle UpdateTestContainer(
+ Action update
+ )
{
var container = this.storage.CurrentTestContainer;
lock (this.monitor)
@@ -162,28 +156,12 @@ public virtual AllureLifecycle UpdateTestContainer(Action u
return this;
}
- public virtual AllureLifecycle UpdateTestContainer(string uuid, Action update)
- {
- var container = this.storage.Get(uuid);
- lock (this.monitor)
- {
- update.Invoke(container);
- }
- return this;
- }
-
public virtual AllureLifecycle StopTestContainer()
{
UpdateTestContainer(stopContainer);
return this;
}
- public virtual AllureLifecycle StopTestContainer(string uuid)
- {
- UpdateTestContainer(uuid, stopContainer);
- return this;
- }
-
public virtual AllureLifecycle WriteTestContainer()
{
var container = this.storage.CurrentTestContainer;
@@ -192,14 +170,6 @@ public virtual AllureLifecycle WriteTestContainer()
return this;
}
- public virtual AllureLifecycle WriteTestContainer(string uuid)
- {
- var container = this.storage.Get(uuid);
- this.storage.RemoveTestContainer(uuid);
- this.writer.Write(container);
- return this;
- }
-
#endregion
#region Fixture
@@ -211,34 +181,6 @@ public virtual AllureLifecycle StartBeforeFixture(FixtureResult result)
return this;
}
- public virtual AllureLifecycle StartBeforeFixture(FixtureResult result, out string uuid)
- {
- uuid = CreateUuid();
- StartBeforeFixture(uuid, result);
- return this;
- }
-
- public virtual AllureLifecycle StartBeforeFixture(string uuid, FixtureResult result)
- {
- UpdateTestContainer(container => container.befores.Add(result));
- StartFixture(uuid, result);
- return this;
- }
-
- public virtual AllureLifecycle StartBeforeFixture(string parentUuid, FixtureResult result, out string uuid)
- {
- uuid = CreateUuid();
- StartBeforeFixture(parentUuid, uuid, result);
- return this;
- }
-
- public virtual AllureLifecycle StartBeforeFixture(string parentUuid, string uuid, FixtureResult result)
- {
- UpdateTestContainer(parentUuid, container => container.befores.Add(result));
- StartFixture(uuid, result);
- return this;
- }
-
public virtual AllureLifecycle StartAfterFixture(FixtureResult result)
{
this.UpdateTestContainer(c => c.afters.Add(result));
@@ -246,21 +188,9 @@ public virtual AllureLifecycle StartAfterFixture(FixtureResult result)
return this;
}
- public virtual AllureLifecycle StartAfterFixture(string parentUuid, FixtureResult result, out string uuid)
- {
- uuid = CreateUuid();
- StartAfterFixture(parentUuid, uuid, result);
- return this;
- }
-
- public virtual AllureLifecycle StartAfterFixture(string parentUuid, string uuid, FixtureResult result)
- {
- UpdateTestContainer(parentUuid, container => container.afters.Add(result));
- StartFixture(uuid, result);
- return this;
- }
-
- public virtual AllureLifecycle UpdateFixture(Action update)
+ public virtual AllureLifecycle UpdateFixture(
+ Action update
+ )
{
var fixture = this.storage.CurrentFixture;
lock (this.monitor)
@@ -270,17 +200,9 @@ public virtual AllureLifecycle UpdateFixture(Action update)
return this;
}
- public virtual AllureLifecycle UpdateFixture(string uuid, Action update)
- {
- var fixture = this.storage.Get(uuid);
- lock (this.monitor)
- {
- update.Invoke(fixture);
- }
- return this;
- }
-
- public virtual AllureLifecycle StopFixture(Action beforeStop)
+ public virtual AllureLifecycle StopFixture(
+ Action beforeStop
+ )
{
this.UpdateFixture(beforeStop);
return this.StopFixture();
@@ -297,27 +219,10 @@ public virtual AllureLifecycle StopFixture()
return this;
}
- public virtual AllureLifecycle StopFixture(string uuid)
- {
- this.UpdateFixture(fixture =>
- {
- fixture.stage = Stage.finished;
- fixture.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- });
- this.storage.RemoveFixture(uuid);
- return this;
- }
-
#endregion
#region TestCase
- public virtual AllureLifecycle StartTestCase(string containerUuid, TestResult testResult)
- {
- UpdateTestContainer(containerUuid, c => c.children.Add(testResult.uuid));
- return StartTestCase(testResult);
- }
-
public virtual AllureLifecycle StartTestCase(TestResult testResult)
{
var container = this.storage.CurrentTestContainerOrNull;
@@ -336,19 +241,6 @@ public virtual AllureLifecycle StartTestCase(TestResult testResult)
return this;
}
- public virtual AllureLifecycle UpdateTestCase(
- string uuid,
- Action update
- )
- {
- var testResult = this.storage.Get(uuid);
- lock (this.monitor)
- {
- update(testResult);
- }
- return this;
- }
-
public virtual AllureLifecycle UpdateTestCase(
Action update
)
@@ -372,9 +264,6 @@ Action beforeStop
public virtual AllureLifecycle StopTestCase() =>
this.UpdateTestCase(stopTestCase);
- public virtual AllureLifecycle StopTestCase(string uuid) =>
- this.UpdateTestCase(uuid, stopTestCase);
-
public virtual AllureLifecycle WriteTestCase()
{
var testResult = this.storage.CurrentTest;
@@ -383,14 +272,6 @@ public virtual AllureLifecycle WriteTestCase()
return this;
}
- public virtual AllureLifecycle WriteTestCase(string uuid)
- {
- var testResult = this.storage.Get(uuid);
- this.storage.RemoveTestCase(uuid);
- this.writer.Write(testResult);
- return this;
- }
-
#endregion
#region Step
@@ -408,28 +289,6 @@ public virtual AllureLifecycle StartStep(StepResult result)
return this;
}
- public virtual AllureLifecycle StartStep(StepResult result, out string uuid)
- {
- uuid = CreateUuid();
- StartStep(this.storage.CurrentStepContainer, uuid, result);
- return this;
- }
-
- public virtual AllureLifecycle StartStep(
- string uuid,
- StepResult result
- ) => this.StartStep(this.storage.CurrentStepContainer, uuid, result);
-
- public virtual AllureLifecycle StartStep(
- string parentUuid,
- string uuid,
- StepResult stepResult
- ) => this.StartStep(
- this.storage.Get(parentUuid),
- uuid,
- stepResult
- );
-
public virtual AllureLifecycle UpdateStep(Action update)
{
var stepResult = this.storage.CurrentStep;
@@ -440,33 +299,12 @@ public virtual AllureLifecycle UpdateStep(Action update)
return this;
}
- public virtual AllureLifecycle UpdateStep(string uuid, Action update)
- {
- var stepResult = storage.Get(uuid);
- lock (this.monitor)
- {
- update.Invoke(stepResult);
- }
- return this;
- }
-
public virtual AllureLifecycle StopStep(Action beforeStop)
{
this.UpdateStep(beforeStop);
return this.StopStep();
}
- public virtual AllureLifecycle StopStep(string uuid)
- {
- this.UpdateStep(uuid, step =>
- {
- step.stage = Stage.finished;
- step.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- });
- this.storage.RemoveStep(uuid);
- return this;
- }
-
public virtual AllureLifecycle StopStep()
{
this.UpdateStep(step =>
@@ -483,16 +321,25 @@ public virtual AllureLifecycle StopStep()
#region Attachment
// TODO: read file in background thread
- public virtual AllureLifecycle AddAttachment(string name, string type, string path)
+ public virtual AllureLifecycle AddAttachment(
+ string name,
+ string type,
+ string path
+ )
{
var fileExtension = new FileInfo(path).Extension;
return AddAttachment(name, type, File.ReadAllBytes(path), fileExtension);
}
- public virtual AllureLifecycle AddAttachment(string name, string type, byte[] content,
- string fileExtension = "")
+ public virtual AllureLifecycle AddAttachment(
+ string name,
+ string type,
+ byte[] content,
+ string fileExtension = ""
+ )
{
- var source = $"{CreateUuid()}{AllureConstants.ATTACHMENT_FILE_SUFFIX}{fileExtension}";
+ var suffix = AllureConstants.ATTACHMENT_FILE_SUFFIX;
+ var source = $"{CreateUuid()}{suffix}{fileExtension}";
var attachment = new Attachment
{
name = name,
@@ -508,7 +355,10 @@ public virtual AllureLifecycle AddAttachment(string name, string type, byte[] co
return this;
}
- public virtual AllureLifecycle AddAttachment(string path, string? name = null)
+ public virtual AllureLifecycle AddAttachment(
+ string path,
+ string? name = null
+ )
{
name ??= Path.GetFileName(path);
var type = MimeTypesMap.GetMimeType(path);
@@ -524,17 +374,6 @@ public virtual void CleanupResultDirectory()
writer.CleanUp();
}
- public virtual AllureLifecycle AddScreenDiff(string testCaseUuid, string expectedPng, string actualPng,
- string diffPng)
- {
- AddAttachment(expectedPng, "expected")
- .AddAttachment(actualPng, "actual")
- .AddAttachment(diffPng, "diff")
- .UpdateTestCase(testCaseUuid, x => x.labels.Add(Label.TestType("screenshotDiff")));
-
- return this;
- }
-
public virtual AllureLifecycle AddScreenDiff(
string expectedPng,
string actualPng,
@@ -555,20 +394,33 @@ string diffPng
private static JObject GetConfiguration()
{
- var jsonConfigPath = Environment.GetEnvironmentVariable(AllureConstants.ALLURE_CONFIG_ENV_VARIABLE);
+ var configEnvVarName = AllureConstants.ALLURE_CONFIG_ENV_VARIABLE;
+ var jsonConfigPath = Environment.GetEnvironmentVariable(
+ configEnvVarName
+ );
if (jsonConfigPath != null && !File.Exists(jsonConfigPath))
+ {
throw new FileNotFoundException(
- $"Couldn't find '{jsonConfigPath}' specified in {AllureConstants.ALLURE_CONFIG_ENV_VARIABLE} environment variable");
+ $"Couldn't find '{jsonConfigPath}' specified " +
+ $"in {configEnvVarName} environment variable"
+ );
+ }
if (File.Exists(jsonConfigPath))
+ {
return JObject.Parse(File.ReadAllText(jsonConfigPath));
+ }
- var defaultJsonConfigPath =
- Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AllureConstants.CONFIG_FILENAME);
+ var defaultJsonConfigPath = Path.Combine(
+ AppDomain.CurrentDomain.BaseDirectory,
+ AllureConstants.CONFIG_FILENAME
+ );
if (File.Exists(defaultJsonConfigPath))
+ {
return JObject.Parse(File.ReadAllText(defaultJsonConfigPath));
+ }
return JObject.Parse("{}");
}
@@ -621,5 +473,354 @@ static string CreateUuid() =>
Guid.NewGuid().ToString("N");
#endregion
+
+ #region Obsolete
+
+ [Obsolete(
+ "This property is a rudimentary part of the API. It has no " +
+ "effect and will be removed soon"
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static Func? CurrentTestIdGetter { get; set; }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartTestContainer(
+ string parentUuid,
+ TestResultContainer container
+ )
+ {
+ UpdateTestContainer(parentUuid, c => c.children.Add(container.uuid));
+ StartTestContainer(container);
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle UpdateTestContainer(
+ string uuid,
+ Action update
+ )
+ {
+ var container = this.storage.Get(uuid);
+ lock (this.monitor)
+ {
+ update.Invoke(container);
+ }
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StopTestContainer(string uuid)
+ {
+ UpdateTestContainer(uuid, stopContainer);
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle WriteTestContainer(string uuid)
+ {
+ var container = this.storage.Get(uuid);
+ this.storage.RemoveTestContainer(uuid);
+ this.writer.Write(container);
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartBeforeFixture(
+ FixtureResult result,
+ out string uuid
+ )
+ {
+ uuid = CreateUuid();
+ StartBeforeFixture(uuid, result);
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartBeforeFixture(
+ string uuid,
+ FixtureResult result
+ )
+ {
+ UpdateTestContainer(container => container.befores.Add(result));
+ StartFixture(uuid, result);
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartBeforeFixture(
+ string parentUuid,
+ FixtureResult result,
+ out string uuid
+ )
+ {
+ uuid = CreateUuid();
+ StartBeforeFixture(parentUuid, uuid, result);
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartBeforeFixture(
+ string parentUuid,
+ string uuid,
+ FixtureResult result
+ )
+ {
+ UpdateTestContainer(parentUuid, container => container.befores.Add(result));
+ StartFixture(uuid, result);
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartAfterFixture(
+ string parentUuid,
+ FixtureResult result,
+ out string uuid
+ )
+ {
+ uuid = CreateUuid();
+ StartAfterFixture(parentUuid, uuid, result);
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartAfterFixture(
+ string parentUuid,
+ string uuid,
+ FixtureResult result
+ )
+ {
+ UpdateTestContainer(
+ parentUuid,
+ container => container.afters.Add(result)
+ );
+ StartFixture(uuid, result);
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle UpdateFixture(
+ string uuid,
+ Action update
+ )
+ {
+ var fixture = this.storage.Get(uuid);
+ lock (this.monitor)
+ {
+ update.Invoke(fixture);
+ }
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StopFixture(string uuid)
+ {
+ this.UpdateFixture(fixture =>
+ {
+ fixture.stage = Stage.finished;
+ fixture.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ });
+ this.storage.RemoveFixture(uuid);
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartTestCase(
+ string containerUuid,
+ TestResult testResult
+ )
+ {
+ UpdateTestContainer(
+ containerUuid,
+ c => c.children.Add(testResult.uuid)
+ );
+ return StartTestCase(testResult);
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle UpdateTestCase(
+ string uuid,
+ Action update
+ )
+ {
+ var testResult = this.storage.Get(uuid);
+ lock (this.monitor)
+ {
+ update(testResult);
+ }
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StopTestCase(string uuid) =>
+ this.UpdateTestCase(uuid, stopTestCase);
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle WriteTestCase(string uuid)
+ {
+ var testResult = this.storage.Get(uuid);
+ this.storage.RemoveTestCase(uuid);
+ this.writer.Write(testResult);
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartStep(
+ StepResult result,
+ out string uuid
+ )
+ {
+ uuid = CreateUuid();
+ StartStep(this.storage.CurrentStepContainer, uuid, result);
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartStep(
+ string uuid,
+ StepResult result
+ ) => this.StartStep(this.storage.CurrentStepContainer, uuid, result);
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartStep(
+ string parentUuid,
+ string uuid,
+ StepResult stepResult
+ ) => this.StartStep(
+ this.storage.Get(parentUuid),
+ uuid,
+ stepResult
+ );
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle UpdateStep(
+ string uuid,
+ Action update
+ )
+ {
+ var stepResult = storage.Get(uuid);
+ lock (this.monitor)
+ {
+ update.Invoke(stepResult);
+ }
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StopStep(string uuid)
+ {
+ this.UpdateStep(uuid, step =>
+ {
+ step.stage = Stage.finished;
+ step.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ });
+ this.storage.RemoveStep(uuid);
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle AddScreenDiff(
+ string testCaseUuid,
+ string expectedPng,
+ string actualPng,
+ string diffPng
+ )
+ {
+ AddAttachment(expectedPng, "expected")
+ .AddAttachment(actualPng, "actual")
+ .AddAttachment(diffPng, "diff")
+ .UpdateTestCase(testCaseUuid, x => x.labels.Add(Label.TestType("screenshotDiff")));
+
+ return this;
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/Allure.Net.Commons/Steps/CoreStepsHelper.cs b/Allure.Net.Commons/Steps/CoreStepsHelper.cs
index 6a51100e..93e28087 100644
--- a/Allure.Net.Commons/Steps/CoreStepsHelper.cs
+++ b/Allure.Net.Commons/Steps/CoreStepsHelper.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using Allure.Net.Commons.Storage;
@@ -9,13 +10,12 @@ public class CoreStepsHelper
{
public static IStepLogger StepLogger { get; set; }
- private static readonly AsyncLocal TestResultAccessorAsyncLocal = new();
-
- public static ITestResultAccessor TestResultAccessor
- {
- get => TestResultAccessorAsyncLocal.Value;
- set => TestResultAccessorAsyncLocal.Value = value;
- }
+ [Obsolete(
+ "This property is a rudimentary part of the API. It has no " +
+ "effect and will be removed soon"
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static ITestResultAccessor TestResultAccessor { get; set; }
#region Fixtures
diff --git a/Allure.Net.Commons/Storage/AllureStorage.cs b/Allure.Net.Commons/Storage/AllureStorage.cs
index 042d824f..3d684bf7 100644
--- a/Allure.Net.Commons/Storage/AllureStorage.cs
+++ b/Allure.Net.Commons/Storage/AllureStorage.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using System.Threading;
@@ -64,89 +65,35 @@ public void PutTestContainer(TestResultContainer container) =>
c => c.WithContainer(container)
);
- public void PutTestContainer(
- string uuid,
- TestResultContainer container
- ) => this.PutAndUpdateContext(
- uuid,
- container,
- c => c.WithContainer(container)
- );
-
public void RemoveTestContainer() => this.UpdateContext(
c => c.WithNoLastContainer()
);
- public void RemoveTestContainer(string uuid) =>
- this.RemoveAndUpdateContext(
- uuid,
- c => ContextWithNoContainer(c, uuid)
- );
-
public void PutFixture(FixtureResult fixture) =>
this.UpdateContext(c => c.WithFixtureContext(fixture));
- public void PutFixture(string uuid, FixtureResult fixture) =>
- this.PutAndUpdateContext(
- uuid,
- fixture,
- c => c.WithFixtureContext(fixture)
- );
-
public void RemoveFixture() =>
this.UpdateContext(c => c.WithNoFixtureContext());
- public void RemoveFixture(string uuid)
- => this.RemoveAndUpdateContext(
- uuid,
- c => c.WithNoFixtureContext()
- );
-
public void PutTestCase(TestResult testResult) =>
this.UpdateContext(
c => c.WithTestContext(testResult)
);
- public void PutTestCase(string uuid, TestResult testResult) =>
- this.PutAndUpdateContext(
- uuid,
- testResult,
- c => c.WithTestContext(testResult)
- );
-
public void RemoveTestCase() =>
this.UpdateContext(
c => c.WithNoTestContext()
);
- public void RemoveTestCase(string uuid) =>
- this.RemoveAndUpdateContext(
- uuid,
- c => c.WithNoTestContext()
- );
-
public void PutStep(StepResult stepResult) =>
this.UpdateContext(
c => c.WithStep(stepResult)
);
- public void PutStep(string uuid, StepResult stepResult) =>
- this.PutAndUpdateContext(
- uuid,
- stepResult,
- c => c.WithStep(stepResult)
- );
-
public void RemoveStep() => this.UpdateContext(
c => c.WithNoLastStep()
);
- public void RemoveStep(string uuid) =>
- this.RemoveAndUpdateContext(
- uuid,
- c => this.ContextWithNoStep(c, uuid)
- );
-
void PutAndUpdateContext(
string uuid,
T value,
@@ -217,5 +164,103 @@ AllureContext ContextWithNoStep(AllureContext context, string uuid)
}
return context;
}
+
+ #region Obsolete
+
+ [Obsolete(
+ "Storage methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void PutTestContainer(
+ string uuid,
+ TestResultContainer container
+ ) => this.PutAndUpdateContext(
+ uuid,
+ container,
+ c => c.WithContainer(container)
+ );
+
+ [Obsolete(
+ "Storage methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void RemoveTestContainer(string uuid) =>
+ this.RemoveAndUpdateContext(
+ uuid,
+ c => ContextWithNoContainer(c, uuid)
+ );
+
+ [Obsolete(
+ "Storage methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void PutFixture(string uuid, FixtureResult fixture) =>
+ this.PutAndUpdateContext(
+ uuid,
+ fixture,
+ c => c.WithFixtureContext(fixture)
+ );
+
+ [Obsolete(
+ "Storage methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void RemoveFixture(string uuid)
+ => this.RemoveAndUpdateContext(
+ uuid,
+ c => c.WithNoFixtureContext()
+ );
+
+ [Obsolete(
+ "Storage methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void PutTestCase(string uuid, TestResult testResult) =>
+ this.PutAndUpdateContext(
+ uuid,
+ testResult,
+ c => c.WithTestContext(testResult)
+ );
+
+ [Obsolete(
+ "Storage methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void RemoveTestCase(string uuid) =>
+ this.RemoveAndUpdateContext(
+ uuid,
+ c => c.WithNoTestContext()
+ );
+
+ [Obsolete(
+ "Storage methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void PutStep(string uuid, StepResult stepResult) =>
+ this.PutAndUpdateContext(
+ uuid,
+ stepResult,
+ c => c.WithStep(stepResult)
+ );
+
+ [Obsolete(
+ "Storage methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void RemoveStep(string uuid) =>
+ this.RemoveAndUpdateContext(
+ uuid,
+ c => this.ContextWithNoStep(c, uuid)
+ );
+
+ #endregion
}
}
\ No newline at end of file
From 6fd9380d183ef5516025b89a9f4fede7880eddbc Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Fri, 28 Jul 2023 12:12:01 +0700
Subject: [PATCH 11/24] Minor changes in context and lifecycle
- Improve error messages and doc comments
- Add DebuggerDisplay for context
- Uuid in string representation of context if names are empty
- Old storage uuid-based methods obsoleted
- Make RunInContext return modified context
- Replace unsupported container nesting with test nested
into each container in chain
- Container and test starting now add objects into storage by
uuids (transition period)
---
.../AllureContextTests.cs | 62 ++--
.../AllureLifeCycleTest.cs | 18 +-
Allure.Net.Commons/AllureLifecycle.cs | 333 +++++++++++++-----
Allure.Net.Commons/Storage/AllureContext.cs | 64 ++--
Allure.Net.Commons/Storage/AllureStorage.cs | 169 +++++----
5 files changed, 432 insertions(+), 214 deletions(-)
diff --git a/Allure.Net.Commons.Tests/AllureContextTests.cs b/Allure.Net.Commons.Tests/AllureContextTests.cs
index 668668c5..645bed5d 100644
--- a/Allure.Net.Commons.Tests/AllureContextTests.cs
+++ b/Allure.Net.Commons.Tests/AllureContextTests.cs
@@ -1,4 +1,5 @@
-using Allure.Net.Commons.Storage;
+using System;
+using Allure.Net.Commons.Storage;
using NUnit.Framework;
namespace Allure.Net.Commons.Tests
@@ -15,9 +16,11 @@ public void TestEmptyContext()
Assert.That(ctx.TestContext, Is.Null);
Assert.That(ctx.StepContext, Is.Empty);
Assert.That(ctx.HasContainer, Is.False);
+ Assert.That(ctx.ContainerContextDepth, Is.Zero);
Assert.That(ctx.HasFixture, Is.False);
Assert.That(ctx.HasTest, Is.False);
Assert.That(ctx.HasStep, Is.False);
+ Assert.That(ctx.StepContextDepth, Is.Zero);
Assert.That(
() => ctx.CurrentContainer,
@@ -73,8 +76,8 @@ public void CanNotAddContainerIfTestIsSet()
Assert.That(
() => ctx.WithContainer(new()),
Throws.InvalidOperationException.With.Message.EqualTo(
- "Unable to change a container context because a test " +
- "context is active."
+ "Unable to change the container context because the " +
+ "test context is active."
)
);
}
@@ -89,8 +92,8 @@ public void CanNotAddContainerIfFixtureIsSet()
Assert.That(
() => ctx.WithContainer(new()),
Throws.InvalidOperationException.With.Message.EqualTo(
- "Unable to change a container context because a fixture " +
- "context is active."
+ "Unable to change the container context because the " +
+ "fixture context is active."
)
);
}
@@ -105,8 +108,8 @@ public void CanNotRemoveContainerIfTestIsSet()
Assert.That(
ctx.WithNoLastContainer,
Throws.InvalidOperationException.With.Message.EqualTo(
- "Unable to change a container context because a test " +
- "context is active."
+ "Unable to change the container context because the " +
+ "test context is active."
)
);
}
@@ -147,6 +150,7 @@ public void OneContainerInContainerContext()
var ctx = new AllureContext().WithContainer(container);
Assert.That(ctx.HasContainer, Is.True);
+ Assert.That(ctx.ContainerContextDepth, Is.EqualTo(1));
Assert.That(ctx.ContainerContext, Is.EqualTo(new[] { container }));
Assert.That(ctx.CurrentContainer, Is.SameAs(container));
}
@@ -165,6 +169,7 @@ public void SecondContainerIsPushedInFront()
ctx.ContainerContext,
Is.EqualTo(new[] { container2, container1 })
);
+ Assert.That(ctx.ContainerContextDepth, Is.EqualTo(2));
Assert.That(ctx.CurrentContainer, Is.SameAs(container2));
}
@@ -176,8 +181,8 @@ public void CanNotRemoveContainerIfNoneExist()
Assert.That(
ctx.WithNoLastContainer,
Throws.InvalidOperationException.With.Message.EqualTo(
- "Unable to deactivate a container context " +
- "because it's inactive."
+ "Unable to deactivate the container context because " +
+ "it is not active."
)
);
}
@@ -190,6 +195,7 @@ public void LatestContainerCanBeRemoved()
.WithNoLastContainer();
Assert.That(ctx.HasContainer, Is.False);
+ Assert.That(ctx.ContainerContextDepth, Is.Zero);
Assert.That(ctx.ContainerContext, Is.Empty);
}
@@ -204,6 +210,7 @@ public void IfContainerIsRemovedThePreviousOneBecomesActive()
Assert.That(ctx.ContainerContext, Is.EqualTo(new[] { container }));
Assert.That(ctx.CurrentContainer, Is.SameAs(container));
+ Assert.That(ctx.ContainerContextDepth, Is.EqualTo(1));
}
[Test]
@@ -217,8 +224,8 @@ public void FixtureContextRequiresContainer()
() => ctx.WithFixtureContext(new()),
Throws.InvalidOperationException
.With.Message.EqualTo(
- "Unable to activate a fixture context because a " +
- "container context is inactive."
+ "Unable to activate the fixture context because " +
+ "the container context is not active."
)
);
}
@@ -259,8 +266,8 @@ public void CanNotRemoveContainerIfFixtureIsSet()
Assert.That(
ctx.WithNoLastContainer,
Throws.InvalidOperationException.With.Message.EqualTo(
- "Unable to change a container context because " +
- "a fixture context is active."
+ "Unable to change the container context because the " +
+ "fixture context is active."
)
);
}
@@ -278,8 +285,8 @@ public void FixturesCanNotBeNested()
() => ctx.WithFixtureContext(new()),
Throws.InvalidOperationException
.With.Message.EqualTo(
- "Unable to activate a fixture context because " +
- "another fixture context is active."
+ "Unable to activate the fixture context because " +
+ "it's already active."
)
);
}
@@ -306,8 +313,8 @@ public void TestsCanNotBeNested()
() => ctx.WithTestContext(new()),
Throws.InvalidOperationException
.With.Message.EqualTo(
- "Unable to activate a test context because another " +
- "test context is active."
+ "Unable to activate the test context because " +
+ "it is already active."
)
);
}
@@ -322,8 +329,8 @@ public void CanNotSetTestContextIfFixtureContextIsActive()
Assert.That(
() => ctx.WithTestContext(new()),
Throws.InvalidOperationException.With.Message.EqualTo(
- "Unable to activate a test context because a fixture " +
- "context is active."
+ "Unable to activate the test context because the " +
+ "fixture context is active."
)
);
}
@@ -393,7 +400,7 @@ public void StepCanNotBeAddedIfNoTestOrFixtureExists()
Assert.That(
() => ctx.WithStep(new()),
Throws.InvalidOperationException.With.Message.EqualTo(
- "Unable to activate a step context because neither " +
+ "Unable to activate the step context because neither " +
"test, nor fixture context is active."
)
);
@@ -407,8 +414,8 @@ public void StepCanNotBeRemovedIfNoStepExists()
Assert.That(
() => ctx.WithNoLastStep(),
Throws.InvalidOperationException.With.Message.EqualTo(
- "Unable to deactivate a step context because it's " +
- "already inactive."
+ "Unable to deactivate the step context because it " +
+ "isn't active."
)
);
}
@@ -424,6 +431,7 @@ public void StepCanBeAddedIfFixtureExists()
Assert.That(ctx.HasStep, Is.True);
Assert.That(ctx.StepContext, Is.EqualTo(new[] { step }));
+ Assert.That(ctx.StepContextDepth, Is.EqualTo(1));
Assert.That(ctx.CurrentStepContainer, Is.SameAs(step));
}
@@ -452,6 +460,7 @@ public void TwoStepsCanBeAdded()
.WithStep(step2);
Assert.That(ctx.StepContext, Is.EqualTo(new[] { step2, step1 }));
+ Assert.That(ctx.StepContextDepth, Is.EqualTo(2));
Assert.That(ctx.CurrentStep, Is.SameAs(step2));
Assert.That(ctx.CurrentStepContainer, Is.SameAs(step2));
}
@@ -469,6 +478,7 @@ public void RemovingStepRestoresPreviousStepAsStepContainer()
Assert.That(ctx.StepContext, Is.EqualTo(new[] { step1 }));
Assert.That(ctx.CurrentStep, Is.SameAs(step1));
+ Assert.That(ctx.StepContextDepth, Is.EqualTo(1));
Assert.That(ctx.CurrentStepContainer, Is.SameAs(step1));
}
@@ -483,6 +493,7 @@ public void RemovingTheOnlyStepRestoresTestAsStepContainer()
Assert.That(ctx.HasStep, Is.False);
Assert.That(ctx.StepContext, Is.Empty);
+ Assert.That(ctx.StepContextDepth, Is.Zero);
Assert.That(ctx.CurrentStepContainer, Is.SameAs(test));
}
@@ -589,6 +600,13 @@ public void ContextToString()
.ToString(),
Is.EqualTo("AllureContext { Containers = [], Fixture = null, Test = t, Steps = [s2 <- s1] }")
);
+ Assert.That(
+ new AllureContext()
+ .WithContainer(new() { uuid = "c" })
+ .WithTestContext(new() { uuid = "t" })
+ .ToString(),
+ Is.EqualTo("AllureContext { Containers = [c], Fixture = null, Test = t, Steps = [] }")
+ );
}
}
}
diff --git a/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs b/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
index c0394bad..a8df397d 100644
--- a/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
+++ b/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
@@ -21,19 +21,6 @@ public void ShouldSetDefaultStateAsNone()
Assert.AreEqual(Status.none, new TestResult().status);
}
- [Test]
- public void Way()
- {
- AsyncLocal myContext = new();
- myContext.Value = "Outer";
- Parallel.For(0, 2, i =>
- {
- Console.WriteLine(myContext.Value);
- myContext.Value = $"Inner {i}";
- });
- Console.WriteLine(myContext.Value);
- }
-
[Test, Description("Integration Test")]
public void IntegrationTest()
{
@@ -155,7 +142,7 @@ public async Task ContextCapturingTest()
{
var writer = new InMemoryResultsWriter();
var lifecycle = new AllureLifecycle(_ => writer);
- AllureContext context = null;
+ AllureContext context = null, modifiedContext = null;
await Task.Factory.StartNew(() =>
{
lifecycle.StartTestCase(new()
@@ -164,13 +151,14 @@ await Task.Factory.StartNew(() =>
});
context = lifecycle.Context;
});
- lifecycle.RunInContext(context, () =>
+ modifiedContext = lifecycle.RunInContext(context, () =>
{
lifecycle.StopTestCase();
lifecycle.WriteTestCase();
});
Assert.That(writer.testResults, Is.Not.Empty);
+ Assert.That(modifiedContext.HasTest, Is.False);
}
[Test]
diff --git a/Allure.Net.Commons/AllureLifecycle.cs b/Allure.Net.Commons/AllureLifecycle.cs
index afaacea8..1e15dbfe 100644
--- a/Allure.Net.Commons/AllureLifecycle.cs
+++ b/Allure.Net.Commons/AllureLifecycle.cs
@@ -16,6 +16,18 @@
namespace Allure.Net.Commons
{
+ ///
+ /// A facade that allows to control the Allure context, set up allure model
+ /// objects and emit output files.
+ ///
+ ///
+ /// This class is primarily intended to be used by a test framework
+ /// integration. We don't advice to use it from test code unless strictly
+ /// necessary.
+ /// NOTE: Modifications of the Allure context persist until either some
+ /// method has affect them, or the execution context is restored to the
+ /// point beyond the call that had introduced them.
+ ///
public class AllureLifecycle
{
private readonly Dictionary typeFormatters = new();
@@ -32,16 +44,12 @@ public class AllureLifecycle
/// Protects mutations of shared allure model objects against data
/// races that may otherwise occur because of multithreaded access.
///
- readonly object monitor = new();
+ readonly object modelMonitor = new();
///
- /// Captures the current context of Allure's execution.
+ /// Captures the current value of Allure context.
///
- ///
- /// This property is intended to be used by Allure integrations with
- /// test frameworks, not by end user's code.
- ///
public AllureContext Context
{
get => this.storage.CurrentContext;
@@ -49,35 +57,34 @@ public AllureContext Context
}
///
- /// Runs the specified code in the specified context restoring it
- /// before returning. Use this method if you need to access the context
- /// somewhere outside the async execution context the allure context
- /// has been set in.
+ /// Binds the provided value as the current Allure context and executes
+ /// the specified function. The context is then restored to the initial
+ /// value. This allows the Allure context to bypass .NET execution
+ /// context boundaries.
///
- ///
- /// This method is intended to be used by Allure integrations with
- /// test frameworks, not by end user's code.
- ///
///
/// A context that was previously captured with .
+ /// If it is null, the code is executed in the current context.
///
/// A code to run.
- public void RunInContext(
+ /// The context after the code is executed.
+ public AllureContext RunInContext(
AllureContext? context,
- Action action
+ Action action
)
{
- if (context is null || context == this.Context)
+ if (context is null)
{
- action(this.Context);
- return;
+ action();
+ return this.Context;
}
var originalContext = this.Context;
try
{
this.Context = context;
- action(context);
+ action();
+ return this.Context;
}
finally
{
@@ -127,45 +134,73 @@ private void AddTypeFormatterImpl(Type type, ITypeFormatter formatter) =>
#region TestContainer
+ ///
+ /// Starts a new test container and pushes it into the container
+ /// context making the container context active. The container becomes
+ /// the current one in the current execution context.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Can't be called if the fixture or the test context is active.
+ ///
+ /// A new test container to start.
+ ///
public virtual AllureLifecycle StartTestContainer(
TestResultContainer container
)
{
container.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- var parent = this.storage.CurrentTestContainerOrNull;
- if (parent is not null)
- {
- lock (this.monitor)
- {
- parent.children.Add(container.uuid);
- }
- }
- storage.PutTestContainer(container);
+ this.storage.PutTestContainer(container.uuid, container);
return this;
}
+ ///
+ /// Applies the specified update function to the current test container.
+ ///
+ ///
+ /// Requires the container context to be active.
+ ///
+ ///
public virtual AllureLifecycle UpdateTestContainer(
Action update
)
{
var container = this.storage.CurrentTestContainer;
- lock (this.monitor)
+ lock (this.modelMonitor)
{
update.Invoke(container);
}
return this;
}
+ ///
+ /// Stops the current test container.
+ ///
+ ///
+ /// Requires the container context to be active.
+ ///
+ ///
public virtual AllureLifecycle StopTestContainer()
{
UpdateTestContainer(stopContainer);
return this;
}
+ ///
+ /// Writes the current test container and removes it from the context.
+ /// If there are another test containers in the context, the most
+ /// recently started one becomes the current container in the current
+ /// execution context. Otherwise the container context is deactivated.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires the container context to be active.
+ ///
+ ///
public virtual AllureLifecycle WriteTestContainer()
{
var container = this.storage.CurrentTestContainer;
- this.storage.RemoveTestContainer();
+ this.storage.RemoveTestContainer(container.uuid);
this.writer.Write(container);
return this;
}
@@ -174,13 +209,35 @@ public virtual AllureLifecycle WriteTestContainer()
#region Fixture
+ ///
+ /// Starts a new before fixture and activates the fixture context with
+ /// it. The fixture is set as the current one in the current execution
+ /// context. Does nothing if the fixture context is already active.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires the container context to be active.
+ ///
+ /// A new fixture.
+ ///
public virtual AllureLifecycle StartBeforeFixture(FixtureResult result)
{
- UpdateTestContainer(container => container.befores.Add(result));
+ this.UpdateTestContainer(container => container.befores.Add(result));
this.StartFixture(result);
return this;
}
+ ///
+ /// Starts a new after fixture and activates the fixture context with
+ /// it. The fixture is set as the current one in the current execution
+ /// context. Does nothing if the fixture context is already active.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires the container context to be active.
+ ///
+ /// A new fixture.
+ ///
public virtual AllureLifecycle StartAfterFixture(FixtureResult result)
{
this.UpdateTestContainer(c => c.afters.Add(result));
@@ -188,18 +245,36 @@ public virtual AllureLifecycle StartAfterFixture(FixtureResult result)
return this;
}
+ ///
+ /// Applies the specified update function to the current fixture.
+ ///
+ ///
+ /// Requires the fixture context to be active.
+ ///
+ ///
public virtual AllureLifecycle UpdateFixture(
Action update
)
{
var fixture = this.storage.CurrentFixture;
- lock (this.monitor)
+ lock (this.modelMonitor)
{
update.Invoke(fixture);
}
return this;
}
+ ///
+ /// Stops the current fixture and deactivates the fixture context.
+ ///
+ ///
+ /// A function applied to the fixture result before it is stopped.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Required the fixture context to be active.
+ ///
+ ///
public virtual AllureLifecycle StopFixture(
Action beforeStop
)
@@ -208,6 +283,14 @@ Action beforeStop
return this.StopFixture();
}
+ ///
+ /// Stops the current fixture and deactivates the fixture context.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Required the fixture context to be active.
+ ///
+ ///
public virtual AllureLifecycle StopFixture()
{
this.UpdateFixture(fixture =>
@@ -223,36 +306,64 @@ public virtual AllureLifecycle StopFixture()
#region TestCase
+ ///
+ /// Starts a new test and activates the test context with it. The test
+ /// becomes the current one in the current execution context.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires the test context to be active.
+ ///
+ /// A new test case.
+ ///
public virtual AllureLifecycle StartTestCase(TestResult testResult)
{
- var container = this.storage.CurrentTestContainerOrNull;
- if (container is not null)
+ var uuid = testResult.uuid;
+ var containers = this.storage.CurrentContext.ContainerContext;
+ lock (this.modelMonitor)
{
- lock (this.monitor)
+ foreach (TestResultContainer container in containers)
{
- container.children.Add(testResult.uuid);
+ container.children.Add(uuid);
}
}
testResult.stage = Stage.running;
testResult.start = testResult.start == 0L
? DateTimeOffset.Now.ToUnixTimeMilliseconds()
: testResult.start;
- this.storage.PutTestCase(testResult);
+ this.storage.PutTestCase(uuid, testResult);
return this;
}
+ ///
+ /// Applies the specified update function to the current test.
+ ///
+ ///
+ /// Requires the test context to be active.
+ ///
+ ///
public virtual AllureLifecycle UpdateTestCase(
Action update
)
{
var testResult = this.storage.CurrentTest;
- lock (this.monitor)
+ lock (this.modelMonitor)
{
update(testResult);
}
return this;
}
+ ///
+ /// Stops the current test.
+ ///
+ ///
+ /// Requires the test context to be active.
+ ///
+ ///
+ /// A function applied to the test result before it is stopped.
+ ///
+ ///
public virtual AllureLifecycle StopTestCase(
Action beforeStop
) => this.UpdateTestCase(testResult =>
@@ -261,13 +372,34 @@ Action beforeStop
stopTestCase(testResult);
});
+ ///
+ /// Stops the current test.
+ ///
+ ///
+ /// Requires the test context to be active.
+ ///
+ ///
public virtual AllureLifecycle StopTestCase() =>
this.UpdateTestCase(stopTestCase);
+ ///
+ /// Writes the current test and removes it from the context. The test
+ /// context is then deactivated.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires the test context to be active.
+ ///
+ ///
public virtual AllureLifecycle WriteTestCase()
{
var testResult = this.storage.CurrentTest;
- this.storage.RemoveTestCase();
+ string uuid;
+ lock (this.modelMonitor)
+ {
+ uuid = testResult.uuid;
+ }
+ this.storage.RemoveTestCase(uuid);
this.writer.Write(testResult);
return this;
}
@@ -276,12 +408,23 @@ public virtual AllureLifecycle WriteTestCase()
#region Step
+ ///
+ /// Starts a new step and pushes it into the step context making the
+ /// step context active. The step becomes the current one in the
+ /// current execution context.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires either the fixture or the test context to be active.
+ ///
+ /// A new step.
+ ///
public virtual AllureLifecycle StartStep(StepResult result)
{
result.stage = Stage.running;
result.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
var parent = this.storage.CurrentStepContainer;
- lock (this.monitor)
+ lock (this.modelMonitor)
{
parent.steps.Add(result);
}
@@ -289,22 +432,54 @@ public virtual AllureLifecycle StartStep(StepResult result)
return this;
}
+ ///
+ /// Applies the specified update function to the current step.
+ ///
+ ///
+ /// Requires the step context to be active.
+ ///
+ ///
public virtual AllureLifecycle UpdateStep(Action update)
{
var stepResult = this.storage.CurrentStep;
- lock (this.monitor)
+ lock (this.modelMonitor)
{
update.Invoke(stepResult);
}
return this;
}
+ ///
+ /// Stops the current step and removes it from the context. If there
+ /// are another steps in the context, the most recently started one
+ /// becomes the current step in the current execution context.
+ /// Otherwise the step context is deactivated.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires the step context to be active.
+ ///
+ ///
+ /// A function that is applied to the step result before it is stopped.
+ ///
+ ///
public virtual AllureLifecycle StopStep(Action beforeStop)
{
this.UpdateStep(beforeStop);
return this.StopStep();
}
+ ///
+ /// Stops the current step and removes it from the context. If there
+ /// are another steps in the context, the most recently started one
+ /// becomes the current step in the current execution context.
+ /// Otherwise the step context is deactivated.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires the step context to be active.
+ ///
+ ///
public virtual AllureLifecycle StopStep()
{
this.UpdateStep(step =>
@@ -348,7 +523,7 @@ public virtual AllureLifecycle AddAttachment(
};
this.writer.Write(source, content);
var target = this.storage.CurrentStepContainer;
- lock (this.monitor)
+ lock (this.modelMonitor)
{
target.attachments.Add(attachment);
}
@@ -432,32 +607,6 @@ private void StartFixture(FixtureResult fixtureResult)
fixtureResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
}
- void StartFixture(string uuid, FixtureResult fixtureResult)
- {
- this.storage.PutFixture(uuid, fixtureResult);
- lock (this.monitor)
- {
- fixtureResult.stage = Stage.running;
- fixtureResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- }
- }
-
- AllureLifecycle StartStep(
- ExecutableItem parent,
- string uuid,
- StepResult stepResult
- )
- {
- stepResult.stage = Stage.running;
- stepResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- lock (this.monitor)
- {
- parent.steps.Add(stepResult);
- }
- this.storage.PutStep(uuid, stepResult);
- return this;
- }
-
static readonly Action stopContainer =
c => c.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
@@ -474,7 +623,7 @@ static string CreateUuid() =>
#endregion
- #region Obsolete
+ #region Obsoleted
[Obsolete(
"This property is a rudimentary part of the API. It has no " +
@@ -493,8 +642,9 @@ public virtual AllureLifecycle StartTestContainer(
TestResultContainer container
)
{
+ container.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
UpdateTestContainer(parentUuid, c => c.children.Add(container.uuid));
- StartTestContainer(container);
+ this.storage.PutTestContainer(container.uuid, container);
return this;
}
@@ -509,7 +659,7 @@ Action update
)
{
var container = this.storage.Get(uuid);
- lock (this.monitor)
+ lock (this.modelMonitor)
{
update.Invoke(container);
}
@@ -521,11 +671,8 @@ Action update
"their counterparts without uuids to manipulate the context."
)]
[EditorBrowsable(EditorBrowsableState.Never)]
- public virtual AllureLifecycle StopTestContainer(string uuid)
- {
+ public virtual AllureLifecycle StopTestContainer(string uuid) =>
UpdateTestContainer(uuid, stopContainer);
- return this;
- }
[Obsolete(
"Lifecycle methods with explicit uuids are obsolete. Use " +
@@ -648,7 +795,7 @@ Action update
)
{
var fixture = this.storage.Get(uuid);
- lock (this.monitor)
+ lock (this.modelMonitor)
{
update.Invoke(fixture);
}
@@ -699,7 +846,7 @@ Action update
)
{
var testResult = this.storage.Get(uuid);
- lock (this.monitor)
+ lock (this.modelMonitor)
{
update(testResult);
}
@@ -778,7 +925,7 @@ Action update
)
{
var stepResult = storage.Get(uuid);
- lock (this.monitor)
+ lock (this.modelMonitor)
{
update.Invoke(stepResult);
}
@@ -821,6 +968,34 @@ string diffPng
return this;
}
+ [Obsolete]
+ void StartFixture(string uuid, FixtureResult fixtureResult)
+ {
+ this.storage.PutFixture(uuid, fixtureResult);
+ lock (this.modelMonitor)
+ {
+ fixtureResult.stage = Stage.running;
+ fixtureResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ }
+ }
+
+ [Obsolete]
+ AllureLifecycle StartStep(
+ ExecutableItem parent,
+ string uuid,
+ StepResult stepResult
+ )
+ {
+ stepResult.stage = Stage.running;
+ stepResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ lock (this.modelMonitor)
+ {
+ parent.steps.Add(stepResult);
+ }
+ this.storage.PutStep(uuid, stepResult);
+ return this;
+ }
+
#endregion
}
}
\ No newline at end of file
diff --git a/Allure.Net.Commons/Storage/AllureContext.cs b/Allure.Net.Commons/Storage/AllureContext.cs
index 54157cfc..3ebb7df1 100644
--- a/Allure.Net.Commons/Storage/AllureContext.cs
+++ b/Allure.Net.Commons/Storage/AllureContext.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Immutable;
+using System.Diagnostics;
using System.Linq;
using System.Text;
@@ -18,6 +19,10 @@ namespace Allure.Net.Commons.Storage
/// between different tests and steps that may potentially be run
/// cuncurrently either by a test framework or by an end user.
///
+ [DebuggerDisplay(
+ "Containers = {ContainerContextDepth}, HasFixture = {HasFixture}, " +
+ "HasTest = {HasTest}, Steps = {StepContextDepth}"
+ )]
public record class AllureContext
{
///
@@ -25,6 +30,11 @@ public record class AllureContext
///
public bool HasContainer => !this.ContainerContext.IsEmpty;
+ ///
+ /// Returns the number of containers in the container context.
+ ///
+ public int ContainerContextDepth => this.ContainerContext.Count();
+
///
/// Returns true if a fixture context is active.
///
@@ -40,6 +50,11 @@ public record class AllureContext
///
public bool HasStep => !this.StepContext.IsEmpty;
+ ///
+ /// Returns the number of steps in the step context.
+ ///
+ public int StepContextDepth => this.StepContext.Count();
+
///
/// A stack of fixture containers affecting subsequent tests.
///
@@ -162,12 +177,18 @@ internal TestResultContainer CurrentContainer
"No fixture, test, or step context is active."
);
+ ///
+ /// Used by to serialize proeprties of the
+ /// context.
+ ///
protected virtual bool PrintMembers(StringBuilder stringBuilder)
{
var containers =
- RepresentStack(this.ContainerContext, c => c.name);
+ RepresentStack(this.ContainerContext, c => c.name ?? c.uuid);
var fixture = this.FixtureContext?.name ?? "null";
- var test = this.TestContext?.name ?? "null";
+ var test = this.TestContext?.name
+ ?? this.TestContext?.uuid
+ ?? "null";
var steps = RepresentStack(this.StepContext, s => s.name);
stringBuilder.AppendFormat("Containers = [{0}], ", containers);
@@ -225,9 +246,12 @@ this with
///
/// Creates a new with the active fixture
- /// context that is set to the specified fixture. Requires an active
- /// container context.
+ /// context that is set to the specified fixture. Requires the
+ /// container context to be active.
///
+ ///
+ /// Only one fixture context can be active at a time.
+ ///
///
/// A new fixture context.
///
@@ -340,8 +364,8 @@ this with
StepContext = this.HasStep
? this.StepContext.Pop()
: throw new InvalidOperationException(
- "Unable to deactivate a step context because it's " +
- "already inactive."
+ "Unable to deactivate the step context because it " +
+ "isn't active."
)
};
@@ -350,7 +374,7 @@ AllureContext ValidateContainerContextCanBeModified()
if (this.FixtureContext is not null)
{
throw new InvalidOperationException(
- "Unable to change a container context because a " +
+ "Unable to change the container context because the " +
"fixture context is active."
);
}
@@ -358,8 +382,8 @@ AllureContext ValidateContainerContextCanBeModified()
if (this.TestContext is not null)
{
throw new InvalidOperationException(
- "Unable to change a container context because a test " +
- "context is active."
+ "Unable to change the container context because the " +
+ "test context is active."
);
}
@@ -371,8 +395,8 @@ AllureContext ValidateContainerCanBeRemoved()
if (!this.HasContainer)
{
throw new InvalidOperationException(
- "Unable to deactivate a container context because it's " +
- "inactive."
+ "Unable to deactivate the container context because it " +
+ "is not active."
);
}
@@ -389,16 +413,16 @@ FixtureResult ValidateNewFixtureContext(FixtureResult fixture)
if (!this.HasContainer)
{
throw new InvalidOperationException(
- "Unable to activate a fixture context " +
- "because a container context is inactive."
+ "Unable to activate the fixture context " +
+ "because the container context is not active."
);
}
if (this.HasFixture)
{
throw new InvalidOperationException(
- "Unable to activate a fixture context " +
- "because another fixture context is active."
+ "Unable to activate the fixture context " +
+ "because it's already active."
);
}
@@ -410,16 +434,16 @@ TestResult ValidateNewTestContext(TestResult testResult)
if (this.HasFixture)
{
throw new InvalidOperationException(
- "Unable to activate a test context " +
- "because a fixture context is active."
+ "Unable to activate the test context " +
+ "because the fixture context is active."
);
}
if (this.HasTest)
{
throw new InvalidOperationException(
- "Unable to activate a test context " +
- "because another test context is active."
+ "Unable to activate the test context " +
+ "because it is already active."
);
}
@@ -431,7 +455,7 @@ StepResult ValidateNewStep(StepResult stepResult)
if (!this.HasTest && !this.HasFixture)
{
throw new InvalidOperationException(
- "Unable to activate a step context because neither " +
+ "Unable to activate the step context because neither " +
"test, nor fixture context is active."
);
}
diff --git a/Allure.Net.Commons/Storage/AllureStorage.cs b/Allure.Net.Commons/Storage/AllureStorage.cs
index 3d684bf7..26abafba 100644
--- a/Allure.Net.Commons/Storage/AllureStorage.cs
+++ b/Allure.Net.Commons/Storage/AllureStorage.cs
@@ -11,14 +11,15 @@ namespace Allure.Net.Commons.Storage
{
internal class AllureStorage
{
- readonly ConcurrentDictionary storage = new();
readonly AsyncLocal context = new();
public AllureContext CurrentContext
{
get => this.context.Value ??= new();
set => this.context.Value = value
- ?? throw new ArgumentNullException(nameof(CurrentContext));
+ ?? throw new ArgumentNullException(
+ nameof(this.CurrentContext)
+ );
}
public AllureStorage()
@@ -44,22 +45,6 @@ public AllureStorage()
public StepResult CurrentStep =>
this.CurrentContext.CurrentStep;
- public T Get(string uuid)
- {
- return (T) storage[uuid];
- }
-
- public T Put(string uuid, T item) where T: notnull
- {
- return (T) storage.GetOrAdd(uuid, item);
- }
-
- public T Remove(string uuid)
- {
- storage.TryRemove(uuid, out var value);
- return (T) value;
- }
-
public void PutTestContainer(TestResultContainer container) =>
this.UpdateContext(
c => c.WithContainer(container)
@@ -94,79 +79,35 @@ public void RemoveStep() => this.UpdateContext(
c => c.WithNoLastStep()
);
- void PutAndUpdateContext(
- string uuid,
- T value,
- Func updateFn
- ) where T : notnull
+ void UpdateContext(Func updateFn)
{
- this.Put(uuid, value);
- this.UpdateContext(updateFn);
+ this.CurrentContext = updateFn(this.CurrentContext);
}
- void RemoveAndUpdateContext(string uuid, Func updateFn)
- {
- this.UpdateContext(updateFn);
- this.Remove(uuid);
- }
+ #region Obsoleted
- void UpdateContext(Func updateFn)
+ [Obsolete]
+ readonly ConcurrentDictionary storage = new();
+
+ [Obsolete]
+ public T Get(string uuid)
{
- this.CurrentContext = updateFn(this.CurrentContext);
+ return (T)storage[uuid];
}
- static AllureContext ContextWithNoContainer(
- AllureContext context,
- string uuid
- )
+ [Obsolete]
+ public T Put(string uuid, T item) where T : notnull
{
- var containersToPushAgain = new Stack();
- while (context.CurrentContainer.uuid != uuid)
- {
- containersToPushAgain.Push(context.CurrentContainer);
- context = context.WithNoLastContainer();
- if (context.ContainerContext.IsEmpty)
- {
- throw new InvalidOperationException(
- $"Container {uuid} is not in the current context"
- );
- }
- }
- while (containersToPushAgain.Any())
- {
- context = context.WithContainer(
- containersToPushAgain.Pop()
- );
- }
- return context;
+ return (T)storage.GetOrAdd(uuid, item);
}
- AllureContext ContextWithNoStep(AllureContext context, string uuid)
+ [Obsolete]
+ public T Remove(string uuid)
{
- var stepResult = this.Get(uuid);
- var stepsToPushAgain = new Stack();
- while (!ReferenceEquals(context.CurrentStep, stepResult))
- {
- stepsToPushAgain.Push(context.CurrentStep);
- context = context.WithNoLastStep();
- if (context.StepContext.IsEmpty)
- {
- throw new InvalidOperationException(
- $"Step {stepResult.name} is not in the current context"
- );
- }
- }
- while (stepsToPushAgain.Any())
- {
- context = context.WithStep(
- stepsToPushAgain.Pop()
- );
- }
- return context;
+ storage.TryRemove(uuid, out var value);
+ return (T)value;
}
- #region Obsolete
-
[Obsolete(
"Storage methods with explicit uuids are obsolete. Use " +
"their counterparts without uuids to manipulate the context."
@@ -261,6 +202,78 @@ public void RemoveStep(string uuid) =>
c => this.ContextWithNoStep(c, uuid)
);
+ [Obsolete]
+ void PutAndUpdateContext(
+ string uuid,
+ T value,
+ Func updateFn
+ ) where T : notnull
+ {
+ this.Put(uuid, value);
+ this.UpdateContext(updateFn);
+ }
+
+ [Obsolete]
+ void RemoveAndUpdateContext(string uuid, Func updateFn)
+ {
+ this.UpdateContext(updateFn);
+ this.Remove(uuid);
+ }
+
+ [Obsolete]
+ static AllureContext ContextWithNoContainer(
+ AllureContext context,
+ string uuid
+ )
+ {
+ var containersToPushAgain = new Stack();
+ while (context.CurrentContainer.uuid != uuid)
+ {
+ containersToPushAgain.Push(context.CurrentContainer);
+ context = context.WithNoLastContainer();
+ if (context.ContainerContext.IsEmpty)
+ {
+ throw new InvalidOperationException(
+ $"Container {uuid} is not in the current context"
+ );
+ }
+ }
+ context = context.WithNoLastContainer();
+ while (containersToPushAgain.Any())
+ {
+ context = context.WithContainer(
+ containersToPushAgain.Pop()
+ );
+ }
+ return context;
+ }
+
+ [Obsolete]
+ AllureContext ContextWithNoStep(AllureContext context, string uuid)
+ {
+ var stepResult = this.Get(uuid);
+ var stepsToPushAgain = new Stack();
+ while (!ReferenceEquals(context.CurrentStep, stepResult))
+ {
+ stepsToPushAgain.Push(context.CurrentStep);
+ context = context.WithNoLastStep();
+ if (context.StepContext.IsEmpty)
+ {
+ throw new InvalidOperationException(
+ $"Step {stepResult.name} is not in the current context"
+ );
+ }
+ }
+ context = context.WithNoLastStep();
+ while (stepsToPushAgain.Any())
+ {
+ context = context.WithStep(
+ stepsToPushAgain.Pop()
+ );
+ }
+ return context;
+ }
+
#endregion
}
}
\ No newline at end of file
From 98d2d228b9d66fd727ae6e6bbc878df4def4557c Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Fri, 28 Jul 2023 12:23:00 +0700
Subject: [PATCH 12/24] Rewrite Allure.NUnit to support the new context scheme
---
Allure.NUnit.Examples/BaseTest.cs | 10 +-
.../AllureDisplayIgnoredAttribute.cs | 30 +--
.../Core/AllureExtendedConfiguration.cs | 8 +-
Allure.NUnit/Core/AllureExtensions.cs | 59 +++---
Allure.NUnit/Core/AllureNUnitAttribute.cs | 24 +--
Allure.NUnit/Core/AllureNUnitHelper.cs | 183 +++++++++++++-----
6 files changed, 196 insertions(+), 118 deletions(-)
diff --git a/Allure.NUnit.Examples/BaseTest.cs b/Allure.NUnit.Examples/BaseTest.cs
index 0687207d..e7e27a91 100644
--- a/Allure.NUnit.Examples/BaseTest.cs
+++ b/Allure.NUnit.Examples/BaseTest.cs
@@ -1,7 +1,5 @@
-using Allure.Net.Commons;
-using NUnit.Allure.Attributes;
+using NUnit.Allure.Attributes;
using NUnit.Allure.Core;
-using NUnit.Framework;
namespace Allure.NUnit.Examples
{
@@ -9,11 +7,5 @@ namespace Allure.NUnit.Examples
[AllureParentSuite("Root Suite")]
public class BaseTest
{
- [OneTimeSetUp]
- public void CleanupResultDirectory()
- {
- AllureExtensions.WrapSetUpTearDownParams(() => { AllureLifecycle.Instance.CleanupResultDirectory(); },
- "Clear Allure Results Directory");
- }
}
}
\ No newline at end of file
diff --git a/Allure.NUnit/Attributes/AllureDisplayIgnoredAttribute.cs b/Allure.NUnit/Attributes/AllureDisplayIgnoredAttribute.cs
index ebf5d5b1..fc055a1c 100644
--- a/Allure.NUnit/Attributes/AllureDisplayIgnoredAttribute.cs
+++ b/Allure.NUnit/Attributes/AllureDisplayIgnoredAttribute.cs
@@ -13,7 +13,6 @@ namespace NUnit.Allure.Attributes
public class AllureDisplayIgnoredAttribute : NUnitAttribute, ITestAction
{
private readonly string _suiteName;
- private string _ignoredContainerId;
public AllureDisplayIgnoredAttribute(string suiteNameForIgnoredTests = "Ignored")
{
@@ -22,13 +21,11 @@ public AllureDisplayIgnoredAttribute(string suiteNameForIgnoredTests = "Ignored"
public void BeforeTest(ITest suite)
{
- _ignoredContainerId = suite.Id + "-ignored";
- var fixture = new TestResultContainer
+ AllureLifecycle.Instance.StartTestContainer(new()
{
- uuid = _ignoredContainerId,
+ uuid = suite.Id + "-ignored",
name = suite.ClassName
- };
- AllureLifecycle.Instance.StartTestContainer(fixture);
+ });
}
public void AfterTest(ITest suite)
@@ -37,12 +34,19 @@ public void AfterTest(ITest suite)
if (suite.HasChildren)
{
var ignoredTests =
- GetAllTests(suite).Where(t => t.RunState == RunState.Ignored || t.RunState == RunState.Skipped);
+ GetAllTests(suite).Where(
+ t => t.RunState == RunState.Ignored
+ || t.RunState == RunState.Skipped
+ );
foreach (var test in ignoredTests)
{
- AllureLifecycle.Instance.UpdateTestContainer(_ignoredContainerId, t => t.children.Add(test.Id));
+ AllureLifecycle.Instance.UpdateTestContainer(
+ t => t.children.Add(test.Id)
+ );
- var reason = test.Properties.Get(PropertyNames.SkipReason).ToString();
+ var reason = test.Properties.Get(
+ PropertyNames.SkipReason
+ ).ToString();
var ignoredTestResult = new TestResult
{
@@ -66,12 +70,12 @@ public void AfterTest(ITest suite)
}
};
AllureLifecycle.Instance.StartTestCase(ignoredTestResult);
- AllureLifecycle.Instance.StopTestCase(ignoredTestResult.uuid);
- AllureLifecycle.Instance.WriteTestCase(ignoredTestResult.uuid);
+ AllureLifecycle.Instance.StopTestCase();
+ AllureLifecycle.Instance.WriteTestCase();
}
- AllureLifecycle.Instance.StopTestContainer(_ignoredContainerId);
- AllureLifecycle.Instance.WriteTestContainer(_ignoredContainerId);
+ AllureLifecycle.Instance.StopTestContainer();
+ AllureLifecycle.Instance.WriteTestContainer();
}
}
diff --git a/Allure.NUnit/Core/AllureExtendedConfiguration.cs b/Allure.NUnit/Core/AllureExtendedConfiguration.cs
index dc00efdf..84c72fb3 100644
--- a/Allure.NUnit/Core/AllureExtendedConfiguration.cs
+++ b/Allure.NUnit/Core/AllureExtendedConfiguration.cs
@@ -6,10 +6,14 @@ namespace NUnit.Allure.Core
{
internal class AllureExtendedConfiguration : AllureConfiguration
{
- public HashSet BrokenTestData { get; set; } = new HashSet();
+ public HashSet BrokenTestData { get; set; } = new();
[JsonConstructor]
- protected AllureExtendedConfiguration(string title, string directory, HashSet links) : base(title,
+ protected AllureExtendedConfiguration(
+ string title,
+ string directory,
+ HashSet links
+ ) : base(title,
directory, links)
{
}
diff --git a/Allure.NUnit/Core/AllureExtensions.cs b/Allure.NUnit/Core/AllureExtensions.cs
index 1d8a7dba..c149d88d 100644
--- a/Allure.NUnit/Core/AllureExtensions.cs
+++ b/Allure.NUnit/Core/AllureExtensions.cs
@@ -58,15 +58,22 @@ public static void WrapSetUpTearDownParams(Action action, string customName = ""
/// Wraps Action into AllureStep.
///
[Obsolete("Use [AllureStep] method attribute")]
- public static void WrapInStep(this AllureLifecycle lifecycle, Action action, string stepName = "", [CallerMemberName] string callerName = "")
+ public static void WrapInStep(
+ this AllureLifecycle lifecycle,
+ Action action,
+ string stepName = "",
+ [CallerMemberName] string callerName = ""
+ )
{
- if (string.IsNullOrEmpty(stepName)) stepName = callerName;
+ if (string.IsNullOrEmpty(stepName))
+ {
+ stepName = callerName;
+ }
- var id = Guid.NewGuid().ToString();
var stepResult = new StepResult {name = stepName};
try
{
- lifecycle.StartStep(id, stepResult);
+ lifecycle.StartStep(stepResult);
action.Invoke();
lifecycle.StopStep(step => stepResult.status = Status.passed);
}
@@ -88,14 +95,22 @@ public static void WrapInStep(this AllureLifecycle lifecycle, Action action, str
///
/// Wraps Func into AllureStep.
///
- public static T WrapInStep(this AllureLifecycle lifecycle, Func func, string stepName = "", [CallerMemberName] string callerName = "")
+ public static T WrapInStep(
+ this AllureLifecycle lifecycle,
+ Func func,
+ string stepName = "",
+ [CallerMemberName] string callerName = ""
+ )
{
- if (string.IsNullOrEmpty(stepName)) stepName = callerName;
- var id = Guid.NewGuid().ToString();
+ if (string.IsNullOrEmpty(stepName))
+ {
+ stepName = callerName;
+ }
+
var stepResult = new StepResult {name = stepName};
try
{
- lifecycle.StartStep(id, stepResult);
+ lifecycle.StartStep(stepResult);
var result = func.Invoke();
lifecycle.StopStep(step => stepResult.status = Status.passed);
return result;
@@ -125,13 +140,15 @@ public static async Task WrapInStepAsync(
[CallerMemberName] string callerName = ""
)
{
- if (string.IsNullOrEmpty(stepName)) stepName = callerName;
+ if (string.IsNullOrEmpty(stepName))
+ {
+ stepName = callerName;
+ }
- var id = Guid.NewGuid().ToString();
var stepResult = new StepResult { name = stepName };
try
{
- lifecycle.StartStep(id, stepResult);
+ lifecycle.StartStep(stepResult);
await action();
lifecycle.StopStep(step => stepResult.status = Status.passed);
}
@@ -160,12 +177,15 @@ public static async Task WrapInStepAsync(
[CallerMemberName] string callerName = ""
)
{
- if (string.IsNullOrEmpty(stepName)) stepName = callerName;
- var id = Guid.NewGuid().ToString();
+ if (string.IsNullOrEmpty(stepName))
+ {
+ stepName = callerName;
+ }
+
var stepResult = new StepResult { name = stepName };
try
{
- lifecycle.StartStep(id, stepResult);
+ lifecycle.StartStep(stepResult);
var result = await func();
lifecycle.StopStep(step => stepResult.status = Status.passed);
return result;
@@ -184,16 +204,5 @@ public static async Task WrapInStepAsync(
throw;
}
}
-
- ///
- /// AllureNUnit AddScreenDiff wrapper method.
- ///
- public static void AddScreenDiff(this AllureLifecycle lifecycle, string expected, string actual, string diff)
- {
- var storageMain = lifecycle.GetType().GetField("storage", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(lifecycle);
- var storageInternal = storageMain.GetType().GetField("storage", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(storageMain);
- var keys = (storageInternal as System.Collections.Concurrent.ConcurrentDictionary).Keys.ToList();
- AllureLifecycle.Instance.AddScreenDiff(keys.Find(key => key.Contains("-tr-")), expected, actual, diff);
- }
}
}
\ No newline at end of file
diff --git a/Allure.NUnit/Core/AllureNUnitAttribute.cs b/Allure.NUnit/Core/AllureNUnitAttribute.cs
index fae2f8ee..02db2585 100644
--- a/Allure.NUnit/Core/AllureNUnitAttribute.cs
+++ b/Allure.NUnit/Core/AllureNUnitAttribute.cs
@@ -1,8 +1,5 @@
using System;
using System.Collections.Concurrent;
-using Allure.Net.Commons;
-using NUnit.Engine;
-using NUnit.Engine.Extensibility;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
@@ -12,21 +9,11 @@ namespace NUnit.Allure.Core
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)]
public class AllureNUnitAttribute : PropertyAttribute, ITestAction, IApplyToContext
{
- private readonly ConcurrentDictionary _allureNUnitHelper = new ConcurrentDictionary();
- private readonly bool _isWrappedIntoStep;
-
- static AllureNUnitAttribute()
- {
- //!_! This is essential for async tests.
- //!_! Async tests are working on different threads, so
- //!_! default ManagedThreadId-separated behaviour in some cases fails on cross-thread execution.
- AllureLifecycle.CurrentTestIdGetter = () => TestContext.CurrentContext.Test.FullName;
- }
+ private readonly ConcurrentDictionary _allureNUnitHelper = new();
[Obsolete("wrapIntoStep parameter is obsolete. Use [AllureStep] method attribute")]
public AllureNUnitAttribute(bool wrapIntoStep = true)
{
- _isWrappedIntoStep = wrapIntoStep;
}
public AllureNUnitAttribute()
@@ -36,7 +23,11 @@ public AllureNUnitAttribute()
public void BeforeTest(ITest test)
{
var helper = new AllureNUnitHelper(test);
- _allureNUnitHelper.AddOrUpdate(test.Id, helper, (key, existing) => helper);
+ _allureNUnitHelper.AddOrUpdate(
+ test.Id,
+ helper,
+ (key, existing) => helper
+ );
if (test.IsSuite)
{
@@ -64,7 +55,8 @@ public void AfterTest(ITest test)
}
}
- public ActionTargets Targets => ActionTargets.Test | ActionTargets.Suite;
+ public ActionTargets Targets =>
+ ActionTargets.Test | ActionTargets.Suite;
public void ApplyToContext(TestExecutionContext context)
{
diff --git a/Allure.NUnit/Core/AllureNUnitHelper.cs b/Allure.NUnit/Core/AllureNUnitHelper.cs
index 5da69d52..3d2756de 100644
--- a/Allure.NUnit/Core/AllureNUnitHelper.cs
+++ b/Allure.NUnit/Core/AllureNUnitHelper.cs
@@ -3,7 +3,6 @@
using System.Linq;
using System.Text;
using Allure.Net.Commons;
-using Allure.Net.Commons.Storage;
using Newtonsoft.Json.Linq;
using NUnit.Allure.Attributes;
using NUnit.Framework;
@@ -15,14 +14,15 @@
namespace NUnit.Allure.Core
{
- public sealed class AllureNUnitHelper : ITestResultAccessor
+ public sealed class AllureNUnitHelper
{
- internal static List ExceptionTypes = new List { typeof(NUnitException), typeof(AssertionException) };
- public TestResultContainer TestResultContainer { get; set; }
- public TestResult TestResult { get; set; }
+ internal static List ExceptionTypes = new()
+ {
+ typeof(NUnitException),
+ typeof(AssertionException)
+ };
private readonly ITest _test;
- private string _testResultGuid;
public AllureNUnitHelper(ITest test)
{
@@ -33,21 +33,22 @@ public AllureNUnitHelper(ITest test)
internal void StartTestContainer()
{
- StepsHelper.TestResultAccessor = this;
- TestResultContainer = new TestResultContainer
+ AllureLifecycle.StartTestContainer(new()
{
uuid = ContainerId,
name = _test.FullName
- };
- AllureLifecycle.StartTestContainer(TestResultContainer);
+ });
}
internal void StartTestCase()
{
- _testResultGuid = string.Concat(Guid.NewGuid().ToString(), "-tr-", _test.Id);
- TestResult = new TestResult
+ var testResult = new TestResult
{
- uuid = _testResultGuid,
+ uuid = string.Concat(
+ Guid.NewGuid().ToString(),
+ "-tr-",
+ _test.Id
+ ),
name = _test.Name,
historyId = _test.FullName,
fullName = _test.FullName,
@@ -55,12 +56,21 @@ internal void StartTestCase()
{
Label.Thread(),
Label.Host(),
- Label.Package(_test.ClassName?.Substring(0, _test.ClassName.LastIndexOf('.'))),
+ Label.Package(
+ _test.ClassName?.Substring(
+ 0,
+ _test.ClassName.LastIndexOf('.')
+ )
+ ),
Label.TestMethod(_test.MethodName),
- Label.TestClass(_test.ClassName?.Substring(_test.ClassName.LastIndexOf('.') + 1))
+ Label.TestClass(
+ _test.ClassName?.Substring(
+ _test.ClassName.LastIndexOf('.') + 1
+ )
+ )
}
};
- AllureLifecycle.StartTestCase(ContainerId, TestResult);
+ AllureLifecycle.StartTestCase(testResult);
}
private TestFixture GetTestFixture(ITest test)
@@ -70,7 +80,10 @@ private TestFixture GetTestFixture(ITest test)
while (isTestSuite != true)
{
currentTest = currentTest.Parent;
- if (currentTest is ParameterizedMethodSuite) currentTest = currentTest.Parent;
+ if (currentTest is ParameterizedMethodSuite)
+ {
+ currentTest = currentTest.Parent;
+ }
isTestSuite = currentTest.IsSuite;
}
@@ -84,31 +97,42 @@ internal void StopTestCase()
for (var i = 0; i < _test.Arguments.Length; i++)
{
- AllureLifecycle.UpdateTestCase(x => x.parameters.Add(new Parameter
- {
- // ReSharper disable once AccessToModifiedClosure
- name = $"Param #{i}",
- // ReSharper disable once AccessToModifiedClosure
- value = _test.Arguments[i] == null ? "NULL" : _test.Arguments[i].ToString()
- }));
+ AllureLifecycle.UpdateTestCase(
+ x => x.parameters.Add(
+ new Parameter
+ {
+ // ReSharper disable once AccessToModifiedClosure
+ name = $"Param #{i}",
+ // ReSharper disable once AccessToModifiedClosure
+ value = _test.Arguments[i] == null
+ ? "NULL"
+ : _test.Arguments[i].ToString()
+ }
+ )
+ );
}
- AllureLifecycle.UpdateTestCase(x => x.statusDetails = new StatusDetails
- {
- message = string.IsNullOrWhiteSpace(TestContext.CurrentContext.Result.Message)
- ? TestContext.CurrentContext.Test.Name
- : TestContext.CurrentContext.Result.Message,
- trace = TestContext.CurrentContext.Result.StackTrace
- });
+ AllureLifecycle.UpdateTestCase(
+ x => x.statusDetails = new StatusDetails
+ {
+ message = string.IsNullOrWhiteSpace(
+ TestContext.CurrentContext.Result.Message
+ ) ? TestContext.CurrentContext.Test.Name
+ : TestContext.CurrentContext.Result.Message,
+ trace = TestContext.CurrentContext.Result.StackTrace
+ }
+ );
- AllureLifecycle.StopTestCase(testCase => testCase.status = GetNUnitStatus());
- AllureLifecycle.WriteTestCase(_testResultGuid);
+ AllureLifecycle.StopTestCase(
+ testCase => testCase.status = GetNUnitStatus()
+ );
+ AllureLifecycle.WriteTestCase();
}
internal void StopTestContainer()
{
- AllureLifecycle.StopTestContainer(ContainerId);
- AllureLifecycle.WriteTestContainer(ContainerId);
+ AllureLifecycle.StopTestContainer();
+ AllureLifecycle.WriteTestContainer();
}
public static Status GetNUnitStatus()
@@ -121,11 +145,18 @@ public static Status GetNUnitStatus()
var allureSection = jo["allure"];
try
{
- var config = allureSection?.ToObject();
+ var config = allureSection
+ ?.ToObject();
if (config?.BrokenTestData != null)
+ {
foreach (var word in config.BrokenTestData)
+ {
if (result.Message.Contains(word))
+ {
return Status.broken;
+ }
+ }
+ }
}
catch (Exception)
{
@@ -155,16 +186,34 @@ public static Status GetNUnitStatus()
private void UpdateTestDataFromAttributes()
{
foreach (var p in GetTestProperties(PropertyNames.Description))
- AllureLifecycle.UpdateTestCase(x => x.description += $"{p}\n");
+ {
+ AllureLifecycle.UpdateTestCase(
+ x => x.description += $"{p}\n"
+ );
+ }
foreach (var p in GetTestProperties(PropertyNames.Author))
- AllureLifecycle.UpdateTestCase(x => x.labels.Add(Label.Owner(p)));
+ {
+ AllureLifecycle.UpdateTestCase(
+ x => x.labels.Add(Label.Owner(p))
+ );
+ }
foreach (var p in GetTestProperties(PropertyNames.Category))
- AllureLifecycle.UpdateTestCase(x => x.labels.Add(Label.Tag(p)));
+ {
+ AllureLifecycle.UpdateTestCase(
+ x => x.labels.Add(Label.Tag(p))
+ );
+ }
- var attributes = _test.Method.GetCustomAttributes(true).ToList();
- attributes.AddRange(GetTestFixture(_test).GetCustomAttributes(true).ToList());
+ var attributes = _test.Method
+ .GetCustomAttributes(true)
+ .ToList();
+ attributes.AddRange(
+ GetTestFixture(_test)
+ .GetCustomAttributes(true)
+ .ToList()
+ );
attributes.ForEach(a =>
{
@@ -174,21 +223,37 @@ private void UpdateTestDataFromAttributes()
private void AddConsoleOutputAttachment()
{
- var output = TestExecutionContext.CurrentContext.CurrentResult.Output;
- AllureLifecycle.AddAttachment("Console Output", "text/plain",
- Encoding.UTF8.GetBytes(output), ".txt");
+ var output = TestExecutionContext
+ .CurrentContext
+ .CurrentResult
+ .Output;
+ AllureLifecycle.AddAttachment(
+ "Console Output",
+ "text/plain",
+ Encoding.UTF8.GetBytes(output),
+ ".txt"
+ );
}
private IEnumerable GetTestProperties(string name)
{
var list = new List();
var currentTest = _test;
- while (currentTest.GetType() != typeof(TestSuite) && currentTest.GetType() != typeof(TestAssembly))
+ while (currentTest.GetType() != typeof(TestSuite)
+ && currentTest.GetType() != typeof(TestAssembly))
{
if (currentTest.Properties.ContainsKey(name))
+ {
if (currentTest.Properties[name].Count > 0)
+ {
for (var i = 0; i < currentTest.Properties[name].Count; i++)
- list.Add(currentTest.Properties[name][i].ToString());
+ {
+ list.Add(
+ currentTest.Properties[name][i].ToString()
+ );
+ }
+ }
+ }
currentTest = currentTest.Parent;
}
@@ -206,12 +271,18 @@ public void WrapInStep(Action action, string stepName = "")
public void SaveOneTimeResultToContext()
{
- var currentResult = TestExecutionContext.CurrentContext.CurrentResult;
+ var currentResult = TestExecutionContext
+ .CurrentContext
+ .CurrentResult;
if (!string.IsNullOrEmpty(currentResult.Output))
{
- AllureLifecycle.Instance.AddAttachment("Console Output", "text/plain",
- Encoding.UTF8.GetBytes(currentResult.Output), ".txt");
+ AllureLifecycle.Instance.AddAttachment(
+ "Console Output",
+ "text/plain",
+ Encoding.UTF8.GetBytes(currentResult.Output),
+ ".txt"
+ );
}
FixtureResult fixtureResult = null;
@@ -226,20 +297,26 @@ public void SaveOneTimeResultToContext()
fixtureResult = fr;
});
- var testFixture = GetTestFixture(TestExecutionContext.CurrentContext.CurrentTest);
+ var testFixture = GetTestFixture(
+ TestExecutionContext.CurrentContext.CurrentTest
+ );
testFixture.Properties.Set("OneTimeSetUpResult", fixtureResult);
}
public void AddOneTimeSetupResult()
{
- var testFixture = GetTestFixture(TestExecutionContext.CurrentContext.CurrentTest);
+ var testFixture = GetTestFixture(
+ TestExecutionContext.CurrentContext.CurrentTest
+ );
FixtureResult fixtureResult = null;
- fixtureResult = testFixture.Properties.Get("OneTimeSetUpResult") as FixtureResult;
+ fixtureResult = testFixture.Properties.Get(
+ "OneTimeSetUpResult"
+ ) as FixtureResult;
if (fixtureResult != null && fixtureResult.steps.Any())
{
- AllureLifecycle.UpdateTestContainer(TestResultContainer.uuid, container =>
+ AllureLifecycle.UpdateTestContainer(container =>
{
container.befores.Add(fixtureResult);
});
From 618843db6c794ac6d2dfe9fc1faebb355b55be18 Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Fri, 28 Jul 2023 12:24:54 +0700
Subject: [PATCH 13/24] Fix Allure.XUnit usage of RunInContext
---
Allure.XUnit.Examples/ExampleUnitTests.cs | 1 +
Allure.XUnit/AllureMessageSink.cs | 22 +++++++++-------------
2 files changed, 10 insertions(+), 13 deletions(-)
diff --git a/Allure.XUnit.Examples/ExampleUnitTests.cs b/Allure.XUnit.Examples/ExampleUnitTests.cs
index 2741bf25..fd10d4d0 100644
--- a/Allure.XUnit.Examples/ExampleUnitTests.cs
+++ b/Allure.XUnit.Examples/ExampleUnitTests.cs
@@ -5,6 +5,7 @@
using Allure.Net.Commons;
using Allure.Xunit;
using Allure.Xunit.Attributes;
+using Allure.XUnit.Attributes.Steps;
using Xunit;
namespace Allure.XUnit.Examples
diff --git a/Allure.XUnit/AllureMessageSink.cs b/Allure.XUnit/AllureMessageSink.cs
index 08b54b39..19358aa9 100644
--- a/Allure.XUnit/AllureMessageSink.cs
+++ b/Allure.XUnit/AllureMessageSink.cs
@@ -106,22 +106,22 @@ MessageHandlerArgs args
void OnTestFailed(MessageHandlerArgs args) =>
this.RunInTestContext(
args.Message.Test,
- _ => AllureXunitHelper.ApplyTestFailure(args.Message)
+ () => AllureXunitHelper.ApplyTestFailure(args.Message)
);
void OnTestPassed(MessageHandlerArgs args) =>
this.RunInTestContext(
args.Message.Test,
- _ => AllureXunitHelper.ApplyTestSuccess(args.Message)
+ () => AllureXunitHelper.ApplyTestSuccess(args.Message)
);
void OnTestSkipped(MessageHandlerArgs args)
{
var message = args.Message;
var test = message.Test;
- this.UpdateTestContext(test, ctx =>
+ this.UpdateTestContext(test, () =>
{
- if (!ctx.HasTest)
+ if (!AllureLifecycle.Instance.Context.HasTest)
{
AllureXunitHelper.StartAllureTestCase(test);
}
@@ -135,7 +135,7 @@ void OnTestFinished(MessageHandlerArgs args)
var test = args.Message.Test;
var arguments = this.allureTestData[test].Arguments;
- this.RunInTestContext(test, _ =>
+ this.RunInTestContext(test, () =>
{
this.AddAllureParameters(test, arguments);
AllureXunitHelper.ReportCurrentTestCase();
@@ -179,20 +179,16 @@ void CaptureTestContext(ITest test) =>
this.GetOrCreateTestData(test).Context =
AllureLifecycle.Instance.Context;
- void RunInTestContext(ITest test, Action action) =>
+ AllureContext RunInTestContext(ITest test, Action action) =>
AllureLifecycle.Instance.RunInContext(
this.GetOrCreateTestData(test).Context,
action
);
- void UpdateTestContext(ITest test, Action action) =>
- this.RunInTestContext(
+ void UpdateTestContext(ITest test, Action action) =>
+ this.GetOrCreateTestData(test).Context = this.RunInTestContext(
test,
- ctx =>
- {
- action(ctx);
- this.CaptureTestContext(test);
- }
+ action
);
void LogUnreportedTheoryArgs(string testName)
From 4e43ea0a07f329d01161c9155a0fdc99d2bbbaf3 Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Fri, 28 Jul 2023 12:45:19 +0700
Subject: [PATCH 14/24] Rewrite Allure.SpecFlowPlugin to support new context
scheme
Additionally:
- Change broken status to failed for some scenario and step conditions
- Add extra test case instead of changing state of existing one in case
after feature failed
- Enable nullable check project-wide
---
.../TestData/After Feature Failure.feature | 2 +-
.../TestData/Invalid Steps.feature | 2 +-
.../IntegrationTests.cs | 114 +--
.../Allure.SpecFlowPlugin.csproj | 1 +
Allure.SpecFlowPlugin/AllureBindingInvoker.cs | 622 +++++++++-----
Allure.SpecFlowPlugin/AllureBindings.cs | 80 +-
Allure.SpecFlowPlugin/AllurePlugin.cs | 21 +-
.../AllureTestTracerWrapper.cs | 167 ++--
Allure.SpecFlowPlugin/PluginConfiguration.cs | 46 +-
Allure.SpecFlowPlugin/PluginHelper.cs | 762 +++++++++++-------
10 files changed, 1145 insertions(+), 672 deletions(-)
diff --git a/Allure.Features/TestData/After Feature Failure.feature b/Allure.Features/TestData/After Feature Failure.feature
index d7aa18ad..33ef1299 100644
--- a/Allure.Features/TestData/After Feature Failure.feature
+++ b/Allure.Features/TestData/After Feature Failure.feature
@@ -5,7 +5,7 @@ Feature: After Feature Failure
Scenario: After Feature Failure 1
Given Step is 'passed'
- @broken
+ @failed
Scenario: After Feature Failure 3
Given Step is 'failed'
diff --git a/Allure.Features/TestData/Invalid Steps.feature b/Allure.Features/TestData/Invalid Steps.feature
index 9ad1dfa0..c94b65c6 100644
--- a/Allure.Features/TestData/Invalid Steps.feature
+++ b/Allure.Features/TestData/Invalid Steps.feature
@@ -12,7 +12,7 @@
Given Step is 'passed'
And I don't have such step too
- @broken
+ @failed
Scenario: Failed step followed by invalid step
Given Step is 'failed'
Given I don't have such step
diff --git a/Allure.SpecFlowPlugin.Tests/IntegrationTests.cs b/Allure.SpecFlowPlugin.Tests/IntegrationTests.cs
index acf80f25..af351499 100644
--- a/Allure.SpecFlowPlugin.Tests/IntegrationTests.cs
+++ b/Allure.SpecFlowPlugin.Tests/IntegrationTests.cs
@@ -14,30 +14,35 @@ namespace Allure.SpecFlowPlugin.Tests
[TestFixture]
public class IntegrationFixture
{
- private readonly HashSet allureContainers = new HashSet();
- private readonly HashSet allureTestResults = new HashSet();
- private IEnumerable> scenariosByStatus;
+ private readonly HashSet allureContainers = new();
+ private readonly HashSet allureTestResults = new();
+ private IDictionary> scenariosByStatus;
- [OneTimeSetUp]
- public void Init()
- {
- var featuresProjectPath = Path.GetFullPath(
- Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"./../../../../Allure.Features"));
- Process.Start(new ProcessStartInfo
- {
- WorkingDirectory = featuresProjectPath,
- FileName = "dotnet",
- Arguments = $"test"
- }).WaitForExit();
- var allureResultsDirectory = new DirectoryInfo(featuresProjectPath).GetDirectories("allure-results", SearchOption.AllDirectories)
- .First();
- var featuresDirectory = Path.Combine(featuresProjectPath, "TestData");
-
-
- // parse allure suites
- ParseAllureSuites(allureResultsDirectory.FullName);
- ParseFeatures(featuresDirectory);
- }
+ [OneTimeSetUp]
+ public void Init()
+ {
+ var featuresProjectPath = Path.GetFullPath(
+ Path.Combine(
+ AppDomain.CurrentDomain.BaseDirectory,
+ @"./../../../../Allure.Features"
+ )
+ );
+ Process.Start(new ProcessStartInfo
+ {
+ WorkingDirectory = featuresProjectPath,
+ FileName = "dotnet",
+ Arguments = $"test"
+ }).WaitForExit();
+ var allureResultsDirectory = new DirectoryInfo(featuresProjectPath)
+ .GetDirectories("allure-results", SearchOption.AllDirectories)
+ .First();
+ var featuresDirectory = Path.Combine(featuresProjectPath, "TestData");
+
+
+ // parse allure suites
+ ParseAllureSuites(allureResultsDirectory.FullName);
+ ParseFeatures(featuresDirectory);
+ }
[TestCase(Status.passed)]
[TestCase(Status.failed)]
@@ -45,38 +50,49 @@ public void Init()
[TestCase(Status.skipped)]
public void TestStatus(Status status)
{
- var expected = scenariosByStatus.FirstOrDefault(x => x.Key == status.ToString()).ToList();
+ var expected = scenariosByStatus[status.ToString()];
var actual = allureTestResults.Where(x => x.status == status).Select(x => x.name).ToList();
Assert.That(actual, Is.EquivalentTo(expected));
}
- private void ParseFeatures(string featuresDir)
- {
- var parser = new Parser();
- var scenarios = new List();
- var features = new DirectoryInfo(featuresDir).GetFiles("*.feature");
- scenarios.AddRange(features.SelectMany(f =>
- {
- var children = parser.Parse(f.FullName).Feature.Children.ToList();
- var scenarioOutlines = children.Where(x => (x as dynamic).Examples.Length > 0).ToList();
- foreach (var s in scenarioOutlines)
+ private void ParseFeatures(string featuresDir)
{
- var examplesCount = ((s as dynamic).Examples as dynamic)[0].TableBody.Length;
- for (int i = 1; i < examplesCount; i++)
- {
- children.Add(s);
- }
+ var parser = new Parser();
+ var scenarios = new List();
+ var features = new DirectoryInfo(featuresDir).GetFiles("*.feature");
+ scenarios.AddRange(features.SelectMany(f =>
+ {
+ var children = parser.Parse(f.FullName).Feature.Children.ToList();
+ var scenarioOutlines = children.Where(
+ x => (x as dynamic).Examples.Length > 0
+ ).ToList();
+ foreach (var s in scenarioOutlines)
+ {
+ var examplesCount = (s as dynamic).Examples[0]
+ .TableBody.Length;
+ for (int i = 1; i < examplesCount; i++)
+ {
+ children.Add(s);
+ }
+ }
+ return children;
+ })
+ .Select(x => x as Scenario));
+
+ scenariosByStatus = scenarios.GroupBy(
+ x => x.Tags.FirstOrDefault(
+ x => Enum.GetNames(
+ typeof(Status)
+ ).Contains(
+ x.Name.Replace("@", "")
+ )
+ )?.Name.Replace("@", "") ?? "_notag_",
+ x => x.Name
+ ).ToDictionary(g => g.Key, g => g.ToList());
+
+ // Extra unknown scenario for testing an exception in AfterFeature
+ scenariosByStatus["broken"].Add("Unknown");
}
- return children;
- })
- .Select(x => x as Scenario));
-
- scenariosByStatus =
- scenarios.GroupBy(x => x.Tags.FirstOrDefault(x =>
- Enum.GetNames(typeof(Status)).Contains(x.Name.Replace("@", "")))?.Name
- .Replace("@", "") ??
- "_notag_", x => x.Name);
- }
private void ParseAllureSuites(string allureResultsDir)
{
diff --git a/Allure.SpecFlowPlugin/Allure.SpecFlowPlugin.csproj b/Allure.SpecFlowPlugin/Allure.SpecFlowPlugin.csproj
index 019ff73d..feb9ea18 100644
--- a/Allure.SpecFlowPlugin/Allure.SpecFlowPlugin.csproj
+++ b/Allure.SpecFlowPlugin/Allure.SpecFlowPlugin.csproj
@@ -3,6 +3,7 @@
net462;netstandard2.0
11
+ enable
Allure.SpecFlow
2.10-SNAPSHOT
Alexander Bakanov
diff --git a/Allure.SpecFlowPlugin/AllureBindingInvoker.cs b/Allure.SpecFlowPlugin/AllureBindingInvoker.cs
index 71211c0d..3aa9d9bb 100644
--- a/Allure.SpecFlowPlugin/AllureBindingInvoker.cs
+++ b/Allure.SpecFlowPlugin/AllureBindingInvoker.cs
@@ -1,10 +1,5 @@
using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
using Allure.Net.Commons;
-using CsvHelper;
using TechTalk.SpecFlow;
using TechTalk.SpecFlow.Bindings;
using TechTalk.SpecFlow.Configuration;
@@ -12,245 +7,438 @@
using TechTalk.SpecFlow.Infrastructure;
using TechTalk.SpecFlow.Tracing;
+
namespace Allure.SpecFlowPlugin
{
+ using AllureBindingCall = Func<
+ IBinding,
+ IContextManager,
+ object[],
+ ITestTracer,
+ (object, TimeSpan)
+ >;
+
internal class AllureBindingInvoker : BindingInvoker
{
- private static readonly AllureLifecycle allure = AllureLifecycle.Instance;
-
- public AllureBindingInvoker(SpecFlowConfiguration specFlowConfiguration, IErrorProvider errorProvider,
- ISynchronousBindingDelegateInvoker synchronousBindingDelegateInvoker) : base(
- specFlowConfiguration, errorProvider, synchronousBindingDelegateInvoker)
+ const string PLACEHOLDER_TESTCASE_KEY =
+ "Allure.SpecFlowPlugin.HAS_PLACEHOLDER_TESTCASE";
+
+ static readonly AllureLifecycle allure = AllureLifecycle.Instance;
+
+ public AllureBindingInvoker(
+ SpecFlowConfiguration specFlowConfiguration,
+ IErrorProvider errorProvider,
+ ISynchronousBindingDelegateInvoker synchronousBindingDelegateInvoker
+ ) : base(
+ specFlowConfiguration,
+ errorProvider,
+ synchronousBindingDelegateInvoker
+ )
{
}
- public override object InvokeBinding(IBinding binding, IContextManager contextManager, object[] arguments,
- ITestTracer testTracer, out TimeSpan duration)
+ public override object InvokeBinding(
+ IBinding binding,
+ IContextManager contextManager,
+ object[] arguments,
+ ITestTracer testTracer,
+ out TimeSpan duration
+ )
{
- // process hook
if (binding is HookBinding hook)
{
- var featureContainerId = PluginHelper.GetFeatureContainerId(contextManager.FeatureContext?.FeatureInfo);
-
- switch (hook.HookType)
- {
- case HookType.BeforeFeature:
- if (hook.HookOrder == int.MinValue)
- {
- // starting point
- var featureContainer = new TestResultContainer
- {
- uuid = PluginHelper.GetFeatureContainerId(contextManager.FeatureContext?.FeatureInfo)
- };
- allure.StartTestContainer(featureContainer);
-
- contextManager.FeatureContext.Set(new HashSet());
- contextManager.FeatureContext.Set(new HashSet());
-
- return base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration);
- }
- else
- {
- try
- {
- StartFixture(hook, featureContainerId);
- var result = base.InvokeBinding(binding, contextManager, arguments, testTracer,
- out duration);
- allure.StopFixture(x => x.status = Status.passed);
- return result;
- }
- catch (Exception ex)
- {
- allure.StopFixture(x => x.status = Status.broken);
-
- // if BeforeFeature is failed, execution is already stopped. We need to create, update, stop and write everything here.
-
- // create fake scenario container
- var scenarioContainer =
- PluginHelper.StartTestContainer(contextManager.FeatureContext, null);
-
- // start fake scenario
- var scenario = PluginHelper.StartTestCase(scenarioContainer.uuid,
- contextManager.FeatureContext, null);
-
- // update, stop and write
- allure
- .StopTestCase(x =>
- {
- x.status = Status.broken;
- x.statusDetails = PluginHelper.GetStatusDetails(ex);
- })
- .WriteTestCase(scenario.uuid)
- .StopTestContainer(scenarioContainer.uuid)
- .WriteTestContainer(scenarioContainer.uuid)
- .StopTestContainer(featureContainerId)
- .WriteTestContainer(featureContainerId);
-
- throw;
- }
- }
-
- case HookType.BeforeStep:
- case HookType.AfterStep:
- {
- var scenario = PluginHelper.GetCurrentTestCase(contextManager.ScenarioContext);
-
- try
- {
- return base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration);
- }
- catch (Exception ex)
- {
- allure
- .UpdateTestCase(scenario.uuid,
- x =>
- {
- x.status = Status.broken;
- x.statusDetails = PluginHelper.GetStatusDetails(ex);
- });
- throw;
- }
- }
-
- case HookType.BeforeScenario:
- case HookType.AfterScenario:
- if (hook.HookOrder == int.MinValue || hook.HookOrder == int.MaxValue)
- {
- return base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration);
- }
- else
- {
- var scenarioContainer = PluginHelper.GetCurrentTestConainer(contextManager.ScenarioContext);
-
- try
- {
- StartFixture(hook, scenarioContainer.uuid);
- var result = base.InvokeBinding(binding, contextManager, arguments, testTracer,
- out duration);
- allure.StopFixture(x => x.status = Status.passed);
- return result;
- }
- catch (Exception ex)
- {
- var status = ex.GetType().Name.Contains(PluginHelper.IGNORE_EXCEPTION)
- ? Status.skipped
- : Status.broken;
-
- allure.StopFixture(x => x.status = status);
-
- // get or add new scenario
- var scenario = PluginHelper.GetCurrentTestCase(contextManager.ScenarioContext) ??
- PluginHelper.StartTestCase(scenarioContainer.uuid,
- contextManager.FeatureContext, contextManager.ScenarioContext);
-
- allure.UpdateTestCase(scenario.uuid,
- x =>
- {
- x.status = status;
- x.statusDetails = PluginHelper.GetStatusDetails(ex);
- });
- throw;
- }
- }
-
- case HookType.AfterFeature:
- if (hook.HookOrder == int.MaxValue)
- // finish point
- {
- WriteScenarios(contextManager);
- allure
- .StopTestContainer(featureContainerId)
- .WriteTestContainer(featureContainerId);
-
- return base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration);
- }
- else
- {
- try
- {
- StartFixture(hook, featureContainerId);
- var result = base.InvokeBinding(binding, contextManager, arguments, testTracer,
- out duration);
- allure.StopFixture(x => x.status = Status.passed);
- return result;
- }
- catch (Exception ex)
- {
- var scenario = contextManager.FeatureContext.Get>().Last();
- allure
- .StopFixture(x => x.status = Status.broken)
- .UpdateTestCase(scenario.uuid,
- x =>
- {
- x.status = Status.broken;
- x.statusDetails = PluginHelper.GetStatusDetails(ex);
- });
-
- WriteScenarios(contextManager);
-
- allure
- .StopTestContainer(featureContainerId)
- .WriteTestContainer(featureContainerId);
-
- throw;
- }
- }
-
- case HookType.BeforeScenarioBlock:
- case HookType.AfterScenarioBlock:
- case HookType.BeforeTestRun:
- case HookType.AfterTestRun:
- default:
- return base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration);
- }
+ (var result, duration) = this.ProcessHook(
+ binding,
+ contextManager,
+ arguments,
+ testTracer,
+ hook
+ );
+ return result;
}
-
- return base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration);
+ return base.InvokeBinding(
+ binding,
+ contextManager,
+ arguments,
+ testTracer,
+ out duration
+ );
}
- private void StartFixture(HookBinding hook, string containerId)
+ (object, TimeSpan) ProcessHook(
+ IBinding binding,
+ IContextManager contextManager,
+ object[] arguments,
+ ITestTracer testTracer,
+ HookBinding hook
+ ) =>
+ IsAllureHook(hook) ? this.InvokeAllureBinding(
+ binding,
+ contextManager,
+ arguments,
+ testTracer,
+ hook
+ ) : hook.HookType switch
+ {
+ HookType.BeforeFeature =>
+ this.MakeFixtureFromBeforeFeatureHook(
+ binding,
+ contextManager,
+ arguments,
+ testTracer,
+ hook
+ ),
+ HookType.BeforeScenario =>
+ this.MakeFixtureFromBeforeScenarioHook(
+ binding,
+ contextManager,
+ arguments,
+ testTracer,
+ hook
+ ),
+ HookType.BeforeStep or HookType.AfterStep =>
+ this.ProcessStepHook(
+ binding,
+ contextManager,
+ arguments,
+ testTracer
+ ),
+ HookType.AfterScenario =>
+ this.MakeFixtureFromAfterScenarioHook(
+ binding,
+ contextManager,
+ arguments,
+ testTracer,
+ hook
+ ),
+ HookType.AfterFeature =>
+ this.MakeFixtureFromAfterFeatureHook(
+ binding,
+ contextManager,
+ arguments,
+ testTracer,
+ hook
+ ),
+ _ => this.CallBaseInvokeBinding(
+ binding,
+ contextManager,
+ arguments,
+ testTracer
+ )
+ };
+
+ (object, TimeSpan) ProcessStepHook(
+ IBinding binding,
+ IContextManager contextManager,
+ object[] arguments,
+ ITestTracer testTracer
+ )
{
- if (hook.HookType.ToString().StartsWith("Before"))
- allure.StartBeforeFixture(containerId, PluginHelper.NewId(), PluginHelper.GetFixtureResult(hook));
- else
- allure.StartAfterFixture(containerId, PluginHelper.NewId(), PluginHelper.GetFixtureResult(hook));
+ try
+ {
+ return this.CallBaseInvokeBinding(
+ binding,
+ contextManager,
+ arguments,
+ testTracer
+ );
+ }
+ catch (Exception ex)
+ {
+ ReportStepError(ex);
+ throw;
+ }
}
- private static void StartStep(StepInfo stepInfo, string containerId)
+ (object, TimeSpan) MakeFixtureFromBeforeFeatureHook(
+ IBinding binding,
+ IContextManager contextManager,
+ object[] arguments,
+ ITestTracer testTracer,
+ HookBinding hook
+ ) =>
+ this.MakeFixtureFromFeatureHook(
+ StartBeforeFixture,
+ _ => { },
+ binding,
+ contextManager,
+ arguments,
+ testTracer,
+ hook
+ );
+
+ (object, TimeSpan) MakeFixtureFromAfterFeatureHook(
+ IBinding binding,
+ IContextManager contextManager,
+ object[] arguments,
+ ITestTracer testTracer,
+ HookBinding hook
+ ) =>
+ PluginHelper.UseCapturedAllureContext(
+ contextManager.FeatureContext,
+ () => this.MakeFixtureFromFeatureHook(
+ StartAfterFixture,
+ _ => AllureBindings.LastAfterFeature(),
+ binding,
+ contextManager,
+ arguments,
+ testTracer,
+ hook
+ )
+ );
+
+ (object, TimeSpan) MakeFixtureFromFeatureHook(
+ Action startFixture,
+ Action callLastHook,
+ IBinding binding,
+ IContextManager contextManager,
+ object[] arguments,
+ ITestTracer testTracer,
+ HookBinding hook
+ )
{
- var stepResult = new StepResult
+ object result;
+ TimeSpan duration;
+
+ startFixture(hook);
+ try
{
- name = $"{stepInfo.StepDefinitionType} {stepInfo.Text}"
- };
+ result = base.InvokeBinding(
+ binding,
+ contextManager,
+ arguments,
+ testTracer,
+ out duration
+ );
+ }
+ catch (Exception ex)
+ {
+ var featureContext = contextManager.FeatureContext;
+ ReportFeatureFixtureError(featureContext, ex);
+ callLastHook(featureContext);
+ throw;
+ }
+ allure.StopFixture(MakePassed);
+
+ return (result, duration);
+ }
- allure.StartStep(containerId, PluginHelper.NewId(), stepResult);
+ (object, TimeSpan) MakeFixtureFromBeforeScenarioHook(
+ IBinding binding,
+ IContextManager contextManager,
+ object[] arguments,
+ ITestTracer testTracer,
+ HookBinding hook
+ ) =>
+ this.MakeFixtureFromScenarioHook(
+ AllureBindings.LastBeforeScenario,
+ StartBeforeFixture,
+ binding,
+ contextManager,
+ arguments,
+ testTracer,
+ hook
+ );
+
+ (object, TimeSpan) MakeFixtureFromAfterScenarioHook(
+ IBinding binding,
+ IContextManager contextManager,
+ object[] arguments,
+ ITestTracer testTracer,
+ HookBinding hook
+ ) =>
+ this.MakeFixtureFromScenarioHook(
+ (_, sc) => AllureBindings.LastAfterScenario(sc),
+ StartAfterFixture,
+ binding,
+ contextManager,
+ arguments,
+ testTracer,
+ hook
+ );
+
+ (object, TimeSpan) MakeFixtureFromScenarioHook(
+ Action callLastHook,
+ Action startFixture,
+ IBinding binding,
+ IContextManager contextManager,
+ object[] arguments,
+ ITestTracer testTracer,
+ HookBinding hook
+ )
+ {
+ (object, TimeSpan) result;
- if (stepInfo.Table != null)
+ startFixture(hook);
+ try
{
- var csvFile = $"{Guid.NewGuid().ToString()}.csv";
- using (var csv = new CsvWriter(File.CreateText(csvFile),CultureInfo.InvariantCulture))
- {
- foreach (var item in stepInfo.Table.Header) csv.WriteField(item);
- csv.NextRecord();
- foreach (var row in stepInfo.Table.Rows)
- {
- foreach (var item in row.Values) csv.WriteField(item);
- csv.NextRecord();
- }
- }
-
- allure.AddAttachment("table", "text/csv", csvFile);
+ result = this.CallBaseInvokeBinding(
+ binding,
+ contextManager,
+ arguments,
+ testTracer
+ );
}
+ catch (Exception ex)
+ {
+ ReportScenarioFixtureError(ex);
+
+ // SpecFlow doesn't call the remained hooks in case of an
+ // exception is thrown. We have to call them explicitly to
+ // ensure side effects on the Allure context are properly
+ // applied.
+ callLastHook(
+ contextManager.FeatureContext,
+ contextManager.ScenarioContext
+ );
+
+ throw;
+ }
+
+ allure.StopFixture(MakePassed);
+ return result;
}
- private static void WriteScenarios(IContextManager contextManager)
+ (object, TimeSpan) InvokeAllureBinding(
+ IBinding binding,
+ IContextManager contextManager,
+ object[] arguments,
+ ITestTracer testTracer,
+ HookBinding hook
+ ) =>
+ this.ResolveAllureBindingCall(hook)(
+ binding,
+ contextManager,
+ arguments,
+ testTracer
+ );
+
+ AllureBindingCall ResolveAllureBindingCall(HookBinding hook) =>
+ hook.HookType is HookType.AfterFeature
+ ? this.CallBaseInvokeBindingInFeatureContext
+ : this.CallBaseInvokeBinding;
+
+ (object, TimeSpan) CallBaseInvokeBinding(
+ IBinding binding,
+ IContextManager contextManager,
+ object[] arguments,
+ ITestTracer testTracer
+ ) =>
+ (base.InvokeBinding(
+ binding,
+ contextManager,
+ arguments,
+ testTracer,
+ out var duration
+ ), duration);
+
+ (object, TimeSpan) CallBaseInvokeBindingInFeatureContext(
+ IBinding binding,
+ IContextManager contextManager,
+ object[] arguments,
+ ITestTracer testTracer
+ ) =>
+ PluginHelper.UseCapturedAllureContext(
+ contextManager.FeatureContext,
+ () => this.CallBaseInvokeBinding(
+ binding,
+ contextManager,
+ arguments,
+ testTracer
+ )
+ );
+
+ static void ReportFeatureFixtureError(
+ FeatureContext featureContext,
+ Exception error
+ )
{
- foreach (var s in contextManager.FeatureContext.Get>()) allure.WriteTestCase(s.uuid);
+ var makeBroken = WrapMakeBroken(error);
+ allure.StopFixture(makeBroken);
+
+ // Create one placeholder test case per failed feature-level hook
+ // to indicate the error.
+ if (!featureContext.ContainsKey(PLACEHOLDER_TESTCASE_KEY))
+ {
+ PluginHelper.StartTestCase(featureContext.FeatureInfo, null);
- foreach (var c in contextManager.FeatureContext.Get>())
allure
- .StopTestContainer(c.uuid)
- .WriteTestContainer(c.uuid);
+ .StopTestCase(makeBroken)
+ .WriteTestCase();
+
+ featureContext.Add(PLACEHOLDER_TESTCASE_KEY, true);
+ }
+ }
+
+ static void ReportScenarioFixtureError(Exception error)
+ {
+ var status = PluginHelper.IsIgnoreException(error)
+ ? Status.skipped
+ : Status.broken;
+ var statusDetails = PluginHelper.GetStatusDetails(error);
+
+ allure.StopFixture(
+ PluginHelper.WrapStatusUpdate(status, statusDetails)
+ );
+
+ // If there is a scenario with no previous error, we update its
+ // status here (this is the case for AfterScenraio hooks).
+ // Otherwise (BeforeScenario) the scenario is updated later based
+ // on the information provided by SpecFlow.
+ if (allure.Context.HasTest)
+ {
+ allure.UpdateTestCase(
+ PluginHelper.WrapStatusOverwrite(
+ status,
+ statusDetails,
+ Status.none,
+ Status.passed
+ )
+ );
+ }
}
+
+ static void ReportStepError(Exception error)
+ {
+ if (allure.Context.HasStep)
+ {
+ MakeStepBroken(error);
+ }
+ MakeTestCaseBroken(error);
+ }
+
+ static void StartBeforeFixture(HookBinding hook) =>
+ allure.StartBeforeFixture(
+ PluginHelper.GetFixtureResult(hook)
+ );
+
+ static void StartAfterFixture(HookBinding hook) =>
+ allure.StartAfterFixture(
+ PluginHelper.GetFixtureResult(hook)
+ );
+
+ static bool IsAllureHook(HookBinding hook) =>
+ hook.Method.Type.FullName == typeof(AllureBindings).FullName;
+
+ static Action WrapMakeBroken(Exception error) =>
+ PluginHelper.WrapStatusOverwrite(
+ Status.broken,
+ PluginHelper.GetStatusDetails(error),
+ Status.none,
+ Status.passed
+ );
+
+ static void MakePassed(ExecutableItem item) =>
+ item.status = Status.passed;
+
+ static void MakeTestCaseBroken(Exception error) =>
+ allure.UpdateTestCase(
+ WrapMakeBroken(error)
+ );
+
+ static void MakeStepBroken(Exception error) =>
+ allure.UpdateStep(
+ WrapMakeBroken(error)
+ );
}
}
\ No newline at end of file
diff --git a/Allure.SpecFlowPlugin/AllureBindings.cs b/Allure.SpecFlowPlugin/AllureBindings.cs
index 6e9ef636..e16ade7d 100644
--- a/Allure.SpecFlowPlugin/AllureBindings.cs
+++ b/Allure.SpecFlowPlugin/AllureBindings.cs
@@ -4,56 +4,56 @@
namespace Allure.SpecFlowPlugin
{
[Binding]
- public class AllureBindings
+ public static class AllureBindings
{
- private static readonly AllureLifecycle allure = AllureLifecycle.Instance;
-
- private readonly FeatureContext featureContext;
- private readonly ScenarioContext scenarioContext;
-
- public AllureBindings(FeatureContext featureContext, ScenarioContext scenarioContext)
- {
- this.featureContext = featureContext;
- this.scenarioContext = scenarioContext;
- }
+ static readonly AllureLifecycle allure = AllureLifecycle.Instance;
[BeforeFeature(Order = int.MinValue)]
- public static void FirstBeforeFeature()
- {
- // start feature container in BindingInvoker
- }
+ public static void FirstBeforeFeature(FeatureContext featureContext) =>
+ // Capturing the context allows us to access the container later in
+ // AfterFeature hooks (it's executed by SpecFlow in a different
+ // execution context).
+ PluginHelper.CaptureAllureContext(
+ featureContext,
+ () => allure.StartTestContainer(new()
+ {
+ uuid = PluginHelper.GetFeatureContainerId(
+ featureContext.FeatureInfo
+ )
+ })
+ );
[AfterFeature(Order = int.MaxValue)]
- public static void LastAfterFeature()
- {
- // write feature container in BindingInvoker
- }
+ public static void LastAfterFeature() =>
+ allure
+ .StopTestContainer()
+ .WriteTestContainer();
[BeforeScenario(Order = int.MinValue)]
- public void FirstBeforeScenario()
- {
- PluginHelper.StartTestContainer(featureContext, scenarioContext);
- //AllureHelper.StartTestCase(scenarioContainer.uuid, featureContext, scenarioContext);
- }
+ public static void FirstBeforeScenario() =>
+ PluginHelper.StartTestContainer();
[BeforeScenario(Order = int.MaxValue)]
- public void LastBeforeScenario()
- {
- // start scenario after last fixture and before the first step to have valid current step context in allure storage
- var scenarioContainer = PluginHelper.GetCurrentTestConainer(scenarioContext);
- PluginHelper.StartTestCase(scenarioContainer.uuid, featureContext, scenarioContext);
- }
+ public static void LastBeforeScenario(
+ FeatureContext featureContext,
+ ScenarioContext scenarioContext
+ ) =>
+ PluginHelper.StartTestCase(
+ featureContext.FeatureInfo,
+ scenarioContext.ScenarioInfo
+ );
[AfterScenario(Order = int.MinValue)]
- public void FirstAfterScenario()
- {
- var scenarioId = PluginHelper.GetCurrentTestCase(scenarioContext).uuid;
-
- // update status to passed if there were no step of binding failures
- allure
- .UpdateTestCase(scenarioId,
- x => x.status = x.status != Status.none ? x.status : Status.passed)
- .StopTestCase(scenarioId);
- }
+ public static void FirstAfterScenario() => allure.StopTestCase();
+
+ [AfterScenario(Order = int.MaxValue)]
+ public static void LastAfterScenario(
+ ScenarioContext scenarioContext
+ ) =>
+ allure.UpdateTestCase(
+ PluginHelper.TestStatusResolver(scenarioContext)
+ ).WriteTestCase()
+ .StopTestContainer()
+ .WriteTestContainer();
}
}
\ No newline at end of file
diff --git a/Allure.SpecFlowPlugin/AllurePlugin.cs b/Allure.SpecFlowPlugin/AllurePlugin.cs
index 3137943e..8921d593 100644
--- a/Allure.SpecFlowPlugin/AllurePlugin.cs
+++ b/Allure.SpecFlowPlugin/AllurePlugin.cs
@@ -1,6 +1,4 @@
-using System;
-using System.IO;
-using Allure.SpecFlowPlugin;
+using Allure.SpecFlowPlugin;
using TechTalk.SpecFlow.Bindings;
using TechTalk.SpecFlow.Plugins;
using TechTalk.SpecFlow.Tracing;
@@ -12,14 +10,19 @@ namespace Allure.SpecFlowPlugin
{
public class AllurePlugin : IRuntimePlugin
{
- public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginParameters runtimePluginParameters,
- UnitTestProviderConfiguration unitTestProviderConfiguration)
+ public void Initialize(
+ RuntimePluginEvents runtimePluginEvents,
+ RuntimePluginParameters runtimePluginParameters,
+ UnitTestProviderConfiguration unitTestProviderConfiguration
+ )
{
- runtimePluginEvents.CustomizeGlobalDependencies += (sender, args) =>
- args.ObjectContainer.RegisterTypeAs();
+ runtimePluginEvents.CustomizeGlobalDependencies +=
+ (sender, args) => args.ObjectContainer
+ .RegisterTypeAs();
- runtimePluginEvents.CustomizeTestThreadDependencies += (sender, args) =>
- args.ObjectContainer.RegisterTypeAs();
+ runtimePluginEvents.CustomizeTestThreadDependencies +=
+ (sender, args) => args.ObjectContainer
+ .RegisterTypeAs();
}
}
}
\ No newline at end of file
diff --git a/Allure.SpecFlowPlugin/AllureTestTracerWrapper.cs b/Allure.SpecFlowPlugin/AllureTestTracerWrapper.cs
index a56ec182..e3b19d48 100644
--- a/Allure.SpecFlowPlugin/AllureTestTracerWrapper.cs
+++ b/Allure.SpecFlowPlugin/AllureTestTracerWrapper.cs
@@ -13,61 +13,101 @@
using TechTalk.SpecFlow.Configuration;
using TechTalk.SpecFlow.Tracing;
+
namespace Allure.SpecFlowPlugin
{
public class AllureTestTracerWrapper : TestTracer, ITestTracer
{
- private static readonly AllureLifecycle allure = AllureLifecycle.Instance;
- private static readonly PluginConfiguration pluginConfiguration = PluginHelper.PluginConfiguration;
- private readonly string noMatchingStepMessage = "No matching step definition found for the step";
-
- public AllureTestTracerWrapper(ITraceListener traceListener, IStepFormatter stepFormatter,
- IStepDefinitionSkeletonProvider stepDefinitionSkeletonProvider, SpecFlowConfiguration specFlowConfiguration)
- : base(traceListener, stepFormatter, stepDefinitionSkeletonProvider, specFlowConfiguration)
+ static readonly AllureLifecycle allure = AllureLifecycle.Instance;
+ static readonly PluginConfiguration pluginConfiguration =
+ PluginHelper.PluginConfiguration;
+ readonly string noMatchingStepMessage =
+ "No matching definition found for this step";
+ readonly string noMatchingStepMessageForTest =
+ "No matching definition found for the step '{0}'";
+
+ public AllureTestTracerWrapper(
+ ITraceListener traceListener,
+ IStepFormatter stepFormatter,
+ IStepDefinitionSkeletonProvider stepDefinitionSkeletonProvider,
+ SpecFlowConfiguration specFlowConfiguration
+ ) : base(
+ traceListener,
+ stepFormatter,
+ stepDefinitionSkeletonProvider,
+ specFlowConfiguration
+ )
{
}
- void ITestTracer.TraceStep(StepInstance stepInstance, bool showAdditionalArguments)
+ void ITestTracer.TraceStep(
+ StepInstance stepInstance,
+ bool showAdditionalArguments
+ )
{
- TraceStep(stepInstance, showAdditionalArguments);
- StartStep(stepInstance);
+ this.TraceStep(stepInstance, showAdditionalArguments);
+ this.StartStep(stepInstance);
}
- void ITestTracer.TraceStepDone(BindingMatch match, object[] arguments, TimeSpan duration)
+ void ITestTracer.TraceStepDone(
+ BindingMatch match,
+ object[] arguments,
+ TimeSpan duration
+ )
{
- TraceStepDone(match, arguments, duration);
+ this.TraceStepDone(match, arguments, duration);
allure.StopStep(x => x.status = Status.passed);
}
void ITestTracer.TraceError(Exception ex, TimeSpan duration)
{
- TraceError(ex, duration);
- allure.StopStep(x => x.status = Status.failed);
+ this.TraceError(ex, duration);
+ allure.StopStep(
+ PluginHelper.WrapStatusInit(Status.failed, ex)
+ );
FailScenario(ex);
}
void ITestTracer.TraceStepSkipped()
{
- TraceStepSkipped();
+ this.TraceStepSkipped();
allure.StopStep(x => x.status = Status.skipped);
}
void ITestTracer.TraceStepPending(BindingMatch match, object[] arguments)
{
- TraceStepPending(match, arguments);
+ this.TraceStepPending(match, arguments);
allure.StopStep(x => x.status = Status.skipped);
}
- void ITestTracer.TraceNoMatchingStepDefinition(StepInstance stepInstance, ProgrammingLanguage targetLanguage,
- CultureInfo bindingCulture, List matchesWithoutScopeCheck)
+ void ITestTracer.TraceNoMatchingStepDefinition(
+ StepInstance stepInstance,
+ ProgrammingLanguage targetLanguage,
+ CultureInfo bindingCulture,
+ List matchesWithoutScopeCheck
+ )
{
- TraceNoMatchingStepDefinition(stepInstance, targetLanguage, bindingCulture, matchesWithoutScopeCheck);
- allure.StopStep(x => x.status = Status.broken);
- allure.UpdateTestCase(x =>
- {
- x.status = Status.broken;
- x.statusDetails = new StatusDetails {message = noMatchingStepMessage};
- });
+ this.TraceNoMatchingStepDefinition(
+ stepInstance,
+ targetLanguage,
+ bindingCulture,
+ matchesWithoutScopeCheck
+ );
+ allure.StopStep(
+ PluginHelper.WrapStatusUpdate(Status.broken, new()
+ {
+ message = noMatchingStepMessage
+ })
+ );
+ allure.UpdateTestCase(
+ PluginHelper.WrapStatusInit(Status.broken, new StatusDetails
+ {
+ message = string.Format(
+ noMatchingStepMessageForTest,
+ stepInstance.Text
+ )
+ })
+ );
}
private void StartStep(StepInstance stepInstance)
@@ -79,18 +119,23 @@ private void StartStep(StepInstance stepInstance)
// parse MultilineTextArgument
- if (stepInstance.MultilineTextArgument != null)
+ if (stepInstance.MultilineTextArgument is not null)
+ {
allure.AddAttachment(
"multiline argument",
"text/plain",
- Encoding.ASCII.GetBytes(stepInstance.MultilineTextArgument),
- ".txt");
+ Encoding.ASCII.GetBytes(
+ stepInstance.MultilineTextArgument
+ ),
+ ".txt"
+ );
+ }
var table = stepInstance.TableArgument;
- var isTableProcessed = table == null;
+ var isTableProcessed = table is null;
// parse table as step params
- if (table != null)
+ if (table is not null)
{
var header = table.Header.ToArray();
if (pluginConfiguration.stepArguments.convertToParameters)
@@ -100,13 +145,24 @@ private void StartStep(StepInstance stepInstance)
// convert 2 column table into param-value
if (table.Header.Count == 2)
{
- var paramNameMatch = Regex.IsMatch(header[0], pluginConfiguration.stepArguments.paramNameRegex);
- var paramValueMatch =
- Regex.IsMatch(header[1], pluginConfiguration.stepArguments.paramValueRegex);
+ var paramNameMatch = Regex.IsMatch(
+ header[0],
+ pluginConfiguration.stepArguments.paramNameRegex
+ );
+ var paramValueMatch = Regex.IsMatch(
+ header[1],
+ pluginConfiguration.stepArguments.paramValueRegex
+ );
if (paramNameMatch && paramValueMatch)
{
for (var i = 0; i < table.RowCount; i++)
- parameters.Add(new Parameter {name = table.Rows[i][0], value = table.Rows[i][1]});
+ {
+ parameters.Add(new()
+ {
+ name = table.Rows[i][0],
+ value = table.Rows[i][1]
+ });
+ }
isTableProcessed = true;
}
@@ -115,7 +171,14 @@ private void StartStep(StepInstance stepInstance)
else if (table.RowCount == 1)
{
for (var i = 0; i < table.Header.Count; i++)
- parameters.Add(new Parameter {name = header[i], value = table.Rows[0][i]});
+ {
+ parameters.Add(new()
+ {
+ name = header[i],
+ value = table.Rows[0][i]
+ });
+ }
+
isTableProcessed = true;
}
@@ -123,18 +186,31 @@ private void StartStep(StepInstance stepInstance)
}
}
- allure.StartStep(PluginHelper.NewId(), stepResult);
+ allure.StartStep(stepResult);
+
+ // add csv table for multi-row table if was not processed as
+ // params already
+ if (isTableProcessed)
+ {
+ return;
+ }
- // add csv table for multi-row table if was not processed as params already
- if (isTableProcessed) return;
using var ms = new MemoryStream();
using var sw = new StreamWriter(ms, System.Text.Encoding.UTF8);
using var csv = new CsvWriter(sw, CultureInfo.InvariantCulture);
- foreach (var item in table.Header) csv.WriteField(item);
+ foreach (var item in table!.Header)
+ {
+ csv.WriteField(item);
+ }
+
csv.NextRecord();
foreach (var row in table.Rows)
{
- foreach (var item in row.Values) csv.WriteField(item);
+ foreach (var item in row.Values)
+ {
+ csv.WriteField(item);
+ }
+
csv.NextRecord();
}
@@ -144,12 +220,11 @@ private void StartStep(StepInstance stepInstance)
private static void FailScenario(Exception ex)
{
- allure.UpdateTestCase(
- x =>
- {
- x.status = x.status != Status.none ? x.status : Status.failed;
- x.statusDetails = PluginHelper.GetStatusDetails(ex);
- });
+ allure.UpdateTestCase(x =>
+ {
+ x.status = x.status != Status.none ? x.status : Status.failed;
+ x.statusDetails = PluginHelper.GetStatusDetails(ex);
+ });
}
}
}
diff --git a/Allure.SpecFlowPlugin/PluginConfiguration.cs b/Allure.SpecFlowPlugin/PluginConfiguration.cs
index 964cc82a..3a5bf354 100644
--- a/Allure.SpecFlowPlugin/PluginConfiguration.cs
+++ b/Allure.SpecFlowPlugin/PluginConfiguration.cs
@@ -2,57 +2,57 @@
{
public class PluginConfiguration
{
- public Steparguments stepArguments { get; set; } = new Steparguments();
- public Grouping grouping { get; set; } = new Grouping();
- public Labels labels { get; set; } = new Labels();
- public Links links { get; set; } = new Links();
+ public Steparguments stepArguments { get; set; } = new();
+ public Grouping grouping { get; set; } = new();
+ public Labels labels { get; set; } = new();
+ public Links links { get; set; } = new();
}
public class Steparguments
{
public bool convertToParameters { get; set; }
- public string paramNameRegex { get; set; }
- public string paramValueRegex { get; set; }
+ public string? paramNameRegex { get; set; }
+ public string? paramValueRegex { get; set; }
}
public class Grouping
{
- public Suites suites { get; set; } = new Suites();
- public Behaviors behaviors { get; set; } = new Behaviors();
- public Packages packages { get; set; } = new Packages();
+ public Suites suites { get; set; } = new();
+ public Behaviors behaviors { get; set; } = new();
+ public Packages packages { get; set; } = new();
}
public class Suites
{
- public string parentSuite { get; set; }
- public string suite { get; set; }
- public string subSuite { get; set; }
+ public string? parentSuite { get; set; }
+ public string? suite { get; set; }
+ public string? subSuite { get; set; }
}
public class Behaviors
{
- public string epic { get; set; }
- public string story { get; set; }
+ public string? epic { get; set; }
+ public string? story { get; set; }
}
public class Packages
{
- public string package { get; set; }
- public string testClass { get; set; }
- public string testMethod { get; set; }
+ public string? package { get; set; }
+ public string? testClass { get; set; }
+ public string? testMethod { get; set; }
}
public class Labels
{
- public string owner { get; set; }
- public string severity { get; set; }
- public string label { get; set; }
+ public string? owner { get; set; }
+ public string? severity { get; set; }
+ public string? label { get; set; }
}
public class Links
{
- public string link { get; set; }
- public string issue { get; set; }
- public string tms { get; set; }
+ public string? link { get; set; }
+ public string? issue { get; set; }
+ public string? tms { get; set; }
}
}
\ No newline at end of file
diff --git a/Allure.SpecFlowPlugin/PluginHelper.cs b/Allure.SpecFlowPlugin/PluginHelper.cs
index 902f68b9..9a0d5206 100644
--- a/Allure.SpecFlowPlugin/PluginHelper.cs
+++ b/Allure.SpecFlowPlugin/PluginHelper.cs
@@ -1,350 +1,540 @@
-using Allure.Net.Commons;
-using Newtonsoft.Json.Linq;
-using System;
+using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
+using Allure.Net.Commons;
+using Allure.Net.Commons.Storage;
+using Newtonsoft.Json.Linq;
using TechTalk.SpecFlow;
using TechTalk.SpecFlow.Bindings;
namespace Allure.SpecFlowPlugin
{
- public static class PluginHelper
- {
- public static string IGNORE_EXCEPTION = "IgnoreException";
- private static readonly ScenarioInfo emptyScenarioInfo = new ScenarioInfo("Unknown", string.Empty, Array.Empty(), new OrderedDictionary());
+ public static class PluginHelper
+ {
+ public static string IGNORE_EXCEPTION = "IgnoreException";
+ private static readonly ScenarioInfo emptyScenarioInfo = new(
+ "Unknown",
+ string.Empty,
+ Array.Empty(),
+ new OrderedDictionary()
+ );
+
+ private static readonly FeatureInfo emptyFeatureInfo = new(
+ CultureInfo.CurrentCulture,
+ string.Empty,
+ string.Empty,
+ string.Empty
+ );
+
+ internal static PluginConfiguration PluginConfiguration =
+ GetConfiguration(AllureLifecycle.Instance.JsonConfiguration);
+
+ public static PluginConfiguration GetConfiguration(
+ string allureConfiguration
+ )
+ {
+ var config = new PluginConfiguration();
+ var configJson = JObject.Parse(allureConfiguration);
+ var specflowSection = configJson["specflow"];
+ if (specflowSection != null)
+ {
+ config = specflowSection.ToObject()
+ ?? throw new NullReferenceException();
+ }
+
+ return config;
+ }
- private static readonly FeatureInfo emptyFeatureInfo = new FeatureInfo(
- CultureInfo.CurrentCulture, string.Empty, string.Empty, string.Empty);
+ internal static string GetFeatureContainerId(
+ FeatureInfo featureInfo
+ ) => featureInfo != null
+ ? featureInfo.GetHashCode().ToString()
+ : emptyFeatureInfo.GetHashCode().ToString();
+
+ internal static string NewId() => Guid.NewGuid().ToString("N");
+
+ internal static FixtureResult GetFixtureResult(HookBinding hook) =>
+ new()
+ {
+ name = $"{hook.Method.Name} [{hook.HookOrder}]"
+ };
+
+ internal static void StartTestContainer() =>
+ AllureLifecycle.Instance.StartTestContainer(new()
+ {
+ uuid = NewId()
+ });
+
+ internal static void StartTestCase(
+ FeatureInfo featureInfo,
+ ScenarioInfo? scenarioInfo
+ )
+ {
+ featureInfo ??= emptyFeatureInfo;
+ scenarioInfo ??= emptyScenarioInfo;
+ var tags = GetTags(featureInfo, scenarioInfo);
+ var parameters = GetParameters(scenarioInfo);
+ var title = scenarioInfo.Title;
+ var testResult = new TestResult
+ {
+ uuid = NewId(),
+ historyId = title + parameters.hash,
+ name = title,
+ fullName = title,
+ labels = new List
+ {
+ Label.Thread(),
+ string.IsNullOrWhiteSpace(
+ AllureLifecycle.Instance.AllureConfiguration.Title
+ ) ? Label.Host() : Label.Host(
+ AllureLifecycle.Instance.AllureConfiguration.Title
+ ),
+ Label.Feature(featureInfo.Title)
+ }
+ .Union(tags.Item1).ToList(),
+ links = tags.Item2,
+ parameters = parameters.parameters
+ };
- internal static PluginConfiguration PluginConfiguration =
- GetConfiguration(AllureLifecycle.Instance.JsonConfiguration);
+ AllureLifecycle.Instance.StartTestCase(testResult);
+ }
- public static PluginConfiguration GetConfiguration(string allureConfiguration)
- {
- var config = new PluginConfiguration();
- var specflowSection = JObject.Parse(allureConfiguration)["specflow"];
- if (specflowSection != null)
- config = specflowSection.ToObject();
- return config;
- }
+ internal static StatusDetails GetStatusDetails(Exception ex) =>
+ new()
+ {
+ message = GetFullExceptionMessage(ex),
+ trace = ex.ToString()
+ };
+
+ internal static T CaptureAllureContext(
+ SpecFlowContext specFlowContext,
+ Func fn
+ )
+ {
+ T result = fn();
+ specFlowContext.Set(AllureLifecycle.Instance.Context);
+ return result;
+ }
- internal static string GetFeatureContainerId(FeatureInfo featureInfo)
- {
- var id = featureInfo != null
- ? featureInfo.GetHashCode().ToString()
- : emptyFeatureInfo.GetHashCode().ToString();
+ internal static void UseCapturedAllureContext(
+ SpecFlowContext specFlowContext,
+ Action fn
+ ) => AllureLifecycle.Instance.RunInContext(
+ specFlowContext.Get(),
+ fn
+ );
+
+ internal static T UseCapturedAllureContext(
+ SpecFlowContext specFlowContext,
+ Func fn
+ )
+ {
+ T? result = default;
+ UseCapturedAllureContext(
+ specFlowContext,
+ () => { result = fn(); }
+ );
+ return result!;
+ }
- return id;
- }
+ internal static Action WrapStatusUpdate(
+ Status status,
+ StatusDetails? statusDetails = null
+ ) => item =>
+ {
+ item.status = status;
+ item.statusDetails = statusDetails;
+ };
+
+ internal static Action WrapStatusOverwrite(
+ Status status,
+ StatusDetails? statusDetails,
+ params Status[] statusesToOverwrite
+ )
+ {
+ var updateStatus = WrapStatusUpdate(status, statusDetails);
+ return item =>
+ {
+ if (statusesToOverwrite.Contains(item.status))
+ {
+ updateStatus(item);
+ }
+ };
+ }
- internal static string NewId()
- {
- return Guid.NewGuid().ToString("N");
- }
+ internal static Action WrapStatusInit(
+ Status status,
+ StatusDetails statusDetails
+ ) =>
+ WrapStatusOverwrite(status, statusDetails, Status.none);
+
+ internal static Action WrapStatusInit(
+ Status status
+ ) =>
+ WrapStatusOverwrite(status, null, Status.none);
+
+ internal static Action WrapStatusInit(
+ Status status,
+ Exception error
+ )
+ {
+ var updateStatus = WrapStatusUpdate(
+ status,
+ PluginHelper.GetStatusDetails(error)
+ );
+ return item =>
+ {
+ if (item.status is Status.none)
+ {
+ updateStatus(item);
+ }
+ };
+ }
- internal static FixtureResult GetFixtureResult(HookBinding hook)
- {
- return new FixtureResult
- {
- name = $"{hook.Method.Name} [{hook.HookOrder}]"
- };
- }
+ internal static bool IsIgnoreException(Exception e) =>
+ e?.GetType().Name.Contains(IGNORE_EXCEPTION) is true;
+
+ internal static Action TestStatusResolver(
+ ScenarioContext scenarioContext
+ ) =>
+ test => (test.status, test.statusDetails) = (
+ ResolveTestCaseStatus(scenarioContext, test.status),
+ ResolveTestCaseDetails(
+ scenarioContext,
+ test.status,
+ test.statusDetails
+ )
+ );
+
+ static Status ResolveTestCaseStatus(
+ ScenarioContext scenarioContext,
+ Status status
+ ) =>
+ status switch
+ {
+ Status.none =>
+ scenarioContext.ScenarioExecutionStatus switch
+ {
+ ScenarioExecutionStatus.OK => Status.passed,
+ ScenarioExecutionStatus.Skipped => Status.skipped,
+ ScenarioExecutionStatus.TestError
+ when IsIgnoreException(
+ scenarioContext.TestError
+ ) => Status.skipped,
+ ScenarioExecutionStatus.TestError => Status.broken,
+ _ => Status.broken
+ },
+ _ => status
+ };
+
+ static StatusDetails ResolveTestCaseDetails(
+ ScenarioContext scenarioContext,
+ Status status,
+ StatusDetails statusDetails
+ ) =>
+ status switch
+ {
+ Status.none
+ when scenarioContext.ScenarioExecutionStatus
+ is ScenarioExecutionStatus.TestError
+ => GetStatusDetails(scenarioContext.TestError),
+ _ => statusDetails
+ };
+
+ static string GetFullExceptionMessage(Exception ex) =>
+ ex.Message + (
+ !string.IsNullOrWhiteSpace(ex.InnerException?.Message)
+ ? $" -> {GetFullExceptionMessage(ex.InnerException!)}"
+ : string.Empty
+ );
+
+ static Tuple, List > GetTags(
+ FeatureInfo featureInfo,
+ ScenarioInfo scenarioInfo
+ )
+ {
+ var result = Tuple.Create(new List(), new List ());
- internal static TestResult StartTestCase(string containerId, FeatureContext featureContext,
- ScenarioContext scenarioContext)
- {
- var featureInfo = featureContext?.FeatureInfo ?? emptyFeatureInfo;
- var scenarioInfo = scenarioContext?.ScenarioInfo ?? emptyScenarioInfo;
- var tags = GetTags(featureInfo, scenarioInfo);
- var parameters = GetParameters(scenarioInfo);
- var title = scenarioInfo.Title;
- var testResult = new TestResult
- {
- uuid = NewId(),
- historyId = title + parameters.hash,
- name = title,
- fullName = title,
- labels = new List
+ var tags = scenarioInfo.Tags
+ .Union(featureInfo.Tags)
+ .Distinct(StringComparer.CurrentCultureIgnoreCase);
+
+ foreach (var tag in tags)
+ {
+ var tagValue = tag;
+ // link
+ if (TryUpdateValueByMatch(PluginConfiguration.links.link, ref tagValue))
{
- Label.Thread(),
- string.IsNullOrWhiteSpace(AllureLifecycle.Instance.AllureConfiguration.Title)
- ? Label.Host()
- : Label.Host(AllureLifecycle.Instance.AllureConfiguration.Title),
- Label.Feature(featureInfo.Title)
+ result.Item2.Add(new()
+ {
+ name = tagValue,
+ url = tagValue
+ });
+ continue;
}
- .Union(tags.Item1).ToList(),
- links = tags.Item2,
- parameters = parameters.parameters
- };
- AllureLifecycle.Instance.StartTestCase(containerId, testResult);
- scenarioContext?.Set(testResult);
- featureContext?.Get>().Add(testResult);
+ // issue
+ if (TryUpdateValueByMatch(PluginConfiguration.links.issue, ref tagValue))
+ {
+ result.Item2.Add(
+ Link.Issue(tagValue, tagValue)
+ );
+ continue;
+ }
- return testResult;
- }
+ // tms
+ if (TryUpdateValueByMatch(PluginConfiguration.links.tms, ref tagValue))
+ {
+ result.Item2.Add(
+ Link.Tms(tagValue, tagValue)
+ );
+ continue;
+ }
- internal static TestResult GetCurrentTestCase(ScenarioContext context)
- {
- context.TryGetValue(out TestResult testresult);
- return testresult;
- }
+ // parent suite
+ if (TryUpdateValueByMatch(PluginConfiguration.grouping.suites.parentSuite, ref tagValue))
+ {
+ result.Item1.Add(
+ Label.ParentSuite(tagValue)
+ );
+ continue;
+ }
- internal static TestResultContainer StartTestContainer(FeatureContext featureContext,
- ScenarioContext scenarioContext)
- {
- var containerId = GetFeatureContainerId(featureContext?.FeatureInfo);
+ // suite
+ if (TryUpdateValueByMatch(PluginConfiguration.grouping.suites.suite, ref tagValue))
+ {
+ result.Item1.Add(
+ Label.Suite(tagValue)
+ );
+ continue;
+ }
- var scenarioContainer = new TestResultContainer
- {
- uuid = NewId()
- };
- AllureLifecycle.Instance.StartTestContainer(containerId, scenarioContainer);
- scenarioContext?.Set(scenarioContainer);
- featureContext?.Get>().Add(scenarioContainer);
+ // sub suite
+ if (TryUpdateValueByMatch(PluginConfiguration.grouping.suites.subSuite, ref tagValue))
+ {
+ result.Item1.Add(
+ Label.SubSuite(tagValue)
+ );
+ continue;
+ }
- return scenarioContainer;
- }
+ // epic
+ if (TryUpdateValueByMatch(PluginConfiguration.grouping.behaviors.epic, ref tagValue))
+ {
+ result.Item1.Add(
+ Label.Epic(tagValue)
+ );
+ continue;
+ }
- internal static TestResultContainer GetCurrentTestConainer(ScenarioContext context)
- {
- context.TryGetValue(out TestResultContainer testresultContainer);
- return testresultContainer;
- }
+ // story
+ if (TryUpdateValueByMatch(PluginConfiguration.grouping.behaviors.story, ref tagValue))
+ {
+ result.Item1.Add(
+ Label.Story(tagValue)
+ );
+ continue;
+ }
- internal static StatusDetails GetStatusDetails(Exception ex)
- {
- return new StatusDetails
- {
- message = GetFullExceptionMessage(ex),
- trace = ex.ToString()
- };
- }
+ // package
+ if (TryUpdateValueByMatch(PluginConfiguration.grouping.packages.package, ref tagValue))
+ {
+ result.Item1.Add(
+ Label.Package(tagValue)
+ );
+ continue;
+ }
- private static string GetFullExceptionMessage(Exception ex)
- {
- return ex.Message +
- (!string.IsNullOrWhiteSpace(ex.InnerException?.Message)
- ? $" -> {GetFullExceptionMessage(ex.InnerException)}"
- : string.Empty);
- }
+ // test class
+ if (TryUpdateValueByMatch(PluginConfiguration.grouping.packages.testClass, ref tagValue))
+ {
+ result.Item1.Add(
+ Label.TestClass(tagValue)
+ );
+ continue;
+ }
- private static Tuple, List > GetTags(FeatureInfo featureInfo, ScenarioInfo scenarioInfo)
- {
- var result = Tuple.Create(new List(), new List ());
+ // test method
+ if (TryUpdateValueByMatch(PluginConfiguration.grouping.packages.testMethod, ref tagValue))
+ {
+ result.Item1.Add(
+ Label.TestMethod(tagValue)
+ );
+ continue;
+ }
- var tags = scenarioInfo.Tags
- .Union(featureInfo.Tags)
- .Distinct(StringComparer.CurrentCultureIgnoreCase);
+ // owner
+ if (TryUpdateValueByMatch(PluginConfiguration.labels.owner, ref tagValue))
+ {
+ result.Item1.Add(
+ Label.Owner(tagValue)
+ );
+ continue;
+ }
- foreach (var tag in tags)
- {
- var tagValue = tag;
- // link
- if (TryUpdateValueByMatch(PluginConfiguration.links.link, ref tagValue))
- {
- result.Item2.Add(new Link { name = tagValue, url = tagValue });
- continue;
- }
+ // severity
+ if (TryUpdateValueByMatch(PluginConfiguration.labels.severity, ref tagValue) &&
+ Enum.TryParse(tagValue, out SeverityLevel level))
+ {
+ result.Item1.Add(
+ Label.Severity(level)
+ );
+ continue;
+ }
- // issue
- if (TryUpdateValueByMatch(PluginConfiguration.links.issue, ref tagValue))
- {
- result.Item2.Add(Link.Issue(tagValue, tagValue));
- continue;
- }
+ // label
+ if (GetLabelProps(PluginConfiguration.labels.label, tagValue, out var props))
+ {
+ result.Item1.Add(new()
+ {
+ name = props.Key,
+ value = props.Value
+ });
+ continue;
+ }
- // tms
- if (TryUpdateValueByMatch(PluginConfiguration.links.tms, ref tagValue))
- {
- result.Item2.Add(Link.Tms(tagValue, tagValue));
- continue;
- }
+ // tag
+ result.Item1.Add(
+ Label.Tag(tagValue)
+ );
+ }
- // parent suite
- if (TryUpdateValueByMatch(PluginConfiguration.grouping.suites.parentSuite, ref tagValue))
- {
- result.Item1.Add(Label.ParentSuite(tagValue));
- continue;
+ return result;
}
- // suite
- if (TryUpdateValueByMatch(PluginConfiguration.grouping.suites.suite, ref tagValue))
+ static (List parameters, string hash) GetParameters(
+ ScenarioInfo scenarioInfo
+ )
{
- result.Item1.Add(Label.Suite(tagValue));
- continue;
+ var sb = new StringBuilder();
+ var parameters = new List();
+ var argumentsEnumerator = scenarioInfo.Arguments.GetEnumerator();
+ while (argumentsEnumerator.MoveNext())
+ {
+ sb.Append(argumentsEnumerator.Key.ToString());
+ sb.Append(argumentsEnumerator.Value.ToString());
+
+ parameters.Add(new()
+ {
+ name = argumentsEnumerator.Key.ToString(),
+ value = argumentsEnumerator.Value.ToString()
+ });
+ }
+ var hash = (parameters.Count > 0)
+ ? sb.ToString().GetDeterministicHashCode().ToString()
+ : string.Empty;
+ return (parameters, hash);
}
- // sub suite
- if (TryUpdateValueByMatch(PluginConfiguration.grouping.suites.subSuite, ref tagValue))
+ static bool TryUpdateValueByMatch(
+ string? expression,
+ ref string? value
+ )
{
- result.Item1.Add(Label.SubSuite(tagValue));
- continue;
- }
+ var matchedGroups = GetMatchedGroups(expression, value);
- // epic
- if (TryUpdateValueByMatch(PluginConfiguration.grouping.behaviors.epic, ref tagValue))
- {
- result.Item1.Add(Label.Epic(tagValue));
- continue;
- }
+ if (!matchedGroups.Any())
+ {
+ return false;
+ }
- // story
- if (TryUpdateValueByMatch(PluginConfiguration.grouping.behaviors.story, ref tagValue))
- {
- result.Item1.Add(Label.Story(tagValue));
- continue;
- }
+ value = matchedGroups.Count == 1
+ ? matchedGroups[0]
+ : matchedGroups[1];
- // package
- if (TryUpdateValueByMatch(PluginConfiguration.grouping.packages.package, ref tagValue))
- {
- result.Item1.Add(Label.Package(tagValue));
- continue;
+ return true;
}
- // test class
- if (TryUpdateValueByMatch(PluginConfiguration.grouping.packages.testClass, ref tagValue))
+ static bool GetLabelProps(
+ string? expression,
+ string? value,
+ out KeyValuePair props
+ )
{
- result.Item1.Add(Label.TestClass(tagValue));
- continue;
- }
+ props = default;
- // test method
- if (TryUpdateValueByMatch(PluginConfiguration.grouping.packages.testMethod, ref tagValue))
- {
- result.Item1.Add(Label.TestMethod(tagValue));
- continue;
- }
+ var matchedGroups = GetMatchedGroups(expression, value);
- // owner
- if (TryUpdateValueByMatch(PluginConfiguration.labels.owner, ref tagValue))
- {
- result.Item1.Add(Label.Owner(tagValue));
- continue;
- }
+ if (matchedGroups.Count != 3)
+ {
+ return false;
+ }
- // severity
- if (TryUpdateValueByMatch(PluginConfiguration.labels.severity, ref tagValue) &&
- Enum.TryParse(tagValue, out SeverityLevel level))
- {
- result.Item1.Add(Label.Severity(level));
- continue;
- }
+ props = new(matchedGroups[1], matchedGroups[2]);
+ return true;
- // label
- if (GetLabelProps(PluginConfiguration.labels.label, tagValue, out var props))
- {
- result.Item1.Add(new Label { name = props.Key, value = props.Value});
- continue;
}
-
- // tag
- result.Item1.Add(Label.Tag(tagValue));
- }
- return result;
- }
-
- private static (List parameters, string hash) GetParameters(ScenarioInfo scenarioInfo)
- {
- var sb = new StringBuilder();
- var parameters = new List();
- var argumentsEnumerator = scenarioInfo.Arguments.GetEnumerator();
- while (argumentsEnumerator.MoveNext())
- {
- sb.Append(argumentsEnumerator.Key.ToString());
- sb.Append(argumentsEnumerator.Value.ToString());
-
- parameters.Add(new Parameter { name = argumentsEnumerator.Key.ToString(), value = argumentsEnumerator.Value.ToString() });
- }
- var hash = (parameters.Count > 0) ? sb.ToString().GetDeterministicHashCode().ToString() : string.Empty;
- return (parameters, hash);
- }
-
- private static bool TryUpdateValueByMatch(string expression, ref string value)
- {
- var matchedGroups = GetMatchedGroups(expression, value);
-
- if (!matchedGroups.Any()) return false;
-
- if (matchedGroups.Count == 1)
- value = matchedGroups[0];
- else
- value = matchedGroups[1];
- return true;
- }
+ static List GetMatchedGroups(
+ string? expression,
+ string? value
+ )
+ {
+ var matchedGroups = new List();
+ if (string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(expression))
+ {
+ return matchedGroups;
+ }
+
+ Regex regex;
+ try
+ {
+ regex = new Regex(
+ expression,
+ RegexOptions.Compiled
+ | RegexOptions.Singleline
+ | RegexOptions.IgnoreCase
+ );
+ }
+ catch (Exception)
+ {
+ return matchedGroups;
+ }
+
+ if (regex == null)
+ {
+ return matchedGroups;
+ }
+
+ if (regex.IsMatch(value))
+ {
+ var groups = regex.Match(value).Groups;
+
+ for (var i = 0; i < groups.Count; i++)
+ {
+ matchedGroups.Add(groups[i].Value);
+ }
- private static bool GetLabelProps(string expression, string value, out KeyValuePair props)
- {
- props = default;
-
- var matchedGroups = GetMatchedGroups(expression, value);
+ return matchedGroups;
+ }
- if (matchedGroups.Count != 3)
- return false;
-
- props = new KeyValuePair(matchedGroups[1], matchedGroups[2]);
- return true;
+ return matchedGroups;
+ }
- }
-
- private static List GetMatchedGroups(string expression, string value)
- {
- var matchedGroups = new List();
- if (string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(expression))
- return matchedGroups;
-
- Regex regex = null;
- try
- {
- regex = new Regex(expression,
- RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
- }
- catch (Exception)
- {
- return matchedGroups;
- }
-
- if (regex == null)
- return matchedGroups;
-
- if (regex.IsMatch(value))
- {
- var groups = regex.Match(value).Groups;
-
- for (var i = 0; i < groups.Count; i++)
+ static int GetDeterministicHashCode(this string str)
{
- matchedGroups.Add(groups[i].Value);
- }
+ unchecked
+ {
+ int hash1 = (5381 << 16) + 5381;
+ int hash2 = hash1;
- return matchedGroups;
- }
+ for (int i = 0; i < str.Length; i += 2)
+ {
+ hash1 = ((hash1 << 5) + hash1) ^ str[i];
+ if (i == str.Length - 1)
+ {
+ break;
+ }
- return matchedGroups;
- }
-
- private static int GetDeterministicHashCode(this string str)
- {
- unchecked
- {
- int hash1 = (5381 << 16) + 5381;
- int hash2 = hash1;
+ hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
+ }
- for (int i = 0; i < str.Length; i += 2)
- {
- hash1 = ((hash1 << 5) + hash1) ^ str[i];
- if (i == str.Length - 1)
- break;
- hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
+ return hash1 + (hash2 * 1566083941);
+ }
}
-
- return hash1 + (hash2 * 1566083941);
- }
}
- }
}
From ee741ba09d4c2a2a9f2b2a8189115828f8749d26 Mon Sep 17 00:00:00 2001
From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com>
Date: Mon, 31 Jul 2023 16:23:26 +0700
Subject: [PATCH 15/24] Separate context from storage
- AllureContext moved to Allure.Net.Commons namespace
- Context and storage are separated; Storage will be removed later
- File scope namespaces for lifecycle and context
---
.../AllureContextTests.cs | 4 +-
.../AllureLifeCycleTest.cs | 6 +-
Allure.Net.Commons.Tests/ConcurrencyTests.cs | 1 -
Allure.Net.Commons/AllureContext.cs | 473 +++++
Allure.Net.Commons/AllureLifecycle.cs | 1773 +++++++++--------
Allure.Net.Commons/Storage/AllureContext.cs | 474 -----
Allure.Net.Commons/Storage/AllureStorage.cs | 254 +--
Allure.SpecFlowPlugin/PluginHelper.cs | 1 -
Allure.XUnit/AllureMessageSink.cs | 1 -
Allure.XUnit/AllureXunitTestData.cs | 2 +-
10 files changed, 1401 insertions(+), 1588 deletions(-)
create mode 100644 Allure.Net.Commons/AllureContext.cs
delete mode 100644 Allure.Net.Commons/Storage/AllureContext.cs
diff --git a/Allure.Net.Commons.Tests/AllureContextTests.cs b/Allure.Net.Commons.Tests/AllureContextTests.cs
index 645bed5d..894aaaaa 100644
--- a/Allure.Net.Commons.Tests/AllureContextTests.cs
+++ b/Allure.Net.Commons.Tests/AllureContextTests.cs
@@ -1,6 +1,4 @@
-using System;
-using Allure.Net.Commons.Storage;
-using NUnit.Framework;
+using NUnit.Framework;
namespace Allure.Net.Commons.Tests
{
diff --git a/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs b/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
index a8df397d..29a519ac 100644
--- a/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
+++ b/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
@@ -1,8 +1,6 @@
-using Allure.Net.Commons.Storage;
-using NUnit.Framework;
-using System;
-using System.Threading;
+using System;
using System.Threading.Tasks;
+using NUnit.Framework;
namespace Allure.Net.Commons.Tests
{
diff --git a/Allure.Net.Commons.Tests/ConcurrencyTests.cs b/Allure.Net.Commons.Tests/ConcurrencyTests.cs
index f014eb20..98a037c8 100644
--- a/Allure.Net.Commons.Tests/ConcurrencyTests.cs
+++ b/Allure.Net.Commons.Tests/ConcurrencyTests.cs
@@ -3,7 +3,6 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using System.Xml.Linq;
using NUnit.Framework;
using NUnit.Framework.Internal;
diff --git a/Allure.Net.Commons/AllureContext.cs b/Allure.Net.Commons/AllureContext.cs
new file mode 100644
index 00000000..612b742c
--- /dev/null
+++ b/Allure.Net.Commons/AllureContext.cs
@@ -0,0 +1,473 @@
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+
+#nullable enable
+
+namespace Allure.Net.Commons;
+
+///
+/// Represents allure-related contextual information required to collect
+/// the report data during a test execution. Comprises four contexts:
+/// container, fxiture, test, and step, as well as methods to query and
+/// modify them.
+///
+///
+/// Instances of this class are immutable to ensure proper isolation
+/// between different tests and steps that may potentially be run
+/// cuncurrently either by a test framework or by an end user.
+///
+[DebuggerDisplay(
+ "Containers = {ContainerContextDepth}, HasFixture = {HasFixture}, " +
+ "HasTest = {HasTest}, Steps = {StepContextDepth}"
+)]
+public record class AllureContext
+{
+ ///
+ /// Returns true if a container context is active.
+ ///
+ public bool HasContainer => !this.ContainerContext.IsEmpty;
+
+ ///
+ /// Returns the number of containers in the container context.
+ ///
+ public int ContainerContextDepth => this.ContainerContext.Count();
+
+ ///
+ /// Returns true if a fixture context is active.
+ ///
+ public bool HasFixture => this.FixtureContext is not null;
+
+ ///
+ /// Returns true if a test context is active.
+ ///
+ public bool HasTest => this.TestContext is not null;
+
+ ///
+ /// Returns true if a step context is active.
+ ///
+ public bool HasStep => !this.StepContext.IsEmpty;
+
+ ///
+ /// Returns the number of steps in the step context.
+ ///
+ public int StepContextDepth => this.StepContext.Count();
+
+ ///
+ /// A stack of fixture containers affecting subsequent tests.
+ ///
+ ///
+ /// Activating this context allows operations on the current container
+ /// (including adding a fixture to or removing a fixture from the
+ /// current container).
+ ///
+ internal IImmutableStack ContainerContext
+ {
+ get;
+ private init;
+ } = ImmutableStack.Empty;
+
+ ///
+ /// A fixture that is being currently executed.
+ ///
+ ///
+ /// Activating this context allows operations on the current fixture
+ /// result.
+ /// This property differs from in that
+ /// instead of throwing it returns null if a fixture context isn't
+ /// active.
+ ///
+ internal FixtureResult? FixtureContext { get; private init; }
+
+ ///
+ /// A test that is being executed.
+ ///
+ ///
+ /// Activating this context allows operations on the current test
+ /// result.
+ ///
+ /// This property differs from in that
+ /// instead of throwing it returns null if a test context isn't active.
+ ///
+ internal TestResult? TestContext { get; private init; }
+
+ ///
+ /// A stack of nested steps that are being executed.
+ ///
+ ///
+ /// Activating this context allows operations on the current step.
+ ///
+ internal IImmutableStack StepContext
+ {
+ get;
+ private init;
+ } = ImmutableStack.Empty;
+
+ ///
+ /// The most recently added container from the container context.
+ ///
+ ///
+ /// It throws if a container
+ /// context isn't active.
+ ///
+ ///
+ internal TestResultContainer CurrentContainer
+ {
+ get => this.ContainerContext.FirstOrDefault()
+ ?? throw new InvalidOperationException(
+ "No container context is active."
+ );
+ }
+
+ ///
+ /// A fixture that is being executed.
+ ///
+ ///
+ /// It throws if a fixture
+ /// context isn't active.
+ ///
+ ///
+ internal FixtureResult CurrentFixture =>
+ this.FixtureContext ?? throw new InvalidOperationException(
+ "No fixture context is active."
+ );
+
+ ///
+ /// A test that is being executed.
+ ///
+ ///
+ /// It throws if a test context
+ /// isn't active.
+ ///
+ ///
+ internal TestResult CurrentTest =>
+ this.TestContext ?? throw new InvalidOperationException(
+ "No test context is active."
+ );
+
+ ///
+ /// A step that is being executed.
+ ///
+ ///
+ /// It throws if a step context
+ /// isn't active.
+ ///
+ ///
+ internal StepResult CurrentStep =>
+ this.StepContext.FirstOrDefault()
+ ?? throw new InvalidOperationException(
+ "No step context is active."
+ );
+
+ ///
+ /// A step container a next step should be put in.
+ ///
+ ///
+ /// A step container can be a fixture, a test of an another step.
+ /// It throws if neither
+ /// fixture, nor test, nor step context is active.
+ ///
+ ///
+ internal ExecutableItem CurrentStepContainer =>
+ this.StepContext.FirstOrDefault() as ExecutableItem
+ ?? this.RootStepContainer
+ ?? throw new InvalidOperationException(
+ "No fixture, test, or step context is active."
+ );
+
+ ///
+ /// Used by to serialize proeprties of the
+ /// context.
+ ///
+ protected virtual bool PrintMembers(StringBuilder stringBuilder)
+ {
+ var containers =
+ RepresentStack(this.ContainerContext, c => c.name ?? c.uuid);
+ var fixture = this.FixtureContext?.name ?? "null";
+ var test = this.TestContext?.name
+ ?? this.TestContext?.uuid
+ ?? "null";
+ var steps = RepresentStack(this.StepContext, s => s.name);
+
+ stringBuilder.AppendFormat("Containers = [{0}], ", containers);
+ stringBuilder.AppendFormat("Fixture = {0}, ", fixture);
+ stringBuilder.AppendFormat("Test = {0}, ", test);
+ stringBuilder.AppendFormat("Steps = [{0}]", steps);
+ return true;
+ }
+
+ ///
+ /// Creates a new with the active container
+ /// context and the specified container pushed on top of it.
+ ///
+ ///
+ /// Can't be called if a fixture or a test context is active.
+ ///
+ ///
+ /// A container to push on top of the container context.
+ ///
+ ///
+ /// A new instance of with the modified
+ /// (always active) container context.
+ ///
+ ///
+ internal AllureContext WithContainer(TestResultContainer container) =>
+ this.ValidateContainerContextCanBeModified() with
+ {
+ ContainerContext = this.ContainerContext.Push(
+ container ?? throw new ArgumentNullException(
+ nameof(container)
+ )
+ )
+ };
+
+ ///
+ /// Creates a new without the most recently
+ /// added container in its container context. Requires an active
+ /// container context. Deactivates a container context if it consists
+ /// of one container only before the call.
+ ///
+ ///
+ /// Can't be called if a fixture or a test context is active.
+ ///
+ ///
+ /// A new instance of with the modified
+ /// (possibly inactive) container context.
+ ///
+ ///
+ internal AllureContext WithNoLastContainer() =>
+ this with
+ {
+ ContainerContext = this.ValidateContainerCanBeRemoved()
+ .ContainerContext.Pop()
+ };
+
+ ///
+ /// Creates a new with the active fixture
+ /// context that is set to the specified fixture. Requires the
+ /// container context to be active.
+ ///
+ ///
+ /// Only one fixture context can be active at a time.
+ ///
+ ///
+ /// A new fixture context.
+ ///
+ ///
+ /// A new instance of with the modified
+ /// (always active) fixture context.
+ ///
+ ///
+ ///
+ internal AllureContext WithFixtureContext(FixtureResult fixtureResult) =>
+ this with
+ {
+ FixtureContext = this.ValidateNewFixtureContext(
+ fixtureResult ?? throw new ArgumentNullException(
+ nameof(fixtureResult)
+ )
+ ),
+ StepContext = this.StepContext.Clear()
+ };
+
+ ///
+ /// Creates a new with inactive fixture and
+ /// step contexts.
+ ///
+ internal AllureContext WithNoFixtureContext() =>
+ this with
+ {
+ FixtureContext = null,
+ StepContext = this.StepContext.Clear()
+ };
+
+ ///
+ /// Creates a new with the active test
+ /// context that is set to the specified test result.
+ /// Can't be used if a fixture context is active.
+ ///
+ ///
+ /// A new test context.
+ ///
+ ///
+ /// A new instance of with the modified
+ /// (always active) test context.
+ ///
+ ///
+ ///
+ internal AllureContext WithTestContext(TestResult testResult) =>
+ this with
+ {
+ TestContext = this.ValidateNewTestContext(
+ testResult ?? throw new ArgumentNullException(
+ nameof(testResult)
+ )
+ )
+ };
+
+ ///
+ /// Creates a new with inactive test,
+ /// fixture and step contexts.
+ ///
+ internal AllureContext WithNoTestContext() =>
+ this with
+ {
+ FixtureContext = null,
+ TestContext = null,
+ StepContext = this.StepContext.Clear()
+ };
+
+ ///
+ /// Creates a new with the active step
+ /// context and the specified step result pushed on top of it.
+ ///
+ ///
+ /// Can't be called if neither fixture, nor test context is active.
+ ///
+ ///
+ /// A new step result to push on top of the step context.
+ ///
+ ///
+ /// A new instance of with the modified
+ /// (always active) step context.
+ ///
+ ///
+ ///
+ internal AllureContext WithStep(StepResult stepResult) =>
+ this with
+ {
+ StepContext = this.StepContext.Push(
+ this.ValidateNewStep(
+ stepResult ?? throw new ArgumentNullException(
+ nameof(stepResult)
+ )
+ )
+ )
+ };
+
+ ///
+ /// Creates a new without the most recently
+ /// added step in its step context. Requires an active step context.
+ /// Deactivates a step context if it consists of one step only before
+ /// the call.
+ ///
+ ///
+ /// A new instance of with the modified
+ /// (possibly inactive) step context.
+ ///
+ ///
+ internal AllureContext WithNoLastStep() =>
+ this with
+ {
+ StepContext = this.HasStep
+ ? this.StepContext.Pop()
+ : throw new InvalidOperationException(
+ "Unable to deactivate the step context because it " +
+ "isn't active."
+ )
+ };
+
+ AllureContext ValidateContainerContextCanBeModified()
+ {
+ if (this.FixtureContext is not null)
+ {
+ throw new InvalidOperationException(
+ "Unable to change the container context because the " +
+ "fixture context is active."
+ );
+ }
+
+ if (this.TestContext is not null)
+ {
+ throw new InvalidOperationException(
+ "Unable to change the container context because the " +
+ "test context is active."
+ );
+ }
+
+ return this;
+ }
+
+ AllureContext ValidateContainerCanBeRemoved()
+ {
+ if (!this.HasContainer)
+ {
+ throw new InvalidOperationException(
+ "Unable to deactivate the container context because it " +
+ "is not active."
+ );
+ }
+
+ return this.ValidateContainerContextCanBeModified();
+ }
+
+ ExecutableItem? RootStepContainer
+ {
+ get => this.FixtureContext as ExecutableItem ?? this.TestContext;
+ }
+
+ FixtureResult ValidateNewFixtureContext(FixtureResult fixture)
+ {
+ if (!this.HasContainer)
+ {
+ throw new InvalidOperationException(
+ "Unable to activate the fixture context " +
+ "because the container context is not active."
+ );
+ }
+
+ if (this.HasFixture)
+ {
+ throw new InvalidOperationException(
+ "Unable to activate the fixture context " +
+ "because it's already active."
+ );
+ }
+
+ return fixture;
+ }
+
+ TestResult ValidateNewTestContext(TestResult testResult)
+ {
+ if (this.HasFixture)
+ {
+ throw new InvalidOperationException(
+ "Unable to activate the test context " +
+ "because the fixture context is active."
+ );
+ }
+
+ if (this.HasTest)
+ {
+ throw new InvalidOperationException(
+ "Unable to activate the test context " +
+ "because it is already active."
+ );
+ }
+
+ return testResult;
+ }
+
+ StepResult ValidateNewStep(StepResult stepResult)
+ {
+ if (!this.HasTest && !this.HasFixture)
+ {
+ throw new InvalidOperationException(
+ "Unable to activate the step context because neither " +
+ "test, nor fixture context is active."
+ );
+ }
+
+ return stepResult;
+ }
+
+ static string RepresentStack(
+ IImmutableStack stack,
+ Func projection
+ ) => string.Join(
+ " <- ",
+ stack.Select(projection)
+ );
+}
diff --git a/Allure.Net.Commons/AllureLifecycle.cs b/Allure.Net.Commons/AllureLifecycle.cs
index 1e15dbfe..4a6f7359 100644
--- a/Allure.Net.Commons/AllureLifecycle.cs
+++ b/Allure.Net.Commons/AllureLifecycle.cs
@@ -3,7 +3,9 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
+using System.Linq;
using System.Runtime.CompilerServices;
+using System.Threading;
using Allure.Net.Commons.Configuration;
using Allure.Net.Commons.Storage;
using Allure.Net.Commons.Writer;
@@ -14,988 +16,1059 @@
[assembly: InternalsVisibleTo("Allure.Net.Commons.Tests")]
-namespace Allure.Net.Commons
+namespace Allure.Net.Commons;
+
+///
+/// A facade that allows to control the Allure context, set up allure model
+/// objects and emit output files.
+///
+///
+/// This class is primarily intended to be used by a test framework
+/// integration. We don't advice to use it from test code unless strictly
+/// necessary.
+/// NOTE: Modifications of the Allure context persist until either some
+/// method has affect them, or the execution context is restored to the
+/// point beyond the call that had introduced them.
+///
+public class AllureLifecycle
{
+ private readonly Dictionary typeFormatters = new();
+ private static readonly Lazy instance =
+ new(Initialize);
+
+ public IReadOnlyDictionary TypeFormatters =>
+ new ReadOnlyDictionary(typeFormatters);
+
+ readonly AllureStorage storage;
+ readonly AsyncLocal context = new();
+
+ readonly IAllureResultsWriter writer;
+
///
- /// A facade that allows to control the Allure context, set up allure model
- /// objects and emit output files.
+ /// Protects mutations of shared allure model objects against data
+ /// races that may otherwise occur because of multithreaded access.
///
- ///
- /// This class is primarily intended to be used by a test framework
- /// integration. We don't advice to use it from test code unless strictly
- /// necessary.
- /// NOTE: Modifications of the Allure context persist until either some
- /// method has affect them, or the execution context is restored to the
- /// point beyond the call that had introduced them.
- ///
- public class AllureLifecycle
+ readonly object modelMonitor = new();
+
+
+ ///
+ /// Captures the current value of Allure context.
+ ///
+ public AllureContext Context
{
- private readonly Dictionary typeFormatters = new();
+ get => this.context.Value ??= new AllureContext();
+ private set => this.context.Value = value;
+ }
- public IReadOnlyDictionary TypeFormatters =>
- new ReadOnlyDictionary(typeFormatters);
+ internal AllureLifecycle() : this(GetConfiguration())
+ {
+ }
- private static readonly Lazy instance =
- new(Initialize);
- private readonly AllureStorage storage;
- private readonly IAllureResultsWriter writer;
+ internal AllureLifecycle(
+ Func writerFactory
+ ) : this(GetConfiguration(), writerFactory)
+ {
+ }
- ///
- /// Protects mutations of shared allure model objects against data
- /// races that may otherwise occur because of multithreaded access.
- ///
- readonly object modelMonitor = new();
+ internal AllureLifecycle(JObject config)
+ : this(config, c => new FileSystemResultsWriter(c))
+ {
+ }
+ internal AllureLifecycle(
+ JObject config,
+ Func writerFactory
+ )
+ {
+ JsonConfiguration = config.ToString();
+ AllureConfiguration = AllureConfiguration.ReadFromJObject(config);
+ writer = writerFactory(AllureConfiguration);
+ storage = new AllureStorage();
+ }
- ///
- /// Captures the current value of Allure context.
- ///
- public AllureContext Context
- {
- get => this.storage.CurrentContext;
- private set => this.storage.CurrentContext = value;
- }
+ public string JsonConfiguration { get; private set; }
- ///
- /// Binds the provided value as the current Allure context and executes
- /// the specified function. The context is then restored to the initial
- /// value. This allows the Allure context to bypass .NET execution
- /// context boundaries.
- ///
- ///
- /// A context that was previously captured with .
- /// If it is null, the code is executed in the current context.
- ///
- /// A code to run.
- /// The context after the code is executed.
- public AllureContext RunInContext(
- AllureContext? context,
- Action action
- )
- {
- if (context is null)
- {
- action();
- return this.Context;
- }
+ public AllureConfiguration AllureConfiguration { get; }
- var originalContext = this.Context;
- try
- {
- this.Context = context;
- action();
- return this.Context;
- }
- finally
- {
- this.Context = originalContext;
- }
- }
+ public string ResultsDirectory => writer.ToString();
- internal AllureLifecycle(): this(GetConfiguration())
- {
- }
+ public static AllureLifecycle Instance { get => instance.Value; }
+
+ public void AddTypeFormatter(TypeFormatter typeFormatter) =>
+ AddTypeFormatterImpl(typeof(T), typeFormatter);
+
+ private void AddTypeFormatterImpl(Type type, ITypeFormatter formatter) =>
+ typeFormatters[type] = formatter;
- internal AllureLifecycle(
- Func writerFactory
- ) : this(GetConfiguration(), writerFactory)
+ ///
+ /// Binds the provided value as the current Allure context and executes
+ /// the specified function. The context is then restored to the initial
+ /// value. This allows the Allure context to bypass .NET execution
+ /// context boundaries.
+ ///
+ ///
+ /// A context that was previously captured with .
+ /// If it is null, the code is executed in the current context.
+ ///
+ /// A code to run.
+ /// The context after the code is executed.
+ public AllureContext RunInContext(
+ AllureContext? context,
+ Action action
+ )
+ {
+ if (context is null)
{
+ action();
+ return this.Context;
}
- internal AllureLifecycle(JObject config)
- : this(config, c => new FileSystemResultsWriter(c))
+ var originalContext = this.Context;
+ try
{
+ this.Context = context;
+ action();
+ return this.Context;
}
-
- internal AllureLifecycle(
- JObject config,
- Func writerFactory
- )
+ finally
{
- JsonConfiguration = config.ToString();
- AllureConfiguration = AllureConfiguration.ReadFromJObject(config);
- writer = writerFactory(AllureConfiguration);
- storage = new AllureStorage();
+ this.Context = originalContext;
}
+ }
- public string JsonConfiguration { get; private set; }
+ #region TestContainer
- public AllureConfiguration AllureConfiguration { get; }
+ ///
+ /// Starts a new test container and pushes it into the container
+ /// context making the container context active. The container becomes
+ /// the current one in the current execution context.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Can't be called if the fixture or the test context is active.
+ ///
+ /// A new test container to start.
+ ///
+ public virtual AllureLifecycle StartTestContainer(
+ TestResultContainer container
+ )
+ {
+ container.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ this.storage.Put(container.uuid, container);
+ this.UpdateContext(c => c.WithContainer(container));
+ return this;
+ }
- public string ResultsDirectory => writer.ToString();
+ ///
+ /// Applies the specified update function to the current test container.
+ ///
+ ///
+ /// Requires the container context to be active.
+ ///
+ ///
+ public virtual AllureLifecycle UpdateTestContainer(
+ Action update
+ )
+ {
+ var container = this.Context.CurrentContainer;
+ lock (this.modelMonitor)
+ {
+ update.Invoke(container);
+ }
+ return this;
+ }
- public static AllureLifecycle Instance { get => instance.Value; }
+ ///
+ /// Stops the current test container.
+ ///
+ ///
+ /// Requires the container context to be active.
+ ///
+ ///
+ public virtual AllureLifecycle StopTestContainer()
+ {
+ UpdateTestContainer(stopContainer);
+ return this;
+ }
- public void AddTypeFormatter(TypeFormatter typeFormatter) =>
- AddTypeFormatterImpl(typeof(T), typeFormatter);
+ ///
+ /// Writes the current test container and removes it from the context.
+ /// If there are another test containers in the context, the most
+ /// recently started one becomes the current container in the current
+ /// execution context. Otherwise the container context is deactivated.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires the container context to be active.
+ ///
+ ///
+ public virtual AllureLifecycle WriteTestContainer()
+ {
+ var container = this.Context.CurrentContainer;
+ this.storage.Remove(container.uuid);
+ this.UpdateContext(c => c.WithNoLastContainer());
+ this.writer.Write(container);
+ return this;
+ }
- private void AddTypeFormatterImpl(Type type, ITypeFormatter formatter) =>
- typeFormatters[type] = formatter;
+ #endregion
- #region TestContainer
+ #region Fixture
- ///
- /// Starts a new test container and pushes it into the container
- /// context making the container context active. The container becomes
- /// the current one in the current execution context.
- ///
- ///
- /// This method modifies the Allure context.
- /// Can't be called if the fixture or the test context is active.
- ///
- /// A new test container to start.
- ///
- public virtual AllureLifecycle StartTestContainer(
- TestResultContainer container
- )
- {
- container.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- this.storage.PutTestContainer(container.uuid, container);
- return this;
- }
+ ///
+ /// Starts a new before fixture and activates the fixture context with
+ /// it. The fixture is set as the current one in the current execution
+ /// context. Does nothing if the fixture context is already active.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires the container context to be active.
+ ///
+ /// A new fixture.
+ ///
+ public virtual AllureLifecycle StartBeforeFixture(FixtureResult result)
+ {
+ this.UpdateTestContainer(c => c.befores.Add(result));
+ this.StartFixture(result);
+ return this;
+ }
- ///
- /// Applies the specified update function to the current test container.
- ///
- ///
- /// Requires the container context to be active.
- ///
- ///
- public virtual AllureLifecycle UpdateTestContainer(
- Action update
- )
- {
- var container = this.storage.CurrentTestContainer;
- lock (this.modelMonitor)
- {
- update.Invoke(container);
- }
- return this;
- }
+ ///
+ /// Starts a new after fixture and activates the fixture context with
+ /// it. The fixture is set as the current one in the current execution
+ /// context. Does nothing if the fixture context is already active.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires the container context to be active.
+ ///
+ /// A new fixture.
+ ///
+ public virtual AllureLifecycle StartAfterFixture(FixtureResult result)
+ {
+ this.UpdateTestContainer(c => c.afters.Add(result));
+ this.StartFixture(result);
+ return this;
+ }
- ///
- /// Stops the current test container.
- ///
- ///
- /// Requires the container context to be active.
- ///
- ///
- public virtual AllureLifecycle StopTestContainer()
+ ///
+ /// Applies the specified update function to the current fixture.
+ ///
+ ///
+ /// Requires the fixture context to be active.
+ ///
+ ///
+ public virtual AllureLifecycle UpdateFixture(
+ Action update
+ )
+ {
+ var fixture = this.Context.CurrentFixture;
+ lock (this.modelMonitor)
{
- UpdateTestContainer(stopContainer);
- return this;
+ update.Invoke(fixture);
}
+ return this;
+ }
- ///
- /// Writes the current test container and removes it from the context.
- /// If there are another test containers in the context, the most
- /// recently started one becomes the current container in the current
- /// execution context. Otherwise the container context is deactivated.
- ///
- ///
- /// This method modifies the Allure context.
- /// Requires the container context to be active.
- ///
- ///
- public virtual AllureLifecycle WriteTestContainer()
- {
- var container = this.storage.CurrentTestContainer;
- this.storage.RemoveTestContainer(container.uuid);
- this.writer.Write(container);
- return this;
- }
+ ///
+ /// Stops the current fixture and deactivates the fixture context.
+ ///
+ ///
+ /// A function applied to the fixture result before it is stopped.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Required the fixture context to be active.
+ ///
+ ///
+ public virtual AllureLifecycle StopFixture(
+ Action beforeStop
+ )
+ {
+ this.UpdateFixture(beforeStop);
+ return this.StopFixture();
+ }
- #endregion
-
- #region Fixture
-
- ///
- /// Starts a new before fixture and activates the fixture context with
- /// it. The fixture is set as the current one in the current execution
- /// context. Does nothing if the fixture context is already active.
- ///
- ///
- /// This method modifies the Allure context.
- /// Requires the container context to be active.
- ///
- /// A new fixture.
- ///
- public virtual AllureLifecycle StartBeforeFixture(FixtureResult result)
- {
- this.UpdateTestContainer(container => container.befores.Add(result));
- this.StartFixture(result);
- return this;
- }
+ ///
+ /// Stops the current fixture and deactivates the fixture context.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Required the fixture context to be active.
+ ///
+ ///
+ public virtual AllureLifecycle StopFixture()
+ {
+ this.UpdateFixture(stopAllureItem);
+ this.UpdateContext(c => c.WithNoFixtureContext());
+ return this;
+ }
- ///
- /// Starts a new after fixture and activates the fixture context with
- /// it. The fixture is set as the current one in the current execution
- /// context. Does nothing if the fixture context is already active.
- ///
- ///
- /// This method modifies the Allure context.
- /// Requires the container context to be active.
- ///
- /// A new fixture.
- ///
- public virtual AllureLifecycle StartAfterFixture(FixtureResult result)
- {
- this.UpdateTestContainer(c => c.afters.Add(result));
- this.StartFixture(result);
- return this;
- }
+ #endregion
+
+ #region TestCase
- ///
- /// Applies the specified update function to the current fixture.
- ///
- ///
- /// Requires the fixture context to be active.
- ///
- ///
- public virtual AllureLifecycle UpdateFixture(
- Action update
- )
+ ///
+ /// Starts a new test and activates the test context with it. The test
+ /// becomes the current one in the current execution context.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires the test context to be active.
+ ///
+ /// A new test case.
+ ///
+ public virtual AllureLifecycle StartTestCase(TestResult testResult)
+ {
+ var uuid = testResult.uuid;
+ var containers = this.Context.ContainerContext;
+ lock (this.modelMonitor)
{
- var fixture = this.storage.CurrentFixture;
- lock (this.modelMonitor)
+ foreach (TestResultContainer container in containers)
{
- update.Invoke(fixture);
+ container.children.Add(uuid);
}
- return this;
}
+ this.storage.Put(uuid, testResult);
+ this.UpdateContext(c => c.WithTestContext(testResult));
+ this.UpdateTestCase(startAllureItemIfNotStarted);
+ return this;
+ }
- ///
- /// Stops the current fixture and deactivates the fixture context.
- ///
- ///
- /// A function applied to the fixture result before it is stopped.
- ///
- ///
- /// This method modifies the Allure context.
- /// Required the fixture context to be active.
- ///
- ///
- public virtual AllureLifecycle StopFixture(
- Action beforeStop
- )
+ ///
+ /// Applies the specified update function to the current test.
+ ///
+ ///
+ /// Requires the test context to be active.
+ ///
+ ///
+ public virtual AllureLifecycle UpdateTestCase(
+ Action update
+ )
+ {
+ var testResult = this.Context.CurrentTest;
+ lock (this.modelMonitor)
{
- this.UpdateFixture(beforeStop);
- return this.StopFixture();
+ update(testResult);
}
+ return this;
+ }
- ///
- /// Stops the current fixture and deactivates the fixture context.
- ///
- ///
- /// This method modifies the Allure context.
- /// Required the fixture context to be active.
- ///
- ///
- public virtual AllureLifecycle StopFixture()
- {
- this.UpdateFixture(fixture =>
- {
- fixture.stage = Stage.finished;
- fixture.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- });
- this.storage.RemoveFixture();
- return this;
- }
+ ///
+ /// Stops the current test.
+ ///
+ ///
+ /// Requires the test context to be active.
+ ///
+ ///
+ /// A function applied to the test result before it is stopped.
+ ///
+ ///
+ public virtual AllureLifecycle StopTestCase(
+ Action beforeStop
+ ) => this.UpdateTestCase(
+ Chain(beforeStop, stopAllureItem)
+ );
- #endregion
-
- #region TestCase
-
- ///
- /// Starts a new test and activates the test context with it. The test
- /// becomes the current one in the current execution context.
- ///
- ///
- /// This method modifies the Allure context.
- /// Requires the test context to be active.
- ///
- /// A new test case.
- ///
- public virtual AllureLifecycle StartTestCase(TestResult testResult)
- {
- var uuid = testResult.uuid;
- var containers = this.storage.CurrentContext.ContainerContext;
- lock (this.modelMonitor)
- {
- foreach (TestResultContainer container in containers)
- {
- container.children.Add(uuid);
- }
- }
- testResult.stage = Stage.running;
- testResult.start = testResult.start == 0L
- ? DateTimeOffset.Now.ToUnixTimeMilliseconds()
- : testResult.start;
- this.storage.PutTestCase(uuid, testResult);
- return this;
- }
+ ///
+ /// Stops the current test.
+ ///
+ ///
+ /// Requires the test context to be active.
+ ///
+ ///
+ public virtual AllureLifecycle StopTestCase() =>
+ this.UpdateTestCase(stopAllureItem);
- ///
- /// Applies the specified update function to the current test.
- ///
- ///
- /// Requires the test context to be active.
- ///
- ///
- public virtual AllureLifecycle UpdateTestCase(
- Action update
- )
+ ///
+ /// Writes the current test and removes it from the context. The test
+ /// context is then deactivated.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires the test context to be active.
+ ///
+ ///
+ public virtual AllureLifecycle WriteTestCase()
+ {
+ var testResult = this.Context.CurrentTest;
+ string uuid;
+ lock (this.modelMonitor)
{
- var testResult = this.storage.CurrentTest;
- lock (this.modelMonitor)
- {
- update(testResult);
- }
- return this;
+ uuid = testResult.uuid;
}
+ this.storage.Remove(uuid);
+ this.UpdateContext(c => c.WithNoTestContext());
+ this.writer.Write(testResult);
+ return this;
+ }
- ///
- /// Stops the current test.
- ///
- ///
- /// Requires the test context to be active.
- ///
- ///
- /// A function applied to the test result before it is stopped.
- ///
- ///
- public virtual AllureLifecycle StopTestCase(
- Action beforeStop
- ) => this.UpdateTestCase(testResult =>
- {
- beforeStop(testResult);
- stopTestCase(testResult);
- });
-
- ///
- /// Stops the current test.
- ///
- ///
- /// Requires the test context to be active.
- ///
- ///
- public virtual AllureLifecycle StopTestCase() =>
- this.UpdateTestCase(stopTestCase);
-
- ///
- /// Writes the current test and removes it from the context. The test
- /// context is then deactivated.
- ///
- ///
- /// This method modifies the Allure context.
- /// Requires the test context to be active.
- ///
- ///
- public virtual AllureLifecycle WriteTestCase()
- {
- var testResult = this.storage.CurrentTest;
- string uuid;
- lock (this.modelMonitor)
- {
- uuid = testResult.uuid;
- }
- this.storage.RemoveTestCase(uuid);
- this.writer.Write(testResult);
- return this;
- }
+ #endregion
- #endregion
-
- #region Step
-
- ///
- /// Starts a new step and pushes it into the step context making the
- /// step context active. The step becomes the current one in the
- /// current execution context.
- ///
- ///
- /// This method modifies the Allure context.
- /// Requires either the fixture or the test context to be active.
- ///
- /// A new step.
- ///
- public virtual AllureLifecycle StartStep(StepResult result)
- {
- result.stage = Stage.running;
- result.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- var parent = this.storage.CurrentStepContainer;
- lock (this.modelMonitor)
- {
- parent.steps.Add(result);
- }
- this.storage.PutStep(result);
- return this;
- }
+ #region Step
- ///
- /// Applies the specified update function to the current step.
- ///
- ///
- /// Requires the step context to be active.
- ///
- ///
- public virtual AllureLifecycle UpdateStep(Action update)
+ ///
+ /// Starts a new step and pushes it into the step context making the
+ /// step context active. The step becomes the current one in the
+ /// current execution context.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires either the fixture or the test context to be active.
+ ///
+ /// A new step.
+ ///
+ public virtual AllureLifecycle StartStep(StepResult result)
+ {
+ var parent = this.Context.CurrentStepContainer;
+ lock (this.modelMonitor)
{
- var stepResult = this.storage.CurrentStep;
- lock (this.modelMonitor)
- {
- update.Invoke(stepResult);
- }
- return this;
+ parent.steps.Add(result);
}
+ this.UpdateContext(c => c.WithStep(result));
+ this.UpdateStep(startAllureItem);
+ return this;
+ }
- ///
- /// Stops the current step and removes it from the context. If there
- /// are another steps in the context, the most recently started one
- /// becomes the current step in the current execution context.
- /// Otherwise the step context is deactivated.
- ///
- ///
- /// This method modifies the Allure context.
- /// Requires the step context to be active.
- ///
- ///
- /// A function that is applied to the step result before it is stopped.
- ///
- ///
- public virtual AllureLifecycle StopStep(Action beforeStop)
+ ///
+ /// Applies the specified update function to the current step.
+ ///
+ ///
+ /// Requires the step context to be active.
+ ///
+ ///
+ public virtual AllureLifecycle UpdateStep(Action update)
+ {
+ var stepResult = this.Context.CurrentStep;
+ lock (this.modelMonitor)
{
- this.UpdateStep(beforeStop);
- return this.StopStep();
+ update.Invoke(stepResult);
}
+ return this;
+ }
- ///
- /// Stops the current step and removes it from the context. If there
- /// are another steps in the context, the most recently started one
- /// becomes the current step in the current execution context.
- /// Otherwise the step context is deactivated.
- ///
- ///
- /// This method modifies the Allure context.
- /// Requires the step context to be active.
- ///
- ///
- public virtual AllureLifecycle StopStep()
- {
- this.UpdateStep(step =>
- {
- step.stage = Stage.finished;
- step.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- });
- this.storage.RemoveStep();
- return this;
- }
+ ///
+ /// Stops the current step and removes it from the context. If there
+ /// are another steps in the context, the most recently started one
+ /// becomes the current step in the current execution context.
+ /// Otherwise the step context is deactivated.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires the step context to be active.
+ ///
+ ///
+ /// A function that is applied to the step result before it is stopped.
+ ///
+ ///
+ public virtual AllureLifecycle StopStep(Action beforeStop)
+ {
+ this.UpdateStep(beforeStop);
+ return this.StopStep();
+ }
- #endregion
+ ///
+ /// Stops the current step and removes it from the context. If there
+ /// are another steps in the context, the most recently started one
+ /// becomes the current step in the current execution context.
+ /// Otherwise the step context is deactivated.
+ ///
+ ///
+ /// This method modifies the Allure context.
+ /// Requires the step context to be active.
+ ///
+ ///
+ public virtual AllureLifecycle StopStep()
+ {
+ this.UpdateStep(stopAllureItem);
+ this.UpdateContext(c => c.WithNoLastStep());
+ return this;
+ }
- #region Attachment
+ #endregion
- // TODO: read file in background thread
- public virtual AllureLifecycle AddAttachment(
- string name,
- string type,
- string path
- )
- {
- var fileExtension = new FileInfo(path).Extension;
- return AddAttachment(name, type, File.ReadAllBytes(path), fileExtension);
- }
+ #region Attachment
- public virtual AllureLifecycle AddAttachment(
- string name,
- string type,
- byte[] content,
- string fileExtension = ""
- )
- {
- var suffix = AllureConstants.ATTACHMENT_FILE_SUFFIX;
- var source = $"{CreateUuid()}{suffix}{fileExtension}";
- var attachment = new Attachment
- {
- name = name,
- type = type,
- source = source
- };
- this.writer.Write(source, content);
- var target = this.storage.CurrentStepContainer;
- lock (this.modelMonitor)
- {
- target.attachments.Add(attachment);
- }
- return this;
- }
+ // TODO: read file in background thread
+ public virtual AllureLifecycle AddAttachment(
+ string name,
+ string type,
+ string path
+ )
+ {
+ var fileExtension = new FileInfo(path).Extension;
+ return this.AddAttachment(
+ name,
+ type,
+ File.ReadAllBytes(path),
+ fileExtension
+ );
+ }
- public virtual AllureLifecycle AddAttachment(
- string path,
- string? name = null
- )
+ public virtual AllureLifecycle AddAttachment(
+ string name,
+ string type,
+ byte[] content,
+ string fileExtension = ""
+ )
+ {
+ var suffix = AllureConstants.ATTACHMENT_FILE_SUFFIX;
+ var source = $"{CreateUuid()}{suffix}{fileExtension}";
+ var attachment = new Attachment
{
- name ??= Path.GetFileName(path);
- var type = MimeTypesMap.GetMimeType(path);
- return AddAttachment(name, type, path);
- }
-
- #endregion
-
- #region Extensions
-
- public virtual void CleanupResultDirectory()
+ name = name,
+ type = type,
+ source = source
+ };
+ this.writer.Write(source, content);
+ var target = this.Context.CurrentStepContainer;
+ lock (this.modelMonitor)
{
- writer.CleanUp();
+ target.attachments.Add(attachment);
}
+ return this;
+ }
- public virtual AllureLifecycle AddScreenDiff(
- string expectedPng,
- string actualPng,
- string diffPng
- ) => this.AddAttachment(expectedPng, "expected")
- .AddAttachment(actualPng, "actual")
- .AddAttachment(diffPng, "diff")
- .UpdateTestCase(
- x => x.labels.Add(Label.TestType("screenshotDiff"))
- );
+ public virtual AllureLifecycle AddAttachment(
+ string path,
+ string? name = null
+ )
+ {
+ name ??= Path.GetFileName(path);
+ var type = MimeTypesMap.GetMimeType(path);
+ return AddAttachment(name, type, path);
+ }
- #endregion
+ #endregion
+ #region Extensions
- #region Privates
+ public virtual void CleanupResultDirectory()
+ {
+ writer.CleanUp();
+ }
- static AllureLifecycle Initialize() => new();
+ public virtual AllureLifecycle AddScreenDiff(
+ string expectedPng,
+ string actualPng,
+ string diffPng
+ ) => this.AddAttachment(expectedPng, "expected")
+ .AddAttachment(actualPng, "actual")
+ .AddAttachment(diffPng, "diff")
+ .UpdateTestCase(
+ x => x.labels.Add(Label.TestType("screenshotDiff"))
+ );
- private static JObject GetConfiguration()
- {
- var configEnvVarName = AllureConstants.ALLURE_CONFIG_ENV_VARIABLE;
- var jsonConfigPath = Environment.GetEnvironmentVariable(
- configEnvVarName
- );
+ #endregion
- if (jsonConfigPath != null && !File.Exists(jsonConfigPath))
- {
- throw new FileNotFoundException(
- $"Couldn't find '{jsonConfigPath}' specified " +
- $"in {configEnvVarName} environment variable"
- );
- }
- if (File.Exists(jsonConfigPath))
- {
- return JObject.Parse(File.ReadAllText(jsonConfigPath));
- }
+ #region Privates
- var defaultJsonConfigPath = Path.Combine(
- AppDomain.CurrentDomain.BaseDirectory,
- AllureConstants.CONFIG_FILENAME
- );
+ static AllureLifecycle Initialize() => new();
- if (File.Exists(defaultJsonConfigPath))
- {
- return JObject.Parse(File.ReadAllText(defaultJsonConfigPath));
- }
+ private static JObject GetConfiguration()
+ {
+ var configEnvVarName = AllureConstants.ALLURE_CONFIG_ENV_VARIABLE;
+ var jsonConfigPath = Environment.GetEnvironmentVariable(
+ configEnvVarName
+ );
- return JObject.Parse("{}");
+ if (jsonConfigPath != null && !File.Exists(jsonConfigPath))
+ {
+ throw new FileNotFoundException(
+ $"Couldn't find '{jsonConfigPath}' specified " +
+ $"in {configEnvVarName} environment variable"
+ );
}
- private void StartFixture(FixtureResult fixtureResult)
+ if (File.Exists(jsonConfigPath))
{
- this.storage.PutFixture(fixtureResult);
- fixtureResult.stage = Stage.running;
- fixtureResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ return JObject.Parse(File.ReadAllText(jsonConfigPath));
}
- static readonly Action stopContainer =
- c => c.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ var defaultJsonConfigPath = Path.Combine(
+ AppDomain.CurrentDomain.BaseDirectory,
+ AllureConstants.CONFIG_FILENAME
+ );
- static readonly Action stopTestCase =
- tr =>
- {
- tr.stage = Stage.finished;
- tr.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- };
-
-
- static string CreateUuid() =>
- Guid.NewGuid().ToString("N");
-
- #endregion
-
- #region Obsoleted
-
- [Obsolete(
- "This property is a rudimentary part of the API. It has no " +
- "effect and will be removed soon"
- )]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public static Func? CurrentTestIdGetter { get; set; }
-
- [Obsolete(
- "Lifecycle methods with explicit uuids are obsolete. Use " +
- "their counterparts without uuids to manipulate the context."
- )]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual AllureLifecycle StartTestContainer(
- string parentUuid,
- TestResultContainer container
- )
+ if (File.Exists(defaultJsonConfigPath))
{
- container.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- UpdateTestContainer(parentUuid, c => c.children.Add(container.uuid));
- this.storage.PutTestContainer(container.uuid, container);
- return this;
+ return JObject.Parse(File.ReadAllText(defaultJsonConfigPath));
}
- [Obsolete(
- "Lifecycle methods with explicit uuids are obsolete. Use " +
- "their counterparts without uuids to manipulate the context."
- )]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual AllureLifecycle UpdateTestContainer(
- string uuid,
- Action update
- )
- {
- var container = this.storage.Get(uuid);
- lock (this.modelMonitor)
- {
- update.Invoke(container);
- }
- return this;
- }
+ return JObject.Parse("{}");
+ }
- [Obsolete(
- "Lifecycle methods with explicit uuids are obsolete. Use " +
- "their counterparts without uuids to manipulate the context."
- )]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual AllureLifecycle StopTestContainer(string uuid) =>
- UpdateTestContainer(uuid, stopContainer);
-
- [Obsolete(
- "Lifecycle methods with explicit uuids are obsolete. Use " +
- "their counterparts without uuids to manipulate the context."
- )]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual AllureLifecycle WriteTestContainer(string uuid)
- {
- var container = this.storage.Get(uuid);
- this.storage.RemoveTestContainer(uuid);
- this.writer.Write(container);
- return this;
- }
+ private void StartFixture(FixtureResult fixtureResult)
+ {
+ this.UpdateContext(c => c.WithFixtureContext(fixtureResult));
+ this.UpdateFixture(startAllureItem);
+ }
- [Obsolete(
- "Lifecycle methods with explicit uuids are obsolete. Use " +
- "their counterparts without uuids to manipulate the context."
- )]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual AllureLifecycle StartBeforeFixture(
- FixtureResult result,
- out string uuid
- )
- {
- uuid = CreateUuid();
- StartBeforeFixture(uuid, result);
- return this;
- }
+ static readonly Action stopContainer =
+ c => c.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- [Obsolete(
- "Lifecycle methods with explicit uuids are obsolete. Use " +
- "their counterparts without uuids to manipulate the context."
- )]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual AllureLifecycle StartBeforeFixture(
- string uuid,
- FixtureResult result
- )
+ static readonly Action startAllureItem =
+ item =>
{
- UpdateTestContainer(container => container.befores.Add(result));
- StartFixture(uuid, result);
- return this;
- }
+ item.stage = Stage.running;
+ item.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ };
- [Obsolete(
- "Lifecycle methods with explicit uuids are obsolete. Use " +
- "their counterparts without uuids to manipulate the context."
- )]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual AllureLifecycle StartBeforeFixture(
- string parentUuid,
- FixtureResult result,
- out string uuid
- )
+ static readonly Action startAllureItemIfNotStarted =
+ item =>
{
- uuid = CreateUuid();
- StartBeforeFixture(parentUuid, uuid, result);
- return this;
- }
+ item.stage = Stage.running;
+ item.stop = item.stop == 0L
+ ? DateTimeOffset.Now.ToUnixTimeMilliseconds()
+ : item.stop;
+ };
- [Obsolete(
- "Lifecycle methods with explicit uuids are obsolete. Use " +
- "their counterparts without uuids to manipulate the context."
- )]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual AllureLifecycle StartBeforeFixture(
- string parentUuid,
- string uuid,
- FixtureResult result
- )
+ static readonly Action stopAllureItem =
+ item =>
{
- UpdateTestContainer(parentUuid, container => container.befores.Add(result));
- StartFixture(uuid, result);
- return this;
- }
+ item.stage = Stage.finished;
+ item.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ };
- [Obsolete(
- "Lifecycle methods with explicit uuids are obsolete. Use " +
- "their counterparts without uuids to manipulate the context."
- )]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual AllureLifecycle StartAfterFixture(
- string parentUuid,
- FixtureResult result,
- out string uuid
- )
- {
- uuid = CreateUuid();
- StartAfterFixture(parentUuid, uuid, result);
- return this;
- }
+ void UpdateContext(Func updateFn)
+ {
+ this.Context = updateFn(this.Context);
+ }
+
+ static string CreateUuid() =>
+ Guid.NewGuid().ToString("N");
- [Obsolete(
- "Lifecycle methods with explicit uuids are obsolete. Use " +
- "their counterparts without uuids to manipulate the context."
- )]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual AllureLifecycle StartAfterFixture(
- string parentUuid,
- string uuid,
- FixtureResult result
- )
+ static Action Chain(params Action[] actions) => v =>
+ {
+ foreach (var action in actions)
{
- UpdateTestContainer(
- parentUuid,
- container => container.afters.Add(result)
- );
- StartFixture(uuid, result);
- return this;
+ action(v);
}
+ };
+
+ #endregion
+
+ #region Obsoleted
+
+ [Obsolete(
+ "This property is a rudimentary part of the API. It has no " +
+ "effect and will be removed soon"
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static Func? CurrentTestIdGetter { get; set; }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartTestContainer(
+ string parentUuid,
+ TestResultContainer container
+ )
+ {
+ this.UpdateTestContainer(
+ parentUuid,
+ c => c.children.Add(container.uuid)
+ );
+ this.StartTestContainer(container);
+ return this;
+ }
- [Obsolete(
- "Lifecycle methods with explicit uuids are obsolete. Use " +
- "their counterparts without uuids to manipulate the context."
- )]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual AllureLifecycle UpdateFixture(
- string uuid,
- Action update
- )
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle UpdateTestContainer(
+ string uuid,
+ Action update
+ )
+ {
+ var container = this.storage.Get(uuid);
+ lock (this.modelMonitor)
{
- var fixture = this.storage.Get(uuid);
- lock (this.modelMonitor)
- {
- update.Invoke(fixture);
- }
- return this;
+ update.Invoke(container);
}
+ return this;
+ }
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StopTestContainer(string uuid) =>
+ this.UpdateTestContainer(uuid, stopContainer);
+
+ [Obsolete(
+ "Lifecycle methods with explicit uuids are obsolete. Use " +
+ "their counterparts without uuids to manipulate the context."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle WriteTestContainer(string uuid)
+ {
+ var container = this.storage.Remove