hydra 0.1.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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +9 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/hydra.gemspec +63 -0
- data/lib/hydra.rb +2 -0
- data/lib/hydra/pipe.rb +105 -0
- data/lib/hydra/ssh.rb +48 -0
- data/test/echo_the_dolphin.rb +11 -0
- data/test/helper.rb +10 -0
- data/test/test_pipe.rb +36 -0
- data/test/test_ssh.rb +29 -0
- metadata +91 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Nick Gauthier
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "hydra"
|
8
|
+
gem.summary = %Q{Distributed testing toolkit}
|
9
|
+
gem.description = %Q{Spread your tests over multiple machines to test your code faster.}
|
10
|
+
gem.email = "nick@smartlogicsolutions.com"
|
11
|
+
gem.homepage = "http://github.com/ngauthier/hydra"
|
12
|
+
gem.authors = ["Nick Gauthier"]
|
13
|
+
gem.add_development_dependency "shoulda", "= 2.10.3"
|
14
|
+
gem.add_dependency "net-ssh", "= 2.0.19"
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/test_*.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: gem install rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :test => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "hydra #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/hydra.gemspec
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{hydra}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Nick Gauthier"]
|
12
|
+
s.date = %q{2010-01-26}
|
13
|
+
s.description = %q{Spread your tests over multiple machines to test your code faster.}
|
14
|
+
s.email = %q{nick@smartlogicsolutions.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"hydra.gemspec",
|
27
|
+
"lib/hydra.rb",
|
28
|
+
"lib/hydra/pipe.rb",
|
29
|
+
"lib/hydra/ssh.rb",
|
30
|
+
"test/echo_the_dolphin.rb",
|
31
|
+
"test/helper.rb",
|
32
|
+
"test/test_pipe.rb",
|
33
|
+
"test/test_ssh.rb"
|
34
|
+
]
|
35
|
+
s.homepage = %q{http://github.com/ngauthier/hydra}
|
36
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
37
|
+
s.require_paths = ["lib"]
|
38
|
+
s.rubygems_version = %q{1.3.5}
|
39
|
+
s.summary = %q{Distributed testing toolkit}
|
40
|
+
s.test_files = [
|
41
|
+
"test/test_ssh.rb",
|
42
|
+
"test/helper.rb",
|
43
|
+
"test/test_pipe.rb",
|
44
|
+
"test/echo_the_dolphin.rb"
|
45
|
+
]
|
46
|
+
|
47
|
+
if s.respond_to? :specification_version then
|
48
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
49
|
+
s.specification_version = 3
|
50
|
+
|
51
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
52
|
+
s.add_development_dependency(%q<shoulda>, ["= 2.10.3"])
|
53
|
+
s.add_runtime_dependency(%q<net-ssh>, ["= 2.0.19"])
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
56
|
+
s.add_dependency(%q<net-ssh>, ["= 2.0.19"])
|
57
|
+
end
|
58
|
+
else
|
59
|
+
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
60
|
+
s.add_dependency(%q<net-ssh>, ["= 2.0.19"])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
data/lib/hydra.rb
ADDED
data/lib/hydra/pipe.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
module Hydra #:nodoc:
|
2
|
+
# Read and write between two processes via pipes. For example:
|
3
|
+
# @pipe = Hydra::Pipe.new
|
4
|
+
# Process.fork do
|
5
|
+
# @pipe.identify_as_child
|
6
|
+
# sleep(1)
|
7
|
+
# puts "A message from my parent:\n#{@pipe.gets}"
|
8
|
+
# @pipe.close
|
9
|
+
# end
|
10
|
+
# @pipe.identify_as_parent
|
11
|
+
# @pipe.write "Hello, Child!"
|
12
|
+
# @pipe.close
|
13
|
+
# When the process forks, the pipe is copied. When a pipe is
|
14
|
+
# identified as a parent or child, it is choosing which ends
|
15
|
+
# of the pipe to use.
|
16
|
+
#
|
17
|
+
# A pipe is actually two pipes:
|
18
|
+
#
|
19
|
+
# Parent == Pipe 1 ==> Child
|
20
|
+
# Parent <== Pipe 2 == Child
|
21
|
+
#
|
22
|
+
# It's like if you had two cardboard tubes and you were using
|
23
|
+
# them to drop balls with messages in them between processes.
|
24
|
+
# One tube is for sending from parent to child, and the other
|
25
|
+
# tube is for sending from child to parent.
|
26
|
+
class Pipe
|
27
|
+
# Creates a new uninitialized pipe pair.
|
28
|
+
def initialize
|
29
|
+
@child_read, @parent_write = IO.pipe
|
30
|
+
@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
|
38
|
+
end
|
39
|
+
|
40
|
+
# Write a line to a pipe. It must have a trailing newline.
|
41
|
+
def write(str)
|
42
|
+
force_identification
|
43
|
+
begin
|
44
|
+
@writer.write(str)
|
45
|
+
return str
|
46
|
+
rescue Errno::EPIPE
|
47
|
+
raise Hydra::PipeError::Broken
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Identify this side of the pipe as the child.
|
52
|
+
def identify_as_child
|
53
|
+
@parent_write.close
|
54
|
+
@parent_read.close
|
55
|
+
@reader = @child_read
|
56
|
+
@writer = @child_write
|
57
|
+
end
|
58
|
+
|
59
|
+
# Identify this side of the pipe as the parent
|
60
|
+
def identify_as_parent
|
61
|
+
@child_write.close
|
62
|
+
@child_read.close
|
63
|
+
@reader = @parent_read
|
64
|
+
@writer = @parent_write
|
65
|
+
end
|
66
|
+
|
67
|
+
# closes the pipes. Once a pipe is closed on one end, the other
|
68
|
+
# end will get a PipeError::Broken if it tries to write.
|
69
|
+
def close
|
70
|
+
done_reading
|
71
|
+
done_writing
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def done_writing #:nodoc:
|
76
|
+
@writer.close unless @writer.closed?
|
77
|
+
end
|
78
|
+
|
79
|
+
def done_reading #:nodoc:
|
80
|
+
@reader.close unless @reader.closed?
|
81
|
+
end
|
82
|
+
|
83
|
+
def force_identification #:nodoc:
|
84
|
+
raise PipeError::Unidentified if @reader.nil? or @writer.nil?
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module PipeError #:nodoc:
|
89
|
+
# Raised if you try to read or write to a pipe when it is unidentified.
|
90
|
+
# Use identify_as_parent and identify_as_child to identify a pipe.
|
91
|
+
class Unidentified < RuntimeError
|
92
|
+
def message #:nodoc:
|
93
|
+
"Must identify as child or parent"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
# Raised when a pipe has been broken between two processes.
|
97
|
+
# This happens when a process exits, and is a signal that
|
98
|
+
# there is no more data to communicate.
|
99
|
+
class Broken < RuntimeError
|
100
|
+
def message #:nodoc:
|
101
|
+
"Other side closed the connection"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/hydra/ssh.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'open3'
|
2
|
+
module Hydra #:nodoc:
|
3
|
+
# Read and write with an ssh connection. For example:
|
4
|
+
# @ssh = Hydra::SSH.new('nick@nite')
|
5
|
+
# @ssh.write("echo hi")
|
6
|
+
# puts @ssh.gets
|
7
|
+
# => hi
|
8
|
+
#
|
9
|
+
# You can also use this to launch an interactive process. For
|
10
|
+
# example:
|
11
|
+
# @ssh = Hydra::SSH.new('nick@nite')
|
12
|
+
# @ssh.write('irb')
|
13
|
+
# @ssh.write("5+3")
|
14
|
+
# @ssh.gets
|
15
|
+
# => "5+3\n" # because irb echoes commands
|
16
|
+
# @ssh.gets
|
17
|
+
# => "8" # the output from irb
|
18
|
+
class SSH
|
19
|
+
include Open3
|
20
|
+
|
21
|
+
# Initialize new SSH connection. The single parameters is passed
|
22
|
+
# directly to ssh for starting a connection. So you can do:
|
23
|
+
# Hydra::SSH.new('localhost')
|
24
|
+
# Hydra::SSH.new('user@server.com')
|
25
|
+
# Hydra::SSH.new('-p 3022 user@server.com')
|
26
|
+
# etc..
|
27
|
+
def initialize(connection_options)
|
28
|
+
@stdin, @stdout, @stderr = popen3("ssh #{connection_options}")
|
29
|
+
end
|
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
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/test/helper.rb
ADDED
data/test/test_pipe.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'helper')
|
2
|
+
|
3
|
+
class TestPipe < Test::Unit::TestCase
|
4
|
+
context "a pipe" do
|
5
|
+
setup do
|
6
|
+
@pipe = Hydra::Pipe.new
|
7
|
+
end
|
8
|
+
should "be able to write messages" do
|
9
|
+
Process.fork do
|
10
|
+
@pipe.identify_as_child
|
11
|
+
assert_equal "Test Message\n", @pipe.gets
|
12
|
+
@pipe.write "Message Received\n"
|
13
|
+
@pipe.write "Second Message\n"
|
14
|
+
@pipe.close
|
15
|
+
end
|
16
|
+
@pipe.identify_as_parent
|
17
|
+
@pipe.write "Test Message\n"
|
18
|
+
assert_equal "Message Received\n", @pipe.gets
|
19
|
+
assert_equal "Second Message\n", @pipe.gets
|
20
|
+
assert_raise Hydra::PipeError::Broken do
|
21
|
+
@pipe.write "anybody home?"
|
22
|
+
end
|
23
|
+
@pipe.close
|
24
|
+
end
|
25
|
+
should "not allow writing if unidentified" do
|
26
|
+
assert_raise Hydra::PipeError::Unidentified do
|
27
|
+
@pipe.write "hey\n"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
should "not allow reading if unidentified" do
|
31
|
+
assert_raise Hydra::PipeError::Unidentified do
|
32
|
+
@pipe.gets
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/test/test_ssh.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'helper')
|
2
|
+
|
3
|
+
class TestSSH < Test::Unit::TestCase
|
4
|
+
context "an ssh connection" do
|
5
|
+
setup do
|
6
|
+
@ssh = Hydra::SSH.new('localhost')
|
7
|
+
end
|
8
|
+
should "be able to execute a command" do
|
9
|
+
@ssh.write "echo hi"
|
10
|
+
assert_equal "hi", @ssh.gets
|
11
|
+
end
|
12
|
+
should "be able to execute a command with a newline" do
|
13
|
+
@ssh.write "echo hi\n"
|
14
|
+
assert_equal "hi", @ssh.gets
|
15
|
+
end
|
16
|
+
should "be able to communicate with a process" do
|
17
|
+
pwd = File.dirname(__FILE__)
|
18
|
+
echo_the_dolphin = File.expand_path(
|
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
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hydra
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nick Gauthier
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-26 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: shoulda
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - "="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.10.3
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: net-ssh
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.0.19
|
34
|
+
version:
|
35
|
+
description: Spread your tests over multiple machines to test your code faster.
|
36
|
+
email: nick@smartlogicsolutions.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.rdoc
|
44
|
+
files:
|
45
|
+
- .document
|
46
|
+
- .gitignore
|
47
|
+
- LICENSE
|
48
|
+
- README.rdoc
|
49
|
+
- Rakefile
|
50
|
+
- VERSION
|
51
|
+
- hydra.gemspec
|
52
|
+
- lib/hydra.rb
|
53
|
+
- lib/hydra/pipe.rb
|
54
|
+
- lib/hydra/ssh.rb
|
55
|
+
- test/echo_the_dolphin.rb
|
56
|
+
- test/helper.rb
|
57
|
+
- test/test_pipe.rb
|
58
|
+
- test/test_ssh.rb
|
59
|
+
has_rdoc: true
|
60
|
+
homepage: http://github.com/ngauthier/hydra
|
61
|
+
licenses: []
|
62
|
+
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options:
|
65
|
+
- --charset=UTF-8
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
version:
|
80
|
+
requirements: []
|
81
|
+
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.3.5
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: Distributed testing toolkit
|
87
|
+
test_files:
|
88
|
+
- test/test_ssh.rb
|
89
|
+
- test/helper.rb
|
90
|
+
- test/test_pipe.rb
|
91
|
+
- test/echo_the_dolphin.rb
|