subprocess 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +46 -0
  3. data/PostInstall.txt +7 -0
  4. data/README.rdoc +77 -0
  5. data/Rakefile +20 -0
  6. data/TODO.rdoc +1 -0
  7. data/examples/simple.irb +22 -0
  8. data/examples/simple_timeout.irb +22 -0
  9. data/features/multiple_popens_sequence.feature +23 -0
  10. data/features/popen.feature +45 -0
  11. data/features/popen_over_ssh.feature +44 -0
  12. data/features/popen_over_ssh_without_blocking.feature +16 -0
  13. data/features/popen_remote_fails_with_invalid_auth_data.feature +13 -0
  14. data/features/popen_reports_runtime.feature +11 -0
  15. data/features/popen_running.feature +11 -0
  16. data/features/popen_with_timeout.feature +19 -0
  17. data/features/popen_without_blocking.feature +16 -0
  18. data/features/step_definitions/common_steps.rb +168 -0
  19. data/features/step_definitions/multiple_popens_sequence_steps.rb +73 -0
  20. data/features/step_definitions/popen_over_ssh_steps.rb +29 -0
  21. data/features/step_definitions/popen_over_ssh_without_blocking_steps.rb +30 -0
  22. data/features/step_definitions/popen_remote_fails_with_invalid_auth_dat_steps.rb +19 -0
  23. data/features/step_definitions/popen_reports_runtime_steps.rb +13 -0
  24. data/features/step_definitions/popen_running_steps.rb +12 -0
  25. data/features/step_definitions/popen_steps.rb +34 -0
  26. data/features/step_definitions/popen_with_timeout_steps.rb +24 -0
  27. data/features/step_definitions/popen_without_blocking_steps.rb +33 -0
  28. data/features/support/common.rb +29 -0
  29. data/features/support/env.rb +15 -0
  30. data/features/support/matchers.rb +11 -0
  31. data/lib/core_ext/hash.rb +14 -0
  32. data/lib/core_ext/process_status.rb +14 -0
  33. data/lib/subprocess/popen.rb +188 -0
  34. data/lib/subprocess/popen_factory.rb +63 -0
  35. data/lib/subprocess/popen_remote.rb +64 -0
  36. data/lib/subprocess/popen_sequence.rb +57 -0
  37. data/lib/subprocess.rb +23 -0
  38. data/script/console +10 -0
  39. data/script/destroy +14 -0
  40. data/script/generate +14 -0
  41. data/spec/spec.opts +1 -0
  42. data/spec/spec_helper.rb +10 -0
  43. data/spec/subprocess/popen_spec.rb +32 -0
  44. data/spec/subprocess_spec.rb +2 -0
  45. data/subprocess.gemspec +36 -0
  46. data/tasks/rspec.rake +21 -0
  47. metadata +138 -0
