hydra 0.5.0 → 0.6.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.
- 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
|