1use anyhow::Result;
15use serde::Serialize;
16use std::collections::HashMap;
17
18pub use magellan::CodeGraph;
20
21pub use magellan::{
23 CondensationResult, Cycle, CycleKind, CycleReport, DeadSymbol, ExecutionPath,
24 PathEnumerationResult, SymbolInfo,
25};
26
27#[allow(unused_imports)]
30use magellan::{
31 CondensationGraph, PathStatistics, ProgramSlice, SliceDirection, SliceResult, SliceStatistics,
32 Supernode,
33};
34
35#[derive(Debug, Clone, Serialize)]
40pub struct DeadSymbolJson {
41 pub fqn: Option<String>,
43 pub file_path: String,
45 pub kind: String,
47 pub reason: String,
49}
50
51impl From<&DeadSymbol> for DeadSymbolJson {
52 fn from(dead: &DeadSymbol) -> Self {
53 Self {
54 fqn: dead.symbol.fqn.clone(),
55 file_path: dead.symbol.file_path.clone(),
56 kind: dead.symbol.kind.clone(),
57 reason: dead.reason.clone(),
58 }
59 }
60}
61
62#[derive(Debug, Clone, Serialize)]
67pub struct SymbolInfoJson {
68 pub symbol_id: Option<String>,
70 pub fqn: Option<String>,
72 pub file_path: String,
74 pub kind: String,
76}
77
78impl From<&SymbolInfo> for SymbolInfoJson {
79 fn from(symbol: &SymbolInfo) -> Self {
80 Self {
81 symbol_id: symbol.symbol_id.clone(),
82 fqn: symbol.fqn.clone(),
83 file_path: symbol.file_path.clone(),
84 kind: symbol.kind.clone(),
85 }
86 }
87}
88
89#[derive(Debug, Clone, Serialize)]
94pub struct SliceWrapper {
95 pub target: SymbolInfoJson,
97 pub direction: String, pub included_symbols: Vec<SymbolInfoJson>,
101 pub symbol_count: usize,
103 pub statistics: SliceStats,
105}
106
107#[derive(Debug, Clone, Serialize)]
109pub struct SliceStats {
110 pub total_symbols: usize,
111 pub data_dependencies: usize,
112 pub control_dependencies: usize,
113}
114
115impl From<&SliceResult> for SliceWrapper {
116 fn from(result: &SliceResult) -> Self {
117 let statistics = SliceStats {
118 total_symbols: result.statistics.total_symbols,
119 data_dependencies: result.statistics.data_dependencies,
120 control_dependencies: result.statistics.control_dependencies,
121 };
122
123 SliceWrapper {
124 target: (&result.slice.target).into(),
125 direction: format!("{:?}", result.slice.direction),
126 included_symbols: result
127 .slice
128 .included_symbols
129 .iter()
130 .map(|s| s.into())
131 .collect(),
132 symbol_count: result.slice.symbol_count,
133 statistics,
134 }
135 }
136}
137
138#[derive(Debug, Clone, Serialize)]
142pub struct ExecutionPathJson {
143 pub symbols: Vec<SymbolInfoJson>,
145 pub length: usize,
147}
148
149impl From<&ExecutionPath> for ExecutionPathJson {
150 fn from(path: &ExecutionPath) -> Self {
151 ExecutionPathJson {
152 symbols: path.symbols.iter().map(|s| s.into()).collect(),
153 length: path.length,
154 }
155 }
156}
157
158#[derive(Debug, Clone, Serialize)]
162pub struct PathEnumerationJson {
163 pub paths: Vec<ExecutionPathJson>,
165 pub total_enumerated: usize,
167 pub truncated: bool,
169 pub statistics: PathStatisticsJson,
171}
172
173#[derive(Debug, Clone, Serialize)]
175pub struct PathStatisticsJson {
176 pub avg_length: f64,
178 pub max_length: usize,
180 pub min_length: usize,
182 pub unique_symbols: usize,
184}
185
186impl From<&PathEnumerationResult> for PathEnumerationJson {
187 fn from(result: &PathEnumerationResult) -> Self {
188 PathEnumerationJson {
189 paths: result.paths.iter().map(|p| p.into()).collect(),
190 total_enumerated: result.total_enumerated,
191 truncated: result.bounded_hit,
192 statistics: PathStatisticsJson {
193 avg_length: result.statistics.avg_length,
194 max_length: result.statistics.max_length,
195 min_length: result.statistics.min_length,
196 unique_symbols: result.statistics.unique_symbols,
197 },
198 }
199 }
200}
201
202#[derive(Debug, Clone, Serialize)]
207pub struct CondensationJson {
208 pub supernode_count: usize,
210 pub edge_count: usize,
212 pub supernodes: Vec<SupernodeJson>,
214 pub largest_scc_size: usize,
216}
217
218#[derive(Debug, Clone, Serialize)]
220pub struct SupernodeJson {
221 pub id: String,
223 pub member_count: usize,
225 pub members: Vec<String>,
227}
228
229impl From<&CondensationResult> for CondensationJson {
230 fn from(result: &CondensationResult) -> Self {
231 let supernodes: Vec<SupernodeJson> = result
232 .graph
233 .supernodes
234 .iter()
235 .map(|sn| SupernodeJson {
236 id: sn.id.to_string(),
237 member_count: sn.members.len(),
238 members: sn.members.iter().filter_map(|m| m.fqn.clone()).collect(),
239 })
240 .collect();
241
242 let largest_scc_size = supernodes
243 .iter()
244 .map(|sn| sn.member_count)
245 .max()
246 .unwrap_or(0);
247
248 CondensationJson {
249 supernode_count: result.graph.supernodes.len(),
250 edge_count: result.graph.edges.len(),
251 supernodes,
252 largest_scc_size,
253 }
254 }
255}
256
257#[derive(Debug, Clone, Serialize)]
261pub struct CycleInfo {
262 pub members: Vec<String>,
264 pub cycle_type: String,
266 pub size: usize,
268}
269
270impl From<&Cycle> for CycleInfo {
271 fn from(cycle: &Cycle) -> Self {
272 let members: Vec<String> = cycle
273 .members
274 .iter()
275 .map(|m| m.fqn.as_deref().unwrap_or("<unknown>").to_string())
276 .collect();
277
278 let cycle_type = match cycle.kind {
279 CycleKind::MutualRecursion => "MutualRecursion",
280 CycleKind::SelfLoop => "SelfLoop",
281 };
282
283 Self {
284 members,
285 cycle_type: cycle_type.to_string(),
286 size: cycle.members.len(),
287 }
288 }
289}
290
291#[derive(Debug, Clone, Serialize)]
295pub struct LoopInfo {
296 pub header: usize,
298 pub back_edge_from: usize,
300 pub body_size: usize,
302 pub nesting_level: usize,
304 pub body_blocks: Vec<usize>,
306}
307
308#[derive(Debug, Clone, Serialize)]
313pub struct EnhancedCycles {
314 pub call_graph_cycles: Vec<CycleInfo>,
316 pub function_loops: HashMap<String, Vec<LoopInfo>>,
318 pub total_cycles: usize,
320}
321
322#[derive(Debug, Clone, Serialize)]
333pub struct EnhancedDeadCode {
334 pub uncalled_functions: Vec<DeadSymbolJson>,
336 pub unreachable_blocks: HashMap<String, Vec<usize>>,
338 pub total_dead_count: usize,
340}
341
342#[derive(Debug, Clone, Serialize)]
348pub struct EnhancedBlastZone {
349 pub target: String,
351 pub forward_reachable: Vec<SymbolInfoJson>,
353 pub backward_reachable: Vec<SymbolInfoJson>,
355 pub path_impact: Option<PathImpactSummary>,
357}
358
359#[derive(Debug, Clone, Serialize)]
363pub struct PathImpactSummary {
364 pub path_id: Option<String>,
366 pub path_length: usize,
368 pub blocks_affected: Vec<usize>,
370 pub unique_blocks_count: usize,
372}
373
374pub struct MagellanBridge {
401 graph: CodeGraph,
403}
404
405impl MagellanBridge {
406 pub fn open(db_path: &str) -> Result<Self> {
425 let graph = CodeGraph::open(db_path)?;
426 Ok(Self { graph })
427 }
428
429 pub fn graph(&self) -> &CodeGraph {
445 &self.graph
446 }
447
448 pub fn reachable_symbols(&self, symbol_id: &str) -> Result<Vec<SymbolInfo>> {
479 self.graph.reachable_symbols(symbol_id, None)
480 }
481
482 pub fn reverse_reachable_symbols(&self, symbol_id: &str) -> Result<Vec<SymbolInfo>> {
512 self.graph.reverse_reachable_symbols(symbol_id, None)
513 }
514
515 pub fn dead_symbols(&self, entry_symbol_id: &str) -> Result<Vec<DeadSymbol>> {
556 self.graph.dead_symbols(entry_symbol_id)
557 }
558
559 pub fn detect_cycles(&self) -> Result<CycleReport> {
586 self.graph.detect_cycles()
587 }
588
589 pub fn backward_slice(&self, symbol_id: &str) -> Result<SliceWrapper> {
620 let result = self.graph.backward_slice(symbol_id)?;
621 Ok((&result).into())
622 }
623
624 pub fn forward_slice(&self, symbol_id: &str) -> Result<SliceWrapper> {
655 let result = self.graph.forward_slice(symbol_id)?;
656 Ok((&result).into())
657 }
658
659 pub fn enumerate_paths(
692 &self,
693 start_symbol_id: &str,
694 end_symbol_id: Option<&str>,
695 max_depth: usize,
696 max_paths: usize,
697 ) -> Result<PathEnumerationResult> {
698 self.graph
699 .enumerate_paths(start_symbol_id, end_symbol_id, max_depth, max_paths)
700 }
701
702 pub fn enumerate_paths_json(
729 &self,
730 start_symbol_id: &str,
731 end_symbol_id: Option<&str>,
732 max_depth: usize,
733 max_paths: usize,
734 ) -> Result<PathEnumerationJson> {
735 let result =
736 self.graph
737 .enumerate_paths(start_symbol_id, end_symbol_id, max_depth, max_paths)?;
738 Ok((&result).into())
739 }
740
741 pub fn condense_call_graph(&self) -> Result<CondensationResult> {
771 self.graph.condense_call_graph()
772 }
773
774 pub fn condense_call_graph_json(&self) -> Result<CondensationJson> {
795 let result = self.graph.condense_call_graph()?;
796 Ok((&result).into())
797 }
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803
804 #[test]
805 fn test_magellan_bridge_creation() {
806 let _ = || -> Result<()> {
809 let _bridge = MagellanBridge::open("test.db")?;
810 Ok(())
811 };
812 }
813
814 #[test]
815 fn test_dead_symbol_json_from_dead_symbol() {
816 use magellan::{DeadSymbol as MagellanDeadSymbol, SymbolInfo};
818
819 let symbol_info = SymbolInfo {
820 symbol_id: Some("test_symbol_id".to_string()),
821 fqn: Some("test::function".to_string()),
822 file_path: "test.rs".to_string(),
823 kind: "Function".to_string(),
824 };
825
826 let dead = MagellanDeadSymbol {
827 symbol: symbol_info,
828 reason: "Not called from entry point".to_string(),
829 };
830
831 let json_symbol: DeadSymbolJson = (&dead).into();
832
833 assert_eq!(json_symbol.fqn, Some("test::function".to_string()));
834 assert_eq!(json_symbol.file_path, "test.rs");
835 assert_eq!(json_symbol.kind, "Function");
836 assert_eq!(json_symbol.reason, "Not called from entry point");
837 }
838
839 #[test]
840 fn test_enhanced_dead_code_serialization() {
841 use magellan::{DeadSymbol as MagellanDeadSymbol, SymbolInfo};
843
844 let symbol_info = SymbolInfo {
845 symbol_id: Some("test_id".to_string()),
846 fqn: Some("dead::function".to_string()),
847 file_path: "test.rs".to_string(),
848 kind: "Function".to_string(),
849 };
850
851 let dead = MagellanDeadSymbol {
852 symbol: symbol_info,
853 reason: "Uncalled".to_string(),
854 };
855
856 let json_symbol: DeadSymbolJson = (&dead).into();
857
858 let mut unreachable_blocks = std::collections::HashMap::new();
859 unreachable_blocks.insert("test_func".to_string(), vec![1, 2, 3]);
860
861 let enhanced = EnhancedDeadCode {
862 uncalled_functions: vec![json_symbol],
863 unreachable_blocks,
864 total_dead_count: 4,
865 };
866
867 let json = serde_json::to_string(&enhanced).unwrap();
869 assert!(json.contains("uncalled_functions"));
870 assert!(json.contains("unreachable_blocks"));
871 assert!(json.contains("total_dead_count"));
872 }
873
874 #[test]
875 fn test_cycle_info_from_cycle() {
876 use magellan::{Cycle, CycleKind, SymbolInfo};
878
879 let symbol1 = SymbolInfo {
880 symbol_id: Some("func_a_id".to_string()),
881 fqn: Some("func_a".to_string()),
882 file_path: "test.rs".to_string(),
883 kind: "Function".to_string(),
884 };
885
886 let symbol2 = SymbolInfo {
887 symbol_id: Some("func_b_id".to_string()),
888 fqn: Some("func_b".to_string()),
889 file_path: "test.rs".to_string(),
890 kind: "Function".to_string(),
891 };
892
893 let mutual_recursion_cycle = Cycle {
895 members: vec![symbol1.clone(), symbol2.clone()],
896 kind: CycleKind::MutualRecursion,
897 };
898
899 let cycle_info: CycleInfo = (&mutual_recursion_cycle).into();
900 assert_eq!(cycle_info.cycle_type, "MutualRecursion");
901 assert_eq!(cycle_info.size, 2);
902 assert_eq!(cycle_info.members, vec!["func_a", "func_b"]);
903
904 let self_loop_cycle = Cycle {
906 members: vec![symbol1],
907 kind: CycleKind::SelfLoop,
908 };
909
910 let cycle_info: CycleInfo = (&self_loop_cycle).into();
911 assert_eq!(cycle_info.cycle_type, "SelfLoop");
912 assert_eq!(cycle_info.size, 1);
913 assert_eq!(cycle_info.members, vec!["func_a"]);
914 }
915
916 #[test]
917 fn test_enhanced_cycles_serialization() {
918 use std::collections::HashMap;
920
921 let mut function_loops = HashMap::new();
922 function_loops.insert(
923 "test_func".to_string(),
924 vec![LoopInfo {
925 header: 1,
926 back_edge_from: 2,
927 body_size: 3,
928 nesting_level: 0,
929 body_blocks: vec![1, 2, 3],
930 }],
931 );
932
933 let call_graph_cycles = vec![CycleInfo {
934 members: vec!["func_a".to_string(), "func_b".to_string()],
935 cycle_type: "MutualRecursion".to_string(),
936 size: 2,
937 }];
938
939 let enhanced = EnhancedCycles {
940 call_graph_cycles,
941 function_loops,
942 total_cycles: 2,
943 };
944
945 let json = serde_json::to_string(&enhanced).unwrap();
947 assert!(json.contains("call_graph_cycles"));
948 assert!(json.contains("function_loops"));
949 assert!(json.contains("total_cycles"));
950 assert!(json.contains("MutualRecursion"));
951 }
952
953 #[test]
954 fn test_loop_info_serialization() {
955 let loop_info = LoopInfo {
957 header: 1,
958 back_edge_from: 3,
959 body_size: 5,
960 nesting_level: 2,
961 body_blocks: vec![1, 2, 3, 4, 5],
962 };
963
964 let json = serde_json::to_string(&loop_info).unwrap();
966 assert!(json.contains(r#""header":1"#));
967 assert!(json.contains(r#""back_edge_from":3"#));
968 assert!(json.contains(r#""body_size":5"#));
969 assert!(json.contains(r#""nesting_level":2"#));
970 assert!(json.contains(r#"body_blocks"#));
971 }
972
973 #[test]
974 fn test_slice_wrapper_serialization() {
975 use magellan::{ProgramSlice, SliceDirection, SliceResult, SliceStatistics};
977
978 let target = SymbolInfo {
979 symbol_id: Some("target_id".to_string()),
980 fqn: Some("target_function".to_string()),
981 file_path: "test.rs".to_string(),
982 kind: "Function".to_string(),
983 };
984
985 let included_symbols = vec![
986 SymbolInfo {
987 symbol_id: Some("sym1_id".to_string()),
988 fqn: Some("sym1".to_string()),
989 file_path: "test.rs".to_string(),
990 kind: "Function".to_string(),
991 },
992 SymbolInfo {
993 symbol_id: Some("sym2_id".to_string()),
994 fqn: Some("sym2".to_string()),
995 file_path: "test.rs".to_string(),
996 kind: "Function".to_string(),
997 },
998 ];
999
1000 let program_slice = ProgramSlice {
1001 target: target.clone(),
1002 direction: SliceDirection::Backward,
1003 included_symbols: included_symbols.clone(),
1004 symbol_count: 3,
1005 };
1006
1007 let statistics = SliceStatistics {
1008 total_symbols: 3,
1009 data_dependencies: 2,
1010 control_dependencies: 1,
1011 };
1012
1013 let slice_result = SliceResult {
1014 slice: program_slice,
1015 statistics,
1016 };
1017
1018 let wrapper: SliceWrapper = (&slice_result).into();
1019
1020 assert_eq!(wrapper.target.fqn, Some("target_function".to_string()));
1022 assert_eq!(wrapper.direction, "Backward");
1023 assert_eq!(wrapper.symbol_count, 3);
1024 assert_eq!(wrapper.statistics.total_symbols, 3);
1025 assert_eq!(wrapper.statistics.data_dependencies, 2);
1026 assert_eq!(wrapper.statistics.control_dependencies, 1);
1027 assert_eq!(wrapper.included_symbols.len(), 2);
1028
1029 let json = serde_json::to_string(&wrapper).unwrap();
1031 assert!(json.contains("target"));
1032 assert!(json.contains("direction"));
1033 assert!(json.contains("Backward"));
1034 assert!(json.contains("included_symbols"));
1035 assert!(json.contains("statistics"));
1036 assert!(json.contains("data_dependencies"));
1037 }
1038
1039 #[test]
1040 fn test_slice_stats_creation() {
1041 let stats = SliceStats {
1043 total_symbols: 10,
1044 data_dependencies: 5,
1045 control_dependencies: 3,
1046 };
1047
1048 assert_eq!(stats.total_symbols, 10);
1049 assert_eq!(stats.data_dependencies, 5);
1050 assert_eq!(stats.control_dependencies, 3);
1051
1052 let json = serde_json::to_string(&stats).unwrap();
1054 assert!(json.contains(r#""total_symbols":10"#));
1055 assert!(json.contains(r#""data_dependencies":5"#));
1056 assert!(json.contains(r#""control_dependencies":3"#));
1057 }
1058
1059 #[test]
1060 fn test_symbol_info_json_from_symbol_info() {
1061 use magellan::SymbolInfo;
1063
1064 let symbol_info = SymbolInfo {
1065 symbol_id: Some("test_symbol_id".to_string()),
1066 fqn: Some("test::function".to_string()),
1067 file_path: "test.rs".to_string(),
1068 kind: "Function".to_string(),
1069 };
1070
1071 let json_symbol: SymbolInfoJson = (&symbol_info).into();
1072
1073 assert_eq!(json_symbol.symbol_id, Some("test_symbol_id".to_string()));
1074 assert_eq!(json_symbol.fqn, Some("test::function".to_string()));
1075 assert_eq!(json_symbol.file_path, "test.rs");
1076 assert_eq!(json_symbol.kind, "Function");
1077 }
1078
1079 #[test]
1080 fn test_enhanced_blast_zone_creation() {
1081 let forward = vec![
1083 SymbolInfoJson {
1084 symbol_id: Some("func_a_id".to_string()),
1085 fqn: Some("func_a".to_string()),
1086 file_path: "a.rs".to_string(),
1087 kind: "Function".to_string(),
1088 },
1089 SymbolInfoJson {
1090 symbol_id: Some("func_b_id".to_string()),
1091 fqn: Some("func_b".to_string()),
1092 file_path: "b.rs".to_string(),
1093 kind: "Function".to_string(),
1094 },
1095 ];
1096
1097 let backward = vec![SymbolInfoJson {
1098 symbol_id: Some("main_id".to_string()),
1099 fqn: Some("main".to_string()),
1100 file_path: "main.rs".to_string(),
1101 kind: "Function".to_string(),
1102 }];
1103
1104 let path_impact = PathImpactSummary {
1105 path_id: Some("test_path_id".to_string()),
1106 path_length: 5,
1107 blocks_affected: vec![1, 2, 3, 4],
1108 unique_blocks_count: 4,
1109 };
1110
1111 let blast_zone = EnhancedBlastZone {
1112 target: "test_function".to_string(),
1113 forward_reachable: forward.clone(),
1114 backward_reachable: backward.clone(),
1115 path_impact: Some(path_impact),
1116 };
1117
1118 assert_eq!(blast_zone.target, "test_function");
1119 assert_eq!(blast_zone.forward_reachable.len(), 2);
1120 assert_eq!(blast_zone.backward_reachable.len(), 1);
1121 assert!(blast_zone.path_impact.is_some());
1122
1123 let json = serde_json::to_string(&blast_zone).unwrap();
1125 assert!(json.contains("target"));
1126 assert!(json.contains("forward_reachable"));
1127 assert!(json.contains("backward_reachable"));
1128 assert!(json.contains("path_impact"));
1129 assert!(json.contains("func_a"));
1130 assert!(json.contains("main"));
1131 }
1132
1133 #[test]
1134 fn test_path_impact_summary_serialization() {
1135 let impact = PathImpactSummary {
1137 path_id: Some("test_path".to_string()),
1138 path_length: 10,
1139 blocks_affected: vec![1, 2, 3, 4, 5],
1140 unique_blocks_count: 5,
1141 };
1142
1143 let json = serde_json::to_string(&impact).unwrap();
1144 assert!(json.contains("path_id"));
1145 assert!(json.contains("path_length"));
1146 assert!(json.contains("blocks_affected"));
1147 assert!(json.contains("unique_blocks_count"));
1148 assert!(json.contains("test_path"));
1149 }
1150
1151 #[test]
1152 fn test_enhanced_blast_zone_without_path_impact() {
1153 let blast_zone = EnhancedBlastZone {
1155 target: "test_function".to_string(),
1156 forward_reachable: vec![],
1157 backward_reachable: vec![],
1158 path_impact: None,
1159 };
1160
1161 assert!(blast_zone.path_impact.is_none());
1162
1163 let json = serde_json::to_string(&blast_zone).unwrap();
1165 assert!(json.contains(r#""path_impact":null"#));
1166 }
1167
1168 #[test]
1169 fn test_condensation_json_creation() {
1170 use magellan::{CondensationGraph, CondensationResult, Supernode};
1172 use std::collections::HashMap;
1173
1174 let symbol1 = SymbolInfo {
1176 symbol_id: Some("func_a_id".to_string()),
1177 fqn: Some("func_a".to_string()),
1178 file_path: "a.rs".to_string(),
1179 kind: "Function".to_string(),
1180 };
1181
1182 let symbol2 = SymbolInfo {
1183 symbol_id: Some("func_b_id".to_string()),
1184 fqn: Some("func_b".to_string()),
1185 file_path: "b.rs".to_string(),
1186 kind: "Function".to_string(),
1187 };
1188
1189 let supernode1 = Supernode {
1190 id: 0,
1191 members: vec![symbol1.clone()],
1192 };
1193
1194 let supernode2 = Supernode {
1195 id: 1,
1196 members: vec![symbol2.clone(), symbol1.clone()], };
1198
1199 let graph = CondensationGraph {
1200 supernodes: vec![supernode1, supernode2],
1201 edges: vec![(0, 1)],
1202 };
1203
1204 let mut mapping = HashMap::new();
1205 mapping.insert("func_a".to_string(), 0);
1206 mapping.insert("func_b".to_string(), 1);
1207
1208 let result = CondensationResult {
1209 graph,
1210 original_to_supernode: mapping,
1211 };
1212
1213 let json: CondensationJson = (&result).into();
1214
1215 assert_eq!(json.supernode_count, 2);
1216 assert_eq!(json.edge_count, 1);
1217 assert_eq!(json.largest_scc_size, 2);
1218 assert_eq!(json.supernodes.len(), 2);
1219 assert_eq!(json.supernodes[0].id, "0");
1220 assert_eq!(json.supernodes[0].member_count, 1);
1221 assert_eq!(json.supernodes[1].id, "1");
1222 assert_eq!(json.supernodes[1].member_count, 2);
1223 assert!(json.supernodes[1].members.contains(&"func_b".to_string()));
1224 }
1225
1226 #[test]
1227 fn test_condensation_json_serialization() {
1228 use magellan::{CondensationGraph, CondensationResult, Supernode};
1230 use std::collections::HashMap;
1231
1232 let supernode = Supernode {
1233 id: 0,
1234 members: vec![SymbolInfo {
1235 symbol_id: Some("test_id".to_string()),
1236 fqn: Some("test_func".to_string()),
1237 file_path: "test.rs".to_string(),
1238 kind: "Function".to_string(),
1239 }],
1240 };
1241
1242 let graph = CondensationGraph {
1243 supernodes: vec![supernode],
1244 edges: vec![],
1245 };
1246
1247 let result = CondensationResult {
1248 graph,
1249 original_to_supernode: HashMap::new(),
1250 };
1251
1252 let json: CondensationJson = (&result).into();
1253 let json_string = serde_json::to_string(&json).unwrap();
1254
1255 assert!(json_string.contains(r#""supernode_count":1"#));
1256 assert!(json_string.contains(r#""edge_count":0"#));
1257 assert!(json_string.contains(r#""largest_scc_size":1"#));
1258 assert!(json_string.contains(r#""id":"0""#));
1259 assert!(json_string.contains("test_func"));
1260 }
1261
1262 #[test]
1263 fn test_supernode_json_creation() {
1264 let supernode = SupernodeJson {
1266 id: "42".to_string(),
1267 member_count: 3,
1268 members: vec![
1269 "func_a".to_string(),
1270 "func_b".to_string(),
1271 "func_c".to_string(),
1272 ],
1273 };
1274
1275 assert_eq!(supernode.id, "42");
1276 assert_eq!(supernode.member_count, 3);
1277 assert_eq!(supernode.members.len(), 3);
1278
1279 let json = serde_json::to_string(&supernode).unwrap();
1280 assert!(json.contains(r#""id":"42""#));
1281 assert!(json.contains(r#""member_count":3"#));
1282 assert!(json.contains("func_a"));
1283 }
1284
1285 #[test]
1286 fn test_execution_path_json_conversion() {
1287 use magellan::{ExecutionPath, SymbolInfo};
1288
1289 let symbols = vec![
1290 SymbolInfo {
1291 symbol_id: Some("main_id".to_string()),
1292 fqn: Some("main".to_string()),
1293 file_path: "main.rs".to_string(),
1294 kind: "Function".to_string(),
1295 },
1296 SymbolInfo {
1297 symbol_id: Some("helper_id".to_string()),
1298 fqn: Some("helper".to_string()),
1299 file_path: "helper.rs".to_string(),
1300 kind: "Function".to_string(),
1301 },
1302 ];
1303
1304 let path = ExecutionPath {
1305 symbols: symbols.clone(),
1306 length: 2,
1307 };
1308
1309 let json_path: ExecutionPathJson = (&path).into();
1310
1311 assert_eq!(json_path.length, 2);
1312 assert_eq!(json_path.symbols.len(), 2);
1313 assert_eq!(json_path.symbols[0].fqn, Some("main".to_string()));
1314 assert_eq!(json_path.symbols[1].fqn, Some("helper".to_string()));
1315
1316 let json = serde_json::to_string(&json_path).unwrap();
1318 assert!(json.contains("symbols"));
1319 assert!(json.contains("length"));
1320 assert!(json.contains("main"));
1321 assert!(json.contains("helper"));
1322 }
1323
1324 #[test]
1325 fn test_path_statistics_json_creation() {
1326 let stats = PathStatisticsJson {
1327 avg_length: 3.5,
1328 max_length: 10,
1329 min_length: 1,
1330 unique_symbols: 5,
1331 };
1332
1333 assert_eq!(stats.avg_length, 3.5);
1334 assert_eq!(stats.max_length, 10);
1335 assert_eq!(stats.min_length, 1);
1336 assert_eq!(stats.unique_symbols, 5);
1337
1338 let json = serde_json::to_string(&stats).unwrap();
1340 assert!(json.contains(r#""avg_length":3.5"#));
1341 assert!(json.contains(r#""max_length":10"#));
1342 assert!(json.contains(r#""min_length":1"#));
1343 assert!(json.contains(r#""unique_symbols":5"#));
1344 }
1345
1346 #[test]
1347 fn test_path_enumeration_json_serialization() {
1348 use magellan::{ExecutionPath, PathStatistics};
1349
1350 let symbols = vec![SymbolInfo {
1351 symbol_id: Some("func1_id".to_string()),
1352 fqn: Some("func1".to_string()),
1353 file_path: "test.rs".to_string(),
1354 kind: "Function".to_string(),
1355 }];
1356
1357 let _path = ExecutionPath { symbols, length: 1 };
1358
1359 let _stats = PathStatistics {
1360 avg_length: 2.0,
1361 max_length: 5,
1362 min_length: 1,
1363 unique_symbols: 3,
1364 };
1365
1366 let json_stats = PathStatisticsJson {
1369 avg_length: 2.0,
1370 max_length: 5,
1371 min_length: 1,
1372 unique_symbols: 3,
1373 };
1374
1375 let json = serde_json::to_string(&json_stats).unwrap();
1376 assert!(json.contains("avg_length"));
1377 assert!(json.contains("max_length"));
1378 assert!(json.contains("min_length"));
1379 assert!(json.contains("unique_symbols"));
1380 }
1381
1382 #[test]
1383 fn test_execution_path_json_empty_path() {
1384 use magellan::ExecutionPath;
1385
1386 let path = ExecutionPath {
1387 symbols: vec![],
1388 length: 0,
1389 };
1390
1391 let json_path: ExecutionPathJson = (&path).into();
1392
1393 assert_eq!(json_path.length, 0);
1394 assert_eq!(json_path.symbols.len(), 0);
1395
1396 let json = serde_json::to_string(&json_path).unwrap();
1398 assert!(json.contains(r#""symbols":[]"#));
1399 assert!(json.contains(r#""length":0"#));
1400 }
1401
1402 #[test]
1408 fn test_condensation_json_from_result() {
1409 let json = CondensationJson {
1411 supernode_count: 5,
1412 edge_count: 8,
1413 supernodes: vec![SupernodeJson {
1414 id: "scc0".to_string(),
1415 member_count: 3,
1416 members: vec![
1417 "func_a".to_string(),
1418 "func_b".to_string(),
1419 "func_c".to_string(),
1420 ],
1421 }],
1422 largest_scc_size: 3,
1423 };
1424
1425 assert_eq!(json.supernode_count, 5);
1426 assert_eq!(json.largest_scc_size, 3);
1427 assert_eq!(json.supernodes[0].member_count, 3);
1428
1429 let serialized = serde_json::to_string(&json).unwrap();
1431 assert!(serialized.contains("supernode_count"));
1432 assert!(serialized.contains("largest_scc_size"));
1433 }
1434
1435 #[test]
1437 fn test_execution_path_json_serialization() {
1438 let path = ExecutionPathJson {
1439 symbols: vec![SymbolInfoJson {
1440 symbol_id: Some("id1".to_string()),
1441 fqn: Some("main".to_string()),
1442 file_path: "main.rs".to_string(),
1443 kind: "Function".to_string(),
1444 }],
1445 length: 1,
1446 };
1447
1448 let json = serde_json::to_string(&path).unwrap();
1449 assert!(json.contains("main"));
1450 assert!(json.contains("\"length\":1"));
1451 }
1452
1453 #[test]
1458 fn test_all_magellan_imports_utilized() {
1459 let _ = std::marker::PhantomData::<CondensationGraph>;
1461 let _ = std::marker::PhantomData::<CondensationResult>;
1462 let _ = std::marker::PhantomData::<Supernode>;
1463
1464 let _ = std::marker::PhantomData::<ExecutionPath>;
1466 let _ = std::marker::PhantomData::<PathEnumerationResult>;
1467 let _ = std::marker::PhantomData::<PathStatistics>;
1468
1469 let _ = std::marker::PhantomData::<CondensationJson>;
1471 let _ = std::marker::PhantomData::<SupernodeJson>;
1472 let _ = std::marker::PhantomData::<ExecutionPathJson>;
1473 let _ = std::marker::PhantomData::<PathEnumerationJson>;
1474 let _ = std::marker::PhantomData::<PathStatisticsJson>;
1475
1476 let _ = std::marker::PhantomData::<ProgramSlice>;
1478 let _ = std::marker::PhantomData::<SliceDirection>;
1479 let _ = std::marker::PhantomData::<SliceResult>;
1480 let _ = std::marker::PhantomData::<SliceStatistics>;
1481 }
1482
1483 #[test]
1485 fn test_phase_11_integration() {
1486 let condensation = CondensationJson {
1488 supernode_count: 2,
1489 edge_count: 1,
1490 supernodes: vec![SupernodeJson {
1491 id: "scc0".to_string(),
1492 member_count: 2,
1493 members: vec!["func_a".to_string(), "func_b".to_string()],
1494 }],
1495 largest_scc_size: 2,
1496 };
1497
1498 let path_stats = PathStatisticsJson {
1499 avg_length: 3.5,
1500 max_length: 10,
1501 min_length: 1,
1502 unique_symbols: 5,
1503 };
1504
1505 let cond_json = serde_json::to_string(&condensation).unwrap();
1507 let stats_json = serde_json::to_string(&path_stats).unwrap();
1508
1509 assert!(cond_json.contains("supernode_count"));
1510 assert!(stats_json.contains("avg_length"));
1511 }
1512
1513 #[test]
1515 fn test_supernode_json_multiple_members() {
1516 let supernode = SupernodeJson {
1517 id: "cycle_42".to_string(),
1518 member_count: 4,
1519 members: vec![
1520 "func_a".to_string(),
1521 "func_b".to_string(),
1522 "func_c".to_string(),
1523 "func_d".to_string(),
1524 ],
1525 };
1526
1527 let json = serde_json::to_string(&supernode).unwrap();
1528 assert!(json.contains("\"member_count\":4"));
1529 assert!(json.contains("cycle_42"));
1530 assert!(json.contains("func_a"));
1531 assert!(json.contains("func_d"));
1532 }
1533
1534 #[test]
1536 fn test_path_statistics_json_edge_cases() {
1537 let empty_stats = PathStatisticsJson {
1539 avg_length: 0.0,
1540 max_length: 0,
1541 min_length: 0,
1542 unique_symbols: 0,
1543 };
1544
1545 let json = serde_json::to_string(&empty_stats).unwrap();
1546 assert!(json.contains("\"avg_length\":0"));
1547 assert!(json.contains("\"unique_symbols\":0"));
1548
1549 let large_stats = PathStatisticsJson {
1551 avg_length: 9999.99,
1552 max_length: 100000,
1553 min_length: 1,
1554 unique_symbols: 50000,
1555 };
1556
1557 let json = serde_json::to_string(&large_stats).unwrap();
1558 assert!(json.contains("9999.99"));
1559 assert!(json.contains("100000"));
1560 }
1561}