// Copyright (c) The cargo-guppy Contributors
// SPDX-License-Identifier: MIT OR Apache-2.0

use fixtures::{
    json::{self, JsonFixture},
    package_id,
};
use guppy::{
    PackageId,
    graph::{
        BuildTargetId, BuildTargetKind, DependencyDirection, DotWrite, PackageDotVisitor,
        PackageLink, PackageMetadata,
        feature::{FeatureId, FeatureLabel, StandardFeatures, named_feature_filter},
    },
};
use std::{collections::HashSet, fmt, iter};

mod small {
    use super::*;
    use crate::feature_helpers::assert_features_for_package;
    use fixtures::json::METADATA_CYCLE_FEATURES_BASE;
    use guppy::graph::PackageGraph;
    use pretty_assertions::assert_eq;

    // Test workspace_default_members field.
    #[test]
    fn metadata_default_members() {
        let graph = PackageGraph::from_json(include_str!(
            "../../../fixtures/small/metadata_default_members.json"
        ))
        .expect("valid metadata");

        let workspace = graph.workspace();
        let default_members: Vec<_> = workspace.default_members().collect();

        assert_eq!(default_members.len(), 1, "workspace has one default member");
        assert_eq!(
            default_members[0].name(),
            "testcrate",
            "default member is testcrate"
        );

        // Test that default_member_ids also works.
        let default_member_ids: Vec<_> = workspace.default_member_ids().collect();
        assert_eq!(
            default_member_ids.len(),
            1,
            "workspace has one default member ID"
        );
    }

    // Test specific details extracted from metadata1.json.
    #[test]
    fn metadata1() {
        let metadata1 = JsonFixture::metadata1();
        metadata1.verify();

        let graph = metadata1.graph();

        assert_eq!(
            graph.workspace().target_directory(),
            "/fakepath/testcrate/target",
            "target directory matches"
        );

        let testcrate = graph
            .metadata(&package_id(json::METADATA1_TESTCRATE))
            .expect("root crate should exist");
        let mut root_deps: Vec<_> = testcrate.direct_links().collect();

        assert_eq!(root_deps.len(), 1, "the root crate has one dependency");
        let link = root_deps.pop().expect("the root crate has one dependency");
        // XXX test for details of dependency edges as well?
        assert!(link.normal().is_present(), "normal dependency is defined");
        assert!(link.build().is_present(), "build dependency is defined");
        assert!(link.dev().is_present(), "dev dependency is defined");

        // Print out dot graphs for small subgraphs.
        static EXPECTED_DOT: &str = r#"digraph {
    0 [label="winapi-x86_64-pc-windows-gnu"]
    11 [label="mach"]
    13 [label="winapi"]
    14 [label="libc"]
    20 [label="winapi-i686-pc-windows-gnu"]
    26 [label="region"]
    31 [label="bitflags"]
    11 -> 14 [label="libc"]
    13 -> 20 [label="winapi-i686-pc-windows-gnu"]
    13 -> 0 [label="winapi-x86_64-pc-windows-gnu"]
    26 -> 31 [label="bitflags"]
    26 -> 14 [label="libc"]
    26 -> 11 [label="mach"]
    26 -> 13 [label="winapi"]
}
"#;
        let package_set = graph
            .query_forward(iter::once(&package_id(json::METADATA1_REGION)))
            .unwrap()
            .resolve();
        assert_eq!(
            EXPECTED_DOT,
            format!("{}", package_set.display_dot(NameVisitor)),
            "dot output matches"
        );

