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
@@ -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,461 +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 ambiguous 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 Ambiguous < Raisable
208
- include Result.query_methods :ambiguous
209
-
210
- def self.ok?(*)
211
- false
212
- end
213
-
214
- def describe_to(visitor, *)
215
- visitor.ambiguous(*)
216
- visitor.duration(duration, *)
217
- self
218
- end
219
-
220
- def to_s
221
- 'A'
222
- end
223
-
224
- def to_message
225
- Cucumber::Messages::TestStepResult.new(
226
- status: Cucumber::Messages::TestStepResultStatus::AMBIGUOUS,
227
- duration: duration.to_message_duration
228
- )
229
- end
230
- end
231
-
232
- class Undefined < Raisable
233
- include Result.query_methods :undefined
234
-
235
- def self.ok?(strict: false)
236
- !strict
237
- end
238
-
239
- def describe_to(visitor, *)
240
- visitor.undefined(*)
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::UNDEFINED,
252
- duration: duration.to_message_duration
253
- )
254
- end
255
- end
256
-
257
- class Skipped < Raisable
258
- include Result.query_methods :skipped
259
-
260
- def self.ok?(*)
261
- true
262
- end
263
-
264
- def describe_to(visitor, *)
265
- visitor.skipped(*)
266
- visitor.duration(duration, *)
267
- self
268
- end
269
-
270
- def to_s
271
- '-'
272
- end
273
-
274
- def to_message
275
- Cucumber::Messages::TestStepResult.new(
276
- status: Cucumber::Messages::TestStepResultStatus::SKIPPED,
277
- duration: duration.to_message_duration
278
- )
279
- end
280
- end
281
-
282
- class Pending < Raisable
283
- include Result.query_methods :pending
284
-
285
- def self.ok?(strict: false)
286
- !strict
287
- end
288
-
289
- def describe_to(visitor, *)
290
- visitor.pending(self, *)
291
- visitor.duration(duration, *)
292
- self
293
- end
294
-
295
- def to_s
296
- 'P'
297
- end
298
-
299
- def to_message
300
- Cucumber::Messages::TestStepResult.new(
301
- status: Cucumber::Messages::TestStepResultStatus::PENDING,
302
- duration: duration.to_message_duration
303
- )
304
- end
305
- end
306
-
307
- # Handles the strict settings for the result types that are
308
- # affected by the strict options (that is the STRICT_AFFECTED_TYPES).
309
- class StrictConfiguration
310
- attr_accessor :settings
311
- private :settings
312
-
313
- def initialize(strict_types = [])
314
- @settings = STRICT_AFFECTED_TYPES.to_h { |t| [t, :default] }
315
- strict_types.each do |type|
316
- set_strict(true, type)
317
- end
318
- end
319
-
320
- def strict?(type = nil)
321
- if type.nil?
322
- settings.each_value do |value|
323
- return true if value == true
324
- end
325
- false
326
- else
327
- return false unless settings.key?(type)
328
- return false unless set?(type)
329
-
330
- settings[type]
331
- end
332
- end
333
-
334
- def set_strict(setting, type = nil)
335
- if type.nil?
336
- STRICT_AFFECTED_TYPES.each { |type| set_strict(setting, type) }
337
- else
338
- settings[type] = setting
339
- end
340
- end
341
-
342
- def merge!(other)
343
- settings.each_key do |type|
344
- set_strict(other.strict?(type), type) if other.set?(type)
345
- end
346
- self
347
- end
348
-
349
- def set?(type)
350
- settings[type] != :default
351
- end
352
- end
353
-
354
- #
355
- # An object that responds to the description protocol from the results and collects summary information.
356
- #
357
- # e.g.
358
- # summary = Result::Summary.new
359
- # Result::Passed.new(0).describe_to(summary)
360
- # puts summary.total_passed
361
- # => 1
362
- #
363
- class Summary
364
- attr_reader :exceptions, :durations
365
-
366
- def initialize
367
- @totals = Hash.new { 0 }
368
- @exceptions = []
369
- @durations = []
370
- end
371
-
372
- def method_missing(name, *_args)
373
- if name =~ /^total_/
374
- get_total(name)
375
- else
376
- increment_total(name)
377
- end
378
- end
379
-
380
- def respond_to_missing?(*)
381
- true
382
- end
383
-
384
- def ok?(strict: StrictConfiguration.new)
385
- TYPES.each do |type|
386
- return false if get_total(type).positive? && !Result.ok?(type, strict: strict)
387
- end
388
- true
389
- end
390
-
391
- def exception(exception)
392
- @exceptions << exception
393
- self
394
- end
395
-
396
- def duration(duration)
397
- @durations << duration
398
- self
399
- end
400
-
401
- def total(for_status = nil)
402
- if for_status
403
- @totals.fetch(for_status, 0)
404
- else
405
- @totals.values.reduce(0) { |total, count| total + count }
406
- end
407
- end
408
-
409
- def decrement_failed
410
- @totals[:failed] -= 1
411
- end
412
-
413
- private
414
-
415
- def get_total(method_name)
416
- status = method_name.to_s.gsub('total_', '').to_sym
417
- @totals.fetch(status, 0)
418
- end
419
-
420
- def increment_total(status)
421
- @totals[status] += 1
422
- self
423
- end
424
- end
425
-
426
- class Duration
427
- include Cucumber::Messages::Helpers::TimeConversion
428
-
429
- attr_reader :nanoseconds
430
-
431
- def initialize(nanoseconds)
432
- @nanoseconds = nanoseconds
433
- end
434
-
435
- def to_message_duration
436
- duration_hash = seconds_to_duration(nanoseconds.to_f / NANOSECONDS_PER_SECOND)
437
- Cucumber::Messages::Duration.new(seconds: duration_hash[:seconds], nanos: duration_hash[:nanos])
438
- end
439
-
440
- def seconds_to_duration(seconds_float)
441
- seconds, second_modulus = seconds_float.divmod(1)
442
- nanos = second_modulus * NANOSECONDS_PER_SECOND
443
- { seconds: seconds, nanos: nanos.to_i }
444
- end
445
- end
446
-
447
- class UnknownDuration
448
- def tap
449
- self
450
- end
451
-
452
- def nanoseconds
453
- raise '#nanoseconds only allowed to be used in #tap block'
454
- end
455
-
456
- def to_message_duration
457
- Cucumber::Messages::Duration.new(seconds: 0, nanos: 0)
458
- end
459
- end
460
- end
461
- end
462
- end
463
- 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'