1use crate::{
5 dep_helpers::{
6 assert_all_links, assert_deps_internal, assert_topo_ids, assert_topo_metadatas,
7 assert_transitive_deps_internal,
8 },
9 package_id,
10};
11use ahash::AHashMap;
12use camino::Utf8PathBuf;
13use guppy::{
14 errors::FeatureGraphWarning,
15 graph::{
16 BuildTargetId, BuildTargetKind, DependencyDirection, EnabledStatus, PackageGraph,
17 PackageLink, PackageMetadata, PackageSource, Workspace,
18 },
19 platform::{EnabledTernary, Platform, PlatformSpec},
20 DependencyKind, PackageId, Version,
21};
22use pretty_assertions::assert_eq;
23use std::collections::BTreeMap;
24
25pub struct FixtureDetails {
28 workspace_members: Option<BTreeMap<Utf8PathBuf, PackageId>>,
29 package_details: AHashMap<PackageId, PackageDetails>,
30 link_details: AHashMap<(PackageId, PackageId), LinkDetails>,
31 feature_graph_warnings: Vec<FeatureGraphWarning>,
32 cycles: Vec<Vec<PackageId>>,
33}
34
35impl FixtureDetails {
36 pub fn new(package_details: AHashMap<PackageId, PackageDetails>) -> Self {
37 Self {
38 workspace_members: None,
39 package_details,
40 link_details: AHashMap::new(),
41 feature_graph_warnings: vec![],
42 cycles: vec![],
43 }
44 }
45
46 pub fn with_workspace_members<'a>(
47 mut self,
48 workspace_members: impl IntoIterator<Item = (impl Into<Utf8PathBuf>, &'a str)>,
49 ) -> Self {
50 self.workspace_members = Some(
51 workspace_members
52 .into_iter()
53 .map(|(path, id)| (path.into(), package_id(id)))
54 .collect(),
55 );
56 self
57 }
58
59 pub fn with_link_details(
60 mut self,
61 link_details: AHashMap<(PackageId, PackageId), LinkDetails>,
62 ) -> Self {
63 self.link_details = link_details;
64 self
65 }
66
67 pub fn with_feature_graph_warnings(mut self, mut warnings: Vec<FeatureGraphWarning>) -> Self {
68 warnings.sort();
69 self.feature_graph_warnings = warnings;
70 self
71 }
72
73 pub fn with_cycles(mut self, cycles: Vec<Vec<&'static str>>) -> Self {
74 let cycles: Vec<_> = cycles
75 .into_iter()
76 .map(|cycle| cycle.into_iter().map(package_id).collect())
77 .collect();
78 self.cycles = cycles;
81 self
82 }
83
84 pub fn known_ids(&self) -> impl Iterator<Item = &PackageId> {
85 self.package_details.keys()
86 }
87
88 pub fn assert_workspace(&self, workspace: Workspace) {
89 if let Some(expected_members) = &self.workspace_members {
90 let members: Vec<_> = workspace
91 .iter_by_path()
92 .map(|(path, metadata)| (path, metadata.id()))
93 .collect();
94 assert_eq!(
95 expected_members
96 .iter()
97 .map(|(path, id)| (path.as_path(), id))
98 .collect::<Vec<_>>(),
99 members,
100 "workspace members should be correct"
101 );
102
103 assert_eq!(
104 workspace.iter_by_path().len(),
105 workspace.iter_by_name().len(),
106 "workspace.members() and members_by_name() return the same number of items"
107 );
108 for (name, metadata) in workspace.iter_by_name() {
109 assert_eq!(
110 name,
111 metadata.name(),
112 "members_by_name returns consistent results"
113 );
114 }
115 }
116 }
117
118 pub fn assert_topo(&self, graph: &PackageGraph) {
119 assert_topo_ids(graph, DependencyDirection::Forward, "topo sort");
120 assert_topo_ids(graph, DependencyDirection::Reverse, "reverse topo sort");
121 assert_topo_metadatas(graph, DependencyDirection::Forward, "topo sort (metadatas)");
122 assert_topo_metadatas(
123 graph,
124 DependencyDirection::Reverse,
125 "reverse topo sort (metadatas)",
126 );
127 assert_all_links(graph, DependencyDirection::Forward, "all links");
128 assert_all_links(graph, DependencyDirection::Reverse, "all links reversed");
129 }
130
131 pub fn assert_metadata(&self, id: &PackageId, metadata: PackageMetadata<'_>, msg: &str) {
132 let details = &self.package_details[id];
133 details.assert_metadata(metadata, msg);
134 }
135
136 pub fn has_build_targets(&self, id: &PackageId) -> bool {
141 let details = &self.package_details[id];
142 details.build_targets.is_some()
143 }
144
145 pub fn assert_build_targets(&self, metadata: PackageMetadata<'_>, msg: &str) {
146 let build_targets = self.package_details[metadata.id()]
147 .build_targets
148 .as_ref()
149 .unwrap();
150
151 let mut actual: Vec<_> = metadata
152 .build_targets()
153 .map(|build_target| {
154 let path = build_target
156 .path()
157 .strip_prefix(
158 metadata
159 .manifest_path()
160 .parent()
161 .expect("manifest path is a file"),
162 )
163 .expect("build target path is inside source dir")
164 .to_path_buf();
165
166 (build_target.id(), build_target.kind().clone(), path)
167 })
168 .collect();
169 actual.sort();
170
171 assert_eq!(build_targets, &actual, "{}: build targets match", msg,);
172 }
173
174 pub fn has_deps(&self, id: &PackageId) -> bool {
180 let details = &self.package_details[id];
181 details.deps.is_some()
182 }
183
184 pub fn assert_deps(&self, graph: &PackageGraph, id: &PackageId, msg: &str) {
185 let details = &self.package_details[id];
186 assert_deps_internal(graph, DependencyDirection::Forward, details, msg);
187 }
188
189 pub fn has_reverse_deps(&self, id: &PackageId) -> bool {
191 let details = &self.package_details[id];
192 details.reverse_deps.is_some()
193 }
194
195 pub fn assert_reverse_deps(&self, graph: &PackageGraph, id: &PackageId, msg: &str) {
196 let details = &self.package_details[id];
197 assert_deps_internal(graph, DependencyDirection::Reverse, details, msg);
198 }
199
200 pub fn has_transitive_deps(&self, id: &PackageId) -> bool {
206 let details = &self.package_details[id];
207 details.transitive_deps.is_some()
208 }
209
210 pub fn assert_transitive_deps(&self, graph: &PackageGraph, id: &PackageId, msg: &str) {
211 assert_transitive_deps_internal(
212 graph,
213 DependencyDirection::Forward,
214 &self.package_details[id],
215 msg,
216 )
217 }
218
219 pub fn has_transitive_reverse_deps(&self, id: &PackageId) -> bool {
221 let details = &self.package_details[id];
222 details.transitive_reverse_deps.is_some()
223 }
224
225 pub fn assert_transitive_reverse_deps(&self, graph: &PackageGraph, id: &PackageId, msg: &str) {
226 assert_transitive_deps_internal(
227 graph,
228 DependencyDirection::Reverse,
229 &self.package_details[id],
230 msg,
231 )
232 }
233
234 pub fn assert_link_details(&self, graph: &PackageGraph, msg: &str) {
239 for ((from, to), details) in &self.link_details {
240 let metadata = graph
241 .metadata(from)
242 .unwrap_or_else(|err| panic!("{}: {}", msg, err));
243 let mut links: Vec<_> = metadata
244 .direct_links()
245 .filter(|link| link.to().id() == to)
246 .collect();
247 assert_eq!(
248 links.len(),
249 1,
250 "{}: exactly 1 link between '{}' and '{}'",
251 msg,
252 from,
253 to
254 );
255
256 let link = links.pop().unwrap();
257 let msg = format!("{}: {} -> {}", msg, from, to);
258 details.assert_metadata(link, &msg);
259 }
260 }
261
262 pub fn has_named_features(&self, id: &PackageId) -> bool {
267 self.package_details[id].named_features.is_some()
268 }
269
270 pub fn assert_named_features(&self, graph: &PackageGraph, id: &PackageId, msg: &str) {
271 let mut actual: Vec<_> = graph
272 .metadata(id)
273 .expect("package id should be valid")
274 .named_features()
275 .collect();
276 actual.sort_unstable();
277 let expected = self.package_details[id].named_features.as_ref().unwrap();
278 assert_eq!(expected, &actual, "{}", msg);
279 }
280
281 pub fn assert_feature_graph_warnings(&self, graph: &PackageGraph, msg: &str) {
282 let mut actual: Vec<_> = graph.feature_graph().build_warnings().to_vec();
283 actual.sort();
284 assert_eq!(&self.feature_graph_warnings, &actual, "{}", msg);
285 }
286
287 pub fn assert_cycles(&self, graph: &PackageGraph, msg: &str) {
292 let actual: Vec<_> = graph.cycles().all_cycles().collect();
293 assert_eq!(&self.cycles, &actual, "{}", msg);
296
297 let mut cache = graph.new_depends_cache();
298
299 for cycle in actual {
300 for &id1 in &cycle {
301 for &id2 in &cycle {
302 assert!(
303 graph.depends_on(id1, id2).expect("valid package IDs"),
304 "{}: within cycle, {} depends on {}",
305 msg,
306 id1,
307 id2
308 );
309 assert!(
310 cache.depends_on(id1, id2).expect("valid package IDs"),
311 "{}: within cycle, {} depends on {} (using cache)",
312 msg,
313 id1,
314 id2
315 )
316 }
317 }
318 }
319
320 let _: Vec<_> = graph.feature_graph().cycles().all_cycles().collect();
322 }
323}
324
325pub struct PackageDetails {
326 id: PackageId,
327 name: &'static str,
328 version: Version,
329 authors: Vec<&'static str>,
330 description: Option<&'static str>,
331 license: Option<&'static str>,
332
333 source: Option<PackageSource<'static>>,
334 build_targets: Option<
335 Vec<(
336 BuildTargetId<'static>,
337 BuildTargetKind<'static>,
338 Utf8PathBuf,
339 )>,
340 >,
341 deps: Option<Vec<(&'static str, PackageId)>>,
344 reverse_deps: Option<Vec<(&'static str, PackageId)>>,
345 transitive_deps: Option<Vec<PackageId>>,
346 transitive_reverse_deps: Option<Vec<PackageId>>,
347 named_features: Option<Vec<&'static str>>,
348}
349
350impl PackageDetails {
351 pub fn new(
352 id: &'static str,
353 name: &'static str,
354 version: &'static str,
355 authors: Vec<&'static str>,
356 description: Option<&'static str>,
357 license: Option<&'static str>,
358 ) -> Self {
359 Self {
360 id: package_id(id),
361 name,
362 version: Version::parse(version).expect("version should be valid"),
363 authors,
364 description,
365 license,
366 source: None,
367 build_targets: None,
368 deps: None,
369 reverse_deps: None,
370 transitive_deps: None,
371 transitive_reverse_deps: None,
372 named_features: None,
373 }
374 }
375
376 pub fn with_workspace_path(mut self, path: &'static str) -> Self {
377 self.source = Some(PackageSource::Workspace(path.into()));
378 self
379 }
380
381 pub fn with_local_path(mut self, path: &'static str) -> Self {
382 self.source = Some(PackageSource::Path(path.into()));
383 self
384 }
385
386 pub fn with_crates_io(self) -> Self {
387 self.with_external_source(PackageSource::CRATES_IO_REGISTRY)
388 }
389
390 pub fn with_external_source(mut self, source: &'static str) -> Self {
391 self.source = Some(PackageSource::External(source));
392 self
393 }
394
395 pub fn with_build_targets(
396 mut self,
397 mut build_targets: Vec<(
398 BuildTargetId<'static>,
399 BuildTargetKind<'static>,
400 &'static str,
401 )>,
402 ) -> Self {
403 build_targets.sort();
404 self.build_targets = Some(
405 build_targets
406 .into_iter()
407 .map(|(id, kind, path)| (id, kind, path.to_string().into()))
408 .collect(),
409 );
410 self
411 }
412
413 pub fn with_deps(mut self, mut deps: Vec<(&'static str, &'static str)>) -> Self {
414 deps.sort_unstable();
415 self.deps = Some(
416 deps.into_iter()
417 .map(|(name, id)| (name, package_id(id)))
418 .collect(),
419 );
420 self
421 }
422
423 pub fn with_reverse_deps(
424 mut self,
425 mut reverse_deps: Vec<(&'static str, &'static str)>,
426 ) -> Self {
427 reverse_deps.sort_unstable();
428 self.reverse_deps = Some(
429 reverse_deps
430 .into_iter()
431 .map(|(name, id)| (name, package_id(id)))
432 .collect(),
433 );
434 self
435 }
436
437 pub fn with_transitive_deps(mut self, mut transitive_deps: Vec<&'static str>) -> Self {
438 transitive_deps.sort_unstable();
439 self.transitive_deps = Some(transitive_deps.into_iter().map(package_id).collect());
440 self
441 }
442
443 pub fn with_transitive_reverse_deps(
444 mut self,
445 mut transitive_reverse_deps: Vec<&'static str>,
446 ) -> Self {
447 transitive_reverse_deps.sort_unstable();
448 self.transitive_reverse_deps = Some(
449 transitive_reverse_deps
450 .into_iter()
451 .map(package_id)
452 .collect(),
453 );
454 self
455 }
456
457 pub fn with_named_features(mut self, mut named_features: Vec<&'static str>) -> Self {
458 named_features.sort_unstable();
459 self.named_features = Some(named_features);
460 self
461 }
462
463 pub fn insert_into(self, map: &mut AHashMap<PackageId, PackageDetails>) {
464 map.insert(self.id.clone(), self);
465 }
466
467 pub fn id(&self) -> &PackageId {
468 &self.id
469 }
470
471 pub fn deps(&self, direction: DependencyDirection) -> Option<&[(&'static str, PackageId)]> {
472 match direction {
473 DependencyDirection::Forward => self.deps.as_deref(),
474 DependencyDirection::Reverse => self.reverse_deps.as_deref(),
475 }
476 }
477
478 pub fn transitive_deps(&self, direction: DependencyDirection) -> Option<&[PackageId]> {
479 match direction {
480 DependencyDirection::Forward => self.transitive_deps.as_deref(),
481 DependencyDirection::Reverse => self.transitive_reverse_deps.as_deref(),
482 }
483 }
484
485 pub fn assert_metadata(&self, metadata: PackageMetadata<'_>, msg: &str) {
486 assert_eq!(&self.id, metadata.id(), "{}: same package ID", msg);
487 assert_eq!(self.name, metadata.name(), "{}: same name", msg);
488 assert_eq!(&self.version, metadata.version(), "{}: same version", msg);
489 assert_eq!(
490 &self.authors,
491 &metadata
492 .authors()
493 .iter()
494 .map(|author| author.as_str())
495 .collect::<Vec<_>>(),
496 "{}: same authors",
497 msg
498 );
499 assert_eq!(
500 &self.description,
501 &metadata.description(),
502 "{}: same description",
503 msg
504 );
505 assert_eq!(&self.license, &metadata.license(), "{}: same license", msg);
506 if let Some(source) = &self.source {
507 assert_eq!(source, &metadata.source(), "{}: same source", msg);
508 }
509 }
510}
511
512#[derive(Clone, Debug)]
513pub struct LinkDetails {
514 from: PackageId,
515 to: PackageId,
516 platform_results: Vec<(DependencyKind, Platform, PlatformResults)>,
517 features: Vec<(DependencyKind, Vec<&'static str>)>,
518}
519
520impl LinkDetails {
521 pub fn new(from: PackageId, to: PackageId) -> Self {
522 Self {
523 from,
524 to,
525 platform_results: vec![],
526 features: vec![],
527 }
528 }
529
530 pub fn with_platform_status(
531 mut self,
532 dep_kind: DependencyKind,
533 platform: Platform,
534 status: PlatformResults,
535 ) -> Self {
536 self.platform_results.push((dep_kind, platform, status));
537 self
538 }
539
540 pub fn with_features(
541 mut self,
542 dep_kind: DependencyKind,
543 mut features: Vec<&'static str>,
544 ) -> Self {
545 features.sort_unstable();
546 self.features.push((dep_kind, features));
547 self
548 }
549
550 pub fn insert_into(self, map: &mut AHashMap<(PackageId, PackageId), Self>) {
551 map.insert((self.from.clone(), self.to.clone()), self);
552 }
553
554 pub fn assert_metadata(&self, link: PackageLink<'_>, msg: &str) {
555 let required_enabled = |status: EnabledStatus<'_>, platform_spec: &PlatformSpec| {
556 (
557 status.required_on(platform_spec),
558 status.enabled_on(platform_spec),
559 )
560 };
561
562 for (dep_kind, platform, results) in &self.platform_results {
563 let platform_spec = platform.clone().into();
564 let req = link.req_for_kind(*dep_kind);
565 assert_eq!(
566 required_enabled(req.status(), &platform_spec),
567 results.status,
568 "{}: for platform '{}', kind {}, status is correct",
569 msg,
570 platform.triple_str(),
571 dep_kind,
572 );
573 assert_eq!(
574 required_enabled(req.default_features(), &platform_spec),
575 results.default_features,
576 "{}: for platform '{}', kind {}, default features is correct",
577 msg,
578 platform.triple_str(),
579 dep_kind,
580 );
581 for (feature, status) in &results.feature_statuses {
582 assert_eq!(
583 required_enabled(req.feature_status(feature), &platform_spec),
584 *status,
585 "{}: for platform '{}', kind {}, feature '{}' has correct status",
586 msg,
587 platform.triple_str(),
588 dep_kind,
589 feature
590 );
591 }
592 }
593
594 for (dep_kind, features) in &self.features {
595 let metadata = link.req_for_kind(*dep_kind);
596 let mut actual_features: Vec<_> = metadata.features().collect();
597 actual_features.sort_unstable();
598 assert_eq!(&actual_features, features, "{}: features is correct", msg);
599 }
600 }
601}
602
603#[derive(Clone, Debug)]
604pub struct PlatformResults {
605 status: (EnabledTernary, EnabledTernary),
607 default_features: (EnabledTernary, EnabledTernary),
608 feature_statuses: AHashMap<String, (EnabledTernary, EnabledTernary)>,
609}
610
611impl PlatformResults {
612 pub fn new(
613 status: (EnabledTernary, EnabledTernary),
614 default_features: (EnabledTernary, EnabledTernary),
615 ) -> Self {
616 Self {
617 status,
618 default_features,
619 feature_statuses: AHashMap::new(),
620 }
621 }
622
623 pub fn with_feature_status(
624 mut self,
625 feature: &str,
626 status: (EnabledTernary, EnabledTernary),
627 ) -> Self {
628 self.feature_statuses.insert(feature.to_string(), status);
629 self
630 }
631}