Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle keyword arguments separately for and_call_original in supported rubies #1324

Closed
wants to merge 11 commits into from

Conversation

JonRowe
Copy link
Member

@JonRowe JonRowe commented Apr 5, 2020

Fixes #1306

@pirj
Copy link
Member

pirj commented Apr 5, 2020

I'm still getting a warning running this:

class Foo
  def initialize(bar: nil); end
end

RSpec.describe Foo do
  it 'works' do
    expect(Foo).to receive(:new).with(bar: :qux).and_call_original
    Foo.new(bar: :qux)
  end
end

@JonRowe
Copy link
Member Author

JonRowe commented Apr 5, 2020

Ah thats a slightly difference scenario, I think we need more tests around this.

@pirj
Copy link
Member

pirj commented Apr 5, 2020

That's from #1306 🤷‍♂

@JonRowe
Copy link
Member Author

JonRowe commented Apr 5, 2020

Yeah I had written a similar spec assuming it was and_call_original to blame, but seems like we'll need more specs considering we have so little surrounding keyword argument usage in these places.

@benoittgt
Copy link
Member

Does #1324 (comment) will be handle in this PR too?

@JonRowe
Copy link
Member Author

JonRowe commented Apr 6, 2020

Yep I'll be continuing some work over in rspec support later, and then using that with more scenarios to fix this.

@imanel
Copy link

imanel commented Jun 8, 2020

Any update on this?

Copy link
Member

@pirj pirj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.
Do you mind for another CI run just in case?

Shame on me. There's still this receive(:new) case left to fix.

@imanel
Copy link

imanel commented Jun 8, 2020

@pirj rspec/rspec-support#419 should solve the case you mentioned, although it's a bit hacky so I'm not sure if it will get accepted.

@pirj
Copy link
Member

pirj commented Jun 9, 2020

Pushed a spec to cover the original request.
It passes nicely with rspec/rspec-support#419.
Do you want me to move the conditional that transforms new to initialize from rspec-support here?

@pirj
Copy link
Member

pirj commented Jul 28, 2020

@JonRowe It seems we either have to merge rspec/rspec-support#419, or to close it and pull the method_or_class_initialize to rspec-mocks.
What would you prefer?

@JonRowe JonRowe changed the base branch from master to main August 2, 2020 02:08
@benoittgt
Copy link
Member

Quick win while waiting for the patch. Bump to Ruby 2.7.2 released this week. No more warnings.

@pirj
Copy link
Member

pirj commented Oct 5, 2020

Bump to Ruby 2.7.2 released this week. No more warnings.

That would be just perfect if we didn't have to do anything tricky with RSpec to be kwargs-compliant and no warnings were issued!

Wondering if this means there's a performance impact in Ruby 3.
Do you think it's possible to take one of our previous kwargs-related fixes and benchmark the difference?

I'm more inclined to let postpone those fixes if the performance gain is marginal rather than maintain this burden up to the point when we drop support for Ruby 2.x (for three more years to come or so).

@JonRowe
Copy link
Member Author

JonRowe commented Oct 5, 2020

The warnings just become opt-in in 2.7.2, they don't change the fact we need to change these things before Ruby 3.0 is released.

@benoittgt
Copy link
Member

The warnings just become opt-in in 2.7.2, they don't change the fact we need to change these things before Ruby 3.0 is released.

Yes of course. :)

@benoittgt
Copy link
Member

I spend already 5 hours trying to implement rspec/rspec-support#419 (comment) but I'm stuck. I am not able to fix the failing spec here.

@pirj or @JonRowe will you be available to tackle this in pairing with me ? I understand we need to find the right signature for the new => initialize but I am no able to properly refer to the method we want to call and I do not see a clear path to reuse the existing code made in rspec-mocks.

@pirj
Copy link
Member

pirj commented Oct 16, 2020

@benoittgt I'm available tomorrow in the afternoon. Lost track of what's going on, so you lead, me image

godfat added a commit to godfat/muack that referenced this pull request Nov 28, 2020
@godfat
Copy link

godfat commented Nov 28, 2020

I am providing two more failures for and_wrap_original and block implementation. This is just a demonstration so I omitted binding.eval and we might want to put those in the corresponding tests.

