nulogy-hydra 0.23.2.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 (75) hide show
  1. data/.document +5 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +39 -0
  4. data/Rakefile +56 -0
  5. data/TODO +18 -0
  6. data/VERSION +1 -0
  7. data/caliper.yml +6 -0
  8. data/hydra-icon-64x64.png +0 -0
  9. data/hydra.gemspec +131 -0
  10. data/hydra_gray.png +0 -0
  11. data/lib/hydra/cucumber/formatter.rb +29 -0
  12. data/lib/hydra/hash.rb +16 -0
  13. data/lib/hydra/js/lint.js +5150 -0
  14. data/lib/hydra/listener/abstract.rb +39 -0
  15. data/lib/hydra/listener/minimal_output.rb +24 -0
  16. data/lib/hydra/listener/notifier.rb +17 -0
  17. data/lib/hydra/listener/progress_bar.rb +48 -0
  18. data/lib/hydra/listener/report_generator.rb +31 -0
  19. data/lib/hydra/master.rb +252 -0
  20. data/lib/hydra/message/master_messages.rb +19 -0
  21. data/lib/hydra/message/runner_messages.rb +46 -0
  22. data/lib/hydra/message/worker_messages.rb +52 -0
  23. data/lib/hydra/message.rb +47 -0
  24. data/lib/hydra/messaging_io.rb +49 -0
  25. data/lib/hydra/pipe.rb +61 -0
  26. data/lib/hydra/proxy_config.rb +27 -0
  27. data/lib/hydra/runner.rb +306 -0
  28. data/lib/hydra/runner_listener/abstract.rb +23 -0
  29. data/lib/hydra/safe_fork.rb +31 -0
  30. data/lib/hydra/spec/autorun_override.rb +3 -0
  31. data/lib/hydra/spec/hydra_formatter.rb +26 -0
  32. data/lib/hydra/ssh.rb +41 -0
  33. data/lib/hydra/stdio.rb +16 -0
  34. data/lib/hydra/sync.rb +99 -0
  35. data/lib/hydra/tasks.rb +366 -0
  36. data/lib/hydra/threadsafe_io.rb +18 -0
  37. data/lib/hydra/tmpdir.rb +11 -0
  38. data/lib/hydra/trace.rb +29 -0
  39. data/lib/hydra/worker.rb +168 -0
  40. data/lib/hydra.rb +16 -0
  41. data/nulogy-hydra.gemspec +122 -0
  42. data/test/fixtures/assert_true.rb +7 -0
  43. data/test/fixtures/bad_proxy_config.yml +4 -0
  44. data/test/fixtures/config.yml +4 -0
  45. data/test/fixtures/conflicting.rb +10 -0
  46. data/test/fixtures/features/step_definitions.rb +21 -0
  47. data/test/fixtures/features/write_alternate_file.feature +7 -0
  48. data/test/fixtures/features/write_file.feature +7 -0
  49. data/test/fixtures/hello_world.rb +3 -0
  50. data/test/fixtures/hydra_worker_init.rb +2 -0
  51. data/test/fixtures/js_file.js +4 -0
  52. data/test/fixtures/json_data.json +4 -0
  53. data/test/fixtures/many_outputs_to_console.rb +9 -0
  54. data/test/fixtures/master_listeners.rb +10 -0
  55. data/test/fixtures/proxy_config.yml +4 -0
  56. data/test/fixtures/proxy_config_http.yml +4 -0
  57. data/test/fixtures/runner_listeners.rb +23 -0
  58. data/test/fixtures/slow.rb +9 -0
  59. data/test/fixtures/sync_test.rb +8 -0
  60. data/test/fixtures/task_test_config.yml +6 -0
  61. data/test/fixtures/write_file.rb +10 -0
  62. data/test/fixtures/write_file_alternate_spec.rb +10 -0
  63. data/test/fixtures/write_file_spec.rb +9 -0
  64. data/test/fixtures/write_file_with_pending_spec.rb +11 -0
  65. data/test/master_test.rb +383 -0
  66. data/test/message_test.rb +31 -0
  67. data/test/pipe_test.rb +38 -0
  68. data/test/proxy_config_test.rb +31 -0
  69. data/test/runner_test.rb +196 -0
  70. data/test/ssh_test.rb +25 -0
  71. data/test/sync_test.rb +113 -0
  72. data/test/task_test.rb +21 -0
  73. data/test/test_helper.rb +107 -0
  74. data/test/worker_test.rb +60 -0
  75. metadata +208 -0
