git-ents.gitmain
⌘K
foforge
layering.rs173 lines · 6.7 KB · rusthistorycomment on this file
1//! Mechanical check of the one-way crate layering documented in
2//! `docs/abstractions.adoc`'s "Layering" section: substrate -> kernel ->
3//! {forge, kiln} -> cli, with forge and kiln forbidden from depending on
4//! each other. Extended with one more rule for `crates/verify/*`
5//! (`verify/README.adoc`): that layer is a pure sink above everything
6//! else — `ents-verify` may depend only on `ents-gate-rules`, and no
7//! crate anywhere in the workspace may depend on `ents-verify`.
8//!
9//! The prose in `docs/abstractions.adoc` states the rule, but nothing
10//! stops a future `Cargo.toml` edit from quietly violating it (a kernel
11//! crate reaching for `ents-forge` behind a feature flag, say). This test
12//! reads the real dependency graph via `cargo metadata`, assigns every
13//! workspace member a layer from its manifest path, and asserts each
14//! workspace-local dependency edge points from an equal-or-higher layer
15//! down to a lower-or-equal one — catching the violation mechanically
16//! instead of relying on review to notice.
17#![allow(clippy::expect_used, reason = "integration test")]
18#![allow(
19 clippy::panic,
20 reason = "this test's whole job is to panic loudly on an unexpected manifest shape or a layering violation"
21)]
22
23use std::collections::BTreeMap;
24
25use cargo_metadata::camino::Utf8Path;
26use cargo_metadata::{MetadataCommand, Package};
27
28/// Layer rank for a workspace package, derived from the `crates/<layer>/*`
29/// prefix of its manifest path: substrate (0) -> kernel (1) ->
30/// forge/kiln (2) -> cli (3) -> verify (4).
31///
32/// A dependency edge is only legal when the depending package's rank is
33/// greater than or equal to the depended-on package's rank — e.g. the CLI
34/// (3) may depend on `ents-forge` (2), but a kernel crate (1) may never
35/// depend on a package crate (2). `verify` sits at the top rank alone, so
36/// this general rule already forbids every other layer from depending on
37/// it; [`ents_verify_depends_only_on_ents_gate_rules`] adds the sharper
38/// rule that its own outgoing edges are restricted too.
39fn layer_rank(package: &Package, workspace_root: &Utf8Path) -> u8 {
40 let relative = package
41 .manifest_path
42 .strip_prefix(workspace_root)
43 .unwrap_or_else(|_| {
44 panic!(
45 "{}'s manifest path {} is not under the workspace root {workspace_root}",
46 package.name, package.manifest_path
47 )
48 });
49 let mut components = relative.components();
50 assert_eq!(
51 components.next().map(|c| c.as_str()),
52 Some("crates"),
53 "{}'s manifest path {relative} is not under crates/",
54 package.name
55 );
56 match components.next().map(|c| c.as_str()) {
57 Some("substrate") => 0,
58 Some("kernel") => 1,
59 Some("forge") | Some("kiln") => 2,
60 Some("cli") => 3,
61 Some("verify") => 4,
62 other => panic!(
63 "{}'s manifest path {relative} has an unrecognized layer directory {other:?}",
64 package.name
65 ),
66 }
67}
68
69/// Every workspace-local dependency edge points from a higher (or equal)
70/// layer to a lower (or equal) one, and `ents-forge`/`ents-kiln` never
71/// depend on each other.
72///
73/// See `crates/kernel/ents-model` and the root `Cargo.toml` for the
74/// self-verification this test is designed to catch: temporarily adding
75/// `ents-forge` as a dependency of a kernel crate must fail this test.
76#[test]
77fn dependencies_never_point_upward() {
78 let metadata = MetadataCommand::new()
79 .manifest_path(concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml"))
80 .no_deps()
81 .exec()
82 .expect("cargo metadata");
83
84 let workspace_packages = metadata.workspace_packages();
85
86 let ranks: BTreeMap<&str, u8> = workspace_packages
87 .iter()
88 .map(|package| {
89 (
90 package.name.as_ref(),
91 layer_rank(package, &metadata.workspace_root),
92 )
93 })
94 .collect();
95
96 let mut forge_depends_on_kiln = false;
97 let mut kiln_depends_on_forge = false;
98
99 for package in &workspace_packages {
100 let from_rank = *ranks
101 .get(package.name.as_ref())
102 .expect("every workspace package has a rank");
103 for dependency in &package.dependencies {
104 let Some(&to_rank) = ranks.get(dependency.name.as_str()) else {
105 continue; // external crate, not workspace-local
106 };
107 assert!(
108 from_rank >= to_rank,
109 "layering violation: {} (layer {from_rank}) depends on {} (layer {to_rank}), \
110 but dependencies must point from a higher layer down to an equal-or-lower one",
111 package.name,
112 dependency.name,
113 );
114
115 if package.name == "ents-forge" && dependency.name == "ents-kiln" {
116 forge_depends_on_kiln = true;
117 }
118 if package.name == "ents-kiln" && dependency.name == "ents-forge" {
119 kiln_depends_on_forge = true;
120 }
121
122 if package.name == "ents-verify" {
123 assert_eq!(
124 dependency.name, "ents-gate-rules",
125 "ents-verify (the verify/ layer's sink crate) may depend on ents-gate-rules only, \
126 but its manifest also names {}",
127 dependency.name
128 );
129 }
130 }
131 }
132
133 assert!(
134 !forge_depends_on_kiln,
135 "ents-forge must not depend on ents-kiln: they are sibling package crates"
136 );
137 assert!(
138 !kiln_depends_on_forge,
139 "ents-kiln must not depend on ents-forge: they are sibling package crates"
140 );
141}
142
143/// `ents-verify` is a pure sink: nothing in the workspace may depend on
144/// it. The general rank rule in [`dependencies_never_point_upward`]
145/// already forbids this (every other layer ranks below `verify`), but
146/// this test states the sink property directly against the real
147/// dependency graph, rather than relying on that rank arithmetic alone.
148///
149/// See `crates/kernel/ents-model` and the root `Cargo.toml` for the
150/// self-verification this test is designed to catch: temporarily adding
151/// `ents-verify` as a dependency of any other crate must fail this test.
152#[test]
153fn nothing_depends_on_ents_verify() {
154 let metadata = MetadataCommand::new()
155 .manifest_path(concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml"))
156 .no_deps()
157 .exec()
158 .expect("cargo metadata");
159
160 for package in metadata.workspace_packages() {
161 if package.name == "ents-verify" {
162 continue;
163 }
164 assert!(
165 package
166 .dependencies
167 .iter()
168 .all(|dependency| dependency.name != "ents-verify"),
169 "{} must not depend on ents-verify: verify/ is a sink layer nothing else may depend on",
170 package.name
171 );
172 }
173}