cucumber-core 16.2.0 → 17.0.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -1
  3. data/LICENSE +14 -13
  4. data/lib/cucumber/core/compiler.rb +11 -10
  5. data/lib/cucumber/core/event.rb +12 -10
  6. data/lib/cucumber/core/event_bus.rb +1 -1
  7. data/lib/cucumber/core/events/base.rb +26 -0
  8. data/lib/cucumber/core/events/envelope.rb +13 -2
  9. data/lib/cucumber/core/events/gherkin_source_parsed.rb +12 -3
  10. data/lib/cucumber/core/events/test_case_created.rb +12 -6
  11. data/lib/cucumber/core/events/test_case_finished.rb +11 -1
  12. data/lib/cucumber/core/events/test_case_started.rb +12 -3
  13. data/lib/cucumber/core/events/test_step_created.rb +12 -6
  14. data/lib/cucumber/core/events/test_step_finished.rb +14 -4
  15. data/lib/cucumber/core/events/test_step_started.rb +12 -3
  16. data/lib/cucumber/core/gherkin/document.rb +16 -6
  17. data/lib/cucumber/core/gherkin/parser.rb +5 -9
  18. data/lib/cucumber/core/report/summary.rb +2 -2
  19. data/lib/cucumber/core/test/action/defined.rb +3 -3
  20. data/lib/cucumber/core/test/action.rb +6 -6
  21. data/lib/cucumber/core/test/around_hook.rb +4 -4
  22. data/lib/cucumber/core/test/case.rb +27 -26
  23. data/lib/cucumber/core/test/data_table.rb +37 -39
  24. data/lib/cucumber/core/test/doc_string.rb +23 -23
  25. data/lib/cucumber/core/test/empty_multiline_argument.rb +8 -8
  26. data/lib/cucumber/core/test/filters/locations_filter.rb +1 -1
  27. data/lib/cucumber/core/test/filters/name_filter.rb +1 -1
  28. data/lib/cucumber/core/test/filters/tag_filter.rb +1 -1
  29. data/lib/cucumber/core/test/filters.rb +3 -3
  30. data/lib/cucumber/core/test/hook_step.rb +1 -1
  31. data/lib/cucumber/core/test/location.rb +0 -2
  32. data/lib/cucumber/core/test/result/ambiguous.rb +41 -0
  33. data/lib/cucumber/core/test/result/boolean_methods.rb +28 -0
  34. data/lib/cucumber/core/test/result/duration.rb +33 -0
  35. data/lib/cucumber/core/test/result/failed.rb +72 -0
  36. data/lib/cucumber/core/test/result/flaky.rb +21 -0
  37. data/lib/cucumber/core/test/result/passed.rb +57 -0
  38. data/lib/cucumber/core/test/result/pending.rb +41 -0
  39. data/lib/cucumber/core/test/result/raisable.rb +46 -0
  40. data/lib/cucumber/core/test/result/skipped.rb +41 -0
  41. data/lib/cucumber/core/test/result/summary.rb +89 -0
  42. data/lib/cucumber/core/test/result/undefined.rb +41 -0
  43. data/lib/cucumber/core/test/result/unknown.rb +40 -0
  44. data/lib/cucumber/core/test/result/unknown_duration.rb +26 -0
  45. data/lib/cucumber/core/test/result.rb +15 -458
  46. data/lib/cucumber/core/test/runner.rb +25 -22
  47. data/lib/cucumber/core/test/step.rb +22 -23
  48. data/lib/cucumber/core/test/tag.rb +2 -0
  49. data/lib/cucumber/core/test/timer.rb +1 -1
  50. data/lib/cucumber/core.rb +6 -5
  51. metadata +26 -16
  52. data/lib/cucumber/core/platform.rb +0 -17
@@ -16,60 +16,52 @@ module Cucumber
16
16
  #
17
17
  # And a matching StepDefinition:
18
18
  #
19
- # Given /I have:/ do |table|
19
+ # Given('I have:') do |table|
20
20
  # data = table.raw
21
21
  # end
22
22
  #
23
23
  # This will store <tt>[['a', 'b'], ['c', 'd']]</tt> in the <tt>data</tt> variable.
24
24
  #
