diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 71cb91f2..e29341a2 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -21,14 +21,15 @@ jobs:
name: "Build"
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3.0.2
+ - uses: actions/checkout@v3.5.3
- name: 'Setup .NET Core SDK'
- uses: actions/setup-dotnet@v2.1.0
+ uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: |
3.1.x
6.0.x
+ 7.0.x
- name: 'Restore packages'
run: dotnet restore ${{ env.SOLUTION_PATH }} --packages ${{ env.RESTORE_OUTPUT_PATH }}
@@ -38,5 +39,5 @@ jobs:
- name: 'Run tests'
run: |
- dotnet test Allure.Net.Commons.Tests/Allure.Net.Commons.Tests.csproj --no-restore --configuration ${{ env.BUILD_CONFIGURATION }}
- dotnet test Allure.SpecFlowPlugin.Tests/Allure.SpecFlowPlugin.Tests.csproj --no-restore --configuration ${{ env.BUILD_CONFIGURATION }}
+ dotnet test Allure.Net.Commons.Tests/Allure.Net.Commons.Tests.csproj --no-build --configuration ${{ env.BUILD_CONFIGURATION }}
+ dotnet test Allure.SpecFlowPlugin.Tests/Allure.SpecFlowPlugin.Tests.csproj --no-build --configuration ${{ env.BUILD_CONFIGURATION }}
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index b62d4b8f..cc09f513 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -14,14 +14,15 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3.0.2
+ - uses: actions/checkout@v3.5.3
- name: 'Setup .NET Core SDK'
- uses: actions/setup-dotnet@v2.1.0
+ uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: |
3.1.x
6.0.x
+ 7.0.x
- name: 'Restore packages'
run: dotnet restore ${{ env.SOLUTION_PATH }} --packages ${{ env.RESTORE_OUTPUT_PATH }}
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.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/Before Feature Failure.feature b/Allure.Features/TestData/Before Feature Failure.feature
index e1159e4a..465ea4c4 100644
--- a/Allure.Features/TestData/Before Feature Failure.feature
+++ b/Allure.Features/TestData/Before Feature Failure.feature
@@ -2,4 +2,4 @@
Feature: Before Feature Failure
@broken
- Scenario: Unknown
\ No newline at end of file
+ Scenario: Feature hook failure placeholder
\ No newline at end of file
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.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.Examples/AllureSetUpFixture.cs b/Allure.NUnit.Examples/AllureSetUpFixture.cs
new file mode 100644
index 00000000..08b224bd
--- /dev/null
+++ b/Allure.NUnit.Examples/AllureSetUpFixture.cs
@@ -0,0 +1,17 @@
+using Allure.Net.Commons;
+using NUnit.Allure.Core;
+using NUnit.Framework;
+
+namespace Allure.NUnit.Examples
+{
+ [SetUpFixture]
+ public class AllureSetUpFixture
+ {
+ [OneTimeSetUp]
+ public static void CleanupResultDirectory() =>
+ AllureExtensions.WrapSetUpTearDownParams(
+ AllureLifecycle.Instance.CleanupResultDirectory,
+ "Clear Allure Results Directory"
+ );
+ }
+}
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/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.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..1776efb6 100644
--- a/Allure.NUnit/Core/AllureExtensions.cs
+++ b/Allure.NUnit/Core/AllureExtensions.cs
@@ -1,10 +1,9 @@
using System;
-using System.Reflection;
+using System.ComponentModel;
using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
using Allure.Net.Commons;
using NUnit.Framework.Internal;
-using System.Linq;
-using System.Threading.Tasks;
namespace NUnit.Allure.Core
{
@@ -58,15 +57,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 +94,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 +139,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 +176,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;
@@ -185,15 +204,16 @@ public static async Task WrapInStepAsync(
}
}
- ///
- /// 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);
- }
+ [Obsolete(
+ "Use AllureLifecycle.AddScreenDiff instance method instead to " +
+ "add a screen diff to the current test."
+ )]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void AddScreenDiff(
+ this AllureLifecycle lifecycle,
+ string expected,
+ string actual,
+ string diff
+ ) => lifecycle.AddScreenDiff(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);
});
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.Tests/AllureContextTests.cs b/Allure.Net.Commons.Tests/AllureContextTests.cs
new file mode 100644
index 00000000..894aaaaa
--- /dev/null
+++ b/Allure.Net.Commons.Tests/AllureContextTests.cs
@@ -0,0 +1,610 @@
+using NUnit.Framework;
+
+namespace Allure.Net.Commons.Tests
+{
+ 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.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,
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "No container context is active."
+ )
+ );
+ Assert.That(
+ () => ctx.CurrentFixture,
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "No fixture context is active."
+ )
+ );
+ Assert.That(
+ () => ctx.CurrentTest,
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "No test context is active."
+ )
+ );
+ Assert.That(
+ () => ctx.CurrentStep,
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "No step context is active."
+ )
+ );
+ Assert.That(
+ () => ctx.CurrentStepContainer,
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "No fixture, test, or step context is active."
+ )
+ );
+ }
+
+ [Test]
+ public void TestContextOnly()
+ {
+ var test = new TestResult();
+
+ 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));
+ }
+
+ [Test]
+ public void CanNotAddContainerIfTestIsSet()
+ {
+ var ctx = new AllureContext()
+ .WithTestContext(new());
+
+ Assert.That(
+ () => ctx.WithContainer(new()),
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "Unable to change the container context because the " +
+ "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 the container context because the " +
+ "fixture context is active."
+ )
+ );
+ }
+
+ [Test]
+ public void CanNotRemoveContainerIfTestIsSet()
+ {
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithTestContext(new());
+
+ Assert.That(
+ ctx.WithNoLastContainer,
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "Unable to change the container context because the " +
+ "test context is active."
+ )
+ );
+ }
+
+ [Test]
+ public void TestContextCanBeRemoved()
+ {
+ var test = new TestResult();
+
+ var ctx = new AllureContext()
+ .WithTestContext(test)
+ .WithNoTestContext();
+
+ Assert.That(ctx.HasTest, Is.False);
+ 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.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));
+ }
+
+ [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.ContainerContextDepth, Is.EqualTo(2));
+ 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 deactivate the container context because " +
+ "it is not active."
+ )
+ );
+ }
+
+ [Test]
+ public void LatestContainerCanBeRemoved()
+ {
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithNoLastContainer();
+
+ Assert.That(ctx.HasContainer, Is.False);
+ Assert.That(ctx.ContainerContextDepth, Is.Zero);
+ 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));
+ Assert.That(ctx.ContainerContextDepth, Is.EqualTo(1));
+ }
+
+ [Test]
+ public void FixtureContextRequiresContainer()
+ {
+ var fixture = new FixtureResult();
+
+ var ctx = new AllureContext();
+
+ Assert.That(
+ () => ctx.WithFixtureContext(new()),
+ Throws.InvalidOperationException
+ .With.Message.EqualTo(
+ "Unable to activate the fixture context because " +
+ "the container context is not active."
+ )
+ );
+ }
+
+ [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.HasFixture, Is.True);
+ 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 change the container context because the " +
+ "fixture context is active."
+ )
+ );
+ }
+
+ [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 activate the fixture context because " +
+ "it's already active."
+ )
+ );
+ }
+
+ [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 activate the test context because " +
+ "it is already active."
+ )
+ );
+ }
+
+ [Test]
+ public void CanNotSetTestContextIfFixtureContextIsActive()
+ {
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithFixtureContext(new());
+
+ Assert.That(
+ () => ctx.WithTestContext(new()),
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "Unable to activate the test context because the " +
+ "fixture context is active."
+ )
+ );
+ }
+
+ [Test]
+ public void ClearingTestContextClearsFixtureContext()
+ {
+ var test = new TestResult();
+
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithTestContext(test)
+ .WithFixtureContext(new())
+ .WithNoTestContext();
+
+ Assert.That(ctx.HasFixture, Is.False);
+ 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.HasFixture, Is.False);
+ 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 activate the step context because neither " +
+ "test, nor fixture context is active."
+ )
+ );
+ }
+
+ [Test]
+ public void StepCanNotBeRemovedIfNoStepExists()
+ {
+ var ctx = new AllureContext();
+
+ Assert.That(
+ () => ctx.WithNoLastStep(),
+ Throws.InvalidOperationException.With.Message.EqualTo(
+ "Unable to deactivate the step context because it " +
+ "isn't active."
+ )
+ );
+ }
+
+ [Test]
+ public void StepCanBeAddedIfFixtureExists()
+ {
+ var step = new StepResult();
+ var ctx = new AllureContext()
+ .WithContainer(new())
+ .WithFixtureContext(new())
+ .WithStep(step);
+
+ 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));
+ }
+
+ [Test]
+ public void StepCanBeAddedIfTestExists()
+ {
+ var step = new StepResult();
+ var ctx = new AllureContext()
+ .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));
+ }
+
+ [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.StepContextDepth, Is.EqualTo(2));
+ 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.StepContextDepth, Is.EqualTo(1));
+ 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.HasStep, Is.False);
+ Assert.That(ctx.StepContext, Is.Empty);
+ Assert.That(ctx.StepContextDepth, Is.Zero);
+ 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.HasStep, Is.False);
+ Assert.That(ctx.StepContext, Is.Empty);
+ }
+
+ [Test]
+ public void RemovingTestClearsStepContext()
+ {
+ var ctx = new AllureContext()
+ .WithTestContext(new())
+ .WithStep(new())
+ .WithNoTestContext();
+
+ Assert.That(ctx.HasStep, Is.False);
+ 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.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] }")
+ );
+ 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 6cd08fd5..29a519ac 100644
--- a/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
+++ b/Allure.Net.Commons.Tests/AllureLifeCycleTest.cs
@@ -1,5 +1,6 @@
-using NUnit.Framework;
+using System;
using System.Threading.Tasks;
+using NUnit.Framework;
namespace Allure.Net.Commons.Tests
{
@@ -40,33 +41,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 =>
{
@@ -81,18 +82,100 @@ 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();
});
+ }
+
+ [Test, Description("A 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(fixture)
+ .StopFixture()
+ .StartStep(new())
+ .StopStep()
+ .StopTestCase()
+ .StopTestContainer()
+ .WriteTestCase()
+ .WriteTestContainer();
+
+ 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));
+ }
+ [Test]
+ public async Task ContextCapturingTest()
+ {
+ var writer = new InMemoryResultsWriter();
+ var lifecycle = new AllureLifecycle(_ => writer);
+ AllureContext context = null, modifiedContext = null;
+ await Task.Factory.StartNew(() =>
+ {
+ lifecycle.StartTestCase(new()
+ {
+ uuid = Guid.NewGuid().ToString()
+ });
+ context = lifecycle.Context;
+ });
+ modifiedContext = lifecycle.RunInContext(context, () =>
+ {
+ lifecycle.StopTestCase();
+ lifecycle.WriteTestCase();
+ });
+
+ Assert.That(writer.testResults, Is.Not.Empty);
+ Assert.That(modifiedContext.HasTest, Is.False);
+ }
+
+ [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.Tests/ConcurrencyTests.cs b/Allure.Net.Commons.Tests/ConcurrencyTests.cs
new file mode 100644
index 00000000..98a037c8
--- /dev/null
+++ b/Allure.Net.Commons.Tests/ConcurrencyTests.cs
@@ -0,0 +1,349 @@
+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;
+ int writes = 0;
+
+ [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");
+ }
+
+ [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
+ */
+ var sync = new ManualResetEventSlim();
+
+ this.WrapInTest(
+ "test",
+ () => this.WrapInStep(
+ "outer",
+ () => RunThreads(
+ 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)
+ )
+ )
+ );
+
+ 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" })
+ })
+ );
+ }
+
+ 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)
+ {
+ this.lifecycle
+ .StartTestCase(new()
+ {
+ name = name,
+ uuid = Guid.NewGuid().ToString()
+ });
+ await Task.Delay(1);
+ await this.AddStepsAsync(steps);
+ this.lifecycle
+ .StopTestCase()
+ .WriteTestCase();
+ writes++;
+ }
+
+ void WrapInTest(string testName, Action action)
+ {
+ this.lifecycle.StartTestCase(
+ new() { name = testName, uuid = Guid.NewGuid().ToString() }
+ );
+ action();
+ this.lifecycle
+ .StopTestCase()
+ .WriteTestCase();
+ }
+
+ void WrapInStep(string stepName, Action action)
+ {
+ this.lifecycle.StartStep(
+ new() { name = stepName }
+ );
+ action();
+ this.lifecycle
+ .StopStep();
+ }
+
+ async Task WrapInStepAsync(string stepName, Func action)
+ {
+ this.lifecycle.StartStep(
+ new() { name = stepName }
+ );
+ await action();
+ this.lifecycle
+ .StopStep();
+ }
+
+ async Task WrapInTestAsync(string testName, Func action)
+ {
+ this.lifecycle.StartTestCase(
+ new() { name = testName, uuid = Guid.NewGuid().ToString() }
+ );
+ await Task.Delay(1);
+ await action();
+ this.lifecycle
+ .StopTestCase()
+ .WriteTestCase();
+ }
+
+ 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 }
+ ).StopStep();
+ }
+
+ void AddStepWithSubsteps(string name, params object[] substeps)
+ {
+ 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 });
+ 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.Length;
+ 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
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 8bd61249..a8878a43 100644
--- a/Allure.Net.Commons/AllureLifecycle.cs
+++ b/Allure.Net.Commons/AllureLifecycle.cs
@@ -1,369 +1,1018 @@
using System;
using System.Collections.Generic;
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.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
+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
{
- 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;
+
+ ///
+ /// Protects mutations of shared allure model objects against data
+ /// races that may otherwise occur because of multithreaded access.
+ ///
+ 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 object Lockobj = new();
- private static AllureLifecycle instance;
- private readonly AllureStorage storage;
- private readonly IAllureResultsWriter writer;
+ internal AllureLifecycle(
+ Func writerFactory
+ ) : this(GetConfiguration(), writerFactory)
+ {
+ }
- /// Method to get the key for separation the steps for different tests.
- public static Func CurrentTestIdGetter { get; set; } = () => Thread.CurrentThread.ManagedThreadId.ToString();
+ internal AllureLifecycle(JObject config)
+ : this(config, c => new FileSystemResultsWriter(c))
+ {
+ }
- internal AllureLifecycle(): this(GetConfiguration())
- {
- }
-
- internal AllureLifecycle(JObject config)
- {
- JsonConfiguration = config.ToString();
- AllureConfiguration = AllureConfiguration.ReadFromJObject(config);
- writer = new FileSystemResultsWriter(AllureConfiguration);
- storage = new AllureStorage();
- }
+ internal AllureLifecycle(
+ JObject config,
+ Func writerFactory
+ )
+ {
+ JsonConfiguration = config.ToString();
+ AllureConfiguration = AllureConfiguration.ReadFromJObject(config);
+ writer = writerFactory(AllureConfiguration);
+ storage = new AllureStorage();
+ }
- public string JsonConfiguration { get; private set; }
- public AllureConfiguration AllureConfiguration { get; }
+ public string JsonConfiguration { get; private set; }
- public string ResultsDirectory => writer.ToString();
+ public AllureConfiguration AllureConfiguration { get; }
- 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 string ResultsDirectory => writer.ToString();
- public void AddTypeFormatter(TypeFormatter typeFormatter) =>
- AddTypeFormatterImpl(typeof(T), typeFormatter);
+ public static AllureLifecycle Instance { get => instance.Value; }
- private void AddTypeFormatterImpl(Type type, ITypeFormatter formatter) =>
- typeFormatters[type] = formatter;
+ public void AddTypeFormatter(TypeFormatter typeFormatter) =>
+ AddTypeFormatterImpl(typeof(T), typeFormatter);
- #region TestContainer
+ private void AddTypeFormatterImpl(Type type, ITypeFormatter formatter) =>
+ typeFormatters[type] = formatter;
- public virtual AllureLifecycle StartTestContainer(TestResultContainer container)
+ ///
+ /// 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)
{
- container.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- storage.Put(container.uuid, container);
- return this;
+ action();
+ return this.Context;
}
- public virtual AllureLifecycle StartTestContainer(string parentUuid, TestResultContainer container)
+ var originalContext = this.Context;
+ try
{
- UpdateTestContainer(parentUuid, c => c.children.Add(container.uuid));
- StartTestContainer(container);
- return this;
+ this.Context = context;
+ action();
+ return this.Context;
}
-
- public virtual AllureLifecycle UpdateTestContainer(string uuid, Action update)
+ finally
{
- update.Invoke(storage.Get(uuid));
- return this;
+ this.Context = originalContext;
}
+ }
- public virtual AllureLifecycle StopTestContainer(string uuid)
- {
- UpdateTestContainer(uuid, c => c.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds());
- return this;
- }
+ #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();
+ this.storage.Put(container.uuid, container);
+ this.UpdateContext(c => c.WithContainer(container));
+ return this;
+ }
- public virtual AllureLifecycle WriteTestContainer(string uuid)
+ ///
+ /// 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)
{
- writer.Write(storage.Remove(uuid));
- return this;
+ update.Invoke(container);
}
+ return this;
+ }
- #endregion
+ ///
+ /// Stops the current test container.
+ ///
+ ///
+ /// Requires the container context to be active.
+ ///
+ ///
+ public virtual AllureLifecycle StopTestContainer()
+ {
+ UpdateTestContainer(stopContainer);
+ return this;
+ }
- #region Fixture
+ ///
+ /// 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;
+ }
- public virtual AllureLifecycle StartBeforeFixture(string parentUuid, FixtureResult result, out string uuid)
- {
- uuid = Guid.NewGuid().ToString("N");
- StartBeforeFixture(parentUuid, uuid, result);
- return this;
- }
+ #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(c => c.befores.Add(result));
+ this.StartFixture(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;
- }
+ ///
+ /// 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;
+ }
- public virtual AllureLifecycle StartAfterFixture(string parentUuid, FixtureResult result, out string uuid)
+ ///
+ /// 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)
{
- uuid = Guid.NewGuid().ToString("N");
- StartAfterFixture(parentUuid, uuid, result);
- return this;
+ 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
+ )
+ {
+ this.UpdateFixture(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(stopAllureItem);
+ this.UpdateContext(c => c.WithNoFixtureContext());
+ return this;
+ }
- public virtual AllureLifecycle StartAfterFixture(string parentUuid, string uuid, FixtureResult result)
+ #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.Context.ContainerContext;
+ lock (this.modelMonitor)
{
- UpdateTestContainer(parentUuid, container => container.afters.Add(result));
- StartFixture(uuid, result);
- return this;
+ foreach (TestResultContainer container in containers)
+ {
+ container.children.Add(uuid);
+ }
}
+ this.storage.Put(uuid, testResult);
+ this.UpdateContext(c => c.WithTestContext(testResult));
+ this.UpdateTestCase(startAllureItem);
+ return this;
+ }
- public virtual AllureLifecycle UpdateFixture(Action update)
+ ///
+ /// 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)
{
- UpdateFixture(storage.GetRootStep(), update);
- return this;
+ update(testResult);
}
+ return this;
+ }
- public virtual AllureLifecycle UpdateFixture(string uuid, Action update)
+ ///
+ /// 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)
+ );
+
+ ///
+ /// Stops the current test.
+ ///
+ ///
+ /// Requires the test context to be active.
+ ///
+ ///
+ public virtual AllureLifecycle StopTestCase() =>
+ this.UpdateTestCase(stopAllureItem);
+
+ ///
+ /// 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)
{
- update.Invoke(storage.Get(uuid));
- return this;
+ uuid = testResult.uuid;
}
+ this.storage.Remove(uuid);
+ this.UpdateContext(c => c.WithNoTestContext());
+ this.writer.Write(testResult);
+ return this;
+ }
- public virtual AllureLifecycle StopFixture(Action beforeStop)
+ #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)
+ {
+ var parent = this.Context.CurrentStepContainer;
+ lock (this.modelMonitor)
{
- UpdateFixture(beforeStop);
- return StopFixture(storage.GetRootStep());
+ parent.steps.Add(result);
}
+ this.UpdateContext(c => c.WithStep(result));
+ this.UpdateStep(startAllureItem);
+ return this;
+ }
- public virtual AllureLifecycle StopFixture(string uuid)
+ ///
+ /// 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)
{
- var fixture = storage.Remove(uuid);
- storage.ClearStepContext();
- fixture.stage = Stage.finished;
- fixture.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- return this;
+ update.Invoke(stepResult);
}
+ return this;
+ }
- #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.
+ ///
+ ///
+ /// 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();
+ }
- #region TestCase
+ ///
+ /// 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;
+ }
- public virtual AllureLifecycle StartTestCase(string containerUuid, TestResult testResult)
- {
- UpdateTestContainer(containerUuid, c => c.children.Add(testResult.uuid));
- return StartTestCase(testResult);
- }
+ #endregion
- public virtual AllureLifecycle StartTestCase(TestResult testResult)
- {
- 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);
- return this;
- }
+ #region Attachment
- public virtual AllureLifecycle UpdateTestCase(string uuid, Action update)
- {
- update.Invoke(storage.Get(uuid));
- 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 UpdateTestCase(Action update)
+ 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
{
- return UpdateTestCase(storage.GetRootStep(), update);
- }
-
- public virtual AllureLifecycle StopTestCase(Action beforeStop)
+ name = name,
+ type = type,
+ source = source
+ };
+ this.writer.Write(source, content);
+ var target = this.Context.CurrentStepContainer;
+ lock (this.modelMonitor)
{
- UpdateTestCase(beforeStop);
- return StopTestCase(storage.GetRootStep());
+ target.attachments.Add(attachment);
}
+ return this;
+ }
- public virtual AllureLifecycle StopTestCase(string uuid)
- {
- var testResult = storage.Get(uuid);
- testResult.stage = Stage.finished;
- testResult.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- storage.ClearStepContext();
- return this;
- }
+ public virtual AllureLifecycle AddAttachment(
+ string path,
+ string? name = null
+ )
+ {
+ name ??= Path.GetFileName(path);
+ var type = MimeTypesMap.GetMimeType(path);
+ return AddAttachment(name, type, path);
+ }
- public virtual AllureLifecycle WriteTestCase(string uuid)
- {
- writer.Write(storage.Remove(uuid));
- return this;
- }
+ #endregion
- #endregion
+ #region Extensions
- #region Step
+ public virtual void CleanupResultDirectory()
+ {
+ writer.CleanUp();
+ }
- public virtual AllureLifecycle StartStep(StepResult result, out string uuid)
- {
- 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);
- return this;
- }
+ ///
+ /// Attaches screen diff images to the current test case.
+ ///
+ ///
+ /// Requires the test context to be active.
+ ///
+ /// A path to the actual screen.
+ /// A path to the expected screen.
+ /// A path to the screen diff.
+ ///
+ 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
+
+
+ #region Privates
+
+ static AllureLifecycle Initialize() => new();
+
+ private static JObject GetConfiguration()
+ {
+ var configEnvVarName = AllureConstants.ALLURE_CONFIG_ENV_VARIABLE;
+ var jsonConfigPath = Environment.GetEnvironmentVariable(
+ configEnvVarName
+ );
- public virtual AllureLifecycle StartStep(string parentUuid, string uuid, StepResult stepResult)
+ if (jsonConfigPath != null && !File.Exists(jsonConfigPath))
{
- stepResult.stage = Stage.running;
- stepResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- storage.StartStep(uuid);
- storage.AddStep(parentUuid, uuid, stepResult);
- return this;
+ throw new FileNotFoundException(
+ $"Couldn't find '{jsonConfigPath}' specified " +
+ $"in {configEnvVarName} environment variable"
+ );
}
- public virtual AllureLifecycle UpdateStep(Action update)
+ if (File.Exists(jsonConfigPath))
{
- update.Invoke(storage.Get(storage.GetCurrentStep()));
- return this;
+ return JObject.Parse(File.ReadAllText(jsonConfigPath));
}
- public virtual AllureLifecycle UpdateStep(string uuid, Action update)
- {
- update.Invoke(storage.Get(uuid));
- return this;
- }
+ var defaultJsonConfigPath = Path.Combine(
+ AppDomain.CurrentDomain.BaseDirectory,
+ AllureConstants.CONFIG_FILENAME
+ );
- public virtual AllureLifecycle StopStep(Action beforeStop)
+ if (File.Exists(defaultJsonConfigPath))
{
- UpdateStep(beforeStop);
- return StopStep(storage.GetCurrentStep());
+ return JObject.Parse(File.ReadAllText(defaultJsonConfigPath));
}
- public virtual AllureLifecycle StopStep(string uuid)
+ return JObject.Parse("{}");
+ }
+
+ private void StartFixture(FixtureResult fixtureResult)
+ {
+ this.UpdateContext(c => c.WithFixtureContext(fixtureResult));
+ this.UpdateFixture(startAllureItem);
+ }
+
+ static readonly Action stopContainer =
+ c => c.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+
+ static readonly Action startAllureItem =
+ item =>
{
- var step = storage.Remove(uuid);
- step.stage = Stage.finished;
- step.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- storage.StopStep();
- return this;
- }
+ item.stage = Stage.running;
+ item.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ };
- public virtual AllureLifecycle StopStep()
+ static readonly Action stopAllureItem =
+ item =>
{
- StopStep(storage.GetCurrentStep());
- return this;
- }
+ item.stage = Stage.finished;
+ item.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+ };
- #endregion
+ void UpdateContext(Func updateFn)
+ {
+ this.Context = updateFn(this.Context);
+ }
- #region Attachment
+ static string CreateUuid() =>
+ Guid.NewGuid().ToString("N");
- // TODO: read file in background thread
- public virtual AllureLifecycle AddAttachment(string name, string type, string path)
+ static Action Chain(params Action[] actions) => v =>
+ {
+ foreach (var action in actions)
{
- var fileExtension = new FileInfo(path).Extension;
- return AddAttachment(name, type, File.ReadAllBytes(path), fileExtension);
+ action(v);
}
+ };
- public virtual AllureLifecycle AddAttachment(string name, string type, byte[] content,
- string fileExtension = "")
- {
- var source = $"{Guid.NewGuid().ToString("N")}{AllureConstants.ATTACHMENT_FILE_SUFFIX}{fileExtension}";
- var attachment = new Attachment
- {
- name = name,
- type = type,
- source = source
- };
- writer.Write(source, content);
- storage.Get(storage.GetCurrentStep()).attachments.Add(attachment);
- return this;
- }
+ #endregion
+
+ #region Obsoleted
+
+ internal const string EXPLICIT_STATE_MGMT_OBSOLETE =
+ "Explicit allure state management is obsolete. Methods with " +
+ "explicit uuid parameters will be removed in the future. Use " +
+ "their counterparts without uuids to manipulate the current" +
+ " context.";
+
+ internal const string API_RUDIMENT_OBSOLETE_MSG =
+ "This is a rudimentary part of the API. It has no " +
+ "effect and will be removed in the future.";
- public virtual AllureLifecycle AddAttachment(string path, string name = null)
+ [Obsolete(API_RUDIMENT_OBSOLETE_MSG)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static Func? CurrentTestIdGetter { get; set; }
+
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [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(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle UpdateTestContainer(
+ string uuid,
+ Action update
+ )
+ {
+ var container = this.storage.Get(uuid);
+ lock (this.modelMonitor)
{
- name = name ?? Path.GetFileName(path);
- var type = MimeTypesMap.GetMimeType(path);
- return AddAttachment(name, type, path);
+ update.Invoke(container);
}
+ return this;
+ }
+
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StopTestContainer(string uuid) =>
+ this.UpdateTestContainer(uuid, stopContainer);
+
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle WriteTestContainer(string uuid)
+ {
+ var container = this.storage.Remove(uuid);
+ this.UpdateContext(c => ContextWithNoContainer(c, uuid));
+ this.writer.Write(container);
+ return this;
+ }
+
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartBeforeFixture(
+ FixtureResult result,
+ out string uuid
+ )
+ {
+ uuid = CreateUuid();
+ this.StartBeforeFixture(uuid, result);
+ return this;
+ }
+
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartBeforeFixture(
+ string uuid,
+ FixtureResult result
+ )
+ {
+ this.UpdateTestContainer(c => c.befores.Add(result));
+ this.StartFixture(uuid, result);
+ return this;
+ }
+
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartBeforeFixture(
+ string parentUuid,
+ FixtureResult result,
+ out string uuid
+ )
+ {
+ uuid = CreateUuid();
+ this.StartBeforeFixture(parentUuid, uuid, result);
+ return this;
+ }
+
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartBeforeFixture(
+ string parentUuid,
+ string uuid,
+ FixtureResult result
+ )
+ {
+ this.UpdateTestContainer(parentUuid, c => c.befores.Add(result));
+ this.StartFixture(uuid, result);
+ return this;
+ }
- #endregion
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartAfterFixture(
+ string parentUuid,
+ FixtureResult result,
+ out string uuid
+ )
+ {
+ uuid = CreateUuid();
+ this.StartAfterFixture(parentUuid, uuid, result);
+ return this;
+ }
- #region Extensions
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartAfterFixture(
+ string parentUuid,
+ string uuid,
+ FixtureResult result
+ )
+ {
+ this.UpdateTestContainer(parentUuid, c => c.afters.Add(result));
+ this.StartFixture(uuid, result);
+ return this;
+ }
- public virtual void CleanupResultDirectory()
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle UpdateFixture(
+ string uuid,
+ Action update
+ )
+ {
+ var fixture = this.storage.Get(uuid);
+ lock (this.modelMonitor)
{
- writer.CleanUp();
+ update.Invoke(fixture);
}
+ return this;
+ }
- public virtual AllureLifecycle AddScreenDiff(string testCaseUuid, string expectedPng, string actualPng,
- string diffPng)
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StopFixture(string uuid)
+ {
+ this.UpdateFixture(uuid, stopAllureItem);
+ var fixture = this.storage.Remove(uuid);
+ if (ReferenceEquals(fixture, this.Context.FixtureContext))
{
- AddAttachment(expectedPng, "expected")
- .AddAttachment(actualPng, "actual")
- .AddAttachment(diffPng, "diff")
- .UpdateTestCase(testCaseUuid, x => x.labels.Add(Label.TestType("screenshotDiff")));
-
- return this;
+ this.UpdateContext(c => c.WithNoFixtureContext());
}
+ return this;
+ }
- #endregion
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartTestCase(
+ string containerUuid,
+ TestResult testResult
+ )
+ {
+ this.UpdateTestContainer(
+ containerUuid,
+ c => c.children.Add(testResult.uuid)
+ );
+ return this.StartTestCase(testResult);
+ }
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle UpdateTestCase(
+ string uuid,
+ Action update
+ )
+ {
+ var testResult = this.storage.Get(uuid);
+ lock (this.modelMonitor)
+ {
+ update(testResult);
+ }
+ return this;
+ }
- #region Privates
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StopTestCase(string uuid) =>
+ this.UpdateTestCase(uuid, stopAllureItem);
- private static JObject GetConfiguration()
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle WriteTestCase(string uuid)
+ {
+ var testResult = this.storage.Remove(uuid);
+ if (this.Context.TestContext?.uuid == uuid)
{
- var jsonConfigPath = Environment.GetEnvironmentVariable(AllureConstants.ALLURE_CONFIG_ENV_VARIABLE);
+ this.UpdateContext(c => c.WithNoTestContext());
+ }
+ this.writer.Write(testResult);
+ return this;
+ }
- if (jsonConfigPath != null && !File.Exists(jsonConfigPath))
- throw new FileNotFoundException(
- $"Couldn't find '{jsonConfigPath}' specified in {AllureConstants.ALLURE_CONFIG_ENV_VARIABLE} environment variable");
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartStep(
+ StepResult result,
+ out string uuid
+ )
+ {
+ uuid = CreateUuid();
+ this.StartStep(this.Context.CurrentStepContainer, uuid, result);
+ return this;
+ }
- if (File.Exists(jsonConfigPath))
- return JObject.Parse(File.ReadAllText(jsonConfigPath));
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartStep(
+ string uuid,
+ StepResult result
+ ) => this.StartStep(this.Context.CurrentStepContainer, uuid, result);
+
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StartStep(
+ string parentUuid,
+ string uuid,
+ StepResult stepResult
+ ) => this.StartStep(
+ this.storage.Get(parentUuid),
+ uuid,
+ stepResult
+ );
+
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle UpdateStep(
+ string uuid,
+ Action update
+ )
+ {
+ var stepResult = storage.Get(uuid);
+ lock (this.modelMonitor)
+ {
+ update.Invoke(stepResult);
+ }
+ return this;
+ }
- var defaultJsonConfigPath =
- Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AllureConstants.CONFIG_FILENAME);
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle StopStep(string uuid)
+ {
+ this.UpdateStep(uuid, stopAllureItem);
+ var stepResult = this.storage.Remove(uuid);
+ this.UpdateContext(c => ContextWithNoStep(c, stepResult));
+ return this;
+ }
- if (File.Exists(defaultJsonConfigPath))
- return JObject.Parse(File.ReadAllText(defaultJsonConfigPath));
+ [Obsolete(EXPLICIT_STATE_MGMT_OBSOLETE)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual AllureLifecycle AddScreenDiff(
+ string testCaseUuid,
+ string expectedPng,
+ string actualPng,
+ string diffPng
+ ) => this.AddAttachment(expectedPng, "expected")
+ .AddAttachment(actualPng, "actual")
+ .AddAttachment(diffPng, "diff")
+ .UpdateTestCase(
+ testCaseUuid,
+ x => x.labels.Add(Label.TestType("screenshotDiff"))
+ );
+
+ [Obsolete]
+ void StartFixture(string uuid, FixtureResult fixtureResult)
+ {
+ this.storage.Put(uuid, fixtureResult);
+ this.UpdateContext(c => c.WithFixtureContext(fixtureResult));
+ this.UpdateStep(uuid, startAllureItem);
+ }
- return JObject.Parse("{}");
+ [Obsolete]
+ AllureLifecycle StartStep(
+ ExecutableItem parent,
+ string uuid,
+ StepResult stepResult
+ )
+ {
+ lock (this.modelMonitor)
+ {
+ parent.steps.Add(stepResult);
}
+ this.storage.Put(uuid, stepResult);
+ this.UpdateContext(c => c.WithStep(stepResult));
+ this.UpdateStep(uuid, startAllureItem);
+ return this;
+ }
- private void StartFixture(string uuid, FixtureResult fixtureResult)
+ [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())
{
- storage.Put(uuid, fixtureResult);
- fixtureResult.stage = Stage.running;
- fixtureResult.start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
- storage.ClearStepContext();
- storage.StartStep(uuid);
+ context = context.WithContainer(
+ containersToPushAgain.Pop()
+ );
}
+ return context;
+ }
- #endregion
+ [Obsolete]
+ static AllureContext ContextWithNoStep(
+ AllureContext context,
+ StepResult stepResult
+ )
+ {
+ 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
diff --git a/Allure.Net.Commons/Internal/IsExternalInit.cs b/Allure.Net.Commons/Internal/IsExternalInit.cs
new file mode 100644
index 00000000..21a4cae7
--- /dev/null
+++ b/Allure.Net.Commons/Internal/IsExternalInit.cs
@@ -0,0 +1,19 @@
+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/README.md b/Allure.Net.Commons/README.md
index 1e86d968..62f72856 100644
--- a/Allure.Net.Commons/README.md
+++ b/Allure.Net.Commons/README.md
@@ -10,13 +10,14 @@ Allure lifecycle is configured via json file with default name `allureConfig.jso
- set ALLURE_CONFIG environment variable to the full path of json config file. This option is preferable for .net core projects which utilize nuget libraries directly from nuget packages folder. See this example of setting it via code: https://github.com/allure-framework/allure-csharp/blob/bdf11bd3e1f41fd1e4a8fd22fa465b90b68e9d3f/Allure.Commons.NetCore.Tests/AllureConfigTests.cs#L13-L15
- place `allureConfig.json` to the location of `Allure.Commons.dll`. This option can be used with .net classic projects which copy all referenced package libraries into binary folder. Do not forget to set 'Copy to Output Directory' property to 'Copy always' or 'Copy if newer' in your test project or set it in .csproj:
-```
-
-
-PreserveNewest
-
-
-```
+ ```xml
+
+
+ PreserveNewest
+
+
+ ```
+
Allure lifecycle will start with default configuration settings if `allureConfig.json` is not found.
Raw json configuration can be accessed from `AllureLifeCycle.Instance.JsonConfiguration` to extend configuration by adapters. See extension example here: https://github.com/allure-framework/allure-csharp/blob/bdf11bd3e1f41fd1e4a8fd22fa465b90b68e9d3f/Allure.SpecFlowPlugin/PluginHelper.cs#L20-L29
@@ -39,13 +40,14 @@ Allure configuration section is used to setup output directory and link patterns
}
}
```
-All
-Link pattern placeholders will be replaced with URL value of corresponding link type, e.g.
+
+All link pattern placeholders will be replaced with URL value of corresponding link type, e.g.
`link(type: "issue", url: "BUG-01") => https://example.org/BUG-01`
### AllureLifecycle
-[AllureLifecycle](https://github.com/allure-framework/allure-csharp/blob/main/Allure.Commons/AllureLifecycle.cs) class provides methods for test engine events processing.
+[AllureLifecycle](https://github.com/allure-framework/allure-csharp/blob/main/Allure.Commons/AllureLifecycle.cs)
+class provides methods for test engine events processing.
Use `AllureLifecycle.Instance` property to access.
@@ -61,7 +63,7 @@ Use `AllureLifecycle.Instance` property to access.
* StopTestCase
* WriteTestCase
-### Step Events
+#### Step Events
* StartStep
* UpdateStep
* StopStep
@@ -73,5 +75,50 @@ Use `AllureLifecycle.Instance` property to access.
#### Utility Methods
* CleanupResultDirectory - can be used in test run setup to clean old result files
+#### Context capturing
+The methods above operate on the current Allure context. This context
+flows naturally as a part of ExecutionContext and is subject to the same
+constraints. Particularly, changes made in an async callee can't be observed
+by the caller.
+
+Use the following methods of `AllureLifecycle` to capture Allure context and
+to operate on a context later, after it has been captured:
+
+* Context
+* RunInContext
+
+Example:
+
+```csharp
+public static async Task Caller(ScenarioContext scenario)
+{
+ await Callee(scenario);
+ AllureLifecycle.Instance.RunInContext(
+ scenario.Get(),
+ () =>
+ {
+ // The test context required by the below methods wouldn't be
+ // visible if they weren't wrapped with RunInContext.
+ AllureLifecycle.Instance.StopTestCase();
+ AllureLifecycle.Instance.WriteTestCase();
+ }
+ );
+}
+
+public static async Task Callee(ScenarioContext scenario)
+{
+ AllureLifecycle.Instance.StartTestCase(
+ new(){ uuid = Guid.NewGuid().ToString() }
+ );
+
+ // Pass Allure context to the caller via ScenarioContext
+ scenario.Set(AllureLifecycle.Instance.Context);
+}
+```
+
+#### Obsoleted methods
+Methods with explicit uuid parameters are deprecated. Migrate to their
+uuid-less counterparts that operate on the current Allure context.
+
### Troubleshooting
...
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 7f124ae2..8673446b 100644
--- a/Allure.Net.Commons/Steps/CoreStepsHelper.cs
+++ b/Allure.Net.Commons/Steps/CoreStepsHelper.cs
@@ -1,289 +1,304 @@
using System;
-using System.Threading;
+using System.ComponentModel;
using System.Threading.Tasks;
using Allure.Net.Commons.Storage;
-namespace Allure.Net.Commons.Steps
+#nullable enable
+
+namespace Allure.Net.Commons.Steps;
+
+public class CoreStepsHelper
{
- public class CoreStepsHelper
+ public static IStepLogger? StepLogger { get; set; }
+
+ #region Fixtures
+
+ public static void StartBeforeFixture(string name)
{
- public static IStepLogger StepLogger { get; set; }
+ AllureLifecycle.Instance.StartBeforeFixture(new() { name = name });
+ StepLogger?.BeforeStarted?.Log(name);
+ }
- private static readonly AsyncLocal TestResultAccessorAsyncLocal = new();
+ public static void StartAfterFixture(string name)
+ {
+ AllureLifecycle.Instance.StartAfterFixture(new() { name = name });
+ StepLogger?.AfterStarted?.Log(name);
+ }
- public static ITestResultAccessor TestResultAccessor
- {
- get => TestResultAccessorAsyncLocal.Value;
- set => TestResultAccessorAsyncLocal.Value = value;
- }
-
- #region Fixtures
+ public static void StopFixture(Action updateResults) =>
+ AllureLifecycle.Instance.StopFixture(updateResults);
- public static string StartBeforeFixture(string name)
- {
- var fixtureResult = new FixtureResult()
- {
- name = name,
- stage = Stage.running,
- start = DateTimeOffset.Now.ToUnixTimeMilliseconds()
- };
-
- AllureLifecycle.Instance.StartBeforeFixture(TestResultAccessor.TestResultContainer.uuid, fixtureResult, out var uuid);
- StepLogger?.BeforeStarted?.Log(name);
- return uuid;
- }
+ public static void StopFixture() =>
+ AllureLifecycle.Instance.StopFixture();
- public static string StartAfterFixture(string name)
- {
- var fixtureResult = new FixtureResult()
- {
- name = name,
- stage = Stage.running,
- start = DateTimeOffset.Now.ToUnixTimeMilliseconds()
- };
-
- AllureLifecycle.Instance.StartAfterFixture(TestResultAccessor.TestResultContainer.uuid, fixtureResult, out var uuid);
- StepLogger?.AfterStarted?.Log(name);
- return uuid;
- }
+ #endregion
- 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 StopFixtureSuppressTestCase(Action updateResults = null)
- {
- var newTestResult = TestResultAccessor.TestResult;
- StopFixture(updateResults);
- AllureLifecycle.Instance.StartTestCase(TestResultAccessor.TestResultContainer.uuid, newTestResult);
- }
+ #region Steps
- #endregion
+ public static void StartStep(string name)
+ {
+ AllureLifecycle.Instance.StartStep(new() { name = name });
+ StepLogger?.StepStarted?.Log(name);
+ }
- #region Steps
+ public static void StartStep(string name, Action updateResults)
+ {
+ StartStep(name);
+ AllureLifecycle.Instance.UpdateStep(updateResults);
+ }
- public static string StartStep(string name, Action updateResults = null)
+ public static void PassStep() => AllureLifecycle.Instance.StopStep(
+ result =>
{
- var stepResult = new StepResult
- {
- name = name,
- stage = Stage.running,
- start = DateTimeOffset.Now.ToUnixTimeMilliseconds()
- };
- updateResults?.Invoke(stepResult);
-
- AllureLifecycle.Instance.StartStep(stepResult, out var uuid);
- StepLogger?.StepStarted?.Log(name);
- return uuid;
+ result.status = Status.passed;
+ StepLogger?.StepPassed?.Log(result.name);
}
+ );
- public static void PassStep(Action updateResults = null)
- {
- AllureLifecycle.Instance.StopStep(result =>
- {
- result.status = Status.passed;
- updateResults?.Invoke(result);
- StepLogger?.StepPassed?.Log(result.name);
- });
- }
+ public static void PassStep(Action updateResults)
+ {
+ AllureLifecycle.Instance.UpdateStep(updateResults);
+ PassStep();
+ }
- public static void PassStep(string uuid, Action updateResults = null)
+ public static void FailStep() => AllureLifecycle.Instance.StopStep(
+ result =>
{
- AllureLifecycle.Instance.UpdateStep(uuid, result =>
- {
- result.status = Status.passed;
- updateResults?.Invoke(result);
- StepLogger?.StepPassed?.Log(result.name);
- });
- AllureLifecycle.Instance.StopStep(uuid);
+ result.status = Status.failed;
+ StepLogger?.StepFailed?.Log(result.name);
}
+ );
- public static void FailStep(Action updateResults = null)
- {
- AllureLifecycle.Instance.StopStep(result =>
- {
- result.status = Status.failed;
- updateResults?.Invoke(result);
- StepLogger?.StepFailed?.Log(result.name);
- });
- }
+ public static void FailStep(Action updateResults)
+ {
+ AllureLifecycle.Instance.UpdateStep(updateResults);
+ FailStep();
+ }
- public static void FailStep(string uuid, Action updateResults = null)
+ public static void BrokeStep() => AllureLifecycle.Instance.StopStep(
+ result =>
{
- AllureLifecycle.Instance.UpdateStep(uuid, result =>
- {
- result.status = Status.failed;
- updateResults?.Invoke(result);
- StepLogger?.StepFailed?.Log(result.name);
- });
- AllureLifecycle.Instance.StopStep(uuid);
- }
-
- public static void BrokeStep(Action updateResults = null)
- {
- AllureLifecycle.Instance.StopStep(result =>
- {
- result.status = Status.broken;
- updateResults?.Invoke(result);
- StepLogger?.StepBroken?.Log(result.name);
- });
- }
-
- public static void BrokeStep(string uuid, Action updateResults = null)
- {
- AllureLifecycle.Instance.UpdateStep(uuid, result =>
- {
- result.status = Status.broken;
- updateResults?.Invoke(result);
- StepLogger?.StepBroken?.Log(result.name);
- });
- AllureLifecycle.Instance.StopStep(uuid);
+ result.status = Status.broken;
+ StepLogger?.StepBroken?.Log(result.name);
}
+ );
+
+ public static void BrokeStep(Action updateResults)
+ {
+ AllureLifecycle.Instance.UpdateStep(updateResults);
+ BrokeStep();
+ }
- #endregion
+ #endregion
- #region Misc
+ #region Misc
- public static void UpdateTestResult(Action update)
- {
- AllureLifecycle.Instance.UpdateTestCase(TestResultAccessor.TestResult.uuid, update);
- }
+ public static void UpdateTestResult(Action update) =>
+ AllureLifecycle.Instance.UpdateTestCase(update);
- #endregion
+ #endregion
- public static Task Step(string name, Func> action)
+ public static Task Step(string name, Func> action)
+ {
+ StartStep(name);
+ return Execute(action);
+ }
+
+ public static T Step(string name, Func action)
+ {
+ StartStep(name);
+ return Execute(name, action);
+ }
+
+ public static void Step(string name, Action action)
+ {
+ Step(name, (Func