fixtures/
dep_helpers.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::details::PackageDetails;
5use guppy::{
6    graph::{
7        feature::{FeatureGraph, FeatureId, FeatureMetadata, FeatureQuery, FeatureSet},
8        DependencyDirection, DependencyReq, PackageGraph, PackageLink, PackageLinkPtrs,
9        PackageMetadata, PackageQuery, PackageSet,
10    },
11    platform::PlatformSpec,
12    DependencyKind, Error, PackageId,
13};
14use pretty_assertions::assert_eq;
15use std::{
16    collections::{BTreeSet, HashSet},
17    fmt,
18    hash::Hash,
19    iter,
20};
21
22fn __from_metadata<'a>(link: &PackageLink<'a>) -> PackageMetadata<'a> {
23    link.from()
24}
25fn __to_metadata<'a>(link: &PackageLink<'a>) -> PackageMetadata<'a> {
26    link.to()
27}
28type LinkToMetadata<'a> = fn(&PackageLink<'a>) -> PackageMetadata<'a>;
29
30/// Some of the messages are different based on whether we're testing forward deps or reverse
31/// ones. For forward deps, we use the terms "known" for 'from' and "variable" for 'to'. For
32/// reverse deps it's the other way round.
33#[derive(Clone, Copy)]
34pub struct DirectionDesc<'a> {
35    direction_desc: &'static str,
36    known_desc: &'static str,
37    variable_desc: &'static str,
38    known_metadata: LinkToMetadata<'a>,
39    variable_metadata: LinkToMetadata<'a>,
40}
41
42impl<'a> DirectionDesc<'a> {
43    fn new(direction: DependencyDirection) -> Self {
44        match direction {
45            DependencyDirection::Forward => Self::forward(),
46            DependencyDirection::Reverse => Self::reverse(),
47        }
48    }
49
50    fn forward() -> Self {
51        Self {
52            direction_desc: "forward",
53            known_desc: "from",
54            variable_desc: "to",
55            known_metadata: __from_metadata as LinkToMetadata<'a>,
56            variable_metadata: __to_metadata as LinkToMetadata<'a>,
57        }
58    }
59
60    fn reverse() -> Self {
61        Self {
62            direction_desc: "reverse",
63            known_desc: "to",
64            variable_desc: "from",
65            known_metadata: __to_metadata as LinkToMetadata<'a>,
66            variable_metadata: __from_metadata as LinkToMetadata<'a>,
67        }
68    }
69
70    fn known_metadata(&self, dep: &PackageLink<'a>) -> PackageMetadata<'a> {
71        (self.known_metadata)(dep)
72    }
73
74    fn variable_metadata(&self, dep: &PackageLink<'a>) -> PackageMetadata<'a> {
75        (self.variable_metadata)(dep)
76    }
77}
78
79impl From<DependencyDirection> for DirectionDesc<'_> {
80    fn from(direction: DependencyDirection) -> Self {
81        Self::new(direction)
82    }
83}
84
85pub(crate) fn assert_deps_internal(
86    graph: &PackageGraph,
87    direction: DependencyDirection,
88    known_details: &PackageDetails,
89    msg: &str,
90) {
91    let desc = DirectionDesc::new(direction);
92
93    // Compare (dep_name, resolved_name, id) triples.
94    let expected_dep_ids: Vec<_> = known_details
95        .deps(direction)
96        .unwrap_or_else(|| {
97            panic!(
98                "{}: {} dependencies must be present",
99                msg, desc.direction_desc
100            )
101        })
102        .iter()
103        .map(|(dep_name, id)| (*dep_name, dep_name.replace('-', "_"), id))
104        .collect();
105    let actual_deps: Vec<_> = graph
106        .metadata(known_details.id())
107        .unwrap_or_else(|err| panic!("{}: {}", msg, err))
108        .direct_links_directed(direction)
109        .collect();
110    let mut actual_dep_ids: Vec<_> = actual_deps
111        .iter()
112        .map(|link| {
113            (
114                link.dep_name(),
115                link.resolved_name().to_string(),
116                desc.variable_metadata(link).id(),
117            )
118        })
119        .collect();
120    actual_dep_ids.sort();
121    assert_eq!(
122        expected_dep_ids, actual_dep_ids,
123        "{}: expected {} dependencies",
124        msg, desc.direction_desc,
125    );
126
127    for (_, _, dep_id) in &actual_dep_ids {
128        // depends_on should agree with the dependencies returned.
129        graph.assert_depends_on(known_details.id(), dep_id, direction, msg);
130        graph.assert_directly_depends_on(known_details.id(), dep_id, direction, msg);
131    }
132
133    // Check that the dependency metadata returned is consistent with what we expect.
134    let known_msg = format!(
135        "{}: {} dependency edge {} this package",
136        msg, desc.direction_desc, desc.known_desc
137    );
138    for actual_dep in &actual_deps {
139        known_details.assert_metadata(desc.known_metadata(actual_dep), &known_msg);
140        // XXX maybe compare version requirements?
141    }
142}
143
144pub(crate) fn assert_transitive_deps_internal(
145    graph: &PackageGraph,
146    direction: DependencyDirection,
147    known_details: &PackageDetails,
148    msg: &str,
149) {
150    let desc = DirectionDesc::new(direction);
151
152    let expected_dep_ids = known_details.transitive_deps(direction).unwrap_or_else(|| {
153        panic!(
154            "{}: {} transitive dependencies must be present",
155            msg, desc.direction_desc
156        )
157    });
158
159    let query = graph
160        .query_directed(iter::once(known_details.id()), direction)
161        .unwrap_or_else(|err| {
162            panic!(
163                "{}: {} transitive dep query failed: {}",
164                msg, desc.direction_desc, err
165            )
166        });
167    let package_set = query.resolve();
168
169    let package_ids = package_set.package_ids(direction);
170    let mut actual_dep_ids: Vec<_> = package_ids.collect();
171    actual_dep_ids.sort();
172
173    let actual_deps: Vec<_> = package_set.links(direction).collect();
174    let actual_ptrs = dep_link_ptrs(actual_deps.iter().copied());
175
176    // Use a BTreeSet for unique identifiers. This is also used later for set operations.
177    let ids_from_links_set: BTreeSet<_> = actual_deps
178        .iter()
179        .flat_map(|link| vec![link.from().id(), link.to().id()])
180        .collect();
181    let ids_from_links: Vec<_> = ids_from_links_set.iter().copied().collect();
182
183    assert_eq!(
184        expected_dep_ids,
185        actual_dep_ids.as_slice(),
186        "{}: expected {} transitive dependency IDs",
187        msg,
188        desc.direction_desc
189    );
190    assert_eq!(
191        expected_dep_ids,
192        ids_from_links.as_slice(),
193        "{}: expected {} transitive dependency infos",
194        msg,
195        desc.direction_desc
196    );
197
198    // The order requirements are weaker than topological -- for forward queries, a dep should show
199    // up at least once in 'to' before it ever shows up in 'from'.
200    assert_link_order(
201        actual_deps,
202        package_set.root_ids(direction),
203        desc,
204        &format!("{}: actual link order", msg),
205    );
206
207    // Do a query in the opposite direction as well to test link order.
208    let opposite = direction.opposite();
209    let opposite_desc = DirectionDesc::new(opposite);
210    let opposite_deps: Vec<_> = package_set.links(opposite).collect();
211    let opposite_ptrs = dep_link_ptrs(opposite_deps.iter().copied());
212
213    // Checking for pointer equivalence is enough since they both use the same graph as a base.
214    assert_eq!(
215        actual_ptrs, opposite_ptrs,
216        "{}: actual and opposite links should return the same pointer triples",
217        msg,
218    );
219
220    assert_link_order(
221        opposite_deps,
222        package_set.root_ids(opposite),
223        opposite_desc,
224        &format!("{}: opposite link order", msg),
225    );
226
227    for dep_id in expected_dep_ids {
228        // depends_on should agree with this.
229        graph.assert_depends_on(known_details.id(), dep_id, direction, msg);
230
231        // Transitive deps should be transitively closed.
232        let dep_actual_dep_ids: BTreeSet<_> = graph
233            .query_directed(iter::once(dep_id), direction)
234            .unwrap_or_else(|err| {
235                panic!(
236                    "{}: {} transitive dep id query failed for dependency '{}': {}",
237                    msg, desc.direction_desc, dep_id, err
238                )
239            })
240            .resolve()
241            .package_ids(direction)
242            .collect();
243        // Use difference instead of is_subset/is_superset for better error messages.
244        let difference: Vec<_> = dep_actual_dep_ids.difference(&ids_from_links_set).collect();
245        assert!(
246            difference.is_empty(),
247            "{}: unexpected extra {} transitive dependency IDs for dep '{}': {:?}",
248            msg,
249            desc.direction_desc,
250            dep_id,
251            difference
252        );
253
254        let dep_ids_from_links: BTreeSet<_> = graph
255            .query_directed(iter::once(dep_id), direction)
256            .unwrap_or_else(|err| {
257                panic!(
258                    "{}: {} transitive dep query failed for dependency '{}': {}",
259                    msg, desc.direction_desc, dep_id, err
260                )
261            })
262            .resolve()
263            .links(direction)
264            .flat_map(|dep| vec![dep.from().id(), dep.to().id()])
265            .collect();
266        // Use difference instead of is_subset/is_superset for better error messages.
267        let difference: Vec<_> = dep_ids_from_links.difference(&ids_from_links_set).collect();
268        assert!(
269            difference.is_empty(),
270            "{}: unexpected extra {} transitive dependencies for dep '{}': {:?}",
271            msg,
272            desc.direction_desc,
273            dep_id,
274            difference
275        );
276    }
277}
278
279pub(crate) fn assert_topo_ids(graph: &PackageGraph, direction: DependencyDirection, msg: &str) {
280    let all_set = graph.resolve_all();
281    let topo_ids = all_set.package_ids(direction);
282    assert_eq!(
283        topo_ids.len(),
284        graph.package_count(),
285        "{}: topo sort returns all packages",
286        msg
287    );
288
289    // A package that comes later cannot depend on one that comes earlier.
290    graph.assert_topo_order(topo_ids, direction, msg);
291}
292
293pub(crate) fn assert_topo_metadatas(
294    graph: &PackageGraph,
295    direction: DependencyDirection,
296    msg: &str,
297) {
298    let all_set = graph.resolve_all();
299    let topo_metadatas = all_set.packages(direction);
300    assert_eq!(
301        topo_metadatas.len(),
302        graph.package_count(),
303        "{}: topo sort returns all packages",
304        msg
305    );
306    let topo_ids = topo_metadatas.map(|metadata| metadata.id());
307
308    // A package that comes later cannot depend on one that comes earlier.
309    graph.assert_topo_order(topo_ids, direction, msg);
310}
311
312pub(crate) fn assert_all_links(graph: &PackageGraph, direction: DependencyDirection, msg: &str) {
313    let desc = DirectionDesc::new(direction);
314    let all_links: Vec<_> = graph.resolve_all().links(direction).collect();
315    assert_eq!(
316        all_links.len(),
317        graph.link_count(),
318        "{}: all links should be returned",
319        msg
320    );
321
322    // The enabled status can't be unknown on the current platform.
323    for link in &all_links {
324        for dep_kind in &[
325            DependencyKind::Normal,
326            DependencyKind::Build,
327            DependencyKind::Development,
328        ] {
329            assert_enabled_status_is_known(
330                link.req_for_kind(*dep_kind),
331                &format!(
332                    "{}: {} -> {} ({})",
333                    msg,
334                    link.from().id(),
335                    link.to().id(),
336                    dep_kind,
337                ),
338            );
339        }
340    }
341
342    // all_links should be in the correct order.
343    assert_link_order(
344        all_links,
345        graph.resolve_all().root_ids(direction),
346        desc,
347        msg,
348    );
349}
350
351fn assert_enabled_status_is_known(req: DependencyReq<'_>, msg: &str) {
352    let current_platform = PlatformSpec::build_target().expect("current platform is known");
353    assert!(
354        req.status().enabled_on(&current_platform).is_known(),
355        "{}: enabled status known for current platform",
356        msg
357    );
358    assert!(
359        req.default_features()
360            .enabled_on(&current_platform)
361            .is_known(),
362        "{}: default feature status known for current platform",
363        msg
364    );
365    for feature in req.features() {
366        assert!(
367            req.feature_status(feature)
368                .enabled_on(&current_platform)
369                .is_known(),
370            "{}: for feature '{}', status known for current platform",
371            msg,
372            feature
373        );
374    }
375}
376
377pub trait GraphAssert<'g>: Copy + fmt::Debug {
378    type Id: Copy + Eq + Hash + fmt::Debug;
379    type Metadata: GraphMetadata<'g, Id = Self::Id>;
380    type Query: GraphQuery<'g, Id = Self::Id, Set = Self::Set>;
381    type Set: GraphSet<'g, Id = Self::Id, Metadata = Self::Metadata>;
382    const NAME: &'static str;
383
384    // TODO: Add support for checks around links once they're defined for feature graphs.
385
386    fn depends_on(&self, a_id: Self::Id, b_id: Self::Id) -> Result<bool, Error>;
387
388    fn directly_depends_on(&self, a_id: Self::Id, b_id: Self::Id) -> Result<bool, Error>;
389
390    fn is_cyclic(&self, a_id: Self::Id, b_id: Self::Id) -> Result<bool, Error>;
391
392    fn query(
393        &self,
394        initials: impl IntoIterator<Item = Self::Id>,
395        direction: DependencyDirection,
396    ) -> Self::Query;
397
398    fn resolve(&self, initials: &[Self::Id], direction: DependencyDirection) -> Self::Set {
399        self.query(initials.iter().copied(), direction).resolve()
400    }
401
402    fn ids(
403        &self,
404        initials: &[Self::Id],
405        query_direction: DependencyDirection,
406        iter_direction: DependencyDirection,
407    ) -> Vec<Self::Id> {
408        let package_set = self.resolve(initials, query_direction);
409        let resolve_len = package_set.len();
410        let ids = package_set.ids(iter_direction);
411        assert_eq!(resolve_len, ids.len(), "resolve.len() is correct");
412        ids
413    }
414
415    fn root_ids(
416        &self,
417        initials: &[Self::Id],
418        query_direction: DependencyDirection,
419        iter_direction: DependencyDirection,
420    ) -> Vec<Self::Id> {
421        self.resolve(initials, query_direction)
422            .root_ids(iter_direction)
423    }
424
425    fn root_metadatas(
426        &self,
427        initials: &[Self::Id],
428        query_direction: DependencyDirection,
429        iter_direction: DependencyDirection,
430    ) -> Vec<Self::Metadata> {
431        self.resolve(initials, query_direction)
432            .root_metadatas(iter_direction)
433    }
434
435    fn assert_topo_order(
436        &self,
437        topo_ids: impl IntoIterator<Item = Self::Id>,
438        direction: DependencyDirection,
439        msg: &str,
440    ) {
441        let topo_ids: Vec<_> = topo_ids.into_iter().collect();
442        for (idx, earlier_package) in topo_ids.iter().enumerate() {
443            // Note that this skips over idx + 1 entries to avoid earlier_package == later_package.
444            // Doing an exhaustive search would be O(n**2) in the number of packages, so just do a
445            // maximum of 20.
446            // TODO: use proptest to generate random queries on the corpus.
447            for later_package in topo_ids.iter().skip(idx + 1).take(20) {
448                self.assert_not_depends_on(*later_package, *earlier_package, direction, msg);
449            }
450        }
451    }
452
453    fn assert_depends_on_any(
454        &self,
455        source_ids: &[Self::Id],
456        query_id: Self::Id,
457        direction: DependencyDirection,
458        msg: &str,
459    ) {
460        let any_depends_on = source_ids.iter().any(|source_id| match direction {
461            DependencyDirection::Forward => self.depends_on(*source_id, query_id).unwrap(),
462            DependencyDirection::Reverse => self.depends_on(query_id, *source_id).unwrap(),
463        });
464        match direction {
465            DependencyDirection::Forward => {
466                assert!(
467                    any_depends_on,
468                    "{}: {} '{:?}' should be a dependency of any of '{:?}'",
469                    msg,
470                    Self::NAME,
471                    query_id,
472                    source_ids
473                );
474            }
475            DependencyDirection::Reverse => {
476                assert!(
477                    any_depends_on,
478                    "{}: {} '{:?}' should depend on any of '{:?}'",
479                    msg,
480                    Self::NAME,
481                    query_id,
482                    source_ids
483                );
484            }
485        }
486    }
487
488    fn assert_depends_on(
489        &self,
490        a_id: Self::Id,
491        b_id: Self::Id,
492        direction: DependencyDirection,
493        msg: &str,
494    ) {
495        match direction {
496            DependencyDirection::Forward => assert!(
497                self.depends_on(a_id, b_id).unwrap(),
498                "{}: {} '{:?}' should depend on '{:?}'",
499                msg,
500                Self::NAME,
501                a_id,
502                b_id,
503            ),
504            DependencyDirection::Reverse => assert!(
505                self.depends_on(b_id, a_id).unwrap(),
506                "{}: {} '{:?}' should be a dependency of '{:?}'",
507                msg,
508                Self::NAME,
509                a_id,
510                b_id,
511            ),
512        }
513    }
514
515    fn assert_not_depends_on(
516        &self,
517        a_id: Self::Id,
518        b_id: Self::Id,
519        direction: DependencyDirection,
520        msg: &str,
521    ) {
522        if self.is_cyclic(a_id, b_id).unwrap() {
523            // This is a dependency cycle -- ignore it in not-depends-on checks.
524            // TODO: make this smarter now that cycles are handled in non-dev order.
525            return;
526        }
527
528        match direction {
529            DependencyDirection::Forward => assert!(
530                !self.depends_on(a_id, b_id).unwrap(),
531                "{}: {} '{:?}' should not depend on '{:?}'",
532                msg,
533                Self::NAME,
534                a_id,
535                b_id,
536            ),
537            DependencyDirection::Reverse => assert!(
538                !self.depends_on(b_id, a_id).unwrap(),
539                "{}: {} '{:?}' should not be a dependency of '{:?}'",
540                msg,
541                Self::NAME,
542                a_id,
543                b_id,
544            ),
545        }
546    }
547
548    fn assert_directly_depends_on(
549        &self,
550        a_id: Self::Id,
551        b_id: Self::Id,
552        direction: DependencyDirection,
553        msg: &str,
554    ) {
555        match direction {
556            DependencyDirection::Forward => assert!(
557                self.directly_depends_on(a_id, b_id).unwrap(),
558                "{}: {} '{:?}' should directly depend on '{:?}'",
559                msg,
560                Self::NAME,
561                a_id,
562                b_id,
563            ),
564            DependencyDirection::Reverse => assert!(
565                self.directly_depends_on(b_id, a_id).unwrap(),
566                "{}: {} '{:?}' should be a direct dependency of '{:?}'",
567                msg,
568                Self::NAME,
569                a_id,
570                b_id,
571            ),
572        }
573    }
574}
575
576pub trait GraphMetadata<'g> {
577    type Id: Copy + Eq + Hash + fmt::Debug;
578    fn id(&self) -> Self::Id;
579}
580
581pub trait GraphQuery<'g> {
582    type Id: Copy + Eq + Hash + fmt::Debug;
583    type Set: GraphSet<'g, Id = Self::Id>;
584
585    fn direction(&self) -> DependencyDirection;
586
587    fn starts_from(&self, id: Self::Id) -> bool;
588
589    fn resolve(self) -> Self::Set;
590}
591
592pub trait GraphSet<'g>: Clone + fmt::Debug {
593    type Id: Copy + Eq + Hash + fmt::Debug;
594    type Metadata: GraphMetadata<'g, Id = Self::Id>;
595    fn len(&self) -> usize;
596
597    fn is_empty(&self) -> bool {
598        self.len() == 0
599    }
600
601    fn contains(&self, id: Self::Id) -> bool;
602
603    fn union(&self, other: &Self) -> Self;
604    fn intersection(&self, other: &Self) -> Self;
605    fn difference(&self, other: &Self) -> Self;
606    fn symmetric_difference(&self, other: &Self) -> Self;
607
608    fn ids(&self, direction: DependencyDirection) -> Vec<Self::Id>;
609    fn metadatas(&self, direction: DependencyDirection) -> Vec<Self::Metadata>;
610    fn root_ids(&self, direction: DependencyDirection) -> Vec<Self::Id>;
611    fn root_metadatas(&self, direction: DependencyDirection) -> Vec<Self::Metadata>;
612}
613
614impl<'g> GraphAssert<'g> for &'g PackageGraph {
615    type Id = &'g PackageId;
616    type Metadata = PackageMetadata<'g>;
617    type Query = PackageQuery<'g>;
618    type Set = PackageSet<'g>;
619    const NAME: &'static str = "package";
620
621    fn depends_on(&self, a_id: Self::Id, b_id: Self::Id) -> Result<bool, Error> {
622        PackageGraph::depends_on(self, a_id, b_id)
623    }
624
625    fn directly_depends_on(&self, a_id: Self::Id, b_id: Self::Id) -> Result<bool, Error> {
626        PackageGraph::directly_depends_on(self, a_id, b_id)
627    }
628
629    fn is_cyclic(&self, a_id: Self::Id, b_id: Self::Id) -> Result<bool, Error> {
630        let cycles = self.cycles();
631        cycles.is_cyclic(a_id, b_id)
632    }
633
634    fn query(
635        &self,
636        initials: impl IntoIterator<Item = Self::Id>,
637        direction: DependencyDirection,
638    ) -> Self::Query {
639        self.query_directed(initials, direction)
640            .expect("valid initials")
641    }
642}
643
644impl<'g> GraphMetadata<'g> for PackageMetadata<'g> {
645    type Id = &'g PackageId;
646    fn id(&self) -> Self::Id {
647        PackageMetadata::id(self)
648    }
649}
650
651impl<'g> GraphQuery<'g> for PackageQuery<'g> {
652    type Id = &'g PackageId;
653    type Set = PackageSet<'g>;
654
655    fn direction(&self) -> DependencyDirection {
656        self.direction()
657    }
658
659    fn starts_from(&self, id: Self::Id) -> bool {
660        self.starts_from(id).expect("valid ID")
661    }
662
663    fn resolve(self) -> Self::Set {
664        self.resolve()
665    }
666}
667
668impl<'g> GraphSet<'g> for PackageSet<'g> {
669    type Id = &'g PackageId;
670    type Metadata = PackageMetadata<'g>;
671
672    fn len(&self) -> usize {
673        self.len()
674    }
675
676    fn contains(&self, id: Self::Id) -> bool {
677        self.contains(id).unwrap()
678    }
679
680    fn union(&self, other: &Self) -> Self {
681        self.union(other)
682    }
683
684    fn intersection(&self, other: &Self) -> Self {
685        self.intersection(other)
686    }
687
688    fn difference(&self, other: &Self) -> Self {
689        self.difference(other)
690    }
691
692    fn symmetric_difference(&self, other: &Self) -> Self {
693        self.symmetric_difference(other)
694    }
695
696    fn ids(&self, direction: DependencyDirection) -> Vec<Self::Id> {
697        self.package_ids(direction).collect()
698    }
699
700    fn metadatas(&self, direction: DependencyDirection) -> Vec<Self::Metadata> {
701        self.packages(direction).collect()
702    }
703
704    fn root_ids(&self, direction: DependencyDirection) -> Vec<Self::Id> {
705        Self::root_ids(self, direction).collect()
706    }
707
708    fn root_metadatas(&self, direction: DependencyDirection) -> Vec<Self::Metadata> {
709        Self::root_packages(self, direction).collect()
710    }
711}
712
713impl<'g> GraphAssert<'g> for FeatureGraph<'g> {
714    type Id = FeatureId<'g>;
715    type Metadata = FeatureMetadata<'g>;
716    type Query = FeatureQuery<'g>;
717    type Set = FeatureSet<'g>;
718    const NAME: &'static str = "feature";
719
720    fn depends_on(&self, a_id: Self::Id, b_id: Self::Id) -> Result<bool, Error> {
721        FeatureGraph::depends_on(self, a_id, b_id)
722    }
723
724    fn directly_depends_on(&self, a_id: Self::Id, b_id: Self::Id) -> Result<bool, Error> {
725        FeatureGraph::directly_depends_on(self, a_id, b_id)
726    }
727
728    fn is_cyclic(&self, a_id: Self::Id, b_id: Self::Id) -> Result<bool, Error> {
729        let cycles = self.cycles();
730        cycles.is_cyclic(a_id, b_id)
731    }
732
733    fn query(
734        &self,
735        initials: impl IntoIterator<Item = Self::Id>,
736        direction: DependencyDirection,
737    ) -> Self::Query {
738        self.query_directed(initials, direction)
739            .expect("valid initials")
740    }
741}
742
743impl<'g> GraphMetadata<'g> for FeatureMetadata<'g> {
744    type Id = FeatureId<'g>;
745    fn id(&self) -> Self::Id {
746        self.feature_id()
747    }
748}
749
750impl<'g> GraphQuery<'g> for FeatureQuery<'g> {
751    type Id = FeatureId<'g>;
752    type Set = FeatureSet<'g>;
753
754    fn direction(&self) -> DependencyDirection {
755        self.direction()
756    }
757
758    fn starts_from(&self, id: Self::Id) -> bool {
759        self.starts_from(id).expect("valid feature ID")
760    }
761
762    fn resolve(self) -> Self::Set {
763        self.resolve()
764    }
765}
766
767impl<'g> GraphSet<'g> for FeatureSet<'g> {
768    type Id = FeatureId<'g>;
769    type Metadata = FeatureMetadata<'g>;
770
771    fn len(&self) -> usize {
772        self.len()
773    }
774
775    fn contains(&self, id: Self::Id) -> bool {
776        self.contains(id).unwrap()
777    }
778
779    fn union(&self, other: &Self) -> Self {
780        self.union(other)
781    }
782
783    fn intersection(&self, other: &Self) -> Self {
784        self.intersection(other)
785    }
786
787    fn difference(&self, other: &Self) -> Self {
788        self.difference(other)
789    }
790
791    fn symmetric_difference(&self, other: &Self) -> Self {
792        self.symmetric_difference(other)
793    }
794
795    fn ids(&self, direction: DependencyDirection) -> Vec<Self::Id> {
796        self.feature_ids(direction).collect()
797    }
798
799    fn metadatas(&self, direction: DependencyDirection) -> Vec<Self::Metadata> {
800        self.features(direction).collect()
801    }
802
803    fn root_ids(&self, direction: DependencyDirection) -> Vec<Self::Id> {
804        Self::root_ids(self, direction).collect()
805    }
806
807    fn root_metadatas(&self, direction: DependencyDirection) -> Vec<Self::Metadata> {
808        Self::root_features(self, direction).collect()
809    }
810}
811
812/// Assert that links are presented in the expected order.
813///
814/// For any given package not in the initial set:
815/// * If direction is Forward, the package should appear in the `to` of a link at least once
816///   before it appears in the `from` of a link.
817/// * If direction is Reverse, the package should appear in the `from` of a link at least once
818///   before it appears in the `to` of a link.
819pub fn assert_link_order<'g>(
820    links: impl IntoIterator<Item = PackageLink<'g>>,
821    initial: impl IntoIterator<Item = &'g PackageId>,
822    desc: impl Into<DirectionDesc<'g>>,
823    msg: &str,
824) {
825    let desc = desc.into();
826
827    // for forward, 'from' is known and 'to' is variable.
828    let mut variable_seen: HashSet<_> = initial.into_iter().collect();
829
830    for link in links {
831        let known_id = desc.known_metadata(&link).id();
832        let variable_id = desc.variable_metadata(&link).id();
833
834        variable_seen.insert(variable_id);
835        assert!(
836            variable_seen.contains(&known_id),
837            "{}: for package '{}': unexpected link {} package seen before any links {} package",
838            msg,
839            &known_id,
840            desc.known_desc,
841            desc.variable_desc,
842        );
843    }
844}
845
846fn dep_link_ptrs<'g>(dep_links: impl IntoIterator<Item = PackageLink<'g>>) -> Vec<PackageLinkPtrs> {
847    let mut triples: Vec<_> = dep_links
848        .into_iter()
849        .map(|link| link.as_inner_ptrs())
850        .collect();
851    triples.sort();
852    triples
853}