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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.rdoc +51 -0
  6. data/Rakefile +8 -0
  7. data/TODO +18 -0
  8. data/hydra-icon-64x64.png +0 -0
  9. data/hydra.gemspec +24 -0
  10. data/hydra_gray.png +0 -0
  11. data/lib/hydra.rb +16 -0
  12. data/lib/hydra/cucumber/formatter.rb +29 -0
  13. data/lib/hydra/cucumber/partial_html.rb +24 -0
  14. data/lib/hydra/hash.rb +16 -0
  15. data/lib/hydra/js/lint.js +5150 -0
  16. data/lib/hydra/listener/abstract.rb +39 -0
  17. data/lib/hydra/listener/cucumber.css +279 -0
  18. data/lib/hydra/listener/cucumber_html_report.rb +148 -0
  19. data/lib/hydra/listener/jquery-min.js +154 -0
  20. data/lib/hydra/listener/minimal_output.rb +24 -0
  21. data/lib/hydra/listener/notifier.rb +17 -0
  22. data/lib/hydra/listener/progress_bar.rb +48 -0
  23. data/lib/hydra/listener/report_generator.rb +33 -0
  24. data/lib/hydra/master.rb +248 -0
  25. data/lib/hydra/message.rb +47 -0
  26. data/lib/hydra/message/master_messages.rb +19 -0
  27. data/lib/hydra/message/runner_messages.rb +46 -0
  28. data/lib/hydra/message/worker_messages.rb +52 -0
  29. data/lib/hydra/messaging_io.rb +49 -0
  30. data/lib/hydra/pipe.rb +61 -0
  31. data/lib/hydra/runner.rb +312 -0
  32. data/lib/hydra/runner_listener/abstract.rb +23 -0
  33. data/lib/hydra/safe_fork.rb +31 -0
  34. data/lib/hydra/spec/autorun_override.rb +3 -0
  35. data/lib/hydra/spec/hydra_formatter.rb +26 -0
  36. data/lib/hydra/ssh.rb +41 -0
  37. data/lib/hydra/stdio.rb +16 -0
  38. data/lib/hydra/sync.rb +99 -0
  39. data/lib/hydra/tasks.rb +375 -0
  40. data/lib/hydra/tmpdir.rb +11 -0
  41. data/lib/hydra/trace.rb +24 -0
  42. data/lib/hydra/version.rb +3 -0
  43. data/lib/hydra/worker.rb +170 -0
  44. data/test/fixtures/assert_true.rb +7 -0
  45. data/test/fixtures/config.yml +4 -0
  46. data/test/fixtures/conflicting.rb +10 -0
  47. data/test/fixtures/features/step_definitions.rb +21 -0
  48. data/test/fixtures/features/write_alternate_file.feature +7 -0
  49. data/test/fixtures/features/write_file.feature +7 -0
  50. data/test/fixtures/hello_world.rb +3 -0
  51. data/test/fixtures/hydra_worker_init.rb +2 -0
  52. data/test/fixtures/js_file.js +4 -0
  53. data/test/fixtures/json_data.json +4 -0
  54. data/test/fixtures/many_outputs_to_console.rb +9 -0
  55. data/test/fixtures/master_listeners.rb +10 -0
  56. data/test/fixtures/runner_listeners.rb +23 -0
  57. data/test/fixtures/slow.rb +9 -0
  58. data/test/fixtures/sync_test.rb +8 -0
  59. data/test/fixtures/task_test_config.yml +6 -0
  60. data/test/fixtures/write_file.rb +10 -0
  61. data/test/fixtures/write_file_alternate_spec.rb +10 -0
  62. data/test/fixtures/write_file_spec.rb +9 -0
  63. data/test/fixtures/write_file_with_pending_spec.rb +11 -0
  64. data/test/master_test.rb +383 -0
  65. data/test/message_test.rb +31 -0
  66. data/test/pipe_test.rb +38 -0
  67. data/test/runner_test.rb +196 -0
  68. data/test/ssh_test.rb +25 -0
  69. data/test/sync_test.rb +113 -0
  70. data/test/task_test.rb +21 -0
  71. data/test/test_helper.rb +107 -0
  72. data/test/worker_test.rb +60 -0
  73. metadata +229 -0
