cucumber 4.0.0.rc.3 → 4.0.1

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -2
  3. data/README.md +2 -2
  4. data/lib/cucumber/cli/options.rb +10 -4
  5. data/lib/cucumber/configuration.rb +5 -0
  6. data/lib/cucumber/deprecate.rb +29 -5
  7. data/lib/cucumber/events.rb +13 -7
  8. data/lib/cucumber/events/envelope.rb +9 -0
  9. data/lib/cucumber/events/hook_test_step_created.rb +13 -0
  10. data/lib/cucumber/events/test_case_created.rb +13 -0
  11. data/lib/cucumber/events/test_case_ready.rb +12 -0
  12. data/lib/cucumber/events/test_step_created.rb +13 -0
  13. data/lib/cucumber/events/undefined_parameter_type.rb +10 -0
  14. data/lib/cucumber/filters.rb +1 -0
  15. data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
  16. data/lib/cucumber/formatter/console.rb +33 -10
  17. data/lib/cucumber/formatter/duration_extractor.rb +1 -1
  18. data/lib/cucumber/formatter/errors.rb +6 -0
  19. data/lib/cucumber/formatter/html.rb +24 -0
  20. data/lib/cucumber/formatter/http_io.rb +146 -0
  21. data/lib/cucumber/formatter/interceptor.rb +3 -8
  22. data/lib/cucumber/formatter/io.rb +14 -8
  23. data/lib/cucumber/formatter/json.rb +17 -8
  24. data/lib/cucumber/formatter/junit.rb +1 -1
  25. data/lib/cucumber/formatter/message.rb +22 -0
  26. data/lib/cucumber/formatter/message_builder.rb +255 -0
  27. data/lib/cucumber/formatter/pretty.rb +10 -4
  28. data/lib/cucumber/formatter/progress.rb +2 -0
  29. data/lib/cucumber/formatter/query/hook_by_test_step.rb +31 -0
  30. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  31. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  32. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  33. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +40 -0
  34. data/lib/cucumber/gherkin/data_table_parser.rb +1 -1
  35. data/lib/cucumber/gherkin/steps_parser.rb +1 -1
  36. data/lib/cucumber/glue/hook.rb +18 -2
  37. data/lib/cucumber/glue/proto_world.rb +29 -18
  38. data/lib/cucumber/glue/registry_and_more.rb +27 -2
  39. data/lib/cucumber/glue/snippet.rb +1 -1
  40. data/lib/cucumber/glue/step_definition.rb +28 -4
  41. data/lib/cucumber/hooks.rb +8 -8
  42. data/lib/cucumber/multiline_argument.rb +1 -1
  43. data/lib/cucumber/multiline_argument/data_table.rb +17 -13
  44. data/lib/cucumber/platform.rb +1 -1
  45. data/lib/cucumber/rake/task.rb +1 -1
  46. data/lib/cucumber/runtime.rb +29 -3
  47. data/lib/cucumber/runtime/after_hooks.rb +6 -2
  48. data/lib/cucumber/runtime/before_hooks.rb +6 -2
  49. data/lib/cucumber/runtime/for_programming_languages.rb +1 -0
  50. data/lib/cucumber/runtime/step_hooks.rb +3 -2
  51. data/lib/cucumber/runtime/support_code.rb +3 -3
  52. data/lib/cucumber/runtime/user_interface.rb +2 -10
  53. data/lib/cucumber/version +1 -1
  54. metadata +116 -93
@@ -36,6 +36,7 @@ module Cucumber
36
36
  @config = config
37
37
  @options = config.to_hash
38
38
  @snippets_input = []
39
+ @undefined_parameter_types = []
39
40
  @total_duration = 0
40
41
  @exceptions = []
41
42
  @gherkin_sources = {}
@@ -54,6 +55,11 @@ module Cucumber
54
55
  @passed_test_cases = []
55
56
  @source_indent = 0
