cucumber 0.8.6 → 0.8.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (180) hide show
  1. data/.rspec +1 -1
  2. data/Caliper.yml +4 -0
  3. data/History.txt +1557 -0
  4. data/LICENSE +1 -1
  5. data/README.rdoc +26 -0
  6. data/Rakefile +51 -5
  7. data/VERSION.yml +5 -0
  8. data/bin/cucumber +1 -7
  9. data/cucumber.gemspec +77 -3
  10. data/examples/i18n/ar/features/step_definitons/calculator_steps.rb +1 -1
  11. data/examples/i18n/he/features/step_definitons/calculator_steps.rb +1 -1
  12. data/examples/i18n/ro/features/step_definitons/calculator_steps.rb +7 -4
  13. data/examples/i18n/ro/features/suma.feature +11 -0
  14. data/examples/i18n/ru/features/division.feature +2 -2
  15. data/examples/i18n/tr/features/step_definitons/hesap_makinesi_adimlari.rb +3 -3
  16. data/examples/sinatra/features/support/env.rb +5 -2
  17. data/examples/v8/features/fibonacci.feature +1 -1
  18. data/examples/watir/features/step_definitions/search_steps.rb +1 -1
  19. data/features/announce.feature +164 -0
  20. data/features/around_hooks.feature +232 -0
  21. data/features/background.feature +95 -284
  22. data/features/bug_371.feature +32 -0
  23. data/features/bug_464.feature +16 -0
  24. data/features/bug_475.feature +42 -0
  25. data/features/bug_585_tab_indentation.feature +22 -0
  26. data/features/bug_600.feature +67 -0
  27. data/features/call_steps_from_stepdefs.feature +154 -0
  28. data/features/cucumber_cli.feature +591 -0
  29. data/features/cucumber_cli_outlines.feature +117 -0
  30. data/features/custom_formatter.feature +73 -3
  31. data/features/default_snippets.feature +42 -0
  32. data/features/diffing.feature +25 -0
  33. data/features/drb_server_integration.feature +174 -0
  34. data/features/exception_in_after_block.feature +127 -0
  35. data/features/exception_in_after_step_block.feature +104 -0
  36. data/features/exception_in_before_block.feature +98 -0
  37. data/features/exclude_files.feature +20 -0
  38. data/features/expand.feature +60 -0
  39. data/features/html_formatter.feature +8 -0
  40. data/features/html_formatter/a.html +582 -0
  41. data/features/json_formatter.feature +245 -160
  42. data/features/junit_formatter.feature +88 -0
  43. data/features/language_from_header.feature +30 -0
  44. data/features/language_help.feature +78 -0
  45. data/features/listener_debugger_formatter.feature +42 -0
  46. data/features/multiline_names.feature +44 -0
  47. data/features/negative_tagged_hooks.feature +60 -0
  48. data/features/post_configuration_hook.feature +37 -0
  49. data/features/profiles.feature +126 -0
  50. data/features/rake_task.feature +152 -0
  51. data/features/report_called_undefined_steps.feature +34 -0
  52. data/features/rerun_formatter.feature +45 -0
  53. data/features/simplest.feature +11 -0
  54. data/features/snippet.feature +23 -0
  55. data/features/snippets_when_using_star_keyword.feature +36 -0
  56. data/features/step_definitions/cucumber_steps.rb +153 -7
  57. data/features/step_definitions/extra_steps.rb +2 -0
  58. data/features/step_definitions/simplest_steps.rb +3 -0
  59. data/features/step_definitions/wire_steps.rb +32 -0
  60. data/features/support/env.rb +140 -18
  61. data/features/support/env.rb.simplest +7 -0
  62. data/features/support/fake_wire_server.rb +77 -0
  63. data/features/table_diffing.feature +45 -0
  64. data/features/table_mapping.feature +34 -0
  65. data/features/tag_logic.feature +258 -0
  66. data/features/transform.feature +245 -0
  67. data/features/unicode_table.feature +35 -0
  68. data/features/usage_and_stepdefs_formatter.feature +169 -0
  69. data/features/wire_protocol.feature +332 -0
  70. data/features/wire_protocol_table_diffing.feature +119 -0
  71. data/features/wire_protocol_tags.feature +87 -0
  72. data/features/wire_protocol_timeouts.feature +63 -0
  73. data/features/work_in_progress.feature +156 -0
  74. data/fixtures/json/features/pystring.feature +8 -0
  75. data/fixtures/junit/features/pending.feature +1 -3
  76. data/fixtures/self_test/features/background/background_tagged_before_on_outline.feature +12 -0
  77. data/fixtures/self_test/features/background/background_with_name.feature +7 -0
  78. data/fixtures/self_test/features/background/failing_background.feature +12 -0
  79. data/fixtures/self_test/features/background/failing_background_after_success.feature +11 -0
  80. data/fixtures/self_test/features/background/multiline_args_background.feature +32 -0
  81. data/fixtures/self_test/features/background/passing_background.feature +10 -0
  82. data/fixtures/self_test/features/background/pending_background.feature +10 -0
  83. data/fixtures/self_test/features/background/scenario_outline_failing_background.feature +16 -0
  84. data/fixtures/self_test/features/background/scenario_outline_passing_background.feature +16 -0
  85. data/fixtures/self_test/features/support/env.rb +0 -8
  86. data/fixtures/tickets/features.html +1 -1
  87. data/gem_tasks/examples.rake +1 -1
  88. data/gem_tasks/features.rake +14 -0
  89. data/gem_tasks/sdoc.rake +12 -0
  90. data/lib/cucumber.rb +0 -12
  91. data/lib/cucumber/ast.rb +1 -1
  92. data/lib/cucumber/ast/background.rb +5 -21
  93. data/lib/cucumber/ast/examples.rb +4 -12
  94. data/lib/cucumber/ast/feature.rb +5 -13
  95. data/lib/cucumber/ast/feature_element.rb +4 -9
  96. data/lib/cucumber/ast/outline_table.rb +4 -4
  97. data/lib/cucumber/ast/py_string.rb +80 -0
  98. data/lib/cucumber/ast/scenario.rb +5 -7
  99. data/lib/cucumber/ast/scenario_outline.rb +15 -23
  100. data/lib/cucumber/ast/step.rb +0 -5
  101. data/lib/cucumber/ast/step_invocation.rb +15 -21
  102. data/lib/cucumber/ast/table.rb +8 -14
  103. data/lib/cucumber/ast/tree_walker.rb +48 -10
  104. data/lib/cucumber/cli/configuration.rb +8 -33
  105. data/lib/cucumber/cli/main.rb +35 -20
  106. data/lib/cucumber/cli/options.rb +7 -8
  107. data/lib/cucumber/cli/profile_loader.rb +0 -2
  108. data/lib/cucumber/core_ext/proc.rb +1 -2
  109. data/lib/cucumber/feature_file.rb +15 -47
  110. data/lib/cucumber/formatter/ansicolor.rb +5 -3
  111. data/lib/cucumber/formatter/color_io.rb +23 -0
  112. data/lib/cucumber/formatter/console.rb +23 -27
  113. data/lib/cucumber/formatter/cucumber.css +17 -34
  114. data/lib/cucumber/formatter/cucumber.sass +182 -173
  115. data/lib/cucumber/formatter/html.rb +11 -46
  116. data/lib/cucumber/formatter/io.rb +4 -2
  117. data/lib/cucumber/formatter/json.rb +152 -15
  118. data/lib/cucumber/formatter/json_pretty.rb +6 -5
  119. data/lib/cucumber/formatter/junit.rb +22 -28
  120. data/lib/cucumber/formatter/pdf.rb +6 -6
  121. data/lib/cucumber/formatter/pretty.rb +5 -5
  122. data/lib/cucumber/formatter/rerun.rb +11 -22
  123. data/lib/cucumber/formatter/tag_cloud.rb +35 -0
  124. data/lib/cucumber/formatter/unicode.rb +20 -41
  125. data/lib/cucumber/js_support/js_dsl.js +4 -4
  126. data/lib/cucumber/js_support/js_language.rb +5 -9
  127. data/lib/cucumber/js_support/js_snippets.rb +2 -2
  128. data/lib/cucumber/language_support.rb +2 -2
  129. data/lib/cucumber/parser/gherkin_builder.rb +30 -35
  130. data/lib/cucumber/platform.rb +8 -8
  131. data/lib/cucumber/py_support/py_language.rb +2 -2
  132. data/lib/cucumber/rake/task.rb +31 -74
  133. data/lib/cucumber/rb_support/rb_dsl.rb +0 -1
  134. data/lib/cucumber/rb_support/rb_language.rb +8 -10
  135. data/lib/cucumber/rb_support/rb_step_definition.rb +0 -8
  136. data/lib/cucumber/rb_support/rb_transform.rb +0 -17
  137. data/lib/cucumber/rb_support/rb_world.rb +18 -26
  138. data/lib/cucumber/rspec/doubles.rb +3 -3
  139. data/lib/cucumber/step_match.rb +2 -6
  140. data/lib/cucumber/step_mother.rb +427 -6
  141. data/lib/cucumber/wire_support/configuration.rb +1 -4
  142. data/lib/cucumber/wire_support/wire_language.rb +10 -3
  143. data/spec/cucumber/ast/background_spec.rb +6 -68
  144. data/spec/cucumber/ast/feature_factory.rb +4 -5
  145. data/spec/cucumber/ast/feature_spec.rb +4 -4
  146. data/spec/cucumber/ast/outline_table_spec.rb +1 -1
  147. data/spec/cucumber/ast/py_string_spec.rb +40 -0
  148. data/spec/cucumber/ast/scenario_outline_spec.rb +11 -15
  149. data/spec/cucumber/ast/scenario_spec.rb +4 -4
  150. data/spec/cucumber/ast/step_spec.rb +3 -3
  151. data/spec/cucumber/ast/table_spec.rb +2 -38
  152. data/spec/cucumber/ast/tree_walker_spec.rb +2 -2
  153. data/spec/cucumber/broadcaster_spec.rb +1 -1
  154. data/spec/cucumber/cli/configuration_spec.rb +6 -32
  155. data/spec/cucumber/cli/drb_client_spec.rb +3 -2
  156. data/spec/cucumber/cli/main_spec.rb +43 -43
  157. data/spec/cucumber/cli/options_spec.rb +1 -28
  158. data/spec/cucumber/cli/profile_loader_spec.rb +1 -1
  159. data/spec/cucumber/core_ext/proc_spec.rb +1 -1
  160. data/spec/cucumber/formatter/ansicolor_spec.rb +1 -1
  161. data/spec/cucumber/formatter/color_io_spec.rb +29 -0
  162. data/spec/cucumber/formatter/duration_spec.rb +1 -1
  163. data/spec/cucumber/formatter/html_spec.rb +5 -3
  164. data/spec/cucumber/formatter/junit_spec.rb +2 -16
  165. data/spec/cucumber/formatter/progress_spec.rb +1 -1
  166. data/spec/cucumber/formatter/spec_helper.rb +12 -11
  167. data/spec/cucumber/rb_support/rb_language_spec.rb +28 -241
  168. data/spec/cucumber/rb_support/rb_step_definition_spec.rb +28 -33
  169. data/spec/cucumber/rb_support/regexp_argument_matcher_spec.rb +1 -1
  170. data/spec/cucumber/step_match_spec.rb +9 -11
  171. data/spec/cucumber/step_mother_spec.rb +302 -0
  172. data/spec/cucumber/wire_support/configuration_spec.rb +1 -1
  173. data/spec/cucumber/wire_support/connection_spec.rb +1 -1
  174. data/spec/cucumber/wire_support/wire_exception_spec.rb +1 -1
  175. data/spec/cucumber/wire_support/wire_language_spec.rb +1 -1
  176. data/spec/cucumber/wire_support/wire_packet_spec.rb +1 -1
  177. data/spec/cucumber/wire_support/wire_step_definition_spec.rb +1 -1
  178. data/spec/cucumber/world/pending_spec.rb +2 -2
  179. data/spec/spec_helper.rb +20 -13
  180. metadata +78 -4
