cucumber 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
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)