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.
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