From d305a46f8e35d87f2fa232cbbfbd76c42abfabbb Mon Sep 17 00:00:00 2001 From: Odin H B Date: Tue, 15 Jun 2021 19:18:59 +0200 Subject: [PATCH] [core] Implement RSpec.current_scope My reasoning is: rspec-rails needs to do the following condition: `if RSpec.current_scope == :before_context_hook` test_prof needs to do the following condition: `if RSpec.current_scope == :before_context_hook` amd my little helper needs the following `unless [:before_example_hook, :example].include?(RSpec.current_scope)` --- This commit was imported from https://github.com/rspec/rspec-core/commit/dc3dda1ce0c79c488a527debb5c35110b968b430. --- rspec-core/.rubocop.yml | 2 +- rspec-core/Changelog.md | 2 + .../features/metadata/current_scope.feature | 87 +++++++++++++++++++ rspec-core/lib/rspec/core.rb | 26 ++++++ rspec-core/lib/rspec/core/configuration.rb | 3 + rspec-core/lib/rspec/core/example.rb | 3 + rspec-core/lib/rspec/core/example_group.rb | 2 + rspec-core/spec/rspec/core_spec.rb | 14 +++ 8 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 rspec-core/features/metadata/current_scope.feature diff --git a/rspec-core/.rubocop.yml b/rspec-core/.rubocop.yml index 29d634157..9a3abf924 100644 --- a/rspec-core/.rubocop.yml +++ b/rspec-core/.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/rspec-core/Changelog.md b/rspec-core/Changelog.md index fdf6a57e9..2c62bfded 100644 --- a/rspec-core/Changelog.md +++ b/rspec-core/Changelog.md @@ -6,6 +6,8 @@ Enhancements: * Improve pluralisation of words ending with `s` (like process). (Joshua Pinter, #2779) * Add ordering by file modification time (most recent first). (Matheus Richard, #2778) * Add `to_s` to reserved names for #let and #subject. (Nick Flückiger, #2886) +* Introduce `RSpec.current_scope` to expose the current scope in which + RSpec is executing. e.g. `:before_example_hook`, `:example` etc. (@odinhb, #2895) Bug fixes: diff --git a/rspec-core/features/metadata/current_scope.feature b/rspec-core/features/metadata/current_scope.feature new file mode 100644 index 000000000..4dacd710e --- /dev/null +++ b/rspec-core/features/metadata/current_scope.feature @@ -0,0 +1,87 @@ +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 + # Outside of the test lifecycle, the current scope is `:suite` + exit(1) unless RSpec.current_scope == :suite + + at_exit do + exit(1) unless RSpec.current_scope == :suite + 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/rspec-core/lib/rspec/core.rb b/rspec-core/lib/rspec/core.rb index 2f10014b2..ad9553c9b 100644 --- a/rspec-core/lib/rspec/core.rb +++ b/rspec-core/lib/rspec/core.rb @@ -129,6 +129,32 @@ 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 (in order of lifecycle): + # * `:suite` as an initial value, this is outside of the test lifecycle. + # * `:before_suite_hook` during `before(:suite)` hooks. + # * `:before_context_hook` during `before(:context)` hooks. + # * `:before_example_hook` during `before(:example)` hooks and `around(:example)` before `example.run`. + # * `:example` within the example run. + # * `:after_example_hook` during `after(:example)` hooks and `around(:example)` after `example.run`. + # * `:after_context_hook` during `after(:context)` hooks. + # * `:after_suite_hook` during `after(:suite)` hooks. + # * `:suite` as a final value, again this is outside of the test lifecycle. + # + # Reminder, `:context` hooks have `:all` alias and `:example` hooks have `:each` alias. + # @return [Symbol] + 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/rspec-core/lib/rspec/core/configuration.rb b/rspec-core/lib/rspec/core/configuration.rb index bb38a8de4..399ae15d6 100644 --- a/rspec-core/lib/rspec/core/configuration.rb +++ b/rspec-core/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 = :suite end end diff --git a/rspec-core/lib/rspec/core/example.rb b/rspec-core/lib/rspec/core/example.rb index d3b891fa9..eb061ee8f 100644 --- a/rspec-core/lib/rspec/core/example.rb +++ b/rspec-core/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/rspec-core/lib/rspec/core/example_group.rb b/rspec-core/lib/rspec/core/example_group.rb index 5efee9f38..8a2d7cb38 100644 --- a/rspec-core/lib/rspec/core/example_group.rb +++ b/rspec-core/lib/rspec/core/example_group.rb @@ -602,6 +602,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? @@ -614,6 +615,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/rspec-core/spec/rspec/core_spec.rb b/rspec-core/spec/rspec/core_spec.rb index 085acbbba..d9a02e97e 100644 --- a/rspec-core/spec/rspec/core_spec.rb +++ b/rspec-core/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