use crate::error::ExtensionError;
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReleaseProfileCheck {
pub panic_unwind: bool,
pub lto_enabled: bool,
pub opt_level_3: bool,
pub codegen_units_1: bool,
}
impl ReleaseProfileCheck {
#[must_use]
pub const fn is_required_satisfied(&self) -> bool {
self.panic_unwind
}
#[must_use]
pub const fn is_fully_optimized(&self) -> bool {
self.panic_unwind && self.lto_enabled && self.opt_level_3 && self.codegen_units_1
}
}
pub fn validate_release_profile(
panic: &str,
lto: &str,
opt_level: &str,
codegen_units: &str,
) -> Result<ReleaseProfileCheck, ExtensionError> {
let check = ReleaseProfileCheck {
panic_unwind: panic == "unwind",
lto_enabled: matches!(lto, "true" | "fat"),
opt_level_3: opt_level == "3",
codegen_units_1: codegen_units == "1",
};
if !check.panic_unwind {
return Err(ExtensionError::new(format!(
"release profile sets panic = \"{panic}\"; it must be \"unwind\". \
quack-rs catches panics at every FFI boundary and turns them into \
DuckDB errors, and std::panic::catch_unwind cannot catch anything \
under panic = \"abort\" — the process aborts instead, taking the \
user's DuckDB session with it"
)));
}
Ok(check)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fully_optimized() {
let check = validate_release_profile("unwind", "true", "3", "1").unwrap();
assert!(check.is_fully_optimized());
assert!(check.is_required_satisfied());
}
#[test]
fn fat_lto_accepted() {
let check = validate_release_profile("unwind", "fat", "3", "1").unwrap();
assert!(check.lto_enabled);
}
#[test]
fn thin_lto_not_full() {
let check = validate_release_profile("unwind", "thin", "3", "1").unwrap();
assert!(!check.lto_enabled);
assert!(!check.is_fully_optimized());
}
#[test]
fn no_lto_still_passes_required() {
let check = validate_release_profile("unwind", "false", "2", "16").unwrap();
assert!(check.is_required_satisfied());
assert!(!check.is_fully_optimized());
}
#[test]
fn panic_abort_rejected_because_it_disables_catch_unwind() {
let err = validate_release_profile("abort", "true", "3", "1").unwrap_err();
assert!(err.as_str().contains("catch_unwind"));
assert!(err.as_str().contains("must be"));
}
#[test]
fn empty_panic_rejected() {
assert!(validate_release_profile("", "true", "3", "1").is_err());
}
#[test]
fn check_fields_independent() {
let check = validate_release_profile("unwind", "false", "2", "4").unwrap();
assert!(check.panic_unwind);
assert!(!check.lto_enabled);
assert!(!check.opt_level_3);
assert!(!check.codegen_units_1);
}
#[test]
fn the_scaffold_and_the_validator_agree() {
let cargo_toml = crate::scaffold::generate_scaffold(&crate::scaffold::ScaffoldConfig {
name: "probe".into(),
description: "d".into(),
maintainer: "m".into(),
github_repo: "o/r".into(),
..Default::default()
})
.expect("scaffold");
let manifest = &cargo_toml
.iter()
.find(|f| f.path == "Cargo.toml")
.expect("Cargo.toml")
.content;
assert!(
manifest.contains(r#"panic = "unwind""#),
"the scaffold must generate the profile this validator requires"
);
assert!(!manifest.contains(r#"panic = "abort""#));
}
}