extern crate alloc;
use remdb::config::WALCompressionType;
use remdb::time_series::compression::*;
use remdb::time_series::*;
use remdb::*;
use serial_test::serial;
#[test]
fn test_time_series_config() {
let config = TimeSeriesConfig::default();
assert_eq!(
config.partition_duration().as_secs(),
3600,
"默认分区时长应为1小时"
);
assert_eq!(
config.retention_period().as_secs(),
7 * 24 * 3600,
"默认保留期应为7天"
);
assert_eq!(
config.compression,
CompressionType::DeltaRunLength,
"默认压缩类型应为DeltaRunLength"
);
assert_eq!(config.max_partitions, 1000, "默认最大分区数应为1000");
let custom_config = TimeSeriesConfig {
partition_duration_secs: 30 * 60, retention_period_secs: config.retention_period_secs,
compression: CompressionType::Delta,
max_partitions: 500,
};
assert_eq!(
custom_config.partition_duration().as_secs(),
1800,
"自定义分区时长应为30分钟"
);
assert_eq!(
custom_config.retention_period().as_secs(),
7 * 24 * 3600,
"自定义保留期应为7天"
);
assert_eq!(
custom_config.compression,
CompressionType::Delta,
"自定义压缩类型应为Delta"
);
assert_eq!(custom_config.max_partitions, 500, "自定义最大分区数应为500");
}
#[test]
fn test_compression_algorithms() {
let values = [100, 101, 102, 103, 104, 105, 106, 107, 108, 109];
let compressed = compress_delta(&values);
assert_eq!(
compressed.len(),
values.len() * 8,
"Delta编码当前实现应存储所有delta值"
);
let decompressed = decompress_delta(&compressed, values.len());
assert_eq!(decompressed, values, "Delta解码应能正确恢复原始数据");
let run_values = [5u64, 5u64, 5u64, 5u64, 5u64, 3u64, 3u64, 3u64, 7u64, 7u64];
let run_compressed = compress_run_length(&run_values);
assert!(
run_compressed.len() < run_values.len() * 8,
"Run-Length编码应该能压缩重复数据"
);
let run_decompressed = decompress_run_length(&run_compressed);
assert_eq!(
run_decompressed, run_values,
"Run-Length解码应能正确恢复原始数据"
);
let float_values = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5];
let float_compressed = compress_delta_float(&float_values);
assert!(
float_compressed.len() == float_values.len() * 8,
"浮点数Delta编码应该保持相同大小"
);
let float_decompressed = decompress_delta_float(&float_compressed, float_values.len());
for i in 0..float_values.len() {
assert!(
(float_decompressed[i] - float_values[i]).abs() < 0.0001,
"浮点数Delta解码应能正确恢复原始数据"
);
}
}
#[test]
fn test_time_series_index() {
let index = TimeSeriesIndex::new();
for i in 0..100 {
let timestamp = 1609459200 + i * 60; index.insert(timestamp, i as usize); }
let result = index.query_time_range(1609459200, 1609459200 + 30 * 60); assert_eq!(result.len(), 31, "时间范围查询应返回31条记录");
let empty_result = index.query_time_range(1609459200 - 1000, 1609459200 - 500);
assert_eq!(empty_result.len(), 0, "空时间范围查询应返回0条记录");
let boundary_result = index.query_time_range(1609459200, 1609459200); assert_eq!(boundary_result.len(), 1, "边界条件查询应返回1条记录");
assert_eq!(boundary_result[0], 0, "边界条件查询应返回正确的记录ID");
index.clear_before(1609459200 + 50 * 60); let after_clean_result = index.query_time_range(1609459200, 1609459200 + 30 * 60);
assert_eq!(after_clean_result.len(), 0, "清理后查询应返回0条记录");
let remaining_result = index.query_time_range(1609459200 + 50 * 60, 1609459200 + 100 * 60);
assert_eq!(remaining_result.len(), 50, "清理后剩余记录应正确");
}
#[test]
fn test_lifecycle_manager() {
let manager = LifecycleManager::new(core::time::Duration::from_hours(24));
let now = LifecycleManager::get_current_timestamp();
let recent_timestamp = now - 12 * 3600; assert!(
!manager.is_expired(recent_timestamp),
"12小时前的数据不应过期"
);
let expired_timestamp = now - 25 * 3600; assert!(
manager.is_expired(expired_timestamp),
"25小时前的数据应过期"
);
let boundary_timestamp = now - 24 * 3600; assert!(
!manager.is_expired(boundary_timestamp),
"刚好24小时前的数据不应过期"
);
let over_timestamp = now - 24 * 3600 - 1; assert!(
manager.is_expired(over_timestamp),
"超过24小时前的数据应过期"
);
let future_timestamp = now + 24 * 3600; assert!(!manager.is_expired(future_timestamp), "未来数据不应过期");
}
#[test]
fn test_partition_manager() {
let mut manager = PartitionManager::new(core::time::Duration::from_hours(1), 100);
let now = 1609459200;
let partition1 = manager.get_or_create_partition(now);
let partition2 = manager.get_or_create_partition(now + 3600); let partition3 = manager.get_or_create_partition(now + 7200);
assert!(
!std::ptr::eq(partition1.as_ref(), partition2.as_ref()),
"不同时间应创建不同分区"
);
assert!(
!std::ptr::eq(partition2.as_ref(), partition3.as_ref()),
"不同时间应创建不同分区"
);
let same_partition = manager.get_or_create_partition(now);
assert!(
std::ptr::eq(partition1.as_ref(), same_partition.as_ref()),
"同一时间应返回相同分区"
);
let partitions = manager.get_partitions_in_range(now - 3600, now + 10800); assert!(partitions.len() >= 3, "应返回至少3个分区");
manager.cleanup_expired_partitions(now + 10800, core::time::Duration::from_hours(1)); let remaining_partitions = manager.get_partitions_in_range(now - 3600, now + 10800);
}
#[test]
fn test_time_series_record() {
let record = TimeSeriesRecord {
timestamp: 1609459200,
value: 42.0,
tag_count: 2,
tags: [100, 200, 0, 0, 0, 0, 0, 0],
};
assert_eq!(record.timestamp, 1609459200, "时间戳应正确");
assert_eq!(record.value, 42.0, "值应正确");
assert_eq!(record.tag_count, 2, "标签数量应正确");
assert_eq!(record.tags[0], 100, "第一个标签值应正确");
assert_eq!(record.tags[1], 200, "第二个标签值应正确");
let cloned_record = record;
assert_eq!(
cloned_record.timestamp, record.timestamp,
"克隆记录的时间戳应相同"
);
assert_eq!(cloned_record.value, record.value, "克隆记录的值应相同");
}
#[test]
#[serial]
fn test_time_series_integration() {
unsafe {
static mut DB_MEMORY: [u8; 1048576] = [0u8; 1048576];
let _ = memory::allocator::init_global_allocator(DB_MEMORY.as_mut_ptr(), DB_MEMORY.len());
let config = TimeSeriesConfig::default();
assert_eq!(
config.partition_duration().as_secs(),
3600,
"默认分区时长应为1小时"
);
let compression = CompressionType::Delta;
assert!(
matches!(compression, CompressionType::Delta),
"压缩类型应正确"
);
let record = TimeSeriesRecord {
timestamp: 1609459200u64,
value: 42.0,
tag_count: 2,
tags: [100, 200, 0, 0, 0, 0, 0, 0],
};
assert_eq!(record.timestamp, 1609459200u64, "时间戳应正确");
assert_eq!(record.value, 42.0, "值应正确");
}
}
#[test]
#[serial]
fn test_time_series_batch_performance() {
unsafe {
static mut DB_MEMORY: [u8; 2097152] = [0u8; 2097152];
let _ = memory::allocator::init_global_allocator(DB_MEMORY.as_mut_ptr(), DB_MEMORY.len());
static TEST_CONFIG: std::sync::LazyLock<crate::config::DbConfig> =
std::sync::LazyLock::new(|| crate::config::DbConfig {
tables: vec![],
total_memory: 2097152,
low_power_mode_supported: false,
low_power_max_records: None,
default_max_records: 10000,
memory_allocator: &crate::config::DefaultMemoryAllocator,
wal_config: crate::config::WALConfig {
log_path: "./wal",
log_mode: crate::config::LogMode::Async,
checkpoint_interval_ms: 60000,
log_file_size_limit: 16 * 1024 * 1024,
log_prealloc_size: 0,
log_segment_size: 16 * 1024 * 1024,
retained_checkpoints: 3,
max_consecutive_invalid: 100,
skip_threshold: 1000,
skip_block_size: 1024 * 1024,
max_skip_attempts: 3,
compression_type: WALCompressionType::None,
compression_level: 3,
},
time_series_defaults: TimeSeriesConfig::DEFAULT,
#[cfg(feature = "pubsub")]
pubsub_config: None,
#[cfg(feature = "ha")]
ha_config: Some(crate::config::HAConfig {
node_id: 1, ha_role: crate::ha::HARole::Auto,
replication_mode: crate::ha::ReplicationMode::Async,
heartbeat_interval_ms: 1000,
failure_detection_ms: 5000,
sync_timeout_ms: 5000,
master_address: None,
master_port: None,
replication_port: 5556,
}),
model_worker_config: Default::default(),
});
let config = &*TEST_CONFIG;
let mut db = RemDb::new(config);
if crate::platform::PLATFORM.get().is_none() {
struct TestPlatform;
impl crate::platform::Platform for TestPlatform {
fn get_timestamp(&self) -> u64 {
1609459200000
}
fn get_timestamp_us(&self) -> u64 {
1609459200000000
}
fn spin_lock(&self, lock: &mut u32) {
while unsafe {
core::sync::atomic::AtomicU32::from_ptr(lock as *mut u32)
.compare_exchange(
0,
1,
core::sync::atomic::Ordering::Acquire,
core::sync::atomic::Ordering::Relaxed,
)
.is_err()
} {
core::hint::spin_loop();
}
}
fn spin_unlock(&self, lock: &mut u32) {
unsafe {
core::sync::atomic::AtomicU32::from_ptr(lock as *mut u32)
.store(0, core::sync::atomic::Ordering::Release);
}
}
fn compiler_barrier(&self) {
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
fn full_memory_barrier(&self) {
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
}
fn memcpy(&self, dest: *mut u8, src: *const u8, size: usize) {
unsafe {
core::ptr::copy(src, dest, size);
}
}
fn memset(&self, dest: *mut u8, value: u8, size: usize) {
unsafe {
core::ptr::write_bytes(dest, value, size);
}
}
fn delay_ms(&self, ms: u32) {
std::thread::sleep(std::time::Duration::from_millis(ms as u64));
}
fn delay_us(&self, us: u32) {
std::thread::sleep(std::time::Duration::from_micros(us as u64));
}
fn file_open(
&self,
_path: &str,
_mode: crate::platform::FileMode,
) -> crate::platform::FileResult<crate::platform::FileHandle> {
Err(())
}
fn file_close(
&self,
_handle: crate::platform::FileHandle,
) -> crate::platform::FileResult<()> {
Err(())
}
fn file_write(
&self,
_handle: crate::platform::FileHandle,
_buffer: *const u8,
_size: usize,
) -> crate::platform::FileResult<usize> {
Err(())
}
fn file_read(
&self,
_handle: crate::platform::FileHandle,
_buffer: *mut u8,
_size: usize,
) -> crate::platform::FileResult<usize> {
Err(())
}
fn file_seek(
&self,
_handle: crate::platform::FileHandle,
_offset: i64,
_whence: crate::platform::SeekWhence,
) -> crate::platform::FileResult<u64> {
Err(())
}
fn file_remove(&self, _path: &str) -> crate::platform::FileResult<()> {
Err(())
}
fn file_size(&self, _path: &str) -> crate::platform::FileResult<usize> {
Err(())
}
fn crc32(&self, _data: *const u8, _size: usize) -> u32 {
0
}
}
static TEST_PLATFORM: TestPlatform = TestPlatform;
crate::platform::init_platform(&TEST_PLATFORM);
}
db.init().unwrap();
let record_count = 1000;
let mut records = Vec::with_capacity(record_count);
for i in 0..record_count {
let record = TimeSeriesRecord {
timestamp: 1609459200u64 + i as u64 * 10,
value: i as f64,
tag_count: 0,
tags: [0; 8],
};
records.push(record);
}
let _ = db.create_time_series_table("test_perf", "timestamp", "value", &[], None);
assert_eq!(
core::mem::size_of::<TimeSeriesRecord>(),
88,
"时序记录大小应正确"
);
assert!(
core::mem::align_of::<TimeSeriesRecord>() <= 8,
"时序记录对齐应不超过8字节"
);
}
}
#[test]
fn test_config_inheritance() {
let base_config = TimeSeriesConfig::default();
let custom_config = TimeSeriesConfig {
partition_duration_secs: 30 * 60, ..base_config
};
assert_eq!(
custom_config.partition_duration().as_secs(),
1800,
"自定义分区时长应生效"
);
assert_eq!(
custom_config.retention_period(),
base_config.retention_period(),
"未修改的保留期应继承默认值"
);
assert_eq!(
custom_config.compression, base_config.compression,
"未修改的压缩类型应继承默认值"
);
assert_eq!(
custom_config.max_partitions, base_config.max_partitions,
"未修改的最大分区数应继承默认值"
);
}