subprocess 0.1.6 → 0.15
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.
- 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
|
-
|