use crate::config::ShardexConfig;
use crate::error::ShardexError;
use crate::shardex_index::ShardexIndex;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
pub mod error {
use crate::error::ShardexError;
#[macro_export]
macro_rules! assert_error_type {
($result:expr, $variant:ident) => {
match $result {
Ok(val) => panic!(
"Expected error of type {}, but got Ok({:?})",
stringify!($variant),
val
),
Err(ref err) => {
if !matches!(err, ShardexError::$variant(..)) {
panic!(
"Expected error of type {}, but got: {}",
stringify!($variant),
err
);
}
}
}
};
($result:expr, $variant:ident { $($field:ident),+ }) => {
match $result {
Ok(val) => panic!(
"Expected error of type {} with fields {}, but got Ok({:?})",
stringify!($variant),
stringify!($($field),+),
val
),
Err(ref err) => {
if !matches!(err, ShardexError::$variant { $($field: _),+ }) {
panic!(
"Expected error of type {} with fields {}, but got: {}",
stringify!($variant),
stringify!($($field),+),
err
);
}
}
}
};
}
#[macro_export]
macro_rules! assert_error_contains {
($result:expr, $text:expr) => {
match $result {
Ok(val) => panic!(
"Expected error containing '{}', but got Ok({:?})",
$text,
val
),
Err(ref err) => {
let error_str = err.to_string();
if !error_str.contains($text) {
panic!(
"Expected error to contain '{}', but error was: '{}'",
$text,
error_str
);
}
}
}
};
($result:expr, $($text:expr),+) => {
$(assert_error_contains!($result, $text);)+
};
}
pub fn expect_error<T, E>(result: Result<T, E>, context: &str) -> E
where
T: std::fmt::Debug,
{
match result {
Ok(val) => panic!("Expected error ({}), but got Ok({:?})", context, val),
Err(err) => err,
}
}
pub fn expect_success<T, E>(result: Result<T, E>, context: &str) -> T
where
E: std::fmt::Display,
{
match result {
Ok(val) => val,
Err(err) => panic!("Expected success ({}), but got error: {}", context, err),
}
}
pub fn create_test_io_error(message: &str) -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::Other, message)
}
pub fn create_test_shardex_error(variant: &str, message: &str) -> ShardexError {
match variant {
"config" => ShardexError::Config(message.to_string()),
"memory_mapping" => ShardexError::MemoryMapping(message.to_string()),
"wal" => ShardexError::Wal(message.to_string()),
"shard" => ShardexError::Shard(message.to_string()),
"search" => ShardexError::Search(message.to_string()),
"corruption" => ShardexError::Corruption(message.to_string()),
"text_corruption" => ShardexError::TextCorruption(message.to_string()),
_ => panic!("Unknown test error variant: {}", variant),
}
}
pub fn assert_error_context(error: &ShardexError, expected_contexts: &[&str]) {
let error_str = error.to_string();
for context in expected_contexts {
if !error_str.contains(context) {
panic!(
"Expected error to contain context '{}', but error was: '{}'",
context, error_str
);
}
}
}
pub fn assert_error_causality(error: &ShardexError, expected_causes: &[&str]) {
let error_str = error.to_string();
for cause in expected_causes {
if !error_str.contains(cause) {
panic!(
"Expected error to contain cause '{}', but error was: '{}'",
cause, error_str
);
}
}
}
}
pub struct TestEnvironment {
pub temp_dir: TempDir,
pub test_name: String,
}
impl TestEnvironment {
pub fn new(test_name: &str) -> Self {
let temp_dir =
TempDir::new().unwrap_or_else(|e| panic!("Failed to create temp dir for test {}: {}", test_name, e));
Self {
temp_dir,
test_name: test_name.to_string(),
}
}
pub fn path(&self) -> &Path {
self.temp_dir.path()
}
pub fn path_buf(&self) -> PathBuf {
self.temp_dir.path().to_path_buf()
}
pub fn create_subdir(&self, name: &str) -> std::io::Result<PathBuf> {
let subdir_path = self.temp_dir.path().join(name);
std::fs::create_dir_all(&subdir_path)?;
Ok(subdir_path)
}
pub fn name(&self) -> &str {
&self.test_name
}
}
pub fn create_temp_dir_for_test(test_name: &str) -> TempDir {
TempDir::new().unwrap_or_else(|e| {
panic!(
"{} for test {}: {}",
test_error_messages::FAILED_TO_CREATE_TEMP_DIR,
test_name,
e
)
})
}
impl Drop for TestEnvironment {
fn drop(&mut self) {
}
}
pub mod test_constants {
pub const DEFAULT_VECTOR_SIZE: usize = 128;
pub const SMALL_VECTOR_SIZE: usize = 64;
pub const LARGE_VECTOR_SIZE: usize = 384;
pub const DEFAULT_SHARD_SIZE: usize = 100;
pub const LARGE_SHARD_SIZE: usize = 1000;
pub const DEFAULT_TEST_CAPACITY: usize = 100;
}
pub mod test_error_messages {
pub const FAILED_TO_CREATE_INDEX: &str = "Failed to create test index";
pub const FAILED_TO_CREATE_CONFIG: &str = "Failed to create test config";
pub const FAILED_TO_CREATE_WRITER: &str = "Failed to create test writer";
pub const FAILED_TO_CREATE_STORAGE: &str = "Failed to create test storage";
pub const FAILED_TO_CREATE_TEMP_DIR: &str = "Failed to create temp dir for test";
pub const FAILED_TO_CREATE_COW_INDEX: &str = "Failed to create COW index";
}
pub struct TestSetupBuilder {
test_name: String,
vector_size: usize,
shard_size: usize,
directory_path: Option<PathBuf>,
}
impl TestSetupBuilder {
pub fn new(test_name: &str) -> Self {
Self {
test_name: test_name.to_string(),
vector_size: test_constants::DEFAULT_VECTOR_SIZE,
shard_size: test_constants::DEFAULT_SHARD_SIZE,
directory_path: None,
}
}
pub fn with_vector_size(mut self, size: usize) -> Self {
self.vector_size = size;
self
}
pub fn with_shard_size(mut self, size: usize) -> Self {
self.shard_size = size;
self
}
pub fn with_directory_path(mut self, path: PathBuf) -> Self {
self.directory_path = Some(path);
self
}
pub fn build(self) -> (TestEnvironment, ShardexConfig) {
let test_env = TestEnvironment::new(&self.test_name);
let directory_path = self.directory_path.unwrap_or_else(|| test_env.path_buf());
let config = ShardexConfig::new()
.directory_path(directory_path)
.vector_size(self.vector_size)
.shard_size(self.shard_size);
(test_env, config)
}
pub fn build_with_index(self) -> Result<(TestEnvironment, ShardexConfig, ShardexIndex), ShardexError> {
let (test_env, config) = self.build();
let index = ShardexIndex::create(config.clone()).map_err(|e| ShardexError::InvalidInput {
field: "index_creation".to_string(),
reason: format!("{}: {}", test_error_messages::FAILED_TO_CREATE_INDEX, e),
suggestion: "Check directory permissions and disk space".to_string(),
})?;
Ok((test_env, config, index))
}
pub fn small(test_name: &str) -> Self {
Self::new(test_name).with_vector_size(test_constants::SMALL_VECTOR_SIZE)
}
pub fn large(test_name: &str) -> Self {
Self::new(test_name)
.with_vector_size(test_constants::LARGE_VECTOR_SIZE)
.with_shard_size(test_constants::LARGE_SHARD_SIZE)
}
pub fn name(&self) -> &str {
&self.test_name
}
}
pub struct ShardexTestEnv {
pub env: TestEnvironment,
pub config: ShardexConfig,
}
impl ShardexTestEnv {
pub fn new(test_name: &str) -> Self {
let env = TestEnvironment::new(test_name);
let config = ShardexConfig::new()
.directory_path(env.path())
.vector_size(test_constants::DEFAULT_VECTOR_SIZE)
.shard_size(test_constants::DEFAULT_SHARD_SIZE);
Self { env, config }
}
pub fn with_vector_size(mut self, size: usize) -> Self {
self.config = self.config.vector_size(size);
self
}
pub fn with_shard_size(mut self, size: usize) -> Self {
self.config = self.config.shard_size(size);
self
}
pub fn small(test_name: &str) -> Self {
Self::new(test_name).with_vector_size(test_constants::SMALL_VECTOR_SIZE)
}
pub fn large(test_name: &str) -> Self {
Self::new(test_name)
.with_vector_size(test_constants::LARGE_VECTOR_SIZE)
.with_shard_size(test_constants::LARGE_SHARD_SIZE)
}
pub fn build_with_index(self) -> Result<(TestEnvironment, ShardexConfig, ShardexIndex), ShardexError> {
let index = ShardexIndex::create(self.config.clone()).map_err(|e| ShardexError::InvalidInput {
field: "index_creation".to_string(),
reason: format!("{}: {}", test_error_messages::FAILED_TO_CREATE_INDEX, e),
suggestion: "Check directory permissions and disk space".to_string(),
})?;
Ok((self.env, self.config, index))
}
pub fn env(&self) -> &TestEnvironment {
&self.env
}
pub fn config(&self) -> &ShardexConfig {
&self.config
}
}
pub struct ConcurrentTestEnv {
pub env: TestEnvironment,
pub config: ShardexConfig,
}
impl ConcurrentTestEnv {
pub fn new(test_name: &str) -> Self {
let env = TestEnvironment::new(test_name);
let config = ShardexConfig::new()
.directory_path(env.path())
.vector_size(test_constants::DEFAULT_VECTOR_SIZE)
.shard_size(test_constants::DEFAULT_SHARD_SIZE);
Self { env, config }
}
pub fn high_concurrency(test_name: &str) -> Self {
Self::new(test_name).with_shard_size(test_constants::LARGE_SHARD_SIZE)
}
pub fn with_vector_size(mut self, size: usize) -> Self {
self.config = self.config.vector_size(size);
self
}
pub fn with_shard_size(mut self, size: usize) -> Self {
self.config = self.config.shard_size(size);
self
}
pub async fn build_with_cow_index(
self,
) -> Result<(TestEnvironment, ShardexConfig, crate::cow_index::CowShardexIndex), ShardexError> {
let index = ShardexIndex::create(self.config.clone()).map_err(|e| ShardexError::InvalidInput {
field: "index_creation".to_string(),
reason: format!("{}: {}", test_error_messages::FAILED_TO_CREATE_INDEX, e),
suggestion: "Check directory permissions and disk space".to_string(),
})?;
let cow_index = crate::cow_index::CowShardexIndex::new(index);
Ok((self.env, self.config, cow_index))
}
pub async fn build_with_concurrent(
self,
) -> Result<(TestEnvironment, ShardexConfig, crate::concurrent::ConcurrentShardex), ShardexError> {
let (env, config, cow_index) = self.build_with_cow_index().await?;
let concurrent = crate::concurrent::ConcurrentShardex::new(cow_index);
Ok((env, config, concurrent))
}
}
pub struct WalTestEnv {
pub env: TestEnvironment,
pub config: ShardexConfig,
}
impl WalTestEnv {
const DEFAULT_WAL_SEGMENT_SIZE: usize = 1024 * 1024;
pub fn new(test_name: &str) -> Self {
let env = TestEnvironment::new(test_name);
let config = ShardexConfig::new()
.directory_path(env.path())
.vector_size(test_constants::DEFAULT_VECTOR_SIZE)
.shard_size(test_constants::DEFAULT_SHARD_SIZE);
Self { env, config }
}
pub fn wal_path(&self) -> PathBuf {
self.env.path().join("wal")
}
pub fn ensure_wal_dir(&self) -> std::io::Result<PathBuf> {
let wal_path = self.wal_path();
std::fs::create_dir_all(&wal_path)?;
Ok(wal_path)
}
pub fn with_vector_size(mut self, size: usize) -> Self {
self.config = self.config.vector_size(size);
self
}
pub fn with_shard_size(mut self, size: usize) -> Self {
self.config = self.config.shard_size(size);
self
}
pub fn build_with_wal_manager(
self,
) -> Result<(TestEnvironment, ShardexConfig, crate::wal::WalManager), ShardexError> {
let _wal_path = self
.ensure_wal_dir()
.map_err(|e| ShardexError::Wal(format!("Failed to create WAL directory: {}", e)))?;
let layout = crate::layout::DirectoryLayout::new(self.env.path());
let wal_manager = crate::wal::WalManager::new(layout, Self::DEFAULT_WAL_SEGMENT_SIZE);
Ok((self.env, self.config, wal_manager))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_environment_creation() {
let test_env = TestEnvironment::new("test_environment_creation");
assert_eq!(test_env.name(), "test_environment_creation");
assert!(test_env.path().exists());
assert!(test_env.path().is_dir());
}
#[test]
fn test_subdir_creation() {
let test_env = TestEnvironment::new("test_subdir_creation");
let subdir = test_env.create_subdir("test_sub").unwrap();
assert!(subdir.exists());
assert!(subdir.is_dir());
assert_eq!(subdir.file_name().unwrap(), "test_sub");
}
#[test]
fn test_file_operations() {
let test_env = TestEnvironment::new("test_file_operations");
let test_file = test_env.path().join("test.txt");
fs::write(&test_file, "test content").unwrap();
assert!(test_file.exists());
let content = fs::read_to_string(&test_file).unwrap();
assert_eq!(content, "test content");
}
#[test]
fn test_path_methods() {
let test_env = TestEnvironment::new("test_path_methods");
let path = test_env.path();
let path_buf = test_env.path_buf();
assert_eq!(path, path_buf.as_path());
assert!(path.exists());
assert!(path_buf.exists());
}
#[test]
fn test_setup_builder_basic() {
let (test_env, config) = TestSetupBuilder::new("test_setup_builder_basic").build();
assert_eq!(test_env.name(), "test_setup_builder_basic");
assert!(test_env.path().exists());
assert_eq!(config.vector_size, test_constants::DEFAULT_VECTOR_SIZE);
assert_eq!(config.shard_size, test_constants::DEFAULT_SHARD_SIZE);
}
#[test]
fn test_setup_builder_custom_config() {
let (test_env, config) = TestSetupBuilder::new("test_setup_builder_custom")
.with_vector_size(256)
.with_shard_size(500)
.build();
assert_eq!(test_env.name(), "test_setup_builder_custom");
assert_eq!(config.vector_size, 256);
assert_eq!(config.shard_size, 500);
}
#[test]
fn test_setup_builder_with_index() {
let result = TestSetupBuilder::new("test_setup_builder_with_index").build_with_index();
assert!(result.is_ok());
let (test_env, config, index) = result.unwrap();
assert_eq!(test_env.name(), "test_setup_builder_with_index");
assert_eq!(config.vector_size, test_constants::DEFAULT_VECTOR_SIZE);
assert_eq!(index.shard_count(), 0); }
#[test]
fn test_setup_builder_small_convenience() {
let (test_env, config) = TestSetupBuilder::small("test_small").build();
assert_eq!(test_env.name(), "test_small");
assert_eq!(config.vector_size, test_constants::SMALL_VECTOR_SIZE);
assert_eq!(config.shard_size, test_constants::DEFAULT_SHARD_SIZE);
}
#[test]
fn test_setup_builder_large_convenience() {
let (test_env, config) = TestSetupBuilder::large("test_large").build();
assert_eq!(test_env.name(), "test_large");
assert_eq!(config.vector_size, test_constants::LARGE_VECTOR_SIZE);
assert_eq!(config.shard_size, test_constants::LARGE_SHARD_SIZE);
}
#[test]
fn test_create_temp_dir_for_test() {
let temp_dir = create_temp_dir_for_test("test_temp_dir_creation");
assert!(temp_dir.path().exists());
assert!(temp_dir.path().is_dir());
}
#[test]
fn test_shardex_test_env_basic() {
let test_env = ShardexTestEnv::new("test_shardex_env_basic");
assert_eq!(test_env.env.name(), "test_shardex_env_basic");
assert!(test_env.env.path().exists());
assert_eq!(test_env.config.vector_size, test_constants::DEFAULT_VECTOR_SIZE);
assert_eq!(test_env.config.shard_size, test_constants::DEFAULT_SHARD_SIZE);
assert_eq!(test_env.config.directory_path, test_env.env.path());
}
#[test]
fn test_shardex_test_env_with_custom_vector_size() {
let test_env = ShardexTestEnv::new("test_shardex_custom_vector").with_vector_size(256);
assert_eq!(test_env.config.vector_size, 256);
assert_eq!(test_env.config.shard_size, test_constants::DEFAULT_SHARD_SIZE);
}
#[test]
fn test_shardex_test_env_with_custom_shard_size() {
let test_env = ShardexTestEnv::new("test_shardex_custom_shard").with_shard_size(500);
assert_eq!(test_env.config.vector_size, test_constants::DEFAULT_VECTOR_SIZE);
assert_eq!(test_env.config.shard_size, 500);
}
#[test]
fn test_shardex_test_env_large() {
let test_env = ShardexTestEnv::large("test_shardex_large");
assert_eq!(test_env.config.shard_size, test_constants::LARGE_SHARD_SIZE);
assert_eq!(test_env.config.vector_size, test_constants::LARGE_VECTOR_SIZE);
}
#[test]
fn test_concurrent_test_env_basic() {
let test_env = ConcurrentTestEnv::new("test_concurrent_env_basic");
assert_eq!(test_env.env.name(), "test_concurrent_env_basic");
assert!(test_env.env.path().exists());
assert_eq!(test_env.config.vector_size, test_constants::DEFAULT_VECTOR_SIZE);
assert_eq!(test_env.config.shard_size, test_constants::DEFAULT_SHARD_SIZE);
}
#[test]
fn test_concurrent_test_env_high_concurrency() {
let test_env = ConcurrentTestEnv::high_concurrency("test_concurrent_high");
assert_eq!(test_env.config.shard_size, test_constants::LARGE_SHARD_SIZE);
assert_eq!(test_env.config.vector_size, test_constants::DEFAULT_VECTOR_SIZE);
}
#[test]
fn test_concurrent_test_env_with_custom_sizes() {
let test_env = ConcurrentTestEnv::new("test_concurrent_custom")
.with_vector_size(512)
.with_shard_size(750);
assert_eq!(test_env.config.vector_size, 512);
assert_eq!(test_env.config.shard_size, 750);
}
#[test]
fn test_wal_test_env_basic() {
let test_env = WalTestEnv::new("test_wal_env_basic");
assert_eq!(test_env.env.name(), "test_wal_env_basic");
assert!(test_env.env.path().exists());
assert_eq!(test_env.config.vector_size, test_constants::DEFAULT_VECTOR_SIZE);
assert_eq!(test_env.config.shard_size, test_constants::DEFAULT_SHARD_SIZE);
}
#[test]
fn test_wal_test_env_with_custom_vector_size() {
let test_env = WalTestEnv::new("test_wal_custom_vector").with_vector_size(384);
assert_eq!(test_env.config.vector_size, 384);
}
#[test]
fn test_wal_test_env_with_custom_shard_size() {
let test_env = WalTestEnv::new("test_wal_custom_shard").with_shard_size(600);
assert_eq!(test_env.config.shard_size, 600);
}
#[test]
fn test_wal_test_env_ensure_wal_dir() {
let test_env = WalTestEnv::new("test_wal_ensure_dir");
let result = test_env.ensure_wal_dir();
assert!(result.is_ok());
let wal_dir = result.unwrap();
assert!(wal_dir.exists());
assert!(wal_dir.is_dir());
}
#[test]
fn test_wal_test_env_build_with_wal_manager() {
let test_env = WalTestEnv::new("test_wal_build_manager");
let result = test_env.build_with_wal_manager();
assert!(result.is_ok());
let (_env, _config, _wal_manager) = result.unwrap();
}
#[tokio::test]
async fn test_concurrent_test_env_build_with_cow_index() {
let test_env = ConcurrentTestEnv::new("test_concurrent_cow_index");
let result = test_env.build_with_cow_index().await;
assert!(result.is_ok());
let (_env, config, _cow_index) = result.unwrap();
assert_eq!(config.vector_size, test_constants::DEFAULT_VECTOR_SIZE);
}
#[tokio::test]
async fn test_concurrent_test_env_build_with_concurrent() {
let test_env = ConcurrentTestEnv::new("test_concurrent_full_build");
let result = test_env.build_with_concurrent().await;
assert!(result.is_ok());
let (_env, config, _concurrent_shardex) = result.unwrap();
assert_eq!(config.vector_size, test_constants::DEFAULT_VECTOR_SIZE);
}
}