cucumber 0.1.7 → 0.1.8

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 (80) hide show
  1. data/History.txt +81 -0
  2. data/Manifest.txt +30 -9
  3. data/README.txt +1 -2
  4. data/bin/cucumber +1 -1
  5. data/examples/calculator/README.txt +5 -0
  6. data/examples/calculator/Rakefile +1 -2
  7. data/examples/calculator/features/addition.feature +1 -0
  8. data/examples/chinese_simplified_calculator/Rakefile +6 -0
  9. data/examples/chinese_simplified_calculator/features/addition.feature +17 -0
  10. data/examples/chinese_simplified_calculator/features/steps/calculator_steps.rb +24 -0
  11. data/examples/chinese_simplified_calculator/lib/calculator.rb +10 -0
  12. data/examples/java/features/steps/hello_steps.rb +1 -1
  13. data/examples/swedish_calculator/Rakefile +5 -0
  14. data/examples/swedish_calculator/features/steps/kalkulator_steps.rb +22 -0
  15. data/examples/swedish_calculator/features/summering.feature +17 -0
  16. data/examples/swedish_calculator/lib/kalkulator.rb +11 -0
  17. data/examples/tickets/Rakefile +8 -1
  18. data/examples/tickets/cucumber.yml +2 -0
  19. data/examples/tickets/features/steps/tickets_steps.rb +15 -0
  20. data/examples/tickets/features/tickets.feature +12 -1
  21. data/gem_tasks/treetop.rake +35 -23
  22. data/lib/cucumber.rb +1 -0
  23. data/lib/cucumber/cli.rb +19 -5
  24. data/lib/cucumber/core_ext/proc.rb +8 -5
  25. data/lib/cucumber/executor.rb +7 -1
  26. data/lib/cucumber/formatters.rb +1 -1
  27. data/lib/cucumber/formatters/html_formatter.rb +6 -3
  28. data/lib/cucumber/formatters/pretty_formatter.rb +29 -7
  29. data/lib/cucumber/formatters/profile_formatter.rb +92 -0
  30. data/lib/cucumber/languages.yml +39 -12
  31. data/lib/cucumber/model.rb +1 -0
  32. data/lib/cucumber/model/table.rb +28 -0
  33. data/lib/cucumber/rails/world.rb +1 -3
  34. data/lib/cucumber/rake/task.rb +1 -1
  35. data/lib/cucumber/tree/feature.rb +8 -2
  36. data/lib/cucumber/tree/scenario.rb +23 -6
  37. data/lib/cucumber/tree/step.rb +5 -4
  38. data/lib/cucumber/treetop_parser/feature.treetop.erb +54 -12
  39. data/lib/cucumber/treetop_parser/feature_da.rb +415 -74
  40. data/lib/cucumber/treetop_parser/feature_de.rb +415 -74
  41. data/lib/cucumber/treetop_parser/feature_en.rb +415 -74
  42. data/lib/cucumber/treetop_parser/feature_es.rb +415 -74
  43. data/lib/cucumber/treetop_parser/feature_et.rb +415 -74
  44. data/lib/cucumber/treetop_parser/feature_fr.rb +415 -74
  45. data/lib/cucumber/treetop_parser/feature_nl.rb +1593 -0
  46. data/lib/cucumber/treetop_parser/feature_no.rb +415 -74
  47. data/lib/cucumber/treetop_parser/feature_pt.rb +415 -74
  48. data/lib/cucumber/treetop_parser/feature_ru.rb +415 -74
  49. data/lib/cucumber/treetop_parser/feature_se.rb +419 -78
  50. data/lib/cucumber/treetop_parser/feature_zh-CN.rb +1593 -0
  51. data/lib/cucumber/version.rb +1 -1
  52. data/{generators → rails_generators}/cucumber/cucumber_generator.rb +0 -0
  53. data/{generators → rails_generators}/cucumber/templates/common_webrat.rb +1 -10
  54. data/{generators → rails_generators}/cucumber/templates/cucumber +0 -0
  55. data/{generators → rails_generators}/cucumber/templates/cucumber.rake +0 -0
  56. data/{generators → rails_generators}/cucumber/templates/env.rb +0 -0
  57. data/{generators → rails_generators}/feature/feature_generator.rb +0 -0
  58. data/{generators → rails_generators}/feature/templates/feature.erb +1 -0
  59. data/{generators → rails_generators}/feature/templates/steps.erb +0 -0
  60. data/spec/cucumber/cli_spec.rb +11 -3
  61. data/spec/cucumber/core_ext/proc_spec.rb +6 -0
  62. data/spec/cucumber/formatters/html_formatter_spec.rb +16 -1
  63. data/spec/cucumber/formatters/pretty_formatter_spec.rb +54 -25
  64. data/spec/cucumber/formatters/profile_formatter_spec.rb +193 -0
  65. data/spec/cucumber/model/table_spec.rb +20 -0
  66. data/spec/cucumber/rails/stubs/mini_rails.rb +17 -0
  67. data/spec/cucumber/rails/stubs/test_help.rb +1 -0
  68. data/spec/cucumber/rails/world_spec.rb +11 -0
  69. data/spec/cucumber/tree/feature_spec.rb +12 -0
  70. data/spec/cucumber/tree/scenario_spec.rb +22 -0
  71. data/spec/cucumber/tree/step_spec.rb +12 -2
  72. data/spec/cucumber/treetop_parser/empty_scenario.feature +1 -1
  73. data/spec/cucumber/treetop_parser/feature_parser_spec.rb +18 -0
  74. data/spec/cucumber/treetop_parser/fit_scenario.feature +1 -0
  75. data/spec/cucumber/treetop_parser/multiline_steps.feature +13 -0
  76. data/spec/cucumber/treetop_parser/multiple_tables.feature +2 -0
  77. data/spec/cucumber/treetop_parser/test_dos.feature +1 -0
  78. data/spec/cucumber/treetop_parser/with_comments.feature +6 -2
  79. metadata +34 -12
  80. data/examples/calculator/cucumber.yml +0 -1
