123456789_123456789_123456789_123456789_123456789_

Class: RSpec::Core::Hooks::HookCollections Private

Do not use. This class is for internal use only.
Relationships & Source Files
Inherits: Object
Defined in: rspec-core/lib/rspec/core/hooks.rb

Overview

This provides the primary API used by other parts of rspec-core. By hiding all implementation details behind this facade, it’s allowed us to heavily optimize this, so that, for example, hook collection objects are only instantiated when a hook is added. This allows us to avoid many object allocations for the common case of a group having no hooks.

This is only possible because this interface provides a “tell, don’t ask”-style API, so that callers tell this class what to do with the hooks, rather than asking this class for a list of hooks, and then doing something with them.

Constant Summary

Class Method Summary

Instance Method Summary

Instance Method Details

#all_hooks_for(position, scope) (protected)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 523

def all_hooks_for(position, scope)
  hooks_for(position, scope) { return EMPTY_HOOK_ARRAY }.items_and_filters.map(&:first)
end

#ensure_hooks_initialized_for(position, scope) (private)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 553

def ensure_hooks_initialized_for(position, scope)
  if position == :before
    if scope == :example
      @before_example_hooks ||= @filterable_item_repo_class.new(:all?)
    else
      @before_context_hooks ||= @filterable_item_repo_class.new(:all?)
    end
  elsif position == :after
    if scope == :example
      @after_example_hooks ||= @filterable_item_repo_class.new(:all?)
    else
      @after_context_hooks ||= @filterable_item_repo_class.new(:all?)
    end
  else # around
    @around_example_hooks ||= @filterable_item_repo_class.new(:all?)
  end
end

#extract_scope_from(args) (private)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 591

def extract_scope_from(args)
  if known_scope?(args.first)
    normalized_scope_for(args.shift)
  elsif args.any? { |a| a.is_a?(Symbol) }
    error_message = "You must explicitly give a scope " \
      "(#{SCOPES.join(", ")}) or scope alias " \
      "(#{SCOPE_ALIASES.keys.join(", ")}) when using symbols as " \
      "metadata for a hook."
    raise ArgumentError.new error_message
  else
    :example
  end
end

#hooks_for(position, scope) (private)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 543

def hooks_for(position, scope)
  if position == :before
    scope == :example ? @before_example_hooks : @before_context_hooks
  elsif position == :after
    scope == :example ? @after_example_hooks : @after_context_hooks
  else # around
    @around_example_hooks
  end || yield
end

#known_scope?(scope) ⇒ Boolean (private)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 605

def known_scope?(scope)
  SCOPES.include?(scope) || SCOPE_ALIASES.keys.include?(scope)
end

#matching_hooks_for(position, scope, example_or_group) (protected)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 507

def matching_hooks_for(position, scope, example_or_group)
  repository = hooks_for(position, scope) { return EMPTY_HOOK_ARRAY }

  # It would be nice to not have to switch on type here, but
  # we don't want to define `ExampleGroup#metadata` because then
  # `metadata` from within an individual example would return the
  # group's metadata but the user would probably expect it to be
  # the example's metadata.
   = case example_or_group
             when ExampleGroup then example_or_group.class.
             else example_or_group.
             end

  repository.items_for()
end

#normalized_scope_for(scope) (private)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 609

def normalized_scope_for(scope)
  SCOPE_ALIASES[scope] || scope
end

#owner_parent_groups (private)

:nocov:

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 638

def owner_parent_groups
  @owner.parent_groups
end

#process(host, parent_groups, globals, position, scope) (private)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 571

def process(host, parent_groups, globals, position, scope)
  hooks_to_process = globals.processable_hooks_for(position, scope, host)
  return if hooks_to_process.empty?

  hooks_to_process -= FlatMap.flat_map(parent_groups) do |group|
    group.hooks.all_hooks_for(position, scope)
  end
  return if hooks_to_process.empty?

  repository = ensure_hooks_initialized_for(position, scope)
  hooks_to_process.each { |hook| repository.append hook, (yield hook) }
