Class: RSpec::Core::Hooks::HookCollections Private
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
-
EMPTY_HOOK_ARRAY =
# File 'rspec-core/lib/rspec/core/hooks.rb', line 505[].freeze
-
HOOK_TYPES =
# File 'rspec-core/lib/rspec/core/hooks.rb', line 495{ :before => Hash.new { BeforeHook }, :after => Hash.new { AfterHook }, :around => Hash.new { AroundHook } }
-
SCOPES =
# File 'rspec-core/lib/rspec/core/hooks.rb', line 491[:example, :context]
-
SCOPE_ALIASES =
# File 'rspec-core/lib/rspec/core/hooks.rb', line 493{ :each => :example, :all => :context }
Class Method Summary
- .new(owner, filterable_item_repo_class) ⇒ HookCollections constructor Internal use only
Instance Method Summary
- #register(prepend_or_append, position, *args, &block) Internal use only
- #register_global_singleton_context_hooks(example, globals) Internal use only
- #register_globals(host, globals) Internal use only
-
#run(position, scope, example_or_group)
Internal use only
Runs all of the blocks stored with the hook in the context of the example.
- #all_hooks_for(position, scope) protected Internal use only
- #matching_hooks_for(position, scope, example_or_group) protected Internal use only
- #processable_hooks_for(position, scope, host) protected Internal use only
- #run_owned_hooks_for(position, scope, example_or_group) protected Internal use only
- #ensure_hooks_initialized_for(position, scope) private Internal use only
- #extract_scope_from(args) private Internal use only
- #hooks_for(position, scope) private Internal use only
- #known_scope?(scope) ⇒ Boolean private Internal use only
- #normalized_scope_for(scope) private Internal use only
-
#owner_parent_groups
private
Internal use only
:nocov:
- #process(host, parent_groups, globals, position, scope) private Internal use only
- #run_around_example_hooks_for(example) private Internal use only
- #run_example_hooks_for(example, position, each_method) private Internal use only
- #scope_and_options_from(*args) private Internal use only
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) } = "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 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)
# 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:
# 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, = (*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, ) ensure_hooks_initialized_for(position, scope).__send__(prepend_or_append, hook, ) 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, &: ) process(host, parent_groups, globals, :after, :example, &: ) process(host, parent_groups, globals, :around, :example, &: ) process(host, parent_groups, globals, :before, :context, &: ) process(host, parent_groups, globals, :after, :context, &: ) 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.
# 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 (*args) return :suite if args.first == :suite scope = extract_scope_from(args) = Metadata.build_hash_from(args, :warn_about_example_group_filtering) return scope, end