cucumber-wire-0.0.1/0000755000004100000410000000000012744237615014324 5ustar www-datawww-datacucumber-wire-0.0.1/Rakefile0000644000004100000410000000034312744237615015771 0ustar www-datawww-datarequire 'rubygems' require 'bundler' Bundler::GemHelper.install_tasks task default: [:unit_tests, :acceptance_tests] task :unit_tests do sh "bundle exec rspec" end task :acceptance_tests do sh "bundle exec cucumber" end cucumber-wire-0.0.1/cucumber-wire.gemspec0000644000004100000410000000205512744237615020444 0ustar www-datawww-data# -*- encoding: utf-8 -*- $LOAD_PATH.unshift File.expand_path("../lib", __FILE__) Gem::Specification.new do |s| s.name = 'cucumber-wire' s.version = File.read(File.dirname(__FILE__) + "/lib/cucumber/wire/version") s.authors = ["Matt Wynne"] s.description = "Wire protocol for Cucumber" s.summary = "cucumber-wire-#{s.version}" s.email = 'cukes@googlegroups.com' s.homepage = "http://cucumber.io" s.platform = Gem::Platform::RUBY s.license = "MIT" s.required_ruby_version = ">= 1.9.3" s.add_development_dependency 'cucumber', '~> 2.1.0' s.add_development_dependency 'bundler', '>= 1.3.5' s.add_development_dependency 'rake', '>= 0.9.2' s.add_development_dependency 'rspec', '~> 3' s.add_development_dependency 'aruba', '~> 0' s.rubygems_version = ">= 1.6.1" s.files = `git ls-files`.split("\n").reject {|path| path =~ /\.gitignore$/ } s.test_files = `git ls-files -- spec/*`.split("\n") s.rdoc_options = ["--charset=UTF-8"] s.require_path = "lib" end cucumber-wire-0.0.1/Gemfile0000644000004100000410000000133312744237615015617 0ustar www-datawww-datagemspec source "https://rubygems.org" unless ENV['CUCUMBER_USE_RELEASED_GEMS'] # cucumber gem cucumber_path = File.expand_path("../../cucumber-ruby", __FILE__) if File.exist?(cucumber_path) && !ENV['CUCUMBER_USE_GIT'] gem "cucumber", path: cucumber_path, branch: "remove-wire-protocol-to-plugin" else gem "cucumber", :git => "git://github.com/cucumber/cucumber-ruby.git", branch: "remove-wire-protocol-to-plugin" end # cucumber-core gem core_path = File.expand_path("../../cucumber-ruby-core", __FILE__) if File.exist?(core_path) && !ENV['CUCUMBER_USE_GIT_CORE'] gem "cucumber-core", path: core_path else gem 'cucumber-core', :git => "git://github.com/cucumber/cucumber-ruby-core.git" end end cucumber-wire-0.0.1/features/0000755000004100000410000000000012744237615016142 5ustar www-datawww-datacucumber-wire-0.0.1/features/step_definitions/0000755000004100000410000000000012744237615021510 5ustar www-datawww-datacucumber-wire-0.0.1/features/step_definitions/aruba_steps.rb0000644000004100000410000000003112744237615024337 0ustar www-datawww-datarequire "aruba/cucumber" cucumber-wire-0.0.1/features/step_definitions/wire_steps.rb0000644000004100000410000000271412744237615024225 0ustar www-datawww-dataGiven /^there is a wire server (running |)on port (\d+) which understands the following protocol:$/ do |running, port, table| protocol = table.hashes.map do |table_hash| table_hash['response'] = table_hash['response'].gsub(/\n/, '\n') table_hash end @server = FakeWireServer.new(port.to_i, protocol) start_wire_server if running.strip == "running" end Given /^the wire server takes (.*) seconds to respond to the invoke message$/ do |timeout| @server.delay_response(:invoke, timeout.to_f) start_wire_server end Given /^I have environment variable (\w+) set to "([^"]*)"$/ do |variable, value| set_env(variable, value) end Then(/^the wire server should have received the following messages:$/) do |expected_messages| expect(messages_received).to eq expected_messages.raw.flatten end module WireHelper attr_reader :messages_received def start_wire_server @messages_received = [] reader, writer = IO.pipe @wire_pid = fork { reader.close @server.run(writer) } writer.close Thread.new do while message = reader.gets @messages_received << message.strip end end at_exit { stop_wire_server } end def stop_wire_server return unless @wire_pid Process.kill('KILL', @wire_pid) Process.wait(@wire_pid) rescue Errno::ESRCH # No such process - wire server has already been stopped by the After hook end end Before do extend(WireHelper) end After do stop_wire_server end cucumber-wire-0.0.1/features/step_matches_message.feature0000644000004100000410000000540412744237615023705 0ustar www-datawww-dataFeature: Step matches message When the features have been parsed, Cucumber will send a `step_matches` message to ask the wire server if it can match a step name. This happens for each of the steps in each of the features. The wire server replies with an array of StepMatch objects. When each StepMatch is returned, it contains the following data: * `id` - identifier for the step definition to be used later when if it needs to be invoked. The identifier can be any string value and is simply used for the wire server's own reference. * `args` - any argument values as captured by the wire end's own regular expression (or other argument matching) process. Background: Given a file named "features/wired.feature" with: """ Feature: High strung Scenario: Wired Given we're all wired """ And a file named "features/step_definitions/some_remote_place.wire" with: """ host: localhost port: 54321 """ Scenario: Dry run finds no step match Given there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[]] | When I run `cucumber --dry-run --no-snippets -f progress` And it should pass with: """ U 1 scenario (1 undefined) 1 step (1 undefined) """ Scenario: Dry run finds a step match Given there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[]}]] | When I run `cucumber --dry-run -f progress` And it should pass with: """ - 1 scenario (1 skipped) 1 step (1 skipped) """ Scenario: Step matches returns details about the remote step definition Optionally, the StepMatch can also contain a source reference, and a native regexp string which will be used by some formatters. Given there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[], "source":"MyApp.MyClass:123", "regexp":"we.*"}]] | When I run `cucumber -f stepdefs --dry-run` Then it should pass with: """ - we.* # MyApp.MyClass:123 1 scenario (1 skipped) 1 step (1 skipped) """ And the stderr should not contain anything cucumber-wire-0.0.1/features/timeouts.feature0000644000004100000410000000441112744237615021370 0ustar www-datawww-dataFeature: Wire protocol timeouts We don't want Cucumber to hang forever on a wire server that's not even there, but equally we need to give the user the flexibility to allow step definitions to take a while to execute, if that's what they need. Background: Given a file named "features/wired.feature" with: """ Feature: Telegraphy Scenario: Wired Given we're all wired """ Scenario: Try to talk to a server that's not there Given a file named "features/step_definitions/some_remote_place.wire" with: """ host: localhost port: 54321 """ When I run `cucumber -f progress` Then the stderr should contain: """ Unable to contact the wire server at localhost:54321 """ @spawn Scenario: Invoke a step definition that takes longer than its timeout Given a file named "features/step_definitions/some_remote_place.wire" with: """ host: localhost port: 54321 timeout: invoke: 0.1 """ And there is a wire server on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[{"val":"wired", "pos":10}]}]] | | ["begin_scenario"] | ["success"] | | ["invoke",{"id":"1","args":["wired"]}] | ["success"] | | ["end_scenario"] | ["success"] | And the wire server takes 0.2 seconds to respond to the invoke message When I run `cucumber -f pretty -q` Then the stderr should not contain anything And it should fail with exactly: """ Feature: Telegraphy Scenario: Wired Given we're all wired Timed out calling wire server with message 'invoke' (Timeout::Error) features/wired.feature:3:in `Given we're all wired' Failing Scenarios: cucumber features/wired.feature:2 1 scenario (1 failed) 1 step (1 failed) """ cucumber-wire-0.0.1/features/readme.md0000644000004100000410000000233112744237615017720 0ustar www-datawww-dataCucumber's wire protocol allows step definitions to be implemented and invoked on any platform. Communication is over a TCP socket, which Cucumber connects to when it finds a definition file with the .wire extension in the step_definitions folder (or other load path). Note that these files are rendered with ERB when loaded. A `Wire::DataPacket` flowing in either direction is formatted as a JSON-encoded string, with a newline character signaling the end of a packet. See the specs for `Cucumber::Wire::DataPacket` for more details. Cucumber sends the following request messages out over the wire: * `step_matches` - Find out whether the wire server has a definition for a step * `invoke` - Ask for a step definition to be invoked * `begin_scenario` - signals that cucumber is about to execute a scenario * `end_scenario` - signals that cucumber has finished executing a scenario * `snippet_text` - requests a snippet for an undefined step Every message supports two standard responses: * `success` - expects different arguments (sometimes none at all) depending on the request that was sent. * `fail` - causes a Cucumber::Wire::Exception to be raised. Some messages support more responses - see individual scenarios for details. cucumber-wire-0.0.1/features/snippets_message.feature0000644000004100000410000000335112744237615023072 0ustar www-datawww-dataFeature: Snippets message If a step doesn't match, Cucumber will ask the wire server to return a snippet of code for a step definition. Background: Given a file named "features/wired.feature" with: """ Feature: High strung Scenario: Wired Given we're all wired """ And a file named "features/step_definitions/some_remote_place.wire" with: """ host: localhost port: 54321 """ @spawn Scenario: Wire server returns snippets for a step that didn't match Given there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[]] | | ["snippet_text",{"step_keyword":"Given","multiline_arg_class":"","step_name":"we're all wired"}] | ["success","foo()\n bar;\nbaz"] | | ["begin_scenario"] | ["success"] | | ["end_scenario"] | ["success"] | When I run `cucumber -f pretty` Then the stderr should not contain anything And it should pass with: """ Feature: High strung Scenario: Wired # features/wired.feature:2 Given we're all wired # features/wired.feature:3 1 scenario (1 undefined) 1 step (1 undefined) """ And the output should contain: """ foo() bar; baz """ cucumber-wire-0.0.1/features/erb_configuration.feature0000644000004100000410000000344112744237615023220 0ustar www-datawww-dataFeature: ERB configuration As a developer on server with multiple users I want to be able to configure which port my wire server runs on So that I can avoid port conflicts Background: Given a file named "features/wired.feature" with: """ Feature: High strung Scenario: Wired Given we're all wired """ Scenario: ERB is used in the wire file which references an environment variable that is not set Given a file named "features/step_definitions/server.wire" with: """ host: localhost port: <%= ENV['PORT'] || 12345 %> """ And there is a wire server running on port 12345 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[]] | When I run `cucumber --dry-run --no-snippets -f progress` Then it should pass with: """ U 1 scenario (1 undefined) 1 step (1 undefined) """ Scenario: ERB is used in the wire file which references an environment variable Given I have environment variable PORT set to "16816" And a file named "features/step_definitions/server.wire" with: """ host: localhost port: <%= ENV['PORT'] || 12345 %> """ And there is a wire server running on port 16816 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[]] | When I run `cucumber --dry-run --no-snippets -f progress` Then it should pass with: """ U 1 scenario (1 undefined) 1 step (1 undefined) """ cucumber-wire-0.0.1/features/tags.feature0000644000004100000410000000526612744237615020466 0ustar www-datawww-dataFeature: Wire protocol tags In order to use Before and After hooks in a wire server, we send tags with the scenario in the begin_scenario and end_scenario messages Background: Given a file named "features/step_definitions/some_remote_place.wire" with: """ host: localhost port: 54321 """ Scenario: Run a scenario Given a file named "features/wired.feature" with: """ @foo @bar Feature: Wired @baz Scenario: Everybody's Wired Given we're all wired """ And there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[]}]] | | ["begin_scenario", {"tags":["bar","baz","foo"]}] | ["success"] | | ["invoke",{"id":"1","args":[]}] | ["success"] | | ["end_scenario", {"tags":["bar","baz","foo"]}] | ["success"] | When I run `cucumber -f pretty -q` Then the stderr should not contain anything And it should pass with: """ @foo @bar Feature: Wired @baz Scenario: Everybody's Wired Given we're all wired 1 scenario (1 passed) 1 step (1 passed) """ Scenario: Run a scenario outline example Given a file named "features/wired.feature" with: """ @foo @bar Feature: Wired @baz Scenario Outline: Everybody's Wired Given we're all Examples: | something | | wired | """ And there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[]}]] | | ["begin_scenario", {"tags":["bar","baz","foo"]}] | ["success"] | | ["invoke",{"id":"1","args":[]}] | ["success"] | | ["end_scenario", {"tags":["bar","baz","foo"]}] | ["success"] | When I run `cucumber -f pretty -q` Then the stderr should not contain anything And it should pass with exactly: """ @foo @bar Feature: Wired @baz Scenario Outline: Everybody's Wired Given we're all Examples: | something | | wired | 1 scenario (1 passed) 1 step (1 passed) """ cucumber-wire-0.0.1/features/table_diffing.feature0000644000004100000410000001270712744237615022303 0ustar www-datawww-dataFeature: Wire protocol table diffing In order to use the amazing functionality in the Cucumber table object As a wire server I want to be able to ask for a table diff during a step definition invocation Background: Given a file named "features/wired.feature" with: """ Feature: Hello Scenario: Wired Given we're all wired """ And a file named "features/step_definitions/some_remote_place.wire" with: """ host: localhost port: 54321 """ @spawn Scenario: Invoke a step definition tries to diff the table and fails Given there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[]}]] | | ["begin_scenario"] | ["success"] | | ["invoke",{"id":"1","args":[]}] | ["diff",[[["a","b"],["c","d"]],[["x","y"],["z","z"]]]] | | ["diff_failed"] | ["fail",{"message":"Not same", "exception":"DifferentException", "backtrace":["a.cs:12","b.cs:34"]}] | | ["end_scenario"] | ["success"] | When I run `cucumber -f progress --backtrace -q` Then the stderr should not contain anything And it should fail with exactly: """ F (::) failed steps (::) Not same (DifferentException from localhost:54321) a.cs:12 b.cs:34 features/wired.feature:3:in `Given we're all wired' Failing Scenarios: cucumber features/wired.feature:2 1 scenario (1 failed) 1 step (1 failed) """ Scenario: Invoke a step definition tries to diff the table and passes Given there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[]}]] | | ["begin_scenario"] | ["success"] | | ["invoke",{"id":"1","args":[]}] | ["diff",[[["a"],["b"]],[["a"],["b"]]]] | | ["diff_ok"] | ["success"] | | ["end_scenario"] | ["success"] | When I run `cucumber -f progress -q` Then it should pass with exactly: """ . 1 scenario (1 passed) 1 step (1 passed) """ @spawn Scenario: Invoke a step definition which successfully diffs a table but then fails Given there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[]}]] | | ["begin_scenario"] | ["success"] | | ["invoke",{"id":"1","args":[]}] | ["diff",[[["a"],["b"]],[["a"],["b"]]]] | | ["diff_ok"] | ["fail",{"message":"I wanted things to be different for us"}] | | ["end_scenario"] | ["success"] | When I run `cucumber -f progress -q` Then it should fail with exactly: """ F (::) failed steps (::) I wanted things to be different for us (Cucumber::Wire::Exception) features/wired.feature:3:in `Given we're all wired' Failing Scenarios: cucumber features/wired.feature:2 1 scenario (1 failed) 1 step (1 failed) """ @spawn Scenario: Invoke a step definition which asks for an immediate diff that fails Given there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[]}]] | | ["begin_scenario"] | ["success"] | | ["invoke",{"id":"1","args":[]}] | ["diff!",[[["a"]],[["b"]]]] | | ["end_scenario"] | ["success"] | When I run `cucumber -f progress -q` And it should fail with exactly: """ F (::) failed steps (::) Tables were not identical: | (-) a | (+) b | (Cucumber::MultilineArgument::DataTable::Different) features/wired.feature:3:in `Given we're all wired' Failing Scenarios: cucumber features/wired.feature:2 1 scenario (1 failed) 1 step (1 failed) """ cucumber-wire-0.0.1/features/handle_unexpected_response.feature0000644000004100000410000000176112744237615025121 0ustar www-datawww-dataFeature: Handle unexpected response When the server sends us back a message we don't understand, this is how Cucumber will behave. Background: Given a file named "features/wired.feature" with: """ Feature: High strung Scenario: Wired Given we're all wired """ And a file named "features/step_definitions/some_remote_place.wire" with: """ host: localhost port: 54321 """ Scenario: Unexpected response Given there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["begin_scenario"] | ["yikes"] | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[]}]] | When I run `cucumber -f pretty` Then the output should contain: """ undefined method `handle_yikes' """ cucumber-wire-0.0.1/features/invoke_message.feature0000644000004100000410000002131412744237615022517 0ustar www-datawww-dataFeature: Invoke message Assuming a StepMatch was returned for a given step name, when it's time to invoke that step definition, Cucumber will send an invoke message. The invoke message contains the ID of the step definition, as returned by the wire server in response to the the step_matches call, along with the arguments that were parsed from the step name during the same step_matches call. The wire server will normally reply one of the following: * `success` * `fail` * `pending` - optionally takes a message argument This isn't quite the whole story: see also table_diffing.feature Background: Given a file named "features/wired.feature" with: """ Feature: High strung Scenario: Wired Given we're all wired """ And a file named "features/step_definitions/some_remote_place.wire" with: """ host: localhost port: 54321 """ @spawn Scenario: Invoke a step definition which is pending Given there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[]}]] | | ["begin_scenario"] | ["success"] | | ["invoke",{"id":"1","args":[]}] | ["pending", "I'll do it later"] | | ["end_scenario"] | ["success"] | When I run `cucumber -f pretty -q` And it should pass with exactly: """ Feature: High strung Scenario: Wired Given we're all wired I'll do it later (Cucumber::Pending) features/wired.feature:3:in `Given we're all wired' 1 scenario (1 pending) 1 step (1 pending) """ Scenario: Invoke a step definition which passes Given there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[]}]] | | ["begin_scenario"] | ["success"] | | ["invoke",{"id":"1","args":[]}] | ["success"] | | ["end_scenario"] | ["success"] | When I run `cucumber -f progress` And it should pass with: """ . 1 scenario (1 passed) 1 step (1 passed) """ @spawn Scenario: Invoke a step definition which fails If an invoked step definition fails, it can return details of the exception in the reply to invoke. This causes a Cucumber::WireSupport::WireException to be raised. Valid arguments are: - `message` (mandatory) - `exception` - `backtrace` See the specs for Cucumber::WireSupport::WireException for more details Given there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[]}]] | | ["begin_scenario"] | ["success"] | | ["invoke",{"id":"1","args":[]}] | ["fail",{"message":"The wires are down", "exception":"Some.Foreign.ExceptionType"}] | | ["end_scenario"] | ["success"] | When I run `cucumber -f progress` Then the stderr should not contain anything And it should fail with: """ F (::) failed steps (::) The wires are down (Some.Foreign.ExceptionType from localhost:54321) features/wired.feature:3:in `Given we're all wired' Failing Scenarios: cucumber features/wired.feature:2 # Scenario: Wired 1 scenario (1 failed) 1 step (1 failed) """ Scenario: Invoke a step definition which takes string arguments (and passes) If the step definition at the end of the wire captures arguments, these are communicated back to Cucumber in the `step_matches` message. Cucumber expects these StepArguments to be returned in the StepMatch. The keys have the following meanings: - `val` - the value of the string captured for that argument from the step name passed in step_matches - `pos` - the position within the step name that the argument was matched (used for formatter highlighting) The argument values are then sent back by Cucumber in the `invoke` message. Given there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[{"val":"wired", "pos":10}]}]] | | ["begin_scenario"] | ["success"] | | ["invoke",{"id":"1","args":["wired"]}] | ["success"] | | ["end_scenario"] | ["success"] | When I run `cucumber -f progress` Then the stderr should not contain anything And it should pass with: """ . 1 scenario (1 passed) 1 step (1 passed) """ Scenario: Invoke a step definition which takes regular and table arguments (and passes) If the step has a multiline table argument, it will be passed with the invoke message as an array of array of strings. In this scenario our step definition takes two arguments - one captures the "we're" and the other takes the table. Given a file named "features/wired_on_tables.feature" with: """ Feature: High strung Scenario: Wired and more Given we're all: | wired | | high | | happy | """ And there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all:"}] | ["success",[{"id":"1", "args":[{"val":"we're", "pos":0}]}]] | | ["begin_scenario"] | ["success"] | | ["invoke",{"id":"1","args":["we're",[["wired"],["high"],["happy"]]]}] | ["success"] | | ["end_scenario"] | ["success"] | When I run `cucumber -f progress features/wired_on_tables.feature` Then the stderr should not contain anything And it should pass with: """ . 1 scenario (1 passed) 1 step (1 passed) """ Scenario: Invoke a scenario outline step Given a file named "features/wired_in_an_outline.feature" with: """ Feature: Scenario Outline: Given we're all Examples: | arg | | wired | """ And there is a wire server running on port 54321 which understands the following protocol: | request | response | | ["step_matches",{"name_to_match":"we're all wired"}] | ["success",[{"id":"1", "args":[]}]] | | ["begin_scenario"] | ["success"] | | ["invoke",{"id":"1","args":[]}] | ["success"] | | ["end_scenario"] | ["success"] | When I run `cucumber -f progress features/wired_in_an_outline.feature` Then the stderr should not contain anything And it should pass with: """ . 1 scenario (1 passed) 1 step (1 passed) """ And the wire server should have received the following messages: | step_matches | | begin_scenario | | invoke | | end_scenario | cucumber-wire-0.0.1/features/support/0000755000004100000410000000000012744237615017656 5ustar www-datawww-datacucumber-wire-0.0.1/features/support/fake_wire_server.rb0000644000004100000410000000354212744237615023531 0ustar www-datawww-datarequire 'multi_json' require 'socket' class FakeWireServer def initialize(port, protocol_table) @port, @protocol_table = port, protocol_table @delays = {} end def run(io) @server = TCPServer.open(@port) loop { handle_connections(io) } end def delay_response(message, delay) @delays[message] = delay end private def handle_connections(io) Thread.start(@server.accept) { |socket| open_session_on socket, io } end def open_session_on(socket, io) begin on_message = lambda { |message| io.puts message } SocketSession.new(socket, @protocol_table, @delays, on_message).start rescue Exception => e raise e ensure socket.close end end class SocketSession def initialize(socket, protocol, delays, on_message) @socket = socket @protocol = protocol @delays = delays @on_message = on_message end def start while message = @socket.gets handle(message) end end private def handle(data) if protocol_entry = response_to(data.strip) sleep delay(data) @on_message.call(MultiJson.load(protocol_entry['request'])[0]) send_response(protocol_entry['response']) else serialized_exception = { :message => "Not understood: #{data}", :backtrace => [] } send_response(['fail', serialized_exception ].to_json) end rescue => e send_response(['fail', { :message => e.message, :backtrace => e.backtrace, :exception => e.class } ].to_json) end def response_to(data) @protocol.detect do |entry| MultiJson.load(entry['request']) == MultiJson.load(data) end end def send_response(response) @socket.puts response + "\n" end def delay(data) message = MultiJson.load(data.strip)[0] @delays[message.to_sym] || 0 end end end cucumber-wire-0.0.1/.rspec0000644000004100000410000000001012744237615015430 0ustar www-datawww-data--color cucumber-wire-0.0.1/spec/0000755000004100000410000000000012744237615015256 5ustar www-datawww-datacucumber-wire-0.0.1/spec/cucumber/0000755000004100000410000000000012744237615017063 5ustar www-datawww-datacucumber-wire-0.0.1/spec/cucumber/wire/0000755000004100000410000000000012744237615020031 5ustar www-datawww-datacucumber-wire-0.0.1/spec/cucumber/wire/exception_spec.rb0000644000004100000410000000234712744237615023374 0ustar www-datawww-datarequire 'cucumber/wire/exception' require 'cucumber/wire/configuration' module Cucumber module Wire describe Exception do before(:each) do @config = Configuration.new('host' => 'localhost', 'port' => 54321) end def exception Wire::Exception.new(@data, @config) end describe "with just a message" do before(:each) do @data = {'message' => 'foo'} end it "#to_s as expecteds" do expect(exception.to_s).to eq "foo" end end describe "with a message and an exception" do before(:each) do @data = {'message' => 'foo', 'exception' => 'Bar'} end it "#to_s as expecteds" do expect(exception.to_s).to eq "foo" end it "#class.to_s returns the name of the exception" do expect(exception.class.to_s).to eq 'Bar from localhost:54321' end end describe "with a custom backtrace" do before(:each) do @data = {'message' => 'foo', 'backtrace' => ['foo', 'bar', 'baz']} end it "#backrace returns the custom backtrace" do expect(exception.backtrace).to eq ['foo', 'bar', 'baz'] end end end end end cucumber-wire-0.0.1/spec/cucumber/wire/connection_spec.rb0000644000004100000410000000330712744237615023532 0ustar www-datawww-datarequire 'cucumber/wire/connection' require 'cucumber/wire/configuration' module Cucumber module Wire describe Connection do class TestConnection < Connection attr_accessor :socket end class TestConfiguration attr_reader :custom_timeout def initialize @custom_timeout = {} end def timeout(message = nil) return :default_timeout if message.nil? @custom_timeout[message] || Configuration::DEFAULT_TIMEOUTS.fetch(message) end def host 'localhost' end def port '3902' end end before(:each) do @config = TestConfiguration.new @connection = TestConnection.new(@config) @connection.socket = @socket = double('socket').as_null_object @response = %q{["response"]} end it "re-raises a timeout error" do allow(Timeout).to receive(:timeout).and_raise(Timeout::Error.new('')) expect(-> { @connection.call_remote(nil, :foo, []) }).to raise_error(Timeout::Error) end it "ignores timeout errors when configured to do so" do @config.custom_timeout[:foo] = :never allow(@socket).to receive(:gets) { @response } handler = double(:handle_response => :response) expect(@connection.call_remote(handler, :foo, [])).to eq :response end it "raises an exception on remote connection closed" do @config.custom_timeout[:foo] = :never allow(@socket).to receive(:gets) expect(-> { @connection.call_remote(nil, :foo, []) }).to raise_error(Wire::Exception, 'Remote Socket with localhost:3902 closed.') end end end end cucumber-wire-0.0.1/spec/cucumber/wire/configuration_spec.rb0000644000004100000410000000273212744237615024243 0ustar www-datawww-datarequire 'cucumber/wire/configuration' require 'tempfile' module Cucumber module Wire describe Configuration do let(:wire_file) { Tempfile.new('wire') } let(:config) { Configuration.from_file(wire_file.path) } def write_wire_file(contents) wire_file << contents wire_file.close end it "reads the hostname / port from the file" do write_wire_file %q{ host: localhost port: 54321 } expect(config.host).to eq 'localhost' expect(config.port).to eq 54321 end it "reads the timeout for a specific message" do write_wire_file %q{ host: localhost port: 54321 timeout: invoke: 99 } expect(config.timeout('invoke')).to eq 99 end it "reads the timeout for a connect message" do write_wire_file %q{ host: localhost port: 54321 timeout: connect: 99 } expect(config.timeout('connect')).to eq 99 end describe "a wire file with no timeouts specified" do before(:each) do write_wire_file %q{ host: localhost port: 54321 } end %w(invoke begin_scenario end_scenario).each do |message| it "sets the default timeout for '#{message}' to 120 seconds" do expect(config.timeout(message)).to eq 120 end end end end end end cucumber-wire-0.0.1/spec/cucumber/wire/data_packet_spec.rb0000644000004100000410000000233212744237615023630 0ustar www-datawww-datarequire 'cucumber/wire/data_packet' module Cucumber module Wire describe DataPacket do describe "#to_json" do it "converts params to a JSON hash" do packet = DataPacket.new('test_message', :foo => :bar) expect(packet.to_json).to eq "[\"test_message\",{\"foo\":\"bar\"}]" end it "does not pass blank params" do packet = DataPacket.new('test_message') expect(packet.to_json).to eq "[\"test_message\"]" end end describe ".parse" do it "understands a raw packet containing null parameters" do packet = DataPacket.parse("[\"test_message\",null]") expect(packet.message).to eq 'test_message' expect(packet.params).to be_nil end it "understands a raw packet containing no parameters" do packet = DataPacket.parse("[\"test_message\"]") expect(packet.message).to eq 'test_message' expect(packet.params).to be_nil end it "understands a raw packet containging parameters data" do packet = DataPacket.parse("[\"test_message\",{\"foo\":\"bar\"}]") expect(packet.params['foo']).to eq 'bar' end end end end end cucumber-wire-0.0.1/spec/cucumber/wire/connections_spec.rb0000644000004100000410000000125412744237615023714 0ustar www-datawww-datarequire 'cucumber/wire/connections' require 'cucumber/wire/configuration' module Cucumber module Wire describe Connections do describe "#step_matches" do it "returns the matches from each of the RemoteSteps" do connection1 = double(step_matches: [:a, :b]) connection2 = double(step_matches: [:c]) connections = Connections.new([connection1, connection2], double) expect(connections.step_matches('')).to eq [:a, :b, :c] end it "copes with no connections" do connections = Connections.new([], double) expect(connections.step_matches('')).to eq [] end end end end end cucumber-wire-0.0.1/.travis.yml0000644000004100000410000000037012744237615016435 0ustar www-datawww-datarvm: - 2.2 - 2.1 - 2.0 - 1.9.3 - jruby-1.7.12 # whitelist branches: only: - master notifications: email: - cukes-devs@googlegroups.com webhooks: urls: # gitter - https://webhooks.gitter.im/e/dc010332f9d40fcc21c4 cucumber-wire-0.0.1/lib/0000755000004100000410000000000012744237615015072 5ustar www-datawww-datacucumber-wire-0.0.1/lib/cucumber/0000755000004100000410000000000012744237615016677 5ustar www-datawww-datacucumber-wire-0.0.1/lib/cucumber/wire.rb0000644000004100000410000000016012744237615020167 0ustar www-datawww-datarequire 'cucumber/wire/plugin' AfterConfiguration do |config| Cucumber::Wire::Plugin.new(config).install end cucumber-wire-0.0.1/lib/cucumber/wire/0000755000004100000410000000000012744237615017645 5ustar www-datawww-datacucumber-wire-0.0.1/lib/cucumber/wire/protocol.rb0000644000004100000410000000206012744237615022031 0ustar www-datawww-datarequire 'cucumber/wire/protocol/requests' module Cucumber module Wire module Protocol def step_matches(name_to_match) handler = Requests::StepMatches.new(self) 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-0.0.1/lib/cucumber/wire/configuration.rb0000644000004100000410000000146612744237615023050 0ustar www-datawww-datarequire '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'] if RUBY_PLATFORM !~ /mingw|mswin/ @timeouts = DEFAULT_TIMEOUTS.merge(args['timeout'] || {}) end def timeout(message = nil) return @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-0.0.1/lib/cucumber/wire/protocol/0000755000004100000410000000000012744237615021506 5ustar www-datawww-datacucumber-wire-0.0.1/lib/cucumber/wire/protocol/requests.rb0000644000004100000410000000703312744237615023711 0ustar www-datawww-datarequire 'cucumber/wire/request_handler' require 'cucumber/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) 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) # TODO: figure out if / how we could get a location for a table from the wire (or make a null location) location = Core::Ast::Location.new(__FILE__, __LINE__) table1 = table(tables[0], location) table2 = table(tables[1], location) 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, location) Cucumber::MultilineArgument.from_core(Core::Ast::DataTable.new(data, location)) 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 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-0.0.1/lib/cucumber/wire/connections.rb0000644000004100000410000000250112744237615022512 0ustar www-datawww-datarequire 'multi_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 private :connections def initialize(connections, configuration) raise ArgumentError unless connections @connections = connections @configuration = configuration 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)}.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-0.0.1/lib/cucumber/wire/connection.rb0000644000004100000410000000345212744237615022335 0ustar www-datawww-datarequire '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 wierd 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 => exception raise(ConnectionError, "Unable to contact the wire server at #{@config}. Is it up?") end end end end cucumber-wire-0.0.1/lib/cucumber/wire/add_hooks_filter.rb0000644000004100000410000000162712744237615023500 0ustar www-datawww-datamodule 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? Cucumber::Hooks.before_hook(test_case.source, Core::Ast::Location.new('TODO:wire')) do connections.begin_scenario(test_case) end end def after_hook(test_case) Cucumber::Hooks.after_hook(test_case.source, Core::Ast::Location.new('TODO:wire')) do connections.end_scenario(test_case) end end end end end cucumber-wire-0.0.1/lib/cucumber/wire/exception.rb0000644000004100000410000000151712744237615022174 0ustar www-datawww-datamodule Cucumber module Wire # Proxy for an exception that occured 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 if 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 end def backtrace @backtrace || super end end end end cucumber-wire-0.0.1/lib/cucumber/wire/plugin.rb0000644000004100000410000000176412744237615021500 0ustar www-datawww-datarequire 'cucumber/wire/connections' require 'cucumber/wire/add_hooks_filter' require 'cucumber/step_match_search' module Cucumber module Wire class Plugin attr_reader :config private :config def initialize(config) @config = config end def install connections = Connections.new(wire_files.map { |f| create_connection(f) }, @config) 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) 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-0.0.1/lib/cucumber/wire/snippet.rb0000644000004100000410000000155012744237615021655 0ustar www-datawww-datamodule 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-0.0.1/lib/cucumber/wire/request_handler.rb0000644000004100000410000000131712744237615023361 0ustar www-datawww-datamodule Cucumber module Wire class RequestHandler def initialize(connection) @connection = connection @message = underscore(self.class.name.split('::').last) 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-0.0.1/lib/cucumber/wire/step_definition.rb0000644000004100000410000000076712744237615023367 0ustar www-datawww-datarequire 'cucumber/core/ast/location' module Cucumber module Wire class StepDefinition attr_reader :regexp_source, :location def initialize(connection, data) @connection = connection @id = data['id'] @regexp_source = data['regexp'] || "Unknown" @location = Core::Ast::Location.from_file_colon_line(data['source'] || "unknown:0") end def invoke(args) @connection.invoke(@id, args) end end end end cucumber-wire-0.0.1/lib/cucumber/wire/data_packet.rb0000644000004100000410000000141312744237615022431 0ustar www-datawww-datarequire 'multi_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 = MultiJson.load(raw.strip) message = attributes[0] params = attributes[1] new(message, params) end end attr_reader :message, :params def initialize(message, params = nil) @message, @params = message, params end def to_json packet = [@message] packet << @params if @params MultiJson.dump(packet) end def handle_with(handler) handler.send("handle_#{@message}", @params) end end end end cucumber-wire-0.0.1/lib/cucumber/wire/version0000644000004100000410000000000612744237615021251 0ustar www-datawww-data0.0.1 cucumber-wire-0.0.1/README.md0000644000004100000410000000011112744237615015574 0ustar www-datawww-dataWork in progress: See https://github.com/cucumber/cucumber-ruby/pull/878