diff libpam-sys/libpam-sys-test/build.rs @ 131:a632a8874131

Get all the Linux-PAM functions into libpam-sys, and get tests right.
author Paul Fisher <paul@pfish.zone>
date Wed, 02 Jul 2025 02:24:21 -0400
parents 80c07e5ab22f
children 0b6a17f8c894
line wrap: on
line diff
--- a/libpam-sys/libpam-sys-test/build.rs	Tue Jul 01 06:11:43 2025 -0400
+++ b/libpam-sys/libpam-sys-test/build.rs	Wed Jul 02 02:24:21 2025 -0400
@@ -6,11 +6,13 @@
 use std::process::Command;
 use std::str::FromStr;
 use std::{env, fs};
-use syn::{Item, ItemConst, Type, TypePath};
+use syn::{Item, ItemConst};
 
 // We're using the macro directly so we can match exhaustively.
 __pam_impl_enum__!();
 
+const REDIR_FD: &str = "pam_modutil_redirect_fd";
+
 fn main() {
     let config = match PamImpl::CURRENT {
         PamImpl::LinuxPam => TestConfig {
@@ -19,7 +21,9 @@
                 "<security/pam_appl.h>",
                 "<security/pam_ext.h>",
                 "<security/pam_modules.h>",
+                "<security/pam_modutil.h>",
             ],
+            allow_types: vec![REDIR_FD],
             ignore_consts: vec![
                 "__LINUX_PAM__",
                 "__LINUX_PAM_MINOR__",
@@ -39,12 +43,10 @@
         },
         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_pam_appl.h\""],
-            ignore_consts: vec!["PAM_CRED_PRELIM_CHECK"],
             ..Default::default()
         },
     };
@@ -57,22 +59,28 @@
         .header_contents("_.h", &config.header_contents())
         .merge_extern_blocks(true)
         .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
-        .blocklist_type(".*")
-        .blocklist_function(".*")
-        .allowlist_var(".*")
+        .allowlist_var("(OPEN)?PAM_.*")
         .default_macro_constant_type(MacroTypeVariation::Signed);
-    for hdr in config.block_headers.iter() {
-        builder = builder.blocklist_file(".*?/".to_owned() + hdr)
+
+    for &typ in config.allow_types.iter() {
+        builder = builder.allowlist_type(typ);
     }
 
-    let generated = builder.generate().unwrap().to_string();
-    let file = syn::parse_file(&generated).unwrap();
+    let generated = builder.generate().unwrap();
+    generated.write_to_file(test_file("bindgen.rs")).unwrap();
+    let file = syn::parse_file(&generated.to_string()).unwrap();
     let mut tests = vec![
-        "use libpam_sys::*;".into(),
-        "#[allow(deprecated, overflowing_literals)]".into(),
-        "fn main() {".into(),
+        "\
+            #[allow(dead_code, non_camel_case_types, non_upper_case_globals)]
+            mod generated {
+                include!(\"bindgen.rs\");
+            }
+            #[allow(deprecated, overflowing_literals)]
+            fn main() {
+        "
+        .into(),
         format!(
-            "assert_eq!(PamImpl::CURRENT, PamImpl::{:?});",
+            "assert_eq!(libpam_sys::PamImpl::CURRENT, libpam_sys::PamImpl::{:?});",
             PamImpl::CURRENT
         ),
     ];
@@ -86,18 +94,14 @@
                     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}, {name});",
-                    tokens = item.expr.to_token_stream(),
-                    name = item.ident
-                )
+            .filter(|&item| config.should_check_const(item))
+            .map(|item| {
+                let name = item.ident.to_string();
+                if let Some(stripped) = name.strip_prefix(&format!("{REDIR_FD}_")) {
+                    format!("assert_eq!(generated::{name} as i32, libpam_sys::{REDIR_FD}::{stripped}.into());")
+                } else {
+                    format!("assert_eq!(generated::{name}, libpam_sys::{name});")
+                }
             }),
     );
     tests.push("}".into());
@@ -108,8 +112,9 @@
 
 fn generate_ctest(config: &TestConfig) {
     let mut test = ctest::TestGenerator::new();
+    test.cfg("_hack_impl", Some(&format!("{:?}", PamImpl::CURRENT)));
 
-    for header in config.headers.iter() {
+    for &header in config.headers.iter() {
         if header.starts_with('"') {
             test.include(env::var("CARGO_MANIFEST_DIR").unwrap());
         }
@@ -118,17 +123,17 @@
     // These are opaque structs.
     test.skip_struct(|name| matches!(name, "pam_handle" | "AppData"));
     test.skip_type(|name| matches!(name, "ConversationCallback" | "CleanupCallback"));
-    test.type_name(|name, _is_struct, is_union| {
+    test.type_name(|name, is_struct, is_union| {
         assert!(!is_union); // we scabbin'
-        match name {
-            "pam_handle" => "struct pam_handle",
-            "pam_conv" => "struct pam_conv",
-            "pam_message" => "struct pam_message",
-            "pam_response" => "struct pam_response",
-            "AppData" => "void",
-            other => other,
+        match (name, is_struct) {
+            ("AppData", _) => "void".into(),
+            (REDIR_FD, _) => format!("enum {REDIR_FD}"),
+            ("passwd", _) => "struct passwd".into(),
+            ("group", _) => "struct group".into(),
+            ("spwd", _) => "struct spwd".into(),
+            (name, true) => format!("struct {name}"),
+            (other, false) => other.into(),
         }
-        .into()
     });
 
     //
@@ -186,7 +191,7 @@
 #[derive(Default)]
 struct TestConfig {
     headers: Vec<&'static str>,
-    block_headers: Vec<&'static str>,
+    allow_types: Vec<&'static str>,
     ignore_consts: Vec<&'static str>,
 }