From 50dd4bb05a96580204671ef2306011757ff543f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Rasmusson?= Date: Tue, 31 Jul 2018 18:54:18 +0200 Subject: [PATCH] Update to use gherkin version 6. The feature file oriented formatters (like the pretty formatter) does not include the rule keyword in its output, but they will find and handle the scenarios within the rules. --- Gemfile | 4 + cucumber.gemspec | 2 +- features/docs/cli/i18n.feature | 2 +- features/docs/gherkin/background.feature | 4 +- .../expand_option_for_outlines.feature | 4 +- lib/cucumber/events/gherkin_source_parsed.rb | 5 +- lib/cucumber/formatter/ast_lookup.rb | 113 +++++++++++++----- lib/cucumber/formatter/json.rb | 10 +- lib/cucumber/formatter/pretty.rb | 24 ++-- lib/cucumber/gherkin/data_table_parser.rb | 14 ++- lib/cucumber/gherkin/steps_parser.rb | 15 ++- lib/cucumber/runtime/support_code.rb | 12 +- spec/cucumber/formatter/pretty_spec.rb | 60 ++++++++-- 13 files changed, 175 insertions(+), 94 deletions(-) diff --git a/Gemfile b/Gemfile index bba195e55e..78355c1431 100644 --- a/Gemfile +++ b/Gemfile @@ -17,3 +17,7 @@ elsif !ENV['CUCUMBER_USE_RELEASED_GEMS'] end gem 'cucumber-expressions', path: ENV['CUCUMBER_EXPRESSIONS_RUBY'] if ENV['CUCUMBER_EXPRESSIONS_RUBY'] + +gem 'gherkin', path: ENV['GHERKIN_RUBY'] if ENV['GHERKIN_RUBY'] + +gem 'cucumber-messages', path: ENV['CUCUMBER_MESSAGES_RUBY'] if ENV['CUCUMBER_MESSAGES_RUBY'] diff --git a/cucumber.gemspec b/cucumber.gemspec index 87aaa6043c..bccecb3159 100644 --- a/cucumber.gemspec +++ b/cucumber.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |s| s.add_dependency 'cucumber-expressions', '~> 6.0.1' s.add_dependency 'cucumber-wire', '~> 0.0.1' s.add_dependency 'diff-lcs', '~> 1.3' - s.add_dependency 'gherkin', '~> 5.1.0' + s.add_dependency 'gherkin', '~> 6.0' s.add_dependency 'multi_json', '>= 1.7.5', '< 2.0' s.add_dependency 'multi_test', '>= 0.1.2' diff --git a/features/docs/cli/i18n.feature b/features/docs/cli/i18n.feature index cfafa790eb..78e3a4d6d6 100644 --- a/features/docs/cli/i18n.feature +++ b/features/docs/cli/i18n.feature @@ -16,7 +16,7 @@ Feature: i18n """ | feature | "Funcionalidade", "Característica", "Caracteristica" | | background | "Contexto", "Cenário de Fundo", "Cenario de Fundo", "Fundo" | - | scenario | "Cenário", "Cenario" | + | scenario | "Exemplo", "Cenário", "Cenario" | | scenario_outline | "Esquema do Cenário", "Esquema do Cenario", "Delineação do Cenário", "Delineacao do Cenario" | | examples | "Exemplos", "Cenários", "Cenarios" | | given | "* ", "Dado ", "Dada ", "Dados ", "Dadas " | diff --git a/features/docs/gherkin/background.feature b/features/docs/gherkin/background.feature index 666c0c78f9..9b100178de 100644 --- a/features/docs/gherkin/background.feature +++ b/features/docs/gherkin/background.feature @@ -461,7 +461,7 @@ Feature: Background Examples: - Scenario: | 10 | + Example: | 10 | Then I should have '10' global cukes Scenario Outline: failing background @@ -469,7 +469,7 @@ Feature: Background Examples: - Scenario: | 10 | + Example: | 10 | And '10' global cukes FAIL (RuntimeError) ./features/step_definitions/cuke_steps.rb:8:in `/^'(.+)' global cukes$/' diff --git a/features/docs/gherkin/expand_option_for_outlines.feature b/features/docs/gherkin/expand_option_for_outlines.feature index 25154a2935..3db4e92c86 100644 --- a/features/docs/gherkin/expand_option_for_outlines.feature +++ b/features/docs/gherkin/expand_option_for_outlines.feature @@ -32,12 +32,12 @@ Feature: Scenario outlines --expand option Examples: - Scenario: | blue | blue | right | + Example: | blue | blue | right | Given the secret code is blue When I guess blue Then I am right - Scenario: | red | blue | wrong | + Example: | red | blue | wrong | Given the secret code is red When I guess blue Then I am wrong diff --git a/lib/cucumber/events/gherkin_source_parsed.rb b/lib/cucumber/events/gherkin_source_parsed.rb index 81b16f2638..deba2ac1c8 100644 --- a/lib/cucumber/events/gherkin_source_parsed.rb +++ b/lib/cucumber/events/gherkin_source_parsed.rb @@ -3,10 +3,7 @@ module Cucumber module Events # Fired after we've parsed the contents of a feature file - class GherkinSourceParsed < Core::Event.new(:uri, :gherkin_document) - # The uri of the file - attr_reader :uri - + class GherkinSourceParsed < Core::Event.new(:gherkin_document) # The Gherkin Ast attr_reader :gherkin_document end diff --git a/lib/cucumber/formatter/ast_lookup.rb b/lib/cucumber/formatter/ast_lookup.rb index b51fcd7d77..9ed2fbea1f 100644 --- a/lib/cucumber/formatter/ast_lookup.rb +++ b/lib/cucumber/formatter/ast_lookup.rb @@ -12,7 +12,7 @@ def initialize(config) end def on_gherkin_source_parsed(event) - @gherkin_documents[event.uri] = event.gherkin_document + @gherkin_documents[event.gherkin_document[:uri]] = event.gherkin_document end def gherkin_document(uri) @@ -21,13 +21,13 @@ def gherkin_document(uri) def scenario_source(test_case) uri = test_case.location.file - @test_case_lookups[uri] ||= create_test_case_lookup(gherkin_document(uri)) + @test_case_lookups[uri] ||= TestCaseLookupBuilder.new(gherkin_document(uri)).lookup_hash @test_case_lookups[uri][test_case.location.lines.max] end def step_source(test_step) uri = test_step.location.file - @test_step_lookups[uri] ||= create_test_step_lookup(gherkin_document(uri)) + @test_step_lookups[uri] ||= TestStepLookupBuilder.new(gherkin_document(uri)).lookup_hash @test_step_lookups[uri][test_step.location.lines.min] end @@ -61,52 +61,99 @@ def snippet_step_keyword(test_step) private def step_keyword_lookup(uri) - @step_keyword_lookups[uri] ||= create_keyword_lookup(gherkin_document(uri)) + @step_keyword_lookups[uri] ||= KeywordLookupBuilder.new(gherkin_document(uri)).lookup_hash end - def create_test_case_lookup(gherkin_document) - feature = gherkin_document[:feature] - lookup_hash = {} - feature[:children].each do |child| - if child[:type] == :Scenario - lookup_hash[child[:location][:line]] = ScenarioSource.new(:Scenario, child) - elsif child[:type] == :ScenarioOutline - child[:examples].each do |examples| - examples[:tableBody].each do |row| - lookup_hash[row[:location][:line]] = ScenarioOutlineSource.new(:ScenarioOutline, child, examples, row) + class TestCaseLookupBuilder + attr_reader :lookup_hash + + def initialize(gherkin_document) + @lookup_hash = {} + process_scenario_container(gherkin_document[:feature]) + end + + private + + def process_scenario_container(container) + container[:children].each do |child| + if !child[:rule].nil? + process_scenario_container(child[:rule]) + elsif !child[:scenario].nil? + if child[:scenario][:examples].empty? + @lookup_hash[child[:scenario][:location][:line]] = ScenarioSource.new(:Scenario, child[:scenario]) + + else + child[:scenario][:examples].each do |examples| + examples[:table_body].each do |row| + @lookup_hash[row[:location][:line]] = ScenarioOutlineSource.new(:ScenarioOutline, child[:scenario], examples, row) + end + end end end end end - lookup_hash end - def create_test_step_lookup(gherkin_document) - feature = gherkin_document[:feature] - lookup_hash = {} - feature[:children].each do |child| - child[:steps].each do |step| - lookup_hash[step[:location][:line]] = StepSource.new(:Step, step) + class TestStepLookupBuilder + attr_reader :lookup_hash + + def initialize(gherkin_document) + @lookup_hash = {} + process_scenario_container(gherkin_document[:feature]) + end + + private + + def process_scenario_container(container) + container[:children].each do |child| + if !child[:rule].nil? + process_scenario_container(child[:rule]) + elsif !child[:scenario].nil? + child[:scenario][:steps].each do |step| + @lookup_hash[step[:location][:line]] = StepSource.new(:Step, step) + end + elsif !child[:background].nil? + child[:background][:steps].each do |step| + @lookup_hash[step[:location][:line]] = StepSource.new(:Step, step) + end + end end end - lookup_hash end KeywordSearchNode = Struct.new(:keyword, :previous_node) - def create_keyword_lookup(gherkin_document) - lookup = {} - original_previous_node = nil - gherkin_document[:feature][:children].each do |child| - previous_node = original_previous_node - child[:steps].each do |step| - node = KeywordSearchNode.new(step[:keyword], previous_node) - lookup[step[:location][:line]] = node - previous_node = node + class KeywordLookupBuilder + attr_reader :lookup_hash + + def initialize(gherkin_document) + @lookup_hash = {} + process_scenario_container(gherkin_document[:feature], nil) + end + + private + + def process_scenario_container(container, original_previous_node) + container[:children].each do |child| + previous_node = original_previous_node + if !child[:rule].nil? + process_scenario_container(child[:rule], original_previous_node) + elsif !child[:scenario].nil? + child[:scenario][:steps].each do |step| + node = KeywordSearchNode.new(step[:keyword], previous_node) + @lookup_hash[step[:location][:line]] = node + previous_node = node + end + elsif !child[:background].nil? + child[:background][:steps].each do |step| + node = KeywordSearchNode.new(step[:keyword], previous_node) + @lookup_hash[step[:location][:line]] = node + previous_node = node + original_previous_node = previous_node + end + end end - original_previous_node = previous_node if child[:type] == :Background end - lookup end end end diff --git a/lib/cucumber/formatter/json.rb b/lib/cucumber/formatter/json.rb index 420d5764ce..81c34b0ae5 100644 --- a/lib/cucumber/formatter/json.rb +++ b/lib/cucumber/formatter/json.rb @@ -167,10 +167,8 @@ def create_step_hash(test_step) name: test_step.text, line: test_step.location.lines.min } - unless step_source[:argument].nil? - step_hash[:doc_string] = create_doc_string_hash(step_source[:argument]) if step_source[:argument][:type] == :DocString - step_hash[:rows] = create_data_table_value(step_source[:argument]) if step_source[:argument][:type] == :DataTable - end + step_hash[:doc_string] = create_doc_string_hash(step_source[:doc_string]) unless step_source[:doc_string].nil? + step_hash[:rows] = create_data_table_value(step_source[:data_table]) unless step_source[:data_table].nil? step_hash end @@ -234,7 +232,7 @@ def initialize(test_case, ast_lookup) uri = test_case.location.file feature = ast_lookup.gherkin_document(uri)[:feature] feature(feature, uri) - background(feature[:children].first) if feature[:children].first[:type] == :Background + background(feature[:children].first[:background]) unless feature[:children].first[:background].nil? scenario(ast_lookup.scenario_source(test_case), test_case) end @@ -300,7 +298,7 @@ def create_id_from_scenario_source(scenario_source) end def calculate_row_number(scenario_source) - scenario_source.examples[:tableBody].each_with_index do |row, index| + scenario_source.examples[:table_body].each_with_index do |row, index| return index + 2 if row == scenario_source.row end end diff --git a/lib/cucumber/formatter/pretty.rb b/lib/cucumber/formatter/pretty.rb index a95cc68b3d..bf503622d6 100644 --- a/lib/cucumber/formatter/pretty.rb +++ b/lib/cucumber/formatter/pretty.rb @@ -192,7 +192,7 @@ def same_feature_as_previous_test_case?(location) def feature_has_background? feature_children = gherkin_document[:feature][:children] return false if feature_children.empty? - feature_children[0][:type] == :Background + !feature_children.first[:background].nil? end def print_step_header(test_case) @@ -296,7 +296,7 @@ def print_description(description) def print_background_data @io.puts - background = gherkin_document[:feature][:children][0] + background = gherkin_document[:feature][:children].first[:background] @source_indent = calculate_source_indent_for_ast_node(background) if options[:source] print_comments(background[:location][:line], 2) print_background_line(background) @@ -355,12 +355,10 @@ def gherkin_document def print_multiline_argument(test_step, result, indent) step = step_source(test_step).step - multiline_arg = step[:argument] - return unless multiline_arg - if multiline_arg[:type] == :DocString - print_doc_string(multiline_arg[:content], result.to_sym, indent) - elsif multiline_arg[:type] == :DataTable - print_data_table(step[:argument], result.to_sym, indent) + if !step[:doc_string].nil? + print_doc_string(step[:doc_string][:content], result.to_sym, indent) + elsif !step[:data_table].nil? + print_data_table(step[:data_table], result.to_sym, indent) end end @@ -371,7 +369,7 @@ def print_data_table(data_table, status, indent) end end - def print_outline_data(scenario_outline) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity + def print_outline_data(scenario_outline) # rubocop:disable Metrics/AbcSize print_comments(scenario_outline[:location][:line], 2) print_tags(scenario_outline[:tags], 2) @source_indent = calculate_source_indent_for_ast_node(scenario_outline) if options[:source] @@ -387,8 +385,8 @@ def print_outline_data(scenario_outline) # rubocop:disable Metrics/AbcSize, Metr end @io.puts next if options[:no_multiline] - print_doc_string(step[:argument][:content], :skipped, 6) if step[:argument] && step[:argument][:type] == :DocString - print_data_table(step[:argument], :skipped, 6) if step[:argument] && step[:argument][:type] == :DataTable + print_doc_string(step[:doc_string][:content], :skipped, 6) unless step[:doc_string].nil? + print_data_table(step[:data_table], :skipped, 6) unless step[:data_table].nil? end @io.flush end @@ -405,8 +403,8 @@ def print_examples_data(examples) print_keyword_name(examples[:keyword], examples[:name], 4) print_description(examples[:description]) unless options[:expand] - print_comments(examples[:tableHeader][:location][:line], 6) - @io.puts(gherkin_source.split("\n")[examples[:tableHeader][:location][:line] - 1].strip.indent(6)) + print_comments(examples[:table_header][:location][:line], 6) + @io.puts(gherkin_source.split("\n")[examples[:table_header][:location][:line] - 1].strip.indent(6)) end @io.flush end diff --git a/lib/cucumber/gherkin/data_table_parser.rb b/lib/cucumber/gherkin/data_table_parser.rb index b276e6c6f4..d63fcc5204 100644 --- a/lib/cucumber/gherkin/data_table_parser.rb +++ b/lib/cucumber/gherkin/data_table_parser.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -require 'gherkin/token_scanner' -require 'gherkin/parser' +require 'gherkin/gherkin' require 'gherkin/dialect' module Cucumber @@ -12,11 +11,14 @@ def initialize(builder) end def parse(text) - token_scanner = ::Gherkin::TokenScanner.new(feature_header + text) - parser = ::Gherkin::Parser.new - gherkin_document = parser.parse(token_scanner) + gherkin_document = nil + messages = ::Gherkin::Gherkin.from_source('dummy', feature_header + text, include_source: false, include_pickles: false) + messages.each do |message| + gherkin_document = message.gherkinDocument.to_hash unless message.gherkinDocument.nil? + end - gherkin_document[:feature][:children][0][:steps][0][:argument][:rows].each do |row| + return if gherkin_document.nil? + gherkin_document[:feature][:children][0][:scenario][:steps][0][:data_table][:rows].each do |row| @builder.row(row[:cells].map { |cell| cell[:value] }) end end diff --git a/lib/cucumber/gherkin/steps_parser.rb b/lib/cucumber/gherkin/steps_parser.rb index 09531ef59b..071a2eb350 100644 --- a/lib/cucumber/gherkin/steps_parser.rb +++ b/lib/cucumber/gherkin/steps_parser.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require 'gherkin/token_scanner' -require 'gherkin/token_matcher' -require 'gherkin/parser' +require 'gherkin/gherkin' require 'gherkin/dialect' module Cucumber @@ -15,12 +13,13 @@ def initialize(builder, language) def parse(text) dialect = ::Gherkin::Dialect.for(@language) - token_matcher = ::Gherkin::TokenMatcher.new(@language) - token_scanner = ::Gherkin::TokenScanner.new(feature_header(dialect) + text) - parser = ::Gherkin::Parser.new - gherkin_document = parser.parse(token_scanner, token_matcher) + gherkin_document = nil + messages = ::Gherkin::Gherkin.from_source('dummy', feature_header(dialect) + text, default_dialect: @language, include_source: false, include_pickles: false) + messages.each do |message| + gherkin_document = message.gherkinDocument.to_hash unless message.gherkinDocument.nil? + end - @builder.steps(gherkin_document[:feature][:children][0][:steps]) + @builder.steps(gherkin_document[:feature][:children][0][:scenario][:steps]) end def feature_header(dialect) diff --git a/lib/cucumber/runtime/support_code.rb b/lib/cucumber/runtime/support_code.rb index 33029be516..f9c3a88abb 100644 --- a/lib/cucumber/runtime/support_code.rb +++ b/lib/cucumber/runtime/support_code.rb @@ -27,14 +27,10 @@ def step(step) end def multiline_arg(step, location) - argument = step[:argument] - - if argument - if argument[:type] == :DocString - MultilineArgument.from(argument[:content], location, argument[:content_type]) - else - MultilineArgument::DataTable.from(argument[:rows].map { |row| row[:cells].map { |cell| cell[:value] } }) - end + if !step[:doc_string].nil? + MultilineArgument.from(step[:doc_string][:content], location, step[:doc_string][:content_type]) + elsif !step[:data_table].nil? + MultilineArgument::DataTable.from(step[:data_table][:rows].map { |row| row[:cells].map { |cell| cell[:value] } }) else MultilineArgument.from(nil) end diff --git a/spec/cucumber/formatter/pretty_spec.rb b/spec/cucumber/formatter/pretty_spec.rb index 5eb7646eca..a17406cc0b 100644 --- a/spec/cucumber/formatter/pretty_spec.rb +++ b/spec/cucumber/formatter/pretty_spec.rb @@ -452,6 +452,46 @@ module Formatter #comment11 | dummy | #comment12 +OUTPUT + end + end + + describe 'with the rule keyword' do + define_feature <<-FEATURE + Feature: Some rules + + Background: FB + Given fb + + Rule: A + The rule A description + + Background: AB + Given ab + + Example: Example A + Given a + + Rule: B + The rule B description + + Example: Example B + Given b + FEATURE + + it 'ignores the rule keyword' do + expect(@out.string).to include < # spec.feature:4 Examples: Fruit - Scenario: | apples | # spec.feature:8 + Example: | apples | # spec.feature:8 Given there are apples # spec.feature:8 - Scenario: | bananas | # spec.feature:9 + Example: | bananas | # spec.feature:9 Given there are bananas # spec.feature:9 Examples: Vegetables - Scenario: | broccoli | # spec.feature:12 + Example: | broccoli | # spec.feature:12 Given there are broccoli # spec.feature:12 - Scenario: | carrots | # spec.feature:13 + Example: | carrots | # spec.feature:13 Given there are carrots # spec.feature:13 OUTPUT lines.split("\n").each do |line| @@ -785,8 +825,8 @@ module Formatter it 'the scenario line controls the source indentation' do lines = <<-OUTPUT Examples: - Scenario: | Hominidae | Very long cell content | # spec.feature:8 - Given there are Hominidae # spec.feature:8 + Example: | Hominidae | Very long cell content | # spec.feature:8 + Given there are Hominidae # spec.feature:8 OUTPUT lines.split("\n").each do |line|