rspec-core 2.9.0 → 2.10.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.
- data/.yardopts +4 -1
- data/Changelog.md +20 -0
- data/README.md +10 -4
- data/features/command_line/format_option.feature +1 -1
- data/features/expectation_framework_integration/configure_expectation_framework.feature +20 -7
- data/features/hooks/around_hooks.feature +1 -1
- data/features/hooks/before_and_after_hooks.feature +5 -5
- data/features/hooks/filtering.feature +2 -2
- data/features/pending/pending_examples.feature +2 -2
- data/lib/rspec/core/configuration_options.rb +4 -3
- data/lib/rspec/core/example.rb +37 -22
- data/lib/rspec/core/example_group.rb +18 -20
- data/lib/rspec/core/formatters/base_formatter.rb +2 -8
- data/lib/rspec/core/formatters/base_text_formatter.rb +13 -4
- data/lib/rspec/core/hooks.rb +120 -77
- data/lib/rspec/core/let.rb +14 -6
- data/lib/rspec/core/metadata.rb +10 -2
- data/lib/rspec/core/subject.rb +34 -13
- data/lib/rspec/core/version.rb +1 -1
- data/lib/rspec/core/world.rb +0 -4
- data/spec/rspec/core/configuration_options_spec.rb +8 -2
- data/spec/rspec/core/drb_options_spec.rb +1 -1
- data/spec/rspec/core/example_group_spec.rb +2 -2
- data/spec/rspec/core/example_spec.rb +39 -16
- data/spec/rspec/core/formatters/base_text_formatter_spec.rb +17 -7
- data/spec/rspec/core/hooks_spec.rb +117 -10
- data/spec/rspec/core/metadata_spec.rb +13 -3
- data/spec/rspec/core/pending_example_spec.rb +3 -2
- data/spec/rspec/core/subject_spec.rb +2 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/config_options_helper.rb +0 -3
- data/spec/support/helper_methods.rb +5 -0
- metadata +153 -142
@@ -47,13 +47,22 @@ module RSpec
|
|
47
47
|
output.puts
|
48
48
|
|
49
49
|
failed_examples.each do |example|
|
50
|
-
output.puts(red("rspec #{
|
50
|
+
output.puts(red("rspec #{RSpec::Core::Metadata::relative_path(example.location)}") + " " + cyan("# #{example.full_description}"))
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
54
|
def dump_profile
|
55
|
-
sorted_examples = examples.sort_by {
|
56
|
-
|
55
|
+
sorted_examples = examples.sort_by {|example|
|
56
|
+
example.execution_result[:run_time] }.reverse.first(10)
|
57
|
+
|
58
|
+
total, slows = [examples, sorted_examples].map {|exs|
|
59
|
+
exs.inject(0.0) {|i, e| i + e.execution_result[:run_time] }}
|
60
|
+
|
61
|
+
time_taken = slows / total
|
62
|
+
percentage = '%.1f' % ((time_taken.nan? ? 0.0 : time_taken) * 100)
|
63
|
+
|
64
|
+
output.puts "\nTop #{sorted_examples.size} slowest examples (#{format_seconds(slows)} seconds, #{percentage}% of total time):\n"
|
65
|
+
|
57
66
|
sorted_examples.each do |example|
|
58
67
|
output.puts " #{example.full_description}"
|
59
68
|
output.puts cyan(" #{red(format_seconds(example.execution_result[:run_time]))} #{red("seconds")} #{format_caller(example.location)}")
|
@@ -186,7 +195,7 @@ module RSpec
|
|
186
195
|
end
|
187
196
|
|
188
197
|
def group_and_ancestors(example)
|
189
|
-
example.example_group.ancestors
|
198
|
+
example.example_group.ancestors + [example.example_group]
|
190
199
|
end
|
191
200
|
end
|
192
201
|
end
|
data/lib/rspec/core/hooks.rb
CHANGED
@@ -3,96 +3,105 @@ module RSpec
|
|
3
3
|
module Hooks
|
4
4
|
include MetadataHashBuilder::WithConfigWarning
|
5
5
|
|
6
|
-
|
6
|
+
module HookExtension
|
7
7
|
attr_reader :options
|
8
8
|
|
9
|
-
def
|
9
|
+
def with(options)
|
10
10
|
@options = options
|
11
|
-
|
12
|
-
@block = block
|
11
|
+
self
|
13
12
|
end
|
14
13
|
|
15
14
|
def options_apply?(example_or_group)
|
16
15
|
example_or_group.all_apply?(options)
|
17
16
|
end
|
17
|
+
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
end
|
19
|
+
module BeforeHookExtension
|
20
|
+
include HookExtension
|
22
21
|
|
23
|
-
def
|
24
|
-
|
22
|
+
def run(example)
|
23
|
+
example.instance_eval(&self)
|
25
24
|
end
|
26
25
|
|
27
26
|
def display_name
|
28
|
-
|
27
|
+
"before hook"
|
29
28
|
end
|
30
29
|
end
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
call
|
38
|
-
end
|
31
|
+
module AfterHookExtension
|
32
|
+
include HookExtension
|
33
|
+
|
34
|
+
def run(example)
|
35
|
+
example.instance_eval_with_rescue(&self)
|
39
36
|
end
|
40
|
-
end
|
41
37
|
|
42
|
-
|
43
|
-
|
44
|
-
if example_group_instance
|
45
|
-
example_group_instance.instance_eval_with_rescue(&self)
|
46
|
-
else
|
47
|
-
call
|
48
|
-
end
|
38
|
+
def display_name
|
39
|
+
"after hook"
|
49
40
|
end
|
50
41
|
end
|
51
42
|
|
52
|
-
|
53
|
-
|
54
|
-
|
43
|
+
module AroundHookExtension
|
44
|
+
include HookExtension
|
45
|
+
|
46
|
+
def display_name
|
47
|
+
"around hook"
|
55
48
|
end
|
56
49
|
end
|
57
50
|
|
58
51
|
class HookCollection < Array
|
59
|
-
def
|
60
|
-
self.class.new(select {|hook| hook.options_apply?(example_or_group)})
|
52
|
+
def for(example_or_group)
|
53
|
+
self.class.new(select {|hook| hook.options_apply?(example_or_group)}).
|
54
|
+
with(example_or_group)
|
61
55
|
end
|
62
56
|
|
63
|
-
def
|
64
|
-
|
57
|
+
def with(example)
|
58
|
+
@example = example
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def run
|
63
|
+
each {|h| h.run(@example) } unless empty?
|
65
64
|
end
|
66
65
|
end
|
67
66
|
|
68
|
-
class
|
69
|
-
def
|
70
|
-
|
67
|
+
class GroupHookCollection < Array
|
68
|
+
def for(group)
|
69
|
+
@group = group
|
70
|
+
self
|
71
71
|
end
|
72
72
|
|
73
|
-
def
|
74
|
-
shift.
|
73
|
+
def run
|
74
|
+
shift.run(@group) until empty?
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
-
class
|
79
|
-
def
|
80
|
-
|
78
|
+
class AroundHookCollection < Array
|
79
|
+
def for(example, initial_procsy=nil)
|
80
|
+
self.class.new(select {|hook| hook.options_apply?(example)}).
|
81
|
+
with(example, initial_procsy)
|
81
82
|
end
|
82
83
|
|
83
|
-
def
|
84
|
-
|
84
|
+
def with(example, initial_procsy)
|
85
|
+
@example = example
|
86
|
+
@initial_procsy = initial_procsy
|
87
|
+
self
|
85
88
|
end
|
86
|
-
end
|
87
89
|
|
88
|
-
|
90
|
+
def run
|
91
|
+
inject(@initial_procsy) do |procsy, around_hook|
|
92
|
+
Example.procsy(procsy.metadata) do
|
93
|
+
@example.instance_eval_with_args(procsy, &around_hook)
|
94
|
+
end
|
95
|
+
end.call
|
96
|
+
end
|
97
|
+
end
|
89
98
|
|
90
99
|
# @private
|
91
100
|
def hooks
|
92
101
|
@hooks ||= {
|
93
|
-
:around => { :each =>
|
94
|
-
:before => { :each =>
|
95
|
-
:after =>
|
102
|
+
:around => { :each => AroundHookCollection.new },
|
103
|
+
:before => { :each => [], :all => [], :suite => HookCollection.new },
|
104
|
+
:after => { :each => [], :all => [], :suite => HookCollection.new }
|
96
105
|
}
|
97
106
|
end
|
98
107
|
|
@@ -256,7 +265,18 @@ module RSpec
|
|
256
265
|
# end
|
257
266
|
def before(*args, &block)
|
258
267
|
scope, options = scope_and_options_from(*args)
|
259
|
-
hooks[:before][scope] <<
|
268
|
+
hooks[:before][scope] << block.extend(BeforeHookExtension).with(options)
|
269
|
+
end
|
270
|
+
|
271
|
+
alias_method :append_before, :before
|
272
|
+
|
273
|
+
# Adds `block` to the front of the list of `before` blocks in the same
|
274
|
+
# scope (`:each`, `:all`, or `:suite`).
|
275
|
+
#
|
276
|
+
# See #before for scoping semantics.
|
277
|
+
def prepend_before(*args, &block)
|
278
|
+
scope, options = scope_and_options_from(*args)
|
279
|
+
hooks[:before][scope].unshift block.extend(BeforeHookExtension).with(options)
|
260
280
|
end
|
261
281
|
|
262
282
|
# @api public
|
@@ -309,7 +329,18 @@ module RSpec
|
|
309
329
|
# they are run in reverse order of that in which they are declared.
|
310
330
|
def after(*args, &block)
|
311
331
|
scope, options = scope_and_options_from(*args)
|
312
|
-
hooks[:after][scope]
|
332
|
+
hooks[:after][scope].unshift block.extend(AfterHookExtension).with(options)
|
333
|
+
end
|
334
|
+
|
335
|
+
alias_method :prepend_after, :after
|
336
|
+
|
337
|
+
# Adds `block` to the back of the list of `after` blocks in the same
|
338
|
+
# scope (`:each`, `:all`, or `:suite`).
|
339
|
+
#
|
340
|
+
# See #after for scoping semantics.
|
341
|
+
def append_after(*args, &block)
|
342
|
+
scope, options = scope_and_options_from(*args)
|
343
|
+
hooks[:after][scope] << block.extend(AfterHookExtension).with(options)
|
313
344
|
end
|
314
345
|
|
315
346
|
# @api public
|
@@ -358,59 +389,71 @@ module RSpec
|
|
358
389
|
#
|
359
390
|
def around(*args, &block)
|
360
391
|
scope, options = scope_and_options_from(*args)
|
361
|
-
hooks[:around][scope]
|
392
|
+
hooks[:around][scope].unshift block.extend(AroundHookExtension).with(options)
|
362
393
|
end
|
363
394
|
|
364
395
|
# @private
|
396
|
+
#
|
365
397
|
# Runs all of the blocks stored with the hook in the context of the
|
366
398
|
# example. If no example is provided, just calls the hook directly.
|
367
|
-
def run_hook(hook, scope,
|
368
|
-
|
399
|
+
def run_hook(hook, scope, example_or_group=ExampleGroup.new, initial_procsy=nil)
|
400
|
+
find_hook(hook, scope, example_or_group, initial_procsy).run
|
369
401
|
end
|
370
402
|
|
371
403
|
# @private
|
372
|
-
|
373
|
-
|
374
|
-
def run_hook!(hook, scope, example_group_instance)
|
375
|
-
hooks[hook][scope].run_all!(example_group_instance)
|
404
|
+
def around_each_hooks_for(example, initial_procsy=nil)
|
405
|
+
AroundHookCollection.new(ancestors.map {|a| a.hooks[:around][:each]}.flatten).for(example, initial_procsy)
|
376
406
|
end
|
377
407
|
|
378
|
-
|
379
|
-
|
380
|
-
|
408
|
+
private
|
409
|
+
|
410
|
+
def before_all_hooks_for(group)
|
411
|
+
GroupHookCollection.new(hooks[:before][:all]).for(group)
|
381
412
|
end
|
382
413
|
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
# ensure we don't re-run :all hooks that were applied to any of the parent groups
|
388
|
-
if scope == :all
|
389
|
-
super_klass = example_group_class.superclass
|
390
|
-
while super_klass != RSpec::Core::ExampleGroup
|
391
|
-
found_hooks = found_hooks.without_hooks_for(super_klass)
|
392
|
-
super_klass = super_klass.superclass
|
393
|
-
end
|
394
|
-
end
|
414
|
+
def after_all_hooks_for(group)
|
415
|
+
GroupHookCollection.new(hooks[:after][:all]).for(group)
|
416
|
+
end
|
395
417
|
|
396
|
-
|
418
|
+
def before_each_hooks_for(example)
|
419
|
+
HookCollection.new(ancestors.reverse.map {|a| a.hooks[:before][:each]}.flatten).for(example)
|
397
420
|
end
|
398
421
|
|
399
|
-
|
422
|
+
def after_each_hooks_for(example)
|
423
|
+
HookCollection.new(ancestors.map {|a| a.hooks[:after][:each]}.flatten).for(example)
|
424
|
+
end
|
425
|
+
|
426
|
+
def find_hook(hook, scope, example_or_group, initial_procsy)
|
427
|
+
case [hook, scope]
|
428
|
+
when [:before, :all]
|
429
|
+
before_all_hooks_for(example_or_group)
|
430
|
+
when [:after, :all]
|
431
|
+
after_all_hooks_for(example_or_group)
|
432
|
+
when [:around, :each]
|
433
|
+
around_each_hooks_for(example_or_group, initial_procsy)
|
434
|
+
when [:before, :each]
|
435
|
+
before_each_hooks_for(example_or_group)
|
436
|
+
when [:after, :each]
|
437
|
+
after_each_hooks_for(example_or_group)
|
438
|
+
when [:before, :suite], [:after, :suite]
|
439
|
+
hooks[hook][:suite].with(example_or_group)
|
440
|
+
end
|
441
|
+
end
|
400
442
|
|
401
443
|
SCOPES = [:each, :all, :suite]
|
402
444
|
|
403
445
|
def scope_and_options_from(*args)
|
404
|
-
|
446
|
+
return extract_scope_from(args), build_metadata_hash_from(args)
|
447
|
+
end
|
448
|
+
|
449
|
+
def extract_scope_from(args)
|
450
|
+
if SCOPES.include?(args.first)
|
405
451
|
args.shift
|
406
452
|
elsif args.any? { |a| a.is_a?(Symbol) }
|
407
453
|
raise ArgumentError.new("You must explicitly give a scope (:each, :all, or :suite) when using symbols as metadata for a hook.")
|
408
454
|
else
|
409
455
|
:each
|
410
456
|
end
|
411
|
-
|
412
|
-
options = build_metadata_hash_from(args)
|
413
|
-
return scope, options
|
414
457
|
end
|
415
458
|
end
|
416
459
|
end
|
data/lib/rspec/core/let.rb
CHANGED
@@ -3,8 +3,17 @@ module RSpec
|
|
3
3
|
module Let
|
4
4
|
|
5
5
|
module ExampleGroupMethods
|
6
|
-
# Generates a method whose return value is memoized
|
7
|
-
#
|
6
|
+
# Generates a method whose return value is memoized after the first
|
7
|
+
# call. Useful for reducing duplication between examples that assign
|
8
|
+
# values to the same local variable.
|
9
|
+
#
|
10
|
+
# @note `let` _can_ enhance readability when used sparingly (1,2, or
|
11
|
+
# maybe 3 declarations) in any given example group, but that can
|
12
|
+
# quickly degrade with overuse. YMMV.
|
13
|
+
#
|
14
|
+
# @note `let` uses an `||=` conditional that has the potential to
|
15
|
+
# behave in surprising ways in examples that spawn separate threads,
|
16
|
+
# though we have yet to see this in practice. You've been warned.
|
8
17
|
#
|
9
18
|
# @example
|
10
19
|
#
|
@@ -25,10 +34,9 @@ module RSpec
|
|
25
34
|
end
|
26
35
|
end
|
27
36
|
|
28
|
-
# Just like
|
29
|
-
#
|
30
|
-
#
|
31
|
-
# reference to that state.
|
37
|
+
# Just like `let`, except the block is invoked by an implicit `before`
|
38
|
+
# hook. This serves a dual purpose of setting up state and providing a
|
39
|
+
# memoized reference to that state.
|
32
40
|
#
|
33
41
|
# @example
|
34
42
|
#
|
data/lib/rspec/core/metadata.rb
CHANGED
@@ -26,6 +26,13 @@ module RSpec
|
|
26
26
|
# @see Configuration#filter_run_excluding
|
27
27
|
class Metadata < Hash
|
28
28
|
|
29
|
+
def self.relative_path(line)
|
30
|
+
line = line.sub(File.expand_path("."), ".")
|
31
|
+
line = line.sub(/\A([^:]+:\d+)$/, '\\1')
|
32
|
+
return nil if line == '-e:1'
|
33
|
+
line
|
34
|
+
end
|
35
|
+
|
29
36
|
# @private
|
30
37
|
module MetadataHash
|
31
38
|
|
@@ -67,7 +74,7 @@ module RSpec
|
|
67
74
|
|
68
75
|
def file_and_line_number
|
69
76
|
first_caller_from_outside_rspec =~ /(.+?):(\d+)(|:\d+)/
|
70
|
-
return [$1, $2.to_i]
|
77
|
+
return [Metadata::relative_path($1), $2.to_i]
|
71
78
|
end
|
72
79
|
|
73
80
|
def first_caller_from_outside_rspec
|
@@ -181,8 +188,9 @@ module RSpec
|
|
181
188
|
metadata[key] =~ value
|
182
189
|
when Proc
|
183
190
|
case value.arity
|
184
|
-
when
|
191
|
+
when 0 then value.call
|
185
192
|
when 2 then value.call(metadata[key], metadata)
|
193
|
+
else value.call(metadata[key])
|
186
194
|
end
|
187
195
|
else
|
188
196
|
metadata[key].to_s == value.to_s
|
data/lib/rspec/core/subject.rb
CHANGED
@@ -3,17 +3,29 @@ module RSpec
|
|
3
3
|
module Subject
|
4
4
|
module ExampleMethods
|
5
5
|
|
6
|
-
# Returns the subject defined by the example group. The subject block
|
7
|
-
# only executed once per example, the result of which is cached and
|
8
|
-
# returned by any subsequent calls to
|
6
|
+
# Returns the subject defined by the example group. The subject block
|
7
|
+
# is only executed once per example, the result of which is cached and
|
8
|
+
# returned by any subsequent calls to `subject`.
|
9
9
|
#
|
10
|
-
# If a class is passed to
|
11
|
-
# declared in the example group, then
|
10
|
+
# If a class is passed to `describe` and no subject is explicitly
|
11
|
+
# declared in the example group, then `subject` will return a new
|
12
12
|
# instance of that class.
|
13
13
|
#
|
14
|
+
# @note `subject` was contributed by Joe Ferris to support the one-liner
|
15
|
+
# syntax embraced by shoulda matchers:
|
16
|
+
#
|
17
|
+
# describe Widget do
|
18
|
+
# it { should validate_presence_of(:name) }
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# While the examples below demonstrate how to use `subject`
|
22
|
+
# explicitly in specs, we think it works best for extensions like
|
23
|
+
# shoulda, custom matchers, and shared example groups, where it is
|
24
|
+
# not referenced explicitly in specs.
|
25
|
+
#
|
14
26
|
# @example
|
15
27
|
#
|
16
|
-
# # explicit
|
28
|
+
# # explicit declaration of subject
|
17
29
|
# describe Person do
|
18
30
|
# subject { Person.new(:birthdate => 19.years.ago) }
|
19
31
|
# it "should be eligible to vote" do
|
@@ -27,6 +39,11 @@ module RSpec
|
|
27
39
|
# subject.should be_eligible_to_vote
|
28
40
|
# end
|
29
41
|
# end
|
42
|
+
#
|
43
|
+
# describe Person do
|
44
|
+
# # one liner syntax - should is invoked on subject
|
45
|
+
# it { should be_eligible_to_vote }
|
46
|
+
# end
|
30
47
|
def subject
|
31
48
|
if defined?(@original_subject)
|
32
49
|
@original_subject
|
@@ -36,9 +53,7 @@ module RSpec
|
|
36
53
|
end
|
37
54
|
|
38
55
|
begin
|
39
|
-
require 'rspec/expectations/
|
40
|
-
alias_method :__should_for_example_group__, :should
|
41
|
-
alias_method :__should_not_for_example_group__, :should_not
|
56
|
+
require 'rspec/expectations/handler'
|
42
57
|
|
43
58
|
# When +should+ is called with no explicit receiver, the call is
|
44
59
|
# delegated to the object returned by +subject+. Combined with
|
@@ -51,7 +66,7 @@ module RSpec
|
|
51
66
|
# it { should be_eligible_to_vote }
|
52
67
|
# end
|
53
68
|
def should(matcher=nil, message=nil)
|
54
|
-
|
69
|
+
RSpec::Expectations::PositiveExpectationHandler.handle_matcher(subject, matcher, message)
|
55
70
|
end
|
56
71
|
|
57
72
|
# Just like +should+, +should_not+ delegates to the subject (implicit or
|
@@ -63,7 +78,7 @@ module RSpec
|
|
63
78
|
# it { should_not be_eligible_to_vote }
|
64
79
|
# end
|
65
80
|
def should_not(matcher=nil, message=nil)
|
66
|
-
|
81
|
+
RSpec::Expectations::NegativeExpectationHandler.handle_matcher(subject, matcher, message)
|
67
82
|
end
|
68
83
|
rescue LoadError
|
69
84
|
end
|
@@ -73,8 +88,8 @@ module RSpec
|
|
73
88
|
end
|
74
89
|
|
75
90
|
def _nested_attribute(subject, attribute)
|
76
|
-
_attribute_chain(attribute).inject(subject) do |
|
77
|
-
|
91
|
+
_attribute_chain(attribute).inject(subject) do |inner_subject, attr|
|
92
|
+
inner_subject.send(attr)
|
78
93
|
end
|
79
94
|
end
|
80
95
|
end
|
@@ -83,6 +98,8 @@ module RSpec
|
|
83
98
|
# Creates a nested example group named by the submitted +attribute+,
|
84
99
|
# and then generates an example using the submitted block.
|
85
100
|
#
|
101
|
+
# @example
|
102
|
+
#
|
86
103
|
# # This ...
|
87
104
|
# describe Array do
|
88
105
|
# its(:size) { should eq(0) }
|
@@ -101,6 +118,8 @@ module RSpec
|
|
101
118
|
# with dots, the result is as though you concatenated that +String+
|
102
119
|
# onto the subject in an expression.
|
103
120
|
#
|
121
|
+
# @example
|
122
|
+
#
|
104
123
|
# describe Person do
|
105
124
|
# subject do
|
106
125
|
# Person.new.tap do |person|
|
@@ -114,6 +133,8 @@ module RSpec
|
|
114
133
|
# When the subject is a +Hash+, you can refer to the Hash keys by
|
115
134
|
# specifying a +Symbol+ or +String+ in an array.
|
116
135
|
#
|
136
|
+
# @example
|
137
|
+
#
|
117
138
|
# describe "a configuration Hash" do
|
118
139
|
# subject do
|
119
140
|
# { :max_users => 3,
|