cucumber 0.3.95 → 0.3.96

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