development_monorepo #13
| @@ -11,7 +11,7 @@ categories = ["os", "filesystem", "api-bindings"] | |||||||
| readme = "README.md" | readme = "README.md" | ||||||
|  |  | ||||||
| [workspace] | [workspace] | ||||||
| members = [".", "vault", "git", "redisclient", "mycelium", "text"] | members = [".", "vault", "git", "redisclient", "mycelium", "text", "os"] | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| hex = "0.4" | hex = "0.4" | ||||||
| @@ -64,6 +64,7 @@ sal-git = { path = "git" } | |||||||
| sal-redisclient = { path = "redisclient" } | sal-redisclient = { path = "redisclient" } | ||||||
| sal-mycelium = { path = "mycelium" } | sal-mycelium = { path = "mycelium" } | ||||||
| sal-text = { path = "text" } | sal-text = { path = "text" } | ||||||
|  | sal-os = { path = "os" } | ||||||
|  |  | ||||||
| # Optional features for specific OS functionality | # Optional features for specific OS functionality | ||||||
| [target.'cfg(unix)'.dependencies] | [target.'cfg(unix)'.dependencies] | ||||||
|   | |||||||
							
								
								
									
										36
									
								
								os/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								os/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | [package] | ||||||
|  | name = "sal-os" | ||||||
|  | version = "0.1.0" | ||||||
|  | edition = "2021" | ||||||
|  | authors = ["PlanetFirst <info@incubaid.com>"] | ||||||
|  | description = "SAL OS - Operating system interaction utilities with cross-platform abstraction" | ||||||
|  | repository = "https://git.threefold.info/herocode/sal" | ||||||
|  | license = "Apache-2.0" | ||||||
|  | keywords = ["system", "os", "filesystem", "download", "package-management"] | ||||||
|  | categories = ["os", "filesystem", "api-bindings"] | ||||||
|  |  | ||||||
|  | [dependencies] | ||||||
|  | # Core dependencies for file system operations | ||||||
|  | dirs = "6.0.0" | ||||||
|  | glob = "0.3.1" | ||||||
|  | libc = "0.2" | ||||||
|  |  | ||||||
|  | # Error handling | ||||||
|  | thiserror = "2.0.12" | ||||||
|  |  | ||||||
|  | # Rhai scripting support | ||||||
|  | rhai = { version = "1.12.0", features = ["sync"] } | ||||||
|  |  | ||||||
|  | # Optional features for specific OS functionality | ||||||
|  | [target.'cfg(unix)'.dependencies] | ||||||
|  | nix = "0.30.1" | ||||||
|  |  | ||||||
|  | [target.'cfg(windows)'.dependencies] | ||||||
|  | windows = { version = "0.61.1", features = [ | ||||||
|  |     "Win32_Foundation", | ||||||
|  |     "Win32_System_Threading",  | ||||||
|  |     "Win32_Storage_FileSystem", | ||||||
|  | ] } | ||||||
|  |  | ||||||
|  | [dev-dependencies] | ||||||
|  | tempfile = "3.5" | ||||||
							
								
								
									
										104
									
								
								os/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								os/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | |||||||