56
57
  @next_comment_to_be_printed = 0
58
+
59
+ bind_events(config)
60
+ end
61
+
62
+ def bind_events(config)
57
63
  config.on_event :gherkin_source_read, &method(:on_gherkin_source_read)
58
64
  config.on_event :step_activated, &method(:on_step_activated)
59
65
  config.on_event :test_case_started, &method(:on_test_case_started)
@@ -61,6 +67,7 @@ module Cucumber
61
67
  config.on_event :test_step_finished, &method(:on_test_step_finished)
62
68
  config.on_event :test_case_finished, &method(:on_test_case_finished)
63
69
  config.on_event :test_run_finished, &method(:on_test_run_finished)
70
+ config.on_event :undefined_parameter_type, &method(:collect_undefined_parameter_type_names)
64
71
  end
65
72
 
66
73
  def on_gherkin_source_read(event)
@@ -131,10 +138,9 @@ module Cucumber
131
138
  print_summary
132
139
  end
133
140
 
134
- def puts(*messages)
135
- messages.each do |message|
136
- @test_step_output.push message
137
- end
141
+ def attach(src, media_type)
142
+ return unless media_type == 'text/x.cucumber.log+plain'
143
+ @test_step_output.push src
138
144
  end
139
145
 
140
146
  private
@@ -21,6 +21,7 @@ module Cucumber
21
21
  @config = config
22
22
  @io = ensure_io(config.out_stream)
23
23
  @snippets_input = []
24
+ @undefined_parameter_types = []
24
25
  @total_duration = 0
25
26
  @matches = {}
26
27
  @pending_step_matches = []
@@ -36,6 +37,7 @@ module Cucumber
36
37
  config.on_event :test_step_finished, &method(:on_test_step_finished)
37
38
  config.on_event :test_case_finished, &method(:on_test_case_finished)
38
39
  config.on_event :test_run_finished, &method(:on_test_run_finished)
40
+ config.on_event :undefined_parameter_type, &method(:collect_undefined_parameter_type_names)
39
41
  end
40
42
 
41
43
  def on_step_activated(event)
