minitest-heat 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []