minitest-heat 1.1.0 → 1.3.0

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.
@@ -69,7 +69,9 @@ module Minitest
69
69
  # @return [String] the cleaned up version of the test name
70
70
  def test_name(issue)
71
71
  test_prefix = 'test_'
72
- identifier = issue.test_identifier
72
+ identifier = issue.test_identifier.to_s
73
+
74
+ return 'Unknown test' if identifier.empty?
73
75
 
74
76
  if identifier.start_with?(test_prefix)
75
77
  identifier.delete_prefix(test_prefix).gsub('_', ' ').capitalize
@@ -87,22 +89,24 @@ module Minitest
87
89
  end
88
90
 
89
91
  def test_location_tokens
92
+ source_line = locations.test_failure.source_code.line
90
93
  [
91
94
  [:default, locations.test_definition.relative_filename],
92
95
  [:muted, ':'],
93
96
  [:default, locations.test_definition.line_number],
94
97
  arrow_token,
95
98
  [:default, locations.test_failure.line_number],
96
- [:muted, "\n #{locations.test_failure.source_code.line.strip}"]
99
+ [:muted, "\n #{source_line&.strip || '(source unavailable)'}"]
97
100
  ]
98
101
  end
99
102
 
100
103
  def location_tokens
104
+ source_line = locations.most_relevant.source_code.line
101
105
  [
102
106
  [:default, locations.most_relevant.relative_filename],
103
107
  [:muted, ':'],
104
108
  [:default, locations.most_relevant.line_number],
105
- [:muted, "\n #{locations.most_relevant.source_code.line.strip}"]
109
+ [:muted, "\n #{source_line&.strip || '(source unavailable)'}"]
106
110
  ]
107
111
  end
108
112
 
@@ -110,12 +114,15 @@ module Minitest
110
114
  filename = locations.project.filename
111
115
  line_number = locations.project.line_number
112
116
  source = Minitest::Heat::Source.new(filename, line_number: line_number)
117
+ source_line = source.line
113
118
 
114
- [[:muted, " #{Output::SYMBOLS[:arrow]} `#{source.line.strip}`"]]
119
+ [[:muted, " #{Output::SYMBOLS[:arrow]} `#{source_line&.strip || '(source unavailable)'}`"]]
115
120
  end
116
121
 
117
122
  def summary_tokens
118
- [[:italicized, issue.summary.delete_suffix('---------------').strip]]
123
+ summary_text = issue.summary.to_s
124
+ cleaned = summary_text.delete_suffix('---------------').strip
125
+ [[:italicized, cleaned.empty? ? '(no details available)' : cleaned]]
119
126
  end
120
127
 
121
128
  def slowness_summary_tokens
@@ -9,7 +9,12 @@ module Minitest
9
9
 
10
10
  attr_accessor :results, :timer
11
11
 
12
- def_delegators :@results, :issues, :errors, :brokens, :failures, :skips, :painfuls, :slows, :problems?
12
+ # Explicitly define issues to avoid Forwardable warning about Object#issues private method
13
+ def issues
14
+ @results.issues
15
+ end
16
+
17
+ def_delegators :@results, :errors, :brokens, :failures, :skips, :painfuls, :slows, :problems?
13
18
 
14
19
  def initialize(results, timer)
15
20
  @results = results
@@ -17,10 +17,10 @@ module Minitest
17
17
  failure: %i[default red],
18
18
  skipped: %i[default yellow],
19
19
  warning_light: %i[light yellow],
20
- italicized: %i[italic gray],
20
+ italicized: %i[italic default],
21
21
  bold: %i[bold default],
22
22
  default: %i[default default],
23
- muted: %i[light gray]
23
+ muted: %i[light default]
24
24
  }.freeze
25
25
 
26
26
  attr_accessor :style_key, :content
@@ -154,8 +154,9 @@ module Minitest
154
154
  tokens.each do |token|
155
155
  begin
156
156
  print Token.new(*token).to_s(token_format)
157
- rescue
158
- puts token.inspect
157
+ rescue ArgumentError => e
158
+ # Token format error - output debug info and continue
159
+ puts "Token error (#{e.message}): #{token.inspect}"
159
160
  end
160
161
  end
161
162
  newline
