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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +154 -0
- data/README.ja.md +389 -0
- data/README.md +193 -69
- data/docs/design-decisions.md +282 -3
- data/docs/evidence-schema.md +153 -5
- data/docs/maintenance.md +26 -9
- data/exe/bulldogger +89 -0
- data/lib/bulldogger/application_frames.rb +36 -0
- data/lib/bulldogger/capture.rb +15 -1
- data/lib/bulldogger/code_state.rb +40 -0
- data/lib/bulldogger/collector_environment.rb +16 -0
- data/lib/bulldogger/config.rb +3 -5
- data/lib/bulldogger/evidence.rb +15 -4
- data/lib/bulldogger/exec.rb +86 -0
- data/lib/bulldogger/exec_collector.rb +142 -0
- data/lib/bulldogger/execution_target.rb +112 -0
- data/lib/bulldogger/failure_output.rb +32 -0
- data/lib/bulldogger/flt.rb +54 -0
- data/lib/bulldogger/flt_collector.rb +186 -0
- data/lib/bulldogger/frames.rb +76 -0
- data/lib/bulldogger/frames_collector.rb +166 -0
- data/lib/bulldogger/instance.rb +97 -0
- data/lib/bulldogger/integrations/minitest.rb +61 -21
- data/lib/bulldogger/integrations/rspec.rb +44 -13
- data/lib/bulldogger/preflight.rb +79 -0
- data/lib/bulldogger/probe/bucket.rb +8 -7
- data/lib/bulldogger/probe/comparator.rb +3 -2
- data/lib/bulldogger/probe/method_stats.rb +1 -1
- data/lib/bulldogger/probe/raise_tracker.rb +3 -3
- data/lib/bulldogger/probe/session.rb +7 -5
- data/lib/bulldogger/probe/writer.rb +11 -1
- data/lib/bulldogger/probe.rb +5 -4
- data/lib/bulldogger/redactor.rb +5 -3
- data/lib/bulldogger/rerun_command.rb +48 -0
- data/lib/bulldogger/skill.rb +26 -0
- data/lib/bulldogger/version.rb +1 -1
- data/lib/bulldogger.rb +33 -63
- data/skills/bulldogger/SKILL.md +133 -20
- data/skills/bulldogger/references/failure-evidence.md +2 -0
- data/skills/bulldogger/references/reexecution.md +149 -0
- metadata +26 -12
- data/docs/trace-schema.md +0 -118
- data/lib/bulldogger/record/session.rb +0 -334
- data/lib/bulldogger/record/sqlite_converter.rb +0 -86
- data/lib/bulldogger/record/writer.rb +0 -67
- data/lib/bulldogger/record.rb +0 -51
- data/skills/bulldogger/references/record.md +0 -28
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 72ea09764a4ece080b0430f55231aa772b451c37174604a2c6352077d36de9e2
|
|
4
|
+
data.tar.gz: d4e01f7ea18e1d7c6e5183dbe8bd7a6e800c4e6c11f0480933db7268d4429eb3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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` を参照してください。
|