128 lines
4.6 KiB
Rust
128 lines
4.6 KiB
Rust
|
|
//! The search connection has to *open* at the ceiling it was sized for.
|
||
|
|
//!
|
||
|
|
//! `db::schema`'s unit tests cover the arithmetic; this covers the wiring,
|
||
|
|
//! which is where it can silently do nothing: the ceiling is resolved inside
|
||
|
|
//! `open_search_reader` from a `sqlite_stat1` read and a process-global, and a
|
||
|
|
//! break anywhere along that path leaves a connection quietly running on the
|
||
|
|
//! read-only profile's 4 MiB with every test still green.
|
||
|
|
//!
|
||
|
|
//! Its own integration binary, and one `#[test]`, for the reason
|
||
|
|
//! `tests/encrypted.rs` gives: it drives process-global state — the key and
|
||
|
|
//! the cache override — which unit tests must never do, because the lib test
|
||
|
|
//! binary runs them in parallel against the same globals.
|
||
|
|
|
||
|
|
use quicksearch_core::db;
|
||
|
|
use quicksearch_core::db::schema::{
|
||
|
|
recommended_search_cache_mib, SEARCH_CACHE_MAX_MIB, SEARCH_CACHE_PLAIN_MIB,
|
||
|
|
};
|
||
|
|
use quicksearch_core::testutil::{measurement_key, scratch_db, seed_index, SeedSpec};
|
||
|
|
|
||
|
|
/// Enough rows that the recommendation clears the floor and is therefore
|
||
|
|
/// actually derived rather than clamped — at 168 B/file, 16 MiB is reached
|
||
|
|
/// around 100k. Seeded without content: the FTS write is the slow part and
|
||
|
|
/// this measures nothing about it.
|
||
|
|
const FILES: usize = 150_000;
|
||
|
|
|
||
|
|
fn spec() -> SeedSpec {
|
||
|
|
SeedSpec {
|
||
|
|
files: FILES,
|
||
|
|
// No document bodies at all; `content_every` past `files` never fires.
|
||
|
|
content_every: FILES + 1,
|
||
|
|
commit_every: 25_000,
|
||
|
|
dup_every: 2,
|
||
|
|
dir_depth: 6,
|
||
|
|
..SeedSpec::default()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// `PRAGMA cache_size` reads back as the negative KiB it was set to.
|
||
|
|
fn cache_mib(conn: &rusqlite::Connection) -> i64 {
|
||
|
|
let kib: i64 = conn
|
||
|
|
.query_row("PRAGMA cache_size", [], |r| r.get(0))
|
||
|
|
.unwrap();
|
||
|
|
assert!(
|
||
|
|
kib < 0,
|
||
|
|
"cache_size came back as {} — a positive value is a *page* count, \
|
||
|
|
which would mean the KiB form never reached SQLite",
|
||
|
|
kib
|
||
|
|
);
|
||
|
|
-kib / 1024
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn the_search_connection_opens_at_the_ceiling_it_was_sized_for() {
|
||
|
|
let path = scratch_db("searchcache");
|
||
|
|
let db_path = path.to_string_lossy().into_owned();
|
||
|
|
|
||
|
|
// --- keyed, automatic -------------------------------------------------
|
||
|
|
db::set_process_key(Some(measurement_key()));
|
||
|
|
db::set_search_cache_override(None);
|
||
|
|
seed_index(&path, &spec());
|
||
|
|
|
||
|
|
// Without stats there is nothing to derive from, so the floor is correct
|
||
|
|
// and is what must come out.
|
||
|
|
{
|
||
|
|
let conn = db::open::open_search_reader(&db_path).unwrap();
|
||
|
|
assert_eq!(
|
||
|
|
cache_mib(&conn),
|
||
|
|
recommended_search_cache_mib(0, true),
|
||
|
|
"a never-analyzed index must fall back, not guess"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// `PRAGMA optimize` is what a real indexing run leaves behind; from here
|
||
|
|
// the row count is readable and the ceiling should track it.
|
||
|
|
{
|
||
|
|
let conn = db::open_existing(&db_path, true).unwrap();
|
||
|
|
conn.execute_batch("ANALYZE;").unwrap();
|
||
|
|
}
|
||
|
|
let expected = recommended_search_cache_mib(FILES as i64, true);
|
||
|
|
assert!(
|
||
|
|
expected > recommended_search_cache_mib(0, true),
|
||
|
|
"the corpus must be big enough to clear the floor, or this proves nothing"
|
||
|
|
);
|
||
|
|
{
|
||
|
|
let conn = db::open::open_search_reader(&db_path).unwrap();
|
||
|
|
assert_eq!(
|
||
|
|
cache_mib(&conn),
|
||
|
|
expected,
|
||
|
|
"the derived ceiling did not reach the connection"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- an explicit override wins, including past the automatic cap ------
|
||
|
|
let big = SEARCH_CACHE_MAX_MIB * 2;
|
||
|
|
db::set_search_cache_override(Some(big));
|
||
|
|
{
|
||
|
|
let conn = db::open::open_search_reader(&db_path).unwrap();
|
||
|
|
assert_eq!(
|
||
|
|
cache_mib(&conn),
|
||
|
|
big,
|
||
|
|
"an explicit ceiling must be applied verbatim, above the cap"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
db::set_search_cache_override(None);
|
||
|
|
|
||
|
|
// --- plain, automatic -------------------------------------------------
|
||
|
|
// Same corpus, no key: the sweep found no knee unencrypted, so this must
|
||
|
|
// be flat regardless of how large the index is.
|
||
|
|
let plain = scratch_db("searchcache-plain");
|
||
|
|
db::set_process_key(None);
|
||
|
|
seed_index(&plain, &spec());
|
||
|
|
{
|
||
|
|
let conn = db::open_existing(&plain.to_string_lossy(), true).unwrap();
|
||
|
|
conn.execute_batch("ANALYZE;").unwrap();
|
||
|
|
}
|
||
|
|
{
|
||
|
|
let conn = db::open::open_search_reader(&plain.to_string_lossy()).unwrap();
|
||
|
|
assert_eq!(
|
||
|
|
cache_mib(&conn),
|
||
|
|
SEARCH_CACHE_PLAIN_MIB,
|
||
|
|
"an unencrypted index must not grow its cache with the corpus"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
std::fs::remove_dir_all(path.parent().unwrap()).ok();
|
||
|
|
std::fs::remove_dir_all(plain.parent().unwrap()).ok();
|
||
|
|
}
|