123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- 'use strict';
- var _ = require('lodash');
- var os = require('os');
- var cp = require('child_process');
- var path = require('path');
- var Promise = require('bluebird');
- var debug = require('debug')('cypress:cli');
- var debugElectron = require('debug')('cypress:electron');
- var util = require('../util');
- var state = require('../tasks/state');
- var xvfb = require('./xvfb');
- var verify = require('../tasks/verify');
- var errors = require('../errors');
- var isXlibOrLibudevRe = /^(?:Xlib|libudev)/;
- var isHighSierraWarningRe = /\*\*\* WARNING/;
- var isRenderWorkerRe = /\.RenderWorker-/;
- var GARBAGE_WARNINGS = [isXlibOrLibudevRe, isHighSierraWarningRe, isRenderWorkerRe];
- var isGarbageLineWarning = function isGarbageLineWarning(str) {
- return _.some(GARBAGE_WARNINGS, function (re) {
- return re.test(str);
- });
- };
- function isPlatform(platform) {
- return os.platform() === platform;
- }
- function needsStderrPiped(needsXvfb) {
- return _.some([isPlatform('darwin'), needsXvfb && isPlatform('linux'), util.isPossibleLinuxWithIncorrectDisplay()]);
- }
- function needsEverythingPipedDirectly() {
- return isPlatform('win32');
- }
- function getStdio(needsXvfb) {
- if (needsEverythingPipedDirectly()) {
- return 'pipe';
- }
- // https://github.com/cypress-io/cypress/issues/921
- // https://github.com/cypress-io/cypress/issues/1143
- // https://github.com/cypress-io/cypress/issues/1745
- if (needsStderrPiped(needsXvfb)) {
- // returning pipe here so we can massage stderr
- // and remove garbage from Xlib and libuv
- // due to starting the Xvfb process on linux
- return ['inherit', 'inherit', 'pipe'];
- }
- return 'inherit';
- }
- module.exports = {
- isGarbageLineWarning: isGarbageLineWarning,
- start: function start(args) {
- var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
- var needsXvfb = xvfb.isNeeded();
- var executable = state.getPathToExecutable(state.getBinaryDir());
- if (util.getEnv('CYPRESS_RUN_BINARY')) {
- executable = path.resolve(util.getEnv('CYPRESS_RUN_BINARY'));
- }
- debug('needs to start own Xvfb?', needsXvfb);
- // always push cwd into the args
- // which additionally acts as a signal to the
- // binary that it was invoked through the NPM module
- args = [].concat(args, '--cwd', process.cwd());
- _.defaults(options, {
- dev: false,
- env: process.env,
- detached: false,
- stdio: getStdio(needsXvfb)
- });
- var spawn = function spawn() {
- var overrides = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
- return new Promise(function (resolve, reject) {
- _.defaults(overrides, {
- onStderrData: false,
- electronLogging: false
- });
- if (options.dev) {
- // if we're in dev then reset
- // the launch cmd to be 'npm run dev'
- executable = 'node';
- args.unshift(path.resolve(__dirname, '..', '..', '..', 'scripts', 'start.js'));
- }
- var onStderrData = overrides.onStderrData,
- electronLogging = overrides.electronLogging;
- var envOverrides = util.getEnvOverrides();
- var electronArgs = _.clone(args);
- var node11WindowsFix = isPlatform('win32');
- if (verify.needsSandbox()) {
- electronArgs.push('--no-sandbox');
- }
- // strip dev out of child process options
- var stdioOptions = _.pick(options, 'env', 'detached', 'stdio');
- // figure out if we're going to be force enabling or disabling colors.
- // also figure out whether we should force stdout and stderr into thinking
- // it is a tty as opposed to a pipe.
- stdioOptions.env = _.extend({}, stdioOptions.env, envOverrides);
- if (node11WindowsFix) {
- stdioOptions = _.extend({}, stdioOptions, { windowsHide: false });
- }
- if (electronLogging) {
- stdioOptions.env.ELECTRON_ENABLE_LOGGING = true;
- }
- if (util.isPossibleLinuxWithIncorrectDisplay()) {
- // make sure we use the latest DISPLAY variable if any
- debug('passing DISPLAY', process.env.DISPLAY);
- stdioOptions.env.DISPLAY = process.env.DISPLAY;
- }
- debug('spawning Cypress with executable: %s', executable);
- debug('spawn args %o %o', electronArgs, _.omit(stdioOptions, 'env'));
- var child = cp.spawn(executable, electronArgs, stdioOptions);
- function resolveOn(event) {
- return function (code, signal) {
- debug('child event fired %o', { event: event, code: code, signal: signal });
- if (code === null) {
- var errorObject = errors.errors.childProcessKilled(event, signal);
- return errors.getError(errorObject).then(reject);
- }
- resolve(code);
- };
- }
- child.on('close', resolveOn('close'));
- child.on('exit', resolveOn('exit'));
- child.on('error', reject);
- // if stdio options is set to 'pipe', then
- // we should set up pipes:
- // process STDIN (read stream) => child STDIN (writeable)
- // child STDOUT => process STDOUT
- // child STDERR => process STDERR with additional filtering
- if (child.stdin) {
- debug('piping process STDIN into child STDIN');
- process.stdin.pipe(child.stdin);
- }
- if (child.stdout) {
- debug('piping child STDOUT to process STDOUT');
- child.stdout.pipe(process.stdout);
- }
- // if this is defined then we are manually piping for linux
- // to filter out the garbage
- if (child.stderr) {
- debug('piping child STDERR to process STDERR');
- child.stderr.on('data', function (data) {
- var str = data.toString();
- // bail if this is warning line garbage
- if (isGarbageLineWarning(str)) {
- return;
- }
- // if we have a callback and this explictly returns
- // false then bail
- if (onStderrData && onStderrData(str) === false) {
- return;
- }
- // else pass it along!
- process.stderr.write(data);
- });
- }
- // https://github.com/cypress-io/cypress/issues/1841
- // https://github.com/cypress-io/cypress/issues/5241
- // In some versions of node, it will throw on windows
- // when you close the parent process after piping
- // into the child process. unpiping does not seem
- // to have any effect. so we're just catching the
- // error here and not doing anything.
- process.stdin.on('error', function (err) {
- if (['EPIPE', 'ENOTCONN'].includes(err.code)) {
- return;
- }
- throw err;
- });
- if (stdioOptions.detached) {
- child.unref();
- }
- });
- };
- var spawnInXvfb = function spawnInXvfb() {
- return xvfb.start().then(userFriendlySpawn).finally(xvfb.stop);
- };
- var userFriendlySpawn = function userFriendlySpawn(linuxWithDisplayEnv) {
- debug('spawning, should retry on display problem?', Boolean(linuxWithDisplayEnv));
- var brokenGtkDisplay = void 0;
- var overrides = {};
- if (linuxWithDisplayEnv) {
- _.extend(overrides, {
- electronLogging: true,
- onStderrData: function onStderrData(str) {
- // if we receive a broken pipe anywhere
- // then we know that's why cypress exited early
- if (util.isBrokenGtkDisplay(str)) {
- brokenGtkDisplay = true;
- }
- // we should attempt to always slurp up
- // the stderr logs unless we've explicitly
- // enabled the electron debug logging
- if (!debugElectron.enabled) {
- return false;
- }
- }
- });
- }
- return spawn(overrides).then(function (code) {
- if (code !== 0 && brokenGtkDisplay) {
- util.logBrokenGtkDisplayWarning();
- return spawnInXvfb();
- }
- return code;
- })
- // we can format and handle an error message from the code above
- // prevent wrapping error again by using "known: undefined" filter
- .catch({ known: undefined }, errors.throwFormErrorText(errors.errors.unexpected));
- };
- if (needsXvfb) {
- return spawnInXvfb();
- }
- // if we are on linux and there's already a DISPLAY
- // set, then we may need to rerun cypress after
- // spawning our own Xvfb server
- var linuxWithDisplayEnv = util.isPossibleLinuxWithIncorrectDisplay();
- return userFriendlySpawn(linuxWithDisplayEnv);
- }
- };
|