305 lines
10 KiB
Python
305 lines
10 KiB
Python
import importlib.util
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
CHECKER = ROOT / "scripts" / "check-rust-boundaries.py"
|
|
|
|
|
|
def load_checker_module():
|
|
spec = importlib.util.spec_from_file_location("check_rust_boundaries", CHECKER)
|
|
module = importlib.util.module_from_spec(spec)
|
|
assert spec.loader is not None
|
|
sys.modules[spec.name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def package(root: Path, name: str, rel_dir: str, dependencies: list[str] | None = None) -> dict:
|
|
dependencies = dependencies or []
|
|
return {
|
|
"id": f"path+file://{root / rel_dir}#{name}",
|
|
"name": name,
|
|
"manifest_path": str(root / rel_dir / "Cargo.toml"),
|
|
"dependencies": [{"name": dependency} for dependency in dependencies],
|
|
}
|
|
|
|
|
|
def dependency(name: str, kind: str | None = None) -> dict:
|
|
return {"name": name, "kind": kind}
|
|
|
|
|
|
def metadata(packages: list[dict], root: Path | None = None) -> dict:
|
|
root = root or Path("/tmp/crank")
|
|
return {
|
|
"workspace_root": str(root),
|
|
"workspace_members": [pkg["id"] for pkg in packages],
|
|
"packages": packages,
|
|
}
|
|
|
|
|
|
class RustBoundaryCheckTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.checker = load_checker_module()
|
|
self.root = Path("/tmp/crank")
|
|
|
|
def test_allows_apps_to_depend_on_workspace_crates(self) -> None:
|
|
packages = [
|
|
package(self.root, "admin-api", "apps/admin-api", ["crank-runtime"]),
|
|
package(self.root, "crank-runtime", "crates/crank-runtime", ["crank-core"]),
|
|
package(self.root, "crank-core", "crates/crank-core"),
|
|
]
|
|
|
|
violations = self.checker.find_violations(metadata(packages, self.root))
|
|
|
|
self.assertEqual(violations, [])
|
|
|
|
def test_rejects_core_dependency_on_runtime(self) -> None:
|
|
packages = [
|
|
package(self.root, "crank-core", "crates/crank-core", ["crank-runtime"]),
|
|
package(self.root, "crank-runtime", "crates/crank-runtime"),
|
|
]
|
|
|
|
violations = self.checker.find_violations(metadata(packages, self.root))
|
|
|
|
self.assertEqual(len(violations), 1)
|
|
self.assertEqual(violations[0].source, "crank-core")
|
|
self.assertEqual(violations[0].dependency, "crank-runtime")
|
|
|
|
def test_rejects_workspace_crate_dependency_on_app(self) -> None:
|
|
packages = [
|
|
package(self.root, "crank-runtime", "crates/crank-runtime", ["admin-api"]),
|
|
package(self.root, "admin-api", "apps/admin-api"),
|
|
]
|
|
|
|
violations = self.checker.find_violations(metadata(packages, self.root))
|
|
|
|
self.assertEqual(len(violations), 1)
|
|
self.assertIn("must not depend on apps", violations[0].reason)
|
|
|
|
def test_rejects_registry_dependency_on_adapter(self) -> None:
|
|
packages = [
|
|
package(self.root, "crank-registry", "crates/crank-registry", ["crank-adapter-rest"]),
|
|
package(self.root, "crank-adapter-rest", "crates/crank-adapter-rest"),
|
|
]
|
|
|
|
violations = self.checker.find_violations(metadata(packages, self.root))
|
|
|
|
self.assertEqual(len(violations), 1)
|
|
self.assertEqual(violations[0].source, "crank-registry")
|
|
self.assertEqual(violations[0].dependency, "crank-adapter-rest")
|
|
|
|
def test_allows_apps_to_depend_on_observability(self) -> None:
|
|
packages = [
|
|
package(
|
|
self.root,
|
|
"admin-api",
|
|
"apps/admin-api",
|
|
["crank-observability"],
|
|
),
|
|
package(
|
|
self.root,
|
|
"crank-observability",
|
|
"crates/crank-observability",
|
|
),
|
|
]
|
|
|
|
violations = self.checker.find_violations(metadata(packages, self.root))
|
|
|
|
self.assertEqual(violations, [])
|
|
|
|
def test_rejects_observability_dependency_on_workspace_crates(self) -> None:
|
|
for dependency in ("crank-core", "crank-registry", "crank-runtime"):
|
|
packages = [
|
|
package(
|
|
self.root,
|
|
"crank-observability",
|
|
"crates/crank-observability",
|
|
[dependency],
|
|
),
|
|
package(self.root, dependency, f"crates/{dependency}"),
|
|
]
|
|
|
|
violations = self.checker.find_violations(metadata(packages, self.root))
|
|
|
|
self.assertEqual(len(violations), 1, dependency)
|
|
self.assertEqual(violations[0].source, "crank-observability")
|
|
self.assertEqual(violations[0].dependency, dependency)
|
|
|
|
def test_allows_observability_and_product_crates_to_depend_on_metrics_contract(self) -> None:
|
|
packages = [
|
|
package(
|
|
self.root,
|
|
"crank-observability",
|
|
"crates/crank-observability",
|
|
["crank-metrics"],
|
|
),
|
|
package(
|
|
self.root,
|
|
"crank-runtime",
|
|
"crates/crank-runtime",
|
|
["crank-metrics"],
|
|
),
|
|
package(self.root, "crank-metrics", "crates/crank-metrics"),
|
|
]
|
|
|
|
violations = self.checker.find_violations(metadata(packages, self.root))
|
|
|
|
self.assertEqual(violations, [])
|
|
|
|
def test_rejects_metrics_contract_dependency_on_workspace_crates(self) -> None:
|
|
packages = [
|
|
package(
|
|
self.root,
|
|
"crank-metrics",
|
|
"crates/crank-metrics",
|
|
["crank-core"],
|
|
),
|
|
package(self.root, "crank-core", "crates/crank-core"),
|
|
]
|
|
|
|
violations = self.checker.find_violations(metadata(packages, self.root))
|
|
|
|
self.assertEqual(len(violations), 1)
|
|
self.assertEqual(violations[0].source, "crank-metrics")
|
|
self.assertEqual(violations[0].dependency, "crank-core")
|
|
|
|
def test_rejects_config_contract_dependency_on_workspace_crates(self) -> None:
|
|
packages = [
|
|
package(
|
|
self.root,
|
|
"crank-config",
|
|
"crates/crank-config",
|
|
["crank-core"],
|
|
),
|
|
package(self.root, "crank-core", "crates/crank-core"),
|
|
]
|
|
|
|
violations = self.checker.find_violations(metadata(packages, self.root))
|
|
|
|
self.assertEqual(len(violations), 1)
|
|
self.assertEqual(violations[0].source, "crank-config")
|
|
self.assertEqual(violations[0].dependency, "crank-core")
|
|
|
|
def test_rejects_artifact_store_dependency_on_workspace_crates(self) -> None:
|
|
packages = [
|
|
package(
|
|
self.root,
|
|
"crank-artifacts",
|
|
"crates/crank-artifacts",
|
|
["crank-core"],
|
|
),
|
|
package(self.root, "crank-core", "crates/crank-core"),
|
|
]
|
|
|
|
violations = self.checker.find_violations(metadata(packages, self.root))
|
|
|
|
self.assertEqual(len(violations), 1)
|
|
self.assertEqual(violations[0].source, "crank-artifacts")
|
|
self.assertEqual(violations[0].dependency, "crank-core")
|
|
|
|
def test_allows_artifact_store_dev_dependency_on_workspace_crates(self) -> None:
|
|
artifacts = package(
|
|
self.root,
|
|
"crank-artifacts",
|
|
"crates/crank-artifacts",
|
|
)
|
|
artifacts["dependencies"] = [dependency("crank-test-support", "dev")]
|
|
packages = [
|
|
artifacts,
|
|
package(
|
|
self.root,
|
|
"crank-test-support",
|
|
"crates/crank-test-support",
|
|
),
|
|
]
|
|
|
|
violations = self.checker.find_violations(metadata(packages, self.root))
|
|
|
|
self.assertEqual(violations, [])
|
|
|
|
def test_rejects_artifact_store_build_dependency_on_workspace_crates(self) -> None:
|
|
artifacts = package(
|
|
self.root,
|
|
"crank-artifacts",
|
|
"crates/crank-artifacts",
|
|
)
|
|
artifacts["dependencies"] = [dependency("crank-core", "build")]
|
|
packages = [
|
|
artifacts,
|
|
package(self.root, "crank-core", "crates/crank-core"),
|
|
]
|
|
|
|
violations = self.checker.find_violations(metadata(packages, self.root))
|
|
|
|
self.assertEqual(len(violations), 1)
|
|
self.assertEqual(violations[0].source, "crank-artifacts")
|
|
self.assertEqual(violations[0].dependency, "crank-core")
|
|
|
|
def test_mixed_dependency_kinds_produce_one_production_violation(self) -> None:
|
|
artifacts = package(
|
|
self.root,
|
|
"crank-artifacts",
|
|
"crates/crank-artifacts",
|
|
)
|
|
artifacts["dependencies"] = [
|
|
dependency("crank-core", "dev"),
|
|
dependency("crank-core"),
|
|
dependency("crank-core", "build"),
|
|
]
|
|
packages = [
|
|
artifacts,
|
|
package(self.root, "crank-core", "crates/crank-core"),
|
|
]
|
|
|
|
violations = self.checker.find_violations(metadata(packages, self.root))
|
|
|
|
self.assertEqual(len(violations), 1)
|
|
self.assertEqual(violations[0].source, "crank-artifacts")
|
|
self.assertEqual(violations[0].dependency, "crank-core")
|
|
|
|
def test_rejects_domain_and_runtime_dependencies_on_observability(self) -> None:
|
|
for source in ("crank-core", "crank-registry", "crank-runtime"):
|
|
packages = [
|
|
package(
|
|
self.root,
|
|
source,
|
|
f"crates/{source}",
|
|
["crank-observability"],
|
|
),
|
|
package(
|
|
self.root,
|
|
"crank-observability",
|
|
"crates/crank-observability",
|
|
),
|
|
]
|
|
|
|
violations = self.checker.find_violations(metadata(packages, self.root))
|
|
|
|
self.assertEqual(len(violations), 1, source)
|
|
self.assertEqual(violations[0].source, source)
|
|
self.assertEqual(violations[0].dependency, "crank-observability")
|
|
|
|
def test_rejects_direct_production_metrics_dependency_outside_contract(self) -> None:
|
|
app = package(self.root, "admin-api", "apps/admin-api")
|
|
app["dependencies"] = [dependency("metrics")]
|
|
|
|
violations = self.checker.find_violations(metadata([app], self.root))
|
|
|
|
self.assertEqual(len(violations), 1)
|
|
self.assertEqual(violations[0].dependency, "metrics")
|
|
|
|
def test_allows_metrics_as_test_only_dependency(self) -> None:
|
|
app = package(self.root, "admin-api", "apps/admin-api")
|
|
app["dependencies"] = [dependency("metrics", "dev")]
|
|
|
|
violations = self.checker.find_violations(metadata([app], self.root))
|
|
|
|
self.assertEqual(violations, [])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|