cucumber 0.3.93 → 0.3.94

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/History.txt +67 -38
  2. data/Manifest.txt +8 -0
  3. data/Rakefile +1 -1
  4. data/cucumber.yml +2 -2
  5. data/examples/python/features/fibonacci.feature +19 -0
  6. data/examples/python/features/step_definitions/fib_steps.rb +7 -0
  7. data/examples/python/features/support/env.rb +21 -0
  8. data/examples/python/lib/fib.py +7 -0
  9. data/examples/self_test/features/tags_sample.feature +17 -0
  10. data/examples/sinatra/README.textile +13 -0
  11. data/features/cucumber_cli.feature +85 -3
  12. data/features/custom_formatter.feature +2 -2
  13. data/features/html_formatter/a.html +1 -1
  14. data/features/junit_formatter.feature +19 -12
  15. data/features/step_definitions/cucumber_steps.rb +8 -15
  16. data/features/support/env.rb +4 -5
  17. data/gem_tasks/contributors.rake +4 -0
  18. data/lib/cucumber/ast/feature.rb +13 -0
  19. data/lib/cucumber/ast/feature_element.rb +8 -4
  20. data/lib/cucumber/ast/features.rb +6 -1
  21. data/lib/cucumber/ast/table.rb +13 -6
  22. data/lib/cucumber/ast/tags.rb +9 -1
  23. data/lib/cucumber/cli/configuration.rb +3 -15
  24. data/lib/cucumber/cli/main.rb +25 -8
  25. data/lib/cucumber/cli/options.rb +53 -31
  26. data/lib/cucumber/filter.rb +4 -3
  27. data/lib/cucumber/formatter/ansicolor.rb +42 -9
  28. data/lib/cucumber/formatter/console.rb +38 -6
  29. data/lib/cucumber/formatter/html.rb +2 -8
  30. data/lib/cucumber/formatter/junit.rb +65 -24
  31. data/lib/cucumber/formatter/ordered_xml_markup.rb +24 -0
  32. data/lib/cucumber/formatter/pretty.rb +9 -4
  33. data/lib/cucumber/formatter/progress.rb +7 -1
  34. data/lib/cucumber/rake/task.rb +3 -3
  35. data/lib/cucumber/step_mother.rb +3 -1
  36. data/lib/cucumber/version.rb +1 -1
  37. data/rails_generators/cucumber/templates/cucumber.rake +18 -6
  38. data/spec/cucumber/ast/scenario_outline_spec.rb +2 -2
  39. data/spec/cucumber/ast/table_spec.rb +5 -0
  40. data/spec/cucumber/cli/configuration_spec.rb +2 -1
  41. data/spec/cucumber/cli/options_spec.rb +11 -6
  42. metadata +10 -2
@@ -4,8 +4,8 @@ Feature: Custom Formatter
4
4
  When I run cucumber --format Cucumber::Formatter::TagCloud features
5
5
  Then it should fail with
6
6
  """
7
- | after_file | background_tagged_before_on_outline | four | lots | one | three | two |
8
- | 1 | 1 | 1 | 1 | 1 | 2 | 1 |
7
+ | after_file | background_tagged_before_on_outline | four | lots | one | sample_four | sample_one | sample_three | sample_two | three | two |
8
+ | 1 | 1 | 1 | 1 | 1 | 2 | 1 | 2 | 1 | 2 | 1 |
9
9
 
10
10
  """
11
11
 
