aslakhellesoy-cucumber 0.0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/License.txt +1 -1
  2. data/Manifest.txt +38 -24
  3. data/README.textile +31 -0
  4. data/TODO.txt +30 -0
  5. data/config/hoe.rb +6 -7
  6. data/examples/java/README.textile +52 -0
  7. data/examples/java/Rakefile +12 -0
  8. data/examples/java/features/hello.feature +11 -0
  9. data/examples/java/features/steps/hello_steps.rb +25 -0
  10. data/examples/java/features/steps/tree_steps.rb +14 -0
  11. data/examples/java/features/tree.feature +9 -0
  12. data/examples/java/src/cucumber/demo/Hello.java +15 -0
  13. data/examples/pure_ruby/Rakefile +6 -0
  14. data/examples/pure_ruby/features/addition.rb +23 -0
  15. data/examples/pure_ruby/{steps → features/steps}/addition_steps.rb +0 -0
  16. data/examples/simple_norwegian/Rakefile +6 -0
  17. data/examples/simple_norwegian/{steg → features/steps}/matte_steg.rb.rb +0 -0
  18. data/examples/simple_norwegian/{summering.story → features/summering.feature} +4 -1
  19. data/examples/web/Rakefile +6 -0
  20. data/examples/web/features/search.feature +9 -0
  21. data/examples/web/features/steps/stories_steps.rb +51 -0
  22. data/gem_tasks/treetop.rake +8 -14
  23. data/lib/cucumber.rb +10 -3
  24. data/lib/cucumber/cli.rb +29 -19
  25. data/lib/cucumber/core_ext/proc.rb +13 -5
  26. data/lib/cucumber/executor.rb +8 -10
  27. data/lib/cucumber/formatters/ansicolor.rb +2 -2
  28. data/lib/cucumber/formatters/html_formatter.rb +6 -6
  29. data/lib/cucumber/formatters/pretty_formatter.rb +68 -15
  30. data/lib/cucumber/{parser/languages.yml → languages.yml} +0 -4
  31. data/lib/cucumber/rake/task.rb +24 -26
  32. data/lib/cucumber/step_mother.rb +3 -3
  33. data/lib/cucumber/tree.rb +8 -117
  34. data/lib/cucumber/tree/feature.rb +45 -0
  35. data/lib/cucumber/tree/features.rb +21 -0
  36. data/lib/cucumber/tree/scenario.rb +79 -0
  37. data/lib/cucumber/tree/step.rb +133 -0
  38. data/lib/cucumber/tree/table.rb +26 -0
  39. data/lib/cucumber/{parser → tree}/top_down_visitor.rb +5 -8
  40. data/lib/cucumber/treetop_parser/feature.treetop.erb +98 -0
  41. data/lib/cucumber/treetop_parser/feature_en.rb +923 -0
  42. data/lib/cucumber/treetop_parser/feature_fr.rb +923 -0
  43. data/lib/cucumber/treetop_parser/feature_no.rb +923 -0
  44. data/lib/cucumber/treetop_parser/feature_parser.rb +32 -0
  45. data/lib/cucumber/treetop_parser/feature_pt.rb +923 -0
  46. data/lib/cucumber/version.rb +2 -2
  47. data/script/console +1 -1
  48. data/spec/cucumber/executor_spec.rb +9 -9
  49. data/spec/cucumber/formatters/{stories.html → features.html} +9 -9
  50. data/spec/cucumber/formatters/html_formatter_spec.rb +11 -11
  51. data/spec/cucumber/{sell_cucumbers.story → sell_cucumbers.feature} +1 -1
  52. data/spec/spec_helper.rb +8 -1
  53. metadata +61 -30
  54. data/README.txt +0 -78
  55. data/examples/Rakefile +0 -7
  56. data/examples/pure_ruby/addition.rb +0 -16
  57. data/examples/simple/addition.story +0 -12
  58. data/examples/simple/division.story +0 -17
  59. data/examples/simple/steps/addition_steps.rb +0 -43
  60. data/examples/web/run_stories.story +0 -10
  61. data/examples/web/steps/stories_steps.rb +0 -39
  62. data/lib/cucumber/parser/nodes.rb +0 -88
  63. data/lib/cucumber/parser/story_parser.rb +0 -13
  64. data/lib/cucumber/parser/story_parser.treetop.erb +0 -41
  65. data/lib/cucumber/parser/story_parser_en.rb +0 -554
  66. data/lib/cucumber/parser/story_parser_fr.rb +0 -554
  67. data/lib/cucumber/parser/story_parser_no.rb +0 -554
  68. data/lib/cucumber/parser/story_parser_pt.rb +0 -554
  69. data/lib/cucumber/ruby_tree.rb +0 -14
  70. data/lib/cucumber/ruby_tree/nodes.rb +0 -68
