diff --git a/.rubocop_rspec_base.yml b/.rubocop_rspec_base.yml index 4e00c2c555..a98671e063 100644 --- a/.rubocop_rspec_base.yml +++ b/.rubocop_rspec_base.yml @@ -1,4 +1,4 @@ -# This file was generated on 2019-12-26T17:20:33+00:00 from the rspec-dev repo. +# This file was generated on 2020-04-03T18:53:21+03:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. # This file contains defaults for RSpec projects. Individual projects diff --git a/.travis.yml b/.travis.yml index cd2c8230f7..8fdc4348b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -# This file was generated on 2019-12-26T17:20:33+00:00 from the rspec-dev repo. +# This file was generated on 2020-04-03T18:53:21+03:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. # In order to install old Rubies, we need to use old Ubuntu distibution. @@ -22,10 +22,10 @@ rvm: - 2.1 - 2.2.10 - 2.3.8 - - 2.4.9 - - 2.5.7 - - 2.6.5 - - 2.7.0 + - 2.4.10 + - 2.5.8 + - 2.6.6 + - 2.7.1 - ruby-head - ree - rbx-3 diff --git a/Changelog.md b/Changelog.md index 40c1237da2..0fe6a23334 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,18 @@ +### 3.9.2 / 2020-05-02 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.9.1...v3.9.2) + +Bug Fixes: + +* Emit a warning when `around` hook is used with `:context` scope + (Phil Pirozhkov, #2687) +* Prevent invalid implementations of `Exception#cause` from being treated as a + valid cause (and causing strange errors) in `RSpec::Core::Formatters::ExceptionPresenter`. + (Jon Rowe, #2703) +* Correctly detect patterns when `rspec_opts` is an array in `RSpec::Core::RakeTask`. + (Marc-André Lafortune, #2704) +* Make `RSpec.clear_examples` reset example counts for example groups. This fixes + an issue with re-running specs not matching ids. (Agis Anastasopoulos, #2723) + ### 3.9.1 / 2019-12-28 [Full Changelog](http://github.com/rspec/rspec-core/compare/v3.9.0...v3.9.1) @@ -11,6 +26,7 @@ Bug Fixes: [Full Changelog](http://github.com/rspec/rspec-core/compare/v3.8.2...v3.9.0) Enhancements: + * Improve the handling of errors during loading support files, if a file errors before loading specs, RSpec will now skip loading the specs. (David Rodríguez, #2568) @@ -2128,6 +2144,7 @@ Bug fixes [Full Changelog](http://github.com/rspec/rspec-core/compare/v2.2.0...v2.2.1) Bug fixes + * alias_method instead of override Kernel#method_missing (John Wilger) * changed --autotest to --tty in generated command (MIKAMI Yoshiyuki) * revert change to debugger (had introduced conflict with Rails) diff --git a/Gemfile b/Gemfile index b6618befeb..b781ec5cdf 100644 --- a/Gemfile +++ b/Gemfile @@ -12,20 +12,20 @@ branch = File.read(File.expand_path("../maintenance-branch", __FILE__)).chomp end end -if RUBY_VERSION >= '2.0.0' - gem 'rake', '>= 10.0.0' -elsif RUBY_VERSION >= '1.9.3' +if RUBY_VERSION < '1.9.3' + gem 'rake', '< 11.0.0' # rake 11 requires Ruby 1.9.3 or later +elsif RUBY_VERSION < '2.0.0' gem 'rake', '< 12.0.0' # rake 12 requires Ruby 2.0.0 or later else - gem 'rake', '< 11.0.0' # rake 11 requires Ruby 1.9.3 or later + gem 'rake', '> 12.3.2' end -gem 'yard', '~> 0.9.12', :require => false +gem 'yard', '~> 0.9.24', :require => false ### deps for rdoc.info group :documentation do - gem 'redcarpet', '2.1.1', :platform => :mri - gem 'github-markup', '0.7.2', :platform => :mri + gem 'redcarpet', :platform => :mri + gem 'github-markup', :platform => :mri end if RUBY_VERSION < '2.0.0' || RUBY_ENGINE == 'java' diff --git a/appveyor.yml b/appveyor.yml index fb9dfcf0d4..496e421529 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -# This file was generated on 2019-12-26T17:20:33+00:00 from the rspec-dev repo. +# This file was generated on 2020-04-03T18:53:21+03:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. version: "{build}" diff --git a/features/hooks/before_and_after_hooks.feature b/features/hooks/before_and_after_hooks.feature index 6110a9819e..82fa55409b 100644 --- a/features/hooks/before_and_after_hooks.feature +++ b/features/hooks/before_and_after_hooks.feature @@ -22,8 +22,11 @@ Feature: `before` and `after` hooks after :suite ``` + A bare `before` or `after` hook defaults to the `:example` scope. + `before` and `after` hooks can be defined directly in the example groups they - should run in, or in a global `RSpec.configure` block. + should run in, or in a global `RSpec.configure` block. Note that the status of + the example does not affect the hooks. **WARNING:** Setting instance variables are not supported in `before(:suite)`. @@ -187,6 +190,29 @@ Feature: `before` and `after` hooks # ./after_context_spec.rb:3 """ + Scenario: A failure in an example does not affect hooks + Given a file named "failure_in_example_spec.rb" with: + """ruby + RSpec.describe "a failing example does not affect hooks" do + before(:context) { puts "before context runs" } + before(:example) { puts "before example runs" } + after(:example) { puts "after example runs" } + after(:context) { puts "after context runs" } + + it "fails the example but runs the hooks" do + raise "An Error" + end + end + """ + When I run `rspec failure_in_example_spec.rb` + Then it should fail with: + """ + before context runs + before example runs + after example runs + Fafter context runs + """ + Scenario: Define `before` and `after` blocks in configuration Given a file named "befores_in_configuration_spec.rb" with: """ruby @@ -231,10 +257,18 @@ Feature: `before` and `after` hooks puts "before example" end + before do + puts "also before example but by default" + end + after(:example) do puts "after example" end + after do + puts "also after example but by default" + end + after(:context) do puts "after context" end @@ -249,11 +283,13 @@ Feature: `before` and `after` hooks """ before context before example + also before example but by default + also after example but by default after example .after context """ - Scenario: `before`/after` blocks defined in configuration are run in order + Scenario: `before`/`after` blocks defined in configuration are run in order Given a file named "configuration_spec.rb" with: """ruby require "rspec/expectations" diff --git a/lib/rspec/core/configuration.rb b/lib/rspec/core/configuration.rb index 8bc099ce2b..0ad7e28b4a 100644 --- a/lib/rspec/core/configuration.rb +++ b/lib/rspec/core/configuration.rb @@ -1350,6 +1350,12 @@ def exclusion_filter # end # end # + # module PreferencesHelpers + # def preferences(user, preferences = {}) + # # ... + # end + # end + # # module UserHelpers # def users(username) # # ... @@ -1358,12 +1364,17 @@ def exclusion_filter # # RSpec.configure do |config| # config.include(UserHelpers) # included in all groups + # + # # included in examples with `:preferences` metadata + # config.include(PreferenceHelpers, :preferences) + # + # # included in examples with `:type => :request` metadata # config.include(AuthenticationHelpers, :type => :request) # end # - # describe "edit profile", :type => :request do + # describe "edit profile", :preferences, :type => :request do # it "can be viewed by owning user" do - # login_as users(:jdoe) + # login_as preferences(users(:jdoe), :lang => 'es') # get "/profiles/jdoe" # assert_select ".username", :text => 'jdoe' # end @@ -1391,17 +1402,21 @@ def include(mod, *filters) # # @example # - # RSpec.shared_context "example users" do + # RSpec.shared_context "example admin user" do # let(:admin_user) { create_user(:admin) } + # end + # + # RSpec.shared_context "example guest user" do # let(:guest_user) { create_user(:guest) } # end # # RSpec.configure do |config| - # config.include_context "example users", :type => :request + # config.include_context "example guest user", :type => :request + # config.include_context "example admin user", :admin, :type => :request # end # # RSpec.describe "The admin page", :type => :request do - # it "can be viewed by admins" do + # it "can be viewed by admins", :admin do # login_with admin_user # get "/admin" # expect(response).to be_ok @@ -1443,12 +1458,20 @@ def include_context(shared_group_name, *filters) # end # end # + # module PermissionHelpers + # def define_permissions + # # ... + # end + # end + # # RSpec.configure do |config| # config.extend(UiHelpers, :type => :request) + # config.extend(PermissionHelpers, :with_permissions, :type => :request) # end # - # describe "edit profile", :type => :request do + # describe "edit profile", :with_permissions, :type => :request do # run_in_browser + # define_permissions # # it "does stuff in the client" do # # ... @@ -1906,7 +1929,8 @@ def apply_derived_metadata_to(metadata) # # This method differs from {Hooks#before} in only one way: it supports # the `:suite` scope. Hooks with the `:suite` scope will be run once before - # the first example of the entire suite is executed. + # the first example of the entire suite is executed. Conditions passed along + # with `:suite` are effectively ignored. # # @see #prepend_before # @see #after @@ -1935,7 +1959,8 @@ def before(scope=nil, *meta, &block) # # This method differs from {Hooks#prepend_before} in only one way: it supports # the `:suite` scope. Hooks with the `:suite` scope will be run once before - # the first example of the entire suite is executed. + # the first example of the entire suite is executed. Conditions passed along + # with `:suite` are effectively ignored. # # @see #before # @see #after @@ -1959,7 +1984,8 @@ def prepend_before(scope=nil, *meta, &block) # # This method differs from {Hooks#after} in only one way: it supports # the `:suite` scope. Hooks with the `:suite` scope will be run once after - # the last example of the entire suite is executed. + # the last example of the entire suite is executed. Conditions passed along + # with `:suite` are effectively ignored. # # @see #append_after # @see #before @@ -1988,7 +2014,8 @@ def after(scope=nil, *meta, &block) # # This method differs from {Hooks#append_after} in only one way: it supports # the `:suite` scope. Hooks with the `:suite` scope will be run once after - # the last example of the entire suite is executed. + # the last example of the entire suite is executed. Conditions passed along + # with `:suite` are effectively ignored. # # @see #append_after # @see #before diff --git a/lib/rspec/core/example_group.rb b/lib/rspec/core/example_group.rb index 05f4fa7596..2654f105d3 100644 --- a/lib/rspec/core/example_group.rb +++ b/lib/rspec/core/example_group.rb @@ -111,16 +111,14 @@ def described_class # @overload $1 # @overload $1(&example_implementation) # @param example_implementation [Block] The implementation of the example. - # @overload $1(doc_string, *metadata_keys, metadata={}) + # @overload $1(doc_string, *metadata) # @param doc_string [String] The example's doc string. - # @param metadata [Hash] Metadata for the example. - # @param metadata_keys [Array] Metadata tags for the example. - # Will be transformed into hash entries with `true` values. - # @overload $1(doc_string, *metadata_keys, metadata={}, &example_implementation) + # @param metadata [Array, Hash] Metadata for the example. + # Symbols will be transformed into hash entries with `true` values. + # @overload $1(doc_string, *metadata, &example_implementation) # @param doc_string [String] The example's doc string. - # @param metadata [Hash] Metadata for the example. - # @param metadata_keys [Array] Metadata tags for the example. - # Will be transformed into hash entries with `true` values. + # @param metadata [Array, Hash] Metadata for the example. + # Symbols will be transformed into hash entries with `true` values. # @param example_implementation [Block] The implementation of the example. # @yield [Example] the example object # @example @@ -139,6 +137,11 @@ def described_class # $1 "does something" do |ex| # # ex is the Example object that contains metadata about the example # end + # + # @example + # $1 "does something", :slow, :load_factor => 100 do + # end + # def self.define_example_method(name, extra_options={}) idempotently_define_singleton_method(name) do |*all_args, &block| desc, *args = *all_args @@ -204,11 +207,10 @@ def self.define_example_method(name, extra_options={}) # @overload $1 # @overload $1(&example_group_definition) # @param example_group_definition [Block] The definition of the example group. - # @overload $1(doc_string, *metadata_keys, metadata={}, &example_implementation) + # @overload $1(doc_string, *metadata, &example_implementation) # @param doc_string [String] The group's doc string. - # @param metadata [Hash] Metadata for the group. - # @param metadata_keys [Array] Metadata tags for the group. - # Will be transformed into hash entries with `true` values. + # @param metadata [Array, Hash] Metadata for the group. + # Symbols will be transformed into hash entries with `true` values. # @param example_group_definition [Block] The definition of the example group. # # Generates a subclass of this example group which inherits @@ -223,12 +225,21 @@ def self.define_example_method(name, extra_options={}) # do_something_before # end # + # before(:example, :clean_env) do + # env.clear! + # end + # # let(:thing) { Thing.new } # # $1 "attribute (of something)" do # # examples in the group get the before hook # # declared above, and can access `thing` # end + # + # $1 "needs additional setup", :clean_env, :implementation => JSON do + # # specifies that hooks with matching metadata + # # should be be run additionally + # end # end # # @see DSL#describe diff --git a/lib/rspec/core/formatters.rb b/lib/rspec/core/formatters.rb index 7a412a1a1c..a693a80c5c 100644 --- a/lib/rspec/core/formatters.rb +++ b/lib/rspec/core/formatters.rb @@ -1,4 +1,5 @@ RSpec::Support.require_rspec_support "directory_maker" + # ## Built-in Formatters # # * progress (default) - Prints dots for passing examples, `F` for failures, `*` diff --git a/lib/rspec/core/formatters/exception_presenter.rb b/lib/rspec/core/formatters/exception_presenter.rb index 9ad6503e17..db612e8858 100644 --- a/lib/rspec/core/formatters/exception_presenter.rb +++ b/lib/rspec/core/formatters/exception_presenter.rb @@ -43,7 +43,7 @@ def formatted_backtrace(exception=@exception) if RSpec::Support::RubyFeatures.supports_exception_cause? def formatted_cause(exception) - last_cause = final_exception(exception) + last_cause = final_exception(exception, [exception]) cause = [] if exception.cause @@ -55,7 +55,9 @@ def formatted_cause(exception) cause << " #{line}" end - cause << (" #{backtrace_formatter.format_backtrace(last_cause.backtrace, example.metadata).first}") + unless last_cause.backtrace.empty? + cause << (" #{backtrace_formatter.format_backtrace(last_cause.backtrace, example.metadata).first}") + end end cause @@ -96,7 +98,8 @@ def fully_formatted_lines(failure_number, colorizer) def final_exception(exception, previous=[]) cause = exception.cause - if cause && !previous.include?(cause) + + if cause && Exception === cause && !previous.include?(cause) previous << cause final_exception(cause, previous) else diff --git a/lib/rspec/core/hooks.rb b/lib/rspec/core/hooks.rb index 56c0be852d..d942dce060 100644 --- a/lib/rspec/core/hooks.rb +++ b/lib/rspec/core/hooks.rb @@ -13,13 +13,14 @@ module Hooks # @overload before(scope, &block) # @param scope [Symbol] `:example`, `:context`, or `:suite` # (defaults to `:example`) - # @overload before(scope, conditions, &block) + # @overload before(scope, *conditions, &block) # @param scope [Symbol] `:example`, `:context`, or `:suite` # (defaults to `:example`) - # @param conditions [Hash] - # constrains this hook to examples matching these conditions e.g. + # @param conditions [Array, Hash] constrains this hook to + # examples matching these conditions e.g. # `before(:example, :ui => true) { ... }` will only run with examples - # or groups declared with `:ui => true`. + # or groups declared with `:ui => true`. Symbols will be transformed + # into hash entries with `true` values. # @overload before(conditions, &block) # @param conditions [Hash] # constrains this hook to examples matching these conditions e.g. @@ -214,13 +215,14 @@ def prepend_before(*args, &block) # @overload after(scope, &block) # @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to # `:example`) - # @overload after(scope, conditions, &block) + # @overload after(scope, *conditions, &block) # @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to # `:example`) - # @param conditions [Hash] - # constrains this hook to examples matching these conditions e.g. + # @param conditions [Array, Hash] constrains this hook to + # examples matching these conditions e.g. # `after(:example, :ui => true) { ... }` will only run with examples - # or groups declared with `:ui => true`. + # or groups declared with `:ui => true`. Symbols will be transformed + # into hash entries with `true` values. # @overload after(conditions, &block) # @param conditions [Hash] # constrains this hook to examples matching these conditions e.g. @@ -290,13 +292,15 @@ def append_after(*args, &block) # @param scope [Symbol] `:example` (defaults to `:example`) # present for syntax parity with `before` and `after`, but # `:example`/`:each` is the only supported value. - # @overload around(scope, conditions, &block) + # @overload around(scope, *conditions, &block) # @param scope [Symbol] `:example` (defaults to `:example`) # present for syntax parity with `before` and `after`, but # `:example`/`:each` is the only supported value. - # @param conditions [Hash] constrains this hook to examples matching - # these conditions e.g. `around(:example, :ui => true) { ... }` will - # only run with examples or groups declared with `:ui => true`. + # @param conditions [Array, Hash] constrains this hook to + # examples matching these conditions e.g. + # `around(:example, :ui => true) { ... }` will only run with examples + # or groups declared with `:ui => true`. Symbols will be transformed + # into hash entries with `true` values. # @overload around(conditions, &block) # @param conditions [Hash] constrains this hook to examples matching # these conditions e.g. `around(:example, :ui => true) { ... }` will @@ -448,6 +452,11 @@ def register(prepend_or_append, position, *args, &block) "`#{position}(:suite)` hook, registered on an example " \ "group, will be ignored." return + elsif scope == :context && position == :around + # TODO: consider making this an error in RSpec 4. For SemVer reasons, + # we are only warning in RSpec 3. + RSpec.warn_with "WARNING: `around(:context)` hooks are not supported and " \ + "behave like `around(:example)." end hook = HOOK_TYPES[position][scope].new(block, options) diff --git a/lib/rspec/core/rake_task.rb b/lib/rspec/core/rake_task.rb index 7f01eab4f3..8cf474a94f 100644 --- a/lib/rspec/core/rake_task.rb +++ b/lib/rspec/core/rake_task.rb @@ -122,7 +122,7 @@ def file_inclusion_specification if ENV['SPEC'] FileList[ENV['SPEC']].sort elsif String === pattern && !File.exist?(pattern) - return if rspec_opts =~ /--pattern/ + return if [*rspec_opts].any? { |opt| opt =~ /--pattern/ } "--pattern #{escape pattern}" else # Before RSpec 3.1, we used `FileList` to get the list of matched diff --git a/lib/rspec/core/shared_example_group.rb b/lib/rspec/core/shared_example_group.rb index b6ced2cc1f..3d9efce282 100644 --- a/lib/rspec/core/shared_example_group.rb +++ b/lib/rspec/core/shared_example_group.rb @@ -1,3 +1,5 @@ +RSpec::Support.require_rspec_support "with_keywords_when_needed" + module RSpec module Core # Represents some functionality that is shared with multiple example groups. @@ -33,7 +35,7 @@ def include_in(klass, inclusion_line, args, customization_block) klass.update_inherited_metadata(@metadata) unless @metadata.empty? SharedExampleGroupInclusionStackFrame.with_frame(@description, inclusion_line) do - klass.class_exec(*args, &@definition) + RSpec::Support::WithKeywordsWhenNeeded.class_exec(klass, *args, &@definition) klass.class_exec(&customization_block) if customization_block end end diff --git a/lib/rspec/core/version.rb b/lib/rspec/core/version.rb index 593f6206f4..37d2e21eaf 100644 --- a/lib/rspec/core/version.rb +++ b/lib/rspec/core/version.rb @@ -3,7 +3,7 @@ module Core # Version information for RSpec Core. module Version # Current version of RSpec Core, in semantic versioning format. - STRING = '3.9.1' + STRING = '3.9.2' end end end diff --git a/lib/rspec/core/world.rb b/lib/rspec/core/world.rb index 1edec7f487..d766424f00 100644 --- a/lib/rspec/core/world.rb +++ b/lib/rspec/core/world.rb @@ -5,7 +5,7 @@ module Core # Internal container for global non-configuration data. class World # @private - attr_reader :example_groups, :filtered_examples + attr_reader :example_groups, :filtered_examples, :example_group_counts_by_spec_file # Used internally to determine what to do when a SIGINT is received. attr_accessor :wants_to_quit @@ -53,6 +53,7 @@ def reset example_groups.clear @sources_by_path.clear if defined?(@sources_by_path) @syntax_highlighter = nil + @example_group_counts_by_spec_file = Hash.new(0) end # @private diff --git a/rspec-core.gemspec b/rspec-core.gemspec index b69b049cf1..f6004a26e3 100644 --- a/rspec-core.gemspec +++ b/rspec-core.gemspec @@ -42,7 +42,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency "rspec-support", "= #{RSpec::Core::Version::STRING}" else # rspec-support must otherwise match our major/minor version - s.add_runtime_dependency "rspec-support", "~> #{RSpec::Core::Version::STRING.split('.')[0..1].concat(['1']).join('.')}" + s.add_runtime_dependency "rspec-support", "~> #{RSpec::Core::Version::STRING.split('.')[0..1].concat(['3']).join('.')}" end s.add_development_dependency "cucumber", "~> 1.3" diff --git a/script/clone_all_rspec_repos b/script/clone_all_rspec_repos index 72fa6df32f..9a741818c5 100755 --- a/script/clone_all_rspec_repos +++ b/script/clone_all_rspec_repos @@ -1,5 +1,5 @@ #!/bin/bash -# This file was generated on 2019-12-26T17:20:33+00:00 from the rspec-dev repo. +# This file was generated on 2020-04-03T18:53:21+03:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. set -e diff --git a/script/functions.sh b/script/functions.sh index 127aa3e933..713b9fb2e7 100644 --- a/script/functions.sh +++ b/script/functions.sh @@ -1,4 +1,4 @@ -# This file was generated on 2019-12-26T17:20:33+00:00 from the rspec-dev repo. +# This file was generated on 2020-04-03T18:53:21+03:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" @@ -187,7 +187,6 @@ function run_all_spec_suites { fold "rspec-core specs" run_spec_suite_for "rspec-core" fold "rspec-expectations specs" run_spec_suite_for "rspec-expectations" fold "rspec-mocks specs" run_spec_suite_for "rspec-mocks" - if rspec_rails_compatible; then fold "rspec-rails specs" run_spec_suite_for "rspec-rails" fi diff --git a/script/predicate_functions.sh b/script/predicate_functions.sh index b54fa61d63..b733b725bb 100644 --- a/script/predicate_functions.sh +++ b/script/predicate_functions.sh @@ -1,4 +1,4 @@ -# This file was generated on 2019-12-26T17:20:33+00:00 from the rspec-dev repo. +# This file was generated on 2020-04-03T18:53:21+03:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. function is_mri { @@ -57,20 +57,16 @@ function is_mri_2plus { fi } -function is_mri_27 { - if is_mri; then - if ruby -e "exit(RUBY_VERSION.to_f == 2.7)"; then - return 0 - else - return 1 - fi +function is_ruby_23_plus { + if ruby -e "exit(RUBY_VERSION.to_f >= 2.3)"; then + return 0 else return 1 fi } -function is_ruby_23_plus { - if ruby -e "exit(RUBY_VERSION.to_f >= 2.3)"; then +function is_ruby_25_plus { + if ruby -e "exit(RUBY_VERSION.to_f >= 2.5)"; then return 0 else return 1 @@ -78,7 +74,7 @@ function is_ruby_23_plus { } function rspec_rails_compatible { - if is_ruby_23_plus; then + if is_ruby_25_plus; then return 0 else return 1 @@ -101,11 +97,7 @@ function additional_specs_available { function documentation_enforced { if [ -x ./bin/yard ]; then if is_mri_2plus; then - if is_mri_27; then - return 1 - else - return 0 - fi + return 0 else return 1 fi diff --git a/script/run_build b/script/run_build index f8ce7a4cda..b5958fb8f2 100755 --- a/script/run_build +++ b/script/run_build @@ -1,5 +1,5 @@ #!/bin/bash -# This file was generated on 2019-12-26T17:20:33+00:00 from the rspec-dev repo. +# This file was generated on 2020-04-03T18:53:21+03:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. set -e diff --git a/script/travis_functions.sh b/script/travis_functions.sh index 3770b23d89..725c1184c5 100644 --- a/script/travis_functions.sh +++ b/script/travis_functions.sh @@ -1,4 +1,4 @@ -# This file was generated on 2019-12-26T17:20:33+00:00 from the rspec-dev repo. +# This file was generated on 2020-04-03T18:53:21+03:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. # Taken from: diff --git a/script/update_rubygems_and_install_bundler b/script/update_rubygems_and_install_bundler index 1b4ff8b04d..4ceb930cd8 100755 --- a/script/update_rubygems_and_install_bundler +++ b/script/update_rubygems_and_install_bundler @@ -1,5 +1,5 @@ #!/bin/bash -# This file was generated on 2019-12-26T17:20:33+00:00 from the rspec-dev repo. +# This file was generated on 2020-04-03T18:53:21+03:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. set -e diff --git a/spec/integration/filtering_spec.rb b/spec/integration/filtering_spec.rb index e8421f2d6b..781b4a96d1 100644 --- a/spec/integration/filtering_spec.rb +++ b/spec/integration/filtering_spec.rb @@ -66,6 +66,49 @@ def run_rerun_command_for_failing_spec end context "passing a line-number filter" do + it "works with different custom runners used in the same process" do + result_counter = Class.new do + RSpec::Core::Formatters.register(self, :example_passed) + + attr_accessor :passed_examples + + def initialize(*) + @passed_examples = 0 + end + + def example_passed(notification) + @passed_examples += 1 + end + end + + spec_file = "spec/filtering_custom_runner_spec.rb" + + write_file_formatted spec_file, " + RSpec.describe 'A group' do + example('ex 1') { } + example('ex 2') { } + end + " + + spec_file_path = expand_path(spec_file) + + formatter = result_counter.new + RSpec.configuration.add_formatter(formatter) + opts = RSpec::Core::ConfigurationOptions.new(["#{spec_file_path}[1:1]"]) + RSpec::Core::Runner.new(opts).run(StringIO.new, StringIO.new) + + expect(formatter.passed_examples).to eq 1 + + RSpec.clear_examples + + formatter = result_counter.new + RSpec.configuration.add_formatter(formatter) + opts = RSpec::Core::ConfigurationOptions.new(["#{spec_file_path}[1:2]"]) + RSpec::Core::Runner.new(opts).run(StringIO.new, StringIO.new) + + expect(formatter.passed_examples).to eq 1 + end + it "trumps exclusions, except for :if/:unless (which are absolute exclusions)" do write_file_formatted 'spec/a_spec.rb', " RSpec.configure do |c| diff --git a/spec/rspec/core/example_spec.rb b/spec/rspec/core/example_spec.rb index 81a7cec4a7..325efe91fc 100644 --- a/spec/rspec/core/example_spec.rb +++ b/spec/rspec/core/example_spec.rb @@ -443,7 +443,9 @@ def assert(val) context 'memory leaks, see GH-321, GH-1921' do def self.reliable_gc - 0 != GC.method(:start).arity # older Rubies don't give us options to ensure a full GC + # older Rubies don't give us options to ensure a full GC + # TruffleRuby GC.start arity matches but GC.disable and GC.enable are mock implementations + 0 != GC.method(:start).arity && !(defined?(RUBY_ENGINE) && RUBY_ENGINE == "truffleruby") end def expect_gc(opts) diff --git a/spec/rspec/core/formatters/exception_presenter_spec.rb b/spec/rspec/core/formatters/exception_presenter_spec.rb index 347524cc26..104673fa5b 100644 --- a/spec/rspec/core/formatters/exception_presenter_spec.rb +++ b/spec/rspec/core/formatters/exception_presenter_spec.rb @@ -11,7 +11,22 @@ module RSpec::Core before do allow(example.execution_result).to receive(:exception) { exception } example.metadata[:absolute_file_path] = __FILE__ - allow(exception).to receive(:cause) if RSpec::Support::RubyFeatures.supports_exception_cause? + end + + # This is a slightly more realistic exception than our instance_double + # created, as this will behave correctly with `Exception#===`, note we + # monkey patch the backtrace / cause in because these are not public + # api but we need specific values for our fakes. + class FakeException < Exception + def initialize(message, backtrace = [], cause = nil) + super(message) + @backtrace = backtrace + @cause = cause + end + attr_reader :backtrace + if RSpec::Support::RubyFeatures.supports_exception_cause? + attr_accessor :cause + end end describe "#fully_formatted" do @@ -25,7 +40,7 @@ module RSpec::Core line_num = __LINE__ + 1 # The failure happened here! Handles encoding too! ЙЦ end - let(:exception) { instance_double(Exception, :message => "Boom\nBam", :backtrace => [ "#{__FILE__}:#{line_num}"]) } + let(:exception) { FakeException.new("Boom\nBam", [ "#{__FILE__}:#{line_num}"]) } it "formats the exception with all the normal details" do expect(presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, '')) @@ -159,16 +174,14 @@ module RSpec::Core EOS end - let(:the_exception) { instance_double(Exception, :cause => second_exception, :message => "Boom\nBam", :backtrace => [ "#{__FILE__}:#{line_num}"]) } + let(:the_exception) { FakeException.new("Boom\nBam", [ "#{__FILE__}:#{line_num}"], second_exception) } let(:second_exception) do - instance_double(Exception, :cause => first_exception, :message => "Second\nexception", :backtrace => ["#{__FILE__}:#{__LINE__}"]) + FakeException.new("Second\nexception", ["#{__FILE__}:#{__LINE__}"], first_exception) end - caused_by_line_num = __LINE__ + 2 - let(:first_exception) do - instance_double(Exception, :cause => nil, :message => "Real\nculprit", :backtrace => ["#{__FILE__}:#{__LINE__}"]) - end + caused_by_line_num = __LINE__ + 1 + let(:first_exception) { FakeException.new("Real\nculprit", ["#{__FILE__}:#{__LINE__}"]) } it 'includes the first exception that caused the failure', :if => RSpec::Support::RubyFeatures.supports_exception_cause? do the_presenter = Formatters::ExceptionPresenter.new(the_exception, example) @@ -211,8 +224,9 @@ module RSpec::Core it 'wont produce a stack error when the cause is an older exception', :if => RSpec::Support::RubyFeatures.supports_exception_cause? do allow(the_exception).to receive(:cause) do - instance_double(Exception, :cause => the_exception, :message => "A loop", :backtrace => the_exception.backtrace) + FakeException.new("A loop", the_exception.backtrace, the_exception) end + the_presenter = Formatters::ExceptionPresenter.new(the_exception, example) expect(the_presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, '')) @@ -230,6 +244,22 @@ module RSpec::Core EOS end + it 'will work when cause is incorrectly overridden', :if => RSpec::Support::RubyFeatures.supports_exception_cause? do + incorrect_cause_exception = FakeException.new("A badly implemented exception", [], "An incorrect cause") + + the_presenter = Formatters::ExceptionPresenter.new(incorrect_cause_exception, example) + + expect(the_presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, '')) + | + | 1) Example + | Failure/Error: Unable to find matching line from backtrace + | A badly implemented exception + | # ------------------ + | # --- Caused by: --- + | # A badly implemented exception + EOS + end + it "adds extra failure lines from the example metadata" do extra_example = example.clone failure_line = 'http://www.example.com/job_details/123' diff --git a/spec/rspec/core/hooks_spec.rb b/spec/rspec/core/hooks_spec.rb index d9fb62de9f..895de436ca 100644 --- a/spec/rspec/core/hooks_spec.rb +++ b/spec/rspec/core/hooks_spec.rb @@ -492,5 +492,34 @@ def yielder :hooks ]) end + + it 'emits a warning for `around(:context)`' do + expect(RSpec).to receive(:warn_with).with(a_string_including( + '`around(:context)` hooks are not supported' + )) + RSpec.describe do + around(:context) { } + end + end + + it 'emits a warning for `around(:context)` defined in `configure`' do + expect(RSpec).to receive(:warn_with).with(a_string_including( + '`around(:context)` hooks are not supported' + )) + RSpec.configure do |c| + c.around(:context) { } + end + end + + [:before, :around, :after].each do |type| + it "emits a warning for `#{type}(:suite)` hooks" do + expect(RSpec).to receive(:warn_with).with(a_string_including( + "`#{type}(:suite)` hooks are only supported on the RSpec configuration object." + )) + RSpec.describe do + send(type, :suite) { } + end + end + end end end diff --git a/spec/rspec/core/rake_task_spec.rb b/spec/rspec/core/rake_task_spec.rb index 06a28e7f6b..a1168b6de7 100644 --- a/spec/rspec/core/rake_task_spec.rb +++ b/spec/rspec/core/rake_task_spec.rb @@ -62,15 +62,26 @@ def spec_command end context "with rspec_opts" do + include RSpec::Core::ShellEscape + it "adds the rspec_opts" do task.rspec_opts = "-Ifoo" - expect(spec_command).to match(/#{task.rspec_path}.*-Ifoo/) + expect(spec_command).to match(/#{task.rspec_path}.*-Ifoo/).and( + include(escape(RSpec::Core::RakeTask::DEFAULT_PATTERN)) # sanity check for following specs + ) end it 'correctly excludes the default pattern if rspec_opts includes --pattern' do task.rspec_opts = "--pattern some_specs" expect(spec_command).to include("--pattern some_specs").and( - exclude(RSpec::Core::RakeTask::DEFAULT_PATTERN) + exclude(escape(RSpec::Core::RakeTask::DEFAULT_PATTERN)) + ) + end + + it 'behaves properly if rspec_opts is an array' do + task.rspec_opts = %w[--pattern some_specs] + expect(spec_command).to include("--pattern some_specs").and( + exclude(escape(RSpec::Core::RakeTask::DEFAULT_PATTERN)) ) end end @@ -159,7 +170,7 @@ def silence_output(&block) expect { task.with_clean_environment = true - task.ruby_opts = '-e "puts \"Environment: #{ENV.keys}\""' + task.ruby_opts = '-e "puts \"Environment: #{ENV.keys.sort.inspect}\""' task.run_task false }.to avoid_outputting.to_stderr.and output(essential_shell_variables).to_stdout_from_any_process end diff --git a/spec/rspec/core/shared_example_group_spec.rb b/spec/rspec/core/shared_example_group_spec.rb index 6b897c395e..ca3c34691d 100644 --- a/spec/rspec/core/shared_example_group_spec.rb +++ b/spec/rspec/core/shared_example_group_spec.rb @@ -69,6 +69,59 @@ def find_implementation_block(registry, scope, name) expect(Kernel).to_not respond_to(shared_method_name) end + # These keyword specs cover all 4 of the keyword / keyword like syntax varients + # they should be warning free. + + if RSpec::Support::RubyFeatures.required_kw_args_supported? + it 'supports required keyword arguments' do + binding.eval(<<-CODE, __FILE__, __LINE__) + group.__send__ shared_method_name, "shared context expects keywords" do |foo:| + it "has an expected value" do + expect(foo).to eq("bar") + end + end + + group.__send__ shared_method_name, "shared context expects hash" do |a_hash| + it "has an expected value" do + expect(a_hash[:foo]).to eq("bar") + end + end + + group.it_behaves_like "shared context expects keywords", foo: "bar" + group.it_behaves_like "shared context expects keywords", { foo: "bar" } + + group.it_behaves_like "shared context expects hash", foo: "bar" + group.it_behaves_like "shared context expects hash", { foo: "bar" } + CODE + expect(group.run).to eq true + end + end + + if RSpec::Support::RubyFeatures.kw_args_supported? + it 'supports optional keyword arguments' do + binding.eval(<<-CODE, __FILE__, __LINE__) + group.__send__ shared_method_name, "shared context expects keywords" do |foo: nil| + it "has an expected value" do + expect(foo).to eq("bar") + end + end + + group.__send__ shared_method_name, "shared context expects hash" do |a_hash| + it "has an expected value" do + expect(a_hash[:foo]).to eq("bar") + end + end + + group.it_behaves_like "shared context expects keywords", foo: "bar" + group.it_behaves_like "shared context expects keywords", { foo: "bar" } + + group.it_behaves_like "shared context expects hash", foo: "bar" + group.it_behaves_like "shared context expects hash", { foo: "bar" } + CODE + expect(group.run).to eq true + end + end + it "displays a warning when adding an example group without a block", :unless => RUBY_VERSION == '1.8.7' do expect_warning_with_call_site(__FILE__, __LINE__ + 1) group.send(shared_method_name, 'name but no block') diff --git a/spec/rspec/core/world_spec.rb b/spec/rspec/core/world_spec.rb index c8714633e4..7c0b52a3e4 100644 --- a/spec/rspec/core/world_spec.rb +++ b/spec/rspec/core/world_spec.rb @@ -36,6 +36,14 @@ module RSpec::Core RSpec.world.reset }.to change(RSpec::ExampleGroups, :constants).to([]) end + + it 'clears #example_group_counts_by_spec_file' do + RSpec.describe "group" + + expect { + RSpec.world.reset + }.to change { world.example_group_counts_by_spec_file }.to be_empty + end end describe "#example_groups" do diff --git a/spec/support/helper_methods.rb b/spec/support/helper_methods.rb index 1ed3d7934c..c4a7027b85 100644 --- a/spec/support/helper_methods.rb +++ b/spec/support/helper_methods.rb @@ -15,8 +15,9 @@ def ignoring_warnings end # In Ruby 2.7 taint was removed and has no effect, whilst SAFE warns that it - # has no effect and will become a normal varible in 3.0. - if RUBY_VERSION >= '2.7' + # has no effect and will become a normal varible in 3.0. Other engines do not + # implement SAFE. + if RUBY_VERSION >= '2.7' || (defined?(RUBY_ENGINE) && RUBY_ENGINE != "ruby") def with_safe_set_to_level_that_triggers_security_errors yield end