cucumber 0.3.95 → 0.3.96

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/History.txt +21 -0
  2. data/Manifest.txt +9 -3
  3. data/examples/sinatra/features/support/env.rb +1 -3
  4. data/features/cucumber_cli.feature +1 -0
  5. data/features/drb_server_integration.feature +56 -3
  6. data/features/junit_formatter.feature +23 -12
  7. data/features/step_definitions/cucumber_steps.rb +13 -2
  8. data/features/support/env.rb +19 -3
  9. data/lib/cucumber/ast/feature_element.rb +1 -1
  10. data/lib/cucumber/ast/step.rb +1 -1
  11. data/lib/cucumber/ast/step_invocation.rb +3 -2
  12. data/lib/cucumber/cli/configuration.rb +9 -25
  13. data/lib/cucumber/cli/drb_client.rb +7 -3
  14. data/lib/cucumber/cli/language_help_formatter.rb +4 -4
  15. data/lib/cucumber/cli/main.rb +26 -46
  16. data/lib/cucumber/cli/options.rb +3 -0
  17. data/lib/cucumber/constantize.rb +28 -0
  18. data/lib/cucumber/feature_file.rb +3 -3
  19. data/lib/cucumber/formatter/junit.rb +13 -9
  20. data/lib/cucumber/formatter/pretty.rb +2 -2
  21. data/lib/cucumber/language_support/hook_methods.rb +9 -0
  22. data/lib/cucumber/language_support/language_methods.rb +47 -0
  23. data/lib/cucumber/language_support/step_definition_methods.rb +44 -0
  24. data/lib/cucumber/parser/natural_language.rb +72 -0
  25. data/lib/cucumber/rb_support/rb_dsl.rb +73 -0
  26. data/lib/cucumber/rb_support/rb_hook.rb +19 -0
  27. data/lib/cucumber/rb_support/rb_language.rb +129 -0
  28. data/lib/cucumber/rb_support/rb_step_definition.rb +56 -0
  29. data/lib/cucumber/step_match.rb +2 -2
  30. data/lib/cucumber/step_mother.rb +87 -206
  31. data/lib/cucumber/version.rb +1 -1
  32. data/lib/cucumber/webrat/element_locator.rb +7 -7
  33. data/lib/cucumber/world.rb +28 -8
  34. data/rails_generators/cucumber/templates/cucumber_environment.rb +2 -2
  35. data/rails_generators/cucumber/templates/spork_env.rb +0 -2
  36. data/rails_generators/cucumber/templates/webrat_steps.rb +17 -0
  37. data/spec/cucumber/ast/background_spec.rb +8 -5
  38. data/spec/cucumber/ast/feature_factory.rb +4 -5
  39. data/spec/cucumber/ast/feature_spec.rb +7 -1
  40. data/spec/cucumber/ast/scenario_outline_spec.rb +10 -6
  41. data/spec/cucumber/ast/scenario_spec.rb +8 -3
  42. data/spec/cucumber/ast/step_collection_spec.rb +2 -2
  43. data/spec/cucumber/cli/configuration_spec.rb +15 -0
  44. data/spec/cucumber/cli/drb_client_spec.rb +35 -1
  45. data/spec/cucumber/cli/main_spec.rb +5 -4
  46. data/spec/cucumber/cli/options_spec.rb +6 -0
  47. data/spec/cucumber/parser/feature_parser_spec.rb +6 -5
  48. data/spec/cucumber/parser/table_parser_spec.rb +1 -1
  49. data/spec/cucumber/step_definition_spec.rb +26 -25
  50. data/spec/cucumber/step_mother_spec.rb +46 -41
  51. data/spec/cucumber/world/pending_spec.rb +4 -5
  52. metadata +11 -5
  53. data/lib/cucumber/cli/rb_step_def_loader.rb +0 -14
  54. data/lib/cucumber/parser/i18n/language.rb +0 -87
  55. data/lib/cucumber/step_definition.rb +0 -122
@@ -7,14 +7,18 @@ module Cucumber
7
7
  end
8
8
  # Runs features on a DRB server, originally created with Spork compatibility in mind.
9
9
  class DRbClient
10
- def self.run(args, error_stream, out_stream)
10
+ DEFAULT_PORT = 8990
11
+
12
+ def self.run(args, error_stream, out_stream, port = nil)
13
+ port ||= ENV["CUCUMBER_DRB"] || DEFAULT_PORT
14
+
11
15
  # See http://redmine.ruby-lang.org/issues/show/496 as to why we specify localhost:0
