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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +30 -0
  3. data/.travis.yml +1 -0
  4. data/Gemfile +4 -4
  5. data/README.md +87 -77
  6. data/Rakefile +1 -1
  7. data/bin/function_override_wrapper.sh.erb +6 -3
  8. data/bin/stub.rb.erb +56 -0
  9. data/lib/rspec/bash.rb +1 -0
  10. data/lib/rspec/bash/call_configuration.rb +39 -14
  11. data/lib/rspec/bash/call_log.rb +33 -43
  12. data/lib/rspec/bash/matchers/called_with_arguments.rb +7 -2
  13. data/lib/rspec/bash/stubbed_command.rb +22 -14
  14. data/lib/rspec/bash/stubbed_env.rb +15 -16
  15. data/lib/rspec/bash/util.rb +2 -0
  16. data/lib/rspec/bash/util/call_conf_argument_list_matcher.rb +47 -0
  17. data/lib/rspec/bash/util/call_log_argument_list_matcher.rb +33 -0
  18. data/return_exitstatus_spec.rb +14 -0
  19. data/rspec-bash.gemspec +2 -3
  20. data/spec/classes/call_configuration_spec.rb +296 -8
  21. data/spec/classes/call_log_spec.rb +168 -272
  22. data/spec/classes/stub_spec.rb +510 -0
  23. data/spec/classes/stubbed_command_spec.rb +26 -26
  24. data/spec/classes/stubbed_env_spec.rb +58 -64
  25. data/spec/classes/util/call_conf_argument_list_matcher_spec.rb +579 -0
  26. data/spec/classes/util/call_log_argument_list_matcher_spec.rb +211 -0
  27. data/spec/helper/shared_tmpdir.rb +6 -0
  28. data/spec/helper/string_file_io.rb +9 -0
  29. data/spec/integration/call_log/called_with_args_spec.rb +48 -0
  30. data/spec/integration/call_log/called_with_no_args_spec.rb +21 -0
  31. data/spec/integration/call_log/stdin_spec.rb +53 -0
  32. data/spec/integration/matchers/be_called_with_arguments_spec.rb +60 -0
  33. data/spec/integration/matchers/be_called_with_no_arguments_spec.rb +35 -0
  34. data/spec/integration/stubbed_command/outputs_spec.rb +262 -0
  35. data/spec/integration/stubbed_command/returns_exitstatus_spec.rb +99 -0
  36. data/spec/integration/stubbed_env/execute_with_env_vars_spec.rb +17 -0
  37. data/spec/integration/stubbed_env/execute_with_path_spec.rb +34 -0
  38. data/spec/integration/stubbed_env/execute_with_stub_wrapper_spec.rb +23 -0
  39. data/spec/spec_helper.rb +10 -0
  40. metadata +42 -24
  41. data/bin/stub +0 -62
  42. data/spec/integration/assert_called_spec.rb +0 -48
  43. data/spec/integration/assert_stdin_spec.rb +0 -39
  44. data/spec/integration/chain_args_spec.rb +0 -65
  45. data/spec/integration/change_exitstatus_spec.rb +0 -53
  46. data/spec/integration/provide_env_vars_spec.rb +0 -31
  47. data/spec/integration/replace_shell_commands_spec.rb +0 -48
  48. data/spec/integration/stub_output_spec.rb +0 -110
  49. data/spec/matchers/be_called_with_arguments_spec.rb +0 -55
  50. 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: ea2520732451e6815750cf97022a407cfb0f19ef
4
- data.tar.gz: 5d11657c4897d0ffcaff2e137b8da47c8bc8ac17
3
+ metadata.gz: 5264f0b8921b472fdb32e393e55004b5e1f3c885
4
+ data.tar.gz: bf99d54315422b4e885dfabab09729e39e94053c
5
5
  SHA512:
6
- metadata.gz: 922d168641fd597e4c98555f365ea51f4ee426ac685a0a86b8980287752b00163e59975015f4e1b134908b6f95e270a0d811b61902740a12ac52da5e46251ccf
7
- data.tar.gz: 97cade40565fd8fec2662a192308db8e56f07d4b70824338cdbc7621671d03e61c2354e52b890f90f7e805ccddd47d31c4c34f7f00514840f470e06d8a4a8d99
6
+ metadata.gz: 3bffee5f454d467b72a797016d1745cc1c5c6aa1743a4b24654394bd762738d953678d42350806e67a0a5c78c976fe99a05c2bc39793715b90a1cafffa811703
7
+ data.tar.gz: b9cb113c2ead82843566c750ce0b4fcf7f98e97a63117df42e8a2c603dbacac27243a0bcf2aaabff60b775c39f681f1bdc65910ef3fde1833c1590cd32c116d5
@@ -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'
@@ -1,6 +1,7 @@
1
1
  language: ruby
