minitest-heat 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -22,10 +22,10 @@ module Minitest
22
22
  end
23
23
  end
24
24
 
25
- attr_reader :test_location, :backtrace
25
+ attr_reader :test_definition_location, :backtrace
26
26
 
27
- def initialize(test_location, backtrace = [])
28
- @test_location = TestDefinition.new(*test_location)
27
+ def initialize(test_definition_location, backtrace = [])
28
+ @test_definition_location = TestDefinition.new(*test_definition_location)
29
29
  @backtrace = Backtrace.new(backtrace)
30
30
  end
31
31
 
@@ -45,9 +45,9 @@ module Minitest
45
45
  # test, and it raises an exception, then it's really a broken test rather than a proper
46
46
  # faiure.
47
47
  #
48
- # @return [Boolean] true if most relevant file is the same as the test location file
48
+ # @return [Boolean] true if final file in the backtrace is the same as the test location file
49
49
  def broken_test?
50
- !test_file.nil? && test_file == most_relevant_file
50
+ !test_file.nil? && test_file == final_file
51
51
  end
52
52
 
53
53
  # Knows if the failure occurred in the actual project source code—as opposed to the test or
@@ -59,6 +59,15 @@ module Minitest
59
59
  !source_code_file.nil? && !broken_test?
60
60
  end
61
61
 
62
+
63
+
64
+ # The final location of the stacktrace regardless of whether it's from within the project
65
+ #
66
+ # @return [String] the relative path to the file from the project root
67
+ def final_file
68
+ Pathname(final_location.pathname)
69
+ end
70
+
62
71
  # The file most likely to be the source of the underlying problem. Often, the most recent
63
72
  # backtrace files will be a gem or external library that's failing indirectly as a result
64
73
  # of a problem with local source code (not always, but frequently). In that case, the best
@@ -69,78 +78,76 @@ module Minitest
69
78
  Pathname(most_relevant_location.pathname)
70
79
  end
71
80
 
72
- # The line number of the `most_relevant_file` where the failure originated
81
+ # The final location from the stacktrace that is a test file
73
82
  #
74
- # @return [Integer] line number
75
- def most_relevant_failure_line
76
- most_relevant_location.line_number
83
+ # @return [String, nil] the relative path to the file from the project root
84
+ def test_file
85
+ Pathname(final_test_location.pathname)
77
86
  end
78
87
 
79
- # The final location of the stacktrace regardless of whether it's from within the project
88
+ # The final location from the stacktrace that is within the project directory
80
89
  #
81
- # @return [String] the relative path to the file from the project root
82
- def final_file
83
- Pathname(final_location.pathname)
84
- end
90
+ # @return [String, nil] the relative path to the file from the project root
91
+ def source_code_file
92
+ return nil if final_source_code_location.nil?
85
93
 
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
94
+ Pathname(final_source_code_location.pathname)
91
95
  end
92
96
 
93
- # The final location of the stacktrace regardless of whether it's from within the project
97
+ # The final location of the stacktrace from within the project (source code or test code)
94
98
  #
95
99
  # @return [String] the relative path to the file from the project root
96
100
  def project_file
97
- broken_test? ? test_file : source_code_file
101
+ return nil if project_location.nil?
102
+
103
+ Pathname(project_location.pathname)
98
104
  end
99
105
 
100
- # The line number of the `project_file` where the failure originated
106
+
107
+ # The line number of the `final_file` where the failure originated
101
108
  #
102
109
  # @return [Integer] line number
103
- def project_failure_line
104
- broken_test? ? test_failure_line || test_definition_line : source_code_failure_line
110
+ def final_failure_line
111
+ final_location.line_number
105
112
  end
106
113
 
107
- # The final location from the stacktrace that is within the project directory
114
+ # The line number of the `most_relevant_file` where the failure originated
108
115
  #
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
116
+ # @return [Integer] line number
117
+ def most_relevant_failure_line
118
+ most_relevant_location.line_number
114
119
  end
115
120
 
116
- # The line number of the `source_code_file` where the failure originated
121
+ # The line number of the `test_file` where the test is defined
117
122
  #
118
123
  # @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
124
+ def test_definition_line
125
+ test_definition_location.line_number
123
126
  end
