Skip to content

Ruby 3: RSpec table syntax broken for Integer tables

Problem

While working towards a working CI build with Ruby 3, I ran into an issue where RSpec tests that use rspec-parameterized table syntax were breaking whenever they consisted of Integer operands, e.g.:

    where(:index, :percentage, :is_enabled) do
      50  | 40 | false
      40  | 50 | true
      nil | 50 | false
    end

This is currently failing because the |-operator refinement added by this gem is not going into effect, thus leading to Ruby interpreting the pipe verbatim i.e. as the bit-wise OR. This then leads to the block returning integer values instead of Table instances.

Example test failure: https://gitlab.com/gitlab-org/gitlab/-/jobs/1470020798

Analysis

I have already spent 1day+ on trying to get to the bottom of this. I have a strong suspicion that the Ruby method cache is polluted somehow, since adding this snippet to the top of the spec file will turn the test green:

module M
  refine Integer do
    def |
      # nop
    end
  end
end

I suspect what's happening is that this (unused) refinement triggers a rebuild of the method cache, thus leading to the RSpec refinement being correctly picked up. But I am not sure.

I am fairly certain it is not other Integer refinements shadowing this one since none that I found also redefine |:

git@24d3a948733e:~/gitlab$ grep -r 'refine Integer' /data/cache/bundle-2.7.2/ruby/3.0.0/gems/**/*
/data/cache/bundle-2.7.2/ruby/3.0.0/gems/rexml-3.2.5/lib/rexml/xpath_parser.rb:    refine Integer do
/data/cache/bundle-2.7.2/ruby/3.0.0/gems/rspec-parameterized-0.4.2/lib/rspec/parameterized/table_syntax.rb:        refine Integer do
/data/cache/bundle-2.7.2/ruby/3.0.0/gems/rspec-parameterized-0.5.0/lib/rspec/parameterized/table_syntax.rb:        refine Integer do

I was also able to fix this issue by running the test with a reduced dependency footprint e.g. by not using spec_helper and/or spring, since those will pull in the entire application dependency stack. When comparing Integer.ancestors between the minimal dependency stack where the test passes and the full stack, I get this diff in code added to the Integer class:

[Net::BER::Extensions::Integer,
 RQRCode::CoreExtensions::Integer::Bitwise,
 MessagePack::CoreExt,
 ActiveSupport::ForkTracker::CoreExtPrivate,
 ActiveSupport::ForkTracker::CoreExt,
 Pry::Shell::Patches::Object,
 GettextI18nRails::HtmlSafeTranslations,
 FastGettext::Translation,
 ERB::Util,
 ActiveSupport::ForkTracker::CoreExtPrivate,
 ActiveSupport::ForkTracker::CoreExt]

So another possibility is that one of these extensions collides with the table-syntax refinement. Or maybe it is merely the structure of the ancestor chain that triggers it, which I think is more likely than it being any of these particular extensions. (I already looked at most of them, and I have found no evidence as to why they would shadow |.)

Related links

Edited by Matthias Käppler