forked from Crockan/MercuryToolbox
261 lines
7.7 KiB
Rust
261 lines
7.7 KiB
Rust
//! 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) {}
|