cucumber 2.0.2 → 2.1.0

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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -6
  3. data/CONTRIBUTING.md +3 -1
  4. data/Gemfile +1 -1
  5. data/History.md +17 -0
  6. data/README.md +3 -3
  7. data/bin/cucumber +1 -2
  8. data/cucumber.gemspec +2 -2
  9. data/examples/i18n/ht/features/adisyon.feature +7 -7
  10. data/features/docs/api/listen_for_events.feature +58 -0
  11. data/features/docs/cli/fail_fast.feature +46 -0
  12. data/features/docs/defining_steps/nested_steps_with_second_arg.feature +3 -22
  13. data/features/docs/extending_cucumber/custom_formatter.feature +40 -4
  14. data/features/docs/gherkin/doc_strings.feature +5 -5
  15. data/features/docs/gherkin/language_help.feature +15 -15
  16. data/features/docs/gherkin/using_descriptions.feature +0 -5
  17. data/lib/cucumber/cli/configuration.rb +10 -92
  18. data/lib/cucumber/cli/main.rb +1 -7
  19. data/lib/cucumber/cli/options.rb +47 -12
  20. data/lib/cucumber/configuration.rb +195 -7
  21. data/lib/cucumber/events.rb +20 -0
  22. data/lib/cucumber/events/after_test_case.rb +25 -0
  23. data/lib/cucumber/events/after_test_step.rb +30 -0
  24. data/lib/cucumber/events/before_test_case.rb +18 -0
  25. data/lib/cucumber/events/before_test_step.rb +23 -0
  26. data/lib/cucumber/events/bus.rb +86 -0
  27. data/lib/cucumber/events/step_match.rb +23 -0
  28. data/lib/cucumber/filters/prepare_world.rb +2 -2
  29. data/lib/cucumber/formatter/backtrace_filter.rb +9 -8
  30. data/lib/cucumber/formatter/console.rb +1 -1
  31. data/lib/cucumber/formatter/event_bus_report.rb +37 -0
  32. data/lib/cucumber/formatter/fail_fast.rb +18 -0
  33. data/lib/cucumber/formatter/html.rb +1 -1
  34. data/lib/cucumber/formatter/io.rb +3 -1
  35. data/lib/cucumber/formatter/json.rb +19 -1
  36. data/lib/cucumber/formatter/legacy_api/adapter.rb +5 -13
  37. data/lib/cucumber/formatter/legacy_api/ast.rb +2 -2
  38. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +3 -1
  39. data/lib/cucumber/formatter/pretty.rb +5 -7
  40. data/lib/cucumber/formatter/progress.rb +1 -1
  41. data/lib/cucumber/formatter/rerun.rb +1 -1
  42. data/lib/cucumber/formatter/steps.rb +1 -1
  43. data/lib/cucumber/formatter/usage.rb +12 -8
  44. data/lib/cucumber/gherkin/data_table_parser.rb +23 -0
  45. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +99 -0
  46. data/lib/cucumber/gherkin/formatter/argument.rb +17 -0
  47. data/lib/cucumber/gherkin/formatter/escaping.rb +17 -0
  48. data/lib/cucumber/gherkin/formatter/hashable.rb +27 -0
  49. data/lib/cucumber/gherkin/i18n.rb +15 -0
  50. data/lib/cucumber/gherkin/steps_parser.rb +41 -0
  51. data/lib/cucumber/language_support/language_methods.rb +6 -5
  52. data/lib/cucumber/multiline_argument.rb +0 -3
  53. data/lib/cucumber/multiline_argument/data_table.rb +6 -5
  54. data/lib/cucumber/multiline_argument/doc_string.rb +1 -2
  55. data/lib/cucumber/platform.rb +1 -1
  56. data/lib/cucumber/rake/task.rb +2 -2
  57. data/lib/cucumber/rb_support/rb_hook.rb +1 -6
  58. data/lib/cucumber/rb_support/rb_language.rb +15 -5
  59. data/lib/cucumber/rb_support/rb_step_definition.rb +11 -17
  60. data/lib/cucumber/rb_support/rb_world.rb +6 -4
  61. data/lib/cucumber/rb_support/regexp_argument_matcher.rb +2 -2
  62. data/lib/cucumber/runtime.rb +36 -16
  63. data/lib/cucumber/runtime/support_code.rb +19 -15
  64. data/lib/cucumber/step_definition_light.rb +5 -5
  65. data/lib/cucumber/step_definitions.rb +2 -2
  66. data/lib/cucumber/step_match.rb +11 -2
  67. data/lib/cucumber/wire_support/wire_protocol/requests.rb +2 -2
  68. data/lib/cucumber/wire_support/wire_step_definition.rb +4 -2
  69. data/{spec → lib}/simplecov_setup.rb +0 -0
  70. data/spec/cucumber/cli/configuration_spec.rb +2 -104
  71. data/spec/cucumber/cli/main_spec.rb +0 -22
  72. data/spec/cucumber/cli/options_spec.rb +3 -1
  73. data/spec/cucumber/configuration_spec.rb +123 -0
  74. data/spec/cucumber/events/bus_spec.rb +94 -0
  75. data/spec/cucumber/formatter/event_bus_report_spec.rb +79 -0
  76. data/spec/cucumber/formatter/fail_fast_spec.rb +88 -0
  77. data/spec/cucumber/formatter/json_spec.rb +43 -1
  78. data/spec/cucumber/formatter/rerun_spec.rb +4 -20
  79. data/spec/cucumber/rb_support/rb_step_definition_spec.rb +29 -0
  80. data/spec/cucumber/runtime_spec.rb +2 -28
  81. data/spec/spec_helper.rb +1 -1
  82. data/spec/support/standard_step_actions.rb +18 -0
  83. metadata +37 -13
  84. data/lib/cucumber/core_ext/proc.rb +0 -36
  85. data/spec/cucumber/core_ext/proc_spec.rb +0 -69