end

#processable_hooks_for(position, scope, host) (protected)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 533

def processable_hooks_for(position, scope, host)
  if scope == :example
    all_hooks_for(position, scope)
  else
    matching_hooks_for(position, scope, host)
  end
end

#register(prepend_or_append, position, *args, &block)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 449

def register(prepend_or_append, position, *args, &block)
  scope, options = scope_and_options_from(*args)

  if scope == :suite
    # TODO: consider making this an error in RSpec 4. For SemVer reasons,
    # we are only warning in RSpec 3.
    RSpec.warn_with "WARNING: `#{position}(:suite)` hooks are only supported on " \
                    "the RSpec configuration object. This " \
                    "`#{position}(:suite)` hook, registered on an example " \
                    "group, will be ignored."
    return
  elsif scope == :context && position == :around
    # TODO: consider making this an error in RSpec 4. For SemVer reasons,
    # we are only warning in RSpec 3.
    RSpec.warn_with "WARNING: `around(:context)` hooks are not supported and " \
                    "behave like `around(:example)`."
  end

  hook = HOOK_TYPES[position][scope].new(block, options)
  ensure_hooks_initialized_for(position, scope).__send__(prepend_or_append, hook, options)
end

#register_global_singleton_context_hooks(example, globals)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 442

def register_global_singleton_context_hooks(example, globals)
  parent_groups = example.example_group.parent_groups

  process(example, parent_groups, globals, :before, :context) { {} }
  process(example, parent_groups, globals, :after,  :context) { {} }
end

#register_globals(host, globals)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 431

def register_globals(host, globals)
  parent_groups = host.parent_groups

  process(host, parent_groups, globals, :before, :example, &:options)
  process(host, parent_groups, globals, :after,  :example, &:options)
  process(host, parent_groups, globals, :around, :example, &:options)

  process(host, parent_groups, globals, :before, :context, &:options)
  process(host, parent_groups, globals, :after,  :context, &:options)
end

#run(position, scope, example_or_group)

Runs all of the blocks stored with the hook in the context of the example. If no example is provided, just calls the hook directly.

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 475

def run(position, scope, example_or_group)
  return if RSpec.configuration.dry_run?

  if scope == :context
    unless example_or_group.class.[:skip]
      run_owned_hooks_for(position, :context, example_or_group)
    end
  else
    case position
    when :before then run_example_hooks_for(example_or_group, :before, :reverse_each)
    when :after  then run_example_hooks_for(example_or_group, :after,  :each)
    when :around then run_around_example_hooks_for(example_or_group) { yield }
    end
  end
end

#run_around_example_hooks_for(example) (private)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 619

def run_around_example_hooks_for(example)
  hooks = FlatMap.flat_map(owner_parent_groups) do |group|
    group.hooks.matching_hooks_for(:around, :example, example)
  end

  return yield if hooks.empty? # exit early to avoid the extra allocation cost of `Example::Procsy`

  initial_procsy = Example::Procsy.new(example) { yield }
  hooks.inject(initial_procsy) do |procsy, around_hook|
    procsy.wrap { around_hook.execute_with(example, procsy) }
  end.call
end

#run_example_hooks_for(example, position, each_method) (private)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 613

def run_example_hooks_for(example, position, each_method)
  owner_parent_groups.__send__(each_method) do |group|
    group.hooks.run_owned_hooks_for(position, :example, example)
  end
end

#run_owned_hooks_for(position, scope, example_or_group) (protected)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 527

def run_owned_hooks_for(position, scope, example_or_group)
  matching_hooks_for(position, scope, example_or_group).each do |hook|
    hook.run(example_or_group)
  end
end

#scope_and_options_from(*args) (private)

[ GitHub ]

  
# File 'rspec-core/lib/rspec/core/hooks.rb', line 584

def scope_and_options_from(*args)
  return :suite if args.first == :suite
  scope = extract_scope_from(args)
  meta  = Metadata.build_hash_from(args, :warn_about_example_group_filtering)
  return scope, meta
end