rspec-core 3.0.4 → 3.12.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|