lumitrace 0.5.0 → 0.6.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/README.md +2 -1
- data/docs/ai-help.md +10 -5
- data/docs/ai-schema.md +12 -3
- data/docs/spec.md +40 -1
- data/docs/supported_syntax.md +7 -0
- data/docs/tutorial.ja.md +63 -31
- data/docs/tutorial.md +63 -0
- data/lib/lumitrace/ai_docs.rb +21 -1
- data/lib/lumitrace/generate_resulted_html.rb +4 -2
- data/lib/lumitrace/help_manifest.rb +9 -4
- data/lib/lumitrace/record_instrument.rb +48 -5
- data/lib/lumitrace/schema_manifest.rb +13 -3
- data/lib/lumitrace/version.rb +1 -1
- data/lib/lumitrace.rb +5 -3
- data/runv/index.html +55 -10
- data/test/test_lumitrace.rb +158 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bbe02bbc074d7d4ef9a4279ea426500296ea6aca0b560c146140f5e143ea4f8f
|
|
4
|
+
data.tar.gz: 6d0101d97cf784392d3f62a90edfc29a244b6a6582da9b15800447d8b3ad9175
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bb1c57562f18e6827bcf0f425e45ab098182553ba9f44f311f4302f8c919e133903a2dbe97695c37914ad01415709deb68445d6109ccaa1f87a3979bb5dd307a
|
|
7
|
+
data.tar.gz: 8f863f58547b5e5ab455b07857919383e2784d18dc3c32486b3ff94e84d799deae180938d4c6ef3cb94bac0a8bd8689c5caf0b9e6b11f1dc52ed8f6bcb81c936
|
data/README.md
CHANGED
data/docs/ai-help.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Lumitrace Help
|
|
4
4
|
|
|
5
|
-
- Version: 0.
|
|
5
|
+
- Version: 0.6.1
|
|
6
6
|
- Help version: 1
|
|
7
7
|
- Primary JSON entrypoint: `lumitrace help --format json`
|
|
8
8
|
- Schema JSON entrypoint: `lumitrace schema --format json`
|
|
@@ -37,11 +37,16 @@
|
|
|
37
37
|
## Key Options
|
|
38
38
|
- `--collect-mode` (default="last"; values=last,types,history)
|
|
39
39
|
- `--max-samples` (default=3; Used by history mode.)
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
40
|
+
- `-j, --json[=PATH]` (Emit JSON output.)
|
|
41
|
+
- `-h, --html[=PATH]` (Emit HTML output.)
|
|
42
|
+
- `-t, --text[=PATH]` (Emit text output.)
|
|
43
|
+
- `-g, --git-diff[=MODE]` (Restrict instrumentation to diff hunks.)
|
|
43
44
|
- `--range SPEC` (repeatable=true; Restrict instrumentation to file ranges.)
|
|
44
|
-
- `--git-diff
|
|
45
|
+
- `--git-diff-context N` (Expand diff hunks by +/-N lines.)
|
|
46
|
+
- `--git-cmd PATH` (Git executable for diff.)
|
|
47
|
+
- `--git-diff-no-untracked` (Exclude untracked files from diff.)
|
|
48
|
+
- `--root PATH` (Root directory for instrumentation.)
|
|
49
|
+
- `--verbose[=LEVEL]` (Verbose logs to stderr (level 1-3).)
|
|
45
50
|
|
|
46
51
|
## Examples
|
|
47
52
|
- `lumitrace --collect-mode history --max-samples 5 -j app.rb`
|
data/docs/ai-schema.md
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
# Lumitrace JSON Schema
|
|
4
4
|
|
|
5
|
-
- Version: 0.
|
|
5
|
+
- Version: 0.6.1
|
|
6
6
|
- Schema version: 1
|
|
7
|
-
- Top level:
|
|
7
|
+
- Top level: object
|
|
8
|
+
- `version` (integer) - Schema version number.
|
|
9
|
+
- `events` (array of event) - Traced expression events.
|
|
10
|
+
- `coverage` (array of coverage_entry) - Per-file coverage summary.
|
|
8
11
|
|
|
9
12
|
## Common Event Fields
|
|
10
13
|
- `file` (string, required) - Absolute source path.
|
|
@@ -14,9 +17,15 @@
|
|
|
14
17
|
- `end_col` (integer, required)
|
|
15
18
|
- `kind` (string, required)
|
|
16
19
|
- `name` (string|null, optional) - Present for kind=arg.
|
|
17
|
-
- `total` (integer, required) - Execution count.
|
|
20
|
+
- `total` (integer, required) - Execution count. 0 means the expression was never executed (uncovered).
|
|
18
21
|
- `types` (object, required) - Ruby class name => observed count.
|
|
19
22
|
|
|
23
|
+
## Coverage Entry Fields
|
|
24
|
+
- `file` (string, required) - Absolute source path.
|
|
25
|
+
- `total_lines` (integer, required) - Number of traced expression lines.
|
|
26
|
+
- `covered_lines` (integer, required) - Lines with total > 0.
|
|
27
|
+
- `coverage_percent` (float, required) - Covered / total * 100, rounded to 1 decimal.
|
|
28
|
+
|
|
20
29
|
## Value Summary Fields
|
|
21
30
|
- `type` (string, required) - Ruby class name.
|
|
22
31
|
- `preview` (string, required) - Value preview string (inspect-based).
|
data/docs/spec.md
CHANGED
|
@@ -167,7 +167,21 @@ Lumitrace instruments Ruby source code at load time (via `RubyVM::InstructionSeq
|
|
|
167
167
|
|
|
168
168
|
### Output JSON
|
|
169
169
|
|
|
170
|
-
`lumitrace_recorded.json`
|
|
170
|
+
`lumitrace_recorded.json` is a JSON object with the following top-level structure:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"version": 1,
|
|
175
|
+
"events": [ ... ],
|
|
176
|
+
"coverage": [ ... ]
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
- `version`: Schema version (currently `1`).
|
|
181
|
+
- `events`: Array of trace event entries (see below).
|
|
182
|
+
- `coverage`: Array of per-file coverage summaries (see below).
|
|
183
|
+
|
|
184
|
+
#### Event entries
|
|
171
185
|
|
|
172
186
|
`collect_mode=last` (default):
|
|
173
187
|
|
|
@@ -226,6 +240,24 @@ Lumitrace instruments Ruby source code at load time (via `RubyVM::InstructionSeq
|
|
|
226
240
|
- `last_value`: summary of the last observed value: `{ type, preview }` (+ `length` only when truncated).
|
|
227
241
|
- `types`: observed Ruby class counts (class name => count).
|
|
228
242
|
- `sampled_values`: retained sample (last N values) of summary objects (`{ type, preview }` + optional `length`) in `history` mode.
|
|
243
|
+
- `total`: total execution count. When `0`, the expression was instrumented but never executed (uncovered).
|
|
244
|
+
|
|
245
|
+
#### Coverage summary
|
|
246
|
+
|
|
247
|
+
Each entry in the `coverage` array summarizes line-level coverage for one file:
|
|
248
|
+
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"file": "/path/to/file.rb",
|
|
252
|
+
"total_lines": 18,
|
|
253
|
+
"covered_lines": 12,
|
|
254
|
+
"coverage_percent": 66.7
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
- `total_lines`: number of lines that contain at least one instrumented expression.
|
|
259
|
+
- `covered_lines`: number of those lines where at least one expression has `total > 0`.
|
|
260
|
+
- `coverage_percent`: `covered_lines / total_lines * 100`, rounded to one decimal place.
|
|
229
261
|
|
|
230
262
|
## CLI
|
|
231
263
|
|
|
@@ -274,8 +306,10 @@ lumitrace [options] exec CMD [args...]
|
|
|
274
306
|
- `Mode: types (type counts)`
|
|
275
307
|
- `Mode: history (last N sample[s])`
|
|
276
308
|
- In `history`, `N` uses configured `max_samples` when available; otherwise it is inferred from the loaded events.
|
|
309
|
+
- When available, the page header also shows the executed command as `Command: ...`.
|
|
277
310
|
- Each file is shown in its own section.
|
|
278
311
|
- When multiple files are present, the HTML UI shows a left file tree and a single-file viewer on the right.
|
|
312
|
+
- Directory nodes in the file tree are expanded by default.
|
|
279
313
|
- Selecting a file in the tree switches the visible file without reloading the page.
|
|
280
314
|
- The selected file is reflected in the URL hash as `#file=...` (using the rendered file path label) so links can open a specific file view.
|
|
281
315
|
- Clicking a line number updates the URL hash to include the selected line (for example `#file=lib/foo.rb&line=42`).
|
|
@@ -286,8 +320,10 @@ lumitrace [options] exec CMD [args...]
|
|
|
286
320
|
- Hovering the icon shows recorded values.
|
|
287
321
|
- Only the last 3 values are shown in the tooltip as `value (Type)`; additional values are summarized as `... (+N more)`.
|
|
288
322
|
- Tooltip is scrollable horizontally for long values.
|
|
323
|
+
- Tooltip is shown above the marker icon (to avoid covering the mouse cursor).
|
|
289
324
|
- When ranges are used, skipped sections are shown as `...` in the line-number column.
|
|
290
325
|
- Lines where all instrumentable expressions are unexecuted are highlighted in a light red. If a line mixes executed and unexecuted expressions, only the unexecuted expressions are highlighted.
|
|
326
|
+
- The page footer includes an attribution link to the Lumitrace site and the Lumitrace version used to generate the report.
|
|
291
327
|
|
|
292
328
|
### HTML Payload Schema (`v1`)
|
|
293
329
|
|
|
@@ -332,6 +368,8 @@ lumitrace [options] exec CMD [args...]
|
|
|
332
368
|
- Human-readable label shown in the HTML header.
|
|
333
369
|
- `meta.max_samples`:
|
|
334
370
|
- Effective/inferred max samples for `history`; may be `null`.
|
|
371
|
+
- `meta.command`:
|
|
372
|
+
- Optional command line shown in the HTML header (for CLI-generated reports).
|
|
335
373
|
- `files[]`:
|
|
336
374
|
- One entry per rendered source file.
|
|
337
375
|
- `files[].path`:
|
|
@@ -367,4 +405,5 @@ lumitrace [options] exec CMD [args...]
|
|
|
367
405
|
|
|
368
406
|
- Requires `RubyVM::InstructionSequence.translate` support in the Ruby build.
|
|
369
407
|
- Instrumentation is for debugging; semantics may change for unusual edge cases.
|
|
408
|
+
- Expressions inside `defined?(...)` are intentionally not instrumented to preserve `defined?` semantics.
|
|
370
409
|
- Tool does not attempt to preserve file encoding comments.
|
data/docs/supported_syntax.md
CHANGED
|
@@ -112,6 +112,13 @@ These are intentionally skipped to keep output valid Ruby:
|
|
|
112
112
|
```
|
|
113
113
|
- The implicit `token` read is **not** instrumented.
|
|
114
114
|
|
|
115
|
+
- Expressions inside `defined?(...)`:
|
|
116
|
+
- Example:
|
|
117
|
+
```ruby
|
|
118
|
+
defined?(foo + bar)
|
|
119
|
+
```
|
|
120
|
+
- The operand expression (`foo + bar`) is **not** instrumented, to preserve `defined?` semantics.
|
|
121
|
+
|
|
115
122
|
## Rationale
|
|
116
123
|
|
|
117
124
|
All skips above correspond to syntactic positions where wrapping the token with
|
data/docs/tutorial.ja.md
CHANGED
|
@@ -131,9 +131,72 @@ HTML について:
|
|
|
131
131
|
- 記録対象の式がすべて未実行の行は薄い赤で表示され、混在行では未実行の式のみ薄赤になります。
|
|
132
132
|
- 範囲指定時の省略は行番号欄に `...` が入ります。
|
|
133
133
|
- 複数ファイルが含まれる場合は、左側のファイルツリーで表示ファイルを切り替えられます。
|
|
134
|
+
- ファイルツリーのディレクトリはデフォルトで展開されています。
|
|
134
135
|
- ファイルツリーには、ファイルごとの行カバレッジが `(実行行数/式のある行数)` で表示されます。
|
|
135
136
|
- 選択中ファイルは URL ハッシュ(`#file=...`)に反映されるため、URL を共有するとそのファイル表示を開けます。
|
|
136
137
|
- 行番号をクリックすると、その行を含む URL ハッシュ(`#file=...&line=...`)に更新され、同じファイル・行を開けます。
|
|
138
|
+
- 移動時に URL ハッシュは自動更新されるため、専用の permalink ボタンはありません。
|
|
139
|
+
- CLI から生成した HTML では、ヘッダに `Command: ...` が表示されます。
|
|
140
|
+
- フッターには、HTML を生成した Lumitrace のバージョンが表示されます。
|
|
141
|
+
|
|
142
|
+
### AI と使う
|
|
143
|
+
|
|
144
|
+
テストが落ちたとき、Lumitrace で実行時の値を取得し、その結果を AI に渡すと効率的にデバッグできます。
|
|
145
|
+
|
|
146
|
+
**典型的なシナリオ:**
|
|
147
|
+
|
|
148
|
+
1. テストが失敗する
|
|
149
|
+
2. Lumitrace でテスト実行時の値を記録する
|
|
150
|
+
3. 結果を AI に渡して原因を分析してもらう
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# テストを lumitrace 付きで実行し、JSON を取得
|
|
154
|
+
lumitrace --collect-mode last -j exec rake test
|
|
155
|
+
|
|
156
|
+
# 結果の JSON を AI に渡す
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**CLAUDE.md / AGENTS.md に貼れるスニペット例:**
|
|
160
|
+
|
|
161
|
+
プロジェクトの `CLAUDE.md` や `AGENTS.md` に以下を追加すると、AI エージェントが Lumitrace を活用しやすくなります:
|
|
162
|
+
|
|
163
|
+
````markdown
|
|
164
|
+
## デバッグ(Lumitrace)
|
|
165
|
+
lumitrace は Ruby の各式の実行時の値を記録するツール。
|
|
166
|
+
テスト失敗時は `lumitrace help` を読んでから使うこと。
|
|
167
|
+
基本: `lumitrace -t exec rake test`
|
|
168
|
+
````
|
|
169
|
+
|
|
170
|
+
**段階的アプローチ(トークン節約):**
|
|
171
|
+
|
|
172
|
+
AI に読ませる前提なら、次の順番にすると効率が良いです。
|
|
173
|
+
|
|
174
|
+
1. まず型分布だけ取る(安く全体像を見る)
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
lumitrace --collect-mode types -j path/to/entry.rb
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
2. 次に最終値を見る(値の当たりを付ける)
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
lumitrace --collect-mode last -j path/to/entry.rb
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
3. 変化が必要な箇所だけ履歴を見る
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
lumitrace --collect-mode history --max-samples 5 -j path/to/entry.rb
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
4. 対象を絞る(トークン節約)
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
lumitrace --collect-mode last -j --range path/to/entry.rb:120-180 path/to/entry.rb
|
|
196
|
+
lumitrace --collect-mode last -j -g path/to/entry.rb
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
補助情報は `lumitrace help --format json` と `lumitrace schema --format json` で機械可読に取得できます。
|
|
137
200
|
|
|
138
201
|
### 範囲指定の例
|
|
139
202
|
|
|
@@ -298,37 +361,6 @@ GitHub Actions への追加手順(`LUMITRACE_GIT_DIFF` や Pages へのアッ
|
|
|
298
361
|
|
|
299
362
|
fork/exec の結果はデフォルトでマージされます。親プロセスが最終出力を行い、子プロセスは `LUMITRACE_RESULTS_DIR` に断片 JSON を保存します。
|
|
300
363
|
|
|
301
|
-
### AI と使う
|
|
302
|
-
|
|
303
|
-
AI に読ませる前提なら、次の順番にすると効率が良いです。
|
|
304
|
-
|
|
305
|
-
1. まず型分布だけ取る(安く全体像を見る)
|
|
306
|
-
|
|
307
|
-
```bash
|
|
308
|
-
lumitrace --collect-mode types -j path/to/entry.rb
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
2. 次に最終値を見る(値の当たりを付ける)
|
|
312
|
-
|
|
313
|
-
```bash
|
|
314
|
-
lumitrace --collect-mode last -j path/to/entry.rb
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
3. 変化が必要な箇所だけ履歴を見る
|
|
318
|
-
|
|
319
|
-
```bash
|
|
320
|
-
lumitrace --collect-mode history --max-samples 5 -j path/to/entry.rb
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
4. 対象を絞る(トークン節約)
|
|
324
|
-
|
|
325
|
-
```bash
|
|
326
|
-
lumitrace --collect-mode last -j --range path/to/entry.rb:120-180 path/to/entry.rb
|
|
327
|
-
lumitrace --collect-mode last -j -g path/to/entry.rb
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
補助情報は `lumitrace help --format json` と `lumitrace schema --format json` で機械可読に取得できます。
|
|
331
|
-
|
|
332
364
|
## 2. ライブラリとして使う
|
|
333
365
|
|
|
334
366
|
CLI を使わずアプリに組み込みたい場合はここから始めます。
|
data/docs/tutorial.md
CHANGED
|
@@ -131,9 +131,72 @@ HTML notes:
|
|
|
131
131
|
- Lines where all instrumentable expressions are unexecuted are shaded light red; mixed lines only shade the unexecuted expressions.
|
|
132
132
|
- When ranges are used, skipped sections are shown as `...` in the line-number column.
|
|
133
133
|
- When multiple files are included, use the left file tree to switch files.
|
|
134
|
+
- Directory nodes in the file tree are expanded by default.
|
|
134
135
|
- The file tree shows per-file line coverage as `(executed/expression-lines)`.
|
|
135
136
|
- The selected file is stored in the URL hash (`#file=...`), so copying the URL shares a direct link to that file view.
|
|
136
137
|
- Click a line number to update the URL with that line (`#file=...&line=...`) and open the same file+line later.
|
|
138
|
+
- The URL hash is updated automatically while navigating (there is no separate permalink button).
|
|
139
|
+
- The header also shows `Command: ...` for CLI-generated reports.
|
|
140
|
+
- The footer shows the Lumitrace version that generated the report.
|
|
141
|
+
|
|
142
|
+
### Using with AI agents
|
|
143
|
+
|
|
144
|
+
When a test fails, you can use Lumitrace to capture runtime values and pass the results to an AI for efficient debugging.
|
|
145
|
+
|
|
146
|
+
**Typical scenario:**
|
|
147
|
+
|
|
148
|
+
1. A test fails
|
|
149
|
+
2. Run the test with Lumitrace to record runtime values
|
|
150
|
+
3. Pass the results to an AI to analyze the root cause
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Run tests with lumitrace and get JSON output
|
|
154
|
+
lumitrace --collect-mode last -j exec rake test
|
|
155
|
+
|
|
156
|
+
# Pass the resulting JSON to your AI
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Snippet for CLAUDE.md / AGENTS.md:**
|
|
160
|
+
|
|
161
|
+
Add the following to your project's `CLAUDE.md` or `AGENTS.md` so AI agents can leverage Lumitrace:
|
|
162
|
+
|
|
163
|
+
````markdown
|
|
164
|
+
## Debugging (Lumitrace)
|
|
165
|
+
lumitrace is a tool that records runtime values of each Ruby expression.
|
|
166
|
+
When a test fails, read `lumitrace help` first, then use it.
|
|
167
|
+
Basic: `lumitrace -t exec rake test`
|
|
168
|
+
````
|
|
169
|
+
|
|
170
|
+
**Gradual approach (save tokens):**
|
|
171
|
+
|
|
172
|
+
When feeding results to an AI, this order is efficient:
|
|
173
|
+
|
|
174
|
+
1. Start with type distributions (cheap overview)
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
lumitrace --collect-mode types -j path/to/entry.rb
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
2. Then check last values (get a feel for the data)
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
lumitrace --collect-mode last -j path/to/entry.rb
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
3. Look at history only where changes are needed
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
lumitrace --collect-mode history --max-samples 5 -j path/to/entry.rb
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
4. Narrow the scope (save tokens)
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
lumitrace --collect-mode last -j --range path/to/entry.rb:120-180 path/to/entry.rb
|
|
196
|
+
lumitrace --collect-mode last -j -g path/to/entry.rb
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Machine-readable help is available via `lumitrace help --format json` and `lumitrace schema --format json`.
|
|
137
200
|
|
|
138
201
|
### Range example
|
|
139
202
|
|
data/lib/lumitrace/ai_docs.rb
CHANGED
|
@@ -70,7 +70,17 @@ module Lumitrace
|
|
|
70
70
|
lines << ""
|
|
71
71
|
lines << "- Version: #{data[:version]}"
|
|
72
72
|
lines << "- Schema version: #{data[:schema_version]}"
|
|
73
|
-
|
|
73
|
+
top = data[:json_top_level]
|
|
74
|
+
if top[:type] == "object" && top[:fields]
|
|
75
|
+
lines << "- Top level: #{top[:type]}"
|
|
76
|
+
top[:fields].each do |name, spec|
|
|
77
|
+
type_text = spec[:items] ? "#{spec[:type]} of #{spec[:items]}" : spec[:type].to_s
|
|
78
|
+
desc = spec[:description] ? " - #{spec[:description]}" : ""
|
|
79
|
+
lines << " - `#{name}` (#{type_text})#{desc}"
|
|
80
|
+
end
|
|
81
|
+
else
|
|
82
|
+
lines << "- Top level: #{top[:type]} of #{top[:items]}"
|
|
83
|
+
end
|
|
74
84
|
lines << ""
|
|
75
85
|
lines << "## Common Event Fields"
|
|
76
86
|
data[:event_common_fields].each do |name, spec|
|
|
@@ -79,6 +89,16 @@ module Lumitrace
|
|
|
79
89
|
desc = spec[:description] ? " - #{spec[:description]}" : ""
|
|
80
90
|
lines << "- `#{name}` (#{type_text}, #{req})#{desc}"
|
|
81
91
|
end
|
|
92
|
+
if data[:coverage_entry_fields]
|
|
93
|
+
lines << ""
|
|
94
|
+
lines << "## Coverage Entry Fields"
|
|
95
|
+
data[:coverage_entry_fields].each do |name, spec|
|
|
96
|
+
req = spec[:required] ? "required" : "optional"
|
|
97
|
+
type_text = Array(spec[:type]).join("|")
|
|
98
|
+
desc = spec[:description] ? " - #{spec[:description]}" : ""
|
|
99
|
+
lines << "- `#{name}` (#{type_text}, #{req})#{desc}"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
82
102
|
lines << ""
|
|
83
103
|
lines << "## Value Summary Fields"
|
|
84
104
|
data[:value_summary_fields].each do |name, spec|
|
|
@@ -25,7 +25,8 @@ module GenerateResultedHtml
|
|
|
25
25
|
abort "missing #{source_path}"
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
raw = JSON.parse(File.read(events_path))
|
|
29
|
+
raw_events = raw.is_a?(Hash) && raw.key?("events") ? raw["events"] : raw
|
|
29
30
|
src = File.read(source_path)
|
|
30
31
|
mode_info = resolve_mode_info(raw_events, collect_mode: collect_mode, max_samples: max_samples)
|
|
31
32
|
normalized_ranges = normalize_ranges(ranges)
|
|
@@ -654,7 +655,8 @@ module GenerateResultedHtml
|
|
|
654
655
|
end
|
|
655
656
|
|
|
656
657
|
def self.render_all(events_path, root: Dir.pwd, ranges_by_file: nil, collect_mode: nil, max_samples: nil, logger: nil, command_text: nil)
|
|
657
|
-
|
|
658
|
+
raw = JSON.parse(File.read(events_path))
|
|
659
|
+
raw_events = raw.is_a?(Hash) && raw.key?("events") ? raw["events"] : raw
|
|
658
660
|
render_all_from_events(
|
|
659
661
|
raw_events,
|
|
660
662
|
root: root,
|
|
@@ -56,11 +56,16 @@ module Lumitrace
|
|
|
56
56
|
key_options: [
|
|
57
57
|
{ name: "--collect-mode", values: COLLECT_MODES, default: "last" },
|
|
58
58
|
{ name: "--max-samples", type: "Integer", default: 3, note: "Used by history mode." },
|
|
59
|
-
{ name: "--json[=PATH]", type: "bool|string", note: "Emit JSON output." },
|
|
60
|
-
{ name: "--html[=PATH]", type: "bool|string", note: "Emit HTML output." },
|
|
61
|
-
{ name: "--text[=PATH]", type: "bool|string", note: "Emit text output." },
|
|
59
|
+
{ name: "-j, --json[=PATH]", type: "bool|string", note: "Emit JSON output." },
|
|
60
|
+
{ name: "-h, --html[=PATH]", type: "bool|string", note: "Emit HTML output." },
|
|
61
|
+
{ name: "-t, --text[=PATH]", type: "bool|string", note: "Emit text output." },
|
|
62
|
+
{ name: "-g, --git-diff[=MODE]", type: "string", note: "Restrict instrumentation to diff hunks." },
|
|
62
63
|
{ name: "--range SPEC", type: "string", repeatable: true, note: "Restrict instrumentation to file ranges." },
|
|
63
|
-
{ name: "--git-diff
|
|
64
|
+
{ name: "--git-diff-context N", type: "Integer", note: "Expand diff hunks by +/-N lines." },
|
|
65
|
+
{ name: "--git-cmd PATH", type: "string", note: "Git executable for diff." },
|
|
66
|
+
{ name: "--git-diff-no-untracked", type: "bool", note: "Exclude untracked files from diff." },
|
|
67
|
+
{ name: "--root PATH", type: "string", note: "Root directory for instrumentation." },
|
|
68
|
+
{ name: "--verbose[=LEVEL]", type: "Integer", note: "Verbose logs to stderr (level 1-3)." }
|
|
64
69
|
],
|
|
65
70
|
outputs: [
|
|
66
71
|
{ kind: "json", default_path: "lumitrace_recorded.json" },
|
|
@@ -495,10 +495,42 @@ module RecordInstrument
|
|
|
495
495
|
|
|
496
496
|
def self.dump_events_json(events, path = nil)
|
|
497
497
|
path ||= File.expand_path("lumitrace_recorded.json", Dir.pwd)
|
|
498
|
-
|
|
498
|
+
payload = {
|
|
499
|
+
version: 1,
|
|
500
|
+
events: events,
|
|
501
|
+
coverage: compute_coverage(events)
|
|
502
|
+
}
|
|
503
|
+
File.write(path, JSON.dump(payload), perm: 0o600)
|
|
499
504
|
path
|
|
500
505
|
end
|
|
501
506
|
|
|
507
|
+
def self.compute_coverage(events)
|
|
508
|
+
by_file = {}
|
|
509
|
+
events.each do |e|
|
|
510
|
+
file = e[:file] || e["file"]
|
|
511
|
+
next unless file
|
|
512
|
+
total = e[:total] || e["total"] || 0
|
|
513
|
+
start_line = e[:start_line] || e["start_line"]
|
|
514
|
+
next unless start_line
|
|
515
|
+
|
|
516
|
+
entry = (by_file[file] ||= { lines: {}, covered_lines: {} })
|
|
517
|
+
entry[:lines][start_line] = true
|
|
518
|
+
entry[:covered_lines][start_line] = true if total > 0
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
by_file.map do |file, entry|
|
|
522
|
+
total_lines = entry[:lines].size
|
|
523
|
+
covered_lines = entry[:covered_lines].size
|
|
524
|
+
pct = total_lines > 0 ? (covered_lines * 100.0 / total_lines).round(1) : 0.0
|
|
525
|
+
{
|
|
526
|
+
file: file,
|
|
527
|
+
total_lines: total_lines,
|
|
528
|
+
covered_lines: covered_lines,
|
|
529
|
+
coverage_percent: pct
|
|
530
|
+
}
|
|
531
|
+
end.sort_by { |e| e[:file] }
|
|
532
|
+
end
|
|
533
|
+
|
|
502
534
|
def self.load_events_json(path)
|
|
503
535
|
JSON.parse(File.read(path))
|
|
504
536
|
end
|
|
@@ -686,25 +718,36 @@ module RecordInstrument
|
|
|
686
718
|
events_from_ids
|
|
687
719
|
end
|
|
688
720
|
|
|
721
|
+
KERNEL_INSPECT = ::Kernel.instance_method(:inspect)
|
|
722
|
+
|
|
723
|
+
def self.safe_inspect(v)
|
|
724
|
+
v.inspect
|
|
725
|
+
rescue ::NoMethodError
|
|
726
|
+
KERNEL_INSPECT.bind_call(v)
|
|
727
|
+
end
|
|
728
|
+
|
|
689
729
|
def self.safe_value(v)
|
|
690
730
|
case v
|
|
691
731
|
when Numeric, TrueClass, FalseClass, NilClass
|
|
692
732
|
v
|
|
693
733
|
else
|
|
694
|
-
s = v
|
|
734
|
+
s = safe_inspect(v)
|
|
695
735
|
s.bytesize > 1000 ? s[0, 1000] + "..." : s
|
|
696
736
|
end
|
|
697
737
|
end
|
|
698
738
|
|
|
739
|
+
KERNEL_CLASS = ::Kernel.instance_method(:class)
|
|
740
|
+
|
|
699
741
|
def self.value_type_name(v)
|
|
700
|
-
|
|
701
|
-
name
|
|
742
|
+
klass = KERNEL_CLASS.bind_call(v)
|
|
743
|
+
name = klass.name
|
|
744
|
+
name && !name.empty? ? name : klass.to_s
|
|
702
745
|
end
|
|
703
746
|
|
|
704
747
|
def self.summarize_value(v, type: nil)
|
|
705
748
|
type ||= value_type_name(v)
|
|
706
749
|
preview_limit = 120
|
|
707
|
-
inspected = v
|
|
750
|
+
inspected = safe_inspect(v)
|
|
708
751
|
if inspected.length > preview_limit
|
|
709
752
|
{
|
|
710
753
|
type: type,
|
|
@@ -9,8 +9,18 @@ module Lumitrace
|
|
|
9
9
|
tool: "lumitrace",
|
|
10
10
|
version: VERSION,
|
|
11
11
|
json_top_level: {
|
|
12
|
-
type: "
|
|
13
|
-
|
|
12
|
+
type: "object",
|
|
13
|
+
fields: {
|
|
14
|
+
version: { type: "integer", description: "Schema version number." },
|
|
15
|
+
events: { type: "array", items: "event", description: "Traced expression events." },
|
|
16
|
+
coverage: { type: "array", items: "coverage_entry", description: "Per-file coverage summary." }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
coverage_entry_fields: {
|
|
20
|
+
file: { type: "string", required: true, description: "Absolute source path." },
|
|
21
|
+
total_lines: { type: "integer", required: true, description: "Number of traced expression lines." },
|
|
22
|
+
covered_lines: { type: "integer", required: true, description: "Lines with total > 0." },
|
|
23
|
+
coverage_percent: { type: "float", required: true, description: "Covered / total * 100, rounded to 1 decimal." }
|
|
14
24
|
},
|
|
15
25
|
event_common_fields: {
|
|
16
26
|
file: { type: "string", required: true, description: "Absolute source path." },
|
|
@@ -20,7 +30,7 @@ module Lumitrace
|
|
|
20
30
|
end_col: { type: "integer", required: true },
|
|
21
31
|
kind: { type: "string", required: true, enum: %w[expr arg] },
|
|
22
32
|
name: { type: ["string", "null"], required: false, description: "Present for kind=arg." },
|
|
23
|
-
total: { type: "integer", required: true, description: "Execution count." },
|
|
33
|
+
total: { type: "integer", required: true, description: "Execution count. 0 means the expression was never executed (uncovered)." },
|
|
24
34
|
types: {
|
|
25
35
|
type: "object",
|
|
26
36
|
required: true,
|
data/lib/lumitrace/version.rb
CHANGED
data/lib/lumitrace.rb
CHANGED
|
@@ -13,6 +13,8 @@ require_relative "lumitrace/ai_docs"
|
|
|
13
13
|
|
|
14
14
|
module Lumitrace
|
|
15
15
|
class Error < StandardError; end
|
|
16
|
+
KERNEL_CLASS = ::Kernel.instance_method(:class)
|
|
17
|
+
|
|
16
18
|
@atexit_registered = false
|
|
17
19
|
@atexit_output_root = nil
|
|
18
20
|
@atexit_ranges_by_file = nil
|
|
@@ -62,7 +64,7 @@ module Lumitrace
|
|
|
62
64
|
def R(id, value)
|
|
63
65
|
events_by_id = RecordInstrument.events_by_id
|
|
64
66
|
entry = events_by_id[id]
|
|
65
|
-
klass = value
|
|
67
|
+
klass = KERNEL_CLASS.bind_call(value)
|
|
66
68
|
type = klass.name
|
|
67
69
|
type = klass.to_s if type.nil? || type.empty?
|
|
68
70
|
if entry
|
|
@@ -81,7 +83,7 @@ module Lumitrace
|
|
|
81
83
|
def R(id, value)
|
|
82
84
|
events_by_id = RecordInstrument.events_by_id
|
|
83
85
|
entry = events_by_id[id]
|
|
84
|
-
klass = value
|
|
86
|
+
klass = KERNEL_CLASS.bind_call(value)
|
|
85
87
|
type = klass.name
|
|
86
88
|
type = klass.to_s if type.nil? || type.empty?
|
|
87
89
|
if entry
|
|
@@ -99,7 +101,7 @@ module Lumitrace
|
|
|
99
101
|
def R(id, value)
|
|
100
102
|
events_by_id = RecordInstrument.events_by_id
|
|
101
103
|
entry = events_by_id[id]
|
|
102
|
-
klass = value
|
|
104
|
+
klass = KERNEL_CLASS.bind_call(value)
|
|
103
105
|
type = klass.name
|
|
104
106
|
type = klass.to_s if type.nil? || type.empty?
|
|
105
107
|
if entry
|
data/runv/index.html
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>LumiTrace Playground</title>
|
|
7
|
-
<meta name="description" content="Run Ruby in the browser and see traced values inline with lumitrace 0.
|
|
7
|
+
<meta name="description" content="Run Ruby in the browser and see traced values inline with lumitrace 0.6.0.">
|
|
8
8
|
<meta property="og:title" content="LumiTrace Playground">
|
|
9
|
-
<meta property="og:description" content="Run Ruby in the browser and see traced values inline with lumitrace 0.
|
|
9
|
+
<meta property="og:description" content="Run Ruby in the browser and see traced values inline with lumitrace 0.6.0.">
|
|
10
10
|
<meta property="og:type" content="website">
|
|
11
11
|
<meta property="og:url" content="https://github.com/ko1/lumitrace/tree/master/runv">
|
|
12
12
|
<meta property="og:see_also" content="https://github.com/ko1/lumitrace/tree/master/runv">
|
|
@@ -290,7 +290,7 @@
|
|
|
290
290
|
<header>
|
|
291
291
|
<div>
|
|
292
292
|
<h1>LumiTrace Playground</h1>
|
|
293
|
-
<p class="sub">Run Ruby in the browser and see traced values inline with lumitrace 0.
|
|
293
|
+
<p class="sub">Run Ruby in the browser and see traced values inline with lumitrace 0.6.0.</p>
|
|
294
294
|
</div>
|
|
295
295
|
</header>
|
|
296
296
|
|
|
@@ -936,10 +936,42 @@ module RecordInstrument
|
|
|
936
936
|
|
|
937
937
|
def self.dump_events_json(events, path = nil)
|
|
938
938
|
path ||= File.expand_path("lumitrace_recorded.json", Dir.pwd)
|
|
939
|
-
|
|
939
|
+
payload = {
|
|
940
|
+
version: 1,
|
|
941
|
+
events: events,
|
|
942
|
+
coverage: compute_coverage(events)
|
|
943
|
+
}
|
|
944
|
+
File.write(path, JSON.dump(payload), perm: 0o600)
|
|
940
945
|
path
|
|
941
946
|
end
|
|
942
947
|
|
|
948
|
+
def self.compute_coverage(events)
|
|
949
|
+
by_file = {}
|
|
950
|
+
events.each do |e|
|
|
951
|
+
file = e[:file] || e["file"]
|
|
952
|
+
next unless file
|
|
953
|
+
total = e[:total] || e["total"] || 0
|
|
954
|
+
start_line = e[:start_line] || e["start_line"]
|
|
955
|
+
next unless start_line
|
|
956
|
+
|
|
957
|
+
entry = (by_file[file] ||= { lines: {}, covered_lines: {} })
|
|
958
|
+
entry[:lines][start_line] = true
|
|
959
|
+
entry[:covered_lines][start_line] = true if total > 0
|
|
960
|
+
end
|
|
961
|
+
|
|
962
|
+
by_file.map do |file, entry|
|
|
963
|
+
total_lines = entry[:lines].size
|
|
964
|
+
covered_lines = entry[:covered_lines].size
|
|
965
|
+
pct = total_lines > 0 ? (covered_lines * 100.0 / total_lines).round(1) : 0.0
|
|
966
|
+
{
|
|
967
|
+
file: file,
|
|
968
|
+
total_lines: total_lines,
|
|
969
|
+
covered_lines: covered_lines,
|
|
970
|
+
coverage_percent: pct
|
|
971
|
+
}
|
|
972
|
+
end.sort_by { |e| e[:file] }
|
|
973
|
+
end
|
|
974
|
+
|
|
943
975
|
def self.load_events_json(path)
|
|
944
976
|
JSON.parse(File.read(path))
|
|
945
977
|
end
|
|
@@ -1127,25 +1159,36 @@ module RecordInstrument
|
|
|
1127
1159
|
events_from_ids
|
|
1128
1160
|
end
|
|
1129
1161
|
|
|
1162
|
+
KERNEL_INSPECT = ::Kernel.instance_method(:inspect)
|
|
1163
|
+
|
|
1164
|
+
def self.safe_inspect(v)
|
|
1165
|
+
v.inspect
|
|
1166
|
+
rescue ::NoMethodError
|
|
1167
|
+
KERNEL_INSPECT.bind_call(v)
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1130
1170
|
def self.safe_value(v)
|
|
1131
1171
|
case v
|
|
1132
1172
|
when Numeric, TrueClass, FalseClass, NilClass
|
|
1133
1173
|
v
|
|
1134
1174
|
else
|
|
1135
|
-
s = v
|
|
1175
|
+
s = safe_inspect(v)
|
|
1136
1176
|
s.bytesize > 1000 ? s[0, 1000] + "..." : s
|
|
1137
1177
|
end
|
|
1138
1178
|
end
|
|
1139
1179
|
|
|
1180
|
+
KERNEL_CLASS = ::Kernel.instance_method(:class)
|
|
1181
|
+
|
|
1140
1182
|
def self.value_type_name(v)
|
|
1141
|
-
|
|
1142
|
-
name
|
|
1183
|
+
klass = KERNEL_CLASS.bind_call(v)
|
|
1184
|
+
name = klass.name
|
|
1185
|
+
name && !name.empty? ? name : klass.to_s
|
|
1143
1186
|
end
|
|
1144
1187
|
|
|
1145
1188
|
def self.summarize_value(v, type: nil)
|
|
1146
1189
|
type ||= value_type_name(v)
|
|
1147
1190
|
preview_limit = 120
|
|
1148
|
-
inspected = v
|
|
1191
|
+
inspected = safe_inspect(v)
|
|
1149
1192
|
if inspected.length > preview_limit
|
|
1150
1193
|
{
|
|
1151
1194
|
type: type,
|
|
@@ -1371,7 +1414,8 @@ module GenerateResultedHtml
|
|
|
1371
1414
|
abort "missing #{source_path}"
|
|
1372
1415
|
end
|
|
1373
1416
|
|
|
1374
|
-
|
|
1417
|
+
raw = JSON.parse(File.read(events_path))
|
|
1418
|
+
raw_events = raw.is_a?(Hash) && raw.key?("events") ? raw["events"] : raw
|
|
1375
1419
|
src = File.read(source_path)
|
|
1376
1420
|
mode_info = resolve_mode_info(raw_events, collect_mode: collect_mode, max_samples: max_samples)
|
|
1377
1421
|
normalized_ranges = normalize_ranges(ranges)
|
|
@@ -2773,7 +2817,8 @@ end
|
|
|
2773
2817
|
end
|
|
2774
2818
|
|
|
2775
2819
|
def self.render_all(events_path, root: Dir.pwd, ranges_by_file: nil, collect_mode: nil, max_samples: nil, logger: nil, command_text: nil)
|
|
2776
|
-
|
|
2820
|
+
raw = JSON.parse(File.read(events_path))
|
|
2821
|
+
raw_events = raw.is_a?(Hash) && raw.key?("events") ? raw["events"] : raw
|
|
2777
2822
|
render_all_from_events(
|
|
2778
2823
|
raw_events,
|
|
2779
2824
|
root: root,
|
data/test/test_lumitrace.rb
CHANGED
|
@@ -499,6 +499,164 @@ class LumiTraceTest < Minitest::Test
|
|
|
499
499
|
assert_equal "ruby script.rb arg1", payload[:meta][:command]
|
|
500
500
|
end
|
|
501
501
|
|
|
502
|
+
def test_value_type_name_with_basic_object
|
|
503
|
+
klass = Class.new(BasicObject)
|
|
504
|
+
obj = klass.new
|
|
505
|
+
type = Lumitrace::RecordInstrument.value_type_name(obj)
|
|
506
|
+
refute_nil type
|
|
507
|
+
refute_empty type
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def test_summarize_value_with_basic_object
|
|
511
|
+
klass = Class.new(BasicObject)
|
|
512
|
+
obj = klass.new
|
|
513
|
+
summary = Lumitrace::RecordInstrument.summarize_value(obj)
|
|
514
|
+
assert summary.is_a?(Hash)
|
|
515
|
+
assert summary.key?(:type)
|
|
516
|
+
assert summary.key?(:preview)
|
|
517
|
+
refute_nil summary[:type]
|
|
518
|
+
refute_empty summary[:type]
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def test_R_with_basic_object_last_mode
|
|
522
|
+
with_record_instrument_state do
|
|
523
|
+
mod = Lumitrace::RecordInstrument
|
|
524
|
+
mod.instance_variable_set(:@events_by_id, [])
|
|
525
|
+
mod.instance_variable_set(:@loc_by_id, [])
|
|
526
|
+
mod.instance_variable_set(:@next_id, 0)
|
|
527
|
+
Lumitrace.install_collect_mode("last")
|
|
528
|
+
|
|
529
|
+
id = mod.register_location(
|
|
530
|
+
"a.rb",
|
|
531
|
+
{ start_line: 1, start_col: 0, end_line: 1, end_col: 1 },
|
|
532
|
+
kind: :expr
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
klass = Class.new(BasicObject)
|
|
536
|
+
obj = klass.new
|
|
537
|
+
result = Lumitrace::R(id, obj)
|
|
538
|
+
assert_same obj, result
|
|
539
|
+
|
|
540
|
+
events = mod.events_from_ids
|
|
541
|
+
assert_equal 1, events.first[:total]
|
|
542
|
+
refute_empty events.first[:types]
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
def test_R_with_basic_object_history_mode
|
|
547
|
+
with_record_instrument_state do
|
|
548
|
+
mod = Lumitrace::RecordInstrument
|
|
549
|
+
mod.instance_variable_set(:@events_by_id, [])
|
|
550
|
+
mod.instance_variable_set(:@loc_by_id, [])
|
|
551
|
+
mod.instance_variable_set(:@next_id, 0)
|
|
552
|
+
Lumitrace.install_collect_mode("history")
|
|
553
|
+
mod.max_samples_per_expr = 3
|
|
554
|
+
|
|
555
|
+
id = mod.register_location(
|
|
556
|
+
"a.rb",
|
|
557
|
+
{ start_line: 1, start_col: 0, end_line: 1, end_col: 1 },
|
|
558
|
+
kind: :expr
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
klass = Class.new(BasicObject)
|
|
562
|
+
Lumitrace::R(id, klass.new)
|
|
563
|
+
Lumitrace::R(id, 42)
|
|
564
|
+
|
|
565
|
+
events = mod.events_from_ids
|
|
566
|
+
assert_equal 2, events.first[:total]
|
|
567
|
+
assert_equal 2, events.first[:sampled_values].length
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
def test_R_with_basic_object_types_mode
|
|
572
|
+
with_record_instrument_state do
|
|
573
|
+
mod = Lumitrace::RecordInstrument
|
|
574
|
+
mod.instance_variable_set(:@events_by_id, [])
|
|
575
|
+
mod.instance_variable_set(:@loc_by_id, [])
|
|
576
|
+
mod.instance_variable_set(:@next_id, 0)
|
|
577
|
+
Lumitrace.install_collect_mode("types")
|
|
578
|
+
|
|
579
|
+
id = mod.register_location(
|
|
580
|
+
"a.rb",
|
|
581
|
+
{ start_line: 1, start_col: 0, end_line: 1, end_col: 1 },
|
|
582
|
+
kind: :expr
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
klass = Class.new(BasicObject)
|
|
586
|
+
Lumitrace::R(id, klass.new)
|
|
587
|
+
|
|
588
|
+
events = mod.events_from_ids
|
|
589
|
+
assert_equal 1, events.first[:total]
|
|
590
|
+
refute_empty events.first[:types]
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def test_compute_coverage
|
|
595
|
+
events = [
|
|
596
|
+
{ file: "a.rb", start_line: 1, start_col: 0, end_line: 1, end_col: 5, total: 3 },
|
|
597
|
+
{ file: "a.rb", start_line: 2, start_col: 0, end_line: 2, end_col: 5, total: 0 },
|
|
598
|
+
{ file: "a.rb", start_line: 3, start_col: 0, end_line: 3, end_col: 5, total: 1 },
|
|
599
|
+
{ file: "b.rb", start_line: 1, start_col: 0, end_line: 1, end_col: 5, total: 0 }
|
|
600
|
+
]
|
|
601
|
+
coverage = Lumitrace::RecordInstrument.compute_coverage(events)
|
|
602
|
+
assert_equal 2, coverage.length
|
|
603
|
+
|
|
604
|
+
a = coverage.find { |c| c[:file] == "a.rb" }
|
|
605
|
+
assert_equal 3, a[:total_lines]
|
|
606
|
+
assert_equal 2, a[:covered_lines]
|
|
607
|
+
assert_equal 66.7, a[:coverage_percent]
|
|
608
|
+
|
|
609
|
+
b = coverage.find { |c| c[:file] == "b.rb" }
|
|
610
|
+
assert_equal 1, b[:total_lines]
|
|
611
|
+
assert_equal 0, b[:covered_lines]
|
|
612
|
+
assert_equal 0.0, b[:coverage_percent]
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
def test_dump_events_json_wrapped_format
|
|
616
|
+
Dir.mktmpdir do |dir|
|
|
617
|
+
events = [
|
|
618
|
+
{ file: "a.rb", start_line: 1, start_col: 0, end_line: 1, end_col: 5, total: 1 }
|
|
619
|
+
]
|
|
620
|
+
path = File.join(dir, "out.json")
|
|
621
|
+
Lumitrace::RecordInstrument.dump_events_json(events, path)
|
|
622
|
+
data = JSON.parse(File.read(path))
|
|
623
|
+
|
|
624
|
+
assert_equal 1, data["version"]
|
|
625
|
+
assert data.key?("events")
|
|
626
|
+
assert data.key?("coverage")
|
|
627
|
+
assert_equal 1, data["events"].length
|
|
628
|
+
assert_equal 1, data["coverage"].length
|
|
629
|
+
assert_equal 100.0, data["coverage"][0]["coverage_percent"]
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
def test_render_all_reads_wrapped_json
|
|
634
|
+
Dir.mktmpdir do |dir|
|
|
635
|
+
sample = File.join(dir, "sample.rb")
|
|
636
|
+
File.write(sample, "puts hi\n")
|
|
637
|
+
events_path = File.join(dir, "events.json")
|
|
638
|
+
payload = {
|
|
639
|
+
"version" => 1,
|
|
640
|
+
"events" => [
|
|
641
|
+
{
|
|
642
|
+
"file" => sample,
|
|
643
|
+
"start_line" => 1,
|
|
644
|
+
"start_col" => 0,
|
|
645
|
+
"end_line" => 1,
|
|
646
|
+
"end_col" => 5,
|
|
647
|
+
"sampled_values" => ["ok"],
|
|
648
|
+
"total" => 1
|
|
649
|
+
}
|
|
650
|
+
],
|
|
651
|
+
"coverage" => []
|
|
652
|
+
}
|
|
653
|
+
File.write(events_path, JSON.dump(payload))
|
|
654
|
+
|
|
655
|
+
html = Lumitrace::GenerateResultedHtml.render_all(events_path, root: dir)
|
|
656
|
+
assert_includes html, "sample.rb"
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
|
|
502
660
|
def test_env_range_parsing
|
|
503
661
|
with_env("LUMITRACE_RANGE" => "a.rb:1-3,5-6;b.rb", "LUMITRACE_COLLECT_MODE" => "types") do
|
|
504
662
|
env = Lumitrace.resolve_env_options
|