cucumber 3.1.2 → 7.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1780 -1146
  3. data/CONTRIBUTING.md +224 -61
  4. data/README.md +144 -22
  5. data/bin/cucumber +1 -1
  6. data/lib/autotest/cucumber_mixin.rb +46 -53
  7. data/lib/cucumber/cli/configuration.rb +28 -6
  8. data/lib/cucumber/cli/main.rb +12 -12
  9. data/lib/cucumber/cli/options.rb +103 -77
  10. data/lib/cucumber/cli/profile_loader.rb +49 -26
  11. data/lib/cucumber/configuration.rb +44 -29
  12. data/lib/cucumber/constantize.rb +2 -5
  13. data/lib/cucumber/deprecate.rb +31 -7
  14. data/lib/cucumber/errors.rb +5 -7
  15. data/lib/cucumber/events/envelope.rb +9 -0
  16. data/lib/cucumber/events/gherkin_source_parsed.rb +11 -0
  17. data/lib/cucumber/events/hook_test_step_created.rb +13 -0
  18. data/lib/cucumber/events/step_activated.rb +2 -1
  19. data/lib/cucumber/events/test_case_created.rb +13 -0
  20. data/lib/cucumber/events/test_case_ready.rb +12 -0
  21. data/lib/cucumber/events/test_step_created.rb +13 -0
  22. data/lib/cucumber/events/undefined_parameter_type.rb +10 -0
  23. data/lib/cucumber/events.rb +14 -7
  24. data/lib/cucumber/file_specs.rb +6 -6
  25. data/lib/cucumber/filters/activate_steps.rb +5 -3
  26. data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
  27. data/lib/cucumber/filters/prepare_world.rb +5 -9
  28. data/lib/cucumber/filters/quit.rb +1 -3
  29. data/lib/cucumber/filters/tag_limits/verifier.rb +2 -4
  30. data/lib/cucumber/filters.rb +1 -0
  31. data/lib/cucumber/formatter/ansicolor.rb +40 -52
  32. data/lib/cucumber/formatter/ast_lookup.rb +163 -0
  33. data/lib/cucumber/formatter/backtrace_filter.rb +10 -8
  34. data/lib/cucumber/formatter/console.rb +69 -69
  35. data/lib/cucumber/formatter/console_counts.rb +4 -9
  36. data/lib/cucumber/formatter/console_issues.rb +6 -3
  37. data/lib/cucumber/formatter/duration.rb +1 -1
  38. data/lib/cucumber/formatter/duration_extractor.rb +3 -1
  39. data/lib/cucumber/formatter/errors.rb +6 -0
  40. data/lib/cucumber/formatter/fanout.rb +2 -0
  41. data/lib/cucumber/formatter/html.rb +11 -598
  42. data/lib/cucumber/formatter/http_io.rb +147 -0
  43. data/lib/cucumber/formatter/ignore_missing_messages.rb +1 -1
  44. data/lib/cucumber/formatter/interceptor.rb +11 -30
  45. data/lib/cucumber/formatter/io.rb +55 -13
  46. data/lib/cucumber/formatter/json.rb +115 -122
  47. data/lib/cucumber/formatter/junit.rb +72 -55
  48. data/lib/cucumber/formatter/message.rb +23 -0
  49. data/lib/cucumber/formatter/message_builder.rb +255 -0
  50. data/lib/cucumber/formatter/pretty.rb +360 -153
  51. data/lib/cucumber/formatter/progress.rb +30 -32
  52. data/lib/cucumber/formatter/publish_banner_printer.rb +77 -0
  53. data/lib/cucumber/formatter/query/hook_by_test_step.rb +31 -0
  54. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  55. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  56. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  57. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +40 -0
  58. data/lib/cucumber/formatter/rerun.rb +22 -4
  59. data/lib/cucumber/formatter/stepdefs.rb +1 -2
  60. data/lib/cucumber/formatter/steps.rb +8 -6
  61. data/lib/cucumber/formatter/summary.rb +16 -8
  62. data/lib/cucumber/formatter/unicode.rb +15 -17
  63. data/lib/cucumber/formatter/url_reporter.rb +17 -0
  64. data/lib/cucumber/formatter/usage.rb +17 -14
  65. data/lib/cucumber/gherkin/data_table_parser.rb +17 -6
  66. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +13 -17
  67. data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
  68. data/lib/cucumber/gherkin/steps_parser.rb +17 -8
  69. data/lib/cucumber/glue/dsl.rb +19 -0
  70. data/lib/cucumber/glue/hook.rb +34 -11
  71. data/lib/cucumber/glue/invoke_in_world.rb +13 -18
  72. data/lib/cucumber/glue/proto_world.rb +37 -44
  73. data/lib/cucumber/glue/registry_and_more.rb +71 -12
  74. data/lib/cucumber/glue/registry_wrapper.rb +31 -0
  75. data/lib/cucumber/glue/snippet.rb +23 -22
  76. data/lib/cucumber/glue/step_definition.rb +42 -20
  77. data/lib/cucumber/glue/world_factory.rb +1 -1
  78. data/lib/cucumber/hooks.rb +11 -11
  79. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +2 -2
  80. data/lib/cucumber/multiline_argument/data_table.rb +97 -64
  81. data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
  82. data/lib/cucumber/multiline_argument.rb +4 -6
  83. data/lib/cucumber/platform.rb +3 -3
  84. data/lib/cucumber/rake/task.rb +16 -18
  85. data/lib/cucumber/rspec/disable_option_parser.rb +9 -8
  86. data/lib/cucumber/rspec/doubles.rb +3 -5
  87. data/lib/cucumber/running_test_case.rb +2 -53
  88. data/lib/cucumber/runtime/after_hooks.rb +8 -4
  89. data/lib/cucumber/runtime/before_hooks.rb +8 -4
  90. data/lib/cucumber/runtime/for_programming_languages.rb +4 -2
  91. data/lib/cucumber/runtime/step_hooks.rb +6 -2
  92. data/lib/cucumber/runtime/support_code.rb +13 -15
  93. data/lib/cucumber/runtime/user_interface.rb +6 -16
  94. data/lib/cucumber/runtime.rb +77 -59
  95. data/lib/cucumber/step_definition_light.rb +4 -3
  96. data/lib/cucumber/step_definitions.rb +2 -2
  97. data/lib/cucumber/step_match.rb +12 -17
  98. data/lib/cucumber/step_match_search.rb +2 -1
  99. data/lib/cucumber/term/ansicolor.rb +9 -9
  100. data/lib/cucumber/term/banner.rb +56 -0
  101. data/lib/cucumber/version +1 -1
  102. data/lib/cucumber.rb +1 -1
  103. metadata +272 -81
  104. data/lib/cucumber/core_ext/string.rb +0 -11
  105. data/lib/cucumber/events/gherkin_source_parsed.rb~ +0 -14
  106. data/lib/cucumber/formatter/ast_lookup.rb~ +0 -9
  107. data/lib/cucumber/formatter/cucumber.css +0 -286
  108. data/lib/cucumber/formatter/cucumber.sass +0 -247
  109. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -42
  110. data/lib/cucumber/formatter/html_builder.rb +0 -121
  111. data/lib/cucumber/formatter/inline-js.js +0 -30
  112. data/lib/cucumber/formatter/jquery-min.js +0 -154
  113. data/lib/cucumber/formatter/json_pretty.rb +0 -11
  114. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  115. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  116. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  117. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  118. data/lib/cucumber/step_argument.rb +0 -25
