view libpam-sys/libpam-sys-test/build.rs @ 124:f469b8d9ad78 default tip

Add tests for the original X/SSO constants list.
author Paul Fisher <paul@pfish.zone>
date Mon, 30 Jun 2025 04:54:38 -0400
parents 9e05e44050d0
children
line wrap: on
line source

use bindgen::MacroTypeVariation;
use libpam_sys::PamImpl;
use quote::{format_ident, ToTokens};
use std::path::PathBuf;
use std::{env, fs};
use syn::{Item, ItemConst, Type, TypePath};

fn main() {
    let config = match PamImpl::CURRENT {
        PamImpl::LinuxPam => TestConfig {
            headers: vec![
                "<security/_pam_types.h>",
                "<security/pam_appl.h>",
                "<security/pam_ext.h>",
                "<security/pam_modules.h>",
            ],
            ignore_consts: vec![
                "__LINUX_PAM__",
                "__LINUX_PAM_MINOR__",
                "PAM_AUTHTOK_RECOVER_ERR",
            ],
            ..Default::default()
        },
        PamImpl::OpenPam => TestConfig {
            headers: vec![
                "<security/pam_types.h>",
                "<security/openpam.h>",
                "<security/pam_appl.h>",
                "<security/pam_constants.h>",
            ],
            ignore_consts: vec!["OPENPAM_VERSION", "OPENPAM_RELEASE", "PAM_SOEXT"],
            ..Default::default()
        },
        PamImpl::Sun => TestConfig {
            headers: vec!["<security/pam_appl.h>", "<security/pam_modules.h>"],
            block_headers: vec!["sys/.*"],
            ..Default::default()
        },
        PamImpl::XSso => TestConfig {
            headers: vec!["\"xsso_constants.h\""],
            ignore_consts: vec!["PAM_CRED_PRELIM_CHECK"],
            ..Default::default()
        },
        other => panic!("Unknown PAM implementation {other:?}"),
    };
    generate_const_test(&config);
}

fn generate_const_test(config: &TestConfig) {
    let mut builder = bindgen::Builder::default()
        .header_contents("_.h", &config.header_contents())
        .merge_extern_blocks(true)
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .blocklist_type(".*")
        .blocklist_function(".*")
        .allowlist_var(".*")
        .default_macro_constant_type(MacroTypeVariation::Signed);
    for hdr in config.block_headers.iter() {
        builder = builder.blocklist_file(".*?/".to_owned() + hdr)
    }

    let generated = builder.generate().unwrap().to_string();
    let file = syn::parse_file(&generated).unwrap();
    let mut tests = vec![];
    tests.push("{".into());
    tests.extend(
        file.items
            .iter()
            .filter_map(|item| {
                if let Item::Const(item) = item {
                    Some(item)
                } else {
                    None
                }
            })
            .filter(|item| config.should_check_const(item))
            .cloned()
            .map(|mut item| {
                item.ty = Box::new(Type::Path(TypePath {
                    qself: None,
                    path: format_ident!("i32").into(),
                }));
                format!(
                    "assert_eq!({tokens}, libpam_sys::{name});",
                    tokens = item.expr.to_token_stream(),
                    name = item.ident
                )
            }),
    );
    tests.push("}".into());
    fs::write(
        PathBuf::from(env::var("OUT_DIR").unwrap()).join("constant_test.rs"),
        tests.join("\n"),
    )
    .unwrap();
}

#[derive(Default)]
struct TestConfig {
    headers: Vec<&'static str>,
    block_headers: Vec<&'static str>,
    ignore_consts: Vec<&'static str>,
}

impl TestConfig {
    fn header_contents(&self) -> String {
        let vec: Vec<_> = self
            .headers
            .iter()
            .map(|h| format!("#include {h}\n"))
            .collect();
        vec.join("")
    }

    fn should_check_const(&self, item: &ItemConst) -> bool {
        !self
            .ignore_consts
            .contains(&item.ident.to_string().as_ref())
    }
}