# 帳票設計書 5-lcov

## 概要

本ドキュメントは、Node.jsテストランナーの「LCOVレポーター」の設計仕様を記載したものである。コードカバレッジ情報をLCOV形式で出力するレポーター機能の実装詳細、出力形式、処理フローについて説明する。

### 本帳票の処理概要

LCOVレポーターは、Node.jsテストランナーが収集したコードカバレッジ情報を、LCOV（Linux Test Project Coverage）形式で標準出力に表示する帳票である。LCOV形式はgcov/lcovツールで生成される標準的なカバレッジフォーマットであり、SonarQube、Coveralls、Codecov等の多くのカバレッジ分析ツールでサポートされている。

**業務上の目的・背景**：コードカバレッジの測定と可視化は、テスト品質の評価とコード品質の向上に不可欠である。LCOV形式は広く普及した標準フォーマットであり、カバレッジ分析ツールとの連携、カバレッジトレンドの追跡、カバレッジレポートの生成に利用される。特にCI/CD環境では、カバレッジ閾値のチェックやカバレッジ低下の検出に活用される。

**帳票の利用シーン**：
- SonarQubeでのコードカバレッジ分析
- Coveralls/Codecovへのカバレッジレポート送信
- ローカル開発環境でのカバレッジ確認（genhtml等でHTML化）
- CI/CDでのカバレッジ閾値チェック
- カバレッジトレンドの長期追跡

**主要な出力内容**：
1. TN（テスト名）行 - 空文字
2. SF（ソースファイル）行 - ファイルパス
3. FN/FNDA（関数カバレッジ）- 関数定義行と実行回数
4. FNF/FNH（関数サマリー）- 関数の発見数と実行数
5. BRDA（ブランチカバレッジ）- ブランチ実行回数
6. BRF/BRH（ブランチサマリー）- ブランチの発見数と実行数
7. DA（ラインカバレッジ）- 行ごとの実行回数
8. LH/LF（ラインサマリー）- 行の実行数と総行数
9. end_of_record - セクション終了マーカー

**帳票の出力タイミング**：
- test:coverageイベント受信時にのみ出力
- カバレッジ計測が有効な場合にテスト完了後に発行される

**帳票の利用者**：
- DevOpsエンジニア
- ソフトウェア開発者
- QAエンジニア
- SonarQube/Coveralls/Codecov等のカバレッジサービス

## 帳票種別

カバレッジレポート / LCOV形式

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | CLIターミナル | `node --test --experimental-test-coverage --test-reporter=lcov` | テスト実行コマンド |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | テキスト（LCOV tracefile） |
| 用紙サイズ | N/A（テキスト出力） |
| 向き | N/A |
| ファイル名 | N/A（標準出力へ直接出力） |
| 出力方法 | 標準出力（stdout） |
| 文字コード | UTF-8 |

### LCOV固有設定

| 項目 | 内容 |
|-----|------|
| フォーマット | LCOV tracefile |
| 行形式 | `TAG:data` |
| セクション区切り | `end_of_record` |
| パス形式 | workingDirectoryからの相対パス |

## 帳票レイアウト

### レイアウト概要

LCOVレポーターは、各ソースファイルごとにカバレッジ情報をセクションとして出力する。

```
┌─────────────────────────────────────┐
│          テスト名部                  │
│  TN:                                │
├─────────────────────────────────────┤
│          ファイルセクション          │
│  SF:src/module.js                   │
│  FN:10,functionName                 │
│  FNDA:5,functionName                │
│  FNF:10                             │
│  FNH:8                              │
│  BRDA:15,0,0,3                      │
│  BRF:5                              │
│  BRH:4                              │
│  DA:10,5                            │
│  LH:50                              │
│  LF:60                              │
│  end_of_record                      │
├─────────────────────────────────────┤
│          次のファイルセクション...    │
└─────────────────────────────────────┘
```

### テスト名部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | TN | テスト名（空） | 固定 | `TN:` |

### ファイルセクション

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | SF | ソースファイルパス | relative(workingDirectory, file.path) | `SF:{path}` |

### 関数カバレッジ部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | FN | 関数定義行と名前 | func.line, func.name | `FN:{line},{name}` |
| 2 | FNDA | 関数実行回数と名前 | func.count, func.name | `FNDA:{count},{name}` |
| 3 | FNF | 関数発見数 | file.totalFunctionCount | `FNF:{count}` |
| 4 | FNH | 関数実行数 | file.coveredFunctionCount | `FNH:{count}` |

### ブランチカバレッジ部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | BRDA | ブランチ情報 | branch.line, j, 0, branch.count | `BRDA:{line},{block},0,{count}` |
| 2 | BRF | ブランチ発見数 | file.totalBranchCount | `BRF:{count}` |
| 3 | BRH | ブランチ実行数 | file.coveredBranchCount | `BRH:{count}` |