        // For reverse reachable ensure that the arrows are in the correct direction.
        static EXPECTED_DOT_REVERSED: &str = r#"digraph {
    1 [label="datatest"]
    9 [label="serde_yaml"]
    15 [label="dtoa"]
    18 [label="testcrate"]
    1 -> 9 [label="serde_yaml"]
    9 -> 15 [label="dtoa"]
    18 -> 1 [label="datatest"]
}
"#;
        let package_set = graph
            .query_reverse(iter::once(&package_id(json::METADATA1_DTOA)))
            .unwrap()
            .resolve();

        assert_eq!(
            EXPECTED_DOT_REVERSED,
            format!("{}", package_set.display_dot(NameVisitor)),
            "reversed dot output matches"
        );

        // ---

        // Check that resolve_with works by dropping all edges into libc (compare to example above).
        static EXPECTED_DOT_NO_LIBC: &str = r#"digraph {
    0 [label="winapi-x86_64-pc-windows-gnu"]
    11 [label="mach"]
    13 [label="winapi"]
    20 [label="winapi-i686-pc-windows-gnu"]
    26 [label="region"]
    31 [label="bitflags"]
    13 -> 20 [label="winapi-i686-pc-windows-gnu"]
    13 -> 0 [label="winapi-x86_64-pc-windows-gnu"]
    26 -> 31 [label="bitflags"]
    26 -> 11 [label="mach"]
    26 -> 13 [label="winapi"]
}
"#;
        let package_set = graph
            .query_forward(iter::once(&package_id(json::METADATA1_REGION)))
            .unwrap()
            .resolve_with_fn(|_, link| link.to().name() != "libc");
        assert_eq!(
            EXPECTED_DOT_NO_LIBC,
            format!("{}", package_set.display_dot(NameVisitor)),
            "dot output matches"
        );

        // ---

        let feature_graph = graph.feature_graph();
        assert_eq!(feature_graph.feature_count(), 506, "feature count");
        assert_eq!(feature_graph.link_count(), 631, "link count");
        let feature_set = feature_graph
            .query_workspace(StandardFeatures::All)
            .resolve();
        let root_ids: Vec<_> = feature_set.root_ids(DependencyDirection::Forward).collect();
        let testcrate_id = package_id(json::METADATA1_TESTCRATE);
        let expected = vec![FeatureId::named(&testcrate_id, "datatest")];
        assert_eq!(root_ids, expected, "feature graph root IDs match");
    }

    proptest_suite!(metadata1);

    #[test]
    fn metadata2() {
        let metadata2 = JsonFixture::metadata2();
        metadata2.verify();

        let feature_graph = metadata2.graph().feature_graph();
        assert_eq!(feature_graph.feature_count(), 484, "feature count");
        assert_eq!(feature_graph.link_count(), 589, "link count");
        let root_ids: Vec<_> = feature_graph
            .query_workspace(StandardFeatures::None)
            .resolve()
            .root_ids(DependencyDirection::Forward)
            .collect();
        let testcrate_id = package_id(json::METADATA2_TESTCRATE);
        let expected = vec![FeatureId::base(&testcrate_id)];
        assert_eq!(root_ids, expected, "feature graph root IDs match");
    }

    proptest_suite!(metadata2);

    #[test]
    fn metadata_dups() {
        let metadata_dups = JsonFixture::metadata_dups();
        metadata_dups.verify();
    }

    proptest_suite!(metadata_dups);

    #[test]
    fn metadata_cycle1() {
        let metadata_cycle1 = JsonFixture::metadata_cycle1();
        metadata_cycle1.verify();
    }

    proptest_suite!(metadata_cycle1);

    #[test]
    fn metadata_cycle2() {
        let metadata_cycle2 = JsonFixture::metadata_cycle2();
        metadata_cycle2.verify();
    }

    proptest_suite!(metadata_cycle2);

    #[test]
    fn metadata_cycle_features() {
        let metadata_cycle_features = JsonFixture::metadata_cycle_features();
        metadata_cycle_features.verify();
        let feature_graph = metadata_cycle_features.graph().feature_graph();

        let base_id = package_id(METADATA_CYCLE_FEATURES_BASE);
        let default_id = FeatureId::named(&base_id, "default");

        // default, default-enable and default-transitive are default features.
        for &f in &["default", "default-enable", "default-transitive"] {
            let this_id = FeatureId::named(&base_id, f);
            assert!(
                feature_graph
                    .is_default_feature(this_id)
                    .expect("valid feature ID"),
                "{f} is a default feature",
            );
            assert!(
                feature_graph
                    .depends_on(default_id, this_id)
                    .expect("valid feature IDs"),
                "{default_id} should depend on {this_id} but does not",
            );
        }

        // helper-enable and helper-transitive are *not* default features even though they are
        // enabled by the cyclic dev dependency. But the dependency relation is present.
        for &f in &["helper-enable", "helper-transitive"] {
            let this_id = FeatureId::named(&base_id, f);
            assert!(
                !feature_graph
                    .is_default_feature(this_id)
                    .expect("valid feature ID"),
                "{f} is NOT a default feature",
            );
            assert!(
                feature_graph
                    .depends_on(default_id, this_id)
                    .expect("valid feature IDs"),
                "{default_id} should depend on {this_id} but does not",
            );
        }
    }

    proptest_suite!(metadata_cycle_features);

    #[test]
    fn metadata_self_dev_cycle() {
        // This fixture exercises both kinds of cycle at once:
        //
        // * `base` declares a path dev-dependency on itself, placing it
        //   in a single-node SCC with a self-loop in both the package
        //   graph and the feature graph.
        // * `cycle-a` and `cycle-b` form a 2-node multi-SCC via a regular
        //   forward edge and a dev back-edge -- a classic dev-cycle.
        //
        // The single-node SCC must still be treated as a forward root
        // (and `helper` as a reverse root), and both directions of link
        // iteration must surface the self-edge alongside the
        // `base -> helper` edge. `all_cycles()` must report both kinds
        // of cycle in topological order.
        let fixture = JsonFixture::metadata_self_dev_cycle();
        fixture.verify();

        let graph = fixture.graph();
        let base_id = package_id(json::METADATA_SELF_DEV_CYCLE_BASE);
        let helper_id = package_id(json::METADATA_SELF_DEV_CYCLE_HELPER);
        let cycle_a_id = package_id(json::METADATA_SELF_DEV_CYCLE_CYCLE_A);
        let cycle_b_id = package_id(json::METADATA_SELF_DEV_CYCLE_CYCLE_B);

        // `all_cycles()` reports both cycles:
        //
        // * `base`'s single-node SCC has a self-loop (cycle of length 1).
        // * `{cycle-a, cycle-b}` is a 2-node multi-SCC.
        //
        // Within the multi-SCC, nodes are returned in non-dev order:
        // `cycle-a -> cycle-b` is the regular edge, so `cycle-a`
        // precedes `cycle-b`.
        //
        // `helper` has no self-loop and is not on any cycle.
        let cycles: Vec<Vec<&PackageId>> = graph.cycles().all_cycles().collect();
        assert_eq!(cycles, vec![vec![&base_id], vec![&cycle_a_id, &cycle_b_id]],);

        let cycles = graph.cycles();
        assert!(
            cycles.is_cyclic(&base_id, &base_id).expect("known id"),
            "base is on a self-loop cycle",
        );
        assert!(
            !cycles.is_cyclic(&helper_id, &helper_id).expect("known id"),
            "helper has no self-loop and is not on any cycle",
        );
        assert!(
            !cycles.is_cyclic(&base_id, &helper_id).expect("known ids"),
            "base and helper are in different SCCs",
        );
        // Multi-SCC membership: both `cycle-a` and `cycle-b` are on a
        // cycle individually, and they lie on a common cycle pairwise.
        assert!(
            cycles
                .is_cyclic(&cycle_a_id, &cycle_a_id)
                .expect("known id"),
            "cycle-a is in a 2-node SCC",
        );
        assert!(
            cycles
                .is_cyclic(&cycle_b_id, &cycle_b_id)
                .expect("known id"),
            "cycle-b is in a 2-node SCC",
        );
        assert!(
            cycles
                .is_cyclic(&cycle_a_id, &cycle_b_id)
                .expect("known ids"),
            "cycle-a and cycle-b share a multi-node SCC",
        );
        // The base self-loop SCC and the cycle-a/cycle-b SCC are
        // distinct, even though both are cyclic.
        assert!(
            !cycles.is_cyclic(&base_id, &cycle_a_id).expect("known ids"),
            "base and cycle-a are in different SCCs",
        );

        let direct: HashSet<(PackageId, PackageId)> = graph
            .metadata(&base_id)
            .expect("known id")
            .direct_links()
            .map(|link| (link.from().id().clone(), link.to().id().clone()))
            .collect();
        assert_eq!(
            direct,
            HashSet::from([
                (base_id.clone(), base_id.clone()),
                (base_id.clone(), helper_id.clone()),
            ]),
        );

        // `directly_depends_on(x, x)` is true iff `x` has a self-loop edge.
        // `base` has a path-self dev-dependency, so it does; `helper` does
        // not.
        assert!(
            graph
                .directly_depends_on(&base_id, &base_id)
                .expect("known id"),
            "base directly depends on itself via its path-self dev-dep",
        );
        assert!(
            !graph
                .directly_depends_on(&helper_id, &helper_id)
                .expect("known id"),
            "helper has no self-edge",
        );

        // Forward query from `base`.
        let resolved_forward = graph
            .query_forward([&base_id])
            .expect("base is a known package id")
            .resolve();
        let resolved_ids: HashSet<PackageId> = resolved_forward
            .package_ids(DependencyDirection::Forward)
            .cloned()
            .collect();
        assert_eq!(
            resolved_ids,
            HashSet::from([base_id.clone(), helper_id.clone()])
        );

        let forward_roots: Vec<PackageId> = resolved_forward
            .root_ids(DependencyDirection::Forward)
            .cloned()
            .collect();
        assert_eq!(forward_roots, vec![base_id.clone()]);

        let forward_links: HashSet<(PackageId, PackageId)> = resolved_forward
            .links(DependencyDirection::Forward)
            .map(|link| (link.from().id().clone(), link.to().id().clone()))
            .collect();
        assert_eq!(
            forward_links,
            HashSet::from([
                (base_id.clone(), base_id.clone()),
                (base_id.clone(), helper_id.clone()),
            ]),
        );

        // Reverse query from `helper`.
        let resolved_reverse = graph
            .query_reverse([&helper_id])
            .expect("helper is a known package id")
            .resolve();
        let resolved_reverse_ids: HashSet<PackageId> = resolved_reverse
            .package_ids(DependencyDirection::Reverse)
            .cloned()
            .collect();
        assert_eq!(
            resolved_reverse_ids,
            HashSet::from([base_id.clone(), helper_id.clone()]),
        );

        let reverse_roots: Vec<PackageId> = resolved_reverse
            .root_ids(DependencyDirection::Reverse)
            .cloned()
            .collect();
        assert_eq!(reverse_roots, vec![helper_id.clone()]);

        let reverse_links: HashSet<(PackageId, PackageId)> = resolved_reverse
            .links(DependencyDirection::Reverse)
            .map(|link| (link.from().id().clone(), link.to().id().clone()))
            .collect();
        assert_eq!(
            reverse_links,
            HashSet::from([
                (base_id.clone(), base_id.clone()),
                (base_id.clone(), helper_id.clone()),
            ]),
        );

        let feature_graph = graph.feature_graph();
        let base_feature = FeatureId::new(&base_id, FeatureLabel::Base);
        let helper_feature = FeatureId::new(&helper_id, FeatureLabel::Base);

        // The same contract for `directly_depends_on` holds in the feature
        // graph: `base/[base]` has a self-loop (from the path dev-dep),
        // while `helper/[helper]` does not. No `SelfLoop` warning is
        // emitted here, since the loop comes from a dependency
        // declaration rather than a `[features]` entry.
        assert!(
            feature_graph
                .directly_depends_on(base_feature, base_feature)
                .expect("base/[base] is a valid feature id"),
            "base/[base] directly depends on itself",
        );
        assert!(
            !feature_graph
                .directly_depends_on(helper_feature, helper_feature)
                .expect("helper/[helper] is a valid feature id"),
            "helper/[helper] has no self-edge",
        );

        let resolved_feature = feature_graph
            .query_forward([base_feature])
            .expect("base/[base] is a valid feature id")
            .resolve();
        let feature_forward_roots: HashSet<FeatureId<'_>> = resolved_feature
            .root_ids(DependencyDirection::Forward)
            .collect();
        assert!(
            feature_forward_roots.contains(&base_feature),
            "feature graph forward roots should contain base/[base], got {feature_forward_roots:?}",
        );

        // Feature-graph cycle membership mirrors the package graph: the
        // self-loop on `base/[base]` makes it cyclic; `helper/[helper]`
        // has no self-loop and is not on any cycle.
        let feature_cycles = feature_graph.cycles();
        assert!(
            feature_cycles
                .is_cyclic(base_feature, base_feature)
                .expect("base/[base] is a valid feature id"),
            "base/[base] is on a self-loop cycle in the feature graph",
        );
        assert!(
            !feature_cycles
                .is_cyclic(helper_feature, helper_feature)
                .expect("helper/[helper] is a valid feature id"),
            "helper/[helper] has no self-loop and is not on any cycle",
        );
        // The feature graph surfaces cycles from both kinds of cycle
        // in the package graph:
        //
        // * `base/[base]` self-loops from the package-level dev-dep.
        // * `base/extra` self-loops because the dev-dep declares
        //   `features = ["extra"]`, making `extra` reachable from itself
        //   via the dev-dep edge.
        // * `cycle-a/[base]` and `cycle-b/[base]` lie on the package-level
        //   2-node SCC, lifted into the feature graph.
        let base_extra_feature = FeatureId::new(&base_id, FeatureLabel::Named("extra"));
        let cycle_a_feature = FeatureId::new(&cycle_a_id, FeatureLabel::Base);
        let cycle_b_feature = FeatureId::new(&cycle_b_id, FeatureLabel::Base);
        let feature_cycle_members: HashSet<FeatureId<'_>> =
            feature_graph.cycles().all_cycles().flatten().collect();
        assert_eq!(
            feature_cycle_members,
            HashSet::from([
                base_feature,
                base_extra_feature,
                cycle_a_feature,
                cycle_b_feature,
            ]),
        );
    }

    proptest_suite!(metadata_self_dev_cycle);

    // Test Windows path handling in fixtures with path dependencies.
    #[test]
    fn metadata_cycle1_windows() {
        // Same drive: workspace on C:, path dep also on C:.
        // The path dependency should be stored as a relative path.
        let fixture = JsonFixture::metadata_cycle1_windows();
        fixture.verify();

        let graph = fixture.graph();
        let helper = graph
            .metadata(&package_id(json::METADATA_CYCLE1_WINDOWS_HELPER))
            .expect("helper package exists");

        let source = helper.source();
        assert!(source.is_path(), "helper should be a path dependency");
        let path = source.local_path().expect("path dependency has local path");
        // Same drive should produce a relative path.
        assert_eq!(
            path.as_str(),
            "../testcycles-helper",
            "same-drive path dependency should be relative"
        );
    }

    proptest_suite!(metadata_cycle1_windows);

    #[test]
    fn metadata_cycle1_windows_different_drives() {
        // Different drives: workspace on C:, path dep on D:.
        // The path dependency should fall back to the absolute path.
        let fixture = JsonFixture::metadata_cycle1_windows_different_drives();
        fixture.verify();

        let graph = fixture.graph();
        let helper = graph
            .metadata(&package_id(
                json::METADATA_CYCLE1_WINDOWS_DIFFERENT_DRIVES_HELPER,
            ))
            .expect("helper package exists");

        let source = helper.source();
        assert!(source.is_path(), "helper should be a path dependency");
        let path = source.local_path().expect("path dependency has local path");
        // Different drives cannot be relative, so the absolute path is stored.
        // The path is normalized to forward slashes on Unix but not on Windows.
        #[cfg(windows)]
        let expected = r"D:\libs\testcycles-helper";
        #[cfg(not(windows))]
        let expected = "D:/libs/testcycles-helper";
        assert_eq!(
            path.as_str(),
            expected,
            "different-drive path dependency should be absolute"
        );
    }

    proptest_suite!(metadata_cycle1_windows_different_drives);

    // Test that Windows metadata with backslash paths is normalized to forward
    // slashes on Unix, so all path operations work correctly. On Windows, the
    // paths should remain with backslashes.
    #[test]
    fn windows_metadata_path_normalization() {
        // Use the different drives fixture since it's more interesting: it has
        // paths on different drives which cannot be made relative.
        let fixture = JsonFixture::metadata_cycle1_windows_different_drives();
        let graph = fixture.graph();

        // On Unix: backslashes should be normalized to forward slashes.
        // On Windows: forward slashes should not appear in absolute paths.
        #[cfg(not(windows))]
        let bad_sep = '\\';
        #[cfg(windows)]
        let bad_sep = '/';

        let root = graph.workspace().root();
        assert!(
            !root.as_str().contains(bad_sep),
            "workspace root should not contain '{}': {}",
            bad_sep,
            root
        );
        assert!(root.parent().is_some(), "workspace root should have parent");

        let target_dir = graph.workspace().target_directory();
        assert!(
            !target_dir.as_str().contains(bad_sep),
            "target directory should not contain '{}': {}",
            bad_sep,
            target_dir
        );

        for package in graph.packages() {
            let manifest = package.manifest_path();
            assert!(
                !manifest.as_str().contains(bad_sep),
                "manifest_path should not contain '{}': {}",
                bad_sep,
                manifest
            );

            for target in package.build_targets() {
                let path = target.path();
                assert!(
                    !path.as_str().contains(bad_sep),
                    "build target path should not contain '{}': {}",
                    bad_sep,
                    path
                );
            }
        }
    }

    #[test]
    fn metadata_targets1() {
        let metadata_targets1 = JsonFixture::metadata_targets1();
        metadata_targets1.verify();

        let package_graph = metadata_targets1.graph();
        let package_set = package_graph.resolve_all();
        let feature_graph = metadata_targets1.graph().feature_graph();
        assert_eq!(feature_graph.feature_count(), 38, "feature count");

        // Some code that might be useful for debugging.
        if false {
            for (source, target, edge) in feature_graph
                .resolve_all()
                .links(DependencyDirection::Forward)
            {
                let source_metadata = package_graph.metadata(source.package_id()).unwrap();
                let target_metadata = package_graph.metadata(target.package_id()).unwrap();

                println!(
                    "feature link: {}:{} {} -> {}:{} {} {:?}",
                    source_metadata.name(),
                    source_metadata.version(),
                    source.label(),
                    target_metadata.name(),
                    target_metadata.version(),
                    target.label(),
                    edge
                );
            }
        }

        assert_eq!(feature_graph.link_count(), 58, "feature link count");

        // Check that resolve_packages + a feature filter works.
        let feature_set = package_set.to_feature_set(named_feature_filter(
            StandardFeatures::Default,
            ["foo", "bar"].iter().copied(),
        ));
        let dep_a_id = package_id(json::METADATA_TARGETS1_DEP_A);
        assert!(
            feature_set
                .contains((&dep_a_id, FeatureLabel::Named("foo")))
                .expect("valid feature ID")
        );
        assert!(
            feature_set
                .contains((&dep_a_id, FeatureLabel::Named("bar")))
                .expect("valid feature ID")
        );
        assert!(
            !feature_set
                .contains((&dep_a_id, FeatureLabel::Named("baz")))
                .expect("valid feature ID")
        );
        assert!(
            !feature_set
                .contains((&dep_a_id, FeatureLabel::Named("quux")))
                .expect("valid feature ID")
        );

        assert_features_for_package(
            &feature_set,
            &package_id(json::METADATA_TARGETS1_TESTCRATE),
            Some(&[FeatureLabel::Base]),
            "testcrate",
        );
        assert_features_for_package(
            &feature_set,
            &dep_a_id,
            Some(&[
                FeatureLabel::Base,
                FeatureLabel::Named("bar"),
                FeatureLabel::Named("foo"),
            ]),
            "dep a",
        );
        assert_features_for_package(
            &feature_set,
            &package_id(json::METADATA_TARGETS1_LAZY_STATIC_1),
            Some(&[FeatureLabel::Base]),
            "lazy_static",
        );
    }

    proptest_suite!(metadata_targets1);

    #[test]
    fn metadata_build_targets1() {
        let metadata_build_targets1 = JsonFixture::metadata_build_targets1();
        metadata_build_targets1.verify();
    }

    // No need for proptests because there are no dependencies involved.

    #[test]
    fn metadata_proc_macro1() {
        let metadata = JsonFixture::metadata_proc_macro1();
        metadata.verify();
        let graph = metadata.graph();

        let package = graph
            .metadata(&package_id(json::METADATA_PROC_MACRO1_MACRO))
            .expect("valid package ID");
        assert!(package.is_proc_macro(), "is proc macro");

        let build_target_kind = package
            .build_target(&BuildTargetId::Library)
            .expect("library package is present")
            .kind();
        assert_eq!(
            build_target_kind,
            BuildTargetKind::ProcMacro,
            "build target kind matches"
        );
    }

    // No need for proptests because this is a really simple test.
}