@@ -1,26 +1,20 @@
1
- class StoryCompiler
1
+ class FeatureCompiler
2
2
  def compile
3
3
  require 'yaml'
4
4
  require 'erb'
5
5
  tt = PLATFORM =~ /win32/ ? 'tt.bat' : 'tt'
6
- template = ERB.new(IO.read(File.dirname(__FILE__) + '/../lib/cucumber/parser/story_parser.treetop.erb'))
7
- langs = YAML.load_file(File.dirname(__FILE__) + '/../lib/cucumber/parser/languages.yml')
8
- langs.each do |lang, words|
9
- grammar_file = File.dirname(__FILE__) + "/../lib/cucumber/parser/story_parser_#{lang}.treetop"
10
6
 
11
- STDOUT.write("Generating and compiling grammar for #{lang}...")
7
+ template = ERB.new(IO.read(File.dirname(__FILE__) + '/../lib/cucumber/treetop_parser/feature.treetop.erb'))
8
+ langs = YAML.load_file(File.dirname(__FILE__) + '/../lib/cucumber/languages.yml')
9
+
10
+ langs.each do |lang, words|
11
+ grammar_file = File.dirname(__FILE__) + "/../lib/cucumber/treetop_parser/feature_#{lang}.treetop"
12
12
  grammar = template.result(binding)
13
-
14
- # http://groups.google.com/group/treetop-dev/browse_thread/thread/c8a8ced33da07f73
15
- # http://github.com/vic/treetop/commit/5cec030a1c363e06783f98e4b45ce6ab121996ab
16
- grammar = "module Cucumber\nmodule Parser\n#{grammar}\nend\nend"
17
-
18
13
  File.open(grammar_file, "wb") do |io|
19
14
  io.write(grammar)
20
15
  end
21
- system "#{tt} #{grammar_file}"
16
+ sh "#{tt} #{grammar_file}"
22
17
  FileUtils.rm(grammar_file)
23
- STDOUT.puts("Done!")
24
18
  end
25
19
  end
26
20
  end
@@ -28,6 +22,6 @@ end
28
22
  namespace :treetop do
29
23
  desc 'Compile the grammar'
30
24
  task :compile do
31
- StoryCompiler.new.compile
25
+ FeatureCompiler.new.compile
32
26
  end
33
27
  end
@@ -5,13 +5,20 @@ require 'treetop/runtime'
5
5
  require 'treetop/ruby_extensions'
6
6
  require 'cucumber/version'
7
7
  require 'cucumber/step_methods'
8
- require 'cucumber/ruby_tree'
8
+ require 'cucumber/tree'
9
9
  require 'cucumber/executor'
10
10
  require 'cucumber/step_mother'
11
11
  require 'cucumber/formatters'
12
- require 'cucumber/parser/story_parser'
12
+ require 'cucumber/treetop_parser/feature_parser'
13
13
  require 'cucumber/cli'
14
14
 
15
15
  module Cucumber
16
-
16
+ class << self
17
+ attr_reader :language
18
+
19
+ def load_language(lang)
20
+ require 'yaml'
21
+ @language = YAML.load_file(File.dirname(__FILE__) + '/cucumber/languages.yml')[lang]
22
+ end
23
+ end
17
24
  end
@@ -4,11 +4,11 @@ require 'cucumber'
4
4
  module Cucumber
5
5
  class CLI
