Add bufferLines option to ChildProcess.exec

It ensures that stdout and stderr callbacks are triggered with whole lines
This commit is contained in:
Nathan Sobo
2012-07-11 16:30:50 -06:00
parent 2afec5cf53
commit 8f5746c8fe
2 changed files with 79 additions and 20 deletions

View File

@@ -1,7 +1,13 @@
ChildProcess = require 'child-process'
fdescribe 'Child Processes', ->
describe 'Child Processes', ->
describe ".exec(command, options)", ->
[stderrHandler, stdoutHandler] = []
beforeEach ->
stderrHandler = jasmine.createSpy "stderrHandler"
stdoutHandler = jasmine.createSpy "stdoutHandler"
it "returns a promise that resolves to stdout and stderr", ->
waitsForPromise ->
cmd = "echo 'good' && echo 'bad' >&2"
@@ -9,23 +15,8 @@ fdescribe 'Child Processes', ->
expect(stdout).toBe 'good\n'
expect(stderr).toBe 'bad\n'
describe "when `options` contains a stdout function", ->
it "calls the stdout function when new data is received", ->
stderrHandler = jasmine.createSpy "stderrHandler"
cmd = "echo '1111' >&2 && sleep .1 && echo '2222' >&2"
ChildProcess.exec(cmd, stderr: stderrHandler)
waitsFor ->
stderrHandler.callCount > 1
runs ->
expect(stderrHandler.argsForCall[0][0]).toBe "1111\n"
expect(stderrHandler.argsForCall[1][0]).toBe "2222\n"
it "calls the stderr function when new data is received", ->
stdoutHandler = jasmine.createSpy "stdoutHandler"
describe "when `options` contains stdout/stderror callbacks", ->
it "calls the stdout callback when new data is received on stdout", ->
cmd = "echo 'first' && sleep .1 && echo 'second' && sleep .1 && echo 'third'"
ChildProcess.exec(cmd, stdout: stdoutHandler)
@@ -37,11 +28,66 @@ fdescribe 'Child Processes', ->
expect(stdoutHandler.argsForCall[1][0]).toBe "second\n"
expect(stdoutHandler.argsForCall[2][0]).toBe "third\n"
it "calls the stderr callback when new data is received on stderr", ->
cmd = "echo '1111' >&2 && sleep .1 && echo '2222' >&2"
ChildProcess.exec(cmd, stderr: stderrHandler)
waitsFor ->
stderrHandler.callCount > 1
runs ->
expect(stderrHandler.argsForCall[0][0]).toBe "1111\n"
expect(stderrHandler.argsForCall[1][0]).toBe "2222\n"
describe "when the `bufferLines` option is true ", ->
[simulateStdout, simulateStderr] = []
beforeEach ->
spyOn($native, 'exec')
ChildProcess.exec("print_the_things", bufferLines: true, stdout: stdoutHandler, stderr: stderrHandler)
{ stdout, stderr } = $native.exec.argsForCall[0][1]
simulateStdout = stdout
simulateStderr = stderr
it "only triggers stdout callbacks with complete lines", ->
simulateStdout """
I am a full line
I am part of """
expect(stdoutHandler).toHaveBeenCalledWith("I am a full line\n")
stdoutHandler.reset()
simulateStdout """
a line
I am another full line\n
"""
expect(stdoutHandler).toHaveBeenCalledWith """
I am part of a line
I am another full line\n
"""
it "only triggers stderr callbacks with complete lines", ->
simulateStderr """
I am a full line
I am part of """
expect(stderrHandler).toHaveBeenCalledWith("I am a full line\n")
stdoutHandler.reset()
simulateStderr """
a line
I am another full line\n
"""
expect(stderrHandler).toHaveBeenCalledWith """
I am part of a line
I am another full line\n
"""
describe "when the command fails", ->
it "executes the callback with error set to the exit status", ->
waitsForPromise ->
cmd = "exit 2"
ChildProcess.exec(cmd).fail (error) ->
expect(error.exitStatus).toBe 2

View File

@@ -8,6 +8,11 @@ module.exports =
class ChildProccess
@exec: (command, options={}) ->
deferred = $.Deferred()
if options.bufferLines
options.stdout = @bufferLines(options.stdout) if options.stdout
options.stderr = @bufferLines(options.stderr) if options.stderr
$native.exec command, options, (exitStatus, stdout, stdin) ->
if error != 0
error = new Error("Exec failed (#{exitStatus}) command '#{command}'")
@@ -18,3 +23,11 @@ class ChildProccess
deferred
@bufferLines: (callback) ->
buffered = ""
(data) ->
buffered += data
lastNewlineIndex = buffered.lastIndexOf('\n')
if lastNewlineIndex >= 0
callback(buffered.substring(0, lastNewlineIndex + 1))
buffered = buffered.substring(lastNewlineIndex + 1)