process_helper 0.0.3 → 0.0.4.pre.beta.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d29ad0fa584a226473f5a65df1b814c0283958cb
4
- data.tar.gz: 3732855e1fe9cb030c64d959555be9a342f3ae84
3
+ metadata.gz: 3d1de91d830fe7cc5e79d69b97de7d248351318d
4
+ data.tar.gz: ff9505e3dede36dbb1888c54c3d3e8a4250f5232
5
5
  SHA512:
6
- metadata.gz: 2960aaf0bc89d84df0a1c580569fb715434e393a8a6e2b352b901fb1f84daf3d9b78fb8b1c9d87955332f32de00bc5ac8c3448dccbb4bcf3b35b9188bf7e3c0a
7
- data.tar.gz: 194c492795fa05862416a84eb7a3d5b346f35fa469287b379c488781f68a6bca1557325cf9ac595690a407a463422484ac472623427813d1424acb99f0636f92
6
+ metadata.gz: b082df1e1da68ee594d86823aa064ce1342049069ac69d660cdcff25c783c465de9cb336b1d4e5ca24fd331837e09b6ba635f032102919a0581cf7cf6f94e2f3
7
+ data.tar.gz: 8ee4d6e370b5c534f3b8a3ff5f4fcf7dd36295ce3f3a65611e9dcc0ad9c3d6147c766d160a4312b6b7e76749bfd0af7767f1c1ee419d33e49d257822f06fc744
data/.gitignore CHANGED
@@ -21,3 +21,4 @@ tmp
21
21
  *.o
22
22
  *.a
23
23
  mkmf.log
24
+ .vscode
data/.rubocop.yml CHANGED
@@ -1,5 +1,17 @@
1
1
  ---
2
2
  # https://github.com/bbatsov/rubocop/blob/master/config/default.yml
3
+
4
+ inherit_from: .rubocop_todo.yml
5
+
6
+ AllCops:
7
+ Include:
8
+ - '**/Rakefile'
9
+ - '**/config.ru'
10
+ Exclude:
11
+ - 'client/node_modules/**/*'
12
+ - 'db/**/*'
13
+ - 'vendor/bundle/**/*'
14
+
3
15
  Metrics/LineLength:
4
16
  Max: 99
5
17
 
@@ -7,23 +19,40 @@ Metrics/MethodLength:
7
19
  CountComments: false # count full line comments?
8
20
  Max: 20
9
21
 
10
- Style/CaseIndentation:
11
- IndentOneStep: true
22
+ Metrics/ModuleLength:
23
+ Max: 300
12
24
 
13
- Style/FirstParameterIndentation:
14
- Enabled: false
15
-
16
- Style/MultilineBlockChain:
17
- Enabled: false
25
+ Style/FileName:
26
+ # File names listed in AllCops:Include are excluded by default. Add extra
27
+ # excludes here.
28
+ Exclude:
29
+ - bin/ruby-lint
18
30
 
19
31
  Style/MultilineOperationIndentation:
20
- Enabled: false
32
+ EnforcedStyle: indented
33
+
34
+ Style/SignalException:
35
+ # prefer fail for app code, but rails generates raise in binstubs so allow that too
36
+ EnforcedStyle: semantic
21
37
 
22
38
  Style/SpaceAroundEqualsInParameterDefault:
23
39
  # compatibility with RubyMine defaults (apparently can't override?)
24
40
  EnforcedStyle: space
25
41
 
26
- Style/TrailingComma:
27
- # can't make this only apply to arrays and not params, so it's disabled
42
+ Style/TrailingCommaInLiteral:
43
+ EnforcedStyleForMultiline: comma
44
+
45
+ Style/TrailingCommaInArguments:
46
+ EnforcedStyleForMultiline: no_comma
47
+
48
+ Style/MultilineMethodCallIndentation:
49
+ # can't convince rubymine to not indent 2 spaces with mult-line method calls
28
50
  Enabled: false
29
- #EnforcedStyleForMultiline: comma
51
+
52
+ Style/MultilineOperationIndentation:
53
+ # can't convince rubymine 7 to not indent 4 spaces with mult-line expect(x).to receive ...
54
+ Enabled: false
55
+
56
+ Style/RegexpLiteral:
57
+ # This is required or else rubocop argues with itself when trying to escape a slash on a single-line regex
58
+ AllowInnerSlashes: true
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,62 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2016-07-25 21:30:11 -0700 using RuboCop version 0.38.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 2
10
+ # Cop supports --auto-correct.
11
+ # Configuration parameters: IndentWhenRelativeTo, SupportedStyles, IndentOneStep, IndentationWidth.
12
+ # SupportedStyles: case, end
13
+ Style/CaseIndentation:
14
+ Enabled: false
15
+
16
+ # Offense count: 3
17
+ # Cop supports --auto-correct.
18
+ Style/ClosingParenthesisIndentation:
19
+ Exclude:
20
+ - 'spec/options/expected_exit_status_spec.rb'
21
+
22
+ # Offense count: 18
23
+ # Cop supports --auto-correct.
24
+ # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
25
+ # SupportedStyles: consistent, special_for_inner_method_call, special_for_inner_method_call_in_parentheses
26
+ Style/FirstParameterIndentation:
27
+ Exclude:
28
+ - 'spec/error_handling_spec.rb'
29
+ - 'spec/input_handling_spec.rb'
30
+ - 'spec/options/expected_exit_status_spec.rb'
31
+ - 'spec/options/include_output_in_exception_spec.rb'
32
+ - 'spec/options/puts_output_spec.rb'
33
+ - 'spec/options/validation_spec.rb'
34
+
35
+ # Offense count: 1
36
+ # Cop supports --auto-correct.
37
+ Style/RedundantParentheses:
38
+ Exclude:
39
+ - 'lib/process_helper.rb'
40
+
41
+ # Offense count: 3
42
+ # Cop supports --auto-correct.
43
+ # Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
44
+ # SupportedStyles: comma, consistent_comma, no_comma
45
+ Style/TrailingCommaInArguments:
46
+ Exclude:
47
+ - 'spec/options/expected_exit_status_spec.rb'
48
+
49
+ # Offense count: 1
50
+ # Cop supports --auto-correct.
51
+ # Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
52
+ # SupportedStyles: comma, consistent_comma, no_comma
53
+ Style/TrailingCommaInLiteral:
54
+ Exclude:
55
+ - 'spec/input_handling_spec.rb'
56
+
57
+ # Offense count: 1
58
+ # Cop supports --auto-correct.
59
+ # Configuration parameters: AllowNamedUnderscoreVariables.
60
+ Style/TrailingUnderscoreVariable:
61
+ Exclude:
62
+ - 'lib/process_helper.rb'
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.1
1
+ ruby-2.3.3
data/.travis.yml CHANGED
@@ -1,2 +1,7 @@
1
1
  ---
