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.
- checksums.yaml +4 -4
- data/Gemfile.lock +6 -6
- 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 +11 -11
- 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 +46 -53
- data/lib/minitest/heat/output/issue.rb +24 -36
- data/lib/minitest/heat/output/map.rb +88 -28
- data/lib/minitest/heat/output.rb +11 -7
- 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 +12 -5
- metadata +5 -4
- data/lib/minitest/heat/backtrace/line.rb +0 -118
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a2dd8d434e13e9719addb1a2afa9f25c751ca6e5712c752244afb1823ac4740
|
4
|
+
data.tar.gz: 722f33b1145d79510f9007b54663198972c1ce4461a7a79c14a7bfdeb190721e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 955b848541141572e94aeff4b02586253f70db8e313af111e54b91c3e8a64c7c44c0f0f90c672f699f9296dc5f691ea04c7852eb948f6ab680173b8261512eee
|
7
|
+
data.tar.gz: 9c3085267bea53a6734a085c982fd8348442715036ccf2b636de9c997f3dc14662e25a709faab1b4425ffb46ceeb109f43672336fadf0bca7c4e0cbbccd0463c
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
minitest-heat (0.0.
|
4
|
+
minitest-heat (0.0.11)
|
5
5
|
minitest
|
6
6
|
|
7
7
|
GEM
|
@@ -10,7 +10,7 @@ GEM
|
|
10
10
|
ast (2.4.2)
|
11
11
|
awesome_print (1.9.2)
|
12
12
|
coderay (1.1.3)
|
13
|
-
dead_end (
|
13
|
+
dead_end (3.0.2)
|
14
14
|
docile (1.4.0)
|
15
15
|
method_source (1.0.0)
|
16
16
|
minitest (5.14.4)
|
@@ -24,7 +24,7 @@ GEM
|
|
24
24
|
rake (12.3.3)
|
25
25
|
regexp_parser (2.1.1)
|
26
26
|
rexml (3.2.5)
|
27
|
-
rubocop (1.22.
|
27
|
+
rubocop (1.22.3)
|
28
28
|
parallel (~> 1.10)
|
29
29
|
parser (>= 3.0.0.0)
|
30
30
|
rainbow (>= 2.2.2, < 4.0)
|
@@ -33,9 +33,9 @@ GEM
|
|
33
33
|
rubocop-ast (>= 1.12.0, < 2.0)
|
34
34
|
ruby-progressbar (~> 1.7)
|
35
35
|
unicode-display_width (>= 1.4.0, < 3.0)
|
36
|
-
rubocop-ast (1.
|
36
|
+
rubocop-ast (1.13.0)
|
37
37
|
parser (>= 3.0.1.1)
|
38
|
-
rubocop-minitest (0.15.
|
38
|
+
rubocop-minitest (0.15.2)
|
39
39
|
rubocop (>= 0.90, < 2.0)
|
40
40
|
rubocop-rake (0.6.0)
|
41
41
|
rubocop (~> 1.0)
|
@@ -64,4 +64,4 @@ DEPENDENCIES
|
|
64
64
|
simplecov
|
65
65
|
|
66
66
|
BUNDLED WITH
|
67
|
-
2.
|
67
|
+
2.2.30
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Minitest
|
6
|
+
module Heat
|
7
|
+
class Backtrace
|
8
|
+
# Represents a line from a backtrace to provide more convenient access to information about
|
9
|
+
# the relevant file and line number for displaying in test results
|
10
|
+
module LineParser
|
11
|
+
# Parses a line from a backtrace in order to convert it to usable components
|
12
|
+
def self.read(raw_text)
|
13
|
+
raw_pathname, raw_line_number, raw_container = raw_text.split(':')
|
14
|
+
raw_container = raw_container.delete_prefix('in `').delete_suffix("'")
|
15
|
+
|
16
|
+
::Minitest::Heat::Location.new(
|
17
|
+
pathname: raw_pathname,
|
18
|
+
line_number: raw_line_number,
|
19
|
+
container: raw_container
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'backtrace/
|
3
|
+
require_relative 'backtrace/line_parser'
|
4
4
|
|
5
5
|
module Minitest
|
6
6
|
module Heat
|
@@ -9,7 +9,7 @@ module Minitest
|
|
9
9
|
attr_reader :raw_backtrace
|
10
10
|
|
11
11
|
# Creates a more flexible backtrace data structure by parsing the lines of the backtrace to
|
12
|
-
# extract
|
12
|
+
# extract specific subsets of lines for investigating the offending files and line numbers
|
13
13
|
# @param raw_backtrace [Array] the array of lines from the backtrace
|
14
14
|
#
|
15
15
|
# @return [self]
|
@@ -24,80 +24,42 @@ module Minitest
|
|
24
24
|
raw_backtrace.empty?
|
25
25
|
end
|
26
26
|
|
27
|
-
#
|
28
|
-
# dependency or the Ruby core libraries
|
27
|
+
# All lines of the backtrace converted to Backtrace::LineParser's
|
29
28
|
#
|
30
|
-
# @return [Line] the
|
31
|
-
def
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
# The final location from within the project exposed in the backtrace. Could be test files or
|
36
|
-
# source code files
|
37
|
-
#
|
38
|
-
# @return [Line] the final project location from the backtrace parsed as a Backtrace::Line
|
39
|
-
def final_project_location
|
40
|
-
project_entries.first
|
41
|
-
end
|
42
|
-
|
43
|
-
# The most recently modified location from within the project
|
44
|
-
#
|
45
|
-
# @return [Line] the most recently modified project location from the backtrace parsed as a
|
46
|
-
# Backtrace::Line
|
47
|
-
def freshest_project_location
|
48
|
-
recently_modified_entries.first
|
49
|
-
end
|
29
|
+
# @return [Line] the full set of backtrace lines parsed as Backtrace::LineParser instances
|
30
|
+
def locations
|
31
|
+
return [] if raw_backtrace.nil?
|
50
32
|
|
51
|
-
|
52
|
-
#
|
53
|
-
# @return [Line] the final source code location from the backtrace parsed as a Backtrace::Line
|
54
|
-
def final_source_code_location
|
55
|
-
source_code_entries.first
|
33
|
+
@locations ||= raw_backtrace.map { |entry| Backtrace::LineParser.read(entry) }
|
56
34
|
end
|
57
35
|
|
58
|
-
#
|
36
|
+
# All entries from the backtrace within the project and sorted with the most recently modified
|
37
|
+
# files at the beginning
|
59
38
|
#
|
60
|
-
# @return [Line] the
|
61
|
-
def
|
62
|
-
|
39
|
+
# @return [Line] the sorted backtrace lines from the project parsed as Backtrace::LineParser's
|
40
|
+
def recently_modified_locations
|
41
|
+
@recently_modified_locations ||= project_locations.sort_by(&:mtime).reverse
|
63
42
|
end
|
64
43
|
|
65
44
|
# All entries from the backtrace that are files within the project
|
66
45
|
#
|
67
|
-
# @return [Line] the backtrace lines from within the project parsed as Backtrace::
|
68
|
-
def
|
69
|
-
@
|
70
|
-
end
|
71
|
-
|
72
|
-
# All entries from the backtrace within the project and sorted with the most recently modified
|
73
|
-
# files at the beginning
|
74
|
-
#
|
75
|
-
# @return [Line] the sorted backtrace lines from the project parsed as Backtrace::Line's
|
76
|
-
def recently_modified_entries
|
77
|
-
@recently_modified_entries ||= project_entries.sort_by(&:mtime).reverse
|
46
|
+
# @return [Line] the backtrace lines from within the project parsed as Backtrace::LineParser's
|
47
|
+
def project_locations
|
48
|
+
@project_locations ||= locations.select(&:project_file?)
|
78
49
|
end
|
79
50
|
|
80
51
|
# All entries from the backtrace within the project tests
|
81
52
|
#
|
82
|
-
# @return [Line] the backtrace lines from within the project tests parsed as Backtrace::
|
83
|
-
def
|
84
|
-
@
|
53
|
+
# @return [Line] the backtrace lines from within the project tests parsed as Backtrace::LineParser's
|
54
|
+
def test_locations
|
55
|
+
@test_locations ||= project_locations.select(&:test_file?)
|
85
56
|
end
|
86
57
|
|
87
58
|
# All source code entries from the backtrace (i.e. excluding tests)
|
88
59
|
#
|
89
|
-
# @return [Line] the backtrace lines from within the source code parsed as Backtrace::
|
90
|
-
def
|
91
|
-
@
|
92
|
-
end
|
93
|
-
|
94
|
-
# All lines of the backtrace converted to Backtrace::Line's
|
95
|
-
#
|
96
|
-
# @return [Line] the full set of backtrace lines parsed as Backtrace::Line instances
|
97
|
-
def parsed_entries
|
98
|
-
return [] if raw_backtrace.nil?
|
99
|
-
|
100
|
-
@parsed_entries ||= raw_backtrace.map { |entry| Backtrace::Line.parse_backtrace(entry) }
|
60
|
+
# @return [Line] the backtrace lines from within the source code parsed as Backtrace::LineParser's
|
61
|
+
def source_code_locations
|
62
|
+
@source_code_locations ||= project_locations.select(&:source_code_file?)
|
101
63
|
end
|
102
64
|
end
|
103
65
|
end
|
data/lib/minitest/heat/hit.rb
CHANGED
@@ -5,8 +5,10 @@ require 'forwardable'
|
|
5
5
|
module Minitest
|
6
6
|
module Heat
|
7
7
|
# Kind of like an issue, but instead of focusing on a failing test, it covers all issues for a
|
8
|
-
# given file to build a heat map of the affected files
|
8
|
+
# given file to build a heat map of the affected files and line numbers
|
9
9
|
class Hit
|
10
|
+
Trace = Struct.new(:type, :line_number, :locations)
|
11
|
+
|
10
12
|
# So we can sort hot spots by liklihood of being the most important spot to check out before
|
11
13
|
# trying to fix something. These are ranked based on the possibility they represent ripple
|
12
14
|
# effects where fixing one problem could potentially fix multiple other failures.
|
@@ -23,7 +25,7 @@ module Minitest
|
|
23
25
|
slow: 0
|
24
26
|
}.freeze
|
25
27
|
|
26
|
-
attr_reader :pathname, :issues
|
28
|
+
attr_reader :pathname, :issues, :lines
|
27
29
|
|
28
30
|
# Creates an instance of a Hit for the given pathname. It must be the full pathname to
|
29
31
|
# uniquely identify the file or we could run into collisions that muddy the water and
|
@@ -34,16 +36,26 @@ module Minitest
|
|
34
36
|
def initialize(pathname)
|
35
37
|
@pathname = Pathname(pathname)
|
36
38
|
@issues = {}
|
39
|
+
@lines = {}
|
37
40
|
end
|
38
41
|
|
39
42
|
# Adds a record of a given issue type for the line number
|
40
43
|
# @param type [Symbol] one of Issue::TYPES
|
41
44
|
# @param line_number [Integer,String] the line number to record the issue on
|
45
|
+
# @param backtrace: nil [Array<Location>] the project locations from the backtrace
|
42
46
|
#
|
43
|
-
# @return [
|
44
|
-
def log(type, line_number)
|
45
|
-
|
46
|
-
|
47
|
+
# @return [void]
|
48
|
+
def log(type, line_number, backtrace: [])
|
49
|
+
line_number = Integer(line_number)
|
50
|
+
issue_type = type.to_sym
|
51
|
+
|
52
|
+
# Store issues by issue type with an array of line numbers
|
53
|
+
@issues[issue_type] ||= []
|
54
|
+
@issues[issue_type] << line_number
|
55
|
+
|
56
|
+
# Store issues by line number with an array of Traces
|
57
|
+
@lines[line_number.to_s] ||= []
|
58
|
+
@lines[line_number.to_s] << Trace.new(issue_type, line_number, backtrace)
|
47
59
|
end
|
48
60
|
|
49
61
|
# Calcuates an approximate weight to serve as a proxy for which files are most likely to be
|
data/lib/minitest/heat/issue.rb
CHANGED
@@ -18,7 +18,7 @@ module Minitest
|
|
18
18
|
}.freeze
|
19
19
|
|
20
20
|
attr_reader :assertions,
|
21
|
-
:
|
21
|
+
:locations,
|
22
22
|
:message,
|
23
23
|
:test_class,
|
24
24
|
:test_identifier,
|
@@ -27,7 +27,7 @@ module Minitest
|
|
27
27
|
:error,
|
28
28
|
:skipped
|
29
29
|
|
30
|
-
def_delegators :@
|
30
|
+
def_delegators :@locations, :backtrace, :test_definition_line, :test_failure_line
|
31
31
|
|
32
32
|
# Extracts the necessary data from result.
|
33
33
|
# @param result [Minitest::Result] the instance of Minitest::Result to examine
|
@@ -39,7 +39,7 @@ module Minitest
|
|
39
39
|
|
40
40
|
new(
|
41
41
|
assertions: result.assertions,
|
42
|
-
|
42
|
+
test_location: result.source_location,
|
43
43
|
test_class: result.klass,
|
44
44
|
test_identifier: result.name,
|
45
45
|
execution_time: result.time,
|
@@ -47,7 +47,7 @@ module Minitest
|
|
47
47
|
error: result.error?,
|
48
48
|
skipped: result.skipped?,
|
49
49
|
message: exception&.message,
|
50
|
-
backtrace: exception&.backtrace
|
50
|
+
backtrace: exception&.backtrace
|
51
51
|
)
|
52
52
|
end
|
53
53
|
|
@@ -57,7 +57,7 @@ module Minitest
|
|
57
57
|
# @param assertions: 1 [Integer] the number of assertions in the result
|
58
58
|
# @param message: nil [String] exception if there is one
|
59
59
|
# @param backtrace: [] [Array<String>] the array of backtrace lines from an exception
|
60
|
-
# @param
|
60
|
+
# @param test_location: nil [Array<String, Integer>] the locations identifier for a test
|
61
61
|
# @param test_class: nil [String] the class name for the test result's containing class
|
62
62
|
# @param test_identifier: nil [String] the name of the test
|
63
63
|
# @param execution_time: nil [Float] the time it took to run the test
|
@@ -66,11 +66,11 @@ module Minitest
|
|
66
66
|
# @param skipped: false [Boolean] true if the test was skipped
|
67
67
|
#
|
68
68
|
# @return [type] [description]
|
69
|
-
def initialize(assertions: 1,
|
69
|
+
def initialize(assertions: 1, test_location: ['Unrecognized Test File', 1], backtrace: [], execution_time: 0.0, message: nil, test_class: nil, test_identifier: nil, passed: false, error: false, skipped: false)
|
70
70
|
@message = message
|
71
71
|
|
72
72
|
@assertions = Integer(assertions)
|
73
|
-
@
|
73
|
+
@locations = Locations.new(test_location, backtrace)
|
74
74
|
|
75
75
|
@test_class = test_class
|
76
76
|
@test_identifier = test_identifier
|
@@ -135,18 +135,18 @@ module Minitest
|
|
135
135
|
# Determines if the issue is an exception that was raised from directly within a test
|
136
136
|
# definition. In these cases, it's more likely to be a quick fix.
|
137
137
|
#
|
138
|
-
# @return [Boolean] true if the final
|
138
|
+
# @return [Boolean] true if the final locations of the stacktrace was a test file
|
139
139
|
def in_test?
|
140
|
-
|
140
|
+
locations.broken_test?
|
141
141
|
end
|
142
142
|
|
143
143
|
# Determines if the issue is an exception that was raised from directly within the project
|
144
144
|
# codebase.
|
145
145
|
#
|
146
|
-
# @return [Boolean] true if the final
|
146
|
+
# @return [Boolean] true if the final locations of the stacktrace was a file from the project
|
147
147
|
# (as opposed to a dependency or Ruby library)
|
148
148
|
def in_source?
|
149
|
-
|
149
|
+
locations.proper_failure?
|
150
150
|
end
|
151
151
|
|
152
152
|
# Was the result a pass? i.e. Skips aren't passes or failures. Slows are still passes. So this
|
@@ -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
|