gorgon 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +52 -0
- data/README.md +53 -0
- data/Rakefile +1 -0
- data/bin/gorgon +41 -0
- data/gorgon.gemspec +33 -0
- data/lib/gorgon.rb +6 -0
- data/lib/gorgon/amqp_service.rb +39 -0
- data/lib/gorgon/callback_handler.rb +21 -0
- data/lib/gorgon/configuration.rb +9 -0
- data/lib/gorgon/failures_printer.rb +37 -0
- data/lib/gorgon/g_logger.rb +22 -0
- data/lib/gorgon/host_state.rb +31 -0
- data/lib/gorgon/job.rb +26 -0
- data/lib/gorgon/job_definition.rb +24 -0
- data/lib/gorgon/job_state.rb +119 -0
- data/lib/gorgon/listener.rb +147 -0
- data/lib/gorgon/originator.rb +120 -0
- data/lib/gorgon/originator_logger.rb +36 -0
- data/lib/gorgon/originator_protocol.rb +65 -0
- data/lib/gorgon/pipe_manager.rb +55 -0
- data/lib/gorgon/progress_bar_view.rb +121 -0
- data/lib/gorgon/source_tree_syncer.rb +37 -0
- data/lib/gorgon/testunit_runner.rb +50 -0
- data/lib/gorgon/version.rb +3 -0
- data/lib/gorgon/worker.rb +103 -0
- data/lib/gorgon/worker_manager.rb +148 -0
- data/lib/gorgon/worker_watcher.rb +22 -0
- data/spec/callback_handler_spec.rb +77 -0
- data/spec/failures_printer_spec.rb +66 -0
- data/spec/host_state_spec.rb +65 -0
- data/spec/job_definition_spec.rb +20 -0
- data/spec/job_state_spec.rb +231 -0
- data/spec/listener_spec.rb +194 -0
- data/spec/originator_logger_spec.rb +40 -0
- data/spec/originator_protocol_spec.rb +134 -0
- data/spec/originator_spec.rb +134 -0
- data/spec/progress_bar_view_spec.rb +98 -0
- data/spec/source_tree_syncer_spec.rb +65 -0
- data/spec/worker_manager_spec.rb +23 -0
- data/spec/worker_spec.rb +114 -0
- metadata +270 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'gorgon/originator_logger'
|
2
|
+
|
3
|
+
describe OriginatorLogger do
|
4
|
+
before do
|
5
|
+
OriginatorLogger.any_instance.stub(:initialize_logger)
|
6
|
+
end
|
7
|
+
|
8
|
+
let (:originator_logger) { OriginatorLogger.new "" }
|
9
|
+
|
10
|
+
describe "#log_message" do
|
11
|
+
it "prints start messages" do
|
12
|
+
payload = {:action => "start",
|
13
|
+
:hostname => "host",
|
14
|
+
:filename => "filename"}
|
15
|
+
originator_logger.should_receive(:log).with("Started running 'filename' at 'host'")
|
16
|
+
originator_logger.log_message(payload)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "prints finish messages" do
|
20
|
+
payload = {:action => "finish",
|
21
|
+
:hostname => "host",
|
22
|
+
:filename => "filename"}
|
23
|
+
originator_logger.should_receive(:log).with("Finished running 'filename' at 'host'")
|
24
|
+
originator_logger.log_message(payload)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "prints failure messages when a test finishes with failures" do
|
28
|
+
payload = {:action => "finish",
|
29
|
+
:type => "fail",
|
30
|
+
:hostname => "host",
|
31
|
+
:filename => "filename",
|
32
|
+
:failures => [
|
33
|
+
"failure"
|
34
|
+
]}
|
35
|
+
|
36
|
+
originator_logger.should_receive(:log).with("Finished running 'filename' at 'host'failure\n")
|
37
|
+
originator_logger.log_message(payload)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'gorgon/originator_protocol'
|
2
|
+
|
3
|
+
describe OriginatorProtocol do
|
4
|
+
let(:connection) { stub("Connection", :disconnect => nil, :on_closed => nil)}
|
5
|
+
let(:queue) { stub("Queue", :bind => nil, :subscribe => nil, :name => "queue", :purge => nil,
|
6
|
+
:delete => nil) }
|
7
|
+
let(:exchange) { stub("Exchange", :publish => nil, :name => "exchange") }
|
8
|
+
let(:channel) { stub("Channel", :queue => queue, :direct => exchange, :fanout => exchange,
|
9
|
+
:default_exchange => exchange) }
|
10
|
+
let(:logger){ stub("Logger", :log => nil)}
|
11
|
+
|
12
|
+
before do
|
13
|
+
AMQP.stub!(:connect).and_return connection
|
14
|
+
AMQP::Channel.stub!(:new).and_return channel
|
15
|
+
@originator_p = OriginatorProtocol.new logger
|
16
|
+
@conn_information = {:host => "host"}
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#connect" do
|
20
|
+
it "opens AMQP connection" do
|
21
|
+
AMQP.should_receive(:connect).with(@conn_information)
|
22
|
+
@originator_p.connect(@conn_information)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "opens a new channel" do
|
26
|
+
AMQP::Channel.should_receive(:new).with(connection)
|
27
|
+
@originator_p.connect @conn_information
|
28
|
+
end
|
29
|
+
|
30
|
+
it "sets Connection#on_close callbacks" do
|
31
|
+
on_disconnect_called = false
|
32
|
+
on_disconnect = Proc.new { on_disconnect_called = true }
|
33
|
+
connection.should_receive(:on_closed).and_yield
|
34
|
+
|
35
|
+
@originator_p.connect @conn_information, :on_closed => on_disconnect
|
36
|
+
@originator_p.disconnect
|
37
|
+
on_disconnect_called.should be_true
|
38
|
+
end
|
39
|
+
|
40
|
+
it "opens a reply and exchange queue" do
|
41
|
+
UUIDTools::UUID.stub!(:timestamp_create).and_return 1
|
42
|
+
channel.should_receive(:queue).twice.with("1")
|
43
|
+
@originator_p.connect @conn_information
|
44
|
+
end
|
45
|
+
|
46
|
+
it "opens a reply exchange and binds reply queue to it" do
|
47
|
+
UUIDTools::UUID.stub!(:timestamp_create).and_return 1
|
48
|
+
channel.should_receive(:direct).with("1")
|
49
|
+
queue.should_receive(:bind).with(exchange)
|
50
|
+
@originator_p.connect @conn_information
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#publish_files" do
|
55
|
+
before do
|
56
|
+
@originator_p.connect @conn_information
|
57
|
+
end
|
58
|
+
|
59
|
+
it "publish each file using channel's default_exchange" do
|
60
|
+
files = ["file1", "file2"]
|
61
|
+
channel.should_receive(:default_exchange)
|
62
|
+
exchange.should_receive(:publish).once.ordered.with("file1", :routing_key => "queue")
|
63
|
+
exchange.should_receive(:publish).once.ordered.with("file2", :routing_key => "queue")
|
64
|
+
@originator_p.publish_files files
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#publish_job" do
|
69
|
+
before do
|
70
|
+
@originator_p.connect @conn_information
|
71
|
+
end
|
72
|
+
|
73
|
+
it "add queue's names to job_definition and fanout using 'gorgon.jobs' exchange" do
|
74
|
+
channel.should_receive(:fanout).with("gorgon.jobs")
|
75
|
+
exp_job_definition = JobDefinition.new
|
76
|
+
exp_job_definition.file_queue_name = "queue"
|
77
|
+
exp_job_definition.reply_exchange_name = "exchange"
|
78
|
+
|
79
|
+
exchange.should_receive(:publish).with(exp_job_definition.to_json)
|
80
|
+
@originator_p.publish_job JobDefinition.new
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "#receive_payloads" do
|
85
|
+
before do
|
86
|
+
@originator_p.connect @conn_information
|
87
|
+
end
|
88
|
+
|
89
|
+
it "subscribe to reply_queue and yield payload" do
|
90
|
+
payload = {:key => "info"}
|
91
|
+
queue.should_receive(:subscribe).and_yield(payload)
|
92
|
+
yielded = false
|
93
|
+
@originator_p.receive_payloads do |p|
|
94
|
+
yielded = true
|
95
|
+
p.should == payload
|
96
|
+
end
|
97
|
+
yielded.should be_true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "#cancel_job" do
|
102
|
+
before do
|
103
|
+
@originator_p.connect @conn_information
|
104
|
+
end
|
105
|
+
|
106
|
+
it "purges file_queue" do
|
107
|
+
queue.should_receive(:purge)
|
108
|
+
@originator_p.cancel_job
|
109
|
+
end
|
110
|
+
|
111
|
+
it "fanout 'cancel' message using 'gorgon.worker_managers' exchange" do
|
112
|
+
msg = Yajl::Encoder.encode({:action => "cancel_job"})
|
113
|
+
channel.should_receive(:fanout).with("gorgon.worker_managers")
|
114
|
+
exchange.should_receive(:publish).with(msg)
|
115
|
+
@originator_p.cancel_job
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#disconnect" do
|
120
|
+
before do
|
121
|
+
@originator_p.connect @conn_information
|
122
|
+
end
|
123
|
+
|
124
|
+
it "deletes reply and file queue" do
|
125
|
+
queue.should_receive(:delete).twice
|
126
|
+
@originator_p.disconnect
|
127
|
+
end
|
128
|
+
|
129
|
+
it "disconnects connection" do
|
130
|
+
connection.should_receive(:disconnect)
|
131
|
+
@originator_p.disconnect
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'gorgon/originator'
|
2
|
+
|
3
|
+
describe Originator do
|
4
|
+
let(:protocol){ stub("Originator Protocol", :connect => nil, :publish_files => nil,
|
5
|
+
:publish_job => nil, :receive_payloads => nil, :cancel_job => nil,
|
6
|
+
:disconnect => nil)}
|
7
|
+
|
8
|
+
let(:configuration){ {:files => ["some/file"]}}
|
9
|
+
let(:job_state){ stub("JobState", :is_job_complete? => false, :file_finished => nil,
|
10
|
+
:add_observer => nil)}
|
11
|
+
let(:progress_bar_view){ stub("Progress Bar View", :show => nil)}
|
12
|
+
let(:originator_logger){ stub("Originator Logger", :log => nil, :log_message => nil)}
|
13
|
+
|
14
|
+
before do
|
15
|
+
OriginatorLogger.stub(:new).and_return originator_logger
|
16
|
+
@originator = Originator.new
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#publish_job" do
|
20
|
+
before do
|
21
|
+
stub_methods
|
22
|
+
end
|
23
|
+
|
24
|
+
it "creates a JobState instance and passes total files" do
|
25
|
+
@originator.stub!(:files).and_return ["a file", "other file"]
|
26
|
+
JobState.should_receive(:new).with(2).and_return job_state
|
27
|
+
|
28
|
+
@originator.publish
|
29
|
+
end
|
30
|
+
|
31
|
+
it "creates a ProgressBarView and show" do
|
32
|
+
JobState.stub!(:new).and_return job_state
|
33
|
+
ProgressBarView.should_receive(:new).with(job_state).and_return progress_bar_view
|
34
|
+
progress_bar_view.should_receive(:show)
|
35
|
+
@originator.publish
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#cancel_job" do
|
40
|
+
before do
|
41
|
+
stub_methods
|
42
|
+
end
|
43
|
+
|
44
|
+
it "call JobState#cancel" do
|
45
|
+
JobState.stub!(:new).and_return job_state
|
46
|
+
job_state.should_receive(:cancel)
|
47
|
+
@originator.publish
|
48
|
+
@originator.cancel_job
|
49
|
+
end
|
50
|
+
|
51
|
+
it "tells @protocol to cancel job and disconnect" do
|
52
|
+
protocol.should_receive(:cancel_job)
|
53
|
+
protocol.should_receive(:disconnect)
|
54
|
+
@originator.publish
|
55
|
+
@originator.cancel_job
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#cleanup_if_job_complete" do
|
60
|
+
before do
|
61
|
+
stub_methods
|
62
|
+
JobState.stub!(:new).and_return job_state
|
63
|
+
@originator.publish
|
64
|
+
end
|
65
|
+
|
66
|
+
it "calls JobState#is_job_complete?" do
|
67
|
+
job_state.should_receive(:is_job_complete?).and_return false
|
68
|
+
@originator.cleanup_if_job_complete
|
69
|
+
end
|
70
|
+
|
71
|
+
it "disconnect if job is complete" do
|
72
|
+
job_state.stub!(:is_job_complete?).and_return true
|
73
|
+
protocol.should_receive(:disconnect)
|
74
|
+
@originator.cleanup_if_job_complete
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "#handle_reply" do
|
79
|
+
before do
|
80
|
+
stub_methods
|
81
|
+
JobState.stub!(:new).and_return job_state
|
82
|
+
@originator.publish
|
83
|
+
end
|
84
|
+
|
85
|
+
it "calls cleanup_if_job_complete" do
|
86
|
+
@originator.should_receive(:cleanup_if_job_complete)
|
87
|
+
@originator.handle_reply finish_payload
|
88
|
+
end
|
89
|
+
|
90
|
+
it "calls JobState#file_started if payload[:action] is 'start'" do
|
91
|
+
payload = Yajl::Parser.new(:symbolize_keys => true).parse(start_payload)
|
92
|
+
job_state.should_receive(:file_started)
|
93
|
+
@originator.handle_reply(start_payload)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "calls JobState#file_finished if payload[:action] is 'finish'" do
|
97
|
+
payload = Yajl::Parser.new(:symbolize_keys => true).parse(finish_payload)
|
98
|
+
job_state.should_receive(:file_finished).with(payload)
|
99
|
+
@originator.handle_reply(finish_payload)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def stub_methods
|
106
|
+
EventMachine.stub!(:run).and_yield
|
107
|
+
ProgressBarView.stub!(:new).and_return progress_bar_view
|
108
|
+
OriginatorProtocol.stub!(:new).and_return protocol
|
109
|
+
@originator.stub!(:configuration).and_return configuration
|
110
|
+
@originator.stub!(:connection_information).and_return 'host'
|
111
|
+
@originator.stub!(:job_definition).and_return JobDefinition.new
|
112
|
+
end
|
113
|
+
|
114
|
+
def start_payload
|
115
|
+
'{
|
116
|
+
"action": "start",
|
117
|
+
"hostname": "host",
|
118
|
+
"worker_id": "1",
|
119
|
+
"filename": "test/file_test.rb"
|
120
|
+
}'
|
121
|
+
end
|
122
|
+
|
123
|
+
def finish_payload
|
124
|
+
'{
|
125
|
+
"action": "finish",
|
126
|
+
"hostname": "host",
|
127
|
+
"worker_id": "1",
|
128
|
+
"filename": "test/file_test.rb",
|
129
|
+
"failures": [],
|
130
|
+
"type": "pass",
|
131
|
+
"time": 3
|
132
|
+
}'
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'gorgon/progress_bar_view'
|
2
|
+
require 'gorgon/job_state'
|
3
|
+
|
4
|
+
describe ProgressBarView do
|
5
|
+
describe "#initialize" do
|
6
|
+
it "adds itself to observers of job_state" do
|
7
|
+
job_state = JobState.new 1
|
8
|
+
job_state.should_receive(:add_observer)
|
9
|
+
ProgressBarView.new job_state
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#show" do
|
14
|
+
before do
|
15
|
+
job_state = JobState.new 1
|
16
|
+
@progress_bar_view = ProgressBarView.new job_state
|
17
|
+
end
|
18
|
+
|
19
|
+
it "prints a message in console saying that is loading workers" do
|
20
|
+
$stdout.should_receive(:write).with(/loading .*workers/i)
|
21
|
+
ProgressBar.should_not_receive(:create)
|
22
|
+
@progress_bar_view.show
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#update" do
|
27
|
+
let(:progress_bar) { stub("Progress Bar", :title= => nil, :progress= => nil, :format => nil,
|
28
|
+
:finished? => false)}
|
29
|
+
let(:payload) {
|
30
|
+
{ :filename => "path/file.rb",
|
31
|
+
:hostname => "host",
|
32
|
+
:failures => ["Failure messages"]}
|
33
|
+
}
|
34
|
+
|
35
|
+
before do
|
36
|
+
ProgressBar.stub!(:create).and_return progress_bar
|
37
|
+
@job_state = JobState.new 2
|
38
|
+
@job_state.stub!(:state).and_return :running
|
39
|
+
@progress_bar_view = ProgressBarView.new @job_state
|
40
|
+
$stdout.stub!(:write)
|
41
|
+
@progress_bar_view.show
|
42
|
+
end
|
43
|
+
|
44
|
+
it "doesn't create ProgressBar if JobState is not running" do
|
45
|
+
@job_state.should_receive(:state).and_return :starting
|
46
|
+
ProgressBar.should_not_receive(:create).with(hash_including(:total => 2))
|
47
|
+
@progress_bar_view.update
|
48
|
+
end
|
49
|
+
|
50
|
+
it "doesn't create a ProgressBar if one was already created" do
|
51
|
+
@progress_bar_view.update
|
52
|
+
ProgressBar.should_not_receive(:create).with(hash_including(:total => 2))
|
53
|
+
@progress_bar_view.update
|
54
|
+
end
|
55
|
+
|
56
|
+
it "gets total files from JobState and create a ProgressBar once JobState is running" do
|
57
|
+
ProgressBar.should_receive(:create).with(hash_including(:total => 2))
|
58
|
+
@progress_bar_view.update
|
59
|
+
end
|
60
|
+
|
61
|
+
it "gets finished_files_count" do
|
62
|
+
@job_state.should_receive :finished_files_count
|
63
|
+
@progress_bar_view.update
|
64
|
+
end
|
65
|
+
|
66
|
+
it "gets failed_files_count" do
|
67
|
+
@job_state.should_receive(:failed_files_count).and_return 0
|
68
|
+
@progress_bar_view.update
|
69
|
+
end
|
70
|
+
|
71
|
+
it "prints failures and finish progress_bar when job is done" do
|
72
|
+
@progress_bar_view.update
|
73
|
+
@job_state.stub!(:each_failed_test).and_yield(payload)
|
74
|
+
@job_state.stub!(:is_job_complete?).and_return :true
|
75
|
+
$stdout.should_receive(:write).with(/Failure messages/)
|
76
|
+
@progress_bar_view.update
|
77
|
+
end
|
78
|
+
|
79
|
+
context "when job is cancelled" do
|
80
|
+
before do
|
81
|
+
@progress_bar_view.update
|
82
|
+
@job_state.stub!(:is_job_cancelled?).and_return :true
|
83
|
+
end
|
84
|
+
|
85
|
+
it "prints failures and finish progress_bar when job is cancelled" do
|
86
|
+
@job_state.stub!(:each_failed_test).and_yield(payload)
|
87
|
+
$stdout.should_receive(:write).with(/Failure messages/)
|
88
|
+
@progress_bar_view.update
|
89
|
+
end
|
90
|
+
|
91
|
+
it "prints files that were running when the job was cancelled" do
|
92
|
+
@job_state.should_receive(:each_running_file).and_yield("hostname", "file1.rb")
|
93
|
+
$stdout.should_receive(:write).with(/file1\.rb.*hostname/)
|
94
|
+
@progress_bar_view.update
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'gorgon/source_tree_syncer'
|
2
|
+
|
3
|
+
describe SourceTreeSyncer.new("") do
|
4
|
+
it { should respond_to :exclude= }
|
5
|
+
it { should respond_to :sync }
|
6
|
+
it { should respond_to :sys_command }
|
7
|
+
it { should respond_to :remove_temp_dir }
|
8
|
+
|
9
|
+
describe "#sync" do
|
10
|
+
before do
|
11
|
+
@syncer = SourceTreeSyncer.new "path/to/source"
|
12
|
+
stub_utilities_methods
|
13
|
+
end
|
14
|
+
|
15
|
+
it "makes tempdir and changes current dir to temdir" do
|
16
|
+
Dir.should_receive(:mktmpdir).and_return("tmp/dir")
|
17
|
+
Dir.should_receive(:chdir).with("tmp/dir")
|
18
|
+
@syncer.sync
|
19
|
+
end
|
20
|
+
|
21
|
+
it "runs rsync system command with appropriate options" do
|
22
|
+
cmd = /rsync.*-az.*-r --rsh=ssh path\/to\/source\/\* \./
|
23
|
+
@syncer.should_receive(:system).with(cmd)
|
24
|
+
@syncer.sync
|
25
|
+
end
|
26
|
+
|
27
|
+
it "exclude files when they are specified" do
|
28
|
+
@syncer.exclude = ["log", ".git"]
|
29
|
+
@syncer.should_receive(:system).with(/--exclude log --exclude .git/)
|
30
|
+
@syncer.sync
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns true if sys command execution was successful" do
|
34
|
+
$?.stub!(:exitstatus).and_return 0
|
35
|
+
@syncer.sync.should be_true
|
36
|
+
end
|
37
|
+
|
38
|
+
it "returns false if sys command execution failed" do
|
39
|
+
$?.stub!(:exitstatus).and_return 1
|
40
|
+
@syncer.sync.should be_false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#remove_temp_dir" do
|
45
|
+
before do
|
46
|
+
@syncer = SourceTreeSyncer.new "path/to/source"
|
47
|
+
stub_utilities_methods
|
48
|
+
@syncer.sync
|
49
|
+
end
|
50
|
+
|
51
|
+
it "remove temporary dir" do
|
52
|
+
FileUtils.should_receive(:remove_entry_secure).with("tmp/dir")
|
53
|
+
@syncer.remove_temp_dir
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def stub_utilities_methods
|
60
|
+
Dir.stub!(:mktmpdir).and_return("tmp/dir")
|
61
|
+
Dir.stub!(:chdir)
|
62
|
+
FileUtils.stub!(:remove_entry_secure)
|
63
|
+
@syncer.stub!(:system)
|
64
|
+
end
|
65
|
+
end
|