minitest-heat 0.0.8 → 0.0.12
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/.rubocop.yml +1 -0
- data/Gemfile.lock +6 -6
- data/README.md +1 -1
- data/lib/minitest/heat/backtrace/line_parser.rb +25 -0
- data/lib/minitest/heat/backtrace.rb +39 -43
- data/lib/minitest/heat/hit.rb +36 -19
- data/lib/minitest/heat/issue.rb +98 -79
- data/lib/minitest/heat/location.rb +115 -116
- data/lib/minitest/heat/locations.rb +105 -0
- data/lib/minitest/heat/map.rb +16 -4
- data/lib/minitest/heat/output/backtrace.rb +52 -44
- data/lib/minitest/heat/output/issue.rb +65 -62
- data/lib/minitest/heat/output/map.rb +99 -25
- data/lib/minitest/heat/output/marker.rb +5 -3
- data/lib/minitest/heat/output/results.rb +3 -2
- data/lib/minitest/heat/output/source_code.rb +1 -1
- data/lib/minitest/heat/output/token.rb +2 -1
- data/lib/minitest/heat/output.rb +66 -6
- data/lib/minitest/heat/results.rb +23 -3
- data/lib/minitest/heat/source.rb +1 -1
- data/lib/minitest/heat/version.rb +1 -1
- data/lib/minitest/heat.rb +3 -2
- data/lib/minitest/heat_plugin.rb +9 -17
- data/lib/minitest/heat_reporter.rb +24 -34
- metadata +5 -4
- data/lib/minitest/heat/line.rb +0 -74
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d95ecb7dad79fef49909bf766fb8c428489a71a0a2a50f815e4f0e7b0dbe480b
|
4
|
+
data.tar.gz: f9e03a7fcb915c0f5f0e74eb20fb7cec2d65de7e4d73f6130b61d50d48d8fafc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9903cc0ea5edb14005ddcef3027ea09414100013f3e829989670a238bce2c2bd02b0227835632658022b82fff658d86dcab07aa58c7ddecdfb2452408258f97
|
7
|
+
data.tar.gz: 2ea218a55b33d8ba6fdb41f8aa3ef7d07443537e482a2a995a7aad2c01fc952c22135263739aa0963d9e0e88a153ffd146ac32e4a9979c5a32ccf218391f66cf
|
data/.rubocop.yml
CHANGED
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.12)
|
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
|
data/README.md
CHANGED
@@ -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,69 +1,65 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'backtrace/line_parser'
|
4
|
+
|
3
5
|
module Minitest
|
4
6
|
module Heat
|
5
7
|
# Wrapper for separating backtrace into component parts
|
6
8
|
class Backtrace
|
7
9
|
attr_reader :raw_backtrace
|
8
10
|
|
11
|
+
# Creates a more flexible backtrace data structure by parsing the lines of the backtrace to
|
12
|
+
# extract specific subsets of lines for investigating the offending files and line numbers
|
13
|
+
# @param raw_backtrace [Array] the array of lines from the backtrace
|
14
|
+
#
|
15
|
+
# @return [self]
|
9
16
|
def initialize(raw_backtrace)
|
10
|
-
@raw_backtrace = raw_backtrace
|
17
|
+
@raw_backtrace = Array(raw_backtrace)
|
11
18
|
end
|
12
19
|
|
20
|
+
# Determines if the raw backtrace has values in it
|
21
|
+
#
|
22
|
+
# @return [Boolean] true if there's no backtrace or it's empty
|
13
23
|
def empty?
|
14
|
-
raw_backtrace.
|
15
|
-
end
|
16
|
-
|
17
|
-
def final_location
|
18
|
-
parsed_entries.first
|
19
|
-
end
|
20
|
-
|
21
|
-
def final_project_location
|
22
|
-
project_entries.first
|
23
|
-
end
|
24
|
-
|
25
|
-
def freshest_project_location
|
26
|
-
recently_modified_entries.first
|
27
|
-
end
|
28
|
-
|
29
|
-
def final_source_code_location
|
30
|
-
source_code_entries.first
|
24
|
+
raw_backtrace.empty?
|
31
25
|
end
|
32
26
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
@project_entries ||= parsed_entries.select { |entry| entry.path.to_s.include?(Dir.pwd) }
|
39
|
-
end
|
40
|
-
|
41
|
-
def recently_modified_entries
|
42
|
-
@recently_modified_entries ||= project_entries.sort_by(&:mtime).reverse
|
43
|
-
end
|
27
|
+
# All lines of the backtrace converted to Backtrace::LineParser's
|
28
|
+
#
|
29
|
+
# @return [Line] the full set of backtrace lines parsed as Backtrace::LineParser instances
|
30
|
+
def locations
|
31
|
+
return [] if raw_backtrace.nil?
|
44
32
|
|
45
|
-
|
46
|
-
@tests_entries ||= project_entries.select { |entry| test_file?(entry) }
|
33
|
+
@locations ||= raw_backtrace.map { |entry| Backtrace::LineParser.read(entry) }
|
47
34
|
end
|
48
35
|
|
49
|
-
|
50
|
-
|
36
|
+
# All entries from the backtrace within the project and sorted with the most recently modified
|
37
|
+
# files at the beginning
|
38
|
+
#
|
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
|
51
42
|
end
|
52
43
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
44
|
+
# All entries from the backtrace that are files within the project
|
45
|
+
#
|
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?)
|
57
49
|
end
|
58
50
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
51
|
+
# All entries from the backtrace within the project tests
|
52
|
+
#
|
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?)
|
63
56
|
end
|
64
57
|
|
65
|
-
|
66
|
-
|
58
|
+
# All source code entries from the backtrace (i.e. excluding tests)
|
59
|
+
#
|
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?)
|
67
63
|
end
|
68
64
|
end
|
69
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
|
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,34 +25,43 @@ module Minitest
|
|
23
25
|
slow: 0
|
24
26
|
}.freeze
|
25
27
|
|
26
|
-
attr_reader :pathname, :issues
|
28
|
+
attr_reader :pathname, :issues, :lines
|
27
29
|
|
30
|
+
# Creates an instance of a Hit for the given pathname. It must be the full pathname to
|
31
|
+
# uniquely identify the file or we could run into collisions that muddy the water and
|
32
|
+
# obscure which files had which errors on which line numbers
|
33
|
+
# @param pathname [Pathname,String] the full pathname to the file
|
34
|
+
#
|
35
|
+
# @return [self]
|
28
36
|
def initialize(pathname)
|
29
37
|
@pathname = Pathname(pathname)
|
30
38
|
@issues = {}
|
39
|
+
@lines = {}
|
31
40
|
end
|
32
41
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
42
|
+
# Adds a record of a given issue type for the line number
|
43
|
+
# @param type [Symbol] one of Issue::TYPES
|
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
|
46
|
+
#
|
47
|
+
# @return [void]
|
48
|
+
def log(type, line_number, backtrace: [])
|
49
|
+
line_number = Integer(line_number)
|
50
|
+
issue_type = type.to_sym
|
37
51
|
|
38
|
-
|
39
|
-
|
40
|
-
|
52
|
+
# Store issues by issue type with an array of line numbers
|
53
|
+
@issues[issue_type] ||= []
|
54
|
+
@issues[issue_type] << line_number
|
41
55
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
def issue_count
|
47
|
-
count = 0
|
48
|
-
Issue::TYPES.each do |issue_type|
|
49
|
-
count += issues.fetch(issue_type) { [] }.size
|
50
|
-
end
|
51
|
-
count
|
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)
|
52
59
|
end
|
53
60
|
|
61
|
+
# Calcuates an approximate weight to serve as a proxy for which files are most likely to be
|
62
|
+
# the most problematic across the various issue types
|
63
|
+
#
|
64
|
+
# @return [Integer] the problem weight for the file
|
54
65
|
def weight
|
55
66
|
weight = 0
|
56
67
|
issues.each_pair do |type, values|
|
@@ -59,6 +70,9 @@ module Minitest
|
|
59
70
|
weight
|
60
71
|
end
|
61
72
|
|
73
|
+
# The total issue count for the file across all issue types. Includes duplicates if they exist
|
74
|
+
#
|
75
|
+
# @return [Integer] the sum of the counts for all line numbers for all issue types
|
62
76
|
def count
|
63
77
|
count = 0
|
64
78
|
issues.each_pair do |_type, values|
|
@@ -67,6 +81,9 @@ module Minitest
|
|
67
81
|
count
|
68
82
|
end
|
69
83
|
|
84
|
+
# The full set of unique line numbers across all issue types
|
85
|
+
#
|
86
|
+
# @return [Array<Integer>] the full set of unique offending line numbers for the hit
|
70
87
|
def line_numbers
|
71
88
|
line_numbers = []
|
72
89
|
issues.each_pair do |_type, values|
|
data/lib/minitest/heat/issue.rb
CHANGED
@@ -17,36 +17,68 @@ module Minitest
|
|
17
17
|
painful: 3.0
|
18
18
|
}.freeze
|
19
19
|
|
20
|
-
attr_reader :
|
20
|
+
attr_reader :assertions,
|
21
|
+
:locations,
|
22
|
+
:message,
|
23
|
+
:test_class,
|
24
|
+
:test_identifier,
|
25
|
+
:execution_time,
|
26
|
+
:passed,
|
27
|
+
:error,
|
28
|
+
:skipped
|
29
|
+
|
30
|
+
def_delegators :@locations, :backtrace, :test_definition_line, :test_failure_line
|
31
|
+
|
32
|
+
# Extracts the necessary data from result.
|
33
|
+
# @param result [Minitest::Result] the instance of Minitest::Result to examine
|
34
|
+
#
|
35
|
+
# @return [Issue] the instance of the issue to use for examining the result
|
36
|
+
def self.from_result(result)
|
37
|
+
# Not all results are failures, so we use the safe navigation operator
|
38
|
+
exception = result.failure&.exception
|
39
|
+
|
40
|
+
new(
|
41
|
+
assertions: result.assertions,
|
42
|
+
test_location: result.source_location,
|
43
|
+
test_class: result.klass,
|
44
|
+
test_identifier: result.name,
|
45
|
+
execution_time: result.time,
|
46
|
+
passed: result.passed?,
|
47
|
+
error: result.error?,
|
48
|
+
skipped: result.skipped?,
|
49
|
+
message: exception&.message,
|
50
|
+
backtrace: exception&.backtrace
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Creates an instance of Issue. In general, the `from_result` approach will be more convenient
|
55
|
+
# for standard usage, but for lower-level purposes like testing, the initializer provides3
|
56
|
+
# more fine-grained control
|
57
|
+
# @param assertions: 1 [Integer] the number of assertions in the result
|
58
|
+
# @param message: nil [String] exception if there is one
|
59
|
+
# @param backtrace: [] [Array<String>] the array of backtrace lines from an exception
|
60
|
+
# @param test_location: nil [Array<String, Integer>] the locations identifier for a test
|
61
|
+
# @param test_class: nil [String] the class name for the test result's containing class
|
62
|
+
# @param test_identifier: nil [String] the name of the test
|
63
|
+
# @param execution_time: nil [Float] the time it took to run the test
|
64
|
+
# @param passed: false [Boolean] true if the test explicitly passed, false otherwise
|
65
|
+
# @param error: false [Boolean] true if the test raised an exception
|
66
|
+
# @param skipped: false [Boolean] true if the test was skipped
|
67
|
+
#
|
68
|
+
# @return [type] [description]
|
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
|
+
@message = message
|
21
71
|
|
22
|
-
|
23
|
-
|
72
|
+
@assertions = Integer(assertions)
|
73
|
+
@locations = Locations.new(test_location, backtrace)
|
24
74
|
|
25
|
-
|
26
|
-
@
|
75
|
+
@test_class = test_class
|
76
|
+
@test_identifier = test_identifier
|
77
|
+
@execution_time = Float(execution_time)
|
27
78
|
|
28
|
-
@
|
29
|
-
@
|
30
|
-
|
31
|
-
|
32
|
-
# Returns the primary location of the issue with the present working directory removed from
|
33
|
-
# the string for conciseness
|
34
|
-
#
|
35
|
-
# @return [String] the pathname for the file relative to the present working directory
|
36
|
-
def short_location
|
37
|
-
location.to_s.delete_prefix("#{Dir.pwd}/")
|
38
|
-
end
|
39
|
-
|
40
|
-
# Converts an issue to the key attributes for recording a 'hit'
|
41
|
-
#
|
42
|
-
# @return [Array] the filename, failure line, and issue type for categorizing a 'hit' to
|
43
|
-
# support generating the heat map
|
44
|
-
def to_hit
|
45
|
-
[
|
46
|
-
location.project_file.to_s,
|
47
|
-
location.project_failure_line,
|
48
|
-
type
|
49
|
-
]
|
79
|
+
@passed = passed
|
80
|
+
@error = error
|
81
|
+
@skipped = skipped
|
50
82
|
end
|
51
83
|
|
52
84
|
# Classifies different issue types so they can be categorized, organized, and prioritized.
|
@@ -58,7 +90,7 @@ module Minitest
|
|
58
90
|
# painfully slow and should get more attention.
|
59
91
|
#
|
60
92
|
# @return [Symbol] issue type for classifying issues and reporting
|
61
|
-
def type
|
93
|
+
def type # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
62
94
|
if error? && in_test?
|
63
95
|
:broken
|
64
96
|
elsif error?
|
@@ -67,9 +99,9 @@ module Minitest
|
|
67
99
|
:skipped
|
68
100
|
elsif !passed?
|
69
101
|
:failure
|
70
|
-
elsif painful?
|
102
|
+
elsif passed? && painful?
|
71
103
|
:painful
|
72
|
-
elsif slow?
|
104
|
+
elsif passed? && slow?
|
73
105
|
:slow
|
74
106
|
else
|
75
107
|
:success
|
@@ -81,7 +113,7 @@ module Minitest
|
|
81
113
|
#
|
82
114
|
# @return [Boolean] true if the test did not pass or if it was slow
|
83
115
|
def hit?
|
84
|
-
!passed? || slow?
|
116
|
+
!passed? || slow? || painful?
|
85
117
|
end
|
86
118
|
|
87
119
|
# Determines if a test should be considered slow by comparing it to the low end definition of
|
@@ -89,7 +121,7 @@ module Minitest
|
|
89
121
|
#
|
90
122
|
# @return [Boolean] true if the test took longer to run than `SLOW_THRESHOLDS[:slow]`
|
91
123
|
def slow?
|
92
|
-
|
124
|
+
execution_time >= SLOW_THRESHOLDS[:slow] && execution_time < SLOW_THRESHOLDS[:painful]
|
93
125
|
end
|
94
126
|
|
95
127
|
# Determines if a test should be considered painfully slow by comparing it to the high end
|
@@ -97,77 +129,64 @@ module Minitest
|
|
97
129
|
#
|
98
130
|
# @return [Boolean] true if the test took longer to run than `SLOW_THRESHOLDS[:painful]`
|
99
131
|
def painful?
|
100
|
-
|
132
|
+
execution_time >= SLOW_THRESHOLDS[:painful]
|
101
133
|
end
|
102
134
|
|
103
135
|
# Determines if the issue is an exception that was raised from directly within a test
|
104
136
|
# definition. In these cases, it's more likely to be a quick fix.
|
105
137
|
#
|
106
|
-
# @return [Boolean] true if the final
|
138
|
+
# @return [Boolean] true if the final locations of the stacktrace was a test file
|
107
139
|
def in_test?
|
108
|
-
|
140
|
+
locations.broken_test?
|
109
141
|
end
|
110
142
|
|
111
143
|
# Determines if the issue is an exception that was raised from directly within the project
|
112
144
|
# codebase.
|
113
145
|
#
|
114
|
-
# @return [Boolean] true if the final
|
146
|
+
# @return [Boolean] true if the final locations of the stacktrace was a file from the project
|
115
147
|
# (as opposed to a dependency or Ruby library)
|
116
148
|
def in_source?
|
117
|
-
|
118
|
-
end
|
119
|
-
|
120
|
-
def test_class
|
121
|
-
result.klass
|
122
|
-
end
|
123
|
-
|
124
|
-
def test_identifier
|
125
|
-
result.name
|
126
|
-
end
|
127
|
-
|
128
|
-
def test_name
|
129
|
-
test_identifier.delete_prefix('test_').gsub('_', ' ').capitalize
|
130
|
-
end
|
131
|
-
|
132
|
-
def exception
|
133
|
-
failure.exception
|
149
|
+
locations.proper_failure?
|
134
150
|
end
|
135
151
|
|
136
|
-
|
137
|
-
|
152
|
+
# Was the result a pass? i.e. Skips aren't passes or failures. Slows are still passes. So this
|
153
|
+
# is purely a measure of whether the test explicitly passed all assertions
|
154
|
+
#
|
155
|
+
# @return [Boolean] false for errors, failures, or skips, true for passes (including slows)
|
156
|
+
def passed?
|
157
|
+
passed
|
138
158
|
end
|
139
159
|
|
140
|
-
|
141
|
-
|
160
|
+
# Was there an exception that triggered a failure?
|
161
|
+
#
|
162
|
+
# @return [Boolean] true if there's an exception
|
163
|
+
def error?
|
164
|
+
error
|
142
165
|
end
|
143
166
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
'Broken Test'
|
150
|
-
elsif error? || !passed?
|
151
|
-
failure.result_label
|
152
|
-
elsif painful?
|
153
|
-
'Passed but Very Slow'
|
154
|
-
elsif slow?
|
155
|
-
'Passed but Slow'
|
156
|
-
end
|
167
|
+
# Was the test skipped?
|
168
|
+
#
|
169
|
+
# @return [Boolean] true if the test was explicitly skipped, false otherwise
|
170
|
+
def skipped?
|
171
|
+
skipped
|
157
172
|
end
|
158
173
|
|
174
|
+
# The more nuanced detail of the failure. If it's an error, digs into the exception. Otherwise
|
175
|
+
# uses the message from the result
|
176
|
+
#
|
177
|
+
# @return [String] a more detailed explanation of the issue
|
159
178
|
def summary
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
def freshest_file
|
164
|
-
backtrace.recently_modified.first
|
179
|
+
# When there's an exception, use the first line from the exception message. Otherwise, the
|
180
|
+
# message represents explanation for a test failure, and should be used in full
|
181
|
+
error? ? first_line_of_exception_message : message
|
165
182
|
end
|
166
183
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
184
|
+
# Returns the first line of an exception message when the issue is from a proper exception
|
185
|
+
# failure since exception messages can be long and cumbersome.
|
186
|
+
#
|
187
|
+
# @return [String] the first line of the exception message
|
188
|
+
def first_line_of_exception_message
|
189
|
+
message.split("\n")[0]
|
171
190
|
end
|
172
191
|
end
|
173
192
|
end
|