124
127
 
125
- # The final location from the stacktrace that is within the project's test directory
128
+ # The line number from within the `test_file` test definition where the failure occurred
126
129
  #
127
- # @return [String, nil] the relative path to the file from the project root
128
- def test_file
129
- Pathname(test_location.pathname)
130
+ # @return [Integer] line number
131
+ def test_failure_line
132
+ final_test_location.line_number
130
133
  end
131
134
 
132
- # The line number of the `test_file` where the test is defined
135
+ # The line number of the `source_code_file` where the failure originated
133
136
  #
134
137
  # @return [Integer] line number
135
- def test_definition_line
136
- test_location.line_number
138
+ def source_code_failure_line
139
+ final_source_code_location&.line_number
137
140
  end
138
141
 
139
- # The line number from within the `test_file` test definition where the failure occurred
142
+ # The line number of the `project_file` where the failure originated
140
143
  #
141
144
  # @return [Integer] line number
142
- def test_failure_line
143
- backtrace.final_test_location&.line_number || test_definition_line
145
+ def project_failure_line
146
+ if !broken_test? && !source_code_file.nil?
147
+ source_code_failure_line
148
+ else
149
+ test_failure_line
150
+ end
144
151
  end
145
152
 
146
153
  # The line number from within the `test_file` test definition where the failure occurred
@@ -148,7 +155,7 @@ module Minitest
148
155
  # @return [Location] the last location from the backtrace or the test location if a backtrace
149
156
  # was not passed to the initializer
150
157
  def final_location
151
- backtrace? ? backtrace.final_location : test_location
158
+ backtrace.parsed_entries.any? ? backtrace.final_location : test_definition_location
152
159
  end
153
160
 
154
161
  # The file most likely to be the source of the underlying problem. Often, the most recent
@@ -159,22 +166,33 @@ module Minitest
159
166
  # @return [Array] file and line number of the most likely source of the problem
160
167
  def most_relevant_location
161
168
  [
162
- source_code_location,
163
- test_location,
169
+ final_source_code_location,
170
+ final_test_location,
164
171
  final_location
165
172
  ].compact.first
166
173
  end
167
174
 
168
- def project_location
169
- source_code_location || test_location
175
+ # Returns the final test location based on the backtrace if present. Otherwise falls back to
176
+ # the test location which represents the test definition.
177
+ #
178
+ # @return [Location] the final location from the test files
179
+ def final_test_location
180
+ backtrace.final_test_location || test_definition_location
170
181
  end
171
182
 
172
- def source_code_location
183
+ # Returns the final source code location based on the backtrace
184
+ #
185
+ # @return [Location] the final location from the source code files
186
+ def final_source_code_location
173
187
  backtrace.final_source_code_location
174
188
  end
175
189
 
176
- def backtrace?
177
- backtrace.parsed_entries.any?
190
+ # Returns the final project location based on the backtrace if present. Otherwise falls back
191
+ # to the test location which represents the test definition.
192
+ #
193
+ # @return [Location] the final location from the project files
194
+ def project_location
195
+ backtrace.final_project_location || test_definition_location
178
196
  end
179
197
  end
180
198
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Minitest
4
4
  module Heat
5
+ # Structured approach to collecting the locations of issues for generating a heat map
5
6
  class Map
6
7
  MAXIMUM_FILES_TO_SHOW = 5
7
8
 
@@ -11,18 +12,30 @@ module Minitest
11
12
  @hits = {}
12
13
  end
13
14
 
15
+ # Records a hit to the list of files and issue types
16
+ # @param filename [String] the unique path and file name for recordings hits
17
+ # @param line_number [Integer] the line number where the issue was encountered
18
+ # @param type [Symbol] the type of issue that was encountered (i.e. :failure, :error, etc.)
19
+ #
20
+ # @return [void]
14
21
  def add(filename, line_number, type)
15
22
  @hits[filename] ||= Hit.new(filename)
16
23
 
17
- @hits[filename].log(type, line_number)
24
+ @hits[filename].log(type.to_sym, line_number)
18
25
  end
19
26
 
