right_popen 1.1.3 → 3.0.1

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.
@@ -1,178 +0,0 @@
1
- #-- -*- mode: ruby; encoding: utf-8 -*-
2
- # Copyright: Copyright (c) 2011 RightScale, Inc.
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # 'Software'), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
- # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
- # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
- # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
- # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
- #++
23
-
24
- require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
25
-
26
- module RightScale::RightPopen
27
- describe Utilities do
28
- describe :run do
29
- it 'should collect stdout and stderr' do
30
- out, err = Utilities::run("echo foo; echo bar >&2")
31
- out.should == "foo\n"
32
- err.should == "bar\n"
33
- end
34
-
35
- it 'should handle utilities that expect input' do
36
- out, err = Utilities::run("echo foo; read unused_input; echo bar >&2")
37
- out.should == "foo\n"
38
- err.should == "bar\n"
39
- end
40
-
41
- it 'should raise an error if the command fails' do
42
- lambda { Utilities::run("false") }.should raise_exception(/Command "false" failed with exit status 1/)
43
- end
44
-
45
- it 'should raise an error if the command gets killed' do
46
- lambda { Utilities::run("kill $$") }.should raise_exception(/Command ".*" failed due to SIGTERM/)
47
- end
48
-
49
- it 'should not invoke the shell when inappropriate' do
50
- out, err = Utilities::run(["echo", "foo;", "echo", "bar", ">&2"])
51
- out.should == "foo; echo bar >&2\n"
52
- err.should be_empty
53
- end
54
- end
55
-
56
- describe :run_collecting_output do
57
- it 'should work' do
58
- status, out, err = Utilities::run_collecting_output("echo foo; echo bar >&2")
59
- status.should be_success
60
- out.should == "foo\n"
61
- err.should == "bar\n"
62
- end
63
-
64
- it 'should handle the command failing gracefully' do
65
- status, out, err = Utilities::run_collecting_output("false")
66
- status.should_not be_success
67
- status.exitstatus.should == 1
68
- out.should be_empty
69
- err.should be_empty
70
- end
71
-
72
- it 'should handle the command self terminating gracefully' do
73
- status, out, err = Utilities::run_collecting_output("kill $$")
74
- status.should_not be_success
75
- status.exitstatus.should be_nil
76
- status.termsig.should == 15
77
- out.should be_empty
78
- err.should be_empty
79
- end
80
-
81
- it 'should not invoke the shell when inappropriate' do
82
- status, out, err = Utilities::run_collecting_output(["echo", "foo;", "read", "foo", ";", "echo", "bar", ">&2"])
83
- status.should be_success
84
- out.should == "foo; read foo ; echo bar >&2\n"
85
- err.should be_empty
86
- end
87
- end
88
-
89
- describe :run_with_stdin_collecting_output do
90
- it 'should work even if subprocess ignores stdin' do
91
- status, out, err = Utilities::run_with_stdin_collecting_output("echo foo; echo bar >&2", "foo")
92
- status.should be_success
93
- out.should == "foo\n"
94
- err.should == "bar\n"
95
- end
96
-
97
- it 'should handle utilities that expect input' do
98
- status, out, err = Utilities::run_with_stdin_collecting_output("echo foo; read foo; echo $foo >&2", "blotz")
99
- status.should be_success
100
- out.should == "foo\n"
101
- err.should == "blotz\n"
102
- end
103
-
104
- it 'should handle the command failing gracefully' do
105
- status, out, err = Utilities::run_with_stdin_collecting_output("false", "blotz")
106
- status.should_not be_success
107
- status.exitstatus.should == 1
108
- out.should be_empty
109
- err.should be_empty
110
- end
111
-
112
- it 'should handle the command self terminating gracefully' do
113
- status, out, err = Utilities::run_with_stdin_collecting_output("kill $$", "blotz")
114
- status.should_not be_success
115
- status.exitstatus.should be_nil
116
- status.termsig.should == 15
117
- out.should be_empty
118
- err.should be_empty
119
- end
120
-
121
- it 'should not invoke the shell when inappropriate' do
122
- status, out, err = Utilities::run_with_stdin_collecting_output(["echo", "foo;", "read", "foo", ";", "echo", "bar", ">&2"], "blotz")
123
- status.should be_success
124
- out.should == "foo; read foo ; echo bar >&2\n"
125
- err.should be_empty
126
- end
127
- end
128
-
129
- describe :run_with_blocks do
130
- it 'should work even if subprocess ignores stdin' do
131
- status = Utilities::run_with_blocks("echo foo; echo bar >&2",
132
- lambda { "foo" },
133
- lambda { |b| b.should == "foo\n" },
134
- lambda { |b| b.should == "bar\n" })
135
- status.should be_success
136
- end
137
-
138
- it 'should handle interaction' do
139
- output_counter = 0
140
- seen = {}
141
- in_block = lambda {
142
- if seen[output_counter]
143
- ""
144
- else
145
- seen[output_counter] = true
146
- "foo\n"
147
- end
148
- }
149
- status = Utilities::run_with_blocks("for x in 1 2 3; do read var; echo $var; done; echo bar >&2",
150
- in_block,
151
- lambda { |b| output_counter += 1; b.should == "foo\n" },
152
- lambda { |b| b.should == "bar\n" })
153
- status.should be_success
154
- output_counter.should == 3
155
- end
156
-
157
- it 'should handle the command failing gracefully' do
158
- status = Utilities::run_with_blocks("echo foo; echo bar >&2; false",
159
- lambda { "blotz" }, nil, nil)
160
- status.should_not be_success
161
- status.exitstatus.should == 1
162
- end
163
-
164
- it 'should handle the command self terminating gracefully' do
165
- status = Utilities::run_with_blocks("echo foo; echo bar >&2; kill $$",
166
- lambda { "blotz" }, nil, nil)
167
- status.should_not be_success
168
- status.exitstatus.should be_nil
169
- status.termsig.should == 15
170
- end
171
-
172
- it 'should not invoke the shell when inappropriate' do
173
- status, out, err = Utilities::run_with_blocks(["echo", "foo;", "read", "foo", ";", "echo", "bar", ">&2"], nil, lambda {|b| b.should == "foo; read foo ; echo bar >&2\n"}, nil)
174
- status.should be_success
175
- end
176
- end
177
- end
178
- end
@@ -1,26 +0,0 @@
1
- require ::File.expand_path(::File.join(::File.dirname(__FILE__), '..', 'spec_helper'))
2
- require ::File.expand_path(::File.join(::File.dirname(__FILE__), '..', '..', 'lib', 'right_popen', 'safe_output_buffer'))
3
-
4
- describe RightScale::RightPopen::SafeOutputBuffer do
5
-
6
- context 'given a default buffer' do
7
- subject { described_class.new }
8
-
9
- it 'should limit line count and length' do
10
- (described_class::DEFAULT_MAX_LINE_COUNT * 2).times do |line_index|
11
- data = 'x' * rand(described_class::DEFAULT_MAX_LINE_LENGTH * 3)
12
- subject.safe_buffer_data(data).should be_true
13
- end
14
- subject.buffer.size.should == described_class::DEFAULT_MAX_LINE_COUNT
15
- subject.buffer.first.should == described_class::ELLIPSIS
16
- subject.buffer.last.should_not == described_class::ELLIPSIS
17
- subject.buffer.each do |line|
18
- (line.length <= described_class::DEFAULT_MAX_LINE_LENGTH).should be_true
19
- end
20
- text = subject.display_text
21
- text.should_not be_empty
22
- text.lines.count.should == subject.buffer.size
23
- end
24
- end
25
-
26
- end
@@ -1,331 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
- require File.expand_path(File.join(File.dirname(__FILE__), 'runner'))
3
-
4
- require 'stringio'
5
- require 'tmpdir'
6
-
7
- describe 'RightScale::RightPopen' do
8
- def is_windows?
9
- return !!(RUBY_PLATFORM =~ /mswin|mingw/)
10
- end
11
-
12
- let(:runner) { ::RightScale::RightPopen::Runner.new }
13
-
14
- it "should correctly handle many small processes [async]" do
15
- pending 'Set environment variable TEST_STRESS to enable' unless ENV['TEST_STRESS']
16
- run_count = 100
17
- command = is_windows? ? ['cmd.exe', '/c', 'exit 0'] : ['sh', '-c', 'exit 0']
18
- @completed = 0
19
- @started = 0
20
- run_cmd = Proc.new do
21
- runner.do_right_popen3_async(command, runner_options={}, popen3_options={}) do |runner_status|
22
- @completed += 1
23
- runner_status.status.exitstatus.should == 0
24
- runner_status.output_text.should == ''
25
- runner_status.error_text.should == ''
26
- runner_status.pid.should > 0
27
- end
28
- @started += 1
29
- if @started < run_count
30
- EM.next_tick { run_cmd.call }
31
- end
32
- end
33
- EM.run do
34
- EM.next_tick { run_cmd.call }
35
-
36
- EM::PeriodicTimer.new(1) do
37
- if @completed >= run_count
38
- EM.stop
39
- end
40
- end
41
- end
42
- end
43
-
44
- [:sync, :async].each do |synchronicity|
45
-
46
- context synchronicity do
47
-
48
- it "should redirect output" do
49
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'produce_output.rb'))}\" \"#{STANDARD_MESSAGE}\" \"#{ERROR_MESSAGE}\""
50
- runner_status = runner.run_right_popen3(synchronicity, command)
51
- runner_status.should_not be_nil
52
- runner_status.status.should_not be_nil
53
- runner_status.status.exitstatus.should == 0
54
- runner_status.output_text.should == STANDARD_MESSAGE + "\n"
55
- runner_status.error_text.should == ERROR_MESSAGE + "\n"
56
- runner_status.pid.should > 0
57
- end
58
-
59
- it "should return the right status" do
60
- ruby = `which ruby`.chomp # which is assumed to be on the PATH for the Windows case
61
- command = [
62
- ruby,
63
- File.expand_path(File.join(File.dirname(__FILE__), 'produce_status.rb')),
64
- EXIT_STATUS
65
- ]
66
- status = runner.run_right_popen3(synchronicity, command)
67
- status.status.exitstatus.should == EXIT_STATUS
68
- status.output_text.should == ''
69
- status.error_text.should == ''
70
- status.pid.should > 0
71
- end
72
-
73
- it "should close all IO handlers, except STDIN, STDOUT and STDERR" do
74
- GC.start
75
- command = [
76
- RUBY_CMD,
77
- File.expand_path(File.join(File.dirname(__FILE__), 'produce_status.rb')),
78
- EXIT_STATUS
79
- ]
80
- status = runner.run_right_popen3(synchronicity, command)
81
- status.status.exitstatus.should == EXIT_STATUS
82
- useless_handlers = 0
83
- ObjectSpace.each_object(IO) do |io|
84
- if ![STDIN, STDOUT, STDERR].include?(io)
85
- useless_handlers += 1 unless io.closed?
86
- end
87
- end
88
- useless_handlers.should == 0
89
- end
90
-
91
- it "should preserve the integrity of stdout when stderr is unavailable" do
92
- count = LARGE_OUTPUT_COUNTER
93
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'produce_stdout_only.rb'))}\" #{count}"
94
- status = runner.run_right_popen3(synchronicity, command)
95
- status.status.exitstatus.should == 0
96
-
97
- results = ''
98
- count.times do |i|
99
- results << "stdout #{i}\n"
100
- end
101
- status.output_text.should == results
102
- status.error_text.should == ''
103
- status.pid.should > 0
104
- end
105
-
106
- it "should preserve the integrity of stderr when stdout is unavailable" do
107
- count = LARGE_OUTPUT_COUNTER
108
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'produce_stderr_only.rb'))}\" #{count}"
109
- status = runner.run_right_popen3(synchronicity, command)
110
- status.status.exitstatus.should == 0
111
-
112
- results = ''
113
- count.times do |i|
114
- results << "stderr #{i}\n"
115
- end
116
- status.error_text.should == results
117
- status.output_text.should == ''
118
- status.pid.should > 0
119
- end
120
-
121
- it "should preserve interleaved output when yielding CPU on consumer thread" do
122
- lines = 11
123
- exit_code = 42
124
- repeats = 5
125
- force_yield = 0.1
126
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'produce_mixed_output.rb'))}\" #{lines} #{exit_code}"
127
- actual_output = StringIO.new
128
- actual_error = StringIO.new
129
- puts
130
- stats = runner.run_right_popen3(synchronicity, command, :repeats=>repeats, :force_yield=>force_yield) do |status|
131
- status.status.exitstatus.should == exit_code
132
- status.pid.should > 0
133
- actual_output << status.output_text
134
- actual_error << status.error_text
135
- end
136
- puts
137
- stats.size.should == repeats
138
-
139
- expected_output = StringIO.new
140
- repeats.times do
141
- lines.times do |i|
142
- expected_output << "stdout #{i}\n"
143
- end
144
- end
145
- actual_output.string.should == expected_output.string
146
-
147
- expected_error = StringIO.new
148
- repeats.times do
149
- lines.times do |i|
150
- (expected_error << "stderr #{i}\n") if 0 == i % 10
151
- end
152
- end
153
- actual_error.string.should == expected_error.string
154
- end
155
-
156
- it "should preserve interleaved output when process is spewing rapidly" do
157
- lines = LARGE_OUTPUT_COUNTER
158
- exit_code = 99
159
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'produce_mixed_output.rb'))}\" #{lines} #{exit_code}"
160
- status = runner.run_right_popen3(synchronicity, command, :timeout=>10)
161
- status.status.exitstatus.should == exit_code
162
-
163
- expected_output = StringIO.new
164
- lines.times do |i|
165
- expected_output << "stdout #{i}\n"
166
- end
167
- status.output_text.should == expected_output.string
168
-
169
- expected_error = StringIO.new
170
- lines.times do |i|
171
- (expected_error << "stderr #{i}\n") if 0 == i % 10
172
- end
173
- status.error_text.should == expected_error.string
174
- status.pid.should > 0
175
- end
176
-
177
- it "should setup environment variables" do
178
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'print_env.rb'))}\""
179
- status = runner.run_right_popen3(synchronicity, command)
180
- status.status.exitstatus.should == 0
181
- status.output_text.should_not include('_test_')
182
- status = runner.run_right_popen3(synchronicity, command, :env=>{ :__test__ => '42' })
183
- status.status.exitstatus.should == 0
184
- status.output_text.should match(/^__test__=42$/)
185
- status.pid.should > 0
186
- end
187
-
188
- it "should restore environment variables" do
189
- begin
190
- ENV['__test__'] = '41'
191
- old_envs = {}
192
- ENV.each { |k, v| old_envs[k] = v }
193
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'print_env.rb'))}\""
194
- status = runner.run_right_popen3(synchronicity, command, :env=>{ :__test__ => '42' })
195
- status.status.exitstatus.should == 0
196
- status.output_text.should match(/^__test__=42$/)
197
- ENV.each { |k, v| old_envs[k].should == v }
198
- old_envs.each { |k, v| ENV[k].should == v }
199
- status.pid.should > 0
200
- ensure
201
- ENV.delete('__test__')
202
- end
203
- end
204
-
205
- if is_windows?
206
- # FIX: this behavior is currently specific to Windows but should probably be
207
- # implemented for Linux.
208
- it "should merge the PATH variable instead of overriding it" do
209
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'print_env.rb'))}\""
210
- status = runner.run_right_popen3(synchronicity, command, :env=>{ 'PATH' => "c:/bogus\\bin" })
211
- status.status.exitstatus.should == 0
212
- status.output_text.should include('c:\\bogus\\bin;')
213
- status.pid.should > 0
214
- end
215
- else
216
- it "should allow running bash command lines starting with a built-in command" do
217
- command = "for i in 1 2 3 4 5; do echo $i;done"
218
- status = runner.run_right_popen3(synchronicity, command)
219
- status.status.exitstatus.should == 0
220
- status.output_text.should == "1\n2\n3\n4\n5\n"
221
- status.pid.should > 0
222
- end
223
-
224
- it "should support running background processes" do
225
- command = "(sleep 20)&"
226
- now = Time.now
227
- status = runner.run_right_popen3(synchronicity, command)
228
- finished = Time.now
229
- (finished - now).should < 20
230
- status.did_timeout.should be_false
231
- status.status.exitstatus.should == 0
232
- status.output_text.should == ""
233
- status.pid.should > 0
234
- end
235
- end
236
-
237
- it "should support raw command arguments" do
238
- command = is_windows? ? ["cmd.exe", "/c", "echo", "*"] : ["echo", "*"]
239
- status = runner.run_right_popen3(synchronicity, command)
240
- status.status.exitstatus.should == 0
241
- status.output_text.should == "*\n"
242
- status.pid.should > 0
243
- end
244
-
245
- it "should run repeatedly without leaking resources" do
246
- pending 'Set environment variable TEST_LEAK to enable' unless ENV['TEST_LEAK']
247
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'produce_output.rb'))}\" \"#{STANDARD_MESSAGE}\" \"#{ERROR_MESSAGE}\""
248
- stats = runner.run_right_popen3(synchronicity, command, :repeats=>REPEAT_TEST_COUNTER)
249
- stats.each do |status|
250
- status.status.exitstatus.should == 0
251
- status.output_text.should == STANDARD_MESSAGE + "\n"
252
- status.error_text.should == ERROR_MESSAGE + "\n"
253
- status.pid.should > 0
254
- end
255
- end
256
-
257
- it "should pass input to child process" do
258
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'increment.rb'))}\""
259
- status = runner.run_right_popen3(synchronicity, command, :input=>"42\n")
260
- status.status.exitstatus.should == 0
261
- status.output_text.should == "43\n"
262
- status.error_text.should be_empty
263
- status.pid.should > 0
264
- end
265
-
266
- it "should run long child process without any watches by default" do
267
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'sleeper.rb'))}\""
268
- runner_status = runner.run_right_popen3(synchronicity, command, :timeout=>nil)
269
- runner_status.status.exitstatus.should == 0
270
- runner_status.did_timeout.should be_false
271
- runner_status.output_text.should == "To sleep... 0\nTo sleep... 1\nTo sleep... 2\nTo sleep... 3\nThe sleeper must awaken.\n"
272
- runner_status.error_text.should == "Perchance to dream... 0\nPerchance to dream... 1\nPerchance to dream... 2\nPerchance to dream... 3\n"
273
- end
274
-
275
- it "should interrupt watched child process when timeout expires" do
276
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'sleeper.rb'))}\" 10"
277
- runner_status = runner.run_right_popen3(synchronicity, command, :expect_timeout=>true, :timeout=>0.1)
278
- runner_status.status.success?.should be_false
279
- runner_status.did_timeout.should be_true
280
- runner_status.output_text.should_not be_empty
281
- runner_status.error_text.should_not be_empty
282
- end
283
-
284
- it "should allow watched child to write files up to size limit" do
285
- ::Dir.mktmpdir do |watched_dir|
286
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'writer.rb'))}\" \"#{watched_dir}\""
287
- runner_status = runner.run_right_popen3(synchronicity, command, :size_limit_bytes=>1000, :watch_directory=>watched_dir, :timeout=>10)
288
- runner_status.status.success?.should be_true
289
- runner_status.did_size_limit.should be_false
290
- end
291
- end
292
-
293
- it "should interrupt watched child at size limit" do
294
- ::Dir.mktmpdir do |watched_dir|
295
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'writer.rb'))}\" \"#{watched_dir}\""
296
- runner_status = runner.run_right_popen3(synchronicity, command, :expect_size_limit=>true, :size_limit_bytes=>100, :watch_directory=>watched_dir, :timeout=>10)
297
- runner_status.status.success?.should be_false
298
- runner_status.did_size_limit.should be_true
299
- end
300
- end
301
-
302
- it "should handle child processes that close stdout but keep running" do
303
- pending 'not implemented for windows' if is_windows? && :sync != synchronicity
304
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'stdout.rb'))}\""
305
- runner_status = runner.run_right_popen3(synchronicity, command, :expect_timeout=>true, :timeout=>2)
306
- runner_status.output_text.should be_empty
307
- runner_status.error_text.should == "Closing stdout\n"
308
- runner_status.did_timeout.should be_true
309
- end
310
-
311
- it "should handle child processes that spawn long running background processes" do
312
- pending 'not implemented for windows' if is_windows?
313
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'background.rb'))}\""
314
- status = runner.run_right_popen3(synchronicity, command)
315
- status.status.exitstatus.should == 0
316
- status.did_timeout.should be_false
317
- status.output_text.should be_empty
318
- status.error_text.should be_empty
319
- end
320
-
321
- it "should run long child process without any watches by default" do
322
- command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'sleeper.rb'))}\""
323
- runner_status = runner.run_right_popen3(synchronicity, command, :timeout=>nil)
324
- runner_status.status.exitstatus.should == 0
325
- runner_status.did_timeout.should be_false
326
- runner_status.output_text.should == "To sleep... 0\nTo sleep... 1\nTo sleep... 2\nTo sleep... 3\nThe sleeper must awaken.\n"
327
- runner_status.error_text.should == "Perchance to dream... 0\nPerchance to dream... 1\nPerchance to dream... 2\nPerchance to dream... 3\n"
328
- end
329
- end
330
- end
331
- end