Core tests make a throwaway blob store with blobs::scratch
blobs, store and portable each built a BlobStore in a temp directory their own way: pid+tag twice, a uuid once. blobs::scratch(tag) is that, with a counter, so two tests can never share a directory even if they pick the same tag. The desktop's and ffi's temp-dir helpers stay, one per crate: sharing them would need a test-util feature on the core crate. DRY pass #2, batch 2, F9 (#5372). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -596,8 +596,8 @@ mod tests {
|
|||||||
|
|
||||||
fn store() -> (Connection, BlobStore, std::path::PathBuf) {
|
fn store() -> (Connection, BlobStore, std::path::PathBuf) {
|
||||||
let conn = crate::local::memory_conn().unwrap();
|
let conn = crate::local::memory_conn().unwrap();
|
||||||
let dir = std::env::temp_dir().join(format!("inkwell-portable-{}", uuid::Uuid::new_v4()));
|
let blobs = crate::sync::blobs::scratch("portable");
|
||||||
let blobs = BlobStore::new(dir.clone()).unwrap();
|
let dir = blobs.root().to_path_buf();
|
||||||
(conn, blobs, dir)
|
(conn, blobs, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1810,12 +1810,6 @@ mod tests {
|
|||||||
assert_eq!(list_labels(&conn).expect("labels")[0].count, Some(1));
|
assert_eq!(list_labels(&conn).expect("labels")[0].count, Some(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn blobs(tag: &str) -> crate::sync::blobs::BlobStore {
|
|
||||||
let dir = std::env::temp_dir().join(format!("ts-store-blobs-{}-{tag}", std::process::id()));
|
|
||||||
let _ = std::fs::remove_dir_all(&dir);
|
|
||||||
crate::sync::blobs::BlobStore::new(dir).expect("blobs")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tombstones(conn: &Connection) -> Vec<(String, String)> {
|
fn tombstones(conn: &Connection) -> Vec<(String, String)> {
|
||||||
let mut stmt = conn
|
let mut stmt = conn
|
||||||
.prepare("SELECT entity, id FROM pending_deletes ORDER BY entity, id")
|
.prepare("SELECT entity, id FROM pending_deletes ORDER BY entity, id")
|
||||||
@@ -1829,7 +1823,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn an_attached_file_is_kept_here_and_queued_to_upload() {
|
fn an_attached_file_is_kept_here_and_queued_to_upload() {
|
||||||
let conn = db();
|
let conn = db();
|
||||||
let blobs = blobs("attach");
|
let blobs = crate::sync::blobs::scratch("attach");
|
||||||
let n = note(&conn, "with a receipt");
|
let n = note(&conn, "with a receipt");
|
||||||
conn.execute("UPDATE notes SET dirty = 0", [])
|
conn.execute("UPDATE notes SET dirty = 0", [])
|
||||||
.expect("clean");
|
.expect("clean");
|
||||||
@@ -1873,7 +1867,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn attaching_to_a_note_that_is_gone_writes_nothing() {
|
fn attaching_to_a_note_that_is_gone_writes_nothing() {
|
||||||
let conn = db();
|
let conn = db();
|
||||||
let blobs = blobs("gone");
|
let blobs = crate::sync::blobs::scratch("gone");
|
||||||
assert!(add_attachment(&conn, &blobs, "missing", "a.txt", "text/plain", b"hi").is_err());
|
assert!(add_attachment(&conn, &blobs, "missing", "a.txt", "text/plain", b"hi").is_err());
|
||||||
let rows: i64 = conn
|
let rows: i64 = conn
|
||||||
.query_row("SELECT COUNT(*) FROM attachments", [], |r| r.get(0))
|
.query_row("SELECT COUNT(*) FROM attachments", [], |r| r.get(0))
|
||||||
@@ -1886,7 +1880,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn only_a_file_the_server_holds_leaves_a_tombstone_when_removed() {
|
fn only_a_file_the_server_holds_leaves_a_tombstone_when_removed() {
|
||||||
let conn = db();
|
let conn = db();
|
||||||
let blobs = blobs("remove");
|
let blobs = crate::sync::blobs::scratch("remove");
|
||||||
let n = note(&conn, "two files");
|
let n = note(&conn, "two files");
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO attachments (id, note_id, url) VALUES ('synced', ?1, '/x')",
|
"INSERT INTO attachments (id, note_id, url) VALUES ('synced', ?1, '/x')",
|
||||||
|
|||||||
+19
-14
@@ -215,18 +215,23 @@ pub fn serve(path: &str, query: Option<&str>) -> (u16, String, Vec<u8>) {
|
|||||||
(200, content_type_for(&claimed), bytes)
|
(200, content_type_for(&claimed), bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A blob store in a fresh throwaway directory, for any core test that needs one.
|
||||||
|
/// No tempfile dependency for one fixture: the process id and a counter keep
|
||||||
|
/// concurrent tests and runs apart, and `tag` says whose it was.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn scratch(tag: &str) -> BlobStore {
|
||||||
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
static SEQ: AtomicU32 = AtomicU32::new(0);
|
||||||
|
let n = SEQ.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let dir = std::env::temp_dir().join(format!("ts-blobs-{}-{n}-{tag}", std::process::id()));
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
BlobStore::new(dir).expect("scratch blob store")
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// A blob store in a throwaway directory. No tempfile dependency for one test
|
|
||||||
/// fixture — the process id keeps concurrent runs apart.
|
|
||||||
fn store(tag: &str) -> BlobStore {
|
|
||||||
let dir = std::env::temp_dir().join(format!("ts-blobs-{}-{tag}", std::process::id()));
|
|
||||||
let _ = fs::remove_dir_all(&dir);
|
|
||||||
BlobStore::new(dir).expect("store")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// sha256("hello") — a fixed vector, so a broken digest can't agree with itself.
|
/// sha256("hello") — a fixed vector, so a broken digest can't agree with itself.
|
||||||
const HELLO: &str = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";
|
const HELLO: &str = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";
|
||||||
|
|
||||||
@@ -237,7 +242,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stores_and_reads_back() {
|
fn stores_and_reads_back() {
|
||||||
let store = store("roundtrip");
|
let store = scratch("roundtrip");
|
||||||
assert!(!store.has(HELLO));
|
assert!(!store.has(HELLO));
|
||||||
store.store(HELLO, b"hello").expect("store");
|
store.store(HELLO, b"hello").expect("store");
|
||||||
assert!(store.has(HELLO));
|
assert!(store.has(HELLO));
|
||||||
@@ -248,7 +253,7 @@ mod tests {
|
|||||||
fn refuses_bytes_that_dont_match_the_hash() {
|
fn refuses_bytes_that_dont_match_the_hash() {
|
||||||
// A corrupted or substituted transfer must never be filed under a name that
|
// A corrupted or substituted transfer must never be filed under a name that
|
||||||
// claims it's genuine.
|
// claims it's genuine.
|
||||||
let store = store("mismatch");
|
let store = scratch("mismatch");
|
||||||
let err = store.store(HELLO, b"goodbye").expect_err("must reject");
|
let err = store.store(HELLO, b"goodbye").expect_err("must reject");
|
||||||
assert!(err.contains("integrity"), "got {err}");
|
assert!(err.contains("integrity"), "got {err}");
|
||||||
assert!(!store.has(HELLO), "nothing should have been written");
|
assert!(!store.has(HELLO), "nothing should have been written");
|
||||||
@@ -257,7 +262,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn rejects_a_hash_that_could_escape_the_directory() {
|
fn rejects_a_hash_that_could_escape_the_directory() {
|
||||||
// The hash arrives from a server response and becomes a filename.
|
// The hash arrives from a server response and becomes a filename.
|
||||||
let store = store("traversal");
|
let store = scratch("traversal");
|
||||||
assert!(store.path("../../etc/passwd").is_none());
|
assert!(store.path("../../etc/passwd").is_none());
|
||||||
assert!(store.store("../../etc/passwd", b"x").is_err());
|
assert!(store.store("../../etc/passwd", b"x").is_err());
|
||||||
assert!(store.path("").is_none());
|
assert!(store.path("").is_none());
|
||||||
@@ -267,7 +272,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn accepts_an_uppercase_hash() {
|
fn accepts_an_uppercase_hash() {
|
||||||
// The wire format isn't guaranteed to be lowercase; the filename is.
|
// The wire format isn't guaranteed to be lowercase; the filename is.
|
||||||
let store = store("case");
|
let store = scratch("case");
|
||||||
store
|
store
|
||||||
.store(&HELLO.to_ascii_uppercase(), b"hello")
|
.store(&HELLO.to_ascii_uppercase(), b"hello")
|
||||||
.expect("store");
|
.expect("store");
|
||||||
@@ -329,14 +334,14 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn put_files_bytes_under_their_own_hash() {
|
fn put_files_bytes_under_their_own_hash() {
|
||||||
let store = store("put");
|
let store = scratch("put");
|
||||||
assert_eq!(store.put(b"hello").expect("put"), HELLO);
|
assert_eq!(store.put(b"hello").expect("put"), HELLO);
|
||||||
assert_eq!(store.read(HELLO).as_deref(), Some(&b"hello"[..]));
|
assert_eq!(store.read(HELLO).as_deref(), Some(&b"hello"[..]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_blob_reads_as_none() {
|
fn missing_blob_reads_as_none() {
|
||||||
let store = store("missing");
|
let store = scratch("missing");
|
||||||
assert!(store.read(HELLO).is_none());
|
assert!(store.read(HELLO).is_none());
|
||||||
assert!(!store.has(HELLO));
|
assert!(!store.has(HELLO));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user