cucumber 4.0.0.rc.1 → 4.0.0.rc.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +97 -4
  3. data/CONTRIBUTING.md +1 -18
  4. data/README.md +4 -5
  5. data/lib/autotest/cucumber_mixin.rb +2 -10
  6. data/lib/cucumber.rb +1 -1
  7. data/lib/cucumber/cli/configuration.rb +1 -1
  8. data/lib/cucumber/cli/main.rb +1 -0
  9. data/lib/cucumber/cli/options.rb +18 -13
  10. data/lib/cucumber/cli/profile_loader.rb +23 -12
  11. data/lib/cucumber/configuration.rb +11 -2
  12. data/lib/cucumber/deprecate.rb +29 -5
  13. data/lib/cucumber/errors.rb +5 -2
  14. data/lib/cucumber/events.rb +12 -7
  15. data/lib/cucumber/events/envelope.rb +9 -0
  16. data/lib/cucumber/events/hook_test_step_created.rb +13 -0
  17. data/lib/cucumber/events/test_case_created.rb +13 -0
  18. data/lib/cucumber/events/test_case_ready.rb +12 -0
  19. data/lib/cucumber/events/test_step_created.rb +13 -0
  20. data/lib/cucumber/filters.rb +1 -0
  21. data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
  22. data/lib/cucumber/formatter/ast_lookup.rb +43 -38
  23. data/lib/cucumber/formatter/backtrace_filter.rb +4 -1
  24. data/lib/cucumber/formatter/console.rb +4 -9
  25. data/lib/cucumber/formatter/console_issues.rb +1 -1
  26. data/lib/cucumber/formatter/duration.rb +1 -1
  27. data/lib/cucumber/formatter/duration_extractor.rb +2 -0
  28. data/lib/cucumber/formatter/errors.rb +6 -0
  29. data/lib/cucumber/formatter/html.rb +24 -0
  30. data/lib/cucumber/formatter/http_io.rb +146 -0
  31. data/lib/cucumber/formatter/interceptor.rb +3 -21
  32. data/lib/cucumber/formatter/io.rb +14 -8
  33. data/lib/cucumber/formatter/json.rb +46 -36
  34. data/lib/cucumber/formatter/junit.rb +13 -11
  35. data/lib/cucumber/formatter/message.rb +22 -0
  36. data/lib/cucumber/formatter/message_builder.rb +243 -0
  37. data/lib/cucumber/formatter/pretty.rb +65 -60
  38. data/lib/cucumber/formatter/query/hook_by_test_step.rb +31 -0
  39. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  40. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  41. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  42. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +40 -0
  43. data/lib/cucumber/formatter/summary.rb +1 -1
  44. data/lib/cucumber/formatter/usage.rb +3 -3
  45. data/lib/cucumber/gherkin/data_table_parser.rb +12 -3
  46. data/lib/cucumber/gherkin/steps_parser.rb +13 -3
  47. data/lib/cucumber/glue/hook.rb +18 -2
  48. data/lib/cucumber/glue/proto_world.rb +30 -18
  49. data/lib/cucumber/glue/registry_and_more.rb +40 -3
  50. data/lib/cucumber/glue/snippet.rb +2 -2
  51. data/lib/cucumber/glue/step_definition.rb +28 -4
  52. data/lib/cucumber/hooks.rb +8 -8
  53. data/lib/cucumber/multiline_argument.rb +1 -1
  54. data/lib/cucumber/multiline_argument/data_table.rb +17 -13
  55. data/lib/cucumber/platform.rb +1 -1
  56. data/lib/cucumber/rake/task.rb +3 -0
  57. data/lib/cucumber/runtime.rb +29 -3
  58. data/lib/cucumber/runtime/after_hooks.rb +6 -2
  59. data/lib/cucumber/runtime/before_hooks.rb +6 -2
  60. data/lib/cucumber/runtime/for_programming_languages.rb +1 -0
  61. data/lib/cucumber/runtime/step_hooks.rb +3 -2
  62. data/lib/cucumber/runtime/support_code.rb +3 -3
  63. data/lib/cucumber/runtime/user_interface.rb +2 -10
  64. data/lib/cucumber/step_definitions.rb +2 -2
  65. data/lib/cucumber/version +1 -1
  66. metadata +227 -73
