Files
VoiceStudio/frontend/src-tauri/build.rs
T
Palash DebnathandClaude Fable 5 3d0c9605df test(shell): backend-lifecycle fault-injection harness (#1551)
* test(shell): backend-lifecycle fault-injection harness

Runs spawn_backend_and_wait/supervise_backend against REAL dying child
processes and asserts the user receives the correct NAMED diagnosis —
not merely that recovery happens. Wrong/missing explanation was 61% of
the historical "can't reach the backend" class; this rig is the
permanent regression harness for every future lifecycle fix.

Seam: OMNIVOICE_BACKEND_CMD (JSON argv or whitespace form) runs any
command as "the backend" — venv bootstrap and ffmpeg resolution are
skipped, everything else (err-log run offsets, drainer threads, env
pinning, real OS pipes, spawn-failure diagnostics) stays real. Plus
OMNIVOICE_LOG_DIR (per-test log+marker dirs, also a support tool) and
harness-only timing overrides OMNIVOICE_STARTUP_BUDGET_S /
OMNIVOICE_SUPERVISOR_POLL_MS whose production defaults are pinned by
unit tests. Lifecycle fns genericized over tauri::Runtime for the
MockRuntime app; behavior-neutral with the env unset (unit-pinned).

Scenarios (tests/backend_lifecycle.rs, scenario children = this test
binary re-invoking itself; serial by mutex + CI --test-threads=1):
- port conflict (exit 78) → the detectHints-matchable port phrasing
- generic chained traceback → root cause survives into the diagnosis
  and the crash marker
- spawn failure → spawn diagnostic reaches the user, NO bogus marker
- slow start past budget → timeout names the budget + last stderr
- post-Ready crash loop → 3 restarts announced, markers before restarts,
  "kept crashing" diagnosis naming the last exit
- SIGKILL (unix) → named as signal 9
- deliberate kill → supervisor yields silently, no marker, never Failed
- deferred-startup FATAL → the named step reaches the user, forensics,
  and the splash narration

CI: harness added to the 3-OS tauri-cross-platform matrix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(ci): temporary Windows loader bisect probe for the harness binary

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(shell): embed Common-Controls v6 manifest into Windows test binaries

Bisected on #1551: EVERY integration-test binary of this crate died at
load on Windows with STATUS_ENTRYPOINT_NOT_FOUND (0xc0000139) — cargo
gives test binaries no manifest, so the loader resolves comctl32 v5,
which lacks the TaskDialogIndirect entry point tauri's dialog/tray stack
imports. build.rs now embeds tests/windows-test.manifest via
rustc-link-arg-tests on Windows targets. Bisect probe removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): scenario gate is PID-valued — the parent can't self-inject

CodeRabbit on #1551: in a parallel local `cargo test`, the parent's own
scenario_child test could observe the armed env and start playing the
backend in-process (binding the port, idling 600s). The gate value is
now the arming process's PID; a matching PID stays inert, so only the
spawned child — a different process — runs the scenario.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 18:45:27 +00:00

65 lines
3.0 KiB
Rust

use std::path::PathBuf;
// Create a placeholder `binaries/<name>-<target-triple>` file if one doesn't
// already exist for the current target. Tauri's `bundle.externalBin`
// config is validated at every build (including `cargo check`), and it
// hard-errors when the source binary is missing — which it is in dev,
// because the real binaries are only fetched during release builds in CI.
// The placeholder is empty (zero bytes) and cannot actually be run;
// `find_bundled_*()` at runtime falls back to PATH or pip-bundled binaries
// when the bundled file isn't a real executable. CI overwrites these files
// with the real binaries before the tauri-action bundle step.
fn ensure_sidecar_placeholder(name: &str) {
let triple = std::env::var("TARGET").unwrap_or_default();
if triple.is_empty() {
return;
}
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into());
let binaries_dir = PathBuf::from(&manifest_dir).join("binaries");
let _ = std::fs::create_dir_all(&binaries_dir);
let suffix = if triple.contains("windows") { ".exe" } else { "" };
let target_path = binaries_dir.join(format!("{}-{}{}", name, triple, suffix));
if !target_path.exists() {
let _ = std::fs::write(&target_path, b"");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Ok(meta) = std::fs::metadata(&target_path) {
let mut perms = meta.permissions();
perms.set_mode(0o755);
let _ = std::fs::set_permissions(&target_path, perms);
}
}
}
}
fn main() {
// backend.rs bakes the analytics destination in with option_env!, which cargo
// resolves at COMPILE time — so without these, a cached build would keep the
// token it was first compiled with (in practice: none), and the secret would
// appear to be ignored. Tell cargo the build depends on them.
println!("cargo:rerun-if-env-changed=VITE_POSTHOG_KEY");
println!("cargo:rerun-if-env-changed=VITE_POSTHOG_HOST");
ensure_sidecar_placeholder("uv");
ensure_sidecar_placeholder("ffmpeg");
ensure_sidecar_placeholder("ffprobe");
// Windows test binaries need the Common-Controls v6 manifest that
// tauri-build embeds into the app binary but cargo gives tests none of:
// without it the loader resolves comctl32 v5 (no TaskDialogIndirect —
// imported by tauri's dialog/tray stack) and every integration-test
// binary dies at load with STATUS_ENTRYPOINT_NOT_FOUND (0xc0000139).
// See tests/windows-test.manifest.
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") {
let manifest = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into()))
.join("tests")
.join("windows-test.manifest");
println!("cargo:rerun-if-changed={}", manifest.display());
println!("cargo:rustc-link-arg-tests=/MANIFEST:EMBED");
println!("cargo:rustc-link-arg-tests=/MANIFESTINPUT:{}", manifest.display());
}
tauri_build::build();
}