2
2
  sudo: false
3
+ addons:
4
+ code_climate:
5
+ repo_token: e7445c267f016d1a041c9cd16889fa806714e9a7c9bc1fc7936465442b9a167b
6
+ after_success:
7
+ - bundle exec codeclimate-test-reporter
data/README.md CHANGED
@@ -1,18 +1,20 @@
1
- [![Travis-CI Build Status](https://travis-ci.org/thewoolleyman/process_helper.svg?branch=master)](https://travis-ci.org/thewoolleyman/process_helper) | [![Code Climate](https://codeclimate.com/github/thewoolleyman/process_helper/badges/gpa.svg)](https://codeclimate.com/github/thewoolleyman/process_helper) | [![Test Coverage](https://codeclimate.com/github/thewoolleyman/process_helper/badges/coverage.svg)](https://codeclimate.com/github/thewoolleyman/process_helper) | [Pivotal Tracker Project](https://www.pivotaltracker.com/n/projects/1117814)
1
+ [![Travis-CI Build Status](https://travis-ci.org/thewoolleyman/process_helper.svg?branch=master)](https://travis-ci.org/thewoolleyman/process_helper) | [![Code Climate](https://codeclimate.com/github/thewoolleyman/process_helper/badges/gpa.svg)](https://codeclimate.com/github/thewoolleyman/process_helper) | [![Test Coverage](https://codeclimate.com/github/thewoolleyman/process_helper/badges/coverage.svg)](https://codeclimate.com/github/thewoolleyman/process_helper) | [![Gem Version](https://badge.fury.io/rb/process_helper.svg)](https://badge.fury.io/rb/process_helper) | [Pivotal Tracker Project](https://www.pivotaltracker.com/n/projects/1117814)
2
2
 
3
3
  # process_helper
4
4
 
5
- Makes it easy to spawn Ruby sub-processes with guaranteed exit status handling, passing of lines to STDIN, and capturing of STDOUT and STDERR streams.
5
+ Makes it easy to spawn Ruby sub-processes with guaranteed exit status handling, capturing and/or suppressing combined STDOUT and STDERR streams, providing STDIN input, timeouts, and running via a pseudo terminal.
6
6
 
7
7
  ## Goals
8
8
 
9
9
  * Always raise an exception on unexpected exit status (i.e. return code or `$!`)
10
- * Combine and interleave STDOUT and STDERR streams into STDOUT (using [Open3.popen2e](http://ruby-doc.org/stdlib-2.1.5/libdoc/open3/rdoc/Open3.html#method-c-popen2e)),
10
+ * Combine and interleave STDOUT and STDERR streams into STDOUT (using [Open3.popen2e](http://ruby-doc.org/stdlib-2.1.5/libdoc/open3/rdoc/Open3.html#method-c-popen2e))
11
+ or [PTY.spawn](https://ruby-doc.org/stdlib-2.2.3/libdoc/pty/rdoc/PTY.html#method-c-spawn),
11
12
  so you don't have to worry about how to capture the output of both streams.
12
13
  * Provide useful options for suppressing output and including output when an exception
13
14
  is raised due to an unexpected exit status
14
15
  * Provide real-time streaming of combined STDOUT/STDERR streams in addition to returning full combined output as a string returned from the method and/or in the exception.
15
- * Support passing multi-line input to the STDIN stream via arrays of strings.
16
+ * Support passing input to the STDIN stream via string or StringIO.
17
+ * Support running commands in a pseudo terminal.
16
18
  * Allow override of the expected exit status(es) (zero is expected by default)
17
19
  * Provide short forms of all options for terse, concise usage.
18
20
 
@@ -24,7 +26,8 @@ Makes it easy to spawn Ruby sub-processes with guaranteed exit status handling,
24
26
 
25
27
  ## Why Yet Another Ruby Process Wrapper Library?
26
28
 
27
- There's many other libraries to make it easier to work with processes in Ruby (see the Resources section). However, `process_helper` was created because none of them made it *easy* to run processes while meeting **all** of these requirements (redundant details are repeated above in Goals section):
29
+ There's many other libraries to make it easier to work with processes in Ruby (see the Resources section).
30
+ However, `process_helper` was created because none of them made it *easy* to run processes while meeting **all** of these requirements (redundant details are repeated above in Goals section):
28
31
 
29
32
  * Combine STDOUT/STDERR output streams ***interleaved chronologically as emitted***
30
33
  * Stream STDOUT/STDERR real-time ***while process is still running***, in addition to returning full output as a string and/or in an exception
@@ -32,6 +35,26 @@ There's many other libraries to make it easier to work with processes in Ruby (s
32
35
  * Can be used ***very concisely***. I.e. All behavior can be invoked via a single mixed-in module with single public method call using terse options with sensible defaults, no need to use IO streams directly or have any blocks or local variables declared.
33
36
 
34
37
 
38
+ ## Table of Contents
39
+
40
+ * [Goals](#goals)
41
+ * [Non-Goals](#non-goals)
42
+ * [Why Yet Another Ruby Process Wrapper Library](#why-yet-another-ruby-process-wrapper-library)
43
+ * [Installation](#installation)
44
+ * [Usage](#usage)
45
+ * [Options](#options)
46
+ * [`:expected_exit_status` (short form `:exp_st`)](#expected_exit_status-short-form-exp_st)
47
+ * [`:include_output_in_exception` (short form `:out_ex`)](#include_output_in_exception-short-form-out_ex)
48
+ * [`:input` (short form `:in`)](#input-short-form-in)
49
+ * [`:pseudo_terminal` (short form `:pty`)](#pseudo_terminal-short-form-pty)
50
+ * [`:puts_output` (short form `:out`)](#puts_output-short-form-out)
51
+ * [`:timeout` (short form `:kill`)](#timeout-short-form-kill)
52
+ * [Warnings if failure output will be suppressed based on options](#warnings-if-failure-output-will-be-suppressed-based-on-options)
53
+ * [Version](#version)
54
+ * [Contributing](#contributing)
55
+ * [(Un)License](#unlicense)
56
+ * [Resources](#resources)
57
+
35
58
  ## Installation
36
59
 
37
60
  Add this line to your application's Gemfile:
@@ -131,6 +154,36 @@ ProcessHelper::UnexpectedExitStatusError: Command failed, pid 64947 exit 1. Comm
131
154
  "
132
155
  ```
133
156
 
157
+ ### `:input` (short form `:in`)
158
+
159
+ A String or StringIO object which will be supplied as standard input to the command.
160
+
161
+ The entire string will be read and piped to the command prior to outputting any output,
162
+ but this behavior may be changed in the future to allow a separator character for
163
+ processing input as "lines".
164
+
165
+ ### `:pseudo_terminal` (short form `:pty`)
166
+
167
+ Valid values are `true` and `false`. Default value is `false`.
168
+
169
+ When this option is `true`, it will cause the command to be processed via
170
+ [PTY.spawn](https://ruby-doc.org/stdlib-2.2.3/libdoc/pty/rdoc/PTY.html#method-c-spawn)
171
+ in a pseudo-terminal.
172
+
173
+ Some commands require a terminal, or "tty" to work properly, or to work at all.
174
+ For example, some commands may not emit colored output unless they
175
+ detect that they are running via a terminal.
176
+
177
+ It is important to note that this can change the behavior of a command.
178
+
179
+ For example, in most default Linux and OSX (BSD) terminals, newlines (`\n`) in output
180
+ will be translated to carriage-return + newline (`\r\n`). This will normally
181
+ have no effect, and can be controlled by the `onlcr (-onlcr)` option
182
+ of the [stty command](https://www.freebsd.org/cgi/man.cgi?query=stty&sektion=1).
183
+ Use `stty -a` to get info on the current terminal.
184
+
185
+ Also, any input given to the command may be echoed to the output as well.
186
+
134
187
  ### `:puts_output` (short form `:out`)
135
188
 
136
189
  Valid values are `:always`, `:error`, and `:never`. Default value is `:always`.
@@ -142,18 +195,19 @@ Valid values are `:always`, `:error`, and `:never`. Default value is `:always`.
142
195
 
143
196
  ### `:timeout` (short form `:kill`)
144
197
 
145
- ***WARNING! This option is beta and will be changed in a future release!***
198
+ ***NOTE: This option will be changed in a future release.***
146
199
 
147
200
  Valid value is a float, e.g. `1.5`. Default value is nil/undefined.
148
201
 
149
- * Currently controls how long `process_helper` will wait to read from
150
- a blocked IO stream before timing out (via [IO.select](http://ruby-doc.org/core-2.2.0/IO.html#method-c-select)). For example, invoking `cat` with no arguments, which by default will continue accepting input until killed.
202
+ * Controls how long `process_helper` will wait to read from
203
+ a blocked IO stream before timing out (via [IO.select](http://ruby-doc.org/core-2.2.0/IO.html#method-c-select)).
204
+ For example, invoking `cat` with no arguments, which by default will continue accepting input until killed.
205
+ * Will also kill long running processes which are ***not*** in blocked waiting on an IO stream read (i.e. kill process regardless of any IO state, not just via [IO.selects](http://ruby-doc.org/core-2.2.0/IO.html#method-c-select) timeout support).
151
206
  * If undefined (default), there will be no timeout, and `process_helper` will hang if a process hangs while waiting to read from IO.
152
207
 
153
208
  ***The following changes are planned for this option:***
154
209
 
155
210
  * Add validation of value (enforced to be a float).
156
- * Add ability for the timeout value to also kill long running processes which are ***not*** in blocked waiting on an IO stream read (i.e. kill process regardless of any IO state, not just via [IO.selects](http://ruby-doc.org/core-2.2.0/IO.html#method-c-select) timeout support).
157
211
  * Have both types of timeouts raise different and unique exception classes.
158
212
  * Possibly have different option names to allow different timeout values for the two types of timeouts.
159
213
 
@@ -202,14 +256,19 @@ ProcessHelper::VERSION
202
256
 
203
257
  ## Resources
204
258
 
205
- Other Ruby Process tools/libraries
259
+ Other Ruby Process tools/libraries:
206
260
 
261
+ * a [comprehensive StackOverflow article](http://stackoverflow.com/questions/7212573/when-to-use-each-method-of-launching-a-subprocess-in-ruby) describing, in detail, the myriad methods of launching Ruby subprocesses.
207
262
  * [open4](https://github.com/ahoward/open4) - a solid and useful library - the main thing I missed in it was easily combining real-time streaming interleaved STDOUT/STDERR streams
208
263
  * [open4 on ruby toolbox](https://www.ruby-toolbox.com/projects/open4) - see if there's some useful higher-level gem that depends on it and gives you functionality you may need
209
264
  * A great series of blog posts by Devver:
210
265
  * [https://devver.wordpress.com/2009/06/30/a-dozen-or-so-ways-to-start-sub-processes-in-ruby-part-1/](https://devver.wordpress.com/2009/06/30/a-dozen-or-so-ways-to-start-sub-processes-in-ruby-part-1/)
211
266
  * [https://devver.wordpress.com/2009/07/13/a-dozen-or-so-ways-to-start-sub-processes-in-ruby-part-2/](https://devver.wordpress.com/2009/07/13/a-dozen-or-so-ways-to-start-sub-processes-in-ruby-part-2/)
212
267
  * [https://devver.wordpress.com/2009/10/12/ruby-subprocesses-part_3/](https://devver.wordpress.com/2009/10/12/ruby-subprocesses-part_3/)
268
+ * [A gist exploring ruby PTY behavior](https://gist.github.com/thewoolleyman/6a060574f22eafd42955812a1a2a7842#file-pty_check_test-rb)
213
269
 
270
+ Some notes on why you should use printf over echo:
214
271
 
272
+ * [echo vs. println](http://unix.stackexchange.com/a/219274)
273
+ * [Why is printf better than echo?](http://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo/65819#65819)
215
274
 
@@ -1,117 +1,129 @@
1
1
  require 'open3'
2
+ require 'pty'
3
+ require 'timeout'
2
4
 
3
5
  # Makes it easier to spawn ruby sub-processes with proper capturing of stdout and stderr streams.
4
6
  module ProcessHelper
5
- PROCESS_HELPER_VERSION = '0.0.3'
7
+ # Don't forget to keep version in sync with gemspec
8
+ VERSION = '0.0.4.pre.beta.2'.freeze
9
+
10
+ # rubocop:disable Style/ModuleFunction
11
+ extend self
6
12
 
7
13
  def process(cmd, options = {})
8
14
  cmd = cmd.to_s
9
15
  fail ProcessHelper::EmptyCommandError, 'command must not be empty' if cmd.empty?
10
16
  options = options.dup
11
17
  options_processing(options)
18
+ output, process_status =
19
+ if options[:pseudo_terminal]
20
+ process_with_pseudo_terminal(cmd, options)
21
+ else
22
+ process_with_popen(cmd, options)
23
+ end
24
+ handle_exit_status(cmd, options, output, process_status)
25
+ output
26
+ end
27
+
28
+ private
29
+
30
+ def process_with_popen(cmd, options)
12
31
  Open3.popen2e(cmd) do |stdin, stdout_and_stderr, wait_thr|
13
- always_puts_output = (options[:puts_output] == :always)
14
- output = get_output(
15
- stdin,
16
- stdout_and_stderr,
17
- options[:input_lines],
18
- always_puts_output,
19
- options[:timeout]
20
- )
21
- stdin.close
22
- handle_exit_status(cmd, options, output, wait_thr)
23
- output
32
+ begin
33
+ output = get_output(stdin, stdout_and_stderr, options)
34
+ rescue TimeoutError
35
+ # ensure the thread is killed
36
+ wait_thr.kill
37
+ raise
38
+ end
39
+ process_status = wait_thr.value
40
+ return [output, process_status]
24
41
  end
25
42
  end
26
43
 
27
- private
44
+ def process_with_pseudo_terminal(cmd, options)
45
+ PTY.spawn(cmd) do |stdout_and_stderr, stdin, pid|
46
+ output = get_output(stdin, stdout_and_stderr, options)
47
+ process_status = PTY.check(pid)
48
+ # TODO: come up with a test that illustrates pid not exiting
49
+ fail "ERROR: pid #{pid} did not exit" unless process_status
50
+ return [output, process_status]
51
+ end
52
+ end
28
53
 
29
54
  def warn_if_output_may_be_suppressed_on_error(options)
30
- return unless options[:puts_output] == :never
55
+ return unless options[:puts_output] == :never &&
56
+ options[:include_output_in_exception] == false
31
57
 
32
- if options[:include_output_in_exception] == false
33
- err_msg = 'WARNING: Check your ProcessHelper options - ' \
58
+ err_msg = 'WARNING: Check your ProcessHelper options - ' \
34
59
  ':puts_output is :never, and :include_output_in_exception ' \
35
60
  'is false, so all error output will be suppressed if process fails.'
36
- else
37
- err_msg = 'WARNING: Check your ProcessHelper options - ' \
38
- ':puts_output is :never, ' \
39
- 'so all error output will be suppressed unless process ' \
40
- "fails with an exit code other than #{options[:expected_exit_status]} " \
41
- '(in which case exception will include output ' \
42
- 'because :include_output_in_exception is true)'
43
- end
44
61
  $stderr.puts(err_msg)
45
62
  end
46
63
 
47
64
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
48
65
  # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
49
- def get_output(stdin, stdout_and_stderr, original_input_lines, always_puts_output, timeout)
50
- input_lines = original_input_lines.dup
51
- input_lines_processed = 0
52
- current_input_line_processed = false
66
+ def get_output(stdin, stdout_and_stderr, options)
67
+ input = options[:input]
68
+ always_puts_output = (options[:puts_output] == :always)
69
+ timeout = options[:timeout]
53
70
  output = ''
54
71
  begin
55
- while (output_line = readline_nonblock(stdout_and_stderr))
56
- current_input_line_processed = true
57
- puts output_line if always_puts_output
58
- output += output_line
59
- output_line = nil
60
- end
61
- rescue EOFError
62
- input_lines_processed -= 1 if !original_input_lines.empty? && !current_input_line_processed
63
- fail_unless_all_input_lines_processed(original_input_lines, input_lines_processed)
64
- rescue IO::WaitReadable
65
- if input_lines.empty?
66
- result = IO.select([stdout_and_stderr], nil, nil, timeout)
67
- retry unless result.nil?
68
- else
69
- current_input_line_processed = false
70
- puts_input_line_to_stdin(stdin, input_lines)
71
- input_lines_processed += 1
72
+ begin
73
+ until input.eof?
74
+ Timeout.timeout(timeout) do
75
+ in_ch = input.read_nonblock(1)
76
+ stdin.write_nonblock(in_ch)
77
+ end
78
+ stdin.flush
79
+ end
80
+ ch = nil
81
+ loop do
82
+ Timeout.timeout(timeout) do
83
+ ch = stdout_and_stderr.read_nonblock(1)
84
+ end
85
+ break unless ch
86
+ printf ch if always_puts_output
87
+ output += ch
88
+ stdout_and_stderr.flush
89
+ end
90
+ rescue EOFError
91
+ return output
92
+ rescue IO::WaitReadable
72
93
  result = IO.select([stdout_and_stderr], nil, nil, timeout)
94
+ raise Timeout::Error if result.nil?
95
+ retry
96
+ rescue IO::WaitWritable
97
+ result = IO.select(nil, [stdin], nil, timeout)
98
+ raise Timeout::Error if result.nil?
73
99
  retry
74
100
  end
101
+ rescue Timeout::Error
102
+ handle_timeout_error(output, options)
103
+ ensure
104
+ stdout_and_stderr.close
105
+ stdin.close
75
106
  end
107
+ # TODO: Why do we sometimes get here with no EOFError occurring, but instead
108
+ # via IO::WaitReadable with a nil select result? (via popen, not sure if via tty)
76
109
  output
77
110
  end
78
111
 
79
- def readline_nonblock(io)
80
- buffer = ''
81
- while (ch = io.read_nonblock(1))
82
- buffer << ch
83
- if ch == "\n"
84
- result = buffer
85
- return result
86
- end
112
+ def handle_timeout_error(output, options)
113
+ msg = "Timed out after #{options.fetch(:timeout)} seconds."
114
+ if options[:include_output_in_exception]
115
+ msg += " Command output prior to timeout: \"#{output}\""
87
116
  end
117
+ fail(TimeoutError, msg)
88
118
  end
89
119
 
90
- def fail_unless_all_input_lines_processed(original_input_lines, input_lines_processed)
91
- unprocessed_input_lines = original_input_lines.length - input_lines_processed
92
- msg = "Output stream closed with #{unprocessed_input_lines} " \
93
- 'input lines left unprocessed:' \
94
- "#{original_input_lines[-(unprocessed_input_lines)..-1]}"
95
- fail(
96
- ProcessHelper::UnprocessedInputError,
97
- msg
98
- ) unless unprocessed_input_lines == 0
99
- end
100
-
101
- def puts_input_line_to_stdin(stdin, input_lines)
102
- return if input_lines.empty?
103
- input_line = input_lines.shift
104
- stdin.puts(input_line)
105
- end
106
-
107
- def handle_exit_status(cmd, options, output, wait_thr)
120
+ def handle_exit_status(cmd, options, output, process_status)
108
121
  expected_exit_status = options[:expected_exit_status]
109
- exit_status = wait_thr.value
110
- return if expected_exit_status.include?(exit_status.exitstatus)
122
+ return if expected_exit_status.include?(process_status.exitstatus)
111
123
 
112
- exception_message = create_exception_message(cmd, exit_status, expected_exit_status)
124
+ exception_message = create_exception_message(cmd, process_status, expected_exit_status)
113
125
  if options[:include_output_in_exception]
114
- exception_message += " Command Output: \"#{output}\""
126
+ exception_message += " Command output: \"#{output}\""
115
127
  end
116
128
  puts_output_only_on_exception(options, output)
117
129
  fail ProcessHelper::UnexpectedExitStatusError, exception_message
@@ -141,25 +153,35 @@ module ProcessHelper
141
153
  def options_processing(options)
142
154
  validate_long_vs_short_option_uniqueness(options)
143
155
  convert_short_options(options)
156
+ validate_input_option(options[:input]) if options[:input]
144
157
  set_option_defaults(options)
145
158
  validate_option_values(options)
146
159
  convert_scalar_expected_exit_status_to_array(options)
147
160
  warn_if_output_may_be_suppressed_on_error(options)
148
161
  end
149
162
 
163
+ def validate_input_option(input_option)
164
+ fail(
165
+ ProcessHelper::InvalidOptionsError,
166
+ "#{quote_and_join_pair(%w(input in))} options must be a String or a StringIO"
167
+ ) unless input_option.is_a?(String) || input_option.is_a?(StringIO)
168
+ end
169
+
150
170
  # rubocop:disable Style/AccessorMethodName
151
171
  def set_option_defaults(options)
152
172
  options[:puts_output] = :always if options[:puts_output].nil?
153
173
  options[:include_output_in_exception] = true if options[:include_output_in_exception].nil?
174
+ options[:pseudo_terminal] = false if options[:pseudo_terminal].nil?
154
175
  options[:expected_exit_status] = [0] if options[:expected_exit_status].nil?
155
- options[:input_lines] = [] if options[:input_lines].nil?
176
+ options[:input] = StringIO.new(options[:input].to_s) unless options[:input].is_a?(StringIO)
156
177
  end
157
178
 
158
179
  def valid_option_pairs
159
180
  pairs = [
160
181
  %w(expected_exit_status exp_st),
161
182
  %w(include_output_in_exception out_ex),
162
- %w(input_lines in),
183
+ %w(input in),
184
+ %w(pseudo_terminal pty),
163
185
  %w(puts_output out),
164
186
  %w(timeout kill),
165
187
  ]
@@ -205,6 +227,7 @@ module ProcessHelper
205
227
  next unless option == long_option_name
206
228
  validate_integer(pair, value) if option.to_s == 'expected_exit_status'
207
229
  validate_boolean(pair, value) if option.to_s == 'include_output_in_exception'
230
+ validate_boolean(pair, value) if option.to_s == 'pseudo_terminal'
208
231
  validate_puts_output(pair, value) if option.to_s == 'puts_output'
209
232
  end
210
233
  end
@@ -263,11 +286,15 @@ module ProcessHelper
263
286
  class InvalidOptionsError < RuntimeError
264
287
  end
265
288
 
289
+ # Error which is raised when any read or write operation takes longer than timeout (kill) option
290
+ class TimeoutError < RuntimeError
291
+ end
292
+
266
293
  # Error which is raised when a command returns an unexpected exit status (return code)
267
294
  class UnexpectedExitStatusError < RuntimeError
268
295
  end
269
296
 
270
- # Error which is raised when command exists while input lines remain unprocessed
297
+ # Error which is raised when command exists while input remains unprocessed
271
298
  class UnprocessedInputError < RuntimeError
272
299
  end
273
300
  end
@@ -1,12 +1,10 @@
1
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'
5
2
 
6
3
  Gem::Specification.new do |spec|
7
4
  spec.name = 'process_helper'
8
- spec.version = ProcessHelper::PROCESS_HELPER_VERSION
9
- spec.authors = ['Glenn Oppegard', 'Chad Woolley']
5
+ # Don't forget to keep version in sync with ProcessHelper::Version
6
+ spec.version = '0.0.4.pre.beta.2'
7
+ spec.authors = ['Chad Woolley', 'Glenn Oppegard']
10
8
  spec.email = ['oppegard@gmail.com', 'thewoolleyman@gmail.com']
11
9
  spec.summary = "Makes it easier to spawn ruby sub-processes with proper capturing /
12
10
  of stdout and stderr streams."
@@ -21,10 +19,11 @@ Gem::Specification.new do |spec|
21
19
  spec.required_ruby_version = '>= 1.9.2'
22
20
 
23
21
  spec.add_development_dependency 'bundler', '~> 1.6'
24
- spec.add_development_dependency 'codeclimate-test-reporter'
22
+ spec.add_development_dependency 'codeclimate-test-reporter', '~> 1'
25
23
  spec.add_development_dependency 'rake', '~> 10'
26
24
  spec.add_development_dependency 'rspec', '~> 3.1'
27
25
  spec.add_development_dependency 'rspec-retry', '~> 0.4'
28
- spec.add_development_dependency 'rubocop', '= 0.29.1' # exact version for static analyis libs
29
- spec.add_development_dependency 'ruby-lint', '= 2.0.2' # exact version for static analyis libs
26
+ spec.add_development_dependency 'rubocop', '= 0.38.0' # exact version for static analyis libs
27
+ spec.add_development_dependency 'ruby-lint', '= 2.1.0' # exact version for static analyis libs
28
+ spec.add_development_dependency 'simplecov', '~> 0'
30
29
  end
data/ruby-lint.yml CHANGED
@@ -1,12 +1,13 @@
1
1
  ---
2
2
  # http://code.yorickpeterse.com/ruby-lint/latest/
3
3
  analysis_classes:
4
- - argument_amount
4
+ # - argument_amount
5
5
  - pedantics
6
6
  - shadowing_variables
7
7
  # - undefined_methods
8
8
  # - undefined_variables
9
- - unused_variables
9
+ # - unused_variables
10
10
  - useless_equality_checks
11
11
  directories:
12
+ - lib
12
13
  - spec
@@ -5,49 +5,32 @@ RSpec.describe 'input handling' do
5
5
 
6
6
  before do
7
7
  @clazz = Clazz.new
8
- @max_process_wait = ENV['MAX_PROCESS_WAIT'] ? ENV['MAX_PROCESS_WAIT'].to_f : 0.5
9
8
  end
10
9
 
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
10
+ it 'handles a single line of STDIN to STDOUT with ruby output flushing' do
11
+ expect do
12
+ clazz.process(
13
+ "ruby -e 'i=$stdin.gets; puts i; $stdout.flush'",
14
+ input: "input1\n"
15
+ )
16
+ end.to output(/input1\n/).to_stdout
17
+ .and(not_output.to_stderr)
18
+ end
33
19
 
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
20
+ it 'handles multiple lines of STDIN to STDOUT with ruby output flushing' do
21
+ expect do
22
+ clazz.process(
23
+ "ruby -e 'while(i=$stdin.gets) do puts i; $stdout.flush; ; if i =~ /2/; break; end; end'",
24
+ input: "input1\ninput2\n"
25
+ )
26
+ end.to output(/input1\ninput2\n/).to_stdout
27
+ .and(not_output.to_stderr)
28
+ end
46
29
 
47
- it 'handles interleaved stdout and stderr based on stdin input' do
48
- expect do
49
- cmd =
50
- 'while ' \
30
+ it 'handles interleaved stdout and stderr based on stdin input' do
31
+ expect do
32
+ cmd =
33
+ 'while ' \
51
34
  ' line = $stdin.readline; ' \
52
35
  ' $stdout.puts("out:#{line}"); ' \
53
36
  ' $stdout.flush; ' \
@@ -55,71 +38,63 @@ RSpec.describe 'input handling' do
55
38
  ' $stderr.flush; ' \
56
39
  ' exit 0 if line =~ /exit/; ' \
57
40
  '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
41
+ clazz.process(
42
+ %(ruby -e '#{cmd}'),
43
+ input: "line1\nline2\nline3\nexit\n"
44
+ )
45
+ end.to output(/out:line1\nerr:line1\nout:line2\nerr:line2\nout:line3\nerr:line3\n/).to_stdout
46
+ .and(not_output.to_stderr)
66
47
  end
67
48
 
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
49
+ it 'handles stdout and stderr triggered via stdin' do
50
+ expect do
51
+ clazz.process(
52
+ 'irb -f --prompt=default',
53
+ input: [
54
+ '$stdout.puts "hi"',
55
+ '$stdout.flush',
56
+ '$stderr.puts "aaa\nbbb\nccc"',
57
+ '$stderr.flush',
58
+ '$stdout.puts "bye"',
59
+ '$stdout.flush',
60
+ "exit\n"
61
+ ].join("\n")
62
+ )
63
+ end.to output(/\nhi\n.*\naaa\nbbb\nccc.*\nbye\n/m).to_stdout
64
+ .and(not_output.to_stderr)
65
+ end
86
66
 
87
- it 'handles unexpected exit status' do
88
- expect do
89
- clazz.process(
90
- "ruby -e 'i=$stdin.gets; $stdout.puts i; $stdout.flush; " \
67
+ it 'handles unexpected exit status' do
68
+ expect do
69
+ clazz.process(
70
+ "ruby -e 'i=$stdin.gets; $stdout.puts i; $stdout.flush; " \
91
71
  "$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
72
+ puts_output: :error,
73
+ input: "hi\n"
74
+ )
75
+ end.to raise_error(
76
+ ProcessHelper::UnexpectedExitStatusError,
77
+ /Command failed/)
78
+ .and(output(/hi\nhi\n/).to_stdout)
79
+ end
100
80
 
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
81
+ it 'pipes input before processing output' do
82
+ expect do
83
+ clazz.process(
84
+ "ruby -e 'i=$stdin.gets; $stdout.puts i; $stdout.flush; exit'",
85
+ input: "hi\n"
86
+ )
87
+ end.to output(/hi\n/m).to_stdout
88
+ .and(not_output.to_stderr)
89
+ end
110
90
 
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
91
+ it 'allows a StringIO as input' do
92
+ expect do
93
+ clazz.process(
94
+ "ruby -e 'i=$stdin.gets; $stdout.puts i; $stdout.flush; exit'",
95
+ input: StringIO.new("hi\n")
96
+ )
97
+ end.to output(/hi\n/m).to_stdout
98
+ .and(not_output.to_stderr)
124
99
  end
125
100
  end
@@ -0,0 +1,33 @@
1
+ require_relative 'spec_helper'
2
+
3
+ RSpec.describe 'module visibility' do
4
+ attr_reader :clazz
5
+
6
+ before do
7
+ @clazz = Clazz.new
8
+ end
9
+
10
+ it 'can be called as an included module' do
11
+ expect do
12
+ clazz.process('echo stdout > /dev/stdout')
13
+ end.to output("stdout\n").to_stdout
14
+ end
15
+
16
+ it 'can be called as a module method' do
17
+ expect do
18
+ ProcessHelper.process('echo stdout > /dev/stdout')
19
+ end.to output("stdout\n").to_stdout
20
+ end
21
+
22
+ it 'does not expose private methods' do
23
+ expect(clazz.private_methods).to include(:process_with_popen)
24
+
25
+ expect do
26
+ clazz.process_with_popen
27
+ end.to raise_error(NoMethodError)
28
+
29
+ expect do
30
+ ProcessHelper.process_with_popen
31
+ end.to raise_error(NoMethodError)
32
+ end
33
+ end
@@ -18,7 +18,7 @@ RSpec.describe ':include_output_in_exception option' do
18
18
  )
19
19
  end.to raise_error(
20
20
  ProcessHelper::UnexpectedExitStatusError,
21
- /Command Output: "ls:.*\/does_not_exist: No such file or directory\n"/)
21
+ /Command output: "ls:.*\/does_not_exist: No such file or directory\n"/)
22
22
  .and(output(/No such file or directory/).to_stdout)
23
23
  end
24
24
 
@@ -31,7 +31,7 @@ RSpec.describe ':include_output_in_exception option' do
31
31
  include_output_in_exception: true)
32
32
  end.to raise_error(
33
33
  ProcessHelper::UnexpectedExitStatusError,
34
- /Command Output: "stdout\n"/)
34
+ /Command output: "stdout\n"/)
35
35
  .and(output("stdout\n").to_stdout)
36
36
  end
37
37
  end
@@ -70,7 +70,7 @@ RSpec.describe ':puts_output option' do
70
70
  'echo stdout > /dev/stdout',
71
71
  puts_output: :never)
72
72
  end.to not_output.to_stdout
73
- .and(output(/WARNING/).to_stderr)
73
+ .and(not_output.to_stderr)
74
74
  end
75
75
  end
76
76
  end
@@ -55,7 +55,7 @@ RSpec.describe 'options validation raises InvalidOptionError' do
55
55
  "'puts_output','out' options must be one of the following: :always, :error, :never")
56
56
  end
57
57
 
58
- it 'when input_lines is passed invalid value'
58
+ it 'when input is passed invalid value'
59
59
 
60
60
  it 'when timeout is passed non-float'
61
61
 
@@ -53,9 +53,32 @@ RSpec.describe 'output handling' do
53
53
  clazz.process(
54
54
  'echo stdout > /dev/stdout',
55
55
  puts_output: :never)
56
- end.to output(/unless process fails with an exit code other than \[0\]/).to_stderr
57
- .and(not_output.to_stdout)
56
+ end.to not_output.to_stdout
57
+ .and(not_output.to_stderr)
58
58
  end
59
59
  end
60
60
  end
61
+
62
+ describe 'when output does not have a newline' do
63
+ it 'captures output' do
64
+ expect do
65
+ clazz.process(
66
+ 'printf stdout',
67
+ puts_output: :always)
68
+ end.to output('stdout').to_stdout
69
+ .and(not_output.to_stderr)
70
+ end
71
+ end
72
+
73
+ describe 'when output is colored' do
74
+ it 'preserves color' do
75
+ colored_text = "\e[0;31mSTDOUT\e[0m"
76
+ expect do
77
+ clazz.process(
78
+ 'printf "\033[0;31mSTDOUT\033[0m" > /dev/stdout',
79
+ puts_output: :always)
80
+ end.to output(colored_text).to_stdout
81
+ .and(not_output.to_stderr)
82
+ end
83
+ end
61
84
  end
@@ -0,0 +1,48 @@
1
+ require_relative 'spec_helper'
2
+
3
+ RSpec.describe 'pty handling' do
4
+ attr_reader :clazz, :max_process_wait
5
+
6
+ before do
7
+ skip('pty specs do not work on travis or circle ci (try docker with -t option)') if ENV['CI']
8
+ @clazz = Clazz.new
9
+ end
10
+
11
+ it 'handles cat cmd' do
12
+ expect do
13
+ clazz.process(
14
+ # -u disables output buffering, -n numbers output lines (to distinguish from input)
15
+ 'cat -u -n',
16
+ input: "line1\nline2\nline3\n\C-d\n",
17
+ pty: true
18
+ )
19
+ end.to output(/1\tline1\r\n.*2\tline2\r\n.*3\tline3\r\n/).to_stdout
20
+ .and(not_output.to_stderr)
21
+ end
22
+
23
+ it 'respects "stty -onlcr"' do
24
+ # NOTE: the `stty -a` default on OSX and Linux terminals seems to be 'onlcr', so all PTY slave
25
+ # terminals which inherit that will transform "\n" into "\r\n". This test shows how to
26
+ # avoid that behavior by prepending 'stty -onlcr && ' to the command
27
+ expect do
28
+ clazz.process(
29
+ 'stty -onlcr && printf "stdout\n" > /dev/stdout && printf "stderr\n" > /dev/stderr',
30
+ puts_output: :always,
31
+ pty: true
32
+ )
33
+ end.to output("stdout\nstderr\n").to_stdout
34
+ .and(not_output.to_stderr)
35
+ end
36
+
37
+ it 'does not require a newline or flush via getch' do
38
+ skip('TODO: this test just hangs, including in a debugger')
39
+ expect do
40
+ clazz.process(
41
+ %q(ruby -e 'require "io/console"; i=$stdin.getch; puts "4" + i;'),
42
+ input: '2',
43
+ pty: true
44
+ )
45
+ end.to output(/42/).to_stdout
46
+ .and(not_output.to_stderr)
47
+ end
48
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,5 @@
1
- if ENV['CODECLIMATE_REPO_TOKEN']
2
- require 'codeclimate-test-reporter'
3
- CodeClimate::TestReporter.start
4
- end
1
+ require 'simplecov'
2
+ SimpleCov.start if ENV['CI']
5
3
 
6
4
  require 'rspec'
7
5
  require 'rspec/retry'
@@ -13,7 +11,7 @@ RSpec::Matchers.define_negated_matcher :not_raise_error, :raise_error
13
11
  # RSpec config
14
12
  RSpec.configure do |config|
15
13
  config.verbose_retry = true
16
- config.default_retry_count = 5
14
+ config.default_retry_count = ENV['CI'] ? 3 : 0
17
15
  config.default_sleep_interval = 1
18
16
  end
19
17
 
@@ -0,0 +1,79 @@
1
+ require_relative 'spec_helper'
2
+
3
+ RSpec.describe 'timout handling with non-exiting blocking cmd requiring timeout' 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
+ # TODO: ensure every place that can raise a timeout is specifically exercised by a test
12
+
13
+ it 'raises on a sleep' do
14
+ expect do
15
+ clazz.process(
16
+ 'sleep 999',
17
+ timeout: max_process_wait
18
+ )
19
+ end.to raise_error(
20
+ ProcessHelper::TimeoutError,
21
+ "Timed out after #{@max_process_wait} seconds. Command output prior to timeout: \"\""
22
+ )
23
+ end
24
+
25
+ it 'does not raise error if timeout is not exceeded' do
26
+ expect do
27
+ clazz.process(
28
+ 'sleep 0.01',
29
+ timeout: max_process_wait
30
+ )
31
+ end.to not_output.to_stdout
32
+ .and(not_output.to_stderr)
33
+ end
34
+
35
+ it 'handles a single line of STDIN to STDOUT with ruby output flushing' do
36
+ expect do
37
+ clazz.process(
38
+ "ruby -e 'while(i=$stdin.gets) do puts i; $stdout.flush; end'",
39
+ input: "input1\n",
40
+ puts_output: :never,
41
+ timeout: max_process_wait
42
+ )
43
+ end.to raise_error(
44
+ ProcessHelper::TimeoutError,
45
+ "Timed out after #{@max_process_wait} seconds. Command output prior to timeout: \"" \
46
+ "input1\n\""
47
+ )
48
+ end
49
+
50
+ it 'handles multiple lines of STDIN to STDOUT with ruby output flushing' do
51
+ expect do
52
+ clazz.process(
53
+ "ruby -e 'while(i=$stdin.gets) do puts i; $stdout.flush; end'",
54
+ input: "input1\ninput2\n",
55
+ puts_output: :never,
56
+ timeout: max_process_wait
57
+ )
58
+ end.to raise_error(
59
+ ProcessHelper::TimeoutError,
60
+ "Timed out after #{@max_process_wait} seconds. Command output prior to timeout: \"" \
61
+ "input1\ninput2\n\""
62
+ )
63
+ end
64
+
65
+ it 'handles cat cmd' do
66
+ expect do
67
+ clazz.process(
68
+ # -u disables output buffering, -n numbers output lines (to distinguish from input)
69
+ 'cat -u -n',
70
+ input: "line1\nline2\nline3\n",
71
+ puts_output: :never,
72
+ timeout: max_process_wait
73
+ )
74
+ end.to raise_error(
75
+ ProcessHelper::TimeoutError,
76
+ /Timed out.*1\tline1\n.*2\tline2\n.*3\tline3\n/
77
+ )
78
+ end
79
+ end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: process_helper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4.pre.beta.2
5
5
  platform: ruby
6
6
  authors:
7
- - Glenn Oppegard
8
7
  - Chad Woolley
8
+ - Glenn Oppegard
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-05-01 00:00:00.000000000 Z
12
+ date: 2017-01-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -29,16 +29,16 @@ dependencies:
29
29
  name: codeclimate-test-reporter
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - ">="
32
+ - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: '0'
34
+ version: '1'
35
35
  type: :development
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - ">="
39
+ - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: '0'
41
+ version: '1'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: rake
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -87,28 +87,42 @@ dependencies:
87
87
  requirements:
88
88
  - - '='
89
89
  - !ruby/object:Gem::Version
90
- version: 0.29.1
90
+ version: 0.38.0
91
91
  type: :development
92
92
  prerelease: false
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
95
  - - '='
96
96
  - !ruby/object:Gem::Version
97
- version: 0.29.1
97
+ version: 0.38.0
98
98
  - !ruby/object:Gem::Dependency
99
99
  name: ruby-lint
100
100
  requirement: !ruby/object:Gem::Requirement
101
101
  requirements:
102
102
  - - '='
103
103
  - !ruby/object:Gem::Version
104
- version: 2.0.2
104
+ version: 2.1.0
105
105
  type: :development
106
106
  prerelease: false
107
107
  version_requirements: !ruby/object:Gem::Requirement
108
108
  requirements:
109
109
  - - '='
110
110
  - !ruby/object:Gem::Version
111
- version: 2.0.2
111
+ version: 2.1.0
112
+ - !ruby/object:Gem::Dependency
113
+ name: simplecov
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
112
126
  description: Wrapper around Open3#popen2e with other useful options.
113
127
  email:
114
128
  - oppegard@gmail.com
@@ -119,6 +133,7 @@ extra_rdoc_files: []
119
133
  files:
120
134
  - ".gitignore"
121
135
  - ".rubocop.yml"
136
+ - ".rubocop_todo.yml"
122
137
  - ".ruby-version"
123
138
  - ".travis.yml"
124
139
  - Gemfile
@@ -130,14 +145,16 @@ files:
130
145
  - ruby-lint.yml
131
146
  - spec/error_handling_spec.rb
132
147
  - spec/input_handling_spec.rb
148
+ - spec/module_visibility_spec.rb
133
149
  - spec/options/expected_exit_status_spec.rb
134
150
  - spec/options/include_output_in_exception_spec.rb
135
151
  - spec/options/puts_output_spec.rb
136
152
  - spec/options/validation_spec.rb
137
153
  - spec/output_handling_spec.rb
154
+ - spec/pty_handling_spec.rb
138
155
  - spec/spec_helper.rb
139
156
  - spec/static_analysis_spec.rb
140
- - spec/version_spec.rb
157
+ - spec/timeout_handling_spec.rb
141
158
  homepage: https://github.com/thewoolleyman/process_helper
142
159
  licenses:
143
160
  - Unlicense
@@ -153,12 +170,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
153
170
  version: 1.9.2
154
171
  required_rubygems_version: !ruby/object:Gem::Requirement
155
172
  requirements:
156
- - - ">="
173
+ - - ">"
157
174
  - !ruby/object:Gem::Version
158
- version: '0'
175
+ version: 1.3.1
159
176
  requirements: []
160
177
  rubyforge_project:
161
- rubygems_version: 2.4.3
178
+ rubygems_version: 2.6.8
162
179
  signing_key:
163
180
  specification_version: 4
164
181
  summary: Makes it easier to spawn ruby sub-processes with proper capturing / of stdout
@@ -166,11 +183,13 @@ summary: Makes it easier to spawn ruby sub-processes with proper capturing / of
166
183
  test_files:
167
184
  - spec/error_handling_spec.rb
168
185
  - spec/input_handling_spec.rb
186
+ - spec/module_visibility_spec.rb
169
187
  - spec/options/expected_exit_status_spec.rb
170
188
  - spec/options/include_output_in_exception_spec.rb
171
189
  - spec/options/puts_output_spec.rb
172
190
  - spec/options/validation_spec.rb
173
191
  - spec/output_handling_spec.rb
192
+ - spec/pty_handling_spec.rb
174
193
  - spec/spec_helper.rb
175
194
  - spec/static_analysis_spec.rb
176
- - spec/version_spec.rb
195
+ - spec/timeout_handling_spec.rb
data/spec/version_spec.rb DELETED
@@ -1,7 +0,0 @@
1
- require_relative 'spec_helper'
2
-
3
- RSpec.describe 'version' do
4
- it 'is semantic' do
5
- expect(::ProcessHelper::PROCESS_HELPER_VERSION).to match(/^\d\.\d\.\d\.*\w*$/)
6
- end
7
- end