//
// Syd: rock-solid application kernel
// lib/tests/tests.rs: libsyd tests
//
// Copyright (c) 2023, 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: LGPL-3.0

use std::{path::PathBuf, process::Command};

#[test]
fn test_libsyd_c() {
    let curdir = std::fs::canonicalize(std::env::current_dir().expect("current directory"))
        .expect("canonicalize");
    let curdir = curdir.display().to_string();
    eprintln!("Current directory: {curdir}");
    let libdir = std::env::var("LD_LIBRARY_PATH").unwrap_or_default();
    let libdir = libdir.split(':').next().unwrap_or("");
    eprintln!("LibSyd directory: {libdir}");

    let rootdir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
    let targetd = std::env::var("CARGO_TARGET_DIR")
        .map(PathBuf::from)
        .unwrap_or_else(|_| PathBuf::from(&rootdir).parent().unwrap().join("target"));
    let out_bin = targetd.join("libsyd_test");
    let src = PathBuf::from(&rootdir)
        .join("examples")
        .join("libsyd_test.c");
    assert!(src.exists(), "examples/libsyd_test.c missing");

    // Ensure required C libs with minimum versions.
    pkg_config::Config::new()
        .atleast_version("0.15.0")
        .probe("check")
        .expect("check >= 0.15.0 not found");
    pkg_config::Config::new()
        .atleast_version("2.1.0")
        .probe("yajl")
        .expect("yajl >= 2.1.0 not found");

    // Use cc to compile and link the C test binary against libsyd + check + yajl.
    let cc = std::env::var_os("CC")
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from("cc"));
    let mut cmd = Command::new(cc);
    cmd.arg("-std=c99")
        .arg("-g")
        .arg("-O2")
        .arg("-Wall")
        .arg("-Wextra")
        .arg("-I")
        .arg(&rootdir)
        .arg(&src)
        .arg("-L")
        .arg(libdir)
        .arg("-lsyd")
        .arg(format!("-Wl,-rpath,{libdir}"))
        .arg("-o")
        .arg(&out_bin);

    // pkg-config flags
    let check = pkg_config::Config::new()
        .probe("check")
        .expect("check probe");
    let yajl = pkg_config::Config::new().probe("yajl").expect("yajl probe");
    for p in yajl.include_paths.iter().chain(check.include_paths.iter()) {
        cmd.arg("-I").arg(p);
    }
    for p in yajl.link_paths.iter().chain(check.link_paths.iter()) {
        cmd.arg("-L").arg(p);
    }
    for l in yajl.libs.iter().chain(check.libs.iter()) {
        cmd.arg(format!("-l{l}"));
    }
    for (k, v) in yajl.defines.iter().chain(check.defines.iter()) {
        if let Some(v) = v {
            cmd.arg(format!("-D{k}={v}"));
        } else {
            cmd.arg(format!("-D{k}"));
        }
    }

    let status = cmd.status().expect("invoke cc");
    assert!(
        status.success(),
        "compile/link C example failed: {status:?}"
    );

    // Execute under Syd.
    //
    // Check complains without trace/allow_unsafe_create.
    //
    // check-0.15.2/src/check_log.c:461:
    // Error in call to fopen while opening file /proc/self/fd/1:
    // Too many levels of symbolic links
    //
    // See: https://builds.sr.ht/~alip/job/1607060#task-libc
    let status = Command::new("syd")
        .arg("-plib")
        .arg("-pnopie")
        .arg("-msandbox/force:on")
        .arg("-msandbox/mem:on")
        .arg("-mdefault/force:warn")
        .arg("-mtrace/allow_unsafe_create:1")
        .arg("--")
        .arg(&out_bin)
        .status()
        .expect("run libsyd_test");
    assert!(status.success(), "libsyd_test status:{status:?}");
}

