jbx 0.6.3

jbx: one-stop Java toolbox for scripts, tools, and agents
Documentation
use std::fs;
use std::process::{Command, Output};

fn juv_command() -> Command {
    Command::new(env!("CARGO_BIN_EXE_jbx"))
}

fn assert_success(out: &Output) {
    assert!(
        out.status.success(),
        "stdout={} stderr={}",
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr)
    );
}

fn fixture_script() -> (tempfile::TempDir, std::path::PathBuf) {
    let tmp = tempfile::tempdir().unwrap();
    let docs = tmp.path().join("README.md");
    fs::write(&docs, "# Docs\n").unwrap();
    fs::write(tmp.path().join("Helper.java"), "class Helper {}\n").unwrap();
    fs::write(tmp.path().join("Extra.java"), "class Extra {}\n").unwrap();
    fs::write(tmp.path().join("app.properties"), "answer=42\n").unwrap();
    let src = tmp.path().join("MetaMain.java");
    fs::write(
        &src,
        r#"
//DESCRIPTION First line
//DESCRIPTION Second line
//GAV dev.telegraphic:juv-demo:1.2.3
//MODULE dev.telegraphic.demo
//DOCS README.md
//DOCS api=https://example.com/api
//MANIFEST Main-Class=MetaMain
//JAVAAGENT org.example:agent:1.0=debug=true
//NATIVE_OPTIONS --no-fallback "--initialize-at-build-time=com.acme.Foo"
//CDS
//NOINTEGRATIONS
//JAVA 25
//DEPS Helper.java
//REPOS central=https://repo1.maven.org/maven2
//SOURCES Extra.java
//FILES config/app.properties=app.properties
//JAVAC_OPTIONS -Xlint:unchecked
//RUNTIME_OPTIONS -Ddemo=true
class MetaMain {
  public static void main(String[] args) {}
}
"#,
    )
    .unwrap();
    (tmp, src)
}

#[test]
fn directives_parse_broad_jbang_metadata() {
    let (_tmp, src) = fixture_script();

    let out = juv_command()
        .arg("info")
        .arg("directives")
        .arg(&src)
        .output()
        .unwrap();

    assert_success(&out);
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(
        stdout.contains("gav: Some(\n        \"dev.telegraphic:juv-demo:1.2.3\""),
        "{stdout}"
    );
    assert!(
        stdout.contains("module: Some(\n        \"dev.telegraphic.demo\""),
        "{stdout}"
    );
    assert!(stdout.contains("enable_cds: true"), "{stdout}");
    assert!(stdout.contains("disable_integrations: true"), "{stdout}");
    assert!(stdout.contains("native_options"), "{stdout}");
    assert!(
        stdout.contains("--initialize-at-build-time=com.acme.Foo"),
        "{stdout}"
    );
    assert!(stdout.contains("manifest_options"), "{stdout}");
    assert!(stdout.contains("java_agents"), "{stdout}");
    assert!(stdout.contains("docs"), "{stdout}");
}

#[test]
fn info_tools_prints_json_and_selects_fields() {
    let (_tmp, src) = fixture_script();
    let cache = tempfile::tempdir().unwrap();

    let out = juv_command()
        .arg("info")
        .arg("tools")
        .arg("--cache-dir")
        .arg(cache.path())
        .arg(&src)
        .output()
        .unwrap();

    assert_success(&out);
    let json: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
    assert_eq!(json["originalResource"], src.to_string_lossy().as_ref());
    assert_eq!(json["mainClass"], "MetaMain");
    assert_eq!(json["gav"], "dev.telegraphic:juv-demo:1.2.3");
    assert_eq!(json["module"], "dev.telegraphic.demo");
    assert_eq!(json["description"], "First line\nSecond line");
    assert_eq!(json["javaVersion"], "25");
    assert_eq!(json["requestedJavaVersion"], "25");
    assert_eq!(json["dependencies"][0], "Helper.java");
    assert_eq!(json["repositories"][0]["id"], "central");
    assert_eq!(
        json["repositories"][0]["url"],
        "https://repo1.maven.org/maven2"
    );
    assert_eq!(json["compileOptions"][0], "-Xlint:unchecked");
    assert_eq!(json["runtimeOptions"][0], "-Ddemo=true");
    assert!(json["applicationClassesDir"]
        .as_str()
        .unwrap()
        .ends_with("/classes"));
    assert_eq!(json["docs"]["main"][0]["originalResource"], "README.md");
    assert_eq!(
        json["docs"]["api"][0]["originalResource"],
        "https://example.com/api"
    );

    let selected = juv_command()
        .arg("info")
        .arg("tools")
        .arg("--select")
        .arg("mainClass")
        .arg("--cache-dir")
        .arg(cache.path())
        .arg(&src)
        .output()
        .unwrap();
    assert_success(&selected);
    assert_eq!(String::from_utf8_lossy(&selected.stdout).trim(), "MetaMain");
}

#[test]
fn info_docs_prints_description_and_doc_targets() {
    let (_tmp, src) = fixture_script();

    let out = juv_command()
        .arg("info")
        .arg("docs")
        .arg(&src)
        .output()
        .unwrap();

    assert_success(&out);
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(stdout.contains("First line\nSecond line"), "{stdout}");
    assert!(stdout.contains("main:"), "{stdout}");
    assert!(stdout.contains("README.md"), "{stdout}");
    assert!(stdout.contains("api:"), "{stdout}");
    assert!(stdout.contains("https://example.com/api"), "{stdout}");
}

#[test]
fn info_cache_prints_effective_cache_dir() {
    let cache = tempfile::tempdir().unwrap();

    let out = juv_command()
        .arg("info")
        .arg("cache")
        .arg("--cache-dir")
        .arg(cache.path())
        .output()
        .unwrap();

    assert_success(&out);
    assert_eq!(
        String::from_utf8_lossy(&out.stdout).trim(),
        cache.path().to_string_lossy()
    );
}