cucumber 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/History.txt +30 -4
  2. data/Rakefile +11 -8
  3. data/VERSION.yml +1 -1
  4. data/bin/cucumber +1 -2
  5. data/cucumber.gemspec +40 -38
  6. data/examples/i18n/da/features/{summering.feature → sammenlaegning.feature} +5 -5
  7. data/examples/i18n/da/features/step_definitons/{kalkulator_steps.rb → lommeregner_steps.rb} +3 -3
  8. data/examples/i18n/da/lib/{kalkulator.rb → lommeregner.rb} +2 -2
  9. data/examples/tickets/Rakefile +5 -1
  10. data/examples/tickets/features.html +138 -0
  11. data/examples/watir/Rakefile +4 -0
  12. data/examples/watir/features/{step_definitons → step_definitions}/search_steps.rb +0 -0
  13. data/examples/watir/features/support/screenshots.rb +45 -0
  14. data/features/announce.feature +122 -0
  15. data/features/html_formatter/a.html +1 -1
  16. data/features/step_definitions/cucumber_steps.rb +1 -1
  17. data/features/step_definitions/wire_steps.rb +14 -0
  18. data/features/support/env.rb +1 -1
  19. data/features/support/fake_wire_server.rb +63 -0
  20. data/features/tag_logic.feature +226 -0
  21. data/features/wire_protocol.feature +177 -0
  22. data/lib/cucumber/ast/examples.rb +4 -0
  23. data/lib/cucumber/ast/feature_element.rb +2 -1
  24. data/lib/cucumber/ast/scenario_outline.rb +4 -0
  25. data/lib/cucumber/ast/table.rb +13 -8
  26. data/lib/cucumber/ast/tags.rb +56 -12
  27. data/lib/cucumber/ast/tree_walker.rb +6 -0
  28. data/lib/cucumber/cli/main.rb +7 -5
  29. data/lib/cucumber/cli/options.rb +19 -10
  30. data/lib/cucumber/filter.rb +1 -2
  31. data/lib/cucumber/formatter/ansicolor.rb +17 -2
  32. data/lib/cucumber/formatter/console.rb +50 -32
  33. data/lib/cucumber/formatter/html.rb +21 -1
  34. data/lib/cucumber/formatter/junit.rb +8 -0
  35. data/lib/cucumber/formatter/pretty.rb +3 -0
  36. data/lib/cucumber/formatter/summary.rb +35 -0
  37. data/lib/cucumber/formatter/usage.rb +4 -4
  38. data/lib/cucumber/rails/active_record.rb +18 -10
  39. data/lib/cucumber/rb_support/rb_language.rb +7 -2
  40. data/lib/cucumber/rb_support/rb_world.rb +4 -0
  41. data/lib/cucumber/step_match.rb +3 -2
  42. data/lib/cucumber/step_mother.rb +6 -1
  43. data/lib/cucumber/wire_support/connection.rb +42 -0
  44. data/lib/cucumber/wire_support/request_handler.rb +19 -0
  45. data/lib/cucumber/wire_support/wire_exception.rb +10 -0
  46. data/lib/cucumber/wire_support/wire_language.rb +52 -0
  47. data/lib/cucumber/wire_support/wire_packet.rb +34 -0
  48. data/lib/cucumber/wire_support/wire_protocol.rb +64 -0
  49. data/lib/cucumber/wire_support/wire_step_definition.rb +21 -0
  50. data/rails_generators/cucumber/cucumber_generator.rb +7 -4
  51. data/rails_generators/cucumber/templates/cucumber_environment.rb +4 -4
  52. data/rails_generators/cucumber/templates/version_check.rb +6 -4
  53. data/spec/cucumber/ast/table_spec.rb +11 -1
  54. data/spec/cucumber/ast/tags_spec.rb +29 -0
  55. data/spec/cucumber/cli/options_spec.rb +8 -4
  56. data/spec/cucumber/formatter/junit_spec.rb +11 -0
  57. data/spec/cucumber/step_match_spec.rb +11 -0
  58. data/spec/cucumber/wire_support/wire_language_spec.rb +47 -0
  59. data/spec/cucumber/wire_support/wire_packet_spec.rb +26 -0
  60. metadata +38 -36
  61. data/examples/cs/.gitignore +0 -1
  62. data/examples/cs/README.textile +0 -1
  63. data/examples/cs/Rakefile +0 -12
  64. data/examples/cs/compile.bat +0 -1
  65. data/examples/cs/features/addition.feature +0 -16
  66. data/examples/cs/features/step_definitons/calculator_steps.rb +0 -19
  67. data/examples/cs/src/demo/Calculator.cs +0 -20
  68. data/examples/java/.gitignore +0 -1
  69. data/examples/java/README.textile +0 -18
  70. data/examples/java/build.xml +0 -33
  71. data/examples/java/features/hello.feature +0 -11
  72. data/examples/java/features/step_definitons/hello_steps.rb +0 -23
  73. data/examples/java/features/step_definitons/tree_steps.rb +0 -14
  74. data/examples/java/features/tree.feature +0 -9
  75. data/examples/java/src/.gitignore +0 -1
  76. data/examples/java/src/cucumber/demo/.gitignore +0 -1
  77. data/examples/java/src/cucumber/demo/Hello.java +0 -16
  78. data/examples/pure_java/README.textile +0 -5