@@ -178,6 +178,6 @@ features/search_sample.feature:10:in `Given failing without a table'</pre></li><
178
178
  features/search_sample.feature:13:in `Given <state> without a table'</pre></td></tr></table></div></div><div class="scenario outline"><h3><span class="keyword">Scenario Outline:</span> <span class="val">Hantu Pisang match</span></h3><ol><li class="step skipped" id="features_search_sample_feature_19"><div><span class="keyword">Given</span> <span class="step val">&lt;state&gt; without a table</span></div></li></ol><div class="examples"><h4><span class="keyword">Examples:</span> <span class="val"></span></h4><table><tr id="row_21"><th class="val skipped_param" id="row_21_0">state</th></tr><tr id="row_22"><td class="val passed" id="row_22_0">passing</td></tr></table></div></div><div class="scenario outline"><h3><span class="keyword">Scenario Outline:</span> <span class="val">no match in name but in examples</span></h3><ol><li class="step skipped" id="features_search_sample_feature_25"><div><span class="keyword">Given</span> <span class="step val">&lt;state&gt; without a table</span></div></li></ol><div class="examples"><h4><span class="keyword">Examples:</span> <span class="val">Hantu Pisang</span></h4><table><tr id="row_27"><th class="val skipped_param" id="row_27_0">state</th></tr><tr id="row_28"><td class="val passed" id="row_28_0">passing</td></tr></table></div><div class="examples"><h4><span class="keyword">Examples:</span> <span class="val">Ignore me</span></h4><table><tr id="row_31"><th class="val skipped_param" id="row_31_0">state</th></tr><tr id="row_32"><td class="val failed" id="row_32_0">failing</td></tr><tr><td class="failed" colspan="1"><pre>FAIL (RuntimeError)
179
179
  ./features/step_definitions/sample_steps.rb:2:in `flunker'
180
180
  ./features/step_definitions/sample_steps.rb:16:in `/^failing without a table$/'
181
- features/search_sample.feature:25:in `Given <state> without a table'</pre></td></tr></table></div></div></div><div class="feature"><span class="tag">@lots</span><h2><span class="val">Feature: Tons of cukes</span></h2><p class="narrative"></p><div class="scenario"><h3><span class="keyword">Scenario:</span> <span class="val">Lots and lots</span></h3><ol><li class="step passed" id="features_tons_of_cukes_feature_4"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step failed" id="features_tons_of_cukes_feature_5"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div><pre class="failed">We already have 2 cukes! (RuntimeError)
181
+ features/search_sample.feature:25:in `Given <state> without a table'</pre></td></tr></table></div></div></div><div class="feature"><span class="tag">@sample_one</span><h2><span class="val">Feature: Tag samples</span></h2><p class="narrative"></p><div class="scenario"><span class="tag">@sample_two</span> <span class="tag">@sample_four</span><h3><span class="keyword">Scenario:</span> <span class="val">Passing</span></h3><ol><li class="step undefined" id="features_tags_sample_feature_6"><div><span class="keyword">Given</span> <span class="step val">missing</span></div></li></ol></div><div class="scenario outline"><span class="tag">@sample_three</span><h3><span class="keyword">Scenario Outline:</span> <span class="val"></span></h3><ol><li class="step skipped" id="features_tags_sample_feature_10"><div><span class="keyword">Given</span> <span class="step val">&lt;state&gt;</span></div></li></ol><div class="examples"><h4><span class="keyword">Examples:</span> <span class="val"></span></h4><table><tr id="row_12"><th class="val skipped_param" id="row_12_0">state</th></tr><tr id="row_13"><td class="val undefined" id="row_13_0">missing</td></tr></table></div></div><div class="scenario"><span class="tag">@sample_three</span> <span class="tag">@sample_four</span><h3><span class="keyword">Scenario:</span> <span class="val">Skipped</span></h3><ol><li class="step undefined" id="features_tags_sample_feature_17"><div><span class="keyword">Given</span> <span class="step val">missing</span></div></li></ol></div></div><div class="feature"><span class="tag">@lots</span><h2><span class="val">Feature: Tons of cukes</span></h2><p class="narrative"></p><div class="scenario"><h3><span class="keyword">Scenario:</span> <span class="val">Lots and lots</span></h3><ol><li class="step passed" id="features_tons_of_cukes_feature_4"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step failed" id="features_tons_of_cukes_feature_5"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div><pre class="failed">We already have 2 cukes! (RuntimeError)
182
182
  ./features/step_definitions/sample_steps.rb:28:in `/^'(.+)' cukes$/'
183
183
  features/tons_of_cukes.feature:5:in `Given '2' cukes'</pre></li><li class="step skipped" id="features_tons_of_cukes_feature_6"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_7"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_8"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_9"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_10"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_11"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_12"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_13"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_14"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_15"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_16"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_17"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_18"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_19"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_20"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_21"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_22"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_23"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_24"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_25"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_26"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_27"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_28"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_29"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_30"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_31"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_32"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_33"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_34"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_35"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_36"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_37"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_38"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_39"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_40"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_41"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_42"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_43"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_44"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_45"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_46"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_47"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_48"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_49"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_50"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_51"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li><li class="step skipped" id="features_tons_of_cukes_feature_52"><div><span class="keyword">Given</span> <span class="step val">'<span class="param">2</span>' cukes</span></div></li></ol></div></div><div class="feature"><h2><span class="val">Feature: undefined multiline args</span></h2><p class="narrative"></p><div class="scenario"><h3><span class="keyword">Scenario:</span> <span class="val">pystring</span></h3><ol><li class="step undefined" id="features_undefined_multiline_args_feature_4"><div><span class="keyword">Given</span> <span class="step val">a pystring</span></div><pre class="val"> example</pre></li></ol></div><div class="scenario"><h3><span class="keyword">Scenario:</span> <span class="val">table</span></h3><ol><li class="step undefined" id="features_undefined_multiline_args_feature_10"><div><span class="keyword">Given</span> <span class="step val">a table</span></div><table><tr id="row_11"><td class="val" id="row_11_0">table</td></tr><tr id="row_12"><td class="val" id="row_12_0">example</td></tr></table></li></ol></div></div><div class="duration">0m30.005s</div></div></body></html>
