minitest-heat 0.0.10 → 0.0.14
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/Gemfile.lock +6 -6
- data/README.md +48 -13
- data/examples/exceptions.png +0 -0
- data/examples/failures.png +0 -0
- data/examples/map.png +0 -0
- data/examples/markers.png +0 -0
- data/examples/skips.png +0 -0
- data/examples/slows.png +0 -0
- data/lib/minitest/heat/backtrace/line_parser.rb +25 -0
- data/lib/minitest/heat/backtrace.rb +21 -59
- data/lib/minitest/heat/hit.rb +18 -6
- data/lib/minitest/heat/issue.rb +35 -17
- data/lib/minitest/heat/location.rb +113 -132
- data/lib/minitest/heat/locations.rb +105 -0
- data/lib/minitest/heat/map.rb +3 -4
- data/lib/minitest/heat/output/backtrace.rb +88 -70
- data/lib/minitest/heat/output/issue.rb +56 -83
- data/lib/minitest/heat/output/map.rb +116 -28
- data/lib/minitest/heat/output.rb +27 -13
- data/lib/minitest/heat/results.rb +11 -6
- data/lib/minitest/heat/source.rb +1 -1
- data/lib/minitest/heat/version.rb +1 -1
- data/lib/minitest/heat.rb +1 -0
- data/lib/minitest/heat_plugin.rb +1 -1
- data/lib/minitest/heat_reporter.rb +13 -6
- metadata +11 -4
- data/lib/minitest/heat/backtrace/line.rb +0 -118
@@ -2,197 +2,178 @@
|
|
2
2
|
|
3
3
|
module Minitest
|
4
4
|
module Heat
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
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
|
+
# Consistent structure for extracting information about a given location. In addition to the
|
6
|
+
# pathname to the file and the line number in the file, it can also include information about
|
7
|
+
# the containing method or block and retrieve source code for the location.
|
16
8
|
class Location
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
@line_number = Integer(line_number)
|
21
|
-
super
|
22
|
-
end
|
23
|
-
end
|
9
|
+
UNRECOGNIZED = '(Unrecognized File)'
|
10
|
+
UNKNOWN_MODIFICATION_TIME = Time.at(0)
|
11
|
+
UNKNOWN_MODIFICATION_SECONDS = -1
|
24
12
|
|
25
|
-
|
13
|
+
attr_accessor :raw_pathname, :raw_line_number, :raw_container
|
26
14
|
|
27
|
-
|
28
|
-
|
29
|
-
|
15
|
+
# Initialize a new Location
|
16
|
+
#
|
17
|
+
# @param [Pathname, String] pathname: the pathname to the file
|
18
|
+
# @param [Integer] line_number: the line number of the location within the file
|
19
|
+
# @param [String] container: nil the containing method or block for the issue
|
20
|
+
#
|
21
|
+
# @return [self]
|
22
|
+
def initialize(pathname:, line_number:, container: nil)
|
23
|
+
@raw_pathname = pathname
|
24
|
+
@raw_line_number = line_number
|
25
|
+
@raw_container = container
|
30
26
|
end
|
31
27
|
|
32
|
-
#
|
33
|
-
# test failure
|
28
|
+
# Generates a formatted string describing the line of code similar to the original backtrace
|
34
29
|
#
|
35
|
-
# @return [String]
|
30
|
+
# @return [String] a consistently-formatted, human-readable string about the line of code
|
36
31
|
def to_s
|
37
|
-
"#{
|
38
|
-
end
|
39
|
-
|
40
|
-
def local?
|
41
|
-
broken_test? || proper_failure?
|
32
|
+
"#{absolute_path}#{filename}:#{line_number} in `#{container}`"
|
42
33
|
end
|
43
34
|
|
44
|
-
#
|
45
|
-
# test, and it raises an exception, then it's really a broken test rather than a proper
|
46
|
-
# faiure.
|
35
|
+
# Generates a simplified location array with the pathname and line number
|
47
36
|
#
|
48
|
-
# @return [
|
49
|
-
def
|
50
|
-
|
37
|
+
# @return [Array<Pathname, Integer>] a no-frills location pair
|
38
|
+
def to_a
|
39
|
+
[
|
40
|
+
pathname,
|
41
|
+
line_number
|
42
|
+
]
|
51
43
|
end
|
52
44
|
|
53
|
-
#
|
54
|
-
# an external piece of code like a gem.
|
45
|
+
# A short relative pathname and line number pair
|
55
46
|
#
|
56
|
-
# @return [
|
57
|
-
|
58
|
-
|
59
|
-
!source_code_file.nil? && !broken_test?
|
47
|
+
# @return [String] the short filename/line number combo. ex. `dir/file.rb:23`
|
48
|
+
def short
|
49
|
+
"#{relative_filename}:#{line_number}"
|
60
50
|
end
|
61
51
|
|
62
|
-
|
63
|
-
|
64
|
-
# The final location of the stacktrace regardless of whether it's from within the project
|
52
|
+
# Determine if there is a file and text at the given line number
|
65
53
|
#
|
66
|
-
# @return [
|
67
|
-
def
|
68
|
-
|
54
|
+
# @return [Boolean] true if the file exists and has text at the given line number
|
55
|
+
def exists?
|
56
|
+
pathname.exist? && source_code.lines.any?
|
69
57
|
end
|
70
58
|
|
71
|
-
# The
|
72
|
-
#
|
73
|
-
# of a problem with local source code (not always, but frequently). In that case, the best
|
74
|
-
# first place to focus is on the code you control.
|
59
|
+
# The pathanme for the location. Written to be safe and fallbackto the project directory if
|
60
|
+
# an exception is raised ocnverting the value to a pathname
|
75
61
|
#
|
76
|
-
# @return [
|
77
|
-
def
|
78
|
-
Pathname(
|
62
|
+
# @return [Pathname] a pathname instance for the relevant file
|
63
|
+
def pathname
|
64
|
+
Pathname(raw_pathname)
|
65
|
+
rescue ArgumentError
|
66
|
+
Pathname(Dir.pwd)
|
79
67
|
end
|
80
68
|
|
81
|
-
#
|
69
|
+
# A safe interface to getting a string representing the path portion of the file
|
82
70
|
#
|
83
|
-
# @return [String
|
84
|
-
|
85
|
-
|
71
|
+
# @return [String] either the path/directory portion of the file name or '(Unrecognized File)'
|
72
|
+
# if the offending file can't be found for some reason
|
73
|
+
def path
|
74
|
+
pathname.exist? ? pathname.dirname.to_s : UNRECOGNIZED
|
86
75
|
end
|
87
76
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
def source_code_file
|
92
|
-
return nil if final_source_code_location.nil?
|
77
|
+
def absolute_path
|
78
|
+
pathname.exist? ? "#{path}/" : UNRECOGNIZED
|
79
|
+
end
|
93
80
|
|
94
|
-
|
81
|
+
def relative_path
|
82
|
+
pathname.exist? ? absolute_path.delete_prefix("#{project_root_dir}/") : UNRECOGNIZED
|
95
83
|
end
|
96
84
|
|
97
|
-
#
|
85
|
+
# A safe interface for getting a string representing the filename portion of the file
|
98
86
|
#
|
99
|
-
# @return [String] the
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
Pathname(project_location.pathname)
|
87
|
+
# @return [String] either the filename portion of the file or '(Unrecognized File)'
|
88
|
+
# if the offending file can't be found for some reason
|
89
|
+
def filename
|
90
|
+
pathname.exist? ? pathname.basename.to_s : UNRECOGNIZED
|
104
91
|
end
|
105
92
|
|
93
|
+
def absolute_filename
|
94
|
+
pathname.exist? ? pathname.to_s : UNRECOGNIZED
|
95
|
+
end
|
106
96
|
|
107
|
-
|
108
|
-
|
109
|
-
# @return [Integer] line number
|
110
|
-
def final_failure_line
|
111
|
-
final_location.line_number
|
97
|
+
def relative_filename
|
98
|
+
pathname.exist? ? pathname.to_s.delete_prefix("#{project_root_dir}/") : UNRECOGNIZED
|
112
99
|
end
|
113
100
|
|
114
|
-
#
|
101
|
+
# Line number identifying the specific line in the file
|
115
102
|
#
|
116
|
-
# @return [Integer] line number
|
117
|
-
|
118
|
-
|
103
|
+
# @return [Integer] line number for the file
|
104
|
+
#
|
105
|
+
def line_number
|
106
|
+
Integer(raw_line_number)
|
107
|
+
rescue ArgumentError
|
108
|
+
1
|
119
109
|
end
|
120
110
|
|
121
|
-
# The
|
111
|
+
# The containing method or block details for the location
|
122
112
|
#
|
123
|
-
# @return [
|
124
|
-
def
|
125
|
-
|
113
|
+
# @return [String] the containing method of the line of code
|
114
|
+
def container
|
115
|
+
raw_container.nil? ? '(Unknown Container)' : String(raw_container)
|
126
116
|
end
|
127
117
|
|
128
|
-
#
|
118
|
+
# Looks up the source code for the location. Can return multiple lines of source code from
|
119
|
+
# the surrounding lines of code for the primary line
|
120
|
+
#
|
121
|
+
# @param [Integer] max_line_count: 1 the maximum number of lines to return from the source
|
129
122
|
#
|
130
|
-
# @return [
|
131
|
-
def
|
132
|
-
|
123
|
+
# @return [Source] an instance of Source for accessing lines and their line numbers
|
124
|
+
def source_code(max_line_count: 1)
|
125
|
+
Minitest::Heat::Source.new(
|
126
|
+
pathname.to_s,
|
127
|
+
line_number: line_number,
|
128
|
+
max_line_count: max_line_count
|
129
|
+
)
|
133
130
|
end
|
134
131
|
|
135
|
-
#
|
132
|
+
# Determines if a given file is from the project directory
|
136
133
|
#
|
137
|
-
# @return [
|
138
|
-
def
|
139
|
-
|
134
|
+
# @return [Boolean] true if the file is in the project (source code or test)
|
135
|
+
def project_file?
|
136
|
+
path.include?(project_root_dir)
|
140
137
|
end
|
141
138
|
|
142
|
-
#
|
139
|
+
# Determines if a given file follows the standard approaching to naming test files.
|
143
140
|
#
|
144
|
-
# @return [
|
145
|
-
def
|
146
|
-
|
147
|
-
source_code_failure_line
|
148
|
-
else
|
149
|
-
test_failure_line
|
150
|
-
end
|
141
|
+
# @return [Boolean] true if the file name starts with `test_` or ends with `_test.rb`
|
142
|
+
def test_file?
|
143
|
+
filename.to_s.start_with?('test_') || filename.to_s.end_with?('_test.rb')
|
151
144
|
end
|
152
145
|
|
153
|
-
#
|
146
|
+
# Determines if a given file is a non-test file from the project directory
|
154
147
|
#
|
155
|
-
# @return [
|
156
|
-
|
157
|
-
|
158
|
-
backtrace.parsed_entries.any? ? backtrace.final_location : test_definition_location
|
148
|
+
# @return [Boolean] true if the file is in the project but not a test file
|
149
|
+
def source_code_file?
|
150
|
+
project_file? && !test_file?
|
159
151
|
end
|
160
152
|
|
161
|
-
#
|
162
|
-
# backtrace files will be a gem or external library that's failing indirectly as a result
|
163
|
-
# of a problem with local source code (not always, but frequently). In that case, the best
|
164
|
-
# first place to focus is on the code you control.
|
153
|
+
# A safe interface to getting the last modified time for the file in question
|
165
154
|
#
|
166
|
-
# @return [
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
final_test_location,
|
171
|
-
final_location
|
172
|
-
].compact.first
|
155
|
+
# @return [Time] the timestamp for when the file in question was last modified or `Time.at(0)`
|
156
|
+
# if the offending file can't be found for some reason
|
157
|
+
def mtime
|
158
|
+
pathname.exist? ? pathname.mtime : UNKNOWN_MODIFICATION_TIME
|
173
159
|
end
|
174
160
|
|
175
|
-
#
|
176
|
-
# the test location which represents the test definition.
|
161
|
+
# A safe interface to getting the number of seconds since the file was modified
|
177
162
|
#
|
178
|
-
# @return [
|
179
|
-
|
180
|
-
|
163
|
+
# @return [Integer] the number of seconds since the file was modified or `-1` if the offending
|
164
|
+
# file can't be found for some reason
|
165
|
+
def age_in_seconds
|
166
|
+
pathname.exist? ? seconds_ago : UNKNOWN_MODIFICATION_SECONDS
|
181
167
|
end
|
182
168
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
backtrace.final_source_code_location
|
169
|
+
private
|
170
|
+
|
171
|
+
def project_root_dir
|
172
|
+
Dir.pwd
|
188
173
|
end
|
189
174
|
|
190
|
-
|
191
|
-
|
192
|
-
#
|
193
|
-
# @return [Location] the final location from the project files
|
194
|
-
def project_location
|
195
|
-
backtrace.final_project_location || test_definition_location
|
175
|
+
def seconds_ago
|
176
|
+
(Time.now - mtime).to_i
|
196
177
|
end
|
197
178
|
end
|
198
179
|
end
|
@@ -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
|
data/lib/minitest/heat/map.rb
CHANGED
@@ -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(
|
22
|
-
@hits[
|
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
|
@@ -3,115 +3,137 @@
|
|
3
3
|
module Minitest
|
4
4
|
module Heat
|
5
5
|
class Output
|
6
|
-
# Builds the collection of tokens for a backtrace when an exception occurs
|
6
|
+
# Builds the collection of tokens for displaying a backtrace when an exception occurs
|
7
7
|
class Backtrace
|
8
8
|
DEFAULT_LINE_COUNT = 10
|
9
9
|
DEFAULT_INDENTATION_SPACES = 2
|
10
10
|
|
11
|
-
attr_accessor :
|
11
|
+
attr_accessor :locations, :backtrace
|
12
12
|
|
13
|
-
def initialize(
|
14
|
-
@
|
15
|
-
@backtrace =
|
13
|
+
def initialize(locations)
|
14
|
+
@locations = locations
|
15
|
+
@backtrace = locations.backtrace
|
16
16
|
@tokens = []
|
17
17
|
end
|
18
18
|
|
19
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
20
|
# Iterate over the selected lines from the backtrace
|
24
|
-
|
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
|
32
|
-
end
|
33
|
-
|
34
|
-
@tokens
|
21
|
+
@tokens = backtrace_locations.map { |location| backtrace_location_tokens(location) }
|
35
22
|
end
|
36
23
|
|
24
|
+
# Determines the number of lines to display from the backtrace.
|
25
|
+
#
|
26
|
+
# @return [Integer] the number of lines to limit the backtrace to
|
37
27
|
def line_count
|
28
|
+
# Defined as a method instead of using the constant directlyr in order to easily support
|
29
|
+
# adding options for controlling how many lines are displayed from a backtrace.
|
30
|
+
#
|
31
|
+
# For example, instead of a fixed number, the backtrace could dynamically calculate how
|
32
|
+
# many lines it should displaye in order to get to the origination point. Or it could have
|
33
|
+
# a default, but inteligently go back further if the backtrace meets some criteria for
|
34
|
+
# displaying more lines.
|
38
35
|
DEFAULT_LINE_COUNT
|
39
36
|
end
|
40
37
|
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
38
|
+
# A subset of parsed lines from the backtrace.
|
39
|
+
#
|
40
|
+
# @return [Array<Location>] the backtrace locations determined to be most relevant to the
|
41
|
+
# context of the underlying issue
|
42
|
+
def backtrace_locations
|
43
|
+
# This could eventually have additional intelligence to determine what lines are most
|
44
|
+
# relevant for a given type of issue. For now, it simply takes the line numbers, but the
|
45
|
+
# idea is that long-term, it could adjust that on the fly to keep the line count as low
|
46
|
+
# as possible but expand it if necessary to ensure enough context is displayed.
|
47
|
+
#
|
48
|
+
# - If there's no clear cut details about the source of the error from within the project,
|
49
|
+
# it could display the entire backtrace without filtering anything.
|
50
|
+
# - It could scan the backtrace to the first appearance of project files and then display
|
51
|
+
# all of the lines that occurred after that instance
|
52
|
+
# - It coudl filter the lines differently whether the issue originated from a test or from
|
53
|
+
# the source code.
|
54
|
+
# - It could allow supporting a "compact" or "robust" reporter style so that someone on
|
55
|
+
# a smaller screen could easily reduce the information shown so that the results could
|
56
|
+
# be higher density even if it means truncating some occasionally useful details
|
57
|
+
# - It could be smarter about displaying context/guidance when the full backtrace is from
|
58
|
+
# outside the project's code
|
59
|
+
#
|
60
|
+
# But for now. It just grabs some lines.
|
61
|
+
backtrace.locations.take(line_count)
|
52
62
|
end
|
53
63
|
|
54
64
|
private
|
55
65
|
|
56
|
-
def
|
66
|
+
def backtrace_location_tokens(location)
|
57
67
|
[
|
58
68
|
indentation_token,
|
59
|
-
path_token(
|
60
|
-
*file_and_line_number_tokens(
|
61
|
-
|
62
|
-
|
63
|
-
|
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)
|
69
|
+
path_token(location),
|
70
|
+
*file_and_line_number_tokens(location),
|
71
|
+
containining_element_token(location),
|
72
|
+
source_code_line_token(location),
|
73
|
+
most_recently_modified_token(location),
|
74
|
+
].compact
|
75
75
|
end
|
76
76
|
|
77
|
-
|
78
|
-
|
77
|
+
# Determines if all lines to be displayed are from within the project directory
|
78
|
+
#
|
79
|
+
# @return [Boolean] true if all lines of the backtrace being displayed are from the project
|
80
|
+
def all_backtrace_from_project?
|
81
|
+
backtrace_locations.all?(&:project_file?)
|
79
82
|
end
|
80
83
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
+
# Determines if the file referenced by a backtrace line is the most recently modified file
|
85
|
+
# of all the files referenced in the visible backtrace locations.
|
86
|
+
#
|
87
|
+
# @param [Location] location the location to examine
|
88
|
+
#
|
89
|
+
# @return [<type>] <description>
|
90
|
+
#
|
91
|
+
def most_recently_modified?(location)
|
92
|
+
# If there's more than one line being displayed (otherwise, with one line, of course it's
|
93
|
+
# the most recently modified because there_aren't any others) and the current line is the
|
94
|
+
# same as the freshest location in the backtrace
|
95
|
+
backtrace_locations.size > 1 && location == locations.freshest
|
84
96
|
end
|
85
97
|
|
86
98
|
def indentation_token
|
87
99
|
[:default, ' ' * indentation]
|
88
100
|
end
|
89
101
|
|
90
|
-
def path_token(
|
91
|
-
|
92
|
-
|
102
|
+
def path_token(location)
|
103
|
+
# If the line is a project file, help it stand out from the backtrace noise
|
104
|
+
style = location.project_file? ? :default : :muted
|
93
105
|
|
94
|
-
# If all of the backtrace lines are from the project, no point in the added redundant
|
95
|
-
#
|
96
|
-
|
106
|
+
# If *all* of the backtrace lines are from the project, no point in the added redundant
|
107
|
+
# noise of showing the project root directory over and over again
|
108
|
+
path_format = all_backtrace_from_project? ? :relative_path : :absolute_path
|
97
109
|
|
98
|
-
[style,
|
110
|
+
[style, location.send(path_format)]
|
99
111
|
end
|
100
112
|
|
101
|
-
def file_and_line_number_tokens(
|
102
|
-
style =
|
113
|
+
def file_and_line_number_tokens(location)
|
114
|
+
style = location.to_s.include?(Dir.pwd) ? :bold : :muted
|
103
115
|
[
|
104
|
-
[style,
|
116
|
+
[style, location.filename],
|
105
117
|
[:muted, ':'],
|
106
|
-
[style,
|
118
|
+
[style, location.line_number]
|
107
119
|
]
|
108
120
|
end
|
109
121
|
|
110
|
-
def source_code_line_token(
|
111
|
-
|
122
|
+
def source_code_line_token(location)
|
123
|
+
return nil unless location.project_file?
|
124
|
+
|
125
|
+
[:muted, " #{Output::SYMBOLS[:arrow]} `#{location.source_code.line.strip}`"]
|
126
|
+
end
|
127
|
+
|
128
|
+
def containining_element_token(location)
|
129
|
+
return nil if !location.project_file? || location.container.nil? || location.container.empty?
|
130
|
+
|
131
|
+
[:muted, " in #{location.container}"]
|
112
132
|
end
|
113
133
|
|
114
|
-
def
|
134
|
+
def most_recently_modified_token(location)
|
135
|
+
return nil unless most_recently_modified?(location)
|
136
|
+
|
115
137
|
[:default, " #{Output::SYMBOLS[:middot]} Most Recently Modified File"]
|
116
138
|
end
|
117
139
|
|
@@ -126,10 +148,6 @@ module Minitest
|
|
126
148
|
def indentation
|
127
149
|
DEFAULT_INDENTATION_SPACES
|
128
150
|
end
|
129
|
-
|
130
|
-
def style_for(path)
|
131
|
-
path.to_s.include?(Dir.pwd) ? :default : :muted
|
132
|
-
end
|
133
151
|
end
|
134
152
|
end
|
135
153
|
end
|