minitest-heat 0.0.10 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minitest
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_definition' represents where the test is defined
10
+ # - 'test_failure' represents the last line from the project's tests. It is further differentiated by
11
+ # the line where the test is defined and the actual line of code in the test that geneated
12
+ # the failure or exception
13
+ # - 'source_code' represents the last line from the project's source code
14
+ # - 'project' represents the last source line, but falls back to the last test line
15
+ # - 'most_relevant' represents the most specific file to investigate starting with the source
16
+ # code and then looking to the test code with final line of the backtrace as a fallback
17
+ class Locations
18
+ attr_reader :test_definition, :backtrace
19
+
20
+ def initialize(test_definition_location, backtrace = [])
21
+ test_definition_pathname, test_definition_line_number = test_definition_location
22
+ @test_definition = ::Minitest::Heat::Location.new(pathname: test_definition_pathname, line_number: test_definition_line_number)
23
+
24
+ @backtrace = Backtrace.new(backtrace)
25
+ end
26
+
27
+ # Prints the pathname and line number of the location most likely to be the source of the
28
+ # test failure
29
+ #
30
+ # @return [String] ex. 'path/to/file.rb:12'
31
+ def to_s
32
+ "#{most_relevant.absolute_filename}:#{most_relevant.line_number}"
33
+ end
34
+
35
+ # Knows if the failure is contained within the test. For example, if there's bad code in a
36
+ # test, and it raises an exception, then it's really a broken test rather than a proper
37
+ # faiure.
38
+ #
39
+ # @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
43
+
44
+ # Knows if the failure occurred in the actual project source code—as opposed to the test or
45
+ # an external piece of code like a gem.
46
+ #
47
+ # @return [Boolean] true if there's a non-test project file in the stacktrace but it's not
48
+ # a result of a broken test
49
+ def proper_failure?
50
+ !source_code.nil? && !broken_test?
51
+ end
52
+
53
+ # The file most likely to be the source of the underlying problem. Often, the most recent
54
+ # backtrace files will be a gem or external library that's failing indirectly as a result
55
+ # of a problem with local source code (not always, but frequently). In that case, the best
56
+ # first place to focus is on the code you control.
57
+ #
58
+ # @return [Array] file and line number of the most likely source of the problem
59
+ def most_relevant
60
+ [
61
+ source_code,
62
+ test_failure,
63
+ final
64
+ ].compact.first
65
+ end
66
+
67
+ def freshest
68
+ backtrace.recently_modified_locations.first
69
+ end
70
+
71
+ # Returns the final test location based on the backtrace if present. Otherwise falls back to
72
+ # the test location which represents the test definition. The `test_definition` attribute
73
+ # provides the location of where the test is defined. `test_failure` represents the actual
74
+ # line from within the test where the problem occurred
75
+ #
76
+ # @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
80
+
81
+ # Returns the final source code location based on the backtrace
82
+ #
83
+ # @return [Location] the final location from the source code files
84
+ def source_code
85
+ backtrace.source_code_locations.first
86
+ end
87
+
88
+ # Returns the final project location based on the backtrace if present. Otherwise falls back
89
+ # to the test location which represents the test definition.
90
+ #
91
+ # @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
95
+
96
+ # The line number from within the `test_file` test definition where the failure occurred
97
+ #
98
+ # @return [Location] the last location from the backtrace or the test location if a backtrace
99
+ # was not passed to the initializer
100
+ def final
101
+ backtrace.locations.any? ? backtrace.locations.first : test_definition
102
+ end
103
+ end
104
+ end
105
+ end
@@ -18,10 +18,9 @@ module Minitest
18
18
  # @param type [Symbol] the type of issue that was encountered (i.e. :failure, :error, etc.)
19
19
  #
20
20
  # @return [void]
21
- def add(filename, line_number, type)
22
- @hits[filename] ||= Hit.new(filename)
23
-
24
- @hits[filename].log(type.to_sym, line_number)
21
+ def add(pathname, line_number, type, backtrace: [])
22
+ @hits[pathname.to_s] ||= Hit.new(pathname)
23
+ @hits[pathname.to_s].log(type.to_sym, line_number, backtrace: backtrace)
25
24
  end
26
25
 
27
26
  # Returns a subset of affected files to keep the list from being overwhelming