@@ -0,0 +1,29 @@
1
+
2
+ Given /^I have a new remote Subprocess instance initialized with "([^\"]*)"$/ do |command|
3
+ @popen_remote = Subprocess::PopenRemote.new(command, 'localhost', 'popen', 10, :password => 'popen')
4
+ end
5
+
6
+ When /^I invoke the run method of said remote subprocess$/ do
7
+ @popen_remote.run
8
+ end
9
+
10
+ When /^I invoke the wait method of said remote subprocess$/ do
11
+ @popen_remote.wait
12
+ end
13
+
14
+ Then /^the remote instances exit status is "([^\"]*)"$/ do |exitstatus|
15
+ @popen_remote.status[:exitstatus].should == exitstatus.to_i
16
+ end
17
+
18
+ Then /^the remote instances stdout matches "([^\"]*)"$/ do |stdout|
19
+ @popen_remote.stdout.should match(stdout)
20
+ end
21
+
22
+ Then /^the remote instances stderr matches "([^\"]*)"$/ do |stderr|
23
+ @popen_remote.stderr.should match(stderr)
24
+ end
25
+
26
+ Then /^the remote instance should have a numerical pid$/ do
27
+ @popen_remote.pid.should be_a_kind_of Fixnum
28
+ end
29
+
@@ -0,0 +1,30 @@
1
+
2
+ Given /^I have a new remote nonblocking subprocess that takes a long time to run$/ do
3
+ @popen = Subprocess::PopenRemote.new('sleep 3 && exit 1', 'localhost', nil, 10, 'popen', :password => 'popen')
4
+ @popen.should_not be_nil
5
+ end
6
+
7
+ When /^I invoke the run method of said nonblocking remote subprocess$/ do
8
+ start_time = Time.now.to_i
9
+ @popen.run
10
+ @total_time = Time.now.to_i - start_time
11
+ end
12
+
13
+ Then /^the remote nonblocking subprocess should not block$/ do
14
+ @total_time.should be_close(0, 2)
15
+ end
16
+
17
+ Then /^the remote nonblocking subprocess should report its run status$/ do
18
+ @popen.should respond_to(:running?)
19
+ end
20
+
21
+ Then /^the remote nonblocking subprocess should support being waited on till complete$/ do
22
+ @popen.wait
23
+ end
24
+
25
+ Then /^the remote nonblocking subprocess should have status info$/ do
26
+ @popen.status[:exitstatus].should be_kind_of Numeric
27
+ @popen.status.should be_a_kind_of Hash
28
+ end
29
+
30
+
@@ -0,0 +1,19 @@
1
+ Given /^I have a new remote subproces with invalid username$/ do
2
+ @popen_remote = Subprocess::PopenRemote.new('tail -n50 /var/log/daemon.log',
3
+ 'localhost', 'someadminnamethatshouldneverexist', 300,
4
+ :password => 'somebadpasswordnooneuses')
5
+ end
6
+
7
+ Given /^invalid password$/ do
8
+ # by leaving this empty its an auto pass and really just serves to make feature read better
9
+ end
10
+
11
+ When /^I run the remote subprocess$/ do
12
+ @popen_remote.run
13
+ end
14
+
15
+ Then /^the remote subprocess should return an error$/ do
16
+ @popen_remote.wait
17
+ #@popen_remote.stderr.should_not be_nil
18
+ end
19
+
@@ -0,0 +1,13 @@
1
+ Given /^I have a new subprocess that takes 3 seconds$/ do
2
+ @popen = Subprocess::Popen.new('sleep 3')
3
+ end
4
+
5
+ When /^I wait on said 3 second process to complete$/ do
6
+ @popen.run
7
+ @popen.wait
8
+ end
9
+
10
+ Then /^the subprocess should report a run time of around 3 seconds$/ do
11
+ @popen.run_time.should be_close(3, 0.2)
12
+ end
13
+
@@ -0,0 +1,12 @@
1
+ Given /^I have a new subprocess that runs fast$/ do
2
+ @popen = Subprocess::Popen.new('echo 1')
3
+ end
4
+
5
+ When /^I invoke the run method of said fast subprocess$/ do
6
+ @popen.run
7
+ end
8
+
9
+ Then /^the subprocess should report running as false without waiting$/ do
10
+ @popen.running?.should be_false
11
+ end
12
+
@@ -0,0 +1,34 @@
1
+ Given /^I have a new Subprocess instance initialized with "([^\"]*)"$/ do |command|
2
+ @popen = Subprocess::Popen.new(command)
3
+ end
4
+
5
+ When /^I invoke the run method of said subprocess$/ do
6
+ @popen.run
7
+ end
8
+
9
+ When /^I invoke the wait method of said subprocess$/ do
10
+ @popen.wait
11
+ end
12
+
13
+ Then /^the instance should have a status attribute$/ do
14
+ @popen.status.should be_a_kind_of Hash
15
+ end
16
+
17
+ Then /^the instances exit status is "([^\"]*)"$/ do |exitstatus|
18
+ @popen.status[:exitstatus].should == exitstatus.to_i
19
+ end
20
+
21
+ Then /^the instances stdout matches "([^\"]*)"$/ do |stdout|
22
+ @popen.stdout.should match(stdout)
23
+ end
24
+
25
+ Then /^the instances stderr matches "([^\"]*)"$/ do |stderr|
26
+ @popen.stderr.should match(stderr)
27
+ end
28
+
29
+ Then /^the instance should have a numerical pid$/ do
30
+ @popen.pid.should be_a_kind_of Fixnum
31
+ end
32
+
33
+
34
+
@@ -0,0 +1,24 @@
1
+ Given /^I have a new subprocess that takes more than 5 seconds to run$/ do
2
+ @popen = Subprocess::Popen.new('sleep 10', 5)
3
+ end
4
+
5
+ Given /^I have a new subprocess that takes less than 5 seconds to run$/ do
6
+ @popen = Subprocess::Popen.new('sleep 1', 5)
7
+ end
8
+
9
+ When /^I invoke the run method of said subprocess with timeout$/ do
10
+ @popen.run
11
+ @popen.wait
12
+ end
13
+
14
+ Given /^I set a timeout of 5 seconds$/ do
15
+ end
16
+
17
+ Then /^the subprocess should exit with exitcode 1$/ do
18
+ @popen.status[:exitstatus].should == 1
19
+ end
20
+
21
+ Then /^the subprocess should complete fine$/ do
22
+ @popen.running?.should be_false
23
+ end
24
+
@@ -0,0 +1,33 @@
1
+
2
+ Given /^I have a new subprocess that takes a long time to run$/ do
3
+ @popen = Subprocess::Popen.new('sleep 3 && exit 1')
4
+ @popen.should_not be_nil
5
+ end
6
+
7
+ When /^I invoke the run method of said nonblocking subprocess$/ do
8
+ start_time = Time.now.to_i
9
+ @popen.run
10
+ @total_time = Time.now.to_i - start_time
11
+ end
12
+
13
+ Then /^the subprocess should not block$/ do
14
+ # this should pass if we backgrounded cause it shouldn't take more than
15
+ # 1 second to get here but the command should take 3 seconds to run
16
+ @total_time.should be_close(0, 2)
17
+ end
18
+
19
+ Then /^the subprocess should report its run status$/ do
20
+ @popen.should respond_to(:running?)
21
+ end
22
+
23
+ Then /^the subprocess should support being waited on till complete$/ do
24
+ @popen.wait
25
+ @popen.status[:exitstatus].should be_kind_of Numeric
26
+ end
27
+
28
+ Then /^the subprocess should have status info$/ do
29
+ @popen.status.should be_a_kind_of Hash
30
+ end
31
+
32
+
33
+
@@ -0,0 +1,29 @@
1
+ module CommonHelpers
2
+ def in_tmp_folder(&block)
3
+ FileUtils.chdir(@tmp_root, &block)
4
+ end
5
+
6
+ def in_project_folder(&block)
7
+ project_folder = @active_project_folder || @tmp_root
8
+ FileUtils.chdir(project_folder, &block)
9
+ end
10
+
11
+ def in_home_folder(&block)
12
+ FileUtils.chdir(@home_path, &block)
13
+ end
14
+
15
+ def force_local_lib_override(project_name = @project_name)
16
+ rakefile = File.read(File.join(project_name, 'Rakefile'))
17
+ File.open(File.join(project_name, 'Rakefile'), "w+") do |f|
18
+ f << "$:.unshift('#{@lib_path}')\n"
19
+ f << rakefile
20
+ end
21
+ end
22
+
23
+ def setup_active_project_folder project_name
24
+ @active_project_folder = File.join(@tmp_root, project_name)
25
+ @project_name = project_name
26
+ end
27
+ end
28
+
29
+ World(CommonHelpers)
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + "/../../lib/subprocess"
2
+
3
+ gem 'cucumber'
4
+ require 'cucumber'
5
+ gem 'rspec', '<= 1.3.0'
6
+ require 'spec'
7
+
8
+ Before do
9
+ @tmp_root = File.dirname(__FILE__) + "/../../tmp"
10
+ @home_path = File.expand_path(File.join(@tmp_root, "home"))
11
+ @lib_path = File.expand_path(File.dirname(__FILE__) + "/../../lib")
12
+ FileUtils.rm_rf @tmp_root
13
+ FileUtils.mkdir_p @home_path
14
+ ENV['HOME'] = @home_path
15
+ end
@@ -0,0 +1,11 @@
1
+ module Matchers
2
+ def contain(expected)
3
+ simple_matcher("contain #{expected.inspect}") do |given, matcher|
4
+ matcher.failure_message = "expected #{given.inspect} to contain #{expected.inspect}"
5
+ matcher.negative_failure_message = "expected #{given.inspect} not to contain #{expected.inspect}"
6
+ given.index expected
7
+ end
8
+ end
9
+ end
10
+
11
+ World(Matchers)
@@ -0,0 +1,14 @@
1
+
2
+ class Hash
3
+ # str8 from activesupport core_ext/hash/keys.rb
4
+ def symbolize_keys
5
+ inject({}) do |options, (key, value)|
6
+ options[(key.to_sym rescue key) || key] = value
7
+ options
8
+ end
9
+ end
10
+ def symbolize_keys!
11
+ self.replace(self.symbolize_keys)
12
+ end
13
+ end
14
+
@@ -0,0 +1,14 @@
1
+
2
+ module Process
3
+ class Status
4
+ def to_hash
5
+ { :exited? => exited?, :exitstatus => exitstatus, :pid => pid,
6
+ :stopped? => stopped?, :stopsig => stopsig, :success? => success?,
7
+ :termsig => termsig, :timed_out? => false }
8
+ end
9
+ def to_json
10
+ to_hash.to_json
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,188 @@
1
+ module Subprocess
2
+ class Popen
3
+ include Timeout
4
+
5
+ attr_accessor :command, :stdout, :stderr, :status, :timeout
6
+
7
+ def initialize(command, timeout=300)
8
+ self.command = command
9
+ self.timeout = timeout
10
+ @running = false
11
+ @ipc_parsed = false
12
+ end
13
+
14
+ def running?
15
+ # return false if no parent pid or if running is already false
16
+ return false unless @parent_pid
17
+ return @running unless @running
18
+
19
+ begin
20
+ # see if the process is running or not
21
+ Process.kill(0, @parent_pid)
22
+ # since we didn't error then we have a pid running
23
+ # so lets see if is over after less than .5 seconds
24
+ begin
25
+ Timeout::timeout(0.5) do
26
+ @parent_pid, parent_status = Process.wait2(@parent_pid, 0)
27
+ end
28
+ rescue Timeout::Error
29
+ # wait timed out so so pid is stll running
30
+ @running = true
31
+ end
32
+ # no timeout so pid is finished
33
+ @running = false
34
+ rescue Errno::ESRCH
35
+ # Process.kill says the pid is not found
36
+ @running = false
37
+ end
38
+
39
+ # parse the child status if pid is complete
40
+ parse_ipc_pipe unless (@running or @ipc_parsed)
41
+ @running
42
+ end
43
+
44
+ def run_time
45
+ defined?(:@status) ? @status[:run_time] : false
46
+ end
47
+
48
+ def pid
49
+ defined?(:@status) ? @status[:pid] : false
50
+ end
51
+
52
+ def perform
53
+ # delayed job anyone?
54
+ run unless running?
55
+ wait
56
+ end
57
+
58
+ def run
59
+ setup_pipes
60
+ # record the time, set running and fork off the command
61
+ @start_time = Time.now.to_f
62
+ @running = true
63
+ @parent_pid = fork_parent
64
+
65
+ # close up the pipes
66
+ [ @stdin_rd, @stdout_wr, @stderr_wr, @ipc_wr ].each do |p|
67
+ p.close
68
+ end
69
+
70
+ # make sure sure the stdin_wr handle is sync
71
+ @stdin_wr.sync = true
72
+ end
73
+
74
+ def wait
75
+ # block until the process completes
76
+ @parent_pid, parent_status = Process.wait2(@parent_pid)
77
+ @running = false
78
+ parse_ipc_pipe unless @ipc_parsed
79
+ end
80
+
81
+ def active_record?
82
+ Module.constants.include?("ActiveRecord")
83
+ end
84
+
85
+ private
86
+ def setup_pipes
87
+ # setup stream pipes and a pipe for interprocess communication
88
+ @stdin_rd, @stdin_wr = IO::pipe
89
+ @stdout_rd, @stdout_wr = IO::pipe
90
+ @stderr_rd, @stderr_wr = IO::pipe
91
+ @ipc_rd, @ipc_wr = IO::pipe
92
+ end
93
+
94
+ def ar_remove_connection
95
+ ActiveRecord::Base.remove_connection
96
+ end
97
+
98
+ def ar_establish_connection(dbconfig)
99
+ ActiveRecord::Base.establish_connection(dbconfig)
100
+ end
101
+
102
+ def fork_parent
103
+ dbconfig = ar_remove_connection if active_record?
104
+ pid = Kernel.fork {
105
+ ar_establish_connection(dbconfig) if active_record?
106
+ begin
107
+ redirect_stdstreams
108
+ report_child_status_to_parent(fork_child)
109
+ teardown_pipes
110
+ ensure
111
+ ar_remove_connection if active_record?
112
+ end
113
+ }
114
+ ar_establish_connection(dbconfig) if active_record?
115
+ pid
116
+ end
117
+
118
+ def redirect_stdstreams
119
+ # close the pipes we don't need
120
+ [ @stdin_wr, @stdout_rd, @stderr_rd ].each do |p|
121
+ p.close
122
+ end
123
+
124
+ # redirect this forks std streams into pipes
125
+ STDIN.reopen(@stdin_rd)
126
+ STDOUT.reopen(@stdout_wr)
127
+ STDERR.reopen(@stderr_wr)
128
+ end
129
+
130
+ def fork_child
131
+ child_status = Hash.new
132
+ child_pid = nil
133
+ start_time = Time.now.to_f
134
+ begin
135
+ Timeout::timeout(self.timeout) do
136
+ dbconfig = ar_remove_connection if active_record?
137
+ begin
138
+ child_pid = Kernel.fork{
139
+ fork_child_exec
140
+ }
141
+ ensure
142
+ ar_establish_connection(dbconfig) if active_record?
143
+ end
144
+ child_status = Process.wait2(child_pid)[1]
145
+ end
146
+ rescue Timeout::Error
147
+ begin
148
+ Process.kill('KILL', child_pid)
149
+ rescue Errno::ESRCH
150
+ end
151
+ child_status[:exitstatus] = 1
152
+ child_status[:timed_out?] = true
153
+ end
154
+ child_status = child_status.to_hash
155
+ child_status[:run_time] = Time.now.to_f - start_time
156
+ child_status
157
+ end
158
+
159
+ def report_child_status_to_parent(child_status)
160
+ @ipc_wr.write child_status.to_json
161
+ @ipc_wr.flush
162
+ end
163
+
164
+ def teardown_pipes
165
+ # close pipes
166
+ [ @stdin_rd, @stdout_wr, @stderr_wr, @ipc_wr ].each do |p|
167
+ p.close
168
+ end
169
+ end
170
+
171
+ def fork_child_exec
172
+ Kernel.exec(@command)
173
+ end
174
+
175
+ def parse_ipc_pipe
176
+ begin
177
+ @status = JSON.parse(@ipc_rd.read).symbolize_keys
178
+ rescue StandardError => err
179
+ @status = { :exitstatus => 1, :timed_out? => false, :error => err }
180
+ end
181
+ @stdout, @stderr = @stdout_rd.read.chomp, @stderr_rd.read.chomp
182
+ @stdout_rd.close; @stderr_rd.close; @ipc_rd.close
183
+ @ipc_parsed = true
184
+ end
185
+
186
+ end # class Popen
187
+ end # module Subprocess
188
+
@@ -0,0 +1,63 @@
1
+ require 'erb'
2
+
3
+ module Subprocess
4
+ module PopenFactoryMixin
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ def def_command(name, command, class_meths=true)
13
+ name = class_meths ? "self.#{name}" : name
14
+ instance_eval "
15
+ def #{name}(*args)
16
+ Subprocess::Popen.new('#{command}', *args)
17
+ end
18
+ ", __FILE__, __LINE__
19
+ end
20
+
21
+ def def_dynamic_command(name, command, class_meths=true)
22
+ name = class_meths ? "self.#{name}" : name
23
+ instance_eval "
24
+ def #{name}(data, *args)
25
+ command = lambda do |data|
26
+ \"#{command}\"
27
+ end
28
+ Subprocess::Popen.new(command.call(data), *args)
29
+ end
30
+ ", __FILE__, __LINE__
31
+ end
32
+ end # ClassMethods
33
+ end # PopenFactoryMixin
34
+
35
+ module PopenRemoteFactoryMixin
36
+
37
+ def self.included(base)
38
+ base.extend(ClassMethods)
39
+ end
40
+
41
+ module ClassMethods
42
+
43
+ def def_command(name, command, hostname_var, username_var, ssh_params_var, timeout=300)
44
+ instance_eval do
45
+ define_method name.to_sym do
46
+ eval("Subprocess::PopenRemote.new('#{command}', #{hostname_var}, #{username_var}, timeout, *#{ssh_params_var})")
47
+ end
48
+ end
49
+ end
50
+
51
+ def def_dynamic_command(name, command, hostname_var, username_var, ssh_params_var, timeout=300)
52
+ instance_eval do
53
+ define_method name.to_sym do |data|
54
+ cmd = ERB.new(command).result(binding)
55
+ eval("Subprocess::PopenRemote.new(cmd, #{hostname_var}, #{username_var}, timeout, *#{ssh_params_var})")
56
+ end
57
+ end
58
+ end
59
+ end # ClassMethods
60
+ end # PopenRemoteFactoryMixin
61
+
62
+ end # Subprocess
63
+
@@ -0,0 +1,64 @@
1
+
2
+ module Subprocess
3
+
4
+ class PopenRemote < Popen
5
+ attr_accessor :hostname, :ssh_params
6
+
7
+ def initialize(command, hostname, username, timeout=300, *ssh_params)
8
+ @command = command
9
+ @timeout = timeout
10
+ @running = false
11
+ @ipc_parsed = false
12
+
13
+ @hostname = hostname
14
+ @username = username
15
+ @ssh_params = ssh_params
16
+ end
17
+
18
+ private
19
+ def log_to_stderr_and_exit(msg)
20
+ $stderr.write("Net::SSH error: #{@hostname} #{msg}")
21
+ exit 1
22
+ end
23
+
24
+ def fork_child_exec
25
+ exit_status = 0
26
+ begin
27
+ ssh = Net::SSH.start(@hostname, @username, *@ssh_params) do |ssh|
28
+ ssh.open_channel do |channel|
29
+ channel.exec(@command) do |chan, success|
30
+ log_to_stderr_and_exit('failed to open exec channel') unless success
31
+
32
+ channel.on_data do |chan, data|
33
+ $stdout.write data
34
+ end
35
+
36
+ channel.on_extended_data do |chan, type, data|
37
+ $stderr.write data
38
+ end
39
+
40
+ channel.on_request('exit-status') do |chan, data|
41
+ exit_status = data.read_long.to_i
42
+ end
43
+
44
+ end
45
+ end
46
+ ssh.loop
47
+ end
48
+ rescue Net::SSH::AuthenticationFailed
49
+ log_to_stderr_and_exit("authentication failure\n")
50
+ rescue Errno::ECONNREFUSED
51
+ log_to_stderr_and_exit("connection refused\n")
52
+ rescue Errno::ETIMEDOUT
53
+ log_to_stderr_and_exit("connection timeout\n")
54
+ rescue Errno::EHOSTUNREACH
55
+ log_to_stderr_and_exit("unreachable\n")
56
+ rescue StandardError => error
57
+ log_to_stderr_and_exit("error: #{error.message}\n")
58
+ end
59
+ exit exit_status
60
+ end
61
+
62
+ end # class PopenRemote
63
+ end # module Subprocess
64
+
@@ -0,0 +1,57 @@
1
+
2
+ module Subprocess
3
+ class PopenSequenceError < StandardError; end
4
+ class PopenSequence
5
+ require 'forwardable'
6
+ extend Forwardable
7
+ attr_reader :running, :queue, :complete, :incomplete, :failed
8
+
9
+ def_delegators :@last, :stdout, :stderr, :status
10
+
11
+ def initialize(queue=[])
12
+ @queue = queue
13
+ @incomplete = queue
14
+ @complete = []
15
+ @failed = []
16
+ @running = false
17
+ @completed = false
18
+ @last = nil
19
+ end
20
+
21
+ def include?(item)
22
+ @queue.include?(item)
23
+ end
24
+
25
+ def [](index)
26
+ @queue[index]
27
+ end
28
+
29
+ def add_popen(popen)
30
+ raise PopenSequenceError,
31
+ "appending a completed sequence is not allowed" if @completed
32
+ @incomplete << popen
33
+ end
34
+ alias :<< :add_popen
35
+
36
+ def perform
37
+ @running = true
38
+ @failure = false
39
+ @queue.each do |popen|
40
+ @last = popen
41
+ popen.perform
42
+ popen.status[:exitstatus] == 0 ? @complete << popen :
43
+ (@failed << popen; break)
44
+ end
45
+ @incomplete = @incomplete - @complete
46
+ @incomplete = @incomplete - @failed
47
+ @running = false
48
+ @completed = true
49
+ end
50
+
51
+ def completed?
52
+ @completed
53
+ end
54
+
55
+ end
56
+ end
57
+
data/lib/subprocess.rb ADDED
@@ -0,0 +1,23 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ begin
5
+ require 'net/ssh'
6
+ require 'json'
7
+ rescue LoadError
8
+ require 'rubygems'
9
+ require 'net/ssh'
10
+ require 'json'
11
+ end
12
+ require 'timeout'
13
+
14
+ module Subprocess
15
+ VERSION = '0.1.6'
16
+ end
17
+
18
+ require 'core_ext/hash'
19
+ require 'core_ext/process_status'
20
+ require 'subprocess/popen'
21
+ require 'subprocess/popen_remote'
22
+ require 'subprocess/popen_sequence'
23
+ require 'subprocess/popen_factory'