minitest-heat 0.0.5 → 0.0.9

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.
@@ -6,19 +6,25 @@ module Minitest
6
6
  class Results
7
7
  extend Forwardable
8
8
 
9
- attr_accessor :results
9
+ attr_accessor :results, :timer
10
10
 
11
- def_delegators :@results, :errors, :brokens, :failures, :slows, :skips, :problems?, :slows?
11
+ def_delegators :@results, :issues, :errors, :brokens, :failures, :skips, :painfuls, :slows, :problems?
12
12
 
13
- def initialize(results)
13
+ def initialize(results, timer)
14
14
  @results = results
15
+ @timer = timer
15
16
  @tokens = []
16
17
  end
17
18
 
18
19
  def tokens
20
+ # Only show the issue type counts if there are issues
19
21
  @tokens << [*issue_counts_tokens] if issue_counts_tokens&.any?
20
- @tokens << [assertions_count_token, test_count_token]
21
- @tokens << [assertions_performance_token, tests_performance_token, timing_token]
22
+
23
+ @tokens << [
24
+ timing_token, spacer_token,
25
+ test_count_token, tests_performance_token, join_token,
26
+ assertions_count_token, assertions_performance_token
27
+ ]
22
28
 
23
29
  @tokens
24
30
  end
@@ -33,16 +39,23 @@ module Minitest
33
39
  end
34
40
 
35
41
  def issue_counts_tokens
36
- return unless problems? || slows?
42
+ return unless issues.any?
37
43
 
38
- counts = [error_count_token, broken_count_token, failure_count_token, skip_count_token, slow_count_token].compact
44
+ counts = [
45
+ error_count_token,
46
+ broken_count_token,
47
+ failure_count_token,
48
+ skip_count_token,
49
+ painful_count_token,
50
+ slow_count_token
51
+ ].compact
39
52
 
40
53
  # # Create an array of separator tokens one less than the total number of issue count tokens
41
- separator_tokens = Array.new(counts.size, separator_token)
54
+ spacer_tokens = Array.new(counts.size, spacer_token)
42
55
 
43
56
  counts_with_separators = counts
44
- .zip(separator_tokens) # Add separators between the counts
45
- .flatten(1) # Flatten the zipped separators, but no more
57
+ .zip(spacer_tokens) # Add separators between the counts
58
+ .flatten(1) # Flatten the zipped separators, but no more
46
59
 
47
60
  counts_with_separators.pop # Remove the final trailing zipped separator that's not needed
48
61
 
@@ -66,29 +79,34 @@ module Minitest
66
79
  issue_count_token(style, skips, name: 'Skip')
67
80
  end
68
81
 
82
+ def painful_count_token
83
+ style = problems? || skips.any? ? :muted : :painful
84
+ issue_count_token(style, painfuls, name: 'Painfully Slow')
85
+ end
86
+
69
87
  def slow_count_token
70
- style = problems? ? :muted : :slow
88
+ style = problems? || skips.any? ? :muted : :slow
71
89
  issue_count_token(style, slows, name: 'Slow')
72
90
  end
73
91
 
74
- def assertions_performance_token
75
- [:bold, "#{results.assertions_per_second} assertions/s"]
92
+ def test_count_token
93
+ [:default, "#{pluralize(timer.test_count, 'test')}"]
76
94
  end
77
95
 
78
96
  def tests_performance_token
79
- [:default, " and #{results.tests_per_second} tests/s"]
97
+ [:default, " (#{timer.tests_per_second}/s)"]
80
98
  end
81
99
 
82
- def timing_token
83
- [:default, " in #{results.total_time.round(2)}s"]
100
+ def assertions_count_token
101
+ [:default, "#{pluralize(timer.assertion_count, 'assertion')}"]
84
102
  end
85
103
 
86
- def assertions_count_token
87
- [:muted, pluralize(results.assertion_count, 'Assertion')]
104
+ def assertions_performance_token
105
+ [:default, " (#{timer.assertions_per_second}/s)"]
88
106
  end
89
107
 
90
- def test_count_token
91
- [:muted, " across #{pluralize(results.test_count, 'Test')}"]
108
+ def timing_token
109
+ [:bold, "#{timer.total_time.round(2)}s"]
92
110
  end
93
111
 
94
112
  def issue_count_token(type, collection, name: type.capitalize)
@@ -97,8 +115,12 @@ module Minitest
97
115
  [type, pluralize(collection.size, name)]
98
116
  end
99
117
 
100
- def separator_token
101
- [:muted, ' · ']
118
+ def spacer_token
119
+ Output::TOKENS[:spacer]
120
+ end
121
+
122
+ def join_token
123
+ [:default, ' with ']
102
124
  end
103
125
  end
104
126
  end
@@ -101,9 +101,9 @@ module Minitest
101
101
  # @return [Array<Symbol>] the Token styles for the line number and line of code
102
102
  def styles_for(line_of_code)
103
103
  if line_of_code == source.line && highlight_key_line?
104
- [:default, :default]
104
+ %i[default default]
105
105
  else
