sskirby-hydra 0.16.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/.document +5 -0
  2. data/.gitignore +22 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/Rakefile +55 -0
  6. data/TODO +18 -0
  7. data/VERSION +1 -0
  8. data/caliper.yml +6 -0
  9. data/hydra-icon-64x64.png +0 -0
  10. data/hydra.gemspec +122 -0
  11. data/hydra_gray.png +0 -0
  12. data/lib/hydra/cucumber/formatter.rb +30 -0
  13. data/lib/hydra/hash.rb +16 -0
  14. data/lib/hydra/listener/abstract.rb +30 -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 +30 -0
  19. data/lib/hydra/master.rb +224 -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 +46 -0
  23. data/lib/hydra/message.rb +47 -0
  24. data/lib/hydra/messaging_io.rb +48 -0
  25. data/lib/hydra/pipe.rb +61 -0
  26. data/lib/hydra/runner.rb +214 -0
  27. data/lib/hydra/safe_fork.rb +31 -0
  28. data/lib/hydra/spec/autorun_override.rb +12 -0
  29. data/lib/hydra/spec/hydra_formatter.rb +17 -0
  30. data/lib/hydra/ssh.rb +40 -0
  31. data/lib/hydra/stdio.rb +16 -0
  32. data/lib/hydra/sync.rb +99 -0
  33. data/lib/hydra/tasks.rb +256 -0
  34. data/lib/hydra/trace.rb +24 -0
  35. data/lib/hydra/worker.rb +146 -0
  36. data/lib/hydra.rb +16 -0
  37. data/test/fixtures/assert_true.rb +7 -0
  38. data/test/fixtures/config.yml +4 -0
  39. data/test/fixtures/features/step_definitions.rb +21 -0
  40. data/test/fixtures/features/write_alternate_file.feature +7 -0
  41. data/test/fixtures/features/write_file.feature +7 -0
  42. data/test/fixtures/hello_world.rb +3 -0
  43. data/test/fixtures/slow.rb +9 -0
  44. data/test/fixtures/sync_test.rb +8 -0
  45. data/test/fixtures/write_file.rb +10 -0
  46. data/test/fixtures/write_file_alternate_spec.rb +10 -0
  47. data/test/fixtures/write_file_spec.rb +9 -0
  48. data/test/fixtures/write_file_with_pending_spec.rb +11 -0
  49. data/test/master_test.rb +152 -0
  50. data/test/message_test.rb +31 -0
  51. data/test/pipe_test.rb +38 -0
  52. data/test/runner_test.rb +144 -0
  53. data/test/ssh_test.rb +14 -0
  54. data/test/sync_test.rb +113 -0
  55. data/test/test_helper.rb +60 -0
  56. data/test/worker_test.rb +58 -0
  57. metadata +179 -0
