Skip to content

Commit

Permalink
Revert "Reify shared example groups as a first-class type."
Browse files Browse the repository at this point in the history
This reverts commit d2633ab.

I realized this broke SemVer by renaming `SharedExampleGroup`
to `SharedExampleGroup::DefinitionAPI`. I'm going to re-work it
in the next commit from scratch.
  • Loading branch information
myronmarston committed Nov 5, 2014
1 parent d129481 commit 9653904
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 113 deletions.
13 changes: 6 additions & 7 deletions lib/rspec/core/example_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@ module Core
#
# Besides the class methods defined here, there are other interesting macros
# defined in {Hooks}, {MemoizedHelpers::ClassMethods} and
# {SharedExampleGroup::DefinitionAPI}. There are additional instance
# methods available to your examples defined in {MemoizedHelpers} and
# {Pending}.
# {SharedExampleGroup}. There are additional instance methods available to
# your examples defined in {MemoizedHelpers} and {Pending}.
class ExampleGroup
extend Hooks

include MemoizedHelpers
extend MemoizedHelpers::ClassMethods
include Pending
extend SharedExampleGroup::DefinitionAPI
extend SharedExampleGroup

unless respond_to?(:define_singleton_method)
# @private
Expand Down Expand Up @@ -284,7 +283,7 @@ def self.define_example_group_method(name, metadata={})
# @macro [attach] define_nested_shared_group_method
# @!scope class
#
# @see SharedExampleGroup::DefinitionAPI
# @see SharedExampleGroup
def self.define_nested_shared_group_method(new_name, report_label="it should behave like")
define_singleton_method(new_name) do |name, *args, &customization_block|
# Pass :caller so the :location metadata is set properly.
Expand All @@ -310,7 +309,7 @@ def self.define_nested_shared_group_method(new_name, report_label="it should beh
# group. If given a block, that block is also eval'd in the current
# context.
#
# @see SharedExampleGroup::DefinitionAPI
# @see SharedExampleGroup
def self.include_context(name, *args, &block)
find_and_eval_shared("context", name, *args, &block)
end
Expand All @@ -320,7 +319,7 @@ def self.include_context(name, *args, &block)
# group. If given a block, that block is also eval'd in the current
# context.
#
# @see SharedExampleGroup::DefinitionAPI
# @see SharedExampleGroup
def self.include_examples(name, *args, &block)
find_and_eval_shared("examples", name, *args, &block)
end
Expand Down
4 changes: 2 additions & 2 deletions lib/rspec/core/hooks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ module Hooks
# @see #around
# @see ExampleGroup
# @see SharedContext
# @see SharedExampleGroup::DefinitionAPI
# @see SharedExampleGroup
# @see Configuration
#
# Declare a block of code to be run before each example (using `:example`)
Expand Down Expand Up @@ -220,7 +220,7 @@ def prepend_before(*args, &block)
# @see #around
# @see ExampleGroup
# @see SharedContext
# @see SharedExampleGroup::DefinitionAPI
# @see SharedExampleGroup
# @see Configuration
#
# Declare a block of code to be run after each example (using `:example`)
Expand Down
149 changes: 65 additions & 84 deletions lib/rspec/core/shared_example_group.rb
Original file line number Diff line number Diff line change
@@ -1,92 +1,68 @@
module RSpec
module Core
# Represents some functionality that is shared with multiple example groups.
# The functionality is defined by the provided block, which is lazily
# eval'd when the `SharedExampleGroup` instance is included in an example
# group.
class SharedExampleGroup < Module
def initialize(description, definition)
@description = description
@definition = definition
end

# Provides a human-readable representation of this module.
def inspect
"#<#{self.class.name} #{@description.inspect}>"
end
alias to_s inspect

# Ruby callback for when a module is included in another module is class.
# Our definition evaluates the shared group block in the context of the
# including example group.
def included(klass)
klass.class_exec(&@definition)
end

# Shared example groups let you define common context and/or common
# examples that you wish to use in multiple example groups.
# Shared example groups let you define common context and/or common
# examples that you wish to use in multiple example groups.
#
# When defined, the shared group block is stored for later evaluation.
# It can later be included in an example group either explicitly
# (using `include_examples`, `include_context` or `it_behaves_like`)
# or implicitly (via matching metadata).
#
# Named shared example groups are scoped based on where they are
# defined. Shared groups defined in an example group are available
# for inclusion in that example group or any child example groups,
# but not in any parent or sibling example groups. Shared example
# groups defined at the top level can be included from any example group.
module SharedExampleGroup
# @overload shared_examples(name, &block)
# @param name [String, Symbol, Module] identifer to use when looking up
# this shared group
# @param block The block to be eval'd
# @overload shared_examples(name, metadata, &block)
# @param name [String, Symbol, Module] identifer to use when looking up
# this shared group
# @param metadata [Array<Symbol>, Hash] metadata to attach to this
# group; any example group with matching metadata will automatically
# include this shared example group.
# @param block The block to be eval'd
# @overload shared_examples(metadata, &block)
# @param metadata [Array<Symbol>, Hash] metadata to attach to this
# group; any example group with matching metadata will automatically
# include this shared example group.
# @param block The block to be eval'd
#
# When defined, the shared group block is stored for later evaluation.
# It can later be included in an example group either explicitly
# (using `include_examples`, `include_context` or `it_behaves_like`)
# or implicitly (via matching metadata).
# Stores the block for later use. The block will be evaluated
# in the context of an example group via `include_examples`,
# `include_context`, or `it_behaves_like`.
#
# Named shared example groups are scoped based on where they are
# defined. Shared groups defined in an example group are available
# for inclusion in that example group or any child example groups,
# but not in any parent or sibling example groups. Shared example
# groups defined at the top level can be included from any example group.
module DefinitionAPI
# @overload shared_examples(name, &block)
# @param name [String, Symbol, Module] identifer to use when looking up
# this shared group
# @param block The block to be eval'd
# @overload shared_examples(name, metadata, &block)
# @param name [String, Symbol, Module] identifer to use when looking up
# this shared group
# @param metadata [Array<Symbol>, Hash] metadata to attach to this
# group; any example group with matching metadata will automatically
# include this shared example group.
# @param block The block to be eval'd
# @overload shared_examples(metadata, &block)
# @param metadata [Array<Symbol>, Hash] metadata to attach to this
# group; any example group with matching metadata will automatically
# include this shared example group.
# @param block The block to be eval'd
#
# Stores the block for later use. The block will be evaluated
# in the context of an example group via `include_examples`,
# `include_context`, or `it_behaves_like`.
#
# @example
# shared_examples "auditable" do
# it "stores an audit record on save!" do
# expect { auditable.save! }.to change(Audit, :count).by(1)
# end
# end
#
# describe Account do
# it_behaves_like "auditable" do
# let(:auditable) { Account.new }
# end
# end
#
# @see ExampleGroup.it_behaves_like
# @see ExampleGroup.include_examples
# @see ExampleGroup.include_context
def shared_examples(name, *args, &block)
top_level = self == ExampleGroup
if top_level && RSpec.thread_local_metadata[:in_example_group]
raise "Creating isolated shared examples from within a context is " \
"not allowed. Remove `RSpec.` prefix or move this to a " \
"top-level scope."
end

RSpec.world.shared_example_group_registry.add(self, name, *args, &block)
# @example
# shared_examples "auditable" do
# it "stores an audit record on save!" do
# expect { auditable.save! }.to change(Audit, :count).by(1)
# end
# end
#
# describe Account do
# it_behaves_like "auditable" do
# let(:auditable) { Account.new }
# end
# end
#
# @see ExampleGroup.it_behaves_like
# @see ExampleGroup.include_examples
# @see ExampleGroup.include_context
def shared_examples(name, *args, &block)
top_level = self == ExampleGroup
if top_level && RSpec.thread_local_metadata[:in_example_group]
raise "Creating isolated shared examples from within a context is " \
"not allowed. Remove `RSpec.` prefix or move this to a " \
"top-level scope."
end
alias shared_context shared_examples
alias shared_examples_for shared_examples

RSpec.world.shared_example_group_registry.add(self, name, *args, &block)
end
alias shared_context shared_examples
alias shared_examples_for shared_examples

# @api private
#
Expand Down Expand Up @@ -146,7 +122,12 @@ def add(context, name, *metadata_args, &block)
end

return if metadata_args.empty?
RSpec.configuration.include SharedExampleGroup.new(name, block), *metadata_args

mod = Module.new
(class << mod; self; end).__send__(:define_method, :included) do |host|
host.class_exec(&block)
end
RSpec.configuration.include mod, *metadata_args
end

def find(lookup_contexts, name)
Expand Down
19 changes: 1 addition & 18 deletions spec/rspec/core/shared_example_group_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module Core
ExampleClass = Class.new

it 'does not add a bunch of private methods to Module' do
seg_methods = RSpec::Core::SharedExampleGroup::DefinitionAPI.private_instance_methods
seg_methods = RSpec::Core::SharedExampleGroup.private_instance_methods
expect(Module.private_methods & seg_methods).to eq([])
end

Expand Down Expand Up @@ -74,23 +74,6 @@ module Core
ExampleGroup.describe('example group') { include_context 'top level in module' }
end

it 'generates a named (rather than anonymous) module' do
define_shared_group("shared behaviors", :include_it) { }
group = RSpec.describe("Group", :include_it) { }

anonymous_module_regex = /#<Module:0x[0-9a-f]+>/
expect(Module.new.inspect).to match(anonymous_module_regex)

include_a_named_rather_than_anonymous_module = (
include(a_string_including(
"#<RSpec::Core::SharedExampleGroup", "shared behaviors"
)).and exclude(a_string_matching(anonymous_module_regex))
)

expect(group.ancestors.map(&:inspect)).to include_a_named_rather_than_anonymous_module
expect(group.ancestors.map(&:to_s)).to include_a_named_rather_than_anonymous_module
end

["name", :name, ExampleModule, ExampleClass].each do |object|
type = object.class.name.downcase
context "given a #{type}" do
Expand Down
1 change: 0 additions & 1 deletion spec/support/matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,3 @@ def failure_reason(example)
RSpec::Matchers.alias_matcher :a_file_collection, :contain_files

RSpec::Matchers.define_negated_matcher :avoid_outputting, :output
RSpec::Matchers.define_negated_matcher :exclude, :include
2 changes: 1 addition & 1 deletion spec/support/sandboxing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def self.sandboxed(&block)
RSpec.configuration = new_config
RSpec.world = new_world
object = Object.new
object.extend(RSpec::Core::SharedExampleGroup::DefinitionAPI)
object.extend(RSpec::Core::SharedExampleGroup)

(class << RSpec::Core::ExampleGroup; self; end).class_exec do
alias_method :orig_run, :run
Expand Down

0 comments on commit 9653904

Please sign in to comment.