diff --git a/Rakefile b/Rakefile index af857fa..8e59fa5 100644 --- a/Rakefile +++ b/Rakefile @@ -11,7 +11,7 @@ begin gem.homepage = "http://github.com/ngauthier/hydra" gem.authors = ["Nick Gauthier"] gem.add_development_dependency "shoulda", "= 2.10.3" - # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings + gem.add_dependency "net-ssh", "= 2.0.19" end Jeweler::GemcutterTasks.new rescue LoadError @@ -34,7 +34,7 @@ begin end rescue LoadError task :rcov do - abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov" + abort "RCov is not available. In order to run rcov, you must: gem install rcov" end end diff --git a/hydra.gemspec b/hydra.gemspec new file mode 100644 index 0000000..dd72fad --- /dev/null +++ b/hydra.gemspec @@ -0,0 +1,62 @@ +# Generated by jeweler +# DO NOT EDIT THIS FILE DIRECTLY +# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = %q{hydra} + s.version = "0.0.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Nick Gauthier"] + s.date = %q{2010-01-26} + s.description = %q{TODO: longer description of your gem} + s.email = %q{nick@smartlogicsolutions.com} + s.extra_rdoc_files = [ + "LICENSE", + "README.rdoc" + ] + s.files = [ + ".document", + ".gitignore", + "LICENSE", + "README.rdoc", + "Rakefile", + "VERSION", + "lib/hydra.rb", + "lib/hydra/pipe.rb", + "lib/hydra/ssh.rb", + "test/echo_the_dolphin.rb", + "test/helper.rb", + "test/test_pipe.rb", + "test/test_ssh.rb" + ] + s.homepage = %q{http://github.com/ngauthier/hydra} + s.rdoc_options = ["--charset=UTF-8"] + s.require_paths = ["lib"] + s.rubygems_version = %q{1.3.5} + s.summary = %q{Distributed testing toolkit} + s.test_files = [ + "test/test_ssh.rb", + "test/helper.rb", + "test/test_pipe.rb", + "test/echo_the_dolphin.rb" + ] + + if s.respond_to? :specification_version then + current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION + s.specification_version = 3 + + if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q, ["= 2.10.3"]) + s.add_runtime_dependency(%q, ["= 2.0.19"]) + else + s.add_dependency(%q, ["= 2.10.3"]) + s.add_dependency(%q, ["= 2.0.19"]) + end + else + s.add_dependency(%q, ["= 2.10.3"]) + s.add_dependency(%q, ["= 2.0.19"]) + end +end + diff --git a/lib/hydra.rb b/lib/hydra.rb index 7f4ad76..633a0ac 100644 --- a/lib/hydra.rb +++ b/lib/hydra.rb @@ -1 +1,2 @@ require 'hydra/pipe' +require 'hydra/ssh' diff --git a/lib/hydra/pipe.rb b/lib/hydra/pipe.rb index f372894..e6d2dcb 100644 --- a/lib/hydra/pipe.rb +++ b/lib/hydra/pipe.rb @@ -16,8 +16,13 @@ module Hydra #:nodoc: # # A pipe is actually two pipes: # - # Parent === Pipe 1 ==> Child - # Parent <== Pipe 2 === Child + # Parent == Pipe 1 ==> Child + # Parent <== Pipe 2 == Child + # + # It's like if you had two cardboard tubes and you were using + # them to drop balls with messages in them between processes. + # One tube is for sending from parent to child, and the other + # tube is for sending from child to parent. class Pipe # Creates a new uninitialized pipe pair. def initialize @@ -43,15 +48,6 @@ module Hydra #:nodoc: end end - # Returns true if there is nothing to read (right now). However it is - # not exactly eof, if the other side writes, this will return false. - # - # It's a good way to tell if there is anything to process right now, - # otherwise, you can sleep. - def eof? - @reader.eof? - end - # Identify this side of the pipe as the child. def identify_as_child @parent_write.close diff --git a/lib/hydra/ssh.rb b/lib/hydra/ssh.rb new file mode 100644 index 0000000..4cc9ec3 --- /dev/null +++ b/lib/hydra/ssh.rb @@ -0,0 +1,48 @@ +require 'open3' +module Hydra #:nodoc: + # Read and write with an ssh connection. For example: + # @ssh = Hydra::SSH.new('nick@nite') + # @ssh.write("echo hi") + # puts @ssh.gets + # => hi + # + # You can also use this to launch an interactive process. For + # example: + # @ssh = Hydra::SSH.new('nick@nite') + # @ssh.write('irb') + # @ssh.write("5+3") + # @ssh.gets + # => "5+3\n" # because irb echoes commands + # @ssh.gets + # => "8" # the output from irb + class SSH + include Open3 + + # Initialize new SSH connection. The single parameters is passed + # directly to ssh for starting a connection. So you can do: + # Hydra::SSH.new('localhost') + # Hydra::SSH.new('user@server.com') + # Hydra::SSH.new('-p 3022 user@server.com') + # etc.. + def initialize(connection_options) + @stdin, @stdout, @stderr = popen3("ssh #{connection_options}") + end + + # Write a string to ssh. This method returns the string passed to + # ssh. Note that if you do not add a newline at the end, it adds + # one for you, and the modified string is returned + def write(str) + unless str =~ /\n$/ + str += "\n" + end + @stdin.write(str) + return str + end + + # Read a line from ssh. This call blocks when there is nothing + # to read. + def gets + @stdout.gets.chomp + end + end +end diff --git a/test/echo_the_dolphin.rb b/test/echo_the_dolphin.rb new file mode 100644 index 0000000..0c67875 --- /dev/null +++ b/test/echo_the_dolphin.rb @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +# read lines from stdin +# echo each line back +# on EOF, quit nicely + +$stdout.sync = true + +while line = $stdin.gets + $stdout.write(line) +end + diff --git a/test/test_pipe.rb b/test/test_pipe.rb index 52932e9..9e19b97 100644 --- a/test/test_pipe.rb +++ b/test/test_pipe.rb @@ -1,6 +1,6 @@ -require 'helper' +require File.join(File.dirname(__FILE__), 'helper') -class TestPipeDream < Test::Unit::TestCase +class TestPipe < Test::Unit::TestCase context "a pipe" do setup do @pipe = Hydra::Pipe.new @@ -16,9 +16,7 @@ class TestPipeDream < Test::Unit::TestCase @pipe.identify_as_parent @pipe.write "Test Message\n" assert_equal "Message Received\n", @pipe.gets - assert !@pipe.eof? assert_equal "Second Message\n", @pipe.gets - assert @pipe.eof? assert_raise Hydra::PipeError::Broken do @pipe.write "anybody home?" end diff --git a/test/test_ssh.rb b/test/test_ssh.rb new file mode 100644 index 0000000..2367a70 --- /dev/null +++ b/test/test_ssh.rb @@ -0,0 +1,29 @@ +require File.join(File.dirname(__FILE__), 'helper') + +class TestSSH < Test::Unit::TestCase + context "an ssh connection" do + setup do + @ssh = Hydra::SSH.new('localhost') + end + should "be able to execute a command" do + @ssh.write "echo hi" + assert_equal "hi", @ssh.gets + end + should "be able to execute a command with a newline" do + @ssh.write "echo hi\n" + assert_equal "hi", @ssh.gets + end + should "be able to communicate with a process" do + pwd = File.dirname(__FILE__) + echo_the_dolphin = File.expand_path( + File.join(File.dirname(__FILE__), 'echo_the_dolphin.rb') + ) + @ssh.write('ruby -e "puts \'Hello\'"') + assert_equal "Hello", @ssh.gets + + @ssh.write("ruby #{echo_the_dolphin}") + @ssh.write("Hello Echo!") + assert_equal "Hello Echo!", @ssh.gets + end + end +end