25
25
  class DataTable
26
- # Creates a new instance. +raw+ should be an Array of Array of String
27
- # or an Array of Hash
26
+ attr_reader :raw
27
+
28
+ # Creates a new instance. +rows+ should be a square (2d), array of strings or an array of hashes
29
+ #
28
30
  # You don't typically create your own DataTable objects - Cucumber will do
29
31
  # it internally and pass them to your Step Definitions.
30
- #
31
32
  def initialize(rows)
32
33
  raw = ensure_array_of_array(rows)
33
34
  verify_rows_are_same_length(raw)
34
35
  @raw = raw.freeze
35
36
  end
36
- attr_reader :raw
37
-
38
- def describe_to(visitor, *)
39
- visitor.data_table(self, *)
40
- end
41
37
 
42
- def to_step_definition_arg
43
- dup
38
+ def ==(other)
39
+ other.class == self.class && raw == other.raw
44
40
  end
45
41
 
46
42
  def data_table?
47
43
  true
48
44
  end
49
45
 
46
+ def describe_to(visitor, *)
47
+ visitor.data_table(self, *)
48
+ end
49
+
50
50
  def doc_string?
51
51
  false
52
52
  end
53
53
 
54
54
  # Creates a copy of this table
55
- #
56
55
  def dup
57
56
  self.class.new(raw.dup)
58
57
  end
59
58
 
