rspec-core 3.1.7 → 3.2.0
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.yardopts +1 -0
- data/Changelog.md +84 -0
- data/README.md +10 -1
- data/lib/rspec/core.rb +28 -8
- data/lib/rspec/core/backport_random.rb +12 -9
- data/lib/rspec/core/configuration.rb +350 -112
- data/lib/rspec/core/configuration_options.rb +14 -7
- data/lib/rspec/core/dsl.rb +7 -4
- data/lib/rspec/core/example.rb +86 -50
- data/lib/rspec/core/example_group.rb +247 -86
- data/lib/rspec/core/filter_manager.rb +38 -93
- data/lib/rspec/core/flat_map.rb +4 -4
- data/lib/rspec/core/formatters.rb +10 -6
- data/lib/rspec/core/formatters/base_formatter.rb +7 -4
- data/lib/rspec/core/formatters/base_text_formatter.rb +12 -12
- data/lib/rspec/core/formatters/console_codes.rb +8 -7
- data/lib/rspec/core/formatters/deprecation_formatter.rb +5 -3
- data/lib/rspec/core/formatters/documentation_formatter.rb +10 -4
- data/lib/rspec/core/formatters/helpers.rb +6 -4
- data/lib/rspec/core/formatters/html_formatter.rb +13 -8
- data/lib/rspec/core/formatters/html_printer.rb +26 -10
- data/lib/rspec/core/formatters/profile_formatter.rb +10 -7
- data/lib/rspec/core/formatters/protocol.rb +27 -18
- data/lib/rspec/core/formatters/snippet_extractor.rb +14 -7
- data/lib/rspec/core/hooks.rb +252 -211
- data/lib/rspec/core/memoized_helpers.rb +16 -16
- data/lib/rspec/core/metadata.rb +67 -28
- data/lib/rspec/core/metadata_filter.rb +151 -24
- data/lib/rspec/core/minitest_assertions_adapter.rb +5 -2
- data/lib/rspec/core/mocking_adapters/flexmock.rb +1 -1
- data/lib/rspec/core/mocking_adapters/mocha.rb +8 -8
- data/lib/rspec/core/notifications.rb +155 -94
- data/lib/rspec/core/option_parser.rb +16 -10
- data/lib/rspec/core/pending.rb +11 -9
- data/lib/rspec/core/project_initializer.rb +1 -1
- data/lib/rspec/core/project_initializer/spec/spec_helper.rb +10 -8
- data/lib/rspec/core/rake_task.rb +37 -52
- data/lib/rspec/core/reporter.rb +30 -7
- data/lib/rspec/core/ruby_project.rb +12 -4
- data/lib/rspec/core/runner.rb +5 -8
- data/lib/rspec/core/sandbox.rb +37 -0
- data/lib/rspec/core/shared_example_group.rb +41 -15
- data/lib/rspec/core/test_unit_assertions_adapter.rb +3 -3
- data/lib/rspec/core/version.rb +1 -1
- data/lib/rspec/core/warnings.rb +2 -2
- data/lib/rspec/core/world.rb +12 -28
- metadata +44 -31
- metadata.gz.sig +0 -0
@@ -84,15 +84,22 @@ module RSpec
|
|
84
84
|
# set before it.
|
85
85
|
:default_path,
|
86
86
|
|
87
|
-
# These must be set before `requires` to support checking
|
88
|
-
# from within `spec_helper.rb` when a
|
87
|
+
# These must be set before `requires` to support checking
|
88
|
+
# `config.files_to_run` from within `spec_helper.rb` when a
|
89
|
+
# `-rspec_helper` option is used.
|
89
90
|
:files_or_directories_to_run, :pattern, :exclude_pattern,
|
90
91
|
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
|
95
|
-
|
92
|
+
# Necessary so that the `--seed` option is applied before requires,
|
93
|
+
# in case required files do something with the provided seed.
|
94
|
+
# (such as seed global randomization with it).
|
95
|
+
:order,
|
96
|
+
|
97
|
+
# In general, we want to require the specified files as early as
|
98
|
+
# possible. The `--require` option is specifically intended to allow
|
99
|
+
# early requires. For later requires, they can just put the require in
|
100
|
+
# their spec files, but `--require` provides a unique opportunity for
|
101
|
+
# users to instruct RSpec to load an extension file early for maximum
|
102
|
+
# flexibility.
|
96
103
|
:requires
|
97
104
|
]
|
98
105
|
|
data/lib/rspec/core/dsl.rb
CHANGED
@@ -8,7 +8,7 @@ module RSpec
|
|
8
8
|
# By default the methods `describe`, `context` and `example_group`
|
9
9
|
# are exposed. These methods define a named context for one or
|
10
10
|
# more examples. The given block is evaluated in the context of
|
11
|
-
# a generated subclass of {RSpec::Core::ExampleGroup}
|
11
|
+
# a generated subclass of {RSpec::Core::ExampleGroup}.
|
12
12
|
#
|
13
13
|
# ## Examples:
|
14
14
|
#
|
@@ -35,6 +35,8 @@ module RSpec
|
|
35
35
|
|
36
36
|
# @private
|
37
37
|
def self.expose_example_group_alias(name)
|
38
|
+
return if example_group_aliases.include?(name)
|
39
|
+
|
38
40
|
example_group_aliases << name
|
39
41
|
|
40
42
|
(class << RSpec; self; end).__send__(:define_method, name) do |*args, &example_group_block|
|
@@ -49,7 +51,7 @@ module RSpec
|
|
49
51
|
attr_accessor :top_level
|
50
52
|
end
|
51
53
|
|
52
|
-
# Adds the describe method to Module and the top level binding
|
54
|
+
# Adds the describe method to Module and the top level binding.
|
53
55
|
# @api private
|
54
56
|
def self.expose_globally!
|
55
57
|
return if exposed_globally?
|
@@ -61,7 +63,7 @@ module RSpec
|
|
61
63
|
@exposed_globally = true
|
62
64
|
end
|
63
65
|
|
64
|
-
# Removes the describe method from Module and the top level binding
|
66
|
+
# Removes the describe method from Module and the top level binding.
|
65
67
|
# @api private
|
66
68
|
def self.remove_globally!
|
67
69
|
return unless exposed_globally?
|
@@ -76,6 +78,7 @@ module RSpec
|
|
76
78
|
# @private
|
77
79
|
def self.expose_example_group_alias_globally(method_name)
|
78
80
|
change_global_dsl do
|
81
|
+
remove_method(method_name) if method_defined?(method_name)
|
79
82
|
define_method(method_name) { |*a, &b| ::RSpec.__send__(method_name, *a, &b) }
|
80
83
|
end
|
81
84
|
end
|
@@ -89,5 +92,5 @@ module RSpec
|
|
89
92
|
end
|
90
93
|
end
|
91
94
|
|
92
|
-
#
|
95
|
+
# Capture main without an eval.
|
93
96
|
::RSpec::Core::DSL.top_level = self
|
data/lib/rspec/core/example.rb
CHANGED
@@ -44,14 +44,15 @@ module RSpec
|
|
44
44
|
class Example
|
45
45
|
# @private
|
46
46
|
#
|
47
|
-
# Used to define methods that delegate to this example's metadata
|
47
|
+
# Used to define methods that delegate to this example's metadata.
|
48
48
|
def self.delegate_to_metadata(key)
|
49
49
|
define_method(key) { @metadata[key] }
|
50
50
|
end
|
51
51
|
|
52
52
|
# @return [ExecutionResult] represents the result of running this example.
|
53
53
|
delegate_to_metadata :execution_result
|
54
|
-
# @return [String] the relative path to the file where this example was
|
54
|
+
# @return [String] the relative path to the file where this example was
|
55
|
+
# defined.
|
55
56
|
delegate_to_metadata :file_path
|
56
57
|
# @return [String] the full description (including the docstrings of
|
57
58
|
# all parent example groups).
|
@@ -59,22 +60,22 @@ module RSpec
|
|
59
60
|
# @return [String] the exact source location of this example in a form
|
60
61
|
# like `./path/to/spec.rb:17`
|
61
62
|
delegate_to_metadata :location
|
62
|
-
# @return [Boolean] flag that indicates that the example is not expected
|
63
|
-
# It will be run and will either have a pending result (if a
|
64
|
-
# or a failed result (if no failure occurs).
|
63
|
+
# @return [Boolean] flag that indicates that the example is not expected
|
64
|
+
# to pass. It will be run and will either have a pending result (if a
|
65
|
+
# failure occurs) or a failed result (if no failure occurs).
|
65
66
|
delegate_to_metadata :pending
|
66
67
|
# @return [Boolean] flag that will cause the example to not run.
|
67
68
|
# The {ExecutionResult} status will be `:pending`.
|
68
69
|
delegate_to_metadata :skip
|
69
70
|
|
70
71
|
# Returns the string submitted to `example` or its aliases (e.g.
|
71
|
-
# `specify`, `it`, etc).
|
72
|
-
# do_something }`) it returns the message generated
|
73
|
-
# there is one, otherwise returns a message including
|
74
|
-
# example.
|
72
|
+
# `specify`, `it`, etc). If no string is submitted (e.g.
|
73
|
+
# `it { is_expected.to do_something }`) it returns the message generated
|
74
|
+
# by the matcher if there is one, otherwise returns a message including
|
75
|
+
# the location of the example.
|
75
76
|
def description
|
76
77
|
description = if metadata[:description].to_s.empty?
|
77
|
-
|
78
|
+
location_description
|
78
79
|
else
|
79
80
|
metadata[:description]
|
80
81
|
end
|
@@ -82,10 +83,28 @@ module RSpec
|
|
82
83
|
RSpec.configuration.format_docstrings_block.call(description)
|
83
84
|
end
|
84
85
|
|
86
|
+
# Returns a description of the example that always includes the location.
|
87
|
+
def inspect_output
|
88
|
+
inspect_output = "\"#{description}\""
|
89
|
+
unless metadata[:description].to_s.empty?
|
90
|
+
inspect_output << " (#{location})"
|
91
|
+
end
|
92
|
+
inspect_output
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the argument that can be passed to the `rspec` command to rerun this example.
|
96
|
+
def rerun_argument
|
97
|
+
loaded_spec_files = RSpec.configuration.loaded_spec_files
|
98
|
+
|
99
|
+
Metadata.ascending(metadata) do |meta|
|
100
|
+
return meta[:location] if loaded_spec_files.include?(meta[:absolute_file_path])
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
85
104
|
# @attr_reader
|
86
105
|
#
|
87
106
|
# Returns the first exception raised in the context of running this
|
88
|
-
# example (nil if no exception is raised)
|
107
|
+
# example (nil if no exception is raised).
|
89
108
|
attr_reader :exception
|
90
109
|
|
91
110
|
# @attr_reader
|
@@ -105,10 +124,14 @@ module RSpec
|
|
105
124
|
attr_accessor :clock
|
106
125
|
|
107
126
|
# Creates a new instance of Example.
|
108
|
-
# @param example_group_class [Class] the subclass of ExampleGroup in which
|
109
|
-
#
|
110
|
-
# @param
|
111
|
-
#
|
127
|
+
# @param example_group_class [Class] the subclass of ExampleGroup in which
|
128
|
+
# this Example is declared
|
129
|
+
# @param description [String] the String passed to the `it` method (or
|
130
|
+
# alias)
|
131
|
+
# @param user_metadata [Hash] additional args passed to `it` to be used as
|
132
|
+
# metadata
|
133
|
+
# @param example_block [Proc] the block of code that represents the
|
134
|
+
# example
|
112
135
|
# @api private
|
113
136
|
def initialize(example_group_class, description, user_metadata, example_block=nil)
|
114
137
|
@example_group_class = example_group_class
|
@@ -137,6 +160,8 @@ module RSpec
|
|
137
160
|
# @param example_group_instance the instance of an ExampleGroup subclass
|
138
161
|
def run(example_group_instance, reporter)
|
139
162
|
@example_group_instance = example_group_instance
|
163
|
+
hooks.register_global_singleton_context_hooks(self, RSpec.configuration.hooks)
|
164
|
+
RSpec.configuration.configure_example(self)
|
140
165
|
RSpec.current_example = self
|
141
166
|
|
142
167
|
start(reporter)
|
@@ -146,7 +171,7 @@ module RSpec
|
|
146
171
|
if skipped?
|
147
172
|
Pending.mark_pending! self, skip
|
148
173
|
elsif !RSpec.configuration.dry_run?
|
149
|
-
|
174
|
+
with_around_and_singleton_context_hooks do
|
150
175
|
begin
|
151
176
|
run_before_example
|
152
177
|
@example_group_instance.instance_exec(self, &@example_block)
|
@@ -171,7 +196,7 @@ module RSpec
|
|
171
196
|
rescue Exception => e
|
172
197
|
set_exception(e)
|
173
198
|
ensure
|
174
|
-
@example_group_instance
|
199
|
+
ExampleGroup.each_instance_variable_for_example(@example_group_instance) do |ivar|
|
175
200
|
@example_group_instance.instance_variable_set(ivar, nil)
|
176
201
|
end
|
177
202
|
@example_group_instance = nil
|
@@ -194,7 +219,7 @@ module RSpec
|
|
194
219
|
# if ex.metadata[:key] == :some_value && some_global_condition
|
195
220
|
# raise "some message"
|
196
221
|
# end
|
197
|
-
# ex.run # run delegates to ex.call
|
222
|
+
# ex.run # run delegates to ex.call.
|
198
223
|
# end
|
199
224
|
# end
|
200
225
|
#
|
@@ -223,7 +248,8 @@ module RSpec
|
|
223
248
|
end
|
224
249
|
alias run call
|
225
250
|
|
226
|
-
# Provides a wrapped proc that will update our `executed?` state when
|
251
|
+
# Provides a wrapped proc that will update our `executed?` state when
|
252
|
+
# executed.
|
227
253
|
def to_proc
|
228
254
|
method(:call).to_proc
|
229
255
|
end
|
@@ -250,21 +276,6 @@ module RSpec
|
|
250
276
|
end
|
251
277
|
end
|
252
278
|
|
253
|
-
# @private
|
254
|
-
def any_apply?(filters)
|
255
|
-
MetadataFilter.any_apply?(filters, metadata)
|
256
|
-
end
|
257
|
-
|
258
|
-
# @private
|
259
|
-
def all_apply?(filters)
|
260
|
-
MetadataFilter.all_apply?(filters, metadata) || @example_group_class.all_apply?(filters)
|
261
|
-
end
|
262
|
-
|
263
|
-
# @private
|
264
|
-
def around_example_hooks
|
265
|
-
@around_example_hooks ||= example_group.hooks.around_example_hooks_for(self)
|
266
|
-
end
|
267
|
-
|
268
279
|
# @private
|
269
280
|
#
|
270
281
|
# Used internally to set an exception in an after hook, which
|
@@ -303,7 +314,7 @@ module RSpec
|
|
303
314
|
# @private
|
304
315
|
#
|
305
316
|
# Used internally to skip without actually executing the example when
|
306
|
-
# skip is used in before(:context)
|
317
|
+
# skip is used in before(:context).
|
307
318
|
def skip_with_exception(reporter, exception)
|
308
319
|
start(reporter)
|
309
320
|
Pending.mark_skipped! self, exception.argument
|
@@ -324,12 +335,12 @@ module RSpec
|
|
324
335
|
|
325
336
|
private
|
326
337
|
|
327
|
-
def
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
338
|
+
def hooks
|
339
|
+
example_group_instance.singleton_class.hooks
|
340
|
+
end
|
341
|
+
|
342
|
+
def with_around_example_hooks
|
343
|
+
hooks.run(:around, :example, self) { yield }
|
333
344
|
rescue Exception => e
|
334
345
|
set_exception(e, "in an `around(:example)` hook")
|
335
346
|
end
|
@@ -365,15 +376,21 @@ module RSpec
|
|
365
376
|
|
366
377
|
def run_before_example
|
367
378
|
@example_group_instance.setup_mocks_for_rspec
|
368
|
-
|
379
|
+
hooks.run(:before, :example, self)
|
380
|
+
end
|
381
|
+
|
382
|
+
def with_around_and_singleton_context_hooks
|
383
|
+
singleton_context_hooks_host = example_group_instance.singleton_class
|
384
|
+
singleton_context_hooks_host.run_before_context_hooks(example_group_instance)
|
385
|
+
with_around_example_hooks { yield }
|
386
|
+
ensure
|
387
|
+
singleton_context_hooks_host.run_after_context_hooks(example_group_instance)
|
369
388
|
end
|
370
389
|
|
371
390
|
def run_after_example
|
372
|
-
|
391
|
+
assign_generated_description if defined?(::RSpec::Matchers)
|
392
|
+
hooks.run(:after, :example, self)
|
373
393
|
verify_mocks
|
374
|
-
assign_generated_description if RSpec.configuration.expecting_with_rspec?
|
375
|
-
rescue Exception => e
|
376
|
-
set_exception(e, "in an `after(:example)` hook")
|
377
394
|
ensure
|
378
395
|
@example_group_instance.teardown_mocks_for_rspec
|
379
396
|
end
|
@@ -383,6 +400,7 @@ module RSpec
|
|
383
400
|
rescue Exception => e
|
384
401
|
if pending?
|
385
402
|
execution_result.pending_fixed = false
|
403
|
+
execution_result.pending_exception = e
|
386
404
|
@exception = nil
|
387
405
|
else
|
388
406
|
set_exception(e)
|
@@ -394,16 +412,25 @@ module RSpec
|
|
394
412
|
end
|
395
413
|
|
396
414
|
def assign_generated_description
|
397
|
-
if metadata[:description].empty? && (description =
|
415
|
+
if metadata[:description].empty? && (description = generate_description)
|
398
416
|
metadata[:description] = description
|
399
417
|
metadata[:full_description] << description
|
400
418
|
end
|
401
|
-
rescue Exception => e
|
402
|
-
set_exception(e, "while assigning the example description")
|
403
419
|
ensure
|
404
420
|
RSpec::Matchers.clear_generated_description
|
405
421
|
end
|
406
422
|
|
423
|
+
def generate_description
|
424
|
+
RSpec::Matchers.generated_description
|
425
|
+
rescue Exception => e
|
426
|
+
location_description + " (Got an error when generating description " \
|
427
|
+
"from matcher: #{e.class}: #{e.message} -- #{e.backtrace.first})"
|
428
|
+
end
|
429
|
+
|
430
|
+
def location_description
|
431
|
+
"example at #{location}"
|
432
|
+
end
|
433
|
+
|
407
434
|
def skip_message
|
408
435
|
if String === skip
|
409
436
|
skip
|
@@ -448,6 +475,15 @@ module RSpec
|
|
448
475
|
|
449
476
|
alias pending_fixed? pending_fixed
|
450
477
|
|
478
|
+
# @return [Boolean] Indicates if the example was completely skipped
|
479
|
+
# (typically done via `:skip` metadata or the `skip` method). Skipped examples
|
480
|
+
# will have a `:pending` result. A `:pending` result can also come from examples
|
481
|
+
# that were marked as `:pending`, which causes them to be run, and produces a
|
482
|
+
# `:failed` result if the example passes.
|
483
|
+
def example_skipped?
|
484
|
+
status == :pending && !pending_exception
|
485
|
+
end
|
486
|
+
|
451
487
|
# @api private
|
452
488
|
# Records the finished status of the example.
|
453
489
|
def record_finished(status, finished_at)
|
@@ -493,7 +529,7 @@ module RSpec
|
|
493
529
|
super(AnonymousExampleGroup, "", {})
|
494
530
|
end
|
495
531
|
|
496
|
-
# To ensure we don't silence errors
|
532
|
+
# To ensure we don't silence errors.
|
497
533
|
def set_exception(exception, _context=nil)
|
498
534
|
raise exception
|
499
535
|
end
|
@@ -3,7 +3,7 @@ RSpec::Support.require_rspec_support 'recursive_const_methods'
|
|
3
3
|
module RSpec
|
4
4
|
module Core
|
5
5
|
# ExampleGroup and {Example} are the main structural elements of
|
6
|
-
# rspec-core.
|
6
|
+
# rspec-core. Consider this example:
|
7
7
|
#
|
8
8
|
# describe Thing do
|
9
9
|
# it "does something" do
|
@@ -17,13 +17,13 @@ module RSpec
|
|
17
17
|
#
|
18
18
|
# Example group bodies (e.g. `describe` or `context` blocks) are evaluated
|
19
19
|
# in the context of a new subclass of ExampleGroup. Individual examples are
|
20
|
-
# evaluated in the context of an instance of the specific ExampleGroup
|
21
|
-
# to which they belong.
|
20
|
+
# evaluated in the context of an instance of the specific ExampleGroup
|
21
|
+
# subclass to which they belong.
|
22
22
|
#
|
23
23
|
# Besides the class methods defined here, there are other interesting macros
|
24
|
-
# defined in {Hooks}, {MemoizedHelpers::ClassMethods} and
|
25
|
-
# There are additional instance methods available to
|
26
|
-
# {MemoizedHelpers} and {Pending}.
|
24
|
+
# defined in {Hooks}, {MemoizedHelpers::ClassMethods} and
|
25
|
+
# {SharedExampleGroup}. There are additional instance methods available to
|
26
|
+
# your examples defined in {MemoizedHelpers} and {Pending}.
|
27
27
|
class ExampleGroup
|
28
28
|
extend Hooks
|
29
29
|
|
@@ -32,10 +32,11 @@ module RSpec
|
|
32
32
|
include Pending
|
33
33
|
extend SharedExampleGroup
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
(
|
35
|
+
# @private
|
36
|
+
def self.idempotently_define_singleton_method(name, &definition)
|
37
|
+
(class << self; self; end).module_exec do
|
38
|
+
remove_method(name) if method_defined?(name)
|
39
|
+
define_method(name, &definition)
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
@@ -44,7 +45,21 @@ module RSpec
|
|
44
45
|
# The [Metadata](Metadata) object associated with this group.
|
45
46
|
# @see Metadata
|
46
47
|
def self.metadata
|
47
|
-
@metadata
|
48
|
+
@metadata ||= nil
|
49
|
+
end
|
50
|
+
|
51
|
+
# Temporarily replace the provided metadata.
|
52
|
+
# Intended primarily to allow an example group's singleton class
|
53
|
+
# to return the metadata of the example that it exists for. This
|
54
|
+
# is necessary for shared example group inclusion to work properly
|
55
|
+
# with singleton example groups.
|
56
|
+
# @private
|
57
|
+
def self.with_replaced_metadata(meta)
|
58
|
+
orig_metadata = metadata
|
59
|
+
@metadata = meta
|
60
|
+
yield
|
61
|
+
ensure
|
62
|
+
@metadata = orig_metadata
|
48
63
|
end
|
49
64
|
|
50
65
|
# @private
|
@@ -56,7 +71,7 @@ module RSpec
|
|
56
71
|
# @private
|
57
72
|
def self.delegate_to_metadata(*names)
|
58
73
|
names.each do |name|
|
59
|
-
|
74
|
+
idempotently_define_singleton_method(name) { metadata.fetch(name) }
|
60
75
|
end
|
61
76
|
end
|
62
77
|
|
@@ -77,7 +92,6 @@ module RSpec
|
|
77
92
|
# end
|
78
93
|
# end
|
79
94
|
#
|
80
|
-
#
|
81
95
|
def described_class
|
82
96
|
self.class.described_class
|
83
97
|
end
|
@@ -89,9 +103,20 @@ module RSpec
|
|
89
103
|
# @private
|
90
104
|
# @macro [attach] define_example_method
|
91
105
|
# @!scope class
|
92
|
-
# @
|
93
|
-
# @
|
94
|
-
#
|
106
|
+
# @overload $1
|
107
|
+
# @overload $1(&example_implementation)
|
108
|
+
# @param example_implementation [Block] The implementation of the example.
|
109
|
+
# @overload $1(doc_string, *metadata_keys, metadata={})
|
110
|
+
# @param doc_string [String] The example's doc string.
|
111
|
+
# @param metadata [Hash] Metadata for the example.
|
112
|
+
# @param metadata_keys [Array<Symbol>] Metadata tags for the example.
|
113
|
+
# Will be transformed into hash entries with `true` values.
|
114
|
+
# @overload $1(doc_string, *metadata_keys, metadata={}, &example_implementation)
|
115
|
+
# @param doc_string [String] The example's doc string.
|
116
|
+
# @param metadata [Hash] Metadata for the example.
|
117
|
+
# @param metadata_keys [Array<Symbol>] Metadata tags for the example.
|
118
|
+
# Will be transformed into hash entries with `true` values.
|
119
|
+
# @param example_implementation [Block] The implementation of the example.
|
95
120
|
# @yield [Example] the example object
|
96
121
|
# @example
|
97
122
|
# $1 do
|
@@ -100,6 +125,9 @@ module RSpec
|
|
100
125
|
# $1 "does something" do
|
101
126
|
# end
|
102
127
|
#
|
128
|
+
# $1 "does something", :slow, :uses_js do
|
129
|
+
# end
|
130
|
+
#
|
103
131
|
# $1 "does something", :with => 'additional metadata' do
|
104
132
|
# end
|
105
133
|
#
|
@@ -107,7 +135,7 @@ module RSpec
|
|
107
135
|
# # ex is the Example object that contains metadata about the example
|
108
136
|
# end
|
109
137
|
def self.define_example_method(name, extra_options={})
|
110
|
-
|
138
|
+
idempotently_define_singleton_method(name) do |*all_args, &block|
|
111
139
|
desc, *args = *all_args
|
112
140
|
|
113
141
|
options = Metadata.build_hash_from(args)
|
@@ -134,25 +162,25 @@ module RSpec
|
|
134
162
|
# end
|
135
163
|
define_example_method :specify
|
136
164
|
|
137
|
-
# Shortcut to define an example with `:focus => true
|
165
|
+
# Shortcut to define an example with `:focus => true`.
|
138
166
|
# @see example
|
139
167
|
define_example_method :focus, :focus => true
|
140
|
-
# Shortcut to define an example with `:focus => true
|
168
|
+
# Shortcut to define an example with `:focus => true`.
|
141
169
|
# @see example
|
142
170
|
define_example_method :fexample, :focus => true
|
143
|
-
# Shortcut to define an example with `:focus => true
|
171
|
+
# Shortcut to define an example with `:focus => true`.
|
144
172
|
# @see example
|
145
173
|
define_example_method :fit, :focus => true
|
146
|
-
# Shortcut to define an example with `:focus => true
|
174
|
+
# Shortcut to define an example with `:focus => true`.
|
147
175
|
# @see example
|
148
176
|
define_example_method :fspecify, :focus => true
|
149
|
-
# Shortcut to define an example with `:skip => 'Temporarily skipped with xexample'
|
177
|
+
# Shortcut to define an example with `:skip => 'Temporarily skipped with xexample'`.
|
150
178
|
# @see example
|
151
179
|
define_example_method :xexample, :skip => 'Temporarily skipped with xexample'
|
152
|
-
# Shortcut to define an example with `:skip => 'Temporarily skipped with xit'
|
180
|
+
# Shortcut to define an example with `:skip => 'Temporarily skipped with xit'`.
|
153
181
|
# @see example
|
154
182
|
define_example_method :xit, :skip => 'Temporarily skipped with xit'
|
155
|
-
# Shortcut to define an example with `:skip => 'Temporarily skipped with xspecify'
|
183
|
+
# Shortcut to define an example with `:skip => 'Temporarily skipped with xspecify'`.
|
156
184
|
# @see example
|
157
185
|
define_example_method :xspecify, :skip => 'Temporarily skipped with xspecify'
|
158
186
|
# Shortcut to define an example with `:skip => true`
|
@@ -167,11 +195,17 @@ module RSpec
|
|
167
195
|
# @!group Defining Example Groups
|
168
196
|
|
169
197
|
# @private
|
170
|
-
# @macro [attach]
|
198
|
+
# @macro [attach] define_example_group_method
|
171
199
|
# @!scope class
|
172
|
-
# @
|
173
|
-
# @
|
174
|
-
#
|
200
|
+
# @overload $1
|
201
|
+
# @overload $1(&example_group_definition)
|
202
|
+
# @param example_group_definition [Block] The definition of the example group.
|
203
|
+
# @overload $1(doc_string, *metadata_keys, metadata={}, &example_implementation)
|
204
|
+
# @param doc_string [String] The group's doc string.
|
205
|
+
# @param metadata [Hash] Metadata for the group.
|
206
|
+
# @param metadata_keys [Array<Symbol>] Metadata tags for the group.
|
207
|
+
# Will be transformed into hash entries with `true` values.
|
208
|
+
# @param example_group_definition [Block] The definition of the example group.
|
175
209
|
#
|
176
210
|
# Generates a subclass of this example group which inherits
|
177
211
|
# everything except the examples themselves.
|
@@ -195,7 +229,7 @@ module RSpec
|
|
195
229
|
#
|
196
230
|
# @see DSL#describe
|
197
231
|
def self.define_example_group_method(name, metadata={})
|
198
|
-
|
232
|
+
idempotently_define_singleton_method(name) do |*args, &example_group_block|
|
199
233
|
thread_data = RSpec.thread_local_metadata
|
200
234
|
top_level = self == ExampleGroup
|
201
235
|
|
@@ -230,8 +264,8 @@ module RSpec
|
|
230
264
|
|
231
265
|
define_example_group_method :example_group
|
232
266
|
|
233
|
-
# An alias of `example_group`. Generally used when grouping
|
234
|
-
#
|
267
|
+
# An alias of `example_group`. Generally used when grouping examples by a
|
268
|
+
# thing you are describing (e.g. an object, class or method).
|
235
269
|
# @see example_group
|
236
270
|
define_example_group_method :describe
|
237
271
|
|
@@ -266,12 +300,12 @@ module RSpec
|
|
266
300
|
#
|
267
301
|
# @see SharedExampleGroup
|
268
302
|
def self.define_nested_shared_group_method(new_name, report_label="it should behave like")
|
269
|
-
|
270
|
-
# Pass :caller so the :location metadata is set properly
|
271
|
-
#
|
303
|
+
idempotently_define_singleton_method(new_name) do |name, *args, &customization_block|
|
304
|
+
# Pass :caller so the :location metadata is set properly.
|
305
|
+
# Otherwise, it'll be set to the next line because that's
|
272
306
|
# the block's source_location.
|
273
|
-
group = example_group("#{report_label} #{name}", :caller => caller) do
|
274
|
-
find_and_eval_shared("examples", name, *args, &customization_block)
|
307
|
+
group = example_group("#{report_label} #{name}", :caller => (the_caller = caller)) do
|
308
|
+
find_and_eval_shared("examples", name, the_caller.first, *args, &customization_block)
|
275
309
|
end
|
276
310
|
group.metadata[:shared_group_name] = name
|
277
311
|
group
|
@@ -287,32 +321,36 @@ module RSpec
|
|
287
321
|
|
288
322
|
# Includes shared content mapped to `name` directly in the group in which
|
289
323
|
# it is declared, as opposed to `it_behaves_like`, which creates a nested
|
290
|
-
# group. If given a block, that block is also eval'd in the current
|
324
|
+
# group. If given a block, that block is also eval'd in the current
|
325
|
+
# context.
|
291
326
|
#
|
292
327
|
# @see SharedExampleGroup
|
293
328
|
def self.include_context(name, *args, &block)
|
294
|
-
find_and_eval_shared("context", name, *args, &block)
|
329
|
+
find_and_eval_shared("context", name, caller.first, *args, &block)
|
295
330
|
end
|
296
331
|
|
297
332
|
# Includes shared content mapped to `name` directly in the group in which
|
298
333
|
# it is declared, as opposed to `it_behaves_like`, which creates a nested
|
299
|
-
# group. If given a block, that block is also eval'd in the current
|
334
|
+
# group. If given a block, that block is also eval'd in the current
|
335
|
+
# context.
|
300
336
|
#
|
301
337
|
# @see SharedExampleGroup
|
302
338
|
def self.include_examples(name, *args, &block)
|
303
|
-
find_and_eval_shared("examples", name, *args, &block)
|
339
|
+
find_and_eval_shared("examples", name, caller.first, *args, &block)
|
304
340
|
end
|
305
341
|
|
306
342
|
# @private
|
307
|
-
def self.find_and_eval_shared(label, name, *args, &customization_block)
|
343
|
+
def self.find_and_eval_shared(label, name, inclusion_location, *args, &customization_block)
|
308
344
|
shared_block = RSpec.world.shared_example_group_registry.find(parent_groups, name)
|
309
345
|
|
310
346
|
unless shared_block
|
311
347
|
raise ArgumentError, "Could not find shared #{label} #{name.inspect}"
|
312
348
|
end
|
313
349
|
|
314
|
-
|
315
|
-
|
350
|
+
SharedExampleGroupInclusionStackFrame.with_frame(name, Metadata.relative_path(inclusion_location)) do
|
351
|
+
module_exec(*args, &shared_block)
|
352
|
+
module_exec(&customization_block) if customization_block
|
353
|
+
end
|
316
354
|
end
|
317
355
|
|
318
356
|
# @!endgroup
|
@@ -338,10 +376,11 @@ module RSpec
|
|
338
376
|
# Ruby 1.9 has a bug that can lead to infinite recursion and a
|
339
377
|
# SystemStackError if you include a module in a superclass after
|
340
378
|
# including it in a subclass: https://gist.github.com/845896
|
341
|
-
# To prevent this, we must include any modules in
|
342
|
-
# before users create example groups and have
|
343
|
-
# the same module in a subclass of
|
344
|
-
# So we need to configure example groups
|
379
|
+
# To prevent this, we must include any modules in
|
380
|
+
# RSpec::Core::ExampleGroup before users create example groups and have
|
381
|
+
# a chance to include the same module in a subclass of
|
382
|
+
# RSpec::Core::ExampleGroup. So we need to configure example groups
|
383
|
+
# here.
|
345
384
|
ensure_example_groups_are_configured
|
346
385
|
|
347
386
|
description = args.shift
|
@@ -353,7 +392,7 @@ module RSpec
|
|
353
392
|
)
|
354
393
|
|
355
394
|
hooks.register_globals(self, RSpec.configuration.hooks)
|
356
|
-
RSpec.
|
395
|
+
RSpec.configuration.configure_group(self)
|
357
396
|
end
|
358
397
|
|
359
398
|
# @private
|
@@ -368,7 +407,8 @@ module RSpec
|
|
368
407
|
|
369
408
|
# @private
|
370
409
|
def self.descendant_filtered_examples
|
371
|
-
@descendant_filtered_examples ||= filtered_examples +
|
410
|
+
@descendant_filtered_examples ||= filtered_examples +
|
411
|
+
FlatMap.flat_map(children, &:descendant_filtered_examples)
|
372
412
|
end
|
373
413
|
|
374
414
|
# @private
|
@@ -378,7 +418,7 @@ module RSpec
|
|
378
418
|
|
379
419
|
# @private
|
380
420
|
def self.descendants
|
381
|
-
@_descendants ||= [self] +
|
421
|
+
@_descendants ||= [self] + FlatMap.flat_map(children, &:descendants)
|
382
422
|
end
|
383
423
|
|
384
424
|
## @private
|
@@ -409,47 +449,66 @@ module RSpec
|
|
409
449
|
|
410
450
|
# @private
|
411
451
|
def self.store_before_context_ivars(example_group_instance)
|
412
|
-
|
413
|
-
|
414
|
-
example_group_instance.instance_variables.each do |ivar|
|
452
|
+
each_instance_variable_for_example(example_group_instance) do |ivar|
|
415
453
|
before_context_ivars[ivar] = example_group_instance.instance_variable_get(ivar)
|
416
454
|
end
|
417
455
|
end
|
418
456
|
|
419
457
|
# @private
|
420
458
|
def self.run_before_context_hooks(example_group_instance)
|
421
|
-
|
422
|
-
|
423
|
-
|
459
|
+
set_ivars(example_group_instance, superclass_before_context_ivars)
|
460
|
+
|
461
|
+
ContextHookMemoizedHash::Before.isolate_for_context_hook(example_group_instance) do
|
462
|
+
hooks.run(:before, :context, example_group_instance)
|
463
|
+
end
|
464
|
+
ensure
|
465
|
+
store_before_context_ivars(example_group_instance)
|
466
|
+
end
|
424
467
|
|
425
|
-
|
426
|
-
|
468
|
+
if RUBY_VERSION.to_f >= 1.9
|
469
|
+
# @private
|
470
|
+
def self.superclass_before_context_ivars
|
471
|
+
superclass.before_context_ivars
|
472
|
+
end
|
473
|
+
else # 1.8.7
|
474
|
+
# @private
|
475
|
+
def self.superclass_before_context_ivars
|
476
|
+
if superclass.respond_to?(:before_context_ivars)
|
477
|
+
superclass.before_context_ivars
|
478
|
+
else
|
479
|
+
# `self` must be the singleton class of an ExampleGroup instance.
|
480
|
+
# On 1.8.7, the superclass of a singleton class of an instance of A
|
481
|
+
# is A's singleton class. On 1.9+, it's A. On 1.8.7, the first ancestor
|
482
|
+
# is A, so we can mirror 1.8.7's behavior here. Note that we have to
|
483
|
+
# search for the first that responds to `before_context_ivars`
|
484
|
+
# in case a module has been included in the singleton class.
|
485
|
+
ancestors.find { |a| a.respond_to?(:before_context_ivars) }.before_context_ivars
|
427
486
|
end
|
428
|
-
ensure
|
429
|
-
store_before_context_ivars(example_group_instance)
|
430
487
|
end
|
431
488
|
end
|
432
489
|
|
433
490
|
# @private
|
434
491
|
def self.run_after_context_hooks(example_group_instance)
|
435
|
-
return if descendant_filtered_examples.empty?
|
436
492
|
set_ivars(example_group_instance, before_context_ivars)
|
437
493
|
|
438
494
|
ContextHookMemoizedHash::After.isolate_for_context_hook(example_group_instance) do
|
439
495
|
hooks.run(:after, :context, example_group_instance)
|
440
496
|
end
|
497
|
+
ensure
|
498
|
+
before_context_ivars.clear
|
441
499
|
end
|
442
500
|
|
443
|
-
# Runs all the examples in this group
|
444
|
-
def self.run(reporter)
|
501
|
+
# Runs all the examples in this group.
|
502
|
+
def self.run(reporter=RSpec::Core::NullReporter.new)
|
445
503
|
if RSpec.world.wants_to_quit
|
446
504
|
RSpec.world.clear_remaining_example_groups if top_level?
|
447
505
|
return
|
448
506
|
end
|
449
507
|
reporter.example_group_started(self)
|
450
508
|
|
509
|
+
should_run_context_hooks = descendant_filtered_examples.any?
|
451
510
|
begin
|
452
|
-
run_before_context_hooks(new)
|
511
|
+
run_before_context_hooks(new('before(:context) hook')) if should_run_context_hooks
|
453
512
|
result_for_this_group = run_examples(reporter)
|
454
513
|
results_for_descendants = ordering_strategy.order(children).map { |child| child.run(reporter) }.all?
|
455
514
|
result_for_this_group && results_for_descendants
|
@@ -459,8 +518,7 @@ module RSpec
|
|
459
518
|
RSpec.world.wants_to_quit = true if fail_fast?
|
460
519
|
for_filtered_examples(reporter) { |example| example.fail_with_exception(reporter, ex) }
|
461
520
|
ensure
|
462
|
-
run_after_context_hooks(new)
|
463
|
-
before_context_ivars.clear
|
521
|
+
run_after_context_hooks(new('after(:context) hook')) if should_run_context_hooks
|
464
522
|
reporter.example_group_finished(self)
|
465
523
|
end
|
466
524
|
end
|
@@ -485,7 +543,7 @@ module RSpec
|
|
485
543
|
def self.run_examples(reporter)
|
486
544
|
ordering_strategy.order(filtered_examples).map do |example|
|
487
545
|
next if RSpec.world.wants_to_quit
|
488
|
-
instance = new
|
546
|
+
instance = new(example.inspect_output)
|
489
547
|
set_ivars(instance, before_context_ivars)
|
490
548
|
succeeded = example.run(instance, reporter)
|
491
549
|
RSpec.world.wants_to_quit = true if fail_fast? && !succeeded
|
@@ -510,21 +568,11 @@ module RSpec
|
|
510
568
|
RSpec.configuration.fail_fast?
|
511
569
|
end
|
512
570
|
|
513
|
-
# @private
|
514
|
-
def self.any_apply?(filters)
|
515
|
-
MetadataFilter.any_apply?(filters, metadata)
|
516
|
-
end
|
517
|
-
|
518
|
-
# @private
|
519
|
-
def self.all_apply?(filters)
|
520
|
-
MetadataFilter.all_apply?(filters, metadata)
|
521
|
-
end
|
522
|
-
|
523
571
|
# @private
|
524
572
|
def self.declaration_line_numbers
|
525
573
|
@declaration_line_numbers ||= [metadata[:line_number]] +
|
526
574
|
examples.map { |e| e.metadata[:line_number] } +
|
527
|
-
|
575
|
+
FlatMap.flat_map(children, &:declaration_line_numbers)
|
528
576
|
end
|
529
577
|
|
530
578
|
# @private
|
@@ -536,6 +584,70 @@ module RSpec
|
|
536
584
|
def self.set_ivars(instance, ivars)
|
537
585
|
ivars.each { |name, value| instance.instance_variable_set(name, value) }
|
538
586
|
end
|
587
|
+
|
588
|
+
if RUBY_VERSION.to_f < 1.9
|
589
|
+
# @private
|
590
|
+
INSTANCE_VARIABLE_TO_IGNORE = '@__inspect_output'.freeze
|
591
|
+
else
|
592
|
+
# @private
|
593
|
+
INSTANCE_VARIABLE_TO_IGNORE = :@__inspect_output
|
594
|
+
end
|
595
|
+
|
596
|
+
# @private
|
597
|
+
def self.each_instance_variable_for_example(group)
|
598
|
+
group.instance_variables.each do |ivar|
|
599
|
+
yield ivar unless ivar == INSTANCE_VARIABLE_TO_IGNORE
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
def initialize(inspect_output=nil)
|
604
|
+
@__inspect_output = inspect_output || '(no description provided)'
|
605
|
+
end
|
606
|
+
|
607
|
+
# @private
|
608
|
+
def inspect
|
609
|
+
"#<#{self.class} #{@__inspect_output}>"
|
610
|
+
end
|
611
|
+
|
612
|
+
unless method_defined?(:singleton_class) # for 1.8.7
|
613
|
+
# @private
|
614
|
+
def singleton_class
|
615
|
+
class << self; self; end
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
# Raised when an RSpec API is called in the wrong scope, such as `before`
|
620
|
+
# being called from within an example rather than from within an example
|
621
|
+
# group block.
|
622
|
+
WrongScopeError = Class.new(NoMethodError)
|
623
|
+
|
624
|
+
def self.method_missing(name, *args)
|
625
|
+
if method_defined?(name)
|
626
|
+
raise WrongScopeError,
|
627
|
+
"`#{name}` is not available on an example group (e.g. a " \
|
628
|
+
"`describe` or `context` block). It is only available from " \
|
629
|
+
"within individual examples (e.g. `it` blocks) or from " \
|
630
|
+
"constructs that run in the scope of an example (e.g. " \
|
631
|
+
"`before`, `let`, etc)."
|
632
|
+
end
|
633
|
+
|
634
|
+
super
|
635
|
+
end
|
636
|
+
private_class_method :method_missing
|
637
|
+
|
638
|
+
private
|
639
|
+
|
640
|
+
def method_missing(name, *args)
|
641
|
+
if self.class.respond_to?(name)
|
642
|
+
raise WrongScopeError,
|
643
|
+
"`#{name}` is not available from within an example (e.g. an " \
|
644
|
+
"`it` block) or from constructs that run in the scope of an " \
|
645
|
+
"example (e.g. `before`, `let`, etc). It is only available " \
|
646
|
+
"on an example group (e.g. a `describe` or `context` block)."
|
647
|
+
end
|
648
|
+
|
649
|
+
super
|
650
|
+
end
|
539
651
|
end
|
540
652
|
|
541
653
|
# @private
|
@@ -545,11 +657,55 @@ module RSpec
|
|
545
657
|
{}
|
546
658
|
end
|
547
659
|
end
|
660
|
+
|
661
|
+
# Contains information about the inclusion site of a shared example group.
|
662
|
+
class SharedExampleGroupInclusionStackFrame
|
663
|
+
# @return [String] the name of the shared example group
|
664
|
+
attr_reader :shared_group_name
|
665
|
+
# @return [String] the location where the shared example was included
|
666
|
+
attr_reader :inclusion_location
|
667
|
+
|
668
|
+
def initialize(shared_group_name, inclusion_location)
|
669
|
+
@shared_group_name = shared_group_name
|
670
|
+
@inclusion_location = inclusion_location
|
671
|
+
end
|
672
|
+
|
673
|
+
# @return [String] The {#inclusion_location}, formatted for display by a formatter.
|
674
|
+
def formatted_inclusion_location
|
675
|
+
@formatted_inclusion_location ||= begin
|
676
|
+
RSpec.configuration.backtrace_formatter.backtrace_line(
|
677
|
+
inclusion_location.sub(/(:\d+):in .+$/, '\1')
|
678
|
+
)
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
682
|
+
# @return [String] Description of this stack frame, in the form used by
|
683
|
+
# RSpec's built-in formatters.
|
684
|
+
def description
|
685
|
+
@description ||= "Shared Example Group: #{shared_group_name.inspect} " \
|
686
|
+
"called from #{formatted_inclusion_location}"
|
687
|
+
end
|
688
|
+
|
689
|
+
# @private
|
690
|
+
def self.current_backtrace
|
691
|
+
RSpec.thread_local_metadata[:shared_example_group_inclusions].reverse
|
692
|
+
end
|
693
|
+
|
694
|
+
# @private
|
695
|
+
def self.with_frame(name, location)
|
696
|
+
current_stack = RSpec.thread_local_metadata[:shared_example_group_inclusions]
|
697
|
+
current_stack << new(name, location)
|
698
|
+
yield
|
699
|
+
ensure
|
700
|
+
current_stack.pop
|
701
|
+
end
|
702
|
+
end
|
548
703
|
end
|
549
704
|
|
550
705
|
# @private
|
551
706
|
#
|
552
|
-
# Namespace for the example group subclasses generated by top-level
|
707
|
+
# Namespace for the example group subclasses generated by top-level
|
708
|
+
# `describe`.
|
553
709
|
module ExampleGroups
|
554
710
|
extend Support::RecursiveConstMethods
|
555
711
|
|
@@ -570,16 +726,20 @@ module RSpec
|
|
570
726
|
def self.base_name_for(group)
|
571
727
|
return "Anonymous" if group.description.empty?
|
572
728
|
|
573
|
-
#
|
574
|
-
name = ' '
|
575
|
-
name.gsub!(/[^0-9a-zA-Z]+([0-9a-zA-Z])/)
|
729
|
+
# Convert to CamelCase.
|
730
|
+
name = ' ' << group.description
|
731
|
+
name.gsub!(/[^0-9a-zA-Z]+([0-9a-zA-Z])/) do
|
732
|
+
match = ::Regexp.last_match[1]
|
733
|
+
match.upcase!
|
734
|
+
match
|
735
|
+
end
|
576
736
|
|
577
|
-
name.lstrip!
|
578
|
-
name.gsub!(/\W/, '') # JRuby, RBX and others don't like non-ascii in const names
|
737
|
+
name.lstrip! # Remove leading whitespace
|
738
|
+
name.gsub!(/\W/, ''.freeze) # JRuby, RBX and others don't like non-ascii in const names
|
579
739
|
|
580
740
|
# Ruby requires first const letter to be A-Z. Use `Nested`
|
581
741
|
# as necessary to enforce that.
|
582
|
-
name.gsub!(/\A([^A-Z]|\z)/, 'Nested\1')
|
742
|
+
name.gsub!(/\A([^A-Z]|\z)/, 'Nested\1'.freeze)
|
583
743
|
|
584
744
|
name
|
585
745
|
end
|
@@ -597,7 +757,8 @@ module RSpec
|
|
597
757
|
def self.disambiguate(name, const_scope)
|
598
758
|
return name unless const_defined_on?(const_scope, name)
|
599
759
|
|
600
|
-
# Add a trailing number if needed to disambiguate from an existing
|
760
|
+
# Add a trailing number if needed to disambiguate from an existing
|
761
|
+
# constant.
|
601
762
|
name << "_2"
|
602
763
|
name.next! while const_defined_on?(const_scope, name)
|
603
764
|
name
|