@@ -8,11 +8,11 @@ module Minitest
8
8
  DEFAULT_LINE_COUNT = 10
9
9
  DEFAULT_INDENTATION_SPACES = 2
10
10
 
11
- attr_accessor :location, :backtrace
11
+ attr_accessor :locations, :backtrace
12
12
 
13
- def initialize(location)
14
- @location = location
15
- @backtrace = location.backtrace
13
+ def initialize(locations)
14
+ @locations = locations
15
+ @backtrace = locations.backtrace
16
16
  @tokens = []
17
17
  end
18
18
 
@@ -21,14 +21,8 @@ module Minitest
21
21
  # final backtrace line if it might be relevant/helpful?
22
22
 
23
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 = backtrace_line_tokens(backtrace_entry)
27
-
28
- # If it's the most recently modified file in the trace, add the token for that
29
- parts << file_freshness(backtrace_entry) if most_recently_modified?(backtrace_entry)
30
-
31
- @tokens << parts
24
+ backtrace_locations.each do |location|
25
+ @tokens << backtrace_location_tokens(location)
32
26
  end
33
27
 
34
28
  @tokens
@@ -47,71 +41,74 @@ module Minitest
47
41
  # ...it could be influenced by a "compact" or "robust" reporter super-style?
48
42
  # ...it's smart about exceptions that were raised outside of the project?
49
43
  # ...it's smart about highlighting lines of code differently based on whether it's source code, test code, or external code?
50
- def backtrace_entries
51
- all_entries
44
+ def backtrace_locations
45
+ backtrace.locations.take(line_count)
52
46
  end
53
47
 
54
48
  private
55
49
 
56
- def backtrace_line_tokens(backtrace_entry)
50
+ def backtrace_location_tokens(location)
57
51
  [
58
52
  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
-
65
- def all_backtrace_entries_from_project?
66
- backtrace_entries.all? { |line| line.path.to_s.include?(project_root_dir) }
67
- end
68
-
69
- def project_root_dir
70
- Dir.pwd
71
- end
72
-
73
- def project_entries
74
- backtrace.project_entries.take(line_count)
53
+ path_token(location),
54
+ *file_and_line_number_tokens(location),
55
+ containining_element_token(location),
56
+ source_code_line_token(location),
57
+ most_recently_modified_token(location),
58
+ ].compact
75
59
  end
76
60
 
77
- def all_entries
78
- backtrace.parsed_entries.take(line_count)
61
+ # Determines if all lines to be displayed are from within the project directory
62
+ #
63
+ # @return [Boolean] true if all lines of the backtrace being displayed are from the project
64
+ def all_backtrace_from_project?
65
+ backtrace_locations.all?(&:project_file?)
79
66
  end
80
67
 
81
- def most_recently_modified?(line)
68
+ def most_recently_modified?(location)
82
69
  # If there's more than one line being displayed, and the current line is the freshest
83
- backtrace_entries.size > 1 && line == backtrace.freshest_project_location
70
+ backtrace_locations.size > 1 && location == locations.freshest
84
71
  end
85
72
 
86
73
  def indentation_token
87
74
  [:default, ' ' * indentation]
88
75
  end
89
76
 
90
- def path_token(line)
91
- style = line.to_s.include?(Dir.pwd) ? :default : :muted
92
- path = "#{line.path}/"
77
+ def path_token(location)
78
+ # If the line is a project file, help it stand out from the backtrace noise
79
+ style = location.project_file? ? :default : :muted
93
80
 
94
- # If all of the backtrace lines are from the project, no point in the added redundant
95
- # noise of showing the project root directory over and over again
96
- path = path.delete_prefix(project_root_dir) if all_backtrace_entries_from_project?
81
+ # If *all* of the backtrace lines are from the project, no point in the added redundant
82
+ # noise of showing the project root directory over and over again
83
+ path_format = all_backtrace_from_project? ? :relative_path : :absolute_path
97
84
 
98
- [style, path]
85
+ [style, location.send(path_format)]
99
86
  end
100
87
 