@@ -10,8 +10,8 @@ module Cucumber
10
10
 
11
11
  begin
12
12
  raise new(with_prefix(step_name)) # rubocop:disable Style/RaiseArgs
13
- rescue StandardError => exception
14
- return exception
13
+ rescue StandardError => e
14
+ return e
15
15
  end
16
16
  end
17
17
 
@@ -49,4 +49,7 @@ module Cucumber
49
49
  super(messages.join("\n"))
50
50
  end
51
51
  end
52
+
53
+ class LogTypeInvalid < StandardError
54
+ end
52
55
  end
@@ -24,16 +24,21 @@ module Cucumber
24
24
 
25
25
  def self.registry
26
26
  Core::Events.build_registry(
27
- TestCaseStarted,
27
+ GherkinSourceParsed,
28
+ GherkinSourceRead,
29
+ HookTestStepCreated,
30
+ StepActivated,
31
+ StepDefinitionRegistered,
32
+ TestCaseCreated,
28
33
  TestCaseFinished,
34
+ TestCaseStarted,
35
+ TestCaseReady,
36
+ TestRunFinished,
37
+ TestRunStarted,
38
+ TestStepCreated,
29
39
  TestStepFinished,
30
40
  TestStepStarted,
31
- StepDefinitionRegistered,
32
- StepActivated,
33
- TestRunFinished,
34
- GherkinSourceRead,
35
- GherkinSourceParsed,
36
- TestRunStarted
41
+ Envelope
37
42
  )
38
43
  end
39
44
  end
@@ -0,0 +1,9 @@
1
+ require 'cucumber/core/events'
2
+
3
+ module Cucumber
4
+ module Events
5
+ class Envelope < Core::Event.new(:envelope)
6
+ attr_reader :envelope
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/core/events'
4
+
5
+ module Cucumber
6
+ module Events
7
+ # Event fired when a step is created from a hook
8
+ class HookTestStepCreated < Core::Event.new(:test_step, :hook)
9
+ attr_reader :test_step
10
+ attr_reader :hook
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/core/events'
4
+
5
+ module Cucumber
6
+ module Events
7
+ # Event fired when a Test::Case is created from a Pickle
8
+ class TestCaseCreated < Core::Event.new(:test_case, :pickle)
9
+ attr_reader :test_case
10
+ attr_reader :pickle
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/core/events'
4
+
5
+ module Cucumber
6
+ module Events
7
+ # Event fired when a Test::Case is ready to be ran (matching has been done, hooks added etc)
8
+ class TestCaseReady < Core::Event.new(:test_case)
9
+ attr_reader :test_case
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/core/events'
4
+
5
+ module Cucumber
6
+ module Events
7
+ # Event fired when a TestStep is created from a PickleStep
8
+ class TestStepCreated < Core::Event.new(:test_step, :pickle_step)
9
+ attr_reader :test_step
10
+ attr_reader :pickle_step
11
+ end
12
+ end
13
+ end
@@ -11,3 +11,4 @@ require 'cucumber/filters/quit'
11
11
  require 'cucumber/filters/randomizer'
12
12
  require 'cucumber/filters/retry'
13
13
  require 'cucumber/filters/tag_limits'
14
+ require 'cucumber/filters/broadcast_test_case_ready_event'
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cucumber
4
+ module Filters
5
+ class BroadcastTestCaseReadyEvent < Core::Filter.new(:config)
6
+ def test_case(test_case)
7
+ config.notify :test_case_ready, test_case
8
+ test_case.describe_to(receiver)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -12,7 +12,7 @@ module Cucumber
12
12
  end
