Skip to content

Commit

Permalink
Prevent mutations on world parameters leaking between test cases (#2362)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidjgoss authored Dec 21, 2023
1 parent 052ac1a commit 2a95e47
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 6 deletions.
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

0 comments on commit 2a95e47

Please sign in to comment.