const LINUX_COMPAT_SOURCES: &[&str] = &[
"compat_ohash.c",
"compat_progname.c",
"compat_recallocarray.c",
"compat_strlcat.c",
"compat_strlcpy.c",
"compat_strtonum.c",
];
const MACOS_COMPAT_SOURCES: &[&str] = &[
"compat_ohash.c",
"compat_progname.c",
"compat_reallocarray.c",
"compat_recallocarray.c",
];
const WINDOWS_MSVC_COMPAT_SOURCES: &[&str] = &[
"compat_err.c",
"compat_ohash.c",
"compat_progname.c",
"compat_reallocarray.c",
"compat_recallocarray.c",
"compat_strlcat.c",
"compat_strlcpy.c",
"compat_strndup.c",
"compat_strtonum.c",
"compat_vasprintf.c",
];
pub(crate) fn target_configuration(
target_os: &str,
target_env: &str,
) -> (&'static str, &'static [&'static str]) {
match (target_os, target_env) {
("linux", "gnu") => ("config/linux-gnu.h", LINUX_COMPAT_SOURCES),
("macos", _) => ("config/macos.h", MACOS_COMPAT_SOURCES),
("windows", "msvc") => ("config/windows-msvc.h", WINDOWS_MSVC_COMPAT_SOURCES),
("linux", env) => {
panic!("libmandoc-rs does not yet provide a checked configuration for Linux/{env}")
}
(os, env) => panic!(
"libmandoc-rs only supports Linux/glibc, macOS, and Windows/MSVC, not {os}/{env}"
),
}
}
#[cfg(test)]
mod tests {
use super::{
LINUX_COMPAT_SOURCES, MACOS_COMPAT_SOURCES, WINDOWS_MSVC_COMPAT_SOURCES,
target_configuration,
};
#[test]
fn target_families_select_explicit_compatibility_sources() {
assert_eq!(
target_configuration("linux", "gnu"),
("config/linux-gnu.h", LINUX_COMPAT_SOURCES)
);
assert_eq!(
target_configuration("macos", ""),
("config/macos.h", MACOS_COMPAT_SOURCES)
);
assert_eq!(
target_configuration("windows", "msvc"),
("config/windows-msvc.h", WINDOWS_MSVC_COMPAT_SOURCES)
);
}
#[test]
fn linux_does_not_depend_on_new_glibc_string_extensions() {
let (_, sources) = target_configuration("linux", "gnu");
let config = include_str!("../config/linux-gnu.h");
assert!(sources.contains(&"compat_strlcat.c"));
assert!(sources.contains(&"compat_strlcpy.c"));
assert!(config.contains("#define HAVE_STRLCAT 0"));
assert!(config.contains("#define HAVE_STRLCPY 0"));
}
#[test]
fn every_target_locks_roff_character_classes_to_ascii() {
let configs = [
include_str!("../config/linux-gnu.h"),
include_str!("../config/macos.h"),
include_str!("../config/windows-msvc.h"),
];
for config in configs {
assert!(config.contains("#include \"mant_ascii_ctype.h\""));
}
let ascii = include_str!("../config/mant_ascii_ctype.h");
for class in [
"isalnum", "isalpha", "isdigit", "isgraph", "islower", "isspace", "isupper", "tolower",
"toupper",
] {
assert!(
ascii.contains(&format!("#define {class}(c) mant_ascii_{class}(c)")),
"missing deterministic override for {class}"
);
}
}
#[test]
fn every_target_namespaces_private_native_symbols() {
let configs = [
include_str!("../config/linux-gnu.h"),
include_str!("../config/macos.h"),
include_str!("../config/windows-msvc.h"),
];
for config in configs {
assert!(config.contains("#include \"mant_symbol_prefix.h\""));
}
let prefix = include_str!("../config/mant_symbol_prefix.h");
for symbol in [
"mparse_alloc",
"mandoc_malloc",
"roff_alloc",
"strlcpy",
"ohash_init",
] {
assert!(
prefix.contains(&format!("#define {symbol} mant_vendored_{symbol}")),
"missing namespace mapping for {symbol}"
);
}
}
#[test]
fn diagnostic_capture_never_falls_back_to_process_stderr() {
let shim = include_str!("../shim/mant_mandoc_shim.c");
assert!(!shim.contains("messages == NULL ? stderr : messages"));
assert!(shim.contains("could not create private diagnostic capture file"));
assert!(shim.contains("mandoc_msg_setoutfile(messages);"));
}
#[test]
fn target_specific_process_state_is_replaced_with_local_compatibility() {
let macos = include_str!("../config/macos.h");
assert!(MACOS_COMPAT_SOURCES.contains(&"compat_progname.c"));
assert!(macos.contains("#define HAVE_PROGNAME 0"));
let windows = include_str!("../config/windows-msvc.h");
let compatibility = include_str!("../shim/windows_compat.c");
assert!(windows.contains("#define _mkgmtime64 mant_timegm"));
assert!(windows.contains("#define gmtime_s mant_gmtime_s"));
assert!(compatibility.contains("mant_timegm(struct tm *value)"));
assert!(compatibility.contains("mant_gmtime_s(struct tm *result"));
}
#[test]
fn every_progname_compatibility_target_declares_its_namespaced_api() {
let targets = [
(include_str!("../config/linux-gnu.h"), LINUX_COMPAT_SOURCES),
(include_str!("../config/macos.h"), MACOS_COMPAT_SOURCES),
(
include_str!("../config/windows-msvc.h"),
WINDOWS_MSVC_COMPAT_SOURCES,
),
];
for (config, sources) in targets {
assert!(sources.contains(&"compat_progname.c"));
let prefix = config
.find("#include \"mant_symbol_prefix.h\"")
.expect("target configuration must namespace native symbols");
for declaration in [
"extern const char *getprogname(void);",
"extern void setprogname(const char *);",
] {
let declaration = config
.find(declaration)
.unwrap_or_else(|| panic!("missing compatibility declaration: {declaration}"));
assert!(
declaration > prefix,
"compatibility declarations must use the prefixed symbol names"
);
}
}
}
#[test]
fn msvc_warning_baseline_is_confined_to_upstream_objects() {
let build = include_str!("../build.rs");
for warning in ["4100", "4146", "4200", "4244", "4267"] {
assert!(build.contains(&format!(".flag(\"/wd{warning}\")")));
}
assert!(build.contains("let mut upstream_build = build.clone();"));
assert!(build.contains("let mut owned_build = build;"));
assert!(build.contains("upstream_build.compile_intermediates()"));
assert!(build.contains("owned_build.compile_intermediates()"));
let roff = include_str!("../vendor/mandoc-1.14.6/roff.c");
let done = roff.find("done = 0;").expect("expansion state reset");
let sign = roff
.find("sign = '\\0';")
.expect("neutral numeric-register sign");
let start = roff
.find("start = buf->buf + pos;")
.expect("expansion scan start");
assert!(done < sign && sign < start);
let man_html = include_str!("../vendor/mandoc-1.14.6/man_html.c");
assert!(man_html.contains("struct tag\t*t = NULL;"));
let term = include_str!("../vendor/mandoc-1.14.6/term.c");
assert!(term.contains("size_t\t\t csz, lsz, ssz = 0;"));
let rhs = term.find("rhs = NULL;").expect("escape result reset");
let uc = term[rhs..]
.find("uc = 0;")
.map(|offset| rhs + offset)
.expect("escape code point reset");
assert!(rhs < uc);
}
#[test]
#[should_panic(expected = "Linux/musl")]
fn unconfigured_linux_libc_is_rejected() {
target_configuration("linux", "musl");
}
}