Skip to content

expose promise timeout helper #1566

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

Merged
merged 11 commits into from
Jul 10, 2021
Merged
1 change: 1 addition & 0 deletions dependency-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ ignoreErrors:
- '@typescript-eslint/eslint-plugin' # peer dependency of standard-with-typescript
- '@typescript-eslint/parser' # peer dependency of @typescript-eslint/eslint-plugin
- '@types/*' # type definitions
- bluebird # features/generator_step_definitions.feature
- coffeescript # features/compiler.feature
- eslint-config-prettier # .eslintrc.yml - extends - prettier
- eslint-config-standard-with-typescript # .eslintrc.yml - extends - standard-with-typescript
Expand Down
7 changes: 3 additions & 4 deletions docs/support_files/timeouts.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,13 @@ Given(/^a slow step$/, {timeout: 60 * 1000}, function() {
Disable timeouts by setting it to -1.
If you use this, you need to implement your own timeout protection.
Otherwise the test suite may end prematurely or hang indefinitely.
The helper `wrapPromiseWithTimeout`, which cucumber-js itself uses to enforce timeouts is available if needed.

```javascript
var {Before, Given} = require('@cucumber/cucumber');
var Promise = require('bluebird');
var {Before, Given, wrapPromiseWithTimeout} = require('@cucumber/cucumber');

Given('the operation completes within {n} minutes', {timeout: -1}, function(minutes) {
const milliseconds = (minutes + 1) * 60 * 1000
const message = `operation did not complete within ${minutes} minutes`
return Promise(this.verifyOperationComplete()).timeout(milliseconds, message);
return wrapPromiseWithTimeout(this.verifyOperationComplete(), milliseconds);
});
```
10 changes: 5 additions & 5 deletions features/attachments.feature
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,17 @@ Feature: Attachments
Given a file named "features/support/hooks.js" with:
"""
const {After} = require('@cucumber/cucumber')
const Promise = require('bluebird')

After(function() {
// Do not return the promise so that the attach happens after the hook completes
Promise.delay(100).then(() => {
// Do not use the callback / promise interface so that the attach happens after the hook completes
setTimeout(() => {
this.attach("text")
})
}, 100)
})
"""
When I run cucumber-js
Then the error output contains the text:
Then it fails
And the error output contains the text:
"""
Cannot attach when a step/hook is not running. Ensure your step/hook waits for the attach to finish.
"""
5 changes: 0 additions & 5 deletions features/before_after_all_hook_interfaces.feature
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ Feature: before / after all hook interfaces
Given a file named "features/step_definitions/failing_steps.js" with:
"""
const {<TYPE>} = require('@cucumber/cucumber')
const Promise = require('bluebird')

<TYPE>(function(callback) {
return Promise.resolve()
Expand All @@ -145,7 +144,6 @@ Feature: before / after all hook interfaces
Given a file named "features/support/hooks.js" with:
"""
const {<TYPE>} = require('@cucumber/cucumber')
const Promise = require('bluebird')

<TYPE>(function() {
return Promise.resolve()
Expand All @@ -163,7 +161,6 @@ Feature: before / after all hook interfaces
Given a file named "features/support/hooks.js" with:
"""
const {<TYPE>} = require('@cucumber/cucumber')
const Promise = require('bluebird')

<TYPE>(function() {
return Promise.reject(new Error('my error'))
Expand All @@ -185,7 +182,6 @@ Feature: before / after all hook interfaces
Given a file named "features/support/hooks.js" with:
"""
const {<TYPE>} = require('@cucumber/cucumber')
const Promise = require('bluebird')

<TYPE>(function() {
return Promise.reject()
Expand All @@ -208,7 +204,6 @@ Feature: before / after all hook interfaces
Given a file named "features/support/hooks.js" with:
"""
const {<TYPE>} = require('@cucumber/cucumber')
const Promise = require('bluebird')

<TYPE>(function() {
return new Promise(function() {
Expand Down
3 changes: 0 additions & 3 deletions features/failing_steps.feature
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ Feature: Failing steps
Given a file named "features/step_definitions/failing_steps.js" with:
"""
const {When} = require('@cucumber/cucumber')
const Promise = require('bluebird')

When(/^a failing step$/, function(callback) {
return Promise.resolve()
Expand All @@ -113,7 +112,6 @@ Feature: Failing steps
Given a file named "features/step_definitions/failing_steps.js" with:
"""
const {When} = require('@cucumber/cucumber')
const Promise = require('bluebird')

When(/^a failing step$/, function() {
return new Promise(function() {
Expand All @@ -134,7 +132,6 @@ Feature: Failing steps
Given a file named "features/step_definitions/failing_steps.js" with:
"""
const {When} = require('@cucumber/cucumber')
const Promise = require('bluebird')

When(/^a failing step$/, function() {
return Promise.reject(new Error('my error'))
Expand Down
5 changes: 0 additions & 5 deletions features/hook_interface.feature
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ Feature: After hook interface
Given a file named "features/step_definitions/failing_steps.js" with:
"""
const {<TYPE>} = require('@cucumber/cucumber')
const Promise = require('bluebird')

<TYPE>(function(scenario, callback) {
return Promise.resolve()
Expand All @@ -153,7 +152,6 @@ Feature: After hook interface
Given a file named "features/support/hooks.js" with:
"""
const {<TYPE>} = require('@cucumber/cucumber')
const Promise = require('bluebird')

<TYPE>(function() {
return Promise.resolve()
Expand All @@ -171,7 +169,6 @@ Feature: After hook interface
Given a file named "features/support/hooks.js" with:
"""
const {<TYPE>} = require('@cucumber/cucumber')
const Promise = require('bluebird')

<TYPE>(function(){
return Promise.reject(new Error('my error'))
Expand All @@ -193,7 +190,6 @@ Feature: After hook interface
Given a file named "features/support/hooks.js" with:
"""
const {<TYPE>} = require('@cucumber/cucumber')
const Promise = require('bluebird')

<TYPE>(function() {
return Promise.reject()
Expand All @@ -216,7 +212,6 @@ Feature: After hook interface
Given a file named "features/support/hooks.js" with:
"""
const {<TYPE>} = require('@cucumber/cucumber')
const Promise = require('bluebird')

<TYPE>(function(){
return new Promise(function() {
Expand Down
2 changes: 0 additions & 2 deletions features/parallel.feature
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ Feature: Running scenarios in parallel
Given a file named "features/step_definitions/cucumber_steps.js" with:
"""
const {Given} = require('@cucumber/cucumber')
const Promise = require('bluebird')

Given(/^a slow step$/, function(callback) {
setTimeout(callback, 1000)
Expand All @@ -27,7 +26,6 @@ Feature: Running scenarios in parallel
Given a file named "features/step_definitions/cucumber_steps.js" with:
"""
const {BeforeAll, Given} = require('@cucumber/cucumber')
const Promise = require('bluebird')

Given(/^a slow step$/, function(callback) {
setTimeout(callback, 1000)
Expand Down
1 change: 0 additions & 1 deletion features/parameter_types.feature
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ Feature: Parameter types
Given a file named "features/step_definitions/particular_steps.js" with:
"""
const {defineParameterType} = require('@cucumber/cucumber')
const Promise = require('bluebird')

defineParameterType({
regexp: /particular/,
Expand Down
1 change: 0 additions & 1 deletion features/passing_steps.feature
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ Feature: Passing steps
Given a file named "features/step_definitions/passing_steps.js" with:
"""
const {Given} = require('@cucumber/cucumber')
const Promise = require('bluebird')

Given(/^a passing step$/, function() {
return Promise.resolve()
Expand Down
13 changes: 9 additions & 4 deletions features/step_definition_timeouts.feature
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ Feature: Step definition timeouts
Given a file named "features/step_definitions/cucumber_steps.js" with:
"""
const {Given, setDefaultTimeout} = require('@cucumber/cucumber')
const Promise = require('bluebird')

setDefaultTimeout(500)

Expand All @@ -21,15 +20,21 @@ Feature: Step definition timeouts
})

Given(/^a promise step runs slowly$/, function() {
return Promise.resolve().delay(1000)
return new Promise(resolve => {
setTimeout(resolve, 1000)
})
})

Given(/^a promise step runs slowly with an increased timeout$/, {timeout: 1500}, function() {
return Promise.resolve().delay(1000)
return new Promise(resolve => {
setTimeout(resolve, 1000)
})
})

Given(/^a promise step with a disabled timeout$/, {timeout: -1}, function() {
return Promise.resolve().delay(1000)
return new Promise(resolve => {
setTimeout(resolve, 1000)
})
})
"""

Expand Down
42 changes: 22 additions & 20 deletions src/cli/configuration_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import ArgvParser, {
import fs from 'mz/fs'
import path from 'path'
import OptionSplitter from './option_splitter'
import bluebird from 'bluebird'
import glob from 'glob'
import { promisify } from 'util'
import { IPickleFilterOptions } from '../pickle_filter'
Expand Down Expand Up @@ -119,21 +118,22 @@ export default class ConfigurationBuilder {
unexpandedPaths: string[],
defaultExtension: string
): Promise<string[]> {
const expandedPaths = await bluebird.map(
unexpandedPaths,
async (unexpandedPath) => {
const expandedPaths = await Promise.all(
unexpandedPaths.map(async (unexpandedPath) => {
const matches = await promisify(glob)(unexpandedPath, {
absolute: true,
cwd: this.cwd,
})
const expanded = await bluebird.map(matches, async (match) => {
if (path.extname(match) === '') {
return await promisify(glob)(`${match}/**/*${defaultExtension}`)
}
return [match]
})
const expanded = await Promise.all(
matches.map(async (match) => {
if (path.extname(match) === '') {
return await promisify(glob)(`${match}/**/*${defaultExtension}`)
}
return [match]
})
)
return expanded.flat()
}
})
)
return expandedPaths.flat().map((x) => path.normalize(x))
}
Expand Down Expand Up @@ -206,15 +206,17 @@ export default class ConfigurationBuilder {

async getUnexpandedFeaturePaths(): Promise<string[]> {
if (this.args.length > 0) {
const nestedFeaturePaths = await bluebird.map(this.args, async (arg) => {
const filename = path.basename(arg)
if (filename[0] === '@') {
const filePath = path.join(this.cwd, arg)
const content = await fs.readFile(filePath, 'utf8')
return content.split('\n').map((x) => x.trim())
}
return [arg]
})
const nestedFeaturePaths = await Promise.all(
this.args.map(async (arg) => {
const filename = path.basename(arg)
if (filename[0] === '@') {
const filePath = path.join(this.cwd, arg)
const content = await fs.readFile(filePath, 'utf8')
return content.split('\n').map((x) => x.trim())
}
return [arg]
})
)
const featurePaths = nestedFeaturePaths.flat()
if (featurePaths.length > 0) {
return featurePaths.filter((x) => x !== '')
Expand Down
17 changes: 7 additions & 10 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ import FormatterBuilder from '../formatter/builder'
import fs from 'mz/fs'
import path from 'path'
import PickleFilter from '../pickle_filter'
import bluebird from 'bluebird'
import ParallelRuntimeCoordinator from '../runtime/parallel/coordinator'
import Runtime from '../runtime'
import supportCodeLibraryBuilder from '../support_code_library_builder'
import { IdGenerator } from '@cucumber/messages'
import { IFormatterStream } from '../formatter'
import Formatter, { IFormatterStream } from '../formatter'
import { WriteStream as TtyWriteStream } from 'tty'
import { doesNotHaveValue } from '../value_checker'
import { GherkinStreams } from '@cucumber/gherkin-streams'
import { ISupportCodeLibrary } from '../support_code_library_builder/types'
import { IParsedArgvFormatOptions } from './argv_parser'
import HttpStream from '../formatter/http_stream'
import { promisify } from 'util'
import { Writable } from 'stream'

const { incrementing, uuid } = IdGenerator
Expand Down Expand Up @@ -88,9 +88,8 @@ export default class Cli {
formats,
supportCodeLibrary,
}: IInitializeFormattersRequest): Promise<() => Promise<void>> {
const formatters = await bluebird.map(
formats,
async ({ type, outputTo }) => {
const formatters: Formatter[] = await Promise.all(
formats.map(async ({ type, outputTo }) => {
let stream: IFormatterStream = this.stdout
if (outputTo !== '') {
if (outputTo.match(/^https?:\/\//) !== null) {
Expand Down Expand Up @@ -129,7 +128,7 @@ export default class Cli {
cleanup:
stream === this.stdout
? async () => await Promise.resolve()
: bluebird.promisify(stream.end.bind(stream)),
: promisify<any>(stream.end.bind(stream)),
supportCodeLibrary,
}
if (doesNotHaveValue(formatOptions.colorsEnabled)) {
Expand All @@ -145,12 +144,10 @@ export default class Cli {
type = 'progress'
}
return FormatterBuilder.build(type, typeOptions)
}
})
)
return async function () {
await bluebird.each(formatters, async (formatter) => {
await formatter.finished()
})
await Promise.all(formatters.map(async (f) => await f.finished()))
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/formatter/progress_bar_formatter_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import ProgressBarFormatter from './progress_bar_formatter'
import { doesHaveValue, doesNotHaveValue } from '../value_checker'
import { PassThrough } from 'stream'
import ProgressBar from 'progress'
import bluebird from 'bluebird'
import { promisify } from 'util'

interface ITestProgressBarFormatterOptions {
runtimeOptions?: Partial<IRuntimeOptions>
Expand Down Expand Up @@ -65,7 +65,7 @@ async function testProgressBarFormatter({
log: logFn,
parsedArgvOptions: {},
stream: passThrough,
cleanup: bluebird.promisify(passThrough.end.bind(passThrough)),
cleanup: promisify(passThrough.end.bind(passThrough)),
supportCodeLibrary,
}) as ProgressBarFormatter
let mocked = false
Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,12 @@ export {
IWorld,
IWorldOptions,
} from './support_code_library_builder/world'

export {
ITestCaseHookParameter,
ITestStepHookParameter,
} from './support_code_library_builder/types'
export const Status = messages.TestStepResultStatus

// Time helpers
export { wrapPromiseWithTimeout } from './time'
3 changes: 1 addition & 2 deletions src/models/step_definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import Definition, {
} from './definition'
import { parseStepArgument } from '../step_arguments'
import { Expression } from '@cucumber/cucumber-expressions'
import bluebird from 'bluebird'
import { doesHaveValue } from '../value_checker'

export default class StepDefinition extends Definition implements IDefinition {
Expand All @@ -24,7 +23,7 @@ export default class StepDefinition extends Definition implements IDefinition {
step,
world,
}: IGetInvocationDataRequest): Promise<IGetInvocationDataResponse> {
const parameters = await bluebird.all(
const parameters = await Promise.all(
this.expression.match(step.text).map((arg) => arg.getValue(world))
)
if (doesHaveValue(step.argument)) {
Expand Down
Loading