cucumber 2.0.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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])