106
- [:muted, :muted]
106
+ %i[muted muted]
107
107
  end
108
108
  end
109
109
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Minitest
2
4
  module Heat
3
5
  # Friendly API for printing nicely-formatted output to the console
@@ -6,18 +8,18 @@ module Minitest
6
8
  class InvalidStyle < ArgumentError; end
7
9
 
8
10
  STYLES = {
9
- success: %i[default green],
10
- slow: %i[bold green],
11
- painful: %i[bold green],
12
- error: %i[bold red],
13
- broken: %i[bold red],
14
- failure: %i[default red],
15
- skipped: %i[default yellow],
11
+ success: %i[default green],
12
+ slow: %i[default green],
13
+ painful: %i[bold green],
14
+ error: %i[bold red],
15
+ broken: %i[bold red],
16
+ failure: %i[default red],
17
+ skipped: %i[default yellow],
16
18
  warning_light: %i[light yellow],
17
- italicized: %i[italic gray],
18
- bold: %i[bold default],
19
- default: %i[default default],
20
- muted: %i[light gray]
19
+ italicized: %i[italic gray],
20
+ bold: %i[bold default],
21
+ default: %i[default default],
22
+ muted: %i[light gray]
21
23
  }.freeze
22
24
 
23
25
  attr_accessor :style_key, :content
@@ -38,14 +40,14 @@ module Minitest
38
40
  end
39
41
 
40
42
  def eql?(other)
41
- style_key == other.style_key && content == other.content
43
+ style_key == other.style_key && content == other.content
42
44
  end
43
45
  alias :== eql?
44
46
 
45
47
  private
46
48
 
47
49
  ESC_SEQUENCE = "\e["
48
- END_SEQUENCE = "m"
50
+ END_SEQUENCE = 'm'
49
51
 
