cucumber 3.1.2 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1880 -1146
  3. data/CONTRIBUTING.md +220 -61
  4. data/README.md +143 -22
  5. data/bin/cucumber +1 -1
  6. data/lib/autotest/cucumber_mixin.rb +49 -53
  7. data/lib/autotest/discover.rb +3 -2
  8. data/lib/cucumber/cli/configuration.rb +32 -7
  9. data/lib/cucumber/cli/main.rb +16 -15
  10. data/lib/cucumber/cli/options.rb +111 -79
  11. data/lib/cucumber/cli/profile_loader.rb +45 -26
  12. data/lib/cucumber/cli/rerun_file.rb +1 -1
  13. data/lib/cucumber/configuration.rb +47 -31
  14. data/lib/cucumber/constantize.rb +3 -6
  15. data/lib/cucumber/deprecate.rb +32 -7
  16. data/lib/cucumber/errors.rb +5 -7
  17. data/lib/cucumber/events/envelope.rb +9 -0
  18. data/lib/cucumber/events/gherkin_source_parsed.rb +11 -0
  19. data/lib/cucumber/events/hook_test_step_created.rb +12 -0
  20. data/lib/cucumber/events/step_activated.rb +0 -5
  21. data/lib/cucumber/events/step_definition_registered.rb +0 -5
  22. data/lib/cucumber/events/test_case_created.rb +12 -0
  23. data/lib/cucumber/events/test_case_ready.rb +12 -0
  24. data/lib/cucumber/events/test_run_finished.rb +2 -1
  25. data/lib/cucumber/events/test_step_created.rb +12 -0
  26. data/lib/cucumber/events/undefined_parameter_type.rb +9 -0
  27. data/lib/cucumber/events.rb +15 -8
  28. data/lib/cucumber/file_specs.rb +8 -7
  29. data/lib/cucumber/filters/activate_steps.rb +6 -3
  30. data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
  31. data/lib/cucumber/filters/prepare_world.rb +5 -9
  32. data/lib/cucumber/filters/quit.rb +1 -3
  33. data/lib/cucumber/filters/tag_limits/verifier.rb +3 -7
  34. data/lib/cucumber/filters/tag_limits.rb +1 -3
  35. data/lib/cucumber/filters.rb +1 -0
  36. data/lib/cucumber/formatter/ansicolor.rb +74 -86
  37. data/lib/cucumber/formatter/ast_lookup.rb +163 -0
  38. data/lib/cucumber/formatter/backtrace_filter.rb +10 -7
  39. data/lib/cucumber/formatter/console.rb +76 -68
  40. data/lib/cucumber/formatter/console_counts.rb +4 -9
  41. data/lib/cucumber/formatter/console_issues.rb +12 -4
  42. data/lib/cucumber/formatter/duration.rb +1 -1
  43. data/lib/cucumber/formatter/duration_extractor.rb +4 -1
  44. data/lib/cucumber/formatter/errors.rb +7 -0
  45. data/lib/cucumber/formatter/fanout.rb +3 -1
  46. data/lib/cucumber/formatter/html.rb +11 -598
  47. data/lib/cucumber/formatter/http_io.rb +152 -0
  48. data/lib/cucumber/formatter/ignore_missing_messages.rb +2 -2
  49. data/lib/cucumber/formatter/interceptor.rb +11 -30
  50. data/lib/cucumber/formatter/io.rb +57 -13
  51. data/lib/cucumber/formatter/json.rb +119 -124
  52. data/lib/cucumber/formatter/junit.rb +75 -55
  53. data/lib/cucumber/formatter/message.rb +23 -0
  54. data/lib/cucumber/formatter/message_builder.rb +256 -0
  55. data/lib/cucumber/formatter/pretty.rb +370 -153
  56. data/lib/cucumber/formatter/progress.rb +31 -32
  57. data/lib/cucumber/formatter/publish_banner_printer.rb +77 -0
  58. data/lib/cucumber/formatter/query/hook_by_test_step.rb +32 -0
  59. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  60. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  61. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  62. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +42 -0
  63. data/lib/cucumber/formatter/rerun.rb +24 -4
  64. data/lib/cucumber/formatter/stepdefs.rb +1 -2
  65. data/lib/cucumber/formatter/steps.rb +8 -6
  66. data/lib/cucumber/formatter/summary.rb +17 -8
  67. data/lib/cucumber/formatter/unicode.rb +18 -20
  68. data/lib/cucumber/formatter/url_reporter.rb +17 -0
  69. data/lib/cucumber/formatter/usage.rb +18 -15
  70. data/lib/cucumber/gherkin/data_table_parser.rb +18 -6
  71. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +14 -18
  72. data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
  73. data/lib/cucumber/gherkin/steps_parser.rb +17 -8
  74. data/lib/cucumber/glue/dsl.rb +29 -15
  75. data/lib/cucumber/glue/hook.rb +37 -11
  76. data/lib/cucumber/glue/invoke_in_world.rb +17 -22
  77. data/lib/cucumber/glue/proto_world.rb +47 -53
  78. data/lib/cucumber/glue/registry_and_more.rb +62 -17
  79. data/lib/cucumber/glue/registry_wrapper.rb +31 -0
  80. data/lib/cucumber/glue/snippet.rb +23 -22
  81. data/lib/cucumber/glue/step_definition.rb +48 -23
  82. data/lib/cucumber/glue/world_factory.rb +1 -1
  83. data/lib/cucumber/hooks.rb +12 -11
  84. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +4 -3
  85. data/lib/cucumber/multiline_argument/data_table.rb +143 -123
  86. data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
  87. data/lib/cucumber/multiline_argument.rb +4 -6
  88. data/lib/cucumber/platform.rb +5 -5
  89. data/lib/cucumber/rake/task.rb +34 -25
  90. data/lib/cucumber/rspec/disable_option_parser.rb +15 -11
  91. data/lib/cucumber/rspec/doubles.rb +3 -5
  92. data/lib/cucumber/running_test_case.rb +3 -53
  93. data/lib/cucumber/runtime/after_hooks.rb +8 -4
  94. data/lib/cucumber/runtime/before_hooks.rb +8 -4
  95. data/lib/cucumber/runtime/for_programming_languages.rb +4 -2
  96. data/lib/cucumber/runtime/meta_message_builder.rb +106 -0
  97. data/lib/cucumber/runtime/step_hooks.rb +6 -2
  98. data/lib/cucumber/runtime/support_code.rb +16 -15
  99. data/lib/cucumber/runtime/user_interface.rb +10 -19
  100. data/lib/cucumber/runtime.rb +78 -76
  101. data/lib/cucumber/step_definition_light.rb +4 -3
  102. data/lib/cucumber/step_definitions.rb +2 -2
  103. data/lib/cucumber/step_match.rb +17 -20
  104. data/lib/cucumber/step_match_search.rb +5 -3
  105. data/lib/cucumber/term/ansicolor.rb +72 -48
  106. data/lib/cucumber/term/banner.rb +57 -0
  107. data/lib/cucumber/version +1 -1
  108. data/lib/cucumber.rb +3 -2
  109. data/lib/simplecov_setup.rb +1 -1
  110. metadata +279 -81
  111. data/lib/cucumber/core_ext/string.rb +0 -11
  112. data/lib/cucumber/events/gherkin_source_parsed.rb~ +0 -14
  113. data/lib/cucumber/formatter/ast_lookup.rb~ +0 -9
  114. data/lib/cucumber/formatter/cucumber.css +0 -286
  115. data/lib/cucumber/formatter/cucumber.sass +0 -247
  116. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -42
  117. data/lib/cucumber/formatter/html_builder.rb +0 -121
  118. data/lib/cucumber/formatter/inline-js.js +0 -30
  119. data/lib/cucumber/formatter/jquery-min.js +0 -154
  120. data/lib/cucumber/formatter/json_pretty.rb +0 -11
  121. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  122. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  123. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  124. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  125. data/lib/cucumber/step_argument.rb +0 -25
