From 7c4989863db5880a81f800f53c5664312a4f46e6 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 18:06:49 +0100 Subject: [PATCH 01/55] Initial files --- ruby/Gemfile | 5 +++ ruby/Gemfile.lock | 73 +++++++++++++++++++++++++++++++++ ruby/LICENSE | 21 ++++++++++ ruby/README.md | 81 +++++++++++++++++++++++++++++++++++++ ruby/VERSION | 1 + ruby/cucumber-query.gemspec | 32 +++++++++++++++ 6 files changed, 213 insertions(+) create mode 100644 ruby/Gemfile create mode 100644 ruby/Gemfile.lock create mode 100644 ruby/LICENSE create mode 100644 ruby/README.md create mode 100644 ruby/VERSION create mode 100644 ruby/cucumber-query.gemspec diff --git a/ruby/Gemfile b/ruby/Gemfile new file mode 100644 index 00000000..7f4f5e95 --- /dev/null +++ b/ruby/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gemspec diff --git a/ruby/Gemfile.lock b/ruby/Gemfile.lock new file mode 100644 index 00000000..01462746 --- /dev/null +++ b/ruby/Gemfile.lock @@ -0,0 +1,73 @@ +PATH + remote: . + specs: + cucumber-compatibility-kit (21.0.0) + +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.2) + diff-lcs (1.5.1) + json (2.9.1) + language_server-protocol (3.17.0.4) + lint_roller (1.1.0) + parallel (1.26.3) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + prism (1.4.0) + racc (1.8.1) + rainbow (3.1.1) + regexp_parser (2.10.0) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.2) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-performance (1.25.0) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) + rubocop-rspec (3.7.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + ruby-progressbar (1.13.0) + unicode-display_width (3.1.4) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + +PLATFORMS + ruby + x86_64-linux + +DEPENDENCIES + cucumber-compatibility-kit! + rspec (~> 3.13) + rubocop (~> 1.80.0) + rubocop-performance (~> 1.25.0) + rubocop-rspec (~> 3.7.0) + +BUNDLED WITH + 2.6.2 diff --git a/ruby/LICENSE b/ruby/LICENSE new file mode 100644 index 00000000..d23a133d --- /dev/null +++ b/ruby/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Cucumber Ltd and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ruby/README.md b/ruby/README.md new file mode 100644 index 00000000..faeb97a2 --- /dev/null +++ b/ruby/README.md @@ -0,0 +1,81 @@ +# The Cucumber Compatibility Kit for Ruby + +The CCK - aka. Cucumber Compatibility Kit - is a set of features and Messages. +It aims to validate an implementation of the +[Cucumber Messages protocol](https://github.com/cucumber/common/tree/main/messages#cucumber-messages). + +## Overview + +This kit (gem), consists of a set of features, miscellaneous files and messages: + +- Each area will contain one feature, which, once executed, will emit an exhaustive set of messages +as specified by the protocol +- Some of these areas may "also" require miscellaneous files to be used when testing functions +such as attaching images or documents or reading data from files +- Each area will contain a set of messages - serialized as a single `.ndjson` file +This is the reference for the CCK, that a given feature from the kit, when executed using any dedicated +step definitions, must emit the **exact** corresponding messages + +## Installation and Usage + +Add `cucumber-compatibility-kit` to your `Gemfile` as a development dependency, and +install it: + + bundle install + +Then add a spec that could look like this: + +```ruby +# spec/my_compatibility_checks_spec.rb +require 'cucumber/compatibility_kit' + +describe Cucumber::CompatibilityKit, type: :feature do + let(:cucumber_command) { 'bundle exec cucumber --publish-quiet --profile none --format message' } + + # Don't run the retry or skipped CCK Examples (For whatever reason) + examples = Cucumber::CompatibilityKit.gherkin.reject { |example| example == 'retry' || example == 'skipped' } + + examples.each do |example_name| + describe "'#{example_name}' example" do + include_examples 'cucumber compatibility kit' do + let(:example) { example_name } + # You will need to specify the relative support code and cck paths + let(:messages) { `#{cucumber_command} --require #{support_code_path} #{cck_path}` } + end + end + end +end +``` + +`CucumberCompatibilityKit.gherkin` will return an array that lists all the gherkin examples available within the CCK. +Here, we want to execute all of them except the `retry` and `skipped` ones (For whatever reason). + +`let(:messages)` will execute the cucumber command. As we are using the `message` formatter, `messages` will +then contain the messages as a `ndjson` document with one message per line. + +You can use `gem open cucumber-compatibility-kit` in order to take a look at the features and the +expected messages they should produce. They are available in the `features` folder within the gem. + +## More info + +The Cucumber Compatibility Kit is part of the development tools of [Cucumber](https://cucumber.io). +It allows us to make sure that all our implementations are properly supporting our internal protocol +and thus are compatible (and consistent), with each other and our common tools like the [html-formatter](https://github.com/cucumber/html-formatter). + +It can be a valuable tool if you are developing integration with cucumber, or your own implementation of it. + +Join us on [github/cucumber/compatibility-kit](https://github.com/cucumber/compatibility-kit) +to get more help if you need to. + +You can also take a look on [cucumber-ruby](https://github.com/cucumber/cucumber-ruby/blob/v9.2.0/compatibility/cck_spec.rb) +to see how the kit is used there. + +## Development + +Before building this project locally, the samples must be copied from the `devkit`. Use: + +``` +cd ../devkit +npm ci && npm run copy-to:ruby +cd ../ruby +``` diff --git a/ruby/VERSION b/ruby/VERSION new file mode 100644 index 00000000..fb5b5130 --- /dev/null +++ b/ruby/VERSION @@ -0,0 +1 @@ +21.0.0 diff --git a/ruby/cucumber-query.gemspec b/ruby/cucumber-query.gemspec new file mode 100644 index 00000000..e0150586 --- /dev/null +++ b/ruby/cucumber-query.gemspec @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +Gem::Specification.new do |s| + s.name = 'cucumber-query' + s.version = File.read(File.expand_path('VERSION', __dir__)).strip + s.authors = ['Luke Hill'] + s.description = 'Cucumber Query - query messages' + s.summary = "#{s.name}-#{s.version}" + s.email = 'cukebot@cucumber.io' + s.homepage = 'https://github.com/cucumber/query' + s.platform = Gem::Platform::RUBY + s.license = 'MIT' + s.required_ruby_version = '>= 3.2' + s.required_rubygems_version = '>= 3.2.8' + + s.metadata = { + 'bug_tracker_uri' => 'https://github.com/cucumber/query/issues', + 'changelog_uri' => 'https://github.com/cucumber/query/blob/main/CHANGELOG.md', + 'documentation_uri' => 'https://github.com/cucumber/query/blob/main/CONTRIBUTING.md', + 'mailing_list_uri' => 'https://groups.google.com/forum/#!forum/cukes', + 'source_code_uri' => 'https://github.com/cucumber/query/blob/main/ruby' + } + + s.add_development_dependency 'rspec', '~> 3.13' + s.add_development_dependency 'rubocop', '~> 1.80.0' + s.add_development_dependency 'rubocop-performance', '~> 1.25.0' + s.add_development_dependency 'rubocop-rspec', '~> 3.7.0' + + s.files = Dir['README.md', 'LICENSE', 'lib/**/*'] + s.rdoc_options = ['--charset=UTF-8'] + s.require_path = 'lib' +end From c6a0dba759031dc7043bb7200b8e174037b21a3c Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 18:08:01 +0100 Subject: [PATCH 02/55] Add hidden files --- ruby/.gitignore | 2 ++ ruby/.rspec | 1 + ruby/.rubocop.yml | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 ruby/.gitignore create mode 100644 ruby/.rspec create mode 100644 ruby/.rubocop.yml diff --git a/ruby/.gitignore b/ruby/.gitignore new file mode 100644 index 00000000..7ae6fcfb --- /dev/null +++ b/ruby/.gitignore @@ -0,0 +1,2 @@ +Gemfile.lock +*.gem diff --git a/ruby/.rspec b/ruby/.rspec new file mode 100644 index 00000000..4e1e0d2f --- /dev/null +++ b/ruby/.rspec @@ -0,0 +1 @@ +--color diff --git a/ruby/.rubocop.yml b/ruby/.rubocop.yml new file mode 100644 index 00000000..b9132120 --- /dev/null +++ b/ruby/.rubocop.yml @@ -0,0 +1,19 @@ +plugins: + - rubocop-performance + - rubocop-rspec + +inherit_mode: + merge: + - Exclude + +AllCops: + TargetRubyVersion: 3.2 + NewCops: enable + +# Stylistic preference for cucumber +Gemspec/DevelopmentDependencies: + Enabled: false + +# Disabled on our repo's to enable polyglot-release +Gemspec/RequireMFA: + Enabled: false From 5f4a90bf87f021016b0dfbd5038e9f861843dd04 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 18:08:51 +0100 Subject: [PATCH 03/55] Fix typo --- testdata/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testdata/README.md b/testdata/README.md index ec760193..7416c957 100644 --- a/testdata/README.md +++ b/testdata/README.md @@ -4,7 +4,7 @@ Query uses the examples from the [cucumber compatibility kit](https://github.com for acceptance testing. These examples consist of `.ndjson` files created by the [`fake-cucumber` reference implementation](https://github.com/cucumber/fake-cucumber). -* The `.njdon` files are copied in by running `npm install`. +* The `.ndjson` files are copied in by running `npm install`. * The expected `.xml` files are created by running the tests We ensure the `.ndjson` files stay up to date by running `npm install` in CI From a988fd0aa922f3a5cf6568ff922ccb84464b5549 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 18:27:46 +0100 Subject: [PATCH 04/55] Add cucumber messages to runtime requirements and port over existing queries --- ruby/Gemfile.lock | 6 ++- ruby/cucumber-query.gemspec | 2 + ruby/lib/cucumber/query/errors.rb | 8 ++++ ruby/lib/cucumber/query/hook_by_test_step.rb | 32 ++++++++++++++ ruby/lib/cucumber/query/pickle_by_test.rb | 26 ++++++++++++ .../query/pickle_step_by_test_step.rb | 26 ++++++++++++ .../query/step_definitions_by_test_step.rb | 40 ++++++++++++++++++ .../query/test_case_started_by_test_case.rb | 42 +++++++++++++++++++ ruby/lib/cucumber/query/test_run_started.rb | 17 ++++++++ 9 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 ruby/lib/cucumber/query/errors.rb create mode 100644 ruby/lib/cucumber/query/hook_by_test_step.rb create mode 100644 ruby/lib/cucumber/query/pickle_by_test.rb create mode 100644 ruby/lib/cucumber/query/pickle_step_by_test_step.rb create mode 100644 ruby/lib/cucumber/query/step_definitions_by_test_step.rb create mode 100644 ruby/lib/cucumber/query/test_case_started_by_test_case.rb create mode 100644 ruby/lib/cucumber/query/test_run_started.rb diff --git a/ruby/Gemfile.lock b/ruby/Gemfile.lock index 01462746..a598c6b3 100644 --- a/ruby/Gemfile.lock +++ b/ruby/Gemfile.lock @@ -1,12 +1,14 @@ PATH remote: . specs: - cucumber-compatibility-kit (21.0.0) + cucumber-query (21.0.0) + cucumber-messages (> 25, < 30) GEM remote: https://rubygems.org/ specs: ast (2.4.2) + cucumber-messages (28.1.0) diff-lcs (1.5.1) json (2.9.1) language_server-protocol (3.17.0.4) @@ -63,7 +65,7 @@ PLATFORMS x86_64-linux DEPENDENCIES - cucumber-compatibility-kit! + cucumber-query! rspec (~> 3.13) rubocop (~> 1.80.0) rubocop-performance (~> 1.25.0) diff --git a/ruby/cucumber-query.gemspec b/ruby/cucumber-query.gemspec index e0150586..d7b32e4b 100644 --- a/ruby/cucumber-query.gemspec +++ b/ruby/cucumber-query.gemspec @@ -21,6 +21,8 @@ Gem::Specification.new do |s| 'source_code_uri' => 'https://github.com/cucumber/query/blob/main/ruby' } + s.add_dependency 'cucumber-messages', '> 25', '< 30' + s.add_development_dependency 'rspec', '~> 3.13' s.add_development_dependency 'rubocop', '~> 1.80.0' s.add_development_dependency 'rubocop-performance', '~> 1.25.0' diff --git a/ruby/lib/cucumber/query/errors.rb b/ruby/lib/cucumber/query/errors.rb new file mode 100644 index 00000000..6526fec1 --- /dev/null +++ b/ruby/lib/cucumber/query/errors.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Cucumber + module Query + TestCaseUnknownError = Class.new(StandardError) + TestStepUnknownError = Class.new(StandardError) + end +end diff --git a/ruby/lib/cucumber/query/hook_by_test_step.rb b/ruby/lib/cucumber/query/hook_by_test_step.rb new file mode 100644 index 00000000..4d495ffc --- /dev/null +++ b/ruby/lib/cucumber/query/hook_by_test_step.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require_relative 'errors' + +module Cucumber + module Query + class HookByTestStep + def initialize(config) + @hook_id_by_test_step_id = {} + + config.on_event :test_step_created, &method(:on_test_step_created) + config.on_event :hook_test_step_created, &method(:on_hook_test_step_created) + end + + def hook_id(test_step) + return @hook_id_by_test_step_id[test_step.id] if @hook_id_by_test_step_id.key?(test_step.id) + + raise TestStepUnknownError, "No hook found for #{test_step.id} }. Known: #{@hook_id_by_test_step_id.keys}" + end + + private + + def on_test_step_created(event) + @hook_id_by_test_step_id[event.test_step.id] = nil + end + + def on_hook_test_step_created(event) + @hook_id_by_test_step_id[event.test_step.id] = event.hook.id + end + end + end +end diff --git a/ruby/lib/cucumber/query/pickle_by_test.rb b/ruby/lib/cucumber/query/pickle_by_test.rb new file mode 100644 index 00000000..73dcbb7e --- /dev/null +++ b/ruby/lib/cucumber/query/pickle_by_test.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require_relative 'errors' + +module Cucumber + module Query + class PickleByTest + def initialize(config) + @pickle_id_by_test_case_id = {} + config.on_event :test_case_created, &method(:on_test_case_created) + end + + def pickle_id(test_case) + return @pickle_id_by_test_case_id[test_case.id] if @pickle_id_by_test_case_id.key?(test_case.id) + + raise TestCaseUnknownError, "No pickle found for #{test_case.id} }. Known: #{@pickle_id_by_test_case_id.keys}" + end + + private + + def on_test_case_created(event) + @pickle_id_by_test_case_id[event.test_case.id] = event.pickle.id + end + end + end +end diff --git a/ruby/lib/cucumber/query/pickle_step_by_test_step.rb b/ruby/lib/cucumber/query/pickle_step_by_test_step.rb new file mode 100644 index 00000000..42e788b5 --- /dev/null +++ b/ruby/lib/cucumber/query/pickle_step_by_test_step.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require_relative 'errors' + +module Cucumber + module Query + class PickleStepByTestStep + def initialize(config) + @pickle_id_step_by_test_step_id = {} + config.on_event :test_step_created, &method(:on_test_step_created) + end + + def pickle_step_id(test_step) + return @pickle_id_step_by_test_step_id[test_step.id] if @pickle_id_step_by_test_step_id.key?(test_step.id) + + raise TestStepUnknownError, "No pickle step found for #{test_step.id} }. Known: #{@pickle_id_step_by_test_step_id.keys}" + end + + private + + def on_test_step_created(event) + @pickle_id_step_by_test_step_id[event.test_step.id] = event.pickle_step.id + end + end + end +end diff --git a/ruby/lib/cucumber/query/step_definitions_by_test_step.rb b/ruby/lib/cucumber/query/step_definitions_by_test_step.rb new file mode 100644 index 00000000..cc524afc --- /dev/null +++ b/ruby/lib/cucumber/query/step_definitions_by_test_step.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require_relative 'errors' + +module Cucumber + module Query + class StepDefinitionsByTestStep + def initialize(config) + @step_definition_ids_by_test_step_id = {} + @step_match_arguments_by_test_step_id = {} + + config.on_event :test_step_created, &method(:on_test_step_created) + config.on_event :step_activated, &method(:on_step_activated) + end + + def step_definition_ids(test_step) + return @step_definition_ids_by_test_step_id[test_step.id] if @step_definition_ids_by_test_step_id.key?(test_step.id) + + raise TestStepUnknownError, "No step definition found for #{test_step.id} }. Known: #{@step_definition_ids_by_test_step_id.keys}" + end + + def step_match_arguments(test_step) + return @step_match_arguments_by_test_step_id[test_step.id] if @step_match_arguments_by_test_step_id.key?(test_step.id) + + raise TestStepUnknownError, "No step match arguments found for #{test_step.id} }. Known: #{@step_match_arguments_by_test_step_id.keys}" + end + + private + + def on_test_step_created(event) + @step_definition_ids_by_test_step_id[event.test_step.id] = [] + end + + def on_step_activated(event) + @step_definition_ids_by_test_step_id[event.test_step.id] << event.step_match.step_definition.id + @step_match_arguments_by_test_step_id[event.test_step.id] = event.step_match.step_arguments + end + end + end +end diff --git a/ruby/lib/cucumber/query/test_case_started_by_test_case.rb b/ruby/lib/cucumber/query/test_case_started_by_test_case.rb new file mode 100644 index 00000000..5584cf68 --- /dev/null +++ b/ruby/lib/cucumber/query/test_case_started_by_test_case.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require_relative 'errors' + +module Cucumber + module Query + class TestCaseStartedByTestCase + def initialize(config) + @config = config + config.on_event :test_case_created, &method(:on_test_case_created) + config.on_event :test_case_started, &method(:on_test_case_started) + + @attempts_by_test_case_id = {} + @test_case_started_id_by_test_case_id = {} + end + + def attempt_by_test_case(test_case) + raise TestCaseUnknownError, "No test case found for #{test_case.id} }. Known: #{@attempts_by_test_case_id.keys}" unless @attempts_by_test_case_id.key?(test_case.id) + + @attempts_by_test_case_id[test_case.id] + end + + def test_case_started_id_by_test_case(test_case) + raise TestCaseUnknownError, "No test case found for #{test_case.id} }. Known: #{@test_case_started_id_by_test_case_id.keys}" unless @test_case_started_id_by_test_case_id.key?(test_case.id) + + @test_case_started_id_by_test_case_id[test_case.id] + end + + private + + def on_test_case_created(event) + @attempts_by_test_case_id[event.test_case.id] = 0 + @test_case_started_id_by_test_case_id[event.test_case.id] = nil + end + + def on_test_case_started(event) + @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 + end + end +end diff --git a/ruby/lib/cucumber/query/test_run_started.rb b/ruby/lib/cucumber/query/test_run_started.rb new file mode 100644 index 00000000..6a28c074 --- /dev/null +++ b/ruby/lib/cucumber/query/test_run_started.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative 'errors' + +module Cucumber + module Query + class TestRunStarted + def initialize(config) + @config = config + end + + def id + @id ||= @config.id_generator.new_id + end + end + end +end From 273e248ffa210aba8315c2f5fb47f39d16aabd98 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 18:33:45 +0100 Subject: [PATCH 05/55] Add in spec files --- ruby/.rspec | 1 + .../cucumber/query/hook_by_test_step_spec.rb | 94 ++++++++++ .../cucumber/query/pickle_by_test_spec.rb | 57 ++++++ .../query/pickle_step_by_test_step_spec.rb | 62 +++++++ .../step_definitions_by_test_step_spec.rb | 163 ++++++++++++++++++ .../test_case_started_by_test_case_spec.rb | 83 +++++++++ ruby/spec/spec_helper.rb | 0 7 files changed, 460 insertions(+) create mode 100644 ruby/spec/cucumber/query/hook_by_test_step_spec.rb create mode 100644 ruby/spec/cucumber/query/pickle_by_test_spec.rb create mode 100644 ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb create mode 100644 ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb create mode 100644 ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb create mode 100644 ruby/spec/spec_helper.rb diff --git a/ruby/.rspec b/ruby/.rspec index 4e1e0d2f..3687797e 100644 --- a/ruby/.rspec +++ b/ruby/.rspec @@ -1 +1,2 @@ +--require spec_helper --color diff --git a/ruby/spec/cucumber/query/hook_by_test_step_spec.rb b/ruby/spec/cucumber/query/hook_by_test_step_spec.rb new file mode 100644 index 00000000..6a9bf9d5 --- /dev/null +++ b/ruby/spec/cucumber/query/hook_by_test_step_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'cucumber/formatter/spec_helper' + +require 'cucumber/query/hook_by_test_step' + +describe Cucumber::Query::HookByTestStep do + extend Cucumber::Formatter::SpecHelperDsl + include Cucumber::Formatter::SpecHelper + + before do + Cucumber::Term::ANSIColor.coloring = false + @test_cases = [] + + @out = StringIO.new + @config = actual_runtime.configuration.with_options(out_stream: @out) + @formatter = described_class.new(@config) + + @config.on_event :test_case_started do |event| + @test_cases << event.test_case + end + + @hook_ids = [] + @config.on_event :envelope do |event| + next unless event.envelope.hook + + @hook_ids << event.envelope.hook.id + end + end + + context 'given a single feature' do + before do + run_defined_feature + end + + context 'with a scenario' do + describe '#pickle_step_id' do + define_feature <<-FEATURE + Feature: Banana party + + Scenario: Monkey eats banana + Given there are bananas + FEATURE + + define_steps do + Before() {} + After() {} + end + + it 'provides the ID of the Before Hook used to generate the Test::Step' do + test_case = @test_cases.first + expect(@formatter.hook_id(test_case.test_steps.first)).to eq(@hook_ids.first) + end + + it 'provides the ID of the After Hook used to generate the Test::Step' do + test_case = @test_cases.first + expect(@formatter.hook_id(test_case.test_steps.last)).to eq(@hook_ids.last) + end + + it 'returns nil if the step was not generated from a hook' do + test_case = @test_cases.first + expect(@formatter.hook_id(test_case.test_steps[1])).to be_nil + end + + it 'raises an exception when the test_step is unknown' do + test_step = double + allow(test_step).to receive(:id).and_return('whatever-id') + + expect { @formatter.hook_id(test_step) }.to raise_error(Cucumber::Formatter::TestStepUnknownError) + end + end + end + + context 'with AfterStep hooks' do + describe '#pickle_step_id' do + define_feature <<-FEATURE + Feature: Banana party + + Scenario: Monkey eats banana + Given there are bananas + FEATURE + + define_steps do + AfterStep() {} + end + + it 'provides the ID of the AfterStepHook used to generate the Test::Step' do + test_case = @test_cases.first + expect(@formatter.hook_id(test_case.test_steps.last)).to eq(@hook_ids.first) + end + end + end + end +end diff --git a/ruby/spec/cucumber/query/pickle_by_test_spec.rb b/ruby/spec/cucumber/query/pickle_by_test_spec.rb new file mode 100644 index 00000000..5f790eaf --- /dev/null +++ b/ruby/spec/cucumber/query/pickle_by_test_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'cucumber/formatter/spec_helper' +require 'cucumber/query/pickle_by_test' + +describe Cucumber::Query::PickleByTest do + extend Cucumber::Formatter::SpecHelperDsl + include Cucumber::Formatter::SpecHelper + + before do + Cucumber::Term::ANSIColor.coloring = false + @test_cases = [] + + @out = StringIO.new + @config = actual_runtime.configuration.with_options(out_stream: @out) + @formatter = described_class.new(@config) + + @config.on_event :test_case_created do |event| + @test_cases << event.test_case + end + + @pickle_ids = [] + @config.on_event :envelope do |event| + next unless event.envelope.pickle + + @pickle_ids << event.envelope.pickle.id + end + end + + describe 'given a single feature' do + before do + run_defined_feature + end + + describe 'with a scenario' do + describe '#pickle_id' do + define_feature <<-FEATURE + Feature: Banana party + + Scenario: Monkey eats banana + Given there are bananas + FEATURE + + it 'provides the ID of the pickle used to generate the Test::Case' do + expect(@formatter.pickle_id(@test_cases.first)).to eq(@pickle_ids.first) + end + + it 'raises an error when the Test::Case is unknown' do + test_case = double + allow(test_case).to receive(:id).and_return('whatever-id') + + expect { @formatter.pickle_id(test_case) }.to raise_error(Cucumber::Formatter::TestCaseUnknownError) + end + end + end + end +end diff --git a/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb b/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb new file mode 100644 index 00000000..2a7415a4 --- /dev/null +++ b/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'cucumber/formatter/spec_helper' +require 'cucumber/formatter/query/pickle_step_by_test_step' + +describe Cucumber::Query::PickleStepByTestStep do + extend Cucumber::Formatter::SpecHelperDsl + include Cucumber::Formatter::SpecHelper + + before do + Cucumber::Term::ANSIColor.coloring = false + @test_cases = [] + + @out = StringIO.new + @config = actual_runtime.configuration.with_options(out_stream: @out) + @formatter = described_class.new(@config) + + @config.on_event :test_case_created do |event| + @test_cases << event.test_case + end + + @pickle_step_ids = [] + @config.on_event :envelope do |event| + next unless event.envelope.pickle + + event.envelope.pickle.steps.each do |step| + @pickle_step_ids << step.id + end + end + end + + describe 'given a single feature' do + before do + run_defined_feature + end + + describe 'with a scenario' do + describe '#pickle_step_id' do + define_feature <<-FEATURE + Feature: Banana party + + Scenario: Monkey eats banana + Given there are bananas + FEATURE + + it 'provides the ID of the PickleStep used to generate the Test::Step' do + test_case = @test_cases.first + test_step = test_case.test_steps.first + + expect(@formatter.pickle_step_id(test_step)).to eq(@pickle_step_ids.first) + end + + it 'raises an exception when the test_step is unknown' do + test_step = double + allow(test_step).to receive(:id).and_return('whatever-id') + + expect { @formatter.pickle_step_id(test_step) }.to raise_error(Cucumber::Formatter::TestStepUnknownError) + end + end + end + end +end diff --git a/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb b/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb new file mode 100644 index 00000000..5b7ab1fd --- /dev/null +++ b/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +require 'cucumber/formatter/spec_helper' +require 'cucumber/formatter/query/step_definitions_by_test_step' + +describe Cucumber::Query::StepDefinitionsByTestStep do + extend Cucumber::Formatter::SpecHelperDsl + include Cucumber::Formatter::SpecHelper + + before do + Cucumber::Term::ANSIColor.coloring = false + @test_cases = [] + + @out = StringIO.new + @config = actual_runtime.configuration.with_options(out_stream: @out) + @formatter = described_class.new(@config) + + @config.on_event :test_case_created do |event| + @test_cases << event.test_case + end + + @step_definition_ids = [] + @config.on_event :envelope do |event| + next unless event.envelope.step_definition + + @step_definition_ids << event.envelope.step_definition.id + end + end + + describe 'given a single feature' do + before do + run_defined_feature + end + + describe '#step_definition_ids' do + context 'with a matching step' do + define_steps do + Given(/^there are bananas$/) {} + end + + define_feature <<-FEATURE + Feature: Banana party + + Scenario: Monkey eats banana + Given there are bananas + FEATURE + + it 'provides the ID of the StepDefinition that matches Test::Step' do + test_case = @test_cases.first + test_step = test_case.test_steps.first + + expect(@formatter.step_definition_ids(test_step)).to eq([@step_definition_ids.first]) + end + end + + context 'with a step that was not activated' do + context 'when there is no match' do + define_feature <<-FEATURE + Feature: Banana party + + Scenario: Monkey eats banana + Given there are bananas + FEATURE + + it 'returns an empty array' do + test_case = @test_cases.first + test_step = test_case.test_steps.first + + expect(@formatter.step_definition_ids(test_step)).to be_empty + end + end + + context 'when there are multiple matches' do + define_steps do + Given(/^there are bananas$/) {} + Given(/^there .* bananas$/) {} + end + + define_feature <<-FEATURE + Feature: Banana party + + Scenario: Monkey eats banana + Given there are bananas + FEATURE + + it 'returns an empty array as the step is not activated' do + test_case = @test_cases.first + test_step = test_case.test_steps.first + + expect(@formatter.step_definition_ids(test_step)).to be_empty + end + end + end + + context 'with an unknown step' do + define_feature 'Feature: Banana party' + + it 'raises an exception' do + test_step = double + allow(test_step).to receive(:id).and_return('whatever-id') + + expect { @formatter.step_definition_ids(test_step) }.to raise_error(Cucumber::Formatter::TestStepUnknownError) + end + end + end + + describe '#step_match_arguments' do + context 'with a matching step without arguments' do + define_steps do + Given(/^there are bananas$/) {} + end + + define_feature <<-FEATURE + Feature: Banana party + + Scenario: Monkey eats banana + Given there are bananas + FEATURE + + it 'returns an empty list' do + test_case = @test_cases.first + test_step = test_case.test_steps.first + + expect(@formatter.step_match_arguments(test_step)).to be_empty + end + end + + context 'with a matching step with arguments' do + define_steps do + Given(/^there are (.*)$/) {} + end + + define_feature <<-FEATURE + Feature: Banana party + + Scenario: Monkey eats banana + Given there are bananas + FEATURE + + it 'returns an empty list' do + test_case = @test_cases.first + test_step = test_case.test_steps.first + matches = @formatter.step_match_arguments(test_step) + + expect(matches.count).to eq(1) + expect(matches.first).to be_a(Cucumber::CucumberExpressions::Argument) + expect(matches.first.group.value).to eq('bananas') + end + end + + context 'with an unknown step' do + define_feature 'Feature: Banana party' + + it 'raises an exception' do + test_step = double + allow(test_step).to receive(:id).and_return('whatever-id') + + expect { @formatter.step_match_arguments(test_step) }.to raise_error(Cucumber::Formatter::TestStepUnknownError) + end + end + end + end +end diff --git a/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb b/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb new file mode 100644 index 00000000..7ed82b48 --- /dev/null +++ b/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'cucumber/formatter/spec_helper' +require 'cucumber/formatter/query/test_case_started_by_test_case' + + +describe Cucumber::Query::TestCaseStartedByTestCase do + extend Cucumber::Formatter::SpecHelperDsl + include Cucumber::Formatter::SpecHelper + + before(:each) do + Cucumber::Term::ANSIColor.coloring = false + + @out = StringIO.new + @config = actual_runtime.configuration.with_options(out_stream: @out) + @formatter = described_class.new(@config) + end + + let(:unknown_test_case) do + test_case = double + allow(test_case).to receive(:id).and_return('whatever-id') + test_case + end + + describe '#attempt_by_test_case' do + it 'raises an exception when the TestCase is unknown' do + expect { @formatter.attempt_by_test_case(unknown_test_case) }.to raise_exception(TestCaseUnknownError) + end + + context 'when the test case has been declared' do + before do + @test_case = double + allow(@test_case).to receive(:id).and_return('some-valid-id') + + @config.notify :test_case_created, @test_case, nil + end + + it 'returns 0 if no test_case_started event has been fired' do + expect(@formatter.attempt_by_test_case(@test_case)).to eq(0) + end + + it 'increments the attemp on every test_case_started event' do + @config.notify :test_case_started, @test_case + expect(@formatter.attempt_by_test_case(@test_case)).to eq(1) + + @config.notify :test_case_started, @test_case + expect(@formatter.attempt_by_test_case(@test_case)).to eq(2) + end + end + end + + describe '#test_case_started_id_by_test_case' do + it 'raises an exception when the TestCase is unknown' do + expect { @formatter.test_case_started_id_by_test_case(unknown_test_case) }.to raise_exception(TestCaseUnknownError) + end + + context 'when the test case has been declared' do + before do + @test_case = double + allow(@test_case).to receive(:id).and_return('some-valid-id') + + @config.notify :test_case_created, @test_case, nil + end + + it 'returns nil if no test_case_started event has been fired' do + expect(@formatter.test_case_started_id_by_test_case(@test_case)).to be_nil + end + + it 'gives a new id when a test_case_started event is fired' do + @config.notify :test_case_started, @test_case + + first_attempt_id = @formatter.test_case_started_id_by_test_case(@test_case) + expect(first_attempt_id).not_to be_nil + + @config.notify :test_case_started, @test_case + second_attempt_id = @formatter.test_case_started_id_by_test_case(@test_case) + expect(second_attempt_id).not_to be_nil + + expect(second_attempt_id).not_to eq(first_attempt_id) + end + end + end +end diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..e69de29b From b6c0a15475a73e6f603102635061e58d760ce544 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 18:48:07 +0100 Subject: [PATCH 06/55] Fix up all loading, relations and requirements --- ruby/Gemfile.lock | 39 +++++++++++- ruby/cucumber-query.gemspec | 1 + .../cucumber/query/hook_by_test_step_spec.rb | 5 -- ruby/spec/spec_helper.rb | 12 ++++ ruby/spec/support/gherkin_helper.rb | 13 ++++ ruby/spec/support/runner_helper.rb | 63 +++++++++++++++++++ 6 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 ruby/spec/support/gherkin_helper.rb create mode 100644 ruby/spec/support/runner_helper.rb diff --git a/ruby/Gemfile.lock b/ruby/Gemfile.lock index a598c6b3..a65432fe 100644 --- a/ruby/Gemfile.lock +++ b/ruby/Gemfile.lock @@ -8,11 +8,44 @@ GEM remote: https://rubygems.org/ specs: ast (2.4.2) - cucumber-messages (28.1.0) + base64 (0.3.0) + bigdecimal (3.2.3) + builder (3.3.0) + cucumber (10.1.0) + base64 (~> 0.2) + builder (~> 3.2) + cucumber-ci-environment (> 9, < 11) + cucumber-core (> 15, < 17) + cucumber-cucumber-expressions (> 17, < 19) + cucumber-html-formatter (> 20.3, < 22) + diff-lcs (~> 1.5) + logger (~> 1.6) + mini_mime (~> 1.1) + multi_test (~> 1.1) + sys-uname (~> 1.3) + cucumber-ci-environment (10.0.1) + cucumber-core (15.2.1) + cucumber-gherkin (> 27, < 33) + cucumber-messages (> 26, < 30) + cucumber-tag-expressions (> 5, < 7) + cucumber-cucumber-expressions (18.0.1) + bigdecimal + cucumber-gherkin (32.2.0) + cucumber-messages (> 25, < 28) + cucumber-html-formatter (21.14.0) + cucumber-messages (> 19, < 28) + cucumber-messages (27.2.0) + cucumber-tag-expressions (6.1.2) diff-lcs (1.5.1) + ffi (1.17.2) + ffi (1.17.2-x86_64-linux-gnu) json (2.9.1) language_server-protocol (3.17.0.4) lint_roller (1.1.0) + logger (1.7.0) + memoist3 (1.0.0) + mini_mime (1.1.5) + multi_test (1.1.0) parallel (1.26.3) parser (3.3.9.0) ast (~> 2.4.1) @@ -56,6 +89,9 @@ GEM lint_roller (~> 1.1) rubocop (~> 1.72, >= 1.72.1) ruby-progressbar (1.13.0) + sys-uname (1.4.1) + ffi (~> 1.1) + memoist3 (~> 1.0.0) unicode-display_width (3.1.4) unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) @@ -65,6 +101,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + cucumber (~> 10.1) cucumber-query! rspec (~> 3.13) rubocop (~> 1.80.0) diff --git a/ruby/cucumber-query.gemspec b/ruby/cucumber-query.gemspec index d7b32e4b..b152a6a4 100644 --- a/ruby/cucumber-query.gemspec +++ b/ruby/cucumber-query.gemspec @@ -23,6 +23,7 @@ Gem::Specification.new do |s| s.add_dependency 'cucumber-messages', '> 25', '< 30' + s.add_development_dependency 'cucumber', '~> 10.1' s.add_development_dependency 'rspec', '~> 3.13' s.add_development_dependency 'rubocop', '~> 1.80.0' s.add_development_dependency 'rubocop-performance', '~> 1.25.0' diff --git a/ruby/spec/cucumber/query/hook_by_test_step_spec.rb b/ruby/spec/cucumber/query/hook_by_test_step_spec.rb index 6a9bf9d5..83c44b0b 100644 --- a/ruby/spec/cucumber/query/hook_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/hook_by_test_step_spec.rb @@ -1,13 +1,8 @@ # frozen_string_literal: true -require 'cucumber/formatter/spec_helper' - require 'cucumber/query/hook_by_test_step' describe Cucumber::Query::HookByTestStep do - extend Cucumber::Formatter::SpecHelperDsl - include Cucumber::Formatter::SpecHelper - before do Cucumber::Term::ANSIColor.coloring = false @test_cases = [] diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb index e69de29b..3c684fd4 100644 --- a/ruby/spec/spec_helper.rb +++ b/ruby/spec/spec_helper.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'cucumber' +require 'cucumber/core' + +require_relative 'support/runner_helper' +require_relative 'support/gherkin_helper' + +RSpec.configure do |c| + c.include RunnerHelper + c.extend GherkinHelper +end diff --git a/ruby/spec/support/gherkin_helper.rb b/ruby/spec/support/gherkin_helper.rb new file mode 100644 index 00000000..9303804a --- /dev/null +++ b/ruby/spec/support/gherkin_helper.rb @@ -0,0 +1,13 @@ + +module GherkinHelper + attr_reader :feature_content, :step_defs, :feature_filename + + def define_feature(string, feature_file = 'spec.feature') + @feature_content = string + @feature_filename = feature_file + end + + def define_steps(&block) + @step_defs = block + end +end diff --git a/ruby/spec/support/runner_helper.rb b/ruby/spec/support/runner_helper.rb new file mode 100644 index 00000000..eebbbfc7 --- /dev/null +++ b/ruby/spec/support/runner_helper.rb @@ -0,0 +1,63 @@ +module RunnerHelper + + include Cucumber::Core + + def run_defined_feature + define_steps + actual_runtime.visitor = Cucumber::Formatter::Fanout.new([@formatter]) + receiver = Test::Runner.new(event_bus) + + event_bus.gherkin_source_read(gherkin_doc.uri, gherkin_doc.body) + + compile [gherkin_doc], receiver, filters, event_bus + + event_bus.test_run_finished + end + + def filters + # TODO: Remove duplication with runtime.rb#filters + [ + Cucumber::Filters::ActivateSteps.new( + Cucumber::StepMatchSearch.new(actual_runtime.support_code.registry.method(:step_matches), actual_runtime.configuration), + actual_runtime.configuration + ), + Cucumber::Filters::ApplyAfterStepHooks.new(actual_runtime.support_code), + Cucumber::Filters::ApplyBeforeHooks.new(actual_runtime.support_code), + Cucumber::Filters::ApplyAfterHooks.new(actual_runtime.support_code), + Cucumber::Filters::ApplyAroundHooks.new(actual_runtime.support_code), + Cucumber::Filters::BroadcastTestRunStartedEvent.new(actual_runtime.configuration), + Cucumber::Filters::BroadcastTestCaseReadyEvent.new(actual_runtime.configuration), + Cucumber::Filters::PrepareWorld.new(actual_runtime) + ] + end + + def gherkin_doc + Cucumber::Core::Gherkin::Document.new(self.class.feature_filename, gherkin) + end + + def gherkin + self.class.feature_content || raise('No feature content defined!') + end + + def actual_runtime + @actual_runtime ||= Cucumber::Runtime.new(options) + end + + def event_bus + actual_runtime.configuration.event_bus + end + + def define_steps + step_defs = self.class.step_defs + + return unless step_defs + + dsl = Object.new + dsl.extend Cucumber::Glue::Dsl + dsl.instance_exec(&step_defs) + end + + def options + {} + end +end From 36e2a52a1a0c3dac77361c37469c487055e8c90f Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 18:48:18 +0100 Subject: [PATCH 07/55] Alter error class for new API --- ruby/spec/cucumber/query/hook_by_test_step_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/spec/cucumber/query/hook_by_test_step_spec.rb b/ruby/spec/cucumber/query/hook_by_test_step_spec.rb index 83c44b0b..24bb16ce 100644 --- a/ruby/spec/cucumber/query/hook_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/hook_by_test_step_spec.rb @@ -61,7 +61,7 @@ test_step = double allow(test_step).to receive(:id).and_return('whatever-id') - expect { @formatter.hook_id(test_step) }.to raise_error(Cucumber::Formatter::TestStepUnknownError) + expect { @formatter.hook_id(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) end end end From 8db670bb78119a42ae2157e486fd77c8ead9a4f9 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 18:50:45 +0100 Subject: [PATCH 08/55] Alter all other tests to pass --- ruby/spec/cucumber/query/pickle_by_test_spec.rb | 6 +----- .../query/pickle_step_by_test_step_spec.rb | 8 ++------ .../query/step_definitions_by_test_step_spec.rb | 10 +++------- .../query/test_case_started_by_test_case_spec.rb | 15 +++++---------- 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/ruby/spec/cucumber/query/pickle_by_test_spec.rb b/ruby/spec/cucumber/query/pickle_by_test_spec.rb index 5f790eaf..6ccce6e5 100644 --- a/ruby/spec/cucumber/query/pickle_by_test_spec.rb +++ b/ruby/spec/cucumber/query/pickle_by_test_spec.rb @@ -1,12 +1,8 @@ # frozen_string_literal: true -require 'cucumber/formatter/spec_helper' require 'cucumber/query/pickle_by_test' describe Cucumber::Query::PickleByTest do - extend Cucumber::Formatter::SpecHelperDsl - include Cucumber::Formatter::SpecHelper - before do Cucumber::Term::ANSIColor.coloring = false @test_cases = [] @@ -49,7 +45,7 @@ test_case = double allow(test_case).to receive(:id).and_return('whatever-id') - expect { @formatter.pickle_id(test_case) }.to raise_error(Cucumber::Formatter::TestCaseUnknownError) + expect { @formatter.pickle_id(test_case) }.to raise_error(Cucumber::Query::TestCaseUnknownError) end end end diff --git a/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb b/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb index 2a7415a4..608fabfa 100644 --- a/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb @@ -1,12 +1,8 @@ # frozen_string_literal: true -require 'cucumber/formatter/spec_helper' -require 'cucumber/formatter/query/pickle_step_by_test_step' +require 'cucumber/query/pickle_step_by_test_step' describe Cucumber::Query::PickleStepByTestStep do - extend Cucumber::Formatter::SpecHelperDsl - include Cucumber::Formatter::SpecHelper - before do Cucumber::Term::ANSIColor.coloring = false @test_cases = [] @@ -54,7 +50,7 @@ test_step = double allow(test_step).to receive(:id).and_return('whatever-id') - expect { @formatter.pickle_step_id(test_step) }.to raise_error(Cucumber::Formatter::TestStepUnknownError) + expect { @formatter.pickle_step_id(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) end end end diff --git a/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb b/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb index 5b7ab1fd..23c55b7c 100644 --- a/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb @@ -1,12 +1,8 @@ # frozen_string_literal: true -require 'cucumber/formatter/spec_helper' -require 'cucumber/formatter/query/step_definitions_by_test_step' +require 'cucumber/query/step_definitions_by_test_step' describe Cucumber::Query::StepDefinitionsByTestStep do - extend Cucumber::Formatter::SpecHelperDsl - include Cucumber::Formatter::SpecHelper - before do Cucumber::Term::ANSIColor.coloring = false @test_cases = [] @@ -99,7 +95,7 @@ test_step = double allow(test_step).to receive(:id).and_return('whatever-id') - expect { @formatter.step_definition_ids(test_step) }.to raise_error(Cucumber::Formatter::TestStepUnknownError) + expect { @formatter.step_definition_ids(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) end end end @@ -155,7 +151,7 @@ test_step = double allow(test_step).to receive(:id).and_return('whatever-id') - expect { @formatter.step_match_arguments(test_step) }.to raise_error(Cucumber::Formatter::TestStepUnknownError) + expect { @formatter.step_match_arguments(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) end end end diff --git a/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb b/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb index 7ed82b48..ed85e51a 100644 --- a/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb +++ b/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb @@ -1,13 +1,8 @@ # frozen_string_literal: true -require 'cucumber/formatter/spec_helper' -require 'cucumber/formatter/query/test_case_started_by_test_case' - +require 'cucumber/query/test_case_started_by_test_case' describe Cucumber::Query::TestCaseStartedByTestCase do - extend Cucumber::Formatter::SpecHelperDsl - include Cucumber::Formatter::SpecHelper - before(:each) do Cucumber::Term::ANSIColor.coloring = false @@ -23,8 +18,8 @@ end describe '#attempt_by_test_case' do - it 'raises an exception when the TestCase is unknown' do - expect { @formatter.attempt_by_test_case(unknown_test_case) }.to raise_exception(TestCaseUnknownError) + it 'raises an error when the TestCase is unknown' do + expect { @formatter.attempt_by_test_case(unknown_test_case) }.to raise_error(Cucumber::Query::TestCaseUnknownError) end context 'when the test case has been declared' do @@ -50,8 +45,8 @@ end describe '#test_case_started_id_by_test_case' do - it 'raises an exception when the TestCase is unknown' do - expect { @formatter.test_case_started_id_by_test_case(unknown_test_case) }.to raise_exception(TestCaseUnknownError) + it 'raises an error when the TestCase is unknown' do + expect { @formatter.test_case_started_id_by_test_case(unknown_test_case) }.to raise_error(Cucumber::Query::TestCaseUnknownError) end context 'when the test case has been declared' do From e3637f1214d230836038df745212ef56a4af73c6 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 18:51:26 +0100 Subject: [PATCH 09/55] Auto-gen rubocop config; --- ruby/.rubocop.yml | 2 + ruby/.rubocop_todo.yml | 113 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 ruby/.rubocop_todo.yml diff --git a/ruby/.rubocop.yml b/ruby/.rubocop.yml index b9132120..4c19d3f7 100644 --- a/ruby/.rubocop.yml +++ b/ruby/.rubocop.yml @@ -1,3 +1,5 @@ +inherit_from: .rubocop_todo.yml + plugins: - rubocop-performance - rubocop-rspec diff --git a/ruby/.rubocop_todo.yml b/ruby/.rubocop_todo.yml new file mode 100644 index 00000000..8c6ecb8c --- /dev/null +++ b/ruby/.rubocop_todo.yml @@ -0,0 +1,113 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2025-09-05 17:51:10 UTC using RuboCop version 1.80.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 +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines +Layout/EmptyLinesAroundModuleBody: + Exclude: + - 'spec/support/runner_helper.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Layout/LeadingEmptyLines: + Exclude: + - 'spec/support/gherkin_helper.rb' + +# Offense count: 8 +# Configuration parameters: AllowComments, AllowEmptyLambdas. +Lint/EmptyBlock: + Exclude: + - 'spec/cucumber/query/hook_by_test_step_spec.rb' + - 'spec/cucumber/query/step_definitions_by_test_step_spec.rb' + +# Offense count: 2 +# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. +Metrics/AbcSize: + Max: 30 + +# Offense count: 1 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. +Metrics/MethodLength: + Max: 13 + +# Offense count: 8 +Performance/MethodObjectAsBlock: + Exclude: + - 'lib/cucumber/query/hook_by_test_step.rb' + - 'lib/cucumber/query/pickle_by_test.rb' + - 'lib/cucumber/query/pickle_step_by_test_step.rb' + - 'lib/cucumber/query/step_definitions_by_test_step.rb' + - 'lib/cucumber/query/test_case_started_by_test_case.rb' + +# Offense count: 1 +# Configuration parameters: Prefixes, AllowedPatterns. +# Prefixes: when, with, without +RSpec/ContextWording: + Exclude: + - 'spec/cucumber/query/hook_by_test_step_spec.rb' + +# Offense count: 2 +# Configuration parameters: CountAsOne. +RSpec/ExampleLength: + Max: 7 + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: implicit, each, example +RSpec/HookArgument: + Exclude: + - 'spec/cucumber/query/test_case_started_by_test_case_spec.rb' + +# Offense count: 87 +# Configuration parameters: AssignmentOnly. +RSpec/InstanceVariable: + Exclude: + - 'spec/cucumber/query/hook_by_test_step_spec.rb' + - 'spec/cucumber/query/pickle_by_test_spec.rb' + - 'spec/cucumber/query/pickle_step_by_test_step_spec.rb' + - 'spec/cucumber/query/step_definitions_by_test_step_spec.rb' + - 'spec/cucumber/query/test_case_started_by_test_case_spec.rb' + +# Offense count: 3 +RSpec/MultipleExpectations: + Max: 3 + +# Offense count: 12 +# Configuration parameters: AllowedGroups. +RSpec/NestedGroups: + Max: 5 + +# Offense count: 6 +# Configuration parameters: AllowedConstants. +Style/Documentation: + Exclude: + - 'lib/cucumber/query/hook_by_test_step.rb' + - 'lib/cucumber/query/pickle_by_test.rb' + - 'lib/cucumber/query/pickle_step_by_test_step.rb' + - 'lib/cucumber/query/step_definitions_by_test_step.rb' + - 'lib/cucumber/query/test_case_started_by_test_case.rb' + - 'lib/cucumber/query/test_run_started.rb' + +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: always, always_true, never +Style/FrozenStringLiteralComment: + Exclude: + - 'spec/support/gherkin_helper.rb' + - 'spec/support/runner_helper.rb' + +# Offense count: 8 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. +# URISchemes: http, https +Layout/LineLength: + Max: 196 From b0963394dce0dd029154fd75b73d036c5ef5b6c7 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 18:54:36 +0100 Subject: [PATCH 10/55] Add note about duplication of code --- ruby/spec/support/gherkin_helper.rb | 1 + ruby/spec/support/runner_helper.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ruby/spec/support/gherkin_helper.rb b/ruby/spec/support/gherkin_helper.rb index 9303804a..a8f83c42 100644 --- a/ruby/spec/support/gherkin_helper.rb +++ b/ruby/spec/support/gherkin_helper.rb @@ -1,3 +1,4 @@ +# TODO: This file is adapted / duplicated from SpecHelperDSL and SpecHelper in cucumber-ruby module GherkinHelper attr_reader :feature_content, :step_defs, :feature_filename diff --git a/ruby/spec/support/runner_helper.rb b/ruby/spec/support/runner_helper.rb index eebbbfc7..8cb9fc03 100644 --- a/ruby/spec/support/runner_helper.rb +++ b/ruby/spec/support/runner_helper.rb @@ -1,3 +1,5 @@ +# TODO: This file is adapted / duplicated from SpecHelperDSL and SpecHelper in cucumber-ruby + module RunnerHelper include Cucumber::Core @@ -15,7 +17,6 @@ def run_defined_feature end def filters - # TODO: Remove duplication with runtime.rb#filters [ Cucumber::Filters::ActivateSteps.new( Cucumber::StepMatchSearch.new(actual_runtime.support_code.registry.method(:step_matches), actual_runtime.configuration), From c0637d893a6a3facb8ae37363e489093677b429f Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 19:01:10 +0100 Subject: [PATCH 11/55] Minor linting and starting to unpick redundant pieces of spec support --- ruby/spec/cucumber/query/hook_by_test_step_spec.rb | 8 +++----- ruby/spec/cucumber/query/pickle_by_test_spec.rb | 8 +++----- .../cucumber/query/pickle_step_by_test_step_spec.rb | 8 +++----- .../query/step_definitions_by_test_step_spec.rb | 8 +++----- .../query/test_case_started_by_test_case_spec.rb | 4 +--- ruby/spec/support/gherkin_helper.rb | 4 ++-- ruby/spec/support/runner_helper.rb | 12 ++++-------- 7 files changed, 19 insertions(+), 33 deletions(-) diff --git a/ruby/spec/cucumber/query/hook_by_test_step_spec.rb b/ruby/spec/cucumber/query/hook_by_test_step_spec.rb index 24bb16ce..79a8d748 100644 --- a/ruby/spec/cucumber/query/hook_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/hook_by_test_step_spec.rb @@ -6,17 +6,15 @@ before do Cucumber::Term::ANSIColor.coloring = false @test_cases = [] - - @out = StringIO.new - @config = actual_runtime.configuration.with_options(out_stream: @out) + @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) @formatter = described_class.new(@config) - @config.on_event :test_case_started do |event| + @config.on_event(:test_case_started) do |event| @test_cases << event.test_case end @hook_ids = [] - @config.on_event :envelope do |event| + @config.on_event(:envelope) do |event| next unless event.envelope.hook @hook_ids << event.envelope.hook.id diff --git a/ruby/spec/cucumber/query/pickle_by_test_spec.rb b/ruby/spec/cucumber/query/pickle_by_test_spec.rb index 6ccce6e5..b59b8295 100644 --- a/ruby/spec/cucumber/query/pickle_by_test_spec.rb +++ b/ruby/spec/cucumber/query/pickle_by_test_spec.rb @@ -6,17 +6,15 @@ before do Cucumber::Term::ANSIColor.coloring = false @test_cases = [] - - @out = StringIO.new - @config = actual_runtime.configuration.with_options(out_stream: @out) + @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) @formatter = described_class.new(@config) - @config.on_event :test_case_created do |event| + @config.on_event(:test_case_created) do |event| @test_cases << event.test_case end @pickle_ids = [] - @config.on_event :envelope do |event| + @config.on_event(:envelope) do |event| next unless event.envelope.pickle @pickle_ids << event.envelope.pickle.id diff --git a/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb b/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb index 608fabfa..5b2af0b5 100644 --- a/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb @@ -6,17 +6,15 @@ before do Cucumber::Term::ANSIColor.coloring = false @test_cases = [] - - @out = StringIO.new - @config = actual_runtime.configuration.with_options(out_stream: @out) + @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) @formatter = described_class.new(@config) - @config.on_event :test_case_created do |event| + @config.on_event(:test_case_created) do |event| @test_cases << event.test_case end @pickle_step_ids = [] - @config.on_event :envelope do |event| + @config.on_event(:envelope) do |event| next unless event.envelope.pickle event.envelope.pickle.steps.each do |step| diff --git a/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb b/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb index 23c55b7c..340c2868 100644 --- a/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb @@ -6,17 +6,15 @@ before do Cucumber::Term::ANSIColor.coloring = false @test_cases = [] - - @out = StringIO.new - @config = actual_runtime.configuration.with_options(out_stream: @out) + @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) @formatter = described_class.new(@config) - @config.on_event :test_case_created do |event| + @config.on_event(:test_case_created) do |event| @test_cases << event.test_case end @step_definition_ids = [] - @config.on_event :envelope do |event| + @config.on_event(:envelope) do |event| next unless event.envelope.step_definition @step_definition_ids << event.envelope.step_definition.id diff --git a/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb b/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb index ed85e51a..29785496 100644 --- a/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb +++ b/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb @@ -5,9 +5,7 @@ describe Cucumber::Query::TestCaseStartedByTestCase do before(:each) do Cucumber::Term::ANSIColor.coloring = false - - @out = StringIO.new - @config = actual_runtime.configuration.with_options(out_stream: @out) + @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) @formatter = described_class.new(@config) end diff --git a/ruby/spec/support/gherkin_helper.rb b/ruby/spec/support/gherkin_helper.rb index a8f83c42..29bc3e6c 100644 --- a/ruby/spec/support/gherkin_helper.rb +++ b/ruby/spec/support/gherkin_helper.rb @@ -1,7 +1,7 @@ # TODO: This file is adapted / duplicated from SpecHelperDSL and SpecHelper in cucumber-ruby module GherkinHelper - attr_reader :feature_content, :step_defs, :feature_filename + attr_reader :feature_content, :step_definitions, :feature_filename def define_feature(string, feature_file = 'spec.feature') @feature_content = string @@ -9,6 +9,6 @@ def define_feature(string, feature_file = 'spec.feature') end def define_steps(&block) - @step_defs = block + @step_definitions = block end end diff --git a/ruby/spec/support/runner_helper.rb b/ruby/spec/support/runner_helper.rb index 8cb9fc03..44d5f67b 100644 --- a/ruby/spec/support/runner_helper.rb +++ b/ruby/spec/support/runner_helper.rb @@ -41,7 +41,7 @@ def gherkin end def actual_runtime - @actual_runtime ||= Cucumber::Runtime.new(options) + @actual_runtime ||= Cucumber::Runtime.new({}) end def event_bus @@ -49,16 +49,12 @@ def event_bus end def define_steps - step_defs = self.class.step_defs + step_definitions = self.class.step_definitions - return unless step_defs + return unless step_definitions dsl = Object.new dsl.extend Cucumber::Glue::Dsl - dsl.instance_exec(&step_defs) - end - - def options - {} + dsl.instance_exec(&step_definitions) end end From ef565006491623902c47484e2f871a8cf0877e72 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 19:06:50 +0100 Subject: [PATCH 12/55] Lint / DRY up tests --- .../cucumber/query/hook_by_test_step_spec.rb | 15 ++++++--------- .../spec/cucumber/query/pickle_by_test_spec.rb | 1 - .../query/pickle_step_by_test_step_spec.rb | 6 +++--- .../step_definitions_by_test_step_spec.rb | 18 +++++++----------- .../test_case_started_by_test_case_spec.rb | 17 +++++++---------- 5 files changed, 23 insertions(+), 34 deletions(-) diff --git a/ruby/spec/cucumber/query/hook_by_test_step_spec.rb b/ruby/spec/cucumber/query/hook_by_test_step_spec.rb index 79a8d748..2b060412 100644 --- a/ruby/spec/cucumber/query/hook_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/hook_by_test_step_spec.rb @@ -4,7 +4,6 @@ describe Cucumber::Query::HookByTestStep do before do - Cucumber::Term::ANSIColor.coloring = false @test_cases = [] @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) @formatter = described_class.new(@config) @@ -21,6 +20,8 @@ end end + let(:first_test_case) { @test_cases.first } + context 'given a single feature' do before do run_defined_feature @@ -41,18 +42,15 @@ end it 'provides the ID of the Before Hook used to generate the Test::Step' do - test_case = @test_cases.first - expect(@formatter.hook_id(test_case.test_steps.first)).to eq(@hook_ids.first) + expect(@formatter.hook_id(first_test_case.test_steps.first)).to eq(@hook_ids.first) end it 'provides the ID of the After Hook used to generate the Test::Step' do - test_case = @test_cases.first - expect(@formatter.hook_id(test_case.test_steps.last)).to eq(@hook_ids.last) + expect(@formatter.hook_id(first_test_case.test_steps.last)).to eq(@hook_ids.last) end it 'returns nil if the step was not generated from a hook' do - test_case = @test_cases.first - expect(@formatter.hook_id(test_case.test_steps[1])).to be_nil + expect(@formatter.hook_id(first_test_case.test_steps[1])).to be_nil end it 'raises an exception when the test_step is unknown' do @@ -78,8 +76,7 @@ end it 'provides the ID of the AfterStepHook used to generate the Test::Step' do - test_case = @test_cases.first - expect(@formatter.hook_id(test_case.test_steps.last)).to eq(@hook_ids.first) + expect(@formatter.hook_id(first_test_case.test_steps.last)).to eq(@hook_ids.first) end end end diff --git a/ruby/spec/cucumber/query/pickle_by_test_spec.rb b/ruby/spec/cucumber/query/pickle_by_test_spec.rb index b59b8295..65d92217 100644 --- a/ruby/spec/cucumber/query/pickle_by_test_spec.rb +++ b/ruby/spec/cucumber/query/pickle_by_test_spec.rb @@ -4,7 +4,6 @@ describe Cucumber::Query::PickleByTest do before do - Cucumber::Term::ANSIColor.coloring = false @test_cases = [] @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) @formatter = described_class.new(@config) diff --git a/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb b/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb index 5b2af0b5..0f5b1ea3 100644 --- a/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb @@ -4,7 +4,6 @@ describe Cucumber::Query::PickleStepByTestStep do before do - Cucumber::Term::ANSIColor.coloring = false @test_cases = [] @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) @formatter = described_class.new(@config) @@ -23,6 +22,8 @@ end end + let(:first_test_case) { @test_cases.first } + describe 'given a single feature' do before do run_defined_feature @@ -38,8 +39,7 @@ FEATURE it 'provides the ID of the PickleStep used to generate the Test::Step' do - test_case = @test_cases.first - test_step = test_case.test_steps.first + test_step = first_test_case.test_steps.first expect(@formatter.pickle_step_id(test_step)).to eq(@pickle_step_ids.first) end diff --git a/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb b/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb index 340c2868..40d9c946 100644 --- a/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb @@ -4,7 +4,6 @@ describe Cucumber::Query::StepDefinitionsByTestStep do before do - Cucumber::Term::ANSIColor.coloring = false @test_cases = [] @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) @formatter = described_class.new(@config) @@ -21,6 +20,8 @@ end end + let(:first_test_case) { @test_cases.first } + describe 'given a single feature' do before do run_defined_feature @@ -40,8 +41,7 @@ FEATURE it 'provides the ID of the StepDefinition that matches Test::Step' do - test_case = @test_cases.first - test_step = test_case.test_steps.first + test_step = first_test_case.test_steps.first expect(@formatter.step_definition_ids(test_step)).to eq([@step_definition_ids.first]) end @@ -57,8 +57,7 @@ FEATURE it 'returns an empty array' do - test_case = @test_cases.first - test_step = test_case.test_steps.first + test_step = first_test_case.test_steps.first expect(@formatter.step_definition_ids(test_step)).to be_empty end @@ -78,8 +77,7 @@ FEATURE it 'returns an empty array as the step is not activated' do - test_case = @test_cases.first - test_step = test_case.test_steps.first + test_step = first_test_case.test_steps.first expect(@formatter.step_definition_ids(test_step)).to be_empty end @@ -112,8 +110,7 @@ FEATURE it 'returns an empty list' do - test_case = @test_cases.first - test_step = test_case.test_steps.first + test_step = first_test_case.test_steps.first expect(@formatter.step_match_arguments(test_step)).to be_empty end @@ -132,8 +129,7 @@ FEATURE it 'returns an empty list' do - test_case = @test_cases.first - test_step = test_case.test_steps.first + test_step = first_test_case.test_steps.first matches = @formatter.step_match_arguments(test_step) expect(matches.count).to eq(1) diff --git a/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb b/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb index 29785496..38ed3e97 100644 --- a/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb +++ b/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb @@ -3,8 +3,7 @@ require 'cucumber/query/test_case_started_by_test_case' describe Cucumber::Query::TestCaseStartedByTestCase do - before(:each) do - Cucumber::Term::ANSIColor.coloring = false + before do @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) @formatter = described_class.new(@config) end @@ -24,8 +23,7 @@ before do @test_case = double allow(@test_case).to receive(:id).and_return('some-valid-id') - - @config.notify :test_case_created, @test_case, nil + @config.notify(:test_case_created, @test_case, nil) end it 'returns 0 if no test_case_started event has been fired' do @@ -33,10 +31,10 @@ end it 'increments the attemp on every test_case_started event' do - @config.notify :test_case_started, @test_case + @config.notify(:test_case_started, @test_case) expect(@formatter.attempt_by_test_case(@test_case)).to eq(1) - @config.notify :test_case_started, @test_case + @config.notify(:test_case_started, @test_case) expect(@formatter.attempt_by_test_case(@test_case)).to eq(2) end end @@ -51,8 +49,7 @@ before do @test_case = double allow(@test_case).to receive(:id).and_return('some-valid-id') - - @config.notify :test_case_created, @test_case, nil + @config.notify(:test_case_created, @test_case, nil) end it 'returns nil if no test_case_started event has been fired' do @@ -60,12 +57,12 @@ end it 'gives a new id when a test_case_started event is fired' do - @config.notify :test_case_started, @test_case + @config.notify(:test_case_started, @test_case) first_attempt_id = @formatter.test_case_started_id_by_test_case(@test_case) expect(first_attempt_id).not_to be_nil - @config.notify :test_case_started, @test_case + @config.notify(:test_case_started, @test_case) second_attempt_id = @formatter.test_case_started_id_by_test_case(@test_case) expect(second_attempt_id).not_to be_nil From f0b2552a27747344281a19084a5fdce54a0dcf01 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 19:19:18 +0100 Subject: [PATCH 13/55] Linting for tests and 1 bug fix where test case started was firing prematurely --- ruby/lib/cucumber/query/hook_by_test_step.rb | 4 +-- ruby/lib/cucumber/query/pickle_by_test.rb | 3 +- .../query/pickle_step_by_test_step.rb | 3 +- .../query/step_definitions_by_test_step.rb | 4 +-- .../query/test_case_started_by_test_case.rb | 6 ++-- .../cucumber/query/hook_by_test_step_spec.rb | 20 +++++------ .../cucumber/query/pickle_by_test_spec.rb | 15 ++++---- .../query/pickle_step_by_test_step_spec.rb | 14 ++++---- .../step_definitions_by_test_step_spec.rb | 26 +++++++------- .../test_case_started_by_test_case_spec.rb | 35 +++++++++---------- 10 files changed, 65 insertions(+), 65 deletions(-) diff --git a/ruby/lib/cucumber/query/hook_by_test_step.rb b/ruby/lib/cucumber/query/hook_by_test_step.rb index 4d495ffc..c1a1e4f8 100644 --- a/ruby/lib/cucumber/query/hook_by_test_step.rb +++ b/ruby/lib/cucumber/query/hook_by_test_step.rb @@ -8,8 +8,8 @@ class HookByTestStep def initialize(config) @hook_id_by_test_step_id = {} - config.on_event :test_step_created, &method(:on_test_step_created) - config.on_event :hook_test_step_created, &method(:on_hook_test_step_created) + config.on_event(:test_step_created, &method(:on_test_step_created)) + config.on_event(:hook_test_step_created, &method(:on_hook_test_step_created)) end def hook_id(test_step) diff --git a/ruby/lib/cucumber/query/pickle_by_test.rb b/ruby/lib/cucumber/query/pickle_by_test.rb index 73dcbb7e..c8668b28 100644 --- a/ruby/lib/cucumber/query/pickle_by_test.rb +++ b/ruby/lib/cucumber/query/pickle_by_test.rb @@ -7,7 +7,8 @@ module Query class PickleByTest def initialize(config) @pickle_id_by_test_case_id = {} - config.on_event :test_case_created, &method(:on_test_case_created) + + config.on_event(:test_case_created, &method(:on_test_case_created)) end def pickle_id(test_case) diff --git a/ruby/lib/cucumber/query/pickle_step_by_test_step.rb b/ruby/lib/cucumber/query/pickle_step_by_test_step.rb index 42e788b5..cac53537 100644 --- a/ruby/lib/cucumber/query/pickle_step_by_test_step.rb +++ b/ruby/lib/cucumber/query/pickle_step_by_test_step.rb @@ -7,7 +7,8 @@ module Query class PickleStepByTestStep def initialize(config) @pickle_id_step_by_test_step_id = {} - config.on_event :test_step_created, &method(:on_test_step_created) + + config.on_event(:test_step_created, &method(:on_test_step_created)) end def pickle_step_id(test_step) diff --git a/ruby/lib/cucumber/query/step_definitions_by_test_step.rb b/ruby/lib/cucumber/query/step_definitions_by_test_step.rb index cc524afc..7894c24f 100644 --- a/ruby/lib/cucumber/query/step_definitions_by_test_step.rb +++ b/ruby/lib/cucumber/query/step_definitions_by_test_step.rb @@ -9,8 +9,8 @@ def initialize(config) @step_definition_ids_by_test_step_id = {} @step_match_arguments_by_test_step_id = {} - config.on_event :test_step_created, &method(:on_test_step_created) - config.on_event :step_activated, &method(:on_step_activated) + config.on_event(:test_step_created, &method(:on_test_step_created)) + config.on_event(:step_activated, &method(:on_step_activated)) end def step_definition_ids(test_step) diff --git a/ruby/lib/cucumber/query/test_case_started_by_test_case.rb b/ruby/lib/cucumber/query/test_case_started_by_test_case.rb index 5584cf68..08005fec 100644 --- a/ruby/lib/cucumber/query/test_case_started_by_test_case.rb +++ b/ruby/lib/cucumber/query/test_case_started_by_test_case.rb @@ -7,11 +7,11 @@ module Query class TestCaseStartedByTestCase def initialize(config) @config = config - config.on_event :test_case_created, &method(:on_test_case_created) - config.on_event :test_case_started, &method(:on_test_case_started) - @attempts_by_test_case_id = {} @test_case_started_id_by_test_case_id = {} + + config.on_event(:test_case_created, &method(:on_test_case_created)) + config.on_event(:test_case_started, &method(:on_test_case_started)) end def attempt_by_test_case(test_case) diff --git a/ruby/spec/cucumber/query/hook_by_test_step_spec.rb b/ruby/spec/cucumber/query/hook_by_test_step_spec.rb index 2b060412..85d1a113 100644 --- a/ruby/spec/cucumber/query/hook_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/hook_by_test_step_spec.rb @@ -5,22 +5,22 @@ describe Cucumber::Query::HookByTestStep do before do @test_cases = [] - @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) - @formatter = described_class.new(@config) + @hook_ids = [] - @config.on_event(:test_case_started) do |event| + config.on_event(:test_case_started) do |event| @test_cases << event.test_case end - @hook_ids = [] - @config.on_event(:envelope) do |event| + config.on_event(:envelope) do |event| next unless event.envelope.hook @hook_ids << event.envelope.hook.id end end + let(:config) { actual_runtime.configuration.with_options(out_stream: StringIO.new) } let(:first_test_case) { @test_cases.first } + let(:formatter) { described_class.new(config) } context 'given a single feature' do before do @@ -42,22 +42,22 @@ end it 'provides the ID of the Before Hook used to generate the Test::Step' do - expect(@formatter.hook_id(first_test_case.test_steps.first)).to eq(@hook_ids.first) + expect(formatter.hook_id(first_test_case.test_steps.first)).to eq(@hook_ids.first) end it 'provides the ID of the After Hook used to generate the Test::Step' do - expect(@formatter.hook_id(first_test_case.test_steps.last)).to eq(@hook_ids.last) + expect(formatter.hook_id(first_test_case.test_steps.last)).to eq(@hook_ids.last) end it 'returns nil if the step was not generated from a hook' do - expect(@formatter.hook_id(first_test_case.test_steps[1])).to be_nil + expect(formatter.hook_id(first_test_case.test_steps[1])).to be_nil end it 'raises an exception when the test_step is unknown' do test_step = double allow(test_step).to receive(:id).and_return('whatever-id') - expect { @formatter.hook_id(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) + expect { formatter.hook_id(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) end end end @@ -76,7 +76,7 @@ end it 'provides the ID of the AfterStepHook used to generate the Test::Step' do - expect(@formatter.hook_id(first_test_case.test_steps.last)).to eq(@hook_ids.first) + expect(formatter.hook_id(first_test_case.test_steps.last)).to eq(@hook_ids.first) end end end diff --git a/ruby/spec/cucumber/query/pickle_by_test_spec.rb b/ruby/spec/cucumber/query/pickle_by_test_spec.rb index 65d92217..f570949a 100644 --- a/ruby/spec/cucumber/query/pickle_by_test_spec.rb +++ b/ruby/spec/cucumber/query/pickle_by_test_spec.rb @@ -5,21 +5,22 @@ describe Cucumber::Query::PickleByTest do before do @test_cases = [] - @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) - @formatter = described_class.new(@config) + @pickle_ids = [] - @config.on_event(:test_case_created) do |event| + config.on_event(:test_case_created) do |event| @test_cases << event.test_case end - @pickle_ids = [] - @config.on_event(:envelope) do |event| + config.on_event(:envelope) do |event| next unless event.envelope.pickle @pickle_ids << event.envelope.pickle.id end end + let(:config) { actual_runtime.configuration.with_options(out_stream: StringIO.new) } + let(:formatter) { described_class.new(config) } + describe 'given a single feature' do before do run_defined_feature @@ -35,14 +36,14 @@ FEATURE it 'provides the ID of the pickle used to generate the Test::Case' do - expect(@formatter.pickle_id(@test_cases.first)).to eq(@pickle_ids.first) + expect(formatter.pickle_id(@test_cases.first)).to eq(@pickle_ids.first) end it 'raises an error when the Test::Case is unknown' do test_case = double allow(test_case).to receive(:id).and_return('whatever-id') - expect { @formatter.pickle_id(test_case) }.to raise_error(Cucumber::Query::TestCaseUnknownError) + expect { formatter.pickle_id(test_case) }.to raise_error(Cucumber::Query::TestCaseUnknownError) end end end diff --git a/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb b/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb index 0f5b1ea3..d1e0a483 100644 --- a/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb @@ -5,15 +5,13 @@ describe Cucumber::Query::PickleStepByTestStep do before do @test_cases = [] - @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) - @formatter = described_class.new(@config) + @pickle_step_ids = [] - @config.on_event(:test_case_created) do |event| + config.on_event(:test_case_created) do |event| @test_cases << event.test_case end - @pickle_step_ids = [] - @config.on_event(:envelope) do |event| + config.on_event(:envelope) do |event| next unless event.envelope.pickle event.envelope.pickle.steps.each do |step| @@ -22,7 +20,9 @@ end end + let(:config) { actual_runtime.configuration.with_options(out_stream: StringIO.new) } let(:first_test_case) { @test_cases.first } + let(:formatter) { described_class.new(config) } describe 'given a single feature' do before do @@ -41,14 +41,14 @@ it 'provides the ID of the PickleStep used to generate the Test::Step' do test_step = first_test_case.test_steps.first - expect(@formatter.pickle_step_id(test_step)).to eq(@pickle_step_ids.first) + expect(formatter.pickle_step_id(test_step)).to eq(@pickle_step_ids.first) end it 'raises an exception when the test_step is unknown' do test_step = double allow(test_step).to receive(:id).and_return('whatever-id') - expect { @formatter.pickle_step_id(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) + expect { formatter.pickle_step_id(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) end end end diff --git a/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb b/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb index 40d9c946..a89e3940 100644 --- a/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb @@ -5,22 +5,22 @@ describe Cucumber::Query::StepDefinitionsByTestStep do before do @test_cases = [] - @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) - @formatter = described_class.new(@config) + @step_definition_ids = [] - @config.on_event(:test_case_created) do |event| + config.on_event(:test_case_created) do |event| @test_cases << event.test_case end - - @step_definition_ids = [] - @config.on_event(:envelope) do |event| + + config.on_event(:envelope) do |event| next unless event.envelope.step_definition @step_definition_ids << event.envelope.step_definition.id end end + let(:config) { actual_runtime.configuration.with_options(out_stream: StringIO.new) } let(:first_test_case) { @test_cases.first } + let(:formatter) { described_class.new(config) } describe 'given a single feature' do before do @@ -43,7 +43,7 @@ it 'provides the ID of the StepDefinition that matches Test::Step' do test_step = first_test_case.test_steps.first - expect(@formatter.step_definition_ids(test_step)).to eq([@step_definition_ids.first]) + expect(formatter.step_definition_ids(test_step)).to eq([@step_definition_ids.first]) end end @@ -59,7 +59,7 @@ it 'returns an empty array' do test_step = first_test_case.test_steps.first - expect(@formatter.step_definition_ids(test_step)).to be_empty + expect(formatter.step_definition_ids(test_step)).to be_empty end end @@ -79,7 +79,7 @@ it 'returns an empty array as the step is not activated' do test_step = first_test_case.test_steps.first - expect(@formatter.step_definition_ids(test_step)).to be_empty + expect(formatter.step_definition_ids(test_step)).to be_empty end end end @@ -91,7 +91,7 @@ test_step = double allow(test_step).to receive(:id).and_return('whatever-id') - expect { @formatter.step_definition_ids(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) + expect { formatter.step_definition_ids(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) end end end @@ -112,7 +112,7 @@ it 'returns an empty list' do test_step = first_test_case.test_steps.first - expect(@formatter.step_match_arguments(test_step)).to be_empty + expect(formatter.step_match_arguments(test_step)).to be_empty end end @@ -130,7 +130,7 @@ it 'returns an empty list' do test_step = first_test_case.test_steps.first - matches = @formatter.step_match_arguments(test_step) + matches = formatter.step_match_arguments(test_step) expect(matches.count).to eq(1) expect(matches.first).to be_a(Cucumber::CucumberExpressions::Argument) @@ -145,7 +145,7 @@ test_step = double allow(test_step).to receive(:id).and_return('whatever-id') - expect { @formatter.step_match_arguments(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) + expect { formatter.step_match_arguments(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) end end end diff --git a/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb b/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb index 38ed3e97..0f943c6f 100644 --- a/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb +++ b/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb @@ -3,11 +3,8 @@ require 'cucumber/query/test_case_started_by_test_case' describe Cucumber::Query::TestCaseStartedByTestCase do - before do - @config = actual_runtime.configuration.with_options(out_stream: StringIO.new) - @formatter = described_class.new(@config) - end - + let(:config) { actual_runtime.configuration.with_options(out_stream: StringIO.new) } + let(:formatter) { described_class.new(config) } let(:unknown_test_case) do test_case = double allow(test_case).to receive(:id).and_return('whatever-id') @@ -16,54 +13,54 @@ describe '#attempt_by_test_case' do it 'raises an error when the TestCase is unknown' do - expect { @formatter.attempt_by_test_case(unknown_test_case) }.to raise_error(Cucumber::Query::TestCaseUnknownError) + expect { formatter.attempt_by_test_case(unknown_test_case) }.to raise_error(Cucumber::Query::TestCaseUnknownError) end context 'when the test case has been declared' do before do @test_case = double allow(@test_case).to receive(:id).and_return('some-valid-id') - @config.notify(:test_case_created, @test_case, nil) + config.notify(:test_case_created, @test_case, nil) end it 'returns 0 if no test_case_started event has been fired' do - expect(@formatter.attempt_by_test_case(@test_case)).to eq(0) + expect(formatter.attempt_by_test_case(@test_case)).to eq(0) end it 'increments the attemp on every test_case_started event' do - @config.notify(:test_case_started, @test_case) - expect(@formatter.attempt_by_test_case(@test_case)).to eq(1) + config.notify(:test_case_started, @test_case) + expect(formatter.attempt_by_test_case(@test_case)).to eq(1) - @config.notify(:test_case_started, @test_case) - expect(@formatter.attempt_by_test_case(@test_case)).to eq(2) + config.notify(:test_case_started, @test_case) + expect(formatter.attempt_by_test_case(@test_case)).to eq(2) end end end describe '#test_case_started_id_by_test_case' do it 'raises an error when the TestCase is unknown' do - expect { @formatter.test_case_started_id_by_test_case(unknown_test_case) }.to raise_error(Cucumber::Query::TestCaseUnknownError) + expect { formatter.test_case_started_id_by_test_case(unknown_test_case) }.to raise_error(Cucumber::Query::TestCaseUnknownError) end context 'when the test case has been declared' do before do @test_case = double allow(@test_case).to receive(:id).and_return('some-valid-id') - @config.notify(:test_case_created, @test_case, nil) + config.notify(:test_case_created, @test_case, nil) end it 'returns nil if no test_case_started event has been fired' do - expect(@formatter.test_case_started_id_by_test_case(@test_case)).to be_nil + expect(formatter.test_case_started_id_by_test_case(@test_case)).to be_nil end it 'gives a new id when a test_case_started event is fired' do - @config.notify(:test_case_started, @test_case) + config.notify(:test_case_started, @test_case) - first_attempt_id = @formatter.test_case_started_id_by_test_case(@test_case) + first_attempt_id = formatter.test_case_started_id_by_test_case(@test_case) expect(first_attempt_id).not_to be_nil - @config.notify(:test_case_started, @test_case) - second_attempt_id = @formatter.test_case_started_id_by_test_case(@test_case) + config.notify(:test_case_started, @test_case) + second_attempt_id = formatter.test_case_started_id_by_test_case(@test_case) expect(second_attempt_id).not_to be_nil expect(second_attempt_id).not_to eq(first_attempt_id) From c9003654ffdc0b3fbec591ee405bfe25d63ca12b Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 19:33:20 +0100 Subject: [PATCH 14/55] Add in legacy ns; --- ruby/lib/cucumber/query/errors.rb | 8 ---- ruby/lib/cucumber/query/hook_by_test_step.rb | 32 -------------- ruby/lib/cucumber/query/legacy/errors.rb | 10 +++++ .../query/legacy/hook_by_test_step.rb | 34 ++++++++++++++ .../cucumber/query/legacy/pickle_by_test.rb | 29 ++++++++++++ .../query/legacy/pickle_step_by_test_step.rb | 29 ++++++++++++ .../legacy/step_definitions_by_test_step.rb | 42 ++++++++++++++++++ .../legacy/test_case_started_by_test_case.rb | 44 +++++++++++++++++++ .../cucumber/query/legacy/test_run_started.rb | 19 ++++++++ ruby/lib/cucumber/query/pickle_by_test.rb | 27 ------------ .../query/pickle_step_by_test_step.rb | 27 ------------ .../query/step_definitions_by_test_step.rb | 40 ----------------- .../query/test_case_started_by_test_case.rb | 42 ------------------ ruby/lib/cucumber/query/test_run_started.rb | 17 ------- .../{ => legacy}/hook_by_test_step_spec.rb | 6 +-- .../query/{ => legacy}/pickle_by_test_spec.rb | 6 +-- .../pickle_step_by_test_step_spec.rb | 6 +-- .../step_definitions_by_test_step_spec.rb | 8 ++-- .../test_case_started_by_test_case_spec.rb | 8 ++-- 19 files changed, 224 insertions(+), 210 deletions(-) delete mode 100644 ruby/lib/cucumber/query/errors.rb delete mode 100644 ruby/lib/cucumber/query/hook_by_test_step.rb create mode 100644 ruby/lib/cucumber/query/legacy/errors.rb create mode 100644 ruby/lib/cucumber/query/legacy/hook_by_test_step.rb create mode 100644 ruby/lib/cucumber/query/legacy/pickle_by_test.rb create mode 100644 ruby/lib/cucumber/query/legacy/pickle_step_by_test_step.rb create mode 100644 ruby/lib/cucumber/query/legacy/step_definitions_by_test_step.rb create mode 100644 ruby/lib/cucumber/query/legacy/test_case_started_by_test_case.rb create mode 100644 ruby/lib/cucumber/query/legacy/test_run_started.rb delete mode 100644 ruby/lib/cucumber/query/pickle_by_test.rb delete mode 100644 ruby/lib/cucumber/query/pickle_step_by_test_step.rb delete mode 100644 ruby/lib/cucumber/query/step_definitions_by_test_step.rb delete mode 100644 ruby/lib/cucumber/query/test_case_started_by_test_case.rb delete mode 100644 ruby/lib/cucumber/query/test_run_started.rb rename ruby/spec/cucumber/query/{ => legacy}/hook_by_test_step_spec.rb (93%) rename ruby/spec/cucumber/query/{ => legacy}/pickle_by_test_spec.rb (89%) rename ruby/spec/cucumber/query/{ => legacy}/pickle_step_by_test_step_spec.rb (89%) rename ruby/spec/cucumber/query/{ => legacy}/step_definitions_by_test_step_spec.rb (94%) rename ruby/spec/cucumber/query/{ => legacy}/test_case_started_by_test_case_spec.rb (89%) diff --git a/ruby/lib/cucumber/query/errors.rb b/ruby/lib/cucumber/query/errors.rb deleted file mode 100644 index 6526fec1..00000000 --- a/ruby/lib/cucumber/query/errors.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -module Cucumber - module Query - TestCaseUnknownError = Class.new(StandardError) - TestStepUnknownError = Class.new(StandardError) - end -end diff --git a/ruby/lib/cucumber/query/hook_by_test_step.rb b/ruby/lib/cucumber/query/hook_by_test_step.rb deleted file mode 100644 index c1a1e4f8..00000000 --- a/ruby/lib/cucumber/query/hook_by_test_step.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -require_relative 'errors' - -module Cucumber - module Query - class HookByTestStep - def initialize(config) - @hook_id_by_test_step_id = {} - - config.on_event(:test_step_created, &method(:on_test_step_created)) - config.on_event(:hook_test_step_created, &method(:on_hook_test_step_created)) - end - - def hook_id(test_step) - return @hook_id_by_test_step_id[test_step.id] if @hook_id_by_test_step_id.key?(test_step.id) - - raise TestStepUnknownError, "No hook found for #{test_step.id} }. Known: #{@hook_id_by_test_step_id.keys}" - end - - private - - def on_test_step_created(event) - @hook_id_by_test_step_id[event.test_step.id] = nil - end - - def on_hook_test_step_created(event) - @hook_id_by_test_step_id[event.test_step.id] = event.hook.id - end - end - end -end diff --git a/ruby/lib/cucumber/query/legacy/errors.rb b/ruby/lib/cucumber/query/legacy/errors.rb new file mode 100644 index 00000000..fc94315d --- /dev/null +++ b/ruby/lib/cucumber/query/legacy/errors.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Cucumber + module Query + module Legacy + TestCaseUnknownError = Class.new(StandardError) + TestStepUnknownError = Class.new(StandardError) + end + end +end diff --git a/ruby/lib/cucumber/query/legacy/hook_by_test_step.rb b/ruby/lib/cucumber/query/legacy/hook_by_test_step.rb new file mode 100644 index 00000000..7953684a --- /dev/null +++ b/ruby/lib/cucumber/query/legacy/hook_by_test_step.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require_relative 'errors' + +module Cucumber + module Query + module Legacy + class HookByTestStep + def initialize(config) + @hook_id_by_test_step_id = {} + + config.on_event(:test_step_created, &method(:on_test_step_created)) + config.on_event(:hook_test_step_created, &method(:on_hook_test_step_created)) + end + + def hook_id(test_step) + return @hook_id_by_test_step_id[test_step.id] if @hook_id_by_test_step_id.key?(test_step.id) + + raise TestStepUnknownError, "No hook found for #{test_step.id} }. Known: #{@hook_id_by_test_step_id.keys}" + end + + private + + def on_test_step_created(event) + @hook_id_by_test_step_id[event.test_step.id] = nil + end + + def on_hook_test_step_created(event) + @hook_id_by_test_step_id[event.test_step.id] = event.hook.id + end + end + end + end +end diff --git a/ruby/lib/cucumber/query/legacy/pickle_by_test.rb b/ruby/lib/cucumber/query/legacy/pickle_by_test.rb new file mode 100644 index 00000000..c9a5a35c --- /dev/null +++ b/ruby/lib/cucumber/query/legacy/pickle_by_test.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require_relative 'errors' + +module Cucumber + module Query + module Legacy + class PickleByTest + def initialize(config) + @pickle_id_by_test_case_id = {} + + config.on_event(:test_case_created, &method(:on_test_case_created)) + end + + def pickle_id(test_case) + return @pickle_id_by_test_case_id[test_case.id] if @pickle_id_by_test_case_id.key?(test_case.id) + + raise TestCaseUnknownError, "No pickle found for #{test_case.id} }. Known: #{@pickle_id_by_test_case_id.keys}" + end + + private + + def on_test_case_created(event) + @pickle_id_by_test_case_id[event.test_case.id] = event.pickle.id + end + end + end + end +end diff --git a/ruby/lib/cucumber/query/legacy/pickle_step_by_test_step.rb b/ruby/lib/cucumber/query/legacy/pickle_step_by_test_step.rb new file mode 100644 index 00000000..aee77f74 --- /dev/null +++ b/ruby/lib/cucumber/query/legacy/pickle_step_by_test_step.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require_relative 'errors' + +module Cucumber + module Query + module Legacy + class PickleStepByTestStep + def initialize(config) + @pickle_id_step_by_test_step_id = {} + + config.on_event(:test_step_created, &method(:on_test_step_created)) + end + + def pickle_step_id(test_step) + return @pickle_id_step_by_test_step_id[test_step.id] if @pickle_id_step_by_test_step_id.key?(test_step.id) + + raise TestStepUnknownError, "No pickle step found for #{test_step.id} }. Known: #{@pickle_id_step_by_test_step_id.keys}" + end + + private + + def on_test_step_created(event) + @pickle_id_step_by_test_step_id[event.test_step.id] = event.pickle_step.id + end + end + end + end +end diff --git a/ruby/lib/cucumber/query/legacy/step_definitions_by_test_step.rb b/ruby/lib/cucumber/query/legacy/step_definitions_by_test_step.rb new file mode 100644 index 00000000..ae82f7c6 --- /dev/null +++ b/ruby/lib/cucumber/query/legacy/step_definitions_by_test_step.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require_relative 'errors' + +module Cucumber + module Query + module Legacy + class StepDefinitionsByTestStep + def initialize(config) + @step_definition_ids_by_test_step_id = {} + @step_match_arguments_by_test_step_id = {} + + config.on_event(:test_step_created, &method(:on_test_step_created)) + config.on_event(:step_activated, &method(:on_step_activated)) + end + + def step_definition_ids(test_step) + return @step_definition_ids_by_test_step_id[test_step.id] if @step_definition_ids_by_test_step_id.key?(test_step.id) + + raise TestStepUnknownError, "No step definition found for #{test_step.id} }. Known: #{@step_definition_ids_by_test_step_id.keys}" + end + + def step_match_arguments(test_step) + return @step_match_arguments_by_test_step_id[test_step.id] if @step_match_arguments_by_test_step_id.key?(test_step.id) + + raise TestStepUnknownError, "No step match arguments found for #{test_step.id} }. Known: #{@step_match_arguments_by_test_step_id.keys}" + end + + private + + def on_test_step_created(event) + @step_definition_ids_by_test_step_id[event.test_step.id] = [] + end + + def on_step_activated(event) + @step_definition_ids_by_test_step_id[event.test_step.id] << event.step_match.step_definition.id + @step_match_arguments_by_test_step_id[event.test_step.id] = event.step_match.step_arguments + end + end + end + end +end diff --git a/ruby/lib/cucumber/query/legacy/test_case_started_by_test_case.rb b/ruby/lib/cucumber/query/legacy/test_case_started_by_test_case.rb new file mode 100644 index 00000000..8153cc50 --- /dev/null +++ b/ruby/lib/cucumber/query/legacy/test_case_started_by_test_case.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require_relative 'errors' + +module Cucumber + module Query + module Legacy + class TestCaseStartedByTestCase + def initialize(config) + @config = config + @attempts_by_test_case_id = {} + @test_case_started_id_by_test_case_id = {} + + config.on_event(:test_case_created, &method(:on_test_case_created)) + config.on_event(:test_case_started, &method(:on_test_case_started)) + end + + def attempt_by_test_case(test_case) + raise TestCaseUnknownError, "No test case found for #{test_case.id} }. Known: #{@attempts_by_test_case_id.keys}" unless @attempts_by_test_case_id.key?(test_case.id) + + @attempts_by_test_case_id[test_case.id] + end + + def test_case_started_id_by_test_case(test_case) + raise TestCaseUnknownError, "No test case found for #{test_case.id} }. Known: #{@test_case_started_id_by_test_case_id.keys}" unless @test_case_started_id_by_test_case_id.key?(test_case.id) + + @test_case_started_id_by_test_case_id[test_case.id] + end + + private + + def on_test_case_created(event) + @attempts_by_test_case_id[event.test_case.id] = 0 + @test_case_started_id_by_test_case_id[event.test_case.id] = nil + end + + def on_test_case_started(event) + @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 + end + end + end +end diff --git a/ruby/lib/cucumber/query/legacy/test_run_started.rb b/ruby/lib/cucumber/query/legacy/test_run_started.rb new file mode 100644 index 00000000..ccc1a162 --- /dev/null +++ b/ruby/lib/cucumber/query/legacy/test_run_started.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require_relative 'errors' + +module Cucumber + module Query + module Legacy + class TestRunStarted + def initialize(config) + @config = config + end + + def id + @id ||= @config.id_generator.new_id + end + end + end + end +end diff --git a/ruby/lib/cucumber/query/pickle_by_test.rb b/ruby/lib/cucumber/query/pickle_by_test.rb deleted file mode 100644 index c8668b28..00000000 --- a/ruby/lib/cucumber/query/pickle_by_test.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require_relative 'errors' - -module Cucumber - module Query - class PickleByTest - def initialize(config) - @pickle_id_by_test_case_id = {} - - config.on_event(:test_case_created, &method(:on_test_case_created)) - end - - def pickle_id(test_case) - return @pickle_id_by_test_case_id[test_case.id] if @pickle_id_by_test_case_id.key?(test_case.id) - - raise TestCaseUnknownError, "No pickle found for #{test_case.id} }. Known: #{@pickle_id_by_test_case_id.keys}" - end - - private - - def on_test_case_created(event) - @pickle_id_by_test_case_id[event.test_case.id] = event.pickle.id - end - end - end -end diff --git a/ruby/lib/cucumber/query/pickle_step_by_test_step.rb b/ruby/lib/cucumber/query/pickle_step_by_test_step.rb deleted file mode 100644 index cac53537..00000000 --- a/ruby/lib/cucumber/query/pickle_step_by_test_step.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require_relative 'errors' - -module Cucumber - module Query - class PickleStepByTestStep - def initialize(config) - @pickle_id_step_by_test_step_id = {} - - config.on_event(:test_step_created, &method(:on_test_step_created)) - end - - def pickle_step_id(test_step) - return @pickle_id_step_by_test_step_id[test_step.id] if @pickle_id_step_by_test_step_id.key?(test_step.id) - - raise TestStepUnknownError, "No pickle step found for #{test_step.id} }. Known: #{@pickle_id_step_by_test_step_id.keys}" - end - - private - - def on_test_step_created(event) - @pickle_id_step_by_test_step_id[event.test_step.id] = event.pickle_step.id - end - end - end -end diff --git a/ruby/lib/cucumber/query/step_definitions_by_test_step.rb b/ruby/lib/cucumber/query/step_definitions_by_test_step.rb deleted file mode 100644 index 7894c24f..00000000 --- a/ruby/lib/cucumber/query/step_definitions_by_test_step.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -require_relative 'errors' - -module Cucumber - module Query - class StepDefinitionsByTestStep - def initialize(config) - @step_definition_ids_by_test_step_id = {} - @step_match_arguments_by_test_step_id = {} - - config.on_event(:test_step_created, &method(:on_test_step_created)) - config.on_event(:step_activated, &method(:on_step_activated)) - end - - def step_definition_ids(test_step) - return @step_definition_ids_by_test_step_id[test_step.id] if @step_definition_ids_by_test_step_id.key?(test_step.id) - - raise TestStepUnknownError, "No step definition found for #{test_step.id} }. Known: #{@step_definition_ids_by_test_step_id.keys}" - end - - def step_match_arguments(test_step) - return @step_match_arguments_by_test_step_id[test_step.id] if @step_match_arguments_by_test_step_id.key?(test_step.id) - - raise TestStepUnknownError, "No step match arguments found for #{test_step.id} }. Known: #{@step_match_arguments_by_test_step_id.keys}" - end - - private - - def on_test_step_created(event) - @step_definition_ids_by_test_step_id[event.test_step.id] = [] - end - - def on_step_activated(event) - @step_definition_ids_by_test_step_id[event.test_step.id] << event.step_match.step_definition.id - @step_match_arguments_by_test_step_id[event.test_step.id] = event.step_match.step_arguments - end - end - end -end diff --git a/ruby/lib/cucumber/query/test_case_started_by_test_case.rb b/ruby/lib/cucumber/query/test_case_started_by_test_case.rb deleted file mode 100644 index 08005fec..00000000 --- a/ruby/lib/cucumber/query/test_case_started_by_test_case.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require_relative 'errors' - -module Cucumber - module Query - class TestCaseStartedByTestCase - def initialize(config) - @config = config - @attempts_by_test_case_id = {} - @test_case_started_id_by_test_case_id = {} - - config.on_event(:test_case_created, &method(:on_test_case_created)) - config.on_event(:test_case_started, &method(:on_test_case_started)) - end - - def attempt_by_test_case(test_case) - raise TestCaseUnknownError, "No test case found for #{test_case.id} }. Known: #{@attempts_by_test_case_id.keys}" unless @attempts_by_test_case_id.key?(test_case.id) - - @attempts_by_test_case_id[test_case.id] - end - - def test_case_started_id_by_test_case(test_case) - raise TestCaseUnknownError, "No test case found for #{test_case.id} }. Known: #{@test_case_started_id_by_test_case_id.keys}" unless @test_case_started_id_by_test_case_id.key?(test_case.id) - - @test_case_started_id_by_test_case_id[test_case.id] - end - - private - - def on_test_case_created(event) - @attempts_by_test_case_id[event.test_case.id] = 0 - @test_case_started_id_by_test_case_id[event.test_case.id] = nil - end - - def on_test_case_started(event) - @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 - end - end -end diff --git a/ruby/lib/cucumber/query/test_run_started.rb b/ruby/lib/cucumber/query/test_run_started.rb deleted file mode 100644 index 6a28c074..00000000 --- a/ruby/lib/cucumber/query/test_run_started.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative 'errors' - -module Cucumber - module Query - class TestRunStarted - def initialize(config) - @config = config - end - - def id - @id ||= @config.id_generator.new_id - end - end - end -end diff --git a/ruby/spec/cucumber/query/hook_by_test_step_spec.rb b/ruby/spec/cucumber/query/legacy/hook_by_test_step_spec.rb similarity index 93% rename from ruby/spec/cucumber/query/hook_by_test_step_spec.rb rename to ruby/spec/cucumber/query/legacy/hook_by_test_step_spec.rb index 85d1a113..b7100eed 100644 --- a/ruby/spec/cucumber/query/hook_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/legacy/hook_by_test_step_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'cucumber/query/hook_by_test_step' +require 'cucumber/query/legacy/hook_by_test_step' -describe Cucumber::Query::HookByTestStep do +describe Cucumber::Query::Legacy::HookByTestStep do before do @test_cases = [] @hook_ids = [] @@ -57,7 +57,7 @@ test_step = double allow(test_step).to receive(:id).and_return('whatever-id') - expect { formatter.hook_id(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) + expect { formatter.hook_id(test_step) }.to raise_error(Cucumber::Query::Legacy::TestStepUnknownError) end end end diff --git a/ruby/spec/cucumber/query/pickle_by_test_spec.rb b/ruby/spec/cucumber/query/legacy/pickle_by_test_spec.rb similarity index 89% rename from ruby/spec/cucumber/query/pickle_by_test_spec.rb rename to ruby/spec/cucumber/query/legacy/pickle_by_test_spec.rb index f570949a..e463b84d 100644 --- a/ruby/spec/cucumber/query/pickle_by_test_spec.rb +++ b/ruby/spec/cucumber/query/legacy/pickle_by_test_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'cucumber/query/pickle_by_test' +require 'cucumber/query/legacy/pickle_by_test' -describe Cucumber::Query::PickleByTest do +describe Cucumber::Query::Legacy::PickleByTest do before do @test_cases = [] @pickle_ids = [] @@ -43,7 +43,7 @@ test_case = double allow(test_case).to receive(:id).and_return('whatever-id') - expect { formatter.pickle_id(test_case) }.to raise_error(Cucumber::Query::TestCaseUnknownError) + expect { formatter.pickle_id(test_case) }.to raise_error(Cucumber::Query::Legacy::TestCaseUnknownError) end end end diff --git a/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb b/ruby/spec/cucumber/query/legacy/pickle_step_by_test_step_spec.rb similarity index 89% rename from ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb rename to ruby/spec/cucumber/query/legacy/pickle_step_by_test_step_spec.rb index d1e0a483..7b16c027 100644 --- a/ruby/spec/cucumber/query/pickle_step_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/legacy/pickle_step_by_test_step_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'cucumber/query/pickle_step_by_test_step' +require 'cucumber/query/legacy/pickle_step_by_test_step' -describe Cucumber::Query::PickleStepByTestStep do +describe Cucumber::Query::Legacy::PickleStepByTestStep do before do @test_cases = [] @pickle_step_ids = [] @@ -48,7 +48,7 @@ test_step = double allow(test_step).to receive(:id).and_return('whatever-id') - expect { formatter.pickle_step_id(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) + expect { formatter.pickle_step_id(test_step) }.to raise_error(Cucumber::Query::Legacy::TestStepUnknownError) end end end diff --git a/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb b/ruby/spec/cucumber/query/legacy/step_definitions_by_test_step_spec.rb similarity index 94% rename from ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb rename to ruby/spec/cucumber/query/legacy/step_definitions_by_test_step_spec.rb index a89e3940..398d6623 100644 --- a/ruby/spec/cucumber/query/step_definitions_by_test_step_spec.rb +++ b/ruby/spec/cucumber/query/legacy/step_definitions_by_test_step_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'cucumber/query/step_definitions_by_test_step' +require 'cucumber/query/legacy/step_definitions_by_test_step' -describe Cucumber::Query::StepDefinitionsByTestStep do +describe Cucumber::Query::Legacy::StepDefinitionsByTestStep do before do @test_cases = [] @step_definition_ids = [] @@ -91,7 +91,7 @@ test_step = double allow(test_step).to receive(:id).and_return('whatever-id') - expect { formatter.step_definition_ids(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) + expect { formatter.step_definition_ids(test_step) }.to raise_error(Cucumber::Query::Legacy::TestStepUnknownError) end end end @@ -145,7 +145,7 @@ test_step = double allow(test_step).to receive(:id).and_return('whatever-id') - expect { formatter.step_match_arguments(test_step) }.to raise_error(Cucumber::Query::TestStepUnknownError) + expect { formatter.step_match_arguments(test_step) }.to raise_error(Cucumber::Query::Legacy::TestStepUnknownError) end end end diff --git a/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb b/ruby/spec/cucumber/query/legacy/test_case_started_by_test_case_spec.rb similarity index 89% rename from ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb rename to ruby/spec/cucumber/query/legacy/test_case_started_by_test_case_spec.rb index 0f943c6f..b27b6ebc 100644 --- a/ruby/spec/cucumber/query/test_case_started_by_test_case_spec.rb +++ b/ruby/spec/cucumber/query/legacy/test_case_started_by_test_case_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'cucumber/query/test_case_started_by_test_case' +require 'cucumber/query/legacy/test_case_started_by_test_case' -describe Cucumber::Query::TestCaseStartedByTestCase do +describe Cucumber::Query::Legacy::TestCaseStartedByTestCase do let(:config) { actual_runtime.configuration.with_options(out_stream: StringIO.new) } let(:formatter) { described_class.new(config) } let(:unknown_test_case) do @@ -13,7 +13,7 @@ describe '#attempt_by_test_case' do it 'raises an error when the TestCase is unknown' do - expect { formatter.attempt_by_test_case(unknown_test_case) }.to raise_error(Cucumber::Query::TestCaseUnknownError) + expect { formatter.attempt_by_test_case(unknown_test_case) }.to raise_error(Cucumber::Query::Legacy::TestCaseUnknownError) end context 'when the test case has been declared' do @@ -39,7 +39,7 @@ describe '#test_case_started_id_by_test_case' do it 'raises an error when the TestCase is unknown' do - expect { formatter.test_case_started_id_by_test_case(unknown_test_case) }.to raise_error(Cucumber::Query::TestCaseUnknownError) + expect { formatter.test_case_started_id_by_test_case(unknown_test_case) }.to raise_error(Cucumber::Query::Legacy::TestCaseUnknownError) end context 'when the test case has been declared' do From f35fa9bca4df41d64e55d13f5eec496e44ef5f15 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 19:44:34 +0100 Subject: [PATCH 15/55] Ported over helpers from JS --- ruby/lib/cucumber/query/foo/bar.rb | 1 + ruby/lib/cucumber/query/helpers.rb | 23 +++++++++++++++++++++++ ruby/lib/cucumber/query/query.rb | 3 +++ 3 files changed, 27 insertions(+) create mode 100644 ruby/lib/cucumber/query/foo/bar.rb create mode 100644 ruby/lib/cucumber/query/helpers.rb create mode 100644 ruby/lib/cucumber/query/query.rb diff --git a/ruby/lib/cucumber/query/foo/bar.rb b/ruby/lib/cucumber/query/foo/bar.rb new file mode 100644 index 00000000..cd877773 --- /dev/null +++ b/ruby/lib/cucumber/query/foo/bar.rb @@ -0,0 +1 @@ +'placeholder' diff --git a/ruby/lib/cucumber/query/helpers.rb b/ruby/lib/cucumber/query/helpers.rb new file mode 100644 index 00000000..0c59d7cf --- /dev/null +++ b/ruby/lib/cucumber/query/helpers.rb @@ -0,0 +1,23 @@ +require 'cucumber/messages/test_step_result_status' + +module Cucumber + module Query + module Helpers + def status_ordinal(status) + [ + TestStepResultStatus::UNKNOWN, + TestStepResultStatus::PASSED, + TestStepResultStatus::SKIPPED, + TestStepResultStatus::PENDING, + TestStepResultStatus::UNDEFINED, + TestStepResultStatus::AMBIGUOUS, + TestStepResultStatus::FAILED + ].index(status) + end + + def assert(target, failure_message) + raise StandardError, failure_message unless target + end + end + end +end diff --git a/ruby/lib/cucumber/query/query.rb b/ruby/lib/cucumber/query/query.rb new file mode 100644 index 00000000..19fb08de --- /dev/null +++ b/ruby/lib/cucumber/query/query.rb @@ -0,0 +1,3 @@ +require_relative 'helpers' + +require_relative 'foo/bar' From c3d7fe51433a270163890e8881e4eee3071a5e58 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 20:33:27 +0100 Subject: [PATCH 16/55] Initial implementation of initializer and update method --- ruby/lib/cucumber/query/query.rb | 79 ++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/ruby/lib/cucumber/query/query.rb b/ruby/lib/cucumber/query/query.rb index 19fb08de..80691def 100644 --- a/ruby/lib/cucumber/query/query.rb +++ b/ruby/lib/cucumber/query/query.rb @@ -1,3 +1,82 @@ require_relative 'helpers' require_relative 'foo/bar' + +module Cucumber + class Query + def initialize + @test_step_result_by_pickle_id = [{}] + @test_step_results_by_pickle_step_id = [[]] + @test_case_by_pickle_id = [] + @pickle_id_by_test_step_id = [] + @pickle_step_id_by_test_step_id = [] + @test_step_results_by_test_step_id = [[]] + @test_step_ids_by_pickle_step_id = [{}] + @hooks_by_id = {} + @attachments_by_test_step_id = [] + @step_match_arguments_lists_by_pickle_step_id = [{}] + @meta = :not_sure_on_this + @test_run_started = :not_sure_on_this + @test_run_finished = :not_sure_on_this + @test_case_started_by_id = [] + @lineage_by_id = [] + @step_by_id = [] + @pickle_by_id = [] + @pickle_step_by_id = [] + @step_definition_by_id = {} + @test_case_by_id = [] + @test_case_finished_by_test_case_started_id = [[]] + @test_step_started_by_test_case_started_id = [[]] + @test_step_finished_by_test_case_started_id = [[]] + @attachments_by_test_case_started_id = [[]] + end + + def update(envelope) + @meta = envelope.meta if envelope.meta + update_gherkin_document(envelope.gherkin_document) if envelope.gherkin_document + update_pickle(envelope.pickle) if envelope.gherkin_document + @hooks_by_id[envelope.hook.id] = envelope.hook if envelope.hook + @step_definition_by_id[envelope.step_definition.id] = envelope.step_definition if envelope.step_definition + @test_run_started = envelope.test_run_started if envelope.test_run_started + update_test_case(envelope.test_case) if envelope.test_case + update_test_case_started(envelope.test_case_started) if envelope.test_case_started + update_test_step_started(envelope.test_step_started) if envelope.test_step_started + update_attachment(envelope.attachment) if envelope.attachment + update_test_step_finished(envelope.test_step_finished) if envelope.test_step_finished + update_test_case_finished(envelope.test_case_finished) if envelope.test_case_finished + @test_run_finished = envelope.test_run_finished if envelope.test_run_finished + end + + def update_gherkin_document(gherkin_document) + :not_yet_implemented + end + + def update_pickle(pickle) + :not_yet_implemented + end + + def update_test_case(test_case) + :not_yet_implemented + end + + def update_test_case_started(test_case_started) + :not_yet_implemented + end + + def update_test_step_started(test_step_started) + :not_yet_implemented + end + + def update_attachment(attachment) + :not_yet_implemented + end + + def update_test_step_finished(test_step_finished) + :not_yet_implemented + end + + def update_test_case_finished(test_case_finished) + :not_yet_implemented + end + end +end From 0a4c7551fe8d374d898b5d4b746d71f8275fc168 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 20:34:19 +0100 Subject: [PATCH 17/55] Remove un-needed placeholder --- ruby/lib/cucumber/query/foo/bar.rb | 1 - ruby/lib/cucumber/query/query.rb | 2 -- 2 files changed, 3 deletions(-) delete mode 100644 ruby/lib/cucumber/query/foo/bar.rb diff --git a/ruby/lib/cucumber/query/foo/bar.rb b/ruby/lib/cucumber/query/foo/bar.rb deleted file mode 100644 index cd877773..00000000 --- a/ruby/lib/cucumber/query/foo/bar.rb +++ /dev/null @@ -1 +0,0 @@ -'placeholder' diff --git a/ruby/lib/cucumber/query/query.rb b/ruby/lib/cucumber/query/query.rb index 80691def..319e38ee 100644 --- a/ruby/lib/cucumber/query/query.rb +++ b/ruby/lib/cucumber/query/query.rb @@ -1,7 +1,5 @@ require_relative 'helpers' -require_relative 'foo/bar' - module Cucumber class Query def initialize From 2b2b2d0c40144a698c5a1ead9c891c44dafff887 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 5 Sep 2025 20:34:47 +0100 Subject: [PATCH 18/55] Regen the rubocop config; --- ruby/.rubocop_todo.yml | 93 ++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/ruby/.rubocop_todo.yml b/ruby/.rubocop_todo.yml index 8c6ecb8c..4f132b72 100644 --- a/ruby/.rubocop_todo.yml +++ b/ruby/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2025-09-05 17:51:10 UTC using RuboCop version 1.80.2. +# on 2025-09-05 19:34:38 UTC using RuboCop version 1.80.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 @@ -16,65 +16,76 @@ Layout/EmptyLinesAroundModuleBody: # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). -Layout/LeadingEmptyLines: +# Configuration parameters: AllowInHeredoc. +Layout/TrailingWhitespace: Exclude: - - 'spec/support/gherkin_helper.rb' + - 'spec/cucumber/query/legacy/step_definitions_by_test_step_spec.rb' # Offense count: 8 # Configuration parameters: AllowComments, AllowEmptyLambdas. Lint/EmptyBlock: Exclude: - - 'spec/cucumber/query/hook_by_test_step_spec.rb' - - 'spec/cucumber/query/step_definitions_by_test_step_spec.rb' + - 'spec/cucumber/query/legacy/hook_by_test_step_spec.rb' + - 'spec/cucumber/query/legacy/step_definitions_by_test_step_spec.rb' + +# Offense count: 8 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions. +# NotImplementedExceptions: NotImplementedError +Lint/UnusedMethodArgument: + Exclude: + - 'lib/cucumber/query/query.rb' -# Offense count: 2 +# Offense count: 4 # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: - Max: 30 + Max: 43 # Offense count: 1 +# Configuration parameters: AllowedMethods, AllowedPatterns. +Metrics/CyclomaticComplexity: + Max: 14 + +# Offense count: 3 # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: - Max: 13 + Max: 24 + +# Offense count: 1 +# Configuration parameters: AllowedMethods, AllowedPatterns. +Metrics/PerceivedComplexity: + Max: 14 # Offense count: 8 Performance/MethodObjectAsBlock: Exclude: - - 'lib/cucumber/query/hook_by_test_step.rb' - - 'lib/cucumber/query/pickle_by_test.rb' - - 'lib/cucumber/query/pickle_step_by_test_step.rb' - - 'lib/cucumber/query/step_definitions_by_test_step.rb' - - 'lib/cucumber/query/test_case_started_by_test_case.rb' + - 'lib/cucumber/query/legacy/hook_by_test_step.rb' + - 'lib/cucumber/query/legacy/pickle_by_test.rb' + - 'lib/cucumber/query/legacy/pickle_step_by_test_step.rb' + - 'lib/cucumber/query/legacy/step_definitions_by_test_step.rb' + - 'lib/cucumber/query/legacy/test_case_started_by_test_case.rb' # Offense count: 1 # Configuration parameters: Prefixes, AllowedPatterns. # Prefixes: when, with, without RSpec/ContextWording: Exclude: - - 'spec/cucumber/query/hook_by_test_step_spec.rb' + - 'spec/cucumber/query/legacy/hook_by_test_step_spec.rb' -# Offense count: 2 +# Offense count: 1 # Configuration parameters: CountAsOne. RSpec/ExampleLength: Max: 7 -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: implicit, each, example -RSpec/HookArgument: - Exclude: - - 'spec/cucumber/query/test_case_started_by_test_case_spec.rb' - -# Offense count: 87 +# Offense count: 32 # Configuration parameters: AssignmentOnly. RSpec/InstanceVariable: Exclude: - - 'spec/cucumber/query/hook_by_test_step_spec.rb' - - 'spec/cucumber/query/pickle_by_test_spec.rb' - - 'spec/cucumber/query/pickle_step_by_test_step_spec.rb' - - 'spec/cucumber/query/step_definitions_by_test_step_spec.rb' - - 'spec/cucumber/query/test_case_started_by_test_case_spec.rb' + - 'spec/cucumber/query/legacy/hook_by_test_step_spec.rb' + - 'spec/cucumber/query/legacy/pickle_by_test_spec.rb' + - 'spec/cucumber/query/legacy/pickle_step_by_test_step_spec.rb' + - 'spec/cucumber/query/legacy/step_definitions_by_test_step_spec.rb' + - 'spec/cucumber/query/legacy/test_case_started_by_test_case_spec.rb' # Offense count: 3 RSpec/MultipleExpectations: @@ -85,23 +96,27 @@ RSpec/MultipleExpectations: RSpec/NestedGroups: Max: 5 -# Offense count: 6 +# Offense count: 8 # Configuration parameters: AllowedConstants. Style/Documentation: Exclude: - - 'lib/cucumber/query/hook_by_test_step.rb' - - 'lib/cucumber/query/pickle_by_test.rb' - - 'lib/cucumber/query/pickle_step_by_test_step.rb' - - 'lib/cucumber/query/step_definitions_by_test_step.rb' - - 'lib/cucumber/query/test_case_started_by_test_case.rb' - - 'lib/cucumber/query/test_run_started.rb' - -# Offense count: 2 + - 'lib/cucumber/query/helpers.rb' + - 'lib/cucumber/query/legacy/hook_by_test_step.rb' + - 'lib/cucumber/query/legacy/pickle_by_test.rb' + - 'lib/cucumber/query/legacy/pickle_step_by_test_step.rb' + - 'lib/cucumber/query/legacy/step_definitions_by_test_step.rb' + - 'lib/cucumber/query/legacy/test_case_started_by_test_case.rb' + - 'lib/cucumber/query/legacy/test_run_started.rb' + - 'lib/cucumber/query/query.rb' + +# Offense count: 4 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: always, always_true, never Style/FrozenStringLiteralComment: Exclude: + - 'lib/cucumber/query/helpers.rb' + - 'lib/cucumber/query/query.rb' - 'spec/support/gherkin_helper.rb' - 'spec/support/runner_helper.rb' @@ -110,4 +125,4 @@ Style/FrozenStringLiteralComment: # Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. # URISchemes: http, https Layout/LineLength: - Max: 196 + Max: 198 From f780531471531b199bb714b13a34b2c7db9af771 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 28 Nov 2025 17:57:36 +0000 Subject: [PATCH 19/55] Remove all legacy query entities --- .../query/{legacy/errors.rb => error.rb} | 2 +- .../query/legacy/hook_by_test_step.rb | 34 -------------- .../cucumber/query/legacy/pickle_by_test.rb | 29 ------------ .../query/legacy/pickle_step_by_test_step.rb | 29 ------------ .../legacy/step_definitions_by_test_step.rb | 42 ------------------ .../legacy/test_case_started_by_test_case.rb | 44 ------------------- .../cucumber/query/legacy/test_run_started.rb | 19 -------- 7 files changed, 1 insertion(+), 198 deletions(-) rename ruby/lib/cucumber/query/{legacy/errors.rb => error.rb} (91%) delete mode 100644 ruby/lib/cucumber/query/legacy/hook_by_test_step.rb delete mode 100644 ruby/lib/cucumber/query/legacy/pickle_by_test.rb delete mode 100644 ruby/lib/cucumber/query/legacy/pickle_step_by_test_step.rb delete mode 100644 ruby/lib/cucumber/query/legacy/step_definitions_by_test_step.rb delete mode 100644 ruby/lib/cucumber/query/legacy/test_case_started_by_test_case.rb delete mode 100644 ruby/lib/cucumber/query/legacy/test_run_started.rb diff --git a/ruby/lib/cucumber/query/legacy/errors.rb b/ruby/lib/cucumber/query/error.rb similarity index 91% rename from ruby/lib/cucumber/query/legacy/errors.rb rename to ruby/lib/cucumber/query/error.rb index fc94315d..f7bc74fd 100644 --- a/ruby/lib/cucumber/query/legacy/errors.rb +++ b/ruby/lib/cucumber/query/error.rb @@ -2,7 +2,7 @@ module Cucumber module Query - module Legacy + module Error TestCaseUnknownError = Class.new(StandardError) TestStepUnknownError = Class.new(StandardError) end diff --git a/ruby/lib/cucumber/query/legacy/hook_by_test_step.rb b/ruby/lib/cucumber/query/legacy/hook_by_test_step.rb deleted file mode 100644 index 7953684a..00000000 --- a/ruby/lib/cucumber/query/legacy/hook_by_test_step.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require_relative 'errors' - -module Cucumber - module Query - module Legacy - class HookByTestStep - def initialize(config) - @hook_id_by_test_step_id = {} - - config.on_event(:test_step_created, &method(:on_test_step_created)) - config.on_event(:hook_test_step_created, &method(:on_hook_test_step_created)) - end - - def hook_id(test_step) - return @hook_id_by_test_step_id[test_step.id] if @hook_id_by_test_step_id.key?(test_step.id) - - raise TestStepUnknownError, "No hook found for #{test_step.id} }. Known: #{@hook_id_by_test_step_id.keys}" - end - - private - - def on_test_step_created(event) - @hook_id_by_test_step_id[event.test_step.id] = nil - end - - def on_hook_test_step_created(event) - @hook_id_by_test_step_id[event.test_step.id] = event.hook.id - end - end - end - end -end diff --git a/ruby/lib/cucumber/query/legacy/pickle_by_test.rb b/ruby/lib/cucumber/query/legacy/pickle_by_test.rb deleted file mode 100644 index c9a5a35c..00000000 --- a/ruby/lib/cucumber/query/legacy/pickle_by_test.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -require_relative 'errors' - -module Cucumber - module Query - module Legacy - class PickleByTest - def initialize(config) - @pickle_id_by_test_case_id = {} - - config.on_event(:test_case_created, &method(:on_test_case_created)) - end - - def pickle_id(test_case) - return @pickle_id_by_test_case_id[test_case.id] if @pickle_id_by_test_case_id.key?(test_case.id) - - raise TestCaseUnknownError, "No pickle found for #{test_case.id} }. Known: #{@pickle_id_by_test_case_id.keys}" - end - - private - - def on_test_case_created(event) - @pickle_id_by_test_case_id[event.test_case.id] = event.pickle.id - end - end - end - end -end diff --git a/ruby/lib/cucumber/query/legacy/pickle_step_by_test_step.rb b/ruby/lib/cucumber/query/legacy/pickle_step_by_test_step.rb deleted file mode 100644 index aee77f74..00000000 --- a/ruby/lib/cucumber/query/legacy/pickle_step_by_test_step.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -require_relative 'errors' - -module Cucumber - module Query - module Legacy - class PickleStepByTestStep - def initialize(config) - @pickle_id_step_by_test_step_id = {} - - config.on_event(:test_step_created, &method(:on_test_step_created)) - end - - def pickle_step_id(test_step) - return @pickle_id_step_by_test_step_id[test_step.id] if @pickle_id_step_by_test_step_id.key?(test_step.id) - - raise TestStepUnknownError, "No pickle step found for #{test_step.id} }. Known: #{@pickle_id_step_by_test_step_id.keys}" - end - - private - - def on_test_step_created(event) - @pickle_id_step_by_test_step_id[event.test_step.id] = event.pickle_step.id - end - end - end - end -end diff --git a/ruby/lib/cucumber/query/legacy/step_definitions_by_test_step.rb b/ruby/lib/cucumber/query/legacy/step_definitions_by_test_step.rb deleted file mode 100644 index ae82f7c6..00000000 --- a/ruby/lib/cucumber/query/legacy/step_definitions_by_test_step.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require_relative 'errors' - -module Cucumber - module Query - module Legacy - class StepDefinitionsByTestStep - def initialize(config) - @step_definition_ids_by_test_step_id = {} - @step_match_arguments_by_test_step_id = {} - - config.on_event(:test_step_created, &method(:on_test_step_created)) - config.on_event(:step_activated, &method(:on_step_activated)) - end - - def step_definition_ids(test_step) - return @step_definition_ids_by_test_step_id[test_step.id] if @step_definition_ids_by_test_step_id.key?(test_step.id) - - raise TestStepUnknownError, "No step definition found for #{test_step.id} }. Known: #{@step_definition_ids_by_test_step_id.keys}" - end - - def step_match_arguments(test_step) - return @step_match_arguments_by_test_step_id[test_step.id] if @step_match_arguments_by_test_step_id.key?(test_step.id) - - raise TestStepUnknownError, "No step match arguments found for #{test_step.id} }. Known: #{@step_match_arguments_by_test_step_id.keys}" - end - - private - - def on_test_step_created(event) - @step_definition_ids_by_test_step_id[event.test_step.id] = [] - end - - def on_step_activated(event) - @step_definition_ids_by_test_step_id[event.test_step.id] << event.step_match.step_definition.id - @step_match_arguments_by_test_step_id[event.test_step.id] = event.step_match.step_arguments - end - end - end - end -end diff --git a/ruby/lib/cucumber/query/legacy/test_case_started_by_test_case.rb b/ruby/lib/cucumber/query/legacy/test_case_started_by_test_case.rb deleted file mode 100644 index 8153cc50..00000000 --- a/ruby/lib/cucumber/query/legacy/test_case_started_by_test_case.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -require_relative 'errors' - -module Cucumber - module Query - module Legacy - class TestCaseStartedByTestCase - def initialize(config) - @config = config - @attempts_by_test_case_id = {} - @test_case_started_id_by_test_case_id = {} - - config.on_event(:test_case_created, &method(:on_test_case_created)) - config.on_event(:test_case_started, &method(:on_test_case_started)) - end - - def attempt_by_test_case(test_case) - raise TestCaseUnknownError, "No test case found for #{test_case.id} }. Known: #{@attempts_by_test_case_id.keys}" unless @attempts_by_test_case_id.key?(test_case.id) - - @attempts_by_test_case_id[test_case.id] - end - - def test_case_started_id_by_test_case(test_case) - raise TestCaseUnknownError, "No test case found for #{test_case.id} }. Known: #{@test_case_started_id_by_test_case_id.keys}" unless @test_case_started_id_by_test_case_id.key?(test_case.id) - - @test_case_started_id_by_test_case_id[test_case.id] - end - - private - - def on_test_case_created(event) - @attempts_by_test_case_id[event.test_case.id] = 0 - @test_case_started_id_by_test_case_id[event.test_case.id] = nil - end - - def on_test_case_started(event) - @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 - end - end - end -end diff --git a/ruby/lib/cucumber/query/legacy/test_run_started.rb b/ruby/lib/cucumber/query/legacy/test_run_started.rb deleted file mode 100644 index ccc1a162..00000000 --- a/ruby/lib/cucumber/query/legacy/test_run_started.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require_relative 'errors' - -module Cucumber - module Query - module Legacy - class TestRunStarted - def initialize(config) - @config = config - end - - def id - @id ||= @config.id_generator.new_id - end - end - end - end -end From 47e4f149be1bd72d865bc0300811ae32544f7516 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 28 Nov 2025 17:59:21 +0000 Subject: [PATCH 20/55] Move query into correct NS and rename helpers --- ruby/lib/cucumber/error.rb | 8 ++++++++ ruby/lib/cucumber/{query => }/query.rb | 2 +- ruby/lib/cucumber/query/error.rb | 10 ---------- .../cucumber/{query/helpers.rb => query_helpers.rb} | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) create mode 100644 ruby/lib/cucumber/error.rb rename ruby/lib/cucumber/{query => }/query.rb (98%) delete mode 100644 ruby/lib/cucumber/query/error.rb rename ruby/lib/cucumber/{query/helpers.rb => query_helpers.rb} (96%) diff --git a/ruby/lib/cucumber/error.rb b/ruby/lib/cucumber/error.rb new file mode 100644 index 00000000..c8343cad --- /dev/null +++ b/ruby/lib/cucumber/error.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Cucumber + module Error + TestCaseUnknownError = Class.new(StandardError) + TestStepUnknownError = Class.new(StandardError) + end +end diff --git a/ruby/lib/cucumber/query/query.rb b/ruby/lib/cucumber/query.rb similarity index 98% rename from ruby/lib/cucumber/query/query.rb rename to ruby/lib/cucumber/query.rb index 319e38ee..0e7530b8 100644 --- a/ruby/lib/cucumber/query/query.rb +++ b/ruby/lib/cucumber/query.rb @@ -1,4 +1,4 @@ -require_relative 'helpers' +require_relative '../ry_helpers' module Cucumber class Query diff --git a/ruby/lib/cucumber/query/error.rb b/ruby/lib/cucumber/query/error.rb deleted file mode 100644 index f7bc74fd..00000000 --- a/ruby/lib/cucumber/query/error.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Cucumber - module Query - module Error - TestCaseUnknownError = Class.new(StandardError) - TestStepUnknownError = Class.new(StandardError) - end - end -end diff --git a/ruby/lib/cucumber/query/helpers.rb b/ruby/lib/cucumber/query_helpers.rb similarity index 96% rename from ruby/lib/cucumber/query/helpers.rb rename to ruby/lib/cucumber/query_helpers.rb index 0c59d7cf..296fb4a7 100644 --- a/ruby/lib/cucumber/query/helpers.rb +++ b/ruby/lib/cucumber/query_helpers.rb @@ -2,7 +2,7 @@ module Cucumber module Query - module Helpers + module QueryHelpers def status_ordinal(status) [ TestStepResultStatus::UNKNOWN, From 3e7247051d61d2774324045e85f8684954f06e08 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 28 Nov 2025 18:00:19 +0000 Subject: [PATCH 21/55] Remove legacy tests --- .../query/legacy/hook_by_test_step_spec.rb | 84 ---------- .../query/legacy/pickle_by_test_spec.rb | 51 ------ .../legacy/pickle_step_by_test_step_spec.rb | 56 ------- .../step_definitions_by_test_step_spec.rb | 153 ------------------ .../test_case_started_by_test_case_spec.rb | 70 -------- ruby/spec/spec_helper.rb | 6 +- ruby/spec/support/gherkin_helper.rb | 14 -- ruby/spec/support/runner_helper.rb | 60 ------- 8 files changed, 1 insertion(+), 493 deletions(-) delete mode 100644 ruby/spec/cucumber/query/legacy/hook_by_test_step_spec.rb delete mode 100644 ruby/spec/cucumber/query/legacy/pickle_by_test_spec.rb delete mode 100644 ruby/spec/cucumber/query/legacy/pickle_step_by_test_step_spec.rb delete mode 100644 ruby/spec/cucumber/query/legacy/step_definitions_by_test_step_spec.rb delete mode 100644 ruby/spec/cucumber/query/legacy/test_case_started_by_test_case_spec.rb delete mode 100644 ruby/spec/support/gherkin_helper.rb delete mode 100644 ruby/spec/support/runner_helper.rb diff --git a/ruby/spec/cucumber/query/legacy/hook_by_test_step_spec.rb b/ruby/spec/cucumber/query/legacy/hook_by_test_step_spec.rb deleted file mode 100644 index b7100eed..00000000 --- a/ruby/spec/cucumber/query/legacy/hook_by_test_step_spec.rb +++ /dev/null @@ -1,84 +0,0 @@ -# frozen_string_literal: true - -require 'cucumber/query/legacy/hook_by_test_step' - -describe Cucumber::Query::Legacy::HookByTestStep do - before do - @test_cases = [] - @hook_ids = [] - - config.on_event(:test_case_started) do |event| - @test_cases << event.test_case - end - - config.on_event(:envelope) do |event| - next unless event.envelope.hook - - @hook_ids << event.envelope.hook.id - end - end - - let(:config) { actual_runtime.configuration.with_options(out_stream: StringIO.new) } - let(:first_test_case) { @test_cases.first } - let(:formatter) { described_class.new(config) } - - context 'given a single feature' do - before do - run_defined_feature - end - - context 'with a scenario' do - describe '#pickle_step_id' do - define_feature <<-FEATURE - Feature: Banana party - - Scenario: Monkey eats banana - Given there are bananas - FEATURE - - define_steps do - Before() {} - After() {} - end - - it 'provides the ID of the Before Hook used to generate the Test::Step' do - expect(formatter.hook_id(first_test_case.test_steps.first)).to eq(@hook_ids.first) - end - - it 'provides the ID of the After Hook used to generate the Test::Step' do - expect(formatter.hook_id(first_test_case.test_steps.last)).to eq(@hook_ids.last) - end - - it 'returns nil if the step was not generated from a hook' do - expect(formatter.hook_id(first_test_case.test_steps[1])).to be_nil - end - - it 'raises an exception when the test_step is unknown' do - test_step = double - allow(test_step).to receive(:id).and_return('whatever-id') - - expect { formatter.hook_id(test_step) }.to raise_error(Cucumber::Query::Legacy::TestStepUnknownError) - end - end - end - - context 'with AfterStep hooks' do - describe '#pickle_step_id' do - define_feature <<-FEATURE - Feature: Banana party - - Scenario: Monkey eats banana - Given there are bananas - FEATURE - - define_steps do - AfterStep() {} - end - - it 'provides the ID of the AfterStepHook used to generate the Test::Step' do - expect(formatter.hook_id(first_test_case.test_steps.last)).to eq(@hook_ids.first) - end - end - end - end -end diff --git a/ruby/spec/cucumber/query/legacy/pickle_by_test_spec.rb b/ruby/spec/cucumber/query/legacy/pickle_by_test_spec.rb deleted file mode 100644 index e463b84d..00000000 --- a/ruby/spec/cucumber/query/legacy/pickle_by_test_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -require 'cucumber/query/legacy/pickle_by_test' - -describe Cucumber::Query::Legacy::PickleByTest do - before do - @test_cases = [] - @pickle_ids = [] - - config.on_event(:test_case_created) do |event| - @test_cases << event.test_case - end - - config.on_event(:envelope) do |event| - next unless event.envelope.pickle - - @pickle_ids << event.envelope.pickle.id - end - end - - let(:config) { actual_runtime.configuration.with_options(out_stream: StringIO.new) } - let(:formatter) { described_class.new(config) } - - describe 'given a single feature' do - before do - run_defined_feature - end - - describe 'with a scenario' do - describe '#pickle_id' do - define_feature <<-FEATURE - Feature: Banana party - - Scenario: Monkey eats banana - Given there are bananas - FEATURE - - it 'provides the ID of the pickle used to generate the Test::Case' do - expect(formatter.pickle_id(@test_cases.first)).to eq(@pickle_ids.first) - end - - it 'raises an error when the Test::Case is unknown' do - test_case = double - allow(test_case).to receive(:id).and_return('whatever-id') - - expect { formatter.pickle_id(test_case) }.to raise_error(Cucumber::Query::Legacy::TestCaseUnknownError) - end - end - end - end -end diff --git a/ruby/spec/cucumber/query/legacy/pickle_step_by_test_step_spec.rb b/ruby/spec/cucumber/query/legacy/pickle_step_by_test_step_spec.rb deleted file mode 100644 index 7b16c027..00000000 --- a/ruby/spec/cucumber/query/legacy/pickle_step_by_test_step_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -require 'cucumber/query/legacy/pickle_step_by_test_step' - -describe Cucumber::Query::Legacy::PickleStepByTestStep do - before do - @test_cases = [] - @pickle_step_ids = [] - - config.on_event(:test_case_created) do |event| - @test_cases << event.test_case - end - - config.on_event(:envelope) do |event| - next unless event.envelope.pickle - - event.envelope.pickle.steps.each do |step| - @pickle_step_ids << step.id - end - end - end - - let(:config) { actual_runtime.configuration.with_options(out_stream: StringIO.new) } - let(:first_test_case) { @test_cases.first } - let(:formatter) { described_class.new(config) } - - describe 'given a single feature' do - before do - run_defined_feature - end - - describe 'with a scenario' do - describe '#pickle_step_id' do - define_feature <<-FEATURE - Feature: Banana party - - Scenario: Monkey eats banana - Given there are bananas - FEATURE - - it 'provides the ID of the PickleStep used to generate the Test::Step' do - test_step = first_test_case.test_steps.first - - expect(formatter.pickle_step_id(test_step)).to eq(@pickle_step_ids.first) - end - - it 'raises an exception when the test_step is unknown' do - test_step = double - allow(test_step).to receive(:id).and_return('whatever-id') - - expect { formatter.pickle_step_id(test_step) }.to raise_error(Cucumber::Query::Legacy::TestStepUnknownError) - end - end - end - end -end diff --git a/ruby/spec/cucumber/query/legacy/step_definitions_by_test_step_spec.rb b/ruby/spec/cucumber/query/legacy/step_definitions_by_test_step_spec.rb deleted file mode 100644 index 398d6623..00000000 --- a/ruby/spec/cucumber/query/legacy/step_definitions_by_test_step_spec.rb +++ /dev/null @@ -1,153 +0,0 @@ -# frozen_string_literal: true - -require 'cucumber/query/legacy/step_definitions_by_test_step' - -describe Cucumber::Query::Legacy::StepDefinitionsByTestStep do - before do - @test_cases = [] - @step_definition_ids = [] - - config.on_event(:test_case_created) do |event| - @test_cases << event.test_case - end - - config.on_event(:envelope) do |event| - next unless event.envelope.step_definition - - @step_definition_ids << event.envelope.step_definition.id - end - end - - let(:config) { actual_runtime.configuration.with_options(out_stream: StringIO.new) } - let(:first_test_case) { @test_cases.first } - let(:formatter) { described_class.new(config) } - - describe 'given a single feature' do - before do - run_defined_feature - end - - describe '#step_definition_ids' do - context 'with a matching step' do - define_steps do - Given(/^there are bananas$/) {} - end - - define_feature <<-FEATURE - Feature: Banana party - - Scenario: Monkey eats banana - Given there are bananas - FEATURE - - it 'provides the ID of the StepDefinition that matches Test::Step' do - test_step = first_test_case.test_steps.first - - expect(formatter.step_definition_ids(test_step)).to eq([@step_definition_ids.first]) - end - end - - context 'with a step that was not activated' do - context 'when there is no match' do - define_feature <<-FEATURE - Feature: Banana party - - Scenario: Monkey eats banana - Given there are bananas - FEATURE - - it 'returns an empty array' do - test_step = first_test_case.test_steps.first - - expect(formatter.step_definition_ids(test_step)).to be_empty - end - end - - context 'when there are multiple matches' do - define_steps do - Given(/^there are bananas$/) {} - Given(/^there .* bananas$/) {} - end - - define_feature <<-FEATURE - Feature: Banana party - - Scenario: Monkey eats banana - Given there are bananas - FEATURE - - it 'returns an empty array as the step is not activated' do - test_step = first_test_case.test_steps.first - - expect(formatter.step_definition_ids(test_step)).to be_empty - end - end - end - - context 'with an unknown step' do - define_feature 'Feature: Banana party' - - it 'raises an exception' do - test_step = double - allow(test_step).to receive(:id).and_return('whatever-id') - - expect { formatter.step_definition_ids(test_step) }.to raise_error(Cucumber::Query::Legacy::TestStepUnknownError) - end - end - end - - describe '#step_match_arguments' do - context 'with a matching step without arguments' do - define_steps do - Given(/^there are bananas$/) {} - end - - define_feature <<-FEATURE - Feature: Banana party - - Scenario: Monkey eats banana - Given there are bananas - FEATURE - - it 'returns an empty list' do - test_step = first_test_case.test_steps.first - - expect(formatter.step_match_arguments(test_step)).to be_empty - end - end - - context 'with a matching step with arguments' do - define_steps do - Given(/^there are (.*)$/) {} - end - - define_feature <<-FEATURE - Feature: Banana party - - Scenario: Monkey eats banana - Given there are bananas - FEATURE - - it 'returns an empty list' do - test_step = first_test_case.test_steps.first - matches = formatter.step_match_arguments(test_step) - - expect(matches.count).to eq(1) - expect(matches.first).to be_a(Cucumber::CucumberExpressions::Argument) - expect(matches.first.group.value).to eq('bananas') - end - end - - context 'with an unknown step' do - define_feature 'Feature: Banana party' - - it 'raises an exception' do - test_step = double - allow(test_step).to receive(:id).and_return('whatever-id') - - expect { formatter.step_match_arguments(test_step) }.to raise_error(Cucumber::Query::Legacy::TestStepUnknownError) - end - end - end - end -end diff --git a/ruby/spec/cucumber/query/legacy/test_case_started_by_test_case_spec.rb b/ruby/spec/cucumber/query/legacy/test_case_started_by_test_case_spec.rb deleted file mode 100644 index b27b6ebc..00000000 --- a/ruby/spec/cucumber/query/legacy/test_case_started_by_test_case_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -# frozen_string_literal: true - -require 'cucumber/query/legacy/test_case_started_by_test_case' - -describe Cucumber::Query::Legacy::TestCaseStartedByTestCase do - let(:config) { actual_runtime.configuration.with_options(out_stream: StringIO.new) } - let(:formatter) { described_class.new(config) } - let(:unknown_test_case) do - test_case = double - allow(test_case).to receive(:id).and_return('whatever-id') - test_case - end - - describe '#attempt_by_test_case' do - it 'raises an error when the TestCase is unknown' do - expect { formatter.attempt_by_test_case(unknown_test_case) }.to raise_error(Cucumber::Query::Legacy::TestCaseUnknownError) - end - - context 'when the test case has been declared' do - before do - @test_case = double - allow(@test_case).to receive(:id).and_return('some-valid-id') - config.notify(:test_case_created, @test_case, nil) - end - - it 'returns 0 if no test_case_started event has been fired' do - expect(formatter.attempt_by_test_case(@test_case)).to eq(0) - end - - it 'increments the attemp on every test_case_started event' do - config.notify(:test_case_started, @test_case) - expect(formatter.attempt_by_test_case(@test_case)).to eq(1) - - config.notify(:test_case_started, @test_case) - expect(formatter.attempt_by_test_case(@test_case)).to eq(2) - end - end - end - - describe '#test_case_started_id_by_test_case' do - it 'raises an error when the TestCase is unknown' do - expect { formatter.test_case_started_id_by_test_case(unknown_test_case) }.to raise_error(Cucumber::Query::Legacy::TestCaseUnknownError) - end - - context 'when the test case has been declared' do - before do - @test_case = double - allow(@test_case).to receive(:id).and_return('some-valid-id') - config.notify(:test_case_created, @test_case, nil) - end - - it 'returns nil if no test_case_started event has been fired' do - expect(formatter.test_case_started_id_by_test_case(@test_case)).to be_nil - end - - it 'gives a new id when a test_case_started event is fired' do - config.notify(:test_case_started, @test_case) - - first_attempt_id = formatter.test_case_started_id_by_test_case(@test_case) - expect(first_attempt_id).not_to be_nil - - config.notify(:test_case_started, @test_case) - second_attempt_id = formatter.test_case_started_id_by_test_case(@test_case) - expect(second_attempt_id).not_to be_nil - - expect(second_attempt_id).not_to eq(first_attempt_id) - end - end - end -end diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb index 3c684fd4..02ae8f30 100644 --- a/ruby/spec/spec_helper.rb +++ b/ruby/spec/spec_helper.rb @@ -3,10 +3,6 @@ require 'cucumber' require 'cucumber/core' -require_relative 'support/runner_helper' -require_relative 'support/gherkin_helper' - RSpec.configure do |c| - c.include RunnerHelper - c.extend GherkinHelper + :no_op end diff --git a/ruby/spec/support/gherkin_helper.rb b/ruby/spec/support/gherkin_helper.rb deleted file mode 100644 index 29bc3e6c..00000000 --- a/ruby/spec/support/gherkin_helper.rb +++ /dev/null @@ -1,14 +0,0 @@ -# TODO: This file is adapted / duplicated from SpecHelperDSL and SpecHelper in cucumber-ruby - -module GherkinHelper - attr_reader :feature_content, :step_definitions, :feature_filename - - def define_feature(string, feature_file = 'spec.feature') - @feature_content = string - @feature_filename = feature_file - end - - def define_steps(&block) - @step_definitions = block - end -end diff --git a/ruby/spec/support/runner_helper.rb b/ruby/spec/support/runner_helper.rb deleted file mode 100644 index 44d5f67b..00000000 --- a/ruby/spec/support/runner_helper.rb +++ /dev/null @@ -1,60 +0,0 @@ -# TODO: This file is adapted / duplicated from SpecHelperDSL and SpecHelper in cucumber-ruby - -module RunnerHelper - - include Cucumber::Core - - def run_defined_feature - define_steps - actual_runtime.visitor = Cucumber::Formatter::Fanout.new([@formatter]) - receiver = Test::Runner.new(event_bus) - - event_bus.gherkin_source_read(gherkin_doc.uri, gherkin_doc.body) - - compile [gherkin_doc], receiver, filters, event_bus - - event_bus.test_run_finished - end - - def filters - [ - Cucumber::Filters::ActivateSteps.new( - Cucumber::StepMatchSearch.new(actual_runtime.support_code.registry.method(:step_matches), actual_runtime.configuration), - actual_runtime.configuration - ), - Cucumber::Filters::ApplyAfterStepHooks.new(actual_runtime.support_code), - Cucumber::Filters::ApplyBeforeHooks.new(actual_runtime.support_code), - Cucumber::Filters::ApplyAfterHooks.new(actual_runtime.support_code), - Cucumber::Filters::ApplyAroundHooks.new(actual_runtime.support_code), - Cucumber::Filters::BroadcastTestRunStartedEvent.new(actual_runtime.configuration), - Cucumber::Filters::BroadcastTestCaseReadyEvent.new(actual_runtime.configuration), - Cucumber::Filters::PrepareWorld.new(actual_runtime) - ] - end - - def gherkin_doc - Cucumber::Core::Gherkin::Document.new(self.class.feature_filename, gherkin) - end - - def gherkin - self.class.feature_content || raise('No feature content defined!') - end - - def actual_runtime - @actual_runtime ||= Cucumber::Runtime.new({}) - end - - def event_bus - actual_runtime.configuration.event_bus - end - - def define_steps - step_definitions = self.class.step_definitions - - return unless step_definitions - - dsl = Object.new - dsl.extend Cucumber::Glue::Dsl - dsl.instance_exec(&step_definitions) - end -end From b2b1c5987ecad7b86a0695a7590349a55a0623e6 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 28 Nov 2025 18:02:33 +0000 Subject: [PATCH 22/55] Fix readme --- ruby/README.md | 81 ------------------------------------- ruby/cucumber-query.gemspec | 4 +- 2 files changed, 2 insertions(+), 83 deletions(-) delete mode 100644 ruby/README.md diff --git a/ruby/README.md b/ruby/README.md deleted file mode 100644 index faeb97a2..00000000 --- a/ruby/README.md +++ /dev/null @@ -1,81 +0,0 @@ -# The Cucumber Compatibility Kit for Ruby - -The CCK - aka. Cucumber Compatibility Kit - is a set of features and Messages. -It aims to validate an implementation of the -[Cucumber Messages protocol](https://github.com/cucumber/common/tree/main/messages#cucumber-messages). - -## Overview - -This kit (gem), consists of a set of features, miscellaneous files and messages: - -- Each area will contain one feature, which, once executed, will emit an exhaustive set of messages -as specified by the protocol -- Some of these areas may "also" require miscellaneous files to be used when testing functions -such as attaching images or documents or reading data from files -- Each area will contain a set of messages - serialized as a single `.ndjson` file -This is the reference for the CCK, that a given feature from the kit, when executed using any dedicated -step definitions, must emit the **exact** corresponding messages - -## Installation and Usage - -Add `cucumber-compatibility-kit` to your `Gemfile` as a development dependency, and -install it: - - bundle install - -Then add a spec that could look like this: - -```ruby -# spec/my_compatibility_checks_spec.rb -require 'cucumber/compatibility_kit' - -describe Cucumber::CompatibilityKit, type: :feature do - let(:cucumber_command) { 'bundle exec cucumber --publish-quiet --profile none --format message' } - - # Don't run the retry or skipped CCK Examples (For whatever reason) - examples = Cucumber::CompatibilityKit.gherkin.reject { |example| example == 'retry' || example == 'skipped' } - - examples.each do |example_name| - describe "'#{example_name}' example" do - include_examples 'cucumber compatibility kit' do - let(:example) { example_name } - # You will need to specify the relative support code and cck paths - let(:messages) { `#{cucumber_command} --require #{support_code_path} #{cck_path}` } - end - end - end -end -``` - -`CucumberCompatibilityKit.gherkin` will return an array that lists all the gherkin examples available within the CCK. -Here, we want to execute all of them except the `retry` and `skipped` ones (For whatever reason). - -`let(:messages)` will execute the cucumber command. As we are using the `message` formatter, `messages` will -then contain the messages as a `ndjson` document with one message per line. - -You can use `gem open cucumber-compatibility-kit` in order to take a look at the features and the -expected messages they should produce. They are available in the `features` folder within the gem. - -## More info - -The Cucumber Compatibility Kit is part of the development tools of [Cucumber](https://cucumber.io). -It allows us to make sure that all our implementations are properly supporting our internal protocol -and thus are compatible (and consistent), with each other and our common tools like the [html-formatter](https://github.com/cucumber/html-formatter). - -It can be a valuable tool if you are developing integration with cucumber, or your own implementation of it. - -Join us on [github/cucumber/compatibility-kit](https://github.com/cucumber/compatibility-kit) -to get more help if you need to. - -You can also take a look on [cucumber-ruby](https://github.com/cucumber/cucumber-ruby/blob/v9.2.0/compatibility/cck_spec.rb) -to see how the kit is used there. - -## Development - -Before building this project locally, the samples must be copied from the `devkit`. Use: - -``` -cd ../devkit -npm ci && npm run copy-to:ruby -cd ../ruby -``` diff --git a/ruby/cucumber-query.gemspec b/ruby/cucumber-query.gemspec index b152a6a4..ec278479 100644 --- a/ruby/cucumber-query.gemspec +++ b/ruby/cucumber-query.gemspec @@ -16,7 +16,7 @@ Gem::Specification.new do |s| s.metadata = { 'bug_tracker_uri' => 'https://github.com/cucumber/query/issues', 'changelog_uri' => 'https://github.com/cucumber/query/blob/main/CHANGELOG.md', - 'documentation_uri' => 'https://github.com/cucumber/query/blob/main/CONTRIBUTING.md', + 'documentation_uri' => 'https://github.com/cucumber/query/blob/main/README.md', 'mailing_list_uri' => 'https://groups.google.com/forum/#!forum/cukes', 'source_code_uri' => 'https://github.com/cucumber/query/blob/main/ruby' } @@ -29,7 +29,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'rubocop-performance', '~> 1.25.0' s.add_development_dependency 'rubocop-rspec', '~> 3.7.0' - s.files = Dir['README.md', 'LICENSE', 'lib/**/*'] + s.files = Dir['LICENSE', 'lib/**/*'] s.rdoc_options = ['--charset=UTF-8'] s.require_path = 'lib' end From 354aee3b0d40ac29f63e9f1c9ae33a68576f5112 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Fri, 28 Nov 2025 18:03:52 +0000 Subject: [PATCH 23/55] Remove un-necessary items from package --- ruby/cucumber-query.gemspec | 1 - ruby/spec/spec_helper.rb | 3 --- 2 files changed, 4 deletions(-) diff --git a/ruby/cucumber-query.gemspec b/ruby/cucumber-query.gemspec index ec278479..e58f47e2 100644 --- a/ruby/cucumber-query.gemspec +++ b/ruby/cucumber-query.gemspec @@ -23,7 +23,6 @@ Gem::Specification.new do |s| s.add_dependency 'cucumber-messages', '> 25', '< 30' - s.add_development_dependency 'cucumber', '~> 10.1' s.add_development_dependency 'rspec', '~> 3.13' s.add_development_dependency 'rubocop', '~> 1.80.0' s.add_development_dependency 'rubocop-performance', '~> 1.25.0' diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb index 02ae8f30..d0b601fc 100644 --- a/ruby/spec/spec_helper.rb +++ b/ruby/spec/spec_helper.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require 'cucumber' -require 'cucumber/core' - RSpec.configure do |c| :no_op end From df0df5672b734da2ce8f43b61eb55e642fe60d03 Mon Sep 17 00:00:00 2001 From: Matt Wynne Date: Sat, 30 May 2026 20:26:31 -0700 Subject: [PATCH 24/55] Move Ruby query implementation into gem --- ruby/Gemfile.lock | 43 +-- ruby/cucumber-query.gemspec | 2 +- ruby/lib/cucumber/query.rb | 305 ++++++++++++++---- ruby/lib/cucumber/repository.rb | 136 ++++++++ ruby/spec/cucumber/query_spec.rb | 61 ++++ .../attachments.findAllPickles.results.json | 1 + .../attachments.findAllTestCases.results.json | 1 + .../attachments.findAllTestSteps.results.json | 1 + .../attachments.findPickleBy.results.json | 38 +++ .../attachments.findTestCaseBy.results.json | 38 +++ ruby/spec/support/attachments.ndjson | 61 ++++ .../support/empty.findAllPickles.results.json | 1 + .../empty.findAllTestCases.results.json | 1 + .../empty.findAllTestSteps.results.json | 1 + .../support/empty.findPickleBy.results.json | 10 + .../support/empty.findTestCaseBy.results.json | 10 + ruby/spec/support/empty.ndjson | 9 + .../support/hooks.findAllPickles.results.json | 1 + .../hooks.findAllTestCases.results.json | 1 + .../hooks.findAllTestSteps.results.json | 1 + .../support/hooks.findPickleBy.results.json | 26 ++ .../support/hooks.findTestCaseBy.results.json | 26 ++ ruby/spec/support/hooks.ndjson | 29 ++ .../minimal.findAllPickles.results.json | 1 + .../minimal.findAllTestCases.results.json | 1 + .../minimal.findAllTestSteps.results.json | 1 + .../support/minimal.findPickleBy.results.json | 14 + .../minimal.findTestCaseBy.results.json | 14 + ruby/spec/support/minimal.ndjson | 12 + .../support/rules.findAllPickles.results.json | 1 + .../rules.findAllTestCases.results.json | 1 + .../rules.findAllTestSteps.results.json | 1 + .../support/rules.findPickleBy.results.json | 40 +++ .../support/rules.findTestCaseBy.results.json | 40 +++ ruby/spec/support/rules.ndjson | 47 +++ 35 files changed, 879 insertions(+), 97 deletions(-) create mode 100644 ruby/lib/cucumber/repository.rb create mode 100644 ruby/spec/cucumber/query_spec.rb create mode 100644 ruby/spec/support/attachments.findAllPickles.results.json create mode 100644 ruby/spec/support/attachments.findAllTestCases.results.json create mode 100644 ruby/spec/support/attachments.findAllTestSteps.results.json create mode 100644 ruby/spec/support/attachments.findPickleBy.results.json create mode 100644 ruby/spec/support/attachments.findTestCaseBy.results.json create mode 100644 ruby/spec/support/attachments.ndjson create mode 100644 ruby/spec/support/empty.findAllPickles.results.json create mode 100644 ruby/spec/support/empty.findAllTestCases.results.json create mode 100644 ruby/spec/support/empty.findAllTestSteps.results.json create mode 100644 ruby/spec/support/empty.findPickleBy.results.json create mode 100644 ruby/spec/support/empty.findTestCaseBy.results.json create mode 100644 ruby/spec/support/empty.ndjson create mode 100644 ruby/spec/support/hooks.findAllPickles.results.json create mode 100644 ruby/spec/support/hooks.findAllTestCases.results.json create mode 100644 ruby/spec/support/hooks.findAllTestSteps.results.json create mode 100644 ruby/spec/support/hooks.findPickleBy.results.json create mode 100644 ruby/spec/support/hooks.findTestCaseBy.results.json create mode 100644 ruby/spec/support/hooks.ndjson create mode 100644 ruby/spec/support/minimal.findAllPickles.results.json create mode 100644 ruby/spec/support/minimal.findAllTestCases.results.json create mode 100644 ruby/spec/support/minimal.findAllTestSteps.results.json create mode 100644 ruby/spec/support/minimal.findPickleBy.results.json create mode 100644 ruby/spec/support/minimal.findTestCaseBy.results.json create mode 100644 ruby/spec/support/minimal.ndjson create mode 100644 ruby/spec/support/rules.findAllPickles.results.json create mode 100644 ruby/spec/support/rules.findAllTestCases.results.json create mode 100644 ruby/spec/support/rules.findAllTestSteps.results.json create mode 100644 ruby/spec/support/rules.findPickleBy.results.json create mode 100644 ruby/spec/support/rules.findTestCaseBy.results.json create mode 100644 ruby/spec/support/rules.ndjson diff --git a/ruby/Gemfile.lock b/ruby/Gemfile.lock index a65432fe..6a5a9ec2 100644 --- a/ruby/Gemfile.lock +++ b/ruby/Gemfile.lock @@ -2,50 +2,17 @@ PATH remote: . specs: cucumber-query (21.0.0) - cucumber-messages (> 25, < 30) + cucumber-messages (> 31, < 33) GEM remote: https://rubygems.org/ specs: ast (2.4.2) - base64 (0.3.0) - bigdecimal (3.2.3) - builder (3.3.0) - cucumber (10.1.0) - base64 (~> 0.2) - builder (~> 3.2) - cucumber-ci-environment (> 9, < 11) - cucumber-core (> 15, < 17) - cucumber-cucumber-expressions (> 17, < 19) - cucumber-html-formatter (> 20.3, < 22) - diff-lcs (~> 1.5) - logger (~> 1.6) - mini_mime (~> 1.1) - multi_test (~> 1.1) - sys-uname (~> 1.3) - cucumber-ci-environment (10.0.1) - cucumber-core (15.2.1) - cucumber-gherkin (> 27, < 33) - cucumber-messages (> 26, < 30) - cucumber-tag-expressions (> 5, < 7) - cucumber-cucumber-expressions (18.0.1) - bigdecimal - cucumber-gherkin (32.2.0) - cucumber-messages (> 25, < 28) - cucumber-html-formatter (21.14.0) - cucumber-messages (> 19, < 28) - cucumber-messages (27.2.0) - cucumber-tag-expressions (6.1.2) + cucumber-messages (32.3.1) diff-lcs (1.5.1) - ffi (1.17.2) - ffi (1.17.2-x86_64-linux-gnu) json (2.9.1) language_server-protocol (3.17.0.4) lint_roller (1.1.0) - logger (1.7.0) - memoist3 (1.0.0) - mini_mime (1.1.5) - multi_test (1.1.0) parallel (1.26.3) parser (3.3.9.0) ast (~> 2.4.1) @@ -89,9 +56,6 @@ GEM lint_roller (~> 1.1) rubocop (~> 1.72, >= 1.72.1) ruby-progressbar (1.13.0) - sys-uname (1.4.1) - ffi (~> 1.1) - memoist3 (~> 1.0.0) unicode-display_width (3.1.4) unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) @@ -101,7 +65,6 @@ PLATFORMS x86_64-linux DEPENDENCIES - cucumber (~> 10.1) cucumber-query! rspec (~> 3.13) rubocop (~> 1.80.0) @@ -109,4 +72,4 @@ DEPENDENCIES rubocop-rspec (~> 3.7.0) BUNDLED WITH - 2.6.2 + 2.5.22 diff --git a/ruby/cucumber-query.gemspec b/ruby/cucumber-query.gemspec index e58f47e2..e9acebcb 100644 --- a/ruby/cucumber-query.gemspec +++ b/ruby/cucumber-query.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| 'source_code_uri' => 'https://github.com/cucumber/query/blob/main/ruby' } - s.add_dependency 'cucumber-messages', '> 25', '< 30' + s.add_dependency 'cucumber-messages', '> 31', '< 33' s.add_development_dependency 'rspec', '~> 3.13' s.add_development_dependency 'rubocop', '~> 1.80.0' diff --git a/ruby/lib/cucumber/query.rb b/ruby/lib/cucumber/query.rb index 0e7530b8..73573519 100644 --- a/ruby/lib/cucumber/query.rb +++ b/ruby/lib/cucumber/query.rb @@ -1,80 +1,273 @@ -require_relative '../ry_helpers' +# frozen_string_literal: true +require 'cucumber/repository' +require 'cucumber/messages' + +# Given one Cucumber Message, find another. +# +# Queries can be made while the test run is incomplete - and this will naturally return incomplete results +# see Cucumber Messages - Message Overview +# module Cucumber class Query - def initialize - @test_step_result_by_pickle_id = [{}] - @test_step_results_by_pickle_step_id = [[]] - @test_case_by_pickle_id = [] - @pickle_id_by_test_step_id = [] - @pickle_step_id_by_test_step_id = [] - @test_step_results_by_test_step_id = [[]] - @test_step_ids_by_pickle_step_id = [{}] - @hooks_by_id = {} - @attachments_by_test_step_id = [] - @step_match_arguments_lists_by_pickle_step_id = [{}] - @meta = :not_sure_on_this - @test_run_started = :not_sure_on_this - @test_run_finished = :not_sure_on_this - @test_case_started_by_id = [] - @lineage_by_id = [] - @step_by_id = [] - @pickle_by_id = [] - @pickle_step_by_id = [] - @step_definition_by_id = {} - @test_case_by_id = [] - @test_case_finished_by_test_case_started_id = [[]] - @test_step_started_by_test_case_started_id = [[]] - @test_step_finished_by_test_case_started_id = [[]] - @attachments_by_test_case_started_id = [[]] + attr_reader :repository + private :repository + + include Cucumber::Messages::Helpers::TimeConversion + include Cucumber::Messages::Helpers::TestStepResultComparator + + def initialize(repository) + @repository = repository + end + + # TODO: count methods (1/2) Complete + # Missing: countMostSevereTestStepResultStatus + + # TODO: findAll methods (11/12) Complete + # Missing: findAllUndefinedParameterTypes + + # TODO: find****By methods (16/25) Complete + # Missing: findSuggestionsBy (2 variants) + # Missing: findUnambiguousStepDefinitionBy (1 variant) + # Missing: findTestStepFinishedAndTestStepBy (1 variant) + # Missing: findAttachmentsBy (2 variants) + # Missing: findTestCaseDurationBy (2 variant) + # REDUNDANT: findLineageBy (9 variants!) + # REDUNDANT: findLocationOf (1 variant) - This strictly speaking isn't a findBy but is located within them + # To Review: findMostSevereTestStepResultBy (2 variants) + # To Review: findTestRunDuration (1 variant) - This strictly speaking isn't a findBy but is located within them + + def count_test_cases_started + find_all_test_case_started.length + end + + def find_all_pickles + repository.pickle_by_id.values + end + + def find_all_pickle_steps + repository.pickle_step_by_id.values + end + + def find_all_step_definitions + repository.step_definition_by_id.values + end + + # This finds all test cases from the following conditions (UNION) + # -> Test cases that have started, but not yet finished + # -> Test cases that have started, finished, but that will NOT be retried + def find_all_test_case_started + repository.test_case_started_by_id.values.select do |test_case_started| + test_case_finished = find_test_case_finished_by(test_case_started) + test_case_finished.nil? || !test_case_finished.will_be_retried + end + end + + # This finds all test cases that have finished AND will not be retried + def find_all_test_case_finished + repository.test_case_finished_by_test_case_started_id.values.reject(&:will_be_retried) + end + + def find_all_test_cases + repository.test_case_by_id.values + end + + def find_all_test_run_hook_started + repository.test_run_hook_started_by_id.values + end + + def find_all_test_run_hook_finished + repository.test_run_hook_finished_by_test_run_hook_started_id.values + end + + def find_all_test_step_started + repository.test_steps_started_by_test_case_started_id.values.flatten + end + + def find_all_test_step_finished + repository.test_steps_finished_by_test_case_started_id.values.flatten + end + + def find_all_test_steps + repository.test_step_by_id.values + end + + # This method will be called with 1 of these 3 messages + # [TestStep || TestRunHookStarted || TestRunHookFinished] + def find_hook_by(message) + ensure_only_message_types!(message, %i[test_step test_run_hook_started test_run_hook_finished], '#find_hook_by') + + if message.is_a?(Cucumber::Messages::TestRunHookFinished) + test_run_hook_started_message = find_test_run_hook_started_by(message) + test_run_hook_started_message ? find_hook_by(test_run_hook_started_message) : nil + else + repository.hook_by_id[message.hook_id] + end + end + + def find_meta + repository.meta + end + + # This method will be called with 1 of these 2 messages + # [TestCaseStarted || TestCaseFinished] + def find_most_severe_test_step_result_by(message) + ensure_only_message_types!(message, %i[test_case_started test_case_finished], '#find_most_severe_test_step_result_by') + + if message.is_a?(Cucumber::Messages::TestCaseStarted) + find_test_steps_finished_by(message) + .map(&:test_step_result) + .max_by { |test_step_result| test_step_result_rankings[test_step_result.status] } + # Java code: "PREVIOUS".max(comparing(TestStepResult::getStatus, new TestStepResultStatusComparator())); + else + test_case_started_message = find_test_case_started_by(message) + test_case_started_message && find_most_severe_test_step_result_by(test_case_started_message) + # Java code: return findTestCaseStartedBy(testCaseFinished).flatMap(this::findMostSevereTestStepResultBy); + end + end + + # This method will be called with 1 of these 5 messages + # [TestCase || TestCaseStarted || TestCaseFinished || TestStepStarted || TestStepFinished] + def find_pickle_by(message) + ensure_only_message_types!(message, %i[test_case test_case_started test_case_finished test_step_started test_step_finished], '#find_pickle_by') + + test_case_message = message.is_a?(Cucumber::Messages::TestCase) ? message : find_test_case_by(message) + repository.pickle_by_id[test_case_message.pickle_id] + end + + # This method will be called with only 1 message + # [TestStep] + def find_pickle_step_by(test_step) + ensure_only_message_types!(test_step, %i[test_step], '#find_pickle_step_by') + + repository.pickle_step_by_id[test_step.pickle_step_id] + end + + # This method will be called with only 1 message + # [PickleStep] + def find_step_by(pickle_step) + ensure_only_message_types!(pickle_step, %i[pickle_step], '#find_step_by') + + repository.step_by_id[pickle_step.ast_node_ids.first] + end + + # This method will be called with only 1 message + # [TestStep] + def find_step_definitions_by(test_step) + ensure_only_message_types!(test_step, %i[test_step], '#find_step_definitions_by') + + ids = test_step.step_definition_ids.nil? ? [] : test_step.step_definition_ids + ids.map { |id| repository.step_definition_by_id[id] }.compact + end + + # This method will be called with 1 of these 4 messages + # [TestCaseStarted || TestCaseFinished || TestStepStarted || TestStepFinished] + def find_test_case_by(message) + ensure_only_message_types!(message, %i[test_case_started test_case_finished test_step_started test_step_finished], '#find_test_case_by') + + test_case_started_message = message.is_a?(Cucumber::Messages::TestCaseStarted) ? message : find_test_case_started_by(message) + repository.test_case_by_id[test_case_started_message.test_case_id] end - def update(envelope) - @meta = envelope.meta if envelope.meta - update_gherkin_document(envelope.gherkin_document) if envelope.gherkin_document - update_pickle(envelope.pickle) if envelope.gherkin_document - @hooks_by_id[envelope.hook.id] = envelope.hook if envelope.hook - @step_definition_by_id[envelope.step_definition.id] = envelope.step_definition if envelope.step_definition - @test_run_started = envelope.test_run_started if envelope.test_run_started - update_test_case(envelope.test_case) if envelope.test_case - update_test_case_started(envelope.test_case_started) if envelope.test_case_started - update_test_step_started(envelope.test_step_started) if envelope.test_step_started - update_attachment(envelope.attachment) if envelope.attachment - update_test_step_finished(envelope.test_step_finished) if envelope.test_step_finished - update_test_case_finished(envelope.test_case_finished) if envelope.test_case_finished - @test_run_finished = envelope.test_run_finished if envelope.test_run_finished + # This method will be called with 1 of these 3 messages + # [TestCaseFinished || TestStepStarted || TestStepFinished] + def find_test_case_started_by(message) + ensure_only_message_types!(message, %i[test_case_finished test_step_started test_step_finished], '#find_test_case_started_by') + + repository.test_case_started_by_id[message.test_case_started_id] end - def update_gherkin_document(gherkin_document) - :not_yet_implemented + # This method will be called with only 1 message + # [TestCaseStarted] + def find_test_case_finished_by(test_case_started) + ensure_only_message_types!(test_case_started, %i[test_case_started], '#find_test_case_finished_by') + + repository.test_case_finished_by_test_case_started_id[test_case_started.id] end - def update_pickle(pickle) - :not_yet_implemented + def find_test_run_duration + if repository.test_run_started.nil? || repository.test_run_finished.nil? + nil + else + timestamp_to_time(repository.test_run_finished.timestamp) - timestamp_to_time(repository.test_run_started.timestamp) + end end - def update_test_case(test_case) - :not_yet_implemented + # This method will be called with only 1 message + # [TestRunHookFinished] + def find_test_run_hook_started_by(test_run_hook_finished) + ensure_only_message_types!(test_run_hook_finished, %i[test_run_hook_finished], '#find_test_run_hook_started_by') + + repository.test_run_hook_started_by_id[test_run_hook_finished.test_run_hook_started_id] end - def update_test_case_started(test_case_started) - :not_yet_implemented + # This method will be called with only 1 message + # [TestRunHookStarted] + def find_test_run_hook_finished_by(test_run_hook_started) + ensure_only_message_types!(test_run_hook_started, %i[test_run_hook_started], '#find_test_run_hook_finished_by') + + repository.test_run_hook_finished_by_test_run_hook_started_id[test_run_hook_started.id] + end + + def find_test_run_started + repository.test_run_started end - def update_test_step_started(test_step_started) - :not_yet_implemented + def find_test_run_finished + repository.test_run_finished end - def update_attachment(attachment) - :not_yet_implemented + # This method will be called with 1 of these 2 messages + # [TestStepStarted || TestStepFinished] + def find_test_step_by(message) + ensure_only_message_types!(message, %i[test_case_started test_case_finished], '#find_test_step_by') + + repository.test_step_by_id[message.test_step_id] end - def update_test_step_finished(test_step_finished) - :not_yet_implemented + # This method will be called with 1 of these 2 messages + # [TestCaseStarted || TestCaseFinished] + def find_test_steps_started_by(message) + ensure_only_message_types!(message, %i[test_case_started test_case_finished], '#find_test_steps_started_by') + + key = message.is_a?(Cucumber::Messages::TestCaseStarted) ? message.id : message.test_case_started_id + repository.test_steps_started_by_test_case_started_id.fetch(key, []) + end + + # This method will be called with 1 of these 2 messages + # [TestCaseStarted || TestCaseFinished] + def find_test_steps_finished_by(message) + ensure_only_message_types!(message, %i[test_case_started test_case_finished], '#find_test_steps_finished_by') + + if message.is_a?(Cucumber::Messages::TestCaseStarted) + repository.test_steps_finished_by_test_case_started_id.fetch(message.id, []) + else + test_case_started_message = find_test_case_started_by(message) + test_case_started_message.nil? ? [] : find_test_steps_finished_by(test_case_started_message) + end + end + + private + + def ensure_only_message_types!(supplied_message, permissible_message_types, method_name) + raise ArgumentError, "Supplied argument is not a Cucumber Message. Argument: #{supplied_message.class}" unless supplied_message.is_a?(Cucumber::Messages::Message) + + permitted_klasses = permissible_message_types.map { |message| message_types[message] } + raise ArgumentError, "Supplied message type '#{supplied_message.class}' is not permitted to be used when calling #{method_name}" unless permitted_klasses.include?(supplied_message.class) end - def update_test_case_finished(test_case_finished) - :not_yet_implemented + def message_types + { + pickle_step: Cucumber::Messages::PickleStep, + test_case: Cucumber::Messages::TestCase, + test_case_started: Cucumber::Messages::TestCaseStarted, + test_case_finished: Cucumber::Messages::TestCaseFinished, + test_run_hook_started: Cucumber::Messages::TestRunHookStarted, + test_run_hook_finished: Cucumber::Messages::TestRunHookFinished, + test_step: Cucumber::Messages::TestStep, + test_step_started: Cucumber::Messages::TestStepStarted, + test_step_finished: Cucumber::Messages::TestStepFinished + } end end end diff --git a/ruby/lib/cucumber/repository.rb b/ruby/lib/cucumber/repository.rb new file mode 100644 index 00000000..9c61dfbb --- /dev/null +++ b/ruby/lib/cucumber/repository.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +module Cucumber + # In memory repository i.e. a thread based link to cucumber-query + class Repository + attr_accessor :meta, :test_run_started, :test_run_finished + attr_reader :attachments_by_test_case_started_id, :attachments_by_test_run_hook_started_id, + :hook_by_id, + :pickle_by_id, :pickle_step_by_id, + :step_by_id, :step_definition_by_id, + :test_case_by_id, :test_case_started_by_id, :test_case_finished_by_test_case_started_id, + :test_run_hook_started_by_id, :test_run_hook_finished_by_test_run_hook_started_id, + :test_step_by_id, :test_steps_started_by_test_case_started_id, :test_steps_finished_by_test_case_started_id + + # TODO: Missing structs (2) + # final Map> suggestionsByPickleStepId = new LinkedHashMap<>(); + # final List 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] = [] } + @hook_by_id = {} + @pickle_by_id = {} + @pickle_step_by_id = {} + @step_by_id = {} + @step_definition_by_id = {} + @test_case_by_id = {} + @test_case_started_by_id = {} + @test_case_finished_by_test_case_started_id = {} + @test_run_hook_started_by_id = {} + @test_run_hook_finished_by_test_run_hook_started_id = {} + @test_step_by_id = {} + @test_steps_started_by_test_case_started_id = Hash.new { |hash, key| hash[key] = [] } + @test_steps_finished_by_test_case_started_id = Hash.new { |hash, key| hash[key] = [] } + end + + def update(envelope) + return self.meta = envelope.meta if envelope.meta + return self.test_run_started = envelope.test_run_started if envelope.test_run_started + return self.test_run_finished = envelope.test_run_finished if envelope.test_run_finished + return update_attachment(envelope.attachment) if envelope.attachment + return update_gherkin_document(envelope.gherkin_document) if envelope.gherkin_document + return update_hook(envelope.hook) if envelope.hook + return update_pickle(envelope.pickle) if envelope.pickle + return update_step_definition(envelope.step_definition) if envelope.step_definition + return update_test_run_hook_started(envelope.test_run_hook_started) if envelope.test_run_hook_started + return update_test_run_hook_finished(envelope.test_run_hook_finished) if envelope.test_run_hook_finished + return update_test_case_started(envelope.test_case_started) if envelope.test_case_started + return update_test_case_finished(envelope.test_case_finished) if envelope.test_case_finished + return update_test_step_started(envelope.test_step_started) if envelope.test_step_started + return update_test_step_finished(envelope.test_step_finished) if envelope.test_step_finished + return update_test_case(envelope.test_case) if envelope.test_case + + nil + end + + private + + def update_attachment(attachment) + attachments_by_test_case_started_id[attachment.test_case_started_id] << attachment if attachment.test_case_started_id + attachments_by_test_run_hook_started_id[attachment.test_run_hook_started_id] << attachment if attachment.test_run_hook_started_id + end + + def update_feature(feature) + feature.children.each do |feature_child| + update_steps(feature_child.background.steps) if feature_child.background + update_scenario(feature_child.scenario) if feature_child.scenario + next unless feature_child.rule + + feature_child.rule.children.each do |rule_child| + update_steps(rule_child.background.steps) if rule_child.background + update_scenario(rule_child.scenario) if rule_child.scenario + end + end + end + + def update_gherkin_document(gherkin_document) + # TODO: Update lineage at a later date. Java Impl -> lineageById.putAll(Lineages.of(document)); + update_feature(gherkin_document.feature) if gherkin_document.feature + end + + def update_hook(hook) + hook_by_id[hook.id] = hook + end + + def update_pickle(pickle) + pickle_by_id[pickle.id] = pickle + pickle.steps.each { |pickle_step| pickle_step_by_id[pickle_step.id] = pickle_step } + end + + def update_scenario(scenario) + update_steps(scenario.steps) + end + + def update_steps(steps) + steps.each { |step| step_by_id[step.id] = step } + end + + def update_step_definition(step_definition) + step_definition_by_id[step_definition.id] = step_definition + end + + def update_test_case(test_case) + test_case_by_id[test_case.id] = test_case + test_case.test_steps.each { |test_step| test_step_by_id[test_step.id] = test_step } + end + + def update_test_case_started(test_case_started) + test_case_started_by_id[test_case_started.id] = test_case_started + end + + def update_test_case_finished(test_case_finished) + test_case_finished_by_test_case_started_id[test_case_finished.test_case_started_id] = test_case_finished + end + + def update_test_run_hook_started(test_run_hook_started) + test_run_hook_started_by_id[test_run_hook_started.id] = test_run_hook_started + end + + def update_test_run_hook_finished(test_run_hook_finished) + test_run_hook_finished_by_test_run_hook_started_id[test_run_hook_finished.test_run_hook_started_id] = test_run_hook_finished + end + + def update_test_step_started(test_step_started) + test_steps_started_by_test_case_started_id[test_step_started.test_case_started_id] << test_step_started + end + + def update_test_step_finished(test_step_finished) + test_steps_finished_by_test_case_started_id[test_step_finished.test_case_started_id] << test_step_finished + end + end +end diff --git a/ruby/spec/cucumber/query_spec.rb b/ruby/spec/cucumber/query_spec.rb new file mode 100644 index 00000000..30ce9f8c --- /dev/null +++ b/ruby/spec/cucumber/query_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'json' +require 'cucumber/query' + +RSpec.describe Cucumber::Query do + SOURCE_NAMES = %w[attachments empty hooks minimal rules].freeze + + QUERIES = { + 'findAllPickles' => ->(query) { query.find_all_pickles.length }, + 'findAllTestCases' => ->(query) { query.find_all_test_cases.length }, + 'findAllTestSteps' => ->(query) { query.find_all_test_steps.length }, + 'findTestCaseBy' => lambda do |query| + results = {} + results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_test_case_by(message).id } + results['testCaseFinished'] = query.find_all_test_case_finished.map { |message| query.find_test_case_by(message).id } + results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_test_case_by(message).id } + results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_test_case_by(message).id } + results + end, + 'findPickleBy' => lambda do |query| + results = {} + results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_pickle_by(message).name } + results['testCaseFinished'] = query.find_all_test_case_finished.map { |message| query.find_pickle_by(message).name } + results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_pickle_by(message).name } + results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_pickle_by(message).name } + results + end + }.freeze + + def testdata_path(name) + File.expand_path("../support/#{name}", __dir__) + end + + def parse_ndjson_file(path) + Cucumber::Messages::Helpers::NdjsonToMessageEnumerator.new(File.read(path)) + end + + subject(:query) { described_class.new(repository) } + + let(:repository) { Cucumber::Repository.new } + + SOURCE_NAMES.product(QUERIES.to_a).each do |source_name, (query_name, query_proc)| + describe "executes the query '#{query_name}' against the testdata source '#{source_name}'" do + let(:source) { testdata_path("#{source_name}.ndjson") } + let(:expected_results_file) { testdata_path("#{source_name}.#{query_name}.results.json") } + let(:messages) { parse_ndjson_file(source) } + + before do + messages.each { |message| repository.update(message) } + end + + it 'returns the expected query result' do + evaluated_query = query_proc.call(query) + expected_query_result = JSON.parse(File.read(expected_results_file)) + + expect(evaluated_query).to eq(expected_query_result) + end + end + end +end diff --git a/ruby/spec/support/attachments.findAllPickles.results.json b/ruby/spec/support/attachments.findAllPickles.results.json new file mode 100644 index 00000000..c7930257 --- /dev/null +++ b/ruby/spec/support/attachments.findAllPickles.results.json @@ -0,0 +1 @@ +7 \ No newline at end of file diff --git a/ruby/spec/support/attachments.findAllTestCases.results.json b/ruby/spec/support/attachments.findAllTestCases.results.json new file mode 100644 index 00000000..c7930257 --- /dev/null +++ b/ruby/spec/support/attachments.findAllTestCases.results.json @@ -0,0 +1 @@ +7 \ No newline at end of file diff --git a/ruby/spec/support/attachments.findAllTestSteps.results.json b/ruby/spec/support/attachments.findAllTestSteps.results.json new file mode 100644 index 00000000..c7930257 --- /dev/null +++ b/ruby/spec/support/attachments.findAllTestSteps.results.json @@ -0,0 +1 @@ +7 \ No newline at end of file diff --git a/ruby/spec/support/attachments.findPickleBy.results.json b/ruby/spec/support/attachments.findPickleBy.results.json new file mode 100644 index 00000000..af46dda9 --- /dev/null +++ b/ruby/spec/support/attachments.findPickleBy.results.json @@ -0,0 +1,38 @@ +{ + "testCaseStarted" : [ + "Strings can be attached with a media type", + "Log text", + "Log ANSI coloured text", + "Log JSON", + "Byte arrays are base64-encoded regardless of media type", + "Attaching PDFs with a different filename", + "Attaching URIs" + ], + "testCaseFinished" : [ + "Strings can be attached with a media type", + "Log text", + "Log ANSI coloured text", + "Log JSON", + "Byte arrays are base64-encoded regardless of media type", + "Attaching PDFs with a different filename", + "Attaching URIs" + ], + "testStepStarted" : [ + "Strings can be attached with a media type", + "Log text", + "Log ANSI coloured text", + "Log JSON", + "Byte arrays are base64-encoded regardless of media type", + "Attaching PDFs with a different filename", + "Attaching URIs" + ], + "testStepFinished" : [ + "Strings can be attached with a media type", + "Log text", + "Log ANSI coloured text", + "Log JSON", + "Byte arrays are base64-encoded regardless of media type", + "Attaching PDFs with a different filename", + "Attaching URIs" + ] +} \ No newline at end of file diff --git a/ruby/spec/support/attachments.findTestCaseBy.results.json b/ruby/spec/support/attachments.findTestCaseBy.results.json new file mode 100644 index 00000000..298a33f9 --- /dev/null +++ b/ruby/spec/support/attachments.findTestCaseBy.results.json @@ -0,0 +1,38 @@ +{ + "testCaseStarted" : [ + "36", + "38", + "40", + "42", + "44", + "46", + "48" + ], + "testCaseFinished" : [ + "36", + "38", + "40", + "42", + "44", + "46", + "48" + ], + "testStepStarted" : [ + "36", + "38", + "40", + "42", + "44", + "46", + "48" + ], + "testStepFinished" : [ + "36", + "38", + "40", + "42", + "44", + "46", + "48" + ] +} \ No newline at end of file diff --git a/ruby/spec/support/attachments.ndjson b/ruby/spec/support/attachments.ndjson new file mode 100644 index 00000000..c16bb877 --- /dev/null +++ b/ruby/spec/support/attachments.ndjson @@ -0,0 +1,61 @@ +{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Attachments\n It is sometimes useful to take a screenshot while a scenario runs or capture some logs.\n\n Cucumber lets you `attach` arbitrary files during execution, and you can\n specify a content type for the contents.\n\n Formatters can then render these attachments in reports.\n\n Attachments must have a body and a content type.\n\n Scenario: Strings can be attached with a media type\n Beware that some formatters such as @cucumber/react use the media type\n to determine how to display an attachment.\n\n When the string \"hello\" is attached as \"application/octet-stream\"\n\n Scenario: Log text\n When the string \"hello\" is logged\n\n Scenario: Log ANSI coloured text\n When text with ANSI escapes is logged\n\n Scenario: Log JSON\n When the following string is attached as \"application/json\":\n ```\n {\"message\": \"The big question\", \"foo\": \"bar\"}\n ```\n\n Scenario: Byte arrays are base64-encoded regardless of media type\n When an array with 10 bytes is attached as \"text/plain\"\n\n Scenario: Attaching PDFs with a different filename\n When a PDF document is attached and renamed\n\n Scenario: Attaching URIs\n When a link to \"https://cucumber.io\" is attached\n","uri":"samples/attachments/attachments.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Attachments","description":" It is sometimes useful to take a screenshot while a scenario runs or capture some logs.\n\n Cucumber lets you `attach` arbitrary files during execution, and you can\n specify a content type for the contents.\n\n Formatters can then render these attachments in reports.\n\n Attachments must have a body and a content type.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario","name":"Strings can be attached with a media type","description":" Beware that some formatters such as @cucumber/react use the media type\n to determine how to display an attachment.","steps":[{"id":"0","location":{"line":15,"column":5},"keyword":"When ","keywordType":"Action","text":"the string \"hello\" is attached as \"application/octet-stream\""}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":17,"column":3},"keyword":"Scenario","name":"Log text","description":"","steps":[{"id":"2","location":{"line":18,"column":5},"keyword":"When ","keywordType":"Action","text":"the string \"hello\" is logged"}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":20,"column":3},"keyword":"Scenario","name":"Log ANSI coloured text","description":"","steps":[{"id":"4","location":{"line":21,"column":5},"keyword":"When ","keywordType":"Action","text":"text with ANSI escapes is logged"}],"examples":[]}},{"scenario":{"id":"7","tags":[],"location":{"line":23,"column":3},"keyword":"Scenario","name":"Log JSON","description":"","steps":[{"id":"6","location":{"line":24,"column":6},"keyword":"When ","keywordType":"Action","text":"the following string is attached as \"application/json\":","docString":{"location":{"line":25,"column":8},"content":"{\"message\": \"The big question\", \"foo\": \"bar\"}","delimiter":"```"}}],"examples":[]}},{"scenario":{"id":"9","tags":[],"location":{"line":29,"column":3},"keyword":"Scenario","name":"Byte arrays are base64-encoded regardless of media type","description":"","steps":[{"id":"8","location":{"line":30,"column":5},"keyword":"When ","keywordType":"Action","text":"an array with 10 bytes is attached as \"text/plain\""}],"examples":[]}},{"scenario":{"id":"11","tags":[],"location":{"line":32,"column":3},"keyword":"Scenario","name":"Attaching PDFs with a different filename","description":"","steps":[{"id":"10","location":{"line":33,"column":5},"keyword":"When ","keywordType":"Action","text":"a PDF document is attached and renamed"}],"examples":[]}},{"scenario":{"id":"13","tags":[],"location":{"line":35,"column":3},"keyword":"Scenario","name":"Attaching URIs","description":"","steps":[{"id":"12","location":{"line":36,"column":5},"keyword":"When ","keywordType":"Action","text":"a link to \"https://cucumber.io\" is attached"}],"examples":[]}}]},"comments":[],"uri":"samples/attachments/attachments.feature"}} +{"pickle":{"id":"15","uri":"samples/attachments/attachments.feature","astNodeIds":["1"],"tags":[],"name":"Strings can be attached with a media type","language":"en","steps":[{"id":"14","text":"the string \"hello\" is attached as \"application/octet-stream\"","type":"Action","astNodeIds":["0"]}]}} +{"pickle":{"id":"17","uri":"samples/attachments/attachments.feature","astNodeIds":["3"],"tags":[],"name":"Log text","language":"en","steps":[{"id":"16","text":"the string \"hello\" is logged","type":"Action","astNodeIds":["2"]}]}} +{"pickle":{"id":"19","uri":"samples/attachments/attachments.feature","astNodeIds":["5"],"tags":[],"name":"Log ANSI coloured text","language":"en","steps":[{"id":"18","text":"text with ANSI escapes is logged","type":"Action","astNodeIds":["4"]}]}} +{"pickle":{"id":"21","uri":"samples/attachments/attachments.feature","astNodeIds":["7"],"tags":[],"name":"Log JSON","language":"en","steps":[{"id":"20","text":"the following string is attached as \"application/json\":","type":"Action","argument":{"docString":{"content":"{\"message\": \"The big question\", \"foo\": \"bar\"}"}},"astNodeIds":["6"]}]}} +{"pickle":{"id":"23","uri":"samples/attachments/attachments.feature","astNodeIds":["9"],"tags":[],"name":"Byte arrays are base64-encoded regardless of media type","language":"en","steps":[{"id":"22","text":"an array with 10 bytes is attached as \"text/plain\"","type":"Action","astNodeIds":["8"]}]}} +{"pickle":{"id":"25","uri":"samples/attachments/attachments.feature","astNodeIds":["11"],"tags":[],"name":"Attaching PDFs with a different filename","language":"en","steps":[{"id":"24","text":"a PDF document is attached and renamed","type":"Action","astNodeIds":["10"]}]}} +{"pickle":{"id":"27","uri":"samples/attachments/attachments.feature","astNodeIds":["13"],"tags":[],"name":"Attaching URIs","language":"en","steps":[{"id":"26","text":"a link to \"https://cucumber.io\" is attached","type":"Action","astNodeIds":["12"]}]}} +{"stepDefinition":{"id":"28","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the string {string} is attached as {string}"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":4}}}} +{"stepDefinition":{"id":"29","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the string {string} is logged"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":8}}}} +{"stepDefinition":{"id":"30","pattern":{"type":"CUCUMBER_EXPRESSION","source":"text with ANSI escapes is logged"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":12}}}} +{"stepDefinition":{"id":"31","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the following string is attached as {string}:"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":18}}}} +{"stepDefinition":{"id":"32","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an array with {int} bytes is attached as {string}"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":22}}}} +{"stepDefinition":{"id":"33","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a PDF document is attached and renamed"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":31}}}} +{"stepDefinition":{"id":"34","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a link to {string} is attached"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":38}}}} +{"testRunStarted":{"id":"35","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"36","pickleId":"15","testSteps":[{"id":"37","pickleStepId":"14","stepDefinitionIds":["28"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":11,"value":"\"hello\"","children":[{"start":12,"value":"hello","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"},{"group":{"start":34,"value":"\"application/octet-stream\"","children":[{"start":35,"value":"application/octet-stream","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} +{"testCase":{"id":"38","pickleId":"17","testSteps":[{"id":"39","pickleStepId":"16","stepDefinitionIds":["29"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":11,"value":"\"hello\"","children":[{"start":12,"value":"hello","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} +{"testCase":{"id":"40","pickleId":"19","testSteps":[{"id":"41","pickleStepId":"18","stepDefinitionIds":["30"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"35"}} +{"testCase":{"id":"42","pickleId":"21","testSteps":[{"id":"43","pickleStepId":"20","stepDefinitionIds":["31"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":36,"value":"\"application/json\"","children":[{"start":37,"value":"application/json","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} +{"testCase":{"id":"44","pickleId":"23","testSteps":[{"id":"45","pickleStepId":"22","stepDefinitionIds":["32"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"10","children":[]},"parameterTypeName":"int"},{"group":{"start":38,"value":"\"text/plain\"","children":[{"start":39,"value":"text/plain","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} +{"testCase":{"id":"46","pickleId":"25","testSteps":[{"id":"47","pickleStepId":"24","stepDefinitionIds":["33"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"35"}} +{"testCase":{"id":"48","pickleId":"27","testSteps":[{"id":"49","pickleStepId":"26","stepDefinitionIds":["34"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"\"https://cucumber.io\"","children":[{"start":11,"value":"https://cucumber.io","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} +{"testCaseStarted":{"id":"50","testCaseId":"36","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"50","testStepId":"37","timestamp":{"seconds":0,"nanos":2000000}}} +{"attachment":{"testCaseStartedId":"50","testStepId":"37","body":"hello","contentEncoding":"IDENTITY","mediaType":"application/octet-stream"}} +{"testStepFinished":{"testCaseStartedId":"50","testStepId":"37","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"50","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"51","testCaseId":"38","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"51","testStepId":"39","timestamp":{"seconds":0,"nanos":6000000}}} +{"attachment":{"testCaseStartedId":"51","testStepId":"39","body":"hello","contentEncoding":"IDENTITY","mediaType":"text/x.cucumber.log+plain"}} +{"testStepFinished":{"testCaseStartedId":"51","testStepId":"39","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testCaseFinished":{"testCaseStartedId":"51","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"52","testCaseId":"40","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"52","testStepId":"41","timestamp":{"seconds":0,"nanos":10000000}}} +{"attachment":{"testCaseStartedId":"52","testStepId":"41","body":"This displays a \u001b[31mr\u001b[0m\u001b[91ma\u001b[0m\u001b[33mi\u001b[0m\u001b[32mn\u001b[0m\u001b[34mb\u001b[0m\u001b[95mo\u001b[0m\u001b[35mw\u001b[0m","contentEncoding":"IDENTITY","mediaType":"text/x.cucumber.log+plain"}} +{"testStepFinished":{"testCaseStartedId":"52","testStepId":"41","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testCaseFinished":{"testCaseStartedId":"52","timestamp":{"seconds":0,"nanos":12000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"53","testCaseId":"42","timestamp":{"seconds":0,"nanos":13000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"53","testStepId":"43","timestamp":{"seconds":0,"nanos":14000000}}} +{"attachment":{"testCaseStartedId":"53","testStepId":"43","body":"{\"message\": \"The big question\", \"foo\": \"bar\"}","contentEncoding":"IDENTITY","mediaType":"application/json"}} +{"testStepFinished":{"testCaseStartedId":"53","testStepId":"43","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testCaseFinished":{"testCaseStartedId":"53","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"54","testCaseId":"44","timestamp":{"seconds":0,"nanos":17000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"54","testStepId":"45","timestamp":{"seconds":0,"nanos":18000000}}} +{"attachment":{"testCaseStartedId":"54","testStepId":"45","body":"AAECAwQFBgcICQ==","contentEncoding":"BASE64","mediaType":"text/plain"}} +{"testStepFinished":{"testCaseStartedId":"54","testStepId":"45","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testCaseFinished":{"testCaseStartedId":"54","timestamp":{"seconds":0,"nanos":20000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"55","testCaseId":"46","timestamp":{"seconds":0,"nanos":21000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"55","testStepId":"47","timestamp":{"seconds":0,"nanos":22000000}}} +{"attachment":{"testCaseStartedId":"55","testStepId":"47","body":"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RmlsZTIgOCAwIFI+PgplbmRvYmoKMTAgMCBvYmoKPDwvVHlwZSAvRm9udAovRm9udERlc2NyaXB0b3IgOSAwIFIKL0Jhc2VGb250IC9BQUFBQUErQXJpYWxNVAovU3VidHlwZSAvQ0lERm9udFR5cGUyCi9DSURUb0dJRE1hcCAvSWRlbnRpdHkKL0NJRFN5c3RlbUluZm8gPDwvUmVnaXN0cnkgKEFkb2JlKQovT3JkZXJpbmcgKElkZW50aXR5KQovU3VwcGxlbWVudCAwPj4KL1cgWzAgWzc1MF0gNTUgWzYxMC44Mzk4NF0gNzIgWzU1Ni4xNTIzNF0gODcgWzI3Ny44MzIwM11dCi9EVyA1MDA+PgplbmRvYmoKMTEgMCBvYmoKPDwvRmlsdGVyIC9GbGF0ZURlY29kZQovTGVuZ3RoIDI1MD4+IHN0cmVhbQp4nF2Qy2rEIBSG9z7FWU4Xg0lmMtNFEMqUQha90LQPYPQkFRoVYxZ5+3pJU6ig8PP/n+dCb+1jq5UH+uaM6NDDoLR0OJvFCYQeR6VJWYFUwm8qvWLiltAAd+vscWr1YEjTAND34M7erXB4kKbHO0JfnUSn9AiHz1sXdLdY+40Tag8FYQwkDuGnZ25f+IRAE3ZsZfCVX4+B+Ut8rBahSrrM3QgjcbZcoON6RNIU4TBonsJhBLX851eZ6gfxxV1Mn64hXRT1mUV1vk/qUid2S5W/zF6ivmQos9fTls5+LBqXs08kFufCMGmDaYrYv9K4L9kaG6l4fwAdQH9hCmVuZHN0cmVhbQplbmRvYmoKNCAwIG9iago8PC9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9UeXBlMAovQmFzZUZvbnQgL0FBQUFBQStBcmlhbE1UCi9FbmNvZGluZyAvSWRlbnRpdHktSAovRGVzY2VuZGFudEZvbnRzIFsxMCAwIFJdCi9Ub1VuaWNvZGUgMTEgMCBSPj4KZW5kb2JqCnhyZWYKMCAxMgowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMTUgMDAwMDAgbiAKMDAwMDAwMDM4MiAwMDAwMCBuIAowMDAwMDAwMTA4IDAwMDAwIG4gCjAwMDAwMDk2MDYgMDAwMDAgbiAKMDAwMDAwMDE0NSAwMDAwMCBuIAowMDAwMDAwNTkwIDAwMDAwIG4gCjAwMDAwMDA2NDUgMDAwMDAgbiAKMDAwMDAwMDY5MiAwMDAwMCBuIAowMDAwMDA4Nzg3IDAwMDAwIG4gCjAwMDAwMDkwMjEgMDAwMDAgbiAKMDAwMDAwOTI4NSAwMDAwMCBuIAp0cmFpbGVyCjw8L1NpemUgMTIKL1Jvb3QgNyAwIFIKL0luZm8gMSAwIFI+PgpzdGFydHhyZWYKOTc0NQolJUVPRgo=","contentEncoding":"BASE64","mediaType":"application/pdf","fileName":"renamed.pdf"}} +{"testStepFinished":{"testCaseStartedId":"55","testStepId":"47","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}} +{"testCaseFinished":{"testCaseStartedId":"55","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"56","testCaseId":"48","timestamp":{"seconds":0,"nanos":25000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"49","timestamp":{"seconds":0,"nanos":26000000}}} +{"attachment":{"testCaseStartedId":"56","testStepId":"49","body":"https://cucumber.io","contentEncoding":"IDENTITY","mediaType":"text/uri-list"}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"49","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":27000000}}} +{"testCaseFinished":{"testCaseStartedId":"56","timestamp":{"seconds":0,"nanos":28000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"35","timestamp":{"seconds":0,"nanos":29000000},"success":true}} diff --git a/ruby/spec/support/empty.findAllPickles.results.json b/ruby/spec/support/empty.findAllPickles.results.json new file mode 100644 index 00000000..56a6051c --- /dev/null +++ b/ruby/spec/support/empty.findAllPickles.results.json @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/ruby/spec/support/empty.findAllTestCases.results.json b/ruby/spec/support/empty.findAllTestCases.results.json new file mode 100644 index 00000000..56a6051c --- /dev/null +++ b/ruby/spec/support/empty.findAllTestCases.results.json @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/ruby/spec/support/empty.findAllTestSteps.results.json b/ruby/spec/support/empty.findAllTestSteps.results.json new file mode 100644 index 00000000..c2270834 --- /dev/null +++ b/ruby/spec/support/empty.findAllTestSteps.results.json @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/ruby/spec/support/empty.findPickleBy.results.json b/ruby/spec/support/empty.findPickleBy.results.json new file mode 100644 index 00000000..377f01bf --- /dev/null +++ b/ruby/spec/support/empty.findPickleBy.results.json @@ -0,0 +1,10 @@ +{ + "testCaseStarted" : [ + "Blank Scenario" + ], + "testCaseFinished" : [ + "Blank Scenario" + ], + "testStepStarted" : [ ], + "testStepFinished" : [ ] +} \ No newline at end of file diff --git a/ruby/spec/support/empty.findTestCaseBy.results.json b/ruby/spec/support/empty.findTestCaseBy.results.json new file mode 100644 index 00000000..5a7775b3 --- /dev/null +++ b/ruby/spec/support/empty.findTestCaseBy.results.json @@ -0,0 +1,10 @@ +{ + "testCaseStarted" : [ + "3" + ], + "testCaseFinished" : [ + "3" + ], + "testStepStarted" : [ ], + "testStepFinished" : [ ] +} \ No newline at end of file diff --git a/ruby/spec/support/empty.ndjson b/ruby/spec/support/empty.ndjson new file mode 100644 index 00000000..9e2b6bd3 --- /dev/null +++ b/ruby/spec/support/empty.ndjson @@ -0,0 +1,9 @@ +{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Empty Scenarios\n Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.\n\n Scenario: Blank Scenario\n","uri":"samples/empty/empty.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Empty Scenarios","description":" Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.","children":[{"scenario":{"id":"0","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"Blank Scenario","description":"","steps":[],"examples":[]}}]},"comments":[],"uri":"samples/empty/empty.feature"}} +{"pickle":{"id":"1","uri":"samples/empty/empty.feature","astNodeIds":["0"],"tags":[],"name":"Blank Scenario","language":"en","steps":[]}} +{"testRunStarted":{"id":"2","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"3","pickleId":"1","testSteps":[],"testRunStartedId":"2"}} +{"testCaseStarted":{"id":"4","testCaseId":"3","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testCaseFinished":{"testCaseStartedId":"4","timestamp":{"seconds":0,"nanos":2000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"2","timestamp":{"seconds":0,"nanos":3000000},"success":true}} diff --git a/ruby/spec/support/hooks.findAllPickles.results.json b/ruby/spec/support/hooks.findAllPickles.results.json new file mode 100644 index 00000000..d8263ee9 --- /dev/null +++ b/ruby/spec/support/hooks.findAllPickles.results.json @@ -0,0 +1 @@ +2 \ No newline at end of file diff --git a/ruby/spec/support/hooks.findAllTestCases.results.json b/ruby/spec/support/hooks.findAllTestCases.results.json new file mode 100644 index 00000000..d8263ee9 --- /dev/null +++ b/ruby/spec/support/hooks.findAllTestCases.results.json @@ -0,0 +1 @@ +2 \ No newline at end of file diff --git a/ruby/spec/support/hooks.findAllTestSteps.results.json b/ruby/spec/support/hooks.findAllTestSteps.results.json new file mode 100644 index 00000000..62f94575 --- /dev/null +++ b/ruby/spec/support/hooks.findAllTestSteps.results.json @@ -0,0 +1 @@ +6 \ No newline at end of file diff --git a/ruby/spec/support/hooks.findPickleBy.results.json b/ruby/spec/support/hooks.findPickleBy.results.json new file mode 100644 index 00000000..4bad4bc7 --- /dev/null +++ b/ruby/spec/support/hooks.findPickleBy.results.json @@ -0,0 +1,26 @@ +{ + "testCaseStarted" : [ + "No tags and a passed step", + "No tags and a failed step" + ], + "testCaseFinished" : [ + "No tags and a passed step", + "No tags and a failed step" + ], + "testStepStarted" : [ + "No tags and a passed step", + "No tags and a passed step", + "No tags and a passed step", + "No tags and a failed step", + "No tags and a failed step", + "No tags and a failed step" + ], + "testStepFinished" : [ + "No tags and a passed step", + "No tags and a passed step", + "No tags and a passed step", + "No tags and a failed step", + "No tags and a failed step", + "No tags and a failed step" + ] +} \ No newline at end of file diff --git a/ruby/spec/support/hooks.findTestCaseBy.results.json b/ruby/spec/support/hooks.findTestCaseBy.results.json new file mode 100644 index 00000000..e6dc9fa5 --- /dev/null +++ b/ruby/spec/support/hooks.findTestCaseBy.results.json @@ -0,0 +1,26 @@ +{ + "testCaseStarted" : [ + "13", + "17" + ], + "testCaseFinished" : [ + "13", + "17" + ], + "testStepStarted" : [ + "13", + "13", + "13", + "17", + "17", + "17" + ], + "testStepFinished" : [ + "13", + "13", + "13", + "17", + "17", + "17" + ] +} \ No newline at end of file diff --git a/ruby/spec/support/hooks.ndjson b/ruby/spec/support/hooks.ndjson new file mode 100644 index 00000000..cc0fc92e --- /dev/null +++ b/ruby/spec/support/hooks.ndjson @@ -0,0 +1,29 @@ +{"meta":{"protocolVersion":"30.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Hooks\n Hooks are special steps that run before or after each scenario's steps.\n\n Scenario: No tags and a passed step\n When a step passes\n\n Scenario: No tags and a failed step\n When a step fails\n","uri":"samples/hooks/hooks.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Hooks","description":" Hooks are special steps that run before or after each scenario's steps.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":4,"column":3},"keyword":"Scenario","name":"No tags and a passed step","description":"","steps":[{"id":"0","location":{"line":5,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"No tags and a failed step","description":"","steps":[{"id":"2","location":{"line":8,"column":5},"keyword":"When ","keywordType":"Action","text":"a step fails"}],"examples":[]}}]},"comments":[],"uri":"samples/hooks/hooks.feature"}} +{"pickle":{"id":"5","uri":"samples/hooks/hooks.feature","astNodeIds":["1"],"tags":[],"name":"No tags and a passed step","language":"en","steps":[{"id":"4","text":"a step passes","type":"Action","astNodeIds":["0"]}]}} +{"pickle":{"id":"7","uri":"samples/hooks/hooks.feature","astNodeIds":["3"],"tags":[],"name":"No tags and a failed step","language":"en","steps":[{"id":"6","text":"a step fails","type":"Action","astNodeIds":["2"]}]}} +{"stepDefinition":{"id":"9","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step passes"},"sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":7}}}} +{"stepDefinition":{"id":"10","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step fails"},"sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":11}}}} +{"hook":{"id":"8","type":"BEFORE_TEST_CASE","sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":3}}}} +{"hook":{"id":"11","type":"AFTER_TEST_CASE","sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":15}}}} +{"testRunStarted":{"id":"12","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"13","pickleId":"5","testSteps":[{"id":"14","hookId":"8"},{"id":"15","pickleStepId":"4","stepDefinitionIds":["9"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"16","hookId":"11"}],"testRunStartedId":"12"}} +{"testCase":{"id":"17","pickleId":"7","testSteps":[{"id":"18","hookId":"8"},{"id":"19","pickleStepId":"6","stepDefinitionIds":["10"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"20","hookId":"11"}],"testRunStartedId":"12"}} +{"testCaseStarted":{"id":"21","testCaseId":"13","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"21","testStepId":"14","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"21","testStepId":"14","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"21","testStepId":"15","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"21","testStepId":"15","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepStarted":{"testCaseStartedId":"21","testStepId":"16","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"21","testStepId":"16","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testCaseFinished":{"testCaseStartedId":"21","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"22","testCaseId":"17","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"22","testStepId":"18","timestamp":{"seconds":0,"nanos":10000000}}} +{"testStepFinished":{"testCaseStartedId":"22","testStepId":"18","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testStepStarted":{"testCaseStartedId":"22","testStepId":"19","timestamp":{"seconds":0,"nanos":12000000}}} +{"testStepFinished":{"testCaseStartedId":"22","testStepId":"19","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/hooks/hooks.feature:8"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}} +{"testStepStarted":{"testCaseStartedId":"22","testStepId":"20","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"22","testStepId":"20","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testCaseFinished":{"testCaseStartedId":"22","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"12","timestamp":{"seconds":0,"nanos":17000000},"success":false}} diff --git a/ruby/spec/support/minimal.findAllPickles.results.json b/ruby/spec/support/minimal.findAllPickles.results.json new file mode 100644 index 00000000..56a6051c --- /dev/null +++ b/ruby/spec/support/minimal.findAllPickles.results.json @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/ruby/spec/support/minimal.findAllTestCases.results.json b/ruby/spec/support/minimal.findAllTestCases.results.json new file mode 100644 index 00000000..56a6051c --- /dev/null +++ b/ruby/spec/support/minimal.findAllTestCases.results.json @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/ruby/spec/support/minimal.findAllTestSteps.results.json b/ruby/spec/support/minimal.findAllTestSteps.results.json new file mode 100644 index 00000000..56a6051c --- /dev/null +++ b/ruby/spec/support/minimal.findAllTestSteps.results.json @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/ruby/spec/support/minimal.findPickleBy.results.json b/ruby/spec/support/minimal.findPickleBy.results.json new file mode 100644 index 00000000..275ca2f3 --- /dev/null +++ b/ruby/spec/support/minimal.findPickleBy.results.json @@ -0,0 +1,14 @@ +{ + "testCaseStarted" : [ + "cukes" + ], + "testCaseFinished" : [ + "cukes" + ], + "testStepStarted" : [ + "cukes" + ], + "testStepFinished" : [ + "cukes" + ] +} \ No newline at end of file diff --git a/ruby/spec/support/minimal.findTestCaseBy.results.json b/ruby/spec/support/minimal.findTestCaseBy.results.json new file mode 100644 index 00000000..e5c7e2f6 --- /dev/null +++ b/ruby/spec/support/minimal.findTestCaseBy.results.json @@ -0,0 +1,14 @@ +{ + "testCaseStarted" : [ + "6" + ], + "testCaseFinished" : [ + "6" + ], + "testStepStarted" : [ + "6" + ], + "testStepFinished" : [ + "6" + ] +} \ No newline at end of file diff --git a/ruby/spec/support/minimal.ndjson b/ruby/spec/support/minimal.ndjson new file mode 100644 index 00000000..f35958f6 --- /dev/null +++ b/ruby/spec/support/minimal.ndjson @@ -0,0 +1,12 @@ +{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: minimal\n \n Cucumber doesn't execute this markdown, but @cucumber/react renders it.\n \n * This is\n * a bullet\n * list\n \n Scenario: cukes\n Given I have 42 cukes in my belly\n","uri":"samples/minimal/minimal.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"minimal","description":" Cucumber doesn't execute this markdown, but @cucumber/react renders it.\n \n * This is\n * a bullet\n * list","children":[{"scenario":{"id":"1","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"cukes","description":"","steps":[{"id":"0","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"I have 42 cukes in my belly"}],"examples":[]}}]},"comments":[],"uri":"samples/minimal/minimal.feature"}} +{"pickle":{"id":"3","uri":"samples/minimal/minimal.feature","astNodeIds":["1"],"tags":[],"name":"cukes","language":"en","steps":[{"id":"2","text":"I have 42 cukes in my belly","type":"Context","astNodeIds":["0"]}]}} +{"stepDefinition":{"id":"4","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I have {int} cukes in my belly"},"sourceReference":{"uri":"samples/minimal/minimal.ts","location":{"line":3}}}} +{"testRunStarted":{"id":"5","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"6","pickleId":"3","testSteps":[{"id":"7","pickleStepId":"2","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":7,"value":"42","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"5"}} +{"testCaseStarted":{"id":"8","testCaseId":"6","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"8","testStepId":"7","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"5","timestamp":{"seconds":0,"nanos":5000000},"success":true}} diff --git a/ruby/spec/support/rules.findAllPickles.results.json b/ruby/spec/support/rules.findAllPickles.results.json new file mode 100644 index 00000000..e440e5c8 --- /dev/null +++ b/ruby/spec/support/rules.findAllPickles.results.json @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/ruby/spec/support/rules.findAllTestCases.results.json b/ruby/spec/support/rules.findAllTestCases.results.json new file mode 100644 index 00000000..e440e5c8 --- /dev/null +++ b/ruby/spec/support/rules.findAllTestCases.results.json @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/ruby/spec/support/rules.findAllTestSteps.results.json b/ruby/spec/support/rules.findAllTestSteps.results.json new file mode 100644 index 00000000..3cacc0b9 --- /dev/null +++ b/ruby/spec/support/rules.findAllTestSteps.results.json @@ -0,0 +1 @@ +12 \ No newline at end of file diff --git a/ruby/spec/support/rules.findPickleBy.results.json b/ruby/spec/support/rules.findPickleBy.results.json new file mode 100644 index 00000000..17a87cba --- /dev/null +++ b/ruby/spec/support/rules.findPickleBy.results.json @@ -0,0 +1,40 @@ +{ + "testCaseStarted" : [ + "Not enough money", + "Enough money", + "No chocolates left" + ], + "testCaseFinished" : [ + "Not enough money", + "Enough money", + "No chocolates left" + ], + "testStepStarted" : [ + "Not enough money", + "Not enough money", + "Not enough money", + "Not enough money", + "Enough money", + "Enough money", + "Enough money", + "Enough money", + "No chocolates left", + "No chocolates left", + "No chocolates left", + "No chocolates left" + ], + "testStepFinished" : [ + "Not enough money", + "Not enough money", + "Not enough money", + "Not enough money", + "Enough money", + "Enough money", + "Enough money", + "Enough money", + "No chocolates left", + "No chocolates left", + "No chocolates left", + "No chocolates left" + ] +} \ No newline at end of file diff --git a/ruby/spec/support/rules.findTestCaseBy.results.json b/ruby/spec/support/rules.findTestCaseBy.results.json new file mode 100644 index 00000000..06e9bbc1 --- /dev/null +++ b/ruby/spec/support/rules.findTestCaseBy.results.json @@ -0,0 +1,40 @@ +{ + "testCaseStarted" : [ + "40", + "45", + "50" + ], + "testCaseFinished" : [ + "40", + "45", + "50" + ], + "testStepStarted" : [ + "40", + "40", + "40", + "40", + "45", + "45", + "45", + "45", + "50", + "50", + "50", + "50" + ], + "testStepFinished" : [ + "40", + "40", + "40", + "40", + "45", + "45", + "45", + "45", + "50", + "50", + "50", + "50" + ] +} \ No newline at end of file diff --git a/ruby/spec/support/rules.ndjson b/ruby/spec/support/rules.ndjson new file mode 100644 index 00000000..81bb984b --- /dev/null +++ b/ruby/spec/support/rules.ndjson @@ -0,0 +1,47 @@ +{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Usage of a `Rule`\n You can place scenarios inside rules. This makes it possible to structure Gherkin documents\n in the same way as [example maps](https://cucumber.io/blog/bdd/example-mapping-introduction/).\n\n You can also use the Examples synonym for Scenario to make them even similar.\n\n Rule: A sale cannot happen if the customer does not have enough money\n # Unhappy path\n Example: Not enough money\n Given the customer has 100 cents\n And there are chocolate bars in stock\n When the customer tries to buy a 125 cent chocolate bar\n Then the sale should not happen\n\n # Happy path\n Example: Enough money\n Given the customer has 100 cents\n And there are chocolate bars in stock\n When the customer tries to buy a 75 cent chocolate bar\n Then the sale should happen\n\n @some-tag\n Rule: a sale cannot happen if there is no stock\n # Unhappy path\n Example: No chocolates left\n Given the customer has 100 cents\n And there are no chocolate bars in stock\n When the customer tries to buy a 1 cent chocolate bar\n Then the sale should not happen\n","uri":"samples/rules/rules.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Usage of a `Rule`","description":" You can place scenarios inside rules. This makes it possible to structure Gherkin documents\n in the same way as [example maps](https://cucumber.io/blog/bdd/example-mapping-introduction/).\n\n You can also use the Examples synonym for Scenario to make them even similar.","children":[{"rule":{"id":"10","location":{"line":7,"column":3},"keyword":"Rule","name":"A sale cannot happen if the customer does not have enough money","description":"","children":[{"scenario":{"id":"4","tags":[],"location":{"line":9,"column":5},"keyword":"Example","name":"Not enough money","description":"","steps":[{"id":"0","location":{"line":10,"column":7},"keyword":"Given ","keywordType":"Context","text":"the customer has 100 cents"},{"id":"1","location":{"line":11,"column":7},"keyword":"And ","keywordType":"Conjunction","text":"there are chocolate bars in stock"},{"id":"2","location":{"line":12,"column":7},"keyword":"When ","keywordType":"Action","text":"the customer tries to buy a 125 cent chocolate bar"},{"id":"3","location":{"line":13,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"the sale should not happen"}],"examples":[]}},{"scenario":{"id":"9","tags":[],"location":{"line":16,"column":5},"keyword":"Example","name":"Enough money","description":"","steps":[{"id":"5","location":{"line":17,"column":7},"keyword":"Given ","keywordType":"Context","text":"the customer has 100 cents"},{"id":"6","location":{"line":18,"column":7},"keyword":"And ","keywordType":"Conjunction","text":"there are chocolate bars in stock"},{"id":"7","location":{"line":19,"column":7},"keyword":"When ","keywordType":"Action","text":"the customer tries to buy a 75 cent chocolate bar"},{"id":"8","location":{"line":20,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"the sale should happen"}],"examples":[]}}],"tags":[]}},{"rule":{"id":"17","location":{"line":23,"column":3},"keyword":"Rule","name":"a sale cannot happen if there is no stock","description":"","children":[{"scenario":{"id":"15","tags":[],"location":{"line":25,"column":5},"keyword":"Example","name":"No chocolates left","description":"","steps":[{"id":"11","location":{"line":26,"column":7},"keyword":"Given ","keywordType":"Context","text":"the customer has 100 cents"},{"id":"12","location":{"line":27,"column":7},"keyword":"And ","keywordType":"Conjunction","text":"there are no chocolate bars in stock"},{"id":"13","location":{"line":28,"column":7},"keyword":"When ","keywordType":"Action","text":"the customer tries to buy a 1 cent chocolate bar"},{"id":"14","location":{"line":29,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"the sale should not happen"}],"examples":[]}}],"tags":[{"location":{"line":22,"column":3},"name":"@some-tag","id":"16"}]}}]},"comments":[{"location":{"line":8,"column":1},"text":" # Unhappy path"},{"location":{"line":15,"column":1},"text":" # Happy path"},{"location":{"line":24,"column":1},"text":" # Unhappy path"}],"uri":"samples/rules/rules.feature"}} +{"pickle":{"id":"22","uri":"samples/rules/rules.feature","astNodeIds":["4"],"tags":[],"name":"Not enough money","language":"en","steps":[{"id":"18","text":"the customer has 100 cents","type":"Context","astNodeIds":["0"]},{"id":"19","text":"there are chocolate bars in stock","type":"Context","astNodeIds":["1"]},{"id":"20","text":"the customer tries to buy a 125 cent chocolate bar","type":"Action","astNodeIds":["2"]},{"id":"21","text":"the sale should not happen","type":"Outcome","astNodeIds":["3"]}]}} +{"pickle":{"id":"27","uri":"samples/rules/rules.feature","astNodeIds":["9"],"tags":[],"name":"Enough money","language":"en","steps":[{"id":"23","text":"the customer has 100 cents","type":"Context","astNodeIds":["5"]},{"id":"24","text":"there are chocolate bars in stock","type":"Context","astNodeIds":["6"]},{"id":"25","text":"the customer tries to buy a 75 cent chocolate bar","type":"Action","astNodeIds":["7"]},{"id":"26","text":"the sale should happen","type":"Outcome","astNodeIds":["8"]}]}} +{"pickle":{"id":"32","uri":"samples/rules/rules.feature","astNodeIds":["15"],"tags":[{"name":"@some-tag","astNodeId":"16"}],"name":"No chocolates left","language":"en","steps":[{"id":"28","text":"the customer has 100 cents","type":"Context","astNodeIds":["11"]},{"id":"29","text":"there are no chocolate bars in stock","type":"Context","astNodeIds":["12"]},{"id":"30","text":"the customer tries to buy a 1 cent chocolate bar","type":"Action","astNodeIds":["13"]},{"id":"31","text":"the sale should not happen","type":"Outcome","astNodeIds":["14"]}]}} +{"stepDefinition":{"id":"33","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the customer has {int} cents"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":4}}}} +{"stepDefinition":{"id":"34","pattern":{"type":"CUCUMBER_EXPRESSION","source":"there are chocolate bars in stock"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":8}}}} +{"stepDefinition":{"id":"35","pattern":{"type":"CUCUMBER_EXPRESSION","source":"there are no chocolate bars in stock"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":12}}}} +{"stepDefinition":{"id":"36","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the customer tries to buy a {int} cent chocolate bar"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":16}}}} +{"stepDefinition":{"id":"37","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the sale should not happen"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":22}}}} +{"stepDefinition":{"id":"38","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the sale should happen"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":26}}}} +{"testRunStarted":{"id":"39","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"40","pickleId":"22","testSteps":[{"id":"41","pickleStepId":"18","stepDefinitionIds":["33"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":17,"value":"100","children":[]},"parameterTypeName":"int"}]}]},{"id":"42","pickleStepId":"19","stepDefinitionIds":["34"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"43","pickleStepId":"20","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":28,"value":"125","children":[]},"parameterTypeName":"int"}]}]},{"id":"44","pickleStepId":"21","stepDefinitionIds":["37"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"39"}} +{"testCase":{"id":"45","pickleId":"27","testSteps":[{"id":"46","pickleStepId":"23","stepDefinitionIds":["33"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":17,"value":"100","children":[]},"parameterTypeName":"int"}]}]},{"id":"47","pickleStepId":"24","stepDefinitionIds":["34"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"48","pickleStepId":"25","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":28,"value":"75","children":[]},"parameterTypeName":"int"}]}]},{"id":"49","pickleStepId":"26","stepDefinitionIds":["38"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"39"}} +{"testCase":{"id":"50","pickleId":"32","testSteps":[{"id":"51","pickleStepId":"28","stepDefinitionIds":["33"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":17,"value":"100","children":[]},"parameterTypeName":"int"}]}]},{"id":"52","pickleStepId":"29","stepDefinitionIds":["35"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"53","pickleStepId":"30","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":28,"value":"1","children":[]},"parameterTypeName":"int"}]}]},{"id":"54","pickleStepId":"31","stepDefinitionIds":["37"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"39"}} +{"testCaseStarted":{"id":"55","testCaseId":"40","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"55","testStepId":"41","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"55","testStepId":"41","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"55","testStepId":"42","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"55","testStepId":"42","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepStarted":{"testCaseStartedId":"55","testStepId":"43","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"55","testStepId":"43","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testStepStarted":{"testCaseStartedId":"55","testStepId":"44","timestamp":{"seconds":0,"nanos":8000000}}} +{"testStepFinished":{"testCaseStartedId":"55","testStepId":"44","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":9000000}}} +{"testCaseFinished":{"testCaseStartedId":"55","timestamp":{"seconds":0,"nanos":10000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"56","testCaseId":"45","timestamp":{"seconds":0,"nanos":11000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"46","timestamp":{"seconds":0,"nanos":12000000}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"46","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"47","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"47","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"48","timestamp":{"seconds":0,"nanos":16000000}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"48","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":17000000}}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"49","timestamp":{"seconds":0,"nanos":18000000}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"49","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testCaseFinished":{"testCaseStartedId":"56","timestamp":{"seconds":0,"nanos":20000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"57","testCaseId":"50","timestamp":{"seconds":0,"nanos":21000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"57","testStepId":"51","timestamp":{"seconds":0,"nanos":22000000}}} +{"testStepFinished":{"testCaseStartedId":"57","testStepId":"51","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}} +{"testStepStarted":{"testCaseStartedId":"57","testStepId":"52","timestamp":{"seconds":0,"nanos":24000000}}} +{"testStepFinished":{"testCaseStartedId":"57","testStepId":"52","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":25000000}}} +{"testStepStarted":{"testCaseStartedId":"57","testStepId":"53","timestamp":{"seconds":0,"nanos":26000000}}} +{"testStepFinished":{"testCaseStartedId":"57","testStepId":"53","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":27000000}}} +{"testStepStarted":{"testCaseStartedId":"57","testStepId":"54","timestamp":{"seconds":0,"nanos":28000000}}} +{"testStepFinished":{"testCaseStartedId":"57","testStepId":"54","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":29000000}}} +{"testCaseFinished":{"testCaseStartedId":"57","timestamp":{"seconds":0,"nanos":30000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"39","timestamp":{"seconds":0,"nanos":31000000},"success":true}} From d238b785321691d5ba8744140a2d87f7f581638e Mon Sep 17 00:00:00 2001 From: Matt Wynne Date: Sun, 31 May 2026 10:29:39 -0700 Subject: [PATCH 25/55] Fix Ruby query packaging and test step lookup --- ruby/cucumber-query.gemspec | 2 +- ruby/lib/cucumber/query.rb | 2 +- ruby/lib/cucumber/query_helpers.rb | 23 ------------------- ruby/spec/cucumber/query_spec.rb | 37 ++++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 25 deletions(-) delete mode 100644 ruby/lib/cucumber/query_helpers.rb diff --git a/ruby/cucumber-query.gemspec b/ruby/cucumber-query.gemspec index e9acebcb..3563c68f 100644 --- a/ruby/cucumber-query.gemspec +++ b/ruby/cucumber-query.gemspec @@ -28,7 +28,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'rubocop-performance', '~> 1.25.0' s.add_development_dependency 'rubocop-rspec', '~> 3.7.0' - s.files = Dir['LICENSE', 'lib/**/*'] + s.files = Dir['LICENSE', 'VERSION', 'lib/**/*'] s.rdoc_options = ['--charset=UTF-8'] s.require_path = 'lib' end diff --git a/ruby/lib/cucumber/query.rb b/ruby/lib/cucumber/query.rb index 73573519..0638b24b 100644 --- a/ruby/lib/cucumber/query.rb +++ b/ruby/lib/cucumber/query.rb @@ -220,7 +220,7 @@ def find_test_run_finished # This method will be called with 1 of these 2 messages # [TestStepStarted || TestStepFinished] def find_test_step_by(message) - ensure_only_message_types!(message, %i[test_case_started test_case_finished], '#find_test_step_by') + ensure_only_message_types!(message, %i[test_step_started test_step_finished], '#find_test_step_by') repository.test_step_by_id[message.test_step_id] end diff --git a/ruby/lib/cucumber/query_helpers.rb b/ruby/lib/cucumber/query_helpers.rb deleted file mode 100644 index 296fb4a7..00000000 --- a/ruby/lib/cucumber/query_helpers.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'cucumber/messages/test_step_result_status' - -module Cucumber - module Query - module QueryHelpers - def status_ordinal(status) - [ - TestStepResultStatus::UNKNOWN, - TestStepResultStatus::PASSED, - TestStepResultStatus::SKIPPED, - TestStepResultStatus::PENDING, - TestStepResultStatus::UNDEFINED, - TestStepResultStatus::AMBIGUOUS, - TestStepResultStatus::FAILED - ].index(status) - end - - def assert(target, failure_message) - raise StandardError, failure_message unless target - end - end - end -end diff --git a/ruby/spec/cucumber/query_spec.rb b/ruby/spec/cucumber/query_spec.rb index 30ce9f8c..d8c9a18a 100644 --- a/ruby/spec/cucumber/query_spec.rb +++ b/ruby/spec/cucumber/query_spec.rb @@ -40,6 +40,43 @@ def parse_ndjson_file(path) let(:repository) { Cucumber::Repository.new } + describe 'packaged files' do + it 'can require every packaged library file after requiring cucumber/query' do + gemspec = Gem::Specification.load(File.expand_path('../../cucumber-query.gemspec', __dir__)) + packaged_files = gemspec.files.grep(%r{\Alib/.*\.rb\z}) + packaged_requires = packaged_files.map { |file| file.delete_prefix('lib/').delete_suffix('.rb') } + + packaged_requires.each do |path| + expect { require path }.not_to raise_error + end + end + end + + describe '#find_test_step_by' do + let(:source) { testdata_path('minimal.ndjson') } + let(:messages) { parse_ndjson_file(source) } + + before do + messages.each { |message| repository.update(message) } + end + + it 'finds test steps by TestStepStarted messages' do + expect(query.find_all_test_step_started).not_to be_empty + + query.find_all_test_step_started.each do |test_step_started| + expect(query.find_test_step_by(test_step_started).id).to eq(test_step_started.test_step_id) + end + end + + it 'finds test steps by TestStepFinished messages' do + expect(query.find_all_test_step_finished).not_to be_empty + + query.find_all_test_step_finished.each do |test_step_finished| + expect(query.find_test_step_by(test_step_finished).id).to eq(test_step_finished.test_step_id) + end + end + end + SOURCE_NAMES.product(QUERIES.to_a).each do |source_name, (query_name, query_proc)| describe "executes the query '#{query_name}' against the testdata source '#{source_name}'" do let(:source) { testdata_path("#{source_name}.ndjson") } From 18b10c8d7b9015a70c234721fd0272d2bc06a27f Mon Sep 17 00:00:00 2001 From: Matt Wynne Date: Sat, 13 Jun 2026 13:37:44 -0700 Subject: [PATCH 26/55] Add Ruby CI and release workflow --- .github/workflows/release-rubygem.yaml | 39 ++++++++++++++++++ .github/workflows/test-ruby.yml | 56 ++++++++++++++++++++++++++ ruby/.rubocop_todo.yml | 5 +++ ruby/lib/cucumber/query.rb | 3 +- ruby/lib/cucumber/repository.rb | 50 ++++++++++++++++------- ruby/spec/cucumber/query_spec.rb | 50 +++++++++++------------ ruby/spec/spec_helper.rb | 2 +- 7 files changed, 163 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/release-rubygem.yaml create mode 100644 .github/workflows/test-ruby.yml diff --git a/.github/workflows/release-rubygem.yaml b/.github/workflows/release-rubygem.yaml new file mode 100644 index 00000000..8376062c --- /dev/null +++ b/.github/workflows/release-rubygem.yaml @@ -0,0 +1,39 @@ +name: Release RubyGems + +permissions: {} + +on: + push: + branches: + - release/* + +jobs: + publish-rubygem: + name: Publish Ruby Gem + runs-on: ubuntu-latest + environment: Release + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.4' + bundler-cache: true + working-directory: ruby + + - run: bundle exec rubocop --format simple + working-directory: ruby + + - run: bundle exec rspec --format progress + working-directory: ruby + + - uses: rubygems/configure-rubygems-credentials@v2.0.0 + + - uses: cucumber/action-publish-rubygem@v2.0.0 + with: + working-directory: ruby diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml new file mode 100644 index 00000000..46161515 --- /dev/null +++ b/.github/workflows/test-ruby.yml @@ -0,0 +1,56 @@ +name: test-ruby + +permissions: {} + +on: + push: + branches: + - main + - renovate/** + paths: + - ruby/** + - testdata/** + - .github/** + pull_request: + branches: + - main + paths: + - ruby/** + - testdata/** + - .github/** + workflow_call: + +jobs: + test-ruby: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + ruby: ['3.2', '3.3', '3.4', '3.5'] + include: + - os: macos-latest + ruby: '3.4' + - os: windows-latest + ruby: '3.4' + + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + working-directory: ruby + + - run: bundle exec rubocop --format simple + working-directory: ruby + + - run: bundle exec rspec --format progress + working-directory: ruby + + - run: gem build cucumber-query.gemspec + working-directory: ruby diff --git a/ruby/.rubocop_todo.yml b/ruby/.rubocop_todo.yml index 4f132b72..aec52a07 100644 --- a/ruby/.rubocop_todo.yml +++ b/ruby/.rubocop_todo.yml @@ -46,6 +46,11 @@ Metrics/AbcSize: Metrics/CyclomaticComplexity: Max: 14 +# Offense count: 2 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. +Metrics/ClassLength: + Max: 160 + # Offense count: 3 # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: diff --git a/ruby/lib/cucumber/query.rb b/ruby/lib/cucumber/query.rb index 0638b24b..21851943 100644 --- a/ruby/lib/cucumber/query.rb +++ b/ruby/lib/cucumber/query.rb @@ -9,6 +9,7 @@ # see Cucumber Messages - Message Overview # module Cucumber + # Provides lookup methods for related Cucumber messages stored in a Repository. class Query attr_reader :repository private :repository @@ -157,7 +158,7 @@ def find_step_definitions_by(test_step) ensure_only_message_types!(test_step, %i[test_step], '#find_step_definitions_by') ids = test_step.step_definition_ids.nil? ? [] : test_step.step_definition_ids - ids.map { |id| repository.step_definition_by_id[id] }.compact + ids.filter_map { |id| repository.step_definition_by_id[id] } end # This method will be called with 1 of these 4 messages diff --git a/ruby/lib/cucumber/repository.rb b/ruby/lib/cucumber/repository.rb index 9c61dfbb..1a85fc0a 100644 --- a/ruby/lib/cucumber/repository.rb +++ b/ruby/lib/cucumber/repository.rb @@ -38,28 +38,40 @@ def initialize @test_steps_finished_by_test_case_started_id = Hash.new { |hash, key| hash[key] = [] } end + UPDATE_HANDLERS = { + meta: :update_meta, + test_run_started: :update_test_run_started, + test_run_finished: :update_test_run_finished, + attachment: :update_attachment, + gherkin_document: :update_gherkin_document, + hook: :update_hook, + pickle: :update_pickle, + step_definition: :update_step_definition, + test_run_hook_started: :update_test_run_hook_started, + test_run_hook_finished: :update_test_run_hook_finished, + test_case_started: :update_test_case_started, + test_case_finished: :update_test_case_finished, + test_step_started: :update_test_step_started, + test_step_finished: :update_test_step_finished, + test_case: :update_test_case + }.freeze + private_constant :UPDATE_HANDLERS + def update(envelope) - return self.meta = envelope.meta if envelope.meta - return self.test_run_started = envelope.test_run_started if envelope.test_run_started - return self.test_run_finished = envelope.test_run_finished if envelope.test_run_finished - return update_attachment(envelope.attachment) if envelope.attachment - return update_gherkin_document(envelope.gherkin_document) if envelope.gherkin_document - return update_hook(envelope.hook) if envelope.hook - return update_pickle(envelope.pickle) if envelope.pickle - return update_step_definition(envelope.step_definition) if envelope.step_definition - return update_test_run_hook_started(envelope.test_run_hook_started) if envelope.test_run_hook_started - return update_test_run_hook_finished(envelope.test_run_hook_finished) if envelope.test_run_hook_finished - return update_test_case_started(envelope.test_case_started) if envelope.test_case_started - return update_test_case_finished(envelope.test_case_finished) if envelope.test_case_finished - return update_test_step_started(envelope.test_step_started) if envelope.test_step_started - return update_test_step_finished(envelope.test_step_finished) if envelope.test_step_finished - return update_test_case(envelope.test_case) if envelope.test_case + UPDATE_HANDLERS.each do |message_name, handler| + message = envelope.public_send(message_name) + return __send__(handler, message) if message + end nil end private + def update_meta(meta) + self.meta = meta + end + def update_attachment(attachment) attachments_by_test_case_started_id[attachment.test_case_started_id] << attachment if attachment.test_case_started_id attachments_by_test_run_hook_started_id[attachment.test_run_hook_started_id] << attachment if attachment.test_run_hook_started_id @@ -117,6 +129,14 @@ def update_test_case_finished(test_case_finished) test_case_finished_by_test_case_started_id[test_case_finished.test_case_started_id] = test_case_finished end + def update_test_run_started(test_run_started) + self.test_run_started = test_run_started + end + + def update_test_run_finished(test_run_finished) + self.test_run_finished = test_run_finished + end + def update_test_run_hook_started(test_run_hook_started) test_run_hook_started_by_id[test_run_hook_started.id] = test_run_hook_started end diff --git a/ruby/spec/cucumber/query_spec.rb b/ruby/spec/cucumber/query_spec.rb index d8c9a18a..e6425b37 100644 --- a/ruby/spec/cucumber/query_spec.rb +++ b/ruby/spec/cucumber/query_spec.rb @@ -3,31 +3,31 @@ require 'json' require 'cucumber/query' -RSpec.describe Cucumber::Query do - SOURCE_NAMES = %w[attachments empty hooks minimal rules].freeze - - QUERIES = { - 'findAllPickles' => ->(query) { query.find_all_pickles.length }, - 'findAllTestCases' => ->(query) { query.find_all_test_cases.length }, - 'findAllTestSteps' => ->(query) { query.find_all_test_steps.length }, - 'findTestCaseBy' => lambda do |query| - results = {} - results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_test_case_by(message).id } - results['testCaseFinished'] = query.find_all_test_case_finished.map { |message| query.find_test_case_by(message).id } - results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_test_case_by(message).id } - results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_test_case_by(message).id } - results - end, - 'findPickleBy' => lambda do |query| - results = {} - results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_pickle_by(message).name } - results['testCaseFinished'] = query.find_all_test_case_finished.map { |message| query.find_pickle_by(message).name } - results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_pickle_by(message).name } - results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_pickle_by(message).name } - results - end - }.freeze +source_names = %w[attachments empty hooks minimal rules].freeze + +queries = { + 'findAllPickles' => ->(query) { query.find_all_pickles.length }, + 'findAllTestCases' => ->(query) { query.find_all_test_cases.length }, + 'findAllTestSteps' => ->(query) { query.find_all_test_steps.length }, + 'findTestCaseBy' => lambda do |query| + results = {} + results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_test_case_by(message).id } + results['testCaseFinished'] = query.find_all_test_case_finished.map { |message| query.find_test_case_by(message).id } + results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_test_case_by(message).id } + results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_test_case_by(message).id } + results + end, + 'findPickleBy' => lambda do |query| + results = {} + results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_pickle_by(message).name } + results['testCaseFinished'] = query.find_all_test_case_finished.map { |message| query.find_pickle_by(message).name } + results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_pickle_by(message).name } + results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_pickle_by(message).name } + results + end +}.freeze +RSpec.describe Cucumber::Query do def testdata_path(name) File.expand_path("../support/#{name}", __dir__) end @@ -77,7 +77,7 @@ def parse_ndjson_file(path) end end - SOURCE_NAMES.product(QUERIES.to_a).each do |source_name, (query_name, query_proc)| + source_names.product(queries.to_a).each do |source_name, (query_name, query_proc)| describe "executes the query '#{query_name}' against the testdata source '#{source_name}'" do let(:source) { testdata_path("#{source_name}.ndjson") } let(:expected_results_file) { testdata_path("#{source_name}.#{query_name}.results.json") } diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb index d0b601fc..81bd3988 100644 --- a/ruby/spec/spec_helper.rb +++ b/ruby/spec/spec_helper.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -RSpec.configure do |c| +RSpec.configure do :no_op end From 367300f8f4bdca281b0d1a8fd047b0c6321d1517 Mon Sep 17 00:00:00 2001 From: Matt Wynne Date: Sat, 13 Jun 2026 13:41:53 -0700 Subject: [PATCH 27/55] Align Ruby gem version --- ruby/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/VERSION b/ruby/VERSION index fb5b5130..2bbd2b4b 100644 --- a/ruby/VERSION +++ b/ruby/VERSION @@ -1 +1 @@ -21.0.0 +15.0.1 From 4c70e37515861ba5bd0169804dd72ac86e92c452 Mon Sep 17 00:00:00 2001 From: Matt Wynne Date: Sat, 13 Jun 2026 13:42:03 -0700 Subject: [PATCH 28/55] Update Ruby lockfile version --- ruby/Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/Gemfile.lock b/ruby/Gemfile.lock index 6a5a9ec2..8bded9b8 100644 --- a/ruby/Gemfile.lock +++ b/ruby/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cucumber-query (21.0.0) + cucumber-query (15.0.1) cucumber-messages (> 31, < 33) GEM From 004ccfa0bc40a4f80777d79a8b697769279dcc55 Mon Sep 17 00:00:00 2001 From: Matt Wynne Date: Sat, 13 Jun 2026 14:08:34 -0700 Subject: [PATCH 29/55] Update changelog for Ruby implementation --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61913df9..f1a20779 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- [Ruby] Add initial Ruby implementation and RubyGems release workflow, covering the needs of the rerun formatter only for now ([#98](https://github.com/cucumber/query/pull/98)) ## [15.0.1] - 2026-01-22 ### Fixed From 0ff633e4a4d174e6309cd866f751c7bd635031b2 Mon Sep 17 00:00:00 2001 From: Matt Wynne Date: Sat, 13 Jun 2026 14:11:18 -0700 Subject: [PATCH 30/55] Pin Ruby workflow actions --- .github/workflows/release-rubygem.yaml | 8 ++++---- .github/workflows/test-ruby.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-rubygem.yaml b/.github/workflows/release-rubygem.yaml index 8376062c..34d58b0c 100644 --- a/.github/workflows/release-rubygem.yaml +++ b/.github/workflows/release-rubygem.yaml @@ -16,11 +16,11 @@ jobs: id-token: write contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: ruby/setup-ruby@v1 + - uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0 with: ruby-version: '3.4' bundler-cache: true @@ -32,8 +32,8 @@ jobs: - run: bundle exec rspec --format progress working-directory: ruby - - uses: rubygems/configure-rubygems-credentials@v2.0.0 + - uses: rubygems/configure-rubygems-credentials@762a4b77c3300434bb57c7ce80b20e36231927aa # v2.0.0 - - uses: cucumber/action-publish-rubygem@v2.0.0 + - uses: cucumber/action-publish-rubygem@4e79bb9aed597c835e8438f57c04d0996ab80d72 # v2.0.0 with: working-directory: ruby diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 46161515..df469ff1 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -36,11 +36,11 @@ jobs: ruby: '3.4' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: ruby/setup-ruby@v1 + - uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true From 05cba616d7986ec30ae6cf29113321154a17369b Mon Sep 17 00:00:00 2001 From: Matt Wynne Date: Sat, 13 Jun 2026 14:12:52 -0700 Subject: [PATCH 31/55] Disable bundler cache in Ruby release workflow --- .github/workflows/release-rubygem.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-rubygem.yaml b/.github/workflows/release-rubygem.yaml index 34d58b0c..d027b8ec 100644 --- a/.github/workflows/release-rubygem.yaml +++ b/.github/workflows/release-rubygem.yaml @@ -23,9 +23,12 @@ jobs: - uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0 with: ruby-version: '3.4' - bundler-cache: true + bundler-cache: false working-directory: ruby + - run: bundle install + working-directory: ruby + - run: bundle exec rubocop --format simple working-directory: ruby From 7b446a793c5155841ebb78d0f57c40803fdd017f Mon Sep 17 00:00:00 2001 From: Matt Wynne Date: Sat, 13 Jun 2026 14:21:09 -0700 Subject: [PATCH 32/55] Add RubyGems badge --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 4a33b541..72c840f3 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ maven-central + + rubygems + build @@ -38,3 +41,4 @@ is rendering the [GherkinDocument](https://github.com/cucumber/messages/blob/mai The Query library makes this easy by providing a function to look up the status of a step, a scenario or an entire file. + From ca2efd2fee03d36a6fcf0348e5d41aa86c2dc8fc Mon Sep 17 00:00:00 2001 From: Matt Wynne Date: Sat, 13 Jun 2026 14:38:36 -0700 Subject: [PATCH 33/55] Tidy Ruby query implementation --- ruby/lib/cucumber/error.rb | 8 --- ruby/lib/cucumber/query.rb | 18 +----- ruby/lib/cucumber/repository.rb | 9 --- ruby/spec/cucumber/query_spec.rb | 2 +- .../attachments.findAllPickles.results.json | 1 - .../attachments.findAllTestCases.results.json | 1 - .../attachments.findAllTestSteps.results.json | 1 - .../attachments.findPickleBy.results.json | 38 ------------ .../attachments.findTestCaseBy.results.json | 38 ------------ ruby/spec/support/attachments.ndjson | 61 ------------------- .../support/empty.findAllPickles.results.json | 1 - .../empty.findAllTestCases.results.json | 1 - .../empty.findAllTestSteps.results.json | 1 - .../support/empty.findPickleBy.results.json | 10 --- .../support/empty.findTestCaseBy.results.json | 10 --- ruby/spec/support/empty.ndjson | 9 --- .../support/hooks.findAllPickles.results.json | 1 - .../hooks.findAllTestCases.results.json | 1 - .../hooks.findAllTestSteps.results.json | 1 - .../support/hooks.findPickleBy.results.json | 26 -------- .../support/hooks.findTestCaseBy.results.json | 26 -------- ruby/spec/support/hooks.ndjson | 29 --------- .../minimal.findAllPickles.results.json | 1 - .../minimal.findAllTestCases.results.json | 1 - .../minimal.findAllTestSteps.results.json | 1 - .../support/minimal.findPickleBy.results.json | 14 ----- .../minimal.findTestCaseBy.results.json | 14 ----- ruby/spec/support/minimal.ndjson | 12 ---- .../support/rules.findAllPickles.results.json | 1 - .../rules.findAllTestCases.results.json | 1 - .../rules.findAllTestSteps.results.json | 1 - .../support/rules.findPickleBy.results.json | 40 ------------ .../support/rules.findTestCaseBy.results.json | 40 ------------ ruby/spec/support/rules.ndjson | 47 -------------- 34 files changed, 3 insertions(+), 463 deletions(-) delete mode 100644 ruby/lib/cucumber/error.rb delete mode 100644 ruby/spec/support/attachments.findAllPickles.results.json delete mode 100644 ruby/spec/support/attachments.findAllTestCases.results.json delete mode 100644 ruby/spec/support/attachments.findAllTestSteps.results.json delete mode 100644 ruby/spec/support/attachments.findPickleBy.results.json delete mode 100644 ruby/spec/support/attachments.findTestCaseBy.results.json delete mode 100644 ruby/spec/support/attachments.ndjson delete mode 100644 ruby/spec/support/empty.findAllPickles.results.json delete mode 100644 ruby/spec/support/empty.findAllTestCases.results.json delete mode 100644 ruby/spec/support/empty.findAllTestSteps.results.json delete mode 100644 ruby/spec/support/empty.findPickleBy.results.json delete mode 100644 ruby/spec/support/empty.findTestCaseBy.results.json delete mode 100644 ruby/spec/support/empty.ndjson delete mode 100644 ruby/spec/support/hooks.findAllPickles.results.json delete mode 100644 ruby/spec/support/hooks.findAllTestCases.results.json delete mode 100644 ruby/spec/support/hooks.findAllTestSteps.results.json delete mode 100644 ruby/spec/support/hooks.findPickleBy.results.json delete mode 100644 ruby/spec/support/hooks.findTestCaseBy.results.json delete mode 100644 ruby/spec/support/hooks.ndjson delete mode 100644 ruby/spec/support/minimal.findAllPickles.results.json delete mode 100644 ruby/spec/support/minimal.findAllTestCases.results.json delete mode 100644 ruby/spec/support/minimal.findAllTestSteps.results.json delete mode 100644 ruby/spec/support/minimal.findPickleBy.results.json delete mode 100644 ruby/spec/support/minimal.findTestCaseBy.results.json delete mode 100644 ruby/spec/support/minimal.ndjson delete mode 100644 ruby/spec/support/rules.findAllPickles.results.json delete mode 100644 ruby/spec/support/rules.findAllTestCases.results.json delete mode 100644 ruby/spec/support/rules.findAllTestSteps.results.json delete mode 100644 ruby/spec/support/rules.findPickleBy.results.json delete mode 100644 ruby/spec/support/rules.findTestCaseBy.results.json delete mode 100644 ruby/spec/support/rules.ndjson diff --git a/ruby/lib/cucumber/error.rb b/ruby/lib/cucumber/error.rb deleted file mode 100644 index c8343cad..00000000 --- a/ruby/lib/cucumber/error.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -module Cucumber - module Error - TestCaseUnknownError = Class.new(StandardError) - TestStepUnknownError = Class.new(StandardError) - end -end diff --git a/ruby/lib/cucumber/query.rb b/ruby/lib/cucumber/query.rb index 21851943..a8fa465c 100644 --- a/ruby/lib/cucumber/query.rb +++ b/ruby/lib/cucumber/query.rb @@ -21,22 +21,8 @@ def initialize(repository) @repository = repository end - # TODO: count methods (1/2) Complete - # Missing: countMostSevereTestStepResultStatus - - # TODO: findAll methods (11/12) Complete - # Missing: findAllUndefinedParameterTypes - - # TODO: find****By methods (16/25) Complete - # Missing: findSuggestionsBy (2 variants) - # Missing: findUnambiguousStepDefinitionBy (1 variant) - # Missing: findTestStepFinishedAndTestStepBy (1 variant) - # Missing: findAttachmentsBy (2 variants) - # Missing: findTestCaseDurationBy (2 variant) - # REDUNDANT: findLineageBy (9 variants!) - # REDUNDANT: findLocationOf (1 variant) - This strictly speaking isn't a findBy but is located within them - # To Review: findMostSevereTestStepResultBy (2 variants) - # To Review: findTestRunDuration (1 variant) - This strictly speaking isn't a findBy but is located within them + # This initial Ruby implementation covers the query methods needed by current Ruby consumers. + # Additional methods can be added here as those consumers migrate from cucumber-ruby's legacy query helpers. def count_test_cases_started find_all_test_case_started.length diff --git a/ruby/lib/cucumber/repository.rb b/ruby/lib/cucumber/repository.rb index 1a85fc0a..862b8df2 100644 --- a/ruby/lib/cucumber/repository.rb +++ b/ruby/lib/cucumber/repository.rb @@ -12,14 +12,6 @@ class Repository :test_run_hook_started_by_id, :test_run_hook_finished_by_test_run_hook_started_id, :test_step_by_id, :test_steps_started_by_test_case_started_id, :test_steps_finished_by_test_case_started_id - # TODO: Missing structs (2) - # final Map> suggestionsByPickleStepId = new LinkedHashMap<>(); - # final List 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] = [] } @@ -91,7 +83,6 @@ def update_feature(feature) end def update_gherkin_document(gherkin_document) - # TODO: Update lineage at a later date. Java Impl -> lineageById.putAll(Lineages.of(document)); update_feature(gherkin_document.feature) if gherkin_document.feature end diff --git a/ruby/spec/cucumber/query_spec.rb b/ruby/spec/cucumber/query_spec.rb index e6425b37..0ec4b088 100644 --- a/ruby/spec/cucumber/query_spec.rb +++ b/ruby/spec/cucumber/query_spec.rb @@ -29,7 +29,7 @@ RSpec.describe Cucumber::Query do def testdata_path(name) - File.expand_path("../support/#{name}", __dir__) + File.expand_path("../../../testdata/src/#{name}", __dir__) end def parse_ndjson_file(path) diff --git a/ruby/spec/support/attachments.findAllPickles.results.json b/ruby/spec/support/attachments.findAllPickles.results.json deleted file mode 100644 index c7930257..00000000 --- a/ruby/spec/support/attachments.findAllPickles.results.json +++ /dev/null @@ -1 +0,0 @@ -7 \ No newline at end of file diff --git a/ruby/spec/support/attachments.findAllTestCases.results.json b/ruby/spec/support/attachments.findAllTestCases.results.json deleted file mode 100644 index c7930257..00000000 --- a/ruby/spec/support/attachments.findAllTestCases.results.json +++ /dev/null @@ -1 +0,0 @@ -7 \ No newline at end of file diff --git a/ruby/spec/support/attachments.findAllTestSteps.results.json b/ruby/spec/support/attachments.findAllTestSteps.results.json deleted file mode 100644 index c7930257..00000000 --- a/ruby/spec/support/attachments.findAllTestSteps.results.json +++ /dev/null @@ -1 +0,0 @@ -7 \ No newline at end of file diff --git a/ruby/spec/support/attachments.findPickleBy.results.json b/ruby/spec/support/attachments.findPickleBy.results.json deleted file mode 100644 index af46dda9..00000000 --- a/ruby/spec/support/attachments.findPickleBy.results.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "testCaseStarted" : [ - "Strings can be attached with a media type", - "Log text", - "Log ANSI coloured text", - "Log JSON", - "Byte arrays are base64-encoded regardless of media type", - "Attaching PDFs with a different filename", - "Attaching URIs" - ], - "testCaseFinished" : [ - "Strings can be attached with a media type", - "Log text", - "Log ANSI coloured text", - "Log JSON", - "Byte arrays are base64-encoded regardless of media type", - "Attaching PDFs with a different filename", - "Attaching URIs" - ], - "testStepStarted" : [ - "Strings can be attached with a media type", - "Log text", - "Log ANSI coloured text", - "Log JSON", - "Byte arrays are base64-encoded regardless of media type", - "Attaching PDFs with a different filename", - "Attaching URIs" - ], - "testStepFinished" : [ - "Strings can be attached with a media type", - "Log text", - "Log ANSI coloured text", - "Log JSON", - "Byte arrays are base64-encoded regardless of media type", - "Attaching PDFs with a different filename", - "Attaching URIs" - ] -} \ No newline at end of file diff --git a/ruby/spec/support/attachments.findTestCaseBy.results.json b/ruby/spec/support/attachments.findTestCaseBy.results.json deleted file mode 100644 index 298a33f9..00000000 --- a/ruby/spec/support/attachments.findTestCaseBy.results.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "testCaseStarted" : [ - "36", - "38", - "40", - "42", - "44", - "46", - "48" - ], - "testCaseFinished" : [ - "36", - "38", - "40", - "42", - "44", - "46", - "48" - ], - "testStepStarted" : [ - "36", - "38", - "40", - "42", - "44", - "46", - "48" - ], - "testStepFinished" : [ - "36", - "38", - "40", - "42", - "44", - "46", - "48" - ] -} \ No newline at end of file diff --git a/ruby/spec/support/attachments.ndjson b/ruby/spec/support/attachments.ndjson deleted file mode 100644 index c16bb877..00000000 --- a/ruby/spec/support/attachments.ndjson +++ /dev/null @@ -1,61 +0,0 @@ -{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} -{"source":{"data":"Feature: Attachments\n It is sometimes useful to take a screenshot while a scenario runs or capture some logs.\n\n Cucumber lets you `attach` arbitrary files during execution, and you can\n specify a content type for the contents.\n\n Formatters can then render these attachments in reports.\n\n Attachments must have a body and a content type.\n\n Scenario: Strings can be attached with a media type\n Beware that some formatters such as @cucumber/react use the media type\n to determine how to display an attachment.\n\n When the string \"hello\" is attached as \"application/octet-stream\"\n\n Scenario: Log text\n When the string \"hello\" is logged\n\n Scenario: Log ANSI coloured text\n When text with ANSI escapes is logged\n\n Scenario: Log JSON\n When the following string is attached as \"application/json\":\n ```\n {\"message\": \"The big question\", \"foo\": \"bar\"}\n ```\n\n Scenario: Byte arrays are base64-encoded regardless of media type\n When an array with 10 bytes is attached as \"text/plain\"\n\n Scenario: Attaching PDFs with a different filename\n When a PDF document is attached and renamed\n\n Scenario: Attaching URIs\n When a link to \"https://cucumber.io\" is attached\n","uri":"samples/attachments/attachments.feature","mediaType":"text/x.cucumber.gherkin+plain"}} -{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Attachments","description":" It is sometimes useful to take a screenshot while a scenario runs or capture some logs.\n\n Cucumber lets you `attach` arbitrary files during execution, and you can\n specify a content type for the contents.\n\n Formatters can then render these attachments in reports.\n\n Attachments must have a body and a content type.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario","name":"Strings can be attached with a media type","description":" Beware that some formatters such as @cucumber/react use the media type\n to determine how to display an attachment.","steps":[{"id":"0","location":{"line":15,"column":5},"keyword":"When ","keywordType":"Action","text":"the string \"hello\" is attached as \"application/octet-stream\""}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":17,"column":3},"keyword":"Scenario","name":"Log text","description":"","steps":[{"id":"2","location":{"line":18,"column":5},"keyword":"When ","keywordType":"Action","text":"the string \"hello\" is logged"}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":20,"column":3},"keyword":"Scenario","name":"Log ANSI coloured text","description":"","steps":[{"id":"4","location":{"line":21,"column":5},"keyword":"When ","keywordType":"Action","text":"text with ANSI escapes is logged"}],"examples":[]}},{"scenario":{"id":"7","tags":[],"location":{"line":23,"column":3},"keyword":"Scenario","name":"Log JSON","description":"","steps":[{"id":"6","location":{"line":24,"column":6},"keyword":"When ","keywordType":"Action","text":"the following string is attached as \"application/json\":","docString":{"location":{"line":25,"column":8},"content":"{\"message\": \"The big question\", \"foo\": \"bar\"}","delimiter":"```"}}],"examples":[]}},{"scenario":{"id":"9","tags":[],"location":{"line":29,"column":3},"keyword":"Scenario","name":"Byte arrays are base64-encoded regardless of media type","description":"","steps":[{"id":"8","location":{"line":30,"column":5},"keyword":"When ","keywordType":"Action","text":"an array with 10 bytes is attached as \"text/plain\""}],"examples":[]}},{"scenario":{"id":"11","tags":[],"location":{"line":32,"column":3},"keyword":"Scenario","name":"Attaching PDFs with a different filename","description":"","steps":[{"id":"10","location":{"line":33,"column":5},"keyword":"When ","keywordType":"Action","text":"a PDF document is attached and renamed"}],"examples":[]}},{"scenario":{"id":"13","tags":[],"location":{"line":35,"column":3},"keyword":"Scenario","name":"Attaching URIs","description":"","steps":[{"id":"12","location":{"line":36,"column":5},"keyword":"When ","keywordType":"Action","text":"a link to \"https://cucumber.io\" is attached"}],"examples":[]}}]},"comments":[],"uri":"samples/attachments/attachments.feature"}} -{"pickle":{"id":"15","uri":"samples/attachments/attachments.feature","astNodeIds":["1"],"tags":[],"name":"Strings can be attached with a media type","language":"en","steps":[{"id":"14","text":"the string \"hello\" is attached as \"application/octet-stream\"","type":"Action","astNodeIds":["0"]}]}} -{"pickle":{"id":"17","uri":"samples/attachments/attachments.feature","astNodeIds":["3"],"tags":[],"name":"Log text","language":"en","steps":[{"id":"16","text":"the string \"hello\" is logged","type":"Action","astNodeIds":["2"]}]}} -{"pickle":{"id":"19","uri":"samples/attachments/attachments.feature","astNodeIds":["5"],"tags":[],"name":"Log ANSI coloured text","language":"en","steps":[{"id":"18","text":"text with ANSI escapes is logged","type":"Action","astNodeIds":["4"]}]}} -{"pickle":{"id":"21","uri":"samples/attachments/attachments.feature","astNodeIds":["7"],"tags":[],"name":"Log JSON","language":"en","steps":[{"id":"20","text":"the following string is attached as \"application/json\":","type":"Action","argument":{"docString":{"content":"{\"message\": \"The big question\", \"foo\": \"bar\"}"}},"astNodeIds":["6"]}]}} -{"pickle":{"id":"23","uri":"samples/attachments/attachments.feature","astNodeIds":["9"],"tags":[],"name":"Byte arrays are base64-encoded regardless of media type","language":"en","steps":[{"id":"22","text":"an array with 10 bytes is attached as \"text/plain\"","type":"Action","astNodeIds":["8"]}]}} -{"pickle":{"id":"25","uri":"samples/attachments/attachments.feature","astNodeIds":["11"],"tags":[],"name":"Attaching PDFs with a different filename","language":"en","steps":[{"id":"24","text":"a PDF document is attached and renamed","type":"Action","astNodeIds":["10"]}]}} -{"pickle":{"id":"27","uri":"samples/attachments/attachments.feature","astNodeIds":["13"],"tags":[],"name":"Attaching URIs","language":"en","steps":[{"id":"26","text":"a link to \"https://cucumber.io\" is attached","type":"Action","astNodeIds":["12"]}]}} -{"stepDefinition":{"id":"28","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the string {string} is attached as {string}"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":4}}}} -{"stepDefinition":{"id":"29","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the string {string} is logged"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":8}}}} -{"stepDefinition":{"id":"30","pattern":{"type":"CUCUMBER_EXPRESSION","source":"text with ANSI escapes is logged"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":12}}}} -{"stepDefinition":{"id":"31","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the following string is attached as {string}:"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":18}}}} -{"stepDefinition":{"id":"32","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an array with {int} bytes is attached as {string}"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":22}}}} -{"stepDefinition":{"id":"33","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a PDF document is attached and renamed"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":31}}}} -{"stepDefinition":{"id":"34","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a link to {string} is attached"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":38}}}} -{"testRunStarted":{"id":"35","timestamp":{"seconds":0,"nanos":0}}} -{"testCase":{"id":"36","pickleId":"15","testSteps":[{"id":"37","pickleStepId":"14","stepDefinitionIds":["28"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":11,"value":"\"hello\"","children":[{"start":12,"value":"hello","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"},{"group":{"start":34,"value":"\"application/octet-stream\"","children":[{"start":35,"value":"application/octet-stream","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} -{"testCase":{"id":"38","pickleId":"17","testSteps":[{"id":"39","pickleStepId":"16","stepDefinitionIds":["29"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":11,"value":"\"hello\"","children":[{"start":12,"value":"hello","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} -{"testCase":{"id":"40","pickleId":"19","testSteps":[{"id":"41","pickleStepId":"18","stepDefinitionIds":["30"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"35"}} -{"testCase":{"id":"42","pickleId":"21","testSteps":[{"id":"43","pickleStepId":"20","stepDefinitionIds":["31"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":36,"value":"\"application/json\"","children":[{"start":37,"value":"application/json","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} -{"testCase":{"id":"44","pickleId":"23","testSteps":[{"id":"45","pickleStepId":"22","stepDefinitionIds":["32"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"10","children":[]},"parameterTypeName":"int"},{"group":{"start":38,"value":"\"text/plain\"","children":[{"start":39,"value":"text/plain","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} -{"testCase":{"id":"46","pickleId":"25","testSteps":[{"id":"47","pickleStepId":"24","stepDefinitionIds":["33"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"35"}} -{"testCase":{"id":"48","pickleId":"27","testSteps":[{"id":"49","pickleStepId":"26","stepDefinitionIds":["34"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"\"https://cucumber.io\"","children":[{"start":11,"value":"https://cucumber.io","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} -{"testCaseStarted":{"id":"50","testCaseId":"36","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} -{"testStepStarted":{"testCaseStartedId":"50","testStepId":"37","timestamp":{"seconds":0,"nanos":2000000}}} -{"attachment":{"testCaseStartedId":"50","testStepId":"37","body":"hello","contentEncoding":"IDENTITY","mediaType":"application/octet-stream"}} -{"testStepFinished":{"testCaseStartedId":"50","testStepId":"37","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} -{"testCaseFinished":{"testCaseStartedId":"50","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} -{"testCaseStarted":{"id":"51","testCaseId":"38","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}} -{"testStepStarted":{"testCaseStartedId":"51","testStepId":"39","timestamp":{"seconds":0,"nanos":6000000}}} -{"attachment":{"testCaseStartedId":"51","testStepId":"39","body":"hello","contentEncoding":"IDENTITY","mediaType":"text/x.cucumber.log+plain"}} -{"testStepFinished":{"testCaseStartedId":"51","testStepId":"39","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} -{"testCaseFinished":{"testCaseStartedId":"51","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} -{"testCaseStarted":{"id":"52","testCaseId":"40","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}} -{"testStepStarted":{"testCaseStartedId":"52","testStepId":"41","timestamp":{"seconds":0,"nanos":10000000}}} -{"attachment":{"testCaseStartedId":"52","testStepId":"41","body":"This displays a \u001b[31mr\u001b[0m\u001b[91ma\u001b[0m\u001b[33mi\u001b[0m\u001b[32mn\u001b[0m\u001b[34mb\u001b[0m\u001b[95mo\u001b[0m\u001b[35mw\u001b[0m","contentEncoding":"IDENTITY","mediaType":"text/x.cucumber.log+plain"}} -{"testStepFinished":{"testCaseStartedId":"52","testStepId":"41","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} -{"testCaseFinished":{"testCaseStartedId":"52","timestamp":{"seconds":0,"nanos":12000000},"willBeRetried":false}} -{"testCaseStarted":{"id":"53","testCaseId":"42","timestamp":{"seconds":0,"nanos":13000000},"attempt":0}} -{"testStepStarted":{"testCaseStartedId":"53","testStepId":"43","timestamp":{"seconds":0,"nanos":14000000}}} -{"attachment":{"testCaseStartedId":"53","testStepId":"43","body":"{\"message\": \"The big question\", \"foo\": \"bar\"}","contentEncoding":"IDENTITY","mediaType":"application/json"}} -{"testStepFinished":{"testCaseStartedId":"53","testStepId":"43","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} -{"testCaseFinished":{"testCaseStartedId":"53","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}} -{"testCaseStarted":{"id":"54","testCaseId":"44","timestamp":{"seconds":0,"nanos":17000000},"attempt":0}} -{"testStepStarted":{"testCaseStartedId":"54","testStepId":"45","timestamp":{"seconds":0,"nanos":18000000}}} -{"attachment":{"testCaseStartedId":"54","testStepId":"45","body":"AAECAwQFBgcICQ==","contentEncoding":"BASE64","mediaType":"text/plain"}} -{"testStepFinished":{"testCaseStartedId":"54","testStepId":"45","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} -{"testCaseFinished":{"testCaseStartedId":"54","timestamp":{"seconds":0,"nanos":20000000},"willBeRetried":false}} -{"testCaseStarted":{"id":"55","testCaseId":"46","timestamp":{"seconds":0,"nanos":21000000},"attempt":0}} -{"testStepStarted":{"testCaseStartedId":"55","testStepId":"47","timestamp":{"seconds":0,"nanos":22000000}}} -{"attachment":{"testCaseStartedId":"55","testStepId":"47","body":"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RmlsZTIgOCAwIFI+PgplbmRvYmoKMTAgMCBvYmoKPDwvVHlwZSAvRm9udAovRm9udERlc2NyaXB0b3IgOSAwIFIKL0Jhc2VGb250IC9BQUFBQUErQXJpYWxNVAovU3VidHlwZSAvQ0lERm9udFR5cGUyCi9DSURUb0dJRE1hcCAvSWRlbnRpdHkKL0NJRFN5c3RlbUluZm8gPDwvUmVnaXN0cnkgKEFkb2JlKQovT3JkZXJpbmcgKElkZW50aXR5KQovU3VwcGxlbWVudCAwPj4KL1cgWzAgWzc1MF0gNTUgWzYxMC44Mzk4NF0gNzIgWzU1Ni4xNTIzNF0gODcgWzI3Ny44MzIwM11dCi9EVyA1MDA+PgplbmRvYmoKMTEgMCBvYmoKPDwvRmlsdGVyIC9GbGF0ZURlY29kZQovTGVuZ3RoIDI1MD4+IHN0cmVhbQp4nF2Qy2rEIBSG9z7FWU4Xg0lmMtNFEMqUQha90LQPYPQkFRoVYxZ5+3pJU6ig8PP/n+dCb+1jq5UH+uaM6NDDoLR0OJvFCYQeR6VJWYFUwm8qvWLiltAAd+vscWr1YEjTAND34M7erXB4kKbHO0JfnUSn9AiHz1sXdLdY+40Tag8FYQwkDuGnZ25f+IRAE3ZsZfCVX4+B+Ut8rBahSrrM3QgjcbZcoON6RNIU4TBonsJhBLX851eZ6gfxxV1Mn64hXRT1mUV1vk/qUid2S5W/zF6ivmQos9fTls5+LBqXs08kFufCMGmDaYrYv9K4L9kaG6l4fwAdQH9hCmVuZHN0cmVhbQplbmRvYmoKNCAwIG9iago8PC9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9UeXBlMAovQmFzZUZvbnQgL0FBQUFBQStBcmlhbE1UCi9FbmNvZGluZyAvSWRlbnRpdHktSAovRGVzY2VuZGFudEZvbnRzIFsxMCAwIFJdCi9Ub1VuaWNvZGUgMTEgMCBSPj4KZW5kb2JqCnhyZWYKMCAxMgowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMTUgMDAwMDAgbiAKMDAwMDAwMDM4MiAwMDAwMCBuIAowMDAwMDAwMTA4IDAwMDAwIG4gCjAwMDAwMDk2MDYgMDAwMDAgbiAKMDAwMDAwMDE0NSAwMDAwMCBuIAowMDAwMDAwNTkwIDAwMDAwIG4gCjAwMDAwMDA2NDUgMDAwMDAgbiAKMDAwMDAwMDY5MiAwMDAwMCBuIAowMDAwMDA4Nzg3IDAwMDAwIG4gCjAwMDAwMDkwMjEgMDAwMDAgbiAKMDAwMDAwOTI4NSAwMDAwMCBuIAp0cmFpbGVyCjw8L1NpemUgMTIKL1Jvb3QgNyAwIFIKL0luZm8gMSAwIFI+PgpzdGFydHhyZWYKOTc0NQolJUVPRgo=","contentEncoding":"BASE64","mediaType":"application/pdf","fileName":"renamed.pdf"}} -{"testStepFinished":{"testCaseStartedId":"55","testStepId":"47","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}} -{"testCaseFinished":{"testCaseStartedId":"55","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}} -{"testCaseStarted":{"id":"56","testCaseId":"48","timestamp":{"seconds":0,"nanos":25000000},"attempt":0}} -{"testStepStarted":{"testCaseStartedId":"56","testStepId":"49","timestamp":{"seconds":0,"nanos":26000000}}} -{"attachment":{"testCaseStartedId":"56","testStepId":"49","body":"https://cucumber.io","contentEncoding":"IDENTITY","mediaType":"text/uri-list"}} -{"testStepFinished":{"testCaseStartedId":"56","testStepId":"49","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":27000000}}} -{"testCaseFinished":{"testCaseStartedId":"56","timestamp":{"seconds":0,"nanos":28000000},"willBeRetried":false}} -{"testRunFinished":{"testRunStartedId":"35","timestamp":{"seconds":0,"nanos":29000000},"success":true}} diff --git a/ruby/spec/support/empty.findAllPickles.results.json b/ruby/spec/support/empty.findAllPickles.results.json deleted file mode 100644 index 56a6051c..00000000 --- a/ruby/spec/support/empty.findAllPickles.results.json +++ /dev/null @@ -1 +0,0 @@ -1 \ No newline at end of file diff --git a/ruby/spec/support/empty.findAllTestCases.results.json b/ruby/spec/support/empty.findAllTestCases.results.json deleted file mode 100644 index 56a6051c..00000000 --- a/ruby/spec/support/empty.findAllTestCases.results.json +++ /dev/null @@ -1 +0,0 @@ -1 \ No newline at end of file diff --git a/ruby/spec/support/empty.findAllTestSteps.results.json b/ruby/spec/support/empty.findAllTestSteps.results.json deleted file mode 100644 index c2270834..00000000 --- a/ruby/spec/support/empty.findAllTestSteps.results.json +++ /dev/null @@ -1 +0,0 @@ -0 \ No newline at end of file diff --git a/ruby/spec/support/empty.findPickleBy.results.json b/ruby/spec/support/empty.findPickleBy.results.json deleted file mode 100644 index 377f01bf..00000000 --- a/ruby/spec/support/empty.findPickleBy.results.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "testCaseStarted" : [ - "Blank Scenario" - ], - "testCaseFinished" : [ - "Blank Scenario" - ], - "testStepStarted" : [ ], - "testStepFinished" : [ ] -} \ No newline at end of file diff --git a/ruby/spec/support/empty.findTestCaseBy.results.json b/ruby/spec/support/empty.findTestCaseBy.results.json deleted file mode 100644 index 5a7775b3..00000000 --- a/ruby/spec/support/empty.findTestCaseBy.results.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "testCaseStarted" : [ - "3" - ], - "testCaseFinished" : [ - "3" - ], - "testStepStarted" : [ ], - "testStepFinished" : [ ] -} \ No newline at end of file diff --git a/ruby/spec/support/empty.ndjson b/ruby/spec/support/empty.ndjson deleted file mode 100644 index 9e2b6bd3..00000000 --- a/ruby/spec/support/empty.ndjson +++ /dev/null @@ -1,9 +0,0 @@ -{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} -{"source":{"data":"Feature: Empty Scenarios\n Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.\n\n Scenario: Blank Scenario\n","uri":"samples/empty/empty.feature","mediaType":"text/x.cucumber.gherkin+plain"}} -{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Empty Scenarios","description":" Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.","children":[{"scenario":{"id":"0","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"Blank Scenario","description":"","steps":[],"examples":[]}}]},"comments":[],"uri":"samples/empty/empty.feature"}} -{"pickle":{"id":"1","uri":"samples/empty/empty.feature","astNodeIds":["0"],"tags":[],"name":"Blank Scenario","language":"en","steps":[]}} -{"testRunStarted":{"id":"2","timestamp":{"seconds":0,"nanos":0}}} -{"testCase":{"id":"3","pickleId":"1","testSteps":[],"testRunStartedId":"2"}} -{"testCaseStarted":{"id":"4","testCaseId":"3","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} -{"testCaseFinished":{"testCaseStartedId":"4","timestamp":{"seconds":0,"nanos":2000000},"willBeRetried":false}} -{"testRunFinished":{"testRunStartedId":"2","timestamp":{"seconds":0,"nanos":3000000},"success":true}} diff --git a/ruby/spec/support/hooks.findAllPickles.results.json b/ruby/spec/support/hooks.findAllPickles.results.json deleted file mode 100644 index d8263ee9..00000000 --- a/ruby/spec/support/hooks.findAllPickles.results.json +++ /dev/null @@ -1 +0,0 @@ -2 \ No newline at end of file diff --git a/ruby/spec/support/hooks.findAllTestCases.results.json b/ruby/spec/support/hooks.findAllTestCases.results.json deleted file mode 100644 index d8263ee9..00000000 --- a/ruby/spec/support/hooks.findAllTestCases.results.json +++ /dev/null @@ -1 +0,0 @@ -2 \ No newline at end of file diff --git a/ruby/spec/support/hooks.findAllTestSteps.results.json b/ruby/spec/support/hooks.findAllTestSteps.results.json deleted file mode 100644 index 62f94575..00000000 --- a/ruby/spec/support/hooks.findAllTestSteps.results.json +++ /dev/null @@ -1 +0,0 @@ -6 \ No newline at end of file diff --git a/ruby/spec/support/hooks.findPickleBy.results.json b/ruby/spec/support/hooks.findPickleBy.results.json deleted file mode 100644 index 4bad4bc7..00000000 --- a/ruby/spec/support/hooks.findPickleBy.results.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "testCaseStarted" : [ - "No tags and a passed step", - "No tags and a failed step" - ], - "testCaseFinished" : [ - "No tags and a passed step", - "No tags and a failed step" - ], - "testStepStarted" : [ - "No tags and a passed step", - "No tags and a passed step", - "No tags and a passed step", - "No tags and a failed step", - "No tags and a failed step", - "No tags and a failed step" - ], - "testStepFinished" : [ - "No tags and a passed step", - "No tags and a passed step", - "No tags and a passed step", - "No tags and a failed step", - "No tags and a failed step", - "No tags and a failed step" - ] -} \ No newline at end of file diff --git a/ruby/spec/support/hooks.findTestCaseBy.results.json b/ruby/spec/support/hooks.findTestCaseBy.results.json deleted file mode 100644 index e6dc9fa5..00000000 --- a/ruby/spec/support/hooks.findTestCaseBy.results.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "testCaseStarted" : [ - "13", - "17" - ], - "testCaseFinished" : [ - "13", - "17" - ], - "testStepStarted" : [ - "13", - "13", - "13", - "17", - "17", - "17" - ], - "testStepFinished" : [ - "13", - "13", - "13", - "17", - "17", - "17" - ] -} \ No newline at end of file diff --git a/ruby/spec/support/hooks.ndjson b/ruby/spec/support/hooks.ndjson deleted file mode 100644 index cc0fc92e..00000000 --- a/ruby/spec/support/hooks.ndjson +++ /dev/null @@ -1,29 +0,0 @@ -{"meta":{"protocolVersion":"30.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} -{"source":{"data":"Feature: Hooks\n Hooks are special steps that run before or after each scenario's steps.\n\n Scenario: No tags and a passed step\n When a step passes\n\n Scenario: No tags and a failed step\n When a step fails\n","uri":"samples/hooks/hooks.feature","mediaType":"text/x.cucumber.gherkin+plain"}} -{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Hooks","description":" Hooks are special steps that run before or after each scenario's steps.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":4,"column":3},"keyword":"Scenario","name":"No tags and a passed step","description":"","steps":[{"id":"0","location":{"line":5,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"No tags and a failed step","description":"","steps":[{"id":"2","location":{"line":8,"column":5},"keyword":"When ","keywordType":"Action","text":"a step fails"}],"examples":[]}}]},"comments":[],"uri":"samples/hooks/hooks.feature"}} -{"pickle":{"id":"5","uri":"samples/hooks/hooks.feature","astNodeIds":["1"],"tags":[],"name":"No tags and a passed step","language":"en","steps":[{"id":"4","text":"a step passes","type":"Action","astNodeIds":["0"]}]}} -{"pickle":{"id":"7","uri":"samples/hooks/hooks.feature","astNodeIds":["3"],"tags":[],"name":"No tags and a failed step","language":"en","steps":[{"id":"6","text":"a step fails","type":"Action","astNodeIds":["2"]}]}} -{"stepDefinition":{"id":"9","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step passes"},"sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":7}}}} -{"stepDefinition":{"id":"10","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step fails"},"sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":11}}}} -{"hook":{"id":"8","type":"BEFORE_TEST_CASE","sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":3}}}} -{"hook":{"id":"11","type":"AFTER_TEST_CASE","sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":15}}}} -{"testRunStarted":{"id":"12","timestamp":{"seconds":0,"nanos":0}}} -{"testCase":{"id":"13","pickleId":"5","testSteps":[{"id":"14","hookId":"8"},{"id":"15","pickleStepId":"4","stepDefinitionIds":["9"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"16","hookId":"11"}],"testRunStartedId":"12"}} -{"testCase":{"id":"17","pickleId":"7","testSteps":[{"id":"18","hookId":"8"},{"id":"19","pickleStepId":"6","stepDefinitionIds":["10"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"20","hookId":"11"}],"testRunStartedId":"12"}} -{"testCaseStarted":{"id":"21","testCaseId":"13","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} -{"testStepStarted":{"testCaseStartedId":"21","testStepId":"14","timestamp":{"seconds":0,"nanos":2000000}}} -{"testStepFinished":{"testCaseStartedId":"21","testStepId":"14","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} -{"testStepStarted":{"testCaseStartedId":"21","testStepId":"15","timestamp":{"seconds":0,"nanos":4000000}}} -{"testStepFinished":{"testCaseStartedId":"21","testStepId":"15","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} -{"testStepStarted":{"testCaseStartedId":"21","testStepId":"16","timestamp":{"seconds":0,"nanos":6000000}}} -{"testStepFinished":{"testCaseStartedId":"21","testStepId":"16","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} -{"testCaseFinished":{"testCaseStartedId":"21","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} -{"testCaseStarted":{"id":"22","testCaseId":"17","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}} -{"testStepStarted":{"testCaseStartedId":"22","testStepId":"18","timestamp":{"seconds":0,"nanos":10000000}}} -{"testStepFinished":{"testCaseStartedId":"22","testStepId":"18","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} -{"testStepStarted":{"testCaseStartedId":"22","testStepId":"19","timestamp":{"seconds":0,"nanos":12000000}}} -{"testStepFinished":{"testCaseStartedId":"22","testStepId":"19","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/hooks/hooks.feature:8"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}} -{"testStepStarted":{"testCaseStartedId":"22","testStepId":"20","timestamp":{"seconds":0,"nanos":14000000}}} -{"testStepFinished":{"testCaseStartedId":"22","testStepId":"20","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} -{"testCaseFinished":{"testCaseStartedId":"22","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}} -{"testRunFinished":{"testRunStartedId":"12","timestamp":{"seconds":0,"nanos":17000000},"success":false}} diff --git a/ruby/spec/support/minimal.findAllPickles.results.json b/ruby/spec/support/minimal.findAllPickles.results.json deleted file mode 100644 index 56a6051c..00000000 --- a/ruby/spec/support/minimal.findAllPickles.results.json +++ /dev/null @@ -1 +0,0 @@ -1 \ No newline at end of file diff --git a/ruby/spec/support/minimal.findAllTestCases.results.json b/ruby/spec/support/minimal.findAllTestCases.results.json deleted file mode 100644 index 56a6051c..00000000 --- a/ruby/spec/support/minimal.findAllTestCases.results.json +++ /dev/null @@ -1 +0,0 @@ -1 \ No newline at end of file diff --git a/ruby/spec/support/minimal.findAllTestSteps.results.json b/ruby/spec/support/minimal.findAllTestSteps.results.json deleted file mode 100644 index 56a6051c..00000000 --- a/ruby/spec/support/minimal.findAllTestSteps.results.json +++ /dev/null @@ -1 +0,0 @@ -1 \ No newline at end of file diff --git a/ruby/spec/support/minimal.findPickleBy.results.json b/ruby/spec/support/minimal.findPickleBy.results.json deleted file mode 100644 index 275ca2f3..00000000 --- a/ruby/spec/support/minimal.findPickleBy.results.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "testCaseStarted" : [ - "cukes" - ], - "testCaseFinished" : [ - "cukes" - ], - "testStepStarted" : [ - "cukes" - ], - "testStepFinished" : [ - "cukes" - ] -} \ No newline at end of file diff --git a/ruby/spec/support/minimal.findTestCaseBy.results.json b/ruby/spec/support/minimal.findTestCaseBy.results.json deleted file mode 100644 index e5c7e2f6..00000000 --- a/ruby/spec/support/minimal.findTestCaseBy.results.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "testCaseStarted" : [ - "6" - ], - "testCaseFinished" : [ - "6" - ], - "testStepStarted" : [ - "6" - ], - "testStepFinished" : [ - "6" - ] -} \ No newline at end of file diff --git a/ruby/spec/support/minimal.ndjson b/ruby/spec/support/minimal.ndjson deleted file mode 100644 index f35958f6..00000000 --- a/ruby/spec/support/minimal.ndjson +++ /dev/null @@ -1,12 +0,0 @@ -{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} -{"source":{"data":"Feature: minimal\n \n Cucumber doesn't execute this markdown, but @cucumber/react renders it.\n \n * This is\n * a bullet\n * list\n \n Scenario: cukes\n Given I have 42 cukes in my belly\n","uri":"samples/minimal/minimal.feature","mediaType":"text/x.cucumber.gherkin+plain"}} -{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"minimal","description":" Cucumber doesn't execute this markdown, but @cucumber/react renders it.\n \n * This is\n * a bullet\n * list","children":[{"scenario":{"id":"1","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"cukes","description":"","steps":[{"id":"0","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"I have 42 cukes in my belly"}],"examples":[]}}]},"comments":[],"uri":"samples/minimal/minimal.feature"}} -{"pickle":{"id":"3","uri":"samples/minimal/minimal.feature","astNodeIds":["1"],"tags":[],"name":"cukes","language":"en","steps":[{"id":"2","text":"I have 42 cukes in my belly","type":"Context","astNodeIds":["0"]}]}} -{"stepDefinition":{"id":"4","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I have {int} cukes in my belly"},"sourceReference":{"uri":"samples/minimal/minimal.ts","location":{"line":3}}}} -{"testRunStarted":{"id":"5","timestamp":{"seconds":0,"nanos":0}}} -{"testCase":{"id":"6","pickleId":"3","testSteps":[{"id":"7","pickleStepId":"2","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":7,"value":"42","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"5"}} -{"testCaseStarted":{"id":"8","testCaseId":"6","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} -{"testStepStarted":{"testCaseStartedId":"8","testStepId":"7","timestamp":{"seconds":0,"nanos":2000000}}} -{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} -{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} -{"testRunFinished":{"testRunStartedId":"5","timestamp":{"seconds":0,"nanos":5000000},"success":true}} diff --git a/ruby/spec/support/rules.findAllPickles.results.json b/ruby/spec/support/rules.findAllPickles.results.json deleted file mode 100644 index e440e5c8..00000000 --- a/ruby/spec/support/rules.findAllPickles.results.json +++ /dev/null @@ -1 +0,0 @@ -3 \ No newline at end of file diff --git a/ruby/spec/support/rules.findAllTestCases.results.json b/ruby/spec/support/rules.findAllTestCases.results.json deleted file mode 100644 index e440e5c8..00000000 --- a/ruby/spec/support/rules.findAllTestCases.results.json +++ /dev/null @@ -1 +0,0 @@ -3 \ No newline at end of file diff --git a/ruby/spec/support/rules.findAllTestSteps.results.json b/ruby/spec/support/rules.findAllTestSteps.results.json deleted file mode 100644 index 3cacc0b9..00000000 --- a/ruby/spec/support/rules.findAllTestSteps.results.json +++ /dev/null @@ -1 +0,0 @@ -12 \ No newline at end of file diff --git a/ruby/spec/support/rules.findPickleBy.results.json b/ruby/spec/support/rules.findPickleBy.results.json deleted file mode 100644 index 17a87cba..00000000 --- a/ruby/spec/support/rules.findPickleBy.results.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "testCaseStarted" : [ - "Not enough money", - "Enough money", - "No chocolates left" - ], - "testCaseFinished" : [ - "Not enough money", - "Enough money", - "No chocolates left" - ], - "testStepStarted" : [ - "Not enough money", - "Not enough money", - "Not enough money", - "Not enough money", - "Enough money", - "Enough money", - "Enough money", - "Enough money", - "No chocolates left", - "No chocolates left", - "No chocolates left", - "No chocolates left" - ], - "testStepFinished" : [ - "Not enough money", - "Not enough money", - "Not enough money", - "Not enough money", - "Enough money", - "Enough money", - "Enough money", - "Enough money", - "No chocolates left", - "No chocolates left", - "No chocolates left", - "No chocolates left" - ] -} \ No newline at end of file diff --git a/ruby/spec/support/rules.findTestCaseBy.results.json b/ruby/spec/support/rules.findTestCaseBy.results.json deleted file mode 100644 index 06e9bbc1..00000000 --- a/ruby/spec/support/rules.findTestCaseBy.results.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "testCaseStarted" : [ - "40", - "45", - "50" - ], - "testCaseFinished" : [ - "40", - "45", - "50" - ], - "testStepStarted" : [ - "40", - "40", - "40", - "40", - "45", - "45", - "45", - "45", - "50", - "50", - "50", - "50" - ], - "testStepFinished" : [ - "40", - "40", - "40", - "40", - "45", - "45", - "45", - "45", - "50", - "50", - "50", - "50" - ] -} \ No newline at end of file diff --git a/ruby/spec/support/rules.ndjson b/ruby/spec/support/rules.ndjson deleted file mode 100644 index 81bb984b..00000000 --- a/ruby/spec/support/rules.ndjson +++ /dev/null @@ -1,47 +0,0 @@ -{"meta":{"protocolVersion":"28.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} -{"source":{"data":"Feature: Usage of a `Rule`\n You can place scenarios inside rules. This makes it possible to structure Gherkin documents\n in the same way as [example maps](https://cucumber.io/blog/bdd/example-mapping-introduction/).\n\n You can also use the Examples synonym for Scenario to make them even similar.\n\n Rule: A sale cannot happen if the customer does not have enough money\n # Unhappy path\n Example: Not enough money\n Given the customer has 100 cents\n And there are chocolate bars in stock\n When the customer tries to buy a 125 cent chocolate bar\n Then the sale should not happen\n\n # Happy path\n Example: Enough money\n Given the customer has 100 cents\n And there are chocolate bars in stock\n When the customer tries to buy a 75 cent chocolate bar\n Then the sale should happen\n\n @some-tag\n Rule: a sale cannot happen if there is no stock\n # Unhappy path\n Example: No chocolates left\n Given the customer has 100 cents\n And there are no chocolate bars in stock\n When the customer tries to buy a 1 cent chocolate bar\n Then the sale should not happen\n","uri":"samples/rules/rules.feature","mediaType":"text/x.cucumber.gherkin+plain"}} -{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Usage of a `Rule`","description":" You can place scenarios inside rules. This makes it possible to structure Gherkin documents\n in the same way as [example maps](https://cucumber.io/blog/bdd/example-mapping-introduction/).\n\n You can also use the Examples synonym for Scenario to make them even similar.","children":[{"rule":{"id":"10","location":{"line":7,"column":3},"keyword":"Rule","name":"A sale cannot happen if the customer does not have enough money","description":"","children":[{"scenario":{"id":"4","tags":[],"location":{"line":9,"column":5},"keyword":"Example","name":"Not enough money","description":"","steps":[{"id":"0","location":{"line":10,"column":7},"keyword":"Given ","keywordType":"Context","text":"the customer has 100 cents"},{"id":"1","location":{"line":11,"column":7},"keyword":"And ","keywordType":"Conjunction","text":"there are chocolate bars in stock"},{"id":"2","location":{"line":12,"column":7},"keyword":"When ","keywordType":"Action","text":"the customer tries to buy a 125 cent chocolate bar"},{"id":"3","location":{"line":13,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"the sale should not happen"}],"examples":[]}},{"scenario":{"id":"9","tags":[],"location":{"line":16,"column":5},"keyword":"Example","name":"Enough money","description":"","steps":[{"id":"5","location":{"line":17,"column":7},"keyword":"Given ","keywordType":"Context","text":"the customer has 100 cents"},{"id":"6","location":{"line":18,"column":7},"keyword":"And ","keywordType":"Conjunction","text":"there are chocolate bars in stock"},{"id":"7","location":{"line":19,"column":7},"keyword":"When ","keywordType":"Action","text":"the customer tries to buy a 75 cent chocolate bar"},{"id":"8","location":{"line":20,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"the sale should happen"}],"examples":[]}}],"tags":[]}},{"rule":{"id":"17","location":{"line":23,"column":3},"keyword":"Rule","name":"a sale cannot happen if there is no stock","description":"","children":[{"scenario":{"id":"15","tags":[],"location":{"line":25,"column":5},"keyword":"Example","name":"No chocolates left","description":"","steps":[{"id":"11","location":{"line":26,"column":7},"keyword":"Given ","keywordType":"Context","text":"the customer has 100 cents"},{"id":"12","location":{"line":27,"column":7},"keyword":"And ","keywordType":"Conjunction","text":"there are no chocolate bars in stock"},{"id":"13","location":{"line":28,"column":7},"keyword":"When ","keywordType":"Action","text":"the customer tries to buy a 1 cent chocolate bar"},{"id":"14","location":{"line":29,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"the sale should not happen"}],"examples":[]}}],"tags":[{"location":{"line":22,"column":3},"name":"@some-tag","id":"16"}]}}]},"comments":[{"location":{"line":8,"column":1},"text":" # Unhappy path"},{"location":{"line":15,"column":1},"text":" # Happy path"},{"location":{"line":24,"column":1},"text":" # Unhappy path"}],"uri":"samples/rules/rules.feature"}} -{"pickle":{"id":"22","uri":"samples/rules/rules.feature","astNodeIds":["4"],"tags":[],"name":"Not enough money","language":"en","steps":[{"id":"18","text":"the customer has 100 cents","type":"Context","astNodeIds":["0"]},{"id":"19","text":"there are chocolate bars in stock","type":"Context","astNodeIds":["1"]},{"id":"20","text":"the customer tries to buy a 125 cent chocolate bar","type":"Action","astNodeIds":["2"]},{"id":"21","text":"the sale should not happen","type":"Outcome","astNodeIds":["3"]}]}} -{"pickle":{"id":"27","uri":"samples/rules/rules.feature","astNodeIds":["9"],"tags":[],"name":"Enough money","language":"en","steps":[{"id":"23","text":"the customer has 100 cents","type":"Context","astNodeIds":["5"]},{"id":"24","text":"there are chocolate bars in stock","type":"Context","astNodeIds":["6"]},{"id":"25","text":"the customer tries to buy a 75 cent chocolate bar","type":"Action","astNodeIds":["7"]},{"id":"26","text":"the sale should happen","type":"Outcome","astNodeIds":["8"]}]}} -{"pickle":{"id":"32","uri":"samples/rules/rules.feature","astNodeIds":["15"],"tags":[{"name":"@some-tag","astNodeId":"16"}],"name":"No chocolates left","language":"en","steps":[{"id":"28","text":"the customer has 100 cents","type":"Context","astNodeIds":["11"]},{"id":"29","text":"there are no chocolate bars in stock","type":"Context","astNodeIds":["12"]},{"id":"30","text":"the customer tries to buy a 1 cent chocolate bar","type":"Action","astNodeIds":["13"]},{"id":"31","text":"the sale should not happen","type":"Outcome","astNodeIds":["14"]}]}} -{"stepDefinition":{"id":"33","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the customer has {int} cents"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":4}}}} -{"stepDefinition":{"id":"34","pattern":{"type":"CUCUMBER_EXPRESSION","source":"there are chocolate bars in stock"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":8}}}} -{"stepDefinition":{"id":"35","pattern":{"type":"CUCUMBER_EXPRESSION","source":"there are no chocolate bars in stock"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":12}}}} -{"stepDefinition":{"id":"36","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the customer tries to buy a {int} cent chocolate bar"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":16}}}} -{"stepDefinition":{"id":"37","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the sale should not happen"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":22}}}} -{"stepDefinition":{"id":"38","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the sale should happen"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":26}}}} -{"testRunStarted":{"id":"39","timestamp":{"seconds":0,"nanos":0}}} -{"testCase":{"id":"40","pickleId":"22","testSteps":[{"id":"41","pickleStepId":"18","stepDefinitionIds":["33"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":17,"value":"100","children":[]},"parameterTypeName":"int"}]}]},{"id":"42","pickleStepId":"19","stepDefinitionIds":["34"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"43","pickleStepId":"20","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":28,"value":"125","children":[]},"parameterTypeName":"int"}]}]},{"id":"44","pickleStepId":"21","stepDefinitionIds":["37"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"39"}} -{"testCase":{"id":"45","pickleId":"27","testSteps":[{"id":"46","pickleStepId":"23","stepDefinitionIds":["33"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":17,"value":"100","children":[]},"parameterTypeName":"int"}]}]},{"id":"47","pickleStepId":"24","stepDefinitionIds":["34"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"48","pickleStepId":"25","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":28,"value":"75","children":[]},"parameterTypeName":"int"}]}]},{"id":"49","pickleStepId":"26","stepDefinitionIds":["38"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"39"}} -{"testCase":{"id":"50","pickleId":"32","testSteps":[{"id":"51","pickleStepId":"28","stepDefinitionIds":["33"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":17,"value":"100","children":[]},"parameterTypeName":"int"}]}]},{"id":"52","pickleStepId":"29","stepDefinitionIds":["35"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"53","pickleStepId":"30","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":28,"value":"1","children":[]},"parameterTypeName":"int"}]}]},{"id":"54","pickleStepId":"31","stepDefinitionIds":["37"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"39"}} -{"testCaseStarted":{"id":"55","testCaseId":"40","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} -{"testStepStarted":{"testCaseStartedId":"55","testStepId":"41","timestamp":{"seconds":0,"nanos":2000000}}} -{"testStepFinished":{"testCaseStartedId":"55","testStepId":"41","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} -{"testStepStarted":{"testCaseStartedId":"55","testStepId":"42","timestamp":{"seconds":0,"nanos":4000000}}} -{"testStepFinished":{"testCaseStartedId":"55","testStepId":"42","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} -{"testStepStarted":{"testCaseStartedId":"55","testStepId":"43","timestamp":{"seconds":0,"nanos":6000000}}} -{"testStepFinished":{"testCaseStartedId":"55","testStepId":"43","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} -{"testStepStarted":{"testCaseStartedId":"55","testStepId":"44","timestamp":{"seconds":0,"nanos":8000000}}} -{"testStepFinished":{"testCaseStartedId":"55","testStepId":"44","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":9000000}}} -{"testCaseFinished":{"testCaseStartedId":"55","timestamp":{"seconds":0,"nanos":10000000},"willBeRetried":false}} -{"testCaseStarted":{"id":"56","testCaseId":"45","timestamp":{"seconds":0,"nanos":11000000},"attempt":0}} -{"testStepStarted":{"testCaseStartedId":"56","testStepId":"46","timestamp":{"seconds":0,"nanos":12000000}}} -{"testStepFinished":{"testCaseStartedId":"56","testStepId":"46","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}} -{"testStepStarted":{"testCaseStartedId":"56","testStepId":"47","timestamp":{"seconds":0,"nanos":14000000}}} -{"testStepFinished":{"testCaseStartedId":"56","testStepId":"47","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} -{"testStepStarted":{"testCaseStartedId":"56","testStepId":"48","timestamp":{"seconds":0,"nanos":16000000}}} -{"testStepFinished":{"testCaseStartedId":"56","testStepId":"48","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":17000000}}} -{"testStepStarted":{"testCaseStartedId":"56","testStepId":"49","timestamp":{"seconds":0,"nanos":18000000}}} -{"testStepFinished":{"testCaseStartedId":"56","testStepId":"49","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} -{"testCaseFinished":{"testCaseStartedId":"56","timestamp":{"seconds":0,"nanos":20000000},"willBeRetried":false}} -{"testCaseStarted":{"id":"57","testCaseId":"50","timestamp":{"seconds":0,"nanos":21000000},"attempt":0}} -{"testStepStarted":{"testCaseStartedId":"57","testStepId":"51","timestamp":{"seconds":0,"nanos":22000000}}} -{"testStepFinished":{"testCaseStartedId":"57","testStepId":"51","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}} -{"testStepStarted":{"testCaseStartedId":"57","testStepId":"52","timestamp":{"seconds":0,"nanos":24000000}}} -{"testStepFinished":{"testCaseStartedId":"57","testStepId":"52","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":25000000}}} -{"testStepStarted":{"testCaseStartedId":"57","testStepId":"53","timestamp":{"seconds":0,"nanos":26000000}}} -{"testStepFinished":{"testCaseStartedId":"57","testStepId":"53","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":27000000}}} -{"testStepStarted":{"testCaseStartedId":"57","testStepId":"54","timestamp":{"seconds":0,"nanos":28000000}}} -{"testStepFinished":{"testCaseStartedId":"57","testStepId":"54","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":29000000}}} -{"testCaseFinished":{"testCaseStartedId":"57","timestamp":{"seconds":0,"nanos":30000000},"willBeRetried":false}} -{"testRunFinished":{"testRunStartedId":"39","timestamp":{"seconds":0,"nanos":31000000},"success":true}} From 171185e80c27f5602c706d74643e2810922154d5 Mon Sep 17 00:00:00 2001 From: Matt Wynne Date: Tue, 16 Jun 2026 22:36:21 -0700 Subject: [PATCH 34/55] Update .github/workflows/test-ruby.yml Co-authored-by: Luke Hill <20105237+luke-hill@users.noreply.github.com> --- .github/workflows/test-ruby.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index df469ff1..89330627 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -28,7 +28,7 @@ jobs: matrix: os: - ubuntu-latest - ruby: ['3.2', '3.3', '3.4', '3.5'] + ruby: ['3.2', '3.3', '3.4', '4.0'] include: - os: macos-latest ruby: '3.4' From 985d9910383c54427703d09cf5b7e9329fa9ff21 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Tue, 30 Jun 2026 17:13:03 +0100 Subject: [PATCH 35/55] Bump gemspec to only use ruby 3.3+ and higher dev gems --- ruby/cucumber-query.gemspec | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ruby/cucumber-query.gemspec b/ruby/cucumber-query.gemspec index 3563c68f..e056bc74 100644 --- a/ruby/cucumber-query.gemspec +++ b/ruby/cucumber-query.gemspec @@ -10,7 +10,7 @@ Gem::Specification.new do |s| s.homepage = 'https://github.com/cucumber/query' s.platform = Gem::Platform::RUBY s.license = 'MIT' - s.required_ruby_version = '>= 3.2' + s.required_ruby_version = '>= 3.3' s.required_rubygems_version = '>= 3.2.8' s.metadata = { @@ -21,12 +21,12 @@ Gem::Specification.new do |s| 'source_code_uri' => 'https://github.com/cucumber/query/blob/main/ruby' } - s.add_dependency 'cucumber-messages', '> 31', '< 33' + s.add_dependency 'cucumber-messages', '> 31', '< 34' s.add_development_dependency 'rspec', '~> 3.13' - s.add_development_dependency 'rubocop', '~> 1.80.0' - s.add_development_dependency 'rubocop-performance', '~> 1.25.0' - s.add_development_dependency 'rubocop-rspec', '~> 3.7.0' + s.add_development_dependency 'rubocop', '~> 1.88.0' + s.add_development_dependency 'rubocop-performance', '~> 1.26.1' + s.add_development_dependency 'rubocop-rspec', '~> 3.10.2' s.files = Dir['LICENSE', 'VERSION', 'lib/**/*'] s.rdoc_options = ['--charset=UTF-8'] From 92a4ed304f71f220829b13323fae48b7a3ec7d4d Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Tue, 30 Jun 2026 17:13:30 +0100 Subject: [PATCH 36/55] Target Ruby 3.3+ --- ruby/.rubocop.yml | 2 +- ruby/Gemfile.lock | 39 ++++++++++++++++++++------------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/ruby/.rubocop.yml b/ruby/.rubocop.yml index 4c19d3f7..f587f878 100644 --- a/ruby/.rubocop.yml +++ b/ruby/.rubocop.yml @@ -9,7 +9,7 @@ inherit_mode: - Exclude AllCops: - TargetRubyVersion: 3.2 + TargetRubyVersion: 3.3 NewCops: enable # Stylistic preference for cucumber diff --git a/ruby/Gemfile.lock b/ruby/Gemfile.lock index 8bded9b8..bc7301b3 100644 --- a/ruby/Gemfile.lock +++ b/ruby/Gemfile.lock @@ -2,7 +2,7 @@ PATH remote: . specs: cucumber-query (15.0.1) - cucumber-messages (> 31, < 33) + cucumber-messages (> 31, < 34) GEM remote: https://rubygems.org/ @@ -11,13 +11,13 @@ GEM cucumber-messages (32.3.1) diff-lcs (1.5.1) json (2.9.1) - language_server-protocol (3.17.0.4) + language_server-protocol (3.17.0.5) lint_roller (1.1.0) parallel (1.26.3) parser (3.3.9.0) ast (~> 2.4.1) racc - prism (1.4.0) + prism (1.9.0) racc (1.8.1) rainbow (3.1.1) regexp_parser (2.10.0) @@ -34,31 +34,32 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.2) - rubocop (1.80.2) + rubocop (1.88.0) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) - parallel (~> 1.10) + parallel (>= 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.46.0, < 2.0) + rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.46.0) + rubocop-ast (1.49.1) parser (>= 3.3.7.2) - prism (~> 1.4) - rubocop-performance (1.25.0) + prism (~> 1.7) + rubocop-performance (1.26.1) lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.38.0, < 2.0) - rubocop-rspec (3.7.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rspec (3.10.2) lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) + regexp_parser (>= 2.0) + rubocop (~> 1.86, >= 1.86.2) ruby-progressbar (1.13.0) - unicode-display_width (3.1.4) - unicode-emoji (~> 4.0, >= 4.0.4) - unicode-emoji (4.0.4) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) PLATFORMS ruby @@ -67,9 +68,9 @@ PLATFORMS DEPENDENCIES cucumber-query! rspec (~> 3.13) - rubocop (~> 1.80.0) - rubocop-performance (~> 1.25.0) - rubocop-rspec (~> 3.7.0) + rubocop (~> 1.88.0) + rubocop-performance (~> 1.26.1) + rubocop-rspec (~> 3.10.2) BUNDLED WITH - 2.5.22 + 4.0.14 From 5d39812ea673e2c2388ef6de57384625b9d64392 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Tue, 30 Jun 2026 17:18:01 +0100 Subject: [PATCH 37/55] Fix CI --- .github/workflows/release-rubygem.yaml | 21 ++++----------------- .github/workflows/test-ruby.yml | 12 +++--------- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/.github/workflows/release-rubygem.yaml b/.github/workflows/release-rubygem.yaml index d027b8ec..c4f47d15 100644 --- a/.github/workflows/release-rubygem.yaml +++ b/.github/workflows/release-rubygem.yaml @@ -16,27 +16,14 @@ jobs: id-token: write contents: read steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - - - uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0 + - uses: ruby/setup-ruby@9eb537ca036ebaed86729dcb9309076e4c5c3b74 # v1.314.0 with: - ruby-version: '3.4' - bundler-cache: false + ruby-version: 4.0.5 working-directory: ruby - - - run: bundle install - working-directory: ruby - - - run: bundle exec rubocop --format simple - working-directory: ruby - - - run: bundle exec rspec --format progress - working-directory: ruby - - - uses: rubygems/configure-rubygems-credentials@762a4b77c3300434bb57c7ce80b20e36231927aa # v2.0.0 - + - uses: rubygems/configure-rubygems-credentials@dc5a8d8553e6ee01fc26761a49e99e733d17954a # v2.1.0 - uses: cucumber/action-publish-rubygem@4e79bb9aed597c835e8438f57c04d0996ab80d72 # v2.0.0 with: working-directory: ruby diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 89330627..254bb811 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -28,29 +28,23 @@ jobs: matrix: os: - ubuntu-latest - ruby: ['3.2', '3.3', '3.4', '4.0'] + ruby: ['3.3', '3.4', '4.0'] include: - os: macos-latest - ruby: '3.4' + ruby: '4.0' - os: windows-latest - ruby: '3.4' + ruby: '4.0' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true working-directory: ruby - - run: bundle exec rubocop --format simple working-directory: ruby - - run: bundle exec rspec --format progress working-directory: ruby - - - run: gem build cucumber-query.gemspec - working-directory: ruby From 648fa0c0439622960889670af982387c3dd463d6 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Tue, 30 Jun 2026 17:52:19 +0100 Subject: [PATCH 38/55] Convert into new testing format for new query; --- ruby/spec/cucumber/query_spec.rb | 45 +++++--------------------------- 1 file changed, 7 insertions(+), 38 deletions(-) diff --git a/ruby/spec/cucumber/query_spec.rb b/ruby/spec/cucumber/query_spec.rb index 0ec4b088..340b006b 100644 --- a/ruby/spec/cucumber/query_spec.rb +++ b/ruby/spec/cucumber/query_spec.rb @@ -17,6 +17,12 @@ results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_test_case_by(message).id } results end, + 'findTestStepBy' => lambda do |query| + results = {} + results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_test_step_by(message).id } + results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_test_step_by(message).id } + results + end, 'findPickleBy' => lambda do |query| results = {} results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_pickle_by(message).name } @@ -40,45 +46,8 @@ def parse_ndjson_file(path) let(:repository) { Cucumber::Repository.new } - describe 'packaged files' do - it 'can require every packaged library file after requiring cucumber/query' do - gemspec = Gem::Specification.load(File.expand_path('../../cucumber-query.gemspec', __dir__)) - packaged_files = gemspec.files.grep(%r{\Alib/.*\.rb\z}) - packaged_requires = packaged_files.map { |file| file.delete_prefix('lib/').delete_suffix('.rb') } - - packaged_requires.each do |path| - expect { require path }.not_to raise_error - end - end - end - - describe '#find_test_step_by' do - let(:source) { testdata_path('minimal.ndjson') } - let(:messages) { parse_ndjson_file(source) } - - before do - messages.each { |message| repository.update(message) } - end - - it 'finds test steps by TestStepStarted messages' do - expect(query.find_all_test_step_started).not_to be_empty - - query.find_all_test_step_started.each do |test_step_started| - expect(query.find_test_step_by(test_step_started).id).to eq(test_step_started.test_step_id) - end - end - - it 'finds test steps by TestStepFinished messages' do - expect(query.find_all_test_step_finished).not_to be_empty - - query.find_all_test_step_finished.each do |test_step_finished| - expect(query.find_test_step_by(test_step_finished).id).to eq(test_step_finished.test_step_id) - end - end - end - source_names.product(queries.to_a).each do |source_name, (query_name, query_proc)| - describe "executes the query '#{query_name}' against the testdata source '#{source_name}'" do + describe "Query: '#{query_name}'. testdata source: '#{source_name}'" do let(:source) { testdata_path("#{source_name}.ndjson") } let(:expected_results_file) { testdata_path("#{source_name}.#{query_name}.results.json") } let(:messages) { parse_ndjson_file(source) } From 84e15603717bd2e14fd604457cced4629525b2f6 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Tue, 30 Jun 2026 18:28:20 +0100 Subject: [PATCH 39/55] Tidy up test iteration code to ensure we only select files that will run (let alone pass!) --- ruby/spec/cucumber/query_spec.rb | 74 +++++++++++++++++++------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/ruby/spec/cucumber/query_spec.rb b/ruby/spec/cucumber/query_spec.rb index 340b006b..a6baf1bd 100644 --- a/ruby/spec/cucumber/query_spec.rb +++ b/ruby/spec/cucumber/query_spec.rb @@ -3,36 +3,6 @@ require 'json' require 'cucumber/query' -source_names = %w[attachments empty hooks minimal rules].freeze - -queries = { - 'findAllPickles' => ->(query) { query.find_all_pickles.length }, - 'findAllTestCases' => ->(query) { query.find_all_test_cases.length }, - 'findAllTestSteps' => ->(query) { query.find_all_test_steps.length }, - 'findTestCaseBy' => lambda do |query| - results = {} - results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_test_case_by(message).id } - results['testCaseFinished'] = query.find_all_test_case_finished.map { |message| query.find_test_case_by(message).id } - results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_test_case_by(message).id } - results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_test_case_by(message).id } - results - end, - 'findTestStepBy' => lambda do |query| - results = {} - results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_test_step_by(message).id } - results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_test_step_by(message).id } - results - end, - 'findPickleBy' => lambda do |query| - results = {} - results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_pickle_by(message).name } - results['testCaseFinished'] = query.find_all_test_case_finished.map { |message| query.find_pickle_by(message).name } - results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_pickle_by(message).name } - results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_pickle_by(message).name } - results - end -}.freeze - RSpec.describe Cucumber::Query do def testdata_path(name) File.expand_path("../../../testdata/src/#{name}", __dir__) @@ -42,11 +12,53 @@ def parse_ndjson_file(path) Cucumber::Messages::Helpers::NdjsonToMessageEnumerator.new(File.read(path)) end + queries = { + 'findAllPickles' => ->(query) { query.find_all_pickles.length }, + 'findAllTestCases' => ->(query) { query.find_all_test_cases.length }, + 'findAllTestSteps' => ->(query) { query.find_all_test_steps.length }, + 'findTestCaseBy' => lambda do |query| + results = {} + results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_test_case_by(message).id } + results['testCaseFinished'] = query.find_all_test_case_finished.map { |message| query.find_test_case_by(message).id } + results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_test_case_by(message).id } + results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_test_case_by(message).id } + results + end, + 'findTestStepBy' => lambda do |query| + results = {} + results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_test_step_by(message).id } + results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_test_step_by(message).id } + results + end, + 'findPickleBy' => lambda do |query| + results = {} + results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_pickle_by(message).name } + results['testCaseFinished'] = query.find_all_test_case_finished.map { |message| query.find_pickle_by(message).name } + results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_pickle_by(message).name } + results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_pickle_by(message).name } + results + end + }.freeze + + # This list needs to be tightly controlled. + # New items should be added when new queries have been adapted to work with them + # AND + # When the requisite sample data result files have been created + enabled_files = %w[attachments empty hooks minimal rules].freeze + + files_in_testdata = Dir.glob(File.expand_path("../../../testdata/src/*.ndjson", __dir__)) + files_in_testdata = files_in_testdata.select do |file| + enabled_files.any? do |enabled_file| + file.match?("/#{enabled_file}.ndjson") + end + end + filenames_in_testdata = files_in_testdata.map { it.split('/').last.split('.').first } + subject(:query) { described_class.new(repository) } let(:repository) { Cucumber::Repository.new } - source_names.product(queries.to_a).each do |source_name, (query_name, query_proc)| + filenames_in_testdata.product(queries.to_a).each do |source_name, (query_name, query_proc)| describe "Query: '#{query_name}'. testdata source: '#{source_name}'" do let(:source) { testdata_path("#{source_name}.ndjson") } let(:expected_results_file) { testdata_path("#{source_name}.#{query_name}.results.json") } From f385309407c336295001385b33efac5c9cf7cb0e Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Tue, 30 Jun 2026 18:30:21 +0100 Subject: [PATCH 40/55] Add in more cck examples to be tested against --- ruby/spec/cucumber/query_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/spec/cucumber/query_spec.rb b/ruby/spec/cucumber/query_spec.rb index a6baf1bd..841701f0 100644 --- a/ruby/spec/cucumber/query_spec.rb +++ b/ruby/spec/cucumber/query_spec.rb @@ -44,7 +44,7 @@ def parse_ndjson_file(path) # New items should be added when new queries have been adapted to work with them # AND # When the requisite sample data result files have been created - enabled_files = %w[attachments empty hooks minimal rules].freeze + enabled_files = %w[attachments empty examples-tables global-hooks hooks minimal rules unknown-parameter-type].freeze files_in_testdata = Dir.glob(File.expand_path("../../../testdata/src/*.ndjson", __dir__)) files_in_testdata = files_in_testdata.select do |file| From 048184e6b424ecb8edd9a61fe4e3933834bdca9d Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Tue, 30 Jun 2026 18:34:04 +0100 Subject: [PATCH 41/55] Remove gemfile.lock --- ruby/Gemfile.lock | 76 ----------------------------------------------- 1 file changed, 76 deletions(-) delete mode 100644 ruby/Gemfile.lock diff --git a/ruby/Gemfile.lock b/ruby/Gemfile.lock deleted file mode 100644 index bc7301b3..00000000 --- a/ruby/Gemfile.lock +++ /dev/null @@ -1,76 +0,0 @@ -PATH - remote: . - specs: - cucumber-query (15.0.1) - cucumber-messages (> 31, < 34) - -GEM - remote: https://rubygems.org/ - specs: - ast (2.4.2) - cucumber-messages (32.3.1) - diff-lcs (1.5.1) - json (2.9.1) - language_server-protocol (3.17.0.5) - lint_roller (1.1.0) - parallel (1.26.3) - parser (3.3.9.0) - ast (~> 2.4.1) - racc - prism (1.9.0) - racc (1.8.1) - rainbow (3.1.1) - regexp_parser (2.10.0) - rspec (3.13.0) - rspec-core (~> 3.13.0) - rspec-expectations (~> 3.13.0) - rspec-mocks (~> 3.13.0) - rspec-core (3.13.2) - rspec-support (~> 3.13.0) - rspec-expectations (3.13.3) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-mocks (3.13.2) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-support (3.13.2) - rubocop (1.88.0) - json (~> 2.3) - language_server-protocol (~> 3.17.0.2) - lint_roller (~> 1.1.0) - parallel (>= 1.10) - parser (>= 3.3.0.2) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.49.0, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.49.1) - parser (>= 3.3.7.2) - prism (~> 1.7) - rubocop-performance (1.26.1) - lint_roller (~> 1.1) - rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.47.1, < 2.0) - rubocop-rspec (3.10.2) - lint_roller (~> 1.1) - regexp_parser (>= 2.0) - rubocop (~> 1.86, >= 1.86.2) - ruby-progressbar (1.13.0) - unicode-display_width (3.2.0) - unicode-emoji (~> 4.1) - unicode-emoji (4.2.0) - -PLATFORMS - ruby - x86_64-linux - -DEPENDENCIES - cucumber-query! - rspec (~> 3.13) - rubocop (~> 1.88.0) - rubocop-performance (~> 1.26.1) - rubocop-rspec (~> 3.10.2) - -BUNDLED WITH - 4.0.14 From 0575da56694edcc82672b24c451efe4d5a9f8086 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 30 Jun 2026 21:06:33 +0200 Subject: [PATCH 42/55] Nitpicks --- .github/workflows/{test-ruby.yml => test-ruby.yaml} | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) rename .github/workflows/{test-ruby.yml => test-ruby.yaml} (84%) diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yaml similarity index 84% rename from .github/workflows/test-ruby.yml rename to .github/workflows/test-ruby.yaml index 254bb811..dadc993d 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yaml @@ -14,17 +14,17 @@ on: pull_request: branches: - main + - renovate/** paths: - ruby/** - testdata/** - .github/** - workflow_call: jobs: test-ruby: runs-on: ${{ matrix.os }} strategy: - fail-fast: false + fail-fast: true matrix: os: - ubuntu-latest @@ -44,7 +44,5 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler-cache: true working-directory: ruby - - run: bundle exec rubocop --format simple - working-directory: ruby - - run: bundle exec rspec --format progress + - run: bundle exec rspec working-directory: ruby From b6d01eb7e0255f05fbd2463851fddbd1061456bf Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Wed, 1 Jul 2026 07:46:58 +0100 Subject: [PATCH 43/55] Fix version --- ruby/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/VERSION b/ruby/VERSION index 2bbd2b4b..946789e6 100644 --- a/ruby/VERSION +++ b/ruby/VERSION @@ -1 +1 @@ -15.0.1 +16.0.0 From 74ad7220d88350062f2d5a45a1aa5088aac4285c Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Wed, 1 Jul 2026 07:57:09 +0100 Subject: [PATCH 44/55] Fix for old ruby --- ruby/spec/cucumber/query_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/spec/cucumber/query_spec.rb b/ruby/spec/cucumber/query_spec.rb index 841701f0..fcae58bd 100644 --- a/ruby/spec/cucumber/query_spec.rb +++ b/ruby/spec/cucumber/query_spec.rb @@ -52,7 +52,7 @@ def parse_ndjson_file(path) file.match?("/#{enabled_file}.ndjson") end end - filenames_in_testdata = files_in_testdata.map { it.split('/').last.split('.').first } + filenames_in_testdata = files_in_testdata.map { |file| file.split('/').last.split('.').first } subject(:query) { described_class.new(repository) } From 4f274cfb9a9790a3731383360f8aae83d62b7c14 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Wed, 1 Jul 2026 11:47:51 +0100 Subject: [PATCH 45/55] Fix requirements and rdoc --- ruby/lib/cucumber/query.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ruby/lib/cucumber/query.rb b/ruby/lib/cucumber/query.rb index a8fa465c..b7edc8da 100644 --- a/ruby/lib/cucumber/query.rb +++ b/ruby/lib/cucumber/query.rb @@ -1,15 +1,16 @@ # frozen_string_literal: true -require 'cucumber/repository' require 'cucumber/messages' +require_relative 'repository' + # Given one Cucumber Message, find another. # # Queries can be made while the test run is incomplete - and this will naturally return incomplete results -# see Cucumber Messages - Message Overview +# see {Cucumber Messages - Message Overview}[https://github.com/cucumber/messages?tab=readme-ov-file#message-overview] # module Cucumber - # Provides lookup methods for related Cucumber messages stored in a Repository. + # Provides lookup methods for related Cucumber messages stored in the `Repository` class. class Query attr_reader :repository private :repository @@ -105,11 +106,9 @@ def find_most_severe_test_step_result_by(message) find_test_steps_finished_by(message) .map(&:test_step_result) .max_by { |test_step_result| test_step_result_rankings[test_step_result.status] } - # Java code: "PREVIOUS".max(comparing(TestStepResult::getStatus, new TestStepResultStatusComparator())); else test_case_started_message = find_test_case_started_by(message) test_case_started_message && find_most_severe_test_step_result_by(test_case_started_message) - # Java code: return findTestCaseStartedBy(testCaseFinished).flatMap(this::findMostSevereTestStepResultBy); end end From 831859f200ba35875dc810dd6d81d4ac24f29439 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Wed, 1 Jul 2026 11:53:26 +0100 Subject: [PATCH 46/55] Fix up test code --- ruby/spec/cucumber/query_spec.rb | 31 +++++++++++++------------------ ruby/spec/spec_helper.rb | 6 +++--- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/ruby/spec/cucumber/query_spec.rb b/ruby/spec/cucumber/query_spec.rb index fcae58bd..2dbdce97 100644 --- a/ruby/spec/cucumber/query_spec.rb +++ b/ruby/spec/cucumber/query_spec.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require 'json' -require 'cucumber/query' - RSpec.describe Cucumber::Query do def testdata_path(name) File.expand_path("../../../testdata/src/#{name}", __dir__) @@ -38,27 +35,25 @@ def parse_ndjson_file(path) results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_pickle_by(message).name } results end - }.freeze - - # This list needs to be tightly controlled. - # New items should be added when new queries have been adapted to work with them - # AND - # When the requisite sample data result files have been created - enabled_files = %w[attachments empty examples-tables global-hooks hooks minimal rules unknown-parameter-type].freeze + } - files_in_testdata = Dir.glob(File.expand_path("../../../testdata/src/*.ndjson", __dir__)) - files_in_testdata = files_in_testdata.select do |file| - enabled_files.any? do |enabled_file| - file.match?("/#{enabled_file}.ndjson") - end - end - filenames_in_testdata = files_in_testdata.map { |file| file.split('/').last.split('.').first } + sources = %w[ + attachments + empty + examples-tables + global-hooks + global-hooks-attachments + hooks + minimal + rules + unknown-parameter-type + ] subject(:query) { described_class.new(repository) } let(:repository) { Cucumber::Repository.new } - filenames_in_testdata.product(queries.to_a).each do |source_name, (query_name, query_proc)| + sources.product(queries.to_a).each do |source_name, (query_name, query_proc)| describe "Query: '#{query_name}'. testdata source: '#{source_name}'" do let(:source) { testdata_path("#{source_name}.ndjson") } let(:expected_results_file) { testdata_path("#{source_name}.#{query_name}.results.json") } diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb index 81bd3988..77322ec5 100644 --- a/ruby/spec/spec_helper.rb +++ b/ruby/spec/spec_helper.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -RSpec.configure do - :no_op -end +require 'cucumber/query' + +require 'json' From 1fcf78120b86fdbe7365b1aaf3a98fd0f189ede4 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Wed, 1 Jul 2026 12:02:46 +0100 Subject: [PATCH 47/55] Refactor out symbol refs to use verbose classes --- ruby/lib/cucumber/query.rb | 103 ++++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 31 deletions(-) diff --git a/ruby/lib/cucumber/query.rb b/ruby/lib/cucumber/query.rb index b7edc8da..76c60935 100644 --- a/ruby/lib/cucumber/query.rb +++ b/ruby/lib/cucumber/query.rb @@ -83,7 +83,11 @@ def find_all_test_steps # This method will be called with 1 of these 3 messages # [TestStep || TestRunHookStarted || TestRunHookFinished] def find_hook_by(message) - ensure_only_message_types!(message, %i[test_step test_run_hook_started test_run_hook_finished], '#find_hook_by') + ensure_only_message_types!( + message, + [Cucumber::Messages::TestStep, Cucumber::Messages::TestRunHookStarted, Cucumber::Messages::TestRunHookFinished], + '#find_hook_by' + ) if message.is_a?(Cucumber::Messages::TestRunHookFinished) test_run_hook_started_message = find_test_run_hook_started_by(message) @@ -100,7 +104,11 @@ def find_meta # This method will be called with 1 of these 2 messages # [TestCaseStarted || TestCaseFinished] def find_most_severe_test_step_result_by(message) - ensure_only_message_types!(message, %i[test_case_started test_case_finished], '#find_most_severe_test_step_result_by') + ensure_only_message_types!( + message, + [Cucumber::Messages::TestCaseStarted, Cucumber::Messages::TestCaseFinished], + '#find_most_severe_test_step_result_by' + ) if message.is_a?(Cucumber::Messages::TestCaseStarted) find_test_steps_finished_by(message) @@ -115,7 +123,11 @@ def find_most_severe_test_step_result_by(message) # This method will be called with 1 of these 5 messages # [TestCase || TestCaseStarted || TestCaseFinished || TestStepStarted || TestStepFinished] def find_pickle_by(message) - ensure_only_message_types!(message, %i[test_case test_case_started test_case_finished test_step_started test_step_finished], '#find_pickle_by') + ensure_only_message_types!( + message, + [Cucumber::Messages::TestCase, Cucumber::Messages::TestCaseStarted, Cucumber::Messages::TestCaseFinished, Cucumber::Messages::TestStepStarted, Cucumber::Messages::TestStepFinished], + '#find_pickle_by' + ) test_case_message = message.is_a?(Cucumber::Messages::TestCase) ? message : find_test_case_by(message) repository.pickle_by_id[test_case_message.pickle_id] @@ -124,7 +136,11 @@ def find_pickle_by(message) # This method will be called with only 1 message # [TestStep] def find_pickle_step_by(test_step) - ensure_only_message_types!(test_step, %i[test_step], '#find_pickle_step_by') + ensure_only_message_types!( + test_step, + [Cucumber::Messages::TestStep], + '#find_pickle_step_by' + ) repository.pickle_step_by_id[test_step.pickle_step_id] end @@ -132,7 +148,11 @@ def find_pickle_step_by(test_step) # This method will be called with only 1 message # [PickleStep] def find_step_by(pickle_step) - ensure_only_message_types!(pickle_step, %i[pickle_step], '#find_step_by') + ensure_only_message_types!( + pickle_step, + [Cucumber::Messages::PickleStep], + '#find_step_by' + ) repository.step_by_id[pickle_step.ast_node_ids.first] end @@ -140,7 +160,11 @@ def find_step_by(pickle_step) # This method will be called with only 1 message # [TestStep] def find_step_definitions_by(test_step) - ensure_only_message_types!(test_step, %i[test_step], '#find_step_definitions_by') + ensure_only_message_types!( + test_step, + [Cucumber::Messages::TestStep], + '#find_step_definitions_by' + ) ids = test_step.step_definition_ids.nil? ? [] : test_step.step_definition_ids ids.filter_map { |id| repository.step_definition_by_id[id] } @@ -149,7 +173,11 @@ def find_step_definitions_by(test_step) # This method will be called with 1 of these 4 messages # [TestCaseStarted || TestCaseFinished || TestStepStarted || TestStepFinished] def find_test_case_by(message) - ensure_only_message_types!(message, %i[test_case_started test_case_finished test_step_started test_step_finished], '#find_test_case_by') + ensure_only_message_types!( + message, + [Cucumber::Messages::TestCaseStarted, Cucumber::Messages::TestCaseFinished, Cucumber::Messages::TestStepStarted, Cucumber::Messages::TestStepFinished], + '#find_test_case_by' + ) test_case_started_message = message.is_a?(Cucumber::Messages::TestCaseStarted) ? message : find_test_case_started_by(message) repository.test_case_by_id[test_case_started_message.test_case_id] @@ -158,7 +186,11 @@ def find_test_case_by(message) # This method will be called with 1 of these 3 messages # [TestCaseFinished || TestStepStarted || TestStepFinished] def find_test_case_started_by(message) - ensure_only_message_types!(message, %i[test_case_finished test_step_started test_step_finished], '#find_test_case_started_by') + ensure_only_message_types!( + message, + [Cucumber::Messages::TestCaseFinished, Cucumber::Messages::TestStepStarted, Cucumber::Messages::TestStepFinished], + '#find_test_case_started_by' + ) repository.test_case_started_by_id[message.test_case_started_id] end @@ -166,7 +198,11 @@ def find_test_case_started_by(message) # This method will be called with only 1 message # [TestCaseStarted] def find_test_case_finished_by(test_case_started) - ensure_only_message_types!(test_case_started, %i[test_case_started], '#find_test_case_finished_by') + ensure_only_message_types!( + test_case_started, + [Cucumber::Messages::TestCaseStarted], + '#find_test_case_finished_by' + ) repository.test_case_finished_by_test_case_started_id[test_case_started.id] end @@ -182,7 +218,11 @@ def find_test_run_duration # This method will be called with only 1 message # [TestRunHookFinished] def find_test_run_hook_started_by(test_run_hook_finished) - ensure_only_message_types!(test_run_hook_finished, %i[test_run_hook_finished], '#find_test_run_hook_started_by') + ensure_only_message_types!( + test_run_hook_finished, + [Cucumber::Messages::TestRunHookFinished], + '#find_test_run_hook_started_by' + ) repository.test_run_hook_started_by_id[test_run_hook_finished.test_run_hook_started_id] end @@ -190,7 +230,11 @@ def find_test_run_hook_started_by(test_run_hook_finished) # This method will be called with only 1 message # [TestRunHookStarted] def find_test_run_hook_finished_by(test_run_hook_started) - ensure_only_message_types!(test_run_hook_started, %i[test_run_hook_started], '#find_test_run_hook_finished_by') + ensure_only_message_types!( + test_run_hook_started, + [Cucumber::Messages::TestRunHookStarted], + '#find_test_run_hook_finished_by' + ) repository.test_run_hook_finished_by_test_run_hook_started_id[test_run_hook_started.id] end @@ -206,7 +250,12 @@ def find_test_run_finished # This method will be called with 1 of these 2 messages # [TestStepStarted || TestStepFinished] def find_test_step_by(message) - ensure_only_message_types!(message, %i[test_step_started test_step_finished], '#find_test_step_by') + # ensure_only_message_types!(message, %i[test_step_started test_step_finished], '#find_test_step_by') + ensure_only_message_types!( + message, + [Cucumber::Messages::TestStepStarted, Cucumber::Messages::TestStepFinished], + '#find_test_step_by' + ) repository.test_step_by_id[message.test_step_id] end @@ -214,7 +263,11 @@ def find_test_step_by(message) # This method will be called with 1 of these 2 messages # [TestCaseStarted || TestCaseFinished] def find_test_steps_started_by(message) - ensure_only_message_types!(message, %i[test_case_started test_case_finished], '#find_test_steps_started_by') + ensure_only_message_types!( + message, + [Cucumber::Messages::TestCaseStarted, Cucumber::Messages::TestCaseFinished], + '#find_test_steps_started_by' + ) key = message.is_a?(Cucumber::Messages::TestCaseStarted) ? message.id : message.test_case_started_id repository.test_steps_started_by_test_case_started_id.fetch(key, []) @@ -223,7 +276,11 @@ def find_test_steps_started_by(message) # This method will be called with 1 of these 2 messages # [TestCaseStarted || TestCaseFinished] def find_test_steps_finished_by(message) - ensure_only_message_types!(message, %i[test_case_started test_case_finished], '#find_test_steps_finished_by') + ensure_only_message_types!( + message, + [Cucumber::Messages::TestCaseStarted, Cucumber::Messages::TestCaseFinished], + '#find_test_steps_finished_by' + ) if message.is_a?(Cucumber::Messages::TestCaseStarted) repository.test_steps_finished_by_test_case_started_id.fetch(message.id, []) @@ -237,23 +294,7 @@ def find_test_steps_finished_by(message) def ensure_only_message_types!(supplied_message, permissible_message_types, method_name) raise ArgumentError, "Supplied argument is not a Cucumber Message. Argument: #{supplied_message.class}" unless supplied_message.is_a?(Cucumber::Messages::Message) - - permitted_klasses = permissible_message_types.map { |message| message_types[message] } - raise ArgumentError, "Supplied message type '#{supplied_message.class}' is not permitted to be used when calling #{method_name}" unless permitted_klasses.include?(supplied_message.class) - end - - def message_types - { - pickle_step: Cucumber::Messages::PickleStep, - test_case: Cucumber::Messages::TestCase, - test_case_started: Cucumber::Messages::TestCaseStarted, - test_case_finished: Cucumber::Messages::TestCaseFinished, - test_run_hook_started: Cucumber::Messages::TestRunHookStarted, - test_run_hook_finished: Cucumber::Messages::TestRunHookFinished, - test_step: Cucumber::Messages::TestStep, - test_step_started: Cucumber::Messages::TestStepStarted, - test_step_finished: Cucumber::Messages::TestStepFinished - } + raise ArgumentError, "Supplied message type '#{supplied_message.class}' is not permitted to be used when calling #{method_name}" unless permissible_message_types.include?(supplied_message.class) end end end From 53e4325966de8dd6d98c489aac58d96620c27e19 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Wed, 1 Jul 2026 13:55:47 +0100 Subject: [PATCH 48/55] Fix required version of messages and improve handlers by utilising new message type on envelopes --- ruby/cucumber-query.gemspec | 2 +- ruby/lib/cucumber/repository.rb | 28 +++------------------------- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/ruby/cucumber-query.gemspec b/ruby/cucumber-query.gemspec index e056bc74..b9870220 100644 --- a/ruby/cucumber-query.gemspec +++ b/ruby/cucumber-query.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| 'source_code_uri' => 'https://github.com/cucumber/query/blob/main/ruby' } - s.add_dependency 'cucumber-messages', '> 31', '< 34' + s.add_dependency 'cucumber-messages', '>= 34', '< 35' s.add_development_dependency 'rspec', '~> 3.13' s.add_development_dependency 'rubocop', '~> 1.88.0' diff --git a/ruby/lib/cucumber/repository.rb b/ruby/lib/cucumber/repository.rb index 862b8df2..c80bc19b 100644 --- a/ruby/lib/cucumber/repository.rb +++ b/ruby/lib/cucumber/repository.rb @@ -30,32 +30,10 @@ def initialize @test_steps_finished_by_test_case_started_id = Hash.new { |hash, key| hash[key] = [] } end - UPDATE_HANDLERS = { - meta: :update_meta, - test_run_started: :update_test_run_started, - test_run_finished: :update_test_run_finished, - attachment: :update_attachment, - gherkin_document: :update_gherkin_document, - hook: :update_hook, - pickle: :update_pickle, - step_definition: :update_step_definition, - test_run_hook_started: :update_test_run_hook_started, - test_run_hook_finished: :update_test_run_hook_finished, - test_case_started: :update_test_case_started, - test_case_finished: :update_test_case_finished, - test_step_started: :update_test_step_started, - test_step_finished: :update_test_step_finished, - test_case: :update_test_case - }.freeze - private_constant :UPDATE_HANDLERS - def update(envelope) - UPDATE_HANDLERS.each do |message_name, handler| - message = envelope.public_send(message_name) - return __send__(handler, message) if message - end - - nil + message_category = envelope.type[:contained_message] + message_value = envelope.public_send(message_category) + send("update_#{message_category}", message_value) end private From 8f9af813b2b8d97a46fe86c77433ccf58b947fcb Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Wed, 1 Jul 2026 15:23:23 +0100 Subject: [PATCH 49/55] Tidy up line length and remove comments --- ruby/lib/cucumber/query.rb | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/ruby/lib/cucumber/query.rb b/ruby/lib/cucumber/query.rb index 76c60935..40d5da7b 100644 --- a/ruby/lib/cucumber/query.rb +++ b/ruby/lib/cucumber/query.rb @@ -22,9 +22,6 @@ def initialize(repository) @repository = repository end - # This initial Ruby implementation covers the query methods needed by current Ruby consumers. - # Additional methods can be added here as those consumers migrate from cucumber-ruby's legacy query helpers. - def count_test_cases_started find_all_test_case_started.length end @@ -125,7 +122,13 @@ def find_most_severe_test_step_result_by(message) def find_pickle_by(message) ensure_only_message_types!( message, - [Cucumber::Messages::TestCase, Cucumber::Messages::TestCaseStarted, Cucumber::Messages::TestCaseFinished, Cucumber::Messages::TestStepStarted, Cucumber::Messages::TestStepFinished], + [ + Cucumber::Messages::TestCase, + Cucumber::Messages::TestCaseStarted, + Cucumber::Messages::TestCaseFinished, + Cucumber::Messages::TestStepStarted, + Cucumber::Messages::TestStepFinished + ], '#find_pickle_by' ) @@ -175,7 +178,12 @@ def find_step_definitions_by(test_step) def find_test_case_by(message) ensure_only_message_types!( message, - [Cucumber::Messages::TestCaseStarted, Cucumber::Messages::TestCaseFinished, Cucumber::Messages::TestStepStarted, Cucumber::Messages::TestStepFinished], + [ + Cucumber::Messages::TestCaseStarted, + Cucumber::Messages::TestCaseFinished, + Cucumber::Messages::TestStepStarted, + Cucumber::Messages::TestStepFinished + ], '#find_test_case_by' ) @@ -250,7 +258,6 @@ def find_test_run_finished # This method will be called with 1 of these 2 messages # [TestStepStarted || TestStepFinished] def find_test_step_by(message) - # ensure_only_message_types!(message, %i[test_step_started test_step_finished], '#find_test_step_by') ensure_only_message_types!( message, [Cucumber::Messages::TestStepStarted, Cucumber::Messages::TestStepFinished], From 6f79e9e3bddca640337e79c1299119db1667d81f Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Wed, 1 Jul 2026 15:34:30 +0100 Subject: [PATCH 50/55] Add in some more simple lambdas to validate more of the queries --- ruby/spec/cucumber/query_spec.rb | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ruby/spec/cucumber/query_spec.rb b/ruby/spec/cucumber/query_spec.rb index 2dbdce97..d21679e1 100644 --- a/ruby/spec/cucumber/query_spec.rb +++ b/ruby/spec/cucumber/query_spec.rb @@ -12,7 +12,18 @@ def parse_ndjson_file(path) queries = { 'findAllPickles' => ->(query) { query.find_all_pickles.length }, 'findAllTestCases' => ->(query) { query.find_all_test_cases.length }, + 'findAllTestRunHookStarted' => ->(query) { query.find_all_test_run_hook_started }, + 'findAllTestRunHookFinished' => ->(query) { query.find_all_test_run_hook_finished }, 'findAllTestSteps' => ->(query) { query.find_all_test_steps.length }, + 'findMeta' => ->(query) { query.find_meta }, + 'findPickleBy' => lambda do |query| + results = {} + results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_pickle_by(message).name } + results['testCaseFinished'] = query.find_all_test_case_finished.map { |message| query.find_pickle_by(message).name } + results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_pickle_by(message).name } + results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_pickle_by(message).name } + results + end, 'findTestCaseBy' => lambda do |query| results = {} results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_test_case_by(message).id } @@ -26,14 +37,6 @@ def parse_ndjson_file(path) results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_test_step_by(message).id } results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_test_step_by(message).id } results - end, - 'findPickleBy' => lambda do |query| - results = {} - results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_pickle_by(message).name } - results['testCaseFinished'] = query.find_all_test_case_finished.map { |message| query.find_pickle_by(message).name } - results['testStepStarted'] = query.find_all_test_step_started.map { |message| query.find_pickle_by(message).name } - results['testStepFinished'] = query.find_all_test_step_finished.map { |message| query.find_pickle_by(message).name } - results end } From 20237970c8bbf42b153f0145c295c110593daba9 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Thu, 2 Jul 2026 08:46:12 +0100 Subject: [PATCH 51/55] Fix findAll lambdas to return int --- ruby/spec/cucumber/query_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ruby/spec/cucumber/query_spec.rb b/ruby/spec/cucumber/query_spec.rb index d21679e1..5a9e111d 100644 --- a/ruby/spec/cucumber/query_spec.rb +++ b/ruby/spec/cucumber/query_spec.rb @@ -12,8 +12,8 @@ def parse_ndjson_file(path) queries = { 'findAllPickles' => ->(query) { query.find_all_pickles.length }, 'findAllTestCases' => ->(query) { query.find_all_test_cases.length }, - 'findAllTestRunHookStarted' => ->(query) { query.find_all_test_run_hook_started }, - 'findAllTestRunHookFinished' => ->(query) { query.find_all_test_run_hook_finished }, + 'findAllTestRunHookStarted' => ->(query) { query.find_all_test_run_hook_started.length }, + 'findAllTestRunHookFinished' => ->(query) { query.find_all_test_run_hook_finished.length }, 'findAllTestSteps' => ->(query) { query.find_all_test_steps.length }, 'findMeta' => ->(query) { query.find_meta }, 'findPickleBy' => lambda do |query| From 7fdde960d4cdac36e7954c768e97e0fd994f0d9f Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Thu, 2 Jul 2026 08:48:54 +0100 Subject: [PATCH 52/55] Fix findMeta to return name of implementation --- ruby/spec/cucumber/query_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/spec/cucumber/query_spec.rb b/ruby/spec/cucumber/query_spec.rb index 5a9e111d..27c66199 100644 --- a/ruby/spec/cucumber/query_spec.rb +++ b/ruby/spec/cucumber/query_spec.rb @@ -15,7 +15,7 @@ def parse_ndjson_file(path) 'findAllTestRunHookStarted' => ->(query) { query.find_all_test_run_hook_started.length }, 'findAllTestRunHookFinished' => ->(query) { query.find_all_test_run_hook_finished.length }, 'findAllTestSteps' => ->(query) { query.find_all_test_steps.length }, - 'findMeta' => ->(query) { query.find_meta }, + 'findMeta' => ->(query) { query.find_meta.implementation.name }, 'findPickleBy' => lambda do |query| results = {} results['testCaseStarted'] = query.find_all_test_case_started.map { |message| query.find_pickle_by(message).name } From 7077a0d93a5923fa63861dfbe447ebf5dc9b9e43 Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Thu, 2 Jul 2026 08:50:46 +0100 Subject: [PATCH 53/55] Add in 2 new queries which also return simple counters; --- ruby/spec/cucumber/query_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ruby/spec/cucumber/query_spec.rb b/ruby/spec/cucumber/query_spec.rb index 27c66199..85eea83f 100644 --- a/ruby/spec/cucumber/query_spec.rb +++ b/ruby/spec/cucumber/query_spec.rb @@ -10,7 +10,9 @@ def parse_ndjson_file(path) end queries = { + 'countTestCasesStarted' => ->(query) { query.count_test_cases_started }, 'findAllPickles' => ->(query) { query.find_all_pickles.length }, + 'findAllStepDefinitions' => ->(query) { query.find_all_step_definitions.length }, 'findAllTestCases' => ->(query) { query.find_all_test_cases.length }, 'findAllTestRunHookStarted' => ->(query) { query.find_all_test_run_hook_started.length }, 'findAllTestRunHookFinished' => ->(query) { query.find_all_test_run_hook_finished.length }, From e04185b156d88ff9232babed26561c4e913c044b Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Thu, 9 Jul 2026 13:27:28 +0100 Subject: [PATCH 54/55] Bump version requirements for messages --- ruby/cucumber-query.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/cucumber-query.gemspec b/ruby/cucumber-query.gemspec index b9870220..c2244113 100644 --- a/ruby/cucumber-query.gemspec +++ b/ruby/cucumber-query.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| 'source_code_uri' => 'https://github.com/cucumber/query/blob/main/ruby' } - s.add_dependency 'cucumber-messages', '>= 34', '< 35' + s.add_dependency 'cucumber-messages', '> 34', '< 36' s.add_development_dependency 'rspec', '~> 3.13' s.add_development_dependency 'rubocop', '~> 1.88.0' From be8521b91e9bfa5a353a9dc4351ffa96c3bedb2c Mon Sep 17 00:00:00 2001 From: Luke Hill Date: Mon, 13 Jul 2026 14:37:59 +0100 Subject: [PATCH 55/55] Add in missing message handlers for 2 message updates plus catchall for new undefined ones --- ruby/lib/cucumber/repository.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ruby/lib/cucumber/repository.rb b/ruby/lib/cucumber/repository.rb index c80bc19b..793296b2 100644 --- a/ruby/lib/cucumber/repository.rb +++ b/ruby/lib/cucumber/repository.rb @@ -38,6 +38,30 @@ def update(envelope) private + # Missing handlers + + def method_missing(method_name, *args, &) + if method_name.to_s.start_with?('update_') + Kernel.warn("Attempting to update the repository with #{method_name}. Please raise this as a missing message handler") + Kernel.warn('Create an issue here: https://github.com/cucumber/query/issues') + nil + else + super + end + end + + def respond_to_missing?(method_name, include_private = false) + method_name.to_s.start_with?('update_') || super + end + + def update_source(_source) + :no_op # Not Implemented Yet. But method will be inherently called from `#update` + end + + def update_undefined_parameter_type(_undefined_parameter_type) + :no_op # Not Implemented Yet. But method will be inherently called from `#update` + end + def update_meta(meta) self.meta = meta end