sskirby-hydra 0.16.9

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 (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
+