@@ -0,0 +1,31 @@
1
+ require 'cucumber/formatter/errors'
2
+
3
+ module Cucumber
4
+ module Formatter
5
+ module Query
6
+ class HookByTestStep
7
+ def initialize(config)
8
+ @hook_id_by_test_step_id = {}
9
+
10
+ config.on_event :test_step_created, &method(:on_test_step_created)
11
+ config.on_event :hook_test_step_created, &method(:on_hook_test_step_created)
12
+ end
13
+
14
+ def hook_id(test_step)
15
+ return @hook_id_by_test_step_id[test_step.id] if @hook_id_by_test_step_id.key?(test_step.id)
16
+ raise TestStepUnknownError, "No hook found for #{test_step.id} }. Known: #{@hook_id_by_test_step_id.keys}"
17
+ end
18
+
19
+ private
20
+
21
+ def on_test_step_created(event)
22
+ @hook_id_by_test_step_id[event.test_step.id] = nil
23
+ end
24
+
25
+ def on_hook_test_step_created(event)
26
+ @hook_id_by_test_step_id[event.test_step.id] = event.hook.id
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ require 'cucumber/formatter/errors'
2
+
3
+ module Cucumber
4
+ module Formatter
5
+ module Query
6
+ class PickleByTest
7
+ def initialize(config)
8
+ @pickle_id_by_test_case_id = {}
9
+ config.on_event :test_case_created, &method(:on_test_case_created)
10
+ end
11
+
12
+ def pickle_id(test_case)
13
+ return @pickle_id_by_test_case_id[test_case.id] if @pickle_id_by_test_case_id.key?(test_case.id)
14
+
15
+ raise TestCaseUnknownError, "No pickle found for #{test_case.id} }. Known: #{@pickle_id_by_test_case_id.keys}"
16
+ end
17
+
18
+ private
19
+
20
+ def on_test_case_created(event)
21
+ @pickle_id_by_test_case_id[event.test_case.id] = event.pickle.id
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ require 'cucumber/formatter/errors'
2
+
3
+ module Cucumber
4
+ module Formatter
5
+ module Query
6
+ class PickleStepByTestStep
7
+ def initialize(config)
8
+ @pickle_id_step_by_test_step_id = {}
9
+ config.on_event :test_step_created, &method(:on_test_step_created)
10
+ end
11
+
12
+ def pickle_step_id(test_step)
13
+ return @pickle_id_step_by_test_step_id[test_step.id] if @pickle_id_step_by_test_step_id.key?(test_step.id)
14
+
15
+ raise TestStepUnknownError, "No pickle step found for #{test_step.id} }. Known: #{@pickle_id_step_by_test_step_id.keys}"
16
+ end
17
+
18
+ private
19
+
20
+ def on_test_step_created(event)
21
+ @pickle_id_step_by_test_step_id[event.test_step.id] = event.pickle_step.id
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,40 @@
1
+ require 'cucumber/formatter/errors'
2
+
3
+ module Cucumber
4
+ module Formatter
5
+ module Query
6
+ class StepDefinitionsByTestStep
7
+ def initialize(config)
8
+ @step_definition_ids_by_test_step_id = {}
9
+ @step_match_arguments_by_test_step_id = {}
10
+
11
+ config.on_event :test_step_created, &method(:on_test_step_created)
12
+ config.on_event :step_activated, &method(:on_step_activated)
13
+ end
14
+
15
+ def step_definition_ids(test_step)
16
+ return @step_definition_ids_by_test_step_id[test_step.id] if @step_definition_ids_by_test_step_id.key?(test_step.id)
17
+
18
+ raise TestStepUnknownError, "No step definition found for #{test_step.id} }. Known: #{@step_definition_ids_by_test_step_id.keys}"
19
+ end
20
+
21
+ def step_match_arguments(test_step)
22
+ return @step_match_arguments_by_test_step_id[test_step.id] if @step_match_arguments_by_test_step_id.key?(test_step.id)
23
+
24
+ raise TestStepUnknownError, "No step match arguments found for #{test_step.id} }. Known: #{@step_match_arguments_by_test_step_id.keys}"
25
+ end
26
+
27
+ private
28
+
29
+ def on_test_step_created(event)
30
+ @step_definition_ids_by_test_step_id[event.test_step.id] = []
31
+ end
32
+
33
+ def on_step_activated(event)
34
+ @step_definition_ids_by_test_step_id[event.test_step.id] << event.step_match.step_definition.id
35
+ @step_match_arguments_by_test_step_id[event.test_step.id] = event.step_match.step_arguments
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ require 'cucumber/formatter/errors'
2
+
3
+ module Cucumber
4
+ module Formatter
5
+ module Query
6
+ class TestCaseStartedByTestCase
7
+ def initialize(config)
8
+ @config = config
9
+ config.on_event :test_case_created, &method(:on_test_case_created)
10
+ config.on_event :test_case_started, &method(:on_test_case_started)
11
+
12
+ @attempts_by_test_case_id = {}
13
+ @test_case_started_id_by_test_case_id = {}
14
+ end
15
+
16
+ def attempt_by_test_case(test_case)
17
+ raise TestCaseUnknownError, "No test case found for #{test_case.id} }. Known: #{@attempts_by_test_case_id.keys}" unless @attempts_by_test_case_id.key?(test_case.id)
18
+ @attempts_by_test_case_id[test_case.id]
19
+ end
20
+
21
+ def test_case_started_id_by_test_case(test_case)
22
+ raise TestCaseUnknownError, "No test case found for #{test_case.id} }. Known: #{@test_case_started_id_by_test_case_id.keys}" unless @test_case_started_id_by_test_case_id.key?(test_case.id)
23
+ @test_case_started_id_by_test_case_id[test_case.id]
24
+ end
25
+
26
+ private
27
+
28
+ def on_test_case_created(event)
29
+ @attempts_by_test_case_id[event.test_case.id] = 0
30
+ @test_case_started_id_by_test_case_id[event.test_case.id] = nil
31
+ end
32
+
33
+ def on_test_case_started(event)
34
+ @attempts_by_test_case_id[event.test_case.id] += 1
35
+ @test_case_started_id_by_test_case_id[event.test_case.id] = @config.id_generator.new_id
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -15,7 +15,7 @@ module Cucumber
15
15
  messages = ::Gherkin.from_source('dummy', feature_header + text, gherkin_options)
