minitest-heat 0.0.1 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -1
- data/lib/minitest/heat/backtrace.rb +48 -16
- data/lib/minitest/heat/issue.rb +36 -16
- data/lib/minitest/heat/location.rb +133 -22
- data/lib/minitest/heat/map.rb +7 -6
- data/lib/minitest/heat/output/backtrace.rb +131 -0
- data/lib/minitest/heat/output/issue.rb +18 -0
- data/lib/minitest/heat/output/location.rb +20 -0
- data/lib/minitest/heat/output/map.rb +20 -0
- data/lib/minitest/heat/output/results.rb +106 -0
- data/lib/minitest/heat/output/source_code.rb +131 -0
- data/lib/minitest/heat/output/token.rb +99 -0
- data/lib/minitest/heat/output.rb +71 -153
- data/lib/minitest/heat/results.rb +8 -10
- data/lib/minitest/heat/source.rb +6 -1
- data/lib/minitest/heat/version.rb +1 -1
- data/lib/minitest/heat_reporter.rb +14 -11
- data/minitest-heat.gemspec +1 -0
- metadata +23 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c760c15a45811d5773dee18e32aadc063ed61c3eb04447c637719d5d3d46178a
|
4
|
+
data.tar.gz: 90c345c56cbd2589719eef8a0264a5973f77a8104c91149f4e4ad2320067a49a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 47be296804a2171023bfcdb5c4192376698349bd76c4c3a2991d9accd9f631b88d2ffb867d821f2479c41bd6fd2cf919af325ecde975f98ee4592277b3b09432
|
7
|
+
data.tar.gz: 79a180260e9a1d8c55d898883f13969f2576ceb6450dc9e080050e55674c73fc7979202a2c8a78542958491fce91fee2a3cb1b66fd2705edc4a8d432f5e28fcc
|
data/Gemfile.lock
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
minitest-heat (0.0.
|
4
|
+
minitest-heat (0.0.5)
|
5
5
|
minitest
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
10
|
coderay (1.1.3)
|
11
|
+
dead_end (1.1.7)
|
11
12
|
docile (1.4.0)
|
12
13
|
method_source (1.0.0)
|
13
14
|
minitest (5.14.4)
|
@@ -26,6 +27,7 @@ PLATFORMS
|
|
26
27
|
ruby
|
27
28
|
|
28
29
|
DEPENDENCIES
|
30
|
+
dead_end
|
29
31
|
minitest (~> 5.0)
|
30
32
|
minitest-heat!
|
31
33
|
pry
|
@@ -6,11 +6,23 @@ module Minitest
|
|
6
6
|
class Backtrace
|
7
7
|
Line = Struct.new(:path, :file, :number, :container, :mtime, keyword_init: true) do
|
8
8
|
def to_s
|
9
|
-
"#{
|
9
|
+
"#{location} in `#{container}`"
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
"#{path}/#{file}"
|
12
|
+
def pathname
|
13
|
+
Pathname("#{path}/#{file}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def location
|
17
|
+
"#{pathname.to_s}:#{number}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def short_pathname
|
21
|
+
pathname.delete_prefix(Dir.pwd)
|
22
|
+
end
|
23
|
+
|
24
|
+
def short_location
|
25
|
+
"#{pathname.basename.to_s}:#{number}"
|
14
26
|
end
|
15
27
|
|
16
28
|
def age_in_seconds
|
@@ -18,6 +30,16 @@ module Minitest
|
|
18
30
|
end
|
19
31
|
end
|
20
32
|
|
33
|
+
UNREADABLE_FILE_ATTRIBUTES = {
|
34
|
+
path: '(Unknown Path)',
|
35
|
+
file: '(Unknown File)',
|
36
|
+
number: '(Unknown Line Number)',
|
37
|
+
container: '(Unknown Method)',
|
38
|
+
mtime: '(Unknown Modification Time)'
|
39
|
+
}
|
40
|
+
|
41
|
+
UNREADABLE_LINE = Line.new(UNREADABLE_FILE_ATTRIBUTES)
|
42
|
+
|
21
43
|
attr_reader :raw_backtrace
|
22
44
|
|
23
45
|
def initialize(raw_backtrace)
|
@@ -29,37 +51,45 @@ module Minitest
|
|
29
51
|
end
|
30
52
|
|
31
53
|
def final_location
|
32
|
-
|
54
|
+
parsed_lines.first
|
33
55
|
end
|
34
56
|
|
35
57
|
def final_project_location
|
36
|
-
|
58
|
+
project_lines.first
|
59
|
+
end
|
60
|
+
|
61
|
+
def freshest_project_location
|
62
|
+
recently_modified_lines.first
|
63
|
+
end
|
64
|
+
|
65
|
+
def final_source_code_location
|
66
|
+
source_code_lines.first
|
37
67
|
end
|
38
68
|
|
39
69
|
def final_test_location
|
40
|
-
|
70
|
+
test_lines.first
|
41
71
|
end
|
42
72
|
|
43
|
-
def
|
44
|
-
|
73
|
+
def project_lines
|
74
|
+
@project_lines ||= parsed_lines.select { |line| line[:path].include?(Dir.pwd) }
|
45
75
|
end
|
46
76
|
|
47
|
-
def
|
48
|
-
@
|
77
|
+
def recently_modified_lines
|
78
|
+
@recently_modified_lines ||= project_lines.sort_by { |line| line[:mtime] }.reverse
|
49
79
|
end
|
50
80
|
|
51
|
-
def
|
52
|
-
@
|
81
|
+
def test_lines
|
82
|
+
@tests_lines ||= project_lines.select { |line| test_file?(line) }
|
53
83
|
end
|
54
84
|
|
55
|
-
def
|
56
|
-
@
|
85
|
+
def source_code_lines
|
86
|
+
@source_code_lines ||= project_lines - test_lines
|
57
87
|
end
|
58
88
|
|
59
|
-
def
|
89
|
+
def parsed_lines
|
60
90
|
return [] if raw_backtrace.nil?
|
61
91
|
|
62
|
-
@
|
92
|
+
@parsed_lines ||= raw_backtrace.map { |line| parse(line) }
|
63
93
|
end
|
64
94
|
|
65
95
|
private
|
@@ -83,6 +113,8 @@ module Minitest
|
|
83
113
|
container: reduce_container(parts[2]),
|
84
114
|
mtime: pathname.exist? ? pathname.mtime : nil
|
85
115
|
}
|
116
|
+
rescue
|
117
|
+
UNREADABLE_FILE_ATTRIBUTES
|
86
118
|
end
|
87
119
|
|
88
120
|
def test_file?(line)
|
data/lib/minitest/heat/issue.rb
CHANGED
@@ -8,7 +8,20 @@ module Minitest
|
|
8
8
|
class Issue
|
9
9
|
extend Forwardable
|
10
10
|
|
11
|
-
|
11
|
+
SLOW_THRESHOLDS = {
|
12
|
+
slow: 1.0,
|
13
|
+
painful: 3.0
|
14
|
+
}
|
15
|
+
|
16
|
+
MARKERS = {
|
17
|
+
success: '·',
|
18
|
+
slow: '–',
|
19
|
+
painful: '—',
|
20
|
+
broken: 'B',
|
21
|
+
error: 'E',
|
22
|
+
skipped: 'S',
|
23
|
+
failure: 'F',
|
24
|
+
}
|
12
25
|
|
13
26
|
SHARED_SYMBOLS = {
|
14
27
|
spacer: ' · ',
|
@@ -27,10 +40,14 @@ module Minitest
|
|
27
40
|
@location = Location.new(result.source_location, @failure&.backtrace)
|
28
41
|
end
|
29
42
|
|
43
|
+
def short_location
|
44
|
+
location.to_s.delete_prefix(Dir.pwd)
|
45
|
+
end
|
46
|
+
|
30
47
|
def to_hit
|
31
48
|
[
|
32
|
-
location.
|
33
|
-
location.
|
49
|
+
location.project_file.to_s,
|
50
|
+
location.project_failure_line,
|
34
51
|
type
|
35
52
|
]
|
36
53
|
end
|
@@ -52,6 +69,8 @@ module Minitest
|
|
52
69
|
:skipped
|
53
70
|
elsif !passed?
|
54
71
|
:failure
|
72
|
+
elsif painful?
|
73
|
+
:painful
|
55
74
|
elsif slow?
|
56
75
|
:slow
|
57
76
|
else
|
@@ -59,20 +78,28 @@ module Minitest
|
|
59
78
|
end
|
60
79
|
end
|
61
80
|
|
81
|
+
def hit?
|
82
|
+
!passed? || slow?
|
83
|
+
end
|
84
|
+
|
62
85
|
def slow?
|
63
|
-
time
|
86
|
+
time >= SLOW_THRESHOLDS[:slow]
|
87
|
+
end
|
88
|
+
|
89
|
+
def painful?
|
90
|
+
time >= SLOW_THRESHOLDS[:painful]
|
64
91
|
end
|
65
92
|
|
66
93
|
def in_test?
|
67
|
-
location.
|
94
|
+
location.broken_test?
|
68
95
|
end
|
69
96
|
|
70
97
|
def in_source?
|
71
|
-
location.
|
98
|
+
location.proper_failure?
|
72
99
|
end
|
73
100
|
|
74
101
|
def test_class
|
75
|
-
result.klass
|
102
|
+
result.klass
|
76
103
|
end
|
77
104
|
|
78
105
|
def test_identifier
|
@@ -100,7 +127,7 @@ module Minitest
|
|
100
127
|
# When the exception came out of the test itself, that's a different kind of exception
|
101
128
|
# that really only indicates there's a problem with the code in the test. It's kind of
|
102
129
|
# between an error and a test.
|
103
|
-
'
|
130
|
+
'Test Error'
|
104
131
|
elsif error? || !passed?
|
105
132
|
failure.result_label
|
106
133
|
elsif slow?
|
@@ -111,14 +138,7 @@ module Minitest
|
|
111
138
|
end
|
112
139
|
|
113
140
|
def marker
|
114
|
-
|
115
|
-
when :broken then 'B'
|
116
|
-
when :error then 'E'
|
117
|
-
when :skipped then 'S'
|
118
|
-
when :failure then 'F'
|
119
|
-
when :slow then 'S'
|
120
|
-
else '.'
|
121
|
-
end
|
141
|
+
MARKERS.fetch(type.to_sym)
|
122
142
|
end
|
123
143
|
|
124
144
|
def summary
|
@@ -2,6 +2,17 @@
|
|
2
2
|
|
3
3
|
module Minitest
|
4
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' represents the last line from the project's tests. It is further differentiated by
|
10
|
+
# the line where the test is defined and the actual line of code in the test that geneated
|
11
|
+
# the failure or exception
|
12
|
+
# - 'source_code' represents the last line from the project's source code
|
13
|
+
# - 'project' represents the last source line, but falls back to the last test line
|
14
|
+
# - 'most_relevant' represents the most specific file to investigate starting with the source
|
15
|
+
# code and then looking to the test code with final line of the backtrace as a fallback
|
5
16
|
class Location
|
6
17
|
attr_reader :test_location, :backtrace
|
7
18
|
|
@@ -10,52 +21,152 @@ module Minitest
|
|
10
21
|
@backtrace = Backtrace.new(backtrace)
|
11
22
|
end
|
12
23
|
|
24
|
+
# Prints the pathname and line number of the location most likely to be the source of the
|
25
|
+
# test failure
|
26
|
+
#
|
27
|
+
# @return [String] ex. 'path/to/file.rb:12'
|
13
28
|
def to_s
|
14
|
-
"#{
|
29
|
+
"#{most_relevant_file}:#{most_relevant_failure_line}"
|
15
30
|
end
|
16
31
|
|
17
|
-
def
|
18
|
-
|
32
|
+
def local?
|
33
|
+
broken_test? || proper_failure?
|
19
34
|
end
|
20
35
|
|
21
|
-
|
22
|
-
|
36
|
+
# Knows if the failure is contained within the test. For example, if there's bad code in a
|
37
|
+
# test, and it raises an exception, then it's really a broken test rather than a proper
|
38
|
+
# faiure.
|
39
|
+
#
|
40
|
+
# @return [Boolean] true if most relevant file is the same as the test location file
|
41
|
+
def broken_test?
|
42
|
+
!test_file.nil? && test_file == most_relevant_file
|
23
43
|
end
|
24
44
|
|
45
|
+
# Knows if the failure occurred in the actual project source code—as opposed to the test or
|
46
|
+
# an external piece of code like a gem.
|
47
|
+
#
|
48
|
+
# @return [Boolean] true if there's a non-test project file in the stacktrace but it's not
|
49
|
+
# a result of a broken test
|
50
|
+
def proper_failure?
|
51
|
+
!source_code_file.nil? && !broken_test?
|
52
|
+
end
|
53
|
+
|
54
|
+
# The file most likely to be the source of the underlying problem. Often, the most recent
|
55
|
+
# backtrace files will be a gem or external library that's failing indirectly as a result
|
56
|
+
# of a problem with local source code (not always, but frequently). In that case, the best
|
57
|
+
# first place to focus is on the code you control.
|
58
|
+
#
|
59
|
+
# @return [String] the relative path to the file from the project root
|
60
|
+
def most_relevant_file
|
61
|
+
Pathname(most_relevant_location[0])
|
62
|
+
end
|
63
|
+
|
64
|
+
# The line number of the `most_relevant_file` where the failure originated
|
65
|
+
#
|
66
|
+
# @return [Integer] line number
|
67
|
+
def most_relevant_failure_line
|
68
|
+
most_relevant_location[1]
|
69
|
+
end
|
70
|
+
|
71
|
+
# The final location of the stacktrace regardless of whether it's from within the project
|
72
|
+
#
|
73
|
+
# @return [String] the relative path to the file from the project root
|
74
|
+
def final_file
|
75
|
+
Pathname(final_location[0])
|
76
|
+
end
|
77
|
+
|
78
|
+
# The line number of the `final_file` where the failure originated
|
79
|
+
#
|
80
|
+
# @return [Integer] line number
|
81
|
+
def final_failure_line
|
82
|
+
final_location[1]
|
83
|
+
end
|
84
|
+
|
85
|
+
# The final location of the stacktrace regardless of whether it's from within the project
|
86
|
+
#
|
87
|
+
# @return [String] the relative path to the file from the project root
|
88
|
+
def project_file
|
89
|
+
broken_test? ? test_file : source_code_file
|
90
|
+
end
|
91
|
+
|
92
|
+
# The line number of the `project_file` where the failure originated
|
93
|
+
#
|
94
|
+
# @return [Integer] line number
|
95
|
+
def project_failure_line
|
96
|
+
broken_test? ? test_failure_line || test_definition_line : source_code_failure_line
|
97
|
+
end
|
98
|
+
|
99
|
+
# The final location from the stacktrace that is within the project directory
|
100
|
+
#
|
101
|
+
# @return [String, nil] the relative path to the file from the project root
|
102
|
+
def source_code_file
|
103
|
+
return nil unless backtrace.source_code_lines.any?
|
104
|
+
|
105
|
+
backtrace.final_source_code_location.pathname
|
106
|
+
end
|
107
|
+
|
108
|
+
# The line number of the `source_code_file` where the failure originated
|
109
|
+
#
|
110
|
+
# @return [Integer] line number
|
111
|
+
def source_code_failure_line
|
112
|
+
return nil unless backtrace.source_code_lines.any?
|
113
|
+
|
114
|
+
backtrace.final_source_code_location.number
|
115
|
+
end
|
116
|
+
|
117
|
+
# The final location from the stacktrace that is within the project's test directory
|
118
|
+
#
|
119
|
+
# @return [String, nil] the relative path to the file from the project root
|
25
120
|
def test_file
|
26
|
-
|
121
|
+
Pathname(test_location[0])
|
27
122
|
end
|
28
123
|
|
124
|
+
# The line number of the `test_file` where the test is defined
|
125
|
+
#
|
126
|
+
# @return [Integer] line number
|
29
127
|
def test_definition_line
|
30
128
|
test_location[1].to_s
|
31
129
|
end
|
32
130
|
|
131
|
+
# The line number from within the `test_file` test definition where the failure occurred
|
132
|
+
#
|
133
|
+
# @return [Integer] line number
|
33
134
|
def test_failure_line
|
34
|
-
|
135
|
+
backtrace.final_test_location&.number || test_definition_line
|
35
136
|
end
|
36
137
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
138
|
+
# The line number from within the `test_file` test definition where the failure occurred
|
139
|
+
#
|
140
|
+
# @return [Location] the last location from the backtrace or the test location if a backtrace
|
141
|
+
# was not passed to the initializer
|
142
|
+
def final_location
|
143
|
+
backtrace? ? backtrace.final_location : test_location
|
43
144
|
end
|
44
145
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
146
|
+
# The file most likely to be the source of the underlying problem. Often, the most recent
|
147
|
+
# backtrace files will be a gem or external library that's failing indirectly as a result
|
148
|
+
# of a problem with local source code (not always, but frequently). In that case, the best
|
149
|
+
# first place to focus is on the code you control.
|
150
|
+
#
|
151
|
+
# @return [Array] file and line number of the most likely source of the problem
|
152
|
+
def most_relevant_location
|
153
|
+
[
|
154
|
+
source_code_location,
|
155
|
+
test_location,
|
156
|
+
final_location
|
157
|
+
].compact.first
|
49
158
|
end
|
50
159
|
|
51
|
-
def
|
52
|
-
|
160
|
+
def project_location
|
161
|
+
source_code_location || test_location
|
53
162
|
end
|
54
163
|
|
55
|
-
|
164
|
+
def source_code_location
|
165
|
+
backtrace.final_source_code_location
|
166
|
+
end
|
56
167
|
|
57
|
-
def
|
58
|
-
|
168
|
+
def backtrace?
|
169
|
+
backtrace.parsed_lines.any?
|
59
170
|
end
|
60
171
|
end
|
61
172
|
end
|
data/lib/minitest/heat/map.rb
CHANGED
@@ -13,11 +13,12 @@ module Minitest
|
|
13
13
|
# test is broken (i.e. raising an exception), that's a special sort of failure that would be
|
14
14
|
# misleading. It doesn't represent a proper failure, but rather a test that doesn't work.
|
15
15
|
WEIGHTS = {
|
16
|
-
error: 3,
|
17
|
-
broken: 1,
|
18
|
-
failure: 1,
|
19
|
-
skipped: 0,
|
20
|
-
|
16
|
+
error: 3, # exceptions from source code have the highest liklihood of a ripple effect
|
17
|
+
broken: 1, # broken tests won't have ripple effects but can't help if they can't run
|
18
|
+
failure: 1, # failures are kind of the whole point, and they could have ripple effects
|
19
|
+
skipped: 0, # skips aren't failures, but they shouldn't go ignored
|
20
|
+
painful: 0, # slow tests aren't failures, but they shouldn't be ignored
|
21
|
+
slow: 0,
|
21
22
|
}
|
22
23
|
|
23
24
|
def initialize
|
@@ -46,7 +47,7 @@ module Minitest
|
|
46
47
|
files = {}
|
47
48
|
@hits.each_pair do |filename, details|
|
48
49
|
# Can't really be a "hot spot" with just a single issue
|
49
|
-
next unless details[:
|
50
|
+
# next unless details[:weight] > 1
|
50
51
|
|
51
52
|
files[filename] = details[:weight]
|
52
53
|
end
|