use std::collections::BTreeMap;
use std::path::Path;
use openusd::sdf;
use openusd::usda;
const ASSETS: &str = concat!(
env!("CARGO_WORKSPACE_DIR"),
"vendor/core-spec-supplemental-release_dec2025/file_formats/tests/assets/text"
);
fn assert_text_format(name: &str) {
let assets = Path::new(ASSETS);
let usda = assets.join("usda").join(format!("{name}.usda"));
let baseline = assets.join("baseline").join(format!("{name}.json"));
let reader = usda::read_file(&usda).unwrap_or_else(|e| panic!("failed to parse {}: {e:#}", usda.display()));
let mut actual: BTreeMap<String, BTreeMap<&str, &sdf::Value>> = BTreeMap::new();
for (path, spec) in reader.iter() {
let fields: BTreeMap<&str, &sdf::Value> = spec.fields.iter().map(|(k, v)| (k.as_str(), v)).collect();
actual.insert(path.to_string(), fields);
}
let mut actual_json = serde_json::to_value(&actual).expect("failed to serialize actual data");
let mut expected_json: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&baseline).expect("failed to read baseline"))
.expect("failed to parse baseline JSON");
normalize_json(&mut actual_json);
normalize_json(&mut expected_json);
drop_relationship_variability(&mut actual_json);
drop_relationship_variability(&mut expected_json);
check_unregistered_fields(&mut actual_json, &mut expected_json, name);
expand_baseline_escapes(&mut expected_json);
let diffs = diff_json::compare_values(&actual_json, &expected_json);
assert!(
diffs.is_empty(),
"mismatch for {name}:\n{}",
diff_json::DiffFormatter::new().format(&diffs)
);
}
fn normalize_json(v: &mut serde_json::Value) {
match v {
serde_json::Value::Number(n) if n.is_f64() => {
let f = n.as_f64().unwrap();
if f.fract() == 0.0 && f.is_finite() && f.abs() < i64::MAX as f64 {
*v = serde_json::Value::from(f as i64);
} else {
let short = format!("{}", f as f32);
let long = format!("{f}");
if short.len() < long.len()
&& let Ok(clean) = short.parse::<f64>()
{
*v = serde_json::json!(clean);
}
}
}
serde_json::Value::Array(a) => a.iter_mut().for_each(normalize_json),
serde_json::Value::Object(m) => {
if m.contains_key("layerOffset") || m.contains_key("asset") {
m.remove("customData");
}
m.values_mut().for_each(normalize_json);
}
_ => {}
}
}
fn expand_baseline_escapes(v: &mut serde_json::Value) {
match v {
serde_json::Value::String(text) => {
*text = text.replace("\\'", "'").replace("\\\"", "\"");
}
serde_json::Value::Object(fields) => fields.values_mut().for_each(expand_baseline_escapes),
serde_json::Value::Array(items) => items.iter_mut().for_each(expand_baseline_escapes),
_ => {}
}
}
fn drop_relationship_variability(v: &mut serde_json::Value) {
let Some(specs) = v.as_object_mut() else { return };
for spec in specs.values_mut() {
let Some(fields) = spec.as_object_mut() else { continue };
if !fields.contains_key("typeName") && fields.get("variability") == Some(&serde_json::json!("uniform")) {
fields.remove("variability");
}
}
}
const UNREGISTERED_FIELDS: &[(&str, &str, &str, &str)] = &[
("layermetadata", "/", "baz", r#""None""#),
("relations", "/foo/otherFoo.everything:in:one", "foo", r#""\"bar\"""#),
(
"relations",
"/foo/otherFoo.everything:in:one",
"this_metadata_value",
r#"{"prepend": ["5.67"]}"#,
),
];
fn check_unregistered_fields(actual: &mut serde_json::Value, expected: &mut serde_json::Value, name: &str) {
for (_, path, field, ours) in UNREGISTERED_FIELDS.iter().filter(|(file, ..)| *file == name) {
let ours: serde_json::Value = serde_json::from_str(ours).expect("valid JSON in the table");
let found = actual
.get(path)
.and_then(|fields| fields.get(field))
.unwrap_or_else(|| panic!("{name}: {path} has no {field}"));
assert_eq!(found, &ours, "{name}: {path}.{field}");
for side in [&mut *actual, expected] {
if let Some(fields) = side.get_mut(path).and_then(serde_json::Value::as_object_mut) {
fields.remove(*field);
}
}
}
}
mod text_parser {
use super::*;
#[test]
fn test_empty() {
assert_text_format("empty");
}
#[test]
fn test_simple() {
assert_text_format("simple");
}
#[test]
fn attributes_malformed() {
let usda = Path::new(ASSETS).join("usda").join("attributes.usda");
let error = usda::read_file(&usda).expect_err("bare scalars against vector3f samples are rejected");
let message = format!("{error:#}");
assert!(
message.contains("vector3f"),
"the error names the offending declaration: {message}"
);
}
#[test]
fn test_relations() {
assert_text_format("relations");
}
#[test]
fn test_primmetadata() {
assert_text_format("primmetadata");
}
#[test]
fn test_layermetadata() {
assert_text_format("layermetadata");
}
#[test]
fn test_dictionaries() {
assert_text_format("dictionaries");
}
#[test]
fn test_splines() {
assert_text_format("splines");
}
#[test]
#[ignore = "Python file_formats baseline uses non-canonical variant-child slash; we follow C++/pcp.txt"]
fn test_variants() {
assert_text_format("variants");
}
#[test]
fn test_geometryattributes() {
assert_text_format("geometryattributes");
}
}