forked from Crockan/MercuryToolbox
chore(release): prepare public source release
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
//! Integration tests for the LLVM COFF helper commands.
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use assert_cmd::Command;
|
||||
use predicates::prelude::*;
|
||||
use serde_json::Value;
|
||||
use tempfile::{TempDir, tempdir};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct ToolCase {
|
||||
binary: &'static str,
|
||||
backend: &'static str,
|
||||
}
|
||||
|
||||
const LLVM_TOOL_CASES: &[ToolCase] = &[
|
||||
ToolCase {
|
||||
binary: "llvmreadobj",
|
||||
backend: "llvm-readobj",
|
||||
},
|
||||
ToolCase {
|
||||
binary: "llvmobjdump",
|
||||
backend: "llvm-objdump",
|
||||
},
|
||||
ToolCase {
|
||||
binary: "llvmnm",
|
||||
backend: "llvm-nm",
|
||||
},
|
||||
];
|
||||
|
||||
fn llvm_command(binary: &str) -> Command {
|
||||
Command::cargo_bin(binary).expect("binary")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_mentions_examples_backend_and_shared_output_flags() {
|
||||
for case in LLVM_TOOL_CASES {
|
||||
let mut command = llvm_command(case.binary);
|
||||
command
|
||||
.arg("--help")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("Examples"))
|
||||
.stdout(predicate::str::contains("--llvm-bin-dir"))
|
||||
.stdout(predicate::str::contains("--llvm-arg"))
|
||||
.stdout(predicate::str::contains("--json"))
|
||||
.stdout(predicate::str::contains("--toon"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_output_exposes_common_top_level_contract() {
|
||||
for case in LLVM_TOOL_CASES {
|
||||
let backend_dir = fake_backend_dir(LLVM_TOOL_CASES);
|
||||
let input_dir = tempdir().expect("input tempdir");
|
||||
let object = write_coff_placeholder(input_dir.path(), case.binary);
|
||||
|
||||
let mut command = llvm_command(case.binary);
|
||||
add_fake_backend(&mut command, backend_dir.path());
|
||||
let output = command
|
||||
.arg("--json")
|
||||
.arg(&object)
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
|
||||
let json = serde_json::from_slice::<Value>(&output).expect("json output");
|
||||
assert_common_json_contract(case.binary, &json);
|
||||
assert!(
|
||||
json["files"]
|
||||
.as_array()
|
||||
.is_some_and(|files| !files.is_empty()),
|
||||
"{}: expected at least one file entry in {json}",
|
||||
case.binary
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdin_lines_feed_multiple_files() {
|
||||
for case in LLVM_TOOL_CASES {
|
||||
let backend_dir = fake_backend_dir(LLVM_TOOL_CASES);
|
||||
let input_dir = tempdir().expect("input tempdir");
|
||||
let first = write_coff_placeholder(input_dir.path(), &format!("{}-first", case.binary));
|
||||
let second = write_coff_placeholder(input_dir.path(), &format!("{}-second", case.binary));
|
||||
let stdin = format!("{}\n{}\n", first.display(), second.display());
|
||||
|
||||
let mut command = llvm_command(case.binary);
|
||||
add_fake_backend(&mut command, backend_dir.path());
|
||||
let output = command
|
||||
.arg("--json")
|
||||
.write_stdin(stdin)
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
|
||||
let json = serde_json::from_slice::<Value>(&output).expect("json output");
|
||||
assert_common_json_contract(case.binary, &json);
|
||||
let files = json["files"].as_array().expect("files array");
|
||||
assert_eq!(
|
||||
files.len(),
|
||||
2,
|
||||
"{}: expected stdin line input to produce two file entries in {json}",
|
||||
case.binary
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn objdump_symbol_and_address_flags_are_recorded_in_backend_argv() {
|
||||
let backend_dir = fake_backend_dir(LLVM_TOOL_CASES);
|
||||
let input_dir = tempdir().expect("input tempdir");
|
||||
let object = write_coff_placeholder(input_dir.path(), "objdump-argv");
|
||||
|
||||
let mut command = llvm_command("llvmobjdump");
|
||||
add_fake_backend(&mut command, backend_dir.path());
|
||||
let output = command
|
||||
.arg("--symbol")
|
||||
.arg("main")
|
||||
.arg("--start-address")
|
||||
.arg("0x0")
|
||||
.arg("--stop-address")
|
||||
.arg("0x20")
|
||||
.arg("--json")
|
||||
.arg(&object)
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
|
||||
let json = serde_json::from_slice::<Value>(&output).expect("json output");
|
||||
let argv = json["backend"]["argv"]
|
||||
.as_array()
|
||||
.expect("backend argv")
|
||||
.iter()
|
||||
.filter_map(Value::as_str)
|
||||
.collect::<Vec<_>>();
|
||||
assert!(argv.contains(&"--disassemble-symbols=main"));
|
||||
assert!(argv.contains(&"--start-address=0x0"));
|
||||
assert!(argv.contains(&"--stop-address=0x20"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_backend_fails_with_actionable_message() {
|
||||
for case in LLVM_TOOL_CASES {
|
||||
let empty_backend_dir = tempdir().expect("backend tempdir");
|
||||
let input_dir = tempdir().expect("input tempdir");
|
||||
let object = write_coff_placeholder(input_dir.path(), case.binary);
|
||||
|
||||
let mut command = llvm_command(case.binary);
|
||||
command
|
||||
.arg("--llvm-bin-dir")
|
||||
.arg(empty_backend_dir.path())
|
||||
.arg(&object)
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains(case.backend))
|
||||
.stderr(predicate::str::contains("--llvm-bin-dir"))
|
||||
.stderr(
|
||||
predicate::str::contains("backend")
|
||||
.or(predicate::str::contains("LLVM"))
|
||||
.or(predicate::str::contains("not found")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_common_json_contract(binary: &str, json: &Value) {
|
||||
assert!(
|
||||
json.get("backend").is_some(),
|
||||
"{binary}: missing top-level backend in {json}"
|
||||
);
|
||||
assert!(
|
||||
json.get("summary").is_some(),
|
||||
"{binary}: missing top-level summary in {json}"
|
||||
);
|
||||
assert!(
|
||||
json.get("files").and_then(Value::as_array).is_some(),
|
||||
"{binary}: missing top-level files array in {json}"
|
||||
);
|
||||
assert!(
|
||||
json.get("parse_warnings")
|
||||
.and_then(Value::as_array)
|
||||
.is_some(),
|
||||
"{binary}: missing top-level parse_warnings array in {json}"
|
||||
);
|
||||
}
|
||||
|
||||
fn add_fake_backend(command: &mut Command, backend_dir: &Path) {
|
||||
command.arg("--llvm-bin-dir").arg(backend_dir);
|
||||
for arg in fake_backend_args() {
|
||||
command.arg("--llvm-arg").arg(arg);
|
||||
}
|
||||
}
|
||||
|
||||
fn fake_backend_dir(cases: &[ToolCase]) -> TempDir {
|
||||
let dir = tempdir().expect("backend tempdir");
|
||||
let shell = host_shell();
|
||||
for case in cases {
|
||||
for file_name in backend_file_names(case.backend) {
|
||||
let destination = dir.path().join(file_name);
|
||||
fs::copy(&shell, &destination).expect("copy fake backend executable");
|
||||
make_executable(&destination);
|
||||
}
|
||||
}
|
||||
dir
|
||||
}
|
||||
|
||||
fn host_shell() -> PathBuf {
|
||||
if cfg!(windows) {
|
||||
env::var_os("COMSPEC").map_or_else(
|
||||
|| PathBuf::from(r"C:\Windows\System32\cmd.exe"),
|
||||
PathBuf::from,
|
||||
)
|
||||
} else {
|
||||
PathBuf::from("/bin/sh")
|
||||
}
|
||||
}
|
||||
|
||||
fn backend_file_names(backend: &str) -> Vec<String> {
|
||||
if cfg!(windows) {
|
||||
vec![backend.to_owned(), format!("{backend}.exe")]
|
||||
} else {
|
||||
vec![backend.to_owned()]
|
||||
}
|
||||
}
|
||||
|
||||
fn fake_backend_args() -> Vec<&'static str> {
|
||||
if cfg!(windows) {
|
||||
vec!["/D", "/S", "/C", "echo fake llvm coff output"]
|
||||
} else {
|
||||
vec!["-c", "printf 'fake llvm coff output\\n'"]
|
||||
}
|
||||
}
|
||||
|
||||
fn write_coff_placeholder(dir: &Path, stem: &str) -> PathBuf {
|
||||
let path = dir.join(format!("{stem}.obj"));
|
||||
fs::write(&path, b"MZ fake COFF placeholder\n").expect("write object placeholder");
|
||||
path
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn make_executable(path: &Path) {
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
|
||||
let mut permissions = fs::metadata(path)
|
||||
.expect("fake backend metadata")
|
||||
.permissions();
|
||||
permissions.set_mode(0o755);
|
||||
fs::set_permissions(path, permissions).expect("fake backend permissions");
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
const fn make_executable(_path: &Path) {}
|
||||
Reference in New Issue
Block a user