minitest-heat 0.0.12 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +3 -0
- data/.github/workflows/main.yml +23 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile.lock +1 -1
- data/README.md +28 -12
- data/lib/minitest/heat/backtrace.rb +5 -5
- data/lib/minitest/heat/configuration.rb +18 -0
- data/lib/minitest/heat/issue.rb +18 -4
- data/lib/minitest/heat/location.rb +11 -4
- data/lib/minitest/heat/output/backtrace.rb +44 -19
- data/lib/minitest/heat/output/issue.rb +31 -42
- data/lib/minitest/heat/output/map.rb +76 -35
- data/lib/minitest/heat/output.rb +21 -7
- data/lib/minitest/heat/results.rb +14 -2
- data/lib/minitest/heat/version.rb +1 -1
- data/lib/minitest/heat.rb +16 -0
- data/lib/minitest/heat_reporter.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf4115aeb375e2fef72d98e7f706dc023ae1e61fa9d1fc3e8f490797a31e2ae0
|
4
|
+
data.tar.gz: ba968646ff2b12fa2891b47fb58d2d98330d117c2a038e29f1caef449bfbd857
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 67a7e17e10fe0f75f5a5cad93bd97a7f4f87e136e6dfe6f338b0e8df0e80f83533d8e70b07e218fc0431e3e7c5bbdc2d14f71c8c3d32a98d846f276cb573f225
|
7
|
+
data.tar.gz: 2a0cb94a433fe5b69bef58cd4fe24393adaad5bf69e76120b9296a3f8224b2e9a55da10e176bb0fa13e228e3aa57f0836d61fe0ca8116bd973681cc7e458912f
|
data/.github/FUNDING.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
env:
|
6
|
+
CI: true
|
7
|
+
|
8
|
+
jobs:
|
9
|
+
build:
|
10
|
+
strategy:
|
11
|
+
matrix:
|
12
|
+
os: [ubuntu-latest, macos-latest]
|
13
|
+
ruby: [2.5.9, 2.6.9, 2.7.5, 3.0.3, 3.1.0-preview1]
|
14
|
+
runs-on: ubuntu-latest
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v2
|
17
|
+
- name: Set up Ruby
|
18
|
+
uses: ruby/setup-ruby@v1
|
19
|
+
with:
|
20
|
+
ruby-version: ${{ matrix.ruby }}
|
21
|
+
bundler-cache: true
|
22
|
+
- name: Run Tests
|
23
|
+
run: bundle exec rake
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
## [Unreleased]
|
2
|
+
|
3
|
+
Nothing at the moment.
|
4
|
+
|
5
|
+
## [1.1.0] - 2021-12-09
|
6
|
+
|
7
|
+
The biggest update is that the slow thresholds are now configurable.
|
8
|
+
|
9
|
+
- Configurable Thresholds
|
10
|
+
- Fixed a bug where `vendor/bundle` gem files were considered project source code files
|
11
|
+
- Set up [GitHub Actions](https://github.com/garrettdimon/minitest-heat/actions) to ensure tests are run on Ubuntu latest and macOs for the [latest Ruby versions](https://github.com/garrettdimon/minitest-heat/blob/main/.github/workflows/main.yml)
|
12
|
+
- Fixed some tests that were accidentally left path-dependent and couldn't pass on other machines
|
13
|
+
|
14
|
+
## [1.0.0] - 2021-12-01
|
15
|
+
|
16
|
+
Initial release.
|
17
|
+
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,11 @@
|
|
1
|
-
# Minitest::Heat
|
2
|
-
|
1
|
+
# 🔥 Minitest::Heat 🔥
|
2
|
+
Minitest::Heat helps you identify problems faster so you can more efficiently resolve test failures. It does this through a few different methods.
|
3
3
|
|
4
|
-
|
4
|
+
For a more detailed explanation of Minitest Heat with screenshots, [head over the wiki for the full story](https://github.com/garrettdimon/minitest-heat/wiki).
|
5
5
|
|
6
|
-
|
6
|
+
Or for some additional insight about priorities and how it works, this [Twitter thread](https://twitter.com/garrettdimon/status/1432703746526560266) is a good read.
|
7
7
|
|
8
8
|
## Installation
|
9
|
-
|
10
9
|
Add this line to your application's Gemfile:
|
11
10
|
|
12
11
|
```ruby
|
@@ -27,27 +26,44 @@ And depending on your usage, you may need to require Minitest Heat in your test
|
|
27
26
|
require 'minitest/heat'
|
28
27
|
```
|
29
28
|
|
30
|
-
##
|
29
|
+
## Configuration
|
30
|
+
Minitest Heat doesn't currently offer a significant set of configuration options, but the thresholds for "Slow" and "Painfully Slow" tests can be adjusted. By default, it considers anything over 1.0s to be 'slow' and anything over 3.0s to be 'painfully slow'.
|
31
31
|
|
32
|
-
|
32
|
+
You can add a configuration block to your `test_helper.rb` file after the `require 'minitest/heat'` line.
|
33
33
|
|
34
|
-
|
34
|
+
For example:
|
35
35
|
|
36
|
-
|
36
|
+
```ruby
|
37
|
+
Minitest::Heat.configure do |config|
|
38
|
+
config.slow_threshold = 0.01
|
39
|
+
config.painfully_slow_threshold = 0.5
|
40
|
+
end
|
41
|
+
```
|
37
42
|
|
43
|
+
## Development
|
38
44
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
39
45
|
|
40
46
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
41
47
|
|
42
|
-
|
48
|
+
### Forcing Test Failures
|
49
|
+
In order to easily see how Minitest Heat handles different combinations of different types of failures, the following environment variables can be used to force failures.
|
43
50
|
|
44
|
-
|
51
|
+
```bash
|
52
|
+
IMPLODE=true # Every possible type of failure, skip, and slow is generated
|
53
|
+
FORCE_EXCEPTIONS=true # Only exception-triggered failures
|
54
|
+
FORCE_FAILURES=true # Only standard assertion failures
|
55
|
+
FORCE_SKIPS=true # No errors, just the skipped tests
|
56
|
+
FORCE_SLOWS=true # No errors or skipped tests, just slow tests
|
57
|
+
```
|
45
58
|
|
59
|
+
So to see the full context of a test suite, `IMPLODE=true bundle exec rake` will work its magic.
|
60
|
+
|
61
|
+
## Contributing
|
62
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/garrettdimon/minitest-heat. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/minitest-heat/blob/master/CODE_OF_CONDUCT.md).
|
46
63
|
|
47
64
|
## License
|
48
65
|
|
49
66
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
50
67
|
|
51
68
|
## Code of Conduct
|
52
|
-
|
53
69
|
Everyone interacting in the Minitest::Heat project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/minitest-heat/blob/master/CODE_OF_CONDUCT.md).
|
@@ -26,7 +26,7 @@ module Minitest
|
|
26
26
|
|
27
27
|
# All lines of the backtrace converted to Backtrace::LineParser's
|
28
28
|
#
|
29
|
-
# @return [
|
29
|
+
# @return [Array<Location>] the full set of backtrace lines parsed as Location instances
|
30
30
|
def locations
|
31
31
|
return [] if raw_backtrace.nil?
|
32
32
|
|
@@ -36,28 +36,28 @@ module Minitest
|
|
36
36
|
# All entries from the backtrace within the project and sorted with the most recently modified
|
37
37
|
# files at the beginning
|
38
38
|
#
|
39
|
-
# @return [
|
39
|
+
# @return [Array<Location>] the sorted backtrace lines from the project
|
40
40
|
def recently_modified_locations
|
41
41
|
@recently_modified_locations ||= project_locations.sort_by(&:mtime).reverse
|
42
42
|
end
|
43
43
|
|
44
44
|
# All entries from the backtrace that are files within the project
|
45
45
|
#
|
46
|
-
# @return [
|
46
|
+
# @return [Array<Location>] the backtrace lines from within the project
|
47
47
|
def project_locations
|
48
48
|
@project_locations ||= locations.select(&:project_file?)
|
49
49
|
end
|
50
50
|
|
51
51
|
# All entries from the backtrace within the project tests
|
52
52
|
#
|
53
|
-
# @return [
|
53
|
+
# @return [Array<Location>] the backtrace lines from within the tests
|
54
54
|
def test_locations
|
55
55
|
@test_locations ||= project_locations.select(&:test_file?)
|
56
56
|
end
|
57
57
|
|
58
58
|
# All source code entries from the backtrace (i.e. excluding tests)
|
59
59
|
#
|
60
|
-
# @return [
|
60
|
+
# @return [Array<Location>] the backtrace lines from within the source code
|
61
61
|
def source_code_locations
|
62
62
|
@source_code_locations ||= project_locations.select(&:source_code_file?)
|
63
63
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Minitest
|
6
|
+
module Heat
|
7
|
+
# For managing configuration options on how Minitest Heat should handle results
|
8
|
+
class Configuration
|
9
|
+
attr_accessor :slow_threshold,
|
10
|
+
:painfully_slow_threshold
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@slow_threshold = 1.0
|
14
|
+
@painfully_slow_threshold = 3.0
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/minitest/heat/issue.rb
CHANGED
@@ -116,20 +116,34 @@ module Minitest
|
|
116
116
|
!passed? || slow? || painful?
|
117
117
|
end
|
118
118
|
|
119
|
+
# The number, in seconds, for a test to be considered "slow"
|
120
|
+
#
|
121
|
+
# @return [Float] number of seconds after which a test is considered slow
|
122
|
+
def slow_threshold
|
123
|
+
Minitest::Heat.configuration.slow_threshold || SLOW_THRESHOLDS[:slow]
|
124
|
+
end
|
125
|
+
|
126
|
+
# The number, in seconds, for a test to be considered "painfully slow"
|
127
|
+
#
|
128
|
+
# @return [Float] number of seconds after which a test is considered painfully slow
|
129
|
+
def painfully_slow_threshold
|
130
|
+
Minitest::Heat.configuration.painfully_slow_threshold || SLOW_THRESHOLDS[:painful]
|
131
|
+
end
|
132
|
+
|
119
133
|
# Determines if a test should be considered slow by comparing it to the low end definition of
|
120
134
|
# what is considered slow.
|
121
135
|
#
|
122
|
-
# @return [Boolean] true if the test took longer to run than `
|
136
|
+
# @return [Boolean] true if the test took longer to run than `slow_threshold`
|
123
137
|
def slow?
|
124
|
-
execution_time >=
|
138
|
+
execution_time >= slow_threshold && execution_time < painfully_slow_threshold
|
125
139
|
end
|
126
140
|
|
127
141
|
# Determines if a test should be considered painfully slow by comparing it to the high end
|
128
142
|
# definition of what is considered slow.
|
129
143
|
#
|
130
|
-
# @return [Boolean] true if the test took longer to run than `
|
144
|
+
# @return [Boolean] true if the test took longer to run than `painfully_slow_threshold`
|
131
145
|
def painful?
|
132
|
-
execution_time >=
|
146
|
+
execution_time >= painfully_slow_threshold
|
133
147
|
end
|
134
148
|
|
135
149
|
# Determines if the issue is an exception that was raised from directly within a test
|
@@ -131,9 +131,16 @@ module Minitest
|
|
131
131
|
|
132
132
|
# Determines if a given file is from the project directory
|
133
133
|
#
|
134
|
-
# @return [Boolean] true if the file is in the project (source code or test)
|
134
|
+
# @return [Boolean] true if the file is in the project (source code or test) but not vendored
|
135
135
|
def project_file?
|
136
|
-
path.include?(project_root_dir)
|
136
|
+
path.include?(project_root_dir) && !bundled_file?
|
137
|
+
end
|
138
|
+
|
139
|
+
# Determines if the file is in the project `vendor/bundle` directory.
|
140
|
+
#
|
141
|
+
# @return [Boolean] true if the file is in `<project_root>/vendor/bundle
|
142
|
+
def bundled_file?
|
143
|
+
path.include?("#{project_root_dir}/vendor/bundle")
|
137
144
|
end
|
138
145
|
|
139
146
|
# Determines if a given file follows the standard approaching to naming test files.
|
@@ -145,9 +152,9 @@ module Minitest
|
|
145
152
|
|
146
153
|
# Determines if a given file is a non-test file from the project directory
|
147
154
|
#
|
148
|
-
# @return [Boolean] true if the file is in the project but not a test file
|
155
|
+
# @return [Boolean] true if the file is in the project but not a test file or vendored file
|
149
156
|
def source_code_file?
|
150
|
-
project_file? && !test_file?
|
157
|
+
project_file? && !test_file? && !bundled_file?
|
151
158
|
end
|
152
159
|
|
153
160
|
# A safe interface to getting the last modified time for the file in question
|
@@ -3,7 +3,7 @@
|
|
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
|
@@ -17,31 +17,47 @@ module Minitest
|
|
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
|
-
backtrace_locations.
|
25
|
-
@tokens << backtrace_location_tokens(location)
|
26
|
-
end
|
27
|
-
|
28
|
-
@tokens
|
21
|
+
@tokens = backtrace_locations.map { |location| backtrace_location_tokens(location) }
|
29
22
|
end
|
30
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
|
31
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.
|
32
35
|
DEFAULT_LINE_COUNT
|
33
36
|
end
|
34
37
|
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
# ...it behaves a little different when it's a broken test vs. a true exception?
|
40
|
-
# ...it could be smart about subtly flagging the lines that show up in the heat map frequently?
|
41
|
-
# ...it could be influenced by a "compact" or "robust" reporter super-style?
|
42
|
-
# ...it's smart about exceptions that were raised outside of the project?
|
43
|
-
# ...it's smart about highlighting lines of code differently based on whether it's source code, test code, or external code?
|
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
|
44
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.
|
45
61
|
backtrace.locations.take(line_count)
|
46
62
|
end
|
47
63
|
|
@@ -65,8 +81,17 @@ module Minitest
|
|
65
81
|
backtrace_locations.all?(&:project_file?)
|
66
82
|
end
|
67
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
|
+
#
|
68
91
|
def most_recently_modified?(location)
|
69
|
-
# If there's more than one line being displayed,
|
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
|
70
95
|
backtrace_locations.size > 1 && location == locations.freshest
|
71
96
|
end
|
72
97
|
|
@@ -14,28 +14,16 @@ module Minitest
|
|
14
14
|
|
15
15
|
def tokens
|
16
16
|
case issue.type
|
17
|
-
when :error then
|
18
|
-
when :
|
19
|
-
when :
|
20
|
-
when :
|
21
|
-
when :painful then painful_tokens
|
22
|
-
when :slow then slow_tokens
|
17
|
+
when :error, :broken then exception_tokens
|
18
|
+
when :failure then failure_tokens
|
19
|
+
when :skipped then skipped_tokens
|
20
|
+
when :painful, :slow then slow_tokens
|
23
21
|
end
|
24
22
|
end
|
25
23
|
|
26
24
|
private
|
27
25
|
|
28
|
-
def
|
29
|
-
[
|
30
|
-
headline_tokens,
|
31
|
-
test_location_tokens,
|
32
|
-
summary_tokens,
|
33
|
-
*backtrace_tokens,
|
34
|
-
newline_tokens
|
35
|
-
]
|
36
|
-
end
|
37
|
-
|
38
|
-
def broken_tokens
|
26
|
+
def exception_tokens
|
39
27
|
[
|
40
28
|
headline_tokens,
|
41
29
|
test_location_tokens,
|
@@ -62,14 +50,6 @@ module Minitest
|
|
62
50
|
]
|
63
51
|
end
|
64
52
|
|
65
|
-
def painful_tokens
|
66
|
-
[
|
67
|
-
headline_tokens,
|
68
|
-
slowness_summary_tokens,
|
69
|
-
newline_tokens
|
70
|
-
]
|
71
|
-
end
|
72
|
-
|
73
53
|
def slow_tokens
|
74
54
|
[
|
75
55
|
headline_tokens,
|
@@ -82,6 +62,11 @@ module Minitest
|
|
82
62
|
[label_token(issue), spacer_token, [:default, test_name(issue)]]
|
83
63
|
end
|
84
64
|
|
65
|
+
# Creates a display-friendly version of the test name with underscores removed and the
|
66
|
+
# first letter capitalized regardless of the formatt used for the test definition
|
67
|
+
# @param issue [Issue] the issue to use to generate the test name
|
68
|
+
#
|
69
|
+
# @return [String] the cleaned up version of the test name
|
85
70
|
def test_name(issue)
|
86
71
|
test_prefix = 'test_'
|
87
72
|
identifier = issue.test_identifier
|
@@ -97,27 +82,10 @@ module Minitest
|
|
97
82
|
[issue.type, issue_label(issue.type)]
|
98
83
|
end
|
99
84
|
|
100
|
-
def issue_label(issue_type)
|
101
|
-
case issue_type
|
102
|
-
when :error then 'Error'
|
103
|
-
when :broken then 'Broken Test'
|
104
|
-
when :failure then 'Failure'
|
105
|
-
when :skipped then 'Skipped'
|
106
|
-
when :slow then 'Passed but Slow'
|
107
|
-
when :painful then 'Passed but Very Slow'
|
108
|
-
when :passed then 'Success'
|
109
|
-
else 'Unknown'
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
85
|
def test_name_and_class_tokens
|
114
86
|
[[:default, issue.test_class], *test_location_tokens]
|
115
87
|
end
|
116
88
|
|
117
|
-
def backtrace_tokens
|
118
|
-
@backtrace_tokens ||= ::Minitest::Heat::Output::Backtrace.new(locations).tokens
|
119
|
-
end
|
120
|
-
|
121
89
|
def test_location_tokens
|
122
90
|
[
|
123
91
|
[:default, locations.test_definition.relative_filename],
|
@@ -176,6 +144,27 @@ module Minitest
|
|
176
144
|
def arrow_token
|
177
145
|
Output::TOKENS[:muted_arrow]
|
178
146
|
end
|
147
|
+
|
148
|
+
def backtrace_tokens
|
149
|
+
@backtrace_tokens ||= ::Minitest::Heat::Output::Backtrace.new(locations).tokens
|
150
|
+
end
|
151
|
+
|
152
|
+
# The string to use to describe the failure type when displaying results/
|
153
|
+
# @param issue_type [Symbol] the symbol representing the issue's failure type
|
154
|
+
#
|
155
|
+
# @return [String] the display-friendly string describing the failure reason
|
156
|
+
def issue_label(issue_type)
|
157
|
+
case issue_type
|
158
|
+
when :error then 'Error'
|
159
|
+
when :broken then 'Broken Test'
|
160
|
+
when :failure then 'Failure'
|
161
|
+
when :skipped then 'Skipped'
|
162
|
+
when :slow then 'Passed but Slow'
|
163
|
+
when :painful then 'Passed but Very Slow'
|
164
|
+
when :passed then 'Success'
|
165
|
+
else 'Unknown'
|
166
|
+
end
|
167
|
+
end
|
179
168
|
end
|
180
169
|
end
|
181
170
|
end
|
@@ -14,24 +14,34 @@ module Minitest
|
|
14
14
|
|
15
15
|
def tokens
|
16
16
|
results.heat_map.file_hits.each do |hit|
|
17
|
-
#
|
17
|
+
# Focus on the relevant issues based on most significant problems. i.e. If there are
|
18
|
+
# legitimate failures or errors, skips and slows aren't relevant
|
18
19
|
next unless relevant_issue_types?(hit)
|
19
20
|
|
21
|
+
# Add a new line
|
20
22
|
@tokens << [[:muted, ""]]
|
23
|
+
|
24
|
+
# Build the summary line for the file
|
21
25
|
@tokens << file_summary_tokens(hit)
|
22
26
|
|
23
|
-
|
24
|
-
|
27
|
+
# Get the set of line numbers that appear more than once
|
28
|
+
repeated_line_numbers = find_repeated_line_numbers_in(hit)
|
25
29
|
|
26
|
-
|
27
|
-
|
30
|
+
# Only display more details if the same line number shows up more than once
|
31
|
+
next unless repeated_line_numbers.any?
|
28
32
|
|
33
|
+
repeated_line_numbers.each do |line_number|
|
34
|
+
# Get the backtraces for the given line numbers
|
29
35
|
traces = hit.lines[line_number.to_s]
|
30
|
-
sorted_traces = traces.sort_by { |trace| trace.locations.last.line_number }
|
31
36
|
|
32
|
-
|
33
|
-
|
34
|
-
|
37
|
+
# If there aren't any traces there's no way to provide additional details
|
38
|
+
break unless traces.any?
|
39
|
+
|
40
|
+
# A short summary explaining the details that will follow
|
41
|
+
@tokens << [[:default, " Line #{line_number}"], [:muted, ' issues triggered from:']]
|
42
|
+
|
43
|
+
# The last relevant location for each error's backtrace
|
44
|
+
@tokens += origination_sources(traces)
|
35
45
|
end
|
36
46
|
end
|
37
47
|
|
@@ -40,16 +50,28 @@ module Minitest
|
|
40
50
|
|
41
51
|
private
|
42
52
|
|
53
|
+
def origination_sources(traces)
|
54
|
+
# 1. Only pull the traces that have proper locations
|
55
|
+
# 2. Sort the traces by the most recent line number so they're displayed in numeric order
|
56
|
+
# 3. Get the final relevant location from the trace
|
57
|
+
traces.
|
58
|
+
select { |trace| trace.locations.any? }.
|
59
|
+
sort_by { |trace| trace.locations.last.line_number }.
|
60
|
+
map { |trace| origination_location_token(trace) }
|
61
|
+
end
|
62
|
+
|
43
63
|
def file_summary_tokens(hit)
|
44
64
|
pathname_tokens = pathname(hit)
|
45
|
-
line_number_list_tokens =
|
65
|
+
line_number_list_tokens = line_number_tokens_for_hit(hit)
|
46
66
|
|
47
67
|
[*pathname_tokens, *line_number_list_tokens]
|
48
68
|
end
|
49
69
|
|
50
70
|
def origination_location_token(trace)
|
51
71
|
# The earliest project line from the backtrace—this is probabyl wholly incorrect in terms
|
52
|
-
# of what would be the most helpful line to display, but it's a start.
|
72
|
+
# of what would be the most helpful line to display, but it's a start. Otherwise, the
|
73
|
+
# logic will need to compare all traces for the issue and find the unique origination
|
74
|
+
# lines
|
53
75
|
location = trace.locations.last
|
54
76
|
|
55
77
|
[
|
@@ -75,16 +97,18 @@ module Minitest
|
|
75
97
|
end
|
76
98
|
|
77
99
|
def relevant_issue_types?(hit)
|
100
|
+
# The intersection of which issue types are relevant based on the context and which issues
|
101
|
+
# matc those issue types
|
78
102
|
intersection_issue_types = relevant_issue_types & hit.issues.keys
|
79
103
|
|
80
104
|
intersection_issue_types.any?
|
81
105
|
end
|
82
106
|
|
83
|
-
def
|
107
|
+
def find_repeated_line_numbers_in(hit)
|
84
108
|
repeated_line_numbers = []
|
85
109
|
|
86
110
|
hit.lines.each_pair do |line_number, traces|
|
87
|
-
# If there aren't multiple traces for a line number, it's not a repeat
|
111
|
+
# If there aren't multiple traces for a line number, it's not a repeat
|
88
112
|
next unless traces.size > 1
|
89
113
|
|
90
114
|
repeated_line_numbers << Integer(line_number)
|
@@ -93,10 +117,6 @@ module Minitest
|
|
93
117
|
repeated_line_numbers.sort
|
94
118
|
end
|
95
119
|
|
96
|
-
def repeated_line_numbers?(hit)
|
97
|
-
repeated_line_numbers(hit).any?
|
98
|
-
end
|
99
|
-
|
100
120
|
def pathname(hit)
|
101
121
|
directory = hit.pathname.dirname.to_s.delete_prefix("#{Dir.pwd}/")
|
102
122
|
filename = hit.pathname.basename.to_s
|
@@ -108,6 +128,11 @@ module Minitest
|
|
108
128
|
]
|
109
129
|
end
|
110
130
|
|
131
|
+
# Gets the list of line numbers for a given hit location (i.e. file) so they can be
|
132
|
+
# displayed after the file name to show which lines were problematic
|
133
|
+
# @param hit [Hit] the instance to extract line numbers from
|
134
|
+
#
|
135
|
+
# @return [Array<Symbol,String>] [description]
|
111
136
|
def line_number_tokens_for_hit(hit)
|
112
137
|
line_number_tokens = []
|
113
138
|
|
@@ -116,34 +141,50 @@ module Minitest
|
|
116
141
|
line_numbers_for_issue_type = hit.issues.fetch(issue_type) { [] }
|
117
142
|
|
118
143
|
# Build the list of tokens representing styled line numbers
|
119
|
-
line_numbers_for_issue_type.each do |line_number|
|
120
|
-
|
144
|
+
line_numbers_for_issue_type.uniq.sort.each do |line_number|
|
145
|
+
frequency = line_numbers_for_issue_type.count(line_number)
|
146
|
+
|
147
|
+
line_number_tokens += line_number_token(issue_type, line_number, frequency)
|
121
148
|
end
|
122
149
|
end
|
123
150
|
|
124
151
|
line_number_tokens.compact
|
125
152
|
end
|
126
153
|
|
127
|
-
|
128
|
-
[style, "#{line_number} "]
|
129
|
-
end
|
130
|
-
|
131
|
-
# Generates the line number tokens styled based on their error type
|
154
|
+
# Builds a token representing a styled line number
|
132
155
|
#
|
133
|
-
# @param [
|
156
|
+
# @param style [Symbol] the relevant display style for the issue
|
157
|
+
# @param line_number [Integer] the affected line number
|
134
158
|
#
|
135
|
-
# @return [Array]
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
first_line_number <=> second_line_number
|
159
|
+
# @return [Array<Symbol,Integer>] array token representing the line number and issue type
|
160
|
+
def line_number_token(style, line_number, frequency)
|
161
|
+
if frequency > 1
|
162
|
+
[
|
163
|
+
[style, "#{line_number}"],
|
164
|
+
[:muted, "✕#{frequency} "]
|
165
|
+
]
|
166
|
+
else
|
167
|
+
[[style, "#{line_number} "]]
|
145
168
|
end
|
146
169
|
end
|
170
|
+
|
171
|
+
# # Sorts line number tokens so that line numbers are displayed in order regardless of their
|
172
|
+
# # underlying issue type
|
173
|
+
# #
|
174
|
+
# # @param hit [Hit] the instance of the hit file details to build the heat map entry
|
175
|
+
# #
|
176
|
+
# # @return [Array] the arrays representing the line number tokens to display next to a file
|
177
|
+
# # name in the heat map. ex [[:error, 12], [:falure, 13]]
|
178
|
+
# def sorted_line_number_list(hit)
|
179
|
+
# # Sort the collected group of line number hits so they're in order
|
180
|
+
# line_number_tokens_for_hit(hit).sort do |a, b|
|
181
|
+
# # Ensure the line numbers are integers for sorting (otherwise '100' comes before '12')
|
182
|
+
# first_line_number = Integer(a[1].strip)
|
183
|
+
# second_line_number = Integer(b[1].strip)
|
184
|
+
|
185
|
+
# first_line_number <=> second_line_number
|
186
|
+
# end
|
187
|
+
# end
|
147
188
|
end
|
148
189
|
end
|
149
190
|
end
|
data/lib/minitest/heat/output.rb
CHANGED
@@ -48,13 +48,20 @@ module Minitest
|
|
48
48
|
newline
|
49
49
|
|
50
50
|
# Issues start with the least critical and go up to the most critical so that the most
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
51
|
+
# pressing issues are displayed at the bottom of the report in order to reduce scrolling.
|
52
|
+
#
|
53
|
+
# This way, as you fix issues, the list gets shorter, and eventually the least critical
|
54
|
+
# issues will be displayed without scrolling once more problematic issues are resolved.
|
54
55
|
%i[slows painfuls skips failures brokens errors].each do |issue_category|
|
56
|
+
# Only show categories for the most pressing issues after the suite runs, otherwise,
|
57
|
+
# suppress them until the more critical issues are resolved.
|
55
58
|
next unless show?(issue_category, results)
|
56
59
|
|
57
|
-
results.send(issue_category)
|
60
|
+
issues = results.send(issue_category)
|
61
|
+
|
62
|
+
issues
|
63
|
+
.sort_by { |issue| issue.locations.most_relevant.to_a }
|
64
|
+
.each { |issue| issue_details(issue) }
|
58
65
|
end
|
59
66
|
rescue StandardError => e
|
60
67
|
message = "Sorry, but Minitest Heat couldn't display the details of any failures."
|
@@ -64,7 +71,7 @@ module Minitest
|
|
64
71
|
def issue_details(issue)
|
65
72
|
print_tokens Minitest::Heat::Output::Issue.new(issue).tokens
|
66
73
|
rescue StandardError => e
|
67
|
-
message = "Sorry, but Minitest Heat couldn't display output for a failure."
|
74
|
+
message = "Sorry, but Minitest Heat couldn't display output for a specific failure."
|
68
75
|
exception_guidance(message, e)
|
69
76
|
end
|
70
77
|
|
@@ -89,6 +96,15 @@ module Minitest
|
|
89
96
|
exception_guidance(message, e)
|
90
97
|
end
|
91
98
|
|
99
|
+
private
|
100
|
+
|
101
|
+
# Displays some guidance related to exceptions generated by Minitest Heat in order to help
|
102
|
+
# people get back on track (and ideally submit issues)
|
103
|
+
# @param message [String] a slightly more specific explanation of which part of minitest-heat
|
104
|
+
# caused the failure
|
105
|
+
# @param exception [Exception] the exception that caused the problem
|
106
|
+
#
|
107
|
+
# @return [void] displays the guidance to the console
|
92
108
|
def exception_guidance(message, exception)
|
93
109
|
newline
|
94
110
|
puts "#{message} Disabling Minitest Heat can get you back on track until the problem can be fixed."
|
@@ -100,8 +116,6 @@ module Minitest
|
|
100
116
|
newline
|
101
117
|
end
|
102
118
|
|
103
|
-
private
|
104
|
-
|
105
119
|
def no_problems?(results)
|
106
120
|
!results.problems?
|
107
121
|
end
|
@@ -30,8 +30,20 @@ module Minitest
|
|
30
30
|
# For heat map purposes, only the project backtrace lines are interesting
|
31
31
|
pathname, line_number = issue.locations.project.to_a
|
32
32
|
|
33
|
-
#
|
34
|
-
|
33
|
+
# A backtrace is only relevant for exception-generating issues (i.e. errors), not slows or skips
|
34
|
+
# However, while assertion failures won't have a backtrace, there can still be repeated line
|
35
|
+
# numbers if the tests reference a shared method with an assertion in it. So in those cases,
|
36
|
+
# the backtrace is simply the test definition
|
37
|
+
backtrace = if issue.error?
|
38
|
+
# With errors, we have a backtrace
|
39
|
+
issue.locations.backtrace.project_locations
|
40
|
+
else
|
41
|
+
# With failures, the test definition is the most granular backtrace equivalent
|
42
|
+
location = issue.locations.test_definition
|
43
|
+
location.raw_container = issue.test_identifier
|
44
|
+
|
45
|
+
[location]
|
46
|
+
end
|
35
47
|
|
36
48
|
@heat_map.add(pathname, line_number, issue.type, backtrace: backtrace)
|
37
49
|
end
|
data/lib/minitest/heat.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'heat/configuration'
|
3
4
|
require_relative 'heat/backtrace'
|
4
5
|
require_relative 'heat/hit'
|
5
6
|
require_relative 'heat/issue'
|
@@ -32,5 +33,20 @@ module Minitest
|
|
32
33
|
# Pulls from minitest-color as well:
|
33
34
|
# https://github.com/teoljungberg/minitest-color/blob/master/lib/minitest/color_plugin.rb
|
34
35
|
module Heat
|
36
|
+
class << self
|
37
|
+
attr_writer :configuration
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.configuration
|
41
|
+
@configuration ||= Configuration.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.reset
|
45
|
+
@configuration = Configuration.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.configure
|
49
|
+
yield(configuration)
|
50
|
+
end
|
35
51
|
end
|
36
52
|
end
|
@@ -96,7 +96,7 @@ module Minitest
|
|
96
96
|
# The list of individual issues and their associated details
|
97
97
|
output.issues_list(results)
|
98
98
|
|
99
|
-
# Display a short summary of the total issue counts
|
99
|
+
# Display a short summary of the total issue counts for each category as well as performance
|
100
100
|
# details for the test suite as a whole
|
101
101
|
output.compact_summary(results, timer)
|
102
102
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minitest-heat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Garrett Dimon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -130,9 +130,12 @@ executables: []
|
|
130
130
|
extensions: []
|
131
131
|
extra_rdoc_files: []
|
132
132
|
files:
|
133
|
+
- ".github/FUNDING.yml"
|
134
|
+
- ".github/workflows/main.yml"
|
133
135
|
- ".gitignore"
|
134
136
|
- ".rubocop.yml"
|
135
137
|
- ".travis.yml"
|
138
|
+
- CHANGELOG.md
|
136
139
|
- CODE_OF_CONDUCT.md
|
137
140
|
- Gemfile
|
138
141
|
- Gemfile.lock
|
@@ -144,6 +147,7 @@ files:
|
|
144
147
|
- lib/minitest/heat.rb
|
145
148
|
- lib/minitest/heat/backtrace.rb
|
146
149
|
- lib/minitest/heat/backtrace/line_parser.rb
|
150
|
+
- lib/minitest/heat/configuration.rb
|
147
151
|
- lib/minitest/heat/hit.rb
|
148
152
|
- lib/minitest/heat/issue.rb
|
149
153
|
- lib/minitest/heat/location.rb
|