cucumber 2.1.0 → 2.2.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +10 -0
  3. data/Gemfile +3 -1
  4. data/History.md +18 -2
  5. data/README.md +5 -1
  6. data/cucumber.gemspec +3 -4
  7. data/cucumber.yml +2 -2
  8. data/features/docs/api/run_cli_main_with_existing_runtime.feature +4 -7
  9. data/features/docs/defining_steps/skip_scenario.feature +6 -2
  10. data/features/docs/formatters/api_methods.feature +36 -0
  11. data/features/docs/profiles.feature +2 -2
  12. data/features/lib/step_definitions/profile_steps.rb +1 -1
  13. data/lib/cucumber.rb +11 -4
  14. data/lib/cucumber/cli/configuration.rb +2 -2
  15. data/lib/cucumber/cli/options.rb +2 -2
  16. data/lib/cucumber/configuration.rb +25 -3
  17. data/lib/cucumber/deprecate.rb +29 -0
  18. data/lib/cucumber/filters/activate_steps.rb +39 -5
  19. data/lib/cucumber/formatter/console.rb +4 -4
  20. data/lib/cucumber/formatter/io.rb +1 -1
  21. data/lib/cucumber/formatter/legacy_api/adapter.rb +30 -30
  22. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +4 -9
  23. data/lib/cucumber/platform.rb +2 -7
  24. data/lib/cucumber/rb_support/rb_language.rb +72 -26
  25. data/lib/cucumber/rb_support/rb_step_definition.rb +2 -2
  26. data/lib/cucumber/rb_support/rb_world.rb +6 -1
  27. data/lib/cucumber/rb_support/snippet.rb +21 -0
  28. data/lib/cucumber/running_test_case.rb +5 -1
  29. data/lib/cucumber/runtime.rb +11 -15
  30. data/lib/cucumber/runtime/support_code.rb +20 -128
  31. data/lib/cucumber/step_argument.rb +25 -0
  32. data/lib/cucumber/step_match.rb +6 -12
  33. data/lib/cucumber/step_match_search.rb +67 -0
  34. data/lib/cucumber/version +1 -0
  35. data/spec/cucumber/configuration_spec.rb +3 -2
  36. data/spec/cucumber/filters/activate_steps_spec.rb +95 -3
  37. data/spec/cucumber/formatter/html_spec.rb +1 -1
  38. data/spec/cucumber/formatter/legacy_api/adapter_spec.rb +55 -28
  39. data/spec/cucumber/formatter/pretty_spec.rb +2 -2
  40. data/spec/cucumber/formatter/spec_helper.rb +22 -12
  41. data/spec/cucumber/rb_support/rb_language_spec.rb +9 -45
  42. data/spec/cucumber/rb_support/rb_step_definition_spec.rb +2 -2
  43. data/spec/cucumber/rb_support/rb_world_spec.rb +47 -0
  44. data/spec/cucumber/runtime/for_programming_languages_spec.rb +1 -1
  45. data/spec/cucumber/runtime/support_code_spec.rb +4 -111
  46. data/spec/cucumber/step_argument_spec.rb +18 -0
  47. data/spec/cucumber/step_match_search_spec.rb +122 -0
  48. data/spec/cucumber/step_match_spec.rb +8 -2
  49. data/spec/cucumber/world/pending_spec.rb +2 -1
  50. data/spec/cucumber_spec.rb +39 -0
  51. metadata +45 -50
  52. data/features/docs/wire_protocol/erb_configuration.feature +0 -56
  53. data/features/docs/wire_protocol/handle_unexpected_response.feature +0 -30
  54. data/features/docs/wire_protocol/invoke_message.feature +0 -216
  55. data/features/docs/wire_protocol/readme.md +0 -26
  56. data/features/docs/wire_protocol/snippets_message.feature +0 -51
  57. data/features/docs/wire_protocol/step_matches_message.feature +0 -81
  58. data/features/docs/wire_protocol/table_diffing.feature +0 -126
  59. data/features/docs/wire_protocol/tags.feature +0 -87
  60. data/features/docs/wire_protocol/timeouts.feature +0 -64
  61. data/lib/cucumber/events/bus.rb +0 -86
  62. data/lib/cucumber/gherkin/formatter/argument.rb +0 -17
  63. data/lib/cucumber/gherkin/formatter/hashable.rb +0 -27
  64. data/lib/cucumber/language_support.rb +0 -30
  65. data/lib/cucumber/language_support/language_methods.rb +0 -72
  66. data/lib/cucumber/rb_support/regexp_argument_matcher.rb +0 -21
  67. data/lib/cucumber/wire_support/configuration.rb +0 -38
  68. data/lib/cucumber/wire_support/connection.rb +0 -61
  69. data/lib/cucumber/wire_support/request_handler.rb +0 -32
  70. data/lib/cucumber/wire_support/wire_exception.rb +0 -32
  71. data/lib/cucumber/wire_support/wire_language.rb +0 -68
  72. data/lib/cucumber/wire_support/wire_packet.rb +0 -34
  73. data/lib/cucumber/wire_support/wire_protocol.rb +0 -43
  74. data/lib/cucumber/wire_support/wire_protocol/requests.rb +0 -133
  75. data/lib/cucumber/wire_support/wire_step_definition.rb +0 -21
  76. data/spec/cucumber/events/bus_spec.rb +0 -94
  77. data/spec/cucumber/rb_support/regexp_argument_matcher_spec.rb +0 -22
  78. data/spec/cucumber/wire_support/configuration_spec.rb +0 -64
  79. data/spec/cucumber/wire_support/connection_spec.rb +0 -64
  80. data/spec/cucumber/wire_support/wire_exception_spec.rb +0 -50
  81. data/spec/cucumber/wire_support/wire_language_spec.rb +0 -46
  82. data/spec/cucumber/wire_support/wire_packet_spec.rb +0 -44
