#![cfg(unix)]
use std::path::{Path, PathBuf};
use std::process::Output;
fn alint_bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_alint"))
}
fn run(dir: &Path, args: &[&str]) -> Output {
std::process::Command::new(alint_bin())
.args(args)
.current_dir(dir)
.output()
.expect("spawn alint")
}
fn repo() -> tempfile::TempDir {
let dir = tempfile::Builder::new()
.prefix("alint-weirdpath-")
.tempdir()
.unwrap();
std::fs::write(
dir.path().join(".alint.yml"),
"version: 1\nrules:\n - id: no-fixme\n kind: file_content_forbidden\n \
paths: \"**/*.txt\"\n pattern: \"FIXME\"\n level: error\n",
)
.unwrap();
dir
}
const JSON_FORMATS: &[&str] = &["json", "sarif", "gitlab", "agent"];
const TEXT_FORMATS: &[&str] = &["human", "github", "markdown", "junit"];
#[test]
fn weird_utf8_filenames_render_across_all_formats() {
let dir = repo();
for name in [
"my file.txt", "café.txt", "文書.txt", "🚀.txt", "quote\".txt", "back\\slash.txt", "angle<>&.txt", "pipe|tick`.txt", "comma,colon:.txt", "pct%hash#.txt", ] {
std::fs::write(dir.path().join(name), "FIXME\n").unwrap();
}
for fmt in JSON_FORMATS.iter().chain(TEXT_FORMATS) {
let out = run(dir.path(), &["check", ".", "--format", fmt]);
assert_ne!(
out.status.code(),
Some(2),
"[{fmt}] aborted (exit 2) on weird UTF-8 filenames; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
!out.stdout.is_empty(),
"[{fmt}] produced no output for weird filenames"
);
if JSON_FORMATS.contains(fmt) {
serde_json::from_slice::<serde_json::Value>(&out.stdout).unwrap_or_else(|e| {
panic!(
"[{fmt}] stdout is not valid JSON: {e}\n{}",
String::from_utf8_lossy(&out.stdout)
)
});
}
}
}
#[cfg(target_os = "linux")]
#[test]
fn non_utf8_filename_does_not_break_json_or_agent() {
use std::os::unix::ffi::OsStrExt;
let dir = repo();
let mut name = std::ffi::OsString::from("bad");
name.push(std::ffi::OsStr::from_bytes(b"\xff"));
name.push("name.txt");
std::fs::write(dir.path().join(&name), "FIXME\n").unwrap();
for (fmt, ptr) in [
("json", &["results", "0", "violations", "0", "path"][..]),
("agent", &["violations", "0", "file"][..]),
] {
let out = run(dir.path(), &["check", ".", "--format", fmt]);
assert_eq!(
out.status.code(),
Some(1),
"[{fmt}] a non-UTF-8 filename must not abort output; got exit {:?}, stderr: {}",
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
let v: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("output must be valid JSON");
let mut cur = &v;
for key in ptr {
cur = key
.parse::<usize>()
.map_or_else(|_| &cur[key], |idx| &cur[idx]);
}
let rendered = cur.as_str().expect("path field is a JSON string");
assert!(
rendered.contains("name.txt") && rendered.contains('\u{fffd}'),
"[{fmt}] expected a lossy path (…\u{fffd}…name.txt), got {rendered:?}"
);
}
for fmt in ["sarif", "gitlab", "junit", "github", "markdown", "human"] {
let out = run(dir.path(), &["check", ".", "--format", fmt]);
assert_ne!(
out.status.code(),
Some(2),
"[{fmt}] regressed on a non-UTF-8 filename; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
}
}