#![cfg(feature = "sqlite")]
#![allow(
clippy::unwrap_used,
clippy::indexing_slicing,
reason = "a test reading its own fixture"
)]
#[path = "sqlite_oracle/mod.rs"]
mod common;
use primase_foundations::sqlite::{Database, Error, Value};
const DB: &[u8] = include_bytes!("fixtures/sqlite/multi.db");
const TSV: &str = include_str!("fixtures/sqlite/multi.tsv");
#[test]
fn every_table_and_value_equals_pythons() {
let (tables, rows) = common::compare(DB, TSV);
assert_eq!((tables, rows), (4, 307));
}
#[test]
fn shape_of_the_fixture_is_what_the_test_claims() {
assert_eq!(DB.len(), 66 * 512);
let db = Database::open(DB).unwrap();
let wide = db.table("WIDE").unwrap();
assert_eq!(wide.column("data"), Some(4));
assert!(
wide.rows
.iter()
.any(|r| matches!(&r.values[1], Value::Text(t) if t.len() == 5000))
);
assert!(
wide.rows
.iter()
.any(|r| matches!(&r.values[4], Value::Blob(b) if b.len() == 3000))
);
let root = db.tables()[0].root_page as usize;
assert_eq!(DB[(root - 1) * 512], 0x05);
}
fn refusal(bytes: &[u8], table: &str) -> String {
match Database::open(bytes).and_then(|d| d.table(table)) {
Err(Error::Invalid(detail)) => detail,
Err(e) => panic!("{e}"),
Ok(_) => panic!("accepted a damaged file"),
}
}
#[test]
fn damaged_files_are_refused_naming_the_page() {
let db = Database::open(DB).unwrap();
let root = db.tables()[0].root_page as usize;
let at = (root - 1) * 512;
let mut bad = DB.to_vec();
bad[at] = 0x0a;
let detail = refusal(&bad, "wide");
assert!(detail.contains(&format!("page {root}")), "{detail}");
let mut bad = DB.to_vec();
bad[at + 8..at + 12].copy_from_slice(&(root as u32).to_be_bytes());
let detail = refusal(&bad, "wide");
assert!(detail.contains(&format!("page {root}")), "{detail}");
let mut bad = DB.to_vec();
bad[at + 8..at + 12].copy_from_slice(&999u32.to_be_bytes());
let detail = refusal(&bad, "wide");
assert!(detail.contains("999"), "{detail}");
assert!(matches!(
Database::open(&DB[..DB.len() - 1]),
Err(Error::Invalid(_))
));
let mut bad = DB.to_vec();
bad[18] = 2;
bad[19] = 2;
let detail = refusal(&bad, "wide");
assert!(detail.to_lowercase().contains("wal"), "{detail}");
assert!(matches!(db.table("nope"), Err(Error::NoTable(_))));
}
#[test]
fn mutated_files_never_panic() {
let mut state = 0x9e37_79b9_7f4a_7c15u64;
let mut next = move |n: usize| {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
(state % n as u64) as usize
};
let (mut refused, mut read) = (0, 0);
for _ in 0..4000 {
let mut bad = DB.to_vec();
for _ in 0..=next(3) {
let at = match next(3) {
0 => next(100),
1 => next(DB.len() / 512) * 512 + next(16),
_ => next(DB.len()),
};
bad[at] = match next(3) {
0 => 0x00,
1 => 0xff,
_ => u8::try_from(next(256)).unwrap(),
};
}
if next(20) == 0 {
bad.truncate(next(DB.len()));
}
let outcome = Database::open(&bad).and_then(|db| {
let names: Vec<String> = db.tables().iter().map(|t| t.name.clone()).collect();
names.iter().try_for_each(|n| db.table(n).map(drop))?;
db.table("sqlite_master").map(drop)
});
match outcome {
Ok(()) => read += 1,
Err(_) => refused += 1,
}
}
assert!(
refused > 500 && read > 0,
"refused {refused}, read {read}: the mutants miss the structure"
);
}