subprocess 0.1.6 → 0.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/README.md +72 -0
  2. data/lib/subprocess.rb +529 -20
  3. metadata +55 -121
  4. data/History.txt +0 -4
  5. data/Manifest.txt +0 -46
  6. data/PostInstall.txt +0 -7
  7. data/README.rdoc +0 -77
  8. data/Rakefile +0 -20
  9. data/TODO.rdoc +0 -1
  10. data/examples/simple.irb +0 -22
  11. data/examples/simple_timeout.irb +0 -22
  12. data/features/multiple_popens_sequence.feature +0 -23
  13. data/features/popen.feature +0 -45
  14. data/features/popen_over_ssh.feature +0 -44
  15. data/features/popen_over_ssh_without_blocking.feature +0 -16
  16. data/features/popen_remote_fails_with_invalid_auth_data.feature +0 -13
  17. data/features/popen_reports_runtime.feature +0 -11
  18. data/features/popen_running.feature +0 -11
  19. data/features/popen_with_timeout.feature +0 -19
  20. data/features/popen_without_blocking.feature +0 -16
  21. data/features/step_definitions/common_steps.rb +0 -168
  22. data/features/step_definitions/multiple_popens_sequence_steps.rb +0 -73
  23. data/features/step_definitions/popen_over_ssh_steps.rb +0 -29
  24. data/features/step_definitions/popen_over_ssh_without_blocking_steps.rb +0 -30
  25. data/features/step_definitions/popen_remote_fails_with_invalid_auth_dat_steps.rb +0 -19
  26. data/features/step_definitions/popen_reports_runtime_steps.rb +0 -13
  27. data/features/step_definitions/popen_running_steps.rb +0 -12
  28. data/features/step_definitions/popen_steps.rb +0 -34
  29. data/features/step_definitions/popen_with_timeout_steps.rb +0 -24
  30. data/features/step_definitions/popen_without_blocking_steps.rb +0 -33
  31. data/features/support/common.rb +0 -29
  32. data/features/support/env.rb +0 -15
  33. data/features/support/matchers.rb +0 -11
  34. data/lib/core_ext/hash.rb +0 -14
  35. data/lib/core_ext/process_status.rb +0 -14
  36. data/lib/subprocess/popen.rb +0 -188
  37. data/lib/subprocess/popen_factory.rb +0 -63
  38. data/lib/subprocess/popen_remote.rb +0 -64
  39. data/lib/subprocess/popen_sequence.rb +0 -57
  40. data/script/console +0 -10
  41. data/script/destroy +0 -14
  42. data/script/generate +0 -14
  43. data/spec/spec.opts +0 -1
  44. data/spec/spec_helper.rb +0 -10
  45. data/spec/subprocess/popen_spec.rb +0 -32
  46. data/spec/subprocess_spec.rb +0 -2
  47. data/subprocess.gemspec +0 -36
  48. 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
-
@@ -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)
@@ -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
-
@@ -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
-