13
13
 
14
14
  def on_gherkin_source_parsed(event)
15
- @gherkin_documents[event.gherkin_document[:uri]] = event.gherkin_document
15
+ @gherkin_documents[event.gherkin_document.uri] = event.gherkin_document
16
16
  end
17
17
 
18
18
  def gherkin_document(uri)
@@ -34,7 +34,7 @@ module Cucumber
34
34
  def snippet_step_keyword(test_step)
35
35
  uri = test_step.location.file
36
36
  document = gherkin_document(uri)
37
- dialect = ::Gherkin::Dialect.for(document[:feature][:language])
37
+ dialect = ::Gherkin::Dialect.for(document.feature.language)
38
38
  given_when_then_keywords = [dialect.given_keywords, dialect.when_keywords, dialect.then_keywords].flatten.uniq.reject { |kw| kw == '* ' }
39
39
  keyword_lookup = step_keyword_lookup(uri)
40
40
  keyword = nil
@@ -69,25 +69,28 @@ module Cucumber
69
69
 
70
70
  def initialize(gherkin_document)
71
71
  @lookup_hash = {}
72
- process_scenario_container(gherkin_document[:feature])
72
+ process_scenario_container(gherkin_document.feature)
73
73
  end
74
74
 
75
75
  private
76
76
 
77
77
  def process_scenario_container(container)
78
- container[:children].each do |child|
79
- if !child[:rule].nil?
80
- process_scenario_container(child[:rule])
81
- elsif !child[:scenario].nil?
82
- if child[:scenario][:examples].empty?
83
- @lookup_hash[child[:scenario][:location][:line]] = ScenarioSource.new(:Scenario, child[:scenario])
84
-
85
- else
86
- child[:scenario][:examples].each do |examples|
87
- examples[:table_body].each do |row|
88
- @lookup_hash[row[:location][:line]] = ScenarioOutlineSource.new(:ScenarioOutline, child[:scenario], examples, row)
89
- end
90
- end
78
+ container.children.each do |child|
79
+ if child.respond_to?(:rule) && child.rule
80
+ process_scenario_container(child.rule)
81
+ elsif child.respond_to?(:scenario) && child.scenario
82
+ process_scenario(child)
83
+ end
84
+ end
85
+ end
86
+
87
+ def process_scenario(child)
88
+ if child.scenario.examples.empty?
89
+ @lookup_hash[child.scenario.location.line] = ScenarioSource.new(:Scenario, child.scenario)
90
+ else
91
+ child.scenario.examples.each do |examples|
92
+ examples.table_body.each do |row|
93
+ @lookup_hash[row.location.line] = ScenarioOutlineSource.new(:ScenarioOutline, child.scenario, examples, row)
91
94
  end
92
95
  end
93
96
  end
@@ -99,22 +102,22 @@ module Cucumber
99
102
 
100
103
  def initialize(gherkin_document)
101
104
  @lookup_hash = {}
102
- process_scenario_container(gherkin_document[:feature])
105
+ process_scenario_container(gherkin_document.feature)
103
106
  end
104
107
 
105
108
  private
106
109
 
107
110
  def process_scenario_container(container)
108
- container[:children].each do |child|
109
- if !child[:rule].nil?
110
- process_scenario_container(child[:rule])
111
- elsif !child[:scenario].nil?
112
- child[:scenario][:steps].each do |step|
113
- @lookup_hash[step[:location][:line]] = StepSource.new(:Step, step)
111
+ container.children.each do |child|
112
+ if child.respond_to?(:rule) && child.rule
113
+ process_scenario_container(child.rule)
114
+ elsif child.respond_to?(:scenario) && child.scenario
115
+ child.scenario.steps.each do |step|
116
+ @lookup_hash[step.location.line] = StepSource.new(:Step, step)
114
117
  end
