aslakhellesoy-cucumber 0.3.101 → 0.3.101.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/History.txt +26 -2
  2. data/Manifest.txt +6 -4
  3. data/config/hoe.rb +2 -2
  4. data/examples/pure_java/README.textile +2 -2
  5. data/examples/self_test/features/step_definitions/sample_steps.rb +0 -12
  6. data/examples/self_test/features/support/env.rb +0 -6
  7. data/features/cucumber_cli.feature +11 -24
  8. data/features/custom_formatter.feature +2 -2
  9. data/features/default_snippets.feature +42 -0
  10. data/features/html_formatter/a.html +1 -1
  11. data/features/negative_tagged_hooks.feature +61 -0
  12. data/features/step_definitions/cucumber_steps.rb +1 -1
  13. data/features/transform.feature +88 -12
  14. data/features/usage.feature +0 -6
  15. data/lib/cucumber/ast/feature.rb +4 -0
  16. data/lib/cucumber/ast/feature_element.rb +49 -42
  17. data/lib/cucumber/ast/step_invocation.rb +1 -1
  18. data/lib/cucumber/ast/tags.rb +25 -3
  19. data/lib/cucumber/cli/drb_client.rb +7 -1
  20. data/lib/cucumber/cli/main.rb +4 -3
  21. data/lib/cucumber/cli/options.rb +13 -24
  22. data/lib/cucumber/core_ext/instance_exec.rb +2 -1
  23. data/lib/cucumber/filter.rb +2 -12
  24. data/lib/cucumber/formatter/console.rb +21 -21
  25. data/lib/cucumber/formatter/html.rb +1 -1
  26. data/lib/cucumber/formatter/pdf.rb +1 -1
  27. data/lib/cucumber/formatter/pretty.rb +1 -1
  28. data/lib/cucumber/language_support/language_methods.rb +19 -2
  29. data/lib/cucumber/parser/feature.rb +13 -50
  30. data/lib/cucumber/parser/feature.tt +13 -47
  31. data/lib/cucumber/parser/natural_language.rb +1 -1
  32. data/lib/cucumber/rails/action_controller.rb +33 -0
  33. data/lib/cucumber/rails/active_record.rb +27 -0
  34. data/lib/cucumber/rails/rspec.rb +1 -1
  35. data/lib/cucumber/rails/test_unit.rb +9 -0
  36. data/lib/cucumber/rails/world.rb +7 -78
  37. data/lib/cucumber/rb_support/rb_dsl.rb +6 -4
  38. data/lib/cucumber/rb_support/rb_language.rb +8 -2
  39. data/lib/cucumber/rb_support/rb_step_definition.rb +2 -5
  40. data/lib/cucumber/rb_support/rb_transform.rb +35 -0
  41. data/lib/cucumber/rb_support/rb_world.rb +7 -1
  42. data/lib/cucumber/step_match.rb +1 -1
  43. data/lib/cucumber/step_mother.rb +8 -31
  44. data/lib/cucumber/version.rb +1 -1
  45. data/rails_generators/cucumber/templates/cucumber_environment.rb +2 -2
  46. data/rails_generators/cucumber/templates/env.rb +1 -8
  47. data/spec/cucumber/ast/feature_element_spec.rb +24 -23
  48. data/spec/cucumber/ast/scenario_outline_spec.rb +1 -1
  49. data/spec/cucumber/cli/options_spec.rb +5 -14
  50. data/spec/cucumber/parser/feature_parser_spec.rb +6 -6
  51. data/spec/cucumber/step_mother_spec.rb +41 -38
  52. metadata +10 -8
  53. data/examples/self_test/features/transform_sample.feature +0 -10
  54. data/spec/cucumber/rails/stubs/mini_rails.rb +0 -18
  55. data/spec/cucumber/rails/stubs/test_help.rb +0 -1
  56. data/spec/cucumber/rails/world_spec.rb +0 -16
@@ -116,14 +116,8 @@ Feature: Cucumber command line
116
116
  Given passing # features/sample.feature:12
117
117
  /^failing expectation$/ # features/step_definitions/sample_steps.rb:62
