gorgon 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/bin/gorgon +10 -0
- data/lib/gorgon/job_state.rb +9 -2
- data/lib/gorgon/listener.rb +22 -14
- data/lib/gorgon/originator.rb +15 -1
- data/lib/gorgon/originator_logger.rb +3 -0
- data/lib/gorgon/progress_bar_view.rb +15 -0
- data/lib/gorgon/source_tree_syncer.rb +16 -5
- data/lib/gorgon/version.rb +1 -1
- data/lib/gorgon/worker.rb +16 -9
- data/lib/gorgon/worker_manager.rb +1 -1
- data/spec/job_state_spec.rb +17 -0
- data/spec/listener_spec.rb +47 -10
- data/spec/originator_spec.rb +28 -1
- data/spec/progress_bar_view_spec.rb +13 -1
- data/spec/source_tree_syncer_spec.rb +73 -17
- metadata +2 -2
data/Gemfile.lock
CHANGED
data/bin/gorgon
CHANGED
@@ -3,6 +3,8 @@ require 'gorgon/originator'
|
|
3
3
|
require 'gorgon/listener'
|
4
4
|
require 'gorgon/worker_manager'
|
5
5
|
|
6
|
+
WELCOME_MSG = "Welcome to Gorgon #{Gorgon::VERSION}"
|
7
|
+
|
6
8
|
def start
|
7
9
|
o = Originator.new
|
8
10
|
o.originate
|
@@ -25,8 +27,12 @@ end
|
|
25
27
|
|
26
28
|
def usage
|
27
29
|
#print instructions on how to use gorgon
|
30
|
+
puts "\tstart - remotely runs all tests specified in gorgon.json"
|
31
|
+
puts "\tlisten - starts a listener process using the settings in gorgon_listener.json"
|
28
32
|
end
|
29
33
|
|
34
|
+
puts WELCOME_MSG
|
35
|
+
|
30
36
|
case ARGV[0]
|
31
37
|
when nil
|
32
38
|
start
|
@@ -36,6 +42,10 @@ when "listen"
|
|
36
42
|
listen
|
37
43
|
when "manage_workers"
|
38
44
|
manage_workers
|
45
|
+
when "help"
|
46
|
+
usage
|
39
47
|
else
|
48
|
+
puts "Unknown command!"
|
40
49
|
usage
|
50
|
+
exit 1
|
41
51
|
end
|
data/lib/gorgon/job_state.rb
CHANGED
@@ -5,12 +5,13 @@ require 'observer'
|
|
5
5
|
class JobState
|
6
6
|
include Observable
|
7
7
|
|
8
|
-
attr_reader :total_files, :remaining_files_count, :state
|
8
|
+
attr_reader :total_files, :remaining_files_count, :state, :crashed_hosts
|
9
9
|
|
10
10
|
def initialize total_files
|
11
11
|
@total_files = total_files
|
12
12
|
@remaining_files_count = total_files
|
13
13
|
@failed_tests = []
|
14
|
+
@crashed_hosts = []
|
14
15
|
@hosts = {}
|
15
16
|
|
16
17
|
if @remaining_files_count > 0
|
@@ -55,6 +56,12 @@ class JobState
|
|
55
56
|
notify_observers payload
|
56
57
|
end
|
57
58
|
|
59
|
+
def gorgon_crash_message payload
|
60
|
+
@crashed_hosts << payload[:hostname]
|
61
|
+
changed
|
62
|
+
notify_observers payload
|
63
|
+
end
|
64
|
+
|
58
65
|
def cancel
|
59
66
|
@remaining_files_count = 0
|
60
67
|
@state = :cancelled
|
@@ -114,6 +121,6 @@ class JobState
|
|
114
121
|
end
|
115
122
|
|
116
123
|
def failed_test? payload
|
117
|
-
payload[:type] == "fail"
|
124
|
+
payload[:type] == "fail" || payload[:type] == "crash"
|
118
125
|
end
|
119
126
|
end
|
data/lib/gorgon/listener.rb
CHANGED
@@ -10,6 +10,7 @@ require "awesome_print"
|
|
10
10
|
require "open4"
|
11
11
|
require "tmpdir"
|
12
12
|
require "socket"
|
13
|
+
require "bundler"
|
13
14
|
|
14
15
|
class Listener
|
15
16
|
include Configuration
|
@@ -25,6 +26,7 @@ class Listener
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def listen
|
29
|
+
at_exit_hook
|
28
30
|
log "Waiting for jobs..."
|
29
31
|
while true
|
30
32
|
sleep 2 unless poll
|
@@ -61,7 +63,7 @@ class Listener
|
|
61
63
|
@callback_handler = CallbackHandler.new(@job_definition.callbacks)
|
62
64
|
copy_source_tree(@job_definition.source_tree_path, @job_definition.sync_exclude)
|
63
65
|
|
64
|
-
if !run_after_sync
|
66
|
+
if !@syncer.success? || !run_after_sync
|
65
67
|
clean_up
|
66
68
|
return
|
67
69
|
end
|
@@ -73,10 +75,14 @@ class Listener
|
|
73
75
|
clean_up
|
74
76
|
end
|
75
77
|
|
78
|
+
def at_exit_hook
|
79
|
+
at_exit { log "Listener will exit!"}
|
80
|
+
end
|
81
|
+
|
76
82
|
private
|
77
83
|
|
78
84
|
def run_after_sync
|
79
|
-
log "Running after_sync callback"
|
85
|
+
log "Running after_sync callback..."
|
80
86
|
begin
|
81
87
|
@callback_handler.after_sync
|
82
88
|
rescue Exception => e
|
@@ -84,7 +90,7 @@ class Listener
|
|
84
90
|
log_error e.message
|
85
91
|
log_error "\n" + e.backtrace.join("\n")
|
86
92
|
|
87
|
-
reply = {:type => :
|
93
|
+
reply = {:type => :exception,
|
88
94
|
:hostname => Socket.gethostname,
|
89
95
|
:message => "after_sync callback failed. Please, check your script in #{@job_definition.callbacks[:after_sync]}. Message: #{e.message}",
|
90
96
|
:backtrace => e.backtrace.join("\n")
|
@@ -99,14 +105,14 @@ class Listener
|
|
99
105
|
log "Downloading source tree to temp directory..."
|
100
106
|
@syncer = SourceTreeSyncer.new source_tree_path
|
101
107
|
@syncer.exclude = exclude
|
102
|
-
|
108
|
+
@syncer.sync
|
109
|
+
if @syncer.success?
|
103
110
|
log "Command '#{@syncer.sys_command}' completed successfully."
|
104
111
|
else
|
105
|
-
|
106
|
-
# - Discard job
|
107
|
-
# - Let the originator know about the error
|
108
|
-
# - Wait for the next job
|
112
|
+
send_crash_message @syncer.output, @syncer.errors
|
109
113
|
log_error "Command '#{@syncer.sys_command}' failed!"
|
114
|
+
log_error "Stdout:\n#{@syncer.output}"
|
115
|
+
log_error "Stderr:\n#{@syncer.errors}"
|
110
116
|
end
|
111
117
|
end
|
112
118
|
|
@@ -115,7 +121,7 @@ class Listener
|
|
115
121
|
end
|
116
122
|
|
117
123
|
def fork_worker_manager
|
118
|
-
log "Forking Worker Manager"
|
124
|
+
log "Forking Worker Manager..."
|
119
125
|
ENV["GORGON_CONFIG_PATH"] = @listener_config_filename
|
120
126
|
pid, stdin, stdout, stderr = Open4::popen4 "bundle exec gorgon manage_workers"
|
121
127
|
stdin.write(@job_definition.to_json)
|
@@ -129,11 +135,7 @@ class Listener
|
|
129
135
|
error_msg = stderr.read
|
130
136
|
log_error "ERROR MSG: #{error_msg}"
|
131
137
|
|
132
|
-
|
133
|
-
:hostname => Socket.gethostname,
|
134
|
-
:stdout => stdout.read,
|
135
|
-
:stderr => error_msg}
|
136
|
-
@reply_exchange.publish(Yajl::Encoder.encode(reply))
|
138
|
+
send_crash_message stdout.read, error_msg
|
137
139
|
end
|
138
140
|
end
|
139
141
|
|
@@ -144,4 +146,10 @@ class Listener
|
|
144
146
|
def configuration
|
145
147
|
@configuration ||= load_configuration_from_file("gorgon_listener.json")
|
146
148
|
end
|
149
|
+
|
150
|
+
def send_crash_message output, error
|
151
|
+
reply = {:type => :crash, :hostname => Socket.gethostname,
|
152
|
+
:stdout => output, :stderr => error}
|
153
|
+
@reply_exchange.publish(Yajl::Encoder.encode(reply))
|
154
|
+
end
|
147
155
|
end
|
data/lib/gorgon/originator.rb
CHANGED
@@ -6,6 +6,8 @@ require 'gorgon/originator_logger'
|
|
6
6
|
require 'gorgon/failures_printer'
|
7
7
|
|
8
8
|
require 'awesome_print'
|
9
|
+
require 'etc'
|
10
|
+
require 'socket'
|
9
11
|
|
10
12
|
class Originator
|
11
13
|
include Configuration
|
@@ -81,7 +83,15 @@ class Originator
|
|
81
83
|
@job_state.file_finished payload
|
82
84
|
elsif payload[:action] == "start"
|
83
85
|
@job_state.file_started payload
|
86
|
+
elsif payload[:type] == "crash"
|
87
|
+
@job_state.gorgon_crash_message payload
|
88
|
+
elsif payload[:type] == "exception"
|
89
|
+
# TODO
|
90
|
+
ap payload
|
91
|
+
else
|
92
|
+
ap payload
|
84
93
|
end
|
94
|
+
|
85
95
|
@logger.log_message payload
|
86
96
|
# Uncomment this to see each message received by originator
|
87
97
|
# ap payload
|
@@ -111,7 +121,11 @@ class Originator
|
|
111
121
|
end
|
112
122
|
|
113
123
|
def job_definition
|
114
|
-
|
124
|
+
job_config = configuration[:job]
|
125
|
+
if !job_config.has_key?(:source_tree_path)
|
126
|
+
job_config[:source_tree_path] = "#{Etc.getlogin}@#{Socket.gethostname}:#{Dir.pwd}"
|
127
|
+
end
|
128
|
+
JobDefinition.new(configuration[:job])
|
115
129
|
end
|
116
130
|
|
117
131
|
def configuration
|
@@ -12,6 +12,9 @@ class OriginatorLogger
|
|
12
12
|
log("Started running '#{payload[:filename]}' at '#{payload[:hostname]}'")
|
13
13
|
elsif payload[:action] == "finish"
|
14
14
|
print_finish(payload)
|
15
|
+
elsif payload[:type] == "crash" || payload[:type] == "exception"
|
16
|
+
# TODO: improve logging of these messages
|
17
|
+
log(payload)
|
15
18
|
else # to be removed
|
16
19
|
ap payload
|
17
20
|
end
|
@@ -20,6 +20,8 @@ class ProgressBarView
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def update payload={}
|
23
|
+
output_gorgon_crash_message payload if gorgon_crashed? payload
|
24
|
+
|
23
25
|
create_progress_bar_if_started_job_running
|
24
26
|
|
25
27
|
return if @progress_bar.nil? || @finished
|
@@ -50,6 +52,19 @@ class ProgressBarView
|
|
50
52
|
end
|
51
53
|
|
52
54
|
private
|
55
|
+
def gorgon_crashed? payload
|
56
|
+
payload[:type] == "crash" && payload[:action] != "finish"
|
57
|
+
end
|
58
|
+
|
59
|
+
def output_gorgon_crash_message payload
|
60
|
+
$stderr.puts "\nA #{'crash'.red} occured at '#{payload[:hostname].colorize HOST_COLOR}':"
|
61
|
+
$stderr.puts payload[:stdout].yellow unless payload[:stdout].to_s.strip.length == 0
|
62
|
+
$stderr.puts payload[:stderr].yellow unless payload[:stderr].to_s.strip.length == 0
|
63
|
+
if @progress_bar.nil?
|
64
|
+
print LOADING_MSG # if still loading, print msg so user won't think the whole job crashed
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
53
68
|
def format colors
|
54
69
|
# TODO: decide what bar to use
|
55
70
|
# bar = "%b>%i".colorize(colors[:bar])
|
@@ -1,9 +1,11 @@
|
|
1
|
+
require 'open4'
|
2
|
+
|
1
3
|
class SourceTreeSyncer
|
2
4
|
attr_accessor :exclude
|
3
|
-
attr_reader :sys_command
|
5
|
+
attr_reader :sys_command, :output, :errors
|
4
6
|
|
5
7
|
SYS_COMMAND = 'rsync'
|
6
|
-
OPTS = '-
|
8
|
+
OPTS = "-azr --timeout=5 --rsh='ssh -o NumberOfPasswordPrompts=0 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'"
|
7
9
|
EXCLUDE_OPT = "--exclude"
|
8
10
|
|
9
11
|
def initialize source_tree_path
|
@@ -16,10 +18,19 @@ class SourceTreeSyncer
|
|
16
18
|
Dir.chdir(@tempdir)
|
17
19
|
|
18
20
|
exclude_opt = build_exclude_opt
|
19
|
-
@sys_command = "#{SYS_COMMAND} #{OPTS} #{exclude_opt}
|
20
|
-
|
21
|
+
@sys_command = "#{SYS_COMMAND} #{OPTS} #{exclude_opt} #{@source_tree_path}/ ."
|
22
|
+
|
23
|
+
pid, stdin, stdout, stderr = Open4::popen4 @sys_command
|
24
|
+
stdin.close
|
25
|
+
|
26
|
+
@output, @errors = [stdout, stderr].map { |p| begin p.read ensure p.close end }
|
27
|
+
|
28
|
+
ignore, status = Process.waitpid2 pid
|
29
|
+
@exitstatus = status.exitstatus
|
30
|
+
end
|
21
31
|
|
22
|
-
|
32
|
+
def success?
|
33
|
+
@exitstatus == 0
|
23
34
|
end
|
24
35
|
|
25
36
|
def remove_temp_dir
|
data/lib/gorgon/version.rb
CHANGED
data/lib/gorgon/worker.rb
CHANGED
@@ -11,14 +11,20 @@ module WorkUnit
|
|
11
11
|
def self.run_file filename
|
12
12
|
require "gorgon/testunit_runner"
|
13
13
|
start_t = Time.now
|
14
|
-
results = TestRunner.run_file(filename)
|
15
|
-
length = Time.now - start_t
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
15
|
+
begin
|
16
|
+
failures = TestRunner.run_file(filename)
|
17
|
+
length = Time.now - start_t
|
18
|
+
|
19
|
+
if failures.empty?
|
20
|
+
results = {:failures => [], :type => :pass, :time => length}
|
21
|
+
else
|
22
|
+
results = {:failures => failures, :type => :fail, :time => length}
|
23
|
+
end
|
24
|
+
rescue Exception => e
|
25
|
+
results = {:failures => ["Exception: #{e.message}\n#{e.backtrace.join("\n")}"], :type => :crash, :time => (Time.now - start_t)}
|
21
26
|
end
|
27
|
+
return results
|
22
28
|
end
|
23
29
|
end
|
24
30
|
|
@@ -64,9 +70,10 @@ class Worker
|
|
64
70
|
end
|
65
71
|
|
66
72
|
def work
|
67
|
-
log "Running before_start callback"
|
73
|
+
log "Running before_start callback..."
|
68
74
|
@callback_handler.before_start
|
69
75
|
|
76
|
+
log "Running files ..."
|
70
77
|
@amqp.start_worker @file_queue_name, @reply_exchange_name do |queue, exchange|
|
71
78
|
while filename = queue.pop
|
72
79
|
exchange.publish make_start_message(filename)
|
@@ -74,8 +81,8 @@ class Worker
|
|
74
81
|
exchange.publish make_finish_message(filename, test_results)
|
75
82
|
end
|
76
83
|
end
|
77
|
-
|
78
|
-
|
84
|
+
ensure # this 'ensure' that we run after_complete even after an 'INT' signal
|
85
|
+
clean_up
|
79
86
|
end
|
80
87
|
|
81
88
|
private
|
@@ -90,7 +90,7 @@ class WorkerManager
|
|
90
90
|
:hostname => Socket.gethostname,
|
91
91
|
:stdout => stdout.read,
|
92
92
|
:stderr => error_msg}
|
93
|
-
@
|
93
|
+
@reply_exchange.publish(Yajl::Encoder.encode(reply))
|
94
94
|
# TODO: find a way to stop the whole system when a worker crashes or do something more clever
|
95
95
|
rescue Exception => e
|
96
96
|
log_error "Exception raised when trying to report crash to originator:"
|
data/spec/job_state_spec.rb
CHANGED
@@ -13,6 +13,7 @@ describe JobState do
|
|
13
13
|
it { should respond_to :finished_files_count }
|
14
14
|
it { should respond_to(:file_started).with(1).argument }
|
15
15
|
it { should respond_to(:file_finished).with(1).argument }
|
16
|
+
it { should respond_to(:gorgon_crash_message).with(1).argument }
|
16
17
|
it { should respond_to :cancel }
|
17
18
|
it { should respond_to :each_failed_test }
|
18
19
|
it { should respond_to :each_running_file }
|
@@ -141,6 +142,22 @@ describe JobState do
|
|
141
142
|
end
|
142
143
|
end
|
143
144
|
|
145
|
+
describe "#gorgon_crash_message" do
|
146
|
+
let(:crash_msg) {{:type => "crash", :hostname => "host",
|
147
|
+
:stdout => "some output", :stderr => "some errors"}}
|
148
|
+
|
149
|
+
it "adds crashed host to JobState#crashed_hosted" do
|
150
|
+
@job_state.gorgon_crash_message(crash_msg)
|
151
|
+
@job_state.crashed_hosts.should == ["host"]
|
152
|
+
end
|
153
|
+
|
154
|
+
it "notify observers" do
|
155
|
+
@job_state.should_receive :notify_observers
|
156
|
+
@job_state.should_receive :changed
|
157
|
+
@job_state.gorgon_crash_message crash_msg
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
144
161
|
describe "#is_job_complete?" do
|
145
162
|
it "returns false if remaining_files_count != 0" do
|
146
163
|
@job_state.is_job_complete?.should be_false
|
data/spec/listener_spec.rb
CHANGED
@@ -3,15 +3,15 @@ require 'gorgon/listener'
|
|
3
3
|
describe Listener do
|
4
4
|
let(:connection_information) { double }
|
5
5
|
let(:queue) { stub("Bunny Queue", :bind => nil) }
|
6
|
-
let(:exchange) { stub("Bunny Exchange") }
|
6
|
+
let(:exchange) { stub("Bunny Exchange", :publish => nil) }
|
7
7
|
let(:bunny) { stub("Bunny", :start => nil, :queue => queue, :exchange => exchange) }
|
8
|
+
let(:logger) { stub("Logger", :info => true, :datetime_format= => "")}
|
8
9
|
|
9
10
|
before do
|
11
|
+
Logger.stub(:new).and_return(logger)
|
10
12
|
Bunny.stub(:new).and_return(bunny)
|
11
13
|
Listener.any_instance.stub(:configuration => {})
|
12
14
|
Listener.any_instance.stub(:connection_information => connection_information)
|
13
|
-
@stub_logger = stub :info => true, :datetime_format= => ""
|
14
|
-
Logger.stub(:new).and_return(@stub_logger)
|
15
15
|
end
|
16
16
|
|
17
17
|
describe "initialization" do
|
@@ -43,7 +43,7 @@ describe Listener do
|
|
43
43
|
end
|
44
44
|
|
45
45
|
it "should log to 'log_file'" do
|
46
|
-
|
46
|
+
logger.should_receive(:info).with("Listener initialized")
|
47
47
|
|
48
48
|
Listener.new
|
49
49
|
end
|
@@ -63,7 +63,7 @@ describe Listener do
|
|
63
63
|
context "without specifying a log file path" do
|
64
64
|
it "should not log" do
|
65
65
|
Logger.should_not_receive(:new)
|
66
|
-
|
66
|
+
logger.should_not_receive(:info)
|
67
67
|
|
68
68
|
Listener.new
|
69
69
|
end
|
@@ -141,26 +141,62 @@ describe Listener do
|
|
141
141
|
:sync_exclude => ["log"], :callbacks => {:a_callback => "path/to/callback"}
|
142
142
|
}}
|
143
143
|
|
144
|
-
let(:syncer) { stub("SourceTreeSyncer", :sync => nil, :exclude= => nil,
|
144
|
+
let(:syncer) { stub("SourceTreeSyncer", :sync => nil, :exclude= => nil, :success? => true,
|
145
|
+
:output => "some output", :errors => "some errors",
|
145
146
|
:remove_temp_dir => nil, :sys_command => "rsync ...")}
|
146
|
-
|
147
|
-
let(:io) { stub("IO object", :write => nil, :close => nil)}
|
148
147
|
let(:process_status) { stub("Process Status", :exitstatus => 0)}
|
149
148
|
let(:callback_handler) { stub("Callback Handler", :after_sync => nil) }
|
149
|
+
let(:stdin) { stub("IO object", :write => nil, :close => nil)}
|
150
|
+
let(:stdout) { stub("IO object", :read => nil, :close => nil)}
|
151
|
+
let(:stderr) { stub("IO object", :read => nil, :close => nil)}
|
150
152
|
|
151
153
|
before do
|
154
|
+
stub_classes
|
152
155
|
@listener = Listener.new
|
153
156
|
@json_payload = Yajl::Encoder.encode(payload)
|
154
|
-
stub_classes
|
155
157
|
end
|
156
158
|
|
157
159
|
it "copy source tree" do
|
158
160
|
SourceTreeSyncer.should_receive(:new).once.with("path/to/source").and_return syncer
|
159
161
|
syncer.should_receive(:exclude=).with(["log"])
|
160
162
|
syncer.should_receive(:sync)
|
163
|
+
syncer.should_receive(:success?).and_return(true)
|
161
164
|
@listener.run_job(@json_payload)
|
162
165
|
end
|
163
166
|
|
167
|
+
context "syncer#sync fails" do
|
168
|
+
before do
|
169
|
+
syncer.stub!(:success?).and_return false
|
170
|
+
syncer.stub!(:output).and_return "some output"
|
171
|
+
syncer.stub!(:errors).and_return "some errors"
|
172
|
+
end
|
173
|
+
|
174
|
+
it "aborts current job" do
|
175
|
+
callback_handler.should_not_receive(:after_sync)
|
176
|
+
@listener.run_job(@json_payload)
|
177
|
+
end
|
178
|
+
|
179
|
+
it "sends message to originator with output and errors from syncer" do
|
180
|
+
reply = {:type => :crash, :hostname => "hostname", :stdout => "some output", :stderr => "some errors"}
|
181
|
+
exchange.should_receive(:publish).with(Yajl::Encoder.encode(reply))
|
182
|
+
@listener.run_job(@json_payload)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
context "Worker Manager crahes" do
|
187
|
+
before do
|
188
|
+
process_status.should_receive(:exitstatus).and_return 1
|
189
|
+
end
|
190
|
+
|
191
|
+
it "sends message to originator with output and errors from worker manager" do
|
192
|
+
stdout.should_receive(:read).and_return "some output"
|
193
|
+
stderr.should_receive(:read).and_return "some errors"
|
194
|
+
reply = {:type => :crash, :hostname => "hostname", :stdout => "some output", :stderr => "some errors"}
|
195
|
+
exchange.should_receive(:publish).with(Yajl::Encoder.encode(reply))
|
196
|
+
@listener.run_job(@json_payload)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
164
200
|
it "remove temp source directory when complete" do
|
165
201
|
syncer.should_receive(:remove_temp_dir)
|
166
202
|
@listener.run_job(@json_payload)
|
@@ -187,8 +223,9 @@ describe Listener do
|
|
187
223
|
def stub_classes
|
188
224
|
SourceTreeSyncer.stub!(:new).and_return syncer
|
189
225
|
CallbackHandler.stub!(:new).and_return callback_handler
|
190
|
-
Open4.stub!(:popen4).and_return([1,
|
226
|
+
Open4.stub!(:popen4).and_return([1, stdin, stdout, stderr])
|
191
227
|
Process.stub!(:waitpid2).and_return([0, process_status])
|
228
|
+
Socket.stub!(:gethostname).and_return("hostname")
|
192
229
|
end
|
193
230
|
end
|
194
231
|
end
|
data/spec/originator_spec.rb
CHANGED
@@ -5,7 +5,7 @@ describe Originator do
|
|
5
5
|
:publish_job => nil, :receive_payloads => nil, :cancel_job => nil,
|
6
6
|
:disconnect => nil)}
|
7
7
|
|
8
|
-
let(:configuration){ {:files => ["some/file"]}}
|
8
|
+
let(:configuration){ {:job => {}, :files => ["some/file"]}}
|
9
9
|
let(:job_state){ stub("JobState", :is_job_complete? => false, :file_finished => nil,
|
10
10
|
:add_observer => nil)}
|
11
11
|
let(:progress_bar_view){ stub("Progress Bar View", :show => nil)}
|
@@ -98,6 +98,33 @@ describe Originator do
|
|
98
98
|
job_state.should_receive(:file_finished).with(payload)
|
99
99
|
@originator.handle_reply(finish_payload)
|
100
100
|
end
|
101
|
+
|
102
|
+
let(:gorgon_crash_message) {{:type => "crash", :hostname => "host",
|
103
|
+
:stdout => "some output", :stderr => "some errors"}}
|
104
|
+
|
105
|
+
it "calls JobState#gorgon_crash_message if payload[:type] is 'crash'" do
|
106
|
+
job_state.should_receive(:gorgon_crash_message).with(gorgon_crash_message)
|
107
|
+
@originator.handle_reply(Yajl::Encoder.encode(gorgon_crash_message))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "#job_definition" do
|
112
|
+
it "returns a JobDefinition object" do
|
113
|
+
@originator.stub!(:configuration).and_return configuration
|
114
|
+
job_definition = JobDefinition.new
|
115
|
+
JobDefinition.should_receive(:new).and_return job_definition
|
116
|
+
@originator.job_definition.should equal job_definition
|
117
|
+
end
|
118
|
+
|
119
|
+
it "builds source_tree_path if it was not specified in the configuration" do
|
120
|
+
@originator.stub!(:configuration).and_return({:job => {}})
|
121
|
+
@originator.job_definition.source_tree_path.should == "#{Etc.getlogin}@#{Socket.gethostname}:#{Dir.pwd}"
|
122
|
+
end
|
123
|
+
|
124
|
+
it "returns source_tree_path specified in configuration if it is present" do
|
125
|
+
@originator.stub!(:configuration).and_return({:job => {:source_tree_path => "login@host:path/to/dir"}})
|
126
|
+
@originator.job_definition.source_tree_path.should == "login@host:path/to/dir"
|
127
|
+
end
|
101
128
|
end
|
102
129
|
|
103
130
|
private
|
@@ -16,7 +16,7 @@ describe ProgressBarView do
|
|
16
16
|
@progress_bar_view = ProgressBarView.new job_state
|
17
17
|
end
|
18
18
|
|
19
|
-
it "prints
|
19
|
+
it "prints in console gorgon's version and that is loading workers" do
|
20
20
|
$stdout.should_receive(:write).with(/loading .*workers/i)
|
21
21
|
ProgressBar.should_not_receive(:create)
|
22
22
|
@progress_bar_view.show
|
@@ -94,5 +94,17 @@ describe ProgressBarView do
|
|
94
94
|
@progress_bar_view.update
|
95
95
|
end
|
96
96
|
end
|
97
|
+
|
98
|
+
context "when payload is a crash message" do
|
99
|
+
let(:crash_message) {{:type => "crash", :hostname => "host",
|
100
|
+
:stdout => "some output", :stderr => "some errors"}}
|
101
|
+
it "prints info about crash in standard error" do
|
102
|
+
$stderr.stub!(:write)
|
103
|
+
$stderr.should_receive(:write).with(/crash.*host/i)
|
104
|
+
$stderr.should_receive(:write).with(/some output/i)
|
105
|
+
$stderr.should_receive(:write).with(/some errors/i)
|
106
|
+
@progress_bar_view.update crash_message
|
107
|
+
end
|
108
|
+
end
|
97
109
|
end
|
98
110
|
end
|
@@ -5,39 +5,93 @@ describe SourceTreeSyncer.new("") do
|
|
5
5
|
it { should respond_to :sync }
|
6
6
|
it { should respond_to :sys_command }
|
7
7
|
it { should respond_to :remove_temp_dir }
|
8
|
+
it { should respond_to :success? }
|
9
|
+
it { should respond_to :output }
|
10
|
+
it { should respond_to :errors }
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
12
|
+
let(:stdin) { stub("IO object", :close => nil)}
|
13
|
+
let(:stdout) { stub("IO object", :read => nil, :close => nil)}
|
14
|
+
let(:stderr) { stub("IO object", :read => nil, :close => nil)}
|
15
|
+
let(:status) { stub("Process Status", :exitstatus => 0)}
|
14
16
|
|
17
|
+
before do
|
18
|
+
@syncer = SourceTreeSyncer.new "path/to/source"
|
19
|
+
stub_utilities_methods
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#sync" do
|
15
23
|
it "makes tempdir and changes current dir to temdir" do
|
16
24
|
Dir.should_receive(:mktmpdir).and_return("tmp/dir")
|
17
25
|
Dir.should_receive(:chdir).with("tmp/dir")
|
18
26
|
@syncer.sync
|
19
27
|
end
|
20
28
|
|
21
|
-
|
22
|
-
|
23
|
-
|
29
|
+
context "options" do
|
30
|
+
it "runs rsync system command with appropriate options" do
|
31
|
+
cmd = /rsync.*-azr .*path\/to\/source\/\ \./
|
32
|
+
Open4.should_receive(:popen4).with(cmd)
|
33
|
+
@syncer.sync
|
34
|
+
end
|
35
|
+
|
36
|
+
it "exclude files when they are specified" do
|
37
|
+
@syncer.exclude = ["log", ".git"]
|
38
|
+
Open4.should_receive(:popen4).with(/--exclude log --exclude .git/)
|
39
|
+
@syncer.sync
|
40
|
+
end
|
41
|
+
|
42
|
+
it "use NumberOfPasswordPrompts 0 as ssh option to avoid password prompts that will hang the listener" do
|
43
|
+
opt = /--rsh='ssh .*-o NumberOfPasswordPrompts=0.*'/
|
44
|
+
Open4.should_receive(:popen4).with(opt)
|
45
|
+
@syncer.sync
|
46
|
+
end
|
47
|
+
|
48
|
+
it "set UserKnownHostsFile to /dev/null so we avoid hosts id changes and eavesdropping warnings in futures connections" do
|
49
|
+
opt = /ssh .*-o UserKnownHostsFile=\/dev\/null/
|
50
|
+
Open4.should_receive(:popen4).with(opt)
|
51
|
+
@syncer.sync
|
52
|
+
end
|
53
|
+
|
54
|
+
it "set StrictHostKeyChecking to 'no' to avoid confirmation prompt of connection to unkown host" do
|
55
|
+
opt = /ssh .*-o StrictHostKeyChecking=no/
|
56
|
+
Open4.should_receive(:popen4).with(opt)
|
57
|
+
@syncer.sync
|
58
|
+
end
|
59
|
+
|
60
|
+
it "uses io timeout to avoid listener hanging forever in case rsync asks for any input" do
|
61
|
+
opt = /--timeout=5/
|
62
|
+
Open4.should_receive(:popen4).with(opt)
|
63
|
+
@syncer.sync
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#success?" do
|
69
|
+
it "returns true if sync execution was successful" do
|
70
|
+
status.should_receive(:exitstatus).and_return(0)
|
24
71
|
@syncer.sync
|
72
|
+
@syncer.success?.should be_true
|
25
73
|
end
|
26
74
|
|
27
|
-
it "
|
28
|
-
|
29
|
-
@syncer.should_receive(:system).with(/--exclude log --exclude .git/)
|
75
|
+
it "returns false if sync execution failed" do
|
76
|
+
status.should_receive(:exitstatus).and_return(1)
|
30
77
|
@syncer.sync
|
78
|
+
@syncer.success?.should be_false
|
31
79
|
end
|
80
|
+
end
|
32
81
|
|
33
|
-
|
34
|
-
|
35
|
-
|
82
|
+
describe "#output" do
|
83
|
+
it "returns standard output of rsync" do
|
84
|
+
stdout.should_receive(:read).and_return("some output")
|
85
|
+
@syncer.sync
|
86
|
+
@syncer.output.should == "some output"
|
36
87
|
end
|
88
|
+
end
|
37
89
|
|
38
|
-
|
39
|
-
|
40
|
-
|
90
|
+
describe "#errors" do
|
91
|
+
it "returns standard error output of rsync" do
|
92
|
+
stderr.should_receive(:read).and_return("some errors")
|
93
|
+
@syncer.sync
|
94
|
+
@syncer.errors.should == "some errors"
|
41
95
|
end
|
42
96
|
end
|
43
97
|
|
@@ -59,6 +113,8 @@ describe SourceTreeSyncer.new("") do
|
|
59
113
|
def stub_utilities_methods
|
60
114
|
Dir.stub!(:mktmpdir).and_return("tmp/dir")
|
61
115
|
Dir.stub!(:chdir)
|
116
|
+
Open4.stub!(:popen4).and_return([1, stdin, stdout, stderr])
|
117
|
+
Process.stub!(:waitpid2).and_return([nil, status])
|
62
118
|
FileUtils.stub!(:remove_entry_secure)
|
63
119
|
@syncer.stub!(:system)
|
64
120
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gorgon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
|
-
date: 2012-09-
|
16
|
+
date: 2012-09-24 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: rspec
|