use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::thread;
use intern_lang::{ConcurrentInterner, Lookup};
#[test]
fn test_overlapping_interning_yields_one_symbol_per_string() {
const THREADS: usize = 16;
const DISTINCT: usize = 500;
let interner = Arc::new(ConcurrentInterner::new());
let words: Arc<Vec<String>> = Arc::new(
(0..2_000)
.map(|i| format!("name_{}", i % DISTINCT))
.collect(),
);
let handles: Vec<_> = (0..THREADS)
.map(|_| {
let interner = Arc::clone(&interner);
let words = Arc::clone(&words);
thread::spawn(move || {
let mut local: HashMap<String, _> = HashMap::new();
for w in words.iter() {
let _ = local.insert(w.clone(), interner.intern(w));
}
local
})
})
.collect();
let maps: Vec<HashMap<String, _>> = handles.into_iter().map(|h| h.join().unwrap()).collect();
let reference = &maps[0];
for map in &maps[1..] {
for (word, symbol) in map {
assert_eq!(reference.get(word), Some(symbol));
}
}
let symbols: HashSet<_> = reference.values().copied().collect();
assert_eq!(symbols.len(), DISTINCT);
assert_eq!(interner.len(), DISTINCT);
}
#[test]
fn test_concurrent_interning_resolves_correctly() {
const THREADS: usize = 8;
const PER_THREAD: usize = 1_000;
let interner = Arc::new(ConcurrentInterner::new());
let handles: Vec<_> = (0..THREADS)
.map(|t| {
let interner = Arc::clone(&interner);
thread::spawn(move || {
let mut pairs = Vec::with_capacity(PER_THREAD);
for i in 0..PER_THREAD {
let s = format!("t{t}_s{i}");
pairs.push((interner.intern(&s), s));
}
pairs
})
})
.collect();
let all: Vec<(_, String)> = handles
.into_iter()
.flat_map(|h| h.join().unwrap())
.collect();
assert_eq!(interner.len(), THREADS * PER_THREAD);
for (symbol, expected) in &all {
assert_eq!(
interner.resolve(*symbol).as_deref(),
Some(expected.as_str())
);
}
}
#[test]
fn test_get_observes_other_threads_interns() {
let interner = Arc::new(ConcurrentInterner::new());
let writer = {
let interner = Arc::clone(&interner);
thread::spawn(move || interner.intern("written_elsewhere"))
};
let symbol = writer.join().unwrap();
assert_eq!(interner.get("written_elsewhere"), Some(symbol));
assert_eq!(
interner.resolve(symbol).as_deref(),
Some("written_elsewhere")
);
}
#[test]
fn test_usable_through_lookup_trait() {
fn first_byte(interner: &impl Lookup, s: &str) -> Option<u8> {
let sym = interner.get(s)?;
interner.resolve_with(sym, |resolved| resolved.as_bytes()[0])
}
let interner = ConcurrentInterner::new();
let _ = interner.intern("zebra");
assert_eq!(first_byte(&interner, "zebra"), Some(b'z'));
assert_eq!(first_byte(&interner, "missing"), None);
}
#[test]
fn test_send_sync_bounds() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<ConcurrentInterner>();
}