12
16
  DRb.start_service("druby://localhost:0")
13
- feature_server = DRbObject.new_with_uri("druby://127.0.0.1:8990")
17
+ feature_server = DRbObject.new_with_uri("druby://127.0.0.1:#{port}")
14
18
  cloned_args = [] # I have no idea why this is needed, but if the regular args are sent then DRb magically transforms it into a DRb object - not an array
15
19
  args.each { |arg| cloned_args << arg }
16
20
  feature_server.run(cloned_args, error_stream, out_stream)
17
- rescue DRb::DRbConnError
21
+ rescue DRb::DRbConnError => e
18
22
  raise DRbClientError, "No DRb server is running."
19
23
  end
20
24
  end
@@ -1,5 +1,5 @@
1
1
  require 'cucumber/formatter/pretty'
2
- require 'cucumber/parser/i18n/language'
2
+ require 'cucumber/parser/natural_language'
3
3
 
4
4
  module Cucumber
5
5
  module Cli
@@ -23,8 +23,8 @@ http://wiki.github.com/aslakhellesoy/cucumber/spoken-languages
23
23
  end
24
24
 
25
25
  def self.list_keywords(io, lang)
26
- language = Parser::I18n::Language[lang]
27
- raw = Parser::I18n::Language::KEYWORD_KEYS.map do |key|
26
+ language = Parser::NaturalLanguage[lang]
27
+ raw = Parser::NaturalLanguage::KEYWORD_KEYS.map do |key|
28
28
  [key, language.keywords(key)]
29
29
  end
30
30
  table = Ast::Table.new(raw)
@@ -46,7 +46,7 @@ http://wiki.github.com/aslakhellesoy/cucumber/spoken-languages
46
46
  def visit_table_cell_value(value, status)
47
47
  if @col == 1
48
48
  if(@options[:check_lang])
49
- @incomplete = Parser::I18n::Language[value].incomplete?
49
+ @incomplete = Parser::NaturalLanguage.get(nil, value).incomplete?
50
50
  end
51
51
  status = :comment
52
52
  elsif @incomplete
@@ -15,17 +15,11 @@ module Cucumber
15
15
 
16
16
  class << self
17
17
  def step_mother
18
- @step_mother
19
- end
20
-
21
- def step_mother=(step_mother)
22
- @step_mother = step_mother
23
- @step_mother.extend(StepMother)
24
- @step_mother.snippet_generator = StepDefinition
18
+ @step_mother ||= StepMother.new
25
19
  end
26
20
 
27
21
  def execute(args)
28
- new(args).execute!(@step_mother)
22
+ new(args).execute!(step_mother)
29
23
  end
30
24
  end
31
25
 
@@ -33,13 +27,15 @@ module Cucumber
33
27
  @args = args
34
28
  @out_stream = out_stream == STDOUT ? Formatter::ColorIO.new : out_stream
35
29
  @error_stream = error_stream
30
+ $err = error_stream
31
+ @unsupported_programming_languages = []
36
32
  end
37
33
 
38
34
  def execute!(step_mother)
39
35
  trap_interrupt
40
36
  if configuration.drb?
41
37
  begin
42
- return DRbClient.run(@args, @error_stream, @out_stream)
38
+ return DRbClient.run(@args, @error_stream, @out_stream, configuration.drb_port)
43
39
  rescue DRbClientError => e
44
40
  @error_stream.puts "WARNING: #{e.message} Running features locally:"
45
41
  end
@@ -50,8 +46,8 @@ module Cucumber
50
46
  # This is because i18n step methods are only aliased when
51
47
  # features are loaded. If we swap the order, the requires
52
48
  # will fail.
53
- features = load_plain_text_features
54
- load_step_defs
49
+ features = load_plain_text_features(step_mother)
50
+ load_step_defs(step_mother)
55
51
  enable_diffing
56
52
 
57
53
  visitor = configuration.build_formatter_broadcaster(step_mother)
@@ -64,7 +60,7 @@ module Cucumber
64
60
  step_mother.scenarios(:passed).any?
65
61
  else
66
62
  step_mother.scenarios(:failed).any? ||
67
- (configuration.strict? && step_mother.steps(:undefined).any?)
63
+ (configuration.strict? && (step_mother.steps(:undefined).any? || step_mother.steps(:pending).any?))
68
64
  end
69
65
  rescue ProfilesNotDefinedError, YmlLoadError, ProfileNotFound => e
70
66
  @error_stream.puts e.message
@@ -84,19 +80,19 @@ module Cucumber
84
80
  exceeded