@@ -0,0 +1,152 @@
1
+ require 'net/http'
2
+ require 'tempfile'
3
+ require 'shellwords'
4
+
5
+ module Cucumber
6
+ module Formatter
7
+ class HTTPIO
8
+ class << self
9
+ # Returns an IO that will write to a HTTP request's body
10
+ # https_verify_mode can be set to OpenSSL::SSL::VERIFY_NONE
11
+ # to ignore unsigned certificate - setting to nil will verify the certificate
12
+ def open(url, https_verify_mode = nil, reporter = nil)
13
+ @https_verify_mode = https_verify_mode
14
+ uri, method, headers = CurlOptionParser.parse(url)
15
+ IOHTTPBuffer.new(uri, method, headers, https_verify_mode, reporter)
16
+ end
17
+ end
18
+ end
19
+
20
+ class CurlOptionParser
21
+ def self.parse(options)
22
+ args = Shellwords.split(options)
23
+
24
+ url = nil
25
+ http_method = 'PUT'
26
+ headers = {}
27
+
28
+ until args.empty?
29
+ arg = args.shift
30
+ case arg
31
+ when '-X', '--request'
32
+ http_method = remove_arg_for(args, arg)
33
+ when '-H'
34
+ header_arg = remove_arg_for(args, arg)
35
+ headers = headers.merge(parse_header(header_arg))
36
+ else
37
+ raise StandardError, "#{options} was not a valid curl command. Can't set url to #{arg} it is already set to #{url}" if url
38
+
39
+ url = arg
40
+ end
41
+ end
42
+ raise StandardError, "#{options} was not a valid curl command" unless url
43
+
44
+ [
45
+ url,
46
+ http_method,
47
+ headers
48
+ ]
49
+ end
50
+
51
+ def self.remove_arg_for(args, arg)
52
+ return args.shift unless args.empty?
53
+
54
+ raise StandardError, "Missing argument for #{arg}"
55
+ end
56
+
57
+ def self.parse_header(header_arg)
58
+ parts = header_arg.split(':', 2)
59
+ raise StandardError, "#{header_arg} was not a valid header" unless parts.length == 2
60
+
61
+ { parts[0].strip => parts[1].strip }
62
+ end
63
+ end
64
+
65
+ class IOHTTPBuffer
66
+ attr_reader :uri, :method, :headers
67
+
68
+ def initialize(uri, method, headers = {}, https_verify_mode = nil, reporter = nil)
69
+ @uri = URI(uri)
70
+ @method = method
71
+ @headers = headers
72
+ @write_io = Tempfile.new('cucumber', encoding: 'UTF-8')
73
+ @https_verify_mode = https_verify_mode
74
+ @reporter = reporter || NoReporter.new
75
+ end
76
+
77
+ def close
78
+ response = send_content(@uri, @method, @headers)
79
+ @reporter.report(response.body)
80
+ @write_io.close
81
+ return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPRedirection)
82
+
83
+ raise StandardError, "request to #{uri} failed with status #{response.code}"
84
+ end
85
+
86
+ def write(data)
87
+ @write_io.write(data)
88
+ end
89
+
90
+ def flush
91
+ @write_io.flush
92
+ end
93
+
94
+ def closed?
95
+ @write_io.closed?
96
+ end
97
+
98
+ private
99
+
100
+ def send_content(uri, method, headers, attempt = 10)
101
+ content = (method == 'GET' ? StringIO.new : @write_io)
102
+ http = build_client(uri, @https_verify_mode)
103
+
104
+ raise StandardError, "request to #{uri} failed (too many redirections)" if attempt <= 0
105
+
106
+ req = build_request(
107
+ uri,
108
+ method,
109
+ headers.merge(
110
+ 'Content-Length' => content.size.to_s
111
+ )
112
+ )
113
+
114
+ content.rewind
115
+ req.body_stream = content
116
+
117
+ begin
118
+ response = http.request(req)
119
+ rescue SystemCallError
120
+ # We may get the redirect response before pushing the file.
121
+ response = http.request(build_request(uri, method, headers))
122
+ end
123
+
124
+ case response
125
+ when Net::HTTPAccepted
126
+ send_content(URI(response['Location']), 'PUT', {}, attempt - 1) if response['Location']
127
+ when Net::HTTPRedirection
128
+ send_content(URI(response['Location']), method, headers, attempt - 1)
129
+ end
130
+ response
131
+ end
132
+
133
+ def build_request(uri, method, headers)
134
+ method_class_name = "#{method[0].upcase}#{method[1..].downcase}"
135
+ req = Net::HTTP.const_get(method_class_name).new(uri)
136
+ headers.each do |header, value|
137
+ req[header] = value
138
+ end
139
+ req
140
+ end
141
+
142
+ def build_client(uri, https_verify_mode)
143
+ http = Net::HTTP.new(uri.hostname, uri.port)
144
+ if uri.scheme == 'https'
145
+ http.use_ssl = true
146
+ http.verify_mode = https_verify_mode if https_verify_mode
147
+ end
148
+ http
149
+ end
150
+ end
151
+ end
152
+ end
@@ -8,11 +8,11 @@ module Cucumber
8
8
  end
