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.
@@ -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,5 @@
1
+ module Minitest
2
+ module Heat
3
+ VERSION = "0.0.1"
4
+ end
5
+ 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: []