diff --git a/.rubocop.yml b/.rubocop.yml index 29d6341570..9a3abf924a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -24,7 +24,7 @@ Metrics/LineLength: # This should go down over time. Metrics/MethodLength: - Max: 37 + Max: 39 # This should go down over time. Metrics/CyclomaticComplexity: diff --git a/features/metadata/current_scope.feature b/features/metadata/current_scope.feature new file mode 100644 index 0000000000..853ea3c1b2 --- /dev/null +++ b/features/metadata/current_scope.feature @@ -0,0 +1,84 @@ +Feature: RSpec provides the current_scope as RSpec::current_scope + + You can detect which rspec scope your helper methods or library code is executing in. + This is useful if for example, your method only makes sense to call in a certain context. + + Scenario: Detecting the current scope + Given a file named "current_scope_spec.rb" with: + """ruby + unless RSpec.current_scope == :suite + raise "bad current scope: #{RSpec.current_scope.inspect}" + end + + RSpec.configure do |c| + c.before :suite do + expect(RSpec.current_scope).to eq(:before_suite_hook) + end + + c.before :context do + expect(RSpec.current_scope).to eq(:before_context_hook) + end + + c.before :example do + expect(RSpec.current_scope).to eq(:before_example_hook) + end + + c.around :example do |ex| + expect(RSpec.current_scope).to eq(:before_example_hook) + ex.run + expect(RSpec.current_scope).to eq(:after_example_hook) + end + + c.after :example do + expect(RSpec.current_scope).to eq(:after_example_hook) + end + + c.after :context do + expect(RSpec.current_scope).to eq(:after_context_hook) + end + + c.after :suite do + expect(RSpec.current_scope).to eq(:after_suite_hook) + end + end + + RSpec.describe "RSpec.current_scope" do + before :context do + expect(RSpec.current_scope).to eq(:before_context_hook) + end + + before :example do + expect(RSpec.current_scope).to eq(:before_example_hook) + end + + around :example do |ex| + expect(RSpec.current_scope).to eq(:before_example_hook) + ex.run + expect(RSpec.current_scope).to eq(:after_example_hook) + end + + after :example do + expect(RSpec.current_scope).to eq(:after_example_hook) + end + + after :context do + expect(RSpec.current_scope).to eq(:after_context_hook) + end + + it "is :example in an example" do + expect(RSpec.current_scope).to eq(:example) + end + + it "works for multiple examples" do + expect(RSpec.current_scope).to eq(:example) + end + + describe "in nested describe blocks" do + it "still works" do + expect(RSpec.current_scope).to eq(:example) + end + end + end + """ + When I run `rspec current_scope_spec.rb` + Then the examples should all pass diff --git a/lib/rspec/core.rb b/lib/rspec/core.rb index 2f10014b2e..fd20f21156 100644 --- a/lib/rspec/core.rb +++ b/lib/rspec/core.rb @@ -129,6 +129,31 @@ def self.current_example=(example) RSpec::Support.thread_local_data[:current_example] = example end + # Set the current scope rspec is executing in + # @api private + def self.current_scope=(scope) + RSpec::Support.thread_local_data[:current_scope] = scope + end + RSpec.current_scope = :suite + + # Get the current RSpec execution scope + # + # Returns `:suite` while loading your tests (as soon as RSpec has loaded) + # + # Returns `:before_example_hook`/`:before_context_hook`/`:before_suite_hook`/ + # `:after_example_hook`/`:after_context_hook`/`:after_suite_hook` in the respective hooks + # + # Returns `:before_example_hook`/`:after_example_hook` inside an `around :each` hook, + # before and after you call `example.run` respectively. + # + # Returns `:example` inside it/example blocks + # + # Returns `nil` after your suite and all hooks are done + # @return [Symbol/nil] + def self.current_scope + RSpec::Support.thread_local_data[:current_scope] + end + # @private # Internal container for global non-configuration data. def self.world diff --git a/lib/rspec/core/configuration.rb b/lib/rspec/core/configuration.rb index bb38a8de4f..bad82e8ebd 100644 --- a/lib/rspec/core/configuration.rb +++ b/lib/rspec/core/configuration.rb @@ -2063,10 +2063,13 @@ def with_suite_hooks return yield if dry_run? begin + RSpec.current_scope = :before_suite_hook run_suite_hooks("a `before(:suite)` hook", @before_suite_hooks) yield ensure + RSpec.current_scope = :after_suite_hook run_suite_hooks("an `after(:suite)` hook", @after_suite_hooks) + RSpec.current_scope = nil end end diff --git a/lib/rspec/core/example.rb b/lib/rspec/core/example.rb index d3b891fa92..eb061ee8fa 100644 --- a/lib/rspec/core/example.rb +++ b/lib/rspec/core/example.rb @@ -259,6 +259,7 @@ def run(example_group_instance, reporter) with_around_and_singleton_context_hooks do begin run_before_example + RSpec.current_scope = :example @example_group_instance.instance_exec(self, &@example_block) if pending? @@ -278,6 +279,7 @@ def run(example_group_instance, reporter) rescue AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt => e set_exception(e) ensure + RSpec.current_scope = :after_example_hook run_after_example end end @@ -462,6 +464,7 @@ def hooks end def with_around_example_hooks + RSpec.current_scope = :before_example_hook hooks.run(:around, :example, self) { yield } rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e set_exception(e) diff --git a/lib/rspec/core/example_group.rb b/lib/rspec/core/example_group.rb index f53e897db0..87f30c3ded 100644 --- a/lib/rspec/core/example_group.rb +++ b/lib/rspec/core/example_group.rb @@ -608,6 +608,7 @@ def self.run(reporter=RSpec::Core::NullReporter) should_run_context_hooks = descendant_filtered_examples.any? begin + RSpec.current_scope = :before_context_hook run_before_context_hooks(new('before(:context) hook')) if should_run_context_hooks result_for_this_group = run_examples(reporter) results_for_descendants = ordering_strategy.order(children).map { |child| child.run(reporter) }.all? @@ -620,6 +621,7 @@ def self.run(reporter=RSpec::Core::NullReporter) RSpec.world.wants_to_quit = true if reporter.fail_fast_limit_met? false ensure + RSpec.current_scope = :after_context_hook run_after_context_hooks(new('after(:context) hook')) if should_run_context_hooks reporter.example_group_finished(self) end diff --git a/spec/rspec/core_spec.rb b/spec/rspec/core_spec.rb index 085acbbbab..d9a02e97e7 100644 --- a/spec/rspec/core_spec.rb +++ b/spec/rspec/core_spec.rb @@ -129,6 +129,20 @@ end end + describe ".current_scope" do + before :context do + expect(RSpec.current_scope).to eq(:before_context_hook) + end + + before do + expect(RSpec.current_scope).to eq(:before_example_hook) + end + + it "returns :example inside an example" do + expect(RSpec.current_scope).to eq(:example) + end + end + describe ".reset" do it "resets the configuration and world objects" do config_before_reset = RSpec.configuration