process_helper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 545dfa70d2c4398cb9e9a83bf98cd7541506d857
4
+ data.tar.gz: 7257372f2aa37bb3d2fd40c4b40cff450dfaee71
5
+ SHA512:
6
+ metadata.gz: 6c0478bcbf1cf87ec7afbfc1b0e1394ecdabbdbe222f6b83bb09ede1e35abaaee6f6660c2a20a215fe3c9f4bac560e08bfa5ca28c6b0959ad146f21aaf7dc0b9
7
+ data.tar.gz: d2be7bb16ee0d16f3d6eee824e9c33747912af1427ef75f0859300ccc4b5f6daf087a1ef4c61dcd9a0b9a068a52f4f080095504d048d32118c3d4151b06f82a9
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .idea
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ *.bundle
20
+ *.so
21
+ *.o
22
+ *.a
23
+ mkmf.log
data/.rubocop.yml ADDED
@@ -0,0 +1,29 @@
1
+ ---
2
+ # https://github.com/bbatsov/rubocop/blob/master/config/default.yml
3
+ Metrics/LineLength:
4
+ Max: 99
5
+
6
+ Metrics/MethodLength:
7
+ CountComments: false # count full line comments?
8
+ Max: 20
9
+
10
+ Style/CaseIndentation:
11
+ IndentOneStep: true
12
+
13
+ Style/FirstParameterIndentation:
14
+ Enabled: false
15
+
16
+ Style/MultilineBlockChain:
17
+ Enabled: false
18
+
19
+ Style/MultilineOperationIndentation:
20
+ Enabled: false
21
+
22
+ Style/SpaceAroundEqualsInParameterDefault:
23
+ # compatibility with RubyMine defaults (apparently can't override?)
24
+ EnforcedStyle: space
25
+
26
+ Style/TrailingComma:
27
+ # can't make this only apply to arrays and not params, so it's disabled
28
+ Enabled: false
29
+ #EnforcedStyleForMultiline: comma
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.1
data/.travis.yml ADDED
@@ -0,0 +1,2 @@
1
+ ---
2
+ sudo: false
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in process_helper.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org/>
data/README.md ADDED
@@ -0,0 +1,180 @@
1
+ [![Travis-CI Build Status](https://travis-ci.org/thewoolleyman/process_helper.svg?branch=master)](https://travis-ci.org/thewoolleyman/process_helper)
2
+
3
+ [Pivotal Tracker Project](https://www.pivotaltracker.com/n/projects/1117814)
4
+
5
+ # process_helper
6
+
7
+ 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.
8
+
9
+ ## Goals
10
+
11
+ * Always raise an exception on unexpected exit status (i.e. return code or `$!`)
12
+ * 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)),
13
+ so you don't have to worry about how to capture the output of both streams.
14
+ * Provide useful options for suppressing output and including output when an exception
15
+ is raised due to an unexpected exit status
16
+ * 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.
17
+ * Support passing multi-line input to the STDIN stream via arrays of strings.
18
+ * Allow override of the expected exit status(es) (zero is expected by default)
19
+ * Provide short forms of all options for terse, concise usage.
20
+
21
+ ## Non-Goals
22
+
23
+ * Any explicit support for process forks, multiple threads, or anything other
24
+ than a single direct child process.
25
+ * Any support for separate handling of STDOUT and STDERR streams
26
+
27
+ ## Why Yet Another Ruby Process Wrapper Library?
28
+
29
+ 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):
30
+
31
+ * Combine STDOUT/STDERR output streams ***interleaved chronologically as emitted***
32
+ * Stream STDOUT/STDERR real-time ***while process is still running***, in addition to returning full output as a string and/or in an exception
33
+ * Guarantee an exception is ***always raised*** on an unexpected exit status (and allow specification of ***multiple nonzero values*** as expected exit statuses)
34
+ * 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.
35
+
36
+
37
+ ## Installation
38
+
39
+ Add this line to your application's Gemfile:
40
+
41
+ gem 'process_helper'
42
+
43
+ And then execute:
44
+
45
+ $ bundle
46
+
47
+ Or install it yourself as:
48
+
49
+ $ gem install process_helper
50
+
51
+ ## Usage
52
+
53
+ `ProcessHelper` is a Ruby module you can include in any Ruby code,
54
+ and then call `#process` to run a command, like this:
55
+
56
+ ```
57
+ require 'process_helper'
58
+ include ProcessHelper
59
+ process('echo "Hello"')
60
+ ```
61
+
62
+ By default, ProcessHelper will combine any STDERR and STDOUT, and output it to STDOUT,
63
+ and also return it as the result of the `#process` method.
64
+
65
+ ## Options
66
+
67
+ ### `:expected_exit_status` (short form `:exp_st`)
68
+
69
+ Expected Integer exit status, or array of expected Integer exit statuses.
70
+ Default value is `[0]`.
71
+
72
+ An exception will be raised by the `ProcessHelper#process` method if the
73
+ actual exit status of the processed command is not (one of) the
74
+ expected exit status(es).
75
+
76
+ Here's an example of expecting a nonzero failure exit status which matches the actual exit status
77
+ (the actual exit status of a failed `ls` command will be 1 on OSX, 2 on Linux):
78
+
79
+ ```
80
+ # The following will NOT raise an exception:
81
+ process('ls /does_not_exist', expected_exit_status: [1,2])
82
+ ```
83
+
84
+ ...but it **WILL** still print the output (in this case STDERR output from the failed `ls`
85
+ command) to STDOUT:
86
+
87
+ ```
88
+ ls: /does_not_exist: No such file or directory
89
+ ```
90
+
91
+ Here's a second example of expecting a nonzero failure exit status but the command succeeds:
92
+
93
+ ```
94
+ # The following WILL raise an exception:
95
+ process('printf FAIL', expected_exit_status: 1)
96
+ ```
97
+
98
+ Here's the output of the above example:
99
+
100
+ ```
101
+ FAIL
102
+ ProcessHelper::UnexpectedExitStatusError: Command succeeded but was expected to fail, pid 62974 exit 0 (expected [1]). Command: `printf FAIL`. Command Output: "FAIL"
103
+ ```
104
+
105
+ ### `:include_output_in_exception` (short form `:out_ex`)
106
+
107
+ Boolean flag indicating whether output should be included in the message of the Exception (error)
108
+ which will be raised by the `ProcessHelper#process` method if the command fails (has an unexpected exit status).
109
+
110
+ Here's an example of a failing command:
111
+
112
+ ```
113
+ process('ls /does_not_exist', include_output_in_exception: true)
114
+ ```
115
+
116
+ Here's the exception generated by the above example. Notice the "Command Output"
117
+ with the *"...No such file or directory"* STDERR output of the failed command:
118
+
119
+ ```
120
+ ProcessHelper::UnexpectedExitStatusError: Command failed, pid 64947 exit 1. Command: `ls /does_not_exist`. Command Output: "ls: /does_not_exist: No such file or directory
121
+ "
122
+ ```
123
+
124
+ ### `:puts_output` (short form `:out`)
125
+
126
+ Valid values are `:always`, `:error`, and `:never`. Default value is `:always`.
127
+
128
+ * `:always` will always print output to STDOUT
129
+ * `:error` will only print output to STDOUT if command has an
130
+ error - i.e. non-zero or unexpected exit status
131
+ * `:never` will never print output to STDOUT
132
+
133
+ ## Warnings if failure output will be suppressed based on options
134
+
135
+ ProcessHelper will give you a warning if you pass a combination of options that would
136
+ prevent **ANY** output from being printed or included in the Exception message if
137
+ a command were to fail.
138
+
139
+ For example, in this case there is no output, and the expected exit status includes
140
+ the actual failure status which is returned, so the warning is printed, and the only
141
+ place the output will be seen is in the return value of the `ProcessHelper#process` method:
142
+
143
+ ```
144
+ > process('ls /does_not_exist', expected_exit_status: [1,2], puts_output: :never, include_output_in_exception: false)
145
+ WARNING: Check your ProcessHelper options - :puts_output is :never, and :include_output_in_exception is false, so all error output will be suppressed if process fails.
146
+ => "ls: /does_not_exist: No such file or directory\n"
147
+ ```
148
+
149
+ ## Version
150
+
151
+ You can see the version of ProcessHelper in the `ProcessHelper::VERSION` constant:
152
+
153
+ ```
154
+ ProcessHelper::VERSION
155
+ => "0.0.1"
156
+ ```
157
+
158
+ ## Contributing
159
+
160
+ 1. Fork it ( https://github.com/thewoolleyman/process_helper/fork )
161
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
162
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
163
+ 4. Push to the branch (`git push origin my-new-feature`)
164
+ 5. If you are awesome, use `git rebase --interactive` to ensure
165
+ you have a single atomic commit on your branch.
166
+ 6. Create a new Pull Request
167
+
168
+ ## Resources
169
+
170
+ Other Ruby Process tools/libraries
171
+
172
+ * [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
173
+ * [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
174
+ * A great series of blog posts by Devver:
175
+ * [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/)
176
+ * [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/)
177
+ * [https://devver.wordpress.com/2009/10/12/ruby-subprocesses-part_3/](https://devver.wordpress.com/2009/10/12/ruby-subprocesses-part_3/)
178
+
179
+
180
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,5 @@
1
+ # Error which is raised when a command is empty
2
+ module ProcessHelper
3
+ class EmptyCommandError < RuntimeError
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ # Error which is raised when options are invalid
2
+ module ProcessHelper
3
+ class InvalidOptionsError < RuntimeError
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ # Error which is raised when a command returns an unexpected exit status (return code)
2
+ module ProcessHelper
3
+ class UnexpectedExitStatusError < RuntimeError
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ # Error which is raised when command exists while input lines remain unprocessed
2
+ module ProcessHelper
3
+ class UnprocessedInputError < RuntimeError
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ # Library version
2
+ module ProcessHelper
3
+ VERSION = '0.0.1'
4
+ end
@@ -0,0 +1,258 @@
1
+ require_relative 'process_helper/version'
2
+ require_relative 'process_helper/empty_command_error'
3
+ require_relative 'process_helper/invalid_options_error'
4
+ require_relative 'process_helper/unexpected_exit_status_error'
5
+ require_relative 'process_helper/unprocessed_input_error'
6
+ require 'open3'
7
+
8
+ # Makes it easier to spawn ruby sub-processes with proper capturing of stdout and stderr streams.
9
+ module ProcessHelper
10
+ def process(cmd, options = {})
11
+ cmd = cmd.to_s
12
+ fail ProcessHelper::EmptyCommandError, 'command must not be empty' if cmd.empty?
13
+ options = options.dup
14
+ options_processing(options)
15
+ Open3.popen2e(cmd) do |stdin, stdout_and_stderr, wait_thr|
16
+ always_puts_output = (options[:puts_output] == :always)
17
+ output = get_output(
18
+ stdin,
19
+ stdout_and_stderr,
20
+ options[:input_lines],
21
+ always_puts_output,
22
+ options[:timeout]
23
+ )
24
+ stdin.close
25
+ handle_exit_status(cmd, options, output, wait_thr)
26
+ output
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def warn_if_output_may_be_suppressed_on_error(options)
33
+ return unless options[:puts_output] == :never
34
+
35
+ if options[:include_output_in_exception] == false
36
+ err_msg = 'WARNING: Check your ProcessHelper options - ' \
37
+ ':puts_output is :never, and :include_output_in_exception ' \
38
+ 'is false, so all error output will be suppressed if process fails.'
39
+ else
40
+ err_msg = 'WARNING: Check your ProcessHelper options - ' \
41
+ ':puts_output is :never, ' \
42
+ 'so all error output will be suppressed unless process ' \
43
+ "fails with an exit code other than #{options[:expected_exit_status]} " \
44
+ '(in which case exception will include output ' \
45
+ 'because :include_output_in_exception is true)'
46
+ end
47
+ $stderr.puts(err_msg)
48
+ end
49
+
50
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
51
+ # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
52
+ def get_output(stdin, stdout_and_stderr, original_input_lines, always_puts_output, timeout)
53
+ input_lines = original_input_lines.dup
54
+ input_lines_processed = 0
55
+ current_input_line_processed = false
56
+ output = ''
57
+ begin
58
+ while (output_line = readline_nonblock(stdout_and_stderr))
59
+ current_input_line_processed = true
60
+ puts output_line if always_puts_output
61
+ output += output_line
62
+ output_line = nil
63
+ end
64
+ rescue EOFError
65
+ input_lines_processed -= 1 if !original_input_lines.empty? && !current_input_line_processed
66
+ fail_unless_all_input_lines_processed(original_input_lines, input_lines_processed)
67
+ rescue IO::WaitReadable
68
+ if input_lines.empty?
69
+ result = IO.select([stdout_and_stderr], nil, nil, timeout)
70
+ retry unless result.nil?
71
+ else
72
+ current_input_line_processed = false
73
+ puts_input_line_to_stdin(stdin, input_lines)
74
+ input_lines_processed += 1
75
+ result = IO.select([stdout_and_stderr], nil, nil, timeout)
76
+ retry
77
+ end
78
+ end
79
+ output
80
+ end
81
+
82
+ def readline_nonblock(io)
83
+ buffer = ''
84
+ while (ch = io.read_nonblock(1))
85
+ buffer << ch
86
+ if ch == "\n"
87
+ result = buffer
88
+ return result
89
+ end
90
+ end
91
+ end
92
+
93
+ def fail_unless_all_input_lines_processed(original_input_lines, input_lines_processed)
94
+ unprocessed_input_lines = original_input_lines.length - input_lines_processed
95
+ msg = "Output stream closed with #{unprocessed_input_lines} " \
96
+ 'input lines left unprocessed:' \
97
+ "#{original_input_lines[-(unprocessed_input_lines)..-1]}"
98
+ fail(
99
+ ProcessHelper::UnprocessedInputError,
100
+ msg
101
+ ) unless unprocessed_input_lines == 0
102
+ end
103
+
104
+ def puts_input_line_to_stdin(stdin, input_lines)
105
+ return if input_lines.empty?
106
+ input_line = input_lines.shift
107
+ stdin.puts(input_line)
108
+ end
109
+
110
+ def handle_exit_status(cmd, options, output, wait_thr)
111
+ expected_exit_status = options[:expected_exit_status]
112
+ exit_status = wait_thr.value
113
+ return if expected_exit_status.include?(exit_status.exitstatus)
114
+
115
+ exception_message = create_exception_message(cmd, exit_status, expected_exit_status)
116
+ if options[:include_output_in_exception]
117
+ exception_message += " Command Output: \"#{output}\""
118
+ end
119
+ puts_output_only_on_exception(options, output)
120
+ fail ProcessHelper::UnexpectedExitStatusError, exception_message
121
+ end
122
+
123
+ def create_exception_message(cmd, exit_status, expected_exit_status)
124
+ if expected_exit_status == [0]
125
+ result_msg = 'failed'
126
+ exit_status_msg = ''
127
+ elsif !expected_exit_status.include?(0)
128
+ result_msg = 'succeeded but was expected to fail'
129
+ exit_status_msg = " (expected #{expected_exit_status})"
130
+ else
131
+ result_msg = 'did not exit with one of the expected exit statuses'
132
+ exit_status_msg = " (expected #{expected_exit_status})"
133
+ end
134
+
135
+ "Command #{result_msg}, #{exit_status}#{exit_status_msg}. " \
136
+ "Command: `#{cmd}`."
137
+ end
138
+
139
+ def puts_output_only_on_exception(options, output)
140
+ return if options[:puts_output] == :always
141
+ puts output if options[:puts_output] == :error
142
+ end
143
+
144
+ def options_processing(options)
145
+ validate_long_vs_short_option_uniqueness(options)
146
+ convert_short_options(options)
147
+ set_option_defaults(options)
148
+ validate_option_values(options)
149
+ convert_scalar_expected_exit_status_to_array(options)
150
+ warn_if_output_may_be_suppressed_on_error(options)
151
+ end
152
+
153
+ # rubocop:disable Style/AccessorMethodName
154
+ def set_option_defaults(options)
155
+ options[:puts_output] = :always if options[:puts_output].nil?
156
+ options[:include_output_in_exception] = true if options[:include_output_in_exception].nil?
157
+ options[:expected_exit_status] = [0] if options[:expected_exit_status].nil?
158
+ options[:input_lines] = [] if options[:input_lines].nil?
159
+ end
160
+
161
+ def valid_option_pairs
162
+ pairs = [
163
+ %w(expected_exit_status exp_st),
164
+ %w(include_output_in_exception out_ex),
165
+ %w(input_lines in),
166
+ %w(puts_output out),
167
+ %w(timeout kill),
168
+ ]
169
+ pairs.each do |pair|
170
+ pair.each_with_index do |opt, index|
171
+ pair[index] = opt.to_sym
172
+ end
173
+ end
174
+ end
175
+
176
+ def valid_options
177
+ valid_option_pairs.flatten
178
+ end
179
+
180
+ def validate_long_vs_short_option_uniqueness(options)
181
+ invalid_options = (options.keys - valid_options)
182
+ fail(
183
+ ProcessHelper::InvalidOptionsError,
184
+ "Invalid option(s) '#{invalid_options.join(', ')}' given. " \
185
+ "Valid options are: #{valid_options.join(', ')}") unless invalid_options.empty?
186
+ valid_option_pairs.each do |pair|
187
+ long_option_name, short_option_name = pair
188
+ both_long_and_short_option_specified =
189
+ options[long_option_name] && options[short_option_name]
190
+ next unless both_long_and_short_option_specified
191
+ fail(
192
+ ProcessHelper::InvalidOptionsError,
193
+ "Cannot specify both '#{long_option_name}' and '#{short_option_name}'")
194
+ end
195
+ end
196
+
197
+ def convert_short_options(options)
198
+ valid_option_pairs.each do |pair|
199
+ long, short = pair
200
+ options[long] = options.delete(short) unless options[short].nil?
201
+ end
202
+ end
203
+
204
+ def validate_option_values(options)
205
+ options.each do |option, value|
206
+ valid_option_pairs.each do |pair|
207
+ long_option_name, _ = pair
208
+ next unless option == long_option_name
209
+ validate_integer(pair, value) if option.to_s == 'expected_exit_status'
210
+ validate_boolean(pair, value) if option.to_s == 'include_output_in_exception'
211
+ validate_puts_output(pair, value) if option.to_s == 'puts_output'
212
+ end
213
+ end
214
+ end
215
+
216
+ def validate_integer(pair, value)
217
+ valid =
218
+ case
219
+ when value.is_a?(Integer)
220
+ true
221
+ when value.is_a?(Array) && value.all? { |v| v.is_a?(Integer) }
222
+ true
223
+ else
224
+ false
225
+ end
226
+
227
+ fail(
228
+ ProcessHelper::InvalidOptionsError,
229
+ "#{quote_and_join_pair(pair)} options must be an Integer or an array of Integers"
230
+ ) unless valid
231
+ end
232
+
233
+ def validate_boolean(pair, value)
234
+ fail(
235
+ ProcessHelper::InvalidOptionsError,
236
+ "#{quote_and_join_pair(pair)} options must be a boolean"
237
+ ) unless value == true || value == false
238
+ end
239
+
240
+ def validate_puts_output(pair, value)
241
+ valid_values = [:always, :error, :never]
242
+ fail(
243
+ ProcessHelper::InvalidOptionsError,
244
+ "#{quote_and_join_pair(pair)} options must be one of the following: " +
245
+ valid_values.map { |v| ":#{v}" }.join(', ')
246
+ ) unless valid_values.include?(value)
247
+ end
248
+
249
+ def quote_and_join_pair(pair)
250
+ pair.map { |o| "'#{o}'" }.join(',')
251
+ end
252
+
253
+ def convert_scalar_expected_exit_status_to_array(options)
254
+ return if options[:expected_exit_status].is_a?(Array)
255
+ options[:expected_exit_status] =
256
+ [options[:expected_exit_status]]
257
+ end
258
+ end