minitest-heat 0.0.10 → 0.0.11
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 +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)
|