cucumber-11.0.0/0000755000004100000410000000000015171146601013430 5ustar www-datawww-datacucumber-11.0.0/bin/0000755000004100000410000000000015171146601014200 5ustar www-datawww-datacucumber-11.0.0/bin/cucumber0000755000004100000410000000054515171146601015737 0ustar www-datawww-data#!/usr/bin/env ruby file_name = File.dirname(__FILE__) + '/../lib' $LOAD_PATH.unshift(file_name) unless $LOAD_PATH.include?(file_name) require 'simplecov_setup' require 'cucumber/rspec/disable_option_parser' require 'cucumber/cli/main' # The dup is to keep ARGV intact, so that tools like ruby-debug can respawn. Cucumber::Cli::Main.new(ARGV.dup).execute! cucumber-11.0.0/cucumber.gemspec0000644000004100000410000002225515171146601016610 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: cucumber 11.0.0 ruby lib Gem::Specification.new do |s| s.name = "cucumber".freeze s.version = "11.0.0".freeze s.required_rubygems_version = Gem::Requirement.new(">= 3.2.8".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "bug_tracker_uri" => "https://github.com/cucumber/cucumber-ruby/issues", "changelog_uri" => "https://github.com/cucumber/cucumber-ruby/blob/main/CHANGELOG.md", "documentation_uri" => "https://www.rubydoc.info/github/cucumber/cucumber-ruby/", "funding_uri" => "https://opencollective.com/cucumber", "mailing_list_uri" => "https://groups.google.com/forum/#!forum/cukes", "source_code_uri" => "https://github.com/cucumber/cucumber-ruby" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Aslak Helles\u00F8y".freeze, "Matt Wynne".freeze, "Steve Tooke".freeze, "Luke Hill".freeze] s.date = "2026-04-14" s.description = "Behaviour Driven Development with elegance and joy".freeze s.email = "cukes@googlegroups.com".freeze s.executables = ["cucumber".freeze] s.files = ["LICENSE".freeze, "README.md".freeze, "VERSION".freeze, "bin/cucumber".freeze, "lib/cucumber.rb".freeze, "lib/cucumber/cli.rb".freeze, "lib/cucumber/cli/configuration.rb".freeze, "lib/cucumber/cli/main.rb".freeze, "lib/cucumber/cli/options.rb".freeze, "lib/cucumber/cli/profile_loader.rb".freeze, "lib/cucumber/cli/rerun_file.rb".freeze, "lib/cucumber/configuration.rb".freeze, "lib/cucumber/constantize.rb".freeze, "lib/cucumber/encoding.rb".freeze, "lib/cucumber/errors.rb".freeze, "lib/cucumber/events.rb".freeze, "lib/cucumber/events/envelope.rb".freeze, "lib/cucumber/events/gherkin_source_parsed.rb".freeze, "lib/cucumber/events/gherkin_source_read.rb".freeze, "lib/cucumber/events/hook_test_step_created.rb".freeze, "lib/cucumber/events/step_activated.rb".freeze, "lib/cucumber/events/step_definition_registered.rb".freeze, "lib/cucumber/events/test_case_created.rb".freeze, "lib/cucumber/events/test_case_finished.rb".freeze, "lib/cucumber/events/test_case_ready.rb".freeze, "lib/cucumber/events/test_case_started.rb".freeze, "lib/cucumber/events/test_run_finished.rb".freeze, "lib/cucumber/events/test_run_hook_finished.rb".freeze, "lib/cucumber/events/test_run_hook_started.rb".freeze, "lib/cucumber/events/test_run_started.rb".freeze, "lib/cucumber/events/test_step_created.rb".freeze, "lib/cucumber/events/test_step_finished.rb".freeze, "lib/cucumber/events/test_step_started.rb".freeze, "lib/cucumber/events/undefined_parameter_type.rb".freeze, "lib/cucumber/file_specs.rb".freeze, "lib/cucumber/filters.rb".freeze, "lib/cucumber/filters/activate_steps.rb".freeze, "lib/cucumber/filters/apply_after_hooks.rb".freeze, "lib/cucumber/filters/apply_after_step_hooks.rb".freeze, "lib/cucumber/filters/apply_around_hooks.rb".freeze, "lib/cucumber/filters/apply_before_hooks.rb".freeze, "lib/cucumber/filters/broadcast_test_case_ready_event.rb".freeze, "lib/cucumber/filters/broadcast_test_run_started_event.rb".freeze, "lib/cucumber/filters/gated_receiver.rb".freeze, "lib/cucumber/filters/prepare_world.rb".freeze, "lib/cucumber/filters/quit.rb".freeze, "lib/cucumber/filters/randomizer.rb".freeze, "lib/cucumber/filters/retry.rb".freeze, "lib/cucumber/filters/reverser.rb".freeze, "lib/cucumber/filters/tag_limits.rb".freeze, "lib/cucumber/filters/tag_limits/test_case_index.rb".freeze, "lib/cucumber/filters/tag_limits/verifier.rb".freeze, "lib/cucumber/formatter.rb".freeze, "lib/cucumber/formatter/ansicolor.rb".freeze, "lib/cucumber/formatter/ast_lookup.rb".freeze, "lib/cucumber/formatter/backtrace_filter.rb".freeze, "lib/cucumber/formatter/console.rb".freeze, "lib/cucumber/formatter/console_counts.rb".freeze, "lib/cucumber/formatter/console_issues.rb".freeze, "lib/cucumber/formatter/curl_option_parser.rb".freeze, "lib/cucumber/formatter/duration.rb".freeze, "lib/cucumber/formatter/duration_extractor.rb".freeze, "lib/cucumber/formatter/errors.rb".freeze, "lib/cucumber/formatter/fail_fast.rb".freeze, "lib/cucumber/formatter/fanout.rb".freeze, "lib/cucumber/formatter/html.rb".freeze, "lib/cucumber/formatter/http_io.rb".freeze, "lib/cucumber/formatter/ignore_missing_messages.rb".freeze, "lib/cucumber/formatter/interceptor.rb".freeze, "lib/cucumber/formatter/io.rb".freeze, "lib/cucumber/formatter/io_http_buffer.rb".freeze, "lib/cucumber/formatter/json.rb".freeze, "lib/cucumber/formatter/junit.rb".freeze, "lib/cucumber/formatter/message.rb".freeze, "lib/cucumber/formatter/message_builder.rb".freeze, "lib/cucumber/formatter/pretty.rb".freeze, "lib/cucumber/formatter/progress.rb".freeze, "lib/cucumber/formatter/publish_banner_printer.rb".freeze, "lib/cucumber/formatter/query/hook_by_test_step.rb".freeze, "lib/cucumber/formatter/query/pickle_by_test.rb".freeze, "lib/cucumber/formatter/query/step_definitions_by_test_step.rb".freeze, "lib/cucumber/formatter/query/test_case_started_by_test_case.rb".freeze, "lib/cucumber/formatter/rerun.rb".freeze, "lib/cucumber/formatter/stepdefs.rb".freeze, "lib/cucumber/formatter/steps.rb".freeze, "lib/cucumber/formatter/summary.rb".freeze, "lib/cucumber/formatter/unicode.rb".freeze, "lib/cucumber/formatter/url_reporter.rb".freeze, "lib/cucumber/formatter/usage.rb".freeze, "lib/cucumber/gherkin/data_table_parser.rb".freeze, "lib/cucumber/gherkin/formatter/ansi_escapes.rb".freeze, "lib/cucumber/gherkin/formatter/escaping.rb".freeze, "lib/cucumber/gherkin/i18n.rb".freeze, "lib/cucumber/gherkin/steps_parser.rb".freeze, "lib/cucumber/glue.rb".freeze, "lib/cucumber/glue/dsl.rb".freeze, "lib/cucumber/glue/hook.rb".freeze, "lib/cucumber/glue/invoke_in_world.rb".freeze, "lib/cucumber/glue/proto_world.rb".freeze, "lib/cucumber/glue/registry_and_more.rb".freeze, "lib/cucumber/glue/registry_wrapper.rb".freeze, "lib/cucumber/glue/snippet.rb".freeze, "lib/cucumber/glue/step_definition.rb".freeze, "lib/cucumber/glue/world_factory.rb".freeze, "lib/cucumber/hooks.rb".freeze, "lib/cucumber/load_path.rb".freeze, "lib/cucumber/multiline_argument.rb".freeze, "lib/cucumber/multiline_argument/data_table.rb".freeze, "lib/cucumber/multiline_argument/data_table/diff_matrices.rb".freeze, "lib/cucumber/multiline_argument/doc_string.rb".freeze, "lib/cucumber/platform.rb".freeze, "lib/cucumber/project_initializer.rb".freeze, "lib/cucumber/query.rb".freeze, "lib/cucumber/rake.rb".freeze, "lib/cucumber/rake/forked_cucumber_runner.rb".freeze, "lib/cucumber/rake/in_process_cucumber_runner.rb".freeze, "lib/cucumber/rake/task.rb".freeze, "lib/cucumber/repository.rb".freeze, "lib/cucumber/rspec/disable_option_parser.rb".freeze, "lib/cucumber/rspec/doubles.rb".freeze, "lib/cucumber/running_test_case.rb".freeze, "lib/cucumber/runtime.rb".freeze, "lib/cucumber/runtime/after_hooks.rb".freeze, "lib/cucumber/runtime/before_hooks.rb".freeze, "lib/cucumber/runtime/for_programming_languages.rb".freeze, "lib/cucumber/runtime/meta_message_builder.rb".freeze, "lib/cucumber/runtime/step_hooks.rb".freeze, "lib/cucumber/runtime/support_code.rb".freeze, "lib/cucumber/runtime/user_interface.rb".freeze, "lib/cucumber/step_definitions.rb".freeze, "lib/cucumber/step_match.rb".freeze, "lib/cucumber/step_match_search.rb".freeze, "lib/cucumber/term/ansicolor.rb".freeze, "lib/cucumber/term/banner.rb".freeze, "lib/cucumber/unit.rb".freeze, "lib/simplecov_setup.rb".freeze] s.homepage = "https://cucumber.io/".freeze s.licenses = ["MIT".freeze] s.rdoc_options = ["--charset=UTF-8".freeze] s.required_ruby_version = Gem::Requirement.new(">= 3.2".freeze) s.rubygems_version = "3.4.20".freeze s.summary = "cucumber-11.0.0".freeze s.specification_version = 4 s.add_runtime_dependency(%q.freeze, ["~> 0.2".freeze]) s.add_runtime_dependency(%q.freeze, ["~> 3.2".freeze]) s.add_runtime_dependency(%q.freeze, ["> 9".freeze, "< 12".freeze]) s.add_development_dependency(%q.freeze, ["~> 22.0".freeze]) s.add_runtime_dependency(%q.freeze, [">= 16.2.0".freeze, "< 17".freeze]) s.add_runtime_dependency(%q.freeze, ["> 17".freeze, "< 20".freeze]) s.add_runtime_dependency(%q.freeze, ["> 21".freeze, "< 24".freeze]) s.add_runtime_dependency(%q.freeze, ["~> 1.5".freeze]) s.add_runtime_dependency(%q.freeze, ["~> 1.6".freeze]) s.add_runtime_dependency(%q.freeze, ["~> 1.1".freeze]) s.add_runtime_dependency(%q.freeze, ["~> 1.1".freeze]) s.add_development_dependency(%q.freeze, ["~> 1.15".freeze]) s.add_development_dependency(%q.freeze, ["~> 13.2".freeze]) s.add_development_dependency(%q.freeze, ["~> 3.13".freeze]) s.add_development_dependency(%q.freeze, ["~> 1.85.1".freeze]) s.add_development_dependency(%q.freeze, ["~> 0.5.2".freeze]) s.add_development_dependency(%q.freeze, ["~> 0.6.0".freeze]) s.add_development_dependency(%q.freeze, ["~> 3.8.0".freeze]) s.add_development_dependency(%q.freeze, ["~> 0.22.0".freeze]) s.add_runtime_dependency(%q.freeze, ["~> 1.5".freeze]) s.add_development_dependency(%q.freeze, ["~> 1.8".freeze]) end cucumber-11.0.0/lib/0000755000004100000410000000000015171146601014176 5ustar www-datawww-datacucumber-11.0.0/lib/simplecov_setup.rb0000644000004100000410000000100015171146601017733 0ustar www-datawww-data# frozen_string_literal: true if ENV['SIMPLECOV'] begin # Suppress warnings in order not to pollute stdout which tests expectations rely on $VERBOSE = nil if defined?(JRUBY_VERSION) require 'simplecov' SimpleCov.root(File.expand_path("#{File.dirname(__FILE__)}/..")) SimpleCov.start do add_filter 'iso-8859-1_steps.rb' add_filter '.-ruby-core/' add_filter '/spec/' add_filter '/features/' end rescue LoadError warn('Unable to load simplecov') end end cucumber-11.0.0/lib/cucumber.rb0000644000004100000410000000142015171146601016325 0ustar www-datawww-data# frozen_string_literal: true require 'yaml' require 'cucumber/encoding' require 'cucumber/platform' require 'cucumber/formatter' require 'cucumber/runtime' require 'cucumber/cli/main' require 'cucumber/step_definitions' require 'cucumber/term/ansicolor' module Cucumber class << self attr_accessor :wants_to_quit def deprecate(message, method, remove_after_version) Kernel.warn( "\nWARNING: #{method} is deprecated" \ " and will be removed after version #{remove_after_version}. #{message}.\n" \ "(Called from #{caller(3..3).first})" ) end def logger return @log if @log @log = Logger.new($stdout).tap { |log| log.level = Logger::INFO } end def logger=(logger) @log = logger end end end cucumber-11.0.0/lib/cucumber/0000755000004100000410000000000015171146601016003 5ustar www-datawww-datacucumber-11.0.0/lib/cucumber/filters/0000755000004100000410000000000015171146601017453 5ustar www-datawww-datacucumber-11.0.0/lib/cucumber/filters/tag_limits/0000755000004100000410000000000015171146601021607 5ustar www-datawww-datacucumber-11.0.0/lib/cucumber/filters/tag_limits/verifier.rb0000644000004100000410000000254015171146601023750 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Filters class TagLimits class Verifier def initialize(tag_limits) @tag_limits = tag_limits end def verify!(test_case_index) breaches = collect_breaches(test_case_index) raise TagLimitExceededError.new(*breaches) unless breaches.empty? end private def collect_breaches(test_case_index) tag_limits.reduce([]) do |breaches, (tag_name, limit)| breaches.tap do |breach| breach << Breach.new(tag_name, limit, test_case_index.locations_of_tag_name(tag_name)) if test_case_index.count_by_tag_name(tag_name) > limit end end end attr_reader :tag_limits class Breach INDENT = (' ' * 2).freeze def initialize(tag_name, limit, locations) @tag_name = tag_name @limit = limit @locations = locations end def to_s [ "#{tag_name} occurred #{tag_count} times, but the limit was set to #{limit}", *locations.map(&:to_s) ].join("\n#{INDENT}") end private def tag_count locations.count end attr_reader :tag_name, :limit, :locations end end end end end cucumber-11.0.0/lib/cucumber/filters/tag_limits/test_case_index.rb0000644000004100000410000000114615171146601025277 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Filters class TagLimits class TestCaseIndex def initialize @index = Hash.new { |hash, key| hash[key] = [] } end def add(test_case) test_case.tags.map(&:name).each do |tag_name| index[tag_name] << test_case end end def count_by_tag_name(tag_name) index[tag_name].count end def locations_of_tag_name(tag_name) index[tag_name].map(&:location) end private attr_accessor :index end end end end cucumber-11.0.0/lib/cucumber/filters/apply_before_hooks.rb0000644000004100000410000000036315171146601023654 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Filters class ApplyBeforeHooks < Core::Filter.new(:hooks) def test_case(test_case) hooks.apply_before_hooks(test_case).describe_to(receiver) end end end end cucumber-11.0.0/lib/cucumber/filters/retry.rb0000644000004100000410000000231415171146601021145 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/filter' require 'cucumber/running_test_case' require 'cucumber/events' module Cucumber module Filters class Retry < Core::Filter.new(:configuration) def initialize(*_args) super @total_permanently_failed = 0 end def test_case(test_case) configuration.on_event(:test_case_finished) do |event| next unless retry_required?(test_case, event) test_case_counts[test_case] += 1 test_case.describe_to(receiver) end super end private def retry_required?(test_case, event) return false unless event.test_case == test_case return false unless event.result.failed? return false if @total_permanently_failed >= configuration.retry_total_tests retry_required = test_case_counts[test_case] < configuration.retry_attempts if retry_required # retry test true else # test failed after max. attempts @total_permanently_failed += 1 false end end def test_case_counts @test_case_counts ||= Hash.new { |h, k| h[k] = 0 } end end end end cucumber-11.0.0/lib/cucumber/filters/prepare_world.rb0000644000004100000410000000203715171146601022647 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/filter' require 'cucumber/core/test/location' require 'cucumber/running_test_case' module Cucumber module Filters class PrepareWorld < Core::Filter.new(:runtime) def test_case(test_case) CaseFilter.new(runtime, test_case).test_case.describe_to receiver end class CaseFilter def initialize(runtime, original_test_case) @runtime = runtime @original_test_case = original_test_case end def test_case init_scenario = Cucumber::Hooks.around_hook do |continue| @runtime.begin_scenario(scenario) continue.call @runtime.end_scenario(scenario) end around_hooks = [init_scenario] + @original_test_case.around_hooks @original_test_case.with_around_hooks(around_hooks).with_steps(@original_test_case.test_steps) end private def scenario @scenario ||= RunningTestCase.new(test_case) end end end end end cucumber-11.0.0/lib/cucumber/filters/reverser.rb0000644000004100000410000000132415171146601021635 0ustar www-datawww-data# frozen_string_literal: true require 'digest/sha2' module Cucumber module Filters # Reverses the order of test cases class Reverser attr_reader :seed private :seed def initialize(receiver = nil) @receiver = receiver @test_cases = [] end def test_case(test_case) @test_cases << test_case self end def done reversed_test_cases.each do |test_case| test_case.describe_to(@receiver) end @receiver.done self end def with_receiver(receiver) self.class.new(receiver) end private def reversed_test_cases @test_cases.reverse end end end end cucumber-11.0.0/lib/cucumber/filters/quit.rb0000644000004100000410000000066615171146601020772 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Filters class Quit def initialize(receiver = nil) @receiver = receiver end def test_case(test_case) test_case.describe_to @receiver unless Cucumber.wants_to_quit self end def done @receiver.done self end def with_receiver(receiver) self.class.new(receiver) end end end end cucumber-11.0.0/lib/cucumber/filters/broadcast_test_run_started_event.rb0000644000004100000410000000123015171146601026610 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Filters # Added at the end of the filter chain to broadcast a list of # all of the test cases that have made it through the filters. class BroadcastTestRunStartedEvent < Core::Filter.new(:config) def initialize(config, receiver = nil) super @test_cases = [] end def test_case(test_case) @test_cases << test_case self end def done config.notify :test_run_started, @test_cases @test_cases.map do |test_case| test_case.describe_to(@receiver) end super self end end end end cucumber-11.0.0/lib/cucumber/filters/randomizer.rb0000644000004100000410000000171615171146601022157 0ustar www-datawww-data# frozen_string_literal: true require 'digest/sha2' module Cucumber module Filters # Batches up all test cases, randomizes them, and then sends them on class Randomizer attr_reader :seed private :seed def initialize(seed, receiver = nil) @receiver = receiver @test_cases = [] @seed = seed end def test_case(test_case) @test_cases << test_case self end def done shuffled_test_cases.each do |test_case| test_case.describe_to(@receiver) end @receiver.done self end def with_receiver(receiver) self.class.new(seed, receiver) end private def shuffled_test_cases digester = Digest::SHA2.new(256) @test_cases.map.with_index .sort_by { |_, index| digester.digest((seed + index).to_s) } .map { |test_case, _| test_case } end end end end cucumber-11.0.0/lib/cucumber/filters/tag_limits.rb0000644000004100000410000000204215171146601022132 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/filters/gated_receiver' require 'cucumber/filters/tag_limits/test_case_index' require 'cucumber/filters/tag_limits/verifier' module Cucumber module Filters class TagLimitExceededError < StandardError def initialize(*limit_breaches) super(limit_breaches.map(&:to_s).join("\n")) end end class TagLimits def initialize(tag_limits, receiver = nil) @tag_limits = tag_limits @gated_receiver = GatedReceiver.new(receiver) @test_case_index = TestCaseIndex.new @verifier = Verifier.new(@tag_limits) end def test_case(test_case) gated_receiver.test_case(test_case) test_case_index.add(test_case) self end def done verifier.verify!(test_case_index) gated_receiver.done self end def with_receiver(receiver) self.class.new(@tag_limits, receiver) end private attr_reader :gated_receiver, :test_case_index, :verifier end end end cucumber-11.0.0/lib/cucumber/filters/broadcast_test_case_ready_event.rb0000644000004100000410000000043015171146601026356 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Filters class BroadcastTestCaseReadyEvent < Core::Filter.new(:config) def test_case(test_case) config.notify(:test_case_ready, test_case) test_case.describe_to(receiver) end end end end cucumber-11.0.0/lib/cucumber/filters/apply_after_step_hooks.rb0000644000004100000410000000055315171146601024547 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/filter' module Cucumber module Filters class ApplyAfterStepHooks < Core::Filter.new(:hooks) def test_case(test_case) test_steps = hooks.find_after_step_hooks(test_case).apply(test_case.test_steps) test_case.with_steps(test_steps).describe_to(receiver) end end end end cucumber-11.0.0/lib/cucumber/filters/gated_receiver.rb0000644000004100000410000000067515171146601022760 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Filters class GatedReceiver def initialize(receiver) @receiver = receiver @test_cases = [] end def test_case(test_case) @test_cases << test_case self end def done @test_cases.map do |test_case| test_case.describe_to(@receiver) end @receiver.done self end end end end cucumber-11.0.0/lib/cucumber/filters/apply_after_hooks.rb0000644000004100000410000000036115171146601023511 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Filters class ApplyAfterHooks < Core::Filter.new(:hooks) def test_case(test_case) hooks.apply_after_hooks(test_case).describe_to(receiver) end end end end cucumber-11.0.0/lib/cucumber/filters/activate_steps.rb0000644000004100000410000000402615171146601023020 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/filter' require 'cucumber/step_match' require 'cucumber/events' require 'cucumber/errors' module Cucumber module Filters class ActivateSteps < Core::Filter.new(:step_match_search, :configuration) def test_case(test_case) CaseFilter.new(test_case, step_match_search, configuration).test_case.describe_to receiver end class CaseFilter def initialize(test_case, step_match_search, configuration) @original_test_case = test_case @step_match_search = step_match_search @configuration = configuration end def test_case @original_test_case.with_steps(new_test_steps) end private def new_test_steps @original_test_case.test_steps.map(&method(:attempt_to_activate)) end def attempt_to_activate(test_step) find_match(test_step).activate(test_step) end def find_match(test_step) FindMatch.new(@step_match_search, @configuration, test_step).result end class FindMatch attr_reader :step_match_search, :configuration, :test_step private :step_match_search, :configuration, :test_step def initialize(step_match_search, configuration, test_step) @step_match_search = step_match_search @configuration = configuration @test_step = test_step end def result begin return NoStepMatch.new(test_step, test_step.text) unless matches.any? rescue Cucumber::Ambiguous => e return AmbiguousStepMatch.new(e) end configuration.notify :step_activated, test_step, match return SkippingStepMatch.new if configuration.dry_run? match end private def match matches.first end def matches step_match_search.call(test_step.text) end end end end end end cucumber-11.0.0/lib/cucumber/filters/apply_around_hooks.rb0000644000004100000410000000052315171146601023700 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/filter' module Cucumber module Filters class ApplyAroundHooks < Core::Filter.new(:hooks) def test_case(test_case) around_hooks = hooks.find_around_hooks(test_case) test_case.with_around_hooks(around_hooks).describe_to(receiver) end end end end cucumber-11.0.0/lib/cucumber/project_initializer.rb0000644000004100000410000000141315171146601022400 0ustar www-datawww-data# frozen_string_literal: true module Cucumber # Generates generic file structure for a cucumber project class ProjectInitializer def run create_directory('features') create_directory('features/step_definitions') create_directory('features/support') create_file('features/support/env.rb') end private def create_directory(directory_name) create_directory_or_file(directory_name, command: :mkdir_p) end def create_file(filename) create_directory_or_file(filename, command: :touch) end def create_directory_or_file(name, command:) if File.exist?(name) puts "#{name} already exists" else puts "creating #{name}" FileUtils.send(command, name) end end end end cucumber-11.0.0/lib/cucumber/term/0000755000004100000410000000000015171146601016752 5ustar www-datawww-datacucumber-11.0.0/lib/cucumber/term/banner.rb0000644000004100000410000000331415171146601020545 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/term/ansicolor' module Cucumber module Term module Banner def display_banner(lines, io, border_modifiers = nil) BannerMaker.new.display_banner(lines, io, border_modifiers || %i[green bold]) end class BannerMaker include Term::ANSIColor def display_banner(lines, io, border_modifiers) lines = lines.split("\n") if lines.is_a? String longest_line_length = lines.map { |line| line_length(line) }.max io.puts apply_modifiers("┌#{'─' * (longest_line_length + 2)}┐", border_modifiers) lines.map do |line| padding = ' ' * (longest_line_length - line_length(line)) io.puts "#{apply_modifiers('│', border_modifiers)} #{display_line(line)}#{padding} #{apply_modifiers('│', border_modifiers)}" end io.puts apply_modifiers("└#{'─' * (longest_line_length + 2)}┘", border_modifiers) end private def display_line(line) line.is_a?(Array) ? line.map { |span| display_span(span) }.join : line end def display_span(span) return apply_modifiers(span.shift, span) if span.is_a?(Array) span end def apply_modifiers(str, modifiers) display = str modifiers.each { |modifier| display = send(modifier, display) } display end def line_length(line) if line.is_a?(Array) line.map { |span| span_length(span) }.sum else line.length end end def span_length(span) span.is_a?(Array) ? span[0].length : span.length end end end end end cucumber-11.0.0/lib/cucumber/term/ansicolor.rb0000644000004100000410000000762115171146601021276 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Term # This module allows to colorize text using ANSI escape sequences. # # Include the module in your class and use its methods to colorize text. # # Example: # # require 'cucumber/term/ansicolor' # # class MyFormatter # include Cucumber::Term::ANSIColor # # def initialize(config) # $stdout.puts yellow("Initializing formatter") # $stdout.puts green("Coloring is active \o/") if Cucumber::Term::ANSIColor.coloring? # $stdout.puts grey("Feature path:") + blue(bold(config.feature_dirs)) # end # end # # To see what colours and effects are available, just run this in your shell: # # ruby -e "require 'rubygems'; require 'cucumber/term/ansicolor'; puts Cucumber::Term::ANSIColor.attributes" # module ANSIColor # :stopdoc: ATTRIBUTES = [ [:clear, 0], [:reset, 0], # synonym for :clear [:bold, 1], [:dark, 2], [:italic, 3], # not widely implemented [:underline, 4], [:underscore, 4], # synonym for :underline [:blink, 5], [:rapid_blink, 6], # not widely implemented [:negative, 7], # no reverse because of String#reverse [:concealed, 8], [:strikethrough, 9], # not widely implemented [:black, 30], [:red, 31], [:green, 32], [:yellow, 33], [:blue, 34], [:magenta, 35], [:cyan, 36], [:white, 37], [:grey, 90], [:on_black, 40], [:on_red, 41], [:on_green, 42], [:on_yellow, 43], [:on_blue, 44], [:on_magenta, 45], [:on_cyan, 46], [:on_white, 47] ].freeze ATTRIBUTE_NAMES = ATTRIBUTES.transpose.first # :startdoc: # Regular expression that is used to scan for ANSI-sequences while # uncoloring strings. COLORED_REGEXP = /\e\[(?:[34][0-7]|[0-9])?m/.freeze @coloring = true class << self # Turns the coloring on or off globally, so you can easily do # this for example: # Cucumber::Term::ANSIColor::coloring = $stdout.isatty attr_accessor :coloring # Returns true, if the coloring function of this module # is switched on, false otherwise. alias coloring? :coloring def included(klass) return unless klass == String ATTRIBUTES.delete(:clear) ATTRIBUTE_NAMES.delete(:clear) end end ATTRIBUTES.each do |color_name, color_code| define_method(color_name) do |text = nil, &block| if block colorize(block.call, color_code) elsif text colorize(text, color_code) elsif respond_to?(:to_str) colorize(to_str, color_code) else colorize(nil, color_code) # switch coloration on end end end # Returns an uncolored version of the string # ANSI-sequences are stripped from the string. def uncolored(text = nil) if block_given? uncolorize(yield) elsif text uncolorize(text) elsif respond_to?(:to_str) uncolorize(to_str) else '' end end # Returns an array of all Cucumber::Term::ANSIColor attributes as symbols. def attributes ATTRIBUTE_NAMES end private def colorize(text, color_code) return String.new(text || '') unless Cucumber::Term::ANSIColor.coloring? return "\e[#{color_code}m" unless text "\e[#{color_code}m#{text}\e[0m" end def uncolorize(string) string.gsub(COLORED_REGEXP, '') end end end end cucumber-11.0.0/lib/cucumber/unit.rb0000644000004100000410000000032515171146601017307 0ustar www-datawww-data# frozen_string_literal: true module Cucumber class Unit def initialize(step_collection) @step_collection = step_collection end def step_count @step_collection.length end end end cucumber-11.0.0/lib/cucumber/platform.rb0000644000004100000410000000120415171146601020151 0ustar www-datawww-data# frozen_string_literal: true require 'rbconfig' module Cucumber VERSION = File.read(File.expand_path('../../VERSION', __dir__)).strip BINARY = File.expand_path("#{File.dirname(__FILE__)}/../../bin/cucumber") RUBY_BINARY = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) JRUBY = defined?(JRUBY_VERSION) WINDOWS = RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ class << self attr_writer :use_full_backtrace def use_full_backtrace @use_full_backtrace ||= false end def file_mode(mode, encoding = 'UTF-8') "#{mode}:#{encoding}" end end end cucumber-11.0.0/lib/cucumber/constantize.rb0000644000004100000410000000210215171146601020664 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/platform' module Cucumber module Constantize # :nodoc: def constantize(camel_cased_word) try = 0 begin try += 1 names = camel_cased_word.split('::') names.shift if names.empty? || names.first.empty? constant = ::Object names.each do |name| constant = constantize_name(constant, name) end constant rescue NameError => e require underscore(camel_cased_word) retry if try < 2 raise e end end # Snagged from active_support 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 private def constantize_name(constant, name) if constant.const_defined?(name, false) constant.const_get(name, false) else constant.const_missing(name) end end end end cucumber-11.0.0/lib/cucumber/configuration.rb0000644000004100000410000001564615171146601021213 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/constantize' require 'cucumber/cli/rerun_file' require 'cucumber/events' require 'cucumber/messages' require 'cucumber/core/event_bus' require 'cucumber/core/test/result' require 'forwardable' require 'cucumber' module Cucumber # The base class for configuring settings for a Cucumber run. class Configuration include Constantize extend Forwardable def self.default new end def self.default_options { autoload_code_paths: %w[features/support features/step_definitions], filters: [], strict: Cucumber::Core::Test::Result::StrictConfiguration.new, require: [], dry_run: false, fail_fast: false, formats: [], excludes: [], tag_expressions: [], name_regexps: [], env_vars: {}, diff_enabled: true, snippets: true, source: true, duration: true, event_bus: Cucumber::Events.make_event_bus, retry_total: Float::INFINITY, tag_limits: {}, retry: 0 } end # Subscribe to an event. # # See {Cucumber::Events} for the list of possible events. # # @param event_id [Symbol, Class, String] Identifier for the type of event to subscribe to # @param handler_object [Object optional] an object to be called when the event occurs # @yield [Object] Block to be called when the event occurs # @method on_event def_instance_delegator :event_bus, :on, :on_event # @private def notify(message, *args) event_bus.send(message, *args) end def initialize(user_options = {}) @options = self.class.default_options.merge(Hash(user_options)) end def with_options(new_options) self.class.new(@options.merge(new_options)) end def out_stream @options[:out_stream] end def error_stream @options[:error_stream] end def randomize? @options[:order] == 'random' end def reverse_order? @options[:order] == 'reverse' end def seed @options[:seed] end def dry_run? @options[:dry_run] end def publish_enabled? @options[:publish_enabled] end def publish_quiet? @options[:publish_quiet] end def fail_fast? @options[:fail_fast] end def retry_attempts @options[:retry] end def retry_total_tests @options[:retry_total] end def guess? @options[:guess] end def strict @options[:strict] end def wip? @options[:wip] end def expand? @options[:expand] end def source? @options[:source] end def duration? @options[:duration] end def snippets? @options[:snippets] end def skip_profile_information? @options[:skip_profile_information] end def profiles @options[:profiles] || [] end def custom_profiles profiles - [@options[:default_profile]] end def paths @options[:paths] end def formats @options[:formats] end def autoload_code_paths @options[:autoload_code_paths] end def snippet_type @options[:snippet_type] end def feature_dirs dirs = paths.map { |f| File.directory?(f) ? f : File.dirname(f) }.uniq dirs.delete('.') unless paths.include?('.') with_default_features_path(dirs) end def tag_limits @options[:tag_limits] end def tag_expressions @options[:tag_expressions] end def name_regexps @options[:name_regexps] end def filters @options[:filters] end def feature_files potential_feature_files = with_default_features_path(paths).map do |path| path = path.tr('\\', '/') # In case we're on windows. Globs don't work with backslashes. path = path.chomp('/') # TODO: Move to using feature loading strategies stored in # options[:feature_loaders] if File.directory?(path) Dir["#{path}/**/*.feature"].sort elsif Cli::RerunFile.can_read?(path) Cli::RerunFile.new(path).features else path end end.flatten.uniq remove_excluded_files_from(potential_feature_files) potential_feature_files end def support_to_load support_files = all_files_to_load.select { |f| f =~ /\/support\// } # env_files are separated from other_files so we can ensure env files # load first. # env_files = support_files.select { |f| f =~ /\/support\/env\..*/ } other_files = support_files - env_files env_files.reverse + other_files.reverse end def all_files_to_load files = require_dirs.map do |path| path = path.tr('\\', '/') # In case we're on windows. Globs don't work with backslashes. path = path.gsub(/\/$/, '') # Strip trailing slash. File.directory?(path) ? Dir["#{path}/**/*"] : path end.flatten.uniq remove_excluded_files_from(files) files.select! { |f| File.file?(f) } files.reject! { |f| File.extname(f) == '.feature' } files.reject! { |f| f =~ /^http/ } files.sort end def step_defs_to_load all_files_to_load.reject { |f| f =~ /\/support\// } end def formatter_factories formats.map do |format, formatter_options, path_or_io| factory = formatter_class(format) yield factory, formatter_options, path_or_io rescue Exception => e raise e, "#{e.message}\nError creating formatter: #{format}", e.backtrace end end def formatter_class(format) if (builtin = Cli::Options::BUILTIN_FORMATS[format]) constantize(builtin[0]) else constantize(format) end end def to_hash @options end # An array of procs that can generate snippets for undefined steps. These procs may be called if a # formatter wants to display snippets to the user. # # Each proc should take the following arguments: # # - keyword # - step text # - multiline argument # - snippet type # def snippet_generators @options[:snippet_generators] ||= [] end def register_snippet_generator(generator) snippet_generators << generator self end def event_bus @options[:event_bus] end def id_generator @id_generator ||= Cucumber::Messages::Helpers::IdGenerator::UUID.new end private def default_features_paths ['features'] end def with_default_features_path(paths) return default_features_paths if paths.empty? paths end def remove_excluded_files_from(files) files.reject! { |path| @options[:excludes].detect { |pattern| path =~ pattern } } end def require_dirs if @options[:require].empty? default_features_paths + Dir['vendor/{gems,plugins}/*/cucumber'] else @options[:require] end end end end cucumber-11.0.0/lib/cucumber/hooks.rb0000644000004100000410000000477415171146601017467 0ustar www-datawww-data# frozen_string_literal: true require 'pathname' require 'cucumber/core/test/location' require 'cucumber/core/test/around_hook' module Cucumber # Hooks quack enough like `Cucumber::Core::Ast` source nodes that we can use them as # source for test steps module Hooks class << self def before_hook(id, location, &block) build_hook_step(id, location, block, BeforeHook, Core::Test::Action::Unskippable) end def after_hook(id, location, &block) build_hook_step(id, location, block, AfterHook, Core::Test::Action::Unskippable) end def after_step_hook(id, test_step, location, &block) raise ArgumentError if test_step.hook? build_hook_step(id, location, block, AfterStepHook, Core::Test::Action::Defined) end def around_hook(&block) Core::Test::AroundHook.new(&block) end private def build_hook_step(id, location, block, hook_type, action_type) action = action_type.new(location, &block) hook = hook_type.new(action.location) Core::Test::HookStep.new(id, hook.text, location, action) end end class AfterHook attr_reader :location def initialize(location) @location = location end def text 'After hook' end def to_s "#{text} at #{location}" end def match_locations?(queried_locations) queried_locations.any? { |other_location| other_location.match?(location) } end def describe_to(visitor, *args) visitor.after_hook(self, *args) end end class BeforeHook attr_reader :location def initialize(location) @location = location end def text 'Before hook' end def to_s "#{text} at #{location}" end def match_locations?(queried_locations) queried_locations.any? { |other_location| other_location.match?(location) } end def describe_to(visitor, *args) visitor.before_hook(self, *args) end end class AfterStepHook attr_reader :location def initialize(location) @location = location end def text 'AfterStep hook' end def to_s "#{text} at #{location}" end def match_locations?(queried_locations) queried_locations.any? { |other_location| other_location.match?(location) } end def describe_to(visitor, *args) visitor.after_step_hook(self, *args) end end end end cucumber-11.0.0/lib/cucumber/events/0000755000004100000410000000000015171146601017307 5ustar www-datawww-datacucumber-11.0.0/lib/cucumber/events/test_run_hook_finished.rb0000644000004100000410000000033415171146601024370 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events class TestRunHookFinished < Core::Event.new(:hook, :test_result) attr_reader :hook, :test_result end end end cucumber-11.0.0/lib/cucumber/events/test_case_created.rb0000644000004100000410000000042515171146601023276 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Event fired when a Test::Case is created from a Pickle class TestCaseCreated < Core::Event.new(:test_case, :pickle) attr_reader :test_case, :pickle end end end cucumber-11.0.0/lib/cucumber/events/test_step_started.rb0000644000004100000410000000053415171146601023376 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Signals that a {Cucumber::Core::Test::Step} is about to be executed class TestStepStarted < Core::Events::TestStepStarted # @return [Cucumber::Core::Test::Step] the test step to be executed attr_reader :test_step end end end cucumber-11.0.0/lib/cucumber/events/gherkin_source_read.rb0000644000004100000410000000053115171146601023635 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Fired after we've read in the contents of a feature file class GherkinSourceRead < Core::Event.new(:path, :body) # The path to the file attr_reader :path # The raw Gherkin source attr_reader :body end end end cucumber-11.0.0/lib/cucumber/events/test_run_started.rb0000644000004100000410000000057215171146601023231 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Event fired once all test cases have been filtered before # the first one is executed. class TestRunStarted < Core::Event.new(:test_cases) # @return [Array] the test cases to be executed attr_reader :test_cases end end end cucumber-11.0.0/lib/cucumber/events/test_run_hook_started.rb0000644000004100000410000000027715171146601024253 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events class TestRunHookStarted < Core::Event.new(:hook) attr_reader :hook end end end cucumber-11.0.0/lib/cucumber/events/envelope.rb0000644000004100000410000000100615171146601021446 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events class Envelope < Core::Event.new(:envelope) attr_reader :envelope def inspect "Envelope Event -> Message Type: #{type}}" end def to_s inspect end private def type envelope.instance_variables.detect { |message| !envelope.send(name_of(message)).nil? } end def name_of(message) message.to_s.delete('@') end end end end cucumber-11.0.0/lib/cucumber/events/step_activated.rb0000644000004100000410000000073115171146601022634 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Event fired when a step is activated class StepActivated < Core::Event.new(:test_step, :step_match) # The test step that was matched. # # @return [Cucumber::Core::Test::Step] attr_reader :test_step # Information about the matching definition. # # @return [Cucumber::StepMatch] attr_reader :step_match end end end cucumber-11.0.0/lib/cucumber/events/test_case_finished.rb0000644000004100000410000000072315171146601023461 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Signals that a {Cucumber::Core::Test::Case} has finished executing class TestCaseFinished < Core::Events::TestCaseFinished # @return [Cucumber::Core::Test::Case] that was executed attr_reader :test_case # @return [Cucumber::Core::Test::Result] the result of running the {Cucumber::Core::Test::Case} attr_reader :result end end end cucumber-11.0.0/lib/cucumber/events/undefined_parameter_type.rb0000644000004100000410000000034715171146601024702 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events class UndefinedParameterType < Core::Event.new(:type_name, :expression) attr_reader :type_name, :expression end end end cucumber-11.0.0/lib/cucumber/events/hook_test_step_created.rb0000644000004100000410000000041515171146601024355 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Event fired when a step is created from a hook class HookTestStepCreated < Core::Event.new(:test_step, :hook) attr_reader :test_step, :hook end end end cucumber-11.0.0/lib/cucumber/events/gherkin_source_parsed.rb0000644000004100000410000000045615171146601024206 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Fired after we've parsed the contents of a feature file class GherkinSourceParsed < Core::Event.new(:gherkin_document) # The Gherkin Ast attr_reader :gherkin_document end end end cucumber-11.0.0/lib/cucumber/events/test_case_started.rb0000644000004100000410000000053415171146601023336 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Signals that a {Cucumber::Core::Test::Case} is about to be executed class TestCaseStarted < Core::Events::TestCaseStarted # @return [Cucumber::Core::Test::Case] the test case to be executed attr_reader :test_case end end end cucumber-11.0.0/lib/cucumber/events/test_run_finished.rb0000644000004100000410000000040115171146601023343 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Event fired after all test cases have finished executing class TestRunFinished < Core::Event.new(:success) attr_reader :success end end end cucumber-11.0.0/lib/cucumber/events/test_case_ready.rb0000644000004100000410000000044515171146601022775 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Event fired when a Test::Case is ready to be ran (matching has been done, hooks added etc) class TestCaseReady < Core::Event.new(:test_case) attr_reader :test_case end end end cucumber-11.0.0/lib/cucumber/events/step_definition_registered.rb0000644000004100000410000000061015171146601025231 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Event fired after each step definition has been registered class StepDefinitionRegistered < Core::Event.new(:step_definition) # The step definition that was just registered. # # @return [RbSupport::RbStepDefinition] attr_reader :step_definition end end end cucumber-11.0.0/lib/cucumber/events/test_step_finished.rb0000644000004100000410000000074115171146601023521 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Signals that a {Cucumber::Core::Test::Step} has finished executing class TestStepFinished < Core::Events::TestStepFinished # @return [Cucumber::Core::Test::Step] the test step that was executed attr_reader :test_step # @return [Cucumber::Core::Test::Result] the result of running the {Cucumber::Core::Test::Step} attr_reader :result end end end cucumber-11.0.0/lib/cucumber/events/test_step_created.rb0000644000004100000410000000044115171146601023334 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Event fired when a TestStep is created from a PickleStep class TestStepCreated < Core::Event.new(:test_step, :pickle_step) attr_reader :test_step, :pickle_step end end end cucumber-11.0.0/lib/cucumber/runtime.rb0000644000004100000410000002033315171146601020014 0ustar www-datawww-data# frozen_string_literal: true require 'fileutils' require 'cucumber/configuration' require 'cucumber/load_path' require 'cucumber/formatter/duration' require 'cucumber/file_specs' require 'cucumber/filters' require 'cucumber/formatter/fanout' require 'cucumber/gherkin/i18n' require 'cucumber/glue/registry_wrapper' require 'cucumber/step_match_search' require 'cucumber/messages' require 'cucumber/runtime/meta_message_builder' require 'sys/uname' module Cucumber module FixRuby21Bug9285 def message String(super).gsub('@ rb_sysopen ', '') end end class FileException < RuntimeError attr_reader :path def initialize(original_exception, path) @path = path super(original_exception) end end class FileNotFoundException < FileException end class FeatureFolderNotFoundException < RuntimeError def initialize(path) @path = path super end def message "No such file or directory - #{@path}" end end require 'cucumber/core' require 'cucumber/runtime/user_interface' require 'cucumber/runtime/support_code' class Runtime attr_reader :results, :support_code, :configuration include Cucumber::Core include Formatter::Duration include Runtime::UserInterface def initialize(configuration = Configuration.default) @configuration = Configuration.new(configuration) @support_code = SupportCode.new(self, @configuration) end # Allows you to take an existing runtime and change its configuration def configure(new_configuration) @configuration = Configuration.new(new_configuration) @support_code.configure(@configuration) end def run! @configuration.notify :envelope, Cucumber::Messages::Envelope.new( meta: MetaMessageBuilder.build_meta_message ) load_step_definitions fire_install_plugin_hook fire_before_all_hook unless dry_run? # TODO: can we remove this state? self.visitor = report receiver = Test::Runner.new(@configuration.event_bus) compile features, receiver, filters, @configuration.event_bus @configuration.notify :test_run_finished, !failure? fire_after_all_hook unless dry_run? end def features_paths @configuration.paths end def dry_run? @configuration.dry_run? end def unmatched_step_definitions @support_code.unmatched_step_definitions end def begin_scenario(test_case) @support_code.fire_hook(:begin_scenario, test_case) end def end_scenario(_scenario) @support_code.fire_hook(:end_scenario) end # Returns Ast::DocString for +string_without_triple_quotes+. # def doc_string(string_without_triple_quotes, content_type = '', _line_offset = 0) Core::Test::DocString.new(string_without_triple_quotes, content_type) end def failure? if @configuration.wip? summary_report.test_cases.total_passed.positive? else !summary_report.ok?(strict: @configuration.strict) end end private def fire_install_plugin_hook # :nodoc: @support_code.fire_hook(:install_plugin, @configuration, registry_wrapper) end def fire_before_all_hook # :nodoc: @support_code.fire_hook(:before_all) end def fire_after_all_hook # :nodoc: @support_code.fire_hook(:after_all) end require 'cucumber/core/gherkin/document' def features @features ||= feature_files.map do |path| source = NormalisedEncodingFile.read(path) @configuration.notify :gherkin_source_read, path, source Cucumber::Core::Gherkin::Document.new(path, source) end end def feature_files filespecs.files end def filespecs @filespecs ||= FileSpecs.new(@configuration.feature_files) end class NormalisedEncodingFile COMMENT_OR_EMPTY_LINE_PATTERN = /^\s*#|^\s*$/.freeze # :nodoc: ENCODING_PATTERN = /^\s*#\s*encoding\s*:\s*([^\s]+)/.freeze # :nodoc: def self.read(path) new(path).read end def initialize(path) @file = File.new(path) set_encoding rescue Errno::EACCES => e raise FileNotFoundException.new(e, File.expand_path(path)) rescue Errno::ENOENT raise FeatureFolderNotFoundException, path end def read @file.read.encode('UTF-8') end private def set_encoding @file.each do |line| if ENCODING_PATTERN =~ line @file.set_encoding Regexp.last_match(1) break end break unless COMMENT_OR_EMPTY_LINE_PATTERN =~ line end @file.rewind end end require 'cucumber/formatter/ignore_missing_messages' require 'cucumber/formatter/fail_fast' require 'cucumber/formatter/publish_banner_printer' require 'cucumber/core/report/summary' def report return @report if @report reports = [summary_report] + formatters reports << fail_fast_report if @configuration.fail_fast? reports << publish_banner_printer unless @configuration.publish_quiet? @report ||= Formatter::Fanout.new(reports) end def summary_report @summary_report ||= Core::Report::Summary.new(@configuration.event_bus) end def fail_fast_report @fail_fast_report ||= Formatter::FailFast.new(@configuration) end def publish_banner_printer @publish_banner_printer ||= Formatter::PublishBannerPrinter.new(@configuration) end def formatters @formatters ||= @configuration.formatter_factories do |factory, formatter_options, path_or_io| create_formatter(factory, formatter_options, path_or_io) end end def create_formatter(factory, formatter_options, path_or_io) if accept_options?(factory) return factory.new(@configuration, formatter_options) if path_or_io.nil? factory.new(@configuration.with_options(out_stream: path_or_io), formatter_options) else return factory.new(@configuration) if path_or_io.nil? factory.new(@configuration.with_options(out_stream: path_or_io)) end end def accept_options?(factory) factory.instance_method(:initialize).arity > 1 end require 'cucumber/core/test/filters' def filters tag_expressions = @configuration.tag_expressions name_regexps = @configuration.name_regexps tag_limits = @configuration.tag_limits [].tap do |filters| filters << Filters::TagLimits.new(tag_limits) if tag_limits.any? filters << Cucumber::Core::Test::TagFilter.new(tag_expressions) filters << Cucumber::Core::Test::NameFilter.new(name_regexps) filters << Cucumber::Core::Test::LocationsFilter.new(filespecs.locations) filters << Filters::Randomizer.new(@configuration.seed) if @configuration.randomize? filters << Filters::Reverser.new if @configuration.reverse_order? # TODO: can we just use Glue::RegistryAndMore's step definitions directly? step_match_search = StepMatchSearch.new(@support_code.registry.method(:step_matches), @configuration) filters << Filters::ActivateSteps.new(step_match_search, @configuration) @configuration.filters.each { |filter| filters << filter } unless configuration.dry_run? filters << Filters::ApplyAfterStepHooks.new(@support_code) filters << Filters::ApplyBeforeHooks.new(@support_code) filters << Filters::ApplyAfterHooks.new(@support_code) filters << Filters::ApplyAroundHooks.new(@support_code) filters << Filters::BroadcastTestRunStartedEvent.new(@configuration) filters << Filters::Quit.new end filters << Filters::BroadcastTestCaseReadyEvent.new(@configuration) unless configuration.dry_run? filters << Filters::Retry.new(@configuration) # need to do this last so it becomes the first test step filters << Filters::PrepareWorld.new(self) end end end def load_step_definitions files = @configuration.support_to_load + @configuration.step_defs_to_load @support_code.load_files!(files) end def registry_wrapper Cucumber::Glue::RegistryWrapper.new(@support_code.registry) end def log Cucumber.logger end end end cucumber-11.0.0/lib/cucumber/repository.rb0000644000004100000410000001346015171146601020553 0ustar www-datawww-data# frozen_string_literal: true module Cucumber # In memory repository i.e. a thread based link to cucumber-query class Repository attr_accessor :meta, :test_run_started, :test_run_finished attr_reader :attachments_by_test_case_started_id, :attachments_by_test_run_hook_started_id, :hook_by_id, :pickle_by_id, :pickle_step_by_id, :step_by_id, :step_definition_by_id, :test_case_by_id, :test_case_started_by_id, :test_case_finished_by_test_case_started_id, :test_run_hook_started_by_id, :test_run_hook_finished_by_test_run_hook_started_id, :test_step_by_id, :test_steps_started_by_test_case_started_id, :test_steps_finished_by_test_case_started_id # TODO: Missing structs (2) # final Map> suggestionsByPickleStepId = new LinkedHashMap<>(); # final List undefinedParameterTypes = new ArrayList<>(); # TODO: Missing handlers # Source # def initialize @attachments_by_test_case_started_id = Hash.new { |hash, key| hash[key] = [] } @attachments_by_test_run_hook_started_id = Hash.new { |hash, key| hash[key] = [] } @hook_by_id = {} @pickle_by_id = {} @pickle_step_by_id = {} @step_by_id = {} @step_definition_by_id = {} @test_case_by_id = {} @test_case_started_by_id = {} @test_case_finished_by_test_case_started_id = {} @test_run_hook_started_by_id = {} @test_run_hook_finished_by_test_run_hook_started_id = {} @test_step_by_id = {} @test_steps_started_by_test_case_started_id = Hash.new { |hash, key| hash[key] = [] } @test_steps_finished_by_test_case_started_id = Hash.new { |hash, key| hash[key] = [] } end def update(envelope) return self.meta = envelope.meta if envelope.meta return self.test_run_started = envelope.test_run_started if envelope.test_run_started return self.test_run_finished = envelope.test_run_finished if envelope.test_run_finished return update_attachment(envelope.attachment) if envelope.attachment return update_gherkin_document(envelope.gherkin_document) if envelope.gherkin_document return update_hook(envelope.hook) if envelope.hook return update_pickle(envelope.pickle) if envelope.pickle return update_step_definition(envelope.step_definition) if envelope.step_definition return update_test_run_hook_started(envelope.test_run_hook_started) if envelope.test_run_hook_started return update_test_run_hook_finished(envelope.test_run_hook_finished) if envelope.test_run_hook_finished return update_test_case_started(envelope.test_case_started) if envelope.test_case_started return update_test_case_finished(envelope.test_case_finished) if envelope.test_case_finished return update_test_step_started(envelope.test_step_started) if envelope.test_step_started return update_test_step_finished(envelope.test_step_finished) if envelope.test_step_finished return update_test_case(envelope.test_case) if envelope.test_case nil end private def update_attachment(attachment) attachments_by_test_case_started_id[attachment.test_case_started_id] << attachment if attachment.test_case_started_id attachments_by_test_run_hook_started_id[attachment.test_run_hook_started_id] << attachment if attachment.test_run_hook_started_id end def update_feature(feature) feature.children.each do |feature_child| update_steps(feature_child.background.steps) if feature_child.background update_scenario(feature_child.scenario) if feature_child.scenario next unless feature_child.rule feature_child.rule.children.each do |rule_child| update_steps(rule_child.background.steps) if rule_child.background update_scenario(rule_child.scenario) if rule_child.scenario end end end def update_gherkin_document(gherkin_document) # TODO: Update lineage at a later date. Java Impl -> lineageById.putAll(Lineages.of(document)); update_feature(gherkin_document.feature) if gherkin_document.feature end def update_hook(hook) hook_by_id[hook.id] = hook end def update_pickle(pickle) pickle_by_id[pickle.id] = pickle pickle.steps.each { |pickle_step| pickle_step_by_id[pickle_step.id] = pickle_step } end def update_scenario(scenario) update_steps(scenario.steps) end def update_steps(steps) steps.each { |step| step_by_id[step.id] = step } end def update_step_definition(step_definition) step_definition_by_id[step_definition.id] = step_definition end def update_test_case(test_case) test_case_by_id[test_case.id] = test_case test_case.test_steps.each { |test_step| test_step_by_id[test_step.id] = test_step } end def update_test_case_started(test_case_started) test_case_started_by_id[test_case_started.id] = test_case_started end def update_test_case_finished(test_case_finished) test_case_finished_by_test_case_started_id[test_case_finished.test_case_started_id] = test_case_finished end def update_test_run_hook_started(test_run_hook_started) test_run_hook_started_by_id[test_run_hook_started.id] = test_run_hook_started end def update_test_run_hook_finished(test_run_hook_finished) test_run_hook_finished_by_test_run_hook_started_id[test_run_hook_finished.test_run_hook_started_id] = test_run_hook_finished end def update_test_step_started(test_step_started) test_steps_started_by_test_case_started_id[test_step_started.test_case_started_id] << test_step_started end def update_test_step_finished(test_step_finished) test_steps_finished_by_test_case_started_id[test_step_finished.test_case_started_id] << test_step_finished end end end cucumber-11.0.0/lib/cucumber/runtime/0000755000004100000410000000000015171146601017466 5ustar www-datawww-datacucumber-11.0.0/lib/cucumber/runtime/after_hooks.rb0000644000004100000410000000135615171146601022324 0ustar www-datawww-data# frozen_string_literal: true module Cucumber class Runtime class AfterHooks def initialize(id_generator, hooks, scenario, event_bus) @hooks = hooks @scenario = scenario @id_generator = id_generator @event_bus = event_bus end def apply_to(test_case) test_case.with_steps(test_case.test_steps + after_hooks.reverse) end private def after_hooks @hooks.map do |hook| action = ->(result) { hook.invoke('After', @scenario.with_result(result)) } hook_step = Hooks.after_hook(@id_generator.new_id, hook.location, &action) @event_bus.hook_test_step_created(hook_step, hook) hook_step end end end end end cucumber-11.0.0/lib/cucumber/runtime/support_code.rb0000644000004100000410000001120315171146601022516 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/constantize' require 'cucumber/runtime/for_programming_languages' require 'cucumber/runtime/step_hooks' require 'cucumber/runtime/before_hooks' require 'cucumber/runtime/after_hooks' require 'cucumber/gherkin/steps_parser' require 'cucumber/step_match_search' module Cucumber class Runtime class SupportCode require 'forwardable' class StepInvoker def initialize(support_code) @support_code = support_code end def steps(steps) steps.each { |step| step(step) } end def step(step) location = Core::Test::Location.of_caller @support_code.invoke_dynamic_step(step[:text], multiline_arg(step, location)) end def multiline_arg(step, location) 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 end end include Constantize attr_reader :registry def initialize(user_interface, configuration = Configuration.default) @configuration = configuration # TODO: needs a better name, or inlining its methods @runtime_facade = Runtime::ForProgrammingLanguages.new(self, user_interface) @registry = Cucumber::Glue::RegistryAndMore.new(@runtime_facade, @configuration) end def configure(new_configuration) @configuration = Configuration.new(new_configuration) end # Invokes a series of steps +steps_text+. Example: # # invoke(%Q{ # Given I have 8 cukes in my belly # Then I should not be thirsty # }) def invoke_dynamic_steps(steps_text, iso_code, _location) parser = Cucumber::Gherkin::StepsParser.new(StepInvoker.new(self), iso_code) parser.parse(steps_text) end # @api private # This allows users to attempt to find, match and execute steps # from code as the features are running, as opposed to regular # steps which are compiled into test steps before execution. # # These are commonly called nested steps. def invoke_dynamic_step(step_name, multiline_argument, _location = nil) matches = step_matches(step_name) raise UndefinedDynamicStep, step_name if matches.empty? matches.first.invoke(multiline_argument) end def load_files!(files) log.debug("Code:\n") files.each do |file| load_file(file) end log.debug("\n") end def load_files_from_paths(paths) files = paths.map { |path| Dir["#{path}/**/*.rb"] }.flatten load_files! files end def unmatched_step_definitions registry.unmatched_step_definitions end def fire_hook(name, *args) # TODO: kill with fire registry.send(name, *args) end def step_definitions registry.step_definitions end def find_after_step_hooks(test_case) scenario = RunningTestCase.new(test_case) hooks = registry.hooks_for(:after_step, scenario) StepHooks.new(@configuration.id_generator, hooks, @configuration.event_bus) end def apply_before_hooks(test_case) return test_case if test_case.test_steps.empty? scenario = RunningTestCase.new(test_case) hooks = registry.hooks_for(:before, scenario) BeforeHooks.new(@configuration.id_generator, hooks, scenario, @configuration.event_bus).apply_to(test_case) end def apply_after_hooks(test_case) return test_case if test_case.test_steps.empty? scenario = RunningTestCase.new(test_case) hooks = registry.hooks_for(:after, scenario) AfterHooks.new(@configuration.id_generator, hooks, scenario, @configuration.event_bus).apply_to(test_case) end def find_around_hooks(test_case) scenario = RunningTestCase.new(test_case) registry.hooks_for(:around, scenario).map do |hook| Hooks.around_hook do |run_scenario| hook.invoke('Around', scenario, &run_scenario) end end end private def step_matches(step_name) StepMatchSearch.new(registry.method(:step_matches), @configuration).call(step_name) end def load_file(file) log.debug(" * #{file}\n") registry.load_code_file(file) end def log Cucumber.logger end end end end cucumber-11.0.0/lib/cucumber/runtime/meta_message_builder.rb0000644000004100000410000000665715171146601024171 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/messages' require 'cucumber/ci_environment' module Cucumber class Runtime # Builder to instantiate a Cucumber::Messages::Meta message filled-in with # the runtime meta-data: # - protocol version: the version of the Cucumber::Messages protocol # - implementation: the name and version of the implementation (e.g. cucumber-ruby 8.0.0) # - runtime: the name and version of the runtime (e.g. ruby 3.0.1) # - os: the name and version of the operating system (e.g. linux 3.13.0-45-generic) # - cpu: the name of the CPU (e.g. x86_64) # - ci: information about the CI environment if any, including: # - name: the name of the CI environment (e.g. Jenkins) # - url: the URL of the CI environment (e.g. https://ci.example.com) # - build_number: the build number of the CI environment (e.g. 123) # - git: the git information of the CI environment if any # - remote: the remote of the git repository (e.g. git@github.com:cucumber/cucumber-ruby.git) # - revision: the revision of the git repository (e.g. abcdef) # - branch: the name of the git branch (e.g. main) # - tag: the name of the git tag (e.g. v1.0.0) class MetaMessageBuilder class << self # Builds a Cucumber::Messages::Meta filled-in with the runtime meta-data # # @param [env] environment data from which the CI information will be # retrieved (default ENV). Can be used to mock the environment for # testing purpose. # # @return [Cucumber::Messages::Meta] the meta message # # @see Cucumber::Runtime::MetaMessageBuilder # # @example # Cucumber::Runtime::MetaMessageBuilder.build_meta_message # def build_meta_message(env = ENV) Cucumber::Messages::Meta.new( protocol_version: protocol_version, implementation: implementation, runtime: runtime, os: os, cpu: cpu, ci: ci(env) ) end private def protocol_version Cucumber::Messages::VERSION end def implementation Cucumber::Messages::Product.new( name: 'cucumber-ruby', version: Cucumber::VERSION ) end def runtime Cucumber::Messages::Product.new( name: RUBY_ENGINE, version: RUBY_VERSION ) end def os Cucumber::Messages::Product.new( name: RbConfig::CONFIG['target_os'], version: Sys::Uname.uname.version ) end def cpu Cucumber::Messages::Product.new( name: RbConfig::CONFIG['target_cpu'] ) end def ci(env) ci_data = Cucumber::CiEnvironment.detect_ci_environment(env) return nil unless ci_data Cucumber::Messages::Ci.new( name: ci_data[:name], url: ci_data[:url], build_number: ci_data[:buildNumber], git: git_info(ci_data) ) end def git_info(ci_data) return nil unless ci_data[:git] Cucumber::Messages::Git.new( remote: ci_data[:git][:remote], revision: ci_data[:git][:revision], branch: ci_data[:git][:branch], tag: ci_data[:git][:tag] ) end end end end end cucumber-11.0.0/lib/cucumber/runtime/step_hooks.rb0000644000004100000410000000137515171146601022177 0ustar www-datawww-data# frozen_string_literal: true module Cucumber class Runtime class StepHooks def initialize(id_generator, hooks, event_bus) @hooks = hooks @id_generator = id_generator @event_bus = event_bus end def apply(test_steps) test_steps.flat_map do |test_step| [test_step] + after_step_hooks(test_step) end end private def after_step_hooks(test_step) @hooks.map do |hook| action = ->(*args) { hook.invoke('AfterStep', [args, test_step]) } hook_step = Hooks.after_step_hook(@id_generator.new_id, test_step, hook.location, &action) @event_bus.hook_test_step_created(hook_step, hook) hook_step end end end end end cucumber-11.0.0/lib/cucumber/runtime/before_hooks.rb0000644000004100000410000000144515171146601022464 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/hooks' module Cucumber class Runtime class BeforeHooks def initialize(id_generator, hooks, scenario, event_bus) @hooks = hooks @scenario = scenario @id_generator = id_generator @event_bus = event_bus end def apply_to(test_case) test_case.with_steps( before_hooks + test_case.test_steps ) end private def before_hooks @hooks.map do |hook| action_block = ->(result) { hook.invoke('Before', @scenario.with_result(result)) } hook_step = Hooks.before_hook(@id_generator.new_id, hook.location, &action_block) @event_bus.hook_test_step_created(hook_step, hook) hook_step end end end end end cucumber-11.0.0/lib/cucumber/runtime/user_interface.rb0000644000004100000410000000373015171146601023014 0ustar www-datawww-data# frozen_string_literal: true require 'timeout' module Cucumber class Runtime module UserInterface attr_writer :visitor # Suspends execution and prompts +question+ to the console (STDOUT). # An operator (manual tester) can then enter a line of text and hit # . The entered text is returned, and both +question+ and # the result is added to the output using #puts. # # If you want a beep to happen (to grab the manual tester's attention), # just prepend ASCII character 7 to the question: # # ask("#{7.chr}How many cukes are in the external system?") # # If that doesn't issue a beep, you can shell out to something else # that makes a sound before invoking #ask. # def ask(question, timeout_seconds) $stdout.puts(question) $stdout.flush puts(question) answer = if Cucumber::JRUBY jruby_gets(timeout_seconds) else mri_gets(timeout_seconds) end raise("Waited for input for #{timeout_seconds} seconds, then timed out.") unless answer puts(answer) answer end # Embed +src+ of MIME type +mime_type+ into the output. The +src+ argument may # be a path to a file, or if it's an image it may also be a Base64 encoded image. # The embedded data may or may not be ignored, depending on what kind of formatter(s) are active. # def attach(src, media_type, filename) @visitor.attach(src, media_type, filename) end private def mri_gets(timeout_seconds) Timeout.timeout(timeout_seconds) do $stdin.gets end rescue Timeout::Error nil end def jruby_gets(timeout_seconds) answer = nil t = java.lang.Thread.new do answer = $stdin.gets end t.start t.join(timeout_seconds * 1000) answer end end end end cucumber-11.0.0/lib/cucumber/runtime/for_programming_languages.rb0000644000004100000410000000162715171146601025237 0ustar www-datawww-data# frozen_string_literal: true require 'forwardable' require 'cucumber/core/test/doc_string' module Cucumber class Runtime # This is what a programming language will consider to be a runtime. # # It's a thin class that directs the handful of methods needed by the programming languages to the right place class ForProgrammingLanguages extend Forwardable attr_reader :support_code def initialize(support_code, user_interface) @support_code = support_code @user_interface = user_interface end def_delegators :@user_interface, :embed, :attach, :ask, :puts, :features_paths, :step_match def_delegators :@support_code, :invoke_dynamic_steps, :invoke_dynamic_step end end end cucumber-11.0.0/lib/cucumber/filters.rb0000644000004100000410000000014415171146601017777 0ustar www-datawww-data# frozen_string_literal: true Dir["#{File.dirname(__FILE__)}/filters/*.rb"].map(&method(:require)) cucumber-11.0.0/lib/cucumber/encoding.rb0000644000004100000410000000027215171146601020117 0ustar www-datawww-data# frozen_string_literal: true # See https://github.com/cucumber/cucumber/issues/693 if defined? Encoding Encoding.default_external = 'utf-8' Encoding.default_internal = 'utf-8' end cucumber-11.0.0/lib/cucumber/file_specs.rb0000644000004100000410000000170515171146601020447 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber' require 'cucumber/core/test/location' module Cucumber class FileSpecs FILE_COLON_LINE_PATTERN = /^([\w\W]*?)(?::([\d:]+))?$/.freeze # :nodoc: def initialize(file_specs) Cucumber.logger.debug("Features:\n") @file_specs = file_specs.map { |spec| FileSpec.new(spec) } Cucumber.logger.debug("\n") end def locations @file_specs.map(&:locations).flatten end def files @file_specs.map(&:file).uniq end class FileSpec def initialize(spec) @file, @lines = *FILE_COLON_LINE_PATTERN.match(spec).captures Cucumber.logger.debug(" * #{@file}\n") @lines = String(@lines).split(':').map { |line| Integer(line) } end attr_reader :file def locations return [Core::Test::Location.new(@file)] if @lines.empty? @lines.map { |line| Core::Test::Location.new(@file, line) } end end end end cucumber-11.0.0/lib/cucumber/step_match_search.rb0000644000004100000410000000377215171146601022015 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module StepMatchSearch def self.new(search, configuration) CachesStepMatch.new( AssertUnambiguousMatch.new( configuration.guess? ? AttemptToGuessAmbiguousMatch.new(search) : search, configuration ) ) end class AssertUnambiguousMatch def initialize(search, configuration) @search = search @configuration = configuration end def call(step_name) result = @search.call(step_name) raise Cucumber::Ambiguous.new(step_name, result, @configuration.guess?) if result.length > 1 result end end class AttemptToGuessAmbiguousMatch def initialize(search) @search = search end def call(step_name) best_matches(step_name, @search.call(step_name)) end private def best_matches(_step_name, step_matches) # :nodoc: no_groups = step_matches.select { |step_match| step_match.args.empty? } max_arg_length = step_matches.map { |step_match| step_match.args.length }.max top_groups = step_matches.select { |step_match| step_match.args.length == max_arg_length } if no_groups.any? longest_regexp_length = no_groups.map(&:text_length).max no_groups.select { |step_match| step_match.text_length == longest_regexp_length } elsif top_groups.any? shortest_capture_length = top_groups.map { |step_match| step_match.args.inject(0) { |sum, c| sum + c.to_s.length } }.min top_groups.select { |step_match| step_match.args.inject(0) { |sum, c| sum + c.to_s.length } == shortest_capture_length } else top_groups end end end require 'delegate' class CachesStepMatch < SimpleDelegator def call(step_name) # :nodoc: @match_cache ||= {} matches = @match_cache[step_name] return matches if matches @match_cache[step_name] = super(step_name) end end end end cucumber-11.0.0/lib/cucumber/rake.rb0000644000004100000410000000014115171146601017246 0ustar www-datawww-data# frozen_string_literal: true Dir["#{File.dirname(__FILE__)}/rake/*.rb"].map(&method(:require)) cucumber-11.0.0/lib/cucumber/rake/0000755000004100000410000000000015171146601016725 5ustar www-datawww-datacucumber-11.0.0/lib/cucumber/rake/forked_cucumber_runner.rb0000644000004100000410000000261515171146601024006 0ustar www-datawww-data# frozen_string_literal: true require 'rake/dsl_definition' module Cucumber module Rake class ForkedCucumberRunner # :nodoc: include ::Rake::DSL if defined?(::Rake::DSL) def initialize(libs, cucumber_bin, cucumber_opts, bundler, feature_files) @libs = libs @cucumber_bin = cucumber_bin @cucumber_opts = cucumber_opts @bundler = bundler @feature_files = feature_files end def load_path [format('"%s"', path: @libs.join(File::PATH_SEPARATOR))] end def quoted_binary(cucumber_bin) [format('"%s"', path: cucumber_bin)] end def use_bundler @bundler.nil? ? File.exist?('./Gemfile') && bundler_gem_available? : @bundler end def bundler_gem_available? Gem::Specification.find_by_name('bundler') rescue Gem::LoadError false end def cmd if use_bundler [ Cucumber::RUBY_BINARY, '-S', 'bundle', 'exec', 'cucumber', @cucumber_opts, @feature_files ].flatten else [ Cucumber::RUBY_BINARY, '-I', load_path, quoted_binary(@cucumber_bin), @cucumber_opts, @feature_files ].flatten end end def run sh cmd.join(' ') do |ok, res| exit res.exitstatus unless ok end end end end end cucumber-11.0.0/lib/cucumber/rake/task.rb0000644000004100000410000000731515171146601020222 0ustar www-datawww-data# frozen_string_literal: true require 'rake/dsl_definition' require_relative '../gherkin/formatter/ansi_escapes' require_relative '../platform' require_relative 'forked_cucumber_runner' require_relative 'in_process_cucumber_runner' module Cucumber module Rake # Defines a Rake task for running features. # # The simplest use of it goes something like: # # Cucumber::Rake::Task.new # # This will define a task named cucumber described as 'Run Cucumber features'. # It will use steps from 'features/**/*.rb' and features in 'features/**/*.feature'. # # To further configure the task, you can pass a block: # # Cucumber::Rake::Task.new do |t| # t.cucumber_opts = %w{--format progress} # end # # See the attributes for additional configuration possibilities. class Task include Cucumber::Gherkin::Formatter::AnsiEscapes include ::Rake::DSL if defined?(::Rake::DSL) # Name of the cucumber binary to use for running features. Defaults to Cucumber::BINARY attr_accessor :binary # Whether or not to run with bundler (bundle exec). Setting this to false may speed # up the execution. The default value is true if Bundler is installed and you have # a Gemfile, false otherwise. # # Note that this attribute has no effect if you don't run in forked mode. attr_accessor :bundler # Extra options to pass to the cucumber binary. Can be overridden by the CUCUMBER_OPTS environment variable. # It's recommended to pass an Array, but if it's a String it will be #split by ' '. attr_reader :cucumber_opts # Whether or not to fork a new ruby interpreter. Defaults to true. You may gain # some startup speed if you set it to false, but this may also cause issues with # your load path and gems. attr_accessor :fork # Directories to add to the Ruby $LOAD_PATH attr_accessor :libs # Define what profile to be used. When used with cucumber_opts it is simply appended # to it. Will be ignored when CUCUMBER_OPTS is used. attr_accessor :profile # Name of the running task attr_reader :task_name def initialize(task_name = 'cucumber', desc = 'Run Cucumber features') @task_name = task_name @desc = desc @fork = true @libs = ['lib'] @rcov_opts = %w[--rails --exclude osx\/objc,gems\/] yield self if block_given? @binary = binary.nil? ? Cucumber::BINARY : File.expand_path(binary) define_task end def cucumber_opts=(opts) # :nodoc: unless opts.instance_of? String @cucumber_opts = opts return end @cucumber_opts = opts.split(' ') return if @cucumber_opts.length <= 1 $stderr.puts 'WARNING: consider using an array rather than a space-delimited string with cucumber_opts to avoid undesired behavior.' end def cucumber_opts_with_profile # :nodoc: Array(cucumber_opts).concat(Array(@profile).flat_map { |p| ['--profile', p] }) end def define_task # :nodoc: desc @desc task @task_name do runner.run end end def feature_files # :nodoc: make_command_line_safe(FileList[ENV['FEATURE'] || []]) end def make_command_line_safe(list) list.map { |string| string.gsub(' ', '\ ') } end def runner(_task_args = nil) # :nodoc: cucumber_opts = [ENV['CUCUMBER_OPTS']&.split(/\s+/) || cucumber_opts_with_profile] return ForkedCucumberRunner.new(libs, binary, cucumber_opts, bundler, feature_files) if fork InProcessCucumberRunner.new(libs, cucumber_opts, feature_files) end end end end cucumber-11.0.0/lib/cucumber/rake/in_process_cucumber_runner.rb0000644000004100000410000000122215171146601024671 0ustar www-datawww-data# frozen_string_literal: true require 'rake/dsl_definition' require_relative '../cli/main' module Cucumber module Rake class InProcessCucumberRunner include ::Rake::DSL if defined?(::Rake::DSL) attr_reader :args def initialize(libs, cucumber_opts, feature_files) raise 'libs must be an Array when running in-process' unless libs.instance_of? Array libs.reverse_each { |lib| $LOAD_PATH.unshift(lib) } @args = (cucumber_opts + feature_files).flatten.compact end def run failure = Cucumber::Cli::Main.execute(args) raise 'Cucumber failed' if failure end end end end cucumber-11.0.0/lib/cucumber/cli/0000755000004100000410000000000015171146601016552 5ustar www-datawww-datacucumber-11.0.0/lib/cucumber/cli/configuration.rb0000644000004100000410000000650215171146601021751 0ustar www-datawww-data# frozen_string_literal: true require 'logger' require 'cucumber/cli/options' require 'cucumber/cli/rerun_file' require 'cucumber/constantize' require 'cucumber' module Cucumber module Cli YmlLoadError = Class.new(StandardError) ProfilesNotDefinedError = Class.new(YmlLoadError) ProfileNotFound = Class.new(StandardError) class Configuration include Constantize attr_reader :out_stream, :options def initialize(out_stream = $stdout, error_stream = $stderr) @out_stream = out_stream @error_stream = error_stream @options = Options.new(@out_stream, @error_stream, default_profile: 'default') end def parse!(args) @args = args @options.parse!(args) arrange_formats raise("You can't use both --strict and --wip") if strict.strict? && wip? set_environment_variables end def verbose? @options[:verbose] end def randomize? @options[:order] == 'random' end def seed Integer(@options[:seed] || rand(0xFFFF)) end def strict @options[:strict] end def wip? @options[:wip] end def guess? @options[:guess] end def dry_run? @options[:dry_run] end def expand? @options[:expand] end def fail_fast? @options[:fail_fast] end def retry_attempts @options[:retry] end def snippet_type @options[:snippet_type] || :cucumber_expression end def log logger = Logger.new(@out_stream) logger.formatter = LogFormatter.new logger.level = Logger::INFO logger.level = Logger::DEBUG if verbose? logger end def tag_limits @options[:tag_limits] end def tag_expressions @options[:tag_expressions] end def name_regexps @options[:name_regexps] end def filters @options.filters end def formats @options[:formats] end def paths @options[:paths] end def to_hash Hash(@options).merge(out_stream: @out_stream, error_stream: @error_stream, seed: seed) end private class LogFormatter < ::Logger::Formatter def call(_severity, _time, _progname, msg) msg end end def set_environment_variables @options[:env_vars].each do |var, value| ENV[var] = value end end def arrange_formats add_default_formatter if needs_default_formatter? @options[:formats] = @options[:formats].sort_by do |f| f[2] == @out_stream ? -1 : 1 end @options[:formats].uniq! @options.check_formatter_stream_conflicts end def add_default_formatter @options[:formats] << ['pretty', {}, @out_stream] end def needs_default_formatter? formatter_missing? || publish_only? end def formatter_missing? @options[:formats].empty? end def publish_only? @options[:formats] .uniq .map { |formatter, _, stream| [formatter, stream] } .uniq .reject { |formatter, stream| formatter == 'message' && stream != @out_stream } .empty? end end end end cucumber-11.0.0/lib/cucumber/cli/rerun_file.rb0000644000004100000410000000104315171146601021227 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Cli class RerunFile attr_reader :path def self.can_read?(path) path[0] == '@' && File.file?(real_path(path)) end def self.real_path(path) path[1..] end def initialize(path) @path = self.class.real_path(path) end def features lines.map { |l| l.scan(/(?:^| |)(.*?\.feature(?:(?::\d+)*))/) }.flatten end private def lines File.read(path).split("\n") end end end end cucumber-11.0.0/lib/cucumber/cli/profile_loader.rb0000644000004100000410000000736215171146601022075 0ustar www-datawww-data# frozen_string_literal: true require 'yaml' require 'erb' module Cucumber module Cli class ProfileLoader def initialize @cucumber_yml = nil end def args_from(profile) unless cucumber_yml.key?(profile) raise(ProfileNotFound, <<~ERROR_MESSAGE) Could not find profile: '#{profile}' Defined profiles in cucumber.yml: * #{cucumber_yml.keys.sort.join("\n * ")} ERROR_MESSAGE end args_from_yml = cucumber_yml[profile] || '' case args_from_yml when String if args_from_yml =~ /^\s*$/ raise YmlLoadError, "The '#{profile}' profile in cucumber.yml was blank." \ " Please define the command line arguments for the '#{profile}' profile in cucumber.yml.\n" end args_from_yml = processed_shellwords(args_from_yml) when Array raise YmlLoadError, "The '#{profile}' profile in cucumber.yml was empty. Please define the command line arguments for the '#{profile}' profile in cucumber.yml.\n" if args_from_yml.empty? else raise YmlLoadError, "The '#{profile}' profile in cucumber.yml was a #{args_from_yml.class}. It must be a String or Array" end args_from_yml end def profile?(profile) cucumber_yml.key?(profile) end def cucumber_yml_defined? cucumber_file && File.exist?(cucumber_file) end private # Loads the profile, processing it through ERB and YAML, and returns it as a hash. def cucumber_yml return @cucumber_yml if @cucumber_yml ensure_configuration_file_exists process_configuration_file_with_erb load_configuration unless @cucumber_yml.is_a?(Hash) raise(YmlLoadError, <<~ERROR_MESSAGE) cucumber.yml was found, but was blank or malformed. Please refer to cucumber's documentation on correct profile usage. Type 'cucumber --help' for usage. ERROR_MESSAGE end @cucumber_yml end def ensure_configuration_file_exists return if cucumber_yml_defined? raise(ProfilesNotDefinedError, <<~ERROR_MESSAGE) cucumber.yml was not found. Current directory is #{Dir.pwd}. Please refer to cucumber's documentation on defining profiles in cucumber.yml. You must define a 'default' profile to use the cucumber command without any arguments. Type 'cucumber --help' for usage. ERROR_MESSAGE end def process_configuration_file_with_erb @cucumber_erb = ERB.new(File.read(cucumber_file), trim_mode: '%').result(binding) rescue StandardError raise(YmlLoadError, "cucumber.yml was found, but could not be parsed with ERB. Please refer to cucumber's documentation on correct profile usage.\n#{$ERROR_INFO.inspect}") end def load_configuration @cucumber_yml = YAML.load(@cucumber_erb) rescue StandardError raise(YmlLoadError, "cucumber.yml was found, but could not be parsed. Please refer to cucumber's documentation on correct profile usage.") end def cucumber_file @cucumber_file ||= Dir.glob('{,.config/,config/}cucumber{.yml,.yaml}').first end def processed_shellwords(args_from_yml) require 'shellwords' return Shellwords.shellwords(args_from_yml) unless Cucumber::WINDOWS # Shellwords treats backslash as an escape character so we have to mask it out temporarily placeholder = 'pseudo_unique_backslash_placeholder' sanitized_line = args_from_yml.gsub('\\', placeholder) Shellwords.shellwords(sanitized_line).collect { |argument| argument.gsub(placeholder, '\\') } end end end end cucumber-11.0.0/lib/cucumber/cli/options.rb0000644000004100000410000006053715171146601020605 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/cli/profile_loader' require 'cucumber/formatter/ansicolor' require 'cucumber/glue/registry_and_more' require 'cucumber/project_initializer' require 'cucumber/core/test/result' module Cucumber module Cli class Options CUCUMBER_PUBLISH_URL = ENV['CUCUMBER_PUBLISH_URL'] || 'https://messages.cucumber.io/api/reports -X GET' INDENT = ' ' * 53 BUILTIN_FORMATS = { 'pretty' => ['Cucumber::Formatter::Pretty', 'Prints the feature as is - in colours.'], 'progress' => ['Cucumber::Formatter::Progress', 'Prints one character per scenario.'], 'rerun' => ['Cucumber::Formatter::Rerun', 'Prints failing files with line numbers.'], 'usage' => ['Cucumber::Formatter::Usage', "Prints where step definitions are used.\n" \ "#{INDENT}The slowest step definitions (with duration) are\n" \ "#{INDENT}listed first. If --dry-run is used the duration\n" \ "#{INDENT}is not shown, and step definitions are sorted by\n" \ "#{INDENT}filename instead."], 'stepdefs' => ['Cucumber::Formatter::Stepdefs', "Prints All step definitions with their locations. Same as\n" \ "#{INDENT}the usage formatter, except that steps are not printed."], 'junit' => ['Cucumber::Formatter::Junit', "Generates a report similar to Ant+JUnit. Use\n" \ "#{INDENT}junit,fileattribute=true to include a file attribute."], 'json' => ['Cucumber::Formatter::Json', "Prints the feature as JSON.\n" \ "#{INDENT}The JSON format is in maintenance mode.\n" \ "#{INDENT}Please consider using the message formatter\n"\ "#{INDENT}with the standalone json-formatter\n" \ "#{INDENT}(https://github.com/cucumber/cucumber/tree/master/json-formatter)."], 'message' => ['Cucumber::Formatter::Message', 'Prints each message in NDJSON form, which can then be consumed by other tools.'], 'html' => ['Cucumber::Formatter::HTML', 'Outputs HTML report'], 'summary' => ['Cucumber::Formatter::Summary', 'Summary output of feature and scenarios'] }.freeze max = BUILTIN_FORMATS.keys.map(&:length).max FORMAT_HELP_MSG = [ 'Use --format rerun --out rerun.txt to write out failing', 'features. You can rerun them with cucumber @rerun.txt.', 'FORMAT can also be the fully qualified class name of', "your own custom formatter. If the class isn't loaded,", 'Cucumber will attempt to require a file with a relative', 'file name that is the underscore name of the class name.', 'Example: --format Foo::BarZap -> Cucumber will look for', 'foo/bar_zap.rb. You can place the file with this relative', 'path underneath your features/support directory or anywhere', "on Ruby's LOAD_PATH, for example in a Ruby gem." ].freeze FORMAT_HELP = (BUILTIN_FORMATS.keys.sort.map do |key| " #{key}#{' ' * (max - key.length)} : #{BUILTIN_FORMATS[key][1]}" end) + FORMAT_HELP_MSG PROFILE_SHORT_FLAG = '-p' NO_PROFILE_SHORT_FLAG = '-P' PROFILE_LONG_FLAG = '--profile' NO_PROFILE_LONG_FLAG = '--no-profile' FAIL_FAST_FLAG = '--fail-fast' RETRY_FLAG = '--retry' RETRY_TOTAL_FLAG = '--retry-total' OPTIONS_WITH_ARGS = [ '-r', '--require', '--i18n-keywords', '-f', '--format', '-o', '--out', '-t', '--tags', '-n', '--name', '-e', '--exclude', PROFILE_SHORT_FLAG, PROFILE_LONG_FLAG, RETRY_FLAG, RETRY_TOTAL_FLAG, '-l', '--lines', '--port', '-I', '--snippet-type' ].freeze ORDER_TYPES = %w[defined random reverse].freeze TAG_LIMIT_MATCHER = /(?@\w+):(?\d+)/x.freeze def self.parse(args, out_stream, error_stream, options = {}) new(out_stream, error_stream, options).parse!(args) end def initialize(out_stream = $stdout, error_stream = $stderr, options = {}) @out_stream = out_stream @error_stream = error_stream @default_profile = options[:default_profile] @profiles = options[:profiles] || [] @overridden_paths = [] @options = default_options.merge(options) @profile_loader = options[:profile_loader] @options[:skip_profile_information] = options[:skip_profile_information] @disable_profile_loading = nil end def [](key) @options[key] end def []=(key, value) @options[key] = value end def parse!(args) @args = args @expanded_args = @args.dup @args.extend(::OptionParser::Arguable) @args.options do |opts| opts.banner = banner opts.on('--publish', 'Publish a report to https://reports.cucumber.io') do set_option :publish_enabled, true end opts.on('--publish-quiet', 'Don\'t print information banner about publishing reports') { set_option :publish_quiet } opts.on('-r LIBRARY|DIR', '--require LIBRARY|DIR', *require_files_msg) { |lib| require_files(lib) } opts.on('-j DIR', '--jars DIR', 'Load all the jars under DIR') { |jars| load_jars(jars) } if Cucumber::JRUBY opts.on("#{RETRY_FLAG} ATTEMPTS", *retry_msg) { |v| set_option :retry, v.to_i } opts.on("#{RETRY_TOTAL_FLAG} TESTS", *retry_total_msg) { |v| set_option :retry_total, v.to_i } opts.on('--i18n-languages', *i18n_languages_msg) { list_languages_and_exit } opts.on('--i18n-keywords LANG', *i18n_keywords_msg) { |lang| language lang } opts.on(FAIL_FAST_FLAG, 'Exit immediately following the first failing scenario') { set_option :fail_fast } opts.on('-f FORMAT', '--format FORMAT', *format_msg, *FORMAT_HELP) do |v| add_option :formats, [*parse_formats(v), @out_stream] end opts.on('--init', *init_msg) { initialize_project } opts.on('-o', '--out [FILE|DIR|URL]', *out_msg) { |v| out_stream v } opts.on('-t TAG_EXPRESSION', '--tags TAG_EXPRESSION', *tags_msg) { |v| add_tag(v) } opts.on('-n NAME', '--name NAME', *name_msg) { |v| add_option(:name_regexps, /#{v}/) } opts.on('-e', '--exclude PATTERN', *exclude_msg) { |v| add_option :excludes, Regexp.new(v) } opts.on(PROFILE_SHORT_FLAG, "#{PROFILE_LONG_FLAG} PROFILE", *profile_short_flag_msg) { |v| add_profile v } opts.on(NO_PROFILE_SHORT_FLAG, NO_PROFILE_LONG_FLAG, *no_profile_short_flag_msg) { |_v| disable_profile_loading } opts.on('-c', '--[no-]color', *color_msg) { |v| color v } opts.on('-d', '--dry-run', *dry_run_msg) { set_dry_run_and_duration } opts.on('-m', '--no-multiline', "Don't print multiline strings and tables under steps.") { set_option :no_multiline } opts.on('-s', '--no-source', "Don't print the file and line of the step definition with the steps.") { set_option :source, false } opts.on('-i', '--no-snippets', "Don't print snippets for pending steps.") { set_option :snippets, false } opts.on('-I', '--snippet-type TYPE', *snippet_type_msg) { |v| set_option :snippet_type, v.to_sym } opts.on('-q', '--quiet', 'Alias for --no-snippets --no-source --no-duration --publish-quiet.') { shut_up } opts.on('--no-duration', "Don't print the duration at the end of the summary") { set_option :duration, false } opts.on('-b', '--backtrace', 'Show full backtrace for all errors.') { Cucumber.use_full_backtrace = true } opts.on('-S', '--[no-]strict', *strict_msg) { |setting| set_strict(setting) } opts.on('--[no-]strict-undefined', 'Fail if there are any undefined results.') { |setting| set_strict(setting, :undefined) } opts.on('--[no-]strict-pending', 'Fail if there are any pending results.') { |setting| set_strict(setting, :pending) } opts.on('--[no-]strict-flaky', 'Fail if there are any flaky results.') { |setting| set_strict(setting, :flaky) } opts.on('-w', '--wip', 'Fail if there are any passing scenarios.') { set_option :wip } opts.on('-v', '--verbose', 'Show the files and features loaded.') { set_option :verbose } opts.on('-g', '--guess', 'Guess best match for Ambiguous steps.') { set_option :guess } opts.on('-l', '--lines LINES', *lines_msg) { |lines| set_option :lines, lines } opts.on('-x', '--expand', 'Expand Scenario Outline Tables in output.') { set_option :expand } opts.on('--order TYPE[:SEED]', 'Run examples in the specified order. Available types:', *<<~TEXT.split("\n")) do |order| [defined] Run scenarios in the order they were defined (default). [random] Shuffle scenarios before running. [reverse] Run scenarios in the opposite order to which they were defined. Specify SEED to reproduce the shuffling from a previous run. e.g. --order random:5738 TEXT @options[:order], @options[:seed] = *order.split(':') raise "'#{@options[:order]}' is not a recognised order type. Please use one of #{ORDER_TYPES.join(', ')}." unless ORDER_TYPES.include?(@options[:order]) end opts.on_tail('--version', 'Show version.') { exit_ok(Cucumber::VERSION) } opts.on_tail('-h', '--help', "You're looking at it.") { exit_ok(opts.help) } end.parse! process_publish_options @args.map! { |a| "#{a}:#{@options[:lines]}" } if @options[:lines] extract_environment_variables @options[:paths] = @args.dup # whatver is left over check_formatter_stream_conflicts merge_profiles self end def custom_profiles @profiles - [@default_profile] end def filters @options[:filters] ||= [] end def check_formatter_stream_conflicts streams = @options[:formats].uniq.map { |(_, _, stream)| stream } return if streams == streams.uniq raise 'All but one formatter must use --out, only one can print to each stream (or STDOUT)' end def to_hash Hash(@options) end protected attr_reader :options, :profiles, :expanded_args protected :options, :profiles, :expanded_args private def process_publish_options @options[:publish_enabled] = true if truthy_string?(ENV['CUCUMBER_PUBLISH_ENABLED']) || ENV['CUCUMBER_PUBLISH_TOKEN'] @options[:formats] << publisher if @options[:publish_enabled] @options[:publish_quiet] = true if truthy_string?(ENV['CUCUMBER_PUBLISH_QUIET']) end def truthy_string?(str) return false if str.nil? str !~ /^(false|no|0)$/i end def color_msg [ 'Whether or not to use ANSI color in the output. Cucumber decides', 'based on your platform and the output destination if not specified.' ] end def dry_run_msg ['Invokes formatters without executing the steps.'] end def exclude_msg ["Don't run feature files or require ruby files matching PATTERN"] end def format_msg ['How to format features (Default: pretty). Available formats:'] end def i18n_languages_msg [ 'List all available languages' ] end def i18n_keywords_msg [ 'List keywords for in a particular language', %(Run with "--i18n help" to see all languages) ] end def init_msg [ 'Initializes folder structure and generates conventional files for', 'a Cucumber project.' ] end def lines_msg ['Run given line numbers. Equivalent to FILE:LINE syntax'] end def no_profile_short_flag_msg [ "Disables all profile loading to avoid using the 'default' profile." ] end def profile_short_flag_msg [ 'Pull commandline arguments from cucumber.yml which can be defined as', "strings or arrays. When a 'default' profile is defined and no profile", 'is specified it is always used. (Unless disabled, see -P below.)', 'When feature files are defined in a profile and on the command line', 'then only the ones from the command line are used.' ] end def retry_msg ['Specify the number of times to retry failing tests (default: 0)'] end def retry_total_msg [ 'The total number of failing test after which retrying of tests is suspended.', 'Example: --retry-total 10 -> Will stop retrying tests after 10 failing tests.' ] end def name_msg [ 'Only execute the feature elements which match part of the given name.', 'If this option is given more than once, it will match against all the', 'given names.' ] end def strict_msg [ 'Fail if there are any strict affected results ', '(that is undefined, pending or flaky results).' ] end def parse_formats(value) formatter, *formatter_options = value.split(',') options_hash = Hash[formatter_options.map { |string| string.split('=') }] [formatter, options_hash] end def out_stream(value) @options[:formats] << ['pretty', {}, nil] if @options[:formats].empty? @options[:formats][-1][2] = value end def tags_msg [ 'Only execute the features or scenarios with tags matching TAG_EXPRESSION.', 'Scenarios inherit tags declared on the Feature level. The simplest', 'TAG_EXPRESSION is simply a tag. Example: --tags @dev. To represent', "boolean NOT preceed the tag with 'not '. Example: --tags 'not @dev'.", 'A tag expression can have several tags separated by an or which represents', "logical OR. Example: --tags '@dev or @wip'. The --tags option can be specified", 'A tag expression can have several tags separated by an and which represents', "logical AND. Example: --tags '@dev and @wip'. The --tags option can be specified", 'several times, and this also represents logical AND.', "Example: --tags '@foo or not @bar' --tags @zap. This represents the boolean", 'expression (@foo || !@bar) && @zap.', "\n", 'Beware that if you want to use several negative tags to exclude several tags', "you have to use logical AND: --tags 'not @fixme and not @buggy'.", "\n", 'Tags can be given a threshold to limit the number of occurrences.', 'Example: --tags @qa:3 will fail if there are more than 3 occurrences of the @qa tag.', 'This can be practical if you are practicing Kanban or CONWIP.' ] end def out_msg [ 'Write output to a file/directory/URL instead of STDOUT. This option', 'applies to the previously specified --format, or the', 'default format if no format is specified. Check the specific', "formatter's docs to see whether to pass a file, dir or URL.", "\n", 'When using a URL, the output of the formatter will be sent as the HTTP request body.', 'HTTP headers and request method can be set with cURL like options.', 'Example: --out "http://example.com -X POST -H Content-Type:text/json"' ] end def require_files_msg [ 'Require files before executing the features. If this', 'option is not specified, all *.rb files that are', 'siblings of or below the features will be loaded auto-', 'matically. Automatic loading is disabled when this', 'option is specified; all loading becomes explicit.', 'Files in directories named "support" are still always', 'loaded first when their parent directories are', 'required or if the "support" directories themselves are', 'explicitly required.', 'This option can be specified multiple times.' ] end def snippet_type_msg [ 'Use different snippet type (Default: cucumber_expression). Available types:', Cucumber::Glue::RegistryAndMore.cli_snippet_type_options ].flatten end def banner [ 'Usage: cucumber [options] [ [FILE|DIR|URL][:LINE[:LINE]*] ]+', '', 'Examples:', 'cucumber examples/i18n/en/features', 'cucumber @rerun.txt (See --format rerun)', 'cucumber examples/i18n/it/features/somma.feature:6:98:113', 'cucumber -s -i http://rubyurl.com/eeCl', '', '' ].join("\n") end def require_files(filenames) @options[:require] << filenames return unless Cucumber::JRUBY && File.directory?(filenames) require 'java' $CLASSPATH << filenames end def require_jars(jars) Dir["#{jars}/**/*.jar"].sort.each { |jar| require jar } end def publisher url = CUCUMBER_PUBLISH_URL url += %( -H "Authorization: Bearer #{ENV['CUCUMBER_PUBLISH_TOKEN']}") if ENV['CUCUMBER_PUBLISH_TOKEN'] ['message', {}, url] end def language(lang) require 'gherkin/dialect' return indicate_invalid_language_and_exit(lang) unless ::Gherkin::DIALECTS.key?(lang) list_keywords_and_exit(lang) end def disable_profile_loading @disable_profile_loading = true end def non_stdout_formats @options[:formats].reject { |_, _, output| output == @out_stream } end def add_option(option, value) @options[option] << value end def add_tag(value) raise("Found tags option '#{value}'. '~@tag' is no longer supported, use 'not @tag' instead.") if value.include?('~') raise("Found tags option '#{value}'. '@tag1,@tag2' is no longer supported, use '@tag or @tag2' instead.") if value.include?(',') @options[:tag_expressions] << value.gsub(/(@\w+)(:\d+)?/, '\1') add_tag_limits(value) end def add_tag_limits(value) value.split(/[, ]/).map { |part| TAG_LIMIT_MATCHER.match(part) }.compact.each do |matchdata| add_tag_limit(@options[:tag_limits], matchdata[:tag_name], matchdata[:limit].to_i) end end def add_tag_limit(tag_limits, tag_name, limit) raise "Inconsistent tag limits for #{tag_name}: #{tag_limits[tag_name]} and #{limit}" if tag_limits[tag_name] && tag_limits[tag_name] != limit tag_limits[tag_name] = limit end def color(color) Cucumber::Term::ANSIColor.coloring = color end def initialize_project ProjectInitializer.new.run && Kernel.exit(0) end def add_profile(profile) @profiles << profile end def set_option(option, value = nil) @options[option] = value.nil? ? true : value end def set_dry_run_and_duration @options[:dry_run] = true @options[:duration] = false end def exit_ok(text) @out_stream.puts(text) Kernel.exit(0) end def shut_up @options[:publish_quiet] = true @options[:snippets] = false @options[:source] = false @options[:duration] = false end def set_strict(setting, type = nil) @options[:strict].set_strict(setting, type) end def stdout_formats @options[:formats].select { |_, _, output| output == @out_stream } end def extract_environment_variables @args.delete_if do |arg| if arg =~ /^(\w+)=(.*)$/ @options[:env_vars][Regexp.last_match(1)] = Regexp.last_match(2) true end end end def merge_profiles return @out_stream.puts 'Disabling profiles...' if @disable_profile_loading @profiles << @default_profile if default_profile_should_be_used? @profiles.each { |profile| merge_with_profile(profile) } @options[:profiles] = @profiles end def merge_with_profile(profile) profile_args = profile_loader.args_from(profile) profile_options = Options.parse( profile_args, @out_stream, @error_stream, skip_profile_information: true, profile_loader: profile_loader ) reverse_merge(profile_options) end def default_profile_should_be_used? @profiles.empty? && profile_loader.cucumber_yml_defined? && profile_loader.profile?(@default_profile) end def profile_loader @profile_loader ||= ProfileLoader.new end def reverse_merge(other_options) @options = other_options.options.merge(@options) @options[:require] += other_options[:require] @options[:excludes] += other_options[:excludes] @options[:name_regexps] += other_options[:name_regexps] @options[:tag_expressions] += other_options[:tag_expressions] merge_tag_limits(@options[:tag_limits], other_options[:tag_limits]) @options[:env_vars] = other_options[:env_vars].merge(@options[:env_vars]) if @options[:paths].empty? @options[:paths] = other_options[:paths] else @overridden_paths += (other_options[:paths] - @options[:paths]) end @options[:source] &= other_options[:source] @options[:snippets] &= other_options[:snippets] @options[:duration] &= other_options[:duration] @options[:strict] = other_options[:strict].merge!(@options[:strict]) @options[:dry_run] |= other_options[:dry_run] @profiles += other_options.profiles @expanded_args += other_options.expanded_args if @options[:formats].empty? @options[:formats] = other_options[:formats] else @options[:formats] += other_options[:formats] @options[:formats] = stdout_formats[0..0] + non_stdout_formats end @options[:retry] = other_options[:retry] if @options[:retry].zero? @options[:retry_total] = other_options[:retry_total] if @options[:retry_total].infinite? self end def merge_tag_limits(option_limits, other_limits) other_limits.each { |key, value| add_tag_limit(option_limits, key, value) } end def indicate_invalid_language_and_exit(lang) @out_stream.write("Invalid language '#{lang}'. Available languages are:\n") list_languages_and_exit end def list_keywords_and_exit(lang) require 'gherkin/dialect' language = ::Gherkin::Dialect.for(lang) data = Cucumber::MultilineArgument::DataTable.from( [ ['feature', to_keywords_string(language.feature_keywords)], ['background', to_keywords_string(language.background_keywords)], ['scenario', to_keywords_string(language.scenario_keywords)], ['scenario_outline', to_keywords_string(language.scenario_outline_keywords)], ['examples', to_keywords_string(language.examples_keywords)], ['given', to_keywords_string(language.given_keywords)], ['when', to_keywords_string(language.when_keywords)], ['then', to_keywords_string(language.then_keywords)], ['and', to_keywords_string(language.and_keywords)], ['but', to_keywords_string(language.but_keywords)], ['given (code)', to_code_keywords_string(language.given_keywords)], ['when (code)', to_code_keywords_string(language.when_keywords)], ['then (code)', to_code_keywords_string(language.then_keywords)], ['and (code)', to_code_keywords_string(language.and_keywords)], ['but (code)', to_code_keywords_string(language.but_keywords)] ] ) @out_stream.write(data.to_s(color: false, prefixes: Hash.new(''))) Kernel.exit(0) end def list_languages_and_exit require 'gherkin/dialect' data = Cucumber::MultilineArgument::DataTable.from( ::Gherkin::DIALECTS.keys.map do |key| [key, ::Gherkin::DIALECTS[key].fetch('name'), ::Gherkin::DIALECTS[key].fetch('native')] end ) @out_stream.write(data.to_s(color: false, prefixes: Hash.new(''))) Kernel.exit(0) end def to_keywords_string(list) list.map { |item| "\"#{item}\"" }.join(', ') end def to_code_keywords_string(list) to_keywords_string(Cucumber::Gherkin::I18n.code_keywords_for(list)) end def default_options Cucumber::Configuration.default_options end end end end cucumber-11.0.0/lib/cucumber/cli/main.rb0000644000004100000410000000476615171146601020040 0ustar www-datawww-data# frozen_string_literal: true require 'optparse' require 'cucumber' require 'logger' require 'cucumber/cli/configuration' module Cucumber module Cli class Main class << self def execute(args) new(args).execute! end end def initialize(args, out = $stdout, err = $stderr, kernel = Kernel) @args = args @out = out @err = err @kernel = kernel end def execute!(existing_runtime = nil) trap_interrupt runtime = runtime(existing_runtime) runtime.run! if Cucumber.wants_to_quit exit_unable_to_finish elsif runtime.failure? exit_tests_failed else exit_ok end rescue SystemExit => e @kernel.exit(e.status) rescue FileNotFoundException => e @err.puts(e.message) @err.puts("Couldn't open #{e.path}") exit_unable_to_finish rescue FeatureFolderNotFoundException => e @err.puts("#{e.message}. You can use `cucumber --init` to get started.") exit_unable_to_finish rescue ProfilesNotDefinedError, YmlLoadError, ProfileNotFound => e @err.puts(e.message) exit_unable_to_finish rescue Errno::EACCES, Errno::ENOENT => e @err.puts("#{e.message} (#{e.class})") exit_unable_to_finish rescue Exception => e @err.puts("#{e.message} (#{e.class})") @err.puts(e.backtrace.join("\n")) exit_unable_to_finish end def configuration @configuration ||= Configuration.new(@out, @err).tap do |configuration| configuration.parse!(@args) Cucumber.logger = configuration.log end end private def exit_ok @kernel.exit 0 end def exit_tests_failed @kernel.exit 1 end def exit_unable_to_finish @kernel.exit 2 end # stops the program immediately, without running at_exit blocks def exit_unable_to_finish! @kernel.exit! 2 end def trap_interrupt trap('INT') do exit_unable_to_finish! if Cucumber.wants_to_quit Cucumber.wants_to_quit = true $stderr.puts "\nExiting... Interrupt again to exit immediately." exit_unable_to_finish end end def runtime(existing_runtime) return Runtime.new(configuration) unless existing_runtime existing_runtime.tap { |runtime| runtime.configure(configuration) } end end end end cucumber-11.0.0/lib/cucumber/events.rb0000644000004100000410000000236415171146601017641 0ustar www-datawww-data# frozen_string_literal: true Dir["#{File.dirname(__FILE__)}/events/*.rb"].map(&method(:require)) module Cucumber # Events tell you what's happening while Cucumber runs your features. # # They're designed to be read-only, appropriate for writing formatters and other # output tools. If you need to be able to influence the result of a scenario, use a {RbSupport::RbDsl hook} instead. # # To subscribe to an event, use {Cucumber::Configuration#on_event} # # @example # InstallPlugin do |config| # config.on_event :test_case_finished do |event| # puts event.result # end # end # module Events def self.make_event_bus Core::EventBus.new(registry) end def self.registry Core::Events.build_registry( GherkinSourceParsed, GherkinSourceRead, HookTestStepCreated, StepActivated, StepDefinitionRegistered, TestCaseCreated, TestCaseFinished, TestCaseStarted, TestCaseReady, TestRunFinished, TestRunHookFinished, TestRunHookStarted, TestRunStarted, TestStepCreated, TestStepFinished, TestStepStarted, Envelope, UndefinedParameterType ) end end end cucumber-11.0.0/lib/cucumber/cli.rb0000644000004100000410000000014015171146601017072 0ustar www-datawww-data# frozen_string_literal: true Dir["#{File.dirname(__FILE__)}/cli/*.rb"].map(&method(:require)) cucumber-11.0.0/lib/cucumber/formatter/0000755000004100000410000000000015171146601020006 5ustar www-datawww-datacucumber-11.0.0/lib/cucumber/formatter/usage.rb0000644000004100000410000001223015171146601021435 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/formatter/progress' require 'cucumber/formatter/console' module Cucumber module Formatter class Usage < Progress include Console class StepDefKey attr_accessor :mean_duration, :status attr_reader :regexp_source, :location def initialize(regexp_source, location) @regexp_source = regexp_source @location = location end def eql?(other) regexp_source == other.regexp_source && location == other.location end def hash regexp_source.hash + 31 * location.to_s.hash end end def initialize(config) super @stepdef_to_match = Hash.new { |h, stepdef_key| h[stepdef_key] = [] } @total_duration = 0 @matches = {} config.on_event :step_activated do |event| test_step, step_match = *event.attributes @matches[test_step.to_s] = step_match end config.on_event :step_definition_registered, &method(:on_step_definition_registered) end def on_step_definition_registered(event) stepdef_key = StepDefKey.new(event.step_definition.expression.to_s, event.step_definition.location) @stepdef_to_match[stepdef_key] = [] end def on_step_match(event) @matches[event.test_step.to_s] = event.step_match super end def on_test_step_finished(event) return if event.test_step.hook? test_step = event.test_step result = event.result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter) step_match = @matches[test_step.to_s] unless step_match.nil? step_definition = step_match.step_definition stepdef_key = StepDefKey.new(step_definition.expression.to_s, step_definition.location) unless @stepdef_to_match[stepdef_key].map { |key| key[:location] }.include? test_step.location duration = DurationExtractor.new(result).result_duration keyword = @ast_lookup.step_source(test_step).step.keyword @stepdef_to_match[stepdef_key] << { keyword: keyword, step_match: step_match, status: result.to_sym, location: test_step.location, duration: duration } end end super end private def print_summary aggregate_info keys = if config.dry_run? @stepdef_to_match.keys.sort_by(&:regexp_source) else @stepdef_to_match.keys.sort_by(&:mean_duration).reverse end keys.each do |stepdef_key| print_step_definition(stepdef_key) if @stepdef_to_match[stepdef_key].any? print_steps(stepdef_key) else @io.puts(" #{format_string('NOT MATCHED BY ANY STEPS', :failed)}") end end @io.puts super end def print_step_definition(stepdef_key) @io.print "#{format_string(format('%.7f', duration: stepdef_key.mean_duration), :skipped)} " unless config.dry_run? @io.print format_string(stepdef_key.regexp_source, stepdef_key.status) if config.source? indent_amount = max_length - stepdef_key.regexp_source.unpack('U*').length line_comment = indent(" # #{stepdef_key.location}", indent_amount) @io.print(format_string(line_comment, :comment)) end @io.puts end def print_steps(stepdef_key) @stepdef_to_match[stepdef_key].each do |step| @io.print ' ' @io.print "#{format_string(format('%.7f', duration: step[:duration]), :skipped)} " unless config.dry_run? @io.print format_step(step[:keyword], step[:step_match], step[:status], nil) if config.source? indent_amount = max_length - (step[:keyword].unpack('U*').length + step[:step_match].format_args.unpack('U*').length) line_comment = indent(" # #{step[:location]}", indent_amount) @io.print(format_string(line_comment, :comment)) end @io.puts end end def max_length [max_stepdef_length, max_step_length].compact.max end def max_stepdef_length @stepdef_to_match.keys.flatten.map { |key| key.regexp_source.unpack('U*').length }.max end def max_step_length @stepdef_to_match.values.to_a.flatten.map do |step| step[:keyword].unpack('U*').length + step[:step_match].format_args.unpack('U*').length end.max end def aggregate_info @stepdef_to_match.each do |key, steps| if steps.empty? key.status = :skipped key.mean_duration = 0 else key.status = worst_status(steps.map { |step| step[:status] }) total_duration = steps.inject(0) { |sum, step| step[:duration] + sum } key.mean_duration = total_duration / steps.length end end end def worst_status(statuses) %i[passed undefined pending skipped failed].find do |status| statuses.include?(status) end end end end end cucumber-11.0.0/lib/cucumber/formatter/unicode.rb0000644000004100000410000000303715171146601021764 0ustar www-datawww-data# frozen_string_literal: true # Require this file if you need Unicode support. # Tips for improvement - esp. ruby 1.9: http://www.ruby-forum.com/topic/184730 require 'cucumber/platform' require 'cucumber/formatter/ansicolor' if Cucumber::WINDOWS if ENV['CUCUMBER_OUTPUT_ENCODING'] Cucumber::CODEPAGE = ENV['CUCUMBER_OUTPUT_ENCODING'] elsif `cmd /c chcp` =~ /(\d+)/ if [65_000, 65_001].include? Regexp.last_match(1).to_i Cucumber::CODEPAGE = 'UTF-8' ENV['ANSICON_API'] = 'ruby' else Cucumber::CODEPAGE = "cp#{Regexp.last_match(1).to_i}" end else Cucumber::CODEPAGE = 'cp1252' $stderr.puts("WARNING: Couldn't detect your output codepage. Assuming it is 1252. You may have to chcp 1252 or SET CUCUMBER_OUTPUT_ENCODING=cp1252.") end module Cucumber # @private module WindowsOutput def self.extended(output) output.instance_eval do def cucumber_preprocess_output(*out) out.map { |arg| arg.to_s.encode(Encoding.default_external) } rescue Encoding::UndefinedConversionError => e $stderr.cucumber_puts("WARNING: #{e.message}") out end alias cucumber_print print def print(*out) cucumber_print(*cucumber_preprocess_output(*out)) end alias cucumber_puts puts def puts(*out) cucumber_puts(*cucumber_preprocess_output(*out)) end end end Kernel.extend(self) $stdout.extend(self) $stderr.extend(self) end end end cucumber-11.0.0/lib/cucumber/formatter/stepdefs.rb0000644000004100000410000000035215171146601022150 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/formatter/usage' module Cucumber module Formatter class Stepdefs < Usage def print_steps(stepdef_key); end def max_step_length 0 end end end end cucumber-11.0.0/lib/cucumber/formatter/duration.rb0000644000004100000410000000055715171146601022167 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Formatter module Duration # Helper method for formatters that need to # format a duration in seconds to the UNIX # time format. def format_duration(seconds) m, s = seconds.divmod(60) "#{m}m#{format('%.3f', seconds: s)}s" end end end end cucumber-11.0.0/lib/cucumber/formatter/message_builder.rb0000644000004100000410000003235315171146601023473 0ustar www-datawww-data# frozen_string_literal: true require 'base64' require 'cucumber/formatter/backtrace_filter' require 'cucumber/formatter/query/hook_by_test_step' require 'cucumber/formatter/query/pickle_by_test' require 'cucumber/formatter/query/step_definitions_by_test_step' require 'cucumber/formatter/query/test_case_started_by_test_case' require 'cucumber/query' module Cucumber module Formatter class MessageBuilder include Cucumber::Messages::Helpers::TimeConversion include Io def initialize(config) @config = config @hook_by_test_step = Query::HookByTestStep.new(config) @pickle_by_test = Query::PickleByTest.new(config) @step_definitions_by_test_step = Query::StepDefinitionsByTestStep.new(config) @test_case_started_by_test_case = Query::TestCaseStartedByTestCase.new(config) @repository = Cucumber::Repository.new @query = Cucumber::Query.new(@repository) @test_run_started_id = config.id_generator.new_id @test_case_by_step_id = {} @pickle_id_step_by_test_step_id = {} # Ensure all handlers for events occur after all ivars are instantiated config.on_event :envelope, &method(:on_envelope) config.on_event :gherkin_source_parsed, &method(:on_gherkin_source_parsed) config.on_event :gherkin_source_read, &method(:on_gherkin_source_read) config.on_event :hook_test_step_created, &method(:on_hook_test_step_created) config.on_event :step_activated, &method(:on_step_activated) config.on_event :step_definition_registered, &method(:on_step_definition_registered) # TODO: Handle TestCaseCreated config.on_event :test_case_ready, &method(:on_test_case_ready) config.on_event :test_case_started, &method(:on_test_case_started) config.on_event :test_case_finished, &method(:on_test_case_finished) config.on_event :test_run_started, &method(:on_test_run_started) config.on_event :test_run_finished, &method(:on_test_run_finished) config.on_event :test_run_hook_started, &method(:on_test_run_hook_started) config.on_event :test_run_hook_finished, &method(:on_test_run_hook_finished) config.on_event :test_step_created, &method(:on_test_step_created) config.on_event :test_step_started, &method(:on_test_step_started) config.on_event :test_step_finished, &method(:on_test_step_finished) config.on_event :undefined_parameter_type, &method(:on_undefined_parameter_type) end def attach(src, media_type, filename) attachment_data = { test_step_id: @current_test_step_id, test_case_started_id: @current_test_case_started_id, media_type: media_type, file_name: filename, timestamp: time_to_timestamp(Time.now) } if media_type&.start_with?('text/') attachment_data[:content_encoding] = Cucumber::Messages::AttachmentContentEncoding::IDENTITY attachment_data[:body] = src else body = src.respond_to?(:read) ? src.read : src attachment_data[:content_encoding] = Cucumber::Messages::AttachmentContentEncoding::BASE64 attachment_data[:body] = Base64.strict_encode64(body) end message = Cucumber::Messages::Envelope.new(attachment: Cucumber::Messages::Attachment.new(**attachment_data)) output_envelope(message) end private def on_envelope(event) output_envelope(event.envelope) end def on_gherkin_source_parsed(_event) # TODO: Handle GherkinSourceParsed end def on_gherkin_source_read(event) message = Cucumber::Messages::Envelope.new( source: Cucumber::Messages::Source.new( uri: event.path, data: event.body, media_type: 'text/x.cucumber.gherkin+plain' ) ) output_envelope(message) end def on_hook_test_step_created(_event) # TODO: Handle HookTestStepCreated end def on_test_case_ready(event) message = Cucumber::Messages::Envelope.new( test_case: Cucumber::Messages::TestCase.new( id: event.test_case.id, pickle_id: @pickle_by_test.pickle_id(event.test_case), test_steps: event.test_case.test_steps.map { |step| test_step_to_message(step) }, test_run_started_id: @test_run_started_id ) ) # TODO: This may be a redundant update. But for now we're leaving this in whilst we're in the transitory phase @repository.update(message) # TODO: Switch this over to using the Repo Query object -> `test_step_by_id` # TODO: The finder in query is `find_test_step_by` (Using +TestStepStarted+ message) event.test_case.test_steps.each do |step| @test_case_by_step_id[step.id] = event.test_case end output_envelope(message) end def test_step_to_message(step) return hook_step_to_message(step) if step.hook? Cucumber::Messages::TestStep.new( id: step.id, # TODO: This "fake query" is only used once. It can likely be replace by `find_pickle_step_by` which # takes a +TestStep+ message from the repo directly. pickle_step_id: @pickle_id_step_by_test_step_id[step.id], step_definition_ids: @step_definitions_by_test_step.step_definition_ids(step), step_match_arguments_lists: step_match_arguments_lists(step) ) end def hook_step_to_message(step) Cucumber::Messages::TestStep.new( id: step.id, hook_id: @hook_by_test_step.hook_id(step) ) end def step_match_arguments_lists(step) match_arguments = step_match_arguments(step) [Cucumber::Messages::StepMatchArgumentsList.new( step_match_arguments: match_arguments )] rescue Cucumber::Formatter::TestStepUnknownError [] end def step_match_arguments(step) @step_definitions_by_test_step.step_match_arguments(step).map do |argument| Cucumber::Messages::StepMatchArgument.new( group: argument_group_to_message(argument.group), parameter_type_name: parameter_type_name(argument) ) end end def argument_group_to_message(group) Cucumber::Messages::Group.new( start: group.start, value: group.value, children: group.children&.map { |child| argument_group_to_message(child) } ) end def parameter_type_name(step_match_argument) step_match_argument.parameter_type&.name if step_match_argument.respond_to?(:parameter_type) end def on_test_run_started(*) message = Cucumber::Messages::Envelope.new( test_run_started: Cucumber::Messages::TestRunStarted.new( timestamp: time_to_timestamp(Time.now), id: @test_run_started_id ) ) output_envelope(message) end def on_test_case_started(event) @current_test_case_started_id = test_case_started_id(event.test_case) message = Cucumber::Messages::Envelope.new( test_case_started: Cucumber::Messages::TestCaseStarted.new( id: test_case_started_id(event.test_case), test_case_id: event.test_case.id, timestamp: time_to_timestamp(Time.now), attempt: @test_case_started_by_test_case.attempt_by_test_case(event.test_case) ) ) output_envelope(message) end def on_test_step_created(event) @pickle_id_step_by_test_step_id[event.test_step.id] = event.pickle_step.id test_step_to_message(event.test_step) # TODO: We need to determine what message to output here (Unsure - Placeholder added) # message = Cucumber::Messages::Envelope.new( # pickle: { # id: '', # uri: '', # location: nil, # name: '', # language: '', # steps: test_step_to_message(event.test_step), # tags: [], # ast_node_ids: [] # } # ) # # output_envelope(message) end def on_test_step_started(event) @current_test_step_id = event.test_step.id test_case = @test_case_by_step_id[event.test_step.id] message = Cucumber::Messages::Envelope.new( test_step_started: Cucumber::Messages::TestStepStarted.new( test_step_id: event.test_step.id, test_case_started_id: test_case_started_id(test_case), timestamp: time_to_timestamp(Time.now) ) ) output_envelope(message) end def on_test_step_finished(event) test_case = @test_case_by_step_id[event.test_step.id] result = event.result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter) result_message = result.to_message if result.failed? || result.pending? message_element = result.failed? ? result.exception : result result_message = Cucumber::Messages::TestStepResult.new( status: result_message.status, duration: result_message.duration, message: create_error_message(message_element), exception: create_exception_object(result, message_element) ) end message = Cucumber::Messages::Envelope.new( test_step_finished: Cucumber::Messages::TestStepFinished.new( test_step_id: event.test_step.id, test_case_started_id: test_case_started_id(test_case), test_step_result: result_message, timestamp: time_to_timestamp(Time.now) ) ) output_envelope(message) end def create_error_message(message_element) <<~ERROR_MESSAGE #{message_element.message} (#{message_element.class}) #{message_element.backtrace} ERROR_MESSAGE end def create_exception_object(result, message_element) return unless result.failed? Cucumber::Messages::Exception.new( type: message_element.class, message: message_element.message, stack_trace: message_element.backtrace.join("\n") ) end def on_test_case_finished(event) test_case_started_id = test_case_started_id(event.test_case) test_case_started_message = @repository.test_case_started_by_id[test_case_started_id] max_attempts = @config.retry_attempts # See "fake query" for reason this is index shifted retries_attempted = test_case_started_message.attempt - 1 will_be_retried = event.result.failed? && (retries_attempted < max_attempts) message = Cucumber::Messages::Envelope.new( test_case_finished: Cucumber::Messages::TestCaseFinished.new( test_case_started_id: test_case_started_id, timestamp: time_to_timestamp(Time.now), will_be_retried: will_be_retried ) ) output_envelope(message) end def on_test_run_finished(event) message = Cucumber::Messages::Envelope.new( test_run_finished: Cucumber::Messages::TestRunFinished.new( timestamp: time_to_timestamp(Time.now), success: event.success, test_run_started_id: @test_run_started_id ) ) output_envelope(message) end def on_step_activated(event) # TODO: Handle StepActivated end def on_step_definition_registered(event) output_envelope(event.step_definition.to_envelope) end def on_test_run_hook_started(event) @current_test_run_hook_started_id = @config.id_generator.new_id message = Cucumber::Messages::Envelope.new( test_run_hook_started: Cucumber::Messages::TestRunHookStarted.new( id: @current_test_run_hook_started_id, hook_id: event.hook.id, test_run_started_id: @test_run_started_id, timestamp: time_to_timestamp(Time.now) ) ) output_envelope(message) end def on_test_run_hook_finished(event) result = event.test_result result_message = result.to_message if result.failed? result_message = Cucumber::Messages::TestStepResult.new( status: result_message.status, duration: result_message.duration, message: create_error_message(result.exception), exception: create_exception_object(result, result.exception) ) end message = Cucumber::Messages::Envelope.new( test_run_hook_finished: Cucumber::Messages::TestRunHookFinished.new( test_run_hook_started_id: @current_test_run_hook_started_id, timestamp: time_to_timestamp(Time.now), result: result_message ) ) output_envelope(message) end def on_undefined_parameter_type(event) message = Cucumber::Messages::Envelope.new( undefined_parameter_type: Cucumber::Messages::UndefinedParameterType.new( name: event.type_name, expression: event.expression ) ) output_envelope(message) end def test_case_started_id(test_case) @test_case_started_by_test_case.test_case_started_id_by_test_case(test_case) end end end end cucumber-11.0.0/lib/cucumber/formatter/junit.rb0000644000004100000410000002101315171146601021461 0ustar www-datawww-data# frozen_string_literal: true require 'builder' require 'cucumber/formatter/backtrace_filter' require 'cucumber/formatter/io' require 'cucumber/formatter/interceptor' require 'fileutils' require 'cucumber/formatter/ast_lookup' module Cucumber module Formatter # The formatter used for --format junit class Junit include Io class UnNamedFeatureError < StandardError def initialize(feature_file) super("The feature in '#{feature_file}' does not have a name. The JUnit XML format requires a name for the testsuite element.") end end def initialize(config) @ast_lookup = AstLookup.new(config) config.on_event :test_case_started, &method(:on_test_case_started) config.on_event :test_case_finished, &method(:on_test_case_finished) config.on_event :test_step_finished, &method(:on_test_step_finished) config.on_event :test_run_finished, &method(:on_test_run_finished) @reportdir = ensure_dir(config.out_stream, 'junit') @config = config @features_data = Hash.new do |h, k| h[k] = { feature: nil, failures: 0, errors: 0, tests: 0, skipped: 0, time: 0, builder: Builder::XmlMarkup.new(indent: 2) } end end def on_test_case_started(event) test_case = event.test_case start_feature(test_case) unless same_feature_as_previous_test_case?(test_case) @failing_test_step = nil # In order to fill out and , we need to # intercept the $stderr and $stdout @interceptedout = Interceptor::Pipe.wrap(:stdout) @interceptederr = Interceptor::Pipe.wrap(:stderr) end def on_test_step_finished(event) test_step, result = *event.attributes return if @failing_test_step @failing_test_step = test_step unless result.ok?(strict: @config.strict) end def on_test_case_finished(event) test_case, result = *event.attributes result = result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter) test_case_name = NameBuilder.new(test_case, @ast_lookup) scenario = test_case_name.scenario_name scenario_designation = "#{scenario}#{test_case_name.name_suffix}" output = create_output_string(test_case, scenario, result, test_case_name.row_name) build_testcase(result, scenario_designation, output) Interceptor::Pipe.unwrap! :stdout Interceptor::Pipe.unwrap! :stderr end def on_test_run_finished(_event) @features_data.each_value { |data| end_feature(data) } end private def same_feature_as_previous_test_case?(test_case) @current_feature_data && @current_feature_data[:uri] == test_case.location.file end def start_feature(test_case) uri = test_case.location.file feature = @ast_lookup.gherkin_document(uri).feature raise UnNamedFeatureError, uri if feature.name.empty? @current_feature_data = @features_data[uri] @current_feature_data[:uri] = uri unless @current_feature_data[:uri] @current_feature_data[:feature] = feature unless @current_feature_data[:feature] end def end_feature(feature_data) @testsuite = Builder::XmlMarkup.new(indent: 2) @testsuite.instruct! @testsuite.testsuite( failures: feature_data[:failures], errors: feature_data[:errors], skipped: feature_data[:skipped], tests: feature_data[:tests], time: format('%