* Copyright (c) 2025 Huawei Technologies Co., Ltd.
* This software is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
#[cfg(test)]
mod tests {
use crate::{
RecordBlock, RecordPayload, RecorderConfig, RecorderHeader, RequestLogger, RequestPlayer,
RequestPlayerBuilder, FOOTER_MAGIC, RECORD_FILE_MAGIC,
};
#[test]
fn test_protobuf_types() {
let header = RecorderHeader::new();
assert_eq!(header.magic, RECORD_FILE_MAGIC);
assert!(header.version.is_some());
assert!(header.context.is_some());
let block = RecordBlock::default();
assert_eq!(block.block_id, 0);
assert_eq!(block.timestamp_ns, 0);
assert_eq!(block.thread_id, 0);
assert!(block.payload.is_none());
println!("✅ Protobuf types basic functionality test passed");
}
#[test]
fn test_record_block_creation() {
let test_data = b"test_request_data";
let payload = RecordPayload {
length: test_data.len() as u64,
crc32_checksum: crc32fast::hash(test_data),
request_data: test_data.to_vec(),
};
let block = RecordBlock {
block_id: 1,
timestamp_ns: 123456789,
thread_id: 999,
payload: Some(payload),
};
assert_eq!(block.block_id, 1);
assert_eq!(block.timestamp_ns, 123456789);
assert_eq!(block.thread_id, 999);
assert!(block.payload.is_some());
let payload = block.payload.as_ref().unwrap();
assert_eq!(payload.length, test_data.len() as u64);
assert_eq!(
<Vec<u8> as AsRef<[u8]>>::as_ref(&payload.request_data),
test_data
);
println!("✅ RecordBlock creation test passed");
}
#[test]
fn test_crc32_checksum() {
let test_data = b"Hello, World!";
let checksum1 = crc32fast::hash(test_data);
let checksum2 = crc32fast::hash(test_data);
assert_eq!(checksum1, checksum2);
let other_data = b"Hello, Rust!";
let checksum3 = crc32fast::hash(other_data);
assert_ne!(checksum1, checksum3);
println!("✅ CRC32 checksum test passed");
}
#[test]
fn test_recorder_config() {
let default_config = RecorderConfig::default();
assert_eq!(default_config.output_path.to_str().unwrap(), "requests.rec");
assert_eq!(default_config.block_size, 128);
assert_eq!(default_config.max_file_size, 1024 * 1024 * 1024);
assert!(default_config.checksum_enabled);
let mut custom_config = default_config.clone();
custom_config.block_size = 128;
custom_config.compression = true;
assert_eq!(custom_config.block_size, 128);
assert!(custom_config.compression);
println!("✅ RecorderConfig test passed");
}
#[test]
fn test_timestamp_generation() {
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp1 = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos() as u64;
std::thread::sleep(std::time::Duration::from_millis(1));
let timestamp2 = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos() as u64;
assert!(timestamp2 > timestamp1);
println!("✅ Timestamp generation test passed");
}
#[test]
fn test_request_logger() {
let config = RecorderConfig::default();
let mut logger = RequestLogger::new(config).unwrap();
let test_data = b"test_request_data1";
logger.log_request_serialized(test_data.to_vec()).unwrap();
let test_data = b"test_request_data2";
logger.log_request_serialized(test_data.to_vec()).unwrap();
let test_data = b"test_request_data3";
logger.log_request_serialized(test_data.to_vec()).unwrap();
let test_data = b"test_request_data4";
logger.log_request_serialized(test_data.to_vec()).unwrap();
let test_data = b"A".repeat(1024);
logger.log_request_serialized(test_data.to_vec()).unwrap();
logger.close().unwrap();
println!("✅ RequestLogger test passed");
}
#[test]
fn test_request_player() {
let mut player = RequestPlayer::from_file("requests.rec").unwrap();
while let Some(Ok(block)) = player.next_block().transpose() {
if let Some(payload) = block.payload {
let request_data = payload.request_data;
let expected_crc = payload.crc32_checksum;
let actual_crc = crc32fast::hash(&request_data);
if expected_crc == actual_crc {
println!(
"Data length: {}; data: {:?}; str: {}",
payload.length,
request_data,
String::from_utf8_lossy(&request_data)
);
}
}
}
println!("✅ RequestPlayer test passed");
}
#[test]
fn test_request_player_builder() {
let mut player = RequestPlayerBuilder::new()
.file_path("requests.rec")
.build()
.unwrap();
let mut count = 0;
while let Some(Ok(_)) = player.next_block().transpose() {
count += 1;
}
assert_eq!(count, 5, "Should have 5 records");
}
#[test]
fn test_request_player_header() {
let player = RequestPlayer::from_file("requests.rec").unwrap();
let header = player.header();
assert_eq!(header.magic, RECORD_FILE_MAGIC);
let version = header.version.unwrap();
println!("Version: {:#?}", version);
assert!(header.version.is_some(), "Version should be set");
assert!(header.context.is_some(), "Context should be set");
}
#[test]
fn test_request_player_footer() {
let player = RequestPlayer::from_file("requests.rec").unwrap();
let footer = player.footer();
let footerstart = player.position();
assert_eq!(footer.blocks, 5);
assert_eq!(footer.sha256_digest, [0xff; 32]);
assert_eq!(footer.magic, FOOTER_MAGIC);
assert_eq!(footerstart, 0);
}
#[test]
fn test_async_performance_benefit() {
use std::time::Instant;
use tempfile::NamedTempFile;
let request_count = 1000000u32;
let test_requests: Vec<[u8; 4]> = (0..request_count).map(|i| i.to_be_bytes()).collect();
let temp_file_sync = NamedTempFile::new().expect("Failed to create temp file");
let sync_start = Instant::now();
{
let config = RecorderConfig {
output_path: temp_file_sync.path().to_path_buf(),
block_size: 1024,
..Default::default()
};
let mut logger =
RequestLogger::new_with_async(config, false).expect("Failed to create sync logger");
println!("sync mode: started logging\n");
for request in &test_requests {
logger
.log_request_serialized(request.to_vec())
.expect("Failed to log");
}
println!("sync mode: finished logging and closing\n");
logger.close().expect("Failed to close");
}
let sync_duration = sync_start.elapsed();
let temp_file_async = NamedTempFile::new().expect("Failed to create temp file");
let async_start = Instant::now();
{
let config = RecorderConfig {
output_path: temp_file_async.path().to_path_buf(),
block_size: 1024,
..Default::default()
};
let mut logger = RequestLogger::new(config).expect("Failed to create async logger");
println!("async mode: started logging\n");
for request in &test_requests {
logger
.log_request_serialized(request.to_vec())
.expect("Failed to log");
}
println!("async mode: finished logging and closing\n");
logger.close().expect("Failed to close");
}
let async_duration = async_start.elapsed();
println!("\n📊 Performance Comparison ({} requests):", request_count);
println!(" Sync mode: {:?}", sync_duration);
println!(" Async mode: {:?}", async_duration);
println!(
" Speedup: {:.2}x",
sync_duration.as_secs_f64() / async_duration.as_secs_f64()
);
println!("✅ Performance test completed");
}
#[test]
fn test_async_log_and_replay_performance() {
use std::path::PathBuf;
use std::time::Instant;
let request_count = 1000000u32;
let test_requests: Vec<[u8; 4]> = (0..request_count).map(|i| i.to_be_bytes()).collect();
let temp_file_async = PathBuf::from("lager_requests.rec");
let async_start_log = Instant::now();
{
let config = RecorderConfig {
output_path: temp_file_async.to_path_buf(),
block_size: 128,
..Default::default()
};
println!("async mode: output_path: {}\n", temp_file_async.display());
let mut logger = RequestLogger::new(config).expect("Failed to create async logger");
println!("async mode: started logging {}\n", test_requests.len());
for request in &test_requests {
logger
.log_request_serialized(request.to_vec())
.expect("Failed to log");
}
println!("async mode: finished logging and closing\n");
logger.close().expect("Failed to close");
}
let async_log_duration = async_start_log.elapsed();
let mut player =
RequestPlayer::from_file(temp_file_async).expect("Failed to create player");
let footer = player.footer();
let start = player.position();
assert_eq!(footer.blocks, request_count as u64);
assert_eq!(footer.sha256_digest, [0xff; 32]);
assert_eq!(start, 0);
let mut i = 0;
let async_start_player = Instant::now();
while let Some(Ok(block)) = player.next_block().transpose() {
let request = block.payload.expect("Failed to rebuild request");
let arr: [u8; 4] = request.request_data.try_into().unwrap();
let n = u32::from_be_bytes(arr);
assert_eq!(n, i);
i += 1;
}
let async_player_duration = async_start_player.elapsed();
println!("\n📊 Performance Comparison ({} requests):", request_count);
println!("played {} requests\n", i);
println!(
" Async mode: async_log_duration {:?}, async_player_duration {:?}",
async_log_duration, async_player_duration
);
println!("✅ Performance test completed");
}
}