Something went wrong. Try again.
atproto Thingiverse but good
Something went wrong. Try again.
26 kB · 792 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793pub use polymodel_ldraw_core::{ CanonicalRecord, Diagnostic, LdMeshSemantic, SceneRecord, SemanticRecord, Span, SyntaxRecord, TexmapEvent, TexmapKind,};use serde::{Deserialize, Serialize};use sha2::{Digest, Sha256};use std::collections::BTreeSet;
pub const SCHEMA_VERSION: &str = "ldraw-authority-v1";pub const CANDIDATE_OBSERVATION_SCHEMA_VERSION: &str = "ldraw-candidate-observation-v1";pub const ORACLE_SCHEMA_VERSION: &str = "ldraw-oracle-v4";pub const ORACLE_WRAPPER_SCHEMA_VERSION: &str = "wrapper-v4";pub const ORACLE_PROJECTION_RULE_VERSION: &str = "pm-78-oracle-only-v4";pub const ORACLE_PATCH_IDENTITY: &str = "ldparse-oracle-access.patch@pm-78-v2";pub const LDRAW_LIMITS: &[(&str, u64)] = &[ ("resource_bytes", 100 * 1024 * 1024), ("line_bytes", 1024 * 1024), ("files", 16_384), ("include_depth", 256), ("commands", 5_000_000), ("instances", 5_000_000), ("triangles", 20_000_000), ("lines", 20_000_000), ("textures", 1_024), ("fetches", 8), ("diagnostics", 10_000),];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Ord, PartialOrd)]#[serde(rename_all = "snake_case")]pub enum Profile { Strict, Compatibility, Lossless,}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]#[serde(rename_all = "snake_case")]pub enum AuthorityClass { RatifiedSpec, PolymodelSafety, HOwned, Observation,}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]#[serde(rename_all = "snake_case")]pub enum ExpectedOutcome { Accepted, Rejected, Preserved, Cancelled, Limited,}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OraclePosition(pub Option<f32>, pub Option<f32>, pub Option<f32>);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OracleMesh { pub vertex_count: usize, pub positions: Vec<OraclePosition>, pub normal_count: usize, pub normals: Vec<OraclePosition>, pub indices: Vec<u32>, pub bf_indices: Vec<u32>, pub triangle_count: usize, pub bf_triangle_count: usize,}
/// Compare the LDParse mesh projection with the candidate's authoritative/// semantic mesh. Oracle coordinates are f32 because that is the pinned C++/// projection; candidate f64 values must round to the same finite f32 bits.pub fn compare_mesh_semantic( model_name: &str, expected: &OracleMesh, expected_step_offsets: &[usize], actual: &LdMeshSemantic,) -> Result<(), String> { fn rounded(value: f64) -> Option<f32> { if value.is_finite() { let value = value as f32; value.is_finite().then_some(value) } else { None } } fn position_matches(actual: &[f64; 3], expected: &OraclePosition) -> bool { [rounded(actual[0]), rounded(actual[1]), rounded(actual[2])] .into_iter() .zip([expected.0, expected.1, expected.2]) .all(|(actual, expected)| match (actual, expected) { (Some(actual), Some(expected)) => actual.to_bits() == expected.to_bits(), (None, None) => true, _ => false, }) } if actual.positions.len() != expected.vertex_count || actual.positions.len() != expected.positions.len() { return Err(format!( "{model_name} positions length mismatch: oracle_count={} oracle_values={} candidate={}", expected.vertex_count, expected.positions.len(), actual.positions.len() )); } for (index, (actual, expected)) in actual.positions.iter().zip(&expected.positions).enumerate() { if !position_matches(actual, expected) { return Err(format!( "{model_name} position mismatch at {index}: oracle={expected:?} candidate={actual:?}" )); } } let actual_normals = actual.normals.as_deref().unwrap_or(&[]); if actual_normals.len() != expected.normal_count || actual_normals.len() != expected.normals.len() { return Err(format!( "{model_name} normals length mismatch: oracle_count={} oracle_values={} candidate={}", expected.normal_count, expected.normals.len(), actual_normals.len() )); } for (index, (actual, expected)) in actual_normals.iter().zip(&expected.normals).enumerate() { if !position_matches(actual, expected) { return Err(format!( "{model_name} normal mismatch at {index}: oracle={expected:?} candidate={actual:?}" )); } } if actual.indices != expected.indices { return Err(format!( "{model_name} forward indices mismatch: oracle={:?} candidate={:?}", expected.indices, actual.indices )); } if actual.bf_indices != expected.bf_indices { return Err(format!( "{model_name} reverse indices mismatch: oracle={:?} candidate={:?}", expected.bf_indices, actual.bf_indices )); } if actual.step_offsets != expected_step_offsets { return Err(format!( "{model_name} step offsets mismatch: oracle={expected_step_offsets:?} candidate={:?}", actual.step_offsets )); } Ok(())}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OracleTransform { pub translation: OraclePosition, pub matrix: Vec<Option<f32>>,}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OracleChild { pub index: usize, pub bfc: String, pub transform: OracleTransform, pub child_name: Option<String>, pub child_path: Option<String>,}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OracleModel { pub name: String, pub model_index: u32, pub path: String, pub certify: String, pub winding: String, pub default_color: u64, pub step_ends: Vec<usize>, pub local_colors: Vec<OracleColor>, pub mesh: OracleMesh, pub children: Vec<OracleChild>,}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OracleColor { pub code: u16, pub slot: u16,}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OracleToken { pub kind: String, pub kind_code: i32, pub value: serde_json::Value, pub line: i32, pub column: i32,}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OracleTokenLine { pub index: usize, pub text: String, pub tokens: Vec<OracleToken>,}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OracleTokenizedModel { pub model_index: usize, pub name: String, pub lines: Vec<OracleTokenLine>,}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OracleGeometry { pub triangles: usize, pub quads: usize, pub lines: usize, pub conditional_lines: usize, pub includes: usize, pub triangle_positions: Vec<Vec<OraclePosition>>,}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OracleDiagnostic { pub message: String, pub token: String, pub fatal: bool,}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OracleError { pub kind: String, pub message: String, pub diagnostics: Vec<OracleDiagnostic>,}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OracleAuthorityRule { pub path: String, pub class: String, pub rule: String, pub evidence: String,}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OracleAuthorityTable { pub version: String, pub classes: Vec<String>, pub rules: Vec<OracleAuthorityRule>,}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct OracleProjection { pub schema_version: String, pub oracle_revision: String, pub wrapper_schema_version: String, pub projection_rule_version: String, pub patch_identity: String, pub fixture_path: String, pub fixture_sha256: String, pub authority_rules: OracleAuthorityTable, pub status: String, pub model_names: Vec<String>, pub root_model: Option<OracleModel>, pub models: Vec<OracleModel>, pub tokenized_models: Vec<OracleTokenizedModel>, pub tokenized_geometry: Option<OracleGeometry>, pub resolved_geometry: Option<OracleGeometry>, pub diagnostics: Vec<OracleDiagnostic>, pub error: Option<OracleError>,}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]#[serde(rename_all = "snake_case")]pub enum DerivationAuthority { OracleDirect, OracleMechanical, InputMechanical, PolymodelPolicy,}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct DerivationRef { pub field: String, pub authority: DerivationAuthority, pub source: String, pub revision: String, pub locator: String, pub basis: String, pub decision: Option<String>,}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct GeneratedMetadata { pub authority: String, pub oracle_revision: String, pub wrapper_schema_version: String, pub projection_rule_version: String, pub patch_identity: String, pub fixture_path: String, pub fixture_sha256: String,}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct PolicyExpected { pub limits: Vec<String>, pub texmap_events: Vec<serde_json::Value>, pub texmap_geometry: Vec<serde_json::Value>, pub data_payloads: Vec<serde_json::Value>,}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct ExpectedSyntaxRecord { pub line_type: u8, pub raw_tail: String, pub fields: Vec<String>,}
pub fn compare_syntax_records( fixture_id: &str, expected: &[ExpectedSyntaxRecord], actual: &[serde_json::Value],) -> Result<(), String> { if expected.len() != actual.len() { return Err(format!( "{fixture_id} syntax record count mismatch: expected={} actual={}", expected.len(), actual.len() )); } for (index, (expected, actual)) in expected.iter().zip(actual).enumerate() { let actual_type = actual.get("line_type").and_then(serde_json::Value::as_u64); if actual_type != Some(u64::from(expected.line_type)) { return Err(format!( "{fixture_id} syntax[{index}] line type mismatch: expected={} actual={actual_type:?}", expected.line_type )); } let actual_raw_tail = actual .get("raw_tail") .and_then(serde_json::Value::as_str) .unwrap_or_default(); if actual_raw_tail != expected.raw_tail { return Err(format!( "{fixture_id} syntax[{index}] raw tail mismatch: expected={:?} actual={actual_raw_tail:?}", expected.raw_tail )); } let actual_fields = match actual.get("fields").and_then(serde_json::Value::as_array) { Some(fields) => fields .iter() .map(|value| { value .as_str() .ok_or_else(|| { format!("{fixture_id} syntax[{index}] field is not a string: {value:?}") }) .map(String::from) }) .collect::<Result<Vec<_>, _>>()?, None => Vec::new(), }; let expected_fields = expected .fields .iter() .map(String::as_str) .collect::<Vec<_>>(); if actual_fields != expected_fields { return Err(format!( "{fixture_id} syntax[{index}] fields mismatch: expected={expected_fields:?} actual={actual_fields:?}" )); } } Ok(())}
#[derive(Debug, Clone, PartialEq, Serialize)]#[serde(deny_unknown_fields)]pub struct ExpectedRecord { pub outcome: ExpectedOutcome, pub profile: Profile, #[serde(default)] pub syntax_expected: Vec<ExpectedSyntaxRecord>, pub oracle_expected: OracleProjection, pub polymodel_policy_expected: PolicyExpected, pub derivations: Vec<DerivationRef>, pub generated: GeneratedMetadata,}
impl<'de> Deserialize<'de> for ExpectedRecord { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de>, { #[derive(Deserialize)] #[serde(deny_unknown_fields)] struct Wire { outcome: ExpectedOutcome, profile: Profile, #[serde(default)] syntax_expected: Vec<ExpectedSyntaxRecord>, oracle_expected: OracleProjection, polymodel_policy_expected: PolicyExpected, derivations: Vec<DerivationRef>, generated: GeneratedMetadata, } let wire = Wire::deserialize(deserializer)?; if wire.generated.authority != "ldparse_oracle_with_policy" { return Err(serde::de::Error::custom( "expected record is not an LDParse oracle projection", )); } Ok(Self { outcome: wire.outcome, profile: wire.profile, syntax_expected: wire.syntax_expected, oracle_expected: wire.oracle_expected, polymodel_policy_expected: wire.polymodel_policy_expected, derivations: wire.derivations, generated: wire.generated, }) }}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]#[serde(deny_unknown_fields)]pub struct Provenance { pub id: String, pub source_url: Option<String>, pub source_revision: String, pub sha256: String, pub license: String, pub original_test: String, pub modifications: String, pub authority: AuthorityClass,}
#[derive(Debug, Clone, PartialEq)]pub struct FixtureCase { pub id: &'static str, pub relative_path: &'static str, pub bytes: &'static [u8], pub tags: &'static [&'static str], pub profiles: &'static [Profile], pub profile: Profile, pub outcome: ExpectedOutcome, pub authority: AuthorityClass, pub provenance: Provenance, pub expected: ExpectedRecord,}
impl FixtureCase { pub fn sha256(&self) -> String { hex_digest(self.bytes) }
pub fn applies_to(&self, profile: Profile) -> bool { self.profiles.contains(&profile) && self.profile == profile }}
pub fn serialize_candidate_observation<T: Serialize>( record: &T,) -> Result<Vec<u8>, serde_json::Error> { serde_json::to_vec(record)}
pub fn serialize_expected(record: &ExpectedRecord) -> Result<Vec<u8>, serde_json::Error> { serde_json::to_vec(record)}
pub fn parse_expected(json: &str) -> Result<ExpectedRecord, serde_json::Error> { serde_json::from_str(json)}
pub fn hex_digest(bytes: &[u8]) -> String { Sha256::digest(bytes) .iter() .map(|byte| format!("{byte:02x}")) .collect()}
pub fn normalize_certification(value: &str) -> Option<&'static str> { match value { "indeterminate" => Some("unknown"), "true" | "certified" => Some("certified"), "false" | "uncertified" => Some("uncertified"), "unknown" => Some("unknown"), _ => None, }}
pub fn ordered_colors(colors: &[OracleColor]) -> Vec<(u16, u16)> { colors .iter() .map(|color| (color.code, color.slot)) .collect()}
pub fn unique_strings(values: impl IntoIterator<Item = String>) -> bool { let values = values.into_iter().collect::<Vec<_>>(); values.len() == values.iter().collect::<BTreeSet<_>>().len()}
const REQUIRED_DERIVATION_GROUPS: &[(&str, &[&str])] = &[ ("syntax", &["syntax_expected"]), ("policy/limits", &["polymodel_policy_expected.limits"]), ( "policy/texmap", &[ "polymodel_policy_expected.texmap_events", "polymodel_policy_expected.texmap_geometry", ], ), ("policy/data", &["polymodel_policy_expected.data_payloads"]),];
fn derivation_group(field: &str) -> Option<&'static str> { match field { "polymodel_policy_expected.limits" => Some("policy/limits"), "polymodel_policy_expected.texmap_events" | "polymodel_policy_expected.texmap_geometry" => { Some("policy/texmap") } "polymodel_policy_expected.data_payloads" => Some("policy/data"), "syntax_expected" => Some("syntax"), _ => None, }}
fn validate_oracle_expected(projection: &OracleProjection, fixture_id: &str) -> Result<(), String> { match (projection.status.as_str(), projection.error.is_some()) { ("ok", false) | ("error", true) => {} (status, has_error) => { return Err(format!( "invalid oracle projection status/error shape {fixture_id}: status={status:?}, error={has_error}" )); } } let mut names = BTreeSet::new(); for (index, model) in projection.models.iter().enumerate() { if model.name.is_empty() { return Err(format!("oracle model has empty name {fixture_id}")); } if model.model_index != index as u32 { return Err(format!( "oracle model indices are not sequential {fixture_id}" )); } if !names.insert(&model.name) { return Err(format!( "duplicate oracle model name {fixture_id}: {}", model.name )); } if normalize_certification(&model.certify).is_none() { return Err(format!( "invalid oracle model certification {fixture_id}: {}", model.certify )); } } Ok(())}
const ORACLE_AUTHORITY_CLASSES: &[&str] = &[ "oracle_direct", "oracle_mechanical", "input_mechanical", "polymodel_policy",];const ORACLE_AUTHORITY_ROOTS: &[&str] = &[ "outcome", "profile", "syntax_expected", "oracle_expected", "polymodel_policy_expected", "derivations",];
pub fn validate_oracle_authority_table( table: &OracleAuthorityTable, fixture_id: &str,) -> Result<(), String> { if table.version != ORACLE_PROJECTION_RULE_VERSION { return Err(format!( "oracle authority revision mismatch {fixture_id}: {}", table.version )); } if table.classes != ORACLE_AUTHORITY_CLASSES { return Err(format!("oracle authority classes mismatch {fixture_id}")); } let mut paths = BTreeSet::new(); for rule in &table.rules { if rule.path.trim().is_empty() || rule.path.contains('*') { return Err(format!( "oracle authority path is not explicit {fixture_id}: {}", rule.path )); } if !ORACLE_AUTHORITY_CLASSES.contains(&rule.class.as_str()) { return Err(format!( "unknown oracle authority class {fixture_id}: {}", rule.class )); } if rule.rule.trim().is_empty() || rule.evidence.trim().is_empty() { return Err(format!( "incomplete oracle authority rule {fixture_id}: {}", rule.path )); } if !paths.insert(rule.path.as_str()) { return Err(format!( "duplicate oracle authority rule {fixture_id}: {}", rule.path )); } } for root in ORACLE_AUTHORITY_ROOTS { if !paths .iter() .any(|path| *path == *root || path.starts_with(&format!("{root}."))) { return Err(format!( "oracle authority table does not cover {root} {fixture_id}" )); } } Ok(())}
pub fn validate_expected_evidence( expected: &ExpectedRecord, fixture_id: &str,) -> Result<(), String> { if expected.generated.authority != "ldparse_oracle_with_policy" { return Err(format!( "expected record is not an LDParse oracle projection {fixture_id}" )); } if expected.derivations.is_empty() { return Err(format!("missing derivation evidence {fixture_id}")); }
let mut groups = BTreeSet::new(); for derivation in &expected.derivations { if derivation.field.trim().is_empty() || derivation.source.trim().is_empty() || derivation.revision.trim().is_empty() || derivation.locator.trim().is_empty() || derivation.basis.trim().is_empty() { return Err(format!("incomplete derivation evidence {fixture_id}")); } if derivation.basis.contains("Generated from") || derivation.basis.contains("cargo run") || derivation.basis.contains("refresh") { return Err(format!( "derivation evidence claims generated or refreshed output {fixture_id}" )); } if !derivation.locator.contains(fixture_id) || !derivation.locator.contains("sha256:") || !derivation.revision.contains("PM-78") { return Err(format!( "derivation evidence lacks fixture/hash/revision binding {fixture_id}" )); } if !derivation.field.starts_with("polymodel_policy_expected.") && derivation.field != "syntax_expected" { return Err(format!( "derivation evidence must target syntax or policy expected fields {fixture_id}: {}", derivation.field )); } if !matches!( derivation.authority, DerivationAuthority::InputMechanical | DerivationAuthority::PolymodelPolicy ) { return Err(format!( "oracle field cannot use derivation evidence {fixture_id}: {}", derivation.field )); } let group = derivation_group(&derivation.field).ok_or_else(|| { format!( "unknown policy derivation field {} {fixture_id}", derivation.field ) })?; groups.insert(group); } for (group, _) in REQUIRED_DERIVATION_GROUPS { if !groups.contains(group) { return Err(format!("missing derivation group {group} {fixture_id}")); } }
let projection = &expected.oracle_expected; validate_oracle_authority_table(&projection.authority_rules, fixture_id)?; if expected.generated.fixture_sha256 != projection.fixture_sha256 || expected.generated.fixture_path != projection.fixture_path || expected.generated.oracle_revision != projection.oracle_revision || expected.generated.wrapper_schema_version != projection.wrapper_schema_version || expected.generated.projection_rule_version != projection.projection_rule_version || expected.generated.patch_identity != projection.patch_identity { return Err(format!( "generated metadata does not match oracle projection {fixture_id}" )); } validate_oracle_expected(projection, fixture_id)}
#[cfg(test)]mod tests { use super::*;
#[test] fn expected_evidence_rejects_missing_derivations_before_oracle_data() { let expected = ExpectedRecord { outcome: ExpectedOutcome::Accepted, profile: Profile::Strict, syntax_expected: Vec::new(), oracle_expected: serde_json::from_str( r#"{ "schema_version":"ldraw-oracle-v4", "oracle_revision":"0fe78dcee22982f26cceced1b483340faa4d04e9", "wrapper_schema_version":"wrapper-v4", "projection_rule_version":"pm-78-oracle-only-v4", "patch_identity":"ldparse-oracle-access.patch@pm-78-v2", "fixture_path":"corpus/fixture.ldr", "fixture_sha256":"fixture", "authority_rules":{"version":"pm-78-oracle-only-v4","classes":[],"rules":[]}, "status":"error", "model_names":[], "root_model":null, "models":[], "tokenized_models":[], "tokenized_geometry":null, "resolved_geometry":null, "diagnostics":[], "error":{"kind":"test","message":"test","diagnostics":[]} }"#, ) .unwrap(), polymodel_policy_expected: PolicyExpected { limits: Vec::new(), texmap_events: Vec::new(), texmap_geometry: Vec::new(), data_payloads: Vec::new(), }, derivations: Vec::new(), generated: GeneratedMetadata { authority: "ldparse_oracle_with_policy".to_owned(), oracle_revision: String::new(), wrapper_schema_version: String::new(), projection_rule_version: String::new(), patch_identity: String::new(), fixture_path: String::new(), fixture_sha256: String::new(), }, }; assert_eq!( validate_expected_evidence(&expected, "fixture").unwrap_err(), "missing derivation evidence fixture" ); }}