cucumber-11.1.1/0000755000004100000410000000000015217270171013433 5ustar www-datawww-datacucumber-11.1.1/bin/0000755000004100000410000000000015217270171014203 5ustar www-datawww-datacucumber-11.1.1/bin/cucumber0000755000004100000410000000054515217270171015742 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.1.1/cucumber.gemspec0000644000004100000410000002211415217270171016605 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: cucumber 11.1.1 ruby lib Gem::Specification.new do |s| s.name = "cucumber".freeze s.version = "11.1.1".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 = "1980-01-02" 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/attach_called.rb".freeze, "lib/cucumber/events/base.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/fire_before_all_hooks.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/fail_fast.rb".freeze, "lib/cucumber/formatter/global_hooks_summary.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/message_handlers.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/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 = "4.0.10".freeze s.summary = "cucumber-11.1.1".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, ["~> 24.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.1.1/lib/0000755000004100000410000000000015217270171014201 5ustar www-datawww-datacucumber-11.1.1/lib/simplecov_setup.rb0000644000004100000410000000100015217270171017736 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.1.1/lib/cucumber.rb0000644000004100000410000000142015217270171016330 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.1.1/lib/cucumber/0000755000004100000410000000000015217270171016006 5ustar www-datawww-datacucumber-11.1.1/lib/cucumber/filters/0000755000004100000410000000000015217270171017456 5ustar www-datawww-datacucumber-11.1.1/lib/cucumber/filters/tag_limits/0000755000004100000410000000000015217270171021612 5ustar www-datawww-datacucumber-11.1.1/lib/cucumber/filters/tag_limits/verifier.rb0000644000004100000410000000254015217270171023753 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.1.1/lib/cucumber/filters/tag_limits/test_case_index.rb0000644000004100000410000000114615217270171025302 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.1.1/lib/cucumber/filters/apply_before_hooks.rb0000644000004100000410000000036315217270171023657 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.1.1/lib/cucumber/filters/retry.rb0000644000004100000410000000231415217270171021150 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.1.1/lib/cucumber/filters/fire_before_all_hooks.rb0000644000004100000410000000144615217270171024312 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Filters # Executes all BeforeAll hooks and ONLY if they were all # successful pass on all the `TestCase` objects down the filter chain class FireBeforeAllHooks < Core::Filter.new(:config) def initialize(support_code, receiver = nil) super @support_code = support_code @test_cases = [] end def test_case(test_case) @test_cases << test_case self end def done if fire_before_all_hook @test_cases.map do |test_case| test_case.describe_to(@receiver) end end receiver.done self end private def fire_before_all_hook @support_code.fire_hook(:before_all) end end end end cucumber-11.1.1/lib/cucumber/filters/prepare_world.rb0000644000004100000410000000203715217270171022652 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.1.1/lib/cucumber/filters/reverser.rb0000644000004100000410000000132415217270171021640 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.1.1/lib/cucumber/filters/quit.rb0000644000004100000410000000066615217270171020775 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.1.1/lib/cucumber/filters/broadcast_test_run_started_event.rb0000644000004100000410000000123015217270171026613 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.1.1/lib/cucumber/filters/randomizer.rb0000644000004100000410000000171615217270171022162 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.1.1/lib/cucumber/filters/tag_limits.rb0000644000004100000410000000204215217270171022135 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.1.1/lib/cucumber/filters/broadcast_test_case_ready_event.rb0000644000004100000410000000043015217270171026361 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.1.1/lib/cucumber/filters/apply_after_step_hooks.rb0000644000004100000410000000055315217270171024552 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.1.1/lib/cucumber/filters/gated_receiver.rb0000644000004100000410000000067515217270171022763 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.1.1/lib/cucumber/filters/apply_after_hooks.rb0000644000004100000410000000036115217270171023514 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.1.1/lib/cucumber/filters/activate_steps.rb0000644000004100000410000000402615217270171023023 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.1.1/lib/cucumber/filters/apply_around_hooks.rb0000644000004100000410000000052315217270171023703 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.1.1/lib/cucumber/project_initializer.rb0000644000004100000410000000141315217270171022403 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.1.1/lib/cucumber/term/0000755000004100000410000000000015217270171016755 5ustar www-datawww-datacucumber-11.1.1/lib/cucumber/term/banner.rb0000644000004100000410000000331415217270171020550 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.1.1/lib/cucumber/term/ansicolor.rb0000644000004100000410000000762115217270171021301 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.1.1/lib/cucumber/unit.rb0000644000004100000410000000032515217270171017312 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.1.1/lib/cucumber/platform.rb0000644000004100000410000000120415217270171020154 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.1.1/lib/cucumber/constantize.rb0000644000004100000410000000210215217270171020667 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.1.1/lib/cucumber/configuration.rb0000644000004100000410000001577615217270171021222 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 def test_run_started_id @test_run_started_id ||= id_generator.new_id 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.1.1/lib/cucumber/hooks.rb0000644000004100000410000000477415217270171017472 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.1.1/lib/cucumber/events/0000755000004100000410000000000015217270171017312 5ustar www-datawww-datacucumber-11.1.1/lib/cucumber/events/attach_called.rb0000644000004100000410000000063215217270171022410 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events # Fired when attach is called in a step definition class AttachCalled < Core::Event.new(:src, :media_type, :filename) # The attachment body attr_reader :src # The content media type attr_reader :media_type # An optional filename attr_reader :filename end end end cucumber-11.1.1/lib/cucumber/events/test_run_hook_finished.rb0000644000004100000410000000053215217270171024373 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Events class TestRunHookFinished < Base attr_reader :hook, :test_result def self.event_id :test_run_hook_finished end def initialize(hook, test_result) @hook = hook @test_result = test_result super() end end end end cucumber-11.1.1/lib/cucumber/events/test_case_created.rb0000644000004100000410000000061615217270171023303 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Events # Event fired when a Test::Case is created from a Pickle class TestCaseCreated < Base attr_reader :test_case, :pickle def self.event_id :test_case_created end def initialize(test_case, pickle) @test_case = test_case @pickle = pickle super() end end end end cucumber-11.1.1/lib/cucumber/events/test_step_started.rb0000644000004100000410000000072415217270171023402 0ustar www-datawww-data# frozen_string_literal: true require_relative 'base' module Cucumber module Events # Signals that a {Cucumber::Core::Test::Step} is about to be executed class TestStepStarted < Base # @return [Cucumber::Core::Test::Step] the test step to be executed attr_reader :test_step def self.event_id :test_step_started end def initialize(test_step) @test_step = test_step super() end end end end cucumber-11.1.1/lib/cucumber/events/gherkin_source_read.rb0000644000004100000410000000070615217270171023644 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Events # Fired after we've read in the contents of a feature file class GherkinSourceRead < Base # The path to the file attr_reader :path # The raw Gherkin source attr_reader :body def self.event_id :gherkin_source_read end def initialize(path, body) @path = path @body = body super() end end end end cucumber-11.1.1/lib/cucumber/events/test_run_started.rb0000644000004100000410000000076515217270171023240 0ustar www-datawww-data# frozen_string_literal: true require_relative 'base' module Cucumber module Events # Event fired once all test cases have been filtered before # the first one is executed. class TestRunStarted < Base # @return [Array] the test cases to be executed attr_reader :test_cases def self.event_id :test_run_started end def initialize(test_cases) @test_cases = test_cases super() end end end end cucumber-11.1.1/lib/cucumber/events/test_run_hook_started.rb0000644000004100000410000000047215217270171024253 0ustar www-datawww-data# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Events class TestRunHookStarted < Base attr_reader :hook def self.event_id :test_run_hook_started end def initialize(hook) @hook = hook super() end end end end cucumber-11.1.1/lib/cucumber/events/envelope.rb0000644000004100000410000000116515217270171021457 0ustar www-datawww-data# frozen_string_literal: true require_relative 'base' module Cucumber module Events class Envelope < Base attr_reader :envelope def self.event_id :envelope end def initialize(envelope) @envelope = envelope super() end 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.1.1/lib/cucumber/events/step_activated.rb0000644000004100000410000000111315217270171022632 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Events # Event fired when a step is activated class StepActivated < Base # 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 def self.event_id :step_activated end def initialize(test_step, step_match) @test_step = test_step @step_match = step_match super() end end end end cucumber-11.1.1/lib/cucumber/events/test_case_finished.rb0000644000004100000410000000110715217270171023461 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Events # Event fired when a Test::Case is created from a Pickle class TestCaseFinished < Base # @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 def self.event_id :test_case_finished end def initialize(test_case, result) @test_case = test_case @result = result super() end end end end cucumber-11.1.1/lib/cucumber/events/base.rb0000644000004100000410000000150115217270171020546 0ustar www-datawww-data# frozen_string_literal: true # TODO: Remove this class and use Cucumber::Core::Event::Base once v17 of core is minimum module Cucumber module Events # An archetype of what each Cucumber Event defined in cucumber-ruby must adhere to class Base # The "key" name of the class to be used as the key in the event registry (Underscored name symbolized) # @return [Symbol] def self.event_id raise 'Must be implemented in subclass' end # The properties of each event. Stored in iVar named format - where the key is the name of the iVar # @return [Hash] def to_h instance_variables.to_h { |variable_name| [variable_name[1..].to_sym, instance_variable_get(variable_name)] } end def event_id self.class.event_id end end end end cucumber-11.1.1/lib/cucumber/events/undefined_parameter_type.rb0000644000004100000410000000055715217270171024710 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Events class UndefinedParameterType < Base attr_reader :type_name, :expression def self.event_id :undefined_parameter_type end def initialize(type_name, expression) @type_name = type_name @expression = expression super() end end end end cucumber-11.1.1/lib/cucumber/events/hook_test_step_created.rb0000644000004100000410000000064015217270171024360 0ustar www-datawww-data# frozen_string_literal: true require_relative 'base' module Cucumber module Events # Event fired when a step is created from a hook class HookTestStepCreated < Base attr_reader :test_step, :hook def self.event_id :hook_test_step_created end def initialize(test_step, hook) @test_step = test_step @hook = hook super() end end end end cucumber-11.1.1/lib/cucumber/events/gherkin_source_parsed.rb0000644000004100000410000000067415217270171024213 0ustar www-datawww-data# frozen_string_literal: true require_relative 'base' module Cucumber module Events # Fired after we've parsed the contents of a feature file class GherkinSourceParsed < Base # # The Gherkin Ast attr_reader :gherkin_document def self.event_id :gherkin_source_parsed end def initialize(gherkin_document) @gherkin_document = gherkin_document super() end end end end cucumber-11.1.1/lib/cucumber/events/test_case_started.rb0000644000004100000410000000072415217270171023342 0ustar www-datawww-data# frozen_string_literal: true require_relative 'base' module Cucumber module Events # Signals that a {Cucumber::Core::Test::Case} is about to be executed class TestCaseStarted < Base # @return [Cucumber::Core::Test::Case] the test case to be executed attr_reader :test_case def self.event_id :test_case_started end def initialize(test_case) @test_case = test_case super() end end end end cucumber-11.1.1/lib/cucumber/events/test_run_finished.rb0000644000004100000410000000054415217270171023356 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Events # Event fired after all test cases have finished executing class TestRunFinished < Base attr_reader :success def self.event_id :test_run_finished end def initialize(success = nil) @success = success super() end end end end cucumber-11.1.1/lib/cucumber/events/test_case_ready.rb0000644000004100000410000000044315217270171022776 0ustar www-datawww-data# frozen_string_literal: true module Cucumber module Events class TestCaseReady < Base attr_reader :test_case def self.event_id :test_case_ready end def initialize(test_case) @test_case = test_case super() end end end end cucumber-11.1.1/lib/cucumber/events/step_definition_registered.rb0000644000004100000410000000102115217270171025231 0ustar www-datawww-data# frozen_string_literal: true require_relative 'base' module Cucumber module Events # Event fired after each step definition has been registered class StepDefinitionRegistered < Base # The step definition that was just registered. # @return [RbSupport::RbStepDefinition] attr_reader :step_definition def self.event_id :step_definition_registered end def initialize(step_definition) @step_definition = step_definition super() end end end end cucumber-11.1.1/lib/cucumber/events/test_step_finished.rb0000644000004100000410000000117215217270171023523 0ustar www-datawww-data# frozen_string_literal: true require_relative 'base' module Cucumber module Events # Signals that a {Cucumber::Core::Test::Step} has finished executing class TestStepFinished < Base # @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 def self.event_id :test_step_finished end def initialize(test_step, result) @test_step = test_step @result = result super() end end end end cucumber-11.1.1/lib/cucumber/events/test_step_created.rb0000644000004100000410000000067515217270171023350 0ustar www-datawww-data# frozen_string_literal: true require_relative 'base' module Cucumber module Events # Event fired when a TestStep is created from a PickleStep class TestStepCreated < Base attr_reader :test_step, :pickle_step def self.event_id :test_step_created end def initialize(test_step, pickle_step) @test_step = test_step @pickle_step = pickle_step super() end end end end cucumber-11.1.1/lib/cucumber/runtime.rb0000644000004100000410000002243015217270171020017 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/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 create_formatters receiver = Test::Runner.new(@configuration.event_bus) compile features, receiver, filters, @configuration.event_bus fire_after_all_hook unless dry_run? @configuration.notify :test_run_finished, !failure? 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) || !global_hooks_summary_report.ok? end end private def fire_install_plugin_hook # :nodoc: @support_code.fire_hook(:install_plugin, @configuration, registry_wrapper) 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 # TODO: When core is v17+ switch the below code out to the following # Cucumber::Core::Gherkin::Document.new(path, source).tap do |document| # @configuration.notify(:envelope, document.to_envelope) # end to_envelope = Cucumber::Messages::Envelope.new( source: Cucumber::Messages::Source.new( uri: path, data: source, media_type: 'text/x.cucumber.gherkin+plain' ) ) @configuration.notify(:envelope, to_envelope) 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 create_formatters # Define all formatters which are specified via cli options formatters # Define the MessageBuilder formatter - Required until all messages are generated at the source # Skip when a user formatter already inherits from MessageBuilder (e.g. HTML) to avoid sending every event twice. message_builder unless formatters.any? { |f| f.is_a?(Formatter::MessageBuilder) } # `summary_report` and `global_hooks_summary_report` are used to determine the exit code summary_report global_hooks_summary_report fail_fast_report if @configuration.fail_fast? publish_banner_printer unless @configuration.publish_quiet? end def message_builder @message_builder ||= Formatter::MessageBuilder.new(@configuration) end def summary_report @summary_report ||= Core::Report::Summary.new(@configuration.event_bus) end def global_hooks_summary_report @global_hooks_summary_report ||= Formatter::GlobalHooksSummary.new(@configuration) 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::FireBeforeAllHooks.new(@support_code) 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.1.1/lib/cucumber/repository.rb0000644000004100000410000001346015217270171020556 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.1.1/lib/cucumber/runtime/0000755000004100000410000000000015217270171017471 5ustar www-datawww-datacucumber-11.1.1/lib/cucumber/runtime/after_hooks.rb0000644000004100000410000000135615217270171022327 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.1.1/lib/cucumber/runtime/support_code.rb0000644000004100000410000001120315217270171022521 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.1.1/lib/cucumber/runtime/meta_message_builder.rb0000644000004100000410000000665715217270171024174 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.1.1/lib/cucumber/runtime/step_hooks.rb0000644000004100000410000000137515217270171022202 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.1.1/lib/cucumber/runtime/before_hooks.rb0000644000004100000410000000144515217270171022467 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.1.1/lib/cucumber/runtime/user_interface.rb0000644000004100000410000000405115217270171023014 0ustar www-datawww-data# frozen_string_literal: true require 'timeout' require 'cucumber/events/attach_called' module Cucumber class Runtime module UserInterface attr_writer :configuration # 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) @configuration.notify(:attach_called, src, media_type, filename) self 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.1.1/lib/cucumber/runtime/for_programming_languages.rb0000644000004100000410000000162715217270171025242 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.1.1/lib/cucumber/filters.rb0000644000004100000410000000014415217270171020002 0ustar www-datawww-data# frozen_string_literal: true Dir["#{File.dirname(__FILE__)}/filters/*.rb"].map(&method(:require)) cucumber-11.1.1/lib/cucumber/encoding.rb0000644000004100000410000000027215217270171020122 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.1.1/lib/cucumber/file_specs.rb0000644000004100000410000000170515217270171020452 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.1.1/lib/cucumber/step_match_search.rb0000644000004100000410000000377215217270171022020 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.1.1/lib/cucumber/rake.rb0000644000004100000410000000014115217270171017251 0ustar www-datawww-data# frozen_string_literal: true Dir["#{File.dirname(__FILE__)}/rake/*.rb"].map(&method(:require)) cucumber-11.1.1/lib/cucumber/rake/0000755000004100000410000000000015217270171016730 5ustar www-datawww-datacucumber-11.1.1/lib/cucumber/rake/forked_cucumber_runner.rb0000644000004100000410000000261515217270171024011 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.1.1/lib/cucumber/rake/task.rb0000644000004100000410000000731515217270171020225 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.1.1/lib/cucumber/rake/in_process_cucumber_runner.rb0000644000004100000410000000122215217270171024674 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.1.1/lib/cucumber/cli/0000755000004100000410000000000015217270171016555 5ustar www-datawww-datacucumber-11.1.1/lib/cucumber/cli/configuration.rb0000644000004100000410000000650215217270171021754 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.1.1/lib/cucumber/cli/rerun_file.rb0000644000004100000410000000104315217270171021232 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.1.1/lib/cucumber/cli/profile_loader.rb0000644000004100000410000000736215217270171022100 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.1.1/lib/cucumber/cli/options.rb0000644000004100000410000006053715217270171020610 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.1.1/lib/cucumber/cli/main.rb0000644000004100000410000000617015217270171020032 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 trap_thread_dump_signal 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 trap_thread_dump_signal signal = %w[INFO PWR].find { |s| Signal.list.key?(s) } return if signal.nil? trap(signal) do Thread.list.each do |thread| prefix = "Thread TID-#{(thread.object_id ^ Process.pid).to_s(36)} #{thread.name || ''}" backtrace = thread.backtrace if backtrace&.any? backtrace.each do |line| @err.puts("#{prefix} #{line}") end else @err.puts("#{prefix} ") end end 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.1.1/lib/cucumber/events.rb0000644000004100000410000000241215217270171017636 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( AttachCalled, GherkinSourceParsed, GherkinSourceRead, HookTestStepCreated, StepActivated, StepDefinitionRegistered, TestCaseCreated, TestCaseFinished, TestCaseStarted, TestCaseReady, TestRunFinished, TestRunHookFinished, TestRunHookStarted, TestRunStarted, TestStepCreated, TestStepFinished, TestStepStarted, Envelope, UndefinedParameterType ) end end end cucumber-11.1.1/lib/cucumber/cli.rb0000644000004100000410000000014015217270171017075 0ustar www-datawww-data# frozen_string_literal: true Dir["#{File.dirname(__FILE__)}/cli/*.rb"].map(&method(:require)) cucumber-11.1.1/lib/cucumber/formatter/0000755000004100000410000000000015217270171020011 5ustar www-datawww-datacucumber-11.1.1/lib/cucumber/formatter/usage.rb0000644000004100000410000001216015217270171021442 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| @matches[event.test_step.to_s] = event.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.1.1/lib/cucumber/formatter/unicode.rb0000644000004100000410000000303715217270171021767 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.1.1/lib/cucumber/formatter/stepdefs.rb0000644000004100000410000000035215217270171022153 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.1.1/lib/cucumber/formatter/duration.rb0000644000004100000410000000055715217270171022172 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.1.1/lib/cucumber/formatter/message_builder.rb0000644000004100000410000003371515217270171023501 0ustar www-datawww-data# frozen_string_literal: true require 'base64' require 'json' require 'cucumber/formatter/backtrace_filter' require 'cucumber/query' require_relative 'message_handlers' module Cucumber module Formatter class MessageBuilder include Cucumber::Messages::Helpers::TimeConversion include Io include MessageHandlers include Console def initialize(config) @config = config @ast_lookup = AstLookup.new(config) @repository = Cucumber::Repository.new @test_run_started_id = config.test_run_started_id # Fake Query objects @test_case_by_step_id = {} @pickle_id_by_test_case_id = {} @pickle_id_step_by_test_step_id = {} @hook_id_by_test_step_id = {} @step_definition_ids_by_test_step_id = {} @step_match_arguments_by_test_step_id = {} # Ensure all handlers for events occur after all ivars are instantiated config.on_event :gherkin_source_parsed, &method(:on_gherkin_source_parsed) 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 :test_case_created, &method(:on_test_case_created) 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_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 :attach_called, &method(:on_attach_called) config.on_event :envelope, &method(:on_envelope) end def on_envelope(event) store_current_test_run_hook_started_id(event) end def on_attach_called(event) attachment_data = if @current_test_run_hook_started_id.nil? { test_step_id: @current_test_step_id, test_case_started_id: @current_test_case_started_id, media_type: event.media_type, file_name: event.filename, timestamp: time_to_timestamp(Time.now) } else { test_run_hook_started_id: @current_test_run_hook_started_id, media_type: event.media_type, file_name: event.filename, timestamp: time_to_timestamp(Time.now) } end streamed_file = event.src.encoding == Encoding::BINARY if streamed_file attachment_data[:content_encoding] = Cucumber::Messages::AttachmentContentEncoding::BASE64 attachment_data[:body] = Base64.strict_encode64(event.src) else attachment_data[:content_encoding] = Cucumber::Messages::AttachmentContentEncoding::IDENTITY attachment_data[:body] = event.src.is_a?(Hash) ? event.src.to_json : event.src end message = Cucumber::Messages::Envelope.new(attachment: Cucumber::Messages::Attachment.new(**attachment_data)) @config.event_bus.envelope(message) end private def on_gherkin_source_parsed(_event) # TODO: Handle GherkinSourceParsed end def on_hook_test_step_created(event) @hook_id_by_test_step_id[event.test_step.id] = event.hook.id end def on_step_activated(event) @step_definition_ids_by_test_step_id[event.test_step.id] << event.step_match.step_definition.id @step_match_arguments_by_test_step_id[event.test_step.id] = event.step_match.step_arguments end def on_test_case_created(event) @pickle_id_by_test_case_id[event.test_case.id] = event.pickle.id 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: fake_query_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) @config.event_bus.envelope(message) end def on_test_case_started(event) # For any new test_case_started events, we must ALWAYS generate a new id for a new run @current_test_case_started_id = @config.id_generator.new_id @current_test_run_hook_started_id = nil find_all_test_case_started_by_test_case_id = @repository.test_case_started_by_id .values .select { |test_case_started| test_case_started.test_case_id == event.test_case.id } # If no TestCaseStarted messages exist. We must be on attempt 1 (Hence the .to_i casting for a `nil` value) attempts_previously_made = find_all_test_case_started_by_test_case_id.map(&:attempt).max.to_i message = Cucumber::Messages::Envelope.new( test_case_started: Cucumber::Messages::TestCaseStarted.new( id: @current_test_case_started_id, test_case_id: event.test_case.id, timestamp: time_to_timestamp(Time.now), attempt: attempts_previously_made + 1 ) ) @config.event_bus.envelope(message) @repository.update(message) end def on_test_case_finished(event) test_case_started_id = @repository.test_case_started_by_id .values .detect { |test_case_started_message| test_case_started_message.test_case_id == event.test_case.id } .id test_case_started_message = @repository.test_case_started_by_id[test_case_started_id] max_attempts = @config.retry_attempts 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 ) ) @config.event_bus.envelope(message) 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 ) ) @config.event_bus.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 ) ) @config.event_bus.envelope(message) end def on_test_step_created(event) @pickle_id_step_by_test_step_id[event.test_step.id] = event.pickle_step.id @step_definition_ids_by_test_step_id[event.test_step.id] = [] end def on_test_step_started(event) @current_test_step_id = event.test_step.id find_test_case_by_step_id = @repository.test_case_by_id .values .detect { |test_case_message| test_case_message.test_steps.any? { |step_message| step_message.id == event.test_step.id } } find_test_case_started_by_test_case = @repository.test_case_started_by_id .values .select { |test_case_started_message| test_case_started_message.test_case_id == find_test_case_by_step_id.id } .max_by(&:attempt) message = Cucumber::Messages::Envelope.new( test_step_started: Cucumber::Messages::TestStepStarted.new( test_step_id: event.test_step.id, test_case_started_id: find_test_case_started_by_test_case.id, timestamp: time_to_timestamp(Time.now) ) ) @config.event_bus.envelope(message) end def on_test_step_finished(event) find_test_case_by_step_id = @repository.test_case_by_id .values .detect { |test_case_message| test_case_message.test_steps.any? { |step_message| step_message.id == event.test_step.id } } find_test_case_started_by_test_case = @repository.test_case_started_by_id .values .select { |test_case_started_message| test_case_started_message.test_case_id == find_test_case_by_step_id.id } .max_by(&:attempt) 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 output_snippet_envelope(event) message = Cucumber::Messages::Envelope.new( test_step_finished: Cucumber::Messages::TestStepFinished.new( test_step_id: event.test_step.id, test_case_started_id: find_test_case_started_by_test_case.id, test_step_result: result_message, timestamp: time_to_timestamp(Time.now) ) ) @config.event_bus.envelope(message) end def output_snippet_envelope(event) return unless event.result.undefined? collect_snippet_data(event.test_step, @ast_lookup) snippet_text_proc = lambda do |step_keyword, step_name, multiline_arg| snippet_text(step_keyword, step_name, multiline_arg) end message = generate_snippet_envelope(snippet_text_proc, event) @config.event_bus.envelope(message) # To ensure we don't redistribute the "same" snippets over and over again snippets_input.clear end def generate_snippet_envelope(snippet_text_proc, event) snippets_array = snippets_input.map do |data| snippet_text_proc.call(data.actual_keyword, data.step.text, data.step.multiline_arg) end.uniq Cucumber::Messages::Envelope.new( suggestion: Cucumber::Messages::Suggestion.new( id: @config.id_generator.new_id, pickle_step_id: @repository.test_step_by_id[event.test_step.id].pickle_step_id, snippets: snippets_array.map { |code_snippet| Cucumber::Messages::Snippet.new(language: 'ruby', code: code_snippet) } ) ) 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 ) ) @config.event_bus.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, pickle_step_id: @pickle_id_step_by_test_step_id[step.id], step_definition_ids: fake_query_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_id_by_test_step_id[step.id] ) end def step_match_arguments_lists(step) match_arguments = step_match_arguments(step) if match_arguments.nil? [] else [Cucumber::Messages::StepMatchArgumentsList.new(step_match_arguments: match_arguments)] end end def step_match_arguments(step) fake_query_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 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 fake_query_hook_id(test_step) @hook_id_by_test_step_id.fetch(test_step.id) end def fake_query_pickle_id(test_case) @pickle_id_by_test_case_id.fetch(test_case.id) end def fake_query_step_definition_ids(test_step) @step_definition_ids_by_test_step_id.fetch(test_step.id) end def fake_query_step_match_arguments(test_step) @step_match_arguments_by_test_step_id.fetch(test_step.id, nil) end end end end cucumber-11.1.1/lib/cucumber/formatter/junit.rb0000644000004100000410000002071515217270171021474 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) return if @failing_test_step @failing_test_step = event.test_step unless event.result.ok?(strict: @config.strict) end def on_test_case_finished(event) result = event.result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter) test_case_name = NameBuilder.new(event.test_case, @ast_lookup) scenario = test_case_name.scenario_name scenario_designation = "#{scenario}#{test_case_name.name_suffix}" output = create_output_string(event.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('%