From 3267dbed11b7e110488cd5128279edd77b3a6aac Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 10 Jul 2026 07:13:59 +0100 Subject: [PATCH 01/10] Flaky.ok? should be true --- lib/cucumber/core/test/result/flaky.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cucumber/core/test/result/flaky.rb b/lib/cucumber/core/test/result/flaky.rb index ee350488..0ed8266e 100644 --- a/lib/cucumber/core/test/result/flaky.rb +++ b/lib/cucumber/core/test/result/flaky.rb @@ -12,7 +12,7 @@ module Result # retry, therefore only the class method self.ok? is needed. class Flaky def self.ok? - false + true end end end From ad1c268cd5556ea81647803a81d216006abea7bd Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 10 Jul 2026 07:36:15 +0100 Subject: [PATCH 02/10] Reworked entire ambiguous result spec to be fully exhaustive --- .../core/test/result/ambiguous_spec.rb | 66 +++++++++++++++++++ .../placeholder_spec.rb} | 0 2 files changed, 66 insertions(+) create mode 100644 spec/cucumber/core/test/result/ambiguous_spec.rb rename spec/cucumber/core/test/{result_spec.rb => result/placeholder_spec.rb} (100%) diff --git a/spec/cucumber/core/test/result/ambiguous_spec.rb b/spec/cucumber/core/test/result/ambiguous_spec.rb new file mode 100644 index 00000000..f9bbb541 --- /dev/null +++ b/spec/cucumber/core/test/result/ambiguous_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'cucumber/core/test/result' +require 'support/duration_matcher' + +describe Cucumber::Core::Test::Result::Ambiguous do + subject(:result) { described_class.new } + + let(:visitor) { double } + let(:args) { double } + + describe '.ok?' do + it { expect(described_class).not_to be_ok } + end + + describe '#describe_to' do + it 'describes itself to a visitor' do + expect(visitor).to receive(:ambiguous).with(args) + expect(visitor).to receive(:duration).with(an_unknown_duration, args) + + result.describe_to(visitor, args) + end + end + + describe '#to_message' do + let(:message) do + Cucumber::Messages::TestStepResult.new( + status: Cucumber::Messages::TestStepResultStatus::AMBIGUOUS + ) + end + + it 'is a `TestStepResult` message' do + expect(result.to_message).to be_a Cucumber::Messages::TestStepResult + end + + it 'has an ambiguous status' do + expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::AMBIGUOUS) + end + + it 'has an ambiguous, unran duration' do + expect(result.to_message.duration).to have_attributes(seconds: 0, nanos: 0) + end + end + + it { expect(result.to_sym).to eq(:ambiguous) } + it { expect(result.to_s).to eq('A') } + + context 'with the BooleanMethods helper' do + describe '#ok?' do + it 'calls the class method' do + expect(described_class).to receive(:ok?) + + result.ok? + end + end + + it { expect(result).not_to be_failed } + it { expect(result).to be_ambiguous } + it { expect(result).not_to be_flaky } + it { expect(result).not_to be_undefined } + it { expect(result).not_to be_pending } + it { expect(result).not_to be_skipped } + it { expect(result).not_to be_passed } + it { expect(result).not_to be_unknown } + end +end diff --git a/spec/cucumber/core/test/result_spec.rb b/spec/cucumber/core/test/result/placeholder_spec.rb similarity index 100% rename from spec/cucumber/core/test/result_spec.rb rename to spec/cucumber/core/test/result/placeholder_spec.rb From 990c1d4cbed67b115b4eaf3e18c69c21933c788b Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 10 Jul 2026 07:43:32 +0100 Subject: [PATCH 03/10] Add failed spec in same format --- .../core/test/result/ambiguous_spec.rb | 2 +- spec/cucumber/core/test/result/failed_spec.rb | 97 +++++++++++++++++++ .../core/test/result/placeholder_spec.rb | 97 ------------------- 3 files changed, 98 insertions(+), 98 deletions(-) create mode 100644 spec/cucumber/core/test/result/failed_spec.rb diff --git a/spec/cucumber/core/test/result/ambiguous_spec.rb b/spec/cucumber/core/test/result/ambiguous_spec.rb index f9bbb541..e7669134 100644 --- a/spec/cucumber/core/test/result/ambiguous_spec.rb +++ b/spec/cucumber/core/test/result/ambiguous_spec.rb @@ -37,7 +37,7 @@ expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::AMBIGUOUS) end - it 'has an ambiguous, unran duration' do + it 'has a duration' do expect(result.to_message.duration).to have_attributes(seconds: 0, nanos: 0) end end diff --git a/spec/cucumber/core/test/result/failed_spec.rb b/spec/cucumber/core/test/result/failed_spec.rb new file mode 100644 index 00000000..322121f0 --- /dev/null +++ b/spec/cucumber/core/test/result/failed_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require 'cucumber/core/test/result' +require 'support/duration_matcher' + +describe Cucumber::Core::Test::Result::Failed do + subject(:result) { described_class.new(duration, exception) } + + let(:duration) { Cucumber::Core::Test::Result::Duration.new(1 * 1000 * 1000) } + let(:exception) { StandardError.new('error message') } + let(:visitor) { double } + let(:args) { double } + + before do + allow(visitor).to receive(:failed) + allow(visitor).to receive(:duration) + allow(visitor).to receive(:exception) + end + + it 'does nothing if step has no backtrace line' do + result.exception.set_backtrace('exception backtrace') + step = 'does not respond_to?(:backtrace_line)' + + expect(result.with_appended_backtrace(step).exception.backtrace).to eq(['exception backtrace']) + end + + it 'appends the backtrace line of the step' do + result.exception.set_backtrace('exception backtrace') + step = double + allow(step).to receive(:backtrace_line).and_return('step_line') + + expect(result.with_appended_backtrace(step).exception.backtrace).to eq(['exception backtrace', 'step_line']) + end + + it 'applies filters to the exception' do + filter_class = double + filter = double + filtered_exception = double + permit_exception_passthrough(filter_class, filter, filtered_exception) + + expect(result.with_filtered_backtrace(filter_class).exception).to eq(filtered_exception) + end + + describe '.ok?' do + it { expect(described_class).not_to be_ok } + end + + describe '#describe_to' do + it 'is described as a failing test' do + expect(visitor).to receive(:failed).with(args) + + result.describe_to(visitor, args) + end + + it 'contains an exception message' do + expect(visitor).to receive(:exception).with(exception, args) + + result.describe_to(visitor, args) + end + end + + describe '#to_message' do + it 'is a `TestStepResult` message' do + expect(result.to_message).to be_a Cucumber::Messages::TestStepResult + end + + it 'has an ambiguous status' do + expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::FAILED) + end + + it 'has a duration' do + expect(result.to_message.duration).to have_attributes(seconds: 0, nanos: 1_000_000) + end + end + + it { expect(result.to_sym).to eq(:failed) } + it { expect(result.to_s).to eq('✗') } + + context 'with the BooleanMethods helper' do + describe '#ok?' do + it 'calls the class method' do + expect(described_class).to receive(:ok?) + + result.ok? + end + end + + it { expect(result).to be_failed } + it { expect(result).not_to be_ambiguous } + it { expect(result).not_to be_flaky } + it { expect(result).not_to be_undefined } + it { expect(result).not_to be_pending } + it { expect(result).not_to be_skipped } + it { expect(result).not_to be_passed } + it { expect(result).not_to be_unknown } + end +end diff --git a/spec/cucumber/core/test/result/placeholder_spec.rb b/spec/cucumber/core/test/result/placeholder_spec.rb index b39db198..79c09579 100644 --- a/spec/cucumber/core/test/result/placeholder_spec.rb +++ b/spec/cucumber/core/test/result/placeholder_spec.rb @@ -54,78 +54,6 @@ it { expect(result).to be_ok } end - describe Cucumber::Core::Test::Result::Failed do - subject(:result) { described_class.new(duration, exception) } - - let(:duration) { Cucumber::Core::Test::Result::Duration.new(1 * 1000 * 1000) } - let(:exception) { StandardError.new('error message') } - - before do - allow(visitor).to receive(:failed) - allow(visitor).to receive(:duration) - allow(visitor).to receive(:exception) - end - - it 'is described as a failing test' do - expect(visitor).to receive(:failed).with(args) - - result.describe_to(visitor, args) - end - - it 'contains an exception message' do - expect(visitor).to receive(:exception).with(exception, args) - - result.describe_to(visitor, args) - end - - it 'has a duration' do - expect(result.duration).to eq(duration) - end - - it 'converts to a Cucumber::Message::TestResult' do - expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::FAILED) - end - - it 'requires both constructor arguments' do - expect { described_class.new }.to raise_error(ArgumentError) - expect { described_class.new(duration) }.to raise_error(ArgumentError) - end - - it 'does nothing if step has no backtrace line' do - result.exception.set_backtrace('exception backtrace') - step = 'does not respond_to?(:backtrace_line)' - - expect(result.with_appended_backtrace(step).exception.backtrace).to eq(['exception backtrace']) - end - - it 'appends the backtrace line of the step' do - result.exception.set_backtrace('exception backtrace') - step = double - allow(step).to receive(:backtrace_line).and_return('step_line') - - expect(result.with_appended_backtrace(step).exception.backtrace).to eq(['exception backtrace', 'step_line']) - end - - it 'applies filters to the exception' do - filter_class = double - filter = double - filtered_exception = double - permit_exception_passthrough(filter_class, filter, filtered_exception) - - expect(result.with_filtered_backtrace(filter_class).exception).to eq(filtered_exception) - end - - it { expect(result.to_sym).to eq(:failed) } - it { expect(result).not_to be_passed } - it { expect(result).to be_failed } - it { expect(result).not_to be_ambiguous } - it { expect(result).not_to be_undefined } - it { expect(result).not_to be_unknown } - it { expect(result).not_to be_skipped } - it { expect(result).not_to be_flaky } - it { expect(result).not_to be_ok } - end - describe Cucumber::Core::Test::Result::Unknown do subject(:result) { described_class.new } @@ -194,31 +122,6 @@ end end - describe Cucumber::Core::Test::Result::Ambiguous do - subject(:result) { described_class.new } - - it 'describes itself to a visitor' do - expect(visitor).to receive(:ambiguous).with(args) - expect(visitor).to receive(:duration).with(an_unknown_duration, args) - - result.describe_to(visitor, args) - end - - it 'converts to a Cucumber::Message::TestResult' do - expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::AMBIGUOUS) - end - - it { expect(result.to_sym).to eq(:ambiguous) } - it { expect(result).not_to be_passed } - it { expect(result).not_to be_failed } - it { expect(result).to be_ambiguous } - it { expect(result).not_to be_undefined } - it { expect(result).not_to be_unknown } - it { expect(result).not_to be_skipped } - it { expect(result).not_to be_flaky } - it { expect(result).not_to be_ok } - end - describe Cucumber::Core::Test::Result::Undefined do subject(:result) { described_class.new } From 3f70ce6b89397ef6cc8f20be15293225c04d0568 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 10 Jul 2026 07:48:54 +0100 Subject: [PATCH 04/10] Tidy up and migrate passing spec --- .../core/test/result/ambiguous_spec.rb | 10 +-- spec/cucumber/core/test/result/failed_spec.rb | 12 ++- spec/cucumber/core/test/result/passed_spec.rb | 73 +++++++++++++++++++ .../core/test/result/placeholder_spec.rb | 47 ------------ 4 files changed, 84 insertions(+), 58 deletions(-) create mode 100644 spec/cucumber/core/test/result/passed_spec.rb diff --git a/spec/cucumber/core/test/result/ambiguous_spec.rb b/spec/cucumber/core/test/result/ambiguous_spec.rb index e7669134..38d93009 100644 --- a/spec/cucumber/core/test/result/ambiguous_spec.rb +++ b/spec/cucumber/core/test/result/ambiguous_spec.rb @@ -7,7 +7,7 @@ subject(:result) { described_class.new } let(:visitor) { double } - let(:args) { double } + let(:args) { double } describe '.ok?' do it { expect(described_class).not_to be_ok } @@ -23,17 +23,11 @@ end describe '#to_message' do - let(:message) do - Cucumber::Messages::TestStepResult.new( - status: Cucumber::Messages::TestStepResultStatus::AMBIGUOUS - ) - end - it 'is a `TestStepResult` message' do expect(result.to_message).to be_a Cucumber::Messages::TestStepResult end - it 'has an ambiguous status' do + it 'has a status' do expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::AMBIGUOUS) end diff --git a/spec/cucumber/core/test/result/failed_spec.rb b/spec/cucumber/core/test/result/failed_spec.rb index 322121f0..faab42c2 100644 --- a/spec/cucumber/core/test/result/failed_spec.rb +++ b/spec/cucumber/core/test/result/failed_spec.rb @@ -6,10 +6,10 @@ describe Cucumber::Core::Test::Result::Failed do subject(:result) { described_class.new(duration, exception) } - let(:duration) { Cucumber::Core::Test::Result::Duration.new(1 * 1000 * 1000) } + let(:duration) { Cucumber::Core::Test::Result::Duration.new(1 * 1_000 * 1_000) } let(:exception) { StandardError.new('error message') } let(:visitor) { double } - let(:args) { double } + let(:args) { double } before do allow(visitor).to receive(:failed) @@ -64,7 +64,7 @@ expect(result.to_message).to be_a Cucumber::Messages::TestStepResult end - it 'has an ambiguous status' do + it 'has a status' do expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::FAILED) end @@ -94,4 +94,10 @@ it { expect(result).not_to be_passed } it { expect(result).not_to be_unknown } end + + # Permit an exception to be filtered and not excluded + def permit_exception_passthrough(filter_class, filter, filtered_value) + allow(filter_class).to receive(:new).with(result.exception).and_return(filter) + allow(filter).to receive(:exception).and_return(filtered_value) + end end diff --git a/spec/cucumber/core/test/result/passed_spec.rb b/spec/cucumber/core/test/result/passed_spec.rb new file mode 100644 index 00000000..df7b2282 --- /dev/null +++ b/spec/cucumber/core/test/result/passed_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'cucumber/core/test/result' +require 'support/duration_matcher' + +describe Cucumber::Core::Test::Result::Passed do + subject(:result) { described_class.new(duration) } + + let(:duration) { Cucumber::Core::Test::Result::Duration.new(1 * 1_000 * 1_000) } + let(:visitor) { double } + let(:args) { double } + + before do + allow(visitor).to receive(:duration) + allow(visitor).to receive(:passed) + end + + it 'does nothing when appending the backtrace' do + expect(result.with_appended_backtrace(double)).to eq(result) + end + + it 'does nothing when filtering the backtrace' do + expect(result.with_filtered_backtrace(double)).to eq(result) + end + + describe '.ok?' do + it { expect(described_class).to be_ok } + end + + describe '#describe_to' do + it 'is described as a passing test' do + expect(visitor).to receive(:passed).with(args) + + result.describe_to(visitor, args) + end + end + + describe '#to_message' do + it 'is a `TestStepResult` message' do + expect(result.to_message).to be_a Cucumber::Messages::TestStepResult + end + + it 'has a status' do + expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::PASSED) + end + + it 'has a duration' do + expect(result.to_message.duration).to have_attributes(seconds: 0, nanos: 1_000_000) + end + end + + it { expect(result.to_sym).to eq(:passed) } + it { expect(result.to_s).to eq('✓') } + + context 'with the BooleanMethods helper' do + describe '#ok?' do + it 'calls the class method' do + expect(described_class).to receive(:ok?) + + result.ok? + end + end + + it { expect(result).not_to be_failed } + it { expect(result).not_to be_ambiguous } + it { expect(result).not_to be_flaky } + it { expect(result).not_to be_undefined } + it { expect(result).not_to be_pending } + it { expect(result).not_to be_skipped } + it { expect(result).to be_passed } + it { expect(result).not_to be_unknown } + end +end diff --git a/spec/cucumber/core/test/result/placeholder_spec.rb b/spec/cucumber/core/test/result/placeholder_spec.rb index 79c09579..a9a57c7c 100644 --- a/spec/cucumber/core/test/result/placeholder_spec.rb +++ b/spec/cucumber/core/test/result/placeholder_spec.rb @@ -7,53 +7,6 @@ let(:visitor) { double } let(:args) { double } - describe Cucumber::Core::Test::Result::Passed do - subject(:result) { described_class.new(duration) } - - let(:duration) { Cucumber::Core::Test::Result::Duration.new(1 * 1000 * 1000) } - - before do - allow(visitor).to receive(:duration) - allow(visitor).to receive(:passed) - end - - it 'is described as a passing test' do - expect(visitor).to receive(:passed).with(args) - - result.describe_to(visitor, args) - end - - it 'converts to a string' do - expect(result.to_s).to eq('✓') - end - - it 'converts to a `Cucumber::Message::TestResult`' do - expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::PASSED) - end - - it 'has a duration' do - expect(result.duration).to eq(duration) - end - - it 'does nothing when appending the backtrace' do - expect(result.with_appended_backtrace(double)).to eq(result) - end - - it 'does nothing when filtering the backtrace' do - expect(result.with_filtered_backtrace(double)).to eq(result) - end - - it { expect(result.to_sym).to eq(:passed) } - it { expect(result).to be_passed } - it { expect(result).not_to be_failed } - it { expect(result).not_to be_ambiguous } - it { expect(result).not_to be_undefined } - it { expect(result).not_to be_unknown } - it { expect(result).not_to be_skipped } - it { expect(result).not_to be_flaky } - it { expect(result).to be_ok } - end - describe Cucumber::Core::Test::Result::Unknown do subject(:result) { described_class.new } From 76b8a1a34e96a7a587bc753e2247919cb127872b Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 10 Jul 2026 11:07:39 +0100 Subject: [PATCH 05/10] Add in tests partitioned and split out for pending/undefined/unknown --- .../cucumber/core/test/result/pending_spec.rb | 64 ++++++++++++++++ .../core/test/result/placeholder_spec.rb | 73 ------------------- .../core/test/result/undefined_spec.rb | 60 +++++++++++++++ .../cucumber/core/test/result/unknown_spec.rb | 60 +++++++++++++++ 4 files changed, 184 insertions(+), 73 deletions(-) create mode 100644 spec/cucumber/core/test/result/pending_spec.rb create mode 100644 spec/cucumber/core/test/result/undefined_spec.rb create mode 100644 spec/cucumber/core/test/result/unknown_spec.rb diff --git a/spec/cucumber/core/test/result/pending_spec.rb b/spec/cucumber/core/test/result/pending_spec.rb new file mode 100644 index 00000000..3837f847 --- /dev/null +++ b/spec/cucumber/core/test/result/pending_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'cucumber/core/test/result' +require 'support/duration_matcher' + +describe Cucumber::Core::Test::Result::Pending do + subject(:result) { described_class.new } + + let(:visitor) { double } + let(:args) { double } + + it 'defines a with_filtered_backtrace method' do + expect(result.with_filtered_backtrace(double)).to eq(result) + end + + describe '.ok?' do + it { expect(described_class).not_to be_ok } + end + + describe '#describe_to' do + it 'describes itself to a visitor' do + expect(visitor).to receive(:pending).with(result, args) + expect(visitor).to receive(:duration).with(an_unknown_duration, args) + + result.describe_to(visitor, args) + end + end + + describe '#to_message' do + it 'is a `TestStepResult` message' do + expect(result.to_message).to be_a Cucumber::Messages::TestStepResult + end + + it 'has a status' do + expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::PENDING) + end + + it 'has a duration' do + expect(result.to_message.duration).to have_attributes(seconds: 0, nanos: 0) + end + end + + it { expect(result.to_sym).to eq(:pending) } + it { expect(result.to_s).to eq('P') } + + context 'with the BooleanMethods helper' do + describe '#ok?' do + it 'calls the class method' do + expect(described_class).to receive(:ok?) + + result.ok? + end + end + + it { expect(result).not_to be_failed } + it { expect(result).not_to be_ambiguous } + it { expect(result).not_to be_flaky } + it { expect(result).not_to be_undefined } + it { expect(result).to be_pending } + it { expect(result).not_to be_skipped } + it { expect(result).not_to be_passed } + it { expect(result).not_to be_unknown } + end +end diff --git a/spec/cucumber/core/test/result/placeholder_spec.rb b/spec/cucumber/core/test/result/placeholder_spec.rb index a9a57c7c..00320761 100644 --- a/spec/cucumber/core/test/result/placeholder_spec.rb +++ b/spec/cucumber/core/test/result/placeholder_spec.rb @@ -7,27 +7,6 @@ let(:visitor) { double } let(:args) { double } - describe Cucumber::Core::Test::Result::Unknown do - subject(:result) { described_class.new } - - it 'defines a with_filtered_backtrace method' do - expect(result.with_filtered_backtrace(double)).to eq(result) - end - - it { expect(result.to_sym).to eq(:unknown) } - it { expect(result).not_to be_passed } - it { expect(result).not_to be_failed } - it { expect(result).not_to be_ambiguous } - it { expect(result).not_to be_undefined } - it { expect(result).to be_unknown } - it { expect(result).not_to be_skipped } - it { expect(result).not_to be_flaky } - - it 'converts to a Cucumber::Message::TestResult' do - expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::UNKNOWN) - end - end - describe Cucumber::Core::Test::Result::Raisable do context 'with or without backtrace' do subject(:result) { described_class.new } @@ -75,32 +54,6 @@ end end - describe Cucumber::Core::Test::Result::Undefined do - subject(:result) { described_class.new } - - it 'describes itself to a visitor' do - expect(visitor).to receive(:undefined).with(args) - expect(visitor).to receive(:duration).with(an_unknown_duration, args) - - result.describe_to(visitor, args) - end - - it 'converts to a Cucumber::Message::TestResult' do - expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::UNDEFINED) - end - - it { expect(result.to_sym).to eq(:undefined) } - it { expect(result).not_to be_passed } - it { expect(result).not_to be_failed } - it { expect(result).not_to be_ambiguous } - it { expect(result).to be_undefined } - it { expect(result).not_to be_unknown } - it { expect(result).not_to be_skipped } - it { expect(result).not_to be_flaky } - - it { expect(result).not_to be_ok } - end - describe Cucumber::Core::Test::Result::Skipped do subject(:result) { described_class.new } @@ -126,32 +79,6 @@ it { expect(result).to be_ok } end - describe Cucumber::Core::Test::Result::Pending do - subject(:result) { described_class.new } - - it 'describes itself to a visitor' do - expect(visitor).to receive(:pending).with(result, args) - expect(visitor).to receive(:duration).with(an_unknown_duration, args) - - result.describe_to(visitor, args) - end - - it 'converts to a Cucumber::Message::TestResult' do - expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::PENDING) - end - - it { expect(result.to_sym).to eq(:pending) } - it { expect(result).not_to be_passed } - it { expect(result).not_to be_failed } - it { expect(result).not_to be_ambiguous } - it { expect(result).not_to be_undefined } - it { expect(result).not_to be_unknown } - it { expect(result).not_to be_skipped } - it { expect(result).not_to be_flaky } - - it { expect(result).not_to be_ok } - end - describe Cucumber::Core::Test::Result::Flaky do it { expect(described_class).not_to be_ok } end diff --git a/spec/cucumber/core/test/result/undefined_spec.rb b/spec/cucumber/core/test/result/undefined_spec.rb new file mode 100644 index 00000000..978de831 --- /dev/null +++ b/spec/cucumber/core/test/result/undefined_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'cucumber/core/test/result' +require 'support/duration_matcher' + +describe Cucumber::Core::Test::Result::Undefined do + subject(:result) { described_class.new } + + let(:visitor) { double } + let(:args) { double } + + describe '.ok?' do + it { expect(described_class).not_to be_ok } + end + + describe '#describe_to' do + it 'describes itself to a visitor' do + expect(visitor).to receive(:undefined).with(args) + expect(visitor).to receive(:duration).with(an_unknown_duration, args) + + result.describe_to(visitor, args) + end + end + + describe '#to_message' do + it 'is a `TestStepResult` message' do + expect(result.to_message).to be_a Cucumber::Messages::TestStepResult + end + + it 'has a status' do + expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::UNDEFINED) + end + + it 'has a duration' do + expect(result.to_message.duration).to have_attributes(seconds: 0, nanos: 0) + end + end + + it { expect(result.to_sym).to eq(:undefined) } + it { expect(result.to_s).to eq('?') } + + context 'with the BooleanMethods helper' do + describe '#ok?' do + it 'calls the class method' do + expect(described_class).to receive(:ok?) + + result.ok? + end + end + + it { expect(result).not_to be_failed } + it { expect(result).not_to be_ambiguous } + it { expect(result).not_to be_flaky } + it { expect(result).to be_undefined } + it { expect(result).not_to be_pending } + it { expect(result).not_to be_skipped } + it { expect(result).not_to be_passed } + it { expect(result).not_to be_unknown } + end +end diff --git a/spec/cucumber/core/test/result/unknown_spec.rb b/spec/cucumber/core/test/result/unknown_spec.rb new file mode 100644 index 00000000..1e8b1c2e --- /dev/null +++ b/spec/cucumber/core/test/result/unknown_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'cucumber/core/test/result' +require 'support/duration_matcher' + +describe Cucumber::Core::Test::Result::Unknown do + subject(:result) { described_class.new } + + let(:visitor) { double } + + it 'defines a with_filtered_backtrace method' do + expect(result.with_filtered_backtrace(double)).to eq(result) + end + + describe '.ok?' do + it { expect(described_class).not_to be_ok } + end + + describe '#describe_to' do + it 'describes itself to a visitor' do + expect(result.describe_to(visitor)).to eq(result) + end + end + + describe '#to_message' do + it 'is a `TestStepResult` message' do + expect(result.to_message).to be_a Cucumber::Messages::TestStepResult + end + + it 'has a status' do + expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::UNKNOWN) + end + + it 'has a duration' do + expect(result.to_message.duration).to have_attributes(seconds: 0, nanos: 0) + end + end + + it { expect(result.to_sym).to eq(:unknown) } + # Unknown should never be fired in the formatter so we don't have a stringified variant + + context 'with the BooleanMethods helper' do + describe '#ok?' do + it 'calls the class method' do + expect(described_class).to receive(:ok?) + + result.ok? + end + end + + it { expect(result).not_to be_failed } + it { expect(result).not_to be_ambiguous } + it { expect(result).not_to be_flaky } + it { expect(result).not_to be_undefined } + it { expect(result).not_to be_pending } + it { expect(result).not_to be_skipped } + it { expect(result).not_to be_passed } + it { expect(result).to be_unknown } + end +end From c971f38654b058a05965d8e45c34539888c043c5 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 10 Jul 2026 11:14:04 +0100 Subject: [PATCH 06/10] Migrate skipped and flaky tests --- spec/cucumber/core/test/result/flaky_spec.rb | 12 ++++ .../core/test/result/placeholder_spec.rb | 29 --------- .../cucumber/core/test/result/skipped_spec.rb | 61 +++++++++++++++++++ 3 files changed, 73 insertions(+), 29 deletions(-) create mode 100644 spec/cucumber/core/test/result/flaky_spec.rb create mode 100644 spec/cucumber/core/test/result/skipped_spec.rb diff --git a/spec/cucumber/core/test/result/flaky_spec.rb b/spec/cucumber/core/test/result/flaky_spec.rb new file mode 100644 index 00000000..546f1ff4 --- /dev/null +++ b/spec/cucumber/core/test/result/flaky_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'cucumber/core/test/result' +require 'support/duration_matcher' + +describe Cucumber::Core::Test::Result::Flaky do + subject(:result) { described_class.new } + + describe '.ok?' do + it { expect(described_class).to be_ok } + end +end diff --git a/spec/cucumber/core/test/result/placeholder_spec.rb b/spec/cucumber/core/test/result/placeholder_spec.rb index 00320761..e7ebffda 100644 --- a/spec/cucumber/core/test/result/placeholder_spec.rb +++ b/spec/cucumber/core/test/result/placeholder_spec.rb @@ -54,35 +54,6 @@ end end - describe Cucumber::Core::Test::Result::Skipped do - subject(:result) { described_class.new } - - it 'describes itself to a visitor' do - expect(visitor).to receive(:skipped).with(args) - expect(visitor).to receive(:duration).with(an_unknown_duration, args) - - result.describe_to(visitor, args) - end - - it 'converts to a Cucumber::Message::TestResult' do - expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::SKIPPED) - end - - it { expect(result.to_sym).to eq(:skipped) } - it { expect(result).not_to be_passed } - it { expect(result).not_to be_failed } - it { expect(result).not_to be_ambiguous } - it { expect(result).not_to be_undefined } - it { expect(result).not_to be_unknown } - it { expect(result).to be_skipped } - it { expect(result).not_to be_flaky } - it { expect(result).to be_ok } - end - - describe Cucumber::Core::Test::Result::Flaky do - it { expect(described_class).not_to be_ok } - end - describe Cucumber::Core::Test::Result::Summary do let(:summary) { described_class.new } let(:failed) { Cucumber::Core::Test::Result::Failed.new(Cucumber::Core::Test::Result::Duration.new(10), exception) } diff --git a/spec/cucumber/core/test/result/skipped_spec.rb b/spec/cucumber/core/test/result/skipped_spec.rb new file mode 100644 index 00000000..55bc4b08 --- /dev/null +++ b/spec/cucumber/core/test/result/skipped_spec.rb @@ -0,0 +1,61 @@ + +# frozen_string_literal: true + +require 'cucumber/core/test/result' +require 'support/duration_matcher' + +describe Cucumber::Core::Test::Result::Skipped do + subject(:result) { described_class.new } + + let(:visitor) { double } + let(:args) { double } + + describe '.ok?' do + it { expect(described_class).to be_ok } + end + + describe '#describe_to' do + it 'describes itself to a visitor' do + expect(visitor).to receive(:skipped).with(args) + expect(visitor).to receive(:duration).with(an_unknown_duration, args) + + result.describe_to(visitor, args) + end + end + + describe '#to_message' do + it 'is a `TestStepResult` message' do + expect(result.to_message).to be_a Cucumber::Messages::TestStepResult + end + + it 'has a status' do + expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::SKIPPED) + end + + it 'has a duration' do + expect(result.to_message.duration).to have_attributes(seconds: 0, nanos: 0) + end + end + + it { expect(result.to_sym).to eq(:skipped) } + it { expect(result.to_s).to eq('-') } + + context 'with the BooleanMethods helper' do + describe '#ok?' do + it 'calls the class method' do + expect(described_class).to receive(:ok?) + + result.ok? + end + end + + it { expect(result).not_to be_failed } + it { expect(result).not_to be_ambiguous } + it { expect(result).not_to be_flaky } + it { expect(result).not_to be_undefined } + it { expect(result).not_to be_pending } + it { expect(result).to be_skipped } + it { expect(result).not_to be_passed } + it { expect(result).not_to be_unknown } + end +end From eea37a7a011e8d5dac2dfded8e36133329989bb8 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 10 Jul 2026 14:25:25 +0100 Subject: [PATCH 07/10] Add in duration and raisable specs --- .../core/test/result/duration_spec.rb | 36 +++++++++++ spec/cucumber/core/test/result/failed_spec.rb | 16 ++--- .../core/test/result/placeholder_spec.rb | 62 ------------------- .../core/test/result/raisable_spec.rb | 57 +++++++++++++++++ 4 files changed, 99 insertions(+), 72 deletions(-) create mode 100644 spec/cucumber/core/test/result/duration_spec.rb create mode 100644 spec/cucumber/core/test/result/raisable_spec.rb diff --git a/spec/cucumber/core/test/result/duration_spec.rb b/spec/cucumber/core/test/result/duration_spec.rb new file mode 100644 index 00000000..aeb6f595 --- /dev/null +++ b/spec/cucumber/core/test/result/duration_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'cucumber/core/test/result' +require 'support/duration_matcher' + +describe Cucumber::Core::Test::Result::Duration do + subject(:duration) { described_class.new(10) } + + describe '#nanoseconds' do + before { duration.tap { |duration| @duration = duration.nanoseconds } } + + it '#nanoseconds can be accessed in #tap' do + expect(@duration).to eq(10) + end + end + + describe '#to_message_duration' do + subject(:message_duration) { duration.to_message_duration } + + it 'returns the correct message type' do + expect(message_duration).to be_a(Cucumber::Messages::Duration) + end + + it 'returns a message with the correct seconds and nanos' do + expect(message_duration).to have_attributes(seconds: 0, nanos: 10) + end + end + + describe '#seconds_to_duration' do + subject(:message_duration) { duration.seconds_to_duration(1.234) } + + it 'returns a hash with the correct seconds and nanos' do + expect(message_duration).to eq({ seconds: 1, nanos: 234_000_000 }) + end + end +end diff --git a/spec/cucumber/core/test/result/failed_spec.rb b/spec/cucumber/core/test/result/failed_spec.rb index faab42c2..38c1d0b2 100644 --- a/spec/cucumber/core/test/result/failed_spec.rb +++ b/spec/cucumber/core/test/result/failed_spec.rb @@ -10,6 +10,9 @@ let(:exception) { StandardError.new('error message') } let(:visitor) { double } let(:args) { double } + let(:filter_class) { double } + let(:filter) { double } + let(:filtered_exception) { double } before do allow(visitor).to receive(:failed) @@ -33,10 +36,9 @@ end it 'applies filters to the exception' do - filter_class = double - filter = double - filtered_exception = double - permit_exception_passthrough(filter_class, filter, filtered_exception) + # Permit an exception to be filtered and not excluded + allow(filter_class).to receive(:new).with(result.exception).and_return(filter) + allow(filter).to receive(:exception).and_return(filtered_exception) expect(result.with_filtered_backtrace(filter_class).exception).to eq(filtered_exception) end @@ -94,10 +96,4 @@ it { expect(result).not_to be_passed } it { expect(result).not_to be_unknown } end - - # Permit an exception to be filtered and not excluded - def permit_exception_passthrough(filter_class, filter, filtered_value) - allow(filter_class).to receive(:new).with(result.exception).and_return(filter) - allow(filter).to receive(:exception).and_return(filtered_value) - end end diff --git a/spec/cucumber/core/test/result/placeholder_spec.rb b/spec/cucumber/core/test/result/placeholder_spec.rb index e7ebffda..ecce930b 100644 --- a/spec/cucumber/core/test/result/placeholder_spec.rb +++ b/spec/cucumber/core/test/result/placeholder_spec.rb @@ -7,53 +7,6 @@ let(:visitor) { double } let(:args) { double } - describe Cucumber::Core::Test::Result::Raisable do - context 'with or without backtrace' do - subject(:result) { described_class.new } - - it 'does nothing if step has no backtrace line' do - step = 'does not respond_to?(:backtrace_line)' - - expect(result.with_appended_backtrace(step).backtrace).to be_nil - end - end - - context 'without backtrace' do - subject(:result) { described_class.new } - - it 'set the backtrace to the backtrace line of the step' do - step = double - allow(step).to receive(:backtrace_line).and_return('step_line') - - expect(result.with_appended_backtrace(step).backtrace).to eq(['step_line']) - end - - it 'does nothing when filtering the backtrace' do - expect(result.with_filtered_backtrace(double)).to eq(result) - end - end - - context 'with backtrace' do - subject(:result) { described_class.new('message', 0, 'backtrace') } - - it 'appends the backtrace line of the step' do - step = double - allow(step).to receive(:backtrace_line).and_return('step_line') - - expect(result.with_appended_backtrace(step).backtrace).to eq(%w[backtrace step_line]) - end - - it 'applies filters to the backtrace' do - filter_class = double - filter = double - filtered_backtrace = double - permit_exception_passthrough(filter_class, filter, filtered_backtrace) - - expect(result.with_filtered_backtrace(filter_class)).to eq(filtered_backtrace) - end - end - end - describe Cucumber::Core::Test::Result::Summary do let(:summary) { described_class.new } let(:failed) { Cucumber::Core::Test::Result::Failed.new(Cucumber::Core::Test::Result::Duration.new(10), exception) } @@ -185,15 +138,6 @@ def describe_to(visitor, *args) end end - describe Cucumber::Core::Test::Result::Duration do - subject(:duration) { described_class.new(10) } - - it '#nanoseconds can be accessed in #tap' do - expect(duration.tap { |duration| @duration = duration.nanoseconds }).to eq(duration) - expect(@duration).to eq(10) - end - end - describe Cucumber::Core::Test::Result::UnknownDuration do subject(:duration) { described_class.new } @@ -205,10 +149,4 @@ def describe_to(visitor, *args) expect { duration.nanoseconds }.to raise_error(RuntimeError) end end - - # Permit an exception to be filtered and not excluded - def permit_exception_passthrough(filter_class, filter, filtered_value) - allow(filter_class).to receive(:new).with(result.exception).and_return(filter) - allow(filter).to receive(:exception).and_return(filtered_value) - end end diff --git a/spec/cucumber/core/test/result/raisable_spec.rb b/spec/cucumber/core/test/result/raisable_spec.rb new file mode 100644 index 00000000..93799b30 --- /dev/null +++ b/spec/cucumber/core/test/result/raisable_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'cucumber/core/test/result' +require 'support/duration_matcher' + +describe Cucumber::Core::Test::Result::Raisable do + let(:visitor) { double } + let(:args) { double } + + subject(:result) { described_class.new } + + context 'with or without backtrace' do + it 'does nothing if step has no backtrace line' do + step = 'does not respond_to?(:backtrace_line)' + + expect(result.with_appended_backtrace(step).backtrace).to be_nil + end + end + + context 'without backtrace' do + it 'set the backtrace to the backtrace line of the step' do + step = double + allow(step).to receive(:backtrace_line).and_return('step_line') + + expect(result.with_appended_backtrace(step).backtrace).to eq(['step_line']) + end + + it 'does nothing when filtering the backtrace' do + expect(result.with_filtered_backtrace(double)).to eq(result) + end + end + + context 'with backtrace' do + subject(:result) { described_class.new('message', 0, 'backtrace') } + + let(:filter_class) { double } + let(:filter) { double } + let(:filtered_backtrace) { double } + + before do + # Permit an exception to be filtered and not excluded + allow(filter_class).to receive(:new).with(result.exception).and_return(filter) + allow(filter).to receive(:exception).and_return(filtered_backtrace) + end + + it 'appends the backtrace line of the step' do + step = double + allow(step).to receive(:backtrace_line).and_return('step_line') + + expect(result.with_appended_backtrace(step).backtrace).to eq(%w[backtrace step_line]) + end + + it 'applies filters to the backtrace' do + expect(result.with_filtered_backtrace(filter_class)).to eq(filtered_backtrace) + end + end +end From 0954a130ad04bc7b2ad724a91452cd7854d09ad7 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Mon, 13 Jul 2026 09:17:38 +0100 Subject: [PATCH 08/10] Final spec porting --- .../core/test/result/placeholder_spec.rb | 152 ------------------ .../cucumber/core/test/result/summary_spec.rb | 136 ++++++++++++++++ .../core/test/result/unknown_duration_spec.rb | 30 ++++ 3 files changed, 166 insertions(+), 152 deletions(-) delete mode 100644 spec/cucumber/core/test/result/placeholder_spec.rb create mode 100644 spec/cucumber/core/test/result/summary_spec.rb create mode 100644 spec/cucumber/core/test/result/unknown_duration_spec.rb diff --git a/spec/cucumber/core/test/result/placeholder_spec.rb b/spec/cucumber/core/test/result/placeholder_spec.rb deleted file mode 100644 index ecce930b..00000000 --- a/spec/cucumber/core/test/result/placeholder_spec.rb +++ /dev/null @@ -1,152 +0,0 @@ -# frozen_string_literal: true - -require 'cucumber/core/test/result' -require 'support/duration_matcher' - -describe Cucumber::Core::Test::Result do - let(:visitor) { double } - let(:args) { double } - - describe Cucumber::Core::Test::Result::Summary do - let(:summary) { described_class.new } - let(:failed) { Cucumber::Core::Test::Result::Failed.new(Cucumber::Core::Test::Result::Duration.new(10), exception) } - let(:passed) { Cucumber::Core::Test::Result::Passed.new(Cucumber::Core::Test::Result::Duration.new(11)) } - let(:skipped) { Cucumber::Core::Test:: Result::Skipped.new } - let(:unknown) { Cucumber::Core::Test::Result::Unknown.new } - let(:pending) { Cucumber::Core::Test::Result::Pending.new } - let(:undefined) { Cucumber::Core::Test::Result::Undefined.new } - let(:exception) { StandardError.new } - - it 'counts failed results' do - failed.describe_to(summary) - - expect(summary.total_failed).to eq(1) - expect(summary.total(:failed)).to eq(1) - expect(summary.total).to eq(1) - end - - it 'counts passed results' do - passed.describe_to(summary) - - expect(summary.total_passed).to eq(1) - expect(summary.total(:passed)).to eq(1) - expect(summary.total).to eq(1) - end - - it 'counts skipped results' do - skipped.describe_to(summary) - - expect(summary.total_skipped).to eq(1) - expect(summary.total(:skipped)).to eq(1) - expect(summary.total).to eq(1) - end - - it 'counts undefined results' do - undefined.describe_to(summary) - - expect(summary.total_undefined).to eq(1) - expect(summary.total(:undefined)).to eq(1) - expect(summary.total).to eq(1) - end - - it 'counts arbitrary raisable results' do - flickering = Class.new(Cucumber::Core::Test::Result::Raisable) do - def describe_to(visitor, *args) - visitor.flickering(*args) - end - end - - flickering.new.describe_to(summary) - - expect(summary.total_flickering).to eq(1) - expect(summary.total(:flickering)).to eq(1) - expect(summary.total).to eq(1) - end - - it 'returns zero for a status where no messages have been received' do - expect(summary.total_passed).to eq(0) - expect(summary.total(:passed)).to eq(0) - expect(summary.total_ponies).to eq(0) - expect(summary.total(:ponies)).to eq(0) - end - - it "doesn't count unknown results" do - unknown.describe_to(summary) - - expect(summary.total).to eq(0) - end - - it 'counts combinations' do - [passed, passed, failed, skipped, undefined].each { |result| result.describe_to(summary) } - - expect(summary.total).to eq(5) - expect(summary.total_passed).to eq(2) - expect(summary.total_failed).to eq(1) - expect(summary.total_skipped).to eq(1) - expect(summary.total_undefined).to eq(1) - end - - it 'records durations' do - [passed, failed].each { |result| result.describe_to(summary) } - - expect(summary.durations[0]).to be_duration(11) - expect(summary.durations[1]).to be_duration(10) - end - - it 'records exceptions' do - [passed, failed].each { |result| result.describe_to(summary) } - - expect(summary.exceptions).to eq([exception]) - end - - describe '#ok?' do - it 'passed result is ok' do - passed.describe_to(summary) - - expect(summary.ok?).to be true - end - - it 'skipped result is ok' do - skipped.describe_to(summary) - - expect(summary.ok?).to be true - end - - it 'failed result is not ok' do - failed.describe_to(summary) - - expect(summary.ok?).to be false - end - - it 'pending result is not ok' do - pending.describe_to(summary) - - expect(summary.ok?).to be false - end - - it 'undefined result is not ok' do - undefined.describe_to(summary) - - expect(summary.ok?).to be false - end - - it 'flaky result is not ok' do - summary.flaky - - expect(summary.ok?).to be false - end - end - end - - describe Cucumber::Core::Test::Result::UnknownDuration do - subject(:duration) { described_class.new } - - it '#tap does not execute the passed block' do - expect(duration.tap { raise 'tap executed block' }).to eq duration - end - - it 'accessing #nanoseconds outside #tap block raises exception' do - expect { duration.nanoseconds }.to raise_error(RuntimeError) - end - end -end diff --git a/spec/cucumber/core/test/result/summary_spec.rb b/spec/cucumber/core/test/result/summary_spec.rb new file mode 100644 index 00000000..7b1dee92 --- /dev/null +++ b/spec/cucumber/core/test/result/summary_spec.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +require 'cucumber/core/test/result' +require 'support/duration_matcher' + +describe Cucumber::Core::Test::Result::Summary do + subject(:summary) { described_class.new } + + let(:failed) { Cucumber::Core::Test::Result::Failed.new(Cucumber::Core::Test::Result::Duration.new(10), exception) } + let(:passed) { Cucumber::Core::Test::Result::Passed.new(Cucumber::Core::Test::Result::Duration.new(11)) } + let(:skipped) { Cucumber::Core::Test:: Result::Skipped.new } + let(:unknown) { Cucumber::Core::Test::Result::Unknown.new } + let(:pending) { Cucumber::Core::Test::Result::Pending.new } + let(:undefined) { Cucumber::Core::Test::Result::Undefined.new } + let(:exception) { StandardError.new } + + it 'counts failed results' do + failed.describe_to(summary) + + expect(summary.total_failed).to eq(1) + expect(summary.total(:failed)).to eq(1) + expect(summary.total).to eq(1) + end + + it 'counts passed results' do + passed.describe_to(summary) + + expect(summary.total_passed).to eq(1) + expect(summary.total(:passed)).to eq(1) + expect(summary.total).to eq(1) + end + + it 'counts skipped results' do + skipped.describe_to(summary) + + expect(summary.total_skipped).to eq(1) + expect(summary.total(:skipped)).to eq(1) + expect(summary.total).to eq(1) + end + + it 'counts undefined results' do + undefined.describe_to(summary) + + expect(summary.total_undefined).to eq(1) + expect(summary.total(:undefined)).to eq(1) + expect(summary.total).to eq(1) + end + + it 'counts arbitrary raisable results' do + flickering = Class.new(Cucumber::Core::Test::Result::Raisable) do + def describe_to(visitor, *args) + visitor.flickering(*args) + end + end + + flickering.new.describe_to(summary) + + expect(summary.total_flickering).to eq(1) + expect(summary.total(:flickering)).to eq(1) + expect(summary.total).to eq(1) + end + + it 'returns zero for a status where no messages have been received' do + expect(summary.total_passed).to eq(0) + expect(summary.total(:passed)).to eq(0) + expect(summary.total_ponies).to eq(0) + expect(summary.total(:ponies)).to eq(0) + end + + it "doesn't count unknown results" do + unknown.describe_to(summary) + + expect(summary.total).to eq(0) + end + + it 'counts combinations' do + [passed, passed, failed, skipped, undefined].each { |result| result.describe_to(summary) } + + expect(summary.total).to eq(5) + expect(summary.total_passed).to eq(2) + expect(summary.total_failed).to eq(1) + expect(summary.total_skipped).to eq(1) + expect(summary.total_undefined).to eq(1) + end + + it 'records durations' do + [passed, failed].each { |result| result.describe_to(summary) } + + expect(summary.durations[0]).to be_duration(11) + expect(summary.durations[1]).to be_duration(10) + end + + it 'records exceptions' do + [passed, failed].each { |result| result.describe_to(summary) } + + expect(summary.exceptions).to eq([exception]) + end + + describe '#ok?' do + it 'passed result is ok' do + passed.describe_to(summary) + + expect(summary.ok?).to be true + end + + it 'skipped result is ok' do + skipped.describe_to(summary) + + expect(summary.ok?).to be true + end + + it 'failed result is not ok' do + failed.describe_to(summary) + + expect(summary.ok?).to be false + end + + it 'pending result is not ok' do + pending.describe_to(summary) + + expect(summary.ok?).to be false + end + + it 'undefined result is not ok' do + undefined.describe_to(summary) + + expect(summary.ok?).to be false + end + + it 'flaky result is ok' do + summary.flaky + + expect(summary.ok?).to be true + end + end +end diff --git a/spec/cucumber/core/test/result/unknown_duration_spec.rb b/spec/cucumber/core/test/result/unknown_duration_spec.rb new file mode 100644 index 00000000..70fad578 --- /dev/null +++ b/spec/cucumber/core/test/result/unknown_duration_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'cucumber/core/test/result' +require 'support/duration_matcher' + +describe Cucumber::Core::Test::Result::UnknownDuration do + subject(:duration) { described_class.new } + + describe '#tap' do + it 'does not execute the passed block' do + expect(duration.tap { raise 'tap executed block' }).to eq(duration) + end + end + + describe '#nanoseconds' do + it 'accessing #nanoseconds outside a #tap block raises exception' do + expect { duration.nanoseconds }.to raise_error(RuntimeError) + end + end + + describe '#to_message_duration' do + it 'returns a Duration message' do + expect(duration.to_message_duration).to be_a Cucumber::Messages::Duration + end + + it 'has no time duration by default' do + expect(duration.to_message_duration).to have_attributes(seconds: 0, nanos: 0) + end + end +end From 2879570a445536d6143f66c39b21905e76f4e924 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Mon, 13 Jul 2026 09:18:46 +0100 Subject: [PATCH 09/10] Regen rubocop --- .rubocop_todo.yml | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b514ca62..cbfd2a33 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -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-07-13 08:18:00 UTC using RuboCop version 1.88.2. # 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 @@ -11,12 +11,26 @@ # TODO: [LH] v16 (Gherkin/Message bump) -> 61 files inspected, 272 offenses detected, 60 offenses autocorrectable # TODO: [LH] v16.2 (Generic refactors / new events) -> 79 files inspected, 222 offenses detected, 10 offenses autocorrectable # TODO: [LH] v17 prep -> 92 files inspected, 212 offenses detected, 10 offenses autocorrectable +# TODO: [LH] v18 prep -> 111 files inspected, 227 offenses detected, 16 offenses autocorrectable + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. +Layout/ExtraSpacing: + Exclude: + - 'spec/cucumber/core/test/result/summary_spec.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Layout/LeadingEmptyLines: + Exclude: + - 'spec/cucumber/core/test/result/skipped_spec.rb' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). Layout/SpaceAroundMethodCallOperator: Exclude: - - 'spec/cucumber/core/test/result_spec.rb' + - 'spec/cucumber/core/test/result/summary_spec.rb' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). @@ -60,7 +74,13 @@ RSpec/ExampleLength: # Configuration parameters: AssignmentOnly. RSpec/InstanceVariable: Exclude: - - 'spec/cucumber/core/test/result_spec.rb' + - 'spec/cucumber/core/test/result/duration_spec.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +RSpec/LeadingSubject: + Exclude: + - 'spec/cucumber/core/test/result/raisable_spec.rb' # Offense count: 23 RSpec/MissingExampleGroupArgument: @@ -73,11 +93,11 @@ RSpec/MissingExampleGroupArgument: - 'spec/cucumber/core/test/locations_filter_spec.rb' - 'spec/cucumber/core_spec.rb' -# Offense count: 66 +# Offense count: 64 RSpec/MultipleExpectations: Max: 5 -# Offense count: 53 +# Offense count: 59 # Configuration parameters: AllowSubject. RSpec/MultipleMemoizedHelpers: Max: 11 From 2b68ea365b50b0e739fff4f80e4fd9acadd9d768 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Mon, 13 Jul 2026 14:27:29 +0100 Subject: [PATCH 10/10] Add docs --- CHANGELOG.md | 1 + upgrading_notes/18.0.0.md | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b88cfaa7..73ce3db0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Please visit [cucumber/CONTRIBUTING.md](https://github.com/cucumber/cucumber/blo ### Breaking Change - Structure of Gherkin writer helpers have been changed. Now one helper per file, and all helpers are still available by requiring `gherkin/writer/helpers`. See upgrading notes for [18.0.0.md](upgrading_notes/18.0.0.md#upgrading-to-cucumber-core-1800), for full changes +- Flaky results are now treated as a passing state. See upgrading notes for [18.0.0.md](upgrading_notes/18.0.0.md#upgrading-to-cucumber-core-1800), for full changes ### Changed - Tidied up a bunch of mostly internal test code to be more rubocop compliant diff --git a/upgrading_notes/18.0.0.md b/upgrading_notes/18.0.0.md index 11400121..38e73323 100644 --- a/upgrading_notes/18.0.0.md +++ b/upgrading_notes/18.0.0.md @@ -1,5 +1,25 @@ # Upgrading to cucumber-core 18.0.0 +## Flaky results + +### Before cucumber-core 18.0.0 + +Any test result that has a status of "flaky" would return a `Cucumber::Core::Test::Result::Flaky` object. +This inherently is treated as a "failing" state in terms of "strict" mode (Something removed in v17 of core) + +```ruby +Cucumber::Core::Test::Result::Flaky.ok? # => false +``` + +### With cucumber-core 18.0.0 + +Any test result that has a status of "flaky" still returns a `Cucumber::Core::Test::Result::Flaky` object. +This inherently is treated as a "passing" state + +```ruby +Cucumber::Core::Test::Result::Flaky.ok? # => true +``` + ## Gherkin Writer Helpers The structure of the gherkin writer helpers was quite muddied pre 18.0.0 and the file contained a series of helper