theorem 1.0.0 → 1.2.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91647688dc2f743ee8d64461f26ee5f2265be617c82623a97e0cfd7cdec58aa8
4
- data.tar.gz: 410086697fb9d6718c3f05a64cbac277b59fa4ced15cf679484b9eed11bb9edf
3
+ metadata.gz: '09d0acbeb340968a19cb27122ba6c852b66b2cc4347fdfca7f6ba6a9f7781ce0'
4
+ data.tar.gz: aef702741a59ff1a11f08e6225b0804b30b813ed0cc8eb5184cdd50c54587901
5
5
  SHA512:
6
- metadata.gz: f66749de7a52cee707d0c149e7e9b273d05b46248a69e7acb4f3669d400e6a833fcac9d268eb0f87acd6f5b3cbc9d8c5bfaa836491d705c8666563ae9dfe6fe8
7
- data.tar.gz: dc737567dc4303c362f5dcb3bfa6a75474981d6b355367b6d8d711c4ad5170c8f3e0c0cf252197f4007ef16f9e403234f75277559e3d9ff8ce908cc5faff94e9
6
+ metadata.gz: 6055b29fd80529f514b73363a18582122a5e23b312fda318b3d83a546bb7d8b062fcaf87f57858340e432bc1327a183f1b494c107d0970861b9548974f34996f
7
+ data.tar.gz: 40c0dc5d7660db43bdf6606aae81a43c48cb5015dd29eacd63a4f2afc2e0daa2d70b34f1ce04560f190d2dd282caef3bc67d4c37236c5bcda3abfce5bed65aad
data/bin/theorize CHANGED
@@ -9,10 +9,13 @@ opts = Slop.parse do |o|
9
9
  o.string '-h', '--harness', 'harness (default: Theorem::Harness)', default: 'Theorem::Harness'
10
10
  o.array '-p', '--publisher', 'publishers (default: Theorem::StdoutReporter)', delimiter: ',', default: ['Theorem::StdoutReporter']
11
11
  o.string '-d', '--directory', 'directory to load tests (default: ".")', default: '.'
12
+ o.array '-i', '--include', 'tags to specify what tests should be included (default: wont filter)', default: []
13
+ o.array '-e', '--exclude', 'tags the specify what tests should be excluded (default: wont filter)', default: []
14
+ o.string '--meta', 'string of metadata to pass to the harness', default: ''
12
15
  o.on '--help' do
13
16
  puts o
14
17
  exit
15
18
  end
16
19
  end
17
20
 
18
- Theorem.run! opts
21
+ Theorem.run! opts.to_h
data/src/harness.rb CHANGED
@@ -7,27 +7,13 @@ module Theorem
7
7
  # default test harness
8
8
  module Harness
9
9
  include Control::Harness
10
- end
11
-
12
- # module retry harness
13
- module RetryHarness
14
- include Control::Harness
15
10
 
16
- on_run do |tests|
17
- arr_of_tests = tests.map { |test| { test: test, index: 0 } }
11
+ load_tests do |options|
12
+ directory = options[:directory] || '.'
18
13
 
19
- final_results = []
20
- arr_of_tests.each do |test|
21
- test[:index] += 1
22
- results = test[:test].run!
23
- if results.any?(&:failed?) && test[:index] <= 3
24
- puts "Retrying iteration: #{test[:index]}\n#{results.map(&:full_name).join("\n")}"
25
- redo
26
- end
27
- final_results.concat results
28
- end
14
+ ExtendedDir.require_all("./#{directory}")
29
15
 
30
- final_results
16
+ filtered_registry(options)
31
17
  end
32
18
  end
33
19
  end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../theorem/reporter'
