Skip to content

Commit

Permalink
Add support for async NUnit tests allure-framework#303
Browse files Browse the repository at this point in the history
  • Loading branch information
overlord committed Dec 29, 2022
1 parent 3af849d commit c0fd75e
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 19 deletions.
24 changes: 24 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# EditorConfig is awesome: http://EditorConfig.org

# indent_style : { "tab", "space" }
# indent_size : { an integer }
# tab_width : { optional positive integer (defaults "indent_size" when "indent_size" is a number) }
# end_of_line : { "lf", "cr", "crlf" }
# charset : { "latin1", "utf-8", "utf-16be", "utf-16le" }
# trim_trailing_whitespace : { "true", "false" }
# insert_final_newline : { "true", "false" }
# max_line_length : { positive integers }

root = true

[*]
trim_trailing_whitespace = true
insert_final_newline = false

[*.md]
trim_trailing_whitespace = false

[*.{cs,csproj}]
charset = utf-8-bom
indent_style = space
indent_size = 4
68 changes: 68 additions & 0 deletions Allure.NUnit.Examples/AllureAsyncLifeCycleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Allure.Net.Commons;
using NUnit.Allure.Attributes;
using NUnit.Allure.Core;
using NUnit.Framework;

namespace Allure.NUnit.Examples
{
[AllureNUnit]
public class AllureAsyncLifeCycleTests
{
[Test]
public async Task AsyncTest_With_AllureStepAttribute()
{
Console.WriteLine("> Сalling async method with [AllureStep] ...");
await StepWithAttribute();
Console.WriteLine(" Done!");
}

[AllureStep("Calling StepWithAttribute")]
private async Task StepWithAttribute()
{
// switching thread on async
Console.WriteLine($" > Delay...");
await Task.Delay(1000);
Console.WriteLine($" Done!");

// use internally allure steps storage on different thread
Console.WriteLine($" > AddAttachment...");
AllureLifecycle.Instance.AddAttachment("attachment-name", "text/plain", Encoding.UTF8.GetBytes("attachment-value"));
Console.WriteLine($" Done!");
}

[Test]
public async Task AsyncTest_With_WrapInStep()
{
Console.WriteLine("> StepLevel1...");
await AllureLifecycle.Instance.WrapInStepAsync(
async () => await StepLevel1()
);
Console.WriteLine(" Done!");
}

private async Task StepLevel1()
{
Console.WriteLine(" > StepLevel2...");
await AllureLifecycle.Instance.WrapInStepAsync(
async () => await StepLevel2()
);
Console.WriteLine(" Done!");
}

private async Task StepLevel2()
{
// switching thread on async
Console.WriteLine($" > Sleep...");
await Task.Delay(1000);
Console.WriteLine($" Done!");

// use internally allure steps storage on different thread
Console.WriteLine($" > AddAttachment...");
AllureLifecycle.Instance.AddAttachment("attachment-name", "text/plain", Encoding.UTF8.GetBytes("attachment-value"));
Console.WriteLine($" Done!");
}
}
}
72 changes: 71 additions & 1 deletion Allure.NUnit/Core/AllureExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Allure.Net.Commons;
using NUnit.Framework.Internal;
using System.Linq;
using System.Threading.Tasks;

namespace NUnit.Allure.Core
{
Expand Down Expand Up @@ -82,7 +83,7 @@ public static void WrapInStep(this AllureLifecycle lifecycle, Action action, str
throw;
}
}

/// <summary>
/// Wraps Func into AllureStep.
/// </summary>
Expand Down Expand Up @@ -113,6 +114,75 @@ public static T WrapInStep<T>(this AllureLifecycle lifecycle, Func<T> func, stri
}
}

/// <summary>
/// Wraps async Action into AllureStep.
/// </summary>
public static async Task WrapInStepAsync(
this AllureLifecycle lifecycle,
Func<Task> action,
string stepName = "",
[CallerMemberName] string callerName = ""
)
{
if (string.IsNullOrEmpty(stepName)) stepName = callerName;

var id = Guid.NewGuid().ToString();
var stepResult = new StepResult { name = stepName };
try
{
lifecycle.StartStep(id, stepResult);
await action();
lifecycle.StopStep(step => stepResult.status = Status.passed);
}
catch (Exception e)
{
lifecycle.StopStep(step =>
{
step.statusDetails = new StatusDetails
{
message = e.Message,
trace = e.StackTrace
};
step.status = AllureNUnitHelper.GetNUnitStatus();
});
throw;
}
}

