hhh 1.0.1

The hhh Binary File Processor
Documentation
// hhh
// Copyright (c) 2023 by Stacy Prowell.  All rights reserved.
// https://gitlab.com/sprowell/hhh

//! Basic integration tests.

use tempfile::TempDir;
use trivet::parse_from_path;

use crate::generator::write_hexdump;
use crate::generator::Generator;
use crate::options::HhhArgs;
use crate::reader::read_hexdump;
use crate::reader::Reader;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;

const TEST_FILE_DIR: &str = "test_files";

/// Perform a test, reading a series of test files and comparing the result to the correct binary
/// output.  Use the parser directly for this test.
#[test]
fn hex_to_bin_1() -> std::io::Result<()> {
    // Get the list of readable .hex files in the test file directory.
    let paths = std::fs::read_dir(TEST_FILE_DIR)?
        .filter_map(|result| result.ok())
        .map(|entry| entry.path())
        .filter_map(|path| {
            if path
                .extension()
                .map_or(false, |extension| extension == "hex")
            {
                Some(path)
            } else {
                None
            }
        })
        .collect::<Vec<_>>();

    // For each .hex file, read the file and the accompanying .bin file, and then compare the
    // two.  They must match exactly.
    for hex_path in paths {
        // Report.
        println!("Integration test: Parsing {:?}...", &hex_path);

        // Construct an argument struct.
        let mut args = HhhArgs::default();

        // Get the binary file.  If we don't have one, we can't do the test.
        let mut bin_path = hex_path.clone();
        bin_path.set_extension("bin");
        if !bin_path.exists() {
            continue;
        }

        // Parse the hex file.
        let mut file_parser = parse_from_path(&hex_path)?;
        let mut hex_parser = Reader::new(&mut args);
        let _ = hex_parser.parse(&mut file_parser);

        // Get the bytes from the parsed file.
        let hex_bytes = hex_parser.finish().get_bytes();

        // Now get the bytes from the binary file.
        let mut bin_bytes = vec![];
        let mut bin_file = File::open(bin_path)?;
        bin_file.read_to_end(&mut bin_bytes)?;

        // Now compare the two arrays.  They must match exactly.
        assert_eq!(hex_bytes, bin_bytes);

        // Report.
        println!("Integration test: Parsing {:?} PASSED", &hex_path);
    }
    Ok(())
}

/// Perform a test, reading a series of binary files and generating a hex dump, then reading the
/// hex dump back to create a second binary.  The two binaries must exactly match.  Use the
/// generator directly for this test.
#[test]
fn round_trip_1() -> std::io::Result<()> {
    let temp_dir = TempDir::new()?;

    // Get the list of readable .bin files in the test file directory.
    let paths = std::fs::read_dir(TEST_FILE_DIR)?
        .filter_map(|result| result.ok())
        .map(|entry| entry.path())
        .filter_map(|path| {
            if path
                .extension()
                .map_or(false, |extension| extension == "bin")
            {
                Some(path)
            } else {
                None
            }
        })
        .collect::<Vec<_>>();

    // For each .hex file, read the file and the accompanying .bin file, and then compare the
    // two.  They must match exactly.
    for bin_path in paths {
        // Report.
        println!("Round-trip test: Parsing {:?}...", &bin_path);

        // Construct an argument struct.
        let mut args = HhhArgs::default();

        // Get a temporary hex file.
        let mut hex_path = PathBuf::from(temp_dir.path());
        hex_path.push(bin_path.file_name().unwrap());
        hex_path.set_extension("hex");

        // Get the content of the binary file.
        let mut bin_file = File::open(&bin_path)?;
        let mut bin_bytes = vec![];
        bin_file.read_to_end(&mut bin_bytes)?;

        // Generate a hex dump of the binary file.
        let hex_file = File::create(&hex_path)?;
        let mut generator = Generator::new(hex_file);
        generator.config(&args);
        generator.add(&bin_bytes);
        generator.finish();

        // Now read back the hexdump.
        let mut file_parser = parse_from_path(&hex_path)?;
        let mut hex_parser = Reader::new(&mut args);
        let _ = hex_parser.parse(&mut file_parser);

        // Get the bytes from the parsed file.
        let hex_bytes = hex_parser.finish().get_bytes();

        // Now compare the two arrays.  They must match exactly.
        assert_eq!(hex_bytes, bin_bytes);

        // Report.
        println!("Integration test: Parsing {:?} PASSED", &hex_path);
    }
    Ok(())
}