4
+
5
+ module Theorem
6
+ module StringRefinements
7
+ refine String do
8
+ # colorization
9
+ def colorize(color_code)
10
+ "\e[#{color_code}m#{self}\e[0m"
11
+ end
12
+
13
+ def red
14
+ colorize(31)
15
+ end
16
+
17
+ def green
18
+ colorize(32)
19
+ end
20
+
21
+ def yellow
22
+ colorize(33)
23
+ end
24
+
25
+ def blue
26
+ colorize(34)
27
+ end
28
+
29
+ def pink
30
+ colorize(35)
31
+ end
32
+
33
+ def light_blue
34
+ colorize(36)
35
+ end
36
+ end
37
+ end
38
+
39
+ # Default Stdout reporter
40
+ module StdoutReporter
41
+ extend Control::Reporter
42
+ using StringRefinements
43
+
44
+ subscribe :test_finished do |test|
45
+ print test.failed? ? 'x'.red : '.'.green
46
+ end
47
+
48
+ subscribe :suite_finished do |results, duration|
49
+ puts "\n"
50
+ report_summary(results)
51
+
52
+ failed_tests = results.select(&:failed?)
53
+
54
+ report_failures(failed_tests)
55
+
56
+ puts "\nTotal time: #{duration} seconds"
57
+ puts "Total tests: #{results.size}\n"
58
+ end
59
+
60
+ def inflate_percentiles(tests)
61
+ sorted = tests.reject { |t| t.duration.nil? }.sort_by(&:duration)
62
+ tests.each_with_object([]) do |test, arr|
63
+ _, below = sorted.partition do |duration_test|
64
+ if test.duration.nil?
65
+ true
66
+ else
67
+ test.duration >= duration_test.duration
68
+ end
69
+ end
70
+ hash = {}
71
+ hash[:percentile] = (below.size.to_f / sorted.size.to_f) * 100
72
+ hash[:test] = test
73
+ arr << hash
74
+ end
75
+ end
76
+
77
+ def report_summary(tests)
78
+ inflated = inflate_percentiles(tests)
79
+
80
+ top_20 = 80 / (100.0 * (tests.size + 1).to_f)
81
+ lowest_20 = 20 / (100.0 * (tests.size + 1).to_f)
82
+
83
+ inflated.each do |test|
84
+ icon = test[:test].failed? ? '❌'.red : '✓'.green
85
+ puts "#{icon} #{test[:test].full_name.blue} : #{duration(test)}"
86
+ end
87
+ end
88
+
89
+ def duration(test)
90
+ return 'Not run' if test[:test].duration.nil?
91
+
92
+ str = "#{format('%<num>0.10f', num: test[:test].duration)} seconds"
93
+ rank = ((test[:percentile] / 100) * (test[:test].duration + 1)) * 100
94
+ if rank < 5
95
+ str.red
96
+ elsif rank > 95
97
+ str.green
98
+ elsif rank < 10
99
+ str.yellow
100
+ else
101
+ str
102
+ end
103
+ end
104
+
105
+ def report_failures(tests)
106
+ tests.each do |failure|
107
+ puts "\n\nFailure in #{failure.full_name}\nError: #{failure.error.message.to_s.red}\nBacktrace:\n------\n#{failure.error.backtrace.map(&:red).join("\n")}"
108
+ end
109
+ end
110
+ end
111
+ end
@@ -9,6 +9,10 @@ module Theorem
9
9
  @ctx = ctx
10
10
  end
11
11
 
12
+ def name
13
+ @test.name
14
+ end
15
+
12
16
  def run!
13
17
  @test.run!(@ctx)
14
18
  end
@@ -54,6 +58,10 @@ module Theorem
54
58
  end
55
59
  end
56
60
 
61
+ def empty?
62
+ @state.empty?
63
+ end
64
+
57
65
  def reverse_prepare(&block)
58
66
  @state.unshift block
59
67
  end
@@ -4,11 +4,14 @@ module Theorem
4
4
  module Control
5
5
  # error class
6
6
  class CompletedTest
7
- attr_reader :test, :error
7
+ attr_reader :test, :duration
8
+ attr_accessor :error, :notary
8
9
 
