rspec-bash 0.1.1 → 0.2.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 +8 -0
- data/Gemfile +1 -0
- data/README.md +23 -0
- data/Rakefile +15 -4
- data/bin/bash_stub.sh +92 -0
- data/bin/bash_wrapper.sh.erb +12 -0
- data/bin/ruby_stub.rb +33 -0
- data/lib/rspec/bash.rb +5 -4
- data/lib/rspec/bash/command.rb +5 -0
- data/lib/rspec/bash/command/call_configuration.rb +76 -0
- data/lib/rspec/bash/command/call_configuration_manager.rb +24 -0
- data/lib/rspec/bash/command/call_log.rb +48 -0
- data/lib/rspec/bash/command/call_log_manager.rb +38 -0
- data/lib/rspec/bash/command/stubbed_command.rb +64 -0
- data/lib/rspec/bash/server.rb +3 -0
- data/lib/rspec/bash/server/bash_stub_marshaller.rb +19 -0
- data/lib/rspec/bash/server/ruby_stub_marshaller.rb +13 -0
- data/lib/rspec/bash/server/stub_server.rb +47 -0
- data/lib/rspec/bash/stubbed_env.rb +75 -54
- data/lib/rspec/bash/util/call_conf_argument_list_matcher.rb +5 -5
- data/lib/rspec/bash/util/call_log_argument_list_matcher.rb +1 -1
- data/lib/rspec/bash/wrapper.rb +4 -0
- data/lib/rspec/bash/wrapper/bash_stub_script.rb +15 -0
- data/lib/rspec/bash/wrapper/bash_wrapper.rb +54 -0
- data/lib/rspec/bash/wrapper/ruby_stub_script.rb +15 -0
- data/lib/rspec/bash/wrapper/stub_function.rb +36 -0
- data/rspec-bash.gemspec +2 -1
- data/spec/classes/command/call_configuration_manager_spec.rb +68 -0
- data/spec/classes/{call_configuration_spec.rb → command/call_configuration_spec.rb} +51 -114
- data/spec/classes/command/call_log_manager_spec.rb +83 -0
- data/spec/classes/{call_log_spec.rb → command/call_log_spec.rb} +23 -82
- data/spec/classes/command/stubbed_command_spec.rb +118 -0
- data/spec/classes/server/bash_stub_marshaller_spec.rb +38 -0
- data/spec/classes/server/ruby_stub_marshaller_spec.rb +31 -0
- data/spec/classes/server/stub_server_spec.rb +121 -0
- data/spec/classes/stubbed_env_spec.rb +141 -280
- data/spec/classes/util/call_conf_argument_list_matcher_spec.rb +17 -17
- data/spec/classes/util/call_log_argument_list_matcher_spec.rb +24 -18
- data/spec/classes/wrapper/bash_wrapper_spec.rb +37 -0
- data/spec/classes/wrapper/ruby_stub_script_spec.rb +204 -0
- data/spec/helper/string_file_io.rb +1 -1
- data/spec/integration/call_log/called_with_args_spec.rb +8 -4
- data/spec/integration/call_log/called_with_no_args_spec.rb +1 -1
- data/spec/integration/call_log/stdin_spec.rb +10 -4
- data/spec/integration/edge_cases_spec.rb +34 -0
- data/spec/integration/matchers/be_called_with_arguments_spec.rb +12 -13
- data/spec/integration/matchers/be_called_with_no_arguments_spec.rb +6 -7
- data/spec/integration/stubbed_command/outputs_spec.rb +111 -91
- data/spec/integration/stubbed_command/returns_exitstatus_spec.rb +46 -37
- data/spec/integration/stubbed_env/execute_with_env_vars_spec.rb +3 -4
- data/spec/integration/stubbed_env/execute_with_path_spec.rb +6 -7
- data/spec/integration/stubbed_env/execute_with_stub_wrapper_spec.rb +4 -12
- data/spec/integration/stubbed_env/override_spec.rb +354 -0
- data/spec/integration/wrapper/bash_stub_script_spec.rb +383 -0
- data/spec/integration/wrapper/bash_wrapper_spec.rb +48 -0
- data/spec/scripts/function_library.sh +9 -1
- data/spec/spec_helper.rb +2 -0
- metadata +65 -21
- data/bin/function_override.sh.erb +0 -7
- data/bin/function_override_wrapper.sh.erb +0 -19
- data/bin/stub.rb.erb +0 -56
- data/lib/rspec/bash/call_configuration.rb +0 -62
- data/lib/rspec/bash/call_log.rb +0 -71
- data/lib/rspec/bash/stubbed_command.rb +0 -88
- data/spec/classes/stub_spec.rb +0 -510
- data/spec/classes/stubbed_command_spec.rb +0 -134
- data/spec/integration/assert_called_spec.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 840b1879e77a1e03bd1d2b0ee87e1ccc47126294
|
4
|
+
data.tar.gz: 73cc3d08c7cb46f4f85198f477aeb2417d7774ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 853e2d1bcf90edcc2ce938269fd9360f225fc46f1a386f929955ad8544ea5de3b7e5bbd0da22b58a4bd0c402f8243901d49fad68e3d560569fd25c7051e00025
|
7
|
+
data.tar.gz: b1a371ab0fa15617d931ad5579d108189d7d0efe20087902dff1beb0ff3bdac136b21b43acba14dae73c7483e13088fbbb09d4926870e38546bb8b2b0633131a
|
data/.rubocop.yml
CHANGED
@@ -7,13 +7,21 @@ Style/NumericLiteralPrefix:
|
|
7
7
|
Style/Documentation:
|
8
8
|
Enabled: false
|
9
9
|
|
10
|
+
Security/MarshalLoad:
|
11
|
+
Enabled: false
|
12
|
+
|
10
13
|
Lint/HandleExceptions:
|
11
14
|
Exclude:
|
12
15
|
- 'spec/classes/stub_spec.rb'
|
16
|
+
- 'spec/classes/wrapper/ruby_stub_script_spec.rb'
|
13
17
|
|
14
18
|
Metrics/BlockLength:
|
15
19
|
Max: 428
|
16
20
|
|
21
|
+
Metrics/MethodLength:
|
22
|
+
Exclude:
|
23
|
+
- 'lib/rspec/bash/stubbed_command.rb'
|
24
|
+
|
17
25
|
Security/YAMLLoad:
|
18
26
|
Exclude:
|
19
27
|
- 'bin/stub.rb.erb'
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -68,15 +68,37 @@ see specs in *spec/integration* folder:
|
|
68
68
|
end
|
69
69
|
```
|
70
70
|
|
71
|
+
### Choosing a stub type
|
72
|
+
The stubbed functions/commands for the test runner were traditionally built with ruby. A faster alternative stub, written in bash, has recently been introduced, and is the current default. This can be configured, however, as shown.
|
73
|
+
For ruby:
|
74
|
+
```ruby
|
75
|
+
let(:stubbed_env) { create_stubbed_env(StubbedEnv::RUBY_STUB) }
|
76
|
+
```
|
77
|
+
For bash (default, if not provided):
|
78
|
+
```ruby
|
79
|
+
let(:stubbed_env) { create_stubbed_env(StubbedEnv::BASH_STUB) }
|
80
|
+
```
|
81
|
+
|
82
|
+
Via environment variable:
|
83
|
+
```bash
|
84
|
+
export RSPEC_BASH_STUB_TYPE=ruby_stub
|
85
|
+
# <run your tests here>
|
86
|
+
...
|
87
|
+
```
|
88
|
+
|
71
89
|
### Stubbing commands:
|
72
90
|
|
73
91
|
```ruby
|
74
92
|
let(:stubbed_env) { create_stubbed_env }
|
75
93
|
let!(:bundle) { stubbed_env.stub_command('bundle') }
|
94
|
+
let!(:absolute_command) { stubbed_env.stub_command('/path/to/bundle') }
|
95
|
+
let!(:relative_command) { stubbed_env.stub_command('./path/to/bundle') }
|
76
96
|
|
77
97
|
it 'is stubbed' do
|
78
98
|
stubbed_env.execute 'my-script.sh'
|
79
99
|
expect(bundle).to be_called_with_arguments('install')
|
100
|
+
expect(absolute_command).to be_called_with_arguments('hello')
|
101
|
+
expect(relative_command).to be_called_with_arguments('world')
|
80
102
|
end
|
81
103
|
```
|
82
104
|
|
@@ -220,6 +242,7 @@ Please see https://www.gnu.org/software/bash/manual/bashref.html#Positional-Para
|
|
220
242
|
|
221
243
|
- 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
244
|
|
245
|
+
- The current form of stub injection does not allow for stubs to be picked up by other commands. Ex. `xargs stubbed_command` will result in the `stubbed_command` not being found. There is a pending issue for this.
|
223
246
|
## More examples
|
224
247
|
|
225
248
|
see the *spec/integration* folder
|
data/Rakefile
CHANGED
@@ -1,13 +1,24 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
2
|
require 'rspec/core/rake_task'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
def spec(task)
|
5
|
+
task.verbose = false
|
6
|
+
task.rspec_opts = '--format documentation'
|
7
|
+
task.rspec_opts << ' --color'
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new(:spec_bash_stub) do |t|
|
11
|
+
ENV['RSPEC_BASH_STUB_TYPE'] = :bash_stub.to_s
|
12
|
+
spec(t)
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec::Core::RakeTask.new(:spec_ruby_stub) do |t|
|
16
|
+
ENV['RSPEC_BASH_STUB_TYPE'] = :ruby_stub.to_s
|
17
|
+
spec(t)
|
8
18
|
end
|
9
19
|
|
10
20
|
require 'rubocop/rake_task'
|
11
21
|
RuboCop::RakeTask.new
|
12
22
|
|
23
|
+
task spec: [:spec_bash_stub, :spec_ruby_stub]
|
13
24
|
task default: [:rubocop, :spec]
|
data/bin/bash_stub.sh
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
function json-encode {
|
3
|
+
echo -n ".${1}." |
|
4
|
+
awk '
|
5
|
+
BEGIN {RS=""}
|
6
|
+
{
|
7
|
+
gsub(/\\/, "\\\\")
|
8
|
+
gsub(/\"/, "\\\"")
|
9
|
+
gsub(/\t/, "\\t")
|
10
|
+
gsub(/\r/, "\\r")
|
11
|
+
gsub(/\b/, "\\b")
|
12
|
+
gsub(/\n/, "\\n")
|
13
|
+
print substr($0, 2, length($0)-2)
|
14
|
+
}
|
15
|
+
'
|
16
|
+
}
|
17
|
+
|
18
|
+
function json-decode {
|
19
|
+
echo -n "${1}" |
|
20
|
+
awk '
|
21
|
+
{
|
22
|
+
gsub(/\\"/, "\"")
|
23
|
+
print $0
|
24
|
+
}
|
25
|
+
'
|
26
|
+
}
|
27
|
+
|
28
|
+
function create-call-log {
|
29
|
+
command_name=${1}; shift
|
30
|
+
command_port=${1}; shift
|
31
|
+
raw_stdin=$([[ -t 0 ]] && echo -n '' || cat -; ret=$?; echo .; exit "$ret")
|
32
|
+
|
33
|
+
argument_list=( "${@}" )
|
34
|
+
command=$(
|
35
|
+
echo "\"command\":\"${command_name}\","
|
36
|
+
)
|
37
|
+
stdin=$(
|
38
|
+
echo "\"stdin\":\"$(json-encode "${raw_stdin%.}")\","
|
39
|
+
)
|
40
|
+
arguments=$(
|
41
|
+
for index in "${!argument_list[@]}"; do
|
42
|
+
arg="${argument_list[${index}]}"
|
43
|
+
echo "\"args.${index}\":\"$(json-encode "${arg}")\","
|
44
|
+
done
|
45
|
+
)
|
46
|
+
|
47
|
+
echo "{"
|
48
|
+
echo "${command}"
|
49
|
+
[[ -n "${arguments}" ]] && echo "${stdin}" || echo "${stdin%,}"
|
50
|
+
[[ -n "${arguments}" ]] && echo "${arguments%,}"
|
51
|
+
echo "}"
|
52
|
+
}
|
53
|
+
|
54
|
+
function send-to-server {
|
55
|
+
echo -n "${2}" | nc localhost ${1}
|
56
|
+
}
|
57
|
+
|
58
|
+
function extract-properties {
|
59
|
+
echo -n "${1}" | sed -En "s/^\"${2}\":\"?([^,\"]*)\"?,?$/\1/gp"
|
60
|
+
}
|
61
|
+
|
62
|
+
function print-output {
|
63
|
+
case ${1} in
|
64
|
+
stdout)
|
65
|
+
echo -en "${2}"
|
66
|
+
;;
|
67
|
+
stderr)
|
68
|
+
echo -en "${2}" >&2
|
69
|
+
;;
|
70
|
+
*)
|
71
|
+
echo -en "${2}" > ${1}
|
72
|
+
;;
|
73
|
+
esac
|
74
|
+
}
|
75
|
+
function main {
|
76
|
+
client_message=$(create-call-log "${@}")
|
77
|
+
server_message=$(send-to-server "${2}" "${client_message}")
|
78
|
+
IFS=$'\n'
|
79
|
+
target_list=( $(extract-properties "${server_message}" "outputs\..*\.target") )
|
80
|
+
content_list=( $(extract-properties "${server_message}" "outputs\..*\.content") )
|
81
|
+
exit_code=$(extract-properties "${server_message}" "exitcode")
|
82
|
+
|
83
|
+
for index in "${!target_list[@]}"; do
|
84
|
+
target=${target_list[${index}]}
|
85
|
+
content=$(json-decode "${content_list[${index}]}")
|
86
|
+
print-output "${target}" "${content}"
|
87
|
+
done
|
88
|
+
|
89
|
+
exit ${exit_code}
|
90
|
+
}
|
91
|
+
|
92
|
+
[[ ${0} == ${BASH_SOURCE} ]] && main "${@}"
|
data/bin/ruby_stub.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
name = ARGV.shift
|
5
|
+
port = ARGV.shift
|
6
|
+
|
7
|
+
sock = TCPSocket.new('localhost', port)
|
8
|
+
call_from_client = {
|
9
|
+
command: name,
|
10
|
+
stdin: STDIN.tty? ? '' : $stdin.read,
|
11
|
+
args: ARGV
|
12
|
+
}
|
13
|
+
sock.write(Marshal.dump(call_from_client))
|
14
|
+
sock.close_write
|
15
|
+
conf_from_server = Marshal.load(sock.read)
|
16
|
+
sock.close_read
|
17
|
+
|
18
|
+
exit 0 if conf_from_server.empty?
|
19
|
+
|
20
|
+
(conf_from_server[:outputs] || []).each do |data|
|
21
|
+
if data[:target] == :stdout
|
22
|
+
$stdout.print data[:content]
|
23
|
+
next
|
24
|
+
end
|
25
|
+
if data[:target] == :stderr
|
26
|
+
$stderr.print data[:content]
|
27
|
+
next
|
28
|
+
end
|
29
|
+
Pathname.new(data[:target]).open('w') do |f|
|
30
|
+
f.print data[:content]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
exit conf_from_server[:exitcode] || 0
|
data/lib/rspec/bash.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
require 'rspec/bash/
|
1
|
+
require 'rspec/bash/command'
|
2
|
+
require 'rspec/bash/matchers'
|
3
|
+
require 'rspec/bash/server'
|
2
4
|
require 'rspec/bash/util'
|
3
|
-
require 'rspec/bash/
|
4
|
-
|
5
|
+
require 'rspec/bash/wrapper'
|
6
|
+
|
5
7
|
require 'rspec/bash/stubbed_env'
|
6
|
-
require 'rspec/bash/matchers'
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Rspec
|
4
|
+
module Bash
|
5
|
+
class CallConfiguration
|
6
|
+
attr_accessor :call_configuration
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@call_configuration = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def set_exitcode(exitcode, args = [])
|
13
|
+
current_conf = create_or_get_conf(args)
|
14
|
+
current_conf[:exitcode] = exitcode
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_output(content, target, args = [])
|
18
|
+
current_conf = create_or_get_conf(args)
|
19
|
+
current_conf[:outputs] << {
|
20
|
+
target: target,
|
21
|
+
content: content
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_best_call_conf(args = [])
|
26
|
+
call_conf_arg_matcher = Util::CallConfArgumentListMatcher.new(@call_configuration)
|
27
|
+
best_call_conf = call_conf_arg_matcher.get_best_call_conf(args)
|
28
|
+
remove_args_from_conf(
|
29
|
+
interpolate_output_targets(
|
30
|
+
best_call_conf,
|
31
|
+
args
|
32
|
+
)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def interpolate_output_targets(conf, args)
|
39
|
+
return conf if conf.empty?
|
40
|
+
conf[:outputs].each do |output|
|
41
|
+
output[:target] = interpolate_target(output[:target], args)
|
42
|
+
end
|
43
|
+
conf
|
44
|
+
end
|
45
|
+
|
46
|
+
def interpolate_target(target, args)
|
47
|
+
return target unless target.is_a? Array
|
48
|
+
target.map do |target_element|
|
49
|
+
next target_element if target_element.is_a? String
|
50
|
+
interpolate_argument(target_element, args)
|
51
|
+
end.join
|
52
|
+
end
|
53
|
+
|
54
|
+
def interpolate_argument(name, args)
|
55
|
+
matching_arg_index = /^arg(\d+)$/.match(name.to_s)
|
56
|
+
return name.to_s unless matching_arg_index
|
57
|
+
args[matching_arg_index[1].to_i - 1]
|
58
|
+
end
|
59
|
+
|
60
|
+
def remove_args_from_conf(conf)
|
61
|
+
conf.select { |key| ![:args].include?(key) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_or_get_conf(args)
|
65
|
+
new_conf = {
|
66
|
+
args: args,
|
67
|
+
exitcode: 0,
|
68
|
+
outputs: []
|
69
|
+
}
|
70
|
+
current_conf = @call_configuration.select { |conf| conf[:args] == args }
|
71
|
+
@call_configuration << new_conf if current_conf.empty?
|
72
|
+
current_conf.first || new_conf
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rspec
|
2
|
+
module Bash
|
3
|
+
class CallConfigurationManager
|
4
|
+
def initialize
|
5
|
+
@call_confs = Hash.new { |hash, key| hash[key] = CallConfiguration.new }
|
6
|
+
end
|
7
|
+
|
8
|
+
def set_exitcode(command, exitcode, args)
|
9
|
+
@call_confs[command]
|
10
|
+
.set_exitcode(exitcode, args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_output(command, output, target, args)
|
14
|
+
@call_confs[command]
|
15
|
+
.add_output(output, target, args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_best_call_conf(command, args)
|
19
|
+
@call_confs[command]
|
20
|
+
.get_best_call_conf(args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Rspec
|
2
|
+
module Bash
|
3
|
+
class CallLog
|
4
|
+
attr_accessor :call_log
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@call_log = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def stdin_for_args(argument_list)
|
11
|
+
call_argument_list_matcher = Util::CallLogArgumentListMatcher.new(argument_list)
|
12
|
+
matching_call_log_list = call_argument_list_matcher.get_call_log_matches(call_log)
|
13
|
+
matching_call_log_list.first[:stdin] unless matching_call_log_list.empty?
|
14
|
+
end
|
15
|
+
|
16
|
+
def call_count(argument_list)
|
17
|
+
call_argument_list_matcher = Util::CallLogArgumentListMatcher.new(argument_list)
|
18
|
+
call_argument_list_matcher.get_call_count(call_log)
|
19
|
+
end
|
20
|
+
|
21
|
+
def called_with_args?(argument_list)
|
22
|
+
call_argument_list_matcher = Util::CallLogArgumentListMatcher.new(argument_list)
|
23
|
+
call_argument_list_matcher.args_match?(call_log)
|
24
|
+
end
|
25
|
+
|
26
|
+
def called_with_no_args?
|
27
|
+
return false if @call_log.empty?
|
28
|
+
|
29
|
+
@call_log.all? do |call_log|
|
30
|
+
argument_list = call_log[:args] || []
|
31
|
+
argument_list.empty?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_log(stdin, argument_list)
|
36
|
+
updated_log = @call_log
|
37
|
+
updated_log << {
|
38
|
+
args: argument_list,
|
39
|
+
stdin: stdin
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def call_log_arguments
|
44
|
+
@call_log.map { |call_log| call_log[:args] || [] }.compact
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Rspec
|
2
|
+
module Bash
|
3
|
+
class CallLogManager
|
4
|
+
def initialize
|
5
|
+
@call_logs = Hash.new { |hash, key| hash[key] = CallLog.new }
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_log(command, stdin, arguments)
|
9
|
+
@call_logs[command]
|
10
|
+
.add_log(stdin, arguments)
|
11
|
+
end
|
12
|
+
|
13
|
+
def stdin_for_args(command, arguments)
|
14
|
+
@call_logs[command]
|
15
|
+
.stdin_for_args(arguments)
|
16
|
+
end
|
17
|
+
|
18
|
+
def call_count(command, arguments)
|
19
|
+
@call_logs[command]
|
20
|
+
.call_count(arguments)
|
21
|
+
end
|
22
|
+
|
23
|
+
def called_with_args?(command, arguments)
|
24
|
+
@call_logs[command]
|
25
|
+
.called_with_args?(arguments)
|
26
|
+
end
|
27
|
+
|
28
|
+
def called_with_no_args?(command)
|
29
|
+
@call_logs[command]
|
30
|
+
.called_with_no_args?
|
31
|
+
end
|
32
|
+
|
33
|
+
def call_log(command)
|
34
|
+
@call_logs[command]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|