diff --git a/spec/rspec/mocks/and_call_original_spec.rb b/spec/rspec/mocks/and_call_original_spec.rb
index 25c5a85c..4459142a 100644
--- a/spec/rspec/mocks/and_call_original_spec.rb
+++ b/spec/rspec/mocks/and_call_original_spec.rb
@@ -88,6 +88,18 @@ RSpec.describe "and_call_original" do
         expect(instance.initialize_arg).to eq :initialize_arg
         CODE
       end
+
+      it 'works with keyword arguments for and_wrap_original' do
+        expect(instance).to receive(:meth_3).and_wrap_original { |m, keyword_arg:| m.call(keyword_arg: keyword_arg.to_s) }
+
+        expect(instance.meth_3(keyword_arg: :original_arg)).to eq 'original_arg'
+      end
+
+      it 'works with keyword arguments for block implementation' do
+        expect(instance).to receive(:meth_4) { |keyword_arg:| keyword_arg }
+
+        expect(instance.meth_4(keyword_arg: :original_arg)).to eq :original_arg
+      end
     end
 
     it 'errors when you pass through the wrong number of args' do

Or in Ruby:

it 'works with keyword arguments for and_wrap_original' do
  expect(instance).to receive(:meth_3).and_wrap_original { |m, keyword_arg:| m.call(keyword_arg: keyword_arg.to_s) }

  expect(instance.meth_3(keyword_arg: :original_arg)).to eq 'original_arg'
end

it 'works with keyword arguments for block implementation' do
  expect(instance).to receive(:meth_4) { |keyword_arg:| keyword_arg }

  expect(instance.meth_4(keyword_arg: :original_arg)).to eq :original_arg
end

@godfat
Copy link

godfat commented Nov 28, 2020

This fixed and_wrap_original

diff --git a/lib/rspec/mocks/message_expectation.rb b/lib/rspec/mocks/message_expectation.rb
index d999dc0f..643b7ca5 100644
--- a/lib/rspec/mocks/message_expectation.rb
+++ b/lib/rspec/mocks/message_expectation.rb
@@ -115,7 +115,9 @@ module RSpec
       #     original_method.call(*args, &block).first(10)
       #   end
       def and_wrap_original(&block)
-        wrap_original(__method__, &block)
+        wrap_original(__method__) do |original, *args, &supplied_block|
+          __call_original(block, original, *args, &supplied_block)
+        end
       end
 
       # @overload and_raise

@bryanp
Copy link
Contributor

bryanp commented Dec 29, 2020

@JonRowe What's the status on getting this finished up and merged? and_call_original is currently broken on Ruby 3 when the original method accepts keyword arguments. Willing to help, but not sure what all is left here.

@pirj
Copy link
Member

pirj commented Dec 29, 2020

@bryanp According to the plan, quite a lot of method have to be marked with ruby2_keywords.

I took a stab at a similar rspec-core issue today, but could barely handle half. There's an uncertainty if the whole chain should be marked as such or something is not right with the approach yet. On a synthetic example, ruby2_keywords do help, but marking just a few methods down the call chain doesn't.

You can certainly help. There is this #1306 (comment) that you can turn into a failing spec like this https://github.com/rspec/rspec-mocks/pull/1383/files#diff-ee4bec83cefa8d926951324b0059b58f31c016628981f29a0c391cd9b8a51315R15. After that, apply the approach described here rspec/rspec-expectations#1221 (comment) and (in brief) here rspec/rspec-expectations#1241 (comment).

If you decide to do so, please open a draft PR so we can avoid double work and intervene early in the process.

@bryanp
Copy link
Contributor

bryanp commented Dec 29, 2020

@pirj Here's an initial PR that resolves the issue I hit today: #1385.

@JonRowe JonRowe closed this Jan 7, 2021
@JonRowe JonRowe deleted the handle-kw-args branch January 7, 2021 12:10
julik pushed a commit to WeTransfer/zip_tricks that referenced this pull request Jan 9, 2021
✅   Locally, for Ruby <= 2.7.2 specs are all passing
❌   For Ruby 3.0 some specs are failing. All errors seem to be related to the changed behavior for kwargs
  -> **update**: see comments below!