@@ -1,4 +1,5 @@
1
1
  require 'delegate'
2
+ require 'cucumber/deprecate'
2
3
 
3
4
  module Cucumber
4
5
  # Represents the current status of a running test case.
@@ -94,7 +95,10 @@ module Cucumber
94
95
  end
95
96
 
96
97
  def skip_invoke!
97
- Cucumber.deprecate(self.class.name, __method__, "Call #skip_this_scenario on the World directly")
98
+ Cucumber.deprecate(
99
+ "Just call #skip_this_scenario directly",
100
+ "skip_invoke!",
101
+ "2.9.9")
98
102
  raise Cucumber::Core::Test::Result::Skipped
99
103
  end
100
104
 
@@ -4,13 +4,13 @@ require 'multi_json'
4
4
  require 'multi_test'
5
5
  require 'cucumber/configuration'
6
6
  require 'cucumber/load_path'
7
- require 'cucumber/language_support/language_methods'
8
7
  require 'cucumber/formatter/duration'
9
8
  require 'cucumber/file_specs'
10
9
  require 'cucumber/filters'
11
10
  require 'cucumber/formatter/fanout'
12
11
  require 'cucumber/formatter/event_bus_report'
13
12
  require 'cucumber/gherkin/i18n'
13
+ require 'cucumber/step_match_search'
14
14
 
15
15
  module Cucumber
16
16
  module FixRuby21Bug9285
@@ -57,12 +57,10 @@ module Cucumber
57
57
  @support_code.configure(@configuration)
58
58
  end
59
59
 
60
- def load_programming_language(language)
61
- @support_code.load_programming_language(language)
62
- end
63
-
60
+ require 'cucumber/wire/plugin'
64
61
  def run!
65
62
  load_step_definitions
63
+ install_wire_plugin
66
64
  fire_after_configuration_hook
67
65
  self.visitor = report
68
66
 
@@ -90,10 +88,6 @@ module Cucumber
90
88
  @support_code.unmatched_step_definitions
91
89
  end
92
90
 
93
- def snippet_text(step_keyword, step_name, multiline_arg) #:nodoc:
94
- @support_code.snippet_text(Cucumber::Gherkin::I18n.code_keyword_for(step_keyword).strip, step_name, multiline_arg)
95
- end
96
-
97
91
  def begin_scenario(scenario)
