gorgon 0.0.1
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/.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
|