Skip to main content

torsh_backend/
version_compat.rs

1//! Comprehensive error checking and version compatibility for ToRSh backends
2//!
3//! This module provides robust error handling, version checking, and compatibility
4//! management for different backend implementations and their dependencies.
5
6// Framework infrastructure - components designed for future use
7#![allow(dead_code)]
8use crate::{BackendResult, BackendType};
9use std::collections::HashMap;
10use torsh_core::error::TorshError;
11
12#[cfg(not(feature = "std"))]
13use alloc::{format, string::String, vec::Vec};
14
15/// Version information for backend components
16#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
17pub struct Version {
18    /// Major version number
19    pub major: u32,
20    /// Minor version number
21    pub minor: u32,
22    /// Patch version number
23    pub patch: u32,
24    /// Pre-release identifier (alpha, beta, rc)
25    pub pre_release: Option<String>,
26    /// Build metadata
27    pub build: Option<String>,
28}
29
30impl Version {
31    /// Create a new version
32    pub fn new(major: u32, minor: u32, patch: u32) -> Self {
33        Self {
34            major,
35            minor,
36            patch,
37            pre_release: None,
38            build: None,
39        }
40    }
41
42    /// Create a version with pre-release identifier
43    pub fn with_pre_release(major: u32, minor: u32, patch: u32, pre_release: String) -> Self {
44        Self {
45            major,
46            minor,
47            patch,
48            pre_release: Some(pre_release),
49            build: None,
50        }
51    }
52
53    /// Parse version from string (e.g., "1.2.3-alpha.1+build.123")
54    pub fn parse(version_str: &str) -> Result<Version, VersionError> {
55        let mut parts = version_str.split('+');
56        let version_part = parts.next().ok_or(VersionError::InvalidFormat)?;
57        let build = parts.next().map(|s| s.to_string());
58
59        let mut version_pre = version_part.split('-');
60        let version_core = version_pre.next().ok_or(VersionError::InvalidFormat)?;
61        let pre_release = version_pre.next().map(|s| s.to_string());
62
63        let core_parts: Vec<&str> = version_core.split('.').collect();
64        if core_parts.len() != 3 {
65            return Err(VersionError::InvalidFormat);
66        }
67
68        let major = core_parts[0]
69            .parse()
70            .map_err(|_| VersionError::InvalidNumber)?;
71        let minor = core_parts[1]
72            .parse()
73            .map_err(|_| VersionError::InvalidNumber)?;
74        let patch = core_parts[2]
75            .parse()
76            .map_err(|_| VersionError::InvalidNumber)?;
77
78        Ok(Version {
79            major,
80            minor,
81            patch,
82            pre_release,
83            build,
84        })
85    }
86
87    /// Check if this version is compatible with another version
88    pub fn is_compatible_with(&self, other: &Version) -> bool {
89        // Same major version and this version is >= other
90        self.major == other.major && self >= other
91    }
92
93    /// Check if this is a breaking change from another version
94    pub fn is_breaking_change_from(&self, other: &Version) -> bool {
95        self.major != other.major
96    }
97
98    /// Get string representation
99    pub fn to_string(&self) -> String {
100        let mut version = format!("{}.{}.{}", self.major, self.minor, self.patch);
101
102        if let Some(ref pre) = self.pre_release {
103            version.push('-');
104            version.push_str(pre);
105        }
106
107        if let Some(ref build) = self.build {
108            version.push('+');
109            version.push_str(build);
110        }
111
112        version
113    }
114}
115
116/// Version parsing and comparison errors
117#[derive(Debug, Clone, PartialEq, Eq)]
118pub enum VersionError {
119    /// Invalid version format
120    InvalidFormat,
121    /// Invalid number in version string
122    InvalidNumber,
123    /// Incompatible versions
124    IncompatibleVersions(String, String),
125    /// Missing required version
126    MissingVersion(String),
127}
128
129impl std::fmt::Display for VersionError {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        match self {
132            VersionError::InvalidFormat => write!(f, "Invalid version format"),
133            VersionError::InvalidNumber => write!(f, "Invalid number in version string"),
134            VersionError::IncompatibleVersions(v1, v2) => {
135                write!(f, "Incompatible versions: {} and {}", v1, v2)
136            }
137            VersionError::MissingVersion(component) => {
138                write!(f, "Missing version for component: {}", component)
139            }
140        }
141    }
142}
143
144impl std::error::Error for VersionError {}
145
146/// Backend dependency information
147#[derive(Debug, Clone)]
148pub struct BackendDependency {
149    /// Name of the dependency
150    pub name: String,
151    /// Required version range
152    pub required_version: VersionRange,
153    /// Current version (if available)
154    pub current_version: Option<Version>,
155    /// Whether this dependency is optional
156    pub optional: bool,
157    /// Features required from this dependency
158    pub required_features: Vec<String>,
159}
160
161/// Version range specification
162#[derive(Debug, Clone)]
163pub enum VersionRange {
164    /// Exact version match
165    Exact(Version),
166    /// Minimum version (inclusive)
167    Minimum(Version),
168    /// Range between two versions (inclusive)
169    Range(Version, Version),
170    /// Compatible with (same major version, >= minor.patch)
171    Compatible(Version),
172    /// Any version
173    Any,
174}
175
176impl VersionRange {
177    /// Check if a version satisfies this range
178    pub fn satisfies(&self, version: &Version) -> bool {
179        match self {
180            VersionRange::Exact(required) => version == required,
181            VersionRange::Minimum(min) => version >= min,
182            VersionRange::Range(min, max) => version >= min && version <= max,
183            VersionRange::Compatible(base) => version.is_compatible_with(base),
184            VersionRange::Any => true,
185        }
186    }
187
188    /// Get string representation of the range
189    pub fn to_string(&self) -> String {
190        match self {
191            VersionRange::Exact(v) => format!("={}", v.to_string()),
192            VersionRange::Minimum(v) => format!(">={}", v.to_string()),
193            VersionRange::Range(min, max) => format!("{}-{}", min.to_string(), max.to_string()),
194            VersionRange::Compatible(v) => format!("~{}", v.to_string()),
195            VersionRange::Any => "*".to_string(),
196        }
197    }
198}
199
200/// Comprehensive error context with debugging information
201#[derive(Debug, Clone)]
202pub struct ErrorContext {
203    /// Operation that failed
204    pub operation: String,
205    /// Backend type where error occurred
206    pub backend_type: Option<BackendType>,
207    /// Device information
208    pub device_info: Option<String>,
209    /// Error location (file:line)
210    pub location: Option<String>,
211    /// Timestamp when error occurred
212    pub timestamp: Option<String>,
213    /// Additional context information
214    pub details: HashMap<String, String>,
215    /// Suggested solutions
216    pub suggestions: Vec<String>,
217    /// Error severity level
218    pub severity: ErrorSeverity,
219}
220
221/// Error severity levels
222#[derive(Debug, Clone, PartialEq, Eq)]
223pub enum ErrorSeverity {
224    /// Fatal error - operation cannot continue
225    Fatal,
226    /// Error - operation failed but system can recover
227    Error,
228    /// Warning - operation succeeded but with issues
229    Warning,
230    /// Info - informational message
231    Info,
232}
233
234impl ErrorContext {
235    /// Create a new error context
236    pub fn new(operation: &str) -> Self {
237        Self {
238            operation: operation.to_string(),
239            backend_type: None,
240            device_info: None,
241            location: None,
242            timestamp: None,
243            details: HashMap::new(),
244            suggestions: Vec::new(),
245            severity: ErrorSeverity::Error,
246        }
247    }
248
249    /// Add backend type to context
250    pub fn with_backend(mut self, backend_type: BackendType) -> Self {
251        self.backend_type = Some(backend_type);
252        self
253    }
254
255    /// Add device information
256    pub fn with_device(mut self, device_info: &str) -> Self {
257        self.device_info = Some(device_info.to_string());
258        self
259    }
260
261    /// Add location information
262    pub fn with_location(mut self, file: &str, line: u32) -> Self {
263        self.location = Some(format!("{}:{}", file, line));
264        self
265    }
266
267    /// Add detail information
268    pub fn with_detail(mut self, key: &str, value: &str) -> Self {
269        self.details.insert(key.to_string(), value.to_string());
270        self
271    }
272
273    /// Add suggestion for fixing the error
274    pub fn with_suggestion(mut self, suggestion: &str) -> Self {
275        self.suggestions.push(suggestion.to_string());
276        self
277    }
278
279    /// Set error severity
280    pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
281        self.severity = severity;
282        self
283    }
284
285    /// Format error context for display
286    pub fn format(&self) -> String {
287        let mut output = format!("Error in operation: {}", self.operation);
288
289        if let Some(ref backend) = self.backend_type {
290            output.push_str(&format!("\nBackend: {:?}", backend));
291        }
292
293        if let Some(ref device) = self.device_info {
294            output.push_str(&format!("\nDevice: {}", device));
295        }
296
297        if let Some(ref location) = self.location {
298            output.push_str(&format!("\nLocation: {}", location));
299        }
300
301        if !self.details.is_empty() {
302            output.push_str("\nDetails:");
303            for (key, value) in &self.details {
304                output.push_str(&format!("\n  {}: {}", key, value));
305            }
306        }
307
308        if !self.suggestions.is_empty() {
309            output.push_str("\nSuggestions:");
310            for suggestion in &self.suggestions {
311                output.push_str(&format!("\n  - {}", suggestion));
312            }
313        }
314
315        output
316    }
317}
318
319/// Version compatibility checker
320#[derive(Debug)]
321pub struct VersionCompatibilityChecker {
322    /// Known backend versions
323    backend_versions: HashMap<BackendType, Version>,
324    /// Backend dependencies
325    dependencies: HashMap<BackendType, Vec<BackendDependency>>,
326    /// Compatibility matrix
327    compatibility_matrix: HashMap<(BackendType, BackendType), bool>,
328}
329
330impl Default for VersionCompatibilityChecker {
331    fn default() -> Self {
332        Self::new()
333    }
334}
335
336impl VersionCompatibilityChecker {
337    /// Create a new compatibility checker
338    pub fn new() -> Self {
339        let mut checker = Self {
340            backend_versions: HashMap::new(),
341            dependencies: HashMap::new(),
342            compatibility_matrix: HashMap::new(),
343        };
344
345        // Initialize with known versions and dependencies
346        checker.initialize_known_versions();
347        checker.initialize_dependencies();
348        checker.build_compatibility_matrix();
349
350        checker
351    }
352
353    /// Initialize known backend versions
354    fn initialize_known_versions(&mut self) {
355        // These would typically be detected at runtime
356        self.backend_versions
357            .insert(BackendType::Cpu, Version::new(0, 1, 0));
358
359        #[cfg(feature = "cuda")]
360        self.backend_versions
361            .insert(BackendType::Cuda, Version::new(0, 1, 0));
362
363        #[cfg(all(feature = "metal", target_os = "macos", target_arch = "aarch64"))]
364        self.backend_versions
365            .insert(BackendType::Metal, Version::new(0, 1, 0));
366
367        #[cfg(feature = "webgpu")]
368        self.backend_versions
369            .insert(BackendType::WebGpu, Version::new(0, 1, 0));
370    }
371
372    /// Initialize known dependencies
373    fn initialize_dependencies(&mut self) {
374        // CPU backend dependencies
375        let cpu_deps = vec![
376            BackendDependency {
377                name: "scirs2-core".to_string(),
378                required_version: VersionRange::Compatible(Version::new(0, 1, 0)),
379                current_version: None,
380                optional: false,
381                required_features: vec!["cpu".to_string()],
382            },
383            BackendDependency {
384                name: "rayon".to_string(),
385                required_version: VersionRange::Minimum(Version::new(1, 5, 0)),
386                current_version: None,
387                optional: false,
388                required_features: vec![],
389            },
390        ];
391        self.dependencies.insert(BackendType::Cpu, cpu_deps);
392
393        // CUDA backend dependencies
394        #[cfg(feature = "cuda")]
395        {
396            let cuda_deps = vec![
397                BackendDependency {
398                    name: "scirs2-core".to_string(),
399                    required_version: VersionRange::Compatible(Version::new(0, 1, 0)),
400                    current_version: None,
401                    optional: false,
402                    required_features: vec!["cuda".to_string()],
403                },
404                BackendDependency {
405                    name: "cuda-runtime".to_string(),
406                    required_version: VersionRange::Minimum(Version::new(11, 0, 0)),
407                    current_version: None,
408                    optional: false,
409                    required_features: vec![],
410                },
411            ];
412            self.dependencies.insert(BackendType::Cuda, cuda_deps);
413        }
414    }
415
416    /// Build compatibility matrix between backends
417    fn build_compatibility_matrix(&mut self) {
418        // All backends are compatible with themselves
419        for backend_type in [
420            BackendType::Cpu,
421            BackendType::Cuda,
422            BackendType::Metal,
423            BackendType::WebGpu,
424        ] {
425            self.compatibility_matrix
426                .insert((backend_type, backend_type), true);
427        }
428
429        // Cross-backend compatibility (for data transfer, etc.)
430        self.compatibility_matrix
431            .insert((BackendType::Cpu, BackendType::Cuda), true);
432        self.compatibility_matrix
433            .insert((BackendType::Cuda, BackendType::Cpu), true);
434        self.compatibility_matrix
435            .insert((BackendType::Cpu, BackendType::Metal), true);
436        self.compatibility_matrix
437            .insert((BackendType::Metal, BackendType::Cpu), true);
438        // WebGPU has limited compatibility
439        self.compatibility_matrix
440            .insert((BackendType::Cpu, BackendType::WebGpu), true);
441        self.compatibility_matrix
442            .insert((BackendType::WebGpu, BackendType::Cpu), true);
443    }
444
445    /// Check if a backend is compatible with current system
446    pub fn check_backend_compatibility(
447        &self,
448        backend_type: BackendType,
449    ) -> BackendResult<CompatibilityReport> {
450        let mut report = CompatibilityReport::new(backend_type);
451
452        // Check backend version
453        if let Some(version) = self.backend_versions.get(&backend_type) {
454            report.backend_version = Some(version.clone());
455        } else {
456            report
457                .errors
458                .push(format!("Backend {:?} version not found", backend_type));
459        }
460
461        // Check dependencies
462        if let Some(deps) = self.dependencies.get(&backend_type) {
463            for dep in deps {
464                let dep_status = self.check_dependency(dep);
465                report
466                    .dependency_status
467                    .insert(dep.name.clone(), dep_status);
468            }
469        }
470
471        // Check runtime requirements
472        self.check_runtime_requirements(backend_type, &mut report);
473
474        Ok(report)
475    }
476
477    /// Check a specific dependency
478    fn check_dependency(&self, dependency: &BackendDependency) -> DependencyStatus {
479        // In a real implementation, this would check if the dependency is available
480        // and what version is installed
481        DependencyStatus {
482            available: true, // Assume available for now
483            current_version: dependency.current_version.clone(),
484            satisfies_requirement: true, // Assume satisfied
485            missing_features: Vec::new(),
486        }
487    }
488
489    /// Check runtime requirements for a backend
490    fn check_runtime_requirements(
491        &self,
492        backend_type: BackendType,
493        report: &mut CompatibilityReport,
494    ) {
495        match backend_type {
496            BackendType::Cuda => {
497                #[cfg(feature = "cuda")]
498                {
499                    // Check CUDA runtime
500                    if !self.check_cuda_runtime() {
501                        report.errors.push("CUDA runtime not available".to_string());
502                        report
503                            .suggestions
504                            .push("Install CUDA toolkit and drivers".to_string());
505                    }
506                }
507                #[cfg(not(feature = "cuda"))]
508                {
509                    report
510                        .errors
511                        .push("CUDA backend not compiled in".to_string());
512                    report
513                        .suggestions
514                        .push("Recompile with --features cuda".to_string());
515                }
516            }
517            BackendType::Metal => {
518                #[cfg(all(feature = "metal", target_os = "macos"))]
519                {
520                    // Check Metal availability
521                    if !self.check_metal_availability() {
522                        report
523                            .errors
524                            .push("Metal not available on this system".to_string());
525                    }
526                }
527                #[cfg(not(all(feature = "metal", target_os = "macos")))]
528                {
529                    report
530                        .errors
531                        .push("Metal backend only available on macOS".to_string());
532                }
533            }
534            BackendType::WebGpu => {
535                #[cfg(feature = "webgpu")]
536                {
537                    // Check WebGPU support
538                    if !self.check_webgpu_support() {
539                        report
540                            .warnings
541                            .push("WebGPU support may be limited".to_string());
542                    }
543                }
544                #[cfg(not(feature = "webgpu"))]
545                {
546                    report
547                        .errors
548                        .push("WebGPU backend not compiled in".to_string());
549                }
550            }
551            BackendType::Cpu => {
552                // CPU backend should always be available
553                // Check for optimal CPU features
554                self.check_cpu_features(report);
555            }
556            _ => {}
557        }
558    }
559
560    #[cfg(feature = "cuda")]
561    fn check_cuda_runtime(&self) -> bool {
562        // In a real implementation, this would check for CUDA libraries
563        true // Assume available for now
564    }
565
566    #[cfg(all(feature = "metal", target_os = "macos"))]
567    fn check_metal_availability(&self) -> bool {
568        // Check if Metal is available
569        true // Assume available on macOS
570    }
571
572    #[cfg(feature = "webgpu")]
573    fn check_webgpu_support(&self) -> bool {
574        // Check WebGPU support
575        true // Assume available
576    }
577
578    fn check_cpu_features(&self, _report: &mut CompatibilityReport) {
579        // Check for optimal CPU features
580        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
581        {
582            if !self.has_avx2() {
583                _report
584                    .warnings
585                    .push("AVX2 not available - performance may be reduced".to_string());
586                _report
587                    .suggestions
588                    .push("Consider using a newer CPU with AVX2 support".to_string());
589            }
590        }
591    }
592
593    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
594    fn has_avx2(&self) -> bool {
595        std::arch::is_x86_feature_detected!("avx2")
596    }
597
598    /// Check compatibility between two backends
599    pub fn check_cross_backend_compatibility(&self, from: BackendType, to: BackendType) -> bool {
600        self.compatibility_matrix
601            .get(&(from, to))
602            .copied()
603            .unwrap_or(false)
604    }
605
606    /// Get all compatible backends for a given backend
607    pub fn get_compatible_backends(&self, backend_type: BackendType) -> Vec<BackendType> {
608        let mut compatible = Vec::new();
609
610        for other_backend in [
611            BackendType::Cpu,
612            BackendType::Cuda,
613            BackendType::Metal,
614            BackendType::WebGpu,
615        ] {
616            if self.check_cross_backend_compatibility(backend_type, other_backend) {
617                compatible.push(other_backend);
618            }
619        }
620
621        compatible
622    }
623}
624
625/// Compatibility check report
626#[derive(Debug, Clone)]
627pub struct CompatibilityReport {
628    /// Backend being checked
629    pub backend_type: BackendType,
630    /// Backend version
631    pub backend_version: Option<Version>,
632    /// Dependency status
633    pub dependency_status: HashMap<String, DependencyStatus>,
634    /// Compatibility errors
635    pub errors: Vec<String>,
636    /// Compatibility warnings
637    pub warnings: Vec<String>,
638    /// Suggestions for improvement
639    pub suggestions: Vec<String>,
640    /// Overall compatibility score (0.0 to 1.0)
641    pub compatibility_score: f32,
642}
643
644impl CompatibilityReport {
645    /// Create a new compatibility report
646    pub fn new(backend_type: BackendType) -> Self {
647        Self {
648            backend_type,
649            backend_version: None,
650            dependency_status: HashMap::new(),
651            errors: Vec::new(),
652            warnings: Vec::new(),
653            suggestions: Vec::new(),
654            compatibility_score: 0.0,
655        }
656    }
657
658    /// Check if the backend is fully compatible
659    pub fn is_compatible(&self) -> bool {
660        self.errors.is_empty()
661    }
662
663    /// Calculate compatibility score
664    pub fn calculate_score(&mut self) {
665        let total_checks = 1 + self.dependency_status.len(); // Backend + dependencies
666        let mut passed_checks = 0;
667
668        // Backend version check
669        if self.backend_version.is_some() {
670            passed_checks += 1;
671        }
672
673        // Dependency checks
674        for status in self.dependency_status.values() {
675            if status.satisfies_requirement {
676                passed_checks += 1;
677            }
678        }
679
680        self.compatibility_score = passed_checks as f32 / total_checks as f32;
681
682        // Reduce score for warnings
683        let warning_penalty = self.warnings.len() as f32 * 0.1;
684        self.compatibility_score = (self.compatibility_score - warning_penalty).max(0.0);
685    }
686
687    /// Format report for display
688    pub fn format(&self) -> String {
689        let mut output = format!("Compatibility Report for {:?} Backend\n", self.backend_type);
690        output.push_str(&format!(
691            "Score: {:.1}%\n\n",
692            self.compatibility_score * 100.0
693        ));
694
695        if let Some(ref version) = self.backend_version {
696            output.push_str(&format!("Backend Version: {}\n", version.to_string()));
697        }
698
699        if !self.dependency_status.is_empty() {
700            output.push_str("\nDependencies:\n");
701            for (name, status) in &self.dependency_status {
702                let status_str = if status.satisfies_requirement {
703                    "✓"
704                } else {
705                    "✗"
706                };
707                output.push_str(&format!("  {} {}\n", status_str, name));
708            }
709        }
710
711        if !self.errors.is_empty() {
712            output.push_str("\nErrors:\n");
713            for error in &self.errors {
714                output.push_str(&format!("  ✗ {}\n", error));
715            }
716        }
717
718        if !self.warnings.is_empty() {
719            output.push_str("\nWarnings:\n");
720            for warning in &self.warnings {
721                output.push_str(&format!("  ⚠ {}\n", warning));
722            }
723        }
724
725        if !self.suggestions.is_empty() {
726            output.push_str("\nSuggestions:\n");
727            for suggestion in &self.suggestions {
728                output.push_str(&format!("  💡 {}\n", suggestion));
729            }
730        }
731
732        output
733    }
734}
735
736/// Status of a dependency check
737#[derive(Debug, Clone)]
738pub struct DependencyStatus {
739    /// Whether the dependency is available
740    pub available: bool,
741    /// Current version of the dependency
742    pub current_version: Option<Version>,
743    /// Whether current version satisfies requirement
744    pub satisfies_requirement: bool,
745    /// Missing required features
746    pub missing_features: Vec<String>,
747}
748
749/// Enhanced error result extension for version compatibility
750pub trait VersionErrorContextExt<T> {
751    /// Add operation context to error
752    fn with_operation(self, operation: &str) -> Result<T, TorshError>;
753
754    /// Add backend context to error
755    fn with_backend_context(
756        self,
757        backend_type: BackendType,
758        operation: &str,
759    ) -> Result<T, TorshError>;
760
761    /// Add location context to error
762    fn with_location_context(self, file: &str, line: u32) -> Result<T, TorshError>;
763}
764
765impl<T> VersionErrorContextExt<T> for Result<T, TorshError> {
766    fn with_operation(self, operation: &str) -> Result<T, TorshError> {
767        self.map_err(|e| {
768            let context = ErrorContext::new(operation);
769            TorshError::BackendError(format!("{}\n{}", e, context.format()))
770        })
771    }
772
773    fn with_backend_context(
774        self,
775        backend_type: BackendType,
776        operation: &str,
777    ) -> Result<T, TorshError> {
778        self.map_err(|e| {
779            let context = ErrorContext::new(operation).with_backend(backend_type);
780            TorshError::BackendError(format!("{}\n{}", e, context.format()))
781        })
782    }
783
784    fn with_location_context(self, file: &str, line: u32) -> Result<T, TorshError> {
785        self.map_err(|e| {
786            let context = ErrorContext::new("unknown").with_location(file, line);
787            TorshError::BackendError(format!("{}\n{}", e, context.format()))
788        })
789    }
790}
791
792/// Macro for adding location context to errors
793#[macro_export]
794macro_rules! error_with_location {
795    ($result:expr) => {
796        $result.with_location_context(file!(), line!())
797    };
798}
799
800#[cfg(test)]
801mod tests {
802    use super::*;
803
804    #[test]
805    fn test_version_parsing() {
806        let version = Version::parse("1.2.3").unwrap();
807        assert_eq!(version.major, 1);
808        assert_eq!(version.minor, 2);
809        assert_eq!(version.patch, 3);
810        assert_eq!(version.pre_release, None);
811
812        let version = Version::parse("2.0.0-alpha.1+build.123").unwrap();
813        assert_eq!(version.major, 2);
814        assert_eq!(version.pre_release, Some("alpha.1".to_string()));
815        assert_eq!(version.build, Some("build.123".to_string()));
816    }
817
818    #[test]
819    fn test_version_compatibility() {
820        let v1 = Version::new(1, 2, 3);
821        let v2 = Version::new(1, 2, 4);
822        let v3 = Version::new(2, 0, 0);
823
824        assert!(v2.is_compatible_with(&v1));
825        assert!(!v1.is_compatible_with(&v2));
826        assert!(v3.is_breaking_change_from(&v1));
827    }
828
829    #[test]
830    fn test_version_range() {
831        let version = Version::new(1, 2, 3);
832
833        let exact = VersionRange::Exact(Version::new(1, 2, 3));
834        assert!(exact.satisfies(&version));
835
836        let min = VersionRange::Minimum(Version::new(1, 0, 0));
837        assert!(min.satisfies(&version));
838
839        let compat = VersionRange::Compatible(Version::new(1, 1, 0));
840        assert!(compat.satisfies(&version));
841    }
842
843    #[test]
844    fn test_error_context() {
845        let context = ErrorContext::new("test_operation")
846            .with_backend(BackendType::Cpu)
847            .with_device("cpu:0")
848            .with_detail("tensor_size", "1000")
849            .with_suggestion("Reduce batch size");
850
851        let formatted = context.format();
852        assert!(formatted.contains("test_operation"));
853        assert!(formatted.contains("Backend: Cpu"));
854        assert!(formatted.contains("Device: cpu:0"));
855    }
856
857    #[test]
858    fn test_compatibility_checker() {
859        let checker = VersionCompatibilityChecker::new();
860
861        let report = checker
862            .check_backend_compatibility(BackendType::Cpu)
863            .unwrap();
864        assert_eq!(report.backend_type, BackendType::Cpu);
865
866        let compatible = checker.get_compatible_backends(BackendType::Cpu);
867        assert!(!compatible.is_empty());
868    }
869
870    #[test]
871    fn test_compatibility_report() {
872        let mut report = CompatibilityReport::new(BackendType::Cpu);
873        report.backend_version = Some(Version::new(0, 1, 0));
874        report.calculate_score();
875
876        assert!(report.compatibility_score > 0.0);
877        assert!(report.is_compatible());
878    }
879
880    #[test]
881    fn test_cross_backend_compatibility() {
882        let checker = VersionCompatibilityChecker::new();
883
884        // CPU and CUDA should be compatible
885        assert!(checker.check_cross_backend_compatibility(BackendType::Cpu, BackendType::Cuda));
886        assert!(checker.check_cross_backend_compatibility(BackendType::Cuda, BackendType::Cpu));
887    }
888}