Skip to content

Commit

Permalink
Handle EPIPE on stdout gracefully
Browse files Browse the repository at this point in the history
  • Loading branch information
arbscht committed Apr 12, 2019
1 parent 8a671f1 commit a63949a
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 0 deletions.
74 changes: 74 additions & 0 deletions __tests__/pipe.js
@@ -0,0 +1,74 @@
import execa from 'execa';
import getStream from 'get-stream';
import {sh} from 'puka';
import makeTemp from './_temp.js';
import * as fs from '../src/util/fs.js';

const path = require('path');
const stream = require('stream');
const net = require('net');

function runYarnStreaming(args: Array<string> = [], options: Object = {}): Promise {
if (!options['env']) {
options['env'] = {...process.env};
options['extendEnv'] = false;
}
options['env']['FORCE_COLOR'] = 0;

return execa.shell(sh`${path.resolve(__dirname, '../bin/yarn')} ${args}`, options);
}

test('terminate console stream quietly on EPIPE', async () => {
const cwd = await makeTemp();
const packageJsonPath = path.join(cwd, 'package.json');
const initialManifestFile = JSON.stringify({name: 'test', license: 'ISC', version: '1.0.0'});

await fs.writeFile(packageJsonPath, initialManifestFile);

const {stdout, stderr} = runYarnStreaming(['versions'], {cwd: cwd});

stdout.destroy();

let stderrOutput;
await getStream(stderr).then(e => {
stderrOutput = e;
});

expect(stderrOutput.toString()).not.toMatch(/EPIPE/);
});

test('terminate console stream preserving zero exit code on EPIPE', async () => {
const cwd = await makeTemp();
const packageJsonPath = path.join(cwd, 'package.json');
const initialManifestFile = JSON.stringify({name: 'test', license: 'ISC', version: '1.0.0'});

await fs.writeFile(packageJsonPath, initialManifestFile);

const proc = runYarnStreaming(['versions'], {cwd: cwd});

const {stdout, stderr} = proc;

stdout.destroy();

proc.on('exit', function (code, signal) {
expect(code).toEqual(0);
})
});

test('terminate console stream preserving non-zero exit code on EPIPE', async () => {
const cwd = await makeTemp();
const packageJsonPath = path.join(cwd, 'package.json');
const initialManifestFile = JSON.stringify({name: 'test', license: 'ISC', version: '1.0.0'});

await fs.writeFile(packageJsonPath, initialManifestFile);

const proc = runYarnStreaming(['add'], {cwd: cwd});

const {stdout, stderr} = proc;

stdout.destroy();

proc.on('exit', function (code, signal) {
expect(code).toEqual(1);
})
});
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -83,6 +83,7 @@
"execa": "^0.11.0",
"fancy-log": "^1.3.2",
"flow-bin": "^0.66.0",
"get-stream": "^5.0.0",
"git-release-notes": "^3.0.0",
"gulp": "^4.0.0",
"gulp-babel": "^7.0.0",
Expand Down
8 changes: 8 additions & 0 deletions src/cli/index.js
Expand Up @@ -26,6 +26,14 @@ import handleSignals from '../util/signal-handler.js';
import {boolify, boolifyWithDefault} from '../util/conversion.js';
import {ProcessTermError} from '../errors';

process.stdout.prependListener('error', err => {
// swallow err only if downstream consumer process closed pipe early
if (err.code === 'EPIPE' || err.code === 'ERR_STREAM_DESTROYED') {
return;
}
throw err;
});

function findProjectRoot(base: string): string {
let prev = null;
let dir = base;
Expand Down
7 changes: 7 additions & 0 deletions yarn.lock
Expand Up @@ -3281,6 +3281,13 @@ get-stream@^4.0.0:
dependencies:
pump "^3.0.0"

get-stream@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.0.0.tgz#7d1f137576da207fa83d843f44824a70ef999b5f"
integrity sha512-frc9F97ehylll1YI31eJRw5M21M86GCItj5U3S3hOEUa6JG6LtcvRKYDAtV/9E5lVasY8QJSPVsrMg/mbCSP5w==
dependencies:
pump "^3.0.0"

get-value@^2.0.3, get-value@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
Expand Down

0 comments on commit a63949a

Please sign in to comment.