hydra 0.1.1 → 0.2.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 +54 -0
- data/VERSION +1 -1
- data/hydra.gemspec +11 -3
- data/lib/hydra.rb +3 -0
- data/lib/hydra/io.rb +34 -0
- data/lib/hydra/message.rb +18 -0
- data/lib/hydra/message/runner_requests_file.rb +7 -0
- data/lib/hydra/pipe.rb +2 -59
- data/lib/hydra/runner.rb +8 -0
- data/lib/hydra/ssh.rb +6 -19
- data/test/echo_the_dolphin.rb +1 -5
- data/test/helper.rb +15 -0
- data/test/test_pipe.rb +16 -22
- data/test/test_runner.rb +24 -0
- data/test/test_ssh.rb +11 -19
- metadata +10 -2
data/TODO
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# runners = []
|
2
|
+
# cores.each do |c|
|
3
|
+
# in = pipes[c][0]
|
4
|
+
# out = pipes[c][1]
|
5
|
+
# runners << TestRunner.new(in, out)
|
6
|
+
# end
|
7
|
+
#
|
8
|
+
# files = [ ... ]
|
9
|
+
# results = []
|
10
|
+
#
|
11
|
+
# runners.each do |r|
|
12
|
+
# Thread.new do
|
13
|
+
# while !files.empty?
|
14
|
+
# results << r.run_file(files.pop)
|
15
|
+
# end
|
16
|
+
# r.shutdown
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# puts results.join("\n")
|
21
|
+
#
|
22
|
+
|
23
|
+
|
24
|
+
# Master
|
25
|
+
# boot up workers
|
26
|
+
# listen for worker messages
|
27
|
+
# add worker messages to message queue
|
28
|
+
# process message queue
|
29
|
+
# "reply" to a message allows sending a message back down to worker
|
30
|
+
#
|
31
|
+
# When worker asks for file but no files left, send shutdown message to worker
|
32
|
+
# when worker connection breaks, end thread
|
33
|
+
# wait on all threads
|
34
|
+
# when all threads are done, all workers must be done
|
35
|
+
#
|
36
|
+
#
|
37
|
+
# Worker
|
38
|
+
# boot up runners
|
39
|
+
# listen for runner messages
|
40
|
+
# add runner messages to message queue
|
41
|
+
# process message queue
|
42
|
+
# "reply" to a message allows sending message back down to runner
|
43
|
+
#
|
44
|
+
# when a runner asks for file but master responds with shutdown, mark self
|
45
|
+
# as terminated, shut down runners. Any runner that asks for a file is
|
46
|
+
# auto-terminated
|
47
|
+
# wait for runner threads to finish
|
48
|
+
# then exit, breaking master connection
|
49
|
+
#
|
50
|
+
# Runner
|
51
|
+
# when booted, ask for a file
|
52
|
+
# then process messages on the queue
|
53
|
+
# when it's a file, run it and send a results message
|
54
|
+
# when it's a shutdown, break main loop
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/hydra.gemspec
CHANGED
@@ -5,16 +5,17 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{hydra}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.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-01-
|
12
|
+
s.date = %q{2010-01-27}
|
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 = [
|
16
16
|
"LICENSE",
|
17
|
-
"README.rdoc"
|
17
|
+
"README.rdoc",
|
18
|
+
"TODO"
|
18
19
|
]
|
19
20
|
s.files = [
|
20
21
|
".document",
|
@@ -22,14 +23,20 @@ Gem::Specification.new do |s|
|
|
22
23
|
"LICENSE",
|
23
24
|
"README.rdoc",
|
24
25
|
"Rakefile",
|
26
|
+
"TODO",
|
25
27
|
"VERSION",
|
26
28
|
"hydra.gemspec",
|
27
29
|
"lib/hydra.rb",
|
30
|
+
"lib/hydra/io.rb",
|
31
|
+
"lib/hydra/message.rb",
|
32
|
+
"lib/hydra/message/runner_requests_file.rb",
|
28
33
|
"lib/hydra/pipe.rb",
|
34
|
+
"lib/hydra/runner.rb",
|
29
35
|
"lib/hydra/ssh.rb",
|
30
36
|
"test/echo_the_dolphin.rb",
|
31
37
|
"test/helper.rb",
|
32
38
|
"test/test_pipe.rb",
|
39
|
+
"test/test_runner.rb",
|
33
40
|
"test/test_ssh.rb"
|
34
41
|
]
|
35
42
|
s.homepage = %q{http://github.com/ngauthier/hydra}
|
@@ -41,6 +48,7 @@ Gem::Specification.new do |s|
|
|
41
48
|
"test/test_ssh.rb",
|
42
49
|
"test/helper.rb",
|
43
50
|
"test/test_pipe.rb",
|
51
|
+
"test/test_runner.rb",
|
44
52
|
"test/echo_the_dolphin.rb"
|
45
53
|
]
|
46
54
|
|
data/lib/hydra.rb
CHANGED
data/lib/hydra/io.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
module MessagingIO
|
3
|
+
# Read a line from the input IO object.
|
4
|
+
def gets
|
5
|
+
raise IOError unless @reader
|
6
|
+
message = @reader.gets
|
7
|
+
return nil unless message
|
8
|
+
return Message.build(eval(message.chomp))
|
9
|
+
end
|
10
|
+
|
11
|
+
# Write a line to the output IO object
|
12
|
+
def write(message)
|
13
|
+
raise IOError unless @writer
|
14
|
+
raise UnprocessableMessage unless message.is_a?(Hydra::Message)
|
15
|
+
begin
|
16
|
+
@writer.write(message.serialize+"\n")
|
17
|
+
rescue Errno::EPIPE
|
18
|
+
raise IOError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def close
|
23
|
+
@reader.close if @reader
|
24
|
+
@writer.close if @writer
|
25
|
+
end
|
26
|
+
|
27
|
+
class UnprocessableMessage < RuntimeError
|
28
|
+
attr_accessor :message
|
29
|
+
def initialize(message = "Message expected")
|
30
|
+
@message = message
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
class Message #:nodoc:
|
3
|
+
def initialize(opts = {})
|
4
|
+
opts.each do |k,v|
|
5
|
+
self.send(k,v)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
def self.build(hash)
|
9
|
+
hash.delete(:class).new(hash)
|
10
|
+
end
|
11
|
+
|
12
|
+
def serialize(opts = {})
|
13
|
+
opts[:class] = self.class
|
14
|
+
opts.inspect
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
require 'hydra/message/runner_requests_file'
|
data/lib/hydra/pipe.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'hydra/io'
|
1
2
|
module Hydra #:nodoc:
|
2
3
|
# Read and write between two processes via pipes. For example:
|
3
4
|
# @pipe = Hydra::Pipe.new
|
@@ -24,31 +25,11 @@ module Hydra #:nodoc:
|
|
24
25
|
# One tube is for sending from parent to child, and the other
|
25
26
|
# tube is for sending from child to parent.
|
26
27
|
class Pipe
|
28
|
+
include Hydra::MessagingIO
|
27
29
|
# Creates a new uninitialized pipe pair.
|
28
30
|
def initialize
|
29
31
|
@child_read, @parent_write = IO.pipe
|
30
32
|
@parent_read, @child_write = IO.pipe
|
31
|
-
[@parent_write, @child_write].each{|io| io.sync = true}
|
32
|
-
end
|
33
|
-
|
34
|
-
# Read a line from a pipe. It will have a trailing newline.
|
35
|
-
def gets
|
36
|
-
force_identification
|
37
|
-
@reader.gets.chomp
|
38
|
-
end
|
39
|
-
|
40
|
-
# Write a line to a pipe. It must have a trailing newline.
|
41
|
-
def write(str)
|
42
|
-
force_identification
|
43
|
-
unless str =~ /\n$/
|
44
|
-
str += "\n"
|
45
|
-
end
|
46
|
-
begin
|
47
|
-
@writer.write(str)
|
48
|
-
return str
|
49
|
-
rescue Errno::EPIPE
|
50
|
-
raise Hydra::PipeError::Broken
|
51
|
-
end
|
52
33
|
end
|
53
34
|
|
54
35
|
# Identify this side of the pipe as the child.
|
@@ -66,43 +47,5 @@ module Hydra #:nodoc:
|
|
66
47
|
@reader = @parent_read
|
67
48
|
@writer = @parent_write
|
68
49
|
end
|
69
|
-
|
70
|
-
# closes the pipes. Once a pipe is closed on one end, the other
|
71
|
-
# end will get a PipeError::Broken if it tries to write.
|
72
|
-
def close
|
73
|
-
done_reading
|
74
|
-
done_writing
|
75
|
-
end
|
76
|
-
|
77
|
-
private
|
78
|
-
def done_writing #:nodoc:
|
79
|
-
@writer.close unless @writer.closed?
|
80
|
-
end
|
81
|
-
|
82
|
-
def done_reading #:nodoc:
|
83
|
-
@reader.close unless @reader.closed?
|
84
|
-
end
|
85
|
-
|
86
|
-
def force_identification #:nodoc:
|
87
|
-
raise PipeError::Unidentified if @reader.nil? or @writer.nil?
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
module PipeError #:nodoc:
|
92
|
-
# Raised if you try to read or write to a pipe when it is unidentified.
|
93
|
-
# Use identify_as_parent and identify_as_child to identify a pipe.
|
94
|
-
class Unidentified < RuntimeError
|
95
|
-
def message #:nodoc:
|
96
|
-
"Must identify as child or parent"
|
97
|
-
end
|
98
|
-
end
|
99
|
-
# Raised when a pipe has been broken between two processes.
|
100
|
-
# This happens when a process exits, and is a signal that
|
101
|
-
# there is no more data to communicate.
|
102
|
-
class Broken < RuntimeError
|
103
|
-
def message #:nodoc:
|
104
|
-
"Other side closed the connection"
|
105
|
-
end
|
106
|
-
end
|
107
50
|
end
|
108
51
|
end
|
data/lib/hydra/runner.rb
ADDED
data/lib/hydra/ssh.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'open3'
|
2
|
+
require 'hydra/io'
|
2
3
|
module Hydra #:nodoc:
|
3
4
|
# Read and write with an ssh connection. For example:
|
4
5
|
# @ssh = Hydra::SSH.new('nick@nite')
|
@@ -17,6 +18,7 @@ module Hydra #:nodoc:
|
|
17
18
|
# => "8" # the output from irb
|
18
19
|
class SSH
|
19
20
|
include Open3
|
21
|
+
include Hydra::MessagingIO
|
20
22
|
|
21
23
|
# Initialize new SSH connection. The single parameters is passed
|
22
24
|
# directly to ssh for starting a connection. So you can do:
|
@@ -24,25 +26,10 @@ module Hydra #:nodoc:
|
|
24
26
|
# Hydra::SSH.new('user@server.com')
|
25
27
|
# Hydra::SSH.new('-p 3022 user@server.com')
|
26
28
|
# etc..
|
27
|
-
def initialize(connection_options)
|
28
|
-
@
|
29
|
-
|
30
|
-
|
31
|
-
# Write a string to ssh. This method returns the string passed to
|
32
|
-
# ssh. Note that if you do not add a newline at the end, it adds
|
33
|
-
# one for you, and the modified string is returned
|
34
|
-
def write(str)
|
35
|
-
unless str =~ /\n$/
|
36
|
-
str += "\n"
|
37
|
-
end
|
38
|
-
@stdin.write(str)
|
39
|
-
return str
|
40
|
-
end
|
41
|
-
|
42
|
-
# Read a line from ssh. This call blocks when there is nothing
|
43
|
-
# to read.
|
44
|
-
def gets
|
45
|
-
@stdout.gets.chomp
|
29
|
+
def initialize(connection_options, directory, command)
|
30
|
+
@writer, @reader, @error = popen3("ssh #{connection_options}")
|
31
|
+
@writer.write("cd #{directory}\n")
|
32
|
+
@writer.write(command+"\n")
|
46
33
|
end
|
47
34
|
end
|
48
35
|
end
|
data/test/echo_the_dolphin.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -8,3 +8,18 @@ require 'hydra'
|
|
8
8
|
|
9
9
|
class Test::Unit::TestCase
|
10
10
|
end
|
11
|
+
|
12
|
+
module Hydra #:nodoc:
|
13
|
+
module Messages #:nodoc:
|
14
|
+
class TestMessage < Hydra::Message
|
15
|
+
attr_accessor :text
|
16
|
+
def initialize(opts = {})
|
17
|
+
@text = opts.fetch(:text){ "test" }
|
18
|
+
end
|
19
|
+
def serialize
|
20
|
+
super(:text => @text)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
data/test/test_pipe.rb
CHANGED
@@ -5,40 +5,34 @@ class TestPipe < Test::Unit::TestCase
|
|
5
5
|
setup do
|
6
6
|
@pipe = Hydra::Pipe.new
|
7
7
|
end
|
8
|
+
teardown do
|
9
|
+
@pipe.close
|
10
|
+
end
|
8
11
|
should "be able to write messages" do
|
9
|
-
Process.fork do
|
12
|
+
child = Process.fork do
|
10
13
|
@pipe.identify_as_child
|
11
|
-
assert_equal "Test Message", @pipe.gets
|
12
|
-
@pipe.write "Message Received"
|
13
|
-
@pipe.write "Second Message"
|
14
|
-
@pipe.close
|
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")
|
15
17
|
end
|
16
18
|
@pipe.identify_as_parent
|
17
|
-
@pipe.write "Test Message"
|
18
|
-
assert_equal "Message Received", @pipe.gets
|
19
|
-
assert_equal "Second Message", @pipe.gets
|
20
|
-
|
21
|
-
|
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?")
|
22
25
|
end
|
23
|
-
@pipe.close
|
24
26
|
end
|
25
27
|
should "not allow writing if unidentified" do
|
26
|
-
assert_raise
|
27
|
-
@pipe.write "
|
28
|
+
assert_raise IOError do
|
29
|
+
@pipe.write Hydra::Messages::TestMessage.new(:text => "Test Message")
|
28
30
|
end
|
29
31
|
end
|
30
32
|
should "not allow reading if unidentified" do
|
31
|
-
assert_raise
|
33
|
+
assert_raise IOError do
|
32
34
|
@pipe.gets
|
33
35
|
end
|
34
36
|
end
|
35
|
-
should "handle newlines" do
|
36
|
-
Process.fork do
|
37
|
-
@pipe.identify_as_child
|
38
|
-
@pipe.write "Message\n"
|
39
|
-
end
|
40
|
-
@pipe.identify_as_parent
|
41
|
-
assert_equal "Message", @pipe.gets
|
42
|
-
end
|
43
37
|
end
|
44
38
|
end
|
data/test/test_runner.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'helper')
|
2
|
+
|
3
|
+
class TestRunner < Test::Unit::TestCase
|
4
|
+
context "a test runner" do
|
5
|
+
setup do
|
6
|
+
@pipe = Hydra::Pipe.new
|
7
|
+
@child = Process.fork do
|
8
|
+
@pipe.identify_as_child
|
9
|
+
Hydra::Runner.new(@pipe)
|
10
|
+
end
|
11
|
+
@pipe.identify_as_parent
|
12
|
+
end
|
13
|
+
teardown do
|
14
|
+
Process.wait(@child)
|
15
|
+
end
|
16
|
+
should "request a file on boot" do
|
17
|
+
assert @pipe.gets.is_a?(Hydra::Messages::RunnerRequestsFile)
|
18
|
+
end
|
19
|
+
should "return a result message after processing a file" do
|
20
|
+
|
21
|
+
end
|
22
|
+
should "terminate when sent a shutdown message"
|
23
|
+
end
|
24
|
+
end
|
data/test/test_ssh.rb
CHANGED
@@ -3,27 +3,19 @@ require File.join(File.dirname(__FILE__), 'helper')
|
|
3
3
|
class TestSSH < Test::Unit::TestCase
|
4
4
|
context "an ssh connection" do
|
5
5
|
setup do
|
6
|
-
@ssh = Hydra::SSH.new(
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
@ssh = Hydra::SSH.new(
|
7
|
+
'localhost', # connect to this machine
|
8
|
+
File.expand_path(File.join(File.dirname(__FILE__))), # move to the test directory
|
9
|
+
"ruby ./echo_the_dolphin.rb"
|
10
|
+
)
|
11
|
+
@message = Hydra::Messages::TestMessage.new
|
11
12
|
end
|
12
|
-
|
13
|
-
@ssh.
|
14
|
-
assert_equal "hi", @ssh.gets
|
13
|
+
teardown do
|
14
|
+
@ssh.close
|
15
15
|
end
|
16
|
-
should "be able to
|
17
|
-
|
18
|
-
|
19
|
-
File.join(File.dirname(__FILE__), 'echo_the_dolphin.rb')
|
20
|
-
)
|
21
|
-
@ssh.write('ruby -e "puts \'Hello\'"')
|
22
|
-
assert_equal "Hello", @ssh.gets
|
23
|
-
|
24
|
-
@ssh.write("ruby #{echo_the_dolphin}")
|
25
|
-
@ssh.write("Hello Echo!")
|
26
|
-
assert_equal "Hello Echo!", @ssh.gets
|
16
|
+
should "be able to execute a command" do
|
17
|
+
@ssh.write @message
|
18
|
+
assert_equal @message.text, @ssh.gets.text
|
27
19
|
end
|
28
20
|
end
|
29
21
|
end
|
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.2.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-01-
|
12
|
+
date: 2010-01-27 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -41,20 +41,27 @@ extensions: []
|
|
41
41
|
extra_rdoc_files:
|
42
42
|
- LICENSE
|
43
43
|
- README.rdoc
|
44
|
+
- TODO
|
44
45
|
files:
|
45
46
|
- .document
|
46
47
|
- .gitignore
|
47
48
|
- LICENSE
|
48
49
|
- README.rdoc
|
49
50
|
- Rakefile
|
51
|
+
- TODO
|
50
52
|
- VERSION
|
51
53
|
- hydra.gemspec
|
52
54
|
- lib/hydra.rb
|
55
|
+
- lib/hydra/io.rb
|
56
|
+
- lib/hydra/message.rb
|
57
|
+
- lib/hydra/message/runner_requests_file.rb
|
53
58
|
- lib/hydra/pipe.rb
|
59
|
+
- lib/hydra/runner.rb
|
54
60
|
- lib/hydra/ssh.rb
|
55
61
|
- test/echo_the_dolphin.rb
|
56
62
|
- test/helper.rb
|
57
63
|
- test/test_pipe.rb
|
64
|
+
- test/test_runner.rb
|
58
65
|
- test/test_ssh.rb
|
59
66
|
has_rdoc: true
|
60
67
|
homepage: http://github.com/ngauthier/hydra
|
@@ -88,4 +95,5 @@ test_files:
|
|
88
95
|
- test/test_ssh.rb
|
89
96
|
- test/helper.rb
|
90
97
|
- test/test_pipe.rb
|
98
|
+
- test/test_runner.rb
|
91
99
|
- test/echo_the_dolphin.rb
|