atagen

macltok

public
Star0
Watch0
beea75a1f14613acd309e69836163d5a23851aea
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/search/index_tests.rsRust150 lines5.0 KB
1
use std::collections::BTreeMap;
2
3
use super::*;
4
5
fn doc(key: &str, room: &str, ts: u64, text: &str) -> Doc {
6
    Doc {
7
        key: key.to_owned(),
8
        room: room.to_owned(),
9
        sender: "@a:x".to_owned(),
10
        ts,
11
        content: key.to_owned(),
12
        content_ts: ts,
13
        text: text.to_owned(),
14
        provisional: false,
15
    }
16
}
17
18
fn keys(hits: &[Hit]) -> Vec<&str> {
19
    hits.iter().map(|hit| hit.key.as_str()).collect()
20
}
21
22
/// the order `Service::commit` uses, the manifest saved with the blobs before `committed`
23
fn commit(index: &mut KeywordIndex, store: &mut BTreeMap<String, Vec<u8>>) {
24
    let changes = index.changes().unwrap();
25
    for key in &changes.deletes {
26
        store.remove(key);
27
    }
28
    for (key, value) in &changes.puts {
29
        store.insert(key.clone(), value.clone());
30
    }
31
    store.insert("meta".to_owned(), changes.manifest.to_string().into_bytes());
32
    index.committed(&changes);
33
}
34
35
fn reload(store: &BTreeMap<String, Vec<u8>>) -> KeywordIndex {
36
    let manifest: serde_json::Value = serde_json::from_slice(&store["meta"]).unwrap();
37
    KeywordIndex::load(&manifest, |key| store.get(key).cloned()).unwrap()
38
}
39
40
fn found(index: &mut KeywordIndex, query: &str) -> Vec<String> {
41
    let (hits, _) = index.search(query, None, None, 100).unwrap();
42
    let mut found: Vec<_> = keys(&hits).into_iter().map(str::to_owned).collect();
43
    found.sort();
44
    found
45
}
46
47
#[test]
48
fn pages_run_newest_first_without_repeats_across_segments() {
49
    let mut index = KeywordIndex::new(false);
50
    for ts in 0..30 {
51
        index.put(doc(&format!("$e{ts}"), "!r:x", ts, "hello there"));
52
        if ts % 7 == 0 {
53
            index.maintain().unwrap();
54
        }
55
    }
56
    let mut seen = Vec::new();
57
    let mut before = None;
58
    loop {
59
        let (hits, _) = index.search("hello", None, before, 8).unwrap();
60
        let Some(last) = hits.last() else { break };
61
        before = Some((last.ts, last.tie));
62
        seen.extend(hits.iter().map(|hit| hit.ts));
63
    }
64
    assert_eq!(seen, (0..30).rev().collect::<Vec<_>>());
65
}
66
67
#[test]
68
fn a_replaced_doc_is_found_once_by_its_new_text() {
69
    let mut index = KeywordIndex::new(false);
70
    index.put(doc("$a", "!r:x", 1, "before"));
71
    index.maintain().unwrap();
72
    index.put(doc("$a", "!r:x", 1, "after"));
73
    assert!(found(&mut index, "before").is_empty());
74
    assert_eq!(found(&mut index, "after"), vec!["$a"]);
75
}
76
77
#[test]
78
fn rooms_filter_the_results() {
79
    let mut index = KeywordIndex::new(false);
80
    index.put(doc("$a", "!one:x", 1, "word"));
81
    index.put(doc("$b", "!two:x", 2, "word"));
82
    let rooms = vec!["!one:x".to_owned()];
83
    assert_eq!(
84
        keys(&index.search("word", Some(&rooms), None, 10).unwrap().0),
85
        vec!["$a"]
86
    );
87
    assert!(
88
        index
89
            .search("word", Some(&[]), None, 10)
90
            .unwrap()
91
            .0
92
            .is_empty()
93
    );
94
}
95
96
#[test]
97
fn provisional_docs_stay_out_of_results() {
98
    let mut index = KeywordIndex::new(false);
99
    let mut pending = doc(&provisional_key("$o", "@a:x"), "!r:x", 5, "early edit");
100
    pending.provisional = true;
101
    index.put(pending);
102
    assert!(found(&mut index, "early").is_empty());
103
    assert!(index.remove_provisional("$o"));
104
    assert!(index.get(&provisional_key("$o", "@a:x")).is_none());
105
}
106
107
/// the manifest used to be read before `committed`, one commit stale, so a restart lost
108
/// the last flush and merge, and a bumped delete generation failed the whole load
109
#[test]
110
fn every_commit_reloads_to_what_was_committed() {
111
    let mut store = BTreeMap::new();
112
    let mut index = KeywordIndex::new(true);
113
    let count = MERGE_FACTOR as u64 + 2;
114
    for ts in 0..count {
115
        index.put(doc(&format!("$e{ts}"), "!r:x", ts, "kept"));
116
        commit(&mut index, &mut store);
117
        assert_eq!(found(&mut reload(&store), "kept").len(), ts as usize + 1);
118
    }
119
    index.remove("$e0");
120
    commit(&mut index, &mut store);
121
    index.remove("$e1");
122
    commit(&mut index, &mut store);
123
    index.put(doc("$e2", "!r:x", 2, "edited"));
124
    commit(&mut index, &mut store);
125
    let mut reloaded = reload(&store);
126
    let expected: Vec<String> = (3..count).map(|ts| format!("$e{ts}")).collect();
127
    assert_eq!(found(&mut reloaded, "kept"), expected);
128
    assert_eq!(found(&mut reloaded, "edited"), vec!["$e2"]);
129
    let mut referenced = reloaded.referenced();
130
    referenced.push("meta".to_owned());
131
    referenced.sort();
132
    let stored: Vec<_> = store.keys().cloned().collect();
133
    assert_eq!(stored, referenced, "a commit left blobs nothing points at");
134
}
135
136
#[test]
137
fn unspaced_text_matches_inside_a_run() {
138
    let mut index = KeywordIndex::new(false);
139
    index.put(doc("$a", "!r:x", 1, "明日東京都庁に行きます"));
140
    assert_eq!(found(&mut index, "東京"), vec!["$a"]);
141
    assert_eq!(found(&mut index, "\"都庁\""), vec!["$a"]);
142
    assert!(found(&mut index, "大阪").is_empty());
143
}
144
145
#[test]
146
fn packed_segments_reject_truncation() {
147
    let packed = pack(&[("a".to_owned(), OwnedBytes::new(vec![1, 2, 3]))]);
148
    assert!(unpack(&packed).is_some());
149
    assert!(unpack(&packed[..packed.len() - 1]).is_none());
150
}