Skip to content

Commit

Permalink
Fix maximum call stack size exceeded in Jasmine (#8175)
Browse files Browse the repository at this point in the history
* Fix maximum call stack size exceeded in Jasmine

* limit behavior for non fibers environments
  • Loading branch information
christian-bromann committed Mar 31, 2022
1 parent 3bf92d1 commit b761483
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 26 deletions.
8 changes: 6 additions & 2 deletions packages/wdio-runner/src/reporter.ts
Expand Up @@ -155,7 +155,9 @@ export default class BaseReporter {
ReporterClass = reporter as Reporters.ReporterClass
options.logFile = options.setLogFile
? options.setLogFile(this._cid, ReporterClass.name)
: this.getLogFile(ReporterClass.name)
: typeof options.logFile === 'string'
? options.logFile
: this.getLogFile(ReporterClass.name)
options.writeStream = this.getWriteStreamObject(ReporterClass.name)
return new ReporterClass(options)
}
Expand All @@ -178,7 +180,9 @@ export default class BaseReporter {
ReporterClass = initialisePlugin(reporter, 'reporter').default as Reporters.ReporterClass
options.logFile = options.setLogFile
? options.setLogFile(this._cid, reporter)
: this.getLogFile(reporter)
: typeof options.logFile === 'string'
? options.logFile
: this.getLogFile(reporter)
options.writeStream = this.getWriteStreamObject(reporter)
return new ReporterClass(options)
}
Expand Down
68 changes: 46 additions & 22 deletions packages/wdio-runner/tests/reporter.test.ts
@@ -1,4 +1,4 @@
import type { Capability } from '@wdio/config'
import type { Options, Capabilities } from '@wdio/types'

import BaseReporter from '../src/reporter'

Expand All @@ -14,7 +14,7 @@ class CustomReporter {
}
}

const capability: Capability = { browserName: 'foo' }
const capability: Capabilities.Capabilities = { browserName: 'foo' }

process.send = jest.fn()

Expand All @@ -26,7 +26,7 @@ describe('BaseReporter', () => {
'dot',
['dot', { foo: 'bar' }]
]
}, '0-0', capability)
} as Options.Testrunner, '0-0', capability)

expect(reporter['_reporters']).toHaveLength(2)
})
Expand All @@ -38,12 +38,30 @@ describe('BaseReporter', () => {
'dot',
['dot', { foo: 'bar' }]
]
}, '0-0', capability)
} as Options.Testrunner, '0-0', capability)

expect(reporter.getLogFile('foobar'))
.toMatch(/(\\|\/)foo(\\|\/)bar(\\|\/)wdio-0-0-foobar-reporter.log/)
})

it('can set custom logFile property', () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [
[CustomReporter, { foo: 'bar', logFile: '/barfoo.log' }],
['dot', { foo: 'bar', logFile: '/foobar.log' }]
],
capabilities: [capability]
} as Options.Testrunner, '0-0', capability)

// @ts-expect-error
expect(reporter['_reporters'][0].options.logFile)
.toBe('/barfoo.log')
// @ts-expect-error
expect(reporter['_reporters'][1].options.logFile)
.toBe('/foobar.log')
})

it('should output log file to custom outputDir', () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
Expand All @@ -52,8 +70,9 @@ describe('BaseReporter', () => {
foo: 'bar',
outputDir: '/foo/bar/baz'
}]
]
}, '0-0', capability)
],
capabilities: [capability]
} as Options.Testrunner, '0-0', capability)
expect(reporter.getLogFile('dot'))
.toMatch(/(\\|\/)foo(\\|\/)bar(\\|\/)baz(\\|\/)wdio-0-0-dot-reporter.log/)
})
Expand All @@ -70,7 +89,7 @@ describe('BaseReporter', () => {
}
}]
]
}, '0-0', capability)
} as Options.Testrunner, '0-0', capability)

expect(reporter.getLogFile('dot'))
.toMatch(/(\\|\/)foo(\\|\/)bar(\\|\/)wdio-results-0-0.xml/)
Expand All @@ -87,7 +106,7 @@ describe('BaseReporter', () => {
outputFileFormat: 'foo'
}]
]
}, '0-0', capability)
} as Options.Testrunner, '0-0', capability)
}).toThrow('outputFileFormat must be a function')
})

Expand All @@ -97,7 +116,7 @@ describe('BaseReporter', () => {
'dot',
['dot', { foo: 'bar' }]
]
}, '0-0', capability)
} as Options.Testrunner, '0-0', capability)

expect(reporter.getLogFile('foobar')).toBe(undefined)
})
Expand All @@ -109,7 +128,7 @@ describe('BaseReporter', () => {
'dot',
['dot', { foo: 'bar' }]
]
}, '0-0', capability)
} as Options.Testrunner, '0-0', capability)

const payload = { foo: [1, 2, 3] }
reporter.emit('runner:start', payload)
Expand All @@ -127,7 +146,7 @@ describe('BaseReporter', () => {
'dot',
['dot', { foo: 'bar' }]
]
}, '0-0', capability)
} as Options.Testrunner, '0-0', capability)