16
16
 
17
17
  messages.each do |message|
18
- gherkin_document = message.gherkinDocument.to_hash unless message.gherkinDocument.nil?
18
+ gherkin_document = message.gherkin_document.to_hash unless message.gherkin_document.nil?
19
19
  end
20
20
 
21
21
  return if gherkin_document.nil?
@@ -17,7 +17,7 @@ module Cucumber
17
17
  messages = ::Gherkin.from_source('dummy', feature_header(dialect) + text, gherkin_options)
18
18
 
19
19
  messages.each do |message|
20
- gherkin_document = message.gherkinDocument.to_hash unless message.gherkinDocument.nil?
20
+ gherkin_document = message.gherkin_document.to_hash unless message.gherkin_document.nil?
21
21
  end
22
22
 
23
23
  @builder.steps(gherkin_document[:feature][:children][0][:scenario][:steps])
@@ -6,9 +6,10 @@ module Cucumber
6
6
  module Glue
7
7
  # TODO: Kill pointless wrapper for Before, After and AfterStep hooks with fire
8
8
  class Hook
9
- attr_reader :tag_expressions, :location
9
+ attr_reader :id, :tag_expressions, :location
10
10
 
11
- def initialize(registry, tag_expressions, proc)
11
+ def initialize(id, registry, tag_expressions, proc)
12
+ @id = id
12
13
  @registry = registry
13
14
  @tag_expressions = sanitize_tag_expressions(tag_expressions)
14
15
  @proc = proc
@@ -27,6 +28,21 @@ module Cucumber
27
28
  )
28
29
  end
29
30
 
31
+ def to_envelope
32
+ Cucumber::Messages::Envelope.new(
33
+ hook: Cucumber::Messages::Hook.new(
34
+ id: id,
35
+ tag_expression: tag_expressions.join(' '),
36
+ source_reference: Cucumber::Messages::SourceReference.new(
37
+ uri: location.file,
38
+ location: Cucumber::Messages::Location.new(
39
+ line: location.lines.first
40
+ )
41
+ )
42
+ )
43
+ )
44
+ end
45
+
30
46
  private
31
47
 
32
48
  def sanitize_tag_expressions(tag_expressions)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'cucumber/gherkin/formatter/ansi_escapes'
4
4
  require 'cucumber/core/test/data_table'
5
+ require 'cucumber/deprecate'
5
6
 
6
7
  module Cucumber
7
8
  module Glue
@@ -67,9 +68,8 @@ module Cucumber
67
68
  # %w{ CUC-101 Peeler 22 }
68
69
  # ])
69
70
  #
70
- def table(text_or_table, file = nil, line = 0)
71
- location = !file ? Core::Test::Location.of_caller : Core::Test::Location.new(file, line)
72
- MultilineArgument::DataTable.from(text_or_table, location)
71
+ def table(text_or_table)
72
+ MultilineArgument::DataTable.from(text_or_table)
73
73
  end
74
74
 
75
75
  # Print a message to the output.
@@ -80,7 +80,16 @@ module Cucumber
80
80
  #
81
81
  # If you'd prefer to see the message immediately, call {Kernel.puts} instead.
82
82
  def puts(*messages)