@@ -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,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)
@@ -0,0 +1,3 @@
1
+ module Hydra
2
+ VERSION = "0.0.1"
3
+ end
@@ -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,7 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class AssertTrueTest < Test::Unit::TestCase
4
+ should "be true" do
5
+ assert true
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ workers:
3
+ - type: local
4
+ runners: 2
@@ -0,0 +1,10 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ # this test is around to make sure that we handle all the errors
4
+ # that can occur when 'require'ing a test file.
5
+ class SyncTest < Object
6
+ def test_it_again
7
+ assert true
8
+ end
9
+ end
10
+
@@ -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,7 @@
1
+ Feature: Write a file
2
+
3
+ Scenario: Write to hydra_test.txt
4
+ Given an alternate target file
5
+ When I write "HYDRA" to the file
6
+ Then "HYDRA" should be written in the file
7
+
@@ -0,0 +1,7 @@
1
+ Feature: Write a file
2
+
3
+ Scenario: Write to hydra_test.txt
4
+ Given a target file
5
+ When I write "HYDRA" to the file
6
+ Then "HYDRA" should be written in the file
7
+
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ $stdout.write "{:class=>Hydra::Messages::TestMessage, :text=>\"Hello World\"}\n"
3
+ $stdout.flush
@@ -0,0 +1,2 @@
1
+ require '../test/fixtures/runner_listeners.rb'
2
+ require '../test/fixtures/master_listeners.rb'
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ var thisvar;
3
+ var thatvar
4
+
@@ -0,0 +1,4 @@
1
+ {
2
+ "var1": "something",
3
+ "var2": "trailing comma",
4
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ 10000.times do
4
+ $stdout.write "A non-hydra message...\n"
5
+ $stdout.flush
6
+ end
7
+
8
+ $stdout.write "{:class=>Hydra::Messages::TestMessage, :text=>\"My message\"}\n"
9
+ $stdout.flush
@@ -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
@@ -0,0 +1,9 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class WriteFileTest < Test::Unit::TestCase
4
+ def test_slow
5
+ sleep(5)
6
+ end
7
+ end
8
+
9
+
@@ -0,0 +1,8 @@
1
+ require 'test/unit'
2
+
3
+ class SyncTest < Test::Unit::TestCase
4
+ def test_truth
5
+ assert true
6
+ end
7
+ end
8
+
@@ -0,0 +1,6 @@
1
+ ---
2
+ workers:
3
+ - type: ssh
4
+ connect: localhost
5
+ directory: /tmp
6
+ runners: 1
@@ -0,0 +1,10 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class WriteFileTest < Test::Unit::TestCase
4
+ def test_write_a_file
5
+ File.open(File.join(Dir.consistent_tmpdir, 'hydra_test.txt'), 'a') do |f|
6
+ f.write "HYDRA"
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,10 @@
1
+ require 'rspec'
2
+ require 'hydra/tmpdir'
3
+ context "file writing" do
4
+ it "writes to a file" do
5
+ File.open(File.join(Dir.consistent_tmpdir, 'alternate_hydra_test.txt'), 'a') do |f|
6
+ f.write "HYDRA"
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,9 @@
1
+ require 'rspec'
2
+ require 'hydra/tmpdir'
3
+ context "file writing" do
4
+ it "writes to a file" do
5
+ File.open(File.join(Dir.consistent_tmpdir, 'hydra_test.txt'), 'a') do |f|
6
+ f.write "HYDRA"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ require 'tmpdir'
2
+ require 'rspec'
3
+ context "file writing" do
4
+ it "writes to a file" do
5
+ File.open(File.join(Dir.consistent_tmpdir, 'hydra_test.txt'), 'a') do |f|
6
+ f.write "HYDRA"
7
+ end
8
+ end
9
+ it 'could do so much more' # pending spec
10
+ end
11
+
@@ -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