diff --git a/.github/workflows/rubocop.yaml b/.github/workflows/rubocop.yaml
index f7656d2ce..9b65a8a81 100644
--- a/.github/workflows/rubocop.yaml
+++ b/.github/workflows/rubocop.yaml
@@ -19,6 +19,6 @@ jobs:
persist-credentials: false
- uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
with:
- ruby-version: '4.0.5'
+ ruby-version: '3.3'
bundler-cache: true
- run: bundle exec rubocop
diff --git a/.github/workflows/test-ruby.yaml b/.github/workflows/test-ruby.yaml
index 9310a8522..a4d46805b 100644
--- a/.github/workflows/test-ruby.yaml
+++ b/.github/workflows/test-ruby.yaml
@@ -19,7 +19,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
- ruby: ['3.2', '3.3', '3.4', '4.0']
+ ruby: ['3.3', '3.4', '4.0']
include:
- os: ubuntu-latest
ruby: jruby-10.0
diff --git a/.github/workflows/zizmor-analysis.yaml b/.github/workflows/zizmor-analysis.yaml
index 9d4418da7..feb10b7d2 100644
--- a/.github/workflows/zizmor-analysis.yaml
+++ b/.github/workflows/zizmor-analysis.yaml
@@ -24,6 +24,5 @@ jobs:
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
-
- name: Run Zizmor
uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
diff --git a/.rubocop.yml b/.rubocop.yml
index a0d68f5f0..21754e5d0 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -12,7 +12,7 @@ require:
AllCops:
NewCops: disable
# Keep this inline with the lowest ruby version in the gemspec
- TargetRubyVersion: 3.2
+ TargetRubyVersion: 3.3
# Display cop name / style guide references
DisplayCopNames: true
DisplayStyleGuide: true
diff --git a/Gemfile b/Gemfile
index ae3253d75..5020a7981 100644
--- a/Gemfile
+++ b/Gemfile
@@ -10,4 +10,5 @@ gemspec
# gem 'cucumber-gherkin', path: '../gherkin/ruby'
# gem 'cucumber-html-formatter', path: '../html-formatter/ruby'
# gem 'cucumber-messages', path: '../messages/ruby'
+# gem 'cucumber-query', path: '../query/ruby'
# gem 'cucumber-tag-expressions', path: '../tag-expressions/ruby'
diff --git a/cucumber.gemspec b/cucumber.gemspec
index 95132ea0e..9f1736b29 100644
--- a/cucumber.gemspec
+++ b/cucumber.gemspec
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
'funding_uri' => 'https://opencollective.com/cucumber'
}
- s.required_ruby_version = '>= 3.2'
+ s.required_ruby_version = '>= 3.3'
s.required_rubygems_version = '>= 3.2.8'
s.add_dependency 'base64', '~> 0.2'
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
s.add_dependency 'cucumber-core', '>= 16.2.0', '< 17'
s.add_dependency 'cucumber-cucumber-expressions', '> 17', '< 20'
s.add_dependency 'cucumber-html-formatter', '> 21', '< 24'
+ s.add_dependency 'cucumber-query', '~> 16.0'
s.add_dependency 'diff-lcs', '~> 1.5'
s.add_dependency 'logger', '~> 1.6'
s.add_dependency 'mini_mime', '~> 1.1'
diff --git a/lib/cucumber/query.rb b/lib/cucumber/query.rb
deleted file mode 100644
index 735735197..000000000
--- a/lib/cucumber/query.rb
+++ /dev/null
@@ -1,273 +0,0 @@
-# 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
- 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
-
- # 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
-
- # 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 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
-
- # 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
-
- # 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 find_test_run_finished
- repository.test_run_finished
- end
-
- # 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
-
- # 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 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/lib/cucumber/repository.rb b/lib/cucumber/repository.rb
deleted file mode 100644
index 9c61dfbb7..000000000
--- a/lib/cucumber/repository.rb
+++ /dev/null
@@ -1,136 +0,0 @@
-# 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