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

Prevent mutations on world parameters leaking between test cases #2362

Merged
merged 4 commits into from
Dec 21, 2023
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
Please see [CONTRIBUTING.md](./CONTRIBUTING.md) on how to contribute to Cucumber.

## [Unreleased]
### Fixed
- Prevent mutations on world parameters leaking between test cases ([#2362](https://github.com/cucumber/cucumber-js/pull/2362))

## [10.0.1] - 2023-10-20
### Fixed
Expand Down
32 changes: 32 additions & 0 deletions features/world_parameters.feature
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,38 @@ Feature: World Parameters
When I run cucumber-js with `--world-parameters '{"a":1,"b":2}' --world-parameters '{"a":3}'`
Then scenario "a scenario" step "Given the world parameters are correct" has status "passed"

Scenario: world parameters are immutable in practise

World parameters should be read only, and state should be added to the world instance directly.
It's difficult to enforce this immutability in practise, especially with users able to define custom world
behaviour. But we can at least ensure we only provide a cloned version to each world instance, so no
mutations can leak between test cases.

Given a file named "features/passing_steps.feature" with:
"""
Feature: a feature
Scenario: troublemaker
When a world parameter is mutated
Scenario: a scenario
Given the world parameters are correct
"""
Given a file named "features/step_definitions/my_steps.js" with:
"""
const assert = require('assert')
const {Given, When} = require('@cucumber/cucumber')
Given('the world parameters are correct', function() {
assert.equal(this.parameters.foo, 'bar')
})
When('a world parameter is mutated', function() {
this.parameters.foo = 'baz'
})
"""
When I run cucumber-js with `--world-parameters '{"foo":"bar"}'`
Then scenario "a scenario" step "Given the world parameters are correct" has status "passed"

Scenario: custom world constructor is passed the parameters
Given a file named "features/support/world.js" with:
"""
Expand Down
3 changes: 2 additions & 1 deletion src/configuration/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { JsonObject } from 'type-fest'
import { FormatOptions } from '../formatter'
import { PickleOrder } from '../models/pickle_order'

Expand Down Expand Up @@ -27,5 +28,5 @@ export interface IConfiguration {
retryTagFilter: string
strict: boolean
tags: string
worldParameters: any
worldParameters: JsonObject
}
3 changes: 2 additions & 1 deletion src/runtime/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EventEmitter } from 'node:events'
import * as messages from '@cucumber/messages'
import { IdGenerator } from '@cucumber/messages'
import { JsonObject } from 'type-fest'
import { EventDataCollector } from '../formatter/helpers'
import { ISupportCodeLibrary } from '../support_code_library_builder/types'
import { assembleTestCases } from './assemble_test_cases'
Expand Down Expand Up @@ -29,7 +30,7 @@ export interface IRuntimeOptions {
retry: number
retryTagFilter: string
strict: boolean
worldParameters: any
worldParameters: JsonObject
}

export default class Runtime implements IRuntime {
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/parallel/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { EventEmitter } from 'node:events'
import { pathToFileURL } from 'node:url'
import * as messages from '@cucumber/messages'
import { IdGenerator } from '@cucumber/messages'
import { JsonObject } from 'type-fest'
import supportCodeLibraryBuilder from '../../support_code_library_builder'
import { ISupportCodeLibrary } from '../../support_code_library_builder/types'
import { doesHaveValue } from '../../value_checker'
Expand Down Expand Up @@ -31,7 +32,7 @@ export default class Worker {
private readonly newId: IdGenerator.NewId
private readonly sendMessage: IMessageSender
private supportCodeLibrary: ISupportCodeLibrary
private worldParameters: any
private worldParameters: JsonObject
private runTestRunHooks: RunsTestRunHooks

constructor({
Expand Down
7 changes: 4 additions & 3 deletions src/runtime/test_case_runner.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EventEmitter } from 'node:events'
import * as messages from '@cucumber/messages'
import { getWorstTestStepResult, IdGenerator } from '@cucumber/messages'
import { JsonObject } from 'type-fest'
import {
ISupportCodeLibrary,
ITestCaseHookParameter,
Expand All @@ -27,7 +28,7 @@ export interface INewTestCaseRunnerOptions {
skip: boolean
filterStackTraces: boolean
supportCodeLibrary: ISupportCodeLibrary
worldParameters: any
worldParameters: JsonObject
}

export default class TestCaseRunner {
Expand All @@ -46,7 +47,7 @@ export default class TestCaseRunner {
private readonly supportCodeLibrary: ISupportCodeLibrary
private testStepResults: messages.TestStepResult[]
private world: any
private readonly worldParameters: any
private readonly worldParameters: JsonObject

constructor({
eventBroadcaster,
Expand Down Expand Up @@ -99,7 +100,7 @@ export default class TestCaseRunner {
this.world = new this.supportCodeLibrary.World({
attach: this.attachmentManager.create.bind(this.attachmentManager),
log: this.attachmentManager.log.bind(this.attachmentManager),
parameters: this.worldParameters,
parameters: structuredClone(this.worldParameters),
})
this.testStepResults = []
}
Expand Down