use super::core_traits::Plugin;
use super::security::{DigitalSignature, Permission, PublisherInfo, SecurityPolicy};
use super::types_config::{
PluginCapability, PluginCategory, PluginConfig, PluginMetadata, PluginParameter,
};
use super::validation::{PluginManifest, PluginValidator, ValidationReport};
use crate::error::Result;
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::time::{Duration, Instant, SystemTime};
#[derive(Debug, Clone)]
pub struct MockPlugin {
pub id: String,
pub metadata: PluginMetadata,
pub config: Option<PluginConfig>,
pub initialized: bool,
pub initialization_error: Option<String>,
pub validation_error: Option<String>,
pub cleanup_error: Option<String>,
pub call_counts: HashMap<String, usize>,
pub artificial_delays: HashMap<String, Duration>,
}
impl MockPlugin {
pub fn new(id: &str) -> Self {
Self {
id: id.to_string(),
metadata: PluginMetadata {
name: format!("Mock Plugin {}", id),
version: "1.0.0".to_string(),
description: "A mock plugin for testing".to_string(),
author: "Test Framework".to_string(),
category: PluginCategory::Algorithm,
supported_types: vec![TypeId::of::<f64>(), TypeId::of::<f32>()],
dependencies: Vec::new(),
capabilities: vec![PluginCapability::Parallel],
min_sdk_version: "0.1.0".to_string(),
},
config: None,
initialized: false,
initialization_error: None,
validation_error: None,
cleanup_error: None,
call_counts: HashMap::new(),
artificial_delays: HashMap::new(),
}
}
pub fn for_category(id: &str, category: PluginCategory) -> Self {
let mut mock = Self::new(id);
mock.metadata.category = category.clone();
mock.metadata.name = format!("Mock {} Plugin", Self::category_name(&category));
mock
}
pub fn add_supported_type(&mut self, type_id: TypeId) {
if !self.metadata.supported_types.contains(&type_id) {
self.metadata.supported_types.push(type_id);
}
}
pub fn remove_supported_type(&mut self, type_id: TypeId) {
self.metadata.supported_types.retain(|&t| t != type_id);
}
pub fn set_initialization_error(&mut self, error: Option<&str>) {
self.initialization_error = error.map(|s| s.to_string());
}
pub fn set_validation_error(&mut self, error: Option<&str>) {
self.validation_error = error.map(|s| s.to_string());
}
pub fn set_cleanup_error(&mut self, error: Option<&str>) {
self.cleanup_error = error.map(|s| s.to_string());
}
pub fn add_artificial_delay(&mut self, method: &str, delay: Duration) {
self.artificial_delays.insert(method.to_string(), delay);
}
pub fn get_call_count(&self, method: &str) -> usize {
self.call_counts.get(method).copied().unwrap_or(0)
}
pub fn reset_call_counts(&mut self) {
self.call_counts.clear();
}
fn record_call(&mut self, method: &str) {
*self.call_counts.entry(method.to_string()).or_insert(0) += 1;
if let Some(delay) = self.artificial_delays.get(method) {
std::thread::sleep(*delay);
}
}
fn category_name(category: &PluginCategory) -> &str {
match category {
PluginCategory::Algorithm => "Algorithm",
PluginCategory::Transformer => "Transformer",
PluginCategory::DataProcessor => "DataProcessor",
PluginCategory::Evaluator => "Evaluator",
PluginCategory::Visualizer => "Visualizer",
PluginCategory::Custom(name) => name,
}
}
}
impl Plugin for MockPlugin {
fn id(&self) -> &str {
&self.id
}
fn metadata(&self) -> PluginMetadata {
self.metadata.clone()
}
fn initialize(&mut self, config: &PluginConfig) -> Result<()> {
self.record_call("initialize");
if let Some(ref error) = self.initialization_error {
return Err(crate::error::SklearsError::InvalidOperation(error.clone()));
}
self.config = Some(config.clone());
self.initialized = true;
Ok(())
}
fn is_compatible(&self, input_type: TypeId) -> bool {
self.metadata.supported_types.contains(&input_type)
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn validate_config(&self, _config: &PluginConfig) -> Result<()> {
if let Some(ref error) = self.validation_error {
return Err(crate::error::SklearsError::InvalidOperation(error.clone()));
}
Ok(())
}
fn cleanup(&mut self) -> Result<()> {
self.record_call("cleanup");
if let Some(ref error) = self.cleanup_error {
return Err(crate::error::SklearsError::InvalidOperation(error.clone()));
}
self.initialized = false;
self.config = None;
Ok(())
}
}
#[derive(Debug)]
pub struct PluginTestFixture {
pub security_policy: SecurityPolicy,
pub plugins: Vec<Box<dyn Plugin>>,
pub manifests: Vec<PluginManifest>,
}
impl PluginTestFixture {
pub fn new() -> Self {
Self {
security_policy: SecurityPolicy::permissive(),
plugins: Vec::new(),
manifests: Vec::new(),
}
}
pub fn with_strict_security() -> Self {
Self {
security_policy: SecurityPolicy::strict(),
plugins: Vec::new(),
manifests: Vec::new(),
}
}
pub fn create_test_plugins(&self) -> Vec<Box<dyn Plugin>> {
vec![
Box::new(MockPlugin::for_category(
"linear_regression",
PluginCategory::Algorithm,
)),
Box::new(MockPlugin::for_category(
"standard_scaler",
PluginCategory::Transformer,
)),
Box::new(MockPlugin::for_category(
"csv_loader",
PluginCategory::DataProcessor,
)),
Box::new(MockPlugin::for_category(
"accuracy_metric",
PluginCategory::Evaluator,
)),
Box::new(MockPlugin::for_category(
"plot_generator",
PluginCategory::Visualizer,
)),
]
}
pub fn create_test_manifests(&self) -> Vec<PluginManifest> {
vec![
self.create_safe_manifest(),
self.create_risky_manifest(),
self.create_invalid_manifest(),
self.create_signed_manifest(),
]
}
fn create_safe_manifest(&self) -> PluginManifest {
PluginManifest {
metadata: PluginMetadata {
name: "SafePlugin".to_string(),
version: "1.0.0".to_string(),
description: "A safe test plugin".to_string(),
author: "Test Suite".to_string(),
category: PluginCategory::Algorithm,
supported_types: vec![TypeId::of::<f64>()],
dependencies: Vec::new(),
capabilities: vec![PluginCapability::Parallel],
min_sdk_version: "0.1.0".to_string(),
},
permissions: vec![Permission::FileSystemRead, Permission::GpuAccess],
api_usage: None,
contains_unsafe_code: false,
dependencies: Vec::new(),
code_analysis: None,
signature: None,
content_hash: "safe_hash_123".to_string(),
publisher: PublisherInfo {
name: "Trusted Publisher".to_string(),
email: "trusted@example.com".to_string(),
website: Some("https://trusted.example.com".to_string()),
verified: true,
trust_score: 9,
},
marketplace: super::validation::MarketplaceInfo {
url: "https://marketplace.example.com/safe-plugin".to_string(),
downloads: 1000,
rating: 4.5,
reviews: 50,
last_updated: "2024-01-15".to_string(),
},
}
}
fn create_risky_manifest(&self) -> PluginManifest {
PluginManifest {
metadata: PluginMetadata {
name: "RiskyPlugin".to_string(),
version: "1.0.0".to_string(),
description: "A risky test plugin".to_string(),
author: "Unknown".to_string(),
category: PluginCategory::Algorithm,
supported_types: vec![TypeId::of::<f64>()],
dependencies: Vec::new(),
capabilities: Vec::new(),
min_sdk_version: "0.1.0".to_string(),
},
permissions: vec![
Permission::FileSystemWrite,
Permission::NetworkAccess,
Permission::SystemCommands,
],
api_usage: Some(super::validation::ApiUsageInfo {
calls: vec!["std::process::Command".to_string()],
network_access: vec!["http://api.example.com".to_string()],
filesystem_access: vec![std::env::temp_dir().display().to_string()],
}),
contains_unsafe_code: true,
dependencies: Vec::new(),
code_analysis: Some(super::validation::CodeAnalysisInfo {
cyclomatic_complexity: 25,
suspicious_patterns: vec!["eval".to_string()],
potential_memory_issues: 2,
lines_of_code: 1500,
test_coverage: 45.0,
}),
signature: None,
content_hash: "risky_hash_456".to_string(),
publisher: PublisherInfo {
name: "Unknown Publisher".to_string(),
email: "unknown@example.com".to_string(),
website: None,
verified: false,
trust_score: 2,
},
marketplace: super::validation::MarketplaceInfo {
url: "https://marketplace.example.com/risky-plugin".to_string(),
downloads: 10,
rating: 2.0,
reviews: 5,
last_updated: "2023-06-01".to_string(),
},
}
}
fn create_invalid_manifest(&self) -> PluginManifest {
PluginManifest {
metadata: PluginMetadata {
name: "".to_string(), version: "invalid".to_string(), description: "Invalid plugin".to_string(),
author: "".to_string(), category: PluginCategory::Algorithm,
supported_types: Vec::new(),
dependencies: Vec::new(),
capabilities: Vec::new(),
min_sdk_version: "999.0.0".to_string(), },
permissions: vec![Permission::Custom("invalid_permission".to_string())],
api_usage: None,
contains_unsafe_code: true,
dependencies: Vec::new(),
code_analysis: None,
signature: None,
content_hash: "".to_string(), publisher: PublisherInfo {
name: "".to_string(),
email: "invalid-email".to_string(), website: None,
verified: false,
trust_score: 15, },
marketplace: super::validation::MarketplaceInfo {
url: "".to_string(),
downloads: 0,
rating: 0.0,
reviews: 0,
last_updated: "".to_string(),
},
}
}
fn create_signed_manifest(&self) -> PluginManifest {
let mut manifest = self.create_safe_manifest();
manifest.signature = Some(DigitalSignature {
algorithm: "RSA-SHA256".to_string(),
signature: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0],
public_key_fingerprint: "SHA256:test_fingerprint_123".to_string(),
timestamp: SystemTime::now(),
signer_certificate: Some(
"-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----".to_string(),
),
});
manifest
}
pub fn create_test_configs(&self) -> Vec<PluginConfig> {
vec![
self.create_minimal_config(),
self.create_full_config(),
self.create_invalid_config(),
]
}
fn create_minimal_config(&self) -> PluginConfig {
PluginConfig::default()
}
fn create_full_config(&self) -> PluginConfig {
let mut config = PluginConfig::default();
config
.parameters
.insert("learning_rate".to_string(), PluginParameter::Float(0.01));
config
.parameters
.insert("max_iterations".to_string(), PluginParameter::Int(1000));
config
.parameters
.insert("use_bias".to_string(), PluginParameter::Bool(true));
config.parameters.insert(
"algorithm".to_string(),
PluginParameter::String("adam".to_string()),
);
config.parameters.insert(
"layer_sizes".to_string(),
PluginParameter::IntArray(vec![100, 50, 10]),
);
config.parameters.insert(
"dropout_rates".to_string(),
PluginParameter::FloatArray(vec![0.2, 0.3, 0.5]),
);
config.runtime_settings.num_threads = Some(4);
config.runtime_settings.memory_limit = Some(1024 * 1024 * 1024); config.runtime_settings.timeout_ms = Some(30000); config.runtime_settings.use_gpu = true;
config
.plugin_settings
.insert("backend".to_string(), "cuda".to_string());
config
.plugin_settings
.insert("precision".to_string(), "float32".to_string());
config
}
fn create_invalid_config(&self) -> PluginConfig {
let mut config = PluginConfig::default();
config.runtime_settings.num_threads = Some(0); config.runtime_settings.timeout_ms = Some(0); config.runtime_settings.memory_limit = Some(0);
config
}
}
impl Default for PluginTestFixture {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct PluginPerformanceTester {
pub test_configs: Vec<PluginConfig>,
pub max_duration: Duration,
pub memory_baseline: usize,
}
impl PluginPerformanceTester {
pub fn new() -> Self {
Self {
test_configs: Vec::new(),
max_duration: Duration::from_secs(10),
memory_baseline: 0,
}
}
pub fn with_timeout(max_duration: Duration) -> Self {
Self {
test_configs: Vec::new(),
max_duration,
memory_baseline: 0,
}
}
pub fn benchmark_initialization(&mut self, plugin: &mut dyn Plugin) -> PerformanceResult {
let config = PluginConfig::default();
let start = Instant::now();
let memory_start = self.get_memory_usage();
let result = plugin.initialize(&config);
let duration = start.elapsed();
let memory_end = self.get_memory_usage();
let memory_used = memory_end.saturating_sub(memory_start);
PerformanceResult {
operation: "initialization".to_string(),
duration,
memory_used,
success: result.is_ok(),
error: result.err().map(|e| e.to_string()),
within_limits: duration <= self.max_duration,
}
}
pub fn benchmark_validation(
&self,
plugin: &dyn Plugin,
config: &PluginConfig,
) -> PerformanceResult {
let start = Instant::now();
let memory_start = self.get_memory_usage();
let result = plugin.validate_config(config);
let duration = start.elapsed();
let memory_end = self.get_memory_usage();
let memory_used = memory_end.saturating_sub(memory_start);
PerformanceResult {
operation: "validation".to_string(),
duration,
memory_used,
success: result.is_ok(),
error: result.err().map(|e| e.to_string()),
within_limits: duration <= self.max_duration,
}
}
pub fn run_performance_suite(&mut self, plugin: &mut dyn Plugin) -> Vec<PerformanceResult> {
let mut results = Vec::new();
results.push(self.benchmark_initialization(plugin));
let fixture = PluginTestFixture::new();
for config in fixture.create_test_configs() {
results.push(self.benchmark_validation(plugin, &config));
}
results.push(self.benchmark_cleanup(plugin));
results
}
fn benchmark_cleanup(&self, plugin: &mut dyn Plugin) -> PerformanceResult {
let start = Instant::now();
let memory_start = self.get_memory_usage();
let result = plugin.cleanup();
let duration = start.elapsed();
let memory_end = self.get_memory_usage();
let memory_used = memory_end.saturating_sub(memory_start);
PerformanceResult {
operation: "cleanup".to_string(),
duration,
memory_used,
success: result.is_ok(),
error: result.err().map(|e| e.to_string()),
within_limits: duration <= self.max_duration,
}
}
fn get_memory_usage(&self) -> usize {
1024 * 1024 }
}
impl Default for PluginPerformanceTester {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PerformanceResult {
pub operation: String,
pub duration: Duration,
pub memory_used: usize,
pub success: bool,
pub error: Option<String>,
pub within_limits: bool,
}
impl PerformanceResult {
pub fn is_good_performance(&self) -> bool {
self.success && self.within_limits
}
pub fn performance_score(&self) -> u8 {
if !self.success {
return 0;
}
let time_score = if self.within_limits { 50 } else { 0 };
let memory_score = if self.memory_used < 10 * 1024 * 1024 {
50
} else {
25
};
(time_score + memory_score).min(100)
}
}
#[derive(Debug)]
pub struct ValidationTestRunner {
validator: PluginValidator,
performance_tester: PluginPerformanceTester,
fixture: PluginTestFixture,
}
impl ValidationTestRunner {
pub fn new() -> Self {
Self {
validator: PluginValidator::new(),
performance_tester: PluginPerformanceTester::new(),
fixture: PluginTestFixture::new(),
}
}
pub fn with_strict_security() -> Self {
Self {
validator: PluginValidator::new(),
performance_tester: PluginPerformanceTester::new(),
fixture: PluginTestFixture::with_strict_security(),
}
}
pub fn run_validation_tests(
&self,
plugin: &dyn Plugin,
manifest: &PluginManifest,
) -> ValidationReport {
self.validator
.validate_comprehensive(plugin, manifest)
.unwrap_or_else(|_| {
let mut report = ValidationReport::new();
report.add_error(super::validation::ValidationError::InvalidMetadata(
"Failed to run validation tests".to_string(),
));
report
})
}
pub fn run_performance_tests(&mut self, plugin: &mut dyn Plugin) -> Vec<PerformanceResult> {
self.performance_tester.run_performance_suite(plugin)
}
pub fn run_compatibility_tests(&self, plugin: &dyn Plugin) -> CompatibilityTestResult {
let mut supported_types = Vec::new();
let mut unsupported_types = Vec::new();
let test_types = vec![
TypeId::of::<f32>(),
TypeId::of::<f64>(),
TypeId::of::<i32>(),
TypeId::of::<i64>(),
TypeId::of::<String>(),
];
for type_id in test_types {
if plugin.is_compatible(type_id) {
supported_types.push(type_id);
} else {
unsupported_types.push(type_id);
}
}
let compatibility_score = (supported_types.len() as f32 / 5.0 * 100.0) as u8;
CompatibilityTestResult {
supported_types,
unsupported_types,
total_types_tested: 5,
compatibility_score,
}
}
pub fn create_test_fixture(&self) -> &PluginTestFixture {
&self.fixture
}
pub fn run_complete_test_suite(
&mut self,
plugin: &mut dyn Plugin,
manifest: &PluginManifest,
) -> CompleteTestResult {
let validation_report = self.run_validation_tests(plugin, manifest);
let performance_results = self.run_performance_tests(plugin);
let compatibility_result = self.run_compatibility_tests(plugin);
let overall_score = self.calculate_overall_score(
&validation_report,
&performance_results,
&compatibility_result,
);
CompleteTestResult {
validation_report,
performance_results,
compatibility_result,
overall_score,
test_passed: overall_score >= 70, }
}
fn calculate_overall_score(
&self,
validation: &ValidationReport,
performance: &[PerformanceResult],
compatibility: &CompatibilityTestResult,
) -> u8 {
let validation_score = if validation.has_errors() { 0 } else { 40 };
let avg_performance = if performance.is_empty() {
0
} else {
performance
.iter()
.map(|r| r.performance_score() as u32)
.sum::<u32>()
/ performance.len() as u32
};
let performance_score = (avg_performance as f32 * 0.3) as u8;
let compatibility_score = (compatibility.compatibility_score as f32 * 0.3) as u8;
(validation_score + performance_score + compatibility_score).min(100)
}
}
impl Default for ValidationTestRunner {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CompatibilityTestResult {
pub supported_types: Vec<TypeId>,
pub unsupported_types: Vec<TypeId>,
pub total_types_tested: usize,
pub compatibility_score: u8,
}
#[derive(Debug)]
pub struct CompleteTestResult {
pub validation_report: ValidationReport,
pub performance_results: Vec<PerformanceResult>,
pub compatibility_result: CompatibilityTestResult,
pub overall_score: u8,
pub test_passed: bool,
}
impl CompleteTestResult {
pub fn summary(&self) -> String {
format!(
"Plugin Test Summary:\n\
- Validation: {} errors, {} warnings\n\
- Performance: {}/{} operations within limits\n\
- Compatibility: {:.1}% type support\n\
- Overall Score: {}/100\n\
- Result: {}",
self.validation_report.errors.len(),
self.validation_report.warnings.len(),
self.performance_results
.iter()
.filter(|r| r.within_limits)
.count(),
self.performance_results.len(),
self.compatibility_result.compatibility_score,
self.overall_score,
if self.test_passed { "PASSED" } else { "FAILED" }
)
}
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mock_plugin_creation() {
let mock = MockPlugin::new("test_plugin");
assert_eq!(mock.id(), "test_plugin");
assert_eq!(mock.metadata.name, "Mock Plugin test_plugin");
assert!(!mock.initialized);
}
#[test]
fn test_mock_plugin_categories() {
let algo = MockPlugin::for_category("test", PluginCategory::Algorithm);
assert_eq!(algo.metadata.category, PluginCategory::Algorithm);
let transformer = MockPlugin::for_category("test", PluginCategory::Transformer);
assert_eq!(transformer.metadata.category, PluginCategory::Transformer);
}
#[test]
fn test_mock_plugin_type_support() {
let mut mock = MockPlugin::new("test");
assert!(mock.is_compatible(TypeId::of::<f64>()));
mock.add_supported_type(TypeId::of::<i32>());
assert!(mock.is_compatible(TypeId::of::<i32>()));
mock.remove_supported_type(TypeId::of::<f64>());
assert!(!mock.is_compatible(TypeId::of::<f64>()));
}
#[test]
fn test_mock_plugin_error_simulation() {
let mut mock = MockPlugin::new("test");
mock.set_initialization_error(Some("Test error"));
let result = mock.initialize(&PluginConfig::default());
assert!(result.is_err());
mock.set_initialization_error(None);
let result = mock.initialize(&PluginConfig::default());
assert!(result.is_ok());
}
#[test]
fn test_plugin_test_fixture() {
let fixture = PluginTestFixture::new();
let plugins = fixture.create_test_plugins();
assert_eq!(plugins.len(), 5);
let manifests = fixture.create_test_manifests();
assert_eq!(manifests.len(), 4);
let configs = fixture.create_test_configs();
assert_eq!(configs.len(), 3);
}
#[test]
fn test_performance_tester() {
let mut tester = PluginPerformanceTester::new();
let mut mock = MockPlugin::new("test");
let result = tester.benchmark_initialization(&mut mock);
assert_eq!(result.operation, "initialization");
assert!(result.success);
}
#[test]
fn test_validation_test_runner() {
let runner = ValidationTestRunner::new();
let mock = MockPlugin::new("test");
let fixture = runner.create_test_fixture();
let manifest = &fixture.create_test_manifests()[3];
let report = runner.run_validation_tests(&mock, manifest);
assert!(!report.has_errors()); }
#[test]
fn test_compatibility_tests() {
let runner = ValidationTestRunner::new();
let mock = MockPlugin::new("test");
let result = runner.run_compatibility_tests(&mock);
assert!(!result.supported_types.is_empty());
assert_eq!(result.total_types_tested, 5);
assert!(result.compatibility_score > 0);
}
#[test]
fn test_performance_result_scoring() {
let good_result = PerformanceResult {
operation: "test".to_string(),
duration: Duration::from_millis(10),
memory_used: 1024, success: true,
error: None,
within_limits: true,
};
assert!(good_result.is_good_performance());
assert_eq!(good_result.performance_score(), 100);
let bad_result = PerformanceResult {
operation: "test".to_string(),
duration: Duration::from_secs(20),
memory_used: 50 * 1024 * 1024, success: false,
error: Some("Error".to_string()),
within_limits: false,
};
assert!(!bad_result.is_good_performance());
assert_eq!(bad_result.performance_score(), 0);
}
}