27
+ # Returns a subset of affected files to keep the list from being overwhelming
28
+ #
29
+ # @return [Array] the list of files and the line numbers for each encountered issue type
20
30
  def file_hits
21
31
  hot_files.take(MAXIMUM_FILES_TO_SHOW)
22
32
  end
23
33
 
24
34
  private
25
35
 
36
+ # Sorts the files by hit "weight" so that the most problematic files are at the beginning
37
+ #
38
+ # @return [Array] the collection of files that encountred issues
26
39
  def hot_files
27
40
  hits.values.sort_by(&:weight).reverse
28
41
  end
@@ -23,13 +23,9 @@ module Minitest
23
23
  # Iterate over the selected lines from the backtrace
24
24
  backtrace_entries.each do |backtrace_entry|
25
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_tokens(backtrace_entry),
30
- source_code_line_token(backtrace_entry.source_code)
31
- ]
26
+ parts = backtrace_line_tokens(backtrace_entry)
32
27
 
28
+ # If it's the most recently modified file in the trace, add the token for that
33
29
  parts << file_freshness(backtrace_entry) if most_recently_modified?(backtrace_entry)
34
30
 
35
31
  @tokens << parts
@@ -57,6 +53,15 @@ module Minitest
57
53
 
58
54
  private
59
55
 
56
+ def backtrace_line_tokens(backtrace_entry)
57
+ [
58
+ indentation_token,
59
+ path_token(backtrace_entry),
60
+ *file_and_line_number_tokens(backtrace_entry),
61
+ source_code_line_token(backtrace_entry.source_code)
62
+ ]
63
+ end
64
+
60
65
  def all_backtrace_entries_from_project?
61
66
  backtrace_entries.all? { |line| line.path.to_s.include?(project_root_dir) }
62
67
  end
@@ -123,7 +128,7 @@ module Minitest
123
128
  end
124
129
 
125
130
  def style_for(path)
126
- style = path.to_s.include?(Dir.pwd) ? :default : :muted
131
+ path.to_s.include?(Dir.pwd) ? :default : :muted
127
132
  end
128
133
  end
129
134
  end
@@ -3,7 +3,8 @@
3
3
  module Minitest
4
4
  module Heat
5
5
  class Output
6
- class Issue
6
+ # Formats issues to output based on the issue type
7
+ class Issue # rubocop:disable Metrics/ClassLength
7
8
  attr_accessor :issue
8
9
 
9
10
  def initialize(issue)
@@ -77,11 +78,43 @@ module Minitest
77
78
  end
78
79
 
79
80
  def headline_tokens
80
- [[issue.type, issue.label], spacer_token, [:default, issue.test_name]]
81
+ [[issue.type, label(issue)], spacer_token, [:default, test_name(issue)]]
82
+ end
83
+
84
+ def test_name(issue)
85
+ test_prefix = 'test_'
86
+ identifier = issue.test_identifier
87
+
88
+ if identifier.start_with?(test_prefix)
89
+ identifier.delete_prefix(test_prefix).gsub('_', ' ').capitalize
90
+ else
91
+ identifier
92
+ end
93
+ end
94
+
95
+ def label(issue)
96
+ if issue.error? && issue.in_test?
97
+ # When the exception came out of the test itself, that's a different kind of exception
98
+ # that really only indicates there's a problem with the code in the test. It's kind of
99
+ # between an error and a test.
100
+ 'Broken Test'
101
+ elsif issue.error?
102
+ 'Error'
103
+ elsif issue.skipped?
104
+ 'Skipped'
105
+ elsif issue.painful?
106
+ 'Passed but Very Slow'
107
+ elsif issue.slow?
108
+ 'Passed but Slow'
109
+ elsif !issue.passed?
110
+ 'Failure'
111
+ else
112
+ 'Success'
113
+ end
81
114
  end
82
115
 
83
116
  def test_name_and_class_tokens
84
- [[:default, issue.test_class], *test_location_tokens ]
117
+ [[:default, issue.test_class], *test_location_tokens]
85
118
  end
86
119
 
87
120
  def backtrace_tokens
@@ -107,12 +140,12 @@ module Minitest
107
140
  end
108
141
 
109
142
  def summary_tokens