@@ -6,6 +6,7 @@ require 'treetop/ruby_extensions'
6
6
  require 'cucumber/version'
7
7
  require 'cucumber/step_methods'
8
8
  require 'cucumber/tree'
9
+ require 'cucumber/model'
9
10
  require 'cucumber/executor'
10
11
  require 'cucumber/step_mother'
11
12
  require 'cucumber/formatters'
@@ -23,7 +23,7 @@ module Cucumber
23
23
  end
24
24
 
25
25
  attr_reader :options
26
- FORMATS = %w{pretty progress html}
26
+ FORMATS = %w{pretty profile progress html}
27
27
 
28
28
  def initialize
29
29
  @paths = []
@@ -33,7 +33,14 @@ module Cucumber
33
33
  return parse_args_from_profile('default') if args.empty?
34
34
  args.extend(OptionParser::Arguable)
35
35
 
36
- @options ||= { :require => nil, :lang => 'en', :format => 'pretty', :dry_run => false, :source => true }
36
+ @options ||= {
37
+ :require => nil,
38
+ :lang => 'en',
39
+ :format => 'pretty',
40
+ :dry_run => false,
41
+ :source => true,
42
+ :out => STDOUT
43
+ }
37
44
  args.options do |opts|
38
45
  opts.banner = "Usage: cucumber [options] FILES|DIRS"