101
- def file_and_line_number_tokens(backtrace_entry)
102
- style = backtrace_entry.to_s.include?(Dir.pwd) ? :bold : :muted
88
+ def file_and_line_number_tokens(location)
89
+ style = location.to_s.include?(Dir.pwd) ? :bold : :muted
103
90
  [
104
- [style, backtrace_entry.file],
91
+ [style, location.filename],
105
92
  [:muted, ':'],
106
- [style, backtrace_entry.line_number]
93
+ [style, location.line_number]
107
94
  ]
108
95
  end
109
96
 
110
- def source_code_line_token(source_code)
111
- [:muted, " #{Output::SYMBOLS[:arrow]} `#{source_code.line.strip}`"]
97
+ def source_code_line_token(location)
98
+ return nil unless location.project_file?
99
+
100
+ [:muted, " #{Output::SYMBOLS[:arrow]} `#{location.source_code.line.strip}`"]
101
+ end
102
+
103
+ def containining_element_token(location)
104
+ return nil if !location.project_file? || location.container.nil? || location.container.empty?
105
+
106
+ [:muted, " in #{location.container}"]
112
107
  end
113
108
 
114
- def file_freshness(_line)
109
+ def most_recently_modified_token(location)
110
+ return nil unless most_recently_modified?(location)
111
+
115
112
  [:default, " #{Output::SYMBOLS[:middot]} Most Recently Modified File"]
116
113
  end
117
114
 
@@ -126,10 +123,6 @@ module Minitest
126
123
  def indentation
127
124
  DEFAULT_INDENTATION_SPACES
128
125
  end
129
-
130
- def style_for(path)
131
- path.to_s.include?(Dir.pwd) ? :default : :muted
132
- end
133
126
  end
134
127
  end
135
128
  end
@@ -5,10 +5,11 @@ module Minitest
5
5
  class Output
6
6
  # Formats issues to output based on the issue type
7
7
  class Issue # rubocop:disable Metrics/ClassLength
8
- attr_accessor :issue
8
+ attr_accessor :issue, :locations
9
9
 
10
10
  def initialize(issue)
11
11
  @issue = issue
12
+ @locations = issue.locations
12
13
  end
13
14
 
14
15
  def tokens
@@ -92,7 +93,7 @@ module Minitest
92
93
  end
93
94
  end
94
95
 
95
- def label(issue)
96
+ def label(issue) # rubocop:disable Metrics
96
97
  if issue.error? && issue.in_test?
97
98
  # When the exception came out of the test itself, that's a different kind of exception
98
99
  # that really only indicates there's a problem with the code in the test. It's kind of
@@ -118,24 +119,34 @@ module Minitest
118
119
  end
119
120
 
120
121
  def backtrace_tokens
121
- backtrace = ::Minitest::Heat::Output::Backtrace.new(issue.location)
122
-
123
- backtrace.tokens
122
+ @backtrace_tokens ||= ::Minitest::Heat::Output::Backtrace.new(locations).tokens
124
123
  end
125
124
 
126
125
  def test_location_tokens
127
- [[:default, test_file_short_location], [:muted, ':'], [:default, issue.test_definition_line], arrow_token, [:default, issue.test_failure_line], [:muted, test_line_source]]
126
+ [
127
+ [:default, locations.test_definition.relative_filename],
128
+ [:muted, ':'],
129
+ [:default, locations.test_definition.line_number],
130
+ arrow_token,
131
+ [:default, locations.test_failure.line_number],
132
+ [:muted, "\n #{locations.test_failure.source_code.line.strip}"]
133
+ ]
128
134
  end
129
135
 
130
136
  def location_tokens
131
- [[:default, most_relevant_short_location], [:muted, ':'], [:default, issue.location.most_relevant_failure_line], [:muted, most_relevant_line_source]]
137
+ [
138
+ [:default, locations.most_relevant.relative_filename],
139
+ [:muted, ':'],
140
+ [:default, locations.most_relevant.line_number],
141
+ [:muted, "\n #{locations.most_relevant.source_code.line.strip}"]
142
+ ]
132
143
  end
133
144
 
134
145
  def source_tokens
135
- filename = issue.location.project_file
136
- line_number = issue.location.project_failure_line
137
-
146
+ filename = locations.project.filename
147
+ line_number = locations.project.line_number
138
148
  source = Minitest::Heat::Source.new(filename, line_number: line_number)
149
+
139
150
  [[:muted, " #{Output::SYMBOLS[:arrow]} `#{source.line.strip}`"]]
