gorgon 0.0.2 → 0.1.0
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/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
|