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

Commit c3aa023

Browse files
committed
Implement RSpec.current_scope w/ introspection
Demonstrating possible extension
1 parent 132f5e1 commit c3aa023

File tree

5 files changed

+151
-0
lines changed

5 files changed

+151
-0
lines changed

lib/rspec/core.rb

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,57 @@ def self.current_example=(example)
129129
RSpec::Support.thread_local_data[:current_example] = example
130130
end
131131

132+
class ScopeSymbol
133+
# @api private
134+
EXAMPLE_CONTEXT = [:before_each_hook, :example, :after_each_hook]
135+
136+
# @api private
137+
# Used whenever you set RSpec.current_scope = :something
138+
def self.wrap(thing)
139+
thing.is_a?(self) ? thing : new(thing)
140+
end
141+
142+
# @private
143+
def initialize(sym)
144+
raise ArgumentError, "I only wrap symbols" unless sym.is_a?(Symbol) || sym.nil?
145+
@sym = sym
146+
end
147+
148+
# Allows comparisons w/ real symbols
149+
def ==(other)
150+
@sym == other
151+
end
152+
153+
# Find out whether you're in the execution context of a single example (either before, during or after)
154+
def in_example_context?
155+
EXAMPLE_CONTEXT.include?(@sym)
156+
end
157+
end
158+
159+
# Set the current scope rspec is executing in
160+
# @api private
161+
def self.current_scope=(scope)
162+
RSpec::Support.thread_local_data[:current_scope] = ScopeSymbol.wrap(scope)
163+
end
164+
RSpec.current_scope = :suite
165+
166+
# Get the current scope rspec is currently executing tests in.
167+
#
168+
# Returns `:suite` while loading your tests
169+
#
170+
# Returns `:before_each_hook`/`:before_all_hook`/`:before_suite_hook`/`:after_each_hook`/`:after_all_hook`/`:after_suite_hook`
171+
# in the respective hooks
172+
#
173+
# Returns `before_each_hook`/`after_each_hook` inside an `around :each` hook, before an after you call `example.run` respectively.
174+
#
175+
# Returns `:example` inside it/example blocks
176+
#
177+
# Returns `nil` after your suite and all hooks are done
178+
# @return [ScopeSymbol/Nil]
179+
def self.current_scope
180+
RSpec::Support.thread_local_data[:current_scope]
181+
end
182+
132183
# @private
133184
# Internal container for global non-configuration data.
134185
def self.world

lib/rspec/core/configuration.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,10 +2063,13 @@ def with_suite_hooks
20632063
return yield if dry_run?
20642064

20652065
begin
2066+
RSpec.current_scope = :before_suite_hook
20662067
run_suite_hooks("a `before(:suite)` hook", @before_suite_hooks)
20672068
yield
20682069
ensure
2070+
RSpec.current_scope = :after_suite_hook
20692071
run_suite_hooks("an `after(:suite)` hook", @after_suite_hooks)
2072+
RSpec.current_scope = nil
20702073
end
20712074
end
20722075

lib/rspec/core/example.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ def skipped?
244244
# the instance of {ExampleGroup}.
245245
# @param example_group_instance the instance of an ExampleGroup subclass
246246
def run(example_group_instance, reporter)
247+
prev_scope = RSpec.current_scope
248+
# RSpec.current_scope = :example
247249
@example_group_instance = example_group_instance
248250
@reporter = reporter
249251
RSpec.configuration.configure_example(self, hooks)
@@ -259,6 +261,7 @@ def run(example_group_instance, reporter)
259261
with_around_and_singleton_context_hooks do
260262
begin
261263
run_before_example
264+
RSpec.current_scope = :example
262265
@example_group_instance.instance_exec(self, &@example_block)
263266

264267
if pending?
@@ -278,6 +281,7 @@ def run(example_group_instance, reporter)
278281
rescue AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt => e
279282
set_exception(e)
280283
ensure
284+
RSpec.current_scope = :after_each_hook
281285
run_after_example
282286
end
283287
end
@@ -286,12 +290,14 @@ def run(example_group_instance, reporter)
286290
set_exception(e)
287291
ensure
288292
@example_group_instance = nil # if you love something... let it go
293+
RSpec.current_scope = prev_scope
289294
end
290295

291296
finish(reporter)
292297
ensure
293298
execution_result.ensure_timing_set(clock)
294299
RSpec.current_example = nil
300+
RSpec.current_scope = prev_scope
295301
end
296302

297303
if RSpec::Support::Ruby.jruby? || RUBY_VERSION.to_f < 1.9
@@ -462,6 +468,7 @@ def hooks
462468
end
463469

464470
def with_around_example_hooks
471+
RSpec.current_scope = :before_each_hook
465472
hooks.run(:around, :example, self) { yield }
466473
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
467474
set_exception(e)

