# 帳票設計書 24-テストエラーレポート

## 概要

本ドキュメントは、OpenSearch プロジェクトのテスト実行時に失敗したテストの詳細エラー情報をファイル出力するテストエラーレポートの設計仕様を定義する。`ErrorReportingTestListener` によりテスト出力のキャプチャ、エラースタックトレースの記録、再現コマンドの表示を行う帳票である。

### 本帳票の処理概要

テストエラーレポートは、OpenSearch のテスト実行時にテストスイートの標準出力・標準エラー出力をキャプチャし、テスト失敗時に詳細なエラー情報をコンソールとファイルに出力する帳票である。

**業務上の目的・背景**：大規模なテストスイートを実行する際、テスト失敗の原因特定は開発効率に直結する重要な課題である。OpenSearch のように数千のテストケースを持つプロジェクトでは、失敗したテストの出力をノイズなく確認できることが不可欠である。本レポートは、全テスト出力をファイルにバッファリングし、失敗したテストスイートの出力のみをコンソールに表示することで、効率的なデバッグを支援する。さらに、テスト再現コマンド（REPRODUCE WITH プレフィックス）をキャプチャし、失敗テストの即座の再実行を可能にする。

**帳票の利用シーン**：CI/CD パイプラインでのテスト失敗時の原因調査、ローカル開発環境でのテストデバッグ、間欠的なテスト失敗（flaky test）の調査、テスト失敗のスタックトレース確認時に利用される。

**主要な出力内容**：
1. 失敗したテストスイートの標準出力・標準エラー出力の全文
2. テスト失敗時の例外スタックトレース（FullExceptionFormatter による整形済み）
3. テスト再現コマンド（REPRODUCE WITH プレフィックス付き）
4. 失敗したテストの一覧（テストタスク完了時にサマリー表示）

**帳票の出力タイミング**：テスト実行中にリアルタイムでファイルにバッファリングされ、テストスイート完了時に失敗があった場合にコンソールへ出力される。テストタスクの最上位スイート完了時に失敗テストの一覧が表示される。

**帳票の利用者**：OpenSearch の開発者、CI/CD パイプライン管理者、テスト自動化エンジニア

## 帳票種別

明細書（テキストファイル + コンソール出力）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| N/A | テスト実行コンソール | N/A | `./gradlew test` 等のテストタスク実行 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | テキストファイル（.out） + コンソール出力 |
| 用紙サイズ | N/A |
| 向き | N/A |
| ファイル名 | `{テストクラスFQCN}.out`（例: `org.opensearch.index.IndexTests.out`） |
| 出力方法 | ファイルシステム出力 + コンソール出力（System.out / System.err） |
| 文字コード | UTF-8（デフォルトプラットフォームエンコーディング） |

### ファイル出力の場所

| 項目 | 内容 |
|-----|------|
| 出力ディレクトリ | `{project}/build/test-results/test/output/` |
| 一時ファイル | テストスイート完了後に自動削除（EventWriter.close() 行309） |

## 帳票レイアウト

### レイアウト概要

テスト実行中の全出力をファイルにバッファリングし、テストスイート失敗時にコンソールに再出力する。出力は標準出力（`1> ` プレフィックス）と標準エラー出力（`2> ` プレフィックス）で区別される。