83
- super
83
+ Cucumber.deprecate(
84
+ 'Messages emitted with "puts" will no longer be caught by Cucumber ' \
85
+ 'and sent to the formatter. If you want message to be in the formatted output, ' \
86
+ "please use log(message) instead.\n" \
87
+ 'If you simply want it in the console, '\
88
+ 'keep using "puts" (or Kernel.puts to avoid this message)',
89
+ 'puts(message)',
90
+ '5.0.0'
91
+ )
92
+ messages.each { |message| log(message.to_s) }
84
93
  end
85
94
 
86
95
  # Pause the tests and ask the operator for input
@@ -89,7 +98,20 @@ module Cucumber
89
98
  end
90
99
 
91
100
  # Embed an image in the output
92
- def embed(file, mime_type, label = 'Screenshot')
101
+ def embed(file, mime_type, _label = 'Screenshot')
102
+ Cucumber.deprecate(
103
+ 'Please use attach(file, media_type) instead',
104
+ 'embed(file, mime_type, label)',
105
+ '5.0.0'
106
+ )
107
+ attach(file, mime_type)
108
+ end
109
+
110
+ def log(*messages)
111
+ messages.each { |message| attach(message.to_s.dup, 'text/x.cucumber.log+plain') }
112
+ end
113
+
114
+ def attach(file, media_type)
93
115
  super
94
116
  end
95
117
 
@@ -146,23 +168,12 @@ module Cucumber
146
168
  runtime.invoke_dynamic_steps(steps_text, language, location)
147
169
  end
148
170
 
149
- # rubocop:disable Style/UnneededInterpolation
150
- define_method(:puts) do |*messages|
151
- # Even though they won't be output until later, converting the messages to
152
- # strings right away will protect them from modifications to their original
153
- # objects in the mean time
154
- messages.collect! { |message| "#{message}" }
155
-
156
- runtime.puts(*messages)
157
- end
158
- # rubocop:enable Style/UnneededInterpolation
159
-
160
171
  define_method(:ask) do |question, timeout_seconds = 60|
161
172
  runtime.ask(question, timeout_seconds)
162
173
  end
163
174
 
164
- define_method(:embed) do |file, mime_type, label = 'Screenshot'|
165
- runtime.embed(file, mime_type, label)
175
+ define_method(:attach) do |file, media_type|
176
+ runtime.attach(file, media_type)
166
177
  end
167
178
 
168
179
  # Prints the list of modules that are included in the World
@@ -72,18 +72,28 @@ module Cucumber
72
72
  end
73
73
 
74
74
  def register_rb_hook(phase, tag_expressions, proc)
75
- add_hook(phase, Hook.new(self, tag_expressions, proc))
75
+ hook = add_hook(phase, Hook.new(@configuration.id_generator.new_id, self, tag_expressions, proc))
76
+ @configuration.notify :envelope, hook.to_envelope
77
+ hook
76
78
  end
77
79
 
78
80
  def define_parameter_type(parameter_type)
81
+ @configuration.notify :envelope, parameter_type_envelope(parameter_type)
82
+
79
83
  @parameter_type_registry.define_parameter_type(parameter_type)
80
84
  end
81
85
 
82
86
  def register_rb_step_definition(string_or_regexp, proc_or_sym, options)
83
- step_definition = StepDefinition.new(self, string_or_regexp, proc_or_sym, options)
87
+ step_definition = StepDefinition.new(@configuration.id_generator.new_id, self, string_or_regexp, proc_or_sym, options)
84
88
  @step_definitions << step_definition
85
89
  @configuration.notify :step_definition_registered, step_definition
90
+ @configuration.notify :envelope, step_definition.to_envelope
86
91
  step_definition
92
+ rescue Cucumber::CucumberExpressions::UndefinedParameterTypeError => e
93
+ # TODO: add a way to extract the parameter type directly from the error.
94
+ type_name = e.message.match(/^Undefined parameter type \{(.*)\}$/)[1]
95
+
96
+ @configuration.notify :undefined_parameter_type, type_name, string_or_regexp
87
97
  end
