fusible 0.4.0

Electronic linter: a fuse that blows to block firmware code generation
//! Unit tests for detection precedence and path lookup.

use std::ffi::OsString;
use std::fs::{create_dir_all, File};
use std::path::{Path, PathBuf};

use crate::tscircuit::detect::detect_runner;
use crate::tscircuit::error::RunnerError;
use crate::tscircuit::RunnerKind;

struct TempDirGuard {
    path: PathBuf,
}

impl TempDirGuard {
    fn new(prefix: &str) -> Self {
        let unique = format!(
            "{prefix}_{}_{}",
            std::process::id(),
            std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_nanos()
        );
        let path = std::env::temp_dir().join(unique);
        create_dir_all(&path).unwrap();
        Self { path }
    }
}

impl Drop for TempDirGuard {
    fn drop(&mut self) {
        let _ = std::fs::remove_dir_all(&self.path);
    }
}

fn create_executable(path: &Path) {
    if let Some(parent) = path.parent() {
        create_dir_all(parent).unwrap();
    }
    File::create(path).unwrap();

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let mut perms = std::fs::metadata(path).unwrap().permissions();
        perms.set_mode(0o755);
        std::fs::set_permissions(path, perms).unwrap();
    }
}

#[test]
fn detects_local_node_modules_tsci_first() {
    let temp = TempDirGuard::new("fusible_detect_local");
    let local_bin = temp.path.join("node_modules/.bin/tsci");
    create_executable(&local_bin);

    let global_dir = temp.path.join("global");
    create_executable(&global_dir.join("tsci"));

    let path_os = std::env::join_paths([&global_dir]).unwrap();
    let runner = detect_runner(&temp.path, Some(&path_os)).expect("runner detected");

    assert_eq!(runner.kind(), &RunnerKind::Local(local_bin));
}

#[test]
fn detects_global_tsci_when_local_absent() {
    let temp = TempDirGuard::new("fusible_detect_global");
    let global_dir = temp.path.join("global");
    let global_bin = global_dir.join("tsci");
    create_executable(&global_bin);

    let path_os = std::env::join_paths([&global_dir]).unwrap();
    let runner = detect_runner(&temp.path, Some(&path_os)).expect("runner detected");

    assert_eq!(runner.kind(), &RunnerKind::Global(global_bin));
}

#[test]
fn detects_global_tscircuit_when_tsci_absent() {
    let temp = TempDirGuard::new("fusible_detect_tscircuit");
    let global_dir = temp.path.join("global");
    let global_bin = global_dir.join("tscircuit");
    create_executable(&global_bin);

    let path_os = std::env::join_paths([&global_dir]).unwrap();
    let runner = detect_runner(&temp.path, Some(&path_os)).expect("runner detected");

    assert_eq!(runner.kind(), &RunnerKind::Global(global_bin));
}

#[test]
fn detection_fails_with_not_found_when_no_runner() {
    let temp = TempDirGuard::new("fusible_detect_empty");
    let empty_path = OsString::new();
    let err = detect_runner(&temp.path, Some(&empty_path)).expect_err("should fail");

    assert!(matches!(err, RunnerError::NotFound));
}