minitest-heat 0.0.6 → 0.0.10
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/.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
|