xll-utils 0.1.0

PE/COFF parsing and export verification utilities for Excel XLL development
Documentation
use xll_utils::pe::parse_pe_file;
use xll_utils::types::Architecture;

fn fixture_path() -> std::path::PathBuf {
    std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests")
        .join("fixtures")
        .join("test_xll.xll")
}

#[test]
fn parse_fixture_xll() {
    let pe = parse_pe_file(fixture_path()).unwrap();
    assert!(pe.is_dll());
    assert_eq!(pe.architecture(), Architecture::X64);
}

#[test]
fn fixture_has_exports() {
    let pe = parse_pe_file(fixture_path()).unwrap();
    assert!(!pe.exports().is_empty(), "fixture should have exports");
    assert!(
        pe.exports().len() >= 4,
        "expected at least 4 exports, got {}",
        pe.exports().len()
    );
}

#[test]
fn fixture_has_xl_auto_open() {
    let pe = parse_pe_file(fixture_path()).unwrap();
    assert!(
        pe.find_export("xlAutoOpen").is_some(),
        "xlAutoOpen not found in exports: {:?}",
        pe.export_names()
    );
}

#[test]
fn fixture_has_xl_auto_close() {
    let pe = parse_pe_file(fixture_path()).unwrap();
    assert!(pe.find_export("xlAutoClose").is_some());
}

#[test]
fn fixture_has_xl_auto_free12() {
    let pe = parse_pe_file(fixture_path()).unwrap();
    assert!(pe.find_export("xlAutoFree12").is_some());
}

#[test]
fn fixture_has_xl_addin_manager_info12() {
    let pe = parse_pe_file(fixture_path()).unwrap();
    assert!(pe.find_export("xlAddInManagerInfo12").is_some());
}

#[test]
fn fixture_has_user_functions() {
    let pe = parse_pe_file(fixture_path()).unwrap();
    for name in ["xl_add", "xl_multiply", "xl_negate", "xl_version"] {
        assert!(
            pe.find_export(name).is_some(),
            "expected export '{name}' not found"
        );
    }
}

#[test]
fn fixture_export_names_sorted() {
    let pe = parse_pe_file(fixture_path()).unwrap();
    let names = pe.export_names();
    let mut sorted = names.clone();
    sorted.sort();
    assert_eq!(names, sorted, "export_names() should return sorted names");
}

#[test]
fn fixture_exports_are_not_forwarded() {
    let pe = parse_pe_file(fixture_path()).unwrap();
    for exp in pe.exports() {
        assert!(
            !exp.is_forwarded,
            "export {:?} should not be forwarded",
            exp.name
        );
        assert!(exp.forward_to.is_none());
        assert!(exp.relative_address.is_some());
    }
}

#[test]
fn fixture_export_directory_present() {
    let pe = parse_pe_file(fixture_path()).unwrap();
    let dir = pe.export_directory().expect("export directory should exist");
    assert!(dir.address_table_entries > 0);
    assert!(dir.number_of_name_pointers > 0);
    assert_eq!(dir.address_table_entries, dir.number_of_name_pointers);
}

#[test]
fn fixture_image_base_nonzero() {
    let pe = parse_pe_file(fixture_path()).unwrap();
    assert!(pe.image_base > 0, "image base should be nonzero");
}

#[test]
fn find_export_by_ordinal() {
    let pe = parse_pe_file(fixture_path()).unwrap();
    let first = &pe.exports()[0];
    let found = pe.find_export_by_ordinal(first.ordinal);
    assert!(found.is_some());
    assert_eq!(found.unwrap().name, first.name);
}

#[test]
fn find_nonexistent_export() {
    let pe = parse_pe_file(fixture_path()).unwrap();
    assert!(pe.find_export("nonExistentExport").is_none());
}

#[test]
fn parse_from_bytes() {
    let data = std::fs::read(fixture_path()).unwrap();
    let pe = xll_utils::PeFile::from_bytes(&data).unwrap();
    assert!(pe.is_dll());
    assert_eq!(pe.architecture(), Architecture::X64);
    assert!(pe.find_export("xlAutoOpen").is_some());
}

#[test]
fn parse_invalid_data() {
    let data = b"this is not a PE file";
    let err = xll_utils::PeFile::from_bytes(data).unwrap_err();
    assert!(
        matches!(err, xll_utils::Error::InvalidFileFormat),
        "expected InvalidFileFormat, got: {err}"
    );
}

#[test]
fn parse_nonexistent_file() {
    let err = parse_pe_file("nonexistent_file.dll").unwrap_err();
    assert!(
        matches!(err, xll_utils::Error::FileOpen { .. }),
        "expected FileOpen, got: {err}"
    );
}