gorgon 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +52 -0
  4. data/README.md +53 -0
  5. data/Rakefile +1 -0
  6. data/bin/gorgon +41 -0
  7. data/gorgon.gemspec +33 -0
  8. data/lib/gorgon.rb +6 -0
  9. data/lib/gorgon/amqp_service.rb +39 -0
  10. data/lib/gorgon/callback_handler.rb +21 -0
  11. data/lib/gorgon/configuration.rb +9 -0
  12. data/lib/gorgon/failures_printer.rb +37 -0
  13. data/lib/gorgon/g_logger.rb +22 -0
  14. data/lib/gorgon/host_state.rb +31 -0
  15. data/lib/gorgon/job.rb +26 -0
  16. data/lib/gorgon/job_definition.rb +24 -0
  17. data/lib/gorgon/job_state.rb +119 -0
  18. data/lib/gorgon/listener.rb +147 -0
  19. data/lib/gorgon/originator.rb +120 -0
  20. data/lib/gorgon/originator_logger.rb +36 -0
  21. data/lib/gorgon/originator_protocol.rb +65 -0
  22. data/lib/gorgon/pipe_manager.rb +55 -0
  23. data/lib/gorgon/progress_bar_view.rb +121 -0
  24. data/lib/gorgon/source_tree_syncer.rb +37 -0
  25. data/lib/gorgon/testunit_runner.rb +50 -0
  26. data/lib/gorgon/version.rb +3 -0
  27. data/lib/gorgon/worker.rb +103 -0
  28. data/lib/gorgon/worker_manager.rb +148 -0
  29. data/lib/gorgon/worker_watcher.rb +22 -0
  30. data/spec/callback_handler_spec.rb +77 -0
  31. data/spec/failures_printer_spec.rb +66 -0
  32. data/spec/host_state_spec.rb +65 -0
  33. data/spec/job_definition_spec.rb +20 -0
  34. data/spec/job_state_spec.rb +231 -0
  35. data/spec/listener_spec.rb +194 -0
  36. data/spec/originator_logger_spec.rb +40 -0
  37. data/spec/originator_protocol_spec.rb +134 -0
  38. data/spec/originator_spec.rb +134 -0
  39. data/spec/progress_bar_view_spec.rb +98 -0
  40. data/spec/source_tree_syncer_spec.rb +65 -0
  41. data/spec/worker_manager_spec.rb +23 -0
  42. data/spec/worker_spec.rb +114 -0
  43. 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