nishikaze 0.3.2

Zephyr build system companion.
Documentation
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::atomic::{AtomicUsize, Ordering};

fn make_temp_dir(label: &str) -> PathBuf {
    static COUNTER: AtomicUsize = AtomicUsize::new(0);
    let mut dir = std::env::temp_dir();
    dir.push(format!(
        "nishikaze-integration-{}-{}",
        label,
        COUNTER.fetch_add(1, Ordering::Relaxed)
    ));
    if dir.exists() {
        fs::remove_dir_all(&dir).expect("clean temp dir");
    }
    fs::create_dir_all(&dir).expect("create temp dir");
    dir
}

fn kaze_bin() -> PathBuf {
    if let Some(bin) = std::env::var_os("CARGO_BIN_EXE_kaze") {
        return PathBuf::from(bin);
    }

    let exe = std::env::current_exe().expect("current_exe");
    let deps_dir = exe.parent().expect("deps dir");
    let debug_dir = deps_dir.parent().expect("debug dir");
    let bin_name = if cfg!(windows) { "kaze.exe" } else { "kaze" };
    let bin = debug_dir.join(bin_name);
    if bin.is_file() {
        return bin;
    }

    panic!("kaze binary not found; tried {}", bin.display());
}

fn write_kaze_toml(dir: &Path, body: &str) {
    let path = dir.join("kaze.toml");
    fs::write(&path, body).expect("write kaze.toml");
}

fn write_fake_tool(dir: &Path, name: &str, out: &str, err: &str, code: i32) -> PathBuf {
    let path = dir.join(name);
    let script = format!(
        "#!/bin/sh\necho \"{}\"\necho \"{}\" 1>&2\nexit {}\n",
        out, err, code
    );
    fs::write(&path, script).expect("write script");
    let mut perms = fs::metadata(&path).expect("stat script").permissions();
    perms.set_mode(0o755);
    fs::set_permissions(&path, perms).expect("chmod script");
    path
}

#[test]
fn kaze_init_creates_config() {
    let dir = make_temp_dir("init");
    let status = Command::new(kaze_bin())
        .arg("--board")
        .arg("native_sim")
        .arg("init")
        .current_dir(&dir)
        .status()
        .expect("run kaze init");
    assert!(status.success());
    assert!(dir.join("kaze.toml").is_file());
}

#[test]
fn kaze_clean_fails_without_config() {
    let dir = make_temp_dir("clean-missing");
    let output = Command::new(kaze_bin())
        .arg("clean")
        .current_dir(&dir)
        .output()
        .expect("run kaze clean");
    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("project_dir discovery failed"));
}

#[test]
fn kaze_build_logs_command_and_profile_status() {
    let dir = make_temp_dir("build-logs");
    write_kaze_toml(
        &dir,
        r#"[project]
board = "native_sim"
"#,
    );

    let output = Command::new(kaze_bin())
        .arg("-vvv")
        .arg("-d")
        .arg("build")
        .env("KAZE_TESTING", "1")
        .env("KAZE_LOGS", "1")
        .current_dir(&dir)
        .output()
        .expect("run kaze build");
    assert!(output.status.success());

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("kaze: Running command 'build'"));
    assert!(stdout.contains("kaze: Profiles not configured - running in profile-less mode"));
    assert!(stdout.contains("kaze: Building project"));
}

#[test]
fn kaze_quiet_suppresses_logs_and_cmd_output() {
    let dir = make_temp_dir("verbosity-quiet");
    let bin_dir = dir.join("bin");
    fs::create_dir_all(&bin_dir).expect("create bin dir");
    write_fake_tool(&bin_dir, "cmake", "CM_OUT", "CM_ERR", 0);

    write_kaze_toml(
        &dir,
        r#"[project]
board = "native_sim"
"#,
    );

    let path = std::env::var("PATH").unwrap_or_default();
    let output = Command::new(kaze_bin())
        .arg("-v")
        .arg("conf")
        .env("PATH", format!("{}:{}", bin_dir.display(), path))
        .current_dir(&dir)
        .output()
        .expect("run kaze conf");

    assert!(output.status.success());
    assert!(output.stdout.is_empty());
    assert!(output.stderr.is_empty());
}

#[test]
fn kaze_normal_shows_logs_only_on_success() {
    let dir = make_temp_dir("verbosity-normal");
    let bin_dir = dir.join("bin");
    fs::create_dir_all(&bin_dir).expect("create bin dir");
    write_fake_tool(&bin_dir, "cmake", "CM_OUT", "CM_ERR", 0);

    write_kaze_toml(
        &dir,
        r#"[project]
board = "native_sim"
"#,
    );

    let path = std::env::var("PATH").unwrap_or_default();
    let output = Command::new(kaze_bin())
        .arg("conf")
        .env("PATH", format!("{}:{}", bin_dir.display(), path))
        .current_dir(&dir)
        .output()
        .expect("run kaze conf");

    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stdout.contains("kaze:"));
    assert!(stdout.contains("Configure completed"));
    assert!(!stdout.contains("CM_OUT"));
    assert!(!stderr.contains("CM_ERR"));
    assert!(!stderr.contains("cmake"));
}

#[test]
fn kaze_verbose_always_shows_command_output() {
    let dir = make_temp_dir("verbosity-verbose");
    let bin_dir = dir.join("bin");
    fs::create_dir_all(&bin_dir).expect("create bin dir");
    write_fake_tool(&bin_dir, "cmake", "CM_OUT", "CM_ERR", 0);

    write_kaze_toml(
        &dir,
        r#"[project]
board = "native_sim"
"#,
    );

    let path = std::env::var("PATH").unwrap_or_default();
    let output = Command::new(kaze_bin())
        .arg("-vvv")
        .arg("conf")
        .env("PATH", format!("{}:{}", bin_dir.display(), path))
        .current_dir(&dir)
        .output()
        .expect("run kaze conf");

    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stdout.contains("kaze:"));
    assert!(!stdout.contains("..."));
    assert!(stdout.contains("CM_OUT"));
    assert!(stderr.contains("CM_ERR"));
    assert!(stderr.contains("cmake"));
}

#[test]
fn kaze_normal_shows_command_output_on_error() {
    let dir = make_temp_dir("verbosity-normal-error");
    let bin_dir = dir.join("bin");
    fs::create_dir_all(&bin_dir).expect("create bin dir");
    write_fake_tool(&bin_dir, "cmake", "CM_OUT", "CM_ERR", 1);

    write_kaze_toml(
        &dir,
        r#"[project]
board = "native_sim"
"#,
    );

    let path = std::env::var("PATH").unwrap_or_default();
    let output = Command::new(kaze_bin())
        .arg("conf")
        .env("PATH", format!("{}:{}", bin_dir.display(), path))
        .current_dir(&dir)
        .output()
        .expect("run kaze conf");

    assert!(!output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stdout.contains("CM_OUT"));
    assert!(stderr.contains("CM_ERR"));
    assert!(stderr.contains("cmake"));
}