/// Perform a series of tests, generating and writing a binary file from a hex description, then
/// comparing the resulting binary file to the "correct" binary output.  The two must match exactly.
/// Use the `read_hexdump` method for this.
#[test]
fn hex_to_bin_2() -> std::io::Result<()> {
    let temp_dir = TempDir::new()?;

    // Get the list of readable .hex files in the test file directory.
    let paths = std::fs::read_dir(TEST_FILE_DIR)?
        .filter_map(|result| result.ok())
        .map(|entry| entry.path())
        .filter_map(|path| {
            if path
                .extension()
                .map_or(false, |extension| extension == "hex")
            {
                Some(path)
            } else {
                None
            }
        })
        .collect::<Vec<_>>();

    // For each .hex file, read the file and the accompanying .bin file, and then compare the
    // two.  They must match exactly.
    for hex_path in paths {
        // Report.
        println!(
            "Integration test: Parsing {:?}...",
            &hex_path.canonicalize().unwrap()
        );

        // Get the "true" binary file.  If we don't have one, we can't do the test.
        let mut oracle_path = hex_path.clone();
        oracle_path.set_extension("bin");
        if !oracle_path.exists() {
            continue;
        }

        // Construct an argument struct.
        let mut args = HhhArgs::default();

        // Add the hex file to the argument list to read.
        args.files.push(hex_path.clone());

        // Get a path to a binary file in the test folder.
        let mut bin_path = PathBuf::from(temp_dir.path());
        bin_path.push(hex_path.file_name().unwrap());
        bin_path.set_extension("bin");

        // Open the output file and read the hex description.
        let bin_file = File::create(&bin_path)?;
        read_hexdump(args.clone(), bin_file).unwrap();

        // Read the bytes from the bin file and from the oracle.
        let mut bin_file = File::open(&bin_path)?;
        let mut oracle_file = File::open(&oracle_path)?;
        let mut bin_bytes = vec![];
        let mut oracle_bytes = vec![];
        bin_file.read_to_end(&mut bin_bytes)?;
        oracle_file.read_to_end(&mut oracle_bytes)?;

        // Now compare the two arrays.  They must match exactly.
        assert_eq!(oracle_bytes, bin_bytes);

        // Report.
        println!("Integration test: Parsing {:?} PASSED", &hex_path);
    }
    Ok(())
}

/// Perform a series of tests, generating a hex dump file for each binary, and then reading the hex dump
/// to produce a second binary.  The binaries must match exactly.  Use the `write_hexdump` and the
/// `read_hexdump` methods are both used for this test.
#[test]
fn round_trip_2() -> std::io::Result<()> {
    let temp_dir = TempDir::new()?;

    // Get the list of readable .bin files in the test file directory.
    let paths = std::fs::read_dir(TEST_FILE_DIR)?
        .filter_map(|result| result.ok())
        .map(|entry| entry.path())
        .filter_map(|path| {
            if path
                .extension()
                .map_or(false, |extension| extension == "bin")
            {
                Some(path)
            } else {
                None
            }
        })
        .collect::<Vec<_>>();

    // Generate a hexdump for each binary file, then parse the hexdump to create a second binary and
    // compare the two.  They must match exactly.
    for oracle_path in paths {
        // Report.
        println!("Round-trip test: Parsing {:?}...", &oracle_path);

        // Construct an argument struct.
        let mut args = HhhArgs::default();

        // Add the binary file argument.
        args.files.push(oracle_path.clone());

        // Get a temporary hex file.
        let mut hex_path = PathBuf::from(temp_dir.path());
        hex_path.push(oracle_path.file_name().unwrap());
        hex_path.set_extension("hex");

        // Generate the hex dump.
        let map = BTreeMap::new();
        let hex_file = File::create(&hex_path)?;
        write_hexdump(args, map, hex_file).unwrap();

        // Construct a new argument struct.
        let mut args = HhhArgs::default();

        // Add the hex file argument.
        args.files.push(hex_path);

        // Get a temporary bin file.
        let mut bin_path = PathBuf::from(temp_dir.path());
        bin_path.push(oracle_path.file_name().unwrap());
        bin_path.set_extension("bin");

        // Generate the new binary file.
        let bin_file = File::create(&bin_path)?;
        read_hexdump(args, bin_file).unwrap();

        // Now we have the oracle file and a new binary file.  Compare the two.
        let mut oracle_file = File::open(&oracle_path)?;
        let mut bin_file = File::open(&bin_path)?;
        let mut oracle_bytes = vec![];
        let mut bin_bytes = vec![];
        oracle_file.read_to_end(&mut oracle_bytes)?;
        bin_file.read_to_end(&mut bin_bytes)?;

        // Make sure the binary files match exactly.
        assert_eq!(oracle_bytes, bin_bytes);

        // Report.
        println!("Integration test: Parsing {:?} PASSED", &oracle_path);
    }
    Ok(())
}