110
- [[:italicized, issue.summary.delete_suffix("---------------")]]
143
+ [[:italicized, issue.summary.delete_suffix('---------------').strip]]
111
144
  end
112
145
 
113
146
  def slowness_summary_tokens
114
147
  [
115
- [:bold, issue.slowness],
148
+ [:bold, slowness(issue)],
116
149
  spacer_token,
117
150
  [:default, issue.location.test_file.to_s.delete_prefix(Dir.pwd)],
118
151
  [:muted, ':'],
@@ -120,6 +153,10 @@ module Minitest
120
153
  ]
121
154
  end
122
155
 
156
+ def slowness(issue)
157
+ "#{issue.execution_time.round(2)}s"
158
+ end
159
+
123
160
  def newline_tokens
124
161
  []
125
162
  end
@@ -155,7 +192,6 @@ module Minitest
155
192
  def arrow_token
156
193
  Output::TOKENS[:muted_arrow]
157
194
  end
158
-
159
195
  end
160
196
  end
161
197
  end
@@ -3,6 +3,7 @@
3
3
  module Minitest
4
4
  module Heat
5
5
  class Output
6
+ # Generates the tokens to output the resulting heat map
6
7
  class Map
7
8
  attr_accessor :results
8
9
 
@@ -34,8 +35,10 @@ module Minitest
34
35
  end
35
36
 
36
37
  def relevant_issue_types
38
+ # These are always relevant.
37
39
  issue_types = %i[error broken failure]
38
40
 
41
+ # These are only relevant if there aren't more serious isues.
39
42
  issue_types << :skipped unless results.problems?
40
43
  issue_types << :painful unless results.problems? || results.skips.any?
41
44
  issue_types << :slow unless results.problems? || results.skips.any?
@@ -44,7 +47,7 @@ module Minitest
44
47
  end
45
48
 
46
49
  def pathname(file)
47
- directory = "#{file.pathname.dirname.to_s.delete_prefix(Dir.pwd)}/"
50
+ directory = "#{file.pathname.dirname.to_s.delete_prefix(Dir.pwd)}/".delete_prefix('/')
48
51
  filename = file.pathname.basename.to_s
49
52
 
