mod common;
mod common_lines;
use common::{assert_success, run_hexy, temp_dir, write_file};
use common_lines::read_nonempty_lines;
fn parse_hex_pairs(line: &str) -> Vec<u8> {
let mut out = Vec::new();
let bytes = line.as_bytes();
let mut i = 0;
while i + 1 < bytes.len() {
let pair = &line[i..i + 2];
if let Ok(b) = u8::from_str_radix(pair, 16) {
out.push(b);
i += 2;
} else {
break;
}
}
out
}
#[test]
fn test_cli_hex_ascii_basic() {
let dir = temp_dir("cli_xa_basic");
let input = dir.join("input.bin");
let out = dir.join("out.txt");
let data: Vec<u8> = (0u8..32).collect();
write_file(&input, &data);
let args = vec![
format!("/IN:{};0x0", input.display()),
"/XA:16".to_string(),
"-o".to_string(),
out.display().to_string(),
];
let output = run_hexy(&args);
assert_success(&output);
let lines = read_nonempty_lines(&out);
assert_eq!(lines.len(), 2);
for (idx, line) in lines.iter().enumerate() {
assert_eq!(line.len(), 32);
let parsed = parse_hex_pairs(line);
assert_eq!(parsed.len(), 16);
let expected = &data[idx * 16..(idx + 1) * 16];
assert_eq!(parsed, expected);
}
}
#[test]
fn test_cli_hex_ascii_separator() {
let dir = temp_dir("cli_xa_sep");
let input = dir.join("input.bin");
let out = dir.join("out.txt");
let data: Vec<u8> = (0u8..16).collect();
write_file(&input, &data);
let args = vec![
format!("/IN:{};0x0", input.display()),
"/XA:16:, ".to_string(),
"-o".to_string(),
out.display().to_string(),
];
let output = run_hexy(&args);
assert_success(&output);
let lines = read_nonempty_lines(&out);
assert_eq!(lines.len(), 1);
let parts: Vec<&str> = lines[0].split(", ").collect();
assert_eq!(parts.len(), 16);
assert!(!lines[0].ends_with(", "));
let parsed: Vec<u8> = parts
.iter()
.map(|p| u8::from_str_radix(p, 16).unwrap())
.collect();
assert_eq!(parsed, data);
}
#[test]
fn test_cli_hex_ascii_long_line() {
let dir = temp_dir("cli_xa_long");
let input = dir.join("input.bin");
let out = dir.join("out.txt");
let data: Vec<u8> = (0u8..32).collect();
write_file(&input, &data);
let args = vec![
format!("/IN:{};0x0", input.display()),
"/XA:0xffffffff".to_string(),
"-o".to_string(),
out.display().to_string(),
];
let output = run_hexy(&args);
assert_success(&output);
let lines = read_nonempty_lines(&out);
assert_eq!(lines.len(), 1);
}
#[test]
fn test_cli_intel_hex_reclen() {
let dir = temp_dir("cli_xi_reclen");
let input = dir.join("input.bin");
let out = dir.join("out.hex");
let data: Vec<u8> = (0u8..32).collect();
write_file(&input, &data);
let args = vec![
format!("/IN:{};0x0", input.display()),
"/XI:16".to_string(),
"-o".to_string(),
out.display().to_string(),
];
let output = run_hexy(&args);
assert_success(&output);
let lines = read_nonempty_lines(&out);
for line in lines {
if line.starts_with(':') && line.len() >= 11 {
let count = u8::from_str_radix(&line[1..3], 16).unwrap();
let rectype = &line[7..9];
if rectype == "00" {
assert_eq!(count, 0x10);
}
}
}
}
#[test]
fn test_cli_intel_hex_reclen_zero_defaults() {
let dir = temp_dir("cli_xi_reclen_zero");
let input = dir.join("input.bin");
let out = dir.join("out.hex");
let data: Vec<u8> = (0u8..20).collect();
write_file(&input, &data);
let args = vec![
format!("/IN:{};0x0", input.display()),
"/XI:0".to_string(),
"-o".to_string(),
out.display().to_string(),
];
let output = run_hexy(&args);
assert_success(&output);
let lines = read_nonempty_lines(&out);
for line in lines {
if line.starts_with(':') && line.len() >= 11 {
let count = u8::from_str_radix(&line[1..3], 16).unwrap();
let rectype = &line[7..9];
if rectype == "00" {
assert_eq!(count, 0x10);
break;
}
}
}
}
#[test]
fn test_cli_intel_hex_forced_modes() {
let dir = temp_dir("cli_xi_modes");
let input = dir.join("input.bin");
let out_linear = dir.join("out_linear.hex");
let out_segment = dir.join("out_segment.hex");
write_file(&input, &[0xAA, 0xBB]);
let args_linear = vec![
format!("/IN:{};0x12000", input.display()),
"/XI:16:1".to_string(),
"-o".to_string(),
out_linear.display().to_string(),
];
let output = run_hexy(&args_linear);
assert_success(&output);
let text = std::fs::read_to_string(&out_linear).unwrap();
assert!(text.contains(":02000004"));
let args_segment = vec![
format!("/IN:{};0x12000", input.display()),
"/XI:16:2".to_string(),
"-o".to_string(),
out_segment.display().to_string(),
];
let output = run_hexy(&args_segment);
assert_success(&output);
let text = std::fs::read_to_string(&out_segment).unwrap();
assert!(text.contains(":02000002"));
}
#[test]
fn test_cli_intel_hex_auto_modes() {
let dir = temp_dir("cli_xi_auto");
let input_seg = dir.join("input_seg.bin");
let input_lin = dir.join("input_lin.bin");
let out_seg = dir.join("out_seg.hex");
let out_lin = dir.join("out_lin.hex");
write_file(&input_seg, &[0xAA, 0xBB]);
write_file(&input_lin, &[0xCC, 0xDD]);
let args_seg = vec![
format!("/IN:{};0x12000", input_seg.display()),
"/XI:16".to_string(),
"-o".to_string(),
out_seg.display().to_string(),
];
let output = run_hexy(&args_seg);
assert_success(&output);
let text = std::fs::read_to_string(&out_seg).unwrap();
assert!(text.contains(":02000002"));
let args_lin = vec![
format!("/IN:{};0x120000", input_lin.display()),
"/XI:16".to_string(),
"-o".to_string(),
out_lin.display().to_string(),
];
let output = run_hexy(&args_lin);
assert_success(&output);
let text = std::fs::read_to_string(&out_lin).unwrap();
assert!(text.contains(":02000004"));
}
#[test]
fn test_cli_srec_auto_and_forced() {
let dir = temp_dir("cli_xs");
let input_small = dir.join("input_small.bin");
let input_mid = dir.join("input_mid.bin");
let input_large = dir.join("input_large.bin");
let out_small = dir.join("out_small.s19");
let out_mid = dir.join("out_mid.s19");
let out_large = dir.join("out_large.s19");
write_file(&input_small, &[0x01, 0x02]);
write_file(&input_mid, &[0x03, 0x04]);
write_file(&input_large, &[0x05, 0x06]);
let args_small = vec![
format!("/IN:{};0x0100", input_small.display()),
"/XS:16".to_string(),
"-o".to_string(),
out_small.display().to_string(),
];
let output = run_hexy(&args_small);
assert_success(&output);
let line = read_nonempty_lines(&out_small)[0].clone();
assert!(line.starts_with("S1"));
let args_mid = vec![
format!("/IN:{};0x10000", input_mid.display()),
"/XS:16".to_string(),
"-o".to_string(),
out_mid.display().to_string(),
];
let output = run_hexy(&args_mid);
assert_success(&output);
let line = read_nonempty_lines(&out_mid)[0].clone();
assert!(line.starts_with("S2"));
let args_large = vec![
format!("/IN:{};0x1000000", input_large.display()),
"/XS:16".to_string(),
"-o".to_string(),
out_large.display().to_string(),
];
let output = run_hexy(&args_large);
assert_success(&output);
let line = read_nonempty_lines(&out_large)[0].clone();
assert!(line.starts_with("S3"));
let bad = vec![
format!("/IN:{};0x10000", input_mid.display()),
"/XS:16:0".to_string(),
"-o".to_string(),
out_mid.display().to_string(),
];
let output = run_hexy(&bad);
assert!(!output.status.success());
}
#[test]
fn test_cli_srec_reclen() {
let dir = temp_dir("cli_xs_reclen");
let input = dir.join("input.bin");
let out = dir.join("out.s19");
let data: Vec<u8> = (0u8..16).collect();
write_file(&input, &data);
let args = vec![
format!("/IN:{};0x0100", input.display()),
"/XS:8".to_string(),
"-o".to_string(),
out.display().to_string(),
];
let output = run_hexy(&args);
assert_success(&output);
let lines = read_nonempty_lines(&out);
for line in lines {
if !line.starts_with('S') || line.len() < 4 {
continue;
}
let record_type = line.chars().nth(1).unwrap_or('0');
if record_type == '1' || record_type == '2' || record_type == '3' {
let count = u8::from_str_radix(&line[2..4], 16).unwrap() as usize;
let addr_len = match record_type {
'1' => 4,
'2' => 6,
_ => 8,
};
let data_len = count - (addr_len / 2) - 1;
assert_eq!(data_len, 8);
}
}
}
#[test]
fn test_cli_srec_rectype_requires_reclen() {
let dir = temp_dir("cli_xs_rectype_reclen");
let input = dir.join("input.bin");
let out = dir.join("out.s19");
write_file(&input, &[0xAA, 0xBB]);
let args = vec![
format!("/IN:{};0x0100", input.display()),
"/XS::2".to_string(),
"-o".to_string(),
out.display().to_string(),
];
let output = run_hexy(&args);
assert!(!output.status.success());
}
#[test]
fn test_cli_intel_hex_rectype_requires_reclen() {
let dir = temp_dir("cli_xi_rectype_reclen");
let input = dir.join("input.bin");
let out = dir.join("out.hex");
write_file(&input, &[0xAA, 0xBB]);
let args = vec![
format!("/IN:{};0x0100", input.display()),
"/XI::2".to_string(),
"-o".to_string(),
out.display().to_string(),
];
let output = run_hexy(&args);
assert!(!output.status.success());
}
#[test]
fn test_cli_srec_reclen_zero_defaults() {
let dir = temp_dir("cli_xs_reclen_zero");
let input = dir.join("input.bin");
let out = dir.join("out.s19");
let data: Vec<u8> = (0u8..20).collect();
write_file(&input, &data);
let args = vec![
format!("/IN:{};0x0100", input.display()),
"/XS:0".to_string(),
"-o".to_string(),
out.display().to_string(),
];
let output = run_hexy(&args);
assert_success(&output);
let lines = read_nonempty_lines(&out);
let data_lines: Vec<&str> = lines
.iter()
.filter(|l| l.starts_with("S1") || l.starts_with("S2") || l.starts_with("S3"))
.map(|s| s.as_str())
.collect();
assert!(!data_lines.is_empty());
let line = data_lines[0];
let count = u8::from_str_radix(&line[2..4], 16).unwrap() as usize;
let data_len = count - 2 - 1; assert_eq!(data_len, 16);
}
#[test]
fn test_cli_binary_and_separate_binary() {
let dir = temp_dir("cli_xn_xsb");
let base = dir.join("base.bin");
let merge = dir.join("merge.bin");
let out = dir.join("out.bin");
write_file(&base, &[0x01, 0x02]);
write_file(&merge, &[0x03, 0x04]);
let args = vec![
format!("/IN:{};0x1000", base.display()),
format!("/MO:{};0x2000", merge.display()),
"/XN".to_string(),
"-o".to_string(),
out.display().to_string(),
];
let output = run_hexy(&args);
assert_success(&output);
let data = std::fs::read(&out).unwrap();
assert_eq!(data, vec![0x01, 0x02, 0x03, 0x04]);
let out_sep = dir.join("sep.bin");
let args = vec![
format!("/IN:{};0x1000", base.display()),
format!("/MO:{};0x2000", merge.display()),
"/XSB".to_string(),
"-o".to_string(),
out_sep.display().to_string(),
];
let output = run_hexy(&args);
assert_success(&output);
let file1 = dir.join("sep_1000.bin");
let file2 = dir.join("sep_2000.bin");
assert_eq!(std::fs::read(file1).unwrap(), vec![0x01, 0x02]);
assert_eq!(std::fs::read(file2).unwrap(), vec![0x03, 0x04]);
let out_sep_dat = dir.join("sep.dat");
let args = vec![
format!("/IN:{};0x1000", base.display()),
format!("/MO:{};0x2000", merge.display()),
"/XSB".to_string(),
"-o".to_string(),
out_sep_dat.display().to_string(),
];
let output = run_hexy(&args);
assert_success(&output);
let file1 = dir.join("sep_1000.dat");
let file2 = dir.join("sep_2000.dat");
assert_eq!(std::fs::read(file1).unwrap(), vec![0x01, 0x02]);
assert_eq!(std::fs::read(file2).unwrap(), vec![0x03, 0x04]);
}
#[test]
fn test_cli_binary_address_order() {
let dir = temp_dir("cli_xn_order");
let base = dir.join("base.bin");
let merge = dir.join("merge.bin");
let out = dir.join("out.bin");
write_file(&base, &[0x01]);
write_file(&merge, &[0x02]);
let args = vec![
format!("/IN:{};0x2000", base.display()),
format!("/MO:{};0x1000", merge.display()),
"/XN".to_string(),
"-o".to_string(),
out.display().to_string(),
];
let output = run_hexy(&args);
assert_success(&output);
let data = std::fs::read(&out).unwrap();
assert_eq!(data, vec![0x02, 0x01]);
}
#[test]
fn test_cli_output_option_exclusive() {
let dir = temp_dir("cli_xx_excl");
let input = dir.join("input.bin");
let out = dir.join("out.hex");
write_file(&input, &[0x01, 0x02]);
let args = vec![
format!("/IN:{};0x0", input.display()),
"/XA:16".to_string(),
"/XI:16".to_string(),
"-o".to_string(),
out.display().to_string(),
];
let output = run_hexy(&args);
assert!(!output.status.success());
}
#[test]
fn test_cli_unsupported_output_rejected_without_output_file() {
let dir = temp_dir("cli_xv_unsupported");
let input = dir.join("input.bin");
write_file(&input, &[0x01, 0x02]);
let args = vec![format!("/IN:{};0x0", input.display()), "/XV".to_string()];
let output = run_hexy(&args);
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("invalid option: /XV"));
}