39
46
  opts.on("-r LIBRARY|DIR", "--require LIBRARY|DIR", "Require files before executing the features.",
@@ -68,6 +75,9 @@ module Cucumber
68
75
  opts.on("-n", "--no-source", "Don't show the file and line of the step definition with the steps.") do
69
76
  @options[:source] = false
70
77
  end
78
+ opts.on("-o", "--out=FILE", "Write output to a file instead of STDOUT.") do |v|
79
+ @options[:out] = File.open(v, 'w')
80
+ end
71
81
  opts.on_tail("--version", "Show version") do
72
82
  puts VERSION::STRING
73
83
  exit
@@ -145,11 +155,15 @@ module Cucumber
145
155
  def formatter(step_mother)
146
156
  case @options[:format]
147
157
  when 'pretty'
148
- Formatters::PrettyFormatter.new(STDOUT, step_mother, @options)
158
+ Formatters::PrettyFormatter.new(@options[:out], step_mother, @options)
149
159
  when 'progress'
150
- Formatters::ProgressFormatter.new(STDOUT)
160
+ Formatters::ProgressFormatter.new(@options[:out])
161
+ when 'profile'
162
+ Formatters::ProfileFormatter.new(@options[:out], step_mother)
151
163
  when 'html'
152
- Formatters::HtmlFormatter.new(STDOUT, step_mother)
164
+ Formatters::HtmlFormatter.new(@options[:out], step_mother)
165
+ else
166
+ raise "Unknown formatter: #{@options[:format]}"
153
167
  end
154
168
  end
155
169
 
@@ -24,16 +24,19 @@ module Cucumber
24
24
  end
25
25
 
26
26
  def to_backtrace_line
27
- "#{file}:in `#{name}'"
27
+ "#{file_colon_line}:in `#{name}'"
28
28
  end
29
29
 
30
30
  def to_comment_line
31
- "# #{file}"
31
+ "# #{file_colon_line}"
32
32
  end
33
33
 
34
- def file
35
- file = to_s.match(/[\d\w]+@(.*)>/)[1]
36
- file =~ /^\.\/(.*)/ ? $1 : file
34
+ def file_colon_line
35
+ path, line = *to_s.match(/[\d\w]+@(.*):(.*)>/)[1..2]
36
+ path = File.expand_path(path)
37
+ pwd = Dir.pwd
38
+ path = path[pwd.length+1..-1]
39
+ "#{path}:#{line}"
37
40
  end
38
41
 
39
42
  def meth
@@ -46,6 +46,7 @@ module Cucumber
46
46
  end
47
47
 
48
48
  def visit_feature(feature)
49
+ formatter.visit_feature(feature) if formatter.respond_to?(:visit_feature)
49
50
  feature.accept(self)
50
51
  end
51
52
 
@@ -62,7 +63,7 @@ module Cucumber
62
63
  end
63
64
 
64
65
  def visit_scenario(scenario)
65
- if @line.nil? || scenario.at_line?(@line)
66
+ if accept?(scenario)
66
67
  @error = nil
67
68
  @pending = nil
68
69
 
@@ -77,6 +78,10 @@ module Cucumber
77
78
  formatter.scenario_executed(scenario) if formatter.respond_to?(:scenario_executed)
78
79
  end
79
80
  end
81
+
82
+ def accept?(scenario)
83
+ @line.nil? || scenario.at_line?(@line)
84
+ end
80
85
 
81
86
  def visit_row_step(step)
82
87
  visit_step(step)
@@ -90,6 +95,7 @@ module Cucumber
90
95
  unless @pending || @error
91
96
  begin
92
97
  regexp, args, proc = step.regexp_args_proc(@step_mother)
98
+ formatter.step_executing(step, regexp, args) if formatter.respond_to?(:step_executing)
93
99
  step.execute_in(@world, regexp, args, proc)
94
100
  @after_step_procs.each{|p| p.call_in(@world, *[])}
95
101
  formatter.step_passed(step, regexp, args)
@@ -1 +1 @@
1
- %w{html pretty progress}.each{|n| require "cucumber/formatters/#{n}_formatter"}
1
+ %w{html pretty progress profile}.each{|n| require "cucumber/formatters/#{n}_formatter"}
@@ -5,6 +5,7 @@ module Cucumber
5
5
  @io = io
6
6
  @step_mother = step_mother
7
7
  @errors = []
8
+ @scenario_table_header = []
8
9
  end
9
10
 
10
11
  def visit_features(features)
@@ -52,6 +53,7 @@ HTML
52
53
  end
53
54
 
54
55
  def visit_regular_scenario(scenario)
56
+ @scenario_table_header = scenario.table_header
55
57
  @io.puts %{ <dl class="new">}
56
58
  @io.puts %{ <dt>#{Cucumber.language['scenario']}: #{scenario.name}</dt>}
57
59
  @io.puts %{ <dd>}
@@ -66,11 +68,12 @@ HTML
66
68
  @io.puts %{ <dl class="new">}
67
69
  @io.puts %{ <dt>#{Cucumber.language['scenario']}: #{scenario.name}</dt>}
68
70
  @io.puts %{ <dd>}
69
- @io.puts %{ <table>}
71
+ @io.puts %{ <table cellpadding="3">}
70
72
  @io.puts %{ <thead>}
71
73
  @io.puts %{ <tr>}
72
- @io.puts %{ <th>COL 1</th>}
73
- @io.puts %{ <th>COL 2</th>}
74
+ @scenario_table_header.each do |column_header|
75
+ @io.puts %{ <th>#{column_header}</th>}
76
+ end
74
77
  @io.puts %{ </tr>}
75
78
  @io.puts %{ </thead>}
76
79
  @io.puts %{ <tbody>}
@@ -17,12 +17,24 @@ module Cucumber
17
17
  @pending = []
18
18
  @skipped = []
19
19
  end
20
-
20
+
21
+ def visit_feature(feature)
22
+ @feature = feature
23
+ end
24
+
21
25
  def header_executing(header)
22
26
  @io.puts if @feature_newline
23
27
  @feature_newline = true
24
- @io.puts passed(header)
25
- @io.puts
28
+
29
+ header_lines = header.split("\n")
30
+ header_lines.each_with_index do |line, index|
31
+ @io.print line
32
+ if @options[:source] && index==0
33
+ @io.print padding_spaces(@feature)
34
+ @io.print comment("# #{@feature.file}")
35
+ end
36
+ @io.puts
37
+ end
26
38
  end
27
39
 
28
40
  def scenario_executing(scenario)
@@ -30,7 +42,12 @@ module Cucumber
30
42
  if scenario.row?
31
43
  @io.print " |"
32
44
  else
33
- @io.puts passed(" #{Cucumber.language['scenario']}: #{scenario.name}")
45
+ @io.print passed(" #{Cucumber.language['scenario']}: #{scenario.name}")
46
+ if @options[:source]
47
+ @io.print padding_spaces(scenario)
48
+ @io.print comment("# #{scenario.file}:#{scenario.line}")
49
+ end
50
+ @io.puts
34
51
  end
35
52
  end
36
53
 
@@ -99,7 +116,12 @@ module Cucumber
99
116
  args.each{|arg| @io.print pending(arg) ; @io.print "|"}
100
117
  else
101
118
  @pending << step
102
- @io.puts pending(" #{step.keyword} #{step.name}")
119
+ @io.print pending(" #{step.keyword} #{step.name}")
120
+ if @options[:source]
121
+ @io.print padding_spaces(step)
122
+ @io.print comment("# #{step.file}:#{step.line}")
123
+ end
124
+ @io.puts
103
125
  end
104
126
  end
105
127
 
@@ -147,8 +169,8 @@ module Cucumber
147
169
  comment(proc.to_comment_line)
148
170
  end
149
171
 
150
- def padding_spaces(step)
151
- " " * step.padding_length
172
+ def padding_spaces(tree_item)
173
+ " " * tree_item.padding_length
152
174
  end
153
175
  end
154
176
  end
@@ -0,0 +1,92 @@
1
+ module Cucumber
2
+ module Formatters
3
+ class ProfileFormatter < ProgressFormatter
4
+ NUMBER_OF_STEP_DEFINITONS_TO_SHOW = 10
5
+ NUMBER_OF_STEP_INVOCATIONS_TO_SHOW = 5
6
+
7
+ def initialize(io, step_mother)
8
+ super(io)
9
+ @step_mother = step_mother
10
+ @step_times = Hash.new { |k,v| k[v] = [] }
11
+ @step_keywords = {}
12
+ end
13
+
14
+ def visit_features(features)
15
+ @io.puts "Profiling enabled.\n"
16
+ end
17
+
18
+ def step_executing(step, regexp, args)
19
+ @step_time = Time.now
20
+ end
21
+
22
+ def step_passed(step, regexp, args)
23
+ execution_time = Time.now - @step_time
24
+ super
25
+ @step_keywords[regexp] ||= step.actual_keyword unless step.row?
26
+ invocation_comment = ''
27
+ definition_comment = ''
28
+
29
+ if step.row?
30
+ description = ''
31
+ args.each do |arg|
32
+ description += %{|#{arg}|}
33
+ end
34
+ else
35
+ description = "#{step.keyword} #{step.format(regexp){|param| underline(param)}}"
36
+ definition_comment = source(step)
37
+ end
38
+ invocation_comment = "# #{step.file}:#{step.line}"
39
+ @step_times["#{@step_keywords[regexp]} #{regexp.inspect}"] << [description, invocation_comment, definition_comment, execution_time]
40
+ end
41
+
42
+ def dump
43
+ super
44
+ @io.puts "\n\nTop #{NUMBER_OF_STEP_DEFINITONS_TO_SHOW} average slowest steps with #{NUMBER_OF_STEP_INVOCATIONS_TO_SHOW} slowest matches:\n"
45
+
46
+ mean_times = map_to_mean_times(@step_times)
47
+ mean_times = mean_times.sort_by { |step_profiles, keyword_regexp, mean_execution_time| mean_execution_time }.reverse
48
+
49
+ mean_times[0...NUMBER_OF_STEP_DEFINITONS_TO_SHOW].each do |step_profiles, keyword_regexp, mean_execution_time|
50
+ print_step_definition(step_profiles, keyword_regexp, mean_execution_time)
51
+ step_profiles = step_profiles.sort_by { |description, invocation_comment, definition_comment, execution_time| execution_time }.reverse
52
+ print_step_invocations(step_profiles, keyword_regexp)
53
+ end
54
+ end
55
+
56
+ private
57
+ def map_to_mean_times(step_times)
58
+ mean_times = []
59
+ step_times.each do |regexp, step_profiles|
60
+ mean_execution_time = (step_profiles.inject(0) { |sum, step_details| step_details[3] + sum } / step_profiles.length)
61
+ mean_times << [step_profiles, regexp, mean_execution_time]
62
+ end
63
+ mean_times
64
+ end
65
+
66
+ def print_step_definition(step_profiles, keyword_regexp, mean_execution_time)
67
+ unless step_profiles.empty?
68
+ _, _, definition_comment, _ = step_profiles.first
69
+ @io.print red(sprintf("%.7f", mean_execution_time))
70
+ @io.print " #{keyword_regexp}"
71
+ @io.print " #{comment(definition_comment)}"
72
+ @io.puts
73
+ end
74
+ end
75
+
76
+ def print_step_invocations(step_profiles, keyword_regexp)
77
+ step_profiles[0...NUMBER_OF_STEP_INVOCATIONS_TO_SHOW].each do |description, invocation_comment, definition_comment, execution_time|
78
+ @io.print " #{yellow(sprintf("%.7f", execution_time))}"
79
+ @io.print " #{description}"
80
+ @io.print " #{comment(invocation_comment)}"
81
+ @io.puts
82
+ end
83
+ end
84
+
85
+ def source(step)
86
+ _, _, proc = step.regexp_args_proc(@step_mother)
87
+ proc.to_comment_line
88
+ end
89
+
90
+ end
91
+ end
92
+ end
@@ -1,6 +1,21 @@
1
1
  # http://en.wikipedia.org/wiki/IETF_language_tag
2
2
  # http://www.iana.org/assignments/language-subtag-registry
3
3
  # http://ftp.ics.uci.edu/pub/ietf/http/related/iso639.txt (Use this I think)
4
+ "en":
5
+ feature: Feature
6
+ scenario: Scenario
7
+ more_examples: More Examples
8
+ given_scenario: GivenScenario
9
+ given: Given
10
+ when: When
11
+ then: Then
12
+ and: And
13
+ but: But
14
+ # Please help us keeping the languages below uptodate. The parsers for a language
15
+ # that is missing a keyword will expect the English word until the missing word(s)
16
+ # are added.
17
+ #
18
+ # Please keep the grammars in alphabetical order from here and down.
4
19
  "da":
5
20
  feature: Egenskab
6
21
  scenario: Scenarie
@@ -19,18 +34,10 @@
19
34
  then: Dann
20
35
  and: Und
21
36
  but: Aber
22
- "en":
23
- feature: Feature
24
- scenario: Scenario
25
- given_scenario: GivenScenario
26
- given: Given
27
- when: When
28
- then: Then
29
- and: And
30
- but: But
31
37
  "es":
32
38
  feature: Característica
33
39
  scenario: Escenario
40
+ more_examples: Más ejemplos
34
41
  given_scenario: DadoElEscenario
35
42
  given: Dado
36
43
  when: Cuando
@@ -40,15 +47,27 @@
40
47
  "fr":
41
48
  feature: Fonction
42
49
  scenario: Scenario
50
+ more_examples: Plus d'exemples
43
51
  given_scenario: SoitScenario
44
52
  given: Soit
45
53
  when: Lorsque
46
54
  then: Alors
47
55
  and: Et
48
56
  but: Mais
57
+ "nl":
58
+ feature: Functionaliteit
59
+ scenario: Scenario
60
+ more_examples: Meer voorbeelden
61
+ given_scenario: GegevenScenario
62
+ given: Gegeven
63
+ when: Als
64
+ then: Dan
65
+ and: En
66
+ but: Maar
49
67
  "no":
50
68
  feature: Egenskap
51
69
  scenario: Scenario
70
+ more_examples: Flere eksempler
52
71
  given_scenario: GittScenario
53
72
  given: Gitt
54
73
  when: Når
@@ -76,9 +95,9 @@
76
95
  "se":
77
96
  feature: Egenskap
78
97
  scenario: Scenario
79
- given_scenario: GittScenario
80
- given: Gitt
81
- when: Når
98
+ given_scenario: GivetScenario
99
+ given: Givet
100
+ when: När
82
101
  then: Så
83
102
  and: Och
84
103
  but: Men
@@ -91,3 +110,11 @@
91
110
  then: Siis
92
111
  and: Ja
93
112
  but: Kuid
113
+ "zh-CN":
114
+ scenario: 场景
115
+ given_scenario: 引用场景
116
+ given: 假如
117
+ when: 当
118
+ then: 那么
119
+ and: 而且
120
+ but: 但是
@@ -0,0 +1 @@
1
+ %w{table}.each{|f| require "cucumber/model/#{f}"}
@@ -0,0 +1,28 @@
1
+ module Cucumber
2
+ module Model
3
+ class Table
4
+ attr_accessor :raw
5
+
6
+ # Creates a new table. The +raw+ argument should be an array
7
+ # of arrays
8
+ def initialize(raw)
9
+ @raw = raw
10
+ end
11
+
12
+ # Turn the table into an array of Hash where each Hash
13
+ # has keys corresponding to the table header (first line)
14
+ # and the values are the individual row cells.
15
+ def hashes
16
+ header = @raw[0]
17
+ @raw[1..-1].map do |row|
18
+ h = {}
19
+ row.each_with_index do |v,n|
20
+ key = header[n]
21
+ h[key] = v
22
+ end
23
+ h
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -22,7 +22,7 @@ Dispatcher.class_eval do
22
22
  end
23
23
 
24
24
  # So that Test::Unit doesn't launch at the end - makes it think it has already been run.
25
- Test::Unit.run = true
25
+ Test::Unit.run = true if Test::Unit.respond_to?(:run=)
26
26
 
27
27
  $main = self
28
28
 
@@ -71,5 +71,3 @@ end
71
71
  World do
72
72
  Cucumber::Rails::World.new
73
73
  end
74
-
75
- ActionMailer::Base.delivery_method = :test if defined?(ActionMailer::Base)