minitest-heat 0.0.4 → 0.0.8
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 +30 -1
- data/README.md +12 -3
- data/Rakefile +8 -6
- data/lib/minitest/heat/backtrace.rb +25 -48
- data/lib/minitest/heat/hit.rb +79 -0
- data/lib/minitest/heat/issue.rb +56 -37
- data/lib/minitest/heat/line.rb +74 -0
- data/lib/minitest/heat/location.rb +144 -23
- data/lib/minitest/heat/map.rb +7 -34
- data/lib/minitest/heat/output/backtrace.rb +121 -0
- data/lib/minitest/heat/output/issue.rb +179 -0
- data/lib/minitest/heat/output/map.rb +76 -0
- data/lib/minitest/heat/output/marker.rb +50 -0
- data/lib/minitest/heat/output/results.rb +128 -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 +42 -226
- data/lib/minitest/heat/results.rb +19 -71
- data/lib/minitest/heat/source.rb +6 -1
- data/lib/minitest/heat/timer.rb +81 -0
- data/lib/minitest/heat/version.rb +3 -1
- data/lib/minitest/heat.rb +3 -0
- data/lib/minitest/heat_plugin.rb +5 -5
- data/lib/minitest/heat_reporter.rb +49 -27
- data/minitest-heat.gemspec +4 -2
- metadata +70 -3
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Minitest
|
4
|
+
module Heat
|
5
|
+
class Output
|
6
|
+
class Results
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
attr_accessor :results, :timer
|
10
|
+
|
11
|
+
def_delegators :@results, :issues, :errors, :brokens, :failures, :skips, :painfuls, :slows, :problems?
|
12
|
+
|
13
|
+
def initialize(results, timer)
|
14
|
+
@results = results
|
15
|
+
@timer = timer
|
16
|
+
@tokens = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def tokens
|
20
|
+
# Only show the issue type counts if there are issues
|
21
|
+
@tokens << [*issue_counts_tokens] if issue_counts_tokens&.any?
|
22
|
+
|
23
|
+
@tokens << [
|
24
|
+
timing_token, spacer_token,
|
25
|
+
test_count_token, tests_performance_token, join_token,
|
26
|
+
assertions_count_token, assertions_performance_token
|
27
|
+
]
|
28
|
+
|
29
|
+
@tokens
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def pluralize(count, singular)
|
35
|
+
singular_style = "#{count} #{singular}"
|
36
|
+
|
37
|
+
# Given the narrow scope, pluralization can be relatively naive here
|
38
|
+
count > 1 ? "#{singular_style}s" : singular_style
|
39
|
+
end
|
40
|
+
|
41
|
+
def issue_counts_tokens
|
42
|
+
return unless issues.any?
|
43
|
+
|
44
|
+
counts = [
|
45
|
+
error_count_token,
|
46
|
+
broken_count_token,
|
47
|
+
failure_count_token,
|
48
|
+
skip_count_token,
|
49
|
+
painful_count_token,
|
50
|
+
slow_count_token
|
51
|
+
].compact
|
52
|
+
|
53
|
+
# # Create an array of separator tokens one less than the total number of issue count tokens
|
54
|
+
spacer_tokens = Array.new(counts.size, spacer_token)
|
55
|
+
|
56
|
+
counts_with_separators = counts
|
57
|
+
.zip(spacer_tokens) # Add separators between the counts
|
58
|
+
.flatten(1) # Flatten the zipped separators, but no more
|
59
|
+
|
60
|
+
counts_with_separators.pop # Remove the final trailing zipped separator that's not needed
|
61
|
+
|
62
|
+
counts_with_separators
|
63
|
+
end
|
64
|
+
|
65
|
+
def error_count_token
|
66
|
+
issue_count_token(:error, errors)
|
67
|
+
end
|
68
|
+
|
69
|
+
def broken_count_token
|
70
|
+
issue_count_token(:broken, brokens)
|
71
|
+
end
|
72
|
+
|
73
|
+
def failure_count_token
|
74
|
+
issue_count_token(:failure, failures)
|
75
|
+
end
|
76
|
+
|
77
|
+
def skip_count_token
|
78
|
+
style = problems? ? :muted : :skipped
|
79
|
+
issue_count_token(style, skips, name: 'Skip')
|
80
|
+
end
|
81
|
+
|
82
|
+
def painful_count_token
|
83
|
+
style = problems? || skips.any? ? :muted : :painful
|
84
|
+
issue_count_token(style, painfuls, name: 'Painfully Slow')
|
85
|
+
end
|
86
|
+
|
87
|
+
def slow_count_token
|
88
|
+
style = problems? || skips.any? ? :muted : :slow
|
89
|
+
issue_count_token(style, slows, name: 'Slow')
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_count_token
|
93
|
+
[:default, "#{pluralize(timer.test_count, 'test')}"]
|
94
|
+
end
|
95
|
+
|
96
|
+
def tests_performance_token
|
97
|
+
[:default, " (#{timer.tests_per_second}/s)"]
|
98
|
+
end
|
99
|
+
|
100
|
+
def assertions_count_token
|
101
|
+
[:default, "#{pluralize(timer.assertion_count, 'assertion')}"]
|
102
|
+
end
|
103
|
+
|
104
|
+
def assertions_performance_token
|
105
|
+
[:default, " (#{timer.assertions_per_second}/s)"]
|
106
|
+
end
|
107
|
+
|
108
|
+
def timing_token
|
109
|
+
[:bold, "#{timer.total_time.round(2)}s"]
|
110
|
+
end
|
111
|
+
|
112
|
+
def issue_count_token(type, collection, name: type.capitalize)
|
113
|
+
return nil if collection.empty?
|
114
|
+
|
115
|
+
[type, pluralize(collection.size, name)]
|
116
|
+
end
|
117
|
+
|
118
|
+
def spacer_token
|
119
|
+
Output::TOKENS[:spacer]
|
120
|
+
end
|
121
|
+
|
122
|
+
def join_token
|
123
|
+
[:default, ' with ']
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Minitest
|
4
|
+
module Heat
|
5
|
+
class Output
|
6
|
+
# Builds the collection of tokens representing a specific set of source code lines
|
7
|
+
class SourceCode
|
8
|
+
DEFAULT_LINE_COUNT = 3
|
9
|
+
DEFAULT_INDENTATION_SPACES = 2
|
10
|
+
HIGHLIGHT_KEY_LINE = true
|
11
|
+
|
12
|
+
attr_reader :filename, :line_number, :max_line_count
|
13
|
+
|
14
|
+
# Provides a collection of tokens representing the output of source code
|
15
|
+
# @param filename [String] the absolute path to the file containing the source code
|
16
|
+
# @param line_number [Integer, String] the primary line number of interest for the file
|
17
|
+
# @param max_line_count: DEFAULT_LINE_COUNT [Integer] maximum total number of lines to
|
18
|
+
# retrieve around the target line (including the target line)
|
19
|
+
#
|
20
|
+
# @return [self]
|
21
|
+
def initialize(filename, line_number, max_line_count: DEFAULT_LINE_COUNT)
|
22
|
+
@filename = filename
|
23
|
+
@line_number = line_number.to_s
|
24
|
+
@max_line_count = max_line_count
|
25
|
+
@tokens = []
|
26
|
+
end
|
27
|
+
|
28
|
+
# The collection of style content tokens to print
|
29
|
+
#
|
30
|
+
# @return [Array<Array<Token>>] an array of arrays of tokens where each top-level array
|
31
|
+
# represents a line where the first element is the line_number and the second is the line
|
32
|
+
# of code to display
|
33
|
+
def tokens
|
34
|
+
source.lines.each_index do |i|
|
35
|
+
current_line_number = source.line_numbers[i]
|
36
|
+
current_line_of_code = source.lines[i]
|
37
|
+
|
38
|
+
number_style, line_style = styles_for(current_line_of_code)
|
39
|
+
|
40
|
+
@tokens << [
|
41
|
+
line_number_token(number_style, current_line_number),
|
42
|
+
line_of_code_token(line_style, current_line_of_code)
|
43
|
+
]
|
44
|
+
end
|
45
|
+
@tokens
|
46
|
+
end
|
47
|
+
|
48
|
+
# The number of digits for the largest line number returned. This is used for formatting and
|
49
|
+
# text justification so that line numbers are right-aligned
|
50
|
+
#
|
51
|
+
# @return [Integer] the number of digits in the longest line number returned
|
52
|
+
def max_line_number_digits
|
53
|
+
source
|
54
|
+
.line_numbers
|
55
|
+
.map(&:to_s)
|
56
|
+
.map(&:length)
|
57
|
+
.max
|
58
|
+
end
|
59
|
+
|
60
|
+
# Whether to visually highlight the target line when displaying the source code. Currently
|
61
|
+
# defauls to true, but long-term, this is a likely candidate to be configurable. For
|
62
|
+
# example, in the future, highlighting could only be used if the source includes more than
|
63
|
+
# three lines. Or it could be something end users could disable in order to reduce noise.
|
64
|
+
#
|
65
|
+
# @return [Boolean] true if the target line should be highlighted
|
66
|
+
def highlight_key_line?
|
67
|
+
HIGHLIGHT_KEY_LINE
|
68
|
+
end
|
69
|
+
|
70
|
+
# The number of spaces each line of code should be indented. Currently defaults to 2 in
|
71
|
+
# order to provide visual separation between test failures, but in the future, it could
|
72
|
+
# be configurable in order to save horizontal space and create more compact output. For
|
73
|
+
# example, it could be smart based on line length and total available horizontal terminal
|
74
|
+
# space, or there could be higher-level "display" setting that could have a `:compact`
|
75
|
+
# option that would reduce the space used.
|
76
|
+
#
|
77
|
+
# @return [type] [description]
|
78
|
+
def indentation
|
79
|
+
DEFAULT_INDENTATION_SPACES
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# The source instance for retrieving the relevant lines of source code
|
85
|
+
#
|
86
|
+
# @return [Source] a Minitest::Heat::Source instance
|
87
|
+
def source
|
88
|
+
@source ||= Minitest::Heat::Source.new(
|
89
|
+
filename,
|
90
|
+
line_number: line_number,
|
91
|
+
max_line_count: max_line_count
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Determines how to style a given line of code token. For now, it's only used for
|
96
|
+
# highlighting the targeted line of code, but it could also be adjusted to mute the line
|
97
|
+
# number or otherwise change the styling of how lines of code are displayed
|
98
|
+
# @param line_of_code [String] the content representing the line of code we're currently
|
99
|
+
# generating a token for
|
100
|
+
#
|
101
|
+
# @return [Array<Symbol>] the Token styles for the line number and line of code
|
102
|
+
def styles_for(line_of_code)
|
103
|
+
if line_of_code == source.line && highlight_key_line?
|
104
|
+
%i[default default]
|
105
|
+
else
|
106
|
+
%i[muted muted]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# The token representing a given line number. Adds the appropriate indention and
|
111
|
+
# justification to right align the line numbers
|
112
|
+
# @param style [Symbol] the symbol representing the style for the line number token
|
113
|
+
# @param line_number [Integer,String] the digits representing the line number
|
114
|
+
#
|
115
|
+
# @return [Array] the style/content token for the current line number
|
116
|
+
def line_number_token(style, line_number)
|
117
|
+
[style, "#{' ' * indentation}#{line_number.to_s.rjust(max_line_number_digits)} "]
|
118
|
+
end
|
119
|
+
|
120
|
+
# The token representing the content of a given line of code.
|
121
|
+
# @param style [Symbol] the symbol representing the style for the line of code token
|
122
|
+
# @param line_number [Integer,String] the content of the line of code
|
123
|
+
#
|
124
|
+
# @return [Array] the style/content token for the current line of code
|
125
|
+
def line_of_code_token(style, line_of_code)
|
126
|
+
[style, line_of_code]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,101 @@
|
|
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 Token
|
8
|
+
class InvalidStyle < ArgumentError; end
|
9
|
+
|
10
|
+
STYLES = {
|
11
|
+
success: %i[default green],
|
12
|
+
slow: %i[default green],
|
13
|
+
painful: %i[bold green],
|
14
|
+
error: %i[bold red],
|
15
|
+
broken: %i[bold red],
|
16
|
+
failure: %i[default red],
|
17
|
+
skipped: %i[default yellow],
|
18
|
+
warning_light: %i[light yellow],
|
19
|
+
italicized: %i[italic gray],
|
20
|
+
bold: %i[bold default],
|
21
|
+
default: %i[default default],
|
22
|
+
muted: %i[light gray]
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
attr_accessor :style_key, :content
|
26
|
+
|
27
|
+
def initialize(style_key, content)
|
28
|
+
@style_key = style_key
|
29
|
+
@content = content
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s(format = :styled)
|
33
|
+
return content unless format == :styled
|
34
|
+
|
35
|
+
[
|
36
|
+
style_string,
|
37
|
+
content,
|
38
|
+
reset_string
|
39
|
+
].join
|
40
|
+
end
|
41
|
+
|
42
|
+
def eql?(other)
|
43
|
+
style_key == other.style_key && content == other.content
|
44
|
+
end
|
45
|
+
alias :== eql?
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
ESC_SEQUENCE = "\e["
|
50
|
+
END_SEQUENCE = 'm'
|
51
|
+
|
52
|
+
WEIGHTS = {
|
53
|
+
default: 0,
|
54
|
+
bold: 1,
|
55
|
+
light: 2,
|
56
|
+
italic: 3
|
57
|
+
}.freeze
|
58
|
+
|
59
|
+
COLORS = {
|
60
|
+
black: 30,
|
61
|
+
red: 31,
|
62
|
+
green: 32,
|
63
|
+
yellow: 33,
|
64
|
+
blue: 34,
|
65
|
+
magenta: 35,
|
66
|
+
cyan: 36,
|
67
|
+
gray: 37,
|
68
|
+
default: 39
|
69
|
+
}.freeze
|
70
|
+
|
71
|
+
def style_string
|
72
|
+
"#{ESC_SEQUENCE}#{weight};#{color}#{END_SEQUENCE}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def reset_string
|
76
|
+
"#{ESC_SEQUENCE}0#{END_SEQUENCE}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def weight_key
|
80
|
+
style_components[0]
|
81
|
+
end
|
82
|
+
|
83
|
+
def color_key
|
84
|
+
style_components[1]
|
85
|
+
end
|
86
|
+
|
87
|
+
def weight
|
88
|
+
WEIGHTS.fetch(weight_key)
|
89
|
+
end
|
90
|
+
|
91
|
+
def color
|
92
|
+
COLORS.fetch(color_key)
|
93
|
+
end
|
94
|
+
|
95
|
+
def style_components
|
96
|
+
STYLES.fetch(style_key) { raise InvalidStyle, "'#{style_key}' is not a valid style option for tokens" }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/minitest/heat/output.rb
CHANGED
@@ -1,105 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'output/backtrace'
|
4
|
+
require_relative 'output/issue'
|
5
|
+
require_relative 'output/map'
|
6
|
+
require_relative 'output/marker'
|
7
|
+
require_relative 'output/results'
|
8
|
+
require_relative 'output/source_code'
|
9
|
+
require_relative 'output/token'
|
10
|
+
|
3
11
|
module Minitest
|
4
12
|
module Heat
|
5
13
|
# Friendly API for printing nicely-formatted output to the console
|
6
14
|
class Output
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def style_string
|
21
|
-
"#{ESC}#{weight};#{color}m"
|
22
|
-
end
|
23
|
-
|
24
|
-
def reset_string
|
25
|
-
"#{ESC}0m"
|
26
|
-
end
|
27
|
-
|
28
|
-
def weight_key
|
29
|
-
style_components[0]
|
30
|
-
end
|
31
|
-
|
32
|
-
def color_key
|
33
|
-
style_components[1]
|
34
|
-
end
|
35
|
-
|
36
|
-
def weight
|
37
|
-
{
|
38
|
-
default: 0,
|
39
|
-
bold: 1,
|
40
|
-
light: 2,
|
41
|
-
italic: 3
|
42
|
-
}.fetch(weight_key)
|
43
|
-
end
|
44
|
-
|
45
|
-
def color
|
46
|
-
{
|
47
|
-
black: 30,
|
48
|
-
red: 31,
|
49
|
-
green: 32,
|
50
|
-
yellow: 33,
|
51
|
-
blue: 34,
|
52
|
-
magenta: 35,
|
53
|
-
cyan: 36,
|
54
|
-
gray: 37,
|
55
|
-
default: 39
|
56
|
-
}.fetch(color_key)
|
57
|
-
end
|
58
|
-
|
59
|
-
def style_components
|
60
|
-
{
|
61
|
-
success: %i[default green],
|
62
|
-
slow: %i[bold green],
|
63
|
-
error: %i[bold red],
|
64
|
-
broken: %i[bold red],
|
65
|
-
failure: %i[default red],
|
66
|
-
skipped: %i[default yellow],
|
67
|
-
warning_light: %i[light yellow],
|
68
|
-
source: %i[italic default],
|
69
|
-
bold: %i[bold default],
|
70
|
-
default: %i[default default],
|
71
|
-
muted: %i[light gray]
|
72
|
-
}.fetch(style)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
FORMATTERS = {
|
77
|
-
error: [
|
78
|
-
[ %i[error label], %i[muted arrow], %i[default test_name] ],
|
79
|
-
[ %i[default summary], ],
|
80
|
-
[ %i[default backtrace_summary] ],
|
81
|
-
],
|
82
|
-
broken: [
|
83
|
-
[ %i[broken label], %i[muted spacer], %i[default test_class], %i[muted arrow], %i[default test_name] ],
|
84
|
-
[ %i[default summary], ],
|
85
|
-
[ %i[default backtrace_summary] ],
|
86
|
-
],
|
87
|
-
failure: [
|
88
|
-
[ %i[failure label], %i[muted spacer], %i[default test_class], %i[muted arrow], %i[default test_name], %i[muted spacer], %i[muted class] ],
|
89
|
-
[ %i[default summary] ],
|
90
|
-
[ %i[muted location], ],
|
91
|
-
[ %i[default source_summary], ],
|
92
|
-
],
|
93
|
-
skipped: [
|
94
|
-
[ %i[skipped label], %i[muted spacer], %i[default test_class], %i[muted arrow], %i[default test_name] ],
|
95
|
-
[ %i[default summary] ],
|
96
|
-
[], # New Line
|
97
|
-
],
|
98
|
-
slow: [
|
99
|
-
[ %i[slow label], %i[muted spacer], %i[default test_class], %i[muted arrow], %i[default test_name], %i[muted spacer], %i[muted class], ],
|
100
|
-
[ %i[bold slowness], %i[muted spacer], %i[default location], ],
|
101
|
-
[], # New Line
|
102
|
-
]
|
15
|
+
SYMBOLS = {
|
16
|
+
middot: '·',
|
17
|
+
arrow: '➜',
|
18
|
+
lead: '|',
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
TOKENS = {
|
22
|
+
spacer: [:muted, " #{SYMBOLS[:middot]} "],
|
23
|
+
muted_arrow: [:muted, " #{SYMBOLS[:arrow]} "],
|
24
|
+
muted_lead: [:muted, "#{SYMBOLS[:lead]} "],
|
103
25
|
}
|
104
26
|
|
105
27
|
attr_reader :stream
|
@@ -120,156 +42,50 @@ module Minitest
|
|
120
42
|
end
|
121
43
|
alias newline puts
|
122
44
|
|
123
|
-
def marker(value)
|
124
|
-
case value
|
125
|
-
when 'E' then text(:error, value)
|
126
|
-
when 'B' then text(:failure, value)
|
127
|
-
when 'F' then text(:failure, value)
|
128
|
-
when 'S' then text(:skipped, value)
|
129
|
-
else text(:success, value)
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
45
|
def issue_details(issue)
|
134
|
-
|
135
|
-
|
136
|
-
formatter.each do |lines|
|
137
|
-
lines.each do |tokens|
|
138
|
-
style, content_method = *tokens
|
139
|
-
|
140
|
-
if issue.respond_to?(content_method)
|
141
|
-
# If it's an available method on issue, use that to get the content
|
142
|
-
content = issue.send(content_method)
|
143
|
-
text(style, content)
|
144
|
-
else
|
145
|
-
# Otherwise, fall back to output and pass issue to *it*
|
146
|
-
send(content_method, issue)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
newline
|
150
|
-
end
|
46
|
+
print_tokens Minitest::Heat::Output::Issue.new(issue).tokens
|
151
47
|
end
|
152
48
|
|
153
|
-
def
|
154
|
-
|
155
|
-
map.files.each do |file|
|
156
|
-
file = file[0]
|
157
|
-
values = map.hits[file]
|
158
|
-
|
159
|
-
filename = file.split('/').last
|
160
|
-
path = file.delete_suffix(filename)
|
161
|
-
|
162
|
-
text(:error, 'E' * values[:error].size) if values[:error]&.any?
|
163
|
-
text(:broken, 'B' * values[:broken].size) if values[:broken]&.any?
|
164
|
-
text(:failure, 'F' * values[:failure].size) if values[:failure]&.any?
|
165
|
-
text(:skipped, 'S' * values[:skipped].size) if values[:skipped]&.any?
|
166
|
-
text(:muted, ' ') if values[:error]&.any? || values[:broken]&.any? || values[:failure]&.any? || values[:skipped]&.any?
|
167
|
-
|
168
|
-
text(:muted, "#{path.delete_prefix('/')}")
|
169
|
-
text(:default, "#{filename}")
|
170
|
-
|
171
|
-
text(:muted, ':')
|
172
|
-
|
173
|
-
all_line_numbers = values.fetch(:error, []) + values.fetch(:failure, [])
|
174
|
-
all_line_numbers += values.fetch(:skipped, [])
|
175
|
-
|
176
|
-
line_numbers = all_line_numbers.compact.uniq.sort
|
177
|
-
line_numbers.each { |line_number| text(:muted, "#{line_number} ") }
|
178
|
-
newline
|
179
|
-
end
|
180
|
-
newline
|
49
|
+
def marker(issue_type)
|
50
|
+
print_token Minitest::Heat::Output::Marker.new(issue_type).token
|
181
51
|
end
|
182
52
|
|
183
|
-
def compact_summary(results)
|
184
|
-
error_count = results.errors.size
|
185
|
-
broken_count = results.brokens.size
|
186
|
-
failure_count = results.failures.size
|
187
|
-
slow_count = results.slows.size
|
188
|
-
skip_count = results.skips.size
|
189
|
-
|
190
|
-
counts = []
|
191
|
-
counts << pluralize(error_count, 'Error') if error_count.positive?
|
192
|
-
counts << pluralize(broken_count, 'Broken') if broken_count.positive?
|
193
|
-
counts << pluralize(failure_count, 'Failure') if failure_count.positive?
|
194
|
-
counts << pluralize(skip_count, 'Skip') if skip_count.positive?
|
195
|
-
counts << pluralize(slow_count, 'Slow') if slow_count.positive?
|
196
|
-
text(:default, counts.join(', '))
|
197
|
-
|
53
|
+
def compact_summary(results, timer)
|
198
54
|
newline
|
199
|
-
|
200
|
-
|
201
|
-
newline
|
202
|
-
text(:muted, pluralize(results.test_count, 'Test') + ' & ')
|
203
|
-
text(:muted, pluralize(results.assertion_count, 'Assertion'))
|
204
|
-
text(:muted, " in #{results.total_time.round(2)}s")
|
55
|
+
print_tokens ::Minitest::Heat::Output::Results.new(results, timer).tokens
|
56
|
+
end
|
205
57
|
|
58
|
+
def heat_map(map)
|
206
59
|
newline
|
207
|
-
|
60
|
+
print_tokens ::Minitest::Heat::Output::Map.new(map).tokens
|
208
61
|
end
|
209
62
|
|
210
63
|
private
|
211
64
|
|
212
|
-
def test_name_summary(issue)
|
213
|
-
text(:default, "#{issue.test_class} > #{issue.test_name}")
|
214
|
-
end
|
215
|
-
|
216
|
-
def backtrace_summary(issue)
|
217
|
-
backtrace_lines = issue.backtrace.project
|
218
|
-
|
219
|
-
backtrace_line = backtrace_lines.first
|
220
|
-
filename = "#{backtrace_line.path.delete_prefix(Dir.pwd)}/#{backtrace_line.file}"
|
221
|
-
|
222
|
-
backtrace_lines.take(3).each do |line|
|
223
|
-
source = Minitest::Heat::Source.new(filename, line_number: line.number, max_line_count: 1)
|
224
|
-
|
225
|
-
text(:muted, " #{line.path.delete_prefix("#{Dir.pwd}/")}/")
|
226
|
-
text(:muted, "#{line.file}:#{line.number}")
|
227
|
-
text(:source, " `#{source.line.strip}`")
|
228
|
-
|
229
|
-
newline
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
def source_summary(issue)
|
234
|
-
filename = issue.location.source_file
|
235
|
-
line_number = issue.location.source_failure_line
|
236
|
-
|
237
|
-
source = Minitest::Heat::Source.new(filename, line_number: line_number, max_line_count: 3)
|
238
|
-
show_source(source, highlight_line: true, indentation: 2)
|
239
|
-
end
|
240
|
-
|
241
|
-
def show_source(source, indentation: 0, highlight_line: false)
|
242
|
-
max_line_number_length = source.line_numbers.map(&:to_s).map(&:length).max
|
243
|
-
source.lines.each_index do |i|
|
244
|
-
line_number = source.line_numbers[i]
|
245
|
-
line = source.lines[i]
|
246
|
-
|
247
|
-
number_style, line_style = if line == source.line && highlight_line
|
248
|
-
[:default, :default]
|
249
|
-
else
|
250
|
-
[:muted, :muted]
|
251
|
-
end
|
252
|
-
text(number_style, "#{' ' * indentation}#{line_number.to_s.rjust(max_line_number_length)} ")
|
253
|
-
text(line_style, line)
|
254
|
-
puts
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
65
|
def style_enabled?
|
259
66
|
stream.tty?
|
260
67
|
end
|
261
68
|
|
262
|
-
def
|
263
|
-
|
69
|
+
def text(style, content)
|
70
|
+
token = Token.new(style, content)
|
71
|
+
print token.to_s(token_format)
|
72
|
+
end
|
264
73
|
|
265
|
-
|
266
|
-
|
74
|
+
def token_format
|
75
|
+
style_enabled? ? :styled : :unstyled
|
267
76
|
end
|
268
77
|
|
269
|
-
def
|
270
|
-
|
78
|
+
def print_token(token)
|
79
|
+
print Token.new(*token).to_s(token_format)
|
80
|
+
end
|
271
81
|
|
272
|
-
|
82
|
+
def print_tokens(lines_of_tokens)
|
83
|
+
lines_of_tokens.each do |tokens|
|
84
|
+
tokens.each do |token|
|
85
|
+
print Token.new(*token).to_s(token_format)
|
86
|
+
end
|
87
|
+
newline
|
88
|
+
end
|
273
89
|
end
|
274
90
|
end
|
275
91
|
end
|