85
81
  end
86
82
 
87
- def load_plain_text_features
83
+ def load_plain_text_features(step_mother)
88
84
  features = Ast::Features.new
89
85
 
90
86
  verbose_log("Features:")
91
87
  configuration.feature_files.each do |f|
92
88
  feature_file = FeatureFile.new(f)
93
- feature = feature_file.parse(configuration.options)
89
+ feature = feature_file.parse(step_mother, configuration.options)
94
90
  if feature
95
91
  features.add_feature(feature)
96
92
  verbose_log(" * #{f}")
97
93
  end
98
94
  end
99
- verbose_log("\n"*2)
95
+ verbose_log("\n")
100
96
  features
101
97
  end
102
98
 
@@ -114,50 +110,36 @@ module Cucumber
114
110
 
115
111
  private
116
112
 
117
- def load_step_defs
113
+ def load_step_defs(step_mother)
118
114
  step_def_files = configuration.step_defs_to_load
119
- verbose_log("Step Definitions Files:")
115
+ verbose_log("Step Definitions:")
120
116
  step_def_files.each do |step_def_file|
121
- load_step_def(step_def_file)
117
+ load_step_def(step_mother, step_def_file)
122
118
  end
119
+ verbose_log("\n")
123
120
  end
124
121
 
125
- def load_step_def(step_def_file)
126
- if loader = step_def_loader_for(step_def_file)
122
+ def load_step_def(step_mother, step_def_file)
123
+ if programming_language = programming_language_for(step_mother, step_def_file)
127
124
  verbose_log(" * #{step_def_file}")
128
- loader.load_step_def_file(self, step_def_file)
125
+ programming_language.load_step_def_file(step_def_file)
126
+ else
127
+ verbose_log(" * #{step_def_file} [NOT SUPPORTED]")
129
128
  end
130
129
  end
131
130
 
132
- def step_def_loader_for(step_def_file)
133
- @sted_def_loaders ||= {}
131
+ def programming_language_for(step_mother, step_def_file) # :nodoc:
134
132
  if ext = File.extname(step_def_file)[1..-1]
135
- loader = @sted_def_loaders[ext]
136
- return nil if loader == :missing
137
- return loader if loader
133
+ return nil if @unsupported_programming_languages.index(ext)
138
134
  begin
139
- loader_class = configuration.constantize("Cucumber::Cli::#{ext.capitalize}StepDefLoader")
140
- return @sted_def_loaders[ext] = loader_class.new
135
+ step_mother.load_programming_language(ext)
141
136
  rescue LoadError
142
- @sted_def_loaders[ext] = :missing
137
+ @unsupported_programming_languages << ext
143
138
  nil
144
139
  end
140
+ else
141
+ nil
145
142
  end
146
- nil
147
- end
148
-
149
- def step_def_files
150
- main.verbose_log("Ruby files required:")
151
- main.verbose_log(requires.map{|lib| " * #{lib}"}.join("\n"))
152
- requires.each do |lib|
153
- begin
154
- require lib
155
- rescue LoadError => e
156
- e.message << "\nFailed to load #{lib}"
157
- raise e
158
- end
159
- end
160
- main.verbose_log("\n")
161
143
  end
162
144
 
163
145
  def enable_diffing
@@ -186,5 +168,3 @@ module Cucumber
186
168
  end
187
169
  end
188
170
  end
189
-
190
- Cucumber::Cli::Main.step_mother = self
@@ -218,6 +218,9 @@ module Cucumber
218
218
  opts.on(DRB_FLAG, "Run features against a DRb server. (i.e. with the spork gem)") do
219
219
  @options[:drb] = true
220
220
  end
221
+ opts.on("--port PORT", "Specify DRb port. Ignored without --drb") do |port|
222
+ @options[:drb_port] = port
223
+ end
221
224
  opts.on_tail("--version", "Show version.") do
222
225
  @out_stream.puts VERSION::STRING
223
226
  Kernel.exit
@@ -0,0 +1,28 @@
1
+ module Cucumber
2
+ module Constantize
3
+ def constantize(camel_cased_word)
4
+ begin
5
+ names = camel_cased_word.split('::')
6
+ names.shift if names.empty? || names.first.empty?
7
+
8
+ constant = Object
9
+ names.each do |name|
10
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
11
+ end
12
+ constant
13
+ rescue NameError
14
+ require underscore(camel_cased_word)
15
+ retry
16
+ end
17
+ end
18
+
19
+ # Snagged from active_support
20
+ def underscore(camel_cased_word)
21
+ camel_cased_word.to_s.gsub(/::/, '/').
22
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
23
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
24
+ tr("-", "_").
25
+ downcase
26
+ end
27
+ end
28
+ end
@@ -1,4 +1,4 @@
1
- require 'cucumber/parser/i18n/language'
1
+ require 'cucumber/parser/natural_language'
2
2
  require 'cucumber/filter'