@@ -35,15 +35,15 @@ module Minitest
35
35
  # numbers if the tests reference a shared method with an assertion in it. So in those cases,
36
36
  # the backtrace is simply the test definition
37
37
  backtrace = if issue.error?
38
- # With errors, we have a backtrace
39
- issue.locations.backtrace.project_locations
40
- else
41
- # With failures, the test definition is the most granular backtrace equivalent
42
- location = issue.locations.test_definition
43
- location.raw_container = issue.test_identifier
38
+ # With errors, we have a backtrace
39
+ issue.locations.backtrace.project_locations
40
+ else
41
+ # With failures, the test definition is the most granular backtrace equivalent
42
+ location = issue.locations.test_definition
43
+ location.raw_container = issue.test_identifier
44
44
 
45
- [location]
46
- end
45
+ [location]
46
+ end
47
47
 
48
48
  @heat_map.add(pathname, line_number, issue.type, backtrace: backtrace)
49
49
  end
@@ -76,6 +76,39 @@ module Minitest
76
76
  @slows ||= select_issues(:slow).sort_by(&:execution_time).reverse
77
77
  end
78
78
 
79
+ # Returns count statistics by issue type
80
+ #
81
+ # @return [Hash] counts for each issue type
82
+ def statistics
83
+ {
84
+ total: issues.size,
85
+ errors: errors.size,
86
+ broken: brokens.size,
87
+ failures: failures.size,
88
+ skipped: skips.size,
89
+ painful: painfuls.size,
90
+ slow: slows.size
91
+ }
92
+ end
93
+
94
+ # Returns all issues that are not successes
95
+ #
96
+ # @return [Array<Issue>] issues that represent problems (errors, failures, skips, slow)
97
+ def issues_with_problems
98
+ issues.select(&:hit?)
99
+ end
100
+
101
+ # Generates a hash representation for JSON serialization
102
+ #
103
+ # @return [Hash] results data
104
+ def to_h
105
+ {
106
+ statistics: statistics,
107
+ heat_map: heat_map.to_h,
108
+ issues: issues_with_problems.map(&:to_h)
109
+ }
110
+ end
111
+
79
112
  private
80
113
 
81
114
  def select_issues(issue_type)
@@ -52,13 +52,14 @@ module Minitest
52
52
  # @return [type] [description]
53
53
  def file_lines
54
54
  @raw_lines ||= File.readlines(filename, chomp: true)
55
- @raw_lines.pop while @raw_lines.last.strip.empty?
55
+ # Remove trailing empty lines, checking for nil/empty safely
56
+ @raw_lines.pop while @raw_lines.any? && @raw_lines.last&.strip.to_s.empty?
56
57
 
57
58
  @raw_lines
58
- rescue Errno::ENOENT
59
+ rescue Errno::ENOENT, Errno::EACCES, Errno::EISDIR, IOError, Encoding::UndefinedConversionError
59
60
  # Occasionally, for a variety of reasons, a file can't be read. In those cases, it's best to
60
61
  # 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
+ # the code being tested because that gets confusing.
62
63
  []
63
64
  end
64
65
 
@@ -66,6 +66,19 @@ module Minitest
66
66
  (assertion_count / total_time).round(2)
67
67
  end
68
68
 
69
+ # Generates a hash representation for JSON serialization
70
+ #
71
+ # @return [Hash] timing data
72
+ def to_h
73
+ {
74
+ total_seconds: total_time,
75
+ test_count: test_count,
76
+ assertion_count: assertion_count,
77
+ tests_per_second: tests_per_second,
78
+ assertions_per_second: assertions_per_second
79
+ }
80
+ end
81
+
69
82
  private
70
83
 
71
84
  # The total time the test suite was running.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Minitest
4
4
  module Heat
5
- VERSION = '1.1.0'
5
+ VERSION = '1.3.0'
6
6
  end
7
7
  end
@@ -3,6 +3,12 @@
3
3
  require_relative 'heat_reporter'
4
4
 
5
5
  module Minitest # rubocop:disable Style/Documentation
6
+ def self.plugin_heat_options(opts, options)
7
+ opts.on '--heat-json', 'Output results as JSON instead of human-readable format' do
8
+ options[:heat_json] = true
9
+ end
10
+ end
11
+
6
12
  def self.plugin_heat_init(options)
7
13
  io = options.fetch(:io, $stdout)
8
14
 
@@ -48,7 +48,9 @@ module Minitest
48
48
  def start
49
49
  timer.start!
50
50
 
51
- # A couple of blank lines to create some breathing room
51
+ # A couple of blank lines to create some breathing room (skip for JSON output)
52
+ return if json_output?
53
+
52
54
  output.newline
53
55
  output.newline
54
56
  end
@@ -72,8 +74,8 @@ module Minitest
72
74
  # Record the issue to show details later
73
75
  results.record(issue)
74
76
 
75
- # Show the marker
76
- output.marker(issue.type)
77
+ # Show the marker (skip for JSON output)
78
+ output.marker(issue.type) unless json_output?
77
79
  rescue StandardError => e
78
80
  display_exception_guidance(e)
79
81
  end
@@ -93,6 +95,45 @@ module Minitest
93
95
  def report
94
96
  timer.stop!
95
97
 
98
+ if json_output?
99
+ output_json
100
+ else
101
+ output_text
102
+ end
103
+ end
104
+
105
+ # Whether to output JSON instead of human-readable text
106
+ #
107
+ # @return [Boolean] true if --heat-json flag was passed
108
+ def json_output?
109
+ options[:heat_json]
110
+ end
111
+
112
+ # Did this run pass?
113
+ def passed?
114
+ results.errors.empty? && results.failures.empty?
115
+ end
116
+
117
+ private
118
+
119
+ def output_json
120
+ require 'json'
121
+ output.stream.puts JSON.pretty_generate(json_results)
122
+ end
123
+
124
+ def json_results
125
+ {
126
+ version: '1.0',
127
+ status: results.problems? ? 'failed' : 'passed',
128
+ timestamp: Time.now.iso8601,
129
+ statistics: results.statistics,
130
+ timing: timer.to_h,
131
+ heat_map: results.heat_map.to_h,
132
+ issues: results.issues_with_problems.map(&:to_h)
133
+ }
134
+ end
135
+
136
+ def output_text
96
137
  # The list of individual issues and their associated details
97
138
  output.issues_list(results)
98
139
 
@@ -107,10 +148,5 @@ module Minitest
107
148
  output.newline
108
149
  output.newline
109
150
  end
110
-
111
- # Did this run pass?
112
- def passed?
113
- results.errors.empty? && results.failures.empty?
114
- end
115
151
  end
116
152
  end
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = 'Presents test results in a visual manner to guide you to where to look first.'
13
13
  spec.homepage = 'https://github.com/garrettdimon/minitest-heat'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = Gem::Requirement.new('>= 2.5.9')
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.1')
16
16
 
17
17
  spec.metadata['homepage_uri'] = spec.homepage
18
18
  spec.metadata['bug_tracker_uri'] = 'https://github.com/garrettdimon/minitest-heat/issues'
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.metadata['documentation_uri'] = 'https://www.rubydoc.info/gems/minitest-heat'
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
+ spec.metadata['rubygems_mfa_required'] = 'true'
23
24
 
24
25
  # Specify which files should be added to the gem when it is released.
25
26
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -33,10 +34,10 @@ Gem::Specification.new do |spec|
33
34
  spec.add_runtime_dependency 'minitest'
34
35
 
35
36
  spec.add_development_dependency 'awesome_print'
36
- spec.add_development_dependency 'dead_end'
37
- spec.add_development_dependency 'pry'
37
+ spec.add_development_dependency 'debug'
38
38
  spec.add_development_dependency 'rubocop'
39
39
  spec.add_development_dependency 'rubocop-minitest'
40
40
  spec.add_development_dependency 'rubocop-rake'
41
41
  spec.add_development_dependency 'simplecov'
42
+ spec.add_development_dependency 'simplecov_json_formatter'
42
43
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minitest-heat
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Garrett Dimon
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2021-12-09 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: minitest
@@ -39,7 +38,7 @@ dependencies:
39
38
  - !ruby/object:Gem::Version
40
39
  version: '0'
41
40
  - !ruby/object:Gem::Dependency
42
- name: dead_end
41
+ name: debug
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
44
  - - ">="
