Files
brk/crates/brk_bindgen/tests/catalog_test.rs
2026-02-03 00:34:54 +01:00

918 lines
35 KiB
Rust

// //! Tests that verify pattern analysis using the real catalog.
// use std::collections::{BTreeMap, BTreeSet};
// use std::fmt::Write;
// use brk_bindgen::ClientMetadata;
// use brk_types::TreeNode;
// /// Load the catalog from the JSON file.
// fn load_catalog() -> TreeNode {
// let path = concat!(env!("CARGO_MANIFEST_DIR"), "/catalog.json");
// let catalog_json = std::fs::read_to_string(path).expect("Failed to read catalog.json");
// serde_json::from_str(&catalog_json).expect("Failed to parse catalog.json")
// }
// /// Load OpenAPI spec from openapi.json.
// fn load_openapi_json() -> String {
// let path = concat!(env!("CARGO_MANIFEST_DIR"), "/openapi.json");
// std::fs::read_to_string(path).expect("Failed to read openapi.json")
// }
// /// Load metadata from the catalog.
// #[allow(unused)]
// fn load_metadata() -> ClientMetadata {
// ClientMetadata::from_catalog(load_catalog())
// }
// /// Collect all leaf metric names from a tree.
// fn collect_leaf_names(node: &TreeNode, names: &mut BTreeSet<String>) {
// match node {
// TreeNode::Leaf(leaf) => {
// names.insert(leaf.name().to_string());
// }
// TreeNode::Branch(children) => {
// for child in children.values() {
// collect_leaf_names(child, names);
// }
// }
// }
// }
// #[test]
// fn test_catalog_loads() {
// let catalog = load_catalog();
// // Should be a branch with top-level categories
// let TreeNode::Branch(categories) = &catalog else {
// panic!("Expected catalog to be a branch");
// };
// // Check some expected top-level categories exist
// assert!(
// categories.contains_key("addresses"),
// "Missing addresses category"
// );
// assert!(categories.contains_key("blocks"), "Missing blocks category");
// assert!(categories.contains_key("market"), "Missing market category");
// assert!(categories.contains_key("supply"), "Missing supply category");
// println!("Catalog has {} top-level categories", categories.len());
// }
// #[test]
// fn test_all_leaves_have_names() {
// let catalog = load_catalog();
// let mut names = BTreeSet::new();
// collect_leaf_names(&catalog, &mut names);
// println!("Catalog has {} unique metric names", names.len());
// assert!(!names.is_empty(), "Should have at least some metrics");
// // All names should be non-empty
// for name in &names {
// assert!(!name.is_empty(), "Found empty metric name");
// }
// }
// #[test]
// fn test_pattern_detection() {
// let catalog = load_catalog();
// let (patterns, concrete_to_pattern, concrete_to_type_param, _node_bases) =
// brk_bindgen::detect_structural_patterns(&catalog);
// println!("Detected {} structural patterns", patterns.len());
// println!(
// "Concrete to pattern mappings: {}",
// concrete_to_pattern.len()
// );
// println!("Type parameter mappings: {}", concrete_to_type_param.len());
// // Print pattern details
// for pattern in &patterns {
// let mode_str = match &pattern.mode {
// Some(brk_bindgen::PatternMode::Suffix { relatives }) => {
// format!("Suffix({})", relatives.len())
// }
// Some(brk_bindgen::PatternMode::Prefix { prefixes }) => {
// format!("Prefix({})", prefixes.len())
// }
// None => "None".to_string(),
// };
// println!(
// " {} (fields: {}, generic: {}, mode: {})",
// pattern.name,
// pattern.fields.len(),
// pattern.is_generic,
// mode_str
// );
// }
// // Should have detected some patterns
// assert!(!patterns.is_empty(), "Should detect at least some patterns");
// // Check that parameterizable patterns have valid modes
// for pattern in &patterns {
// if pattern.is_parameterizable() {
// let mode = pattern.mode.as_ref().unwrap();
// match mode {
// brk_bindgen::PatternMode::Suffix { relatives } => {
// assert_eq!(
// relatives.len(),
// pattern.fields.len(),
// "Pattern {} should have relative for each field",
// pattern.name
// );
// }
// brk_bindgen::PatternMode::Prefix { prefixes } => {
// assert_eq!(
// prefixes.len(),
// pattern.fields.len(),
// "Pattern {} should have prefix for each field",
// pattern.name
// );
// }
// }
// }
// }
// }
// #[test]
// fn test_cost_basis_pattern() {
// let catalog = load_catalog();
// let (patterns, _, _, _) = brk_bindgen::detect_structural_patterns(&catalog);
// // Find CostBasisPattern2 and inspect it
// let cost_basis = patterns
// .iter()
// .find(|p| p.name == "CostBasisPattern2")
// .expect("CostBasisPattern2 should exist");
// println!("CostBasisPattern2:");
// println!(
// " Fields: {:?}",
// cost_basis
// .fields
// .iter()
// .map(|f| &f.name)
// .collect::<Vec<_>>()
// );
// println!(" Mode: {:?}", cost_basis.mode);
// println!(" Is generic: {}", cost_basis.is_generic);
// // With suffix naming convention (cost_basis_max, cost_basis_min, cost_basis):
// //
// // At root level: common prefix is "cost_basis_" -> suffix mode
// // max -> "max"
// // min -> "min"
// // percentiles -> "" (identity)
// //
// // At lth_ level: common prefix is "lth_cost_basis_" -> suffix mode
// // max -> "max"
// // min -> "min"
// // percentiles -> "" (identity)
// //
// // Both use suffix mode with same relatives, so pattern IS parameterizable!
// assert!(
// cost_basis.is_parameterizable(),
// "CostBasisPattern2 should be parameterizable with consistent suffix mode"
// );
// }
// #[test]
// fn test_realized_pattern3_fields() {
// let catalog = load_catalog();
// let metadata = ClientMetadata::from_catalog(catalog);
// let pattern = metadata
// .find_pattern("RealizedPattern3")
// .expect("RealizedPattern3 should exist");
// println!("RealizedPattern3 fields:");
// for field in &pattern.fields {
// let is_branch = field.is_branch();
// let is_pattern = metadata.find_pattern(&field.rust_type).is_some();
// let is_param = metadata.is_parameterizable(&field.rust_type);
// println!(
// " {} -> {} (branch={}, pattern={}, param={})",
// field.name, field.rust_type, is_branch, is_pattern, is_param
// );
// }
// // Check if RealizedPattern3 is considered parameterizable
// println!(
// "\nRealizedPattern3 is_parameterizable (metadata): {}",
// metadata.is_parameterizable("RealizedPattern3")
// );
// }
// #[test]
// fn test_parameterizable_patterns_have_mode() {
// let catalog = load_catalog();
// let (patterns, _, _, _) = brk_bindgen::detect_structural_patterns(&catalog);
// // All patterns that appear 2+ times should either:
// // 1. Be parameterizable (have a mode)
// // 2. Or have inconsistent instances (mode = None)
// //
// // Patterns with mode = None should be inlined, not generate factories
// let parameterizable: Vec<_> = patterns.iter().filter(|p| p.is_parameterizable()).collect();
// let non_parameterizable: Vec<_> = patterns
// .iter()
// .filter(|p| !p.is_parameterizable())
// .collect();
// println!("\nParameterizable patterns ({}):", parameterizable.len());
// for p in &parameterizable {
// let mode = p.mode.as_ref().unwrap();
// let mode_type = match mode {
// brk_bindgen::PatternMode::Suffix { .. } => "Suffix",
// brk_bindgen::PatternMode::Prefix { .. } => "Prefix",
// };
// println!(" {} ({} fields, {})", p.name, p.fields.len(), mode_type);
// }
// println!(
// "\nNon-parameterizable patterns ({}):",
// non_parameterizable.len()
// );
// for p in &non_parameterizable {
// println!(" {} ({} fields)", p.name, p.fields.len());
// }
// // Verify all parameterizable patterns have valid modes with all fields
// for pattern in &parameterizable {
// let mode = pattern.mode.as_ref().unwrap();
// let field_names: BTreeSet<_> = pattern.fields.iter().map(|f| f.name.clone()).collect();
// match mode {
// brk_bindgen::PatternMode::Suffix { relatives } => {
// let mode_fields: BTreeSet<_> = relatives.keys().cloned().collect();
// assert_eq!(
// field_names, mode_fields,
// "Pattern {} suffix mode should have all fields",
// pattern.name
// );
// }
// brk_bindgen::PatternMode::Prefix { prefixes } => {
// let mode_fields: BTreeSet<_> = prefixes.keys().cloned().collect();
// assert_eq!(
// field_names, mode_fields,
// "Pattern {} prefix mode should have all fields",
// pattern.name
// );
// }
// }
// }
// }
// #[test]
// fn test_fee_rate_pattern_relatives() {
// let catalog = load_catalog();
// let (patterns, _, _, _) = brk_bindgen::detect_structural_patterns(&catalog);
// let fee_rate_pattern = patterns
// .iter()
// .find(|p| p.name == "FeeRatePattern")
// .expect("FeeRatePattern should exist");
// println!("FeeRatePattern mode:");
// if let Some(mode) = &fee_rate_pattern.mode {
// match mode {
// brk_bindgen::PatternMode::Suffix { relatives } => {
// println!(" Suffix mode:");
// for (field, relative) in relatives {
// println!(" {} -> '{}'", field, relative);
// }
// }
// brk_bindgen::PatternMode::Prefix { prefixes } => {
// println!(" Prefix mode:");
// for (field, prefix) in prefixes {
// println!(" {} -> '{}'", field, prefix);
// }
// }
// }
// } else {
// println!(" No mode (not parameterizable)");
// }
// // Check that relatives are correct - should be "average", "max", etc.
// // NOT "tx_weight_average", "tx_weight_max", etc.
// if let Some(brk_bindgen::PatternMode::Suffix { relatives }) = &fee_rate_pattern.mode {
// assert_eq!(
// relatives.get("average"),
// Some(&"average".to_string()),
// "average relative should be 'average', not 'tx_weight_average'"
// );
// }
// }
// #[test]
// fn test_index_patterns() {
// let catalog = load_catalog();
// let index_patterns = brk_bindgen::detect_index_patterns(&catalog);
// // println!("Used indexes: {:?}", used_indexes);
// println!("Index set patterns: {}", index_patterns.len());
// for pattern in &index_patterns {
// println!(" {} -> {:?}", pattern.name, pattern.indexes);
// }
// // Should have detected some index patterns
// assert!(!index_patterns.is_empty(), "Should detect index patterns");
// }
// #[test]
// fn test_generated_rust_output() {
// let catalog = load_catalog();
// let metadata = ClientMetadata::from_catalog(catalog.clone());
// // Collect all metric names from the catalog
// let mut all_metrics = BTreeSet::new();
// collect_leaf_names(&catalog, &mut all_metrics);
// // Generate Rust client output
// let mut rust_output = String::new();
// brk_bindgen::rust::client::generate_imports(&mut rust_output);
// brk_bindgen::rust::client::generate_base_client(&mut rust_output);
// brk_bindgen::rust::client::generate_metric_pattern_trait(&mut rust_output);
// brk_bindgen::rust::client::generate_endpoint(&mut rust_output);
// brk_bindgen::rust::client::generate_index_accessors(
// &mut rust_output,
// &metadata.index_set_patterns,
// );
// brk_bindgen::rust::client::generate_pattern_structs(
// &mut rust_output,
// &metadata.structural_patterns,
// &metadata,
// );
// brk_bindgen::rust::tree::generate_tree(&mut rust_output, &metadata.catalog, &metadata);
// brk_bindgen::rust::api::generate_main_client(&mut rust_output, &[]);
// // Count metrics that appear as direct string literals
// let mut direct_metrics = 0;
// for metric in &all_metrics {
// if rust_output.contains(&format!("\"{}\"", metric)) {
// direct_metrics += 1;
// }
// }
// println!("\nGenerated Rust output stats:");
// println!(" Total metrics in catalog: {}", all_metrics.len());
// println!(" Direct string literals: {}", direct_metrics);
// println!(
// " Via pattern factories: {}",
// all_metrics.len() - direct_metrics
// );
// println!(" Output size: {} bytes", rust_output.len());
// // Write output to test directory (not actual client)
// let output_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/output");
// std::fs::create_dir_all(output_dir).ok();
// let output_path = format!("{}/rust_client.rs", output_dir);
// std::fs::write(&output_path, &rust_output).expect("Failed to write client output");
// println!(" Wrote output to: {}", output_path);
// // Verify the output contains the key components
// assert!(rust_output.contains("fn _m("), "Should define _m helper");
// assert!(
// rust_output.contains("pub struct MetricsTree"),
// "Should have MetricsTree"
// );
// assert!(
// rust_output.contains("impl MetricsTree"),
// "Should have MetricsTree impl"
// );
// // Count parameterizable patterns (these use _m for dynamic metric names)
// // Use metadata.is_parameterizable() for full recursive check
// let parameterizable_count = metadata
// .structural_patterns
// .iter()
// .filter(|p| metadata.is_parameterizable(&p.name))
// .count();
// println!(" Parameterizable patterns: {}", parameterizable_count);
// // Verify all pattern structs are generated (parameterizable and non)
// for pattern in &metadata.structural_patterns {
// assert!(
// rust_output.contains(&format!("pub struct {}", pattern.name)),
// "Missing pattern struct: {}",
// pattern.name
// );
// }
// println!("\nGenerated Rust client is complete!");
// }
// #[test]
// fn test_generated_javascript_output() {
// let catalog = load_catalog();
// let metadata = ClientMetadata::from_catalog(catalog.clone());
// // Collect all metric names from the catalog
// let mut all_metrics = BTreeSet::new();
// collect_leaf_names(&catalog, &mut all_metrics);
// // Load schemas from OpenAPI spec only (catalog schemas require runtime data)
// let openapi_json = load_openapi_json();
// let schemas = brk_bindgen::extract_schemas(&openapi_json);
// // Generate JavaScript client output
// let mut js_output = String::new();
// writeln!(js_output, "// Auto-generated BRK JavaScript client").unwrap();
// writeln!(js_output, "// Do not edit manually\n").unwrap();
// brk_bindgen::javascript::types::generate_type_definitions(&mut js_output, &schemas);
// brk_bindgen::javascript::client::generate_base_client(&mut js_output);
// brk_bindgen::javascript::client::generate_index_accessors(
// &mut js_output,
// &metadata.index_set_patterns,
// );
// brk_bindgen::javascript::client::generate_structural_patterns(
// &mut js_output,
// &metadata.structural_patterns,
// &metadata,
// );
// brk_bindgen::javascript::tree::generate_tree_typedefs(
// &mut js_output,
// &metadata.catalog,
// &metadata,
// );
// brk_bindgen::javascript::tree::generate_main_client(
// &mut js_output,
// &metadata.catalog,
// &metadata,
// &[],
// );
// // Count metrics that appear as direct string literals
// let mut direct_metrics = 0;
// for metric in &all_metrics {
// if js_output.contains(&format!("'{}'", metric))
// || js_output.contains(&format!("\"{}\"", metric))
// {
// direct_metrics += 1;
// }
// }
// println!("\nGenerated JavaScript output stats:");
// println!(" Total metrics in catalog: {}", all_metrics.len());
// println!(" Direct string literals: {}", direct_metrics);
// println!(
// " Via pattern factories: {}",
// all_metrics.len() - direct_metrics
// );
// println!(" Output size: {} bytes", js_output.len());
// println!(" Output lines: {}", js_output.lines().count());
// // Write output to test directory (not actual client)
// let output_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/output");
// std::fs::create_dir_all(output_dir).ok();
// let output_path = format!("{}/js_client.js", output_dir);
// std::fs::write(&output_path, &js_output).expect("Failed to write JS client output");
// println!(" Wrote output to: {}", output_path);
// // Verify the output contains key components
// assert!(js_output.contains("const _m ="), "Should define _m helper");
// assert!(js_output.contains("const _p ="), "Should define _p helper");
// assert!(
// js_output.contains("@typedef {Object} MetricsTree"),
// "Should have MetricsTree typedef"
// );
// assert!(
// js_output.contains("class BrkClient"),
// "Should have BrkClient class"
// );
// // Verify all pattern factories are generated
// for pattern in &metadata.structural_patterns {
// assert!(
// js_output.contains(&format!("function create{}(", pattern.name)),
// "Missing pattern factory: {}",
// pattern.name
// );
// }
// println!("\nGenerated JavaScript client is complete!");
// }
// #[test]
// fn test_generated_python_output() {
// let catalog = load_catalog();
// let metadata = ClientMetadata::from_catalog(catalog.clone());
// // Collect all metric names from the catalog
// let mut all_metrics = BTreeSet::new();
// collect_leaf_names(&catalog, &mut all_metrics);
// // Load schemas from OpenAPI spec only (catalog schemas require runtime data)
// let openapi_json = load_openapi_json();
// let schemas = brk_bindgen::extract_schemas(&openapi_json);
// // Generate Python client output
// let mut py_output = String::new();
// writeln!(py_output, "# Auto-generated BRK Python client").unwrap();
// writeln!(py_output, "# Do not edit manually\n").unwrap();
// writeln!(py_output, "from typing import TypeVar, Generic, Any, Optional, List, Literal, TypedDict, Union, Protocol, overload, Iterator, Tuple, TYPE_CHECKING").unwrap();
// writeln!(py_output, "\nif TYPE_CHECKING:").unwrap();
// writeln!(py_output, " import pandas as pd # type: ignore[import-not-found]").unwrap();
// writeln!(py_output, " import polars as pl # type: ignore[import-not-found]").unwrap();
// writeln!(
// py_output,
// "from http.client import HTTPSConnection, HTTPConnection"
// )
// .unwrap();
// writeln!(py_output, "from urllib.parse import urlparse").unwrap();
// writeln!(py_output, "from datetime import date, timedelta").unwrap();
// writeln!(py_output, "from dataclasses import dataclass").unwrap();
// writeln!(py_output, "import json\n").unwrap();
// writeln!(py_output, "T = TypeVar('T')\n").unwrap();
// brk_bindgen::python::types::generate_type_definitions(&mut py_output, &schemas);
// brk_bindgen::python::client::generate_base_client(&mut py_output);
// brk_bindgen::python::client::generate_endpoint_class(&mut py_output);
// brk_bindgen::python::client::generate_index_accessors(
// &mut py_output,
// &metadata.index_set_patterns,
// );
// brk_bindgen::python::client::generate_structural_patterns(
// &mut py_output,
// &metadata.structural_patterns,
// &metadata,
// );
// brk_bindgen::python::tree::generate_tree_classes(&mut py_output, &metadata.catalog, &metadata);
// brk_bindgen::python::api::generate_main_client(&mut py_output, &[]);
// // Count metrics that appear as direct string literals
// let mut direct_metrics = 0;
// for metric in &all_metrics {
// if py_output.contains(&format!("'{}'", metric))
// || py_output.contains(&format!("\"{}\"", metric))
// {
// direct_metrics += 1;
// }
// }
// println!("\nGenerated Python output stats:");
// println!(" Total metrics in catalog: {}", all_metrics.len());
// println!(" Direct string literals: {}", direct_metrics);
// println!(
// " Via pattern factories: {}",
// all_metrics.len() - direct_metrics
// );
// println!(" Output size: {} bytes", py_output.len());
// println!(" Output lines: {}", py_output.lines().count());
// // Write output to test directory (not actual client)
// let output_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/output");
// std::fs::create_dir_all(output_dir).ok();
// let output_path = format!("{}/python_client.py", output_dir);
// std::fs::write(&output_path, &py_output).expect("Failed to write Python client output");
// println!(" Wrote output to: {}", output_path);
// // Verify the output contains key components
// assert!(py_output.contains("def _m("), "Should define _m helper");
// assert!(py_output.contains("def _p("), "Should define _p helper");
// assert!(
// py_output.contains("class MetricsTree:"),
// "Should have MetricsTree class"
// );
// assert!(
// py_output.contains("class BrkClient"),
// "Should have BrkClient class"
// );
// // Verify all pattern classes have constructors
// for pattern in &metadata.structural_patterns {
// assert!(
// py_output.contains(&format!("class {}:", pattern.name))
// || py_output.contains(&format!("class {}(", pattern.name)),
// "Missing pattern class: {}",
// pattern.name
// );
// }
// println!("\nGenerated Python client is complete!");
// }
// #[test]
// fn test_cost_basis_relatives() {
// let catalog = load_catalog();
// // Find cost_basis branches that have 3 direct children (max, min, percentiles)
// fn find_cost_basis_with_percentiles(
// node: &TreeNode,
// path: &str,
// ) -> Vec<(String, Vec<(String, String)>)> {
// let mut results = Vec::new();
// if let TreeNode::Branch(children) = node {
// for (name, child) in children {
// let child_path = if path.is_empty() {
// name.clone()
// } else {
// format!("{}.{}", path, name)
// };
// if name == "cost_basis"
// && let TreeNode::Branch(cb_children) = child
// && cb_children.contains_key("percentiles")
// {
// // Found a cost_basis with percentiles
// let mut metrics = Vec::new();
// for (field_name, field_node) in cb_children {
// match field_node {
// TreeNode::Leaf(leaf) => {
// metrics.push((field_name.clone(), leaf.name().to_string()));
// }
// TreeNode::Branch(pct_children) => {
// // Get first percentile as example
// if let Some((_, TreeNode::Leaf(first))) = pct_children.iter().next()
// {
// metrics.push((
// format!("{}.first", field_name),
// first.name().to_string(),
// ));
// }
// }
// }
// }
// results.push((child_path.clone(), metrics));
// }
// results.extend(find_cost_basis_with_percentiles(child, &child_path));
// }
// }
// results
// }
// let instances = find_cost_basis_with_percentiles(&catalog, "");
// println!("\nCostBasisPattern2 instances (with percentiles):");
// for (path, metrics) in instances.iter().take(10) {
// println!(" {}:", path);
// for (field, metric) in metrics {
// println!(" {} -> {}", field, metric);
// }
// }
// // Now compute what relatives the pattern detection would see
// // The key is: percentiles returns its BASE (common prefix of pct05, pct10, etc.)
// // not the individual percentile metrics
// use brk_bindgen::find_common_prefix;
// println!("\nComputing relatives (simulating branch base returns):");
// for (path, metrics) in instances.iter().take(5) {
// println!(" Instance: {}", path);
// // For leaves (max, min), the base is the metric name
// // For branches (percentiles), the base is the common prefix of its children
// let mut child_bases: std::collections::BTreeMap<String, String> =
// std::collections::BTreeMap::new();
// for (field, metric) in metrics {
// if field.starts_with("percentiles.") {
// // This is a percentile metric - compute what the percentiles branch would return
// // The base is the metric name with the pct suffix stripped
// let base = metric
// .strip_suffix("_pct05")
// .or_else(|| metric.strip_suffix("_pct10"))
// .unwrap_or(metric)
// .to_string();
// child_bases.insert("percentiles".to_string(), base);
// } else {
// child_bases.insert(field.clone(), metric.clone());
// }
// }
// let bases: Vec<&str> = child_bases.values().map(|s| s.as_str()).collect();
// println!(" Child bases:");
// for (field, base) in &child_bases {
// println!(" {} -> {}", field, base);
// }
// if let Some(prefix) = find_common_prefix(&bases) {
// println!(" Common prefix: '{}'", prefix);
// for (field, base) in &child_bases {
// let relative = base.strip_prefix(&prefix).unwrap_or(base);
// println!(" {} -> relative '{}'", field, relative);
// }
// } else {
// println!(" No common prefix found!");
// }
// }
// }
// #[test]
// fn test_debug_cost_basis_pattern2_mode() {
// // Debug why CostBasisPattern2 has mode=None
// let catalog = load_catalog();
// let metadata = brk_bindgen::ClientMetadata::from_catalog(catalog.clone());
// let pattern_lookup = metadata.pattern_lookup();
// let pattern = metadata
// .find_pattern("CostBasisPattern2")
// .expect("CostBasisPattern2 should exist");
// println!("\nCostBasisPattern2 fields:");
// for field in &pattern.fields {
// println!(" {} (type: {})", field.name, field.rust_type);
// }
// println!("Mode: {:?}", pattern.mode);
// // Now debug the instance collection
// #[derive(Debug, Clone)]
// struct DebugInstanceAnalysis {
// base: String,
// field_parts: std::collections::BTreeMap<String, String>,
// is_suffix_mode: bool,
// }
// fn collect_debug(
// node: &TreeNode,
// pattern_lookup: &std::collections::BTreeMap<Vec<brk_bindgen::PatternField>, String>,
// all_analyses: &mut std::collections::BTreeMap<String, Vec<DebugInstanceAnalysis>>,
// ) -> Option<String> {
// match node {
// TreeNode::Leaf(leaf) => Some(leaf.name().to_string()),
// TreeNode::Branch(children) => {
// let mut child_bases: std::collections::BTreeMap<String, String> =
// std::collections::BTreeMap::new();
// for (field_name, child_node) in children {
// if let Some(base) = collect_debug(child_node, pattern_lookup, all_analyses) {
// child_bases.insert(field_name.clone(), base);
// }
// }
// if child_bases.is_empty() {
// return None;
// }
// // Analyze this instance
// let bases: Vec<&str> = child_bases.values().map(|s| s.as_str()).collect();
// let (base, field_parts, is_suffix_mode) =
// if let Some(common_prefix) = brk_bindgen::find_common_prefix(&bases) {
// let base = common_prefix.trim_end_matches('_').to_string();
// let mut parts = std::collections::BTreeMap::new();
// for (field_name, child_base) in &child_bases {
// let relative = if *child_base == base {
// String::new()
// } else {
// child_base
// .strip_prefix(&common_prefix)
// .unwrap_or(child_base)
// .to_string()
// };
// parts.insert(field_name.clone(), relative);
// }
// (base, parts, true)
// } else {
// let base = child_bases.values().next().cloned().unwrap_or_default();
// let parts = child_bases
// .iter()
// .map(|(k, v)| (k.clone(), v.clone()))
// .collect();
// (base, parts, true)
// };
// let analysis = DebugInstanceAnalysis {
// base: base.clone(),
// field_parts,
// is_suffix_mode,
// };
// // Get the pattern name for this node
// let fields = brk_bindgen::get_node_fields(children, pattern_lookup);
// if let Some(pattern_name) = pattern_lookup.get(&fields) {
// all_analyses
// .entry(pattern_name.clone())
// .or_default()
// .push(analysis);
// }
// Some(base)
// }
// }
// }
// let mut all_analyses: BTreeMap<String, Vec<DebugInstanceAnalysis>> = BTreeMap::new();
// collect_debug(&catalog, &pattern_lookup, &mut all_analyses);
// if let Some(analyses) = all_analyses.get("CostBasisPattern2") {
// println!(
// "\nCollected {} instances of CostBasisPattern2:",
// analyses.len()
// );
// for (i, a) in analyses.iter().enumerate() {
// println!(" Instance {}:", i);
// println!(" base: {}", a.base);
// println!(" is_suffix: {}", a.is_suffix_mode);
// println!(" field_parts:");
// for (f, p) in &a.field_parts {
// println!(" {} -> '{}'", f, p);
// }
// }
// // Check consistency
// if analyses.len() >= 2 {
// let first = &analyses[0];
// for (i, a) in analyses.iter().enumerate().skip(1) {
// if a.is_suffix_mode != first.is_suffix_mode {
// println!(" INCONSISTENT: Instance {} has different mode", i);
// }
// for (field, part) in &a.field_parts {
// if first.field_parts.get(field) != Some(part) {
// println!(
// " INCONSISTENT: Instance {} field '{}' has part '{}' vs '{}'",
// i,
// field,
// part,
// first
// .field_parts
// .get(field)
// .unwrap_or(&"<missing>".to_string())
// );
// }
// }
// }
// }
// } else {
// println!("\nNo instances collected for CostBasisPattern2!");
// }
// }
// #[test]
// fn test_root_cost_basis_prefix() {
// use brk_bindgen::find_common_prefix;
// // Root-level cost_basis has:
// // max -> "max_cost_basis"
// // min -> "min_cost_basis"
// // percentiles -> "cost_basis" (base of pct05, pct10, etc.)
// let bases = vec!["max_cost_basis", "min_cost_basis", "cost_basis"];
// let prefix = find_common_prefix(&bases);
// println!("Root cost_basis prefix: {:?}", prefix);
// // Compare with nested cost_basis
// let nested_bases = vec![
// "utxos_at_least_15y_old_max_cost_basis",
// "utxos_at_least_15y_old_min_cost_basis",
// "utxos_at_least_15y_old_cost_basis",
// ];
// let nested_prefix = find_common_prefix(&nested_bases);
// println!("Nested cost_basis prefix: {:?}", nested_prefix);
// }
// #[test]
// fn test_utxo_cohorts_all_activity_base() {
// // Test that distribution.utxo_cohorts.all.activity uses empty base
// // because its children (coinblocks_destroyed, coindays_destroyed, etc.)
// // have no common prefix or suffix.
// let catalog = load_catalog();
// let metadata = ClientMetadata::from_catalog(catalog.clone());
// // Generate JavaScript output
// let mut js_output = String::new();
// writeln!(js_output, "// Test output").unwrap();
// brk_bindgen::javascript::client::generate_base_client(&mut js_output);
// brk_bindgen::javascript::client::generate_index_accessors(
// &mut js_output,
// &metadata.index_set_patterns,
// );
// brk_bindgen::javascript::client::generate_structural_patterns(
// &mut js_output,
// &metadata.structural_patterns,
// &metadata,
// );
// brk_bindgen::javascript::tree::generate_tree_typedefs(
// &mut js_output,
// &metadata.catalog,
// &metadata,
// );
// brk_bindgen::javascript::tree::generate_main_client(
// &mut js_output,
// &metadata.catalog,
// &metadata,
// &[],
// );
// // The all.activity should use empty base, so metrics don't get duplicated
// // Look for: activity: createActivityPattern2(this, '')
// // NOT: activity: createActivityPattern2(this, 'coinblocks_destroyed')
// assert!(
// !js_output.contains("createActivityPattern2(this, 'coinblocks_destroyed')"),
// "all.activity should NOT use 'coinblocks_destroyed' as base (causes duplication)"
// );
// // Check that it uses empty string as base
// assert!(
// js_output.contains("activity: createActivityPattern2(this, '')"),
// "all.activity should use empty base"
// );
// println!("utxo_cohorts.all.activity base test passed!");
// }