rspec-core 3.0.4 → 3.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/.document +1 -1
- data/.yardopts +2 -1
- data/Changelog.md +888 -2
- data/{License.txt → LICENSE.md} +6 -5
- data/README.md +165 -24
- data/lib/rspec/autorun.rb +1 -0
- data/lib/rspec/core/backtrace_formatter.rb +19 -20
- data/lib/rspec/core/bisect/coordinator.rb +62 -0
- data/lib/rspec/core/bisect/example_minimizer.rb +173 -0
- data/lib/rspec/core/bisect/fork_runner.rb +138 -0
- data/lib/rspec/core/bisect/server.rb +61 -0
- data/lib/rspec/core/bisect/shell_command.rb +126 -0
- data/lib/rspec/core/bisect/shell_runner.rb +73 -0
- data/lib/rspec/core/bisect/utilities.rb +69 -0
- data/lib/rspec/core/configuration.rb +1287 -246
- data/lib/rspec/core/configuration_options.rb +95 -35
- data/lib/rspec/core/did_you_mean.rb +46 -0
- data/lib/rspec/core/drb.rb +21 -12
- data/lib/rspec/core/dsl.rb +10 -6
- data/lib/rspec/core/example.rb +305 -113
- data/lib/rspec/core/example_group.rb +431 -223
- data/lib/rspec/core/example_status_persister.rb +235 -0
- data/lib/rspec/core/filter_manager.rb +86 -115
- data/lib/rspec/core/flat_map.rb +6 -4
- data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
- data/lib/rspec/core/formatters/base_formatter.rb +14 -116
- data/lib/rspec/core/formatters/base_text_formatter.rb +18 -21
- data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
- data/lib/rspec/core/formatters/bisect_progress_formatter.rb +157 -0
- data/lib/rspec/core/formatters/console_codes.rb +29 -18
- data/lib/rspec/core/formatters/deprecation_formatter.rb +16 -16
- data/lib/rspec/core/formatters/documentation_formatter.rb +49 -16
- data/lib/rspec/core/formatters/exception_presenter.rb +525 -0
- data/lib/rspec/core/formatters/failure_list_formatter.rb +23 -0
- data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
- data/lib/rspec/core/formatters/helpers.rb +45 -15
- data/lib/rspec/core/formatters/html_formatter.rb +33 -28
- data/lib/rspec/core/formatters/html_printer.rb +30 -20
- data/lib/rspec/core/formatters/html_snippet_extractor.rb +120 -0
- data/lib/rspec/core/formatters/json_formatter.rb +18 -9
- data/lib/rspec/core/formatters/profile_formatter.rb +10 -9
- data/lib/rspec/core/formatters/progress_formatter.rb +5 -4
- data/lib/rspec/core/formatters/protocol.rb +182 -0
- data/lib/rspec/core/formatters/snippet_extractor.rb +113 -82
- data/lib/rspec/core/formatters/syntax_highlighter.rb +91 -0
- data/lib/rspec/core/formatters.rb +81 -41
- data/lib/rspec/core/hooks.rb +314 -244
- data/lib/rspec/core/invocations.rb +87 -0
- data/lib/rspec/core/memoized_helpers.rb +161 -51
- data/lib/rspec/core/metadata.rb +132 -61
- data/lib/rspec/core/metadata_filter.rb +224 -64
- data/lib/rspec/core/minitest_assertions_adapter.rb +6 -3
- data/lib/rspec/core/mocking_adapters/flexmock.rb +4 -2
- data/lib/rspec/core/mocking_adapters/mocha.rb +11 -9
- data/lib/rspec/core/mocking_adapters/null.rb +2 -0
- data/lib/rspec/core/mocking_adapters/rr.rb +3 -1
- data/lib/rspec/core/mocking_adapters/rspec.rb +3 -1
- data/lib/rspec/core/notifications.rb +192 -206
- data/lib/rspec/core/option_parser.rb +174 -69
- data/lib/rspec/core/ordering.rb +48 -35
- data/lib/rspec/core/output_wrapper.rb +29 -0
- data/lib/rspec/core/pending.rb +25 -33
- data/lib/rspec/core/profiler.rb +34 -0
- data/lib/rspec/core/project_initializer/.rspec +0 -2
- data/lib/rspec/core/project_initializer/spec/spec_helper.rb +59 -39
- data/lib/rspec/core/project_initializer.rb +5 -3
- data/lib/rspec/core/rake_task.rb +99 -55
- data/lib/rspec/core/reporter.rb +128 -15
- data/lib/rspec/core/ruby_project.rb +14 -6
- data/lib/rspec/core/runner.rb +96 -45
- data/lib/rspec/core/sandbox.rb +37 -0
- data/lib/rspec/core/set.rb +54 -0
- data/lib/rspec/core/shared_example_group.rb +133 -43
- data/lib/rspec/core/shell_escape.rb +49 -0
- data/lib/rspec/core/test_unit_assertions_adapter.rb +4 -4
- data/lib/rspec/core/version.rb +1 -1
- data/lib/rspec/core/warnings.rb +6 -6
- data/lib/rspec/core/world.rb +172 -68
- data/lib/rspec/core.rb +66 -21
- data.tar.gz.sig +0 -0
- metadata +93 -69
- metadata.gz.sig +0 -0
- data/lib/rspec/core/backport_random.rb +0 -336
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
# This is borrowed (slightly modified) from Scott Taylor's
|
|
2
2
|
# project_path project:
|
|
3
3
|
# http://github.com/smtlaissezfaire/project_path
|
|
4
|
-
|
|
5
|
-
require 'pathname'
|
|
6
|
-
|
|
7
4
|
module RSpec
|
|
8
5
|
module Core
|
|
9
6
|
# @private
|
|
10
7
|
module RubyProject
|
|
11
8
|
def add_to_load_path(*dirs)
|
|
12
|
-
dirs.
|
|
9
|
+
dirs.each { |dir| add_dir_to_load_path(File.join(root, dir)) }
|
|
13
10
|
end
|
|
14
11
|
|
|
15
12
|
def add_dir_to_load_path(dir)
|
|
@@ -25,12 +22,23 @@ module RSpec
|
|
|
25
22
|
end
|
|
26
23
|
|
|
27
24
|
def find_first_parent_containing(dir)
|
|
28
|
-
ascend_until {|path| File.exist?(File.join(path, dir))}
|
|
25
|
+
ascend_until { |path| File.exist?(File.join(path, dir)) }
|
|
29
26
|
end
|
|
30
27
|
|
|
31
28
|
def ascend_until
|
|
32
|
-
|
|
29
|
+
fs = File::SEPARATOR
|
|
30
|
+
escaped_slash = "\\#{fs}"
|
|
31
|
+
special = "_RSPEC_ESCAPED_SLASH_"
|
|
32
|
+
project_path = File.expand_path(".")
|
|
33
|
+
parts = project_path.gsub(escaped_slash, special).squeeze(fs).split(fs).map do |x|
|
|
34
|
+
x.gsub(special, escaped_slash)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
until parts.empty?
|
|
38
|
+
path = parts.join(fs)
|
|
39
|
+
path = fs if path == ""
|
|
33
40
|
return path if yield(path)
|
|
41
|
+
parts.pop
|
|
34
42
|
end
|
|
35
43
|
end
|
|
36
44
|
|
data/lib/rspec/core/runner.rb
CHANGED
|
@@ -2,6 +2,9 @@ module RSpec
|
|
|
2
2
|
module Core
|
|
3
3
|
# Provides the main entry point to run a suite of RSpec examples.
|
|
4
4
|
class Runner
|
|
5
|
+
# @attr_reader
|
|
6
|
+
# @private
|
|
7
|
+
attr_reader :options, :configuration, :world
|
|
5
8
|
|
|
6
9
|
# Register an `at_exit` hook that runs the suite when the process exits.
|
|
7
10
|
#
|
|
@@ -18,21 +21,25 @@ module RSpec
|
|
|
18
21
|
return
|
|
19
22
|
end
|
|
20
23
|
|
|
21
|
-
at_exit
|
|
22
|
-
# Don't bother running any specs and just let the program terminate
|
|
23
|
-
# if we got here due to an unrescued exception (anything other than
|
|
24
|
-
# SystemExit, which is raised when somebody calls Kernel#exit).
|
|
25
|
-
next unless $!.nil? || $!.kind_of?(SystemExit)
|
|
26
|
-
|
|
27
|
-
# We got here because either the end of the program was reached or
|
|
28
|
-
# somebody called Kernel#exit. Run the specs and then override any
|
|
29
|
-
# existing exit status with RSpec's exit status if any specs failed.
|
|
30
|
-
invoke
|
|
31
|
-
end
|
|
24
|
+
at_exit { perform_at_exit }
|
|
32
25
|
@installed_at_exit = true
|
|
33
26
|
end
|
|
34
27
|
|
|
35
|
-
#
|
|
28
|
+
# @private
|
|
29
|
+
def self.perform_at_exit
|
|
30
|
+
# Don't bother running any specs and just let the program terminate
|
|
31
|
+
# if we got here due to an unrescued exception (anything other than
|
|
32
|
+
# SystemExit, which is raised when somebody calls Kernel#exit).
|
|
33
|
+
return unless $!.nil? || $!.is_a?(SystemExit)
|
|
34
|
+
|
|
35
|
+
# We got here because either the end of the program was reached or
|
|
36
|
+
# somebody called Kernel#exit. Run the specs and then override any
|
|
37
|
+
# existing exit status with RSpec's exit status if any specs failed.
|
|
38
|
+
invoke
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Runs the suite of specs and exits the process with an appropriate exit
|
|
42
|
+
# code.
|
|
36
43
|
def self.invoke
|
|
37
44
|
disable_autorun!
|
|
38
45
|
status = run(ARGV, $stderr, $stdout).to_i
|
|
@@ -58,14 +65,8 @@ module RSpec
|
|
|
58
65
|
trap_interrupt
|
|
59
66
|
options = ConfigurationOptions.new(args)
|
|
60
67
|
|
|
61
|
-
if options.options[:
|
|
62
|
-
|
|
63
|
-
begin
|
|
64
|
-
DRbRunner.new(options).run(err, out)
|
|
65
|
-
rescue DRb::DRbConnError
|
|
66
|
-
err.puts "No DRb server is running. Running in local process instead ..."
|
|
67
|
-
new(options).run(err, out)
|
|
68
|
-
end
|
|
68
|
+
if options.options[:runner]
|
|
69
|
+
options.options[:runner].call(options, err, out)
|
|
69
70
|
else
|
|
70
71
|
new(options).run(err, out)
|
|
71
72
|
end
|
|
@@ -83,7 +84,11 @@ module RSpec
|
|
|
83
84
|
# @param out [IO] output stream
|
|
84
85
|
def run(err, out)
|
|
85
86
|
setup(err, out)
|
|
86
|
-
|
|
87
|
+
return @configuration.reporter.exit_early(exit_code) if RSpec.world.wants_to_quit
|
|
88
|
+
|
|
89
|
+
run_specs(@world.ordered_example_groups).tap do
|
|
90
|
+
persist_example_statuses
|
|
91
|
+
end
|
|
87
92
|
end
|
|
88
93
|
|
|
89
94
|
# Wires together the various configuration objects and state holders.
|
|
@@ -91,10 +96,11 @@ module RSpec
|
|
|
91
96
|
# @param err [IO] error stream
|
|
92
97
|
# @param out [IO] output stream
|
|
93
98
|
def setup(err, out)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
configure(err, out)
|
|
100
|
+
return if RSpec.world.wants_to_quit
|
|
101
|
+
|
|
97
102
|
@configuration.load_spec_files
|
|
103
|
+
ensure
|
|
98
104
|
@world.announce_filters
|
|
99
105
|
end
|
|
100
106
|
|
|
@@ -105,15 +111,25 @@ module RSpec
|
|
|
105
111
|
# or the configured failure exit code (1 by default) if specs
|
|
106
112
|
# failed.
|
|
107
113
|
def run_specs(example_groups)
|
|
108
|
-
@
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
@configuration.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
examples_count = @world.example_count(example_groups)
|
|
115
|
+
examples_passed = @configuration.reporter.report(examples_count) do |reporter|
|
|
116
|
+
@configuration.with_suite_hooks do
|
|
117
|
+
if examples_count == 0 && @configuration.fail_if_no_examples
|
|
118
|
+
return @configuration.failure_exit_code
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
example_groups.map { |g| g.run(reporter) }.all?
|
|
115
122
|
end
|
|
116
123
|
end
|
|
124
|
+
|
|
125
|
+
exit_code(examples_passed)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @private
|
|
129
|
+
def configure(err, out)
|
|
130
|
+
@configuration.error_stream = err
|
|
131
|
+
@configuration.output_stream = out if @configuration.output_stream == $stdout
|
|
132
|
+
@options.configure(@configuration)
|
|
117
133
|
end
|
|
118
134
|
|
|
119
135
|
# @private
|
|
@@ -133,29 +149,64 @@ module RSpec
|
|
|
133
149
|
|
|
134
150
|
# @private
|
|
135
151
|
def self.running_in_drb?
|
|
136
|
-
|
|
137
|
-
if defined?(DRb) && DRb.current_server
|
|
138
|
-
require 'socket'
|
|
139
|
-
require 'uri'
|
|
152
|
+
return false unless defined?(DRb)
|
|
140
153
|
|
|
141
|
-
|
|
154
|
+
server = begin
|
|
155
|
+
DRb.current_server
|
|
156
|
+
rescue DRb::DRbServerNotFound
|
|
157
|
+
return false
|
|
158
|
+
end
|
|
142
159
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
160
|
+
return false unless server && server.alive?
|
|
161
|
+
|
|
162
|
+
require 'socket'
|
|
163
|
+
require 'uri'
|
|
164
|
+
|
|
165
|
+
local_ipv4 = begin
|
|
166
|
+
IPSocket.getaddress(Socket.gethostname)
|
|
167
|
+
rescue SocketError
|
|
168
|
+
return false
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
["127.0.0.1", "localhost", local_ipv4].any? { |addr| addr == URI(DRb.current_server.uri).host }
|
|
149
172
|
end
|
|
150
173
|
|
|
151
174
|
# @private
|
|
152
175
|
def self.trap_interrupt
|
|
153
|
-
trap('INT')
|
|
154
|
-
|
|
176
|
+
trap('INT') { handle_interrupt }
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# @private
|
|
180
|
+
def self.handle_interrupt
|
|
181
|
+
if RSpec.world.wants_to_quit
|
|
182
|
+
exit!(1)
|
|
183
|
+
else
|
|
155
184
|
RSpec.world.wants_to_quit = true
|
|
156
|
-
|
|
185
|
+
$stderr.puts "\nRSpec is shutting down and will print the summary report... Interrupt again to force quit."
|
|
157
186
|
end
|
|
158
187
|
end
|
|
188
|
+
|
|
189
|
+
# @private
|
|
190
|
+
def exit_code(examples_passed=false)
|
|
191
|
+
return @configuration.error_exit_code || @configuration.failure_exit_code if @world.non_example_failure
|
|
192
|
+
return @configuration.failure_exit_code unless examples_passed
|
|
193
|
+
|
|
194
|
+
0
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
def persist_example_statuses
|
|
200
|
+
return if @configuration.dry_run
|
|
201
|
+
return unless (path = @configuration.example_status_persistence_file_path)
|
|
202
|
+
|
|
203
|
+
ExampleStatusPersister.persist(@world.all_examples, path)
|
|
204
|
+
rescue SystemCallError => e
|
|
205
|
+
RSpec.warning "Could not write example statuses to #{path} (configured as " \
|
|
206
|
+
"`config.example_status_persistence_file_path`) due to a " \
|
|
207
|
+
"system error: #{e.inspect}. Please check that the config " \
|
|
208
|
+
"option is set to an accessible, valid file path", :call_site => nil
|
|
209
|
+
end
|
|
159
210
|
end
|
|
160
211
|
end
|
|
161
212
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module RSpec
|
|
2
|
+
module Core
|
|
3
|
+
# A sandbox isolates the enclosed code into an environment that looks 'new'
|
|
4
|
+
# meaning globally accessed objects are reset for the duration of the
|
|
5
|
+
# sandbox.
|
|
6
|
+
#
|
|
7
|
+
# @note This module is not normally available. You must require
|
|
8
|
+
# `rspec/core/sandbox` to load it.
|
|
9
|
+
module Sandbox
|
|
10
|
+
# Execute a provided block with RSpec global objects (configuration,
|
|
11
|
+
# world) reset. This is used to test RSpec with RSpec.
|
|
12
|
+
#
|
|
13
|
+
# When calling this the configuration is passed into the provided block.
|
|
14
|
+
# Use this to set custom configs for your sandboxed examples.
|
|
15
|
+
#
|
|
16
|
+
# ```
|
|
17
|
+
# Sandbox.sandboxed do |config|
|
|
18
|
+
# config.before(:context) { RSpec.current_example = nil }
|
|
19
|
+
# end
|
|
20
|
+
# ```
|
|
21
|
+
def self.sandboxed
|
|
22
|
+
orig_config = RSpec.configuration
|
|
23
|
+
orig_world = RSpec.world
|
|
24
|
+
orig_example = RSpec.current_example
|
|
25
|
+
|
|
26
|
+
RSpec.configuration = RSpec::Core::Configuration.new
|
|
27
|
+
RSpec.world = RSpec::Core::World.new(RSpec.configuration)
|
|
28
|
+
|
|
29
|
+
yield RSpec.configuration
|
|
30
|
+
ensure
|
|
31
|
+
RSpec.configuration = orig_config
|
|
32
|
+
RSpec.world = orig_world
|
|
33
|
+
RSpec.current_example = orig_example
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module RSpec
|
|
2
|
+
module Core
|
|
3
|
+
# @private
|
|
4
|
+
#
|
|
5
|
+
# We use this to replace `::Set` so we can have the advantage of
|
|
6
|
+
# constant time key lookups for unique arrays but without the
|
|
7
|
+
# potential to pollute a developers environment with an extra
|
|
8
|
+
# piece of the stdlib. This helps to prevent false positive
|
|
9
|
+
# builds.
|
|
10
|
+
#
|
|
11
|
+
class Set
|
|
12
|
+
include Enumerable
|
|
13
|
+
|
|
14
|
+
def initialize(array=[])
|
|
15
|
+
@values = {}
|
|
16
|
+
merge(array)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def empty?
|
|
20
|
+
@values.empty?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def <<(key)
|
|
24
|
+
@values[key] = true
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def delete(key)
|
|
29
|
+
@values.delete(key)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def each(&block)
|
|
33
|
+
@values.keys.each(&block)
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def include?(key)
|
|
38
|
+
@values.key?(key)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def merge(values)
|
|
42
|
+
values.each do |key|
|
|
43
|
+
@values[key] = true
|
|
44
|
+
end
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def clear
|
|
49
|
+
@values.clear
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -1,5 +1,46 @@
|
|
|
1
|
+
RSpec::Support.require_rspec_support "with_keywords_when_needed"
|
|
2
|
+
|
|
1
3
|
module RSpec
|
|
2
4
|
module Core
|
|
5
|
+
# Represents some functionality that is shared with multiple example groups.
|
|
6
|
+
# The functionality is defined by the provided block, which is lazily
|
|
7
|
+
# eval'd when the `SharedExampleGroupModule` instance is included in an example
|
|
8
|
+
# group.
|
|
9
|
+
class SharedExampleGroupModule < Module
|
|
10
|
+
# @private
|
|
11
|
+
attr_reader :definition
|
|
12
|
+
|
|
13
|
+
def initialize(description, definition, metadata)
|
|
14
|
+
@description = description
|
|
15
|
+
@definition = definition
|
|
16
|
+
@metadata = metadata
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Provides a human-readable representation of this module.
|
|
20
|
+
def inspect
|
|
21
|
+
"#<#{self.class.name} #{@description.inspect}>"
|
|
22
|
+
end
|
|
23
|
+
alias to_s inspect
|
|
24
|
+
|
|
25
|
+
# Ruby callback for when a module is included in another module is class.
|
|
26
|
+
# Our definition evaluates the shared group block in the context of the
|
|
27
|
+
# including example group.
|
|
28
|
+
def included(klass)
|
|
29
|
+
inclusion_line = klass.metadata[:location]
|
|
30
|
+
include_in klass, inclusion_line, [], nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @private
|
|
34
|
+
def include_in(klass, inclusion_line, args, customization_block)
|
|
35
|
+
klass.update_inherited_metadata(@metadata) unless @metadata.empty?
|
|
36
|
+
|
|
37
|
+
SharedExampleGroupInclusionStackFrame.with_frame(@description, inclusion_line) do
|
|
38
|
+
RSpec::Support::WithKeywordsWhenNeeded.class_exec(klass, *args, &@definition)
|
|
39
|
+
klass.class_exec(&customization_block) if customization_block
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
3
44
|
# Shared example groups let you define common context and/or common
|
|
4
45
|
# examples that you wish to use in multiple example groups.
|
|
5
46
|
#
|
|
@@ -15,16 +56,15 @@ module RSpec
|
|
|
15
56
|
# groups defined at the top level can be included from any example group.
|
|
16
57
|
module SharedExampleGroup
|
|
17
58
|
# @overload shared_examples(name, &block)
|
|
18
|
-
# @param name [String, Symbol, Module] identifer to use when looking up
|
|
59
|
+
# @param name [String, Symbol, Module] identifer to use when looking up
|
|
60
|
+
# this shared group
|
|
19
61
|
# @param block The block to be eval'd
|
|
20
62
|
# @overload shared_examples(name, metadata, &block)
|
|
21
|
-
# @param name [String, Symbol, Module] identifer to use when looking up
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# @param metadata [Array<Symbol>, Hash] metadata to attach to this group; any example group
|
|
27
|
-
# with matching metadata will automatically include this shared example group.
|
|
63
|
+
# @param name [String, Symbol, Module] identifer to use when looking up
|
|
64
|
+
# this shared group
|
|
65
|
+
# @param metadata [Array<Symbol>, Hash] metadata to attach to this
|
|
66
|
+
# group; any example group or example with matching metadata will
|
|
67
|
+
# automatically include this shared example group.
|
|
28
68
|
# @param block The block to be eval'd
|
|
29
69
|
#
|
|
30
70
|
# Stores the block for later use. The block will be evaluated
|
|
@@ -38,7 +78,7 @@ module RSpec
|
|
|
38
78
|
# end
|
|
39
79
|
# end
|
|
40
80
|
#
|
|
41
|
-
# describe Account do
|
|
81
|
+
# RSpec.describe Account do
|
|
42
82
|
# it_behaves_like "auditable" do
|
|
43
83
|
# let(:auditable) { Account.new }
|
|
44
84
|
# end
|
|
@@ -49,9 +89,9 @@ module RSpec
|
|
|
49
89
|
# @see ExampleGroup.include_context
|
|
50
90
|
def shared_examples(name, *args, &block)
|
|
51
91
|
top_level = self == ExampleGroup
|
|
52
|
-
if top_level && RSpec.
|
|
53
|
-
raise "Creating isolated shared examples from within a context is "
|
|
54
|
-
"not allowed. Remove `RSpec.` prefix or move this to a "
|
|
92
|
+
if top_level && RSpec::Support.thread_local_data[:in_example_group]
|
|
93
|
+
raise "Creating isolated shared examples from within a context is " \
|
|
94
|
+
"not allowed. Remove `RSpec.` prefix or move this to a " \
|
|
55
95
|
"top-level scope."
|
|
56
96
|
end
|
|
57
97
|
|
|
@@ -62,7 +102,7 @@ module RSpec
|
|
|
62
102
|
|
|
63
103
|
# @api private
|
|
64
104
|
#
|
|
65
|
-
# Shared examples top level DSL
|
|
105
|
+
# Shared examples top level DSL.
|
|
66
106
|
module TopLevelDSL
|
|
67
107
|
# @private
|
|
68
108
|
def self.definitions
|
|
@@ -82,7 +122,7 @@ module RSpec
|
|
|
82
122
|
|
|
83
123
|
# @api private
|
|
84
124
|
#
|
|
85
|
-
# Adds the top level DSL methods to Module and the top level binding
|
|
125
|
+
# Adds the top level DSL methods to Module and the top level binding.
|
|
86
126
|
def self.expose_globally!
|
|
87
127
|
return if exposed_globally?
|
|
88
128
|
Core::DSL.change_global_dsl(&definitions)
|
|
@@ -91,7 +131,7 @@ module RSpec
|
|
|
91
131
|
|
|
92
132
|
# @api private
|
|
93
133
|
#
|
|
94
|
-
# Removes the top level DSL methods to Module and the top level binding
|
|
134
|
+
# Removes the top level DSL methods to Module and the top level binding.
|
|
95
135
|
def self.remove_globally!
|
|
96
136
|
return unless exposed_globally?
|
|
97
137
|
|
|
@@ -103,28 +143,32 @@ module RSpec
|
|
|
103
143
|
|
|
104
144
|
@exposed_globally = false
|
|
105
145
|
end
|
|
106
|
-
|
|
107
146
|
end
|
|
108
147
|
|
|
109
148
|
# @private
|
|
110
149
|
class Registry
|
|
111
150
|
def add(context, name, *metadata_args, &block)
|
|
112
|
-
|
|
151
|
+
unless block
|
|
152
|
+
RSpec.warning "Shared example group #{name} was defined without a "\
|
|
153
|
+
"block and will have no effect. Please define a "\
|
|
154
|
+
"block or remove the definition."
|
|
155
|
+
end
|
|
113
156
|
|
|
114
|
-
if
|
|
115
|
-
|
|
116
|
-
shared_example_groups[context][name] = block
|
|
117
|
-
else
|
|
118
|
-
metadata_args.unshift name
|
|
157
|
+
if RSpec.configuration.shared_context_metadata_behavior == :trigger_inclusion
|
|
158
|
+
return legacy_add(context, name, *metadata_args, &block)
|
|
119
159
|
end
|
|
120
160
|
|
|
121
|
-
unless
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
host.class_exec(&block)
|
|
125
|
-
end
|
|
126
|
-
RSpec.configuration.include mod, *metadata_args
|
|
161
|
+
unless valid_name?(name)
|
|
162
|
+
raise ArgumentError, "Shared example group names can only be a string, " \
|
|
163
|
+
"symbol or module but got: #{name.inspect}"
|
|
127
164
|
end
|
|
165
|
+
|
|
166
|
+
ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line }
|
|
167
|
+
warn_if_key_taken context, name, block
|
|
168
|
+
|
|
169
|
+
metadata = Metadata.build_hash_from(metadata_args)
|
|
170
|
+
shared_module = SharedExampleGroupModule.new(name, block, metadata)
|
|
171
|
+
shared_example_groups[context][name] = shared_module
|
|
128
172
|
end
|
|
129
173
|
|
|
130
174
|
def find(lookup_contexts, name)
|
|
@@ -138,40 +182,86 @@ module RSpec
|
|
|
138
182
|
|
|
139
183
|
private
|
|
140
184
|
|
|
185
|
+
# TODO: remove this in RSpec 4. This exists only to support
|
|
186
|
+
# `config.shared_context_metadata_behavior == :trigger_inclusion`,
|
|
187
|
+
# the legacy behavior of shared context metadata, which we do
|
|
188
|
+
# not want to support in RSpec 4.
|
|
189
|
+
def legacy_add(context, name, *metadata_args, &block)
|
|
190
|
+
ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line }
|
|
191
|
+
shared_module = SharedExampleGroupModule.new(name, block, {})
|
|
192
|
+
|
|
193
|
+
if valid_name?(name)
|
|
194
|
+
warn_if_key_taken context, name, block
|
|
195
|
+
shared_example_groups[context][name] = shared_module
|
|
196
|
+
else
|
|
197
|
+
metadata_args.unshift name
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
return if metadata_args.empty?
|
|
201
|
+
RSpec.configuration.include shared_module, *metadata_args
|
|
202
|
+
end
|
|
203
|
+
|
|
141
204
|
def shared_example_groups
|
|
142
205
|
@shared_example_groups ||= Hash.new { |hash, context| hash[context] = {} }
|
|
143
206
|
end
|
|
144
207
|
|
|
145
208
|
def valid_name?(candidate)
|
|
146
209
|
case candidate
|
|
147
|
-
|
|
148
|
-
|
|
210
|
+
when String, Symbol, Module then true
|
|
211
|
+
else false
|
|
149
212
|
end
|
|
150
213
|
end
|
|
151
214
|
|
|
152
215
|
def warn_if_key_taken(context, key, new_block)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
216
|
+
existing_module = shared_example_groups[context][key]
|
|
217
|
+
return unless existing_module
|
|
218
|
+
|
|
219
|
+
old_definition_location = formatted_location existing_module.definition
|
|
220
|
+
new_definition_location = formatted_location new_block
|
|
221
|
+
loaded_spec_files = RSpec.configuration.loaded_spec_files
|
|
222
|
+
|
|
223
|
+
if loaded_spec_files.include?(new_definition_location) && old_definition_location == new_definition_location
|
|
224
|
+
RSpec.warn_with <<-WARNING.gsub(/^ +\|/, ''), :call_site => nil
|
|
225
|
+
|WARNING: Your shared example group, '#{key}', defined at:
|
|
226
|
+
| #{old_definition_location}
|
|
227
|
+
|was automatically loaded by RSpec because the file name
|
|
228
|
+
|matches the configured autoloading pattern (#{RSpec.configuration.pattern}),
|
|
229
|
+
|and is also being required from somewhere else. To fix this
|
|
230
|
+
|warning, either rename the file to not match the pattern, or
|
|
231
|
+
|do not explicitly require the file.
|
|
232
|
+
WARNING
|
|
233
|
+
else
|
|
234
|
+
RSpec.warn_with <<-WARNING.gsub(/^ +\|/, ''), :call_site => nil
|
|
235
|
+
|WARNING: Shared example group '#{key}' has been previously defined at:
|
|
236
|
+
| #{old_definition_location}
|
|
237
|
+
|...and you are now defining it at:
|
|
238
|
+
| #{new_definition_location}
|
|
239
|
+
|The new definition will overwrite the original one.
|
|
240
|
+
WARNING
|
|
241
|
+
end
|
|
162
242
|
end
|
|
163
243
|
|
|
164
|
-
|
|
165
|
-
block
|
|
244
|
+
if RUBY_VERSION.to_f >= 1.9
|
|
245
|
+
def formatted_location(block)
|
|
246
|
+
block.source_location.join(":")
|
|
247
|
+
end
|
|
248
|
+
else # 1.8.7
|
|
249
|
+
# :nocov:
|
|
250
|
+
def formatted_location(block)
|
|
251
|
+
block.source_location.join(":").gsub(/:in.*$/, '')
|
|
252
|
+
end
|
|
253
|
+
# :nocov:
|
|
166
254
|
end
|
|
167
255
|
|
|
168
256
|
if Proc.method_defined?(:source_location)
|
|
169
|
-
def ensure_block_has_source_location(
|
|
257
|
+
def ensure_block_has_source_location(_block); end
|
|
170
258
|
else # for 1.8.7
|
|
259
|
+
# :nocov:
|
|
171
260
|
def ensure_block_has_source_location(block)
|
|
172
261
|
source_location = yield.split(':')
|
|
173
|
-
block.extend
|
|
262
|
+
block.extend(Module.new { define_method(:source_location) { source_location } })
|
|
174
263
|
end
|
|
264
|
+
# :nocov:
|
|
175
265
|
end
|
|
176
266
|
end
|
|
177
267
|
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module RSpec
|
|
2
|
+
module Core
|
|
3
|
+
# @private
|
|
4
|
+
# Deals with the fact that `shellwords` only works on POSIX systems.
|
|
5
|
+
module ShellEscape
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def quote(argument)
|
|
9
|
+
"'#{argument.to_s.gsub("'", "\\\\'")}'"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
if RSpec::Support::OS.windows?
|
|
13
|
+
# :nocov:
|
|
14
|
+
alias escape quote
|
|
15
|
+
# :nocov:
|
|
16
|
+
else
|
|
17
|
+
require 'shellwords'
|
|
18
|
+
|
|
19
|
+
def escape(shell_command)
|
|
20
|
+
Shellwords.escape(shell_command.to_s)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Known shells that require quoting: zsh, csh, tcsh.
|
|
25
|
+
#
|
|
26
|
+
# Feel free to add other shells to this list that are known to
|
|
27
|
+
# allow `rspec ./some_spec.rb[1:1]` syntax without quoting the id.
|
|
28
|
+
#
|
|
29
|
+
# @private
|
|
30
|
+
SHELLS_ALLOWING_UNQUOTED_IDS = %w[ bash ksh fish ]
|
|
31
|
+
|
|
32
|
+
def conditionally_quote(id)
|
|
33
|
+
return id if shell_allows_unquoted_ids?
|
|
34
|
+
quote(id)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def shell_allows_unquoted_ids?
|
|
38
|
+
# Note: ENV['SHELL'] isn't necessarily the shell the user is currently running.
|
|
39
|
+
# According to http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html:
|
|
40
|
+
# "This variable shall represent a pathname of the user's preferred command language interpreter."
|
|
41
|
+
#
|
|
42
|
+
# It's the best we can easily do, though. We err on the side of safety (quoting
|
|
43
|
+
# the id when not actually needed) so it's not a big deal if the user is actually
|
|
44
|
+
# using a different shell.
|
|
45
|
+
SHELLS_ALLOWING_UNQUOTED_IDS.include?(ENV['SHELL'].to_s.split('/').last)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -16,14 +16,14 @@ module RSpec
|
|
|
16
16
|
# adding a shim for the new updates. Thus instead of checking on the
|
|
17
17
|
# RUBY_VERSION we need to check ancestors.
|
|
18
18
|
begin
|
|
19
|
-
# MiniTest is 4.x
|
|
20
|
-
# Minitest is 5.x
|
|
19
|
+
# MiniTest is 4.x.
|
|
20
|
+
# Minitest is 5.x.
|
|
21
21
|
if ancestors.include?(::Minitest::Assertions)
|
|
22
22
|
require 'rspec/core/minitest_assertions_adapter'
|
|
23
23
|
include ::RSpec::Core::MinitestAssertionsAdapter
|
|
24
24
|
end
|
|
25
|
-
rescue NameError
|
|
26
|
-
# No-op. Minitest 5.x was not loaded
|
|
25
|
+
rescue NameError
|
|
26
|
+
# No-op. Minitest 5.x was not loaded.
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
end
|