cucumber-wire-8.0.0/0000755000004100000410000000000015074020542014317 5ustar www-datawww-datacucumber-wire-8.0.0/cucumber-wire.gemspec0000644000004100000410000000474415074020542020446 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: cucumber-wire 8.0.0 ruby lib Gem::Specification.new do |s| s.name = "cucumber-wire".freeze s.version = "8.0.0".freeze s.required_rubygems_version = Gem::Requirement.new(">= 3.2.8".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Matt Wynne".freeze, "Aurelien Reeves".freeze, "Luke Hill".freeze] s.date = "2025-10-14" s.description = "Wire protocol for Cucumber".freeze s.email = "cukes@googlegroups.com".freeze s.files = ["CHANGELOG.md".freeze, "LICENSE".freeze, "README.md".freeze, "lib/cucumber/wire.rb".freeze, "lib/cucumber/wire/add_hooks_filter.rb".freeze, "lib/cucumber/wire/configuration.rb".freeze, "lib/cucumber/wire/connection.rb".freeze, "lib/cucumber/wire/connections.rb".freeze, "lib/cucumber/wire/data_packet.rb".freeze, "lib/cucumber/wire/exception.rb".freeze, "lib/cucumber/wire/plugin.rb".freeze, "lib/cucumber/wire/protocol.rb".freeze, "lib/cucumber/wire/protocol/requests.rb".freeze, "lib/cucumber/wire/request_handler.rb".freeze, "lib/cucumber/wire/snippet.rb".freeze, "lib/cucumber/wire/step_argument.rb".freeze, "lib/cucumber/wire/step_definition.rb".freeze] s.homepage = "http://cucumber.io".freeze s.licenses = ["MIT".freeze] s.rdoc_options = ["--charset=UTF-8".freeze] s.required_ruby_version = Gem::Requirement.new(">= 3.1".freeze) s.rubygems_version = "3.4.20".freeze s.summary = "cucumber-wire-8.0.0".freeze s.specification_version = 4 s.add_development_dependency(%q.freeze, ["~> 2.2".freeze]) s.add_development_dependency(%q.freeze, ["> 8".freeze, "< 11".freeze]) s.add_runtime_dependency(%q.freeze, ["> 11".freeze, "< 16".freeze]) s.add_runtime_dependency(%q.freeze, ["> 14".freeze, "< 20".freeze]) s.add_development_dependency(%q.freeze, ["~> 13.3".freeze]) s.add_development_dependency(%q.freeze, ["~> 3.13".freeze]) s.add_development_dependency(%q.freeze, ["~> 1.81.0".freeze]) s.add_development_dependency(%q.freeze, ["~> 0.6.0".freeze]) s.add_development_dependency(%q.freeze, ["~> 1.26.0".freeze]) s.add_development_dependency(%q.freeze, ["~> 0.7.1".freeze]) s.add_development_dependency(%q.freeze, ["~> 3.7.0".freeze]) end cucumber-wire-8.0.0/lib/0000755000004100000410000000000015074020542015065 5ustar www-datawww-datacucumber-wire-8.0.0/lib/cucumber/0000755000004100000410000000000015074020542016672 5ustar www-datawww-datacucumber-wire-8.0.0/lib/cucumber/wire.rb0000644000004100000410000000031115074020542020160 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/wire/plugin' return unless respond_to?(:InstallPlugin) InstallPlugin do |config, registry| Cucumber::Wire::Plugin.new(config, registry).install end cucumber-wire-8.0.0/lib/cucumber/wire/0000755000004100000410000000000015074020542017640 5ustar www-datawww-datacucumber-wire-8.0.0/lib/cucumber/wire/protocol/0000755000004100000410000000000015074020542021501 5ustar www-datawww-datacucumber-wire-8.0.0/lib/cucumber/wire/protocol/requests.rb0000644000004100000410000000732715074020542023712 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/wire/request_handler' require 'cucumber/wire/step_argument' module Cucumber module Wire module Protocol module Requests class StepMatches < RequestHandler def execute(name_to_match) @name_to_match = name_to_match request_params = { name_to_match: name_to_match } super(request_params) end def handle_success(params) params.map do |raw_step_match| create_step_match(raw_step_match) end end alias handle_step_matches handle_success private def create_step_match(raw_step_match) step_definition = StepDefinition.new(@connection, raw_step_match, @registry) step_args = raw_step_match['args'].map do |raw_arg| StepArgument.new(raw_arg['pos'], raw_arg['val']) end step_match(step_definition, step_args) end def step_match(step_definition, step_args) StepMatch.new(step_definition, @name_to_match, step_args) end end class SnippetText < RequestHandler def execute(step_keyword, step_name, multiline_arg_class_name) request_params = { step_keyword: step_keyword, step_name: step_name, multiline_arg_class: multiline_arg_class_name } super(request_params) end def handle_success(snippet_text) snippet_text end alias handle_snippet_text handle_success end class Invoke < RequestHandler def execute(step_definition_id, args) request_params = { id: step_definition_id, args: args } super(request_params) end def handle_pending(message) raise Pending, message || 'TODO' end def handle_diff!(tables) table1 = table(tables[0]) table2 = table(tables[1]) table1.diff!(table2) end def handle_diff(tables) begin handle_diff!(tables) rescue Cucumber::MultilineArgument::DataTable::Different @connection.diff_failed end @connection.diff_ok end alias handle_step_failed handle_fail private def table(data) Cucumber::MultilineArgument.from_core(Core::Test::DataTable.new(data)) end end class DiffFailed < RequestHandler alias handle_step_failed handle_fail end class DiffOk < RequestHandler alias handle_step_failed handle_fail end class HookRequestHandler < RequestHandler def execute(test_case) super(request_params(test_case)) end def method_missing(name, *args, &block) # TODO: Hard-code this until Ruby 3.4 is a minimum then this can be removed raise NoMethodError, "Undefined method '#{name}' for #{self.class}" end def respond_to_missing?(name, include_private = false) # TODO: Remove this once ruby 3.4 is the minimum super end private def request_params(test_case) return nil unless test_case.tags.any? { 'tags' => clean_tag_names(test_case.tags) } end def clean_tag_names(tags) tags.map { |tag| tag.name.gsub(/^@/, '') }.sort end end BeginScenario = Class.new(HookRequestHandler) EndScenario = Class.new(HookRequestHandler) end end end end cucumber-wire-8.0.0/lib/cucumber/wire/add_hooks_filter.rb0000644000004100000410000000352715074020542023474 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Wire class AddHooksFilter < Core::Filter.new(:connections) def test_case(test_case) test_case .with_steps([before_hook(test_case)] + test_case.test_steps + [after_hook(test_case)]) .describe_to receiver end def before_hook(test_case) # TODO: is this dependency on Cucumber::Hooks OK? Feels a bit internal.. # TODO: how do we express the location of the hook? Should we create one hook per connection so we can use the host:port of the connection? hook = instanciate_wire_hook(:begin_scenario) action = ->(result) { hook.invoke('Before', [Cucumber::RunningTestCase.new(test_case).with_result(result), connections]) } hook_step = Cucumber::Hooks.before_hook(id_generator.new_id, Core::Test::Location.new('wire'), &action) configuration.event_bus.hook_test_step_created(hook_step, hook) hook_step end def after_hook(test_case) hook = instanciate_wire_hook(:end_scenario) action = ->(result) { hook.invoke('After', [Cucumber::RunningTestCase.new(test_case).with_result(result), connections]) } hook_step = Cucumber::Hooks.after_hook(id_generator.new_id, Core::Test::Location.new('wire'), &action) configuration.event_bus.hook_test_step_created(hook_step, hook) hook_step end def id_generator @id_generator ||= connections.configuration.id_generator end def configuration @configuration ||= connections.configuration end private def instanciate_wire_hook(hook_method) Cucumber::Glue::Hook.new( id_generator.new_id, connections.registry, [], ->(test_case, connections) { connections.send(hook_method, test_case) } ) end end end end cucumber-wire-8.0.0/lib/cucumber/wire/configuration.rb0000644000004100000410000000153015074020542023033 0ustar www-datawww-data# frozen_string_literal: true require 'yaml' require 'erb' module Cucumber module Wire class Configuration attr_reader :host, :port, :unix def self.from_file(wire_file) settings = YAML.load(ERB.new(File.read(wire_file)).result) new(settings) end def initialize(args) @host = args['host'] @port = args['port'] @unix = args['unix'] unless RUBY_PLATFORM.match?(/mingw|mswin/) @timeouts = DEFAULT_TIMEOUTS.merge(args['timeout'] || {}) end def timeout(message = nil) @timeouts[message.to_s] || 3 end def to_s return @unix if @unix "#{@host}:#{@port}" end DEFAULT_TIMEOUTS = { 'connect' => 11, 'invoke' => 120, 'begin_scenario' => 120, 'end_scenario' => 120 } end end end cucumber-wire-8.0.0/lib/cucumber/wire/snippet.rb0000644000004100000410000000161015074020542021645 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Wire module Snippet class Generator def initialize(connections) # This array is shared mutable state with the wire language. @connections = connections end def call(code_keyword, step_name, multiline_arg, _snippet_type) @connections.snippets(code_keyword, step_name, MultilineArgClassName.new(multiline_arg).to_s).join("\n") end class MultilineArgClassName def initialize(arg) arg.describe_to(self) @result = '' end def data_table(*) @result = 'Cucumber::MultilineArgument::DataTable' end def doc_string(*) @result = 'Cucumber::MultilineArgument::DocString' end def to_s @result end end end end end end cucumber-wire-8.0.0/lib/cucumber/wire/step_definition.rb0000644000004100000410000000134415074020542023352 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/test/location' module Cucumber module Wire class StepDefinition attr_reader :id, :regexp_source, :location, :registry, :expression def initialize(connection, data, registry) @connection = connection @registry = registry @id = data['id'] @regexp_source = begin Regexp.new(data['regexp']) rescue StandardError data['regexp'] || 'Unknown' end @expression = registry.create_expression(@regexp_source) @location = Core::Test::Location.from_file_colon_line(data['source'] || 'unknown:0') end def invoke(args) @connection.invoke(@id, args) end end end end cucumber-wire-8.0.0/lib/cucumber/wire/connections.rb0000644000004100000410000000265015074020542022512 0ustar www-datawww-data# frozen_string_literal: true require 'json' require 'socket' require 'cucumber/wire/connection' require 'cucumber/wire/configuration' require 'cucumber/wire/data_packet' require 'cucumber/wire/exception' require 'cucumber/wire/step_definition' require 'cucumber/wire/snippet' require 'cucumber/configuration' require 'cucumber/step_match' module Cucumber module Wire class Connections attr_reader :connections, :configuration, :registry private :connections def initialize(connections, configuration, registry) raise ArgumentError unless connections @connections = connections @configuration = configuration @registry = registry end def find_match(test_step) matches = step_matches(test_step.name) return unless matches.any? # TODO: handle ambiguous matches (push to cucumber?) matches.first end def step_matches(step_name) connections.map { |c| c.step_matches(step_name, @registry) }.flatten end def begin_scenario(test_case) connections.each { |c| c.begin_scenario(test_case) } end def end_scenario(test_case) connections.each { |c| c.end_scenario(test_case) } end def snippets(code_keyword, step_name, multiline_arg_class_name) connections.map { |c| c.snippet_text(code_keyword, step_name, multiline_arg_class_name) }.flatten end end end end cucumber-wire-8.0.0/lib/cucumber/wire/data_packet.rb0000644000004100000410000000146015074020542022426 0ustar www-datawww-data# frozen_string_literal: true require 'json' module Cucumber module Wire # Represents the packet of data sent over the wire as JSON data, containing # a message and a hash of arguments class DataPacket class << self def parse(raw) attributes = JSON.parse(raw.strip) message = attributes[0] params = attributes[1] new(message, params) end end attr_reader :message, :params def initialize(message, params = nil) @message = message @params = params end def to_json(*_args) packet = [@message] packet << @params if @params JSON.generate(packet) end def handle_with(handler) handler.send(:"handle_#{@message}", @params) end end end end cucumber-wire-8.0.0/lib/cucumber/wire/plugin.rb0000644000004100000410000000227015074020542021464 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/wire/connections' require 'cucumber/wire/add_hooks_filter' require 'cucumber/step_match_search' module Cucumber module Wire class Plugin attr_reader :config, :registry private :config, :registry def self.installed? @@installed ||= false end def initialize(config, registry) @config = config @registry = registry end def install connections = Connections.new(wire_files.map { |f| create_connection(f) }, config, registry) config.filters << Filters::ActivateSteps.new(StepMatchSearch.new(connections.method(:step_matches), @config), @config) config.filters << AddHooksFilter.new(connections) unless @config.dry_run? config.register_snippet_generator Snippet::Generator.new(connections) @@installed = true end def create_connection(wire_file) Connection.new(Configuration.from_file(wire_file)) end def wire_files # TODO: change Cucumber's config object to allow us to get this information config.send(:require_dirs).map { |dir| Dir.glob("#{dir}/**/*.wire") }.flatten end end end end cucumber-wire-8.0.0/lib/cucumber/wire/request_handler.rb0000644000004100000410000000146615074020542023361 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Wire class RequestHandler def initialize(connection, registry = nil) @connection = connection @message = underscore(self.class.name.split('::').last) @registry = registry end def execute(request_params = nil) @connection.call_remote(self, @message, request_params) end def handle_fail(params) raise @connection.exception(params) end def handle_success(params); end private # Props to Rails def underscore(camel_cased_word) camel_cased_word .to_s .gsub('::', '/') .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') .gsub(/([a-z\d])([A-Z])/, '\1_\2') .tr('-', '_') .downcase end end end end cucumber-wire-8.0.0/lib/cucumber/wire/exception.rb0000644000004100000410000000162515074020542022167 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Wire # Proxy for an exception that occurred at the remote end of the wire class Exception < StandardError module CanSetName attr_writer :exception_name def to_s @exception_name end end def initialize(args, config) super(args['message']) if args['exception'] self.class.extend(CanSetName) self.class.exception_name = "#{args['exception']} from #{config}" end return unless args['backtrace'] @backtrace = if args['backtrace'].is_a?(String) args['backtrace'].split("\n") # TODO: change cuke4nuke to pass an array instead of a big string else args['backtrace'] end end def backtrace @backtrace || super end end end end cucumber-wire-8.0.0/lib/cucumber/wire/step_argument.rb0000644000004100000410000000101115074020542023033 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/cucumber_expressions/group' module Cucumber module Wire # Defines the location and value of a captured argument from the step # text class StepArgument attr_reader :offset def initialize(offset, val) @offset = offset @value = val end def value(_current_world) @value end def group CucumberExpressions::Group.new(@value, @offset, @offset + @value.length, []) end end end end cucumber-wire-8.0.0/lib/cucumber/wire/protocol.rb0000644000004100000410000000214215074020542022025 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/wire/protocol/requests' module Cucumber module Wire module Protocol def step_matches(name_to_match, registry) handler = Requests::StepMatches.new(self, registry) handler.execute(name_to_match) end def snippet_text(step_keyword, step_name, multiline_arg_class_name) handler = Requests::SnippetText.new(self) handler.execute(step_keyword, step_name, multiline_arg_class_name) end def invoke(step_definition_id, args) handler = Requests::Invoke.new(self) handler.execute(step_definition_id, args) end def diff_failed handler = Requests::DiffFailed.new(self) handler.execute end def diff_ok handler = Requests::DiffOk.new(self) handler.execute end def begin_scenario(scenario) handler = Requests::BeginScenario.new(self) handler.execute(scenario) end def end_scenario(scenario) handler = Requests::EndScenario.new(self) handler.execute(scenario) end end end end cucumber-wire-8.0.0/lib/cucumber/wire/connection.rb0000644000004100000410000000351015074020542022323 0ustar www-datawww-data# frozen_string_literal: true require 'timeout' require 'cucumber/wire/protocol' require 'cucumber/wire/exception' require 'cucumber/wire/data_packet' module Cucumber module Wire class Connection class ConnectionError < StandardError; end include Wire::Protocol def initialize(config) @config = config end def call_remote(request_handler, message, params) packet = DataPacket.new(message, params) begin send_data_to_socket(packet.to_json) response = fetch_data_from_socket(@config.timeout(message)) response.handle_with(request_handler) rescue Timeout::Error => e backtrace = e.backtrace backtrace.shift # because Timeout puts some weird stuff in there raise Timeout::Error, "Timed out calling wire server with message '#{message}'", backtrace end end def exception(params) Wire::Exception.new(params, @config) end private def send_data_to_socket(data) Timeout.timeout(@config.timeout('connect')) { socket.puts(data) } end def fetch_data_from_socket(timeout) raw_response = if timeout == :never socket.gets else Timeout.timeout(timeout) { socket.gets } end raise exception({ 'message' => "Remote Socket with #{@config.host}:#{@config.port} closed." }) if raw_response.nil? DataPacket.parse(raw_response) end def socket return @socket if @socket if @config.unix @socket = UNIXSocket.new(@config.unix) else @socket = TCPSocket.new(@config.host, @config.port) end rescue Errno::ECONNREFUSED raise(ConnectionError, "Unable to contact the wire server at #{@config}. Is it up?") end end end end cucumber-wire-8.0.0/LICENSE0000644000004100000410000000207615074020542015331 0ustar www-datawww-dataMIT License Copyright (c) 2015 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. cucumber-wire-8.0.0/README.md0000644000004100000410000000207615074020542015603 0ustar www-datawww-data[![Test cucumber-wire](https://github.com/cucumber/cucumber-ruby-wire/actions/workflows/test.yaml/badge.svg)](https://github.com/cucumber/cucumber-ruby-wire/actions/workflows/test.yaml) # cucumber-wire This gem was extracted from the [cucumber gem](https://github.com/cucumber/cucumber-ruby) This is available as an optional runtime dependency for cucumber-ruby and also as a runtime dependency for cucumber cpp. The tests here are a bit hairy and prone to the occasional flicker. ## Configuration You can configure the connection using a YML file called a `.wire` file: ```yml host: localhost port: 54321 timeout: connect: 11 invoke: 120 begin_scenario: 120 end_scenario: 120 ``` ### Timeouts The default timeout is 120 seconds. `connect` has a default timeout of 11 seconds. ### YML with ERB templating The file format is YAML, with ERB templating, so you could make the configuration configurable: ```yml,erb host: localhost port: 54321 timeout: connect: <%= (ENV['MY_CONNECT_TIMEOUT'] || 11).to_i %> invoke: 120 begin_scenario: 120 end_scenario: 120 ``` cucumber-wire-8.0.0/CHANGELOG.md0000644000004100000410000000664015074020542016136 0ustar www-datawww-data# Changelog All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). This document is formatted according to the principles of [Keep A CHANGELOG](http://keepachangelog.com). Please visit [cucumber/CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CONTRIBUTING.md) for more info on how to contribute to Cucumber. ## [Unreleased] ## [8.0.0] - 2025-10-14 ### Added - Added some basic rubocop compliance to suite (More fixes to come) ([#69](https://github.com/cucumber/cucumber-ruby-wire/pull/69) [#70](https://github.com/cucumber/cucumber-ruby-wire/pull/70) [#84](https://github.com/cucumber/cucumber-ruby-wire/pull/84)) ### Changed - Minimum ruby version is now 3.1 ([#82](https://github.com/cucumber/cucumber-ruby-wire/pull/82)) - Enabled latest versions of `cucumber-core` and `cucumber-expressions` ([#82](https://github.com/cucumber/cucumber-ruby-wire/pull/82)) ### Fixed - When unknown errors are called, a more Ruby 3.5-esque way of displaying `NoMethodError` is performed ([#85](https://github.com/cucumber/cucumber-ruby-wire/pull/85)) ## [7.0.0] - 2023-11-10 ### Changed - Minimum ruby version is now 2.6 ([#68](https://github.com/cucumber/cucumber-ruby-wire/pull/68)) - Compatibility with cucumber versions up to cucumber 9.x now added ([#68](https://github.com/cucumber/cucumber-ruby-wire/pull/68)) ### Fixed - Fixed up some basic style incompatibilities with old rspec tests ([#68](https://github.com/cucumber/cucumber-ruby-wire/pull/68)) ## [6.2.1] - 2022-01-07 ### Fixed - Fix usage with message-related formatters like `html-formatter` ([#57](https://github.com/cucumber/cucumber-ruby-wire/pull/57)) - Removed dependency to `cucumber-messages` ## [6.2.0] - 2021-10-12 ### Changed - Patched `cucumber-core`, `cucumber-expressions` and `cucumber-messages` ## [6.1.1] - 2021-08-27 ### Changed - Use the new `InstallPlugin` hook rather than `AfterConfiguration`. ([#52](https://github.com/cucumber/cucumber-ruby-wire/pull/52)) ## [6.1.0] - 2021-08-09 ### Added - The possibility to verify if the plugin has already been installed or not. To do so, use `Cucumber::Wire::Plugin.installed?`. ([46](https://github.com/cucumber/cucumber-ruby-wire/pull/46)) ### Changed - `cucumber-wire` is now able to install itself as a plugin into `cucumber-ruby`. ([46](https://github.com/cucumber/cucumber-ruby-wire/pull/46)) ## [6.0.1] - 2021-07-19 ### Changed - Patched `cucumber-core` and `cucumber-messages` ## [6.0.0] - 2021-07-09 ### Changed - Updated `cucumber-core` ~> 10.0.0 and `cucumber-messages` ~> 17.0.0 This brings breaking changes because of internal changes in Cucumber::Messages. ([#44](https://github.com/cucumber/cucumber-ruby-wire/pull/44)) [Unreleased]: https://github.com/cucumber/cucumber-ruby-wire/compare/v8.0.0...HEAD [8.0.0]: https://github.com/cucumber/cucumber-ruby-wire/compare/v7.0.0...v8.0.0 [7.0.0]: https://github.com/cucumber/cucumber-ruby-wire/compare/v6.2.1...v7.0.0 [6.2.1]: https://github.com/cucumber/cucumber-ruby-wire/compare/v6.2.0...v6.2.1 [6.2.0]: https://github.com/cucumber/cucumber-ruby-wire/compare/v6.1.1...v6.2.0 [6.1.1]: https://github.com/cucumber/cucumber-ruby-wire/compare/v6.1.0...v6.1.1 [6.1.0]: https://github.com/cucumber/cucumber-ruby-wire/compare/v6.0.1...v6.1.0 [6.0.1]: https://github.com/cucumber/cucumber-ruby-wire/compare/v6.0.0...v6.0.1 [6.0.0]: https://github.com/cucumber/cucumber-ruby-wire/compare/v5.0.1...v6.0.0