```
┌─────────────────────────────────────────────────────────────┐
│ [テストスイート失敗時のコンソール出力]                            │
│                                                             │
│ Suite: {テストスイート名}                                      │
│   1> [標準出力メッセージ...]                                   │
│   2> [標準エラー出力メッセージ...]                               │
│   2> [例外スタックトレース...]                                  │
│                                                             │
│ REPRODUCE WITH: {再現コマンド}                                │
├─────────────────────────────────────────────────────────────┤
│ [テストタスク完了時のサマリー]                                   │
│                                                             │
│ Tests with failures:                                        │
│  - {クラス名}.{メソッド名}                                     │
│  - {クラス名}.{メソッド名}                                     │
└─────────────────────────────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | スイート名 | 失敗したテストスイートの名前 | TestDescriptor.toString() | "Suite: {名前}" |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | 標準出力行 | テストの標準出力 | TestOutputEvent (StdOut) | "  1> {メッセージ}" | 可変 |
| 2 | 標準エラー出力行 | テストの標準エラー出力 | TestOutputEvent (StdErr) | "  2> {メッセージ}" | 可変 |
| 3 | 例外スタックトレース | テスト失敗時の例外情報 | TestResult.getExceptions() | FullExceptionFormatter 整形 | 可変 |
| 4 | 再現コマンド | テスト再現用コマンド | "REPRODUCE WITH" プレフィックス付き出力 | テキスト | 可変 |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | 失敗テスト一覧 | 全失敗テストのリスト | failedTests セット | "Tests with failures:\n - {FQCN}.{メソッド名}" |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| テスト失敗 | テストスイートの ResultType が FAILURE | Yes（コンソール出力のトリガー） |
| テスト出力あり | テスト実行中に標準出力/標準エラー出力が存在 | No（出力なしでも失敗時はスイート名を表示） |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | テスト実行順 | 実行順（時系列） |

### 改ページ条件

改ページは発生しない（コンソールへの連続出力）。

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

### 参照テーブル一覧

本帳票はデータベースを使用せず、Gradle のテスト実行イベントを入力とする。

| データソース | 用途 | 取得方法 |
|-----------|------|---------|
| TestOutputEvent | テスト出力のキャプチャ | TestOutputListener.onOutput() |
| TestResult | テスト結果（成功/失敗） | TestListener.afterTest() / afterSuite() |
| TestDescriptor | テスト識別情報 | Gradle テストフレームワーク |

### データソース別参照項目詳細

#### TestOutputEvent

| 参照項目 | 帳票項目との対応 | 取得条件 | 備考 |
|---------|----------------|---------|------|
| getMessage() | 出力メッセージ本文 | テスト実行中のあらゆる出力 | StdOut/StdErr の判別は getDestination() |
| getDestination() | 出力先の区別（1> / 2>） | 同上 | StdOut or StdErr |

#### TestResult

| 参照項目 | 帳票項目との対応 | 取得条件 | 備考 |
|---------|----------------|---------|------|
| getResultType() | 失敗判定 | テスト完了時 | FAILURE の場合にレポート出力 |
| getExceptions() | 例外スタックトレース | テスト失敗時 | FullExceptionFormatter で整形 |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| 出力プレフィックス | StdOut -> "  1> ", StdErr -> "  2> " | N/A | EventWriter.write() 行274-278 |
| スタックトレース整形 | formatter.format(testDescriptor, exceptions).substring(4) | N/A | 先頭4文字を除去（行182） |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[テスト実行開始] --> B[onOutput: テスト出力キャプチャ]
    B --> C{REPRODUCE WITH?}
    C -->|Yes| D[reproductionLines に保存]
    C -->|No| E[EventWriter でファイル出力]
    D --> E
    E --> F{テスト完了}
    F --> G[afterTest: テスト結果判定]
    G --> H{失敗?}
    H -->|Yes| I[failedTests に追加]
    I --> J[再現コマンドを stderr に出力]
    J --> K[スタックトレースを EventWriter に追記]
    H -->|No| L[次のテストへ]
    K --> M{テストスイート完了}
    L --> M
    M --> N[afterSuite: スイート結果判定]
    N --> O{スイート失敗?}
    O -->|Yes| P[ファイルをフラッシュ]
    P --> Q[ファイル全文をコンソールに出力]
    O -->|No| R[リソースクリーンアップ]
    Q --> R
    R --> S{最上位スイート?}
    S -->|Yes| T[失敗テスト一覧をログ出力]
    S -->|No| U[終了]
    T --> U
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| ファイル作成失敗 | 出力ディレクトリへのファイル作成不可 | UncheckedIOException: "Unable to create test suite output file" | 出力ディレクトリの権限を確認 |
| ファイル書き込み失敗 | ディスク容量不足等 | UncheckedIOException: "Unable to write test suite output" | ディスク容量を確認 |
| ファイル読み込み失敗 | 一時ファイルの読み取り不可 | UncheckedIOException: "Unable to read test suite output file" | ファイルの存在と権限を確認 |
| ファイル読み込みIOエラー | コンソール出力時のIO例外 | UncheckedIOException: "Error reading test suite output" | ファイルシステムの状態を確認 |
| ファイルクローズ失敗 | 出力ストリームのクローズ時エラー | "Failed to close test suite output stream" | ログに記録されるのみ（処理継続） |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | テストスイートあたり数KB～数MBの出力 |
| 目標出力時間 | テスト実行時間に含まれる（オーバーヘッドは最小限） |
| 同時出力数上限 | ConcurrentHashMap により並列テストスイートに対応 |

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

- テスト出力にはテストデータが含まれる可能性があるが、本番データは含まれない
- 一時ファイルはテストスイート完了後に自動削除される（EventWriter.close() 行309）
- コンソール出力は CI/CD のビルドログとして保存される場合があるため、テスト内でシークレット情報を出力しないよう注意が必要

## 備考

- `ErrorReportingTestListener` は `TestOutputListener` と `TestListener` の両方を実装している
- テスト出力のバッファリングは `EventWriter` 内部クラスで行われ、`{テストクラスFQCN}.out` ファイルに書き出される（行261）
- 再現コマンド（REPRODUCE WITH プレフィックス）は、テスト失敗直後に stderr に出力される（行176-178）
- `synchronized(this)` ブロックにより、複数テストスイートの出力が混在しないよう制御されている（行115）
- `Descriptor` 内部クラスは TestDescriptor のメモリリーク対策として導入されている（行213-254）
- 一時ファイルはテストスイート完了後に `outputFile.delete()` で削除される（行309）

---

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

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

### 推奨読解順序

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

テストエラーレポートで使用されるデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ErrorReportingTestListener.java | `buildSrc/src/main/java/org/opensearch/gradle/test/ErrorReportingTestListener.java` | 内部データ構造。行69-71 のフィールド定義（eventWriters, reproductionLines, failedTests）を確認 |
| 1-2 | ErrorReportingTestListener.java | 同上 | Descriptor 内部クラス（行215-254）。TestDescriptor のラッパーで、メモリリーク対策として name, className, parent のみ保持 |

**読解のコツ**: `ConcurrentHashMap` が使用されている点に注意。Gradle のテスト実行は並列で行われるため、スレッドセーフなデータ構造が必要。

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

ErrorReportingTestListener の登録方法を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | OpenSearchTestBasePlugin.java | `buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java` | 行78-84: 全テストタスクに ErrorReportingTestListener を登録。TestOutputListener と TestListener の両方として追加 |

**主要処理フロー**:
1. **行79**: テスト出力ディレクトリを JUnit XML 出力先の下に設定
2. **行81**: ErrorReportingTestListener を生成（TestLogging, Logger, 出力ディレクトリ）
3. **行82**: テスト拡張として登録
4. **行83**: TestOutputListener として追加（onOutput イベント受信）
5. **行84**: TestListener として追加（beforeSuite/afterSuite/beforeTest/afterTest イベント受信）

#### Step 3: テスト出力キャプチャを理解する

テスト実行中の出力キャプチャ処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ErrorReportingTestListener.java | `buildSrc/src/main/java/org/opensearch/gradle/test/ErrorReportingTestListener.java` | onOutput() メソッド（行80-96）。テスト出力のキャプチャと REPRODUCE WITH プレフィックスの検出 |
| 3-2 | ErrorReportingTestListener.java | 同上 | EventWriter 内部クラス（行256-311）。ファイルへの出力バッファリング |

**主要処理フロー**:
- **行80-96**: `onOutput()` でテスト出力をキャプチャ。composite テストの場合は自身をスイートとして扱う（行84-86）
- **行89-92**: "REPRODUCE WITH" プレフィックスの出力を reproductionLines に保存
- **行94-95**: EventWriter を生成/取得してファイルに書き出し
- **行273-289**: EventWriter.write() で StdOut は "  1> "、StdErr は "  2> " プレフィックスを付与

#### Step 4: テスト失敗処理を理解する

テスト失敗時の処理フローを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ErrorReportingTestListener.java | `buildSrc/src/main/java/org/opensearch/gradle/test/ErrorReportingTestListener.java` | afterTest() メソッド（行166-203）。テスト失敗時の処理 |
| 4-2 | ErrorReportingTestListener.java | 同上 | afterSuite() メソッド（行104-158）。テストスイート完了時の処理 |

**主要処理フロー**:
- **行167-168**: テスト失敗時に failedTests セットに追加
- **行172-178**: 再現コマンドを reproductionLines から取得し stderr に出力
- **行181-199**: 例外のスタックトレースを FullExceptionFormatter で整形し EventWriter に追記
- **行109**: テストスイート失敗時に EventWriter をフラッシュ
- **行119-131**: ファイル内容をコンソールに出力（`1> ` は stdout、`2> ` は stderr に振り分け）
- **行136-143**: 最上位スイート完了時に失敗テスト一覧をログ出力

#### Step 5: リソース管理を理解する

一時ファイルのライフサイクル管理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | ErrorReportingTestListener.java | `buildSrc/src/main/java/org/opensearch/gradle/test/ErrorReportingTestListener.java` | EventWriter.close() メソッド（行305-310）。ファイルクローズと削除 |

**主要処理フロー**:
- **行148-157**: afterSuite() の finally ブロックで reproductionLines と eventWriters をクリーンアップ
- **行305-309**: EventWriter.close() でストリームをクローズし、一時ファイルを削除

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

```
OpenSearchTestBasePlugin.apply()                         [OpenSearchTestBasePlugin.java 行78]
    |
    +-- ErrorReportingTestListener 生成・登録              [行81-84]
        |
        +-- onOutput(TestDescriptor, TestOutputEvent)     [ErrorReportingTestListener.java 行80]
        |   +-- REPRODUCE WITH 検出                      [行89-92]
        |   +-- EventWriter.write()                      [行94-95]
        |       +-- ファイル出力 ({FQCN}.out)              [行256-289]
        |
        +-- afterTest(TestDescriptor, TestResult)         [行166]
        |   +-- failedTests.add()                        [行168]
        |   +-- 再現コマンド出力 (stderr)                  [行172-178]
        |   +-- スタックトレース出力 (EventWriter)          [行181-199]
        |
        +-- afterSuite(TestDescriptor, TestResult)        [行104]
            +-- (if FAILURE) eventWriter.flush()          [行117]
            +-- (if FAILURE) ファイル全文をコンソール出力     [行119-131]
            +-- (if 最上位) 失敗テスト一覧出力               [行136-143]
            +-- reproductionLines.remove()                [行148]
            +-- eventWriter.close()                       [行149-156]
                +-- outputFile.delete()                   [行309]