@@ -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,146 @@
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
+ # Create a new worker.
13
+ # * io: The IO object to use to communicate with the master
14
+ # * num_runners: The number of runners to launch
15
+ def initialize(opts = {})
16
+ @verbose = opts.fetch(:verbose) { false }
17
+ @io = opts.fetch(:io) { raise "No IO Object" }
18
+ @runners = []
19
+ @listeners = []
20
+
21
+ boot_runners(opts.fetch(:runners) { 1 })
22
+ process_messages
23
+
24
+ @runners.each{|r| Process.wait r[:pid] }
25
+ end
26
+
27
+
28
+ # message handling methods
29
+
30
+ # When a runner wants a file, it hits this method with a message.
31
+ # Then the worker bubbles the file request up to the master.
32
+ def request_file(message, runner)
33
+ @io.write(RequestFile.new)
34
+ runner[:idle] = true
35
+ end
36
+
37
+ # When the master sends a file down to the worker, it hits this
38
+ # method. Then the worker delegates the file down to a runner.
39
+ def delegate_file(message)
40
+ runner = idle_runner
41
+ runner[:idle] = false
42
+ runner[:io].write(RunFile.new(eval(message.serialize)))
43
+ end
44
+
45
+ # When a runner finishes, it sends the results up to the worker. Then the
46
+ # worker sends the results up to the master.
47
+ def relay_results(message, runner)
48
+ runner[:idle] = true
49
+ @io.write(Results.new(eval(message.serialize)))
50
+ end
51
+
52
+ # When a master issues a shutdown order, it hits this method, which causes
53
+ # the worker to send shutdown messages to its runners.
54
+ def shutdown
55
+ @running = false
56
+ trace "Notifying #{@runners.size} Runners of Shutdown"
57
+ @runners.each do |r|
58
+ trace "Sending Shutdown to Runner"
59
+ trace "\t#{r.inspect}"
60
+ r[:io].write(Shutdown.new)
61
+ end
62
+ Thread.exit
63
+ end
64
+
65
+ private
66
+
67
+ def boot_runners(num_runners) #:nodoc:
68
+ trace "Booting #{num_runners} Runners"
69
+ num_runners.times do
70
+ pipe = Hydra::Pipe.new
71
+ child = SafeFork.fork do
72
+ pipe.identify_as_child
73
+ Hydra::Runner.new(:io => pipe, :verbose => @verbose)
74
+ end
75
+ pipe.identify_as_parent
76
+ @runners << { :pid => child, :io => pipe, :idle => false }
77
+ end
78
+ trace "#{@runners.size} Runners booted"
79
+ end
80
+
81
+ # Continuously process messages
82
+ def process_messages #:nodoc:
83
+ trace "Processing Messages"
84
+ @running = true
85
+
86
+ Thread.abort_on_exception = true
87
+
88
+ process_messages_from_master
89
+ process_messages_from_runners
90
+
91
+ @listeners.each{|l| l.join }
92
+ @io.close
93
+ trace "Done processing messages"
94
+ end
95
+
96
+ def process_messages_from_master
97
+ @listeners << Thread.new do
98
+ while @running
99
+ begin
100
+ message = @io.gets
101
+ if message and !message.class.to_s.index("Master").nil?
102
+ trace "Received Message from Master"
103
+ trace "\t#{message.inspect}"
104
+ message.handle(self)
105
+ else
106
+ trace "Nothing from Master, Pinging"
107
+ @io.write Ping.new
108
+ end
109
+ rescue IOError => ex
110
+ trace "Worker lost Master"
111
+ Thread.exit
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ def process_messages_from_runners
118
+ @runners.each do |r|
119
+ @listeners << Thread.new do
120
+ while @running
121
+ begin
122
+ message = r[:io].gets
123
+ if message and !message.class.to_s.index("Runner").nil?
124
+ trace "Received Message from Runner"
125
+ trace "\t#{message.inspect}"
126
+ message.handle(self, r)
127
+ end
128
+ rescue IOError => ex
129
+ trace "Worker lost Runner [#{r.inspect}]"
130
+ Thread.exit
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ # Get the next idle runner
138
+ def idle_runner #:nodoc:
139
+ idle_r = nil
140
+ while idle_r.nil?
141
+ idle_r = @runners.detect{|runner| runner[:idle]}
142
+ end
143
+ return idle_r
144
+ end
145
+ end
146
+ 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
+
@@ -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,21 @@
1
+ Given /^a target file$/ do
2
+ @target_file = File.expand_path(File.join(Dir.tmpdir, 'hydra_test.txt'))
3
+ end
4
+
5
+ Given /^an alternate target file$/ do
6
+ @target_file = File.expand_path(File.join(Dir.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,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,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.tmpdir, 'hydra_test.txt'), 'a') do |f|
6
+ f.write "HYDRA"
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,10 @@
1
+ require 'tmpdir'
2
+ require 'spec'
3
+ context "file writing" do
4
+ it "writes to a file" do
5
+ File.open(File.join(Dir.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 'tmpdir'
2
+ require 'spec'
3
+ context "file writing" do
4
+ it "writes to a file" do
5
+ File.open(File.join(Dir.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 'spec'
3
+ context "file writing" do
4
+ it "writes to a file" do
5
+ File.open(File.join(Dir.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,152 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class MasterTest < Test::Unit::TestCase
4
+ context "with a file to test and a destination to verify" do
5
+ setup do
6
+ # avoid having other tests interfering with us
7
+ sleep(0.2)
8
+ FileUtils.rm_f(target_file)
9
+ end
10
+
11
+ teardown do
12
+ FileUtils.rm_f(target_file)
13
+ end
14
+
15
+ should "run a test" do
16
+ Hydra::Master.new(
17
+ :files => [test_file]
18
+ )
19
+ assert File.exists?(target_file)
20
+ assert_equal "HYDRA", File.read(target_file)
21
+ end
22
+
23
+ should "run a spec with pending examples" do
24
+ progress_bar = Hydra::Listener::ProgressBar.new(StringIO.new)
25
+ Hydra::Master.new(
26
+ :files => [rspec_file_with_pending],
27
+ :listeners => [progress_bar]
28
+ )
29
+ assert File.exists?(target_file)
30
+ assert_equal "HYDRA", File.read(target_file)
31
+ assert_equal false, progress_bar.instance_variable_get('@errors')
32
+ end
33
+
34
+ should "generate a report" do
35
+ Hydra::Master.new(:files => [test_file])
36
+ assert File.exists?(target_file)
37
+ assert_equal "HYDRA", File.read(target_file)
38
+ report_file = File.join(Dir.tmpdir, 'hydra_heuristics.yml')
39
+ assert File.exists?(report_file)
40
+ assert report = YAML.load_file(report_file)
41
+ assert_not_nil report[test_file]
42
+ end
43
+
44
+ should "run a test 6 times on 1 worker with 2 runners" do
45
+ Hydra::Master.new(
46
+ :files => [test_file]*6,
47
+ :workers => [ { :type => :local, :runners => 2 } ]
48
+ )
49
+ assert File.exists?(target_file)
50
+ assert_equal "HYDRA"*6, File.read(target_file)
51
+ end
52
+
53
+ # The test being run sleeps for 5 seconds. So, if this was run in
54
+ # series, it would take at least 50 seconds. This test ensures that
55
+ # in runs in less than that amount of time. Since there are 10
56
+ # runners to run the file 10 times, it should only take 5-10 seconds
57
+ # based on overhead.
58
+ should "run a slow test 10 times on 1 worker with 10 runners quickly" do
59
+ start = Time.now
60
+ Hydra::Master.new(
61
+ :files => [File.join(File.dirname(__FILE__), 'fixtures', 'slow.rb')]*10,
62
+ :workers => [
63
+ { :type => :local, :runners => 10 }
64
+ ]
65
+ )
66
+ finish = Time.now
67
+ assert (finish-start) < 30, "took #{finish-start} seconds"
68
+ end
69
+
70
+ should "run a slow test 10 times on 2 workers with 5 runners each quickly" do
71
+ start = Time.now
72
+ Hydra::Master.new(
73
+ :files => [File.join(File.dirname(__FILE__), 'fixtures', 'slow.rb')]*10,
74
+ :workers => [
75
+ { :type => :local, :runners => 5 },
76
+ { :type => :local, :runners => 5 }
77
+ ]
78
+ )
79
+ finish = Time.now
80
+ assert (finish-start) < 15, "took #{finish-start} seconds"
81
+ end
82
+
83
+ should "run a test via ssh" do
84
+ Hydra::Master.new(
85
+ :files => [test_file],
86
+ :workers => [{
87
+ :type => :ssh,
88
+ :connect => 'localhost',
89
+ :directory => File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')),
90
+ :runners => 1
91
+ }]
92
+ )
93
+ assert File.exists?(target_file)
94
+ assert_equal "HYDRA", File.read(target_file)
95
+ end
96
+
97
+ should "run a test with config from a yaml file" do
98
+ Hydra::Master.new(
99
+ :files => [test_file],
100
+ :config => File.join(File.dirname(__FILE__), 'fixtures', 'config.yml')
101
+ )
102
+ assert File.exists?(target_file)
103
+ assert_equal "HYDRA", File.read(target_file)
104
+ end
105
+
106
+ should "synchronize a test file over ssh with rsync" do
107
+ local = File.join(Dir.tmpdir, 'hydra', 'local')
108
+ remote = File.join(Dir.tmpdir, 'hydra', 'remote')
109
+ sync_test = File.join(File.dirname(__FILE__), 'fixtures', 'sync_test.rb')
110
+ [local, remote].each{|f| FileUtils.rm_rf f; FileUtils.mkdir_p f}
111
+
112
+ # setup the folders:
113
+ # local:
114
+ # - test_a
115
+ # - test_c
116
+ # remote:
117
+ # - test_b
118
+ #
119
+ # add test_c to exludes
120
+ FileUtils.cp(sync_test, File.join(local, 'test_a.rb'))
121
+ FileUtils.cp(sync_test, File.join(local, 'test_c.rb'))
122
+ FileUtils.cp(sync_test, File.join(remote, 'test_b.rb'))
123
+
124
+ # ensure a is not on remote
125
+ assert !File.exists?(File.join(remote, 'test_a.rb')), "A should not be on remote"
126
+ # ensure c is not on remote
127
+ assert !File.exists?(File.join(remote, 'test_c.rb')), "C should not be on remote"
128
+ # ensure b is on remote
129
+ assert File.exists?(File.join(remote, 'test_b.rb')), "B should be on remote"
130
+
131
+ Hydra::Master.new(
132
+ :files => ['test_a.rb'],
133
+ :workers => [{
134
+ :type => :ssh,
135
+ :connect => 'localhost',
136
+ :directory => remote,
137
+ :runners => 1
138
+ }],
139
+ :sync => {
140
+ :directory => local,
141
+ :exclude => ['test_c.rb']
142
+ }
143
+ )
144
+ # ensure a is copied
145
+ assert File.exists?(File.join(remote, 'test_a.rb')), "A was not copied"
146
+ # ensure c is not copied
147
+ assert !File.exists?(File.join(remote, 'test_c.rb')), "C was copied, should be excluded"
148
+ # ensure b is deleted
149
+ assert !File.exists?(File.join(remote, 'test_b.rb')), "B was not deleted"
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,31 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class MessageTest < Test::Unit::TestCase
4
+ class MyMessage < Hydra::Message
5
+ attr_accessor :my_var
6
+ def serialize
7
+ super(:my_var => @my_var)
8
+ end
9
+ end
10
+
11
+ context "with a message" do
12
+ setup do
13
+ @m = MyMessage.new(:my_var => 'my value')
14
+ end
15
+ should "set values" do
16
+ assert_equal 'my value', @m.my_var
17
+ end
18
+ should "serialize" do
19
+ assert_equal(
20
+ {:class=>MyMessage, :my_var=>"my value"},
21
+ eval(@m.serialize)
22
+ )
23
+ end
24
+ should "build from serialization" do
25
+ assert_equal(
26
+ @m.my_var,
27
+ Hydra::Message.build(eval(@m.serialize)).my_var
28
+ )
29
+ end
30
+ end
31
+ end
data/test/pipe_test.rb ADDED
@@ -0,0 +1,38 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class PipeTest < Test::Unit::TestCase
4
+ context "a pipe" do
5
+ setup do
6
+ @pipe = Hydra::Pipe.new
7
+ end
8
+ teardown do
9
+ @pipe.close
10
+ end
11
+ should "be able to write messages" do
12
+ child = Process.fork do
13
+ @pipe.identify_as_child
14
+ assert_equal "Test Message", @pipe.gets.text
15
+ @pipe.write Hydra::Messages::TestMessage.new(:text => "Message Received")
16
+ @pipe.write Hydra::Messages::TestMessage.new(:text => "Second Message")
17
+ end
18
+ @pipe.identify_as_parent
19
+ @pipe.write Hydra::Messages::TestMessage.new(:text => "Test Message")
20
+ assert_equal "Message Received", @pipe.gets.text
21
+ assert_equal "Second Message", @pipe.gets.text
22
+ Process.wait(child) #ensure it quits, so there is nothing to write to
23
+ assert_raise IOError do
24
+ @pipe.write Hydra::Messages::TestMessage.new(:text => "anyone there?")
25
+ end
26
+ end
27
+ should "not allow writing if unidentified" do
28
+ assert_raise IOError do
29
+ @pipe.write Hydra::Messages::TestMessage.new(:text => "Test Message")
30
+ end
31
+ end
32
+ should "not allow reading if unidentified" do
33
+ assert_raise IOError do
34
+ @pipe.gets
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,144 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class RunnerTest < Test::Unit::TestCase
4
+ context "with a file to test and a destination to verify" do
5
+ setup do
6
+ sleep(0.2)
7
+ FileUtils.rm_f(target_file)
8
+ FileUtils.rm_f(alternate_target_file)
9
+ end
10
+
11
+ teardown do
12
+ FileUtils.rm_f(target_file)
13
+ FileUtils.rm_f(alternate_target_file)
14
+ end
15
+
16
+
17
+ should "run a test in the foreground" do
18
+ # flip it around to the parent is in the fork, this gives
19
+ # us more direct control over the runner and proper test
20
+ # coverage output
21
+ pipe = Hydra::Pipe.new
22
+ parent = Process.fork do
23
+ request_a_file_and_verify_completion(pipe, test_file)
24
+ end
25
+ run_the_runner(pipe)
26
+ Process.wait(parent)
27
+ end
28
+
29
+ # this flips the above test, so that the main process runs a bit of the parent
30
+ # code, but only with minimal assertion
31
+ should "run a test in the background" do
32
+ pipe = Hydra::Pipe.new
33
+ child = Process.fork do
34
+ run_the_runner(pipe)
35
+ end
36
+ request_a_file_and_verify_completion(pipe, test_file)
37
+ Process.wait(child)
38
+ end
39
+
40
+ should "run two rspec tests" do
41
+ runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
42
+ runner.run_file(rspec_file)
43
+ assert File.exists?(target_file)
44
+ assert_equal "HYDRA", File.read(target_file)
45
+
46
+ FileUtils.rm_f(target_file)
47
+
48
+ runner.run_file(alternate_rspec_file)
49
+ assert File.exists?(alternate_target_file)
50
+ assert_equal "HYDRA", File.read(alternate_target_file)
51
+ assert !File.exists?(target_file)
52
+ end
53
+
54
+ should "run rspec tests with pending examples" do
55
+ runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
56
+ assert File.exists?(rspec_file_with_pending)
57
+
58
+ runner.run_file(rspec_file_with_pending)
59
+
60
+ assert File.exists?(target_file)
61
+ assert_equal "HYDRA", File.read(target_file)
62
+
63
+ FileUtils.rm_f(target_file)
64
+ end
65
+
66
+ should "run two cucumber tests" do
67
+ # because of all the crap cucumber pulls in
68
+ # we run this in a fork to not contaminate
69
+ # the main test environment
70
+ pid = Process.fork do
71
+ puts "THE FOLLOWING WARNINGS CAN BE IGNORED"
72
+ puts "It is caused by Cucumber loading all rb files near its features"
73
+
74
+ runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
75
+ runner.run_file(cucumber_feature_file)
76
+ assert File.exists?(target_file)
77
+ assert_equal "HYDRA", File.read(target_file)
78
+
79
+ FileUtils.rm_f(target_file)
80
+
81
+ runner.run_file(alternate_cucumber_feature_file)
82
+ assert File.exists?(alternate_target_file)
83
+ assert_equal "HYDRA", File.read(alternate_target_file)
84
+ assert !File.exists?(target_file)
85
+
86
+ puts "END IGNORABLE OUTPUT"
87
+ end
88
+ Process.wait pid
89
+ end
90
+
91
+ should "be able to run a runner over ssh" do
92
+ ssh = Hydra::SSH.new(
93
+ 'localhost',
94
+ File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')),
95
+ "ruby -e \"require 'rubygems'; require 'hydra'; Hydra::Runner.new(:io => Hydra::Stdio.new, :verbose => true);\""
96
+ )
97
+ assert ssh.gets.is_a?(Hydra::Messages::Runner::RequestFile)
98
+ ssh.write(Hydra::Messages::Worker::RunFile.new(:file => test_file))
99
+
100
+ # grab its response. This makes us wait for it to finish
101
+ echo = ssh.gets # get the ssh echo
102
+ response = ssh.gets
103
+
104
+ assert_equal Hydra::Messages::Runner::Results, response.class
105
+
106
+ # tell it to shut down
107
+ ssh.write(Hydra::Messages::Worker::Shutdown.new)
108
+
109
+ ssh.close
110
+
111
+ # ensure it ran
112
+ assert File.exists?(target_file)
113
+ assert_equal "HYDRA", File.read(target_file)
114
+ end
115
+ end
116
+
117
+ module RunnerTestHelper
118
+ def request_a_file_and_verify_completion(pipe, file)
119
+ pipe.identify_as_parent
120
+
121
+ # make sure it asks for a file, then give it one
122
+ assert pipe.gets.is_a?(Hydra::Messages::Runner::RequestFile)
123
+ pipe.write(Hydra::Messages::Worker::RunFile.new(:file => file))
124
+
125
+ # grab its response. This makes us wait for it to finish
126
+ response = pipe.gets
127
+ puts response.output
128
+
129
+ # tell it to shut down
130
+ pipe.write(Hydra::Messages::Worker::Shutdown.new)
131
+
132
+ # ensure it ran
133
+ assert File.exists?(target_file)
134
+ assert_equal "HYDRA", File.read(target_file)
135
+ end
136
+
137
+ def run_the_runner(pipe)
138
+ pipe.identify_as_child
139
+ Hydra::Runner.new(:io => pipe)
140
+ end
141
+ end
142
+ include RunnerTestHelper
143
+ end
144
+