9
9
 
10
10
  def method_missing(message, *args)
11
- @receiver.send(message, *args) if @receiver.respond_to?(message)
11
+ @receiver.respond_to?(message) ? @receiver.send(message, *args) : super
12
12
  end
13
13
 
14
14
  def respond_to_missing?(name, include_private = false)
15
- @receiver.respond_to?(name, include_private)
15
+ @receiver.respond_to?(name, include_private) || super(name, include_private)
16
16
  end
17
17
  end
18
18
  end
@@ -5,34 +5,23 @@ module Cucumber
5
5
  module Interceptor
6
6
  class Pipe
7
7
  attr_reader :pipe
8
+
8
9
  def initialize(pipe)
9
10
  @pipe = pipe
10
11
  @buffer = StringIO.new
11
12
  @wrapped = true
13
+ @lock = Mutex.new
12
14
  end
13
15
 
14
16
  def write(str)
15
- lock.synchronize do
17
+ @lock.synchronize do
16
18
  @buffer << str if @wrapped
17
19
  return @pipe.write(str)
18
20
  end
19
21
  end
20
22
 
21
- # @deprecated use #buffer_string
22
- def buffer
23
- require 'cucumber/deprecate.rb'
24
- Cucumber.deprecate(
25
- 'Use Cucumber::Formatter::Interceptor::Pipe#buffer_string instead',
26
- 'Cucumber::Formatter::Interceptor::Pipe#buffer',
27
- '3.99'
28
- )
29
- lock.synchronize do
30
- return @buffer.string.lines
31
- end
32
- end
33
-
34
23
  def buffer_string
