1use crate::cfg::CfgModule;
6use crate::edit::EditModule;
7use crate::error::Result;
8use crate::graph::GraphModule;
9use crate::search::SearchModule;
10use crate::types::Symbol;
11use std::sync::Arc;
12use std::time::Instant;
13
14pub mod complexity;
15pub mod dead_code;
16pub mod diff;
17pub mod impact;
18pub mod modules;
19
20pub use complexity::{ComplexityMetrics, RiskLevel};
21pub use dead_code::{DeadCodeAnalyzer, DeadSymbol};
22pub use diff::{
23 DeleteOperation, Diff, EditOperation, ErrorResult, InsertOperation, RenameOperation,
24};
25pub use impact::{CallChain, CrossReferences, ImpactAnalysis, ImpactData, ReferenceChain};
26pub use modules::{ModuleAnalyzer, ModuleDependencyGraph, ModuleInfo};
27
28pub struct AnalysisModule {
30 graph: GraphModule,
31 search: SearchModule,
32 cfg: CfgModule,
33 edit: EditModule,
34}
35
36#[derive(Debug, Clone)]
38pub struct BenchmarkResults {
39 pub impact_analysis_ms: f64,
41 pub dead_code_ms: f64,
43 pub reference_chain_ms: f64,
45 pub call_chain_ms: f64,
47 pub total_ms: f64,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum ApplyResult {
54 Applied,
56 AlwaysError,
58 Pending,
60 Failed(String),
62}
63
64#[derive(Debug, Clone)]
66pub struct ModuleDependency {
67 pub from: String,
69 pub to: String,
71}
72
73impl AnalysisModule {
74 pub fn new(graph: GraphModule, cfg: CfgModule, edit: EditModule, search: SearchModule) -> Self {
76 Self {
77 graph,
78 search,
79 cfg,
80 edit,
81 }
82 }
83
84 pub fn graph(&self) -> &GraphModule {
86 &self.graph
87 }
88
89 pub fn search(&self) -> &SearchModule {
91 &self.search
92 }
93
94 pub fn cfg(&self) -> &CfgModule {
96 &self.cfg
97 }
98
99 pub fn edit(&self) -> &EditModule {
101 &self.edit
102 }
103
104 pub async fn impact_analysis(&self, symbol: &str) -> Result<ImpactData> {
108 let start = Instant::now();
109
110 let callers = self.graph.callers_of(symbol).await.unwrap_or_default();
112
113 let refs = self.graph.references(symbol).await.unwrap_or_default();
115
116 let _symbol_info = self
118 .graph
119 .find_symbol(symbol)
120 .await
121 .unwrap_or_default()
122 .into_iter()
123 .next();
124
125 let ref_count = refs.len();
127 let call_count = callers.len();
128 let impact_score = ref_count + call_count * 2; tracing::debug!(
131 "Impact analysis for '{}' completed in {:?}",
132 symbol,
133 start.elapsed()
134 );
135
136 Ok(ImpactData {
137 symbol: symbol.to_string(),
138 ref_count,
139 call_count,
140 referenced_by: callers
141 .into_iter()
142 .map(|r| {
143 let name: Arc<str> = Arc::from(r.from_name.unwrap_or_default());
144 Symbol {
145 id: r.from,
146 name: name.clone(),
147 fully_qualified_name: name,
148 kind: crate::types::SymbolKind::Function,
149 language: crate::types::Language::Unknown("unknown".to_string()),
150 location: r.location,
151 parent_id: None,
152 metadata: serde_json::Value::Null,
153 }
154 })
155 .collect(),
156 references: refs
157 .into_iter()
158 .map(|r| {
159 let name: Arc<str> = Arc::from(r.to_name.unwrap_or_default());
160 Symbol {
161 id: r.to,
162 name: name.clone(),
163 fully_qualified_name: name,
164 kind: crate::types::SymbolKind::Function,
165 language: crate::types::Language::Unknown("unknown".to_string()),
166 location: r.location,
167 parent_id: None,
168 metadata: serde_json::Value::Null,
169 }
170 })
171 .collect(),
172 impact_score,
173 })
174 }
175
176 pub async fn analyze_impact(&self, symbol_name: &str) -> Result<ImpactAnalysis> {
180 let impact = self.impact_analysis(symbol_name).await?;
181 Ok(ImpactAnalysis {
182 affected_symbols: impact.referenced_by,
183 call_sites: impact.call_count,
184 })
185 }
186
187 pub async fn dead_code_detection(&self) -> Result<Vec<Symbol>> {
191 let start = Instant::now();
192
193 let db_path = self.graph.store().db_path().join("graph.db");
194
195 if !db_path.exists() {
197 tracing::debug!(
198 "No graph database found at {:?}, returning empty dead code list",
199 db_path
200 );
201 return Ok(Vec::new());
202 }
203
204 let analyzer = dead_code::DeadCodeAnalyzer::new(&db_path);
205
206 match analyzer.find_dead_code() {
207 Ok(dead_symbols) => {
208 let result: Vec<Symbol> = dead_symbols.into_iter().map(Into::into).collect();
209 tracing::debug!(
210 "Dead code detection found {} symbols in {:?}",
211 result.len(),
212 start.elapsed()
213 );
214 Ok(result)
215 }
216 Err(e) => {
217 tracing::warn!("Dead code detection failed: {}", e);
218 Ok(Vec::new())
220 }
221 }
222 }
223
224 pub async fn deep_impact_analysis(
228 &self,
229 symbol_name: &str,
230 depth: u32,
231 ) -> Result<Vec<crate::graph::ImpactedSymbol>> {
232 self.graph.impact_analysis(symbol_name, Some(depth)).await
233 }
234
235 pub async fn find_dead_code(&self) -> Result<Vec<Symbol>> {
239 self.dead_code_detection().await
240 }
241
242 pub async fn reference_chain(&self, symbol: &str) -> Result<Vec<Symbol>> {
246 let start = Instant::now();
247
248 let refs = self.graph.references(symbol).await?;
249
250 let chain: Vec<Symbol> = refs
251 .into_iter()
252 .map(|r| {
253 let name: Arc<str> = Arc::from(r.to_name.unwrap_or_default());
254 Symbol {
255 id: r.from,
256 name: name.clone(),
257 fully_qualified_name: name,
258 kind: crate::types::SymbolKind::Function,
259 language: crate::types::Language::Unknown("unknown".to_string()),
260 location: r.location,
261 parent_id: None,
262 metadata: serde_json::Value::Null,
263 }
264 })
265 .collect();
266
267 tracing::debug!(
268 "Reference chain for '{}' has {} symbols, found in {:?}",
269 symbol,
270 chain.len(),
271 start.elapsed()
272 );
273 Ok(chain)
274 }
275
276 pub async fn call_chain(&self, symbol: &str) -> Result<Vec<Symbol>> {
280 let start = Instant::now();
281
282 let callers = self.graph.callers_of(symbol).await?;
283
284 let chain: Vec<Symbol> = callers
285 .into_iter()
286 .map(|r| {
287 let name: Arc<str> = Arc::from(r.from_name.unwrap_or_default());
288 Symbol {
289 id: r.from,
290 name: name.clone(),
291 fully_qualified_name: name,
292 kind: crate::types::SymbolKind::Function,
293 language: crate::types::Language::Unknown("unknown".to_string()),
294 location: r.location,
295 parent_id: None,
296 metadata: serde_json::Value::Null,
297 }
298 })
299 .collect();
300
301 tracing::debug!(
302 "Call chain for '{}' has {} symbols, found in {:?}",
303 symbol,
304 chain.len(),
305 start.elapsed()
306 );
307 Ok(chain)
308 }
309
310 pub async fn benchmarks(&self) -> Result<BenchmarkResults> {
314 let total_start = Instant::now();
315
316 let impact_start = Instant::now();
318 let _ = self.impact_analysis("test_symbol").await;
319 let impact_analysis_ms = impact_start.elapsed().as_secs_f64() * 1000.0;
320
321 let dead_start = Instant::now();
323 let _ = self.dead_code_detection().await;
324 let dead_code_ms = dead_start.elapsed().as_secs_f64() * 1000.0;
325
326 let ref_start = Instant::now();
328 let _ = self.reference_chain("test_symbol").await;
329 let reference_chain_ms = ref_start.elapsed().as_secs_f64() * 1000.0;
330
331 let call_start = Instant::now();
333 let _ = self.call_chain("test_symbol").await;
334 let call_chain_ms = call_start.elapsed().as_secs_f64() * 1000.0;
335
336 let total_ms = total_start.elapsed().as_secs_f64() * 1000.0;
337
338 Ok(BenchmarkResults {
339 impact_analysis_ms,
340 dead_code_ms,
341 reference_chain_ms,
342 call_chain_ms,
343 total_ms,
344 })
345 }
346
347 pub async fn complexity_metrics(&self, symbol_name: &str) -> Result<ComplexityMetrics> {
351 let symbols = self
353 .graph
354 .find_symbol(symbol_name)
355 .await
356 .unwrap_or_default();
357 if let Some(sym) = symbols.first() {
358 let full_path = self
359 .graph
360 .store()
361 .codebase_path
362 .join(&sym.location.file_path);
363 if let Ok(content) = tokio::fs::read_to_string(&full_path).await {
364 let start = sym.location.byte_start as usize;
366 let end = sym.location.byte_end as usize;
367 if end <= content.len() {
368 let source = &content[start..end];
369 return Ok(self.analyze_source_complexity(source));
370 }
371 }
372 }
373
374 Ok(ComplexityMetrics {
376 cyclomatic_complexity: 1,
377 lines_of_code: 1,
378 max_nesting_depth: 0,
379 decision_points: 0,
380 })
381 }
382
383 pub fn analyze_source_complexity(&self, source: &str) -> ComplexityMetrics {
385 let metrics = complexity::analyze_source_complexity(source);
386 ComplexityMetrics {
387 cyclomatic_complexity: metrics.cyclomatic_complexity,
388 lines_of_code: metrics.lines_of_code,
389 max_nesting_depth: metrics.max_nesting_depth,
390 decision_points: metrics.decision_points,
391 }
392 }
393
394 pub async fn cross_references(&self, symbol_name: &str) -> Result<CrossReferences> {
398 let caller_refs = self.graph.callers_of(symbol_name).await?;
399 let callee_refs = self.graph.references(symbol_name).await?;
400
401 let callers = caller_refs
402 .iter()
403 .map(|r| {
404 let name: Arc<str> = Arc::from(r.from_name.clone().unwrap_or_default());
405 Symbol {
406 id: r.from,
407 name: name.clone(),
408 fully_qualified_name: name,
409 kind: crate::types::SymbolKind::Function,
410 language: crate::types::Language::Unknown(String::new()),
411 location: r.location.clone(),
412 parent_id: None,
413 metadata: serde_json::Value::Null,
414 }
415 })
416 .collect();
417
418 let callees = callee_refs
419 .iter()
420 .map(|r| {
421 let name: Arc<str> = Arc::from(r.to_name.clone().unwrap_or_default());
422 Symbol {
423 id: r.to,
424 name: name.clone(),
425 fully_qualified_name: name,
426 kind: crate::types::SymbolKind::Function,
427 language: crate::types::Language::Unknown(String::new()),
428 location: r.location.clone(),
429 parent_id: None,
430 metadata: serde_json::Value::Null,
431 }
432 })
433 .collect();
434
435 Ok(CrossReferences { callers, callees })
436 }
437
438 pub async fn module_dependencies(&self) -> Result<Vec<ModuleDependency>> {
442 let db_path = self.graph.store().db_path();
443 let analyzer = ModuleAnalyzer::new(db_path);
444
445 let graph = analyzer.analyze_dependencies()?;
446
447 let mut deps = Vec::new();
448 for (from, tos) in graph.dependencies {
449 for to in tos {
450 deps.push(ModuleDependency {
451 from: from.clone(),
452 to,
453 });
454 }
455 }
456
457 Ok(deps)
458 }
459
460 pub async fn find_dependency_cycles(&self) -> Result<Vec<Vec<String>>> {
462 let db_path = self.graph.store().db_path();
463 let analyzer = ModuleAnalyzer::new(db_path);
464 analyzer.find_cycles()
465 }
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471 use crate::storage::BackendKind;
472
473 #[tokio::test]
474 async fn test_impact_data_creation() {
475 let impact = ImpactData {
476 symbol: "test_function".to_string(),
477 ref_count: 5,
478 call_count: 3,
479 referenced_by: vec![],
480 references: vec![],
481 impact_score: 11,
482 };
483 assert_eq!(impact.symbol, "test_function");
484 assert_eq!(impact.ref_count, 5);
485 assert_eq!(impact.call_count, 3);
486 assert_eq!(impact.impact_score, 11);
487 }
488
489 #[tokio::test]
490 async fn test_impact_analysis() {
491 let temp_dir = tempfile::tempdir().unwrap();
492 let store = std::sync::Arc::new(
493 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
494 .await
495 .unwrap(),
496 );
497 let graph = GraphModule::new(Arc::clone(&store));
498 let search = SearchModule::new(Arc::clone(&store));
499 let cfg = CfgModule::new(Arc::clone(&store));
500 let edit = EditModule::new(store);
501
502 let analysis = AnalysisModule::new(graph, cfg, edit, search);
503 let impact = analysis.impact_analysis("nonexistent").await.unwrap();
504
505 assert_eq!(impact.symbol, "nonexistent");
506 assert_eq!(impact.ref_count, 0);
507 assert_eq!(impact.call_count, 0);
508 }
509
510 #[tokio::test]
511 async fn test_dead_code_detection() {
512 let temp_dir = tempfile::tempdir().unwrap();
513 let store = std::sync::Arc::new(
514 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
515 .await
516 .unwrap(),
517 );
518 let graph = GraphModule::new(Arc::clone(&store));
519 let search = SearchModule::new(Arc::clone(&store));
520 let cfg = CfgModule::new(Arc::clone(&store));
521 let edit = EditModule::new(store);
522
523 let analysis = AnalysisModule::new(graph, cfg, edit, search);
524 let dead_code = analysis.dead_code_detection().await.unwrap();
525
526 assert!(dead_code.is_empty());
528 }
529
530 #[tokio::test]
531 async fn test_reference_chain() {
532 let temp_dir = tempfile::tempdir().unwrap();
533 let store = std::sync::Arc::new(
534 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
535 .await
536 .unwrap(),
537 );
538 let graph = GraphModule::new(Arc::clone(&store));
539 let search = SearchModule::new(Arc::clone(&store));
540 let cfg = CfgModule::new(Arc::clone(&store));
541 let edit = EditModule::new(store);
542
543 let analysis = AnalysisModule::new(graph, cfg, edit, search);
544 let chain = analysis.reference_chain("test_symbol").await.unwrap();
545
546 assert!(chain.is_empty());
548 }
549
550 #[tokio::test]
551 async fn test_call_chain() {
552 let temp_dir = tempfile::tempdir().unwrap();
553 let store = std::sync::Arc::new(
554 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
555 .await
556 .unwrap(),
557 );
558 let graph = GraphModule::new(Arc::clone(&store));
559 let search = SearchModule::new(Arc::clone(&store));
560 let cfg = CfgModule::new(Arc::clone(&store));
561 let edit = EditModule::new(store);
562
563 let analysis = AnalysisModule::new(graph, cfg, edit, search);
564 let chain = analysis.call_chain("test_function").await.unwrap();
565
566 assert!(chain.is_empty());
568 }
569
570 #[tokio::test]
571 async fn test_benchmarks() {
572 let temp_dir = tempfile::tempdir().unwrap();
573 let store = std::sync::Arc::new(
574 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
575 .await
576 .unwrap(),
577 );
578 let graph = GraphModule::new(Arc::clone(&store));
579 let search = SearchModule::new(Arc::clone(&store));
580 let cfg = CfgModule::new(Arc::clone(&store));
581 let edit = EditModule::new(store);
582
583 let analysis = AnalysisModule::new(graph, cfg, edit, search);
584 let benchmarks = analysis.benchmarks().await.unwrap();
585
586 assert!(benchmarks.impact_analysis_ms >= 0.0);
588 assert!(benchmarks.dead_code_ms >= 0.0);
589 assert!(benchmarks.reference_chain_ms >= 0.0);
590 assert!(benchmarks.call_chain_ms >= 0.0);
591 assert!(benchmarks.total_ms >= 0.0);
592 }
593
594 #[tokio::test]
595 async fn test_analyze_impact_backward_compat() {
596 let temp_dir = tempfile::tempdir().unwrap();
597 let store = std::sync::Arc::new(
598 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
599 .await
600 .unwrap(),
601 );
602 let graph = GraphModule::new(Arc::clone(&store));
603 let search = SearchModule::new(Arc::clone(&store));
604 let cfg = CfgModule::new(Arc::clone(&store));
605 let edit = EditModule::new(store);
606
607 let analysis = AnalysisModule::new(graph, cfg, edit, search);
608 let impact = analysis.analyze_impact("test").await.unwrap();
609
610 assert_eq!(impact.call_sites, 0);
612 assert!(impact.affected_symbols.is_empty());
613 }
614
615 #[tokio::test]
616 async fn test_find_dead_code_backward_compat() {
617 let temp_dir = tempfile::tempdir().unwrap();
618 let store = std::sync::Arc::new(
619 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
620 .await
621 .unwrap(),
622 );
623 let graph = GraphModule::new(Arc::clone(&store));
624 let search = SearchModule::new(Arc::clone(&store));
625 let cfg = CfgModule::new(Arc::clone(&store));
626 let edit = EditModule::new(store);
627
628 let analysis = AnalysisModule::new(graph, cfg, edit, search);
629 let dead_code = analysis.find_dead_code().await.unwrap();
630
631 assert!(dead_code.is_empty());
633 }
634
635 #[test]
636 fn test_impact_analysis_creation() {
637 let impact = ImpactAnalysis {
638 affected_symbols: vec![],
639 call_sites: 0,
640 };
641 assert!(impact.affected_symbols.is_empty());
642 assert_eq!(impact.call_sites, 0);
643 }
644
645 #[test]
646 fn test_cross_references_creation() {
647 let xrefs = CrossReferences {
648 callers: vec![],
649 callees: vec![],
650 };
651 assert!(xrefs.callers.is_empty());
652 assert!(xrefs.callees.is_empty());
653 }
654
655 #[test]
656 fn test_complexity_metrics_creation() {
657 let metrics = ComplexityMetrics {
658 cyclomatic_complexity: 5,
659 lines_of_code: 100,
660 max_nesting_depth: 3,
661 decision_points: 4,
662 };
663 assert_eq!(metrics.cyclomatic_complexity, 5);
664 assert_eq!(metrics.lines_of_code, 100);
665 assert_eq!(metrics.max_nesting_depth, 3);
666 assert_eq!(metrics.decision_points, 4);
667 }
668
669 #[test]
670 fn test_module_dependency_creation() {
671 let dep = ModuleDependency {
672 from: "mod_a".to_string(),
673 to: "mod_b".to_string(),
674 };
675 assert_eq!(dep.from, "mod_a");
676 assert_eq!(dep.to, "mod_b");
677 }
678
679 #[tokio::test]
682 async fn test_cross_module_error_handling() {
683 let temp_dir = tempfile::tempdir().unwrap();
684 let store = std::sync::Arc::new(
685 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
686 .await
687 .unwrap(),
688 );
689 let graph = GraphModule::new(Arc::clone(&store));
690 let search = SearchModule::new(Arc::clone(&store));
691 let cfg = CfgModule::new(Arc::clone(&store));
692 let edit = EditModule::new(store);
693
694 let analysis = AnalysisModule::new(graph, cfg, edit, search);
695
696 let result = analysis.graph().find_symbol("").await;
698 assert!(result.is_ok()); let search_result = analysis.search().pattern_search("test").await;
702 assert!(search_result.is_ok());
703
704 let semantic_result = analysis.search().semantic_search("test").await;
706 assert!(semantic_result.is_ok());
707 }
708
709 #[tokio::test]
710 async fn test_deep_impact_analysis_integration() {
711 let temp_dir = tempfile::tempdir().unwrap();
712 let store = std::sync::Arc::new(
713 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
714 .await
715 .unwrap(),
716 );
717 let graph = GraphModule::new(Arc::clone(&store));
718 let search = SearchModule::new(Arc::clone(&store));
719 let cfg = CfgModule::new(Arc::clone(&store));
720 let edit = EditModule::new(store);
721
722 let analysis = AnalysisModule::new(graph, cfg, edit, search);
723
724 let impacted = analysis.deep_impact_analysis("test", 2).await.unwrap();
726 assert!(impacted.is_empty()); }
728
729 #[tokio::test]
730 async fn test_module_dependencies_integration() {
731 let temp_dir = tempfile::tempdir().unwrap();
732 let store = std::sync::Arc::new(
733 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
734 .await
735 .unwrap(),
736 );
737 let graph = GraphModule::new(Arc::clone(&store));
738 let search = SearchModule::new(Arc::clone(&store));
739 let cfg = CfgModule::new(Arc::clone(&store));
740 let edit = EditModule::new(store);
741
742 let analysis = AnalysisModule::new(graph, cfg, edit, search);
743
744 let deps = analysis.module_dependencies().await.unwrap();
746 assert!(deps.is_empty()); let cycles = analysis.find_dependency_cycles().await.unwrap();
750 assert!(cycles.is_empty());
751 }
752
753 #[tokio::test]
754 async fn test_complexity_metrics_integration() {
755 let temp_dir = tempfile::tempdir().unwrap();
756 let store = std::sync::Arc::new(
757 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
758 .await
759 .unwrap(),
760 );
761 let graph = GraphModule::new(Arc::clone(&store));
762 let search = SearchModule::new(Arc::clone(&store));
763 let cfg = CfgModule::new(Arc::clone(&store));
764 let edit = EditModule::new(store);
765
766 let analysis = AnalysisModule::new(graph, cfg, edit, search);
767
768 let metrics = analysis.complexity_metrics("test_function").await.unwrap();
770 assert_eq!(metrics.cyclomatic_complexity, 1);
771 assert_eq!(metrics.decision_points, 0);
772
773 let source = r#"
775 fn example(x: i32) -> i32 {
776 if x > 0 {
777 return x * 2;
778 }
779 x
780 }
781 "#;
782 let source_metrics = analysis.analyze_source_complexity(source);
783 assert!(source_metrics.cyclomatic_complexity >= 1);
784 }
785
786 #[tokio::test]
787 async fn test_cross_references_integration() {
788 let temp_dir = tempfile::tempdir().unwrap();
789 let store = std::sync::Arc::new(
790 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
791 .await
792 .unwrap(),
793 );
794 let graph = GraphModule::new(Arc::clone(&store));
795 let search = SearchModule::new(Arc::clone(&store));
796 let cfg = CfgModule::new(Arc::clone(&store));
797 let edit = EditModule::new(store);
798
799 let analysis = AnalysisModule::new(graph, cfg, edit, search);
800
801 let xrefs = analysis.cross_references("test").await.unwrap();
803 assert!(xrefs.callers.is_empty());
804 assert!(xrefs.callees.is_empty());
805 }
806
807 #[tokio::test]
808 async fn test_performance_benchmarks_integration() {
809 let temp_dir = tempfile::tempdir().unwrap();
810 let store = std::sync::Arc::new(
811 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
812 .await
813 .unwrap(),
814 );
815 let graph = GraphModule::new(Arc::clone(&store));
816 let search = SearchModule::new(Arc::clone(&store));
817 let cfg = CfgModule::new(Arc::clone(&store));
818 let edit = EditModule::new(store);
819
820 let analysis = AnalysisModule::new(graph, cfg, edit, search);
821
822 let benchmarks = analysis.benchmarks().await.unwrap();
824
825 assert!(benchmarks.impact_analysis_ms >= 0.0);
827 assert!(benchmarks.dead_code_ms >= 0.0);
828 assert!(benchmarks.reference_chain_ms >= 0.0);
829 assert!(benchmarks.call_chain_ms >= 0.0);
830 assert!(benchmarks.total_ms >= 0.0);
831
832 let sum = benchmarks.impact_analysis_ms
834 + benchmarks.dead_code_ms
835 + benchmarks.reference_chain_ms
836 + benchmarks.call_chain_ms;
837 assert!(benchmarks.total_ms >= sum * 0.9); }
839}