@@ -0,0 +1,177 @@
1
+ @wire
2
+ Feature: Wire Protocol
3
+ In order to be allow Cucumber to touch my app in intimate places
4
+ As a developer on platform which doesn't support Ruby
5
+ I want a low-level protocol which Cucumber can use to run steps within my app
6
+
7
+ #
8
+ # Cucumber's wire protocol is an implementation of Cucumber's internal 'programming language' abstraction,
9
+ # and allows step definitions to be implemented and invoked on any platform.
10
+ #
11
+ # Communication is over a TCP socket, which Cucumber connects to when it finds a definition file with the
12
+ # .wire extension in the step_definitions folder (or other load path).
13
+ #
14
+ # There are currently two messages which Cucumber sends over the wire:
15
+ #
16
+ # * step_matches : this is used to find out whether the wire end has a definition for a given step
17
+ # * invoke : this is used to ask for a step definition to be invoked
18
+ #
19
+ # Message packets are formatted as JSON-encoded strings, with a newline character signalling the end of a
20
+ # packet. These messages are described below, with examples.
21
+ #
22
+
23
+ Background:
24
+ Given a standard Cucumber project directory structure
25
+ And a file named "features/wired.feature" with:
26
+ """
27
+ Scenario: Wired
28
+ Given we're all wired
29
+
30
+ """
31
+ And a file named "features/step_definitions/some_remote_place.wire" with:
32
+ """
33
+ host: localhost
34
+ port: 98989
35
+
36
+ """
37
+
38
+
39
+ #
40
+ # step_matches
41
+ #
42
+ # When the features have been parsed, Cucumber will send a step_matches message to ask the wire end
43
+ # if it can match a step name. This happens for each of the steps in each of the features.
44
+ # The wire end replies with a step_match array, containing the IDs of any step definitions that could
45
+ # be invoked for the given step name.
46
+
47
+ Scenario: Dry run finds no step match
48
+ Given there is a wire server running on port 98989 which understands the following protocol:
49
+ | request | response |
50
+ | ["step_matches",{"name_to_match":"we're all wired"}] | ["step_matches",[]] |
51
+ When I run cucumber --dry-run -f progress features
52
+ And it should pass with
53
+ """
54
+ U
55
+
56
+ 1 scenario (1 undefined)
57
+ 1 step (1 undefined)
58
+
59
+ """
60
+
61
+ # When a step match is returned, it contains an identifier for the step definition to be used
62
+ # later when referring to this step definition again if it needs to be invoked. The identifier
63
+ # can take any form (as long as it's within a string) and is simply used for the wire end's own
64
+ # reference.
65
+ # The step match also contains any argument values as parsed out by the wire end's own regular
66
+ # expression or other argument matching process.
67
+ Scenario: Dry run finds a step match
68
+ Given there is a wire server running on port 98989 which understands the following protocol:
69
+ | request | response |
70
+ | ["step_matches",{"name_to_match":"we're all wired"}] | ["step_matches",[{"id":"1", "args":[]}]] |
71
+ When I run cucumber --dry-run -f progress features
72
+ And it should pass with
73
+ """
74
+ -
75
+
76
+ 1 scenario (1 skipped)
77
+ 1 step (1 skipped)
78
+
79
+ """
80
+
81
+
82
+ #
83
+ # invoke
84
+ #
85
+ # Assuming a step_match was returned for a given step name, when it's time to invoke that
86
+ # step definition, Cucumber will send an invoke message.
87
+ # The message contains the ID of the step definition, as returned by the wire end from the
88
+ # step_matches call, along with the arguments that were parsed from the step name during the
89
+ # same step_matches call.
90
+ # The wire end will reply with either a step_failed or a success message.
91
+
92
+ Scenario: Invoke a step definition which passes
93
+ Given there is a wire server running on port 98989 which understands the following protocol:
94
+ | request | response |
95
+ | ["step_matches",{"name_to_match":"we're all wired"}] | ["step_matches",[{"id":"1", "args":[]}]] |
96
+ | ["begin_scenario",null] | ["success",null] |
97
+ | ["invoke",{"id":"1","args":[]}] | ["success",null] |
98
+ | ["end_scenario",null] | ["success",null] |
99
+ When I run cucumber -f progress --backtrace features
100
+ And it should pass with
101
+ """
102
+ .
103
+
104
+ 1 scenario (1 passed)
105
+ 1 step (1 passed)
106
+
107
+ """
108
+
109
+ # When a step definition fails, it can return details of the exception in the reply to invoke. These
110
+ # will then be passed by Cucumber to the formatters for display to the user.
111
+ #
112
+ Scenario: Invoke a step definition which fails
113
+ Given there is a wire server running on port 98989 which understands the following protocol:
114
+ | request | response |
115
+ | ["step_matches",{"name_to_match":"we're all wired"}] | ["step_matches",[{"id":"1", "args":[]}]] |
116
+ | ["begin_scenario",null] | ["success",null] |
117
+ | ["invoke",{"id":"1","args":[]}] | ["step_failed",{"message":"The wires are down"}] |
118
+ | ["end_scenario",null] | ["success",null] |
119
+ When I run cucumber -f progress features
120
+ Then STDERR should be empty
121
+ And it should fail with
122
+ """
123
+ F
124
+
125
+ (::) failed steps (::)
126
+
127
+ The wires are down (Cucumber::WireSupport::WireException)
128
+ features/wired.feature:2:in `Given we're all wired'
129
+
130
+ Failing Scenarios:
131
+ cucumber features/wired.feature:1 # Scenario: Wired
132
+
133
+ 1 scenario (1 failed)
134
+ 1 step (1 failed)
135
+
136
+ """
137
+
138
+ # Imagine we have a step definition like:
139
+ #
140
+ # Given /we're all (.*)/ do |what_we_are|
141
+ # end
142
+ #
143
+ # When this step definition matches the step name in our feature, the word 'wired' will be parsed as an
144
+ # argument.
145
+ #
146
+ # Cucumber expects this StepArgument to be returned in the StepMatch. The keys have the following meanings:
147
+ # * val : the value of the string captured for that argument from the step name passed in step_matches
148
+ # * pos : the position within the step name that the argument was matched (used for formatter highlighting)
149
+ #
150
+ Scenario: Invoke a step definition which takes arguments (and passes)
151
+ Given there is a wire server running on port 98989 which understands the following protocol:
152
+ | request | response |
153
+ | ["step_matches",{"name_to_match":"we're all wired"}] | ["step_matches",[{"id":"1", "args":[{"val":"wired", "pos":10}]}]] |
154
+ | ["begin_scenario",null] | ["success",null] |
155
+ | ["invoke",{"id":"1","args":["wired"]}] | ["success",null] |
156
+ | ["end_scenario",null] | ["success",null] |
157
+ When I run cucumber -f progress --backtrace features
158
+ Then STDERR should be empty
159
+ And it should pass with
160
+ """
161
+ .
162
+
163
+ 1 scenario (1 passed)
164
+ 1 step (1 passed)
165
+
166
+ """
167
+
168
+
169
+ Scenario: Unexpected response
170
+ Given there is a wire server running on port 98989 which understands the following protocol:
171
+ | request | response |
172
+ | ["begin_scenario",null] | ["yikes"] |
173
+ When I run cucumber -f progress features
174
+ Then STDERR should match
175
+ """
176
+ undefined method `handle_yikes'
177
+ """
@@ -20,6 +20,10 @@ module Cucumber
20
20
  @outline_table.cells_rows[1..-1].each(&proc)
