subprocess 0.1.6 → 0.15
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +72 -0
- data/lib/subprocess.rb +529 -20
- metadata +55 -121
- data/History.txt +0 -4
- data/Manifest.txt +0 -46
- data/PostInstall.txt +0 -7
- data/README.rdoc +0 -77
- data/Rakefile +0 -20
- data/TODO.rdoc +0 -1
- data/examples/simple.irb +0 -22
- data/examples/simple_timeout.irb +0 -22
- data/features/multiple_popens_sequence.feature +0 -23
- data/features/popen.feature +0 -45
- data/features/popen_over_ssh.feature +0 -44
- data/features/popen_over_ssh_without_blocking.feature +0 -16
- data/features/popen_remote_fails_with_invalid_auth_data.feature +0 -13
- data/features/popen_reports_runtime.feature +0 -11
- data/features/popen_running.feature +0 -11
- data/features/popen_with_timeout.feature +0 -19
- data/features/popen_without_blocking.feature +0 -16
- data/features/step_definitions/common_steps.rb +0 -168
- data/features/step_definitions/multiple_popens_sequence_steps.rb +0 -73
- data/features/step_definitions/popen_over_ssh_steps.rb +0 -29
- data/features/step_definitions/popen_over_ssh_without_blocking_steps.rb +0 -30
- data/features/step_definitions/popen_remote_fails_with_invalid_auth_dat_steps.rb +0 -19
- data/features/step_definitions/popen_reports_runtime_steps.rb +0 -13
- data/features/step_definitions/popen_running_steps.rb +0 -12
- data/features/step_definitions/popen_steps.rb +0 -34
- data/features/step_definitions/popen_with_timeout_steps.rb +0 -24
- data/features/step_definitions/popen_without_blocking_steps.rb +0 -33
- data/features/support/common.rb +0 -29
- data/features/support/env.rb +0 -15
- data/features/support/matchers.rb +0 -11
- data/lib/core_ext/hash.rb +0 -14
- data/lib/core_ext/process_status.rb +0 -14
- data/lib/subprocess/popen.rb +0 -188
- data/lib/subprocess/popen_factory.rb +0 -63
- data/lib/subprocess/popen_remote.rb +0 -64
- data/lib/subprocess/popen_sequence.rb +0 -57
- data/script/console +0 -10
- data/script/destroy +0 -14
- data/script/generate +0 -14
- data/spec/spec.opts +0 -1
- data/spec/spec_helper.rb +0 -10
- data/spec/subprocess/popen_spec.rb +0 -32
- data/spec/subprocess_spec.rb +0 -2
- data/subprocess.gemspec +0 -36
- data/tasks/rspec.rake +0 -21
@@ -1,19 +0,0 @@
|
|
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
|
-
|
@@ -1,13 +0,0 @@
|
|
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
|
-
|
@@ -1,12 +0,0 @@
|
|
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
|
-
|
@@ -1,34 +0,0 @@
|
|
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
|
-
|
@@ -1,24 +0,0 @@
|
|
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
|
-
|
@@ -1,33 +0,0 @@
|
|
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
|
-
|
data/features/support/common.rb
DELETED
@@ -1,29 +0,0 @@
|
|
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)
|
data/features/support/env.rb
DELETED
@@ -1,15 +0,0 @@
|
|
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
|
@@ -1,11 +0,0 @@
|
|
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)
|
data/lib/core_ext/hash.rb
DELETED
@@ -1,14 +0,0 @@
|
|
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
|
-
|
@@ -1,14 +0,0 @@
|
|
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
|
-
|
data/lib/subprocess/popen.rb
DELETED
@@ -1,188 +0,0 @@
|
|
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
|
-
|
@@ -1,63 +0,0 @@
|
|
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
|
-
|