Skip to content

Commit

Permalink
[NUnit] Fix async behaviour in [AllureStep] aspect + Allow '{obj.prop…
Browse files Browse the repository at this point in the history
…}' placeholders in [AllureStep] step name (#329)

Co-authored-by: Кабанов Константин Юрьевич <kykabano@mts.ru>
  • Loading branch information
overlord and Кабанов Константин Юрьевич authored Feb 13, 2023
1 parent e4f0456 commit 5a161a0
Show file tree
Hide file tree
Showing 4 changed files with 334 additions and 105 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,5 @@ __pycache__/
.DS_Store
*mono_crash*
/.vscode

.editorconfig
67 changes: 67 additions & 0 deletions Allure.NUnit.Examples/AllureStepNameInternalsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.Reflection;
using NUnit.Allure.Attributes;
using NUnit.Allure.Core.Steps;
using NUnit.Framework;

namespace Allure.NUnit.Examples
{
[AllureSuite("Tests - Step Names")]
public class AllureStepNameInternalsTest : BaseTest
{
internal class LocalTestClass
{
public string TestField;
public string TestProp { get; set; }

public void TestMethod(string name, LocalTestClass request, int id)
{
Console.WriteLine($"{id} - {name} ({request})");
}
}

[TestCase("", ExpectedResult = "")]
[TestCase(null, ExpectedResult = "")]
[TestCase("{0} - {1} - {2} - {3} - {100} - {-100}", ExpectedResult = "Super Mario - Allure.NUnit.Examples.AllureStepNameInternalsTest+LocalTestClass - 12345 - {3} - {100} - {-100}")]
[TestCase("{id} - {0}", ExpectedResult = "12345 - Super Mario")]
[TestCase("{id} - {name} ({request})", ExpectedResult = "12345 - Super Mario (Allure.NUnit.Examples.AllureStepNameInternalsTest+LocalTestClass)")]
[TestCase("{id} - {request.TestField} - {request.TestProp}", ExpectedResult = "12345 - FieldValue - PropValue")]
[TestCase("{notExistingParameter} - {request.NotExistingField}", ExpectedResult = "{notExistingParameter} - {request.NotExistingField}")]
public string ApplyValues_Test(string stepNamePattern)
{
MethodBase methodBase = typeof(LocalTestClass).GetMethod(nameof(LocalTestClass.TestMethod))!;
object[] arguments = new object[]
{
"Super Mario", // name = {0}
new LocalTestClass // request = {1}
{
TestField = "FieldValue",
TestProp = "PropValue",
},
12345, // id = {2}
};

return AllureStepParameterHelper.GetStepName(stepNamePattern, methodBase, arguments);
}

[TestCase("", ExpectedResult = "")]
[TestCase(null, ExpectedResult = "")]
[TestCase("{0} - {1} - {2} - {3} - {100} - {-100}", ExpectedResult = "Super Mario - null - 12345 - {3} - {100} - {-100}")]
[TestCase("{id} - {0}", ExpectedResult = "12345 - Super Mario")]
[TestCase("{id} - {name} ({request})", ExpectedResult = "12345 - Super Mario (null)")]
[TestCase("{id} - {request.TestField} - {request.TestProp}", ExpectedResult = "12345 - {request.TestField} - {request.TestProp}")]
[TestCase("{notExistingParameter} - {request.NotExistingField}", ExpectedResult = "{notExistingParameter} - {request.NotExistingField}")]
public string ApplyNullValues_Test(string stepNamePattern)
{
MethodBase methodBase = typeof(LocalTestClass).GetMethod(nameof(LocalTestClass.TestMethod))!;
object[] arguments = new object[]
{
"Super Mario", // name = {0}
null,
12345, // id = {2}
};

return AllureStepParameterHelper.GetStepName(stepNamePattern, methodBase, arguments);
}
}
}
247 changes: 147 additions & 100 deletions Allure.NUnit/Core/Steps/AllureStepAspect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,107 +19,68 @@ public class AllureStepAspect

private static readonly MethodInfo SyncHandler =
typeof(AllureStepAspect).GetMethod(nameof(WrapSync), BindingFlags.NonPublic | BindingFlags.Static);

private static readonly Type _voidTaskResult = Type.GetType("System.Threading.Tasks.VoidTaskResult");

private static readonly MethodInfo SyncVoidHandler =
typeof(AllureStepAspect).GetMethod(nameof(WrapSyncVoid), BindingFlags.NonPublic | BindingFlags.Static);

private static readonly Type _typeVoidTaskResult = Type.GetType("System.Threading.Tasks.VoidTaskResult");
private static readonly Type _typeVoid = typeof(void);
private static readonly Type _typeTask = typeof(Task);