@@ -0,0 +1,147 @@
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
+ url = arg
39
+ end
40
+ end
41
+ raise StandardError, "#{options} was not a valid curl command" unless url
42
+
43
+ [
44
+ url,
45
+ http_method,
46
+ headers
47
+ ]
48
+ end
49
+
50
+ def self.remove_arg_for(args, arg)
51
+ return args.shift unless args.empty?
52
+ raise StandardError, "Missing argument for #{arg}"
53
+ end
54
+
55
+ def self.parse_header(header_arg)
56
+ parts = header_arg.split(':', 2)
57
+ raise StandardError, "#{header_arg} was not a valid header" unless parts.length == 2
58
+ { parts[0].strip => parts[1].strip }
59
+ end
60
+ end
61
+
62
+ class IOHTTPBuffer
63
+ attr_reader :uri, :method, :headers
64
+
65
+ def initialize(uri, method, headers = {}, https_verify_mode = nil, reporter = nil)
66
+ @uri = URI(uri)
67
+ @method = method
68
+ @headers = headers
69
+ @write_io = Tempfile.new('cucumber', encoding: 'UTF-8')
70
+ @https_verify_mode = https_verify_mode
71
+ @reporter = reporter || NoReporter.new
72
+ end
73
+
74
+ def close
75
+ response = send_content(@uri, @method, @headers)
76
+ @reporter.report(response.body)
77
+ @write_io.close
78
+ return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPRedirection)
79
+ raise StandardError, "request to #{uri} failed with status #{response.code}"
80
+ end
81
+
82
+ def write(data)
83
+ @write_io.write(data)
84
+ end
85
+
86
+ def flush
87
+ @write_io.flush
88
+ end
89
+
90
+ def closed?
91
+ @write_io.closed?
92
+ end
93
+
94
+ private
95
+
96
+ def send_content(uri, method, headers, attempt = 10)
97
+ content = (method == 'GET' ? StringIO.new : @write_io)
98
+ http = build_client(uri, @https_verify_mode)
99
+
100
+ raise StandardError, "request to #{uri} failed (too many redirections)" if attempt <= 0
101
+ req = build_request(
102
+ uri,
103
+ method,
104
+ headers.merge(
105
+ 'Content-Length' => content.size.to_s
106
+ )
107
+ )
108
+
109
+ content.rewind
110
+ req.body_stream = content
111
+
112
+ begin
113
+ response = http.request(req)
114
+ rescue SystemCallError
115
+ # We may get the redirect response before pushing the file.
116
+ response = http.request(build_request(uri, method, headers))
117
+ end
118
+
119
+ case response
120
+ when Net::HTTPAccepted
121
+ send_content(URI(response['Location']), 'PUT', {}, attempt - 1) if response['Location']
122
+ when Net::HTTPRedirection
123
+ send_content(URI(response['Location']), method, headers, attempt - 1)
124
+ end
125
+ response
126
+ end
127
+
128
+ def build_request(uri, method, headers)
129
+ method_class_name = "#{method[0].upcase}#{method[1..-1].downcase}"
130
+ req = Net::HTTP.const_get(method_class_name).new(uri)
131
+ headers.each do |header, value|
132
+ req[header] = value
133
+ end
134
+ req
135
+ end
136
+
137
+ def build_client(uri, https_verify_mode)
138
+ http = Net::HTTP.new(uri.hostname, uri.port)
139
+ if uri.scheme == 'https'
140
+ http.use_ssl = true
141
+ http.verify_mode = https_verify_mode if https_verify_mode
142
+ end
143
+ http
144
+ end
145
+ end
146
+ end
147
+ end
@@ -8,7 +8,7 @@ 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)
@@ -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,32 +1,74 @@
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(%r{^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 String == path.class
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
+ ensure_io(path, nil)
26
68
  end
27
69
 
28
70
  def ensure_dir(path, name)
29
- raise "You *must* specify --out DIR for the #{name} formatter" unless String === path
71
+ raise "You *must* specify --out DIR for the #{name} formatter" unless String == path.class
30
72
  raise "I can't write #{name} reports to a file - it has to be a directory" if File.file?(path)
31
73
  FileUtils.mkdir_p(path) unless File.directory?(path)
32
74
  File.absolute_path path