115
- elsif !child[:background].nil?
116
- child[:background][:steps].each do |step|
117
- @lookup_hash[step[:location][:line]] = StepSource.new(:Step, step)
118
+ elsif !child.background.nil?
119
+ child.background.steps.each do |step|
120
+ @lookup_hash[step.location.line] = StepSource.new(:Step, step)
118
121
  end
119
122
  end
120
123
  end
@@ -128,32 +131,34 @@ module Cucumber
128
131
 
129
132
  def initialize(gherkin_document)
130
133
  @lookup_hash = {}
131
- process_scenario_container(gherkin_document[:feature], nil)
134
+ process_scenario_container(gherkin_document.feature, nil)
132
135
  end
133
136
 
134
137
  private
135
138
 
139
+ # rubocop:disable Metrics/PerceivedComplexity
136
140
  def process_scenario_container(container, original_previous_node)
137
- container[:children].each do |child|
141
+ container.children.each do |child|
138
142
  previous_node = original_previous_node
139
- if !child[:rule].nil?
140
- process_scenario_container(child[:rule], original_previous_node)
141
- elsif !child[:scenario].nil?
142
- child[:scenario][:steps].each do |step|
143
- node = KeywordSearchNode.new(step[:keyword], previous_node)
144
- @lookup_hash[step[:location][:line]] = node
143
+ if child.respond_to?(:rule) && child.rule
144
+ process_scenario_container(child.rule, original_previous_node)
145
+ elsif child.respond_to?(:scenario) && child.scenario
146
+ child.scenario.steps.each do |step|
147
+ node = KeywordSearchNode.new(step.keyword, previous_node)
148
+ @lookup_hash[step.location.line] = node
145
149
  previous_node = node
146
150
  end
147
- elsif !child[:background].nil?
148
- child[:background][:steps].each do |step|
149
- node = KeywordSearchNode.new(step[:keyword], previous_node)
150
- @lookup_hash[step[:location][:line]] = node
151
+ elsif child.respond_to?(:background) && child.background
152
+ child.background.steps.each do |step|
153
+ node = KeywordSearchNode.new(step.keyword, previous_node)
154
+ @lookup_hash[step.location.line] = node
151
155
  previous_node = node
152
156
  original_previous_node = previous_node
153
157
  end
154
158
  end
155
159
  end
156
160
  end
161
+ # rubocop:enable Metrics/PerceivedComplexity
157
162
  end
158
163
  end
159
164
  end
@@ -10,13 +10,16 @@ module Cucumber
10
10
  bin/cucumber:
11
11
  lib/rspec
12
12
  gems/
13
+ site_ruby/
13
14
  minitest
14
15
  test/unit
15
16
  .gem/ruby
16
- lib/ruby/
17
17
  bin/bundle
18
18
  ]
19
19
 
20
+ @backtrace_filters << RbConfig::CONFIG['rubyarchdir'] if RbConfig::CONFIG['rubyarchdir']
21
+ @backtrace_filters << RbConfig::CONFIG['rubylibdir'] if RbConfig::CONFIG['rubylibdir']
22
+
20
23
  @backtrace_filters << 'org/jruby/' if ::Cucumber::JRUBY
21
24
 
22
25
  BACKTRACE_FILTER_PATTERNS = Regexp.new(@backtrace_filters.join('|'))
@@ -143,7 +143,7 @@ module Cucumber
143
143
  return unless config.wip?
144
144
  messages = passed_test_cases.map do |test_case|
145
145
  scenario_source = ast_lookup.scenario_source(test_case)
146
- keyword = scenario_source.type == :Scenario ? scenario_source.scenario[:keyword] : scenario_source.scenario_outline[:keyword]
146
+ keyword = scenario_source.type == :Scenario ? scenario_source.scenario.keyword : scenario_source.scenario_outline.keyword
147
147
  linebreaks("#{test_case.location.on_line(test_case.location.lines.max)}:in `#{keyword}: #{test_case.name}'", ENV['CUCUMBER_TRUNCATE_OUTPUT'].to_i)