118
118
  Given failing expectation # features/failing_expectation.feature:4
119
- /^I should transform ('\d+' to an Integer)$/ # features/step_definitions/sample_steps.rb:83
120
- Then I should transform '10' to an Integer # features/transform_sample.feature:4
121
- /^I should transform ('\w+' to a Symbol)$/ # features/step_definitions/sample_steps.rb:87
122
- Then I should transform 'abc' to a Symbol # features/transform_sample.feature:7
123
119
  /^failing$/ # features/step_definitions/sample_steps.rb:8
124
120
  Given failing # features/sample.feature:18
125
- /^I should not transform ('\d+') to an Integer$/ # features/step_definitions/sample_steps.rb:91
126
- Then I should not transform '10' to an Integer # features/transform_sample.feature:10
127
121
  (::) UNUSED (::)
128
122
  /^unused$/ # features/step_definitions/sample_steps.rb:66
129
123
  /^another unused$/ # features/step_definitions/sample_steps.rb:69
@@ -26,6 +26,10 @@ module Cucumber
26
26
  end
27
27
  end
28
28
 
29
+ def source_tag_names
30
+ @tags.tag_names
31
+ end
32
+
29
33
  def accept_hook?(hook)
30
34
  @tags.accept_hook?(hook)
31
35
  end
@@ -1,61 +1,68 @@
1
1
  require 'enumerator'
2
+ require 'cucumber/ast/tags'
2
3
 
3
4
  module Cucumber
4
- module FeatureElement #:nodoc:
5
- attr_accessor :feature
5
+ module Ast
6
+ module FeatureElement #:nodoc:
7
+ attr_accessor :feature
6
8
 
7
- def attach_steps(steps)
8
- steps.each {|step| step.feature_element = self}
9
- end
9
+ def attach_steps(steps)
10
+ steps.each {|step| step.feature_element = self}
11
+ end
10
12
 
11
- def file_colon_line(line = @line)
12
- @feature.file_colon_line(line) if @feature
13
- end
13
+ def file_colon_line(line = @line)
14
+ @feature.file_colon_line(line) if @feature
15
+ end
14
16
 
15
- def text_length
16
- name_line_lengths.max
17
- end
17
+ def text_length
18
+ name_line_lengths.max
19
+ end
18
20
 
19
- def first_line_length
20
- name_line_lengths[0]
21
- end
21
+ def first_line_length
22
+ name_line_lengths[0]
23
+ end
22
24
 
23
- def name_line_lengths
24
- if @name.strip.empty?
25
- [@keyword.jlength]
26
- else
27
- @name.split("\n").enum_for(:each_with_index).map do |line, line_number|
28
- line_number == 0 ? @keyword.jlength + line.jlength : line.jlength + Ast::Step::INDENT - 1 # We -1 as names which are not keyword lines are missing a space between keyword and name
25
+ def name_line_lengths
26
+ if @name.strip.empty?
27
+ [@keyword.jlength]
28
+ else
29
+ @name.split("\n").enum_for(:each_with_index).map do |line, line_number|
30
+ line_number == 0 ? @keyword.jlength + line.jlength : line.jlength + Ast::Step::INDENT - 1 # We -1 as names which are not keyword lines are missing a space between keyword and name
31
+ end
29
32
  end
30
33
  end
31
- end
32
34
 
33
- def matches_scenario_names?(scenario_name_regexps)
34
- scenario_name_regexps.detect{|name| name =~ @name}
35
- end
35
+ def matches_scenario_names?(scenario_name_regexps)
36
+ scenario_name_regexps.detect{|name| name =~ @name}
37
+ end
36
38
 
37
- def backtrace_line(name = "#{@keyword} #{@name}", line = @line)
38
- @feature.backtrace_line(name, line) if @feature
39
- end
39
+ def backtrace_line(name = "#{@keyword} #{@name}", line = @line)
40
+ @feature.backtrace_line(name, line) if @feature
41
+ end
40
42
 
41
- def source_indent(text_length)
42
- max_line_length - text_length
43
- end
43
+ def source_indent(text_length)
44
+ max_line_length - text_length
45
+ end
44
46
 
