diff options
m--------- | commonlib | 0 | ||||
-rw-r--r-- | lib/external_command.rb | 135 | ||||
-rwxr-xr-x | spec/lib/external_command_scripts/output.sh | 22 | ||||
-rw-r--r-- | spec/lib/external_command_spec.rb | 40 |
4 files changed, 0 insertions, 197 deletions
diff --git a/commonlib b/commonlib -Subproject a87ebeae21166b3b4a8a66b32399861fcd6d0c4 +Subproject cf056c6678d59f74fc29eb2b2c1427573fc643a diff --git a/lib/external_command.rb b/lib/external_command.rb deleted file mode 100644 index 96292854f..000000000 --- a/lib/external_command.rb +++ /dev/null @@ -1,135 +0,0 @@ -# Run an external command, capturing its stdout and stderr -# streams into variables. -# -# So it’s rather like the `backtick` built-in, except that: -# - The command is run as-is, rather than being parsed by the shell; -# - Standard error is also captured. -# -# After the run() method has been called, the instance variables -# out, err and status contain the contents of the process’s stdout, -# the contents of its stderr, and the exit status. -# -# Example usage: -# require 'external_command' -# xc = ExternalCommand("ls", "-l").run() -# puts "Ran ls -l with exit status #{xc.status}" -# puts "===STDOUT===\n#{xc.out}" -# puts "===STDERR===\n#{xc.err}" -# -# The out and err attributes are writeable. If you assign -# a string, after calling the constructor and before calling -# run(), then the subprocess output/error will be appended -# to this string. - -# <rant author="robin"> -# In any sane language, this would be implemented with a -# single child process. The parent process would block on -# select(), and when the child process terminated, the -# select call would be interrupted by a CHLD signal -# and return EINTR. Unfortunately Ruby goes out of its -# way to prevent this from working, automatically restarting -# the select call if EINTR is returned. Therefore we -# use a parent-child-grandchild arrangement, where the -# parent blocks on select() and the child blocks on -# waitpid(). When the child detects that the grandchild -# has finished, it writes to a pipe that’s included in -# the parent’s select() for this purpose. -# </rant> - -class ExternalCommand - attr_accessor :out, :err - attr_reader :status - - def initialize(cmd, *args) - @cmd = cmd - @args = args - - # Strings to collect stdout and stderr from the child process - # These may be replaced by the caller, to append to existing strings. - @out = "" - @err = "" - @fin = "" - end - - def run() - # Pipes for parent-child communication - @out_read, @out_write = IO::pipe - @err_read, @err_write = IO::pipe - @fin_read, @fin_write = IO::pipe - - @pid = fork do - # Here we’re in the child process. - child_process - end - - # Here we’re in the parent process. - parent_process - - return self - end - - private - - def child_process() - # Reopen stdout and stderr to point at the pipes - STDOUT.reopen(@out_write) - STDERR.reopen(@err_write) - - # Close all the filehandles other than the ones we intend to use. - ObjectSpace.each_object(IO) do |fh| - fh.close unless ( - [STDOUT, STDERR, @fin_write].include?(fh) || fh.closed?) - end - - Process::waitpid(fork { grandchild_process }) - @fin_write.puts($?.exitstatus.to_s) - - exit! 0 - end - - def grandchild_process() - exec(@cmd, *@args) - - # This is only reached if the exec fails - @err_write.print("Failed to exec: #{[@cmd, *@args].join(' ')}") - exit! 99 - end - - def parent_process() - # Close the writing ends of the pipes - @out_write.close - @err_write.close - @fin_write.close - - @fhs = {@out_read => @out, @err_read => @err, @fin_read => @fin} - - while @fin.empty? - ok = read_data - if !ok - raise "select() timed out even with a nil (infinite) timeout" - end - end - - while read_data(0) - # Pull out any data that’s left in the pipes - end - - Process::waitpid(@pid) - @status = @fin.to_i - @out_read.close - @err_read.close - end - - def read_data(timeout=nil) - ready_array = IO.select(@fhs.keys, [], [], timeout) - return false if ready_array.nil? - ready_array[0].each do |fh| - begin - @fhs[fh] << fh.readpartial(8192) - rescue EOFError - @fhs.delete fh - end - end - return true - end -end diff --git a/spec/lib/external_command_scripts/output.sh b/spec/lib/external_command_scripts/output.sh deleted file mode 100755 index 0472c89a3..000000000 --- a/spec/lib/external_command_scripts/output.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -out_msg=${1:-out} -err_msg=${2:-} -repeats=${3:-10} -exit_status=${4:-0} - -n=0 -while [ "$n" -lt "$repeats" ] -do - if [ -n "$out_msg" ] - then - echo "$out_msg $n" - fi - if [ -n "$err_msg" ] - then - echo >&2 "$err_msg $n" - fi - n=$[$n + 1] -done - -exit "$exit_status" diff --git a/spec/lib/external_command_spec.rb b/spec/lib/external_command_spec.rb deleted file mode 100644 index 0ff1a9c0a..000000000 --- a/spec/lib/external_command_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -# This is a test of the external_command library - -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') -script_dir = File.join(File.dirname(__FILE__), 'external_command_scripts') -output_script = File.join(script_dir, "output.sh") - -require 'external_command' - -describe "when running ExternalCommand" do - - it "should get correct status code for /bin/true" do - t = ExternalCommand.new("/bin/true").run() - t.status.should == 0 - t.out.should == "" - t.err.should == "" - end - - it "should get correct status code for /bin/false" do - f = ExternalCommand.new("/bin/false").run() - f.status.should == 1 - f.out.should == "" - f.err.should == "" - end - - it "should get stdout and stderr" do - f = ExternalCommand.new(output_script, "out", "err", "10", "23").run() - f.status.should == 23 - f.out.should == (0..9).map {|i| "out #{i}\n"}.join("") - f.err.should == (0..9).map {|i| "err #{i}\n"}.join("") - end - - it "should work with large amounts of data" do - f = ExternalCommand.new(output_script, "a longer output line", "a longer error line", "10000", "5").run() - f.status.should == 5 - f.out.should == (0..9999).map {|i| "a longer output line #{i}\n"}.join("") - f.err.should == (0..9999).map {|i| "a longer error line #{i}\n"}.join("") - end - -end - |