use anyhow::Result;
use serde::Serialize;
use std::collections::HashMap;
pub use magellan::CodeGraph;
pub use magellan::{
CondensationResult, Cycle, CycleKind, CycleReport, DeadSymbol, ExecutionPath,
PathEnumerationResult, SymbolInfo,
};
#[allow(unused_imports)]
use magellan::{
CondensationGraph, PathStatistics, ProgramSlice, SliceDirection, SliceResult, SliceStatistics,
Supernode,
};
#[derive(Debug, Clone, Serialize)]
pub struct DeadSymbolJson {
pub fqn: Option<String>,
pub file_path: String,
pub kind: String,
pub reason: String,
}
impl From<&DeadSymbol> for DeadSymbolJson {
fn from(dead: &DeadSymbol) -> Self {
Self {
fqn: dead.symbol.fqn.clone(),
file_path: dead.symbol.file_path.clone(),
kind: dead.symbol.kind.clone(),
reason: dead.reason.clone(),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct SymbolInfoJson {
pub symbol_id: Option<String>,
pub fqn: Option<String>,
pub file_path: String,
pub kind: String,
}
impl From<&SymbolInfo> for SymbolInfoJson {
fn from(symbol: &SymbolInfo) -> Self {
Self {
symbol_id: symbol.symbol_id.clone(),
fqn: symbol.fqn.clone(),
file_path: symbol.file_path.clone(),
kind: symbol.kind.clone(),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct SliceWrapper {
pub target: SymbolInfoJson,
pub direction: String, pub included_symbols: Vec<SymbolInfoJson>,
pub symbol_count: usize,
pub statistics: SliceStats,
}
#[derive(Debug, Clone, Serialize)]
pub struct SliceStats {
pub total_symbols: usize,
pub data_dependencies: usize,
pub control_dependencies: usize,
}
impl From<&SliceResult> for SliceWrapper {
fn from(result: &SliceResult) -> Self {
let statistics = SliceStats {
total_symbols: result.statistics.total_symbols,
data_dependencies: result.statistics.data_dependencies,
control_dependencies: result.statistics.control_dependencies,
};
SliceWrapper {
target: (&result.slice.target).into(),
direction: format!("{:?}", result.slice.direction),
included_symbols: result
.slice
.included_symbols
.iter()
.map(|s| s.into())
.collect(),
symbol_count: result.slice.symbol_count,
statistics,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ExecutionPathJson {
pub symbols: Vec<SymbolInfoJson>,
pub length: usize,
}
impl From<&ExecutionPath> for ExecutionPathJson {
fn from(path: &ExecutionPath) -> Self {
ExecutionPathJson {
symbols: path.symbols.iter().map(|s| s.into()).collect(),
length: path.length,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct PathEnumerationJson {
pub paths: Vec<ExecutionPathJson>,
pub total_enumerated: usize,
pub truncated: bool,
pub statistics: PathStatisticsJson,
}
#[derive(Debug, Clone, Serialize)]
pub struct PathStatisticsJson {
pub avg_length: f64,
pub max_length: usize,
pub min_length: usize,
pub unique_symbols: usize,
}
impl From<&PathEnumerationResult> for PathEnumerationJson {
fn from(result: &PathEnumerationResult) -> Self {
PathEnumerationJson {
paths: result.paths.iter().map(|p| p.into()).collect(),
total_enumerated: result.total_enumerated,
truncated: result.bounded_hit,
statistics: PathStatisticsJson {
avg_length: result.statistics.avg_length,
max_length: result.statistics.max_length,
min_length: result.statistics.min_length,
unique_symbols: result.statistics.unique_symbols,
},
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CondensationJson {
pub supernode_count: usize,
pub edge_count: usize,
pub supernodes: Vec<SupernodeJson>,
pub largest_scc_size: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct SupernodeJson {
pub id: String,
pub member_count: usize,
pub members: Vec<String>,
}
impl From<&CondensationResult> for CondensationJson {
fn from(result: &CondensationResult) -> Self {
let supernodes: Vec<SupernodeJson> = result
.graph
.supernodes
.iter()
.map(|sn| SupernodeJson {
id: sn.id.to_string(),
member_count: sn.members.len(),
members: sn.members.iter().filter_map(|m| m.fqn.clone()).collect(),
})
.collect();
let largest_scc_size = supernodes
.iter()
.map(|sn| sn.member_count)
.max()
.unwrap_or(0);
CondensationJson {
supernode_count: result.graph.supernodes.len(),
edge_count: result.graph.edges.len(),
supernodes,
largest_scc_size,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CycleInfo {
pub members: Vec<String>,
pub cycle_type: String,
pub size: usize,
}
impl From<&Cycle> for CycleInfo {
fn from(cycle: &Cycle) -> Self {
let members: Vec<String> = cycle
.members
.iter()
.map(|m| m.fqn.as_deref().unwrap_or("<unknown>").to_string())
.collect();
let cycle_type = match cycle.kind {
CycleKind::MutualRecursion => "MutualRecursion",
CycleKind::SelfLoop => "SelfLoop",
};
Self {
members,
cycle_type: cycle_type.to_string(),
size: cycle.members.len(),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct LoopInfo {
pub header: usize,
pub back_edge_from: usize,
pub body_size: usize,
pub nesting_level: usize,
pub body_blocks: Vec<usize>,
}
#[derive(Debug, Clone, Serialize)]
pub struct EnhancedCycles {
pub call_graph_cycles: Vec<CycleInfo>,
pub function_loops: HashMap<String, Vec<LoopInfo>>,
pub total_cycles: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct EnhancedDeadCode {
pub uncalled_functions: Vec<DeadSymbolJson>,
pub unreachable_blocks: HashMap<String, Vec<usize>>,
pub total_dead_count: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct EnhancedBlastZone {
pub target: String,
pub forward_reachable: Vec<SymbolInfoJson>,
pub backward_reachable: Vec<SymbolInfoJson>,
pub path_impact: Option<PathImpactSummary>,
}
#[derive(Debug, Clone, Serialize)]
pub struct PathImpactSummary {
pub path_id: Option<String>,
pub path_length: usize,
pub blocks_affected: Vec<usize>,
pub unique_blocks_count: usize,
}
pub struct MagellanBridge {
graph: CodeGraph,
}
impl MagellanBridge {
pub fn open(db_path: &str) -> Result<Self> {
let graph = CodeGraph::open(db_path)?;
Ok(Self { graph })
}
pub fn graph(&self) -> &CodeGraph {
&self.graph
}
pub fn reachable_symbols(&self, symbol_id: &str) -> Result<Vec<SymbolInfo>> {
self.graph.reachable_symbols(symbol_id, None)
}
pub fn reverse_reachable_symbols(&self, symbol_id: &str) -> Result<Vec<SymbolInfo>> {
self.graph.reverse_reachable_symbols(symbol_id, None)
}
pub fn dead_symbols(&self, entry_symbol_id: &str) -> Result<Vec<DeadSymbol>> {
self.graph.dead_symbols(entry_symbol_id)
}
pub fn detect_cycles(&self) -> Result<CycleReport> {
self.graph.detect_cycles()
}
pub fn backward_slice(&self, symbol_id: &str) -> Result<SliceWrapper> {
let result = self.graph.backward_slice(symbol_id)?;
Ok((&result).into())
}
pub fn forward_slice(&self, symbol_id: &str) -> Result<SliceWrapper> {
let result = self.graph.forward_slice(symbol_id)?;
Ok((&result).into())
}
pub fn enumerate_paths(
&self,
start_symbol_id: &str,
end_symbol_id: Option<&str>,
max_depth: usize,
max_paths: usize,
) -> Result<PathEnumerationResult> {
self.graph
.enumerate_paths(start_symbol_id, end_symbol_id, max_depth, max_paths)
}
pub fn enumerate_paths_json(
&self,
start_symbol_id: &str,
end_symbol_id: Option<&str>,
max_depth: usize,
max_paths: usize,
) -> Result<PathEnumerationJson> {
let result =
self.graph
.enumerate_paths(start_symbol_id, end_symbol_id, max_depth, max_paths)?;
Ok((&result).into())
}
pub fn condense_call_graph(&self) -> Result<CondensationResult> {
self.graph.condense_call_graph()
}
pub fn condense_call_graph_json(&self) -> Result<CondensationJson> {
let result = self.graph.condense_call_graph()?;
Ok((&result).into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_magellan_bridge_creation() {
let _ = || -> Result<()> {
let _bridge = MagellanBridge::open("test.db")?;
Ok(())
};
}
#[test]
fn test_dead_symbol_json_from_dead_symbol() {
use magellan::{DeadSymbol as MagellanDeadSymbol, SymbolInfo};
let symbol_info = SymbolInfo {
symbol_id: Some("test_symbol_id".to_string()),
fqn: Some("test::function".to_string()),
file_path: "test.rs".to_string(),
kind: "Function".to_string(),
};
let dead = MagellanDeadSymbol {
symbol: symbol_info,
reason: "Not called from entry point".to_string(),
};
let json_symbol: DeadSymbolJson = (&dead).into();
assert_eq!(json_symbol.fqn, Some("test::function".to_string()));
assert_eq!(json_symbol.file_path, "test.rs");
assert_eq!(json_symbol.kind, "Function");
assert_eq!(json_symbol.reason, "Not called from entry point");
}
#[test]
fn test_enhanced_dead_code_serialization() {
use magellan::{DeadSymbol as MagellanDeadSymbol, SymbolInfo};
let symbol_info = SymbolInfo {
symbol_id: Some("test_id".to_string()),
fqn: Some("dead::function".to_string()),
file_path: "test.rs".to_string(),
kind: "Function".to_string(),
};
let dead = MagellanDeadSymbol {
symbol: symbol_info,
reason: "Uncalled".to_string(),
};
let json_symbol: DeadSymbolJson = (&dead).into();
let mut unreachable_blocks = std::collections::HashMap::new();
unreachable_blocks.insert("test_func".to_string(), vec![1, 2, 3]);
let enhanced = EnhancedDeadCode {
uncalled_functions: vec![json_symbol],
unreachable_blocks,
total_dead_count: 4,
};
let json = serde_json::to_string(&enhanced).unwrap();
assert!(json.contains("uncalled_functions"));
assert!(json.contains("unreachable_blocks"));
assert!(json.contains("total_dead_count"));
}
#[test]
fn test_cycle_info_from_cycle() {
use magellan::{Cycle, CycleKind, SymbolInfo};
let symbol1 = SymbolInfo {
symbol_id: Some("func_a_id".to_string()),
fqn: Some("func_a".to_string()),
file_path: "test.rs".to_string(),
kind: "Function".to_string(),
};
let symbol2 = SymbolInfo {
symbol_id: Some("func_b_id".to_string()),
fqn: Some("func_b".to_string()),
file_path: "test.rs".to_string(),
kind: "Function".to_string(),
};
let mutual_recursion_cycle = Cycle {
members: vec![symbol1.clone(), symbol2.clone()],
kind: CycleKind::MutualRecursion,
};
let cycle_info: CycleInfo = (&mutual_recursion_cycle).into();
assert_eq!(cycle_info.cycle_type, "MutualRecursion");
assert_eq!(cycle_info.size, 2);
assert_eq!(cycle_info.members, vec!["func_a", "func_b"]);
let self_loop_cycle = Cycle {
members: vec![symbol1],
kind: CycleKind::SelfLoop,
};
let cycle_info: CycleInfo = (&self_loop_cycle).into();
assert_eq!(cycle_info.cycle_type, "SelfLoop");
assert_eq!(cycle_info.size, 1);
assert_eq!(cycle_info.members, vec!["func_a"]);
}
#[test]
fn test_enhanced_cycles_serialization() {
use std::collections::HashMap;
let mut function_loops = HashMap::new();
function_loops.insert(
"test_func".to_string(),
vec![LoopInfo {
header: 1,
back_edge_from: 2,
body_size: 3,
nesting_level: 0,
body_blocks: vec![1, 2, 3],
}],
);
let call_graph_cycles = vec![CycleInfo {
members: vec!["func_a".to_string(), "func_b".to_string()],
cycle_type: "MutualRecursion".to_string(),
size: 2,
}];
let enhanced = EnhancedCycles {
call_graph_cycles,
function_loops,
total_cycles: 2,
};
let json = serde_json::to_string(&enhanced).unwrap();
assert!(json.contains("call_graph_cycles"));
assert!(json.contains("function_loops"));
assert!(json.contains("total_cycles"));
assert!(json.contains("MutualRecursion"));
}
#[test]
fn test_loop_info_serialization() {
let loop_info = LoopInfo {
header: 1,
back_edge_from: 3,
body_size: 5,
nesting_level: 2,
body_blocks: vec![1, 2, 3, 4, 5],
};
let json = serde_json::to_string(&loop_info).unwrap();
assert!(json.contains(r#""header":1"#));
assert!(json.contains(r#""back_edge_from":3"#));
assert!(json.contains(r#""body_size":5"#));
assert!(json.contains(r#""nesting_level":2"#));
assert!(json.contains(r#"body_blocks"#));
}
#[test]
fn test_slice_wrapper_serialization() {
use magellan::{ProgramSlice, SliceDirection, SliceResult, SliceStatistics};
let target = SymbolInfo {
symbol_id: Some("target_id".to_string()),
fqn: Some("target_function".to_string()),
file_path: "test.rs".to_string(),
kind: "Function".to_string(),
};
let included_symbols = vec![
SymbolInfo {
symbol_id: Some("sym1_id".to_string()),
fqn: Some("sym1".to_string()),
file_path: "test.rs".to_string(),
kind: "Function".to_string(),
},
SymbolInfo {
symbol_id: Some("sym2_id".to_string()),
fqn: Some("sym2".to_string()),
file_path: "test.rs".to_string(),
kind: "Function".to_string(),
},
];
let program_slice = ProgramSlice {
target: target.clone(),
direction: SliceDirection::Backward,
included_symbols: included_symbols.clone(),
symbol_count: 3,
};
let statistics = SliceStatistics {
total_symbols: 3,
data_dependencies: 2,
control_dependencies: 1,
};
let slice_result = SliceResult {
slice: program_slice,
statistics,
};
let wrapper: SliceWrapper = (&slice_result).into();
assert_eq!(wrapper.target.fqn, Some("target_function".to_string()));
assert_eq!(wrapper.direction, "Backward");
assert_eq!(wrapper.symbol_count, 3);
assert_eq!(wrapper.statistics.total_symbols, 3);
assert_eq!(wrapper.statistics.data_dependencies, 2);
assert_eq!(wrapper.statistics.control_dependencies, 1);
assert_eq!(wrapper.included_symbols.len(), 2);
let json = serde_json::to_string(&wrapper).unwrap();
assert!(json.contains("target"));
assert!(json.contains("direction"));
assert!(json.contains("Backward"));
assert!(json.contains("included_symbols"));
assert!(json.contains("statistics"));
assert!(json.contains("data_dependencies"));
}
#[test]
fn test_slice_stats_creation() {
let stats = SliceStats {
total_symbols: 10,
data_dependencies: 5,
control_dependencies: 3,
};
assert_eq!(stats.total_symbols, 10);
assert_eq!(stats.data_dependencies, 5);
assert_eq!(stats.control_dependencies, 3);
let json = serde_json::to_string(&stats).unwrap();
assert!(json.contains(r#""total_symbols":10"#));
assert!(json.contains(r#""data_dependencies":5"#));
assert!(json.contains(r#""control_dependencies":3"#));
}
#[test]
fn test_symbol_info_json_from_symbol_info() {
use magellan::SymbolInfo;
let symbol_info = SymbolInfo {
symbol_id: Some("test_symbol_id".to_string()),
fqn: Some("test::function".to_string()),
file_path: "test.rs".to_string(),
kind: "Function".to_string(),
};
let json_symbol: SymbolInfoJson = (&symbol_info).into();
assert_eq!(json_symbol.symbol_id, Some("test_symbol_id".to_string()));
assert_eq!(json_symbol.fqn, Some("test::function".to_string()));
assert_eq!(json_symbol.file_path, "test.rs");
assert_eq!(json_symbol.kind, "Function");
}
#[test]
fn test_enhanced_blast_zone_creation() {
let forward = vec![
SymbolInfoJson {
symbol_id: Some("func_a_id".to_string()),
fqn: Some("func_a".to_string()),
file_path: "a.rs".to_string(),
kind: "Function".to_string(),
},
SymbolInfoJson {
symbol_id: Some("func_b_id".to_string()),
fqn: Some("func_b".to_string()),
file_path: "b.rs".to_string(),
kind: "Function".to_string(),
},
];
let backward = vec![SymbolInfoJson {
symbol_id: Some("main_id".to_string()),
fqn: Some("main".to_string()),
file_path: "main.rs".to_string(),
kind: "Function".to_string(),
}];
let path_impact = PathImpactSummary {
path_id: Some("test_path_id".to_string()),
path_length: 5,
blocks_affected: vec![1, 2, 3, 4],
unique_blocks_count: 4,
};
let blast_zone = EnhancedBlastZone {
target: "test_function".to_string(),
forward_reachable: forward.clone(),
backward_reachable: backward.clone(),
path_impact: Some(path_impact),
};
assert_eq!(blast_zone.target, "test_function");
assert_eq!(blast_zone.forward_reachable.len(), 2);
assert_eq!(blast_zone.backward_reachable.len(), 1);
assert!(blast_zone.path_impact.is_some());
let json = serde_json::to_string(&blast_zone).unwrap();
assert!(json.contains("target"));
assert!(json.contains("forward_reachable"));
assert!(json.contains("backward_reachable"));
assert!(json.contains("path_impact"));
assert!(json.contains("func_a"));
assert!(json.contains("main"));
}
#[test]
fn test_path_impact_summary_serialization() {
let impact = PathImpactSummary {
path_id: Some("test_path".to_string()),
path_length: 10,
blocks_affected: vec![1, 2, 3, 4, 5],
unique_blocks_count: 5,
};
let json = serde_json::to_string(&impact).unwrap();
assert!(json.contains("path_id"));
assert!(json.contains("path_length"));
assert!(json.contains("blocks_affected"));
assert!(json.contains("unique_blocks_count"));
assert!(json.contains("test_path"));
}
#[test]
fn test_enhanced_blast_zone_without_path_impact() {
let blast_zone = EnhancedBlastZone {
target: "test_function".to_string(),
forward_reachable: vec![],
backward_reachable: vec![],
path_impact: None,
};
assert!(blast_zone.path_impact.is_none());
let json = serde_json::to_string(&blast_zone).unwrap();
assert!(json.contains(r#""path_impact":null"#));
}
#[test]
fn test_condensation_json_creation() {
use magellan::{CondensationGraph, CondensationResult, Supernode};
use std::collections::HashMap;
let symbol1 = SymbolInfo {
symbol_id: Some("func_a_id".to_string()),
fqn: Some("func_a".to_string()),
file_path: "a.rs".to_string(),
kind: "Function".to_string(),
};
let symbol2 = SymbolInfo {
symbol_id: Some("func_b_id".to_string()),
fqn: Some("func_b".to_string()),
file_path: "b.rs".to_string(),
kind: "Function".to_string(),
};
let supernode1 = Supernode {
id: 0,
members: vec![symbol1.clone()],
};
let supernode2 = Supernode {
id: 1,
members: vec![symbol2.clone(), symbol1.clone()], };
let graph = CondensationGraph {
supernodes: vec![supernode1, supernode2],
edges: vec![(0, 1)],
};
let mut mapping = HashMap::new();
mapping.insert("func_a".to_string(), 0);
mapping.insert("func_b".to_string(), 1);
let result = CondensationResult {
graph,
original_to_supernode: mapping,
};
let json: CondensationJson = (&result).into();
assert_eq!(json.supernode_count, 2);
assert_eq!(json.edge_count, 1);
assert_eq!(json.largest_scc_size, 2);
assert_eq!(json.supernodes.len(), 2);
assert_eq!(json.supernodes[0].id, "0");
assert_eq!(json.supernodes[0].member_count, 1);
assert_eq!(json.supernodes[1].id, "1");
assert_eq!(json.supernodes[1].member_count, 2);
assert!(json.supernodes[1].members.contains(&"func_b".to_string()));
}
#[test]
fn test_condensation_json_serialization() {
use magellan::{CondensationGraph, CondensationResult, Supernode};
use std::collections::HashMap;
let supernode = Supernode {
id: 0,
members: vec![SymbolInfo {
symbol_id: Some("test_id".to_string()),
fqn: Some("test_func".to_string()),
file_path: "test.rs".to_string(),
kind: "Function".to_string(),
}],
};
let graph = CondensationGraph {
supernodes: vec![supernode],
edges: vec![],
};
let result = CondensationResult {
graph,
original_to_supernode: HashMap::new(),
};
let json: CondensationJson = (&result).into();
let json_string = serde_json::to_string(&json).unwrap();
assert!(json_string.contains(r#""supernode_count":1"#));
assert!(json_string.contains(r#""edge_count":0"#));
assert!(json_string.contains(r#""largest_scc_size":1"#));
assert!(json_string.contains(r#""id":"0""#));
assert!(json_string.contains("test_func"));
}
#[test]
fn test_supernode_json_creation() {
let supernode = SupernodeJson {
id: "42".to_string(),
member_count: 3,
members: vec![
"func_a".to_string(),
"func_b".to_string(),
"func_c".to_string(),
],
};
assert_eq!(supernode.id, "42");
assert_eq!(supernode.member_count, 3);
assert_eq!(supernode.members.len(), 3);
let json = serde_json::to_string(&supernode).unwrap();
assert!(json.contains(r#""id":"42""#));
assert!(json.contains(r#""member_count":3"#));
assert!(json.contains("func_a"));
}
#[test]
fn test_execution_path_json_conversion() {
use magellan::{ExecutionPath, SymbolInfo};
let symbols = vec![
SymbolInfo {
symbol_id: Some("main_id".to_string()),
fqn: Some("main".to_string()),
file_path: "main.rs".to_string(),
kind: "Function".to_string(),
},
SymbolInfo {
symbol_id: Some("helper_id".to_string()),
fqn: Some("helper".to_string()),
file_path: "helper.rs".to_string(),
kind: "Function".to_string(),
},
];
let path = ExecutionPath {
symbols: symbols.clone(),
length: 2,
};
let json_path: ExecutionPathJson = (&path).into();
assert_eq!(json_path.length, 2);
assert_eq!(json_path.symbols.len(), 2);
assert_eq!(json_path.symbols[0].fqn, Some("main".to_string()));
assert_eq!(json_path.symbols[1].fqn, Some("helper".to_string()));
let json = serde_json::to_string(&json_path).unwrap();
assert!(json.contains("symbols"));
assert!(json.contains("length"));
assert!(json.contains("main"));
assert!(json.contains("helper"));
}
#[test]
fn test_path_statistics_json_creation() {
let stats = PathStatisticsJson {
avg_length: 3.5,
max_length: 10,
min_length: 1,
unique_symbols: 5,
};
assert_eq!(stats.avg_length, 3.5);
assert_eq!(stats.max_length, 10);
assert_eq!(stats.min_length, 1);
assert_eq!(stats.unique_symbols, 5);
let json = serde_json::to_string(&stats).unwrap();
assert!(json.contains(r#""avg_length":3.5"#));
assert!(json.contains(r#""max_length":10"#));
assert!(json.contains(r#""min_length":1"#));
assert!(json.contains(r#""unique_symbols":5"#));
}
#[test]
fn test_path_enumeration_json_serialization() {
use magellan::{ExecutionPath, PathStatistics};
let symbols = vec![SymbolInfo {
symbol_id: Some("func1_id".to_string()),
fqn: Some("func1".to_string()),
file_path: "test.rs".to_string(),
kind: "Function".to_string(),
}];
let _path = ExecutionPath { symbols, length: 1 };
let _stats = PathStatistics {
avg_length: 2.0,
max_length: 5,
min_length: 1,
unique_symbols: 3,
};
let json_stats = PathStatisticsJson {
avg_length: 2.0,
max_length: 5,
min_length: 1,
unique_symbols: 3,
};
let json = serde_json::to_string(&json_stats).unwrap();
assert!(json.contains("avg_length"));
assert!(json.contains("max_length"));
assert!(json.contains("min_length"));
assert!(json.contains("unique_symbols"));
}
#[test]
fn test_execution_path_json_empty_path() {
use magellan::ExecutionPath;
let path = ExecutionPath {
symbols: vec![],
length: 0,
};
let json_path: ExecutionPathJson = (&path).into();
assert_eq!(json_path.length, 0);
assert_eq!(json_path.symbols.len(), 0);
let json = serde_json::to_string(&json_path).unwrap();
assert!(json.contains(r#""symbols":[]"#));
assert!(json.contains(r#""length":0"#));
}
#[test]
fn test_condensation_json_from_result() {
let json = CondensationJson {
supernode_count: 5,
edge_count: 8,
supernodes: vec![SupernodeJson {
id: "scc0".to_string(),
member_count: 3,
members: vec![
"func_a".to_string(),
"func_b".to_string(),
"func_c".to_string(),
],
}],
largest_scc_size: 3,
};
assert_eq!(json.supernode_count, 5);
assert_eq!(json.largest_scc_size, 3);
assert_eq!(json.supernodes[0].member_count, 3);
let serialized = serde_json::to_string(&json).unwrap();
assert!(serialized.contains("supernode_count"));
assert!(serialized.contains("largest_scc_size"));
}
#[test]
fn test_execution_path_json_serialization() {
let path = ExecutionPathJson {
symbols: vec![SymbolInfoJson {
symbol_id: Some("id1".to_string()),
fqn: Some("main".to_string()),
file_path: "main.rs".to_string(),
kind: "Function".to_string(),
}],
length: 1,
};
let json = serde_json::to_string(&path).unwrap();
assert!(json.contains("main"));
assert!(json.contains("\"length\":1"));
}
#[test]
fn test_all_magellan_imports_utilized() {
let _ = std::marker::PhantomData::<CondensationGraph>;
let _ = std::marker::PhantomData::<CondensationResult>;
let _ = std::marker::PhantomData::<Supernode>;
let _ = std::marker::PhantomData::<ExecutionPath>;
let _ = std::marker::PhantomData::<PathEnumerationResult>;
let _ = std::marker::PhantomData::<PathStatistics>;
let _ = std::marker::PhantomData::<CondensationJson>;
let _ = std::marker::PhantomData::<SupernodeJson>;
let _ = std::marker::PhantomData::<ExecutionPathJson>;
let _ = std::marker::PhantomData::<PathEnumerationJson>;
let _ = std::marker::PhantomData::<PathStatisticsJson>;
let _ = std::marker::PhantomData::<ProgramSlice>;
let _ = std::marker::PhantomData::<SliceDirection>;
let _ = std::marker::PhantomData::<SliceResult>;
let _ = std::marker::PhantomData::<SliceStatistics>;
}
#[test]
fn test_phase_11_integration() {
let condensation = CondensationJson {
supernode_count: 2,
edge_count: 1,
supernodes: vec![SupernodeJson {
id: "scc0".to_string(),
member_count: 2,
members: vec!["func_a".to_string(), "func_b".to_string()],
}],
largest_scc_size: 2,
};
let path_stats = PathStatisticsJson {
avg_length: 3.5,
max_length: 10,
min_length: 1,
unique_symbols: 5,
};
let cond_json = serde_json::to_string(&condensation).unwrap();
let stats_json = serde_json::to_string(&path_stats).unwrap();
assert!(cond_json.contains("supernode_count"));
assert!(stats_json.contains("avg_length"));
}
#[test]
fn test_supernode_json_multiple_members() {
let supernode = SupernodeJson {
id: "cycle_42".to_string(),
member_count: 4,
members: vec![
"func_a".to_string(),
"func_b".to_string(),
"func_c".to_string(),
"func_d".to_string(),
],
};
let json = serde_json::to_string(&supernode).unwrap();
assert!(json.contains("\"member_count\":4"));
assert!(json.contains("cycle_42"));
assert!(json.contains("func_a"));
assert!(json.contains("func_d"));
}
#[test]
fn test_path_statistics_json_edge_cases() {
let empty_stats = PathStatisticsJson {
avg_length: 0.0,
max_length: 0,
min_length: 0,
unique_symbols: 0,
};
let json = serde_json::to_string(&empty_stats).unwrap();
assert!(json.contains("\"avg_length\":0"));
assert!(json.contains("\"unique_symbols\":0"));
let large_stats = PathStatisticsJson {
avg_length: 9999.99,
max_length: 100000,
min_length: 1,
unique_symbols: 50000,
};
let json = serde_json::to_string(&large_stats).unwrap();
assert!(json.contains("9999.99"));
assert!(json.contains("100000"));
}
}