minitest-heat 0.0.1
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 +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +36 -0
- data/LICENSE.txt +21 -0
- data/README.md +44 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/minitest/heat/backtrace.rb +93 -0
- data/lib/minitest/heat/issue.rb +139 -0
- data/lib/minitest/heat/location.rb +64 -0
- data/lib/minitest/heat/map.rb +57 -0
- data/lib/minitest/heat/output.rb +271 -0
- data/lib/minitest/heat/results.rb +112 -0
- data/lib/minitest/heat/source.rb +149 -0
- data/lib/minitest/heat/version.rb +5 -0
- data/lib/minitest/heat.rb +32 -0
- data/lib/minitest/heat_plugin.rb +26 -0
- data/lib/minitest/heat_reporter.rb +99 -0
- data/minitest-heat.gemspec +39 -0
- metadata +114 -0
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Minitest
|
4
|
+
module Heat
|
5
|
+
# Gets the most relevant lines of code surrounding the specified line of code
|
6
|
+
class Source
|
7
|
+
attr_reader :filename
|
8
|
+
|
9
|
+
attr_accessor :line_number, :max_line_count, :context
|
10
|
+
|
11
|
+
CONTEXTS = %i[before around after].freeze
|
12
|
+
|
13
|
+
def initialize(filename, line_number:, max_line_count: 1, context: :around)
|
14
|
+
@filename = filename
|
15
|
+
@line_number = Integer(line_number)
|
16
|
+
@max_line_count = max_line_count
|
17
|
+
@context = context
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns relevant lines as a hash with line numbers as the keys
|
21
|
+
#
|
22
|
+
# @return [Hash] hash of relevant lines with line numbers as keys
|
23
|
+
def to_h
|
24
|
+
line_numbers.map(&:to_s).zip(lines).to_h
|
25
|
+
end
|
26
|
+
|
27
|
+
# Looks up the line of code referenced
|
28
|
+
#
|
29
|
+
# @return [String] the line of code at filename:line_number
|
30
|
+
def line
|
31
|
+
file_lines[line_number - 1]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Looks up the available lines of code around the referenced line number
|
35
|
+
#
|
36
|
+
# @return [Array<String>] the range of lines of code around
|
37
|
+
def lines
|
38
|
+
return [line] if max_line_count == 1
|
39
|
+
|
40
|
+
file_lines[(line_numbers.first - 1)..(line_numbers.last - 1)]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Line numbers for the returned lines
|
44
|
+
#
|
45
|
+
# @return [Array<Integer>] the line numbers corresponding to the lines returned
|
46
|
+
def line_numbers
|
47
|
+
(first_line_number..last_line_number).to_a.uniq
|
48
|
+
end
|
49
|
+
|
50
|
+
# Reads (and chomps) the lines of the target file
|
51
|
+
#
|
52
|
+
# @return [type] [description]
|
53
|
+
def file_lines
|
54
|
+
@raw_lines ||= File.readlines("#{Dir.pwd}#{filename}", chomp: true)
|
55
|
+
@raw_lines.pop while @raw_lines.last.strip.empty?
|
56
|
+
|
57
|
+
@raw_lines
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# The largest possible value for line numbers
|
63
|
+
#
|
64
|
+
# @return [Integer] the last line number of the file
|
65
|
+
def max_line_number
|
66
|
+
file_lines.length
|
67
|
+
end
|
68
|
+
|
69
|
+
# The number of the first line of code to return
|
70
|
+
#
|
71
|
+
# @return [Integer] line number
|
72
|
+
def first_line_number
|
73
|
+
target = line_number - first_line_offset - leftover_trailing_lines_count
|
74
|
+
|
75
|
+
# Can't go earlier than the first line
|
76
|
+
target < 1 ? 1 : target
|
77
|
+
end
|
78
|
+
|
79
|
+
# The number of the last line of code to return
|
80
|
+
#
|
81
|
+
# @return [Integer] line number
|
82
|
+
def last_line_number
|
83
|
+
target = line_number + last_line_offset + leftover_preceding_lines_count
|
84
|
+
|
85
|
+
# Can't go past the end of the file
|
86
|
+
target > max_line_number ? max_line_number : target
|
87
|
+
end
|
88
|
+
|
89
|
+
# The target number of preceding lines to include
|
90
|
+
#
|
91
|
+
# @return [Integer] number of preceding lines to include
|
92
|
+
def first_line_offset
|
93
|
+
case context
|
94
|
+
when :before then other_lines_count
|
95
|
+
when :around then preceding_lines_split_count
|
96
|
+
when :after then 0
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# The target number of trailing lines to include
|
101
|
+
#
|
102
|
+
# @return [Integer] number of trailing lines to include
|
103
|
+
def last_line_offset
|
104
|
+
case context
|
105
|
+
when :before then 0
|
106
|
+
when :around then trailing_lines_split_count
|
107
|
+
when :after then other_lines_count
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# If the preceding lines offset takes_it past the beginning of the file, this provides the
|
112
|
+
# total number of lines that weren't used
|
113
|
+
#
|
114
|
+
# @return [Integer] number of preceding lines that don't exist
|
115
|
+
def leftover_preceding_lines_count
|
116
|
+
target_line_number = line_number - first_line_offset
|
117
|
+
|
118
|
+
target_line_number < 1 ? target_line_number.abs + 1 : 0
|
119
|
+
end
|
120
|
+
|
121
|
+
# If the trailing lines offset takes_it past the end of the file, this provides the total
|
122
|
+
# number of lines that weren't used
|
123
|
+
#
|
124
|
+
# @return [Integer] number of trailing lines that don't exist
|
125
|
+
def leftover_trailing_lines_count
|
126
|
+
target_line_number = line_number + last_line_offset
|
127
|
+
|
128
|
+
target_line_number > max_line_number ? target_line_number - max_line_number : 0
|
129
|
+
end
|
130
|
+
|
131
|
+
# The total number of lines to include in addition to the primary line
|
132
|
+
def other_lines_count
|
133
|
+
max_line_count - 1
|
134
|
+
end
|
135
|
+
|
136
|
+
def preceding_lines_split_count
|
137
|
+
# Round up preceding lines if it's uneven because preceding lines are more likely to be
|
138
|
+
# helpful when debugging
|
139
|
+
(other_lines_count / 2).round(0, half: :up)
|
140
|
+
end
|
141
|
+
|
142
|
+
def trailing_lines_split_count
|
143
|
+
# Round down preceding lines because they provide context in the file but don't contribute
|
144
|
+
# in terms of the code that led to the error
|
145
|
+
(other_lines_count / 2).round(0, half: :down)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'heat/backtrace'
|
4
|
+
require_relative 'heat/issue'
|
5
|
+
require_relative 'heat/location'
|
6
|
+
require_relative 'heat/map'
|
7
|
+
require_relative 'heat/output'
|
8
|
+
require_relative 'heat/results'
|
9
|
+
require_relative 'heat/source'
|
10
|
+
require_relative 'heat/version'
|
11
|
+
|
12
|
+
module Minitest
|
13
|
+
# Custom minitest reporter just for Reviewer. Focuses on printing directly actionable guidance.
|
14
|
+
# - Colorize the Output
|
15
|
+
# - What files had the most errors?
|
16
|
+
# - Show the most impacted areas first.
|
17
|
+
# - Show lowest-level (most nested code) frist.
|
18
|
+
#
|
19
|
+
# Pulls from existing reporters:
|
20
|
+
# https://github.com/seattlerb/minitest/blob/master/lib/minitest.rb#L554
|
21
|
+
#
|
22
|
+
# Lots of insight from:
|
23
|
+
# http://www.monkeyandcrow.com/blog/reading_ruby_minitest_plugin_system/
|
24
|
+
#
|
25
|
+
# And a good example available at:
|
26
|
+
# https://github.com/adamsanderson/minitest-snail
|
27
|
+
#
|
28
|
+
# Pulls from minitest-color as well:
|
29
|
+
# https://github.com/teoljungberg/minitest-color/blob/master/lib/minitest/color_plugin.rb
|
30
|
+
module Heat
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'heat_reporter'
|
4
|
+
|
5
|
+
module Minitest
|
6
|
+
def self.plugin_heat_options(opts, options)
|
7
|
+
opts.on '--show-fast', "Show failures as they happen instead of waiting for the entire suite." do
|
8
|
+
# Heat.show_fast!
|
9
|
+
end
|
10
|
+
|
11
|
+
# TODO options.
|
12
|
+
# 1. Fail Fast
|
13
|
+
# 2. Don't worry about skips.
|
14
|
+
# 3. Skip coverage.
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.plugin_heat_init(options)
|
18
|
+
io = options[:io]
|
19
|
+
|
20
|
+
# Clean out the existing reporters.
|
21
|
+
self.reporter.reporters = []
|
22
|
+
|
23
|
+
# Use Reviewer as the sole reporter.
|
24
|
+
self.reporter << HeatReporter.new(io, options)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "heat"
|
4
|
+
|
5
|
+
module Minitest
|
6
|
+
# Custom minitest reporter to proactively identify likely culprits in test failures by focusing on
|
7
|
+
# the files and line numbers with the most issues and that were most recently modified. It also
|
8
|
+
# visually emphasizes results based on the most significant problems.
|
9
|
+
# 1. Errors - Anything that raised an exception could have significant ripple effects.
|
10
|
+
# 2. Failures - Assuming no exceptions, these are kind of important.
|
11
|
+
# -- Everything else...
|
12
|
+
# 3. Coverage (If using Simplecov) - If things are passing but coverage isn't up to par
|
13
|
+
# 4. Skips - Don't want to skip tests.
|
14
|
+
# 5. Slows (If everything good, but there's )
|
15
|
+
# - Colorize the Output
|
16
|
+
# - What files had the most errors?
|
17
|
+
# - Show the most impacted areas first.
|
18
|
+
# - Show lowest-level (most nested code) frist.
|
19
|
+
#
|
20
|
+
# Pulls from existing reporters:
|
21
|
+
# https://github.com/seattlerb/minitest/blob/master/lib/minitest.rb#L554
|
22
|
+
#
|
23
|
+
# Lots of insight from:
|
24
|
+
# http://www.monkeyandcrow.com/blog/reading_ruby_minitest_plugin_system/
|
25
|
+
#
|
26
|
+
# And a good example available at:
|
27
|
+
# https://github.com/adamsanderson/minitest-snail
|
28
|
+
#
|
29
|
+
# Pulls from minitest-color as well:
|
30
|
+
# https://github.com/teoljungberg/minitest-color/blob/master/lib/minitest/color_plugin.rb
|
31
|
+
class HeatReporter < AbstractReporter
|
32
|
+
|
33
|
+
attr_reader :output,
|
34
|
+
:options,
|
35
|
+
:results,
|
36
|
+
:map
|
37
|
+
|
38
|
+
def initialize(io = $stdout, options = {})
|
39
|
+
@output = Heat::Output.new(io)
|
40
|
+
@options = options
|
41
|
+
|
42
|
+
@results = Heat::Results.new
|
43
|
+
@map = Heat::Map.new
|
44
|
+
end
|
45
|
+
|
46
|
+
# Starts reporting on the run.
|
47
|
+
def start
|
48
|
+
output.puts
|
49
|
+
output.puts
|
50
|
+
@results.start_timer!
|
51
|
+
end
|
52
|
+
|
53
|
+
# About to start running a test. This allows a reporter to show that it is starting or that we
|
54
|
+
# are in the middle of a test run.
|
55
|
+
def prerecord(klass, name)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Records the data from a result.
|
59
|
+
# Minitest::Result source:
|
60
|
+
# https://github.com/seattlerb/minitest/blob/f4f57afaeb3a11bd0b86ab0757704cb78db96cf4/lib/minitest.rb#L504
|
61
|
+
def record(result)
|
62
|
+
@results.count(result)
|
63
|
+
if !result.passed? || result.time > ::Minitest::Heat::Issue::SLOW_THRESHOLD
|
64
|
+
issue = @results.record_issue(result)
|
65
|
+
@map.add(*issue.to_hit)
|
66
|
+
output.marker(issue.marker)
|
67
|
+
else
|
68
|
+
output.marker(result.result_code)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Outputs the summary of the run.
|
73
|
+
def report
|
74
|
+
@results.stop_timer!
|
75
|
+
|
76
|
+
output.newline
|
77
|
+
output.newline
|
78
|
+
|
79
|
+
# Issues start with the least critical and go up to the most critical so that the most
|
80
|
+
# pressing issues are displayed at the bottom of the report in order to reduce scrolling.
|
81
|
+
# This way, as you fix issues, the list gets shorter, and eventually the least critical
|
82
|
+
# issues will be displayed without scrolling once more problematic issues are resolved.
|
83
|
+
results.slows.each { |issue| output.issue_details(issue) }
|
84
|
+
results.skips.each { |issue| output.issue_details(issue) }
|
85
|
+
results.failures.each { |issue| output.issue_details(issue) }
|
86
|
+
results.brokens.each { |issue| output.issue_details(issue) }
|
87
|
+
results.errors.each { |issue| output.issue_details(issue) }
|
88
|
+
|
89
|
+
output.compact_summary(results)
|
90
|
+
|
91
|
+
output.heat_map(map)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Did this run pass?
|
95
|
+
def passed?
|
96
|
+
results.errors.empty? && results.failures.empty?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/minitest/heat/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'minitest-heat'
|
7
|
+
spec.version = Minitest::Heat::VERSION
|
8
|
+
spec.authors = ['Garrett Dimon']
|
9
|
+
spec.email = ['email@garrettdimon.com']
|
10
|
+
|
11
|
+
spec.summary = 'Presents test results in a visual manner to guide you to where to look first.'
|
12
|
+
spec.description = 'Presents test results in a visual manner to guide you to where to look first.'
|
13
|
+
spec.homepage = 'https://github.com/garrettdimon/minitest-heat'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.5.9')
|
16
|
+
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/garrettdimon/minitest-heat/issues'
|
19
|
+
spec.metadata['changelog_uri'] = 'https://github.com/garrettdimon/minitest-heat/CHANGELOG.md'
|
20
|
+
spec.metadata['documentation_uri'] = 'https://www.rubydoc.info/gems/minitest-heat'
|
21
|
+
spec.metadata['source_code_uri'] = 'https://github.com/garrettdimon/minitest-heat'
|
22
|
+
spec.metadata['wiki_uri'] = 'https://github.com/garrettdimon/minitest-heat/wiki'
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
# Specify which files should be added to the gem when it is released.
|
27
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
28
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
29
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
30
|
+
end
|
31
|
+
spec.bindir = 'exe'
|
32
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = ['lib']
|
34
|
+
|
35
|
+
spec.add_runtime_dependency 'minitest'
|
36
|
+
|
37
|
+
spec.add_development_dependency 'pry'
|
38
|
+
spec.add_development_dependency 'simplecov'
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: minitest-heat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Garrett Dimon
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-08-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simplecov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Presents test results in a visual manner to guide you to where to look
|
56
|
+
first.
|
57
|
+
email:
|
58
|
+
- email@garrettdimon.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- ".travis.yml"
|
65
|
+
- CODE_OF_CONDUCT.md
|
66
|
+
- Gemfile
|
67
|
+
- Gemfile.lock
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- bin/console
|
72
|
+
- bin/setup
|
73
|
+
- lib/minitest/heat.rb
|
74
|
+
- lib/minitest/heat/backtrace.rb
|
75
|
+
- lib/minitest/heat/issue.rb
|
76
|
+
- lib/minitest/heat/location.rb
|
77
|
+
- lib/minitest/heat/map.rb
|
78
|
+
- lib/minitest/heat/output.rb
|
79
|
+
- lib/minitest/heat/results.rb
|
80
|
+
- lib/minitest/heat/source.rb
|
81
|
+
- lib/minitest/heat/version.rb
|
82
|
+
- lib/minitest/heat_plugin.rb
|
83
|
+
- lib/minitest/heat_reporter.rb
|
84
|
+
- minitest-heat.gemspec
|
85
|
+
homepage: https://github.com/garrettdimon/minitest-heat
|
86
|
+
licenses:
|
87
|
+
- MIT
|
88
|
+
metadata:
|
89
|
+
homepage_uri: https://github.com/garrettdimon/minitest-heat
|
90
|
+
bug_tracker_uri: https://github.com/garrettdimon/minitest-heat/issues
|
91
|
+
changelog_uri: https://github.com/garrettdimon/minitest-heat/CHANGELOG.md
|
92
|
+
documentation_uri: https://www.rubydoc.info/gems/minitest-heat
|
93
|
+
source_code_uri: https://github.com/garrettdimon/minitest-heat
|
94
|
+
wiki_uri: https://github.com/garrettdimon/minitest-heat/wiki
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 2.5.9
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubygems_version: 3.1.4
|
111
|
+
signing_key:
|
112
|
+
specification_version: 4
|
113
|
+
summary: Presents test results in a visual manner to guide you to where to look first.
|
114
|
+
test_files: []
|