cucumber 0.8.6 → 0.8.7

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