minitest-heat 0.0.10 → 0.0.14
Sign up to get free protection for your applications and to get access to all the features.
- 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
|