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.
@@ -0,0 +1,25 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe RobotArmy::AtExit do
4
+ before do
5
+ @at_exit = RobotArmy::AtExit.shared_instance
6
+ end
7
+
8
+ it "runs the provided block when directed" do
9
+ foo = 'foo'
10
+ @at_exit.at_exit { foo = 'bar' }
11
+ foo.must == 'foo'
12
+ @at_exit.do_exit
13
+ foo.must == 'bar'
14
+ end
15
+
16
+ it "does not run the same block twice" do
17
+ foo = 0
18
+ @at_exit.at_exit { foo += 1 }
19
+ foo.must == 0
20
+ @at_exit.do_exit
21
+ foo.must == 1
22
+ @at_exit.do_exit
23
+ foo.must == 1
24
+ end
25
+ end
@@ -0,0 +1,126 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe RobotArmy::Connection do
4
+ before do
5
+ # given
6
+ @host = 'example.com'
7
+ @connection = RobotArmy::Connection.new(@host)
8
+ @messenger = mock(:messenger)
9
+ @messenger.stub!(:post)
10
+ @connection.stub!(:messenger).and_return(@messenger)
11
+ @connection.stub!(:start_child)
12
+ end
13
+
14
+ it "is not closed after opening" do
15
+ # when
16
+ @connection.open
17
+
18
+ # then
19
+ @connection.must_not be_closed
20
+ end
21
+
22
+ it "returns itself from open" do
23
+ @connection.open.must == @connection
24
+ end
25
+
26
+ it "returns the result of the block passed to open" do
27
+ # when
28
+ @connection.stub!(:close)
29
+
30
+ # then
31
+ @connection.open{ 3 }.must == 3
32
+ end
33
+
34
+ it "closes the connection if a block is passed to open" do
35
+ # then
36
+ proc{ @connection.open{ 3 } }.
37
+ must_not change(@connection, :closed?).from(true)
38
+ end
39
+
40
+ it "closes the connection even if an exception is raised in the block passed to open" do
41
+ # then
42
+ proc do
43
+ proc{ @connection.open{ raise 'BOO!' } }.
44
+ must_not change(@connection, :closed?).from(true)
45
+ end.
46
+ must raise_error('BOO!')
47
+ end
48
+
49
+ it "does not start another child process if we're already open" do
50
+ # then
51
+ @connection.should_not_receive(:start_child)
52
+
53
+ # when
54
+ @connection.stub!(:closed?).and_return(false)
55
+ @connection.open
56
+ end
57
+
58
+ it "raises an exception when calling close if a connection is already closed" do
59
+ # when
60
+ @connection.stub!(:closed?).and_return(true)
61
+
62
+ # then
63
+ proc{ @connection.close }.must raise_error(RobotArmy::ConnectionNotOpen)
64
+ end
65
+
66
+ it "sends an exit command to its child upon closing" do
67
+ # then
68
+ @messenger.should_receive(:post).with(:command => :exit)
69
+
70
+ # when
71
+ @connection.stub!(:closed?).and_return(false)
72
+ @connection.close
73
+ end
74
+
75
+ it "starts a closed local connection when calling localhost" do
76
+ RobotArmy::Connection.localhost.must be_closed
77
+ end
78
+
79
+ it "raises a Warning when handling a message with status=warning" do
80
+ proc{ @connection.handle_response(:status => 'warning', :data => 'foobar') }.
81
+ must raise_error(RobotArmy::Warning)
82
+ end
83
+ end
84
+
85
+ describe RobotArmy::Connection, 'answer_sudo_prompt' do
86
+ before do
87
+ @connection = RobotArmy::Connection.new(:localhost, 'root')
88
+ @password_proc = proc { 'password' }
89
+ @stdin = StringIO.new
90
+ @stderr = stub(:stderr, :readpartial => nil)
91
+ end
92
+
93
+ it "calls back using the password proc if it is a proc" do
94
+ # when
95
+ @connection.stub!(:password).and_return(@password_proc)
96
+ @connection.stub!(:asking_for_password?).and_return(true, false)
97
+ @connection.answer_sudo_prompt(@stdin, @stderr)
98
+
99
+ # then
100
+ @stdin.string.must == "password\n"
101
+ end
102
+
103
+ it "raises if password is a string and is rejected" do
104
+ # when
105
+ @connection.stub!(:password).and_return('password')
106
+ @connection.stub!(:asking_for_password?).and_return(true)
107
+
108
+ # then
109
+ proc { @connection.answer_sudo_prompt(@stdin, @stderr) }.
110
+ must raise_error(RobotArmy::InvalidPassword)
111
+ end
112
+
113
+ it "calls back three times before raising if password is a proc" do
114
+ calls = 0
115
+
116
+ # when
117
+ @connection.stub!(:password).and_return(proc{ calls += 1 })
118
+
119
+ # then
120
+ @connection.should_receive(:asking_for_password?).
121
+ exactly(4).times.and_return(true)
122
+ proc { @connection.answer_sudo_prompt(@stdin, @stderr) }.
123
+ must raise_error(RobotArmy::InvalidPassword)
124
+ calls.must == 3
125
+ end
126
+ end
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe RobotArmy::DependencyLoader do
4
+ before do
5
+ @loader = RobotArmy::DependencyLoader.new
6
+ end
7
+
8
+ it "should have no dependencies by default" do
9
+ @loader.dependencies.must == []
10
+ end
11
+
12
+ it "should store the dependency requirement by name" do
13
+ name = "RedCloth"
14
+ @loader.add_dependency name
15
+ @loader.dependencies.must == [[name]]
16
+ end
17
+
18
+ it "should store the dependency requirement with version restriction" do
19
+ name = "RedCloth"
20
+ version_str = "> 3.1.0"
21
+ @loader.add_dependency name, version_str
22
+ @loader.dependencies.must == [[name, version_str]]
23
+ end
24
+
25
+ it "should gem load a dependency by name only" do
26
+ name = "foobarbaz"
27
+ @loader.add_dependency name
28
+ @loader.should_receive(:gem).with(name)
29
+ @loader.load!
30
+ end
31
+
32
+ it "should gem load a dependency by name and version" do
33
+ name = "foobarbaz"
34
+ version = "> 3.1"
35
+ @loader.add_dependency name, version
36
+ @loader.should_receive(:gem).with(name, version)
37
+ @loader.load!
38
+ end
39
+
40
+
41
+ it "should raise when a dependency is not met" do
42
+ @loader.add_dependency "foobarbaz"
43
+ @loader.should_receive(:gem).and_raise Gem::LoadError
44
+ lambda { @loader.load! }.must raise_error(RobotArmy::DependencyError)
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe RobotArmy::GateKeeper do
4
+ before do
5
+ # given
6
+ @keeper = RobotArmy::GateKeeper.new
7
+ @host = 'example.com'
8
+ @connection = mock(:connection)
9
+ @connection.stub!(:closed?).and_return(false)
10
+ end
11
+
12
+ it "establishes a new connection to a host if one does not already exist" do
13
+ # then
14
+ @keeper.should_receive(:establish_connection).with(@host)
15
+
16
+ # when
17
+ @keeper.stub!(:get_connection).and_return(nil)
18
+ @keeper.connect(@host)
19
+ end
20
+
21
+ it "terminates all connections on close" do
22
+ # then
23
+ @connection.should_receive(:close)
24
+
25
+ # when
26
+ @keeper.stub!(:connections).and_return(@host => @connection)
27
+ @keeper.close
28
+ end
29
+
30
+ it "creates a new Connection with the given host when establish_connection is called" do
31
+ # then
32
+ RobotArmy::OfficerConnection.should_receive(:new).with(@host).and_return(@connection)
33
+ @connection.should_receive(:open).and_return(@connection)
34
+
35
+ # when
36
+ @keeper.establish_connection(@host)
37
+
38
+ # and
39
+ @keeper.connections[@host].must == @connection
40
+ end
41
+
42
+ it "has a shared instance that doesn't change" do
43
+ RobotArmy::GateKeeper.shared_instance.must be_an_instance_of(RobotArmy::GateKeeper)
44
+ RobotArmy::GateKeeper.shared_instance.must == RobotArmy::GateKeeper.shared_instance
45
+ end
46
+ end
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ # THIS SPEC IS INTENDED TO BE RUN BY ITSELF:
4
+ #
5
+ # env SUDO_AS=brad INTEGRATION_HOST=example.com spec spec/integration_spec.rb --format=specdoc --color
6
+ #
7
+
8
+ if $INTEGRATION_HOST = ENV['INTEGRATION_HOST']
9
+ $TESTING = false
10
+ $ROBOT_ARMY_DEBUG = true
11
+
12
+ class Integration < RobotArmy::TaskMaster
13
+ host $INTEGRATION_HOST
14
+ end
15
+
16
+ describe Integration do
17
+ before do
18
+ @tm = Integration.new
19
+ end
20
+
21
+ it "can do sudo" do
22
+ @tm.sudo { Time.now }.must be_a(Time)
23
+ end
24
+
25
+ it "does sudo as root by default" do
26
+ @tm.sudo { Process.uid }.must == 0
27
+ end
28
+
29
+ it "can do sudo as ourselves" do
30
+ my_remote_uid = @tm.remote { Process.uid }
31
+ @tm.sudo(:user => ENV['USER']) { Process.uid }.must == my_remote_uid
32
+ end
33
+
34
+ if sudo_user = ENV['SUDO_AS']
35
+ it "can sudo as another non-root user" do
36
+ @tm.sudo(:user => sudo_user) { ENV['USER'] }.must == sudo_user
37
+ end
38
+ end
39
+ end
40
+ end
data/spec/io_spec.rb ADDED
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe RobotArmy::IO, 'class method read_data' do
4
+ before do
5
+ @stream = stub(:stream)
6
+ end
7
+
8
+ it "reads all data as long as it is available" do
9
+ RobotArmy::IO.stub!(:has_data?).and_return(true, true, false)
10
+ @stream.stub!(:readpartial).and_return('foo', 'bar')
11
+
12
+ RobotArmy::IO.read_data(@stream).must == "foobar"
13
+ end
14
+ end
15
+
16
+ describe RobotArmy::IO do
17
+ before do
18
+ @stream = RobotArmy::IO.new(:stdout)
19
+ @upstream = RobotArmy.upstream = stub(:upstream)
20
+ end
21
+
22
+ it "can capture output of IO method calls" do
23
+ @stream.send(:capture, :print, 'foo').must == 'foo'
24
+ end
25
+
26
+ it "proxies output upstream" do
27
+ @upstream.should_receive(:post).
28
+ with(:status => 'output', :data => {:stream => 'stdout', :string => "foo\n"})
29
+
30
+ @stream.puts 'foo'
31
+ end
32
+
33
+ after do
34
+ @stream.stop_capture
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe RobotArmy::Keychain do
4
+ before do
5
+ @keychain = RobotArmy::Keychain.new
6
+ end
7
+
8
+ it "asks for all passwords over stdin" do
9
+ @keychain.
10
+ should_receive(:read_with_prompt).
11
+ with("[sudo] password for bob@example.com: ").
12
+ and_return("god")
13
+ @keychain.get_password_for_user_on_host('bob', 'example.com').must == 'god'
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe RobotArmy::Loader do
4
+ before do
5
+ @loader = RobotArmy::Loader.new
6
+ @messenger = @loader.messenger = mock(:messenger)
7
+ @messenger.stub!(:post)
8
+ end
9
+
10
+ it "doesn't catch the RobotArmy::Exit exception" do
11
+ proc{ @loader.safely{ raise RobotArmy::Exit } }.must raise_error(RobotArmy::Exit)
12
+ end
13
+ end
@@ -0,0 +1,89 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ class CannotMarshalMe
4
+ def marshalable?
5
+ false
6
+ end
7
+ end
8
+
9
+ class CustomContainer
10
+ def initialize(child)
11
+ @child = child
12
+ end
13
+ end
14
+
15
+ describe Marshal do
16
+ it "can dump Fixnums" do
17
+ 42.must be_marshalable
18
+ end
19
+
20
+ it "can dump Strings" do
21
+ "foo".must be_marshalable
22
+ end
23
+
24
+ it "can dump Arrays" do
25
+ [2, 'foo'].must be_marshalable
26
+ end
27
+
28
+ it "can't dump things that return false for marshalable?" do
29
+ CannotMarshalMe.new.must_not be_marshalable
30
+ end
31
+
32
+ it "can't dump Arrays containing unmarshalable items" do
33
+ [2, $stdin].must_not be_marshalable
34
+ [2, CannotMarshalMe.new].must_not be_marshalable
35
+ end
36
+
37
+ it "can dump hashes" do
38
+ {:foo => 'bar'}.must be_marshalable
39
+ end
40
+
41
+ it "can't dump hashes with unmarshalable keys" do
42
+ {$stdin => 'bar'}.must_not be_marshalable
43
+ {CannotMarshalMe.new => 'bar'}.must_not be_marshalable
44
+ end
45
+
46
+ it "can't dump hashes with unmarshalable values" do
47
+ {:foo => $stdin}.must_not be_marshalable
48
+ {:foo => CannotMarshalMe.new}.must_not be_marshalable
49
+ end
50
+
51
+ it "can't dump hashes with an unmarshalable default value" do
52
+ Hash.new($stdin).must_not be_marshalable
53
+ Hash.new(CannotMarshalMe.new).must_not be_marshalable
54
+ end
55
+
56
+ it "can dump hashes with a marshalable default value" do
57
+ Hash.new(0).must be_marshalable
58
+ end
59
+
60
+ it "can't dump hashes with a default proc" do
61
+ Hash.new {|a,b| a[b] = rand}.must_not be_marshalable
62
+ end
63
+
64
+ it "can't dump objects containing references to unmarshalable objects" do
65
+ CustomContainer.new(CannotMarshalMe.new).must_not be_marshalable
66
+ end
67
+
68
+ it "can't dump IOs" do
69
+ $stdin.must_not be_marshalable
70
+ end
71
+
72
+ it "can't dump Methods" do
73
+ method(:to_s).must_not be_marshalable
74
+ end
75
+
76
+ it "can't dump bindings" do
77
+ binding.must_not be_marshalable
78
+ end
79
+
80
+ it "can't dump Procs" do
81
+ proc{ 2 }.must_not be_marshalable
82
+ end
83
+
84
+ it "can't dump anything whose _dump method raises a TypeError" do
85
+ class NotDumpable; def _dump(*args); raise TypeError; end; end
86
+ NotDumpable.new.must_not be_marshalable
87
+ end
88
+ end
89
+