sklears_core/plugin/
testing_utils.rs

1//! Plugin Testing Utilities
2//!
3//! This module provides comprehensive testing infrastructure for plugin development,
4//! validation, and quality assurance. It includes mock implementations, test fixtures,
5//! performance testing tools, and validation frameworks.
6
7use super::core_traits::Plugin;
8use super::security::{DigitalSignature, Permission, PublisherInfo, SecurityPolicy};
9use super::types_config::{
10    PluginCapability, PluginCategory, PluginConfig, PluginMetadata, PluginParameter,
11};
12use super::validation::{PluginManifest, PluginValidator, ValidationReport};
13use crate::error::Result;
14use std::any::{Any, TypeId};
15use std::collections::HashMap;
16use std::time::{Duration, Instant, SystemTime};
17
18/// Mock plugin implementation for testing
19///
20/// MockPlugin provides a configurable test implementation of the Plugin trait
21/// that can be used for unit testing, integration testing, and validation scenarios.
22///
23/// # Features
24///
25/// - Configurable metadata and behavior
26/// - Simulation of various plugin states and conditions
27/// - Built-in test data generation
28/// - Error injection capabilities
29///
30/// # Examples
31///
32/// ```rust,ignore
33/// use sklears_core::plugin::{MockPlugin, PluginCategory, PluginCapability};
34/// use std::any::TypeId;
35///
36/// // Create a basic mock plugin
37/// let mut mock = MockPlugin::new("test_plugin");
38/// mock.metadata.category = PluginCategory::Algorithm;
39/// mock.metadata.capabilities.push(PluginCapability::Parallel);
40///
41/// // Add supported types
42/// mock.add_supported_type(TypeId::of::`<f64>`());
43///
44/// // Configure error behavior
45/// mock.set_initialization_error(Some("Test error"));
46///
47/// // Use in tests
48/// assert_eq!(mock.id(), "test_plugin");
49/// assert!(mock.is_compatible(TypeId::of::`<f64>`()));
50/// ```
51#[derive(Debug, Clone)]
52pub struct MockPlugin {
53    /// Plugin identifier
54    pub id: String,
55    /// Plugin metadata
56    pub metadata: PluginMetadata,
57    /// Configuration for the plugin
58    pub config: Option<PluginConfig>,
59    /// Whether plugin is initialized
60    pub initialized: bool,
61    /// Simulated initialization error
62    pub initialization_error: Option<String>,
63    /// Simulated validation error
64    pub validation_error: Option<String>,
65    /// Simulated cleanup error
66    pub cleanup_error: Option<String>,
67    /// Call counters for testing
68    pub call_counts: HashMap<String, usize>,
69    /// Artificial delays for performance testing
70    pub artificial_delays: HashMap<String, Duration>,
71}
72
73impl MockPlugin {
74    /// Create a new mock plugin with default settings
75    ///
76    /// # Arguments
77    ///
78    /// * `id` - The plugin identifier
79    ///
80    /// # Examples
81    ///
82    /// ```rust,ignore
83    /// use sklears_core::plugin::MockPlugin;
84    ///
85    /// let mock = MockPlugin::new("test_algorithm");
86    /// assert_eq!(mock.id(), "test_algorithm");
87    /// ```
88    pub fn new(id: &str) -> Self {
89        Self {
90            id: id.to_string(),
91            metadata: PluginMetadata {
92                name: format!("Mock Plugin {}", id),
93                version: "1.0.0".to_string(),
94                description: "A mock plugin for testing".to_string(),
95                author: "Test Framework".to_string(),
96                category: PluginCategory::Algorithm,
97                supported_types: vec![TypeId::of::<f64>(), TypeId::of::<f32>()],
98                dependencies: Vec::new(),
99                capabilities: vec![PluginCapability::Parallel],
100                min_sdk_version: "0.1.0".to_string(),
101            },
102            config: None,
103            initialized: false,
104            initialization_error: None,
105            validation_error: None,
106            cleanup_error: None,
107            call_counts: HashMap::new(),
108            artificial_delays: HashMap::new(),
109        }
110    }
111
112    /// Create a mock plugin for a specific algorithm category
113    ///
114    /// # Arguments
115    ///
116    /// * `id` - The plugin identifier
117    /// * `category` - The plugin category
118    ///
119    /// # Examples
120    ///
121    /// ```rust,ignore
122    /// use sklears_core::plugin::{MockPlugin, PluginCategory};
123    ///
124    /// let transformer = MockPlugin::for_category("scaler", PluginCategory::Transformer);
125    /// assert_eq!(transformer.metadata.category, PluginCategory::Transformer);
126    /// ```
127    pub fn for_category(id: &str, category: PluginCategory) -> Self {
128        let mut mock = Self::new(id);
129        mock.metadata.category = category.clone();
130        mock.metadata.name = format!("Mock {} Plugin", Self::category_name(&category));
131        mock
132    }
133
134    /// Add a supported type to the plugin
135    ///
136    /// # Arguments
137    ///
138    /// * `type_id` - The TypeId to add as supported
139    pub fn add_supported_type(&mut self, type_id: TypeId) {
140        if !self.metadata.supported_types.contains(&type_id) {
141            self.metadata.supported_types.push(type_id);
142        }
143    }
144
145    /// Remove a supported type from the plugin
146    ///
147    /// # Arguments
148    ///
149    /// * `type_id` - The TypeId to remove
150    pub fn remove_supported_type(&mut self, type_id: TypeId) {
151        self.metadata.supported_types.retain(|&t| t != type_id);
152    }
153
154    /// Set an initialization error to simulate failure
155    ///
156    /// # Arguments
157    ///
158    /// * `error` - Optional error message (None to clear)
159    pub fn set_initialization_error(&mut self, error: Option<&str>) {
160        self.initialization_error = error.map(|s| s.to_string());
161    }
162
163    /// Set a validation error to simulate failure
164    ///
165    /// # Arguments
166    ///
167    /// * `error` - Optional error message (None to clear)
168    pub fn set_validation_error(&mut self, error: Option<&str>) {
169        self.validation_error = error.map(|s| s.to_string());
170    }
171
172    /// Set a cleanup error to simulate failure
173    ///
174    /// # Arguments
175    ///
176    /// * `error` - Optional error message (None to clear)
177    pub fn set_cleanup_error(&mut self, error: Option<&str>) {
178        self.cleanup_error = error.map(|s| s.to_string());
179    }
180
181    /// Add an artificial delay for a specific method
182    ///
183    /// # Arguments
184    ///
185    /// * `method` - The method name
186    /// * `delay` - The delay duration
187    pub fn add_artificial_delay(&mut self, method: &str, delay: Duration) {
188        self.artificial_delays.insert(method.to_string(), delay);
189    }
190
191    /// Get the call count for a specific method
192    ///
193    /// # Arguments
194    ///
195    /// * `method` - The method name
196    ///
197    /// # Returns
198    ///
199    /// The number of times the method was called
200    pub fn get_call_count(&self, method: &str) -> usize {
201        self.call_counts.get(method).copied().unwrap_or(0)
202    }
203
204    /// Reset all call counts
205    pub fn reset_call_counts(&mut self) {
206        self.call_counts.clear();
207    }
208
209    /// Increment call count and apply artificial delay
210    fn record_call(&mut self, method: &str) {
211        *self.call_counts.entry(method.to_string()).or_insert(0) += 1;
212
213        if let Some(delay) = self.artificial_delays.get(method) {
214            std::thread::sleep(*delay);
215        }
216    }
217
218    /// Get category name as string
219    fn category_name(category: &PluginCategory) -> &str {
220        match category {
221            PluginCategory::Algorithm => "Algorithm",
222            PluginCategory::Transformer => "Transformer",
223            PluginCategory::DataProcessor => "DataProcessor",
224            PluginCategory::Evaluator => "Evaluator",
225            PluginCategory::Visualizer => "Visualizer",
226            PluginCategory::Custom(name) => name,
227        }
228    }
229}
230
231impl Plugin for MockPlugin {
232    fn id(&self) -> &str {
233        &self.id
234    }
235
236    fn metadata(&self) -> PluginMetadata {
237        self.metadata.clone()
238    }
239
240    fn initialize(&mut self, config: &PluginConfig) -> Result<()> {
241        self.record_call("initialize");
242
243        if let Some(ref error) = self.initialization_error {
244            return Err(crate::error::SklearsError::InvalidOperation(error.clone()));
245        }
246
247        self.config = Some(config.clone());
248        self.initialized = true;
249        Ok(())
250    }
251
252    fn is_compatible(&self, input_type: TypeId) -> bool {
253        self.metadata.supported_types.contains(&input_type)
254    }
255
256    fn as_any(&self) -> &dyn Any {
257        self
258    }
259
260    fn as_any_mut(&mut self) -> &mut dyn Any {
261        self
262    }
263
264    fn validate_config(&self, _config: &PluginConfig) -> Result<()> {
265        if let Some(ref error) = self.validation_error {
266            return Err(crate::error::SklearsError::InvalidOperation(error.clone()));
267        }
268        Ok(())
269    }
270
271    fn cleanup(&mut self) -> Result<()> {
272        self.record_call("cleanup");
273
274        if let Some(ref error) = self.cleanup_error {
275            return Err(crate::error::SklearsError::InvalidOperation(error.clone()));
276        }
277
278        self.initialized = false;
279        self.config = None;
280        Ok(())
281    }
282}
283
284/// Plugin test fixture for comprehensive testing scenarios
285///
286/// PluginTestFixture provides a complete testing environment with pre-configured
287/// plugins, validation scenarios, and test data for thorough plugin testing.
288///
289/// # Examples
290///
291/// ```rust,ignore
292/// use sklears_core::plugin::PluginTestFixture;
293///
294/// let fixture = PluginTestFixture::new();
295/// let plugins = fixture.create_test_plugins();
296/// assert!(!plugins.is_empty());
297///
298/// let manifests = fixture.create_test_manifests();
299/// assert!(!manifests.is_empty());
300/// ```
301#[derive(Debug)]
302pub struct PluginTestFixture {
303    /// Security policy for testing
304    pub security_policy: SecurityPolicy,
305    /// Test plugins
306    pub plugins: Vec<Box<dyn Plugin>>,
307    /// Test manifests
308    pub manifests: Vec<PluginManifest>,
309}
310
311impl PluginTestFixture {
312    /// Create a new test fixture
313    pub fn new() -> Self {
314        Self {
315            security_policy: SecurityPolicy::permissive(),
316            plugins: Vec::new(),
317            manifests: Vec::new(),
318        }
319    }
320
321    /// Create test fixture with strict security policy
322    pub fn with_strict_security() -> Self {
323        Self {
324            security_policy: SecurityPolicy::strict(),
325            plugins: Vec::new(),
326            manifests: Vec::new(),
327        }
328    }
329
330    /// Create a set of test plugins covering various scenarios
331    ///
332    /// # Returns
333    ///
334    /// Vector of test plugins with different configurations and behaviors.
335    pub fn create_test_plugins(&self) -> Vec<Box<dyn Plugin>> {
336        vec![
337            Box::new(MockPlugin::for_category(
338                "linear_regression",
339                PluginCategory::Algorithm,
340            )),
341            Box::new(MockPlugin::for_category(
342                "standard_scaler",
343                PluginCategory::Transformer,
344            )),
345            Box::new(MockPlugin::for_category(
346                "csv_loader",
347                PluginCategory::DataProcessor,
348            )),
349            Box::new(MockPlugin::for_category(
350                "accuracy_metric",
351                PluginCategory::Evaluator,
352            )),
353            Box::new(MockPlugin::for_category(
354                "plot_generator",
355                PluginCategory::Visualizer,
356            )),
357        ]
358    }
359
360    /// Create test manifests for validation testing
361    ///
362    /// # Returns
363    ///
364    /// Vector of plugin manifests with various security profiles and configurations.
365    pub fn create_test_manifests(&self) -> Vec<PluginManifest> {
366        vec![
367            self.create_safe_manifest(),
368            self.create_risky_manifest(),
369            self.create_invalid_manifest(),
370            self.create_signed_manifest(),
371        ]
372    }
373
374    /// Create a safe plugin manifest for testing
375    fn create_safe_manifest(&self) -> PluginManifest {
376        PluginManifest {
377            metadata: PluginMetadata {
378                name: "SafePlugin".to_string(),
379                version: "1.0.0".to_string(),
380                description: "A safe test plugin".to_string(),
381                author: "Test Suite".to_string(),
382                category: PluginCategory::Algorithm,
383                supported_types: vec![TypeId::of::<f64>()],
384                dependencies: Vec::new(),
385                capabilities: vec![PluginCapability::Parallel],
386                min_sdk_version: "0.1.0".to_string(),
387            },
388            permissions: vec![Permission::FileSystemRead, Permission::GpuAccess],
389            api_usage: None,
390            contains_unsafe_code: false,
391            dependencies: Vec::new(),
392            code_analysis: None,
393            signature: None,
394            content_hash: "safe_hash_123".to_string(),
395            publisher: PublisherInfo {
396                name: "Trusted Publisher".to_string(),
397                email: "trusted@example.com".to_string(),
398                website: Some("https://trusted.example.com".to_string()),
399                verified: true,
400                trust_score: 9,
401            },
402            marketplace: super::validation::MarketplaceInfo {
403                url: "https://marketplace.example.com/safe-plugin".to_string(),
404                downloads: 1000,
405                rating: 4.5,
406                reviews: 50,
407                last_updated: "2024-01-15".to_string(),
408            },
409        }
410    }
411
412    /// Create a risky plugin manifest for testing security validation
413    fn create_risky_manifest(&self) -> PluginManifest {
414        PluginManifest {
415            metadata: PluginMetadata {
416                name: "RiskyPlugin".to_string(),
417                version: "1.0.0".to_string(),
418                description: "A risky test plugin".to_string(),
419                author: "Unknown".to_string(),
420                category: PluginCategory::Algorithm,
421                supported_types: vec![TypeId::of::<f64>()],
422                dependencies: Vec::new(),
423                capabilities: Vec::new(),
424                min_sdk_version: "0.1.0".to_string(),
425            },
426            permissions: vec![
427                Permission::FileSystemWrite,
428                Permission::NetworkAccess,
429                Permission::SystemCommands,
430            ],
431            api_usage: Some(super::validation::ApiUsageInfo {
432                calls: vec!["std::process::Command".to_string()],
433                network_access: vec!["http://api.example.com".to_string()],
434                filesystem_access: vec!["/tmp/".to_string()],
435            }),
436            contains_unsafe_code: true,
437            dependencies: Vec::new(),
438            code_analysis: Some(super::validation::CodeAnalysisInfo {
439                cyclomatic_complexity: 25,
440                suspicious_patterns: vec!["eval".to_string()],
441                potential_memory_issues: 2,
442                lines_of_code: 1500,
443                test_coverage: 45.0,
444            }),
445            signature: None,
446            content_hash: "risky_hash_456".to_string(),
447            publisher: PublisherInfo {
448                name: "Unknown Publisher".to_string(),
449                email: "unknown@example.com".to_string(),
450                website: None,
451                verified: false,
452                trust_score: 2,
453            },
454            marketplace: super::validation::MarketplaceInfo {
455                url: "https://marketplace.example.com/risky-plugin".to_string(),
456                downloads: 10,
457                rating: 2.0,
458                reviews: 5,
459                last_updated: "2023-06-01".to_string(),
460            },
461        }
462    }
463
464    /// Create an invalid plugin manifest for testing error handling
465    fn create_invalid_manifest(&self) -> PluginManifest {
466        PluginManifest {
467            metadata: PluginMetadata {
468                name: "".to_string(),           // Invalid empty name
469                version: "invalid".to_string(), // Invalid version format
470                description: "Invalid plugin".to_string(),
471                author: "".to_string(), // Invalid empty author
472                category: PluginCategory::Algorithm,
473                supported_types: Vec::new(),
474                dependencies: Vec::new(),
475                capabilities: Vec::new(),
476                min_sdk_version: "999.0.0".to_string(), // Invalid high version
477            },
478            permissions: vec![Permission::Custom("invalid_permission".to_string())],
479            api_usage: None,
480            contains_unsafe_code: true,
481            dependencies: Vec::new(),
482            code_analysis: None,
483            signature: None,
484            content_hash: "".to_string(), // Invalid empty hash
485            publisher: PublisherInfo {
486                name: "".to_string(),
487                email: "invalid-email".to_string(), // Invalid email format
488                website: None,
489                verified: false,
490                trust_score: 15, // Invalid trust score > 10
491            },
492            marketplace: super::validation::MarketplaceInfo {
493                url: "".to_string(),
494                downloads: 0,
495                rating: 0.0,
496                reviews: 0,
497                last_updated: "".to_string(),
498            },
499        }
500    }
501
502    /// Create a signed plugin manifest for testing signature validation
503    fn create_signed_manifest(&self) -> PluginManifest {
504        let mut manifest = self.create_safe_manifest();
505        manifest.signature = Some(DigitalSignature {
506            algorithm: "RSA-SHA256".to_string(),
507            signature: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0],
508            public_key_fingerprint: "SHA256:test_fingerprint_123".to_string(),
509            timestamp: SystemTime::now(),
510            signer_certificate: Some(
511                "-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----".to_string(),
512            ),
513        });
514        manifest
515    }
516
517    /// Create test plugin configurations
518    ///
519    /// # Returns
520    ///
521    /// Vector of plugin configurations for testing various scenarios.
522    pub fn create_test_configs(&self) -> Vec<PluginConfig> {
523        vec![
524            self.create_minimal_config(),
525            self.create_full_config(),
526            self.create_invalid_config(),
527        ]
528    }
529
530    /// Create a minimal plugin configuration
531    fn create_minimal_config(&self) -> PluginConfig {
532        PluginConfig::default()
533    }
534
535    /// Create a comprehensive plugin configuration
536    fn create_full_config(&self) -> PluginConfig {
537        let mut config = PluginConfig::default();
538
539        // Add various parameter types
540        config
541            .parameters
542            .insert("learning_rate".to_string(), PluginParameter::Float(0.01));
543        config
544            .parameters
545            .insert("max_iterations".to_string(), PluginParameter::Int(1000));
546        config
547            .parameters
548            .insert("use_bias".to_string(), PluginParameter::Bool(true));
549        config.parameters.insert(
550            "algorithm".to_string(),
551            PluginParameter::String("adam".to_string()),
552        );
553        config.parameters.insert(
554            "layer_sizes".to_string(),
555            PluginParameter::IntArray(vec![100, 50, 10]),
556        );
557        config.parameters.insert(
558            "dropout_rates".to_string(),
559            PluginParameter::FloatArray(vec![0.2, 0.3, 0.5]),
560        );
561
562        // Runtime settings
563        config.runtime_settings.num_threads = Some(4);
564        config.runtime_settings.memory_limit = Some(1024 * 1024 * 1024); // 1GB
565        config.runtime_settings.timeout_ms = Some(30000); // 30 seconds
566        config.runtime_settings.use_gpu = true;
567
568        // Plugin-specific settings
569        config
570            .plugin_settings
571            .insert("backend".to_string(), "cuda".to_string());
572        config
573            .plugin_settings
574            .insert("precision".to_string(), "float32".to_string());
575
576        config
577    }
578
579    /// Create an invalid plugin configuration for testing error handling
580    fn create_invalid_config(&self) -> PluginConfig {
581        let mut config = PluginConfig::default();
582
583        // Invalid runtime settings
584        config.runtime_settings.num_threads = Some(0); // Invalid: zero threads
585        config.runtime_settings.timeout_ms = Some(0); // Invalid: zero timeout
586        config.runtime_settings.memory_limit = Some(0); // Invalid: zero memory
587
588        config
589    }
590}
591
592impl Default for PluginTestFixture {
593    fn default() -> Self {
594        Self::new()
595    }
596}
597
598/// Performance testing utilities for plugins
599///
600/// PluginPerformanceTester provides tools for measuring plugin performance,
601/// memory usage, and identifying bottlenecks in plugin implementations.
602///
603/// # Examples
604///
605/// ```rust,ignore
606/// use sklears_core::plugin::{PluginPerformanceTester, MockPlugin};
607///
608/// let mut tester = PluginPerformanceTester::new();
609/// let mut plugin = MockPlugin::new("test_plugin");
610///
611/// let result = tester.benchmark_initialization(&mut plugin);
612/// println!("Initialization took: {:?}", result.duration);
613/// ```
614#[derive(Debug)]
615pub struct PluginPerformanceTester {
616    /// Test configurations for benchmarking
617    pub test_configs: Vec<PluginConfig>,
618    /// Maximum allowed duration for operations
619    pub max_duration: Duration,
620    /// Memory usage baseline
621    pub memory_baseline: usize,
622}
623
624impl PluginPerformanceTester {
625    /// Create a new performance tester
626    pub fn new() -> Self {
627        Self {
628            test_configs: Vec::new(),
629            max_duration: Duration::from_secs(10),
630            memory_baseline: 0,
631        }
632    }
633
634    /// Create performance tester with custom timeout
635    ///
636    /// # Arguments
637    ///
638    /// * `max_duration` - Maximum allowed duration for operations
639    pub fn with_timeout(max_duration: Duration) -> Self {
640        Self {
641            test_configs: Vec::new(),
642            max_duration,
643            memory_baseline: 0,
644        }
645    }
646
647    /// Benchmark plugin initialization
648    ///
649    /// # Arguments
650    ///
651    /// * `plugin` - The plugin to benchmark
652    ///
653    /// # Returns
654    ///
655    /// Performance metrics for the initialization operation.
656    pub fn benchmark_initialization(&mut self, plugin: &mut dyn Plugin) -> PerformanceResult {
657        let config = PluginConfig::default();
658        let start = Instant::now();
659        let memory_start = self.get_memory_usage();
660
661        let result = plugin.initialize(&config);
662
663        let duration = start.elapsed();
664        let memory_end = self.get_memory_usage();
665        let memory_used = memory_end.saturating_sub(memory_start);
666
667        PerformanceResult {
668            operation: "initialization".to_string(),
669            duration,
670            memory_used,
671            success: result.is_ok(),
672            error: result.err().map(|e| e.to_string()),
673            within_limits: duration <= self.max_duration,
674        }
675    }
676
677    /// Benchmark plugin validation
678    ///
679    /// # Arguments
680    ///
681    /// * `plugin` - The plugin to benchmark
682    /// * `config` - The configuration to validate
683    ///
684    /// # Returns
685    ///
686    /// Performance metrics for the validation operation.
687    pub fn benchmark_validation(
688        &self,
689        plugin: &dyn Plugin,
690        config: &PluginConfig,
691    ) -> PerformanceResult {
692        let start = Instant::now();
693        let memory_start = self.get_memory_usage();
694
695        let result = plugin.validate_config(config);
696
697        let duration = start.elapsed();
698        let memory_end = self.get_memory_usage();
699        let memory_used = memory_end.saturating_sub(memory_start);
700
701        PerformanceResult {
702            operation: "validation".to_string(),
703            duration,
704            memory_used,
705            success: result.is_ok(),
706            error: result.err().map(|e| e.to_string()),
707            within_limits: duration <= self.max_duration,
708        }
709    }
710
711    /// Run a comprehensive performance test suite
712    ///
713    /// # Arguments
714    ///
715    /// * `plugin` - The plugin to test
716    ///
717    /// # Returns
718    ///
719    /// Vector of performance results for all operations.
720    pub fn run_performance_suite(&mut self, plugin: &mut dyn Plugin) -> Vec<PerformanceResult> {
721        let mut results = Vec::new();
722
723        // Test initialization
724        results.push(self.benchmark_initialization(plugin));
725
726        // Test validation with different configs
727        let fixture = PluginTestFixture::new();
728        for config in fixture.create_test_configs() {
729            results.push(self.benchmark_validation(plugin, &config));
730        }
731
732        // Test cleanup
733        results.push(self.benchmark_cleanup(plugin));
734
735        results
736    }
737
738    /// Benchmark plugin cleanup
739    fn benchmark_cleanup(&self, plugin: &mut dyn Plugin) -> PerformanceResult {
740        let start = Instant::now();
741        let memory_start = self.get_memory_usage();
742
743        let result = plugin.cleanup();
744
745        let duration = start.elapsed();
746        let memory_end = self.get_memory_usage();
747        let memory_used = memory_end.saturating_sub(memory_start);
748
749        PerformanceResult {
750            operation: "cleanup".to_string(),
751            duration,
752            memory_used,
753            success: result.is_ok(),
754            error: result.err().map(|e| e.to_string()),
755            within_limits: duration <= self.max_duration,
756        }
757    }
758
759    /// Get current memory usage (placeholder implementation)
760    fn get_memory_usage(&self) -> usize {
761        // In a real implementation, this would measure actual memory usage
762        // For now, return a placeholder value
763        1024 * 1024 // 1MB placeholder
764    }
765}
766
767impl Default for PluginPerformanceTester {
768    fn default() -> Self {
769        Self::new()
770    }
771}
772
773/// Performance measurement result
774#[derive(Debug, Clone)]
775pub struct PerformanceResult {
776    /// Operation name
777    pub operation: String,
778    /// Time taken for the operation
779    pub duration: Duration,
780    /// Memory used during operation
781    pub memory_used: usize,
782    /// Whether the operation succeeded
783    pub success: bool,
784    /// Error message if operation failed
785    pub error: Option<String>,
786    /// Whether the operation completed within time limits
787    pub within_limits: bool,
788}
789
790impl PerformanceResult {
791    /// Check if the performance result indicates good performance
792    pub fn is_good_performance(&self) -> bool {
793        self.success && self.within_limits
794    }
795
796    /// Get a performance score (0-100)
797    pub fn performance_score(&self) -> u8 {
798        if !self.success {
799            return 0;
800        }
801
802        let time_score = if self.within_limits { 50 } else { 0 };
803        let memory_score = if self.memory_used < 10 * 1024 * 1024 {
804            50
805        } else {
806            25
807        }; // < 10MB is good
808
809        (time_score + memory_score).min(100)
810    }
811}
812
813/// Validation test runner for comprehensive plugin testing
814///
815/// ValidationTestRunner executes a complete test suite including security
816/// validation, performance testing, and compatibility checks.
817///
818/// # Examples
819///
820/// ```rust,ignore
821/// use sklears_core::plugin::{ValidationTestRunner, MockPlugin};
822///
823/// let runner = ValidationTestRunner::new();
824/// let plugin = MockPlugin::new("test_plugin");
825/// let fixture = runner.create_test_fixture();
826///
827/// let report = runner.run_validation_tests(&plugin, &fixture.create_test_manifests()[0]);
828/// println!("Validation passed: {}", !report.has_errors());
829/// ```
830#[derive(Debug)]
831pub struct ValidationTestRunner {
832    /// Plugin validator
833    validator: PluginValidator,
834    /// Performance tester
835    performance_tester: PluginPerformanceTester,
836    /// Test fixture
837    fixture: PluginTestFixture,
838}
839
840impl ValidationTestRunner {
841    /// Create a new validation test runner
842    pub fn new() -> Self {
843        Self {
844            validator: PluginValidator::new(),
845            performance_tester: PluginPerformanceTester::new(),
846            fixture: PluginTestFixture::new(),
847        }
848    }
849
850    /// Create test runner with strict security
851    pub fn with_strict_security() -> Self {
852        Self {
853            validator: PluginValidator::new(),
854            performance_tester: PluginPerformanceTester::new(),
855            fixture: PluginTestFixture::with_strict_security(),
856        }
857    }
858
859    /// Run comprehensive validation tests
860    ///
861    /// # Arguments
862    ///
863    /// * `plugin` - The plugin to test
864    /// * `manifest` - The plugin manifest to validate
865    ///
866    /// # Returns
867    ///
868    /// Comprehensive validation report.
869    pub fn run_validation_tests(
870        &self,
871        plugin: &dyn Plugin,
872        manifest: &PluginManifest,
873    ) -> ValidationReport {
874        self.validator
875            .validate_comprehensive(plugin, manifest)
876            .unwrap_or_else(|_| {
877                let mut report = ValidationReport::new();
878                report.add_error(super::validation::ValidationError::InvalidMetadata(
879                    "Failed to run validation tests".to_string(),
880                ));
881                report
882            })
883    }
884
885    /// Run performance tests
886    ///
887    /// # Arguments
888    ///
889    /// * `plugin` - The plugin to test
890    ///
891    /// # Returns
892    ///
893    /// Vector of performance results.
894    pub fn run_performance_tests(&mut self, plugin: &mut dyn Plugin) -> Vec<PerformanceResult> {
895        self.performance_tester.run_performance_suite(plugin)
896    }
897
898    /// Run compatibility tests
899    ///
900    /// # Arguments
901    ///
902    /// * `plugin` - The plugin to test
903    ///
904    /// # Returns
905    ///
906    /// Compatibility test results.
907    pub fn run_compatibility_tests(&self, plugin: &dyn Plugin) -> CompatibilityTestResult {
908        let mut supported_types = Vec::new();
909        let mut unsupported_types = Vec::new();
910
911        // Test common types
912        let test_types = vec![
913            TypeId::of::<f32>(),
914            TypeId::of::<f64>(),
915            TypeId::of::<i32>(),
916            TypeId::of::<i64>(),
917            TypeId::of::<String>(),
918        ];
919
920        for type_id in test_types {
921            if plugin.is_compatible(type_id) {
922                supported_types.push(type_id);
923            } else {
924                unsupported_types.push(type_id);
925            }
926        }
927
928        let compatibility_score = (supported_types.len() as f32 / 5.0 * 100.0) as u8;
929
930        CompatibilityTestResult {
931            supported_types,
932            unsupported_types,
933            total_types_tested: 5,
934            compatibility_score,
935        }
936    }
937
938    /// Create a test fixture for the runner
939    pub fn create_test_fixture(&self) -> &PluginTestFixture {
940        &self.fixture
941    }
942
943    /// Run complete test suite
944    ///
945    /// # Arguments
946    ///
947    /// * `plugin` - The plugin to test
948    /// * `manifest` - The plugin manifest
949    ///
950    /// # Returns
951    ///
952    /// Complete test results.
953    pub fn run_complete_test_suite(
954        &mut self,
955        plugin: &mut dyn Plugin,
956        manifest: &PluginManifest,
957    ) -> CompleteTestResult {
958        let validation_report = self.run_validation_tests(plugin, manifest);
959        let performance_results = self.run_performance_tests(plugin);
960        let compatibility_result = self.run_compatibility_tests(plugin);
961
962        let overall_score = self.calculate_overall_score(
963            &validation_report,
964            &performance_results,
965            &compatibility_result,
966        );
967
968        CompleteTestResult {
969            validation_report,
970            performance_results,
971            compatibility_result,
972            overall_score,
973            test_passed: overall_score >= 70, // 70% minimum score to pass
974        }
975    }
976
977    /// Calculate overall test score
978    fn calculate_overall_score(
979        &self,
980        validation: &ValidationReport,
981        performance: &[PerformanceResult],
982        compatibility: &CompatibilityTestResult,
983    ) -> u8 {
984        let validation_score = if validation.has_errors() { 0 } else { 40 };
985
986        let avg_performance = if performance.is_empty() {
987            0
988        } else {
989            performance
990                .iter()
991                .map(|r| r.performance_score() as u32)
992                .sum::<u32>()
993                / performance.len() as u32
994        };
995        let performance_score = (avg_performance as f32 * 0.3) as u8;
996
997        let compatibility_score = (compatibility.compatibility_score as f32 * 0.3) as u8;
998
999        (validation_score + performance_score + compatibility_score).min(100)
1000    }
1001}
1002
1003impl Default for ValidationTestRunner {
1004    fn default() -> Self {
1005        Self::new()
1006    }
1007}
1008
1009/// Compatibility test result
1010#[derive(Debug, Clone)]
1011pub struct CompatibilityTestResult {
1012    /// Types that the plugin supports
1013    pub supported_types: Vec<TypeId>,
1014    /// Types that the plugin doesn't support
1015    pub unsupported_types: Vec<TypeId>,
1016    /// Total number of types tested
1017    pub total_types_tested: usize,
1018    /// Compatibility score (0-100)
1019    pub compatibility_score: u8,
1020}
1021
1022/// Complete test result containing all test outcomes
1023#[derive(Debug)]
1024pub struct CompleteTestResult {
1025    /// Validation test results
1026    pub validation_report: ValidationReport,
1027    /// Performance test results
1028    pub performance_results: Vec<PerformanceResult>,
1029    /// Compatibility test results
1030    pub compatibility_result: CompatibilityTestResult,
1031    /// Overall test score (0-100)
1032    pub overall_score: u8,
1033    /// Whether the plugin passed all tests
1034    pub test_passed: bool,
1035}
1036
1037impl CompleteTestResult {
1038    /// Get a summary of the test results
1039    pub fn summary(&self) -> String {
1040        format!(
1041            "Plugin Test Summary:\n\
1042             - Validation: {} errors, {} warnings\n\
1043             - Performance: {}/{} operations within limits\n\
1044             - Compatibility: {:.1}% type support\n\
1045             - Overall Score: {}/100\n\
1046             - Result: {}",
1047            self.validation_report.errors.len(),
1048            self.validation_report.warnings.len(),
1049            self.performance_results
1050                .iter()
1051                .filter(|r| r.within_limits)
1052                .count(),
1053            self.performance_results.len(),
1054            self.compatibility_result.compatibility_score,
1055            self.overall_score,
1056            if self.test_passed { "PASSED" } else { "FAILED" }
1057        )
1058    }
1059}
1060
1061#[allow(non_snake_case)]
1062#[cfg(test)]
1063mod tests {
1064    use super::*;
1065
1066    #[test]
1067    fn test_mock_plugin_creation() {
1068        let mock = MockPlugin::new("test_plugin");
1069        assert_eq!(mock.id(), "test_plugin");
1070        assert_eq!(mock.metadata.name, "Mock Plugin test_plugin");
1071        assert!(!mock.initialized);
1072    }
1073
1074    #[test]
1075    fn test_mock_plugin_categories() {
1076        let algo = MockPlugin::for_category("test", PluginCategory::Algorithm);
1077        assert_eq!(algo.metadata.category, PluginCategory::Algorithm);
1078
1079        let transformer = MockPlugin::for_category("test", PluginCategory::Transformer);
1080        assert_eq!(transformer.metadata.category, PluginCategory::Transformer);
1081    }
1082
1083    #[test]
1084    fn test_mock_plugin_type_support() {
1085        let mut mock = MockPlugin::new("test");
1086
1087        // Should support f64 by default
1088        assert!(mock.is_compatible(TypeId::of::<f64>()));
1089
1090        // Add i32 support
1091        mock.add_supported_type(TypeId::of::<i32>());
1092        assert!(mock.is_compatible(TypeId::of::<i32>()));
1093
1094        // Remove f64 support
1095        mock.remove_supported_type(TypeId::of::<f64>());
1096        assert!(!mock.is_compatible(TypeId::of::<f64>()));
1097    }
1098
1099    #[test]
1100    fn test_mock_plugin_error_simulation() {
1101        let mut mock = MockPlugin::new("test");
1102
1103        // Set initialization error
1104        mock.set_initialization_error(Some("Test error"));
1105        let result = mock.initialize(&PluginConfig::default());
1106        assert!(result.is_err());
1107
1108        // Clear error
1109        mock.set_initialization_error(None);
1110        let result = mock.initialize(&PluginConfig::default());
1111        assert!(result.is_ok());
1112    }
1113
1114    #[test]
1115    fn test_plugin_test_fixture() {
1116        let fixture = PluginTestFixture::new();
1117
1118        let plugins = fixture.create_test_plugins();
1119        assert_eq!(plugins.len(), 5);
1120
1121        let manifests = fixture.create_test_manifests();
1122        assert_eq!(manifests.len(), 4);
1123
1124        let configs = fixture.create_test_configs();
1125        assert_eq!(configs.len(), 3);
1126    }
1127
1128    #[test]
1129    fn test_performance_tester() {
1130        let mut tester = PluginPerformanceTester::new();
1131        let mut mock = MockPlugin::new("test");
1132
1133        let result = tester.benchmark_initialization(&mut mock);
1134        assert_eq!(result.operation, "initialization");
1135        assert!(result.success);
1136    }
1137
1138    #[test]
1139    fn test_validation_test_runner() {
1140        let runner = ValidationTestRunner::new();
1141        let mock = MockPlugin::new("test");
1142        let fixture = runner.create_test_fixture();
1143        let manifest = &fixture.create_test_manifests()[3]; // Signed manifest for validation
1144
1145        let report = runner.run_validation_tests(&mock, manifest);
1146        assert!(!report.has_errors()); // Should pass validation
1147    }
1148
1149    #[test]
1150    fn test_compatibility_tests() {
1151        let runner = ValidationTestRunner::new();
1152        let mock = MockPlugin::new("test"); // Supports f64 and f32 by default
1153
1154        let result = runner.run_compatibility_tests(&mock);
1155        assert!(!result.supported_types.is_empty());
1156        assert_eq!(result.total_types_tested, 5);
1157        assert!(result.compatibility_score > 0);
1158    }
1159
1160    #[test]
1161    fn test_performance_result_scoring() {
1162        let good_result = PerformanceResult {
1163            operation: "test".to_string(),
1164            duration: Duration::from_millis(10),
1165            memory_used: 1024, // 1KB
1166            success: true,
1167            error: None,
1168            within_limits: true,
1169        };
1170        assert!(good_result.is_good_performance());
1171        assert_eq!(good_result.performance_score(), 100);
1172
1173        let bad_result = PerformanceResult {
1174            operation: "test".to_string(),
1175            duration: Duration::from_secs(20),
1176            memory_used: 50 * 1024 * 1024, // 50MB
1177            success: false,
1178            error: Some("Error".to_string()),
1179            within_limits: false,
1180        };
1181        assert!(!bad_result.is_good_performance());
1182        assert_eq!(bad_result.performance_score(), 0);
1183    }
1184}