@@ -0,0 +1,366 @@
1
+ require 'open3'
2
+ module Hydra #:nodoc:
3
+ # Hydra Task Common attributes and methods
4
+ class Task
5
+ # Name of the task. Default 'hydra'
6
+ attr_accessor :name
7
+
8
+ # Files to test.
9
+ # You can add files manually via:
10
+ # t.files << [file1, file2, etc]
11
+ #
12
+ # Or you can use the add_files method
13
+ attr_accessor :files
14
+
15
+ # True if you want to see Hydra's message traces
16
+ attr_accessor :verbose
17
+
18
+ # Path to the hydra config file.
19
+ # If not set, it will check 'hydra.yml' and 'config/hydra.yml'
20
+ attr_accessor :config
21
+
22
+ # Automatically sort files using their historical runtimes.
23
+ # Defaults to true
24
+ # To disable:
25
+ # t.autosort = false
26
+ attr_accessor :autosort
27
+
28
+ # Event listeners. Defaults to the MinimalOutput listener.
29
+ # You can add additional listeners if you'd like. For example,
30
+ # on linux (with notify-send) you can add the notifier listener:
31
+ # t.listeners << Hydra::Listener::Notifier.new
32
+ attr_accessor :listeners
33
+
34
+ # Set to true if you want to run this task only on the local
35
+ # machine with one runner. A "Safe Mode" for some test
36
+ # files that may not play nice with others.
37
+ attr_accessor :serial
38
+
39
+ attr_accessor :environment
40
+
41
+ # Set to false if you don't want to show the total running time
42
+ attr_accessor :show_time
43
+
44
+ # Set to a valid file path if you want to save the output of the runners
45
+ # in a log file
46
+ attr_accessor :runner_log_file
47
+
48
+ #
49
+ # Search for the hydra config file
50
+ def find_config_file
51
+ @config ||= 'hydra.yml'
52
+ return @config if File.exists?(@config)
53
+ @config = File.join('config', 'hydra.yml')
54
+ return @config if File.exists?(@config)
55
+ @config = nil
56
+ end
57
+
58
+ # Add files to test by passing in a string to be run through Dir.glob.
59
+ # For example:
60
+ #
61
+ # t.add_files 'test/units/*.rb'
62
+ def add_files(pattern)
63
+ @files += Dir.glob(pattern)
64
+ end
65
+
66
+ end
67
+
68
+ # Define a test task that uses hydra to test the files.
69
+ #
70
+ # Hydra::TestTask.new('hydra') do |t|
71
+ # t.add_files 'test/unit/**/*_test.rb'
72
+ # t.add_files 'test/functional/**/*_test.rb'
73
+ # t.add_files 'test/integration/**/*_test.rb'
74
+ # t.verbose = false # optionally set to true for lots of debug messages
75
+ # t.autosort = false # disable automatic sorting based on runtime of tests
76
+ # end
77
+ class TestTask < Hydra::Task
78
+
79
+ # Create a new HydraTestTask
80
+ def initialize(name = :hydra)
81
+ @name = name
82
+ @files = []
83
+ @verbose = false
84
+ @autosort = true
85
+ @serial = false
86
+ @listeners = [Hydra::Listener::ProgressBar.new]
87
+ @show_time = true
88
+
89
+ yield self if block_given?
90
+
91
+ # Ensure we override rspec's at_exit
92
+ if defined?(RSpec)
93
+ RSpec::Core::Runner.disable_autorun!
94
+ end
95
+
96
+ unless @serial
97
+ @config = find_config_file
98
+ end
99
+
100
+ @opts = {
101
+ :verbose => @verbose,
102
+ :autosort => @autosort,
103
+ :files => @files,
104
+ :listeners => @listeners,
105
+ :environment => @environment,
106
+ :runner_log_file => @runner_log_file
107
+ }
108
+ if @config
109
+ @opts.merge!(:config => @config)
110
+ else
111
+ @opts.merge!(:workers => [{:type => :local, :runners => 1}])
112
+ end
113
+
114
+ define
115
+ end
116
+
117
+ private
118
+ # Create the rake task defined by this HydraTestTask
119
+ def define
120
+ desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}")
121
+ task @name do
122
+ if Object.const_defined?('Rails') && Rails.env == 'development'
123
+ $stderr.puts %{WARNING: Rails Environment is "development". Make sure to set it properly (ex: "RAILS_ENV=test rake hydra")}
124
+ end
125
+
126
+ start = Time.now if @show_time
127
+
128
+ master = Hydra::Master.new(@opts)
129
+
130
+ $stdout.puts "\nFinished in #{'%.6f' % (Time.now - start)} seconds." if @show_time
131
+
132
+ unless master.failed_files.empty?
133
+ raise "Hydra: Not all tests passes"
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ # Define a test task that uses hydra to profile your test files
140
+ #
141
+ # Hydra::ProfileTask.new('hydra:prof') do |t|
142
+ # t.add_files 'test/unit/**/*_test.rb'
143
+ # t.add_files 'test/functional/**/*_test.rb'
144
+ # t.add_files 'test/integration/**/*_test.rb'
145
+ # t.generate_html = true # defaults to false
146
+ # t.generate_text = true # defaults to true
147
+ # end
148
+ class ProfileTask < Hydra::Task
149
+ # boolean: generate html output from ruby-prof
150
+ attr_accessor :generate_html
151
+ # boolean: generate text output from ruby-prof
152
+ attr_accessor :generate_text
153
+
154
+ # Create a new Hydra ProfileTask
155
+ def initialize(name = 'hydra:profile')
156
+ @name = name
157
+ @files = []
158
+ @verbose = false
159
+ @generate_html = false
160
+ @generate_text = true
161
+
162
+ yield self if block_given?
163
+
164
+ # Ensure we override rspec's at_exit
165
+ require 'hydra/spec/autorun_override'
166
+
167
+ @config = find_config_file
168
+
169
+ @opts = {
170
+ :verbose => @verbose,
171
+ :files => @files
172
+ }
173
+ define
174
+ end
175
+
176
+ private
177
+ # Create the rake task defined by this HydraTestTask
178
+ def define
179
+ desc "Hydra Test Profile" + (@name == :hydra ? "" : " for #{@name}")
180
+ task @name do
181
+ require 'ruby-prof'
182
+ RubyProf.start
183
+
184
+ runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
185
+ @files.each do |file|
186
+ $stdout.write runner.run_file(file)
187
+ $stdout.flush
188
+ end
189
+
190
+ $stdout.write "\nTests complete. Generating profiling output\n"
191
+ $stdout.flush
192
+
193
+ result = RubyProf.stop
194
+
195
+ if @generate_html
196
+ printer = RubyProf::GraphHtmlPrinter.new(result)
197
+ out = File.new("ruby-prof.html", 'w')
198
+ printer.print(out, :min_self => 0.05)
199
+ out.close
200
+ $stdout.write "Profiling data written to [ruby-prof.html]\n"
201
+ end
202
+
203
+ if @generate_text
204
+ printer = RubyProf::FlatPrinter.new(result)
205
+ out = File.new("ruby-prof.txt", 'w')
206
+ printer.print(out, :min_self => 0.05)
207
+ out.close
208
+ $stdout.write "Profiling data written to [ruby-prof.txt]\n"
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ # Define a sync task that uses hydra to rsync the source tree under test to remote workers.
215
+ #
216
+ # This task is very useful to run before a remote db:reset task to make sure the db/schema.rb
217
+ # file is up to date on the remote workers.
218
+ #
219
+ # Hydra::SyncTask.new('hydra:sync') do |t|
220
+ # t.verbose = false # optionally set to true for lots of debug messages
221
+ # end
222
+ class SyncTask < Hydra::Task
223
+
224
+ # Create a new SyncTestTask
225
+ def initialize(name = :sync)
226
+ @name = name
227
+ @verbose = false
228
+
229
+ yield self if block_given?
230
+
231
+ @config = find_config_file
232
+
233
+ @opts = {
234
+ :verbose => @verbose
235
+ }
236
+ @opts.merge!(:config => @config) if @config
237
+
238
+ define
239
+ end
240
+
241
+ private
242
+ # Create the rake task defined by this HydraSyncTask
243
+ def define
244
+ desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}")
245
+ task @name do
246
+ Hydra::Sync.sync_many(@opts)
247
+ end
248
+ end
249
+ end
250
+
251
+ # Setup a task that will be run across all remote workers
252
+ # Hydra::RemoteTask.new('db:reset')
253
+ #
254
+ # Then you can run:
255
+ # rake hydra:remote:db:reset
256
+ class RemoteTask < Hydra::Task
257
+ include Open3
258
+ # Create a new hydra remote task with the given name.
259
+ # The task will be named hydra:remote:<name>
260
+ def initialize(name, command=nil)
261
+ @name = name
262
+ @command = command
263
+ yield self if block_given?
264
+ @config = find_config_file
265
+ if @config
266
+ define
267
+ else
268
+ task "hydra:remote:#{@name}" do ; end
269
+ end
270
+ end
271
+
272
+ private
273
+ def define
274
+ desc "Run #{@name} remotely on all workers"
275
+ task "hydra:remote:#{@name}" do
276
+ config = Hydra::ProxyConfig.load(IO.read(@config))
277
+ environment = config.fetch('environment') { 'test' }
278
+ workers = config.fetch('workers') { [] }
279
+ workers = workers.select{|w| w['type'] == 'ssh'}
280
+ @command = "RAILS_ENV=#{environment} rake #{@name}" unless @command
281
+
282
+ $stdout.write "==== Hydra Running #{@name} ====\n"
283
+ Thread.abort_on_exception = true
284
+ @listeners = []
285
+ @results = {}
286
+ workers.each do |worker|
287
+ @listeners << Thread.new do
288
+ begin
289
+ @results[worker] = if run_command(worker, @command)
290
+ "==== #{@name} passed on #{worker['connect']} ====\n"
291
+ else
292
+ "==== #{@name} failed on #{worker['connect']} ====\nPlease see above for more details.\n"
293
+ end
294
+ rescue
295
+ @results[worker] = "==== #{@name} failed for #{worker['connect']} ====\n#{$!.inspect}\n#{$!.backtrace.join("\n")}"
296
+ end
297
+ end
298
+ end
299
+ @listeners.each{|l| l.join}
300
+ $stdout.write "\n==== Hydra Running #{@name} COMPLETE ====\n\n"
301
+ $stdout.write @results.values.join("\n")
302
+ end
303
+ end
304
+
305
+ def run_command worker, command
306
+ $stdout.write "==== Hydra Running #{@name} on #{worker['connect']} ====\n"
307
+ ssh_opts = worker.fetch('ssh_opts') { '' }
308
+ writer, reader, error = popen3("ssh -tt #{ssh_opts} #{worker['connect']} ")
309
+ writer.write("cd #{worker['directory']}\n")
310
+ writer.write "echo BEGIN HYDRA\n"
311
+ writer.write(command + "\r")
312
+ writer.write "echo END HYDRA\n"
313
+ writer.write("exit\n")
314
+ writer.close
315
+ ignoring = true
316
+ passed = true
317
+ while line = reader.gets
318
+ line.chomp!
319
+ if line =~ /^rake aborted!$/
320
+ passed = false
321
+ end
322
+ if line =~ /echo END HYDRA$/
323
+ ignoring = true
324
+ end
325
+ $stdout.write "#{worker['connect']}: #{line}\n" unless ignoring
326
+ if line == 'BEGIN HYDRA'
327
+ ignoring = false
328
+ end
329
+ end
330
+ passed
331
+ end
332
+ end
333
+
334
+ # A Hydra global task is a task that is run both locally and remotely.
335
+ #
336
+ # For example:
337
+ #
338
+ # Hydra::GlobalTask.new('db:reset')
339
+ #
340
+ # Allows you to run:
341
+ #
342
+ # rake hydra:db:reset
343
+ #
344
+ # Then, db:reset will be run locally and on all remote workers. This
345
+ # makes it easy to setup your workers and run tasks all in a row.
346
+ #
347
+ # For example:
348
+ #
349
+ # rake hydra:db:reset hydra:factories hydra:tests
350
+ #
351
+ # Assuming you setup hydra:db:reset and hydra:db:factories as global
352
+ # tasks and hydra:tests as a Hydra::TestTask for all your tests
353
+ class GlobalTask < Hydra::Task
354
+ def initialize(name)
355
+ @name = name
356
+ define
357
+ end
358
+
359
+ private
360
+ def define
361
+ Hydra::RemoteTask.new(@name)
362
+ desc "Run #{@name.to_s} Locally and Remotely across all Workers"
363
+ task "hydra:#{@name.to_s}" => [@name.to_s, "hydra:remote:#{@name.to_s}"]
364
+ end
365
+ end
366
+ end
@@ -0,0 +1,18 @@
1
+ require 'thread'
2
+ require 'fcntl'
3
+
4
+ module Hydra
5
+ class ThreadsafeIO < IO
6
+ def initialize(existing_io)
7
+ fd = existing_io.fcntl(Fcntl::F_DUPFD)
8
+ super(fd)
9
+ @mutex = Mutex.new
10
+ end
11
+
12
+ def write(*args)
13
+ @mutex.synchronize do
14
+ super(*args)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ require 'tmpdir'
2
+
3
+ class Dir
4
+ def self.consistent_tmpdir
5
+ if RUBY_PLATFORM =~ /darwin/i
6
+ '/tmp' # OS X normally returns a crazy tmpdir, BUT when logged in via SSH, it is '/tmp'. This unifies it.
7
+ else
8
+ Dir.tmpdir
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,29 @@
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
+
21
+ prefix = self.class._traceable_prefix.upcase
22
+ unless (prefix == 'MASTER' || prefix == 'SYNC')
23
+ $stdout.write "\n"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ Object.extend(Hydra::Trace::ClassMethods)
@@ -0,0 +1,168 @@
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
+
23
+ load_worker_initializer
24
+
25
+ @runner_event_listeners = Array(opts.fetch(:runner_listeners) { nil })
26
+ @runner_event_listeners.select{|l| l.is_a? String}.each do |l|
27
+ @runner_event_listeners.delete_at(@runner_event_listeners.index(l))
28
+ listener = eval(l)
29
+ @runner_event_listeners << listener if listener.is_a?(Hydra::RunnerListener::Abstract)
30
+ end
31
+ @runner_log_file = opts.fetch(:runner_log_file) { nil }
32
+
33
+ boot_runners(opts.fetch(:runners) { 1 })
34
+ @io.write(Hydra::Messages::Worker::WorkerBegin.new)
35
+
36
+ process_messages
37
+
38
+ @runners.each{|r| Process.wait r[:pid] }
39
+ end
40
+
41
+ def load_worker_initializer
42
+ if File.exist?('./hydra_worker_init.rb')
43
+ trace('Requiring hydra_worker_init.rb')
44
+ require 'hydra_worker_init'
45
+ else
46
+ trace('hydra_worker_init.rb not present')
47
+ end
48
+ end
49
+
50
+ # message handling methods
51
+
52
+ # When a runner wants a file, it hits this method with a message.
53
+ # Then the worker bubbles the file request up to the master.
54
+ def request_file(message, runner)
55
+ @io.write(RequestFile.new)
56
+ runner[:idle] = true
57
+ end
58
+
59
+ # When the master sends a file down to the worker, it hits this
60
+ # method. Then the worker delegates the file down to a runner.
61
+ def delegate_file(message)
62
+ runner = idle_runner
63
+ runner[:idle] = false
64
+ runner[:io].write(RunFile.new(eval(message.serialize)))
65
+ end
66
+
67
+ # When a runner finishes, it sends the results up to the worker. Then the
68
+ # worker sends the results up to the master.
69
+ def relay_results(message, runner)
70
+ runner[:idle] = true
71
+ @io.write(Results.new(eval(message.serialize)))
72
+ end
73
+
74
+ # When a master issues a shutdown order, it hits this method, which causes
75
+ # the worker to send shutdown messages to its runners.
76
+ def shutdown
77
+ @running = false
78
+ trace "Notifying #{@runners.size} Runners of Shutdown"
79
+ @runners.each do |r|
80
+ trace "Sending Shutdown to Runner"
81
+ trace "\t#{r.inspect}"
82
+ r[:io].write(Shutdown.new)
83
+ end
84
+ Thread.exit
85
+ end
86
+
87
+ private
88
+
89
+ def boot_runners(num_runners) #:nodoc:
90
+ trace "Booting #{num_runners} Runners"
91
+ num_runners.times do
92
+ pipe = Hydra::Pipe.new
93
+ child = SafeFork.fork do
94
+ pipe.identify_as_child
95
+ Hydra::Runner.new(:io => pipe, :verbose => @verbose, :runner_listeners => @runner_event_listeners, :runner_log_file => @runner_log_file )
96
+ end
97
+ pipe.identify_as_parent
98
+ @runners << { :pid => child, :io => pipe, :idle => false }
99
+ end
100
+ trace "#{@runners.size} Runners booted"
101
+ end
102
+
103
+ # Continuously process messages
104
+ def process_messages #:nodoc:
105
+ trace "Processing Messages"
106
+ @running = true
107
+
108
+ Thread.abort_on_exception = true
109
+
110
+ process_messages_from_master
111
+ process_messages_from_runners
112
+
113
+ @listeners.each{|l| l.join }
114
+ @io.close
115
+ trace "Done processing messages"
116
+ end
117
+
118
+ def process_messages_from_master
119
+ @listeners << Thread.new do
120
+ while @running
121
+ begin
122
+ message = @io.gets
123
+ if message and !message.class.to_s.index("Master").nil?
124
+ trace "Received Message from Master"
125
+ trace "\t#{message.inspect}"
126
+ message.handle(self)
127
+ else
128
+ trace "Nothing from Master, Pinging"
129
+ @io.write Ping.new
130
+ end
131
+ rescue IOError => ex
132
+ trace "Worker lost Master"
133
+ shutdown
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ def process_messages_from_runners
140
+ @runners.each do |r|
141
+ @listeners << Thread.new do
142
+ while @running
143
+ begin
144
+ message = r[:io].gets
145
+ if message and !message.class.to_s.index("Runner").nil?
146
+ trace "Received Message from Runner"
147
+ trace "\t#{message.inspect}"
148
+ message.handle(self, r)
149
+ end
150
+ rescue IOError => ex
151
+ trace "Worker lost Runner [#{r.inspect}]"
152
+ Thread.exit
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ # Get the next idle runner
160
+ def idle_runner #:nodoc:
161
+ idle_r = nil
162
+ while idle_r.nil?
163
+ idle_r = @runners.detect{|runner| runner[:idle]}
164
+ end
165
+ return idle_r
166
+ end
167
+ end
168
+ end
data/lib/hydra.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'hydra/trace'
2
+ require 'hydra/pipe'
3
+ require 'hydra/ssh'
4
+ require 'hydra/stdio'
5
+ require 'hydra/message'
6
+ require 'hydra/safe_fork'
7
+ require 'hydra/runner'
8
+ require 'hydra/worker'
9
+ require 'hydra/master'
10
+ require 'hydra/sync'
11
+ require 'hydra/listener/abstract'
12
+ require 'hydra/listener/minimal_output'
13
+ require 'hydra/listener/report_generator'
14
+ require 'hydra/listener/notifier'
15
+ require 'hydra/listener/progress_bar'
16
+ require 'hydra/runner_listener/abstract'