diff --git a/src-python/docs/details/backend_test.md b/src-python/docs/details/backend_test.md index af0413c0..9cd03d81 100644 --- a/src-python/docs/details/backend_test.md +++ b/src-python/docs/details/backend_test.md @@ -3,13 +3,26 @@ ## 概要 VRCTアプリケーションのAPIエンドポイントを包括的にテストするためのモジュールです。メインループの各種機能をランダムアクセスでテストし、システムの安定性と堅牢性を検証します。 +### 2025-10 更新点(commit a54538e 反映) + +動的値が必要な `/set/data/*` エンドポイントに対し、事前に対応する `/get/data/...` エンドポイントを呼び出して最新値を取得し、`self.config_dict` にキャッシュしてからランダム選択する方式に変更しました。これにより以下が改善されています: + +- 翻訳/転写エンジン選択時の存在しないキー送信防止 +- 計算デバイス / 重みタイプ選択の安定化(リスト更新後に選択) +- マイク/スピーカー関連タイムアウト値の正常範囲チェック(取得した最新値を基準にバリデーション) +- Whisper / CTranslate2 重みタイプ辞書のキー集合変化への追従 + +例: `"/set/data/selected_translation_engines"` 試験前に `"/get/data/translation_engines"` を呼び、取得したリストから `random.choice()`。従来の初期キャッシュ依存から、実行時取得へ移行。 + ## 主要機能 ### Color クラス + - ANSIエスケープシーケンスを使用したコンソール出力色彩管理 - テスト結果の視覚的表示(成功・失敗・スキップ等) ### TestMainloop クラス + - APIエンドポイントの包括的テスト実行 - ランダムアクセステスト - テスト結果の記録・分析 @@ -18,23 +31,27 @@ VRCTアプリケーションのAPIエンドポイントを包括的にテスト ## 主要メソッド ### テスト実行メソッド + - `test_endpoints_on_off_all()`: ON/OFF系エンドポイントの全テスト - `test_set_data_endpoints_all()`: データ設定系エンドポイントの全テスト - `test_run_endpoints_all()`: 実行系エンドポイントの全テスト - `test_endpoints_all_random()`: 全エンドポイントのランダムアクセステスト ### 特定機能テスト + - `test_translate_all_language_pairs()`: 全言語ペアでの翻訳テスト - `test_endpoints_on_off_continuous()`: ON/OFF連続切り替えテスト - `test_endpoints_specific_random()`: 特定エンドポイントのランダムテスト ### 結果分析 + - `generate_summary()`: テスト結果のサマリー生成 - `record_test_result()`: テスト結果の記録 ## 使用方法 ### 基本的な使い方 + ```python # テストインスタンスを作成 test = TestMainloop() @@ -49,6 +66,7 @@ test.generate_summary() ``` ### ランダムテストの実行 + ```python # 全エンドポイントのランダムアクセステスト test.test_endpoints_all_random() @@ -58,6 +76,7 @@ test.test_endpoints_specific_random() ``` ## 依存関係 + - `mainloop`: VRCTメインループモジュール - `random`: ランダムテストデータ生成 - `time`: テスト間隔制御 @@ -65,31 +84,48 @@ test.test_endpoints_specific_random() ## テスト対象エンドポイント ### 制御系 + - `/set/enable/*`: 機能有効化 - `/set/disable/*`: 機能無効化 ### データ設定系 + - `/set/data/*`: 各種設定データの更新 +動的取得対象(代表例): + +- `selected_translation_engines` → `/get/data/translation_engines` +- `selected_transcription_engine` → `/get/data/transcription_engines` +- `selected_translation_compute_device` → `/get/data/translation_compute_device_list` +- `ctranslate2_weight_type` → `/get/data/selectable_ctranslate2_weight_type_dict` +- `whisper_weight_type` → `/get/data/selectable_whisper_weight_type_dict` +- `selected_mic_host` / `selected_mic_device` → `/get/data/mic_host_list` / `/get/data/mic_device_list` +- `selected_speaker_device` → `/get/data/speaker_device_list` + ### 実行系 + - `/run/*`: 各種機能の実行 ### データ削除系 + - `/delete/data/*`: データの削除 ## 注意事項 + - テスト実行前に`config.json`を削除して初期化 - 重いAIモデルを使用するテストは実行時間に注意 - ランダムテストは指定回数(デフォルト1000-10000回)実行される - テスト終了時は自動的にすべての機能を無効化する ## エラーハンドリング + - 各テストは独立して実行され、一つの失敗が全体に影響しない - 期待されるステータスコードと実際の結果を比較 - VRAM不足等のリソースエラーも適切にハンドリング ## テスト結果の分類 + - **PASS**: 期待されるステータスコードと一致 - **ERROR**: 期待されるステータスコードと不一致 - **SKIP**: テスト実行不可(401ステータス) -- **Invalid**: 無効なエンドポイント(404ステータス) \ No newline at end of file +- **Invalid**: 無効なエンドポイント(404ステータス) diff --git a/src-python/docs/mainloop.md b/src-python/docs/mainloop.md index 4536d49e..8eb3691b 100644 --- a/src-python/docs/mainloop.md +++ b/src-python/docs/mainloop.md @@ -9,9 +9,11 @@ ### 1. グローバル変数 #### `run_mapping` (dict) + フロントエンドへの通知用エンドポイントマッピング。Controllerが `run()` コールバックを通じてフロントエンドに状態変化を通知する際に使用。 **主要なエンドポイント:** + - `/run/enable_translation` - 翻訳機能の有効/無効状態 - `/run/transcription_mic_message` - マイク音声認識結果 - `/run/transcription_speaker_message` - スピーカー音声認識結果 @@ -19,11 +21,14 @@ - `/run/initialization_complete` - 初期化完了通知 #### `mapping` (dict) + フロントエンドからのリクエストを処理する関数マッピング。各エンドポイントに対して: + - `status`: ロック状態(True: 処理可能, False: ロック中) - `variable`: 実行する Controller メソッド **エンドポイント分類:** + - `/get/data/*` - 設定値の取得(初期化時に使用) - `/set/data/*` - 設定値の更新 - `/set/enable/*` - 機能の有効化 @@ -31,6 +36,7 @@ - `/run/*` - アクション実行(メッセージ送信、ダウンロード等) #### `init_mapping` (dict) + 初期化時に実行される `/get/data/*` エンドポイントのサブセット。アプリケーション起動時に全設定値をフロントエンドに送信するために使用。 ### 2. Mainクラス @@ -38,11 +44,13 @@ #### コンストラクタ `__init__(controller_instance, mapping_data, worker_count)` **パラメータ:** + - `controller_instance`: Controller インスタンス - `mapping_data`: エンドポイントマッピング辞書 - `worker_count`: ハンドラワーカースレッド数(デフォルト: 3) **初期化処理:** + 1. リクエストキュー (`Queue[Tuple[str, Any]]`) の作成 2. 停止イベント (`Event`) の作成 3. エンドポイント別 Lock の生成: @@ -50,6 +58,7 @@ - 同一機能の有効化/無効化リクエストが競合しないよう排他制御 **正規化ロジックの例:** + ```python "/set/enable/translation" → "/lock/set/translation" "/set/disable/translation" → "/lock/set/translation" @@ -61,6 +70,7 @@ **責務:** stdin から JSON リクエストを読み取り、キューに投入 **処理フロー:** + 1. `sys.stdin.readline()` でブロッキング読み取り 2. JSON パース (`json.loads()`) 3. エンドポイントとデータを抽出 @@ -69,6 +79,7 @@ 6. キューに投入 `self.queue.put((endpoint, data))` **エラー処理:** + - JSON パースエラー: ログ出力して継続 - EOF 到達: 0.1秒待機して再試行 - その他の例外: `errorLogging()` でトレースバック記録 @@ -80,6 +91,7 @@ **責務:** キューからリクエストを取り出し、適切なロックを取得して処理 **処理フロー:** + 1. キューから `(endpoint, data)` を取得(0.5秒タイムアウト) 2. エンドポイントを正規化キーに変換 3. 対応する Lock を取得試行(非ブロッキング) @@ -89,10 +101,12 @@ 5. レスポンスを stdout に出力 (`printResponse()`) **排他制御の意義:** + - 例: 翻訳機能の有効化中に無効化リクエストが来た場合、無効化は待機 - 異なる機能のリクエストは並列実行可能 **再キューロジック:** + - status == 423 (Locked): 0.1秒待機して再キュー - これにより、初期化中の設定変更リクエストが適切にリトライされる @@ -103,6 +117,7 @@ **責務:** 実際のビジネスロジック実行 **処理フロー:** + 1. `mapping` から対応するハンドラを取得 2. エンドポイントが存在しない → status 404 3. ハンドラの `status` が False → status 423 (Locked) @@ -111,15 +126,33 @@ 6. status と result を抽出して返却 **エラー処理:** + - 例外発生時: `errorLogging()` でトレースバック記録、status 500 を返却 -#### `start()` / `stop(wait)` メソッド +#### `start()` / `stop(wait)` メソッド(2025-10 仕様) **start():** -- `startReceiver()` - stdin 読み取りスレッド起動 -- `startHandler()` - ハンドラワーカースレッド起動 +コミット `5ce281e` によりシンプルな「メインループ維持」機能のみになりました。`startReceiver()` / `startHandler()` は `start()` 内では呼ばれません。呼び出し側(`__main__` ブロックや外部プロセスマネージャ)で必要なスレッドを起動した後、`start()` を呼び出して以下の挙動を提供します。 + +```python +def start(self) -> None: + try: + while not self._stop_event.is_set(): + time.sleep(1) + except KeyboardInterrupt: + self.stop() +``` + +ポイント: + +- メインスレッドを 1 秒スリープしつつ存続させ、Ctrl+C(KeyboardInterrupt)で安全に停止へ遷移。 +- 既存の receiver / handler スレッド二重起動のリスクを除去。 +- ライフサイクル明確化: スレッド起動責務を `start()` から分離しテスト・拡張容易性向上。 + +従来仕様ドキュメントの「`start()` がスレッドを直接起動する」記述は廃止済みであり、最新コードでは不要です。 **stop(wait):** + - `_stop_event.set()` - 全スレッドに停止シグナル送信 - 各スレッドを `join(timeout=remaining)` で待機(最大 `wait` 秒) @@ -131,16 +164,22 @@ 2. `startReceiver()` - stdin リスニング開始 3. `startHandler()` - リクエスト処理開始 4. **Watchdog 設定:** - - `controller.setWatchdogCallback(main_instance.stop)` + - `controller.setWatchdogCallback(main_instance.stop)` - Watchdog がタイムアウトした場合にプロセス全体を停止 5. **Controller 初期化:** - - `controller.init()` + - `controller.init()` - Model の遅延初期化、デバイス列挙、ネットワーク接続チェック - `init_mapping` のすべてのエンドポイントを実行して初期設定をフロントエンドに送信 6. **マッピングのアンロック:** - すべての `mapping[key]["status"]` を True に設定 - これにより初期化中だった機能が利用可能になる -7. `main_instance.start()` - 実質的には何もしない(既に起動済み) +7. `main_instance.start()` - メインループ維持のみ(receiver / handler は事前に起動済み) + +### Watchdog エンドポイント連携 + +バックエンドはクライアント側から `/run/feed_watchdog` が一定間隔(例: 30 秒)で送信される前提の設計が可能です。テストクライアント(`test_client.py`)は初期化完了後にバックグラウンドスレッドでこのエンドポイントを自動送信し、内部監視(`controller` 側の Watchdog コールバック)タイムアウト防止を行います。 + +ログ上は通常レスポンス非取得(fire & forget)となるため、負荷低減のためにハートビート頻度調整が可能です。 ## 並列処理とスレッドセーフティ @@ -182,6 +221,7 @@ ``` **フィールド:** + - `endpoint`: 実行するエンドポイント(必須) - `data`: パラメータ(オプション、Base64 エンコード) @@ -196,12 +236,13 @@ ``` **フィールド:** + - `status`: HTTP ステータスコード相当 - - 200: 成功 - - 400: バリデーションエラー - - 404: 無効なエンドポイント - - 423: ロック中(リトライされる) - - 500: 内部エラー + - 200: 成功 + - 400: バリデーションエラー + - 404: 無効なエンドポイント + - 423: ロック中(リトライされる) + - 500: 内部エラー - `endpoint`: リクエストされたエンドポイント - `result`: 処理結果(型はエンドポイントに依存) @@ -218,52 +259,62 @@ ## エラーハンドリング ### 1. JSON パースエラー + - **発生箇所:** `receiver()` の `json.loads()` - **処理:** `errorLogging()` でトレースバック記録、リクエストをスキップ ### 2. ハンドラ実行エラー + - **発生箇所:** `_call_handler()` の `handler["variable"](data)` -- **処理:** - - `errorLogging()` でトレースバック記録 - - status 500 と "Internal error" を返却 - - プロセスは継続 +- **処理:** + - `errorLogging()` でトレースバック記録 + - status 500 と "Internal error" を返却 + - プロセスは継続 ### 3. JSON シリアライズエラー + - **発生箇所:** `printResponse()` の `json.dumps()` - **処理:** - - エラーログに詳細を記録 - - フォールバック JSON を出力(status 500) - - プロセスは継続 + - エラーログに詳細を記録 + - フォールバック JSON を出力(status 500) + - プロセスは継続 ### 4. EOF (stdin 終了) + - **発生箇所:** `receiver()` の `readline()` - **処理:** 0.1秒待機して再試行(フロントエンドの再起動待ち) ## パフォーマンス最適化 ### 1. 複数ワーカースレッド + - デフォルト3スレッドで並列処理 - CPU バウンドな処理(翻訳、文字起こし)を効率化 ### 2. 非ブロッキングロック + - ロック競合時に即座に再キュー - スレッドのブロッキング時間を最小化 ### 3. 処理安定化待機 + - 各ハンドラ実行後に 0.2秒待機 - 連続リクエストによる競合状態を回避 ## 制限事項 ### 1. 初期化中の制限 + - `mapping[key]["status"] = False` の間はリクエストが 423 でリトライされる - 初期化完了まで最大数秒のレイテンシが発生 ### 2. stdin の単方向性 + - stdin → キュー → ハンドラの一方向フロー - 複数のフロントエンドからの同時接続は非対応 ### 3. シリアル実行の保証 + - 同一エンドポイントのリクエストは排他的に実行されるが、 - 異なるエンドポイントは並列実行される可能性がある - 依存関係のある操作は呼び出し側で順序制御が必要 @@ -294,14 +345,17 @@ ## 今後の拡張性 ### 1. 双方向通信 + - WebSocket への移行でリアルタイム通知を改善 - stdin/stdout は互換性のため維持 ### 2. 動的ワーカー数調整 + - キューの深さに応じてスレッド数を自動調整 - CPU 負荷に応じた適応的なスケーリング ### 3. 優先度キュー + - 重要なリクエスト(エラー通知等)を優先処理 - `queue.PriorityQueue` への移行 @@ -315,6 +369,7 @@ ## コーディング規約 本ファイルは以下の規約に従う: + - PEP 8 スタイルガイド - 型ヒント (`typing` モジュール) - Docstring は Google スタイル @@ -323,6 +378,7 @@ ## テストシナリオ ### 1. 基本動作テスト + ```python # stdin に JSON を送信 echo '{"endpoint": "/get/data/version", "data": null}' | python mainloop.py @@ -330,14 +386,17 @@ echo '{"endpoint": "/get/data/version", "data": null}' | python mainloop.py ``` ### 2. 並列リクエストテスト + - 複数の設定変更リクエストを同時送信 - すべてが正常に処理されることを確認 ### 3. ロック競合テスト + - 翻訳の有効化と無効化を連続送信 - 両方が排他的に実行されることを確認 ### 4. エラー回復テスト + - 不正なJSON、無効なエンドポイント、不正なデータを送信 - プロセスがクラッシュせずエラーレスポンスを返すことを確認 diff --git a/src-python/docs/test_client.md b/src-python/docs/test_client.md new file mode 100644 index 00000000..b552815c --- /dev/null +++ b/src-python/docs/test_client.md @@ -0,0 +1,139 @@ +# test_client.py ドキュメント + +## 概要 +`test_client.py` は stdin/stdout 経由でバックエンド (`mainloop.py`) と通信し、各種 API エンドポイントを自動 / 半自動でテストするためのクライアントユーティリティ。初期化完了待機、ログ (status=348) 展開表示、サイレントモード、結果エクスポート(JSON/CSV)、Watchdog ハートビート送信 (/run/feed_watchdog) などの補助機能を備える。 + +## 主な責務 +- バックエンドプロセス起動と初期化完了待機 (`/run/initialization_complete`) +- エンドポイント単発送信/応答待機 (Base64 エンコード/デコード) +- status=348 ログエントリの全文展開表示 +- タイムアウト / 例外発生時の復旧メッセージ出力 +- Watchdog ハートビート送信スレッド管理 +- 自動テスター (`AutomatedEndpointTester`) による包括的エンドポイント試験 +- テスト結果の JSON / CSV エクスポート (インタラクティブ指定) + +## クラス構成 +### `Color` +ANSIカラー定数を定義し、可読性の高い出力を実現。 + +### `TestClient` +バックエンドとの 1 プロセス・1 チャンネル通信を管理。 + +| 属性 | 役割 | +|------|------| +| `process` | 起動した Python バックエンド subprocess | +| `_watchdog_stop_event` | Watchdog スレッド停止制御用 Event | +| `_watchdog_thread` | /run/feed_watchdog 送信スレッド | + +#### 初期化フロー +1. `subprocess.Popen([sys.executable, 'mainloop.py'], ...)` でバックエンド起動 +2. `_wait_for_initialization()` を呼び出し `/run/initialization_complete` 受信まで待機 + - VRCT_INIT_TIMEOUT 環境変数があれば soft timeout として利用 (超過時 WARN のみ) + - 30 秒間隔で進捗ログ (最後に受信した endpoint) + - status=348 レコードは全文 JSON 展開 +3. 初期化完了後 `_start_watchdog()` がバックグラウンドで /run/feed_watchdog を 30 秒間隔送信 + +#### 重要メソッド +- `send_request(endpoint, data=None, timeout=30.0, silent=False)` + - リクエスト JSON を構築し送信 + - `data` は JSON シリアライズ → Base64 → `data` フィールド + - 指定 endpoint のレスポンス行まで逐次読み取り (他 endpoint のログ行は通過) + - status=348 の場合ログとして全文表示(silent=False のとき) + - タイムアウト時 504 レスポンスを合成 + +- `_wait_for_initialization(timeout=None)` + - 無期限または soft timeout 待機 + - プロセス死亡検知で RuntimeError + +- `_start_watchdog()` / `cleanup()` + - Watchdog スレッド開始と安全停止 (Event セット後 join) + +### `AutomatedEndpointTester` +`backend_test.py` のロジック移植版(stdin/stdout プロトコル向け)。 + +| 属性 | 説明 | +|------|------| +| `silent` | True ならクライアント側詳細出力を抑制 | +| `export_path` | JSON エクスポート先 (None なら未出力) | +| `export_csv` | CSV 追加出力有無 | +| `results` | 収集したテストレコード一覧 | + +#### エンドポイント分類 (ハードコード暫定) +- 有効/無効化: `/set/enable/*`, `/set/disable/*` +- 設定更新: `/set/data/*` +- 実行系: `/run/*` +- 削除系: `/delete/data/*` + +#### 主メソッド +- `test_validity_single(endpoint)` 有効/無効化系単一試験 +- `test_set_data_single(endpoint)` 設定更新系単一試験(事前に動的取得が必要な値は `/get/data/...` を呼び最新値をキャッシュ) +- `test_run_single(endpoint)` 実行系単一試験 +- `test_delete_single(endpoint)` 削除系単一試験 +- `run_all()` 全カテゴリ順次実行 +- `run_random(count=1000)` 全エンドポイントプールからランダム選択 +- `run_specific_random(category, count)` 指定カテゴリ内ランダム +- `summary()` 結果集計出力および必要な場合 JSON/CSV エクスポート + +#### 結果レコード構造 +```json +{ + "endpoint": "/set/data/transparency", + "status": 200, + "result": 85, + "expected": "status==200" +} +``` + +CSV 例: +``` +endpoint,status,expected,success +/set/data/transparency,200,status==200,True +``` + +## status=348 ログ取り扱い +- 初期化待機中: 展開してインデント付き表示 +- リクエスト応答処理中: silent=False ならログエントリ全文を優先表示 +- 通常 API 応答 (status != 348) との区別を明確化しデバッグ容易化 + +## Watchdog ハートビート +- 30 秒間隔で `/run/feed_watchdog` を送信 (fire-and-forget) +- 送信失敗時は警告を表示しループ終了 +- クライアント終了時に停止イベントをセットしスレッド join でリーク防止 + +## エクスポート機能 +### JSON エクスポート +- `export_path` が指定されていれば `results` を UTF-8 / ensure_ascii=False で整形出力 +- フィールド: endpoint, status, result, expected, success + +### CSV エクスポート +- JSON パスから拡張子置換(`.csv`)で派生 (内部 `_derive_csv_path` 相当ロジック) +- 成功判定: `status` と `expected` 文字列評価結果に依存 (単純比較) + +## 例外 / エラー処理方針 +| ケース | 対応 | +|--------|------| +| バックエンド終了検知 | 初期化待機中: RuntimeError を投げる / 通常通信: 500 レスポンス合成 | +| JSONDecodeError | ログ行扱いでスキップ (初期化中は進捗ログとして表示) | +| BrokenPipe / OSError | 通信切断とみなして 500 レスポンス返却 | +| タイムアウト | 504 レスポンス返却 (endpoint 同梱) | + +## 制限事項 +- エンドポイント一覧は動的取得ではなくハードコード (将来的改善余地) +- レスポンスの並行受信は未対応(1 リクエスト同期待ち) +- status=200 以外の詳細な意味的検証は限定的 (expected = 単純条件) +- Watchdog レスポンスは読み取らないため送信失敗検知は例外経路のみ + +## 今後の改善候補 +1. CLI 引数サポート (`--mode random --count 500 --silent --export result.json`) +2. 動的エンドポイント列挙 API 追加後の自動反映 +3. リトライポリシー (指数バックオフ) 導入 +4. 応答時間測定とパフォーマンスレポート出力 +5. 並列テスト実行 (複数 subprocess / async IO) + +## 参考 +- `backend_test.py` : 元ロジック +- `utils.py` : status=348 ログ出力仕様 +- `mainloop.md` : 通信プロトコル詳細 + +## まとめ +`test_client.py` は VRCT バックエンドに対する包括的なテストおよび運用補助を 1 ファイルで実現するツール。初期化待機の堅牢化(無期限 + soft timeout)、Watchdog ハートビート、ログ展開、静音化オプション、結果エクスポートにより長時間動作・回帰試験・CI への組み込みを容易にする基盤を提供する。