cucumber-core 16.1.1 → 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 +25 -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 -433
  46. data/lib/cucumber/core/test/runner.rb +29 -19
  47. data/lib/cucumber/core/test/step.rb +24 -21
  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
@@ -0,0 +1,89 @@
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
+ # An object that responds to the description protocol from the results and collects summary information.
11
+ #
12
+ # e.g.
13
+ # summary = Result::Summary.new
14
+ # Result::Passed.new(0).describe_to(summary)
15
+ # puts summary.total_passed
16
+ # => 1
17
+ #
18
+ class Summary
19
+ TYPES = %i[failed ambiguous flaky undefined pending skipped passed unknown].freeze
20
+
21
+ attr_reader :exceptions, :durations
22
+
23
+ def initialize
24
+ @totals = Hash.new { 0 }
25
+ @exceptions = []
26
+ @durations = []
27
+ end
28
+
29
+ def method_missing(name, *_args)
30
+ if name =~ /^total_/
31
+ get_total(name)
32
+ else
33
+ increment_total(name)
34
+ end
35
+ end
36
+
37
+ def respond_to_missing?(*)
38
+ true
39
+ end
40
+
41
+ def ok?
42
+ TYPES.each do |type|
43
+ return false if get_total(type).positive? && !with_type(type).ok?
44
+ end
45
+ true
46
+ end
47
+
48
+ def exception(exception)
49
+ @exceptions << exception
50
+ self
51
+ end
52
+
53
+ def duration(duration)
54
+ @durations << duration
55
+ self
56
+ end
57
+
58
+ def total(for_status = nil)
59
+ if for_status
60
+ @totals.fetch(for_status, 0)
61
+ else
62
+ @totals.values.reduce(0) { |total, count| total + count }
63
+ end
64
+ end
65
+
66
+ def with_type(type)
67
+ Object.const_get("Cucumber::Core::Test::Result::#{type.capitalize}")
68
+ end
69
+
70
+ def decrement_failed
71
+ @totals[:failed] -= 1
72
+ end
73
+
74
+ private
75
+
76
+ def get_total(method_name)
77
+ status = method_name.to_s.gsub('total_', '').to_sym
78
+ @totals.fetch(status, 0)
79
+ end
80
+
81
+ def increment_total(status)
82
+ @totals[status] += 1
83
+ self
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ 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 Undefined < Raisable
11
+ include BooleanMethods
12
+
13
+ def self.ok?
14
+ false
15
+ end
16
+
17
+ def describe_to(visitor, *)
18
+ visitor.undefined(*)
19
+ visitor.duration(duration, *)
20
+ self
21
+ end
22
+
23
+ def to_s
24
+ '?'
25
+ end
26
+
27
+ def to_sym
28
+ :undefined
29
+ end
30
+
31
+ def to_message
32
+ Cucumber::Messages::TestStepResult.new(
33
+ status: Cucumber::Messages::TestStepResultStatus::UNDEFINED,
34
+ duration: duration.to_message_duration
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
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
+ # Null object for results. Represents the state where we haven't run anything yet
11
+ class Unknown
12
+ include BooleanMethods
13
+
14
+ def self.ok?
15
+ false
16
+ end
17
+
18
+ def describe_to(_visitor, *_args)
19
+ self
20
+ end
21
+
22
+ def with_filtered_backtrace(_filter)
23
+ self
24
+ end
25
+
26
+ def to_message
27
+ Cucumber::Messages::TestStepResult.new(
28
+ status: Cucumber::Messages::TestStepResultStatus::UNKNOWN,
29
+ duration: UnknownDuration.new.to_message_duration
30
+ )
31
+ end
32
+
33
+ def to_sym
34
+ :unknown
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
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 UnknownDuration
11
+ def tap
12
+ self
13
+ end
14
+
15
+ def nanoseconds
16
+ raise '#nanoseconds only allowed to be used in #tap block'
17
+ end
18
+
19
+ def to_message_duration
20
+ Cucumber::Messages::Duration.new(seconds: 0, nanos: 0)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -3,436 +3,18 @@
3
3
  require 'cucumber/messages'
4
4
  require 'cucumber/messages/helpers/time_conversion'
5
5
 
6
- module Cucumber
7
- module Core
8
- module Test
9
- module Result
10
- TYPES = %i[failed flaky skipped undefined pending passed unknown].freeze
11
- STRICT_AFFECTED_TYPES = %i[flaky undefined pending].freeze
12
-
13
- def self.ok?(type, strict: StrictConfiguration.new)
14
- class_name = type.to_s.slice(0, 1).capitalize + type.to_s.slice(1..-1)
15
- const_get(class_name).ok?(strict: strict.strict?(type))
16
- end
17
-
18
- # Defines to_sym on a result class for the given result type
19
- #
20
- # Defines predicate methods on a result class with only the given one returning true
21
- def self.query_methods(result_type)
22
- Module.new do
23
- define_method :to_sym do
24
- result_type
25
- end
26
-
27
- TYPES.each do |possible_result_type|
28
- define_method("#{possible_result_type}?") do
29
- possible_result_type == to_sym
30
- end
31
- end
32
- end
33
- end
34
-
35
- # Null object for results. Represents the state where we haven't run anything yet
36
- class Unknown
37
- include Result.query_methods :unknown
38
-
39
- def describe_to(_visitor, *_args)
40
- self
41
- end
42
-
43
- def with_filtered_backtrace(_filter)
44
- self
45
- end
46
-
47
- def to_message
48
- Cucumber::Messages::TestStepResult.new(
49
- status: Cucumber::Messages::TestStepResultStatus::UNKNOWN,
50
- duration: UnknownDuration.new.to_message_duration
51
- )
52
- end
53
- end
54
-
55
- class Passed
56
- include Result.query_methods :passed
57
-
58
- attr_accessor :duration
59
-
60
- def self.ok?(*)
61
- true
62
- end
63
-
64
- def initialize(duration)
65
- raise ArgumentError unless duration
66
-
67
- @duration = duration
68
- end
69
-
70
- def describe_to(visitor, *)
71
- visitor.passed(*)
72
- visitor.duration(duration, *)
73
- self
74
- end
75
-
76
- def to_s
77
- '✓'
78
- end
79
-
80
- def to_message
81
- Cucumber::Messages::TestStepResult.new(
82
- status: Cucumber::Messages::TestStepResultStatus::PASSED,
83
- duration: duration.to_message_duration
84
- )
85
- end
86
-
87
- def ok?(*)
88
- self.class.ok?
89
- end
90
-
91
- def with_appended_backtrace(_step)
92
- self
93
- end
94
-
95
- def with_filtered_backtrace(_filter)
96
- self
97
- end
98
- end
99
-
100
- class Failed
101
- include Result.query_methods :failed
102
-
103
- attr_reader :duration, :exception
104
-
105
- def self.ok?(*)
106
- false
107
- end
108
-
109
- def initialize(duration, exception)
110
- raise ArgumentError unless duration
111
- raise ArgumentError unless exception
112
-
113
- @duration = duration
114
- @exception = exception
115
- end
116
-
117
- def describe_to(visitor, *)
118
- visitor.failed(*)
119
- visitor.duration(duration, *)
120
- visitor.exception(exception, *) if exception
121
- self
122
- end
123
-
124
- def to_s
125
- '✗'
126
- end
127
-
128
- def to_message
129
- begin
130
- message = exception.backtrace.join("\n")
131
- rescue NoMethodError
132
- message = ''
133
- end
134
-
135
- Cucumber::Messages::TestStepResult.new(
136
- status: Cucumber::Messages::TestStepResultStatus::FAILED,
137
- duration: duration.to_message_duration,
138
- message: message
139
- )
140
- end
141
-
142
- def ok?(*)
143
- self.class.ok?
144
- end
145
-
146
- def with_duration(new_duration)
147
- self.class.new(new_duration, exception)
148
- end
149
-
150
- def with_appended_backtrace(step)
151
- exception.backtrace << step.backtrace_line if step.respond_to?(:backtrace_line)
152
- self
153
- end
154
-
155
- def with_filtered_backtrace(filter)
156
- self.class.new(duration, filter.new(exception.dup).exception)
157
- end
158
- end
159
-
160
- # Flaky is not used directly as an execution result, but is used as a
161
- # reporting result type for test cases that fails and the passes on
162
- # retry, therefore only the class method self.ok? is needed.
163
- class Flaky
164
- def self.ok?(strict: false)
165
- !strict
166
- end
167
- end
168
-
169
- # Base class for exceptions that can be raised in a step definition causing the step to have that result.
170
- class Raisable < StandardError
171
- attr_reader :message, :duration
172
-
173
- def initialize(message = '', duration = UnknownDuration.new, backtrace = nil)
174
- @message = message
175
- @duration = duration
176
- super(message)
177
- set_backtrace(backtrace) if backtrace
178
- end
179
-
180
- def with_message(new_message)
181
- self.class.new(new_message, duration, backtrace)
182
- end
183
-
184
- def with_duration(new_duration)
185
- self.class.new(message, new_duration, backtrace)
186
- end
187
-
188
- def with_appended_backtrace(step)
189
- return self unless step.respond_to?(:backtrace_line)
190
-
191
- set_backtrace([]) unless backtrace
192
- backtrace << step.backtrace_line
193
- self
194
- end
195
-
196
- def with_filtered_backtrace(filter)
197
- return self unless backtrace
198
-
199
- filter.new(dup).exception
200
- end
201
-
202
- def ok?(strict: StrictConfiguration.new)
203
- self.class.ok?(strict: strict.strict?(to_sym))
204
- end
205
- end
206
-
207
- class Undefined < Raisable
208
- include Result.query_methods :undefined
209
-
210
- def self.ok?(strict: false)
211
- !strict
212
- end
213
-
214
- def describe_to(visitor, *)
215
- visitor.undefined(*)
216
- visitor.duration(duration, *)
217
- self
218
- end
219
-
220
- def to_s
221
- '?'
222
- end
223
-
224
- def to_message
225
- Cucumber::Messages::TestStepResult.new(
226
- status: Cucumber::Messages::TestStepResultStatus::UNDEFINED,
227
- duration: duration.to_message_duration
228
- )
229
- end
230
- end
231
-
232
- class Skipped < Raisable
233
- include Result.query_methods :skipped
234
-
235
- def self.ok?(*)
236
- true
237
- end
238
-
239
- def describe_to(visitor, *)
240
- visitor.skipped(*)
241
- visitor.duration(duration, *)
242
- self
243
- end
244
-
245
- def to_s
246
- '-'
247
- end
248
-
249
- def to_message
250
- Cucumber::Messages::TestStepResult.new(
251
- status: Cucumber::Messages::TestStepResultStatus::SKIPPED,
252
- duration: duration.to_message_duration
253
- )
254
- end
255
- end
256
-
257
- class Pending < Raisable
258
- include Result.query_methods :pending
259
-
260
- def self.ok?(strict: false)
261
- !strict
262
- end
263
-
264
- def describe_to(visitor, *)
265
- visitor.pending(self, *)
266
- visitor.duration(duration, *)
267
- self
268
- end
269
-
270
- def to_s
271
- 'P'
272
- end
273
-
274
- def to_message
275
- Cucumber::Messages::TestStepResult.new(
276
- status: Cucumber::Messages::TestStepResultStatus::PENDING,
277
- duration: duration.to_message_duration
278
- )
279
- end
280
- end
281
-
282
- # Handles the strict settings for the result types that are
283
- # affected by the strict options (that is the STRICT_AFFECTED_TYPES).
284
- class StrictConfiguration
285
- attr_accessor :settings
286
- private :settings
287
-
288
- def initialize(strict_types = [])
289
- @settings = STRICT_AFFECTED_TYPES.to_h { |t| [t, :default] }
290
- strict_types.each do |type|
291
- set_strict(true, type)
292
- end
293
- end
294
-
295
- def strict?(type = nil)
296
- if type.nil?
297
- settings.each_value do |value|
298
- return true if value == true
299
- end
300
- false
301
- else
302
- return false unless settings.key?(type)
303
- return false unless set?(type)
304
-
305
- settings[type]
306
- end
307
- end
308
-
309
- def set_strict(setting, type = nil)
310
- if type.nil?
311
- STRICT_AFFECTED_TYPES.each { |type| set_strict(setting, type) }
312
- else
313
- settings[type] = setting
314
- end
315
- end
316
-
317
- def merge!(other)
318
- settings.each_key do |type|
319
- set_strict(other.strict?(type), type) if other.set?(type)
320
- end
321
- self
322
- end
323
-
324
- def set?(type)
325
- settings[type] != :default
326
- end
327
- end
328
-
329
- #
330
- # An object that responds to the description protocol from the results and collects summary information.
331
- #
332
- # e.g.
333
- # summary = Result::Summary.new
334
- # Result::Passed.new(0).describe_to(summary)
335
- # puts summary.total_passed
336
- # => 1
337
- #
338
- class Summary
339
- attr_reader :exceptions, :durations
340
-
341
- def initialize
342
- @totals = Hash.new { 0 }
343
- @exceptions = []
344
- @durations = []
345
- end
346
-
347
- def method_missing(name, *_args)
348
- if name =~ /^total_/
349
- get_total(name)
350
- else
351
- increment_total(name)
352
- end
353
- end
354
-
355
- def respond_to_missing?(*)
356
- true
357
- end
358
-
359
- def ok?(strict: StrictConfiguration.new)
360
- TYPES.each do |type|
361
- return false if get_total(type).positive? && !Result.ok?(type, strict: strict)
362
- end
363
- true
364
- end
365
-
366
- def exception(exception)
367
- @exceptions << exception
368
- self
369
- end
370
-
371
- def duration(duration)
372
- @durations << duration
373
- self
374
- end
375
-
376
- def total(for_status = nil)
377
- if for_status
378
- @totals.fetch(for_status, 0)
379
- else
380
- @totals.values.reduce(0) { |total, count| total + count }
381
- end
382
- end
383
-
384
- def decrement_failed
385
- @totals[:failed] -= 1
386
- end
387
-
388
- private
389
-
390
- def get_total(method_name)
391
- status = method_name.to_s.gsub('total_', '').to_sym
392
- @totals.fetch(status, 0)
393
- end
394
-
395
- def increment_total(status)
396
- @totals[status] += 1
397
- self
398
- end
399
- end
400
-
401
- class Duration
402
- include Cucumber::Messages::Helpers::TimeConversion
403
-
404
- attr_reader :nanoseconds
405
-
406
- def initialize(nanoseconds)
407
- @nanoseconds = nanoseconds
408
- end
409
-
410
- def to_message_duration
411
- duration_hash = seconds_to_duration(nanoseconds.to_f / NANOSECONDS_PER_SECOND)
412
- Cucumber::Messages::Duration.new(seconds: duration_hash[:seconds], nanos: duration_hash[:nanos])
413
- end
414
-
415
- def seconds_to_duration(seconds_float)
416
- seconds, second_modulus = seconds_float.divmod(1)
417
- nanos = second_modulus * NANOSECONDS_PER_SECOND
418
- { seconds: seconds, nanos: nanos.to_i }
419
- end
420
- end
421
-
422
- class UnknownDuration
423
- def tap
424
- self
425
- end
426
-
427
- def nanoseconds
428
- raise '#nanoseconds only allowed to be used in #tap block'
429
- end
430
-
431
- def to_message_duration
432
- Cucumber::Messages::Duration.new(seconds: 0, nanos: 0)
433
- end
434
- end
435
- end
436
- end
437
- end
438
- end
6
+ require_relative 'result/boolean_methods'
7
+
8
+ require_relative 'result/raisable'
9
+
10
+ require_relative 'result/ambiguous'
11
+ require_relative 'result/duration'
12
+ require_relative 'result/failed'
13
+ require_relative 'result/flaky'
14
+ require_relative 'result/passed'
15
+ require_relative 'result/pending'
16
+ require_relative 'result/summary'
17
+ require_relative 'result/skipped'
18
+ require_relative 'result/undefined'
19
+ require_relative 'result/unknown'
20
+ require_relative 'result/unknown_duration'