tdd-guard-rspec 0.2.0 → 0.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.
- checksums.yaml +4 -4
- data/lib/tdd_guard_rspec/formatter.rb +71 -4
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 17d7ea075f1b90cf70693e8e26cf16008a22658102df684292403c4c7f669985
|
|
4
|
+
data.tar.gz: ae3a1961293344b6cfd7c80a5f9d399aa5fb48f7efd8034552c137b90188157e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b3e389b8c466ed9dc314f14fd0fbaa8dc73f2f4a290c02b61f697953d0e393150de463538ba4d9a29dbd7a916ee603852fbd50cdb8083e3dc3a0b9c6a68cca53
|
|
7
|
+
data.tar.gz: b107e039edd33dd273d8e0a1b153719fb172e9c4683dc0fa5d05c4795f1dc7dc3f36e35685083190341f07b3197aa678039178e80fe64b47e61bdef26071eb6e
|
|
@@ -9,6 +9,7 @@ module TddGuardRspec
|
|
|
9
9
|
# Mirrors the pytest reporter's single-class architecture.
|
|
10
10
|
class Formatter < RSpec::Core::Formatters::BaseFormatter
|
|
11
11
|
RSpec::Core::Formatters.register self,
|
|
12
|
+
:start,
|
|
12
13
|
:example_passed,
|
|
13
14
|
:example_failed,
|
|
14
15
|
:example_pending,
|
|
@@ -18,22 +19,34 @@ module TddGuardRspec
|
|
|
18
19
|
|
|
19
20
|
DEFAULT_DATA_DIR = ".claude/tdd-guard/data"
|
|
20
21
|
|
|
22
|
+
ANSI_ESCAPE = /\e\[[0-9;]*m/
|
|
23
|
+
CLASS_NAME_LINE = /\A[A-Z(][\w:() ]*:\z/
|
|
24
|
+
|
|
21
25
|
def initialize(output)
|
|
22
26
|
super(output)
|
|
23
27
|
@test_results = []
|
|
24
28
|
@load_errors = []
|
|
29
|
+
@unhandled_errors = []
|
|
25
30
|
@errors_outside = 0
|
|
31
|
+
@expected_count = 0
|
|
26
32
|
@storage_dir = determine_storage_dir
|
|
27
33
|
end
|
|
28
34
|
|
|
35
|
+
def start(notification)
|
|
36
|
+
super
|
|
37
|
+
@expected_count = notification.count
|
|
38
|
+
end
|
|
39
|
+
|
|
29
40
|
def example_passed(notification)
|
|
30
41
|
record_example(notification.example, "passed")
|
|
31
42
|
end
|
|
32
43
|
|
|
33
44
|
def example_failed(notification)
|
|
34
45
|
example = notification.example
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
error = { "message" => notification.exception.message }
|
|
47
|
+
stack = extract_relevant_stack(notification.exception.backtrace)
|
|
48
|
+
error["stack"] = stack if stack
|
|
49
|
+
record_example(example, "failed", [error])
|
|
37
50
|
end
|
|
38
51
|
|
|
39
52
|
def example_pending(notification)
|
|
@@ -42,7 +55,12 @@ module TddGuardRspec
|
|
|
42
55
|
|
|
43
56
|
def message(notification)
|
|
44
57
|
msg = notification.message
|
|
45
|
-
|
|
58
|
+
if msg.include?("An error occurred while loading")
|
|
59
|
+
@load_errors << msg
|
|
60
|
+
elsif msg.include?("Failure/Error:")
|
|
61
|
+
parsed = parse_unhandled_error(msg)
|
|
62
|
+
@unhandled_errors << parsed if parsed
|
|
63
|
+
end
|
|
46
64
|
end
|
|
47
65
|
|
|
48
66
|
def dump_summary(notification)
|
|
@@ -60,10 +78,18 @@ module TddGuardRspec
|
|
|
60
78
|
end
|
|
61
79
|
|
|
62
80
|
has_failures = @test_results.any? { |t| t["state"] == "failed" }
|
|
81
|
+
reason = if has_failures
|
|
82
|
+
"failed"
|
|
83
|
+
elsif @expected_count > 0 && @test_results.length < @expected_count
|
|
84
|
+
"interrupted"
|
|
85
|
+
else
|
|
86
|
+
"passed"
|
|
87
|
+
end
|
|
63
88
|
result = {
|
|
64
89
|
"testModules" => modules_map.values,
|
|
65
|
-
"reason" =>
|
|
90
|
+
"reason" => reason
|
|
66
91
|
}
|
|
92
|
+
result["unhandledErrors"] = @unhandled_errors unless @unhandled_errors.empty?
|
|
67
93
|
|
|
68
94
|
FileUtils.mkdir_p(@storage_dir)
|
|
69
95
|
File.write(File.join(@storage_dir, "test.json"), JSON.pretty_generate(result))
|
|
@@ -109,6 +135,47 @@ module TddGuardRspec
|
|
|
109
135
|
"#{match[1]}: #{match[2].strip}"
|
|
110
136
|
end
|
|
111
137
|
|
|
138
|
+
def extract_relevant_stack(backtrace)
|
|
139
|
+
return nil unless backtrace
|
|
140
|
+
|
|
141
|
+
frame = backtrace.find { |line| line.include?("spec/") && !line.include?("/gems/") }
|
|
142
|
+
return nil unless frame
|
|
143
|
+
|
|
144
|
+
frame.sub(%r{^.*/(?=spec/)}, "").sub(%r{^\./}, "")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Parses a formatted exception string produced by RSpec's internal
|
|
148
|
+
# ExceptionPresenter (delivered via the :message callback for errors outside
|
|
149
|
+
# of examples, such as before/after :suite and :context hook failures).
|
|
150
|
+
#
|
|
151
|
+
# Returns a Hash with "name", "message", and optional "stack" on success,
|
|
152
|
+
# or nil if the expected structure cannot be found (safe fallback: the
|
|
153
|
+
# error is dropped rather than producing malformed output).
|
|
154
|
+
def parse_unhandled_error(raw)
|
|
155
|
+
lines = raw.gsub(ANSI_ESCAPE, "").split("\n")
|
|
156
|
+
|
|
157
|
+
# The header ends where the backtrace begins (first "# ..." line).
|
|
158
|
+
# If there is no backtrace at all, the entire message is the header.
|
|
159
|
+
bt_start = lines.index { |line| line.start_with?("# ") } || lines.length
|
|
160
|
+
|
|
161
|
+
header_lines = lines[0...bt_start]
|
|
162
|
+
class_idx = header_lines.rindex { |line| line =~ CLASS_NAME_LINE }
|
|
163
|
+
return nil unless class_idx
|
|
164
|
+
|
|
165
|
+
name = header_lines[class_idx].chomp(":")
|
|
166
|
+
message_lines = header_lines[(class_idx + 1)..-1] || []
|
|
167
|
+
message_body = message_lines.map { |line| line.sub(/\A /, "") }.join("\n").strip
|
|
168
|
+
return nil if message_body.empty?
|
|
169
|
+
|
|
170
|
+
backtrace_frames = lines[bt_start..-1].to_a.select { |line| line.start_with?("# ") }
|
|
171
|
+
backtrace_paths = backtrace_frames.map { |line| line.sub(/\A# /, "") }
|
|
172
|
+
stack = extract_relevant_stack(backtrace_paths)
|
|
173
|
+
|
|
174
|
+
result = { "name" => name, "message" => message_body }
|
|
175
|
+
result["stack"] = stack if stack
|
|
176
|
+
result
|
|
177
|
+
end
|
|
178
|
+
|
|
112
179
|
def determine_storage_dir
|
|
113
180
|
project_root = ENV["TDD_GUARD_PROJECT_ROOT"]
|
|
114
181
|
return DEFAULT_DATA_DIR unless project_root && !project_root.empty?
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tdd-guard-rspec
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.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-04-
|
|
11
|
+
date: 2026-04-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rspec-core
|
|
@@ -78,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
78
78
|
- !ruby/object:Gem::Version
|
|
79
79
|
version: '0'
|
|
80
80
|
requirements: []
|
|
81
|
-
rubygems_version: 3.
|
|
81
|
+
rubygems_version: 3.5.22
|
|
82
82
|
signing_key:
|
|
83
83
|
specification_version: 4
|
|
84
84
|
summary: RSpec formatter for TDD Guard - enforces Test-Driven Development principles
|