@@ -53,7 +52,7 @@ dependencies:
53
52
  - !ruby/object:Gem::Version
54
53
  version: '0'
55
54
  - !ruby/object:Gem::Dependency
56
- name: pry
55
+ name: rubocop
57
56
  requirement: !ruby/object:Gem::Requirement
58
57
  requirements:
59
58
  - - ">="
@@ -67,7 +66,7 @@ dependencies:
67
66
  - !ruby/object:Gem::Version
68
67
  version: '0'
69
68
  - !ruby/object:Gem::Dependency
70
- name: rubocop
69
+ name: rubocop-minitest
71
70
  requirement: !ruby/object:Gem::Requirement
72
71
  requirements:
73
72
  - - ">="
@@ -81,7 +80,7 @@ dependencies:
81
80
  - !ruby/object:Gem::Version
82
81
  version: '0'
83
82
  - !ruby/object:Gem::Dependency
84
- name: rubocop-minitest
83
+ name: rubocop-rake
85
84
  requirement: !ruby/object:Gem::Requirement
86
85
  requirements:
87
86
  - - ">="
@@ -95,7 +94,7 @@ dependencies:
95
94
  - !ruby/object:Gem::Version
96
95
  version: '0'
97
96
  - !ruby/object:Gem::Dependency
98
- name: rubocop-rake
97
+ name: simplecov
99
98
  requirement: !ruby/object:Gem::Requirement
100
99
  requirements:
101
100
  - - ">="
@@ -109,7 +108,7 @@ dependencies:
109
108
  - !ruby/object:Gem::Version
110
109
  version: '0'
111
110
  - !ruby/object:Gem::Dependency
112
- name: simplecov
111
+ name: simplecov_json_formatter
113
112
  requirement: !ruby/object:Gem::Requirement
114
113
  requirements:
115
114
  - - ">="
@@ -132,20 +131,22 @@ extra_rdoc_files: []
132
131
  files:
133
132
  - ".github/FUNDING.yml"
134
133
  - ".github/workflows/main.yml"
134
+ - ".github/workflows/release.yml"
135
135
  - ".gitignore"
136
136
  - ".rubocop.yml"
137
- - ".travis.yml"
138
137
  - CHANGELOG.md
139
138
  - CODE_OF_CONDUCT.md
140
139
  - Gemfile
141
140
  - Gemfile.lock
142
141
  - LICENSE.txt
143
142
  - README.md
143
+ - RELEASING.md
144
144
  - Rakefile
145
145
  - bin/console
146
146
  - bin/setup
147
147
  - lib/minitest/heat.rb
148
148
  - lib/minitest/heat/backtrace.rb
149
+ - lib/minitest/heat/backtrace/line_count.rb
149
150
  - lib/minitest/heat/backtrace/line_parser.rb
150
151
  - lib/minitest/heat/configuration.rb
151
152
  - lib/minitest/heat/hit.rb
@@ -178,7 +179,7 @@ metadata:
178
179
  documentation_uri: https://www.rubydoc.info/gems/minitest-heat
179
180
  source_code_uri: https://github.com/garrettdimon/minitest-heat
180
181
  wiki_uri: https://github.com/garrettdimon/minitest-heat/wiki
181
- post_install_message:
182
+ rubygems_mfa_required: 'true'
182
183
  rdoc_options: []
183
184
  require_paths:
184
185
  - lib
@@ -186,15 +187,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
186
187
  requirements:
187
188
  - - ">="
188
189
  - !ruby/object:Gem::Version
189
- version: 2.5.9
190
+ version: '3.1'
190
191
  required_rubygems_version: !ruby/object:Gem::Requirement
191
192
  requirements:
192
193
  - - ">="
193
194
  - !ruby/object:Gem::Version
194
195
  version: '0'
195
196
  requirements: []
196
- rubygems_version: 3.2.22
197
- signing_key:
197
+ rubygems_version: 3.6.9
198
198
  specification_version: 4
199
199
  summary: Presents test results in a visual manner to guide you to where to look first.
200
200
  test_files: []
data/.travis.yml DELETED
@@ -1,6 +0,0 @@
1
- ---
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.7.2
6
- before_install: gem install bundler -v 2.1.4