6
6
  class << self
7
- attr_writer :step_mother, :stories
7
+ attr_writer :step_mother, :features
8
8
 
9
9
  def execute
10
10
  @execute_called = true
11
- parse(ARGV).execute!(@step_mother, @stories)
11
+ parse(ARGV).execute!(@step_mother, @features)
12
12
  end
13
13
 
14
14
  def execute_called?
@@ -31,46 +31,58 @@ module Cucumber
31
31
  @options = { :require => nil, :lang => 'en', :format => 'pretty', :dry_run => false }
32
32
  @args.options do |opts|
33
33
  opts.banner = "Usage: cucumber [options] FILES|DIRS"
34
- opts.on("-r LIBRARY|DIR", "--require LIBRARY|DIR", "Require files before executing the stories.",
34
+ opts.on("-r LIBRARY|DIR", "--require LIBRARY|DIR", "Require files before executing the features.",
35
35
  "If this option is not specified, all *.rb files that",
36
- "are siblings or below the stories will be autorequired") do |v|
36
+ "are siblings or below the features will be autorequired") do |v|
37
37
  @options[:require] ||= []
38
38
  @options[:require] << v
39
39
  end
40
40
  opts.on("-l LINE", "--line LANG", "Only execute the scenario at the given line") do |v|
41
41
  @options[:line] = v
42
42
  end
43
- opts.on("-a LANG", "--language LANG", "Specify language for stories (Default: #{@options[:lang]})") do |v|
43
+ opts.on("-a LANG", "--language LANG", "Specify language for features (Default: #{@options[:lang]})") do |v|
44
44
  @options[:lang] = v
45
45
  end
46
- opts.on("-f FORMAT", "--format FORMAT", "How to format stories (Default: #{@options[:format]})") do |v|
46
+ opts.on("-f FORMAT", "--format FORMAT", "How to format features (Default: #{@options[:format]})") do |v|
47
47
  @options[:format] = v
48
48
  end
49
49
  opts.on("-d", "--dry-run", "Invokes formatters without executing the steps.") do
50
50
  @options[:dry_run] = true
51
51
  end
52
+ opts.on_tail("--version", "Show version") do
53
+ puts VERSION::STRING
54
+ exit
55
+ end
56
+ opts.on_tail("--help", "You're looking at it") do
57
+ puts opts.help
58
+ exit
59
+ end
52
60
  end.parse!
61
+
53
62
  # Whatever is left after option parsing
54
63
  @files = @args.map do |path|
55
64
  path = path.gsub(/\\/, '/') # In case we're on windows. Globs don't work with backslashes.
56
- File.directory?(path) ? Dir["#{path}/**/*.story"] : path
65
+ File.directory?(path) ? Dir["#{path}/**/*.feature"] : path
57
66
  end.flatten
58
67
  end
59
68
 
60
- def execute!(step_mother, stories)
69
+ def execute!(step_mother, features)
70
+ Cucumber.load_language(@options[:lang])
61
71
  $executor = Executor.new(formatter, step_mother)
62
72
  require_files
63
- load_plain_text_stories(stories)
73
+ load_plain_text_features(features)
64
74
  $executor.line = @options[:line].to_i if @options[:line]
65
- $executor.visit_stories(stories)
75
+ $executor.visit_features(features)
66
76
  exit 1 if $executor.failed
67
77
  end
68
78
 
69
79
  private
70
80
 
71
- # Requires files - typically step files and ruby story files.
81
+ # Requires files - typically step files and ruby feature files.
72
82
  def require_files
73
- require "cucumber/parser/story_parser_#{@options[:lang]}"
83
+ require "cucumber/treetop_parser/feature_#{@options[:lang]}"
84
+ require "cucumber/treetop_parser/feature_parser"
85
+
74
86
  requires = @options[:require] || @args.map{|f| File.directory?(f) ? f : File.dirname(f)}.uniq
75
87
  libs = requires.map do |path|
76
88
  path = path.gsub(/\\/, '/') # In case we're on windows. Globs don't work with backslashes.
@@ -86,10 +98,10 @@ module Cucumber
86
98
  end
