hydra 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/TODO +11 -0
- data/VERSION +1 -1
- data/hydra.gemspec +5 -2
- data/lib/hydra.rb +1 -0
- data/lib/hydra/master.rb +46 -9
- data/lib/hydra/messaging_io.rb +3 -0
- data/lib/hydra/runner.rb +11 -1
- data/lib/hydra/stdio.rb +16 -0
- data/lib/hydra/worker.rb +1 -1
- data/test/fixtures/slow.rb +9 -0
- data/test/fixtures/write_file.rb +1 -1
- data/test/master_test.rb +59 -3
- data/test/runner_test.rb +23 -3
- data/test/test_helper.rb +2 -2
- metadata +5 -2
data/TODO
CHANGED
@@ -4,6 +4,17 @@ IO selection configuration for master
|
|
4
4
|
|
5
5
|
YML configuration
|
6
6
|
|
7
|
+
---
|
8
|
+
hydra:
|
9
|
+
workers:
|
10
|
+
- type: local
|
11
|
+
runners: 4
|
12
|
+
- type: ssh
|
13
|
+
connect: localhost
|
14
|
+
directory: /path/to/suite
|
15
|
+
[command: rake hydra:worker RUNNERS=1]
|
16
|
+
runners: 4
|
17
|
+
|
7
18
|
v0.6.0
|
8
19
|
|
9
20
|
multitest backwards compatible
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.0
|
data/hydra.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{hydra}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.6.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Nick Gauthier"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-02-03}
|
13
13
|
s.description = %q{Spread your tests over multiple machines to test your code faster.}
|
14
14
|
s.email = %q{nick@smartlogicsolutions.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -36,9 +36,11 @@ Gem::Specification.new do |s|
|
|
36
36
|
"lib/hydra/pipe.rb",
|
37
37
|
"lib/hydra/runner.rb",
|
38
38
|
"lib/hydra/ssh.rb",
|
39
|
+
"lib/hydra/stdio.rb",
|
39
40
|
"lib/hydra/worker.rb",
|
40
41
|
"test/fixtures/assert_true.rb",
|
41
42
|
"test/fixtures/echo_the_dolphin.rb",
|
43
|
+
"test/fixtures/slow.rb",
|
42
44
|
"test/fixtures/write_file.rb",
|
43
45
|
"test/master_test.rb",
|
44
46
|
"test/message_test.rb",
|
@@ -58,6 +60,7 @@ Gem::Specification.new do |s|
|
|
58
60
|
"test/test_helper.rb",
|
59
61
|
"test/ssh_test.rb",
|
60
62
|
"test/fixtures/write_file.rb",
|
63
|
+
"test/fixtures/slow.rb",
|
61
64
|
"test/fixtures/assert_true.rb",
|
62
65
|
"test/fixtures/echo_the_dolphin.rb",
|
63
66
|
"test/master_test.rb",
|
data/lib/hydra.rb
CHANGED
data/lib/hydra/master.rb
CHANGED
@@ -18,7 +18,15 @@ module Hydra #:nodoc:
|
|
18
18
|
@files = opts.fetch(:files) { [] }
|
19
19
|
@workers = []
|
20
20
|
@listeners = []
|
21
|
-
|
21
|
+
@verbose = opts.fetch(:verbose) { false }
|
22
|
+
# default is one worker that is configured to use a pipe with one runner
|
23
|
+
worker_cfg = opts.fetch(:workers) {
|
24
|
+
[ { :type => :local, :runners => 1} ]
|
25
|
+
}
|
26
|
+
|
27
|
+
$stdout.write "MASTER| Initialized\n" if @verbose
|
28
|
+
|
29
|
+
boot_workers worker_cfg
|
22
30
|
process_messages
|
23
31
|
end
|
24
32
|
|
@@ -39,25 +47,54 @@ module Hydra #:nodoc:
|
|
39
47
|
private
|
40
48
|
|
41
49
|
def boot_workers(workers)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
+
$stdout.write "MASTER| Booting workers\n" if @verbose
|
51
|
+
workers.select{|worker| worker[:type] == :local}.each do |worker|
|
52
|
+
boot_local_worker(worker)
|
53
|
+
end
|
54
|
+
workers.select{|worker| worker[:type] == :ssh}.each do |worker|
|
55
|
+
@workers << worker # will boot later, during the listening phase
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def boot_local_worker(worker)
|
60
|
+
runners = worker.fetch(:runners) { raise "You must specify the number of runners" }
|
61
|
+
$stdout.write "MASTER| Booting local worker\n" if @verbose
|
62
|
+
pipe = Hydra::Pipe.new
|
63
|
+
child = Process.fork do
|
64
|
+
pipe.identify_as_child
|
65
|
+
Hydra::Worker.new(:io => pipe, :runners => runners)
|
50
66
|
end
|
67
|
+
pipe.identify_as_parent
|
68
|
+
@workers << { :pid => child, :io => pipe, :idle => false, :type => :local }
|
69
|
+
end
|
70
|
+
|
71
|
+
def boot_ssh_worker(worker)
|
72
|
+
runners = worker.fetch(:runners) { raise "You must specify the number of runners" }
|
73
|
+
connect = worker.fetch(:connect) { raise "You must specify SSH connection options" }
|
74
|
+
directory = worker.fetch(:directory) { raise "You must specify a remote directory" }
|
75
|
+
command = worker.fetch(:command) {
|
76
|
+
"ruby -e \"require 'rubygems'; require 'hydra'; Hydra::Worker.new(:io => Hydra::Stdio.new, :runners => #{runners}, :verbose => #{@verbose});\""
|
77
|
+
}
|
78
|
+
|
79
|
+
$stdout.write "MASTER| Booting SSH worker\n" if @verbose
|
80
|
+
ssh = Hydra::SSH.new(connect, directory, command)
|
81
|
+
return { :io => ssh, :idle => false, :type => :ssh }
|
51
82
|
end
|
52
83
|
|
53
84
|
def process_messages
|
54
85
|
Thread.abort_on_exception = true
|
55
86
|
|
87
|
+
$stdout.write "MASTER| Processing Messages\n" if @verbose
|
88
|
+
$stdout.write "MASTER| Workers: #{@workers}\n" if @verbose
|
56
89
|
@workers.each do |worker|
|
57
90
|
@listeners << Thread.new do
|
91
|
+
$stdout.write "MASTER| Listening to #{worker.inspect}\n" if @verbose
|
92
|
+
worker = boot_ssh_worker(worker) if worker.fetch(:type){ :local } == :ssh
|
58
93
|
while true
|
59
94
|
begin
|
95
|
+
$stdout.write "MASTER| listen....\n" if @verbose
|
60
96
|
message = worker[:io].gets
|
97
|
+
$stdout.write "MASTER| got message: #{message}\n" if @verbose
|
61
98
|
message.handle(self, worker) if message
|
62
99
|
rescue IOError => ex
|
63
100
|
$stderr.write "Master lost Worker [#{worker.inspect}]\n"
|
data/lib/hydra/messaging_io.rb
CHANGED
@@ -12,6 +12,9 @@ module Hydra #:nodoc:
|
|
12
12
|
message = @reader.gets
|
13
13
|
return nil unless message
|
14
14
|
return Message.build(eval(message.chomp))
|
15
|
+
rescue SyntaxError, NameError
|
16
|
+
$stderr.write "Not a message: [#{message.inspect}]\n"
|
17
|
+
return gets
|
15
18
|
end
|
16
19
|
|
17
20
|
# Write a Message to the output IO object. It will automatically
|
data/lib/hydra/runner.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'test/unit/testresult'
|
1
3
|
module Hydra #:nodoc:
|
2
4
|
# Hydra class responsible for running test files.
|
3
5
|
#
|
@@ -15,13 +17,21 @@ module Hydra #:nodoc:
|
|
15
17
|
@verbose = opts.fetch(:verbose) { false }
|
16
18
|
|
17
19
|
Test::Unit.run = true
|
20
|
+
$stdout.sync = true
|
21
|
+
$stdout.write "RUNNER| Booted. Sending Request for file\n" if @verbose
|
18
22
|
|
19
23
|
@io.write RequestFile.new
|
20
|
-
|
24
|
+
begin
|
25
|
+
process_messages
|
26
|
+
rescue => ex
|
27
|
+
$stdout.write "#{ex.to_s}\n" if @verbose
|
28
|
+
raise ex
|
29
|
+
end
|
21
30
|
end
|
22
31
|
|
23
32
|
# Run a test file and report the results
|
24
33
|
def run_file(file)
|
34
|
+
$stdout.write "RUNNER| Running file: #{file}\n" if @verbose
|
25
35
|
require file
|
26
36
|
output = []
|
27
37
|
@result = Test::Unit::TestResult.new
|
data/lib/hydra/stdio.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'hydra/messaging_io'
|
2
|
+
module Hydra #:nodoc:
|
3
|
+
# Read and write via stdout and stdin.
|
4
|
+
class Stdio
|
5
|
+
include Hydra::MessagingIO
|
6
|
+
|
7
|
+
# Initialize new Stdio
|
8
|
+
def initialize()
|
9
|
+
@reader = $stdin
|
10
|
+
@writer = $stdout
|
11
|
+
@reader.sync = true
|
12
|
+
@writer.sync = true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
data/lib/hydra/worker.rb
CHANGED
data/test/fixtures/write_file.rb
CHANGED
@@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), '..', 'test_helper')
|
|
2
2
|
|
3
3
|
class WriteFileTest < Test::Unit::TestCase
|
4
4
|
def test_write_a_file
|
5
|
-
File.open(File.join(Dir.tmpdir, 'hydra_test.txt'), '
|
5
|
+
File.open(File.join(Dir.tmpdir, 'hydra_test.txt'), 'a') do |f|
|
6
6
|
f.write "HYDRA"
|
7
7
|
end
|
8
8
|
end
|
data/test/master_test.rb
CHANGED
@@ -11,9 +11,65 @@ class MasterTest < Test::Unit::TestCase
|
|
11
11
|
end
|
12
12
|
|
13
13
|
should "run a test" do
|
14
|
-
|
15
|
-
:files =>
|
16
|
-
|
14
|
+
Hydra::Master.new(
|
15
|
+
:files => [test_file]
|
16
|
+
)
|
17
|
+
assert File.exists?(target_file)
|
18
|
+
assert_equal "HYDRA", File.read(target_file)
|
19
|
+
end
|
20
|
+
|
21
|
+
should "run a test 6 times on 1 worker with 2 runners" do
|
22
|
+
Hydra::Master.new(
|
23
|
+
:files => [test_file]*6,
|
24
|
+
:local => {
|
25
|
+
:runners => 2
|
26
|
+
}
|
27
|
+
)
|
28
|
+
assert File.exists?(target_file)
|
29
|
+
assert_equal "HYDRA"*6, File.read(target_file)
|
30
|
+
end
|
31
|
+
|
32
|
+
# The test being run sleeps for 2 seconds. So, if this was run in
|
33
|
+
# series, it would take at least 20 seconds. This test ensures that
|
34
|
+
# in runs in less than that amount of time. Since there are 10
|
35
|
+
# runners to run the file 10 times, it should only take 2-4 seconds
|
36
|
+
# based on overhead.
|
37
|
+
should "run a slow test 10 times on 1 worker with 10 runners quickly" do
|
38
|
+
start = Time.now
|
39
|
+
Hydra::Master.new(
|
40
|
+
:files => [File.join(File.dirname(__FILE__), 'fixtures', 'slow.rb')]*10,
|
41
|
+
:workers => [
|
42
|
+
{ :type => :local, :runners => 10 }
|
43
|
+
]
|
44
|
+
)
|
45
|
+
finish = Time.now
|
46
|
+
assert (finish-start) < 15, "took #{finish-start} seconds"
|
47
|
+
end
|
48
|
+
|
49
|
+
should "run a slow test 10 times on 2 workers with 5 runners each quickly" do
|
50
|
+
start = Time.now
|
51
|
+
Hydra::Master.new(
|
52
|
+
:files => [File.join(File.dirname(__FILE__), 'fixtures', 'slow.rb')]*10,
|
53
|
+
:workers => [
|
54
|
+
{ :type => :local, :runners => 5 },
|
55
|
+
{ :type => :local, :runners => 5 }
|
56
|
+
]
|
57
|
+
)
|
58
|
+
finish = Time.now
|
59
|
+
assert (finish-start) < 15, "took #{finish-start} seconds"
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
should "run a test via ssh" do
|
64
|
+
Hydra::Master.new(
|
65
|
+
:files => [test_file],
|
66
|
+
:workers => [{
|
67
|
+
:type => :ssh,
|
68
|
+
:connect => 'localhost',
|
69
|
+
:directory => File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')),
|
70
|
+
:runners => 1
|
71
|
+
}]
|
72
|
+
)
|
17
73
|
assert File.exists?(target_file)
|
18
74
|
assert_equal "HYDRA", File.read(target_file)
|
19
75
|
end
|
data/test/runner_test.rb
CHANGED
@@ -11,7 +11,7 @@ class RunnerTest < Test::Unit::TestCase
|
|
11
11
|
end
|
12
12
|
|
13
13
|
|
14
|
-
should "run a test" do
|
14
|
+
should "run a test in the foreground" do
|
15
15
|
# flip it around to the parent is in the fork, this gives
|
16
16
|
# us more direct control over the runner and proper test
|
17
17
|
# coverage output
|
@@ -25,7 +25,7 @@ class RunnerTest < Test::Unit::TestCase
|
|
25
25
|
|
26
26
|
# this flips the above test, so that the main process runs a bit of the parent
|
27
27
|
# code, but only with minimal assertion
|
28
|
-
should "
|
28
|
+
should "run a test in the background" do
|
29
29
|
pipe = Hydra::Pipe.new
|
30
30
|
child = Process.fork do
|
31
31
|
run_the_runner(pipe)
|
@@ -33,6 +33,26 @@ class RunnerTest < Test::Unit::TestCase
|
|
33
33
|
request_a_file_and_verify_completion(pipe)
|
34
34
|
Process.wait(child)
|
35
35
|
end
|
36
|
+
|
37
|
+
should "be able to run a runner over ssh" do
|
38
|
+
ssh = Hydra::SSH.new(
|
39
|
+
'localhost',
|
40
|
+
File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')),
|
41
|
+
"ruby -e \"require 'rubygems'; require 'hydra'; Hydra::Runner.new(:io => Hydra::Stdio.new);\""
|
42
|
+
)
|
43
|
+
assert ssh.gets.is_a?(Hydra::Messages::Runner::RequestFile)
|
44
|
+
ssh.write(Hydra::Messages::Worker::RunFile.new(:file => test_file))
|
45
|
+
|
46
|
+
# grab its response. This makes us wait for it to finish
|
47
|
+
response = ssh.gets
|
48
|
+
|
49
|
+
# tell it to shut down
|
50
|
+
ssh.write(Hydra::Messages::Worker::Shutdown.new)
|
51
|
+
|
52
|
+
# ensure it ran
|
53
|
+
assert File.exists?(target_file)
|
54
|
+
assert_equal "HYDRA", File.read(target_file)
|
55
|
+
end
|
36
56
|
end
|
37
57
|
|
38
58
|
module RunnerTestHelper
|
@@ -56,7 +76,7 @@ class RunnerTest < Test::Unit::TestCase
|
|
56
76
|
|
57
77
|
def run_the_runner(pipe)
|
58
78
|
pipe.identify_as_child
|
59
|
-
Hydra::Runner.new(
|
79
|
+
Hydra::Runner.new(:io => pipe)
|
60
80
|
end
|
61
81
|
end
|
62
82
|
include RunnerTestHelper
|
data/test/test_helper.rb
CHANGED
@@ -9,11 +9,11 @@ require 'hydra'
|
|
9
9
|
|
10
10
|
class Test::Unit::TestCase
|
11
11
|
def target_file
|
12
|
-
File.join(Dir.tmpdir, 'hydra_test.txt')
|
12
|
+
File.expand_path(File.join(Dir.tmpdir, 'hydra_test.txt'))
|
13
13
|
end
|
14
14
|
|
15
15
|
def test_file
|
16
|
-
File.join(File.dirname(__FILE__), 'fixtures', 'write_file.rb')
|
16
|
+
File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'write_file.rb'))
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hydra
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Gauthier
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-02-03 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -61,9 +61,11 @@ files:
|
|
61
61
|
- lib/hydra/pipe.rb
|
62
62
|
- lib/hydra/runner.rb
|
63
63
|
- lib/hydra/ssh.rb
|
64
|
+
- lib/hydra/stdio.rb
|
64
65
|
- lib/hydra/worker.rb
|
65
66
|
- test/fixtures/assert_true.rb
|
66
67
|
- test/fixtures/echo_the_dolphin.rb
|
68
|
+
- test/fixtures/slow.rb
|
67
69
|
- test/fixtures/write_file.rb
|
68
70
|
- test/master_test.rb
|
69
71
|
- test/message_test.rb
|
@@ -105,6 +107,7 @@ test_files:
|
|
105
107
|
- test/test_helper.rb
|
106
108
|
- test/ssh_test.rb
|
107
109
|
- test/fixtures/write_file.rb
|
110
|
+
- test/fixtures/slow.rb
|
108
111
|
- test/fixtures/assert_true.rb
|
109
112
|
- test/fixtures/echo_the_dolphin.rb
|
110
113
|
- test/master_test.rb
|