50
52
  WEIGHTS = {
51
53
  default: 0,
@@ -2,8 +2,8 @@
2
2
 
3
3
  require_relative 'output/backtrace'
4
4
  require_relative 'output/issue'
5
- require_relative 'output/location'
6
5
  require_relative 'output/map'
6
+ require_relative 'output/marker'
7
7
  require_relative 'output/results'
8
8
  require_relative 'output/source_code'
9
9
  require_relative 'output/token'
@@ -12,33 +12,16 @@ module Minitest
12
12
  module Heat
13
13
  # Friendly API for printing nicely-formatted output to the console
14
14
  class Output
15
- FORMATTERS = {
16
- error: [
17
- [ %i[error label], %i[muted spacer], %i[default test_name] ],
18
- [ %i[italicized summary], ],
19
- [ %i[default backtrace_summary] ],
20
- ],
21
- broken: [
22
- [ %i[broken label], %i[muted spacer], %i[default test_name], %i[muted spacer], %i[muted test_class] ],
23
- [ %i[italicized summary], ],
24
- [ %i[default backtrace_summary] ],
25
- ],
26
- failure: [
27
- [ %i[failure label], %i[muted spacer], %i[default test_name], %i[muted spacer], %i[muted test_class] ],
28
- [ %i[italicized summary] ],
29
- [ %i[muted short_location], ],
30
- [ %i[default source_summary], ],
31
- ],
32
- skipped: [
33
- [ %i[skipped label], %i[muted spacer], %i[default test_name], %i[muted spacer], %i[muted test_class] ],
34
- [ %i[italicized summary] ],
35
- [], # New Line
36
- ],
37
- slow: [
38
- [ %i[slow label], %i[muted spacer], %i[default test_name], %i[muted spacer], %i[default test_class] ],
39
- [ %i[bold slowness], %i[muted spacer], %i[default location], ],
40
- [], # New Line
41
- ]
15
+ SYMBOLS = {
16
+ middot: '·',
17
+ arrow: '➜',
18
+ lead: '|',
19
+ }.freeze
20
+
21
+ TOKENS = {
22
+ spacer: [:muted, " #{SYMBOLS[:middot]} "],
23
+ muted_arrow: [:muted, " #{SYMBOLS[:arrow]} "],
24
+ muted_lead: [:muted, "#{SYMBOLS[:lead]} "],
42
25
  }
43
26
 
44
27
  attr_reader :stream
@@ -59,106 +42,22 @@ module Minitest
59
42
  end
60
43
  alias newline puts
61
44
 
62
- # TOOD: Convert to output class
63
- # - This should likely live in the output/issue class
64
- # - Add a 'fail_fast' option that shows the issue as soon as the failure occurs
65
- def marker(value)
66
- case value
67
- when 'E' then text(:error, value)
68
- when 'B' then text(:failure, value)
69
- when 'F' then text(:failure, value)
70
- when 'S' then text(:skipped, value)
71
- else text(:success, value)
72
- end
73
- end
74
-
75
- # TOOD: Convert to output class
76
- # - This should likely live in the output/issue class
77
- # - There may be justification for creating different "strategies" for the various types
78
45
  def issue_details(issue)
79
- formatter = FORMATTERS[issue.type]
80
-
81
- formatter.each do |lines|
82
- lines.each do |tokens|
83
- style, content_method = *tokens
84
-
85
- if issue.respond_to?(content_method)
86
- # If it's an available method on issue, use that to get the content
87
- content = issue.send(content_method)
88
- text(style, content)
89
- else
90
- # Otherwise, fall back to output and pass issue to *it*
91
- send(content_method, issue)
92
- end
93
- end
94
- newline
95
- end
46
+ print_tokens Minitest::Heat::Output::Issue.new(issue).tokens
96
47
  end
97
48
 
98
- # TOOD: Convert to output class
99
- def heat_map(map)
100
- map.files.each do |file|
101
- pathname = Pathname(file[0])
102
-
103
- path = pathname.dirname.to_s
104
- filename = pathname.basename.to_s
105
-
106
- values = map.hits[pathname.to_s]
107
-
108
-
109
- text(:error, 'E' * values[:error].size) if values[:error]&.any?
110
- text(:broken, 'B' * values[:broken].size) if values[:broken]&.any?
111
- text(:failure, 'F' * values[:failure].size) if values[:failure]&.any?
112
-
113
- unless values[:error]&.any? || values[:broken]&.any? || values[:failure]&.any?
114
- text(:skipped, 'S' * values[:skipped].size) if values[:skipped]&.any?
115
- text(:painful, '—' * values[:painful].size) if values[:painful]&.any?
116
- text(:slow, '–' * values[:slow].size) if values[:slow]&.any?
117
- end
118
-
119
- text(:muted, ' ') if map.hits.any?
120
-
121
- text(:muted, "#{path.delete_prefix(Dir.pwd)}/")
122
- text(:default, filename)
123
-
124
- text(:muted, ':')
125
-
126
- all_line_numbers = values.fetch(:error, []) + values.fetch(:failure, [])
127
- all_line_numbers += values.fetch(:skipped, [])
128
-
129
- line_numbers = all_line_numbers.compact.uniq.sort
130
- line_numbers.each { |line_number| text(:muted, "#{line_number} ") }
131
- newline
132
- end
133
- newline
49
+ def marker(issue_type)
50
+ print_token Minitest::Heat::Output::Marker.new(issue_type).token
134
51
  end
135
52
 
136
- # TOOD: Convert to output class
137
- def test_name_summary(issue)
138
- text(:default, "#{issue.test_class} > #{issue.test_name}")
139
- end
140
-
141
- def compact_summary(results)
142
- results_tokens = ::Minitest::Heat::Output::Results.new(results).tokens
143
-
144
- newline
145
- print_tokens(results_tokens)
53
+ def compact_summary(results, timer)
146
54
  newline
55
+ print_tokens ::Minitest::Heat::Output::Results.new(results, timer).tokens
147
56
  end
148
57
 
149
- def backtrace_summary(issue)
150
- location = issue.location
151
-
152
- backtrace_tokens = ::Minitest::Heat::Output::Backtrace.new(location).tokens
153
- print_tokens(backtrace_tokens)
154
- end
155
-
156
- def source_summary(issue)
157
- filename = issue.location.project_file
158
- line_number = issue.location.project_failure_line
159
-
160
- source_code_tokens = ::Minitest::Heat::Output::SourceCode.new(filename, line_number).tokens
161
- print_tokens(source_code_tokens)
58
+ def heat_map(map)
59
+ newline
60
+ print_tokens ::Minitest::Heat::Output::Map.new(map).tokens
162
61
  end
163
62
 
164
63
  private
@@ -176,6 +75,10 @@ module Minitest
176
75
  style_enabled? ? :styled : :unstyled
177
76
  end
178
77
 
78
+ def print_token(token)
79
+ print Token.new(*token).to_s(token_format)
80
+ end
81
+
179
82
  def print_tokens(lines_of_tokens)
180
83
  lines_of_tokens.each do |tokens|
181
84
  tokens.each do |token|
@@ -2,108 +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
10
+ @issues = []
11
+ @heat_map = Heat::Map.new
31
12
  end
32
13
 
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)
46
- end
47
-
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) { [] }
36
+ @skips ||= select_issues(:skipped)
70
37
  end
71
38
 
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?
39
+ def painfuls
40
+ @painfuls ||= select_issues(:painful).sort_by(&:time).reverse
82
41
  end
83
42
 
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 slows?
97
- slows.any?
43
+ def slows
44
+ @slows ||= select_issues(:slow).sort_by(&:time).reverse
98
45
  end
99
46
 
100
- def record(issue)
101
- @test_count += 1
102
- @assertion_count += issue.result.assertions
103
- @success_count += 1 if issue.result.passed?
47
+ private
104
48
 
105
- @issues[issue.type] ||= []
106
- @issues[issue.type] << issue
49
+ def select_issues(issue_type)
50
+ issues.select { |issue| issue.type == issue_type }
107
51
  end
108
52
  end
109
53
  end
@@ -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.5"
5
+ VERSION = '0.0.9'
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