87
99
  end
88
100
 
89
- def load_plain_text_stories(stories)
90
- parser = Parser::StoryParser.new
101
+ def load_plain_text_features(features)
102
+ parser = TreetopParser::FeatureParser.new
91
103
  @files.each do |f|
92
- stories << Parser::StoryNode.parse(f, parser)
104
+ features << parser.parse_feature(f)
93
105
  end
94
106
  end
95
107
 
@@ -105,13 +117,11 @@ module Cucumber
105
117
  end
106
118
  end
107
119
 
108
- # Hook the toplevel StepMother to the CLI
109
- # TODO: Hook in a RubyStories object on toplevel for pure ruby stories
110
120
  extend Cucumber::StepMethods
111
121
  Cucumber::CLI.step_mother = step_mother
112
122
 
113
- extend(Cucumber::RubyTree)
114
- Cucumber::CLI.stories = stories
123
+ extend(Cucumber::Tree)
124
+ Cucumber::CLI.features = features
115
125
 
116
126
  at_exit do
117
127
  Cucumber::CLI.execute unless Cucumber::CLI.execute_called?
@@ -1,22 +1,30 @@
1
1
  module Cucumber
2
+ class ArgCountError < StandardError
3
+ end
4
+
2
5
  module CoreExt
3
6
  # Proc extension that allows a proc to be called in the context of any object.
4
7
  # Also makes it possible to tack a name onto a Proc.
5
8
  module CallIn
6
9
  attr_accessor :name
7
10
 
8
- def call_in(obj, *args, &proc)
11
+ def call_in(obj, *args)
9
12
  obj.extend(mod)
10
- # raise ArgCountError.new("The #{name} block takes #{arity2} arguments, but there are #{args.length} matched variables") if args.length != arity2
11
- obj.__send__(meth, *args, &proc)
13
+ a = arity == -1 ? 0 : arity
14
+ if self != Tree::Step::PENDING && args.length != a
15
+ # We have to manually raise when the block has arity -1 (no pipes)
16
+ raise ArgCountError.new("wrong number of arguments (#{args.length} for 0)")
17
+ else
18
+ obj.__send__(meth, *args)
19
+ end
12
20
  end
13
21
 
14
22
  def arity2
15
23
  arity == -1 ? 0 : arity
16
24
  end
17
-
25
+
18
26
  def backtrace_line
19
- inspect.match(/[\d\w]+@(.*)>/)[1] + ":in `#{name}'"
27
+ to_s.match(/[\d\w]+@(.*)>/)[1] + ":in `#{name}'"
20
28
  end
21
29
 
22
30
  def meth
@@ -4,9 +4,6 @@ module Cucumber
4
4
  class Pending < StandardError
5
5
  end
6
6
 
7
- class ArgCountError < StandardError
8
- end
9
-
10
7
  class Executor
11
8
  attr_reader :failed
12
9
 
@@ -36,16 +33,16 @@ module Cucumber
36
33
  @after_procs << proc
37
34
  end
38
35
 
39
- def visit_stories(stories)
40
- raise "Line number can only be specified when there is 1 story. There were #{stories.length}." if @line && stories.length != 1
41
- @step_mother.visit_stories(stories)
42
- @formatter.visit_stories(stories) if @formatter.respond_to?(:visit_stories)
43
- stories.accept(self)
36
+ def visit_features(features)
37
+ raise "Line number can only be specified when there is 1 feature. There were #{features.length}." if @line && features.length != 1
38
+ @step_mother.visit_features(features)
39
+ @formatter.visit_features(features) if @formatter.respond_to?(:visit_features)
40
+ features.accept(self)
44
41
  @formatter.dump
45
42
  end
46
43
 
47
- def visit_story(story)
48
- story.accept(self)
44
+ def visit_feature(feature)
45
+ feature.accept(self)
49
46
  end
50
47
 
51
48
  def visit_header(header)
@@ -64,6 +61,7 @@ module Cucumber
64
61
  @before_procs.each{|p| p.call_in(@world, *[])}
65
62
  scenario.accept(self)
