Mercurial > crates > nonstick
comparison 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 | 80c07e5ab22f |
comparison
equal
deleted
inserted
replaced
| 126:57c812e308bd | 127:c77846f3a979 |
|---|---|
| 1 use bindgen::MacroTypeVariation; | 1 use bindgen::MacroTypeVariation; |
| 2 use libpam_sys_impls::__pam_impl_enum__; | 2 use libpam_sys_impls::__pam_impl_enum__; |
| 3 use proc_macro2::{Group, TokenStream, TokenTree}; | |
| 3 use quote::{format_ident, ToTokens}; | 4 use quote::{format_ident, ToTokens}; |
| 4 use std::path::PathBuf; | 5 use std::path::Path; |
| 6 use std::process::Command; | |
| 7 use std::str::FromStr; | |
| 5 use std::{env, fs}; | 8 use std::{env, fs}; |
| 6 use syn::{Item, ItemConst, Type, TypePath}; | 9 use syn::{Item, ItemConst, Type, TypePath}; |
| 7 | 10 |
| 8 // We're using the macro directly so we can match exhaustively. | 11 // We're using the macro directly so we can match exhaustively. |
| 9 __pam_impl_enum__!(); | 12 __pam_impl_enum__!(); |
| 44 ignore_consts: vec!["PAM_CRED_PRELIM_CHECK"], | 47 ignore_consts: vec!["PAM_CRED_PRELIM_CHECK"], |
| 45 ..Default::default() | 48 ..Default::default() |
| 46 }, | 49 }, |
| 47 }; | 50 }; |
| 48 generate_const_test(&config); | 51 generate_const_test(&config); |
| 52 generate_ctest(&config); | |
| 49 } | 53 } |
| 50 | 54 |
| 51 fn generate_const_test(config: &TestConfig) { | 55 fn generate_const_test(config: &TestConfig) { |
| 52 let mut builder = bindgen::Builder::default() | 56 let mut builder = bindgen::Builder::default() |
| 53 .header_contents("_.h", &config.header_contents()) | 57 .header_contents("_.h", &config.header_contents()) |
| 61 builder = builder.blocklist_file(".*?/".to_owned() + hdr) | 65 builder = builder.blocklist_file(".*?/".to_owned() + hdr) |
| 62 } | 66 } |
| 63 | 67 |
| 64 let generated = builder.generate().unwrap().to_string(); | 68 let generated = builder.generate().unwrap().to_string(); |
| 65 let file = syn::parse_file(&generated).unwrap(); | 69 let file = syn::parse_file(&generated).unwrap(); |
| 66 let mut tests = vec![]; | 70 let mut tests = vec![ |
| 67 tests.push("{".into()); | 71 "use libpam_sys::*;".into(), |
| 72 "#[allow(deprecated, overflowing_literals)]".into(), | |
| 73 "fn main() {".into(), | |
| 74 format!( | |
| 75 "assert_eq!(PamImpl::CURRENT, PamImpl::{:?});", | |
| 76 PamImpl::CURRENT | |
| 77 ), | |
| 78 ]; | |
| 68 tests.extend( | 79 tests.extend( |
| 69 file.items | 80 file.items |
| 70 .iter() | 81 .iter() |
| 71 .filter_map(|item| { | 82 .filter_map(|item| { |
| 72 if let Item::Const(item) = item { | 83 if let Item::Const(item) = item { |
| 81 item.ty = Box::new(Type::Path(TypePath { | 92 item.ty = Box::new(Type::Path(TypePath { |
| 82 qself: None, | 93 qself: None, |
| 83 path: format_ident!("i32").into(), | 94 path: format_ident!("i32").into(), |
| 84 })); | 95 })); |
| 85 format!( | 96 format!( |
| 86 "assert_eq!({tokens}, libpam_sys::{name});", | 97 "assert_eq!({tokens}, {name});", |
| 87 tokens = item.expr.to_token_stream(), | 98 tokens = item.expr.to_token_stream(), |
| 88 name = item.ident | 99 name = item.ident |
| 89 ) | 100 ) |
| 90 }), | 101 }), |
| 91 ); | 102 ); |
| 92 tests.push("}".into()); | 103 tests.push("}".into()); |
| 93 fs::write( | 104 let const_test = test_file("constant_test.rs"); |
| 94 PathBuf::from(env::var("OUT_DIR").unwrap()).join("constant_test.rs"), | 105 fs::write(&const_test, tests.join("\n")).unwrap(); |
| 95 tests.join("\n"), | 106 rustfmt(&const_test); |
| 107 } | |
| 108 | |
| 109 fn generate_ctest(config: &TestConfig) { | |
| 110 let mut test = ctest::TestGenerator::new(); | |
| 111 | |
| 112 for header in config.headers.iter() { | |
| 113 if header.starts_with('"') { | |
| 114 test.include(env::var("CARGO_MANIFEST_DIR").unwrap()); | |
| 115 } | |
| 116 test.header(&header[1..header.len() - 1]); | |
| 117 } | |
| 118 // These are opaque structs. | |
| 119 test.skip_struct(|name| matches!(name, "pam_handle" | "AppData")); | |
| 120 test.skip_type(|name| matches!(name, "ConversationCallback" | "CleanupCallback")); | |
| 121 test.type_name(|name, _is_struct, is_union| { | |
| 122 assert!(!is_union); // we scabbin' | |
| 123 match name { | |
| 124 "pam_handle" => "struct pam_handle", | |
| 125 "pam_conv" => "struct pam_conv", | |
| 126 "pam_message" => "struct pam_message", | |
| 127 "pam_response" => "struct pam_response", | |
| 128 "AppData" => "void", | |
| 129 other => other, | |
| 130 } | |
| 131 .into() | |
| 132 }); | |
| 133 | |
| 134 // | |
| 135 // Welcome to THE HACK ZONE. | |
| 136 // | |
| 137 | |
| 138 // Define away constness because the various PAM implementations | |
| 139 // have different const annotations and this will surely drive you crazy. | |
| 140 test.define("const", Some("")); | |
| 141 | |
| 142 // Also replace all the `const`s with `mut`s in the ffi.rs file. | |
| 143 let file_contents = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../src/ffi.rs")); | |
| 144 let deconsted_file = test_file("ffi.rs"); | |
| 145 remove_consts(file_contents, &deconsted_file); | |
| 146 | |
| 147 test.generate(&deconsted_file, "ctest.rs"); | |
| 148 } | |
| 149 | |
| 150 fn remove_consts(file_contents: &str, out_file: impl AsRef<Path>) { | |
| 151 let deconstified = deconstify( | |
| 152 TokenStream::from_str(file_contents).unwrap(), | |
| 153 &TokenStream::from_str("mut") | |
| 154 .unwrap() | |
| 155 .into_iter() | |
| 156 .next() | |
| 157 .unwrap(), | |
| 96 ) | 158 ) |
| 97 .unwrap(); | 159 .to_string(); |
| 160 let out_file = out_file.as_ref(); | |
| 161 fs::write(out_file, deconstified).unwrap(); | |
| 162 rustfmt(out_file) | |
| 163 } | |
| 164 | |
| 165 fn rustfmt(file: impl AsRef<Path>) { | |
| 166 let status = Command::new(env!("CARGO")) | |
| 167 .args(["fmt", "--", file.as_ref().to_str().unwrap()]) | |
| 168 .status() | |
| 169 .unwrap(); | |
| 170 assert!(status.success(), "rustfmt exited with code {status}"); | |
| 171 } | |
| 172 | |
| 173 fn deconstify(stream: TokenStream, mut_token: &TokenTree) -> TokenStream { | |
| 174 TokenStream::from_iter(stream.into_iter().map(|token| { | |
| 175 match token { | |
| 176 TokenTree::Group(g) => { | |
| 177 TokenTree::Group(Group::new(g.delimiter(), deconstify(g.stream(), mut_token))) | |
| 178 .into_token_stream() | |
| 179 } | |
| 180 TokenTree::Ident(id) if id == "const" => mut_token.into_token_stream(), | |
| 181 other => other.into_token_stream(), | |
| 182 } | |
| 183 })) | |
| 184 } | |
| 185 | |
| 186 fn test_file(name: impl AsRef<str>) -> String { | |
| 187 format!("{}/{}", env::var("OUT_DIR").unwrap(), name.as_ref()) | |
| 98 } | 188 } |
| 99 | 189 |
| 100 #[derive(Default)] | 190 #[derive(Default)] |
| 101 struct TestConfig { | 191 struct TestConfig { |
| 102 headers: Vec<&'static str>, | 192 headers: Vec<&'static str>, |