@@ -88,7 +88,6 @@ module Cucumber
88
88
 
89
89
  # Registers a proc that will run after Cucumber is configured. You can register as
90
90
  # as you want (typically from ruby scripts under <tt>support/hooks.rb</tt>).
91
- # TODO: Deprecate this
92
91
  def AfterConfiguration(&proc)
93
92
  RbDsl.register_rb_hook('after_configuration', [], proc)
94
93
  end
@@ -1,4 +1,3 @@
1
- require 'cucumber/core_ext/instance_exec'
2
1
  require 'cucumber/rb_support/rb_dsl'
3
2
  require 'cucumber/rb_support/rb_world'
4
3
  require 'cucumber/rb_support/rb_step_definition'
@@ -22,7 +21,7 @@ module Cucumber
22
21
  message << first_proc.backtrace_line('World') << "\n"
23
22
  message << second_proc.backtrace_line('World') << "\n\n"
24
23
  message << "Use Ruby modules instead to extend your worlds. See the Cucumber::RbSupport::RbDsl#World RDoc\n"
25
- message << "or http://wiki.github.com/cucumber/cucumber/a-whole-new-world.\n\n"
24
+ message << "or http://wiki.github.com/aslakhellesoy/cucumber/a-whole-new-world.\n\n"
26
25
  super(message)