66
63
  @after_procs.each{|p| p.call_in(@world, *[])}
64
+ @formatter.scenario_executed(scenario) if @formatter.respond_to?(:scenario_executed)
67
65
  end
68
66
  end
69
67
 
@@ -5,7 +5,7 @@ rescue LoadError
5
5
  STDERR.puts "You must gem install win32console to get coloured output on this ruby platform (#{PLATFORM})"
6
6
  ::Term::ANSIColor.coloring = false
7
7
  end
8
- ::Term::ANSIColor.coloring = false if PLATFORM =~ /java/ || !STDOUT.tty?
8
+ ::Term::ANSIColor.coloring = false if !STDOUT.tty?
9
9
 
10
10
  module Cucumber
11
11
  module Formatters
@@ -80,7 +80,7 @@ module Cucumber
80
80
  colors = color_string.split(",").reverse
81
81
  define_method(m) do |*s|
82
82
  clear + colors.inject(s[0]) do |memo, color|
83
- s[0].nil? ? __send__(color) + memo.to_s : __send__(color, memo)
83
+ s[0].nil? ? __send__(color) + memo.to_s : __send__(color, memo.to_s)
84
84
  end
85
85
  end
86
86
  end
@@ -6,7 +6,7 @@ module Cucumber
6
6
  @errors = []
7
7
  end
8
8
 
9
- def visit_stories(stories)
9
+ def visit_features(features)
10
10
  # IMPORTANT NOTICE ABOUT JQUERY BELOW. THE ORIGINAL BACKSLASHES (\) HAVE
11
11
  # BEEN REPLACED BY DOUBLE BACKSLASHES (\\) IN THIS FILE TO MAKE SURE THEY
12
12
  # ARE PRINTED PROPERLY WITH ONLY ONE BACKSLASH (\).
