1
use std::borrow::Cow;
2
use std::collections::hash_map::RandomState;
3
use std::collections::HashSet;
4
use std::hash::{BuildHasher, Hasher};
5
use std::path::{Path, PathBuf};
6
use std::sync::Arc;
7
use std::thread;
8

            
9
use crate::global::{
10
    GlobalPool, GlobalString, StaticPooledBuffer, StaticPooledPath, StaticPooledString,
11
};
12
use crate::pool::PoolKindSealed;
13
use crate::shared::{SharedPool, SharedString, StringPool};
14
use crate::Pooled;
15

            
16
static GLOBAL_STRINGS: GlobalPool<String> = GlobalPool::new();
17
static GLOBAL_PATHS: GlobalPool<PathBuf> = GlobalPool::new();
18
static GLOBAL_BUFFERS: GlobalPool<Vec<u8>> = GlobalPool::new();
19

            
20
1
#[test]
21
1
fn basics() {
22
1
    let first_symbol = GLOBAL_STRINGS.get("basics-test-symbol");
23
1
    let slot = first_symbol.0 .0.index;
24
1
    let first_again = GLOBAL_STRINGS.get(String::from("basics-test-symbol"));
25
1
    assert_eq!(slot, first_again.0 .0.index);
26
1
    assert_eq!(first_symbol, first_again);
27
1
    assert_eq!(first_symbol, "basics-test-symbol");
28
1
    assert_eq!(first_symbol.to_string(), "basics-test-symbol");
29
1
    drop(first_again);
30
1
    // Dropping the second copy shouldn't free the underlying symbol
31
1
    (&GLOBAL_STRINGS).with_active_symbols(|symbols| {
32
1
        assert!(symbols.active.contains("basics-test-symbol"));
33
1
        assert!(!symbols.slots.is_empty());
34
1
        assert!(symbols.slots[slot].is_some());
35
1
        assert!(!symbols.free_slots.iter().any(|free| *free == slot));
36
1
    });
37
1
    drop(first_symbol);
38
1
    (&GLOBAL_STRINGS).with_active_symbols(|symbols| {
39
1
        assert!(!symbols.active.contains("basics-test-symbol"));
40
1
        match &symbols.slots[slot] {
41
            Some(new_symbol) => {
42
                // This test isn't run in isolation, so other symbols may get
43
                // registered between the drop and this block. Very unlikely,
44
                // but possible.
45
                assert_ne!(new_symbol, "basics-test-symbol");
46
            }
47
            None => {
48
1
                assert!(symbols.free_slots.iter().any(|free| *free == slot));
49
            }
50
        }
51
1
    });
52
1
}
53

            
54
1
#[test]
55
1
fn shared_is_separate() {
56
1
    // First get a global string to ensure that we get a non-zero index for the
57
1
    // string that will have the same contents as the local pool.
58
1
    let first_symbol = GLOBAL_STRINGS.get("shared-is-separate-ignored");
59
1
    let from_global = GLOBAL_STRINGS.get("shared_is_separate");
60
1
    // Create our local pool and request the same string.
61
1
    let shared = SharedPool::<String>::default();
62
1
    let from_shared = shared.get(String::from("shared_is_separate"));
63
1
    // Verify that the strings are indeed different.
64
1
    assert!(!Pooled::ptr_eq(&from_shared, &from_global));
65
1
    let from_shared_borrowed = shared.get("shared_is_separate");
66
1
    assert!(Pooled::ptr_eq(&from_shared, &from_shared_borrowed));
67

            
68
    // Test both directions of partialeq
69
1
    assert_eq!(from_shared, from_global);
70
1
    assert_eq!(from_global, from_shared);
71

            
72
    // And test not equal against the first symbol, despite the indexes
73
    // potentialy being equal (but not guaranteed since other tests can be
74
    // running simultaneously).
75
1
    assert_ne!(first_symbol, from_shared);
76
1
    assert_ne!(from_shared, first_symbol);
77
1
}
78

            
79
1
#[test]
80
1
fn paths() {
81
1
    let first_symbol = GLOBAL_PATHS.get(PathBuf::from("ignored-global-path"));
82
1
    assert_eq!(first_symbol, Path::new("ignored-global-path"));
83
1
    let from_global = GLOBAL_PATHS.get(Path::new("shared_is_separate_path"));
84
1
    let shared = SharedPool::<PathBuf>::default();
85
1
    let from_shared = shared.get(PathBuf::from("shared_is_separate_path"));
86
1
    assert_ne!(from_shared.0 .0.index, from_global.0 .0.index);
87
1
    let from_shared_borrowed = shared.get(Path::new("shared_is_separate_path"));
88
1
    assert_eq!(from_shared.0 .0.index, from_shared_borrowed.0 .0.index);
89

            
90
    // Test both directions of partialeq
91
1
    assert_eq!(from_shared, from_global);
92
1
    assert_eq!(from_global, from_shared);
93

            
94
1
    assert_ne!(first_symbol, from_shared);
95
1
    assert_ne!(from_shared, first_symbol);
96
1
}
97

            
98
1
#[test]
99
1
fn buffers() {
100
1
    let first_symbol = GLOBAL_BUFFERS.get(b"ignored-global-buffer".to_vec());
101
1
    assert_eq!(first_symbol, &b"ignored-global-buffer"[..]);
102
1
    let from_global = GLOBAL_BUFFERS.get(&b"shared_is_separate_buffer"[..]);
103
1
    let shared = SharedPool::<Vec<u8>>::default();
104
1
    let from_shared = shared.get(b"shared_is_separate_buffer".to_vec());
105
1
    assert_ne!(from_shared.0 .0.index, from_global.0 .0.index);
106
1
    let from_shared_borrowed = shared.get(&b"shared_is_separate_buffer"[..]);
107
1
    assert_eq!(from_shared.0 .0.index, from_shared_borrowed.0 .0.index);
108

            
109
    // Test both directions of partialeq
110
1
    assert_eq!(from_shared, from_global);
111
1
    assert_eq!(from_global, from_shared);
112

            
113
1
    assert_ne!(first_symbol, from_shared);
114
1
    assert_ne!(from_shared, first_symbol);
115
1
}
116

            
117
1
#[test]
118
1
fn hashing() {
119
1
    let mut set = HashSet::new();
120
1
    let shared = StringPool::default();
121
1
    set.insert(shared.get("hello"));
122
1
    assert!(set.contains(&shared.get("hello")));
123
1
    assert!(!set.contains(&shared.get("world")));
124
1
}
125

            
126
1
#[test]
127
1
fn with_hasher() {
128
1
    let mut set = HashSet::new();
129
1
    let shared = StringPool::with_hasher(RandomState::default());
130
1
    set.insert(shared.get("hello"));
131
1
    assert!(set.contains(&shared.get("hello")));
132
1
    assert!(!set.contains(&shared.get("world")));
133
1
}
134

            
135
1
#[test]
136
1
fn multithreaded_reaquire() {
137
1
    // We have an edge case code path that's hard to test. In SharedData::drop,
138
1
    // there is a path that gets hit only when other threads are getting a new
139
1
    // reference to the string being dropped.
140
1
    let mut threads = Vec::new();
141
5
    for _ in 0..4 {
142
4
        threads.push(thread::spawn(|| {
143
4004
            for _ in 0..1000 {
144
4000
                let _: GlobalString = GLOBAL_STRINGS.get("multithreaded");
145
4000
            }
146
4
        }));
147
4
    }
148
5
    for t in threads {
149
4
        t.join().unwrap();
150
4
    }
151
    // The failure case for the code would end up not freing the string.
152
1
    (&GLOBAL_STRINGS).with_active_symbols(|symbols| {
153
1
        assert!(!symbols.active.contains("multithreaded"));
154
1
    });
155
1
}
156

            
157
1
#[test]
158
1
fn ptr_eq() {
159
1
    let pool_a = StringPool::default();
160
1
    let from_a = pool_a.get("hello");
161
1
    let pool_b = StringPool::default();
162
1
    let from_b = pool_b.get("hello");
163
1
    assert_eq!(from_a, from_b);
164
1
    assert!(!SharedString::ptr_eq(&from_a, &from_b));
165

            
166
1
    assert!(SharedString::ptr_eq(&from_a, &pool_a.get("hello")));
167
1
}
168

            
169
1
#[test]
170
1
fn custom_global_pool() {
171
2
    #[derive(Default, Clone)]
172
1
    struct BadHasher(u8);
173
1
    impl Hasher for BadHasher {
174
2
        fn finish(&self) -> u64 {
175
2
            u64::from(self.0)
176
2
        }
177
1

            
178
4
        fn write(&mut self, bytes: &[u8]) {
179
16
            for byte in bytes {
180
12
                self.0 ^= *byte;
181
12
            }
182
4
        }
183
1
    }
184
1

            
185
1
    impl BuildHasher for BadHasher {
186
1
        type Hasher = BadHasher;
187
1

            
188
2
        fn build_hasher(&self) -> Self::Hasher {
189
2
            self.clone()
190
2
        }
191
1
    }
192
1

            
193
1
    static CUSTOM_POOL: GlobalPool<String, BadHasher> = GlobalPool::with_hasher(BadHasher(0));
194
1

            
195
1
    let from_custom = CUSTOM_POOL.get("hello");
196
1
    let global = GLOBAL_STRINGS.get("hello");
197
1
    assert!(!Pooled::ptr_eq(&from_custom, &global));
198
1
}
199

            
200
1
#[test]
201
1
fn pooled_debug() {
202
1
    let shared_pool = StringPool::default();
203
1
    let string = shared_pool.get("test");
204
1
    let expected = "\"test\"";
205
1

            
206
1
    let debugged = format!("{string:?}");
207
1
    println!("{debugged}");
208
1
    assert_eq!(debugged, expected);
209

            
210
1
    let second = shared_pool.get("test");
211
1
    let second_debugged = format!("{second:?}");
212
1
    assert_eq!(second_debugged, expected);
213
1
}
214

            
215
1
#[test]
216
1
fn statics() {
217
1
    static STATIC_STR: StaticPooledString = GLOBAL_STRINGS.get_static("static");
218
1
    static STATIC_STR_LAZY: StaticPooledString =
219
1
        GLOBAL_STRINGS.get_static_with(|| Cow::Borrowed("static-lazy"));
220
1

            
221
1
    static STATIC_PATH_LAZY: StaticPooledPath =
222
1
        GLOBAL_PATHS.get_static_with(|| Cow::Borrowed(Path::new("static-lazy")));
223
1

            
224
1
    static STATIC_BUFFER: StaticPooledBuffer = GLOBAL_BUFFERS.get_static(b"static");
225
1
    static STATIC_BUFFER_LAZY: StaticPooledBuffer =
226
1
        GLOBAL_BUFFERS.get_static_with(|| Cow::Borrowed(b"static-lazy"));
227
1

            
228
1
    macro_rules! test_static {
229
1
        ($static:ident, $against:expr) => {{
230
1
            let first = $static.get();
231
1
            let first_ptr = Arc::as_ptr(&first.0 .0);
232
1

            
233
1
            let second = $static.get();
234
1
            let second_ptr = Arc::as_ptr(&second.0 .0);
235
1
            assert_eq!(first_ptr, second_ptr);
236
1
            assert_eq!(second, $against);
237
1

            
238
1
            assert_eq!($static, *second);
239
1
            assert_eq!(second, &*$static);
240
1
        }};
241
1
    }
242
1

            
243
1
    test_static!(STATIC_STR, "static");
244
1
    test_static!(STATIC_STR_LAZY, "static-lazy");
245

            
246
1
    test_static!(STATIC_PATH_LAZY, Path::new("static-lazy"));
247

            
248
1
    test_static!(STATIC_BUFFER, &b"static"[..]);
249
1
    test_static!(STATIC_BUFFER_LAZY, &b"static-lazy"[..]);
250
1
}