[Advice(Kind.Around, Targets = Target.Method)]
public object Around([Argument(Source.Name)] string name,
public object Around(
[Argument(Source.Name)] string name,
[Argument(Source.Arguments)] object[] args,
[Argument(Source.Target)] Func<object[], object> target,
[Argument(Source.Metadata)] MethodBase metadata,
[Argument(Source.ReturnType)] Type returnType)
[Argument(Source.ReturnType)] Type returnType
)
{
object executionResult;
var stepName = metadata.GetCustomAttribute<AllureStepBaseAttribute>().Name ?? name;
var stepNamePattern = metadata.GetCustomAttribute<AllureStepBaseAttribute>().Name ?? name;
var stepName = AllureStepParameterHelper.GetStepName(stepNamePattern, metadata, args);
var stepParameters = GetStepParameters(metadata, args);

for (var i = 0; i < args.Length; i++)
if (_typeTask.IsAssignableFrom(returnType))
{
stepName = stepName?.Replace("{" + i + "}", args[i]?.ToString() ?? "null");
if (stepName.Contains("{" + i + "}"))
{
// TODO: provide error description link
Console.Error.WriteLine("Indexed step arguments is obsolete. Use named arguments instead. ({0} -> {argumentName}) See: LINK_TO_ERROR");
}
var syncResultType = returnType.IsConstructedGenericType
? returnType.GenericTypeArguments[0]
: _typeVoidTaskResult;
return AsyncHandler.MakeGenericMethod(syncResultType)
.Invoke(this, new object[] { target, args, metadata, stepName, stepParameters });
}

stepName = metadata.GetParameters().Aggregate(stepName,
(current, parameterInfo) => current?.Replace("{" + parameterInfo.Name + "}",
args[parameterInfo.Position]?.ToString() ?? "null"));

var stepResult = string.IsNullOrEmpty(stepName)
? new StepResult {name = name, parameters = AllureStepParameterHelper.CreateParameters(args)}
: new StepResult {name = stepName, parameters = AllureStepParameterHelper.CreateParameters(args)};

var stepParameters = metadata.GetParameters()
.Select(x => (
name: x.GetCustomAttribute<NameAttribute>()?.Name ?? x.Name,
skip: x.GetCustomAttribute<SkipAttribute>() != null))
.Zip(args, (parameter, value) => parameter.skip
? null
: new Parameter
{
name = parameter.name,
value = value?.ToString()
})
.Where(x => x != null)
.ToList();

try
else if (_typeVoid.IsAssignableFrom(returnType))
{
StartFixture(metadata, stepName);
StartStep(metadata, stepName, stepParameters);

executionResult = GetStepExecutionResult(returnType, target, args);
if (executionResult != null && typeof(Task).IsAssignableFrom(executionResult.GetType()))
{
((Task)executionResult).ContinueWith((task) =>
{
if (task.IsFaulted)
{
var e = task.Exception;
ThrowStep(metadata, e?.InnerException);
ThrowFixture(metadata, e?.InnerException);
}
else
{
PassStep(metadata);
PassFixture(metadata);
}
});
}
else
{
PassStep(metadata);
PassFixture(metadata);
}
return SyncVoidHandler
.Invoke(this, new object[] { target, args, metadata, stepName, stepParameters });
}
catch (Exception e)
else
{
ThrowStep(metadata, e);
ThrowFixture(metadata, e);
throw;
return SyncHandler.MakeGenericMethod(returnType)
.Invoke(this, new object[] { target, args, metadata, stepName, stepParameters });
}

return executionResult;
}

private static void StartStep(MethodBase metadata, string stepName, List<Parameter> stepParameters)

// ------------------------------

private static string StartStep(MethodBase metadata, string stepName, List<Parameter> stepParameters)
{
if (metadata.GetCustomAttribute<AllureStepAttribute>() != null)
{
StepsHelper.StartStep(stepName, step => step.parameters = stepParameters);
return StepsHelper.StartStep(stepName, step => step.parameters = stepParameters);
}

return null;
}
private static void PassStep(MethodBase metadata)

private static void PassStep(string uuid, MethodBase metadata)
{
if (metadata.GetCustomAttribute<AllureStepAttribute>() != null)
{
StepsHelper.PassStep();
StepsHelper.PassStep(uuid);
}
}
private static void ThrowStep(MethodBase metadata, Exception e)

private static void ThrowStep(string uuid, MethodBase metadata, Exception e)
{
if (metadata.GetCustomAttribute<AllureStepAttribute>() != null)
{
Expand All @@ -128,18 +89,18 @@ private static void ThrowStep(MethodBase metadata, Exception e)
message = e.Message,
trace = e.StackTrace
};

if (e is NUnitException || e is AssertionException)
{
StepsHelper.FailStep(result => result.statusDetails = exceptionStatusDetails);
StepsHelper.FailStep(uuid, result => result.statusDetails = exceptionStatusDetails);
}
else
{
StepsHelper.BrokeStep(result => result.statusDetails = exceptionStatusDetails);
StepsHelper.BrokeStep(uuid, result => result.statusDetails = exceptionStatusDetails);
}
}
}