140
151
  end
141
152
 
@@ -147,9 +158,10 @@ module Minitest
147
158
  [
148
159
  [:bold, slowness(issue)],
149
160
  spacer_token,
150
- [:default, issue.location.test_file.to_s.delete_prefix(Dir.pwd)],
161
+ [:default, locations.test_definition.relative_path],
162
+ [:default, locations.test_definition.filename],
151
163
  [:muted, ':'],
152
- [:default, issue.location.test_definition_line]
164
+ [:default, locations.test_definition.line_number]
153
165
  ]
154
166
  end
155
167
 
@@ -161,30 +173,6 @@ module Minitest
161
173
  []
162
174
  end
163
175
 
164
- def most_relevant_short_location
165
- issue.location.most_relevant_file.to_s.delete_prefix("#{Dir.pwd}/")
166
- end
167
-
168
- def test_file_short_location
169
- issue.location.test_file.to_s.delete_prefix("#{Dir.pwd}/")
170
- end
171
-
172
- def most_relevant_line_source
173
- filename = issue.location.project_file
174
- line_number = issue.location.project_failure_line
175
-
176
- source = Minitest::Heat::Source.new(filename, line_number: line_number)
177
- "\n #{source.line.strip}"
178
- end
179
-
180
- def test_line_source
181
- filename = issue.location.test_file
182
- line_number = issue.location.test_failure_line
183
-
184
- source = Minitest::Heat::Source.new(filename, line_number: line_number)
185
- "\n #{source.line.strip}"
186
- end
187
-
188
176
  def spacer_token
189
177
  Output::TOKENS[:spacer]
190
178
  end
@@ -13,16 +13,26 @@ module Minitest
13
13
  end
14
14
 
15
15
  def tokens
16
- map.file_hits.each do |hit|
17
- file_tokens = pathname(hit)
18
- line_number_tokens = line_numbers(hit)
16
+ results.heat_map.file_hits.each do |hit|
17
+ # If there's legitimate failures or errors, skips and slows aren't relevant
18
+ next unless relevant_issue_types?(hit)
19
19
 
20
- next if line_number_tokens.empty?
20
+ @tokens << [[:muted, ""]]
21
+ @tokens << file_summary_tokens(hit)
21
22
 
22
- @tokens << [
23
- *file_tokens,
24
- *line_number_tokens
25
- ]
23
+ repeats = repeated_line_numbers(hit)
24
+ next unless repeats.any?
25
+
26
+ repeats.each do |line_number|
27
+ @tokens << [[:muted, " Issues on Line #{line_number} initially triggered from these locations:"]]
28
+
29
+ traces = hit.lines[line_number.to_s]
30
+ sorted_traces = traces.sort_by { |trace| trace.locations.last.line_number }
31
+
32
+ sorted_traces.each do |trace|
33
+ @tokens << origination_location_token(trace)
34
+ end
35
+ end
26
36
  end
27
37
 
28
38
  @tokens
@@ -30,8 +40,26 @@ module Minitest
30
40
 
31
41
  private
32
42
 
33
- def map
34
- results.heat_map
43
+ def file_summary_tokens(hit)
44
+ pathname_tokens = pathname(hit)
45
+ line_number_list_tokens = sorted_line_number_list(hit)
46
+
47
+ [*pathname_tokens, *line_number_list_tokens]
48
+ end
49
+
50
+ def origination_location_token(trace)
51
+ # The earliest project line from the backtrace—this is probabyl wholly incorrect in terms
52
+ # of what would be the most helpful line to display, but it's a start.
53
+ location = trace.locations.last
54
+
55
+ [
56
+ [:muted, " #{Output::SYMBOLS[:arrow]} "],
57
+ [:default, location.relative_filename],
58
+ [:muted, ':'],
59
+ [:default, location.line_number],
60
+ [:muted, " in #{location.container}"],
61
+ [:muted, " #{Output::SYMBOLS[:arrow]} `location.source_code.line.strip`"],
62
+ ]
35
63
  end
36
64
 
37
65
  def relevant_issue_types
@@ -46,37 +74,69 @@ module Minitest
46
74
  issue_types
47
75
  end
48
76
 