35
- lock.synchronize do
24
+ @lock.synchronize do
36
25
  return @buffer.string.dup
37
26
  end
38
27
  end
@@ -43,17 +32,15 @@ module Cucumber
43
32
  end
44
33
 
45
34
  def method_missing(method, *args, &blk)
46
- @pipe.send(method, *args, &blk)
35
+ @pipe.respond_to?(method) ? @pipe.send(method, *args, &blk) : super
47
36
  end
48
37
 
49
- def respond_to?(method, include_private = false)
38
+ def respond_to_missing?(method, include_private = false)
50
39
  super || @pipe.respond_to?(method, include_private)
51
40
  end
52
41
 
53
42
  def self.validate_pipe(pipe)
54
- unless [:stdout, :stderr].include? pipe
55
- raise ArgumentError, '#wrap only accepts :stderr or :stdout'
56
- end
43
+ raise ArgumentError, '#wrap only accepts :stderr or :stdout' unless %i[stdout stderr].include? pipe
57
44
  end
58
45
 
59
46
  def self.unwrap!(pipe)
@@ -75,19 +62,13 @@ module Cucumber
75
62
 
76
63
  case pipe
77
64
  when :stderr
78
- $stderr = self.new($stderr)
79
- return $stderr
65
+ $stderr = new($stderr)
66
+ $stderr
80
67
  when :stdout
