bulldogger 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +262 -0
- data/docs/design-decisions.md +142 -0
- data/docs/evidence-schema.md +388 -0
- data/docs/maintenance.md +92 -0
- data/docs/trace-schema.md +118 -0
- data/lib/bulldogger/capture.rb +98 -0
- data/lib/bulldogger/config.rb +57 -0
- data/lib/bulldogger/evidence.rb +106 -0
- data/lib/bulldogger/formatter.rb +103 -0
- data/lib/bulldogger/frame_source.rb +147 -0
- data/lib/bulldogger/integrations/minitest.rb +87 -0
- data/lib/bulldogger/integrations/rspec.rb +56 -0
- data/lib/bulldogger/minitest.rb +7 -0
- data/lib/bulldogger/pending.rb +58 -0
- data/lib/bulldogger/probe/bucket.rb +90 -0
- data/lib/bulldogger/probe/comparator.rb +95 -0
- data/lib/bulldogger/probe/method_stats.rb +215 -0
- data/lib/bulldogger/probe/raise_tracker.rb +141 -0
- data/lib/bulldogger/probe/registry.rb +32 -0
- data/lib/bulldogger/probe/session.rb +159 -0
- data/lib/bulldogger/probe/target.rb +13 -0
- data/lib/bulldogger/probe/target_resolver.rb +86 -0
- data/lib/bulldogger/probe/writer.rb +61 -0
- data/lib/bulldogger/probe.rb +36 -0
- data/lib/bulldogger/record/session.rb +334 -0
- data/lib/bulldogger/record/sqlite_converter.rb +86 -0
- data/lib/bulldogger/record/writer.rb +67 -0
- data/lib/bulldogger/record.rb +51 -0
- data/lib/bulldogger/redactor.rb +30 -0
- data/lib/bulldogger/rspec.rb +7 -0
- data/lib/bulldogger/run.rb +113 -0
- data/lib/bulldogger/version.rb +5 -0
- data/lib/bulldogger.rb +133 -0
- data/skills/bulldogger/SKILL.md +37 -0
- data/skills/bulldogger/references/failure-evidence.md +56 -0
- data/skills/bulldogger/references/probe.md +36 -0
- data/skills/bulldogger/references/record.md +28 -0
- metadata +153 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 90b1681b42b715c3a8c90836ae9fe8c2378d15fb542b014512ad9cbab6451089
|
|
4
|
+
data.tar.gz: 9535ac4a1206eb1dd91bef0170abf2d7d877ac4a18257dd47faafb15c4bcfa71
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 042df51e319ebf9d8057d2f9125cffdd8437c6b3898e1ee5d450c33be21bec3d1218321f2256e0ea5cd51d4f9c1c73d9350e2fcfb2a40424ac7deb8d627df473
|
|
7
|
+
data.tar.gz: 27e433a4e256a55150270aa5448a8fb9ee06d378a1e528236d63b5f558016e04724cf574382ae64b9730f878deb3e3d5b49f3825b9ba4865de0f40ff86f93b1c
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 meganemura
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# bulldogger
|
|
2
|
+
|
|
3
|
+
bulldogger writes Ruby test failures as structured evidence for coding agents.
|
|
4
|
+
Each JSON file contains the exception, the backtrace, and captured frame values.
|
|
5
|
+
The failure output gives the absolute path to that file.
|
|
6
|
+
|
|
7
|
+
Development and measurements used Ruby 4.0.6 and debug 1.11.1.
|
|
8
|
+
Publication of bulldogger 0.1.0 is pending.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Add both gems to the test group:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
gem "bulldogger", group: :test
|
|
16
|
+
gem "debug", group: :test
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The `debug` gem supplies `DEBUGGER__.capture_frames`, which gives bulldogger locals from each frame.
|
|
20
|
+
Ruby distributes `debug` as a bundled gem.
|
|
21
|
+
Bundler only exposes it when the application Gemfile includes it.
|
|
22
|
+
|
|
23
|
+
Without `debug`, bulldogger records locals from the raising frame.
|
|
24
|
+
It records file, line, and label data for the remaining frames.
|
|
25
|
+
|
|
26
|
+
Add one framework entry point.
|
|
27
|
+
|
|
28
|
+
For Minitest, add this line to `test_helper.rb`:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
require "bulldogger/minitest"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For RSpec, add this line to `spec_helper.rb`:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
require "bulldogger/rspec"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Each entry point starts capture and records each failed test.
|
|
41
|
+
It finishes the run index when the suite ends.
|
|
42
|
+
|
|
43
|
+
## Three approaches
|
|
44
|
+
|
|
45
|
+
bulldogger provides three ways to collect runtime evidence:
|
|
46
|
+
|
|
47
|
+
- A failure snapshot is the default. Green tests do not capture data when they raise no exception.
|
|
48
|
+
- `probe` watches named methods during one explicit run.
|
|
49
|
+
- `record` traces all Ruby method calls during one explicit run.
|
|
50
|
+
|
|
51
|
+
Use a failure snapshot when a failed test already provides an evidence path.
|
|
52
|
+
Use `probe` to inspect one method or to compare behavior before and after a change.
|
|
53
|
+
Use `record` when you must follow the full call sequence.
|
|
54
|
+
|
|
55
|
+
## Failure output
|
|
56
|
+
|
|
57
|
+
This output came from:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
bundle exec ruby -Ilib test/fixtures/minitest_red/red_test.rb
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
2) Error:
|
|
65
|
+
RedTest#test_deep_raise:
|
|
66
|
+
ArgumentError: expected 3 to equal the sum of [1, 2, 3]
|
|
67
|
+
test/fixtures/minitest_red/app.rb:9:in 'Order.total'
|
|
68
|
+
test/fixtures/minitest_red/red_test.rb:20:in 'RedTest#test_deep_raise'
|
|
69
|
+
bulldogger evidence: /home/you/project/tmp/bulldogger/run-20260828-173512-6178/002-RedTest-test_deep_raise.json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Open the path after `bulldogger evidence:`.
|
|
73
|
+
The file contains one failure and its captured runtime values.
|
|
74
|
+
|
|
75
|
+
Each run uses this layout:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
tmp/bulldogger/
|
|
79
|
+
latest -> run-20260828-173512-6178
|
|
80
|
+
run-20260828-173512-6178/
|
|
81
|
+
001-RedTest-test_assertion_failure.json
|
|
82
|
+
002-RedTest-test_deep_raise.json
|
|
83
|
+
index.json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The [`bulldogger` skill](skills/bulldogger/SKILL.md) tells an agent how to inspect these files.
|
|
87
|
+
The [evidence schema](docs/evidence-schema.md) defines every field and capture mode.
|
|
88
|
+
|
|
89
|
+
## Target a method with probe
|
|
90
|
+
|
|
91
|
+
Wrap the relevant test or operation with a named target:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
before_path = Bulldogger.probe("Billing::Invoice#amount") do
|
|
95
|
+
run_related_test
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The evidence summarizes argument and return classes, `nil` values, raised exits, and callers.
|
|
100
|
+
It serializes the first 10 samples by default and counts every call.
|
|
101
|
+
|
|
102
|
+
Run the probe before and after a change, then compare the two files:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
result = Bulldogger.probe_compare(before_path, after_path)
|
|
106
|
+
result.fetch("identical")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
An `identical` value of `true` shows that the compared behavior stayed the same.
|
|
110
|
+
The comparison covers call counts, classes, `nil` counts, raised exits, parameters, callers, and normalized samples.
|
|
111
|
+
|
|
112
|
+
This excerpt came from a generated probe file:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"kind": "probe",
|
|
117
|
+
"targets": ["ProseSample#amount"],
|
|
118
|
+
"methods": {
|
|
119
|
+
"ProseSample#amount": {
|
|
120
|
+
"calls": 3,
|
|
121
|
+
"raised_exits": 1,
|
|
122
|
+
"returns": {
|
|
123
|
+
"classes": {"Integer": 1, "NilClass": 1},
|
|
124
|
+
"nil_count": 1,
|
|
125
|
+
"samples": [{"value": "21"}, {"value": "nil"}]
|
|
126
|
+
},
|
|
127
|
+
"raised": {"ArgumentError": 1},
|
|
128
|
+
"callers": {"-e:1:in 'block in <main>'": 3}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"limits": {"max_samples": 10, "max_value_length": 200}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Record the call sequence
|
|
136
|
+
|
|
137
|
+
Wrap one focused operation when you need a full call sequence:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
trace_path = Bulldogger.record do
|
|
141
|
+
run_related_test
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The result is a JSONL file with a header and one object for each call, return, or raise event.
|
|
146
|
+
This excerpt came from a generated trace:
|
|
147
|
+
|
|
148
|
+
```jsonl
|
|
149
|
+
{"schema_version":1,"kind":"record","events":["call","return","raise"],"limits":{"max_value_length":200}}
|
|
150
|
+
{"event":"call","seq":1,"depth":1,"path":"-e","line":1,"method":"ProseTrace#outer","args":{"value":{"value":"3"}}}
|
|
151
|
+
{"event":"return","seq":4,"depth":1,"path":"-e","line":1,"method":"ProseTrace#outer","return":{"value":"6"}}
|
|
152
|
+
{"event":"raise","seq":7,"depth":2,"path":"-e","line":1,"method":"ProseTrace#inner","exception":{"class":"ArgumentError","message":"negative"}}
|
|
153
|
+
{"event":"return","seq":8,"depth":2,"path":"-e","line":1,"method":"ProseTrace#inner","raised":true}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The [trace schema](docs/trace-schema.md) defines the event fields and tested `jq` queries.
|
|
157
|
+
|
|
158
|
+
JSONL is the primary record format.
|
|
159
|
+
`Bulldogger.trace_to_sqlite(trace_path, db_path)` converts an existing trace when the `sqlite3` gem is available.
|
|
160
|
+
The converter uses a soft require, so bulldogger keeps zero runtime dependencies.
|
|
161
|
+
|
|
162
|
+
## Cost
|
|
163
|
+
|
|
164
|
+
`TracePoint(:raise)` observes every raised exception, including exceptions that application code rescues.
|
|
165
|
+
These measurements used Ruby 4.0.6.
|
|
166
|
+
Each condition has three runs, and the table gives each median.
|
|
167
|
+
|
|
168
|
+
| condition | without bulldogger | with bulldogger | ratio |
|
|
169
|
+
|---|---:|---:|---:|
|
|
170
|
+
| 2,000,000 no-op iterations with no raised exception | 0.0389s | 0.0387s | 0.99x |
|
|
171
|
+
| 10,000 raise and rescue cycles | 0.0052s | 0.4256s | 82.60x |
|
|
172
|
+
| 200 recorded failures with file output | 0.0001s | 0.0252s | 412.85x |
|
|
173
|
+
|
|
174
|
+
The second condition costs 42.042 microseconds for each exception.
|
|
175
|
+
Repeated runs measured 40 to 42 microseconds and 76x to 83x.
|
|
176
|
+
|
|
177
|
+
The capture cost has this measured breakdown:
|
|
178
|
+
|
|
179
|
+
| stage | added cost for each exception |
|
|
180
|
+
|---|---:|
|
|
181
|
+
| Subscribe to `TracePoint(:raise)` | 0.019 microseconds |
|
|
182
|
+
| Call `DEBUGGER__.capture_frames` | 1.411 microseconds |
|
|
183
|
+
| Serialize, redact, and insert into the ring | 24.198 microseconds |
|
|
184
|
+
|
|
185
|
+
Frame capture has a small cost in this measurement, while later processing accounts for most of the measured cost.
|
|
186
|
+
|
|
187
|
+
A green suite with no raised exception caused no measurable overhead in this test.
|
|
188
|
+
A green suite can still raise and rescue exceptions, and each exception incurs the capture cost.
|
|
189
|
+
|
|
190
|
+
### Explicit verb cost
|
|
191
|
+
|
|
192
|
+
The proportionality harness used one app fixture for both verbs.
|
|
193
|
+
It measured about 1,330 ns per targeted method call for `probe`.
|
|
194
|
+
It measured about 4,500 ns per traced call for `record`.
|
|
195
|
+
|
|
196
|
+
`probe` cost scales with calls to the targeted method, which the harness names M.
|
|
197
|
+
`record` cost scales with all traced calls, which the harness names N.
|
|
198
|
+
With M/N at 0.25, the fixture measured `probe` at 8.28x and `record` at 111.76x.
|
|
199
|
+
These ratios describe this app fixture, and another app has a different M/N value.
|
|
200
|
+
|
|
201
|
+
`probe` and `record` are explicit verbs for one focused run before or after a change.
|
|
202
|
+
Do not apply either verb continuously to the full suite.
|
|
203
|
+
|
|
204
|
+
## Disable bulldogger
|
|
205
|
+
|
|
206
|
+
Set `BULLDOGGER_DISABLE=1` to disable capture and output for one test process.
|
|
207
|
+
`BULLDOGGER_DISABLED=1` is an alias with the same behavior.
|
|
208
|
+
|
|
209
|
+
With either switch, startup returns before the `TracePoint(:raise)` subscription.
|
|
210
|
+
It writes no evidence, creates no run directory, and adds no evidence line to a failure.
|
|
211
|
+
The test exit code and failure count stay unchanged.
|
|
212
|
+
Acceptance tests confirm this behavior for Minitest and RSpec.
|
|
213
|
+
|
|
214
|
+
A rescue-heavy green suite measured 0.99x with bulldogger disabled.
|
|
215
|
+
|
|
216
|
+
## Environment variables
|
|
217
|
+
|
|
218
|
+
These environment variables configure a child test process:
|
|
219
|
+
|
|
220
|
+
| Variable | Accepted value | Default and effect |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| `BULLDOGGER_DISABLE` | `1` | Capture is enabled by default. `1` disables capture and output. |
|
|
223
|
+
| `BULLDOGGER_DISABLED` | `1` | Alias for `BULLDOGGER_DISABLE`. |
|
|
224
|
+
| `BULLDOGGER_OUTPUT_DIR` | A nonempty path | The default is `tmp/bulldogger`, relative to the working directory. |
|
|
225
|
+
| `BULLDOGGER_FRAME_SOURCE` | `capture_frames` or `degraded` | The default is automatic selection. |
|
|
226
|
+
|
|
227
|
+
## Secrets and limits
|
|
228
|
+
|
|
229
|
+
Captured values can contain secrets.
|
|
230
|
+
bulldogger checks each local name before it calls `inspect` on the value.
|
|
231
|
+
A matching local becomes `{"redacted": true, "reason": "name"}` and has no `value` field.
|
|
232
|
+
|
|
233
|
+
The default patterns match these names without regard to case:
|
|
234
|
+
|
|
235
|
+
- `password`, `passwd`, and `pass`
|
|
236
|
+
- `secret` and `token`
|
|
237
|
+
- `api_key` and `api-key`
|
|
238
|
+
- the word `key`
|
|
239
|
+
- `credential`, `auth`, `session`, and `cookie`
|
|
240
|
+
|
|
241
|
+
The patterns favor redaction when a name is ambiguous.
|
|
242
|
+
For example, `/auth/i` also matches `author` and `authorized`.
|
|
243
|
+
Applications can replace `Bulldogger.config.redact_patterns` with their own regular expressions.
|
|
244
|
+
|
|
245
|
+
bulldogger also checks keys while it renders a Hash.
|
|
246
|
+
A matching key has the string `"[REDACTED]"` as its rendered value.
|
|
247
|
+
|
|
248
|
+
The defaults keep 20 frames and 50 locals for each frame.
|
|
249
|
+
Each rendered value keeps 200 characters, and each Array or Hash keeps 10 elements.
|
|
250
|
+
The evidence file marks omitted or truncated data.
|
|
251
|
+
|
|
252
|
+
## Scope of version 0.1
|
|
253
|
+
|
|
254
|
+
Version 0.1 provides failure snapshots, targeted probes, and explicit full records.
|
|
255
|
+
It writes JSON evidence and JSONL traces.
|
|
256
|
+
The version 0.1 boundary exposes file artifacts and an offline SQLite converter.
|
|
257
|
+
|
|
258
|
+
The [design decisions](docs/design-decisions.md) explain the three approaches and their measured costs.
|
|
259
|
+
|
|
260
|
+
## License
|
|
261
|
+
|
|
262
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Design decisions
|
|
2
|
+
|
|
3
|
+
These decisions define the failure capture path in bulldogger 0.1.0.
|
|
4
|
+
The measurements used Ruby 4.0.6 and debug 1.11.1.
|
|
5
|
+
|
|
6
|
+
## Capture only raised exceptions
|
|
7
|
+
|
|
8
|
+
An early design traced each method call and return.
|
|
9
|
+
Measurements placed its run time at 60x to 106x the baseline.
|
|
10
|
+
That cost applied throughout each test run.
|
|
11
|
+
|
|
12
|
+
bulldogger subscribes to `TracePoint(:raise)`.
|
|
13
|
+
The subscription produces no capture work until Ruby raises an exception.
|
|
14
|
+
|
|
15
|
+
## Build no shadow stack
|
|
16
|
+
|
|
17
|
+
Another design kept each frame `Binding` in a shadow call stack.
|
|
18
|
+
A `TracePoint` updated the stack on every call and return.
|
|
19
|
+
Measurements placed this design at 9x to 18x the baseline.
|
|
20
|
+
|
|
21
|
+
bulldogger asks `DEBUGGER__.capture_frames` for the stack when Ruby raises.
|
|
22
|
+
This choice removes the continuous call and return work.
|
|
23
|
+
|
|
24
|
+
## Use one frame capture call
|
|
25
|
+
|
|
26
|
+
`binding_of_caller` can return a caller binding.
|
|
27
|
+
Repeated calls intended to walk outward returned the same frame during testing.
|
|
28
|
+
This behavior made the captured stack incorrect.
|
|
29
|
+
|
|
30
|
+
`DEBUGGER__.capture_frames` returns the available frame bindings in one call.
|
|
31
|
+
|
|
32
|
+
## Serialize during the raise hook
|
|
33
|
+
|
|
34
|
+
A `DEBUGGER__::FrameInfo` holds a live `Binding`.
|
|
35
|
+
The binding keeps objects reachable from its frame alive.
|
|
36
|
+
|
|
37
|
+
A measurement retained a frame from a `TracePoint` block.
|
|
38
|
+
The retained binding could still reach the block's captured array.
|
|
39
|
+
Keeping frame objects would let a bounded entry ring retain unbounded object graphs.
|
|
40
|
+
|
|
41
|
+
bulldogger renders values before the `:raise` hook returns.
|
|
42
|
+
The pending ring then holds Hashes, Strings, numbers, and Boolean values.
|
|
43
|
+
It holds no captured `FrameInfo` or `Binding`.
|
|
44
|
+
|
|
45
|
+
The full capture path measured 42.042 microseconds for each exception.
|
|
46
|
+
This path captures frames, renders values, applies redaction, and inserts the snapshot into the ring.
|
|
47
|
+
|
|
48
|
+
## Exclude bulldogger frames
|
|
49
|
+
|
|
50
|
+
`DEBUGGER__.capture_frames(prefix)` removes frames whose paths start with `prefix`.
|
|
51
|
+
A test passed the application directory and received zero application frames.
|
|
52
|
+
|
|
53
|
+
bulldogger passes its own `lib` directory as the prefix.
|
|
54
|
+
This value removes bulldogger frames while it retains application frames.
|
|
55
|
+
|
|
56
|
+
## Keep zero runtime dependencies
|
|
57
|
+
|
|
58
|
+
The gemspec declares development dependencies and no runtime dependency.
|
|
59
|
+
The core uses the Ruby standard library.
|
|
60
|
+
|
|
61
|
+
The complete frame source uses `debug/frame_info` when the application bundle exposes it.
|
|
62
|
+
The `debug` gem is bundled with Ruby 4.0.6, and it is not a default gem.
|
|
63
|
+
Under Bundler, the application Gemfile must include `debug`.
|
|
64
|
+
|
|
65
|
+
When `debug/frame_info` raises `LoadError`, bulldogger uses the degraded frame source.
|
|
66
|
+
That source records raising-frame locals and the remaining frame locations.
|
|
67
|
+
|
|
68
|
+
This design lets applications choose the `debug` dependency.
|
|
69
|
+
The installation guide includes `gem "debug", group: :test` for complete frame locals.
|
|
70
|
+
|
|
71
|
+
## Require an explicit Minitest entry point
|
|
72
|
+
|
|
73
|
+
Minitest discovers files named `minitest/*_plugin.rb` in each active gem.
|
|
74
|
+
A measured plugin file started bulldogger in every Minitest run that activated the gem.
|
|
75
|
+
The test suite did not need a `require "bulldogger"` line to trigger this behavior.
|
|
76
|
+
|
|
77
|
+
This repository's own tests then ran with bulldogger capture active.
|
|
78
|
+
The automatic behavior conflicted with the explicit integration boundary.
|
|
79
|
+
|
|
80
|
+
bulldogger provides `require "bulldogger/minitest"` as the Minitest entry point.
|
|
81
|
+
The require line makes capture activation visible in the test setup.
|
|
82
|
+
|
|
83
|
+
## Keep complete probe counts
|
|
84
|
+
|
|
85
|
+
The probe records every caller, class, and `nil` occurrence.
|
|
86
|
+
Caller sampling could hide a call site and support a false claim that it did not occur.
|
|
87
|
+
Count sampling could hide a later `nil` value and support the same false claim.
|
|
88
|
+
We rejected both performance proposals because they would create false negative evidence.
|
|
89
|
+
|
|
90
|
+
The probe serializes only the first 10 values by default.
|
|
91
|
+
It continues the complete class, `nil`, and caller counts after that limit.
|
|
92
|
+
This design keeps the evidence complete for the questions that probe answers.
|
|
93
|
+
|
|
94
|
+
A raised method also produces a Ruby `:return` event with a `nil` value.
|
|
95
|
+
The Ruby-level rescue counter identifies that raised exit.
|
|
96
|
+
The evidence records `raised_exits` and excludes that event from normal return counts.
|
|
97
|
+
|
|
98
|
+
## Measure the shipped event work
|
|
99
|
+
|
|
100
|
+
Early estimates measured a different amount of work.
|
|
101
|
+
Using those estimates would state a low probe cost and a high record cost.
|
|
102
|
+
Measurement of the shipped paths corrected both directions.
|
|
103
|
+
|
|
104
|
+
The common proportionality harness measured about 1,330 ns per targeted probe call.
|
|
105
|
+
It measured about 4,500 ns per traced record call.
|
|
106
|
+
The record-specific harness measured 40.94x for capture and 58.34x with file output.
|
|
107
|
+
|
|
108
|
+
## Use proportionality as the performance rule
|
|
109
|
+
|
|
110
|
+
A probe observes M calls to its named methods.
|
|
111
|
+
A record observes N calls to all Ruby methods in its traced operation.
|
|
112
|
+
The measured probe cost follows M, and the measured record cost follows N.
|
|
113
|
+
|
|
114
|
+
A fixed cost ratio changes when the workload changes M/N.
|
|
115
|
+
The proportionality rule lets a reader apply the measurement to an application call graph.
|
|
116
|
+
|
|
117
|
+
## Keep JSONL as the record writer
|
|
118
|
+
|
|
119
|
+
The record path writes versioned JSONL with one event on each line.
|
|
120
|
+
`trace_to_sqlite` converts a finished file when the `sqlite3` gem is available.
|
|
121
|
+
|
|
122
|
+
This converter keeps SQLite behind the file boundary.
|
|
123
|
+
It also keeps the core at zero runtime dependencies.
|
|
124
|
+
|
|
125
|
+
## Account for the coverage blind spot
|
|
126
|
+
|
|
127
|
+
Ruby `Coverage` does not observe lines that run under a `TracePoint` callback.
|
|
128
|
+
The suite calls the callback logic directly to test those lines.
|
|
129
|
+
|
|
130
|
+
The coverage gate also keeps a line-specific ledger for callback gaps.
|
|
131
|
+
The measured ledger has zero entries, and its fixed cap is zero.
|
|
132
|
+
The gate fails when a new entry appears.
|
|
133
|
+
|
|
134
|
+
## Limit dogfooding to the acceptance suite
|
|
135
|
+
|
|
136
|
+
The acceptance suite runs under bulldogger and keeps its outer capture instance active.
|
|
137
|
+
The dogfood demo produced `capture_frames` evidence with a planted local and a redacted secret.
|
|
138
|
+
|
|
139
|
+
The unit setup resets the module singleton before each test.
|
|
140
|
+
That reset also stops an outer dogfood instance.
|
|
141
|
+
An instance API would let the subject and the test tool coexist.
|
|
142
|
+
That API is planned after version 0.1.
|