98
92
  @support_code.fire_hook(:begin_scenario, scenario)
99
93
  end
@@ -102,10 +96,6 @@ module Cucumber
102
96
  @support_code.fire_hook(:end_scenario)
103
97
  end
104
98
 
105
- def unknown_programming_language?
106
- @support_code.unknown_programming_language?
107
- end
108
-
109
99
  # Returns Ast::DocString for +string_without_triple_quotes+.
110
100
  #
111
101
  def doc_string(string_without_triple_quotes, content_type='', line_offset=0)
@@ -213,7 +203,7 @@ module Cucumber
213
203
  formatter = factory.new(runtime_facade, path_or_io, options)
214
204
  Formatter::LegacyApi::Adapter.new(
215
205
  Formatter::IgnoreMissingMessages.new(formatter),
216
- results, @support_code, @configuration)
206
+ results, @configuration)
217
207
  end
218
208
 
219
209
  def legacy_formatter?(factory)
@@ -242,7 +232,9 @@ module Cucumber
242
232
  filters << Cucumber::Core::Test::NameFilter.new(name_regexps)
243
233
  filters << Cucumber::Core::Test::LocationsFilter.new(filespecs.locations)
244
234
  filters << Filters::Quit.new
245
- filters << Filters::ActivateSteps.new(@support_code)
235
+ # TODO: can we just use RbLanguages's step definitions directly?
236
+ step_match_search = StepMatchSearch.new(@support_code.ruby.method(:step_matches), @configuration)
237
+ filters << Filters::ActivateSteps.new(step_match_search, @configuration)
246
238
  @configuration.filters.each do |filter|
247
239
  filters << filter
248
240
  end
@@ -262,6 +254,10 @@ module Cucumber
262
254
  @support_code.load_files!(files)
263
255
  end
264
256
 
257
+ def install_wire_plugin
258
+ Cucumber::Wire::Plugin.new(@configuration).install if @configuration.all_files_to_load.any? {|f| f =~ %r{\.wire$} }
259
+ end
260
+
265
261
  def log
266
262
  Cucumber.logger
267
263
  end
@@ -5,6 +5,7 @@ require 'cucumber/runtime/before_hooks'
5
5
  require 'cucumber/runtime/after_hooks'
6
6
  require 'cucumber/events/step_match'
7
7
  require 'cucumber/gherkin/steps_parser'
8
+ require 'cucumber/step_match_search'
8
9
 
9
10
  module Cucumber
10
11
 
@@ -43,12 +44,11 @@ module Cucumber
43
44
 
44
45
  include Constantize
45
46
 
46
- def initialize(user_interface, configuration={})
47
- @configuration = Configuration.new(configuration)
47
+ attr_reader :ruby
48
+ def initialize(user_interface, configuration=Configuration.default)
49
+ @configuration = configuration
48
50
  @runtime_facade = Runtime::ForProgrammingLanguages.new(self, user_interface)
49
- @unsupported_programming_languages = []
50
- @programming_languages = []
51
- @language_map = {}
51
+ @ruby = Cucumber::RbSupport::RbLanguage.new(@runtime_facade, @configuration)
52
52
  end
53
53
 
54
54
  def configure(new_configuration)
@@ -73,24 +73,9 @@ module Cucumber
73
73
  #
74
74
  # These are commonly called nested steps.
75
75
  def invoke_dynamic_step(step_name, multiline_argument, location=nil)
76
- begin
77
- step_match(step_name).invoke(multiline_argument)
78
- rescue Undefined => exception
79
- raise UndefinedDynamicStep, step_name
80
- end
81
- end
82
-
83
- # Loads and registers programming language implementation.
84
- # Instances are cached, so calling with the same argument
85
- # twice will return the same instance.
86
- #
87
- def load_programming_language(ext)
88
- return @language_map[ext] if @language_map[ext]
89
- programming_language_class = constantize("Cucumber::#{ext.capitalize}Support::#{ext.capitalize}Language")
90
- programming_language = programming_language_class.new(@runtime_facade)
91
- @programming_languages << programming_language
92
- @language_map[ext] = programming_language
93
- programming_language
76
+ matches = step_matches(step_name)
77
+ raise UndefinedDynamicStep, step_name if matches.empty?
78
+ matches.first.invoke(multiline_argument)
94
79
  end
95
80
 
96
81
  def load_files!(files)
@@ -102,158 +87,65 @@ module Cucumber
102
87
  end
103
88
 
104
89
  def load_files_from_paths(paths)
105
- files = paths.map { |path| Dir["#{path}/**/*"] }.flatten
90
+ files = paths.map { |path| Dir["#{path}/**/*.rb"] }.flatten
106
91
  load_files! files
107
92
  end
108
93
 
109
94
  def unmatched_step_definitions
110
- @programming_languages.map do |programming_language|
111
- programming_language.unmatched_step_definitions
112
- end.flatten
113
- end
114
-
115
- def snippet_text(step_keyword, step_name, multiline_arg) #:nodoc:
116
- load_programming_language('rb') if unknown_programming_language?
117
- @programming_languages.map do |programming_language|
118
- programming_language.snippet_text(step_keyword, step_name, multiline_arg, @configuration.snippet_type)
119
- end.join("\n")
120
- end
121
-
122
- def unknown_programming_language?
123
- @programming_languages.empty?
95
+ @ruby.unmatched_step_definitions
124
96
  end
125
97
 
126
98
  def fire_hook(name, *args)
127
- @programming_languages.each do |programming_language|
128
- programming_language.send(name, *args)
129
- end
99
+ @ruby.send(name, *args)
130
100
  end
131
101
 
132
102
  def step_definitions
133
- @programming_languages.map do |programming_language|
134
- programming_language.step_definitions
135
- end.flatten
136
- end
137
-
138
- def find_match(test_step)
139
- begin
140
- match = step_match(test_step.name)
141
- rescue Cucumber::Undefined
142
- return NoStepMatch.new(test_step.source.last, test_step.name)
143
- end
144
- # TODO: move this onto Filters::ActivateSteps
145
- @configuration.notify Events::StepMatch.new(test_step, match)
146
- if @configuration.dry_run?
147
- return SkippingStepMatch.new
148
- end
149
- match
103
+ @ruby.step_definitions
150
104
  end
151
105
 
152
106
  def find_after_step_hooks(test_case)
153
- ruby = load_programming_language('rb')
154
107
  scenario = RunningTestCase.new(test_case)
155
- hooks = ruby.hooks_for(:after_step, scenario)
108
+ hooks = @ruby.hooks_for(:after_step, scenario)
156
109
  StepHooks.new hooks
157
110
  end
158
111
 
159
112
  def apply_before_hooks(test_case)
160
- ruby = load_programming_language('rb')
161
113
  scenario = RunningTestCase.new(test_case)
162
- hooks = ruby.hooks_for(:before, scenario)
114
+ hooks = @ruby.hooks_for(:before, scenario)
163
115
  BeforeHooks.new(hooks, scenario).apply_to(test_case)
164
116
  end
165
117
 
166
118
  def apply_after_hooks(test_case)
167
- ruby = load_programming_language('rb')
168
119
  scenario = RunningTestCase.new(test_case)
169
- hooks = ruby.hooks_for(:after, scenario)
120
+ hooks = @ruby.hooks_for(:after, scenario)
170
121
  AfterHooks.new(hooks, scenario).apply_to(test_case)
171
122
  end
172
123
 
173
124
  def find_around_hooks(test_case)
174
- ruby = load_programming_language('rb')
175
125
  scenario = RunningTestCase.new(test_case)
176
126
 
177
- ruby.hooks_for(:around, scenario).map do |hook|
127
+ @ruby.hooks_for(:around, scenario).map do |hook|
178
128
  Hooks.around_hook(test_case.source) do |run_scenario|
179
129
  hook.invoke('Around', scenario, &run_scenario)
180
130
  end
181
131
  end
182
132
  end
183
133
 
184
- def step_match(step_name, name_to_report=nil) #:nodoc:
185
- @match_cache ||= {}
186
-
187
- match = @match_cache[[step_name, name_to_report]]
188
- return match if match
189
-
190
- @match_cache[[step_name, name_to_report]] = step_match_without_cache(step_name, name_to_report)
191
- end
192
-
193
134
  private
194
135
 
195
- def step_match_without_cache(step_name, name_to_report=nil)
196
- matches = matches(step_name, name_to_report)
197
- raise Undefined.new(step_name) if matches.empty?
198
- matches = best_matches(step_name, matches) if matches.size > 1 && guess_step_matches?
199
- raise Ambiguous.new(step_name, matches, guess_step_matches?) if matches.size > 1
200
- matches[0]
201
- end
202
-
203
- def guess_step_matches?
204
- @configuration.guess?
205
- end
206
-
207
- def matches(step_name, name_to_report)
208
- @programming_languages.map do |programming_language|
209
- programming_language.step_matches(step_name, name_to_report).to_a
210
- end.flatten
211
- end
212
-
213
- def best_matches(step_name, step_matches) #:nodoc:
214
- no_groups = step_matches.select {|step_match| step_match.args.length == 0}
215
- max_arg_length = step_matches.map {|step_match| step_match.args.length }.max
216
- top_groups = step_matches.select {|step_match| step_match.args.length == max_arg_length }
217
-
218
- if no_groups.any?
219
- longest_regexp_length = no_groups.map {|step_match| step_match.text_length }.max
220
- no_groups.select {|step_match| step_match.text_length == longest_regexp_length }
221
- elsif top_groups.any?
222
- shortest_capture_length = top_groups.map {|step_match| step_match.args.inject(0) {|sum, c| sum + c.to_s.length } }.min
223
- top_groups.select {|step_match| step_match.args.inject(0) {|sum, c| sum + c.to_s.length } == shortest_capture_length }
224
- else
225
- top_groups
226
- end
136
+ def step_matches(step_name)
137
+ StepMatchSearch.new(@ruby.method(:step_matches), @configuration).call(step_name)
227
138
  end
228
139
 
229
140
  def load_file(file)
230
- if programming_language = programming_language_for(file)
231
- log.debug(" * #{file}\n")
232
- programming_language.load_code_file(file)
233
- else
234
- log.debug(" * #{file} [NOT SUPPORTED]\n")
235
- end
141
+ log.debug(" * #{file}\n")
142
+ @ruby.load_code_file(file)
236
143
  end
237
144
 
238
145
  def log
239
146
  Cucumber.logger
240
147
  end
241
148
 
242
- def programming_language_for(step_def_file)
243
- if ext = File.extname(step_def_file)[1..-1]
244
- return nil if @unsupported_programming_languages.index(ext)
245
- begin
246
- load_programming_language(ext)
247
- rescue LoadError => e
248
- log.debug("Failed to load '#{ext}' programming language for file #{step_def_file}: #{e.message}\n")
249
- @unsupported_programming_languages << ext
250
- nil
251
- end
252
- else
253
- nil
254
- end
255
- end
256
-
257
149
  end
258
150
  end
259
151
  end
@@ -0,0 +1,25 @@
1
+ module Cucumber
2
+ # Defines the location and value of a captured argument from the step
3
+ # text
4
+ class StepArgument
5
+ def self.arguments_from(regexp, step_name)
6
+ match = regexp.match(step_name)
7
+ if match
8
+ n = 0
9
+ match.captures.map do |val|
10
+ n += 1
11
+ offset = match.offset(n)[0]
12
+ new(offset, val)
13
+ end
14
+ else
15
+ nil
16
+ end
17
+ end
18
+
19
+ attr_reader :offset, :val
20
+
21
+ def initialize(offset, val)
22
+ @offset, @val = offset, val
23
+ end
24
+ end
25
+ end
@@ -1,26 +1,20 @@
1
1
  require 'cucumber/multiline_argument'
2
2
 
3
3
  module Cucumber
4
+
5
+ # Represents the match found between a Test Step and it's activation
4
6
  class StepMatch #:nodoc:
5
7
  attr_reader :step_definition, :step_arguments
6
8
 
