#![cfg(feature = "cli")]
use byteorder::{BigEndian, ByteOrder};
use std::fs;
use std::io::Write;
use tempfile::NamedTempFile;
use idb::cli::watch::{execute, WatchOptions};
use idb::innodb::constants::*;
const PAGE_SIZE: u32 = 16384;
const PS: usize = PAGE_SIZE as usize;
fn build_fsp_hdr_page(space_id: u32, total_pages: u32) -> Vec<u8> {
let mut page = vec![0u8; PS];
BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], 0);
BigEndian::write_u32(&mut page[FIL_PAGE_PREV..], FIL_NULL);
BigEndian::write_u32(&mut page[FIL_PAGE_NEXT..], FIL_NULL);
BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], 1000);
BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 8); BigEndian::write_u64(&mut page[FIL_PAGE_FILE_FLUSH_LSN..], 1000);
BigEndian::write_u32(&mut page[FIL_PAGE_SPACE_ID..], space_id);
let fsp = FIL_PAGE_DATA;
BigEndian::write_u32(&mut page[fsp + FSP_SPACE_ID..], space_id);
BigEndian::write_u32(&mut page[fsp + FSP_SIZE..], total_pages);
BigEndian::write_u32(&mut page[fsp + FSP_FREE_LIMIT..], total_pages);
BigEndian::write_u32(&mut page[fsp + FSP_SPACE_FLAGS..], 0);
let trailer = PS - SIZE_FIL_TRAILER;
BigEndian::write_u32(&mut page[trailer + 4..], 1000 & 0xFFFFFFFF);
write_crc32c_checksum(&mut page);
page
}
fn build_index_page(page_num: u32, space_id: u32, lsn: u64) -> Vec<u8> {
let mut page = vec![0u8; PS];
BigEndian::write_u32(&mut page[FIL_PAGE_OFFSET..], page_num);
BigEndian::write_u32(&mut page[FIL_PAGE_PREV..], FIL_NULL);
BigEndian::write_u32(&mut page[FIL_PAGE_NEXT..], FIL_NULL);
BigEndian::write_u64(&mut page[FIL_PAGE_LSN..], lsn);
BigEndian::write_u16(&mut page[FIL_PAGE_TYPE..], 17855); BigEndian::write_u32(&mut page[FIL_PAGE_SPACE_ID..], space_id);
let trailer = PS - SIZE_FIL_TRAILER;
BigEndian::write_u32(&mut page[trailer + 4..], (lsn & 0xFFFFFFFF) as u32);
write_crc32c_checksum(&mut page);
page
}
fn write_crc32c_checksum(page: &mut [u8]) {
let end = PS - SIZE_FIL_TRAILER;
let crc1 = crc32c::crc32c(&page[FIL_PAGE_OFFSET..FIL_PAGE_FILE_FLUSH_LSN]);
let crc2 = crc32c::crc32c(&page[FIL_PAGE_DATA..end]);
BigEndian::write_u32(&mut page[FIL_PAGE_SPACE_OR_CHKSUM..], crc1 ^ crc2);
}
fn write_tablespace(pages: &[Vec<u8>]) -> NamedTempFile {
let mut tmp = NamedTempFile::new().expect("create temp file");
for page in pages {
tmp.write_all(page).expect("write page");
}
tmp.flush().expect("flush");
tmp
}
fn default_opts(file: &str) -> WatchOptions {
WatchOptions {
file: file.to_string(),
interval: 100,
verbose: false,
json: false,
events: false,
page_size: None,
keyring: None,
mmap: false,
}
}
#[test]
fn test_watch_file_deleted_stops_gracefully() {
let tmp = write_tablespace(&[build_fsp_hdr_page(1, 2), build_index_page(1, 1, 2000)]);
let path = tmp.path().to_str().unwrap().to_string();
drop(tmp);
let mut buf = Vec::new();
let opts = default_opts(&path);
let result = execute(&opts, &mut buf);
assert!(result.is_err());
}
#[test]
fn test_watch_file_deleted_during_poll_text() {
let dir = tempfile::tempdir().expect("create tempdir");
let file_path = dir.path().join("test.ibd");
let pages = vec![build_fsp_hdr_page(1, 2), build_index_page(1, 1, 2000)];
{
let mut f = fs::File::create(&file_path).expect("create file");
for page in &pages {
f.write_all(page).expect("write page");
}
f.flush().expect("flush");
}
fs::remove_file(&file_path).expect("delete file");
let opts = WatchOptions {
file: file_path.to_str().unwrap().to_string(),
interval: 50,
verbose: false,
json: false,
events: false,
page_size: None,
keyring: None,
mmap: false,
};
let mut buf = Vec::new();
let result = execute(&opts, &mut buf);
assert!(result.is_err());
}
#[test]
fn test_watch_json_opts_construction() {
let tmp = write_tablespace(&[build_fsp_hdr_page(1, 2), build_index_page(1, 1, 2000)]);
let opts = WatchOptions {
file: tmp.path().to_str().unwrap().to_string(),
interval: 500,
verbose: true,
json: true,
events: false,
page_size: Some(16384),
keyring: None,
mmap: false,
};
assert_eq!(opts.interval, 500);
assert!(opts.verbose);
assert!(opts.json);
assert!(!opts.events);
assert_eq!(opts.page_size, Some(16384));
}
#[test]
fn test_watch_nonexistent_file() {
let opts = default_opts("/tmp/nonexistent_watch_test_12345.ibd");
let mut buf = Vec::new();
let result = execute(&opts, &mut buf);
assert!(result.is_err());
}
#[test]
fn test_watch_page_size_override() {
let tmp = write_tablespace(&[build_fsp_hdr_page(1, 2), build_index_page(1, 1, 2000)]);
let opts = WatchOptions {
file: tmp.path().to_str().unwrap().to_string(),
interval: 100,
verbose: false,
json: false,
events: false,
page_size: Some(16384),
keyring: None,
mmap: false,
};
assert_eq!(opts.page_size, Some(16384));
}
#[test]
fn test_watch_events_opts_construction() {
let tmp = write_tablespace(&[build_fsp_hdr_page(1, 2), build_index_page(1, 1, 2000)]);
let opts = WatchOptions {
file: tmp.path().to_str().unwrap().to_string(),
interval: 500,
verbose: false,
json: false,
events: true,
page_size: None,
keyring: None,
mmap: false,
};
assert!(opts.events);
assert!(!opts.json);
}
#[test]
fn test_watch_events_and_json_opts() {
let tmp = write_tablespace(&[build_fsp_hdr_page(1, 2), build_index_page(1, 1, 2000)]);
let opts = WatchOptions {
file: tmp.path().to_str().unwrap().to_string(),
interval: 500,
verbose: false,
json: true,
events: true,
page_size: None,
keyring: None,
mmap: false,
};
assert!(opts.events);
assert!(opts.json);
}
#[test]
fn test_watch_events_nonexistent_file() {
let opts = WatchOptions {
file: "/tmp/nonexistent_watch_events_test_12345.ibd".to_string(),
interval: 100,
verbose: false,
json: false,
events: true,
page_size: None,
keyring: None,
mmap: false,
};
let mut buf = Vec::new();
let result = execute(&opts, &mut buf);
assert!(result.is_err());
}