rspec-expectations
rspec-expectations copied to clipboard
Regression in 3.13: custom matcher hash argument improperly converted to keyword args, results in `ArgumentError`
Subject of the issue
My project has a custom matcher defined with an optional keyword arg. After upgrading to RSpec 3.13, I get an ArgumentError that indicates that RSpec is converting a hash argument into keyword args for some reason.
Your environment
- Ruby version: 3.2.2
- rspec-expectations version: 3.13.0
Steps to reproduce
Create a file named rspec_bug.rb with these contents:
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "rspec", ENV.fetch("RSPEC_VERSION", "3.13.0")
end
require "rspec"
require "rspec/autorun"
puts "Ruby version: #{RUBY_VERSION}"
puts "RSpec::Expectations version: #{RSpec::Expectations::Version::STRING}"
RSpec::Matchers.define :match_against_with_optional_kwarg do |hash, optional_kwarg: true|
match { |actual| true }
end
RSpec::Matchers.define :match_against_without_optional_kwarg do |hash|
match { |actual| true }
end
RSpec.describe "A custom matcher" do
it "can accept an optional kwarg" do
expect(:something).to match_against_with_optional_kwarg({5 => "five", "some" => "hash", "of" => "data", [:foo] => 10})
end
it "can be used without an optional kwarg" do
expect(:something).to match_against_without_optional_kwarg({5 => "five", "some" => "hash", "of" => "data", [:foo] => 10})
end
end
Expected behavior
I expect this spec to pass, as it does on every recent version of RSpec:
~/code/ RSPEC_VERSION=3.8.0 ruby rspec_bug.rb -b
Ruby version: 3.2.2
RSpec::Expectations version: 3.8.6
..
Finished in 0.00132 seconds (files took 0.04301 seconds to load)
2 examples, 0 failures
~/code/ RSPEC_VERSION=3.9.0 ruby rspec_bug.rb -b
Ruby version: 3.2.2
RSpec::Expectations version: 3.9.4
..
Finished in 0.00194 seconds (files took 0.04708 seconds to load)
2 examples, 0 failures
~/code/ RSPEC_VERSION=3.10.0 ruby rspec_bug.rb -b
Ruby version: 3.2.2
RSpec::Expectations version: 3.10.2
..
Finished in 0.00181 seconds (files took 0.04384 seconds to load)
2 examples, 0 failures
~/code/ RSPEC_VERSION=3.11.0 ruby rspec_bug.rb -b
Ruby version: 3.2.2
RSpec::Expectations version: 3.11.1
..
Finished in 0.00135 seconds (files took 0.04207 seconds to load)
2 examples, 0 failures
~/code/ RSPEC_VERSION=3.12.0 ruby rspec_bug.rb -b
Ruby version: 3.2.2
RSpec::Expectations version: 3.12.3
..
Finished in 0.00166 seconds (files took 0.04384 seconds to load)
2 examples, 0 failures
Actual behavior
On RSpec 3.13, it fails with a very odd error:
~/code/ RSPEC_VERSION=3.13.0 ruby rspec_bug.rb -b
Ruby version: 3.2.2
RSpec::Expectations version: 3.13.0
F.
Failures:
1) A custom matcher can accept an optional kwarg
Failure/Error:
RSpec::Matchers.define :match_against_with_optional_kwarg do |hash, optional_kwarg: true|
match { |actual| true }
end
ArgumentError:
unknown keywords: 5, "some", "of", [:foo]
# rspec_bug.rb:14:in `block in <main>'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-support-3.13.1/lib/rspec/support/with_keywords_when_needed.rb:20:in `class_exec'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-support-3.13.1/lib/rspec/support/with_keywords_when_needed.rb:20:in `class_exec'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-support-3.13.1/lib/rspec/support/with_keywords_when_needed.rb:19:in `eval'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-support-3.13.1/lib/rspec/support/with_keywords_when_needed.rb:19:in `class_exec'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-expectations-3.13.0/lib/rspec/matchers/dsl.rb:475:in `initialize'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-expectations-3.13.0/lib/rspec/matchers/dsl.rb:76:in `new'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-expectations-3.13.0/lib/rspec/matchers/dsl.rb:76:in `block in define'
# rspec_bug.rb:24:in `block (2 levels) in <main>'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:263:in `instance_exec'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:263:in `block in run'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb:486:in `block in run'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb:624:in `run_around_example_hooks_for'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb:486:in `run'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:259:in `run'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/example_group.rb:642:in `map'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/example_group.rb:642:in `run_examples'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/example_group.rb:607:in `run'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:121:in `map'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/configuration.rb:2091:in `with_suite_hooks'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:116:in `block in run_specs'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/reporter.rb:74:in `report'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:115:in `run_specs'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:89:in `run'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:71:in `run'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:45:in `invoke'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:38:in `perform_at_exit'
# /Users/myron/.rvm/gems/ruby-3.2.2/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:24:in `block in autorun'
Finished in 0.00177 seconds (files took 0.04443 seconds to load)
2 examples, 1 failure
Failed examples:
rspec rspec_bug.rb:23 # A custom matcher can accept an optional kwarg
The error (ArgumentError: unknown keywords: 5, "some", "of", [:foo]) indicates that RSpec is somehow converting my hash of data into keyword args, even though the keys aren't valid for keyword args, and it's just a hash of data being passed as a positional arg.
This bug appears to be triggered by the presence of an optional keyword arg on the definition of the matcher itself.