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
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>,