148
148
  end
149
149
  do_print_passing_wip(messages)
@@ -158,16 +158,11 @@ module Cucumber
158
158
  end
159
159
  end
160
160
 
161
- def embed(file, mime_type, label)
162
- # no-op
163
- end
164
-
165
- def puts(*messages)
161
+ def attach(src, media_type)
162
+ return unless media_type == 'text/x.cucumber.log+plain'
166
163
  return unless @io
167
164
  @io.puts
168
- messages.each do |message|
169
- @io.puts(format_string(message, :tag))
170
- end
165
+ @io.puts(format_string(src, :tag))
171
166
  @io.flush
172
167
  end
173
168
 
@@ -37,7 +37,7 @@ module Cucumber
37
37
  return [] if test_cases.empty?
38
38
  [format_string("#{type_heading(type)} Scenarios:", type)] + test_cases.map do |test_case|
39
39
  scenario_source = @ast_lookup.scenario_source(test_case)
40
- keyword = scenario_source.type == :Scenario ? scenario_source.scenario[:keyword] : scenario_source.scenario_outline[:keyword]
40
+ keyword = scenario_source.type == :Scenario ? scenario_source.scenario.keyword : scenario_source.scenario_outline.keyword
41
41
  source = @config.source? ? format_string(" # #{keyword}: #{test_case.name}", :comment) : ''
42
42
  format_string("cucumber #{profiles_string}#{test_case.location.file}:#{test_case.location.lines.max}", type) + source
43
43
  end
@@ -8,7 +8,7 @@ module Cucumber
8
8
  # <tt>time</tt> format.
9
9
  def format_duration(seconds)
10
10
  m, s = seconds.divmod(60)
11
- "#{m}m#{format('%.3f', s)}s"
11
+ "#{m}m#{format('%<seconds>.3f', seconds: s)}s"
12
12
  end
13
13
  end
14
14
  end
@@ -24,6 +24,8 @@ module Cucumber
24
24
  def duration(duration, *)
25
25
  duration.tap { |dur| @result_duration = dur.nanoseconds / 10**9.0 }
26
26
  end
27
+
28
+ def attach(*) end
27
29
  end
28
30
  end
29
31
  end