49
- def pathname(file)
50
- directory = "#{file.pathname.dirname.to_s.delete_prefix(Dir.pwd)}/".delete_prefix('/')
51
- filename = file.pathname.basename.to_s
77
+ def relevant_issue_types?(hit)
78
+ intersection_issue_types = relevant_issue_types & hit.issues.keys
52
79
 
53
- [
54
- [:default, directory],
55
- [:bold, filename],
56
- [:default, ' · ']
57
- ]
80
+ intersection_issue_types.any?
58
81
  end
59
82
 
60
- def hit_line_numbers(file, issue_type)
61
- numbers = []
62
- line_numbers_for_issue_type = file.issues.fetch(issue_type) { [] }
63
- line_numbers_for_issue_type.map do |line_number|
64
- numbers << [issue_type, "#{line_number} "]
83
+ def repeated_line_numbers(hit)
84
+ repeated_line_numbers = []
85
+
86
+ hit.lines.each_pair do |line_number, traces|
87
+ # If there aren't multiple traces for a line number, it's not a repeat, right?
88
+ next unless traces.size > 1
89
+
90
+ repeated_line_numbers << Integer(line_number)
65
91
  end
66
92
 
67
- numbers
93
+ repeated_line_numbers.sort
68
94
  end
69
95
 
70
- def line_numbers(file)
96
+ def repeated_line_numbers?(hit)
97
+ repeated_line_numbers(hit).any?
98
+ end
99
+
100
+ def pathname(hit)
101
+ directory = hit.pathname.dirname.to_s.delete_prefix("#{Dir.pwd}/")
102
+ filename = hit.pathname.basename.to_s
103
+
104
+ [
105
+ [:default, "#{directory}/"],
106
+ [:bold, filename],
107
+ [:default, ' · ']
108
+ ]
109
+ end
110
+
111
+ def line_number_tokens_for_hit(hit)
71
112
  line_number_tokens = []
72
113
 
73
- # Merge the hits for all issue types into one list
74
114
  relevant_issue_types.each do |issue_type|
75
- line_number_tokens += hit_line_numbers(file, issue_type)
115
+ # Retrieve any line numbers for the issue type
116
+ line_numbers_for_issue_type = hit.issues.fetch(issue_type) { [] }
117
+
118
+ # Build the list of tokens representing styled line numbers
119
+ line_numbers_for_issue_type.each do |line_number|
120
+ line_number_tokens << line_number_token(issue_type, line_number)
121
+ end
76
122
  end
77
123
 
124
+ line_number_tokens.compact
125
+ end
126
+
127
+ def line_number_token(style, line_number)
128
+ [style, "#{line_number} "]
129
+ end
130
+
131
+ # Generates the line number tokens styled based on their error type
132
+ #
133
+ # @param [Hit] hit the instance of the hit file details to build the heat map entry
134
+ #
135
+ # @return [Array] the arrays representing the line number tokens to display next to a file
136
+ # name in the heat map
137
+ def sorted_line_number_list(hit)
78
138
  # Sort the collected group of line number hits so they're in order
79
- line_number_tokens.compact.sort do |a, b|
139
+ line_number_tokens_for_hit(hit).sort do |a, b|
80
140
  # Ensure the line numbers are integers for sorting (otherwise '100' comes before '12')
81
141
  first_line_number = Integer(a[1].strip)
82
142
  second_line_number = Integer(b[1].strip)
@@ -11,7 +11,7 @@ require_relative 'output/token'
11
11
  module Minitest
12
12
  module Heat
13
13
  # Friendly API for printing nicely-formatted output to the console
