minitest-heat 0.0.3 → 0.0.7

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.
@@ -2,110 +2,50 @@
2
2
 
3
3
  module Minitest
4
4
  module Heat
5
+ # A collection of test failures
5
6
  class Results
6
-
7
- attr_reader :test_count,
8
- :assertion_count,
9
- :success_count,
10
- :issues,
11
- :start_time,
12
- :stop_time
7
+ attr_reader :issues
13
8
 
14
9
  def initialize
15
- @test_count = 0
16
- @assertion_count = 0
17
- @success_count = 0
18
- @issues = {
19
- error: [],
20
- broken: [],
21
- failure: [],
22
- skipped: [],
23
- slow: []
24
- }
25
- @start_time = nil
26
- @stop_time = nil
27
- end
28
-
29
- def start_timer!
30
- @start_time = Minitest.clock_time
10
+ @issues = []
31
11
  end
32
12
 
33
- def stop_timer!
34
- @stop_time = Minitest.clock_time
35
- end
36
-
37
- def total_time
38
- delta = @stop_time - @start_time
39
-
40
- # Don't return 0
41
- delta.zero? ? 0.1 : delta
13
+ def record(issue)
14
+ @issues << issue
42
15
  end
43
16
 
44
- def tests_per_second
45
- (assertion_count / total_time).round(2)
46
- end
47
-
48
- def assertions_per_second
49
- (assertion_count / total_time).round(2)
50
- end
51
-
52
- def issues?
53
- errors? || failures? || skips?
17
+ def problems?
18
+ errors.any? || brokens.any? || failures.any?
54
19
  end
55
20
 
56
21
  def errors
57
- issues.fetch(:error) { [] }
22
+ @errors ||= select_issues(:error)
58
23
  end
59
24
 
60
25
  def brokens
61
- issues.fetch(:broken) { [] }
26
+ @brokens ||= select_issues(:broken)
62
27
  end
63
28
 
64
29
  def failures
65
- issues.fetch(:failure) { [] }
30
+ @failures ||= select_issues(:failure)
66
31
  end
67
32
 
68
33
  def skips
69
- issues.fetch(:skipped) { [] }
34
+ @skips ||= select_issues(:skipped)
70
35
  end
71
36
 
72
- def slows
73
- issues
74
- .fetch(:slow) { [] }
75
- .sort { |issue| issue.time }
76
- .reverse
77
- .take(3)
37
+ def painfuls
38
+ @painfuls ||= select_issues(:painful).sort_by(&:time).reverse
78
39
  end
79
40
 
80
- def errors?
81
- errors.any?
82
- end
83
-
84
- def brokens?
85
- brokens.any?
86
- end
87
-
88
- def failures?
89
- failures.any?
90
- end
91
-
92
- def skips?
93
- skips.any?
94
- end
95
-
96
- def count(result)
97
- @test_count += 1
98
- @assertion_count += result.assertions
99
- @success_count += 1 if result.passed?
41
+ def slows
42
+ @slows ||= select_issues(:slow).sort_by(&:time).reverse
100
43
  end
101
44
 
102
- def record_issue(result)
103
- issue = Heat::Issue.new(result)
104
-
105
- @issues[issue.type] ||= []
106
- @issues[issue.type] << issue
45
+ private
107
46
 
108
- issue
47
+ def select_issues(issue_type)
48
+ issues.select { |issue| issue.type == issue_type }
109
49
  end
110
50
  end
111
51
  end
@@ -51,10 +51,15 @@ module Minitest
51
51
  #
52
52
  # @return [type] [description]
53
53
  def file_lines
54
- @raw_lines ||= File.readlines("#{Dir.pwd}#{filename}", chomp: true)
54
+ @raw_lines ||= File.readlines(filename, chomp: true)
55
55
  @raw_lines.pop while @raw_lines.last.strip.empty?
56
56
 
57
57
  @raw_lines
58
+ rescue Errno::ENOENT
59
+ # Occasionally, for a variety of reasons, a file can't be read. In those cases, it's best to
60
+ # return no source code lines rather than have the test suite raise an error unrelated to
61
+ # the code being tested becaues that gets confusing.
62
+ []
58
63
  end
