Skip to content
Draft
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
141 changes: 78 additions & 63 deletions packages/server/lib/cypress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import argsUtils from './util/args'
import { telemetry } from '@packages/telemetry'
import { getCtx, hasCtx } from '@packages/data-context'
import { warning as errorsWarning } from './errors'

import type { CypressError } from '@packages/errors'
import { toNumber } from 'lodash'
const debug = Debug('cypress:server:cypress')

type Mode = 'exit' | 'info' | 'interactive' | 'pkg' | 'record' | 'results' | 'run' | 'smokeTest' | 'version' | 'returnPkg' | 'exitWithCode'
Expand All @@ -42,6 +43,8 @@ const exit = async (code = 0) => {
debug('telemetry shutdown errored with: ', err)
})

debug('process.exit', code)

return process.exit(code)
}

Expand All @@ -66,18 +69,31 @@ const exit0 = () => {
return exit(0)
}

const exitErr = (err: any) => {
function isCypressError (err: unknown): err is CypressError {
return (err as CypressError).isCypressErr
}

async function exitErr (err: unknown, posixExitCodes?: boolean) {
// log errors to the console
// and potentially raygun
// and exit with 1
debug('exiting with err', err)

return require('./errors').logException(err)
.then(() => {
debug('calling exit 1')
await require('./errors').logException(err)

return exit(1)
})
if (isCypressError(err)) {
if (
posixExitCodes && (
err.type === 'CLOUD_CANNOT_PROCEED_IN_PARALLEL' ||
err.type === 'CLOUD_CANNOT_PROCEED_IN_SERIAL'
)) {
return exit(112)
}
}

debug('calling exit 1')

return exit(1)
}

export = {
Expand Down Expand Up @@ -151,7 +167,7 @@ export = {
debug('could not parse CLI arguments: %o', argv)

// note - this is promise-returned call
return exitErr(argumentsError)
return exitErr(argumentsError, options?.posixExitCodes)
}

debug('from argv %o got options %o', argv, options)
Expand Down Expand Up @@ -206,80 +222,79 @@ export = {
})
},

startInMode (mode: Mode, options: any) {
async startInMode (mode: Mode, options: any) {
debug('starting in mode %s with options %o', mode, options)

switch (mode) {
case 'version':
return require('./modes/pkg')(options)
.get('version')
.then((version: any) => {
return console.log(version) // eslint-disable-line no-console
}).then(exit0)
.catch(exitErr)

case 'info':
return require('./modes/info')(options)
.then(exit0)
.catch(exitErr)

case 'smokeTest':
return this.runElectron(mode, options)
.then((pong: any) => {
if (mode === 'interactive') {
return this.runElectron(mode, options)
}

try {
switch (mode) {
case 'version': {
const version = await require('./modes/pkg')(options).get('version')

// eslint-disable-next-line no-console
console.log(version)
break
}
case 'info': {
await require('./modes/info')(options)
break
}
case 'smokeTest': {
const pong = await this.runElectron(mode, options)

if (!this.isCurrentlyRunningElectron()) {
return pong
return exit(pong)
} else if (pong !== options.ping) {
return exit(1)
}

if (pong === options.ping) {
return 0
}
break
}
case 'returnPkg': {
const pkg = await require('./modes/pkg')(options)

// eslint-disable-next-line no-console
console.log(JSON.stringify(pkg))
break
}
case 'exitWithCode': {
return exit(toNumber(options.exitWithCode))
break
}
case 'run': {
const results = await this.runElectron(mode, options)

return 1
}).then(exit)
.catch(exitErr)

case 'returnPkg':
return require('./modes/pkg')(options)
.then((pkg: any) => {
return console.log(JSON.stringify(pkg)) // eslint-disable-line no-console
}).then(exit0)
.catch(exitErr)

case 'exitWithCode':
return require('./modes/exit')(options)
.then(exit)
.catch(exitErr)

case 'run':
// run headlessly and exit
// with num of totalFailed
return this.runElectron(mode, options)
.then((results: any) => {
if (results.runs) {
const isCanceled = results.runs.filter((run) => run.skippedSpec).length

if (isCanceled) {
// eslint-disable-next-line no-console
console.log(require('chalk').magenta('\n Exiting with non-zero exit code because the run was canceled.'))

return 1
return exit(1)
}
}

debug('results.totalFailed, posix?', results.totalFailed, options.posixExitCodes)

if (options.posixExitCodes) {
return results.totalFailed ? 1 : 0
return exit(results.totalFailed ? 1 : 0)
}

return results.totalFailed
})
.then(exit)
.catch(exitErr)

case 'interactive':
return this.runElectron(mode, options)

default:
throw new Error(`Cannot start. Invalid mode: '${mode}'`)
return exit(results.totalFailed ?? 0)
}
default: {
throw new Error(`Cannot start. Invalid mode: '${mode}'`)
}
}
} catch (err) {
return exitErr(err, options.posixExitCodes)
}
debug('end of startInMode, exit 0')

return exit(0)
},
}
8 changes: 0 additions & 8 deletions packages/server/lib/modes/exit.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/server/lib/modes/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ async function listenForProjectEnd (project: ProjectBase, exit: boolean): Promis
res(results)
})
}),
earlyExitTerminator.waitForEarlyExit(project, exit),
earlyExitTerminator.waitForEarlyExit(project),
]).then((results) => {
if (exit === false) {
console.log('not exiting due to options.exit being false')
Expand Down
2 changes: 1 addition & 1 deletion packages/server/lib/util/graceful_crash_handling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class EarlyExitTerminator {
this.terminator = pDefer<BaseReporterResults>()
}

waitForEarlyExit (project: ProjectBase, exit?: boolean) {
waitForEarlyExit (project: ProjectBase) {
debug('waiting for early exit')

project.on('test:before:run', ({
Expand Down
38 changes: 38 additions & 0 deletions packages/server/test/integration/cypress_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,44 @@ describe('lib/cypress', () => {
})
})

it('exits with code 112 for cloud API failures when posix-exit-codes is enabled', function () {
// Mock cloud API to fail with a 500 error
sinon.stub(api, 'createRun').rejects({
statusCode: 500,
message: 'Cloud service unavailable',
})

return cypress.start([
`--run-project=${this.todosPath}`,
'--record',
'--key=test-key',
'--posix-exit-codes',
])
.then(() => {
this.expectExitWith(112)
})
})

it('exits with code 112 for parallel cloud API failures when posix-exit-codes is enabled', function () {
// Mock cloud API to fail with a 500 error
sinon.stub(api, 'createRun').rejects({
statusCode: 500,
message: 'Cloud service unavailable',
})

return cypress.start([
`--run-project=${this.todosPath}`,
'--record',
'--key=test-key',
'--parallel',
'--ci-build-id=test-build-id',
'--posix-exit-codes',
])
.then(() => {
this.expectExitWith(112)
})
})

it('does not add project to the global cache', function () {
return cache.getProjectRoots()
.then((projects) => {
Expand Down
Loading
Loading