21
21
  end
22
22
 
23
+ def failed?
24
+ @outline_table.cells_rows[1..-1].select{|row| row.failed?}.any?
25
+ end
26
+
23
27
  def to_sexp
24
28
  sexp = [:examples, @keyword, @name]
25
29
  comment = @comment.to_sexp
@@ -49,7 +49,8 @@ module Cucumber
49
49
  end
50
50
 
51
51
  def accept_hook?(hook)
52
- Tags.matches?(source_tag_names, hook.tag_names)
52
+ parsed_hook_tag_names = hook.tag_names.map {|tag_string| Cli::Options.parse_tag_arguments(tag_string)}
53
+ Tags.matches?(source_tag_names, parsed_hook_tag_names)
53
54
  end
54
55
 
55
56
  def source_tag_names
@@ -80,6 +80,10 @@ module Cucumber
80
80
  )
81
81
  end
82
82
 
83
+ def failed?
84
+ @examples_array.select{|examples| examples.failed?}.any?
85
+ end
86
+
83
87
  def to_sexp
84
88
  sexp = [:scenario_outline, @keyword, @name]
85
89
  comment = @comment.to_sexp
@@ -39,7 +39,8 @@ module Cucumber
39
39
  "table"
40
40
  end
41
41
 
42
- # Creates a new instance. +raw+ should be an Array of Array of String.
42
+ # Creates a new instance. +raw+ should be an Array of Array of String
43
+ # or an Array of Hash (similar to what #hashes returns).
43
44
  # You don't typically create your own Table objects - Cucumber will do