private static void StartFixture(MethodBase metadata, string stepName)
{
if (metadata.GetCustomAttribute<AllureBeforeAttribute>() != null)
Expand All @@ -152,7 +113,7 @@ private static void StartFixture(MethodBase metadata, string stepName)
StepsHelper.StartAfterFixture(stepName);
}
}

private static void PassFixture(MethodBase metadata)
{
if (metadata.GetCustomAttribute<AllureBeforeAttribute>() != null ||
Expand All @@ -161,7 +122,7 @@ private static void PassFixture(MethodBase metadata)
StepsHelper.StopFixtureSuppressTestCase(result => result.status = Status.passed);
}
}

private static void ThrowFixture(MethodBase metadata, Exception e)
{
if (metadata.GetCustomAttribute<AllureBeforeAttribute>() != null ||
Expand Down Expand Up @@ -192,34 +153,120 @@ private static void ThrowFixture(MethodBase metadata, Exception e)
}
}

private object GetStepExecutionResult(Type returnType, Func<object[], object> target, object[] args)
// ------------------------------

private List<Parameter> GetStepParameters(MethodBase metadata, object[] args)
{
if (typeof(Task).IsAssignableFrom(returnType))
return metadata.GetParameters()
.Select(x => (
name: x.GetCustomAttribute<NameAttribute>()?.Name ?? x.Name,
skip: x.GetCustomAttribute<SkipAttribute>() != null))
.Zip(args,
(parameter, value) => parameter.skip
? null
: new Parameter
{
name = parameter.name,
value = value?.ToString()
})
.Where(x => x != null)
.ToList();
}

// ------------------------------

private static string BeforeTargetInvoke(MethodBase metadata, string stepName, List<Parameter> stepParameters)
{
StartFixture(metadata, stepName);
var stepUuid = StartStep(metadata, stepName, stepParameters);
return stepUuid;
}

private static void AfterTargetInvoke(string stepUuid, MethodBase metadata)
{
PassStep(stepUuid, metadata);
PassFixture(metadata);
}

private static void OnTargetInvokeException(string stepUuid, MethodBase metadata, Exception e)
{
ThrowStep(stepUuid, metadata, e);
ThrowFixture(metadata, e);
}

// ------------------------------

private static T WrapSync<T>(
Func<object[], object> target,
object[] args,
MethodBase metadata,
string stepName,
List<Parameter> stepParameters
)
{
string stepUuid = null;

try
{
var syncResultType = returnType.IsConstructedGenericType
? returnType.GenericTypeArguments[0]
: _voidTaskResult;
return AsyncHandler.MakeGenericMethod(syncResultType)
.Invoke(this, new object[] { target, args });
}
stepUuid = BeforeTargetInvoke(metadata, stepName, stepParameters);
var result = (T)target(args);
AfterTargetInvoke(stepUuid, metadata);

if (typeof(void).IsAssignableFrom(returnType))
return result;
}
catch (Exception e)
{
return target(args);
OnTargetInvokeException(stepUuid, metadata, e);
throw;
}

return SyncHandler.MakeGenericMethod(returnType)
.Invoke(this, new object[] { target, args });
}

private static T WrapSync<T>(Func<object[], object> target, object[] args)
private static void WrapSyncVoid(
Func<object[], object> target,
object[] args,
MethodBase metadata,
string stepName,
List<Parameter> stepParameters
)
{
return (T)target(args);
string stepUuid = null;

try
{
stepUuid = BeforeTargetInvoke(metadata, stepName, stepParameters);
target(args);
AfterTargetInvoke(stepUuid, metadata);
}
catch (Exception e)
{
OnTargetInvokeException(stepUuid, metadata, e);
throw;
}
}

private static async Task<T> WrapAsync<T>(Func<object[], object> target, object[] args)
private static async Task<T> WrapAsync<T>(
Func<object[], object> target,
object[] args,
MethodBase metadata,
string stepName,
List<Parameter> stepParameters
)
{
return await (Task<T>)target(args);
string stepUuid = null;

try
{
stepUuid = BeforeTargetInvoke(metadata, stepName, stepParameters);
var result = await ((Task<T>)target(args)).ConfigureAwait(false);
AfterTargetInvoke(stepUuid, metadata);

return result;
}
catch (Exception e)
{
OnTargetInvokeException(stepUuid, metadata, e);
throw;
}
}
}
}
Loading

0 comments on commit 5a161a0

Please sign in to comment.