theorem 1.0.0 → 1.2.1

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