@@ -0,0 +1,20 @@
1
+ module Cucumber
2
+
3
+ # Events tell you what's happening while Cucumber runs your features.
4
+ #
5
+ # They're designed to be read-only, appropriate for writing formatters and other
6
+ # output tools. If you need to be able to influence the result of a scenario, use a hook instead.
7
+ #
8
+ # To subscribe to an event, use {Cucumber::Configuration#on_event}
9
+ #
10
+ # @example
11
+ # AfterConfiguration do |config|
12
+ # config.on_event :after_test_step do |event|
13
+ # puts event.result
14
+ # end
15
+ # end
16
+ module Events
17
+ end
18
+ end
19
+
20
+ Dir[File.dirname(__FILE__) + '/events/*.rb'].map(&method(:require))
@@ -0,0 +1,25 @@
1
+ module Cucumber
2
+ module Events
3
+
4
+ # Event fired after a test case has finished executing
5
+ class AfterTestCase
6
+
7
+ # The test case that was just executed.
8
+ #
9
+ # @return [Cucumber::Core::Test::Case]
10
+ attr_reader :test_case
11
+
12
+ # The result of executing the test case.
13
+ #
14
+ # @return [Cucumber::Core::Test::Result]
15
+ attr_reader :result
16
+
17
+ # @private
18
+ def initialize(test_case, result)
19
+ @test_case, @result = test_case, result
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ module Cucumber
2
+ module Events
3
+
4
+ # Event fired after each test step has been executed
5
+ class AfterTestStep
6
+
7
+ # The test case currently being executed.
8
+ #
9
+ # @return [Cucumber::Core::Test::Case]
10
+ attr_reader :test_case
11
+
12
+ # The test step that was just executed.
13
+ #
14
+ # @return [Cucumber::Core::Test::Step]
15
+ attr_reader :test_step
16
+
17
+ # The result of executing the test step.
18
+ #
19
+ # @return [Cucumber::Core::Test::Result]
20
+ attr_reader :result
21
+
22
+ # @private
23
+ def initialize(test_case, test_step, result)
24
+ @test_case, @test_step, @result = test_case, test_step, result
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ module Cucumber
2
+ module Events
3
+
4
+ # Event fired before a test case is executed
5
+ class BeforeTestCase
6
+
7
+ # The test case about to be executed.
8
+ #
9
+ # @return [Cucumber::Core::Test::Case]
10
+ attr_reader :test_case
11
+
12
+ # @private
13
+ def initialize(test_case)
14
+ @test_case = test_case
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ module Cucumber
2
+ module Events
3
+
4
+ # Event fired before a test step is executed
5
+ class BeforeTestStep
6
+
7
+ # The test case currently being executed.
8
+ #
9
+ # @return [Cucumber::Core::Test::Case]
10
+ attr_reader :test_case
11
+
12
+ # The test step about to be executed.
13
+ #
14
+ # @return [Cucumber::Core::Test::Step]
15
+ attr_reader :test_step
16
+
17
+ # @private
18
+ def initialize(test_case, test_step)
19
+ @test_case, @test_step = test_case, test_step
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,86 @@
1
+ module Cucumber
2
+ module Events
3
+
4
+ # Event bus
5
+ #
6
+ # Implements and in-process pub-sub events broadcaster allowing multiple observers
7
+ # to subscribe to different events that fire as your tests are executed.
8
+ #
9
+ # @private
10
+ class Bus
11
+
12
+ def initialize(default_namespace)
13
+ @default_namespace = default_namespace.to_s
14
+ @handlers = {}
15
+ end
16
+
17
+ # Register for an event
18
+ def register(event_id, handler_object = nil, &handler_proc)
19
+ handler = handler_proc || handler_object
20
+ raise ArgumentError.new("Please pass either an object or a handler block") unless handler
21
+ event_class = parse_event_id(event_id)
22
+ handlers_for(event_class) << handler
23
+ end
24
+
25
+ # Broadcast an event
26
+ def notify(event)
27
+ handlers_for(event.class).each { |handler| handler.call(event) }
28
+ end
29
+
30
+ private
31
+
32
+ def handlers_for(event_class)
33
+ @handlers[event_class.to_s] ||= []
34
+ end
35
+
36
+ def parse_event_id(event_id)
37
+ case event_id
38
+ when Class
39
+ return event_id
40
+ when String
41
+ constantize(event_id)
42
+ else
43
+ constantize("#{@default_namespace}::#{camel_case(event_id)}")
44
+ end
45
+ end
46
+
47
+ def camel_case(underscored_name)
48
+ underscored_name.to_s.split("_").map { |word| word.upcase[0] + word[1..-1] }.join
49
+ end
50
+
51
+ # Thanks ActiveSupport
52
+ # (Only needed to support Ruby 1.9.3 and JRuby)
53
+ def constantize(camel_cased_word)
54
+ names = camel_cased_word.split('::')
55
+
56
+ # Trigger a built-in NameError exception including the ill-formed constant in the message.
57
+ Object.const_get(camel_cased_word) if names.empty?
58
+
59
+ # Remove the first blank element in case of '::ClassName' notation.
60
+ names.shift if names.size > 1 && names.first.empty?
61
+
62
+ names.inject(Object) do |constant, name|
63
+ if constant == Object
64
+ constant.const_get(name)
65
+ else
66
+ candidate = constant.const_get(name)
67
+ next candidate if constant.const_defined?(name, false)
68
+ next candidate unless Object.const_defined?(name)
69
+
70
+ # Go down the ancestors to check if it is owned directly. The check
71
+ # stops when we reach Object or the end of ancestors tree.
72
+ constant = constant.ancestors.inject do |const, ancestor|
73
+ break const if ancestor == Object
74
+ break ancestor if ancestor.const_defined?(name, false)
75
+ const
76
+ end
77
+
78
+ # owner is in Object, so raise
79
+ constant.const_get(name, false)
80
+ end
81
+ end
82
+ end
83
+
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,23 @@
1
+ module Cucumber
2
+ module Events
3
+
4
+ # Event fired when a step is matched to a definition
5
+ class StepMatch
6
+
7
+ # The test step that was matched.
8
+ #
9
+ # @return [Cucumber::Core::Test::Step]
10
+ attr_reader :test_step
11
+
12
+ # Information about the matching definition.
13
+ #
14
+ # @return [Cucumber::StepMatch]
15
+ attr_reader :step_match
16
+
17
+ # @private
18
+ def initialize(test_step, step_match)
19
+ @test_step, @step_match = test_step, step_match
20
+ end
21
+ end
22
+ end
23
+ end
@@ -25,8 +25,8 @@ module Cucumber
25
25
  around_hooks = [init_scenario] + @original_test_case.around_hooks