mod large {
    use super::*;
    use fixtures::dep_helpers::GraphAssert;

    #[test]
    fn metadata_libra() {
        let metadata_libra = JsonFixture::metadata_libra();
        metadata_libra.verify();
    }

    proptest_suite!(metadata_libra);

    #[test]
    fn metadata_libra_f0091a4() {
        let metadata = JsonFixture::metadata_libra_f0091a4();
        metadata.verify();
    }

    proptest_suite!(metadata_libra_f0091a4);

    #[test]
    fn metadata_libra_9ffd93b() {
        let metadata = JsonFixture::metadata_libra_9ffd93b();
        metadata.verify();

        let graph = metadata.graph();
        graph.assert_depends_on(
            &package_id(json::METADATA_LIBRA_ADMISSION_CONTROL_SERVICE),
            &package_id(json::METADATA_LIBRA_EXECUTOR_UTILS),
            DependencyDirection::Forward,
            "admission-control-service should depend on executor-utils",
        );
        graph.assert_not_depends_on(
            &package_id(json::METADATA_LIBRA_EXECUTOR_UTILS),
            &package_id(json::METADATA_LIBRA_ADMISSION_CONTROL_SERVICE),
            DependencyDirection::Forward,
            "executor-utils should not depend on admission-control-service",
        );

        let proc_macro_packages: Vec<_> = graph
            .workspace()
            .iter_by_path()
            .filter_map(|(_, metadata)| {
                if metadata.is_proc_macro() {
                    Some(metadata.name())
                } else {
                    None
                }
            })
            .collect();
        assert_eq!(
            proc_macro_packages,
            ["num-variants", "libra-crypto-derive"],
            "proc macro packages"
        );

        let build_script_packages: Vec<_> = graph
            .workspace()
            .iter_by_path()
            .filter_map(|(_, metadata)| {
                if metadata.has_build_script() {
                    Some(metadata.name())
                } else {
                    None
                }
            })
            .collect();
        assert_eq!(
            build_script_packages,
            [
                "admission-control-proto",
                "libra-dev",
                "debug-interface",
                "libra-metrics",
                "storage-proto",
                "libra_fuzzer_fuzz",
                "libra-types"
            ],
            "build script packages"
        );

        let mut build_dep_but_no_build_script: Vec<_> = graph
            .resolve_all()
            .links(DependencyDirection::Forward)
            .filter_map(|link| {
                if link.build().is_present() && !link.from().has_build_script() {
                    Some(link.from().name())
                } else {
                    None
                }
            })
            .collect();
        build_dep_but_no_build_script.sort_unstable();
        assert_eq!(
            build_dep_but_no_build_script,
            ["libra-mempool", "rusoto_signature"],
            "packages with build deps but no build scripts"
        );
    }

