bulldogger 0.1.0 → 0.3.1

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +154 -0
  3. data/README.ja.md +389 -0
  4. data/README.md +193 -69
  5. data/docs/design-decisions.md +282 -3
  6. data/docs/evidence-schema.md +153 -5
  7. data/docs/maintenance.md +26 -9
  8. data/exe/bulldogger +89 -0
  9. data/lib/bulldogger/application_frames.rb +36 -0
  10. data/lib/bulldogger/capture.rb +15 -1
  11. data/lib/bulldogger/code_state.rb +40 -0
  12. data/lib/bulldogger/collector_environment.rb +16 -0
  13. data/lib/bulldogger/config.rb +3 -5
  14. data/lib/bulldogger/evidence.rb +15 -4
  15. data/lib/bulldogger/exec.rb +86 -0
  16. data/lib/bulldogger/exec_collector.rb +142 -0
  17. data/lib/bulldogger/execution_target.rb +112 -0
  18. data/lib/bulldogger/failure_output.rb +32 -0
  19. data/lib/bulldogger/flt.rb +54 -0
  20. data/lib/bulldogger/flt_collector.rb +186 -0
  21. data/lib/bulldogger/frames.rb +76 -0
  22. data/lib/bulldogger/frames_collector.rb +166 -0
  23. data/lib/bulldogger/instance.rb +97 -0
  24. data/lib/bulldogger/integrations/minitest.rb +61 -21
  25. data/lib/bulldogger/integrations/rspec.rb +44 -13
  26. data/lib/bulldogger/preflight.rb +79 -0
  27. data/lib/bulldogger/probe/bucket.rb +8 -7
  28. data/lib/bulldogger/probe/comparator.rb +3 -2
  29. data/lib/bulldogger/probe/method_stats.rb +1 -1
  30. data/lib/bulldogger/probe/raise_tracker.rb +3 -3
  31. data/lib/bulldogger/probe/session.rb +7 -5
  32. data/lib/bulldogger/probe/writer.rb +11 -1
  33. data/lib/bulldogger/probe.rb +5 -4
  34. data/lib/bulldogger/redactor.rb +5 -3
  35. data/lib/bulldogger/rerun_command.rb +48 -0
  36. data/lib/bulldogger/skill.rb +26 -0
  37. data/lib/bulldogger/version.rb +1 -1
  38. data/lib/bulldogger.rb +33 -63
  39. data/skills/bulldogger/SKILL.md +133 -20
  40. data/skills/bulldogger/references/failure-evidence.md +2 -0
  41. data/skills/bulldogger/references/reexecution.md +149 -0
  42. metadata +26 -12
  43. data/docs/trace-schema.md +0 -118
  44. data/lib/bulldogger/record/session.rb +0 -334
  45. data/lib/bulldogger/record/sqlite_converter.rb +0 -86
  46. data/lib/bulldogger/record/writer.rb +0 -67
  47. data/lib/bulldogger/record.rb +0 -51
  48. data/skills/bulldogger/references/record.md +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90b1681b42b715c3a8c90836ae9fe8c2378d15fb542b014512ad9cbab6451089
4
- data.tar.gz: 9535ac4a1206eb1dd91bef0170abf2d7d877ac4a18257dd47faafb15c4bcfa71
3
+ metadata.gz: 72ea09764a4ece080b0430f55231aa772b451c37174604a2c6352077d36de9e2
4
+ data.tar.gz: d4e01f7ea18e1d7c6e5183dbe8bd7a6e800c4e6c11f0480933db7268d4429eb3
5
5
  SHA512:
