1use crate::{
2 CheckCtx, PathBuf,
3 cfg::ValidationContext,
4 diag::{self, ErrorSink, FileId, Files, KrateSpans, PackChannel},
5};
6
7#[derive(Default, Clone)]
8pub struct KrateGather<'k> {
9 pub name: &'k str,
10 pub features: &'k [&'k str],
11 pub targets: &'k [&'k str],
12 pub all_features: bool,
13 pub no_default_features: bool,
14}
15
16impl<'k> KrateGather<'k> {
17 pub fn new(name: &'k str) -> Self {
18 Self {
19 name,
20 ..Default::default()
21 }
22 }
23
24 pub fn gather(self) -> crate::Krates {
25 let mut project_dir = crate::PathBuf::from("./tests/test_data");
26 project_dir.push(self.name);
27
28 let mut cmd = krates::Cmd::new();
29 cmd.current_dir(project_dir);
30
31 if self.all_features {
32 cmd.all_features();
33 }
34
35 if self.no_default_features {
36 cmd.no_default_features();
37 }
38
39 if !self.features.is_empty() {
40 cmd.features(self.features.iter().map(|f| (*f).to_owned()));
41 }
42
43 let mut kb = krates::Builder::new();
44
45 if !self.targets.is_empty() {
46 kb.include_targets(self.targets.iter().map(|t| (t, vec![])));
47 }
48
49 kb.build(cmd, krates::NoneFilter)
50 .expect("failed to build crate graph")
51 }
52}
53
54pub struct Config<C> {
55 pub deserialized: C,
56 pub config: String,
57}
58
59impl<C> Default for Config<C>
60where
61 C: Default,
62{
63 fn default() -> Self {
64 Self {
65 deserialized: C::default(),
66 config: "".to_owned(),
67 }
68 }
69}
70
71impl<C> Config<C>
72where
73 C: toml_span::DeserializeOwned,
74{
75 pub fn new(config: impl Into<String>) -> Self {
76 let config = config.into();
77 let mut val = toml_span::parse(&config).expect("failed to parse test config");
78 let deserialized = C::deserialize(&mut val).expect("failed to deserialize test config");
79 Self {
80 deserialized,
81 config,
82 }
83 }
84}
85
86impl<'de, C> From<&'de str> for Config<C>
87where
88 C: toml_span::DeserializeOwned,
89{
90 fn from(s: &'de str) -> Self {
91 Self::new(s)
92 }
93}
94
95impl<C> From<String> for Config<C>
96where
97 C: toml_span::DeserializeOwned + Default,
98{
99 fn from(s: String) -> Self {
100 Self::new(s)
101 }
102}
103
104pub struct ConfigData<T> {
105 pub config: T,
106 pub files: Files,
107 pub id: FileId,
108}
109
110impl<T> ConfigData<T> {
111 pub fn file(&self) -> &str {
112 self.files.source(self.id)
113 }
114}
115
116impl<T> ConfigData<T>
117where
118 T: toml_span::DeserializeOwned,
119{
120 pub fn load_str(name: impl Into<crate::PathBuf>, contents: impl Into<String>) -> Self {
121 let contents: String = contents.into();
122
123 let res = {
124 let mut cval = toml_span::parse(&contents).expect("failed to parse toml");
125 T::deserialize(&mut cval)
126 };
127
128 let mut files = Files::new();
129 let id = files.add(name, contents);
130
131 let config = match res {
132 Ok(v) => v,
133 Err(derr) => {
134 let diag_str = write_diagnostics(
135 &files,
136 derr.errors.into_iter().map(|err| err.to_diagnostic(id)),
137 );
138 panic!("failed to deserialize:\n---\n{diag_str}\n---");
139 }
140 };
141
142 ConfigData { config, files, id }
143 }
144
145 pub fn load(path: impl Into<PathBuf>) -> Self {
146 let path = path.into();
147 let contents = std::fs::read_to_string(&path).unwrap();
148
149 Self::load_str(path, contents)
150 }
151}
152
153impl<T> ConfigData<T> {
154 pub fn validate<IV, V>(mut self, conv: impl FnOnce(T) -> IV) -> V
155 where
156 IV: super::UnvalidatedConfig<ValidCfg = V>,
157 {
158 let uvc = conv(self.config);
159
160 let mut diagnostics = Vec::new();
161 let vcfg = uvc.validate(ValidationContext {
162 cfg_id: self.id,
163 files: &mut self.files,
164 diagnostics: &mut diagnostics,
165 });
166
167 if diagnostics.is_empty() {
168 vcfg
169 } else {
170 let diag_str = write_diagnostics(&self.files, diagnostics.into_iter());
171
172 panic!("failed to validate config:\n---\n{diag_str}\n---");
173 }
174 }
175
176 pub fn validate_with_diags<IV, V>(
177 mut self,
178 conv: impl FnOnce(T) -> IV,
179 on_diags: impl FnOnce(&Files, Vec<crate::diag::Diagnostic>),
180 ) -> V
181 where
182 IV: super::UnvalidatedConfig<ValidCfg = V>,
183 {
184 let uvc = conv(self.config);
185
186 let mut diagnostics = Vec::new();
187 let vcfg = uvc.validate(ValidationContext {
188 cfg_id: self.id,
189 files: &mut self.files,
190 diagnostics: &mut diagnostics,
191 });
192
193 on_diags(&self.files, diagnostics);
194 vcfg
195 }
196}
197
198pub(crate) fn write_diagnostics(
199 files: &Files,
200 errors: impl Iterator<Item = crate::diag::Diagnostic>,
201) -> String {
202 let mut s = codespan_reporting::term::termcolor::NoColor::new(Vec::new());
203 let config = crate::diag::codespan_config();
204
205 for diag in errors {
206 codespan_reporting::term::emit(&mut s, &config, files, &diag).unwrap();
207 }
208
209 String::from_utf8(s.into_inner()).unwrap()
210}
211
212#[inline]
213pub fn gather_diagnostics<C, VC, R>(
214 krates: &crate::Krates,
215 test_name: &str,
216 cfg: Config<C>,
217 runner: R,
218) -> Vec<serde_json::Value>
219where
220 C: crate::UnvalidatedConfig<ValidCfg = VC>,
221 VC: Send,
222 R: FnOnce(CheckCtx<'_, VC>, PackChannel) + Send,
223{
224 let ctx = setup(krates, test_name, cfg);
225 run_gather(ctx, runner)
226}
227
228pub struct GatherCtx<'k, VC> {
229 pub krates: &'k crate::Krates,
230 pub files: crate::diag::Files,
231 pub valid_cfg: VC,
232 pub spans: crate::diag::KrateSpans<'k>,
233}
234
235pub fn setup<'k, C, VC>(
236 krates: &'k crate::Krates,
237 test_name: &str,
238 cfg: Config<C>,
239) -> GatherCtx<'k, VC>
240where
241 C: crate::UnvalidatedConfig<ValidCfg = VC>,
242 VC: Send,
243{
244 let mut files = crate::diag::Files::new();
245 let spans = KrateSpans::synthesize(krates, test_name, &mut files);
246
247 let config = cfg.deserialized;
248 let cfg_id = files.add(format!("{test_name}.toml"), cfg.config);
249
250 let mut cfg_diags = Vec::new();
251 let valid_cfg = config.validate(crate::cfg::ValidationContext {
252 cfg_id,
253 files: &mut files,
254 diagnostics: &mut cfg_diags,
255 });
256
257 if cfg_diags
258 .iter()
259 .any(|d| d.severity >= crate::diag::Severity::Error)
260 {
261 panic!("encountered errors validating config: {cfg_diags:#?}");
262 }
263
264 GatherCtx {
265 krates,
266 files,
267 valid_cfg,
268 spans,
269 }
270}
271
272pub fn run_gather<VC, R>(ctx: GatherCtx<'_, VC>, runner: R) -> Vec<serde_json::Value>
273where
274 VC: Send,
275 R: FnOnce(CheckCtx<'_, VC>, PackChannel) + Send,
276{
277 let (tx, rx) = crossbeam::channel::unbounded();
278
279 let grapher = diag::InclusionGrapher::new(ctx.krates);
280
281 let (_, gathered) = rayon::join(
282 || {
283 let cctx = crate::CheckCtx {
284 krates: ctx.krates,
285 krate_spans: &ctx.spans,
286 cfg: ctx.valid_cfg,
287 serialize_extra: true,
288 colorize: false,
289 log_level: log::LevelFilter::Info,
290 files: &ctx.files,
291 };
292 runner(cctx, tx);
293 },
294 || {
295 let mut diagnostics = Vec::new();
296
297 let default = if std::env::var_os("CI").is_some() {
298 60
299 } else {
300 30
301 };
302
303 let timeout = std::env::var("CARGO_DENY_TEST_TIMEOUT_SECS")
304 .ok()
305 .and_then(|ts| ts.parse().ok())
306 .unwrap_or(default);
307 let timeout = std::time::Duration::from_secs(timeout);
308
309 let trx = crossbeam::channel::after(timeout);
310 loop {
311 crossbeam::select! {
312 recv(rx) -> msg => {
313 if let Ok(pack) = msg {
314 diagnostics.extend(pack);
315 } else {
316 break;
318 }
319 }
320 recv(trx) -> _ => {
321 anyhow::bail!("Timed out after {timeout:?}");
322 }
323 }
324 }
325
326 Ok(diagnostics)
327 },
328 );
329
330 gathered
331 .unwrap()
332 .into_iter()
333 .map(|d| diag::diag_to_json(d, &ctx.files, Some(&grapher)))
334 .collect()
335}
336
337#[macro_export]
338macro_rules! field_eq {
339 ($obj:expr, $field:expr, $expected:expr) => {
340 $obj.pointer($field) == Some(&serde_json::json!($expected))
341 };
342}
343
344#[macro_export]
345macro_rules! assert_field_eq {
346 ($obj:expr, $field:expr, $expected:expr) => {
347 assert_eq!($obj.pointer($field), Some(&serde_json::json!($expected)));
348 };
349}
350
351#[macro_export]
352macro_rules! func_name {
353 () => {{
354 fn f() {}
355 fn type_name_of<T>(_: T) -> &'static str {
356 std::any::type_name::<T>()
357 }
358 let name = type_name_of(f);
359 &name[..name.len() - 3]
360 }};
361}
362
363#[macro_export]
364macro_rules! overrides {
365 ($($code:expr => $severity:ident),* $(,)?) => {
366 {
367 let mut map = std::collections::BTreeMap::new();
368
369 $(map.insert($code, $crate::diag::Severity::$severity);)*
370
371 $crate::diag::DiagnosticOverrides {
372 code_overrides: map,
373 level_overrides: Vec::new(),
374 }
375 }
376 }
377}
378
379#[inline]
380pub fn gather_bans(
381 name: &str,
382 kg: KrateGather<'_>,
383 cfg: impl Into<Config<crate::bans::cfg::Config>>,
384) -> Vec<serde_json::Value> {
385 let krates = kg.gather();
386 let cfg = cfg.into();
387
388 gather_diagnostics::<crate::bans::cfg::Config, _, _>(&krates, name, cfg, |ctx, tx| {
389 crate::bans::check(ctx, None, tx);
390 })
391}
392
393#[inline]
394pub fn gather_bans_with_overrides(
395 name: &str,
396 kg: KrateGather<'_>,
397 cfg: impl Into<Config<crate::bans::cfg::Config>>,
398 overrides: diag::DiagnosticOverrides,
399) -> Vec<serde_json::Value> {
400 let krates = kg.gather();
401 let cfg = cfg.into();
402
403 gather_diagnostics::<crate::bans::cfg::Config, _, _>(&krates, name, cfg, |ctx, tx| {
404 crate::bans::check(
405 ctx,
406 None,
407 ErrorSink {
408 overrides: Some(std::sync::Arc::new(overrides)),
409 channel: tx,
410 },
411 );
412 })
413}