class RSpec::Core::Hooks::HookCollections
asking this class for a list of hooks, and then doing something with them.
API, so that callers tell this class what to do with the hooks, rather than
This is only possible because this interface provides a “tell, don’t ask”-style
case of a group having no hooks.
a hook is added. This allows us to avoid many object allocations for the common
this, so that, for example, hook collection objects are only instantiated when
implementation details behind this facade, it’s allowed us to heavily optimize
This provides the primary API used by other parts of rspec-core. By hiding all
@private
def all_hooks_for(position, scope)
def all_hooks_for(position, scope) hooks_for(position, scope) { return EMPTY_HOOK_ARRAY }.items_and_filters.map(&:first) end
def ensure_hooks_initialized_for(position, scope)
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
def extract_scope_from(args)
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
def hooks_for(position, scope)
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
def initialize(owner, filterable_item_repo_class)
def initialize(owner, filterable_item_repo_class) @owner = owner @filterable_item_repo_class = filterable_item_repo_class @before_example_hooks = nil @after_example_hooks = nil @before_context_hooks = nil @after_context_hooks = nil @around_example_hooks = nil end
def known_scope?(scope)
def known_scope?(scope) SCOPES.include?(scope) || SCOPE_ALIASES.keys.include?(scope) end
def matching_hooks_for(position, scope, example_or_group)
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. metadata = case example_or_group when ExampleGroup then example_or_group.class.metadata else example_or_group.metadata end repository.items_for(metadata) end
def normalized_scope_for(scope)
def normalized_scope_for(scope) SCOPE_ALIASES[scope] || scope end
def owner_parent_groups
def owner_parent_groups @owner.parent_groups end
def owner_parent_groups
Ruby < 2.1 (see https://bugs.ruby-lang.org/issues/8035)
def owner_parent_groups @owner_parent_groups ||= [@owner] + @owner.parent_groups end
def process(host, parent_groups, globals, position, scope)
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
def processable_hooks_for(position, scope, host)
def processable_hooks_for(position, scope, host) if scope == :example all_hooks_for(position, scope) else matching_hooks_for(position, scope, host) end end
def register(prepend_or_append, position, *args, &block)
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 end hook = HOOK_TYPES[position][scope].new(block, options) ensure_hooks_initialized_for(position, scope).__send__(prepend_or_append, hook, options) end
def register_global_singleton_context_hooks(example, globals)
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
def register_globals(host, globals)
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
def run(position, scope, example_or_group)
- Private: -
def run(position, scope, example_or_group) return if RSpec.configuration.dry_run? if scope == :context unless example_or_group.class.metadata[: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
def run_around_example_hooks_for(example)
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
def run_example_hooks_for(example, position, each_method)
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
def run_owned_hooks_for(position, scope, example_or_group)
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
def scope_and_options_from(*args)
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