45
- def max_line_length
46
- @steps.max_line_length(self)
47
- end
47
+ def max_line_length
48
+ @steps.max_line_length(self)
49
+ end
48
50
 
49
- def accept_hook?(hook)
50
- @tags.accept_hook?(hook) || @feature.accept_hook?(hook)
51
- end
51
+ def accept_hook?(hook)
52
+ Tags.matches?(source_tag_names, hook.tag_names)
53
+ end
52
54
 
53
- def tag_count(tag)
54
- @feature.tag_count(tag) == 0 ? @tags.count(tag) : @feature.tag_count(tag)
55
- end
55
+ def source_tag_names
56
+ (@tags.tag_names + (@feature ? @feature.source_tag_names : [])).uniq
57
+ end
56
58
 
57
- def language
58
- @feature.language
59
+ def tag_count(tag)
60
+ @feature.tag_count(tag) == 0 ? @tags.count(tag) : @feature.tag_count(tag)
61
+ end
62
+
63
+ def language
64
+ @feature.language
65
+ end
59
66
  end
60
- end
67
+ end
61
68
  end
@@ -163,4 +163,4 @@ module Cucumber
163
163
  end
164
164
  end
165
165
  end
166
- end
166
+ end
@@ -7,10 +7,32 @@ module Cucumber
7
7
  # This gets stored internally as <tt>["invoice", "release_2"]</tt>
8
8
  #
9
9
  class Tags #:nodoc:
10
- def self.strip_prefix(tag_name)
11
- tag_name =~ /^@(.*)/ ? $1 : tag_name
10
+ class << self
11
+ EXCLUDE_PATTERN = /^~/
12
+
13
+ def matches?(source_tag_names, tag_names)
14
+ exclude_tag_names, include_tag_names = tag_names.partition{|tag_name| exclude_tag?(tag_name)}
15
+ exclude_tag_names.map!{|name| name[1..-1]}
16
+ !excluded?(source_tag_names, exclude_tag_names) && included?(source_tag_names, include_tag_names)
17
+ end
18
+
19
+ def exclude_tag?(tag_name)
20
+ tag_name =~ EXCLUDE_PATTERN
21
+ end
22
+
23
+ private
24
+
25
+ def excluded?(source_tag_names, query_tag_names)
26
+ source_tag_names.any? && (source_tag_names & query_tag_names).any?
27
+ end
28
+
29
+ def included?(source_tag_names, query_tag_names)
30
+ query_tag_names.empty? || (source_tag_names & query_tag_names).any?
31
+ end
12
32
  end
13
33
 
34
+ attr_reader :tag_names
35
+
14
36
  def initialize(line, tag_names)
15
37
  @line, @tag_names = line, tag_names
16
38
  end
@@ -23,7 +45,7 @@ module Cucumber
23
45
  end
24
46
 
25
47
  def accept_hook?(hook)
26
- hook.tag_names.empty? || (hook.tag_names.map{|tag| Ast::Tags.strip_prefix(tag)} & @tag_names).any?
48
+ self.class.matches?(@tag_names, hook.tag_names)
27
49
  end
28
50
 
29
51
  def count(tag)
@@ -13,7 +13,13 @@ module Cucumber
13
13
  port ||= ENV["CUCUMBER_DRB"] || DEFAULT_PORT
14
14
 
15
15
  # See http://redmine.ruby-lang.org/issues/show/496 as to why we specify localhost:0
16
- DRb.start_service("druby://localhost:0")
16
+ begin
17
+ DRb.start_service("druby://localhost:0")
18
+ rescue SocketError
19
+ # Ruby-1.8.7 on snow leopard doesn't like localhost:0 - but just :0
20
+ # seems to work just fine
21
+ DRb.start_service("druby://:0")
22
+ end
17
23
  feature_server = DRbObject.new_with_uri("druby://127.0.0.1:#{port}")
18
24
  cloned_args = [] # I have no idea why this is needed, but if the regular args are sent then DRb magically transforms it into a DRb object - not an array
19
25
  args.each { |arg| cloned_args << arg }
