dcr 0.8.4

DCR is a utility for managing C/C++ projects in a Cargo-like style.
mod common;
use common::*;
use std::fs;
use std::path::Path;

#[test]
fn gen_project_info_has_artifact_path() {
    let dir = unique_sandbox_dir("gen_pi");
    let out = run_dcr(&["new", "app", "--vcs", "none"], &dir);
    assert!(out.status.success());
    let project = dir.join("app");

    let out = run_dcr(&["gen", "project-info", "-q"], &project);
    assert!(
        out.status.success(),
        "project-info failed: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(
        stdout.contains("\"artifact_path\""),
        "missing artifact_path: {stdout}"
    );
    assert!(stdout.contains("\"target_dir\""), "missing target_dir");
    assert!(
        stdout.contains("\"artifact_kind\""),
        "missing artifact_kind"
    );
    assert!(stdout.contains("\"source_roots\""), "missing source_roots");

    let root_canon = fs::canonicalize(&project).unwrap();
    assert!(
        stdout.contains(&root_canon.to_string_lossy().to_string()) || stdout.contains("app"),
        "root should be absolute-ish: {stdout}"
    );

    assert!(project.join(".dcr").join("build-info.json").is_file());
    assert!(project.join(".dcr").join("toolchain.json").is_file());
    assert!(project.join(".clangd").is_file());
    let clangd = fs::read_to_string(project.join(".clangd")).unwrap();
    assert!(clangd.contains("CompilationDatabase: .dcr"));
}

#[test]
fn gen_compile_commands_in_dcr_dir() {
    let dir = unique_sandbox_dir("gen_cc");
    let out = run_dcr(&["new", "app", "--vcs", "none"], &dir);
    assert!(out.status.success());
    let project = dir.join("app");

    let out = run_dcr(&["gen", "compile-commands", "-q"], &project);
    assert!(
        out.status.success(),
        "compile-commands failed: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    assert!(
        !project.join("compile_commands.json").exists(),
        "compile_commands.json must not be in project root"
    );
    let cc = project.join(".dcr").join("compile_commands.json");
    assert!(cc.is_file(), "expected .dcr/compile_commands.json");
    let content = fs::read_to_string(&cc).unwrap();
    assert!(content.contains("\"directory\""), "missing directory field");
    assert!(content.contains("\"file\""), "missing file field");
    assert!(content.starts_with('['));

    assert!(project.join(".clangd").is_file());
    assert!(project.join(".dcr").join("ide").is_dir());
}

#[test]
fn gen_clangd_not_overwritten_if_custom() {
    let dir = unique_sandbox_dir("gen_clangd");
    let out = run_dcr(&["new", "app", "--vcs", "none"], &dir);
    assert!(out.status.success());
    let project = dir.join("app");

    let custom = "CompileFlags:\n  Add: [-Wall]\n";
    fs::write(project.join(".clangd"), custom).unwrap();

    let out = run_dcr(&["gen", "compile-commands", "-q"], &project);
    assert!(out.status.success());
    let after = fs::read_to_string(project.join(".clangd")).unwrap();
    assert_eq!(after, custom, "custom .clangd must not be overwritten");
}

#[test]
fn gen_vscode_launch_uses_resolver() {
    let dir = unique_sandbox_dir("gen_vscode");
    let out = run_dcr(&["new", "app", "--vcs", "none"], &dir);
    assert!(out.status.success());
    let project = dir.join("app");

    let out = run_dcr(&["gen", "vscode"], &project);
    assert!(
        out.status.success(),
        "gen vscode failed: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    let launch = fs::read_to_string(project.join(".vscode").join("launch.json")).unwrap();
    assert!(launch.contains("\"program\""), "launch missing program");
    let normalized = launch.replace("\\\\", "/").replace('\\', "/");
    let expected_rel = host_profile_dir(&project, "debug")
        .join(bin_name("app"))
        .strip_prefix(&project)
        .unwrap_or_else(|_| Path::new("target"))
        .to_string_lossy()
        .replace('\\', "/");
    assert!(
        normalized.contains(&expected_rel),
        "program should use host profile layout ({expected_rel}): {launch}"
    );
    let settings = fs::read_to_string(project.join(".vscode").join("settings.json")).unwrap();
    assert!(
        settings.contains(".dcr") || settings.contains("compile-commands-dir"),
        "settings should point at .dcr: {settings}"
    );
}

#[test]
fn build_print_artifact_path() {
    if available_compiler().is_none() {
        return;
    }
    let dir = unique_sandbox_dir("build_print");
    let out = run_dcr(&["new", "app", "--vcs", "none"], &dir);
    assert!(out.status.success());
    let project = dir.join("app");

    let out = run_dcr(&["build", "--print-artifact-path"], &project);
    assert!(
        out.status.success(),
        "build failed: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stdout = String::from_utf8_lossy(&out.stdout);
    let path_line = stdout
        .lines()
        .map(str::trim)
        .rfind(|l| !l.is_empty())
        .unwrap_or("");
    assert!(
        Path::new(path_line).is_absolute() || path_line.contains("app"),
        "expected artifact path on stdout, got: {stdout}"
    );
    assert!(
        project.join(".dcr").join("build-info.json").is_file(),
        "build-info.json should be written after build"
    );
}