feat: Add support for new OS package
Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run
Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run
- Add a new `sal-os` package containing OS interaction utilities. - Update workspace members to include the new package. - Add README and basic usage examples for the new package.
This commit is contained in:
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.
|
522
os/src/download.rs
Normal file
522
os/src/download.rs
Normal file
@@ -0,0 +1,522 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
// Define a custom error type for download operations
|
||||
#[derive(Debug)]
|
||||
pub enum DownloadError {
|
||||
CreateDirectoryFailed(io::Error),
|
||||
CurlExecutionFailed(io::Error),
|
||||
DownloadFailed(String),
|
||||
FileMetadataError(io::Error),
|
||||
FileTooSmall(i64, i64),
|
||||
RemoveFileFailed(io::Error),
|
||||
ExtractionFailed(String),
|
||||
CommandExecutionFailed(io::Error),
|
||||
InvalidUrl(String),
|
||||
NotAFile(String),
|
||||
PlatformNotSupported(String),
|
||||
InstallationFailed(String),
|
||||
}
|
||||
|
||||
// Implement Display for DownloadError
|
||||
impl fmt::Display for DownloadError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
DownloadError::CreateDirectoryFailed(e) => {
|
||||
write!(f, "Error creating directories: {}", e)
|
||||
}
|
||||
DownloadError::CurlExecutionFailed(e) => write!(f, "Error executing curl: {}", e),
|
||||
DownloadError::DownloadFailed(url) => write!(f, "Error downloading url: {}", url),
|
||||
DownloadError::FileMetadataError(e) => write!(f, "Error getting file metadata: {}", e),
|
||||
DownloadError::FileTooSmall(size, min) => write!(
|
||||
f,
|
||||
"Error: Downloaded file is too small ({}KB < {}KB)",
|
||||
size, min
|
||||
),
|
||||
DownloadError::RemoveFileFailed(e) => write!(f, "Error removing file: {}", e),
|
||||
DownloadError::ExtractionFailed(e) => write!(f, "Error extracting archive: {}", e),
|
||||
DownloadError::CommandExecutionFailed(e) => write!(f, "Error executing command: {}", e),
|
||||
DownloadError::InvalidUrl(url) => write!(f, "Invalid URL: {}", url),
|
||||
DownloadError::NotAFile(path) => write!(f, "Not a file: {}", path),
|
||||
DownloadError::PlatformNotSupported(msg) => write!(f, "{}", msg),
|
||||
DownloadError::InstallationFailed(msg) => write!(f, "{}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Error trait for DownloadError
|
||||
impl Error for DownloadError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
DownloadError::CreateDirectoryFailed(e) => Some(e),
|
||||
DownloadError::CurlExecutionFailed(e) => Some(e),
|
||||
DownloadError::FileMetadataError(e) => Some(e),
|
||||
DownloadError::RemoveFileFailed(e) => Some(e),
|
||||
DownloadError::CommandExecutionFailed(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file from URL to destination using the curl command.
|
||||
* This function is primarily intended for downloading archives that will be extracted
|
||||
* to a directory.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `url` - The URL to download from
|
||||
* * `dest` - The destination directory where the file will be saved or extracted
|
||||
* * `min_size_kb` - Minimum required file size in KB (0 for no minimum)
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(String)` - The path where the file was saved or extracted
|
||||
* * `Err(DownloadError)` - An error if the download failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```no_run
|
||||
* use sal::os::download;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* // Download a file with no minimum size requirement
|
||||
* let path = download("https://example.com/file.txt", "/tmp/", 0)?;
|
||||
*
|
||||
* // Download a file with minimum size requirement of 100KB
|
||||
* let path = download("https://example.com/file.zip", "/tmp/", 100)?;
|
||||
*
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* # Notes
|
||||
*
|
||||
* If the URL ends with .tar.gz, .tgz, .tar, or .zip, the file will be automatically
|
||||
* extracted to the destination directory.
|
||||
*/
|
||||
pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, DownloadError> {
|
||||
// Create parent directories if they don't exist
|
||||
let dest_path = Path::new(dest);
|
||||
fs::create_dir_all(dest_path).map_err(DownloadError::CreateDirectoryFailed)?;
|
||||
|
||||
// Extract filename from URL
|
||||
let filename = match url.split('/').last() {
|
||||
Some(name) => name,
|
||||
None => {
|
||||
return Err(DownloadError::InvalidUrl(
|
||||
"cannot extract filename".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// Create a full path for the downloaded file
|
||||
let file_path = format!("{}/{}", dest.trim_end_matches('/'), filename);
|
||||
|
||||
// Create a temporary path for downloading
|
||||
let temp_path = format!("{}.download", file_path);
|
||||
|
||||
// Use curl to download the file with progress bar
|
||||
println!("Downloading {} to {}", url, file_path);
|
||||
let output = Command::new("curl")
|
||||
.args(&[
|
||||
"--progress-bar",
|
||||
"--location",
|
||||
"--fail",
|
||||
"--output",
|
||||
&temp_path,
|
||||
url,
|
||||
])
|
||||
.status()
|
||||
.map_err(DownloadError::CurlExecutionFailed)?;
|
||||
|
||||
if !output.success() {
|
||||
return Err(DownloadError::DownloadFailed(url.to_string()));
|
||||
}
|
||||
|
||||
// Show file size after download
|
||||
match fs::metadata(&temp_path) {
|
||||
Ok(metadata) => {
|
||||
let size_bytes = metadata.len();
|
||||
let size_kb = size_bytes / 1024;
|
||||
let size_mb = size_kb / 1024;
|
||||
if size_mb > 1 {
|
||||
println!(
|
||||
"Download complete! File size: {:.2} MB",
|
||||
size_bytes as f64 / (1024.0 * 1024.0)
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"Download complete! File size: {:.2} KB",
|
||||
size_bytes as f64 / 1024.0
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(_) => println!("Download complete!"),
|
||||
}
|
||||
|
||||
// Check file size if minimum size is specified
|
||||
if min_size_kb > 0 {
|
||||
let metadata = fs::metadata(&temp_path).map_err(DownloadError::FileMetadataError)?;
|
||||
let size_kb = metadata.len() as i64 / 1024;
|
||||
if size_kb < min_size_kb {
|
||||
fs::remove_file(&temp_path).map_err(DownloadError::RemoveFileFailed)?;
|
||||
return Err(DownloadError::FileTooSmall(size_kb, min_size_kb));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a compressed file that needs extraction
|
||||
let lower_url = url.to_lowercase();
|
||||
let is_archive = lower_url.ends_with(".tar.gz")
|
||||
|| lower_url.ends_with(".tgz")
|
||||
|| lower_url.ends_with(".tar")
|
||||
|| lower_url.ends_with(".zip");
|
||||
|
||||
if is_archive {
|
||||
// Extract the file using the appropriate command with progress indication
|
||||
println!("Extracting {} to {}", temp_path, dest);
|
||||
let output = if lower_url.ends_with(".zip") {
|
||||
Command::new("unzip")
|
||||
.args(&["-o", &temp_path, "-d", dest]) // Removed -q for verbosity
|
||||
.status()
|
||||
} else if lower_url.ends_with(".tar.gz") || lower_url.ends_with(".tgz") {
|
||||
Command::new("tar")
|
||||
.args(&["-xzvf", &temp_path, "-C", dest]) // Added v for verbosity
|
||||
.status()
|
||||
} else {
|
||||
Command::new("tar")
|
||||
.args(&["-xvf", &temp_path, "-C", dest]) // Added v for verbosity
|
||||
.status()
|
||||
};
|
||||
|
||||
match output {
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
return Err(DownloadError::ExtractionFailed(
|
||||
"Error extracting archive".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
|
||||
}
|
||||
|
||||
// Show number of extracted files
|
||||
match fs::read_dir(dest) {
|
||||
Ok(entries) => {
|
||||
let count = entries.count();
|
||||
println!("Extraction complete! Extracted {} files/directories", count);
|
||||
}
|
||||
Err(_) => println!("Extraction complete!"),
|
||||
}
|
||||
|
||||
// Remove the temporary file
|
||||
fs::remove_file(&temp_path).map_err(DownloadError::RemoveFileFailed)?;
|
||||
|
||||
Ok(dest.to_string())
|
||||
} else {
|
||||
// Just rename the temporary file to the final destination
|
||||
fs::rename(&temp_path, &file_path).map_err(|e| DownloadError::CreateDirectoryFailed(e))?;
|
||||
|
||||
Ok(file_path)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file from URL to a specific file destination using the curl command.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `url` - The URL to download from
|
||||
* * `dest` - The destination file path where the file will be saved
|
||||
* * `min_size_kb` - Minimum required file size in KB (0 for no minimum)
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(String)` - The path where the file was saved
|
||||
* * `Err(DownloadError)` - An error if the download failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```no_run
|
||||
* use sal::os::download_file;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* // Download a file with no minimum size requirement
|
||||
* let path = download_file("https://example.com/file.txt", "/tmp/file.txt", 0)?;
|
||||
*
|
||||
* // Download a file with minimum size requirement of 100KB
|
||||
* let path = download_file("https://example.com/file.zip", "/tmp/file.zip", 100)?;
|
||||
*
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String, DownloadError> {
|
||||
// Create parent directories if they don't exist
|
||||
let dest_path = Path::new(dest);
|
||||
if let Some(parent) = dest_path.parent() {
|
||||
fs::create_dir_all(parent).map_err(DownloadError::CreateDirectoryFailed)?;
|
||||
}
|
||||
|
||||
// Create a temporary path for downloading
|
||||
let temp_path = format!("{}.download", dest);
|
||||
|
||||
// Use curl to download the file with progress bar
|
||||
println!("Downloading {} to {}", url, dest);
|
||||
let output = Command::new("curl")
|
||||
.args(&[
|
||||
"--progress-bar",
|
||||
"--location",
|
||||
"--fail",
|
||||
"--output",
|
||||
&temp_path,
|
||||
url,
|
||||
])
|
||||
.status()
|
||||
.map_err(DownloadError::CurlExecutionFailed)?;
|
||||
|
||||
if !output.success() {
|
||||
return Err(DownloadError::DownloadFailed(url.to_string()));
|
||||
}
|
||||
|
||||
// Show file size after download
|
||||
match fs::metadata(&temp_path) {
|
||||
Ok(metadata) => {
|
||||
let size_bytes = metadata.len();
|
||||
let size_kb = size_bytes / 1024;
|
||||
let size_mb = size_kb / 1024;
|
||||
if size_mb > 1 {
|
||||
println!(
|
||||
"Download complete! File size: {:.2} MB",
|
||||
size_bytes as f64 / (1024.0 * 1024.0)
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"Download complete! File size: {:.2} KB",
|
||||
size_bytes as f64 / 1024.0
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(_) => println!("Download complete!"),
|
||||
}
|
||||
|
||||
// Check file size if minimum size is specified
|
||||
if min_size_kb > 0 {
|
||||
let metadata = fs::metadata(&temp_path).map_err(DownloadError::FileMetadataError)?;
|
||||
let size_kb = metadata.len() as i64 / 1024;
|
||||
if size_kb < min_size_kb {
|
||||
fs::remove_file(&temp_path).map_err(DownloadError::RemoveFileFailed)?;
|
||||
return Err(DownloadError::FileTooSmall(size_kb, min_size_kb));
|
||||
}
|
||||
}
|
||||
|
||||
// Rename the temporary file to the final destination
|
||||
fs::rename(&temp_path, dest).map_err(|e| DownloadError::CreateDirectoryFailed(e))?;
|
||||
|
||||
Ok(dest.to_string())
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a file executable (equivalent to chmod +x).
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `path` - The path to the file to make executable
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(String)` - A success message including the path
|
||||
* * `Err(DownloadError)` - An error if the operation failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```no_run
|
||||
* use sal::os::chmod_exec;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* // Make a file executable
|
||||
* chmod_exec("/path/to/file")?;
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
|
||||
let path_obj = Path::new(path);
|
||||
|
||||
// Check if the path exists and is a file
|
||||
if !path_obj.exists() {
|
||||
return Err(DownloadError::NotAFile(format!(
|
||||
"Path does not exist: {}",
|
||||
path
|
||||
)));
|
||||
}
|
||||
|
||||
if !path_obj.is_file() {
|
||||
return Err(DownloadError::NotAFile(format!(
|
||||
"Path is not a file: {}",
|
||||
path
|
||||
)));
|
||||
}
|
||||
|
||||
// Get current permissions
|
||||
let metadata = fs::metadata(path).map_err(DownloadError::FileMetadataError)?;
|
||||
let mut permissions = metadata.permissions();
|
||||
|
||||
// Set executable bit for user, group, and others
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mode = permissions.mode();
|
||||
// Add executable bit for user, group, and others (equivalent to +x)
|
||||
let new_mode = mode | 0o111;
|
||||
permissions.set_mode(new_mode);
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
// On non-Unix platforms, we can't set executable bit directly
|
||||
// Just return success with a warning
|
||||
return Ok(format!(
|
||||
"Made {} executable (note: non-Unix platform, may not be fully supported)",
|
||||
path
|
||||
));
|
||||
}
|
||||
|
||||
// Apply the new permissions
|
||||
fs::set_permissions(path, permissions).map_err(|e| {
|
||||
DownloadError::CommandExecutionFailed(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Failed to set executable permissions: {}", e),
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(format!("Made {} executable", path))
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file and install it if it's a supported package format.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `url` - The URL to download from
|
||||
* * `min_size_kb` - Minimum required file size in KB (0 for no minimum)
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(String)` - The path where the file was saved or extracted
|
||||
* * `Err(DownloadError)` - An error if the download or installation failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```no_run
|
||||
* use sal::os::download_install;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* // Download and install a .deb package
|
||||
* let result = download_install("https://example.com/package.deb", 100)?;
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* # Notes
|
||||
*
|
||||
* Currently only supports .deb packages on Debian-based systems.
|
||||
* For other file types, it behaves the same as the download function.
|
||||
*/
|
||||
pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadError> {
|
||||
// Extract filename from URL
|
||||
let filename = match url.split('/').last() {
|
||||
Some(name) => name,
|
||||
None => {
|
||||
return Err(DownloadError::InvalidUrl(
|
||||
"cannot extract filename".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// Create a proper destination path
|
||||
let dest_path = format!("/tmp/{}", filename);
|
||||
|
||||
// Check if it's a compressed file that needs extraction
|
||||
let lower_url = url.to_lowercase();
|
||||
let is_archive = lower_url.ends_with(".tar.gz")
|
||||
|| lower_url.ends_with(".tgz")
|
||||
|| lower_url.ends_with(".tar")
|
||||
|| lower_url.ends_with(".zip");
|
||||
|
||||
let download_result = if is_archive {
|
||||
// For archives, use the directory-based download function
|
||||
download(url, "/tmp", min_size_kb)?
|
||||
} else {
|
||||
// For regular files, use the file-specific download function
|
||||
download_file(url, &dest_path, min_size_kb)?
|
||||
};
|
||||
|
||||
// Check if the downloaded result is a file
|
||||
let path = Path::new(&dest_path);
|
||||
if !path.is_file() {
|
||||
return Ok(download_result); // Not a file, might be an extracted directory
|
||||
}
|
||||
|
||||
// Check if it's a .deb package
|
||||
if dest_path.to_lowercase().ends_with(".deb") {
|
||||
// Check if we're on a Debian-based platform
|
||||
let platform_check = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg("command -v dpkg > /dev/null && command -v apt > /dev/null || test -f /etc/debian_version")
|
||||
.status();
|
||||
|
||||
match platform_check {
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
return Err(DownloadError::PlatformNotSupported(
|
||||
"Cannot install .deb package: not on a Debian-based system".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(DownloadError::PlatformNotSupported(
|
||||
"Failed to check system compatibility for .deb installation".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Install the .deb package non-interactively
|
||||
println!("Installing package: {}", dest_path);
|
||||
let install_result = Command::new("sudo")
|
||||
.args(&["dpkg", "--install", &dest_path])
|
||||
.status();
|
||||
|
||||
match install_result {
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
// If dpkg fails, try to fix dependencies and retry
|
||||
println!("Attempting to resolve dependencies...");
|
||||
let fix_deps = Command::new("sudo")
|
||||
.args(&["apt-get", "install", "-f", "-y"])
|
||||
.status();
|
||||
|
||||
if let Ok(fix_status) = fix_deps {
|
||||
if !fix_status.success() {
|
||||
return Err(DownloadError::InstallationFailed(
|
||||
"Failed to resolve package dependencies".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(DownloadError::InstallationFailed(
|
||||
"Failed to resolve package dependencies".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
println!("Package installation completed successfully");
|
||||
}
|
||||
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(download_result)
|
||||
}
|
1185
os/src/fs.rs
Normal file
1185
os/src/fs.rs
Normal file
File diff suppressed because it is too large
Load Diff
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;
|
972
os/src/package.rs
Normal file
972
os/src/package.rs
Normal file
@@ -0,0 +1,972 @@
|
||||
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
|
||||
#[derive(Debug)]
|
||||
pub enum PackageError {
|
||||
/// Command failed with error message
|
||||
CommandFailed(String),
|
||||
/// Command execution failed with IO error
|
||||
CommandExecutionFailed(std::io::Error),
|
||||
/// Unsupported platform
|
||||
UnsupportedPlatform(String),
|
||||
/// Other error
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PackageError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
PackageError::CommandFailed(msg) => write!(f, "Command failed: {}", msg),
|
||||
PackageError::CommandExecutionFailed(e) => write!(f, "Command execution failed: {}", e),
|
||||
PackageError::UnsupportedPlatform(msg) => write!(f, "Unsupported platform: {}", msg),
|
||||
PackageError::Other(msg) => write!(f, "Error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for PackageError {}
|
||||
|
||||
/// Platform enum for detecting the current operating system
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Platform {
|
||||
/// Ubuntu Linux
|
||||
Ubuntu,
|
||||
/// macOS
|
||||
MacOS,
|
||||
/// Unknown platform
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
/// Detect the current platform
|
||||
pub fn detect() -> Self {
|
||||
// Check for macOS
|
||||
if std::path::Path::new("/usr/bin/sw_vers").exists() {
|
||||
return Platform::MacOS;
|
||||
}
|
||||
|
||||
// Check for Ubuntu
|
||||
if std::path::Path::new("/etc/lsb-release").exists() {
|
||||
// Read the file to confirm it's Ubuntu
|
||||
if let Ok(content) = std::fs::read_to_string("/etc/lsb-release") {
|
||||
if content.contains("Ubuntu") {
|
||||
return Platform::Ubuntu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Platform::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
// Thread-local storage for debug flag
|
||||
thread_local! {
|
||||
static DEBUG: std::cell::RefCell<bool> = std::cell::RefCell::new(false);
|
||||
}
|
||||
|
||||
/// Set the debug flag for the current thread
|
||||
pub fn set_thread_local_debug(debug: bool) {
|
||||
DEBUG.with(|cell| {
|
||||
*cell.borrow_mut() = debug;
|
||||
});
|
||||
}
|
||||
|
||||
/// Get the debug flag for the current thread
|
||||
pub fn thread_local_debug() -> bool {
|
||||
DEBUG.with(|cell| *cell.borrow())
|
||||
}
|
||||
|
||||
/// Execute a package management command and return the result
|
||||
pub fn execute_package_command(args: &[&str], debug: bool) -> Result<CommandResult, PackageError> {
|
||||
// Save the current debug flag
|
||||
let previous_debug = thread_local_debug();
|
||||
|
||||
// Set the thread-local debug flag
|
||||
set_thread_local_debug(debug);
|
||||
|
||||
if debug {
|
||||
println!("Executing command: {}", args.join(" "));
|
||||
}
|
||||
|
||||
let output = Command::new(args[0]).args(&args[1..]).output();
|
||||
|
||||
// Restore the previous debug flag
|
||||
set_thread_local_debug(previous_debug);
|
||||
|
||||
match output {
|
||||
Ok(output) => {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
||||
|
||||
let result = CommandResult {
|
||||
stdout,
|
||||
stderr,
|
||||
success: output.status.success(),
|
||||
code: output.status.code().unwrap_or(-1),
|
||||
};
|
||||
|
||||
// Always output stdout/stderr when debug is true
|
||||
if debug {
|
||||
if !result.stdout.is_empty() {
|
||||
println!("Command stdout: {}", result.stdout);
|
||||
}
|
||||
|
||||
if !result.stderr.is_empty() {
|
||||
println!("Command stderr: {}", result.stderr);
|
||||
}
|
||||
|
||||
if result.success {
|
||||
println!("Command succeeded with code {}", result.code);
|
||||
} else {
|
||||
println!("Command failed with code {}", result.code);
|
||||
}
|
||||
}
|
||||
|
||||
if result.success {
|
||||
Ok(result)
|
||||
} else {
|
||||
// If command failed and debug is false, output stderr
|
||||
if !debug {
|
||||
println!(
|
||||
"Command failed with code {}: {}",
|
||||
result.code,
|
||||
result.stderr.trim()
|
||||
);
|
||||
}
|
||||
Err(PackageError::CommandFailed(format!(
|
||||
"Command failed with code {}: {}",
|
||||
result.code,
|
||||
result.stderr.trim()
|
||||
)))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// Always output error information
|
||||
println!("Command execution failed: {}", e);
|
||||
Err(PackageError::CommandExecutionFailed(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for package managers
|
||||
pub trait PackageManager {
|
||||
/// Install a package
|
||||
fn install(&self, package: &str) -> Result<CommandResult, PackageError>;
|
||||
|
||||
/// Remove a package
|
||||
fn remove(&self, package: &str) -> Result<CommandResult, PackageError>;
|
||||
|
||||
/// Update package lists
|
||||
fn update(&self) -> Result<CommandResult, PackageError>;
|
||||
|
||||
/// Upgrade installed packages
|
||||
fn upgrade(&self) -> Result<CommandResult, PackageError>;
|
||||
|
||||
/// List installed packages
|
||||
fn list_installed(&self) -> Result<Vec<String>, PackageError>;
|
||||
|
||||
/// Search for packages
|
||||
fn search(&self, query: &str) -> Result<Vec<String>, PackageError>;
|
||||
|
||||
/// Check if a package is installed
|
||||
fn is_installed(&self, package: &str) -> Result<bool, PackageError>;
|
||||
}
|
||||
|
||||
/// APT package manager for Ubuntu
|
||||
pub struct AptPackageManager {
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
impl AptPackageManager {
|
||||
/// Create a new APT package manager
|
||||
pub fn new(debug: bool) -> Self {
|
||||
Self { debug }
|
||||
}
|
||||
}
|
||||
|
||||
impl PackageManager for AptPackageManager {
|
||||
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
// Use -y to make it non-interactive and --quiet to reduce output
|
||||
execute_package_command(
|
||||
&["apt-get", "install", "-y", "--quiet", package],
|
||||
self.debug,
|
||||
)
|
||||
}
|
||||
|
||||
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
// Use -y to make it non-interactive and --quiet to reduce output
|
||||
execute_package_command(&["apt-get", "remove", "-y", "--quiet", package], self.debug)
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<CommandResult, PackageError> {
|
||||
// Use -y to make it non-interactive and --quiet to reduce output
|
||||
execute_package_command(&["apt-get", "update", "-y", "--quiet"], self.debug)
|
||||
}
|
||||
|
||||
fn upgrade(&self) -> Result<CommandResult, PackageError> {
|
||||
// Use -y to make it non-interactive and --quiet to reduce output
|
||||
execute_package_command(&["apt-get", "upgrade", "-y", "--quiet"], self.debug)
|
||||
}
|
||||
|
||||
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
|
||||
let result = execute_package_command(&["dpkg", "--get-selections"], self.debug)?;
|
||||
let packages = result
|
||||
.stdout
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() >= 2 && parts[1] == "install" {
|
||||
Some(parts[0].to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Ok(packages)
|
||||
}
|
||||
|
||||
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
|
||||
let result = execute_package_command(&["apt-cache", "search", query], self.debug)?;
|
||||
let packages = result
|
||||
.stdout
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if !parts.is_empty() {
|
||||
parts[0].to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
})
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
Ok(packages)
|
||||
}
|
||||
|
||||
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
|
||||
let result = execute_package_command(&["dpkg", "-s", package], self.debug);
|
||||
match result {
|
||||
Ok(cmd_result) => Ok(cmd_result.success),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Homebrew package manager for macOS
|
||||
pub struct BrewPackageManager {
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
impl BrewPackageManager {
|
||||
/// Create a new Homebrew package manager
|
||||
pub fn new(debug: bool) -> Self {
|
||||
Self { debug }
|
||||
}
|
||||
}
|
||||
|
||||
impl PackageManager for BrewPackageManager {
|
||||
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
// Use --quiet to reduce output
|
||||
execute_package_command(&["brew", "install", "--quiet", package], self.debug)
|
||||
}
|
||||
|
||||
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
// Use --quiet to reduce output
|
||||
execute_package_command(&["brew", "uninstall", "--quiet", package], self.debug)
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<CommandResult, PackageError> {
|
||||
// Use --quiet to reduce output
|
||||
execute_package_command(&["brew", "update", "--quiet"], self.debug)
|
||||
}
|
||||
|
||||
fn upgrade(&self) -> Result<CommandResult, PackageError> {
|
||||
// Use --quiet to reduce output
|
||||
execute_package_command(&["brew", "upgrade", "--quiet"], self.debug)
|
||||
}
|
||||
|
||||
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
|
||||
let result = execute_package_command(&["brew", "list", "--formula"], self.debug)?;
|
||||
let packages = result
|
||||
.stdout
|
||||
.lines()
|
||||
.map(|line| line.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
Ok(packages)
|
||||
}
|
||||
|
||||
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
|
||||
let result = execute_package_command(&["brew", "search", query], self.debug)?;
|
||||
let packages = result
|
||||
.stdout
|
||||
.lines()
|
||||
.map(|line| line.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
Ok(packages)
|
||||
}
|
||||
|
||||
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
|
||||
let result = execute_package_command(&["brew", "list", package], self.debug);
|
||||
match result {
|
||||
Ok(cmd_result) => Ok(cmd_result.success),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// PackHero factory for package management
|
||||
pub struct PackHero {
|
||||
platform: Platform,
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
impl PackHero {
|
||||
/// Create a new PackHero instance
|
||||
pub fn new() -> Self {
|
||||
let platform = Platform::detect();
|
||||
Self {
|
||||
platform,
|
||||
debug: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the debug mode
|
||||
pub fn set_debug(&mut self, debug: bool) -> &mut Self {
|
||||
self.debug = debug;
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the debug mode
|
||||
pub fn debug(&self) -> bool {
|
||||
self.debug
|
||||
}
|
||||
|
||||
/// Get the detected platform
|
||||
pub fn platform(&self) -> Platform {
|
||||
self.platform
|
||||
}
|
||||
|
||||
/// Get a package manager for the current platform
|
||||
fn get_package_manager(&self) -> Result<Box<dyn PackageManager>, PackageError> {
|
||||
match self.platform {
|
||||
Platform::Ubuntu => Ok(Box::new(AptPackageManager::new(self.debug))),
|
||||
Platform::MacOS => Ok(Box::new(BrewPackageManager::new(self.debug))),
|
||||
Platform::Unknown => Err(PackageError::UnsupportedPlatform(
|
||||
"Unsupported platform".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Install a package
|
||||
pub fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.install(package)
|
||||
}
|
||||
|
||||
/// Remove a package
|
||||
pub fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.remove(package)
|
||||
}
|
||||
|
||||
/// Update package lists
|
||||
pub fn update(&self) -> Result<CommandResult, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.update()
|
||||
}
|
||||
|
||||
/// Upgrade installed packages
|
||||
pub fn upgrade(&self) -> Result<CommandResult, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.upgrade()
|
||||
}
|
||||
|
||||
/// List installed packages
|
||||
pub fn list_installed(&self) -> Result<Vec<String>, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.list_installed()
|
||||
}
|
||||
|
||||
/// Search for packages
|
||||
pub fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.search(query)
|
||||
}
|
||||
|
||||
/// Check if a package is installed
|
||||
pub fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.is_installed(package)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// Import the std::process::Command directly for some test-specific commands
|
||||
use super::*;
|
||||
use std::process::Command as StdCommand;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[test]
|
||||
fn test_platform_detection() {
|
||||
// This test will return different results depending on the platform it's run on
|
||||
let platform = Platform::detect();
|
||||
println!("Detected platform: {:?}", platform);
|
||||
|
||||
// Just ensure it doesn't panic
|
||||
assert!(true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug_flag() {
|
||||
// Test setting and getting the debug flag
|
||||
set_thread_local_debug(true);
|
||||
assert_eq!(thread_local_debug(), true);
|
||||
|
||||
set_thread_local_debug(false);
|
||||
assert_eq!(thread_local_debug(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_package_error_display() {
|
||||
// Test the Display implementation for PackageError
|
||||
let err1 = PackageError::CommandFailed("command failed".to_string());
|
||||
assert_eq!(err1.to_string(), "Command failed: command failed");
|
||||
|
||||
let err2 = PackageError::UnsupportedPlatform("test platform".to_string());
|
||||
assert_eq!(err2.to_string(), "Unsupported platform: test platform");
|
||||
|
||||
let err3 = PackageError::Other("other error".to_string());
|
||||
assert_eq!(err3.to_string(), "Error: other error");
|
||||
|
||||
// We can't easily test CommandExecutionFailed because std::io::Error doesn't implement PartialEq
|
||||
}
|
||||
|
||||
// Mock package manager for testing
|
||||
struct MockPackageManager {
|
||||
// debug field is kept for consistency with real package managers
|
||||
#[allow(dead_code)]
|
||||
debug: bool,
|
||||
install_called: Arc<Mutex<bool>>,
|
||||
remove_called: Arc<Mutex<bool>>,
|
||||
update_called: Arc<Mutex<bool>>,
|
||||
upgrade_called: Arc<Mutex<bool>>,
|
||||
list_installed_called: Arc<Mutex<bool>>,
|
||||
search_called: Arc<Mutex<bool>>,
|
||||
is_installed_called: Arc<Mutex<bool>>,
|
||||
// Control what the mock returns
|
||||
should_succeed: bool,
|
||||
}
|
||||
|
||||
impl MockPackageManager {
|
||||
fn new(debug: bool, should_succeed: bool) -> Self {
|
||||
Self {
|
||||
debug,
|
||||
install_called: Arc::new(Mutex::new(false)),
|
||||
remove_called: Arc::new(Mutex::new(false)),
|
||||
update_called: Arc::new(Mutex::new(false)),
|
||||
upgrade_called: Arc::new(Mutex::new(false)),
|
||||
list_installed_called: Arc::new(Mutex::new(false)),
|
||||
search_called: Arc::new(Mutex::new(false)),
|
||||
is_installed_called: Arc::new(Mutex::new(false)),
|
||||
should_succeed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PackageManager for MockPackageManager {
|
||||
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
*self.install_called.lock().unwrap() = true;
|
||||
if self.should_succeed {
|
||||
Ok(CommandResult {
|
||||
stdout: format!("Installed package {}", package),
|
||||
stderr: String::new(),
|
||||
success: true,
|
||||
code: 0,
|
||||
})
|
||||
} else {
|
||||
Err(PackageError::CommandFailed(
|
||||
"Mock install failed".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
*self.remove_called.lock().unwrap() = true;
|
||||
if self.should_succeed {
|
||||
Ok(CommandResult {
|
||||
stdout: format!("Removed package {}", package),
|
||||
stderr: String::new(),
|
||||
success: true,
|
||||
code: 0,
|
||||
})
|
||||
} else {
|
||||
Err(PackageError::CommandFailed(
|
||||
"Mock remove failed".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<CommandResult, PackageError> {
|
||||
*self.update_called.lock().unwrap() = true;
|
||||
if self.should_succeed {
|
||||
Ok(CommandResult {
|
||||
stdout: "Updated package lists".to_string(),
|
||||
stderr: String::new(),
|
||||
success: true,
|
||||
code: 0,
|
||||
})
|
||||
} else {
|
||||
Err(PackageError::CommandFailed(
|
||||
"Mock update failed".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn upgrade(&self) -> Result<CommandResult, PackageError> {
|
||||
*self.upgrade_called.lock().unwrap() = true;
|
||||
if self.should_succeed {
|
||||
Ok(CommandResult {
|
||||
stdout: "Upgraded packages".to_string(),
|
||||
stderr: String::new(),
|
||||
success: true,
|
||||
code: 0,
|
||||
})
|
||||
} else {
|
||||
Err(PackageError::CommandFailed(
|
||||
"Mock upgrade failed".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
|
||||
*self.list_installed_called.lock().unwrap() = true;
|
||||
if self.should_succeed {
|
||||
Ok(vec!["package1".to_string(), "package2".to_string()])
|
||||
} else {
|
||||
Err(PackageError::CommandFailed(
|
||||
"Mock list_installed failed".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
|
||||
*self.search_called.lock().unwrap() = true;
|
||||
if self.should_succeed {
|
||||
Ok(vec![
|
||||
format!("result1-{}", query),
|
||||
format!("result2-{}", query),
|
||||
])
|
||||
} else {
|
||||
Err(PackageError::CommandFailed(
|
||||
"Mock search failed".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
|
||||
*self.is_installed_called.lock().unwrap() = true;
|
||||
if self.should_succeed {
|
||||
Ok(package == "installed-package")
|
||||
} else {
|
||||
Err(PackageError::CommandFailed(
|
||||
"Mock is_installed failed".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom PackHero for testing with a mock package manager
|
||||
struct TestPackHero {
|
||||
platform: Platform,
|
||||
#[allow(dead_code)]
|
||||
debug: bool,
|
||||
mock_manager: MockPackageManager,
|
||||
}
|
||||
|
||||
impl TestPackHero {
|
||||
fn new(platform: Platform, debug: bool, should_succeed: bool) -> Self {
|
||||
Self {
|
||||
platform,
|
||||
debug,
|
||||
mock_manager: MockPackageManager::new(debug, should_succeed),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_package_manager(&self) -> Result<&dyn PackageManager, PackageError> {
|
||||
match self.platform {
|
||||
Platform::Ubuntu | Platform::MacOS => Ok(&self.mock_manager),
|
||||
Platform::Unknown => Err(PackageError::UnsupportedPlatform(
|
||||
"Unsupported platform".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.install(package)
|
||||
}
|
||||
|
||||
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.remove(package)
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<CommandResult, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.update()
|
||||
}
|
||||
|
||||
fn upgrade(&self) -> Result<CommandResult, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.upgrade()
|
||||
}
|
||||
|
||||
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.list_installed()
|
||||
}
|
||||
|
||||
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.search(query)
|
||||
}
|
||||
|
||||
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.is_installed(package)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_packhero_with_mock_success() {
|
||||
// Test PackHero with a mock package manager that succeeds
|
||||
let hero = TestPackHero::new(Platform::Ubuntu, false, true);
|
||||
|
||||
// Test install
|
||||
let result = hero.install("test-package");
|
||||
assert!(result.is_ok());
|
||||
assert!(*hero.mock_manager.install_called.lock().unwrap());
|
||||
|
||||
// Test remove
|
||||
let result = hero.remove("test-package");
|
||||
assert!(result.is_ok());
|
||||
assert!(*hero.mock_manager.remove_called.lock().unwrap());
|
||||
|
||||
// Test update
|
||||
let result = hero.update();
|
||||
assert!(result.is_ok());
|
||||
assert!(*hero.mock_manager.update_called.lock().unwrap());
|
||||
|
||||
// Test upgrade
|
||||
let result = hero.upgrade();
|
||||
assert!(result.is_ok());
|
||||
assert!(*hero.mock_manager.upgrade_called.lock().unwrap());
|
||||
|
||||
// Test list_installed
|
||||
let result = hero.list_installed();
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
result.unwrap(),
|
||||
vec!["package1".to_string(), "package2".to_string()]
|
||||
);
|
||||
assert!(*hero.mock_manager.list_installed_called.lock().unwrap());
|
||||
|
||||
// Test search
|
||||
let result = hero.search("query");
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
result.unwrap(),
|
||||
vec!["result1-query".to_string(), "result2-query".to_string()]
|
||||
);
|
||||
assert!(*hero.mock_manager.search_called.lock().unwrap());
|
||||
|
||||
// Test is_installed
|
||||
let result = hero.is_installed("installed-package");
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
assert!(*hero.mock_manager.is_installed_called.lock().unwrap());
|
||||
|
||||
let result = hero.is_installed("not-installed-package");
|
||||
assert!(result.is_ok());
|
||||
assert!(!result.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_packhero_with_mock_failure() {
|
||||
// Test PackHero with a mock package manager that fails
|
||||
let hero = TestPackHero::new(Platform::Ubuntu, false, false);
|
||||
|
||||
// Test install
|
||||
let result = hero.install("test-package");
|
||||
assert!(result.is_err());
|
||||
assert!(*hero.mock_manager.install_called.lock().unwrap());
|
||||
|
||||
// Test remove
|
||||
let result = hero.remove("test-package");
|
||||
assert!(result.is_err());
|
||||
assert!(*hero.mock_manager.remove_called.lock().unwrap());
|
||||
|
||||
// Test update
|
||||
let result = hero.update();
|
||||
assert!(result.is_err());
|
||||
assert!(*hero.mock_manager.update_called.lock().unwrap());
|
||||
|
||||
// Test upgrade
|
||||
let result = hero.upgrade();
|
||||
assert!(result.is_err());
|
||||
assert!(*hero.mock_manager.upgrade_called.lock().unwrap());
|
||||
|
||||
// Test list_installed
|
||||
let result = hero.list_installed();
|
||||
assert!(result.is_err());
|
||||
assert!(*hero.mock_manager.list_installed_called.lock().unwrap());
|
||||
|
||||
// Test search
|
||||
let result = hero.search("query");
|
||||
assert!(result.is_err());
|
||||
assert!(*hero.mock_manager.search_called.lock().unwrap());
|
||||
|
||||
// Test is_installed
|
||||
let result = hero.is_installed("installed-package");
|
||||
assert!(result.is_err());
|
||||
assert!(*hero.mock_manager.is_installed_called.lock().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_packhero_unsupported_platform() {
|
||||
// Test PackHero with an unsupported platform
|
||||
let hero = TestPackHero::new(Platform::Unknown, false, true);
|
||||
|
||||
// All operations should fail with UnsupportedPlatform error
|
||||
let result = hero.install("test-package");
|
||||
assert!(result.is_err());
|
||||
match result {
|
||||
Err(PackageError::UnsupportedPlatform(_)) => (),
|
||||
_ => panic!("Expected UnsupportedPlatform error"),
|
||||
}
|
||||
|
||||
let result = hero.remove("test-package");
|
||||
assert!(result.is_err());
|
||||
match result {
|
||||
Err(PackageError::UnsupportedPlatform(_)) => (),
|
||||
_ => panic!("Expected UnsupportedPlatform error"),
|
||||
}
|
||||
|
||||
let result = hero.update();
|
||||
assert!(result.is_err());
|
||||
match result {
|
||||
Err(PackageError::UnsupportedPlatform(_)) => (),
|
||||
_ => panic!("Expected UnsupportedPlatform error"),
|
||||
}
|
||||
}
|
||||
|
||||
// Real-world tests that actually install and remove packages on Ubuntu
|
||||
// These tests will only run on Ubuntu and will be skipped on other platforms
|
||||
#[test]
|
||||
fn test_real_package_operations_on_ubuntu() {
|
||||
// Check if we're on Ubuntu
|
||||
let platform = Platform::detect();
|
||||
if platform != Platform::Ubuntu {
|
||||
println!(
|
||||
"Skipping real package operations test on non-Ubuntu platform: {:?}",
|
||||
platform
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
println!("Running real package operations test on Ubuntu");
|
||||
|
||||
// Create a PackHero instance with debug enabled
|
||||
let mut hero = PackHero::new();
|
||||
hero.set_debug(true);
|
||||
|
||||
// Test package to install/remove
|
||||
let test_package = "wget";
|
||||
|
||||
// First, check if the package is already installed
|
||||
let is_installed_before = match hero.is_installed(test_package) {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
println!("Error checking if package is installed: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
println!(
|
||||
"Package {} is installed before test: {}",
|
||||
test_package, is_installed_before
|
||||
);
|
||||
|
||||
// If the package is already installed, we'll remove it first
|
||||
if is_installed_before {
|
||||
println!("Removing existing package {} before test", test_package);
|
||||
match hero.remove(test_package) {
|
||||
Ok(_) => println!("Successfully removed package {}", test_package),
|
||||
Err(e) => {
|
||||
println!("Error removing package {}: {}", test_package, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify it was removed
|
||||
match hero.is_installed(test_package) {
|
||||
Ok(is_installed) => {
|
||||
if is_installed {
|
||||
println!("Failed to remove package {}", test_package);
|
||||
return;
|
||||
} else {
|
||||
println!("Verified package {} was removed", test_package);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!(
|
||||
"Error checking if package is installed after removal: {}",
|
||||
e
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now install the package
|
||||
println!("Installing package {}", test_package);
|
||||
match hero.install(test_package) {
|
||||
Ok(_) => println!("Successfully installed package {}", test_package),
|
||||
Err(e) => {
|
||||
println!("Error installing package {}: {}", test_package, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify it was installed
|
||||
match hero.is_installed(test_package) {
|
||||
Ok(is_installed) => {
|
||||
if !is_installed {
|
||||
println!("Failed to install package {}", test_package);
|
||||
return;
|
||||
} else {
|
||||
println!("Verified package {} was installed", test_package);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!(
|
||||
"Error checking if package is installed after installation: {}",
|
||||
e
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Test the search functionality
|
||||
println!("Searching for packages with 'wget'");
|
||||
match hero.search("wget") {
|
||||
Ok(results) => {
|
||||
println!("Search results: {:?}", results);
|
||||
assert!(
|
||||
results.iter().any(|r| r.contains("wget")),
|
||||
"Search results should contain wget"
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error searching for packages: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Test listing installed packages
|
||||
println!("Listing installed packages");
|
||||
match hero.list_installed() {
|
||||
Ok(packages) => {
|
||||
println!("Found {} installed packages", packages.len());
|
||||
// Check if our test package is in the list
|
||||
assert!(
|
||||
packages.iter().any(|p| p == test_package),
|
||||
"Installed packages list should contain {}",
|
||||
test_package
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error listing installed packages: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Now remove the package if it wasn't installed before
|
||||
if !is_installed_before {
|
||||
println!("Removing package {} after test", test_package);
|
||||
match hero.remove(test_package) {
|
||||
Ok(_) => println!("Successfully removed package {}", test_package),
|
||||
Err(e) => {
|
||||
println!("Error removing package {}: {}", test_package, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify it was removed
|
||||
match hero.is_installed(test_package) {
|
||||
Ok(is_installed) => {
|
||||
if is_installed {
|
||||
println!("Failed to remove package {}", test_package);
|
||||
return;
|
||||
} else {
|
||||
println!("Verified package {} was removed", test_package);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!(
|
||||
"Error checking if package is installed after removal: {}",
|
||||
e
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test update functionality
|
||||
println!("Testing package list update");
|
||||
match hero.update() {
|
||||
Ok(_) => println!("Successfully updated package lists"),
|
||||
Err(e) => {
|
||||
println!("Error updating package lists: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
println!("All real package operations tests passed on Ubuntu");
|
||||
}
|
||||
|
||||
// Test to check if apt-get is available on the system
|
||||
#[test]
|
||||
fn test_apt_get_availability() {
|
||||
// This test checks if apt-get is available on the system
|
||||
let output = StdCommand::new("which")
|
||||
.arg("apt-get")
|
||||
.output()
|
||||
.expect("Failed to execute which apt-get");
|
||||
|
||||
let success = output.status.success();
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
|
||||
println!("apt-get available: {}", success);
|
||||
if success {
|
||||
println!("apt-get path: {}", stdout.trim());
|
||||
}
|
||||
|
||||
// On Ubuntu, this should pass
|
||||
if Platform::detect() == Platform::Ubuntu {
|
||||
assert!(success, "apt-get should be available on Ubuntu");
|
||||
}
|
||||
}
|
||||
}
|
75
os/src/platform.rs
Normal file
75
os/src/platform.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
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")]
|
||||
pub fn is_osx() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn is_osx() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn is_linux() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn is_linux() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn is_arm() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
pub fn is_arm() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn is_x86() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86_64"))]
|
||||
pub fn is_x86() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn check_linux_x86() -> Result<(), PlatformError> {
|
||||
if is_linux() && is_x86() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PlatformError::new(
|
||||
"Platform Check Error",
|
||||
"This operation is only supported on Linux x86_64.",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_macos_arm() -> Result<(), PlatformError> {
|
||||
if is_osx() && is_arm() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PlatformError::new(
|
||||
"Platform Check Error",
|
||||
"This operation is only supported on macOS ARM.",
|
||||
))
|
||||
}
|
||||
}
|
424
os/src/rhai.rs
Normal file
424
os/src/rhai.rs
Normal file
@@ -0,0 +1,424 @@
|
||||
//! Rhai wrappers for OS module functions
|
||||
//!
|
||||
//! This module provides Rhai wrappers for the functions in the OS module.
|
||||
|
||||
use crate::package::PackHero;
|
||||
use crate::{download as dl, fs, package};
|
||||
use rhai::{Array, Engine, EvalAltResult, Position};
|
||||
|
||||
/// 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
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `engine` - The Rhai engine to register the functions with
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
|
||||
pub fn register_os_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||
// Register file system functions
|
||||
engine.register_fn("copy", copy);
|
||||
engine.register_fn("copy_bin", copy_bin);
|
||||
engine.register_fn("exist", exist);
|
||||
engine.register_fn("find_file", find_file);
|
||||
engine.register_fn("find_files", find_files);
|
||||
engine.register_fn("find_dir", find_dir);
|
||||
engine.register_fn("find_dirs", find_dirs);
|
||||
engine.register_fn("delete", delete);
|
||||
engine.register_fn("mkdir", mkdir);
|
||||
engine.register_fn("file_size", file_size);
|
||||
engine.register_fn("rsync", rsync);
|
||||
engine.register_fn("chdir", chdir);
|
||||
engine.register_fn("file_read", file_read);
|
||||
engine.register_fn("file_write", file_write);
|
||||
engine.register_fn("file_write_append", file_write_append);
|
||||
|
||||
// Register command check functions
|
||||
engine.register_fn("which", which);
|
||||
engine.register_fn("cmd_ensure_exists", cmd_ensure_exists);
|
||||
|
||||
// Register download functions
|
||||
engine.register_fn("download", download);
|
||||
engine.register_fn("download_file", download_file);
|
||||
engine.register_fn("download_install", download_install);
|
||||
engine.register_fn("chmod_exec", chmod_exec);
|
||||
|
||||
// Register move function
|
||||
engine.register_fn("mv", mv);
|
||||
|
||||
// Register package management functions
|
||||
engine.register_fn("package_install", package_install);
|
||||
engine.register_fn("package_remove", package_remove);
|
||||
engine.register_fn("package_update", package_update);
|
||||
engine.register_fn("package_upgrade", package_upgrade);
|
||||
engine.register_fn("package_list", package_list);
|
||||
engine.register_fn("package_search", package_search);
|
||||
engine.register_fn("package_is_installed", package_is_installed);
|
||||
engine.register_fn("package_set_debug", package_set_debug);
|
||||
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(())
|
||||
}
|
||||
|
||||
//
|
||||
// File System Function Wrappers
|
||||
//
|
||||
|
||||
/// Wrapper for fs::copy
|
||||
///
|
||||
/// Recursively copy a file or directory from source to destination.
|
||||
pub fn copy(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::copy(src, dest).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::copy_bin
|
||||
///
|
||||
/// Copy a binary to the correct location based on OS and user privileges.
|
||||
pub fn copy_bin(src: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::copy_bin(src).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::exist
|
||||
///
|
||||
/// Check if a file or directory exists.
|
||||
pub fn exist(path: &str) -> bool {
|
||||
fs::exist(path)
|
||||
}
|
||||
|
||||
/// Wrapper for fs::find_file
|
||||
///
|
||||
/// Find a file in a directory (with support for wildcards).
|
||||
pub fn find_file(dir: &str, filename: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::find_file(dir, filename).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::find_files
|
||||
///
|
||||
/// Find multiple files in a directory (recursive, with support for wildcards).
|
||||
pub fn find_files(dir: &str, filename: &str) -> Result<Array, Box<EvalAltResult>> {
|
||||
let files = fs::find_files(dir, filename).to_rhai_error()?;
|
||||
|
||||
// Convert Vec<String> to Rhai Array
|
||||
let mut array = Array::new();
|
||||
for file in files {
|
||||
array.push(file.into());
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
/// Wrapper for fs::find_dir
|
||||
///
|
||||
/// Find a directory in a parent directory (with support for wildcards).
|
||||
pub fn find_dir(dir: &str, dirname: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::find_dir(dir, dirname).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::find_dirs
|
||||
///
|
||||
/// Find multiple directories in a parent directory (recursive, with support for wildcards).
|
||||
pub fn find_dirs(dir: &str, dirname: &str) -> Result<Array, Box<EvalAltResult>> {
|
||||
let dirs = fs::find_dirs(dir, dirname).to_rhai_error()?;
|
||||
|
||||
// Convert Vec<String> to Rhai Array
|
||||
let mut array = Array::new();
|
||||
for dir in dirs {
|
||||
array.push(dir.into());
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
/// Wrapper for fs::delete
|
||||
///
|
||||
/// Delete a file or directory (defensive - doesn't error if file doesn't exist).
|
||||
pub fn delete(path: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::delete(path).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::mkdir
|
||||
///
|
||||
/// Create a directory and all parent directories (defensive - doesn't error if directory exists).
|
||||
pub fn mkdir(path: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::mkdir(path).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::file_size
|
||||
///
|
||||
/// Get the size of a file in bytes.
|
||||
pub fn file_size(path: &str) -> Result<i64, Box<EvalAltResult>> {
|
||||
fs::file_size(path).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::rsync
|
||||
///
|
||||
/// Sync directories using rsync (or platform equivalent).
|
||||
pub fn rsync(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::rsync(src, dest).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::chdir
|
||||
///
|
||||
/// Change the current working directory.
|
||||
pub fn chdir(path: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::chdir(path).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::file_read
|
||||
///
|
||||
/// Read the contents of a file.
|
||||
pub fn file_read(path: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::file_read(path).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::file_write
|
||||
///
|
||||
/// 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>> {
|
||||
fs::file_write(path, content).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::file_write_append
|
||||
///
|
||||
/// 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>> {
|
||||
fs::file_write_append(path, content).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::mv
|
||||
///
|
||||
/// Move a file or directory from source to destination.
|
||||
pub fn mv(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::mv(src, dest).to_rhai_error()
|
||||
}
|
||||
|
||||
//
|
||||
// Download Function Wrappers
|
||||
//
|
||||
|
||||
/// Wrapper for os::download
|
||||
///
|
||||
/// 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>> {
|
||||
dl::download(url, dest, min_size_kb).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::download_file
|
||||
///
|
||||
/// 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>> {
|
||||
dl::download_file(url, dest, min_size_kb).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::download_install
|
||||
///
|
||||
/// 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>> {
|
||||
dl::download_install(url, min_size_kb).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::chmod_exec
|
||||
///
|
||||
/// Make a file executable (equivalent to chmod +x).
|
||||
pub fn chmod_exec(path: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
dl::chmod_exec(path).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::which
|
||||
///
|
||||
/// Check if a command exists in the system PATH.
|
||||
pub fn which(command: &str) -> String {
|
||||
fs::which(command)
|
||||
}
|
||||
|
||||
/// Wrapper for os::cmd_ensure_exists
|
||||
///
|
||||
/// Ensure that one or more commands exist in the system PATH.
|
||||
/// If any command doesn't exist, an error is thrown.
|
||||
pub fn cmd_ensure_exists(commands: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::cmd_ensure_exists(commands).to_rhai_error()
|
||||
}
|
||||
|
||||
//
|
||||
// Package Management Function Wrappers
|
||||
//
|
||||
|
||||
/// Wrapper for os::package::PackHero::install
|
||||
///
|
||||
/// Install a package using the system package manager.
|
||||
pub fn package_install(package: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
let hero = PackHero::new();
|
||||
hero.install(package)
|
||||
.map(|_| format!("Package '{}' installed successfully", package))
|
||||
.to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::package::PackHero::remove
|
||||
///
|
||||
/// Remove a package using the system package manager.
|
||||
pub fn package_remove(package: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
let hero = PackHero::new();
|
||||
hero.remove(package)
|
||||
.map(|_| format!("Package '{}' removed successfully", package))
|
||||
.to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::package::PackHero::update
|
||||
///
|
||||
/// Update package lists using the system package manager.
|
||||
pub fn package_update() -> Result<String, Box<EvalAltResult>> {
|
||||
let hero = PackHero::new();
|
||||
hero.update()
|
||||
.map(|_| "Package lists updated successfully".to_string())
|
||||
.to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::package::PackHero::upgrade
|
||||
///
|
||||
/// Upgrade installed packages using the system package manager.
|
||||
pub fn package_upgrade() -> Result<String, Box<EvalAltResult>> {
|
||||
let hero = PackHero::new();
|
||||
hero.upgrade()
|
||||
.map(|_| "Packages upgraded successfully".to_string())
|
||||
.to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::package::PackHero::list_installed
|
||||
///
|
||||
/// List installed packages using the system package manager.
|
||||
pub fn package_list() -> Result<Array, Box<EvalAltResult>> {
|
||||
let hero = PackHero::new();
|
||||
let packages = hero.list_installed().to_rhai_error()?;
|
||||
|
||||
// Convert Vec<String> to Rhai Array
|
||||
let mut array = Array::new();
|
||||
for package in packages {
|
||||
array.push(package.into());
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
/// Wrapper for os::package::PackHero::search
|
||||
///
|
||||
/// Search for packages using the system package manager.
|
||||
pub fn package_search(query: &str) -> Result<Array, Box<EvalAltResult>> {
|
||||
let hero = PackHero::new();
|
||||
let packages = hero.search(query).to_rhai_error()?;
|
||||
|
||||
// Convert Vec<String> to Rhai Array
|
||||
let mut array = Array::new();
|
||||
for package in packages {
|
||||
array.push(package.into());
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
/// Wrapper for os::package::PackHero::is_installed
|
||||
///
|
||||
/// Check if a package is installed using the system package manager.
|
||||
pub fn package_is_installed(package: &str) -> Result<bool, Box<EvalAltResult>> {
|
||||
let hero = PackHero::new();
|
||||
hero.is_installed(package).to_rhai_error()
|
||||
}
|
||||
|
||||
// Thread-local storage for package debug flag
|
||||
thread_local! {
|
||||
static PACKAGE_DEBUG: std::cell::RefCell<bool> = std::cell::RefCell::new(false);
|
||||
}
|
||||
|
||||
/// Set the debug mode for package management operations
|
||||
pub fn package_set_debug(debug: bool) -> bool {
|
||||
let mut hero = PackHero::new();
|
||||
hero.set_debug(debug);
|
||||
|
||||
// Also set the thread-local debug flag
|
||||
PACKAGE_DEBUG.with(|cell| {
|
||||
*cell.borrow_mut() = debug;
|
||||
});
|
||||
|
||||
debug
|
||||
}
|
||||
|
||||
/// Get the current platform name for package management
|
||||
pub fn package_platform() -> String {
|
||||
let hero = PackHero::new();
|
||||
match hero.platform() {
|
||||
package::Platform::Ubuntu => "Ubuntu".to_string(),
|
||||
package::Platform::MacOS => "MacOS".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());
|
||||
}
|
111
os/tests/rhai/01_file_operations.rhai
Normal file
111
os/tests/rhai/01_file_operations.rhai
Normal file
@@ -0,0 +1,111 @@
|
||||
// 01_file_operations.rhai
|
||||
// Tests for file system operations in the OS module
|
||||
|
||||
// Custom assert function
|
||||
fn assert_true(condition, message) {
|
||||
if !condition {
|
||||
print(`ASSERTION FAILED: ${message}`);
|
||||
throw message;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a test directory structure
|
||||
let test_dir = "rhai_test_fs";
|
||||
let sub_dir = test_dir + "/subdir";
|
||||
|
||||
// Test mkdir function
|
||||
print("Testing mkdir...");
|
||||
let mkdir_result = mkdir(test_dir);
|
||||
assert_true(exist(test_dir), "Directory creation failed");
|
||||
print(`✓ mkdir: ${mkdir_result}`);
|
||||
|
||||
// Test nested directory creation
|
||||
let nested_result = mkdir(sub_dir);
|
||||
assert_true(exist(sub_dir), "Nested directory creation failed");
|
||||
print(`✓ mkdir (nested): ${nested_result}`);
|
||||
|
||||
// Test file_write function
|
||||
let test_file = test_dir + "/test.txt";
|
||||
let file_content = "This is a test file created by Rhai test script.";
|
||||
let write_result = file_write(test_file, file_content);
|
||||
assert_true(exist(test_file), "File creation failed");
|
||||
print(`✓ file_write: ${write_result}`);
|
||||
|
||||
// Test file_read function
|
||||
let read_content = file_read(test_file);
|
||||
assert_true(read_content == file_content, "File content doesn't match");
|
||||
print(`✓ file_read: Content matches`);
|
||||
|
||||
// Test file_size function
|
||||
let size = file_size(test_file);
|
||||
assert_true(size > 0, "File size should be greater than 0");
|
||||
print(`✓ file_size: ${size} bytes`);
|
||||
|
||||
// Test file_write_append function
|
||||
let append_content = "\nThis is appended content.";
|
||||
let append_result = file_write_append(test_file, append_content);
|
||||
let new_content = file_read(test_file);
|
||||
assert_true(new_content == file_content + append_content, "Appended content doesn't match");
|
||||
print(`✓ file_write_append: ${append_result}`);
|
||||
|
||||
// Test copy function
|
||||
let copied_file = test_dir + "/copied.txt";
|
||||
let copy_result = copy(test_file, copied_file);
|
||||
assert_true(exist(copied_file), "File copy failed");
|
||||
print(`✓ copy: ${copy_result}`);
|
||||
|
||||
// Test mv function
|
||||
let moved_file = test_dir + "/moved.txt";
|
||||
let mv_result = mv(copied_file, moved_file);
|
||||
assert_true(exist(moved_file), "File move failed");
|
||||
assert_true(!exist(copied_file), "Source file still exists after move");
|
||||
print(`✓ mv: ${mv_result}`);
|
||||
|
||||
// Test find_file function
|
||||
let found_file = find_file(test_dir, "*.txt");
|
||||
assert_true(found_file.contains("test.txt") || found_file.contains("moved.txt"), "find_file failed");
|
||||
print(`✓ find_file: ${found_file}`);
|
||||
|
||||
// Test find_files function
|
||||
let found_files = find_files(test_dir, "*.txt");
|
||||
assert_true(found_files.len() == 2, "find_files should find 2 files");
|
||||
print(`✓ find_files: Found ${found_files.len()} files`);
|
||||
|
||||
// Test find_dir function
|
||||
let found_dir = find_dir(test_dir, "sub*");
|
||||
assert_true(found_dir.contains("subdir"), "find_dir failed");
|
||||
print(`✓ find_dir: ${found_dir}`);
|
||||
|
||||
// Test find_dirs function
|
||||
let found_dirs = find_dirs(test_dir, "sub*");
|
||||
assert_true(found_dirs.len() == 1, "find_dirs should find 1 directory");
|
||||
print(`✓ find_dirs: Found ${found_dirs.len()} directories`);
|
||||
|
||||
// Test chdir function
|
||||
// Save current directory path before changing
|
||||
let chdir_result = chdir(test_dir);
|
||||
print(`✓ chdir: ${chdir_result}`);
|
||||
|
||||
// Change back to parent directory
|
||||
chdir("..");
|
||||
|
||||
// Test rsync function (if available)
|
||||
let rsync_dir = test_dir + "/rsync_dest";
|
||||
mkdir(rsync_dir);
|
||||
let rsync_result = rsync(test_dir, rsync_dir);
|
||||
print(`✓ rsync: ${rsync_result}`);
|
||||
|
||||
// Test delete function
|
||||
let delete_file_result = delete(test_file);
|
||||
assert_true(!exist(test_file), "File deletion failed");
|
||||
print(`✓ delete (file): ${delete_file_result}`);
|
||||
|
||||
// Clean up
|
||||
delete(moved_file);
|
||||
delete(sub_dir);
|
||||
delete(rsync_dir);
|
||||
delete(test_dir);
|
||||
assert_true(!exist(test_dir), "Directory deletion failed");
|
||||
print(`✓ delete (directory): Directory cleaned up`);
|
||||
|
||||
print("All file system tests completed successfully!");
|
53
os/tests/rhai/02_download_operations.rhai
Normal file
53
os/tests/rhai/02_download_operations.rhai
Normal file
@@ -0,0 +1,53 @@
|
||||
// 02_download_operations.rhai
|
||||
// Tests for download operations in the OS module
|
||||
|
||||
// Custom assert function
|
||||
fn assert_true(condition, message) {
|
||||
if !condition {
|
||||
print(`ASSERTION FAILED: ${message}`);
|
||||
throw message;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a test directory
|
||||
let test_dir = "rhai_test_download";
|
||||
mkdir(test_dir);
|
||||
print(`Created test directory: ${test_dir}`);
|
||||
|
||||
// Test which function to ensure curl is available
|
||||
let curl_path = which("curl");
|
||||
if curl_path == "" {
|
||||
print("Warning: curl not found, download tests may fail");
|
||||
} else {
|
||||
print(`✓ which: curl found at ${curl_path}`);
|
||||
}
|
||||
|
||||
// Test cmd_ensure_exists function
|
||||
let ensure_result = cmd_ensure_exists("curl");
|
||||
print(`✓ cmd_ensure_exists: ${ensure_result}`);
|
||||
|
||||
// Test download function with a small file
|
||||
let download_url = "https://raw.githubusercontent.com/rust-lang/rust/master/LICENSE-MIT";
|
||||
let download_dest = test_dir + "/license.txt";
|
||||
let min_size_kb = 1; // Minimum size in KB
|
||||
|
||||
print(`Downloading ${download_url}...`);
|
||||
let download_result = download_file(download_url, download_dest, min_size_kb);
|
||||
assert_true(exist(download_dest), "Download failed");
|
||||
print(`✓ download_file: ${download_result}`);
|
||||
|
||||
// Verify the downloaded file
|
||||
let file_content = file_read(download_dest);
|
||||
assert_true(file_content.contains("Permission is hereby granted"), "Downloaded file content is incorrect");
|
||||
print("✓ Downloaded file content verified");
|
||||
|
||||
// Test chmod_exec function
|
||||
let chmod_result = chmod_exec(download_dest);
|
||||
print(`✓ chmod_exec: ${chmod_result}`);
|
||||
|
||||
// Clean up
|
||||
delete(test_dir);
|
||||
assert_true(!exist(test_dir), "Directory deletion failed");
|
||||
print(`✓ Cleanup: Directory ${test_dir} removed`);
|
||||
|
||||
print("All download tests completed successfully!");
|
56
os/tests/rhai/03_package_operations.rhai
Normal file
56
os/tests/rhai/03_package_operations.rhai
Normal file
@@ -0,0 +1,56 @@
|
||||
// 03_package_operations.rhai
|
||||
// Tests for package management operations in the OS module
|
||||
|
||||
// Custom assert function
|
||||
fn assert_true(condition, message) {
|
||||
if !condition {
|
||||
print(`ASSERTION FAILED: ${message}`);
|
||||
throw message;
|
||||
}
|
||||
}
|
||||
|
||||
// Test package_platform function
|
||||
let platform = package_platform();
|
||||
print(`Current platform: ${platform}`);
|
||||
|
||||
// Test package_set_debug function
|
||||
let debug_enabled = package_set_debug(true);
|
||||
assert_true(debug_enabled, "Debug mode should be enabled");
|
||||
print("✓ package_set_debug: Debug mode enabled");
|
||||
|
||||
// Disable debug mode for remaining tests
|
||||
package_set_debug(false);
|
||||
|
||||
// Test package_is_installed function with a package that should exist on most systems
|
||||
let common_packages = ["bash", "curl", "grep"];
|
||||
let found_package = false;
|
||||
|
||||
for pkg in common_packages {
|
||||
let is_installed = package_is_installed(pkg);
|
||||
if is_installed {
|
||||
print(`✓ package_is_installed: ${pkg} is installed`);
|
||||
found_package = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !found_package {
|
||||
print("Warning: None of the common packages were found installed");
|
||||
}
|
||||
|
||||
// Test package_search function with a common term
|
||||
// Note: This might be slow and produce a lot of output
|
||||
print("Testing package_search (this might take a moment)...");
|
||||
let search_results = package_search("lib");
|
||||
print(`✓ package_search: Found ${search_results.len()} packages containing 'lib'`);
|
||||
|
||||
// Test package_list function
|
||||
// Note: This might be slow and produce a lot of output
|
||||
print("Testing package_list (this might take a moment)...");
|
||||
let installed_packages = package_list();
|
||||
print(`✓ package_list: Found ${installed_packages.len()} installed packages`);
|
||||
|
||||
// Note: We're not testing package_install, package_remove, package_update, or package_upgrade
|
||||
// as they require root privileges and could modify the system state
|
||||
|
||||
print("All package management tests completed successfully!");
|
148
os/tests/rhai/run_all_tests.rhai
Normal file
148
os/tests/rhai/run_all_tests.rhai
Normal file
@@ -0,0 +1,148 @@
|
||||
// run_all_tests.rhai
|
||||
// Runs all OS module tests
|
||||
|
||||
print("=== Running OS Module Tests ===");
|
||||
|
||||
// Custom assert function
|
||||
fn assert_true(condition, message) {
|
||||
if !condition {
|
||||
print(`ASSERTION FAILED: ${message}`);
|
||||
throw message;
|
||||
}
|
||||
}
|
||||
|
||||
// Run each test directly
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
// Test 1: File Operations
|
||||
print("\n--- Running File Operations Tests ---");
|
||||
try {
|
||||
// Create a test directory structure
|
||||
let test_dir = "rhai_test_fs";
|
||||
let sub_dir = test_dir + "/subdir";
|
||||
|
||||
// Test mkdir function
|
||||
print("Testing mkdir...");
|
||||
let mkdir_result = mkdir(test_dir);
|
||||
assert_true(exist(test_dir), "Directory creation failed");
|
||||
print(`✓ mkdir: ${mkdir_result}`);
|
||||
|
||||
// Test nested directory creation
|
||||
let nested_result = mkdir(sub_dir);
|
||||
assert_true(exist(sub_dir), "Nested directory creation failed");
|
||||
print(`✓ mkdir (nested): ${nested_result}`);
|
||||
|
||||
// Test file_write function
|
||||
let test_file = test_dir + "/test.txt";
|
||||
let file_content = "This is a test file created by Rhai test script.";
|
||||
let write_result = file_write(test_file, file_content);
|
||||
assert_true(exist(test_file), "File creation failed");
|
||||
print(`✓ file_write: ${write_result}`);
|
||||
|
||||
// Test file_read function
|
||||
let read_content = file_read(test_file);
|
||||
assert_true(read_content == file_content, "File content doesn't match");
|
||||
print(`✓ file_read: Content matches`);
|
||||
|
||||
// Test file_size function
|
||||
let size = file_size(test_file);
|
||||
assert_true(size > 0, "File size should be greater than 0");
|
||||
print(`✓ file_size: ${size} bytes`);
|
||||
|
||||
// Clean up
|
||||
delete(test_file);
|
||||
delete(sub_dir);
|
||||
delete(test_dir);
|
||||
assert_true(!exist(test_dir), "Directory deletion failed");
|
||||
print(`✓ delete: Directory cleaned up`);
|
||||
|
||||
print("--- File Operations Tests completed successfully ---");
|
||||
passed += 1;
|
||||
} catch(err) {
|
||||
print(`!!! Error in File Operations Tests: ${err}`);
|
||||
failed += 1;
|
||||
}
|
||||
|
||||
// Test 2: Download Operations
|
||||
print("\n--- Running Download Operations Tests ---");
|
||||
try {
|
||||
// Create a test directory
|
||||
let test_dir = "rhai_test_download";
|
||||
mkdir(test_dir);
|
||||
print(`Created test directory: ${test_dir}`);
|
||||
|
||||
// Test which function to ensure curl is available
|
||||
let curl_path = which("curl");
|
||||
if curl_path == "" {
|
||||
print("Warning: curl not found, download tests may fail");
|
||||
} else {
|
||||
print(`✓ which: curl found at ${curl_path}`);
|
||||
}
|
||||
|
||||
// Test cmd_ensure_exists function
|
||||
let ensure_result = cmd_ensure_exists("curl");
|
||||
print(`✓ cmd_ensure_exists: ${ensure_result}`);
|
||||
|
||||
// Test download function with a small file
|
||||
let download_url = "https://raw.githubusercontent.com/rust-lang/rust/master/LICENSE-MIT";
|
||||
let download_dest = test_dir + "/license.txt";
|
||||
let min_size_kb = 1; // Minimum size in KB
|
||||
|
||||
print(`Downloading ${download_url}...`);
|
||||
let download_result = download_file(download_url, download_dest, min_size_kb);
|
||||
assert_true(exist(download_dest), "Download failed");
|
||||
print(`✓ download_file: ${download_result}`);
|
||||
|
||||
// Verify the downloaded file
|
||||
let file_content = file_read(download_dest);
|
||||
assert_true(file_content.contains("Permission is hereby granted"), "Downloaded file content is incorrect");
|
||||
print("✓ Downloaded file content verified");
|
||||
|
||||
// Clean up
|
||||
delete(test_dir);
|
||||
assert_true(!exist(test_dir), "Directory deletion failed");
|
||||
print(`✓ Cleanup: Directory ${test_dir} removed`);
|
||||
|
||||
print("--- Download Operations Tests completed successfully ---");
|
||||
passed += 1;
|
||||
} catch(err) {
|
||||
print(`!!! Error in Download Operations Tests: ${err}`);
|
||||
failed += 1;
|
||||
}
|
||||
|
||||
// Test 3: Package Operations
|
||||
print("\n--- Running Package Operations Tests ---");
|
||||
try {
|
||||
// Test package_platform function
|
||||
let platform = package_platform();
|
||||
print(`Current platform: ${platform}`);
|
||||
|
||||
// Test package_set_debug function
|
||||
let debug_enabled = package_set_debug(true);
|
||||
assert_true(debug_enabled, "Debug mode should be enabled");
|
||||
print("✓ package_set_debug: Debug mode enabled");
|
||||
|
||||
// Disable debug mode for remaining tests
|
||||
package_set_debug(false);
|
||||
|
||||
print("--- Package Operations Tests completed successfully ---");
|
||||
passed += 1;
|
||||
} catch(err) {
|
||||
print(`!!! Error in Package Operations Tests: ${err}`);
|
||||
failed += 1;
|
||||
}
|
||||
|
||||
print("\n=== Test Summary ===");
|
||||
print(`Passed: ${passed}`);
|
||||
print(`Failed: ${failed}`);
|
||||
print(`Total: ${passed + failed}`);
|
||||
|
||||
if failed == 0 {
|
||||
print("\n✅ All tests passed!");
|
||||
} else {
|
||||
print("\n❌ Some tests failed!");
|
||||
}
|
||||
|
||||
// Return the number of failed tests (0 means success)
|
||||
failed;
|
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
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user