cucumber 0.4.2 → 0.4.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 (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