81
- $stdout = self.new($stdout)
82
- return $stdout
68
+ $stdout = new($stdout)
69
+ $stdout
83
70
  end
84
71
  end
85
-
86
- private
87
-
88
- def lock
89
- @lock ||= Mutex.new
90
- end
91
72
  end
92
73
  end
93
74
  end
@@ -1,33 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cucumber/formatter/http_io'
4
+ require 'cucumber/formatter/url_reporter'
5
+ require 'cucumber/cli/options'
6
+
3
7
  module Cucumber
4
8
  module Formatter
5
9
  module Io
6
10
  module_function
7
11
 
8
- def ensure_io(path_or_io)
9
- return nil if path_or_io.nil?
10
- return path_or_io if path_or_io.respond_to?(:write)
11
- file = File.open(path_or_io, Cucumber.file_mode('w'))
12
- at_exit do
13
- unless file.closed?
14
- file.flush
15
- file.close
12
+ def ensure_io(path_or_url_or_io, error_stream)
13
+ return nil if path_or_url_or_io.nil?
14
+ return path_or_url_or_io if io?(path_or_url_or_io)
15
+
16
+ io = if url?(path_or_url_or_io)
17
+ url = path_or_url_or_io
18
+ reporter = url.start_with?(Cucumber::Cli::Options::CUCUMBER_PUBLISH_URL) ? URLReporter.new(error_stream) : NoReporter.new
19
+ HTTPIO.open(url, nil, reporter)
20
+ else
21
+ File.open(path_or_url_or_io, Cucumber.file_mode('w'))
22
+ end
23
+ @io_objects_to_close ||= []
24
+ @io_objects_to_close.push(io)
25
+ io
26
+ end
27
+
28
+ module ClassMethods
29
+ def new(*args, &block)
30
+ instance = super
31
+
32
+ config = args[0]
33
+ if config.respond_to? :on_event
34
+ config.on_event :test_run_finished do
35
+ ios = instance.instance_variable_get(:@io_objects_to_close) || []
36
+ ios.each do |io|
37
+ at_exit do
38
+ unless io.closed?
39
+ io.flush
40
+ io.close
41
+ end
42
+ end
43
+ end
44
+ end
16
45
  end
46
+
47
+ instance
17
48
  end
18
- file
49
+ end
50
+
51
+ def self.included(formatter_class)
52
+ formatter_class.extend(ClassMethods)
53
+ end
54
+
55
+ def io?(path_or_url_or_io)
56
+ path_or_url_or_io.respond_to?(:write)
57
+ end
58
+
59
+ def url?(path_or_url_or_io)
60
+ path_or_url_or_io.match(/^https?:\/\//)
19
61
  end
20
62
 
21
63
  def ensure_file(path, name)
22
- raise "You *must* specify --out FILE for the #{name} formatter" unless String === path
64
+ raise "You *must* specify --out FILE for the #{name} formatter" unless path.instance_of? String
23
65
  raise "I can't write #{name} to a directory - it has to be a file" if File.directory?(path)
24
- raise "I can't write #{name} to a file in the non-existing directory #{File.dirname(path)}" if !File.directory?(File.dirname(path))
25
- ensure_io(path)
66
+ raise "I can't write #{name} to a file in the non-existing directory #{File.dirname(path)}" unless File.directory?(File.dirname(path))
67
+
68
+ ensure_io(path, nil)
26
69
  end
27
70
 
28
71
  def ensure_dir(path, name)
29
- raise "You *must* specify --out DIR for the #{name} formatter" unless String === path
72
+ raise "You *must* specify --out DIR for the #{name} formatter" unless path.instance_of? String
30
73
  raise "I can't write #{name} reports to a file - it has to be a directory" if File.file?(path)
74
+
31
75
  FileUtils.mkdir_p(path) unless File.directory?(path)
32
76
  File.absolute_path path
33
77
  end