3
3
 
4
4
  module Cucumber
@@ -20,9 +20,9 @@ module Cucumber
20
20
  # Parses a file and returns a Cucumber::Ast
21
21
  # If +options+ contains tags, the result will
22
22
  # be filtered.
23
- def parse(options={})
23
+ def parse(step_mother, options)
24
24
  filter = Filter.new(@lines, options)
25
- language = Parser::I18n::Language[lang || options[:lang] || 'en']
25
+ language = Parser::NaturalLanguage.get(step_mother, (lang || options[:lang] || 'en'))
26
26
  language.parse(source, @path, filter)
27
27
  end
28
28
 
@@ -45,8 +45,8 @@ module Cucumber
45
45
  end
46
46
 
47
47
  def visit_scenario_name(keyword, name, file_colon_line, source_indent)
48
- scenario_name = name.strip
49
- scenario_name = "Unnamed scenario" if name == ""
48
+ scenario_name = name.strip.delete(".\r\n")
49
+ scenario_name = "Unnamed scenario" if name.blank?
50
50
  @scenario = scenario_name
51
51
  @outline = keyword.include?('Scenario Outline')
52
52
  @output = "Scenario#{ " outline" if @outline}: #{@scenario}\n\n"
@@ -96,16 +96,20 @@ module Cucumber
96
96
  @time += duration
97
97
  classname = "#{@feature_name}.#{@scenario}"
98
98
  name = "#{@scenario}#{suffix}"
99
- @builder.testcase(:classname => classname, :name => name, :time => "%.6f" % duration) do
100
- if status != :passed
101
- @builder.failure(:message => "#{status.to_s} #{name}", :type => status.to_s) do
102
- @builder.text! @output
103
- @builder.text!(format_exception(exception)) if exception
99
+ failed = (status == :failed || (status == :pending && @options[:strict]))
100
+ #puts "FAILED:!!#{failed}"
101
+ if status == :passed || failed
102
+ @builder.testcase(:classname => classname, :name => name, :time => "%.6f" % duration) do
103
+ if failed
104
+ @builder.failure(:message => "#{status.to_s} #{name}", :type => status.to_s) do
105
+ @builder.text! @output
106
+ @builder.text!(format_exception(exception)) if exception
107
+ end
108
+ @failures += 1
104
109
  end
105
- @failures += 1
106
110
  end
111
+ @tests += 1
107
112
  end
108
- @tests += 1
109
113
  end
110
114
 
111
115
  def format_exception(exception)
@@ -97,9 +97,9 @@ module Cucumber
97
97
  end
98
98
 
99
99
  def visit_examples_name(keyword, name)
100
- names = name.empty? ? [name] : name.split("\n")
100
+ names = name.strip.empty? ? [name.strip] : name.split("\n")
101
101
  @io.puts("\n #{keyword} #{names[0]}")
102
- names[1..-1].each {|s| @io.puts " #{s}" }
102
+ names[1..-1].each {|s| @io.puts " #{s}" } unless names.empty?
103
103
  @io.flush
104
104
  @indent = 6
105
105
  @scenario_indent = 6
