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.
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,