|  | # SAL OS Package (`sal-os`) | ||||||
|  |  | ||||||
|  | The `sal-os` package provides a comprehensive suite of operating system interaction utilities. It offers a cross-platform abstraction layer for common OS-level tasks, simplifying system programming in Rust. | ||||||
|  |  | ||||||
|  | ## Features | ||||||
|  |  | ||||||
|  | - **File System Operations**: Comprehensive file and directory manipulation | ||||||
|  | - **Download Utilities**: File downloading with automatic extraction support | ||||||
|  | - **Package Management**: System package manager integration | ||||||
|  | - **Platform Detection**: Cross-platform OS and architecture detection | ||||||
|  | - **Rhai Integration**: Full scripting support for all OS operations | ||||||
|  |  | ||||||
|  | ## Modules | ||||||
|  |  | ||||||
|  | - `fs`: File system operations (create, copy, delete, find, etc.) | ||||||
|  | - `download`: File downloading and basic installation | ||||||
|  | - `package`: System package management | ||||||
|  | - `platform`: Platform and architecture detection | ||||||
|  |  | ||||||
|  | ## Usage | ||||||
|  |  | ||||||
|  | Add this to your `Cargo.toml`: | ||||||
|  |  | ||||||
|  | ```toml | ||||||
|  | [dependencies] | ||||||
|  | sal-os = "0.1.0" | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### File System Operations | ||||||
|  |  | ||||||
|  | ```rust | ||||||
|  | use sal_os::fs; | ||||||
|  |  | ||||||
|  | fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||||
|  |     // Create directory | ||||||
|  |     fs::mkdir("my_dir")?; | ||||||
|  |      | ||||||
|  |     // Write and read files | ||||||
|  |     fs::file_write("my_dir/example.txt", "Hello from SAL!")?; | ||||||
|  |     let content = fs::file_read("my_dir/example.txt")?; | ||||||
|  |      | ||||||
|  |     // Find files | ||||||
|  |     let files = fs::find_files(".", "*.txt")?; | ||||||
|  |      | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Download Operations | ||||||
|  |  | ||||||
|  | ```rust | ||||||
|  | use sal_os::download; | ||||||
|  |  | ||||||
|  | fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||||
|  |     // Download and extract archive | ||||||
|  |     let path = download::download("https://example.com/archive.tar.gz", "/tmp", 1024)?; | ||||||
|  |      | ||||||
|  |     // Download specific file | ||||||
|  |     download::download_file("https://example.com/script.sh", "/tmp/script.sh", 0)?; | ||||||
|  |     download::chmod_exec("/tmp/script.sh")?; | ||||||
|  |      | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Platform Detection | ||||||
|  |  | ||||||
|  | ```rust | ||||||
|  | use sal_os::platform; | ||||||
|  |  | ||||||
|  | fn main() { | ||||||
|  |     if platform::is_linux() { | ||||||
|  |         println!("Running on Linux"); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     if platform::is_arm() { | ||||||
|  |         println!("ARM architecture detected"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Rhai Integration | ||||||
|  |  | ||||||
|  | The package provides full Rhai scripting support: | ||||||
|  |  | ||||||
|  | ```rhai | ||||||
|  | // File operations | ||||||
|  | mkdir("test_dir"); | ||||||
|  | file_write("test_dir/hello.txt", "Hello World!"); | ||||||
|  | let content = file_read("test_dir/hello.txt"); | ||||||
|  |  | ||||||
|  | // Download operations | ||||||
|  | download("https://example.com/file.zip", "/tmp", 0); | ||||||
|  | chmod_exec("/tmp/script.sh"); | ||||||
|  |  | ||||||
|  | // Platform detection | ||||||
|  | if is_linux() { | ||||||
|  |     print("Running on Linux"); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## License | ||||||
|  |  | ||||||
|  | Licensed under the Apache License, Version 2.0. | ||||||
							
								
								
									
										13
									
								
								os/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								os/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | pub mod download; | ||||||
|  | pub mod fs; | ||||||
|  | pub mod package; | ||||||
|  | pub mod platform; | ||||||
|  |  | ||||||
|  | // Re-export all public functions and types | ||||||
|  | pub use download::*; | ||||||
|  | pub use fs::*; | ||||||
|  | pub use package::*; | ||||||
|  | pub use platform::*; | ||||||
|  |  | ||||||
|  | // Rhai integration module | ||||||
|  | pub mod rhai; | ||||||
| @@ -1,6 +1,14 @@ | |||||||
| use crate::process::CommandResult; |  | ||||||
| use std::process::Command; | use std::process::Command; | ||||||
| 
 | 
 | ||||||
|  | /// A structure to hold command execution results
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
|  | pub struct CommandResult { | ||||||
|  |     pub stdout: String, | ||||||
|  |     pub stderr: String, | ||||||
|  |     pub success: bool, | ||||||
|  |     pub code: i32, | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /// Error type for package management operations
 | /// Error type for package management operations
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub enum PackageError { | pub enum PackageError { | ||||||
| @@ -1,4 +1,16 @@ | |||||||
| use crate::rhai::error::SalError; | use thiserror::Error; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Error)] | ||||||
|  | pub enum PlatformError { | ||||||
|  |     #[error("{0}: {1}")] | ||||||
|  |     Generic(String, String), | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl PlatformError { | ||||||
|  |     pub fn new(kind: &str, message: &str) -> Self { | ||||||
|  |         PlatformError::Generic(kind.to_string(), message.to_string()) | ||||||
|  |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| #[cfg(target_os = "macos")] | #[cfg(target_os = "macos")] | ||||||
| pub fn is_osx() -> bool { | pub fn is_osx() -> bool { | ||||||
| @@ -40,24 +52,24 @@ pub fn is_x86() -> bool { | |||||||
|     false |     false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn check_linux_x86() -> Result<(), SalError> { | pub fn check_linux_x86() -> Result<(), PlatformError> { | ||||||
|     if is_linux() && is_x86() { |     if is_linux() && is_x86() { | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } else { |     } else { | ||||||
|         Err(SalError::Generic( |         Err(PlatformError::new( | ||||||
|             "Platform Check Error".to_string(), |             "Platform Check Error", | ||||||
|             "This operation is only supported on Linux x86_64.".to_string(), |             "This operation is only supported on Linux x86_64.", | ||||||
|         )) |         )) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn check_macos_arm() -> Result<(), SalError> { | pub fn check_macos_arm() -> Result<(), PlatformError> { | ||||||
|     if is_osx() && is_arm() { |     if is_osx() && is_arm() { | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } else { |     } else { | ||||||
|         Err(SalError::Generic( |         Err(PlatformError::new( | ||||||
|             "Platform Check Error".to_string(), |             "Platform Check Error", | ||||||
|             "This operation is only supported on macOS ARM.".to_string(), |             "This operation is only supported on macOS ARM.", | ||||||
|         )) |         )) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -2,10 +2,25 @@ | |||||||
| //!
 | //!
 | ||||||
| //! This module provides Rhai wrappers for the functions in the OS module.
 | //! This module provides Rhai wrappers for the functions in the OS module.
 | ||||||
| 
 | 
 | ||||||
| use rhai::{Engine, EvalAltResult, Array}; | use crate::package::PackHero; | ||||||
| use crate::os; | use crate::{download as dl, fs, package}; | ||||||
| use crate::os::package::PackHero; | use rhai::{Array, Engine, EvalAltResult, Position}; | ||||||
| use super::error::{ToRhaiError, register_error_types}; | 
 | ||||||
|  | /// A trait for converting a Result to a Rhai-compatible error
 | ||||||
|  | pub trait ToRhaiError<T> { | ||||||
|  |     fn to_rhai_error(self) -> Result<T, Box<EvalAltResult>>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T, E: std::error::Error> ToRhaiError<T> for Result<T, E> { | ||||||
|  |     fn to_rhai_error(self) -> Result<T, Box<EvalAltResult>> { | ||||||
|  |         self.map_err(|e| { | ||||||
|  |             Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |                 e.to_string().into(), | ||||||
|  |                 Position::NONE, | ||||||
|  |             )) | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| /// Register OS module functions with the Rhai engine
 | /// Register OS module functions with the Rhai engine
 | ||||||
| ///
 | ///
 | ||||||
| @@ -17,9 +32,6 @@ use super::error::{ToRhaiError, register_error_types}; | |||||||
| ///
 | ///
 | ||||||
| /// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
 | /// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
 | ||||||
| pub fn register_os_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> { | pub fn register_os_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> { | ||||||
|     // Register error types
 |  | ||||||
|     register_error_types(engine)?; |  | ||||||
|     
 |  | ||||||
|     // Register file system functions
 |     // Register file system functions
 | ||||||
|     engine.register_fn("copy", copy); |     engine.register_fn("copy", copy); | ||||||
|     engine.register_fn("copy_bin", copy_bin); |     engine.register_fn("copy_bin", copy_bin); | ||||||
| @@ -36,20 +48,20 @@ pub fn register_os_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> | |||||||
|     engine.register_fn("file_read", file_read); |     engine.register_fn("file_read", file_read); | ||||||
|     engine.register_fn("file_write", file_write); |     engine.register_fn("file_write", file_write); | ||||||
|     engine.register_fn("file_write_append", file_write_append); |     engine.register_fn("file_write_append", file_write_append); | ||||||
|     
 | 
 | ||||||
|     // Register command check functions
 |     // Register command check functions
 | ||||||
|     engine.register_fn("which", which); |     engine.register_fn("which", which); | ||||||
|     engine.register_fn("cmd_ensure_exists", cmd_ensure_exists); |     engine.register_fn("cmd_ensure_exists", cmd_ensure_exists); | ||||||
|     
 | 
 | ||||||
|     // Register download functions
 |     // Register download functions
 | ||||||
|     engine.register_fn("download", download); |     engine.register_fn("download", download); | ||||||
|     engine.register_fn("download_file", download_file); |     engine.register_fn("download_file", download_file); | ||||||
|     engine.register_fn("download_install", download_install); |     engine.register_fn("download_install", download_install); | ||||||
|     engine.register_fn("chmod_exec", chmod_exec); |     engine.register_fn("chmod_exec", chmod_exec); | ||||||
|     
 | 
 | ||||||
|     // Register move function
 |     // Register move function
 | ||||||
|     engine.register_fn("mv", mv); |     engine.register_fn("mv", mv); | ||||||
|     
 | 
 | ||||||
|     // Register package management functions
 |     // Register package management functions
 | ||||||
|     engine.register_fn("package_install", package_install); |     engine.register_fn("package_install", package_install); | ||||||
|     engine.register_fn("package_remove", package_remove); |     engine.register_fn("package_remove", package_remove); | ||||||
| @@ -60,7 +72,15 @@ pub fn register_os_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> | |||||||
|     engine.register_fn("package_is_installed", package_is_installed); |     engine.register_fn("package_is_installed", package_is_installed); | ||||||
|     engine.register_fn("package_set_debug", package_set_debug); |     engine.register_fn("package_set_debug", package_set_debug); | ||||||
|     engine.register_fn("package_platform", package_platform); |     engine.register_fn("package_platform", package_platform); | ||||||
|     
 | 
 | ||||||
|  |     // Register platform detection functions
 | ||||||
|  |     engine.register_fn("platform_is_osx", platform_is_osx); | ||||||
|  |     engine.register_fn("platform_is_linux", platform_is_linux); | ||||||
|  |     engine.register_fn("platform_is_arm", platform_is_arm); | ||||||
|  |     engine.register_fn("platform_is_x86", platform_is_x86); | ||||||
|  |     engine.register_fn("platform_check_linux_x86", platform_check_linux_x86); | ||||||
|  |     engine.register_fn("platform_check_macos_arm", platform_check_macos_arm); | ||||||
|  | 
 | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -68,132 +88,132 @@ pub fn register_os_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> | |||||||
| // File System Function Wrappers
 | // File System Function Wrappers
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::copy
 | /// Wrapper for fs::copy
 | ||||||
| ///
 | ///
 | ||||||
| /// Recursively copy a file or directory from source to destination.
 | /// Recursively copy a file or directory from source to destination.
 | ||||||
| pub fn copy(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> { | pub fn copy(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::copy(src, dest).to_rhai_error() |     fs::copy(src, dest).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::copy_bin
 | /// Wrapper for fs::copy_bin
 | ||||||
| ///
 | ///
 | ||||||
| /// Copy a binary to the correct location based on OS and user privileges.
 | /// Copy a binary to the correct location based on OS and user privileges.
 | ||||||
| pub fn copy_bin(src: &str) -> Result<String, Box<EvalAltResult>> { | pub fn copy_bin(src: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::copy_bin(src).to_rhai_error() |     fs::copy_bin(src).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::exist
 | /// Wrapper for fs::exist
 | ||||||
| ///
 | ///
 | ||||||
| /// Check if a file or directory exists.
 | /// Check if a file or directory exists.
 | ||||||
| pub fn exist(path: &str) -> bool { | pub fn exist(path: &str) -> bool { | ||||||
|     os::exist(path) |     fs::exist(path) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::find_file
 | /// Wrapper for fs::find_file
 | ||||||
| ///
 | ///
 | ||||||
| /// Find a file in a directory (with support for wildcards).
 | /// Find a file in a directory (with support for wildcards).
 | ||||||
| pub fn find_file(dir: &str, filename: &str) -> Result<String, Box<EvalAltResult>> { | pub fn find_file(dir: &str, filename: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::find_file(dir, filename).to_rhai_error() |     fs::find_file(dir, filename).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::find_files
 | /// Wrapper for fs::find_files
 | ||||||
| ///
 | ///
 | ||||||
| /// Find multiple files in a directory (recursive, with support for wildcards).
 | /// Find multiple files in a directory (recursive, with support for wildcards).
 | ||||||
| pub fn find_files(dir: &str, filename: &str) -> Result<Array, Box<EvalAltResult>> { | pub fn find_files(dir: &str, filename: &str) -> Result<Array, Box<EvalAltResult>> { | ||||||
|     let files = os::find_files(dir, filename).to_rhai_error()?; |     let files = fs::find_files(dir, filename).to_rhai_error()?; | ||||||
|     
 | 
 | ||||||
|     // Convert Vec<String> to Rhai Array
 |     // Convert Vec<String> to Rhai Array
 | ||||||
|     let mut array = Array::new(); |     let mut array = Array::new(); | ||||||
|     for file in files { |     for file in files { | ||||||
|         array.push(file.into()); |         array.push(file.into()); | ||||||
|     } |     } | ||||||
|     
 | 
 | ||||||
|     Ok(array) |     Ok(array) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::find_dir
 | /// Wrapper for fs::find_dir
 | ||||||
| ///
 | ///
 | ||||||
| /// Find a directory in a parent directory (with support for wildcards).
 | /// Find a directory in a parent directory (with support for wildcards).
 | ||||||
| pub fn find_dir(dir: &str, dirname: &str) -> Result<String, Box<EvalAltResult>> { | pub fn find_dir(dir: &str, dirname: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::find_dir(dir, dirname).to_rhai_error() |     fs::find_dir(dir, dirname).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::find_dirs
 | /// Wrapper for fs::find_dirs
 | ||||||
| ///
 | ///
 | ||||||
| /// Find multiple directories in a parent directory (recursive, with support for wildcards).
 | /// Find multiple directories in a parent directory (recursive, with support for wildcards).
 | ||||||
| pub fn find_dirs(dir: &str, dirname: &str) -> Result<Array, Box<EvalAltResult>> { | pub fn find_dirs(dir: &str, dirname: &str) -> Result<Array, Box<EvalAltResult>> { | ||||||
|     let dirs = os::find_dirs(dir, dirname).to_rhai_error()?; |     let dirs = fs::find_dirs(dir, dirname).to_rhai_error()?; | ||||||
|     
 | 
 | ||||||
|     // Convert Vec<String> to Rhai Array
 |     // Convert Vec<String> to Rhai Array
 | ||||||
|     let mut array = Array::new(); |     let mut array = Array::new(); | ||||||
|     for dir in dirs { |     for dir in dirs { | ||||||
|         array.push(dir.into()); |         array.push(dir.into()); | ||||||
|     } |     } | ||||||
|     
 | 
 | ||||||
|     Ok(array) |     Ok(array) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::delete
 | /// Wrapper for fs::delete
 | ||||||
| ///
 | ///
 | ||||||
| /// Delete a file or directory (defensive - doesn't error if file doesn't exist).
 | /// Delete a file or directory (defensive - doesn't error if file doesn't exist).
 | ||||||
| pub fn delete(path: &str) -> Result<String, Box<EvalAltResult>> { | pub fn delete(path: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::delete(path).to_rhai_error() |     fs::delete(path).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::mkdir
 | /// Wrapper for fs::mkdir
 | ||||||
| ///
 | ///
 | ||||||
| /// Create a directory and all parent directories (defensive - doesn't error if directory exists).
 | /// Create a directory and all parent directories (defensive - doesn't error if directory exists).
 | ||||||
| pub fn mkdir(path: &str) -> Result<String, Box<EvalAltResult>> { | pub fn mkdir(path: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::mkdir(path).to_rhai_error() |     fs::mkdir(path).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::file_size
 | /// Wrapper for fs::file_size
 | ||||||
| ///
 | ///
 | ||||||
| /// Get the size of a file in bytes.
 | /// Get the size of a file in bytes.
 | ||||||
| pub fn file_size(path: &str) -> Result<i64, Box<EvalAltResult>> { | pub fn file_size(path: &str) -> Result<i64, Box<EvalAltResult>> { | ||||||
|     os::file_size(path).to_rhai_error() |     fs::file_size(path).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::rsync
 | /// Wrapper for fs::rsync
 | ||||||
| ///
 | ///
 | ||||||
| /// Sync directories using rsync (or platform equivalent).
 | /// Sync directories using rsync (or platform equivalent).
 | ||||||
| pub fn rsync(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> { | pub fn rsync(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::rsync(src, dest).to_rhai_error() |     fs::rsync(src, dest).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::chdir
 | /// Wrapper for fs::chdir
 | ||||||
| ///
 | ///
 | ||||||
| /// Change the current working directory.
 | /// Change the current working directory.
 | ||||||
| pub fn chdir(path: &str) -> Result<String, Box<EvalAltResult>> { | pub fn chdir(path: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::chdir(path).to_rhai_error() |     fs::chdir(path).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::file_read
 | /// Wrapper for fs::file_read
 | ||||||
| ///
 | ///
 | ||||||
| /// Read the contents of a file.
 | /// Read the contents of a file.
 | ||||||
| pub fn file_read(path: &str) -> Result<String, Box<EvalAltResult>> { | pub fn file_read(path: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::file_read(path).to_rhai_error() |     fs::file_read(path).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::file_write
 | /// Wrapper for fs::file_write
 | ||||||
| ///
 | ///
 | ||||||
| /// Write content to a file (creates the file if it doesn't exist, overwrites if it does).
 | /// Write content to a file (creates the file if it doesn't exist, overwrites if it does).
 | ||||||
| pub fn file_write(path: &str, content: &str) -> Result<String, Box<EvalAltResult>> { | pub fn file_write(path: &str, content: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::file_write(path, content).to_rhai_error() |     fs::file_write(path, content).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::file_write_append
 | /// Wrapper for fs::file_write_append
 | ||||||
| ///
 | ///
 | ||||||
| /// Append content to a file (creates the file if it doesn't exist).
 | /// Append content to a file (creates the file if it doesn't exist).
 | ||||||
| pub fn file_write_append(path: &str, content: &str) -> Result<String, Box<EvalAltResult>> { | pub fn file_write_append(path: &str, content: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::file_write_append(path, content).to_rhai_error() |     fs::file_write_append(path, content).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::mv
 | /// Wrapper for fs::mv
 | ||||||
| ///
 | ///
 | ||||||
| /// Move a file or directory from source to destination.
 | /// Move a file or directory from source to destination.
 | ||||||
| pub fn mv(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> { | pub fn mv(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::mv(src, dest).to_rhai_error() |     fs::mv(src, dest).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| //
 | //
 | ||||||
| @@ -204,35 +224,39 @@ pub fn mv(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> { | |||||||
| ///
 | ///
 | ||||||
| /// Download a file from URL to destination using the curl command.
 | /// Download a file from URL to destination using the curl command.
 | ||||||
| pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Box<EvalAltResult>> { | pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::download(url, dest, min_size_kb).to_rhai_error() |     dl::download(url, dest, min_size_kb).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::download_file
 | /// Wrapper for os::download_file
 | ||||||
| ///
 | ///
 | ||||||
| /// Download a file from URL to a specific file destination using the curl command.
 | /// Download a file from URL to a specific file destination using the curl command.
 | ||||||
| pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Box<EvalAltResult>> { | pub fn download_file( | ||||||
|     os::download_file(url, dest, min_size_kb).to_rhai_error() |     url: &str, | ||||||
|  |     dest: &str, | ||||||
|  |     min_size_kb: i64, | ||||||
|  | ) -> Result<String, Box<EvalAltResult>> { | ||||||
|  |     dl::download_file(url, dest, min_size_kb).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::download_install
 | /// Wrapper for os::download_install
 | ||||||
| ///
 | ///
 | ||||||
| /// Download a file and install it if it's a supported package format.
 | /// Download a file and install it if it's a supported package format.
 | ||||||
| pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, Box<EvalAltResult>> { | pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::download_install(url, min_size_kb).to_rhai_error() |     dl::download_install(url, min_size_kb).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::chmod_exec
 | /// Wrapper for os::chmod_exec
 | ||||||
| ///
 | ///
 | ||||||
| /// Make a file executable (equivalent to chmod +x).
 | /// Make a file executable (equivalent to chmod +x).
 | ||||||
| pub fn chmod_exec(path: &str) -> Result<String, Box<EvalAltResult>> { | pub fn chmod_exec(path: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::chmod_exec(path).to_rhai_error() |     dl::chmod_exec(path).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::which
 | /// Wrapper for os::which
 | ||||||
| ///
 | ///
 | ||||||
| /// Check if a command exists in the system PATH.
 | /// Check if a command exists in the system PATH.
 | ||||||
| pub fn which(command: &str) -> String { | pub fn which(command: &str) -> String { | ||||||
|     os::which(command) |     fs::which(command) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Wrapper for os::cmd_ensure_exists
 | /// Wrapper for os::cmd_ensure_exists
 | ||||||
| @@ -240,7 +264,7 @@ pub fn which(command: &str) -> String { | |||||||
| /// Ensure that one or more commands exist in the system PATH.
 | /// Ensure that one or more commands exist in the system PATH.
 | ||||||
| /// If any command doesn't exist, an error is thrown.
 | /// If any command doesn't exist, an error is thrown.
 | ||||||
| pub fn cmd_ensure_exists(commands: &str) -> Result<String, Box<EvalAltResult>> { | pub fn cmd_ensure_exists(commands: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|     os::cmd_ensure_exists(commands).to_rhai_error() |     fs::cmd_ensure_exists(commands).to_rhai_error() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| //
 | //
 | ||||||
| @@ -293,13 +317,13 @@ pub fn package_upgrade() -> Result<String, Box<EvalAltResult>> { | |||||||
| pub fn package_list() -> Result<Array, Box<EvalAltResult>> { | pub fn package_list() -> Result<Array, Box<EvalAltResult>> { | ||||||
|     let hero = PackHero::new(); |     let hero = PackHero::new(); | ||||||
|     let packages = hero.list_installed().to_rhai_error()?; |     let packages = hero.list_installed().to_rhai_error()?; | ||||||
|     
 | 
 | ||||||
|     // Convert Vec<String> to Rhai Array
 |     // Convert Vec<String> to Rhai Array
 | ||||||
|     let mut array = Array::new(); |     let mut array = Array::new(); | ||||||
|     for package in packages { |     for package in packages { | ||||||
|         array.push(package.into()); |         array.push(package.into()); | ||||||
|     } |     } | ||||||
|     
 | 
 | ||||||
|     Ok(array) |     Ok(array) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -309,13 +333,13 @@ pub fn package_list() -> Result<Array, Box<EvalAltResult>> { | |||||||
| pub fn package_search(query: &str) -> Result<Array, Box<EvalAltResult>> { | pub fn package_search(query: &str) -> Result<Array, Box<EvalAltResult>> { | ||||||
|     let hero = PackHero::new(); |     let hero = PackHero::new(); | ||||||
|     let packages = hero.search(query).to_rhai_error()?; |     let packages = hero.search(query).to_rhai_error()?; | ||||||
|     
 | 
 | ||||||
|     // Convert Vec<String> to Rhai Array
 |     // Convert Vec<String> to Rhai Array
 | ||||||
|     let mut array = Array::new(); |     let mut array = Array::new(); | ||||||
|     for package in packages { |     for package in packages { | ||||||
|         array.push(package.into()); |         array.push(package.into()); | ||||||
|     } |     } | ||||||
|     
 | 
 | ||||||
|     Ok(array) |     Ok(array) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -336,12 +360,12 @@ thread_local! { | |||||||
| pub fn package_set_debug(debug: bool) -> bool { | pub fn package_set_debug(debug: bool) -> bool { | ||||||
|     let mut hero = PackHero::new(); |     let mut hero = PackHero::new(); | ||||||
|     hero.set_debug(debug); |     hero.set_debug(debug); | ||||||
|     
 | 
 | ||||||
|     // Also set the thread-local debug flag
 |     // Also set the thread-local debug flag
 | ||||||
|     PACKAGE_DEBUG.with(|cell| { |     PACKAGE_DEBUG.with(|cell| { | ||||||
|         *cell.borrow_mut() = debug; |         *cell.borrow_mut() = debug; | ||||||
|     }); |     }); | ||||||
|     
 | 
 | ||||||
|     debug |     debug | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -349,8 +373,52 @@ pub fn package_set_debug(debug: bool) -> bool { | |||||||
| pub fn package_platform() -> String { | pub fn package_platform() -> String { | ||||||
|     let hero = PackHero::new(); |     let hero = PackHero::new(); | ||||||
|     match hero.platform() { |     match hero.platform() { | ||||||
|         os::package::Platform::Ubuntu => "Ubuntu".to_string(), |         package::Platform::Ubuntu => "Ubuntu".to_string(), | ||||||
|         os::package::Platform::MacOS => "MacOS".to_string(), |         package::Platform::MacOS => "MacOS".to_string(), | ||||||
|         os::package::Platform::Unknown => "Unknown".to_string(), |         package::Platform::Unknown => "Unknown".to_string(), | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // Platform Detection Function Wrappers
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | /// Wrapper for platform::is_osx
 | ||||||
|  | pub fn platform_is_osx() -> bool { | ||||||
|  |     crate::platform::is_osx() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Wrapper for platform::is_linux
 | ||||||
|  | pub fn platform_is_linux() -> bool { | ||||||
|  |     crate::platform::is_linux() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Wrapper for platform::is_arm
 | ||||||
|  | pub fn platform_is_arm() -> bool { | ||||||
|  |     crate::platform::is_arm() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Wrapper for platform::is_x86
 | ||||||
|  | pub fn platform_is_x86() -> bool { | ||||||
|  |     crate::platform::is_x86() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Wrapper for platform::check_linux_x86
 | ||||||
|  | pub fn platform_check_linux_x86() -> Result<(), Box<EvalAltResult>> { | ||||||
|  |     crate::platform::check_linux_x86().map_err(|e| { | ||||||
|  |         Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Platform Check Error: {}", e).into(), | ||||||
|  |             Position::NONE, | ||||||
|  |         )) | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Wrapper for platform::check_macos_arm
 | ||||||
|  | pub fn platform_check_macos_arm() -> Result<(), Box<EvalAltResult>> { | ||||||
|  |     crate::platform::check_macos_arm().map_err(|e| { | ||||||
|  |         Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Platform Check Error: {}", e).into(), | ||||||
|  |             Position::NONE, | ||||||
|  |         )) | ||||||
|  |     }) | ||||||
|  | } | ||||||
							
								
								
									
										208
									
								
								os/tests/download_tests.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								os/tests/download_tests.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,208 @@ | |||||||
|  | use sal_os::{download, DownloadError}; | ||||||
|  | use std::fs; | ||||||
|  | use tempfile::TempDir; | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_chmod_exec() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let test_file = temp_dir.path().join("test_script.sh"); | ||||||
|  |  | ||||||
|  |     // Create a test file | ||||||
|  |     fs::write(&test_file, "#!/bin/bash\necho 'test'").unwrap(); | ||||||
|  |  | ||||||
|  |     // Make it executable | ||||||
|  |     let result = download::chmod_exec(test_file.to_str().unwrap()); | ||||||
|  |     assert!(result.is_ok()); | ||||||
|  |  | ||||||
|  |     // Check if file is executable (Unix only) | ||||||
|  |     #[cfg(unix)] | ||||||
|  |     { | ||||||
|  |         use std::os::unix::fs::PermissionsExt; | ||||||
|  |         let metadata = fs::metadata(&test_file).unwrap(); | ||||||
|  |         let permissions = metadata.permissions(); | ||||||
|  |         assert!(permissions.mode() & 0o111 != 0); // Check if any execute bit is set | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_download_error_handling() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |  | ||||||
|  |     // Test with invalid URL | ||||||
|  |     let result = download::download("invalid-url", temp_dir.path().to_str().unwrap(), 0); | ||||||
|  |     assert!(result.is_err()); | ||||||
|  |  | ||||||
|  |     // Test with non-existent domain | ||||||
|  |     let result = download::download( | ||||||
|  |         "https://nonexistentdomain12345.com/file.txt", | ||||||
|  |         temp_dir.path().to_str().unwrap(), | ||||||
|  |         0, | ||||||
|  |     ); | ||||||
|  |     assert!(result.is_err()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_download_file_error_handling() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let dest_file = temp_dir.path().join("downloaded_file.txt"); | ||||||
|  |  | ||||||
|  |     // Test with invalid URL | ||||||
|  |     let result = download::download_file("invalid-url", dest_file.to_str().unwrap(), 0); | ||||||
|  |     assert!(result.is_err()); | ||||||
|  |  | ||||||
|  |     // Test with non-existent domain | ||||||
|  |     let result = download::download_file( | ||||||
|  |         "https://nonexistentdomain12345.com/file.txt", | ||||||
|  |         dest_file.to_str().unwrap(), | ||||||
|  |         0, | ||||||
|  |     ); | ||||||
|  |     assert!(result.is_err()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_download_install_error_handling() { | ||||||
|  |     // Test with invalid URL | ||||||
|  |     let result = download::download_install("invalid-url", 0); | ||||||
|  |     assert!(result.is_err()); | ||||||
|  |  | ||||||
|  |     // Test with non-existent domain | ||||||
|  |     let result = download::download_install("https://nonexistentdomain12345.com/package.deb", 0); | ||||||
|  |     assert!(result.is_err()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_download_minimum_size_validation() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |  | ||||||
|  |     // Test with a very high minimum size requirement that won't be met | ||||||
|  |     // This should fail even if the URL exists | ||||||
|  |     let result = download::download( | ||||||
|  |         "https://httpbin.org/bytes/10", // This returns only 10 bytes | ||||||
|  |         temp_dir.path().to_str().unwrap(), | ||||||
|  |         1000, // Require 1000KB minimum | ||||||
|  |     ); | ||||||
|  |     // This might succeed or fail depending on network, but we're testing the interface | ||||||
|  |     // The important thing is that it doesn't panic | ||||||
|  |     let _ = result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_download_to_nonexistent_directory() { | ||||||
|  |     // Test downloading to a directory that doesn't exist | ||||||
|  |     // The download function should create parent directories | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let nonexistent_dir = temp_dir.path().join("nonexistent").join("nested"); | ||||||
|  |  | ||||||
|  |     let _ = download::download( | ||||||
|  |         "https://httpbin.org/status/404", // This will fail, but directory creation should work | ||||||
|  |         nonexistent_dir.to_str().unwrap(), | ||||||
|  |         0, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     // The directory should be created even if download fails | ||||||
|  |     assert!(nonexistent_dir.exists()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_chmod_exec_nonexistent_file() { | ||||||
|  |     // Test chmod_exec on a file that doesn't exist | ||||||
|  |     let result = download::chmod_exec("/nonexistent/path/file.sh"); | ||||||
|  |     assert!(result.is_err()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_download_file_path_validation() { | ||||||
|  |     let _ = TempDir::new().unwrap(); | ||||||
|  |  | ||||||
|  |     // Test with invalid destination path | ||||||
|  |     let result = download::download_file( | ||||||
|  |         "https://httpbin.org/status/404", | ||||||
|  |         "/invalid/path/that/does/not/exist/file.txt", | ||||||
|  |         0, | ||||||
|  |     ); | ||||||
|  |     assert!(result.is_err()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Integration test that requires network access | ||||||
|  | // This test is marked with ignore so it doesn't run by default | ||||||
|  | #[test] | ||||||
|  | #[ignore] | ||||||
|  | fn test_download_real_file() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |  | ||||||
|  |     // Download a small file from httpbin (a testing service) | ||||||
|  |     let result = download::download( | ||||||
|  |         "https://httpbin.org/bytes/100", // Returns 100 random bytes | ||||||
|  |         temp_dir.path().to_str().unwrap(), | ||||||
|  |         0, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     if result.is_ok() { | ||||||
|  |         // If download succeeded, verify the file exists | ||||||
|  |         let downloaded_path = result.unwrap(); | ||||||
|  |         assert!(fs::metadata(&downloaded_path).is_ok()); | ||||||
|  |  | ||||||
|  |         // Verify file size is approximately correct | ||||||
|  |         let metadata = fs::metadata(&downloaded_path).unwrap(); | ||||||
|  |         assert!(metadata.len() >= 90 && metadata.len() <= 110); // Allow some variance | ||||||
|  |     } | ||||||
|  |     // If download failed (network issues), that's okay for this test | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Integration test for download_file | ||||||
|  | #[test] | ||||||
|  | #[ignore] | ||||||
|  | fn test_download_file_real() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let dest_file = temp_dir.path().join("test_download.bin"); | ||||||
|  |  | ||||||
|  |     // Download a small file to specific location | ||||||
|  |     let result = download::download_file( | ||||||
|  |         "https://httpbin.org/bytes/50", | ||||||
|  |         dest_file.to_str().unwrap(), | ||||||
|  |         0, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     if result.is_ok() { | ||||||
|  |         // Verify the file was created at the specified location | ||||||
|  |         assert!(dest_file.exists()); | ||||||
|  |  | ||||||
|  |         // Verify file size | ||||||
|  |         let metadata = fs::metadata(&dest_file).unwrap(); | ||||||
|  |         assert!(metadata.len() >= 40 && metadata.len() <= 60); // Allow some variance | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_download_error_types() { | ||||||
|  |     // DownloadError is already imported at the top | ||||||
|  |  | ||||||
|  |     // Test that our error types can be created and displayed | ||||||
|  |     let error = DownloadError::InvalidUrl("test".to_string()); | ||||||
|  |     assert!(!error.to_string().is_empty()); | ||||||
|  |  | ||||||
|  |     let error = DownloadError::DownloadFailed("test".to_string()); | ||||||
|  |     assert!(!error.to_string().is_empty()); | ||||||
|  |  | ||||||
|  |     let error = DownloadError::FileTooSmall(50, 100); | ||||||
|  |     assert!(!error.to_string().is_empty()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_download_url_parsing() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |  | ||||||
|  |     // Test with URL that has no filename | ||||||
|  |     let result = download::download("https://example.com/", temp_dir.path().to_str().unwrap(), 0); | ||||||
|  |     // Should fail with invalid URL error | ||||||
|  |     assert!(result.is_err()); | ||||||
|  |  | ||||||
|  |     // Test with URL that has query parameters | ||||||
|  |     let result = download::download( | ||||||
|  |         "https://httpbin.org/get?param=value", | ||||||
|  |         temp_dir.path().to_str().unwrap(), | ||||||
|  |         0, | ||||||
|  |     ); | ||||||
|  |     // This might succeed or fail depending on network, but shouldn't panic | ||||||
|  |     let _ = result; | ||||||
|  | } | ||||||
							
								
								
									
										212
									
								
								os/tests/fs_tests.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								os/tests/fs_tests.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,212 @@ | |||||||
|  | use sal_os::fs; | ||||||
|  | use std::fs as std_fs; | ||||||
|  | use tempfile::TempDir; | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_exist() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let temp_path = temp_dir.path(); | ||||||
|  |      | ||||||
|  |     // Test directory exists | ||||||
|  |     assert!(fs::exist(temp_path.to_str().unwrap())); | ||||||
|  |      | ||||||
|  |     // Test file doesn't exist | ||||||
|  |     let non_existent = temp_path.join("non_existent.txt"); | ||||||
|  |     assert!(!fs::exist(non_existent.to_str().unwrap())); | ||||||
|  |      | ||||||
|  |     // Create a file and test it exists | ||||||
|  |     let test_file = temp_path.join("test.txt"); | ||||||
|  |     std_fs::write(&test_file, "test content").unwrap(); | ||||||
|  |     assert!(fs::exist(test_file.to_str().unwrap())); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_mkdir() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let new_dir = temp_dir.path().join("new_directory"); | ||||||
|  |      | ||||||
|  |     // Directory shouldn't exist initially | ||||||
|  |     assert!(!fs::exist(new_dir.to_str().unwrap())); | ||||||
|  |      | ||||||
|  |     // Create directory | ||||||
|  |     let result = fs::mkdir(new_dir.to_str().unwrap()); | ||||||
|  |     assert!(result.is_ok()); | ||||||
|  |      | ||||||
|  |     // Directory should now exist | ||||||
|  |     assert!(fs::exist(new_dir.to_str().unwrap())); | ||||||
|  |      | ||||||
|  |     // Creating existing directory should not error (defensive) | ||||||
|  |     let result2 = fs::mkdir(new_dir.to_str().unwrap()); | ||||||
|  |     assert!(result2.is_ok()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_file_write_and_read() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let test_file = temp_dir.path().join("test_write.txt"); | ||||||
|  |     let content = "Hello, World!"; | ||||||
|  |      | ||||||
|  |     // Write file | ||||||
|  |     let write_result = fs::file_write(test_file.to_str().unwrap(), content); | ||||||
|  |     assert!(write_result.is_ok()); | ||||||
|  |      | ||||||
|  |     // File should exist | ||||||
|  |     assert!(fs::exist(test_file.to_str().unwrap())); | ||||||
|  |      | ||||||
|  |     // Read file | ||||||
|  |     let read_result = fs::file_read(test_file.to_str().unwrap()); | ||||||
|  |     assert!(read_result.is_ok()); | ||||||
|  |     assert_eq!(read_result.unwrap(), content); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_file_write_append() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let test_file = temp_dir.path().join("test_append.txt"); | ||||||
|  |      | ||||||
|  |     // Write initial content | ||||||
|  |     let initial_content = "Line 1\n"; | ||||||
|  |     let append_content = "Line 2\n"; | ||||||
|  |      | ||||||
|  |     let write_result = fs::file_write(test_file.to_str().unwrap(), initial_content); | ||||||
|  |     assert!(write_result.is_ok()); | ||||||
|  |      | ||||||
|  |     // Append content | ||||||
|  |     let append_result = fs::file_write_append(test_file.to_str().unwrap(), append_content); | ||||||
|  |     assert!(append_result.is_ok()); | ||||||
|  |      | ||||||
|  |     // Read and verify | ||||||
|  |     let read_result = fs::file_read(test_file.to_str().unwrap()); | ||||||
|  |     assert!(read_result.is_ok()); | ||||||
|  |     assert_eq!(read_result.unwrap(), format!("{}{}", initial_content, append_content)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_file_size() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let test_file = temp_dir.path().join("test_size.txt"); | ||||||
|  |     let content = "Hello, World!"; // 13 bytes | ||||||
|  |      | ||||||
|  |     // Write file | ||||||
|  |     fs::file_write(test_file.to_str().unwrap(), content).unwrap(); | ||||||
|  |      | ||||||
|  |     // Check size | ||||||
|  |     let size_result = fs::file_size(test_file.to_str().unwrap()); | ||||||
|  |     assert!(size_result.is_ok()); | ||||||
|  |     assert_eq!(size_result.unwrap(), 13); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_delete() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let test_file = temp_dir.path().join("test_delete.txt"); | ||||||
|  |      | ||||||
|  |     // Create file | ||||||
|  |     fs::file_write(test_file.to_str().unwrap(), "test").unwrap(); | ||||||
|  |     assert!(fs::exist(test_file.to_str().unwrap())); | ||||||
|  |      | ||||||
|  |     // Delete file | ||||||
|  |     let delete_result = fs::delete(test_file.to_str().unwrap()); | ||||||
|  |     assert!(delete_result.is_ok()); | ||||||
|  |      | ||||||
|  |     // File should no longer exist | ||||||
|  |     assert!(!fs::exist(test_file.to_str().unwrap())); | ||||||
|  |      | ||||||
|  |     // Deleting non-existent file should not error (defensive) | ||||||
|  |     let delete_result2 = fs::delete(test_file.to_str().unwrap()); | ||||||
|  |     assert!(delete_result2.is_ok()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_copy() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let source_file = temp_dir.path().join("source.txt"); | ||||||
|  |     let dest_file = temp_dir.path().join("dest.txt"); | ||||||
|  |     let content = "Copy test content"; | ||||||
|  |      | ||||||
|  |     // Create source file | ||||||
|  |     fs::file_write(source_file.to_str().unwrap(), content).unwrap(); | ||||||
|  |      | ||||||
|  |     // Copy file | ||||||
|  |     let copy_result = fs::copy(source_file.to_str().unwrap(), dest_file.to_str().unwrap()); | ||||||
|  |     assert!(copy_result.is_ok()); | ||||||
|  |      | ||||||
|  |     // Destination should exist and have same content | ||||||
|  |     assert!(fs::exist(dest_file.to_str().unwrap())); | ||||||
|  |     let dest_content = fs::file_read(dest_file.to_str().unwrap()).unwrap(); | ||||||
|  |     assert_eq!(dest_content, content); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_mv() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let source_file = temp_dir.path().join("source_mv.txt"); | ||||||
|  |     let dest_file = temp_dir.path().join("dest_mv.txt"); | ||||||
|  |     let content = "Move test content"; | ||||||
|  |      | ||||||
|  |     // Create source file | ||||||
|  |     fs::file_write(source_file.to_str().unwrap(), content).unwrap(); | ||||||
|  |      | ||||||
|  |     // Move file | ||||||
|  |     let mv_result = fs::mv(source_file.to_str().unwrap(), dest_file.to_str().unwrap()); | ||||||
|  |     assert!(mv_result.is_ok()); | ||||||
|  |      | ||||||
|  |     // Source should no longer exist, destination should exist | ||||||
|  |     assert!(!fs::exist(source_file.to_str().unwrap())); | ||||||
|  |     assert!(fs::exist(dest_file.to_str().unwrap())); | ||||||
|  |      | ||||||
|  |     // Destination should have same content | ||||||
|  |     let dest_content = fs::file_read(dest_file.to_str().unwrap()).unwrap(); | ||||||
|  |     assert_eq!(dest_content, content); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_which() { | ||||||
|  |     // Test with a command that should exist on most systems | ||||||
|  |     let result = fs::which("ls"); | ||||||
|  |     assert!(!result.is_empty()); | ||||||
|  |      | ||||||
|  |     // Test with a command that shouldn't exist | ||||||
|  |     let result = fs::which("nonexistentcommand12345"); | ||||||
|  |     assert!(result.is_empty()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_find_files() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let temp_path = temp_dir.path(); | ||||||
|  |      | ||||||
|  |     // Create test files | ||||||
|  |     fs::file_write(&temp_path.join("test1.txt").to_string_lossy(), "content1").unwrap(); | ||||||
|  |     fs::file_write(&temp_path.join("test2.txt").to_string_lossy(), "content2").unwrap(); | ||||||
|  |     fs::file_write(&temp_path.join("other.log").to_string_lossy(), "log content").unwrap(); | ||||||
|  |      | ||||||
|  |     // Find .txt files | ||||||
|  |     let txt_files = fs::find_files(temp_path.to_str().unwrap(), "*.txt"); | ||||||
|  |     assert!(txt_files.is_ok()); | ||||||
|  |     let files = txt_files.unwrap(); | ||||||
|  |     assert_eq!(files.len(), 2); | ||||||
|  |      | ||||||
|  |     // Find all files | ||||||
|  |     let all_files = fs::find_files(temp_path.to_str().unwrap(), "*"); | ||||||
|  |     assert!(all_files.is_ok()); | ||||||
|  |     let files = all_files.unwrap(); | ||||||
|  |     assert!(files.len() >= 3); // At least our 3 files | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_find_dirs() { | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let temp_path = temp_dir.path(); | ||||||
|  |      | ||||||
|  |     // Create test directories | ||||||
|  |     fs::mkdir(&temp_path.join("dir1").to_string_lossy()).unwrap(); | ||||||
|  |     fs::mkdir(&temp_path.join("dir2").to_string_lossy()).unwrap(); | ||||||
|  |     fs::mkdir(&temp_path.join("subdir").to_string_lossy()).unwrap(); | ||||||
|  |      | ||||||
|  |     // Find directories | ||||||
|  |     let dirs = fs::find_dirs(temp_path.to_str().unwrap(), "dir*"); | ||||||
|  |     assert!(dirs.is_ok()); | ||||||
|  |     let found_dirs = dirs.unwrap(); | ||||||
|  |     assert!(found_dirs.len() >= 2); // At least dir1 and dir2 | ||||||
|  | } | ||||||
							
								
								
									
										366
									
								
								os/tests/package_tests.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										366
									
								
								os/tests/package_tests.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,366 @@ | |||||||
|  | use sal_os::package::{PackHero, Platform}; | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_pack_hero_creation() { | ||||||
|  |     // Test that we can create a PackHero instance | ||||||
|  |     let hero = PackHero::new(); | ||||||
|  |  | ||||||
|  |     // Test that platform detection works | ||||||
|  |     let platform = hero.platform(); | ||||||
|  |     match platform { | ||||||
|  |         Platform::Ubuntu | Platform::MacOS | Platform::Unknown => { | ||||||
|  |             // All valid platforms | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_platform_detection() { | ||||||
|  |     let hero = PackHero::new(); | ||||||
|  |     let platform = hero.platform(); | ||||||
|  |  | ||||||
|  |     // Platform should be deterministic | ||||||
|  |     let platform2 = hero.platform(); | ||||||
|  |     assert_eq!(format!("{:?}", platform), format!("{:?}", platform2)); | ||||||
|  |  | ||||||
|  |     // Test platform display | ||||||
|  |     match platform { | ||||||
|  |         Platform::Ubuntu => { | ||||||
|  |             assert_eq!(format!("{:?}", platform), "Ubuntu"); | ||||||
|  |         } | ||||||
|  |         Platform::MacOS => { | ||||||
|  |             assert_eq!(format!("{:?}", platform), "MacOS"); | ||||||
|  |         } | ||||||
|  |         Platform::Unknown => { | ||||||
|  |             assert_eq!(format!("{:?}", platform), "Unknown"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_debug_mode() { | ||||||
|  |     let mut hero = PackHero::new(); | ||||||
|  |  | ||||||
|  |     // Test setting debug mode | ||||||
|  |     hero.set_debug(true); | ||||||
|  |     hero.set_debug(false); | ||||||
|  |  | ||||||
|  |     // Debug mode setting should not panic | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_package_operations_error_handling() { | ||||||
|  |     let hero = PackHero::new(); | ||||||
|  |  | ||||||
|  |     // Test with invalid package name | ||||||
|  |     let result = hero.is_installed("nonexistent-package-12345-xyz"); | ||||||
|  |     // This should return a result (either Ok(false) or Err) | ||||||
|  |     // Validate that we get a proper result type | ||||||
|  |     match result { | ||||||
|  |         Ok(is_installed) => { | ||||||
|  |             // Should return false for non-existent package | ||||||
|  |             assert!( | ||||||
|  |                 !is_installed, | ||||||
|  |                 "Non-existent package should not be reported as installed" | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |         Err(_) => { | ||||||
|  |             // Error is also acceptable (e.g., no package manager available) | ||||||
|  |             // The important thing is it doesn't panic | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Test install with invalid package | ||||||
|  |     let result = hero.install("nonexistent-package-12345-xyz"); | ||||||
|  |     // This should return an error | ||||||
|  |     assert!(result.is_err()); | ||||||
|  |  | ||||||
|  |     // Test remove with invalid package | ||||||
|  |     let result = hero.remove("nonexistent-package-12345-xyz"); | ||||||
|  |     // This might succeed (if package wasn't installed) or fail | ||||||
|  |     // Validate that we get a proper result type | ||||||
|  |     match result { | ||||||
|  |         Ok(_) => { | ||||||
|  |             // Success is acceptable (package wasn't installed) | ||||||
|  |         } | ||||||
|  |         Err(err) => { | ||||||
|  |             // Error is also acceptable | ||||||
|  |             // Verify error message is meaningful | ||||||
|  |             let error_msg = err.to_string(); | ||||||
|  |             assert!(!error_msg.is_empty(), "Error message should not be empty"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_package_search_basic() { | ||||||
|  |     let hero = PackHero::new(); | ||||||
|  |  | ||||||
|  |     // Test search with empty query | ||||||
|  |     let result = hero.search(""); | ||||||
|  |     // Should handle empty query gracefully | ||||||
|  |     // Validate that we get a proper result type | ||||||
|  |     match result { | ||||||
|  |         Ok(packages) => { | ||||||
|  |             // Empty search might return all packages or empty list | ||||||
|  |             // Verify the result is a valid vector | ||||||
|  |             assert!( | ||||||
|  |                 packages.len() < 50000, | ||||||
|  |                 "Empty search returned unreasonably large result" | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |         Err(err) => { | ||||||
|  |             // Error is acceptable for empty query | ||||||
|  |             let error_msg = err.to_string(); | ||||||
|  |             assert!(!error_msg.is_empty(), "Error message should not be empty"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Test search with very specific query that likely won't match | ||||||
|  |     let result = hero.search("nonexistent-package-xyz-12345"); | ||||||
|  |     if let Ok(packages) = result { | ||||||
|  |         // If search succeeded, it should return a vector | ||||||
|  |         // The vector should be valid (we can get its length) | ||||||
|  |         let _count = packages.len(); | ||||||
|  |         // Search results should be reasonable (not absurdly large) | ||||||
|  |         assert!( | ||||||
|  |             packages.len() < 10000, | ||||||
|  |             "Search returned unreasonably large result set" | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |     // If search failed, that's also acceptable | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_package_list_basic() { | ||||||
|  |     let hero = PackHero::new(); | ||||||
|  |  | ||||||
|  |     // Test listing installed packages | ||||||
|  |     let result = hero.list_installed(); | ||||||
|  |     if let Ok(packages) = result { | ||||||
|  |         // If listing succeeded, it should return a vector | ||||||
|  |         // On most systems, there should be at least some packages installed | ||||||
|  |         println!("Found {} installed packages", packages.len()); | ||||||
|  |     } | ||||||
|  |     // If listing failed (e.g., no package manager available), that's acceptable | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_package_update_basic() { | ||||||
|  |     let hero = PackHero::new(); | ||||||
|  |  | ||||||
|  |     // Test package list update | ||||||
|  |     let result = hero.update(); | ||||||
|  |     // This might succeed or fail depending on permissions and network | ||||||
|  |     // Validate that we get a proper result type | ||||||
|  |     match result { | ||||||
|  |         Ok(_) => { | ||||||
|  |             // Success is good - package list was updated | ||||||
|  |         } | ||||||
|  |         Err(err) => { | ||||||
|  |             // Error is acceptable (no permissions, no network, etc.) | ||||||
|  |             let error_msg = err.to_string(); | ||||||
|  |             assert!(!error_msg.is_empty(), "Error message should not be empty"); | ||||||
|  |             // Common error patterns we expect | ||||||
|  |             let error_lower = error_msg.to_lowercase(); | ||||||
|  |             assert!( | ||||||
|  |                 error_lower.contains("permission") | ||||||
|  |                     || error_lower.contains("network") | ||||||
|  |                     || error_lower.contains("command") | ||||||
|  |                     || error_lower.contains("not found") | ||||||
|  |                     || error_lower.contains("failed"), | ||||||
|  |                 "Error message should indicate a reasonable failure cause: {}", | ||||||
|  |                 error_msg | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | #[ignore] // Skip by default as this can take a very long time and modify the system | ||||||
|  | fn test_package_upgrade_basic() { | ||||||
|  |     let hero = PackHero::new(); | ||||||
|  |  | ||||||
|  |     // Test package upgrade (this is a real system operation) | ||||||
|  |     let result = hero.upgrade(); | ||||||
|  |     // Validate that we get a proper result type | ||||||
|  |     match result { | ||||||
|  |         Ok(_) => { | ||||||
|  |             // Success means packages were upgraded | ||||||
|  |             println!("Package upgrade completed successfully"); | ||||||
|  |         } | ||||||
|  |         Err(err) => { | ||||||
|  |             // Error is acceptable (no permissions, no packages to upgrade, etc.) | ||||||
|  |             let error_msg = err.to_string(); | ||||||
|  |             assert!(!error_msg.is_empty(), "Error message should not be empty"); | ||||||
|  |             println!("Package upgrade failed as expected: {}", error_msg); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_package_upgrade_interface() { | ||||||
|  |     // Test that the upgrade interface works without actually upgrading | ||||||
|  |     let hero = PackHero::new(); | ||||||
|  |  | ||||||
|  |     // Verify that PackHero has the upgrade method and it returns the right type | ||||||
|  |     // This tests the interface without performing the actual upgrade | ||||||
|  |     let _upgrade_fn = PackHero::upgrade; | ||||||
|  |  | ||||||
|  |     // Test that we can call upgrade (it will likely fail due to permissions/network) | ||||||
|  |     // but we're testing that the interface works correctly | ||||||
|  |     let result = hero.upgrade(); | ||||||
|  |  | ||||||
|  |     // The result should be a proper Result type | ||||||
|  |     match result { | ||||||
|  |         Ok(_) => { | ||||||
|  |             // Upgrade succeeded (unlikely in test environment) | ||||||
|  |         } | ||||||
|  |         Err(err) => { | ||||||
|  |             // Expected in most test environments | ||||||
|  |             // Verify error is meaningful | ||||||
|  |             let error_msg = err.to_string(); | ||||||
|  |             assert!(!error_msg.is_empty(), "Error should have a message"); | ||||||
|  |             assert!(error_msg.len() > 5, "Error message should be descriptive"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Platform-specific tests | ||||||
|  | #[cfg(target_os = "linux")] | ||||||
|  | #[test] | ||||||
|  | fn test_linux_platform_detection() { | ||||||
|  |     let hero = PackHero::new(); | ||||||
|  |     let platform = hero.platform(); | ||||||
|  |  | ||||||
|  |     // On Linux, should detect Ubuntu or Unknown (if not Ubuntu-based) | ||||||
|  |     match platform { | ||||||
|  |         Platform::Ubuntu | Platform::Unknown => { | ||||||
|  |             // Expected on Linux | ||||||
|  |         } | ||||||
|  |         Platform::MacOS => { | ||||||
|  |             panic!("Should not detect macOS on Linux system"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(target_os = "macos")] | ||||||
|  | #[test] | ||||||
|  | fn test_macos_platform_detection() { | ||||||
|  |     let hero = PackHero::new(); | ||||||
|  |     let platform = hero.platform(); | ||||||
|  |  | ||||||
|  |     // On macOS, should detect MacOS | ||||||
|  |     match platform { | ||||||
|  |         Platform::MacOS => { | ||||||
|  |             // Expected on macOS | ||||||
|  |         } | ||||||
|  |         Platform::Ubuntu | Platform::Unknown => { | ||||||
|  |             panic!("Should detect macOS on macOS system, got {:?}", platform); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Integration tests that require actual package managers | ||||||
|  | // These are marked with ignore so they don't run by default | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | #[ignore] | ||||||
|  | fn test_real_package_check() { | ||||||
|  |     let hero = PackHero::new(); | ||||||
|  |  | ||||||
|  |     // Test with a package that's commonly installed | ||||||
|  |     #[cfg(target_os = "linux")] | ||||||
|  |     let test_package = "bash"; | ||||||
|  |  | ||||||
|  |     #[cfg(target_os = "macos")] | ||||||
|  |     let test_package = "bash"; | ||||||
|  |  | ||||||
|  |     #[cfg(not(any(target_os = "linux", target_os = "macos")))] | ||||||
|  |     let test_package = "unknown"; | ||||||
|  |  | ||||||
|  |     let result = hero.is_installed(test_package); | ||||||
|  |     if let Ok(is_installed) = result { | ||||||
|  |         println!("Package '{}' is installed: {}", test_package, is_installed); | ||||||
|  |     } else { | ||||||
|  |         println!( | ||||||
|  |             "Failed to check if '{}' is installed: {:?}", | ||||||
|  |             test_package, result | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | #[ignore] | ||||||
|  | fn test_real_package_search() { | ||||||
|  |     let hero = PackHero::new(); | ||||||
|  |  | ||||||
|  |     // Search for a common package | ||||||
|  |     let result = hero.search("git"); | ||||||
|  |     if let Ok(packages) = result { | ||||||
|  |         println!("Found {} packages matching 'git'", packages.len()); | ||||||
|  |         if !packages.is_empty() { | ||||||
|  |             println!( | ||||||
|  |                 "First few matches: {:?}", | ||||||
|  |                 &packages[..std::cmp::min(5, packages.len())] | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         println!("Package search failed: {:?}", result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | #[ignore] | ||||||
|  | fn test_real_package_list() { | ||||||
|  |     let hero = PackHero::new(); | ||||||
|  |  | ||||||
|  |     // List installed packages | ||||||
|  |     let result = hero.list_installed(); | ||||||
|  |     if let Ok(packages) = result { | ||||||
|  |         println!("Total installed packages: {}", packages.len()); | ||||||
|  |         if !packages.is_empty() { | ||||||
|  |             println!( | ||||||
|  |                 "First few packages: {:?}", | ||||||
|  |                 &packages[..std::cmp::min(10, packages.len())] | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         println!("Package listing failed: {:?}", result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_platform_enum_properties() { | ||||||
|  |     // Test that Platform enum can be compared | ||||||
|  |     assert_eq!(Platform::Ubuntu, Platform::Ubuntu); | ||||||
|  |     assert_eq!(Platform::MacOS, Platform::MacOS); | ||||||
|  |     assert_eq!(Platform::Unknown, Platform::Unknown); | ||||||
|  |  | ||||||
|  |     assert_ne!(Platform::Ubuntu, Platform::MacOS); | ||||||
|  |     assert_ne!(Platform::Ubuntu, Platform::Unknown); | ||||||
|  |     assert_ne!(Platform::MacOS, Platform::Unknown); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_pack_hero_multiple_instances() { | ||||||
|  |     // Test that multiple PackHero instances work correctly | ||||||
|  |     let hero1 = PackHero::new(); | ||||||
|  |     let hero2 = PackHero::new(); | ||||||
|  |  | ||||||
|  |     // Both should detect the same platform | ||||||
|  |     assert_eq!( | ||||||
|  |         format!("{:?}", hero1.platform()), | ||||||
|  |         format!("{:?}", hero2.platform()) | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     // Both should handle debug mode independently | ||||||
|  |     let mut hero1_mut = hero1; | ||||||
|  |     let mut hero2_mut = hero2; | ||||||
|  |  | ||||||
|  |     hero1_mut.set_debug(true); | ||||||
|  |     hero2_mut.set_debug(false); | ||||||
|  |  | ||||||
|  |     // No assertions here since debug mode doesn't have observable effects in tests | ||||||
|  |     // But this ensures the API works correctly | ||||||
|  | } | ||||||
							
								
								
									
										199
									
								
								os/tests/platform_tests.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								os/tests/platform_tests.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,199 @@ | |||||||
|  | use sal_os::platform; | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_platform_detection_consistency() { | ||||||
|  |     // Test that platform detection functions return consistent results | ||||||
|  |     let is_osx = platform::is_osx(); | ||||||
|  |     let is_linux = platform::is_linux(); | ||||||
|  |      | ||||||
|  |     // On any given system, only one of these should be true | ||||||
|  |     // (or both false if running on Windows or other OS) | ||||||
|  |     if is_osx { | ||||||
|  |         assert!(!is_linux, "Cannot be both macOS and Linux"); | ||||||
|  |     } | ||||||
|  |     if is_linux { | ||||||
|  |         assert!(!is_osx, "Cannot be both Linux and macOS"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_architecture_detection_consistency() { | ||||||
|  |     // Test that architecture detection functions return consistent results | ||||||
|  |     let is_arm = platform::is_arm(); | ||||||
|  |     let is_x86 = platform::is_x86(); | ||||||
|  |      | ||||||
|  |     // On any given system, only one of these should be true | ||||||
|  |     // (or both false if running on other architectures) | ||||||
|  |     if is_arm { | ||||||
|  |         assert!(!is_x86, "Cannot be both ARM and x86"); | ||||||
|  |     } | ||||||
|  |     if is_x86 { | ||||||
|  |         assert!(!is_arm, "Cannot be both x86 and ARM"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_platform_functions_return_bool() { | ||||||
|  |     // Test that all platform detection functions return boolean values | ||||||
|  |     let _: bool = platform::is_osx(); | ||||||
|  |     let _: bool = platform::is_linux(); | ||||||
|  |     let _: bool = platform::is_arm(); | ||||||
|  |     let _: bool = platform::is_x86(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(target_os = "macos")] | ||||||
|  | #[test] | ||||||
|  | fn test_macos_detection() { | ||||||
|  |     // When compiled for macOS, is_osx should return true | ||||||
|  |     assert!(platform::is_osx()); | ||||||
|  |     assert!(!platform::is_linux()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(target_os = "linux")] | ||||||
|  | #[test] | ||||||
|  | fn test_linux_detection() { | ||||||
|  |     // When compiled for Linux, is_linux should return true | ||||||
|  |     assert!(platform::is_linux()); | ||||||
|  |     assert!(!platform::is_osx()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(target_arch = "aarch64")] | ||||||
|  | #[test] | ||||||
|  | fn test_arm_detection() { | ||||||
|  |     // When compiled for ARM64, is_arm should return true | ||||||
|  |     assert!(platform::is_arm()); | ||||||
|  |     assert!(!platform::is_x86()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(target_arch = "x86_64")] | ||||||
|  | #[test] | ||||||
|  | fn test_x86_detection() { | ||||||
|  |     // When compiled for x86_64, is_x86 should return true | ||||||
|  |     assert!(platform::is_x86()); | ||||||
|  |     assert!(!platform::is_arm()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_check_linux_x86() { | ||||||
|  |     let result = platform::check_linux_x86(); | ||||||
|  |      | ||||||
|  |     // The result should depend on the current platform | ||||||
|  |     #[cfg(all(target_os = "linux", target_arch = "x86_64"))] | ||||||
|  |     { | ||||||
|  |         assert!(result.is_ok(), "Should succeed on Linux x86_64"); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] | ||||||
|  |     { | ||||||
|  |         assert!(result.is_err(), "Should fail on non-Linux x86_64 platforms"); | ||||||
|  |          | ||||||
|  |         // Check that the error message is meaningful | ||||||
|  |         let error = result.unwrap_err(); | ||||||
|  |         let error_string = error.to_string(); | ||||||
|  |         assert!(error_string.contains("Linux x86_64"),  | ||||||
|  |                 "Error message should mention Linux x86_64: {}", error_string); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_check_macos_arm() { | ||||||
|  |     let result = platform::check_macos_arm(); | ||||||
|  |      | ||||||
|  |     // The result should depend on the current platform | ||||||
|  |     #[cfg(all(target_os = "macos", target_arch = "aarch64"))] | ||||||
|  |     { | ||||||
|  |         assert!(result.is_ok(), "Should succeed on macOS ARM"); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     #[cfg(not(all(target_os = "macos", target_arch = "aarch64")))] | ||||||
|  |     { | ||||||
|  |         assert!(result.is_err(), "Should fail on non-macOS ARM platforms"); | ||||||
|  |          | ||||||
|  |         // Check that the error message is meaningful | ||||||
|  |         let error = result.unwrap_err(); | ||||||
|  |         let error_string = error.to_string(); | ||||||
|  |         assert!(error_string.contains("macOS ARM"),  | ||||||
|  |                 "Error message should mention macOS ARM: {}", error_string); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_platform_error_creation() { | ||||||
|  |     use sal_os::platform::PlatformError; | ||||||
|  |      | ||||||
|  |     // Test that we can create platform errors | ||||||
|  |     let error = PlatformError::new("Test Error", "This is a test error message"); | ||||||
|  |     let error_string = error.to_string(); | ||||||
|  |      | ||||||
|  |     assert!(error_string.contains("Test Error")); | ||||||
|  |     assert!(error_string.contains("This is a test error message")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_platform_error_display() { | ||||||
|  |     use sal_os::platform::PlatformError; | ||||||
|  |      | ||||||
|  |     // Test error display formatting | ||||||
|  |     let error = PlatformError::Generic("Category".to_string(), "Message".to_string()); | ||||||
|  |     let error_string = format!("{}", error); | ||||||
|  |      | ||||||
|  |     assert!(error_string.contains("Category")); | ||||||
|  |     assert!(error_string.contains("Message")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_platform_error_debug() { | ||||||
|  |     use sal_os::platform::PlatformError; | ||||||
|  |      | ||||||
|  |     // Test error debug formatting | ||||||
|  |     let error = PlatformError::Generic("Category".to_string(), "Message".to_string()); | ||||||
|  |     let debug_string = format!("{:?}", error); | ||||||
|  |      | ||||||
|  |     assert!(debug_string.contains("Generic")); | ||||||
|  |     assert!(debug_string.contains("Category")); | ||||||
|  |     assert!(debug_string.contains("Message")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_platform_functions_are_deterministic() { | ||||||
|  |     // Platform detection should be deterministic - same result every time | ||||||
|  |     let osx1 = platform::is_osx(); | ||||||
|  |     let osx2 = platform::is_osx(); | ||||||
|  |     assert_eq!(osx1, osx2); | ||||||
|  |      | ||||||
|  |     let linux1 = platform::is_linux(); | ||||||
|  |     let linux2 = platform::is_linux(); | ||||||
|  |     assert_eq!(linux1, linux2); | ||||||
|  |      | ||||||
|  |     let arm1 = platform::is_arm(); | ||||||
|  |     let arm2 = platform::is_arm(); | ||||||
|  |     assert_eq!(arm1, arm2); | ||||||
|  |      | ||||||
|  |     let x86_1 = platform::is_x86(); | ||||||
|  |     let x86_2 = platform::is_x86(); | ||||||
|  |     assert_eq!(x86_1, x86_2); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_platform_check_functions_consistency() { | ||||||
|  |     // The check functions should be consistent with the individual detection functions | ||||||
|  |     let is_linux_x86 = platform::is_linux() && platform::is_x86(); | ||||||
|  |     let check_linux_x86_result = platform::check_linux_x86().is_ok(); | ||||||
|  |     assert_eq!(is_linux_x86, check_linux_x86_result); | ||||||
|  |      | ||||||
|  |     let is_macos_arm = platform::is_osx() && platform::is_arm(); | ||||||
|  |     let check_macos_arm_result = platform::check_macos_arm().is_ok(); | ||||||
|  |     assert_eq!(is_macos_arm, check_macos_arm_result); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_current_platform_info() { | ||||||
|  |     // Print current platform info for debugging (this will show in test output with --nocapture) | ||||||
|  |     println!("Current platform detection:"); | ||||||
|  |     println!("  is_osx(): {}", platform::is_osx()); | ||||||
|  |     println!("  is_linux(): {}", platform::is_linux()); | ||||||
|  |     println!("  is_arm(): {}", platform::is_arm()); | ||||||
|  |     println!("  is_x86(): {}", platform::is_x86()); | ||||||
|  |     println!("  check_linux_x86(): {:?}", platform::check_linux_x86()); | ||||||
|  |     println!("  check_macos_arm(): {:?}", platform::check_macos_arm()); | ||||||
|  | } | ||||||
							
								
								
									
										364
									
								
								os/tests/rhai_integration_tests.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										364
									
								
								os/tests/rhai_integration_tests.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,364 @@ | |||||||
|  | use rhai::Engine; | ||||||
|  | use sal_os::rhai::register_os_module; | ||||||
|  | use tempfile::TempDir; | ||||||
|  |  | ||||||
|  | fn create_test_engine() -> Engine { | ||||||
|  |     let mut engine = Engine::new(); | ||||||
|  |     register_os_module(&mut engine).expect("Failed to register OS module"); | ||||||
|  |     engine | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_rhai_module_registration() { | ||||||
|  |     // Test that the OS module can be registered without errors | ||||||
|  |     let _engine = create_test_engine(); | ||||||
|  |  | ||||||
|  |     // If we get here without panicking, the module was registered successfully | ||||||
|  |     // We can't easily test function registration without calling the functions | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_rhai_file_operations() { | ||||||
|  |     let engine = create_test_engine(); | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let temp_path = temp_dir.path().to_str().unwrap(); | ||||||
|  |  | ||||||
|  |     // Test file operations through Rhai | ||||||
|  |     let script = format!( | ||||||
|  |         r#" | ||||||
|  |         let test_dir = "{}/test_rhai"; | ||||||
|  |         let test_file = test_dir + "/test.txt"; | ||||||
|  |         let content = "Hello from Rhai!"; | ||||||
|  |          | ||||||
|  |         // Create directory | ||||||
|  |         mkdir(test_dir); | ||||||
|  |          | ||||||
|  |         // Check if directory exists | ||||||
|  |         let dir_exists = exist(test_dir); | ||||||
|  |          | ||||||
|  |         // Write file | ||||||
|  |         file_write(test_file, content); | ||||||
|  |          | ||||||
|  |         // Check if file exists | ||||||
|  |         let file_exists = exist(test_file); | ||||||
|  |          | ||||||
|  |         // Read file | ||||||
|  |         let read_content = file_read(test_file); | ||||||
|  |          | ||||||
|  |         // Return results | ||||||
|  |         #{{"dir_exists": dir_exists, "file_exists": file_exists, "content_match": read_content == content}} | ||||||
|  |     "#, | ||||||
|  |         temp_path | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     let result: rhai::Map = engine.eval(&script).expect("Script execution failed"); | ||||||
|  |  | ||||||
|  |     assert_eq!(result["dir_exists"].as_bool().unwrap(), true); | ||||||
|  |     assert_eq!(result["file_exists"].as_bool().unwrap(), true); | ||||||
|  |     assert_eq!(result["content_match"].as_bool().unwrap(), true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_rhai_file_size() { | ||||||
|  |     let engine = create_test_engine(); | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let temp_path = temp_dir.path().to_str().unwrap(); | ||||||
|  |  | ||||||
|  |     let script = format!( | ||||||
|  |         r#" | ||||||
|  |         let test_file = "{}/size_test.txt"; | ||||||
|  |         let content = "12345"; // 5 bytes | ||||||
|  |          | ||||||
|  |         file_write(test_file, content); | ||||||
|  |         let size = file_size(test_file); | ||||||
|  |          | ||||||
|  |         size | ||||||
|  |     "#, | ||||||
|  |         temp_path | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     let result: i64 = engine.eval(&script).expect("Script execution failed"); | ||||||
|  |     assert_eq!(result, 5); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_rhai_file_append() { | ||||||
|  |     let engine = create_test_engine(); | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let temp_path = temp_dir.path().to_str().unwrap(); | ||||||
|  |  | ||||||
|  |     let script = format!( | ||||||
|  |         r#" | ||||||
|  |         let test_file = "{}/append_test.txt"; | ||||||
|  |          | ||||||
|  |         file_write(test_file, "Line 1\n"); | ||||||
|  |         file_write_append(test_file, "Line 2\n"); | ||||||
|  |          | ||||||
|  |         let content = file_read(test_file); | ||||||
|  |         content | ||||||
|  |     "#, | ||||||
|  |         temp_path | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     let result: String = engine.eval(&script).expect("Script execution failed"); | ||||||
|  |     assert_eq!(result, "Line 1\nLine 2\n"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_rhai_copy_and_move() { | ||||||
|  |     let engine = create_test_engine(); | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let temp_path = temp_dir.path().to_str().unwrap(); | ||||||
|  |  | ||||||
|  |     let script = format!( | ||||||
|  |         r#" | ||||||
|  |         let source = "{}/source.txt"; | ||||||
|  |         let copy_dest = "{}/copy.txt"; | ||||||
|  |         let move_dest = "{}/moved.txt"; | ||||||
|  |         let content = "Test content"; | ||||||
|  |  | ||||||
|  |         // Create source file | ||||||
|  |         file_write(source, content); | ||||||
|  |  | ||||||
|  |         // Copy file | ||||||
|  |         copy(source, copy_dest); | ||||||
|  |  | ||||||
|  |         // Move the copy | ||||||
|  |         mv(copy_dest, move_dest); | ||||||
|  |  | ||||||
|  |         // Check results | ||||||
|  |         let source_exists = exist(source); | ||||||
|  |         let copy_exists = exist(copy_dest); | ||||||
|  |         let move_exists = exist(move_dest); | ||||||
|  |         let move_content = file_read(move_dest); | ||||||
|  |  | ||||||
|  |         #{{"source_exists": source_exists, "copy_exists": copy_exists, "move_exists": move_exists, "content_match": move_content == content}} | ||||||
|  |     "#, | ||||||
|  |         temp_path, temp_path, temp_path | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     let result: rhai::Map = engine.eval(&script).expect("Script execution failed"); | ||||||
|  |  | ||||||
|  |     assert_eq!(result["source_exists"].as_bool().unwrap(), true); | ||||||
|  |     assert_eq!(result["copy_exists"].as_bool().unwrap(), false); // Should be moved | ||||||
|  |     assert_eq!(result["move_exists"].as_bool().unwrap(), true); | ||||||
|  |     assert_eq!(result["content_match"].as_bool().unwrap(), true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_rhai_delete() { | ||||||
|  |     let engine = create_test_engine(); | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let temp_path = temp_dir.path().to_str().unwrap(); | ||||||
|  |  | ||||||
|  |     let script = format!( | ||||||
|  |         r#" | ||||||
|  |         let test_file = "{}/delete_test.txt"; | ||||||
|  |          | ||||||
|  |         // Create file | ||||||
|  |         file_write(test_file, "content"); | ||||||
|  |         let exists_before = exist(test_file); | ||||||
|  |          | ||||||
|  |         // Delete file | ||||||
|  |         delete(test_file); | ||||||
|  |         let exists_after = exist(test_file); | ||||||
|  |          | ||||||
|  |         #{{"before": exists_before, "after": exists_after}} | ||||||
|  |     "#, | ||||||
|  |         temp_path | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     let result: rhai::Map = engine.eval(&script).expect("Script execution failed"); | ||||||
|  |  | ||||||
|  |     assert_eq!(result["before"].as_bool().unwrap(), true); | ||||||
|  |     assert_eq!(result["after"].as_bool().unwrap(), false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_rhai_find_files() { | ||||||
|  |     let engine = create_test_engine(); | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let temp_path = temp_dir.path().to_str().unwrap(); | ||||||
|  |  | ||||||
|  |     let script = format!( | ||||||
|  |         r#" | ||||||
|  |         let test_dir = "{}/find_test"; | ||||||
|  |         mkdir(test_dir); | ||||||
|  |          | ||||||
|  |         // Create test files | ||||||
|  |         file_write(test_dir + "/file1.txt", "content1"); | ||||||
|  |         file_write(test_dir + "/file2.txt", "content2"); | ||||||
|  |         file_write(test_dir + "/other.log", "log content"); | ||||||
|  |          | ||||||
|  |         // Find .txt files | ||||||
|  |         let txt_files = find_files(test_dir, "*.txt"); | ||||||
|  |         let all_files = find_files(test_dir, "*"); | ||||||
|  |          | ||||||
|  |         #{{"txt_count": txt_files.len(), "all_count": all_files.len()}} | ||||||
|  |     "#, | ||||||
|  |         temp_path | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     let result: rhai::Map = engine.eval(&script).expect("Script execution failed"); | ||||||
|  |  | ||||||
|  |     assert_eq!(result["txt_count"].as_int().unwrap(), 2); | ||||||
|  |     assert!(result["all_count"].as_int().unwrap() >= 3); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_rhai_which_command() { | ||||||
|  |     let engine = create_test_engine(); | ||||||
|  |  | ||||||
|  |     let script = r#" | ||||||
|  |         let ls_path = which("ls"); | ||||||
|  |         let nonexistent = which("nonexistentcommand12345"); | ||||||
|  |          | ||||||
|  |         #{"ls_found": ls_path.len() > 0, "nonexistent_found": nonexistent.len() > 0} | ||||||
|  |     "#; | ||||||
|  |  | ||||||
|  |     let result: rhai::Map = engine.eval(script).expect("Script execution failed"); | ||||||
|  |  | ||||||
|  |     assert_eq!(result["ls_found"].as_bool().unwrap(), true); | ||||||
|  |     assert_eq!(result["nonexistent_found"].as_bool().unwrap(), false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_rhai_error_handling() { | ||||||
|  |     let engine = create_test_engine(); | ||||||
|  |  | ||||||
|  |     // Test that errors are properly propagated to Rhai | ||||||
|  |     // Instead of try-catch, just test that the function call fails | ||||||
|  |     let script = r#"file_read("/nonexistent/path/file.txt")"#; | ||||||
|  |  | ||||||
|  |     let result = engine.eval::<String>(script); | ||||||
|  |     assert!( | ||||||
|  |         result.is_err(), | ||||||
|  |         "Expected error when reading non-existent file" | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_rhai_package_functions() { | ||||||
|  |     let engine = create_test_engine(); | ||||||
|  |  | ||||||
|  |     // Test that package functions are registered by calling them | ||||||
|  |  | ||||||
|  |     let script = r#" | ||||||
|  |         let platform = package_platform(); | ||||||
|  |         let debug_result = package_set_debug(true); | ||||||
|  |          | ||||||
|  |         #{"platform": platform, "debug": debug_result} | ||||||
|  |     "#; | ||||||
|  |  | ||||||
|  |     let result: rhai::Map = engine.eval(script).expect("Script execution failed"); | ||||||
|  |  | ||||||
|  |     // Platform should be a non-empty string | ||||||
|  |     let platform: String = result["platform"].clone().try_cast().unwrap(); | ||||||
|  |     assert!(!platform.is_empty()); | ||||||
|  |  | ||||||
|  |     // Debug setting should return true | ||||||
|  |     assert_eq!(result["debug"].as_bool().unwrap(), true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_rhai_download_functions() { | ||||||
|  |     let engine = create_test_engine(); | ||||||
|  |  | ||||||
|  |     // Test that download functions are registered by calling them | ||||||
|  |  | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let temp_path = temp_dir.path().to_str().unwrap(); | ||||||
|  |  | ||||||
|  |     let script = format!( | ||||||
|  |         r#" | ||||||
|  |         let test_file = "{}/test_script.sh"; | ||||||
|  |          | ||||||
|  |         // Create a test script | ||||||
|  |         file_write(test_file, "echo 'test'"); | ||||||
|  |          | ||||||
|  |         // Make it executable | ||||||
|  |         try {{ | ||||||
|  |             let result = chmod_exec(test_file); | ||||||
|  |             result.len() >= 0 // chmod_exec returns a string, so check if it's valid | ||||||
|  |         }} catch {{ | ||||||
|  |             false | ||||||
|  |         }} | ||||||
|  |     "#, | ||||||
|  |         temp_path | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     let result: bool = engine.eval(&script).expect("Script execution failed"); | ||||||
|  |     assert!(result); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_rhai_array_returns() { | ||||||
|  |     let engine = create_test_engine(); | ||||||
|  |     let temp_dir = TempDir::new().unwrap(); | ||||||
|  |     let temp_path = temp_dir.path().to_str().unwrap(); | ||||||
|  |  | ||||||
|  |     let script = format!( | ||||||
|  |         r#" | ||||||
|  |         let test_dir = "{}/array_test"; | ||||||
|  |         mkdir(test_dir); | ||||||
|  |          | ||||||
|  |         // Create some files | ||||||
|  |         file_write(test_dir + "/file1.txt", "content"); | ||||||
|  |         file_write(test_dir + "/file2.txt", "content"); | ||||||
|  |          | ||||||
|  |         // Test that find_files returns an array | ||||||
|  |         let files = find_files(test_dir, "*.txt"); | ||||||
|  |          | ||||||
|  |         // Test array operations | ||||||
|  |         let count = files.len(); | ||||||
|  |         let first_file = if count > 0 {{ files[0] }} else {{ "" }}; | ||||||
|  |          | ||||||
|  |         #{{"count": count, "has_files": count > 0, "first_file_exists": first_file.len() > 0}} | ||||||
|  |     "#, | ||||||
|  |         temp_path | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     let result: rhai::Map = engine.eval(&script).expect("Script execution failed"); | ||||||
|  |  | ||||||
|  |     assert_eq!(result["count"].as_int().unwrap(), 2); | ||||||
|  |     assert_eq!(result["has_files"].as_bool().unwrap(), true); | ||||||
|  |     assert_eq!(result["first_file_exists"].as_bool().unwrap(), true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn test_rhai_platform_functions() { | ||||||
|  |     let engine = create_test_engine(); | ||||||
|  |  | ||||||
|  |     let script = r#" | ||||||
|  |         let is_osx = platform_is_osx(); | ||||||
|  |         let is_linux = platform_is_linux(); | ||||||
|  |         let is_arm = platform_is_arm(); | ||||||
|  |         let is_x86 = platform_is_x86(); | ||||||
|  |  | ||||||
|  |         // Test that platform detection is consistent | ||||||
|  |         let platform_consistent = !(is_osx && is_linux); | ||||||
|  |         let arch_consistent = !(is_arm && is_x86); | ||||||
|  |  | ||||||
|  |         #{"osx": is_osx, "linux": is_linux, "arm": is_arm, "x86": is_x86, "platform_consistent": platform_consistent, "arch_consistent": arch_consistent} | ||||||
|  |     "#; | ||||||
|  |  | ||||||
|  |     let result: rhai::Map = engine.eval(script).expect("Script execution failed"); | ||||||
|  |  | ||||||
|  |     // Verify platform detection consistency | ||||||
|  |     assert_eq!(result["platform_consistent"].as_bool().unwrap(), true); | ||||||
|  |     assert_eq!(result["arch_consistent"].as_bool().unwrap(), true); | ||||||
|  |  | ||||||
|  |     // At least one platform should be detected | ||||||
|  |     let osx = result["osx"].as_bool().unwrap(); | ||||||
|  |     let linux = result["linux"].as_bool().unwrap(); | ||||||
|  |  | ||||||
|  |     // At least one architecture should be detected | ||||||
|  |     let arm = result["arm"].as_bool().unwrap(); | ||||||
|  |     let x86 = result["x86"].as_bool().unwrap(); | ||||||
|  |  | ||||||
|  |     // Print current platform for debugging | ||||||
|  |     println!( | ||||||
|  |         "Platform detection: OSX={}, Linux={}, ARM={}, x86={}", | ||||||
|  |         osx, linux, arm, x86 | ||||||
|  |     ); | ||||||
|  | } | ||||||
| @@ -40,7 +40,7 @@ pub type Result<T> = std::result::Result<T, Error>; | |||||||
| pub mod cmd; | pub mod cmd; | ||||||
| pub use sal_mycelium as mycelium; | pub use sal_mycelium as mycelium; | ||||||
| pub mod net; | pub mod net; | ||||||
| pub mod os; | pub use sal_os as os; | ||||||
| pub mod postgresclient; | pub mod postgresclient; | ||||||
| pub mod process; | pub mod process; | ||||||
| pub use sal_redisclient as redisclient; | pub use sal_redisclient as redisclient; | ||||||
|   | |||||||
							
								
								
									
										245
									
								
								src/os/README.md
									
									
									
									
									
								
							
							
						
						
									
										245
									
								
								src/os/README.md
									
									
									
									
									
								
							| @@ -1,245 +0,0 @@ | |||||||
| # SAL OS Module (`sal::os`) |  | ||||||
|  |  | ||||||
| The `sal::os` module provides a comprehensive suite of operating system interaction utilities. It aims to offer a cross-platform abstraction layer for common OS-level tasks, simplifying system programming in Rust. |  | ||||||
|  |  | ||||||
| This module is composed of three main sub-modules: |  | ||||||
| - [`fs`](#fs): File system operations. |  | ||||||
| - [`download`](#download): File downloading and basic installation. |  | ||||||
| - [`package`](#package): System package management. |  | ||||||
|  |  | ||||||
| ## Key Design Points |  | ||||||
|  |  | ||||||
| The `sal::os` module is engineered with several core principles to provide a robust and developer-friendly interface for OS interactions: |  | ||||||
|  |  | ||||||
| - **Cross-Platform Abstraction**: A primary goal is to offer a unified API for common OS tasks, smoothing over differences between operating systems (primarily Linux and macOS). While it strives for abstraction, it leverages platform-specific tools (e.g., `rsync` on Linux, `robocopy` on Windows for `fs::copy` or `fs::rsync`; `apt` on Debian-based systems, `brew` on macOS for `package` management) for optimal performance and behavior when necessary. |  | ||||||
| - **Modular Structure**: Functionality is organized into logical sub-modules: |  | ||||||
|     - `fs`: For comprehensive file and directory manipulation. |  | ||||||
|     - `download`: For retrieving files from URLs, with support for extraction and basic installation. |  | ||||||
|     - `package`: For interacting with system package managers. |  | ||||||
| - **Granular Error Handling**: Each sub-module features custom error enums (`FsError`, `DownloadError`, `PackageError`) to provide specific and actionable feedback, aiding in debugging and robust error management. |  | ||||||
| - **Sensible Defaults and Defensive Operations**: Many functions are designed to be "defensive," e.g., `mkdir` creates parent directories if they don't exist and doesn't fail if the directory already exists. `delete` doesn't error if the target is already gone. |  | ||||||
| - **Facade for Simplicity**: The `package` sub-module uses a `PackHero` facade to provide a simple entry point for common package operations, automatically detecting the underlying OS and package manager. |  | ||||||
| - **Rhai Scriptability**: A significant portion of the `sal::os` module's functionality is exposed to Rhai scripts via `herodo`, enabling powerful automation of OS-level tasks. |  | ||||||
|  |  | ||||||
| ## `fs` - File System Operations |  | ||||||
|  |  | ||||||
| The `fs` sub-module (`sal::os::fs`) offers a robust set of functions for interacting with the file system. |  | ||||||
|  |  | ||||||
| **Key Features:** |  | ||||||
|  |  | ||||||
| *   **Error Handling**: A custom `FsError` enum for detailed error reporting on file system operations. |  | ||||||
| *   **File Operations**: |  | ||||||
|     *   `copy(src, dest)`: Copies files and directories, with support for wildcards and recursive copying. Uses platform-specific commands (`cp -R`, `robocopy /MIR`). |  | ||||||
|     *   `exist(path)`: Checks if a file or directory exists. |  | ||||||
|     *   `find_file(dir, filename_pattern)`: Finds a single file in a directory, supporting wildcards. |  | ||||||
|     *   `find_files(dir, filename_pattern)`: Finds multiple files in a directory, supporting wildcards. |  | ||||||
|     *   `file_size(path)`: Returns the size of a file in bytes. |  | ||||||
|     *   `file_read(path)`: Reads the entire content of a file into a string. |  | ||||||
|     *   `file_write(path, content)`: Writes content to a file, overwriting if it exists, and creating parent directories if needed. |  | ||||||
|     *   `file_write_append(path, content)`: Appends content to a file, creating it and parent directories if needed. |  | ||||||
| *   **Directory Operations**: |  | ||||||
|     *   `find_dir(parent_dir, dirname_pattern)`: Finds a single directory within a parent directory, supporting wildcards. |  | ||||||
|     *   `find_dirs(parent_dir, dirname_pattern)`: Finds multiple directories recursively within a parent directory, supporting wildcards. |  | ||||||
|     *   `delete(path)`: Deletes files or directories. |  | ||||||
|     *   `mkdir(path)`: Creates a directory, including parent directories if necessary. |  | ||||||
|     *   `rsync(src, dest)`: Synchronizes directories using platform-specific commands (`rsync -a --delete`, `robocopy /MIR`). |  | ||||||
|     *   `chdir(path)`: Changes the current working directory. |  | ||||||
| *   **Path Operations**: |  | ||||||
|     *   `mv(src, dest)`: Moves or renames files and directories. Handles cross-device moves by falling back to copy-then-delete. |  | ||||||
| *   **Command Utilities**: |  | ||||||
|     *   `which(command_name)`: Checks if a command exists in the system's PATH and returns its path. |  | ||||||
|  |  | ||||||
| **Usage Example (fs):** |  | ||||||
|  |  | ||||||
| ```rust |  | ||||||
| use sal::os::fs; |  | ||||||
|  |  | ||||||
| fn main() -> Result<(), Box<dyn std::error::Error>> { |  | ||||||
|     if !fs::exist("my_dir") { |  | ||||||
|         fs::mkdir("my_dir")?; |  | ||||||
|         println!("Created directory 'my_dir'"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fs::file_write("my_dir/example.txt", "Hello from SAL!")?; |  | ||||||
|     let content = fs::file_read("my_dir/example.txt")?; |  | ||||||
|     println!("File content: {}", content); |  | ||||||
|  |  | ||||||
|     Ok(()) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## `download` - File Downloading and Installation |  | ||||||
|  |  | ||||||
| The `download` sub-module (`sal::os::download`) provides utilities for downloading files from URLs and performing basic installation tasks. |  | ||||||
|  |  | ||||||
| **Key Features:** |  | ||||||
|  |  | ||||||
| *   **Error Handling**: A custom `DownloadError` enum for download-specific errors. |  | ||||||
| *   **File Downloading**: |  | ||||||
|     *   `download(url, dest_dir, min_size_kb)`: Downloads a file to a specified directory. |  | ||||||
|         *   Uses `curl` with progress display. |  | ||||||
|         *   Supports minimum file size checks. |  | ||||||
|         *   Automatically extracts common archive formats (`.tar.gz`, `.tgz`, `.tar`, `.zip`) into `dest_dir`. |  | ||||||
|     *   `download_file(url, dest_file_path, min_size_kb)`: Downloads a file to a specific file path without automatic extraction. |  | ||||||
| *   **File Permissions**: |  | ||||||
|     *   `chmod_exec(path)`: Makes a file executable (equivalent to `chmod +x` on Unix-like systems). |  | ||||||
| *   **Download and Install**: |  | ||||||
|     *   `download_install(url, min_size_kb)`: Downloads a file (to `/tmp/`) and attempts to install it if it's a supported package format. |  | ||||||
|         *   Currently supports `.deb` packages on Debian-based systems. |  | ||||||
|         *   For `.deb` files, it uses `sudo dpkg --install` and attempts `sudo apt-get install -f -y` to fix dependencies if needed. |  | ||||||
|         *   Handles archives by extracting them to `/tmp/` first. |  | ||||||
|  |  | ||||||
| **Usage Example (download):** |  | ||||||
|  |  | ||||||
| ```rust |  | ||||||
| use sal::os::download; |  | ||||||
|  |  | ||||||
| fn main() -> Result<(), Box<dyn std::error::Error>> { |  | ||||||
|     let archive_url = "https://example.com/my_archive.tar.gz"; |  | ||||||
|     let output_dir = "/tmp/my_app"; |  | ||||||
|      |  | ||||||
|     // Download and extract an archive |  | ||||||
|     let extracted_path = download::download(archive_url, output_dir, 1024)?; // Min 1MB |  | ||||||
|     println!("Archive extracted to: {}", extracted_path); |  | ||||||
|  |  | ||||||
|     // Download a script and make it executable |  | ||||||
|     let script_url = "https://example.com/my_script.sh"; |  | ||||||
|     let script_path = "/tmp/my_script.sh"; |  | ||||||
|     download::download_file(script_url, script_path, 0)?; |  | ||||||
|     download::chmod_exec(script_path)?; |  | ||||||
|     println!("Script downloaded and made executable at: {}", script_path); |  | ||||||
|  |  | ||||||
|     Ok(()) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## `package` - System Package Management |  | ||||||
|  |  | ||||||
| The `package` sub-module (`sal::os::package`) offers an abstraction layer for interacting with system package managers like APT (for Debian/Ubuntu) and Homebrew (for macOS). |  | ||||||
|  |  | ||||||
| **Key Features:** |  | ||||||
|  |  | ||||||
| *   **Error Handling**: A custom `PackageError` enum. |  | ||||||
| *   **Platform Detection**: Identifies the current OS (Ubuntu, macOS, or Unknown) to use the appropriate package manager. |  | ||||||
| *   **`PackageManager` Trait**: Defines a common interface for package operations: |  | ||||||
|     *   `install(package_name)` |  | ||||||
|     *   `remove(package_name)` |  | ||||||
|     *   `update()` (updates package lists) |  | ||||||
|     *   `upgrade()` (upgrades all installed packages) |  | ||||||
|     *   `list_installed()` |  | ||||||
|     *   `search(query)` |  | ||||||
|     *   `is_installed(package_name)` |  | ||||||
| *   **Implementations**: |  | ||||||
|     *   `AptPackageManager`: For Debian/Ubuntu systems (uses `apt-get`, `dpkg`). |  | ||||||
|     *   `BrewPackageManager`: For macOS systems (uses `brew`). |  | ||||||
| *   **`PackHero` Facade**: A simple entry point to access package management functions in a platform-agnostic way. |  | ||||||
|     *   `PackHero::new().install("nginx")?` |  | ||||||
|  |  | ||||||
| **Usage Example (package):** |  | ||||||
|  |  | ||||||
| ```rust |  | ||||||
| use sal::os::package::PackHero; |  | ||||||
|  |  | ||||||
| fn main() -> Result<(), Box<dyn std::error::Error>> { |  | ||||||
|     let pack_hero = PackHero::new(); |  | ||||||
|  |  | ||||||
|     // Check if a package is installed |  | ||||||
|     if !pack_hero.is_installed("htop")? { |  | ||||||
|         println!("htop is not installed. Attempting to install..."); |  | ||||||
|         pack_hero.install("htop")?; |  | ||||||
|         println!("htop installed successfully."); |  | ||||||
|     } else { |  | ||||||
|         println!("htop is already installed."); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Update package lists |  | ||||||
|     println!("Updating package lists..."); |  | ||||||
|     pack_hero.update()?; |  | ||||||
|     println!("Package lists updated."); |  | ||||||
|  |  | ||||||
|     Ok(()) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Rhai Scripting with `herodo` |  | ||||||
|  |  | ||||||
| The `sal::os` module is extensively scriptable via `herodo`, allowing for automation of various operating system tasks directly from Rhai scripts. The `sal::rhai::os` module registers the necessary functions. |  | ||||||
|  |  | ||||||
| ### File System (`fs`) Functions |  | ||||||
|  |  | ||||||
| - `copy(src: String, dest: String) -> String`: Copies files/directories (supports wildcards). |  | ||||||
| - `exist(path: String) -> bool`: Checks if a file or directory exists. |  | ||||||
| - `find_file(dir: String, filename_pattern: String) -> String`: Finds a single file in `dir` matching `filename_pattern`. |  | ||||||
| - `find_files(dir: String, filename_pattern: String) -> Array`: Finds multiple files in `dir` (recursive). |  | ||||||
| - `find_dir(parent_dir: String, dirname_pattern: String) -> String`: Finds a single directory in `parent_dir`. |  | ||||||
| - `find_dirs(parent_dir: String, dirname_pattern: String) -> Array`: Finds multiple directories in `parent_dir` (recursive). |  | ||||||
| - `delete(path: String) -> String`: Deletes a file or directory. |  | ||||||
| - `mkdir(path: String) -> String`: Creates a directory (and parents if needed). |  | ||||||
| - `file_size(path: String) -> Int`: Returns file size in bytes. |  | ||||||
| - `rsync(src: String, dest: String) -> String`: Synchronizes directories. |  | ||||||
| - `chdir(path: String) -> String`: Changes the current working directory. |  | ||||||
| - `file_read(path: String) -> String`: Reads entire file content. |  | ||||||
| - `file_write(path: String, content: String) -> String`: Writes content to a file (overwrites). |  | ||||||
| - `file_write_append(path: String, content: String) -> String`: Appends content to a file. |  | ||||||
| - `mv(src: String, dest: String) -> String`: Moves/renames a file or directory. |  | ||||||
| - `which(command_name: String) -> String`: Checks if a command exists in PATH and returns its path. |  | ||||||
| - `cmd_ensure_exists(commands: String) -> String`: Ensures one or more commands (comma-separated) exist in PATH; throws an error if any are missing. |  | ||||||
|  |  | ||||||
| ### Download Functions |  | ||||||
|  |  | ||||||
| - `download(url: String, dest_dir: String, min_size_kb: Int) -> String`: Downloads from `url` to `dest_dir`, extracts common archives. |  | ||||||
| - `download_file(url: String, dest_file_path: String, min_size_kb: Int) -> String`: Downloads from `url` to `dest_file_path` (no extraction). |  | ||||||
| - `download_install(url: String, min_size_kb: Int) -> String`: Downloads and attempts to install (e.g., `.deb` packages). |  | ||||||
| - `chmod_exec(path: String) -> String`: Makes a file executable (`chmod +x`). |  | ||||||
|  |  | ||||||
| ### Package Management Functions |  | ||||||
|  |  | ||||||
| - `package_install(package_name: String) -> String`: Installs a package. |  | ||||||
| - `package_remove(package_name: String) -> String`: Removes a package. |  | ||||||
| - `package_update() -> String`: Updates package lists. |  | ||||||
| - `package_upgrade() -> String`: Upgrades all installed packages. |  | ||||||
| - `package_list() -> Array`: Lists all installed packages. |  | ||||||
| - `package_search(query: String) -> Array`: Searches for packages. |  | ||||||
| - `package_is_installed(package_name: String) -> bool`: Checks if a package is installed. |  | ||||||
| - `package_set_debug(debug: bool) -> bool`: Enables/disables debug logging for package operations. |  | ||||||
| - `package_platform() -> String`: Returns the detected package platform (e.g., "Ubuntu", "MacOS"). |  | ||||||
|  |  | ||||||
| ### Rhai Example |  | ||||||
|  |  | ||||||
| ```rhai |  | ||||||
| // File system operations |  | ||||||
| let test_dir = "/tmp/sal_os_rhai_test"; |  | ||||||
| if exist(test_dir) { |  | ||||||
|     delete(test_dir); |  | ||||||
| } |  | ||||||
| mkdir(test_dir); |  | ||||||
| print(`Created directory: ${test_dir}`); |  | ||||||
|  |  | ||||||
| file_write(`${test_dir}/message.txt`, "Hello from Rhai OS module!"); |  | ||||||
| let content = file_read(`${test_dir}/message.txt`); |  | ||||||
| print(`File content: ${content}`); |  | ||||||
|  |  | ||||||
| // Download operation (example URL, may not be active) |  | ||||||
| // let script_url = "https://raw.githubusercontent.com/someuser/somescript/main/script.sh"; |  | ||||||
| // let script_path = `${test_dir}/downloaded_script.sh`; |  | ||||||
| // try { |  | ||||||
| //   download_file(script_url, script_path, 0); |  | ||||||
| //   chmod_exec(script_path); |  | ||||||
| //   print(`Downloaded and made executable: ${script_path}`); |  | ||||||
| // } catch (e) { |  | ||||||
| //   print(`Download example failed (this is okay for a test): ${e}`); |  | ||||||
| // } |  | ||||||
|  |  | ||||||
| // Package management (illustrative, requires sudo for install/remove/update) |  | ||||||
| print(`Package platform: ${package_platform()}`); |  | ||||||
| if !package_is_installed("htop") { |  | ||||||
|     print("htop is not installed."); |  | ||||||
|     // package_install("htop"); // Would require sudo |  | ||||||
| } else { |  | ||||||
|     print("htop is already installed."); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| print("OS module Rhai script finished."); |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| This module provides a powerful and convenient way to handle common OS-level tasks within your Rust applications. |  | ||||||
| @@ -1,8 +0,0 @@ | |||||||
| mod fs; |  | ||||||
| mod download; |  | ||||||
| pub mod package; |  | ||||||
|  |  | ||||||
| pub use fs::*; |  | ||||||
| pub use download::*; |  | ||||||
| pub use package::*; |  | ||||||
| pub mod platform; |  | ||||||
| @@ -2,9 +2,9 @@ | |||||||
| //! | //! | ||||||
| //! This module provides Rhai wrappers for functions that interact with the Rhai engine itself. | //! This module provides Rhai wrappers for functions that interact with the Rhai engine itself. | ||||||
|  |  | ||||||
| use rhai::{Engine, EvalAltResult, NativeCallContext}; |  | ||||||
| use crate::os; |  | ||||||
| use super::error::ToRhaiError; | use super::error::ToRhaiError; | ||||||
|  | use rhai::{Engine, EvalAltResult, NativeCallContext}; | ||||||
|  | use sal_os as os; | ||||||
|  |  | ||||||
| /// Register core module functions with the Rhai engine | /// Register core module functions with the Rhai engine | ||||||
| /// | /// | ||||||
| @@ -37,7 +37,7 @@ pub fn exec(context: NativeCallContext, source: &str) -> Result<rhai::Dynamic, B | |||||||
|         let file_name = source.split('/').last().unwrap_or("script.rhai"); |         let file_name = source.split('/').last().unwrap_or("script.rhai"); | ||||||
|         let dest_path = temp_dir.join(format!("{}-{}", uuid::Uuid::new_v4(), file_name)); |         let dest_path = temp_dir.join(format!("{}-{}", uuid::Uuid::new_v4(), file_name)); | ||||||
|         let dest_str = dest_path.to_str().unwrap(); |         let dest_str = dest_path.to_str().unwrap(); | ||||||
|          |  | ||||||
|         os::download_file(source, dest_str, 0).to_rhai_error()?; |         os::download_file(source, dest_str, 0).to_rhai_error()?; | ||||||
|         os::file_read(dest_str).to_rhai_error()? |         os::file_read(dest_str).to_rhai_error()? | ||||||
|     } else if os::exist(source) { |     } else if os::exist(source) { | ||||||
| @@ -50,4 +50,4 @@ pub fn exec(context: NativeCallContext, source: &str) -> Result<rhai::Dynamic, B | |||||||
|  |  | ||||||
|     // Execute the script content |     // Execute the script content | ||||||
|     context.engine().eval(&content) |     context.engine().eval(&content) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,8 +7,8 @@ mod buildah; | |||||||
| mod core; | mod core; | ||||||
| pub mod error; | pub mod error; | ||||||
| mod nerdctl; | mod nerdctl; | ||||||
| mod os; | // OS module is now provided by sal-os package | ||||||
| mod platform; | // Platform module is now provided by sal-os package | ||||||
| mod postgresclient; | mod postgresclient; | ||||||
| mod process; | mod process; | ||||||
|  |  | ||||||
| @@ -26,8 +26,8 @@ pub use rhai::{Array, Dynamic, Engine, EvalAltResult, Map}; | |||||||
| // Re-export error module | // Re-export error module | ||||||
| pub use error::*; | pub use error::*; | ||||||
|  |  | ||||||
| // Re-export specific functions from modules to avoid name conflicts | // Re-export specific functions from sal-os package | ||||||
| pub use os::{ | pub use sal_os::rhai::{ | ||||||
|     delete, |     delete, | ||||||
|     // Download functions |     // Download functions | ||||||
|     download, |     download, | ||||||
| @@ -106,7 +106,7 @@ pub use sal_text::rhai::register_text_module; | |||||||
| pub use vault::register_crypto_module; | pub use vault::register_crypto_module; | ||||||
|  |  | ||||||
| // Rename copy functions to avoid conflicts | // Rename copy functions to avoid conflicts | ||||||
| pub use os::copy as os_copy; | pub use sal_os::rhai::copy as os_copy; | ||||||
|  |  | ||||||
| /// Register all SAL modules with the Rhai engine | /// Register all SAL modules with the Rhai engine | ||||||
| /// | /// | ||||||
| @@ -132,7 +132,7 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> { | |||||||
|     core::register_core_module(engine)?; |     core::register_core_module(engine)?; | ||||||
|  |  | ||||||
|     // Register OS module functions |     // Register OS module functions | ||||||
|     os::register_os_module(engine)?; |     sal_os::rhai::register_os_module(engine)?; | ||||||
|  |  | ||||||
|     // Register Process module functions |     // Register Process module functions | ||||||
|     process::register_process_module(engine)?; |     process::register_process_module(engine)?; | ||||||
| @@ -167,8 +167,7 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> { | |||||||
|     // Register PostgreSQL client module functions |     // Register PostgreSQL client module functions | ||||||
|     postgresclient::register_postgresclient_module(engine)?; |     postgresclient::register_postgresclient_module(engine)?; | ||||||
|  |  | ||||||
|     // Register Platform module functions |     // Platform functions are now registered by sal-os package | ||||||
|     platform::register(engine); |  | ||||||
|  |  | ||||||
|     // Register Screen module functions |     // Register Screen module functions | ||||||
|     screen::register(engine); |     screen::register(engine); | ||||||
|   | |||||||
| @@ -1,40 +0,0 @@ | |||||||
| use crate::os::platform; |  | ||||||
| use rhai::{plugin::*, Engine}; |  | ||||||
|  |  | ||||||
| #[export_module] |  | ||||||
| pub mod platform_functions { |  | ||||||
|     #[rhai_fn(name = "platform_is_osx")] |  | ||||||
|     pub fn is_osx() -> bool { |  | ||||||
|         platform::is_osx() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[rhai_fn(name = "platform_is_linux")] |  | ||||||
|     pub fn is_linux() -> bool { |  | ||||||
|         platform::is_linux() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[rhai_fn(name = "platform_is_arm")] |  | ||||||
|     pub fn is_arm() -> bool { |  | ||||||
|         platform::is_arm() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[rhai_fn(name = "platform_is_x86")] |  | ||||||
|     pub fn is_x86() -> bool { |  | ||||||
|         platform::is_x86() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[rhai_fn(name = "platform_check_linux_x86")] |  | ||||||
|     pub fn check_linux_x86() -> Result<(), crate::rhai::error::SalError> { |  | ||||||
|         platform::check_linux_x86() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[rhai_fn(name = "platform_check_macos_arm")] |  | ||||||
|     pub fn check_macos_arm() -> Result<(), crate::rhai::error::SalError> { |  | ||||||
|         platform::check_macos_arm() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn register(engine: &mut Engine) { |  | ||||||
|     let platform_module = exported_module!(platform_functions); |  | ||||||
|     engine.register_global_module(platform_module.into()); |  | ||||||
| } |  | ||||||
| @@ -1,9 +1,9 @@ | |||||||
| // File: /root/code/git.threefold.info/herocode/sal/src/virt/nerdctl/container.rs | // File: /root/code/git.threefold.info/herocode/sal/src/virt/nerdctl/container.rs | ||||||
|  |  | ||||||
| use std::collections::HashMap; |  | ||||||
| use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError}; |  | ||||||
| use crate::os; |  | ||||||
| use super::container_types::Container; | use super::container_types::Container; | ||||||
|  | use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError}; | ||||||
|  | use sal_os as os; | ||||||
|  | use std::collections::HashMap; | ||||||
|  |  | ||||||
| impl Container { | impl Container { | ||||||
|     /// Create a new container reference with the given name |     /// Create a new container reference with the given name | ||||||
| @@ -18,18 +18,22 @@ impl Container { | |||||||
|     pub fn new(name: &str) -> Result<Self, NerdctlError> { |     pub fn new(name: &str) -> Result<Self, NerdctlError> { | ||||||
|         // Check if required commands exist |         // Check if required commands exist | ||||||
|         match os::cmd_ensure_exists("nerdctl,runc,buildah") { |         match os::cmd_ensure_exists("nerdctl,runc,buildah") { | ||||||
|             Err(e) => return Err(NerdctlError::CommandExecutionFailed( |             Err(e) => { | ||||||
|                 std::io::Error::new(std::io::ErrorKind::NotFound, |                 return Err(NerdctlError::CommandExecutionFailed(std::io::Error::new( | ||||||
|                 format!("Required commands not found: {}", e)) |                     std::io::ErrorKind::NotFound, | ||||||
|             )), |                     format!("Required commands not found: {}", e), | ||||||
|  |                 ))) | ||||||
|  |             } | ||||||
|             _ => {} |             _ => {} | ||||||
|         } |         } | ||||||
|          |  | ||||||
|         // Check if container exists |         // Check if container exists | ||||||
|         let result = execute_nerdctl_command(&["ps", "-a", "--format", "{{.Names}} {{.ID}}"])?; |         let result = execute_nerdctl_command(&["ps", "-a", "--format", "{{.Names}} {{.ID}}"])?; | ||||||
|          |  | ||||||
|         // Look for the container name in the output |         // Look for the container name in the output | ||||||
|         let container_id = result.stdout.lines() |         let container_id = result | ||||||
|  |             .stdout | ||||||
|  |             .lines() | ||||||
|             .filter_map(|line| { |             .filter_map(|line| { | ||||||
|                 if line.starts_with(&format!("{} ", name)) { |                 if line.starts_with(&format!("{} ", name)) { | ||||||
|                     Some(line.split_whitespace().nth(1)?.to_string()) |                     Some(line.split_whitespace().nth(1)?.to_string()) | ||||||
| @@ -38,7 +42,7 @@ impl Container { | |||||||
|                 } |                 } | ||||||
|             }) |             }) | ||||||
|             .next(); |             .next(); | ||||||
|          |  | ||||||
|         Ok(Self { |         Ok(Self { | ||||||
|             name: name.to_string(), |             name: name.to_string(), | ||||||
|             container_id, |             container_id, | ||||||
| @@ -59,7 +63,7 @@ impl Container { | |||||||
|             snapshotter: None, |             snapshotter: None, | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|      |  | ||||||
|     /// Create a container from an image |     /// Create a container from an image | ||||||
|     /// |     /// | ||||||
|     /// # Arguments |     /// # Arguments | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ use tempfile::NamedTempFile; | |||||||
| #[test] | #[test] | ||||||
| fn test_template_builder_basic_string_variable() { | fn test_template_builder_basic_string_variable() { | ||||||
|     // Create a temporary template file |     // Create a temporary template file | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     let template_content = "Hello {{name}}!"; |     let template_content = "Hello {{name}}!"; | ||||||
|     fs::write(temp_file.path(), template_content).expect("Failed to write template"); |     fs::write(temp_file.path(), template_content).expect("Failed to write template"); | ||||||
|  |  | ||||||
| @@ -30,7 +30,7 @@ fn test_template_builder_basic_string_variable() { | |||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn test_template_builder_multiple_variables() { | fn test_template_builder_multiple_variables() { | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     let template_content = "{{greeting}} {{name}}, you have {{count}} messages."; |     let template_content = "{{greeting}} {{name}}, you have {{count}} messages."; | ||||||
|     fs::write(temp_file.path(), template_content).expect("Failed to write template"); |     fs::write(temp_file.path(), template_content).expect("Failed to write template"); | ||||||
|  |  | ||||||
| @@ -47,7 +47,7 @@ fn test_template_builder_multiple_variables() { | |||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn test_template_builder_different_types() { | fn test_template_builder_different_types() { | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     let template_content = "String: {{text}}, Int: {{number}}, Float: {{decimal}}, Bool: {{flag}}"; |     let template_content = "String: {{text}}, Int: {{number}}, Float: {{decimal}}, Bool: {{flag}}"; | ||||||
|     fs::write(temp_file.path(), template_content).expect("Failed to write template"); |     fs::write(temp_file.path(), template_content).expect("Failed to write template"); | ||||||
|  |  | ||||||
| @@ -65,8 +65,9 @@ fn test_template_builder_different_types() { | |||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn test_template_builder_array_variable() { | fn test_template_builder_array_variable() { | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     let template_content = "Items: {% for item in items %}{{item}}{% if not loop.last %}, {% endif %}{% endfor %}"; |     let template_content = | ||||||
|  |         "Items: {% for item in items %}{{item}}{% if not loop.last %}, {% endif %}{% endfor %}"; | ||||||
|     fs::write(temp_file.path(), template_content).expect("Failed to write template"); |     fs::write(temp_file.path(), template_content).expect("Failed to write template"); | ||||||
|  |  | ||||||
|     let items = vec!["apple", "banana", "cherry"]; |     let items = vec!["apple", "banana", "cherry"]; | ||||||
| @@ -81,7 +82,7 @@ fn test_template_builder_array_variable() { | |||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn test_template_builder_add_vars_hashmap() { | fn test_template_builder_add_vars_hashmap() { | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     let template_content = "{{title}}: {{description}}"; |     let template_content = "{{title}}: {{description}}"; | ||||||
|     fs::write(temp_file.path(), template_content).expect("Failed to write template"); |     fs::write(temp_file.path(), template_content).expect("Failed to write template"); | ||||||
|  |  | ||||||
| @@ -101,7 +102,7 @@ fn test_template_builder_add_vars_hashmap() { | |||||||
| #[test] | #[test] | ||||||
| fn test_template_builder_render_to_file() { | fn test_template_builder_render_to_file() { | ||||||
|     // Create template file |     // Create template file | ||||||
|     let mut template_file = NamedTempFile::new().expect("Failed to create template file"); |     let template_file = NamedTempFile::new().expect("Failed to create template file"); | ||||||
|     let template_content = "Hello {{name}}, today is {{day}}."; |     let template_content = "Hello {{name}}, today is {{day}}."; | ||||||
|     fs::write(template_file.path(), template_content).expect("Failed to write template"); |     fs::write(template_file.path(), template_content).expect("Failed to write template"); | ||||||
|  |  | ||||||
| @@ -121,8 +122,9 @@ fn test_template_builder_render_to_file() { | |||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn test_template_builder_conditional() { | fn test_template_builder_conditional() { | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     let template_content = "{% if show_message %}Message: {{message}}{% else %}No message{% endif %}"; |     let template_content = | ||||||
|  |         "{% if show_message %}Message: {{message}}{% else %}No message{% endif %}"; | ||||||
|     fs::write(temp_file.path(), template_content).expect("Failed to write template"); |     fs::write(temp_file.path(), template_content).expect("Failed to write template"); | ||||||
|  |  | ||||||
|     // Test with condition true |     // Test with condition true | ||||||
| @@ -148,7 +150,7 @@ fn test_template_builder_conditional() { | |||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn test_template_builder_loop_with_index() { | fn test_template_builder_loop_with_index() { | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     let template_content = "{% for item in items %}{{loop.index}}: {{item}}\n{% endfor %}"; |     let template_content = "{% for item in items %}{{loop.index}}: {{item}}\n{% endfor %}"; | ||||||
|     fs::write(temp_file.path(), template_content).expect("Failed to write template"); |     fs::write(temp_file.path(), template_content).expect("Failed to write template"); | ||||||
|  |  | ||||||
| @@ -164,7 +166,7 @@ fn test_template_builder_loop_with_index() { | |||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn test_template_builder_nested_variables() { | fn test_template_builder_nested_variables() { | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     let template_content = "User: {{user.name}} ({{user.email}})"; |     let template_content = "User: {{user.name}} ({{user.email}})"; | ||||||
|     fs::write(temp_file.path(), template_content).expect("Failed to write template"); |     fs::write(temp_file.path(), template_content).expect("Failed to write template"); | ||||||
|  |  | ||||||
| @@ -183,7 +185,7 @@ fn test_template_builder_nested_variables() { | |||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn test_template_builder_missing_variable_error() { | fn test_template_builder_missing_variable_error() { | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     let template_content = "Hello {{missing_var}}!"; |     let template_content = "Hello {{missing_var}}!"; | ||||||
|     fs::write(temp_file.path(), template_content).expect("Failed to write template"); |     fs::write(temp_file.path(), template_content).expect("Failed to write template"); | ||||||
|  |  | ||||||
| @@ -196,7 +198,7 @@ fn test_template_builder_missing_variable_error() { | |||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn test_template_builder_invalid_template_syntax() { | fn test_template_builder_invalid_template_syntax() { | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     let template_content = "Hello {{unclosed_var!"; |     let template_content = "Hello {{unclosed_var!"; | ||||||
|     fs::write(temp_file.path(), template_content).expect("Failed to write template"); |     fs::write(temp_file.path(), template_content).expect("Failed to write template"); | ||||||
|  |  | ||||||
| @@ -215,7 +217,7 @@ fn test_template_builder_nonexistent_file() { | |||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn test_template_builder_empty_template() { | fn test_template_builder_empty_template() { | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     fs::write(temp_file.path(), "").expect("Failed to write empty template"); |     fs::write(temp_file.path(), "").expect("Failed to write empty template"); | ||||||
|  |  | ||||||
|     let result = TemplateBuilder::open(temp_file.path()) |     let result = TemplateBuilder::open(temp_file.path()) | ||||||
| @@ -228,7 +230,7 @@ fn test_template_builder_empty_template() { | |||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn test_template_builder_template_with_no_variables() { | fn test_template_builder_template_with_no_variables() { | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     let template_content = "This is a static template with no variables."; |     let template_content = "This is a static template with no variables."; | ||||||
|     fs::write(temp_file.path(), template_content).expect("Failed to write template"); |     fs::write(temp_file.path(), template_content).expect("Failed to write template"); | ||||||
|  |  | ||||||
| @@ -242,7 +244,7 @@ fn test_template_builder_template_with_no_variables() { | |||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn test_template_builder_complex_report() { | fn test_template_builder_complex_report() { | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     let template_content = r#" |     let template_content = r#" | ||||||
| # {{report_title}} | # {{report_title}} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,13 +1,13 @@ | |||||||
| //! Unit tests for text replacement functionality | //! Unit tests for text replacement functionality | ||||||
| //! | //! | ||||||
| //! These tests validate the TextReplacer and TextReplacerBuilder including: | //! These tests validate the TextReplacer including: | ||||||
| //! - Literal string replacement | //! - Literal string replacement | ||||||
| //! - Regex pattern replacement | //! - Regex pattern replacement | ||||||
| //! - Multiple chained replacements | //! - Multiple chained replacements | ||||||
| //! - File operations (read, write, in-place) | //! - File operations (read, write, in-place) | ||||||
| //! - Error handling and edge cases | //! - Error handling and edge cases | ||||||
|  |  | ||||||
| use sal_text::{TextReplacer, TextReplacerBuilder}; | use sal_text::TextReplacer; | ||||||
| use std::fs; | use std::fs; | ||||||
| use tempfile::NamedTempFile; | use tempfile::NamedTempFile; | ||||||
|  |  | ||||||
| @@ -141,7 +141,7 @@ fn test_text_replacer_no_matches() { | |||||||
| #[test] | #[test] | ||||||
| fn test_text_replacer_file_operations() { | fn test_text_replacer_file_operations() { | ||||||
|     // Create a temporary file with test content |     // Create a temporary file with test content | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     let test_content = "Hello world, there are 123 items"; |     let test_content = "Hello world, there are 123 items"; | ||||||
|     fs::write(temp_file.path(), test_content).expect("Failed to write to temp file"); |     fs::write(temp_file.path(), test_content).expect("Failed to write to temp file"); | ||||||
|  |  | ||||||
| @@ -157,18 +157,21 @@ fn test_text_replacer_file_operations() { | |||||||
|         .expect("Failed to build replacer"); |         .expect("Failed to build replacer"); | ||||||
|  |  | ||||||
|     // Test replace_file |     // Test replace_file | ||||||
|     let result = replacer.replace_file(temp_file.path()).expect("Failed to replace file content"); |     let result = replacer | ||||||
|  |         .replace_file(temp_file.path()) | ||||||
|  |         .expect("Failed to replace file content"); | ||||||
|     assert_eq!(result, "Hello universe, there are NUMBER items"); |     assert_eq!(result, "Hello universe, there are NUMBER items"); | ||||||
|  |  | ||||||
|     // Verify original file is unchanged |     // Verify original file is unchanged | ||||||
|     let original_content = fs::read_to_string(temp_file.path()).expect("Failed to read original file"); |     let original_content = | ||||||
|  |         fs::read_to_string(temp_file.path()).expect("Failed to read original file"); | ||||||
|     assert_eq!(original_content, test_content); |     assert_eq!(original_content, test_content); | ||||||
| } | } | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn test_text_replacer_file_in_place() { | fn test_text_replacer_file_in_place() { | ||||||
|     // Create a temporary file with test content |     // Create a temporary file with test content | ||||||
|     let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); |     let temp_file = NamedTempFile::new().expect("Failed to create temp file"); | ||||||
|     let test_content = "Hello world, there are 123 items"; |     let test_content = "Hello world, there are 123 items"; | ||||||
|     fs::write(temp_file.path(), test_content).expect("Failed to write to temp file"); |     fs::write(temp_file.path(), test_content).expect("Failed to write to temp file"); | ||||||
|  |  | ||||||
| @@ -180,7 +183,9 @@ fn test_text_replacer_file_in_place() { | |||||||
|         .expect("Failed to build replacer"); |         .expect("Failed to build replacer"); | ||||||
|  |  | ||||||
|     // Test replace_file_in_place |     // Test replace_file_in_place | ||||||
|     replacer.replace_file_in_place(temp_file.path()).expect("Failed to replace file in place"); |     replacer | ||||||
|  |         .replace_file_in_place(temp_file.path()) | ||||||
|  |         .expect("Failed to replace file in place"); | ||||||
|  |  | ||||||
|     // Verify file content was changed |     // Verify file content was changed | ||||||
|     let new_content = fs::read_to_string(temp_file.path()).expect("Failed to read modified file"); |     let new_content = fs::read_to_string(temp_file.path()).expect("Failed to read modified file"); | ||||||
| @@ -190,7 +195,7 @@ fn test_text_replacer_file_in_place() { | |||||||
| #[test] | #[test] | ||||||
| fn test_text_replacer_file_to_file() { | fn test_text_replacer_file_to_file() { | ||||||
|     // Create source file |     // Create source file | ||||||
|     let mut source_file = NamedTempFile::new().expect("Failed to create source file"); |     let source_file = NamedTempFile::new().expect("Failed to create source file"); | ||||||
|     let test_content = "Hello world, there are 123 items"; |     let test_content = "Hello world, there are 123 items"; | ||||||
|     fs::write(source_file.path(), test_content).expect("Failed to write to source file"); |     fs::write(source_file.path(), test_content).expect("Failed to write to source file"); | ||||||
|  |  | ||||||
| @@ -205,11 +210,13 @@ fn test_text_replacer_file_to_file() { | |||||||
|         .expect("Failed to build replacer"); |         .expect("Failed to build replacer"); | ||||||
|  |  | ||||||
|     // Test replace_file_to |     // Test replace_file_to | ||||||
|     replacer.replace_file_to(source_file.path(), dest_file.path()) |     replacer | ||||||
|  |         .replace_file_to(source_file.path(), dest_file.path()) | ||||||
|         .expect("Failed to replace file to destination"); |         .expect("Failed to replace file to destination"); | ||||||
|  |  | ||||||
|     // Verify source file is unchanged |     // Verify source file is unchanged | ||||||
|     let source_content = fs::read_to_string(source_file.path()).expect("Failed to read source file"); |     let source_content = | ||||||
|  |         fs::read_to_string(source_file.path()).expect("Failed to read source file"); | ||||||
|     assert_eq!(source_content, test_content); |     assert_eq!(source_content, test_content); | ||||||
|  |  | ||||||
|     // Verify destination file has replaced content |     // Verify destination file has replaced content | ||||||
| @@ -263,9 +270,10 @@ fn test_text_replacer_multiline_text() { | |||||||
|         .build() |         .build() | ||||||
|         .expect("Failed to build replacer"); |         .expect("Failed to build replacer"); | ||||||
|  |  | ||||||
|     let input = "function test() {\n    // This is a comment\n    return true;\n    // Another comment\n}"; |     let input = | ||||||
|  |         "function test() {\n    // This is a comment\n    return true;\n    // Another comment\n}"; | ||||||
|     let result = replacer.replace(input); |     let result = replacer.replace(input); | ||||||
|      |  | ||||||
|     // Note: This test depends on how the regex engine handles multiline mode |     // Note: This test depends on how the regex engine handles multiline mode | ||||||
|     // The actual behavior might need adjustment based on regex flags |     // The actual behavior might need adjustment based on regex flags | ||||||
|     assert!(result.contains("function test()")); |     assert!(result.contains("function test()")); | ||||||
| @@ -288,7 +296,7 @@ fn test_text_replacer_unicode_text() { | |||||||
| #[test] | #[test] | ||||||
| fn test_text_replacer_large_text() { | fn test_text_replacer_large_text() { | ||||||
|     let large_text = "word ".repeat(10000); |     let large_text = "word ".repeat(10000); | ||||||
|      |  | ||||||
|     let replacer = TextReplacer::builder() |     let replacer = TextReplacer::builder() | ||||||
|         .pattern("word") |         .pattern("word") | ||||||
|         .replacement("term") |         .replacement("term") | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user