minitest-heat 0.0.4 → 0.0.8

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