From 252c44e2e37bec433d508e25f354a3b1a927ea44 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Mon, 27 Jul 2026 18:43:38 +0100 Subject: [PATCH 1/4] Change events to be singular in inheritance trees --- lib/cucumber/core/{events => event}/base.rb | 2 +- lib/cucumber/core/{events => event}/envelope.rb | 2 +- lib/cucumber/core/{events => event}/gherkin_source_parsed.rb | 2 +- lib/cucumber/core/{events => event}/test_case_created.rb | 2 +- lib/cucumber/core/{events => event}/test_case_finished.rb | 2 +- lib/cucumber/core/{events => event}/test_case_started.rb | 2 +- lib/cucumber/core/{events => event}/test_step_created.rb | 2 +- lib/cucumber/core/{events => event}/test_step_finished.rb | 2 +- lib/cucumber/core/{events => event}/test_step_started.rb | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) rename lib/cucumber/core/{events => event}/base.rb (97%) rename lib/cucumber/core/{events => event}/envelope.rb (96%) rename lib/cucumber/core/{events => event}/gherkin_source_parsed.rb (96%) rename lib/cucumber/core/{events => event}/test_case_created.rb (96%) rename lib/cucumber/core/{events => event}/test_case_finished.rb (97%) rename lib/cucumber/core/{events => event}/test_case_started.rb (96%) rename lib/cucumber/core/{events => event}/test_step_created.rb (96%) rename lib/cucumber/core/{events => event}/test_step_finished.rb (97%) rename lib/cucumber/core/{events => event}/test_step_started.rb (96%) diff --git a/lib/cucumber/core/events/base.rb b/lib/cucumber/core/event/base.rb similarity index 97% rename from lib/cucumber/core/events/base.rb rename to lib/cucumber/core/event/base.rb index cf2d9f16..9f4cbc4d 100644 --- a/lib/cucumber/core/events/base.rb +++ b/lib/cucumber/core/event/base.rb @@ -2,7 +2,7 @@ module Cucumber module Core - module Events + module Event # An archetype of what each Cucumber Event defined in cucumber-ruby must adhere to class Base # The "key" name of the class to be used as the key in the event registry (Underscored name symbolized) diff --git a/lib/cucumber/core/events/envelope.rb b/lib/cucumber/core/event/envelope.rb similarity index 96% rename from lib/cucumber/core/events/envelope.rb rename to lib/cucumber/core/event/envelope.rb index f944eda8..18e9e3ff 100644 --- a/lib/cucumber/core/events/envelope.rb +++ b/lib/cucumber/core/event/envelope.rb @@ -4,7 +4,7 @@ module Cucumber module Core - module Events + module Event class Envelope < Base attr_reader :envelope diff --git a/lib/cucumber/core/events/gherkin_source_parsed.rb b/lib/cucumber/core/event/gherkin_source_parsed.rb similarity index 96% rename from lib/cucumber/core/events/gherkin_source_parsed.rb rename to lib/cucumber/core/event/gherkin_source_parsed.rb index 87860074..e2145bb2 100644 --- a/lib/cucumber/core/events/gherkin_source_parsed.rb +++ b/lib/cucumber/core/event/gherkin_source_parsed.rb @@ -4,7 +4,7 @@ module Cucumber module Core - module Events + module Event # Signals that a gherkin source has been parsed class GherkinSourceParsed < Base # @return [GherkinDocument] the GherkinDocument Ast Node that was parsed diff --git a/lib/cucumber/core/events/test_case_created.rb b/lib/cucumber/core/event/test_case_created.rb similarity index 96% rename from lib/cucumber/core/events/test_case_created.rb rename to lib/cucumber/core/event/test_case_created.rb index 1374acf7..d4d9a25c 100644 --- a/lib/cucumber/core/events/test_case_created.rb +++ b/lib/cucumber/core/event/test_case_created.rb @@ -4,7 +4,7 @@ module Cucumber module Core - module Events + module Event # Signals that a Test::Case was created from a Pickle class TestCaseCreated < Base attr_reader :test_case, :pickle diff --git a/lib/cucumber/core/events/test_case_finished.rb b/lib/cucumber/core/event/test_case_finished.rb similarity index 97% rename from lib/cucumber/core/events/test_case_finished.rb rename to lib/cucumber/core/event/test_case_finished.rb index f86880d7..35995735 100644 --- a/lib/cucumber/core/events/test_case_finished.rb +++ b/lib/cucumber/core/event/test_case_finished.rb @@ -4,7 +4,7 @@ module Cucumber module Core - module Events + module Event # Signals that a {Test::Case} has finished executing class TestCaseFinished < Base # @return [Test::Case] that was executed diff --git a/lib/cucumber/core/events/test_case_started.rb b/lib/cucumber/core/event/test_case_started.rb similarity index 96% rename from lib/cucumber/core/events/test_case_started.rb rename to lib/cucumber/core/event/test_case_started.rb index e8c12d7b..346993d6 100644 --- a/lib/cucumber/core/events/test_case_started.rb +++ b/lib/cucumber/core/event/test_case_started.rb @@ -4,7 +4,7 @@ module Cucumber module Core - module Events + module Event # Signals that a {Test::Case} is about to be executed class TestCaseStarted < Base # @return [Cucumber::Core::Test::Case] the test case to be executed diff --git a/lib/cucumber/core/events/test_step_created.rb b/lib/cucumber/core/event/test_step_created.rb similarity index 96% rename from lib/cucumber/core/events/test_step_created.rb rename to lib/cucumber/core/event/test_step_created.rb index 044033dc..c0e2b194 100644 --- a/lib/cucumber/core/events/test_step_created.rb +++ b/lib/cucumber/core/event/test_step_created.rb @@ -4,7 +4,7 @@ module Cucumber module Core - module Events + module Event # Signals that a Test::Step was created from a PickleStep class TestStepCreated < Base attr_reader :test_step, :pickle_step diff --git a/lib/cucumber/core/events/test_step_finished.rb b/lib/cucumber/core/event/test_step_finished.rb similarity index 97% rename from lib/cucumber/core/events/test_step_finished.rb rename to lib/cucumber/core/event/test_step_finished.rb index 21849e96..1fbffaaa 100644 --- a/lib/cucumber/core/events/test_step_finished.rb +++ b/lib/cucumber/core/event/test_step_finished.rb @@ -4,7 +4,7 @@ module Cucumber module Core - module Events + module Event # Signals that a {Test::Step} has finished executing class TestStepFinished < Base # @return [Cucumber::Core::Test::Step] the test step that was executed diff --git a/lib/cucumber/core/events/test_step_started.rb b/lib/cucumber/core/event/test_step_started.rb similarity index 96% rename from lib/cucumber/core/events/test_step_started.rb rename to lib/cucumber/core/event/test_step_started.rb index 87c000a9..6b95c94c 100644 --- a/lib/cucumber/core/events/test_step_started.rb +++ b/lib/cucumber/core/event/test_step_started.rb @@ -4,7 +4,7 @@ module Cucumber module Core - module Events + module Event # Signals that a {Test::Step} is about to be executed class TestStepStarted < Base # @return [Cucumber::Core::Test::Step] the test step to be executed From c5b4d958cf018fd1b8503c4d8411826406189810 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Mon, 27 Jul 2026 18:53:36 +0100 Subject: [PATCH 2/4] Move all event bus based logic into event bus class and simplify up event base Remove legacy event metaprogrammed class --- lib/cucumber/core/event.rb | 67 +---- lib/cucumber/core/event/test_case_finished.rb | 2 +- lib/cucumber/core/event_bus.rb | 26 +- lib/cucumber/core/events.rb | 38 --- spec/cucumber/core/event_bus_spec.rb | 242 ++++++++++-------- 5 files changed, 162 insertions(+), 213 deletions(-) delete mode 100644 lib/cucumber/core/events.rb diff --git a/lib/cucumber/core/event.rb b/lib/cucumber/core/event.rb index a0b5c660..9637a8f9 100644 --- a/lib/cucumber/core/event.rb +++ b/lib/cucumber/core/event.rb @@ -1,61 +1,10 @@ # frozen_string_literal: true -module Cucumber - module Core - class Event - # Macro to generate new subclasses of {Event} with attribute readers. - def self.new(*events) - # Use normal constructor for subclasses of Event - return super if ancestors.index(Event).positive? - - Class.new(Event) do - # NB: We need to use metaprogramming here instead of direct variable obtainment - # because JRuby does not guarantee the order in which variables are defined is equivalent - # to the order in which they are obtainable - # - # See https://github.com/jruby/jruby/issues/7988 for more info - attr_reader(*events) - - define_method(:initialize) do |*attributes| - events.zip(attributes) do |name, value| - instance_variable_set(:"@#{name}", value) - end - end - end - end - - def to_h - instance_variables.to_h do |variable_name| - [variable_name[1..].to_sym, instance_variable_get(variable_name)] - end - end - - def attributes - instance_variables.map { |var| instance_variable_get(var) } - end - - def event_id - self.class.event_id - end - - class << self - # @return [Symbol] the underscored name of the class to be used as the key in an event registry - def event_id - underscore(name.split('::').last).to_sym - end - - private - - def underscore(string) - string - .to_s - .gsub('::', '/') - .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') - .gsub(/([a-z\d])([A-Z])/, '\1_\2') - .tr('-', '_') - .downcase - end - end - end - end -end +require_relative 'event/envelope' +require_relative 'event/gherkin_source_parsed' +require_relative 'event/test_case_created' +require_relative 'event/test_case_started' +require_relative 'event/test_case_finished' +require_relative 'event/test_step_created' +require_relative 'event/test_step_started' +require_relative 'event/test_step_finished' diff --git a/lib/cucumber/core/event/test_case_finished.rb b/lib/cucumber/core/event/test_case_finished.rb index 35995735..3a6586b7 100644 --- a/lib/cucumber/core/event/test_case_finished.rb +++ b/lib/cucumber/core/event/test_case_finished.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative '../event' +require_relative 'base' module Cucumber module Core diff --git a/lib/cucumber/core/event_bus.rb b/lib/cucumber/core/event_bus.rb index f317944e..6410a791 100644 --- a/lib/cucumber/core/event_bus.rb +++ b/lib/cucumber/core/event_bus.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'events' +require_relative 'event' module Cucumber module Core @@ -11,8 +11,30 @@ module Core class EventBus attr_reader :event_types + # The registry contains all the event registered in the core, that will be used by the {EventBus} by default. + def self.registry + build_registry( + Envelope, + GherkinSourceParsed, + TestCaseCreated, + TestCaseStarted, + TestCaseFinished, + TestStepCreated, + TestStepStarted, + TestStepFinished + ) + end + + # Build an event registry to be passed to the {EventBus} constructor from a list of types. + # Each type must respond to `event_id` so that it can be added to the registry hash + # + # @return [Hash{Symbol => Class}] + def self.build_registry(*types) + types.to_h { |type| [type.event_id, type] } + end + # @param registry [Hash{Symbol => Class}] a hash of event types to use on the bus - def initialize(registry = Events.registry) + def initialize(registry = Event.registry) @event_types = registry.freeze @handlers = {} @event_queue = [] diff --git a/lib/cucumber/core/events.rb b/lib/cucumber/core/events.rb deleted file mode 100644 index e0d5ae08..00000000 --- a/lib/cucumber/core/events.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -require_relative 'events/envelope' -require_relative 'events/gherkin_source_parsed' -require_relative 'events/test_case_created' -require_relative 'events/test_case_started' -require_relative 'events/test_case_finished' -require_relative 'events/test_step_created' -require_relative 'events/test_step_started' -require_relative 'events/test_step_finished' - -module Cucumber - module Core - module Events - # The registry contains all the events registered in the core, that will be used by the {EventBus} by default. - def self.registry - build_registry( - Envelope, - GherkinSourceParsed, - TestCaseCreated, - TestCaseStarted, - TestCaseFinished, - TestStepCreated, - TestStepStarted, - TestStepFinished - ) - end - - # Build an event registry to be passed to the {EventBus} constructor from a list of types. - # Each type must respond to `event_id` so that it can be added to the registry hash - # - # @return [Hash{Symbol => Class}] - def self.build_registry(*types) - types.to_h { |type| [type.event_id, type] } - end - end - end -end diff --git a/spec/cucumber/core/event_bus_spec.rb b/spec/cucumber/core/event_bus_spec.rb index 6b5995b9..1778aad8 100644 --- a/spec/cucumber/core/event_bus_spec.rb +++ b/spec/cucumber/core/event_bus_spec.rb @@ -1,157 +1,173 @@ # frozen_string_literal: true -module Cucumber - module Core - module Events - class TestEvent < Core::Event.new(:some_attribute) +describe Cucumber::Core::EventBus do + subject(:event_bus) { described_class.new(registry) } + + let(:registry) { { test_event: test_event, another_test_event: another_test_event } } + let(:test_event) do + Class.new(Cucumber::Core::Event::Base) do + def self.event_id + :test_event end - AnotherTestEvent = Core::Event.new + attr_reader :some_attribute - UnregisteredEvent = Core::Event.new + def initialize(some_attribute) + @some_attribute = some_attribute + super() + end end + end + let(:another_test_event) do + Class.new(Cucumber::Core::Event::Base) do + def self.event_id + :another_test_event + end + end + end + let(:unregistered_event) do + Class.new(Cucumber::Core::Event::Base) do + def self.event_id + :unregistered_event + end + end + end - describe EventBus do - let(:event_bus) { described_class.new(registry) } - let(:registry) { { test_event: Events::TestEvent, another_test_event: Events::AnotherTestEvent } } - - context 'when broadcasting events' do - it 'can broadcast by calling a method named after the event ID' do - called = false - event_bus.on(:test_event) { called = true } - event_bus.test_event + context 'when broadcasting events' do + it 'can broadcast by calling a method named after the event ID' do + called = false + event_bus.on(:test_event) { called = true } + event_bus.test_event(:redundant_value) - expect(called).to be true - end + expect(called).to be true + end - it 'can broadcast by calling the `broadcast` method with an instance of the event type' do - called = false - event_bus.on(:test_event) { called = true } - event_bus.broadcast(Events::TestEvent.new(:some_attribute)) + it 'can broadcast by calling the `broadcast` method with an instance of the event type' do + called = false + event_bus.on(:test_event) { called = true } + event_bus.broadcast(test_event.new(:some_attribute)) - expect(called).to be true - end + expect(called).to be true + end - it 'calls a subscriber for an event, passing details of the event' do - received_payload = nil - event_bus.on(:test_event) { |event| received_payload = event } - event_bus.test_event :some_attribute + it 'calls a subscriber for an event, passing details of the event' do + received_payload = nil + event_bus.on(:test_event) { |event| received_payload = event } + event_bus.test_event :some_attribute - expect(received_payload.some_attribute).to eq(:some_attribute) - end + expect(received_payload.some_attribute).to eq(:some_attribute) + end - it 'does not call subscribers for other events' do - handler_called = false - event_bus.on :test_event do - handler_called = true - end + it 'does not call subscribers for other events' do + handler_called = false + event_bus.on :test_event do + handler_called = true + end - event_bus.another_test_event + event_bus.another_test_event - expect(handler_called).to be false - end + expect(handler_called).to be false + end - it 'broadcasts to multiple subscribers' do - received_events = [] - event_bus.on :test_event do - received_events << :event - end - event_bus.on :test_event do - received_events << :event - end + it 'broadcasts to multiple subscribers' do + received_events = [] + event_bus.on :test_event do + received_events << :event + end + event_bus.on :test_event do + received_events << :event + end - event_bus.test_event(:some_attribute) + event_bus.test_event(:some_attribute) - expect(received_events.length).to eq(2) - end + expect(received_events.length).to eq(2) + end - it "raises an error when given an event to broadcast that it doesn't recognise" do - expect { event_bus.some_unknown_event }.to raise_error(NameError) - end + it "raises an error when given an event to broadcast that it doesn't recognise" do + expect { event_bus.some_unknown_event }.to raise_error(NameError) + end - describe '#broadcast method' do - it 'must be passed an instance of a registered event type' do - expect { event_bus.broadcast(Events::UnregisteredEvent) }.to raise_error(ArgumentError) - end - end + describe '#broadcast method' do + it 'must be passed an instance of a registered event type' do + expect { event_bus.broadcast(unregistered_event) }.to raise_error(ArgumentError) end + end + end - context 'when subscribing to events' do - let(:regular_handler) do - Class.new do - attr_reader :received_payload + context 'when subscribing to events' do + let(:regular_handler) do + Class.new do + attr_reader :received_payload - def call(event) - @received_payload = event - end - end + def call(event) + @received_payload = event end + end + end - let(:proc_handler) do - Class.new do - attr_reader :received_payload - - def initialize(event_bus) - event_bus.on :test_event, &method(:on_test_event) - end + let(:proc_handler) do + Class.new do + attr_reader :received_payload - def on_test_event(event) - @received_payload = event - end - end + def initialize(event_bus) + event_bus.on :test_event, &method(:on_test_event) end - it 'allows subscription by symbol (Event ID)' do - received_payload = nil - event_bus.on(:test_event) do |event| - received_payload = event - end - - event_bus.test_event :some_attribute - - expect(received_payload.some_attribute).to eq(:some_attribute) + def on_test_event(event) + @received_payload = event end + end + end - it 'raises an error if you use an unknown Event ID' do - expect { event_bus.on(:some_unknown_event) { :whatever } }.to raise_error(ArgumentError) - end + it 'allows subscription by symbol (Event ID)' do + received_payload = nil + event_bus.on(:test_event) do |event| + received_payload = event + end - it 'allows handlers that are objects with a `call` method' do - handler = regular_handler.new - event_bus.on(:test_event, handler) + event_bus.test_event :some_attribute - event_bus.test_event :some_attribute + expect(received_payload.some_attribute).to eq(:some_attribute) + end - expect(handler.received_payload.some_attribute).to eq(:some_attribute) - end + it 'raises an error if you use an unknown Event ID' do + expect { event_bus.on(:some_unknown_event) { :whatever } }.to raise_error(ArgumentError) + end - it 'allows handlers that are procs' do - handler = proc_handler.new(event_bus) + it 'allows handlers that are objects with a `call` method' do + handler = regular_handler.new + event_bus.on(:test_event, handler) - event_bus.test_event :some_attribute - expect(handler.received_payload.some_attribute).to eq(:some_attribute) - end + event_bus.test_event :some_attribute - it 'sends events that were broadcast before you subscribed' do - event_bus.test_event(:some_attribute) - event_bus.another_test_event + expect(handler.received_payload.some_attribute).to eq(:some_attribute) + end - received_payload = nil - event_bus.on(:test_event) do |event| - received_payload = event - end + it 'allows handlers that are procs' do + handler = proc_handler.new(event_bus) - expect(received_payload.some_attribute).to eq(:some_attribute) - end - end + event_bus.test_event :some_attribute + expect(handler.received_payload.some_attribute).to eq(:some_attribute) + end - it 'lets you inspect the registry' do - expect(event_bus.event_types[:test_event]).to eq(Events::TestEvent) - end + it 'sends events that were broadcast before you subscribed' do + event_bus.test_event(:some_attribute) + event_bus.another_test_event - it 'does not let you modify the registry' do - expect { event_bus.event_types[:foo] = :bar }.to raise_error(RuntimeError) + received_payload = nil + event_bus.on(:test_event) do |event| + received_payload = event end + + expect(received_payload.some_attribute).to eq(:some_attribute) end end + + it 'lets you inspect the registry' do + expect(event_bus.event_types[:test_event]).to eq(test_event) + end + + it 'does not let you modify the registry' do + expect { event_bus.event_types[:foo] = :bar }.to raise_error(RuntimeError) + end end From 3c32eff16b34d17b6e6283398435df4ff009d7e1 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Mon, 27 Jul 2026 18:58:33 +0100 Subject: [PATCH 3/4] Remove a bunch of metaprogramming from the specs --- lib/cucumber/core/event_bus.rb | 18 ++++---- .../core/{events => event}/base_spec.rb | 2 +- spec/cucumber/core/event_spec.rb | 39 ----------------- spec/cucumber/core/report/summary_spec.rb | 43 +++++++++---------- 4 files changed, 31 insertions(+), 71 deletions(-) rename spec/cucumber/core/{events => event}/base_spec.rb (94%) delete mode 100644 spec/cucumber/core/event_spec.rb diff --git a/lib/cucumber/core/event_bus.rb b/lib/cucumber/core/event_bus.rb index 6410a791..f621224d 100644 --- a/lib/cucumber/core/event_bus.rb +++ b/lib/cucumber/core/event_bus.rb @@ -14,14 +14,14 @@ class EventBus # The registry contains all the event registered in the core, that will be used by the {EventBus} by default. def self.registry build_registry( - Envelope, - GherkinSourceParsed, - TestCaseCreated, - TestCaseStarted, - TestCaseFinished, - TestStepCreated, - TestStepStarted, - TestStepFinished + Event::Envelope, + Event::GherkinSourceParsed, + Event::TestCaseCreated, + Event::TestCaseStarted, + Event::TestCaseFinished, + Event::TestStepCreated, + Event::TestStepStarted, + Event::TestStepFinished ) end @@ -34,7 +34,7 @@ def self.build_registry(*types) end # @param registry [Hash{Symbol => Class}] a hash of event types to use on the bus - def initialize(registry = Event.registry) + def initialize(registry = self.class.registry) @event_types = registry.freeze @handlers = {} @event_queue = [] diff --git a/spec/cucumber/core/events/base_spec.rb b/spec/cucumber/core/event/base_spec.rb similarity index 94% rename from spec/cucumber/core/events/base_spec.rb rename to spec/cucumber/core/event/base_spec.rb index 1313df4f..b6d7801e 100644 --- a/spec/cucumber/core/events/base_spec.rb +++ b/spec/cucumber/core/event/base_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe Cucumber::Core::Events::Base do +describe Cucumber::Core::Event::Base do subject(:event) { my_event_type.new(1, 2) } let(:my_event_type) do diff --git a/spec/cucumber/core/event_spec.rb b/spec/cucumber/core/event_spec.rb deleted file mode 100644 index d3979709..00000000 --- a/spec/cucumber/core/event_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -describe Cucumber::Core::Event do - describe '.new' do - it 'generates new types of events' do - my_event_type = described_class.new - my_event = my_event_type.new - expect(my_event).to be_a(described_class) - end - - it 'generates events with attributes' do - my_event_type = described_class.new(:foo, :bar) - my_event = my_event_type.new(1, 2) - expect(my_event.attributes).to eq([1, 2]) - expect(my_event.foo).to eq(1) - expect(my_event.bar).to eq(2) - end - end - - describe 'a generated event' do - let(:my_event_type) do - Class.new(described_class.new(:foo, :bar)) do - # Anonymous classes don't respond to .name. So we override it! - def self.name - 'Cucumber::Core::MyEventType' - end - end - end - - it 'can be converted to a hash' do - expect(my_event_type.new(1, 2).to_h).to eq(foo: 1, bar: 2) - end - - it 'has an event_id' do - expect(my_event_type.event_id).to eq(:my_event_type) - expect(my_event_type.new(1, 2).event_id).to eq(:my_event_type) - end - end -end diff --git a/spec/cucumber/core/report/summary_spec.rb b/spec/cucumber/core/report/summary_spec.rb index f6882244..c2136677 100644 --- a/spec/cucumber/core/report/summary_spec.rb +++ b/spec/cucumber/core/report/summary_spec.rb @@ -8,8 +8,7 @@ describe Cucumber::Core::Report::Summary do subject(:summary) { described_class.new(event_bus) } - let(:event_bus) { Cucumber::Core::EventBus.new(registry) } - let(:registry) { Cucumber::Core::Events.registry } + let(:event_bus) { Cucumber::Core::EventBus.new } let(:passed_result) { Cucumber::Core::Test::Result::Passed.new(duration) } let(:failed_result) { Cucumber::Core::Test::Result::Failed.new(duration, exception) } let(:pending_result) { Cucumber::Core::Test::Result::Pending.new(duration) } @@ -22,35 +21,35 @@ let(:test_case) { double } it 'counts passed test cases' do - event_bus.send(:test_case_finished, test_case, passed_result) + event_bus.test_case_finished(test_case, passed_result) expect(summary.test_cases.total(:passed)).to eq(1) expect(summary.test_cases.total).to eq(1) end it 'counts failed test cases' do - event_bus.send(:test_case_finished, test_case, failed_result) + event_bus.test_case_finished(test_case, failed_result) expect(summary.test_cases.total(:failed)).to eq(1) expect(summary.test_cases.total).to eq(1) end it 'counts pending test cases' do - event_bus.send(:test_case_finished, test_case, pending_result) + event_bus.test_case_finished(test_case, pending_result) expect(summary.test_cases.total(:pending)).to eq(1) expect(summary.test_cases.total).to eq(1) end it 'counts skipped test cases' do - event_bus.send(:test_case_finished, test_case, skipped_result) + event_bus.test_case_finished(test_case, skipped_result) expect(summary.test_cases.total(:skipped)).to eq(1) expect(summary.test_cases.total).to eq(1) end it 'counts undefined test cases' do - event_bus.send(:test_case_finished, test_case, undefined_result) + event_bus.test_case_finished(test_case, undefined_result) expect(summary.test_cases.total(:undefined)).to eq(1) expect(summary.test_cases.total).to eq(1) @@ -58,8 +57,8 @@ it 'handles flaky test cases' do allow(test_case).to receive(:==).and_return(false, true) - event_bus.send(:test_case_finished, test_case, failed_result) - event_bus.send(:test_case_finished, test_case, passed_result) + event_bus.test_case_finished(test_case, failed_result) + event_bus.test_case_finished(test_case, passed_result) expect(summary.test_cases.total(:failed)).to eq(0) expect(summary.test_cases.total(:flaky)).to eq(1) @@ -68,8 +67,8 @@ it 'handles flaky with following skip test cases' do allow(test_case).to receive(:==).and_return(false, true) - event_bus.send(:test_case_finished, test_case, failed_result) - event_bus.send(:test_case_finished, test_case, skipped_result) + event_bus.test_case_finished(test_case, failed_result) + event_bus.test_case_finished(test_case, skipped_result) expect(summary.test_cases.total(:failed)).to eq(0) expect(summary.test_cases.total(:skipped)).to eq(0) @@ -83,35 +82,35 @@ let(:test_step) { instance_double(Cucumber::Core::Test::Step, hook?: false) } it 'counts passed test steps' do - event_bus.send(:test_step_finished, test_step, passed_result) + event_bus.test_step_finished(test_step, passed_result) expect(summary.test_steps.total(:passed)).to eq(1) expect(summary.test_steps.total).to eq(1) end it 'counts failed test cases' do - event_bus.send(:test_step_finished, test_step, failed_result) + event_bus.test_step_finished(test_step, failed_result) expect(summary.test_steps.total(:failed)).to eq(1) expect(summary.test_steps.total).to eq(1) end it 'counts pending test cases' do - event_bus.send(:test_step_finished, test_step, pending_result) + event_bus.test_step_finished(test_step, pending_result) expect(summary.test_steps.total(:pending)).to eq(1) expect(summary.test_steps.total).to eq(1) end it 'counts skipped test cases' do - event_bus.send(:test_step_finished, test_step, skipped_result) + event_bus.test_step_finished(test_step, skipped_result) expect(summary.test_steps.total(:skipped)).to eq(1) expect(summary.test_steps.total).to eq(1) end it 'counts undefined test cases' do - event_bus.send(:test_step_finished, test_step, undefined_result) + event_bus.test_step_finished(test_step, undefined_result) expect(summary.test_steps.total(:undefined)).to eq(1) expect(summary.test_steps.total).to eq(1) @@ -122,7 +121,7 @@ let(:test_step) { instance_double(Cucumber::Core::Test::Step, hook?: true) } it 'ignores test steps not defined by gherkin steps' do - event_bus.send(:test_step_finished, test_step, passed_result) + event_bus.test_step_finished(test_step, passed_result) expect(summary.test_steps.total).to eq(0) end @@ -133,31 +132,31 @@ let(:test_case) { double } it 'passed test cases are ok' do - event_bus.send(:test_case_finished, test_case, passed_result) + event_bus.test_case_finished(test_case, passed_result) expect(summary.ok?).to be true end it 'skipped test cases are ok' do - event_bus.send(:test_case_finished, test_case, skipped_result) + event_bus.test_case_finished(test_case, skipped_result) expect(summary.ok?).to be true end it 'failed test cases are not ok' do - event_bus.send(:test_case_finished, test_case, failed_result) + event_bus.test_case_finished(test_case, failed_result) expect(summary.ok?).to be false end it 'pending test cases are not ok' do - event_bus.send(:test_case_finished, test_case, pending_result) + event_bus.test_case_finished(test_case, pending_result) expect(summary.ok?).to be false end it 'undefined test cases are not ok' do - event_bus.send(:test_case_finished, test_case, undefined_result) + event_bus.test_case_finished(test_case, undefined_result) expect(summary.ok?).to be false end From c8e278ee63990590e92c0b0efc81617e56e7fa55 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Wed, 12 Aug 2026 12:05:02 +0100 Subject: [PATCH 4/4] Add upgrading docs --- CHANGELOG.md | 1 + upgrading_notes/19.0.0.md | 74 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 upgrading_notes/19.0.0.md diff --git a/CHANGELOG.md b/CHANGELOG.md index c3aa422d..773ae88a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Please visit [cucumber/CONTRIBUTING.md](https://github.com/cucumber/cucumber/blo ### Changed - Increased upper bounds of `cucumber-messages` to v34 and `cucumber-gherkin` to v42 +- **BREAKING CHANGE:** Event base class has been internally refactored. See upgrading notes for [19.0.0.md](upgrading_notes/19.0.0.md#upgrading-to-cucumber-core-1900), for full changes ## [18.0.0] - 2026-07-13 ### Changed diff --git a/upgrading_notes/19.0.0.md b/upgrading_notes/19.0.0.md new file mode 100644 index 00000000..a6092ab7 --- /dev/null +++ b/upgrading_notes/19.0.0.md @@ -0,0 +1,74 @@ +# Upgrading to cucumber-core 19.0.0 + +## Event Base Class Migration + +The event base class "pre" cucumber-core v19 had a series of methods and was meta-programmed in. So you inherited +from a derivation of the base class and had access to a series of methods that were not explicitly defined in the +class itself. This was a bit confusing and made it hard to understand what methods were available on the event class. + +One of these methods was an array of all of the attributes. Which in some versions of JRuby could be not in the order +you would expect. This could cause issues when you were trying to use the attributes in a specific order. + +There was also a hashified response of the attributes and values, this was meta-programmed but is remaining in the new +structure of the event base class. However, the attributes array has been removed and is no longer available. + +Another aspect was that the events id was also derived based on its class name. This is now just specified as a method +on the class proper (As it needs to be accessible for both class and instance). + +### Before cucumber-core 19.0.0 + +```ruby +# Signals that a Test::Step was created from a PickleStep +class TestStepCreated < Event.new(:test_step, :pickle_step) + # The created test step + attr_reader :test_step + + # The source pickle step + attr_reader :pickle_step +end +``` + +Which you could then call in the following way + +```ruby +test_step = Test::Step.new(...) +pickle_step = Pickle::Step.new(...) +event = TestStepCreated.new(test_step, pickle_step) +event.event_id # => :test_step_created +event.test_step # => test_step +event.pickle_step # => pickle_step +event.to_h # => { test_step: test_step, pickle_step: pickle_step } +event.attributes # => [test_step, pickle_step] +``` + +### With cucumber-core 19.0.0 + +```ruby +# Signals that a Test::Step was created from a PickleStep +class TestStepCreated < Base + attr_reader :test_step, :pickle_step + + def self.event_id + :test_step_created + end + + def initialize(test_step, pickle_step) + @test_step = test_step + @pickle_step = pickle_step + super() + end +end +``` + +Which you can now call in the following way + +```ruby +test_step = Test::Step.new(...) +pickle_step = Pickle::Step.new(...) +event = TestStepCreated.new(test_step, pickle_step) +event.event_id # => :test_step_created +event.test_step # => test_step +event.pickle_step # => pickle_step +event.to_h # => { test_step: test_step, pickle_step: pickle_step } +event.attributes # => NoMethodError: undefined method `attributes' for # +```