#![cfg(feature = "cli")]
use byteorder::{BigEndian, ByteOrder};
use std::io::Write;
use tempfile::NamedTempFile;
use idb::cli::diff::{execute, DiffOptions};
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 run_diff(opts: &DiffOptions) -> String {
let mut buf = Vec::new();
execute(opts, &mut buf).expect("execute diff");
String::from_utf8(buf).expect("valid utf8")
}
fn default_opts(file1: &str, file2: &str) -> DiffOptions {
DiffOptions {
file1: file1.to_string(),
file2: file2.to_string(),
verbose: false,
byte_ranges: false,
page: None,
version_aware: false,
json: false,
page_size: None,
keyring: None,
mmap: false,
}
}
#[test]
fn test_diff_identical_files() {
let pages = vec![
build_fsp_hdr_page(1, 4),
build_index_page(1, 1, 2000),
build_index_page(2, 1, 3000),
build_index_page(3, 1, 4000),
];
let tmp1 = write_tablespace(&pages);
let tmp2 = write_tablespace(&pages);
let opts = default_opts(tmp1.path().to_str().unwrap(), tmp2.path().to_str().unwrap());
let output = run_diff(&opts);
assert!(output.contains("Identical pages: 4"));
assert!(output.contains("Modified pages: 0"));
assert!(output.contains("Only in file 1: 0"));
assert!(output.contains("Only in file 2: 0"));
}
#[test]
fn test_diff_different_lsn() {
let pages1 = vec![build_fsp_hdr_page(1, 2), build_index_page(1, 1, 2000)];
let pages2 = vec![
build_fsp_hdr_page(1, 2),
build_index_page(1, 1, 5000), ];
let tmp1 = write_tablespace(&pages1);
let tmp2 = write_tablespace(&pages2);
let mut opts = default_opts(tmp1.path().to_str().unwrap(), tmp2.path().to_str().unwrap());
opts.verbose = true;
let output = run_diff(&opts);
assert!(output.contains("Identical pages: 1"));
assert!(output.contains("Modified pages: 1"));
assert!(output.contains("LSN: 2000 -> 5000"));
}
#[test]
fn test_diff_different_page_types() {
let pages1 = vec![
build_fsp_hdr_page(1, 2),
build_index_page(1, 1, 2000), ];
let mut alloc_page = vec![0u8; PS];
BigEndian::write_u32(&mut alloc_page[FIL_PAGE_OFFSET..], 1);
BigEndian::write_u32(&mut alloc_page[FIL_PAGE_PREV..], FIL_NULL);
BigEndian::write_u32(&mut alloc_page[FIL_PAGE_NEXT..], FIL_NULL);
BigEndian::write_u64(&mut alloc_page[FIL_PAGE_LSN..], 2000);
BigEndian::write_u16(&mut alloc_page[FIL_PAGE_TYPE..], 0); BigEndian::write_u32(&mut alloc_page[FIL_PAGE_SPACE_ID..], 1);
let trailer = PS - SIZE_FIL_TRAILER;
BigEndian::write_u32(&mut alloc_page[trailer + 4..], 2000u32);
write_crc32c_checksum(&mut alloc_page);
let pages2 = vec![build_fsp_hdr_page(1, 2), alloc_page];
let tmp1 = write_tablespace(&pages1);
let tmp2 = write_tablespace(&pages2);
let mut opts = default_opts(tmp1.path().to_str().unwrap(), tmp2.path().to_str().unwrap());
opts.verbose = true;
let output = run_diff(&opts);
assert!(output.contains("Page Type: INDEX -> ALLOCATED"));
}
#[test]
fn test_diff_different_page_counts() {
let pages1 = vec![
build_fsp_hdr_page(1, 4),
build_index_page(1, 1, 2000),
build_index_page(2, 1, 3000),
build_index_page(3, 1, 4000),
];
let pages2 = vec![
build_fsp_hdr_page(1, 6),
build_index_page(1, 1, 2000),
build_index_page(2, 1, 3000),
build_index_page(3, 1, 4000),
build_index_page(4, 1, 5000),
build_index_page(5, 1, 6000),
];
let tmp1 = write_tablespace(&pages1);
let tmp2 = write_tablespace(&pages2);
let opts = default_opts(tmp1.path().to_str().unwrap(), tmp2.path().to_str().unwrap());
let output = run_diff(&opts);
assert!(output.contains("Only in file 2: 2"));
assert!(output.contains("Identical pages: 3"));
}
#[test]
fn test_diff_single_page_mode() {
let pages1 = vec![
build_fsp_hdr_page(1, 3),
build_index_page(1, 1, 2000),
build_index_page(2, 1, 3000),
];
let pages2 = vec![
build_fsp_hdr_page(1, 3),
build_index_page(1, 1, 9000), build_index_page(2, 1, 3000),
];
let tmp1 = write_tablespace(&pages1);
let tmp2 = write_tablespace(&pages2);
let mut opts = default_opts(tmp1.path().to_str().unwrap(), tmp2.path().to_str().unwrap());
opts.page = Some(1);
opts.verbose = true;
let output = run_diff(&opts);
assert!(output.contains("Modified pages: 1"));
assert!(output.contains("Identical pages: 0"));
assert!(output.contains("LSN: 2000 -> 9000"));
assert!(!output.contains("Page 0:"));
assert!(!output.contains("Page 2:"));
}
#[test]
fn test_diff_byte_ranges() {
let mut page1 = build_index_page(1, 1, 2000);
let mut page2 = page1.clone();
for i in 100..110 {
page2[i] = 0xFF;
}
for i in 200..205 {
page2[i] = 0xAA;
}
write_crc32c_checksum(&mut page2);
write_crc32c_checksum(&mut page1);
let pages1 = vec![build_fsp_hdr_page(1, 2), page1];
let pages2 = vec![build_fsp_hdr_page(1, 2), page2];
let tmp1 = write_tablespace(&pages1);
let tmp2 = write_tablespace(&pages2);
let mut opts = default_opts(tmp1.path().to_str().unwrap(), tmp2.path().to_str().unwrap());
opts.verbose = true;
opts.byte_ranges = true;
let output = run_diff(&opts);
assert!(output.contains("Byte diff ranges:"));
assert!(output.contains("100-110 (10 bytes)"));
assert!(output.contains("200-205 (5 bytes)"));
}
#[test]
fn test_diff_json_output() {
let pages1 = vec![build_fsp_hdr_page(1, 2), build_index_page(1, 1, 2000)];
let pages2 = vec![
build_fsp_hdr_page(1, 2),
build_index_page(1, 1, 5000), ];
let tmp1 = write_tablespace(&pages1);
let tmp2 = write_tablespace(&pages2);
let mut opts = default_opts(tmp1.path().to_str().unwrap(), tmp2.path().to_str().unwrap());
opts.json = true;
let output = run_diff(&opts);
let json: serde_json::Value = serde_json::from_str(&output).expect("valid JSON");
assert_eq!(json["summary"]["identical"], 1);
assert_eq!(json["summary"]["modified"], 1);
assert_eq!(json["file1"]["page_size"], 16384);
assert_eq!(json["file2"]["page_size"], 16384);
assert_eq!(json["page_size_mismatch"], false);
let modified = json["modified_pages"].as_array().unwrap();
assert_eq!(modified.len(), 1);
assert_eq!(modified[0]["page_number"], 1);
}
#[test]
fn test_diff_page_size_mismatch() {
let pages1 = vec![build_fsp_hdr_page(1, 2), build_index_page(1, 1, 2000)];
let tmp1 = write_tablespace(&pages1);
let small_ps: usize = 8192;
let mut page0 = vec![0u8; small_ps];
BigEndian::write_u32(&mut page0[FIL_PAGE_OFFSET..], 0);
BigEndian::write_u32(&mut page0[FIL_PAGE_PREV..], FIL_NULL);
BigEndian::write_u32(&mut page0[FIL_PAGE_NEXT..], FIL_NULL);
BigEndian::write_u64(&mut page0[FIL_PAGE_LSN..], 1000);
BigEndian::write_u16(&mut page0[FIL_PAGE_TYPE..], 8); BigEndian::write_u32(&mut page0[FIL_PAGE_SPACE_ID..], 1);
let fsp = FIL_PAGE_DATA;
BigEndian::write_u32(&mut page0[fsp + FSP_SPACE_ID..], 1);
BigEndian::write_u32(&mut page0[fsp + FSP_SIZE..], 2);
BigEndian::write_u32(
&mut page0[fsp + FSP_SPACE_FLAGS..],
4 << FSP_FLAGS_POS_PAGE_SSIZE,
);
let trailer = small_ps - SIZE_FIL_TRAILER;
BigEndian::write_u32(&mut page0[trailer + 4..], 1000u32);
let end = small_ps - SIZE_FIL_TRAILER;
let crc1 = crc32c::crc32c(&page0[FIL_PAGE_OFFSET..FIL_PAGE_FILE_FLUSH_LSN]);
let crc2 = crc32c::crc32c(&page0[FIL_PAGE_DATA..end]);
BigEndian::write_u32(&mut page0[FIL_PAGE_SPACE_OR_CHKSUM..], crc1 ^ crc2);
let mut page1 = vec![0u8; small_ps];
BigEndian::write_u32(&mut page1[FIL_PAGE_OFFSET..], 1);
BigEndian::write_u32(&mut page1[FIL_PAGE_PREV..], FIL_NULL);
BigEndian::write_u32(&mut page1[FIL_PAGE_NEXT..], FIL_NULL);
BigEndian::write_u64(&mut page1[FIL_PAGE_LSN..], 2000);
BigEndian::write_u16(&mut page1[FIL_PAGE_TYPE..], 17855);
BigEndian::write_u32(&mut page1[FIL_PAGE_SPACE_ID..], 1);
let trailer = small_ps - SIZE_FIL_TRAILER;
BigEndian::write_u32(&mut page1[trailer + 4..], 2000u32);
let end = small_ps - SIZE_FIL_TRAILER;
let crc1 = crc32c::crc32c(&page1[FIL_PAGE_OFFSET..FIL_PAGE_FILE_FLUSH_LSN]);
let crc2 = crc32c::crc32c(&page1[FIL_PAGE_DATA..end]);
BigEndian::write_u32(&mut page1[FIL_PAGE_SPACE_OR_CHKSUM..], crc1 ^ crc2);
let mut tmp2 = NamedTempFile::new().unwrap();
tmp2.write_all(&page0).unwrap();
tmp2.write_all(&page1).unwrap();
tmp2.flush().unwrap();
let opts = default_opts(tmp1.path().to_str().unwrap(), tmp2.path().to_str().unwrap());
let output = run_diff(&opts);
assert!(output.contains("WARNING: Page size mismatch"));
}