88
98
 
89
99
  def build_rb_world_factory(world_modules, namespaced_world_modules, proc)
@@ -171,6 +181,21 @@ module Cucumber
171
181
 
172
182
  private
173
183
 
184
+ def parameter_type_envelope(parameter_type)
185
+ # TODO: should me moved to Cucumber::Expression::ParameterType#to_envelope ?
186
+ # Note: that would mean that cucumber-expression would depend on cucumber-messages
187
+
188
+ Cucumber::Messages::Envelope.new(
189
+ parameter_type: Cucumber::Messages::ParameterType.new(
190
+ id: @configuration.id_generator.new_id,
191
+ name: parameter_type.name,
192
+ regular_expressions: parameter_type.regexps.map(&:to_s),
193
+ prefer_for_regular_expression_match: parameter_type.prefer_for_regexp_match?,
194
+ use_for_snippets: parameter_type.use_for_snippets?
195
+ )
196
+ )
197
+ end
198
+
174
199
  def available_step_definition_hash
175
200
  @available_step_definition_hash ||= {}
176
201
  end
@@ -174,7 +174,7 @@ module Cucumber
174
174
 
175
175
  class DocString
176
176
  def append_block_parameter_to(array)
177
- array << 'string'
177
+ array << 'doc_string'
178
178
  end
179
179
 
180
180
  def append_comment_to(string); end
@@ -24,9 +24,9 @@ module Cucumber
24
24
  end
25
25
 
26
26
  class << self
27
- def new(registry, string_or_regexp, proc_or_sym, options)
27
+ def new(id, registry, string_or_regexp, proc_or_sym, options)
28
28
  raise MissingProc if proc_or_sym.nil?
29
- super registry, registry.create_expression(string_or_regexp), create_proc(proc_or_sym, options)
29
+ super id, registry, registry.create_expression(string_or_regexp), create_proc(proc_or_sym, options)
30
30
  end
31
31
 
32
32
  private
@@ -62,16 +62,40 @@ module Cucumber
62
62
  end
63
63
  end
64
64
 
65
- attr_reader :expression, :registry
65
+ attr_reader :id, :expression, :registry
66
66
 
67
- def initialize(registry, expression, proc)
67
+ def initialize(id, registry, expression, proc)
68
68
  raise 'No regexp' if expression.is_a?(Regexp)
69
+ @id = id
69
70
  @registry = registry
70
71
  @expression = expression
71
72
  @proc = proc
72
73
  # @registry.available_step_definition(regexp_source, location)
73
74
  end
74
75
 
76
+ def to_envelope
77
+ Cucumber::Messages::Envelope.new(
78
+ step_definition: Cucumber::Messages::StepDefinition.new(
79
+ id: id,
80
+ pattern: Cucumber::Messages::StepDefinition::StepDefinitionPattern.new(
81
+ source: expression.source.to_s,
82
+ type: expression_type
83
+ ),
84
+ source_reference: Cucumber::Messages::SourceReference.new(
85
+ uri: location.file,
86
+ location: Cucumber::Messages::Location.new(
87
+ line: location.lines.first
88
+ )
89
+ )
90
+ )
91
+ )
92
+ end
93
+
94
+ def expression_type
95
+ return Cucumber::Messages::StepDefinition::StepDefinitionPattern::StepDefinitionPatternType::CUCUMBER_EXPRESSION if expression.is_a?(CucumberExpressions::CucumberExpression)
96
+ Cucumber::Messages::StepDefinition::StepDefinitionPattern::StepDefinitionPatternType::REGULAR_EXPRESSION
97
+ end
98
+
75
99
  # @api private
76
100
  def to_hash
77
101
  type = expression.is_a?(CucumberExpressions::RegularExpression) ? 'regular expression' : 'cucumber expression'