26
26
 
27
27
  empty_hook = proc {} #no op - legacy format adapter expects a before hooks
28
- file, line = *empty_hook.source_location
29
- default_hook = Cucumber::Hooks.before_hook(@original_test_case.source, Cucumber::Core::Ast::Location.new(file, line), &empty_hook)
28
+ empty_hook_location = Cucumber::Core::Ast::Location.from_source_location(*empty_hook.source_location)
29
+ default_hook = Cucumber::Hooks.before_hook(@original_test_case.source, empty_hook_location, &empty_hook)
30
30
  steps = [default_hook] + @original_test_case.test_steps
31
31
 
32
32
  @original_test_case.with_around_hooks(around_hooks).with_steps(steps)
@@ -3,22 +3,23 @@ require 'cucumber/platform'
3
3
 
4
4
  module Cucumber
5
5
  module Formatter
6
-
7
- class BacktraceFilter
8
- BACKTRACE_FILTER_PATTERNS = \
6
+ BACKTRACE_FILTER_PATTERNS = \
9
7
  [/vendor\/rails|lib\/cucumber|bin\/cucumber:|lib\/rspec|gems\/|minitest|test\/unit|.gem\/ruby|lib\/ruby/]
10
- if(::Cucumber::JRUBY)
11
- BACKTRACE_FILTER_PATTERNS << /org\/jruby/
12
- end
13
- PWD_PATTERN = /#{::Regexp.escape(::Dir.pwd)}\//m
14
8
 