27
26
  end
28
27
  end
@@ -30,8 +29,7 @@ module Cucumber
30
29
  # The Ruby implementation of the programming language API.
31
30
  class RbLanguage
32
31
  include LanguageSupport::LanguageMethods
33
- attr_reader :current_world,
34
- :step_definitions
32
+ attr_reader :current_world
35
33
 
36
34
  Gherkin::I18n.code_keywords.each do |adverb|
37
35
  RbDsl.alias_adverb(adverb)
@@ -67,7 +65,7 @@ module Cucumber
67
65
 
68
66
  # Gets called for each file under features (or whatever is overridden
69
67
  # with --require).
70
- def step_definitions_for(rb_file) # Looks Unused - Delete?
68
+ def step_definitions_for(rb_file)
71
69
  begin
72
70
  require rb_file # This will cause self.add_step_definition and self.add_hook to be called from RbDsl
73
71
  step_definitions
@@ -78,7 +76,7 @@ module Cucumber
78
76
  @step_definitions = nil
79
77
  end
80
78
  end
81
-
79
+
82
80
  def step_matches(name_to_match, name_to_format)
83
81
  @step_definitions.map do |step_definition|
84
82
  if(arguments = step_definition.arguments_from(name_to_match))
@@ -91,7 +89,7 @@ module Cucumber
91
89
 
