use std::path::Path;
use std::process::{Command, Output};
use insta_cmd::assert_cmd_snapshot;
const KSEG0: &str = "0x80000000";
fn hereticus() -> Command {
let mut command = Command::new(insta_cmd::get_cargo_bin("hereticus"));
command.current_dir(Path::new(env!("CARGO_MANIFEST_DIR")).join(".."));
command
}
fn raw(name: &str) -> String {
let path = format!("fixtures/build/raw/{name}.bin");
assert!(
Path::new(env!("CARGO_MANIFEST_DIR")).join("..").join(&path).exists(),
"{path} is missing, run `just fixtures` first"
);
path
}
fn compare(arch: &str, before: &str, after: &str) -> Command {
let mut command = hereticus();
command.args(["--progress", "never", "--raw", "--base", KSEG0, "--arch", arch]);
command.args([raw(before), raw(after)]);
command
}
fn run(command: &mut Command) -> Output {
command.output().expect("the tool runs")
}
#[test]
fn compares_little_endian_mips_images() {
assert_cmd_snapshot!(compare("mips32", "mips-O2-basic", "mips-O2-basic_v2"));
}
#[test]
fn compares_big_endian_mips_images() {
assert_cmd_snapshot!(compare("mips32-be", "mipsbe-O2-basic", "mipsbe-O2-basic_v2"));
}
#[test]
fn compares_unoptimized_big_endian_mips_images() {
assert_cmd_snapshot!(compare("mips32-be", "mipsbe-O0-basic", "mipsbe-O0-basic_v2"));
}
#[test]
fn names_the_kind_of_edit_between_two_mips_firmware_images() {
assert_cmd_snapshot!(
compare("mips32-be", "mipsbe-O2-firmware", "mipsbe-O2-firmware_v2").arg("--show-retouched")
);
}
#[test]
fn compares_position_independent_mips_firmware_images() {
assert_cmd_snapshot!(
compare("mips32-be", "mipsbe-pic-O2-firmware", "mipsbe-pic-O2-firmware_v2")
.arg("--show-retouched")
);
}
#[test]
fn reports_every_mips_function_when_asked() {
assert_cmd_snapshot!(
compare("mips32-be", "mipsbe-O2-basic", "mipsbe-O2-basic_v2").arg("--all")
);
}
#[test]
fn sorts_the_mips_report_by_how_much_changed() {
assert_cmd_snapshot!(
compare("mips32-be", "mipsbe-O2-firmware", "mipsbe-O2-firmware_v2")
.args(["--sort", "modified"])
);
}
#[test]
fn prints_a_compact_mips_report() {
assert_cmd_snapshot!(
compare("mips32-be", "mipsbe-O2-firmware", "mipsbe-O2-firmware_v2").args([
"--show-retouched",
"--format",
"compact"
])
);
}
#[test]
fn diffs_one_raw_mips_function_at_instruction_level() {
assert_cmd_snapshot!(
compare("mips32-be", "mipsbe-O2-basic", "mipsbe-O2-basic_v2")
.args(["--diff", "sub_80000050"])
);
}
#[test]
fn diffs_one_little_endian_mips_function_at_instruction_level() {
assert_cmd_snapshot!(
compare("mips32", "mips-O2-basic", "mips-O2-basic_v2").args(["--diff", "sub_80000050"])
);
}
#[test]
fn a_mips_change_sets_the_exit_status_when_asked() {
assert_cmd_snapshot!(
compare("mips32-be", "mipsbe-O2-basic", "mipsbe-O2-basic_v2").arg("--exit-code")
);
}
#[test]
fn a_mips_image_compared_with_itself_reports_nothing() {
assert_cmd_snapshot!(
compare("mips32-be", "mipsbe-O2-firmware", "mipsbe-O2-firmware").arg("--exit-code")
);
}
#[test]
fn warns_about_a_big_endian_mips_base_that_looks_wrong() {
let mut command = hereticus();
command.args(["--progress", "never", "--raw", "--base", "0x80000400", "--arch", "mips32-be"]);
command.args([raw("vectors-mipsbe"), raw("vectors-mipsbe")]);
assert_cmd_snapshot!(command);
}
#[test]
fn warns_about_a_little_endian_mips_base_that_looks_wrong() {
let mut command = hereticus();
command.args(["--progress", "never", "--raw", "--base", "0x80000400", "--arch", "mips32"]);
command.args([raw("vectors-mips"), raw("vectors-mips")]);
assert_cmd_snapshot!(command);
}
#[test]
fn stays_quiet_about_the_right_mips_base() {
assert_cmd_snapshot!(compare("mips32-be", "vectors-mipsbe", "vectors-mipsbe"));
}
#[test]
fn reads_a_mips_image_given_the_wrong_byte_order() {
assert_cmd_snapshot!(compare("mips32", "mipsbe-O2-basic", "mipsbe-O2-basic_v2"));
}
#[test]
fn arch_without_raw_is_refused() {
assert_cmd_snapshot!(hereticus().args([
"--arch",
"mips32-be",
"fixtures/build/mipsbe-O2/basic.o",
"fixtures/build/mipsbe-O2/basic_v2.o",
]));
}
#[test]
fn an_unknown_arch_is_refused() {
assert_cmd_snapshot!(hereticus().args([
"--raw",
"--base",
KSEG0,
"--arch",
"mips64",
"fixtures/build/raw/mipsbe-O2-basic.bin",
"fixtures/build/raw/mipsbe-O2-basic_v2.bin",
]));
}
#[test]
fn both_byte_orders_print_one_report() {
for (name, flags) in [
("basic", &[][..]),
("firmware", &["--show-retouched"][..]),
("basic", &["--all"][..]),
("basic", &["--diff", "sub_800000a0"][..]),
("firmware", &["--format", "compact"][..]),
] {
let v2 = format!("{name}_v2");
let little = run(
compare("mips32", &format!("mips-O2-{name}"), &format!("mips-O2-{v2}")).args(flags)
);
let big =
run(compare("mips32-be", &format!("mipsbe-O2-{name}"), &format!("mipsbe-O2-{v2}"))
.args(flags));
assert!(little.status.success() && big.status.success(), "{name} {flags:?}");
assert_eq!(
String::from_utf8_lossy(&little.stdout),
String::from_utf8_lossy(&big.stdout),
"{name} {flags:?}: the byte order changed the report"
);
}
}
#[test]
fn a_raw_mips_report_matches_the_object_report() {
let object = run(hereticus().args([
"--progress",
"never",
"--ignore-names",
"--show-retouched",
"--format",
"compact",
"fixtures/build/mipsbe-O2/firmware.o",
"fixtures/build/mipsbe-O2/firmware_v2.o",
]));
let raw = run(compare("mips32-be", "mipsbe-O2-firmware", "mipsbe-O2-firmware_v2").args([
"--show-retouched",
"--format",
"compact",
]));
let rows = |output: &Output| -> Vec<String> {
String::from_utf8_lossy(&output.stdout)
.lines()
.map(|line| {
let fields: Vec<&str> = line.split('\t').collect();
[&fields[..1], &fields[3..]].concat().join("\t")
})
.collect()
};
let raw_rows = rows(&raw);
assert_eq!(raw_rows.len(), 4, "four matched, by code or by place");
assert_eq!(raw_rows, rows(&object));
}
#[test]
fn the_object_report_of_mips_firmware_is_stable() {
assert_cmd_snapshot!(hereticus().args([
"--progress",
"never",
"--ignore-names",
"--show-retouched",
"--format",
"compact",
"fixtures/build/mipsbe-O2/firmware.o",
"fixtures/build/mipsbe-O2/firmware_v2.o",
]));
}