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