9
- def initialize(test, error = nil)
10
+ def initialize(test, error = nil, notary:, duration: nil)
10
11
  @test = test
11
12
  @error = error
13
+ @notary = notary
14
+ @duration = duration
12
15
  end
13
16
 
14
17
  def full_name
@@ -11,9 +11,21 @@ module Theorem
11
11
  mod.define_singleton_method :included do |inner|
12
12
  inner.define_singleton_method :run! do |options: {}|
13
13
  tests = inner.instance_exec options, &mod.test_loader
14
+
15
+ inner.suite_started_subscribers.each do |subscriber|
16
+ subscriber.call tests.map(&:tests).flatten.map do |test|
17
+ { name: test.name, metadata: test.metadata }
18
+ end
19
+ end
20
+
21
+ starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
14
22
  results = inner.instance_exec tests, options, &mod.run_loader
15
- inner.completed_suite_subscribers.each do |subscriber|
16
- subscriber.call(results)
23
+ ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
24
+
25
+ duration = ending - starting
26
+
27
+ inner.suite_finished_subscribers.each do |subscriber|
28
+ subscriber.call(results, duration)
17
29
  end
18
30
  exit results.any?(&:failed?) ? 1 : 0
19
31
  end
@@ -22,19 +34,6 @@ module Theorem
22
34
 
23
35
  # harness helpers
24
36
  module ClassMethods
25
- DEFAULT_LOADER = ->(options) do
26
- directory = options[:directory] || '.'
27
-
28
- ExtendedDir.require_all("./#{directory}")
29
-
30
- registry
31
- end
32
-
33
- DEFAULT_RUNNER = ->(tests, options) do
34
- tests.each_with_object([]) do |test, memo|
35
- memo.concat test.run!
36
- end
37
- end
38
37
 
39
38
  def load_tests(&block)
40
39
  @on_load_tests = block
@@ -45,11 +44,31 @@ module Theorem
45
44
  end
46
45
 
47
46
  def run_loader
48
- @on_run || DEFAULT_RUNNER
47
+ @on_run || default_runner
49
48
  end
50
49
 
51
50
  def test_loader
52
- @on_load_tests || DEFAULT_LOADER
51
+ @on_load_tests || default_loader
52
+ end
53
+
54
+ private
55
+
56
+ def default_loader
57
+ lambda do |options|
58
+ directory = options[:directory] || '.'
59
+
60
+ ExtendedDir.require_all("./#{directory}")
61
+
62
+ registry
63
+ end
64
+ end
65
+
66
+ def default_runner
67
+ lambda do |tests, options|
68
+ tests.each_with_object([]) do |test, memo|
69
+ memo.concat test.run!
70
+ end
71
+ end
53
72
  end
54
73
  end
55
74
  end
@@ -15,6 +15,16 @@ module Theorem
15
15
  mod
16
16
  end
17
17
 
18
+ klass.attr_reader :notary
19
+
20
+ klass.define_method :initialize do
21
+ @notary = Notation.new
22
+ end
23
+
24
+ klass.define_method :notate do |&block|
25
+ block.call(@notary)
26
+ end
27
+
18
28
  klass.extend ClassMethods
19
29
  klass.instance_eval do
20
30
  @before_all ||= Beaker.new
@@ -29,9 +39,9 @@ module Theorem
29
39
  mod.add_to_registry(klass)
30
40
  end
31
41
 
32
- mod.const_set(:Beaker, Beaker)
33
- mod.const_set(:Test, Test)
34
- mod.const_set(:CompletedTest, CompletedTest)
42
+ mod.const_set(:Beaker, Beaker) unless mod.const_defined?(:Beaker)
43
+ mod.const_set(:Test, Test) unless mod.const_defined?(:Test)
44
+ mod.const_set(:CompletedTest, CompletedTest) unless mod.const_defined?(:CompletedTest)
35
45
  mod.extend(Registry)
36
46
  end