6
- metadata.gz: 042df51e319ebf9d8057d2f9125cffdd8437c6b3898e1ee5d450c33be21bec3d1218321f2256e0ea5cd51d4f9c1c73d9350e2fcfb2a40424ac7deb8d627df473
7
- data.tar.gz: 27e433a4e256a55150270aa5448a8fb9ee06d378a1e528236d63b5f558016e04724cf574382ae64b9730f878deb3e3d5b49f3825b9ba4865de0f40ff86f93b1c
6
+ metadata.gz: 25cfda27f1f9255f254b581004346847fafd2397099a67b67bb244801ff50125d95e5dfc437a27f78ce4b110d6982a6194b7970f08cfab5be18dbab242e4e787
7
+ data.tar.gz: 22382a54852bd5bd0b0d062d041fb058d3b8a7ccd9da4f77378faf0d27afe4f27efdd8f36e3e3949ad78ac75c7ee4afece7b240958dec617db246dafef556a4f
data/CHANGELOG.md ADDED
@@ -0,0 +1,154 @@
1
+ # Changelog
2
+
3
+ ## 0.3.1
4
+
5
+ ### The re-run a failure needs is now an explicit verb, not an automatic one
6
+
7
+ `0.2.0` reached the value behind an assertion failure by re-running the one
8
+ failing test automatically, under full recording, in a child process. That
9
+ worked, but it re-ran on every qualifying failure whether or not anyone
10
+ asked, and it paid for a whole-run trace to answer one question. This release
11
+ replaces that automatic replay with re-execution the agent drives, one
12
+ question at a time, and retires the `record` verb along with it.
13
+
14
+ This removes `record`, automatic replay, and their evidence keys, so it is
15
+ a breaking change for a `0.2.0` user who read them. The failure snapshot and
16
+ `probe` are unchanged.
17
+
18
+ `0.3.0` carries the same work, but its release run stopped before publishing
19
+ when a fixture pinned an older minitest than the suite now uses. Its tag
20
+ stands at that commit and no gem was pushed, so `0.3.1` is the first
21
+ published release of the re-execution verbs, with the fixture pinned to the
22
+ minitest the suite loads.
23
+
24
+ - **Four re-execution verbs join `probe`.** `frames` runs a test in a fresh
25
+ process and indexes every call by a stable identifier, `fid`
26
+ (`path:method#k`, where `k` counts calls to that method inside the test
27
+ window). `preflight` runs a test twice in isolation and compares the two
28
+ application-frame sequences, so an agent can trust a `fid` from one run to
29
+ address the same call in the next. `flt` traces one selected frame: its
30
+ entry arguments and locals, each line's changed locals, and its return.
31
+ `exec` evaluates one statement at one addressed line visit inside a
32
+ selected frame, gated behind the `BULLDOGGER_EXEC=1` launch token the
33
+ launcher sets for the child process.
34
+ - **`record` and automatic replay retire.** Whole-run tracing measured 881x
35
+ to 2869x with debug.gem's recorder, and 34x to 51x with the shipped record
36
+ path; `frames` plus `flt` answer the same questions at re-execution cost,
37
+ paid only when invoked. `flt` and `exec` both refuse a target outside the
38
+ project's own source, naming the frames index, the gem source, and `probe`
39
+ as the alternatives.
40
+ - **The failure output's rerun line is the whole handoff.** `bulldogger
41
+ rerun:` prints the complete command for the failing test and seed; an
42
+ agent runs it under `frames` to reach a value whose producing method had
43
+ already returned. A green run still costs nothing extra, and re-execution
44
+ runs only when an agent invokes it.
45
+
46
+ ### Running bulldogger over its own suite
47
+
48
+ - **`Bulldogger::Instance`** separates the observing tool from the subject
49
+ under test. A single module singleton could not be both, because a suite's
50
+ own setup resets bulldogger for test isolation and destroyed any outer
51
+ observer. The module now delegates to a default instance, and
52
+ `Bulldogger::Minitest.instance=` and `Bulldogger::RSpec.instance=` accept
53
+ another one.
54
+ - `rake dogfood` covers the unit suite as well as acceptance.
55
+
56
+ ### Reaching the skill
57
+
58
+ - **`exe/bulldogger`** launches the re-execution verbs and answers two more
59
+ questions: `skill path` prints the location of the skill shipped with the
60
+ installed gem, and `version` prints the version.
61
+ - Evidence and probe evidence carry a `skill` key holding that path. The key
62
+ is omitted when the file is absent, because a path that does not resolve
63
+ costs a reader more than a missing one.
64
+ - The skill teaches what the files cannot: that an absent local means the run
65
+ could not see it, that a probe reporting zero calls was blind rather than
66
+ idle, and how to walk a trace from a symptom down to the value's origin.
67
+
68
+ ### Cost
69
+
70
+ - Redaction matches one union pattern instead of nine separate ones, checked
71
+ against 26 boundary names and 2,000 random names. Every capture path
72
+ shares the same redactor: failure snapshots, `probe`, `flt`, and `exec`.
73
+ - A failure snapshot still costs 44.126 microseconds for each raise-and-rescue
74
+ cycle, and a green test that raises nothing still costs nothing measurable.
75
+ `frames`, `preflight`, `flt`, and `exec` start a new process only when an
76
+ agent runs them; `probe` runs only around a block it selects.
77
+
78
+ ### Documentation
79
+
80
+ - `README.ja.md` translates the README. `README.md` stays the source of the
81
+ claims.
82
+ - `docs/design-decisions.md` records the observation limits found while
83
+ building this: `Coverage` cannot see lines under a `TracePoint` callback,
84
+ and neither can a probe, which is why bulldogger cannot probe its own
85
+ serialization path.
86
+
87
+ ## 0.2.0
88
+
89
+ ### A failing test now leads to the value that caused it
90
+
91
+ `0.1.0` could not do that for the most ordinary failure there is. When an
92
+ assertion fails, the method that produced the wrong value has already
93
+ returned, so the snapshot held the test framework and the test body and no
94
+ application code. Measured against two real gems, it found nothing.
95
+
96
+ - **replay**: bulldogger re-runs the one failing test in a child process
97
+ under full recording, and the trace holds the producing call with its
98
+ arguments and its return. The child keeps the parent suite's result
99
+ untouched, and a green run replays nothing. Evidence gains `replay` and
100
+ `replay_reproduced`.
101
+ - **Replay runs only when it can add something.** A propagating exception
102
+ leaves the raising method on the stack with its locals, so the snapshot
103
+ already answers and no second run happens. Evidence records that choice
104
+ in `replay_skipped_reason` rather than staying silent. `replay_on_failure`
105
+ accepts `true`, `:always`, and `false`; `BULLDOGGER_REPLAY` accepts `0`,
106
+ `1`, and `always`.
107
+ - **The failure output says which file to read and why.** The line worth
108
+ opening carries a short clause, so the first step needs nothing else.
109
+ Four states each say what they hold, including a replay whose child
110
+ passed, which shows a passing run rather than the failure.
111
+
112
+ ### Running bulldogger over its own suite
113
+
114
+ - **`Bulldogger::Instance`** separates the observing tool from the subject
115
+ under test. A single module singleton could not be both, because a suite's
116
+ own setup resets bulldogger for test isolation and destroyed any outer
117
+ observer. The module now delegates to a default instance, and
118
+ `Bulldogger::Minitest.instance=` and `Bulldogger::RSpec.instance=` accept
119
+ another one.
120
+ - `rake dogfood` covers the unit suite as well as acceptance.
121
+
122
+ ### Reaching the skill
123
+
124
+ - **`exe/bulldogger`** answers two questions: `skill path` prints the
125
+ location of the skill shipped with the installed gem, and `version` prints
126
+ the version.
127
+ - Evidence, probe evidence, and record headers carry a `skill` key holding
128
+ that path. The key is omitted when the file is absent, because a path that
129
+ does not resolve costs a reader more than a missing one.
130
+ - The skill teaches what the files cannot: that an absent local means the run
131
+ could not see it, that a probe reporting zero calls was blind rather than
132
+ idle, and how to walk a trace from a symptom down to the value's origin.
133
+
134
+ ### Cost
135
+
136
+ - Redaction matches one union pattern instead of nine separate ones, which
137
+ took value capture from about 41x to between 34x and 37x. Redaction was
138
+ about half the cost of serializing one local, and almost no name matches
139
+ any pattern.
140
+
141
+ ### Documentation
142
+
143
+ - `README.ja.md` translates the README. `README.md` stays the source of the
144
+ claims.
145
+ - `docs/design-decisions.md` records the observation limits found while
146
+ building this: `Coverage` cannot see lines under a `TracePoint` callback,
147
+ and neither can a probe, which is why bulldogger cannot probe its own
148
+ serialization path.
149
+
150
+ ## 0.1.0
151
+
152
+ First release. A failing Ruby test writes a JSON snapshot of its own failure
153
+ and names the file in its output, with `probe` and `record` as explicit verbs
154
+ for targeted and full observation.
data/README.ja.md ADDED
@@ -0,0 +1,389 @@
1
+ # bulldogger
2
+
3
+ このファイルは `README.md` の翻訳であり、内容が一致しない場合は `README.md` を正とします。
4
+
5
+ bulldogger は Ruby のテスト失敗を、コーディングエージェント向けの構造化された証拠として書き出します。
6
+ 各 JSON ファイルには、例外、バックトレース、取得したフレームの値が含まれます。
7
+ 失敗出力には、そのファイルの絶対パスが示されます。
8
+
9
+ 開発とスナップショットの計測には Ruby 4.0.6 と debug 1.11.1 を使用しました。
10
+
11
+ ## インストール
12
+
13
+ テストグループに両方の gem を追加します。
14
+
15
+ ```ruby
16
+ gem "bulldogger", group: :test
17
+ gem "debug", group: :test
18
+ ```
19
+
20
+ `debug` gem は `DEBUGGER__.capture_frames` を提供し、bulldogger は各フレームのローカル変数を取得できます。
21
+ Ruby は `debug` を bundled gem として配布しています。
22
+ Bundler はアプリケーションの Gemfile に `debug` が含まれる場合に限って、この gem を利用可能にします。
23
+
24
+ `debug` がない場合、bulldogger は例外が発生したフレームのローカル変数を記録します。
25
+ 残りのフレームについては、ファイル、行、ラベルのデータを記録します。
26
+
27
+ デフォルトブランチからエージェントホストへ skill をインストールします。
28
+
29
+ ```sh
30
+ gh skill install meganemura/bulldogger
31
+ ```
32
+
33
+ バージョンをそろえる必要がある場合は、インストール済みの gem と一致するコピーを使います。
34
+
35
+ ```sh
36
+ bulldogger skill path
37
+ ```
38
+
39
+ 最初のコマンドはエージェントホストに skill をインストールし、2 番目のコマンドは一致する gem 内のコピーを表示します。
40
+
41
+ CLI には次のサブコマンドがあります。
42
+
43
+ ```text
44
+ bulldogger frames -- command...
45
+ bulldogger preflight -- command...
46
+ bulldogger flt 'path:method#k' [--index path] -- command...
47
+ bulldogger exec 'path:method#k' --line N [--visit K] --statement text [--index path] -- command...
48
+ bulldogger skill path
49
+ bulldogger version
50
+ bulldogger --version
51
+ ```
52
+
53
+ フレームワーク用のエントリーポイントを 1 つ追加します。
54
+
55
+ Minitest では、次の行を `test_helper.rb` に追加します。
56
+
57
+ ```ruby
58
+ require "bulldogger/minitest"
59
+ ```
60
+
61
+ RSpec では、次の行を `spec_helper.rb` に追加します。
62
+
63
+ ```ruby
64
+ require "bulldogger/rspec"
65
+ ```
66
+
67
+ 各エントリーポイントは取得を開始し、失敗した各テストを記録します。
68
+ テストスイートが終了すると、実行インデックスを完成させます。
69
+
70
+ ## 独立したインスタンスの使用
71
+
72
+ `Bulldogger` モジュールは API を `Bulldogger.default` に委譲します。
73
+ `Bulldogger.start` や `Bulldogger.probe` などの既存の呼び出しは、このデフォルトインスタンスを使います。
74
+
75
+ 2 つの取得ライフサイクルを同時に動かす必要がある場合は、別のインスタンスを作成します。
76
+
77
+ ```ruby
78
+ observer = Bulldogger::Instance.new
79
+ observer.start
80
+ ```
81
+
82
+ 各インスタンスは、設定、取得の購読、実行、証拠の状態を所有します。
83
+ モジュールのファサードが提供する失敗と probe の各メソッドも利用できます。
84
+
85
+ インテグレーションには、デフォルトの代わりに指定したインスタンスを使えます。
86
+
87
+ ```ruby
88
+ Bulldogger::Minitest.instance = observer
89
+ Bulldogger::RSpec.instance = observer
90
+ ```
91
+
92
+ テストスイートが始まる前にインスタンスを指定します。
93
+ この分離により、テストのセットアップがデフォルトインスタンスを置き換えても、外側のオブザーバーは動作を続けられます。
94
+
95
+ ## コストモデル
96
+
97
+ 常時有効な処理は、失敗スナップショットを取得します。
98
+ 例外が発生しない green テストでは処理を行いません。
99
+ 監視した例外 1 件には microseconds 単位のコストがかかります。
100
+
101
+ スナップショットのベンチマークには Ruby 4.0.6 を使いました。
102
+ raise と rescue の 1 サイクルあたり 44.126 microseconds を計測しました。
103
+ そのうちフレーム取得には 1.288 microseconds がかかりました。
104
+
105
+ 再実行は明示的です。
106
+ `frames`、`preflight`、`flt`、`exec` は、ユーザーが実行したときだけ新しいプロセスを開始します。
107
+ `probe` も、ユーザーが選んだブロックの周囲でだけ動作します。
108
+
109
+ ## 失敗スナップショットから開始
110
+
111
+ 次のコマンドから、この出力を得ました。
112
+
113
+ ```sh
114
+ bundle exec ruby -Ilib test/fixtures/minitest_red/red_test.rb --seed 12345
115
+ ```
116
+
117
+ ```text
118
+ 1) Error:
119
+ RedTest#test_deep_raise:
120
+ ArgumentError: expected 3 to equal the sum of [1, 2, 3]
121
+ test/fixtures/minitest_red/app.rb:9:in 'Order.total'
122
+ test/fixtures/minitest_red/red_test.rb:20:in 'RedTest#test_deep_raise'
123
+ bulldogger evidence: /home/you/project/tmp/bulldogger/run-20260831-192835-69510/001-RedTest-test_deep_raise.json (raising method is in these frames)
124
+ bulldogger rerun: bundle exec ruby -Itest test/fixtures/minitest_red/red_test.rb -n /\\Atest_deep_raise\\z/ --seed 12345
125
+ ```
126
+
127
+ 括弧内の案内がある行のパスを開きます。
128
+ このファイルには、1 件の失敗と取得した実行時の値が含まれます。
129
+ rerun 行には、そのテストと seed に対する完全なコマンドが含まれます。
130
+ bulldogger は、このコマンドを自動では実行しません。
131
+
132
+ 各実行では次の配置を使います。
133
+
134
+ ```text
135
+ tmp/bulldogger/
136
+ latest -> run-20260829-100406-58231
137
+ run-20260829-100406-58231/
138
+ 001-RedTest-test_deep_raise.json
139
+ 002-RedTest-test_assertion_failure.json
140
+ index.json
141
+ ```
142
+
143
+ [`bulldogger` skill](skills/bulldogger/SKILL.md) は、エージェントがこれらのファイルを調べる方法を説明します。
144
+ [証拠スキーマ](docs/evidence-schema.md)は、すべてのフィールドと取得モードを定義します。
145
+
146
+ ## frames による分離実行のインデックス作成
147
+
148
+ rerun コマンドを `frames` の下で実行します。
149
+
150
+ ```sh
151
+ bulldogger frames -- bundle exec ruby -Itest test/fixtures/frames/minitest_frames_test.rb --seed 12345
152
+ ```
153
+
154
+ コマンドはインデックスのパスと子プロセスの結果を表示します。
155
+
156
+ ```text
157
+ bulldogger frames: /home/you/project/tmp/bulldogger/frames-69833.jsonl
158
+ bulldogger result: pass (exit 0)
159
+ ```
160
+
161
+ インデックスは各呼び出しにフレーム識別子 `fid` を付けます。
162
+ 形式は `path:method#k` です。
163
+ 数値は、テスト区間内にある同じメソッドの呼び出しを数えます。
164
+ インデックスには、アプリケーション、フレームワーク、gem のフレームが含まれます。
165
+
166
+ ## preflight による分離再実行の検証
167
+
168
+ `flt` または `exec` の前に `preflight` を実行します。
169
+
170
+ ```sh
171
+ bulldogger preflight -- bundle exec ruby -Itest test/fixtures/frames/minitest_frames_test.rb --seed 12345
172
+ ```
173
+
174
+ 同じコマンドを別々のプロセスで 2 回実行します。
175
+ 2 つのアプリケーションフレーム列を比較します。
176
+
177
+ ```text
178
+ bulldogger preflight: deterministic (app frames: 3)
179
+ bulldogger preflight indexes: /home/you/project/tmp/bulldogger/frames-70427.jsonl /home/you/project/tmp/bulldogger/frames-70432.jsonl
180
+ ```
181
+
182
+ preflight が `deterministic` を表示した場合に限って、`flt` または `exec` を使います。
183
+ どちらの動詞もアプリケーションのフレーム識別子だけを受け付けます。
184
+
185
+ ## flt による 1 フレームのトレース
186
+
187
+ アプリケーションの `fid` と同じ分離コマンドを渡します。
188
+
189
+ ```sh
190
+ bulldogger flt 'test/fixtures/flt/minitest_flt_test.rb:branchy#1' -- bundle exec ruby -Itest test/fixtures/flt/minitest_flt_test.rb --seed 12345
191
+ ```
192
+
193
+ ```text
194
+ bulldogger flt: /home/you/project/tmp/bulldogger/flt-77206.jsonl
195
+ bulldogger result: pass (exit 0)
196
+ ```
197
+
198
+ トレースには、フレーム開始、行ごとの変更、raise、return が含まれます。
199
+ 新しいローカル変数は `new` に、更新は `changed` に記録します。
200
+ スコープを外れたローカル変数は `out_of_scope` に列挙します。
201
+ ループ中間の反復は `skipped_iterations` レコードにまとめます。
202
+
203
+ `--index path` は、インデックスと再実行に同じコード状態マーカーを要求します。
204
+
205
+ ## exec による 1 文の評価
206
+
207
+ アプリケーションフレーム内の 1 回の行訪問を指定します。
208
+
209
+ ```sh
210
+ bulldogger exec 'test/fixtures/exec/minitest_exec_test.rb:threshold#1' --line 9 --statement 'binding.local_variable_set(:result, 10)' -- bundle exec ruby -Itest test/fixtures/exec/minitest_exec_test.rb --seed 12345 -n test_injection_can_change_the_outcome
211
+ ```
212
+
213
+ ```text
214
+ bulldogger exec: /home/you/project/tmp/bulldogger/exec-77267.jsonl
215
+ bulldogger value: 10
216
+ bulldogger result: pass (exit 0)
217
+ ```
218
+
219
+ `exec` は選択したフレームの binding で文を評価します。
220
+ デフォルトでは、その行の最初の訪問を選びます。
221
+ 後の訪問には `--visit K` を使います。
222
+ launcher は必要な `BULLDOGGER_EXEC=1` token を子プロセスへ渡します。
223
+ `--index path` は、同じコード状態マーカーを要求します。
224
+
225
+ 文はテストの動作を変え、副作用を発生させる可能性があります。
226
+ 変更後の結果を証拠に使う前に、結果ファイルを読みます。
227
+
228
+ ## RSpec の乱数 seed
229
+
230
+ RSpec は seed を例の順序に使います。
231
+ RSpec は `Kernel.srand` を呼びません。
232
+ 例が `rand` を呼ぶ場合は、`RSpec.configure` 内に次の行を追加します。
233
+
234
+ ```ruby
235
+ config.before(:suite) { Kernel.srand config.seed }
236
+ ```
237
+
238
+ この行により、表示された rerun seed で乱数値を再現できます。
239
+ bulldogger はアプリケーションに代わって `Kernel.srand` を呼びません。
240
+
241
+ ## probe によるメソッドの指定
242
+
243
+ 対象名を指定し、関連するテストまたは処理を囲みます。
244
+
245
+ ```ruby
246
+ before_path = Bulldogger.probe("Billing::Invoice#amount") do
247
+ run_related_test
248
+ end
249
+ ```
250
+
251
+ 証拠は、引数と戻り値のクラス、`nil` 値、例外による終了、呼び出し元を要約します。
252
+ デフォルトでは最初の 10 サンプルをシリアライズし、すべての呼び出しを数えます。
253
+
254
+ 変更の前後で probe を実行し、2 つのファイルを比較します。
255
+
256
+ ```ruby
257
+ result = Bulldogger.probe_compare(before_path, after_path)
258
+ result.fetch("identical")
259
+ ```
260
+
261
+ `identical` の値が `true` なら、比較した動作は同じです。
262
+ 比較の対象は、呼び出し回数、クラス、`nil` の数、例外による終了、パラメーター、呼び出し元、正規化したサンプルです。
263
+
264
+ 次の抜粋は、生成した probe ファイルから得ました。
265
+
266
+ ```json
267
+ {
268
+ "kind": "probe",
269
+ "targets": ["ProseSample#amount"],
270
+ "methods": {
271
+ "ProseSample#amount": {
272
+ "calls": 3,
273
+ "raised_exits": 1,
274
+ "returns": {
275
+ "classes": {"Integer": 1, "NilClass": 1},
276
+ "nil_count": 1,
277
+ "samples": [{"value": "21"}, {"value": "nil"}]
278
+ },
279
+ "raised": {"ArgumentError": 1},
280
+ "callers": {"-e:1:in 'block in <main>'": 3}
281
+ }
282
+ },
283
+ "limits": {"max_samples": 10, "max_value_length": 200}
284
+ }
285
+ ```
286
+
287
+ ## コスト
288
+
289
+ `TracePoint(:raise)` は、アプリケーションコードが rescue する例外を含む、発生したすべての例外を監視します。
290
+ この計測には Ruby 4.0.6 を使用しました。
291
+ 各条件を 3 回実行し、表には各中央値を示します。
292
+
293
+ | 条件 | bulldogger なし | bulldogger あり | 比率 |
294
+ |---|---:|---:|---:|
295
+ | 例外が発生しない 2,000,000 回の no-op 反復 | 0.0423s | 0.0424s | 1.00x |
296
+ | 10,000 回の raise と rescue | 0.0055s | 0.4468s | 81.14x |
297
+ | ファイル出力を伴う 200 件の失敗記録 | 0.0001s | 0.0277s | 413.58x |
298
+
299
+ この計測では、2 番目の条件で例外 1 件あたり 44.126 microseconds かかりました。
300
+
301
+ 取得コストの内訳を次に示します。
302
+
303
+ | 段階 | 例外 1 件あたりの追加コスト |
304
+ |---|---:|
305
+ | `TracePoint(:raise)` を購読 | 0.136 microseconds |
306
+ | `DEBUGGER__.capture_frames` を呼び出し | 1.288 microseconds |
307
+ | シリアライズ、秘匿、リングへの挿入 | 23.220 microseconds |
308
+
309
+ この計測ではフレーム取得のコストは小さく、その後の処理が計測コストの大部分を占めます。
310
+
311
+ このテストでは、例外が発生しない green のテストスイートに計測可能なオーバーヘッドはありませんでした。
312
+ green のテストスイートでも例外を発生させて rescue する場合があり、その各例外には取得コストがかかります。
313
+
314
+ ### 明示的な動詞のコスト
315
+
316
+ `probe` では対象メソッドの呼び出し 1 回あたり 1461.5 ns を計測しました。
317
+ フレームの gate では、対象外の呼び出し 1 件あたり約 105 ns を計測しました。
318
+
319
+ `flt` の line event には約 0.5 microseconds の固定コストがあります。
320
+ 各ローカル変数の読み取りには、line event 1 件あたり約 0.1 microseconds が加わります。
321
+ 20 個のローカル変数と 10,000 件の line event を持つフレームには約 27 ms かかりました。
322
+
323
+ これらの計測には Ruby 4.0.6 と rubygems.org のテストスイートを使いました。
324
+ スイートには 4,925 件のテストがあり、seed 12345 と 1 worker を使いました。
325
+ 計測日は 2026-08-31 です。
326
+
327
+ ## bulldogger の無効化
328
+
329
+ 1 つのテストプロセスで取得と出力を無効にするには、`BULLDOGGER_DISABLE=1` を設定します。
330
+ `BULLDOGGER_DISABLED=1` は同じ動作をする別名です。
331
+
332
+ どちらかのスイッチを使うと、起動処理は `TracePoint(:raise)` の購読前に戻ります。
333
+ 証拠を書かず、実行ディレクトリを作らず、失敗に証拠の行を追加しません。
334
+ テストの終了コードと失敗数は変わりません。
335
+ 受け入れテストは、Minitest と RSpec についてこの動作を確認しています。
336
+
337
+ rescue を多用する green のテストスイートでは、bulldogger を無効にした状態で 1.00x を計測しました。
338
+
339
+ ## 環境変数
340
+
341
+ 次の環境変数は、子テストプロセスを設定します。
342
+
343
+ | 変数 | 受け付ける値 | デフォルトと効果 |
344
+ |---|---|---|
345
+ | `BULLDOGGER_DISABLE` | `1` | デフォルトでは取得が有効です。`1` は取得と出力を無効にします。 |
346
+ | `BULLDOGGER_DISABLED` | `1` | `BULLDOGGER_DISABLE` の別名です。 |
347
+ | `BULLDOGGER_OUTPUT_DIR` | 空でないパス | デフォルトは作業ディレクトリからの相対パス `tmp/bulldogger` です。 |
348
+ | `BULLDOGGER_FRAME_SOURCE` | `capture_frames` または `degraded` | デフォルトでは自動選択します。 |
349
+
350
+ ## シークレットと上限
351
+
352
+ 取得した値にはシークレットが含まれる可能性があります。
353
+ bulldogger は値に対して `inspect` を呼ぶ前に、各ローカル変数名を検査します。
354
+ 一致するローカル変数は `{"redacted": true, "reason": "name"}` となり、`value` フィールドを持ちません。
355
+
356
+ デフォルトのパターンは、大文字と小文字を区別せずに次の名前と一致します。
357
+
358
+ - `password`、`passwd`、`pass`
359
+ - `secret`、`token`
360
+ - `api_key`、`api-key`
361
+ - 単語としての `key`
362
+ - `credential`、`auth`、`session`、`cookie`
363
+
364
+ 名前が曖昧な場合、パターンは秘匿する側へ寄せます。
365
+ たとえば `/auth/i` は `author` と `authorized` にも一致します。
366
+ アプリケーションは `Bulldogger.config.redact_patterns` を独自の正規表現で置き換えられます。
367
+ Bulldogger は redactor を構築するときに、これらのパターンを 1 つの union にコンパイルします。
368
+ 元の配列をその場で変更しても、既存の redactor は変わりません。
369
+ そのパターンを使う取得またはトレースのセッションを Bulldogger が構築する前に、新しいパターン配列を割り当てます。
370
+
371
+ bulldogger は Hash をレンダリングするときにキーも検査します。
372
+ 一致するキーのレンダリング値は文字列 `"[REDACTED]"` になります。
373
+
374
+ デフォルトでは 20 フレームと、各フレームの 50 ローカル変数を保持します。
375
+ レンダリングした各値は 200 文字を保持し、各 Array または Hash は 10 要素を保持します。
376
+ 証拠ファイルは、省略または切り詰めたデータを示します。
377
+
378
+ ## バージョン 0.2 の範囲
379
+
380
+ Version 0.2 は、失敗スナップショット、probe、フレームインデックス、決定性検査、フレーム生存期間トレース、文の評価、独立したインスタンスを提供します。
381
+ JSON の証拠と JSONL の成果物を書き出します。
382
+ 重い取得は、明示的な動詞からだけ開始します。
383
+
384
+ [設計判断](docs/design-decisions.md)は、証拠モデルと計測コストを説明します。
385
+
386
+ ## ライセンス
387
+
388
+ MIT です。
389
+ `LICENSE` を参照してください。