59
64
 
60
65
  private
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minitest
4
+ module Heat
5
+ # Provides a timer to keep track of the full test suite duration and provide convenient methods
6
+ # for calculating tests/second and assertions/second
7
+ class Timer
8
+ attr_reader :test_count, :assertion_count, :start_time, :stop_time
9
+
10
+ # Creates an instance of a timer to be used for the duration of a test suite run
11
+ #
12
+ # @return [self]
13
+ def initialize
14
+ @test_count = 0
15
+ @assertion_count = 0
16
+
17
+ @start_time = nil
18
+ @stop_time = nil
19
+ end
20
+
21
+ # Records the start time for the full test suite using `Minitest.clock_time`
22
+ #
23
+ # @return [Float] the Minitest.clock_time
24
+ def start!
25
+ @start_time = Minitest.clock_time
26
+ end
27
+
28
+ # Records the stop time for the full test suite using `Minitest.clock_time`
29
+ #
30
+ # @return [Float] the Minitest.clock_time
31
+ def stop!
32
+ @stop_time = Minitest.clock_time
33
+ end
34
+
35
+ # Calculates the total time take for the full test suite to run while ensuring it never
36
+ # returns a zero that would be problematic as a denomitor in calculating average times
37
+ #
38
+ # @return [Float] the clocktime duration of the test suite run in seconds
39
+ def total_time
40
+ # Don't return 0. The time can end up being 0 for a new or realy fast test suite, and
41
+ # dividing by 0 doesn't go well when determining average time, so this ensures it uses a
42
+ # close-enough-but-not-zero value.
43
+ delta.zero? ? 0.01 : delta
44
+ end
45
+
46
+ # Records the test and assertion counts for a given test outcome
47
+ # @param count [Integer] the number of assertions from the test
48
+ #
49
+ # @return [void]
50
+ def increment_counts(count)
51
+ @test_count += 1
52
+ @assertion_count += count
53
+ end
54
+
55
+ # Provides a nice rounded answer for about how many tests were completed per second
56
+ #
57
+ # @return [Float] the average number of tests completed per second
58
+ def tests_per_second
59
+ (test_count / total_time).round(2)
60
+ end
61
+
62
+ # Provides a nice rounded answer for about how many assertions were completed per second
63
+ #
64
+ # @return [Float] the average number of assertions completed per second
65
+ def assertions_per_second
66
+ (assertion_count / total_time).round(2)
67
+ end
68
+
69
+ private
70
+
71
+ # The total time the test suite was running.
72
+ #
73
+ # @return [Float] the time in seconds elapsed between starting the timer and stopping it
74
+ def delta
75
+ return 0 unless start_time && stop_time
76
+
77
+ stop_time - start_time
78
+ end
79
+ end
80
+ end
81
+ end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Minitest
2
4
  module Heat
3
- VERSION = "0.0.3"
5
+ VERSION = '0.0.7'
4
6
  end
5
7
  end
data/lib/minitest/heat.rb CHANGED
@@ -1,12 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'heat/backtrace'
4
+ require_relative 'heat/hit'
4
5
  require_relative 'heat/issue'
6
+ require_relative 'heat/line'
5
7
  require_relative 'heat/location'
6
8
  require_relative 'heat/map'
7
9
  require_relative 'heat/output'
8
10
  require_relative 'heat/results'
9
11
  require_relative 'heat/source'
12
+ require_relative 'heat/timer'
10
13
  require_relative 'heat/version'
11
14
 
12
15
  module Minitest
@@ -3,12 +3,12 @@
3
3
  require_relative 'heat_reporter'
4
4
 
5
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
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
8
  # Heat.show_fast!
9
9
  end
10
10
 
11
- # TODO options.
11
+ # TODO: options.
12
12
  # 1. Fail Fast
13
13
  # 2. Don't worry about skips.
14
14
  # 3. Skip coverage.
@@ -18,9 +18,9 @@ module Minitest
18
18
  io = options[:io]
19
19
 
20
20
  # Clean out the existing reporters.
21
- self.reporter.reporters = []
21
+ reporter.reporters = []
22
22
 
