sim-codec-classfile 0.1.0

Bounded, lossless Java Virtual Machine classfile codec for SIM
Documentation
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};

use sim_codec_classfile::{OPCODES, Opcode};

fn crate_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}

fn run_generator(root: &Path, arguments: &[&str]) -> std::process::Output {
    Command::new("python3")
        .arg(root.join("generate_opcodes.py"))
        .args(arguments)
        .output()
        .expect("run opcode generator")
}

fn scratch_copy() -> PathBuf {
    let nonce = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("clock after epoch")
        .as_nanos();
    let destination = std::env::temp_dir().join(format!(
        "sim-codec-classfile-opcodes-{}-{nonce}",
        std::process::id()
    ));
    fs::create_dir_all(destination.join("src")).expect("create scratch source directory");
    let source = crate_root();
    for relative in [
        "generate_opcodes.py",
        "opcode-manifest.tsv",
        "OPCODES.md",
        "src/opcode_generated.rs",
    ] {
        fs::copy(source.join(relative), destination.join(relative)).expect("copy generator input");
    }
    destination
}

#[test]
fn generated_opcode_artifacts_are_current_and_unique() {
    let output = run_generator(&crate_root(), &["--check", "--scan"]);
    assert!(
        output.status.success(),
        "opcode fixpoint failed:\n{}{}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
}

#[test]
fn every_byte_has_the_matching_generated_identity() {
    assert_eq!(OPCODES.len(), 256);
    for byte in u8::MIN..=u8::MAX {
        let opcode = Opcode::from_byte(byte);
        assert_eq!(opcode as u8, byte);
        assert_eq!(opcode.metadata(), &OPCODES[usize::from(byte)]);
    }
}

#[test]
fn manifest_edits_without_regeneration_fail_the_fixpoint() {
    let scratch = scratch_copy();
    let manifest = scratch.join("opcode-manifest.tsv");
    let mut text = fs::read_to_string(&manifest).expect("read scratch manifest");
    let first_row = text
        .lines()
        .find(|line| line.starts_with("0x00\t"))
        .expect("manifest has first opcode")
        .to_owned();
    text.push_str(&first_row);
    text.push('\n');
    fs::write(manifest, text).expect("mutate scratch manifest");

    let output = run_generator(&scratch, &["--check"]);
    fs::remove_dir_all(&scratch).expect("remove scratch directory");
    assert!(
        !output.status.success(),
        "stale manifest unexpectedly passed"
    );
}

#[test]
fn a_parallel_source_inventory_is_rejected_as_a_source_fact() {
    let scratch = scratch_copy();
    let bytes = (0_u8..8)
        .map(|byte| format!("0x{byte:02x}"))
        .collect::<Vec<_>>()
        .join(", ");
    fs::write(
        scratch.join("src/parallel.rs"),
        format!("const INSTRUCTION_OPCODE_TABLE: [u8; 8] = [{bytes}];\n"),
    )
    .expect("write parallel inventory");

    let output = run_generator(&scratch, &["--check", "--scan"]);
    fs::remove_dir_all(&scratch).expect("remove scratch directory");
    assert!(
        !output.status.success(),
        "parallel opcode inventory unexpectedly passed"
    );
    assert!(
        String::from_utf8_lossy(&output.stderr).contains("parallel opcode-like inventory"),
        "failure did not identify the parallel inventory: {}",
        String::from_utf8_lossy(&output.stderr)
    );
}