cucumber 1.1.2 → 1.1.3

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