test: build e2e yoi binary provider
This commit is contained in:
parent
a4df975415
commit
13d0053036
|
|
@ -0,0 +1,21 @@
|
||||||
|
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.
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
title: 'E2E harness が最新 yoi binary を自動 build して使うようにする'
|
title: 'E2E harness が最新 yoi binary を自動 build して使うようにする'
|
||||||
state: 'inprogress'
|
state: 'inprogress'
|
||||||
created_at: '2026-06-13T15:46:07Z'
|
created_at: '2026-06-13T15:46:07Z'
|
||||||
updated_at: '2026-06-13T15:47:00Z'
|
updated_at: '2026-06-13T15:54:18Z'
|
||||||
assignee: null
|
assignee: null
|
||||||
readiness: 'ready'
|
readiness: 'ready'
|
||||||
queued_by: 'yoi ticket'
|
queued_by: 'yoi ticket'
|
||||||
|
|
|
||||||
|
|
@ -83,4 +83,33 @@ Escalate if:
|
||||||
|
|
||||||
Ticket evidence、existing E2E harness code map、Orchestrator worktree clean state を確認した。ユーザーが明示的に修正を依頼しており、blocking relation はない。実装 side effect の前に inprogress acceptance を記録する。
|
Ticket evidence、existing E2E harness code map、Orchestrator worktree clean state を確認した。ユーザーが明示的に修正を依頼しており、blocking relation はない。実装 side effect の前に inprogress acceptance を記録する。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- 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.
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use std::os::fd::{AsRawFd, FromRawFd};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Child, Command, ExitStatus, Stdio};
|
use std::process::{Child, Command, ExitStatus, Stdio};
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex, OnceLock};
|
||||||
use std::thread::{self, JoinHandle};
|
use std::thread::{self, JoinHandle};
|
||||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
|
@ -23,12 +23,42 @@ static FIXTURE_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, HarnessError>;
|
pub type Result<T> = std::result::Result<T, HarnessError>;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
pub struct BinaryProviderInfo {
|
||||||
|
pub provider: String,
|
||||||
|
pub binary: PathBuf,
|
||||||
|
pub workspace_root: PathBuf,
|
||||||
|
pub cargo: Option<PathBuf>,
|
||||||
|
pub build_args: Vec<String>,
|
||||||
|
pub build_command: Option<String>,
|
||||||
|
pub profile: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BinaryProviderInfo {
|
||||||
|
fn log(&self) {
|
||||||
|
match &self.build_command {
|
||||||
|
Some(command) => eprintln!(
|
||||||
|
"yoi-e2e binary provider={} command={} binary={}",
|
||||||
|
self.provider,
|
||||||
|
command,
|
||||||
|
self.binary.display()
|
||||||
|
),
|
||||||
|
None => eprintln!(
|
||||||
|
"yoi-e2e binary provider={} binary={}",
|
||||||
|
self.provider,
|
||||||
|
self.binary.display()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum HarnessError {
|
pub enum HarnessError {
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
Json(serde_json::Error),
|
Json(serde_json::Error),
|
||||||
CommandFailed {
|
CommandFailed {
|
||||||
program: PathBuf,
|
program: PathBuf,
|
||||||
|
args: Vec<String>,
|
||||||
status: ExitStatus,
|
status: ExitStatus,
|
||||||
stdout: String,
|
stdout: String,
|
||||||
stderr: String,
|
stderr: String,
|
||||||
|
|
@ -51,13 +81,14 @@ impl std::fmt::Display for HarnessError {
|
||||||
Self::Json(err) => write!(f, "json error: {err}"),
|
Self::Json(err) => write!(f, "json error: {err}"),
|
||||||
Self::CommandFailed {
|
Self::CommandFailed {
|
||||||
program,
|
program,
|
||||||
|
args,
|
||||||
status,
|
status,
|
||||||
stdout,
|
stdout,
|
||||||
stderr,
|
stderr,
|
||||||
} => write!(
|
} => write!(
|
||||||
f,
|
f,
|
||||||
"{} exited with {status}\nstdout:\n{stdout}\nstderr:\n{stderr}",
|
"{} exited with {status}\nstdout:\n{stdout}\nstderr:\n{stderr}",
|
||||||
program.display()
|
command_display(program, args)
|
||||||
),
|
),
|
||||||
Self::Timeout { what, artifacts } => write!(
|
Self::Timeout { what, artifacts } => write!(
|
||||||
f,
|
f,
|
||||||
|
|
@ -66,7 +97,7 @@ impl std::fmt::Display for HarnessError {
|
||||||
),
|
),
|
||||||
Self::MissingBinary(path) => write!(
|
Self::MissingBinary(path) => write!(
|
||||||
f,
|
f,
|
||||||
"missing yoi binary {}; run `cargo build -p yoi --features e2e-test` or set YOI_E2E_BIN",
|
"missing yoi binary {}; set YOI_E2E_BIN to an existing binary or inspect target/e2e-artifacts/binary-provider.json",
|
||||||
path.display()
|
path.display()
|
||||||
),
|
),
|
||||||
Self::MouseCaptureNotEnabled { artifacts } => write!(
|
Self::MouseCaptureNotEnabled { artifacts } => write!(
|
||||||
|
|
@ -496,13 +527,7 @@ pub struct FixtureWorkspace {
|
||||||
|
|
||||||
impl FixtureWorkspace {
|
impl FixtureWorkspace {
|
||||||
pub fn new(binary: &Path) -> Result<Self> {
|
pub fn new(binary: &Path) -> Result<Self> {
|
||||||
let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
let workspace_root = workspace_root()?;
|
||||||
.parent()
|
|
||||||
.and_then(Path::parent)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
HarnessError::Protocol("could not resolve workspace root for artifacts".to_owned())
|
|
||||||
})?
|
|
||||||
.to_path_buf();
|
|
||||||
let root = workspace_root
|
let root = workspace_root
|
||||||
.join("target")
|
.join("target")
|
||||||
.join("e2e-artifacts")
|
.join("e2e-artifacts")
|
||||||
|
|
@ -602,19 +627,134 @@ impl FixtureWorkspace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn yoi_binary() -> PathBuf {
|
pub fn yoi_binary() -> Result<PathBuf> {
|
||||||
if let Some(path) = std::env::var_os("YOI_E2E_BIN") {
|
Ok(yoi_binary_info()?.binary)
|
||||||
return PathBuf::from(path);
|
}
|
||||||
|
|
||||||
|
pub fn yoi_binary_info() -> Result<BinaryProviderInfo> {
|
||||||
|
static BINARY_INFO: OnceLock<std::result::Result<BinaryProviderInfo, String>> = OnceLock::new();
|
||||||
|
match BINARY_INFO.get_or_init(|| resolve_yoi_binary().map_err(|err| err.to_string())) {
|
||||||
|
Ok(info) => Ok(info.clone()),
|
||||||
|
Err(message) => Err(HarnessError::Protocol(message.clone())),
|
||||||
}
|
}
|
||||||
let mut path = std::env::current_exe().expect("current executable path");
|
}
|
||||||
|
|
||||||
|
fn resolve_yoi_binary() -> Result<BinaryProviderInfo> {
|
||||||
|
if let Some(path) = std::env::var_os("YOI_E2E_BIN") {
|
||||||
|
let info = BinaryProviderInfo {
|
||||||
|
provider: "YOI_E2E_BIN".to_owned(),
|
||||||
|
binary: PathBuf::from(path),
|
||||||
|
workspace_root: workspace_root()?,
|
||||||
|
cargo: None,
|
||||||
|
build_args: Vec::new(),
|
||||||
|
build_command: None,
|
||||||
|
profile: test_profile(),
|
||||||
|
};
|
||||||
|
info.log();
|
||||||
|
write_binary_provider_artifact(&info)?;
|
||||||
|
return Ok(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
let workspace_root = workspace_root()?;
|
||||||
|
let cargo = PathBuf::from(std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into()));
|
||||||
|
let mut args = vec![
|
||||||
|
"build".to_owned(),
|
||||||
|
"-p".to_owned(),
|
||||||
|
"yoi".to_owned(),
|
||||||
|
"--features".to_owned(),
|
||||||
|
"e2e-test".to_owned(),
|
||||||
|
"--bin".to_owned(),
|
||||||
|
"yoi".to_owned(),
|
||||||
|
];
|
||||||
|
if test_profile() == "release" {
|
||||||
|
args.push("--release".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
let command = command_display(&cargo, &args);
|
||||||
|
eprintln!("yoi-e2e binary provider=cargo-build command={command}");
|
||||||
|
let output = Command::new(&cargo)
|
||||||
|
.args(&args)
|
||||||
|
.current_dir(&workspace_root)
|
||||||
|
.output()?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(HarnessError::CommandFailed {
|
||||||
|
program: cargo,
|
||||||
|
args,
|
||||||
|
status: output.status,
|
||||||
|
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
|
||||||
|
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let binary = current_target_profile_dir()?.join(binary_name());
|
||||||
|
let info = BinaryProviderInfo {
|
||||||
|
provider: "cargo-build".to_owned(),
|
||||||
|
binary,
|
||||||
|
workspace_root,
|
||||||
|
cargo: Some(cargo),
|
||||||
|
build_args: args,
|
||||||
|
build_command: Some(command),
|
||||||
|
profile: test_profile(),
|
||||||
|
};
|
||||||
|
info.log();
|
||||||
|
write_binary_provider_artifact(&info)?;
|
||||||
|
if !info.binary.exists() {
|
||||||
|
return Err(HarnessError::MissingBinary(info.binary));
|
||||||
|
}
|
||||||
|
Ok(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn workspace_root() -> Result<PathBuf> {
|
||||||
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.parent()
|
||||||
|
.and_then(Path::parent)
|
||||||
|
.map(Path::to_path_buf)
|
||||||
|
.ok_or_else(|| HarnessError::Protocol("could not resolve workspace root".to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_target_profile_dir() -> Result<PathBuf> {
|
||||||
|
let mut path = std::env::current_exe()?;
|
||||||
while let Some(name) = path.file_name().and_then(|name| name.to_str()) {
|
while let Some(name) = path.file_name().and_then(|name| name.to_str()) {
|
||||||
if name == "debug" || name == "release" {
|
if name == "debug" || name == "release" {
|
||||||
path.push("yoi");
|
return Ok(path);
|
||||||
return path;
|
|
||||||
}
|
}
|
||||||
path.pop();
|
path.pop();
|
||||||
}
|
}
|
||||||
PathBuf::from("target/debug/yoi")
|
Ok(workspace_root()?.join("target").join(test_profile()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_profile() -> String {
|
||||||
|
let Ok(mut path) = std::env::current_exe() else {
|
||||||
|
return "debug".to_owned();
|
||||||
|
};
|
||||||
|
while let Some(name) = path.file_name().and_then(|name| name.to_str()) {
|
||||||
|
if name == "debug" || name == "release" {
|
||||||
|
return name.to_owned();
|
||||||
|
}
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
"debug".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn binary_name() -> String {
|
||||||
|
format!("yoi{}", std::env::consts::EXE_SUFFIX)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_binary_provider_artifact(info: &BinaryProviderInfo) -> Result<()> {
|
||||||
|
let dir = info.workspace_root.join("target").join("e2e-artifacts");
|
||||||
|
fs::create_dir_all(&dir)?;
|
||||||
|
fs::write(
|
||||||
|
dir.join("binary-provider.json"),
|
||||||
|
serde_json::to_vec_pretty(info)?,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_display(program: &Path, args: &[String]) -> String {
|
||||||
|
std::iter::once(program.display().to_string())
|
||||||
|
.chain(args.iter().cloned())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_pty(size: (u16, u16)) -> Result<(File, File)> {
|
fn open_pty(size: (u16, u16)) -> Result<(File, File)> {
|
||||||
|
|
@ -704,6 +844,7 @@ fn run_yoi_capture(
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(HarnessError::CommandFailed {
|
return Err(HarnessError::CommandFailed {
|
||||||
program: binary.to_path_buf(),
|
program: binary.to_path_buf(),
|
||||||
|
args: args.iter().map(|arg| (*arg).to_owned()).collect(),
|
||||||
status: output.status,
|
status: output.status,
|
||||||
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
|
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
|
||||||
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
|
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use yoi_e2e::{FixtureWorkspace, KeyPress, PanelHarness, yoi_binary};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn panel_mouse_click_selects_row_without_dispatching_action() -> yoi_e2e::Result<()> {
|
fn panel_mouse_click_selects_row_without_dispatching_action() -> yoi_e2e::Result<()> {
|
||||||
let binary = yoi_binary();
|
let binary = yoi_binary()?;
|
||||||
let fixture = FixtureWorkspace::new(&binary)?;
|
let fixture = FixtureWorkspace::new(&binary)?;
|
||||||
let mut panel = PanelHarness::spawn(fixture.panel_config(binary))?;
|
let mut panel = PanelHarness::spawn(fixture.panel_config(binary))?;
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ fn panel_mouse_click_selects_row_without_dispatching_action() -> yoi_e2e::Result
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn panel_ctrl_c_exits_promptly_after_background_barrier() -> yoi_e2e::Result<()> {
|
fn panel_ctrl_c_exits_promptly_after_background_barrier() -> yoi_e2e::Result<()> {
|
||||||
let binary = yoi_binary();
|
let binary = yoi_binary()?;
|
||||||
let fixture = FixtureWorkspace::new(&binary)?;
|
let fixture = FixtureWorkspace::new(&binary)?;
|
||||||
let mut panel =
|
let mut panel =
|
||||||
PanelHarness::spawn(fixture.panel_config_holding_background_task(binary, "reload"))?;
|
PanelHarness::spawn(fixture.panel_config_holding_background_task(binary, "reload"))?;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user