cucumber 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+