use std::process::ExitCode;
use serde::Serialize;
use ytsaurus_client::{Client, ClientError, MapSpec, yson_build};
const BASE: &str = "//tmp/ytsaurus_rs_diagnose";
const WORKER: &str = "target/x86_64-unknown-linux-musl/release-worker/boom";
const MARKER: &str = "boom: this job fails on purpose";
const SAMPLE: [Row; 2] = [Row { key: "a", count: 1 }, Row { key: "b", count: 2 }];
fn main() -> ExitCode {
match run() {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("\ndiagnose failed: {e}");
ExitCode::FAILURE
}
}
}
fn run() -> Result<(), ClientError> {
let client = Client::from_env()?;
if !std::path::Path::new(WORKER).exists() {
eprintln!("worker not found at {WORKER}");
eprintln!("build it first: scripts/build-worker.sh boom");
return Err(ClientError::Config(
"the worker binary has not been built".to_owned(),
));
}
step("Preparing Cypress");
client.remove_tree(BASE)?;
client.create("map_node", BASE)?;
client.create("table", &format!("{BASE}/input"))?;
client.create("table", &format!("{BASE}/output"))?;
client.upload_worker(WORKER, &format!("{BASE}/boom"))?;
client.write_table_rows(format!("{BASE}/input"), SAMPLE)?;
done(&format!("{BASE} ready, worker uploaded"));
step("Starting an operation that will fail");
let spec = MapSpec::new(
"./boom",
[format!("{BASE}/input")],
[format!("{BASE}/output")],
)
.with_local_file(format!("{BASE}/boom"))
.with_memory_limit(512 * 1024 * 1024)
.with_raw("max_failed_job_count", yson_build::int(1));
let id = client.start_map(&spec)?;
done(&format!("operation {id}"));
step("Waiting for it to fail");
let Err(error) = client.wait_for_operation(&id) else {
return Err(ClientError::Config(
"the operation completed; `boom` is supposed to fail".to_owned(),
));
};
step("What the launcher was told");
println!("{error}\n");
let ClientError::OperationFailed { jobs, .. } = &error else {
eprintln!(" expected an OperationFailed, got a different error");
return Err(error);
};
check("a failed job was reported", !jobs.is_empty())?;
check(
"the job's stderr came back",
jobs.iter().any(|j| j.stderr.is_some()),
)?;
check(
"the stderr is the job's own panic",
error.to_string().contains(MARKER),
)?;
check(
"the job error explains the exit",
jobs.iter().any(|j| j.error.is_some()),
)?;
println!("\nA failed operation now says why, without opening the web UI.");
println!("Tables left at {BASE}");
Ok(())
}
#[derive(Serialize)]
struct Row {
key: &'static str,
count: i64,
}
fn step(what: &str) {
println!("\n== {what}");
}
fn done(what: &str) {
println!(" ok {what}");
}
fn check(what: &str, passed: bool) -> Result<(), ClientError> {
if passed {
done(what);
return Ok(());
}
eprintln!(" FAIL {what}");
Err(ClientError::Config(format!("check failed: {what}")))
}