23
23
  # Use Reviewer as the sole reporter.
24
- self.reporter << HeatReporter.new(io, options)
24
+ reporter << HeatReporter.new(io, options)
25
25
  end
26
26
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "heat"
3
+ require_relative 'heat'
4
4
 
5
5
  module Minitest
6
6
  # Custom minitest reporter to proactively identify likely culprits in test failures by focusing on
@@ -29,50 +29,55 @@ module Minitest
29
29
  # Pulls from minitest-color as well:
30
30
  # https://github.com/teoljungberg/minitest-color/blob/master/lib/minitest/color_plugin.rb
31
31
  class HeatReporter < AbstractReporter
32
-
33
32
  attr_reader :output,
34
33
  :options,
34
+ :timer,
35
35
  :results,
36
36
  :map
37
37
 
38
38
  def initialize(io = $stdout, options = {})
39
- @output = Heat::Output.new(io)
40
39
  @options = options
41
40
 
42
- @results = Heat::Results.new
43
- @map = Heat::Map.new
41
+ @timer = Heat::Timer.new
42
+ @results = Heat::Results.new
43
+ @map = Heat::Map.new
44
+ @output = Heat::Output.new(io)
44
45
  end
45
46
 
46
47
  # Starts reporting on the run.
47
48
  def start
48
- output.puts
49
- output.puts
50
- @results.start_timer!
49
+ timer.start!
50
+
51
+ # A couple of blank lines to create some breathing room
52
+ output.newline
53
+ output.newline
51
54
  end
52
55
 
53
56
  # About to start running a test. This allows a reporter to show that it is starting or that we
54
57
  # are in the middle of a test run.
55
- def prerecord(klass, name)
56
- end
58
+ def prerecord(klass, name); end
57
59
 
58
60
  # Records the data from a result.
61
+ #
59
62
  # Minitest::Result source:
60
63
  # https://github.com/seattlerb/minitest/blob/f4f57afaeb3a11bd0b86ab0757704cb78db96cf4/lib/minitest.rb#L504
61
64
  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
65
+ # Convert a Minitest Result into an "issue" to more consistently expose the data needed to
66
+ # adjust the failure output to the type of failure
67
+ issue = Heat::Issue.new(result)
68
+
69
+ timer.increment_counts(issue.result.assertions)
70
+ results.record(issue)
71
+ map.add(*issue.to_hit) if issue.hit?
72
+
73
+ output.marker(issue.type)
70
74
  end
71
75
 
72
76
  # Outputs the summary of the run.
73
77
  def report
74
- @results.stop_timer!
78
+ timer.stop!
75
79
 
80
+ # A couple of blank lines to create some breathing room
76
81
  output.newline
77
82
  output.newline
78
83
 
@@ -80,26 +85,45 @@ module Minitest
80
85
  # pressing issues are displayed at the bottom of the report in order to reduce scrolling.
81
86
  # This way, as you fix issues, the list gets shorter, and eventually the least critical
82
87
  # issues will be displayed without scrolling once more problematic issues are resolved.
83
- if results.failures.empty? && results.brokens.empty? && results.errors.empty? && results.skips.empty?
84
- results.slows.each { |issue| output.issue_details(issue) }
85
- end
88
+ %i[slows painfuls skips failures brokens errors].each do |issue_category|
89
+ next unless show?(issue_category)
86
90
 
87
- if results.failures.empty? && results.brokens.empty? && results.errors.empty?
88
- results.skips.each { |issue| output.issue_details(issue) }
91
+ results.send(issue_category).each { |issue| output.issue_details(issue) }
89
92
  end
90
93
 
91
- results.failures.each { |issue| output.issue_details(issue) }
92
- results.brokens.each { |issue| output.issue_details(issue) }
93
- results.errors.each { |issue| output.issue_details(issue) }
94
-
95
- output.compact_summary(results)
94
+ # Display a short summary of the total issue counts fore ach category as well as performance
95
+ # details for the test suite as a whole
96
+ output.compact_summary(results, timer)
96
97
 