9
+ if(::Cucumber::JRUBY)
10
+ BACKTRACE_FILTER_PATTERNS << /org\/jruby/
11
+ end
12
+
13
+ class BacktraceFilter
15
14
  def initialize(exception)
16
15
  @exception = exception
17
16
  end
18
17
 
19
18
  def exception
20
19
  return @exception if ::Cucumber.use_full_backtrace
21
- @exception.backtrace.each{|line| line.gsub!(PWD_PATTERN, "./")}
20
+
21
+ pwd_pattern = /#{::Regexp.escape(::Dir.pwd)}\//m
22
+ @exception.backtrace.each { |line| line.gsub!(pwd_pattern, "./") }
22
23
 
23
24
  filtered = (@exception.backtrace || []).reject do |line|
24
25
  BACKTRACE_FILTER_PATTERNS.detect { |p| line =~ p }
@@ -32,7 +32,7 @@ module Cucumber
32
32
 
33
33
  def format_step(keyword, step_match, status, source_indent)
34
34
  comment = if source_indent
35
- c = ('# ' + step_match.file_colon_line).indent(source_indent)
35
+ c = ('# ' + step_match.location.to_s).indent(source_indent)
36
36
  format_string(c, :comment)
37
37
  else
38
38
  ''
@@ -0,0 +1,37 @@
1
+ module Cucumber
2
+ module Formatter
3
+
4
+ # Adapter between Cucumber::Core::Test::Runner's Report API and
5
+ # Cucumber's event bus
6
+ class EventBusReport
7
+ attr_reader :config
8
+ private :config
9
+
10
+ def initialize(config)
11
+ @config = config
12
+ end
13
+
14
+ def before_test_case(test_case)
15
+ @config.notify Events::BeforeTestCase.new(test_case)
16
+ @test_case = test_case
17
+ end
18
+
19
+ def before_test_step(test_step)
20
+ @config.notify Events::BeforeTestStep.new(@test_case, test_step)
21
+ end
22
+
23
+ def after_test_step(test_step, result)
24
+ @config.notify Events::AfterTestStep.new(@test_case, test_step, result)
25
+ end
26
+
27
+ def after_test_case(test_case, result)
28
+ @config.notify Events::AfterTestCase.new(test_case, result)
29
+ end
30
+
31
+ def done
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+
@@ -0,0 +1,18 @@
1
+ require 'cucumber/formatter/io'
2
+ require 'cucumber/formatter/console'
3
+
4
+ module Cucumber
5
+ module Formatter
6
+
7
+ class FailFast
8
+
9
+ def initialize(configuration)
10
+ configuration.on_event :after_test_case do |event|
11
+ Cucumber.wants_to_quit = true unless event.result.ok?(configuration.strict?)
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+ end
@@ -20,7 +20,7 @@ module Cucumber
20
20
  include Io
21
21
 
22
22
  def initialize(runtime, path_or_io, options)
23
- @io = ensure_io(path_or_io, "html")
23
+ @io = ensure_io(path_or_io)
24
24
  @runtime = runtime
25
25
  @options = options