### ラインカバレッジ部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | DA | 行実行情報 | sortedLines[j].line, count | `DA:{line},{count}` |
| 2 | LH | 実行行数 | file.coveredLineCount | `LH:{count}` |
| 3 | LF | 総行数 | file.totalLineCount | `LF:{count}` |
| 4 | end_of_record | セクション終了 | 固定 | `end_of_record` |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| test:coverage イベント | カバレッジイベントのみ処理 | Yes |
| --experimental-test-coverage | カバレッジ計測有効化 | Yes |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | ファイル順 | summary.files配列順 |
| 2 | DA行 | 行番号昇順（toSorted） |

### 改ページ条件

なし（連続出力）

## データベース参照仕様

### 参照テーブル一覧

本帳票はデータベースを参照しない。テストランナーからのカバレッジイベントを入力として使用する。

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| N/A | - | - |

### イベントデータ構造

#### test:coverage イベント

| 参照項目 | 帳票項目との対応 | 取得条件 | 備考 |
|---------|----------------|---------|------|
| data.summary.workingDirectory | パス計算基準 | - | 相対パス計算用 |
| data.summary.files | ファイルリスト | - | 各ファイルのカバレッジ |

#### file オブジェクト

| 参照項目 | 帳票項目との対応 | 取得条件 | 備考 |
|---------|----------------|---------|------|
| path | SF行 | - | 相対パス変換 |
| functions | FN/FNDA行 | - | 関数リスト |
| totalFunctionCount | FNF行 | - | 関数総数 |
| coveredFunctionCount | FNH行 | - | 実行関数数 |
| branches | BRDA行 | - | ブランチリスト |
| totalBranchCount | BRF行 | - | ブランチ総数 |
| coveredBranchCount | BRH行 | - | 実行ブランチ数 |
| lines | DA行 | - | 行リスト |
| coveredLineCount | LH行 | - | 実行行数 |
| totalLineCount | LF行 | - | 総行数 |

#### function オブジェクト

| 参照項目 | 帳票項目との対応 | 取得条件 | 備考 |
|---------|----------------|---------|------|
| line | FN行の行番号 | - | - |
| name | FN/FNDA行の関数名 | - | 空の場合は`anonymous_{j}` |
| count | FNDA行の実行回数 | - | - |

#### branch オブジェクト

| 参照項目 | 帳票項目との対応 | 取得条件 | 備考 |
|---------|----------------|---------|------|
| line | BRDA行の行番号 | - | - |
| count | BRDA行の実行回数 | - | - |

#### line オブジェクト

| 参照項目 | 帳票項目との対応 | 取得条件 | 備考 |
|---------|----------------|---------|------|
| line | DA行の行番号 | - | - |
| count | DA行の実行回数 | - | - |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| 相対パス | relative(workingDirectory, file.path) | N/A | path.relative使用 |
| 関数名 | func.name \|\| `anonymous_${j}` | N/A | 匿名関数対応 |
| ソート済み行 | file.lines.toSorted((a, b) => a.line - b.line) | N/A | 行番号昇順 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[イベント受信] --> B{type === test:coverage?}
    B -->|No| C[callback(null) - 何も出力しない]
    B -->|Yes| D[lcov変数初期化]
    D --> E[TN:追加]
    E --> F[ファイルループ開始]
    F --> G[SF:ファイルパス追加]
    G --> H[関数ループ]
    H --> I[FN:行,名前 追加]
    I --> J[fnda文字列蓄積]
    J --> K[関数ループ終了]
    K --> L[fnda追加]
    L --> M[FNF/FNH追加]
    M --> N[ブランチループ]
    N --> O[BRDA:行,block,0,count 追加]
    O --> P[ブランチループ終了]
    P --> Q[BRF/BRH追加]
    Q --> R[行ソート]
    R --> S[行ループ]
    S --> T[DA:行,count 追加]
    T --> U[行ループ終了]
    U --> V[LH/LF追加]
    V --> W[end_of_record追加]
    W --> X{次のファイルあり?}
    X -->|Yes| F
    X -->|No| Y[callback(null, lcov)]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| 処理エラー | ファイル処理中の例外 | - | callback(error)で伝播 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | ファイル数 × 関数/ブランチ/行数 |
| 目標出力時間 | カバレッジイベント受信後即時 |
| 同時出力数上限 | 1（シングルイベント） |

## セキュリティ考慮事項

- ソースファイルパスが出力されるため、ディレクトリ構造が露出する
- 関数名やコード構造に関する情報が含まれる
- カバレッジデータから未テストコードの位置が特定可能

