subprocess 0.1.6

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.
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'