Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
pirj committed Dec 30, 2020
1 parent 0c3a9d8 commit 684b584
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 72 deletions.
136 changes: 76 additions & 60 deletions lib/rspec/core/example_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def self.idempotently_define_singleton_method(name, &definition)
(class << self; self; end).module_exec do
remove_method(name) if method_defined?(name) && instance_method(name).owner == self
define_method(name, &definition)
ruby2_keywords name if respond_to?(:ruby2_keywords, true)
end
end

Expand Down Expand Up @@ -268,7 +269,7 @@ def self.define_example_group_method(name, metadata={})
combined_metadata.merge!(args.pop) if args.last.is_a? Hash
args << combined_metadata

subclass(self, description, args, registration_collection, &example_group_block)
subclass(self, description, registration_collection, *args, &example_group_block)
ensure
thread_data.delete(:in_example_group) if top_level
end
Expand Down Expand Up @@ -340,8 +341,11 @@ def self.define_nested_shared_group_method(new_name, report_label="it should beh
# context.
#
# @see SharedExampleGroup
def self.include_context(name, *args, &block)
find_and_eval_shared("context", name, caller.first, *args, &block)
class << self
def include_context(name, *args, &block)
find_and_eval_shared("context", name, caller.first, *args, &block)
end
ruby2_keywords :include_context if respond_to?(:ruby2_keywords, true)
end

# Includes shared content mapped to `name` directly in the group in which
Expand All @@ -350,8 +354,11 @@ def self.include_context(name, *args, &block)
# context.
#
# @see SharedExampleGroup
def self.include_examples(name, *args, &block)
find_and_eval_shared("examples", name, caller.first, *args, &block)
class << self
def include_examples(name, *args, &block)
find_and_eval_shared("examples", name, caller.first, *args, &block)
end
ruby2_keywords :include_examples if respond_to?(:ruby2_keywords, true)
end

# Clear memoized values when adding/removing examples
Expand All @@ -376,74 +383,83 @@ def self.remove_example(example)
end

# @private
def self.find_and_eval_shared(label, name, inclusion_location, *args, &customization_block)
shared_module = RSpec.world.shared_example_group_registry.find(parent_groups, name)
class << self
def find_and_eval_shared(label, name, inclusion_location, *args, &customization_block)
shared_module = RSpec.world.shared_example_group_registry.find(parent_groups, name)

unless shared_module
raise ArgumentError, "Could not find shared #{label} #{name.inspect}"
end
unless shared_module
raise ArgumentError, "Could not find shared #{label} #{name.inspect}"
end

shared_module.include_in(
self, Metadata.relative_path(inclusion_location),
args, customization_block
)
shared_module.include_in(
self, Metadata.relative_path(inclusion_location),
args, customization_block
)
end
ruby2_keywords :find_and_eval_shared if respond_to?(:ruby2_keywords, true)
end

# @!endgroup

# @private
def self.subclass(parent, description, args, registration_collection, &example_group_block)
subclass = Class.new(parent)
subclass.set_it_up(description, args, registration_collection, &example_group_block)
subclass.module_exec(&example_group_block) if example_group_block

# The LetDefinitions module must be included _after_ other modules
# to ensure that it takes precedence when there are name collisions.
# Thus, we delay including it until after the example group block
# has been eval'd.
MemoizedHelpers.define_helpers_on(subclass)

subclass
end

# @private
def self.set_it_up(description, args, registration_collection, &example_group_block)
# Ruby 1.9 has a bug that can lead to infinite recursion and a
# SystemStackError if you include a module in a superclass after
# including it in a subclass: https://gist.github.com/845896
# To prevent this, we must include any modules in
# RSpec::Core::ExampleGroup before users create example groups and have
# a chance to include the same module in a subclass of
# RSpec::Core::ExampleGroup. So we need to configure example groups
# here.
ensure_example_groups_are_configured

# Register the example with the group before creating the metadata hash.
# This is necessary since creating the metadata hash triggers
# `when_first_matching_example_defined` callbacks, in which users can
# load RSpec support code which defines hooks. For that to work, the
# examples and example groups must be registered at the time the
# support code is called or be defined afterwards.
# Begin defined beforehand but registered afterwards causes hooks to
# not be applied where they should.
registration_collection << self
class << self
def subclass(parent, description, registration_collection, *args, &example_group_block)
subclass = Class.new(parent)
subclass.set_it_up(description, args, registration_collection, &example_group_block)
subclass.module_exec(&example_group_block) if example_group_block

@user_metadata = Metadata.build_hash_from(args)
# The LetDefinitions module must be included _after_ other modules
# to ensure that it takes precedence when there are name collisions.
# Thus, we delay including it until after the example group block
# has been eval'd.
MemoizedHelpers.define_helpers_on(subclass)

@metadata = Metadata::ExampleGroupHash.create(
superclass_metadata, @user_metadata,
superclass.method(:next_runnable_index_for),
description, *args, &example_group_block
)
subclass
end
ruby2_keywords :subclass if respond_to?(:ruby2_keywords, true)
end

# @private
class << self
def set_it_up(description, args, registration_collection, &example_group_block)
# Ruby 1.9 has a bug that can lead to infinite recursion and a
# SystemStackError if you include a module in a superclass after
# including it in a subclass: https://gist.github.com/845896
# To prevent this, we must include any modules in
# RSpec::Core::ExampleGroup before users create example groups and have
# a chance to include the same module in a subclass of
# RSpec::Core::ExampleGroup. So we need to configure example groups
# here.
ensure_example_groups_are_configured

# Register the example with the group before creating the metadata hash.
# This is necessary since creating the metadata hash triggers
# `when_first_matching_example_defined` callbacks, in which users can
# load RSpec support code which defines hooks. For that to work, the
# examples and example groups must be registered at the time the
# support code is called or be defined afterwards.
# Begin defined beforehand but registered afterwards causes hooks to
# not be applied where they should.
registration_collection << self

@user_metadata = Metadata.build_hash_from(args)

@metadata = Metadata::ExampleGroupHash.create(
superclass_metadata, @user_metadata,
superclass.method(:next_runnable_index_for),
description, *args, &example_group_block
)

config = RSpec.configuration
config.apply_derived_metadata_to(@metadata)
config = RSpec.configuration
config.apply_derived_metadata_to(@metadata)

ExampleGroups.assign_const(self)
ExampleGroups.assign_const(self)

@currently_executing_a_context_hook = false
@currently_executing_a_context_hook = false

config.configure_group(self)
config.configure_group(self)
end
ruby2_keywords :set_it_up if respond_to?(:ruby2_keywords, true)
end

# @private
Expand Down
21 changes: 12 additions & 9 deletions lib/rspec/core/metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -245,17 +245,20 @@ def full_description

# @private
class ExampleGroupHash < HashPopulator
def self.create(parent_group_metadata, user_metadata, example_group_index, *args, &block)
group_metadata = hash_with_backwards_compatibility_default_proc
class << self
def create(parent_group_metadata, user_metadata, example_group_index, *args, &block)
group_metadata = hash_with_backwards_compatibility_default_proc

if parent_group_metadata
group_metadata.update(parent_group_metadata)
group_metadata[:parent_example_group] = parent_group_metadata
end
if parent_group_metadata
group_metadata.update(parent_group_metadata)
group_metadata[:parent_example_group] = parent_group_metadata
end

hash = new(group_metadata, user_metadata, example_group_index, args, block)
hash.populate
hash.metadata
hash = new(group_metadata, user_metadata, example_group_index, args, block)
hash.populate
hash.metadata
end
ruby2_keywords :create if respond_to?(:ruby2_keywords, true)
end

def self.hash_with_backwards_compatibility_default_proc
Expand Down
4 changes: 1 addition & 3 deletions lib/rspec/core/shared_example_group.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
RSpec::Support.require_rspec_support "with_keywords_when_needed"

module RSpec
module Core
# Represents some functionality that is shared with multiple example groups.
Expand Down Expand Up @@ -35,7 +33,7 @@ def include_in(klass, inclusion_line, args, customization_block)
klass.update_inherited_metadata(@metadata) unless @metadata.empty?

SharedExampleGroupInclusionStackFrame.with_frame(@description, inclusion_line) do
RSpec::Support::WithKeywordsWhenNeeded.class_exec(klass, *args, &@definition)
klass.class_exec(*args, &@definition)
klass.class_exec(&customization_block) if customization_block
end
end
Expand Down

0 comments on commit 684b584

Please sign in to comment.