tdd-guard-minitest 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/lib/tdd_guard_minitest/reporter.rb +94 -24
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f78fce6dfb331408b3524d8c8ae2d727829a4dead71b875433a4f68b0011d758
|
|
4
|
+
data.tar.gz: 1b86798bd82f35a31a5601fabb6f10f8a47cf2be70a5a0d5c5e7db7e37ba5e62
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f19357d0e0ae8cac4f0b0db8c607905727e0dfb36c72fa7f387f69e9da3c568fe16466174f792669dd15ef29dcbe804b947fe6d1d5c5ab9634a7cb9b589c2554
|
|
7
|
+
data.tar.gz: c10546e57c08e637fa6d304af8b10ec8a49f97df610311b274f3d26807086a544d034f2feef18b3e6fdd093939f1594f314d6d4b83c1be9ad0a902d5cea684d5
|
|
@@ -6,9 +6,11 @@ require "minitest"
|
|
|
6
6
|
|
|
7
7
|
module TddGuardMinitest
|
|
8
8
|
@unhandled_errors = []
|
|
9
|
+
@reported = false
|
|
9
10
|
|
|
10
11
|
class << self
|
|
11
12
|
attr_reader :unhandled_errors
|
|
13
|
+
attr_accessor :reported
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
# Minitest reporter that captures test results for TDD Guard validation.
|
|
@@ -34,10 +36,17 @@ module TddGuardMinitest
|
|
|
34
36
|
# point's at_exit hook when $! is set.
|
|
35
37
|
#
|
|
36
38
|
# Injects a synthetic entry into @test_results and writes the JSON
|
|
37
|
-
# through the normal report path. Skips
|
|
38
|
-
#
|
|
39
|
+
# through the normal report path. Skips when this process has already
|
|
40
|
+
# written test.json via the normal Minitest flow, so it never clobbers
|
|
41
|
+
# real results from the same run. A stale test.json left behind by a
|
|
42
|
+
# previous process is overwritten so the file always reflects the most
|
|
43
|
+
# recent run's state.
|
|
39
44
|
def self.handle_load_error(exception)
|
|
40
45
|
new(StringIO.new).handle_load_error(exception)
|
|
46
|
+
rescue ArgumentError
|
|
47
|
+
# Project root is not configured; the user has already seen the
|
|
48
|
+
# configuration error from the main test run. Avoid double-raising
|
|
49
|
+
# from the autorun at_exit hook.
|
|
41
50
|
end
|
|
42
51
|
|
|
43
52
|
# Reads the existing test.json, merges in the unhandledErrors field,
|
|
@@ -45,6 +54,8 @@ module TddGuardMinitest
|
|
|
45
54
|
# autorun.rb after Minitest.after_run blocks have completed.
|
|
46
55
|
def self.append_unhandled_errors(errors)
|
|
47
56
|
new(StringIO.new).append_unhandled_errors(errors)
|
|
57
|
+
rescue ArgumentError
|
|
58
|
+
# Same as above: skip when the project root is not configured.
|
|
48
59
|
end
|
|
49
60
|
|
|
50
61
|
def append_unhandled_errors(errors)
|
|
@@ -57,7 +68,7 @@ module TddGuardMinitest
|
|
|
57
68
|
end
|
|
58
69
|
|
|
59
70
|
def handle_load_error(exception)
|
|
60
|
-
return if
|
|
71
|
+
return if TddGuardMinitest.reported
|
|
61
72
|
|
|
62
73
|
add_load_error(exception)
|
|
63
74
|
report
|
|
@@ -109,6 +120,7 @@ module TddGuardMinitest
|
|
|
109
120
|
|
|
110
121
|
FileUtils.mkdir_p(@storage_dir)
|
|
111
122
|
File.write(File.join(@storage_dir, "test.json"), JSON.pretty_generate(result))
|
|
123
|
+
TddGuardMinitest.reported = true
|
|
112
124
|
end
|
|
113
125
|
|
|
114
126
|
def passed?
|
|
@@ -119,6 +131,17 @@ module TddGuardMinitest
|
|
|
119
131
|
|
|
120
132
|
def compute_expected_count
|
|
121
133
|
filter = options[:filter]
|
|
134
|
+
# Skip the count when the filter cannot be reliably matched against
|
|
135
|
+
# method names. Two cases motivate this:
|
|
136
|
+
# - filter is something other than String/Regexp (e.g. a Proc), which
|
|
137
|
+
# Minitest's grep-based methods_matching cannot count.
|
|
138
|
+
# - Rails passes line-targeted runs (`rails test path:N`) via
|
|
139
|
+
# options[:test_files] as "path:N" entries while leaving filter nil,
|
|
140
|
+
# so runnable_methods returns the file's full set and an inflated
|
|
141
|
+
# expected_count would falsely flip the run's reason to "interrupted".
|
|
142
|
+
return 0 if filter && !filter.is_a?(String) && !filter.is_a?(Regexp)
|
|
143
|
+
return 0 if line_targeted?(options[:test_files])
|
|
144
|
+
|
|
122
145
|
Minitest::Runnable.runnables.sum do |klass|
|
|
123
146
|
if filter
|
|
124
147
|
klass.methods_matching(filter).size
|
|
@@ -128,9 +151,14 @@ module TddGuardMinitest
|
|
|
128
151
|
end
|
|
129
152
|
end
|
|
130
153
|
|
|
154
|
+
def line_targeted?(test_files)
|
|
155
|
+
return false unless test_files.is_a?(Array)
|
|
156
|
+
test_files.any? { |entry| entry.is_a?(String) && entry =~ /:\d+\z/ }
|
|
157
|
+
end
|
|
158
|
+
|
|
131
159
|
def build_unhandled_error(exception)
|
|
132
160
|
name = exception.class.name || "(anonymous error class)"
|
|
133
|
-
error = { "name" => name, "message" => exception.message }
|
|
161
|
+
error = { "name" => name, "message" => scrub_utf8(exception.message) }
|
|
134
162
|
stack = extract_relevant_stack(exception.backtrace)
|
|
135
163
|
error["stack"] = stack if stack
|
|
136
164
|
error
|
|
@@ -139,16 +167,31 @@ module TddGuardMinitest
|
|
|
139
167
|
def build_error(failure)
|
|
140
168
|
if failure.is_a?(Minitest::UnexpectedError)
|
|
141
169
|
exception = failure.error
|
|
142
|
-
error = { "message" => exception.message }
|
|
170
|
+
error = { "message" => scrub_utf8(exception.message) }
|
|
143
171
|
stack = extract_relevant_stack(exception.backtrace)
|
|
144
172
|
else
|
|
145
|
-
error = { "message" => failure.message }
|
|
173
|
+
error = { "message" => scrub_utf8(failure.message) }
|
|
146
174
|
stack = extract_relevant_stack(failure.backtrace)
|
|
147
175
|
end
|
|
148
176
|
error["stack"] = stack if stack
|
|
149
177
|
error
|
|
150
178
|
end
|
|
151
179
|
|
|
180
|
+
# Replace bytes that cannot be represented as UTF-8 so that
|
|
181
|
+
# JSON.pretty_generate does not raise on binary or alternately
|
|
182
|
+
# encoded strings (e.g. Shift_JIS, ASCII-8BIT). Valid UTF-8
|
|
183
|
+
# strings, including Japanese, pass through unchanged.
|
|
184
|
+
def scrub_utf8(str)
|
|
185
|
+
return str unless str.is_a?(String)
|
|
186
|
+
return str if str.encoding == Encoding::UTF_8 && str.valid_encoding?
|
|
187
|
+
|
|
188
|
+
if str.encoding == Encoding::UTF_8
|
|
189
|
+
str.scrub
|
|
190
|
+
else
|
|
191
|
+
str.encode("UTF-8", invalid: :replace, undef: :replace)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
152
195
|
def extract_relevant_stack(backtrace)
|
|
153
196
|
return nil unless backtrace
|
|
154
197
|
|
|
@@ -164,8 +207,17 @@ module TddGuardMinitest
|
|
|
164
207
|
source = result.source_location
|
|
165
208
|
return "unknown" unless source
|
|
166
209
|
|
|
167
|
-
|
|
168
|
-
|
|
210
|
+
relative_path(source.first)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Strip a leading cwd prefix and any "./" so file paths in test.json
|
|
214
|
+
# are reported relative to the project root regardless of whether they
|
|
215
|
+
# arrived as absolute paths (from a backtrace) or already-relative
|
|
216
|
+
# paths (from result.source_location).
|
|
217
|
+
def relative_path(path)
|
|
218
|
+
return "unknown" if path.nil? || path.to_s.empty?
|
|
219
|
+
|
|
220
|
+
path = path.to_s
|
|
169
221
|
cwd = "#{Dir.pwd}/"
|
|
170
222
|
path = path.delete_prefix(cwd) if path.start_with?(cwd)
|
|
171
223
|
path.sub(%r{^\./}, "")
|
|
@@ -173,30 +225,47 @@ module TddGuardMinitest
|
|
|
173
225
|
|
|
174
226
|
def determine_storage_dir
|
|
175
227
|
project_root = ENV["TDD_GUARD_PROJECT_ROOT"]
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
228
|
+
if project_root.nil? || project_root.empty?
|
|
229
|
+
raise ArgumentError,
|
|
230
|
+
"project root must be configured via TDD_GUARD_PROJECT_ROOT environment variable"
|
|
231
|
+
end
|
|
179
232
|
|
|
180
|
-
File.
|
|
181
|
-
|
|
233
|
+
expanded = File.expand_path(project_root)
|
|
234
|
+
unless File.directory?(expanded)
|
|
235
|
+
raise ArgumentError,
|
|
236
|
+
"project root does not exist: #{expanded.inspect}"
|
|
237
|
+
end
|
|
182
238
|
|
|
183
|
-
|
|
184
|
-
|
|
239
|
+
resolved = canonical_path(expanded)
|
|
240
|
+
unless cwd_within?(resolved)
|
|
241
|
+
raise ArgumentError,
|
|
242
|
+
"current directory must be within project root #{resolved.inspect}"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
File.join(resolved, DEFAULT_DATA_DIR)
|
|
185
246
|
end
|
|
186
247
|
|
|
187
248
|
def cwd_within?(root)
|
|
188
|
-
|
|
189
|
-
cwd
|
|
190
|
-
|
|
249
|
+
cwd = canonical_path(Dir.pwd)
|
|
250
|
+
cwd == root || cwd.start_with?("#{root}/")
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Resolve symlinks when the path exists so that platforms with
|
|
254
|
+
# symlinked tempdirs (macOS /var -> /private/var) compare consistently.
|
|
255
|
+
def canonical_path(path)
|
|
256
|
+
File.realpath(path)
|
|
257
|
+
rescue Errno::ENOENT
|
|
258
|
+
path
|
|
191
259
|
end
|
|
192
260
|
|
|
193
261
|
# Injects a synthetic failed test entry derived from an exception raised
|
|
194
262
|
# before Minitest could run.
|
|
195
263
|
def add_load_error(exception)
|
|
196
264
|
frame = first_user_frame(exception.backtrace)
|
|
197
|
-
file_path = frame ? frame.split(":", 2).first
|
|
198
|
-
|
|
199
|
-
|
|
265
|
+
file_path = frame ? relative_path(frame.split(":", 2).first) : "unknown"
|
|
266
|
+
msg = scrub_utf8(exception.message)
|
|
267
|
+
name = "#{exception.class}: #{msg.lines.first.to_s.strip}"
|
|
268
|
+
message = build_load_error_message(exception, frame, msg)
|
|
200
269
|
|
|
201
270
|
@test_results << {
|
|
202
271
|
"name" => name,
|
|
@@ -214,11 +283,12 @@ module TddGuardMinitest
|
|
|
214
283
|
end
|
|
215
284
|
end
|
|
216
285
|
|
|
217
|
-
def build_load_error_message(exception, frame)
|
|
218
|
-
|
|
286
|
+
def build_load_error_message(exception, frame, message = nil)
|
|
287
|
+
msg = message || scrub_utf8(exception.message)
|
|
288
|
+
header = "#{exception.class}: #{msg}"
|
|
219
289
|
return header unless frame
|
|
220
290
|
|
|
221
|
-
"#{header}\n #{frame
|
|
291
|
+
"#{header}\n #{relative_path(frame)}"
|
|
222
292
|
end
|
|
223
293
|
end
|
|
224
294
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tdd-guard-minitest
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hiro-Chiba
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-05-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|