Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Apply module/shared context inclusion to individual examples #1749

Merged
merged 14 commits into from
Jan 20, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmarks/module_inclusion_filtering.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def include(mod, *filters)

def old_configure_group(group)
@include_extend_or_prepend_modules.each do |include_extend_or_prepend, mod, filters|
next unless filters.empty? || group.apply?(:any?, filters)
next unless filters.empty? || RSpec::Core::MetadataFilter.apply?(:any?, filters, group.metadata)
__send__("safe_#{include_extend_or_prepend}", mod, group)
end
end
Expand Down
120 changes: 120 additions & 0 deletions benchmarks/singleton_example_groups/helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
require_relative "../../bundle/bundler/setup" # configures load paths
require 'rspec/core'
require 'stackprof'

class << RSpec
attr_writer :world
end

RSpec::Core::Example.class_eval do
alias_method :new_with_around_and_singleton_context_hooks, :with_around_and_singleton_context_hooks
alias_method :old_with_around_and_singleton_context_hooks, :with_around_example_hooks
end

RSpec::Core::Hooks::HookCollections.class_eval do
def old_register_global_singleton_context_hooks(*)
# no-op: this method didn't exist before
end
alias_method :new_register_global_singleton_context_hooks, :register_global_singleton_context_hooks
end

RSpec::Core::Configuration.class_eval do
def old_configure_example(*)
# no-op: this method didn't exist before
end
alias_method :new_configure_example, :configure_example
end

RSpec.configure do |c|
c.output_stream = StringIO.new
end

require 'benchmark/ips'

class BenchmarkHelpers
def self.prepare_implementation(prefix)
RSpec.world = RSpec::Core::World.new # clear our state
RSpec::Core::Example.__send__ :alias_method, :with_around_and_singleton_context_hooks, :"#{prefix}_with_around_and_singleton_context_hooks"
RSpec::Core::Hooks::HookCollections.__send__ :alias_method, :register_global_singleton_context_hooks, :"#{prefix}_register_global_singleton_context_hooks"
RSpec::Core::Configuration.__send__ :alias_method, :configure_example, :"#{prefix}_configure_example"
end

@@runner = RSpec::Core::Runner.new(RSpec::Core::ConfigurationOptions.new([]))
def self.define_and_run_examples(desc, count, group_meta: {}, example_meta: {})
groups = count.times.map do |i|
RSpec.describe "Group #{desc} #{i}", group_meta do
10.times { |j| example("ex #{j}", example_meta) { } }
end
end

@@runner.run_specs(groups)
end

def self.profile(count, meta = { example_meta: { apply_it: true } })
[:new, :old].map do |prefix|
prepare_implementation(prefix)

results = StackProf.run(mode: :cpu) do
define_and_run_examples("No match/#{prefix}", count, meta)
end

format_profile_results(results, prefix)
end
end

def self.format_profile_results(results, prefix)
File.open("tmp/#{prefix}_stack_prof_results.txt", "w") do |f|
StackProf::Report.new(results).print_text(false, nil, f)
end
system "open tmp/#{prefix}_stack_prof_results.txt"

File.open("tmp/#{prefix}_stack_prof_results.graphviz", "w") do |f|
StackProf::Report.new(results).print_graphviz(nil, f)
end

system "dot tmp/#{prefix}_stack_prof_results.graphviz -Tpdf > tmp/#{prefix}_stack_prof_results.pdf"
system "open tmp/#{prefix}_stack_prof_results.pdf"
end

def self.run_benchmarks
Benchmark.ips do |x|
implementations = { :old => "without", :new => "with" }
# Historically, many of our benchmarks have initially been order-sensitive,
# where whichever implementation went first got favored because defining
# more groups (or whatever) would cause things to slow down. To easily
# check if we're having those problems, you can pass REVERSE=1 to try
# it out in the opposite order.
implementations = implementations.to_a.reverse.to_h if ENV['REVERSE']

implementations.each do |prefix, description|
x.report("No match -- #{description} singleton group support") do |times|
prepare_implementation(prefix)
define_and_run_examples("No match/#{description}", times)
end
end

implementations.each do |prefix, description|
x.report("Example match -- #{description} singleton group support") do |times|
prepare_implementation(prefix)
define_and_run_examples("Example match/#{description}", times, example_meta: { apply_it: true })
end
end

implementations.each do |prefix, description|
x.report("Group match -- #{description} singleton group support") do |times|
prepare_implementation(prefix)
define_and_run_examples("Group match/#{description}", times, group_meta: { apply_it: true })
end
end

implementations.each do |prefix, description|
x.report("Both match -- #{description} singleton group support") do |times|
prepare_implementation(prefix)
define_and_run_examples("Both match/#{description}", times,
example_meta: { apply_it: true },
group_meta: { apply_it: true })
end
end
end
end
end
28 changes: 28 additions & 0 deletions benchmarks/singleton_example_groups/with_config_hooks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require_relative "helper"

RSpec.configure do |c|
10.times do
c.before(:context, :apply_it) { }
c.after(:context, :apply_it) { }
end
end

BenchmarkHelpers.run_benchmarks

__END__
No match -- without singleton group support
575.250 (±29.0%) i/s - 2.484k
No match -- with singleton group support
503.671 (±21.8%) i/s - 2.250k
Example match -- without singleton group support
544.191 (±25.7%) i/s - 2.160k
Example match -- with singleton group support
413.538 (±22.2%) i/s - 1.715k
Group match -- without singleton group support
517.998 (±28.2%) i/s - 2.058k
Group match -- with singleton group support
431.554 (±15.3%) i/s - 1.960k
Both match -- without singleton group support
525.306 (±25.1%) i/s - 2.107k in 5.556760s
Both match -- with singleton group support
440.288 (±16.6%) i/s - 1.848k
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require_relative "helper"

RSpec.configure do |c|
10.times do
c.before(:context, :apply_it) { }
c.after(:context, :apply_it) { }
c.include Module.new, :apply_it
end
end

1.upto(10) do |i|
RSpec.shared_context "context #{i}", :apply_it do
end
end

BenchmarkHelpers.run_benchmarks

__END__

No match -- without singleton group support
544.396 (±34.0%) i/s - 2.340k
No match -- with singleton group support
451.635 (±31.0%) i/s - 1.935k
Example match -- without singleton group support
538.788 (±23.8%) i/s - 2.450k
Example match -- with singleton group support
342.990 (±22.4%) i/s - 1.440k
Group match -- without singleton group support
509.969 (±26.7%) i/s - 2.070k
Group match -- with singleton group support
405.284 (±20.5%) i/s - 1.518k
Both match -- without singleton group support
513.344 (±24.0%) i/s - 1.927k
Both match -- with singleton group support
406.111 (±18.5%) i/s - 1.760k
28 changes: 28 additions & 0 deletions benchmarks/singleton_example_groups/with_module_inclusions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require_relative "helper"

RSpec.configure do |c|
1.upto(10) do
c.include Module.new, :apply_it
end
end

BenchmarkHelpers.run_benchmarks

__END__

No match -- without singleton group support
555.498 (±27.0%) i/s - 2.496k
No match -- with singleton group support
529.826 (±23.0%) i/s - 2.397k in 5.402305s
Example match -- without singleton group support
541.845 (±29.0%) i/s - 2.208k
Example match -- with singleton group support
465.440 (±20.4%) i/s - 2.091k
Group match -- without singleton group support
530.976 (±24.1%) i/s - 2.303k
Group match -- with singleton group support
505.291 (±18.8%) i/s - 2.226k
Both match -- without singleton group support
542.168 (±28.4%) i/s - 2.067k in 5.414905s
Both match -- with singleton group support
503.226 (±27.2%) i/s - 1.880k in 5.621210s
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require_relative "helper"

BenchmarkHelpers.run_benchmarks

__END__

No match -- without singleton group support
565.198 (±28.8%) i/s - 2.438k
No match -- with singleton group support
539.781 (±18.9%) i/s - 2.496k
Example match -- without singleton group support
539.287 (±28.2%) i/s - 2.450k in 5.555471s
Example match -- with singleton group support
511.576 (±28.1%) i/s - 2.058k
Group match -- without singleton group support
535.298 (±23.2%) i/s - 2.352k
Group match -- with singleton group support
539.454 (±19.1%) i/s - 2.350k
Both match -- without singleton group support
550.932 (±32.1%) i/s - 2.145k in 5.930432s
Both match -- with singleton group support
540.183 (±19.6%) i/s - 2.300k
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require_relative "helper"

1.upto(10) do |i|
RSpec.shared_context "context #{i}", :apply_it do
end
end

BenchmarkHelpers.run_benchmarks
# BenchmarkHelpers.profile(1000)

__END__