37
47
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Theorem
4
+ module Control
5
+ class Notation
6
+ def initialize(state = {})
7
+ @state = state
8
+ end
9
+
10
+ def write(key, value)
11
+ @state[key] = value
12
+ end
13
+
14
+ def read(key)
15
+ @state[key]
16
+ end
17
+
18
+ def dump
19
+ @state
20
+ end
21
+
22
+ def merge(notary)
23
+ Notation.new(@state.merge(notary.dump))
24
+ end
25
+
26
+ def edit(key, &block)
27
+ data = read(key)
28
+ block.call(data)
29
+ write(key, data)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -8,35 +8,37 @@ module Theorem
8
8
  @registry ||= []
9
9
  end
10
10
 
11
- def add_to_registry(klass)
12
- registry << klass
13
- end
11
+ def filtered_registry(options)
12
+ registry.each do |test_class|
13
+ if options[:include]&.any?
14
+ test_class.tests.select! do |test|
15
+ test.metadata[:tags]&.intersection(options[:include])&.any?
16
+ end
17
+ end
14
18
 
15
- def on_extra_event(&block)
16
- @extra_events ||= []
17
- @extra_events << block
18
- end
19
+ next unless options[:exclude]&.any?
19
20
 
20
- def extra_event_subscribers
21
- @extra_events || []
21
+ test_class.tests.reject! do |test|
22
+ test.metadata[:tags]&.intersection(options[:include])&.any?
23
+ end
24
+ end
22
25
  end
23
26
 
24
- def on_completed_test(&block)
25
- @completed_tests ||= []
26
- @completed_tests << block
27
+ def add_to_registry(klass)
28
+ registry << klass
27
29
  end
28
30
 
29
- def completed_test_subscribers
30
- @completed_tests || []
31
- end
31
+ %i[suite_started test_started test_finished suite_finished].each do |method|
32
+ define_method method do |&block|
33
+ instance_variable_set("@#{method}_subscribers", []) unless instance_variable_get("@#{method}_subscribers")
34
+ instance_variable_get("@#{method}_subscribers").append(block)
35
+ end
32
36
 
33
- def on_completed_suite(&block)
34
- @completed_suites ||= []
35
- @completed_suites << block
36
- end
37
+ define_method "#{method}_subscribers" do
38
+ return [] unless instance_variable_get("@#{method}_subscribers")
37
39
 
38
- def completed_suite_subscribers
39
- @completed_suites || []
40
+ instance_variable_get("@#{method}_subscribers")
41
+ end
40
42
  end
41
43
  end
42
44
  end
data/src/theorem/test.rb CHANGED
@@ -1,23 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative './beaker'
3
+ require_relative 'notation'
3
4
 
4
5
  module Theorem
5
6
  module Control
6
7
  # test new
7
8
  class Test
8
- def initialize(name, namespace, **opts, &block)
9
+ def initialize(name, namespace, arguments: {}, **metadata, &block)
9
10
  @name = name
10
11
  @namespace = namespace
11
12
  @block = block
12
- @arguments = opts
13
+ @arguments = arguments
14
+ @metadata = metadata
15
+ @notary = Notation.new
13
16
  end
14
17
 
15
- attr_reader :block, :name, :arguments, :namespace
18
+ attr_reader :block, :name, :arguments, :namespace, :metadata, :notary
16
19
 
17
20
  def full_name
18
21
  "#{namespace} #{name}"
19
22
  end
20
23
 
24
+ def notate(&block)
25
+ block.call(notary)
26
+ end
27
+
21
28
  def run!(ctx)
22
29
  ctx.instance_exec self, **arguments, &block
23
30
  end
@@ -68,7 +75,7 @@ module Theorem
68
75
  obj.include(control)
69
76
  obj.instance_eval &block if block
70
77
  obj.instance_exec self, klass, opts do |consumer, experiment_klass, params|
71
- @tests.concat experiment_klass.tests(_experiment_namespace: consumer.to_s, **params)
78
+ @tests.concat experiment_klass.tests(_experiment_namespace: consumer.to_s, arguments: params)
72
79
  end
