Skip to content

Commit

Permalink
Relax specs for Ruby 3.4 hash formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
JonRowe committed Oct 19, 2024
1 parent d8f0054 commit f8b4934
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 51 deletions.
6 changes: 3 additions & 3 deletions features/setting_constraints/matching_arguments.feature
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Feature: Matching arguments
end
"""
When I run `rspec keyword_example_spec.rb`
Then it should fail with the following output:
Then it should fail with the following output, ignoring hash syntax:
| 2 examples, 1 failure |
| |
| Failure/Error: dbl.foo(bar: "incorrect") |
Expand All @@ -100,7 +100,7 @@ Feature: Matching arguments
end
"""
When I run `rspec keyword_example_spec.rb`
Then it should fail with the following output:
Then it should fail with the following output, ignoring hash syntax:
| 1 example, 1 failure |
| |
| Failure/Error: dbl.foo({bar: "also incorrect"}) |
Expand Down Expand Up @@ -156,7 +156,7 @@ Feature: Matching arguments
end
"""
When I run `rspec rspec_satisfy_spec.rb`
Then it should fail with the following output:
Then it should fail with the following output, ignoring hash syntax:
| 2 examples, 1 failure |
| |
| Failure/Error: dbl.foo({ :a => { :b => { :c => 3 } } }) |
Expand Down
7 changes: 6 additions & 1 deletion features/step_definitions/additional_cli_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@
diffable
end

Then /^it should fail with the following output:$/ do |table|
Then /^it should fail with the following output(, ignoring hash syntax)?:$/ do |ignore_hash_syntax, table|
step %q(the exit status should be 1)
lines = table.raw.flatten.reject(&:empty?)

if ignore_hash_syntax && RUBY_VERSION.to_f > 3.3
lines = lines.map { |line| line.gsub(/([^\s])=>/, '\1 => ') }
end

expect(all_output).to match_table(lines)
end
14 changes: 9 additions & 5 deletions spec/rspec/mocks/argument_matchers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ module Mocks
expect(a_double).to receive(:random_call).with(hash_including(:a => 1))
expect {
a_double.random_call(:a => 2)
}.to fail_including "expected: (hash_including(:a=>1))"
}.to fail_including "expected: (hash_including(#{hash_syntax(:a => 1)}))"
end
end

Expand All @@ -270,7 +270,7 @@ module Mocks
expect(a_double).to receive(:random_call).with(hash_excluding(:a => 1))
expect {
a_double.random_call(:a => 1)
}.to fail_including "expected: (hash_not_including(:a=>1))"
}.to fail_including "expected: (hash_not_including(#{hash_syntax(:a => 1)}))"
end
end

Expand Down Expand Up @@ -431,7 +431,7 @@ def ==(other)
expect(a_double).to receive(:random_call).with(:a => "a", :b => "b")
expect do
a_double.random_call(opts)
end.to fail_with(/expected: \(\{(:a=>\"a\", :b=>\"b\"|:b=>\"b\", :a=>\"a\")\}\)/)
end.to fail_with(/expected: \(\{(:a\s*=>\s*\"a\", :b\s*=>\s*\"b\"|:b\s*=>\s*\"b\", :a\s*=>\s*\"a\")\}\)/)
end
else
it "matches against a hash submitted as a positional argument and received as keyword arguments in Ruby 2.7 or before" do
Expand All @@ -445,14 +445,14 @@ def ==(other)
expect(a_double).to receive(:random_call).with(:a => "b", :c => "d")
expect do
a_double.random_call(:a => "b", :c => "e")
end.to fail_with(/expected: \(\{(:a=>\"b\", :c=>\"d\"|:c=>\"d\", :a=>\"b\")\}\)/)
end.to fail_with(/expected: \(\{(:a\s*=>\s*\"b\", :c\s*=>\s*\"d\"|:c\s*=>\s*\"d\", :a\s*=>\s*\"b\")\}\)/)
end

it "fails for a hash w/ wrong keys", :reset => true do
expect(a_double).to receive(:random_call).with(:a => "b", :c => "d")
expect do
a_double.random_call("a" => "b", "c" => "d")
end.to fail_with(/expected: \(\{(:a=>\"b\", :c=>\"d\"|:c=>\"d\", :a=>\"b\")\}\)/)
end.to fail_with(/expected: \(\{(:a\s*=>\s*\"b\", :c\s*=>\s*\"d\"|:c\s*=>\s*\"d\", :a\s*=>\s*\"b\")\}\)/)
end

it "matches a class against itself" do
Expand Down Expand Up @@ -502,6 +502,10 @@ def ==(other)
expect { a_double.msg 3 }.to fail_including "expected: (my_thing)"
end
end

def hash_syntax(hash)
hash.inspect.gsub(/\{(.*)\}/, '\1')
end
end
end
end
84 changes: 54 additions & 30 deletions spec/rspec/mocks/diffing_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,18 @@
if RSpec::Support::RubyFeatures.distincts_kw_args_from_positional_hash?
eval <<-'RUBY', nil, __FILE__, __LINE__ + 1
it "prints a diff when keyword argument were expected but got an option hash (using splat)" do
message =
"#<Double \"double\"> received :foo with unexpected arguments\n" \
" expected: ({:baz=>:quz, :foo=>:bar}) (keyword arguments)\n" \
" got: ({:baz=>:quz, :foo=>:bar}) (options hash)"
message = message.gsub('=>', ' => ') if RUBY_VERSION.to_f > 3.3
with_unfulfilled_double do |d|
expect(d).to receive(:foo).with(**expected_hash)
expect {
d.foo(expected_hash)
}.to fail_with(
"#<Double \"double\"> received :foo with unexpected arguments\n" \
" expected: ({:baz=>:quz, :foo=>:bar}) (keyword arguments)\n" \
" got: ({:baz=>:quz, :foo=>:bar}) (options hash)"
)
}.to fail_with(message)
end
end
RUBY
Expand All @@ -117,14 +120,18 @@
it "prints a diff when keyword argument were expected but got an option hash (literal)" do
with_unfulfilled_double do |d|
expect(d).to receive(:foo).with(:positional, keyword: 1)
expect {
options = { keyword: 1 }
d.foo(:positional, options)
}.to fail_with(
message =
"#<Double \"double\"> received :foo with unexpected arguments\n" \
" expected: (:positional, {:keyword=>1}) (keyword arguments)\n" \
" got: (:positional, {:keyword=>1}) (options hash)"
)
message = message.gsub('=>',' => ') if RUBY_VERSION.to_f > 3.3
expect {
options = { keyword: 1 }
d.foo(:positional, options)
}.to fail_with(message)
end
end
RUBY
Expand All @@ -139,18 +146,21 @@
expect(d).to receive(:foo).with(expected_input, one: 1)
expect {
options = { one: 1 }
d.foo(actual_input, options)
}.to fail_with(
message =
"#<Double \"double\"> received :foo with unexpected arguments\n" \
" expected: (#{expected_input.inspect}, {:one=>1}) (keyword arguments)\n" \
" got: (#{actual_input.inspect}, {:one=>1}) (options hash)\n" \
"Diff:\n" \
"@@ -1 +1 @@\n" \
"-[#{expected_input.inspect}, {:one=>1}]\n" \
"+[#{actual_input.inspect}, {:one=>1}]\n"
)
message = message.gsub('=>',' => ') if RUBY_VERSION.to_f > 3.3
expect {
options = { one: 1 }
d.foo(actual_input, options)
}.to fail_with(message)
end
end
RUBY
Expand All @@ -172,28 +182,34 @@
if RSpec::Support::RubyFeatures.distincts_kw_args_from_positional_hash?
eval <<-'RUBY', nil, __FILE__, __LINE__ + 1
it "prints a diff when keyword argument were expected but got an option hash (using splat)" do
expect(d).to receive(:foo).with(:positional, **expected_hash)
expect {
d.foo(:positional, expected_hash)
}.to fail_with(
message =
"#{d.inspect} received :foo with unexpected arguments\n" \
" expected: (:positional, {:baz=>:quz, :foo=>:bar}) (keyword arguments)\n" \
" got: (:positional, {:baz=>:quz, :foo=>:bar}) (options hash)"
)
message.gsub!('=>',' => ') if RUBY_VERSION.to_f > 3.3
expect(d).to receive(:foo).with(:positional, **expected_hash)
expect {
d.foo(:positional, expected_hash)
}.to fail_with(message)
end
RUBY

eval <<-'RUBY', nil, __FILE__, __LINE__ + 1
it "prints a diff when keyword argument were expected but got an option hash (literal)" do
message =
"#{d.inspect} received :foo with unexpected arguments\n" \
" expected: (:positional, {:keyword=>1}) (keyword arguments)\n" \
" got: (:positional, {:keyword=>1}) (options hash)"
message.gsub!('=>',' => ') if RUBY_VERSION.to_f > 3.3
expect(d).to receive(:foo).with(:positional, keyword: 1)
expect {
options = { keyword: 1 }
d.foo(:positional, options)
}.to fail_with(
"#{d.inspect} received :foo with unexpected arguments\n" \
" expected: (:positional, {:keyword=>1}) (keyword arguments)\n" \
" got: (:positional, {:keyword=>1}) (options hash)"
)
}.to fail_with(message)
end
RUBY

Expand All @@ -206,18 +222,21 @@
expect(d).to receive(:foo).with(expected_input, one: 1)
expect {
options = { one: 1 }
d.foo(actual_input, options)
}.to fail_with(
message =
"#{d.inspect} received :foo with unexpected arguments\n" \
" expected: (#{expected_input.inspect}, {:one=>1}) (keyword arguments)\n" \
" got: (#{actual_input.inspect}, {:one=>1}) (options hash)\n" \
"Diff:\n" \
"@@ -1 +1 @@\n" \
"-[#{expected_input.inspect}, {:one=>1}]\n" \
"+[#{actual_input.inspect}, {:one=>1}]\n"
)
message = message.gsub('=>', ' => ') if RUBY_VERSION.to_f > 3.3
expect {
options = { one: 1 }
d.foo(actual_input, options)
}.to fail_with(message)
end
RUBY
end
Expand All @@ -232,6 +251,11 @@
def hash_regex_inspect(hash)
"\\{(#{hash.map { |key, value| "#{key.inspect}=>#{value.inspect}.*" }.join "|"}){#{hash.size}}\\}"
end
elsif RUBY_VERSION.to_f > 3.3
# Ruby head / 3.4 is changing the hash syntax inspect, but we use PP when diffing which just spaces out hashrockets
def hash_regex_inspect(hash)
Regexp.escape("{#{hash.map { |key, value| "#{key.inspect} => #{value.inspect}"}.join(", ")}}")
end
else
def hash_regex_inspect(hash)
Regexp.escape(hash.inspect)
Expand Down
9 changes: 8 additions & 1 deletion spec/rspec/mocks/double_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -526,10 +526,17 @@ def initialize(amount, units)

if kw_args_supported?
it 'fails when calling yielding method with invalid kw args' do
message =
if RUBY_VERSION.to_f > 3.3
'#<Double "test double"> yielded |{:x => 1, :y => 2}| to block with optional keyword args (:x)'
else
'#<Double "test double"> yielded |{:x=>1, :y=>2}| to block with optional keyword args (:x)'
end

expect(@double).to receive(:yield_back).and_yield(:x => 1, :y => 2)
expect {
eval("@double.yield_back { |x: 1| }")
}.to fail_with '#<Double "test double"> yielded |{:x=>1, :y=>2}| to block with optional keyword args (:x)'
}.to fail_with message
end
end

Expand Down
9 changes: 8 additions & 1 deletion spec/rspec/mocks/hash_excluding_matcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ module ArgumentMatchers
RSpec.describe HashExcludingMatcher do

it "describes itself properly" do
expect(HashExcludingMatcher.new(:a => 5).description).to eq "hash_not_including(:a=>5)"
message =
if RUBY_VERSION.to_f > 3.3
"hash_not_including(a: 5)"
else
"hash_not_including(:a=>5)"
end

expect(HashExcludingMatcher.new(:a => 5).description).to eq message
end

describe "passing" do
Expand Down
9 changes: 8 additions & 1 deletion spec/rspec/mocks/hash_including_matcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ module ArgumentMatchers
RSpec.describe HashIncludingMatcher do

it "describes itself properly" do
expect(HashIncludingMatcher.new(:a => 1).description).to eq "hash_including(:a=>1)"
message =
if RUBY_VERSION.to_f > 3.3
"hash_including(a: 1)"
else
"hash_including(:a=>1)"
end

expect(HashIncludingMatcher.new(:a => 1).description).to eq message
end

it "describes passed matchers" do
Expand Down
26 changes: 19 additions & 7 deletions spec/rspec/mocks/matchers/receive_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,24 @@ def kw_args_method(a:, b:); end
dbl.kw_args_method(a: 1, b: 2)
end
if RUBY_VERSION >= '3.0'
if RUBY_VERSION.to_f >= 3.0
it "fails to expect to receive hash with keyword args" do
expect {
dbl = instance_double(TestObject)
expect(dbl).to receive(:kw_args_method).with(a: 1, b: 2)
dbl.kw_args_method({a: 1, b: 2})
}.to fail_with do |failure|
reset_all
expect(failure.message)
.to include('expected: ({:a=>1, :b=>2}) (keyword arguments)')
.and include('got: ({:a=>1, :b=>2}) (options hash)')
if RUBY_VERSION.to_f > 3.3
expect(failure.message)
.to include('expected: ({:a => 1, :b => 2}) (keyword arguments)')
.and include('got: ({:a => 1, :b => 2}) (options hash)')
else
expect(failure.message)
.to include('expected: ({:a=>1, :b=>2}) (keyword arguments)')
.and include('got: ({:a=>1, :b=>2}) (options hash)')
end
end
end
else
Expand Down Expand Up @@ -505,9 +512,14 @@ def receiver.method_missing(*); end # a poor man's stub...
receiver.foo(1, :bar => 2)
receiver.foo(1, :bar => 3)

expect { verify_all }.to(
raise_error(/received: 2 times with arguments: \(anything, hash_including\(:bar=>"anything"\)\)$/)
)
message =
if RUBY_VERSION.to_f > 3.3
/received: 2 times with arguments: \(anything, hash_including\(bar: "anything"\)\)$/
else
/received: 2 times with arguments: \(anything, hash_including\(:bar=>"anything"\)\)$/
end

expect { verify_all }.to raise_error(message)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,19 @@ module Mocks
dbl.kw_args_method(1, :required_arg => 2, :optional_arg => 3)
end

if RUBY_VERSION >= "3"
if RUBY_VERSION.to_f >= 3.0
it "fails to match against a hash submitted as a positional argument and received as keyword arguments in Ruby 3.0 or later", :reset => true do
messages =
if RUBY_VERSION.to_f > 3.3
["expected: (1, {:optional_arg => 3, :required_arg => 2}) (keyword arguments)", "got: (1, {:optional_arg => 3, :required_arg => 2}) (options hash)"]
else
["expected: (1, {:optional_arg=>3, :required_arg=>2}) (keyword arguments)", "got: (1, {:optional_arg=>3, :required_arg=>2}) (options hash)"]
end

expect(dbl).to receive(:kw_args_method).with(1, :required_arg => 2, :optional_arg => 3)
expect do
dbl.kw_args_method(1, {:required_arg => 2, :optional_arg => 3})
end.to fail_with(a_string_including("expected: (1, {:optional_arg=>3, :required_arg=>2}) (keyword arguments)", "got: (1, {:optional_arg=>3, :required_arg=>2}) (options hash)"))
end.to fail_with(a_string_including(*messages))
end
else
it "matches against a hash submitted as a positional argument and received as keyword arguments in Ruby 2.7 or before" do
Expand Down

0 comments on commit f8b4934

Please sign in to comment.