const payload = { foo: [1, 2, 3] }
reporter.emit('test:fail', payload)
Expand All @@ -142,8 +161,9 @@ describe('BaseReporter', () => {
it('should allow to load custom reporters', () => {
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [CustomReporter]
}, '0-0', capability)
reporters: [CustomReporter] as any,
capabilities: [capability]
} as Options.Testrunner, '0-0', capability)
expect(reporter['_reporters']).toHaveLength(1)
// @ts-ignore
expect(reporter['_reporters'][0].isCustom).toBe(true)
Expand All @@ -154,8 +174,9 @@ describe('BaseReporter', () => {
outputDir: '/foo/bar',
reporters: [[CustomReporter, {
outputDir: '/foo/baz/bar'
}]]
}, '0-0', capability)
}]] as any,
capabilities: [capability]
} as Options.Testrunner, '0-0', capability)

expect(reporter.getLogFile('CustomReporter')).toMatch(/(\\|\/)foo(\\|\/)baz(\\|\/)bar(\\|\/)wdio-0-0-CustomReporter-reporter.log/)
})
Expand All @@ -165,8 +186,9 @@ describe('BaseReporter', () => {
try {
new BaseReporter({
outputDir: '/foo/bar',
reporters: [{ foo: 'bar' }]
}, '0-0', capability)
reporters: [{ foo: 'bar' } as any],
capabilities: [capability]
} as Options.Testrunner, '0-0', capability)
} catch (err: any) {
expect(err.message).toBe('Invalid reporters config')
}
Expand All @@ -178,8 +200,9 @@ describe('BaseReporter', () => {
const start = Date.now()
const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [CustomReporter, CustomReporter]
}, '0-0', capability)
reporters: [CustomReporter, CustomReporter] as any,
capabilities: [capability]
} as Options.Testrunner, '0-0', capability)

// @ts-ignore test reporter param
setTimeout(() => (reporter['_reporters'][0].inSync = true), 100)
Expand All @@ -194,10 +217,11 @@ describe('BaseReporter', () => {

const reporter = new BaseReporter({
outputDir: '/foo/bar',
reporters: [CustomReporter],
reporters: [CustomReporter] as any,
reporterSyncInterval: 10,
reporterSyncTimeout: 100
}, '0-0', capability)
reporterSyncTimeout: 100,
capabilities: [capability]
} as Options.Testrunner, '0-0', capability)

// @ts-ignore test reporter param
setTimeout(() => (reporter['_reporters'][0].inSync = true), 112)
Expand Down
10 changes: 10 additions & 0 deletions packages/wdio-utils/src/shim.ts
Expand Up @@ -411,6 +411,16 @@ async function executeAsync(this: any, fn: Function, retries: Retries, args: any
asyncSpec = asyncSpecBefore
}

/**
* if test fail in jasmine (e.g. timeout) the finally statement
* would not be executed in async mode where fibers is not supported
*/
const nodeVersion = parseInt(process.version.slice(1).split('.').shift() || '0', 10)
if (isJasmine && nodeVersion >= 16) {
// @ts-ignore
global.expect = expectSync
}

return await result
} catch (err: any) {
if (retries.limit > retries.attempts) {
Expand Down
5 changes: 5 additions & 0 deletions tests/jasmine/test-timeout.js
Expand Up @@ -2,4 +2,9 @@ describe('Jasmine timeout test', () => {
it('should fail due to custom timeout', () => {
return new Promise((resolve) => setTimeout(resolve, 2000))
}, 1000)

// https://github.com/webdriverio/webdriverio/issues/8126
it('do not run into max callstack errors', () => {
expect(true).toBe(false)
})
})
29 changes: 27 additions & 2 deletions tests/smoke.runner.js
Expand Up @@ -29,8 +29,16 @@ async function runTests (tests) {
const testFilter = process.argv[2]

if (process.env.CI || testFilter) {
// sequential
const testsFiltered = testFilter ? tests.filter(test => test.name === testFilter) : tests

if (testsFiltered.length === 0) {
throw new Error(
`No test was selected! Smoke test "${testFilter}" ` +
`picked but only ${tests.map(test => test.name).join(', ')} available`
)
}

// sequential
for (let test of testsFiltered) {
await test()
}
Expand Down Expand Up @@ -113,15 +121,32 @@ const jasmineReporter = async () => {
* Jasmine timeout test
*/
const jasmineTimeout = async () => {
const logFile = path.join(__dirname, 'jasmineTimeout.spec.log')
const err = await launch(
path.resolve(__dirname, 'helpers', 'config.js'),
{
specs: [path.resolve(__dirname, 'jasmine', 'test-timeout.js')],
reporters: [['spec', { outputDir: __dirname }]],
reporters: [
['spec', {
outputDir: __dirname,
stdout: false,
logFile
}]
],
framework: 'jasmine'
}
).catch(err => err)
assert.strictEqual(err.message, 'Smoke test failed')

const specLogs = (await fs.readFile(logFile)).toString()
assert.ok(
specLogs.includes('Error: Timeout - Async function did not complete within 1000ms (custom timeout)'),
'spec was not failing due to timeout error'
)
assert.ok(
!specLogs.includes('RangeError: Maximum call stack size exceeded'),
'spec was failing due to unexpected "Maximum call stack size exceeded"'
)
}

/**
Expand Down

0 comments on commit b761483

Please sign in to comment.