44
45
  # it internally and pass them to your Step Definitions.
45
46
  #
@@ -47,12 +48,18 @@ module Cucumber
47
48
  @cells_class = Cells
48
49
  @cell_class = Cell
49
50
 
51
+ raw = ensure_array_of_array(raw)
50
52
  # Verify that it's square
51
53
  transposed = raw.transpose
52
54
  create_cell_matrix(raw)
53
55
  @conversion_procs = conversion_procs
54
56
  end
55
57
 
58
+ # JSON representation
59
+ def to_json
60
+ raw.to_json
61
+ end
62
+
56
63
  # Creates a copy of this table, inheriting any column mappings.
57
64
  # registered with #map_headers!
58
65
  #
@@ -261,7 +268,7 @@ module Cucumber
261
268
  # objects in their cells, you may want to use #map_column! before calling
262
269
  # #diff!. You can use #map_column! on either of the tables.
263
270
  #
264
- # An exception is raised if there are missing rows or columns, or
271
+ # A Different error is raised if there are missing rows or columns, or
265
272
  # surplus rows. An error is <em>not</em> raised for surplus columns.
266
273
  # Whether to raise or not raise can be changed by setting values in
267
274
  # +options+ to true or false:
@@ -524,20 +531,18 @@ module Cucumber
524
531
 
525
532
  def ensure_table(table_or_array) #:nodoc:
526
533
  return table_or_array if Table === table_or_array
527
- table_or_array = hashes_to_array(table_or_array) if Hash === table_or_array[0]
528
- table_or_array = enumerable_to_array(table_or_array) unless Array == table_or_array[0]
529
534
  Table.new(table_or_array)
530
535
  end
531
536
 
537
+ def ensure_array_of_array(array)
538
+ Hash === array[0] ? hashes_to_array(array) : array
539
+ end
540
+
532
541
  def hashes_to_array(hashes) #:nodoc:
533
542
  header = hashes[0].keys
534
543
  [header] + hashes.map{|hash| header.map{|key| hash[key]}}
535
544
  end
536
545
 
537
- def enumerable_to_array(rows) #:nodoc:
538
- rows.map{|row| row.map{|cell| cell}}
539
- end
540
-
541
546
  def ensure_green! #:nodoc:
542
547
  each_cell{|cell| cell.status = :passed}
543
548
  end
@@ -1,5 +1,8 @@
1
+ require 'set'
2
+
1
3
  module Cucumber
2
4
  module Ast
5
+
3
6
  # Holds the names of tags parsed from a feature file:
4
7
  #
5
8
  # @invoice @release_2
@@ -7,33 +10,74 @@ module Cucumber
7
10
  # This gets stored internally as <tt>["invoice", "release_2"]</tt>
8
11
  #
9
12
  class Tags #:nodoc:
13
+
14
+ class And #:nodoc:
15
+ def initialize(tag_names)
16
+ @negative_tags, @positive_tags = tag_names.partition{|tag_name| Tags.exclude_tag?(tag_name)}
17
+ @negative_tags = Tags.strip_negative_char(@negative_tags)
18
+ end
19
+
20
+ def matches?(tag_names)
21
+ included?(tag_names) && !excluded?(tag_names)
22
+ end
23
+
24
+ private
25
+
26
+ def excluded?(tag_names)
27
+ (@negative_tags & tag_names).any?
28
+ end
29
+
30
+ def included?(tag_names)
31
+ positive_tag_set = Set.new(@positive_tags)
32
+ tag_names_set = Set.new(tag_names)
33
+ positive_tag_set.subset?(tag_names_set)
34
+ end
35
+ end
36
+
37
+ class Or #:nodoc:
38
+ def initialize(tag_exp)
39
+ @exp = tag_exp
40
+ end
41
+
42
+ def matches?(tag_names)
43
+ @exp.inject(false){|matches, tag_exp| matches || tag_exp.matches?(tag_names)}
44
+ end
45
+ end
46
+
10
47
  class << self
11
48
  EXCLUDE_PATTERN = /^~/
12
49
 
13
50
  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
- check_at_sign_prefix(exclude_tag_names + include_tag_names)
17
- !excluded?(source_tag_names, exclude_tag_names) && included?(source_tag_names, include_tag_names)
51
+ validate_tags(tag_names)
52
+ tag_names.empty? ? true : check_if_tags_match(source_tag_names, tag_names)
18
53
  end
19
54
 
20
55
  def exclude_tag?(tag_name)
21
56
  tag_name =~ EXCLUDE_PATTERN
22
57
  end
23
-
58
+
59
+ def strip_negative_char(tag_names)
60
+ tag_names.map{|name| name[1..-1]}
61
+ end
62
+
24
63
  private
25
64
 
26
- def check_at_sign_prefix(tag_names)
27
- tag_names.each{|tag_name| raise "Tag names must start with an @ sign. The following tag name didn't: #{tag_name}" unless tag_name[0..0] == '@'}
65
+ def validate_tags(tag_name_list)
66
+ all_tag_names = tag_name_list.flatten
67
+ exclude_tag_names, include_tag_names = all_tag_names.partition{|tag_name| exclude_tag?(tag_name)}
68
+ exclude_tag_names = strip_negative_char(exclude_tag_names)
69
+ check_at_sign_prefix(exclude_tag_names + include_tag_names)
28
70
  end