```
Finished in 19.31 seconds (files took 0.67243 seconds to load)
123 examples, 7 failures

Failed examples:

rspec ./spec/zip_tricks/block_deflate_spec.rb:46 # ZipTricks::BlockDeflate deflate_in_blocks_and_terminate uses deflate_in_blocks
rspec ./spec/zip_tricks/block_deflate_spec.rb:58 # ZipTricks::BlockDeflate deflate_in_blocks_and_terminate passes a custom compression level
rspec ./spec/zip_tricks/block_deflate_spec.rb:87 # ZipTricks::BlockDeflate.deflate_in_blocks honors the block size
rspec ./spec/zip_tricks/rails_streaming_spec.rb:4 # ZipTricks::RailsStreaming calls the requisite controller methods
rspec ./spec/zip_tricks/streamer_spec.rb:276 # ZipTricks::Streamer writes the correct archive elements when using data descriptors
rspec ./spec/zip_tricks/streamer_spec.rb:420 # ZipTricks::Streamer prevents duplicates in the stored files
rspec ./spec/zip_tricks/streamer_spec.rb:525 # ZipTricks::Streamer writes the specified modification time
```

-> I've added a new issue to address the actual fixes for Ruby 3 in a separated PR: #104


**update**
the actual problem (for the first spec at least) is `rspec-mocks`, in particular this line: https://github.com/rspec/rspec-mocks/blob/6ab343ca479c606e892c82ce0245e7410e3f0eac/lib/rspec/mocks/message_expectation.rb#L101

```ruby
      def and_call_original
        wrap_original(__method__) do |original, *args, &block|
          original.call(*args, &block)
        end
      end
```

which will receive the following `args`:
```ruby
[#<StringIO:0x00007ff8d713bfc8>, #<StringIO:0x00007ff8d713be88>, {:level=>-1, :block_size=>65536}]
```
hence this is expanded to 3 params, 2x a string, and a hash, which is then not splatted into kwargs.
Further research shows, there is already an Issue: rspec/rspec-mocks#1306 with a fixing PR: rspec/rspec-mocks#1324
julik pushed a commit to julik/zip_kit that referenced this pull request Feb 29, 2024
✅   Locally, for Ruby <= 2.7.2 specs are all passing
❌   For Ruby 3.0 some specs are failing. All errors seem to be related to the changed behavior for kwargs
  -> **update**: see comments below!

```
Finished in 19.31 seconds (files took 0.67243 seconds to load)
123 examples, 7 failures

Failed examples:

rspec ./spec/zip_tricks/block_deflate_spec.rb:46 # ZipTricks::BlockDeflate deflate_in_blocks_and_terminate uses deflate_in_blocks
rspec ./spec/zip_tricks/block_deflate_spec.rb:58 # ZipTricks::BlockDeflate deflate_in_blocks_and_terminate passes a custom compression level
rspec ./spec/zip_tricks/block_deflate_spec.rb:87 # ZipTricks::BlockDeflate.deflate_in_blocks honors the block size
rspec ./spec/zip_tricks/rails_streaming_spec.rb:4 # ZipTricks::RailsStreaming calls the requisite controller methods
rspec ./spec/zip_tricks/streamer_spec.rb:276 # ZipTricks::Streamer writes the correct archive elements when using data descriptors
rspec ./spec/zip_tricks/streamer_spec.rb:420 # ZipTricks::Streamer prevents duplicates in the stored files
rspec ./spec/zip_tricks/streamer_spec.rb:525 # ZipTricks::Streamer writes the specified modification time
```

-> I've added a new issue to address the actual fixes for Ruby 3 in a separated PR: WeTransfer#104


**update**
the actual problem (for the first spec at least) is `rspec-mocks`, in particular this line: https://github.com/rspec/rspec-mocks/blob/6ab343ca479c606e892c82ce0245e7410e3f0eac/lib/rspec/mocks/message_expectation.rb#L101

```ruby
      def and_call_original
        wrap_original(__method__) do |original, *args, &block|
          original.call(*args, &block)
        end
      end
```

which will receive the following `args`:
```ruby
[#<StringIO:0x00007ff8d713bfc8>, #<StringIO:0x00007ff8d713be88>, {:level=>-1, :block_size=>65536}]
```
hence this is expanded to 3 params, 2x a string, and a hash, which is then not splatted into kwargs.
Further research shows, there is already an Issue: rspec/rspec-mocks#1306 with a fixing PR: rspec/rspec-mocks#1324
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Keyword argument warning on Ruby 2.7 for #and_call_original
7 participants