14
- class Output
14
+ class Output # rubocop:disable Metrics/ClassLength
15
15
  SYMBOLS = {
16
16
  middot: '·',
17
17
  arrow: '➜',
@@ -56,14 +56,14 @@ module Minitest
56
56
 
57
57
  results.send(issue_category).each { |issue| issue_details(issue) }
58
58
  end
59
- rescue => e
59
+ rescue StandardError => e
60
60
  message = "Sorry, but Minitest Heat couldn't display the details of any failures."
61
61
  exception_guidance(message, e)
62
62
  end
63
63
 
64
64
  def issue_details(issue)
65
65
  print_tokens Minitest::Heat::Output::Issue.new(issue).tokens
66
- rescue => e
66
+ rescue StandardError => e
67
67
  message = "Sorry, but Minitest Heat couldn't display output for a failure."
68
68
  exception_guidance(message, e)
69
69
  end
@@ -75,7 +75,7 @@ module Minitest
75
75
  def compact_summary(results, timer)
76
76
  newline
77
77
  print_tokens ::Minitest::Heat::Output::Results.new(results, timer).tokens
78
- rescue => e
78
+ rescue StandardError => e
79
79
  message = "Sorry, but Minitest Heat couldn't display the summary."
80
80
  exception_guidance(message, e)
81
81
  end
@@ -84,7 +84,7 @@ module Minitest
84
84
  newline
85
85
  print_tokens ::Minitest::Heat::Output::Map.new(map).tokens
86
86
  newline
87
- rescue => e
87
+ rescue StandardError => e
88
88
  message = "Sorry, but Minitest Heat couldn't display the heat map."
89
89
  exception_guidance(message, e)
90
90
  end
@@ -92,7 +92,7 @@ module Minitest
92
92
  def exception_guidance(message, exception)
93
93
  newline
94
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"
95
+ puts 'Please use the following exception details to submit an issue at https://github.com/garrettdimon/minitest-heat/issues'
96
96
  puts "#{exception.message}:"
97
97
  exception.backtrace.each do |line|
98
98
  puts " #{line}"
@@ -138,7 +138,11 @@ module Minitest
138
138
  def print_tokens(lines_of_tokens)
139
139
  lines_of_tokens.each do |tokens|
140
140
  tokens.each do |token|
141
- print Token.new(*token).to_s(token_format)
141
+ begin
142
+ print Token.new(*token).to_s(token_format)
143
+ rescue
144
+ puts token.inspect
145
+ end
142
146
  end
143
147
  newline
144
148
  end
@@ -19,16 +19,21 @@ module Minitest
19
19
  # Record everything—even if it's a success
20
20
  @issues.push(issue)
21
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?
22
+ # If it's not a genuine problem, we're done here...
23
+ return unless issue.hit?
24
+
25
+ # ...otherwise update the heat map
26
+ update_heat_map(issue)
24
27
  end
25
28
 
26
29
  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
+ # For heat map purposes, only the project backtrace lines are interesting
31
+ pathname, line_number = issue.locations.project.to_a
32
+
33
+ # Backtrace is only relevant for exception-generating issues, not slows, skips, or failures
34
+ backtrace = issue.error? ? issue.locations.backtrace.project_locations : []
30
35
 
31
- @heat_map.add(pathname, line_number, issue.type)
36
+ @heat_map.add(pathname, line_number, issue.type, backtrace: backtrace)
32
37
  end
33
38
 
34
39
  def problems?
@@ -35,7 +35,7 @@ module Minitest
35
35
  #
36
36
  # @return [Array<String>] the range of lines of code around
37
37
  def lines
38
- return [line] if max_line_count == 1
38
+ return [line].compact if max_line_count == 1
39
39
 
40
40
  file_lines[(line_numbers.first - 1)..(line_numbers.last - 1)]
41
41
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Minitest
4
4
  module Heat
5
- VERSION = '0.0.10'
5
+ VERSION = '0.0.11'
6
6
  end
7
7
  end
data/lib/minitest/heat.rb CHANGED
@@ -4,6 +4,7 @@ require_relative 'heat/backtrace'
4
4
  require_relative 'heat/hit'
5
5
  require_relative 'heat/issue'
6
6
  require_relative 'heat/location'
7
+ require_relative 'heat/locations'
7
8
  require_relative 'heat/map'
8
9
  require_relative 'heat/output'
9
10
  require_relative 'heat/results'
@@ -6,7 +6,7 @@ module Minitest # rubocop:disable Style/Documentation
6
6
  def self.plugin_heat_init(options)
7
7
  io = options.fetch(:io, $stdout)
8
8
 
9
- self.reporter.reporters.reject! do |reporter|
9
+ reporter.reporters.reject! do |reporter|
10
10
  # Minitest Heat acts as a unified Progress *and* Summary reporter. Using other reporters of
11
11
  # those types in conjunction with it creates some overly-verbose output
12
12
  reporter.is_a?(ProgressReporter) || reporter.is_a?(SummaryReporter)