right_popen 1.1.3 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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