92
90
  ARGUMENT_PATTERNS = ['"([^"]*)"', '(\d+)']
93
91
 
94
- def snippet_text(code_keyword, step_name, multiline_arg_class)
92
+ def snippet_text(step_keyword, step_name, multiline_arg_class)
95
93
  snippet_pattern = Regexp.escape(step_name).gsub('\ ', ' ').gsub('/', '\/')
96
94
  arg_count = 0
97
95
  ARGUMENT_PATTERNS.each do |pattern|
@@ -107,7 +105,7 @@ module Cucumber
107
105
  multiline_class_comment = "# #{multiline_arg_class.default_arg_name} is a #{multiline_arg_class.to_s}\n "
108
106
  end
109
107
 
110
- "#{code_keyword} /^#{snippet_pattern}$/ do#{block_arg_string}\n #{multiline_class_comment}pending # express the regexp above with the code you wish you had\nend"
108
+ "#{Gherkin::I18n.code_keyword_for(step_keyword)} /^#{snippet_pattern}$/ do#{block_arg_string}\n #{multiline_class_comment}pending # express the regexp above with the code you wish you had\nend"
111
109
  end
112
110
 
113
111
  def begin_rb_scenario(scenario)
@@ -140,9 +138,9 @@ module Cucumber
140
138
  end
141
139
 
142
140
  def load_code_file(code_file)
143
- load File.expand_path(code_file) # This will cause self.add_step_definition, self.add_hook, and self.add_transform to be called from RbDsl
141
+ require File.expand_path(code_file) # This will cause self.add_step_definition, self.add_hook, and self.add_transform to be called from RbDsl
144
142
  end
145
-
143
+
146
144
  protected
147
145
 
148
146
  def begin_scenario(scenario)
@@ -38,14 +38,6 @@ module Cucumber
38
38
  @regexp.inspect
39
39
  end
40
40
 
41
- def to_hash
42
- flags = ''
43
- flags += 'm' if (@regexp.options & Regexp::MULTILINE) != 0
44
- flags += 'i' if (@regexp.options & Regexp::IGNORECASE) != 0
45
- flags += 'x' if (@regexp.options & Regexp::EXTENDED) != 0
46
- {'source' => @regexp.source, 'flags' => flags}
47
- end
48
-
49
41
  def ==(step_definition)
50
42
  regexp_source == step_definition.regexp_source
51
43
  end
@@ -32,23 +32,6 @@ module Cucumber
32
32
  @rb_language.current_world.cucumber_instance_exec(true, @regexp.inspect, *args, &@proc)
33
33
  end
34
34
  end
35
-
36
- def to_s
37
- strip_captures(strip_anchors(@regexp.source))
38
- end
39
-
40
- private
41
-
42
- def strip_captures(regexp_source)
43
- regexp_source.
44
- gsub(/(\()/, '').
45
- gsub(/(\))/, '')
46
- end
47
-
48
- def strip_anchors(regexp_source)
49
- regexp_source.
50
- gsub(/(^\^|\$$)/, '')
51
- end
52
35
  end
53
36
  end
54
37
  end
@@ -1,11 +1,7 @@
1
- require 'gherkin/formatter/ansi_escapes'
2
-
3
1
  module Cucumber
4
2
  module RbSupport
5
3
  # All steps are run in the context of an object that extends this module.
6
4
  module RbWorld
7
- include Gherkin::Formatter::AnsiEscapes
8
-
9
5
  class << self
10
6
  def alias_adverb(adverb)
11
7
  alias_method adverb, :__cucumber_invoke
@@ -36,19 +32,14 @@ module Cucumber
36
32
  @__cucumber_step_mother.table(text_or_table, file, line_offset)
37
33
  end
38
34
 
39
- # See StepMother#doc_string
40
- def doc_string(string_with_triple_quotes, file=nil, line_offset=0)
41
- @__cucumber_step_mother.doc_string(string_with_triple_quotes, file, line_offset)
42
- end
43
-
44
- def announce(*messages)
45
- STDERR.puts failed + "WARNING: #announce is deprecated. Use #puts instead:" + caller[0] + reset
46
- puts(*messages)
35
+ # See StepMother#py_string
36
+ def py_string(string_with_triple_quotes, file=nil, line_offset=0)
37
+ @__cucumber_step_mother.py_string(string_with_triple_quotes, file, line_offset)
47
38
  end
