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