ngauthier-hydra 0.24.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.rdoc +51 -0
- data/Rakefile +8 -0
- data/TODO +18 -0
- data/hydra-icon-64x64.png +0 -0
- data/hydra.gemspec +24 -0
- data/hydra_gray.png +0 -0
- data/lib/hydra.rb +16 -0
- data/lib/hydra/cucumber/formatter.rb +29 -0
- data/lib/hydra/cucumber/partial_html.rb +24 -0
- data/lib/hydra/hash.rb +16 -0
- data/lib/hydra/js/lint.js +5150 -0
- data/lib/hydra/listener/abstract.rb +39 -0
- data/lib/hydra/listener/cucumber.css +279 -0
- data/lib/hydra/listener/cucumber_html_report.rb +148 -0
- data/lib/hydra/listener/jquery-min.js +154 -0
- data/lib/hydra/listener/minimal_output.rb +24 -0
- data/lib/hydra/listener/notifier.rb +17 -0
- data/lib/hydra/listener/progress_bar.rb +48 -0
- data/lib/hydra/listener/report_generator.rb +33 -0
- data/lib/hydra/master.rb +248 -0
- data/lib/hydra/message.rb +47 -0
- data/lib/hydra/message/master_messages.rb +19 -0
- data/lib/hydra/message/runner_messages.rb +46 -0
- data/lib/hydra/message/worker_messages.rb +52 -0
- data/lib/hydra/messaging_io.rb +49 -0
- data/lib/hydra/pipe.rb +61 -0
- data/lib/hydra/runner.rb +312 -0
- data/lib/hydra/runner_listener/abstract.rb +23 -0
- data/lib/hydra/safe_fork.rb +31 -0
- data/lib/hydra/spec/autorun_override.rb +3 -0
- data/lib/hydra/spec/hydra_formatter.rb +26 -0
- data/lib/hydra/ssh.rb +41 -0
- data/lib/hydra/stdio.rb +16 -0
- data/lib/hydra/sync.rb +99 -0
- data/lib/hydra/tasks.rb +375 -0
- data/lib/hydra/tmpdir.rb +11 -0
- data/lib/hydra/trace.rb +24 -0
- data/lib/hydra/version.rb +3 -0
- data/lib/hydra/worker.rb +170 -0
- data/test/fixtures/assert_true.rb +7 -0
- data/test/fixtures/config.yml +4 -0
- data/test/fixtures/conflicting.rb +10 -0
- data/test/fixtures/features/step_definitions.rb +21 -0
- data/test/fixtures/features/write_alternate_file.feature +7 -0
- data/test/fixtures/features/write_file.feature +7 -0
- data/test/fixtures/hello_world.rb +3 -0
- data/test/fixtures/hydra_worker_init.rb +2 -0
- data/test/fixtures/js_file.js +4 -0
- data/test/fixtures/json_data.json +4 -0
- data/test/fixtures/many_outputs_to_console.rb +9 -0
- data/test/fixtures/master_listeners.rb +10 -0
- data/test/fixtures/runner_listeners.rb +23 -0
- data/test/fixtures/slow.rb +9 -0
- data/test/fixtures/sync_test.rb +8 -0
- data/test/fixtures/task_test_config.yml +6 -0
- data/test/fixtures/write_file.rb +10 -0
- data/test/fixtures/write_file_alternate_spec.rb +10 -0
- data/test/fixtures/write_file_spec.rb +9 -0
- data/test/fixtures/write_file_with_pending_spec.rb +11 -0
- data/test/master_test.rb +383 -0
- data/test/message_test.rb +31 -0
- data/test/pipe_test.rb +38 -0
- data/test/runner_test.rb +196 -0
- data/test/ssh_test.rb +25 -0
- data/test/sync_test.rb +113 -0
- data/test/task_test.rb +21 -0
- data/test/test_helper.rb +107 -0
- data/test/worker_test.rb +60 -0
- metadata +229 -0
data/lib/hydra/tmpdir.rb
ADDED
data/lib/hydra/trace.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
# Trace output when in verbose mode.
|
3
|
+
module Trace
|
4
|
+
module ClassMethods
|
5
|
+
# Make a class traceable. Takes one parameter,
|
6
|
+
# which is the prefix for the trace to identify this class
|
7
|
+
def traceable(prefix = self.class.to_s)
|
8
|
+
include Hydra::Trace::InstanceMethods
|
9
|
+
class << self; attr_accessor :_traceable_prefix; end
|
10
|
+
self._traceable_prefix = prefix
|
11
|
+
$stdout.sync = true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
# Trace some output with the class's prefix and a newline.
|
17
|
+
# Checks to ensure we're running verbosely.
|
18
|
+
def trace(str)
|
19
|
+
$stdout.write "#{Time.now.to_f} #{self.class._traceable_prefix}| #{str}\n" if @verbose
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
Object.extend(Hydra::Trace::ClassMethods)
|
data/lib/hydra/worker.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
# Hydra class responsible to dispatching runners and communicating with the master.
|
3
|
+
#
|
4
|
+
# The Worker is never run directly by a user. Workers are created by a
|
5
|
+
# Master to delegate to Runners.
|
6
|
+
#
|
7
|
+
# The general convention is to have one Worker per machine on a distributed
|
8
|
+
# network.
|
9
|
+
class Worker
|
10
|
+
include Hydra::Messages::Worker
|
11
|
+
traceable('WORKER')
|
12
|
+
|
13
|
+
attr_reader :runners
|
14
|
+
# Create a new worker.
|
15
|
+
# * io: The IO object to use to communicate with the master
|
16
|
+
# * num_runners: The number of runners to launch
|
17
|
+
def initialize(opts = {})
|
18
|
+
@verbose = opts.fetch(:verbose) { false }
|
19
|
+
@io = opts.fetch(:io) { raise "No IO Object" }
|
20
|
+
@runners = []
|
21
|
+
@listeners = []
|
22
|
+
@options = opts.fetch(:options) { "" }
|
23
|
+
|
24
|
+
load_worker_initializer
|
25
|
+
|
26
|
+
@runner_event_listeners = Array(opts.fetch(:runner_listeners) { nil })
|
27
|
+
@runner_event_listeners.select{|l| l.is_a? String}.each do |l|
|
28
|
+
@runner_event_listeners.delete_at(@runner_event_listeners.index(l))
|
29
|
+
listener = eval(l)
|
30
|
+
@runner_event_listeners << listener if listener.is_a?(Hydra::RunnerListener::Abstract)
|
31
|
+
end
|
32
|
+
@runner_log_file = opts.fetch(:runner_log_file) { nil }
|
33
|
+
|
34
|
+
boot_runners(opts.fetch(:runners) { 1 })
|
35
|
+
@io.write(Hydra::Messages::Worker::WorkerBegin.new)
|
36
|
+
|
37
|
+
process_messages
|
38
|
+
|
39
|
+
@runners.each{|r| Process.wait r[:pid] }
|
40
|
+
end
|
41
|
+
|
42
|
+
def load_worker_initializer
|
43
|
+
if File.exist?('./hydra_worker_init.rb')
|
44
|
+
trace('Requiring hydra_worker_init.rb')
|
45
|
+
require 'hydra_worker_init'
|
46
|
+
else
|
47
|
+
trace('hydra_worker_init.rb not present')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# message handling methods
|
52
|
+
|
53
|
+
# When a runner wants a file, it hits this method with a message.
|
54
|
+
# Then the worker bubbles the file request up to the master.
|
55
|
+
def request_file(message, runner)
|
56
|
+
@io.write(RequestFile.new)
|
57
|
+
runner[:idle] = true
|
58
|
+
end
|
59
|
+
|
60
|
+
# When the master sends a file down to the worker, it hits this
|
61
|
+
# method. Then the worker delegates the file down to a runner.
|
62
|
+
def delegate_file(message)
|
63
|
+
runner = idle_runner
|
64
|
+
runner[:idle] = false
|
65
|
+
runner[:io].write(RunFile.new(eval(message.serialize)))
|
66
|
+
end
|
67
|
+
|
68
|
+
# When a runner finishes, it sends the results up to the worker. Then the
|
69
|
+
# worker sends the results up to the master.
|
70
|
+
def relay_results(message, runner)
|
71
|
+
runner[:idle] = true
|
72
|
+
@io.write(Results.new(eval(message.serialize)))
|
73
|
+
end
|
74
|
+
|
75
|
+
# When a master issues a shutdown order, it hits this method, which causes
|
76
|
+
# the worker to send shutdown messages to its runners.
|
77
|
+
def shutdown
|
78
|
+
@running = false
|
79
|
+
trace "Notifying #{@runners.size} Runners of Shutdown"
|
80
|
+
@runners.each do |r|
|
81
|
+
trace "Sending Shutdown to Runner"
|
82
|
+
trace "\t#{r.inspect}"
|
83
|
+
r[:io].write(Shutdown.new)
|
84
|
+
end
|
85
|
+
Thread.exit
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def boot_runners(num_runners) #:nodoc:
|
91
|
+
trace "Booting #{num_runners} Runners"
|
92
|
+
num_runners.times do
|
93
|
+
pipe = Hydra::Pipe.new
|
94
|
+
|
95
|
+
child = SafeFork.fork do
|
96
|
+
pipe.identify_as_child
|
97
|
+
Hydra::Runner.new(:io => pipe, :verbose => @verbose, :runner_listeners => @runner_event_listeners, :runner_log_file => @runner_log_file, :options => @options)
|
98
|
+
end
|
99
|
+
pipe.identify_as_parent
|
100
|
+
@runners << { :pid => child, :io => pipe, :idle => false }
|
101
|
+
end
|
102
|
+
trace "#{@runners.size} Runners booted"
|
103
|
+
end
|
104
|
+
|
105
|
+
# Continuously process messages
|
106
|
+
def process_messages #:nodoc:
|
107
|
+
trace "Processing Messages"
|
108
|
+
@running = true
|
109
|
+
|
110
|
+
Thread.abort_on_exception = true
|
111
|
+
|
112
|
+
process_messages_from_master
|
113
|
+
process_messages_from_runners
|
114
|
+
|
115
|
+
@listeners.each{|l| l.join }
|
116
|
+
@io.close
|
117
|
+
trace "Done processing messages"
|
118
|
+
end
|
119
|
+
|
120
|
+
def process_messages_from_master
|
121
|
+
@listeners << Thread.new do
|
122
|
+
while @running
|
123
|
+
begin
|
124
|
+
message = @io.gets
|
125
|
+
if message and !message.class.to_s.index("Master").nil?
|
126
|
+
trace "Received Message from Master"
|
127
|
+
trace "\t#{message.inspect}"
|
128
|
+
message.handle(self)
|
129
|
+
else
|
130
|
+
trace "Nothing from Master, Pinging"
|
131
|
+
@io.write Ping.new
|
132
|
+
end
|
133
|
+
rescue IOError => ex
|
134
|
+
trace "Worker lost Master"
|
135
|
+
shutdown
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def process_messages_from_runners
|
142
|
+
@runners.each do |r|
|
143
|
+
@listeners << Thread.new do
|
144
|
+
while @running
|
145
|
+
begin
|
146
|
+
message = r[:io].gets
|
147
|
+
if message and !message.class.to_s.index("Runner").nil?
|
148
|
+
trace "Received Message from Runner"
|
149
|
+
trace "\t#{message.inspect}"
|
150
|
+
message.handle(self, r)
|
151
|
+
end
|
152
|
+
rescue IOError => ex
|
153
|
+
trace "Worker lost Runner [#{r.inspect}]"
|
154
|
+
Thread.exit
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Get the next idle runner
|
162
|
+
def idle_runner #:nodoc:
|
163
|
+
idle_r = nil
|
164
|
+
while idle_r.nil?
|
165
|
+
idle_r = @runners.detect{|runner| runner[:idle]}
|
166
|
+
end
|
167
|
+
return idle_r
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Given /^a target file$/ do
|
2
|
+
@target_file = File.expand_path(File.join(Dir.consistent_tmpdir, 'hydra_test.txt'))
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^an alternate target file$/ do
|
6
|
+
@target_file = File.expand_path(File.join(Dir.consistent_tmpdir, 'alternate_hydra_test.txt'))
|
7
|
+
end
|
8
|
+
|
9
|
+
When /^I write "([^\"]*)" to the file$/ do |text|
|
10
|
+
f = File.new(@target_file, 'w')
|
11
|
+
f.write text
|
12
|
+
f.flush
|
13
|
+
f.close
|
14
|
+
end
|
15
|
+
|
16
|
+
Then /^"([^\"]*)" should be written in the file$/ do |text|
|
17
|
+
f = File.new(@target_file, 'r')
|
18
|
+
raise 'Did not write to file' unless text == f.read
|
19
|
+
f.close
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module HydraExtension
|
2
|
+
module Listener
|
3
|
+
class WorkerBeganFlag < Hydra::Listener::Abstract
|
4
|
+
# Fired after runner processes have been started
|
5
|
+
def worker_begin(worker)
|
6
|
+
FileUtils.touch File.expand_path(File.join(Dir.consistent_tmpdir, 'worker_began_flag'))
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module HydraExtension
|
2
|
+
module RunnerListener
|
3
|
+
class RunnerBeginTest < Hydra::RunnerListener::Abstract
|
4
|
+
# Fired by the runner just before requesting the first file
|
5
|
+
def runner_begin( runner )
|
6
|
+
FileUtils.touch File.expand_path(File.join(Dir.consistent_tmpdir, 'alternate_hydra_test.txt'))
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class RunnerEndTest < Hydra::RunnerListener::Abstract
|
11
|
+
# Fired by the runner just before requesting the first file
|
12
|
+
def runner_begin( runner )
|
13
|
+
FileUtils.touch File.expand_path(File.join(Dir.consistent_tmpdir, 'runner_began_flag')) #used to know when the runner is ready
|
14
|
+
end
|
15
|
+
# Fired by the runner just after stoping
|
16
|
+
def runner_end( runner )
|
17
|
+
# NOTE: do not use trace here
|
18
|
+
#runner.trace "Ending runner"
|
19
|
+
FileUtils.touch File.expand_path(File.join(Dir.consistent_tmpdir, 'alternate_hydra_test.txt'))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/test/master_test.rb
ADDED
@@ -0,0 +1,383 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
require File.join(File.dirname(__FILE__), 'fixtures', 'runner_listeners')
|
3
|
+
require File.join(File.dirname(__FILE__), 'fixtures', 'master_listeners')
|
4
|
+
|
5
|
+
class MasterTest < Test::Unit::TestCase
|
6
|
+
context "with a file to test and a destination to verify" do
|
7
|
+
setup do
|
8
|
+
# avoid having other tests interfering with us
|
9
|
+
sleep(0.2)
|
10
|
+
FileUtils.rm_f(target_file)
|
11
|
+
end
|
12
|
+
|
13
|
+
teardown do
|
14
|
+
FileUtils.rm_f(target_file)
|
15
|
+
end
|
16
|
+
|
17
|
+
should "run a test" do
|
18
|
+
Hydra::Master.new(
|
19
|
+
:files => [test_file]
|
20
|
+
)
|
21
|
+
assert File.exists?(target_file)
|
22
|
+
assert_equal "HYDRA", File.read(target_file)
|
23
|
+
end
|
24
|
+
|
25
|
+
# this test simulates what happens when we have 2 tests with the same
|
26
|
+
# class name but with different parent classes. This can happen when
|
27
|
+
# we have a functional and an integration test class with the same name.
|
28
|
+
should "run even with a test that will not require" do
|
29
|
+
class FileOutputListener < Hydra::Listener::Abstract
|
30
|
+
attr_accessor :output
|
31
|
+
def initialize(&block)
|
32
|
+
self.output = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def file_end(file, output)
|
36
|
+
self.output[file] = output
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
listener = FileOutputListener.new
|
41
|
+
sync_test = File.join(File.dirname(__FILE__), 'fixtures', 'sync_test.rb')
|
42
|
+
Hydra::Master.new(
|
43
|
+
# we want the actual test to run last to make sure the runner can still run tests
|
44
|
+
:files => [sync_test, conflicting_test_file, test_file],
|
45
|
+
:autosort => false,
|
46
|
+
:listeners => [listener]
|
47
|
+
)
|
48
|
+
assert_match /superclass mismatch for class SyncTest/, listener.output[conflicting_test_file]
|
49
|
+
assert_match conflicting_test_file, listener.output[conflicting_test_file]
|
50
|
+
assert File.exists?(target_file)
|
51
|
+
assert_equal "HYDRA", File.read(target_file)
|
52
|
+
end
|
53
|
+
|
54
|
+
should "run a spec with pending examples" do
|
55
|
+
progress_bar = Hydra::Listener::ProgressBar.new(StringIO.new)
|
56
|
+
Hydra::Master.new(
|
57
|
+
:files => [rspec_file_with_pending],
|
58
|
+
:listeners => [progress_bar]
|
59
|
+
)
|
60
|
+
assert File.exists?(target_file)
|
61
|
+
assert_equal "HYDRA", File.read(target_file)
|
62
|
+
assert_equal false, progress_bar.instance_variable_get('@errors')
|
63
|
+
end
|
64
|
+
|
65
|
+
should "generate a report" do
|
66
|
+
Hydra::Master.new(:files => [test_file])
|
67
|
+
assert File.exists?(target_file)
|
68
|
+
assert_equal "HYDRA", File.read(target_file)
|
69
|
+
report_file = File.join(Dir.consistent_tmpdir, 'hydra_heuristics.yml')
|
70
|
+
assert File.exists?(report_file)
|
71
|
+
assert report = YAML.load_file(report_file)
|
72
|
+
assert_not_nil report[test_file]
|
73
|
+
end
|
74
|
+
|
75
|
+
should "run a test 6 times on 1 worker with 2 runners" do
|
76
|
+
Hydra::Master.new(
|
77
|
+
:files => [test_file]*6,
|
78
|
+
:workers => [ { :type => :local, :runners => 2 } ]
|
79
|
+
)
|
80
|
+
assert File.exists?(target_file)
|
81
|
+
assert_equal "HYDRA"*6, File.read(target_file)
|
82
|
+
end
|
83
|
+
|
84
|
+
# The test being run sleeps for 5 seconds. So, if this was run in
|
85
|
+
# series, it would take at least 50 seconds. This test ensures that
|
86
|
+
# in runs in less than that amount of time. Since there are 10
|
87
|
+
# runners to run the file 10 times, it should only take 5-10 seconds
|
88
|
+
# based on overhead.
|
89
|
+
should "run a slow test 10 times on 1 worker with 10 runners quickly" do
|
90
|
+
start = Time.now
|
91
|
+
Hydra::Master.new(
|
92
|
+
:files => [File.join(File.dirname(__FILE__), 'fixtures', 'slow.rb')]*10,
|
93
|
+
:workers => [
|
94
|
+
{ :type => :local, :runners => 10 }
|
95
|
+
]
|
96
|
+
)
|
97
|
+
finish = Time.now
|
98
|
+
assert (finish-start) < 30, "took #{finish-start} seconds"
|
99
|
+
end
|
100
|
+
|
101
|
+
should "run a slow test 10 times on 2 workers with 5 runners each quickly" do
|
102
|
+
start = Time.now
|
103
|
+
Hydra::Master.new(
|
104
|
+
:files => [File.join(File.dirname(__FILE__), 'fixtures', 'slow.rb')]*10,
|
105
|
+
:workers => [
|
106
|
+
{ :type => :local, :runners => 5 },
|
107
|
+
{ :type => :local, :runners => 5 }
|
108
|
+
]
|
109
|
+
)
|
110
|
+
finish = Time.now
|
111
|
+
assert (finish-start) < 15, "took #{finish-start} seconds"
|
112
|
+
end
|
113
|
+
|
114
|
+
should "run a test via ssh" do
|
115
|
+
Hydra::Master.new(
|
116
|
+
:files => [test_file],
|
117
|
+
:workers => [{
|
118
|
+
:type => :ssh,
|
119
|
+
:connect => 'localhost',
|
120
|
+
:directory => remote_dir_path,
|
121
|
+
:runners => 1
|
122
|
+
}]
|
123
|
+
)
|
124
|
+
assert File.exists?(target_file)
|
125
|
+
assert_equal "HYDRA", File.read(target_file)
|
126
|
+
end
|
127
|
+
|
128
|
+
should "run a test with config from a yaml file" do
|
129
|
+
Hydra::Master.new(
|
130
|
+
:files => [test_file],
|
131
|
+
:config => File.join(File.dirname(__FILE__), 'fixtures', 'config.yml')
|
132
|
+
)
|
133
|
+
assert File.exists?(target_file)
|
134
|
+
assert_equal "HYDRA", File.read(target_file)
|
135
|
+
end
|
136
|
+
|
137
|
+
should "synchronize a test file over ssh with rsync" do
|
138
|
+
local = File.join(Dir.consistent_tmpdir, 'hydra', 'local')
|
139
|
+
remote = File.join(Dir.consistent_tmpdir, 'hydra', 'remote')
|
140
|
+
sync_test = File.join(File.dirname(__FILE__), 'fixtures', 'sync_test.rb')
|
141
|
+
[local, remote].each{|f| FileUtils.rm_rf f; FileUtils.mkdir_p f}
|
142
|
+
|
143
|
+
# setup the folders:
|
144
|
+
# local:
|
145
|
+
# - test_a
|
146
|
+
# - test_c
|
147
|
+
# remote:
|
148
|
+
# - test_b
|
149
|
+
#
|
150
|
+
# add test_c to exludes
|
151
|
+
FileUtils.cp(sync_test, File.join(local, 'test_a.rb'))
|
152
|
+
FileUtils.cp(sync_test, File.join(local, 'test_c.rb'))
|
153
|
+
FileUtils.cp(sync_test, File.join(remote, 'test_b.rb'))
|
154
|
+
|
155
|
+
# ensure a is not on remote
|
156
|
+
assert !File.exists?(File.join(remote, 'test_a.rb')), "A should not be on remote"
|
157
|
+
# ensure c is not on remote
|
158
|
+
assert !File.exists?(File.join(remote, 'test_c.rb')), "C should not be on remote"
|
159
|
+
# ensure b is on remote
|
160
|
+
assert File.exists?(File.join(remote, 'test_b.rb')), "B should be on remote"
|
161
|
+
|
162
|
+
Hydra::Master.new(
|
163
|
+
:files => ['test_a.rb'],
|
164
|
+
:workers => [{
|
165
|
+
:type => :ssh,
|
166
|
+
:connect => 'localhost',
|
167
|
+
:directory => remote,
|
168
|
+
:runners => 1
|
169
|
+
}],
|
170
|
+
:sync => {
|
171
|
+
:directory => local,
|
172
|
+
:exclude => ['test_c.rb']
|
173
|
+
}
|
174
|
+
)
|
175
|
+
# ensure a is copied
|
176
|
+
assert File.exists?(File.join(remote, 'test_a.rb')), "A was not copied"
|
177
|
+
# ensure c is not copied
|
178
|
+
assert !File.exists?(File.join(remote, 'test_c.rb')), "C was copied, should be excluded"
|
179
|
+
# ensure b is deleted
|
180
|
+
assert !File.exists?(File.join(remote, 'test_b.rb')), "B was not deleted"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
context "with a runner_end event" do
|
185
|
+
setup do
|
186
|
+
# avoid having other tests interfering with us
|
187
|
+
sleep(0.2)
|
188
|
+
FileUtils.rm_f(target_file)
|
189
|
+
FileUtils.rm_f(alternate_target_file)
|
190
|
+
|
191
|
+
@runner_began_flag = File.expand_path(File.join(Dir.consistent_tmpdir, 'runner_began_flag')) #used to know when the worker is ready
|
192
|
+
FileUtils.rm_f(@runner_began_flag)
|
193
|
+
|
194
|
+
@runner_listener = 'HydraExtension::RunnerListener::RunnerEndTest.new' # runner_end method that creates alternate_target_file
|
195
|
+
@master_listener = HydraExtension::Listener::WorkerBeganFlag.new #used to know when the runner is up
|
196
|
+
end
|
197
|
+
|
198
|
+
teardown do
|
199
|
+
FileUtils.rm_f(target_file)
|
200
|
+
FileUtils.rm_f(alternate_target_file)
|
201
|
+
end
|
202
|
+
|
203
|
+
context "running a local worker" do
|
204
|
+
should "run runner_end on successful termination" do
|
205
|
+
@pid = Process.fork do
|
206
|
+
Hydra::Master.new(
|
207
|
+
:files => [test_file] * 6,
|
208
|
+
:autosort => false,
|
209
|
+
:listeners => [@master_listener],
|
210
|
+
:runner_listeners => [@runner_listener],
|
211
|
+
:verbose => false
|
212
|
+
)
|
213
|
+
end
|
214
|
+
Process.waitpid @pid
|
215
|
+
|
216
|
+
assert_file_exists alternate_target_file
|
217
|
+
end
|
218
|
+
|
219
|
+
should "run runner_end after interruption signal" do
|
220
|
+
add_infinite_worker_begin_to @master_listener
|
221
|
+
|
222
|
+
capture_stderr do # redirect stderr
|
223
|
+
@pid = Process.fork do
|
224
|
+
Hydra::Master.new(
|
225
|
+
:files => [test_file],
|
226
|
+
:autosort => false,
|
227
|
+
:listeners => [@master_listener],
|
228
|
+
:runner_listeners => [@runner_listener],
|
229
|
+
:verbose => false
|
230
|
+
)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
wait_for_runner_to_begin
|
234
|
+
|
235
|
+
Process.kill 'SIGINT', @pid
|
236
|
+
Process.waitpid @pid
|
237
|
+
|
238
|
+
assert_file_exists alternate_target_file
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
context "running a remote worker" do
|
243
|
+
setup do
|
244
|
+
copy_worker_init_file # this method has a protection to avoid erasing an existing worker_init_file
|
245
|
+
end
|
246
|
+
|
247
|
+
teardown do
|
248
|
+
FileUtils.rm_f(@remote_init_file) unless @protect_init_file
|
249
|
+
end
|
250
|
+
|
251
|
+
should "run runner_end on successful termination" do
|
252
|
+
capture_stderr do # redirect stderr
|
253
|
+
@pid = Process.fork do
|
254
|
+
Hydra::Master.new(
|
255
|
+
:files => [test_file],
|
256
|
+
:autosort => false,
|
257
|
+
:listeners => [@master_listener],
|
258
|
+
:runner_listeners => [@runner_listener],
|
259
|
+
:workers => [{
|
260
|
+
:type => :ssh,
|
261
|
+
:connect => 'localhost',
|
262
|
+
:directory => remote_dir_path,
|
263
|
+
:runners => 1
|
264
|
+
}],
|
265
|
+
:verbose => false
|
266
|
+
)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
Process.waitpid @pid
|
270
|
+
|
271
|
+
assert_file_exists alternate_target_file
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
context "redirecting runner's output and errors" do
|
277
|
+
setup do
|
278
|
+
# avoid having other tests interfering with us
|
279
|
+
sleep(0.2)
|
280
|
+
FileUtils.rm_f(target_file)
|
281
|
+
FileUtils.rm_f(runner_log_file)
|
282
|
+
FileUtils.rm_f("#{remote_dir_path}/#{runner_log_file}")
|
283
|
+
end
|
284
|
+
|
285
|
+
teardown do
|
286
|
+
FileUtils.rm_f(target_file)
|
287
|
+
FileUtils.rm_f(runner_log_file)
|
288
|
+
FileUtils.rm_f("#{remote_dir_path}/#{runner_log_file}")
|
289
|
+
end
|
290
|
+
|
291
|
+
should "create a runner log file when usign local worker and passing a log file name" do
|
292
|
+
@pid = Process.fork do
|
293
|
+
Hydra::Master.new(
|
294
|
+
:files => [test_file],
|
295
|
+
:runner_log_file => runner_log_file,
|
296
|
+
:verbose => false
|
297
|
+
)
|
298
|
+
end
|
299
|
+
Process.waitpid @pid
|
300
|
+
|
301
|
+
assert_file_exists target_file # ensure the test was successfully ran
|
302
|
+
assert_file_exists runner_log_file
|
303
|
+
end
|
304
|
+
|
305
|
+
should "create a runner log file when usign remote worker and passing a log file name" do
|
306
|
+
@pid = Process.fork do
|
307
|
+
Hydra::Master.new(
|
308
|
+
:files => [test_file],
|
309
|
+
:workers => [{
|
310
|
+
:type => :ssh,
|
311
|
+
:connect => 'localhost',
|
312
|
+
:directory => remote_dir_path,
|
313
|
+
:runners => 1
|
314
|
+
}],
|
315
|
+
:verbose => false,
|
316
|
+
:runner_log_file => runner_log_file
|
317
|
+
)
|
318
|
+
end
|
319
|
+
Process.waitpid @pid
|
320
|
+
|
321
|
+
assert_file_exists target_file # ensure the test was successfully ran
|
322
|
+
assert_file_exists "#{remote_dir_path}/#{runner_log_file}"
|
323
|
+
end
|
324
|
+
|
325
|
+
should "create the default runner log file when passing an incorrect log file path" do
|
326
|
+
default_log_file = "#{remote_dir_path}/#{Hydra::Runner::DEFAULT_LOG_FILE}" # hydra-runner.log"
|
327
|
+
FileUtils.rm_f(default_log_file)
|
328
|
+
|
329
|
+
@pid = Process.fork do
|
330
|
+
Hydra::Master.new(
|
331
|
+
:files => [test_file],
|
332
|
+
:workers => [{
|
333
|
+
:type => :ssh,
|
334
|
+
:connect => 'localhost',
|
335
|
+
:directory => remote_dir_path,
|
336
|
+
:runners => 1
|
337
|
+
}],
|
338
|
+
:verbose => false,
|
339
|
+
:runner_log_file => 'invalid-dir/#{runner_log_file}'
|
340
|
+
)
|
341
|
+
end
|
342
|
+
Process.waitpid @pid
|
343
|
+
|
344
|
+
assert_file_exists target_file # ensure the test was successfully ran
|
345
|
+
assert_file_exists default_log_file #default log file
|
346
|
+
assert !File.exists?( "#{remote_dir_path}/#{runner_log_file}" )
|
347
|
+
|
348
|
+
FileUtils.rm_f(default_log_file)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
private
|
353
|
+
|
354
|
+
def runner_log_file
|
355
|
+
"my-hydra-runner.log"
|
356
|
+
end
|
357
|
+
|
358
|
+
def add_infinite_worker_begin_to master_listener
|
359
|
+
class << master_listener
|
360
|
+
def worker_begin( worker )
|
361
|
+
super
|
362
|
+
sleep 1 while true #ensure the process doesn't finish before killing it
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# this requires that a worker_begin listener creates a file named worker_began_flag in tmp directory
|
368
|
+
def wait_for_runner_to_begin
|
369
|
+
assert_file_exists @runner_began_flag
|
370
|
+
end
|
371
|
+
|
372
|
+
# with a protection to avoid erasing something important in lib
|
373
|
+
def copy_worker_init_file
|
374
|
+
@remote_init_file = "#{remote_dir_path}/#{File.basename( hydra_worker_init_file )}"
|
375
|
+
if File.exists?( @remote_init_file )
|
376
|
+
$stderr.puts "\nWARNING!!!: #{@remote_init_file} exits and this test needs to create a new file here. Make sure there is nothing inportant in that file and remove it before running this test\n\n"
|
377
|
+
@protect_init_file = true
|
378
|
+
exit
|
379
|
+
end
|
380
|
+
# copy the hydra_worker_init to the correct location
|
381
|
+
FileUtils.cp(hydra_worker_init_file, remote_dir_path)
|
382
|
+
end
|
383
|
+
end
|