/// <summary>
/// Wraps async Func into AllureStep.
/// </summary>
public static async Task<T> WrapInStepAsync<T>(
this AllureLifecycle lifecycle,
Func<Task<T>> func,
string stepName = "",
[CallerMemberName] string callerName = ""
)
{
if (string.IsNullOrEmpty(stepName)) stepName = callerName;
var id = Guid.NewGuid().ToString();
var stepResult = new StepResult { name = stepName };
try
{
lifecycle.StartStep(id, stepResult);
var result = await func();
lifecycle.StopStep(step => stepResult.status = Status.passed);
return result;
}
catch (Exception e)
{
lifecycle.StopStep(step =>
{
step.statusDetails = new StatusDetails
{
message = e.Message,
trace = e.StackTrace
};
step.status = AllureNUnitHelper.GetNUnitStatus();
});
throw;
}
}

/// <summary>
/// AllureNUnit AddScreenDiff wrapper method.
Expand Down
25 changes: 20 additions & 5 deletions Allure.NUnit/Core/AllureNUnitAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Threading;
using System.Collections.Concurrent;
using Allure.Net.Commons;
using NUnit.Framework;
using NUnit.Framework.Interfaces;

Expand All @@ -8,23 +9,37 @@ namespace NUnit.Allure.Core
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)]
public class AllureNUnitAttribute : PropertyAttribute, ITestAction
{
private readonly ThreadLocal<AllureNUnitHelper> _allureNUnitHelper = new ThreadLocal<AllureNUnitHelper>(true);
private readonly ConcurrentDictionary<string, AllureNUnitHelper> _allureNUnitHelper = new ConcurrentDictionary<string, AllureNUnitHelper>();
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;
}

public AllureNUnitAttribute(bool wrapIntoStep = true)
{
_isWrappedIntoStep = wrapIntoStep;
}

public void BeforeTest(ITest test)
{
_allureNUnitHelper.Value = new AllureNUnitHelper(test);
_allureNUnitHelper.Value.StartAll(_isWrappedIntoStep);
var value = new AllureNUnitHelper(test);

_allureNUnitHelper.AddOrUpdate(test.Id, value, (key, existing) => value);

value.StartAll(_isWrappedIntoStep);
}

public void AfterTest(ITest test)
{
_allureNUnitHelper.Value.StopAll(_isWrappedIntoStep);
if(_allureNUnitHelper.TryGetValue(test.Id, out var value))
{
value.StopAll(_isWrappedIntoStep);
}
}

public ActionTargets Targets => ActionTargets.Test;
Expand Down
15 changes: 9 additions & 6 deletions Allure.Net.Commons/AllureLifecycle.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using Allure.Net.Commons.Helpers;
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;
Expand All @@ -19,6 +20,9 @@ public class AllureLifecycle
private readonly AllureStorage storage;
private readonly IAllureResultsWriter writer;

/// <summary> Method to get the key for separation the steps for different tests. </summary>
public static Func<string> CurrentTestIdGetter { get; set; } = () => Thread.CurrentThread.ManagedThreadId.ToString();

internal AllureLifecycle(): this(GetConfiguration())
{
}
Expand All @@ -29,10 +33,6 @@ internal AllureLifecycle(JObject config)
AllureConfiguration = AllureConfiguration.ReadFromJObject(config);
writer = new FileSystemResultsWriter(AllureConfiguration);
storage = new AllureStorage();
lock (Lockobj)
{
instance = this;
}
}

public string JsonConfiguration { get; private set; }
Expand All @@ -49,7 +49,10 @@ public static AllureLifecycle Instance
lock (Lockobj)
{
if (instance == null)
new AllureLifecycle();
{
var localInstance = new AllureLifecycle();
Interlocked.Exchange(ref instance, localInstance);
}
}
}

Expand Down
15 changes: 8 additions & 7 deletions Allure.Net.Commons/Storage/AllureStorage.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
using System.Collections.Concurrent;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;

namespace Allure.Net.Commons.Storage
{
internal class AllureStorage
{
private readonly ThreadLocal<LinkedList<string>> stepContext = new ThreadLocal<LinkedList<string>>(() =>
{
return new LinkedList<string>();
});
private readonly ConcurrentDictionary<string, LinkedList<string>> stepContext = new();

private readonly ConcurrentDictionary<string, object> storage = new ConcurrentDictionary<string, object>();
private readonly ConcurrentDictionary<string, object> storage = new();

private LinkedList<string> Steps => stepContext.Value;
private LinkedList<string> Steps => stepContext.GetOrAdd(
AllureLifecycle.CurrentTestIdGetter(),
new LinkedList<string>()
);

public T Get<T>(string uuid)
{
Expand Down

0 comments on commit c0fd75e

Please sign in to comment.