lib/rspec/core/example_group.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,10 +598,12 @@ def self.run_after_context_hooks(example_group_instance)
598598
# Runs all the examples in this group.
599599
def self.run(reporter=RSpec::Core::NullReporter)
600600
return if RSpec.world.wants_to_quit
601+
prev_scope = RSpec.current_scope
601602
reporter.example_group_started(self)
602603

603604
should_run_context_hooks = descendant_filtered_examples.any?
604605
begin
606+
RSpec.current_scope = :before_all_hook
605607
run_before_context_hooks(new('before(:context) hook')) if should_run_context_hooks
606608
result_for_this_group = run_examples(reporter)
607609
results_for_descendants = ordering_strategy.order(children).map { |child| child.run(reporter) }.all?
@@ -614,8 +616,10 @@ def self.run(reporter=RSpec::Core::NullReporter)
614616
RSpec.world.wants_to_quit = true if reporter.fail_fast_limit_met?
615617
false
616618
ensure
619+
RSpec.current_scope = :after_all_hook
617620
run_after_context_hooks(new('after(:context) hook')) if should_run_context_hooks
618621
reporter.example_group_finished(self)
622+
RSpec.current_scope = prev_scope
619623
end
620624
end
621625

spec/rspec/core_spec.rb

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,92 @@
129129
end
130130
end
131131

132+
describe ".current_scope" do
133+
unless RSpec.current_scope == :suite
134+
raise "bad current scope: #{RSpec.current_scope.inspect}"
135+
end
136+
137+
if RSpec.current_scope.in_example_context?
138+
raise "this is not an example context"
139+
end
140+
141+
RSpec.configure do |c|
142+
c.before :suite do
143+
expect(RSpec.current_scope).to eq(:before_suite_hook)
144+
expect(RSpec.current_scope.in_example_context?).to eq(false)
145+
end
146+
147+
c.before :all do
148+
expect(RSpec.current_scope).to eq(:before_all_hook)
149+
expect(RSpec.current_scope.in_example_context?).to eq(false)
150+
end
151+
152+
c.before :each do
153+
expect(RSpec.current_scope).to eq(:before_each_hook)
154+
expect(RSpec.current_scope.in_example_context?).to eq(true)
155+
end
156+
157+
c.around :each do |ex|
158+
expect(RSpec.current_scope).to eq(:before_each_hook)
159+
expect(RSpec.current_scope.in_example_context?).to eq(true)
160+
ex.run
161+
expect(RSpec.current_scope.in_example_context?).to eq(true)
162+
expect(RSpec.current_scope).to eq(:after_each_hook)
163+
end
164+
165+
c.after :each do
166+
expect(RSpec.current_scope).to eq(:after_each_hook)
167+
expect(RSpec.current_scope.in_example_context?).to eq(true)
168+
end
169+
170+
c.after :all do
171+
expect(RSpec.current_scope).to eq(:after_all_hook)
172+
expect(RSpec.current_scope.in_example_context?).to eq(false)
173+
end
174+
175+
c.after :suite do
176+
expect(RSpec.current_scope).to eq(:after_suite_hook)
177+
expect(RSpec.current_scope.in_example_context?).to eq(false)
178+
end
179+
end
180+
181+
before :all do
182+
expect(RSpec.current_scope.in_example_context?).to eq(false)
183+
expect(RSpec.current_scope).to eq(:before_all_hook)
184+
end
185+
186+
before :each do
187+
expect(RSpec.current_scope).to eq(:before_each_hook)
188+
expect(RSpec.current_scope.in_example_context?).to eq(true)
189+
end
190+
191+
around :each do |ex|
192+
expect(RSpec.current_scope).to eq(:before_each_hook)
193+
ex.run
194+
expect(RSpec.current_scope).to eq(:after_each_hook)
195+
end
196+
197+
after :each do
198+
expect(RSpec.current_scope.in_example_context?).to eq(true)
199+
expect(RSpec.current_scope).to eq(:after_each_hook)
200+
end
201+
202+
after :all do
203+
expect(RSpec.current_scope).to eq(:after_all_hook)
204+
expect(RSpec.current_scope.in_example_context?).to eq(false)
205+
end
206+
207+
it "returns :example inside an example" do
208+
expect(RSpec.current_scope).to eq(:example)
209+
expect(RSpec.current_scope.in_example_context?).to eq(true)
210+
end
211+
212+
it "works for more than one example in a describe block" do
213+
expect(RSpec.current_scope).to eq(:example)
214+
expect(RSpec.current_scope.in_example_context?).to eq(true)
215+
end
216+
end
217+
132218
describe ".reset" do
133219
it "resets the configuration and world objects" do
134220
config_before_reset = RSpec.configuration

0 commit comments

Comments
 (0)