50
53
  [
@@ -57,18 +60,29 @@ module Minitest
57
60
  def hit_line_numbers(file, issue_type)
58
61
  numbers = []
59
62
  line_numbers_for_issue_type = file.issues.fetch(issue_type) { [] }
60
- line_numbers_for_issue_type.sort.map do |line_number|
63
+ line_numbers_for_issue_type.map do |line_number|
61
64
  numbers << [issue_type, "#{line_number} "]
62
65
  end
66
+
63
67
  numbers
64
68
  end
65
69
 
66
70
  def line_numbers(file)
67
71
  line_number_tokens = []
72
+
73
+ # Merge the hits for all issue types into one list
68
74
  relevant_issue_types.each do |issue_type|
69
75
  line_number_tokens += hit_line_numbers(file, issue_type)
70
76
  end
71
- line_number_tokens.compact.sort_by { |number_token| number_token[1] }
77
+
78
+ # Sort the collected group of line number hits so they're in order
79
+ line_number_tokens.compact.sort do |a, b|
80
+ # Ensure the line numbers are integers for sorting (otherwise '100' comes before '12')
81
+ first_line_number = Integer(a[1].strip)
82
+ second_line_number = Integer(b[1].strip)
83
+
84
+ first_line_number <=> second_line_number
85
+ end
72
86
  end
73
87
  end
74
88
  end
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Minitest
4
4
  module Heat
5
- # Friendly API for printing nicely-formatted output to the console
6
5
  class Output
6
+ # Friendly API for printing consistent markers for the various issue types
7
7
  class Marker
8
8
  SYMBOLS = {
9
9
  success: '·',
@@ -12,7 +12,8 @@ module Minitest
12
12
  broken: 'B',
13
13
  error: 'E',
14
14
  skipped: 'S',
15
- failure: 'F'
15
+ failure: 'F',
16
+ reporter: '✖'
16
17
  }.freeze
17
18
 
18
19
  STYLES = {
@@ -22,7 +23,8 @@ module Minitest
22
23
  broken: :error,
23
24
  error: :error,
24
25
  skipped: :skipped,
25
- failure: :failure
26
+ failure: :failure,
27
+ reporter: :error
26
28
  }.freeze
27
29
 
28
30
  attr_accessor :issue_type
@@ -3,6 +3,7 @@
3
3
  module Minitest
4
4
  module Heat
5
5
  class Output
6
+ # Generates the output tokens to display the results summary
6
7
  class Results
7
8
  extend Forwardable
8
9
 
@@ -90,7 +91,7 @@ module Minitest
90
91
  end
91
92
 
92
93
  def test_count_token
93
- [:default, "#{pluralize(timer.test_count, 'test')}"]
94
+ [:default, pluralize(timer.test_count, 'test').to_s]
94
95
  end
95
96
 
96
97
  def tests_performance_token
@@ -98,7 +99,7 @@ module Minitest
98
99
  end
99
100
 
100
101
  def assertions_count_token
101
- [:default, "#{pluralize(timer.assertion_count, 'assertion')}"]
102
+ [:default, pluralize(timer.assertion_count, 'assertion').to_s]
102
103
  end
103
104
 
104
105
  def assertions_performance_token
@@ -3,7 +3,7 @@
3
3
  module Minitest
4
4
  module Heat
5
5
  class Output
6
- # Builds the collection of tokens representing a specific set of source code lines
6
+ # Generates the tokens representing a specific set of source code lines
7
7
  class SourceCode
8
8
  DEFAULT_LINE_COUNT = 3
9
9
  DEFAULT_INDENTATION_SPACES = 2
@@ -2,8 +2,9 @@
2
2
 
3
3
  module Minitest
4
4
  module Heat
5
- # Friendly API for printing nicely-formatted output to the console
6
5
  class Output
6
+ # Provides a convenient interface for creating console-friendly output while ensuring
7
+ # consistency in the applied styles.
7
8
  class Token
8
9
  class InvalidStyle < ArgumentError; end
9
10
 
@@ -15,14 +15,14 @@ module Minitest
15
15
  SYMBOLS = {
16
16
  middot: '·',
17
17
  arrow: '➜',
18
- lead: '|',
18
+ lead: '|'
19
19
  }.freeze
20
20
 
21
21
  TOKENS = {
22
- spacer: [:muted, " #{SYMBOLS[:middot]} "],
22
+ spacer: [:muted, " #{SYMBOLS[:middot]} "],
23
23
  muted_arrow: [:muted, " #{SYMBOLS[:arrow]} "],
24
- muted_lead: [:muted, "#{SYMBOLS[:lead]} "],
25
- }
24
+ muted_lead: [:muted, "#{SYMBOLS[:lead]} "]
25
+ }.freeze
26
26
 
27
27
  attr_reader :stream
28
28
 
@@ -42,8 +42,30 @@ module Minitest
42
42
  end
43
43
  alias newline puts
44
44
 
45
+ def issues_list(results)
46
+ # A couple of blank lines to create some breathing room
47
+ newline
48
+ newline
49
+
50
+ # Issues start with the least critical and go up to the most critical so that the most
51
+ # pressing issues are displayed at the bottom of the report in order to reduce scrolling.
52
+ # This way, as you fix issues, the list gets shorter, and eventually the least critical
53
+ # issues will be displayed without scrolling once more problematic issues are resolved.
54
+ %i[slows painfuls skips failures brokens errors].each do |issue_category|
55
+ next unless show?(issue_category, results)
56
+
57
+ results.send(issue_category).each { |issue| issue_details(issue) }
58
+ end
59
+ rescue => e
60
+ message = "Sorry, but Minitest Heat couldn't display the details of any failures."
61
+ exception_guidance(message, e)
62
+ end
63
+
45
64
  def issue_details(issue)
46
65
  print_tokens Minitest::Heat::Output::Issue.new(issue).tokens
66
+ rescue => e
67
+ message = "Sorry, but Minitest Heat couldn't display output for a failure."
68
+ exception_guidance(message, e)
47
69
  end
48
70
 
49
71
  def marker(issue_type)
@@ -53,15 +75,49 @@ module Minitest
53
75
  def compact_summary(results, timer)
54
76
  newline