26
26
  @buffer = {}
@@ -1,7 +1,9 @@
1
1
  module Cucumber
2
2
  module Formatter
3
3
  module Io
4
- def ensure_io(path_or_io, name)
4
+ module_function
5
+
6
+ def ensure_io(path_or_io)
5
7
  return nil if path_or_io.nil?
6
8
  return path_or_io if path_or_io.respond_to?(:write)
7
9
  file = File.open(path_or_io, Cucumber.file_mode('w'))
@@ -11,7 +11,7 @@ module Cucumber
11
11
 
12
12
  def initialize(runtime, io, _options)
13
13
  @runtime = runtime
14
- @io = ensure_io(io, 'json')
14
+ @io = ensure_io(io)
15
15
  @feature_hashes = []
16
16
  end
17
17
 
@@ -29,6 +29,7 @@ module Cucumber
29
29
  feature_elements << @test_case_hash
30
30
  @element_hash = @test_case_hash
31
31
  end
32
+ @any_step_failed = false
32
33
  end
33
34
 
34
35
  def before_test_step(test_step)
@@ -51,6 +52,11 @@ module Cucumber
51
52
  def after_test_step(test_step, result)
52
53
  return if internal_hook?(test_step)
53
54
  add_match_and_result(test_step, result)
55
+ @any_step_failed = true if result.failed?
56
+ end
57
+
58
+ def after_test_case(test_case, result)
59
+ add_failed_around_hook(result) if result.failed? && !@any_step_failed
54
60
  end
55
61
 
56
62
  def done
@@ -123,6 +129,10 @@ module Cucumber
123
129
  @element_hash[:after] ||= []
124
130
  end
125
131
 
132
+ def around_hooks
133
+ @element_hash[:around] ||= []
134
+ end
135
+
126
136
  def after_step_hooks
127
137
  @step_hash[:after] ||= []
128
138
  end
@@ -159,6 +169,14 @@ module Cucumber
159
169
  @step_or_hook_hash[:result] = create_result_hash(result)
160
170
  end
161
171
 
172
+ def add_failed_around_hook(result)
173
+ @step_or_hook_hash = {}
174
+ around_hooks << @step_or_hook_hash
175
+ @step_or_hook_hash[:match] = { location: "unknown_hook_location:1" }
176
+
177
+ @step_or_hook_hash[:result] = create_result_hash(result)
178
+ end
179
+
162
180
  def create_match_hash(test_step, result)
163
181
  { location: test_step.action_location }
164
182
  end
@@ -207,9 +207,10 @@ module Cucumber
207
207
 
208
208
  def before
209
209
  formatter.before_feature(node)
210
- Ast::Comments.new(node.comments).accept(formatter)
210
+ language_comment = node.language.iso_code != 'en' ? ["# language: #{node.language.iso_code}"] : []
211
+ Ast::Comments.new(language_comment + node.comments).accept(formatter)
211
212
  Ast::Tags.new(node.tags).accept(formatter)
212
- formatter.feature_name node.keyword, indented(node.legacy_conflated_name_and_description)
213
+ formatter.feature_name node.keyword, node.legacy_conflated_name_and_description
213
214
  @delayed_messages = []
214
215
  @delayed_embeddings = []
215
216
  self
@@ -413,15 +414,6 @@ module Cucumber
413
414
  to.class.name == ScenarioOutlinePrinter.name
414
415
  end
415
416
 
416
- def indented(nasty_old_conflation_of_name_and_description)
417
- indent = ""
418
- nasty_old_conflation_of_name_and_description.split("\n").map do |l|
419
- s = "#{indent}#{l}"
420
- indent = " "
421
- s
422
- end.join("\n")
423
- end
424
-
425
417
  end
426
418
 
427
419
  module PrintsAfterHooks
@@ -749,9 +741,9 @@ module Cucumber
749
741
  max_width.result
750
742
  end
751
743
 
752
- require 'gherkin/formatter/escaping'
744
+ require 'cucumber/gherkin/formatter/escaping'
753
745
  FindMaxWidth = Struct.new(:index) do
754
- include ::Gherkin::Formatter::Escaping
746
+ include ::Cucumber::Gherkin::Formatter::Escaping
755
747
 
756
748
  def examples_table(table, &descend)
757
749
  @result = char_length_of(table.header.values[index])