98
+ # If there were issues, shows a short heat map summary of which files and lines were the most
99
+ # common sources of issues
97
100
  output.heat_map(map)
101
+
102
+ # A blank line to create some breathing room
103
+ output.newline
98
104
  end
99
105
 
100
106
  # Did this run pass?
101
107
  def passed?
102
108
  results.errors.empty? && results.failures.empty?
103
109
  end
110
+
111
+ private
112
+
113
+ def no_problems?
114
+ !results.problems?
115
+ end
116
+
117
+ def no_problems_or_skips?
118
+ !results.problems? && !results.skips.any?
119
+ end
120
+
121
+ def show?(issue_category)
122
+ case issue_category
123
+ when :skips then no_problems?
124
+ when :painfuls, :slows then no_problems_or_skips?
125
+ else true
126
+ end
127
+ end
104
128
  end
105
129
  end
@@ -21,8 +21,6 @@ Gem::Specification.new do |spec|
21
21
  spec.metadata['source_code_uri'] = 'https://github.com/garrettdimon/minitest-heat'
22
22
  spec.metadata['wiki_uri'] = 'https://github.com/garrettdimon/minitest-heat/wiki'
23
23
 
24
-
25
-
26
24
  # Specify which files should be added to the gem when it is released.
27
25
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
28
26
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
@@ -34,7 +32,11 @@ Gem::Specification.new do |spec|
34
32
 
35
33
  spec.add_runtime_dependency 'minitest'
36
34
 
35
+ spec.add_development_dependency 'awesome_print'
37
36
  spec.add_development_dependency 'dead_end'
38
37
  spec.add_development_dependency 'pry'
38
+ spec.add_development_dependency 'rubocop'
39
+ spec.add_development_dependency 'rubocop-minitest'
40
+ spec.add_development_dependency 'rubocop-rake'
39
41
  spec.add_development_dependency 'simplecov'
40
42
  end
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: 0.0.3
4
+ version: 0.0.7
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-08-30 00:00:00.000000000 Z
11
+ date: 2021-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: awesome_print
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'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: dead_end
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +66,48 @@ dependencies:
52
66
  - - ">="
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
55
111
  - !ruby/object:Gem::Dependency
56
112
  name: simplecov
57
113
  requirement: !ruby/object:Gem::Requirement
@@ -75,6 +131,7 @@ extensions: []
75
131
  extra_rdoc_files: []
76
132
  files:
77
133
  - ".gitignore"
134
+ - ".rubocop.yml"
78
135
  - ".travis.yml"
79
136
  - CODE_OF_CONDUCT.md
80
137
  - Gemfile
@@ -86,12 +143,22 @@ files:
86
143
  - bin/setup
87
144
  - lib/minitest/heat.rb
88
145
  - lib/minitest/heat/backtrace.rb
146
+ - lib/minitest/heat/hit.rb
89
147
  - lib/minitest/heat/issue.rb
148
+ - lib/minitest/heat/line.rb
90
149
  - lib/minitest/heat/location.rb
91
150
  - lib/minitest/heat/map.rb
92
151
  - lib/minitest/heat/output.rb
152
+ - lib/minitest/heat/output/backtrace.rb
153
+ - lib/minitest/heat/output/issue.rb
154
+ - lib/minitest/heat/output/map.rb
155
+ - lib/minitest/heat/output/marker.rb
156
+ - lib/minitest/heat/output/results.rb
157
+ - lib/minitest/heat/output/source_code.rb
158
+ - lib/minitest/heat/output/token.rb
93
159
  - lib/minitest/heat/results.rb
94
160
  - lib/minitest/heat/source.rb
161
+ - lib/minitest/heat/timer.rb
95
162
  - lib/minitest/heat/version.rb
96
163
  - lib/minitest/heat_plugin.rb
97
164
  - lib/minitest/heat_reporter.rb
@@ -121,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
188
  - !ruby/object:Gem::Version
122
189
  version: '0'
123
190
  requirements: []
124
- rubygems_version: 3.1.4
191
+ rubygems_version: 3.1.6
125
192
  signing_key:
126
193
  specification_version: 4
127
194
  summary: Presents test results in a visual manner to guide you to where to look first.