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.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +55 -0
- data/TODO +18 -0
- data/VERSION +1 -0
- data/caliper.yml +6 -0
- data/hydra-icon-64x64.png +0 -0
- data/hydra.gemspec +122 -0
- data/hydra_gray.png +0 -0
- data/lib/hydra/cucumber/formatter.rb +30 -0
- data/lib/hydra/hash.rb +16 -0
- data/lib/hydra/listener/abstract.rb +30 -0
- data/lib/hydra/listener/minimal_output.rb +24 -0
- data/lib/hydra/listener/notifier.rb +17 -0
- data/lib/hydra/listener/progress_bar.rb +48 -0
- data/lib/hydra/listener/report_generator.rb +30 -0
- data/lib/hydra/master.rb +224 -0
- data/lib/hydra/message/master_messages.rb +19 -0
- data/lib/hydra/message/runner_messages.rb +46 -0
- data/lib/hydra/message/worker_messages.rb +46 -0
- data/lib/hydra/message.rb +47 -0
- data/lib/hydra/messaging_io.rb +48 -0
- data/lib/hydra/pipe.rb +61 -0
- data/lib/hydra/runner.rb +214 -0
- data/lib/hydra/safe_fork.rb +31 -0
- data/lib/hydra/spec/autorun_override.rb +12 -0
- data/lib/hydra/spec/hydra_formatter.rb +17 -0
- data/lib/hydra/ssh.rb +40 -0
- data/lib/hydra/stdio.rb +16 -0
- data/lib/hydra/sync.rb +99 -0
- data/lib/hydra/tasks.rb +256 -0
- data/lib/hydra/trace.rb +24 -0
- data/lib/hydra/worker.rb +146 -0
- data/lib/hydra.rb +16 -0
- data/test/fixtures/assert_true.rb +7 -0
- data/test/fixtures/config.yml +4 -0
- data/test/fixtures/features/step_definitions.rb +21 -0
- data/test/fixtures/features/write_alternate_file.feature +7 -0
- data/test/fixtures/features/write_file.feature +7 -0
- data/test/fixtures/hello_world.rb +3 -0
- data/test/fixtures/slow.rb +9 -0
- data/test/fixtures/sync_test.rb +8 -0
- data/test/fixtures/write_file.rb +10 -0
- data/test/fixtures/write_file_alternate_spec.rb +10 -0
- data/test/fixtures/write_file_spec.rb +9 -0
- data/test/fixtures/write_file_with_pending_spec.rb +11 -0
- data/test/master_test.rb +152 -0
- data/test/message_test.rb +31 -0
- data/test/pipe_test.rb +38 -0
- data/test/runner_test.rb +144 -0
- data/test/ssh_test.rb +14 -0
- data/test/sync_test.rb +113 -0
- data/test/test_helper.rb +60 -0
- data/test/worker_test.rb +58 -0
- metadata +179 -0
data/lib/hydra/trace.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
# Trace output when in verbose mode.
|
3
|
+
module Trace
|
4
|
+
module ClassMethods
|
5
|
+
# Make a class traceable. Takes one parameter,
|
6
|
+
# which is the prefix for the trace to identify this class
|
7
|
+
def traceable(prefix = self.class.to_s)
|
8
|
+
include Hydra::Trace::InstanceMethods
|
9
|
+
class << self; attr_accessor :_traceable_prefix; end
|
10
|
+
self._traceable_prefix = prefix
|
11
|
+
$stdout.sync = true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
# Trace some output with the class's prefix and a newline.
|
17
|
+
# Checks to ensure we're running verbosely.
|
18
|
+
def trace(str)
|
19
|
+
$stdout.write "#{Time.now.to_f} #{self.class._traceable_prefix}| #{str}\n" if @verbose
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
Object.extend(Hydra::Trace::ClassMethods)
|
data/lib/hydra/worker.rb
ADDED
@@ -0,0 +1,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,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
|
+
|
data/test/master_test.rb
ADDED
@@ -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
|
data/test/runner_test.rb
ADDED
@@ -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
|
+
|