#![allow(clippy::expect_used)]
use libmagic_rs::{EvaluationConfig, MagicDatabase};
use std::path::Path;
use std::process::Command;
const SYSTEM_MAGIC_DIR: &str = "/usr/share/file/magic/";
fn has_system_magic_dir(path: &Path) -> bool {
path.is_dir()
}
fn has_file_binary() -> bool {
Command::new("file")
.arg("--version")
.output()
.is_ok_and(|output| output.status.success())
}
#[test]
fn test_skip_gate_is_reachable_for_a_missing_directory() {
let temp = tempfile::TempDir::new().expect("create temp dir");
let fake_path = temp.path().join("libmagic-rs-nonexistent-child");
assert!(
!has_system_magic_dir(&fake_path),
"the gate helper must report false for a path that does not exist"
);
}
#[test]
fn test_system_magic_dir_loads_and_evaluates_cargo_toml_without_fatal_error() {
let system_dir = Path::new(SYSTEM_MAGIC_DIR);
if !has_system_magic_dir(system_dir) {
eprintln!(
"SKIP: {SYSTEM_MAGIC_DIR} not present on this host -- \
gated system-DB load test skipped cleanly"
);
return;
}
let db = MagicDatabase::load_from_file(system_dir)
.expect("loading the system magic directory must not fail");
let cargo_toml = std::fs::read("Cargo.toml").expect("Cargo.toml must be readable in CI/dev");
let result = db
.evaluate_buffer(&cargo_toml)
.expect("evaluating Cargo.toml against the system DB must not fatally error");
let _ = result;
}
#[test]
fn test_system_magic_dir_loads_and_evaluates_rmagic_binary_if_present() {
let system_dir = Path::new(SYSTEM_MAGIC_DIR);
if !has_system_magic_dir(system_dir) {
eprintln!(
"SKIP: {SYSTEM_MAGIC_DIR} not present on this host -- \
gated system-DB load test skipped cleanly"
);
return;
}
let binary_path = Path::new("target/debug/rmagic");
if !binary_path.exists() {
eprintln!("SKIP: target/debug/rmagic not built -- binary-target load test skipped");
return;
}
let db = MagicDatabase::load_from_file(system_dir)
.expect("loading the system magic directory must not fail");
let buffer = std::fs::read(binary_path).expect("target/debug/rmagic must be readable");
let result = db
.evaluate_buffer(&buffer)
.expect("evaluating the rmagic binary against the system DB must not fatally error");
let _ = result;
}
struct Sample {
filename: &'static str,
content: &'static [u8],
expect_assembler_detected: bool,
description: &'static str,
}
const SAMPLES: &[Sample] = &[
Sample {
filename: "leading_tab_asciiz.s",
content: b"\t.asciiz \"hi\"\n",
expect_assembler_detected: true,
description: "leading tab then .asciiz -- the plan's worked-example positive case",
},
Sample {
filename: "column_zero_globl.s",
content: b".globl main\n",
expect_assembler_detected: true,
description: "a different affected assembler keyword (.globl) at column 0",
},
Sample {
filename: "plain_text.txt",
content: b"hello world, this is not assembler\n",
expect_assembler_detected: false,
description: "ordinary prose text with no assembler directive anywhere",
},
Sample {
filename: "non_whitespace_prefix_asciiz.s",
content: b"xyz .asciiz\n",
expect_assembler_detected: false,
description: "non-whitespace prefix before .asciiz -- must not match (anchor requires \
0-50 leading whitespace chars, not arbitrary prefix text)",
},
];
fn contains_assembler_signal(text: &str) -> bool {
text.contains("assembler source text")
}
fn rmagic_detects_assembler(db: &MagicDatabase, content: &[u8]) -> bool {
let result = db
.evaluate_buffer(content)
.expect("evaluation must not fatally error for a curated sample");
result
.matches
.iter()
.any(|m| contains_assembler_signal(&m.message))
}
fn file_binary_detects_assembler(path: &Path) -> bool {
let output = Command::new("file")
.arg("--magic-file")
.arg(SYSTEM_MAGIC_DIR)
.arg(path)
.output()
.expect("failed to invoke the `file` binary");
assert!(
output.status.success(),
"`file` exited non-zero for {}: stderr={}",
path.display(),
String::from_utf8_lossy(&output.stderr)
);
contains_assembler_signal(&String::from_utf8_lossy(&output.stdout))
}
#[test]
fn test_differential_parity_against_gnu_file_for_assembler_detection() {
let system_dir = Path::new(SYSTEM_MAGIC_DIR);
if !has_system_magic_dir(system_dir) {
eprintln!(
"SKIP: {SYSTEM_MAGIC_DIR} not present on this host -- \
differential parity test skipped cleanly"
);
return;
}
if !has_file_binary() {
eprintln!("SKIP: `file` binary not on PATH -- differential parity test skipped cleanly");
return;
}
let config = EvaluationConfig::default().with_stop_at_first_match(false);
let db = MagicDatabase::load_from_file_with_config(system_dir, config)
.expect("loading the system magic directory must not fail");
let temp_dir = tempfile::TempDir::new().expect("failed to create temp dir for samples");
let probe_path = temp_dir.path().join("__probe_leading_tab_asciiz.s");
std::fs::write(&probe_path, b"\t.asciiz \"hi\"\n").expect("failed to write probe sample");
let host_db_has_assembler_rules = file_binary_detects_assembler(&probe_path);
for sample in SAMPLES {
let sample_path = temp_dir.path().join(sample.filename);
std::fs::write(&sample_path, sample.content).expect("failed to write sample file");
let rmagic_result = rmagic_detects_assembler(&db, sample.content);
let file_result = file_binary_detects_assembler(&sample_path);
assert_eq!(
rmagic_result, file_result,
"PARITY FAILURE for {:?} ({}): rmagic={rmagic_result} file={file_result}",
sample.filename, sample.description
);
if host_db_has_assembler_rules {
assert_eq!(
rmagic_result,
sample.expect_assembler_detected,
"rmagic detection mismatch for {:?} ({}): expected {}, got {}",
sample.filename,
sample.description,
sample.expect_assembler_detected,
rmagic_result
);
}
}
}
fn deterministic_binary_blob() -> Vec<u8> {
(0..64u32)
.map(|i| u8::try_from((i * 37 + 11) % 256).expect("modulo 256 always fits in u8"))
.collect()
}
#[test]
fn test_default_config_assembler_source_no_longer_blank() {
let system_dir = Path::new(SYSTEM_MAGIC_DIR);
if !has_system_magic_dir(system_dir) {
eprintln!(
"SKIP: {SYSTEM_MAGIC_DIR} not present on this host -- \
default-config assembler test skipped cleanly"
);
return;
}
let db = MagicDatabase::load_from_file(system_dir)
.expect("loading the system magic directory must not fail");
let result = db
.evaluate_buffer(b"\t.asciiz \"hi\"\n")
.expect("evaluation must not fatally error");
assert!(
!result.description.trim().is_empty(),
"description must not be blank for an assembler-source buffer, got: {:?}",
result.description
);
if has_file_binary() {
let temp_dir = tempfile::TempDir::new().expect("failed to create temp dir");
let sample_path = temp_dir.path().join("leading_tab_asciiz.s");
std::fs::write(&sample_path, b"\t.asciiz \"hi\"\n").expect("failed to write sample");
let file_says_assembler = file_binary_detects_assembler(&sample_path);
assert_eq!(
contains_assembler_signal(&result.description),
file_says_assembler,
"assembler-detection parity failure under default config: rmagic description={:?}, \
GNU `file` detected assembler={file_says_assembler}",
result.description
);
}
}
#[test]
fn test_default_config_plain_ascii_text_no_longer_blank() {
let system_dir = Path::new(SYSTEM_MAGIC_DIR);
if !has_system_magic_dir(system_dir) {
eprintln!(
"SKIP: {SYSTEM_MAGIC_DIR} not present on this host -- \
default-config plain-text test skipped cleanly"
);
return;
}
let db = MagicDatabase::load_from_file(system_dir)
.expect("loading the system magic directory must not fail");
let result = db
.evaluate_buffer(b"hello world, this is not assembler\n")
.expect("evaluation must not fatally error");
assert_eq!(result.description, "ASCII text");
}
#[test]
fn test_default_config_c_source_is_c_program_text() {
let system_dir = Path::new(SYSTEM_MAGIC_DIR);
if !has_system_magic_dir(system_dir) {
eprintln!(
"SKIP: {SYSTEM_MAGIC_DIR} not present on this host -- \
default-config c-source test skipped cleanly"
);
return;
}
let db = MagicDatabase::load_from_file(system_dir)
.expect("loading the system magic directory must not fail");
let c_source = b"#include <stdio.h>\nint main(void){return 0;}\n";
let result = db
.evaluate_buffer(c_source)
.expect("evaluation must not fatally error");
if has_file_binary() {
let temp_dir = tempfile::TempDir::new().expect("failed to create temp dir");
let sample_path = temp_dir.path().join("sample.c");
std::fs::write(&sample_path, c_source).expect("failed to write sample");
let output = Command::new("file")
.arg("--magic-file")
.arg(SYSTEM_MAGIC_DIR)
.arg(&sample_path)
.output()
.expect("failed to invoke the `file` binary");
let file_desc = String::from_utf8_lossy(&output.stdout);
if file_desc.contains("c program text") {
assert!(
result.description.contains("c program text"),
"message-bearing `clear` regression: rmagic must emit \
\"c program text\" for a C source under the default config, \
got: {:?} (file said: {:?})",
result.description,
file_desc.trim()
);
}
}
}
#[test]
fn test_default_config_binary_blob_is_data() {
let system_dir = Path::new(SYSTEM_MAGIC_DIR);
if !has_system_magic_dir(system_dir) {
eprintln!(
"SKIP: {SYSTEM_MAGIC_DIR} not present on this host -- \
default-config binary-blob test skipped cleanly"
);
return;
}
let db = MagicDatabase::load_from_file(system_dir)
.expect("loading the system magic directory must not fail");
let result = db
.evaluate_buffer(&deterministic_binary_blob())
.expect("evaluation must not fatally error");
assert_eq!(result.description, "data");
}
#[test]
fn test_default_config_differential_parity_three_cases() {
let system_dir = Path::new(SYSTEM_MAGIC_DIR);
if !has_system_magic_dir(system_dir) {
eprintln!(
"SKIP: {SYSTEM_MAGIC_DIR} not present on this host -- \
default-config differential parity test skipped cleanly"
);
return;
}
if !has_file_binary() {
eprintln!("SKIP: `file` binary not on PATH -- differential parity test skipped cleanly");
return;
}
let db = MagicDatabase::load_from_file(system_dir)
.expect("loading the system magic directory must not fail");
let temp_dir = tempfile::TempDir::new().expect("failed to create temp dir for samples");
let cases: &[(&str, &[u8])] = &[
("assembler.s", b"\t.asciiz \"hi\"\n"),
("plain.txt", b"hello world, this is not assembler\n"),
];
for (filename, content) in cases {
let path = temp_dir.path().join(filename);
std::fs::write(&path, content).expect("failed to write sample file");
let rmagic_result = db
.evaluate_buffer(content)
.expect("evaluation must not fatally error");
let file_output = Command::new("file")
.arg("--magic-file")
.arg(SYSTEM_MAGIC_DIR)
.arg(&path)
.output()
.expect("failed to invoke the `file` binary");
assert!(file_output.status.success());
let file_stdout = String::from_utf8_lossy(&file_output.stdout);
assert!(
!rmagic_result.description.trim().is_empty(),
"rmagic must not produce a blank description for {filename:?}"
);
if *filename == "assembler.s" {
assert_eq!(
contains_assembler_signal(&rmagic_result.description),
contains_assembler_signal(&file_stdout),
"assembler-detection parity failure for {filename:?}: \
rmagic={:?}, file={file_stdout:?}",
rmagic_result.description
);
} else {
assert_eq!(rmagic_result.description, "ASCII text");
assert!(
file_stdout.contains("ASCII text"),
"GNU `file` should also report ASCII text for {filename:?}, got: {file_stdout:?}"
);
}
}
let binary_path = temp_dir.path().join("blob.bin");
let blob = deterministic_binary_blob();
std::fs::write(&binary_path, &blob).expect("failed to write binary blob");
let rmagic_blob_result = db
.evaluate_buffer(&blob)
.expect("evaluation must not fatally error");
let file_blob_output = Command::new("file")
.arg("--magic-file")
.arg(SYSTEM_MAGIC_DIR)
.arg(&binary_path)
.output()
.expect("failed to invoke the `file` binary");
assert!(file_blob_output.status.success());
let file_blob_stdout = String::from_utf8_lossy(&file_blob_output.stdout);
assert_eq!(rmagic_blob_result.description, "data");
assert!(
file_blob_stdout.contains("data"),
"GNU `file` should also report \"data\" for the binary blob, got: {file_blob_stdout:?}"
);
}
#[test]
fn test_default_config_no_match_falls_back_to_text_classification_end_to_end() {
let temp_dir = tempfile::TempDir::new().expect("failed to create temp dir");
let magic_path = temp_dir.path().join("messageless_only.magic");
std::fs::write(&magic_path, "0\tbyte\t0x09\n").expect("failed to write magic file");
let db = MagicDatabase::load_from_file(&magic_path)
.expect("loading a single-rule magic file must not fail");
let result = db
.evaluate_buffer(b"\t.asciiz \"hi\"\n")
.expect("evaluation must not fatally error");
assert_eq!(
result.description, "ASCII text",
"a match with no usable description text must fall back to the text \
classifier, not a blank description; got: {:?}",
result.description
);
}
#[test]
fn test_default_config_messageless_gate_does_not_blank_output_end_to_end() {
let temp_dir = tempfile::TempDir::new().expect("failed to create temp dir");
let magic_path = temp_dir.path().join("gate_then_message.magic");
std::fs::write(
&magic_path,
"0\tstring\t\\t.asciiz\n0\tstring\t\\t.asciiz\tassembler source text\n",
)
.expect("failed to write magic file");
let db = MagicDatabase::load_from_file(&magic_path)
.expect("loading the two-rule magic file must not fail");
let result = db
.evaluate_buffer(b"\t.asciiz \"hi\"\n")
.expect("evaluation must not fatally error");
assert!(
result.description.contains("assembler source text"),
"the message-bearing rule's text must survive a preceding message-less \
match under stop_at_first_match; got: {:?}",
result.description
);
}