minitest-heat 0.0.4 → 0.0.8

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,104 +2,52 @@
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, :heat_map
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
31
- end
32
-
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
42
- end
43
-
44
- def tests_per_second
45
- (assertion_count / total_time).round(2)
10
+ @issues = []
11
+ @heat_map = Heat::Map.new
46
12
  end
47
13
 
48
- def assertions_per_second
49
- (assertion_count / total_time).round(2)
14
+ def record(issue)
15
+ @issues.push(issue)
16
+ @heat_map.add(*issue.to_hit) if issue.hit?
50
17
  end
51
18
 
52
19
  def problems?
53
- errors? || brokens? || failures? || skips?
20
+ errors.any? || brokens.any? || failures.any?
54
21
  end
55
22
 
56
23
  def errors
57
- issues.fetch(:error) { [] }
24
+ @errors ||= select_issues(:error)
58
25
  end
59
26
 
60
27
  def brokens
61
- issues.fetch(:broken) { [] }
28
+ @brokens ||= select_issues(:broken)
62
29
  end
63
30
 
64
31
  def failures
65
- issues.fetch(:failure) { [] }
32
+ @failures ||= select_issues(:failure)
66
33
  end
67
34
 
68
35
  def skips
69
- issues.fetch(:skipped) { [] }
70
- end
71
-
72
- def slows
73
- issues
74
- .fetch(:slow) { [] }
75
- .sort { |issue| issue.time }
76
- .reverse
77
- .take(3)
78
- end
79
-
80
- def errors?
81
- errors.any?
82
- end
83
-
84
- def brokens?
85
- brokens.any?
36
+ @skips ||= select_issues(:skipped)
86
37
  end
87
38
 
88
- def failures?
89
- failures.any?
39
+ def painfuls
40
+ @painfuls ||= select_issues(:painful).sort_by(&:time).reverse
90
41
  end
91
42
 
92
- def skips?
93
- skips.any?
43
+ def slows
44
+ @slows ||= select_issues(:slow).sort_by(&:time).reverse
94
45
  end
95
46
 
96
- def record(issue)
97
- @test_count += 1
98
- @assertion_count += issue.result.assertions
99
- @success_count += 1 if issue.result.passed?
47
+ private
100
48
 
101
- @issues[issue.type] ||= []
102
- @issues[issue.type] << issue
49
+ def select_issues(issue_type)
50
+ issues.select { |issue| issue.type == issue_type }
103
51
  end
104
52
  end
105
53
  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.4"
5
+ VERSION = '0.0.8'
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,49 +29,52 @@ 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,
35
- :results,
36
- :map
34
+ :timer,
35
+ :results
37
36
 
38
37
  def initialize(io = $stdout, options = {})
39
- @output = Heat::Output.new(io)
40
38
  @options = options
41
39
 
42
- @results = Heat::Results.new
43
- @map = Heat::Map.new
40
+ @timer = Heat::Timer.new
41
+ @results = Heat::Results.new
42
+ @output = Heat::Output.new(io)
44
43
  end
45
44
 
46
45
  # Starts reporting on the run.
47
46
  def start
48
- output.puts
49
- output.puts
50
- @results.start_timer!
47
+ timer.start!
48
+
49
+ # A couple of blank lines to create some breathing room
50
+ output.newline
51
+ output.newline
51
52
  end
52
53
 
53
54
  # About to start running a test. This allows a reporter to show that it is starting or that we
54
55
  # are in the middle of a test run.
55
- def prerecord(klass, name)
56
- end
56
+ def prerecord(klass, name); end
57
57
 
58
58
  # Records the data from a result.
59
+ #
59
60
  # Minitest::Result source:
60
61
  # https://github.com/seattlerb/minitest/blob/f4f57afaeb3a11bd0b86ab0757704cb78db96cf4/lib/minitest.rb#L504
61
62
  def record(result)
63
+ # Convert a Minitest Result into an "issue" to more consistently expose the data needed to
64
+ # adjust the failure output to the type of failure
62
65
  issue = Heat::Issue.new(result)
63
66
 
64
- @results.record(issue)
65
-
66
- @map.add(*issue.to_hit) if issue.hit?
67
+ timer.increment_counts(issue.result.assertions)
68
+ results.record(issue)
67
69
 
68
- output.marker(issue.marker)
70
+ output.marker(issue.type)
69
71
  end
70
72
 
71
73
  # Outputs the summary of the run.
72
74
  def report
73
- @results.stop_timer!
75
+ timer.stop!
74
76
 
77
+ # A couple of blank lines to create some breathing room
75
78
  output.newline
76
79
  output.newline
77
80
 
@@ -79,26 +82,45 @@ module Minitest
79
82
  # pressing issues are displayed at the bottom of the report in order to reduce scrolling.
80
83
  # This way, as you fix issues, the list gets shorter, and eventually the least critical
81
84
  # issues will be displayed without scrolling once more problematic issues are resolved.
82
- if results.failures.empty? && results.brokens.empty? && results.errors.empty? && results.skips.empty?
83
- results.slows.each { |issue| output.issue_details(issue) }
84
- end
85
+ %i[slows painfuls skips failures brokens errors].each do |issue_category|
86
+ next unless show?(issue_category)
85
87
 
86
- if results.failures.empty? && results.brokens.empty? && results.errors.empty?
87
- results.skips.each { |issue| output.issue_details(issue) }
88
+ results.send(issue_category).each { |issue| output.issue_details(issue) }
88
89
  end
89
90
 
90
- results.failures.each { |issue| output.issue_details(issue) }
91
- results.brokens.each { |issue| output.issue_details(issue) }
92
- results.errors.each { |issue| output.issue_details(issue) }
91
+ # Display a short summary of the total issue counts fore ach category as well as performance
92
+ # details for the test suite as a whole
93
+ output.compact_summary(results, timer)
93
94
 
94
- output.compact_summary(results)
95
+ # If there were issues, shows a short heat map summary of which files and lines were the most
96
+ # common sources of issues
97
+ output.heat_map(results)
95
98
 
96
- output.heat_map(map)
99
+ # A blank line to create some breathing room
100
+ output.newline
97
101
  end
98
102
 
99
103
  # Did this run pass?
100
104
  def passed?
101
105
  results.errors.empty? && results.failures.empty?
102
106
  end
107
+
108
+ private
109
+
110
+ def no_problems?
111
+ !results.problems?
112
+ end
113
+
114
+ def no_problems_or_skips?
115
+ !results.problems? && !results.skips.any?
116
+ end
117
+
118
+ def show?(issue_category)
119
+ case issue_category
120
+ when :skips then no_problems?
121
+ when :painfuls, :slows then no_problems_or_skips?
122
+ else true
123
+ end
124
+ end
103
125
  end
104
126
  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.4
4
+ version: 0.0.8
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-31 00:00:00.000000000 Z
11
+ date: 2021-10-15 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.