minitest-heat 0.0.1 → 0.0.5
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 +4 -4
- data/Gemfile.lock +3 -1
- data/lib/minitest/heat/backtrace.rb +48 -16
- data/lib/minitest/heat/issue.rb +36 -16
- data/lib/minitest/heat/location.rb +133 -22
- data/lib/minitest/heat/map.rb +7 -6
- data/lib/minitest/heat/output/backtrace.rb +131 -0
- data/lib/minitest/heat/output/issue.rb +18 -0
- data/lib/minitest/heat/output/location.rb +20 -0
- data/lib/minitest/heat/output/map.rb +20 -0
- data/lib/minitest/heat/output/results.rb +106 -0
- data/lib/minitest/heat/output/source_code.rb +131 -0
- data/lib/minitest/heat/output/token.rb +99 -0
- data/lib/minitest/heat/output.rb +71 -153
- data/lib/minitest/heat/results.rb +8 -10
- data/lib/minitest/heat/source.rb +6 -1
- data/lib/minitest/heat/version.rb +1 -1
- data/lib/minitest/heat_reporter.rb +14 -11
- data/minitest-heat.gemspec +1 -0
- metadata +23 -2
data/lib/minitest/heat/output.rb
CHANGED
@@ -1,94 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'output/backtrace'
|
4
|
+
require_relative 'output/issue'
|
5
|
+
require_relative 'output/location'
|
6
|
+
require_relative 'output/map'
|
7
|
+
require_relative 'output/results'
|
8
|
+
require_relative 'output/source_code'
|
9
|
+
require_relative 'output/token'
|
10
|
+
|
3
11
|
module Minitest
|
4
12
|
module Heat
|
5
13
|
# Friendly API for printing nicely-formatted output to the console
|
6
14
|
class Output
|
7
|
-
Token = Struct.new(:style, :content) do
|
8
|
-
STYLES = {
|
9
|
-
error: %i[bold red],
|
10
|
-
broken: %i[bold red],
|
11
|
-
failure: %i[default red],
|
12
|
-
skipped: %i[bold yellow],
|
13
|
-
success: %i[default green],
|
14
|
-
slow: %i[bold green],
|
15
|
-
source: %i[italic default],
|
16
|
-
bold: %i[bold default],
|
17
|
-
default: %i[default default],
|
18
|
-
subtle: %i[light white],
|
19
|
-
muted: %i[light gray],
|
20
|
-
}.freeze
|
21
|
-
|
22
|
-
WEIGHTS = {
|
23
|
-
default: 0,
|
24
|
-
bold: 1,
|
25
|
-
light: 2,
|
26
|
-
italic: 3,
|
27
|
-
underline: 4,
|
28
|
-
frame: 51,
|
29
|
-
encircle: 52,
|
30
|
-
overline: 53,
|
31
|
-
}.freeze
|
32
|
-
|
33
|
-
COLORS = {
|
34
|
-
black: 30,
|
35
|
-
red: 31,
|
36
|
-
green: 32,
|
37
|
-
yellow: 33,
|
38
|
-
blue: 34,
|
39
|
-
magenta: 35,
|
40
|
-
cyan: 36,
|
41
|
-
gray: 37, white: 97,
|
42
|
-
default: 39,
|
43
|
-
}.freeze
|
44
|
-
|
45
|
-
def to_s
|
46
|
-
"\e[#{weight};#{color}m#{content}#{reset}"
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def weight
|
52
|
-
WEIGHTS.fetch(style_components[0])
|
53
|
-
end
|
54
|
-
|
55
|
-
def color
|
56
|
-
COLORS.fetch(style_components[1])
|
57
|
-
end
|
58
|
-
|
59
|
-
def reset
|
60
|
-
"\e[0m"
|
61
|
-
end
|
62
|
-
|
63
|
-
def style_components
|
64
|
-
STYLES[style]
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
15
|
FORMATTERS = {
|
69
16
|
error: [
|
70
|
-
[ %i[error label], %i[muted spacer], %i[
|
71
|
-
[ %i[
|
17
|
+
[ %i[error label], %i[muted spacer], %i[default test_name] ],
|
18
|
+
[ %i[italicized summary], ],
|
72
19
|
[ %i[default backtrace_summary] ],
|
73
20
|
],
|
74
21
|
broken: [
|
75
|
-
[ %i[broken label], %i[muted spacer], %i[
|
76
|
-
[ %i[
|
22
|
+
[ %i[broken label], %i[muted spacer], %i[default test_name], %i[muted spacer], %i[muted test_class] ],
|
23
|
+
[ %i[italicized summary], ],
|
77
24
|
[ %i[default backtrace_summary] ],
|
78
25
|
],
|
79
26
|
failure: [
|
80
|
-
[ %i[failure label], %i[muted spacer], %i[
|
81
|
-
[ %i[
|
82
|
-
[ %i[
|
27
|
+
[ %i[failure label], %i[muted spacer], %i[default test_name], %i[muted spacer], %i[muted test_class] ],
|
28
|
+
[ %i[italicized summary] ],
|
29
|
+
[ %i[muted short_location], ],
|
83
30
|
[ %i[default source_summary], ],
|
84
31
|
],
|
85
32
|
skipped: [
|
86
|
-
[ %i[skipped label], %i[muted spacer], %i[
|
87
|
-
[ %i[
|
33
|
+
[ %i[skipped label], %i[muted spacer], %i[default test_name], %i[muted spacer], %i[muted test_class] ],
|
34
|
+
[ %i[italicized summary] ],
|
88
35
|
[], # New Line
|
89
36
|
],
|
90
37
|
slow: [
|
91
|
-
[ %i[slow label], %i[muted spacer], %i[default
|
38
|
+
[ %i[slow label], %i[muted spacer], %i[default test_name], %i[muted spacer], %i[default test_class] ],
|
92
39
|
[ %i[bold slowness], %i[muted spacer], %i[default location], ],
|
93
40
|
[], # New Line
|
94
41
|
]
|
@@ -112,17 +59,22 @@ module Minitest
|
|
112
59
|
end
|
113
60
|
alias newline puts
|
114
61
|
|
62
|
+
# TOOD: Convert to output class
|
63
|
+
# - This should likely live in the output/issue class
|
64
|
+
# - Add a 'fail_fast' option that shows the issue as soon as the failure occurs
|
115
65
|
def marker(value)
|
116
66
|
case value
|
117
67
|
when 'E' then text(:error, value)
|
118
68
|
when 'B' then text(:failure, value)
|
119
69
|
when 'F' then text(:failure, value)
|
120
70
|
when 'S' then text(:skipped, value)
|
121
|
-
when 'T' then text(:slow, value)
|
122
71
|
else text(:success, value)
|
123
72
|
end
|
124
73
|
end
|
125
74
|
|
75
|
+
# TOOD: Convert to output class
|
76
|
+
# - This should likely live in the output/issue class
|
77
|
+
# - There may be justification for creating different "strategies" for the various types
|
126
78
|
def issue_details(issue)
|
127
79
|
formatter = FORMATTERS[issue.type]
|
128
80
|
|
@@ -143,25 +95,31 @@ module Minitest
|
|
143
95
|
end
|
144
96
|
end
|
145
97
|
|
98
|
+
# TOOD: Convert to output class
|
146
99
|
def heat_map(map)
|
147
|
-
# text(:default, "🔥 Hot Spots 🔥\n")
|
148
100
|
map.files.each do |file|
|
149
|
-
|
150
|
-
|
101
|
+
pathname = Pathname(file[0])
|
102
|
+
|
103
|
+
path = pathname.dirname.to_s
|
104
|
+
filename = pathname.basename.to_s
|
105
|
+
|
106
|
+
values = map.hits[pathname.to_s]
|
151
107
|
|
152
|
-
filename = file.split('/').last
|
153
|
-
path = file.delete_suffix(filename)
|
154
108
|
|
155
|
-
text(:error, 'E' * values[:error].size)
|
156
|
-
text(:broken, 'B' * values[:broken].size)
|
109
|
+
text(:error, 'E' * values[:error].size) if values[:error]&.any?
|
110
|
+
text(:broken, 'B' * values[:broken].size) if values[:broken]&.any?
|
157
111
|
text(:failure, 'F' * values[:failure].size) if values[:failure]&.any?
|
158
|
-
text(:skipped, 'S' * values[:skipped].size) if values[:skipped]&.any?
|
159
|
-
text(:slow, 'S' * values[:skipped].size) if values[:skipped]&.any?
|
160
112
|
|
161
|
-
|
113
|
+
unless values[:error]&.any? || values[:broken]&.any? || values[:failure]&.any?
|
114
|
+
text(:skipped, 'S' * values[:skipped].size) if values[:skipped]&.any?
|
115
|
+
text(:painful, '—' * values[:painful].size) if values[:painful]&.any?
|
116
|
+
text(:slow, '–' * values[:slow].size) if values[:slow]&.any?
|
117
|
+
end
|
118
|
+
|
119
|
+
text(:muted, ' ') if map.hits.any?
|
162
120
|
|
163
|
-
text(:muted, "#{path.delete_prefix(
|
164
|
-
text(:default,
|
121
|
+
text(:muted, "#{path.delete_prefix(Dir.pwd)}/")
|
122
|
+
text(:default, filename)
|
165
123
|
|
166
124
|
text(:muted, ':')
|
167
125
|
|
@@ -169,102 +127,62 @@ module Minitest
|
|
169
127
|
all_line_numbers += values.fetch(:skipped, [])
|
170
128
|
|
171
129
|
line_numbers = all_line_numbers.compact.uniq.sort
|
172
|
-
line_numbers.each { |line_number| text(:
|
130
|
+
line_numbers.each { |line_number| text(:muted, "#{line_number} ") }
|
173
131
|
newline
|
174
132
|
end
|
175
133
|
newline
|
176
134
|
end
|
177
135
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
slow_count = results.slows.size
|
183
|
-
skip_count = results.skips.size
|
184
|
-
|
185
|
-
counts = []
|
186
|
-
counts << pluralize(error_count, 'Error') if error_count.positive?
|
187
|
-
counts << pluralize(broken_count, 'Broken') if broken_count.positive?
|
188
|
-
counts << pluralize(failure_count, 'Failure') if failure_count.positive?
|
189
|
-
counts << pluralize(skip_count, 'Skip') if skip_count.positive?
|
190
|
-
counts << pluralize(slow_count, 'Slow') if slow_count.positive?
|
191
|
-
text(:default, counts.join(', '))
|
192
|
-
|
193
|
-
newline
|
194
|
-
text(:subtle, "#{results.tests_per_second} tests/s and #{results.assertions_per_second} assertions/s ")
|
136
|
+
# TOOD: Convert to output class
|
137
|
+
def test_name_summary(issue)
|
138
|
+
text(:default, "#{issue.test_class} > #{issue.test_name}")
|
139
|
+
end
|
195
140
|
|
196
|
-
|
197
|
-
|
198
|
-
text(:muted, pluralize(results.assertion_count, 'Assertion'))
|
199
|
-
text(:muted, " in #{results.total_time.round(2)}s")
|
141
|
+
def compact_summary(results)
|
142
|
+
results_tokens = ::Minitest::Heat::Output::Results.new(results).tokens
|
200
143
|
|
201
144
|
newline
|
145
|
+
print_tokens(results_tokens)
|
202
146
|
newline
|
203
147
|
end
|
204
148
|
|
205
|
-
private
|
206
|
-
|
207
|
-
def test_name_summary(issue)
|
208
|
-
text(:default, "#{issue.test_class} > #{issue.test_name}")
|
209
|
-
end
|
210
|
-
|
211
149
|
def backtrace_summary(issue)
|
212
|
-
|
213
|
-
|
214
|
-
line = lines.first
|
215
|
-
filename = "#{line.path.delete_prefix(Dir.pwd)}/#{line.file}"
|
150
|
+
location = issue.location
|
216
151
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
text(:muted, " #{line.path.delete_prefix("#{Dir.pwd}/")}/")
|
221
|
-
text(:subtle, "#{line.file}:#{line.number}")
|
222
|
-
text(:source, " `#{source.line.strip}`")
|
223
|
-
|
224
|
-
newline
|
225
|
-
end
|
152
|
+
backtrace_tokens = ::Minitest::Heat::Output::Backtrace.new(location).tokens
|
153
|
+
print_tokens(backtrace_tokens)
|
226
154
|
end
|
227
155
|
|
228
156
|
def source_summary(issue)
|
229
|
-
filename
|
230
|
-
line_number = issue.location.
|
157
|
+
filename = issue.location.project_file
|
158
|
+
line_number = issue.location.project_failure_line
|
231
159
|
|
232
|
-
|
233
|
-
|
160
|
+
source_code_tokens = ::Minitest::Heat::Output::SourceCode.new(filename, line_number).tokens
|
161
|
+
print_tokens(source_code_tokens)
|
234
162
|
end
|
235
163
|
|
236
|
-
|
237
|
-
max_line_number_length = source.line_numbers.map(&:to_s).map(&:length).max
|
238
|
-
source.lines.each_index do |i|
|
239
|
-
line_number = source.line_numbers[i]
|
240
|
-
line = source.lines[i]
|
241
|
-
|
242
|
-
number_style, line_style = if line == source.line && highlight_line
|
243
|
-
[:default, :default]
|
244
|
-
else
|
245
|
-
[:subtle, :subtle]
|
246
|
-
end
|
247
|
-
text(number_style, "#{' ' * indentation}#{line_number.to_s.rjust(max_line_number_length)} ")
|
248
|
-
text(line_style, line)
|
249
|
-
puts
|
250
|
-
end
|
251
|
-
end
|
164
|
+
private
|
252
165
|
|
253
166
|
def style_enabled?
|
254
167
|
stream.tty?
|
255
168
|
end
|
256
169
|
|
257
|
-
def
|
258
|
-
|
259
|
-
|
260
|
-
# Given the narrow scope, pluralization can be relatively naive here
|
261
|
-
count > 1 ? "#{singular_style}s" : singular_style
|
170
|
+
def text(style, content)
|
171
|
+
token = Token.new(style, content)
|
172
|
+
print token.to_s(token_format)
|
262
173
|
end
|
263
174
|
|
264
|
-
def
|
265
|
-
|
175
|
+
def token_format
|
176
|
+
style_enabled? ? :styled : :unstyled
|
177
|
+
end
|
266
178
|
|
267
|
-
|
179
|
+
def print_tokens(lines_of_tokens)
|
180
|
+
lines_of_tokens.each do |tokens|
|
181
|
+
tokens.each do |token|
|
182
|
+
print Token.new(*token).to_s(token_format)
|
183
|
+
end
|
184
|
+
newline
|
185
|
+
end
|
268
186
|
end
|
269
187
|
end
|
270
188
|
end
|
@@ -49,8 +49,8 @@ module Minitest
|
|
49
49
|
(assertion_count / total_time).round(2)
|
50
50
|
end
|
51
51
|
|
52
|
-
def
|
53
|
-
errors? || failures? || skips?
|
52
|
+
def problems?
|
53
|
+
errors? || brokens? || failures? || skips?
|
54
54
|
end
|
55
55
|
|
56
56
|
def errors
|
@@ -93,19 +93,17 @@ module Minitest
|
|
93
93
|
skips.any?
|
94
94
|
end
|
95
95
|
|
96
|
-
def
|
97
|
-
|
98
|
-
@assertion_count += result.assertions
|
99
|
-
@success_count += 1 if result.passed?
|
96
|
+
def slows?
|
97
|
+
slows.any?
|
100
98
|
end
|
101
99
|
|
102
|
-
def
|
103
|
-
|
100
|
+
def record(issue)
|
101
|
+
@test_count += 1
|
102
|
+
@assertion_count += issue.result.assertions
|
103
|
+
@success_count += 1 if issue.result.passed?
|
104
104
|
|
105
105
|
@issues[issue.type] ||= []
|
106
106
|
@issues[issue.type] << issue
|
107
|
-
|
108
|
-
issue
|
109
107
|
end
|
110
108
|
end
|
111
109
|
end
|
data/lib/minitest/heat/source.rb
CHANGED
@@ -51,10 +51,15 @@ module Minitest
|
|
51
51
|
#
|
52
52
|
# @return [type] [description]
|
53
53
|
def file_lines
|
54
|
-
@raw_lines ||= File.readlines(
|
54
|
+
@raw_lines ||= File.readlines(filename, chomp: true)
|
55
55
|
@raw_lines.pop while @raw_lines.last.strip.empty?
|
56
56
|
|
57
57
|
@raw_lines
|
58
|
+
rescue Errno::ENOENT
|
59
|
+
# Occasionally, for a variety of reasons, a file can't be read. In those cases, it's best to
|
60
|
+
# return no source code lines rather than have the test suite raise an error unrelated to
|
61
|
+
# the code being tested becaues that gets confusing.
|
62
|
+
[]
|
58
63
|
end
|
59
64
|
|
60
65
|
private
|
@@ -59,14 +59,12 @@ module Minitest
|
|
59
59
|
# Minitest::Result source:
|
60
60
|
# https://github.com/seattlerb/minitest/blob/f4f57afaeb3a11bd0b86ab0757704cb78db96cf4/lib/minitest.rb#L504
|
61
61
|
def record(result)
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
output.marker(result.result_code)
|
69
|
-
end
|
62
|
+
issue = Heat::Issue.new(result)
|
63
|
+
|
64
|
+
@results.record(issue)
|
65
|
+
@map.add(*issue.to_hit) if issue.hit?
|
66
|
+
|
67
|
+
output.marker(issue.marker)
|
70
68
|
end
|
71
69
|
|
72
70
|
# Outputs the summary of the run.
|
@@ -80,14 +78,19 @@ module Minitest
|
|
80
78
|
# pressing issues are displayed at the bottom of the report in order to reduce scrolling.
|
81
79
|
# This way, as you fix issues, the list gets shorter, and eventually the least critical
|
82
80
|
# issues will be displayed without scrolling once more problematic issues are resolved.
|
83
|
-
results.
|
84
|
-
|
81
|
+
if results.failures.empty? && results.brokens.empty? && results.errors.empty? && results.skips.empty?
|
82
|
+
results.slows.each { |issue| output.issue_details(issue) }
|
83
|
+
end
|
84
|
+
|
85
|
+
if results.failures.empty? && results.brokens.empty? && results.errors.empty?
|
86
|
+
results.skips.each { |issue| output.issue_details(issue) }
|
87
|
+
end
|
88
|
+
|
85
89
|
results.failures.each { |issue| output.issue_details(issue) }
|
86
90
|
results.brokens.each { |issue| output.issue_details(issue) }
|
87
91
|
results.errors.each { |issue| output.issue_details(issue) }
|
88
92
|
|
89
93
|
output.compact_summary(results)
|
90
|
-
|
91
94
|
output.heat_map(map)
|
92
95
|
end
|
93
96
|
|
data/minitest-heat.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minitest-heat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Garrett Dimon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dead_end
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: pry
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -76,6 +90,13 @@ files:
|
|
76
90
|
- lib/minitest/heat/location.rb
|
77
91
|
- lib/minitest/heat/map.rb
|
78
92
|
- lib/minitest/heat/output.rb
|
93
|
+
- lib/minitest/heat/output/backtrace.rb
|
94
|
+
- lib/minitest/heat/output/issue.rb
|
95
|
+
- lib/minitest/heat/output/location.rb
|
96
|
+
- lib/minitest/heat/output/map.rb
|
97
|
+
- lib/minitest/heat/output/results.rb
|
98
|
+
- lib/minitest/heat/output/source_code.rb
|
99
|
+
- lib/minitest/heat/output/token.rb
|
79
100
|
- lib/minitest/heat/results.rb
|
80
101
|
- lib/minitest/heat/source.rb
|
81
102
|
- lib/minitest/heat/version.rb
|