Skip to content
Merged
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
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Please visit [cucumber/CONTRIBUTING.md](https://github.com/cucumber/cucumber/blo
> which will begin being migrated in the start of 2026

### Changed
- Use the test result type 'ambiguous' added to cucumber-ruby-core
- Use the test result type 'ambiguous' added to cucumber-ruby-core when steps are ambiguous
([#1815](https://github.com/cucumber/cucumber-ruby/pull/1815)) [brasmusson](https://github.com/brasmusson))
- Use the new internal `cucumber-query` structure for the `rerun` formatter
> This is a very large refactor, but should not change any behaviour. The `cucumber-query` structure is a new internal structure that is designed to be used by formatters to query
Expand All @@ -27,11 +27,16 @@ Please visit [cucumber/CONTRIBUTING.md](https://github.com/cucumber/cucumber/blo
> formatters and will allow us to test the new structure in a real-world scenario.
- Updated `cucumber-compatibility-kit` to v22
- Security: Switched out `IO.read` for more secure `File.read` in a few areas of the codebase
- Implemented the new cucumber-query structure in all message based formatters (Currently HTML / Rerun and Message)
([#1844](https://github.com/cucumber/cucumber-ruby/pull/1844) [luke-hill](https://github.com/luke-hill))

### Fixed
- Fix crash when `Cucumber::Messages::Group#children` is `nil`
- Fixed a longstanding issue that could affect formatters reporting of retried scenarios (Now each scenario should only be reported once, with the final result of the scenario)
([#1844](https://github.com/cucumber/cucumber-ruby/pull/1844) [luke-hill](https://github.com/luke-hill))
- Fixed an issue where the default flags derived in the `Options` and `Configuration` classes were not congruent
([#1846](https://github.com/cucumber/cucumber-ruby/pull/1846)) [luke-hill](https://github.com/luke-hill))

## [10.2.0] - 2025-12-10
### Changed
- Permit the latest version of the `cucumber-html-formatter` (v22.0.0+)
Expand Down
1 change: 1 addition & 0 deletions lib/cucumber/formatter/html.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def initialize(config)
end

def output_envelope(envelope)
@repository.update(envelope)
@html_formatter.write_message(envelope)
@html_formatter.write_post_message if envelope.test_run_finished
end
Expand Down
1 change: 1 addition & 0 deletions lib/cucumber/formatter/message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def initialize(config)
end

def output_envelope(envelope)
@repository.update(envelope)
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 looks redundant. The message formatter only ever has to write to file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is one of the "benefits" of the way the MessageBuilder is architected.

Every method calls output_envelope at the end of the notification which will use the newest defined one.

This allows me to avoid writing update in every item in the builder.

Later on I can probably make this better, but for now this means that everywhere inside cucumber-ruby we now have a fully updated query object - so now I can replace them (slowly later on).

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.

Then maybe a call is missing here.

@io.write(envelope.to_json)
@io.write("\n")
end
Expand Down
36 changes: 26 additions & 10 deletions lib/cucumber/formatter/message_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def on_test_case_ready(event)
)
)

# TODO: This may be a redundant update. But for now we're leaving this in whilst we're in the transitory phase
@repository.update(message)

# TODO: Switch this over to using the Repo Query object -> `test_step_by_id`
Expand All @@ -126,9 +127,6 @@ def on_test_case_ready(event)
@test_case_by_step_id[step.id] = event.test_case
end

# TODO: Once we're comfortable switching this over. Call @repository.update(message) alongside output_envelope
# however this may not be necessary as output_envelope may/should already be doing this?

output_envelope(message)
end

Expand Down Expand Up @@ -186,7 +184,6 @@ def on_test_run_started(*)
message = Cucumber::Messages::Envelope.new(
test_run_started: Cucumber::Messages::TestRunStarted.new(
timestamp: time_to_timestamp(Time.now),
# TODO: Switch this over to using the Query object -> `find_test_run_started`
id: @test_run_started_id
)
)
Expand All @@ -211,10 +208,21 @@ def on_test_case_started(event)

def on_test_step_created(event)
@pickle_id_step_by_test_step_id[event.test_step.id] = event.pickle_step.id
Cucumber::Messages::Envelope.new(
test_case: test_step_to_message(event.test_step)
)
# TODO: This seems to output extra test cases that aren't expected
test_step_to_message(event.test_step)
# TODO: We need to determine what message to output here (Unsure - Placeholder added)
# message = Cucumber::Messages::Envelope.new(
# pickle: {
# id: '',
# uri: '',
# location: nil,
# name: '',
# language: '',
# steps: test_step_to_message(event.test_step),
# tags: [],
# ast_node_ids: []
# }
# )
#
# output_envelope(message)
end

Expand Down Expand Up @@ -279,10 +287,18 @@ def create_exception_object(result, message_element)
end

def on_test_case_finished(event)
test_case_started_id = test_case_started_id(event.test_case)
test_case_started_message = @repository.test_case_started_by_id[test_case_started_id]
max_attempts = @config.retry_attempts
# See "fake query" for reason this is index shifted
retries_attempted = test_case_started_message.attempt - 1
will_be_retried = event.result.failed? && (retries_attempted < max_attempts)

message = Cucumber::Messages::Envelope.new(
test_case_finished: Cucumber::Messages::TestCaseFinished.new(
test_case_started_id: test_case_started_id(event.test_case),
timestamp: time_to_timestamp(Time.now)
test_case_started_id: test_case_started_id,
timestamp: time_to_timestamp(Time.now),
will_be_retried: will_be_retried
)
)

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.

You don't have to create the TestCaseFinished twice. It should be possible to work out will_be_retried before creating the message based on the event and the previous messages.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Might need to pair with you on this. For porting this in. Because I know it "should" be possible. But one of the issues I was finding here was trying to access the test_case_started.

The way in which "fake query" was architected by vincent / aurelien was to use events. But the cucumber-query uses messages. I could not find a reliable way to find a cucumber message using an id. i.e. if there was a cucumber-query method called
find_test_case_started_by_id or potentially I could call @repository.test_case_started[id]

WDYT. Is calling the repository directly viable? Let's maybe get some time together to pair on this when we're free if possible

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I only create the test case finished twice, because the first time gives me a TestCaseFinished message which I can then use to find the TestCaseStarted message (Using query)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed this up. The other items are needed to ensure that each message goes into the repository

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def on_test_case_created(event)
end

def on_test_case_started(event)
# TODO: LH - Apr '26 -> Clarify if this should start with attempt 1 or attempt 0
@attempts_by_test_case_id[event.test_case.id] += 1
@test_case_started_id_by_test_case_id[event.test_case.id] = @config.id_generator.new_id
end
Expand Down
4 changes: 4 additions & 0 deletions lib/cucumber/repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class Repository
# final Map<String, List<Suggestion>> suggestionsByPickleStepId = new LinkedHashMap<>();
# final List<UndefinedParameterType> undefinedParameterTypes = new ArrayList<>();

# TODO: Missing handlers
# Source
#

def initialize
@attachments_by_test_case_started_id = Hash.new { |hash, key| hash[key] = [] }
@attachments_by_test_run_hook_started_id = Hash.new { |hash, key| hash[key] = [] }
Expand Down
14 changes: 6 additions & 8 deletions spec/cucumber/rake/forked_cucumber_runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
require 'rake'

RSpec.describe Cucumber::Rake::ForkedCucumberRunner do
subject(:rake_task) { described_class.new(libs, binary, cucumber_opts, bundler, feature_files) }

let(:libs) { ['lib'] }
let(:binary) { Cucumber::BINARY }
let(:cucumber_opts) { ['--cuke-option'] }
Expand All @@ -13,14 +15,12 @@
context 'when running with bundler' do
let(:bundler) { true }

subject { described_class.new(libs, binary, cucumber_opts, bundler, feature_files) }

it 'does use bundler if bundler is set to true' do
expect(subject.use_bundler).to be true
expect(rake_task.use_bundler).to be true
end

it 'uses bundle exec to find cucumber and libraries' do
expect(subject.cmd).to eq [
expect(rake_task.cmd).to eq [
Cucumber::RUBY_BINARY,
'-S',
'bundle',
Expand All @@ -34,14 +34,12 @@
context 'when running without bundler' do
let(:bundler) { false }

subject { described_class.new(libs, binary, cucumber_opts, bundler, feature_files) }

it 'does not use bundler if bundler is set to false' do
expect(subject.use_bundler).to be false
expect(rake_task.use_bundler).to be false
end

it 'uses well known cucumber location and specified libraries' do
expect(subject.cmd).to eq [
expect(rake_task.cmd).to eq [
Cucumber::RUBY_BINARY,
'-I',
'"lib"',
Expand Down