@@ -205,19 +205,19 @@ div.auto_complete ul li.selected {
205
205
  <body>
206
206
  <div id="container">
207
207
  HTML
208
- stories.accept(self)
208
+ features.accept(self)
209
209
  @io.puts " </div>"
210
210
  end
211
211
 
212
- def visit_story(story)
213
- @io.puts " <dl class=\"story new\">"
214
- story.accept(self)
212
+ def visit_feature(feature)
213
+ @io.puts " <dl class=\"feature new\">"
214
+ feature.accept(self)
215
215
  @io.puts " </dd>"
216
216
  @io.puts " </dl>"
217
217
  end
218
218
 
219
219
  def visit_header(header)
220
- @io.puts " <dt>Story: #{header}</dt>"
220
+ @io.puts " <dt>Feature: #{header}</dt>"
221
221
  end
222
222
 
223
223
  def visit_narrative(narrative)
@@ -16,23 +16,43 @@ module Cucumber
16
16
  end
17
17
 
18
18
  def header_executing(header)
19
- @io.puts if @story_newline
20
- @story_newline = true
21
- # TODO: i18n Story
22
- @io.puts passed("Story: #{header}")
23
- end
24
-
25
- def narrative_executing(narrative)
26
- @io.puts passed(narrative)
19
+ @io.puts if @feature_newline
20
+ @feature_newline = true
21
+ @io.puts passed(header)
22
+ @io.puts
27
23
  end
28
24
 
29
25
  def scenario_executing(scenario)
26
+ @scenario_failed = false
27
+ if scenario.row?
28
+ @io.print " |"
29
+ else
30
+ # TODO: i18n Secnario
31
+ @io.puts passed(" Scenario: #{scenario.name}")
32
+ end
33
+ end
34
+
35
+ def scenario_executed(scenario)
30
36
  @io.puts
31
- # TODO: i18n Secnario
32
- @io.puts passed(" Scenario: #{scenario.name}")
37
+ if !scenario.row? && scenario.table_header
38
+ @io.print " |"
39
+ scenario.table_header.each { |h| @io.print h ; @io.print "|" }
40
+ @io.puts
41
+ elsif scenario.row? && @scenario_failed
42
+ @io.puts
43
+ step_failed(@failed.last)
44
+ end
33
45
  end
34
-
46
+
35
47
  def step_executed(step)
48
+ if step.row?
49
+ row_step_executed(step)
50
+ else
51
+ regular_step_executed(step)
52
+ end
53
+ end
54
+
55
+ def regular_step_executed(step)
36
56
  case(step.error)
37
57
  when Pending
38
58
  @pending << step
@@ -42,23 +62,56 @@ module Cucumber
42
62
  @io.puts passed(" #{step.keyword} #{step.gzub{|param| passed_param(param) << passed}}")
43
63
  else
44
64
  @failed << step
65
+ @scenario_failed = true
45
66
  @io.puts failed(" #{step.keyword} #{step.gzub{|param| failed_param(param) << failed}}")
46
- @io.puts failed(" #{step.error.message.split("\n").join(INDENT)}")
47
- @io.puts failed(" #{step.error.backtrace.join(INDENT)}")
67
+ step_failed(step)
68
+ end
69
+ end
70
+
71
+ def step_failed(step)
72
+ @io.puts failed(" #{step.error.message.split("\n").join(INDENT)} (#{step.error.class})")
73
+ @io.puts failed(" #{step.error.backtrace.join(INDENT)}")
74
+ end
75
+
76
+ def row_step_executed(step)
77
+ case(step.error)
78
+ when Pending
79
+ @pending << step
80
+ step.args.each { |arg| @io.print pending(arg) ; @io.print "|" }
81
+ when NilClass
82
+ @passed << step
83
+ step.args.each { |arg| @io.print passed(arg) ; @io.print "|" }
84
+ else
85
+ @failed << step
86
+ @scenario_failed = true
87
+ step.args.each { |arg| @io.print failed(arg) ; @io.print "|" }
48
88
  end
49
89
  end
50
90
 
51
91
  def step_skipped(step)
52
92
  @skipped << step
53
- @io.puts skipped(" #{step.keyword} #{step.gzub{|param| skipped_param(param) << skipped}}")
93
+ if step.row?
94
+ step.args.each { |arg| @io.print skipped(arg) ; @io.print "|" }
95
+ else
96
+ @io.puts skipped(" #{step.keyword} #{step.gzub{|param| skipped_param(param) << skipped}}")
97
+ end
54
98
  end
55
99
 
56
100
  def dump
57
101
  @io.puts
58
102
  @io.puts passed("#{@passed.length} steps passed") unless @passed.empty?
59
103
  @io.puts failed("#{@failed.length} steps failed") unless @failed.empty?
60
- @io.puts pending("#{@pending.length} steps pending") unless @pending.empty?
61
104
  @io.puts skipped("#{@skipped.length} steps skipped") unless @skipped.empty?
105
+ @io.puts pending("#{@pending.length} steps pending") unless @pending.empty?
106
+
107
+ unless @pending.empty?
108
+ @io.puts "\nYou can use these snippets to implement pending steps:\n\n"
109
+
110
+ @pending.each do |step|
111
+ @io.puts "#{step.keyword} /#{step.name}/ do\nend\n\n" unless step.row?
112
+ end
113
+ end
114
+
62
115
  @io.print reset
63
116
  end
64
117
  end
@@ -2,7 +2,6 @@
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
4
  "en":
5
- story: Story
6
5
  scenario: Scenario
7
6
  given_scenario: GivenScenario
8
7
  given: Given
@@ -10,7 +9,6 @@
10
9
  then: Then
11
10
  and: And
12
11
  "no":
13
- story: Historie
14
12
  scenario: Scenario
15
13
  given_scenario: GittScenario
16
14
  given: Gitt
@@ -18,7 +16,6 @@
18
16
  then: Så
19
17
  and: Og
20
18
  "fr":
21
- story: Histoire
22
19
  scenario: Scenario
23
20
  given_scenario: SoitScenario
24
21
  given: Soit
@@ -26,7 +23,6 @@
26
23
  then: Alors
27
24
  and: Et
28
25
  "pt":
29
- story: Relato
30
26
  scenario: Escenario
31
27
  given_scenario: DadoElEscenario
32
28
  given: Dado