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