minitest-heat 0.0.10 → 0.0.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +6 -6
- data/lib/minitest/heat/backtrace/line_parser.rb +25 -0
- data/lib/minitest/heat/backtrace.rb +21 -59
- data/lib/minitest/heat/hit.rb +18 -6
- data/lib/minitest/heat/issue.rb +11 -11
- data/lib/minitest/heat/location.rb +113 -132
- data/lib/minitest/heat/locations.rb +105 -0
- data/lib/minitest/heat/map.rb +3 -4
- data/lib/minitest/heat/output/backtrace.rb +46 -53
- data/lib/minitest/heat/output/issue.rb +24 -36
- data/lib/minitest/heat/output/map.rb +88 -28
- data/lib/minitest/heat/output.rb +11 -7
- data/lib/minitest/heat/results.rb +11 -6
- data/lib/minitest/heat/source.rb +1 -1
- data/lib/minitest/heat/version.rb +1 -1
- data/lib/minitest/heat.rb +1 -0
- data/lib/minitest/heat_plugin.rb +1 -1
- data/lib/minitest/heat_reporter.rb +12 -5
- metadata +5 -4
- data/lib/minitest/heat/backtrace/line.rb +0 -118
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Minitest
|
4
|
+
module Heat
|
5
|
+
# Convenience methods for determining the file and line number where the problem occurred.
|
6
|
+
# There are several layers of specificity to help make it easy to communicate the relative
|
7
|
+
# location of the failure:
|
8
|
+
# - 'final' represents the final line of the backtrace regardless of where it is
|
9
|
+
# - 'test_definition' represents where the test is defined
|
10
|
+
# - 'test_failure' represents the last line from the project's tests. It is further differentiated by
|
11
|
+
# the line where the test is defined and the actual line of code in the test that geneated
|
12
|
+
# the failure or exception
|
13
|
+
# - 'source_code' represents the last line from the project's source code
|
14
|
+
# - 'project' represents the last source line, but falls back to the last test line
|
15
|
+
# - 'most_relevant' represents the most specific file to investigate starting with the source
|
16
|
+
# code and then looking to the test code with final line of the backtrace as a fallback
|
17
|
+
class Locations
|
18
|
+
attr_reader :test_definition, :backtrace
|
19
|
+
|
20
|
+
def initialize(test_definition_location, backtrace = [])
|
21
|
+
test_definition_pathname, test_definition_line_number = test_definition_location
|
22
|
+
@test_definition = ::Minitest::Heat::Location.new(pathname: test_definition_pathname, line_number: test_definition_line_number)
|
23
|
+
|
24
|
+
@backtrace = Backtrace.new(backtrace)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Prints the pathname and line number of the location most likely to be the source of the
|
28
|
+
# test failure
|
29
|
+
#
|
30
|
+
# @return [String] ex. 'path/to/file.rb:12'
|
31
|
+
def to_s
|
32
|
+
"#{most_relevant.absolute_filename}:#{most_relevant.line_number}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Knows if the failure is contained within the test. For example, if there's bad code in a
|
36
|
+
# test, and it raises an exception, then it's really a broken test rather than a proper
|
37
|
+
# faiure.
|
38
|
+
#
|
39
|
+
# @return [Boolean] true if final file in the backtrace is the same as the test location file
|
40
|
+
def broken_test?
|
41
|
+
!test_failure.nil? && test_failure == final
|
42
|
+
end
|
43
|
+
|
44
|
+
# Knows if the failure occurred in the actual project source code—as opposed to the test or
|
45
|
+
# an external piece of code like a gem.
|
46
|
+
#
|
47
|
+
# @return [Boolean] true if there's a non-test project file in the stacktrace but it's not
|
48
|
+
# a result of a broken test
|
49
|
+
def proper_failure?
|
50
|
+
!source_code.nil? && !broken_test?
|
51
|
+
end
|
52
|
+
|
53
|
+
# The file most likely to be the source of the underlying problem. Often, the most recent
|
54
|
+
# backtrace files will be a gem or external library that's failing indirectly as a result
|
55
|
+
# of a problem with local source code (not always, but frequently). In that case, the best
|
56
|
+
# first place to focus is on the code you control.
|
57
|
+
#
|
58
|
+
# @return [Array] file and line number of the most likely source of the problem
|
59
|
+
def most_relevant
|
60
|
+
[
|
61
|
+
source_code,
|
62
|
+
test_failure,
|
63
|
+
final
|
64
|
+
].compact.first
|
65
|
+
end
|
66
|
+
|
67
|
+
def freshest
|
68
|
+
backtrace.recently_modified_locations.first
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the final test location based on the backtrace if present. Otherwise falls back to
|
72
|
+
# the test location which represents the test definition. The `test_definition` attribute
|
73
|
+
# provides the location of where the test is defined. `test_failure` represents the actual
|
74
|
+
# line from within the test where the problem occurred
|
75
|
+
#
|
76
|
+
# @return [Location] the final location from the test files
|
77
|
+
def test_failure
|
78
|
+
backtrace.test_locations.any? ? backtrace.test_locations.first : test_definition
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the final source code location based on the backtrace
|
82
|
+
#
|
83
|
+
# @return [Location] the final location from the source code files
|
84
|
+
def source_code
|
85
|
+
backtrace.source_code_locations.first
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns the final project location based on the backtrace if present. Otherwise falls back
|
89
|
+
# to the test location which represents the test definition.
|
90
|
+
#
|
91
|
+
# @return [Location] the final location from the project files
|
92
|
+
def project
|
93
|
+
backtrace.project_locations.any? ? backtrace.project_locations.first : test_definition
|
94
|
+
end
|
95
|
+
|
96
|
+
# The line number from within the `test_file` test definition where the failure occurred
|
97
|
+
#
|
98
|
+
# @return [Location] the last location from the backtrace or the test location if a backtrace
|
99
|
+
# was not passed to the initializer
|
100
|
+
def final
|
101
|
+
backtrace.locations.any? ? backtrace.locations.first : test_definition
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/minitest/heat/map.rb
CHANGED
@@ -18,10 +18,9 @@ module Minitest
|
|
18
18
|
# @param type [Symbol] the type of issue that was encountered (i.e. :failure, :error, etc.)
|
19
19
|
#
|
20
20
|
# @return [void]
|
21
|
-
def add(
|
22
|
-
@hits[
|
23
|
-
|
24
|
-
@hits[filename].log(type.to_sym, line_number)
|
21
|
+
def add(pathname, line_number, type, backtrace: [])
|
22
|
+
@hits[pathname.to_s] ||= Hit.new(pathname)
|
23
|
+
@hits[pathname.to_s].log(type.to_sym, line_number, backtrace: backtrace)
|
25
24
|
end
|
26
25
|
|
27
26
|
# Returns a subset of affected files to keep the list from being overwhelming
|
@@ -8,11 +8,11 @@ module Minitest
|
|
8
8
|
DEFAULT_LINE_COUNT = 10
|
9
9
|
DEFAULT_INDENTATION_SPACES = 2
|
10
10
|
|
11
|
-
attr_accessor :
|
11
|
+
attr_accessor :locations, :backtrace
|
12
12
|
|
13
|
-
def initialize(
|
14
|
-
@
|
15
|
-
@backtrace =
|
13
|
+
def initialize(locations)
|
14
|
+
@locations = locations
|
15
|
+
@backtrace = locations.backtrace
|
16
16
|
@tokens = []
|
17
17
|
end
|
18
18
|
|
@@ -21,14 +21,8 @@ module Minitest
|
|
21
21
|
# final backtrace line if it might be relevant/helpful?
|
22
22
|
|
23
23
|
# Iterate over the selected lines from the backtrace
|
24
|
-
|
25
|
-
|
26
|
-
parts = backtrace_line_tokens(backtrace_entry)
|
27
|
-
|
28
|
-
# If it's the most recently modified file in the trace, add the token for that
|
29
|
-
parts << file_freshness(backtrace_entry) if most_recently_modified?(backtrace_entry)
|
30
|
-
|
31
|
-
@tokens << parts
|
24
|
+
backtrace_locations.each do |location|
|
25
|
+
@tokens << backtrace_location_tokens(location)
|
32
26
|
end
|
33
27
|
|
34
28
|
@tokens
|
@@ -47,71 +41,74 @@ module Minitest
|
|
47
41
|
# ...it could be influenced by a "compact" or "robust" reporter super-style?
|
48
42
|
# ...it's smart about exceptions that were raised outside of the project?
|
49
43
|
# ...it's smart about highlighting lines of code differently based on whether it's source code, test code, or external code?
|
50
|
-
def
|
51
|
-
|
44
|
+
def backtrace_locations
|
45
|
+
backtrace.locations.take(line_count)
|
52
46
|
end
|
53
47
|
|
54
48
|
private
|
55
49
|
|
56
|
-
def
|
50
|
+
def backtrace_location_tokens(location)
|
57
51
|
[
|
58
52
|
indentation_token,
|
59
|
-
path_token(
|
60
|
-
*file_and_line_number_tokens(
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
def all_backtrace_entries_from_project?
|
66
|
-
backtrace_entries.all? { |line| line.path.to_s.include?(project_root_dir) }
|
67
|
-
end
|
68
|
-
|
69
|
-
def project_root_dir
|
70
|
-
Dir.pwd
|
71
|
-
end
|
72
|
-
|
73
|
-
def project_entries
|
74
|
-
backtrace.project_entries.take(line_count)
|
53
|
+
path_token(location),
|
54
|
+
*file_and_line_number_tokens(location),
|
55
|
+
containining_element_token(location),
|
56
|
+
source_code_line_token(location),
|
57
|
+
most_recently_modified_token(location),
|
58
|
+
].compact
|
75
59
|
end
|
76
60
|
|
77
|
-
|
78
|
-
|
61
|
+
# Determines if all lines to be displayed are from within the project directory
|
62
|
+
#
|
63
|
+
# @return [Boolean] true if all lines of the backtrace being displayed are from the project
|
64
|
+
def all_backtrace_from_project?
|
65
|
+
backtrace_locations.all?(&:project_file?)
|
79
66
|
end
|
80
67
|
|
81
|
-
def most_recently_modified?(
|
68
|
+
def most_recently_modified?(location)
|
82
69
|
# If there's more than one line being displayed, and the current line is the freshest
|
83
|
-
|
70
|
+
backtrace_locations.size > 1 && location == locations.freshest
|
84
71
|
end
|
85
72
|
|
86
73
|
def indentation_token
|
87
74
|
[:default, ' ' * indentation]
|
88
75
|
end
|
89
76
|
|
90
|
-
def path_token(
|
91
|
-
|
92
|
-
|
77
|
+
def path_token(location)
|
78
|
+
# If the line is a project file, help it stand out from the backtrace noise
|
79
|
+
style = location.project_file? ? :default : :muted
|
93
80
|
|
94
|
-
# If all of the backtrace lines are from the project, no point in the added redundant
|
95
|
-
#
|
96
|
-
|
81
|
+
# If *all* of the backtrace lines are from the project, no point in the added redundant
|
82
|
+
# noise of showing the project root directory over and over again
|
83
|
+
path_format = all_backtrace_from_project? ? :relative_path : :absolute_path
|
97
84
|
|
98
|
-
[style,
|
85
|
+
[style, location.send(path_format)]
|
99
86
|
end
|
100
87
|
|
101
|
-
def file_and_line_number_tokens(
|
102
|
-
style =
|
88
|
+
def file_and_line_number_tokens(location)
|
89
|
+
style = location.to_s.include?(Dir.pwd) ? :bold : :muted
|
103
90
|
[
|
104
|
-
[style,
|
91
|
+
[style, location.filename],
|
105
92
|
[:muted, ':'],
|
106
|
-
[style,
|
93
|
+
[style, location.line_number]
|
107
94
|
]
|
108
95
|
end
|
109
96
|
|
110
|
-
def source_code_line_token(
|
111
|
-
|
97
|
+
def source_code_line_token(location)
|
98
|
+
return nil unless location.project_file?
|
99
|
+
|
100
|
+
[:muted, " #{Output::SYMBOLS[:arrow]} `#{location.source_code.line.strip}`"]
|
101
|
+
end
|
102
|
+
|
103
|
+
def containining_element_token(location)
|
104
|
+
return nil if !location.project_file? || location.container.nil? || location.container.empty?
|
105
|
+
|
106
|
+
[:muted, " in #{location.container}"]
|
112
107
|
end
|
113
108
|
|
114
|
-
def
|
109
|
+
def most_recently_modified_token(location)
|
110
|
+
return nil unless most_recently_modified?(location)
|
111
|
+
|
115
112
|
[:default, " #{Output::SYMBOLS[:middot]} Most Recently Modified File"]
|
116
113
|
end
|
117
114
|
|
@@ -126,10 +123,6 @@ module Minitest
|
|
126
123
|
def indentation
|
127
124
|
DEFAULT_INDENTATION_SPACES
|
128
125
|
end
|
129
|
-
|
130
|
-
def style_for(path)
|
131
|
-
path.to_s.include?(Dir.pwd) ? :default : :muted
|
132
|
-
end
|
133
126
|
end
|
134
127
|
end
|
135
128
|
end
|
@@ -5,10 +5,11 @@ module Minitest
|
|
5
5
|
class Output
|
6
6
|
# Formats issues to output based on the issue type
|
7
7
|
class Issue # rubocop:disable Metrics/ClassLength
|
8
|
-
attr_accessor :issue
|
8
|
+
attr_accessor :issue, :locations
|
9
9
|
|
10
10
|
def initialize(issue)
|
11
11
|
@issue = issue
|
12
|
+
@locations = issue.locations
|
12
13
|
end
|
13
14
|
|
14
15
|
def tokens
|
@@ -92,7 +93,7 @@ module Minitest
|
|
92
93
|
end
|
93
94
|
end
|
94
95
|
|
95
|
-
def label(issue)
|
96
|
+
def label(issue) # rubocop:disable Metrics
|
96
97
|
if issue.error? && issue.in_test?
|
97
98
|
# When the exception came out of the test itself, that's a different kind of exception
|
98
99
|
# that really only indicates there's a problem with the code in the test. It's kind of
|
@@ -118,24 +119,34 @@ module Minitest
|
|
118
119
|
end
|
119
120
|
|
120
121
|
def backtrace_tokens
|
121
|
-
|
122
|
-
|
123
|
-
backtrace.tokens
|
122
|
+
@backtrace_tokens ||= ::Minitest::Heat::Output::Backtrace.new(locations).tokens
|
124
123
|
end
|
125
124
|
|
126
125
|
def test_location_tokens
|
127
|
-
[
|
126
|
+
[
|
127
|
+
[:default, locations.test_definition.relative_filename],
|
128
|
+
[:muted, ':'],
|
129
|
+
[:default, locations.test_definition.line_number],
|
130
|
+
arrow_token,
|
131
|
+
[:default, locations.test_failure.line_number],
|
132
|
+
[:muted, "\n #{locations.test_failure.source_code.line.strip}"]
|
133
|
+
]
|
128
134
|
end
|
129
135
|
|
130
136
|
def location_tokens
|
131
|
-
[
|
137
|
+
[
|
138
|
+
[:default, locations.most_relevant.relative_filename],
|
139
|
+
[:muted, ':'],
|
140
|
+
[:default, locations.most_relevant.line_number],
|
141
|
+
[:muted, "\n #{locations.most_relevant.source_code.line.strip}"]
|
142
|
+
]
|
132
143
|
end
|
133
144
|
|
134
145
|
def source_tokens
|
135
|
-
filename =
|
136
|
-
line_number =
|
137
|
-
|
146
|
+
filename = locations.project.filename
|
147
|
+
line_number = locations.project.line_number
|
138
148
|
source = Minitest::Heat::Source.new(filename, line_number: line_number)
|
149
|
+
|
139
150
|
[[:muted, " #{Output::SYMBOLS[:arrow]} `#{source.line.strip}`"]]
|
140
151
|
end
|
141
152
|
|
@@ -147,9 +158,10 @@ module Minitest
|
|
147
158
|
[
|
148
159
|
[:bold, slowness(issue)],
|
149
160
|
spacer_token,
|
150
|
-
[:default,
|
161
|
+
[:default, locations.test_definition.relative_path],
|
162
|
+
[:default, locations.test_definition.filename],
|
151
163
|
[:muted, ':'],
|
152
|
-
[:default,
|
164
|
+
[:default, locations.test_definition.line_number]
|
153
165
|
]
|
154
166
|
end
|
155
167
|
|
@@ -161,30 +173,6 @@ module Minitest
|
|
161
173
|
[]
|
162
174
|
end
|
163
175
|
|
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
176
|
def spacer_token
|
189
177
|
Output::TOKENS[:spacer]
|
190
178
|
end
|
@@ -13,16 +13,26 @@ module Minitest
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def tokens
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
results.heat_map.file_hits.each do |hit|
|
17
|
+
# If there's legitimate failures or errors, skips and slows aren't relevant
|
18
|
+
next unless relevant_issue_types?(hit)
|
19
19
|
|
20
|
-
|
20
|
+
@tokens << [[:muted, ""]]
|
21
|
+
@tokens << file_summary_tokens(hit)
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
repeats = repeated_line_numbers(hit)
|
24
|
+
next unless repeats.any?
|
25
|
+
|
26
|
+
repeats.each do |line_number|
|
27
|
+
@tokens << [[:muted, " Issues on Line #{line_number} initially triggered from these locations:"]]
|
28
|
+
|
29
|
+
traces = hit.lines[line_number.to_s]
|
30
|
+
sorted_traces = traces.sort_by { |trace| trace.locations.last.line_number }
|
31
|
+
|
32
|
+
sorted_traces.each do |trace|
|
33
|
+
@tokens << origination_location_token(trace)
|
34
|
+
end
|
35
|
+
end
|
26
36
|
end
|
27
37
|
|
28
38
|
@tokens
|
@@ -30,8 +40,26 @@ module Minitest
|
|
30
40
|
|
31
41
|
private
|
32
42
|
|
33
|
-
def
|
34
|
-
|
43
|
+
def file_summary_tokens(hit)
|
44
|
+
pathname_tokens = pathname(hit)
|
45
|
+
line_number_list_tokens = sorted_line_number_list(hit)
|
46
|
+
|
47
|
+
[*pathname_tokens, *line_number_list_tokens]
|
48
|
+
end
|
49
|
+
|
50
|
+
def origination_location_token(trace)
|
51
|
+
# The earliest project line from the backtrace—this is probabyl wholly incorrect in terms
|
52
|
+
# of what would be the most helpful line to display, but it's a start.
|
53
|
+
location = trace.locations.last
|
54
|
+
|
55
|
+
[
|
56
|
+
[:muted, " #{Output::SYMBOLS[:arrow]} "],
|
57
|
+
[:default, location.relative_filename],
|
58
|
+
[:muted, ':'],
|
59
|
+
[:default, location.line_number],
|
60
|
+
[:muted, " in #{location.container}"],
|
61
|
+
[:muted, " #{Output::SYMBOLS[:arrow]} `location.source_code.line.strip`"],
|
62
|
+
]
|
35
63
|
end
|
36
64
|
|
37
65
|
def relevant_issue_types
|
@@ -46,37 +74,69 @@ module Minitest
|
|
46
74
|
issue_types
|
47
75
|
end
|
48
76
|
|
49
|
-
def
|
50
|
-
|
51
|
-
filename = file.pathname.basename.to_s
|
77
|
+
def relevant_issue_types?(hit)
|
78
|
+
intersection_issue_types = relevant_issue_types & hit.issues.keys
|
52
79
|
|
53
|
-
|
54
|
-
[:default, directory],
|
55
|
-
[:bold, filename],
|
56
|
-
[:default, ' · ']
|
57
|
-
]
|
80
|
+
intersection_issue_types.any?
|
58
81
|
end
|
59
82
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
83
|
+
def repeated_line_numbers(hit)
|
84
|
+
repeated_line_numbers = []
|
85
|
+
|
86
|
+
hit.lines.each_pair do |line_number, traces|
|
87
|
+
# If there aren't multiple traces for a line number, it's not a repeat, right?
|
88
|
+
next unless traces.size > 1
|
89
|
+
|
90
|
+
repeated_line_numbers << Integer(line_number)
|
65
91
|
end
|
66
92
|
|
67
|
-
|
93
|
+
repeated_line_numbers.sort
|
68
94
|
end
|
69
95
|
|
70
|
-
def
|
96
|
+
def repeated_line_numbers?(hit)
|
97
|
+
repeated_line_numbers(hit).any?
|
98
|
+
end
|
99
|
+
|
100
|
+
def pathname(hit)
|
101
|
+
directory = hit.pathname.dirname.to_s.delete_prefix("#{Dir.pwd}/")
|
102
|
+
filename = hit.pathname.basename.to_s
|
103
|
+
|
104
|
+
[
|
105
|
+
[:default, "#{directory}/"],
|
106
|
+
[:bold, filename],
|
107
|
+
[:default, ' · ']
|
108
|
+
]
|
109
|
+
end
|
110
|
+
|
111
|
+
def line_number_tokens_for_hit(hit)
|
71
112
|
line_number_tokens = []
|
72
113
|
|
73
|
-
# Merge the hits for all issue types into one list
|
74
114
|
relevant_issue_types.each do |issue_type|
|
75
|
-
|
115
|
+
# Retrieve any line numbers for the issue type
|
116
|
+
line_numbers_for_issue_type = hit.issues.fetch(issue_type) { [] }
|
117
|
+
|
118
|
+
# Build the list of tokens representing styled line numbers
|
119
|
+
line_numbers_for_issue_type.each do |line_number|
|
120
|
+
line_number_tokens << line_number_token(issue_type, line_number)
|
121
|
+
end
|
76
122
|
end
|
77
123
|
|
124
|
+
line_number_tokens.compact
|
125
|
+
end
|
126
|
+
|
127
|
+
def line_number_token(style, line_number)
|
128
|
+
[style, "#{line_number} "]
|
129
|
+
end
|
130
|
+
|
131
|
+
# Generates the line number tokens styled based on their error type
|
132
|
+
#
|
133
|
+
# @param [Hit] hit the instance of the hit file details to build the heat map entry
|
134
|
+
#
|
135
|
+
# @return [Array] the arrays representing the line number tokens to display next to a file
|
136
|
+
# name in the heat map
|
137
|
+
def sorted_line_number_list(hit)
|
78
138
|
# Sort the collected group of line number hits so they're in order
|
79
|
-
|
139
|
+
line_number_tokens_for_hit(hit).sort do |a, b|
|
80
140
|
# Ensure the line numbers are integers for sorting (otherwise '100' comes before '12')
|
81
141
|
first_line_number = Integer(a[1].strip)
|
82
142
|
second_line_number = Integer(b[1].strip)
|
data/lib/minitest/heat/output.rb
CHANGED
@@ -11,7 +11,7 @@ require_relative 'output/token'
|
|
11
11
|
module Minitest
|
12
12
|
module Heat
|
13
13
|
# Friendly API for printing nicely-formatted output to the console
|
14
|
-
class Output
|
14
|
+
class Output # rubocop:disable Metrics/ClassLength
|
15
15
|
SYMBOLS = {
|
16
16
|
middot: '·',
|
17
17
|
arrow: '➜',
|
@@ -56,14 +56,14 @@ module Minitest
|
|
56
56
|
|
57
57
|
results.send(issue_category).each { |issue| issue_details(issue) }
|
58
58
|
end
|
59
|
-
rescue => e
|
59
|
+
rescue StandardError => e
|
60
60
|
message = "Sorry, but Minitest Heat couldn't display the details of any failures."
|
61
61
|
exception_guidance(message, e)
|
62
62
|
end
|
63
63
|
|
64
64
|
def issue_details(issue)
|
65
65
|
print_tokens Minitest::Heat::Output::Issue.new(issue).tokens
|
66
|
-
rescue => e
|
66
|
+
rescue StandardError => e
|
67
67
|
message = "Sorry, but Minitest Heat couldn't display output for a failure."
|
68
68
|
exception_guidance(message, e)
|
69
69
|
end
|
@@ -75,7 +75,7 @@ module Minitest
|
|
75
75
|
def compact_summary(results, timer)
|
76
76
|
newline
|
77
77
|
print_tokens ::Minitest::Heat::Output::Results.new(results, timer).tokens
|
78
|
-
rescue => e
|
78
|
+
rescue StandardError => e
|
79
79
|
message = "Sorry, but Minitest Heat couldn't display the summary."
|
80
80
|
exception_guidance(message, e)
|
81
81
|
end
|
@@ -84,7 +84,7 @@ module Minitest
|
|
84
84
|
newline
|
85
85
|
print_tokens ::Minitest::Heat::Output::Map.new(map).tokens
|
86
86
|
newline
|
87
|
-
rescue => e
|
87
|
+
rescue StandardError => e
|
88
88
|
message = "Sorry, but Minitest Heat couldn't display the heat map."
|
89
89
|
exception_guidance(message, e)
|
90
90
|
end
|
@@ -92,7 +92,7 @@ module Minitest
|
|
92
92
|
def exception_guidance(message, exception)
|
93
93
|
newline
|
94
94
|
puts "#{message} Disabling Minitest Heat can get you back on track until the problem can be fixed."
|
95
|
-
puts
|
95
|
+
puts 'Please use the following exception details to submit an issue at https://github.com/garrettdimon/minitest-heat/issues'
|
96
96
|
puts "#{exception.message}:"
|
97
97
|
exception.backtrace.each do |line|
|
98
98
|
puts " #{line}"
|
@@ -138,7 +138,11 @@ module Minitest
|
|
138
138
|
def print_tokens(lines_of_tokens)
|
139
139
|
lines_of_tokens.each do |tokens|
|
140
140
|
tokens.each do |token|
|
141
|
-
|
141
|
+
begin
|
142
|
+
print Token.new(*token).to_s(token_format)
|
143
|
+
rescue
|
144
|
+
puts token.inspect
|
145
|
+
end
|
142
146
|
end
|
143
147
|
newline
|
144
148
|
end
|
@@ -19,16 +19,21 @@ module Minitest
|
|
19
19
|
# Record everything—even if it's a success
|
20
20
|
@issues.push(issue)
|
21
21
|
|
22
|
-
# If it's not a genuine problem, we're done here
|
23
|
-
|
22
|
+
# If it's not a genuine problem, we're done here...
|
23
|
+
return unless issue.hit?
|
24
|
+
|
25
|
+
# ...otherwise update the heat map
|
26
|
+
update_heat_map(issue)
|
24
27
|
end
|
25
28
|
|
26
29
|
def update_heat_map(issue)
|
27
|
-
#
|
28
|
-
pathname
|
29
|
-
|
30
|
+
# For heat map purposes, only the project backtrace lines are interesting
|
31
|
+
pathname, line_number = issue.locations.project.to_a
|
32
|
+
|
33
|
+
# Backtrace is only relevant for exception-generating issues, not slows, skips, or failures
|
34
|
+
backtrace = issue.error? ? issue.locations.backtrace.project_locations : []
|
30
35
|
|
31
|
-
@heat_map.add(pathname, line_number, issue.type)
|
36
|
+
@heat_map.add(pathname, line_number, issue.type, backtrace: backtrace)
|
32
37
|
end
|
33
38
|
|
34
39
|
def problems?
|
data/lib/minitest/heat/source.rb
CHANGED
data/lib/minitest/heat.rb
CHANGED
data/lib/minitest/heat_plugin.rb
CHANGED
@@ -6,7 +6,7 @@ module Minitest # rubocop:disable Style/Documentation
|
|
6
6
|
def self.plugin_heat_init(options)
|
7
7
|
io = options.fetch(:io, $stdout)
|
8
8
|
|
9
|
-
|
9
|
+
reporter.reporters.reject! do |reporter|
|
10
10
|
# Minitest Heat acts as a unified Progress *and* Summary reporter. Using other reporters of
|
11
11
|
# those types in conjunction with it creates some overly-verbose output
|
12
12
|
reporter.is_a?(ProgressReporter) || reporter.is_a?(SummaryReporter)
|