@@ -0,0 +1,9 @@
1
+ module Cucumber
2
+ module LanguageSupport
3
+ module HookMethods
4
+ def matches_tag_names?(other_tag_names)
5
+ tag_names.empty? || (tag_names.map{|tag| Ast::Tags.strip_prefix(tag)} & other_tag_names).any?
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,47 @@
1
+ module Cucumber
2
+ module LanguageSupport
3
+ module LanguageMethods
4
+ def before(scenario)
5
+ step_mother.begin_scenario
6
+ execute_before(scenario)
7
+ end
8
+
9
+ def after(scenario)
10
+ execute_after(scenario)
11
+ step_mother.end_scenario
12
+ end
13
+
14
+ def execute_before(scenario)
15
+ step_mother.hooks_for(:before, scenario).each do |hook|
16
+ invoke(hook, 'Before', scenario, true)
17
+ end
18
+ end
19
+
20
+ def execute_after(scenario)
21
+ step_mother.hooks_for(:after, scenario).each do |hook|
22
+ invoke(hook, 'After', scenario, true)
23
+ end
24
+ end
25
+
26
+ def execute_after_step(scenario)
27
+ step_mother.hooks_for(:after_step, scenario).each do |hook|
28
+ invoke(hook, 'AfterStep', scenario, false)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def invoke(hook, location, scenario, exception_fails_scenario)
35
+ begin
36
+ hook.invoke(location, scenario)
37
+ rescue Exception => exception
38
+ if exception_fails_scenario
39
+ scenario.fail!(exception)
40
+ else
41
+ raise
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,44 @@
1
+ module Cucumber
2
+ module LanguageSupport
3
+ module StepDefinitionMethods
4
+ def step_match(name_to_match, name_to_report)
5
+ if(match = regexp.match(name_to_match))
6
+ StepMatch.new(self, name_to_match, name_to_report, match.captures)
7
+ else
8
+ nil
9
+ end
10
+ end
11
+
12
+ # Formats the matched arguments of the associated Step. This method
13
+ # is usually called from visitors, which render output.
14
+ #
15
+ # The +format+ can either be a String or a Proc.
16
+ #
17
+ # If it is a String it should be a format string according to
18
+ # <tt>Kernel#sprinf</tt>, for example:
19
+ #
20
+ # '<span class="param">%s</span></tt>'
21
+ #
22
+ # If it is a Proc, it should take one argument and return the formatted
23
+ # argument, for example:
24
+ #
25
+ # lambda { |param| "[#{param}]" }
26
+ #
27
+ def format_args(step_name, format)
28
+ step_name.gzub(regexp, format)
29
+ end
30
+
31
+ def same_regexp?(regexp)
32
+ self.regexp == regexp
33
+ end
34
+
35
+ def backtrace_line
36
+ "#{file_colon_line}:in `#{regexp.inspect}'"
37
+ end
38
+
39
+ def text_length
40
+ regexp.inspect.jlength
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,72 @@
1
+ module Cucumber
2
+ module Parser
3
+ class NaturalLanguage
4
+ KEYWORD_KEYS = %w{name native encoding feature background scenario scenario_outline examples given when then but}
5
+
6
+ class << self
7
+ def get(step_mother, lang)
8
+ languages[lang] ||= new(step_mother, lang)
9
+ end
10
+
11
+ def languages
12
+ @languages ||= {}
13
+ end
14
+ end
15
+
16
+ def initialize(step_mother, lang)
17
+ @keywords = Cucumber::LANGUAGES[lang]
18
+ raise "Language not supported: #{lang.inspect}" if @keywords.nil?
19
+ @keywords['grammar_name'] = @keywords['name'].gsub(/\s/, '')
20
+ register_adverbs(step_mother) if step_mother
21
+ end
22
+
23
+ def register_adverbs(step_mother)
24
+ adverbs = %w{given when then and but}.map{|keyword| @keywords[keyword].split('|').map{|w| w.gsub(/\s/, '')}}.flatten
25
+ step_mother.register_adverbs(adverbs)
26
+ end
27
+
28
+ def parser
29
+ return @parser if @parser
30
+ i18n_tt = File.expand_path(File.dirname(__FILE__) + '/i18n.tt')
31
+ template = File.open(i18n_tt, Cucumber.file_mode('r')).read
32
+ erb = ERB.new(template)
33
+ grammar = erb.result(binding)
34
+ Treetop.load_from_string(grammar)
35
+ @parser = Parser::I18n.const_get("#{@keywords['grammar_name']}Parser").new
36
+ def @parser.inspect
37
+ "#<#{self.class.name}>"
38
+ end
39
+ @parser
40
+ end
41
+
42
+ def parse(source, path, filter)
43
+ feature = parser.parse_or_fail(source, path, filter)
44
+ feature.language = self if feature
45
+ feature
46
+ end
47
+
48
+ def keywords(key, raw=false)
49
+ return @keywords[key] if raw
50
+ return nil unless @keywords[key]
51
+ values = @keywords[key].split('|')
52
+ values.map{|value| "'#{value}'"}.join(" / ")
53
+ end
54
+
55
+ def incomplete?
56
+ KEYWORD_KEYS.detect{|key| @keywords[key].nil?}
57
+ end
58
+
59
+ def scenario_keyword
60
+ @keywords['scenario'].split('|')[0] + ':'
61
+ end
62
+
63
+ def but_keywords
64
+ @keywords['but'].split('|')
65
+ end
66
+
67
+ def and_keywords
68
+ @keywords['and'].split('|')
69
+ end
70
+ end
71
+ end
72
+ end