    proptest_suite!(metadata_libra_9ffd93b);

    #[test]
    fn mnemos_b3b4da9() {
        let metadata = JsonFixture::mnemos_b3b4da9();
        metadata.verify();
    }

    proptest_suite!(mnemos_b3b4da9);

    #[test]

    fn hyper_util_7afb1ed() {
        let metadata = JsonFixture::hyper_util_7afb1ed();
        metadata.verify();
    }

    proptest_suite!(hyper_util_7afb1ed);
}

mod guppy_tests {
    use super::*;
    use fixtures::json::METADATA_GUPPY_CARGO_GUPPY;
    use guppy::PackageId;

    #[test]
    fn metadata_guppy_44b62fa() {
        let metadata = JsonFixture::metadata_guppy_44b62fa();
        metadata.verify();

        // This is --no-deps metadata: check that there are no dependency edges at all.
        let graph = metadata.graph();
        let package = graph
            .metadata(&PackageId::new(METADATA_GUPPY_CARGO_GUPPY))
            .expect("cargo-guppy package found");
        assert_eq!(
            package.direct_links().count(),
            0,
            "no-deps => package has no direct links"
        );
        assert_eq!(graph.link_count(), 0, "no-deps => no edges");
    }

    proptest_suite!(metadata_guppy_44b62fa);
}

struct NameVisitor;

impl PackageDotVisitor for NameVisitor {
    fn visit_package(&self, package: PackageMetadata<'_>, f: &mut DotWrite<'_, '_>) -> fmt::Result {
        write!(f, "{}", package.name())
    }

    fn visit_link(&self, link: PackageLink<'_>, f: &mut DotWrite<'_, '_>) -> fmt::Result {
        write!(f, "{}", link.dep_name())
    }
}
