FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Handle parser errors without crashing by PNHD · Pull Request #334 · extrabacon/python-shell · GitHub

Repository navigation

Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .md  (1) .ts  (2) All 2 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
2 changes: 1 addition & 1 deletion README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ Fires when the process has been terminated, with an error or not.

#### event: `pythonError`

Fires when the process terminates with a non-zero exit code.
Fires when the process terminates with a non-zero exit code or an output parser throws before normal completion. Parser exceptions are also passed to `.end()` and reject `PythonShell.run()`.

#### event: `error`

Expand Down
27 changes: 25 additions & 2 deletions index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export class PythonShell extends EventEmitter {

let self = this;
let errorData = '';
let parserError: Error;
EventEmitter.call(this);

options = <Options>extend({}, PythonShell.defaultOptions, options);
Expand Down Expand Up @@ -205,7 +206,15 @@ export class PythonShell extends EventEmitter {
// note that setting the encoding turns the chunk into a string
stdoutSplitter.setEncoding(options.encoding || 'utf8');
this.stdout.pipe(stdoutSplitter).on('data', (chunk: string) => {
this.emit('message', self.parser(chunk));
if (parserError) return;
let parsedChunk: any;
try {
parsedChunk = self.parser(chunk);
} catch (err) {
parserError = err instanceof Error ? err : new Error(String(err));
return;
}
this.emit('message', parsedChunk);
});
}

Expand All @@ -215,7 +224,15 @@ export class PythonShell extends EventEmitter {
// note that setting the encoding turns the chunk into a string
stderrSplitter.setEncoding(options.encoding || 'utf8');
this.stderr.pipe(stderrSplitter).on('data', (chunk: string) => {
this.emit('stderr', self.stderrParser(chunk));
if (parserError) return;
let parsedChunk: any;
try {
parsedChunk = self.stderrParser(chunk);
} catch (err) {
parserError = err instanceof Error ? err : new Error(String(err));
return;
}
this.emit('stderr', parsedChunk);
});
}

Expand Down Expand Up @@ -266,6 +283,12 @@ export class PythonShell extends EventEmitter {
'process exited with code ' + self.exitCode,
);
}
} else if (parserError) {
err = new PythonShellError(parserError.message);
err.stack = parserError.stack;
}

if (err) {
err = <PythonShellError>extend(err, {
executable: pythonPath,
options: pythonOptions.length ? pythonOptions : null,
Expand Down
87 changes: 86 additions & 1 deletion test/test-python-shell.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as should from 'should';
import { PythonShell } from '..';
import { NewlineTransformer, PythonShell } from '..';
import { sep, join } from 'path';
import { EOL as newline } from 'os';
import { chdir, cwd } from 'process';
Expand Down Expand Up @@ -415,6 +415,51 @@ describe('PythonShell', function () {
})
.end(done);
});
it('should report JSON parser errors through the end callback', function (done) {
let pyshell = new PythonShell('echo_text.py', {
mode: 'json',
formatter: 'text',
});
pyshell.send('not-json').end(function (err) {
should.exist(err);
err.should.be.an.Error;
err.message.should.match(/JSON|Unexpected token/);
done();
});
});
it('should not treat message listener exceptions as parser errors', function (done) {
let splitter = new NewlineTransformer();
let pyshell = new PythonShell('exit-code.py', { mode: 'text' }, splitter);
let listenerError = new Error('message listener failed');
let thrownError: Error;

pyshell.on('message', function () {
throw listenerError;
});

try {
splitter.emit('data', 'hello');
} catch (err) {
thrownError = err;
}

should.exist(thrownError);
thrownError.should.be.exactly(listenerError);
pyshell.end(function (err) {
should.not.exist(err);
done();
});
});
it('should prefer process errors over parser errors on non-zero exit', function (done) {
PythonShell.run('echo_hi_then_error.py', { mode: 'json' }).then(
() => done('expected the process to reject'),
(err) => {
err.message.should.be.exactly('Exception: fibble-fah');
err.stack.should.containEql('----- Python Traceback -----');
done();
},
);
});
it('should properly buffer partial messages', function (done) {
// echo_text_with_newline_control echoes text with $'s replaced with newlines
let pyshell = new PythonShell('echo_text_with_newline_control.py', {
Expand Down Expand Up @@ -494,6 +539,46 @@ describe('PythonShell', function () {
.send('world')
.end(done);
});
it('should report stderr parser errors through the end callback', function (done) {
let pyshell = new PythonShell('stderrLogging.py', {
stderrParser: function () {
throw new Error('stderr parser failed');
},
});
pyshell.end(function (err) {
should.exist(err);
err.message.should.be.exactly('stderr parser failed');
done();
});
});
it('should not treat stderr listener exceptions as parser errors', function (done) {
let splitter = new NewlineTransformer();
let pyshell = new PythonShell(
'exit-code.py',
{ mode: 'text' },
null,
splitter,
);
let listenerError = new Error('stderr listener failed');
let thrownError: Error;

pyshell.on('stderr', function () {
throw listenerError;
});

try {
splitter.emit('data', 'hello');
} catch (err) {
thrownError = err;
}

should.exist(thrownError);
thrownError.should.be.exactly(listenerError);
pyshell.end(function (err) {
should.not.exist(err);
done();
});
});
it('should not be invoked when mode is "binary"', function (done) {
let pyshell = new PythonShell('stderrLogging.py', {
stderrParser: 'binary',
Expand Down

Back | FazBrowse Home | New Git URL