use anyhow::Context; use anyhow::Result; use codex_app_server_protocol::read_schema_fixture_tree; use codex_app_server_protocol::write_schema_fixtures; use similar::TextDiff; use std::path::Path; use std::time::Instant; #[test] fn schema_fixtures_match_generated() -> Result<()> { let start = Instant::now(); let schema_root = schema_root()?; eprintln!( "[schema_fixtures] resolved schema root in {:?}: {}", start.elapsed(), schema_root.display() ); let fixture_read_start = Instant::now(); let fixture_tree = read_tree(&schema_root)?; eprintln!( "[schema_fixtures] read {} vendored files in {:?}", fixture_tree.len(), fixture_read_start.elapsed() ); let temp_dir = tempfile::tempdir().context("create temp dir")?; let generate_start = Instant::now(); write_schema_fixtures(temp_dir.path(), None).context("generate schema fixtures")?; eprintln!( "[schema_fixtures] generated schema fixtures in {:?}", generate_start.elapsed() ); let generated_read_start = Instant::now(); let generated_tree = read_tree(temp_dir.path())?; eprintln!( "[schema_fixtures] read {} generated files in {:?}", generated_tree.len(), generated_read_start.elapsed() ); let fixture_paths = fixture_tree .keys() .map(|p| p.display().to_string()) .collect::>(); let generated_paths = generated_tree .keys() .map(|p| p.display().to_string()) .collect::>(); if fixture_paths != generated_paths { let expected = fixture_paths.join("\n"); let actual = generated_paths.join("\n"); let diff = TextDiff::from_lines(&expected, &actual) .unified_diff() .header("fixture", "generated") .to_string(); panic!( "Vendored app-server schema fixture file set doesn't match freshly generated output. \ Run `just write-app-server-schema` to overwrite with your changes.\n\n{diff}" ); } // If the file sets match, diff contents for each file for a nicer error. for (path, expected) in &fixture_tree { let actual = generated_tree .get(path) .ok_or_else(|| anyhow::anyhow!("missing generated file: {}", path.display()))?; if expected == actual { continue; } let expected_str = String::from_utf8_lossy(expected); let actual_str = String::from_utf8_lossy(actual); let diff = TextDiff::from_lines(&expected_str, &actual_str) .unified_diff() .header("fixture", "generated") .to_string(); panic!( "Vendored app-server schema fixture {} differs from generated output. \ Run `just write-app-server-schema` to overwrite with your changes.\n\n{diff}", path.display() ); } eprintln!( "[schema_fixtures] compared {} files in {:?}", fixture_tree.len(), start.elapsed() ); Ok(()) } fn schema_root() -> Result { // In Bazel runfiles (especially manifest-only mode), resolving directories is not // reliable. Resolve a known file, then walk up to the schema root. let typescript_index = codex_utils_cargo_bin::find_resource!("schema/typescript/index.ts") .context("resolve TypeScript schema index.ts")?; let schema_root = typescript_index .parent() .and_then(|p| p.parent()) .context("derive schema root from schema/typescript/index.ts")? .to_path_buf(); // Sanity check that the JSON fixtures resolve to the same schema root. let json_bundle = codex_utils_cargo_bin::find_resource!("schema/json/codex_app_server_protocol.schemas.json") .context("resolve JSON schema bundle")?; let json_root = json_bundle .parent() .and_then(|p| p.parent()) .context("derive schema root from schema/json/codex_app_server_protocol.schemas.json")?; anyhow::ensure!( schema_root == json_root, "schema roots disagree: typescript={} json={}", schema_root.display(), json_root.display() ); Ok(schema_root) } fn read_tree(root: &Path) -> Result>> { read_schema_fixture_tree(root).context("read schema fixture tree") }