60
- # Returns a new, transposed table. Example:
61
- #
62
- # | a | 7 | 4 |
63
- # | b | 9 | 2 |
64
- #
65
- # Gets converted into the following:
66
- #
67
- # | a | b |
68
- # | 7 | 9 |
69
- # | 4 | 2 |
70
- #
71
- def transpose
72
- self.class.new(raw.transpose)
59
+ def inspect
60
+ %{#<#{self.class} #{raw.inspect}>}
61
+ end
62
+
63
+ def lines_count
64
+ raw.count
73
65
  end
74
66
 
75
67
  def map(&block)
@@ -80,26 +72,26 @@ module Cucumber
80
72
  self.class.new(new_raw)
81
73
  end
82
74
 
83
- def lines_count
84
- raw.count
85
- end
86
-
87
- def ==(other)
88
- other.class == self.class && raw == other.raw
75
+ def to_step_definition_arg
76
+ dup
89
77
  end
90
78
 
91
- def inspect
92
- %{#<#{self.class} #{raw.inspect})>}
79
+ # Returns a new, transposed table. Example:
80
+ #
81
+ # | a | 7 | 4 |
82
+ # | b | 9 | 2 |
83
+ #
84
+ # Gets converted into the following:
85
+ #
86
+ # | a | b |
87
+ # | 7 | 9 |
88
+ # | 4 | 2 |
89
+ def transpose
90
+ self.class.new(raw.transpose)
93
91
  end
94
92
 
95
93
  private
96
94
 
97
- def verify_rows_are_same_length(raw)
98
- raw.transpose
99
- rescue IndexError
100
- raise ArgumentError, 'Rows must all be the same length'
101
- end
102
-
103
95
  def ensure_array_of_array(array)
104
96
  array[0].is_a?(Hash) ? hashes_to_array(array) : array
105
97
  end
@@ -108,6 +100,12 @@ module Cucumber
108
100
  header = hashes[0].keys.sort
109
101
  [header] + hashes.map { |hash| header.map { |key| hash[key] } }
110
102
  end
103
+
104
+ def verify_rows_are_same_length(raw)
105
+ raw.transpose
106
+ rescue IndexError
107
+ raise ArgumentError, 'Rows must all be the same length'
108
+ end
111
109
  end
112
110
  end
113
111
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'delegate'
4
+
4
5
  module Cucumber
5
6
  module Core
6
7
  module Test
@@ -19,7 +20,6 @@ module Cucumber
19
20
  # example above, that would return: <tt>"I like\nCucumber sandwich"</tt>
20
21
  #
21
22
  # Note how the indentation from the source is stripped away.
22
- #
23
23
  class DocString < SimpleDelegator
24
24
  attr_reader :content_type, :content
25
25
 
@@ -29,47 +29,47 @@ module Cucumber
29
29
  super(@content)
30
30
  end
31
31
 
32
- def describe_to(visitor, *)
33
- visitor.doc_string(self, *)
32
+ def ==(other)
33
+ return false if other.respond_to?(:content_type) && content_type != other.content_type
34
+ return content == other.to_str if other.respond_to?(:to_str)
35
+
36
+ false
34
37
  end
35
38
 
36
39
  def data_table?
37
40
  false
38
41
  end
39
42
 
40
- def doc_string?
41
- true
43
+ def describe_to(visitor, *)
44
+ visitor.doc_string(self, *)
42
45
  end
43
46
 
44
- def map
45
- raise ArgumentError, 'No block given' unless block_given?
46
-
47
- new_content = yield content
48
- self.class.new(new_content, content_type)
47
+ def doc_string?
48
+ true
49
49
  end
50
50
 
51
- def to_step_definition_arg
52
- self
51
+ def inspect
52
+ [
53
+ %(#<#{self.class}),
54
+ %( """#{content_type}),
55
+ %( #{@content}),
56
+ %( """>)
57
+ ].join("\n")
53
58
  end
54
59
 
55
60
  def lines_count
56
61
  lines.count + 2
57
62
  end
58
63
 
59
- def ==(other)
60
- return false if other.respond_to?(:content_type) && content_type != other.content_type
61
- return content == other.to_str if other.respond_to?(:to_str)
64
+ def map
65
+ raise ArgumentError, 'No block given' unless block_given?
62
66
 
63
- false
67
+ new_content = yield content
68
+ self.class.new(new_content, content_type)
64
69
  end
65
70
 
66
- def inspect
67
- [
68
- %(#<#{self.class}),
69
- %( """#{content_type}),
70
- %( #{@content}),
71
- %( """>)
72
- ].join("\n")
71
+ def to_step_definition_arg
72
+ self
73
73
  end
74
74
  end
75
75
  end
@@ -4,28 +4,28 @@ module Cucumber
4
4
  module Core
5
5
  module Test
6
6
  class EmptyMultilineArgument
7
- def describe_to(*)
8
- self
9
- end
10
-
11
7
  def data_table?
12
8
  false
13
9
  end
14
10
 
11
+ def describe_to(*)
12
+ self
13
+ end
14
+
15
15
  def doc_string?
16
16
  false
17
17
  end
18
18
 
19
- def map
20
- self
19
+ def inspect
20
+ "#<#{self.class}>"
21
21
  end
22
22
 
23
23
  def lines_count
24
24
  0
25
25
  end
26
26
 
27
- def inspect
28
- "#<#{self.class}>"
27
+ def map
28
+ self
29
29
  end
30
30
  end
31
31
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cucumber/core/filter'
3
+ require_relative '../../filter'
4
4
 
5
5
  module Cucumber
6
6
  module Core
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cucumber/core/filter'
3
+ require_relative '../../filter'
4
4
 
5
5
  module Cucumber
6
6
  module Core
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cucumber/core/filter'
3
+ require_relative '../../filter'
4
4
 
5
5
  module Cucumber
6
6
  module Core
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cucumber/core/test/filters/locations_filter'
4
- require 'cucumber/core/test/filters/name_filter'
5
- require 'cucumber/core/test/filters/tag_filter'
3
+ require_relative 'filters/locations_filter'
4
+ require_relative 'filters/name_filter'
5
+ require_relative 'filters/tag_filter'
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cucumber/core/test/step'
3
+ require_relative 'step'
4
4
 
5
5
  module Cucumber
6
6
  module Core
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
- require 'cucumber/core/platform'
5
3
  require 'set'
6
4
 
7
5
  module Cucumber
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/messages'
4
+ require 'cucumber/messages/helpers/time_conversion'
5
+
6
+ module Cucumber
7
+ module Core
8
+ module Test
9
+ module Result
10
+ class Ambiguous < Raisable
11
+ include BooleanMethods
12
+
13
+ def self.ok?
14
+ false
15
+ end
16
+
17
+ def describe_to(visitor, *)
18
+ visitor.ambiguous(*)
19
+ visitor.duration(duration, *)
20
+ self
21
+ end
22
+
23
+ def to_s
24
+ 'A'
25
+ end
26
+
27
+ def to_sym
28
+ :ambiguous
29
+ end
30
+
31
+ def to_message
32
+ Cucumber::Messages::TestStepResult.new(
33
+ status: Cucumber::Messages::TestStepResultStatus::AMBIGUOUS,
34
+ duration: duration.to_message_duration
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/messages'
4
+ require 'cucumber/messages/helpers/time_conversion'
5
+
6
+ module Cucumber
7
+ module Core
8
+ module Test
9
+ module Result
10
+ # Simple module that when included generates all the boolean methods for each category of result
11
+ # The single exception to this is the class method `self.ok?` which is defined for each result individually
12
+ module BooleanMethods
13
+ TYPES = %i[failed ambiguous flaky undefined pending skipped passed unknown].freeze
14
+
15
+ TYPES.each do |result|
16
+ define_method("#{result}?") do
17
+ result == to_sym
18
+ end
19
+ end
20
+
21
+ def ok?
22
+ self.class.ok?
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/messages'
4
+ require 'cucumber/messages/helpers/time_conversion'
5
+
6
+ module Cucumber
7
+ module Core
8
+ module Test
9
+ module Result
10
+ class Duration
11
+ include Cucumber::Messages::Helpers::TimeConversion
12
+
13
+ attr_reader :nanoseconds
14
+
15
+ def initialize(nanoseconds)
16
+ @nanoseconds = nanoseconds
17
+ end
18
+
19
+ def to_message_duration
20
+ duration_hash = seconds_to_duration(nanoseconds.to_f / NANOSECONDS_PER_SECOND)
21
+ Cucumber::Messages::Duration.new(seconds: duration_hash[:seconds], nanos: duration_hash[:nanos])
22
+ end
23
+
24
+ def seconds_to_duration(seconds_float)
25
+ seconds, second_modulus = seconds_float.divmod(1)
26
+ nanos = second_modulus * NANOSECONDS_PER_SECOND
27
+ { seconds: seconds, nanos: nanos.to_i }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/messages'
4
+ require 'cucumber/messages/helpers/time_conversion'
5
+
6
+ module Cucumber
7
+ module Core
8
+ module Test
9
+ module Result
10
+ class Failed
11
+ include BooleanMethods
12
+
13
+ attr_reader :duration, :exception
14
+
15
+ def self.ok?
16
+ false
17
+ end
18
+
19
+ def initialize(duration, exception)
20
+ raise ArgumentError unless duration
21
+ raise ArgumentError unless exception
22
+
23
+ @duration = duration
24
+ @exception = exception
25
+ end
26
+
27
+ def describe_to(visitor, *)
28
+ visitor.failed(*)
29
+ visitor.duration(duration, *)
30
+ visitor.exception(exception, *) if exception
31
+ self
32
+ end
33
+
34
+ def to_s
35
+ '✗'
36
+ end
37
+
38
+ def to_sym
39
+ :failed
40
+ end
41
+
42
+ def to_message
43
+ begin
44
+ message = exception.backtrace.join("\n")
45
+ rescue NoMethodError
46
+ message = ''
47
+ end
48
+
49
+ Cucumber::Messages::TestStepResult.new(
50
+ status: Cucumber::Messages::TestStepResultStatus::FAILED,
51
+ duration: duration.to_message_duration,
52
+ message: message
53
+ )
54
+ end
55
+
56
+ def with_duration(new_duration)
57
+ self.class.new(new_duration, exception)
58
+ end
59
+
60
+ def with_appended_backtrace(step)
61
+ exception.backtrace << step.backtrace_line if step.respond_to?(:backtrace_line)
62
+ self
63
+ end
64
+
65
+ def with_filtered_backtrace(filter)
66
+ self.class.new(duration, filter.new(exception.dup).exception)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/messages'
4
+ require 'cucumber/messages/helpers/time_conversion'
5
+
6
+ module Cucumber
7
+ module Core
8
+ module Test
9
+ module Result
10
+ # Flaky is not used directly as an execution result, but is used as a
11
+ # reporting result type for test cases that fails and the passes on
12
+ # retry, therefore only the class method self.ok? is needed.
13
+ class Flaky
14
+ def self.ok?
15
+ false
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/messages'
4
+ require 'cucumber/messages/helpers/time_conversion'
5
+
6
+ module Cucumber
7
+ module Core
8
+ module Test
9
+ module Result
10
+ class Passed
11
+ include BooleanMethods
12
+
13
+ attr_accessor :duration
14
+
15
+ def self.ok?
16
+ true
17
+ end
18
+
19
+ def initialize(duration)
20
+ raise ArgumentError unless duration
21
+
22
+ @duration = duration
23
+ end
24
+
25
+ def describe_to(visitor, *)
26
+ visitor.passed(*)
27
+ visitor.duration(duration, *)
28
+ self
29
+ end
30
+
31
+ def to_s
32
+ '✓'
33
+ end
34
+
35
+ def to_sym
36
+ :passed
37
+ end
38
+
39
+ def to_message
40
+ Cucumber::Messages::TestStepResult.new(
41
+ status: Cucumber::Messages::TestStepResultStatus::PASSED,
42
+ duration: duration.to_message_duration
43
+ )
44
+ end
45
+
46
+ def with_appended_backtrace(_step)
47
+ self
48
+ end
49
+
50
+ def with_filtered_backtrace(_filter)
51
+ self
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/messages'
4
+ require 'cucumber/messages/helpers/time_conversion'
5
+
6
+ module Cucumber
7
+ module Core
8
+ module Test
9
+ module Result
10
+ class Pending < Raisable
11
+ include BooleanMethods
12
+
13
+ def self.ok?
14
+ false
15
+ end
16
+
17
+ def describe_to(visitor, *)
18
+ visitor.pending(self, *)
19
+ visitor.duration(duration, *)
20
+ self
21
+ end
22
+
23
+ def to_s
24
+ 'P'
25
+ end
26
+
27
+ def to_sym
28
+ :pending
29
+ end
30
+
31
+ def to_message
32
+ Cucumber::Messages::TestStepResult.new(
33
+ status: Cucumber::Messages::TestStepResultStatus::PENDING,
34
+ duration: duration.to_message_duration
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/messages'
4
+ require 'cucumber/messages/helpers/time_conversion'
5
+
6
+ module Cucumber
7
+ module Core
8
+ module Test
9
+ module Result
10
+ # Base class for exceptions that can be raised in a step definition causing the step to have that result.
11
+ class Raisable < StandardError
12
+ attr_reader :message, :duration
13
+
14
+ def initialize(message = '', duration = UnknownDuration.new, backtrace = nil)
15
+ @message = message
16
+ @duration = duration
17
+ super(message)
18
+ set_backtrace(backtrace) if backtrace
19
+ end
20
+
21
+ def with_message(new_message)
22
+ self.class.new(new_message, duration, backtrace)
23
+ end
24
+
25
+ def with_duration(new_duration)
26
+ self.class.new(message, new_duration, backtrace)
27
+ end
28
+
29
+ def with_appended_backtrace(step)
30
+ return self unless step.respond_to?(:backtrace_line)
31
+
32
+ set_backtrace([]) unless backtrace
33
+ backtrace << step.backtrace_line
34
+ self
35
+ end
36
+
37
+ def with_filtered_backtrace(filter)
38
+ return self unless backtrace
39
+
40
+ filter.new(dup).exception
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/messages'
4
+ require 'cucumber/messages/helpers/time_conversion'
5
+
6
+ module Cucumber
7
+ module Core
8
+ module Test
9
+ module Result
10
+ class Skipped < Raisable
11
+ include BooleanMethods
12
+
13
+ def self.ok?
14
+ true
15
+ end
16
+
17
+ def describe_to(visitor, *)
18
+ visitor.skipped(*)
19
+ visitor.duration(duration, *)
20
+ self
21
+ end
22
+
23
+ def to_s
24
+ '-'
25
+ end
26
+
27
+ def to_sym
28
+ :skipped
29
+ end
30
+
31
+ def to_message
32
+ Cucumber::Messages::TestStepResult.new(
33
+ status: Cucumber::Messages::TestStepResultStatus::SKIPPED,
34
+ duration: duration.to_message_duration
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end