minitest-heat 1.1.0 → 1.3.0
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/.github/workflows/main.yml +79 -8
- data/.github/workflows/release.yml +98 -0
- data/.rubocop.yml +7 -2
- data/CHANGELOG.md +35 -1
- data/Gemfile +5 -1
- data/Gemfile.lock +77 -37
- data/README.md +91 -9
- data/RELEASING.md +191 -0
- data/Rakefile +117 -0
- data/lib/minitest/heat/backtrace/line_count.rb +38 -0
- data/lib/minitest/heat/backtrace/line_parser.rb +4 -2
- data/lib/minitest/heat/backtrace.rb +2 -1
- data/lib/minitest/heat/configuration.rb +7 -2
- data/lib/minitest/heat/hit.rb +30 -14
- data/lib/minitest/heat/issue.rb +43 -9
- data/lib/minitest/heat/location.rb +24 -2
- data/lib/minitest/heat/map.rb +7 -0
- data/lib/minitest/heat/output/backtrace.rb +5 -5
- data/lib/minitest/heat/output/issue.rb +12 -5
- data/lib/minitest/heat/output/results.rb +6 -1
- data/lib/minitest/heat/output/token.rb +2 -2
- data/lib/minitest/heat/output.rb +3 -2
- data/lib/minitest/heat/results.rb +41 -8
- data/lib/minitest/heat/source.rb +4 -3
- data/lib/minitest/heat/timer.rb +13 -0
- data/lib/minitest/heat/version.rb +1 -1
- data/lib/minitest/heat_plugin.rb +6 -0
- data/lib/minitest/heat_reporter.rb +44 -8
- data/minitest-heat.gemspec +4 -3
- metadata +14 -14
- data/.travis.yml +0 -6
data/RELEASING.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Releasing Minitest Heat
|
|
2
|
+
|
|
3
|
+
## Quick Reference
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# 1. Update version and changelog
|
|
7
|
+
# 2. Commit and push
|
|
8
|
+
git add -A && git commit -m "Release vX.Y.Z" && git push
|
|
9
|
+
|
|
10
|
+
# 3. Tag and push
|
|
11
|
+
git tag vX.Y.Z && git push origin vX.Y.Z
|
|
12
|
+
|
|
13
|
+
# Done - automation handles the rest
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## How It Works
|
|
17
|
+
|
|
18
|
+
Releases are fully automated via GitHub Actions:
|
|
19
|
+
|
|
20
|
+
1. **Branch protection** requires CI to pass before merging to `main`
|
|
21
|
+
2. When you push a version tag, the release workflow:
|
|
22
|
+
- Validates the tag points to a commit on `main` (ensures CI passed)
|
|
23
|
+
- Builds and publishes the gem to RubyGems
|
|
24
|
+
- Creates a GitHub Release with changelog excerpt
|
|
25
|
+
|
|
26
|
+
No redundant test runs. If it's on `main`, it already passed CI.
|
|
27
|
+
|
|
28
|
+
## Release Steps
|
|
29
|
+
|
|
30
|
+
### 1. Update Version
|
|
31
|
+
|
|
32
|
+
Edit `lib/minitest/heat/version.rb`:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
VERSION = 'X.Y.Z'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Update CHANGELOG
|
|
39
|
+
|
|
40
|
+
Move items from `[Unreleased]` to a new version section:
|
|
41
|
+
|
|
42
|
+
```markdown
|
|
43
|
+
## [Unreleased]
|
|
44
|
+
|
|
45
|
+
## [X.Y.Z] - YYYY-MM-DD
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
- New feature description
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
- Bug fix description
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Commit, Tag, Push
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git add lib/minitest/heat/version.rb CHANGELOG.md
|
|
58
|
+
git commit -m "Release vX.Y.Z"
|
|
59
|
+
git push origin main
|
|
60
|
+
git tag vX.Y.Z
|
|
61
|
+
git push origin vX.Y.Z
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 4. Verify
|
|
65
|
+
|
|
66
|
+
- Watch the [Actions tab](https://github.com/garrettdimon/minitest-heat/actions) for workflow completion
|
|
67
|
+
- Check [RubyGems](https://rubygems.org/gems/minitest-heat) for the new version
|
|
68
|
+
- Check [GitHub Releases](https://github.com/garrettdimon/minitest-heat/releases) for the release page
|
|
69
|
+
|
|
70
|
+
## Versioning Policy
|
|
71
|
+
|
|
72
|
+
Follow [Semantic Versioning](https://semver.org/):
|
|
73
|
+
|
|
74
|
+
- **MAJOR** (x.0.0): Breaking changes to public API or configuration
|
|
75
|
+
- **MINOR** (0.x.0): New features, deprecations
|
|
76
|
+
- **PATCH** (0.0.x): Bug fixes, documentation
|
|
77
|
+
|
|
78
|
+
### What's a Breaking Change?
|
|
79
|
+
|
|
80
|
+
- Removing or renaming public classes/methods
|
|
81
|
+
- Changing method signatures incompatibly
|
|
82
|
+
- Changing default configuration behavior
|
|
83
|
+
- Dropping Ruby version support
|
|
84
|
+
|
|
85
|
+
## Local Tools
|
|
86
|
+
|
|
87
|
+
### Full Preflight Check
|
|
88
|
+
|
|
89
|
+
Run all checks before pushing:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
bundle exec rake release:preflight
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This runs tests, security audit, and release validation in sequence.
|
|
96
|
+
|
|
97
|
+
### Individual Tasks
|
|
98
|
+
|
|
99
|
+
| Task | Purpose |
|
|
100
|
+
|------|---------|
|
|
101
|
+
| `rake release:preflight` | Run all checks (test, audit, check) |
|
|
102
|
+
| `rake release:check` | Validate version format, changelog entry, git state |
|
|
103
|
+
| `rake release:audit` | Check for vulnerable dependencies |
|
|
104
|
+
| `rake release:dry_run` | Build gem locally, show contents and size |
|
|
105
|
+
| `rake test` | Run test suite |
|
|
106
|
+
|
|
107
|
+
### Preview a Release
|
|
108
|
+
|
|
109
|
+
Before tagging, preview what the gem will contain:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
bundle exec rake release:dry_run
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This builds the gem, displays its contents and size, then cleans up.
|
|
116
|
+
|
|
117
|
+
## One-Time Setup
|
|
118
|
+
|
|
119
|
+
### RubyGems Trusted Publishing
|
|
120
|
+
|
|
121
|
+
1. Go to [rubygems.org/profile/oidc/pending_trusted_publishers](https://rubygems.org/profile/oidc/pending_trusted_publishers)
|
|
122
|
+
2. Add trusted publisher:
|
|
123
|
+
- **Gem name:** `minitest-heat`
|
|
124
|
+
- **Repository owner:** `garrettdimon`
|
|
125
|
+
- **Repository name:** `minitest-heat`
|
|
126
|
+
- **Workflow filename:** `release.yml`
|
|
127
|
+
- **Environment:** `rubygems`
|
|
128
|
+
|
|
129
|
+
### GitHub Environment
|
|
130
|
+
|
|
131
|
+
1. Go to repository Settings > Environments
|
|
132
|
+
2. Create environment named `rubygems`
|
|
133
|
+
3. (Optional) Add required reviewers for extra safety
|
|
134
|
+
|
|
135
|
+
### Repository Ruleset
|
|
136
|
+
|
|
137
|
+
1. Go to repository Settings > Rules > Rulesets
|
|
138
|
+
2. Edit the `main` ruleset (or create one targeting the default branch)
|
|
139
|
+
3. Enable "Require status checks to pass" with these checks:
|
|
140
|
+
- `Security`
|
|
141
|
+
- `Test (Ruby 3.1)`
|
|
142
|
+
- `Test (Ruby 3.2)`
|
|
143
|
+
- `Test (Ruby 3.3)`
|
|
144
|
+
- `Test (Ruby 3.4)`
|
|
145
|
+
- `Test (Ruby 4.0)`
|
|
146
|
+
- `Changelog`
|
|
147
|
+
- `Version`
|
|
148
|
+
|
|
149
|
+
## Troubleshooting
|
|
150
|
+
|
|
151
|
+
### Release workflow fails with "must point to commit on main"
|
|
152
|
+
|
|
153
|
+
You tagged a commit that isn't on the main branch. Delete the tag and re-tag a commit on main:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
git tag -d vX.Y.Z # Delete local tag
|
|
157
|
+
git push origin :vX.Y.Z # Delete remote tag
|
|
158
|
+
git checkout main
|
|
159
|
+
git pull
|
|
160
|
+
git tag vX.Y.Z
|
|
161
|
+
git push origin vX.Y.Z
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### "You do not have permission to push to this gem"
|
|
165
|
+
|
|
166
|
+
The RubyGems trusted publisher isn't configured, or the environment name doesn't match. Check the one-time setup steps above.
|
|
167
|
+
|
|
168
|
+
### Forgot to update CHANGELOG
|
|
169
|
+
|
|
170
|
+
Delete the tag, update CHANGELOG, amend the commit, re-tag:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
git tag -d vX.Y.Z
|
|
174
|
+
git push origin :vX.Y.Z
|
|
175
|
+
# Update CHANGELOG.md
|
|
176
|
+
git add CHANGELOG.md
|
|
177
|
+
git commit --amend --no-edit
|
|
178
|
+
git push --force-with-lease origin main
|
|
179
|
+
git tag vX.Y.Z
|
|
180
|
+
git push origin vX.Y.Z
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Manual Release (Fallback)
|
|
184
|
+
|
|
185
|
+
If automation fails and you need to publish manually:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
bundle exec rake release
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
This builds the gem and pushes to RubyGems using your local credentials.
|
data/Rakefile
CHANGED
|
@@ -10,3 +10,120 @@ Rake::TestTask.new(:test) do |t|
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
task default: :test
|
|
13
|
+
|
|
14
|
+
# rubocop:disable Metrics/BlockLength
|
|
15
|
+
namespace :release do
|
|
16
|
+
desc 'Run bundle-audit to check for vulnerable dependencies'
|
|
17
|
+
task :audit do
|
|
18
|
+
puts 'Running security audit...'
|
|
19
|
+
sh 'bundle exec bundle-audit check --update'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
desc 'Validate version, changelog, and git state before release'
|
|
23
|
+
task :check do
|
|
24
|
+
require_relative 'lib/minitest/heat/version'
|
|
25
|
+
errors = ReleaseChecker.new(Minitest::Heat::VERSION).validate
|
|
26
|
+
if errors.any?
|
|
27
|
+
puts "\nRelease check failed:"
|
|
28
|
+
errors.each { |e| puts " - #{e}" }
|
|
29
|
+
exit 1
|
|
30
|
+
else
|
|
31
|
+
puts 'All release checks passed.'
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
desc 'Run all pre-release checks (tests, audit, release:check)'
|
|
36
|
+
task preflight: %i[test audit check] do
|
|
37
|
+
puts "\nAll preflight checks passed. Ready to release."
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
desc 'Build gem locally and show contents (dry run)'
|
|
41
|
+
task :dry_run do
|
|
42
|
+
require_relative 'lib/minitest/heat/version'
|
|
43
|
+
DryRun.new(Minitest::Heat::VERSION).run
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
# rubocop:enable Metrics/BlockLength
|
|
47
|
+
|
|
48
|
+
# Validates release readiness
|
|
49
|
+
class ReleaseChecker
|
|
50
|
+
def initialize(version)
|
|
51
|
+
@version = version
|
|
52
|
+
@errors = []
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def validate
|
|
56
|
+
puts "Checking release readiness for v#{@version}..."
|
|
57
|
+
check_version_format
|
|
58
|
+
check_changelog
|
|
59
|
+
check_git_clean
|
|
60
|
+
check_main_branch
|
|
61
|
+
@errors
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def check_version_format
|
|
67
|
+
return if @version.match?(/\A\d+\.\d+\.\d+\z/)
|
|
68
|
+
|
|
69
|
+
@errors << "Version '#{@version}' is not valid semver (expected X.Y.Z)"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def check_changelog
|
|
73
|
+
changelog = File.read('CHANGELOG.md')
|
|
74
|
+
return if changelog.include?("[#{@version}]")
|
|
75
|
+
|
|
76
|
+
@errors << "CHANGELOG.md has no entry for version #{@version}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def check_git_clean
|
|
80
|
+
return if `git status --porcelain`.empty?
|
|
81
|
+
|
|
82
|
+
@errors << 'Working directory has uncommitted changes'
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def check_main_branch
|
|
86
|
+
current_branch = `git branch --show-current`.strip
|
|
87
|
+
return if current_branch == 'main'
|
|
88
|
+
|
|
89
|
+
@errors << "Not on main branch (currently on '#{current_branch}')"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Builds gem and displays contents without publishing
|
|
94
|
+
class DryRun
|
|
95
|
+
def initialize(version)
|
|
96
|
+
@version = version
|
|
97
|
+
@gem_file = "minitest-heat-#{version}.gem"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def run
|
|
101
|
+
build || abort('Gem build failed')
|
|
102
|
+
show_contents
|
|
103
|
+
show_size
|
|
104
|
+
ensure
|
|
105
|
+
cleanup if File.exist?(@gem_file)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def build
|
|
111
|
+
puts "Building #{@gem_file}..."
|
|
112
|
+
system 'gem build minitest-heat.gemspec --silent'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def show_contents
|
|
116
|
+
puts "\nGem contents:"
|
|
117
|
+
system "tar -tf #{@gem_file}"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def show_size
|
|
121
|
+
size = File.size(@gem_file)
|
|
122
|
+
puts "\nGem size: #{(size / 1024.0).round(1)} KB"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def cleanup
|
|
126
|
+
File.delete(@gem_file)
|
|
127
|
+
puts "Cleaned up #{@gem_file}"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Minitest
|
|
4
|
+
module Heat
|
|
5
|
+
class Backtrace
|
|
6
|
+
# Determines an optimal line count for backtrace locations in order to have relevant
|
|
7
|
+
# information but keep the backtrace as compact as possible
|
|
8
|
+
class LineCount
|
|
9
|
+
DEFAULT_LINE_COUNT = 20
|
|
10
|
+
|
|
11
|
+
attr_accessor :locations
|
|
12
|
+
|
|
13
|
+
def initialize(locations)
|
|
14
|
+
@locations = locations
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def earliest_project_location
|
|
18
|
+
locations.rindex { |element| element.project_file? }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def max_location
|
|
22
|
+
[locations.size - 1, 0].max
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def limit
|
|
26
|
+
return 0 if locations.empty?
|
|
27
|
+
|
|
28
|
+
# Find the minimum relevant index, then add 1 to convert from index to count
|
|
29
|
+
[
|
|
30
|
+
DEFAULT_LINE_COUNT - 1,
|
|
31
|
+
earliest_project_location,
|
|
32
|
+
max_location
|
|
33
|
+
].compact.min + 1
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -10,8 +10,10 @@ module Minitest
|
|
|
10
10
|
module LineParser
|
|
11
11
|
# Parses a line from a backtrace in order to convert it to usable components
|
|
12
12
|
def self.read(raw_text)
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
return nil if raw_text.nil? || raw_text.empty?
|
|
14
|
+
|
|
15
|
+
raw_pathname, raw_line_number, raw_container = raw_text.to_s.split(':')
|
|
16
|
+
raw_container = raw_container&.delete_prefix('in `')&.delete_suffix("'")
|
|
15
17
|
|
|
16
18
|
::Minitest::Heat::Location.new(
|
|
17
19
|
pathname: raw_pathname,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'backtrace/line_count'
|
|
3
4
|
require_relative 'backtrace/line_parser'
|
|
4
5
|
|
|
5
6
|
module Minitest
|
|
@@ -30,7 +31,7 @@ module Minitest
|
|
|
30
31
|
def locations
|
|
31
32
|
return [] if raw_backtrace.nil?
|
|
32
33
|
|
|
33
|
-
@locations ||= raw_backtrace.map { |entry| Backtrace::LineParser.read(entry) }
|
|
34
|
+
@locations ||= raw_backtrace.map { |entry| Backtrace::LineParser.read(entry) }.compact
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
# All entries from the backtrace within the project and sorted with the most recently modified
|
|
@@ -6,12 +6,17 @@ module Minitest
|
|
|
6
6
|
module Heat
|
|
7
7
|
# For managing configuration options on how Minitest Heat should handle results
|
|
8
8
|
class Configuration
|
|
9
|
+
DEFAULTS = {
|
|
10
|
+
slow_threshold: 1.0,
|
|
11
|
+
painfully_slow_threshold: 3.0
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
9
14
|
attr_accessor :slow_threshold,
|
|
10
15
|
:painfully_slow_threshold
|
|
11
16
|
|
|
12
17
|
def initialize
|
|
13
|
-
@slow_threshold
|
|
14
|
-
@painfully_slow_threshold =
|
|
18
|
+
@slow_threshold = DEFAULTS[:slow_threshold]
|
|
19
|
+
@painfully_slow_threshold = DEFAULTS[:painfully_slow_threshold]
|
|
15
20
|
end
|
|
16
21
|
end
|
|
17
22
|
end
|
data/lib/minitest/heat/hit.rb
CHANGED
|
@@ -63,33 +63,49 @@ module Minitest
|
|
|
63
63
|
#
|
|
64
64
|
# @return [Integer] the problem weight for the file
|
|
65
65
|
def weight
|
|
66
|
-
|
|
67
|
-
issues.each_pair do |type, values|
|
|
68
|
-
weight += values.size * WEIGHTS.fetch(type, 0)
|
|
69
|
-
end
|
|
70
|
-
weight
|
|
66
|
+
issues.sum { |type, values| values.size * WEIGHTS.fetch(type, 0) }
|
|
71
67
|
end
|
|
72
68
|
|
|
73
69
|
# The total issue count for the file across all issue types. Includes duplicates if they exist
|
|
74
70
|
#
|
|
75
71
|
# @return [Integer] the sum of the counts for all line numbers for all issue types
|
|
76
72
|
def count
|
|
77
|
-
|
|
78
|
-
issues.each_pair do |_type, values|
|
|
79
|
-
count += values.size
|
|
80
|
-
end
|
|
81
|
-
count
|
|
73
|
+
issues.sum { |_type, values| values.size }
|
|
82
74
|
end
|
|
83
75
|
|
|
84
76
|
# The full set of unique line numbers across all issue types
|
|
85
77
|
#
|
|
86
78
|
# @return [Array<Integer>] the full set of unique offending line numbers for the hit
|
|
87
79
|
def line_numbers
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
issues.values.flatten.uniq.sort
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Generates a hash representation for JSON serialization
|
|
84
|
+
#
|
|
85
|
+
# @return [Hash] hit data with file, weight, and line details
|
|
86
|
+
def to_h
|
|
87
|
+
{
|
|
88
|
+
file: relative_path,
|
|
89
|
+
weight: weight,
|
|
90
|
+
lines: lines_summary
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def relative_path
|
|
97
|
+
pathname.to_s.delete_prefix("#{Dir.pwd}/")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def lines_summary
|
|
101
|
+
line_numbers.map do |line_num|
|
|
102
|
+
traces = lines[line_num.to_s] || []
|
|
103
|
+
{
|
|
104
|
+
line: line_num,
|
|
105
|
+
types: traces.map(&:type).uniq,
|
|
106
|
+
count: traces.size
|
|
107
|
+
}
|
|
91
108
|
end
|
|
92
|
-
line_numbers.uniq.sort
|
|
93
109
|
end
|
|
94
110
|
end
|
|
95
111
|
end
|
data/lib/minitest/heat/issue.rb
CHANGED
|
@@ -10,12 +10,12 @@ module Minitest
|
|
|
10
10
|
|
|
11
11
|
TYPES = %i[error broken failure skipped painful slow].freeze
|
|
12
12
|
|
|
13
|
-
# Long-term, these could be configurable so that people can determine their own thresholds of
|
|
14
|
-
# pain for slow tests
|
|
15
|
-
SLOW_THRESHOLDS = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}.freeze
|
|
13
|
+
# # Long-term, these could be configurable so that people can determine their own thresholds of
|
|
14
|
+
# # pain for slow tests
|
|
15
|
+
# SLOW_THRESHOLDS = {
|
|
16
|
+
# slow: 1.0,
|
|
17
|
+
# painful: 3.0
|
|
18
|
+
# }.freeze
|
|
19
19
|
|
|
20
20
|
attr_reader :assertions,
|
|
21
21
|
:locations,
|
|
@@ -120,14 +120,14 @@ module Minitest
|
|
|
120
120
|
#
|
|
121
121
|
# @return [Float] number of seconds after which a test is considered slow
|
|
122
122
|
def slow_threshold
|
|
123
|
-
Minitest::Heat.configuration.slow_threshold
|
|
123
|
+
Minitest::Heat.configuration.slow_threshold
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
# The number, in seconds, for a test to be considered "painfully slow"
|
|
127
127
|
#
|
|
128
128
|
# @return [Float] number of seconds after which a test is considered painfully slow
|
|
129
129
|
def painfully_slow_threshold
|
|
130
|
-
Minitest::Heat.configuration.painfully_slow_threshold
|
|
130
|
+
Minitest::Heat.configuration.painfully_slow_threshold
|
|
131
131
|
end
|
|
132
132
|
|
|
133
133
|
# Determines if a test should be considered slow by comparing it to the low end definition of
|
|
@@ -200,7 +200,41 @@ module Minitest
|
|
|
200
200
|
#
|
|
201
201
|
# @return [String] the first line of the exception message
|
|
202
202
|
def first_line_of_exception_message
|
|
203
|
-
message.
|
|
203
|
+
return '' if message.nil? || message.empty?
|
|
204
|
+
|
|
205
|
+
text = message.split("\n")[0].to_s
|
|
206
|
+
|
|
207
|
+
text.size > exception_message_limit ? "#{text[0..exception_message_limit]}..." : text
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def exception_message_limit
|
|
211
|
+
200
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Generates a hash representation for JSON serialization
|
|
215
|
+
#
|
|
216
|
+
# @return [Hash] issue data
|
|
217
|
+
def to_h
|
|
218
|
+
{
|
|
219
|
+
type: type,
|
|
220
|
+
test_class: test_class,
|
|
221
|
+
test_name: test_identifier,
|
|
222
|
+
execution_time: execution_time,
|
|
223
|
+
assertions: assertions,
|
|
224
|
+
message: message,
|
|
225
|
+
test_location: locations.test_definition&.to_h,
|
|
226
|
+
failure_location: failure_location_hash
|
|
227
|
+
}
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
private
|
|
231
|
+
|
|
232
|
+
def failure_location_hash
|
|
233
|
+
location = locations.most_relevant
|
|
234
|
+
return nil if location.nil?
|
|
235
|
+
return nil if location == locations.test_definition
|
|
236
|
+
|
|
237
|
+
location.to_h
|
|
204
238
|
end
|
|
205
239
|
end
|
|
206
240
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'pathname'
|
|
4
|
+
|
|
3
5
|
module Minitest
|
|
4
6
|
module Heat
|
|
5
7
|
# Consistent structure for extracting information about a given location. In addition to the
|
|
@@ -42,6 +44,17 @@ module Minitest
|
|
|
42
44
|
]
|
|
43
45
|
end
|
|
44
46
|
|
|
47
|
+
# Generates a hash representation for JSON serialization
|
|
48
|
+
#
|
|
49
|
+
# @return [Hash] location data with file, line, and container
|
|
50
|
+
def to_h
|
|
51
|
+
{
|
|
52
|
+
file: relative_filename,
|
|
53
|
+
line: line_number,
|
|
54
|
+
container: container
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
45
58
|
# A short relative pathname and line number pair
|
|
46
59
|
#
|
|
47
60
|
# @return [String] the short filename/line number combo. ex. `dir/file.rb:23`
|
|
@@ -133,7 +146,7 @@ module Minitest
|
|
|
133
146
|
#
|
|
134
147
|
# @return [Boolean] true if the file is in the project (source code or test) but not vendored
|
|
135
148
|
def project_file?
|
|
136
|
-
path.include?(project_root_dir) && !bundled_file?
|
|
149
|
+
path.include?(project_root_dir) && !bundled_file? && !binstub_file?
|
|
137
150
|
end
|
|
138
151
|
|
|
139
152
|
# Determines if the file is in the project `vendor/bundle` directory.
|
|
@@ -143,6 +156,15 @@ module Minitest
|
|
|
143
156
|
path.include?("#{project_root_dir}/vendor/bundle")
|
|
144
157
|
end
|
|
145
158
|
|
|
159
|
+
# Determines if the file is in the project `bin` directory. With binstub'd gems, they'll
|
|
160
|
+
# appear to be source code because the code is located in the project directory. This helps
|
|
161
|
+
# make sure the backtraces don't think that's the case
|
|
162
|
+
#
|
|
163
|
+
# @return [Boolean] true if the file is in `<project_root>/bin
|
|
164
|
+
def binstub_file?
|
|
165
|
+
path.include?("#{project_root_dir}/bin")
|
|
166
|
+
end
|
|
167
|
+
|
|
146
168
|
# Determines if a given file follows the standard approaching to naming test files.
|
|
147
169
|
#
|
|
148
170
|
# @return [Boolean] true if the file name starts with `test_` or ends with `_test.rb`
|
|
@@ -154,7 +176,7 @@ module Minitest
|
|
|
154
176
|
#
|
|
155
177
|
# @return [Boolean] true if the file is in the project but not a test file or vendored file
|
|
156
178
|
def source_code_file?
|
|
157
|
-
project_file? && !test_file?
|
|
179
|
+
project_file? && !test_file?
|
|
158
180
|
end
|
|
159
181
|
|
|
160
182
|
# A safe interface to getting the last modified time for the file in question
|
data/lib/minitest/heat/map.rb
CHANGED
|
@@ -30,6 +30,13 @@ module Minitest
|
|
|
30
30
|
hot_files.take(MAXIMUM_FILES_TO_SHOW)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
# Generates a hash representation for JSON serialization
|
|
34
|
+
#
|
|
35
|
+
# @return [Array<Hash>] array of hit hashes sorted by weight (highest first)
|
|
36
|
+
def to_h
|
|
37
|
+
hot_files.map(&:to_h)
|
|
38
|
+
end
|
|
39
|
+
|
|
33
40
|
private
|
|
34
41
|
|
|
35
42
|
# Sorts the files by hit "weight" so that the most problematic files are at the beginning
|
|
@@ -5,7 +5,6 @@ module Minitest
|
|
|
5
5
|
class Output
|
|
6
6
|
# Builds the collection of tokens for displaying a backtrace when an exception occurs
|
|
7
7
|
class Backtrace
|
|
8
|
-
DEFAULT_LINE_COUNT = 10
|
|
9
8
|
DEFAULT_INDENTATION_SPACES = 2
|
|
10
9
|
|
|
11
10
|
attr_accessor :locations, :backtrace
|
|
@@ -25,14 +24,14 @@ module Minitest
|
|
|
25
24
|
#
|
|
26
25
|
# @return [Integer] the number of lines to limit the backtrace to
|
|
27
26
|
def line_count
|
|
28
|
-
# Defined as a method instead of using the constant
|
|
27
|
+
# Defined as a method instead of using the constant directly in order to easily support
|
|
29
28
|
# adding options for controlling how many lines are displayed from a backtrace.
|
|
30
29
|
#
|
|
31
30
|
# For example, instead of a fixed number, the backtrace could dynamically calculate how
|
|
32
31
|
# many lines it should displaye in order to get to the origination point. Or it could have
|
|
33
32
|
# a default, but inteligently go back further if the backtrace meets some criteria for
|
|
34
33
|
# displaying more lines.
|
|
35
|
-
|
|
34
|
+
::Minitest::Heat::Backtrace::LineCount.new(backtrace.locations).limit
|
|
36
35
|
end
|
|
37
36
|
|
|
38
37
|
# A subset of parsed lines from the backtrace.
|
|
@@ -49,7 +48,7 @@ module Minitest
|
|
|
49
48
|
# it could display the entire backtrace without filtering anything.
|
|
50
49
|
# - It could scan the backtrace to the first appearance of project files and then display
|
|
51
50
|
# all of the lines that occurred after that instance
|
|
52
|
-
# - It
|
|
51
|
+
# - It could filter the lines differently whether the issue originated from a test or from
|
|
53
52
|
# the source code.
|
|
54
53
|
# - It could allow supporting a "compact" or "robust" reporter style so that someone on
|
|
55
54
|
# a smaller screen could easily reduce the information shown so that the results could
|
|
@@ -122,7 +121,8 @@ module Minitest
|
|
|
122
121
|
def source_code_line_token(location)
|
|
123
122
|
return nil unless location.project_file?
|
|
124
123
|
|
|
125
|
-
|
|
124
|
+
source_line = location.source_code.line
|
|
125
|
+
[:muted, " #{Output::SYMBOLS[:arrow]} `#{source_line&.strip || '(source unavailable)'}`"]
|
|
126
126
|
end
|
|
127
127
|
|
|
128
128
|
def containining_element_token(location)
|