aslakhellesoy-cucumber 0.0.1 → 0.1.1

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