merge: integrate orchestration branch
This commit is contained in:
commit
2b339247ed
|
|
@ -0,0 +1 @@
|
|||
{"id":"orch-plan-20260613-141646-1","ticket_id":"00001KSKBP9YG","kind":"accepted_plan","accepted_plan":{"summary":"E2E harness Ticket を inprogress 受理する。Playwright-like declarative API、independent opt-in crate、read-only structured TUI test events、PTY input、failure artifacts、Panel mouse selection / quit latency regression scenario を最小 vertical slice として実装する。root/original workspace では作業しない。","branch":"ticket-00001KSKBP9YG-e2e-harness","worktree":"/home/hare/Projects/yoi/.worktree/e2e-harness","role_plan":"Orchestrator が dedicated child worktree を作成し、Coder Pod に E2E harness / TUI observability / CLI test hook に必要な限定 write scope を渡す。Coder は first slice として declarative PTY Panel harness と mouse/quit regression scenarios を優先し、Reviewer は production contamination と read-only observability invariant を重点確認する。"},"author":"orchestrator","at":"2026-06-13T14:16:46Z"}
|
||||
21
.yoi/tickets/00001KSKBP9YG/artifacts/relations.json
Normal file
21
.yoi/tickets/00001KSKBP9YG/artifacts/relations.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"version": 1,
|
||||
"relations": [
|
||||
{
|
||||
"ticket_id": "00001KSKBP9YG",
|
||||
"kind": "related",
|
||||
"target": "00001KV0723PC",
|
||||
"note": "Panel quit latency regression exposed need for measured PTY E2E, ready/barrier synchronization, and failure artifacts.",
|
||||
"author": "orchestrator",
|
||||
"at": "2026-06-13T13:56:37Z"
|
||||
},
|
||||
{
|
||||
"ticket_id": "00001KSKBP9YG",
|
||||
"kind": "related",
|
||||
"target": "00001KV072V89",
|
||||
"note": "Panel mouse selection regression exposed need for TUI/Panel PTY E2E with structured UI feedback and mouse input assertions.",
|
||||
"author": "orchestrator",
|
||||
"at": "2026-06-13T13:56:37Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
Approve.
|
||||
|
||||
Delta reviewed:
|
||||
- Re-reviewed the fix commit `b30b43b9 test: cfg-gate e2e observer payloads` after the earlier request-changes review.
|
||||
- Inspected the updated observer module boundary and call sites in `crates/tui/src/lib.rs` and `crates/tui/src/multi_pod.rs`, plus the unchanged harness/tests in `tests/e2e`.
|
||||
|
||||
Evidence:
|
||||
- `e2e_observer` is now only compiled from `crates/tui/src/lib.rs` under `#[cfg(feature = "e2e-test")]`; the previous normal-build no-op facade was removed.
|
||||
- Observer payload construction is gated at call sites with `#[cfg(feature = "e2e-test")]`, including `panel_ready`, `selection_changed`, `action_requested`, `quit_requested`, and `emit_rows_rendered` calls.
|
||||
- Panel E2E DTOs/helpers (`PanelE2eRowKey`, `PanelE2eRect`, `PanelE2eRenderedRow`, `PanelE2eRowsRendered`, `App::emit_rows_rendered`) are now behind `#[cfg(feature = "e2e-test")]`, so the normal panel render path no longer builds row snapshots or retains that runtime helper path.
|
||||
- The background-task hold seam is still feature-gated: `check_background_task_hold` and `release_background_task_hold` calls are under `#[cfg(feature = "e2e-test")]`, and `YOI_TUI_TEST_HOLD_BACKGROUND_TASK` behavior lives in the gated observer module.
|
||||
- Mouse capture tracking remains intact in the harness: it tracks `?1000h` and `?1006h`, `click(...)` requires both capture modes before injecting PTY bytes, the test waits for rendered rows, asserts `selection_changed`, and asserts no `action_requested` dispatch.
|
||||
- Quit-latency coverage remains intact: the test waits for `panel_ready`, then verifies an actual pending `reload` background-task barrier before sending Ctrl+C through the PTY and asserting bounded exit.
|
||||
- The production/non-production boundary now satisfies the Ticket intent: the harness remains opt-in, observability is read-only and feature-gated, and no UI input/action path is bypassed.
|
||||
|
||||
Validation run in `/home/hare/Projects/yoi/.worktree/e2e-harness`:
|
||||
- `git diff --check 134e8b8b..HEAD` — passed.
|
||||
- `cargo fmt --check` — passed.
|
||||
- `cargo check -p tui --all-targets` — passed.
|
||||
- `cargo check -p yoi --all-targets` — passed.
|
||||
- `cargo check -p tui --all-targets --features e2e-test` — passed.
|
||||
- `cargo check -p yoi --all-targets --features e2e-test` — passed.
|
||||
- `cargo build -p yoi --features e2e-test` — passed.
|
||||
- `YOI_E2E_BIN=/home/hare/Projects/yoi/.worktree/e2e-harness/target/debug/yoi cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` — passed; 2 tests passed.
|
||||
- `cargo check -p yoi-e2e --all-targets --features e2e` — passed.
|
||||
|
||||
No source changes were made during re-review.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
Request changes.
|
||||
|
||||
Evidence reviewed:
|
||||
- Inspected Ticket record and `git diff 134e8b8b..HEAD` for commits `96561897` and `10a1c383`.
|
||||
- `tests/e2e` provides a credible first declarative harness (`PanelHarness::spawn`, `wait_for`, `wait_for_rows`, `click`, `press`, `expect_selection`, `expect_exit_within`, artifacts/metadata/input/output/event logs). This is not merely a fixed-sleep shell script.
|
||||
- Mouse-selection scenario waits for rendered rows, verifies both normal mouse and SGR mouse capture before `click`, sends the click through PTY bytes, waits for `selection_changed`, and asserts no `action_requested` dispatch.
|
||||
- Quit-latency scenario creates a real feature-gated background-task hold barrier, waits until the task is actually waiting before sending Ctrl+C through the PTY, and measures bounded exit latency.
|
||||
- `yoi-e2e` is opt-in via package feature/test `required-features = ["e2e"]`; e2e tests are outside default members. `YOI_TUI_TEST_EVENTS` and `YOI_TUI_TEST_HOLD_BACKGROUND_TASK` env behavior is behind `tui/e2e-test` / `yoi/e2e-test` feature gates, and the hook is observability-only.
|
||||
|
||||
Required change:
|
||||
- The normal production build still contains/evaluates too much e2e harness glue. In non-`e2e-test` builds, `crates/tui/src/e2e_observer.rs` exposes no-op `emit`/hold functions, but call sites still execute test-specific data construction. In particular `App::emit_rows_rendered` and its panel row key/rect DTOs are compiled unconditionally and `app.emit_rows_rendered()` is called from the panel render path, causing row snapshots to be built every draw even though emission is a no-op. Selection/action/quit call sites also construct `serde_json::json!` payloads before the no-op facade. This violates the recorded boundary that production binaries should not contain harness logic and production-side hooks must be feature-gated/compiled out for normal builds.
|
||||
- Please cfg-gate the call sites/helpers/DTOs, or use a lazy cfg-gated macro/helper so normal builds do not evaluate or retain e2e event payload construction. A tiny compile-only facade is acceptable only if it does not execute or allocate e2e-specific work and does not keep harness DTO logic in the normal runtime path.
|
||||
|
||||
Validation run in `/home/hare/Projects/yoi/.worktree/e2e-harness`:
|
||||
- `git diff --check 134e8b8b..HEAD` — passed.
|
||||
- `cargo fmt --check` — passed.
|
||||
- `cargo check -p tui --all-targets` — passed.
|
||||
- `cargo check -p yoi --all-targets` — passed.
|
||||
- `cargo build -p yoi --features e2e-test` — passed.
|
||||
- `YOI_E2E_BIN=/home/hare/Projects/yoi/.worktree/e2e-harness/target/debug/yoi cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` — passed.
|
||||
- `cargo check -p tui --all-targets --features e2e-test` — passed.
|
||||
- `cargo check -p yoi --all-targets --features e2e-test` — passed.
|
||||
- `cargo check -p yoi-e2e --all-targets --features e2e` — passed.
|
||||
|
||||
No source changes were made during review.
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
---
|
||||
title: "E2E テストハーネス"
|
||||
state: "planning"
|
||||
state: 'done'
|
||||
created_at: "2026-05-27T00:00:02Z"
|
||||
updated_at: "2026-05-27T00:00:02Z"
|
||||
updated_at: '2026-06-13T15:45:26Z'
|
||||
queued_by: 'yoi ticket'
|
||||
queued_at: '2026-06-13T14:17:34Z'
|
||||
---
|
||||
|
||||
## Migration reference
|
||||
|
|
|
|||
|
|
@ -5,3 +5,528 @@
|
|||
Migrated from tickets/e2e-harness.md. No legacy review file was present at migration time.
|
||||
|
||||
---
|
||||
|
||||
<!-- event: decision author: orchestrator at: 2026-06-13T13:56:37Z -->
|
||||
|
||||
## Decision
|
||||
|
||||
E2E scope refinement: TUI/Panel PTY 自動化もこの Ticket の範囲に含める。
|
||||
|
||||
背景:
|
||||
- Panel mouse selection / Panel Quit latency の直近不具合では、focused unit test と code-path review だけで `done` 判定し、実端末経路の positive validation / measured validation が不足していた。
|
||||
- 既存本文の「TUI バイナリを PTY で叩く方針は採らない」は、blind な固定入力スクリプトや GUI 代替としての ad hoc 操作を避ける意図として扱い、TUI/Panel の実プロセス・実端末入力を検証する automated PTY harness は本 Ticket に含める。
|
||||
- Pod protocol/subprocess E2E と TUI/Panel PTY E2E は harness の部品は違うが、どちらも「実プロセスを spawn して user-visible boundary を検証する」ため、別 umbrella に分けず、この E2E harness Ticket の phase として扱う。
|
||||
|
||||
方針:
|
||||
- 固定 sleep + 固定 input だけの PTY script は採用しない。Harness は UI からの structured feedback を待ってから入力を送る。
|
||||
- TUI/Panel には test-only / opt-in の observability route を追加する。これは UI action を bypass する command channel ではなく、状態観測・同期・失敗診断のための read-only probe とする。
|
||||
- 実際の keyboard / mouse / Ctrl+C 入力は PTY 経由で送る。Probe は `first_draw`、`panel_snapshot_ready`、`rows_rendered`、`selection_changed`、`actionbar_changed`、`background_task_started/finished/aborted`、`quit_requested`、`terminal_cleanup_started/finished`、`exit` などの structured event を JSONL 等で吐く。
|
||||
- Mouse E2E は `rows_rendered` の row key と screen rect を待ち、SGR mouse sequence を PTY に送って、`selection_changed` と screen/actionbar/detail の変化を確認する。
|
||||
- Quit latency E2E は `panel_ready` / background work pending などの barrier event を待ってから `Ctrl+C` / `Ctrl+D` を送り、`quit_requested -> exit` の elapsed を測る。非本質 background work が abort/drop され、terminal cleanup が行われることも event で確認する。
|
||||
- Screen output は `vt100`/`vte` 等の terminal parser で secondary oracle / artifact として保存する。主要同期は structured event に寄せる。
|
||||
- Test probe は `--tui-test-events <path>` 等の明示的な hidden/dev/test flag か `e2e` feature 配下の構成で有効化し、通常実行・model context・Ticket authority・Pod protocol には影響させない。
|
||||
- Failure artifact として event JSONL、input log、screen dump、stdout/stderr、runtime/data/workspace tmpdir の relevant tree、timing summary を保存する。
|
||||
|
||||
受け入れ条件の追加案:
|
||||
- `cargo test -p e2e --features e2e`(または同等の opt-in command)で実 `yoi panel` を PTY 上で起動し、structured probe feedback を待ってから入力する harness が動く。
|
||||
- Panel row click E2E: rendered row rect を使って SGR mouse click を送り、selected row が変わることを assertion する。
|
||||
- Panel quit latency E2E: ready/pending background work barrier 後に Quit 入力を送り、exit latency が閾値内で、nonessential background work が quit を block しないことを assertion する。
|
||||
- Fixed sleep だけに依存する test は不可。ready/barrier event が来なければ screen dump と event log を artifact として失敗する。
|
||||
- Probe は read-only observability であり、input/action path を bypass しないことを reviewer が確認する。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: decision author: orchestrator at: 2026-06-13T14:03:56Z -->
|
||||
|
||||
## Decision
|
||||
|
||||
E2E design decision: Playwright-like declarative test API と production binary 非混入を前提にする。
|
||||
|
||||
Decision:
|
||||
- E2E は ad hoc shell / fixed sleep script ではなく、Rust の独立 crate から宣言的に scenario を書ける構造にする。
|
||||
- 例: `PanelHarness::spawn(...)`、`panel.wait_for(PanelReady)`、`panel.click(row("ticket", id))`、`panel.expect_selection(...)`、`panel.press(CtrlC)`、`panel.expect_exit_within(...)` のように、Playwright 的な wait/action/assertion API を提供する。
|
||||
- Harness crate は production binary / normal library API から独立させる。想定配置は `tests/e2e/` または `crates/e2e_harness` + integration tests で、通常 build / release package / normal `yoi` binary に test harness logic を混ぜない。
|
||||
- 本番 binary に混ぜる必要があるものは、原則として「既存 TUI state から read-only diagnostic event を emit するための最小 test hook」に限定する。その hook も normal runtime では無効で、明示 feature / hidden dev flag / cfg(test/e2e) 等でしか有効化しない。
|
||||
- E2E harness は production code の内部関数を直接呼んで state mutation しない。入力は PTY、観測は structured test events / terminal screen parser、assertion は harness 側で行う。
|
||||
- Structured events は protocol authority ではなく test observability artifact として扱う。Ticket/Pod authority や user-visible semantics を変えない。
|
||||
|
||||
Rationale:
|
||||
- 今回の Panel mouse / Quit latency の失敗は、unit/focused tests と code-path review だけでは user-visible terminal behavior を保証できないことを示した。
|
||||
- 一方で fixed sleep + input script は再現性・診断性が低く、ready 状態や background work barrier を確認できない。
|
||||
- Playwright-like API なら、test は「何を待ち、何を入力し、何を観測するか」を宣言的に表現でき、失敗時に event log / screen dump / timing artifact を残せる。
|
||||
- Production binary への混入を避けることで、release behavior / binary size / authority surface / model-visible surfaces を汚さない。
|
||||
|
||||
Acceptance refinement:
|
||||
- E2E test author が fixed sleep ではなく `wait_for` / `expect` / `within` を使って Panel/TUI scenario を書ける。
|
||||
- Mouse selection と Quit latency の regression は、この declarative harness API 上の scenario として表現される。
|
||||
- Test-only observability route は opt-in であり、release/normal execution では無効または到達不能であることを reviewer が確認する。
|
||||
- Failure artifact に scenario step、last observed events、screen snapshot、timing、binary path、workspace/runtime dirs が含まれる。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: decision author: orchestrator at: 2026-06-13T14:16:24Z -->
|
||||
|
||||
## Decision
|
||||
|
||||
Routing decision: implementation_ready
|
||||
|
||||
Reason:
|
||||
- ユーザーが E2E harness を 1 Ticket として扱い、Playwright-like declarative API、structured feedback、production binary 非混入を前提に進めることを明示した。
|
||||
- Ticket body は旧名/旧構成を含むが、thread decisions により現在の binding direction は明確化済み: Pod subprocess/protocol E2E と TUI/Panel PTY E2E を同じ harness Ticket の phase として扱う。
|
||||
- 直近の Panel mouse selection / Panel Quit latency の regression から、実プロセス・実 PTY・structured event feedback・failure artifact を最小スライスに含める必要がある。
|
||||
- `TicketRelationQuery` では durable blocker はなく、関連 Ticket は context link のみ。
|
||||
- Orchestrator worktree は clean。implementation side effect は state acceptance 後に dedicated child worktree で行う。
|
||||
|
||||
Evidence checked:
|
||||
- Ticket body / thread decisions。
|
||||
- relation records: `00001KV072V89` / `00001KV0723PC` への related links。
|
||||
- orchestration plan records: なし。
|
||||
- current workspace state: Orchestrator worktree clean、queued/inprogress work なし、implementation child Pods なし。
|
||||
- project context: AGENTS guidance の E2E 未設計、prompt/resource boundary、production binary contamination 回避方針、直近 Panel validation failure records。
|
||||
|
||||
IntentPacket:
|
||||
|
||||
Intent:
|
||||
- Yoi の E2E testing foundation を、実プロセス spawn と TUI/Panel PTY automation の両方を扱える opt-in harness として導入する。
|
||||
- 最初の vertical slice は、Playwright-like declarative API、structured UI feedback、failure artifact、Panel mouse selection / Panel quit latency の regression scenario を実装できる形にする。
|
||||
|
||||
Binding decisions / invariants:
|
||||
- E2E harness は independent crate / test surface とし、normal release / normal `yoi` binary に harness logic を混ぜない。
|
||||
- 本番 binary 側に必要な変更は opt-in read-only observability hook に限定する。UI action/state mutation を test hook で bypass しない。
|
||||
- 実入力は PTY 経由で送る。structured event は synchronization / assertion / artifact のための観測情報であり、authority channel ではない。
|
||||
- fixed sleep + fixed input だけの blind script を acceptance にしない。
|
||||
- Pod/Ticket authority、prompt/resource boundary、public runtime behavior を E2E 都合で歪めない。
|
||||
|
||||
Requirements / acceptance criteria:
|
||||
- E2E author が Rust code で `spawn` / `wait_for` / `click` / `press` / `expect_*` / `within` を使って scenario を宣言的に書ける。
|
||||
- Opt-in command(例: `cargo test -p e2e --features e2e` または同等)で通常 CI 既定から分離される。
|
||||
- TUI/Panel test は panel ready / rows rendered / selection changed / background task / quit events など structured feedback を待ってから PTY input を送る。
|
||||
- Panel mouse selection regression と Panel quit latency regression の少なくとも skeleton または minimal passing scenario が declarative harness 上で表現される。
|
||||
- Failure artifact として event log、input log、screen dump、timing、binary path、workspace/runtime dirs が残る。
|
||||
- Production binary contamination がないこと、または opt-in hook が normal runtime で無効/到達不能であることを reviewer が確認できる。
|
||||
|
||||
Implementation latitude:
|
||||
- `tests/e2e/` crate か `crates/e2e_harness` + integration tests のどちらに置くかは Coder が codebase constraints を見て選んでよい。ただし normal build/release contamination は避ける。
|
||||
- PTY crate、terminal parser、event JSONL format、fixture workspace builder の具体設計は Coder が選んでよい。
|
||||
- 最初の slice は full provider E2E ではなく、Panel/TUI harness と minimal process lifecycle / artifact foundation を優先してよい。
|
||||
- 既存旧名 `INSOMNIA_*` / `pod` references は現在の `yoi` / config surface に合わせて整理してよい。
|
||||
|
||||
Escalate if:
|
||||
- read-only observability hook では足りず、production UI action path を test-only command channel で直接操作したくなる場合。
|
||||
- normal release binary / normal CLI surface に test-only options を露出させる必要がある場合。
|
||||
- workspace structure、Cargo package layout、Nix/package source filter に大きな変更が必要になる場合。
|
||||
- Provider stub / Pod protocol E2E まで同時に広げないと Panel slice が進められない場合。
|
||||
|
||||
Validation:
|
||||
- focused E2E harness tests / example scenarios。
|
||||
- `cargo fmt --check`。
|
||||
- `git diff --check`。
|
||||
- 変更範囲に応じて `cargo check --workspace --all-targets` または narrower package checks。
|
||||
- 新 E2E command が opt-in で実行可能であることを report する。
|
||||
|
||||
Current code map:
|
||||
- `crates/yoi` / CLI launch path: hidden/test-only flag injection の候補。
|
||||
- `crates/tui/src/multi_pod.rs`: Panel events / observable state emission の候補。
|
||||
- `tests/e2e/` or new harness crate: declarative scenario API / PTY runner / artifact collector。
|
||||
- root `Cargo.toml` / package metadata: opt-in package registration と release contamination check。
|
||||
|
||||
Critical risks / reviewer focus:
|
||||
- Harness code が production binary に混ざっていないこと。
|
||||
- Observability hook が read-only で、input/action path を bypass していないこと。
|
||||
- Test が fixed sleep 依存ではなく structured feedback / timeouts / artifacts を持つこと。
|
||||
- Panel mouse / quit latency regression が今後「unit test だけで done」にならない程度の user-visible path を cover すること。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: intake_summary author: orchestrator at: 2026-06-13T14:16:54Z -->
|
||||
|
||||
## Intake summary
|
||||
|
||||
ユーザー確認により、既存 E2E harness Ticket は Pod subprocess E2E と TUI/Panel PTY E2E を一つの実装対象として扱う。Playwright-like declarative API、independent opt-in crate、production binary 非混入、read-only structured observability、PTY input、failure artifact、Panel mouse / quit latency regression scenario が受け入れ方向として明確化済み。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: orchestrator at: 2026-06-13T14:16:54Z from: planning to: ready reason: user_authorized_e2e_harness_implementation field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
Ticket planning が完了しました。state planning -> ready。
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: "yoi ticket" at: 2026-06-13T14:17:34Z from: ready to: queued reason: queued field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
Ticket を `yoi ticket` が queued にしました。
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: orchestrator at: 2026-06-13T14:17:40Z from: queued to: inprogress reason: orchestrator_acceptance_after_user_authorization field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
ユーザーが明示的に inprogress 化して進めることを承認した。Ticket evidence / relation records / OrchestrationPlan accepted plan / Orchestrator worktree clean state を確認済みで、blocking relation はない。Implementation side effect の前に state を inprogress として記録する。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: implementation_report author: orchestrator at: 2026-06-13T14:18:55Z -->
|
||||
|
||||
## Implementation report
|
||||
|
||||
Implementation handoff started.
|
||||
|
||||
- Worktree: `/home/hare/Projects/yoi/.worktree/e2e-harness`
|
||||
- Branch: `ticket-00001KSKBP9YG-e2e-harness`
|
||||
- Coder Pod: `coder-00001KSKBP9YG-e2e`
|
||||
- Scope: child worktree read、root `Cargo.toml` / `Cargo.lock` write、`tests/e2e` write、selected crates (`crates/tui`, `crates/yoi`, `crates/pod`, `crates/protocol`) write、`target` write、this Ticket record write。`.yoi/memory` や local/runtime/log/lock/secret-like `.yoi` paths は write scope に含めていない。
|
||||
- Note: runtime launch validation のため `/home/hare/Projects/yoi` に非再帰 read grant を付けたが、Coder には root/original workspace を inspect/write/git/validate/merge/cleanup しないよう明示済み。
|
||||
|
||||
Next:
|
||||
- Coder は Playwright-like declarative E2E harness、read-only structured observability、PTY input、Panel mouse / quit latency regression scenario の first slice を実装する。
|
||||
- Coder の commit / implementation_report / validation evidence を確認後、Reviewer を read-only 基本で起動する。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: decision author: orchestrator at: 2026-06-13T14:31:31Z -->
|
||||
|
||||
## Decision
|
||||
|
||||
Design note: Panel mouse E2E は raw SGR sequence を固定送信するだけでは不十分。
|
||||
|
||||
Rationale:
|
||||
- Harness が PTY に直接 `ESC [ < ... M` を書くと、実端末が mouse capture 有効時だけ mouse sequence を生成するという条件を bypass してしまい、今回のような「実端末ではイベントが来ない」系の不具合を見逃す可能性がある。
|
||||
- Mouse E2E は locator/row rect を使って coordinate を決めるだけでなく、TUI が mouse capture mode を有効化したことも検証する必要がある。
|
||||
|
||||
追加すべき test harness 要件:
|
||||
- virtual terminal / screen parser は app output の terminal mode sequence(例: normal mouse + SGR mouse mode, `?1000h` / `?1006h` 相当)を tracking し、click helper は capture enabled が観測されていない場合に fail する。
|
||||
- もしくは explicit read-only structured event と terminal output assertion の両方で `mouse_capture_enabled` を確認する。
|
||||
- `click(row_selector)` は `rows_rendered` の rect から座標を選ぶが、raw SGR injection だけで成功扱いにしない。
|
||||
- assertion は `selection_changed` event、screen/actionbar/detail の secondary oracle、そして workflow action が発火していないことを組み合わせる。
|
||||
- これにより mouse selection E2E は面倒になるが、capture setup / coordinate mapping / event handling の user-visible path を分離して検証できる。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: decision author: orchestrator at: 2026-06-13T14:40:42Z -->
|
||||
|
||||
## Decision
|
||||
|
||||
Coder first-slice review: changes requested before external review.
|
||||
|
||||
The initial E2E harness direction is promising, but it does not yet satisfy the regression-prevention bar from this Ticket.
|
||||
|
||||
Required changes sent to Coder:
|
||||
1. Mouse E2E must not pass solely by injecting raw SGR mouse bytes. Harness must track terminal mouse capture enable output (`?1000h` / `?1006h` equivalent) and fail `click(...)` / expose `expect_mouse_capture_enabled()` when capture was not observed.
|
||||
2. Production binary contamination must be reduced. The TUI JSONL observer currently appears to be available in normal builds via `YOI_TUI_TEST_EVENTS`; prefer explicit `e2e-test` feature/cfg gating so release/normal binary has the hook compiled out. Report if feature gating is too large.
|
||||
3. Quit latency E2E must wait for a real pending/background-work barrier at the moment of quit, not merely assert that `background_task_started` happened sometime earlier. Strengthen the scenario to prove Ctrl+C while pending work exists exits promptly.
|
||||
4. Update implementation_report and validation evidence after changes.
|
||||
|
||||
Reason:
|
||||
- The mouse selection regression specifically needs to catch missing terminal mouse capture, which raw SGR injection can bypass.
|
||||
- The user explicitly requested a structure where E2E harness logic does not mix into the production binary.
|
||||
- The quit latency regression needs measured user-visible behavior under a synchronized pending-work condition, not a loose startup smoke test.
|
||||
|
||||
---
|
||||
|
||||
<!-- event: implementation_report author: hare at: 2026-06-13T14:38:03Z -->
|
||||
|
||||
## Implementation report
|
||||
|
||||
Implemented an opt-in E2E testing foundation for real `yoi panel` process automation.
|
||||
|
||||
API / harness shape:
|
||||
- Added workspace package `tests/e2e` (`yoi-e2e`) with required feature `e2e`, so scenarios are opt-in.
|
||||
- Added `PanelHarness::spawn(...)`, `wait_for(...)`, `wait_for_rows(...)`, `click(...)`, `press(...)`, `expect_selection(...)`, `expect_exit_within(...)`, and `artifacts()`.
|
||||
- Harness drives the real `yoi panel` binary through a Unix PTY and sends SGR mouse / keyboard bytes through that PTY only.
|
||||
- Harness captures artifacts under a per-run artifact directory: `events.jsonl`, `input.log`, `pty-output.log`, and `run.json`.
|
||||
|
||||
Production / non-production boundary:
|
||||
- Harness logic stays in `tests/e2e` and is not mixed into production crates.
|
||||
- Production-side change is limited to an opt-in read-only TUI JSONL observer enabled only by `YOI_TUI_TEST_EVENTS`.
|
||||
- Observer records Panel/TUI synchronization and assertion events (`panel_ready`, `rows_rendered`, `selection_changed`, `mouse_click`, `action_requested`, `quit_requested`, background task lifecycle, terminal cleanup, exit).
|
||||
- The observer does not mutate UI state, inject input, bypass actions, or grant authority; real input remains PTY-only.
|
||||
|
||||
Scenarios added:
|
||||
- Panel mouse selection regression: waits for rendered rows, sends an SGR mouse click through PTY, asserts selection changed, and asserts no panel action was dispatched.
|
||||
- Panel quit latency regression: waits for Panel ready plus background-task barrier, sends Ctrl+C through PTY, asserts clean exit within the threshold, and verifies the quit event.
|
||||
|
||||
Files changed:
|
||||
- `Cargo.toml`, `Cargo.lock`
|
||||
- `crates/tui/src/lib.rs`
|
||||
- `crates/tui/src/multi_pod.rs`
|
||||
- `crates/tui/src/e2e_observer.rs`
|
||||
- `tests/e2e/Cargo.toml`
|
||||
- `tests/e2e/src/lib.rs`
|
||||
- `tests/e2e/tests/panel.rs`
|
||||
|
||||
Validation:
|
||||
- `cargo build -p yoi` — passed.
|
||||
- `YOI_E2E_BIN=/home/hare/Projects/yoi/.worktree/e2e-harness/target/debug/yoi cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` — passed (2 tests).
|
||||
- `cargo fmt --check` — passed.
|
||||
- `cargo check -p tui --all-targets` — passed.
|
||||
- `cargo check -p yoi --all-targets` — passed.
|
||||
- `cargo check -p yoi-e2e --all-targets --features e2e` — passed.
|
||||
- `git diff --check` — passed.
|
||||
|
||||
Remaining gaps / risks:
|
||||
- The first slice is Unix PTY-based; cross-platform PTY support is not implemented.
|
||||
- The screen artifact is currently raw PTY output rather than a parsed terminal snapshot.
|
||||
- Fixture setup blocks Companion/Orchestrator role auto-spawn by placing intentionally invalid isolated test pod metadata in the fixture data dir; this avoids leaking live background Pods but should be replaced by a cleaner fixture strategy if the Panel gains an explicit test fixture mode.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: implementation_report author: hare at: 2026-06-13T15:00:29Z -->
|
||||
|
||||
## Implementation report
|
||||
|
||||
Follow-up implementation update for review-required changes:
|
||||
|
||||
Mouse E2E false-positive prevention:
|
||||
- `PanelHarness` now tracks the PTY output for terminal mouse mode enable/disable escape sequences.
|
||||
- It requires both normal mouse tracking (`ESC[?1000h`, with disable `ESC[?1000l`) and SGR mouse mode (`ESC[?1006h`, with disable `ESC[?1006l`) to have been observed as currently enabled.
|
||||
- Added `PanelHarness::expect_mouse_capture_enabled()` and the Panel mouse E2E calls it before clicking.
|
||||
- `PanelHarness::click(...)` also fails with `MouseCaptureNotEnabled` if mouse capture has not been observed, so raw SGR mouse injection alone cannot make the test pass.
|
||||
|
||||
Production binary contamination reduction:
|
||||
- Added explicit feature gates:
|
||||
- `tui/e2e-test`
|
||||
- `yoi/e2e-test = ["tui/e2e-test"]`
|
||||
- The `YOI_TUI_TEST_EVENTS` JSONL event path and the background-task hold seam are compiled only under `tui/e2e-test`.
|
||||
- Normal builds still type-check a no-op facade so call sites stay simple, but env-var observability/hold behavior is compiled out unless the feature is enabled.
|
||||
- E2E binary build command is now: `cargo build -p yoi --features e2e-test`.
|
||||
|
||||
Quit latency barrier strengthening:
|
||||
- Added a feature-gated test-only seam `YOI_TUI_TEST_HOLD_BACKGROUND_TASK=reload` that holds the Panel reload task pending in the E2E binary until quit aborts it.
|
||||
- Added `PanelHarness::expect_background_task_pending("reload")`, which verifies that a `background_task_started` event exists and no matching `background_task_finished` or `background_task_aborted` event has superseded it.
|
||||
- The quit latency test now uses the hold seam and asserts pending reload work immediately before sending Ctrl+C through the PTY.
|
||||
|
||||
Harness/artifact adjustment:
|
||||
- E2E fixture artifacts now persist under workspace `target/e2e-artifacts/...` instead of package-local temporary dirs, so failure artifacts remain inspectable.
|
||||
|
||||
Validation after update:
|
||||
- `cargo fmt --check` — passed.
|
||||
- `cargo build -p yoi --features e2e-test` — passed.
|
||||
- `YOI_E2E_BIN=/home/hare/Projects/yoi/.worktree/e2e-harness/target/debug/yoi cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` — passed (2 tests).
|
||||
- `git diff --check` — passed.
|
||||
- `cargo check -p tui --all-targets` — passed.
|
||||
- `cargo check -p yoi --all-targets` — passed.
|
||||
- `cargo check -p tui --all-targets --features e2e-test` — passed.
|
||||
- `cargo check -p yoi --all-targets --features e2e-test` — passed.
|
||||
- `cargo check -p yoi-e2e --all-targets --features e2e` — passed.
|
||||
|
||||
Remaining gaps / risks:
|
||||
- The E2E harness remains Unix PTY based.
|
||||
- The screen artifact remains raw PTY output, not a parsed terminal snapshot.
|
||||
- The test-only pending-task hold intentionally changes timing only for the `e2e-test` binary; it is not compiled into normal builds.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: review author: hare at: 2026-06-13T15:05:52Z status: request_changes -->
|
||||
|
||||
## Review: request changes
|
||||
|
||||
Request changes.
|
||||
|
||||
Evidence reviewed:
|
||||
- Inspected Ticket record and `git diff 134e8b8b..HEAD` for commits `96561897` and `10a1c383`.
|
||||
- `tests/e2e` provides a credible first declarative harness (`PanelHarness::spawn`, `wait_for`, `wait_for_rows`, `click`, `press`, `expect_selection`, `expect_exit_within`, artifacts/metadata/input/output/event logs). This is not merely a fixed-sleep shell script.
|
||||
- Mouse-selection scenario waits for rendered rows, verifies both normal mouse and SGR mouse capture before `click`, sends the click through PTY bytes, waits for `selection_changed`, and asserts no `action_requested` dispatch.
|
||||
- Quit-latency scenario creates a real feature-gated background-task hold barrier, waits until the task is actually waiting before sending Ctrl+C through the PTY, and measures bounded exit latency.
|
||||
- `yoi-e2e` is opt-in via package feature/test `required-features = ["e2e"]`; e2e tests are outside default members. `YOI_TUI_TEST_EVENTS` and `YOI_TUI_TEST_HOLD_BACKGROUND_TASK` env behavior is behind `tui/e2e-test` / `yoi/e2e-test` feature gates, and the hook is observability-only.
|
||||
|
||||
Required change:
|
||||
- The normal production build still contains/evaluates too much e2e harness glue. In non-`e2e-test` builds, `crates/tui/src/e2e_observer.rs` exposes no-op `emit`/hold functions, but call sites still execute test-specific data construction. In particular `App::emit_rows_rendered` and its panel row key/rect DTOs are compiled unconditionally and `app.emit_rows_rendered()` is called from the panel render path, causing row snapshots to be built every draw even though emission is a no-op. Selection/action/quit call sites also construct `serde_json::json!` payloads before the no-op facade. This violates the recorded boundary that production binaries should not contain harness logic and production-side hooks must be feature-gated/compiled out for normal builds.
|
||||
- Please cfg-gate the call sites/helpers/DTOs, or use a lazy cfg-gated macro/helper so normal builds do not evaluate or retain e2e event payload construction. A tiny compile-only facade is acceptable only if it does not execute or allocate e2e-specific work and does not keep harness DTO logic in the normal runtime path.
|
||||
|
||||
Validation run in `/home/hare/Projects/yoi/.worktree/e2e-harness`:
|
||||
- `git diff --check 134e8b8b..HEAD` — passed.
|
||||
- `cargo fmt --check` — passed.
|
||||
- `cargo check -p tui --all-targets` — passed.
|
||||
- `cargo check -p yoi --all-targets` — passed.
|
||||
- `cargo build -p yoi --features e2e-test` — passed.
|
||||
- `YOI_E2E_BIN=/home/hare/Projects/yoi/.worktree/e2e-harness/target/debug/yoi cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` — passed.
|
||||
- `cargo check -p tui --all-targets --features e2e-test` — passed.
|
||||
- `cargo check -p yoi --all-targets --features e2e-test` — passed.
|
||||
- `cargo check -p yoi-e2e --all-targets --features e2e` — passed.
|
||||
|
||||
No source changes were made during review.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: implementation_report author: hare at: 2026-06-13T15:18:21Z -->
|
||||
|
||||
## Implementation report
|
||||
|
||||
Follow-up implementation update for reviewer request `559adb9a ticket: request e2e harness changes`:
|
||||
|
||||
Production/non-production boundary fix:
|
||||
- Removed the normal-build no-op `e2e_observer` module path and now compile the observer module only behind `tui/e2e-test` via `#[cfg(feature = "e2e-test")] mod e2e_observer;`.
|
||||
- Added `#[cfg(feature = "e2e-test")]` to all Panel/TUI observability call sites that build JSON payloads, including terminal cleanup/exit, panel ready, rows-rendered, mouse click, selection changes, action requests, quit request, and background task lifecycle events.
|
||||
- `App::emit_rows_rendered`, `PanelE2eRowKey`, `PanelE2eRect`, `PanelE2eRenderedRow`, `PanelE2eRowsRendered`, and the conversion helpers are now compiled only with `tui/e2e-test`.
|
||||
- Normal builds no longer call `app.emit_rows_rendered()`, no longer evaluate `serde_json::json!` e2e payloads, and no longer retain the Panel E2E DTO/helper logic in the runtime path.
|
||||
- The background reload hold seam remains compiled/called only under `tui/e2e-test`; `YOI_TUI_TEST_EVENTS` and `YOI_TUI_TEST_HOLD_BACKGROUND_TASK` behavior remains feature-gated.
|
||||
|
||||
Preserved E2E behavior:
|
||||
- Mouse E2E still verifies PTY output for normal mouse tracking + SGR mouse enable sequences before any raw SGR click can be sent.
|
||||
- `PanelHarness::click(...)` still fails if mouse capture was not observed.
|
||||
- Quit latency E2E still uses the feature-gated pending reload hold barrier and asserts the reload task is pending before Ctrl+C.
|
||||
|
||||
Validation:
|
||||
- `cargo fmt --check` — passed.
|
||||
- `cargo check -p tui --all-targets` — passed.
|
||||
- `cargo check -p yoi --all-targets` — passed.
|
||||
- `cargo check -p tui --all-targets --features e2e-test` — passed.
|
||||
- `cargo check -p yoi --all-targets --features e2e-test` — passed.
|
||||
- `cargo build -p yoi --features e2e-test` — passed.
|
||||
- `YOI_E2E_BIN=/home/hare/Projects/yoi/.worktree/e2e-harness/target/debug/yoi cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` — passed (2 tests).
|
||||
- `cargo check -p yoi-e2e --all-targets --features e2e` — passed.
|
||||
- `git diff --check` — passed.
|
||||
|
||||
Remaining gaps / risks unchanged:
|
||||
- The E2E harness remains Unix PTY based.
|
||||
- The screen artifact remains raw PTY output rather than a parsed terminal snapshot.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: review author: hare at: 2026-06-13T15:21:30Z status: approve -->
|
||||
|
||||
## Review: approve
|
||||
|
||||
Approve.
|
||||
|
||||
Delta reviewed:
|
||||
- Re-reviewed the fix commit `b30b43b9 test: cfg-gate e2e observer payloads` after the earlier request-changes review.
|
||||
- Inspected the updated observer module boundary and call sites in `crates/tui/src/lib.rs` and `crates/tui/src/multi_pod.rs`, plus the unchanged harness/tests in `tests/e2e`.
|
||||
|
||||
Evidence:
|
||||
- `e2e_observer` is now only compiled from `crates/tui/src/lib.rs` under `#[cfg(feature = "e2e-test")]`; the previous normal-build no-op facade was removed.
|
||||
- Observer payload construction is gated at call sites with `#[cfg(feature = "e2e-test")]`, including `panel_ready`, `selection_changed`, `action_requested`, `quit_requested`, and `emit_rows_rendered` calls.
|
||||
- Panel E2E DTOs/helpers (`PanelE2eRowKey`, `PanelE2eRect`, `PanelE2eRenderedRow`, `PanelE2eRowsRendered`, `App::emit_rows_rendered`) are now behind `#[cfg(feature = "e2e-test")]`, so the normal panel render path no longer builds row snapshots or retains that runtime helper path.
|
||||
- The background-task hold seam is still feature-gated: `check_background_task_hold` and `release_background_task_hold` calls are under `#[cfg(feature = "e2e-test")]`, and `YOI_TUI_TEST_HOLD_BACKGROUND_TASK` behavior lives in the gated observer module.
|
||||
- Mouse capture tracking remains intact in the harness: it tracks `?1000h` and `?1006h`, `click(...)` requires both capture modes before injecting PTY bytes, the test waits for rendered rows, asserts `selection_changed`, and asserts no `action_requested` dispatch.
|
||||
- Quit-latency coverage remains intact: the test waits for `panel_ready`, then verifies an actual pending `reload` background-task barrier before sending Ctrl+C through the PTY and asserting bounded exit.
|
||||
- The production/non-production boundary now satisfies the Ticket intent: the harness remains opt-in, observability is read-only and feature-gated, and no UI input/action path is bypassed.
|
||||
|
||||
Validation run in `/home/hare/Projects/yoi/.worktree/e2e-harness`:
|
||||
- `git diff --check 134e8b8b..HEAD` — passed.
|
||||
- `cargo fmt --check` — passed.
|
||||
- `cargo check -p tui --all-targets` — passed.
|
||||
- `cargo check -p yoi --all-targets` — passed.
|
||||
- `cargo check -p tui --all-targets --features e2e-test` — passed.
|
||||
- `cargo check -p yoi --all-targets --features e2e-test` — passed.
|
||||
- `cargo build -p yoi --features e2e-test` — passed.
|
||||
- `YOI_E2E_BIN=/home/hare/Projects/yoi/.worktree/e2e-harness/target/debug/yoi cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` — passed; 2 tests passed.
|
||||
- `cargo check -p yoi-e2e --all-targets --features e2e` — passed.
|
||||
|
||||
No source changes were made during re-review.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: implementation_report author: orchestrator at: 2026-06-13T15:23:35Z -->
|
||||
|
||||
## Implementation report
|
||||
|
||||
Integration outcome: merged and validated.
|
||||
|
||||
- Implementation branch: `ticket-00001KSKBP9YG-e2e-harness`
|
||||
- Implementation commits: `96561897 test: add opt-in panel e2e harness`, `10a1c383 test: harden panel e2e harness`, `b30b43b9 test: cfg-gate e2e observer payloads`
|
||||
- Review commits/events: initial `request_changes` for production-boundary leakage, then `approve` after `e2e-test` cfg gating。
|
||||
- Orchestrator merge commit: `b3bd6b11 merge: e2e harness`
|
||||
|
||||
Implemented first vertical slice:
|
||||
- Added opt-in `tests/e2e` package `yoi-e2e` with Playwright-like `PanelHarness` API for real `yoi panel` process automation through PTY.
|
||||
- Added feature-gated read-only TUI observer behind `tui/e2e-test` / `yoi/e2e-test`; normal builds do not compile observer module, event payload construction, row DTOs, or background hold seam.
|
||||
- Added Panel mouse selection E2E that waits for rendered rows, verifies terminal mouse capture output (`?1000h` and `?1006h`), sends click through PTY, asserts selection change, and asserts no action dispatch.
|
||||
- Added Panel quit latency E2E that creates a feature-gated pending reload barrier, sends Ctrl+C through PTY, and asserts bounded exit.
|
||||
- Artifacts include event log, input log, raw PTY output, and run metadata under `target/e2e-artifacts`.
|
||||
|
||||
Orchestrator validation after merge:
|
||||
- `cargo fmt --check`: PASS
|
||||
- `cargo check -p tui --all-targets`: PASS
|
||||
- `cargo check -p yoi --all-targets`: PASS
|
||||
- `cargo check -p tui --all-targets --features e2e-test`: PASS
|
||||
- `cargo check -p yoi --all-targets --features e2e-test`: PASS
|
||||
- `cargo build -p yoi --features e2e-test`: PASS
|
||||
- `YOI_E2E_BIN=/home/hare/Projects/yoi/.worktree/orchestration/yoi-orchestrator/target/debug/yoi cargo test -p yoi-e2e --features e2e --test panel -- --nocapture`: PASS(2 tests)
|
||||
- `cargo check -p yoi-e2e --all-targets --features e2e`: PASS
|
||||
- `git diff --check`: PASS
|
||||
|
||||
Remaining gaps / risks:
|
||||
- Harness is Unix PTY based。
|
||||
- Screen artifact is raw PTY output rather than parsed terminal snapshot。
|
||||
- This is a first vertical slice for Panel/TUI PTY E2E; broader Pod protocol/provider stub scenarios remain future work under this E2E harness direction。
|
||||
|
||||
Next:
|
||||
- Mark Ticket `done` and clean up child coder/reviewer Pods plus implementation worktree/branch. Closure remains separate.
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: orchestrator at: 2026-06-13T15:23:44Z from: inprogress to: done reason: merged_and_validated field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
E2E harness implementation branch was reviewed, approved after requested production-boundary changes, merged into the Orchestrator branch as `b3bd6b11`, and validated in the Orchestrator worktree. Opt-in Panel PTY E2E tests for mouse selection and quit latency passed, along with formatting, diff check, normal/e2e feature package checks, and E2E package check. Ticket implementation work is done; closure remains separate.
|
||||
|
||||
---
|
||||
|
||||
<!-- event: comment author: orchestrator at: 2026-06-13T15:27:18Z -->
|
||||
|
||||
## Comment
|
||||
|
||||
Post-merge design gap: E2E harness の `yoi` binary freshness はまだ自動保証されていない。
|
||||
|
||||
Current behavior:
|
||||
- `tests/e2e/src/lib.rs::yoi_binary()` は `YOI_E2E_BIN` があればその path を使う。
|
||||
- `YOI_E2E_BIN` が無い場合は E2E test binary の `current_exe()` から `target/{debug,release}/yoi` を推測し、最後に `target/debug/yoi` へ fallback する。
|
||||
- Harness は `PanelHarness::spawn` と fixture setup commands の両方でその binary path を使い、`YOI_POD_RUNTIME_COMMAND` も同じ binary に向ける。
|
||||
- しかし harness 自身は `cargo build -p yoi --features e2e-test` を実行しない。したがって任意タイミングの `cargo test -p yoi-e2e --features e2e` だけでは、最新 source から rebuild された binary が使われる保証はない。
|
||||
|
||||
Gap:
|
||||
- 今回の validation は Orchestrator が事前に `cargo build -p yoi --features e2e-test` を実行したため正しい binary を使った。
|
||||
- ただし harness design としては freshness が runner/manual discipline に依存しており、stale `target/debug/yoi` や別 path の `YOI_E2E_BIN` を使っても test が走り得る。
|
||||
|
||||
Follow-up direction:
|
||||
- `cargo xtask e2e` / `yoi-e2e-runner` / documented `just e2e` など、必ず `cargo build -p yoi --features e2e-test` を実行してから `YOI_E2E_BIN=<fresh target binary> cargo test -p yoi-e2e --features e2e ...` する single entrypoint が必要。
|
||||
- さらに harness は起動 binary が `e2e-test` feature 有効であることを handshake/event/version で検証し、可能なら source commit/build timestamp/path metadata を artifact に残して stale/mismatched binary を diagnostic にするべき。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: decision author: orchestrator at: 2026-06-13T15:43:37Z -->
|
||||
|
||||
## Decision
|
||||
|
||||
Follow-up design note: E2E の `yoi` binary freshness は `cargo run` 直起動より、harness 内 `cargo build` + built binary spawn を標準にする。
|
||||
|
||||
Decision candidate:
|
||||
- `cargo test -p yoi-e2e --features e2e` の test setup から `cargo build -p yoi --features e2e-test --bin yoi` を実行することは可能で、opt-in E2E では許容する。
|
||||
- ただし PTY scenario の process-under-test を `cargo run ... -- panel` にするのは避ける。Cargo wrapper の build output、process tree、signal forwarding、exit timing が混ざり、Panel quit latency の測定対象が曖昧になるため。
|
||||
- Harness には `BinaryProvider::CargoBuild` のような起動経路を持たせ、test 開始時に current workspace source から `yoi` を build し、得られた `target/{profile}/yoi` path を PTY で直接 spawn する。
|
||||
- これにより「任意タイミングの E2E 実行で最新 source から作った binary を使う」ことを起動経路として保証しつつ、実際の UI/latency 測定は Cargo wrapper ではなく `yoi` binary 本体を対象にできる。
|
||||
- 複数 test の重複 build は `OnceLock`/suite setup 等で 1 回にまとめる。parallel test 実行時の cargo target lock 待ちは opt-in E2E では許容し、必要なら serial 化する。
|
||||
|
||||
Rationale:
|
||||
- 起動後 handshake で正しさを検証するより、起動経路として build step を harness に内蔵する方が単純。
|
||||
- `cargo run` は可能だが、`run` は build + wrapper spawn を同時に行うため、PTY/Signal/timing の被測定経路に Cargo が入ってしまう。`cargo build` と direct binary spawn に分ける方が E2E の oracle が明確。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: decision author: orchestrator at: 2026-06-13T15:45:26Z -->
|
||||
|
||||
## Decision
|
||||
|
||||
Follow-up requested by user: E2E harness should build the current `yoi` binary itself instead of relying on a prebuilt `YOI_E2E_BIN` / inferred `target/debug/yoi`.
|
||||
|
||||
Required correction:
|
||||
- Default E2E binary provider should run `cargo build -p yoi --features e2e-test --bin yoi` from the workspace root at test time, then spawn the resulting `target/{profile}/yoi` directly through PTY.
|
||||
- `YOI_E2E_BIN` may remain as an explicit override, but normal arbitrary `cargo test -p yoi-e2e --features e2e ...` should use a freshly built binary without requiring a separate manual build step.
|
||||
- Do not use `cargo run` as the process-under-test because that would put Cargo in the PTY/signal/quit-latency measurement path.
|
||||
- Preserve the existing production/non-production boundary and E2E feature gating.
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
title: 'Panel Quit 時の断続的な遅延を調査して解消する'
|
||||
state: 'done'
|
||||
created_at: '2026-06-13T10:04:55Z'
|
||||
updated_at: '2026-06-13T11:41:26Z'
|
||||
updated_at: '2026-06-13T12:57:53Z'
|
||||
assignee: null
|
||||
readiness: 'spike_needed'
|
||||
risk_flags: ['tui-panel', 'shutdown-latency', 'async-cancellation']
|
||||
|
|
|
|||
|
|
@ -260,3 +260,32 @@ Next:
|
|||
Implementation branch `ticket-00001KV0723PC-panel-quit-latency` was reviewed, approved, merged into the Orchestrator branch as `db7bad7a`, and validated in the Orchestrator worktree. Focused panel quit latency tests, formatting, diff check, and `cargo check -p tui --all-targets` passed. Ticket implementation work is done; closure remains separate.
|
||||
|
||||
---
|
||||
|
||||
<!-- event: comment author: orchestrator at: 2026-06-13T12:57:53Z -->
|
||||
|
||||
## Comment
|
||||
|
||||
Post-merge measurement report: Panel quit latency is still present / not proven fixed.
|
||||
|
||||
User report:
|
||||
- 「相変わらずPanelをquitする際の遅延も解決してない」
|
||||
|
||||
Measurement performed after merge in Orchestrator worktree `/home/hare/Projects/yoi/.worktree/orchestration/yoi-orchestrator`:
|
||||
- Built current debug binary: `cargo build -p yoi`。
|
||||
- PTY measurement command shape: pipe `sleep <delay>; Ctrl+C` into `script -q -c 'target/debug/yoi panel --workspace ...'` and measure process elapsed.
|
||||
- Current debug binary `target/debug/yoi` results with Ctrl+C sent after 1.5s:
|
||||
- approx after Ctrl+C: 2488ms, 2561ms, 2453ms, 2558ms, 2507ms。
|
||||
- Varying Ctrl+C send delay against current debug binary:
|
||||
- delay 0.2s -> total 3958ms
|
||||
- delay 1.0s -> total 3919ms
|
||||
- delay 2.0s -> total 4062ms
|
||||
- delay 3.5s -> total 4095ms
|
||||
- delay 5.0s -> total 6309ms
|
||||
- Installed `/home/hare/.nix-profile/bin/yoi` showed approx 24-25ms after Ctrl+C in the same scripted shape, and also appeared not to contain the new test/symbol strings, so binary-path freshness differs between measurements.
|
||||
|
||||
Conclusion:
|
||||
- The original merge was based on a plausible code-path fix and focused tests, but no interactive/user-path latency measurement was done before marking done。
|
||||
- The current debug binary still shows a roughly 4s startup/quit floor in the PTY measurement, so the merged fix did not prove the user's observed latency is gone and may have missed a different blocking path。
|
||||
- Follow-up must identify the actual user command/binary path and measure `yoi panel` quit latency before and after any further fix. If the relevant path is current source/debug, this Ticket should be treated as not actually resolved.
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
title: 'Workspace panel の View item をマウスで選択できるようにする'
|
||||
state: 'done'
|
||||
created_at: '2026-06-13T10:05:19Z'
|
||||
updated_at: '2026-06-13T11:26:30Z'
|
||||
updated_at: '2026-06-13T12:57:53Z'
|
||||
assignee: null
|
||||
readiness: 'implementation_ready'
|
||||
risk_flags: ['tui-input', 'mouse-capture', 'panel-ux']
|
||||
|
|
|
|||
|
|
@ -220,3 +220,24 @@ Next:
|
|||
Implementation branch `ticket-00001KV072V89-panel-mouse-selection` was reviewed, approved, merged into the Orchestrator branch as `02311883`, and validated in the Orchestrator worktree. Focused mouse-selection tests, formatting, diff check, and `cargo check -p tui --all-targets` passed. Ticket implementation work is done; closure remains separate.
|
||||
|
||||
---
|
||||
|
||||
<!-- event: comment author: orchestrator at: 2026-06-13T12:57:53Z -->
|
||||
|
||||
## Comment
|
||||
|
||||
Post-merge user verification report: mouse selection is still ineffective.
|
||||
|
||||
User report:
|
||||
- 「マウス選択全く効かない」
|
||||
|
||||
What was re-checked:
|
||||
- Merge commit for this Ticket: `02311883 merge: panel mouse selection`。
|
||||
- Implementation added `PanelRowHitBox` / `TermEvent::Mouse` handling in `crates/tui/src/multi_pod.rs` and focused tests, but there was no interactive `yoi panel` manual validation before marking done。
|
||||
- Current built debug binary contains `PanelRowHitBox` symbols; installed `/home/hare/.nix-profile/bin/yoi` did not contain those symbols in `strings` check, so at least one live/user-visible binary path may still be stale。
|
||||
- This does not fully explain the report if the user already rebuilt/restarted; the remaining likely failure areas are terminal mouse-event delivery, runtime binary freshness, hitbox coordinate mismatch, or lack of interactive validation coverage。
|
||||
|
||||
Conclusion:
|
||||
- The original done decision was insufficient because it relied on internal hit-test/focused tests and did not prove actual terminal mouse selection in a live Panel.
|
||||
- Follow-up must include measured/manual validation with the same command/binary path the user runs, including a positive proof that a click changes selected row in `yoi panel`.
|
||||
|
||||
---
|
||||
|
|
|
|||
0
.yoi/tickets/00001KV0TJVN5/artifacts/.gitkeep
Normal file
0
.yoi/tickets/00001KV0TJVN5/artifacts/.gitkeep
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
Implementation report for Ticket 00001KV0TJVN5
|
||||
|
||||
Files changed:
|
||||
- `tests/e2e/src/lib.rs`
|
||||
- Added a cached e2e binary provider using `OnceLock`.
|
||||
- Preserves `YOI_E2E_BIN=<path>` as the explicit override and skips the default cargo build provider in that path.
|
||||
- Default path runs `${CARGO:-cargo} build -p yoi --features e2e-test --bin yoi` from the workspace root, then returns the direct `target/{profile}/yoi` binary path for PTY spawning.
|
||||
- Writes `target/e2e-artifacts/binary-provider.json` and emits diagnostics with provider, build command, binary path, and tested-subprocess env policy.
|
||||
- Expanded command-failure diagnostics to include command args.
|
||||
- Follow-up: isolated tested `yoi` subprocess environments in both `PanelHarness::spawn` and fixture setup `run_yoi_capture` with `env_clear()` plus explicit allowlists only.
|
||||
- Follow-up: recorded env policy in `run.json`, `binary-provider.json`, and per-fixture `fixture-commands.jsonl` artifacts.
|
||||
- Follow-up: added a regression assertion that tested-subprocess policies use `env_clear`, do not allow `PATH`, and default-deny provider credentials (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`) and secret-like patterns.
|
||||
- Follow-up: relative `YOI_E2E_BIN` values are resolved against the workspace root and must exist, so tested subprocess launch does not rely on `PATH` lookup.
|
||||
- `tests/e2e/tests/panel.rs`
|
||||
- Updated panel tests to use the fallible cached binary provider.
|
||||
|
||||
Env isolation policy:
|
||||
- Cargo build provider remains a build-tool command and is not treated as the tested `yoi` subprocess.
|
||||
- Tested `yoi` fixture setup commands receive only: `HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME`, `XDG_CONFIG_HOME`, `YOI_POD_RUNTIME_COMMAND`.
|
||||
- Tested `yoi panel` commands receive only: fixture `HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME`, `XDG_CONFIG_HOME`, `TERM`, `YOI_TUI_TEST_EVENTS`, `YOI_POD_RUNTIME_COMMAND`, and `YOI_TUI_TEST_HOLD_BACKGROUND_TASK` when used.
|
||||
- `PATH` is intentionally not passed to tested `yoi` subprocesses; the harness launches the already-resolved binary path directly.
|
||||
- Host provider credentials / token / secret-like environment variables are default-denied. Future provider/LLM E2E should use fixture providers, canned servers, or explicit test env instead of inheriting host credentials.
|
||||
|
||||
Validation:
|
||||
- `cargo fmt --check` — passed.
|
||||
- `git diff --check` — passed.
|
||||
- `cargo check -p yoi-e2e --all-targets --features e2e` — passed.
|
||||
- `cargo test -p yoi-e2e --features e2e tested_yoi_env_policy_is_env_clear_allowlist -- --nocapture` — passed.
|
||||
- `unset YOI_E2E_BIN && OPENAI_API_KEY=host-secret ANTHROPIC_API_KEY=host-secret GEMINI_API_KEY=host-secret cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` — passed; default provider built the current `yoi` binary and tested `yoi` subprocesses used isolated env policy artifacts. Host provider env was present for the harness but is not inherited by tested `yoi` subprocesses because `env_clear()` is applied before the allowlist.
|
||||
- `YOI_E2E_BIN=/home/hare/Projects/yoi/.worktree/e2e-binary-provider/target/debug/yoi OPENAI_API_KEY=host-secret ANTHROPIC_API_KEY=host-secret GEMINI_API_KEY=host-secret cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` — passed; override provider path used without invoking the default cargo-build provider, and tested `yoi` subprocesses still used isolated env policy.
|
||||
|
||||
Remaining gaps:
|
||||
- None known.
|
||||
13
.yoi/tickets/00001KV0TJVN5/artifacts/relations.json
Normal file
13
.yoi/tickets/00001KV0TJVN5/artifacts/relations.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"version": 1,
|
||||
"relations": [
|
||||
{
|
||||
"ticket_id": "00001KV0TJVN5",
|
||||
"kind": "related",
|
||||
"target": "00001KSKBP9YG",
|
||||
"note": "既存 E2E harness first slice の post-merge binary freshness gap を補正する follow-up。",
|
||||
"author": "orchestrator",
|
||||
"at": "2026-06-13T15:46:12Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
## Review: approve
|
||||
|
||||
Decision: approve for Ticket `00001KV0TJVN5`.
|
||||
|
||||
Evidence reviewed:
|
||||
- Ticket intent/acceptance criteria require default E2E setup to build `yoi` with `cargo build -p yoi --features e2e-test --bin yoi`, then direct-spawn the produced binary, while preserving `YOI_E2E_BIN` override and existing panel E2E behavior.
|
||||
- `tests/e2e/src/lib.rs` now resolves `yoi_binary()` through a `OnceLock`-cached `BinaryProviderInfo`. The default path runs `${CARGO:-cargo} build -p yoi --features e2e-test --bin yoi` from the workspace root and returns `target/{debug|release}/yoi`; the override path validates and uses `YOI_E2E_BIN` without invoking the cargo-build provider.
|
||||
- PTY execution remains `Command::new(&config.binary).arg("panel")`; `cargo run` is not in the process-under-test path.
|
||||
- `PanelHarness::spawn` and fixture `run_yoi_capture` both call `env_clear()` and then set only explicit fixture/test variables. `PATH` and provider credentials are not allowlisted. `YOI_POD_RUNTIME_COMMAND` is set to the resolved binary path, so tested subprocesses do not need host `PATH`.
|
||||
- Diagnostics/artifacts include provider/build/env policy in `target/e2e-artifacts/binary-provider.json`, panel `run.json`, and fixture `fixture-commands.jsonl`.
|
||||
- Existing mouse-capture guard (`expect_mouse_capture_enabled` / SGR 1000+1006 tracking), background-task quit barrier assertions, and `e2e-test` production boundary code were not weakened by this diff.
|
||||
|
||||
Validation:
|
||||
- Reviewer reran `git diff --check a4df9754..HEAD` — passed.
|
||||
- Reviewer reran `cargo test -p yoi-e2e --features e2e tested_yoi_env_policy_is_env_clear_allowlist -- --nocapture` — passed.
|
||||
- Also accepted Orchestrator-reported full validation, including fmt/check, `cargo check -p yoi-e2e --all-targets --features e2e`, default panel E2E with host provider env present, and `YOI_E2E_BIN` override panel E2E with host provider env present — all reported passed.
|
||||
|
||||
Risks / follow-up:
|
||||
- No blocking issues found. The cargo build provider intentionally still uses build-tool environment; tested `yoi` subprocesses are isolated.
|
||||
34
.yoi/tickets/00001KV0TJVN5/item.md
Normal file
34
.yoi/tickets/00001KV0TJVN5/item.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
title: 'E2E harness が最新 yoi binary を自動 build して使うようにする'
|
||||
state: 'done'
|
||||
created_at: '2026-06-13T15:46:07Z'
|
||||
updated_at: '2026-06-13T16:09:29Z'
|
||||
assignee: null
|
||||
readiness: 'ready'
|
||||
queued_by: 'yoi ticket'
|
||||
queued_at: '2026-06-13T15:46:29Z'
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
`00001KSKBP9YG` の E2E harness first slice では `YOI_E2E_BIN` または推測された `target/debug/yoi` を process-under-test として使っていた。これだと任意タイミングの `cargo test -p yoi-e2e --features e2e ...` 実行時に、最新 source から build された `yoi` binary が使われる保証がない。
|
||||
|
||||
ユーザー判断:
|
||||
- `cargo run` を process-under-test にするより、E2E harness が test setup で `cargo build -p yoi --features e2e-test --bin yoi` を実行し、生成された binary を直接 PTY spawn する方針で修正する。
|
||||
|
||||
## 要件
|
||||
|
||||
- `YOI_E2E_BIN` が明示されていない通常 E2E 実行では、harness が workspace root で `cargo build -p yoi --features e2e-test --bin yoi` を実行してから、生成された binary path を使う。
|
||||
- `cargo run` を PTY の process-under-test にしない。PTY / Ctrl+C / Quit latency 測定対象は `yoi` binary 本体にする。
|
||||
- `YOI_E2E_BIN` は明示 override として残してよい。
|
||||
- 複数 test で build が重複しすぎないよう、可能なら `OnceLock` 等で同一 test process 内 1 回に寄せる。
|
||||
- artifact / error message に binary provider / build command / binary path が分かる情報を残す。
|
||||
- 既存の production/non-production boundary、`e2e-test` feature gating、mouse capture tracking、quit pending barrier を壊さない。
|
||||
|
||||
## 受け入れ条件
|
||||
|
||||
- `cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` だけで、事前の手動 `cargo build -p yoi --features e2e-test` なしに E2E が実行できる。
|
||||
- E2E 実行時に build された `target/debug/yoi` が PTY に直接 spawn される。
|
||||
- `YOI_E2E_BIN=<path>` 指定時は override としてその path が使われる。
|
||||
- 既存 Panel E2E 2 本が pass する。
|
||||
- `cargo fmt --check`、`git diff --check`、関連 package check が pass する。
|
||||
270
.yoi/tickets/00001KV0TJVN5/thread.md
Normal file
270
.yoi/tickets/00001KV0TJVN5/thread.md
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
<!-- event: create author: orchestrator at: 2026-06-13T15:46:07Z -->
|
||||
|
||||
## 作成
|
||||
|
||||
LocalTicketBackend によって作成されました。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: intake_summary author: orchestrator at: 2026-06-13T15:46:19Z -->
|
||||
|
||||
## Intake summary
|
||||
|
||||
ユーザーが `cargo build` による最新 `yoi` binary 入手を E2E harness default にする方針を明示した。要件・受け入れ条件は、`YOI_E2E_BIN` override を残しつつ、通常 E2E 実行では harness が `cargo build -p yoi --features e2e-test --bin yoi` を実行し、生成 binary を直接 PTY spawn すること。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: orchestrator at: 2026-06-13T15:46:19Z from: planning to: ready reason: user_authorized_followup_ready field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
Ticket planning が完了しました。state planning -> ready。
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: "yoi ticket" at: 2026-06-13T15:46:29Z from: ready to: queued reason: queued field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
Ticket を `yoi ticket` が queued にしました。
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: decision author: orchestrator at: 2026-06-13T15:46:54Z -->
|
||||
|
||||
## Decision
|
||||
|
||||
Routing decision: implementation_ready
|
||||
|
||||
Reason:
|
||||
- ユーザーが方針を明示した: `cargo run` を PTY の process-under-test にせず、E2E harness が `cargo build -p yoi --features e2e-test --bin yoi` を実行し、生成された binary を直接 spawn する。
|
||||
- Ticket は `queued` で、要件・受け入れ条件は具体的。blocking relation はなく、既存 E2E harness の小さな follow-up として実装可能。
|
||||
- 既存 production/non-production boundary、mouse capture check、quit pending barrier は維持すべき invariant として明記済み。
|
||||
|
||||
Evidence checked:
|
||||
- Ticket body / relation to `00001KSKBP9YG`。
|
||||
- Existing E2E harness code: `tests/e2e/src/lib.rs::yoi_binary()` currently prefers `YOI_E2E_BIN`, then infers `target/debug/yoi`, but does not build it。
|
||||
- Orchestrator worktree is clean。
|
||||
|
||||
IntentPacket:
|
||||
|
||||
Intent:
|
||||
- `cargo test -p yoi-e2e --features e2e ...` を任意タイミングで実行しても、harness が最新 source から E2E 用 `yoi` binary を build して直接 PTY spawn するようにする。
|
||||
|
||||
Binding decisions / invariants:
|
||||
- `cargo run` を process-under-test にしない。
|
||||
- `YOI_E2E_BIN` explicit override は残してよい。
|
||||
- default path は harness 内 `cargo build -p yoi --features e2e-test --bin yoi` + direct binary spawn。
|
||||
- production/non-production boundary と `e2e-test` feature gating を壊さない。
|
||||
- mouse capture tracking / quit pending barrier を壊さない。
|
||||
|
||||
Requirements / acceptance criteria:
|
||||
- 事前 manual build なしに `cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` が pass する。
|
||||
- `YOI_E2E_BIN=<path>` 指定時は override としてその path が使われる。
|
||||
- Build command / binary path が artifact または diagnostics から分かる。
|
||||
- `cargo fmt --check`、`git diff --check`、関連 checks が pass。
|
||||
|
||||
Implementation latitude:
|
||||
- `OnceLock` 等で同一 test process 内の build を 1 回にまとめてよい。
|
||||
- Workspace root detection は `CARGO_MANIFEST_DIR` parent traversal、`cargo metadata`、または robust helper のいずれでもよい。
|
||||
- Cargo executable は `CARGO` env を尊重する。
|
||||
|
||||
Escalate if:
|
||||
- workspace/package layout 上、E2E test process 内から cargo build するのが大きな cyclic/build-system 問題を起こす場合。
|
||||
- Nix/package source filter 等の追加変更が必要になる場合。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: orchestrator at: 2026-06-13T15:47:00Z from: queued to: inprogress reason: orchestrator_acceptance_user_requested_fix field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
Ticket evidence、existing E2E harness code map、Orchestrator worktree clean state を確認した。ユーザーが明示的に修正を依頼しており、blocking relation はない。実装 side effect の前に inprogress acceptance を記録する。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: decision author: orchestrator at: 2026-06-13T15:49:47Z -->
|
||||
|
||||
## Decision
|
||||
|
||||
追加の critical gap: E2E subprocess environment が host credentials を継承し得る。
|
||||
|
||||
Current finding:
|
||||
- `PanelHarness::spawn` と fixture setup の `run_yoi_capture` は `Command::new(binary)` に対して `env_clear()` を呼んでいない。
|
||||
- `HOME` / `XDG_*` は fixture dir に差し替えているが、通常の process environment は継承される。
|
||||
- そのため、現時点の Panel tests は LLM call を意図的に起こしていないものの、何らかの E2E path が provider/LLM call に到達した場合、host の `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / その他 provider credentials や secret-related env を見てしまう可能性がある。
|
||||
|
||||
Decision:
|
||||
- E2E harness は default で host environment を継承してはならない。
|
||||
- `Command` 起動時は `env_clear()` を使い、必要最小限の env(`HOME`, `XDG_*`, `TERM`, `PATH`/`CARGO` 等 build/runtime に必要なもの、`YOI_TUI_TEST_EVENTS`, `YOI_POD_RUNTIME_COMMAND`, explicit test env)だけを allowlist で渡す。
|
||||
- Provider credentials / secret refs / network credentials は default deny。LLM/provider E2E を将来追加する場合は fixture provider / canned server / explicit test-only env で渡す。
|
||||
- Panel/TUI E2E は real LLM call が発生しないことを前提にし、host auth への依存を禁止する。
|
||||
|
||||
Acceptance addition for `00001KV0TJVN5`:
|
||||
- Existing subprocess launches (`PanelHarness::spawn`, `run_yoi_capture`, cargo build provider where appropriate) must not leak host provider credentials to the tested `yoi` process。
|
||||
- At minimum, yoi subprocess env should be allowlisted and provider key names should be absent unless explicitly supplied by a future fixture provider test。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: decision author: orchestrator at: 2026-06-13T15:53:07Z -->
|
||||
|
||||
## Decision
|
||||
|
||||
Environment-boundary reference added: `docs/development/environment.md` is binding context for this follow-up.
|
||||
|
||||
Relevant policy:
|
||||
- Environment variables are a minimized runtime boundary.
|
||||
- Provider credentials and runtime configuration should not depend on ambient process state.
|
||||
- Tests should prefer typed fixtures/injection and mutate process environment only around thin env-reader behavior.
|
||||
- `YOI_POD_RUNTIME_COMMAND` is a development-only executable-path override, not a general ambient configuration channel.
|
||||
|
||||
Implication for this Ticket:
|
||||
- E2E harness must not let tested `yoi` subprocesses inherit host env by default.
|
||||
- `PanelHarness::spawn` and fixture setup commands should use `env_clear()` plus an explicit allowlist.
|
||||
- Provider/secret/network credential env must be absent unless a future provider-fixture E2E explicitly injects test-only values.
|
||||
- Existing `YOI_*` test env should be limited to the feature-gated E2E observability/binary runtime paths and documented as test-only/dev-only surface, not normal runtime configuration.
|
||||
- The cargo build provider should avoid putting Cargo itself in the measured PTY path; any build env inheritance must be considered separately from the tested `yoi` subprocess env boundary.
|
||||
|
||||
---
|
||||
|
||||
<!-- event: implementation_report author: hare at: 2026-06-13T15:54:18Z -->
|
||||
|
||||
## Implementation report
|
||||
|
||||
Implementation report for Ticket 00001KV0TJVN5
|
||||
|
||||
Files changed:
|
||||
- `tests/e2e/src/lib.rs`
|
||||
- Added a cached e2e binary provider using `OnceLock`.
|
||||
- Preserves `YOI_E2E_BIN=<path>` as the explicit override and skips the default cargo build provider in that path.
|
||||
- Default path runs `${CARGO:-cargo} build -p yoi --features e2e-test --bin yoi` from the workspace root, then returns the direct `target/{profile}/yoi` binary path for PTY spawning.
|
||||
- Writes `target/e2e-artifacts/binary-provider.json` and emits diagnostics with provider, build command, and binary path.
|
||||
- Expanded command-failure diagnostics to include command args.
|
||||
- `tests/e2e/tests/panel.rs`
|
||||
- Updated panel tests to use the fallible cached binary provider.
|
||||
|
||||
Validation:
|
||||
- `cargo fmt --check` — passed.
|
||||
- `git diff --check` — passed.
|
||||
- `cargo check -p yoi-e2e --all-targets --features e2e` — passed.
|
||||
- `unset YOI_E2E_BIN && cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` — passed; default provider built the current `yoi` binary and PTY-spawned `target/debug/yoi`.
|
||||
- `YOI_E2E_BIN=/home/hare/Projects/yoi/.worktree/e2e-binary-provider/target/debug/yoi cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` — passed; override provider path used without invoking the default cargo-build provider.
|
||||
|
||||
Remaining gaps:
|
||||
- None known.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: implementation_report author: hare at: 2026-06-13T16:01:51Z -->
|
||||
|
||||
## Implementation report
|
||||
|
||||
Implementation report for Ticket 00001KV0TJVN5
|
||||
|
||||
Files changed:
|
||||
- `tests/e2e/src/lib.rs`
|
||||
- Added a cached e2e binary provider using `OnceLock`.
|
||||
- Preserves `YOI_E2E_BIN=<path>` as the explicit override and skips the default cargo build provider in that path.
|
||||
- Default path runs `${CARGO:-cargo} build -p yoi --features e2e-test --bin yoi` from the workspace root, then returns the direct `target/{profile}/yoi` binary path for PTY spawning.
|
||||
- Writes `target/e2e-artifacts/binary-provider.json` and emits diagnostics with provider, build command, binary path, and tested-subprocess env policy.
|
||||
- Expanded command-failure diagnostics to include command args.
|
||||
- Follow-up: isolated tested `yoi` subprocess environments in both `PanelHarness::spawn` and fixture setup `run_yoi_capture` with `env_clear()` plus explicit allowlists only.
|
||||
- Follow-up: recorded env policy in `run.json`, `binary-provider.json`, and per-fixture `fixture-commands.jsonl` artifacts.
|
||||
- Follow-up: added a regression assertion that tested-subprocess policies use `env_clear`, do not allow `PATH`, and default-deny provider credentials (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`) and secret-like patterns.
|
||||
- Follow-up: relative `YOI_E2E_BIN` values are resolved against the workspace root and must exist, so tested subprocess launch does not rely on `PATH` lookup.
|
||||
- `tests/e2e/tests/panel.rs`
|
||||
- Updated panel tests to use the fallible cached binary provider.
|
||||
|
||||
Env isolation policy:
|
||||
- Cargo build provider remains a build-tool command and is not treated as the tested `yoi` subprocess.
|
||||
- Tested `yoi` fixture setup commands receive only: `HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME`, `XDG_CONFIG_HOME`, `YOI_POD_RUNTIME_COMMAND`.
|
||||
- Tested `yoi panel` commands receive only: fixture `HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME`, `XDG_CONFIG_HOME`, `TERM`, `YOI_TUI_TEST_EVENTS`, `YOI_POD_RUNTIME_COMMAND`, and `YOI_TUI_TEST_HOLD_BACKGROUND_TASK` when used.
|
||||
- `PATH` is intentionally not passed to tested `yoi` subprocesses; the harness launches the already-resolved binary path directly.
|
||||
- Host provider credentials / token / secret-like environment variables are default-denied. Future provider/LLM E2E should use fixture providers, canned servers, or explicit test env instead of inheriting host credentials.
|
||||
|
||||
Validation:
|
||||
- `cargo fmt --check` — passed.
|
||||
- `git diff --check` — passed.
|
||||
- `cargo check -p yoi-e2e --all-targets --features e2e` — passed.
|
||||
- `cargo test -p yoi-e2e --features e2e tested_yoi_env_policy_is_env_clear_allowlist -- --nocapture` — passed.
|
||||
- `unset YOI_E2E_BIN && OPENAI_API_KEY=host-secret ANTHROPIC_API_KEY=host-secret GEMINI_API_KEY=host-secret cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` — passed; default provider built the current `yoi` binary and tested `yoi` subprocesses used isolated env policy artifacts. Host provider env was present for the harness but is not inherited by tested `yoi` subprocesses because `env_clear()` is applied before the allowlist.
|
||||
- `YOI_E2E_BIN=/home/hare/Projects/yoi/.worktree/e2e-binary-provider/target/debug/yoi OPENAI_API_KEY=host-secret ANTHROPIC_API_KEY=host-secret GEMINI_API_KEY=host-secret cargo test -p yoi-e2e --features e2e --test panel -- --nocapture` — passed; override provider path used without invoking the default cargo-build provider, and tested `yoi` subprocesses still used isolated env policy.
|
||||
|
||||
Remaining gaps:
|
||||
- None known.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: review author: hare at: 2026-06-13T16:06:53Z status: approve -->
|
||||
|
||||
## Review: approve
|
||||
|
||||
## Review: approve
|
||||
|
||||
Decision: approve for Ticket `00001KV0TJVN5`.
|
||||
|
||||
Evidence reviewed:
|
||||
- Ticket intent/acceptance criteria require default E2E setup to build `yoi` with `cargo build -p yoi --features e2e-test --bin yoi`, then direct-spawn the produced binary, while preserving `YOI_E2E_BIN` override and existing panel E2E behavior.
|
||||
- `tests/e2e/src/lib.rs` now resolves `yoi_binary()` through a `OnceLock`-cached `BinaryProviderInfo`. The default path runs `${CARGO:-cargo} build -p yoi --features e2e-test --bin yoi` from the workspace root and returns `target/{debug|release}/yoi`; the override path validates and uses `YOI_E2E_BIN` without invoking the cargo-build provider.
|
||||
- PTY execution remains `Command::new(&config.binary).arg("panel")`; `cargo run` is not in the process-under-test path.
|
||||
- `PanelHarness::spawn` and fixture `run_yoi_capture` both call `env_clear()` and then set only explicit fixture/test variables. `PATH` and provider credentials are not allowlisted. `YOI_POD_RUNTIME_COMMAND` is set to the resolved binary path, so tested subprocesses do not need host `PATH`.
|
||||
- Diagnostics/artifacts include provider/build/env policy in `target/e2e-artifacts/binary-provider.json`, panel `run.json`, and fixture `fixture-commands.jsonl`.
|
||||
- Existing mouse-capture guard (`expect_mouse_capture_enabled` / SGR 1000+1006 tracking), background-task quit barrier assertions, and `e2e-test` production boundary code were not weakened by this diff.
|
||||
|
||||
Validation:
|
||||
- Reviewer reran `git diff --check a4df9754..HEAD` — passed.
|
||||
- Reviewer reran `cargo test -p yoi-e2e --features e2e tested_yoi_env_policy_is_env_clear_allowlist -- --nocapture` — passed.
|
||||
- Also accepted Orchestrator-reported full validation, including fmt/check, `cargo check -p yoi-e2e --all-targets --features e2e`, default panel E2E with host provider env present, and `YOI_E2E_BIN` override panel E2E with host provider env present — all reported passed.
|
||||
|
||||
Risks / follow-up:
|
||||
- No blocking issues found. The cargo build provider intentionally still uses build-tool environment; tested `yoi` subprocesses are isolated.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: implementation_report author: orchestrator at: 2026-06-13T16:09:20Z -->
|
||||
|
||||
## Implementation report
|
||||
|
||||
Integration outcome: merged and validated.
|
||||
|
||||
- Implementation branch: `ticket-00001KV0TJVN5-e2e-binary-provider`
|
||||
- Implementation commits: `13d00530 test: build e2e yoi binary provider`, `47efeb01 test: isolate e2e yoi subprocess env`
|
||||
- Review commit/event: `7e24a8df ticket: approve e2e binary provider`
|
||||
- Orchestrator merge commit: `8abc2b7f merge: e2e binary provider`
|
||||
|
||||
Implemented:
|
||||
- Default E2E binary provider now runs `${CARGO:-cargo} build -p yoi --features e2e-test --bin yoi` from workspace root, caches the result, and direct-spawns the produced `target/{profile}/yoi` binary for PTY tests。
|
||||
- `YOI_E2E_BIN=<path>` remains an explicit override and skips the default cargo-build provider。
|
||||
- `cargo run` is not used as process-under-test; Cargo is not in the PTY/signal/quit-latency measured path。
|
||||
- Tested `yoi` subprocesses (`PanelHarness::spawn` and fixture setup `run_yoi_capture`) now use `env_clear()` plus explicit allowlists only。
|
||||
- Host provider credentials / token / secret-like env are default-denied for tested `yoi` subprocesses。
|
||||
- Artifacts include binary provider/build command/binary path and tested subprocess env policy。
|
||||
|
||||
Orchestrator validation after merge:
|
||||
- `cargo fmt --check`: PASS
|
||||
- `git diff --check`: PASS
|
||||
- `cargo check -p yoi-e2e --all-targets --features e2e`: PASS
|
||||
- `cargo test -p yoi-e2e --features e2e tested_yoi_env_policy_is_env_clear_allowlist -- --nocapture`: PASS
|
||||
- `unset YOI_E2E_BIN && OPENAI_API_KEY=host-secret ANTHROPIC_API_KEY=host-secret GEMINI_API_KEY=host-secret cargo test -p yoi-e2e --features e2e --test panel -- --nocapture`: PASS(default cargo-build provider used; 2 panel E2E tests passed)
|
||||
- `YOI_E2E_BIN=/home/hare/Projects/yoi/.worktree/orchestration/yoi-orchestrator/target/debug/yoi OPENAI_API_KEY=host-secret ANTHROPIC_API_KEY=host-secret GEMINI_API_KEY=host-secret cargo test -p yoi-e2e --features e2e --test panel -- --nocapture`: PASS(override provider used; 2 panel E2E tests passed)
|
||||
|
||||
Residual note:
|
||||
- Cargo build provider intentionally uses build-tool environment; only tested `yoi` subprocesses are env-isolated. Future LLM/provider E2E should use fixture providers/canned servers/explicit test env, not host credentials。
|
||||
|
||||
Next:
|
||||
- Mark Ticket `done` and clean up child coder/reviewer Pods plus implementation worktree/branch. Closure remains separate。
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: orchestrator at: 2026-06-13T16:09:29Z from: inprogress to: done reason: merged_and_validated field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
E2E binary provider follow-up was reviewed, approved, merged into the Orchestrator branch as `8abc2b7f`, and validated in the Orchestrator worktree. Default E2E runs now build the current `yoi` binary before direct PTY spawn, `YOI_E2E_BIN` override remains available, and tested `yoi` subprocesses are isolated with `env_clear()` plus allowlist so host provider credentials are not inherited. Ticket implementation work is done; closure remains separate.
|
||||
|
||||
---
|
||||
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -4806,6 +4806,16 @@ dependencies = [
|
|||
"tui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoi-e2e"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.2"
|
||||
|
|
|
|||
25
Cargo.toml
25
Cargo.toml
|
|
@ -23,6 +23,31 @@ members = [
|
|||
"crates/ticket",
|
||||
"crates/project-record",
|
||||
"crates/workflow",
|
||||
"tests/e2e",
|
||||
]
|
||||
default-members = [
|
||||
"crates/client",
|
||||
"crates/daemon",
|
||||
"crates/llm-worker",
|
||||
"crates/llm-worker-macros",
|
||||
"crates/session-store",
|
||||
"crates/secrets",
|
||||
"crates/manifest",
|
||||
"crates/pod",
|
||||
"crates/yoi",
|
||||
"crates/pod-store",
|
||||
"crates/protocol",
|
||||
"crates/provider",
|
||||
"crates/pod-registry",
|
||||
"crates/session-metrics",
|
||||
"crates/session-analytics",
|
||||
"crates/lint-common",
|
||||
"crates/tools",
|
||||
"crates/tui",
|
||||
"crates/memory",
|
||||
"crates/ticket",
|
||||
"crates/project-record",
|
||||
"crates/workflow",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ version = "0.1.0"
|
|||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
e2e-test = []
|
||||
|
||||
[dependencies]
|
||||
client = { workspace = true }
|
||||
protocol = { workspace = true }
|
||||
|
|
|
|||
77
crates/tui/src/e2e_observer.rs
Normal file
77
crates/tui/src/e2e_observer.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
use std::fs::{File, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
const EVENT_PATH_ENV: &str = "YOI_TUI_TEST_EVENTS";
|
||||
const HOLD_BACKGROUND_TASK_ENV: &str = "YOI_TUI_TEST_HOLD_BACKGROUND_TASK";
|
||||
|
||||
static EVENT_WRITER: OnceLock<Option<Mutex<File>>> = OnceLock::new();
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct EventEnvelope<'a, T> {
|
||||
ts_ms: u128,
|
||||
surface: &'a str,
|
||||
event: &'a str,
|
||||
data: T,
|
||||
}
|
||||
|
||||
pub(crate) fn emit<T>(surface: &'static str, event: &'static str, data: T)
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let Some(writer) = EVENT_WRITER.get_or_init(open_event_writer).as_ref() else {
|
||||
return;
|
||||
};
|
||||
let Ok(mut writer) = writer.lock() else {
|
||||
return;
|
||||
};
|
||||
let envelope = EventEnvelope {
|
||||
ts_ms: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| duration.as_millis())
|
||||
.unwrap_or_default(),
|
||||
surface,
|
||||
event,
|
||||
data,
|
||||
};
|
||||
if serde_json::to_writer(&mut *writer, &envelope).is_ok() {
|
||||
let _ = writer.write_all(b"\n");
|
||||
let _ = writer.flush();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn hold_background_task_if_requested(task: &'static str) {
|
||||
let requested = std::env::var(HOLD_BACKGROUND_TASK_ENV).unwrap_or_default();
|
||||
if !requested
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.any(|requested| requested == task)
|
||||
{
|
||||
return;
|
||||
}
|
||||
emit(
|
||||
"panel",
|
||||
"background_task_hold_started",
|
||||
serde_json::json!({ "task": task }),
|
||||
);
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_millis(25)).await;
|
||||
}
|
||||
}
|
||||
|
||||
fn open_event_writer() -> Option<Mutex<File>> {
|
||||
let path = std::env::var_os(EVENT_PATH_ENV).map(PathBuf::from)?;
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(path)
|
||||
.ok()
|
||||
.map(Mutex::new)
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ mod cache;
|
|||
mod command;
|
||||
mod composer_history;
|
||||
mod composer_keys;
|
||||
#[cfg(feature = "e2e-test")]
|
||||
mod e2e_observer;
|
||||
mod input;
|
||||
pub mod keys;
|
||||
mod markdown;
|
||||
|
|
@ -108,6 +110,8 @@ pub async fn launch(options: LaunchOptions) -> ExitCode {
|
|||
// Always restore the terminal first so any pending eprintln below
|
||||
// shows up cleanly in scrollback rather than inside an active
|
||||
// alternate-screen buffer.
|
||||
#[cfg(feature = "e2e-test")]
|
||||
e2e_observer::emit("tui", "terminal_cleanup_started", serde_json::json!({}));
|
||||
let mut stdout = io::stdout();
|
||||
let _ = execute!(
|
||||
stdout,
|
||||
|
|
@ -117,9 +121,15 @@ pub async fn launch(options: LaunchOptions) -> ExitCode {
|
|||
);
|
||||
let _ = disable_raw_mode();
|
||||
let _ = execute!(stdout, crossterm::cursor::Show);
|
||||
#[cfg(feature = "e2e-test")]
|
||||
e2e_observer::emit("tui", "terminal_cleanup_finished", serde_json::json!({}));
|
||||
|
||||
match result {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Ok(()) => {
|
||||
#[cfg(feature = "e2e-test")]
|
||||
e2e_observer::emit("tui", "exit", serde_json::json!({ "status": "success" }));
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Err(e) => {
|
||||
// SpawnError has already been painted into the inline
|
||||
// viewport's final frame, so it's already visible in the
|
||||
|
|
@ -129,6 +139,8 @@ pub async fn launch(options: LaunchOptions) -> ExitCode {
|
|||
if e.downcast_ref::<spawn::SpawnError>().is_none() {
|
||||
eprintln!("yoi: {e}");
|
||||
}
|
||||
#[cfg(feature = "e2e-test")]
|
||||
e2e_observer::emit("tui", "exit", serde_json::json!({ "status": "failure" }));
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,6 +133,8 @@ pub(crate) async fn run(
|
|||
}
|
||||
}
|
||||
let mut next_poll = Instant::now() + MULTI_POD_POLL_INTERVAL;
|
||||
#[cfg(feature = "e2e-test")]
|
||||
let mut emitted_panel_ready = false;
|
||||
|
||||
loop {
|
||||
if let Some(result) = pending_queue_attention_notice.finish_if_ready().await {
|
||||
|
|
@ -146,6 +148,14 @@ pub(crate) async fn run(
|
|||
}
|
||||
|
||||
terminal.draw(|f| draw(f, app))?;
|
||||
#[cfg(feature = "e2e-test")]
|
||||
{
|
||||
if !emitted_panel_ready {
|
||||
crate::e2e_observer::emit("panel", "panel_ready", serde_json::json!({}));
|
||||
emitted_panel_ready = true;
|
||||
}
|
||||
app.emit_rows_rendered();
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
if now >= next_poll {
|
||||
|
|
@ -163,6 +173,8 @@ pub(crate) async fn run(
|
|||
TermEvent::Key(key) => match app.handle_key(key) {
|
||||
MultiPodAction::None => {}
|
||||
MultiPodAction::Quit => {
|
||||
#[cfg(feature = "e2e-test")]
|
||||
crate::e2e_observer::emit("panel", "quit_requested", serde_json::json!({}));
|
||||
abort_panel_background_work_for_quit(
|
||||
&mut pending_reload,
|
||||
&mut pending_queue_attention_notice,
|
||||
|
|
@ -170,12 +182,24 @@ pub(crate) async fn run(
|
|||
return Ok(MultiPodOutcome::Quit);
|
||||
}
|
||||
MultiPodAction::Open => {
|
||||
#[cfg(feature = "e2e-test")]
|
||||
crate::e2e_observer::emit(
|
||||
"panel",
|
||||
"action_requested",
|
||||
serde_json::json!({ "action": "open" }),
|
||||
);
|
||||
if let Some(request) = app.prepare_open() {
|
||||
terminal.draw(|f| draw(f, app))?;
|
||||
return Ok(MultiPodOutcome::Open(request));
|
||||
}
|
||||
}
|
||||
MultiPodAction::DispatchTicketAction(request) => {
|
||||
#[cfg(feature = "e2e-test")]
|
||||
crate::e2e_observer::emit(
|
||||
"panel",
|
||||
"action_requested",
|
||||
serde_json::json!({ "action": "ticket_action" }),
|
||||
);
|
||||
pending_reload.abort();
|
||||
pending_queue_attention_notice.abort();
|
||||
terminal.draw(|f| draw(f, app))?;
|
||||
|
|
@ -187,6 +211,12 @@ pub(crate) async fn run(
|
|||
next_poll = Instant::now() + MULTI_POD_POLL_INTERVAL;
|
||||
}
|
||||
MultiPodAction::LaunchIntake(request) => {
|
||||
#[cfg(feature = "e2e-test")]
|
||||
crate::e2e_observer::emit(
|
||||
"panel",
|
||||
"action_requested",
|
||||
serde_json::json!({ "action": "launch_intake" }),
|
||||
);
|
||||
pending_reload.abort();
|
||||
pending_queue_attention_notice.abort();
|
||||
terminal.draw(|f| draw(f, app))?;
|
||||
|
|
@ -198,6 +228,12 @@ pub(crate) async fn run(
|
|||
next_poll = Instant::now() + MULTI_POD_POLL_INTERVAL;
|
||||
}
|
||||
MultiPodAction::SendCompanion(request) => {
|
||||
#[cfg(feature = "e2e-test")]
|
||||
crate::e2e_observer::emit(
|
||||
"panel",
|
||||
"action_requested",
|
||||
serde_json::json!({ "action": "send_companion" }),
|
||||
);
|
||||
pending_reload.abort();
|
||||
pending_queue_attention_notice.abort();
|
||||
terminal.draw(|f| draw(f, app))?;
|
||||
|
|
@ -228,7 +264,18 @@ impl PendingReload {
|
|||
if self.handle.is_some() {
|
||||
return false;
|
||||
}
|
||||
#[cfg(feature = "e2e-test")]
|
||||
crate::e2e_observer::emit(
|
||||
"panel",
|
||||
"background_task_started",
|
||||
serde_json::json!({
|
||||
"task": "reload",
|
||||
"lifecycle_mode": format!("{lifecycle_mode:?}"),
|
||||
}),
|
||||
);
|
||||
self.handle = Some(tokio::spawn(async move {
|
||||
#[cfg(feature = "e2e-test")]
|
||||
crate::e2e_observer::hold_background_task_if_requested("reload").await;
|
||||
load_multi_pod_snapshot(None, lifecycle_mode).await
|
||||
}));
|
||||
true
|
||||
|
|
@ -252,6 +299,12 @@ impl PendingReload {
|
|||
return None;
|
||||
}
|
||||
let handle = self.handle.take()?;
|
||||
#[cfg(feature = "e2e-test")]
|
||||
crate::e2e_observer::emit(
|
||||
"panel",
|
||||
"background_task_finished",
|
||||
serde_json::json!({ "task": "reload" }),
|
||||
);
|
||||
Some(match handle.await {
|
||||
Ok(result) => result,
|
||||
Err(e) => Err(MultiPodError::Io(io::Error::other(format!(
|
||||
|
|
@ -262,6 +315,12 @@ impl PendingReload {
|
|||
|
||||
fn abort(&mut self) {
|
||||
if let Some(handle) = self.handle.take() {
|
||||
#[cfg(feature = "e2e-test")]
|
||||
crate::e2e_observer::emit(
|
||||
"panel",
|
||||
"background_task_aborted",
|
||||
serde_json::json!({ "task": "reload" }),
|
||||
);
|
||||
handle.abort();
|
||||
}
|
||||
}
|
||||
|
|
@ -753,6 +812,63 @@ impl PanelRowHitBox {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "e2e-test")]
|
||||
#[derive(Debug, Serialize)]
|
||||
struct PanelE2eRowKey {
|
||||
kind: &'static str,
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "e2e-test")]
|
||||
#[derive(Debug, Serialize)]
|
||||
struct PanelE2eRect {
|
||||
x: u16,
|
||||
y: u16,
|
||||
width: u16,
|
||||
height: u16,
|
||||
}
|
||||
|
||||
#[cfg(feature = "e2e-test")]
|
||||
#[derive(Debug, Serialize)]
|
||||
struct PanelE2eRenderedRow {
|
||||
key: PanelE2eRowKey,
|
||||
title: String,
|
||||
status: Option<String>,
|
||||
action: Option<&'static str>,
|
||||
rect: PanelE2eRect,
|
||||
}
|
||||
|
||||
#[cfg(feature = "e2e-test")]
|
||||
#[derive(Debug, Serialize)]
|
||||
struct PanelE2eRowsRendered {
|
||||
selected: Option<PanelE2eRowKey>,
|
||||
rows: Vec<PanelE2eRenderedRow>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "e2e-test")]
|
||||
fn panel_e2e_row_key(key: &PanelRowKey) -> PanelE2eRowKey {
|
||||
match key {
|
||||
PanelRowKey::Ticket(id) => PanelE2eRowKey {
|
||||
kind: "ticket",
|
||||
id: id.clone(),
|
||||
},
|
||||
PanelRowKey::Pod(name) => PanelE2eRowKey {
|
||||
kind: "pod",
|
||||
id: name.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "e2e-test")]
|
||||
fn panel_e2e_rect(rect: Rect) -> PanelE2eRect {
|
||||
PanelE2eRect {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MultiPodApp {
|
||||
pub(crate) list: PodList,
|
||||
pub(crate) panel: WorkspacePanelViewModel,
|
||||
|
|
@ -1069,6 +1185,16 @@ impl MultiPodApp {
|
|||
else {
|
||||
return false;
|
||||
};
|
||||
#[cfg(feature = "e2e-test")]
|
||||
crate::e2e_observer::emit(
|
||||
"panel",
|
||||
"mouse_click",
|
||||
serde_json::json!({
|
||||
"column": event.column,
|
||||
"row": event.row,
|
||||
"target": panel_e2e_row_key(&key),
|
||||
}),
|
||||
);
|
||||
self.select_panel_key(key);
|
||||
true
|
||||
}
|
||||
|
|
@ -1077,6 +1203,43 @@ impl MultiPodApp {
|
|||
self.row_hit_boxes = row_hit_boxes(rows, area);
|
||||
}
|
||||
|
||||
#[cfg(feature = "e2e-test")]
|
||||
fn emit_rows_rendered(&self) {
|
||||
let rows = self
|
||||
.row_hit_boxes
|
||||
.iter()
|
||||
.map(|hit| {
|
||||
let panel_row = self.panel.row(&hit.key);
|
||||
let (title, status, action) = match panel_row {
|
||||
Some(row) => (
|
||||
row.title.clone(),
|
||||
Some(row.status.clone()),
|
||||
row.next_action.map(NextUserAction::label),
|
||||
),
|
||||
None => match &hit.key {
|
||||
PanelRowKey::Pod(name) => (name.clone(), None, None),
|
||||
PanelRowKey::Ticket(id) => (id.clone(), None, None),
|
||||
},
|
||||
};
|
||||
PanelE2eRenderedRow {
|
||||
key: panel_e2e_row_key(&hit.key),
|
||||
title,
|
||||
status,
|
||||
action,
|
||||
rect: panel_e2e_rect(hit.rect),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
crate::e2e_observer::emit(
|
||||
"panel",
|
||||
"rows_rendered",
|
||||
PanelE2eRowsRendered {
|
||||
selected: self.selected_row.as_ref().map(panel_e2e_row_key),
|
||||
rows,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn ensure_selection_visible(&mut self) {
|
||||
let visible = visible_panel_keys(&self.panel, &self.list);
|
||||
if visible.is_empty() {
|
||||
|
|
@ -1127,12 +1290,26 @@ impl MultiPodApp {
|
|||
if let PanelRowKey::Pod(name) = &key {
|
||||
self.list.selected_name = Some(name.clone());
|
||||
}
|
||||
#[cfg(feature = "e2e-test")]
|
||||
let selected_key = key.clone();
|
||||
self.selected_row = Some(key);
|
||||
#[cfg(feature = "e2e-test")]
|
||||
crate::e2e_observer::emit(
|
||||
"panel",
|
||||
"selection_changed",
|
||||
serde_json::json!({ "selected": panel_e2e_row_key(&selected_key) }),
|
||||
);
|
||||
}
|
||||
|
||||
fn clear_panel_selection(&mut self) {
|
||||
self.selected_row = None;
|
||||
self.list.selected_name = None;
|
||||
#[cfg(feature = "e2e-test")]
|
||||
crate::e2e_observer::emit(
|
||||
"panel",
|
||||
"selection_changed",
|
||||
serde_json::json!({ "selected": serde_json::Value::Null }),
|
||||
);
|
||||
}
|
||||
|
||||
fn ensure_composer_target_available(&mut self) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ version = "0.1.0"
|
|||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
e2e-test = ["tui/e2e-test"]
|
||||
|
||||
[dependencies]
|
||||
project-record = { workspace = true }
|
||||
chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
||||
|
|
|
|||
21
tests/e2e/Cargo.toml
Normal file
21
tests/e2e/Cargo.toml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "yoi-e2e"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
default = []
|
||||
e2e = []
|
||||
|
||||
[dependencies]
|
||||
libc.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
[[test]]
|
||||
name = "panel"
|
||||
path = "tests/panel.rs"
|
||||
required-features = ["e2e"]
|
||||
1110
tests/e2e/src/lib.rs
Normal file
1110
tests/e2e/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
72
tests/e2e/tests/panel.rs
Normal file
72
tests/e2e/tests/panel.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use yoi_e2e::{FixtureWorkspace, KeyPress, PanelHarness, yoi_binary};
|
||||
|
||||
#[test]
|
||||
fn panel_mouse_click_selects_row_without_dispatching_action() -> yoi_e2e::Result<()> {
|
||||
let binary = yoi_binary()?;
|
||||
let fixture = FixtureWorkspace::new(&binary)?;
|
||||
let mut panel = PanelHarness::spawn(fixture.panel_config(binary))?;
|
||||
|
||||
panel.expect_mouse_capture_enabled()?;
|
||||
let rows = panel.wait_for_rows(2)?;
|
||||
let selected = rows.selected.clone();
|
||||
let target = rows
|
||||
.rows
|
||||
.iter()
|
||||
.find(|row| Some(&row.key) != selected.as_ref())
|
||||
.cloned()
|
||||
.expect("fixture should render a second selectable row");
|
||||
|
||||
let before_events = panel.events()?.len();
|
||||
panel.click(&target)?;
|
||||
panel.expect_selection(&target.key)?;
|
||||
|
||||
let events = panel.events()?;
|
||||
assert!(
|
||||
events[before_events..]
|
||||
.iter()
|
||||
.all(|event| event.event != "action_requested"),
|
||||
"mouse selection must not dispatch panel actions; artifacts at {}",
|
||||
panel.artifacts().dir.display()
|
||||
);
|
||||
|
||||
panel.press(KeyPress::CtrlC)?;
|
||||
let status = panel.expect_exit_within(PanelHarness::default_exit_wait())?;
|
||||
assert!(status.success(), "panel should exit cleanly with Ctrl+C");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panel_ctrl_c_exits_promptly_after_background_barrier() -> yoi_e2e::Result<()> {
|
||||
let binary = yoi_binary()?;
|
||||
let fixture = FixtureWorkspace::new(&binary)?;
|
||||
let mut panel =
|
||||
PanelHarness::spawn(fixture.panel_config_holding_background_task(binary, "reload"))?;
|
||||
|
||||
panel.wait_for("panel_ready", Duration::from_secs(5), |event| {
|
||||
event.event == "panel_ready"
|
||||
})?;
|
||||
panel.expect_background_task_pending("reload")?;
|
||||
|
||||
let started = std::time::Instant::now();
|
||||
panel.press(KeyPress::CtrlC)?;
|
||||
let status = panel.expect_exit_within(PanelHarness::default_exit_wait())?;
|
||||
let elapsed = started.elapsed();
|
||||
|
||||
assert!(status.success(), "panel should exit cleanly with Ctrl+C");
|
||||
assert!(
|
||||
elapsed <= PanelHarness::default_exit_wait(),
|
||||
"quit latency {elapsed:?} exceeded threshold; artifacts at {}",
|
||||
panel.artifacts().dir.display()
|
||||
);
|
||||
assert!(
|
||||
panel
|
||||
.events()?
|
||||
.iter()
|
||||
.any(|event| event.event == "quit_requested"),
|
||||
"quit_requested observability event missing; artifacts at {}",
|
||||
panel.artifacts().dir.display()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user