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.
- data/History.txt +26 -2
- data/Manifest.txt +6 -4
- data/config/hoe.rb +2 -2
- data/examples/pure_java/README.textile +2 -2
- data/examples/self_test/features/step_definitions/sample_steps.rb +0 -12
- data/examples/self_test/features/support/env.rb +0 -6
- data/features/cucumber_cli.feature +11 -24
- data/features/custom_formatter.feature +2 -2
- data/features/default_snippets.feature +42 -0
- data/features/html_formatter/a.html +1 -1
- data/features/negative_tagged_hooks.feature +61 -0
- data/features/step_definitions/cucumber_steps.rb +1 -1
- data/features/transform.feature +88 -12
- data/features/usage.feature +0 -6
- data/lib/cucumber/ast/feature.rb +4 -0
- data/lib/cucumber/ast/feature_element.rb +49 -42
- data/lib/cucumber/ast/step_invocation.rb +1 -1
- data/lib/cucumber/ast/tags.rb +25 -3
- data/lib/cucumber/cli/drb_client.rb +7 -1
- data/lib/cucumber/cli/main.rb +4 -3
- data/lib/cucumber/cli/options.rb +13 -24
- data/lib/cucumber/core_ext/instance_exec.rb +2 -1
- data/lib/cucumber/filter.rb +2 -12
- data/lib/cucumber/formatter/console.rb +21 -21
- data/lib/cucumber/formatter/html.rb +1 -1
- data/lib/cucumber/formatter/pdf.rb +1 -1
- data/lib/cucumber/formatter/pretty.rb +1 -1
- data/lib/cucumber/language_support/language_methods.rb +19 -2
- data/lib/cucumber/parser/feature.rb +13 -50
- data/lib/cucumber/parser/feature.tt +13 -47
- data/lib/cucumber/parser/natural_language.rb +1 -1
- data/lib/cucumber/rails/action_controller.rb +33 -0
- data/lib/cucumber/rails/active_record.rb +27 -0
- data/lib/cucumber/rails/rspec.rb +1 -1
- data/lib/cucumber/rails/test_unit.rb +9 -0
- data/lib/cucumber/rails/world.rb +7 -78
- data/lib/cucumber/rb_support/rb_dsl.rb +6 -4
- data/lib/cucumber/rb_support/rb_language.rb +8 -2
- data/lib/cucumber/rb_support/rb_step_definition.rb +2 -5
- data/lib/cucumber/rb_support/rb_transform.rb +35 -0
- data/lib/cucumber/rb_support/rb_world.rb +7 -1
- data/lib/cucumber/step_match.rb +1 -1
- data/lib/cucumber/step_mother.rb +8 -31
- data/lib/cucumber/version.rb +1 -1
- data/rails_generators/cucumber/templates/cucumber_environment.rb +2 -2
- data/rails_generators/cucumber/templates/env.rb +1 -8
- data/spec/cucumber/ast/feature_element_spec.rb +24 -23
- data/spec/cucumber/ast/scenario_outline_spec.rb +1 -1
- data/spec/cucumber/cli/options_spec.rb +5 -14
- data/spec/cucumber/parser/feature_parser_spec.rb +6 -6
- data/spec/cucumber/step_mother_spec.rb +41 -38
- metadata +10 -8
- data/examples/self_test/features/transform_sample.feature +0 -10
- data/spec/cucumber/rails/stubs/mini_rails.rb +0 -18
- data/spec/cucumber/rails/stubs/test_help.rb +0 -1
- data/spec/cucumber/rails/world_spec.rb +0 -16
data/features/usage.feature
CHANGED
@@ -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
|
data/lib/cucumber/ast/feature.rb
CHANGED
@@ -1,61 +1,68 @@
|
|
1
1
|
require 'enumerator'
|
2
|
+
require 'cucumber/ast/tags'
|
2
3
|
|
3
4
|
module Cucumber
|
4
|
-
module
|
5
|
-
|
5
|
+
module Ast
|
6
|
+
module FeatureElement #:nodoc:
|
7
|
+
attr_accessor :feature
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
9
|
+
def attach_steps(steps)
|
10
|
+
steps.each {|step| step.feature_element = self}
|
11
|
+
end
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
13
|
+
def file_colon_line(line = @line)
|
14
|
+
@feature.file_colon_line(line) if @feature
|
15
|
+
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
def text_length
|
18
|
+
name_line_lengths.max
|
19
|
+
end
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
def first_line_length
|
22
|
+
name_line_lengths[0]
|
23
|
+
end
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
35
|
+
def matches_scenario_names?(scenario_name_regexps)
|
36
|
+
scenario_name_regexps.detect{|name| name =~ @name}
|
37
|
+
end
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
-
|
39
|
+
def backtrace_line(name = "#{@keyword} #{@name}", line = @line)
|
40
|
+
@feature.backtrace_line(name, line) if @feature
|
41
|
+
end
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
43
|
+
def source_indent(text_length)
|
44
|
+
max_line_length - text_length
|
45
|
+
end
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
-
|
47
|
+
def max_line_length
|
48
|
+
@steps.max_line_length(self)
|
49
|
+
end
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
-
|
51
|
+
def accept_hook?(hook)
|
52
|
+
Tags.matches?(source_tag_names, hook.tag_names)
|
53
|
+
end
|
52
54
|
|
53
|
-
|
54
|
-
|
55
|
-
|
55
|
+
def source_tag_names
|
56
|
+
(@tags.tag_names + (@feature ? @feature.source_tag_names : [])).uniq
|
57
|
+
end
|
56
58
|
|
57
|
-
|
58
|
-
|
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
|
-
|
67
|
+
end
|
61
68
|
end
|
data/lib/cucumber/ast/tags.rb
CHANGED
@@ -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
|
-
|
11
|
-
|
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
|
-
|
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
|
-
|
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 }
|
data/lib/cucumber/cli/main.rb
CHANGED
@@ -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[:
|
71
|
-
|
72
|
-
tag_count = features.tag_count(
|
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
|
data/lib/cucumber/cli/options.rb
CHANGED
@@ -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
|
24
|
-
"
|
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.
|
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
|
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
|
-
|
145
|
-
@options[:
|
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
|
-
|
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(
|
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[:
|
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
|
-
:
|
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(*
|
25
|
+
instance_exec(*args, &block)
|
25
26
|
end
|
26
27
|
end
|
27
28
|
end
|
data/lib/cucumber/filter.rb
CHANGED
@@ -4,8 +4,7 @@ module Cucumber
|
|
4
4
|
def initialize(lines, options)
|
5
5
|
@lines = lines
|
6
6
|
|
7
|
-
@
|
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
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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[:
|
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
|
@@ -197,7 +197,7 @@ module Cucumber
|
|
197
197
|
end
|
198
198
|
|
199
199
|
def visit_tag_name(tag_name)
|
200
|
-
tag = format_string(
|
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
|
|