diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb index e226847d2e4125899436b5f342fa64d8ca989d85..9d5239e15937068556291d8d11706443f957cc91 100644 --- a/lib/gitlab/ci/pipeline/chain/command.rb +++ b/lib/gitlab/ci/pipeline/chain/command.rb @@ -14,16 +14,11 @@ module Chain :chat_data, :allow_mirror_update, :bridge, :content, :dry_run, :logger, :execution_policy_dry_run, # These attributes are set by Chains during processing: :config_content, :yaml_processor_result, :workflow_rules_result, :pipeline_seed, - :pipeline_config, :execution_policy_pipelines, :partition_id + :pipeline_config, :execution_policy_pipelines, :partition_id, + keyword_init: true ) do include Gitlab::Utils::StrongMemoize - def initialize(params = {}) - params.each do |key, value| - self[key] = value - end - end - def dry_run? dry_run || execution_policy_dry_run end diff --git a/spec/lib/gitlab/relative_positioning/range_spec.rb b/spec/lib/gitlab/relative_positioning/range_spec.rb index cb3e1504a7aa2a987de65bdd746c974e2a2d57ba..f1951f53934a45eddd11cc8d85bd93b75587baba 100644 --- a/spec/lib/gitlab/relative_positioning/range_spec.rb +++ b/spec/lib/gitlab/relative_positioning/range_spec.rb @@ -95,7 +95,7 @@ item_c = position_struct.new(150, :z, true) item_d = position_struct.new(050, :w, true) item_e = position_struct.new(250, :r, true) - item_f = position_struct.new(positioned?: false) + item_f = position_struct.new(nil, nil, false) item_ax = position_struct.new(100, :not_x, true) item_bx = position_struct.new(200, :not_y, true) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d426e20af6979aec886663711bd154efe4f69fa0..65137f1bc2ee34c0bf475638f89926d6b2aa369c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -21,6 +21,8 @@ require './spec/crystalball_env' CrystalballEnv.start! +require_relative 'support/struct_with_kwargs' + ENV["RAILS_ENV"] = 'test' ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true' ENV["RSPEC_ALLOW_INVALID_URLS"] = 'true' diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb index dfc881caddce425d3609092762b7bc1231bf64b3..4036ef51b2e9ee2097babd3bb5766a2c07ecaf4f 100644 --- a/spec/support/rspec.rb +++ b/spec/support/rspec.rb @@ -3,6 +3,7 @@ require_relative 'rake' require_relative 'rspec_order' require_relative 'rspec_run_time' +require_relative 'struct_with_kwargs' require_relative 'system_exit_detected' require_relative 'helpers/stub_configuration' require_relative 'helpers/stub_metrics' diff --git a/spec/support/struct_with_kwargs.rb b/spec/support/struct_with_kwargs.rb new file mode 100644 index 0000000000000000000000000000000000000000..ef86c8490eaa039890b8c295e13c92cf1d8dab45 --- /dev/null +++ b/spec/support/struct_with_kwargs.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# Detect incompatbilities between Ruby 3.1 and Ruby 3.2+ in the cases where +# Struct.new is missing `keyword_init: true` and a Hash is passed. +# +# See https://gitlab.com/gitlab-org/gitlab/-/issues/474743 +module StructWithKwargs + # Excluding structs we don't own and cannot patch. + EXCLUDE = %w[ + Aws::S3::EndpointParameters + ].freeze + + def self.check?(klass) + !EXCLUDE.include?(klass.name) # rubocop:disable Rails/NegateInclude -- Rails is not always available. + end + + module Patch + def new(*args, **kwargs) + return super if kwargs[:keyword_init] + + super.prepend KwargsCheck + end + end + + module KwargsCheck + def initialize(*args, **kwargs, &block) + if args.empty? && kwargs.any? && StructWithKwargs.check?(self.class) + raise <<~MESSAGE + Passing only keyword arguments to #{self.class}#initialize will behave differently from Ruby 3.2. Please pass `keyword_init: true` to `Struct` constructor or use a Hash literal like .new({k: v}) instead of .new(k: v). + MESSAGE + end + + super + end + end +end + +Struct.singleton_class.prepend StructWithKwargs::Patch diff --git a/spec/support_specs/struct_with_kwargs_spec.rb b/spec/support_specs/struct_with_kwargs_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..02ea87f134d461f8f3ae46d7ba091ad955b626db --- /dev/null +++ b/spec/support_specs/struct_with_kwargs_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'rspec-parameterized' + +RSpec.describe StructWithKwargs, feature_category: :tooling do + let(:const_name) { 'TEST_STRUCT' } + + before do + stub_const(const_name, struct) + end + + context 'with lack of keyword_init: true' do + subject(:struct) { Struct.new(:foo) } + + it { is_expected.to include(described_class::KwargsCheck) } + + it 'accepts plain values' do + expect(struct.new(23)).to have_attributes(foo: 23) + end + + it 'accepts hash' do + expect(struct.new({ foo: 23 })).to have_attributes(foo: { foo: 23 }) + end + + it 'raises with kwargs' do + expect { struct.new(foo: 23) } + .to raise_error(RuntimeError, /Passing only keyword arguments to TEST_STRUCT#initialize/) + end + + context 'and also positional arguments' do + subject(:struct) { Struct.new(:foo, :bar) } + + it 'accepts mix of plain and hash' do + expect(struct.new(1, x: 23)).to have_attributes(foo: 1, bar: { x: 23 }) + end + + it 'raises with kwargs only' do + expect { struct.new(foo: 23, bar: 42) } + .to raise_error(RuntimeError, /Passing only keyword arguments to TEST_STRUCT#initialize/) + end + end + end + + context 'with Struct.new(..., keyword_init: true)' do + subject(:struct) { Struct.new(:foo, keyword_init: true) } + + it { is_expected.not_to include(described_class::KwargsCheck) } + + it 'accepts kwargs or hash', :aggregate_failures do + expect(struct.new(foo: 23)).to have_attributes(foo: 23) + expect(struct.new({ foo: 23 })).to have_attributes(foo: 23) + end + end + + describe 'excludes' do + where(:exclude) { described_class::EXCLUDE } + + with_them do + let(:const_name) { exclude } + + subject(:struct) do + Struct.new(:a) do + # Simulate libraries' implementation. + def initialize(**kwargs) + super() + + kwargs.each do |key, value| + self[key] = value + end + end + end + end + + it { is_expected.to include(described_class::KwargsCheck) } + + it 'accepts hash' do + expect(struct.new(a: 23)).to have_attributes(a: 23) + end + end + end +end