minitest-heat 0.0.6 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/lib/minitest/heat/backtrace/line.rb +118 -0
- data/lib/minitest/heat/backtrace.rb +48 -14
- data/lib/minitest/heat/hit.rb +28 -27
- data/lib/minitest/heat/issue.rb +92 -73
- data/lib/minitest/heat/location.rb +71 -53
- data/lib/minitest/heat/map.rb +14 -17
- data/lib/minitest/heat/output/backtrace.rb +28 -13
- data/lib/minitest/heat/output/issue.rb +93 -23
- data/lib/minitest/heat/output/map.rb +47 -24
- data/lib/minitest/heat/output/marker.rb +5 -3
- data/lib/minitest/heat/output/results.rb +32 -21
- data/lib/minitest/heat/output/source_code.rb +1 -1
- data/lib/minitest/heat/output/token.rb +2 -1
- data/lib/minitest/heat/output.rb +70 -2
- data/lib/minitest/heat/results.rb +27 -81
- data/lib/minitest/heat/timer.rb +81 -0
- data/lib/minitest/heat/version.rb +1 -1
- data/lib/minitest/heat.rb +3 -2
- data/lib/minitest/heat_plugin.rb +9 -17
- data/lib/minitest/heat_reporter.rb +29 -23
- metadata +4 -3
- data/lib/minitest/heat/line.rb +0 -74
@@ -22,10 +22,10 @@ module Minitest
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
attr_reader :
|
25
|
+
attr_reader :test_definition_location, :backtrace
|
26
26
|
|
27
|
-
def initialize(
|
28
|
-
@
|
27
|
+
def initialize(test_definition_location, backtrace = [])
|
28
|
+
@test_definition_location = TestDefinition.new(*test_definition_location)
|
29
29
|
@backtrace = Backtrace.new(backtrace)
|
30
30
|
end
|
31
31
|
|
@@ -45,9 +45,9 @@ module Minitest
|
|
45
45
|
# test, and it raises an exception, then it's really a broken test rather than a proper
|
46
46
|
# faiure.
|
47
47
|
#
|
48
|
-
# @return [Boolean] true if
|
48
|
+
# @return [Boolean] true if final file in the backtrace is the same as the test location file
|
49
49
|
def broken_test?
|
50
|
-
!test_file.nil? && test_file ==
|
50
|
+
!test_file.nil? && test_file == final_file
|
51
51
|
end
|
52
52
|
|
53
53
|
# Knows if the failure occurred in the actual project source code—as opposed to the test or
|
@@ -59,6 +59,15 @@ module Minitest
|
|
59
59
|
!source_code_file.nil? && !broken_test?
|
60
60
|
end
|
61
61
|
|
62
|
+
|
63
|
+
|
64
|
+
# The final location of the stacktrace regardless of whether it's from within the project
|
65
|
+
#
|
66
|
+
# @return [String] the relative path to the file from the project root
|
67
|
+
def final_file
|
68
|
+
Pathname(final_location.pathname)
|
69
|
+
end
|
70
|
+
|
62
71
|
# The file most likely to be the source of the underlying problem. Often, the most recent
|
63
72
|
# backtrace files will be a gem or external library that's failing indirectly as a result
|
64
73
|
# of a problem with local source code (not always, but frequently). In that case, the best
|
@@ -69,78 +78,76 @@ module Minitest
|
|
69
78
|
Pathname(most_relevant_location.pathname)
|
70
79
|
end
|
71
80
|
|
72
|
-
# The
|
81
|
+
# The final location from the stacktrace that is a test file
|
73
82
|
#
|
74
|
-
# @return [
|
75
|
-
def
|
76
|
-
|
83
|
+
# @return [String, nil] the relative path to the file from the project root
|
84
|
+
def test_file
|
85
|
+
Pathname(final_test_location.pathname)
|
77
86
|
end
|
78
87
|
|
79
|
-
# The final location
|
88
|
+
# The final location from the stacktrace that is within the project directory
|
80
89
|
#
|
81
|
-
# @return [String] the relative path to the file from the project root
|
82
|
-
def
|
83
|
-
|
84
|
-
end
|
90
|
+
# @return [String, nil] the relative path to the file from the project root
|
91
|
+
def source_code_file
|
92
|
+
return nil if final_source_code_location.nil?
|
85
93
|
|
86
|
-
|
87
|
-
#
|
88
|
-
# @return [Integer] line number
|
89
|
-
def final_failure_line
|
90
|
-
final_location.line_number
|
94
|
+
Pathname(final_source_code_location.pathname)
|
91
95
|
end
|
92
96
|
|
93
|
-
# The final location of the stacktrace
|
97
|
+
# The final location of the stacktrace from within the project (source code or test code)
|
94
98
|
#
|
95
99
|
# @return [String] the relative path to the file from the project root
|
96
100
|
def project_file
|
97
|
-
|
101
|
+
return nil if project_location.nil?
|
102
|
+
|
103
|
+
Pathname(project_location.pathname)
|
98
104
|
end
|
99
105
|
|
100
|
-
|
106
|
+
|
107
|
+
# The line number of the `final_file` where the failure originated
|
101
108
|
#
|
102
109
|
# @return [Integer] line number
|
103
|
-
def
|
104
|
-
|
110
|
+
def final_failure_line
|
111
|
+
final_location.line_number
|
105
112
|
end
|
106
113
|
|
107
|
-
# The
|
114
|
+
# The line number of the `most_relevant_file` where the failure originated
|
108
115
|
#
|
109
|
-
# @return [
|
110
|
-
def
|
111
|
-
|
112
|
-
|
113
|
-
backtrace.final_source_code_location.pathname
|
116
|
+
# @return [Integer] line number
|
117
|
+
def most_relevant_failure_line
|
118
|
+
most_relevant_location.line_number
|
114
119
|
end
|
115
120
|
|
116
|
-
# The line number of the `
|
121
|
+
# The line number of the `test_file` where the test is defined
|
117
122
|
#
|
118
123
|
# @return [Integer] line number
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
backtrace.final_source_code_location.line_number
|
124
|
+
def test_definition_line
|
125
|
+
test_definition_location.line_number
|
123
126
|
end
|
124
127
|
|
125
|
-
# The
|
128
|
+
# The line number from within the `test_file` test definition where the failure occurred
|
126
129
|
#
|
127
|
-
# @return [
|
128
|
-
def
|
129
|
-
|
130
|
+
# @return [Integer] line number
|
131
|
+
def test_failure_line
|
132
|
+
final_test_location.line_number
|
130
133
|
end
|
131
134
|
|
132
|
-
# The line number of the `
|
135
|
+
# The line number of the `source_code_file` where the failure originated
|
133
136
|
#
|
134
137
|
# @return [Integer] line number
|
135
|
-
def
|
136
|
-
|
138
|
+
def source_code_failure_line
|
139
|
+
final_source_code_location&.line_number
|
137
140
|
end
|
138
141
|
|
139
|
-
# The line number
|
142
|
+
# The line number of the `project_file` where the failure originated
|
140
143
|
#
|
141
144
|
# @return [Integer] line number
|
142
|
-
def
|
143
|
-
|
145
|
+
def project_failure_line
|
146
|
+
if !broken_test? && !source_code_file.nil?
|
147
|
+
source_code_failure_line
|
148
|
+
else
|
149
|
+
test_failure_line
|
150
|
+
end
|
144
151
|
end
|
145
152
|
|
146
153
|
# The line number from within the `test_file` test definition where the failure occurred
|
@@ -148,7 +155,7 @@ module Minitest
|
|
148
155
|
# @return [Location] the last location from the backtrace or the test location if a backtrace
|
149
156
|
# was not passed to the initializer
|
150
157
|
def final_location
|
151
|
-
backtrace? ? backtrace.final_location :
|
158
|
+
backtrace.parsed_entries.any? ? backtrace.final_location : test_definition_location
|
152
159
|
end
|
153
160
|
|
154
161
|
# The file most likely to be the source of the underlying problem. Often, the most recent
|
@@ -159,22 +166,33 @@ module Minitest
|
|
159
166
|
# @return [Array] file and line number of the most likely source of the problem
|
160
167
|
def most_relevant_location
|
161
168
|
[
|
162
|
-
|
163
|
-
|
169
|
+
final_source_code_location,
|
170
|
+
final_test_location,
|
164
171
|
final_location
|
165
172
|
].compact.first
|
166
173
|
end
|
167
174
|
|
168
|
-
|
169
|
-
|
175
|
+
# Returns the final test location based on the backtrace if present. Otherwise falls back to
|
176
|
+
# the test location which represents the test definition.
|
177
|
+
#
|
178
|
+
# @return [Location] the final location from the test files
|
179
|
+
def final_test_location
|
180
|
+
backtrace.final_test_location || test_definition_location
|
170
181
|
end
|
171
182
|
|
172
|
-
|
183
|
+
# Returns the final source code location based on the backtrace
|
184
|
+
#
|
185
|
+
# @return [Location] the final location from the source code files
|
186
|
+
def final_source_code_location
|
173
187
|
backtrace.final_source_code_location
|
174
188
|
end
|
175
189
|
|
176
|
-
|
177
|
-
|
190
|
+
# Returns the final project location based on the backtrace if present. Otherwise falls back
|
191
|
+
# to the test location which represents the test definition.
|
192
|
+
#
|
193
|
+
# @return [Location] the final location from the project files
|
194
|
+
def project_location
|
195
|
+
backtrace.final_project_location || test_definition_location
|
178
196
|
end
|
179
197
|
end
|
180
198
|
end
|
data/lib/minitest/heat/map.rb
CHANGED
@@ -2,43 +2,40 @@
|
|
2
2
|
|
3
3
|
module Minitest
|
4
4
|
module Heat
|
5
|
+
# Structured approach to collecting the locations of issues for generating a heat map
|
5
6
|
class Map
|
6
7
|
MAXIMUM_FILES_TO_SHOW = 5
|
7
8
|
|
8
9
|
attr_reader :hits
|
9
10
|
|
10
|
-
# So we can sort hot spots by liklihood of being the most important spot to check out before
|
11
|
-
# trying to fix something. These are ranked based on the possibility they represent ripple
|
12
|
-
# effects where fixing one problem could potentially fix multiple other failures.
|
13
|
-
#
|
14
|
-
# For example, if there's an exception in the file, start there. Broken code can't run. If a
|
15
|
-
# test is broken (i.e. raising an exception), that's a special sort of failure that would be
|
16
|
-
# misleading. It doesn't represent a proper failure, but rather a test that doesn't work.
|
17
|
-
WEIGHTS = {
|
18
|
-
error: 3, # exceptions from source code have the highest liklihood of a ripple effect
|
19
|
-
broken: 2, # broken tests won't have ripple effects but can't help if they can't run
|
20
|
-
failure: 1, # failures are kind of the whole point, and they could have ripple effects
|
21
|
-
skipped: 0, # skips aren't failures, but they shouldn't go ignored
|
22
|
-
painful: 0, # slow tests aren't failures, but they shouldn't be ignored
|
23
|
-
slow: 0
|
24
|
-
}.freeze
|
25
|
-
|
26
11
|
def initialize
|
27
12
|
@hits = {}
|
28
13
|
end
|
29
14
|
|
15
|
+
# Records a hit to the list of files and issue types
|
16
|
+
# @param filename [String] the unique path and file name for recordings hits
|
17
|
+
# @param line_number [Integer] the line number where the issue was encountered
|
18
|
+
# @param type [Symbol] the type of issue that was encountered (i.e. :failure, :error, etc.)
|
19
|
+
#
|
20
|
+
# @return [void]
|
30
21
|
def add(filename, line_number, type)
|
31
22
|
@hits[filename] ||= Hit.new(filename)
|
32
23
|
|
33
|
-
@hits[filename].log(type, line_number)
|
24
|
+
@hits[filename].log(type.to_sym, line_number)
|
34
25
|
end
|
35
26
|
|
27
|
+
# Returns a subset of affected files to keep the list from being overwhelming
|
28
|
+
#
|
29
|
+
# @return [Array] the list of files and the line numbers for each encountered issue type
|
36
30
|
def file_hits
|
37
31
|
hot_files.take(MAXIMUM_FILES_TO_SHOW)
|
38
32
|
end
|
39
33
|
|
40
34
|
private
|
41
35
|
|
36
|
+
# Sorts the files by hit "weight" so that the most problematic files are at the beginning
|
37
|
+
#
|
38
|
+
# @return [Array] the collection of files that encountred issues
|
42
39
|
def hot_files
|
43
40
|
hits.values.sort_by(&:weight).reverse
|
44
41
|
end
|
@@ -5,7 +5,7 @@ module Minitest
|
|
5
5
|
class Output
|
6
6
|
# Builds the collection of tokens for a backtrace when an exception occurs
|
7
7
|
class Backtrace
|
8
|
-
DEFAULT_LINE_COUNT =
|
8
|
+
DEFAULT_LINE_COUNT = 10
|
9
9
|
DEFAULT_INDENTATION_SPACES = 2
|
10
10
|
|
11
11
|
attr_accessor :location, :backtrace
|
@@ -23,13 +23,9 @@ module Minitest
|
|
23
23
|
# Iterate over the selected lines from the backtrace
|
24
24
|
backtrace_entries.each do |backtrace_entry|
|
25
25
|
# Get the source code for the line from the backtrace
|
26
|
-
parts =
|
27
|
-
indentation_token,
|
28
|
-
path_token(backtrace_entry),
|
29
|
-
file_and_line_number_token(backtrace_entry),
|
30
|
-
source_code_line_token(backtrace_entry.source_code)
|
31
|
-
]
|
26
|
+
parts = backtrace_line_tokens(backtrace_entry)
|
32
27
|
|
28
|
+
# If it's the most recently modified file in the trace, add the token for that
|
33
29
|
parts << file_freshness(backtrace_entry) if most_recently_modified?(backtrace_entry)
|
34
30
|
|
35
31
|
@tokens << parts
|
@@ -52,11 +48,20 @@ module Minitest
|
|
52
48
|
# ...it's smart about exceptions that were raised outside of the project?
|
53
49
|
# ...it's smart about highlighting lines of code differently based on whether it's source code, test code, or external code?
|
54
50
|
def backtrace_entries
|
55
|
-
|
51
|
+
all_entries
|
56
52
|
end
|
57
53
|
|
58
54
|
private
|
59
55
|
|
56
|
+
def backtrace_line_tokens(backtrace_entry)
|
57
|
+
[
|
58
|
+
indentation_token,
|
59
|
+
path_token(backtrace_entry),
|
60
|
+
*file_and_line_number_tokens(backtrace_entry),
|
61
|
+
source_code_line_token(backtrace_entry.source_code)
|
62
|
+
]
|
63
|
+
end
|
64
|
+
|
60
65
|
def all_backtrace_entries_from_project?
|
61
66
|
backtrace_entries.all? { |line| line.path.to_s.include?(project_root_dir) }
|
62
67
|
end
|
@@ -83,25 +88,31 @@ module Minitest
|
|
83
88
|
end
|
84
89
|
|
85
90
|
def path_token(line)
|
91
|
+
style = line.to_s.include?(Dir.pwd) ? :default : :muted
|
86
92
|
path = "#{line.path}/"
|
87
93
|
|
88
94
|
# If all of the backtrace lines are from the project, no point in the added redundant
|
89
95
|
# noise of showing the project root directory over and over again
|
90
96
|
path = path.delete_prefix(project_root_dir) if all_backtrace_entries_from_project?
|
91
97
|
|
92
|
-
[
|
98
|
+
[style, path]
|
93
99
|
end
|
94
100
|
|
95
|
-
def
|
96
|
-
|
101
|
+
def file_and_line_number_tokens(backtrace_entry)
|
102
|
+
style = backtrace_entry.to_s.include?(Dir.pwd) ? :bold : :muted
|
103
|
+
[
|
104
|
+
[style, backtrace_entry.file],
|
105
|
+
[:muted, ':'],
|
106
|
+
[style, backtrace_entry.line_number]
|
107
|
+
]
|
97
108
|
end
|
98
109
|
|
99
110
|
def source_code_line_token(source_code)
|
100
|
-
[:muted, " `#{source_code.line.strip}`"]
|
111
|
+
[:muted, " #{Output::SYMBOLS[:arrow]} `#{source_code.line.strip}`"]
|
101
112
|
end
|
102
113
|
|
103
114
|
def file_freshness(_line)
|
104
|
-
[:
|
115
|
+
[:default, " #{Output::SYMBOLS[:middot]} Most Recently Modified File"]
|
105
116
|
end
|
106
117
|
|
107
118
|
# The number of spaces each line of code should be indented. Currently defaults to 2 in
|
@@ -115,6 +126,10 @@ module Minitest
|
|
115
126
|
def indentation
|
116
127
|
DEFAULT_INDENTATION_SPACES
|
117
128
|
end
|
129
|
+
|
130
|
+
def style_for(path)
|
131
|
+
path.to_s.include?(Dir.pwd) ? :default : :muted
|
132
|
+
end
|
118
133
|
end
|
119
134
|
end
|
120
135
|
end
|
@@ -3,12 +3,8 @@
|
|
3
3
|
module Minitest
|
4
4
|
module Heat
|
5
5
|
class Output
|
6
|
-
|
7
|
-
|
8
|
-
spacer: ' · ',
|
9
|
-
arrow: ' > '
|
10
|
-
}.freeze
|
11
|
-
|
6
|
+
# Formats issues to output based on the issue type
|
7
|
+
class Issue # rubocop:disable Metrics/ClassLength
|
12
8
|
attr_accessor :issue
|
13
9
|
|
14
10
|
def initialize(issue)
|
@@ -31,6 +27,7 @@ module Minitest
|
|
31
27
|
def error_tokens
|
32
28
|
[
|
33
29
|
headline_tokens,
|
30
|
+
test_location_tokens,
|
34
31
|
summary_tokens,
|
35
32
|
*backtrace_tokens,
|
36
33
|
newline_tokens
|
@@ -40,6 +37,7 @@ module Minitest
|
|
40
37
|
def broken_tokens
|
41
38
|
[
|
42
39
|
headline_tokens,
|
40
|
+
test_location_tokens,
|
43
41
|
summary_tokens,
|
44
42
|
*backtrace_tokens,
|
45
43
|
newline_tokens
|
@@ -49,9 +47,8 @@ module Minitest
|
|
49
47
|
def failure_tokens
|
50
48
|
[
|
51
49
|
headline_tokens,
|
50
|
+
test_location_tokens,
|
52
51
|
summary_tokens,
|
53
|
-
location_tokens,
|
54
|
-
*source_tokens,
|
55
52
|
newline_tokens
|
56
53
|
]
|
57
54
|
end
|
@@ -59,7 +56,7 @@ module Minitest
|
|
59
56
|
def skipped_tokens
|
60
57
|
[
|
61
58
|
headline_tokens,
|
62
|
-
|
59
|
+
test_location_tokens,
|
63
60
|
newline_tokens
|
64
61
|
]
|
65
62
|
end
|
@@ -67,7 +64,7 @@ module Minitest
|
|
67
64
|
def painful_tokens
|
68
65
|
[
|
69
66
|
headline_tokens,
|
70
|
-
|
67
|
+
slowness_summary_tokens,
|
71
68
|
newline_tokens
|
72
69
|
]
|
73
70
|
end
|
@@ -75,17 +72,49 @@ module Minitest
|
|
75
72
|
def slow_tokens
|
76
73
|
[
|
77
74
|
headline_tokens,
|
78
|
-
|
75
|
+
slowness_summary_tokens,
|
79
76
|
newline_tokens
|
80
77
|
]
|
81
78
|
end
|
82
79
|
|
83
80
|
def headline_tokens
|
84
|
-
[[issue.type, issue
|
81
|
+
[[issue.type, label(issue)], spacer_token, [:default, test_name(issue)]]
|
85
82
|
end
|
86
83
|
|
87
|
-
def
|
88
|
-
|
84
|
+
def test_name(issue)
|
85
|
+
test_prefix = 'test_'
|
86
|
+
identifier = issue.test_identifier
|
87
|
+
|
88
|
+
if identifier.start_with?(test_prefix)
|
89
|
+
identifier.delete_prefix(test_prefix).gsub('_', ' ').capitalize
|
90
|
+
else
|
91
|
+
identifier
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def label(issue)
|
96
|
+
if issue.error? && issue.in_test?
|
97
|
+
# When the exception came out of the test itself, that's a different kind of exception
|
98
|
+
# that really only indicates there's a problem with the code in the test. It's kind of
|
99
|
+
# between an error and a test.
|
100
|
+
'Broken Test'
|
101
|
+
elsif issue.error?
|
102
|
+
'Error'
|
103
|
+
elsif issue.skipped?
|
104
|
+
'Skipped'
|
105
|
+
elsif issue.painful?
|
106
|
+
'Passed but Very Slow'
|
107
|
+
elsif issue.slow?
|
108
|
+
'Passed but Slow'
|
109
|
+
elsif !issue.passed?
|
110
|
+
'Failure'
|
111
|
+
else
|
112
|
+
'Success'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_name_and_class_tokens
|
117
|
+
[[:default, issue.test_class], *test_location_tokens]
|
89
118
|
end
|
90
119
|
|
91
120
|
def backtrace_tokens
|
@@ -94,33 +123,74 @@ module Minitest
|
|
94
123
|
backtrace.tokens
|
95
124
|
end
|
96
125
|
|
126
|
+
def test_location_tokens
|
127
|
+
[[:default, test_file_short_location], [:muted, ':'], [:default, issue.test_definition_line], arrow_token, [:default, issue.test_failure_line], [:muted, test_line_source]]
|
128
|
+
end
|
129
|
+
|
97
130
|
def location_tokens
|
98
|
-
[[:muted, issue.
|
131
|
+
[[:default, most_relevant_short_location], [:muted, ':'], [:default, issue.location.most_relevant_failure_line], [:muted, most_relevant_line_source]]
|
99
132
|
end
|
100
133
|
|
101
134
|
def source_tokens
|
102
135
|
filename = issue.location.project_file
|
103
136
|
line_number = issue.location.project_failure_line
|
104
137
|
|
105
|
-
|
138
|
+
source = Minitest::Heat::Source.new(filename, line_number: line_number)
|
139
|
+
[[:muted, " #{Output::SYMBOLS[:arrow]} `#{source.line.strip}`"]]
|
140
|
+
end
|
141
|
+
|
142
|
+
def summary_tokens
|
143
|
+
[[:italicized, issue.summary.delete_suffix('---------------').strip]]
|
144
|
+
end
|
106
145
|
|
107
|
-
|
146
|
+
def slowness_summary_tokens
|
147
|
+
[
|
148
|
+
[:bold, slowness(issue)],
|
149
|
+
spacer_token,
|
150
|
+
[:default, issue.location.test_file.to_s.delete_prefix(Dir.pwd)],
|
151
|
+
[:muted, ':'],
|
152
|
+
[:default, issue.location.test_definition_line]
|
153
|
+
]
|
108
154
|
end
|
109
155
|
|
110
|
-
def
|
111
|
-
|
156
|
+
def slowness(issue)
|
157
|
+
"#{issue.execution_time.round(2)}s"
|
112
158
|
end
|
113
159
|
|
114
160
|
def newline_tokens
|
115
161
|
[]
|
116
162
|
end
|
117
163
|
|
118
|
-
def
|
119
|
-
|
164
|
+
def most_relevant_short_location
|
165
|
+
issue.location.most_relevant_file.to_s.delete_prefix("#{Dir.pwd}/")
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_file_short_location
|
169
|
+
issue.location.test_file.to_s.delete_prefix("#{Dir.pwd}/")
|
170
|
+
end
|
171
|
+
|
172
|
+
def most_relevant_line_source
|
173
|
+
filename = issue.location.project_file
|
174
|
+
line_number = issue.location.project_failure_line
|
175
|
+
|
176
|
+
source = Minitest::Heat::Source.new(filename, line_number: line_number)
|
177
|
+
"\n #{source.line.strip}"
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_line_source
|
181
|
+
filename = issue.location.test_file
|
182
|
+
line_number = issue.location.test_failure_line
|
183
|
+
|
184
|
+
source = Minitest::Heat::Source.new(filename, line_number: line_number)
|
185
|
+
"\n #{source.line.strip}"
|
186
|
+
end
|
187
|
+
|
188
|
+
def spacer_token
|
189
|
+
Output::TOKENS[:spacer]
|
120
190
|
end
|
121
191
|
|
122
|
-
def
|
123
|
-
|
192
|
+
def arrow_token
|
193
|
+
Output::TOKENS[:muted_arrow]
|
124
194
|
end
|
125
195
|
end
|
126
196
|
end
|
@@ -3,23 +3,25 @@
|
|
3
3
|
module Minitest
|
4
4
|
module Heat
|
5
5
|
class Output
|
6
|
+
# Generates the tokens to output the resulting heat map
|
6
7
|
class Map
|
7
|
-
|
8
|
+
attr_accessor :results
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
# def_delegators :@results, :errors, :brokens, :failures, :slows, :skips, :problems?, :slows?
|
12
|
-
|
13
|
-
def initialize(map)
|
14
|
-
@map = map
|
10
|
+
def initialize(results)
|
11
|
+
@results = results
|
15
12
|
@tokens = []
|
16
13
|
end
|
17
14
|
|
18
15
|
def tokens
|
19
|
-
map.file_hits.each do |
|
16
|
+
map.file_hits.each do |hit|
|
17
|
+
file_tokens = pathname(hit)
|
18
|
+
line_number_tokens = line_numbers(hit)
|
19
|
+
|
20
|
+
next if line_number_tokens.empty?
|
21
|
+
|
20
22
|
@tokens << [
|
21
|
-
*
|
22
|
-
*
|
23
|
+
*file_tokens,
|
24
|
+
*line_number_tokens
|
23
25
|
]
|
24
26
|
end
|
25
27
|
|
@@ -28,8 +30,24 @@ module Minitest
|
|
28
30
|
|
29
31
|
private
|
30
32
|
|
33
|
+
def map
|
34
|
+
results.heat_map
|
35
|
+
end
|
36
|
+
|
37
|
+
def relevant_issue_types
|
38
|
+
# These are always relevant.
|
39
|
+
issue_types = %i[error broken failure]
|
40
|
+
|
41
|
+
# These are only relevant if there aren't more serious isues.
|
42
|
+
issue_types << :skipped unless results.problems?
|
43
|
+
issue_types << :painful unless results.problems? || results.skips.any?
|
44
|
+
issue_types << :slow unless results.problems? || results.skips.any?
|
45
|
+
|
46
|
+
issue_types
|
47
|
+
end
|
48
|
+
|
31
49
|
def pathname(file)
|
32
|
-
directory = "#{file.pathname.dirname.to_s.delete_prefix(Dir.pwd)}/"
|
50
|
+
directory = "#{file.pathname.dirname.to_s.delete_prefix(Dir.pwd)}/".delete_prefix('/')
|
33
51
|
filename = file.pathname.basename.to_s
|
34
52
|
|
35
53
|
[
|
@@ -40,26 +58,31 @@ module Minitest
|
|
40
58
|
end
|
41
59
|
|
42
60
|
def hit_line_numbers(file, issue_type)
|
43
|
-
line_numbers_for_issue_type = file.issues.fetch(issue_type) { [] }
|
44
|
-
|
45
|
-
return nil if line_numbers_for_issue_type.empty?
|
46
|
-
|
47
61
|
numbers = []
|
48
|
-
line_numbers_for_issue_type.
|
62
|
+
line_numbers_for_issue_type = file.issues.fetch(issue_type) { [] }
|
63
|
+
line_numbers_for_issue_type.map do |line_number|
|
49
64
|
numbers << [issue_type, "#{line_number} "]
|
50
65
|
end
|
66
|
+
|
51
67
|
numbers
|
52
68
|
end
|
53
69
|
|
54
70
|
def line_numbers(file)
|
55
|
-
[
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
71
|
+
line_number_tokens = []
|
72
|
+
|
73
|
+
# Merge the hits for all issue types into one list
|
74
|
+
relevant_issue_types.each do |issue_type|
|
75
|
+
line_number_tokens += hit_line_numbers(file, issue_type)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Sort the collected group of line number hits so they're in order
|
79
|
+
line_number_tokens.compact.sort do |a, b|
|
80
|
+
# Ensure the line numbers are integers for sorting (otherwise '100' comes before '12')
|
81
|
+
first_line_number = Integer(a[1].strip)
|
82
|
+
second_line_number = Integer(b[1].strip)
|
83
|
+
|
84
|
+
first_line_number <=> second_line_number
|
85
|
+
end
|
63
86
|
end
|
64
87
|
end
|
65
88
|
end
|