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.
Files changed (33) hide show
  1. data/.yardopts +4 -1
  2. data/Changelog.md +20 -0
  3. data/README.md +10 -4
  4. data/features/command_line/format_option.feature +1 -1
  5. data/features/expectation_framework_integration/configure_expectation_framework.feature +20 -7
  6. data/features/hooks/around_hooks.feature +1 -1
  7. data/features/hooks/before_and_after_hooks.feature +5 -5
  8. data/features/hooks/filtering.feature +2 -2
  9. data/features/pending/pending_examples.feature +2 -2
  10. data/lib/rspec/core/configuration_options.rb +4 -3
  11. data/lib/rspec/core/example.rb +37 -22
  12. data/lib/rspec/core/example_group.rb +18 -20
  13. data/lib/rspec/core/formatters/base_formatter.rb +2 -8
  14. data/lib/rspec/core/formatters/base_text_formatter.rb +13 -4
  15. data/lib/rspec/core/hooks.rb +120 -77
  16. data/lib/rspec/core/let.rb +14 -6
  17. data/lib/rspec/core/metadata.rb +10 -2
  18. data/lib/rspec/core/subject.rb +34 -13
  19. data/lib/rspec/core/version.rb +1 -1
  20. data/lib/rspec/core/world.rb +0 -4
  21. data/spec/rspec/core/configuration_options_spec.rb +8 -2
  22. data/spec/rspec/core/drb_options_spec.rb +1 -1
  23. data/spec/rspec/core/example_group_spec.rb +2 -2
  24. data/spec/rspec/core/example_spec.rb +39 -16
  25. data/spec/rspec/core/formatters/base_text_formatter_spec.rb +17 -7
  26. data/spec/rspec/core/hooks_spec.rb +117 -10
  27. data/spec/rspec/core/metadata_spec.rb +13 -3
  28. data/spec/rspec/core/pending_example_spec.rb +3 -2
  29. data/spec/rspec/core/subject_spec.rb +2 -0
  30. data/spec/spec_helper.rb +1 -0
  31. data/spec/support/config_options_helper.rb +0 -3
  32. data/spec/support/helper_methods.rb +5 -0
  33. 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 #{BaseFormatter::relative_path(example.location)}") + " " + cyan("# #{example.full_description}"))
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 { |example| example.execution_result[:run_time] }.reverse.first(10)
56
- output.puts "\nTop #{sorted_examples.size} slowest examples:\n"
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.push(example.example_group)
198
+ example.example_group.ancestors + [example.example_group]
190
199
  end
191
200
  end
192
201
  end
@@ -3,96 +3,105 @@ module RSpec
3
3
  module Hooks
4
4
  include MetadataHashBuilder::WithConfigWarning
5
5
 
6
- class Hook
6
+ module HookExtension
7
7
  attr_reader :options
8
8
 
9
- def initialize(options, &block)
9
+ def with(options)
10
10
  @options = options
11
- raise "no block given for #{display_name}" unless block
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
- def to_proc
20
- @block
21
- end
19
+ module BeforeHookExtension
20
+ include HookExtension
22
21
 
23
- def call
24
- @block.call
22
+ def run(example)
23
+ example.instance_eval(&self)
25
24
  end
26
25
 
27
26
  def display_name
28
- self.class.name.split('::').last.gsub('Hook','').downcase << " hook"
27
+ "before hook"
29
28
  end
30
29
  end
31
30
 
32
- class BeforeHook < Hook
33
- def run_in(example_group_instance)
34
- if example_group_instance
35
- example_group_instance.instance_eval(&self)
36
- else
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
- class AfterHook < Hook
43
- def run_in(example_group_instance)
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
- class AroundHook < Hook
53
- def call(wrapped_example)
54
- @block.call(wrapped_example)
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 find_hooks_for(example_or_group)
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 without_hooks_for(example_or_group)
64
- self.class.new(reject {|hook| hook.options_apply?(example_or_group)})
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 BeforeHooks < HookCollection
69
- def run_all(example_group_instance)
70
- each {|h| h.run_in(example_group_instance) } unless empty?
67
+ class GroupHookCollection < Array
68
+ def for(group)
69
+ @group = group
70
+ self
71
71
  end
72
72
 
73
- def run_all!(example_group_instance)
74
- shift.run_in(example_group_instance) until empty?
73
+ def run
74
+ shift.run(@group) until empty?
75
75
  end
76
76
  end
77
77
 
78
- class AfterHooks < HookCollection
79
- def run_all(example_group_instance)
80
- reverse.each {|h| h.run_in(example_group_instance) }
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 run_all!(example_group_instance)
84
- pop.run_in(example_group_instance) until empty?
84
+ def with(example, initial_procsy)
85
+ @example = example
86
+ @initial_procsy = initial_procsy
87
+ self
85
88
  end
86
- end
87
89
 
88
- class AroundHooks < HookCollection; end
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 => AroundHooks.new },
94
- :before => { :each => BeforeHooks.new, :all => BeforeHooks.new, :suite => BeforeHooks.new },
95
- :after => { :each => AfterHooks.new, :all => AfterHooks.new, :suite => AfterHooks.new }
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] << BeforeHook.new(options, &block)
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] << AfterHook.new(options, &block)
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] << AroundHook.new(options, &block)
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, example_group_instance=nil)
368
- hooks[hook][scope].run_all(example_group_instance)
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
- # Just like run_hook, except it removes the blocks as it evalutes them,
373
- # ensuring that they will only be run once.
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
- # @private
379
- def run_hook_filtered(hook, scope, group, example_group_instance, example = nil)
380
- find_hook(hook, scope, group, example).run_all(example_group_instance)
408
+ private
409
+
410
+ def before_all_hooks_for(group)
411
+ GroupHookCollection.new(hooks[:before][:all]).for(group)
381
412
  end
382
413
 
383
- # @private
384
- def find_hook(hook, scope, example_group_class, example = nil)
385
- found_hooks = hooks[hook][scope].find_hooks_for(example || example_group_class)
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
- found_hooks
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
- private
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
- scope = if SCOPES.include?(args.first)
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
@@ -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
- # after the first call.
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 <tt>let()</tt>, except the block is invoked
29
- # by an implicit <tt>before</tt> hook. This serves a dual
30
- # purpose of setting up state and providing a memoized
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
  #
@@ -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 1 then value.call(metadata[key])
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
@@ -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 is
7
- # only executed once per example, the result of which is cached and
8
- # returned by any subsequent calls to +subject+.
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 +describe+ and no subject is explicitly
11
- # declared in the example group, then +subject+ will return a new
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 subject defined by the subject method
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/extensions/kernel'
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
- self == subject ? self.__should_for_example_group__(matcher) : subject.should(matcher,message)
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
- self == subject ? self.__should_not_for_example_group__(matcher) : subject.should_not(matcher,message)
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 |subject, attr|
77
- subject.send(attr)
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,