diff libpam-sys/libpam-sys-test/build.rs @ 127:c77846f3a979

GET CTEST WORKING. This will verify that the functions we're exporting are correct. It has been a nightmare.
author Paul Fisher <paul@pfish.zone>
date Mon, 30 Jun 2025 22:56:26 -0400
parents 2b255c92417b
children
line wrap: on
line diff
--- a/libpam-sys/libpam-sys-test/build.rs	Mon Jun 30 21:52:10 2025 -0400
+++ b/libpam-sys/libpam-sys-test/build.rs	Mon Jun 30 22:56:26 2025 -0400
@@ -1,7 +1,10 @@
 use bindgen::MacroTypeVariation;
 use libpam_sys_impls::__pam_impl_enum__;
+use proc_macro2::{Group, TokenStream, TokenTree};
 use quote::{format_ident, ToTokens};
-use std::path::PathBuf;
+use std::path::Path;
+use std::process::Command;
+use std::str::FromStr;
 use std::{env, fs};
 use syn::{Item, ItemConst, Type, TypePath};
 
@@ -46,6 +49,7 @@
         },
     };
     generate_const_test(&config);
+    generate_ctest(&config);
 }
 
 fn generate_const_test(config: &TestConfig) {
@@ -63,8 +67,15 @@
 
     let generated = builder.generate().unwrap().to_string();
     let file = syn::parse_file(&generated).unwrap();
-    let mut tests = vec![];
-    tests.push("{".into());
+    let mut tests = vec![
+        "use libpam_sys::*;".into(),
+        "#[allow(deprecated, overflowing_literals)]".into(),
+        "fn main() {".into(),
+        format!(
+            "assert_eq!(PamImpl::CURRENT, PamImpl::{:?});",
+            PamImpl::CURRENT
+        ),
+    ];
     tests.extend(
         file.items
             .iter()
@@ -83,18 +94,97 @@
                     path: format_ident!("i32").into(),
                 }));
                 format!(
-                    "assert_eq!({tokens}, libpam_sys::{name});",
+                    "assert_eq!({tokens}, {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"),
+    let const_test = test_file("constant_test.rs");
+    fs::write(&const_test, tests.join("\n")).unwrap();
+    rustfmt(&const_test);
+}
+
+fn generate_ctest(config: &TestConfig) {
+    let mut test = ctest::TestGenerator::new();
+
+    for header in config.headers.iter() {
+        if header.starts_with('"') {
+            test.include(env::var("CARGO_MANIFEST_DIR").unwrap());
+        }
+        test.header(&header[1..header.len() - 1]);
+    }
+    // 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| {
+        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,
+        }
+        .into()
+    });
+
+    //
+    // Welcome to THE HACK ZONE.
+    //
+
+    // Define away constness because the various PAM implementations
+    // have different const annotations and this will surely drive you crazy.
+    test.define("const", Some(""));
+
+    // Also replace all the `const`s with `mut`s in the ffi.rs file.
+    let file_contents = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../src/ffi.rs"));
+    let deconsted_file = test_file("ffi.rs");
+    remove_consts(file_contents, &deconsted_file);
+
+    test.generate(&deconsted_file, "ctest.rs");
+}
+
+fn remove_consts(file_contents: &str, out_file: impl AsRef<Path>) {
+    let deconstified = deconstify(
+        TokenStream::from_str(file_contents).unwrap(),
+        &TokenStream::from_str("mut")
+            .unwrap()
+            .into_iter()
+            .next()
+            .unwrap(),
     )
-    .unwrap();
+    .to_string();
+    let out_file = out_file.as_ref();
+    fs::write(out_file, deconstified).unwrap();
+    rustfmt(out_file)
+}
+
+fn rustfmt(file: impl AsRef<Path>) {
+    let status = Command::new(env!("CARGO"))
+        .args(["fmt", "--", file.as_ref().to_str().unwrap()])
+        .status()
+        .unwrap();
+    assert!(status.success(), "rustfmt exited with code {status}");
+}
+
+fn deconstify(stream: TokenStream, mut_token: &TokenTree) -> TokenStream {
+    TokenStream::from_iter(stream.into_iter().map(|token| {
+        match token {
+            TokenTree::Group(g) => {
+                TokenTree::Group(Group::new(g.delimiter(), deconstify(g.stream(), mut_token)))
+                    .into_token_stream()
+            }
+            TokenTree::Ident(id) if id == "const" => mut_token.into_token_stream(),
+            other => other.into_token_stream(),
+        }
+    }))
+}
+
+fn test_file(name: impl AsRef<str>) -> String {
+    format!("{}/{}", env::var("OUT_DIR").unwrap(), name.as_ref())
 }
 
 #[derive(Default)]