//! Comprehensive tests for error handling //! //! These tests ensure all error variants are properly handled //! and that error conversions work correctly. use jj_cz::{CommitMessageError, DescriptionError, Error, ScopeError}; /// Test that all error variants can be created and displayed #[test] fn test_all_error_variants() { // Domain errors let invalid_scope = Error::InvalidScope("test".to_string()); let _invalid_desc = Error::InvalidDescription("test".to_string()); let _invalid_msg = Error::InvalidCommitMessage("test".to_string()); // Infrastructure errors let not_repo = Error::NotARepository; let _jj_op = Error::JjOperation { context: "test".to_string(), }; let _repo_locked = Error::RepositoryLocked; let _failed_dir = Error::FailedGettingCurrentDir; let _failed_config = Error::FailedReadingConfig { context: "test".to_string(), }; // Application errors let cancelled = Error::Cancelled; let _non_interactive = Error::NonInteractive; // Verify all variants can be displayed assert_eq!(format!("{}", invalid_scope), "Invalid scope: test"); assert_eq!(format!("{}", not_repo), "Not a Jujutsu repository"); assert_eq!(format!("{}", cancelled), "Operation cancelled by user"); } /// Test error conversions from domain types #[test] fn test_error_conversions() { // ScopeError -> Error::InvalidScope let scope_err = ScopeError::TooLong { actual: 31, max: 30, }; let error: Error = scope_err.into(); assert!(matches!(error, Error::InvalidScope(_))); // DescriptionError -> Error::InvalidDescription let desc_err = DescriptionError::Empty; let error: Error = desc_err.into(); assert!(matches!(error, Error::InvalidDescription(_))); // CommitMessageError -> Error::InvalidCommitMessage let msg_err = CommitMessageError::FirstLineTooLong { actual: 73, max: 72, }; let error: Error = msg_err.into(); assert!(matches!(error, Error::InvalidCommitMessage(_))); } /// Test error equality and partial equality #[test] fn test_error_equality() { let err1 = Error::NotARepository; let err2 = Error::NotARepository; assert_eq!(err1, err2); let err3 = Error::Cancelled; assert_ne!(err1, err3); } /// Test error debugging #[test] fn test_error_debug() { let error = Error::JjOperation { context: "test operation".to_string(), }; let debug_str = format!("{:?}", error); assert!(debug_str.contains("JjOperation")); assert!(debug_str.contains("test operation")); } /// Test error cloning #[test] fn test_error_clone() { let original = Error::JjOperation { context: "original".to_string(), }; let cloned = original.clone(); assert_eq!(original, cloned); } /// Test error send and sync traits #[test] fn test_error_send_sync() { fn assert_send() {} fn assert_sync() {} let _error = Error::NotARepository; assert_send::(); assert_sync::(); // Test with owned data let _owned_error = Error::JjOperation { context: "test".to_string(), }; assert_send::(); assert_sync::(); } /// Test error matching patterns #[test] fn test_error_matching() { let error = Error::Cancelled; match error { Error::Cancelled => {} Error::NotARepository => panic!("Should not match"), Error::JjOperation { context } => panic!("Should not match: {}", context), _ => panic!("Should not match other variants"), } } /// Test error context extraction #[test] fn test_jj_operation_context() { let error = Error::JjOperation { context: "repository locked".to_string(), }; if let Error::JjOperation { context } = error { assert_eq!(context, "repository locked"); } else { panic!("Expected JjOperation variant"); } } /// Test conversion from std::io::Error #[test] fn test_from_io_error() { let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found"); let error: Error = io_err.into(); assert!(matches!(error, Error::FailedGettingCurrentDir)); } /// Test conversion from std::sync::PoisonError #[test] fn test_from_poison_error() { let mutex = std::sync::Mutex::new(()); // Poison the mutex by panicking while holding the lock let poison_err = std::panic::catch_unwind(|| { let _guard = mutex.lock().unwrap(); panic!("deliberate panic"); }); assert!(poison_err.is_err()); // Now lock should fail with PoisonError let result = mutex.lock(); assert!(result.is_err()); let error: Error = result.unwrap_err().into(); assert!(matches!(error, Error::JjOperation { .. })); assert_eq!( format!("{}", error), "Repository operation failed: internal lock poisoned" ); } /// Test from_revset_evaluation_error constructs RevsetResolutionError #[test] fn test_from_revset_evaluation_error() { let underlying = std::io::Error::new(std::io::ErrorKind::Other, "store failure"); let eval_err = jj_lib::revset::RevsetEvaluationError::Other(Box::new(underlying)); let error = Error::from_revset_evaluation_error("@", eval_err); assert!(matches!(error, Error::RevsetResolutionError { .. })); let description = format!("{}", error); assert!(description.contains("@")); assert!(description.contains("store failure")); } /// Test from_revset_resolution_error constructs RevsetResolutionError #[test] fn test_from_revset_resolution_error() { let resolution_err = jj_lib::revset::RevsetResolutionError::NoSuchRevision { name: "nonexistent".to_string(), candidates: Vec::new(), }; let error = Error::from_revset_resolution_error("@", resolution_err); assert!(matches!(error, Error::RevsetResolutionError { .. })); let description = format!("{}", error); assert!(description.contains("@")); assert!(description.contains("nonexistent")); } /// Test NewFlagWithMultipleRevisions error display #[test] fn test_new_flag_with_multiple_revisions() { let error = Error::NewFlagWithMultipleRevisions; assert_eq!( format!("{}", error), "--new cannot be used with multiple revisions" ); } /// Test NonInteractive error display #[test] fn test_non_interactive() { let error = Error::NonInteractive; assert_eq!(format!("{}", error), "Non-interactive terminal detected"); } /// Test FailedReadingConfig error display #[test] fn test_failed_reading_config() { let error = Error::FailedReadingConfig { context: "config parse error".to_string(), }; let description = format!("{}", error); assert!(description.contains("config parse error")); } /// Test MultipleRevisions error display #[test] fn test_multiple_revisions() { let error = Error::MultipleRevisions { revset: "abc | def".to_string(), }; let description = format!("{}", error); assert!(description.contains("abc | def")); assert!(description.contains("multiple commits")); } /// Test RepositoryLocked error display #[test] fn test_repository_locked() { let error = Error::RepositoryLocked; assert_eq!( format!("{}", error), "Repository is locked by another process" ); } /// Test FailedGettingCurrentDir error display #[test] fn test_failed_getting_current_dir() { let error = Error::FailedGettingCurrentDir; assert_eq!(format!("{}", error), "Could not get current directory"); } /// Test error matching on all variants #[test] fn test_error_matching_all_variants() { let variants: Vec = vec![ Error::InvalidScope("s".into()), Error::InvalidDescription("d".into()), Error::InvalidCommitMessage("m".into()), Error::NotARepository, Error::JjOperation { context: "c".into(), }, Error::RepositoryLocked, Error::FailedGettingCurrentDir, Error::FailedReadingConfig { context: "c".into(), }, Error::Cancelled, Error::NonInteractive, Error::RevsetResolutionError { revset: "@".into(), context: "c".into(), }, Error::MultipleRevisions { revset: "@".into() }, Error::NewFlagWithMultipleRevisions, ]; // All variants should be displayable without panicking for variant in &variants { let _ = format!("{}", variant); let _ = format!("{:?}", variant); } // Verify all variants can be cloned let cloned: Vec = variants.iter().map(|e| e.clone()).collect(); assert_eq!(variants.len(), cloned.len()); for (original, clone) in variants.iter().zip(cloned.iter()) { assert_eq!(original, clone); } }