wesabe-robot-army 0.1.1 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/examples/whoami.rb +13 -0
- data/lib/robot-army.rb +47 -21
- data/lib/robot-army/at_exit.rb +19 -0
- data/lib/robot-army/eval_builder.rb +84 -0
- data/lib/robot-army/eval_command.rb +17 -0
- data/lib/robot-army/keychain.rb +10 -0
- data/lib/robot-army/marshal_ext.rb +26 -7
- data/lib/robot-army/remote_evaler.rb +59 -0
- data/lib/robot-army/ruby2ruby_ext.rb +6 -18
- data/lib/robot-army/task_master.rb +113 -201
- data/spec/at_exit_spec.rb +25 -0
- data/spec/connection_spec.rb +126 -0
- data/spec/dependency_loader_spec.rb +46 -0
- data/spec/gate_keeper_spec.rb +46 -0
- data/spec/integration_spec.rb +40 -0
- data/spec/io_spec.rb +36 -0
- data/spec/keychain_spec.rb +15 -0
- data/spec/loader_spec.rb +13 -0
- data/spec/marshal_ext_spec.rb +89 -0
- data/spec/messenger_spec.rb +28 -0
- data/spec/officer_spec.rb +36 -0
- data/spec/proxy_spec.rb +52 -0
- data/spec/ruby2ruby_ext_spec.rb +67 -0
- data/spec/soldier_spec.rb +71 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/task_master_spec.rb +272 -0
- metadata +49 -16
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe RobotArmy::Messenger do
|
4
|
+
before do
|
5
|
+
# given
|
6
|
+
@in, @out = StringIO.new, StringIO.new
|
7
|
+
|
8
|
+
@messenger = RobotArmy::Messenger.new(@in, @out)
|
9
|
+
@response = {:status => 'ok', :data => 1}
|
10
|
+
@dump = "#{Base64.encode64(Marshal.dump(@response))}|"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "posts messages to @out" do
|
14
|
+
# when
|
15
|
+
@messenger.post(@response)
|
16
|
+
|
17
|
+
# then
|
18
|
+
@out.string.must == @dump
|
19
|
+
end
|
20
|
+
|
21
|
+
it "gets messages from @in" do
|
22
|
+
# when
|
23
|
+
@in.string = @dump
|
24
|
+
|
25
|
+
# then
|
26
|
+
@messenger.get.must == @response
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe RobotArmy::Officer do
|
4
|
+
before do
|
5
|
+
# given
|
6
|
+
@messenger = mock(:messenger)
|
7
|
+
@officer = RobotArmy::Officer.new(@messenger)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "evaluates each command in a different process" do
|
11
|
+
# when
|
12
|
+
pid = proc{ @officer.run(:eval, :code => 'Process.pid', :file => __FILE__, :line => __LINE__) }
|
13
|
+
|
14
|
+
# then
|
15
|
+
pid.call.must_not == pid.call
|
16
|
+
end
|
17
|
+
|
18
|
+
it "asks for a password by posting back status=password" do
|
19
|
+
# then
|
20
|
+
@messenger.should_receive(:post).
|
21
|
+
with(:status => 'password', :data => {:as => 'root', :user => ENV['USER']})
|
22
|
+
|
23
|
+
# when
|
24
|
+
@messenger.stub!(:get).and_return(:status => 'ok', :data => 'password')
|
25
|
+
@officer.ask_for_password('root')
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns the password given upstream" do
|
29
|
+
# when
|
30
|
+
@messenger.stub!(:post)
|
31
|
+
@messenger.stub!(:get).and_return(:status => 'ok', :data => 'password')
|
32
|
+
|
33
|
+
# then
|
34
|
+
@officer.ask_for_password('root').must == 'password'
|
35
|
+
end
|
36
|
+
end
|
data/spec/proxy_spec.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe RobotArmy::Proxy do
|
4
|
+
before do
|
5
|
+
# given
|
6
|
+
@messenger = stub(:messenger, :post => nil, :get => nil)
|
7
|
+
@hash = self.hash
|
8
|
+
@proxy = RobotArmy::Proxy.new(@messenger, @hash)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "posts back a proxy status when a method is called on it" do
|
12
|
+
# then
|
13
|
+
@messenger.should_receive(:post).
|
14
|
+
with(:status => 'proxy', :data => {:hash => @hash, :call => [:to_s]})
|
15
|
+
|
16
|
+
# when
|
17
|
+
@messenger.stub!(:get).and_return(:status => 'ok', :data => 'foo')
|
18
|
+
RobotArmy::Connection.stub!(:handle_response)
|
19
|
+
@proxy.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns the value returned by a successful incoming message" do
|
23
|
+
# when
|
24
|
+
@messenger.stub!(:get).and_return(:status => 'ok', :data => 'bar')
|
25
|
+
|
26
|
+
# then
|
27
|
+
@proxy.to_s.must == 'bar'
|
28
|
+
end
|
29
|
+
|
30
|
+
it "lets exceptions bubble up from handling the message" do
|
31
|
+
# when
|
32
|
+
RobotArmy::Connection.stub!(:handle_response).and_raise
|
33
|
+
|
34
|
+
# then
|
35
|
+
proc { @proxy.to_s }.must raise_error
|
36
|
+
end
|
37
|
+
|
38
|
+
it "returns a new proxy if the response has status 'proxy'" do
|
39
|
+
# then
|
40
|
+
RobotArmy::Proxy.should_receive(:new).
|
41
|
+
with(@messenger, @hash)
|
42
|
+
|
43
|
+
# when
|
44
|
+
@messenger.stub!(:get).and_return(:status => 'proxy', :data => @hash)
|
45
|
+
@proxy.me
|
46
|
+
end
|
47
|
+
|
48
|
+
it "can generate Ruby code to create a Proxy for an object" do
|
49
|
+
RobotArmy::Proxy.generator_for(self).
|
50
|
+
must == "RobotArmy::Proxy.new(RobotArmy.upstream, #{self.hash.inspect})"
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Proc, "to_ruby" do
|
4
|
+
before do
|
5
|
+
@proc = proc{ 1 }
|
6
|
+
end
|
7
|
+
|
8
|
+
it "can render itself as ruby not enclosed in a proc" do
|
9
|
+
@proc.to_ruby_without_proc_wrapper.must == "1"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "can render itself as ruby that evaluates to a Proc" do
|
13
|
+
@proc.to_ruby.must == "proc { 1 }"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "can get a list of arguments" do
|
17
|
+
proc{ |a, b| a + b }.arguments.must == %w[a b]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class MethodToRubyFixture
|
22
|
+
def one
|
23
|
+
1
|
24
|
+
end
|
25
|
+
|
26
|
+
def echo(a)
|
27
|
+
a
|
28
|
+
end
|
29
|
+
|
30
|
+
def add(a, b)
|
31
|
+
a + b
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe Method, "to_ruby" do
|
36
|
+
before do
|
37
|
+
@method = MethodToRubyFixture.new.method(:one)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can render itself as ruby that executes itself" do
|
41
|
+
@method.to_ruby_without_method_declaration.must =~ /\A\s*1\s*\Z/
|
42
|
+
end
|
43
|
+
|
44
|
+
it "can render itself as ruby that evaluates to a Method" do
|
45
|
+
@method.to_ruby.must == "def one\n 1\nend"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe Method, "arguments" do
|
50
|
+
before do
|
51
|
+
@no_args = MethodToRubyFixture.new.method(:one)
|
52
|
+
@one_arg = MethodToRubyFixture.new.method(:echo)
|
53
|
+
@many_args = MethodToRubyFixture.new.method(:add)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "returns an empty list for a method without arguments" do
|
57
|
+
@no_args.arguments.must == []
|
58
|
+
end
|
59
|
+
|
60
|
+
it "returns a single argument for a method with a single argument" do
|
61
|
+
@one_arg.arguments.must == %w[a]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "returns a comma-separated list of arguments when there are many args" do
|
65
|
+
@many_args.arguments.must == %w[a b]
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe RobotArmy::Soldier do
|
4
|
+
before do
|
5
|
+
# given
|
6
|
+
@messenger = mock(:messenger)
|
7
|
+
@soldier = RobotArmy::Soldier.new(@messenger)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "can accept eval commands" do
|
11
|
+
# then
|
12
|
+
@soldier.run(:eval, :code => '3+4', :file => __FILE__, :line => __LINE__).
|
13
|
+
must == 7
|
14
|
+
|
15
|
+
# and
|
16
|
+
@soldier.run(:eval, :code => 'Time.now', :file => __FILE__, :line => __LINE__).
|
17
|
+
must be_an_instance_of(Time)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "evaluates each command in the same process" do
|
21
|
+
# when
|
22
|
+
pid = proc{ @soldier.run(:eval, :code => 'Process.pid', :file => __FILE__, :line => __LINE__) }
|
23
|
+
|
24
|
+
# then
|
25
|
+
pid.call.must == pid.call
|
26
|
+
end
|
27
|
+
|
28
|
+
it "raises on unrecognized commands" do
|
29
|
+
proc{ @soldier.run(:foo, nil) }.must raise_error(ArgumentError)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "listens for commands from the messenger to run" do
|
33
|
+
# then
|
34
|
+
@soldier.should_receive(:run).with(:eval, :code => 'Hash.new')
|
35
|
+
|
36
|
+
# when
|
37
|
+
@messenger.stub!(:post)
|
38
|
+
@messenger.stub!(:get).and_return(:command => :eval, :data => {:code => 'Hash.new'})
|
39
|
+
@soldier.listen
|
40
|
+
end
|
41
|
+
|
42
|
+
it "posts through the messenger the result of commands run by listening" do
|
43
|
+
# then
|
44
|
+
@messenger.should_receive(:post).with(:status => 'ok', :data => 1)
|
45
|
+
|
46
|
+
# when
|
47
|
+
@messenger.stub!(:get).and_return(:command => :eval, :data => {:code => '1'})
|
48
|
+
@soldier.stub!(:run).and_return(1)
|
49
|
+
@soldier.listen
|
50
|
+
end
|
51
|
+
|
52
|
+
it "posts back and raises RobotArmy::Exit when running the exit command" do
|
53
|
+
@messenger.should_receive(:post).with(:status => 'ok')
|
54
|
+
proc{ @soldier.run(:exit, nil) }.must raise_error(RobotArmy::Exit)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "returns the pid and type when asked for info" do
|
58
|
+
@soldier.run(:info, nil).must == {:pid => Process.pid, :type => 'RobotArmy::Soldier'}
|
59
|
+
end
|
60
|
+
|
61
|
+
it "posts back a warning if the :eval return value is not marshalable" do
|
62
|
+
# then
|
63
|
+
@messenger.should_receive(:post).
|
64
|
+
with(:status => 'warning', :data => "ignoring invalid remote return value #{$stdin.inspect}")
|
65
|
+
|
66
|
+
# when
|
67
|
+
@messenger.stub!(:get).and_return(
|
68
|
+
:command => :eval, :data => {:code => '$stdin', :file => __FILE__, :line => __LINE__})
|
69
|
+
@soldier.listen
|
70
|
+
end
|
71
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
$TESTING=true
|
2
|
+
load File.join(File.dirname(__FILE__), '..', 'lib', 'robot-army.rb')
|
3
|
+
|
4
|
+
module Spec::Expectations::ObjectExpectations
|
5
|
+
alias_method :must, :should
|
6
|
+
alias_method :must_not, :should_not
|
7
|
+
undef_method :should
|
8
|
+
undef_method :should_not
|
9
|
+
end
|
10
|
+
|
11
|
+
Spec::Runner.configure do |config|
|
12
|
+
def capture(stream)
|
13
|
+
begin
|
14
|
+
stream = stream.to_s
|
15
|
+
eval "$#{stream} = StringIO.new"
|
16
|
+
yield
|
17
|
+
result = eval("$#{stream}").string
|
18
|
+
ensure
|
19
|
+
eval("$#{stream} = #{stream.upcase}")
|
20
|
+
end
|
21
|
+
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
alias silence capture
|
26
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class Example < RobotArmy::TaskMaster
|
4
|
+
hosts %[www1.example.com www2.example.com]
|
5
|
+
end
|
6
|
+
|
7
|
+
class Localhost < RobotArmy::TaskMaster
|
8
|
+
host :localhost
|
9
|
+
end
|
10
|
+
|
11
|
+
describe RobotArmy::TaskMaster, 'host management' do
|
12
|
+
before do
|
13
|
+
@example = Example.new
|
14
|
+
end
|
15
|
+
|
16
|
+
it "allows setting a single host" do
|
17
|
+
Example.host 'example.com'
|
18
|
+
Example.host.must == 'example.com'
|
19
|
+
end
|
20
|
+
|
21
|
+
it "allows accessing multi-hosts when using the single-host interface" do
|
22
|
+
Example.host 'example.com'
|
23
|
+
Example.hosts.must == %w[example.com]
|
24
|
+
end
|
25
|
+
|
26
|
+
it "allows setting multiple hosts on the class" do
|
27
|
+
Example.hosts %w[example.com test.com]
|
28
|
+
Example.hosts.must == %w[example.com test.com]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "denies accessing a single host when using the multi-host interface" do
|
32
|
+
Example.hosts %w[example.com test.com]
|
33
|
+
proc { Example.host }.must raise_error(
|
34
|
+
RobotArmy::HostArityError, "There are 2 hosts, so calling host doesn't make sense")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "instances default to the hosts set on the class" do
|
38
|
+
Example.host 'example.com'
|
39
|
+
@example.host.must == 'example.com'
|
40
|
+
|
41
|
+
Example.hosts %w[example.com test.com]
|
42
|
+
@example.hosts.must == %w[example.com test.com]
|
43
|
+
end
|
44
|
+
|
45
|
+
it "allows setting a single host on an instance" do
|
46
|
+
@example.host = 'example.com'
|
47
|
+
@example.host.must == 'example.com'
|
48
|
+
end
|
49
|
+
|
50
|
+
it "allows accessing multi-hosts when using the single-host interface on instances" do
|
51
|
+
@example.host = 'example.com'
|
52
|
+
@example.hosts.must == %w[example.com]
|
53
|
+
end
|
54
|
+
|
55
|
+
it "allows setting multiple hosts on an instance" do
|
56
|
+
@example.hosts = %w[example.com test.com]
|
57
|
+
@example.hosts.must == %w[example.com test.com]
|
58
|
+
end
|
59
|
+
|
60
|
+
it "denies accessing a single host when using the multi-host interface" do
|
61
|
+
@example.hosts = %w[example.com test.com test2.com]
|
62
|
+
proc { @example.host }.must raise_error(
|
63
|
+
RobotArmy::HostArityError, "There are 3 hosts, so calling host doesn't make sense")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe RobotArmy::TaskMaster, 'remote' do
|
68
|
+
before do
|
69
|
+
@localhost = Localhost.new
|
70
|
+
@example = Example.new
|
71
|
+
end
|
72
|
+
|
73
|
+
it "returns a single item when using the single-host interface" do
|
74
|
+
@localhost.stub!(:remote_eval).and_return(7)
|
75
|
+
@localhost.remote { 3+4 }.must == 7
|
76
|
+
end
|
77
|
+
|
78
|
+
it "returns an array of items when using the multi-host interface" do
|
79
|
+
@example.stub!(:remote_eval).and_return(7)
|
80
|
+
@example.remote { 3+4 }.must == [7, 7]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe RobotArmy::TaskMaster do
|
85
|
+
before do
|
86
|
+
@localhost = Localhost.new
|
87
|
+
@example = Example.new
|
88
|
+
end
|
89
|
+
|
90
|
+
it "runs a remote block on each host" do
|
91
|
+
@example.should_receive(:remote_eval).exactly(2).times
|
92
|
+
@example.remote { 3+4 }
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
it "can execute a Ruby block and return the result" do
|
97
|
+
@localhost.remote { 3+4 }.must == 7
|
98
|
+
end
|
99
|
+
|
100
|
+
it "executes its block in a different process" do
|
101
|
+
@localhost.remote { Process.pid }.must_not == Process.pid
|
102
|
+
end
|
103
|
+
|
104
|
+
it "preserves local variables" do
|
105
|
+
a = 42
|
106
|
+
@localhost.remote { a }.must == 42
|
107
|
+
end
|
108
|
+
|
109
|
+
it "warns about invalid remote return values" do
|
110
|
+
capture(:stderr) { @localhost.remote { $stdin } }.
|
111
|
+
must =~ /WARNING: ignoring invalid remote return value/
|
112
|
+
end
|
113
|
+
|
114
|
+
it "returns nil if the remote return value is invalid" do
|
115
|
+
silence(:stderr) { @localhost.remote { $stdin }.must be_nil }
|
116
|
+
end
|
117
|
+
|
118
|
+
it "re-raises exceptions thrown remotely" do
|
119
|
+
proc { @localhost.remote { raise ArgumentError, "You fool!" } }.
|
120
|
+
must raise_error(ArgumentError)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "prints the child Ruby's stderr to stderr" do
|
124
|
+
pending('we may not want to do this, even')
|
125
|
+
capture(:stderr) { @localhost.remote { $stderr.print "foo" } }.must == "foo"
|
126
|
+
end
|
127
|
+
|
128
|
+
it "runs multiple remote blocks for the same host in different processes" do
|
129
|
+
@localhost.remote { $a = 1 }
|
130
|
+
@localhost.remote { $a }.must be_nil
|
131
|
+
end
|
132
|
+
|
133
|
+
it "only loads one Officer process on the remote machine" do
|
134
|
+
info = @localhost.connection(@localhost.host).info
|
135
|
+
info[:pid].must_not == Process.pid
|
136
|
+
info[:type].must == 'RobotArmy::Officer'
|
137
|
+
@localhost.connection(@localhost.host).info.must == info
|
138
|
+
end
|
139
|
+
|
140
|
+
it "runs as a normal (non-super) user by default" do
|
141
|
+
@localhost.remote{ Process.uid }.must_not == 0
|
142
|
+
end
|
143
|
+
|
144
|
+
it "loads dependencies" do
|
145
|
+
@localhost.dependency "thor"
|
146
|
+
@localhost.remote { Thor ; 45 }.must == 45 # loading should not bail here
|
147
|
+
end
|
148
|
+
|
149
|
+
it "delegates scp to the scp binary" do
|
150
|
+
@localhost.should_receive(:`).with('scp -q file.tgz example.com:/tmp 2>&1')
|
151
|
+
@localhost.host = 'example.com'
|
152
|
+
@localhost.scp 'file.tgz', '/tmp'
|
153
|
+
end
|
154
|
+
|
155
|
+
it "delegates to scp without a host when host is localhost" do
|
156
|
+
@localhost.should_receive(:`).with('scp -q file.tgz /tmp 2>&1')
|
157
|
+
@localhost.scp 'file.tgz', '/tmp'
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe RobotArmy::TaskMaster, 'scp' do
|
162
|
+
before do
|
163
|
+
@localhost = Localhost.new
|
164
|
+
end
|
165
|
+
|
166
|
+
it "raises if scp fails due to a permissions error" do
|
167
|
+
@localhost.stub!(:`).and_return("scp: /tmp/foo: Permission denied\n")
|
168
|
+
$?.stub!(:exitstatus).and_return(1)
|
169
|
+
lambda { @localhost.scp('foo', '/tmp') }.must raise_error(Errno::EACCES)
|
170
|
+
end
|
171
|
+
|
172
|
+
it "raises if scp cannot locate the source file" do
|
173
|
+
lambda { @localhost.scp('i-dont-exist', '/tmp') }.must raise_error(Errno::ENOENT)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
describe RobotArmy::TaskMaster, 'remote (with args)' do
|
178
|
+
before do
|
179
|
+
@localhost = Localhost.new
|
180
|
+
end
|
181
|
+
|
182
|
+
it "can pass arguments explicitly" do
|
183
|
+
@localhost.remote(:args => [42]) { |a| a }.must == 42
|
184
|
+
end
|
185
|
+
|
186
|
+
it "shadows local variables of the same name" do
|
187
|
+
a = 23
|
188
|
+
@localhost.remote(:args => [42]) { |a| a }.must == 42
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe RobotArmy::TaskMaster, 'cptemp' do
|
193
|
+
before do
|
194
|
+
@localhost = Localhost.new
|
195
|
+
@path = 'cptemp-spec-file'
|
196
|
+
File.open(@path, 'w') {|f| f << 'testing'}
|
197
|
+
end
|
198
|
+
|
199
|
+
it "safely copies to a new temporary directory" do
|
200
|
+
destination = @localhost.cptemp @path
|
201
|
+
File.read(destination).must == 'testing'
|
202
|
+
end
|
203
|
+
|
204
|
+
it "yields the path to each host if a block is passed" do
|
205
|
+
path, pid = @localhost.cptemp(@path) { |path| [path, Process.pid] }
|
206
|
+
File.basename(path).must == @path
|
207
|
+
pid.must_not be_nil
|
208
|
+
pid.must_not == Process.pid
|
209
|
+
end
|
210
|
+
|
211
|
+
it "deletes the file on exit" do
|
212
|
+
destination = @localhost.cptemp @path
|
213
|
+
RobotArmy::AtExit.shared_instance.do_exit
|
214
|
+
fail "Expected cptemp'ed file to be deleted when exit callbacks were run" if File.exist?(destination)
|
215
|
+
end
|
216
|
+
|
217
|
+
after do
|
218
|
+
FileUtils.rm_f(@path)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe RobotArmy::TaskMaster, 'with proxies' do
|
223
|
+
before do
|
224
|
+
@localhost = Localhost.new
|
225
|
+
end
|
226
|
+
|
227
|
+
it "can allow remote method calls on the local object" do
|
228
|
+
def @localhost.foo; 'bar'; end
|
229
|
+
@localhost.remote { foo }.must == 'bar'
|
230
|
+
end
|
231
|
+
|
232
|
+
it "allows calling methods with arguments" do
|
233
|
+
def @localhost.echo(o) o; end
|
234
|
+
@localhost.remote { echo 42 }.must == 42
|
235
|
+
end
|
236
|
+
|
237
|
+
it "allows passing a block to method calls on proxy objects" do
|
238
|
+
pending('this is insane. should I do this?')
|
239
|
+
end
|
240
|
+
|
241
|
+
it "allows interaction with IOs" do
|
242
|
+
capture(:stdout) {
|
243
|
+
stdout = $stdout
|
244
|
+
@localhost.remote { stdout.puts "hey there" }
|
245
|
+
}.must == "hey there\n"
|
246
|
+
end
|
247
|
+
|
248
|
+
it "returns a proxy if the return value of an upstream call can't be marshaled" do
|
249
|
+
def @localhost.stdout; $stdout; end
|
250
|
+
capture(:stdout) { @localhost.remote { stdout.puts "foo" } }.must == "foo\n"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
describe RobotArmy::TaskMaster, 'sudo' do
|
255
|
+
before do
|
256
|
+
@localhost = Localhost.new
|
257
|
+
end
|
258
|
+
|
259
|
+
it "runs remote with the root user by default" do
|
260
|
+
@localhost.should_receive(:remote).
|
261
|
+
with(@localhost.hosts, :user => 'root')
|
262
|
+
|
263
|
+
@localhost.sudo { File.read('/etc/passwd') }
|
264
|
+
end
|
265
|
+
|
266
|
+
it "allows specifying a particular user" do
|
267
|
+
@localhost.should_receive(:remote).
|
268
|
+
with(@localhost.hosts, :user => 'www-data')
|
269
|
+
|
270
|
+
@localhost.sudo(:user => 'www-data') { %x{/etc/init.d/apache2 restart} }
|
271
|
+
end
|
272
|
+
end
|