48
39
 
49
- # See StepMother#puts
50
- def puts(*messages)
51
- @__cucumber_step_mother.puts(*messages)
40
+ # See StepMother#announce
41
+ def announce(announcement)
42
+ @__cucumber_step_mother.announce(announcement)
52
43
  end
53
44
 
54
45
  # See StepMother#ask
@@ -57,8 +48,17 @@ module Cucumber
57
48
  end
58
49
 
59
50
  # See StepMother#embed
60
- def embed(file, mime_type, label='Screenshot')
61
- @__cucumber_step_mother.embed(file, mime_type, label)
51
+ def embed(file, mime_type)
52
+ @__cucumber_step_mother.embed(file, mime_type)
53
+ end
54
+
55
+ # Prints out the world class, followed by all included modules.
56
+ def announce_world
57
+ announce "WORLD:\n #{self.class}"
58
+ world = self
59
+ (class << self; self; end).instance_eval do
60
+ world.announce " #{included_modules.join("\n ")}"
61
+ end
62
62
  end
63
63
 
64
64
  # Mark the matched step as pending.
@@ -88,15 +88,7 @@ module Cucumber
88
88
  # such errors in World we define it to just return a simple String.
89
89
  #
90
90
  def inspect #:nodoc:
91
- modules = [self.class]
92
- (class << self; self; end).instance_eval do
93
- modules += included_modules
94
- end
95
- sprintf("#<%s:0x%x>", modules.join('+'), self.object_id)
96
- end
97
-
98
- def to_s
99
- inspect
91
+ sprintf("#<%s:0x%x>", self.class, self.object_id)
100
92
  end
101
93
  end
102
94
  end
@@ -4,13 +4,13 @@ RSpec.configuration.configure_mock_framework
4
4
  World(RSpec::Core::MockFrameworkAdapter)
5
5
 
6
6
  Before do
7
- RSpec::Mocks::setup(self)
7
+ _setup_mocks
8
8
  end
9
9
 
10
10
  After do
11
11
  begin
12
- RSpec::Mocks::verify
12
+ _verify_mocks
13
13
  ensure
14
- RSpec::Mocks::teardown
14
+ _teardown_mocks
15
15
  end
16
16
  end
@@ -1,6 +1,6 @@
1
1
  module Cucumber
2
2
  class StepMatch #:nodoc:
3
- attr_reader :step_definition, :step_arguments
3
+ attr_reader :step_definition
4
4
 
5
5
  # Creates a new StepMatch. The +name_to_report+ argument is what's reported, unless it's is,
6
6
  # in which case +name_to_report+ is used instead.
@@ -20,7 +20,7 @@ module Cucumber
20
20
  end
21
21
 
22
22
  def invoke(multiline_arg)
23
- multiline_arg = Ast::DocString.new(multiline_arg) if String === multiline_arg
23
+ multiline_arg = Ast::PyString.new(multiline_arg) if String === multiline_arg
24
24
  all_args = args
25
25
  all_args << multiline_arg.to_step_definition_arg if multiline_arg
26
26
  @step_definition.invoke(all_args)
@@ -107,9 +107,5 @@ module Cucumber
107
107
  def text_length
108
108
  @step.text_length
109
109
  end
110
-
111
- def step_arguments
112
- []
113
- end
114
110
  end
115
111
  end
@@ -1,10 +1,431 @@
1
- require 'cucumber/runtime'
1
+ require 'cucumber/constantize'
2
+ require 'cucumber/core_ext/instance_exec'
3
+ require 'cucumber/language_support/language_methods'
4
+ require 'cucumber/formatter/duration'
5
+ require 'cucumber/cli/options'
6
+ require 'timeout'
2
7
 
3
8
  module Cucumber
