hypothalamus 0.6.0

An optimizing Brainfuck AOT compiler with an LLVM IR backend
Documentation
use assert_cmd::Command;
use predicates::prelude::*;
use predicates::str::contains;
use std::fs;

#[test]
fn help_is_generic_about_targets() {
    Command::cargo_bin("hypothalamus")
        .expect("binary should build")
        .arg("--help")
        .assert()
        .success()
        .stdout(contains("Hypothalamus - Brainfuck AOT compiler"))
        .stdout(contains("--target <TARGET>"))
        .stdout(contains("--list-targets"))
        .stdout(contains("tools doctor"))
        .stdout(contains("built-in runner"))
        .stdout(contains("--gba-gcc").not());
}

#[test]
fn version_prints_package_version() {
    Command::cargo_bin("hypothalamus")
        .expect("binary should build")
        .arg("--version")
        .assert()
        .success()
        .stdout(contains(format!(
            "hypothalamus {}",
            env!("CARGO_PKG_VERSION")
        )));
}

#[test]
fn list_targets_includes_current_presets() {
    Command::cargo_bin("hypothalamus")
        .expect("binary should build")
        .arg("--list-targets")
        .assert()
        .success()
        .stdout(contains("native"))
        .stdout(contains("x86_64-none"))
        .stdout(contains("i386-none"))
        .stdout(contains("nds-arm9"))
        .stdout(contains("gba"));
}

#[test]
fn tools_doctor_reports_local_capabilities() {
    Command::cargo_bin("hypothalamus")
        .expect("binary should build")
        .args([
            "tools",
            "doctor",
            "--cc",
            "/definitely/missing/clang",
            "--lli",
            "/definitely/missing/lli",
        ])
        .assert()
        .success()
        .stdout(contains("Hypothalamus tool doctor"))
        .stdout(contains("Execution:"))
        .stdout(contains("Tools:"))
        .stdout(contains("Targets:"))
        .stdout(contains("run: ok"))
        .stdout(contains("clang: missing"))
        .stdout(contains("lli: missing"))
        .stdout(contains("nds-arm9"))
        .stdout(contains("gba"));
}

#[test]
fn emits_llvm_ir_to_stdout() {
    Command::cargo_bin("hypothalamus")
        .expect("binary should build")
        .args(["--emit", "llvm-ir", "-o", "-", "examples/hello.bf"])
        .assert()
        .success()
        .stdout(contains("; Generated by hypothalamus."))
        .stdout(contains("define i32 @main()"))
        .stdout(contains("@putchar"));
}

#[test]
fn writes_llvm_ir_to_output_file() {
    let temp_dir = tempfile::tempdir().expect("create temp dir");
    let output = temp_dir.path().join("hello.ll");

    Command::cargo_bin("hypothalamus")
        .expect("binary should build")
        .args(["--emit", "llvm-ir", "examples/hello.bf", "-o"])
        .arg(&output)
        .assert()
        .success();

    let ir = fs::read_to_string(output).expect("read generated LLVM IR");
    assert!(ir.contains("; Generated by hypothalamus."));
    assert!(ir.contains("define i32 @main()"));
}

#[test]
fn rejects_stdout_output_for_non_llvm_ir_modes() {
    for args in [
        ["--emit", "obj", "-o", "-", "examples/hello.bf"],
        ["--emit", "asm", "-o", "-", "examples/hello.bf"],
        ["--target", "gba", "-o", "-", "examples/hello.bf"],
    ] {
        Command::cargo_bin("hypothalamus")
            .expect("binary should build")
            .args(args)
            .assert()
            .code(1)
            .stderr(contains("--emit llvm-ir"));
    }
}

#[test]
fn run_uses_builtin_runner_without_lli() {
    Command::cargo_bin("hypothalamus")
        .expect("binary should build")
        .args([
            "--run",
            "--lli",
            "/definitely/missing/lli",
            "examples/hello.bf",
        ])
        .assert()
        .success()
        .stdout(contains("Hello World!"));
}

#[test]
fn llvm_jit_uses_lli() {
    Command::cargo_bin("hypothalamus")
        .expect("binary should build")
        .args([
            "--emit",
            "llvm-jit",
            "--lli",
            "/definitely/missing/lli",
            "examples/hello.bf",
        ])
        .assert()
        .failure()
        .stderr(contains("failed to run"))
        .stderr(contains("/definitely/missing/lli"));
}

#[test]
fn run_reads_stdin() {
    let temp_dir = tempfile::tempdir().expect("create temp dir");
    let source = temp_dir.path().join("input.bf");
    fs::write(&source, ",+.").expect("write input program");

    Command::cargo_bin("hypothalamus")
        .expect("binary should build")
        .args(["--run"])
        .arg(source)
        .write_stdin("A")
        .assert()
        .success()
        .stdout("B");
}

#[test]
fn run_reports_pointer_bounds_errors() {
    let temp_dir = tempfile::tempdir().expect("create temp dir");
    let source = temp_dir.path().join("underflow.bf");
    fs::write(&source, "<").expect("write underflow program");

    Command::cargo_bin("hypothalamus")
        .expect("binary should build")
        .args(["--run"])
        .arg(source)
        .assert()
        .failure()
        .stderr(contains("runtime error"))
        .stderr(contains("out of bounds"));
}

#[test]
fn emits_custom_freestanding_symbols_to_stdout() {
    Command::cargo_bin("hypothalamus")
        .expect("binary should build")
        .args([
            "--freestanding",
            "--entry",
            "kernel_bf_main",
            "--putchar-symbol",
            "serial_write_byte",
            "--getchar-symbol",
            "serial_read_byte",
            "--emit",
            "llvm-ir",
            "-o",
            "-",
            "examples/hello.bf",
        ])
        .assert()
        .success()
        .stdout(contains("define void @kernel_bf_main()"))
        .stdout(contains("declare void @serial_write_byte(i8)"))
        .stdout(contains("declare i32 @serial_read_byte()"))
        .stdout(contains("define i32 @main()").not());
}

#[test]
fn nds_arm9_target_emits_freestanding_llvm_ir() {
    Command::cargo_bin("hypothalamus")
        .expect("binary should build")
        .args([
            "--target",
            "nds-arm9",
            "--emit",
            "llvm-ir",
            "-o",
            "-",
            "examples/hello.bf",
        ])
        .assert()
        .success()
        .stdout(contains("target triple = \"armv5te-none-eabi\""))
        .stdout(contains("define void @bf_main()"))
        .stdout(contains("declare void @bf_putchar(i8)"))
        .stdout(contains("define i32 @main()").not());
}