rspec-bash 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +30 -0
- data/.travis.yml +1 -0
- data/Gemfile +4 -4
- data/README.md +87 -77
- data/Rakefile +1 -1
- data/bin/function_override_wrapper.sh.erb +6 -3
- data/bin/stub.rb.erb +56 -0
- data/lib/rspec/bash.rb +1 -0
- data/lib/rspec/bash/call_configuration.rb +39 -14
- data/lib/rspec/bash/call_log.rb +33 -43
- data/lib/rspec/bash/matchers/called_with_arguments.rb +7 -2
- data/lib/rspec/bash/stubbed_command.rb +22 -14
- data/lib/rspec/bash/stubbed_env.rb +15 -16
- data/lib/rspec/bash/util.rb +2 -0
- data/lib/rspec/bash/util/call_conf_argument_list_matcher.rb +47 -0
- data/lib/rspec/bash/util/call_log_argument_list_matcher.rb +33 -0
- data/return_exitstatus_spec.rb +14 -0
- data/rspec-bash.gemspec +2 -3
- data/spec/classes/call_configuration_spec.rb +296 -8
- data/spec/classes/call_log_spec.rb +168 -272
- data/spec/classes/stub_spec.rb +510 -0
- data/spec/classes/stubbed_command_spec.rb +26 -26
- data/spec/classes/stubbed_env_spec.rb +58 -64
- data/spec/classes/util/call_conf_argument_list_matcher_spec.rb +579 -0
- data/spec/classes/util/call_log_argument_list_matcher_spec.rb +211 -0
- data/spec/helper/shared_tmpdir.rb +6 -0
- data/spec/helper/string_file_io.rb +9 -0
- data/spec/integration/call_log/called_with_args_spec.rb +48 -0
- data/spec/integration/call_log/called_with_no_args_spec.rb +21 -0
- data/spec/integration/call_log/stdin_spec.rb +53 -0
- data/spec/integration/matchers/be_called_with_arguments_spec.rb +60 -0
- data/spec/integration/matchers/be_called_with_no_arguments_spec.rb +35 -0
- data/spec/integration/stubbed_command/outputs_spec.rb +262 -0
- data/spec/integration/stubbed_command/returns_exitstatus_spec.rb +99 -0
- data/spec/integration/stubbed_env/execute_with_env_vars_spec.rb +17 -0
- data/spec/integration/stubbed_env/execute_with_path_spec.rb +34 -0
- data/spec/integration/stubbed_env/execute_with_stub_wrapper_spec.rb +23 -0
- data/spec/spec_helper.rb +10 -0
- metadata +42 -24
- data/bin/stub +0 -62
- data/spec/integration/assert_called_spec.rb +0 -48
- data/spec/integration/assert_stdin_spec.rb +0 -39
- data/spec/integration/chain_args_spec.rb +0 -65
- data/spec/integration/change_exitstatus_spec.rb +0 -53
- data/spec/integration/provide_env_vars_spec.rb +0 -31
- data/spec/integration/replace_shell_commands_spec.rb +0 -48
- data/spec/integration/stub_output_spec.rb +0 -110
- data/spec/matchers/be_called_with_arguments_spec.rb +0 -55
- data/spec/matchers/be_called_with_no_arguments_spec.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5264f0b8921b472fdb32e393e55004b5e1f3c885
|
4
|
+
data.tar.gz: bf99d54315422b4e885dfabab09729e39e94053c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3bffee5f454d467b72a797016d1745cc1c5c6aa1743a4b24654394bd762738d953678d42350806e67a0a5c78c976fe99a05c2bc39793715b90a1cafffa811703
|
7
|
+
data.tar.gz: b9cb113c2ead82843566c750ce0b4fcf7f98e97a63117df42e8a2c603dbacac27243a0bcf2aaabff60b775c39f681f1bdc65910ef3fde1833c1590cd32c116d5
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Metrics/LineLength:
|
2
|
+
Max: 100
|
3
|
+
|
4
|
+
Style/NumericLiteralPrefix:
|
5
|
+
Enabled: false
|
6
|
+
|
7
|
+
Style/Documentation:
|
8
|
+
Enabled: false
|
9
|
+
|
10
|
+
Lint/HandleExceptions:
|
11
|
+
Exclude:
|
12
|
+
- 'spec/classes/stub_spec.rb'
|
13
|
+
|
14
|
+
Metrics/BlockLength:
|
15
|
+
Max: 428
|
16
|
+
|
17
|
+
Security/YAMLLoad:
|
18
|
+
Exclude:
|
19
|
+
- 'bin/stub.rb.erb'
|
20
|
+
- 'spec/classes/stub_spec.rb'
|
21
|
+
- 'lib/rspec/bash/call_configuration.rb'
|
22
|
+
- 'lib/rspec/bash/call_log.rb'
|
23
|
+
- 'spec/classes/util/call_conf_argument_list_matcher_spec.rb'
|
24
|
+
|
25
|
+
Metrics/BlockLength:
|
26
|
+
Max: 1000
|
27
|
+
|
28
|
+
Style/TrivialAccessors:
|
29
|
+
Exclude:
|
30
|
+
- 'lib/rspec/bash/call_log.rb'
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
[![Stories in Ready](https://badge.waffle.io/mdurban/rspec-bash.png?label=ready&title=Ready)](http://waffle.io/mdurban/rspec-bash)
|
2
|
+
[![Build Status](https://travis-ci.org/mdurban/rspec-bash.svg?branch=master)](https://travis-ci.org/mdurban/rspec-bash)
|
3
|
+
|
1
4
|
# Rspec::Bash
|
2
5
|
|
3
6
|
Run your shell script in a mocked environment to test its behavior using RSpec.
|
@@ -6,13 +9,11 @@ Run your shell script in a mocked environment to test its behavior using RSpec.
|
|
6
9
|
- Test bash functions, entire scripts and inline scripts
|
7
10
|
- Stub shell commands and their exitstatus and outputs
|
8
11
|
- Partial mocks of functions
|
9
|
-
- Control exit status codes
|
10
12
|
- Control multiple outputs (through STDOUT, STDERR or files)
|
11
13
|
- Verify STDIN, STDOUT, STDERR
|
12
|
-
- Verify
|
13
|
-
- Verify command is called with specific argument sequence
|
14
|
+
- Verify command was called with specific argument sequence
|
14
15
|
- Verify command was called correct number of times
|
15
|
-
- Supports RSpec
|
16
|
+
- Supports RSpec matchers
|
16
17
|
|
17
18
|
## Installation
|
18
19
|
|
@@ -60,7 +61,7 @@ see specs in *spec/integration* folder:
|
|
60
61
|
it 'runs the script' do
|
61
62
|
stdout, stderr, status = stubbed_env.execute(
|
62
63
|
'my-shell-script.sh',
|
63
|
-
{ '
|
64
|
+
{ 'OPTIONAL_ENV' => 'env vars' }
|
64
65
|
)
|
65
66
|
expect(status.exitstatus).to eq 0
|
66
67
|
end
|
@@ -82,134 +83,143 @@ see specs in *spec/integration* folder:
|
|
82
83
|
### Changing exitstatus of stubs:
|
83
84
|
|
84
85
|
```ruby
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
86
|
+
stubbed_env.stub_command('rake').returns_exitstatus(5)
|
87
|
+
```
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
stubbed_env.stub_command('rake').with_args('spec').returns_exitstatus(3)
|
90
91
|
```
|
91
92
|
|
92
93
|
### Stubbing output:
|
93
94
|
|
94
95
|
```ruby
|
95
|
-
let(:
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
# last one creates 'prefix-foo.txt' when called as 'rake convert foo'
|
103
|
-
end
|
96
|
+
let(:rake_stub) { stubbed_env.stub_command('rake') }
|
97
|
+
|
98
|
+
rake_stub.outputs('informative message', to: :stdout)
|
99
|
+
.outputs('error message', to: :stderr)
|
100
|
+
.outputs('log contents', to: 'logfile.log')
|
101
|
+
# creates 'prefix-foo.txt' when called as 'rake convert foo'
|
102
|
+
.outputs('converted result', to: ['prefix-', :arg2, '.txt'])
|
104
103
|
```
|
105
104
|
|
106
105
|
### Verifying stdin:
|
107
106
|
|
108
107
|
```ruby
|
109
108
|
let(:stubbed_env) { create_stubbed_env }
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
109
|
+
|
110
|
+
it 'verifies stdin with no args' do
|
111
|
+
cat_stub = stubbed_env.stub_command('cat')
|
112
|
+
|
114
113
|
expect(cat_stub.stdin).to eql 'hello'
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'verifies stdin with args' do
|
117
|
+
mail_stub = stubbed_env.stub_command('mail')
|
118
|
+
|
115
119
|
expect(mail_stub.with_args('-s', 'hello').stdin).to eql 'world'
|
116
120
|
end
|
117
121
|
```
|
118
|
-
|
122
|
+
|
123
|
+
### Test entire script, specific function or inline script
|
119
124
|
|
120
125
|
```ruby
|
121
|
-
let(:stubbed_env) { Rspec::Bash::StubbedEnv.new }
|
122
126
|
stubbed_env.execute('./path/to/script.sh')
|
123
127
|
```
|
124
128
|
|
125
|
-
### Test specific function
|
126
|
-
|
127
129
|
```ruby
|
128
|
-
let(:stubbed_env) { Rspec::Bash::StubbedEnv.new }
|
129
|
-
stubbed_env.stub_command('overridden_function')
|
130
130
|
stubbed_env.execute_function(
|
131
131
|
'./path/to/script.sh',
|
132
132
|
'overridden_function'
|
133
133
|
)
|
134
|
-
|
135
134
|
```
|
136
135
|
|
137
|
-
### Test inline script
|
138
|
-
|
139
136
|
```ruby
|
140
|
-
let(:stubbed_env) { create_stubbed_env }
|
141
|
-
stubbed_env.stub_command('stubbed_command')
|
142
137
|
stubbed_env.execute_inline(<<-multiline_script
|
143
138
|
stubbed_command first_argument second_argument
|
144
139
|
multiline_script
|
145
140
|
)
|
146
|
-
|
147
141
|
```
|
142
|
+
|
148
143
|
### Check that mock was called with specific arguments
|
149
144
|
|
150
145
|
```ruby
|
151
|
-
|
152
|
-
|
153
|
-
|
146
|
+
stubbed_env.execute_inline(<<-multiline_script
|
147
|
+
stubbed_command first_argument second_argument
|
148
|
+
multiline_script
|
149
|
+
)
|
154
150
|
|
155
|
-
|
156
|
-
|
157
|
-
before(:each) do
|
158
|
-
@command = stubbed_env.stub_command('stubbed_command')
|
159
|
-
@actual_stdout, @actual_stderr, @actual_status = stubbed_env.execute_inline(<<-multiline_script
|
160
|
-
stubbed_command first_argument second_argument
|
161
|
-
multiline_script
|
162
|
-
)
|
163
|
-
end
|
164
|
-
it 'correctly identifies the called arguments' do
|
165
|
-
expect(@command).to be_called_with_arguments('first_argument', 'second_argument')
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
151
|
+
it 'correctly identifies the called arguments' do
|
152
|
+
expect(@command).to be_called_with_arguments('first_argument', 'second_argument')
|
169
153
|
end
|
170
154
|
```
|
171
155
|
|
172
156
|
### Check that mock was not called with any arguments
|
173
157
|
|
174
158
|
```ruby
|
175
|
-
@command
|
176
|
-
|
177
|
-
stubbed_command
|
178
|
-
multiline_script
|
159
|
+
expect(@command).to be_called_with_no_arguments
|
160
|
+
```
|
179
161
|
|
180
|
-
|
181
|
-
|
162
|
+
### Check that mock was called a certain number of times
|
163
|
+
```ruby
|
164
|
+
@actual_stdout, @actual_stderr, @actual_status = stubbed_env.execute_inline(<<-multiline_script
|
165
|
+
stubbed_command duplicated_argument
|
166
|
+
stubbed_command duplicated_argument
|
167
|
+
stubbed_command once_called_argument
|
168
|
+
multiline_script
|
169
|
+
|
170
|
+
expect(@command).to be_called_with_arguments('duplicated_argument').times(2)
|
171
|
+
expect(@command).to be_called_with_arguments('once_called_argument').times(1)
|
182
172
|
end
|
183
173
|
```
|
184
174
|
|
185
|
-
###
|
175
|
+
### Supports RSpec matchers
|
186
176
|
```ruby
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
stubbed_command duplicated_argument
|
193
|
-
stubbed_command once_called_argument
|
194
|
-
multiline_script
|
195
|
-
)
|
196
|
-
end
|
197
|
-
it 'matches when arguments are called twice' do
|
198
|
-
expect(@command).to be_called_with_arguments('duplicated_argument').times(2)
|
199
|
-
end
|
200
|
-
it 'matches when argument is called once' do
|
201
|
-
expect(@command).to be_called_with_arguments('once_called_argument').times(1)
|
202
|
-
end
|
177
|
+
it 'stub call with a wildcard used for an argument' do
|
178
|
+
grep_mock = stubbed_env.stub_command('grep')
|
179
|
+
grep_mock.with_args('-r', anything).outputs('output from grep')
|
180
|
+
|
181
|
+
expect(command).to be_called_with_arguments('output from grep')
|
203
182
|
end
|
204
183
|
```
|
205
184
|
|
206
|
-
### Use rspec "anything" wildcards for arguments you don't need to match exactly
|
207
185
|
```ruby
|
208
186
|
it 'correctly matches when wildcard is used for arguments' do
|
209
187
|
expect(@command).to be_called_with_arguments(anything, 'second_argument', anything)
|
210
188
|
end
|
211
189
|
```
|
212
190
|
|
191
|
+
```ruby
|
192
|
+
it 'matches any arguments' do
|
193
|
+
expect(@command).to be_called_with_arguments
|
194
|
+
end
|
195
|
+
```
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
it 'matches all arguments' do
|
199
|
+
expect(@command).to be_called_with_arguments(any_args)
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
it 'matches any String argument' do
|
205
|
+
expect(@command).to be_called_with_arguments(instance_of(String))
|
206
|
+
end
|
207
|
+
```
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
it 'matches using regexp' do
|
211
|
+
expect(@command).to be_called_with_arguments(/s..arg/)
|
212
|
+
end
|
213
|
+
```
|
214
|
+
|
215
|
+
### Pitfalls and known issues
|
216
|
+
|
217
|
+
- Use `$BASH_SOURCE[0]` instead of `$0` in your Bash scripts when trying to get the directory that your called script is in. This is a good habit to use when writing scripts as `$0` should rarely be used.
|
218
|
+
`$0` also has some ramifications when using this gem; it will always be `bash` and will not be the name of the script.
|
219
|
+
Please see https://www.gnu.org/software/bash/manual/bashref.html#Positional-Parameters for more information on `$0`
|
220
|
+
|
221
|
+
- The `execute_function()` method is recommended to be used only when testing Bash libraries. This is because it needs to source the entire file to run the function under test, so any executable code in the script will be run even if it is outside of the function being tested
|
222
|
+
|
213
223
|
## More examples
|
214
224
|
|
215
225
|
see the *spec/integration* folder
|
@@ -220,7 +230,7 @@ Ruby 2+, no JRuby, due to issues with `Open3.capture3`
|
|
220
230
|
|
221
231
|
## Contributing
|
222
232
|
|
223
|
-
1. Fork it (
|
233
|
+
1. Fork it (https://github.com/mdurban/rspec-bash)
|
224
234
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
225
235
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
226
236
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/Rakefile
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
/usr/bin/env bash -c '
|
2
2
|
# load in command and function overrides
|
3
|
-
|
3
|
+
system_ls=$(which ls)
|
4
|
+
system_grep=$(which grep)
|
5
|
+
|
6
|
+
for override_file in $(${system_ls} -d <%=function_override_path_binding_for_template%> 2> /dev/null); do
|
4
7
|
source ${override_file}
|
5
8
|
done
|
6
9
|
|
@@ -10,7 +13,7 @@ done
|
|
10
13
|
command_exit_code=$?
|
11
14
|
|
12
15
|
# filter stderr for readonly problems
|
13
|
-
|
16
|
+
${system_grep} -v "readonly function" <%=wrapped_error_path_binding_for_template%> >&2
|
14
17
|
|
15
18
|
# return original exit code
|
16
|
-
exit ${command_exit_code}'
|
19
|
+
exit ${command_exit_code}'
|
data/bin/stub.rb.erb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift('<%= rspec_bash_library_path_for_template %>')
|
3
|
+
|
4
|
+
require 'rspec/bash'
|
5
|
+
require 'pathname'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
include Rspec::Bash
|
9
|
+
|
10
|
+
command = File.basename(__FILE__)
|
11
|
+
folder = File.dirname(__FILE__)
|
12
|
+
|
13
|
+
call_log_path = Pathname.new(folder).join("#{command}_calls.yml")
|
14
|
+
call_log = CallLog.new(call_log_path)
|
15
|
+
call_log.add_log(STDIN.tty? ? '' : $stdin.read, ARGV)
|
16
|
+
|
17
|
+
def interpolate_filename(elements)
|
18
|
+
return elements if elements.is_a? String
|
19
|
+
return nil unless elements.is_a? Array
|
20
|
+
|
21
|
+
elements.map do |element|
|
22
|
+
case element
|
23
|
+
when String then
|
24
|
+
element
|
25
|
+
when Symbol then
|
26
|
+
interpolate_argument(element)
|
27
|
+
end
|
28
|
+
end.join
|
29
|
+
end
|
30
|
+
|
31
|
+
def interpolate_argument(name)
|
32
|
+
return unless (data = /^arg(\d+)$/.match(name.to_s))
|
33
|
+
ARGV[data[1].to_i - 1]
|
34
|
+
end
|
35
|
+
|
36
|
+
call_conf_path = Pathname.new(folder).join("#{command}_stub.yml")
|
37
|
+
if call_conf_path.exist?
|
38
|
+
call_conf = CallConfiguration.new(call_conf_path, command)
|
39
|
+
config = call_conf.call_configuration
|
40
|
+
|
41
|
+
call_conf_arg_matcher = Util::CallConfArgumentListMatcher.new(config)
|
42
|
+
best_matching_call_conf = call_conf_arg_matcher.get_best_call_conf(*ARGV)
|
43
|
+
exit 0 if best_matching_call_conf.empty?
|
44
|
+
|
45
|
+
(best_matching_call_conf[:outputs] || []).each do |data|
|
46
|
+
$stdout.print data[:content] if data[:target] == :stdout
|
47
|
+
$stderr.print data[:content] if data[:target] == :stderr
|
48
|
+
|
49
|
+
output_filename = interpolate_filename(data[:target])
|
50
|
+
next unless output_filename
|
51
|
+
Pathname.new(output_filename).open('w') do |f|
|
52
|
+
f.print data[:content]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
exit best_matching_call_conf[:exitcode] || 0
|
56
|
+
end
|
data/lib/rspec/bash.rb
CHANGED
@@ -2,36 +2,61 @@ require 'yaml'
|
|
2
2
|
|
3
3
|
module Rspec
|
4
4
|
module Bash
|
5
|
-
# Configuration of a stubbed command
|
6
5
|
class CallConfiguration
|
7
6
|
attr_reader :command
|
8
7
|
|
9
8
|
def initialize(config_path, command)
|
10
9
|
@config_path = config_path
|
11
|
-
@configuration =
|
10
|
+
@configuration = []
|
12
11
|
@command = command
|
13
12
|
end
|
14
13
|
|
15
|
-
def set_exitcode(
|
16
|
-
|
17
|
-
|
14
|
+
def set_exitcode(exitcode, args = [])
|
15
|
+
current_conf = create_or_get_conf(args)
|
16
|
+
current_conf[:exitcode] = exitcode
|
17
|
+
write @configuration
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
def add_output(content, target, args = [])
|
21
|
+
current_conf = create_or_get_conf(args)
|
22
|
+
current_conf[:outputs] << {
|
23
|
+
target: target,
|
24
|
+
content: content
|
25
|
+
}
|
26
|
+
write @configuration
|
24
27
|
end
|
25
28
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
+
def call_configuration
|
30
|
+
@config_path.open('r') do |conf_file|
|
31
|
+
YAML.load(conf_file.read) || []
|
29
32
|
end
|
33
|
+
rescue NoMethodError, Errno::ENOENT
|
34
|
+
return []
|
35
|
+
end
|
36
|
+
|
37
|
+
def call_configuration=(new_conf)
|
38
|
+
write new_conf
|
39
|
+
end
|
30
40
|
|
31
|
-
|
32
|
-
|
41
|
+
private
|
42
|
+
|
43
|
+
def write(call_conf_to_write)
|
44
|
+
@config_path.open('w') do |conf_file|
|
45
|
+
conf_file.write call_conf_to_write.to_yaml
|
33
46
|
end
|
34
47
|
end
|
48
|
+
|
49
|
+
def create_or_get_conf(args)
|
50
|
+
@configuration = call_configuration
|
51
|
+
new_conf = {
|
52
|
+
args: args,
|
53
|
+
exitcode: 0,
|
54
|
+
outputs: []
|
55
|
+
}
|
56
|
+
current_conf = @configuration.select { |conf| conf[:args] == args }
|
57
|
+
@configuration << new_conf if current_conf.empty?
|
58
|
+
current_conf.first || new_conf
|
59
|
+
end
|
35
60
|
end
|
36
61
|
end
|
37
62
|
end
|