process_helper 0.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.
@@ -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