From 8f5746c8feb5fe0d1af7e57207a9cc54a02ef85c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 11 Jul 2012 16:30:50 -0600 Subject: [PATCH] Add bufferLines option to ChildProcess.exec It ensures that stdout and stderr callbacks are triggered with whole lines --- spec/stdlib/child-process-spec.coffee | 86 ++++++++++++++++++++------- src/stdlib/child-process.coffee | 13 ++++ 2 files changed, 79 insertions(+), 20 deletions(-) diff --git a/spec/stdlib/child-process-spec.coffee b/spec/stdlib/child-process-spec.coffee index 74c106e06..6c8b7e7d8 100644 --- a/spec/stdlib/child-process-spec.coffee +++ b/spec/stdlib/child-process-spec.coffee @@ -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 - - diff --git a/src/stdlib/child-process.coffee b/src/stdlib/child-process.coffee index 18977c41e..164fd1c13 100644 --- a/src/stdlib/child-process.coffee +++ b/src/stdlib/child-process.coffee @@ -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)