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