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

Add Allure.Reqnroll test output handler support #526

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

coman3
Copy link

@coman3 coman3 commented Jul 4, 2024

Context

Usage of the built in reqnroll test output helper was ignored, and no event was in place to handle these outputs. As text based test output can be extremely useful to help diagnose issues, this was a hard requirement for us to migrate from SpecFlow and LivingDoc to Reqnroll and Allure Reports.

Checklist

  • [Sign Allure CLA][cla]
  • Provide unit tests TBD

Unit tests are still to be updated, i just wanted to get opinions on how better this could potentially be implemented.

Notes

I really didn't want to modify the Allure.Net.Commons project in order to enable support for this, however i did want to ensure that test outputs can be captured in as many cases as possible, without each write to these outputs being placed into a single file - hence the OutputCache static property that I really didn't want to need.

Any suggestions / improvements here are of course welcome. This PR is a major step to towards us being able to implement Allure instead of LivingDoc for our team, so hopefully we can figure out a way to get this in, otherwise we might have to fork this 😊

Usage of the ExecutableItem as a key for the dictionary ensures support for parallel test executions within a container, however there is limitations on a single container I can't seem to identify exactly the purpose of the container, or when multiple containers might exist, any advice here would be great, and I can then adapt this to work for many containers if required.

Preview

A preview of what this looks like in the test overview is like so:
msedge_oOmb02boRr

Usage is compatible with existing ReqnRoll / SpecFlow output helper:

    readonly ITestOutputHelper outputHelper;
    public SomeClass(ITestOutputHelper outputHelper)
    {
        this.outputHelper = outputHelper;
    }
    [StepDefinition("Step with test output")] //Note that AfterFeature, AfterScenario, AfterStep (and before) all work as well
    public void StepWithTestOutput() => outputHelper.WriteLine("Test output message");

@CLAassistant
Copy link

CLAassistant commented Jul 4, 2024

CLA assistant check
All committers have signed the CLA.

@@ -38,6 +39,8 @@ static class AllureReqnrollStateFacade

static AllureLifecycle Lifecycle { get => AllureLifecycle.Instance; }

static Dictionary<ExecutableItem, StringBuilder> OutputCache { get; } = new();
Copy link
Author

@coman3 coman3 Jul 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I typically try to avoid static dictionaries, especially in parallel execution contexts, however i couldn't see a path forward not using something like this, without modifying the Allure.Net.Common library.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a valid concern. Please take a look at my review comment for the alternative.

@delatrie
Copy link
Contributor

delatrie commented Jul 4, 2024

Hi, @coman3 ! Thank you for your contribution. This is definitely a feature we want to have. I agree we should scope it at the Allure.Reqnroll level for now, because a general abstraction needs more design effort than a Reqnroll-specific implementation.

I will take a closer look soon and provide you with my feedback!

@coman3
Copy link
Author

coman3 commented Aug 7, 2024

hey @delatrie, would love a quick update on this if you have a chance 😊

@coman3 coman3 marked this pull request as ready for review August 16, 2024 01:40
Copy link
Contributor

@delatrie delatrie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello, @coman3!

Sorry for having kept you waiting.

I took a look, and I like your approach. It's simple and works well both with steps and hooks.

Let's fix some concurrency issues, though:

  • Access to StringBuilder should be wrapped in lock.
  • Use ConcurrentDictionary and its GetOrAdd and TryRemove methods instead of plain Dictionary.
  • Use ExecutionItem.uuid as keys instead of ExecutionItem instances themselves.

May I also ask you to factor out the code related to the output handling to a separate class please? Something like TestOutputCache with the following methods:

  • AddOutput(string text)
  • AttachOutput()
  • Clear()

Here is how to use the concurrent dictionary inside those methods:

public void AddOutput(string text)
{
    var sb = this.outputCache.GetOrAdd(GetKey(), () => new StringBuilder());
    lock (sb)
    {
        sb.AppendLine(text);
    }
}

public void AttachOutput()
{
    var output = "";
    if (this.outputCache.TryRemove(GetKey(), out var sb))
    {
        lock (sb)
        {
            output = sb.ToString();
        }
    }

    /* the rest of the code */
}

static string GetKey()
{
    string key = default;
    AllureLifecycle.Instance.UpdateExecutableItem(tr => { key = tr.uuid; });
    return key;
}

A static instance of that class can then be put in the state facade.


protected override void HandleInAllureContext(OutputAddedEvent eventData) {
AllureReqnrollStateFacade.AddOutput(
eventData.Text + "\n"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is the only place where AddOutput is called, we may reduce the number of concatenations by using StringBuilder's AppendLine instead of appending the new line character here and calling Append.

@@ -38,6 +39,8 @@ static class AllureReqnrollStateFacade

static AllureLifecycle Lifecycle { get => AllureLifecycle.Instance; }

static Dictionary<ExecutableItem, StringBuilder> OutputCache { get; } = new();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a valid concern. Please take a look at my review comment for the alternative.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants