cucumber 1.1.2 → 1.1.3

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 (51) hide show
  1. data/.rvmrc +1 -0
  2. data/.travis.yml +12 -2
  3. data/History.md +14 -0
  4. data/cucumber.gemspec +2 -2
  5. data/cucumber.yml +1 -1
  6. data/features/.cucumber/stepdefs.json +1159 -0
  7. data/features/issue_57.feature +35 -0
  8. data/legacy_features/wire_protocol.feature +1 -1
  9. data/lib/cucumber/ast/feature.rb +1 -1
  10. data/lib/cucumber/ast/table.rb +52 -31
  11. data/lib/cucumber/cli/configuration.rb +4 -0
  12. data/lib/cucumber/cli/main.rb +1 -0
  13. data/lib/cucumber/cli/options.rb +3 -0
  14. data/lib/cucumber/constantize.rb +1 -1
  15. data/lib/cucumber/formatter/gherkin_formatter_adapter.rb +1 -1
  16. data/lib/cucumber/formatter/html.rb +6 -1
  17. data/lib/cucumber/formatter/rerun.rb +30 -7
  18. data/lib/cucumber/js_support/js_language.rb +1 -1
  19. data/lib/cucumber/language_support/language_methods.rb +0 -4
  20. data/lib/cucumber/platform.rb +1 -1
  21. data/lib/cucumber/py_support/py_language.rb +0 -4
  22. data/lib/cucumber/rb_support/rb_language.rb +0 -14
  23. data/lib/cucumber/rb_support/regexp_argument_matcher.rb +3 -3
  24. data/lib/cucumber/runtime.rb +39 -2
  25. data/lib/cucumber/step_match.rb +3 -3
  26. data/lib/cucumber/wire_support/wire_protocol.rb +0 -1
  27. data/lib/cucumber/wire_support/wire_protocol/requests.rb +3 -1
  28. data/spec/cucumber/ast/table_spec.rb +13 -0
  29. data/spec/cucumber/cli/main_spec.rb +1 -1
  30. data/spec/cucumber/constantize_spec.rb +12 -0
  31. data/spec/cucumber/rb_support/regexp_argument_matcher_spec.rb +2 -2
  32. metadata +54 -68
  33. data/.gitignore +0 -27
  34. data/examples/i18n/de/.gitignore +0 -1
  35. data/examples/i18n/en/.gitignore +0 -1
  36. data/examples/i18n/eo/.gitignore +0 -1
  37. data/examples/i18n/fi/.gitignore +0 -1
  38. data/examples/i18n/hu/.gitignore +0 -1
  39. data/examples/i18n/id/.gitignore +0 -1
  40. data/examples/i18n/ja/.gitignore +0 -1
  41. data/examples/i18n/ko/.gitignore +0 -1
  42. data/examples/i18n/lt/.gitignore +0 -1
  43. data/examples/i18n/pl/.gitignore +0 -1
  44. data/examples/i18n/sk/.gitignore +0 -1
  45. data/examples/i18n/tr/.gitignore +0 -1
  46. data/examples/i18n/zh-TW/.gitignore +0 -1
  47. data/examples/python/lib/.gitignore +0 -1
  48. data/examples/ruby2python/lib/.gitignore +0 -1
  49. data/examples/watir/.gitignore +0 -2
  50. data/fixtures/self_test/.gitignore +0 -1
  51. data/lib/cucumber/step_argument.rb +0 -9
@@ -0,0 +1,35 @@
1
+ Feature: Rerun formatter: Test for Issue #57
2
+ details see https://github.com/cucumber/cucumber/issues/57
3
+
4
+ Background:
5
+ Given a file named "features/one_passing_one_failing.feature" with:
6
+ """
7
+ Feature: One passing example, one failing example
8
+
9
+ Scenario Outline:
10
+ Given a <certain> step
11
+
12
+ Examples:
13
+ |certain|
14
+ |passing|
15
+ |failing|
16
+
17
+ """
18
+ And a file named "features/step_definitions/steps.rb" with:
19
+ """
20
+ Given /a passing step/ do
21
+ #does nothing
22
+ end
23
+
24
+ Given /a failing step/ do
25
+ fail
26
+ end
27
+ """
28
+
29
+ Scenario: Handle examples with the rerun formatter
30
+ When I run cucumber "features/one_passing_one_failing.feature -r features -f rerun"
31
+ Then it should fail with:
32
+ """
33
+ features/one_passing_one_failing.feature:9
34
+ """
35
+
@@ -329,4 +329,4 @@ Feature: Wire Protocol
329
329
  Then STDERR should match
330
330
  """
331
331
  undefined method `handle_yikes'
332
- """
332
+ """
@@ -8,7 +8,7 @@ module Cucumber
8
8
 
9
9
  attr_accessor :language
10
10
  attr_writer :features, :background
11
- attr_reader :file
11
+ attr_reader :file, :feature_elements
12
12
 
13
13
  def initialize(background, comment, tags, keyword, title, description, feature_elements)
14
14
  @background, @comment, @tags, @keyword, @title, @description, @feature_elements = background, comment, tags, keyword, title, description, feature_elements
@@ -51,7 +51,7 @@ module Cucumber
51
51
  include Enumerable
52
52
  include Gherkin::Rubify
53
53
 
54
- NULL_CONVERSIONS = Hash.new(lambda{ |cell_value| cell_value }).freeze
54
+ NULL_CONVERSIONS = Hash.new({ :strict => false, :proc => lambda{ |cell_value| cell_value } }).freeze
55
55
 
56
56
  attr_accessor :file
57
57
 
@@ -71,7 +71,7 @@ module Cucumber
71
71
  # You don't typically create your own Table objects - Cucumber will do
72
72
  # it internally and pass them to your Step Definitions.
73
73
  #
74
- def initialize(raw, conversion_procs = NULL_CONVERSIONS.dup)
74
+ def initialize(raw, conversion_procs = NULL_CONVERSIONS.dup, header_mappings = {}, header_conversion_proc = nil)
75
75
  @cells_class = Cells
76
76
  @cell_class = Cell
77
77
  raw = ensure_array_of_array(rubify(raw))
@@ -79,17 +79,19 @@ module Cucumber
79
79
  transposed = raw.transpose
80
80
  create_cell_matrix(raw)
81
81
  @conversion_procs = conversion_procs
82
+ @header_mappings = header_mappings
83
+ @header_conversion_proc = header_conversion_proc
82
84
  end
83
85
 
84
86
  def to_step_definition_arg
85
87
  dup
86
88
  end
87
89
 
88
- # Creates a copy of this table, inheriting any column mappings.
89
- # registered with #map_headers!
90
+ # Creates a copy of this table, inheriting any column and header mappings
91
+ # registered with #map_column! and #map_headers!.
90
92
  #
91
93
  def dup
92
- self.class.new(raw.dup, @conversion_procs.dup)
94
+ self.class.new(raw.dup, @conversion_procs.dup, @header_mappings.dup, @header_conversion_proc)
93
95
  end
94
96
 
95
97
  # Returns a new, transposed table. Example:
@@ -104,9 +106,9 @@ module Cucumber
104
106
  # | 4 | 2 |
105
107
  #
106
108
  def transpose
107
- self.class.new(raw.transpose, @conversion_procs.dup)
109
+ self.class.new(raw.transpose, @conversion_procs.dup, @header_mappings.dup, @header_conversion_proc)
108
110
  end
109
-
111
+
110
112
  # Converts this table into an Array of Hash where the keys of each
111
113
  # Hash are the headers in the table. For example, a Table built from
112
114
  # the following plain text:
@@ -122,9 +124,7 @@ module Cucumber
122
124
  # Use #map_column! to specify how values in a column are converted.
123
125
  #
124
126
  def hashes
125
- @hashes ||= cells_rows[1..-1].map do |row|
126
- row.to_hash
127
- end
127
+ @hashes ||= build_hashes
128
128
  end
129
129
 
130
130
  # Converts this table into a Hash where the first column is
@@ -238,22 +238,8 @@ module Cucumber
238
238
  # # => ['phone number', 'ADDRESS']
239
239
  #
240
240
  def map_headers!(mappings={}, &block)
241
- header_cells = cell_matrix[0]
242
-
243
- if block_given?
244
- header_values = header_cells.map { |cell| cell.value } - mappings.keys
245
- mappings = mappings.merge(Hash[*header_values.zip(header_values.map(&block)).flatten])
246
- end
247
-
248
- mappings.each_pair do |pre, post|
249
- mapped_cells = header_cells.select{|cell| pre === cell.value}
250
- raise "No headers matched #{pre.inspect}" if mapped_cells.empty?
251
- raise "#{mapped_cells.length} headers matched #{pre.inspect}: #{mapped_cells.map{|c| c.value}.inspect}" if mapped_cells.length > 1
252
- mapped_cells[0].value = post
253
- if @conversion_procs.has_key?(pre)
254
- @conversion_procs[post] = @conversion_procs.delete(pre)
255
- end
256
- end
241
+ @header_mappings = mappings
242
+ @header_conversion_proc = block
257
243
  end
258
244
 
259
245
  # Returns a new Table where the headers are redefined. See #map_headers!
@@ -276,8 +262,7 @@ module Cucumber
276
262
  # end
277
263
  #
278
264
  def map_column!(column_name, strict=true, &conversion_proc)
279
- verify_column(column_name.to_s) if strict
280
- @conversion_procs[column_name.to_s] = conversion_proc
265
+ @conversion_procs[column_name.to_s] = { :strict => strict, :proc => conversion_proc }
281
266
  self
282
267
  end
283
268
 
@@ -319,8 +304,12 @@ module Cucumber
319
304
  options = {:missing_row => true, :surplus_row => true, :missing_col => true, :surplus_col => false}.merge(options)
320
305
 
321
306
  other_table = ensure_table(other_table)
307
+ other_table.convert_headers!
322
308
  other_table.convert_columns!
323
309
  ensure_green!
310
+
311
+ convert_headers!
312
+ convert_columns!
324
313
 
325
314
  original_width = cell_matrix[0].length
326
315
  other_table_cell_matrix = pad!(other_table.cell_matrix)
@@ -331,7 +320,6 @@ module Cucumber
331
320
 
332
321
  require_diff_lcs
333
322
  cell_matrix.extend(Diff::LCS)
334
- convert_columns!
335
323
  changes = cell_matrix.diff(other_table_cell_matrix).flatten
336
324
 
337
325
  inserted = 0
@@ -386,7 +374,8 @@ module Cucumber
386
374
  hash[key.to_s] if key.is_a?(Symbol)
387
375
  end
388
376
  column_names.each_with_index do |column_name, column_index|
389
- value = @conversion_procs[column_name].call(cells.value(column_index))
377
+ verify_column(column_name) if @conversion_procs[column_name][:strict]
378
+ value = @conversion_procs[column_name][:proc].call(cells.value(column_index))
390
379
  hash[column_name] = value
391
380
  end
392
381
  hash
@@ -470,6 +459,14 @@ module Cucumber
470
459
 
471
460
  protected
472
461
 
462
+ def build_hashes
463
+ convert_headers!
464
+ convert_columns!
465
+ cells_rows[1..-1].map do |row|
466
+ row.to_hash
467
+ end
468
+ end
469
+
473
470
  def inspect_rows(missing_row, inserted_row) #:nodoc:
474
471
  missing_row.each_with_index do |missing_cell, col|
475
472
  inserted_cell = inserted_row[col]
@@ -490,14 +487,38 @@ module Cucumber
490
487
  end
491
488
 
492
489
  def convert_columns! #:nodoc:
490
+ @conversion_procs.each do |column_name, conversion_proc|
491
+ verify_column(column_name) if conversion_proc[:strict]
492
+ end
493
+
493
494
  cell_matrix.transpose.each do |col|
494
- conversion_proc = @conversion_procs[col[0].value]
495
+ column_name = col[0].value
496
+ conversion_proc = @conversion_procs[column_name][:proc]
495
497
  col[1..-1].each do |cell|
496
498
  cell.value = conversion_proc.call(cell.value)
497
499
  end
498
500
  end
499
501
  end
500
502
 
503
+ def convert_headers! #:nodoc:
504
+ header_cells = cell_matrix[0]
505
+
506
+ if @header_conversion_proc
507
+ header_values = header_cells.map { |cell| cell.value } - @header_mappings.keys
508
+ @header_mappings = @header_mappings.merge(Hash[*header_values.zip(header_values.map(&@header_conversion_proc)).flatten])
509
+ end
510
+
511
+ @header_mappings.each_pair do |pre, post|
512
+ mapped_cells = header_cells.select { |cell| pre === cell.value }
513
+ raise "No headers matched #{pre.inspect}" if mapped_cells.empty?
514
+ raise "#{mapped_cells.length} headers matched #{pre.inspect}: #{mapped_cells.map { |c| c.value }.inspect}" if mapped_cells.length > 1
515
+ mapped_cells[0].value = post
516
+ if @conversion_procs.has_key?(pre)
517
+ @conversion_procs[post] = @conversion_procs.delete(pre)
518
+ end
519
+ end
520
+ end
521
+
501
522
  def require_diff_lcs #:nodoc:
502
523
  begin
503
524
  require 'diff/lcs'
@@ -64,6 +64,10 @@ module Cucumber
64
64
  @options[:expand]
65
65
  end
66
66
 
67
+ def dotcucumber
68
+ @options[:dotcucumber]
69
+ end
70
+
67
71
  def build_tree_walker(step_mother)
68
72
  Ast::TreeWalker.new(step_mother, formatters(step_mother), self)
69
73
  end
@@ -41,6 +41,7 @@ module Cucumber
41
41
  end
42
42
 
43
43
  runtime.run!
44
+ runtime.write_stepdefs_json
44
45
  runtime.results.failure?
45
46
  rescue ProfilesNotDefinedError, YmlLoadError, ProfileNotFound => e
46
47
  @error_stream.puts e.message
@@ -271,6 +271,9 @@ module Cucumber
271
271
  opts.on("--port PORT", "Specify DRb port. Ignored without --drb") do |port|
272
272
  @options[:drb_port] = port
273
273
  end
274
+ opts.on("--dotcucumber DIR", "Write metadata to DIR") do |dir|
275
+ @options[:dotcucumber] = dir
276
+ end
274
277
  opts.on_tail("--version", "Show version.") do
275
278
  @out_stream.puts Cucumber::VERSION
276
279
  Kernel.exit(0)
@@ -7,7 +7,7 @@ module Cucumber
7
7
  names = camel_cased_word.split('::')
8
8
  names.shift if names.empty? || names.first.empty?
9
9
 
10
- constant = Object
10
+ constant = ::Object
11
11
  names.each do |name|
12
12
  constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
13
13
  end
@@ -47,7 +47,7 @@ module Cucumber
47
47
  end
48
48
 
49
49
  def before_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
50
- arguments = step_match.step_arguments.map{|a| Gherkin::Formatter::Argument.new(a.byte_offset, a.val)}
50
+ arguments = step_match.step_arguments.map{|a| Gherkin::Formatter::Argument.new(a.offset, a.val)}
51
51
  location = step_match.file_colon_line
52
52
  match = Gherkin::Formatter::Model::Match.new(arguments, location)
53
53
  if @print_emtpy_match
@@ -311,7 +311,7 @@ module Cucumber
311
311
  @builder.tr do
312
312
  @builder.td(:colspan => @col_index.to_s, :class => 'failed') do
313
313
  @builder.pre do |pre|
314
- pre << format_exception(table_row.exception)
314
+ pre << h(format_exception(table_row.exception))
315
315
  end
316
316
  end
317
317
  end
@@ -378,6 +378,11 @@ module Cucumber
378
378
  matches = message.match(/<code>([^(\/)]+)<\//m)
379
379
  message = matches ? matches[1] : ""
380
380
  end
381
+
382
+ unless exception.instance_of?(RuntimeError)
383
+ message << " (#{exception.class})"
384
+ end
385
+
381
386
  @builder.pre do
382
387
  @builder.text!(message)
383
388
  end
@@ -22,9 +22,9 @@ module Cucumber
22
22
  @file_colon_lines = Hash.new{|h,k| h[k] = []}
23
23
  end
24
24
 
25
- def before_feature(*)
25
+ def before_feature(feature_element)
26
26
  @lines = []
27
- @file = nil
27
+ @file = feature_element.file
28
28
  end
29
29
 
30
30
  def after_feature(*)
@@ -46,17 +46,40 @@ module Cucumber
46
46
  end
47
47
 
48
48
  def after_feature_element(feature_element)
49
- if @rerun
50
- file, line = *feature_element.file_colon_line.split(':')
51
- @lines << line
52
- @file = file
49
+ if (@rerun || feature_element.failed?) && !(Ast::ScenarioOutline === feature_element)
50
+ @lines << feature_element.line
53
51
  end
54
52
  end
55
53
 
54
+ def after_table_row(table_row)
55
+ return unless @in_examples and Cucumber::Ast::OutlineTable::ExampleRow === table_row
56
+ unless @header_row
57
+ if table_row.failed?
58
+ @rerun = true
59
+ @lines << table_row.line
60
+ end
61
+ end
62
+
63
+ @header_row = false if @header_row
64
+ end
65
+
66
+ def before_examples(*args)
67
+ @header_row = true
68
+ @in_examples = true
69
+ end
70
+
71
+ def after_examples(*args)
72
+ @in_examples = false
73
+ end
74
+
75
+ def before_table_row(table_row)
76
+ return unless @in_examples
77
+ end
78
+
56
79
  def step_name(keyword, step_match, status, source_indent, background)
57
80
  @rerun = true if [:failed, :pending, :undefined].index(status)
58
81
  end
59
-
82
+
60
83
  private
61
84
 
62
85
  def after_first_time
@@ -97,7 +97,7 @@ module Cucumber
97
97
  @arg
98
98
  end
99
99
 
100
- def byte_offset
100
+ def offset
101
101
  end
102
102
  end
103
103
 
@@ -4,10 +4,6 @@ require 'cucumber/step_definition_light'
4
4
  module Cucumber
5
5
  module LanguageSupport
6
6
  module LanguageMethods
7
- def create_step_match(step_definition, step_name, name_to_report, step_arguments)
8
- StepMatch.new(step_definition, step_name, name_to_report, step_arguments)
9
- end
10
-
11
7
  def around(scenario)
12
8
  execute_around(scenario) do
13
9
  yield
@@ -4,7 +4,7 @@ require 'rbconfig'
4
4
 
5
5
  module Cucumber
6
6
  unless defined?(Cucumber::VERSION)
7
- VERSION = '1.1.2'
7
+ VERSION = '1.1.3'
8
8
  BINARY = File.expand_path(File.dirname(__FILE__) + '/../../bin/cucumber')
9
9
  LIBDIR = File.expand_path(File.dirname(__FILE__) + '/../../lib')
10
10
  JRUBY = defined?(JRUBY_VERSION)
@@ -24,10 +24,6 @@ module Cucumber
24
24
  def alias_adverbs(adverbs)
25
25
  end
26
26
 
27
- def step_definitions_for(py_file)
28
- mod = import(py_file)
29
- end
30
-
31
27
  def snippet_text(code_keyword, step_name, multiline_arg_class)
32
28
  "python snippet: #{code_keyword}, #{step_name}"
33
29
  end
@@ -65,20 +65,6 @@ module Cucumber
65
65
  end
66
66
  end
67
67
 
68
- # Gets called for each file under features (or whatever is overridden
69
- # with --require).
70
- def step_definitions_for(rb_file) # Looks Unused - Delete?
71
- begin
72
- require rb_file # This will cause self.add_step_definition and self.add_hook to be called from RbDsl
73
- step_definitions
74
- rescue LoadError => e
75
- e.message << "\nFailed to load #{code_file}"
76
- raise e
77
- ensure
78
- @step_definitions = nil
79
- end
80
- end
81
-
82
68
  def step_matches(name_to_match, name_to_format)
83
69
  @step_definitions.map do |step_definition|
84
70
  if(arguments = step_definition.arguments_from(name_to_match))
@@ -1,4 +1,4 @@
1
- require 'cucumber/step_argument'
1
+ require 'gherkin/formatter/argument'
2
2
 
3
3
  module Cucumber
4
4
  module RbSupport
@@ -9,8 +9,8 @@ module Cucumber
9
9
  n = 0
10
10
  match.captures.map do |val|
11
11
  n += 1
12
- start = match.offset(n)[0]
13
- StepArgument.new(val, start)
12
+ offset = match.offset(n)[0]
13
+ Gherkin::Formatter::Argument.new(offset, val)
14
14
  end
15
15
  else
16
16
  nil
@@ -1,3 +1,4 @@
1
+ require 'fileutils'
1
2
  require 'gherkin/rubify'
2
3
  require 'gherkin/i18n'
3
4
  require 'cucumber/configuration'
@@ -118,6 +119,42 @@ module Cucumber
118
119
  @support_code.unknown_programming_language?
119
120
  end
120
121
 
122
+ def write_stepdefs_json
123
+ if(@configuration.dotcucumber)
124
+ stepdefs = []
125
+ @support_code.step_definitions.sort{|a,b| a.to_hash['source'] <=> a.to_hash['source']}.each do |stepdef|
126
+ stepdef_hash = stepdef.to_hash
127
+ steps = []
128
+ features.each do |feature|
129
+ feature.feature_elements.each do |feature_element|
130
+ feature_element.raw_steps.each do |step|
131
+ args = stepdef.arguments_from(step.name)
132
+ if(args)
133
+ steps << {
134
+ 'name' => step.name,
135
+ 'args' => args.map do |arg|
136
+ {
137
+ 'offset' => arg.offset,
138
+ 'val' => arg.val
139
+ }
140
+ end
141
+ }
142
+ end
143
+ end
144
+ end
145
+ end
146
+ stepdef_hash['steps'] = steps.uniq.sort {|a,b| a['name'] <=> b['name']}
147
+ stepdefs << stepdef_hash
148
+ end
149
+ if !File.directory?(@configuration.dotcucumber)
150
+ FileUtils.mkdir_p(@configuration.dotcucumber)
151
+ end
152
+ File.open(File.join(@configuration.dotcucumber, 'stepdefs.json'), 'w') do |io|
153
+ io.write(JSON.pretty_generate(stepdefs))
154
+ end
155
+ end
156
+ end
157
+
121
158
  private
122
159
 
123
160
  def fire_after_configuration_hook #:nodoc
@@ -125,11 +162,11 @@ module Cucumber
125
162
  end
126
163
 
127
164
  def features
128
- loader = Runtime::FeaturesLoader.new(
165
+ @loader ||= Runtime::FeaturesLoader.new(
129
166
  @configuration.feature_files,
130
167
  @configuration.filters,
131
168
  @configuration.tag_expression)
132
- loader.features
169
+ @loader.features
133
170
  end
134
171
 
135
172
  def load_step_definitions