From 4e87b6be08d297cdf3f2b184643dcecb45c857a5 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 19:51:44 +0300 Subject: [PATCH 01/39] [support] Simplify fork_supported? This commit was imported from https://github.com/rspec/rspec-support/commit/75149f319649a98cdb9dabd3485b96d5a98b76d6. --- rspec-support/lib/rspec/support/ruby_features.rb | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/rspec-support/lib/rspec/support/ruby_features.rb b/rspec-support/lib/rspec/support/ruby_features.rb index daba00ea5..74f7fbc52 100644 --- a/rspec-support/lib/rspec/support/ruby_features.rb +++ b/rspec-support/lib/rspec/support/ruby_features.rb @@ -60,20 +60,8 @@ def truffleruby? module RubyFeatures module_function - if Ruby.jruby? && RUBY_VERSION.to_f < 1.9 - # On JRuby 1.7 `--1.8` mode, `Process.respond_to?(:fork)` returns true, - # but when you try to fork, it raises an error: - # NotImplementedError: fork is not available on this platform - # - # When we drop support for JRuby 1.7 and/or Ruby 1.8, we can drop - # this special case. - def fork_supported? - false - end - else - def fork_supported? - Process.respond_to?(:fork) - end + def fork_supported? + Process.respond_to?(:fork) end def optional_and_splat_args_supported? From fc3704b6558bb810e8a6718eab984923d7281fdc Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 19:55:50 +0300 Subject: [PATCH 02/39] [support] Simplify ripper_supported? This commit was imported from https://github.com/rspec/rspec-support/commit/7c2c610be3a3dfba82404e95c300436cdbbf0ba0. --- .../lib/rspec/support/ruby_features.rb | 22 ++++++------------- .../spec/rspec/support/ruby_features_spec.rb | 7 ------ 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/rspec-support/lib/rspec/support/ruby_features.rb b/rspec-support/lib/rspec/support/ruby_features.rb index 74f7fbc52..dbffdda15 100644 --- a/rspec-support/lib/rspec/support/ruby_features.rb +++ b/rspec-support/lib/rspec/support/ruby_features.rb @@ -91,26 +91,18 @@ def supports_taint? true end end - ripper_requirements = [ComparableVersion.new(RUBY_VERSION) >= '1.9.2'] - ripper_requirements.push(false) if Ruby.rbx? - - if Ruby.jruby? - ripper_requirements.push(Ruby.jruby_version >= '1.7.5') - # Ripper on JRuby 9.0.0.0.rc1 - 9.1.8.0 reports wrong line number - # or cannot parse source including `:if`. - # Ripper on JRuby 9.x.x.x < 9.1.17.0 can't handle keyword arguments - # Neither can JRuby 9.2, e.g. < 9.2.1.0 - ripper_requirements.push(!Ruby.jruby_version.between?('9.0.0.0.rc1', '9.2.0.0')) - end - - if ripper_requirements.all? + # Ripper on JRuby 9.0.0.0.rc1 - 9.1.8.0 reports wrong line number + # or cannot parse source including `:if`. + # Ripper on JRuby 9.x.x.x < 9.1.17.0 can't handle keyword arguments + # Neither can JRuby prior to 9.2.1.0 + if Ruby.rbx? || (Ruby.jruby? && RSpec::Support::Ruby.jruby_version < '9.2.1.0') def ripper_supported? - true + false end else def ripper_supported? - false + true end end diff --git a/rspec-support/spec/rspec/support/ruby_features_spec.rb b/rspec-support/spec/rspec/support/ruby_features_spec.rb index b430e51dd..51293edc9 100644 --- a/rspec-support/spec/rspec/support/ruby_features_spec.rb +++ b/rspec-support/spec/rspec/support/ruby_features_spec.rb @@ -99,13 +99,6 @@ module Support RubyFeatures.supports_taint? end - specify "#caller_locations_supported? exists" do - RubyFeatures.caller_locations_supported? - if Ruby.mri? - expect(RubyFeatures.caller_locations_supported?).to eq(RUBY_VERSION >= '2.0.0') - end - end - describe "#ripper_supported?" do def ripper_is_implemented? in_sub_process_if_possible do From d1abc975656ae63a0599379e63a5b2b563980036 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 20:38:22 +0300 Subject: [PATCH 03/39] [support] Remove redundant jruby_9000? We always test against 9.0+, 9.2 actually, and 9.2 is recommended for all users, there's no excuse to be using 9.0 or 9.1 --- This commit was imported from https://github.com/rspec/rspec-support/commit/2f3489e6d8a13a3df7bdb8e5da6261b570cf77ed. --- rspec-support/lib/rspec/support/ruby_features.rb | 4 ---- rspec-support/spec/rspec/support/ruby_features_spec.rb | 10 ---------- 2 files changed, 14 deletions(-) diff --git a/rspec-support/lib/rspec/support/ruby_features.rb b/rspec-support/lib/rspec/support/ruby_features.rb index dbffdda15..1a568e1e6 100644 --- a/rspec-support/lib/rspec/support/ruby_features.rb +++ b/rspec-support/lib/rspec/support/ruby_features.rb @@ -32,10 +32,6 @@ def jruby_version @jruby_version ||= ComparableVersion.new(JRUBY_VERSION) end - def jruby_9000? - jruby? && JRUBY_VERSION >= '9.0.0.0' - end - def rbx? defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx' end diff --git a/rspec-support/spec/rspec/support/ruby_features_spec.rb b/rspec-support/spec/rspec/support/ruby_features_spec.rb index 51293edc9..24d13e844 100644 --- a/rspec-support/spec/rspec/support/ruby_features_spec.rb +++ b/rspec-support/spec/rspec/support/ruby_features_spec.rb @@ -48,16 +48,6 @@ module Support expect(Ruby).to_not be_rbx end - specify "jruby_9000? reflects the state of RUBY_PLATFORM and JRUBY_VERSION" do - stub_const("RUBY_PLATFORM", "java") - stub_const("JRUBY_VERSION", "") - expect(Ruby).to_not be_jruby_9000 - stub_const("JRUBY_VERSION", "9.0.3.0") - expect(Ruby).to be_jruby_9000 - stub_const("RUBY_PLATFORM", "") - expect(Ruby).to_not be_jruby_9000 - end - specify "rbx? reflects the state of RUBY_ENGINE" do hide_const("RUBY_ENGINE") expect(Ruby).to be_mri From 965e28904525f3980f04ac3efeb425df81515bab Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 20:44:38 +0300 Subject: [PATCH 04/39] [support] Remove kw_args_supported? This commit was imported from https://github.com/rspec/rspec-support/commit/3842f173791c6f533240716d2e57b61aa367f6d7. --- .../support/method_signature_verifier.rb | 9 +- .../lib/rspec/support/ruby_features.rb | 17 - .../support/with_keywords_when_needed.rb | 25 +- .../support/method_signature_verifier_spec.rb | 398 ++++++++---------- .../spec/rspec/support/ruby_features_spec.rb | 4 - .../support/with_keywords_when_needed_spec.rb | 6 +- 6 files changed, 189 insertions(+), 270 deletions(-) diff --git a/rspec-support/lib/rspec/support/method_signature_verifier.rb b/rspec-support/lib/rspec/support/method_signature_verifier.rb index 16736224e..cce30e4e3 100644 --- a/rspec-support/lib/rspec/support/method_signature_verifier.rb +++ b/rspec-support/lib/rspec/support/method_signature_verifier.rb @@ -301,13 +301,8 @@ def with_expectation(expectation) # rubocop:disable MethodLength, Metrics/Percei @unlimited_args = false end - if RubyFeatures.kw_args_supported? - @kw_args = expectation.keywords - @arbitrary_kw_args = expectation.expect_arbitrary_keywords - else - @kw_args = [] - @arbitrary_kw_args = false - end + @kw_args = expectation.keywords + @arbitrary_kw_args = expectation.expect_arbitrary_keywords end self diff --git a/rspec-support/lib/rspec/support/ruby_features.rb b/rspec-support/lib/rspec/support/ruby_features.rb index 1a568e1e6..52a123906 100644 --- a/rspec-support/lib/rspec/support/ruby_features.rb +++ b/rspec-support/lib/rspec/support/ruby_features.rb @@ -103,10 +103,6 @@ def ripper_supported? end if Ruby.mri? - def kw_args_supported? - RUBY_VERSION >= '2.0.0' - end - def required_kw_args_supported? RUBY_VERSION >= '2.1.0' end @@ -116,19 +112,6 @@ def supports_rebinding_module_methods? end else # RBX / JRuby et al support is unknown for keyword arguments - begin - eval("o = Object.new; def o.m(a: 1); end;"\ - " raise SyntaxError unless o.method(:m).parameters.include?([:key, :a])") - - def kw_args_supported? - true - end - rescue SyntaxError - def kw_args_supported? - false - end - end - begin eval("o = Object.new; def o.m(a: ); end;"\ "raise SyntaxError unless o.method(:m).parameters.include?([:keyreq, :a])") diff --git a/rspec-support/lib/rspec/support/with_keywords_when_needed.rb b/rspec-support/lib/rspec/support/with_keywords_when_needed.rb index 56b67e70b..6b50c897f 100644 --- a/rspec-support/lib/rspec/support/with_keywords_when_needed.rb +++ b/rspec-support/lib/rspec/support/with_keywords_when_needed.rb @@ -8,26 +8,17 @@ module WithKeywordsWhenNeeded module_function - if RSpec::Support::RubyFeatures.kw_args_supported? - # Remove this in RSpec 4 in favour of explictly passed in kwargs where - # this is used. Works around a warning in Ruby 2.7 - - def class_exec(klass, *args, &block) - if MethodSignature.new(block).has_kw_args_in?(args) - binding.eval(<<-CODE, __FILE__, __LINE__) - kwargs = args.pop - klass.class_exec(*args, **kwargs, &block) - CODE - else - klass.class_exec(*args, &block) - end - end - ruby2_keywords :class_exec if respond_to?(:ruby2_keywords, true) - else - def class_exec(klass, *args, &block) + # Remove this in RSpec 4 in favour of explictly passed in kwargs where + # this is used. Works around a warning in Ruby 2.7 + def class_exec(klass, *args, &block) + if MethodSignature.new(block).has_kw_args_in?(args) + kwargs = args.pop + klass.class_exec(*args, **kwargs, &block) + else klass.class_exec(*args, &block) end end + ruby2_keywords :class_exec if respond_to?(:ruby2_keywords, true) end end end diff --git a/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb b/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb index dd9b3c6ea..19f046992 100644 --- a/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb +++ b/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb @@ -93,25 +93,13 @@ def arity_two(x, y); end expect(validate_expectation :unlimited_args).to eq(false) end - if RubyFeatures.kw_args_supported? - it 'does not match keywords' do - expect(validate_expectation :optional_keyword).to eq(false) - expect(validate_expectation 2, :optional_keyword).to eq(false) - end - - it 'does not match arbitrary keywords' do - expect(validate_expectation :arbitrary_kw_args).to eq(false) - end - else - it 'ignores keyword expectations' do - expect(validate_expectation :optional_keyword).to eq(false) - expect(validate_expectation 2, :optional_keyword).to eq(true) - end + it 'does not match keywords' do + expect(validate_expectation :optional_keyword).to eq(false) + expect(validate_expectation 2, :optional_keyword).to eq(false) + end - it 'ignores arbitrary keyword expectations' do - expect(validate_expectation :arbitrary_kw_args).to eq(false) - expect(validate_expectation 2, :arbitrary_kw_args).to eq(true) - end + it 'does not match arbitrary keywords' do + expect(validate_expectation :arbitrary_kw_args).to eq(false) end end end @@ -166,26 +154,14 @@ def arity_splat(_, *); end end end - if RubyFeatures.kw_args_supported? - it 'does not match keywords' do - expect(validate_expectation :optional_keyword).to eq(false) - expect(validate_expectation 2, :optional_keyword).to eq(false) - end - - it 'does not match arbitrary keywords' do - expect(validate_expectation :arbitrary_kw_args).to eq(false) - expect(validate_expectation 2, :arbitrary_kw_args).to eq(false) - end - else - it 'ignores keyword expectations' do - expect(validate_expectation :optional_keyword).to eq(false) - expect(validate_expectation 2, :optional_keyword).to eq(true) - end + it 'does not match keywords' do + expect(validate_expectation :optional_keyword).to eq(false) + expect(validate_expectation 2, :optional_keyword).to eq(false) + end - it 'ignores arbitrary keyword expectations' do - expect(validate_expectation :arbitrary_kw_args).to eq(false) - expect(validate_expectation 2, :arbitrary_kw_args).to eq(true) - end + it 'does not match arbitrary keywords' do + expect(validate_expectation :arbitrary_kw_args).to eq(false) + expect(validate_expectation 2, :arbitrary_kw_args).to eq(false) end end end @@ -246,218 +222,200 @@ def arity_optional(x, y, z = 1); end expect(validate_expectation :unlimited_args).to eq(false) end - if RubyFeatures.kw_args_supported? - it 'does not match keywords' do - expect(validate_expectation :optional_keyword).to eq(false) - expect(validate_expectation 2, :optional_keyword).to eq(false) - end + it 'does not match keywords' do + expect(validate_expectation :optional_keyword).to eq(false) + expect(validate_expectation 2, :optional_keyword).to eq(false) + end - it 'does not match arbitrary keywords' do - expect(validate_expectation :arbitrary_kw_args).to eq(false) - end - else - it 'ignores keyword expectations' do - expect(validate_expectation :optional_keyword).to eq(false) + it 'does not match arbitrary keywords' do + expect(validate_expectation :arbitrary_kw_args).to eq(false) + end + end + end - expect(validate_expectation 2, :optional_keyword).to eq(true) - end + describe 'a method with optional keyword arguments' do + eval <<-RUBY + def arity_kw(x, y:1, z:2); end + RUBY - it 'ignores arbitrary keyword expectations' do - expect(validate_expectation :arbitrary_kw_args).to eq(false) + let(:test_method) { method(:arity_kw) } - expect(validate_expectation 2, :arbitrary_kw_args).to eq(true) - end - end + it 'does not require any of the arguments' do + expect(valid?(nil)).to eq(true) + expect(valid?(nil, nil)).to eq(false) end - end - if RubyFeatures.kw_args_supported? - describe 'a method with optional keyword arguments' do - eval <<-RUBY - def arity_kw(x, y:1, z:2); end - RUBY + it 'does not allow an invalid keyword arguments' do + expect(valid?(nil, :a => 1)).to eq(false) + end - let(:test_method) { method(:arity_kw) } + it 'mentions the invalid keyword args in the error', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do + expect(error_for(nil, :a => 0, :b => 1)).to \ + eq("Invalid keyword arguments provided: a, b") + end - it 'does not require any of the arguments' do - expect(valid?(nil)).to eq(true) - expect(valid?(nil, nil)).to eq(false) - end + it 'describes invalid arity precisely' do + expect(error_for()).to \ + eq("Wrong number of arguments. Expected 1, got 0.") + end - it 'does not allow an invalid keyword arguments' do - expect(valid?(nil, :a => 1)).to eq(false) - end + it 'does not blow up when given a BasicObject as the last arg' do + expect(valid?(BasicObject.new)).to eq(true) + end - it 'mentions the invalid keyword args in the error', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do - expect(error_for(nil, :a => 0, :b => 1)).to \ - eq("Invalid keyword arguments provided: a, b") - end + it 'does not mutate the provided args array' do + args = [nil, { :y => 1 }] + described_class.new(signature, args).valid? + expect(args).to eq([nil, { :y => 1 }]) + end - it 'describes invalid arity precisely' do - expect(error_for()).to \ - eq("Wrong number of arguments. Expected 1, got 0.") - end + it 'mentions the arity and optional kw args in the description', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do + expect(signature_description).to eq("arity of 1 and optional keyword args (:y, :z)") + end - it 'does not blow up when given a BasicObject as the last arg' do - expect(valid?(BasicObject.new)).to eq(true) - end + it "indicates the optional keyword args" do + expect(signature.optional_kw_args).to contain_exactly(:y, :z) + end - it 'does not mutate the provided args array' do - args = [nil, { :y => 1 }] - described_class.new(signature, args).valid? - expect(args).to eq([nil, { :y => 1 }]) - end + it "indicates it has no required keyword args" do + expect(signature.required_kw_args).to eq([]) + end - it 'mentions the arity and optional kw args in the description', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do - expect(signature_description).to eq("arity of 1 and optional keyword args (:y, :z)") + describe 'with an expectation object' do + it 'matches the exact arity' do + expect(validate_expectation 0).to eq(false) + expect(validate_expectation 1).to eq(true) + expect(validate_expectation 2).to eq(false) end - it "indicates the optional keyword args" do - expect(signature.optional_kw_args).to contain_exactly(:y, :z) + it 'matches the exact range' do + expect(validate_expectation 0, 1).to eq(false) + expect(validate_expectation 1, 1).to eq(true) + expect(validate_expectation 1, 2).to eq(false) end - it "indicates it has no required keyword args" do - expect(signature.required_kw_args).to eq([]) + it 'does not match unlimited arguments' do + expect(validate_expectation :unlimited_args).to eq(false) end - describe 'with an expectation object' do - it 'matches the exact arity' do - expect(validate_expectation 0).to eq(false) - expect(validate_expectation 1).to eq(true) - expect(validate_expectation 2).to eq(false) - end + it 'matches optional keywords with the correct arity' do + expect(validate_expectation :y).to eq(false) + expect(validate_expectation :z).to eq(false) + expect(validate_expectation :y, :z).to eq(false) - it 'matches the exact range' do - expect(validate_expectation 0, 1).to eq(false) - expect(validate_expectation 1, 1).to eq(true) - expect(validate_expectation 1, 2).to eq(false) - end + expect(validate_expectation 1, :y).to eq(true) + expect(validate_expectation 1, :z).to eq(true) + expect(validate_expectation 1, :y, :z).to eq(true) - it 'does not match unlimited arguments' do - expect(validate_expectation :unlimited_args).to eq(false) - end + expect(validate_expectation 2, :y).to eq(false) + expect(validate_expectation 2, :z).to eq(false) + expect(validate_expectation 2, :y, :z).to eq(false) + end - it 'matches optional keywords with the correct arity' do - expect(validate_expectation :y).to eq(false) - expect(validate_expectation :z).to eq(false) - expect(validate_expectation :y, :z).to eq(false) + it 'does not match invalid keywords' do + expect(validate_expectation :w).to eq(false) + expect(validate_expectation :w, :z).to eq(false) - expect(validate_expectation 1, :y).to eq(true) - expect(validate_expectation 1, :z).to eq(true) - expect(validate_expectation 1, :y, :z).to eq(true) + expect(validate_expectation 1, :w).to eq(false) + expect(validate_expectation 1, :w, :z).to eq(false) + end - expect(validate_expectation 2, :y).to eq(false) - expect(validate_expectation 2, :z).to eq(false) - expect(validate_expectation 2, :y, :z).to eq(false) - end + it 'does not match arbitrary keywords' do + expect(validate_expectation :arbitrary_kw_args).to eq(false) + end + end + end - it 'does not match invalid keywords' do - expect(validate_expectation :w).to eq(false) - expect(validate_expectation :w, :z).to eq(false) + describe 'a method with optional argument and keyword arguments' do + eval <<-RUBY + def arity_kw(x, y = {}, z:2); end + RUBY - expect(validate_expectation 1, :w).to eq(false) - expect(validate_expectation 1, :w, :z).to eq(false) - end + let(:test_method) { method(:arity_kw) } - it 'does not match arbitrary keywords' do - expect(validate_expectation :arbitrary_kw_args).to eq(false) - end - end + it 'does not require any of the arguments' do + expect(valid?(nil)).to eq(true) + expect(valid?(nil, nil)).to eq(true) end - end - if RubyFeatures.kw_args_supported? - describe 'a method with optional argument and keyword arguments' do - eval <<-RUBY - def arity_kw(x, y = {}, z:2); end - RUBY + it 'does not allow an invalid keyword arguments' do + expect(valid?(nil, nil, :a => 1)).to eq(false) + expect(valid?(nil, :a => 1)).to eq(false) + end - let(:test_method) { method(:arity_kw) } + it 'treats symbols as keyword arguments and the rest as optional argument' do + expect(valid?(nil, 'a' => 1)).to eq(true) + expect(valid?(nil, 'a' => 1, :z => 3)).to eq(true) + expect(valid?(nil, 'a' => 1, :b => 3)).to eq(false) + expect(valid?(nil, 'a' => 1, :b => 2, :z => 3)).to eq(false) + end - it 'does not require any of the arguments' do - expect(valid?(nil)).to eq(true) - expect(valid?(nil, nil)).to eq(true) - end + it 'mentions the invalid keyword args in the error', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do + expect(error_for(1, 2, :a => 0)).to eq("Invalid keyword arguments provided: a") + expect(error_for(1, :a => 0)).to eq("Invalid keyword arguments provided: a") + expect(error_for(1, 'a' => 0, :b => 0)).to eq("Invalid keyword arguments provided: b") + end - it 'does not allow an invalid keyword arguments' do - expect(valid?(nil, nil, :a => 1)).to eq(false) - expect(valid?(nil, :a => 1)).to eq(false) - end + it 'describes invalid arity precisely' do + expect(error_for()).to \ + eq("Wrong number of arguments. Expected 1 to 2, got 0.") + end - it 'treats symbols as keyword arguments and the rest as optional argument' do - expect(valid?(nil, 'a' => 1)).to eq(true) - expect(valid?(nil, 'a' => 1, :z => 3)).to eq(true) - expect(valid?(nil, 'a' => 1, :b => 3)).to eq(false) - expect(valid?(nil, 'a' => 1, :b => 2, :z => 3)).to eq(false) - end + it 'does not blow up when given a BasicObject as the last arg' do + expect(valid?(BasicObject.new)).to eq(true) + end - it 'mentions the invalid keyword args in the error', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do - expect(error_for(1, 2, :a => 0)).to eq("Invalid keyword arguments provided: a") - expect(error_for(1, :a => 0)).to eq("Invalid keyword arguments provided: a") - expect(error_for(1, 'a' => 0, :b => 0)).to eq("Invalid keyword arguments provided: b") - end + it 'does not mutate the provided args array' do + args = [nil, nil, { :y => 1 }] + described_class.new(signature, args).valid? + expect(args).to eq([nil, nil, { :y => 1 }]) + end - it 'describes invalid arity precisely' do - expect(error_for()).to \ - eq("Wrong number of arguments. Expected 1 to 2, got 0.") - end + it 'mentions the arity and optional kw args in the description', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do + expect(signature_description).to eq("arity of 1 to 2 and optional keyword args (:z)") + end - it 'does not blow up when given a BasicObject as the last arg' do - expect(valid?(BasicObject.new)).to eq(true) - end + it "indicates the optional keyword args" do + expect(signature.optional_kw_args).to contain_exactly(:z) + end - it 'does not mutate the provided args array' do - args = [nil, nil, { :y => 1 }] - described_class.new(signature, args).valid? - expect(args).to eq([nil, nil, { :y => 1 }]) - end + it "indicates it has no required keyword args" do + expect(signature.required_kw_args).to eq([]) + end - it 'mentions the arity and optional kw args in the description', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do - expect(signature_description).to eq("arity of 1 to 2 and optional keyword args (:z)") + describe 'with an expectation object' do + it 'matches the exact arity' do + expect(validate_expectation 0).to eq(false) + expect(validate_expectation 1).to eq(true) + expect(validate_expectation 2).to eq(true) end - it "indicates the optional keyword args" do - expect(signature.optional_kw_args).to contain_exactly(:z) + it 'matches the exact range' do + expect(validate_expectation 0, 1).to eq(false) + expect(validate_expectation 1, 1).to eq(true) + expect(validate_expectation 1, 2).to eq(true) + expect(validate_expectation 1, 3).to eq(false) end - it "indicates it has no required keyword args" do - expect(signature.required_kw_args).to eq([]) + it 'does not match unlimited arguments' do + expect(validate_expectation :unlimited_args).to eq(false) end - describe 'with an expectation object' do - it 'matches the exact arity' do - expect(validate_expectation 0).to eq(false) - expect(validate_expectation 1).to eq(true) - expect(validate_expectation 2).to eq(true) - end - - it 'matches the exact range' do - expect(validate_expectation 0, 1).to eq(false) - expect(validate_expectation 1, 1).to eq(true) - expect(validate_expectation 1, 2).to eq(true) - expect(validate_expectation 1, 3).to eq(false) - end - - it 'does not match unlimited arguments' do - expect(validate_expectation :unlimited_args).to eq(false) - end - - it 'matches optional keywords with the correct arity' do - expect(validate_expectation :z).to eq(false) - expect(validate_expectation 1, :z).to eq(true) # Are we OK with that? - expect(validate_expectation 1, 2, :z).to eq(true) - expect(validate_expectation 1, 2, :y).to eq(false) - end + it 'matches optional keywords with the correct arity' do + expect(validate_expectation :z).to eq(false) + expect(validate_expectation 1, :z).to eq(true) # Are we OK with that? + expect(validate_expectation 1, 2, :z).to eq(true) + expect(validate_expectation 1, 2, :y).to eq(false) + end - it 'does not match invalid keywords' do - expect(validate_expectation :w).to eq(false) + it 'does not match invalid keywords' do + expect(validate_expectation :w).to eq(false) - expect(validate_expectation 2, :w).to eq(false) - end + expect(validate_expectation 2, :w).to eq(false) + end - it 'does not match arbitrary keywords' do - expect(validate_expectation :arbitrary_kw_args).to eq(false) - end + it 'does not match arbitrary keywords' do + expect(validate_expectation :arbitrary_kw_args).to eq(false) end end end @@ -996,17 +954,15 @@ def arity_block(_, &block); end describe StrictSignatureVerifier do it_behaves_like 'a method verifier' - if RubyFeatures.kw_args_supported? - describe 'providing a matcher for optional keyword arguments' do - eval <<-RUBY - def arity_kw(x, y:1); end - RUBY + describe 'providing a matcher for optional keyword arguments' do + eval <<-RUBY + def arity_kw(x, y:1); end + RUBY - let(:test_method) { method(:arity_kw) } + let(:test_method) { method(:arity_kw) } - it 'is not allowed' do - expect(valid?(nil, fake_matcher)).to eq(false) - end + it 'is not allowed' do + expect(valid?(nil, fake_matcher)).to eq(false) end end @@ -1028,21 +984,19 @@ def arity_kw_required(x, y:); end describe LooseSignatureVerifier do it_behaves_like 'a method verifier' - if RubyFeatures.kw_args_supported? - describe 'for optional keyword arguments' do - eval <<-RUBY - def arity_kw(x, y:1, z:2); end - RUBY + describe 'for optional keyword arguments' do + eval <<-RUBY + def arity_kw(x, y:1, z:2); end + RUBY - let(:test_method) { method(:arity_kw) } + let(:test_method) { method(:arity_kw) } - it 'allows a matcher' do - expect(valid?(nil, fake_matcher)).to eq(true) - end + it 'allows a matcher' do + expect(valid?(nil, fake_matcher)).to eq(true) + end - it 'allows a matcher only for positional arguments' do - expect(valid?(fake_matcher)).to eq(true) - end + it 'allows a matcher only for positional arguments' do + expect(valid?(fake_matcher)).to eq(true) end end diff --git a/rspec-support/spec/rspec/support/ruby_features_spec.rb b/rspec-support/spec/rspec/support/ruby_features_spec.rb index 24d13e844..9cb16b278 100644 --- a/rspec-support/spec/rspec/support/ruby_features_spec.rb +++ b/rspec-support/spec/rspec/support/ruby_features_spec.rb @@ -73,10 +73,6 @@ module Support RubyFeatures.supports_exception_cause? end - specify "#kw_args_supported? exists" do - RubyFeatures.kw_args_supported? - end - specify "#required_kw_args_supported? exists" do RubyFeatures.required_kw_args_supported? end diff --git a/rspec-support/spec/rspec/support/with_keywords_when_needed_spec.rb b/rspec-support/spec/rspec/support/with_keywords_when_needed_spec.rb index 588bf385c..4a2a80a34 100644 --- a/rspec-support/spec/rspec/support/with_keywords_when_needed_spec.rb +++ b/rspec-support/spec/rspec/support/with_keywords_when_needed_spec.rb @@ -26,19 +26,19 @@ def run(klass, *args, &block) run(klass, "value" => 42) { |arg| check_argument(arg["value"]) } end - it "will run a block with optional keyword arguments when none are provided", :if => kw_args_supported? do + it "will run a block with optional keyword arguments when none are provided" do binding.eval(<<-CODE, __FILE__, __LINE__) run(klass, 42) { |arg, val: nil| check_argument(arg) } CODE end - it "will run a block with optional keyword arguments when they are provided", :if => required_kw_args_supported? do + it "will run a block with optional keyword arguments when they are provided" do binding.eval(<<-CODE, __FILE__, __LINE__) run(klass, val: 42) { |val: nil| check_argument(val) } CODE end - it "will run a block with required keyword arguments", :if => required_kw_args_supported? do + it "will run a block with required keyword arguments" do binding.eval(<<-CODE, __FILE__, __LINE__) run(klass, val: 42) { |val:| check_argument(val) } CODE From a9de71b36f3d787690243a6f72e76bb6f836d89b Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 21:16:11 +0300 Subject: [PATCH 05/39] [support] Remove supports_rebinding_module_methods? This commit was imported from https://github.com/rspec/rspec-support/commit/26ca803869f743525ccbb4fba117a9fff57d4a0a. --- rspec-support/lib/rspec/support.rb | 36 +++++-------------- .../lib/rspec/support/ruby_features.rb | 16 --------- .../spec/rspec/support/encoded_string_spec.rb | 3 +- .../spec/rspec/support/ruby_features_spec.rb | 4 --- rspec-support/spec/rspec/support_spec.rb | 9 ++--- 5 files changed, 12 insertions(+), 56 deletions(-) diff --git a/rspec-support/lib/rspec/support.rb b/rspec-support/lib/rspec/support.rb index 4bbbc64cd..e11581bf2 100644 --- a/rspec-support/lib/rspec/support.rb +++ b/rspec-support/lib/rspec/support.rb @@ -43,33 +43,15 @@ def self.define_optimized_require_for_rspec(lib, &require_relative) # - BasicObject subclasses that mixin a Kernel dup (e.g. SimpleDelegator) # - Objects that undefine method and delegate everything to another # object (e.g. Mongoid association objects) - if RubyFeatures.supports_rebinding_module_methods? - def self.method_handle_for(object, method_name) - KERNEL_METHOD_METHOD.bind(object).call(method_name) - rescue NameError => original - begin - handle = object.method(method_name) - raise original unless handle.is_a? Method - handle - rescue Support::AllExceptionsExceptOnesWeMustNotRescue - raise original - end - end - else - def self.method_handle_for(object, method_name) - if ::Kernel === object - KERNEL_METHOD_METHOD.bind(object).call(method_name) - else - object.method(method_name) - end - rescue NameError => original - begin - handle = object.method(method_name) - raise original unless handle.is_a? Method - handle - rescue Support::AllExceptionsExceptOnesWeMustNotRescue - raise original - end + def self.method_handle_for(object, method_name) + KERNEL_METHOD_METHOD.bind(object).call(method_name) + rescue NameError => original + begin + handle = object.method(method_name) + raise original unless handle.is_a? Method + handle + rescue Support::AllExceptionsExceptOnesWeMustNotRescue + raise original end end diff --git a/rspec-support/lib/rspec/support/ruby_features.rb b/rspec-support/lib/rspec/support/ruby_features.rb index 52a123906..236d077c9 100644 --- a/rspec-support/lib/rspec/support/ruby_features.rb +++ b/rspec-support/lib/rspec/support/ruby_features.rb @@ -106,10 +106,6 @@ def ripper_supported? def required_kw_args_supported? RUBY_VERSION >= '2.1.0' end - - def supports_rebinding_module_methods? - RUBY_VERSION.to_i >= 2 - end else # RBX / JRuby et al support is unknown for keyword arguments begin @@ -124,18 +120,6 @@ def required_kw_args_supported? false end end - - begin - Module.new { def foo; end }.instance_method(:foo).bind(Object.new) - - def supports_rebinding_module_methods? - true - end - rescue TypeError - def supports_rebinding_module_methods? - false - end - end end def module_refinement_supported? diff --git a/rspec-support/spec/rspec/support/encoded_string_spec.rb b/rspec-support/spec/rspec/support/encoded_string_spec.rb index feb7e83ce..c4e66b435 100644 --- a/rspec-support/spec/rspec/support/encoded_string_spec.rb +++ b/rspec-support/spec/rspec/support/encoded_string_spec.rb @@ -62,8 +62,7 @@ module RSpec::Support }.to raise_error(Encoding::InvalidByteSequenceError) end - # See JRuby issue https://github.com/jruby/jruby/issues/2580 - it 'replaces invalid byte sequences with the REPLACE string', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do + it 'replaces invalid byte sequences with the REPLACE string' do resulting_string = build_encoded_string(string, target_encoding).to_s replacement = EncodedString::REPLACE * 3 expected_string = forced_encoding("I have a bad byt#{replacement}", target_encoding) diff --git a/rspec-support/spec/rspec/support/ruby_features_spec.rb b/rspec-support/spec/rspec/support/ruby_features_spec.rb index 9cb16b278..83f3542db 100644 --- a/rspec-support/spec/rspec/support/ruby_features_spec.rb +++ b/rspec-support/spec/rspec/support/ruby_features_spec.rb @@ -77,10 +77,6 @@ module Support RubyFeatures.required_kw_args_supported? end - specify "#supports_rebinding_module_methods? exists" do - RubyFeatures.supports_rebinding_module_methods? - end - specify "#supports_taint?" do RubyFeatures.supports_taint? end diff --git a/rspec-support/spec/rspec/support_spec.rb b/rspec-support/spec/rspec/support_spec.rb index 2d7e5a5a3..ce4bf29f9 100644 --- a/rspec-support/spec/rspec/support_spec.rb +++ b/rspec-support/spec/rspec/support_spec.rb @@ -96,21 +96,16 @@ def method_missing(name, *args, &block) end end - it 'still works', :if => supports_rebinding_module_methods? do + it 'still works' do object = basic_class.new expect(Support.method_handle_for(object, :foo).call).to eq :bar end - it 'works when `method` has been overriden', :if => supports_rebinding_module_methods? do + it 'works when `method` has been overriden' do object = basic_class_with_method_override.new expect(Support.method_handle_for(object, :foo).call).to eq :bar end - it 'allows `method` to be proxied', :unless => supports_rebinding_module_methods? do - object = basic_class_with_proxying.new - expect(Support.method_handle_for(object, :reverse).call).to eq "oof" - end - it 'still works when Kernel has been mixed in' do object = basic_class_with_kernel.new expect(Support.method_handle_for(object, :foo).call).to eq :bar From 9243b1db2734042226476d7273fef75276bbb8ab Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 21:28:07 +0300 Subject: [PATCH 06/39] [support] Remove required_kw_args_supported? This commit was imported from https://github.com/rspec/rspec-support/commit/514ce68d9a321659bfcf001f45f1516ca212305c. --- .../lib/rspec/support/ruby_features.rb | 20 - .../support/method_signature_verifier_spec.rb | 524 +++++++++--------- .../spec/rspec/support/ruby_features_spec.rb | 4 - 3 files changed, 259 insertions(+), 289 deletions(-) diff --git a/rspec-support/lib/rspec/support/ruby_features.rb b/rspec-support/lib/rspec/support/ruby_features.rb index 236d077c9..4e88a965f 100644 --- a/rspec-support/lib/rspec/support/ruby_features.rb +++ b/rspec-support/lib/rspec/support/ruby_features.rb @@ -102,26 +102,6 @@ def ripper_supported? end end - if Ruby.mri? - def required_kw_args_supported? - RUBY_VERSION >= '2.1.0' - end - else - # RBX / JRuby et al support is unknown for keyword arguments - begin - eval("o = Object.new; def o.m(a: ); end;"\ - "raise SyntaxError unless o.method(:m).parameters.include?([:keyreq, :a])") - - def required_kw_args_supported? - true - end - rescue SyntaxError - def required_kw_args_supported? - false - end - end - end - def module_refinement_supported? Module.method_defined?(:refine) || Module.private_method_defined?(:refine) end diff --git a/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb b/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb index 19f046992..878149c44 100644 --- a/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb +++ b/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb @@ -420,325 +420,323 @@ def arity_kw(x, y = {}, z:2); end end end - if RubyFeatures.required_kw_args_supported? - describe 'a method with required keyword arguments' do - eval <<-RUBY - def arity_required_kw(x, y:, z:, a: 'default'); end - RUBY + describe 'a method with required keyword arguments' do + eval <<-RUBY + def arity_required_kw(x, y:, z:, a: 'default'); end + RUBY - let(:test_method) { method(:arity_required_kw) } + let(:test_method) { method(:arity_required_kw) } - it 'returns false unless all required keywords args are present' do - expect(valid?(nil, :a => 0, :y => 1, :z => 2)).to eq(true) - expect(valid?(nil, :a => 0, :y => 1)).to eq(false) - expect(valid?(nil, nil, :a => 0, :y => 1, :z => 2)).to eq(false) - expect(valid?(nil, nil)).to eq(false) - end + it 'returns false unless all required keywords args are present' do + expect(valid?(nil, :a => 0, :y => 1, :z => 2)).to eq(true) + expect(valid?(nil, :a => 0, :y => 1)).to eq(false) + expect(valid?(nil, nil, :a => 0, :y => 1, :z => 2)).to eq(false) + expect(valid?(nil, nil)).to eq(false) + end - it 'mentions the missing required keyword args in the error' do - expect(error_for(nil, :a => 0)).to \ - eq("Missing required keyword arguments: y, z") - end + it 'mentions the missing required keyword args in the error' do + expect(error_for(nil, :a => 0)).to \ + eq("Missing required keyword arguments: y, z") + end + + it 'is described precisely when arity is wrong' do + expect(error_for(nil, nil, :z => 0, :y => 1)).to \ + eq("Wrong number of arguments. Expected 1, got 2.") + end + + it 'mentions the arity, optional kw args and required kw args in the description' do + expect(signature_description).to \ + eq("arity of 1 and optional keyword args (:a) and required keyword args (:y, :z)") + end + + it "indicates the optional keyword args" do + expect(signature.optional_kw_args).to contain_exactly(:a) + end + + it "indicates the required keyword args" do + expect(signature.required_kw_args).to contain_exactly(:y, :z) + end - it 'is described precisely when arity is wrong' do - expect(error_for(nil, nil, :z => 0, :y => 1)).to \ - eq("Wrong number of arguments. Expected 1, got 2.") + describe 'with an expectation object' do + it 'does not match the exact arity without the required keywords' do + expect(validate_expectation 0).to eq(false) + expect(validate_expectation 1).to eq(false) + expect(validate_expectation 1, :y).to eq(false) + expect(validate_expectation 1, :z).to eq(false) + expect(validate_expectation 2).to eq(false) end - it 'mentions the arity, optional kw args and required kw args in the description' do - expect(signature_description).to \ - eq("arity of 1 and optional keyword args (:a) and required keyword args (:y, :z)") + it 'does not match the range without the required keywords' do + expect(validate_expectation 0, 1).to eq(false) + expect(validate_expectation 1, 1).to eq(false) + expect(validate_expectation 1, 1, :y).to eq(false) + expect(validate_expectation 1, 1, :z).to eq(false) + expect(validate_expectation 1, 2).to eq(false) end - it "indicates the optional keyword args" do - expect(signature.optional_kw_args).to contain_exactly(:a) + it 'matches the exact arity with the required keywords' do + expect(validate_expectation 0, :y, :z).to eq(false) + expect(validate_expectation 1, :y, :z).to eq(true) + expect(validate_expectation 2, :y, :z).to eq(false) end - it "indicates the required keyword args" do - expect(signature.required_kw_args).to contain_exactly(:y, :z) + it 'matches the range with the required keywords' do + expect(validate_expectation 0, 1, :y, :z).to eq(false) + expect(validate_expectation 1, 1, :y, :z).to eq(true) + expect(validate_expectation 1, 2, :y, :z).to eq(false) end - describe 'with an expectation object' do - it 'does not match the exact arity without the required keywords' do - expect(validate_expectation 0).to eq(false) - expect(validate_expectation 1).to eq(false) - expect(validate_expectation 1, :y).to eq(false) - expect(validate_expectation 1, :z).to eq(false) - expect(validate_expectation 2).to eq(false) - end + it 'does not match unlimited arguments' do + expect(validate_expectation :unlimited_args).to eq(false) + expect(validate_expectation :y, :z, :unlimited_args).to eq(false) + end - it 'does not match the range without the required keywords' do - expect(validate_expectation 0, 1).to eq(false) - expect(validate_expectation 1, 1).to eq(false) - expect(validate_expectation 1, 1, :y).to eq(false) - expect(validate_expectation 1, 1, :z).to eq(false) - expect(validate_expectation 1, 2).to eq(false) - end + it 'matches optional keywords with the required keywords' do + expect(validate_expectation 1, :a, :y, :z).to eq(true) + end - it 'matches the exact arity with the required keywords' do - expect(validate_expectation 0, :y, :z).to eq(false) - expect(validate_expectation 1, :y, :z).to eq(true) - expect(validate_expectation 2, :y, :z).to eq(false) - end + it 'does not match optional keywords without the required keywords' do + expect(validate_expectation :a).to eq(false) + expect(validate_expectation :a, :y).to eq(false) + expect(validate_expectation :a, :z).to eq(false) - it 'matches the range with the required keywords' do - expect(validate_expectation 0, 1, :y, :z).to eq(false) - expect(validate_expectation 1, 1, :y, :z).to eq(true) - expect(validate_expectation 1, 2, :y, :z).to eq(false) - end + expect(validate_expectation 1, :a).to eq(false) + expect(validate_expectation 1, :a, :y).to eq(false) + expect(validate_expectation 1, :a, :z).to eq(false) + end - it 'does not match unlimited arguments' do - expect(validate_expectation :unlimited_args).to eq(false) - expect(validate_expectation :y, :z, :unlimited_args).to eq(false) - end + it 'does not match invalid keywords' do + expect(validate_expectation :w).to eq(false) + expect(validate_expectation :w, :y, :z).to eq(false) - it 'matches optional keywords with the required keywords' do - expect(validate_expectation 1, :a, :y, :z).to eq(true) - end + expect(validate_expectation 1, :w).to eq(false) + expect(validate_expectation 1, :w, :y, :z).to eq(false) + end - it 'does not match optional keywords without the required keywords' do - expect(validate_expectation :a).to eq(false) - expect(validate_expectation :a, :y).to eq(false) - expect(validate_expectation :a, :z).to eq(false) + it 'does not match arbitrary keywords' do + expect(validate_expectation :arbitrary_kw_args).to eq(false) + expect(validate_expectation :y, :z, :arbitrary_kw_args).to eq(false) + end + end + end - expect(validate_expectation 1, :a).to eq(false) - expect(validate_expectation 1, :a, :y).to eq(false) - expect(validate_expectation 1, :a, :z).to eq(false) - end + describe 'a method with required keyword arguments and a splat' do + eval <<-RUBY + def arity_required_kw_splat(w, *x, y:, z:, a: 'default'); end + RUBY - it 'does not match invalid keywords' do - expect(validate_expectation :w).to eq(false) - expect(validate_expectation :w, :y, :z).to eq(false) + let(:test_method) { method(:arity_required_kw_splat) } - expect(validate_expectation 1, :w).to eq(false) - expect(validate_expectation 1, :w, :y, :z).to eq(false) - end + it 'returns false unless all required keywords args are present' do + expect(valid?(nil, :a => 0, :y => 1, :z => 2)).to eq(true) + expect(valid?(nil, :a => 0, :y => 1)).to eq(false) + expect(valid?(nil, nil, :a => 0, :y => 1, :z => 2)).to eq(true) + expect(valid?(nil, nil, nil)).to eq(false) + expect(valid?).to eq(false) + end - it 'does not match arbitrary keywords' do - expect(validate_expectation :arbitrary_kw_args).to eq(false) - expect(validate_expectation :y, :z, :arbitrary_kw_args).to eq(false) - end - end + it 'mentions missing required keyword args in the error' do + expect(error_for(nil, :y => 1)).to \ + eq("Missing required keyword arguments: z") end - describe 'a method with required keyword arguments and a splat' do - eval <<-RUBY - def arity_required_kw_splat(w, *x, y:, z:, a: 'default'); end - RUBY + it 'mentions the arity, optional kw args and required kw args in the description' do + expect(signature_description).to \ + eq("arity of 1 or more and optional keyword args (:a) and required keyword args (:y, :z)") + end - let(:test_method) { method(:arity_required_kw_splat) } + describe 'with an expectation object' do + it 'does not match a value from the lower bound upwards' do + expect(validate_expectation 0).to eq(false) + expect(validate_expectation 1).to eq(false) + expect(validate_expectation 1, :y).to eq(false) + expect(validate_expectation 1, :z).to eq(false) + expect(validate_expectation 2).to eq(false) + end - it 'returns false unless all required keywords args are present' do - expect(valid?(nil, :a => 0, :y => 1, :z => 2)).to eq(true) - expect(valid?(nil, :a => 0, :y => 1)).to eq(false) - expect(valid?(nil, nil, :a => 0, :y => 1, :z => 2)).to eq(true) - expect(valid?(nil, nil, nil)).to eq(false) - expect(valid?).to eq(false) + it 'does not match a range from the lower bound upwards' do + expect(validate_expectation 0, 1).to eq(false) + expect(validate_expectation 1, 1).to eq(false) + expect(validate_expectation 1, 1, :y).to eq(false) + expect(validate_expectation 1, 1, :z).to eq(false) + expect(validate_expectation 1, 2).to eq(false) end - it 'mentions missing required keyword args in the error' do - expect(error_for(nil, :y => 1)).to \ - eq("Missing required keyword arguments: z") + it 'matches a value from the lower bound upwards with the required keywords' do + expect(validate_expectation 0, :y, :z).to eq(false) + expect(validate_expectation 1, :y, :z).to eq(true) + expect(validate_expectation 2, :y, :z).to eq(true) + expect(validate_expectation 3, :y, :z).to eq(true) end - it 'mentions the arity, optional kw args and required kw args in the description' do - expect(signature_description).to \ - eq("arity of 1 or more and optional keyword args (:a) and required keyword args (:y, :z)") + it 'matches a range from the lower bound upwards with the required keywords' do + expect(validate_expectation 0, 1, :y, :z).to eq(false) + expect(validate_expectation 1, 1, :y, :z).to eq(true) + expect(validate_expectation 1, 2, :y, :z).to eq(true) + expect(validate_expectation 3, 4, :y, :z).to eq(true) + expect(validate_expectation 3, 2 ** 31, :y, :z).to eq(true) end - describe 'with an expectation object' do - it 'does not match a value from the lower bound upwards' do - expect(validate_expectation 0).to eq(false) - expect(validate_expectation 1).to eq(false) - expect(validate_expectation 1, :y).to eq(false) - expect(validate_expectation 1, :z).to eq(false) - expect(validate_expectation 2).to eq(false) - end + it 'matches unlimited arguments with the minimum arity and the required keywords' do + expect(validate_expectation :unlimited_args).to eq(false) + expect(validate_expectation 1, :unlimited_args).to eq(false) + expect(validate_expectation :y, :z, :unlimited_args).to eq(false) + expect(validate_expectation 1, :y, :z, :unlimited_args).to eq(true) + end - it 'does not match a range from the lower bound upwards' do - expect(validate_expectation 0, 1).to eq(false) - expect(validate_expectation 1, 1).to eq(false) - expect(validate_expectation 1, 1, :y).to eq(false) - expect(validate_expectation 1, 1, :z).to eq(false) - expect(validate_expectation 1, 2).to eq(false) - end + it 'matches optional keywords with the required keywords' do + expect(validate_expectation 1, :a, :y, :z).to eq(true) + end - it 'matches a value from the lower bound upwards with the required keywords' do - expect(validate_expectation 0, :y, :z).to eq(false) - expect(validate_expectation 1, :y, :z).to eq(true) - expect(validate_expectation 2, :y, :z).to eq(true) - expect(validate_expectation 3, :y, :z).to eq(true) - end + it 'does not match optional keywords without the required keywords' do + expect(validate_expectation :a).to eq(false) + expect(validate_expectation :a, :y).to eq(false) + expect(validate_expectation :a, :z).to eq(false) - it 'matches a range from the lower bound upwards with the required keywords' do - expect(validate_expectation 0, 1, :y, :z).to eq(false) - expect(validate_expectation 1, 1, :y, :z).to eq(true) - expect(validate_expectation 1, 2, :y, :z).to eq(true) - expect(validate_expectation 3, 4, :y, :z).to eq(true) - expect(validate_expectation 3, 2 ** 31, :y, :z).to eq(true) - end + expect(validate_expectation 1, :a).to eq(false) + expect(validate_expectation 1, :a, :y).to eq(false) + expect(validate_expectation 1, :a, :z).to eq(false) + end - it 'matches unlimited arguments with the minimum arity and the required keywords' do - expect(validate_expectation :unlimited_args).to eq(false) - expect(validate_expectation 1, :unlimited_args).to eq(false) - expect(validate_expectation :y, :z, :unlimited_args).to eq(false) - expect(validate_expectation 1, :y, :z, :unlimited_args).to eq(true) - end + it 'does not match invalid keywords' do + expect(validate_expectation :w).to eq(false) + expect(validate_expectation :w, :y, :z).to eq(false) - it 'matches optional keywords with the required keywords' do - expect(validate_expectation 1, :a, :y, :z).to eq(true) - end + expect(validate_expectation 1, :w).to eq(false) + expect(validate_expectation 1, :w, :y, :z).to eq(false) + end - it 'does not match optional keywords without the required keywords' do - expect(validate_expectation :a).to eq(false) - expect(validate_expectation :a, :y).to eq(false) - expect(validate_expectation :a, :z).to eq(false) + it 'does not match arbitrary keywords' do + expect(validate_expectation :arbitrary_kw_args).to eq(false) + expect(validate_expectation :y, :z, :arbitrary_kw_args).to eq(false) + end + end + end - expect(validate_expectation 1, :a).to eq(false) - expect(validate_expectation 1, :a, :y).to eq(false) - expect(validate_expectation 1, :a, :z).to eq(false) - end + describe 'a method with required keyword arguments and a keyword arg splat' do + eval <<-RUBY + def arity_kw_arg_splat(x:, **rest); end + RUBY - it 'does not match invalid keywords' do - expect(validate_expectation :w).to eq(false) - expect(validate_expectation :w, :y, :z).to eq(false) + let(:test_method) { method(:arity_kw_arg_splat) } - expect(validate_expectation 1, :w).to eq(false) - expect(validate_expectation 1, :w, :y, :z).to eq(false) - end + it 'allows extra undeclared keyword args' do + expect(valid?(:x => 1)).to eq(true) + expect(valid?(:x => 1, :y => 2)).to eq(true) + end - it 'does not match arbitrary keywords' do - expect(validate_expectation :arbitrary_kw_args).to eq(false) - expect(validate_expectation :y, :z, :arbitrary_kw_args).to eq(false) - end - end + it 'mentions missing required keyword args in the error' do + expect(error_for(:y => 1)).to \ + eq("Missing required keyword arguments: x") end - describe 'a method with required keyword arguments and a keyword arg splat' do - eval <<-RUBY - def arity_kw_arg_splat(x:, **rest); end - RUBY + it 'mentions the required kw args and keyword splat in the description' do + expect(signature_description).to \ + eq("required keyword args (:x) and any additional keyword args") + end - let(:test_method) { method(:arity_kw_arg_splat) } + describe 'with an expectation object' do + it 'does not match the exact arity without the required keywords' do + expect(validate_expectation 0).to eq(false) + expect(validate_expectation 1).to eq(false) + end - it 'allows extra undeclared keyword args' do - expect(valid?(:x => 1)).to eq(true) - expect(valid?(:x => 1, :y => 2)).to eq(true) + it 'does not match the exact range without the required keywords' do + expect(validate_expectation 0, 0).to eq(false) + expect(validate_expectation 0, 1).to eq(false) end - it 'mentions missing required keyword args in the error' do - expect(error_for(:y => 1)).to \ - eq("Missing required keyword arguments: x") + it 'matches the exact arity with the required keywords' do + expect(validate_expectation 0, :x).to eq(true) end - it 'mentions the required kw args and keyword splat in the description' do - expect(signature_description).to \ - eq("required keyword args (:x) and any additional keyword args") + it 'does matches the exact range without the required keywords' do + expect(validate_expectation 0, 0, :x).to eq(true) + expect(validate_expectation 0, 1, :x).to eq(false) end - describe 'with an expectation object' do - it 'does not match the exact arity without the required keywords' do - expect(validate_expectation 0).to eq(false) - expect(validate_expectation 1).to eq(false) - end + it 'does not match unlimited arguments' do + expect(validate_expectation :unlimited_args).to eq(false) + expect(validate_expectation :x, :unlimited_args).to eq(false) + end - it 'does not match the exact range without the required keywords' do - expect(validate_expectation 0, 0).to eq(false) - expect(validate_expectation 0, 1).to eq(false) - end + it 'matches arbitrary keywords with the required keywords' do + expect(validate_expectation 0, :x, :u, :v).to eq(true) + end - it 'matches the exact arity with the required keywords' do - expect(validate_expectation 0, :x).to eq(true) - end + it 'does not match arbitrary keywords without the required keywords' do + expect(validate_expectation :a).to eq(false) - it 'does matches the exact range without the required keywords' do - expect(validate_expectation 0, 0, :x).to eq(true) - expect(validate_expectation 0, 1, :x).to eq(false) - end + expect(validate_expectation 0, :a).to eq(false) + end - it 'does not match unlimited arguments' do - expect(validate_expectation :unlimited_args).to eq(false) - expect(validate_expectation :x, :unlimited_args).to eq(false) - end + it 'matches arbitrary keywords with the required keywords' do + expect(validate_expectation :arbitrary_kw_args).to eq(false) + expect(validate_expectation :x, :arbitrary_kw_args).to eq(true) + end + end + end - it 'matches arbitrary keywords with the required keywords' do - expect(validate_expectation 0, :x, :u, :v).to eq(true) - end + describe 'a method with a required arg and a keyword arg splat' do + eval <<-RUBY + def arity_kw_arg_splat(x, **rest); end + RUBY - it 'does not match arbitrary keywords without the required keywords' do - expect(validate_expectation :a).to eq(false) + let(:test_method) { method(:arity_kw_arg_splat) } - expect(validate_expectation 0, :a).to eq(false) - end + it 'allows a single arg and any number of keyword args' do + expect(valid?(nil)).to eq(true) + expect(valid?(nil, :x => 1)).to eq(true) + expect(valid?(nil, :x => 1, :y => 2)).to eq(true) + expect(valid?(:x => 1)).to eq(true) + expect(valid?(nil, {})).to eq(true) - it 'matches arbitrary keywords with the required keywords' do - expect(validate_expectation :arbitrary_kw_args).to eq(false) - expect(validate_expectation :x, :arbitrary_kw_args).to eq(true) - end - end + expect(valid?).to eq(false) + expect(valid?(nil, nil)).to eq(false) + expect(valid?(nil, nil, :x => 1)).to eq(false) end - describe 'a method with a required arg and a keyword arg splat' do - eval <<-RUBY - def arity_kw_arg_splat(x, **rest); end - RUBY - - let(:test_method) { method(:arity_kw_arg_splat) } + it 'describes the arity precisely' do + expect(error_for()).to \ + eq("Wrong number of arguments. Expected 1, got 0.") + end - it 'allows a single arg and any number of keyword args' do - expect(valid?(nil)).to eq(true) - expect(valid?(nil, :x => 1)).to eq(true) - expect(valid?(nil, :x => 1, :y => 2)).to eq(true) - expect(valid?(:x => 1)).to eq(true) - expect(valid?(nil, {})).to eq(true) + it 'mentions the required kw args and keyword splat in the description' do + expect(signature_description).to \ + eq("arity of 1 and any additional keyword args") + end - expect(valid?).to eq(false) - expect(valid?(nil, nil)).to eq(false) - expect(valid?(nil, nil, :x => 1)).to eq(false) + describe 'with an expectation object' do + it 'matches the exact arity' do + expect(validate_expectation 0).to eq(false) + expect(validate_expectation 1).to eq(true) + expect(validate_expectation 2).to eq(false) end - it 'describes the arity precisely' do - expect(error_for()).to \ - eq("Wrong number of arguments. Expected 1, got 0.") + it 'matches the exact range' do + expect(validate_expectation 0, 0).to eq(false) + expect(validate_expectation 0, 1).to eq(false) + expect(validate_expectation 1, 1).to eq(true) + expect(validate_expectation 1, 2).to eq(false) + expect(validate_expectation 2, 2).to eq(false) end - it 'mentions the required kw args and keyword splat in the description' do - expect(signature_description).to \ - eq("arity of 1 and any additional keyword args") + it 'does not match unlimited arguments' do + expect(validate_expectation :unlimited_args).to eq(false) + expect(validate_expectation 1, :unlimited_args).to eq(false) end - describe 'with an expectation object' do - it 'matches the exact arity' do - expect(validate_expectation 0).to eq(false) - expect(validate_expectation 1).to eq(true) - expect(validate_expectation 2).to eq(false) - end - - it 'matches the exact range' do - expect(validate_expectation 0, 0).to eq(false) - expect(validate_expectation 0, 1).to eq(false) - expect(validate_expectation 1, 1).to eq(true) - expect(validate_expectation 1, 2).to eq(false) - expect(validate_expectation 2, 2).to eq(false) - end - - it 'does not match unlimited arguments' do - expect(validate_expectation :unlimited_args).to eq(false) - expect(validate_expectation 1, :unlimited_args).to eq(false) - end - - it 'matches unlisted keywords with the required arity' do - expect(validate_expectation 1, :u, :v).to eq(true) - end + it 'matches unlisted keywords with the required arity' do + expect(validate_expectation 1, :u, :v).to eq(true) + end - it 'matches unlisted keywords with the exact range' do - expect(validate_expectation 1, 1, :u, :v).to eq(true) - end + it 'matches unlisted keywords with the exact range' do + expect(validate_expectation 1, 1, :u, :v).to eq(true) + end - it 'matches arbitrary keywords with the required arity' do - expect(validate_expectation :arbitrary_kw_args).to eq(false) - expect(validate_expectation 1, :arbitrary_kw_args).to eq(true) - end + it 'matches arbitrary keywords with the required arity' do + expect(validate_expectation :arbitrary_kw_args).to eq(false) + expect(validate_expectation 1, :arbitrary_kw_args).to eq(true) end end end @@ -966,17 +964,15 @@ def arity_kw(x, y:1); end end end - if RubyFeatures.required_kw_args_supported? - describe 'providing a matcher for required keyword arguments' do - eval <<-RUBY - def arity_kw_required(x, y:); end - RUBY + describe 'providing a matcher for required keyword arguments' do + eval <<-RUBY + def arity_kw_required(x, y:); end + RUBY - let(:test_method) { method(:arity_kw_required) } + let(:test_method) { method(:arity_kw_required) } - it 'is not allowed' do - expect(valid?(nil, fake_matcher)).to eq(false) - end + it 'is not allowed' do + expect(valid?(nil, fake_matcher)).to eq(false) end end end @@ -1000,17 +996,15 @@ def arity_kw(x, y:1, z:2); end end end - if RubyFeatures.required_kw_args_supported? - describe 'providing a matcher for required keyword arguments' do - eval <<-RUBY - def arity_kw_required(x, y:); end - RUBY + describe 'providing a matcher for required keyword arguments' do + eval <<-RUBY + def arity_kw_required(x, y:); end + RUBY - let(:test_method) { method(:arity_kw_required) } + let(:test_method) { method(:arity_kw_required) } - it 'is allowed' do - expect(valid?(nil, fake_matcher)).to eq(true) - end + it 'is allowed' do + expect(valid?(nil, fake_matcher)).to eq(true) end end end diff --git a/rspec-support/spec/rspec/support/ruby_features_spec.rb b/rspec-support/spec/rspec/support/ruby_features_spec.rb index 83f3542db..a6fa4327a 100644 --- a/rspec-support/spec/rspec/support/ruby_features_spec.rb +++ b/rspec-support/spec/rspec/support/ruby_features_spec.rb @@ -73,10 +73,6 @@ module Support RubyFeatures.supports_exception_cause? end - specify "#required_kw_args_supported? exists" do - RubyFeatures.required_kw_args_supported? - end - specify "#supports_taint?" do RubyFeatures.supports_taint? end From caec61b3b036a22273cfd55975bbffbe3d27d9fe Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 21:36:57 +0300 Subject: [PATCH 07/39] [support] Always use require_relative This commit was imported from https://github.com/rspec/rspec-support/commit/87dca939815e05e919be5033a31161c131c492ff. --- rspec-support/lib/rspec/support.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/rspec-support/lib/rspec/support.rb b/rspec-support/lib/rspec/support.rb index e11581bf2..614e5ea7d 100644 --- a/rspec-support/lib/rspec/support.rb +++ b/rspec-support/lib/rspec/support.rb @@ -14,14 +14,8 @@ module Support def self.define_optimized_require_for_rspec(lib, &require_relative) name = "require_rspec_#{lib}" - if Kernel.respond_to?(:require_relative) - (class << self; self; end).__send__(:define_method, name) do |f| - require_relative.call("#{lib}/#{f}") - end - else - (class << self; self; end).__send__(:define_method, name) do |f| - require "rspec/#{lib}/#{f}" - end + (class << self; self; end).__send__(:define_method, name) do |f| + require_relative.call("#{lib}/#{f}") end end From 2aa15cb027ec15ad504d62b3c05fde39185ffab4 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 22:00:06 +0300 Subject: [PATCH 08/39] [support] Remove optional_and_splat_args_supported? This commit was imported from https://github.com/rspec/rspec-support/commit/8109750a83cffd59305efb02c5795ab9087278f9. --- .../support/method_signature_verifier.rb | 173 +++++++----------- .../lib/rspec/support/ruby_features.rb | 4 - .../support/method_signature_verifier_spec.rb | 45 +---- 3 files changed, 74 insertions(+), 148 deletions(-) diff --git a/rspec-support/lib/rspec/support/method_signature_verifier.rb b/rspec-support/lib/rspec/support/method_signature_verifier.rb index cce30e4e3..45abe6eb7 100644 --- a/rspec-support/lib/rspec/support/method_signature_verifier.rb +++ b/rspec-support/lib/rspec/support/method_signature_verifier.rb @@ -45,116 +45,84 @@ def classify_arity(arity=@method.arity) end end - if RubyFeatures.optional_and_splat_args_supported? - def description - @description ||= begin - parts = [] + def description + @description ||= begin + parts = [] - unless non_kw_args_arity_description == "0" - parts << "arity of #{non_kw_args_arity_description}" - end - - if @optional_kw_args.any? - parts << "optional keyword args (#{@optional_kw_args.map(&:inspect).join(", ")})" - end - - if @required_kw_args.any? - parts << "required keyword args (#{@required_kw_args.map(&:inspect).join(", ")})" - end - - parts << "any additional keyword args" if @allows_any_kw_args - - parts.join(" and ") + unless non_kw_args_arity_description == "0" + parts << "arity of #{non_kw_args_arity_description}" end - end - - def missing_kw_args_from(given_kw_args) - @required_kw_args - given_kw_args - end - - def invalid_kw_args_from(given_kw_args) - return [] if @allows_any_kw_args - given_kw_args - @allowed_kw_args - end - - # If the last argument is Hash, Ruby will treat only symbol keys as keyword arguments - # the rest will be grouped in another Hash and passed as positional argument. - def has_kw_args_in?(args) - Hash === args.last && - could_contain_kw_args?(args) && - (args.last.empty? || args.last.keys.any? { |x| x.is_a?(Symbol) }) - end - # Without considering what the last arg is, could it - # contain keyword arguments? - def could_contain_kw_args?(args) - return false if args.count <= min_non_kw_args + if @optional_kw_args.any? + parts << "optional keyword args (#{@optional_kw_args.map(&:inspect).join(", ")})" + end - @allows_any_kw_args || @allowed_kw_args.any? - end + if @required_kw_args.any? + parts << "required keyword args (#{@required_kw_args.map(&:inspect).join(", ")})" + end - def arbitrary_kw_args? - @allows_any_kw_args - end + parts << "any additional keyword args" if @allows_any_kw_args - def unlimited_args? - @max_non_kw_args == INFINITY + parts.join(" and ") end + end - def classify_parameters - optional_non_kw_args = @min_non_kw_args = 0 - @allows_any_kw_args = false - - @method.parameters.each do |(type, name)| - case type - # def foo(a:) - when :keyreq then @required_kw_args << name - # def foo(a: 1) - when :key then @optional_kw_args << name - # def foo(**kw_args) - when :keyrest then @allows_any_kw_args = true - # def foo(a) - when :req then @min_non_kw_args += 1 - # def foo(a = 1) - when :opt then optional_non_kw_args += 1 - # def foo(*a) - when :rest then optional_non_kw_args = INFINITY - end - end + def missing_kw_args_from(given_kw_args) + @required_kw_args - given_kw_args + end - @max_non_kw_args = @min_non_kw_args + optional_non_kw_args - @allowed_kw_args = @required_kw_args + @optional_kw_args - end - else - def description - "arity of #{non_kw_args_arity_description}" - end + def invalid_kw_args_from(given_kw_args) + return [] if @allows_any_kw_args + given_kw_args - @allowed_kw_args + end - def missing_kw_args_from(_given_kw_args) - [] - end + # If the last argument is Hash, Ruby will treat only symbol keys as keyword arguments + # the rest will be grouped in another Hash and passed as positional argument. + def has_kw_args_in?(args) + Hash === args.last && + could_contain_kw_args?(args) && + (args.last.empty? || args.last.keys.any? { |x| x.is_a?(Symbol) }) + end - def invalid_kw_args_from(_given_kw_args) - [] - end + # Without considering what the last arg is, could it + # contain keyword arguments? + def could_contain_kw_args?(args) + return false if args.count <= min_non_kw_args - def has_kw_args_in?(_args) - false - end + @allows_any_kw_args || @allowed_kw_args.any? + end - def could_contain_kw_args?(*) - false - end + def arbitrary_kw_args? + @allows_any_kw_args + end - def arbitrary_kw_args? - false - end + def unlimited_args? + @max_non_kw_args == INFINITY + end - def unlimited_args? - false + def classify_parameters + optional_non_kw_args = @min_non_kw_args = 0 + @allows_any_kw_args = false + + @method.parameters.each do |(type, name)| + case type + # def foo(a:) + when :keyreq then @required_kw_args << name + # def foo(a: 1) + when :key then @optional_kw_args << name + # def foo(**kw_args) + when :keyrest then @allows_any_kw_args = true + # def foo(a) + when :req then @min_non_kw_args += 1 + # def foo(a = 1) + when :opt then optional_non_kw_args += 1 + # def foo(*a) + when :rest then optional_non_kw_args = INFINITY + end end - alias_method :classify_parameters, :classify_arity + @max_non_kw_args = @min_non_kw_args + optional_non_kw_args + @allowed_kw_args = @required_kw_args + @optional_kw_args end INFINITY = 1 / 0.0 @@ -163,8 +131,7 @@ def unlimited_args? if RSpec::Support::Ruby.jruby? # JRuby has only partial support for UnboundMethod#parameters, so we fall back on using #arity # https://github.com/jruby/jruby/issues/2816 and https://github.com/jruby/jruby/issues/2817 - if RubyFeatures.optional_and_splat_args_supported? && - Java::JavaLang::String.instance_method(:char_at).parameters == [] + if Java::JavaLang::String.instance_method(:char_at).parameters == [] class MethodSignature < remove_const(:MethodSignature) private @@ -264,11 +231,9 @@ def keywords=(values) # # @api private class BlockSignature < MethodSignature - if RubyFeatures.optional_and_splat_args_supported? - def classify_parameters - super - @min_non_kw_args = @max_non_kw_args unless @max_non_kw_args == INFINITY - end + def classify_parameters + super + @min_non_kw_args = @max_non_kw_args unless @max_non_kw_args == INFINITY end end @@ -294,13 +259,7 @@ def with_expectation(expectation) # rubocop:disable MethodLength, Metrics/Percei else @min_non_kw_args = @non_kw_args = expectation.min_count || 0 @max_non_kw_args = expectation.max_count || @min_non_kw_args - - if RubyFeatures.optional_and_splat_args_supported? - @unlimited_args = expectation.expect_unlimited_arguments - else - @unlimited_args = false - end - + @unlimited_args = expectation.expect_unlimited_arguments @kw_args = expectation.keywords @arbitrary_kw_args = expectation.expect_arbitrary_keywords end diff --git a/rspec-support/lib/rspec/support/ruby_features.rb b/rspec-support/lib/rspec/support/ruby_features.rb index 4e88a965f..34895c78a 100644 --- a/rspec-support/lib/rspec/support/ruby_features.rb +++ b/rspec-support/lib/rspec/support/ruby_features.rb @@ -60,10 +60,6 @@ def fork_supported? Process.respond_to?(:fork) end - def optional_and_splat_args_supported? - Method.method_defined?(:parameters) - end - def caller_locations_supported? respond_to?(:caller_locations, true) end diff --git a/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb b/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb index 878149c44..c6b45b4fa 100644 --- a/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb +++ b/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb @@ -140,18 +140,10 @@ def arity_splat(_, *); end expect(validate_expectation 1, 2 ** 31).to eq(true) end - if RubyFeatures.optional_and_splat_args_supported? - it 'matches unlimited arguments with the minimum arity' do - expect(validate_expectation :unlimited_args).to eq(false) - - expect(validate_expectation 1, :unlimited_args).to eq(true) - end - else - it 'ignores unlimited arguments expectations' do - expect(validate_expectation :unlimited_args).to eq(false) + it 'matches unlimited arguments with the minimum arity' do + expect(validate_expectation :unlimited_args).to eq(false) - expect(validate_expectation 1, :unlimited_args).to eq(true) - end + expect(validate_expectation 1, :unlimited_args).to eq(true) end it 'does not match keywords' do @@ -175,22 +167,11 @@ def arity_optional(x, y, z = 1); end expect(valid_non_kw_args?(1)).to eq(false) expect(valid_non_kw_args?(2)).to eq(true) expect(valid_non_kw_args?(3)).to eq(true) - - if RubyFeatures.optional_and_splat_args_supported? - expect(valid_non_kw_args?(4)).to eq(false) - else - expect(valid_non_kw_args?(4)).to eq(true) - end + expect(valid_non_kw_args?(4)).to eq(false) end - if RubyFeatures.optional_and_splat_args_supported? - it 'describes the arity as a range' do - expect(error_description).to eq("2 to 3") - end - else - it 'describes the arity with no upper bound' do - expect(error_description).to eq("2 or more") - end + it 'describes the arity as a range' do + expect(error_description).to eq("2 to 3") end describe 'with an expectation object' do @@ -198,24 +179,14 @@ def arity_optional(x, y, z = 1); end expect(validate_expectation 1).to eq(false) expect(validate_expectation 2).to eq(true) expect(validate_expectation 3).to eq(true) - - if RubyFeatures.optional_and_splat_args_supported? - expect(validate_expectation 4).to eq(false) - else - expect(validate_expectation 4).to eq(true) - end + expect(validate_expectation 4).to eq(false) end it 'matches a range from the lower bound upwards' do expect(validate_expectation 1, 2).to eq(false) expect(validate_expectation 2, 2).to eq(true) expect(validate_expectation 2, 3).to eq(true) - - if RubyFeatures.optional_and_splat_args_supported? - expect(validate_expectation 2, 4).to eq(false) - else - expect(validate_expectation 2, 4).to eq(true) - end + expect(validate_expectation 2, 4).to eq(false) end it 'does not match unlimited arguments' do From 0877d141c6e2d7050eefaa8c3368961b7bed570e Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 22:09:48 +0300 Subject: [PATCH 09/39] [support] Remove caller_locations_supported? This commit was imported from https://github.com/rspec/rspec-support/commit/ba17eca2630678804c84124e2d848ceb7f3c2063. --- .../lib/rspec/support/caller_filter.rb | 82 +++++++++---------- .../lib/rspec/support/ruby_features.rb | 4 - 2 files changed, 37 insertions(+), 49 deletions(-) diff --git a/rspec-support/lib/rspec/support/caller_filter.rb b/rspec-support/lib/rspec/support/caller_filter.rb index cd59a30f8..9559013bb 100644 --- a/rspec-support/lib/rspec/support/caller_filter.rb +++ b/rspec-support/lib/rspec/support/caller_filter.rb @@ -27,56 +27,48 @@ class CallerFilter # with this complexity in our `RSpec.deprecate` calls, so we ignore it here. IGNORE_REGEX = Regexp.union(LIB_REGEX, "rubygems/core_ext/kernel_require.rb") - if RSpec::Support::RubyFeatures.caller_locations_supported? - # This supports args because it's more efficient when the caller specifies - # these. It allows us to skip frames the caller knows are part of RSpec, - # and to decrease the increment size if the caller is confident the line will - # be found in a small number of stack frames from `skip_frames`. - # - # Note that there is a risk to passing a `skip_frames` value that is too high: - # If it skippped the first non-rspec line, then this method would return the - # 2nd or 3rd (or whatever) non-rspec line. Thus, you generally shouldn't pass - # values for these parameters, particularly since most places that use this are - # not hot spots (generally it gets used for deprecation warnings). However, - # if you do have a hot spot that calls this, passing `skip_frames` can make - # a significant difference. Just make sure that that particular use is tested - # so that if the provided `skip_frames` changes to no longer be accurate in - # such a way that would return the wrong stack frame, a test will fail to tell you. - # - # See benchmarks/skip_frames_for_caller_filter.rb for measurements. - def self.first_non_rspec_line(skip_frames=3, increment=5) - # Why a default `skip_frames` of 3? - # By the time `caller_locations` is called below, the first 3 frames are: - # lib/rspec/support/caller_filter.rb:63:in `block in first_non_rspec_line' - # lib/rspec/support/caller_filter.rb:62:in `loop' - # lib/rspec/support/caller_filter.rb:62:in `first_non_rspec_line' + # This supports args because it's more efficient when the caller specifies + # these. It allows us to skip frames the caller knows are part of RSpec, + # and to decrease the increment size if the caller is confident the line will + # be found in a small number of stack frames from `skip_frames`. + # + # Note that there is a risk to passing a `skip_frames` value that is too high: + # If it skippped the first non-rspec line, then this method would return the + # 2nd or 3rd (or whatever) non-rspec line. Thus, you generally shouldn't pass + # values for these parameters, particularly since most places that use this are + # not hot spots (generally it gets used for deprecation warnings). However, + # if you do have a hot spot that calls this, passing `skip_frames` can make + # a significant difference. Just make sure that that particular use is tested + # so that if the provided `skip_frames` changes to no longer be accurate in + # such a way that would return the wrong stack frame, a test will fail to tell you. + # + # See benchmarks/skip_frames_for_caller_filter.rb for measurements. + def self.first_non_rspec_line(skip_frames=3, increment=5) + # Why a default `skip_frames` of 3? + # By the time `caller_locations` is called below, the first 3 frames are: + # lib/rspec/support/caller_filter.rb:63:in `block in first_non_rspec_line' + # lib/rspec/support/caller_filter.rb:62:in `loop' + # lib/rspec/support/caller_filter.rb:62:in `first_non_rspec_line' - # `caller` is an expensive method that scales linearly with the size of - # the stack. The performance hit for fetching it in chunks is small, - # and since the target line is probably near the top of the stack, the - # overall improvement of a chunked search like this is significant. - # - # See benchmarks/caller.rb for measurements. + # `caller` is an expensive method that scales linearly with the size of + # the stack. The performance hit for fetching it in chunks is small, + # and since the target line is probably near the top of the stack, the + # overall improvement of a chunked search like this is significant. + # + # See benchmarks/caller.rb for measurements. - # The default increment of 5 for this method are mostly arbitrary, but - # is chosen to give good performance on the common case of creating a double. + # The default increment of 5 for this method are mostly arbitrary, but + # is chosen to give good performance on the common case of creating a double. - loop do - stack = caller_locations(skip_frames, increment) - raise "No non-lib lines in stack" unless stack + loop do + stack = caller_locations(skip_frames, increment) + raise "No non-lib lines in stack" unless stack - line = stack.find { |l| l.path !~ IGNORE_REGEX } - return line.to_s if line + line = stack.find { |l| l.path !~ IGNORE_REGEX } + return line.to_s if line - skip_frames += increment - increment *= 2 # The choice of two here is arbitrary. - end - end - else - # Earlier rubies do not support the two argument form of `caller`. This - # fallback is logically the same, but slower. - def self.first_non_rspec_line(*) - caller.find { |line| line !~ IGNORE_REGEX } + skip_frames += increment + increment *= 2 # The choice of two here is arbitrary. end end end diff --git a/rspec-support/lib/rspec/support/ruby_features.rb b/rspec-support/lib/rspec/support/ruby_features.rb index 34895c78a..6991bdc74 100644 --- a/rspec-support/lib/rspec/support/ruby_features.rb +++ b/rspec-support/lib/rspec/support/ruby_features.rb @@ -60,10 +60,6 @@ def fork_supported? Process.respond_to?(:fork) end - def caller_locations_supported? - respond_to?(:caller_locations, true) - end - if Exception.method_defined?(:cause) def supports_exception_cause? true From ee9ed5684a072c87d3e075c5830d057b5a4cb816 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 22:18:35 +0300 Subject: [PATCH 10/39] [support] Remove supports_exception_cause? This commit was imported from https://github.com/rspec/rspec-support/commit/a3727620c3729a01a24141f28cf1b4af507048ef. --- rspec-support/lib/rspec/support/ruby_features.rb | 10 ---------- rspec-support/spec/rspec/support/ruby_features_spec.rb | 4 ---- 2 files changed, 14 deletions(-) diff --git a/rspec-support/lib/rspec/support/ruby_features.rb b/rspec-support/lib/rspec/support/ruby_features.rb index 6991bdc74..1240596b8 100644 --- a/rspec-support/lib/rspec/support/ruby_features.rb +++ b/rspec-support/lib/rspec/support/ruby_features.rb @@ -60,16 +60,6 @@ def fork_supported? Process.respond_to?(:fork) end - if Exception.method_defined?(:cause) - def supports_exception_cause? - true - end - else - def supports_exception_cause? - false - end - end - if RUBY_VERSION.to_f >= 2.7 def supports_taint? false diff --git a/rspec-support/spec/rspec/support/ruby_features_spec.rb b/rspec-support/spec/rspec/support/ruby_features_spec.rb index a6fa4327a..45c927442 100644 --- a/rspec-support/spec/rspec/support/ruby_features_spec.rb +++ b/rspec-support/spec/rspec/support/ruby_features_spec.rb @@ -69,10 +69,6 @@ module Support RubyFeatures.fork_supported? end - specify "#supports_exception_cause? exists" do - RubyFeatures.supports_exception_cause? - end - specify "#supports_taint?" do RubyFeatures.supports_taint? end From ef067a03a0b90e8d30038d3a38dbc831e67f4658 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 22:18:50 +0300 Subject: [PATCH 11/39] [support] Remove unused module_refinement_supported? This commit was imported from https://github.com/rspec/rspec-support/commit/67f560da10b23dca8445e96fa925e6c3d90b4f77. --- rspec-support/lib/rspec/support/ruby_features.rb | 4 ---- rspec-support/spec/rspec/support/ruby_features_spec.rb | 6 ------ 2 files changed, 10 deletions(-) diff --git a/rspec-support/lib/rspec/support/ruby_features.rb b/rspec-support/lib/rspec/support/ruby_features.rb index 1240596b8..15f256bfa 100644 --- a/rspec-support/lib/rspec/support/ruby_features.rb +++ b/rspec-support/lib/rspec/support/ruby_features.rb @@ -84,10 +84,6 @@ def ripper_supported? end end - def module_refinement_supported? - Module.method_defined?(:refine) || Module.private_method_defined?(:refine) - end - def module_prepends_supported? Module.method_defined?(:prepend) || Module.private_method_defined?(:prepend) end diff --git a/rspec-support/spec/rspec/support/ruby_features_spec.rb b/rspec-support/spec/rspec/support/ruby_features_spec.rb index 45c927442..57a45d789 100644 --- a/rspec-support/spec/rspec/support/ruby_features_spec.rb +++ b/rspec-support/spec/rspec/support/ruby_features_spec.rb @@ -59,12 +59,6 @@ module Support end describe RubyFeatures do - specify "#module_refinement_supported? reflects refinement support" do - if Ruby.mri? && RUBY_VERSION >= '2.1.0' - expect(RubyFeatures.module_refinement_supported?).to eq true - end - end - specify "#fork_supported? exists" do RubyFeatures.fork_supported? end From 61437f1733009a80c2c4ce6afe2ca99626523263 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 22:23:54 +0300 Subject: [PATCH 12/39] [support] Remove JRuby arity check workaround Java::JavaLang::String.instance_method(:char_at).arity evaluates to 1 on JRuby 9.2(.13.0) --- This commit was imported from https://github.com/rspec/rspec-support/commit/c6257e976ea2da82bcb29f226d118e032a7dcf88. --- .../support/method_signature_verifier.rb | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/rspec-support/lib/rspec/support/method_signature_verifier.rb b/rspec-support/lib/rspec/support/method_signature_verifier.rb index 45abe6eb7..a8b002f9b 100644 --- a/rspec-support/lib/rspec/support/method_signature_verifier.rb +++ b/rspec-support/lib/rspec/support/method_signature_verifier.rb @@ -144,37 +144,6 @@ def classify_parameters end end end - - # JRuby used to always report -1 arity for Java proxy methods. - # The workaround essentially makes use of Java's introspection to figure - # out matching methods (which could be more than one partly because Java - # supports multiple overloads, and partly because JRuby introduces - # aliases to make method names look more Rubyesque). If there is only a - # single match, we can use that methods arity directly instead of the - # default -1 arity. - # - # This workaround only works for Java proxy methods, and in order to - # support regular methods and blocks, we need to be careful about calling - # owner and java_class as they might not be available - if Java::JavaLang::String.instance_method(:char_at).arity == -1 - class MethodSignature < remove_const(:MethodSignature) - private - - def classify_parameters - super - return unless @method.arity == -1 - return unless @method.respond_to?(:owner) - return unless @method.owner.respond_to?(:java_class) - java_instance_methods = @method.owner.java_class.java_instance_methods - compatible_overloads = java_instance_methods.select do |java_method| - @method == @method.owner.instance_method(java_method.name) - end - if compatible_overloads.size == 1 - classify_arity(compatible_overloads.first.arity) - end - end - end - end end # Encapsulates expectations about the number of arguments and From 99c8e51e90af4706f82cd2f87a34c60bd463c73c Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 22:30:59 +0300 Subject: [PATCH 13/39] [support] Remove 1.8.7-specific workaround This commit was imported from https://github.com/rspec/rspec-support/commit/2a9c58a45f7d8735a1a07e400011c2751ff3fcf7. --- rspec-support/lib/rspec/support/spec/stderr_splitter.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rspec-support/lib/rspec/support/spec/stderr_splitter.rb b/rspec-support/lib/rspec/support/spec/stderr_splitter.rb index 03b50e939..a7db18f7f 100644 --- a/rspec-support/lib/rspec/support/spec/stderr_splitter.rb +++ b/rspec-support/lib/rspec/support/spec/stderr_splitter.rb @@ -9,9 +9,8 @@ def initialize(original) @last_line = nil end - respond_to_name = (::RUBY_VERSION.to_f < 1.9) ? :respond_to? : :respond_to_missing? - define_method respond_to_name do |*args| - @orig_stderr.respond_to?(*args) || super(*args) + def respond_to_missing?(*args) + @orig_stderr.respond_to?(*args) || super end def method_missing(name, *args, &block) From 141c93e021c77f909e1845236353a90a848578ee Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 22:31:35 +0300 Subject: [PATCH 14/39] [support] Remove JRuby in 1.8.7 compat mode workaround This commit was imported from https://github.com/rspec/rspec-support/commit/cd8b41ea3c740ead0243dd5bfcdd0ccf0d00cde3. --- rspec-support/lib/rspec/support/spec/in_sub_process.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rspec-support/lib/rspec/support/spec/in_sub_process.rb b/rspec-support/lib/rspec/support/spec/in_sub_process.rb index 85196997c..367b1dab6 100644 --- a/rspec-support/lib/rspec/support/spec/in_sub_process.rb +++ b/rspec-support/lib/rspec/support/spec/in_sub_process.rb @@ -1,7 +1,7 @@ module RSpec module Support module InSubProcess - if Process.respond_to?(:fork) && !(Ruby.jruby? && RUBY_VERSION == '1.8.7') + if Process.respond_to?(:fork) UnmarshableObject = Struct.new(:error) From 94cd6c8931a4c24d2ca22b3b609cba6156a3e7d6 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 22:33:31 +0300 Subject: [PATCH 15/39] [support] Remove 1.9 Open3 workaround This commit was imported from https://github.com/rspec/rspec-support/commit/ad44a2e4a7d45710b36506ec1c3a6fb6003f5517. --- .../lib/rspec/support/spec/shell_out.rb | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/rspec-support/lib/rspec/support/spec/shell_out.rb b/rspec-support/lib/rspec/support/spec/shell_out.rb index d9d2afcaf..2c987ddb6 100644 --- a/rspec-support/lib/rspec/support/spec/shell_out.rb +++ b/rspec-support/lib/rspec/support/spec/shell_out.rb @@ -16,26 +16,9 @@ def with_env(vars) end end - if Open3.respond_to?(:capture3) # 1.9+ - def shell_out(*command) - stdout, stderr, status = Open3.capture3(*command) - return stdout, filter(stderr), status - end - else # 1.8.7 - # popen3 doesn't provide the exit status so we fake it out. - FakeProcessStatus = Struct.new(:exitstatus) - - def shell_out(*command) - stdout = stderr = nil - - Open3.popen3(*command) do |_in, out, err| - stdout = out.read - stderr = err.read - end - - status = FakeProcessStatus.new(0) - return stdout, filter(stderr), status - end + def shell_out(*command) + stdout, stderr, status = Open3.capture3(*command) + return stdout, filter(stderr), status end def run_ruby_with_current_load_path(ruby_command, *flags) From 4d8a3ea70e58ae6d39f9d61e2701e1ab7353d137 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 22:36:20 +0300 Subject: [PATCH 16/39] [support] Remove module_prepends_supported? This commit was imported from https://github.com/rspec/rspec-support/commit/ffd451896800782a2926cc8bc87555a944e875cc. --- rspec-support/lib/rspec/support/ruby_features.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rspec-support/lib/rspec/support/ruby_features.rb b/rspec-support/lib/rspec/support/ruby_features.rb index 15f256bfa..cad74da0e 100644 --- a/rspec-support/lib/rspec/support/ruby_features.rb +++ b/rspec-support/lib/rspec/support/ruby_features.rb @@ -83,10 +83,6 @@ def ripper_supported? true end end - - def module_prepends_supported? - Module.method_defined?(:prepend) || Module.private_method_defined?(:prepend) - end end end end From 5c4954d2a9ed597cebfbcb585f4f775190e75a9b Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 22:58:50 +0300 Subject: [PATCH 17/39] [support] Remove String#encoding workarounds This commit was imported from https://github.com/rspec/rspec-support/commit/1d1270df46e07d8530f472d86e9f6a0a720deb53. --- rspec-support/lib/rspec/support/differ.rb | 10 +- .../lib/rspec/support/encoded_string.rb | 172 ++++---- rspec-support/lib/rspec/support/source.rb | 15 +- .../lib/rspec/support/spec/string_matcher.rb | 46 +-- .../spec/rspec/support/differ_spec.rb | 89 ++-- .../spec/rspec/support/encoded_string_spec.rb | 379 +++++++++--------- 6 files changed, 315 insertions(+), 396 deletions(-) diff --git a/rspec-support/lib/rspec/support/differ.rb b/rspec-support/lib/rspec/support/differ.rb index b81184239..30ae540c4 100644 --- a/rspec-support/lib/rspec/support/differ.rb +++ b/rspec-support/lib/rspec/support/differ.rb @@ -102,14 +102,8 @@ def diffably_stringify(array) end end - if String.method_defined?(:encoding) - def multiline?(string) - string.include?("\n".encode(string.encoding)) - end - else - def multiline?(string) - string.include?("\n") - end + def multiline?(string) + string.include?("\n".encode(string.encoding)) end def build_hunks(actual, expected) diff --git a/rspec-support/lib/rspec/support/encoded_string.rb b/rspec-support/lib/rspec/support/encoded_string.rb index 13c323503..942de79a5 100644 --- a/rspec-support/lib/rspec/support/encoded_string.rb +++ b/rspec-support/lib/rspec/support/encoded_string.rb @@ -46,115 +46,81 @@ def to_s end alias :to_str :to_s - if String.method_defined?(:encoding) - - private - - # Encoding Exceptions: + private + + # Encoding Exceptions: + # + # Raised by Encoding and String methods: + # Encoding::UndefinedConversionError: + # when a transcoding operation fails + # if the String contains characters invalid for the target encoding + # e.g. "\x80".encode('UTF-8','ASCII-8BIT') + # vs "\x80".encode('UTF-8','ASCII-8BIT', undef: :replace, replace: '') + # # => '' + # Encoding::CompatibilityError + # when Encoding.compatibile?(str1, str2) is nil + # e.g. utf_16le_emoji_string.split("\n") + # e.g. valid_unicode_string.encode(utf8_encoding) << ascii_string + # Encoding::InvalidByteSequenceError: + # when the string being transcoded contains a byte invalid for + # either the source or target encoding + # e.g. "\x80".encode('UTF-8','US-ASCII') + # vs "\x80".encode('UTF-8','US-ASCII', invalid: :replace, replace: '') + # # => '' + # ArgumentError + # when operating on a string with invalid bytes + # e.g."\x80".split("\n") + # TypeError + # when a symbol is passed as an encoding + # Encoding.find(:"UTF-8") + # when calling force_encoding on an object + # that doesn't respond to #to_str + # + # Raised by transcoding methods: + # Encoding::ConverterNotFoundError: + # when a named encoding does not correspond with a known converter + # e.g. 'abc'.force_encoding('UTF-8').encode('foo') + # or a converter path cannot be found + # e.g. "\x80".force_encoding('ASCII-8BIT').encode('Emacs-Mule') + # + # Raised by byte <-> char conversions + # RangeError: out of char range + # e.g. the UTF-16LE emoji: 128169.chr + def matching_encoding(string) + string = remove_invalid_bytes(string) + string.encode(@encoding) + rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError + # Originally defined as a constant to avoid uneeded allocations, this hash must + # be defined inline (without {}) to avoid warnings on Ruby 2.7 # - # Raised by Encoding and String methods: - # Encoding::UndefinedConversionError: - # when a transcoding operation fails - # if the String contains characters invalid for the target encoding - # e.g. "\x80".encode('UTF-8','ASCII-8BIT') - # vs "\x80".encode('UTF-8','ASCII-8BIT', undef: :replace, replace: '') - # # => '' - # Encoding::CompatibilityError - # when Encoding.compatibile?(str1, str2) is nil - # e.g. utf_16le_emoji_string.split("\n") - # e.g. valid_unicode_string.encode(utf8_encoding) << ascii_string - # Encoding::InvalidByteSequenceError: - # when the string being transcoded contains a byte invalid for - # either the source or target encoding - # e.g. "\x80".encode('UTF-8','US-ASCII') - # vs "\x80".encode('UTF-8','US-ASCII', invalid: :replace, replace: '') - # # => '' - # ArgumentError - # when operating on a string with invalid bytes - # e.g."\x80".split("\n") - # TypeError - # when a symbol is passed as an encoding - # Encoding.find(:"UTF-8") - # when calling force_encoding on an object - # that doesn't respond to #to_str + # In MRI 2.1 'invalid: :replace' changed to also replace an invalid byte sequence + # see https://github.com/ruby/ruby/blob/v2_1_0/NEWS#L176 + # https://www.ruby-forum.com/topic/6861247 + # https://twitter.com/nalsh/status/553413844685438976 # - # Raised by transcoding methods: - # Encoding::ConverterNotFoundError: - # when a named encoding does not correspond with a known converter - # e.g. 'abc'.force_encoding('UTF-8').encode('foo') - # or a converter path cannot be found - # e.g. "\x80".force_encoding('ASCII-8BIT').encode('Emacs-Mule') + # For example, given: + # "\x80".force_encoding("Emacs-Mule").encode(:invalid => :replace).bytes.to_a # - # Raised by byte <-> char conversions - # RangeError: out of char range - # e.g. the UTF-16LE emoji: 128169.chr - def matching_encoding(string) - string = remove_invalid_bytes(string) - string.encode(@encoding) - rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError - # Originally defined as a constant to avoid uneeded allocations, this hash must - # be defined inline (without {}) to avoid warnings on Ruby 2.7 - # - # In MRI 2.1 'invalid: :replace' changed to also replace an invalid byte sequence - # see https://github.com/ruby/ruby/blob/v2_1_0/NEWS#L176 - # https://www.ruby-forum.com/topic/6861247 - # https://twitter.com/nalsh/status/553413844685438976 - # - # For example, given: - # "\x80".force_encoding("Emacs-Mule").encode(:invalid => :replace).bytes.to_a - # - # On MRI 2.1 or above: 63 # '?' - # else : 128 # "\x80" - # - string.encode(@encoding, :invalid => :replace, :undef => :replace, :replace => REPLACE) - rescue Encoding::ConverterNotFoundError - # Originally defined as a constant to avoid uneeded allocations, this hash must - # be defined inline (without {}) to avoid warnings on Ruby 2.7 - string.dup.force_encoding(@encoding).encode(:invalid => :replace, :replace => REPLACE) - end - - # Prevents raising ArgumentError - if String.method_defined?(:scrub) - # https://github.com/ruby/ruby/blob/eeb05e8c11/doc/NEWS-2.1.0#L120-L123 - # https://github.com/ruby/ruby/blob/v2_1_0/string.c#L8242 - # https://github.com/hsbt/string-scrub - # https://github.com/rubinius/rubinius/blob/v2.5.2/kernel/common/string.rb#L1913-L1972 - def remove_invalid_bytes(string) - string.scrub(REPLACE) - end - else - # http://stackoverflow.com/a/8711118/879854 - # Loop over chars in a string replacing chars - # with invalid encoding, which is a pretty good proxy - # for the invalid byte sequence that causes an ArgumentError - def remove_invalid_bytes(string) - string.chars.map do |char| - char.valid_encoding? ? char : REPLACE - end.join - end - end - - def detect_source_encoding(string) - string.encoding - end - - def self.pick_encoding(source_a, source_b) - Encoding.compatible?(source_a, source_b) || Encoding.default_external - end - else - - def self.pick_encoding(_source_a, _source_b) - end + # On MRI 2.1 or above: 63 # '?' + # else : 128 # "\x80" + # + string.encode(@encoding, :invalid => :replace, :undef => :replace, :replace => REPLACE) + rescue Encoding::ConverterNotFoundError + # Originally defined as a constant to avoid uneeded allocations, this hash must + # be defined inline (without {}) to avoid warnings on Ruby 2.7 + string.dup.force_encoding(@encoding).encode(:invalid => :replace, :replace => REPLACE) + end - private + def remove_invalid_bytes(string) + string.scrub(REPLACE) + end - def matching_encoding(string) - string - end + def detect_source_encoding(string) + string.encoding + end - def detect_source_encoding(_string) - US_ASCII - end + def self.pick_encoding(source_a, source_b) + Encoding.compatible?(source_a, source_b) || Encoding.default_external end end end diff --git a/rspec-support/lib/rspec/support/source.rb b/rspec-support/lib/rspec/support/source.rb index ec0e1aeb1..ee1846a89 100644 --- a/rspec-support/lib/rspec/support/source.rb +++ b/rspec-support/lib/rspec/support/source.rb @@ -23,18 +23,9 @@ def self.from_file(path) new(source, path) end - if String.method_defined?(:encoding) - def initialize(source_string, path=nil) - @source = RSpec::Support::EncodedString.new(source_string, Encoding.default_external) - @path = path ? File.expand_path(path) : '(string)' - end - else # for 1.8.7 - # :nocov: - def initialize(source_string, path=nil) - @source = RSpec::Support::EncodedString.new(source_string) - @path = path ? File.expand_path(path) : '(string)' - end - # :nocov: + def initialize(source_string, path=nil) + @source = RSpec::Support::EncodedString.new(source_string, Encoding.default_external) + @path = path ? File.expand_path(path) : '(string)' end def lines diff --git a/rspec-support/lib/rspec/support/spec/string_matcher.rb b/rspec-support/lib/rspec/support/spec/string_matcher.rb index 7df319918..c1386a3dd 100644 --- a/rspec-support/lib/rspec/support/spec/string_matcher.rb +++ b/rspec-support/lib/rspec/support/spec/string_matcher.rb @@ -4,36 +4,26 @@ # which also relies on EncodedString. Instead, confirm the # strings have the same bytes. RSpec::Matchers.define :be_identical_string do |expected| - if String.method_defined?(:encoding) - match do - expected_encoding? && - actual.bytes.to_a == expected.bytes.to_a - end - - failure_message do - "expected\n#{actual.inspect} (#{actual.encoding.name}) to be identical to\n"\ - "#{expected.inspect} (#{expected.encoding.name})\n"\ - "The exact bytes are printed below for more detail:\n"\ - "#{actual.bytes.to_a}\n"\ - "#{expected.bytes.to_a}\n"\ - end + match do + expected_encoding? && + actual.bytes.to_a == expected.bytes.to_a + end - # Depends on chaining :with_same_encoding for it to - # check for string encoding. - def expected_encoding? - if defined?(@expect_same_encoding) && @expect_same_encoding - actual.encoding == expected.encoding - else - true - end - end - else - match do - actual.split(//) == expected.split(//) - end + failure_message do + "expected\n#{actual.inspect} (#{actual.encoding.name}) to be identical to\n"\ + "#{expected.inspect} (#{expected.encoding.name})\n"\ + "The exact bytes are printed below for more detail:\n"\ + "#{actual.bytes.to_a}\n"\ + "#{expected.bytes.to_a}\n"\ + end - failure_message do - "expected\n#{actual.inspect} to be identical to\n#{expected.inspect}\n" + # Depends on chaining :with_same_encoding for it to + # check for string encoding. + def expected_encoding? + if defined?(@expect_same_encoding) && @expect_same_encoding + actual.encoding == expected.encoding + else + true end end diff --git a/rspec-support/spec/rspec/support/differ_spec.rb b/rspec-support/spec/rspec/support/differ_spec.rb index cbd1ed28f..720380597 100644 --- a/rspec-support/spec/rspec/support/differ_spec.rb +++ b/rspec-support/spec/rspec/support/differ_spec.rb @@ -122,59 +122,56 @@ def differ_ivars end ] end - if String.method_defined?(:encoding) - it "returns an empty string if strings are not multiline" do - expected = "Tu avec carte {count} item has".encode('UTF-16LE') - actual = "Tu avec cartรฉ {count} itรฉm has".encode('UTF-16LE') + it "returns an empty string if strings are not multiline" do + expected = "Tu avec carte {count} item has".encode('UTF-16LE') + actual = "Tu avec cartรฉ {count} itรฉm has".encode('UTF-16LE') + diff = differ.diff(actual, expected) + expect(diff).to be_empty + end - diff = differ.diff(actual, expected) - expect(diff).to be_empty - end - - it 'copes with encoded strings', :skip => RSpec::Support::OS.windows? do - expected = "Tu avec carte {count} item has\n".encode('UTF-16LE') - actual = "Tu avec cartรฉ {count} itรฉm has\n".encode('UTF-16LE') - expected_diff = dedent(<<-EOD).encode('UTF-16LE') - | - |@@ #{one_line_header} @@ - |-Tu avec carte {count} item has - |+Tu avec cartรฉ {count} itรฉm has - | - EOD + it 'copes with encoded strings', :skip => RSpec::Support::OS.windows? do + expected = "Tu avec carte {count} item has\n".encode('UTF-16LE') + actual = "Tu avec cartรฉ {count} itรฉm has\n".encode('UTF-16LE') + expected_diff = dedent(<<-EOD).encode('UTF-16LE') + | + |@@ #{one_line_header} @@ + |-Tu avec carte {count} item has + |+Tu avec cartรฉ {count} itรฉm has + | + EOD - diff = differ.diff(actual, expected) - expect(diff).to be_diffed_as(expected_diff) - end + diff = differ.diff(actual, expected) + expect(diff).to be_diffed_as(expected_diff) + end - it 'handles differently encoded strings that are compatible' do - expected = "abc\n".encode('us-ascii') - actual = "๊ฐ•์ธ์ฒ \n".encode('UTF-8') - expected_diff = "\n@@ #{one_line_header} @@\n-abc\n+๊ฐ•์ธ์ฒ \n" - diff = differ.diff(actual, expected) - expect(diff).to be_diffed_as(expected_diff) - end + it 'handles differently encoded strings that are compatible' do + expected = "abc\n".encode('us-ascii') + actual = "๊ฐ•์ธ์ฒ \n".encode('UTF-8') + expected_diff = "\n@@ #{one_line_header} @@\n-abc\n+๊ฐ•์ธ์ฒ \n" + diff = differ.diff(actual, expected) + expect(diff).to be_diffed_as(expected_diff) + end - it 'uses the default external encoding when the two strings have incompatible encodings' do - expected = "Tu avec carte {count} item has\n" - actual = "Tu avec cartรฉ {count} itรฉm has\n".encode('UTF-16LE') - expected_diff = "\n@@ #{one_line_header} @@\n-Tu avec carte {count} item has\n+Tu avec cartรฉ {count} itรฉm has\n" + it 'uses the default external encoding when the two strings have incompatible encodings' do + expected = "Tu avec carte {count} item has\n" + actual = "Tu avec cartรฉ {count} itรฉm has\n".encode('UTF-16LE') + expected_diff = "\n@@ #{one_line_header} @@\n-Tu avec carte {count} item has\n+Tu avec cartรฉ {count} itรฉm has\n" - diff = differ.diff(actual, expected) - expect(diff).to be_diffed_as(expected_diff) - expect(diff.encoding).to eq(Encoding.default_external) - end + diff = differ.diff(actual, expected) + expect(diff).to be_diffed_as(expected_diff) + expect(diff.encoding).to eq(Encoding.default_external) + end - it 'handles any encoding error that occurs with a helpful error message' do - expect(RSpec::Support::HunkGenerator).to receive(:new). - and_raise(Encoding::CompatibilityError) - expected = "Tu avec carte {count} item has\n".encode('us-ascii') - actual = "Tu avec cartรฉ {count} itรฉm has\n" - diff = differ.diff(actual, expected) - expect(diff).to match(/Could not produce a diff/) - expect(diff).to match(/actual string \(UTF-8\)/) - expect(diff).to match(/expected string \(US-ASCII\)/) - end + it 'handles any encoding error that occurs with a helpful error message' do + expect(RSpec::Support::HunkGenerator).to receive(:new). + and_raise(Encoding::CompatibilityError) + expected = "Tu avec carte {count} item has\n".encode('us-ascii') + actual = "Tu avec cartรฉ {count} itรฉm has\n" + diff = differ.diff(actual, expected) + expect(diff).to match(/Could not produce a diff/) + expect(diff).to match(/actual string \(UTF-8\)/) + expect(diff).to match(/expected string \(US-ASCII\)/) end it "outputs unified diff message of two objects" do diff --git a/rspec-support/spec/rspec/support/encoded_string_spec.rb b/rspec-support/spec/rspec/support/encoded_string_spec.rb index c4e66b435..3bb821ed6 100644 --- a/rspec-support/spec/rspec/support/encoded_string_spec.rb +++ b/rspec-support/spec/rspec/support/encoded_string_spec.rb @@ -15,253 +15,234 @@ module RSpec::Support end end - describe '::pick_encoding' do - if String.method_defined?(:encoding) - it "picks the default external encoding for incompatible encodings" do - - str1 = forced_encoding("\xa1", "iso-8859-1") - str2 = forced_encoding("\xa1\xa1", "euc-jp") - expect(Encoding.compatible?(str1, str2)).to be_nil - expect(EncodedString.pick_encoding(str1, str2)).to eq(Encoding.default_external) - end + describe '.pick_encoding' do + it "picks the default external encoding for incompatible encodings" do - # https://github.com/rubyspec/rubyspec/blob/91ce9f6549/core/encoding/compatible_spec.rb#L31 - it "picks a compatible encoding" do - str1 = forced_encoding "abc", Encoding::US_ASCII - str2 = "\u3042".encode("utf-8") - expect(EncodedString.pick_encoding(str1, str2)).to eq(Encoding::UTF_8) - end - else - it "returns nil" do - str1 = "\xa1" - str2 = "\xa1\xa1" - expect(EncodedString.pick_encoding(str1, str2)).to be_nil - end + str1 = forced_encoding("\xa1", "iso-8859-1") + str2 = forced_encoding("\xa1\xa1", "euc-jp") + expect(Encoding.compatible?(str1, str2)).to be_nil + expect(EncodedString.pick_encoding(str1, str2)).to eq(Encoding.default_external) + end + + # https://github.com/rubyspec/rubyspec/blob/91ce9f6549/core/encoding/compatible_spec.rb#L31 + it "picks a compatible encoding" do + str1 = forced_encoding "abc", Encoding::US_ASCII + str2 = "\u3042".encode("utf-8") + expect(EncodedString.pick_encoding(str1, str2)).to eq(Encoding::UTF_8) end end - if String.method_defined?(:encoding) + describe '#source_encoding' do + it 'knows the original encoding of the string' do + str = EncodedString.new("abc".encode('ASCII-8BIT'), "UTF-8") + expect(str.source_encoding.to_s).to eq('ASCII-8BIT') + end + end - describe '#source_encoding' do - it 'knows the original encoding of the string' do - str = EncodedString.new("abc".encode('ASCII-8BIT'), "UTF-8") - expect(str.source_encoding.to_s).to eq('ASCII-8BIT') + describe '#to_s' do + context 'when encoding a string with invalid bytes in the target encoding' do + # see https://github.com/jruby/jruby/blob/c1be61a501/test/mri/ruby/test_transcode.rb#L13 + let(:source_encoding) { Encoding.find('US-ASCII') } + let(:target_encoding) { Encoding.find('UTF-8') } + let(:string) { forced_encoding("I have a bad bytรฉ\x80", source_encoding) } + + it 'normally raises an EncodedString::InvalidByteSequenceError' do + expect { + string.encode(target_encoding) + }.to raise_error(Encoding::InvalidByteSequenceError) end - end - describe '#to_s' do - context 'when encoding a string with invalid bytes in the target encoding' do - # see https://github.com/jruby/jruby/blob/c1be61a501/test/mri/ruby/test_transcode.rb#L13 - let(:source_encoding) { Encoding.find('US-ASCII') } - let(:target_encoding) { Encoding.find('UTF-8') } - let(:string) { forced_encoding("I have a bad bytรฉ\x80", source_encoding) } + it 'replaces invalid byte sequences with the REPLACE string' do + resulting_string = build_encoded_string(string, target_encoding).to_s + replacement = EncodedString::REPLACE * 3 + expected_string = forced_encoding("I have a bad byt#{replacement}", target_encoding) + expect(resulting_string).to be_identical_string(expected_string).with_same_encoding + end + end - it 'normally raises an EncodedString::InvalidByteSequenceError' do - expect { - string.encode(target_encoding) - }.to raise_error(Encoding::InvalidByteSequenceError) - end + context 'when no converter is known for an encoding' do + # see https://github.com/rubyspec/rubyspec/blob/91ce9f6549/core/string/shared/encode.rb#L12 + let(:source_encoding) { Encoding.find('ASCII-8BIT') } + let(:no_converter_encoding) { Encoding::Emacs_Mule } + let(:string) { forced_encoding("\x80", source_encoding) } - it 'replaces invalid byte sequences with the REPLACE string' do - resulting_string = build_encoded_string(string, target_encoding).to_s - replacement = EncodedString::REPLACE * 3 - expected_string = forced_encoding("I have a bad byt#{replacement}", target_encoding) - expect(resulting_string).to be_identical_string(expected_string).with_same_encoding - end + it 'normally raises an Encoding::ConverterNotFoundError' do + expect { + string.encode(no_converter_encoding) + }.to raise_error(Encoding::ConverterNotFoundError) end - context 'when no converter is known for an encoding' do - # see https://github.com/rubyspec/rubyspec/blob/91ce9f6549/core/string/shared/encode.rb#L12 - let(:source_encoding) { Encoding.find('ASCII-8BIT') } - let(:no_converter_encoding) { Encoding::Emacs_Mule } - let(:string) { forced_encoding("\x80", source_encoding) } - - it 'normally raises an Encoding::ConverterNotFoundError' do - expect { - string.encode(no_converter_encoding) - }.to raise_error(Encoding::ConverterNotFoundError) + # See comment above ENCODE_UNCONVERTABLE_BYTES in encoded_string.rb + # for why the behavior differs by (MRI) Ruby version. + if RUBY_VERSION < '2.1' + it 'does nothing' do + resulting_string = build_encoded_string(string, no_converter_encoding).to_s + expected_string = forced_encoding("\x80", no_converter_encoding) + expect(resulting_string).to be_identical_string(expected_string).with_same_encoding end - - # See comment above ENCODE_UNCONVERTABLE_BYTES in encoded_string.rb - # for why the behavior differs by (MRI) Ruby version. - if RUBY_VERSION < '2.1' - it 'does nothing' do - resulting_string = build_encoded_string(string, no_converter_encoding).to_s - expected_string = forced_encoding("\x80", no_converter_encoding) - expect(resulting_string).to be_identical_string(expected_string).with_same_encoding - end - else - it 'forces the encoding and replaces invalid characters with the REPLACE string' do - resulting_string = build_encoded_string(string, no_converter_encoding).to_s - expected_string = forced_encoding(EncodedString::REPLACE, no_converter_encoding) - expect(resulting_string).to be_identical_string(expected_string).with_same_encoding - end - - it 'does not mutate the input string' do - expect { - build_encoded_string(string, no_converter_encoding) - }.not_to change { [string, string.encoding] } - end + else + it 'forces the encoding and replaces invalid characters with the REPLACE string' do + resulting_string = build_encoded_string(string, no_converter_encoding).to_s + expected_string = forced_encoding(EncodedString::REPLACE, no_converter_encoding) + expect(resulting_string).to be_identical_string(expected_string).with_same_encoding end - end - # see https://github.com/ruby/ruby/blob/34fbf57aaa/transcode.c#L4289 - # ISO-8859-1 -> UTF-8 -> EUC-JP - # "\xa0" NO-BREAK SPACE, which is available in UTF-8 but not in EUC-JP - context 'when there is an undefined conversion to the target encoding' do - let(:source_encoding) { Encoding.find('ISO-8859-1') } - let(:incompatible_encoding) { Encoding.find('EUC-JP') } - let(:string) { forced_encoding("\xa0 hi I am not going to work", source_encoding) } - - it 'normally raises an Encoding::UndefinedConversionError' do + it 'does not mutate the input string' do expect { - string.encode(incompatible_encoding) - }.to raise_error(Encoding::UndefinedConversionError) - end - - it 'replaces all undefines conversions with the REPLACE string' do - resulting_string = build_encoded_string(string, incompatible_encoding).to_s - replacement = EncodedString::REPLACE - expected_string = forced_encoding("#{replacement} hi I am not going to work", 'EUC-JP') - expect(resulting_string).to be_identical_string(expected_string).with_same_encoding + build_encoded_string(string, no_converter_encoding) + }.not_to change { [string, string.encoding] } end end end - let(:ascii_arrow_symbol) { "\xAE" } - let(:utf_8_euro_symbol) { "\xE2\x82\xAC" } - - describe '#<<' do - context 'with strings that can be converted to the target encoding' do - let(:valid_ascii_string) { forced_encoding("abcde", "ASCII-8BIT") } - let(:valid_unicode_string) { forced_encoding(utf_8_euro_symbol, 'UTF-8') } - - it 'encodes and appends the string' do - resulting_string = build_encoded_string(valid_unicode_string, utf8_encoding) << valid_ascii_string - expected_string = forced_encoding("#{utf_8_euro_symbol}abcde", 'UTF-8') - expect(resulting_string).to be_identical_string(expected_string).with_same_encoding - end + # see https://github.com/ruby/ruby/blob/34fbf57aaa/transcode.c#L4289 + # ISO-8859-1 -> UTF-8 -> EUC-JP + # "\xa0" NO-BREAK SPACE, which is available in UTF-8 but not in EUC-JP + context 'when there is an undefined conversion to the target encoding' do + let(:source_encoding) { Encoding.find('ISO-8859-1') } + let(:incompatible_encoding) { Encoding.find('EUC-JP') } + let(:string) { forced_encoding("\xa0 hi I am not going to work", source_encoding) } + + it 'normally raises an Encoding::UndefinedConversionError' do + expect { + string.encode(incompatible_encoding) + }.to raise_error(Encoding::UndefinedConversionError) end - context 'with a string that cannot be converted to the target encoding' do - context 'when appending a string with an incompatible character encoding' do - let(:ascii_string) { forced_encoding(ascii_arrow_symbol, "ASCII-8BIT") } - let(:valid_unicode_string) { forced_encoding(utf_8_euro_symbol, 'UTF-8') } - - it "normally raises an Encoding::CompatibilityError" do - expect { - valid_unicode_string.encode(utf8_encoding) << ascii_string - }.to raise_error(Encoding::CompatibilityError) - end - - it 'replaces unconvertable characters with the REPLACE string' do - resulting_string = build_encoded_string(valid_unicode_string, utf8_encoding) << ascii_string - expected_string = "#{utf_8_euro_symbol}#{EncodedString::REPLACE}" - expect(resulting_string).to be_identical_string(expected_string).with_same_encoding - end - end + it 'replaces all undefines conversions with the REPLACE string' do + resulting_string = build_encoded_string(string, incompatible_encoding).to_s + replacement = EncodedString::REPLACE + expected_string = forced_encoding("#{replacement} hi I am not going to work", 'EUC-JP') + expect(resulting_string).to be_identical_string(expected_string).with_same_encoding end + end + end - context 'with two ascii strings with a target encoding of UTF-8 ' do - it 'has an encoding of UTF-8' do - ascii_string = forced_encoding('abc', "ASCII-8BIT") - other_ascii_string = forced_encoding('123', "ASCII-8BIT") + let(:ascii_arrow_symbol) { "\xAE" } + let(:utf_8_euro_symbol) { "\xE2\x82\xAC" } - resulting_string = build_encoded_string(ascii_string, utf8_encoding) << other_ascii_string - expected_string = forced_encoding('abc123', utf8_encoding) - expect(resulting_string).to be_identical_string(expected_string).with_same_encoding - end + describe '#<<' do + context 'with strings that can be converted to the target encoding' do + let(:valid_ascii_string) { forced_encoding("abcde", "ASCII-8BIT") } + let(:valid_unicode_string) { forced_encoding(utf_8_euro_symbol, 'UTF-8') } + + it 'encodes and appends the string' do + resulting_string = build_encoded_string(valid_unicode_string, utf8_encoding) << valid_ascii_string + expected_string = forced_encoding("#{utf_8_euro_symbol}abcde", 'UTF-8') + expect(resulting_string).to be_identical_string(expected_string).with_same_encoding end end - describe '#split' do - context 'when there is an undefined conversion to the target encoding' do - let(:wrapped_string_template) { "abaaaaaaaaaa%saaaaa" } - let(:wrapped_string) { sprintf(wrapped_string_template, ascii_arrow_symbol).force_encoding("ASCII-8BIT") } + context 'with a string that cannot be converted to the target encoding' do + context 'when appending a string with an incompatible character encoding' do + let(:ascii_string) { forced_encoding(ascii_arrow_symbol, "ASCII-8BIT") } + let(:valid_unicode_string) { forced_encoding(utf_8_euro_symbol, 'UTF-8') } - it 'normally raises an Encoding::UndefinedConversionError' do + it "normally raises an Encoding::CompatibilityError" do expect { - wrapped_string.encode(utf8_encoding) - }.to raise_error(Encoding::UndefinedConversionError) + valid_unicode_string.encode(utf8_encoding) << ascii_string + }.to raise_error(Encoding::CompatibilityError) end - it 'splits the string based on the delimiter accounting for encoding' do - delimiter = forced_encoding("b", utf8_encoding) - resulting_string = build_encoded_string(wrapped_string, utf8_encoding).split(delimiter) - exp1, exp2 = sprintf(wrapped_string_template, EncodedString::REPLACE).force_encoding(utf8_encoding).split(delimiter) - expect(resulting_string).to match [ - a_string_identical_to(exp1).with_same_encoding, - a_string_identical_to(exp2).with_same_encoding - ] + it 'replaces unconvertable characters with the REPLACE string' do + resulting_string = build_encoded_string(valid_unicode_string, utf8_encoding) << ascii_string + expected_string = "#{utf_8_euro_symbol}#{EncodedString::REPLACE}" + expect(resulting_string).to be_identical_string(expected_string).with_same_encoding end + end + end - it 'handles invalidly encoded strings' do - source_string = forced_encoding("an\xAE\nother", 'US-ASCII') - expect( - build_encoded_string(source_string, utf8_encoding).split("\n") - ).to eq([ - 'an?', - 'other' - ]) - end + context 'with two ascii strings with a target encoding of UTF-8 ' do + it 'has an encoding of UTF-8' do + ascii_string = forced_encoding('abc', "ASCII-8BIT") + other_ascii_string = forced_encoding('123', "ASCII-8BIT") + + resulting_string = build_encoded_string(ascii_string, utf8_encoding) << other_ascii_string + expected_string = forced_encoding('abc123', utf8_encoding) + expect(resulting_string).to be_identical_string(expected_string).with_same_encoding end + end + end - # see https://github.com/rspec/rspec-expectations/blob/f8a1232/spec/rspec/expectations/fail_with_spec.rb#L50 - # https://github.com/rspec/rspec-expectations/issues/201 - # https://github.com/rspec/rspec-expectations/pull/220 - context 'with a string that cannot be converted to the target encoding' do - let(:binary_poop) {'๐Ÿ’ฉ' } # [128169] "\u{1F4A9}" - let(:non_ascii_compatible_string) { "This is a pile of poo: #{binary_poop}, yuck".encode("UTF-16LE") } + describe '#split' do + context 'when there is an undefined conversion to the target encoding' do + let(:wrapped_string_template) { "abaaaaaaaaaa%saaaaa" } + let(:wrapped_string) { sprintf(wrapped_string_template, ascii_arrow_symbol).force_encoding("ASCII-8BIT") } - it 'normally raises an Encoding::CompatibilityError' do - expect { - non_ascii_compatible_string.split("\n") - }.to raise_error(Encoding::CompatibilityError) - end + it 'normally raises an Encoding::UndefinedConversionError' do + expect { + wrapped_string.encode(utf8_encoding) + }.to raise_error(Encoding::UndefinedConversionError) + end - it 'makes no changes to the resulting string' do - resulting_array = build_encoded_string(non_ascii_compatible_string).split("\n") - expect(resulting_array).to match [ - a_string_identical_to(non_ascii_compatible_string).with_same_encoding - ] - end + it 'splits the string based on the delimiter accounting for encoding' do + delimiter = forced_encoding("b", utf8_encoding) + resulting_string = build_encoded_string(wrapped_string, utf8_encoding).split(delimiter) + exp1, exp2 = sprintf(wrapped_string_template, EncodedString::REPLACE).force_encoding(utf8_encoding).split(delimiter) + expect(resulting_string).to match [ + a_string_identical_to(exp1).with_same_encoding, + a_string_identical_to(exp2).with_same_encoding + ] end - context 'when the string has an invalid byte sequence' do - let(:message_with_invalid_byte_sequence) { forced_encoding("\xEF \255 \xAD I have bad bytes", utf8_encoding) } + it 'handles invalidly encoded strings' do + source_string = forced_encoding("an\xAE\nother", 'US-ASCII') + expect( + build_encoded_string(source_string, utf8_encoding).split("\n") + ).to eq([ + 'an?', + 'other' + ]) + end + end - it 'normally raises an ArgumentError' do - expect(message_with_invalid_byte_sequence).not_to be_valid_encoding - expect { - message_with_invalid_byte_sequence.split("\n") - }.to raise_error(ArgumentError) - end + # see https://github.com/rspec/rspec-expectations/blob/f8a1232/spec/rspec/expectations/fail_with_spec.rb#L50 + # https://github.com/rspec/rspec-expectations/issues/201 + # https://github.com/rspec/rspec-expectations/pull/220 + context 'with a string that cannot be converted to the target encoding' do + let(:binary_poop) {'๐Ÿ’ฉ' } # [128169] "\u{1F4A9}" + let(:non_ascii_compatible_string) { "This is a pile of poo: #{binary_poop}, yuck".encode("UTF-16LE") } + + it 'normally raises an Encoding::CompatibilityError' do + expect { + non_ascii_compatible_string.split("\n") + }.to raise_error(Encoding::CompatibilityError) + end - it 'replaces invalid bytes with the REPLACE string' do - resulting_array = build_encoded_string(message_with_invalid_byte_sequence, utf8_encoding).split("\n") - expected_string = "? ? ? I have bad bytes" - expect(resulting_array).to match [ - a_string_identical_to(expected_string).with_same_encoding - ] - end + it 'makes no changes to the resulting string' do + resulting_array = build_encoded_string(non_ascii_compatible_string).split("\n") + expect(resulting_array).to match [ + a_string_identical_to(non_ascii_compatible_string).with_same_encoding + ] end end - def build_encoded_string(string, target_encoding = string.encoding) - EncodedString.new(string, target_encoding) - end + context 'when the string has an invalid byte sequence' do + let(:message_with_invalid_byte_sequence) { forced_encoding("\xEF \255 \xAD I have bad bytes", utf8_encoding) } - def forced_encoding(string, encoding) - string.dup.force_encoding(encoding) - end - else + it 'normally raises an ArgumentError' do + expect(message_with_invalid_byte_sequence).not_to be_valid_encoding + expect { + message_with_invalid_byte_sequence.split("\n") + }.to raise_error(ArgumentError) + end - describe '#source_encoding' do - it 'defaults to US-ASCII' do - str = EncodedString.new("abc", "UTF-8") - expect(str.source_encoding).to eq('US-ASCII') + it 'replaces invalid bytes with the REPLACE string' do + resulting_array = build_encoded_string(message_with_invalid_byte_sequence, utf8_encoding).split("\n") + expected_string = "? ? ? I have bad bytes" + expect(resulting_array).to match [ + a_string_identical_to(expected_string).with_same_encoding + ] end end end + + def build_encoded_string(string, target_encoding = string.encoding) + EncodedString.new(string, target_encoding) + end + + def forced_encoding(string, encoding) + string.dup.force_encoding(encoding) + end end end From 1c7d940d81bf595c3b88303fc54ad1b333fc30f5 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 23:00:55 +0300 Subject: [PATCH 18/39] [support] Remove Time#nsec workaround This commit was imported from https://github.com/rspec/rspec-support/commit/3496910acb1c157779e982a86718c660f9dacdfd. --- rspec-support/lib/rspec/support/object_formatter.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/rspec-support/lib/rspec/support/object_formatter.rb b/rspec-support/lib/rspec/support/object_formatter.rb index 2798a57b7..cd75d45f0 100644 --- a/rspec-support/lib/rspec/support/object_formatter.rb +++ b/rspec-support/lib/rspec/support/object_formatter.rb @@ -143,14 +143,8 @@ def self.can_inspect?(object) Time === object end - if Time.method_defined?(:nsec) - def inspect - object.strftime("#{FORMAT}.#{"%09d" % object.nsec} %z") - end - else # for 1.8.7 - def inspect - object.strftime("#{FORMAT}.#{"%06d" % object.usec} %z") - end + def inspect + object.strftime("#{FORMAT}.#{"%09d" % object.nsec} %z") end end From 058c8449fa1b29ceaca78a7ec4e9643de58e4743 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 23:02:46 +0300 Subject: [PATCH 19/39] [support] Remove 1.9.2-specific workaround This commit was imported from https://github.com/rspec/rspec-support/commit/81e47133832ce2e2ffa99d5111d5e0b10c049ac7. --- rspec-support/lib/rspec/support/object_formatter.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/rspec-support/lib/rspec/support/object_formatter.rb b/rspec-support/lib/rspec/support/object_formatter.rb index cd75d45f0..f86ab567c 100644 --- a/rspec-support/lib/rspec/support/object_formatter.rb +++ b/rspec-support/lib/rspec/support/object_formatter.rb @@ -207,9 +207,6 @@ def klass # http://stackoverflow.com/a/2818916 def native_object_id OBJECT_ID_FORMAT % (object.__id__ << 1) - rescue NoMethodError - # In Ruby 1.9.2, BasicObject responds to none of #__id__, #object_id, #id... - '-' end end From 0492fc4e7057b20f913bb12a06f4bedaf0f3fd1d Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 23:09:40 +0300 Subject: [PATCH 20/39] [support] Simplify a macro definition This commit was imported from https://github.com/rspec/rspec-support/commit/e91cc38d4e5a55a3adfd3f562d90ce4f0319bfb6. --- rspec-support/lib/rspec/support.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rspec-support/lib/rspec/support.rb b/rspec-support/lib/rspec/support.rb index 614e5ea7d..8cc0c16ea 100644 --- a/rspec-support/lib/rspec/support.rb +++ b/rspec-support/lib/rspec/support.rb @@ -12,9 +12,7 @@ module Support # hand, does a linear O(N) search over the dirs in the $LOAD_PATH until # it can resolve the file relative to one of the dirs. def self.define_optimized_require_for_rspec(lib, &require_relative) - name = "require_rspec_#{lib}" - - (class << self; self; end).__send__(:define_method, name) do |f| + define_singleton_method("require_rspec_#{lib}") do |f| require_relative.call("#{lib}/#{f}") end end From 2e85a67513520d4c48c07c49b6807fc304106749 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 23:16:53 +0300 Subject: [PATCH 21/39] [support] Update required Ruby version gemspec constraint This commit was imported from https://github.com/rspec/rspec-support/commit/7e36bd380720f932b73669a076a15c1f63900745. --- rspec-support/rspec-support.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rspec-support/rspec-support.gemspec b/rspec-support/rspec-support.gemspec index c32cd57d4..19eb84b0c 100644 --- a/rspec-support/rspec-support.gemspec +++ b/rspec-support/rspec-support.gemspec @@ -33,7 +33,7 @@ Gem::Specification.new do |spec| spec.cert_chain = [File.expand_path('~/.gem/rspec-gem-public_cert.pem')] end - spec.required_ruby_version = '>= 1.8.7' + spec.required_ruby_version = '>= 2.3.0' spec.add_development_dependency "rake", "> 10.0.0" spec.add_development_dependency "thread_order", "~> 1.1.0" From daa023398b0a1dd9bf0fc932ad6f7e18fbc5ffdd Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 23:25:50 +0300 Subject: [PATCH 22/39] [support] Remove explicit gem version constraints This commit was imported from https://github.com/rspec/rspec-support/commit/e07221990c804fa0e2e9d9b187e7a2145ab8e67e. --- rspec-support/Gemfile | 36 +++-------------------------- rspec-support/rspec-support.gemspec | 2 +- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/rspec-support/Gemfile b/rspec-support/Gemfile index 3652d838c..b82941338 100644 --- a/rspec-support/Gemfile +++ b/rspec-support/Gemfile @@ -13,51 +13,21 @@ branch = File.read(File.expand_path("../maintenance-branch", __FILE__)).chomp end end -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', '>= 12.3.3' -end - if ENV['DIFF_LCS_VERSION'] gem 'diff-lcs', ENV['DIFF_LCS_VERSION'] else gem 'diff-lcs', '~> 1.4', '>= 1.4.3' end -if RUBY_VERSION < '2.3.0' && !!(RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/) - gem "childprocess", "< 1.0.0" -elsif RUBY_VERSION < '2.3.0' - gem "childprocess", "< 3.0.0" -else - gem "childprocess", ">= 3.0.0" -end +gem "childprocess", ">= 3.0.0" +gem 'ffi', '~> 1.13.0' ### dep for ci/coverage gem 'simplecov', '~> 0.8' -if RUBY_VERSION < '2.0.0' || RUBY_ENGINE == 'java' - gem 'json', '< 2.0.0' # is a dependency of simplecov -else - gem 'json', '> 2.3.0' -end - -if RUBY_VERSION < '2.2.0' && !!(RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/) - gem 'ffi', '< 1.10' -elsif RUBY_VERSION < '2.0' - # ffi dropped Ruby 1.8 support in 1.9.19 and Ruby 1.9 support in 1.11.0 - gem 'ffi', '< 1.9.19' -elsif RUBY_VERSION < '2.3.0' - gem 'ffi', '~> 1.12.0' -else - gem 'ffi', '~> 1.13.0' -end - # No need to run rubocop on earlier versions if RUBY_VERSION >= '2.4' && RUBY_ENGINE == 'ruby' gem "rubocop", "~> 0.52.1" end -eval File.read('Gemfile-custom') if File.exist?('Gemfile-custom') +eval_gemfile 'Gemfile-custom' if File.exist?('Gemfile-custom') diff --git a/rspec-support/rspec-support.gemspec b/rspec-support/rspec-support.gemspec index 19eb84b0c..7321aff2d 100644 --- a/rspec-support/rspec-support.gemspec +++ b/rspec-support/rspec-support.gemspec @@ -35,6 +35,6 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 2.3.0' - spec.add_development_dependency "rake", "> 10.0.0" + spec.add_development_dependency "rake", ">= 12.3.3" spec.add_development_dependency "thread_order", "~> 1.1.0" end From 327014a1d9816c743d44c1d41c1950f19ac687ba Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Thu, 12 Nov 2020 23:59:17 +0300 Subject: [PATCH 23/39] [support] Remove assorted older rubies workarounds This commit was imported from https://github.com/rspec/rspec-support/commit/993994939f7f1415d7897cd31189e170158dfb5b. --- rspec-support/lib/rspec/support/spec.rb | 2 +- .../rspec/support/spec/library_wide_checks.rb | 10 +---- .../spec/rspec/support/differ_spec.rb | 38 +++++++++---------- .../spec/rspec/support/encoded_string_spec.rb | 28 +++++--------- .../rspec/support/object_formatter_spec.rb | 32 +++++----------- .../spec/rspec/support/ruby_features_spec.rb | 12 ++---- .../rspec/support/spec/in_sub_process_spec.rb | 2 +- .../spec/rspec/support/spec/shell_out_spec.rb | 6 +-- .../support/spec/stderr_splitter_spec.rb | 3 -- rspec-support/spec/rspec/support_spec.rb | 2 +- 10 files changed, 46 insertions(+), 89 deletions(-) diff --git a/rspec-support/lib/rspec/support/spec.rb b/rspec-support/lib/rspec/support/spec.rb index d914b1044..c39d89214 100644 --- a/rspec-support/lib/rspec/support/spec.rb +++ b/rspec-support/lib/rspec/support/spec.rb @@ -48,7 +48,7 @@ def self.setup_simplecov(&block) # Simplecov emits some ruby warnings when loaded, so silence them. old_verbose, $VERBOSE = $VERBOSE, false - return if ENV['NO_COVERAGE'] || RUBY_VERSION < '1.9.3' + return if ENV['NO_COVERAGE'] return if RUBY_ENGINE != 'ruby' || RSpec::Support::OS.windows? # Don't load it when we're running a single isolated diff --git a/rspec-support/lib/rspec/support/spec/library_wide_checks.rb b/rspec-support/lib/rspec/support/spec/library_wide_checks.rb index 56b059364..3075b8557 100644 --- a/rspec-support/lib/rspec/support/spec/library_wide_checks.rb +++ b/rspec-support/lib/rspec/support/spec/library_wide_checks.rb @@ -64,7 +64,7 @@ def load_all_files(files, preamble, postamble=nil) stdout, stderr, status = with_env 'NO_COVERAGE' => '1' do options = %w[ -w ] - options << "--disable=gem" if RUBY_VERSION.to_f >= 1.9 && RSpec::Support::Ruby.mri? + options << "--disable=gem" if RSpec::Support::Ruby.mri? run_ruby_with_current_load_path(command, *options) end @@ -115,14 +115,6 @@ def have_successful_no_warnings_output it 'only loads a known set of stdlibs so gem authors are forced ' \ 'to load libs they use to have passing specs', :slow do loaded_features = @loaded_feature_lines.split("\n") - if RUBY_VERSION == '1.8.7' - # On 1.8.7, $" returns the relative require path if that was used - # to require the file. LIB_REGEX will not match the relative version - # since it has a `/lib` prefix. Here we deal with this by expanding - # relative files relative to the $LOAD_PATH dir (lib). - Dir.chdir("lib") { loaded_features.map! { |f| File.expand_path(f) } } - end - loaded_features.reject! { |feature| RSpec::CallerFilter::LIB_REGEX =~ feature } loaded_features.reject! { |feature| allowed_loaded_feature_regexps.any? { |r| r =~ feature } } diff --git a/rspec-support/spec/rspec/support/differ_spec.rb b/rspec-support/spec/rspec/support/differ_spec.rb index 720380597..b08a65e72 100644 --- a/rspec-support/spec/rspec/support/differ_spec.rb +++ b/rspec-support/spec/rspec/support/differ_spec.rb @@ -287,26 +287,24 @@ def inspect; ""; end expect(diff).to be_diffed_as(expected_diff) end - unless RUBY_VERSION == '1.8.7' # We can't count on the ordering of the hash on 1.8.7... - it "outputs unified diff message for hashes inside arrays with differing key orders" do - expected = [{ :foo => 'bar', :baz => 'quux', :metasyntactic => 'variable', :delta => 'charlie', :width =>'quite wide' }] - actual = [{ :metasyntactic => 'variable', :delta => 'charlotte', :width =>'quite wide', :foo => 'bar' }] + it "outputs unified diff message for hashes inside arrays with differing key orders" do + expected = [{ :foo => 'bar', :baz => 'quux', :metasyntactic => 'variable', :delta => 'charlie', :width =>'quite wide' }] + actual = [{ :metasyntactic => 'variable', :delta => 'charlotte', :width =>'quite wide', :foo => 'bar' }] - expected_diff = dedent(<<-'EOD') - | - |@@ -1,4 +1,5 @@ - |-[{:delta=>"charlotte", - |+[{:baz=>"quux", - |+ :delta=>"charlie", - | :foo=>"bar", - | :metasyntactic=>"variable", - | :width=>"quite wide"}] - | - EOD + expected_diff = dedent(<<-'EOD') + | + |@@ -1,4 +1,5 @@ + |-[{:delta=>"charlotte", + |+[{:baz=>"quux", + |+ :delta=>"charlie", + | :foo=>"bar", + | :metasyntactic=>"variable", + | :width=>"quite wide"}] + | + EOD - diff = differ.diff(expected,actual) - expect(diff).to be_diffed_as(expected_diff) - end + diff = differ.diff(expected,actual) + expect(diff).to be_diffed_as(expected_diff) end it 'outputs unified diff message of two hashes with differing encoding' do @@ -314,7 +312,7 @@ def inspect; ""; end | |@@ #{one_line_header} @@ |-"a" => "a", - |#{ (RUBY_VERSION.to_f > 1.8) ? %Q{+"รถ" => "รถ"} : '+"\303\266" => "\303\266"' }, + |+"รถ" => "รถ", | EOD @@ -327,7 +325,7 @@ def inspect; ""; end | |@@ #{one_line_header} @@ |-:a => "a", - |#{ (RUBY_VERSION.to_f > 1.8) ? %Q{+\"ํ•œ๊ธ€\" => \"ํ•œ๊ธ€2\"} : '+"\355\225\234\352\270\200" => "\355\225\234\352\270\2002"' }, + |+\"ํ•œ๊ธ€\" => \"ํ•œ๊ธ€2\", | EOD diff --git a/rspec-support/spec/rspec/support/encoded_string_spec.rb b/rspec-support/spec/rspec/support/encoded_string_spec.rb index 3bb821ed6..1e1eaa026 100644 --- a/rspec-support/spec/rspec/support/encoded_string_spec.rb +++ b/rspec-support/spec/rspec/support/encoded_string_spec.rb @@ -72,26 +72,16 @@ module RSpec::Support }.to raise_error(Encoding::ConverterNotFoundError) end - # See comment above ENCODE_UNCONVERTABLE_BYTES in encoded_string.rb - # for why the behavior differs by (MRI) Ruby version. - if RUBY_VERSION < '2.1' - it 'does nothing' do - resulting_string = build_encoded_string(string, no_converter_encoding).to_s - expected_string = forced_encoding("\x80", no_converter_encoding) - expect(resulting_string).to be_identical_string(expected_string).with_same_encoding - end - else - it 'forces the encoding and replaces invalid characters with the REPLACE string' do - resulting_string = build_encoded_string(string, no_converter_encoding).to_s - expected_string = forced_encoding(EncodedString::REPLACE, no_converter_encoding) - expect(resulting_string).to be_identical_string(expected_string).with_same_encoding - end + it 'forces the encoding and replaces invalid characters with the REPLACE string' do + resulting_string = build_encoded_string(string, no_converter_encoding).to_s + expected_string = forced_encoding(EncodedString::REPLACE, no_converter_encoding) + expect(resulting_string).to be_identical_string(expected_string).with_same_encoding + end - it 'does not mutate the input string' do - expect { - build_encoded_string(string, no_converter_encoding) - }.not_to change { [string, string.encoding] } - end + it 'does not mutate the input string' do + expect { + build_encoded_string(string, no_converter_encoding) + }.not_to change { [string, string.encoding] } end end diff --git a/rspec-support/spec/rspec/support/object_formatter_spec.rb b/rspec-support/spec/rspec/support/object_formatter_spec.rb index 033e34712..03ee1e661 100644 --- a/rspec-support/spec/rspec/support/object_formatter_spec.rb +++ b/rspec-support/spec/rspec/support/object_formatter_spec.rb @@ -22,25 +22,17 @@ module Support it 'formats those objects within the hash output, at any level of nesting' do formatted = ObjectFormatter.format(input) - - if RUBY_VERSION == '1.8.7' - # We can't count on the ordering of the hash on 1.8.7... - expect(formatted).to include(%Q{"key"=>#{formatted_time}}, %Q{#{formatted_time}=>"value"}, %Q{"nested"=>{"key"=>#{formatted_time}}}) - else - expect(formatted).to eq(%Q{{"key"=>#{formatted_time}, #{formatted_time}=>"value", "nested"=>{"key"=>#{formatted_time}}}}) - end + expect(formatted).to eq(%Q{{"key"=>#{formatted_time}, #{formatted_time}=>"value", "nested"=>{"key"=>#{formatted_time}}}}) end end - unless RUBY_VERSION == '1.8.7' # We can't count on the ordering of the hash on 1.8.7... - context 'with a hash object' do - let(:input) { { :c => "ccc", :a => "aaa", "b" => 'bbb' } } - let(:expected) { '{:a=>"aaa", "b"=>"bbb", :c=>"ccc"}' } + context 'with a hash object' do + let(:input) { { :c => "ccc", :a => "aaa", "b" => 'bbb' } } + let(:expected) { '{:a=>"aaa", "b"=>"bbb", :c=>"ccc"}' } - it 'sorts keys to ensure objects are always displayed the same way' do - formatted = ObjectFormatter.format(input) - expect(formatted).to eq expected - end + it 'sorts keys to ensure objects are always displayed the same way' do + formatted = ObjectFormatter.format(input) + expect(formatted).to eq expected end end @@ -222,14 +214,8 @@ def self.to_s end end - if RUBY_VERSION == '1.9.2' - it 'produces an #inspect-like output without object id' do - expect(output).to eq('#') - end - else - it "produces an output emulating MRI's #inspect-like output generated by C implementation" do - expect(output).to match(/\A#\z/) - end + it "produces an output emulating MRI's #inspect-like output generated by C implementation" do + expect(output).to match(/\A#\z/) end end diff --git a/rspec-support/spec/rspec/support/ruby_features_spec.rb b/rspec-support/spec/rspec/support/ruby_features_spec.rb index 57a45d789..477656d3b 100644 --- a/rspec-support/spec/rspec/support/ruby_features_spec.rb +++ b/rspec-support/spec/rspec/support/ruby_features_spec.rb @@ -112,14 +112,10 @@ def ripper_can_parse_source_referencing_keyword_arguments? require 'ripper' # It doesn't matter if keyword arguments don't exist. if Ruby.mri? || Ruby.jruby? || Ruby.truffleruby? - if RUBY_VERSION < '2.0' - true - else - begin - !::Ripper.sexp('def a(**kw_args); end').nil? - rescue NoMethodError - false - end + begin + !::Ripper.sexp('def a(**kw_args); end').nil? + rescue NoMethodError + false end end end diff --git a/rspec-support/spec/rspec/support/spec/in_sub_process_spec.rb b/rspec-support/spec/rspec/support/spec/in_sub_process_spec.rb index 81f0a349a..b25d3c04b 100644 --- a/rspec-support/spec/rspec/support/spec/in_sub_process_spec.rb +++ b/rspec-support/spec/rspec/support/spec/in_sub_process_spec.rb @@ -10,7 +10,7 @@ module NotIsolated expect(defined? NotIsolated).to be_nil end - if Process.respond_to?(:fork) && !(RUBY_PLATFORM == 'java' && RUBY_VERSION == '1.8.7') + if Process.respond_to?(:fork) it 'returns the result of sub process' do expect(in_sub_process { :foo }).to eq(:foo) diff --git a/rspec-support/spec/rspec/support/spec/shell_out_spec.rb b/rspec-support/spec/rspec/support/spec/shell_out_spec.rb index 57b3f4ed9..e537a12e1 100644 --- a/rspec-support/spec/rspec/support/spec/shell_out_spec.rb +++ b/rspec-support/spec/rspec/support/spec/shell_out_spec.rb @@ -13,10 +13,8 @@ _, _, good_status = shell_out("ruby", "-e", '3 + 3') expect(good_status.exitstatus).to eq(0) - unless RUBY_VERSION.to_f < 1.9 # except 1.8... - _, _, bad_status = shell_out("ruby", "-e", 'boom') - expect(bad_status.exitstatus).to eq(1) - end + _, _, bad_status = shell_out("ruby", "-e", 'boom') + expect(bad_status.exitstatus).to eq(1) end it 'can shell out to ruby with the current load path' do diff --git a/rspec-support/spec/rspec/support/spec/stderr_splitter_spec.rb b/rspec-support/spec/rspec/support/spec/stderr_splitter_spec.rb index 896b44359..7f7c55937 100644 --- a/rspec-support/spec/rspec/support/spec/stderr_splitter_spec.rb +++ b/rspec-support/spec/rspec/support/spec/stderr_splitter_spec.rb @@ -28,9 +28,6 @@ # NotImplementedError: pressed?() function is unimplemented on this machine stderr_methods = stderr.methods.select { |method| stderr.respond_to?(method) } - # On 2.2, there's a weird issue where stderr sometimes responds to `birthtime` and sometimes doesn't... - stderr_methods -= [:birthtime] if RUBY_VERSION =~ /^2\.2/ - # No idea why, but on our AppVeyor windows builds it doesn't respond to these... stderr_methods -= [:close_on_exec?, :close_on_exec=] if RSpec::Support::OS.windows? && ENV['CI'] diff --git a/rspec-support/spec/rspec/support_spec.rb b/rspec-support/spec/rspec/support_spec.rb index ce4bf29f9..839c4d57e 100644 --- a/rspec-support/spec/rspec/support_spec.rb +++ b/rspec-support/spec/rspec/support_spec.rb @@ -65,7 +65,7 @@ def method_missing(name, *args, &block) }.to raise_error(NameError) end - context "for a BasicObject subclass", :if => RUBY_VERSION.to_f > 1.8 do + context "for a BasicObject subclass" do let(:basic_class) do Class.new(BasicObject) do def foo From 8ae5e71a1ce407aebe1422833f9d6d7796231a1d Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Fri, 13 Nov 2020 00:18:05 +0300 Subject: [PATCH 24/39] [support] Explicitly require ruby_features where needed This commit was imported from https://github.com/rspec/rspec-support/commit/b9c5674a1b737c066a6da701ae59df1b1613005c. --- rspec-support/lib/rspec/support.rb | 1 - rspec-support/lib/rspec/support/caller_filter.rb | 2 -- rspec-support/lib/rspec/support/directory_maker.rb | 2 -- rspec-support/lib/rspec/support/encoded_string.rb | 2 ++ rspec-support/lib/rspec/support/spec/library_wide_checks.rb | 1 + 5 files changed, 3 insertions(+), 5 deletions(-) diff --git a/rspec-support/lib/rspec/support.rb b/rspec-support/lib/rspec/support.rb index 8cc0c16ea..2464543d3 100644 --- a/rspec-support/lib/rspec/support.rb +++ b/rspec-support/lib/rspec/support.rb @@ -19,7 +19,6 @@ def self.define_optimized_require_for_rspec(lib, &require_relative) define_optimized_require_for_rspec(:support) { |f| require_relative(f) } require_rspec_support "version" - require_rspec_support "ruby_features" # @api private KERNEL_METHOD_METHOD = ::Kernel.instance_method(:method) diff --git a/rspec-support/lib/rspec/support/caller_filter.rb b/rspec-support/lib/rspec/support/caller_filter.rb index 9559013bb..ee173ca7e 100644 --- a/rspec-support/lib/rspec/support/caller_filter.rb +++ b/rspec-support/lib/rspec/support/caller_filter.rb @@ -1,5 +1,3 @@ -RSpec::Support.require_rspec_support "ruby_features" - module RSpec # Consistent implementation for "cleaning" the caller method to strip out # non-rspec lines. This enables errors to be reported at the call site in diff --git a/rspec-support/lib/rspec/support/directory_maker.rb b/rspec-support/lib/rspec/support/directory_maker.rb index 39a280e3a..4c9bae908 100644 --- a/rspec-support/lib/rspec/support/directory_maker.rb +++ b/rspec-support/lib/rspec/support/directory_maker.rb @@ -1,5 +1,3 @@ -RSpec::Support.require_rspec_support 'ruby_features' - module RSpec module Support # @api private diff --git a/rspec-support/lib/rspec/support/encoded_string.rb b/rspec-support/lib/rspec/support/encoded_string.rb index 942de79a5..55aa47b5b 100644 --- a/rspec-support/lib/rspec/support/encoded_string.rb +++ b/rspec-support/lib/rspec/support/encoded_string.rb @@ -1,3 +1,5 @@ +RSpec::Support.require_rspec_support "ruby_features" + module RSpec module Support # @private diff --git a/rspec-support/lib/rspec/support/spec/library_wide_checks.rb b/rspec-support/lib/rspec/support/spec/library_wide_checks.rb index 3075b8557..41beab1e6 100644 --- a/rspec-support/lib/rspec/support/spec/library_wide_checks.rb +++ b/rspec-support/lib/rspec/support/spec/library_wide_checks.rb @@ -1,4 +1,5 @@ require 'rspec/support/spec/shell_out' +RSpec::Support.require_rspec_support 'ruby_features' module RSpec module Support From 27e4b704c071ff07fa52622388fc08a12819c8cb Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Fri, 13 Nov 2020 00:31:46 +0300 Subject: [PATCH 25/39] [support] Remove const_defined? workaround This commit was imported from https://github.com/rspec/rspec-support/commit/69288f37b656775f0af0d7da907d912cf62480ba. --- .../rspec/support/recursive_const_methods.rb | 55 ++++++------------- 1 file changed, 16 insertions(+), 39 deletions(-) diff --git a/rspec-support/lib/rspec/support/recursive_const_methods.rb b/rspec-support/lib/rspec/support/recursive_const_methods.rb index b19ad0666..2739b8c5d 100644 --- a/rspec-support/lib/rspec/support/recursive_const_methods.rb +++ b/rspec-support/lib/rspec/support/recursive_const_methods.rb @@ -5,52 +5,29 @@ module Support module RecursiveConstMethods # We only want to consider constants that are defined directly on a # particular module, and not include top-level/inherited constants. - # Unfortunately, the constant API changed between 1.8 and 1.9, so - # we need to conditionally define methods to ignore the top-level/inherited - # constants. # # Given: # class A; B = 1; end # class C < A; end # - # On 1.8: - # - C.const_get("Hash") # => ::Hash - # - C.const_defined?("Hash") # => false - # - C.constants # => ["B"] - # - None of these methods accept the extra `inherit` argument - # On 1.9: - # - C.const_get("Hash") # => ::Hash - # - C.const_defined?("Hash") # => true - # - C.const_get("Hash", false) # => raises NameError - # - C.const_defined?("Hash", false) # => false - # - C.constants # => [:B] - # - C.constants(false) #=> [] - if Module.method(:const_defined?).arity == 1 - def const_defined_on?(mod, const_name) - mod.const_defined?(const_name) - end - - def get_const_defined_on(mod, const_name) - return mod.const_get(const_name) if const_defined_on?(mod, const_name) - - raise NameError, "uninitialized constant #{mod.name}::#{const_name}" - end - - def constants_defined_on(mod) - mod.constants.select { |c| const_defined_on?(mod, c) } - end - else - def const_defined_on?(mod, const_name) - mod.const_defined?(const_name, false) - end + # Then: + # C.const_get("Hash") # => ::Hash + # C.const_defined?("Hash") # => true + # C.const_get("Hash", false) # => raises NameError + # C.const_defined?("Hash", false) # => false + # C.constants # => [:B] + # C.constants(false) #=> [] + + def const_defined_on?(mod, const_name) + mod.const_defined?(const_name, false) + end - def get_const_defined_on(mod, const_name) - mod.const_get(const_name, false) - end + def get_const_defined_on(mod, const_name) + mod.const_get(const_name, false) + end - def constants_defined_on(mod) - mod.constants(false) - end + def constants_defined_on(mod) + mod.constants(false) end def recursive_const_get(const_name) From 4866b1ae6194707b0093b4ac384d6d8ab0792e1b Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Fri, 13 Nov 2020 00:34:30 +0300 Subject: [PATCH 26/39] [support] Remove version-specific details from a doc This commit was imported from https://github.com/rspec/rspec-support/commit/1b2467f4abbb2792e8a53276dd81fabffe61c19f. --- rspec-support/lib/rspec/support/encoded_string.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/rspec-support/lib/rspec/support/encoded_string.rb b/rspec-support/lib/rspec/support/encoded_string.rb index 55aa47b5b..9a51cd067 100644 --- a/rspec-support/lib/rspec/support/encoded_string.rb +++ b/rspec-support/lib/rspec/support/encoded_string.rb @@ -95,16 +95,11 @@ def matching_encoding(string) # Originally defined as a constant to avoid uneeded allocations, this hash must # be defined inline (without {}) to avoid warnings on Ruby 2.7 # - # In MRI 2.1 'invalid: :replace' changed to also replace an invalid byte sequence - # see https://github.com/ruby/ruby/blob/v2_1_0/NEWS#L176 - # https://www.ruby-forum.com/topic/6861247 - # https://twitter.com/nalsh/status/553413844685438976 + # 'invalid: :replace' also replaces an invalid byte sequence # - # For example, given: - # "\x80".force_encoding("Emacs-Mule").encode(:invalid => :replace).bytes.to_a - # - # On MRI 2.1 or above: 63 # '?' - # else : 128 # "\x80" + # For example: + # "\x80".force_encoding("Emacs-Mule").encode(:invalid => :replace).bytes.to_a # => + # => 63 # '?' # string.encode(@encoding, :invalid => :replace, :undef => :replace, :replace => REPLACE) rescue Encoding::ConverterNotFoundError From 8ac7c9cf470f8ae716cf772f38715a18573cb4db Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Fri, 13 Nov 2020 01:01:49 +0300 Subject: [PATCH 27/39] [support] Remove old JRuby workaround This commit was imported from https://github.com/rspec/rspec-support/commit/b66b7c87dfbeccad68993ea9ee4ed00f634935dc. --- .../spec/rspec/support/object_formatter_spec.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/rspec-support/spec/rspec/support/object_formatter_spec.rb b/rspec-support/spec/rspec/support/object_formatter_spec.rb index 03ee1e661..b5fa65c6a 100644 --- a/rspec-support/spec/rspec/support/object_formatter_spec.rb +++ b/rspec-support/spec/rspec/support/object_formatter_spec.rb @@ -97,11 +97,6 @@ def with_date_loaded it 'includes a conventional representation of the decimal' do in_sub_process_if_possible do require 'bigdecimal' - # Suppress warning on JRuby 1.7: - # file:/Users/me/.rbenv/versions/jruby-1.7.26/lib/jruby.jar!/jruby/bigdecimal.rb:1 - # warning: loading in progress, circular require considered harmful - bigdecimal.jar - $stderr.reset! - expect(formatted_decimal).to include('3.3 (#" end end From 6513122c9b358b7179817afaa84602757ea8d908 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Fri, 13 Nov 2020 01:04:00 +0300 Subject: [PATCH 28/39] [support] Remove usages of jruby_9000 This commit was imported from https://github.com/rspec/rspec-support/commit/571a6928e551a3681c949594bfaecaaec10245e9. --- .../spec/rspec/support/method_signature_verifier_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb b/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb index c6b45b4fa..5a1725641 100644 --- a/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb +++ b/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb @@ -220,7 +220,7 @@ def arity_kw(x, y:1, z:2); end expect(valid?(nil, :a => 1)).to eq(false) end - it 'mentions the invalid keyword args in the error', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do + it 'mentions the invalid keyword args in the error' do expect(error_for(nil, :a => 0, :b => 1)).to \ eq("Invalid keyword arguments provided: a, b") end @@ -240,7 +240,7 @@ def arity_kw(x, y:1, z:2); end expect(args).to eq([nil, { :y => 1 }]) end - it 'mentions the arity and optional kw args in the description', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do + it 'mentions the arity and optional kw args in the description' do expect(signature_description).to eq("arity of 1 and optional keyword args (:y, :z)") end @@ -321,7 +321,7 @@ def arity_kw(x, y = {}, z:2); end expect(valid?(nil, 'a' => 1, :b => 2, :z => 3)).to eq(false) end - it 'mentions the invalid keyword args in the error', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do + it 'mentions the invalid keyword args in the error' do expect(error_for(1, 2, :a => 0)).to eq("Invalid keyword arguments provided: a") expect(error_for(1, :a => 0)).to eq("Invalid keyword arguments provided: a") expect(error_for(1, 'a' => 0, :b => 0)).to eq("Invalid keyword arguments provided: b") @@ -342,7 +342,7 @@ def arity_kw(x, y = {}, z:2); end expect(args).to eq([nil, nil, { :y => 1 }]) end - it 'mentions the arity and optional kw args in the description', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do + it 'mentions the arity and optional kw args in the description' do expect(signature_description).to eq("arity of 1 to 2 and optional keyword args (:z)") end From 2ba518aba00b2b5f25d04b19d233677aa13ad6ff Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Fri, 13 Nov 2020 01:07:14 +0300 Subject: [PATCH 29/39] [support] Remove non_mri? This commit was imported from https://github.com/rspec/rspec-support/commit/0c3c74eb4075c1b606a394108705fec7e718056f. --- rspec-support/lib/rspec/support/ruby_features.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rspec-support/lib/rspec/support/ruby_features.rb b/rspec-support/lib/rspec/support/ruby_features.rb index cad74da0e..0772b1af0 100644 --- a/rspec-support/lib/rspec/support/ruby_features.rb +++ b/rspec-support/lib/rspec/support/ruby_features.rb @@ -36,10 +36,6 @@ def rbx? defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx' end - def non_mri? - !mri? - end - def mri? !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby' end From 78b10d8b8ddb061574de2b5f7a6b9d32d3786c43 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Sat, 14 Nov 2020 23:58:08 +0300 Subject: [PATCH 30/39] [support] Add Changelog entry This commit was imported from https://github.com/rspec/rspec-support/commit/a92dc585e92b1cc8bc07d10f2a1afccb8bd7ebca. --- rspec-support/Changelog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rspec-support/Changelog.md b/rspec-support/Changelog.md index eb5a86fdb..c6401d1ae 100644 --- a/rspec-support/Changelog.md +++ b/rspec-support/Changelog.md @@ -1,3 +1,9 @@ +### Development (unreleased) + +Breaking Changes: + +* Ruby < 2.3 is no longer supported. (Phil Pirozhkov, #436) + ### 3.10.0 / 2020-10-30 No changes. Released to support other RSpec releases. From 4c4db9d7cc39b260a2103722cff6dc7561179927 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Sun, 15 Nov 2020 18:26:14 +0300 Subject: [PATCH 31/39] [support] Get rid of evals This commit was imported from https://github.com/rspec/rspec-support/commit/b69244f584a320acf8c4cda833a34da4dd13cc7f. --- .../support/method_signature_verifier_spec.rb | 40 +++++-------------- .../support/with_keywords_when_needed_spec.rb | 6 --- 2 files changed, 10 insertions(+), 36 deletions(-) diff --git a/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb b/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb index 5a1725641..813eb7640 100644 --- a/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb +++ b/rspec-support/spec/rspec/support/method_signature_verifier_spec.rb @@ -205,9 +205,7 @@ def arity_optional(x, y, z = 1); end end describe 'a method with optional keyword arguments' do - eval <<-RUBY - def arity_kw(x, y:1, z:2); end - RUBY + def arity_kw(x, y:1, z:2); end let(:test_method) { method(:arity_kw) } @@ -298,9 +296,7 @@ def arity_kw(x, y:1, z:2); end end describe 'a method with optional argument and keyword arguments' do - eval <<-RUBY - def arity_kw(x, y = {}, z:2); end - RUBY + def arity_kw(x, y = {}, z:2); end let(:test_method) { method(:arity_kw) } @@ -392,9 +388,7 @@ def arity_kw(x, y = {}, z:2); end end describe 'a method with required keyword arguments' do - eval <<-RUBY - def arity_required_kw(x, y:, z:, a: 'default'); end - RUBY + def arity_required_kw(x, y:, z:, a: 'default'); end let(:test_method) { method(:arity_required_kw) } @@ -492,9 +486,7 @@ def arity_required_kw(x, y:, z:, a: 'default'); end end describe 'a method with required keyword arguments and a splat' do - eval <<-RUBY - def arity_required_kw_splat(w, *x, y:, z:, a: 'default'); end - RUBY + def arity_required_kw_splat(w, *x, y:, z:, a: 'default'); end let(:test_method) { method(:arity_required_kw_splat) } @@ -585,9 +577,7 @@ def arity_required_kw_splat(w, *x, y:, z:, a: 'default'); end end describe 'a method with required keyword arguments and a keyword arg splat' do - eval <<-RUBY - def arity_kw_arg_splat(x:, **rest); end - RUBY + def arity_kw_arg_splat(x:, **rest); end let(:test_method) { method(:arity_kw_arg_splat) } @@ -649,9 +639,7 @@ def arity_kw_arg_splat(x:, **rest); end end describe 'a method with a required arg and a keyword arg splat' do - eval <<-RUBY - def arity_kw_arg_splat(x, **rest); end - RUBY + def arity_kw_arg_splat(x, **rest); end let(:test_method) { method(:arity_kw_arg_splat) } @@ -924,9 +912,7 @@ def arity_block(_, &block); end it_behaves_like 'a method verifier' describe 'providing a matcher for optional keyword arguments' do - eval <<-RUBY - def arity_kw(x, y:1); end - RUBY + def arity_kw(x, y:1); end let(:test_method) { method(:arity_kw) } @@ -936,9 +922,7 @@ def arity_kw(x, y:1); end end describe 'providing a matcher for required keyword arguments' do - eval <<-RUBY - def arity_kw_required(x, y:); end - RUBY + def arity_kw_required(x, y:); end let(:test_method) { method(:arity_kw_required) } @@ -952,9 +936,7 @@ def arity_kw_required(x, y:); end it_behaves_like 'a method verifier' describe 'for optional keyword arguments' do - eval <<-RUBY - def arity_kw(x, y:1, z:2); end - RUBY + def arity_kw(x, y:1, z:2); end let(:test_method) { method(:arity_kw) } @@ -968,9 +950,7 @@ def arity_kw(x, y:1, z:2); end end describe 'providing a matcher for required keyword arguments' do - eval <<-RUBY - def arity_kw_required(x, y:); end - RUBY + def arity_kw_required(x, y:); end let(:test_method) { method(:arity_kw_required) } diff --git a/rspec-support/spec/rspec/support/with_keywords_when_needed_spec.rb b/rspec-support/spec/rspec/support/with_keywords_when_needed_spec.rb index 4a2a80a34..72b84fe85 100644 --- a/rspec-support/spec/rspec/support/with_keywords_when_needed_spec.rb +++ b/rspec-support/spec/rspec/support/with_keywords_when_needed_spec.rb @@ -27,21 +27,15 @@ def run(klass, *args, &block) end it "will run a block with optional keyword arguments when none are provided" do - binding.eval(<<-CODE, __FILE__, __LINE__) run(klass, 42) { |arg, val: nil| check_argument(arg) } - CODE end it "will run a block with optional keyword arguments when they are provided" do - binding.eval(<<-CODE, __FILE__, __LINE__) run(klass, val: 42) { |val: nil| check_argument(val) } - CODE end it "will run a block with required keyword arguments" do - binding.eval(<<-CODE, __FILE__, __LINE__) run(klass, val: 42) { |val:| check_argument(val) } - CODE end end end From 47979aa9d4ef548de13a4fdd56e03fb9f6efe270 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Mon, 16 Nov 2020 02:03:28 +0300 Subject: [PATCH 32/39] [support] Fix offences This commit was imported from https://github.com/rspec/rspec-support/commit/fdd801cc2746f782f9f4aa5cb1b7705fb441356e. --- rspec-support/lib/rspec/support/encoded_string.rb | 10 +++++----- .../lib/rspec/support/method_signature_verifier.rb | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rspec-support/lib/rspec/support/encoded_string.rb b/rspec-support/lib/rspec/support/encoded_string.rb index 9a51cd067..e08c714f2 100644 --- a/rspec-support/lib/rspec/support/encoded_string.rb +++ b/rspec-support/lib/rspec/support/encoded_string.rb @@ -48,6 +48,10 @@ def to_s end alias :to_str :to_s + def self.pick_encoding(source_a, source_b) + Encoding.compatible?(source_a, source_b) || Encoding.default_external + end + private # Encoding Exceptions: @@ -98,7 +102,7 @@ def matching_encoding(string) # 'invalid: :replace' also replaces an invalid byte sequence # # For example: - # "\x80".force_encoding("Emacs-Mule").encode(:invalid => :replace).bytes.to_a # => + # "\x80".force_encoding("Emacs-Mule").encode(:invalid => :replace).bytes.to_a # => # => 63 # '?' # string.encode(@encoding, :invalid => :replace, :undef => :replace, :replace => REPLACE) @@ -115,10 +119,6 @@ def remove_invalid_bytes(string) def detect_source_encoding(string) string.encoding end - - def self.pick_encoding(source_a, source_b) - Encoding.compatible?(source_a, source_b) || Encoding.default_external - end end end end diff --git a/rspec-support/lib/rspec/support/method_signature_verifier.rb b/rspec-support/lib/rspec/support/method_signature_verifier.rb index a8b002f9b..d1e23726f 100644 --- a/rspec-support/lib/rspec/support/method_signature_verifier.rb +++ b/rspec-support/lib/rspec/support/method_signature_verifier.rb @@ -8,7 +8,7 @@ module Support # keyword args of a given method. # # @private - class MethodSignature # rubocop:disable ClassLength + class MethodSignature attr_reader :min_non_kw_args, :max_non_kw_args, :optional_kw_args, :required_kw_args def initialize(method) @@ -219,7 +219,7 @@ def initialize(signature, args=[]) @arbitrary_kw_args = @unlimited_args = false end - def with_expectation(expectation) # rubocop:disable MethodLength, Metrics/PerceivedComplexity + def with_expectation(expectation) return self unless MethodSignatureExpectation === expectation if expectation.empty? From 6341085782d322d28533ca725fa72634656266f9 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Wed, 18 Nov 2020 10:53:20 +0300 Subject: [PATCH 33/39] [support] Use Mutex from core, prevent it from being stubbed This commit was imported from https://github.com/rspec/rspec-support/commit/d181a2854202fb0bc4db617c267034a3276e8dc1. --- rspec-support/lib/rspec/support/mutex.rb | 73 ------------------- .../lib/rspec/support/reentrant_mutex.rb | 29 ++------ .../spec/rspec/support/mutex_spec.rb | 8 -- .../rspec/support/reentrant_mutex_spec.rb | 9 ++- 4 files changed, 16 insertions(+), 103 deletions(-) delete mode 100644 rspec-support/lib/rspec/support/mutex.rb delete mode 100644 rspec-support/spec/rspec/support/mutex_spec.rb diff --git a/rspec-support/lib/rspec/support/mutex.rb b/rspec-support/lib/rspec/support/mutex.rb deleted file mode 100644 index 1bc3ccf69..000000000 --- a/rspec-support/lib/rspec/support/mutex.rb +++ /dev/null @@ -1,73 +0,0 @@ -module RSpec - module Support - # On 1.8.7, it's in the stdlib. - # We don't want to load the stdlib, b/c this is a test tool, and can affect - # the test environment, causing tests to pass where they should fail. - # - # So we're transcribing/modifying it from - # https://github.com/ruby/ruby/blob/v1_8_7_374/lib/thread.rb#L56 - # Some methods we don't need are deleted. Anything I don't - # understand (there's quite a bit, actually) is left in. - # - # Some formating changes are made to appease the robot overlord: - # https://travis-ci.org/rspec/rspec-core/jobs/54410874 - # @private - class Mutex - def initialize - @waiting = [] - @locked = false - @waiting.taint - taint - end - - # @private - def lock - while Thread.critical = true && @locked - @waiting.push Thread.current - Thread.stop - end - @locked = true - Thread.critical = false - self - end - - # @private - def unlock - return unless @locked - Thread.critical = true - @locked = false - wakeup_and_run_waiting_thread - self - end - - # @private - def synchronize - lock - begin - yield - ensure - unlock - end - end - - private - - def wakeup_and_run_waiting_thread - begin - t = @waiting.shift - t.wakeup if t - rescue ThreadError - retry - end - Thread.critical = false - begin - t.run if t - rescue ThreadError - :noop - end - end - - # Avoid warnings for library wide checks spec - end unless defined?(::RSpec::Support::Mutex) || defined?(::Mutex) - end -end diff --git a/rspec-support/lib/rspec/support/reentrant_mutex.rb b/rspec-support/lib/rspec/support/reentrant_mutex.rb index 361135942..e61b953f3 100644 --- a/rspec-support/lib/rspec/support/reentrant_mutex.rb +++ b/rspec-support/lib/rspec/support/reentrant_mutex.rb @@ -1,15 +1,19 @@ module RSpec module Support + # This class protects us against Mutex.new stubbed out within tests. + # @private + class Mutex < ::Mutex + class << self + define_method(:new, &::Mutex.method(:new)) + end + end + # Allows a thread to lock out other threads from a critical section of code, # while allowing the thread with the lock to reenter that section. # # Based on Monitor as of 2.2 - # https://github.com/ruby/ruby/blob/eb7ddaa3a47bf48045d26c72eb0f263a53524ebc/lib/monitor.rb#L9 # - # Depends on Mutex, but Mutex is only available as part of core since 1.9.1: - # exists - http://ruby-doc.org/core-1.9.1/Mutex.html - # dne - http://ruby-doc.org/core-1.9.0/Mutex.html - # # @private class ReentrantMutex def initialize @@ -40,22 +44,5 @@ def exit @mutex.unlock end end - - if defined? ::Mutex - # On 1.9 and up, this is in core, so we just use the real one - class Mutex < ::Mutex - # If you mock Mutex.new you break our usage of Mutex, so - # instead we capture the original method to return Mutexs. - NEW_MUTEX_METHOD = Mutex.method(:new) - - def self.new - NEW_MUTEX_METHOD.call - end - end - else # For 1.8.7 - # :nocov: - RSpec::Support.require_rspec_support "mutex" - # :nocov: - end end end diff --git a/rspec-support/spec/rspec/support/mutex_spec.rb b/rspec-support/spec/rspec/support/mutex_spec.rb deleted file mode 100644 index a600ed695..000000000 --- a/rspec-support/spec/rspec/support/mutex_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'rspec/support/mutex' - -RSpec.describe RSpec::Support::Mutex do - it "allows ::Mutex to be mocked", :if => defined?(::Mutex) do - expect(Mutex).to receive(:new) - ::Mutex.new - end -end diff --git a/rspec-support/spec/rspec/support/reentrant_mutex_spec.rb b/rspec-support/spec/rspec/support/reentrant_mutex_spec.rb index f5daa5e51..036879811 100644 --- a/rspec-support/spec/rspec/support/reentrant_mutex_spec.rb +++ b/rspec-support/spec/rspec/support/reentrant_mutex_spec.rb @@ -1,8 +1,15 @@ require 'rspec/support/reentrant_mutex' require 'thread_order' +RSpec.describe RSpec::Support::Mutex do + it "allows ::Mutex to be mocked" do + expect(Mutex).to receive(:new) + ::Mutex.new + end +end + # There are no assertions specifically -# They are pass if they don't deadlock +# They pass if they don't deadlock RSpec.describe RSpec::Support::ReentrantMutex do let!(:mutex) { described_class.new } let!(:order) { ThreadOrder.new } From 864d6e0c87a6f5bc1f1373db55068015efc4dc65 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Wed, 18 Nov 2020 18:45:45 +0300 Subject: [PATCH 34/39] [support] Remove StdErrSplitter workarounds Kept gems warning muted due to: 1. RuntimeError: Warnings were generated: /home/runner/work/rspec-support/bundle/ruby/2.5.0/gems/actioncable-6.0.3.4/lib/action_cable/channel/test_case.rb:178: warning: method redefined; discarding old connection Shared Example Group: "an rspec-rails example group mixin" called from ./spec/rspec/rails/example/channel_example_group_spec.rb:6 # /home/runner/work/rspec-support/rspec-support/lib/rspec/support/spec/stderr_splitter.rb:38:in `verify_no_warnings!' 2. RuntimeError: Warnings were generated: /home/runner/work/rspec-support/bundle/ruby/2.5.0/gems/actionpack-6.0.3.4/lib/action_dispatch/routing/route_set.rb:560: warning: instance variable @_routes not initialized Shared Example Group: "an rspec-rails example group mixin" called from ./spec/rspec/rails/example/view_example_group_spec.rb:5 # /home/runner/work/rspec-support/rspec-support/lib/rspec/support/spec/stderr_splitter.rb:38:in `verify_no_warnings!' --- This commit was imported from https://github.com/rspec/rspec-support/commit/dd56e6f3347d159b17ed6c8f85c241703da83a72. --- rspec-support/lib/rspec/support/spec/stderr_splitter.rb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/rspec-support/lib/rspec/support/spec/stderr_splitter.rb b/rspec-support/lib/rspec/support/spec/stderr_splitter.rb index a7db18f7f..cacba39fa 100644 --- a/rspec-support/lib/rspec/support/spec/stderr_splitter.rb +++ b/rspec-support/lib/rspec/support/spec/stderr_splitter.rb @@ -27,15 +27,8 @@ def reopen(*args) @orig_stderr.reopen(*args) end - # To work around JRuby error: - # can't convert RSpec::Support::StdErrSplitter into String - def to_io - @orig_stderr.to_io - end - - # To work around JRuby error: - # TypeError: $stderr must have write method, RSpec::StdErrSplitter given def write(line) + # Ignore warnings coming from gems, specifically Rails on Ruby 2.5+ return if line =~ %r{^\S+/gems/\S+:\d+: warning:} # http://rubular.com/r/kqeUIZOfPG # Ruby 2.7.0 warnings from keyword argments span multiple lines, extend check above From a8854334ecfe41d1a90e63d59dae7173a1489ac8 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Sun, 22 Nov 2020 17:54:10 +0300 Subject: [PATCH 35/39] [support] Remove redundant boilerplate This commit was imported from https://github.com/rspec/rspec-support/commit/38885cfa42a65a684336fca7d35c2e47b58ebbb1. --- rspec-support/rspec-support.gemspec | 1 - rspec-support/spec/rspec/support/caller_filter_spec.rb | 1 - rspec-support/spec/rspec/support/differ_spec.rb | 2 -- rspec-support/spec/rspec/support/directory_maker_spec.rb | 1 - rspec-support/spec/rspec/support/encoded_string_spec.rb | 2 -- rspec-support/spec/rspec/support/fuzzy_matcher_spec.rb | 1 - rspec-support/spec/rspec/support/matcher_definition_spec.rb | 2 -- rspec-support/spec/rspec/support/warnings_spec.rb | 1 - 8 files changed, 11 deletions(-) diff --git a/rspec-support/rspec-support.gemspec b/rspec-support/rspec-support.gemspec index 7321aff2d..625f57cd9 100644 --- a/rspec-support/rspec-support.gemspec +++ b/rspec-support/rspec-support.gemspec @@ -1,4 +1,3 @@ -# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'rspec/support/version' diff --git a/rspec-support/spec/rspec/support/caller_filter_spec.rb b/rspec-support/spec/rspec/support/caller_filter_spec.rb index 4c7004cb3..bb02ea4fa 100644 --- a/rspec-support/spec/rspec/support/caller_filter_spec.rb +++ b/rspec-support/spec/rspec/support/caller_filter_spec.rb @@ -1,4 +1,3 @@ -require 'spec_helper' require 'fileutils' require 'rspec/support/caller_filter' diff --git a/rspec-support/spec/rspec/support/differ_spec.rb b/rspec-support/spec/rspec/support/differ_spec.rb index b08a65e72..f7ef22116 100644 --- a/rspec-support/spec/rspec/support/differ_spec.rb +++ b/rspec-support/spec/rspec/support/differ_spec.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 -require 'spec_helper' require 'ostruct' require 'timeout' require 'rspec/support/spec/string_matcher' diff --git a/rspec-support/spec/rspec/support/directory_maker_spec.rb b/rspec-support/spec/rspec/support/directory_maker_spec.rb index 28b4bfc34..3e0105ebe 100644 --- a/rspec-support/spec/rspec/support/directory_maker_spec.rb +++ b/rspec-support/spec/rspec/support/directory_maker_spec.rb @@ -1,4 +1,3 @@ -require "spec_helper" require "fileutils" RSpec::Support.require_rspec_support("directory_maker") diff --git a/rspec-support/spec/rspec/support/encoded_string_spec.rb b/rspec-support/spec/rspec/support/encoded_string_spec.rb index 1e1eaa026..486002871 100644 --- a/rspec-support/spec/rspec/support/encoded_string_spec.rb +++ b/rspec-support/spec/rspec/support/encoded_string_spec.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 -require 'spec_helper' require 'rspec/support/encoded_string' require 'rspec/support/spec/string_matcher' diff --git a/rspec-support/spec/rspec/support/fuzzy_matcher_spec.rb b/rspec-support/spec/rspec/support/fuzzy_matcher_spec.rb index 2c0abec6b..a24f3511c 100644 --- a/rspec-support/spec/rspec/support/fuzzy_matcher_spec.rb +++ b/rspec-support/spec/rspec/support/fuzzy_matcher_spec.rb @@ -1,4 +1,3 @@ -require 'spec_helper' require 'rspec/support/fuzzy_matcher' module RSpec diff --git a/rspec-support/spec/rspec/support/matcher_definition_spec.rb b/rspec-support/spec/rspec/support/matcher_definition_spec.rb index 59ab76597..eac5bac6f 100644 --- a/rspec-support/spec/rspec/support/matcher_definition_spec.rb +++ b/rspec-support/spec/rspec/support/matcher_definition_spec.rb @@ -1,5 +1,3 @@ -require "spec_helper" - module RSpec module Support RSpec.describe "matcher definitions" do diff --git a/rspec-support/spec/rspec/support/warnings_spec.rb b/rspec-support/spec/rspec/support/warnings_spec.rb index 5972819e5..0cbd8b3a4 100644 --- a/rspec-support/spec/rspec/support/warnings_spec.rb +++ b/rspec-support/spec/rspec/support/warnings_spec.rb @@ -1,4 +1,3 @@ -require "spec_helper" require "rspec/support/warnings" require 'rspec/support/spec/shell_out' From 75f330a5521c9ff49ae586af9d0259f008c2018c Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Mon, 23 Nov 2020 22:20:28 +0300 Subject: [PATCH 36/39] [support] Remove BasicObject checks This commit was imported from https://github.com/rspec/rspec-support/commit/a9ab5227c998c8b568ada08d1347fecf393209b9. --- rspec-support/spec/rspec/support_spec.rb | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/rspec-support/spec/rspec/support_spec.rb b/rspec-support/spec/rspec/support_spec.rb index 839c4d57e..21d174339 100644 --- a/rspec-support/spec/rspec/support_spec.rb +++ b/rspec-support/spec/rspec/support_spec.rb @@ -128,24 +128,10 @@ def method_missing(name, *args, &block) context 'with a BasicObject instance' do let(:object) do - basic_object_class.new + BasicObject.new end - let(:basic_object_class) do - defined?(BasicObject) ? BasicObject : fake_basic_object_class - end - - let(:fake_basic_object_class) do - Class.new do - def self.to_s - 'BasicObject' - end - - undef class, inspect, respond_to? - end - end - - it { should equal(basic_object_class) } + it { should equal(BasicObject) } end context 'with nil' do From e094fe8388311db95c9df68d0e07c50829123a14 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Tue, 8 Dec 2020 15:38:15 +0300 Subject: [PATCH 37/39] [support] Remove filtering for unsupported rubies This commit was imported from https://github.com/rspec/rspec-support/commit/048eedd1f6a2ca7a6cfe7634855f9da21432e624. --- rspec-support/lib/rspec/support/spec/shell_out.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/rspec-support/lib/rspec/support/spec/shell_out.rb b/rspec-support/lib/rspec/support/spec/shell_out.rb index 2c987ddb6..e480d766c 100644 --- a/rspec-support/lib/rspec/support/spec/shell_out.rb +++ b/rspec-support/lib/rspec/support/spec/shell_out.rb @@ -45,8 +45,6 @@ def run_ruby_with_current_load_path(ruby_command, *flags) %r{jruby-\d\.\d\.\d+\.\d/lib/ruby/stdlib/rubygems}, # This is required for windows for some reason %r{lib/bundler/rubygems}, - # This is a JRuby file that generates warnings on 9.0.3.0 - %r{lib/ruby/stdlib/jar}, # This is a JRuby file that generates warnings on 9.1.7.0 %r{org/jruby/RubyKernel\.java}, # This is a JRuby gem that generates warnings on 9.1.7.0 From f4192053db18e55aca3afbfede70919325a6ac45 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Tue, 8 Dec 2020 15:42:46 +0300 Subject: [PATCH 38/39] [support] Fix code doc This commit was imported from https://github.com/rspec/rspec-support/commit/a787fa250c2ea15cf150b33890f892aa1c5e6522. --- rspec-support/lib/rspec/support/fuzzy_matcher.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rspec-support/lib/rspec/support/fuzzy_matcher.rb b/rspec-support/lib/rspec/support/fuzzy_matcher.rb index 415194954..d225c5bcb 100644 --- a/rspec-support/lib/rspec/support/fuzzy_matcher.rb +++ b/rspec-support/lib/rspec/support/fuzzy_matcher.rb @@ -17,8 +17,7 @@ def self.values_match?(expected, actual) begin expected === actual rescue ArgumentError - # Some objects, like 0-arg lambdas on 1.9+, raise - # ArgumentError for `expected === actual`. + # Some objects, like 0-arg lambdas, raise ArgumentError when compared with ===. false end end From d8e8eb455b7177f6f3135235e189e9f4d46959db Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Sat, 12 Dec 2020 12:46:20 +0300 Subject: [PATCH 39/39] [support] Downgrade ffi due to their usage of Gem This commit was imported from https://github.com/rspec/rspec-support/commit/0d41dfabb118c922b6ad07f996075da206fa5dd1. --- rspec-support/Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rspec-support/Gemfile b/rspec-support/Gemfile index b82941338..d033cf576 100644 --- a/rspec-support/Gemfile +++ b/rspec-support/Gemfile @@ -20,7 +20,7 @@ else end gem "childprocess", ">= 3.0.0" -gem 'ffi', '~> 1.13.0' +gem 'ffi', '~> 1.12.0' ### dep for ci/coverage gem 'simplecov', '~> 0.8'