process_helper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'process_helper/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'process_helper'
8
+ spec.version = ProcessHelper::VERSION
9
+ spec.authors = ['Glenn Oppegard', 'Chad Woolley']
10
+ spec.email = ['oppegard@gmail.com', 'thewoolleyman@gmail.com']
11
+ spec.summary = "Makes it easier to spawn ruby sub-processes with proper capturing /
12
+ of stdout and stderr streams."
13
+ spec.description = 'Wrapper around Open3#popen2e with other useful options.'
14
+ spec.homepage = 'https://github.com/thewoolleyman/process_helper'
15
+ spec.license = 'Unlicense'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(/^(test|spec|features)\//)
20
+ spec.require_paths = ['lib']
21
+ spec.required_ruby_version = '>= 1.9.2'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.6'
24
+ spec.add_development_dependency 'codeclimate-test-reporter'
25
+ spec.add_development_dependency 'rake', '~> 10'
26
+ spec.add_development_dependency 'rspec', '~> 3.1'
27
+ spec.add_development_dependency 'rubocop', '= 0.29.1' # exact version for static analyis libs
28
+ spec.add_development_dependency 'ruby-lint', '= 2.0.2' # exact version for static analyis libs
29
+ end
data/ruby-lint.yml ADDED
@@ -0,0 +1,12 @@
1
+ ---
2
+ # http://code.yorickpeterse.com/ruby-lint/latest/
3
+ analysis_classes:
4
+ - argument_amount
5
+ - pedantics
6
+ - shadowing_variables
7
+ # - undefined_methods
8
+ # - undefined_variables
9
+ - unused_variables
10
+ - useless_equality_checks
11
+ directories:
12
+ - spec
@@ -0,0 +1,21 @@
1
+ require_relative 'spec_helper'
2
+
3
+ RSpec.describe 'error handling' do
4
+ attr_reader :clazz
5
+
6
+ before do
7
+ @clazz = Clazz.new
8
+ end
9
+
10
+ it 'fails if command is nil' do
11
+ expect { clazz.process(nil) }.to raise_error(
12
+ ProcessHelper::EmptyCommandError,
13
+ 'command must not be empty')
14
+ end
15
+
16
+ it 'fails if command is empty' do
17
+ expect { clazz.process('') }.to raise_error(
18
+ ProcessHelper::EmptyCommandError,
19
+ 'command must not be empty')
20
+ end
21
+ end
@@ -0,0 +1,125 @@
1
+ require_relative 'spec_helper'
2
+
3
+ RSpec.describe 'input handling' do
4
+ attr_reader :clazz, :max_process_wait
5
+
6
+ before do
7
+ @clazz = Clazz.new
8
+ @max_process_wait = ENV['MAX_PROCESS_WAIT'] ? ENV['MAX_PROCESS_WAIT'].to_f : 0.5
9
+ end
10
+
11
+ describe 'with non-exiting blocking cmd requiring timeout' do
12
+ it 'handles a single line of STDIN to STDOUT with ruby output flushing' do
13
+ expect do
14
+ clazz.process(
15
+ "ruby -e 'while(i=$stdin.gets) do puts i; $stdout.flush; end'",
16
+ input_lines: ['input1'],
17
+ timeout: max_process_wait
18
+ )
19
+ end.to output(/input1\n/).to_stdout
20
+ .and(not_output.to_stderr)
21
+ end
22
+
23
+ it 'handles multiple lines of STDIN to STDOUT with ruby output flushing' do
24
+ expect do
25
+ clazz.process(
26
+ "ruby -e 'while(i=$stdin.gets) do puts i; $stdout.flush; end'",
27
+ input_lines: %w(input1 input2),
28
+ timeout: max_process_wait
29
+ )
30
+ end.to output(/input1\ninput2\n/).to_stdout
31
+ .and(not_output.to_stderr)
32
+ end
33
+
34
+ it 'handles cat cmd' do
35
+ expect do
36
+ clazz.process(
37
+ # -u disables output buffering, -n numbers output lines (to distinguish from input)
38
+ 'cat -u -n',
39
+ # TODO: how to send Ctrl-D to exit without timeout being required?
40
+ input_lines: ['line1', 'line2', 'line3', "\C-d"],
41
+ timeout: max_process_wait
42
+ )
43
+ end.to output(/.*1\tline1\n.*2\tline2\n.*3\tline3\n.*4\t\u0004\n/).to_stdout
44
+ .and(not_output.to_stderr)
45
+ end
46
+
47
+ it 'handles interleaved stdout and stderr based on stdin input' do
48
+ expect do
49
+ cmd =
50
+ 'while ' \
51
+ ' line = $stdin.readline; ' \
52
+ ' $stdout.puts("out:#{line}"); ' \
53
+ ' $stdout.flush; ' \
54
+ ' $stderr.puts("err:#{line}"); ' \
55
+ ' $stderr.flush; ' \
56
+ ' exit 0 if line =~ /exit/; ' \
57
+ 'end'
58
+ clazz.process(
59
+ %(ruby -e '#{cmd}'),
60
+ input_lines: %w(line1 line2 line3 exit),
61
+ timeout: max_process_wait
62
+ )
63
+ end.to output(/out:line1\nerr:line1\nout:line2\nerr:line2\nout:line3\nerr:line3\n/).to_stdout
64
+ .and(not_output.to_stderr)
65
+ end
66
+ end
67
+
68
+ describe 'with exiting cmd' do
69
+ it 'handles stdout and stderr triggered via stdin' do
70
+ expect do
71
+ clazz.process(
72
+ 'irb -f --prompt=default',
73
+ input_lines: [
74
+ '$stdout.puts "hi"',
75
+ '$stdout.flush',
76
+ '$stderr.puts "aaa\nbbb\nccc"',
77
+ '$stderr.flush',
78
+ '$stdout.puts "bye"',
79
+ '$stdout.flush',
80
+ 'exit'
81
+ ]
82
+ )
83
+ end.to output(/\nhi\n.*\naaa\nbbb\nccc.*\nbye\n/m).to_stdout
84
+ .and(not_output.to_stderr)
85
+ end
86
+
87
+ it 'handles unexpected exit status' do
88
+ expect do
89
+ clazz.process(
90
+ "ruby -e 'i=$stdin.gets; $stdout.puts i; $stdout.flush; " \
91
+ "$stderr.puts i; $stderr.flush; exit 1'",
92
+ puts_output: :error,
93
+ input_lines: ['hi']
94
+ )
95
+ end.to raise_error(
96
+ ProcessHelper::UnexpectedExitStatusError,
97
+ /Command failed/)
98
+ .and(output(/hi\nhi\n/).to_stdout)
99
+ end
100
+
101
+ it 'pipes input before processing output' do
102
+ expect do
103
+ clazz.process(
104
+ "ruby -e 'i=$stdin.gets; $stdout.puts i; $stdout.flush; exit'",
105
+ input_lines: ['hi']
106
+ )
107
+ end.to output(/hi\n/m).to_stdout
108
+ .and(not_output.to_stderr)
109
+ end
110
+
111
+ it 'fails if unprocessed input remains when command exits' do
112
+ # TODO: This fails when run with code coverage instrumentation
113
+ # enabled (via RubyMine). Why???
114
+ expect do
115
+ clazz.process(
116
+ "ruby -e 'i=$stdin.gets; $stdout.puts i; exit'",
117
+ input_lines: %w(hi unprocessed)
118
+ )
119
+ end.to raise_error(
120
+ ProcessHelper::UnprocessedInputError,
121
+ /Output stream closed with 1 input lines left unprocessed/)
122
+ .and(output(/hi\n/).to_stdout)
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,138 @@
1
+ require_relative '../spec_helper'
2
+
3
+ RSpec.describe ':expected_exit_status option' do
4
+ attr_reader :clazz
5
+
6
+ before do
7
+ @clazz = Clazz.new
8
+ end
9
+
10
+ describe '== 0 (default)' do
11
+ describe 'when exit_status == 0' do
12
+ it 'succeeds' do
13
+ expect do
14
+ clazz.process('echo')
15
+ end.to not_raise_error
16
+ .and(output("\n").to_stdout)
17
+ end
18
+
19
+ it 'succeeds when :expected_exit_status is explicitly 0' do
20
+ expect do
21
+ clazz.process('echo', exp_st: 0)
22
+ end.to not_raise_error
23
+ .and(output("\n").to_stdout)
24
+ end
25
+ end
26
+
27
+ describe 'when exit_status != 0' do
28
+ it 'fails with message' do
29
+ cmd_regex = Regexp.escape('`ls /does_not_exist`')
30
+ expect do
31
+ clazz.process('ls /does_not_exist', puts_output: :error)
32
+ end.to raise_error(
33
+ ProcessHelper::UnexpectedExitStatusError,
34
+ /Command failed, pid \d+ exit [1,2]\. Command: #{cmd_regex}\./
35
+ )
36
+ .and(output(/No such file or directory/).to_stdout)
37
+ end
38
+ end
39
+ end
40
+
41
+ describe '!= 0' do
42
+ describe 'when exit_status == 0' do
43
+ it 'fails with message' do
44
+ expected_regex = 'Command succeeded but was expected to fail, ' \
45
+ 'pid \d+ exit 0 \(expected \[1, 2\]\). Command: ' \
46
+ "#{Regexp.escape('`echo`')}" \
47
+ '\.'
48
+ expect do
49
+ clazz.process('echo', exp_st: [1, 2], puts_output: :error)
50
+ end.to raise_error(ProcessHelper::UnexpectedExitStatusError, /#{expected_regex}/)
51
+ .and(output("\n").to_stdout)
52
+ end
53
+ end
54
+
55
+ describe 'when exit_status != 0' do
56
+ it 'succeeds' do
57
+ expect do
58
+ clazz.process('ls /does_not_exist', exp_st: [1, 2], puts_output: :error)
59
+ end.to_not raise_error
60
+ end
61
+ end
62
+ end
63
+
64
+ describe 'multiple values' do
65
+ describe 'when exit_status == 0' do
66
+ describe 'when 0 is one of the expected values' do
67
+ it 'succeeds' do
68
+ expect do
69
+ clazz.process(
70
+ 'echo',
71
+ expected_exit_status: [0, 98, 99],
72
+ )
73
+ end.to not_raise_error
74
+ .and(output("\n").to_stdout)
75
+ end
76
+ end
77
+
78
+ describe 'when 0 is not one of the expected values' do
79
+ it 'fails with message' do
80
+ expected_regex = 'Command succeeded but was expected to fail, ' \
81
+ 'pid \d+ exit 0 \(expected \[98, 99\]\). Command: ' \
82
+ "#{Regexp.escape('`echo`')}" \
83
+ '\.'
84
+ expect do
85
+ clazz.process(
86
+ 'echo',
87
+ expected_exit_status: [98, 99],
88
+ )
89
+ end.to raise_error(
90
+ ProcessHelper::UnexpectedExitStatusError,
91
+ /#{expected_regex}/
92
+ )
93
+ .and(output("\n").to_stdout)
94
+ end
95
+ end
96
+ end
97
+
98
+ describe 'when exit_status != 0' do
99
+ describe 'when the exit status is one of the expected values' do
100
+ it 'succeeds' do
101
+ expect do
102
+ clazz.process(
103
+ 'ls /does_not_exist',
104
+ expected_exit_status: [0, 1, 2],
105
+ )
106
+ end.to not_raise_error
107
+ .and(output(/No such file or directory/).to_stdout)
108
+ end
109
+ end
110
+
111
+ describe 'when the exit status is not one of the expected values' do
112
+ it 'fails with message' do
113
+ expected_regex = 'Command did not exit with one of the expected exit statuses, ' \
114
+ 'pid \d+ exit [1,2] \(expected \[0, 98, 99\]\). Command: ' \
115
+ "#{Regexp.escape('`ls /does_not_exist`')}" \
116
+ '\.'
117
+ expect do
118
+ clazz.process(
119
+ 'ls /does_not_exist',
120
+ expected_exit_status: [0, 98, 99],
121
+ puts_output: :error)
122
+ end.to raise_error(
123
+ ProcessHelper::UnexpectedExitStatusError,
124
+ /#{expected_regex}/
125
+ )
126
+ .and(output(/No such file or directory/).to_stdout)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ it 'supports long form of option' do
133
+ expect do
134
+ clazz.process('echo', expected_exit_status: 0)
135
+ end.to not_raise_error
136
+ .and(output("\n").to_stdout)
137
+ end
138
+ end
@@ -0,0 +1,75 @@
1
+ require_relative '../spec_helper'
2
+
3
+ RSpec.describe ':include_output_in_exception option' do
4
+ attr_reader :clazz
5
+
6
+ before do
7
+ @clazz = Clazz.new
8
+ end
9
+
10
+ describe '== true (default)' do
11
+ describe 'includes output in exception if exception' do
12
+ it 'when :expected_exit_status is zero' do
13
+ expect do
14
+ clazz.process(
15
+ 'ls /does_not_exist',
16
+ puts_output: :error,
17
+ include_output_in_exception: true
18
+ )
19
+ end.to raise_error(
20
+ ProcessHelper::UnexpectedExitStatusError,
21
+ /Command Output: "ls:.*\/does_not_exist: No such file or directory\n"/)
22
+ .and(output(/No such file or directory/).to_stdout)
23
+ end
24
+
25
+ it 'when :expected_exit_status is nonzero' do
26
+ expect do
27
+ clazz.process(
28
+ 'echo stdout > /dev/stdout',
29
+ puts_output: :error,
30
+ expected_exit_status: 1,
31
+ include_output_in_exception: true)
32
+ end.to raise_error(
33
+ ProcessHelper::UnexpectedExitStatusError,
34
+ /Command Output: "stdout\n"/)
35
+ .and(output("stdout\n").to_stdout)
36
+ end
37
+ end
38
+ end
39
+
40
+ describe '== false' do
41
+ describe 'does not includes output in exception if exception' do
42
+ it 'when :expected_exit_status is zero' do
43
+ expect do
44
+ clazz.process(
45
+ 'ls /does_not_exist',
46
+ puts_output: :error,
47
+ include_output_in_exception: false)
48
+ end
49
+ .to output(/No such file or directory/).to_stdout
50
+ .and(
51
+ raise_error(ProcessHelper::UnexpectedExitStatusError) do |e|
52
+ expect(e.message).not_to match(/Command Output/)
53
+ expect(e.message).not_to match(/No such file or directory/)
54
+ end
55
+ )
56
+ end
57
+
58
+ it 'when :expected_exit_status is nonzero' do
59
+ expect do
60
+ clazz.process(
61
+ 'echo stdout > /dev/stdout',
62
+ expected_exit_status: 1,
63
+ puts_output: :error,
64
+ include_output_in_exception: false)
65
+ end.to output("stdout\n").to_stdout
66
+ .and(
67
+ raise_error(ProcessHelper::UnexpectedExitStatusError) do |e|
68
+ expect(e.message).not_to match(/Command Output:/)
69
+ expect(e.message).not_to match(/No such file or directory/)
70
+ end
71
+ )
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,76 @@
1
+ require_relative '../spec_helper'
2
+
3
+ RSpec.describe ':puts_output option' do
4
+ attr_reader :clazz
5
+
6
+ before do
7
+ @clazz = Clazz.new
8
+ end
9
+
10
+ describe '== :always (default)' do
11
+ it 'puts output to stdout' do
12
+ expect do
13
+ clazz.process('echo stdout > /dev/stdout', puts_output: :always)
14
+ end.to output("stdout\n").to_stdout
15
+
16
+ expect do
17
+ clazz.process('echo stdout > /dev/stdout')
18
+ end.to output("stdout\n").to_stdout
19
+ end
20
+ end
21
+
22
+ describe '== :error' do
23
+ describe 'when :expected_exit_status is zero' do
24
+ it 'puts output to stdout on exception' do
25
+ expect do
26
+ clazz.process('ls /does_not_exist', puts_output: :error)
27
+ end.to raise_error(
28
+ ProcessHelper::UnexpectedExitStatusError,
29
+ /Command failed/)
30
+ .and(output(/No such file or directory/).to_stdout)
31
+ end
32
+
33
+ it 'suppresses stdout if no exception' do
34
+ expect do
35
+ clazz.process('echo stdout > /dev/stdout', puts_output: :error)
36
+ end.to not_output.to_stdout
37
+ .and(not_output.to_stderr)
38
+ end
39
+ end
40
+
41
+ describe 'when :expected_exit_status is nonzero' do
42
+ it 'puts output to stdout on exception' do
43
+ expect do
44
+ clazz.process(
45
+ 'echo stdout > /dev/stdout',
46
+ expected_exit_status: 1,
47
+ puts_output: :error)
48
+ end.to raise_error(
49
+ ProcessHelper::UnexpectedExitStatusError,
50
+ /Command succeeded but was expected to fail/)
51
+ .and(output("stdout\n").to_stdout)
52
+ end
53
+
54
+ it 'suppresses stdout if no exception' do
55
+ expect do
56
+ clazz.process(
57
+ 'ls /does_not_exist',
58
+ expected_exit_status: [1, 2],
59
+ puts_output: :error)
60
+ end.to not_output.to_stdout
61
+ .and(not_output.to_stderr)
62
+ end
63
+ end
64
+ end
65
+
66
+ describe '== :never' do
67
+ it 'suppresses stdout' do
68
+ expect do
69
+ clazz.process(
70
+ 'echo stdout > /dev/stdout',
71
+ puts_output: :never)
72
+ end.to not_output.to_stdout
73
+ .and(output(/WARNING/).to_stderr)
74
+ end
75
+ end
76
+ end