#[test]
fn test_libsyd_go() {
    let curdir = std::fs::canonicalize(std::env::current_dir().expect("current directory"))
        .expect("canonicalize");
    let curdir = curdir.display().to_string();
    eprintln!("Current directory: {curdir}");
    let libdir = std::env::var("LD_LIBRARY_PATH").unwrap_or_default();
    let libdir = libdir.split(':').next().unwrap_or("");
    eprintln!("LibSyd directory: {libdir}");

    let status = Command::new("syd")
        .current_dir("./src")
        .env("CGO_CFLAGS", format!("-I{curdir}"))
        .env("GOCACHE", curdir)
        .arg("-poff")
        .arg("-mlock:off") // Go uses green threads, lock:exec doesn't work.
        .arg("-pnopie") // Allow non-PIE.
        .arg("-eLD_LIBRARY_PATH=") // pass-through
        .arg("-msandbox/force:on")
        .arg("-msandbox/mem:on")
        .arg("-mdefault/force:warn")
        .arg("-mtrace/allow_unsafe_exec_libc:1") // or else AT_SECURE prevents pass-through
        .arg("--")
        .args(["go", "test", "-ldflags", &format!("-extldflags '-L{libdir}'"), "-p", "1"])
        .status()
        .expect("execute go test");
    assert!(status.success(), "status:{status:?}");
}

#[test]
fn test_libsyd_pl() {
    let curdir = std::fs::canonicalize(std::env::current_dir().expect("current directory"))
        .expect("canonicalize");
    let curdir = curdir.display().to_string();
    eprintln!("Current directory: {curdir}");
    let libdir = std::env::var("LD_LIBRARY_PATH").unwrap_or_default();
    let libdir = libdir.split(':').next().unwrap_or("");
    eprintln!("LibSyd directory: {libdir}");

    let status = Command::new("syd")
        .arg("-plib")
        .arg("-pnopie") // Allow non-PIE.
        .arg("-eLD_LIBRARY_PATH=") // pass-through
        .arg("-msandbox/force:on")
        .arg("-msandbox/mem:on")
        .arg("-mdefault/force:warn")
        .arg("-mtrace/allow_unsafe_exec_libc:1") // or else AT_SECURE prevents pass-through
        .arg("--")
        .arg("./src/syd.pl")
        .status()
        .expect("execute syd.pl");
    assert!(status.success(), "status:{status:?}");
}

#[test]
fn test_libsyd_py() {
    let status = Command::new("syd")
        .arg("-plib")
        .arg("-pnomem") // Allow W^X memory.
        .arg("-pnopie") // Allow non-PIE.
        .arg("-eLD_LIBRARY_PATH=") // pass-through
        .arg("-msandbox/force:on")
        .arg("-msandbox/mem:on")
        .arg("-mdefault/force:warn")
        .arg("-mtrace/allow_unsafe_exec_libc:1") // or else AT_SECURE prevents pass-through
        .arg("--")
        .arg("./src/syd.py")
        .status()
        .expect("execute syd.py");
    assert!(status.success(), "status:{status:?}");
}

#[test]
fn test_libsyd_rb() {
    let curdir = std::fs::canonicalize(std::env::current_dir().expect("current directory"))
        .expect("canonicalize");
    let curdir = curdir.display().to_string();
    eprintln!("Current directory: {curdir}");
    let libdir = std::env::var("LD_LIBRARY_PATH").unwrap_or_default();
    let libdir = libdir.split(':').next().unwrap_or("");
    eprintln!("LibSyd directory: {libdir}");

    let status = Command::new("syd")
        .arg("-plib")
        .arg("-pnomem") // Allow W^X memory.
        .arg("-pnopie") // Allow non-PIE.
        .arg("-eLD_LIBRARY_PATH=") // pass-through
        .arg("-msandbox/force:on")
        .arg("-msandbox/mem:on")
        .arg("-mdefault/force:warn")
        .arg("-mtrace/allow_unsafe_exec_libc:1") // or else AT_SECURE prevents pass-through
        .arg("--")
        .arg("./src/syd.rb")
        .arg("-s0")
        .arg("-v")
        .status()
        .expect("execute syd.rb");
    assert!(status.success(), "status:{status:?}");
}
