Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2026-06-02 19:13:42 UTC using RuboCop version 1.87.0.
# on 2026-06-14 16:06:06 UTC using RuboCop version 1.87.0.
# 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
Expand All @@ -24,12 +24,17 @@ Lint/RedundantRequireStatement:
Exclude:
- 'lib/cucumber/core/test/location.rb'

# Offense count: 1
# Offense count: 3
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 21

# Offense count: 1
# Configuration parameters: CountComments, CountAsOne.
Metrics/ClassLength:
Max: 111

# Offense count: 3
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength:
Max: 28
Expand Down
106 changes: 101 additions & 5 deletions lib/cucumber/core/test/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,51 @@
require 'cucumber/messages/helpers/test_step_result_comparator'

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, backtrace_filter = nil, max_attempts = 1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn't this come from the config for the retries on the current flag

@event_bus = event_bus
@max_attempts = max_attempts
@backtrace_filter = backtrace_filter
@id_generator = Cucumber::Messages::Helpers::IdGenerator::UUID.new

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs to lean on the cucumber config - as this can be variable

@current_test_case = nil
end

def test_case(test_case, &descend)
@attempt = @current_test_case == test_case ? @attempt + 1 : 1
@current_test_case = test_case

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is overwriting a check above? And the current check above would always be nil to start with? So are we saying we should always start at 1 then do something else. It's not that clear

@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)
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)
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
Expand All @@ -46,6 +63,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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we use a #to_xxx structure. So this could be test_case_started_to_envelope - Unless we're able to directly put these on the event - which is something I'm trying to do (Make the #to_envelope definitions as close to their requirement as poss).

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
include Cucumber::Messages::Helpers::TestStepResultComparator

Expand Down
6 changes: 5 additions & 1 deletion spec/cucumber/core/test/runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,11 @@
it 'calls skip, rather than execute on test step of the hook' do
expect(failing_hook).not_to receive(:execute)

allow(failing_hook).to receive(:skip).and_return(Cucumber::Core::Test::Result::Failed.new(Cucumber::Core::Test::Result::UnknownDuration.new, instance_double(StandardError, backtrace: [])))
allow(failing_hook).to receive(:skip).and_return(
Cucumber::Core::Test::Result::Failed.new(
Cucumber::Core::Test::Result::UnknownDuration.new, instance_double(StandardError, backtrace: [], message: nil)
)
)
test_case.describe_to(runner)
end
end
Expand Down
Loading