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

New Feature: shared context in beforeAll and afterAll hooks #1770

Merged

Conversation

BlueMona
Copy link

@BlueMona BlueMona commented Aug 17, 2021

Description

This is a draft failing scenario to try and describe what @charlierudolph and
@davidjgoss are describing in #1393

I'd like to work on a fix but thought it would be best to get feedback on the scenario first.

Motivation & context

Fixes #1393

Type of change

  • New feature (non-breaking change which adds new behaviour)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

This is a draft failing scenario to try and describe what @charlierudolph and
@davidjgoss are describing in cucumber#1393

Feedback welcome.

Co-authored-by: Matt Wynne <matt@cucumber.io>
@mattwynne mattwynne added ✅ accepted The core team has agreed that it is a good idea to fix this ⚡ enhancement Request for new functionality beginners mob candidate labels Aug 17, 2021
@mattwynne
Copy link
Member

The scenario we have so far doesn't address the issue around immutability. I suggest we add another scenario showing that even if the first scenario mutates the value that was set in the BeforeAll hook, the second scenario gets the original value again. (i.e. we can't use it to leak state between scenarios; each new World instance gets a fresh copy of the testRunContext)

@davidjgoss
Copy link
Contributor

@BlueMona thanks for getting this going, and great idea to start with the scenario. I think my only suggestion would be for the stuff that we set in BeforeAll to be set on the world under its own testRunContext property rather than mixed in, so like:

Given('first step', function() {
  expect(this.testRunContext.myVar.foo).to.eql(1) 
})

Then it's like a sibling of parameters and attach that you can access from your world (including a custom one).

@mattwynne seems a good direction re immutability.

mattwynne and others added 7 commits February 4, 2022 09:28
Co-authored-by: Matt Wynne <matt@mattwynne.net>
Co-authored-by: Matt Wynne <matt@mattwynne.net>
Co-authored-by: Matt Wynne <matt@mattwynne.net>
Co-authored-by: Matt Wynne <matt@mattwynne.net>
Co-authored-by: Matt Wynne <matt@mattwynne.net>
@coveralls
Copy link

coveralls commented Feb 24, 2022

Coverage Status

coverage: 98.381%. remained the same
when pulling 4c269b1 on BlueMona:1393-add-world-object-reference-in-beforeall
into 96a65ca on cucumber:main.

@BlueMona
Copy link
Author

The scenario is now passing, but there are a few questions / things left to do.

  • is object an ok type for testRunContext?
  • testRunContext should likely be a property of world, instead of being merged into the root of the object, which would help avoid potential conflicts
  • how do we implement this for the worker and how is it tested?
  • do we need a unit test for the testCaseRunner?
  • what about immutability? Should we add a scenario testing for it?

@davidjgoss
Copy link
Contributor

Thanks for the update @BlueMona, this is looking good. To your points:

is object an ok type for testRunContext?

It's kinda the same need as worldParameters, which we type as any. Technically it should be a JSON-serialisable object so any is a bit broad, but there's no nice way to express that in TypeScript at the moment, so I'd say any is good for now.

testRunContext should likely be a property of world, instead of being merged into the root of the object, which would help avoid potential conflicts

I definitely agree, and I'd even go as far as giving each World instance a clone of the original testRunContext so that if users do modify it, the changes won't bleed into other scenarios running.

how do we implement this for the worker and how is it tested?

Good question, I'll get back to you! We run the test run hooks once for each worker which makes things...interesting. Testing of parallel stuff is generally in the feature tests.

what about immutability? Should we add a scenario testing for it?

Yep would be good to test this. Hopefully the cloning idea above would have the desired effect of (enough) immutability. Marking it readonly in TypeScript would be an easy win too.

@mattwynne
Copy link
Member

@davidjgoss thanks for the quick feedback!

I'm wondering if we could achieve the immutability by copying the testRunContext when we create the merged this object in invokeStep.

i.e. something like

const world = Object.assign(this.world, { testRunContext: { ...this.testRunContext }})

WDYT?

@mattwynne mattwynne mentioned this pull request Feb 25, 2022
7 tasks
@davidjgoss
Copy link
Contributor

@mattwynne yeah that's basically what I was suggesting, although we'd want a deep clone since doing a spread just gives you a shallow clone.

@mattwynne
Copy link
Member

@BlueMona I've extracted a function to run the BeforeAll hooks in #1933. We'll want to merge that into this PR before proceeding.

One slightly tricky part of that merge will be sharing the thisArg / testRunContext with the new function. I suggest adding it as a new argument to the function, but you could also consider making it be the return value, I suppose.

@kamelbou
Copy link

kamelbou commented Apr 7, 2022

Hello,

Is it plan to merge this great feature soon ? :)

Thank you

Copy link
Contributor

@davidjgoss davidjgoss left a comment

Choose a reason for hiding this comment

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

Looking fantastic, thanks for taking this on Mona. And sorry for the delayed review.

One thing I think we're missing is the correct typing of the BeforeAll and AfterAll functions so people can access this.testRunContext while using TypeScript without a compilation error.

If you go to test-d/hooks.ts and add:

// should allow test run context via this arg in test run hooks
BeforeAll(async function () {
  this.testRunContext.foo = 'bar'
})
AfterAll(async function () {
  this.testRunContext
})

