1#[allow(dead_code)]
7use super::core::*;
8use super::sdk::*;
9use scirs2_core::ndarray::Array1;
10use scirs2_core::numeric::Float;
11use std::collections::HashMap;
12use std::fmt::Debug;
13use std::time::{Duration, Instant};
14
15#[derive(Debug)]
17pub struct PluginValidationFramework<A: Float> {
18 config: ValidationConfig,
20 test_suites: Vec<Box<dyn ValidationTestSuite<A>>>,
22 compliance_checkers: Vec<Box<dyn ComplianceChecker>>,
24 benchmarker: PerformanceBenchmarker<A>,
26 results: ValidationResults<A>,
28}
29
30#[derive(Debug, Clone)]
32pub struct ValidationConfig {
33 pub strict_mode: bool,
35 pub numerical_tolerance: f64,
37 pub performance_tolerance: f64,
39 pub max_test_duration: Duration,
41 pub check_memory_leaks: bool,
43 pub check_thread_safety: bool,
45 pub check_convergence: bool,
47 pub random_seed: u64,
49 pub test_data_sizes: Vec<usize>,
51}
52
53pub trait ValidationTestSuite<A: Float>: Debug {
55 fn run_tests(&self, plugin: &mut dyn OptimizerPlugin<A>) -> SuiteResult;
57
58 fn name(&self) -> &str;
60
61 fn description(&self) -> &str;
63
64 fn test_count(&self) -> usize;
66}
67
68#[derive(Debug, Clone)]
70pub struct SuiteResult {
71 pub suite_name: String,
73 pub test_results: Vec<TestResult>,
75 pub suite_passed: bool,
77 pub execution_time: Duration,
79 pub summary: TestSummary,
81 pub verified: bool,
86}
87
88#[derive(Debug, Clone)]
90pub struct TestSummary {
91 pub total_tests: usize,
93 pub passed_tests: usize,
95 pub failed_tests: usize,
97 pub skipped_tests: usize,
99 pub success_rate: f64,
101}
102
103pub trait ComplianceChecker: Debug {
105 fn check_compliance(&self, plugininfo: &PluginInfo) -> ComplianceResult;
107
108 fn name(&self) -> &str;
110
111 fn requirements(&self) -> Vec<ComplianceRequirement>;
113}
114
115#[derive(Debug, Clone)]
117pub struct ComplianceResult {
118 pub compliant: bool,
120 pub violations: Vec<ComplianceViolation>,
122 pub warnings: Vec<String>,
124 pub compliance_score: f64,
126 pub verified: bool,
133}
134
135#[derive(Debug, Clone)]
137pub struct ComplianceViolation {
138 pub violation_type: ViolationType,
140 pub description: String,
142 pub severity: ViolationSeverity,
144 pub suggested_fix: Option<String>,
146}
147
148#[derive(Debug, Clone)]
150pub enum ViolationType {
151 MissingMetadata,
153 InvalidConfiguration,
155 SecurityViolation,
157 PerformanceViolation,
159 ApiViolation,
161 DocumentationViolation,
163}
164
165#[derive(Debug, Clone)]
167pub enum ViolationSeverity {
168 Low,
169 Medium,
170 High,
171 Critical,
172}
173
174#[derive(Debug, Clone)]
176pub struct ComplianceRequirement {
177 pub id: String,
179 pub description: String,
181 pub mandatory: bool,
183 pub category: ComplianceCategory,
185}
186
187#[derive(Debug, Clone)]
189pub enum ComplianceCategory {
190 Security,
191 Performance,
192 API,
193 Documentation,
194 Metadata,
195 Testing,
196}
197
198#[derive(Debug)]
200pub struct PerformanceBenchmarker<A: Float> {
201 config: BenchmarkConfig,
203 benchmarks: Vec<Box<dyn PerformanceBenchmark<A>>>,
205 baselines: HashMap<String, BenchmarkBaseline>,
207}
208
209pub trait PerformanceBenchmark<A: Float>: Debug {
211 fn run(&self, plugin: &mut dyn OptimizerPlugin<A>) -> BenchmarkResult<A>;
213
214 fn name(&self) -> &str;
216
217 fn benchmark_type(&self) -> BenchmarkType;
219
220 fn expected_baseline(&self) -> Option<BenchmarkBaseline>;
222}
223
224#[derive(Debug, Clone)]
226pub enum BenchmarkType {
227 Throughput,
229 Latency,
231 Memory,
233 Convergence,
235 Scalability,
237}
238
239#[derive(Debug, Clone)]
241pub struct BenchmarkBaseline {
242 pub expected_value: f64,
244 pub tolerance: f64,
246 pub units: String,
248}
249
250#[derive(Debug, Clone)]
252pub struct ValidationResults<A: Float> {
253 pub validation_passed: bool,
255 pub suite_results: Vec<SuiteResult>,
257 pub compliance_results: Vec<ComplianceResult>,
259 pub benchmark_results: Vec<BenchmarkResult<A>>,
261 pub overall_score: Option<f64>,
265 pub timestamp: std::time::SystemTime,
267 pub total_time: Duration,
269}
270
271#[derive(Debug)]
275pub struct FunctionalityTestSuite<A: Float> {
276 config: ValidationConfig,
277 _phantom: std::marker::PhantomData<A>,
278}
279
280#[derive(Debug)]
282pub struct NumericalAccuracyTestSuite<A: Float> {
283 config: ValidationConfig,
284 _phantom: std::marker::PhantomData<A>,
285}
286
287#[derive(Debug)]
289pub struct ThreadSafetyTestSuite<A: Float + std::fmt::Debug> {
290 config: ValidationConfig,
291 _phantom: std::marker::PhantomData<A>,
292}
293
294impl<A: Float + std::fmt::Debug + Send + Sync> ThreadSafetyTestSuite<A> {
295 pub fn new(config: ValidationConfig) -> Self {
297 Self {
298 config,
299 _phantom: std::marker::PhantomData,
300 }
301 }
302}
303
304impl<A: Float + std::fmt::Debug + Send + Sync + 'static> ThreadSafetyTestSuite<A> {
305 fn test_concurrent_steps(&self, plugin: &mut dyn OptimizerPlugin<A>) -> TestResult {
311 use std::sync::atomic::{AtomicBool, Ordering};
312 use std::sync::{Arc, Mutex};
313 let start_time = Instant::now();
314
315 const NUM_THREADS: usize = 4;
316 const STEPS_PER_THREAD: usize = 25;
317 let dim = self
320 .config
321 .test_data_sizes
322 .iter()
323 .copied()
324 .find(|size| *size > 0)
325 .unwrap_or(8);
326 let dim = dim.min(4096);
327
328 let shared: Arc<Mutex<Box<dyn OptimizerPlugin<A>>>> =
329 Arc::new(Mutex::new(plugin.clone_plugin()));
330 {
331 let mut guard = shared.lock().unwrap_or_else(|e| e.into_inner());
332 if let Err(e) = guard.initialize(&[dim]) {
333 return TestResult {
334 passed: false,
335 message: format!("initialize failed before concurrency test: {e}"),
336 execution_time: start_time.elapsed(),
337 data: HashMap::new(),
338 };
339 }
340 }
341
342 let params: Array1<A> =
343 Array1::from_iter((0..dim).map(|i| A::from(1.0 + i as f64).unwrap_or_else(A::one)));
344 let gradients: Array1<A> =
345 Array1::from_iter((0..dim).map(|_| A::from(0.01).unwrap_or_else(A::zero)));
346 let saw_error = Arc::new(AtomicBool::new(false));
347
348 let mut handles = Vec::with_capacity(NUM_THREADS);
349 for _ in 0..NUM_THREADS {
350 let shared = Arc::clone(&shared);
351 let saw_error = Arc::clone(&saw_error);
352 let params = params.clone();
353 let gradients = gradients.clone();
354 handles.push(std::thread::spawn(move || {
355 for _ in 0..STEPS_PER_THREAD {
356 let mut guard = shared.lock().unwrap_or_else(|e| e.into_inner());
357 match guard.step(¶ms, &gradients) {
358 Ok(result) => {
359 if result.iter().any(|v| !v.is_finite()) {
360 saw_error.store(true, Ordering::SeqCst);
361 }
362 }
363 Err(_) => saw_error.store(true, Ordering::SeqCst),
364 }
365 }
366 }));
367 }
368
369 let mut any_panicked = false;
370 for handle in handles {
371 if handle.join().is_err() {
372 any_panicked = true;
373 }
374 }
375
376 let passed = !any_panicked && !saw_error.load(Ordering::SeqCst);
377 let message = if any_panicked {
378 "A worker thread panicked while calling step() concurrently through Arc<Mutex<_>>"
379 .to_string()
380 } else if passed {
381 format!(
382 "{NUM_THREADS} threads completed {STEPS_PER_THREAD} concurrent step() calls \
383 each through a shared Arc<Mutex<_>> instance with no panics and finite output"
384 )
385 } else {
386 "Concurrent step() calls produced an error or a non-finite result".to_string()
387 };
388
389 TestResult {
390 passed,
391 message,
392 execution_time: start_time.elapsed(),
393 data: HashMap::new(),
394 }
395 }
396}
397
398impl<A: Float + std::fmt::Debug + Send + Sync + 'static> ValidationTestSuite<A>
399 for ThreadSafetyTestSuite<A>
400{
401 fn run_tests(&self, plugin: &mut dyn OptimizerPlugin<A>) -> SuiteResult {
402 let start_time = Instant::now();
403 let result = self.test_concurrent_steps(plugin);
404 let passed = result.passed;
405
406 SuiteResult {
407 suite_name: "Thread Safety".to_string(),
408 test_results: vec![result],
409 suite_passed: passed,
410 execution_time: start_time.elapsed(),
411 summary: TestSummary {
412 total_tests: 1,
413 passed_tests: passed as usize,
414 failed_tests: (!passed) as usize,
415 skipped_tests: 0,
416 success_rate: if passed { 1.0 } else { 0.0 },
417 },
418 verified: true,
419 }
420 }
421
422 fn name(&self) -> &str {
423 "Thread Safety Tests"
424 }
425
426 fn description(&self) -> &str {
427 "Tests for thread safety and concurrent access"
428 }
429
430 fn test_count(&self) -> usize {
431 1
432 }
433}
434
435#[derive(Debug)]
437pub struct MemoryTestSuite<A: Float + std::fmt::Debug> {
438 config: ValidationConfig,
439 _phantom: std::marker::PhantomData<A>,
440}
441
442impl<A: Float + std::fmt::Debug + Send + Sync> MemoryTestSuite<A> {
443 pub fn new(config: ValidationConfig) -> Self {
445 Self {
446 config,
447 _phantom: std::marker::PhantomData,
448 }
449 }
450}
451
452impl<A: Float + std::fmt::Debug + Send + Sync> MemoryTestSuite<A> {
453 fn test_memory_growth(&self, plugin: &mut dyn OptimizerPlugin<A>) -> TestResult {
461 let start_time = Instant::now();
462 const SAMPLES: usize = 50;
463 let dim = self
465 .config
466 .test_data_sizes
467 .iter()
468 .copied()
469 .find(|size| *size > 0)
470 .unwrap_or(16)
471 .min(4096);
472
473 if !self.config.check_memory_leaks {
474 return TestResult {
475 passed: true,
476 message: "memory leak detection disabled by ValidationConfig::check_memory_leaks"
477 .to_string(),
478 execution_time: start_time.elapsed(),
479 data: HashMap::new(),
480 };
481 }
482
483 if let Err(e) = plugin.initialize(&[dim]) {
484 return TestResult {
485 passed: false,
486 message: format!("initialize failed before memory growth probe: {e}"),
487 execution_time: start_time.elapsed(),
488 data: HashMap::new(),
489 };
490 }
491
492 let mut params: Array1<A> = Array1::from_elem(dim, A::one());
493 let gradients: Array1<A> = Array1::from_elem(dim, A::from(0.01).unwrap_or_else(A::zero));
494 let baseline = plugin.memory_usage().current_usage;
495 let mut samples = Vec::with_capacity(SAMPLES);
496
497 for _ in 0..SAMPLES {
498 match plugin.step(¶ms, &gradients) {
499 Ok(next) => params = next,
500 Err(e) => {
501 return TestResult {
502 passed: false,
503 message: format!("step failed during memory growth probe: {e}"),
504 execution_time: start_time.elapsed(),
505 data: HashMap::new(),
506 };
507 }
508 }
509 samples.push(plugin.memory_usage().current_usage);
510 }
511
512 let half = SAMPLES / 2;
513 let first_half_peak = samples[..half].iter().copied().max().unwrap_or(0);
514 let second_half_peak = samples[half..].iter().copied().max().unwrap_or(0);
515 let growth_factor = if first_half_peak == 0 {
516 if second_half_peak == 0 {
517 1.0
518 } else {
519 f64::INFINITY
520 }
521 } else {
522 second_half_peak as f64 / first_half_peak as f64
523 };
524 let passed = growth_factor <= 1.5;
527
528 TestResult {
529 passed,
530 message: format!(
531 "self-reported current_usage over {SAMPLES} steps: baseline={baseline}B \
532 first_half_peak={first_half_peak}B second_half_peak={second_half_peak}B \
533 growth_factor={growth_factor:.2}"
534 ),
535 execution_time: start_time.elapsed(),
536 data: HashMap::new(),
537 }
538 }
539}
540
541impl<A: Float + std::fmt::Debug + Send + Sync> ValidationTestSuite<A> for MemoryTestSuite<A> {
542 fn run_tests(&self, plugin: &mut dyn OptimizerPlugin<A>) -> SuiteResult {
543 let start_time = Instant::now();
544 let result = self.test_memory_growth(plugin);
545 let passed = result.passed;
546
547 SuiteResult {
548 suite_name: "Memory Management".to_string(),
549 test_results: vec![result],
550 suite_passed: passed,
551 execution_time: start_time.elapsed(),
552 summary: TestSummary {
553 total_tests: 1,
554 passed_tests: passed as usize,
555 failed_tests: (!passed) as usize,
556 skipped_tests: 0,
557 success_rate: if passed { 1.0 } else { 0.0 },
558 },
559 verified: true,
560 }
561 }
562
563 fn name(&self) -> &str {
564 "Memory Management Tests"
565 }
566
567 fn description(&self) -> &str {
568 "Tests for memory allocation and management"
569 }
570
571 fn test_count(&self) -> usize {
572 1
573 }
574}
575
576pub mod convergence;
577pub use convergence::{ConvergenceTestSuite, TestProblem};
578
579#[derive(Debug)]
583pub struct ApiComplianceChecker;
584
585#[derive(Debug)]
587pub struct SecurityComplianceChecker;
588
589#[derive(Debug)]
591pub struct PerformanceComplianceChecker;
592
593#[derive(Debug)]
595pub struct DocumentationComplianceChecker;
596
597#[derive(Debug)]
601pub struct ThroughputBenchmark<A: Float> {
602 problemsize: usize,
603 iterations: usize,
604 _phantom: std::marker::PhantomData<A>,
605}
606
607impl<A: Float + Send + Sync> ThroughputBenchmark<A> {
608 pub fn new(problemsize: usize, iterations: usize) -> Self {
610 Self {
611 problemsize,
612 iterations,
613 _phantom: std::marker::PhantomData,
614 }
615 }
616}
617
618impl<A: Float + Debug + Send + Sync> PerformanceBenchmark<A> for ThroughputBenchmark<A> {
619 fn run(&self, plugin: &mut dyn OptimizerPlugin<A>) -> BenchmarkResult<A> {
620 let start_time = Instant::now();
621 let dim = self.problemsize.max(1);
622
623 if let Err(e) = plugin.initialize(&[dim]) {
624 let mut metrics = HashMap::new();
625 metrics.insert("error".to_string(), 0.0);
626 return BenchmarkResult {
627 name: format!("Throughput (initialize failed: {e})"),
628 score: 0.0,
629 metrics,
630 execution_time: start_time.elapsed(),
631 memory_usage: 0,
632 data: HashMap::new(),
633 verified: false,
634 };
635 }
636
637 let params: Array1<A> = Array1::from_iter(
638 (0..dim).map(|i| A::from(1.0 + (i % 7) as f64 * 0.1).unwrap_or_else(A::one)),
639 );
640 let gradients: Array1<A> = Array1::from_iter(
641 (0..dim).map(|i| A::from(0.01 + (i % 5) as f64 * 0.001).unwrap_or_else(A::zero)),
642 );
643
644 let run_start = Instant::now();
645 let mut current = params;
646 let mut completed = 0usize;
647 for _ in 0..self.iterations {
648 match plugin.step(¤t, &gradients) {
649 Ok(next) => {
650 current = next;
651 completed += 1;
652 }
653 Err(_) => break,
654 }
655 }
656 let elapsed_secs = run_start.elapsed().as_secs_f64();
657 let ops_per_sec = if elapsed_secs > 0.0 {
658 completed as f64 / elapsed_secs
659 } else {
660 completed as f64
661 };
662
663 let score = self
668 .expected_baseline()
669 .map(|baseline| {
670 (ops_per_sec / baseline.expected_value.max(f64::EPSILON)).clamp(0.0, 1.0)
671 })
672 .unwrap_or(0.0);
673
674 let mut metrics = HashMap::new();
675 metrics.insert("ops_per_sec".to_string(), ops_per_sec);
676 metrics.insert("completed_iterations".to_string(), completed as f64);
677
678 BenchmarkResult {
679 name: "Throughput".to_string(),
680 score,
681 metrics,
682 execution_time: start_time.elapsed(),
683 memory_usage: plugin.memory_usage().current_usage,
684 data: HashMap::new(),
685 verified: true,
686 }
687 }
688
689 fn name(&self) -> &str {
690 "Throughput Benchmark"
691 }
692
693 fn benchmark_type(&self) -> BenchmarkType {
694 BenchmarkType::Throughput
695 }
696
697 fn expected_baseline(&self) -> Option<BenchmarkBaseline> {
698 Some(BenchmarkBaseline {
699 expected_value: 50.0,
700 tolerance: 10.0,
701 units: "ops/sec".to_string(),
702 })
703 }
704}
705
706#[derive(Debug)]
708pub struct LatencyBenchmark<A: Float> {
709 problemsize: usize,
710 _phantom: std::marker::PhantomData<A>,
711}
712
713impl<A: Float + Send + Sync> LatencyBenchmark<A> {
714 pub fn new(problemsize: usize) -> Self {
716 Self {
717 problemsize,
718 _phantom: std::marker::PhantomData,
719 }
720 }
721}
722
723impl<A: Float + Debug + Send + Sync> PerformanceBenchmark<A> for LatencyBenchmark<A> {
724 fn run(&self, plugin: &mut dyn OptimizerPlugin<A>) -> BenchmarkResult<A> {
725 let start_time = Instant::now();
726 let dim = self.problemsize.max(1);
727
728 if let Err(e) = plugin.initialize(&[dim]) {
729 return BenchmarkResult {
730 name: format!("Latency (initialize failed: {e})"),
731 score: 0.0,
732 metrics: HashMap::new(),
733 execution_time: start_time.elapsed(),
734 memory_usage: 0,
735 data: HashMap::new(),
736 verified: false,
737 };
738 }
739
740 let params: Array1<A> = Array1::from_iter(
741 (0..dim).map(|i| A::from(1.0 + (i % 7) as f64 * 0.1).unwrap_or_else(A::one)),
742 );
743 let gradients: Array1<A> =
744 Array1::from_iter((0..dim).map(|_| A::from(0.01).unwrap_or_else(A::zero)));
745
746 const SAMPLES: usize = 50;
747 let run_start = Instant::now();
748 let mut current = params;
749 let mut completed = 0usize;
750 for _ in 0..SAMPLES {
751 match plugin.step(¤t, &gradients) {
752 Ok(next) => {
753 current = next;
754 completed += 1;
755 }
756 Err(_) => break,
757 }
758 }
759 let elapsed = run_start.elapsed();
760 let avg_latency_ms = if completed > 0 {
761 elapsed.as_secs_f64() * 1000.0 / completed as f64
762 } else {
763 f64::INFINITY
764 };
765
766 let score = self
770 .expected_baseline()
771 .map(|baseline| {
772 if avg_latency_ms.is_finite() && avg_latency_ms > 0.0 {
773 (baseline.expected_value / avg_latency_ms).clamp(0.0, 1.0)
774 } else {
775 0.0
776 }
777 })
778 .unwrap_or(0.0);
779
780 let mut metrics = HashMap::new();
781 metrics.insert("avg_latency_ms".to_string(), avg_latency_ms);
782
783 BenchmarkResult {
784 name: "Latency".to_string(),
785 score,
786 metrics,
787 execution_time: start_time.elapsed(),
788 memory_usage: plugin.memory_usage().current_usage,
789 data: HashMap::new(),
790 verified: true,
791 }
792 }
793
794 fn name(&self) -> &str {
795 "Latency Benchmark"
796 }
797
798 fn benchmark_type(&self) -> BenchmarkType {
799 BenchmarkType::Latency
800 }
801
802 fn expected_baseline(&self) -> Option<BenchmarkBaseline> {
803 Some(BenchmarkBaseline {
804 expected_value: 20.0,
805 tolerance: 5.0,
806 units: "ms".to_string(),
807 })
808 }
809}
810
811#[derive(Debug)]
813pub struct MemoryBenchmark<A: Float> {
814 problemsize: usize,
815 _phantom: std::marker::PhantomData<A>,
816}
817
818impl<A: Float + Send + Sync> MemoryBenchmark<A> {
819 pub fn new(problemsize: usize) -> Self {
821 Self {
822 problemsize,
823 _phantom: std::marker::PhantomData,
824 }
825 }
826}
827
828impl<A: Float + Debug + Send + Sync> PerformanceBenchmark<A> for MemoryBenchmark<A> {
829 fn run(&self, plugin: &mut dyn OptimizerPlugin<A>) -> BenchmarkResult<A> {
830 let start_time = Instant::now();
831 let dim = self.problemsize.max(1);
832
833 if let Err(e) = plugin.initialize(&[dim]) {
834 return BenchmarkResult {
835 name: format!("Memory (initialize failed: {e})"),
836 score: 0.0,
837 metrics: HashMap::new(),
838 execution_time: start_time.elapsed(),
839 memory_usage: 0,
840 data: HashMap::new(),
841 verified: false,
842 };
843 }
844
845 let params: Array1<A> = Array1::from_iter(
846 (0..dim).map(|i| A::from(1.0 + (i % 7) as f64 * 0.1).unwrap_or_else(A::one)),
847 );
848 let gradients: Array1<A> =
849 Array1::from_iter((0..dim).map(|_| A::from(0.01).unwrap_or_else(A::zero)));
850
851 let mut current = params;
852 for _ in 0..20 {
853 match plugin.step(¤t, &gradients) {
854 Ok(next) => current = next,
855 Err(_) => break,
856 }
857 }
858
859 let usage = plugin.memory_usage();
860 let usage_mb = usage.current_usage as f64 / (1024.0 * 1024.0);
861
862 let score = self
868 .expected_baseline()
869 .map(|baseline| {
870 if usage_mb > 0.0 {
871 (baseline.expected_value / usage_mb).clamp(0.0, 1.0)
872 } else {
873 1.0
874 }
875 })
876 .unwrap_or(0.0);
877
878 let mut metrics = HashMap::new();
879 metrics.insert("memory_usage_mb".to_string(), usage_mb);
880
881 BenchmarkResult {
882 name: "Memory".to_string(),
883 score,
884 metrics,
885 execution_time: start_time.elapsed(),
886 memory_usage: usage.current_usage,
887 data: HashMap::new(),
888 verified: true,
889 }
890 }
891
892 fn name(&self) -> &str {
893 "Memory Benchmark"
894 }
895
896 fn benchmark_type(&self) -> BenchmarkType {
897 BenchmarkType::Memory
898 }
899
900 fn expected_baseline(&self) -> Option<BenchmarkBaseline> {
901 Some(BenchmarkBaseline {
902 expected_value: 100.0,
903 tolerance: 20.0,
904 units: "MB".to_string(),
905 })
906 }
907}
908
909impl<A: Float + Debug + Send + Sync + 'static> PluginValidationFramework<A> {
910 pub fn new(config: ValidationConfig) -> Self {
912 let mut framework = Self {
913 config: config.clone(),
914 test_suites: Vec::new(),
915 compliance_checkers: Vec::new(),
916 benchmarker: PerformanceBenchmarker::new(BenchmarkConfig::default()),
917 results: ValidationResults::new(),
918 };
919
920 framework.add_default_test_suites();
922 framework.add_default_compliance_checkers();
923 framework.add_default_benchmarks();
924
925 framework
926 }
927
928 pub fn validate_plugin(&mut self, plugin: &mut dyn OptimizerPlugin<A>) -> ValidationResults<A> {
930 let start_time = Instant::now();
931 let mut suite_results = Vec::new();
932 let mut compliance_results = Vec::new();
933 let mut benchmark_results = Vec::new();
934
935 for testsuite in &self.test_suites {
937 let result = testsuite.run_tests(plugin);
938 suite_results.push(result);
939 }
940
941 let plugininfo = plugin.plugin_info();
943 for checker in &self.compliance_checkers {
944 let result = checker.check_compliance(&plugininfo);
945 compliance_results.push(result);
946 }
947
948 let bench_results = self.benchmarker.run_all_benchmarks(plugin);
950 benchmark_results.extend(bench_results);
951
952 let overall_score =
956 self.calculate_overall_score(&suite_results, &compliance_results, &benchmark_results);
957
958 let validation_passed = match overall_score {
963 Some(score) => {
964 score >= 0.8 && suite_results
966 .iter()
967 .filter(|r| r.verified)
968 .all(|r| r.suite_passed)
969 && compliance_results
970 .iter()
971 .filter(|r| r.verified)
972 .all(|r| r.compliant)
973 }
974 None => false,
975 };
976
977 let results = ValidationResults {
978 validation_passed,
979 suite_results,
980 compliance_results,
981 benchmark_results,
982 overall_score,
983 timestamp: std::time::SystemTime::now(),
984 total_time: start_time.elapsed(),
985 };
986 self.results = results.clone();
991 results
992 }
993
994 pub fn last_results(&self) -> &ValidationResults<A> {
998 &self.results
999 }
1000
1001 pub fn add_test_suite(&mut self, testsuite: Box<dyn ValidationTestSuite<A>>) {
1003 self.test_suites.push(testsuite);
1004 }
1005
1006 pub fn add_compliance_checker(&mut self, checker: Box<dyn ComplianceChecker>) {
1008 self.compliance_checkers.push(checker);
1009 }
1010
1011 pub fn add_benchmark(&mut self, benchmark: Box<dyn PerformanceBenchmark<A>>) {
1013 self.benchmarker.add_benchmark(benchmark);
1014 }
1015
1016 fn add_default_test_suites(&mut self) {
1017 self.test_suites
1018 .push(Box::new(FunctionalityTestSuite::new(self.config.clone())));
1019 self.test_suites
1020 .push(Box::new(NumericalAccuracyTestSuite::new(
1021 self.config.clone(),
1022 )));
1023
1024 if self.config.check_thread_safety {
1025 self.test_suites
1026 .push(Box::new(ThreadSafetyTestSuite::new(self.config.clone())));
1027 }
1028
1029 if self.config.check_memory_leaks {
1030 self.test_suites
1031 .push(Box::new(MemoryTestSuite::new(self.config.clone())));
1032 }
1033
1034 if self.config.check_convergence {
1035 self.test_suites
1036 .push(Box::new(ConvergenceTestSuite::new(self.config.clone())));
1037 }
1038 }
1039
1040 fn add_default_compliance_checkers(&mut self) {
1041 self.compliance_checkers
1042 .push(Box::new(ApiComplianceChecker));
1043 self.compliance_checkers
1044 .push(Box::new(SecurityComplianceChecker));
1045 self.compliance_checkers
1046 .push(Box::new(PerformanceComplianceChecker));
1047 self.compliance_checkers
1048 .push(Box::new(DocumentationComplianceChecker));
1049 }
1050
1051 fn add_default_benchmarks(&mut self) {
1052 for &size in &self.config.test_data_sizes {
1053 self.benchmarker
1054 .add_benchmark(Box::new(ThroughputBenchmark::new(size, 100)));
1055 self.benchmarker
1056 .add_benchmark(Box::new(LatencyBenchmark::new(size)));
1057 self.benchmarker
1058 .add_benchmark(Box::new(MemoryBenchmark::new(size)));
1059 }
1060 }
1061
1062 fn calculate_overall_score(
1071 &self,
1072 suite_results: &[SuiteResult],
1073 compliance_results: &[ComplianceResult],
1074 benchmark_results: &[BenchmarkResult<A>],
1075 ) -> Option<f64> {
1076 let mut total_score = 0.0;
1077 let mut weight_sum = 0.0;
1078
1079 let verified_suites: Vec<&SuiteResult> =
1081 suite_results.iter().filter(|r| r.verified).collect();
1082 if !verified_suites.is_empty() {
1083 let suite_score = verified_suites
1084 .iter()
1085 .map(|r| r.summary.success_rate)
1086 .sum::<f64>()
1087 / verified_suites.len() as f64;
1088 total_score += suite_score * 0.5;
1089 weight_sum += 0.5;
1090 }
1091
1092 let verified_compliance: Vec<&ComplianceResult> =
1094 compliance_results.iter().filter(|r| r.verified).collect();
1095 if !verified_compliance.is_empty() {
1096 let compliance_score = verified_compliance
1097 .iter()
1098 .map(|r| r.compliance_score)
1099 .sum::<f64>()
1100 / verified_compliance.len() as f64;
1101 total_score += compliance_score * 0.3;
1102 weight_sum += 0.3;
1103 }
1104
1105 let verified_benchmarks: Vec<&BenchmarkResult<A>> =
1111 benchmark_results.iter().filter(|r| r.verified).collect();
1112 if !verified_benchmarks.is_empty() {
1113 let perf_score = verified_benchmarks.iter().map(|r| r.score).sum::<f64>()
1114 / verified_benchmarks.len() as f64;
1115 total_score += perf_score * 0.2;
1116 weight_sum += 0.2;
1117 }
1118
1119 if weight_sum > 0.0 {
1120 Some((total_score / weight_sum).clamp(0.0, 1.0))
1121 } else {
1122 None
1123 }
1124 }
1125}
1126
1127impl<A: Float + Debug + Send + Sync + 'static> FunctionalityTestSuite<A> {
1130 fn new(config: ValidationConfig) -> Self {
1131 Self {
1132 config,
1133 _phantom: std::marker::PhantomData,
1134 }
1135 }
1136}
1137
1138impl<A: Float + Debug + Send + Sync + 'static> ValidationTestSuite<A>
1139 for FunctionalityTestSuite<A>
1140{
1141 fn run_tests(&self, plugin: &mut dyn OptimizerPlugin<A>) -> SuiteResult {
1142 let start_time = Instant::now();
1143 let mut test_results = Vec::new();
1144
1145 let result1 = self.test_basic_step(plugin);
1147 test_results.push(result1);
1148
1149 let result2 = self.test_initialization(plugin);
1151 test_results.push(result2);
1152
1153 let result3 = self.test_state_management(plugin);
1155 test_results.push(result3);
1156
1157 let result4 = self.test_configuration(plugin);
1159 test_results.push(result4);
1160
1161 let passed_tests = test_results.iter().filter(|r| r.passed).count();
1162 let total_tests = test_results.len();
1163
1164 SuiteResult {
1165 suite_name: self.name().to_string(),
1166 test_results,
1167 suite_passed: passed_tests == total_tests,
1168 execution_time: start_time.elapsed(),
1169 summary: TestSummary {
1170 total_tests,
1171 passed_tests,
1172 failed_tests: total_tests - passed_tests,
1173 skipped_tests: 0,
1174 success_rate: passed_tests as f64 / total_tests as f64,
1175 },
1176 verified: true,
1177 }
1178 }
1179
1180 fn name(&self) -> &str {
1181 "Functionality Tests"
1182 }
1183
1184 fn description(&self) -> &str {
1185 "Tests basic optimizer functionality and API compliance"
1186 }
1187
1188 fn test_count(&self) -> usize {
1189 4
1190 }
1191}
1192
1193impl<A: Float + Debug + Send + Sync + 'static> FunctionalityTestSuite<A> {
1194 fn test_basic_step(&self, plugin: &mut dyn OptimizerPlugin<A>) -> TestResult {
1195 let start_time = Instant::now();
1196
1197 let literal = |value: f64| A::from(value);
1202 let (Some(p0), Some(p1), Some(g0), Some(g1)) =
1203 (literal(1.0), literal(2.0), literal(0.1), literal(0.2))
1204 else {
1205 return TestResult {
1206 passed: false,
1207 message: "the element type cannot represent the test literals".to_string(),
1208 execution_time: start_time.elapsed(),
1209 data: HashMap::new(),
1210 };
1211 };
1212 let params = Array1::from_vec(vec![p0, p1]);
1213 let gradients = Array1::from_vec(vec![g0, g1]);
1214
1215 match plugin.step(¶ms, &gradients) {
1216 Ok(result) => {
1217 if result.len() == params.len() {
1218 let moved = result.iter().zip(params.iter()).any(|(&after, &before)| {
1223 (after - before)
1224 .abs()
1225 .to_f64()
1226 .is_some_and(|delta| delta > self.config.numerical_tolerance)
1227 });
1228 TestResult {
1229 passed: moved,
1230 message: if moved {
1231 "Basic step test passed".to_string()
1232 } else {
1233 format!(
1234 "step left every parameter within numerical_tolerance {:.3e}, so \
1235 no optimization happened",
1236 self.config.numerical_tolerance
1237 )
1238 },
1239 execution_time: start_time.elapsed(),
1240 data: HashMap::new(),
1241 }
1242 } else {
1243 TestResult {
1244 passed: false,
1245 message: "Step result has incorrect dimensions".to_string(),
1246 execution_time: start_time.elapsed(),
1247 data: HashMap::new(),
1248 }
1249 }
1250 }
1251 Err(e) => TestResult {
1252 passed: false,
1253 message: format!("Step function failed: {}", e),
1254 execution_time: start_time.elapsed(),
1255 data: HashMap::new(),
1256 },
1257 }
1258 }
1259
1260 fn test_initialization(&self, plugin: &mut dyn OptimizerPlugin<A>) -> TestResult {
1261 let start_time = Instant::now();
1262
1263 match plugin.initialize(&[10, 20]) {
1264 Ok(()) => TestResult {
1265 passed: true,
1266 message: "Initialization test passed".to_string(),
1267 execution_time: start_time.elapsed(),
1268 data: HashMap::new(),
1269 },
1270 Err(e) => TestResult {
1271 passed: false,
1272 message: format!("Initialization failed: {}", e),
1273 execution_time: start_time.elapsed(),
1274 data: HashMap::new(),
1275 },
1276 }
1277 }
1278
1279 fn test_state_management(&self, plugin: &mut dyn OptimizerPlugin<A>) -> TestResult {
1280 let start_time = Instant::now();
1281
1282 match (plugin.get_state(), plugin.reset()) {
1284 (Ok(_), Ok(())) => TestResult {
1285 passed: true,
1286 message: "State management test passed".to_string(),
1287 execution_time: start_time.elapsed(),
1288 data: HashMap::new(),
1289 },
1290 (Err(e), _) => TestResult {
1291 passed: false,
1292 message: format!("Failed to get state: {}", e),
1293 execution_time: start_time.elapsed(),
1294 data: HashMap::new(),
1295 },
1296 (_, Err(e)) => TestResult {
1297 passed: false,
1298 message: format!("Failed to reset: {}", e),
1299 execution_time: start_time.elapsed(),
1300 data: HashMap::new(),
1301 },
1302 }
1303 }
1304
1305 fn test_configuration(&self, plugin: &mut dyn OptimizerPlugin<A>) -> TestResult {
1306 let start_time = Instant::now();
1307
1308 let config = plugin.get_config();
1309 match plugin.set_config(config) {
1310 Ok(()) => TestResult {
1311 passed: true,
1312 message: "Configuration test passed".to_string(),
1313 execution_time: start_time.elapsed(),
1314 data: HashMap::new(),
1315 },
1316 Err(e) => TestResult {
1317 passed: false,
1318 message: format!("Configuration test failed: {}", e),
1319 execution_time: start_time.elapsed(),
1320 data: HashMap::new(),
1321 },
1322 }
1323 }
1324}
1325
1326impl<A: Float + Debug + Send + Sync + 'static> NumericalAccuracyTestSuite<A> {
1329 fn new(config: ValidationConfig) -> Self {
1330 Self {
1331 config,
1332 _phantom: std::marker::PhantomData,
1333 }
1334 }
1335
1336 fn test_config_roundtrip(&self, plugin: &mut dyn OptimizerPlugin<A>) -> TestResult {
1340 let start_time = Instant::now();
1341 let original = plugin.get_config();
1342 if let Err(e) = plugin.set_config(original.clone()) {
1343 return TestResult {
1344 passed: false,
1345 message: format!("set_config failed during roundtrip: {e}"),
1346 execution_time: start_time.elapsed(),
1347 data: HashMap::new(),
1348 };
1349 }
1350 let after = plugin.get_config();
1351 let diff = (after.learning_rate - original.learning_rate).abs();
1352 let passed = diff <= self.config.numerical_tolerance;
1353 TestResult {
1354 passed,
1355 message: format!(
1356 "learning_rate roundtrip |diff|={diff:.3e} tolerance={:.3e}",
1357 self.config.numerical_tolerance
1358 ),
1359 execution_time: start_time.elapsed(),
1360 data: HashMap::new(),
1361 }
1362 }
1363
1364 fn test_step_output_finite(&self, plugin: &mut dyn OptimizerPlugin<A>) -> TestResult {
1366 let start_time = Instant::now();
1367 const DIM: usize = 6;
1368
1369 if let Err(e) = plugin.initialize(&[DIM]) {
1370 return TestResult {
1371 passed: false,
1372 message: format!("initialize failed before numerical accuracy probe: {e}"),
1373 execution_time: start_time.elapsed(),
1374 data: HashMap::new(),
1375 };
1376 }
1377
1378 let params: Array1<A> = Array1::from_iter(
1379 (0..DIM).map(|i| A::from(1.0 + i as f64 * 0.1).unwrap_or_else(A::one)),
1380 );
1381 let gradients: Array1<A> = Array1::from_iter(
1382 (0..DIM).map(|i| A::from(0.05 - i as f64 * 0.005).unwrap_or_else(A::zero)),
1383 );
1384
1385 match plugin.step(¶ms, &gradients) {
1386 Ok(result) => {
1387 let all_finite = result.iter().all(|v| v.is_finite());
1388 TestResult {
1389 passed: all_finite,
1390 message: if all_finite {
1391 "step() output is finite for well-conditioned input".to_string()
1392 } else {
1393 "step() produced a non-finite value for finite, well-conditioned input"
1394 .to_string()
1395 },
1396 execution_time: start_time.elapsed(),
1397 data: HashMap::new(),
1398 }
1399 }
1400 Err(e) => TestResult {
1401 passed: false,
1402 message: format!("step() failed: {e}"),
1403 execution_time: start_time.elapsed(),
1404 data: HashMap::new(),
1405 },
1406 }
1407 }
1408}
1409
1410impl<A: Float + Debug + Send + Sync + 'static> ValidationTestSuite<A>
1411 for NumericalAccuracyTestSuite<A>
1412{
1413 fn run_tests(&self, plugin: &mut dyn OptimizerPlugin<A>) -> SuiteResult {
1414 let start_time = Instant::now();
1415 let test_results = vec![
1416 self.test_config_roundtrip(plugin),
1417 self.test_step_output_finite(plugin),
1418 ];
1419
1420 let passed_tests = test_results.iter().filter(|r| r.passed).count();
1421 let total_tests = test_results.len();
1422
1423 SuiteResult {
1424 suite_name: self.name().to_string(),
1425 test_results,
1426 suite_passed: passed_tests == total_tests,
1427 execution_time: start_time.elapsed(),
1428 summary: TestSummary {
1429 total_tests,
1430 passed_tests,
1431 failed_tests: total_tests - passed_tests,
1432 skipped_tests: 0,
1433 success_rate: passed_tests as f64 / total_tests as f64,
1434 },
1435 verified: true,
1436 }
1437 }
1438
1439 fn name(&self) -> &str {
1440 "Numerical Accuracy Tests"
1441 }
1442
1443 fn description(&self) -> &str {
1444 "Tests numerical precision and accuracy of optimization steps"
1445 }
1446
1447 fn test_count(&self) -> usize {
1448 2
1449 }
1450}
1451
1452impl<A: Float + Send + Sync> PerformanceBenchmarker<A> {
1455 fn new(config: BenchmarkConfig) -> Self {
1456 Self {
1457 config,
1458 benchmarks: Vec::new(),
1459 baselines: HashMap::new(),
1460 }
1461 }
1462
1463 fn add_benchmark(&mut self, benchmark: Box<dyn PerformanceBenchmark<A>>) {
1464 self.benchmarks.push(benchmark);
1465 }
1466
1467 pub fn set_baseline(&mut self, benchmark_name: String, baseline: BenchmarkBaseline) {
1469 self.baselines.insert(benchmark_name, baseline);
1470 }
1471
1472 pub fn baselines(&self) -> &HashMap<String, BenchmarkBaseline> {
1474 &self.baselines
1475 }
1476
1477 pub fn config(&self) -> &BenchmarkConfig {
1479 &self.config
1480 }
1481
1482 fn run_all_benchmarks(
1491 &mut self,
1492 plugin: &mut dyn OptimizerPlugin<A>,
1493 ) -> Vec<BenchmarkResult<A>> {
1494 let runs = self.config.runs.max(1);
1495 let warmup = self.config.warmup_iterations;
1496 let mut results = Vec::with_capacity(self.benchmarks.len());
1497
1498 for bench in &self.benchmarks {
1499 for _ in 0..warmup {
1500 let _ = bench.run(plugin);
1501 }
1502 let mut best: Option<BenchmarkResult<A>> = None;
1503 for _ in 0..runs {
1504 let candidate = bench.run(plugin);
1505 best = match best {
1506 Some(current) if current.score >= candidate.score => Some(current),
1507 _ => Some(candidate),
1508 };
1509 }
1510 let Some(mut result) = best else { continue };
1511
1512 if let Some(baseline) = self.baselines.get(&result.name) {
1513 let measured = result.execution_time.as_secs_f64();
1514 let ceiling = baseline.expected_value * (1.0 + baseline.tolerance / 100.0);
1515 let within = measured <= ceiling;
1516 result
1517 .metrics
1518 .insert("baseline_expected".to_string(), baseline.expected_value);
1519 result
1520 .metrics
1521 .insert("baseline_ceiling".to_string(), ceiling);
1522 result.metrics.insert(
1523 "baseline_within_tolerance".to_string(),
1524 if within { 1.0 } else { 0.0 },
1525 );
1526 if !within {
1527 result.score = 0.0;
1530 }
1531 }
1532 results.push(result);
1533 }
1534
1535 results
1536 }
1537}
1538
1539impl<A: Float + Send + Sync> ValidationResults<A> {
1540 fn new() -> Self {
1541 Self {
1542 validation_passed: false,
1543 suite_results: Vec::new(),
1544 compliance_results: Vec::new(),
1545 benchmark_results: Vec::new(),
1546 overall_score: None,
1547 timestamp: std::time::SystemTime::now(),
1548 total_time: Duration::from_secs(0),
1549 }
1550 }
1551}
1552
1553impl Default for ValidationConfig {
1556 fn default() -> Self {
1557 Self {
1558 strict_mode: false,
1559 numerical_tolerance: 1e-10,
1560 performance_tolerance: 20.0,
1561 max_test_duration: Duration::from_secs(300),
1562 check_memory_leaks: true,
1563 check_thread_safety: false,
1564 check_convergence: true,
1565 random_seed: 42,
1566 test_data_sizes: vec![10, 100, 1000],
1567 }
1568 }
1569}
1570
1571impl ComplianceChecker for ApiComplianceChecker {
1574 fn check_compliance(&self, plugininfo: &PluginInfo) -> ComplianceResult {
1575 let mut violations = Vec::new();
1576 let mut score = 1.0;
1577
1578 if plugininfo.name.trim().is_empty() {
1579 violations.push(ComplianceViolation {
1580 violation_type: ViolationType::ApiViolation,
1581 description: "Plugin name is empty".to_string(),
1582 severity: ViolationSeverity::Critical,
1583 suggested_fix: Some("Provide a non-empty plugin name".to_string()),
1584 });
1585 score -= 0.4;
1586 }
1587
1588 if plugininfo.version.trim().is_empty() {
1589 violations.push(ComplianceViolation {
1590 violation_type: ViolationType::ApiViolation,
1591 description: "Plugin version is empty".to_string(),
1592 severity: ViolationSeverity::High,
1593 suggested_fix: Some("Provide a semantic version string".to_string()),
1594 });
1595 score -= 0.3;
1596 }
1597
1598 if plugininfo.supported_types.is_empty() {
1599 violations.push(ComplianceViolation {
1600 violation_type: ViolationType::ApiViolation,
1601 description: "Plugin declares no supported data types".to_string(),
1602 severity: ViolationSeverity::Medium,
1603 suggested_fix: Some(
1604 "Declare at least one entry in `supported_types` (e.g. DataType::F64)"
1605 .to_string(),
1606 ),
1607 });
1608 score -= 0.2;
1609 }
1610
1611 if plugininfo.min_sdk_version.trim().is_empty() {
1612 violations.push(ComplianceViolation {
1613 violation_type: ViolationType::ApiViolation,
1614 description: "Plugin declares no minimum SDK version".to_string(),
1615 severity: ViolationSeverity::Low,
1616 suggested_fix: Some(
1617 "Set `min_sdk_version` to the SDK version targeted".to_string(),
1618 ),
1619 });
1620 score -= 0.1;
1621 }
1622
1623 ComplianceResult {
1624 compliant: violations.is_empty(),
1625 violations,
1626 warnings: Vec::new(),
1627 compliance_score: score.max(0.0),
1628 verified: true,
1629 }
1630 }
1631
1632 fn name(&self) -> &str {
1633 "API Compliance"
1634 }
1635
1636 fn requirements(&self) -> Vec<ComplianceRequirement> {
1637 vec![
1638 ComplianceRequirement {
1639 id: "api-1".to_string(),
1640 description: "Plugin must declare a non-empty name".to_string(),
1641 mandatory: true,
1642 category: ComplianceCategory::API,
1643 },
1644 ComplianceRequirement {
1645 id: "api-2".to_string(),
1646 description: "Plugin must declare a non-empty version".to_string(),
1647 mandatory: true,
1648 category: ComplianceCategory::API,
1649 },
1650 ComplianceRequirement {
1651 id: "api-3".to_string(),
1652 description: "Plugin must declare at least one supported data type".to_string(),
1653 mandatory: true,
1654 category: ComplianceCategory::API,
1655 },
1656 ]
1657 }
1658}
1659
1660impl ComplianceChecker for SecurityComplianceChecker {
1661 fn check_compliance(&self, plugininfo: &PluginInfo) -> ComplianceResult {
1669 let mut violations = Vec::new();
1670 let mut score: f64 = 1.0;
1671
1672 if plugininfo.license.trim().is_empty() {
1673 violations.push(ComplianceViolation {
1674 violation_type: ViolationType::SecurityViolation,
1675 description: "Plugin declares no license; provenance cannot be assessed"
1676 .to_string(),
1677 severity: ViolationSeverity::Medium,
1678 suggested_fix: Some("Declare an SPDX license identifier".to_string()),
1679 });
1680 score -= 0.3;
1681 }
1682
1683 for dep in &plugininfo.dependencies {
1684 let version_req = dep.version.trim();
1685 if version_req.is_empty() || version_req == "*" {
1686 violations.push(ComplianceViolation {
1687 violation_type: ViolationType::SecurityViolation,
1688 description: format!(
1689 "Dependency '{}' has an unbounded version requirement ('{}'); this \
1690 lets any future release -- including a compromised one -- be pulled \
1691 in transparently",
1692 dep.name, dep.version
1693 ),
1694 severity: ViolationSeverity::High,
1695 suggested_fix: Some("Pin dependencies to a bounded version range".to_string()),
1696 });
1697 score -= 0.2;
1698 }
1699 }
1700
1701 let compliant = !violations.iter().any(|v| {
1702 matches!(
1703 v.severity,
1704 ViolationSeverity::Critical | ViolationSeverity::High
1705 )
1706 });
1707
1708 ComplianceResult {
1709 compliant,
1710 violations,
1711 warnings: vec![
1712 "Security compliance here covers declared metadata only; code-level scanning \
1713 and signature verification happen separately in PluginLoader::SecurityManager"
1714 .to_string(),
1715 ],
1716 compliance_score: score.max(0.0),
1717 verified: true,
1718 }
1719 }
1720
1721 fn name(&self) -> &str {
1722 "Security Compliance"
1723 }
1724
1725 fn requirements(&self) -> Vec<ComplianceRequirement> {
1726 vec![
1727 ComplianceRequirement {
1728 id: "sec-1".to_string(),
1729 description: "Plugin should declare a license".to_string(),
1730 mandatory: false,
1731 category: ComplianceCategory::Security,
1732 },
1733 ComplianceRequirement {
1734 id: "sec-2".to_string(),
1735 description: "Dependencies must not use unbounded version requirements".to_string(),
1736 mandatory: true,
1737 category: ComplianceCategory::Security,
1738 },
1739 ]
1740 }
1741}
1742
1743impl ComplianceChecker for PerformanceComplianceChecker {
1744 fn check_compliance(&self, _plugininfo: &PluginInfo) -> ComplianceResult {
1752 ComplianceResult {
1753 compliant: false,
1754 violations: Vec::new(),
1755 warnings: vec![
1756 "Performance compliance is not decidable from PluginInfo alone; see the \
1757 benchmark suite (ThroughputBenchmark/LatencyBenchmark/MemoryBenchmark) instead"
1758 .to_string(),
1759 ],
1760 compliance_score: 0.0,
1761 verified: false,
1762 }
1763 }
1764
1765 fn name(&self) -> &str {
1766 "Performance Compliance"
1767 }
1768
1769 fn requirements(&self) -> Vec<ComplianceRequirement> {
1770 vec![ComplianceRequirement {
1771 id: "perf-1".to_string(),
1772 description: "Performance must meet the declared benchmark baseline (see \
1773 PerformanceBenchmarker)"
1774 .to_string(),
1775 mandatory: false,
1776 category: ComplianceCategory::Performance,
1777 }]
1778 }
1779}
1780
1781impl ComplianceChecker for DocumentationComplianceChecker {
1782 fn check_compliance(&self, plugininfo: &PluginInfo) -> ComplianceResult {
1783 let mut violations = Vec::new();
1784 let mut score = 1.0;
1785
1786 if plugininfo.description.len() < 10 {
1787 violations.push(ComplianceViolation {
1788 violation_type: ViolationType::DocumentationViolation,
1789 description: "Plugin description is too short".to_string(),
1790 severity: ViolationSeverity::Medium,
1791 suggested_fix: Some("Provide a more detailed description".to_string()),
1792 });
1793 score -= 0.2;
1794 }
1795
1796 if plugininfo.author.is_empty() {
1797 violations.push(ComplianceViolation {
1798 violation_type: ViolationType::MissingMetadata,
1799 description: "Author information is missing".to_string(),
1800 severity: ViolationSeverity::Low,
1801 suggested_fix: Some("Add author information".to_string()),
1802 });
1803 score -= 0.1;
1804 }
1805
1806 ComplianceResult {
1807 compliant: violations.is_empty(),
1808 violations,
1809 warnings: Vec::new(),
1810 compliance_score: score.max(0.0),
1811 verified: true,
1812 }
1813 }
1814
1815 fn name(&self) -> &str {
1816 "Documentation Compliance"
1817 }
1818
1819 fn requirements(&self) -> Vec<ComplianceRequirement> {
1820 Vec::new()
1821 }
1822}
1823
1824#[cfg(test)]
1825mod tests {
1826 use super::*;
1827
1828 #[test]
1829 fn test_validation_config_default() {
1830 let config = ValidationConfig::default();
1831 assert!(!config.strict_mode);
1832 assert!(config.check_memory_leaks);
1833 assert!(config.check_convergence);
1834 }
1835
1836 #[test]
1837 fn test_validation_framework_creation() {
1838 let config = ValidationConfig::default();
1839 let framework = PluginValidationFramework::<f64>::new(config);
1840 assert!(!framework.test_suites.is_empty());
1841 assert!(!framework.compliance_checkers.is_empty());
1842 }
1843
1844 #[test]
1845 fn test_documentation_compliance_checker() {
1846 let checker = DocumentationComplianceChecker;
1847
1848 let info = PluginInfo {
1849 description: "Short".to_string(),
1850 author: "".to_string(),
1851 ..Default::default()
1852 };
1853
1854 let result = checker.check_compliance(&info);
1855 assert!(!result.compliant);
1856 assert_eq!(result.violations.len(), 2);
1857 }
1858}