-
-
Notifications
You must be signed in to change notification settings - Fork 1
Add Ruby cucumber-query implementation #167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2fee02e
12ae4b0
643a0d2
7c4e54d
610c8cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| with: | ||
| persist-credentials: false | ||
| - uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0 | ||
| with: | ||
| ruby-version: 4.0.4 | ||
| working-directory: ruby | ||
| - uses: rubygems/configure-rubygems-credentials@762a4b77c3300434bb57c7ce80b20e36231927aa # v2.0.0 | ||
| - uses: cucumber/action-publish-rubygem@4e79bb9aed597c835e8438f57c04d0996ab80d72 # v2.0.0 | ||
| with: | ||
| working-directory: ruby |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| name: test-ruby | ||
|
|
||
| permissions: {} | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| - renovate/** | ||
| paths: | ||
| - ruby/** | ||
| - testdata/** | ||
| - .github/** | ||
| pull_request: | ||
| branches: | ||
| - main | ||
| paths: | ||
| - ruby/** | ||
| - testdata/** | ||
| - .github/** | ||
|
|
||
| jobs: | ||
| test-ruby: | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| os: | ||
| - ubuntu-latest | ||
| ruby: ['3.2', '3.3', '3.4', '4.0'] | ||
| include: | ||
| - os: macos-latest | ||
| ruby: '3.4' | ||
| - os: windows-latest | ||
| ruby: '3.4' | ||
|
|
||
| steps: | ||
| - name: set git core.autocrlf to 'input' | ||
| run: git config --global core.autocrlf input | ||
|
|
||
| - 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 rake | ||
| working-directory: ruby |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,4 @@ | ||
| .idea/ | ||
| *.iml | ||
|
|
||
| ruby/Gemfile.lock |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| --require spec_helper | ||
| --format documentation |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| plugins: | ||
| - rubocop-performance | ||
| - rubocop-rake | ||
| - rubocop-rspec | ||
|
|
||
| AllCops: | ||
| NewCops: enable | ||
| TargetRubyVersion: 3.2 | ||
|
|
||
| Layout/EndOfLine: | ||
| EnforcedStyle: lf | ||
|
|
||
| Style/Documentation: | ||
| Enabled: false | ||
|
|
||
| Gemspec/DevelopmentDependencies: | ||
| Enabled: false | ||
|
|
||
| RSpec/MultipleExpectations: | ||
| Exclude: | ||
| - spec/unit/**/* | ||
|
|
||
| RSpec/SpecFilePathFormat: | ||
| Exclude: | ||
| - spec/unit/**/* |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| source 'https://rubygems.org' | ||
|
|
||
| gem 'cucumber-messages', path: ENV.fetch('CUCUMBER_MESSAGES_RUBY_PATH') if ENV['CUCUMBER_MESSAGES_RUBY_PATH'] | ||
|
|
||
| gemspec |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| # Cucumber Query for Ruby | ||
|
|
||
| `cucumber-query` builds an in-memory index of Cucumber Messages so formatters | ||
| and other message consumers can answer questions such as "which pickle belongs | ||
| to this test case?", "which step definition matched this test step?", or "what | ||
| was the most severe result for this scenario?". | ||
|
|
||
| The Ruby implementation follows the public API shape used by the other | ||
| `cucumber-query` packages while using Ruby snake_case method names. | ||
|
|
||
| ## Installation | ||
|
|
||
| ```ruby | ||
| gem 'cucumber-query', '~> 15.0' | ||
| ``` | ||
|
|
||
| Ruby 3.2 or newer is required. The gem depends on `cucumber-messages` and | ||
| accepts `Cucumber::Messages::Envelope` objects. | ||
|
|
||
| ## Basic usage | ||
|
|
||
| Create one query per message stream, then pass every envelope to `#update` in | ||
| the order it was produced: | ||
|
|
||
| ```ruby | ||
| require 'cucumber/query' | ||
| require 'cucumber/messages/helpers/ndjson_to_message_enumerator' | ||
|
|
||
| query = Cucumber::Query::Query.new | ||
|
|
||
| File.open('cucumber-messages.ndjson', 'r') do |io| | ||
| Cucumber::Messages::Helpers::NdjsonToMessageEnumerator.new(io).each do |envelope| | ||
| query.update(envelope) | ||
| end | ||
| end | ||
|
|
||
| puts query.count_test_cases_started | ||
| puts query.find_all_pickles.map(&:name) | ||
| ``` | ||
|
|
||
| `#update` returns `nil`. After each update, the query indexes the parts of the | ||
| envelope it understands and keeps the original envelope in `#envelopes` for | ||
| inspection/debugging. | ||
|
|
||
| ## Message flow and architecture | ||
|
|
||
| The query is deliberately stateful: | ||
|
|
||
| 1. `meta`, `gherkin_document`, `pickle`, `hook`, `step_definition`, `test_case` | ||
| and other definition messages populate lookup tables. | ||
| 2. Runtime messages such as `test_case_started`, `test_step_finished`, | ||
| `attachment`, `test_case_finished`, and global hook messages are linked back | ||
| to those definitions by id. | ||
| 3. Query methods read those lookup tables and return message objects from | ||
| `cucumber-messages`, not wrapper objects. | ||
|
|
||
| Because Cucumber Messages refer to related data by id, feed the complete stream | ||
| to the query when possible. If a formatter asks for a pickle before the | ||
| corresponding `pickle` message has been seen, the lookup will return `nil`. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On this, it would be good to update the table in https://github.com/cucumber/query/blob/main/CONTRIBUTING.md#types, this gives good hints for when somebody wants to add a new method across languages.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, like, an additional column for Ruby with
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep |
||
|
|
||
| ## Common query methods | ||
|
|
||
| Counts and result aggregation: | ||
|
|
||
| ```ruby | ||
| query.count_test_cases_started | ||
| query.count_most_severe_test_step_result_status | ||
| ``` | ||
|
|
||
| Top-level collections: | ||
|
|
||
| ```ruby | ||
| query.find_all_pickles | ||
| query.find_all_pickle_steps | ||
| query.find_all_step_definitions | ||
| query.find_all_test_steps | ||
| query.find_all_test_case_started | ||
| query.find_all_test_case_finished | ||
| query.find_all_test_run_hook_started | ||
| query.find_all_test_run_hook_finished | ||
| query.find_all_undefined_parameter_types | ||
| ``` | ||
|
|
||
| Relationship lookups: | ||
|
|
||
| ```ruby | ||
| query.find_pickle_by(test_case_started_or_finished) | ||
| query.find_location_of(pickle) | ||
| query.find_lineage_by(pickle_or_runtime_message) | ||
| query.find_test_case_by(test_case_started_or_finished) | ||
| query.find_test_case_started_by(test_case_finished_or_step_message) | ||
| query.find_test_case_finished_by(test_case_started) | ||
| query.find_test_step_by(test_step_started_or_finished) | ||
| query.find_pickle_step_by(test_step) | ||
| query.find_step_by(pickle_step) | ||
| query.find_hook_by(test_step_or_test_run_hook_message) | ||
| query.find_step_definitions_by(test_step) | ||
| query.find_unambiguous_step_definition_by(test_step) | ||
| query.find_attachments_by(test_step_finished_or_test_run_hook_finished) | ||
| query.find_suggestions_by(pickle_or_pickle_step) | ||
| ``` | ||
|
|
||
| Duration helpers: | ||
|
|
||
| ```ruby | ||
| query.find_test_case_duration_by(test_case_started_or_finished) | ||
| query.find_test_run_duration | ||
| ``` | ||
|
|
||
| Ordering helpers: | ||
|
|
||
| ```ruby | ||
| query.find_all_test_case_started_order_by(->(query, item) { query.find_pickle_by(item).name }, ->(a, b) { a <=> b }) | ||
| query.find_all_test_case_finished_order_by(->(query, item) { item.timestamp.seconds }, ->(a, b) { a <=> b }) | ||
| ``` | ||
|
|
||
| The ordering methods receive a projection callable and a comparison callable. | ||
| Items whose projection is `nil` sort after items with a value. | ||
|
|
||
| ## Local development dependencies | ||
|
|
||
| Use released gems by default. To test against a local `cucumber-messages` | ||
| checkout without editing this repository, set an environment variable before | ||
| running Bundler: | ||
|
|
||
| ```sh | ||
| cd repos/query/ruby | ||
| CUCUMBER_MESSAGES_RUBY_PATH=../../messages/ruby bundle install | ||
| ``` | ||
|
|
||
| Run the Ruby checks from this directory: | ||
|
|
||
| ```sh | ||
| bundle exec rake | ||
| ``` | ||
|
|
||
| The acceptance specs load shared NDJSON fixtures from `../testdata/src` and | ||
| compare the Ruby query results with the shared expected JSON files. | ||
|
|
||
| ## Performance and thread-safety notes | ||
|
|
||
| `Cucumber::Query::Query` keeps indexes for the whole message stream in memory. | ||
| This is the intended trade-off for formatters that need to correlate runtime | ||
| messages with source, pickle, hook, attachment, and suggestion messages. | ||
|
|
||
| A query instance is mutable and is not designed for concurrent `#update` calls. | ||
| Use one query per formatter/message stream, or synchronize access externally if | ||
| you share an instance across threads. | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| - A lookup returning `nil` usually means the related message has not been fed to | ||
| the query yet, or the input stream is incomplete. | ||
| - Retried scenarios whose `test_case_finished.will_be_retried` flag is true are | ||
| excluded from final scenario/result collections such as | ||
| `find_all_test_case_started` and `find_all_test_case_finished`. | ||
| - Status aggregation uses Cucumber's severity order: | ||
| `UNKNOWN`, `PASSED`, `SKIPPED`, `PENDING`, `UNDEFINED`, `AMBIGUOUS`, `FAILED`. | ||
| - The Ruby package is developed against shared Cucumber Query testdata; when a | ||
| new shared fixture appears, add/adjust Ruby acceptance coverage rather than | ||
| creating Ruby-only behaviour. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require 'bundler/gem_tasks' | ||
| require 'rspec/core/rake_task' | ||
| require 'rubocop/rake_task' | ||
|
|
||
| RSpec::Core::RakeTask.new(:spec) | ||
| RuboCop::RakeTask.new(:rubocop) | ||
|
|
||
| task default: %i[spec rubocop] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 15.0.1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| version = File.read(File.expand_path('VERSION', __dir__)).strip | ||
|
|
||
| Gem::Specification.new do |s| | ||
| s.name = 'cucumber-query' | ||
| s.version = version | ||
| s.authors = ['Matt Wynne'] | ||
| s.description = 'Query and correlate Cucumber messages' | ||
| s.summary = "cucumber-query-#{s.version}" | ||
| s.email = 'hello@cucumber.io' | ||
| s.homepage = 'https://github.com/cucumber/query#readme' | ||
| 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/tree/main/ruby', | ||
| 'rubygems_mfa_required' => 'true', | ||
| 'source_code_uri' => 'https://github.com/cucumber/query' | ||
| } | ||
|
|
||
| s.add_dependency 'cucumber-messages', '>= 32.0.0', '< 33.0.0' | ||
|
|
||
| s.add_development_dependency 'rake', '~> 13.1' | ||
| s.add_development_dependency 'rspec', '~> 3.13' | ||
| s.add_development_dependency 'rubocop', '~> 1.80' | ||
| s.add_development_dependency 'rubocop-performance', '~> 1.24' | ||
| s.add_development_dependency 'rubocop-rake', '~> 0.6' | ||
| s.add_development_dependency 'rubocop-rspec', '~> 3.7' | ||
|
|
||
| s.files = Dir['README.md', 'VERSION', 'lib/**/*'] | ||
| s.rdoc_options = ['--charset=UTF-8'] | ||
| s.require_path = 'lib' | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require_relative 'query/version' | ||
| require_relative 'query/state' | ||
| require_relative 'query/gherkin_indexer' | ||
| require_relative 'query/envelope_indexer' | ||
| require_relative 'query/result_order' | ||
| require_relative 'query/naming_strategy' | ||
| require_relative 'query/finders' | ||
| require_relative 'query/query' | ||
|
|
||
| module Cucumber | ||
| module Query | ||
| end | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The recommendation for YAML files is to use the
.yamlextension.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also copy over the the
release-rubygem.yamlfrom any other project.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<Matt's robot>Updated in f01d586: renamed the Ruby test workflow to
.github/workflows/test-ruby.yaml. The RubyGems release workflow is already present as.github/workflows/release-rubygem.yaml, copied in this PR.</Matt's robot>