@@ -8,6 +8,7 @@ require 'cucumber/formatter/color_io'
8
8
  require 'cucumber/cli/language_help_formatter'
9
9
  require 'cucumber/cli/configuration'
10
10
  require 'cucumber/cli/drb_client'
11
+ require 'cucumber/ast/tags'
11
12
 
12
13
  module Cucumber
13
14
  module Cli
@@ -67,9 +68,9 @@ module Cucumber
67
68
 
68
69
  def exceeded_tag_limts?(features)
69
70
  exceeded = false
70
- configuration.options[:include_tags].each do |tag, limit|
71
- unless limit.nil?
72
- tag_count = features.tag_count(tag)
71
+ configuration.options[:tag_names].each do |tag_name, limit|
72
+ if !Ast::Tags.exclude_tag?(tag_name) && limit
73
+ tag_count = features.tag_count(tag_name)
73
74
  if tag_count > limit.to_i
74
75
  exceeded = true
75
76
  end
@@ -20,8 +20,8 @@ module Cucumber
20
20
  max = BUILTIN_FORMATS.keys.map{|s| s.length}.max
21
21
  FORMAT_HELP = (BUILTIN_FORMATS.keys.sort.map do |key|
22
22
  " #{key}#{' ' * (max - key.length)} : #{BUILTIN_FORMATS[key][1]}"
23
- end) + ["Use --format rerun --out features.txt to rerun failing",
24
- "failing features in a new run with cucumber @features.txt.",
23
+ end) + ["Use --format rerun --out features.txt to write out failing",
24
+ "features. You can rerun them with cucumber @features.txt.",
25
25
  "FORMAT can also be the fully qualified class name of",
26
26
  "your own custom formatter. If the class isn't loaded,",
27
27
  "Cucumber will attempt to require a file with a relative",
@@ -134,16 +134,14 @@ module Cucumber
134
134
  end
135
135
  opts.on("-t TAGS", "--tags TAGS",
136
136
  "Only execute the features or scenarios with the specified tags.",
137
- "TAGS must be comma-separated without spaces. They can be",
138
- "specified with or without the @ prefix. Example: --tags dev\n",
137
+ "TAGS must be comma-separated without spaces. Example: --tags @dev\n",
139
138
  "Negative tags: Prefix tags with ~ to exclude features or scenarios",
140
- "having that tag.\n",
139
+ "having that tag. Example: --tags ~@slow\n",
141
140
  "Limit WIP: Positive tags can be given a threshold to limit the",
142
- "number of occurrences. Example: --tags qa:3 will fail if there",
141
+ "number of occurrences. Example: --tags @qa:3 will fail if there",
143
142
  "are more than 3 occurrences of the @qa tag.") do |v|
144
- include_tags, exclude_tags = *parse_tags(v)
145
- @options[:include_tags].merge!(include_tags)
146
- @options[:exclude_tags].merge!(exclude_tags)
143
+ tag_names = parse_tags(v)
144
+ @options[:tag_names].merge!(tag_names)
147
145
  end
148
146
  opts.on("-n NAME", "--name NAME",
149
147
  "Only execute the feature elements which match part of the given name.",
@@ -276,22 +274,15 @@ module Cucumber
276
274
 
277
275
  def parse_tags(tag_string)
278
276
  tag_names = tag_string.split(",")
279
- excludes, includes = tag_names.partition{|tag| tag =~ /^~/}
280
- excludes = excludes.map{|tag| tag[1..-1]}
281
-
282
- # Strip @
283
- includes = includes.map{|tag| Ast::Tags.strip_prefix(tag)}
284
- excludes = excludes.map{|tag| Ast::Tags.strip_prefix(tag)}
285
- [parse_tag_limits(includes), parse_tag_limits(excludes)]
277
+ parse_tag_limits(tag_names)
286
278
  end
287
279
 
288
- def parse_tag_limits(includes)
289
- dict = {}
290
- includes.each do |tag|
280
+ def parse_tag_limits(tag_names)
281
+ tag_names.inject({}) do |dict, tag|
291
282
  tag, limit = tag.split(':')
292
283
  dict[tag] = limit.nil? ? limit : limit.to_i
284
+ dict
293
285
  end
294
- dict
295
286
  end
296
287
 
297
288
  def disable_profile_loading?
@@ -328,8 +319,7 @@ module Cucumber
328
319
  def reverse_merge(other_options)
329
320
  @options = other_options.options.merge(@options)
330
321
  @options[:require] += other_options[:require]
331
- @options[:include_tags].merge! other_options[:include_tags]
332
- @options[:exclude_tags].merge! other_options[:exclude_tags]
322
+ @options[:tag_names].merge! other_options[:tag_names]
333
323
  @options[:env_vars] = other_options[:env_vars].merge(@options[:env_vars])
334
324
  if @options[:paths].empty?
335
325
  @options[:paths] = other_options[:paths]
@@ -381,8 +371,7 @@ module Cucumber
381
371
  :dry_run => false,
382
372
  :formats => [],
383
373
  :excludes => [],
384
- :include_tags => {},
385
- :exclude_tags => {},
374
+ :tag_names => {},
386
375
  :name_regexps => [],
387
376
  :env_vars => {},
388
377
  :diff_enabled => true
@@ -8,6 +8,7 @@ module Cucumber
8
8
  end
9
9
 
10
10
  class Object #:nodoc:
11
+ # TODO: Move most of this stuff out to an InstanceExecutor class.
11
12
  def cucumber_instance_exec(check_arity, pseudo_method, *args, &block)
12
13
  cucumber_run_with_backtrace_filtering(pseudo_method) do
13
14
  if check_arity && !cucumber_compatible_arity?(args, block)
@@ -21,7 +22,7 @@ class Object #:nodoc:
21
22
  )
22
23
  end
23
24
  else
24
- instance_exec(*Cucumber::StepMother.transform_arguments(args), &block)
25
+ instance_exec(*args, &block)
25
26
  end
26
27
  end
27
28
  end
@@ -4,8 +4,7 @@ module Cucumber
4
4
  def initialize(lines, options)
5
5
  @lines = lines
6
6
 
7
- @include_tags = options[:include_tags] ? options[:include_tags].keys : []
8
- @exclude_tags = options[:exclude_tags] ? options[:exclude_tags].keys : []
7
+ @tag_names = options[:tag_names] ? options[:tag_names].keys : []
9
8
  @name_regexps = options[:name_regexps] || []
10
9
  end
11
10
 
@@ -29,18 +28,9 @@ module Cucumber
29
28
  end
30
29
 
31
30
  def matches_tags?(syntax_node)
32
- !excluded_by_tags?(syntax_node) &&
33
- included_by_tags?(syntax_node)
31
+ syntax_node.matches_tags?(@tag_names)
34
32
  end
35
33
 
36
- def included_by_tags?(syntax_node)
37
- @include_tags.empty? || syntax_node.has_all_tags?(@include_tags)
38
- end
39
-
40
- def excluded_by_tags?(syntax_node)
41
- @exclude_tags.any? && syntax_node.has_tags?(@exclude_tags)
42
- end
43
-
44
34
  def outline_matches_names?(syntax_node)
45
35
  @name_regexps.nil? || @name_regexps.empty? || @name_regexps.detect{|name_regexp| syntax_node.outline_matches_name?(name_regexp)}
46
36
  end
@@ -92,6 +92,8 @@ module Cucumber
92
92
  return unless options[:snippets]
93
93
  undefined = step_mother.steps(:undefined)
94
94
  return if undefined.empty?
95
+
96
+ unknown_programming_language = step_mother.unknown_programming_language?
95
97
  snippets = undefined.map do |step|
96
98
  step_name = Undefined === step.exception ? step.exception.step_name : step.name
97
99
  step_multiline_class = step.multiline_arg ? step.multiline_arg.class : nil
@@ -101,8 +103,13 @@ module Cucumber
101
103
 
102
104
  text = "\nYou can implement step definitions for undefined steps with these snippets:\n\n"
103
105
  text += snippets.join("\n\n")
104
-
105
106
  @io.puts format_string(text, :undefined)
107
+
108
+ if unknown_programming_language
109
+ @io.puts format_string("\nIf you want snippets in a different programming language, just make sure a file\n" +
110
+ "with the appropriate file extension exists where cucumber looks for step definitions.", :failed)
111
+ end
112
+
106
113
  @io.puts
107
114
  @io.flush
108
115
  end
@@ -119,23 +126,25 @@ module Cucumber
119
126
  end
120
127
 
121
128
  def print_tag_limit_warnings(options)
122
- return unless tag_limit_breached?(options, @tag_occurences)
123
- @io.puts
124
- @io.puts format_string("Failed due to exceeding the tag limit", :failed)
125
- options[:include_tags].each do |tag_name, limit|
126
- tag_frequnecy = @tag_occurences[tag_name].size
127
- if limit && tag_frequnecy > limit
128
- @io.puts format_string("@#{tag_name} occurred:#{tag_frequnecy} limit:#{limit}", :failed)
129
- @tag_occurences[tag_name].each {|location| @io.puts format_string(" #{location}", :failed)}
130
- @io.flush
129
+ first_tag = true
130
+ options[:tag_names].each do |tag_name, limit|
131
+ unless Ast::Tags.exclude_tag?(tag_name)
132
+ tag_frequnecy = @tag_occurences[tag_name].size
133
+ if limit && tag_frequnecy > limit
134
+ @io.puts if first_tag
135
+ first_tag = false
136
+ @io.puts format_string("#{tag_name} occurred #{tag_frequnecy} times, but the limit was set to #{limit}", :failed)
137
+ @tag_occurences[tag_name].each {|location| @io.puts format_string(" #{location}", :failed)}
138
+ @io.flush
139
+ end
131
140
  end
132
141
  end
133
142
  end
134
143
 
135
144
  def record_tag_occurrences(feature_element, options)
136
145
  @tag_occurences ||= Hash.new{|k,v| k[v] = []}
137
- options[:include_tags].each do |tag_name, limit|
138
- if feature_element.tag_count(tag_name) > 0
146
+ options[:tag_names].each do |tag_name, limit|
147
+ if !Ast::Tags.exclude_tag?(tag_name) && feature_element.tag_count(tag_name) > 0
139
148
  @tag_occurences[tag_name] << feature_element.file_colon_line
140
149
  end
141
150
  end
@@ -171,15 +180,6 @@ module Cucumber
171
180
  raise "No format for #{key.inspect}: #{FORMATS.inspect}" if fmt.nil?
172
181
  fmt
173
182
  end
174
-
175
- def tag_limit_breached?(options, tag_occurences)
176
- return if tag_occurences.nil?
177
- tag_limit_breached = false
178
- options[:include_tags].each do |tag_name, limit|
179
- tag_limit_breached ||= limit && (tag_occurences[tag_name].size > limit)
180
- end
181
- tag_limit_breached
182
- end
183
183
  end
184
184
  end
185
185
  end
@@ -68,7 +68,7 @@ module Cucumber
68
68
  def visit_tag_name(tag_name)
69
69
  @builder.text!(@tag_spacer) if @tag_spacer
70
70
  @tag_spacer = ' '
71
- @builder.span("@#{tag_name}", :class => 'tag')
71
+ @builder.span(tag_name, :class => 'tag')
72
72
  end
73
73
 
74
74
  def visit_feature_name(name)
@@ -197,7 +197,7 @@ module Cucumber
197
197
  end
198
198
 
199
199
  def visit_tag_name(tag_name)
200
- tag = format_string("@#{tag_name}", :tag).indent(@indent)
200
+ tag = format_string(tag_name, :tag).indent(@indent)
201
201
  # TODO should we render tags at all? skipped for now. difficult to place due to page breaks
202
202
  end
203
203
 
@@ -63,7 +63,7 @@ module Cucumber
63
63
  end
64
64
 
65
65
  def visit_tag_name(tag_name)
66
- tag = format_string("@#{tag_name}", :tag).indent(@indent)
66
+ tag = format_string(tag_name, :tag).indent(@indent)
67
67
  @io.print(tag)
68
68
  @io.flush
69
69
  @indent = 1