73
80
  end
74
81
 
@@ -92,39 +99,74 @@ module Theorem
92
99
  @after_all
93
100
  end
94
101
 
95
- def test(name, &block)
96
- @tests << Test.new(name, to_s, &block)
102
+ def test(name, **hargs, &block)
103
+ @tests << Test.new(name, to_s, **hargs, &block)
97
104
  end
98
105
 
99
106
  def run!
100
107
  test_case = new
101
108
 
109
+ # run before all beakers to create state in test case
102
110
  before_failures = run_before_all_beakers(test_case)
111
+
103
112
  if before_failures.any?
113
+ before_failures.each do |failure|
114
+ publish_test_completion(failure)
115
+ end
104
116
  return before_failures
105
117
  end
106
118
 
119
+ # duplicate the before_all arrangement for the after all hook
120
+ duplicate_test_case = test_case.clone
121
+
107
122
  results = []
108
123
  @tests.each do |test|
124
+ test_start = clock_time
125
+
126
+ publish_test_start(test)
127
+
109
128
  error ||= run_before_each_beakers(test_case)
110
- error ||= run_test(test, test_case)
111
- error ||= run_after_each_beakers(test_case)
112
129
 
113
- completed_test = CompletedTest.new(test, error)
114
- publish_test_completion(completed_test)
130
+ before_test_case = test_case.clone
131
+ error ||= run_test(test, before_test_case)
132
+ error ||= run_after_each_beakers(before_test_case)
133
+
134
+ notary = test_case.notary.merge(test.notary)
135
+
136
+ duration = clock_time - test_start
137
+
138
+ completed_test = CompletedTest.new(test, error, duration: duration, notary: notary.dump)
139
+
140
+ # publish_early if there are no after_all beakers
141
+ publish_test_completion(completed_test) if @after_all.empty?
142
+
115
143
  results << completed_test
116
144
  end
117
145
 
118
- after_failures = run_after_all_beakers(test_case)
146
+ after_failures = run_after_all_beakers(results, duplicate_test_case)
147
+
119
148
  if after_failures.any?
149
+ after_failures.each do |failure|
150
+ publish_test_completion(failure)
151
+ end
120
152
  return after_failures
121
153
  end
122
154
 
155
+ results.each do |completed_test|
156
+ # merge any after_all notations
157
+ completed_test.notary.merge!(duplicate_test_case.notary.dump)
158
+ publish_test_completion(completed_test) unless @after_all.empty?
159
+ end
160
+
123
161
  results
124
162
  end
125
163
 
126
164
  private
127
165
 
166
+ def clock_time
167
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
168
+ end
169
+
128
170
  def run_test(test, test_case)
129
171
  if @around.empty?
130
172
  begin
@@ -140,7 +182,7 @@ module Theorem
140
182
  end
141
183
  end
142
184
 
143
- def run_after_all_beakers(test_case)
185
+ def run_after_all_beakers(results, test_case)
144
186
  @after_all.run!(test_case)
145
187
 
146
188
  @parent_after_all&.each do |beaker|
@@ -151,9 +193,12 @@ module Theorem
151
193
  rescue Exception => error
152
194
  Theorem.handle_exception(error)
153
195
 
154
- @tests.map do |test|
155
- CompletedTest.new(test, error)
196
+ results.each do |test|
197
+ test.error = error
198
+ test.notary = test_case.notary
156
199
  end
200
+
201
+ results
157
202
  end
158
203
 
159
204
  def run_after_each_beakers(test_case)
@@ -191,16 +236,21 @@ module Theorem
191
236
  Theorem.handle_exception(error)
192
237
 
193
238
  @tests.map do |test|
194
- CompletedTest.new(test, error)
239
+ CompletedTest.new(test, error, notary: test_case.notary)
195
240
  end
196
241
  end
197
242
 
198
243
  def publish_test_completion(completed_test)