29
71
 
30
- def excluded?(source_tag_names, query_tag_names)
31
- source_tag_names.any? && (source_tag_names & query_tag_names).any?
72
+ def check_if_tags_match(source_tag_names, tag_names)
73
+ tag_exp = Or.new(tag_names.map{|tag_name_list| And.new(tag_name_list) })
74
+ tag_exp.matches?(source_tag_names)
32
75
  end
33
-
34
- def included?(source_tag_names, query_tag_names)
35
- query_tag_names.empty? || (source_tag_names & query_tag_names).any?
76
+
77
+ def check_at_sign_prefix(tag_names)
78
+ tag_names.each{|tag_name| raise "Tag names must start with an @ sign. The following tag name didn't: #{tag_name}" unless tag_name[0..0] == '@'}
36
79
  end
80
+
37
81
  end
38
82
 
39
83
  attr_reader :tag_names
@@ -146,6 +146,12 @@ module Cucumber
146
146
  def announce(announcement)
147
147
  broadcast(announcement)
148
148
  end
149
+
150
+ # Embed +file+ of +mime_type+ in the formatter. This method can be called from within StepDefinitions.
151
+ # For most formatters this is a no-op.
152
+ def embed(file, mime_type)
153
+ broadcast(file, mime_type)
154
+ end
149
155
 
150
156
  private
151
157
 
@@ -69,11 +69,13 @@ module Cucumber
69
69
 
70
70
  def exceeded_tag_limts?(features)
71
71
  exceeded = false
72
- configuration.options[:tag_names].each do |tag_name, limit|
73
- if !Ast::Tags.exclude_tag?(tag_name) && limit
74
- tag_count = features.tag_count(tag_name)
75
- if tag_count > limit.to_i
76
- exceeded = true
72
+ configuration.options[:tag_names].each do |tag_list|
73
+ tag_list.each do |tag_name, limit|
74
+ if !Ast::Tags.exclude_tag?(tag_name) && limit
75
+ tag_count = features.tag_count(tag_name)
76
+ if tag_count > limit.to_i
77
+ exceeded = true
78
+ end
77
79
  end
78
80
  end
79
81
  end
