fix(xtask): let a standalone crate dev-depend on test support

The standalone rule ran before the testkit exemption and returned early, so `cos`, `geom`,
`filters`, `crypt` and `arlington` could not use `pdfcraft-testkit` or `pdfcraft-oracle` even as a
dev-dependency. Both crates were already registered in the layering table as `Class::Testkit`, so
the first real use of either from a standalone crate would have been rejected.

A dev-dependency is not part of the graph a standalone crate publishes, which is what the rule
protects; a normal dependency on test support, and any dependency on a product crate, are still
rejected. The existing test covered neither direction of the standalone case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Tanish Manku
2026-10-14 13:56:00 -07:00
co-authored by Claude Opus 5
parent 9931329b9f
commit 727555ca29
+19 -2
View File
@@ -198,11 +198,17 @@ pub fn check(crates: &[Crate]) -> Vec<Violation> {
continue;
}
if d.workspace {
if matches!(class, Class::Standalone(_)) && !standalone_allowed(&c.name, &d.name) {
let dep_class = classify(&d.name);
// Test support is a dev-dependency only (`Class::Testkit`) and so never reaches
// the product graph a standalone crate publishes. Checking it against the
// standalone allow-list would stop `cos` using the shared oracles in its own
// tests, which is the one thing those crates exist for.
let testkit_dev = dep_class == Some(Class::Testkit) && d.kind == DepKind::Dev;
if matches!(class, Class::Standalone(_)) && !testkit_dev && !standalone_allowed(&c.name, &d.name) {
out.push(Violation::StandaloneHasWorkspaceDep { krate: c.name.clone(), dep: d.name.clone() });
continue;
}
match classify(&d.name) {
match dep_class {
None => {}
Some(Class::Testkit) if d.kind != DepKind::Dev && class != Class::Testkit => {
out.push(Violation::TestkitAsNormalDep { krate: c.name.clone(), dep: d.name.clone() });
@@ -322,6 +328,17 @@ mod tests {
assert!(check(&[c("pdfcraft-render", &[("pdfcraft-oracle", Dev, true)])]).is_empty());
let v = check(&[c("pdfcraft-render", &[("pdfcraft-oracle", Normal, true)])]);
assert!(matches!(v[..], [Violation::TestkitAsNormalDep { .. }]));
// A standalone crate may use test support in its own tests: a dev-dependency is not part
// of the graph the standalone rule protects. `cos` is where the qpdf oracle is needed.
assert!(check(&[c("pdfcraft-cos", &[("pdfcraft-oracle", Dev, true)])]).is_empty());
assert!(check(&[c("pdfcraft-geom", &[("pdfcraft-testkit", Dev, true)])]).is_empty());
// ...but only as a dev-dependency, and only for test support. Product crates are still
// off-limits to a standalone crate however they are depended on.
let v = check(&[c("pdfcraft-cos", &[("pdfcraft-oracle", Normal, true)])]);
assert!(matches!(v[..], [Violation::StandaloneHasWorkspaceDep { .. }]), "{v:?}");
let v = check(&[c("pdfcraft-cos", &[("pdfcraft-model", Dev, true)])]);
assert!(matches!(v[..], [Violation::StandaloneHasWorkspaceDep { .. }]), "{v:?}");
}
#[test]