From f59170f5f82b7b45a64422489a0e9d9164421c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Rasmusson?= Date: Sun, 31 May 2026 19:11:55 +0200 Subject: [PATCH 1/3] Generate test_case_started/finished messages at the source --- lib/cucumber/core/test/runner.rb | 39 +++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/cucumber/core/test/runner.rb b/lib/cucumber/core/test/runner.rb index cf329c40..6240576a 100644 --- a/lib/cucumber/core/test/runner.rb +++ b/lib/cucumber/core/test/runner.rb @@ -1,26 +1,59 @@ # frozen_string_literal: true require_relative 'timer' +require 'cucumber/messages' module Cucumber module Core module Test class Runner - attr_reader :event_bus, :running_test_case, :running_test_step - private :event_bus, :running_test_case, :running_test_step + include Cucumber::Messages::Helpers::TimeConversion - def initialize(event_bus) + attr_reader :event_bus, :running_test_case, :running_test_step, :id_generator + private :event_bus, :running_test_case, :running_test_step, :id_generator + + def initialize(event_bus, max_attempts = 1) @event_bus = event_bus + @max_attempts = max_attempts + @id_generator = Cucumber::Messages::Helpers::IdGenerator::UUID.new + @current_test_case = nil end def test_case(test_case, &descend) + if @current_test_case == test_case + @attempt += 1 + else + @attempt = 1 + end + @current_test_case = test_case + @current_test_case_started_id = id_generator.new_id @running_test_case = RunningTestCase.new @running_test_step = nil event_bus.test_case_started(test_case) + message = Cucumber::Messages::Envelope.new( + test_case_started: Cucumber::Messages::TestCaseStarted.new( + id: @current_test_case_started_id, + test_case_id: test_case.id, + timestamp: time_to_timestamp(Time.now), + attempt: @attempt + ) + ) + event_bus.envelope(message) + descend.call(self) result = running_test_case.result result = Result::Undefined.new('The test case has no steps', Result::UnknownDuration.new, ["#{test_case.location}:in `#{test_case.name}'"]) if result.unknown? + event_bus.test_case_finished(test_case, result) + will_be_retried = result.failed? && (@attempt < @max_attempts) + message = Cucumber::Messages::Envelope.new( + test_case_finished: Cucumber::Messages::TestCaseFinished.new( + test_case_started_id: @current_test_case_started_id, + timestamp: time_to_timestamp(Time.now), + will_be_retried: will_be_retried + ) + ) + event_bus.envelope(message) self end From d0ce88fd0a244a523d7f15ba37de3cdd10e46a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Rasmusson?= Date: Mon, 8 Jun 2026 16:41:18 +0200 Subject: [PATCH 2/3] Generate test_step_started/finished messages at the source --- lib/cucumber/core/test/runner.rb | 115 ++++++++++++++++++++++++------- 1 file changed, 89 insertions(+), 26 deletions(-) diff --git a/lib/cucumber/core/test/runner.rb b/lib/cucumber/core/test/runner.rb index 6240576a..a8d84734 100644 --- a/lib/cucumber/core/test/runner.rb +++ b/lib/cucumber/core/test/runner.rb @@ -12,56 +12,40 @@ class Runner attr_reader :event_bus, :running_test_case, :running_test_step, :id_generator private :event_bus, :running_test_case, :running_test_step, :id_generator - def initialize(event_bus, max_attempts = 1) + def initialize(event_bus, backtrace_filter = nil, max_attempts = 1) @event_bus = event_bus @max_attempts = max_attempts + @backtrace_filter = backtrace_filter @id_generator = Cucumber::Messages::Helpers::IdGenerator::UUID.new @current_test_case = nil end def test_case(test_case, &descend) - if @current_test_case == test_case - @attempt += 1 - else - @attempt = 1 - end + @attempt = @current_test_case == test_case ? @attempt + 1 : 1 @current_test_case = test_case @current_test_case_started_id = id_generator.new_id @running_test_case = RunningTestCase.new @running_test_step = nil event_bus.test_case_started(test_case) - message = Cucumber::Messages::Envelope.new( - test_case_started: Cucumber::Messages::TestCaseStarted.new( - id: @current_test_case_started_id, - test_case_id: test_case.id, - timestamp: time_to_timestamp(Time.now), - attempt: @attempt - ) - ) - event_bus.envelope(message) + event_bus.envelope(create_test_case_started_message(test_case)) descend.call(self) - result = running_test_case.result - result = Result::Undefined.new('The test case has no steps', Result::UnknownDuration.new, ["#{test_case.location}:in `#{test_case.name}'"]) if result.unknown? + result = calculate_test_case_result(test_case) event_bus.test_case_finished(test_case, result) - will_be_retried = result.failed? && (@attempt < @max_attempts) - message = Cucumber::Messages::Envelope.new( - test_case_finished: Cucumber::Messages::TestCaseFinished.new( - test_case_started_id: @current_test_case_started_id, - timestamp: time_to_timestamp(Time.now), - will_be_retried: will_be_retried - ) - ) - event_bus.envelope(message) + event_bus.envelope(create_test_case_finished_message(result)) self end def test_step(test_step) @running_test_step = test_step event_bus.test_step_started test_step + event_bus.envelope(create_test_step_started_message(test_step)) + step_result = running_test_case.execute(test_step) + event_bus.test_step_finished test_step, step_result + event_bus.envelope(create_test_step_finished_message(test_step, step_result)) @running_test_step = nil self end @@ -77,6 +61,85 @@ def done self end + def calculate_test_case_result(test_case) + if running_test_case.result.unknown? + Result::Undefined.new('The test case has no steps', Result::UnknownDuration.new, ["#{test_case.location}:in `#{test_case.name}'"]) + else + running_test_case.result + end + end + + def create_test_case_started_message(test_case) + Cucumber::Messages::Envelope.new( + test_case_started: Cucumber::Messages::TestCaseStarted.new( + id: @current_test_case_started_id, + test_case_id: test_case.id, + timestamp: time_to_timestamp(Time.now), + attempt: @attempt + ) + ) + end + + def create_test_case_finished_message(result) + Cucumber::Messages::Envelope.new( + test_case_finished: Cucumber::Messages::TestCaseFinished.new( + test_case_started_id: @current_test_case_started_id, + timestamp: time_to_timestamp(Time.now), + will_be_retried: result.failed? && (@attempt < @max_attempts) + ) + ) + end + + def create_test_step_started_message(test_step) + Cucumber::Messages::Envelope.new( + test_step_started: Cucumber::Messages::TestStepStarted.new( + test_step_id: test_step.id, + test_case_started_id: @current_test_case_started_id, + timestamp: time_to_timestamp(Time.now) + ) + ) + end + + def create_test_step_finished_message(test_step, step_result) + result = @backtrace_filter.nil? ? step_result : step_result.with_filtered_backtrace(@backtrace_filter) + result_message = result.to_message + if result.failed? || result.pending? + message_element = result.failed? ? result.exception : result + + result_message = Cucumber::Messages::TestStepResult.new( + status: result_message.status, + duration: result_message.duration, + message: create_error_message(message_element), + exception: create_exception_object(result, message_element) + ) + end + Cucumber::Messages::Envelope.new( + test_step_finished: Cucumber::Messages::TestStepFinished.new( + test_step_id: test_step.id, + test_case_started_id: @current_test_case_started_id, + test_step_result: result_message, + timestamp: time_to_timestamp(Time.now) + ) + ) + end + + def create_error_message(message_element) + <<~ERROR_MESSAGE + #{message_element.message} (#{message_element.class}) + #{message_element.backtrace} + ERROR_MESSAGE + end + + def create_exception_object(result, message_element) + return unless result.failed? + + Cucumber::Messages::Exception.new( + type: message_element.class, + message: message_element.message, + stack_trace: message_element.backtrace.join("\n") + ) + end + class RunningTestCase def initialize @timer = Timer.new.start From 4fbc2c5de3a8fa8327b35f99d11900b044b7cfa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Rasmusson?= Date: Sun, 14 Jun 2026 15:37:26 +0200 Subject: [PATCH 3/3] Update .rubocop_todo.yml (the class Runner is to long) --- .rubocop_todo.yml | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 35af4cd1..503425ca 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,30 +1,28 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2026-01-06 12:52:49 UTC using RuboCop version 1.81.7. +# on 2026-06-14 13:36:07 UTC using RuboCop version 1.81.7. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# TODO: [LH] v14 Pre-release iteration -> 58 files inspected, 392 offenses detected, 100 offenses auto-correctable -# TODO: [LH] v14 Pre-release iteration 2 (Rubocop upgrade) -> 60 files inspected, 324 offenses detected, 86 offenses autocorrectable -# TODO: [LH] v14 Pre-release iteration 3 (Rubocop additional upgrade) -> 60 files inspected, 267 offenses detected, 26 offenses autocorrectable -# TODO: [LH] v15 Release incoming (Minimum ruby bump) -> 60 files inspected, 234 offenses detected, 12 offenses autocorrectable -# TODO: [LH] v15.1 (Minor fixes) -> 60 files inspected, 239 offenses detected, 15 offenses autocorrectable -# TODO: [LH] v16 (Gherkin/Message bump) -> 61 files inspected, 272 offenses detected, 60 offenses autocorrectable - # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). Lint/RedundantRequireStatement: Exclude: - 'lib/cucumber/core/test/location.rb' -# Offense count: 2 +# Offense count: 3 # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: Max: 21 -# Offense count: 4 +# Offense count: 1 +# Configuration parameters: CountComments, CountAsOne. +Metrics/ClassLength: + Max: 111 + +# Offense count: 5 # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: Max: 28 @@ -61,7 +59,7 @@ RSpec/MissingExampleGroupArgument: - 'spec/cucumber/core/test/locations_filter_spec.rb' - 'spec/cucumber/core_spec.rb' -# Offense count: 61 +# Offense count: 60 RSpec/MultipleExpectations: Max: 5