cucumber 0.3.93 → 0.3.94

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 (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