Forward standard error stream from remote execution

This commit is contained in:
Thibault Jouan 2014-07-21 12:45:57 +00:00
parent a033e19583
commit db91eb06cd
14 changed files with 93 additions and 17 deletions

View File

@ -11,6 +11,16 @@ Feature: `sh' task action
When I successfully execute the recipe on remote target
Then the output must contain exactly "hello from remote\n"
Scenario: forwards error ouput
Given a recipe with:
"""
task :sh_action do
sh '\echo error from remote >&2'
end
"""
When I successfully execute the recipe on remote target
Then the error output must contain exactly "error from remote\n"
Scenario: aborts on failed command execution
Given a recipe with:
"""

View File

@ -21,3 +21,7 @@ end
Then /^the output must contain exactly:$/ do |content|
assert_exact_output content, all_output
end
Then /^the error output must contain exactly "([^"]+)"$/ do |content|
assert_exact_output content, all_stderr
end

View File

@ -2,7 +2,7 @@ module Producer
module Core
class Action
extend Forwardable
def_delegators :@env, :input, :output, :remote
def_delegators :@env, :input, :output, :error_output, :remote
def_delegators :remote, :fs
attr_reader :env, :arguments

View File

@ -7,7 +7,7 @@ module Producer
end
def apply
remote.execute(arguments.first, output)
remote.execute(arguments.first, output, error_output)
end
end
end

View File

@ -31,7 +31,7 @@ module Producer
@arguments = args
@stdin = stdin
@stdout = stdout
@env = Env.new(input: stdin, output: stdout)
@env = Env.new(input: stdin, output: stdout, error_output: stderr)
end
def parse_arguments!

View File

@ -1,15 +1,16 @@
module Producer
module Core
class Env
attr_reader :input, :output, :registry, :logger
attr_reader :input, :output, :error_output, :registry, :logger
attr_accessor :target, :verbose, :dry_run
def initialize(input: $stdin, output: $stdout, remote: nil, registry: {})
@verbose = @dry_run = false
@input = input
@output = output
@remote = remote
@registry = registry
def initialize(input: $stdin, output: $stdout, error_output: $stderr, remote: nil, registry: {})
@verbose = @dry_run = false
@input = input
@output = output
@error_output = error_output
@remote = remote
@registry = registry
end
def remote

View File

@ -24,13 +24,17 @@ module Producer
@fs ||= Remote::FS.new(session.sftp.connect)
end
def execute(command, output = '')
def execute(command, output = '', error_output = '')
channel = session.open_channel do |channel|
channel.exec command do |ch, success|
ch.on_data do |c, data|
output << data
end
ch.on_extended_data do |c, type, data|
error_output << data
end
ch.on_request 'exit-status' do |c, data|
exit_status = data.read_long
fail RemoteCommandExecutionError, command if exit_status != 0

View File

@ -6,13 +6,18 @@ module Producer
fail 'no session for mock remote!'
end
def execute(command, output = '')
tokens = command.split
def execute(command, output = '', error_output = '')
tokens = command.gsub(/\d?>.*/, '').split
program = tokens.shift
case program
when 'echo'
output << tokens.join(' ') << "\n"
out = tokens.join(' ') << "\n"
if command =~ />&2\z/
error_output << out
else
output << out
end
when 'true'
output << ''
when 'false'

View File

@ -19,6 +19,15 @@ module Producer::Core
sh.apply
expect(output).to eq "#{command_args}\n"
end
context 'when content is written to standard error' do
let(:command) { "echo #{command_args} >&2" }
it 'writes errors to given error stream' do
sh.apply
expect(error_output).to eq "#{command_args}\n"
end
end
end
end
end

View File

@ -12,7 +12,9 @@ module Producer::Core
let(:stdout) { StringIO.new }
let(:stderr) { StringIO.new }
subject(:cli) { CLI.new(arguments, stdin: stdin, stdout: stdout) }
subject(:cli) { described_class.new(
arguments,
stdin: stdin, stdout: stdout, stderr: stderr) }
describe '.run!' do
let(:cli) { double('cli').as_null_object }
@ -118,6 +120,10 @@ module Producer::Core
it 'assigns CLI stdout as the env output' do
expect(cli.env.output).to be stdout
end
it 'assigns CLI stderr as the env error output' do
expect(cli.env.error_output).to be stderr
end
end
describe '#parse_arguments!' do

View File

@ -10,6 +10,10 @@ module Producer::Core
expect(env.input).to be $stdin
end
it 'assigns $stderr as the default error output' do
expect(env.error_output).to be $stderr
end
it 'assigns no default target' do
expect(env.target).not_to be
end
@ -51,6 +55,15 @@ module Producer::Core
end
end
context 'when error output is given as argument' do
let(:error_output) { StringIO.new }
subject(:env) { described_class.new(error_output: error_output) }
it 'assigns the given error output' do
expect(env.error_output).to be error_output
end
end
context 'when remote is given as argument' do
let(:remote) { double 'remote' }
subject(:env) { described_class.new(remote: remote) }

View File

@ -106,6 +106,16 @@ module Producer::Core
expect(output.string).to eq arguments
end
it 'writes command error output to provided error output' do
error_output = StringIO.new
story_with_new_channel do |ch|
ch.sends_exec command
ch.gets_extended_data arguments
end
remote.execute command, output, error_output
expect(error_output.string).to eq arguments
end
context 'when command execution fails' do
before do
story_with_new_channel do |ch|

View File

@ -29,6 +29,12 @@ module Producer::Core
end
end
describe '#error_output' do
it 'returns env error output' do
expect(action.error_output).to be env.error_output
end
end
describe '#remote' do
it 'returns env remote' do
expect(action.remote).to be env.remote

View File

@ -9,6 +9,10 @@ module TestEnvHelpers
env.output.string
end
def error_output
env.error_output.string
end
def remote_fs
env.remote.fs
end
@ -17,14 +21,18 @@ module TestEnvHelpers
opts = { expected_from: caller.first }
RSpec::Mocks
.expect_message(env.remote, :execute, opts)
.with(command, env.output)
.with(command, env.output, env.error_output)
end
private
def build_env
Producer::Core::Env.new(output: StringIO.new, remote: build_remote)
Producer::Core::Env.new(
output: StringIO.new,
error_output: StringIO.new,
remote: build_remote
)
end
def build_remote