Then that will currently fail if you run npm run types-test. I think the place to fix it is in src/support_code_library_builder/types.ts. We have explicit types for TestCaseHookFunction etc there and so it would be good to follow that pattern - although we don't need the complexity with the generics for world there, so it should be easier, something like:

export type TestRunHookFunction = (
  this: { testRunContext: TestRunContext }
) => any | Promise<any>

We'll also need to update some docs:

I'm very happy to help out with any of this as required, just let me know. Thanks again!

this.attach = attach
this.log = log
this.parameters = parameters
this.testRunContext = cloneDeep(testRunContext)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is part of the default World class. When users write a custom world they typically extend this but not always. So I think it would be better to do the cloneDeep outside of the world, probably in test_case_runner.ts::resetTestProgressData, and then we know the world will always receive a clone.

@@ -37,6 +37,7 @@ export default class Worker {
private supportCodeLibrary: ISupportCodeLibrary
private worldParameters: any
private runTestRunHooks: RunsTestRunHooks
private testRunContext = {}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is fine to initialize here but we should type it as TestRunContext like in the non-parallel runtime.

@@ -139,6 +141,7 @@ export default class Worker {
skip,
supportCodeLibrary: this.supportCodeLibrary,
worldParameters: this.worldParameters,
testRunContext: {}, // TODO: decide what this needs to be
Copy link
Contributor

@davidjgoss davidjgoss Aug 19, 2022

Choose a reason for hiding this comment

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

I think the value here should be this.testRunContext.

(At the moment, for parallel each worker runs all the BeforeAll hooks in isolation. In the future, we'll allow doing them at the coordinator level as well and that will make this more complex - but we don't have to worry about that yet!)

When I run cucumber-js
Then it passes

Rule: testRunContext can't leak between scenarios
Copy link
Contributor

Choose a reason for hiding this comment

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

We should test the parallel runtime in another scenario here. We'd lose the guaranteed order so it gets less simple, but we could do something like 5 scenarios of

Scenario:
  Given the value of foo is unchanged
  When I change foo to {int}

And run with --parallel 2.

So in other words every scenario checks it has an unmodified context, and then tries to modify its current one. I think that would be enough to give us confidence.

const {AfterAll, BeforeAll, Given} = require('@cucumber/cucumber')
const {expect} = require('chai')

BeforeAll(function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider adding a second BeforeAll like:

BeforeAll(function() {
  this.testRunContext.otherVar = {bar: 2}
})

And then checking it's all there in the AfterAll hook, to prove that we can incrementally build up the context across multiple BeforeAll hooks. I've no doubt this will work without any changes, but just to document it.

@ksathyanm
Copy link

Just curious if we have any updates or plan for this PR?

@mattwynne
Copy link
Member

@BlueMona ping me if you want to pair on finishing this off. I'm here to help if you want it.

@BlueMona
Copy link
Author

BlueMona commented Sep 2, 2023 via email

@mattwynne
Copy link
Member

Let’s do it this week then!

@BlueMona great!

Can you use https://calendly.com/mattwynne to book a slot?

@LANDG-MartinP
Copy link

any news on this PR?

@davidjgoss
Copy link
Contributor

Just noting here the idea raised on the issue of, rather than adding a new testRunContext concept, just passing in the world parameters and allowing a BeforeAll to return a fresh world parameters if it wants to. So like:

BeforeAll(async function() {
  this.token = await authenticate(this.parameters.url, this.parameters.clientId, this.parameters.clientSecret)
  return {
    ...this.parameters,
    token
  }
})

I think this would solve all the use cases nicely and this PR could be adapted to it without much work - @BlueMona @mattwynne I'm happy to pick this up if you're time-constrained, since I'm working in this area already.

Also somewhat related is #2356 which I'm looking at today.

@BlueMona
Copy link
Author

BlueMona commented Dec 21, 2023 via email

Copy link
Contributor

@davidjgoss davidjgoss left a comment

Choose a reason for hiding this comment

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

After some conflict untangling, this fitted together nicely with some other recent changes.

Many thanks @BlueMona for doing most of this!

@davidjgoss davidjgoss merged commit 739fca6 into cucumber:main Dec 21, 2023
6 checks passed
@aslakhellesoy
Copy link
Contributor

Hi @BlueMona,

Thanks for your making your first contribution to Cucumber, and welcome to the Cucumber committers team! You can now push directly to this repo and all other repos under the cucumber organization! 🍾

In return for this generous offer we hope you will:

  • ✅ Continue to use branches and pull requests. When someone on the core team approves a pull request (yours or someone else's), you're welcome to merge it yourself.
  • 💚 Commit to setting a good example by following and upholding our code of conduct in your interactions with other collaborators and users.
  • 💬 Join the community Slack channel to meet the rest of the team and make yourself at home.
  • ℹ️ Don't feel obliged to help, just do what you can if you have the time and the energy.
  • 🙋 Ask if you need anything. We're looking for feedback about how to make the project more welcoming, so please tell us!

On behalf of the Cucumber core team,
Aslak Hellesøy
Creator of Cucumber

@iamkenos
Copy link

iamkenos commented Dec 21, 2023

This one's a great holiday gift. Thanks team 🖤. Happy holidays!

@mattwynne
Copy link
Member

mattwynne commented Dec 22, 2023

🎉 nice one @BlueMona! (and @davidjgoss 😄 )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✅ accepted The core team has agreed that it is a good idea to fix this ⚡ enhancement Request for new functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature Request: Give BeforeAll a reference to the Cucumber World object
10 participants