199
- control.completed_test_subscribers.each do |subscriber|
244
+ control.test_finished_subscribers.each do |subscriber|
200
245
  subscriber.call(completed_test)
201
246
  end
202
247
  end
203
248
 
249
+ def publish_test_start(test)
250
+ control.test_started_subscribers.each do |subscriber|
251
+ subscriber.call(test)
252
+ end
253
+ end
204
254
  end
205
255
  end
206
256
  end
data/src/theorem.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  require_relative 'theorem/hypothesis'
2
2
  require_relative 'harness'
3
3
  require_relative 'experiment'
4
- require_relative 'stdout_reporter'
4
+ require_relative 'publishers/stdout_reporter'
5
+ require_relative 'theorem/harness'
5
6
  require 'json'
6
7
 
7
8
  module Theorem
@@ -43,18 +44,6 @@ module Theorem
43
44
  mod.run!(options: options)
44
45
  end
45
46
 
46
- module JsonReporter
47
- extend Control::Reporter
48
-
49
- subscribe :on_completed_suite do |results|
50
- results = results.map do |result|
51
- { name: result.full_name, failed: result.failed? }
52
- end
53
- puts results.to_json
54
- puts "\n\n"
55
- end
56
- end
57
-
58
47
  module Hypothesis
59
48
  include Control::Hypothesis
60
49
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: theorem
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Gregory
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-09 00:00:00.000000000 Z
11
+ date: 2022-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: extended_dir
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov-cobertura
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  description: simple and extensible test library toolkit
84
112
  email: sean.christopher.gregory@gmail.com
85
113
  executables:
@@ -90,12 +118,13 @@ files:
90
118
  - bin/theorize
91
119
  - src/experiment.rb
92
120
  - src/harness.rb
93
- - src/stdout_reporter.rb
121
+ - src/publishers/stdout_reporter.rb
94
122
  - src/theorem.rb
95
123
  - src/theorem/beaker.rb
96
124
  - src/theorem/completed_test.rb
97
125
  - src/theorem/harness.rb
98
126
  - src/theorem/hypothesis.rb
127
+ - src/theorem/notation.rb
99
128
  - src/theorem/registry.rb
100
129
  - src/theorem/reporter.rb
101
130
  - src/theorem/test.rb
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative './theorem/reporter'
4
-
5
- module Theorem
6
- module StringRefinements
7
- refine String do
8
- # colorization
9
- def colorize(color_code)
10
- "\e[#{color_code}m#{self}\e[0m"
11
- end
12
-
13
- def red
14
- colorize(31)
15
- end
16
-
17
- def green
18
- colorize(32)
19
- end
20
-
21
- def yellow
22
- colorize(33)
23
- end
24
-
25
- def blue
26
- colorize(34)
27
- end
28
-
29
- def pink
30
- colorize(35)
31
- end
32
-
33
- def light_blue
34
- colorize(36)
35
- end
36
- end
37
- end
38
-
39
- # Default Stdout reporter
40
- module StdoutReporter
41
- extend Control::Reporter
42
- using StringRefinements
43
-
44
- subscribe :on_completed_test do |test|
45
- print test.failed? ? 'x'.red : '.'.green
46
- end
47
-
48
- subscribe :on_completed_suite do |results|
49
- puts "\n"
50
- report_summary(results)
51
-
52
- failed_tests = results.select(&:failed?)
53
-
54
- report_failures(failed_tests)
55
- end
56
-
57
- def report_summary(tests)
58
- tests.each do |test|
59
- icon = test.failed? ? '❌'.red : '✓'.green
60
- puts "#{icon} #{test.full_name.blue}"
61
- end
62
- end
63
-
64
- def report_failures(tests)
65
- tests.each do |failure|
66
- puts "Failure in #{failure.full_name}\nError: #{failure.error.message.to_s.red}\nBacktrace:\n------\n#{failure.error.backtrace.map(&:red).join("\n")}"
67
- end
68
- end
69
- end
70
- end