@@ -6,42 +6,49 @@ Feature: JUnit output formatter
6
6
  Given I am in junit
7
7
  And the tmp directory is empty
8
8
 
9
- @mri186 @diffxml
9
+ @mri186
10
10
  Scenario: one feature, one passing scenario, one failing scenario
11
11
  When I run cucumber --format junit --out tmp/ features/one_passing_one_failing.feature
12
12
  Then it should fail with
13
13
  """
14
14
 
15
15
  """
16
- And "examples/junit/tmp/TEST-one_passing_one_failing.xml" should contain XML
16
+ And "examples/junit/tmp/TEST-one_passing_one_failing.xml" with junit duration "0.005" should contain
17
17
  """
18
18
  <?xml version="1.0" encoding="UTF-8"?>
19
- <testsuite errors="0" tests="2" name="One passing scenario, one failing scenario" failures="1">
20
- <testcase name="Given a passing scenario" classname="One passing scenario, one failing scenario.Passing">
19
+ <testsuite errors="0" failures="1" name="One passing scenario, one failing scenario" tests="2" time="0.005">
20
+ <testcase classname="One passing scenario, one failing scenario.Passing" name="Passing" time="0.005">
21
21
  </testcase>
22
- <testcase name="Given a failing scenario" classname="One passing scenario, one failing scenario.Failing">
23
- <failure message="Given a failing scenario">
22
+ <testcase classname="One passing scenario, one failing scenario.Failing" name="Failing" time="0.005">
23
+ <failure message="failed Failing" type="failed">
24
+ Scenario: Failing
25
+
26
+ Given a failing scenario
27
+
28
+ Message:
24
29
  (RuntimeError)
25
30
  ./features/step_definitions/steps.rb:6:in `/a failing scenario/'
26
31
  features/one_passing_one_failing.feature:7:in `Given a failing scenario' </failure>
27
32
  </testcase>
28
33
  </testsuite>
29
-
34
+
30
35
  """
31
36
 
32
- @mri186 @diffxml
37
+ @mri186
33
38
  Scenario: pending step
34
39
  When I run cucumber --format junit --out tmp/ features/pending.feature
35
40
  Then it should pass with
36
41
  """
37
42
 
38
43
  """
