minitest-heat 2.1.0 → 2.2.0
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/.flayignore +1 -0
- data/.github/workflows/main.yml +39 -2
- data/.reek.yml +87 -0
- data/.reviewer.yml +87 -0
- data/.reviewer_history.yml +42 -0
- data/.rubocop.yml +33 -2
- data/CHANGELOG.md +29 -0
- data/Gemfile +14 -2
- data/Gemfile.lock +64 -7
- data/README.md +30 -2
- data/favicon.svg +3 -0
- data/lib/minitest/heat/backtrace/line_count.rb +1 -1
- data/lib/minitest/heat/backtrace/line_parser.rb +2 -2
- data/lib/minitest/heat/backtrace.rb +5 -15
- data/lib/minitest/heat/hit.rb +5 -12
- data/lib/minitest/heat/issue.rb +15 -39
- data/lib/minitest/heat/location.rb +26 -64
- data/lib/minitest/heat/locations.rb +8 -24
- data/lib/minitest/heat/map.rb +3 -9
- data/lib/minitest/heat/output/backtrace.rb +16 -26
- data/lib/minitest/heat/output/issue.rb +1 -1
- data/lib/minitest/heat/output/map.rb +7 -7
- data/lib/minitest/heat/output/marker.rb +3 -9
- data/lib/minitest/heat/output/results.rb +15 -37
- data/lib/minitest/heat/output/source_code.rb +4 -12
- data/lib/minitest/heat/output/token.rb +23 -22
- data/lib/minitest/heat/output.rb +9 -11
- data/lib/minitest/heat/source.rb +14 -27
- data/lib/minitest/heat/timer.rb +6 -12
- data/lib/minitest/heat/version.rb +1 -1
- data/lib/minitest/heat_plugin.rb +4 -1
- data/lib/minitest/heat_reporter.rb +19 -15
- data/minitest-heat.gemspec +1 -9
- metadata +13 -100
data/lib/minitest/heat/hit.rb
CHANGED
|
@@ -7,6 +7,7 @@ module Minitest
|
|
|
7
7
|
# Kind of like an issue, but instead of focusing on a failing test, it covers all issues for a
|
|
8
8
|
# given file to build a heat map of the affected files and line numbers
|
|
9
9
|
class Hit
|
|
10
|
+
# Represents a single occurrence of an issue at a specific line
|
|
10
11
|
Trace = Struct.new(:type, :line_number, :locations)
|
|
11
12
|
|
|
12
13
|
# So we can sort hot spots by liklihood of being the most important spot to check out before
|
|
@@ -62,23 +63,17 @@ module Minitest
|
|
|
62
63
|
# the most problematic across the various issue types
|
|
63
64
|
#
|
|
64
65
|
# @return [Integer] the problem weight for the file
|
|
65
|
-
def weight
|
|
66
|
-
issues.sum { |type, values| values.size * WEIGHTS.fetch(type, 0) }
|
|
67
|
-
end
|
|
66
|
+
def weight = issues.sum { |type, values| values.size * WEIGHTS.fetch(type, 0) }
|
|
68
67
|
|
|
69
68
|
# The total issue count for the file across all issue types. Includes duplicates if they exist
|
|
70
69
|
#
|
|
71
70
|
# @return [Integer] the sum of the counts for all line numbers for all issue types
|
|
72
|
-
def count
|
|
73
|
-
issues.sum { |_type, values| values.size }
|
|
74
|
-
end
|
|
71
|
+
def count = issues.sum { |_type, values| values.size }
|
|
75
72
|
|
|
76
73
|
# The full set of unique line numbers across all issue types
|
|
77
74
|
#
|
|
78
75
|
# @return [Array<Integer>] the full set of unique offending line numbers for the hit
|
|
79
|
-
def line_numbers
|
|
80
|
-
issues.values.flatten.uniq.sort
|
|
81
|
-
end
|
|
76
|
+
def line_numbers = issues.values.flatten.uniq.sort
|
|
82
77
|
|
|
83
78
|
# Generates a hash representation for JSON serialization
|
|
84
79
|
#
|
|
@@ -93,9 +88,7 @@ module Minitest
|
|
|
93
88
|
|
|
94
89
|
private
|
|
95
90
|
|
|
96
|
-
def relative_path
|
|
97
|
-
pathname.to_s.delete_prefix("#{Dir.pwd}/")
|
|
98
|
-
end
|
|
91
|
+
def relative_path = pathname.to_s.delete_prefix("#{Dir.pwd}/")
|
|
99
92
|
|
|
100
93
|
def lines_summary
|
|
101
94
|
line_numbers.map do |line_num|
|
data/lib/minitest/heat/issue.rb
CHANGED
|
@@ -90,7 +90,7 @@ module Minitest
|
|
|
90
90
|
# painfully slow and should get more attention.
|
|
91
91
|
#
|
|
92
92
|
# @return [Symbol] issue type for classifying issues and reporting
|
|
93
|
-
def type # rubocop:disable Metrics/
|
|
93
|
+
def type # rubocop:disable Metrics/PerceivedComplexity
|
|
94
94
|
if error? && in_test?
|
|
95
95
|
:broken
|
|
96
96
|
elsif error?
|
|
@@ -112,88 +112,66 @@ module Minitest
|
|
|
112
112
|
# (Because slow tests still pass and wouldn't otherwise be considered an issue.)
|
|
113
113
|
#
|
|
114
114
|
# @return [Boolean] true if the test did not pass or if it was slow
|
|
115
|
-
def hit?
|
|
116
|
-
!passed? || slow? || painful?
|
|
117
|
-
end
|
|
115
|
+
def hit? = !passed? || slow? || painful?
|
|
118
116
|
|
|
119
117
|
# The number, in seconds, for a test to be considered "slow"
|
|
120
118
|
#
|
|
121
119
|
# @return [Float] number of seconds after which a test is considered slow
|
|
122
|
-
def slow_threshold
|
|
123
|
-
Minitest::Heat.configuration.slow_threshold
|
|
124
|
-
end
|
|
120
|
+
def slow_threshold = Minitest::Heat.configuration.slow_threshold
|
|
125
121
|
|
|
126
122
|
# The number, in seconds, for a test to be considered "painfully slow"
|
|
127
123
|
#
|
|
128
124
|
# @return [Float] number of seconds after which a test is considered painfully slow
|
|
129
|
-
def painfully_slow_threshold
|
|
130
|
-
Minitest::Heat.configuration.painfully_slow_threshold
|
|
131
|
-
end
|
|
125
|
+
def painfully_slow_threshold = Minitest::Heat.configuration.painfully_slow_threshold
|
|
132
126
|
|
|
133
127
|
# Determines if a test should be considered slow by comparing it to the low end definition of
|
|
134
128
|
# what is considered slow.
|
|
135
129
|
#
|
|
136
130
|
# @return [Boolean] true if the test took longer to run than `slow_threshold`
|
|
137
|
-
def slow?
|
|
138
|
-
execution_time >= slow_threshold && execution_time < painfully_slow_threshold
|
|
139
|
-
end
|
|
131
|
+
def slow? = execution_time >= slow_threshold && execution_time < painfully_slow_threshold
|
|
140
132
|
|
|
141
133
|
# Determines if a test should be considered painfully slow by comparing it to the high end
|
|
142
134
|
# definition of what is considered slow.
|
|
143
135
|
#
|
|
144
136
|
# @return [Boolean] true if the test took longer to run than `painfully_slow_threshold`
|
|
145
|
-
def painful?
|
|
146
|
-
execution_time >= painfully_slow_threshold
|
|
147
|
-
end
|
|
137
|
+
def painful? = execution_time >= painfully_slow_threshold
|
|
148
138
|
|
|
149
139
|
# Determines if the issue is an exception that was raised from directly within a test
|
|
150
140
|
# definition. In these cases, it's more likely to be a quick fix.
|
|
151
141
|
#
|
|
152
142
|
# @return [Boolean] true if the final locations of the stacktrace was a test file
|
|
153
|
-
def in_test?
|
|
154
|
-
locations.broken_test?
|
|
155
|
-
end
|
|
143
|
+
def in_test? = locations.broken_test?
|
|
156
144
|
|
|
157
145
|
# Determines if the issue is an exception that was raised from directly within the project
|
|
158
146
|
# codebase.
|
|
159
147
|
#
|
|
160
148
|
# @return [Boolean] true if the final locations of the stacktrace was a file from the project
|
|
161
149
|
# (as opposed to a dependency or Ruby library)
|
|
162
|
-
def in_source?
|
|
163
|
-
locations.proper_failure?
|
|
164
|
-
end
|
|
150
|
+
def in_source? = locations.proper_failure?
|
|
165
151
|
|
|
166
152
|
# Was the result a pass? i.e. Skips aren't passes or failures. Slows are still passes. So this
|
|
167
153
|
# is purely a measure of whether the test explicitly passed all assertions
|
|
168
154
|
#
|
|
169
155
|
# @return [Boolean] false for errors, failures, or skips, true for passes (including slows)
|
|
170
|
-
def passed?
|
|
171
|
-
passed
|
|
172
|
-
end
|
|
156
|
+
def passed? = passed
|
|
173
157
|
|
|
174
158
|
# Was there an exception that triggered a failure?
|
|
175
159
|
#
|
|
176
160
|
# @return [Boolean] true if there's an exception
|
|
177
|
-
def error?
|
|
178
|
-
error
|
|
179
|
-
end
|
|
161
|
+
def error? = error
|
|
180
162
|
|
|
181
163
|
# Was the test skipped?
|
|
182
164
|
#
|
|
183
165
|
# @return [Boolean] true if the test was explicitly skipped, false otherwise
|
|
184
|
-
def skipped?
|
|
185
|
-
skipped
|
|
186
|
-
end
|
|
166
|
+
def skipped? = skipped
|
|
187
167
|
|
|
188
168
|
# The more nuanced detail of the failure. If it's an error, digs into the exception. Otherwise
|
|
189
169
|
# uses the message from the result
|
|
190
170
|
#
|
|
191
171
|
# @return [String] a more detailed explanation of the issue
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
error? ? first_line_of_exception_message : message
|
|
196
|
-
end
|
|
172
|
+
# When there's an exception, use the first line from the exception message. Otherwise, the
|
|
173
|
+
# message represents explanation for a test failure, and should be used in full
|
|
174
|
+
def summary = error? ? first_line_of_exception_message : message
|
|
197
175
|
|
|
198
176
|
# Returns the first line of an exception message when the issue is from a proper exception
|
|
199
177
|
# failure since exception messages can be long and cumbersome.
|
|
@@ -207,9 +185,7 @@ module Minitest
|
|
|
207
185
|
text.size > exception_message_limit ? "#{text[0..exception_message_limit]}..." : text
|
|
208
186
|
end
|
|
209
187
|
|
|
210
|
-
def exception_message_limit
|
|
211
|
-
200
|
|
212
|
-
end
|
|
188
|
+
def exception_message_limit = 200
|
|
213
189
|
|
|
214
190
|
# Generates a hash representation for JSON serialization
|
|
215
191
|
#
|
|
@@ -30,19 +30,12 @@ module Minitest
|
|
|
30
30
|
# Generates a formatted string describing the line of code similar to the original backtrace
|
|
31
31
|
#
|
|
32
32
|
# @return [String] a consistently-formatted, human-readable string about the line of code
|
|
33
|
-
def to_s
|
|
34
|
-
"#{absolute_path}#{filename}:#{line_number} in `#{container}`"
|
|
35
|
-
end
|
|
33
|
+
def to_s = "#{absolute_path}#{filename}:#{line_number} in `#{container}`"
|
|
36
34
|
|
|
37
35
|
# Generates a simplified location array with the pathname and line number
|
|
38
36
|
#
|
|
39
37
|
# @return [Array<Pathname, Integer>] a no-frills location pair
|
|
40
|
-
def to_a
|
|
41
|
-
[
|
|
42
|
-
pathname,
|
|
43
|
-
line_number
|
|
44
|
-
]
|
|
45
|
-
end
|
|
38
|
+
def to_a = [pathname, line_number]
|
|
46
39
|
|
|
47
40
|
# Generates a hash representation for JSON serialization
|
|
48
41
|
#
|
|
@@ -58,23 +51,24 @@ module Minitest
|
|
|
58
51
|
# A short relative pathname and line number pair
|
|
59
52
|
#
|
|
60
53
|
# @return [String] the short filename/line number combo. ex. `dir/file.rb:23`
|
|
61
|
-
def short
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
def short = "#{relative_filename}:#{line_number}"
|
|
55
|
+
|
|
56
|
+
# Determine if the file exists on disk
|
|
57
|
+
#
|
|
58
|
+
# @return [Boolean] true if the file exists
|
|
59
|
+
def file_exists? = pathname.exist?
|
|
64
60
|
|
|
65
61
|
# Determine if there is a file and text at the given line number
|
|
66
62
|
#
|
|
67
63
|
# @return [Boolean] true if the file exists and has text at the given line number
|
|
68
|
-
def exists?
|
|
69
|
-
pathname.exist? && source_code.lines.any?
|
|
70
|
-
end
|
|
64
|
+
def exists? = file_exists? && source_code.lines.any?
|
|
71
65
|
|
|
72
66
|
# The pathanme for the location. Written to be safe and fallbackto the project directory if
|
|
73
67
|
# an exception is raised ocnverting the value to a pathname
|
|
74
68
|
#
|
|
75
69
|
# @return [Pathname] a pathname instance for the relevant file
|
|
76
70
|
def pathname
|
|
77
|
-
Pathname(raw_pathname)
|
|
71
|
+
Pathname(raw_pathname).expand_path
|
|
78
72
|
rescue ArgumentError
|
|
79
73
|
Pathname(Dir.pwd)
|
|
80
74
|
end
|
|
@@ -83,33 +77,21 @@ module Minitest
|
|
|
83
77
|
#
|
|
84
78
|
# @return [String] either the path/directory portion of the file name or '(Unrecognized File)'
|
|
85
79
|
# if the offending file can't be found for some reason
|
|
86
|
-
def path
|
|
87
|
-
pathname.exist? ? pathname.dirname.to_s : UNRECOGNIZED
|
|
88
|
-
end
|
|
80
|
+
def path = file_exists? ? pathname.dirname.to_s : UNRECOGNIZED
|
|
89
81
|
|
|
90
|
-
def absolute_path
|
|
91
|
-
pathname.exist? ? "#{path}/" : UNRECOGNIZED
|
|
92
|
-
end
|
|
82
|
+
def absolute_path = file_exists? ? "#{path}/" : UNRECOGNIZED
|
|
93
83
|
|
|
94
|
-
def relative_path
|
|
95
|
-
pathname.exist? ? absolute_path.delete_prefix("#{project_root_dir}/") : UNRECOGNIZED
|
|
96
|
-
end
|
|
84
|
+
def relative_path = file_exists? ? absolute_path.delete_prefix("#{project_root_dir}/") : UNRECOGNIZED
|
|
97
85
|
|
|
98
86
|
# A safe interface for getting a string representing the filename portion of the file
|
|
99
87
|
#
|
|
100
88
|
# @return [String] either the filename portion of the file or '(Unrecognized File)'
|
|
101
89
|
# if the offending file can't be found for some reason
|
|
102
|
-
def filename
|
|
103
|
-
pathname.exist? ? pathname.basename.to_s : UNRECOGNIZED
|
|
104
|
-
end
|
|
90
|
+
def filename = file_exists? ? pathname.basename.to_s : UNRECOGNIZED
|
|
105
91
|
|
|
106
|
-
def absolute_filename
|
|
107
|
-
pathname.exist? ? pathname.to_s : UNRECOGNIZED
|
|
108
|
-
end
|
|
92
|
+
def absolute_filename = file_exists? ? pathname.to_s : UNRECOGNIZED
|
|
109
93
|
|
|
110
|
-
def relative_filename
|
|
111
|
-
pathname.exist? ? pathname.to_s.delete_prefix("#{project_root_dir}/") : UNRECOGNIZED
|
|
112
|
-
end
|
|
94
|
+
def relative_filename = file_exists? ? pathname.to_s.delete_prefix("#{project_root_dir}/") : UNRECOGNIZED
|
|
113
95
|
|
|
114
96
|
# Line number identifying the specific line in the file
|
|
115
97
|
#
|
|
@@ -124,9 +106,7 @@ module Minitest
|
|
|
124
106
|
# The containing method or block details for the location
|
|
125
107
|
#
|
|
126
108
|
# @return [String] the containing method of the line of code
|
|
127
|
-
def container
|
|
128
|
-
raw_container.nil? ? '(Unknown Container)' : String(raw_container)
|
|
129
|
-
end
|
|
109
|
+
def container = raw_container.nil? ? '(Unknown Container)' : String(raw_container)
|
|
130
110
|
|
|
131
111
|
# Looks up the source code for the location. Can return multiple lines of source code from
|
|
132
112
|
# the surrounding lines of code for the primary line
|
|
@@ -145,65 +125,47 @@ module Minitest
|
|
|
145
125
|
# Determines if a given file is from the project directory
|
|
146
126
|
#
|
|
147
127
|
# @return [Boolean] true if the file is in the project (source code or test) but not vendored
|
|
148
|
-
def project_file?
|
|
149
|
-
path.include?(project_root_dir) && !bundled_file? && !binstub_file?
|
|
150
|
-
end
|
|
128
|
+
def project_file? = path.include?(project_root_dir) && !bundled_file? && !binstub_file?
|
|
151
129
|
|
|
152
130
|
# Determines if the file is in the project `vendor/bundle` directory.
|
|
153
131
|
#
|
|
154
132
|
# @return [Boolean] true if the file is in `<project_root>/vendor/bundle
|
|
155
|
-
def bundled_file?
|
|
156
|
-
path.include?("#{project_root_dir}/vendor/bundle")
|
|
157
|
-
end
|
|
133
|
+
def bundled_file? = path.include?("#{project_root_dir}/vendor/bundle")
|
|
158
134
|
|
|
159
135
|
# Determines if the file is in the project `bin` directory. With binstub'd gems, they'll
|
|
160
136
|
# appear to be source code because the code is located in the project directory. This helps
|
|
161
137
|
# make sure the backtraces don't think that's the case
|
|
162
138
|
#
|
|
163
139
|
# @return [Boolean] true if the file is in `<project_root>/bin
|
|
164
|
-
def binstub_file?
|
|
165
|
-
path.include?("#{project_root_dir}/bin")
|
|
166
|
-
end
|
|
140
|
+
def binstub_file? = path.include?("#{project_root_dir}/bin")
|
|
167
141
|
|
|
168
142
|
# Determines if a given file follows the standard approaching to naming test files.
|
|
169
143
|
#
|
|
170
144
|
# @return [Boolean] true if the file name starts with `test_` or ends with `_test.rb`
|
|
171
|
-
def test_file?
|
|
172
|
-
filename.to_s.start_with?('test_') || filename.to_s.end_with?('_test.rb')
|
|
173
|
-
end
|
|
145
|
+
def test_file? = filename.to_s.start_with?('test_') || filename.to_s.end_with?('_test.rb')
|
|
174
146
|
|
|
175
147
|
# Determines if a given file is a non-test file from the project directory
|
|
176
148
|
#
|
|
177
149
|
# @return [Boolean] true if the file is in the project but not a test file or vendored file
|
|
178
|
-
def source_code_file?
|
|
179
|
-
project_file? && !test_file?
|
|
180
|
-
end
|
|
150
|
+
def source_code_file? = project_file? && !test_file?
|
|
181
151
|
|
|
182
152
|
# A safe interface to getting the last modified time for the file in question
|
|
183
153
|
#
|
|
184
154
|
# @return [Time] the timestamp for when the file in question was last modified or `Time.at(0)`
|
|
185
155
|
# if the offending file can't be found for some reason
|
|
186
|
-
def mtime
|
|
187
|
-
pathname.exist? ? pathname.mtime : UNKNOWN_MODIFICATION_TIME
|
|
188
|
-
end
|
|
156
|
+
def mtime = file_exists? ? pathname.mtime : UNKNOWN_MODIFICATION_TIME
|
|
189
157
|
|
|
190
158
|
# A safe interface to getting the number of seconds since the file was modified
|
|
191
159
|
#
|
|
192
160
|
# @return [Integer] the number of seconds since the file was modified or `-1` if the offending
|
|
193
161
|
# file can't be found for some reason
|
|
194
|
-
def age_in_seconds
|
|
195
|
-
pathname.exist? ? seconds_ago : UNKNOWN_MODIFICATION_SECONDS
|
|
196
|
-
end
|
|
162
|
+
def age_in_seconds = file_exists? ? seconds_ago : UNKNOWN_MODIFICATION_SECONDS
|
|
197
163
|
|
|
198
164
|
private
|
|
199
165
|
|
|
200
|
-
def project_root_dir
|
|
201
|
-
Dir.pwd
|
|
202
|
-
end
|
|
166
|
+
def project_root_dir = Dir.pwd
|
|
203
167
|
|
|
204
|
-
def seconds_ago
|
|
205
|
-
(Time.now - mtime).to_i
|
|
206
|
-
end
|
|
168
|
+
def seconds_ago = (Time.now - mtime).to_i
|
|
207
169
|
end
|
|
208
170
|
end
|
|
209
171
|
end
|
|
@@ -28,27 +28,21 @@ module Minitest
|
|
|
28
28
|
# test failure
|
|
29
29
|
#
|
|
30
30
|
# @return [String] ex. 'path/to/file.rb:12'
|
|
31
|
-
def to_s
|
|
32
|
-
"#{most_relevant.absolute_filename}:#{most_relevant.line_number}"
|
|
33
|
-
end
|
|
31
|
+
def to_s = "#{most_relevant.absolute_filename}:#{most_relevant.line_number}"
|
|
34
32
|
|
|
35
33
|
# Knows if the failure is contained within the test. For example, if there's bad code in a
|
|
36
34
|
# test, and it raises an exception, then it's really a broken test rather than a proper
|
|
37
35
|
# faiure.
|
|
38
36
|
#
|
|
39
37
|
# @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
|
|
38
|
+
def broken_test? = !test_failure.nil? && test_failure == final
|
|
43
39
|
|
|
44
40
|
# Knows if the failure occurred in the actual project source code—as opposed to the test or
|
|
45
41
|
# an external piece of code like a gem.
|
|
46
42
|
#
|
|
47
43
|
# @return [Boolean] true if there's a non-test project file in the stacktrace but it's not
|
|
48
44
|
# a result of a broken test
|
|
49
|
-
def proper_failure?
|
|
50
|
-
!source_code.nil? && !broken_test?
|
|
51
|
-
end
|
|
45
|
+
def proper_failure? = !source_code.nil? && !broken_test?
|
|
52
46
|
|
|
53
47
|
# The file most likely to be the source of the underlying problem. Often, the most recent
|
|
54
48
|
# backtrace files will be a gem or external library that's failing indirectly as a result
|
|
@@ -64,9 +58,7 @@ module Minitest
|
|
|
64
58
|
].compact.first
|
|
65
59
|
end
|
|
66
60
|
|
|
67
|
-
def freshest
|
|
68
|
-
backtrace.recently_modified_locations.first
|
|
69
|
-
end
|
|
61
|
+
def freshest = backtrace.recently_modified_locations.first
|
|
70
62
|
|
|
71
63
|
# Returns the final test location based on the backtrace if present. Otherwise falls back to
|
|
72
64
|
# the test location which represents the test definition. The `test_definition` attribute
|
|
@@ -74,32 +66,24 @@ module Minitest
|
|
|
74
66
|
# line from within the test where the problem occurred
|
|
75
67
|
#
|
|
76
68
|
# @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
|
|
69
|
+
def test_failure = backtrace.test_locations.any? ? backtrace.test_locations.first : test_definition
|
|
80
70
|
|
|
81
71
|
# Returns the final source code location based on the backtrace
|
|
82
72
|
#
|
|
83
73
|
# @return [Location] the final location from the source code files
|
|
84
|
-
def source_code
|
|
85
|
-
backtrace.source_code_locations.first
|
|
86
|
-
end
|
|
74
|
+
def source_code = backtrace.source_code_locations.first
|
|
87
75
|
|
|
88
76
|
# Returns the final project location based on the backtrace if present. Otherwise falls back
|
|
89
77
|
# to the test location which represents the test definition.
|
|
90
78
|
#
|
|
91
79
|
# @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
|
|
80
|
+
def project = backtrace.project_locations.any? ? backtrace.project_locations.first : test_definition
|
|
95
81
|
|
|
96
82
|
# The line number from within the `test_file` test definition where the failure occurred
|
|
97
83
|
#
|
|
98
84
|
# @return [Location] the last location from the backtrace or the test location if a backtrace
|
|
99
85
|
# was not passed to the initializer
|
|
100
|
-
def final
|
|
101
|
-
backtrace.locations.any? ? backtrace.locations.first : test_definition
|
|
102
|
-
end
|
|
86
|
+
def final = backtrace.locations.any? ? backtrace.locations.first : test_definition
|
|
103
87
|
end
|
|
104
88
|
end
|
|
105
89
|
end
|
data/lib/minitest/heat/map.rb
CHANGED
|
@@ -26,25 +26,19 @@ module Minitest
|
|
|
26
26
|
# Returns a subset of affected files to keep the list from being overwhelming
|
|
27
27
|
#
|
|
28
28
|
# @return [Array] the list of files and the line numbers for each encountered issue type
|
|
29
|
-
def file_hits
|
|
30
|
-
hot_files.take(MAXIMUM_FILES_TO_SHOW)
|
|
31
|
-
end
|
|
29
|
+
def file_hits = hot_files.take(MAXIMUM_FILES_TO_SHOW)
|
|
32
30
|
|
|
33
31
|
# Generates a hash representation for JSON serialization
|
|
34
32
|
#
|
|
35
33
|
# @return [Array<Hash>] array of hit hashes sorted by weight (highest first)
|
|
36
|
-
def to_h
|
|
37
|
-
hot_files.map(&:to_h)
|
|
38
|
-
end
|
|
34
|
+
def to_h = hot_files.map(&:to_h)
|
|
39
35
|
|
|
40
36
|
private
|
|
41
37
|
|
|
42
38
|
# Sorts the files by hit "weight" so that the most problematic files are at the beginning
|
|
43
39
|
#
|
|
44
40
|
# @return [Array] the collection of files that encountred issues
|
|
45
|
-
def hot_files
|
|
46
|
-
hits.values.sort_by(&:weight).reverse
|
|
47
|
-
end
|
|
41
|
+
def hot_files = hits.values.sort_by(&:weight).reverse
|
|
48
42
|
end
|
|
49
43
|
end
|
|
50
44
|
end
|
|
@@ -23,16 +23,14 @@ module Minitest
|
|
|
23
23
|
# Determines the number of lines to display from the backtrace.
|
|
24
24
|
#
|
|
25
25
|
# @return [Integer] the number of lines to limit the backtrace to
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
::Minitest::Heat::Backtrace::LineCount.new(backtrace.locations).limit
|
|
35
|
-
end
|
|
26
|
+
# Defined as a method instead of using the constant directly in order to easily support
|
|
27
|
+
# adding options for controlling how many lines are displayed from a backtrace.
|
|
28
|
+
#
|
|
29
|
+
# For example, instead of a fixed number, the backtrace could dynamically calculate how
|
|
30
|
+
# many lines it should displaye in order to get to the origination point. Or it could have
|
|
31
|
+
# a default, but inteligently go back further if the backtrace meets some criteria for
|
|
32
|
+
# displaying more lines.
|
|
33
|
+
def line_count = ::Minitest::Heat::Backtrace::LineCount.new(backtrace.locations).limit
|
|
36
34
|
|
|
37
35
|
# A subset of parsed lines from the backtrace.
|
|
38
36
|
#
|
|
@@ -69,16 +67,14 @@ module Minitest
|
|
|
69
67
|
*file_and_line_number_tokens(location),
|
|
70
68
|
containining_element_token(location),
|
|
71
69
|
source_code_line_token(location),
|
|
72
|
-
most_recently_modified_token(location)
|
|
70
|
+
most_recently_modified_token(location)
|
|
73
71
|
].compact
|
|
74
72
|
end
|
|
75
73
|
|
|
76
74
|
# Determines if all lines to be displayed are from within the project directory
|
|
77
75
|
#
|
|
78
76
|
# @return [Boolean] true if all lines of the backtrace being displayed are from the project
|
|
79
|
-
def all_backtrace_from_project?
|
|
80
|
-
backtrace_locations.all?(&:project_file?)
|
|
81
|
-
end
|
|
77
|
+
def all_backtrace_from_project? = backtrace_locations.all?(&:project_file?)
|
|
82
78
|
|
|
83
79
|
# Determines if the file referenced by a backtrace line is the most recently modified file
|
|
84
80
|
# of all the files referenced in the visible backtrace locations.
|
|
@@ -87,16 +83,12 @@ module Minitest
|
|
|
87
83
|
#
|
|
88
84
|
# @return [<type>] <description>
|
|
89
85
|
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
backtrace_locations.size > 1 && location == locations.freshest
|
|
95
|
-
end
|
|
86
|
+
# If there's more than one line being displayed (otherwise, with one line, of course it's
|
|
87
|
+
# the most recently modified because there_aren't any others) and the current line is the
|
|
88
|
+
# same as the freshest location in the backtrace
|
|
89
|
+
def most_recently_modified?(location) = backtrace_locations.size > 1 && location == locations.freshest
|
|
96
90
|
|
|
97
|
-
def indentation_token
|
|
98
|
-
[:default, ' ' * indentation]
|
|
99
|
-
end
|
|
91
|
+
def indentation_token = [:default, ' ' * indentation]
|
|
100
92
|
|
|
101
93
|
def path_token(location)
|
|
102
94
|
# If the line is a project file, help it stand out from the backtrace noise
|
|
@@ -145,9 +137,7 @@ module Minitest
|
|
|
145
137
|
# option that would reduce the space used.
|
|
146
138
|
#
|
|
147
139
|
# @return [type] [description]
|
|
148
|
-
def indentation
|
|
149
|
-
DEFAULT_INDENTATION_SPACES
|
|
150
|
-
end
|
|
140
|
+
def indentation = DEFAULT_INDENTATION_SPACES
|
|
151
141
|
end
|
|
152
142
|
end
|
|
153
143
|
end
|
|
@@ -19,7 +19,7 @@ module Minitest
|
|
|
19
19
|
next unless relevant_issue_types?(hit)
|
|
20
20
|
|
|
21
21
|
# Add a new line
|
|
22
|
-
@tokens << [[:muted,
|
|
22
|
+
@tokens << [[:muted, '']]
|
|
23
23
|
|
|
24
24
|
# Build the summary line for the file
|
|
25
25
|
@tokens << file_summary_tokens(hit)
|
|
@@ -54,10 +54,10 @@ module Minitest
|
|
|
54
54
|
# 1. Only pull the traces that have proper locations
|
|
55
55
|
# 2. Sort the traces by the most recent line number so they're displayed in numeric order
|
|
56
56
|
# 3. Get the final relevant location from the trace
|
|
57
|
-
traces
|
|
58
|
-
select { |trace| trace.locations.any? }
|
|
59
|
-
sort_by { |trace| trace.locations.last.line_number }
|
|
60
|
-
map { |trace| origination_location_token(trace) }
|
|
57
|
+
traces
|
|
58
|
+
.select { |trace| trace.locations.any? }
|
|
59
|
+
.sort_by { |trace| trace.locations.last.line_number }
|
|
60
|
+
.map { |trace| origination_location_token(trace) }
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def file_summary_tokens(hit)
|
|
@@ -80,7 +80,7 @@ module Minitest
|
|
|
80
80
|
[:muted, ':'],
|
|
81
81
|
[:default, location.line_number],
|
|
82
82
|
[:muted, " in #{location.container}"],
|
|
83
|
-
[:muted, " #{Output::SYMBOLS[:arrow]} `#{location.source_code.line.strip}`"]
|
|
83
|
+
[:muted, " #{Output::SYMBOLS[:arrow]} `#{location.source_code.line.strip}`"]
|
|
84
84
|
]
|
|
85
85
|
end
|
|
86
86
|
|
|
@@ -160,7 +160,7 @@ module Minitest
|
|
|
160
160
|
def line_number_token(style, line_number, frequency)
|
|
161
161
|
if frequency > 1
|
|
162
162
|
[
|
|
163
|
-
[style,
|
|
163
|
+
[style, line_number.to_s],
|
|
164
164
|
[:muted, "✕#{frequency} "]
|
|
165
165
|
]
|
|
166
166
|
else
|
|
@@ -33,19 +33,13 @@ module Minitest
|
|
|
33
33
|
@issue_type = issue_type
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def token
|
|
37
|
-
[style, symbol]
|
|
38
|
-
end
|
|
36
|
+
def token = [style, symbol]
|
|
39
37
|
|
|
40
38
|
private
|
|
41
39
|
|
|
42
|
-
def style
|
|
43
|
-
STYLES.fetch(issue_type, :default)
|
|
44
|
-
end
|
|
40
|
+
def style = STYLES.fetch(issue_type, :default)
|
|
45
41
|
|
|
46
|
-
def symbol
|
|
47
|
-
SYMBOLS.fetch(issue_type, '?')
|
|
48
|
-
end
|
|
42
|
+
def symbol = SYMBOLS.fetch(issue_type, '?')
|
|
49
43
|
end
|
|
50
44
|
end
|
|
51
45
|
end
|