Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NUnit] Fix async behaviour in [AllureStep] aspect + Allow '{obj.prop}' placeholders in [AllureStep] step name #329

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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