mirror of
https://codeberg.org/Toasterson/ips.git
synced 2026-04-10 21:30:41 +00:00
Add tests for Image functionality and refactor image handling logic
- Implement unit tests to cover `Image` creation, metadata management, saving/loading, and error handling. - Add separate constructors for `Full` and `Partial` image types (`new_full`, `new_partial`). - Introduce robust path handling methods (`metadata_dir`, `image_json_path`) for consistent metadata structure. - Enhance error diagnostics with `InvalidPath` variant in `ImageError`. - Update `Cargo.toml` for test module inclusion.
This commit is contained in:
parent
f60abfdbd1
commit
f7f017f7b9
5 changed files with 235 additions and 19 deletions
|
|
@ -8,7 +8,7 @@ members = [
|
||||||
"userland",
|
"userland",
|
||||||
"specfile",
|
"specfile",
|
||||||
"ports",
|
"ports",
|
||||||
"crates/*",
|
"pkg6",
|
||||||
"xtask",
|
"xtask",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
mod properties;
|
mod properties;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
use miette::Diagnostic;
|
use miette::Diagnostic;
|
||||||
use properties::*;
|
use properties::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::{self, File};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -23,23 +25,49 @@ pub enum ImageError {
|
||||||
help("Check the JSON format and try again")
|
help("Check the JSON format and try again")
|
||||||
)]
|
)]
|
||||||
Json(#[from] serde_json::Error),
|
Json(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[error("Invalid image path: {0}")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(ips::image_error::invalid_path),
|
||||||
|
help("Provide a valid path for the image")
|
||||||
|
)]
|
||||||
|
InvalidPath(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, ImageError>;
|
pub type Result<T> = std::result::Result<T, ImageError>;
|
||||||
|
|
||||||
|
/// Type of image, either Full (base path of "/") or Partial (attached to a full image)
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
pub enum ImageType {
|
||||||
|
/// Full image with base path of "/"
|
||||||
|
Full,
|
||||||
|
/// Partial image attached to a full image
|
||||||
|
Partial,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an IPS image, which can be either a Full image or a Partial image
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
|
/// Path to the image
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
/// Type of image (Full or Partial)
|
||||||
|
image_type: ImageType,
|
||||||
|
/// Image properties
|
||||||
props: Vec<ImageProperty>,
|
props: Vec<ImageProperty>,
|
||||||
|
/// Image version
|
||||||
version: i32,
|
version: i32,
|
||||||
|
/// Variants
|
||||||
variants: HashMap<String, String>,
|
variants: HashMap<String, String>,
|
||||||
|
/// Mediators
|
||||||
mediators: HashMap<String, String>,
|
mediators: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
pub fn new<P: Into<PathBuf>>(path: P) -> Image {
|
/// Creates a new Full image at the specified path
|
||||||
|
pub fn new_full<P: Into<PathBuf>>(path: P) -> Image {
|
||||||
Image {
|
Image {
|
||||||
path: path.into(),
|
path: path.into(),
|
||||||
|
image_type: ImageType::Full,
|
||||||
version: 5,
|
version: 5,
|
||||||
variants: HashMap::new(),
|
variants: HashMap::new(),
|
||||||
mediators: HashMap::new(),
|
mediators: HashMap::new(),
|
||||||
|
|
@ -47,21 +75,95 @@ impl Image {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Image> {
|
/// Creates a new Partial image at the specified path
|
||||||
|
pub fn new_partial<P: Into<PathBuf>>(path: P) -> Image {
|
||||||
|
Image {
|
||||||
|
path: path.into(),
|
||||||
|
image_type: ImageType::Partial,
|
||||||
|
version: 5,
|
||||||
|
variants: HashMap::new(),
|
||||||
|
mediators: HashMap::new(),
|
||||||
|
props: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the path to the image
|
||||||
|
pub fn path(&self) -> &Path {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the type of the image
|
||||||
|
pub fn image_type(&self) -> &ImageType {
|
||||||
|
&self.image_type
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the path to the metadata directory for this image
|
||||||
|
pub fn metadata_dir(&self) -> PathBuf {
|
||||||
|
match self.image_type {
|
||||||
|
ImageType::Full => self.path.join("var/pkg"),
|
||||||
|
ImageType::Partial => self.path.join(".pkg"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the path to the image JSON file
|
||||||
|
pub fn image_json_path(&self) -> PathBuf {
|
||||||
|
self.metadata_dir().join("pkg6.image.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the metadata directory if it doesn't exist
|
||||||
|
pub fn create_metadata_dir(&self) -> Result<()> {
|
||||||
|
let metadata_dir = self.metadata_dir();
|
||||||
|
fs::create_dir_all(&metadata_dir).map_err(|e| {
|
||||||
|
ImageError::IO(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
format!("Failed to create metadata directory at {:?}: {}", metadata_dir, e),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Saves the image data to the metadata directory
|
||||||
|
pub fn save(&self) -> Result<()> {
|
||||||
|
self.create_metadata_dir()?;
|
||||||
|
let json_path = self.image_json_path();
|
||||||
|
let file = File::create(&json_path).map_err(|e| {
|
||||||
|
ImageError::IO(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
format!("Failed to create image JSON file at {:?}: {}", json_path, e),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
serde_json::to_writer_pretty(file, self).map_err(ImageError::Json)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads an image from the specified path
|
||||||
|
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
|
||||||
//TODO: Parse the old INI format of pkg5
|
// Check for both full and partial image JSON files
|
||||||
//TODO once root images are implemented, look for metadata under sub directory var/pkg
|
let full_image = Image::new_full(path);
|
||||||
let props_path = path.join("pkg6.image.json");
|
let partial_image = Image::new_partial(path);
|
||||||
let mut f = File::open(props_path)?;
|
|
||||||
Ok(serde_json::from_reader(&mut f)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open_default<P: AsRef<Path>>(path: P) -> Image {
|
let full_json_path = full_image.image_json_path();
|
||||||
if let Ok(img) = Image::open(path.as_ref()) {
|
let partial_json_path = partial_image.image_json_path();
|
||||||
img
|
|
||||||
|
// Determine which JSON file exists
|
||||||
|
let json_path = if full_json_path.exists() {
|
||||||
|
full_json_path
|
||||||
|
} else if partial_json_path.exists() {
|
||||||
|
partial_json_path
|
||||||
} else {
|
} else {
|
||||||
Image::new(path.as_ref())
|
return Err(ImageError::InvalidPath(format!(
|
||||||
}
|
"Image JSON file not found at either {:?} or {:?}",
|
||||||
|
full_json_path, partial_json_path
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let file = File::open(&json_path).map_err(|e| {
|
||||||
|
ImageError::IO(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
format!("Failed to open image JSON file at {:?}: {}", json_path, e),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
serde_json::from_reader(file).map_err(ImageError::Json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
114
libips/src/image/tests.rs
Normal file
114
libips/src/image/tests.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
use super::*;
|
||||||
|
use std::path::Path;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_image_types() {
|
||||||
|
let full_image = Image::new_full("/");
|
||||||
|
let partial_image = Image::new_partial("/tmp/partial");
|
||||||
|
|
||||||
|
assert_eq!(*full_image.image_type(), ImageType::Full);
|
||||||
|
assert_eq!(*partial_image.image_type(), ImageType::Partial);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_metadata_paths() {
|
||||||
|
let full_image = Image::new_full("/");
|
||||||
|
let partial_image = Image::new_partial("/tmp/partial");
|
||||||
|
|
||||||
|
assert_eq!(full_image.metadata_dir(), Path::new("/var/pkg"));
|
||||||
|
assert_eq!(partial_image.metadata_dir(), Path::new("/tmp/partial/.pkg"));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
full_image.image_json_path(),
|
||||||
|
Path::new("/var/pkg/pkg6.image.json")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
partial_image.image_json_path(),
|
||||||
|
Path::new("/tmp/partial/.pkg/pkg6.image.json")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_save_and_load() -> Result<()> {
|
||||||
|
// Create a temporary directory for testing
|
||||||
|
let temp_dir = tempdir().expect("Failed to create temp directory");
|
||||||
|
let temp_path = temp_dir.path();
|
||||||
|
|
||||||
|
// Create a full image
|
||||||
|
let mut full_image = Image::new_full(temp_path);
|
||||||
|
|
||||||
|
// Add some test data
|
||||||
|
full_image.props.push(ImageProperty::String("test_prop".to_string()));
|
||||||
|
|
||||||
|
// Save the image
|
||||||
|
full_image.save()?;
|
||||||
|
|
||||||
|
// Check that the metadata directory and JSON file were created
|
||||||
|
let metadata_dir = temp_path.join("var/pkg");
|
||||||
|
let json_path = metadata_dir.join("pkg6.image.json");
|
||||||
|
|
||||||
|
assert!(metadata_dir.exists());
|
||||||
|
assert!(json_path.exists());
|
||||||
|
|
||||||
|
// Load the image
|
||||||
|
let loaded_image = Image::load(temp_path)?;
|
||||||
|
|
||||||
|
// Check that the loaded image matches the original
|
||||||
|
assert_eq!(*loaded_image.image_type(), ImageType::Full);
|
||||||
|
assert_eq!(loaded_image.path, full_image.path);
|
||||||
|
assert_eq!(loaded_image.props.len(), 1);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
temp_dir.close().expect("Failed to clean up temp directory");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_partial_image() -> Result<()> {
|
||||||
|
// Create a temporary directory for testing
|
||||||
|
let temp_dir = tempdir().expect("Failed to create temp directory");
|
||||||
|
let temp_path = temp_dir.path();
|
||||||
|
|
||||||
|
// Create a partial image
|
||||||
|
let mut partial_image = Image::new_partial(temp_path);
|
||||||
|
|
||||||
|
// Add some test data
|
||||||
|
partial_image.props.push(ImageProperty::Boolean(true));
|
||||||
|
|
||||||
|
// Save the image
|
||||||
|
partial_image.save()?;
|
||||||
|
|
||||||
|
// Check that the metadata directory and JSON file were created
|
||||||
|
let metadata_dir = temp_path.join(".pkg");
|
||||||
|
let json_path = metadata_dir.join("pkg6.image.json");
|
||||||
|
|
||||||
|
assert!(metadata_dir.exists());
|
||||||
|
assert!(json_path.exists());
|
||||||
|
|
||||||
|
// Load the image
|
||||||
|
let loaded_image = Image::load(temp_path)?;
|
||||||
|
|
||||||
|
// Check that the loaded image matches the original
|
||||||
|
assert_eq!(*loaded_image.image_type(), ImageType::Partial);
|
||||||
|
assert_eq!(loaded_image.path, partial_image.path);
|
||||||
|
assert_eq!(loaded_image.props.len(), 1);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
temp_dir.close().expect("Failed to clean up temp directory");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_path() {
|
||||||
|
let result = Image::load("/nonexistent/path");
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
if let Err(ImageError::InvalidPath(_)) = result {
|
||||||
|
// Expected error
|
||||||
|
} else {
|
||||||
|
panic!("Expected InvalidPath error, got {:?}", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,6 @@ readme.workspace = true
|
||||||
keywords.workspace = true
|
keywords.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libips = { version = "*", path = "../../libips" }
|
libips = { version = "*", path = "../libips" }
|
||||||
diff-struct = "0.5.3"
|
diff-struct = "0.5.3"
|
||||||
serde = { version = "1.0.207", features = ["derive"] }
|
serde = { version = "1.0.207", features = ["derive"] }
|
||||||
Loading…
Add table
Reference in a new issue