@@ -0,0 +1,6 @@
1
+ module Cucumber
2
+ module Formatter
3
+ class TestCaseUnknownError < StandardError; end
4
+ class TestStepUnknownError < StandardError; end
5
+ end
6
+ end
@@ -0,0 +1,24 @@
1
+ require 'cucumber/formatter/io'
2
+ require 'cucumber/html_formatter'
3
+ require 'cucumber/formatter/message_builder'
4
+
5
+ module Cucumber
6
+ module Formatter
7
+ class HTML < MessageBuilder
8
+ include Io
9
+
10
+ def initialize(config)
11
+ @io = ensure_io(config.out_stream)
12
+ @html_formatter = Cucumber::HTMLFormatter::Formatter.new(@io)
13
+ @html_formatter.write_pre_message
14
+
15
+ super(config)
16
+ end
17
+
18
+ def output_envelope(envelope)
19
+ @html_formatter.write_message(envelope)
20
+ @html_formatter.write_post_message if envelope.test_run_finished
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,146 @@
1
+ require 'net/http'
2
+ require 'tempfile'
3
+
4
+ module Cucumber
5
+ module Formatter
6
+ class HTTPIO
7
+ class << self
8
+ # Returns an IO that will write to a HTTP request's body
9
+ def open(url, https_verify_mode = nil)
10
+ @https_verify_mode = https_verify_mode
11
+ uri, method, headers = CurlOptionParser.parse(url)
12
+ IOHTTPBuffer.new(uri, method, headers, https_verify_mode)
13
+ end
14
+ end
15
+ end
16
+
17
+ class CurlOptionParser
18
+ def self.parse(options)
19
+ chunks = options.split(/\s/).compact
20
+ http_method = 'PUT'
21
+ url = chunks[0]
22
+ headers = ''
23
+
24
+ last_flag = nil
25
+ chunks.each do |chunk|
26
+ if ['-X', '--request'].include?(chunk)
27
+ last_flag = '-X'
28
+ next
29
+ end
30
+
31
+ if chunk == '-H'
32
+ last_flag = '-H'
33
+ next
34
+ end
35
+
36
+ if last_flag == '-X'
37
+ http_method = chunk
38
+ last_flag = nil
39
+ end
40
+
41
+ headers += chunk if last_flag == '-H'
42
+ end
43
+
44
+ [
45
+ url,
46
+ http_method,
47
+ make_headers(headers)
48
+ ]
49
+ end
50
+
51
+ def self.make_headers(headers)
52
+ hash_headers = {}
53
+ str_scanner = /("(?<key>[^":]+)\s*:\s*(?<value>[^":]+)")|('(?<key1>[^':]+)\s*:\s*(?<value1>[^':]+)')/
54
+
55
+ headers.scan(str_scanner) do |header|
56
+ header = header.compact!
57
+ hash_headers[header[0]] = header[1]&.strip
58
+ end
59
+
60
+ hash_headers
61
+ end
62
+ end
63
+
64
+ class IOHTTPBuffer
65
+ attr_reader :uri, :method, :headers
66
+
67
+ def initialize(uri, method, headers = {}, https_verify_mode = nil)
68
+ @uri = URI(uri)
69
+ @method = method
70
+ @headers = headers
71
+ @write_io = Tempfile.new('cucumber', encoding: 'UTF-8')
72
+ @https_verify_mode = https_verify_mode
73
+ end
74
+
75
+ def close
76
+ post_content(@uri, @method, @headers)
77
+ @write_io.close
78
+ end
79
+
80
+ def write(data)
81
+ @write_io.write(data)
82
+ end
83
+
84
+ def flush
85
+ @write_io.flush
86
+ end
87
+
88
+ def closed?
89
+ @write_io.closed?
90
+ end
91
+
92
+ private
93
+
94
+ def post_content(uri, method, headers, attempt = 10)
95
+ content = @write_io
96
+ http = build_client(uri, @https_verify_mode)
97
+
98
+ raise StandardError, "request to #{uri} failed (too many redirections)" if attempt <= 0
99
+ req = build_request(
100
+ uri,
101
+ method,
102
+ headers.merge(
103
+ 'Content-Length' => content.size.to_s
104
+ )
105
+ )
106
+
107
+ content.rewind
108
+ req.body_stream = content
109
+
110
+ begin
111
+ response = http.request(req)
112
+ rescue SystemCallError
113
+ # We may get the redirect response before pushing the file.
114
+ response = http.request(build_request(uri, method, headers))
115
+ end
116
+
117
+ case response
118
+ when Net::HTTPSuccess
119
+ response
120
+ when Net::HTTPRedirection
121
+ post_content(URI(response['Location']), method, headers, attempt - 1)
122
+ else
123
+ raise StandardError, "request to #{uri} failed with status #{response.code}"
124
+ end
125
+ end
126
+
127
+ def build_request(uri, method, headers)
128
+ method_class_name = "#{method[0].upcase}#{method[1..-1].downcase}"
129
+ req = Net::HTTP.const_get(method_class_name).new(uri)
130
+ headers.each do |header, value|
131
+ req[header] = value
132
+ end
133
+ req
134
+ end
135
+
136
+ def build_client(uri, https_verify_mode)
137
+ http = Net::HTTP.new(uri.hostname, uri.port)
138
+ if uri.scheme == 'https'
139
+ http.use_ssl = true
140
+ http.verify_mode = https_verify_mode if https_verify_mode
141
+ end
142
+ http
143
+ end
144
+ end
145
+ end
146
+ end