2
2
  sudo: false
3
3
  rvm:
4
+ - 2.2.0
4
5
  - 2.1.0
5
6
  - 2.0.0
6
7
  - ruby-head
data/Gemfile CHANGED
@@ -1,7 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gemspec
4
- gem 'rubocop'
3
+ gem 'rainbow', '>= 2.1.0', '< 2.2.0'
5
4
  gem 'rake'
6
- gem 'rspec'
7
- gem 'simplecov'
5
+ gem 'rspec', '3.5.0'
6
+ gem 'rubocop', '0.47.1'
7
+ gem 'simplecov', require: false, group: :test
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 if command is called
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 "anything" wildcard matchers
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
- { 'SOME_OPTIONAL' => 'env vars' }
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
- let(:stubbed_env) { create_stubbed_env }
86
- before do
87
- stubbed_env.stub_command('rake').returns_exitstatus(5)
88
- stubbed_env.stub_command('rake').with_args('spec').returns_exitstatus(3)
89
- end
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(:stubbed_env) { create_stubbed_env }
96
- let(:rake_stub) { stubbed_env.stub_command 'rake' }
97
- before do
98
- rake_stub.outputs('informative message', to: :stdout)
99
- .outputs('error message', to: :stderr)
100
- .outputs('log contents', to: 'logfile.log')
101
- .outputs('converted result', to: ['prefix-', :arg2, '.txt'])
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
- let(:cat_stub) { stubbed_env.stub_command 'cat' }
111
- let(:mail_stub) { stubbed_env.stub_command 'mail' }
112
- it 'verifies stdin' do
113
- stubbed_env.execute_script 'script.sh'
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
- ### Test entire script
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
- describe 'be_called_with_arguments' do
152
- include Rspec::Bash
153
- let(:stubbed_env) { create_stubbed_env }
146
+ stubbed_env.execute_inline(<<-multiline_script
147
+ stubbed_command first_argument second_argument
148
+ multiline_script
149
+ )
154
150
 
155
- context 'with a command' do
156
- context 'and no chain calls' do
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 = stubbed_env.stub_command('stubbed_command')
176
- stubbed_env.execute_inline(<<-multiline_script
177
- stubbed_command
178
- multiline_script
159
+ expect(@command).to be_called_with_no_arguments
160
+ ```
179
161
 
180
- it 'correctly identifies that no arguments were called' do
181
- expect(@command).to be_called_with_no_arguments
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
- ### Check that mock was called a certain number of times
175
+ ### Supports RSpec matchers
186
176
  ```ruby
187
- context 'and the times chain call' do
188
- before(:each) do
189
- @command = stubbed_env.stub_command('stubbed_command')
190
- @actual_stdout, @actual_stderr, @actual_status = stubbed_env.execute_inline(<<-multiline_script
191
- stubbed_command duplicated_argument
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 ( https://github.com/mdurban/rspec-bash )
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,6 @@
1
1
  require 'bundler/gem_tasks'
2
-
3
2
  require 'rspec/core/rake_task'
3
+
4
4
  RSpec::Core::RakeTask.new(:spec) do |t|
5
5
  t.verbose = false
6
6
  t.rspec_opts = '--format documentation'
@@ -1,6 +1,9 @@
1
1
  /usr/bin/env bash -c '
2
2
  # load in command and function overrides
3
- for override_file in <%=function_override_path_binding_for_template%>; do
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
- grep -v "readonly function" <%=wrapped_error_path_binding_for_template%> >&2
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}'
@@ -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
@@ -1,4 +1,5 @@
1
1
  require 'rspec/bash/stubbed_command'
2
+ require 'rspec/bash/util'
2
3
  require 'rspec/bash/call_configuration'
3
4
  require 'rspec/bash/call_log'
4
5
  require 'rspec/bash/stubbed_env'
@@ -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(statuscode, args = [])
16
- @configuration[args] ||= {}
17
- @configuration[args][:statuscode] = statuscode
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 set_output(content, target, args = [])
21
- @configuration[args] ||= {}
22
- @configuration[args][:outputs] ||= []
23
- @configuration[args][:outputs] << { target: target, content: content }
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 write
27
- structure = @configuration.map do |args, results|
28
- { args: args }.merge results
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
- @config_path.open('w') do |f|
32
- f.puts structure.to_yaml
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