39
- And "examples/junit/tmp/TEST-pending.xml" should contain XML
44
+ And "examples/junit/tmp/TEST-pending.xml" with junit duration "0.009" should contain
40
45
  """
41
46
  <?xml version="1.0" encoding="UTF-8"?>
42
- <testsuite errors="0" tests="1" name="Pending step" failures="1">
43
- <testcase name="Given a pending step" classname="Pending step.Pending">
44
- <failure message="Given a pending step">
47
+ <testsuite errors="0" failures="1" name="Pending step" tests="1" time="0.009">
48
+ <testcase classname="Pending step.Pending" name="Pending" time="0.009">
49
+ <failure message="pending Pending" type="pending">
50
+ Scenario: Pending
51
+
45
52
  TODO (Cucumber::Pending)
46
53
  ./features/step_definitions/steps.rb:10:in `/a pending step/'
47
54
  features/pending.feature:4:in `Given a pending step' </failure>
@@ -39,7 +39,6 @@ Given /^I am not running (?:.*) in the background$/ do
39
39
  # no-op
40
40
  end
41
41
 
42
-
43
42
  When /^I run cucumber (.*)$/ do |cucumber_opts|
44
43
  run "#{Cucumber::RUBY_BINARY} #{Cucumber::BINARY} --no-color #{cucumber_opts}"
45
44
  end
@@ -75,23 +74,17 @@ Then /^the output should be$/ do |text|
75
74
  last_stdout.should == text
76
75
  end
77
76
 
78
- Then /^"(.*)" should contain XML$/ do |file, xml|
79
- t = Tempfile.new('cucumber-junit')
80
- t.write(xml)
81
- t.flush
82
- t.close
83
- cmd = "diffxml #{t.path} #{file}"
84
- diff = `#{cmd}`
85
- if diff =~ /<delta>/m
86
- raise diff + "\nXML WAS:\n" + IO.read(file)
87
- end
77
+ Then /^"([^\"]*)" should contain$/ do |file, text|
78
+ strip_duration(IO.read(file)).should == text
88
79
  end
89
80
 
90
- Then /^"(.*)" should contain$/ do |file, text|
91
- strip_duration(IO.read(file)).should == text
81
+ Then /^"([^\"]*)" with junit duration "([^\"]*)" should contain$/ do |actual_file, duration_replacement, text|
82
+ actual = IO.read(actual_file)
83
+ actual = replace_junit_duration(actual, duration_replacement)
84
+ actual.should == text
92
85
  end
93
86
 
94
- Then /^"(.*)" should match$/ do |file, text|
87
+ Then /^"([^\"]*)" should match$/ do |file, text|
95
88
  IO.read(file).should =~ Regexp.new(text)
96
89
  end
97
90
 
@@ -119,7 +112,7 @@ Then /^STDERR should be empty$/ do
119
112
  last_stderr.should == ""
120
113
  end
121
114
 
122
- Then /^"(.*)" should exist$/ do |file|
115
+ Then /^"([^\"]*)" should exist$/ do |file|
123
116
  File.exists?(file).should be_true
124
117
  FileUtils.rm(file)
125
118
  end
@@ -50,6 +50,10 @@ class CucumberWorld
50
50
  s.gsub(/\d+m\d+\.\d+s/m, replacement)
51
51
  end
52
52
 
53
+ def replace_junit_duration(s, replacement)
54
+ s.gsub(/\d+\.\d\d+/m, replacement)
55
+ end
56
+
53
57
  def create_file(file_name, file_content)
54
58
  file_content.gsub!("CUCUMBER_LIB", "'#{cucumber_lib_dir}'") # Some files, such as Rakefiles need to use the lib dir
55
59
  in_current_dir do
@@ -118,8 +122,3 @@ end
118
122
  After do
119
123
  terminate_background_jobs
120
124
  end
121
-
122
- Before('@diffxml') do
123
- `diffxml --version`
124
- raise "Please install diffxml from http://diffxml.sourceforge.net/" if $? != 0
125
- end
@@ -0,0 +1,4 @@
1
+ task :contributors do
2
+ contributors = `git log --pretty=short --no-merges | git shortlog -ne | egrep -ve '^ +' | egrep -ve '^$'`
3
+ puts contributors.split("\n").length
4
+ end
@@ -44,6 +44,19 @@ module Cucumber
44
44
  "#{@file}:#{line}"
45
45
  end
46
46
 
47
+ def tag_count(tag)
48
+ if @tags.respond_to?(:count)
49
+ @tags.count(tag) # 1.9
50
+ else
51
+ @tags.select{|t| t == tag}.length # 1.8
52
+ end
53
+ end
54
+
55
+ def feature_and_children_tag_count(tag)
56
+ children_tag_count = @feature_elements.inject(0){|count, feature_element| count += feature_element.tag_count(tag)}
57
+ children_tag_count + tag_count(tag)
58
+ end
59
+
47
60
  def short_name
48
61
  first_line = name.split(/\n/)[0]
49
62
  if first_line =~ /#{language.keywords('feature', true)}:(.*)/
@@ -24,7 +24,7 @@ module Cucumber
24
24
  if @name.empty?
25
25
  [@keyword.jlength]
26
26
  else
27
- @name.split("\n").enum_for(:each_with_index).map do |line, line_number|
27
+ @name.split("\n").enum_for(:each_with_index).map do |line, line_number|
28
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
29
29
  end
30
30
  end
@@ -32,7 +32,7 @@ module Cucumber
32
32
 
33
33
  def matches_scenario_names?(scenario_name_regexps)
34
34
  scenario_name_regexps.detect{|name| name =~ @name}
35
- end
35
+ end
36
36
 
37
37
  def backtrace_line(name = "#{@keyword} #{@name}", line = @line)
38
38
  @feature.backtrace_line(name, line) if @feature
@@ -49,9 +49,13 @@ module Cucumber
49
49
  def accept_hook?(hook)
50
50
  @tags.accept_hook?(hook) || @feature.accept_hook?(hook)
51
51
  end
52
-
52
+
53
+ def tag_count(tag)
54
+ @feature.tag_count(tag) == 0 ? @tags.count(tag) : @feature.tag_count(tag)
55
+ end
56
+
53
57
  def language
54
58
  @feature.language
55
59
  end
56
- end
60
+ end
57
61
  end
@@ -30,6 +30,11 @@ module Cucumber
30
30
  end
31
31
  @duration = Time.now - start
32
32
  end
33
+
34
+ def tag_count(tag)
35
+ @features.inject(0){|count, feature| count += feature.feature_and_children_tag_count(tag)}
36
+ end
37
+
33
38
  end
34
39
  end
35
- end
40
+ end
@@ -126,23 +126,30 @@ module Cucumber
126
126
  [:table, *cells_rows.map{|row| row.to_sexp}]
127
127
  end
128
128
 
129
- # Redefines the table headers. This makes it
130
- # possible to use prettier header names in the features. Example:
129
+ # Redefines the table headers. This makes it possible to use
130
+ # prettier and more flexible header names in the features. The
131
+ # keys of +mappings+ are Strings or regular expressions
132
+ # (anything that responds to #=== will work) that may match
133
+ # column headings in the table. The values of +mappings+ are
134
+ # desired names for the columns.
135
+ #
136
+ # Example:
131
137
  #
132
138
  # | Phone Number | Address |
133
139
  # | 123456 | xyz |
134
140
  # | 345678 | abc |
135
141
  #
136
- # A StepDefinition receiving this table can then map the columns:
142
+ # A StepDefinition receiving this table can then map the columns
143
+ # with both Regexp and String:
137
144
  #
138
- # mapped_table = table.map_columns('Phone Number' => :phone, 'Address' => :address)
139
- # hashes = mapped_table.hashes
145
+ # table.map_headers!(/phone( number)?/i => :phone, 'Address' => :address)
146
+ # table.hashes
140
147
  # # => [{:phone => '123456', :address => 'xyz'}, {:phone => '345678', :address => 'abc'}]
141
148
  #
142
149
  def map_headers!(mappings)
143
150
  header_cells = cell_matrix[0]
144
151
  mappings.each_pair do |pre, post|
145
- header_cell = header_cells.detect{|cell| cell.value == pre}
152
+ header_cell = header_cells.detect{|cell| pre === cell.value}
146
153
  header_cell.value = post
147
154
  if @conversion_procs.has_key?(pre)
148
155
  @conversion_procs[post] = @conversion_procs.delete(pre)
@@ -10,7 +10,7 @@ module Cucumber
10
10
  def self.strip_prefix(tag_name)
11
11
  tag_name =~ /^@(.*)/ ? $1 : tag_name
12
12
  end
13
-
13
+
14
14
  def initialize(line, tag_names)
15
15
  @line, @tag_names = line, tag_names
16
16
  end
@@ -26,6 +26,14 @@ module Cucumber
26
26
  hook.matches_tag_names?(@tag_names)
27
27
  end
28
28
 
29
+ def count(tag)
30
+ if @tag_names.respond_to?(:count)
31
+ @tag_names.count(tag) # 1.9
32
+ else
33
+ @tag_names.select{|t| t == tag}.length # 1.8
34
+ end
35
+ end
36
+
29
37
  def to_sexp
30
38
  @tag_names.map{|tag_name| [:tag, tag_name]}
31
39
  end
@@ -7,18 +7,6 @@ module Cucumber
7
7
  class ProfileNotFound < StandardError; end
8
8
 
9
9
  class Configuration
10
- BUILTIN_FORMATS = {
11
- 'html' => 'Cucumber::Formatter::Html',
12
- 'pretty' => 'Cucumber::Formatter::Pretty',
13
- 'profile' => 'Cucumber::Formatter::Profile',
14
- 'progress' => 'Cucumber::Formatter::Progress',
15
- 'rerun' => 'Cucumber::Formatter::Rerun',
16
- 'usage' => 'Cucumber::Formatter::Usage',
17
- 'junit' => 'Cucumber::Formatter::Junit',
18
- 'tag_cloud' => 'Cucumber::Formatter::TagCloud',
19
- 'steps' => 'Cucumber::Formatter::Steps'
20
- }
21
-
22
10
  attr_reader :options
23
11
 
24
12
  def initialize(out_stream = STDOUT, error_stream = STDERR)
@@ -96,8 +84,8 @@ module Cucumber
96
84
  end
97
85
 
98
86
  def formatter_class(format)
99
- if(builtin = BUILTIN_FORMATS[format])
100
- constantize(builtin)
87
+ if(builtin = Options::BUILTIN_FORMATS[format])
88
+ constantize(builtin[0])
101
89
  else
102
90
  constantize(format)
103
91
  end
@@ -180,7 +168,7 @@ module Cucumber
180
168
  tr("-", "_").
181
169
  downcase
182
170
  end
183
-
171
+
184
172
  end
185
173
 
186
174
  end
@@ -11,6 +11,8 @@ require 'cucumber/cli/drb_client'
11
11
  module Cucumber
12
12
  module Cli
13
13
  class Main
14
+ FAILURE = 1
15
+
14
16
  class << self
15
17
  def step_mother
16
18
  @step_mother
@@ -32,7 +34,7 @@ module Cucumber
32
34
  @out_stream = out_stream == STDOUT ? Formatter::ColorIO.new : out_stream
33
35
  @error_stream = error_stream
34
36
  end
35
-
37
+
36
38
  def execute!(step_mother)
37
39
  trap_interrupt
38
40
  if configuration.drb?
@@ -56,17 +58,32 @@ module Cucumber
56
58
  step_mother.visitor = visitor # Needed to support World#announce
57
59
  visitor.visit_features(features)
58
60
 
59
- failure = if configuration.wip?
60
- step_mother.scenarios(:passed).any?
61
- else
62
- step_mother.scenarios(:failed).any? ||
63
- (configuration.strict? && step_mother.steps(:undefined).any?)
64
- end
61
+ failure = if exceeded_tag_limts?(features)
62
+ FAILURE
63
+ elsif configuration.wip?
64
+ step_mother.scenarios(:passed).any?
65
+ else
66
+ step_mother.scenarios(:failed).any? ||
67
+ (configuration.strict? && step_mother.steps(:undefined).any?)
68
+ end
65
69
  rescue ProfilesNotDefinedError, YmlLoadError, ProfileNotFound => e
66
70
  @error_stream.puts e.message
67
71
  true
68
72
  end
69
73
 
74
+ def exceeded_tag_limts?(features)
75
+ exceeded = false
76
+ configuration.options[:include_tags].each do |tag, limit|
77
+ unless limit.nil?
78
+ tag_count = features.tag_count(tag)
79
+ if tag_count > limit.to_i
80
+ exceeded = true
81
+ end
82
+ end
83
+ end
84
+ exceeded
85
+ end
86
+
70
87
  def load_plain_text_features
71
88
  features = Ast::Features.new
72
89
 
@@ -85,7 +102,7 @@ module Cucumber
85
102
 
86
103
  def configuration
87
104
  return @configuration if @configuration
88
-
105
+
89
106
  @configuration = Configuration.new(@out_stream, @error_stream)
90
107
  @configuration.parse!(@args)
91
108
  @configuration
@@ -4,16 +4,28 @@ module Cucumber
4
4
 
5
5
  class Options
6
6
  BUILTIN_FORMATS = {
7
- 'html' => 'Cucumber::Formatter::Html',
8
- 'pretty' => 'Cucumber::Formatter::Pretty',
9
- 'profile' => 'Cucumber::Formatter::Profile',
10
- 'progress' => 'Cucumber::Formatter::Progress',
11
- 'rerun' => 'Cucumber::Formatter::Rerun',
12
- 'usage' => 'Cucumber::Formatter::Usage',
13
- 'junit' => 'Cucumber::Formatter::Junit',
14
- 'tag_cloud' => 'Cucumber::Formatter::TagCloud',
15
- 'steps' => 'Cucumber::Formatter::Steps'
7
+ 'html' => ['Cucumber::Formatter::Html', 'Generates a nice looking HTML report.'],
8
+ 'pretty' => ['Cucumber::Formatter::Pretty', 'Prints the feature as is - in colours.'],
9
+ 'profile' => ['Cucumber::Formatter::Profile', 'Prints the 10 slowest steps at the end.'],
10
+ 'progress' => ['Cucumber::Formatter::Progress', 'Prints one character per scenario.'],
11
+ 'rerun' => ['Cucumber::Formatter::Rerun', 'Prints failing files with line numbers.'],
12
+ 'usage' => ['Cucumber::Formatter::Usage', 'Prints where step definitions are used.'],
13
+ 'junit' => ['Cucumber::Formatter::Junit', 'Generates a report similar to Ant+JUnit.'],
14
+ 'tag_cloud' => ['Cucumber::Formatter::TagCloud', 'Prints a tag cloud of tag usage.'],
15
+ 'steps' => ['Cucumber::Formatter::Steps', 'Prints location of step definitions.']
16
16
  }
17
+ max = BUILTIN_FORMATS.keys.map{|s| s.length}.max
18
+ FORMAT_HELP = (BUILTIN_FORMATS.keys.sort.map do |key|
19
+ " #{key}#{' ' * (max - key.length)} : #{BUILTIN_FORMATS[key][1]}"
20
+ end) + ["FORMAT can also be the fully qualified class name of",
21
+ "your own custom formatter. If the class isn't loaded,",
22
+ "Cucumber will attempt to require a file with a relative",
23
+ "file name that is the underscore name of the class name.",
24
+ "Example: --format Foo::BarZap -> Cucumber will look for",
25
+ "foo/bar_zap.rb. You can place the file with this relative",
26
+ "path underneath your features/support directory or anywhere",
27
+ "on Ruby's LOAD_PATH, for example in a Ruby gem."
28
+ ]
17
29
  DRB_FLAG = '--drb'
18
30
  PROFILE_SHORT_FLAG = '-p'
19
31
  PROFILE_LONG_FLAG = '--profile'
@@ -31,6 +43,7 @@ module Cucumber
31
43
  @default_profile = options[:default_profile]
32
44
  @skip_profile_information = options[:skip_profile_information]
33
45
  @profiles = []
46
+ @overridden_paths = []
34
47
  @options = default_options
35
48
  end
36
49
 
@@ -54,7 +67,7 @@ module Cucumber
54
67
  previous_flag_was_profile = true
55
68
  next true
56
69
  end
57
- arg == DRB_FLAG
70
+ arg == DRB_FLAG || @overridden_paths.include?(arg)
58
71
  end
59
72
  )
60
73
  end
@@ -96,16 +109,8 @@ module Cucumber
96
109
  end
97
110
  end
98
111
  opts.on("-f FORMAT", "--format FORMAT",
99
- "How to format features (Default: pretty)",
100
- "Available formats: #{BUILTIN_FORMATS.keys.sort.join(", ")}",
101
- "FORMAT can also be the fully qualified class name of",
102
- "your own custom formatter. If the class isn't loaded,",
103
- "Cucumber will attempt to require a file with a relative",
104
- "file name that is the underscore name of the class name.",
105
- "Example: --format Foo::BarZap -> Cucumber will look for",
106
- "foo/bar_zap.rb. You can place the file with this relative",
107
- "path underneath your features/support directory or anywhere",
108
- "on Ruby's LOAD_PATH, for example in a Ruby gem.") do |v|
112
+ "How to format features (Default: pretty). Available formats:",
113
+ *FORMAT_HELP) do |v|
109
114
  @options[:formats] << [v, @out_stream]
110
115
  end
111
116
  opts.on("-o", "--out [FILE|DIR]",
@@ -118,12 +123,16 @@ module Cucumber
118
123
  end
119
124
  opts.on("-t TAGS", "--tags TAGS",
120
125
  "Only execute the features or scenarios with the specified tags.",
121
- "TAGS must be comma-separated without spaces. Prefix tags with ~ to",
122
- "exclude features or scenarios having that tag. Tags can be specified",
123
- "with or without the @ prefix.") do |v|
126
+ "TAGS must be comma-separated without spaces. They can be",
127
+ "specified with or without the @ prefix. Example: --tags dev\n",
128
+ "Negative tags: Prefix tags with ~ to exclude features or scenarios",
129
+ "having that tag.\n",
130
+ "Limit WIP: Positive tags can be given a threshold to limit the",
131
+ "number of occurrences. Example: --tags qa:3 will fail if there",
132
+ "are more than 3 occurrences of the @qa tag.") do |v|
124
133
  include_tags, exclude_tags = *parse_tags(v)
125
- @options[:include_tags] += include_tags
126
- @options[:exclude_tags] += exclude_tags
134
+ @options[:include_tags].merge!(include_tags)
135
+ @options[:exclude_tags].merge!(exclude_tags)
127
136
  end
128
137
  opts.on("-n NAME", "--name NAME",
129
138
  "Only execute the feature elements which match part of the given name.",
@@ -261,7 +270,16 @@ module Cucumber
261
270
  # Strip @
262
271
  includes = includes.map{|tag| Ast::Tags.strip_prefix(tag)}
263
272
  excludes = excludes.map{|tag| Ast::Tags.strip_prefix(tag)}
264
- [includes, excludes]
273
+ [parse_tag_limits(includes), parse_tag_limits(excludes)]
274
+ end
275
+
276
+ def parse_tag_limits(includes)
277
+ dict = {}
278
+ includes.each do |tag|
279
+ tag, limit = tag.split(':')
280
+ dict[tag] = limit.nil? ? limit : limit.to_i
281
+ end
282
+ dict
265
283
  end
266
284
 
267
285
  def disable_profile_loading?
@@ -298,10 +316,14 @@ module Cucumber
298
316
  def reverse_merge(other_options)
299
317
  @options = other_options.options.merge(@options)
300
318
  @options[:require] += other_options[:require]
301
- @options[:include_tags] += other_options[:include_tags]
302
- @options[:exclude_tags] += other_options[:exclude_tags]
319
+ @options[:include_tags].merge! other_options[:include_tags]
320
+ @options[:exclude_tags].merge! other_options[:exclude_tags]
303
321
  @options[:env_vars] = other_options[:env_vars].merge(@options[:env_vars])
304
- @options[:paths] = other_options[:paths] if @options[:paths].empty?
322
+ if @options[:paths].empty?
323
+ @options[:paths] = other_options[:paths]
324
+ else
325
+ @overridden_paths += (other_options[:paths] - @options[:paths])
326
+ end
305
327
  @options[:source] &= other_options[:source]
306
328
  @options[:snippets] &= other_options[:snippets]
307
329
 
@@ -348,8 +370,8 @@ module Cucumber
348
370
  :dry_run => false,
349
371
  :formats => [],
350
372
  :excludes => [],
351
- :include_tags => [],
352
- :exclude_tags => [],
373
+ :include_tags => {},
374
+ :exclude_tags => {},
353
375
  :name_regexps => [],
354
376
  :env_vars => {},
355
377
  :diff_enabled => true