wesabe-robot-army 0.1.1 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+