55
77
  print_tokens ::Minitest::Heat::Output::Results.new(results, timer).tokens
78
+ rescue => e
79
+ message = "Sorry, but Minitest Heat couldn't display the summary."
80
+ exception_guidance(message, e)
56
81
  end
57
82
 
58
83
  def heat_map(map)
59
84
  newline
60
85
  print_tokens ::Minitest::Heat::Output::Map.new(map).tokens
86
+ newline
87
+ rescue => e
88
+ message = "Sorry, but Minitest Heat couldn't display the heat map."
89
+ exception_guidance(message, e)
90
+ end
91
+
92
+ def exception_guidance(message, exception)
93
+ newline
94
+ puts "#{message} Disabling Minitest Heat can get you back on track until the problem can be fixed."
95
+ puts "Please use the following exception details to submit an issue at https://github.com/garrettdimon/minitest-heat/issues"
96
+ puts "#{exception.message}:"
97
+ exception.backtrace.each do |line|
98
+ puts " #{line}"
99
+ end
100
+ newline
61
101
  end
62
102
 
63
103
  private
64
104
 
105
+ def no_problems?(results)
106
+ !results.problems?
107
+ end
108
+
109
+ def no_problems_or_skips?(results)
110
+ !results.problems? && results.skips.none?
111
+ end
112
+
113
+ def show?(issue_category, results)
114
+ case issue_category
115
+ when :skips then no_problems?(results)
116
+ when :painfuls, :slows then no_problems_or_skips?(results)
117
+ else true
118
+ end
119
+ end
120
+
65
121
  def style_enabled?
66
122
  stream.tty?
67
123
  end
@@ -11,9 +11,24 @@ module Minitest
11
11
  @heat_map = Heat::Map.new
12
12
  end
13
13
 
14
+ # Logs an issue to the results for later reporting
15
+ # @param issue [Issue] the issue generated from a given test result
16
+ #
17
+ # @return [type] [description]
14
18
  def record(issue)
19
+ # Record everything—even if it's a success
15
20
  @issues.push(issue)
16
- @heat_map.add(*issue.to_hit) if issue.hit?
21
+
22
+ # If it's not a genuine problem, we're done here, otherwise update the heat map
23
+ update_heat_map(issue) if issue.hit?
24
+ end
25
+
26
+ def update_heat_map(issue)
27
+ # Get the elements we need to generate a heat map entry
28
+ pathname = issue.location.project_file.to_s
29
+ line_number = issue.location.project_failure_line.to_i
30
+
31
+ @heat_map.add(pathname, line_number, issue.type)
17
32
  end
18
33
 
19
34
  def problems?
@@ -37,11 +52,11 @@ module Minitest
37
52
  end
38
53
 
39
54
  def painfuls
40
- @painfuls ||= select_issues(:painful).sort_by(&:time).reverse
55
+ @painfuls ||= select_issues(:painful).sort_by(&:execution_time).reverse
41
56
  end
42
57
 
43
58
  def slows
44
- @slows ||= select_issues(:slow).sort_by(&:time).reverse
59
+ @slows ||= select_issues(:slow).sort_by(&:execution_time).reverse
45
60
  end
46
61
 
47
62
  private
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Minitest
4
4
  module Heat
5
- VERSION = '0.0.9'
5
+ VERSION = '0.0.10'
6
6
  end
7
7
  end
data/lib/minitest/heat.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  require_relative 'heat/backtrace'
4
4
  require_relative 'heat/hit'
5
5
  require_relative 'heat/issue'
6
- require_relative 'heat/line'
7
6
  require_relative 'heat/location'
8
7
  require_relative 'heat/map'
9
8
  require_relative 'heat/output'
@@ -13,7 +12,8 @@ require_relative 'heat/timer'
13
12
  require_relative 'heat/version'
14
13
 
15
14
  module Minitest
16
- # Custom minitest reporter just for Reviewer. Focuses on printing directly actionable guidance.
15
+ # Custom Minitest reporter focused on generating output designed around efficiently identifying
16
+ # issues and potential solutions
17
17
  # - Colorize the Output
18
18
  # - What files had the most errors?
19
19
  # - Show the most impacted areas first.