use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use serde::Deserialize;
use super::FloorError;
#[derive(Debug, Clone, Deserialize)]
pub struct PackageMeta {
pub name: String,
pub manifest_path: PathBuf,
#[serde(default)]
pub targets: Vec<TargetMeta>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TargetMeta {
pub name: String,
#[serde(default)]
pub kind: Vec<String>,
#[serde(default, rename = "required-features")]
pub required_features: Vec<String>,
}
impl TargetMeta {
#[must_use]
fn primary_kind(&self) -> &str {
self.kind.first().map_or("", String::as_str)
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct WorkspaceMetadata {
#[serde(default)]
pub packages: Vec<PackageMeta>,
}
impl WorkspaceMetadata {
#[must_use]
pub fn package_names(&self) -> BTreeSet<String> {
self.packages.iter().map(|p| p.name.clone()).collect()
}
}
pub fn load(cwd: &Path) -> Result<WorkspaceMetadata, FloorError> {
let out = super::runner::isolated_command(&super::runner::cargo_bin(), None)
.arg("metadata")
.arg("--no-deps")
.arg("--format-version")
.arg("1")
.current_dir(cwd)
.output()
.map_err(|e| FloorError::Capture {
what: "metadata",
message: format!("could not run `cargo metadata`: {e}"),
})?;
if !out.status.success() {
return Err(FloorError::Capture {
what: "metadata",
message: format!(
"`cargo metadata` exited {:?}; failing closed. stderr: {}",
out.status.code(),
String::from_utf8_lossy(&out.stderr).trim()
),
});
}
serde_json::from_slice(&out.stdout).map_err(|e| FloorError::Capture {
what: "metadata",
message: format!("could not parse `cargo metadata` output: {e}"),
})
}
fn target_key(package: &str, kind: &str, target: &str) -> String {
format!("{package}/{kind}/{target}")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ManifestKind {
Lib,
Bin,
Test,
Bench,
Example,
}
impl ManifestKind {
#[must_use]
fn is_test_producing(self) -> bool {
matches!(
self,
ManifestKind::Lib | ManifestKind::Bin | ManifestKind::Test
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ManifestTarget {
pub kind: ManifestKind,
pub name: Option<String>,
pub harness: Option<bool>,
pub test: Option<bool>,
}
#[derive(Debug, Default, Deserialize)]
struct RawManifest {
lib: Option<RawTarget>,
#[serde(default)]
bin: Vec<RawTarget>,
#[serde(default)]
test: Vec<RawTarget>,
#[serde(default)]
bench: Vec<RawTarget>,
#[serde(default)]
example: Vec<RawTarget>,
}
#[derive(Debug, Default, Deserialize)]
struct RawTarget {
name: Option<String>,
harness: Option<bool>,
test: Option<bool>,
}
impl RawTarget {
fn into_manifest(self, kind: ManifestKind) -> ManifestTarget {
ManifestTarget {
kind,
name: self.name,
harness: self.harness,
test: self.test,
}
}
}
#[must_use]
pub fn parse_manifest_targets(manifest: &str) -> Vec<ManifestTarget> {
let Ok(raw) = toml::from_str::<RawManifest>(manifest) else {
return Vec::new();
};
let mut out = Vec::new();
if let Some(lib) = raw.lib {
out.push(lib.into_manifest(ManifestKind::Lib));
}
for b in raw.bin {
out.push(b.into_manifest(ManifestKind::Bin));
}
for t in raw.test {
out.push(t.into_manifest(ManifestKind::Test));
}
for b in raw.bench {
out.push(b.into_manifest(ManifestKind::Bench));
}
for e in raw.example {
out.push(e.into_manifest(ManifestKind::Example));
}
out
}
#[must_use]
pub fn forged_harness_in_manifest(manifest: &str) -> Vec<ManifestTarget> {
parse_manifest_targets(manifest)
.into_iter()
.filter(|t| t.kind.is_test_producing() && t.harness == Some(false))
.collect()
}
#[must_use]
pub fn forged_harness_targets(meta: &WorkspaceMetadata) -> Vec<String> {
let mut forged = Vec::new();
for pkg in &meta.packages {
let Ok(manifest) = std::fs::read_to_string(&pkg.manifest_path) else {
continue;
};
for t in forged_harness_in_manifest(&manifest) {
let kind = match t.kind {
ManifestKind::Lib => "lib",
ManifestKind::Bin => "bin",
ManifestKind::Test => "test",
ManifestKind::Bench => "bench",
ManifestKind::Example => "example",
};
let name = t.name.as_deref().unwrap_or("<default>");
forged.push(format!("{} [{kind}] {name}", pkg.name));
}
}
forged
}
fn expected_test_targets_with<F>(meta: &WorkspaceMetadata, mut read_manifest: F) -> BTreeSet<String>
where
F: FnMut(&Path) -> Option<String>,
{
let mut expected = BTreeSet::new();
for pkg in &meta.packages {
let Some(manifest) = read_manifest(&pkg.manifest_path) else {
continue;
};
let mtargets = parse_manifest_targets(&manifest);
for t in &pkg.targets {
let kind = t.primary_kind();
if kind != "lib" && kind != "test" {
continue;
}
if !t.required_features.is_empty() {
continue;
}
let mkind = if kind == "lib" {
ManifestKind::Lib
} else {
ManifestKind::Test
};
let disabled = mtargets.iter().any(|mt| {
mt.kind == mkind
&& manifest_matches_target(mt, &t.name)
&& (mt.test == Some(false) || mt.harness == Some(false))
});
if !disabled {
expected.insert(target_key(&pkg.name, kind, &t.name));
}
}
}
expected
}
fn manifest_matches_target(mt: &ManifestTarget, target_name: &str) -> bool {
match (mt.kind, &mt.name) {
(ManifestKind::Lib, _) => true,
(_, Some(name)) => name == target_name,
(_, None) => false,
}
}
#[must_use]
pub fn expected_test_targets(meta: &WorkspaceMetadata) -> BTreeSet<String> {
expected_test_targets_with(meta, |p| std::fs::read_to_string(p).ok())
}
#[must_use]
pub fn has_test_targets(meta: &WorkspaceMetadata) -> bool {
meta.packages.iter().any(|p| {
p.targets
.iter()
.any(|t| matches!(t.primary_kind(), "lib" | "test" | "bin"))
})
}
pub fn reject_forged_harness(meta: &WorkspaceMetadata) -> Result<(), FloorError> {
let mut forged = Vec::new();
for pkg in &meta.packages {
let Ok(manifest) = std::fs::read_to_string(&pkg.manifest_path) else {
continue;
};
if toml::from_str::<RawManifest>(&manifest).is_err() {
return Err(FloorError::Capture {
what: "tests",
message: format!(
"package {}'s Cargo.toml at {} is unparseable; refusing to certify a capture \
whose custom-harness declarations cannot be verified (failing closed)",
pkg.name,
pkg.manifest_path.display()
),
});
}
for t in forged_harness_in_manifest(&manifest) {
let kind = match t.kind {
ManifestKind::Lib => "lib",
ManifestKind::Bin => "bin",
ManifestKind::Test => "test",
ManifestKind::Bench => "bench",
ManifestKind::Example => "example",
};
let name = t.name.as_deref().unwrap_or("<default>");
forged.push(format!("{} [{kind}] {name}", pkg.name));
}
}
if forged.is_empty() {
Ok(())
} else {
Err(FloorError::Capture {
what: "tests",
message: format!(
"forged custom test harness (`harness = false`) on test-producing target(s): {}; \
failing closed (a `[[bench]] harness = false` is allowed, these are not)",
forged.join(", ")
),
})
}
}
pub fn verify_enumeration(
meta: &WorkspaceMetadata,
captured: &BTreeSet<String>,
) -> Result<(), FloorError> {
if captured.is_empty() && has_test_targets(meta) {
return Err(FloorError::Capture {
what: "tests",
message: "captured test enumeration is empty but `cargo metadata` reports \
test-producing targets; failing closed (compromised/empty enumeration)"
.into(),
});
}
let expected = expected_test_targets(meta);
let missing: Vec<&String> = expected.difference(captured).collect();
if missing.is_empty() {
Ok(())
} else {
Err(FloorError::Capture {
what: "tests",
message: format!(
"captured enumeration is missing metadata-expected test target(s): {}; \
failing closed",
missing
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", ")
),
})
}
}
#[must_use]
pub fn lib_target_name(pkg: &PackageMeta) -> Option<String> {
pkg.targets
.iter()
.find(|t| t.primary_kind() == "lib")
.map(|t| t.name.clone())
}
#[cfg(test)]
mod tests {
use super::*;
fn meta_from(json: &str) -> WorkspaceMetadata {
serde_json::from_str(json).unwrap()
}
#[test]
fn parse_manifest_reads_target_flags() {
let m = r#"
[package]
name = "p"
[lib]
test = false
[[test]]
name = "e2e"
[[bench]]
name = "crit"
harness = false
[[bin]]
name = "cli"
harness = false
"#;
let ts = parse_manifest_targets(m);
assert!(ts.contains(&ManifestTarget {
kind: ManifestKind::Lib,
name: None,
harness: None,
test: Some(false),
}));
assert!(ts.contains(&ManifestTarget {
kind: ManifestKind::Bench,
name: Some("crit".into()),
harness: Some(false),
test: None,
}));
}
#[test]
fn forged_harness_flags_test_producing_but_allows_bench() {
let m = r#"
[package]
name = "p"
[[bench]]
name = "crit"
harness = false
[[test]]
name = "e2e"
harness = false
[lib]
harness = false
"#;
let forged = forged_harness_in_manifest(m);
assert_eq!(forged.len(), 2);
assert!(forged.iter().any(|t| t.kind == ManifestKind::Test));
assert!(forged.iter().any(|t| t.kind == ManifestKind::Lib));
assert!(!forged.iter().any(|t| t.kind == ManifestKind::Bench));
}
#[test]
fn forged_harness_ignores_declared_true_and_unset() {
let m = r#"
[package]
name = "p"
[lib]
harness = true
[[test]]
name = "e2e"
"#;
assert!(forged_harness_in_manifest(m).is_empty());
}
#[test]
fn expected_targets_includes_lib_and_test_excludes_disabled() {
let meta = meta_from(
r#"{"packages":[
{"name":"pkgA","manifest_path":"/A/Cargo.toml","targets":[
{"name":"pkgA","kind":["lib"]},
{"name":"e2e","kind":["test"]},
{"name":"cli","kind":["bin"]},
{"name":"crit","kind":["bench"]}
]}
]}"#,
);
let expected = expected_test_targets_with(&meta, |_| {
Some(
r#"[package]
name = "pkgA"
[[test]]
name = "e2e"
test = false"#
.to_string(),
)
});
assert!(expected.contains("pkgA/lib/pkgA"));
assert!(!expected.contains("pkgA/test/e2e"), "test=false excluded");
assert!(!expected.iter().any(|k| k.contains("/bin/")));
assert!(!expected.iter().any(|k| k.contains("/bench/")));
}
#[test]
fn expected_targets_skips_unreadable_manifest_without_false_block() {
let meta = meta_from(
r#"{"packages":[
{"name":"pkgA","manifest_path":"/A/Cargo.toml","targets":[
{"name":"pkgA","kind":["lib"]}
]}
]}"#,
);
let expected = expected_test_targets_with(&meta, |_| None);
assert!(expected.is_empty());
}
#[test]
fn has_test_targets_detects_presence() {
let with = meta_from(
r#"{"packages":[{"name":"p","manifest_path":"/p","targets":[{"name":"p","kind":["lib"]}]}]}"#,
);
assert!(has_test_targets(&with));
let without = meta_from(
r#"{"packages":[{"name":"p","manifest_path":"/p","targets":[{"name":"p","kind":["custom-build"]}]}]}"#,
);
assert!(!has_test_targets(&without));
}
#[test]
fn verify_enumeration_fails_closed_on_empty_when_tests_exist() {
let meta = meta_from(
r#"{"packages":[{"name":"p","manifest_path":"/p","targets":[{"name":"p","kind":["lib"]}]}]}"#,
);
let empty = BTreeSet::new();
assert!(verify_enumeration(&meta, &empty).is_err());
let no_tests = meta_from(
r#"{"packages":[{"name":"p","manifest_path":"/p","targets":[{"name":"p","kind":["custom-build"]}]}]}"#,
);
assert!(verify_enumeration(&no_tests, &empty).is_ok());
}
#[test]
fn verify_enumeration_requires_expected_superset() {
let meta = meta_from(
r#"{"packages":[{"name":"p","manifest_path":"/nonexistent-Cargo.toml","targets":[
{"name":"p","kind":["lib"]}]}]}"#,
);
let captured: BTreeSet<String> = ["p/lib/p".to_string()].into_iter().collect();
assert!(verify_enumeration(&meta, &captured).is_ok());
}
#[test]
fn reject_forged_harness_errs_on_unreadable_ok() {
let meta = meta_from(
r#"{"packages":[{"name":"p","manifest_path":"/no/such/Cargo.toml","targets":[]}]}"#,
);
assert!(reject_forged_harness(&meta).is_ok());
}
#[test]
fn reject_forged_harness_fails_closed_on_unparseable_manifest() {
let dir = tempfile::TempDir::new().unwrap();
let manifest = dir.path().join("Cargo.toml");
std::fs::write(&manifest, "this is [not valid toml == ").unwrap();
let meta = meta_from(&format!(
r#"{{"packages":[{{"name":"p","manifest_path":"{}","targets":[]}}]}}"#,
manifest.display()
));
let err = reject_forged_harness(&meta).unwrap_err();
assert!(format!("{err}").contains("unparseable"), "{err}");
}
#[test]
fn expected_targets_excludes_required_features_targets() {
let meta = meta_from(
r#"{"packages":[
{"name":"p","manifest_path":"/p/Cargo.toml","targets":[
{"name":"p","kind":["lib"]},
{"name":"gpu","kind":["test"],"required-features":["gpu"]}
]}
]}"#,
);
let expected =
expected_test_targets_with(&meta, |_| Some("[package]\nname = \"p\"".to_string()));
assert!(expected.contains("p/lib/p"));
assert!(
!expected.contains("p/test/gpu"),
"required-features excluded"
);
}
#[test]
fn package_names_are_sorted_and_unique() {
let meta = meta_from(
r#"{"packages":[
{"name":"b","manifest_path":"/b","targets":[]},
{"name":"a","manifest_path":"/a","targets":[]}
]}"#,
);
let names: Vec<String> = meta.package_names().into_iter().collect();
assert_eq!(names, vec!["a".to_string(), "b".to_string()]);
}
}