7
- # Creates a new StepMatch. The +name_to_report+ argument is what's
8
- # reported, unless it's is, in which case +name_to_report+ is used instead.
9
- #
10
- def initialize(step_definition, name_to_match, name_to_report, step_arguments)
11
- raise "name_to_match can't be nil" if name_to_match.nil?
9
+ def initialize(step_definition, step_name, step_arguments)
12
10
  raise "step_arguments can't be nil (but it can be an empty array)" if step_arguments.nil?
13
- @step_definition, @name_to_match, @name_to_report, @step_arguments = step_definition, name_to_match, name_to_report, step_arguments
11
+ @step_definition, @name_to_match, @step_arguments = step_definition, step_name, step_arguments
14
12
  end
15
13
 
16
14
  def args
17
15
  @step_arguments.map{|g| g.val }
18
16
  end
19
17
 
20
- def name
21
- @name_to_report
22
- end
23
-
24
18
  def activate(test_step)
25
19
  test_step.with_action(@step_definition.location) do
26
20
  invoke(MultilineArgument.from_core(test_step.source.last.multiline_arg))
@@ -49,7 +43,7 @@ module Cucumber
49
43
  # lambda { |param| "[#{param}]" }
50
44
  #
51
45
  def format_args(format = lambda{|a| a}, &proc)
52
- @name_to_report || replace_arguments(@name_to_match, @step_arguments, format, &proc)
46
+ replace_arguments(@name_to_match, @step_arguments, format, &proc)
53
47
  end
54
48
 
55
49
  def location
@@ -90,7 +84,7 @@ module Cucumber
90
84
  end
91
85
 
92
86
  def inspect #:nodoc:
93
- sprintf("#<%s:0x%x>", self.class, self.object_id)
87
+ "#<#{self.class}: #{location}>"
94
88
  end
95
89
 
96
90
  private
@@ -0,0 +1,67 @@
1
+ module Cucumber
2
+ module StepMatchSearch
3
+ def self.new(search, configuration)
4
+ CachesStepMatch.new(
5
+ AssertUnambiguousMatch.new(
6
+ configuration.guess? ? AttemptToGuessAmbiguousMatch.new(search) : search,
7
+ configuration
8
+ )
9
+ )
10
+ end
11
+
12
+ class AssertUnambiguousMatch
13
+ def initialize(search, configuration)
14
+ @search, @configuration = search, configuration
15
+ end
16
+
17
+ def call(step_name)
18
+ result = @search.call(step_name)
19
+ raise Cucumber::Ambiguous.new(step_name, result, @configuration.guess?) if result.length > 1
20
+ result
21
+ end
22
+ end
23
+
24
+ class AttemptToGuessAmbiguousMatch
25
+ def initialize(search)
26
+ @search = search
27
+ end
28
+
29
+ def call(step_name)
30
+ best_matches(step_name, @search.call(step_name))
31
+ end
32
+
33
+ private
34
+
35
+ def best_matches(step_name, step_matches) #:nodoc:
36
+ no_groups = step_matches.select {|step_match| step_match.args.length == 0}
37
+ max_arg_length = step_matches.map {|step_match| step_match.args.length }.max
38
+ top_groups = step_matches.select {|step_match| step_match.args.length == max_arg_length }
39
+
40
+ if no_groups.any?
41
+ longest_regexp_length = no_groups.map {|step_match| step_match.text_length }.max
42
+ no_groups.select {|step_match| step_match.text_length == longest_regexp_length }
43
+ elsif top_groups.any?
44
+ shortest_capture_length = top_groups.map {|step_match| step_match.args.inject(0) {|sum, c| sum + c.to_s.length } }.min
45
+ top_groups.select {|step_match| step_match.args.inject(0) {|sum, c| sum + c.to_s.length } == shortest_capture_length }
46
+ else
47
+ top_groups
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ require 'delegate'
54
+ class CachesStepMatch < SimpleDelegator
55
+ def call(step_name) #:nodoc:
56
+ @match_cache ||= {}
57
+
58
+ matches = @match_cache[step_name]
59
+ return matches if matches
60
+
61
+ @match_cache[step_name] = super(step_name)
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+