No match -- without singleton group support
563.304 (±29.6%) i/s - 2.385k
No match -- with singleton group support
538.738 (±22.3%) i/s - 2.209k
Example match -- without singleton group support
546.605 (±25.6%) i/s - 2.450k
Example match -- with singleton group support
421.111 (±23.5%) i/s - 1.845k
Group match -- without singleton group support
536.267 (±27.4%) i/s - 2.050k
Group match -- with singleton group support
508.644 (±17.7%) i/s - 2.268k
Both match -- without singleton group support
538.047 (±27.7%) i/s - 2.067k in 5.431649s
Both match -- with singleton group support
505.388 (±26.7%) i/s - 1.880k in 5.578614s
20 changes: 20 additions & 0 deletions features/example_groups/shared_context.feature
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Feature: shared context
of example groups either explicitly, using `include_context`, or implicitly by
matching metadata.

When implicitly including shared contexts via matching metadata, the normal way is to define matching metadata on an example group (in which case the ontext is included in the entire group), but you can also include it in an individual example. RSpec treats every example as having a singleton example group (analogous to Ruby's singleton classes) containing just the one example.

Background:
Given a file named "shared_stuff.rb" with:
"""ruby
Expand Down Expand Up @@ -90,3 +92,21 @@ Feature: shared context
"""
When I run `rspec shared_context_example.rb`
Then the examples should all pass

Scenario: Declare a shared context and include it with metadata of an individual example
Given a file named "shared_context_example.rb" with:
"""ruby
require "./shared_stuff.rb"

RSpec.describe "group that does not include the shared context" do
it "does not have access to shared methods normally" do
expect(self).not_to respond_to(:shared_method)
end

it "has access to shared methods from examples with matching metadata", :a => :b do
expect(shared_method).to eq("it works")
end
end
"""
When I run `rspec shared_context_example.rb`
Then the examples should all pass
6 changes: 6 additions & 0 deletions features/helper_methods/modules.feature
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Feature: Define helper methods in a module
given metadata will `include` or `extend` the module. You can also specify
metadata using only symbols.

Note that examples that match a `config.include` module's metadata will also have the module included. RSpec treats every example as having a singleton example group (analogous to Ruby's singleton classes) containing just the one example.

Background:
Given a file named "helpers.rb" with:
"""ruby
Expand Down Expand Up @@ -79,6 +81,10 @@ Feature: Define helper methods in a module
it "does not have access to the helper methods defined in the module" do
expect { help }.to raise_error(NameError)
end

it "does have access when the example has matching metadata", :foo => :bar do
expect(help).to be(:available)
end
end
"""
When I run `rspec include_module_in_some_groups_spec.rb`
Expand Down
20 changes: 16 additions & 4 deletions features/hooks/filtering.feature
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Feature: filters
end
```

Note that filtered `:context` hooks will still be applied to individual examples with matching metadata -- in effect, every example has a singleton example group containing just the one example (analogous to Ruby's singleton classes).

You can also specify metadata using only symbols.

Scenario: Filter `before(:example)` hooks using arbitrary metadata
Expand Down Expand Up @@ -127,6 +129,10 @@ Feature: filters
expect(@hook).to be_nil
end

it "runs the hook for a single example with matching metadata", :foo => :bar do
expect(@hook).to eq(:before_context_foo_bar)
end

describe "a nested subgroup with matching metadata", :foo => :bar do
it "runs the hook" do
expect(@hook).to eq(:before_context_foo_bar)
Expand Down Expand Up @@ -166,31 +172,37 @@ Feature: filters
it "does not run the hook" do
puts "unfiltered"
end

it "runs the hook for a single example with matching metadata", :foo => :bar do
puts "filtered 1"
end
end

describe "a group with matching metadata", :foo => :bar do
it "runs the hook" do
puts "filtered 1"
puts "filtered 2"
end
end

describe "another group without matching metadata" do
describe "a nested subgroup with matching metadata", :foo => :bar do
it "runs the hook" do
puts "filtered 2"
puts "filtered 3"
end
end
end
end
"""
When I run `rspec --format progress filter_after_context_hooks_spec.rb`
When I run `rspec --format progress filter_after_context_hooks_spec.rb --order defined`
Then the examples should all pass
And the output should contain:
"""
unfiltered
.filtered 1
after :context
.filtered 2
.after :context
filtered 2
filtered 3
.after :context
"""

Expand Down
Loading