@@ -6,14 +6,14 @@ module Cucumber
6
6
  BUILTIN_FORMATS = {
7
7
  'html' => ['Cucumber::Formatter::Html', 'Generates a nice looking HTML report.'],
8
8
  'pretty' => ['Cucumber::Formatter::Pretty', 'Prints the feature as is - in colours.'],
9
- 'pdf' => ['Cucumber::Formatter::Pdf', "Generates a PDF report. You need to have the\n" +
10
- "#{' ' * 51}prawn gem installed. Will pick up logo from\n" +
9
+ 'pdf' => ['Cucumber::Formatter::Pdf', "Generates a PDF report. You need to have the\n" +
10
+ "#{' ' * 51}prawn gem installed. Will pick up logo from\n" +
11
11
  "#{' ' * 51}features/support/logo.png or\n" +
12
12
  "#{' ' * 51}features/support/logo.jpg if present."],
13
13
  'progress' => ['Cucumber::Formatter::Progress', 'Prints one character per scenario.'],
14
14
  'rerun' => ['Cucumber::Formatter::Rerun', 'Prints failing files with line numbers.'],
15
15
  'usage' => ['Cucumber::Formatter::Usage', "Prints where step definitions are used.\n" +
16
- "#{' ' * 51}The slowest step definitions (with duration) are\n" +
16
+ "#{' ' * 51}The slowest step definitions (with duration) are\n" +
17
17
  "#{' ' * 51}listed first. If --dry-run is used the duration\n" +
18
18
  "#{' ' * 51}is not shown, and step definitions are sorted by\n" +
19
19
  "#{' ' * 51}filename instead."],
@@ -48,6 +48,10 @@ module Cucumber
48
48
  new(out_stream, error_stream, options).parse!(args)
49
49
  end
50
50
 
51
+ def self.parse_tag_arguments(tags_string)
52
+ tags_string.split(',')
53
+ end
54
+
51
55
  def initialize(out_stream = STDOUT, error_stream = STDERR, options = {})
52
56
  @out_stream = out_stream
53
57
  @error_stream = error_stream
@@ -141,13 +145,18 @@ module Cucumber
141
145
  opts.on("-t TAGS", "--tags TAGS",
142
146
  "Only execute the features or scenarios with the specified tags.",
143
147
  "TAGS must be comma-separated without spaces. Example: --tags @dev\n",
148
+ "You can select tags using logical AND or logical OR:",
149
+ "To execute anything that is tagged with both @dev AND @prod\n",
150
+ "Example: --tags @dev,@prod",
151
+ "To execute anything that is tagged with @dev OR @prod\n",
152
+ "Example: --tags @dev --tags @prod\n",
144
153
  "Negative tags: Prefix tags with ~ to exclude features or scenarios",
145
154
  "having that tag. Example: --tags ~@slow\n",
146
155
  "Limit WIP: Positive tags can be given a threshold to limit the",
147
156
  "number of occurrences. Example: --tags @qa:3 will fail if there",
148
157
  "are more than 3 occurrences of the @qa tag.") do |v|
149
158
  tag_names = parse_tags(v)
150
- @options[:tag_names].merge!(tag_names)
159
+ @options[:tag_names] << tag_names
151
160
  end
152
161
  opts.on("-n NAME", "--name NAME",
153
162
  "Only execute the feature elements which match part of the given name.",
@@ -158,7 +167,7 @@ module Cucumber
158
167
  opts.on("-e", "--exclude PATTERN", "Don't run feature files or require ruby files matching PATTERN") do |v|
159
168
  @options[:excludes] << Regexp.new(v)
160
169
  end
161
- opts.on(PROFILE_SHORT_FLAG, "#{PROFILE_LONG_FLAG} PROFILE",
170
+ opts.on(PROFILE_SHORT_FLAG, "#{PROFILE_LONG_FLAG} PROFILE",
162
171
  "Pull commandline arguments from cucumber.yml which can be defined as",
163
172
  "strings or arrays. When a 'default' profile is defined and no profile",
164
173
  "is specified it is always used. (Unless disabled, see -P below.)",
@@ -241,7 +250,7 @@ module Cucumber
241
250
  Kernel.exit(0)
242
251
  end
243
252
  end.parse!
244
-
253
+
245
254
  if @quiet
246
255
  @options[:snippets] = @options[:source] = false
247
256
  else
@@ -283,10 +292,10 @@ module Cucumber
283
292
  end
284
293
 
285
294
  def parse_tags(tag_string)
286
- tag_names = tag_string.split(",")
295
+ tag_names = Options.parse_tag_arguments(tag_string)
287
296
  parse_tag_limits(tag_names)
288
297
  end
289
-
298
+
290
299
  def parse_tag_limits(tag_names)
291
300
  tag_names.inject({}) do |dict, tag|
292
301
  tag, limit = tag.split(':')
@@ -331,7 +340,7 @@ module Cucumber
331
340
  @options[:require] += other_options[:require]
332
341
  @options[:excludes] += other_options[:excludes]
333
342
  @options[:name_regexps] += other_options[:name_regexps]
334
- @options[:tag_names].merge! other_options[:tag_names]
343
+ @options[:tag_names] += other_options[:tag_names]
335
344
  @options[:env_vars] = other_options[:env_vars].merge(@options[:env_vars])
336
345
  if @options[:paths].empty?
337
346
  @options[:paths] = other_options[:paths]
@@ -384,7 +393,7 @@ module Cucumber
384
393
  :dry_run => false,
385
394
  :formats => [],
386
395
  :excludes => [],
387
- :tag_names => {},
396
+ :tag_names => [],
388
397
  :name_regexps => [],
389
398
  :env_vars => {},
390
399
  :diff_enabled => true