```

### データフロー図

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

TestOutputEvent           --> onOutput()                    --> {FQCN}.out ファイル
(テスト実行中の出力)             |                                  (一時バッファ)
                                |
                                +-- REPRODUCE WITH 検出    --> reproductionLines
                                                               (メモリ内保持)

TestResult (FAILURE)      --> afterTest()                   --> System.err
(テスト失敗結果)                |                                  (再現コマンド)
                                |
                                +-- FullExceptionFormatter  --> EventWriter
                                    (スタックトレース整形)         (ファイル追記)

TestResult (スイート失敗)   --> afterSuite()                 --> System.out / System.err
                                |                                  (全出力のリプレイ)
                                |
                                +-- (最上位スイート)         --> Logger (taskLogger)
                                                               (失敗テスト一覧)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ErrorReportingTestListener.java | `buildSrc/src/main/java/org/opensearch/gradle/test/ErrorReportingTestListener.java` | ソース | テストエラーレポートの中心実装 |
| OpenSearchTestBasePlugin.java | `buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java` | ソース | テストタスクへのリスナー登録 |
| BuildPlugin.groovy | `buildSrc/src/main/groovy/org/opensearch/gradle/BuildPlugin.groovy` | ソース | ビルドプラグイン（ErrorReportingTestListener への参照あり） |