## 備考

- Transformストリームとして実装（writableObjectMode: true）
- test:coverage以外のイベントは無視（callback(null)）
- LCOV仕様書: https://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
- 匿名関数は`anonymous_{index}`として出力
- ブランチのblock番号はインデックス（j）、branch番号は常に0

---

## コードリーディングガイド

本帳票を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

カバレッジデータの構造とLCOVフォーマットの対応を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | lcov.js | `lib/internal/test_runner/reporter/lcov.js` | コメント内のLCOV仕様説明（6-9行目） |
| 1-2 | coverage.js | `lib/internal/test_runner/coverage.js` | カバレッジデータの生成元（参考） |

**読解のコツ**: LCOVの各タグ（TN, SF, FN, FNDA, FNF, FNH, BRDA, BRF, BRH, DA, LH, LF）の意味を理解しておくと、コードの意図が明確になる。

#### Step 2: エントリーポイントを理解する

LcovReporterクラスとTransformストリームの実装を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | lcov.js | `lib/internal/test_runner/reporter/lcov.js` | LcovReporterクラス全体（10-107行目） |

**主要処理フロー**:
1. **10-13行目**: クラス定義とコンストラクタ（writableObjectMode: true）
2. **15-18行目**: test:coverage以外のイベントをスキップ
3. **19-24行目**: lcov変数初期化、TN行出力
4. **25-29行目**: workingDirectoryの取得
5. **30-98行目**: ファイルループでのカバレッジ出力

#### Step 3: ファイル処理ロジックを理解する

各ファイルのカバレッジ情報をLCOV形式に変換するロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | lcov.js | `lib/internal/test_runner/reporter/lcov.js` | ファイルループ内処理（31-98行目） |

**主要処理フロー**:
- **36行目**: SF行 - 相対パスでファイルパスを出力
- **49-56行目**: 関数ループ - FN行とFNDA行を蓄積
- **52行目**: 匿名関数対応（`name || 'anonymous_${j}'`）
- **62-63行目**: FNF/FNH行 - 関数サマリー
- **71-73行目**: ブランチループ - BRDA行を出力
- **78-79行目**: BRF/BRH行 - ブランチサマリー
- **84行目**: 行のソート（toSorted）
- **85-87行目**: 行ループ - DA行を出力
- **93-94行目**: LH/LF行 - ラインサマリー
- **98行目**: end_of_record - セクション終了

#### Step 4: エラー処理を理解する

try-catchによるエラー処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | lcov.js | `lib/internal/test_runner/reporter/lcov.js` | エラー処理（100-103行目） |

**主要処理フロー**:
- **100-101行目**: catchブロックでcallback(error)
- **103行目**: 正常終了でcallback(null, lcov)

### プログラム呼び出し階層図

```
node --test --experimental-test-coverage --test-reporter=lcov
    │
    ├─ lib/internal/test_runner/runner.js
    │      └─ parseCommandLine() → getReportersMap()
    │              └─ require('internal/test_runner/reporter/lcov')
    │
    ├─ lib/internal/test_runner/coverage.js
    │      └─ カバレッジデータ収集 → test:coverage イベント発行
    │
    └─ lib/internal/test_runner/reporter/lcov.js
           │
           ├─ LcovReporter (Transform)
           │      └─ _transform()
           │             ├─ test:coverage以外 → スキップ
           │             └─ test:coverage → LCOV形式変換
           │
           └─ relative()
                  └─ path
```

### データフロー図

```
[入力]                    [処理]                           [出力]

test:coverage ───▶ LcovReporter._transform() ───▶ 標準出力(stdout)
   イベント               │
                         ├─ TN: (空)
                         │
                         └─ ファイルごとに:
                                ├─ SF: 相対パス
                                ├─ FN: 行,関数名
                                ├─ FNDA: 回数,関数名
                                ├─ FNF/FNH: 関数サマリー
                                ├─ BRDA: ブランチ情報
                                ├─ BRF/BRH: ブランチサマリー
                                ├─ DA: 行,回数
                                ├─ LH/LF: ラインサマリー
                                └─ end_of_record
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| lcov.js | `lib/internal/test_runner/reporter/lcov.js` | ソース | LCOVレポーターのメイン実装 |
| coverage.js | `lib/internal/test_runner/coverage.js` | ソース | カバレッジデータ収集 |
| utils.js | `lib/internal/test_runner/utils.js` | ソース | kBuiltinReporters登録 |
| transform.js | `lib/internal/streams/transform.js` | ソース | Transformストリーム基底クラス |
| path.js | `path` | Node.js組込み | relative()関数 |
| runner.js | `lib/internal/test_runner/runner.js` | ソース | テストランナーのメイン処理 |
