minitest-heat 0.0.2 → 0.0.6
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/.gitignore +1 -0
- data/.rubocop.yml +23 -0
- data/Gemfile +5 -3
- data/Gemfile.lock +32 -1
- data/README.md +12 -3
- data/Rakefile +8 -6
- data/lib/minitest/heat/backtrace.rb +25 -48
- data/lib/minitest/heat/hit.rb +83 -0
- data/lib/minitest/heat/issue.rb +67 -32
- data/lib/minitest/heat/line.rb +74 -0
- data/lib/minitest/heat/location.rb +144 -23
- data/lib/minitest/heat/map.rb +14 -24
- data/lib/minitest/heat/output/backtrace.rb +121 -0
- data/lib/minitest/heat/output/issue.rb +128 -0
- data/lib/minitest/heat/output/map.rb +67 -0
- data/lib/minitest/heat/output/marker.rb +50 -0
- data/lib/minitest/heat/output/results.rb +118 -0
- data/lib/minitest/heat/output/source_code.rb +131 -0
- data/lib/minitest/heat/output/token.rb +101 -0
- data/lib/minitest/heat/output.rb +31 -222
- data/lib/minitest/heat/results.rb +24 -13
- data/lib/minitest/heat/source.rb +6 -1
- data/lib/minitest/heat/version.rb +3 -1
- data/lib/minitest/heat.rb +2 -0
- data/lib/minitest/heat_plugin.rb +5 -5
- data/lib/minitest/heat_reporter.rb +28 -24
- data/minitest-heat.gemspec +5 -2
- metadata +83 -3
@@ -2,59 +2,180 @@
|
|
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
|
17
|
+
TestDefinition = Struct.new(:pathname, :line_number) do
|
18
|
+
def initialize(pathname, line_number)
|
19
|
+
@pathname = Pathname(pathname)
|
20
|
+
@line_number = Integer(line_number)
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
6
25
|
attr_reader :test_location, :backtrace
|
7
26
|
|
8
27
|
def initialize(test_location, backtrace = [])
|
9
|
-
@test_location = test_location
|
28
|
+
@test_location = TestDefinition.new(*test_location)
|
10
29
|
@backtrace = Backtrace.new(backtrace)
|
11
30
|
end
|
12
31
|
|
32
|
+
# Prints the pathname and line number of the location most likely to be the source of the
|
33
|
+
# test failure
|
34
|
+
#
|
35
|
+
# @return [String] ex. 'path/to/file.rb:12'
|
13
36
|
def to_s
|
14
|
-
"#{
|
37
|
+
"#{most_relevant_file}:#{most_relevant_failure_line}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def local?
|
41
|
+
broken_test? || proper_failure?
|
42
|
+
end
|
43
|
+
|
44
|
+
# Knows if the failure is contained within the test. For example, if there's bad code in a
|
45
|
+
# test, and it raises an exception, then it's really a broken test rather than a proper
|
46
|
+
# faiure.
|
47
|
+
#
|
48
|
+
# @return [Boolean] true if most relevant file is the same as the test location file
|
49
|
+
def broken_test?
|
50
|
+
!test_file.nil? && test_file == most_relevant_file
|
51
|
+
end
|
52
|
+
|
53
|
+
# Knows if the failure occurred in the actual project source code—as opposed to the test or
|
54
|
+
# an external piece of code like a gem.
|
55
|
+
#
|
56
|
+
# @return [Boolean] true if there's a non-test project file in the stacktrace but it's not
|
57
|
+
# a result of a broken test
|
58
|
+
def proper_failure?
|
59
|
+
!source_code_file.nil? && !broken_test?
|
60
|
+
end
|
61
|
+
|
62
|
+
# The file most likely to be the source of the underlying problem. Often, the most recent
|
63
|
+
# backtrace files will be a gem or external library that's failing indirectly as a result
|
64
|
+
# of a problem with local source code (not always, but frequently). In that case, the best
|
65
|
+
# first place to focus is on the code you control.
|
66
|
+
#
|
67
|
+
# @return [String] the relative path to the file from the project root
|
68
|
+
def most_relevant_file
|
69
|
+
Pathname(most_relevant_location.pathname)
|
70
|
+
end
|
71
|
+
|
72
|
+
# The line number of the `most_relevant_file` where the failure originated
|
73
|
+
#
|
74
|
+
# @return [Integer] line number
|
75
|
+
def most_relevant_failure_line
|
76
|
+
most_relevant_location.line_number
|
77
|
+
end
|
78
|
+
|
79
|
+
# The final location of the stacktrace regardless of whether it's from within the project
|
80
|
+
#
|
81
|
+
# @return [String] the relative path to the file from the project root
|
82
|
+
def final_file
|
83
|
+
Pathname(final_location.pathname)
|
15
84
|
end
|
16
85
|
|
17
|
-
|
18
|
-
|
86
|
+
# The line number of the `final_file` where the failure originated
|
87
|
+
#
|
88
|
+
# @return [Integer] line number
|
89
|
+
def final_failure_line
|
90
|
+
final_location.line_number
|
19
91
|
end
|
20
92
|
|
21
|
-
|
22
|
-
|
93
|
+
# The final location of the stacktrace regardless of whether it's from within the project
|
94
|
+
#
|
95
|
+
# @return [String] the relative path to the file from the project root
|
96
|
+
def project_file
|
97
|
+
broken_test? ? test_file : source_code_file
|
23
98
|
end
|
24
99
|
|
100
|
+
# The line number of the `project_file` where the failure originated
|
101
|
+
#
|
102
|
+
# @return [Integer] line number
|
103
|
+
def project_failure_line
|
104
|
+
broken_test? ? test_failure_line || test_definition_line : source_code_failure_line
|
105
|
+
end
|
106
|
+
|
107
|
+
# The final location from the stacktrace that is within the project directory
|
108
|
+
#
|
109
|
+
# @return [String, nil] the relative path to the file from the project root
|
110
|
+
def source_code_file
|
111
|
+
return nil unless backtrace.source_code_entries.any?
|
112
|
+
|
113
|
+
backtrace.final_source_code_location.pathname
|
114
|
+
end
|
115
|
+
|
116
|
+
# The line number of the `source_code_file` where the failure originated
|
117
|
+
#
|
118
|
+
# @return [Integer] line number
|
119
|
+
def source_code_failure_line
|
120
|
+
return nil unless backtrace.source_code_entries.any?
|
121
|
+
|
122
|
+
backtrace.final_source_code_location.line_number
|
123
|
+
end
|
124
|
+
|
125
|
+
# The final location from the stacktrace that is within the project's test directory
|
126
|
+
#
|
127
|
+
# @return [String, nil] the relative path to the file from the project root
|
25
128
|
def test_file
|
26
|
-
|
129
|
+
Pathname(test_location.pathname)
|
27
130
|
end
|
28
131
|
|
132
|
+
# The line number of the `test_file` where the test is defined
|
133
|
+
#
|
134
|
+
# @return [Integer] line number
|
29
135
|
def test_definition_line
|
30
|
-
test_location
|
136
|
+
test_location.line_number
|
31
137
|
end
|
32
138
|
|
139
|
+
# The line number from within the `test_file` test definition where the failure occurred
|
140
|
+
#
|
141
|
+
# @return [Integer] line number
|
33
142
|
def test_failure_line
|
34
|
-
|
143
|
+
backtrace.final_test_location&.line_number || test_definition_line
|
35
144
|
end
|
36
145
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
146
|
+
# The line number from within the `test_file` test definition where the failure occurred
|
147
|
+
#
|
148
|
+
# @return [Location] the last location from the backtrace or the test location if a backtrace
|
149
|
+
# was not passed to the initializer
|
150
|
+
def final_location
|
151
|
+
backtrace? ? backtrace.final_location : test_location
|
43
152
|
end
|
44
153
|
|
45
|
-
|
46
|
-
|
154
|
+
# The file most likely to be the source of the underlying problem. Often, the most recent
|
155
|
+
# backtrace files will be a gem or external library that's failing indirectly as a result
|
156
|
+
# of a problem with local source code (not always, but frequently). In that case, the best
|
157
|
+
# first place to focus is on the code you control.
|
158
|
+
#
|
159
|
+
# @return [Array] file and line number of the most likely source of the problem
|
160
|
+
def most_relevant_location
|
161
|
+
[
|
162
|
+
source_code_location,
|
163
|
+
test_location,
|
164
|
+
final_location
|
165
|
+
].compact.first
|
166
|
+
end
|
47
167
|
|
48
|
-
|
168
|
+
def project_location
|
169
|
+
source_code_location || test_location
|
49
170
|
end
|
50
171
|
|
51
|
-
|
172
|
+
def source_code_location
|
173
|
+
backtrace.final_source_code_location
|
174
|
+
end
|
52
175
|
|
53
|
-
def
|
54
|
-
|
176
|
+
def backtrace?
|
177
|
+
backtrace.parsed_entries.any?
|
55
178
|
end
|
56
179
|
end
|
57
180
|
end
|
58
181
|
end
|
59
|
-
|
60
|
-
|
data/lib/minitest/heat/map.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
module Minitest
|
4
4
|
module Heat
|
5
5
|
class Map
|
6
|
+
MAXIMUM_FILES_TO_SHOW = 5
|
7
|
+
|
6
8
|
attr_reader :hits
|
7
9
|
|
8
10
|
# So we can sort hot spots by liklihood of being the most important spot to check out before
|
@@ -13,44 +15,32 @@ module Minitest
|
|
13
15
|
# test is broken (i.e. raising an exception), that's a special sort of failure that would be
|
14
16
|
# misleading. It doesn't represent a proper failure, but rather a test that doesn't work.
|
15
17
|
WEIGHTS = {
|
16
|
-
error: 3,
|
17
|
-
broken:
|
18
|
-
failure: 1,
|
19
|
-
skipped: 0,
|
20
|
-
|
21
|
-
|
18
|
+
error: 3, # exceptions from source code have the highest liklihood of a ripple effect
|
19
|
+
broken: 2, # broken tests won't have ripple effects but can't help if they can't run
|
20
|
+
failure: 1, # failures are kind of the whole point, and they could have ripple effects
|
21
|
+
skipped: 0, # skips aren't failures, but they shouldn't go ignored
|
22
|
+
painful: 0, # slow tests aren't failures, but they shouldn't be ignored
|
23
|
+
slow: 0
|
24
|
+
}.freeze
|
22
25
|
|
23
26
|
def initialize
|
24
27
|
@hits = {}
|
25
28
|
end
|
26
29
|
|
27
30
|
def add(filename, line_number, type)
|
28
|
-
@hits[filename] ||=
|
29
|
-
@hits[filename][:total] += 1
|
30
|
-
@hits[filename][:weight] += WEIGHTS[type]
|
31
|
+
@hits[filename] ||= Hit.new(filename)
|
31
32
|
|
32
|
-
@hits[filename]
|
33
|
-
@hits[filename][type] << line_number
|
33
|
+
@hits[filename].log(type, line_number)
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
37
|
-
hot_files
|
38
|
-
.sort_by { |filename, weight| weight }
|
39
|
-
.reverse
|
40
|
-
.take(5)
|
36
|
+
def file_hits
|
37
|
+
hot_files.take(MAXIMUM_FILES_TO_SHOW)
|
41
38
|
end
|
42
39
|
|
43
40
|
private
|
44
41
|
|
45
42
|
def hot_files
|
46
|
-
|
47
|
-
@hits.each_pair do |filename, details|
|
48
|
-
# Can't really be a "hot spot" with just a single issue
|
49
|
-
next unless details[:total] > 1
|
50
|
-
|
51
|
-
files[filename] = details[:weight]
|
52
|
-
end
|
53
|
-
files
|
43
|
+
hits.values.sort_by(&:weight).reverse
|
54
44
|
end
|
55
45
|
end
|
56
46
|
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Minitest
|
4
|
+
module Heat
|
5
|
+
class Output
|
6
|
+
# Builds the collection of tokens for a backtrace when an exception occurs
|
7
|
+
class Backtrace
|
8
|
+
DEFAULT_LINE_COUNT = 5
|
9
|
+
DEFAULT_INDENTATION_SPACES = 2
|
10
|
+
|
11
|
+
attr_accessor :location, :backtrace
|
12
|
+
|
13
|
+
def initialize(location)
|
14
|
+
@location = location
|
15
|
+
@backtrace = location.backtrace
|
16
|
+
@tokens = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def tokens
|
20
|
+
# There could be option to expand and display more than one line of source code for the
|
21
|
+
# final backtrace line if it might be relevant/helpful?
|
22
|
+
|
23
|
+
# Iterate over the selected lines from the backtrace
|
24
|
+
backtrace_entries.each do |backtrace_entry|
|
25
|
+
# Get the source code for the line from the backtrace
|
26
|
+
parts = [
|
27
|
+
indentation_token,
|
28
|
+
path_token(backtrace_entry),
|
29
|
+
file_and_line_number_token(backtrace_entry),
|
30
|
+
source_code_line_token(backtrace_entry.source_code)
|
31
|
+
]
|
32
|
+
|
33
|
+
parts << file_freshness(backtrace_entry) if most_recently_modified?(backtrace_entry)
|
34
|
+
|
35
|
+
@tokens << parts
|
36
|
+
end
|
37
|
+
|
38
|
+
@tokens
|
39
|
+
end
|
40
|
+
|
41
|
+
def line_count
|
42
|
+
DEFAULT_LINE_COUNT
|
43
|
+
end
|
44
|
+
|
45
|
+
# This should probably be smart about what lines are displayed in a backtrace.
|
46
|
+
# Maybe...
|
47
|
+
# ...it could intelligently display the full back trace?
|
48
|
+
# ...only the backtrace from the first/last line of project source?
|
49
|
+
# ...it behaves a little different when it's a broken test vs. a true exception?
|
50
|
+
# ...it could be smart about subtly flagging the lines that show up in the heat map frequently?
|
51
|
+
# ...it could be influenced by a "compact" or "robust" reporter super-style?
|
52
|
+
# ...it's smart about exceptions that were raised outside of the project?
|
53
|
+
# ...it's smart about highlighting lines of code differently based on whether it's source code, test code, or external code?
|
54
|
+
def backtrace_entries
|
55
|
+
project_entries
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def all_backtrace_entries_from_project?
|
61
|
+
backtrace_entries.all? { |line| line.path.to_s.include?(project_root_dir) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def project_root_dir
|
65
|
+
Dir.pwd
|
66
|
+
end
|
67
|
+
|
68
|
+
def project_entries
|
69
|
+
backtrace.project_entries.take(line_count)
|
70
|
+
end
|
71
|
+
|
72
|
+
def all_entries
|
73
|
+
backtrace.parsed_entries.take(line_count)
|
74
|
+
end
|
75
|
+
|
76
|
+
def most_recently_modified?(line)
|
77
|
+
# If there's more than one line being displayed, and the current line is the freshest
|
78
|
+
backtrace_entries.size > 1 && line == backtrace.freshest_project_location
|
79
|
+
end
|
80
|
+
|
81
|
+
def indentation_token
|
82
|
+
[:default, ' ' * indentation]
|
83
|
+
end
|
84
|
+
|
85
|
+
def path_token(line)
|
86
|
+
path = "#{line.path}/"
|
87
|
+
|
88
|
+
# If all of the backtrace lines are from the project, no point in the added redundant
|
89
|
+
# noise of showing the project root directory over and over again
|
90
|
+
path = path.delete_prefix(project_root_dir) if all_backtrace_entries_from_project?
|
91
|
+
|
92
|
+
[:muted, path]
|
93
|
+
end
|
94
|
+
|
95
|
+
def file_and_line_number_token(backtrace_entry)
|
96
|
+
[:default, "#{backtrace_entry.file}:#{backtrace_entry.line_number}"]
|
97
|
+
end
|
98
|
+
|
99
|
+
def source_code_line_token(source_code)
|
100
|
+
[:muted, " `#{source_code.line.strip}`"]
|
101
|
+
end
|
102
|
+
|
103
|
+
def file_freshness(_line)
|
104
|
+
[:bold, ' < Most Recently Modified']
|
105
|
+
end
|
106
|
+
|
107
|
+
# The number of spaces each line of code should be indented. Currently defaults to 2 in
|
108
|
+
# order to provide visual separation between test failures, but in the future, it could
|
109
|
+
# be configurable in order to save horizontal space and create more compact output. For
|
110
|
+
# example, it could be smart based on line length and total available horizontal terminal
|
111
|
+
# space, or there could be higher-level "display" setting that could have a `:compact`
|
112
|
+
# option that would reduce the space used.
|
113
|
+
#
|
114
|
+
# @return [type] [description]
|
115
|
+
def indentation
|
116
|
+
DEFAULT_INDENTATION_SPACES
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Minitest
|
4
|
+
module Heat
|
5
|
+
class Output
|
6
|
+
class Issue
|
7
|
+
SHARED_SYMBOLS = {
|
8
|
+
spacer: ' · ',
|
9
|
+
arrow: ' > '
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
attr_accessor :issue
|
13
|
+
|
14
|
+
def initialize(issue)
|
15
|
+
@issue = issue
|
16
|
+
end
|
17
|
+
|
18
|
+
def tokens
|
19
|
+
case issue.type
|
20
|
+
when :error then error_tokens
|
21
|
+
when :broken then broken_tokens
|
22
|
+
when :failure then failure_tokens
|
23
|
+
when :skipped then skipped_tokens
|
24
|
+
when :painful then painful_tokens
|
25
|
+
when :slow then slow_tokens
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def error_tokens
|
32
|
+
[
|
33
|
+
headline_tokens,
|
34
|
+
summary_tokens,
|
35
|
+
*backtrace_tokens,
|
36
|
+
newline_tokens
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
def broken_tokens
|
41
|
+
[
|
42
|
+
headline_tokens,
|
43
|
+
summary_tokens,
|
44
|
+
*backtrace_tokens,
|
45
|
+
newline_tokens
|
46
|
+
]
|
47
|
+
end
|
48
|
+
|
49
|
+
def failure_tokens
|
50
|
+
[
|
51
|
+
headline_tokens,
|
52
|
+
summary_tokens,
|
53
|
+
location_tokens,
|
54
|
+
*source_tokens,
|
55
|
+
newline_tokens
|
56
|
+
]
|
57
|
+
end
|
58
|
+
|
59
|
+
def skipped_tokens
|
60
|
+
[
|
61
|
+
headline_tokens,
|
62
|
+
summary_tokens,
|
63
|
+
newline_tokens
|
64
|
+
]
|
65
|
+
end
|
66
|
+
|
67
|
+
def painful_tokens
|
68
|
+
[
|
69
|
+
headline_tokens,
|
70
|
+
slowness_tokens,
|
71
|
+
newline_tokens
|
72
|
+
]
|
73
|
+
end
|
74
|
+
|
75
|
+
def slow_tokens
|
76
|
+
[
|
77
|
+
headline_tokens,
|
78
|
+
slowness_tokens,
|
79
|
+
newline_tokens
|
80
|
+
]
|
81
|
+
end
|
82
|
+
|
83
|
+
def headline_tokens
|
84
|
+
[[issue.type, issue.label], [:muted, spacer], [:default, issue.test_name], [:muted, spacer], [:muted, issue.test_class]]
|
85
|
+
end
|
86
|
+
|
87
|
+
def summary_tokens
|
88
|
+
[[:italicized, issue.summary]]
|
89
|
+
end
|
90
|
+
|
91
|
+
def backtrace_tokens
|
92
|
+
backtrace = ::Minitest::Heat::Output::Backtrace.new(issue.location)
|
93
|
+
|
94
|
+
backtrace.tokens
|
95
|
+
end
|
96
|
+
|
97
|
+
def location_tokens
|
98
|
+
[[:muted, issue.short_location]]
|
99
|
+
end
|
100
|
+
|
101
|
+
def source_tokens
|
102
|
+
filename = issue.location.project_file
|
103
|
+
line_number = issue.location.project_failure_line
|
104
|
+
|
105
|
+
source_code = ::Minitest::Heat::Output::SourceCode.new(filename, line_number)
|
106
|
+
|
107
|
+
source_code.tokens
|
108
|
+
end
|
109
|
+
|
110
|
+
def slowness_tokens
|
111
|
+
[[:bold, issue.slowness], [:muted, spacer], [:default, issue.short_location] ]
|
112
|
+
end
|
113
|
+
|
114
|
+
def newline_tokens
|
115
|
+
[]
|
116
|
+
end
|
117
|
+
|
118
|
+
def spacer
|
119
|
+
SHARED_SYMBOLS[:spacer]
|
120
|
+
end
|
121
|
+
|
122
|
+
def arrow
|
123
|
+
SHARED_SYMBOLS[:arrow]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Minitest
|
4
|
+
module Heat
|
5
|
+
class Output
|
6
|
+
class Map
|
7
|
+
# extend Forwardable
|
8
|
+
|
9
|
+
attr_accessor :map
|
10
|
+
|
11
|
+
# def_delegators :@results, :errors, :brokens, :failures, :slows, :skips, :problems?, :slows?
|
12
|
+
|
13
|
+
def initialize(map)
|
14
|
+
@map = map
|
15
|
+
@tokens = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def tokens
|
19
|
+
map.file_hits.each do |file|
|
20
|
+
@tokens << [
|
21
|
+
*pathname(file),
|
22
|
+
*line_numbers(file)
|
23
|
+
]
|
24
|
+
end
|
25
|
+
|
26
|
+
@tokens
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def pathname(file)
|
32
|
+
directory = "#{file.pathname.dirname.to_s.delete_prefix(Dir.pwd)}/"
|
33
|
+
filename = file.pathname.basename.to_s
|
34
|
+
|
35
|
+
[
|
36
|
+
[:default, directory],
|
37
|
+
[:bold, filename],
|
38
|
+
[:default, ' · ']
|
39
|
+
]
|
40
|
+
end
|
41
|
+
|
42
|
+
def hit_line_numbers(file, issue_type)
|
43
|
+
line_numbers_for_issue_type = file.issues.fetch(issue_type) { [] }
|
44
|
+
|
45
|
+
return nil if line_numbers_for_issue_type.empty?
|
46
|
+
|
47
|
+
numbers = []
|
48
|
+
line_numbers_for_issue_type.sort.map do |line_number|
|
49
|
+
numbers << [issue_type, "#{line_number} "]
|
50
|
+
end
|
51
|
+
numbers
|
52
|
+
end
|
53
|
+
|
54
|
+
def line_numbers(file)
|
55
|
+
[
|
56
|
+
*hit_line_numbers(file, :error),
|
57
|
+
*hit_line_numbers(file, :broken),
|
58
|
+
*hit_line_numbers(file, :failure),
|
59
|
+
*hit_line_numbers(file, :skipped),
|
60
|
+
*hit_line_numbers(file, :painful),
|
61
|
+
*hit_line_numbers(file, :slow)
|
62
|
+
].compact.sort_by { |number_token| number_token[1] }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Minitest
|
4
|
+
module Heat
|
5
|
+
# Friendly API for printing nicely-formatted output to the console
|
6
|
+
class Output
|
7
|
+
class Marker
|
8
|
+
SYMBOLS = {
|
9
|
+
success: '·',
|
10
|
+
slow: '♦',
|
11
|
+
painful: '♦',
|
12
|
+
broken: 'B',
|
13
|
+
error: 'E',
|
14
|
+
skipped: 'S',
|
15
|
+
failure: 'F'
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
STYLES = {
|
19
|
+
success: :success,
|
20
|
+
slow: :slow,
|
21
|
+
painful: :painful,
|
22
|
+
broken: :error,
|
23
|
+
error: :error,
|
24
|
+
skipped: :skipped,
|
25
|
+
failure: :failure
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
attr_accessor :issue_type
|
29
|
+
|
30
|
+
def initialize(issue_type)
|
31
|
+
@issue_type = issue_type
|
32
|
+
end
|
33
|
+
|
34
|
+
def token
|
35
|
+
[style, symbol]
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def style
|
41
|
+
STYLES.fetch(issue_type, :default)
|
42
|
+
end
|
43
|
+
|
44
|
+
def symbol
|
45
|
+
SYMBOLS.fetch(issue_type, '?')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|