4
- class StepMother < Runtime
5
- def initialize(*args)
6
- warn("StepMother has been deprecated and will be gently put to sleep at the next major release. Please use Runtime instead. #{caller[0]}")
7
- super
9
+ # Raised when there is no matching StepDefinition for a step.
10
+ class Undefined < StandardError
11
+ attr_reader :step_name
12
+
13
+ def initialize(step_name)
14
+ super %{Undefined step: "#{step_name}"}
15
+ @step_name = step_name
16
+ end
17
+
18
+ def nested!
19
+ @nested = true
20
+ end
21
+
22
+ def nested?
23
+ @nested
24
+ end
25
+ end
26
+
27
+ # Raised when a StepDefinition's block invokes World#pending
28
+ class Pending < StandardError
29
+ end
30
+
31
+ # Raised when a step matches 2 or more StepDefinitions
32
+ class Ambiguous < StandardError
33
+ def initialize(step_name, step_definitions, used_guess)
34
+ message = "Ambiguous match of \"#{step_name}\":\n\n"
35
+ message << step_definitions.map{|sd| sd.backtrace_line}.join("\n")
36
+ message << "\n\n"
37
+ message << "You can run again with --guess to make Cucumber be more smart about it\n" unless used_guess
38
+ super(message)
39
+ end
40
+ end
41
+
42
+ class TagExcess < StandardError
43
+ def initialize(messages)
44
+ super(messages.join("\n"))
45
+ end
46
+ end
47
+
48
+ # This is the meaty part of Cucumber that ties everything together.
49
+ class StepMother
50
+ include Constantize
51
+ include Formatter::Duration
52
+ attr_writer :options, :visitor, :log
53
+
54
+ def initialize
55
+ @unsupported_programming_languages = []
56
+ @programming_languages = []
57
+ @language_map = {}
58
+ @current_scenario = nil
59
+ end
60
+
61
+ def load_plain_text_features(feature_files)
62
+ features = Ast::Features.new
63
+
64
+ tag_counts = {}
65
+ start = Time.new
66
+ log.debug("Features:\n")
67
+ feature_files.each do |f|
68
+ feature_file = FeatureFile.new(f)
69
+ feature = feature_file.parse(options, tag_counts)
70
+ if feature
71
+ features.add_feature(feature)
72
+ log.debug(" * #{f}\n")
73
+ end
74
+ end
75
+ duration = Time.now - start
76
+ log.debug("Parsing feature files took #{format_duration(duration)}\n\n")
77
+
78
+ check_tag_limits(tag_counts)
79
+
80
+ features
81
+ end
82
+
83
+ def check_tag_limits(tag_counts)
84
+ error_messages = []
85
+ options[:tag_expression].limits.each do |tag_name, tag_limit|
86
+ tag_locations = (tag_counts[tag_name] || [])
87
+ tag_count = tag_locations.length
88
+ if tag_count > tag_limit
89
+ error = "#{tag_name} occurred #{tag_count} times, but the limit was set to #{tag_limit}\n " +
90
+ tag_locations.join("\n ")
91
+ error_messages << error
92
+ end
93
+ end
94
+ raise TagExcess.new(error_messages) if error_messages.any?
95
+ end
96
+
97
+ def load_code_files(step_def_files)
98
+ log.debug("Code:\n")
99
+ step_def_files.each do |step_def_file|
100
+ load_code_file(step_def_file)
101
+ end
102
+ log.debug("\n")
103
+ end
104
+
105
+ def load_code_file(step_def_file)
106
+ if programming_language = programming_language_for(step_def_file)
107
+ log.debug(" * #{step_def_file}\n")
108
+ programming_language.load_code_file(step_def_file)
109
+ else
110
+ log.debug(" * #{step_def_file} [NOT SUPPORTED]\n")
111
+ end
112
+ end
113
+
114
+ # Loads and registers programming language implementation.
115
+ # Instances are cached, so calling with the same argument
116
+ # twice will return the same instance.
117
+ #
118
+ def load_programming_language(ext)
119
+ return @language_map[ext] if @language_map[ext]
120
+ programming_language_class = constantize("Cucumber::#{ext.capitalize}Support::#{ext.capitalize}Language")
121
+ programming_language = programming_language_class.new(self)
122
+ @programming_languages << programming_language
123
+ @language_map[ext] = programming_language
124
+ programming_language
125
+ end
126
+
127
+ # Returns the options passed on the command line.
128
+ def options
129
+ @options ||= Cli::Options.new
130
+ end
131
+
132
+ def step_visited(step) #:nodoc:
133
+ steps << step unless steps.index(step)
134
+ end
135
+
136
+ def steps(status = nil) #:nodoc:
137
+ @steps ||= []
138
+ if(status)
139
+ @steps.select{|step| step.status == status}
140
+ else
141
+ @steps
142
+ end
143
+ end
144
+
145
+ # Output +announcement+ alongside the formatted output.
146
+ # This is an alternative to using Kernel#puts - it will display
147
+ # nicer, and in all outputs (in case you use several formatters)
148
+ #
149
+ def announce(msg)
150
+ msg.respond_to?(:join) ? @visitor.announce(msg.join("\n")) : @visitor.announce(msg.to_s)
151
+ end
152
+
153
+ # Suspends execution and prompts +question+ to the console (STDOUT).
154
+ # An operator (manual tester) can then enter a line of text and hit
155
+ # <ENTER>. The entered text is returned, and both +question+ and
156
+ # the result is added to the output using #announce.
157
+ #
158
+ # If you want a beep to happen (to grab the manual tester's attention),
159
+ # just prepend ASCII character 7 to the question:
160
+ #
161
+ # ask("#{7.chr}How many cukes are in the external system?")
162
+ #
163
+ # If that doesn't issue a beep, you can shell out to something else
164
+ # that makes a sound before invoking #ask.
165
+ #
166
+ def ask(question, timeout_seconds)
167
+ STDOUT.puts(question)
168
+ STDOUT.flush
169
+ announce(question)
170
+
171
+ if(Cucumber::JRUBY)
172
+ answer = jruby_gets(timeout_seconds)
173
+ else
174
+ answer = mri_gets(timeout_seconds)
175
+ end
176
+
177
+ if(answer)
178
+ announce(answer)
179
+ answer
180
+ else
181
+ raise("Waited for input for #{timeout_seconds} seconds, then timed out.")
182
+ end
183
+ end
184
+
185
+ # Embed +file+ of MIME type +mime_type+ into the output. This may or may
186
+ # not be ignored, depending on what kind of formatter(s) are active.
187
+ #
188
+ def embed(file, mime_type)
189
+ @visitor.embed(file, mime_type)
190
+ end
191
+
192
+ def scenarios(status = nil) #:nodoc:
193
+ @scenarios ||= []
194
+ if(status)
195
+ @scenarios.select{|scenario| scenario.status == status}
196
+ else
197
+ @scenarios
198
+ end
199
+ end
200
+
201
+ def invoke(step_name, multiline_argument=nil)
202
+ begin
203
+ step_match(step_name).invoke(multiline_argument)
204
+ rescue Exception => e
205
+ e.nested! if Undefined === e
206
+ raise e
207
+ end
208
+ end
209
+
210
+ # Invokes a series of steps +steps_text+. Example:
211
+ #
212
+ # invoke(%Q{
213
+ # Given I have 8 cukes in my belly
214
+ # Then I should not be thirsty
215
+ # })
216
+ def invoke_steps(steps_text, i18n, file_colon_line)
217
+ file, line = file_colon_line.split(':')
218
+ parser = Gherkin::Parser::Parser.new(StepInvoker.new(self), true, 'steps')
219
+ parser.parse(steps_text, file, line.to_i)
220
+ end
221
+
222
+ class StepInvoker
223
+ def initialize(step_mother)
224
+ @step_mother = step_mother
225
+ end
226
+
227
+ def step(statement, multiline_arg, result)
228
+ cucumber_multiline_arg = case(multiline_arg)
229
+ when Gherkin::Formatter::Model::PyString
230
+ multiline_arg.value
231
+ when Array
232
+ Ast::Table.new(multiline_arg.map{|row| row.cells})
233
+ else
234
+ nil
235
+ end
236
+ @step_mother.invoke(*[statement.name, cucumber_multiline_arg].compact)
237
+ end
238
+
239
+ def eof
240
+ end
241
+ end
242
+
243
+ # Returns a Cucumber::Ast::Table for +text_or_table+, which can either
244
+ # be a String:
245
+ #
246
+ # table(%{
247
+ # | account | description | amount |
248
+ # | INT-100 | Taxi | 114 |
249
+ # | CUC-101 | Peeler | 22 |
250
+ # })
251
+ #
252
+ # or a 2D Array:
253
+ #
254
+ # table([
255
+ # %w{ account description amount },
256
+ # %w{ INT-100 Taxi 114 },
257
+ # %w{ CUC-101 Peeler 22 }
258
+ # ])
259
+ #
260
+ def table(text_or_table, file=nil, line_offset=0)
261
+ if Array === text_or_table
262
+ Ast::Table.new(text_or_table)
263
+ else
264
+ Ast::Table.parse(text_or_table, file, line_offset)
265
+ end
266
+ end
267
+
268
+ # Returns a regular String for +string_with_triple_quotes+. Example:
269
+ #
270
+ # """
271
+ # hello
272
+ # world
273
+ # """
274
+ #
275
+ # Is retured as: " hello\nworld"
276
+ #
277
+ def py_string(string_with_triple_quotes, file=nil, line_offset=0)
278
+ Ast::PyString.parse(string_with_triple_quotes)
279
+ end
280
+
281
+ def step_match(step_name, name_to_report=nil) #:nodoc:
282
+ matches = @programming_languages.map do |programming_language|
283
+ programming_language.step_matches(step_name, name_to_report).to_a
284
+ end.flatten
285
+ raise Undefined.new(step_name) if matches.empty?
286
+ matches = best_matches(step_name, matches) if matches.size > 1 && options[:guess]
287
+ raise Ambiguous.new(step_name, matches, options[:guess]) if matches.size > 1
288
+ matches[0]
289
+ end
290
+
291
+ def best_matches(step_name, step_matches) #:nodoc:
292
+ no_groups = step_matches.select {|step_match| step_match.args.length == 0}
293
+ max_arg_length = step_matches.map {|step_match| step_match.args.length }.max
294
+ top_groups = step_matches.select {|step_match| step_match.args.length == max_arg_length }
295
+
296
+ if no_groups.any?
297
+ longest_regexp_length = no_groups.map {|step_match| step_match.text_length }.max
298
+ no_groups.select {|step_match| step_match.text_length == longest_regexp_length }
299
+ elsif top_groups.any?
300
+ shortest_capture_length = top_groups.map {|step_match| step_match.args.inject(0) {|sum, c| sum + c.to_s.length } }.min
301
+ top_groups.select {|step_match| step_match.args.inject(0) {|sum, c| sum + c.to_s.length } == shortest_capture_length }
302
+ else
303
+ top_groups
304
+ end
305
+ end
306
+
307
+ def unmatched_step_definitions
308
+ @programming_languages.map do |programming_language|
309
+ programming_language.unmatched_step_definitions
310
+ end.flatten
311
+ end
312
+
313
+ def snippet_text(step_keyword, step_name, multiline_arg_class) #:nodoc:
314
+ load_programming_language('rb') if unknown_programming_language?
315
+ @programming_languages.map do |programming_language|
316
+ programming_language.snippet_text(step_keyword, step_name, multiline_arg_class)
317
+ end.join("\n")
318
+ end
319
+
320
+ def unknown_programming_language?
321
+ @programming_languages.empty?
322
+ end
323
+
324
+ def with_hooks(scenario, skip_hooks=false)
325
+ around(scenario, skip_hooks) do
326
+ before_and_after(scenario, skip_hooks) do
327
+ yield scenario
328
+ end
329
+ end
330
+ end
331
+
332
+ def around(scenario, skip_hooks=false, &block) #:nodoc:
333
+ unless skip_hooks
334
+ @programming_languages.reverse.inject(block) do |blk, programming_language|
335
+ proc do
336
+ programming_language.around(scenario) do
337
+ blk.call(scenario)
338
+ end
339
+ end
340
+ end.call
341
+ else
342
+ yield
343
+ end
344
+ end
345
+
346
+ def before_and_after(scenario, skip_hooks=false) #:nodoc:
347
+ before(scenario) unless skip_hooks
348
+ yield scenario
349
+ after(scenario) unless skip_hooks
350
+ scenario_visited(scenario)
351
+ end
352
+
353
+ def before(scenario) #:nodoc:
354
+ return if options[:dry_run] || @current_scenario
355
+ @current_scenario = scenario
356
+ @programming_languages.each do |programming_language|
357
+ programming_language.before(scenario)
358
+ end
359
+ end
360
+
361
+ def after(scenario) #:nodoc:
362
+ @current_scenario = nil
363
+ return if options[:dry_run]
364
+ @programming_languages.each do |programming_language|
365
+ programming_language.after(scenario)
366
+ end
367
+ end
368
+
369
+ def after_step #:nodoc:
370
+ return if options[:dry_run]
371
+ @programming_languages.each do |programming_language|
372
+ programming_language.execute_after_step(@current_scenario)
373
+ end
374
+ end
375
+
376
+ def after_configuration(configuration) #:nodoc
377
+ @programming_languages.each do |programming_language|
378
+ programming_language.after_configuration(configuration)
379
+ end
380
+ end
381
+
382
+ private
383
+
384
+ def programming_language_for(step_def_file) #:nodoc:
385
+ if ext = File.extname(step_def_file)[1..-1]
386
+ return nil if @unsupported_programming_languages.index(ext)
387
+ begin
388
+ load_programming_language(ext)
389
+ rescue LoadError => e
390
+ log.debug("Failed to load '#{ext}' programming language for file #{step_def_file}: #{e.message}\n")
391
+ @unsupported_programming_languages << ext
392
+ nil
393
+ end
394
+ else
395
+ nil
396
+ end
397
+ end
398
+
399
+ def max_step_definition_length #:nodoc:
400
+ @max_step_definition_length ||= step_definitions.map{|step_definition| step_definition.text_length}.max
401
+ end
402
+
403
+ def scenario_visited(scenario) #:nodoc:
404
+ scenarios << scenario unless scenarios.index(scenario)
405
+ end
406
+
407
+ def log
408
+ @log ||= Logger.new(STDOUT)
409
+ end
410
+
411
+ def mri_gets(timeout_seconds)
412
+ begin
413
+ Timeout.timeout(timeout_seconds) do
414
+ STDIN.gets
415
+ end
416
+ rescue Timeout::Error => e
417
+ nil
418
+ end
419
+ end
420
+
421
+ def jruby_gets(timeout_seconds)
422
+ answer = nil
423
+ t = java.lang.Thread.new do
424
+ answer = STDIN.gets
425
+ end
426
+ t.start
427
+ t.join(timeout_seconds * 1000)
428
+ answer
8
429
  end
9
430
  end
10
- end
431
+ end