2021-06-23 14:48:33 +02:00
|
|
|
pub use search::{default_crop_length, SearchQuery, SearchResult, DEFAULT_SEARCH_LIMIT};
|
2021-09-28 22:22:59 +02:00
|
|
|
pub use updates::{apply_settings_to_builder, Checked, Facets, Settings, Unchecked};
|
2021-06-14 21:26:35 +02:00
|
|
|
|
2021-05-26 22:52:06 +02:00
|
|
|
mod dump;
|
2021-10-06 13:01:02 +02:00
|
|
|
pub mod error;
|
2021-05-31 16:03:39 +02:00
|
|
|
mod search;
|
2021-10-06 13:01:02 +02:00
|
|
|
pub mod update_handler;
|
2021-05-31 16:03:39 +02:00
|
|
|
mod updates;
|
2021-10-06 13:01:02 +02:00
|
|
|
|
|
|
|
#[allow(clippy::module_inception)]
|
2021-10-04 12:15:21 +02:00
|
|
|
mod index;
|
2021-04-01 16:44:42 +02:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
pub use index::{Document, IndexMeta, IndexStats};
|
2021-03-04 14:20:19 +01:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
#[cfg(not(test))]
|
|
|
|
pub use index::Index;
|
2021-09-24 11:53:11 +02:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
pub use test::MockIndex as Index;
|
2021-09-24 11:53:11 +02:00
|
|
|
|
2021-10-06 14:34:14 +02:00
|
|
|
/// The index::test module provides means of mocking an index instance. I can be used throughout the
|
|
|
|
/// code for unit testing, in places where an index would normally be used.
|
2021-10-04 12:15:21 +02:00
|
|
|
#[cfg(test)]
|
2021-10-04 18:27:42 +02:00
|
|
|
pub mod test {
|
2021-10-04 12:15:21 +02:00
|
|
|
use std::any::Any;
|
|
|
|
use std::collections::HashMap;
|
2021-10-04 18:27:42 +02:00
|
|
|
use std::panic::{RefUnwindSafe, UnwindSafe};
|
2021-10-06 14:34:14 +02:00
|
|
|
use std::path::Path;
|
2021-10-04 12:15:21 +02:00
|
|
|
use std::path::PathBuf;
|
2021-10-06 14:51:46 +02:00
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
2021-10-06 14:34:14 +02:00
|
|
|
use std::sync::{Arc, Mutex};
|
2021-09-24 11:53:11 +02:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
use serde_json::{Map, Value};
|
|
|
|
use uuid::Uuid;
|
2021-09-24 11:53:11 +02:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
use crate::index_controller::update_file_store::UpdateFileStore;
|
|
|
|
use crate::index_controller::updates::status::{Failed, Processed, Processing};
|
2021-03-04 11:56:32 +01:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
use super::error::Result;
|
2021-10-06 13:01:02 +02:00
|
|
|
use super::index::Index;
|
2021-10-04 12:15:21 +02:00
|
|
|
use super::update_handler::UpdateHandler;
|
2021-10-06 13:01:02 +02:00
|
|
|
use super::{Checked, IndexMeta, IndexStats, SearchQuery, SearchResult, Settings};
|
2021-10-04 12:15:21 +02:00
|
|
|
|
|
|
|
pub struct Stub<A, R> {
|
|
|
|
name: String,
|
2021-10-06 14:51:46 +02:00
|
|
|
times: Mutex<Option<usize>>,
|
2021-10-04 12:15:21 +02:00
|
|
|
stub: Box<dyn Fn(A) -> R + Sync + Send>,
|
2021-10-06 14:51:46 +02:00
|
|
|
invalidated: AtomicBool,
|
2021-03-04 11:56:32 +01:00
|
|
|
}
|
2021-03-04 12:38:55 +01:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
impl<A, R> Drop for Stub<A, R> {
|
|
|
|
fn drop(&mut self) {
|
2021-10-06 14:51:46 +02:00
|
|
|
if !self.invalidated.load(Ordering::Relaxed) {
|
|
|
|
let lock = self.times.lock().unwrap();
|
|
|
|
if let Some(n) = *lock {
|
2021-10-04 18:27:42 +02:00
|
|
|
assert_eq!(n, 0, "{} not called enough times", self.name);
|
2021-10-04 12:15:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-24 11:53:11 +02:00
|
|
|
}
|
|
|
|
|
2021-10-06 13:01:02 +02:00
|
|
|
impl<A, R> Stub<A, R> {
|
2021-10-06 14:51:46 +02:00
|
|
|
fn invalidate(&self) {
|
|
|
|
self.invalidated.store(true, Ordering::Relaxed);
|
2021-10-06 13:01:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-04 18:27:42 +02:00
|
|
|
impl<A: UnwindSafe, R> Stub<A, R> {
|
2021-10-06 14:51:46 +02:00
|
|
|
fn call(&self, args: A) -> R {
|
|
|
|
let mut lock = self.times.lock().unwrap();
|
|
|
|
match *lock {
|
2021-10-04 12:15:21 +02:00
|
|
|
Some(0) => panic!("{} called to many times", self.name),
|
2021-10-06 13:01:02 +02:00
|
|
|
Some(ref mut times) => {
|
|
|
|
*times -= 1;
|
|
|
|
}
|
2021-10-04 12:15:21 +02:00
|
|
|
None => (),
|
|
|
|
}
|
|
|
|
|
2021-10-06 14:34:14 +02:00
|
|
|
// Since we add assertions in the drop implementation for Stub, a panic can occur in a
|
|
|
|
// panic, causing a hard abort of the program. To handle that, we catch the panic, and
|
|
|
|
// set the stub as invalidated so the assertions aren't run during the drop.
|
2021-10-04 18:27:42 +02:00
|
|
|
impl<'a, A, R> RefUnwindSafe for StubHolder<'a, A, R> {}
|
|
|
|
struct StubHolder<'a, A, R>(&'a (dyn Fn(A) -> R + Sync + Send));
|
|
|
|
|
|
|
|
let stub = StubHolder(self.stub.as_ref());
|
|
|
|
|
|
|
|
match std::panic::catch_unwind(|| (stub.0)(args)) {
|
|
|
|
Ok(r) => r,
|
|
|
|
Err(panic) => {
|
2021-10-06 13:01:02 +02:00
|
|
|
self.invalidate();
|
2021-10-04 18:27:42 +02:00
|
|
|
std::panic::resume_unwind(panic);
|
|
|
|
}
|
|
|
|
}
|
2021-10-04 12:15:21 +02:00
|
|
|
}
|
|
|
|
}
|
2021-09-24 11:53:11 +02:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
#[derive(Debug, Default)]
|
|
|
|
struct StubStore {
|
2021-10-06 13:01:02 +02:00
|
|
|
inner: Arc<Mutex<HashMap<String, Box<dyn Any + Sync + Send>>>>,
|
2021-05-26 22:52:06 +02:00
|
|
|
}
|
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
impl StubStore {
|
|
|
|
pub fn insert<A: 'static, R: 'static>(&self, name: String, stub: Stub<A, R>) {
|
|
|
|
let mut lock = self.inner.lock().unwrap();
|
|
|
|
lock.insert(name, Box::new(stub));
|
|
|
|
}
|
|
|
|
|
2021-10-06 14:51:46 +02:00
|
|
|
pub fn get<A, B>(&self, name: &str) -> Option<&Stub<A, B>> {
|
2021-10-04 12:15:21 +02:00
|
|
|
let mut lock = self.inner.lock().unwrap();
|
|
|
|
match lock.get_mut(name) {
|
|
|
|
Some(s) => {
|
|
|
|
let s = s.as_mut() as *mut dyn Any as *mut Stub<A, B>;
|
|
|
|
Some(unsafe { &mut *s })
|
|
|
|
}
|
|
|
|
None => None,
|
|
|
|
}
|
|
|
|
}
|
2021-05-24 18:16:35 +02:00
|
|
|
}
|
2021-03-04 12:38:55 +01:00
|
|
|
|
2021-10-05 11:47:50 +02:00
|
|
|
pub struct StubBuilder<'a, A, R> {
|
2021-10-04 12:15:21 +02:00
|
|
|
name: String,
|
|
|
|
store: &'a StubStore,
|
|
|
|
times: Option<usize>,
|
2021-10-06 13:01:02 +02:00
|
|
|
_f: std::marker::PhantomData<fn(A) -> R>,
|
2021-03-04 12:38:55 +01:00
|
|
|
}
|
2021-03-04 14:20:19 +01:00
|
|
|
|
2021-10-05 11:47:50 +02:00
|
|
|
impl<'a, A: 'static, R: 'static> StubBuilder<'a, A, R> {
|
|
|
|
/// Asserts the stub has been called exactly `times` times.
|
2021-10-06 13:01:02 +02:00
|
|
|
#[must_use]
|
2021-10-04 12:15:21 +02:00
|
|
|
pub fn times(mut self, times: usize) -> Self {
|
|
|
|
self.times = Some(times);
|
|
|
|
self
|
|
|
|
}
|
2021-03-04 14:20:19 +01:00
|
|
|
|
2021-10-05 11:47:50 +02:00
|
|
|
/// Asserts the stub has been called exactly once.
|
2021-10-06 13:01:02 +02:00
|
|
|
#[must_use]
|
2021-10-05 11:47:50 +02:00
|
|
|
pub fn once(mut self) -> Self {
|
|
|
|
self.times = Some(1);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The function that will be called when the stub is called. This needs to be called to
|
|
|
|
/// actually build the stub and register it to the stub store.
|
|
|
|
pub fn then(self, f: impl Fn(A) -> R + Sync + Send + 'static) {
|
2021-10-06 14:51:46 +02:00
|
|
|
let times = Mutex::new(self.times);
|
2021-10-04 12:15:21 +02:00
|
|
|
let stub = Stub {
|
|
|
|
stub: Box::new(f),
|
2021-10-06 14:51:46 +02:00
|
|
|
times,
|
2021-10-04 12:15:21 +02:00
|
|
|
name: self.name.clone(),
|
2021-10-06 14:51:46 +02:00
|
|
|
invalidated: AtomicBool::new(false),
|
2021-10-04 12:15:21 +02:00
|
|
|
};
|
2021-03-04 14:20:19 +01:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
self.store.insert(self.name, stub);
|
|
|
|
}
|
|
|
|
}
|
2021-03-04 14:20:19 +01:00
|
|
|
|
2021-10-04 18:27:42 +02:00
|
|
|
/// Mocker allows to stub metod call on any struct. you can register stubs by calling
|
|
|
|
/// `Mocker::when` and retrieve it in the proxy implementation when with `Mocker::get`.
|
|
|
|
#[derive(Debug, Default)]
|
|
|
|
pub struct Mocker {
|
|
|
|
store: StubStore,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Mocker {
|
2021-10-05 11:47:50 +02:00
|
|
|
pub fn when<A, R>(&self, name: &str) -> StubBuilder<A, R> {
|
2021-10-04 12:15:21 +02:00
|
|
|
StubBuilder {
|
|
|
|
name: name.to_string(),
|
|
|
|
store: &self.store,
|
|
|
|
times: None,
|
2021-10-05 11:47:50 +02:00
|
|
|
_f: std::marker::PhantomData,
|
2021-10-04 12:15:21 +02:00
|
|
|
}
|
2021-03-04 14:20:19 +01:00
|
|
|
}
|
|
|
|
|
2021-10-06 14:51:46 +02:00
|
|
|
pub fn get<A, R>(&self, name: &str) -> &Stub<A, R> {
|
|
|
|
match self.store.get(name) {
|
2021-10-04 12:15:21 +02:00
|
|
|
Some(stub) => stub,
|
2021-10-06 13:01:02 +02:00
|
|
|
None => {
|
2021-10-06 14:34:14 +02:00
|
|
|
// panic here causes the stubs to get dropped, and panic in turn. To prevent
|
|
|
|
// that, we forget them, and let them be cleaned by the os later. This is not
|
|
|
|
// optimal, but is still better than nested panicks.
|
|
|
|
let mut stubs = self.store.inner.lock().unwrap();
|
2021-10-06 14:51:46 +02:00
|
|
|
let stubs = std::mem::take(&mut *stubs);
|
2021-10-06 14:34:14 +02:00
|
|
|
std::mem::forget(stubs);
|
2021-10-06 13:01:02 +02:00
|
|
|
panic!("unexpected call to {}", name)
|
|
|
|
}
|
2021-10-04 12:15:21 +02:00
|
|
|
}
|
|
|
|
}
|
2021-03-04 14:20:19 +01:00
|
|
|
}
|
2021-03-04 15:09:00 +01:00
|
|
|
|
2021-10-04 18:27:42 +02:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum MockIndex {
|
|
|
|
Vrai(Index),
|
|
|
|
Faux(Arc<Mocker>),
|
|
|
|
}
|
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
impl MockIndex {
|
2021-10-04 18:27:42 +02:00
|
|
|
pub fn faux(faux: Mocker) -> Self {
|
2021-10-04 12:15:21 +02:00
|
|
|
Self::Faux(Arc::new(faux))
|
|
|
|
}
|
2021-03-04 15:09:00 +01:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
pub fn open(
|
|
|
|
path: impl AsRef<Path>,
|
|
|
|
size: usize,
|
|
|
|
update_file_store: Arc<UpdateFileStore>,
|
|
|
|
uuid: Uuid,
|
|
|
|
update_handler: Arc<UpdateHandler>,
|
|
|
|
) -> Result<Self> {
|
|
|
|
let index = Index::open(path, size, update_file_store, uuid, update_handler)?;
|
|
|
|
Ok(Self::Vrai(index))
|
|
|
|
}
|
2021-03-04 15:09:00 +01:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
pub fn load_dump(
|
|
|
|
src: impl AsRef<Path>,
|
|
|
|
dst: impl AsRef<Path>,
|
|
|
|
size: usize,
|
|
|
|
update_handler: &UpdateHandler,
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
Index::load_dump(src, dst, size, update_handler)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-03-15 10:36:12 +01:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
pub fn handle_update(&self, update: Processing) -> std::result::Result<Processed, Failed> {
|
|
|
|
match self {
|
|
|
|
MockIndex::Vrai(index) => index.handle_update(update),
|
2021-10-05 11:47:50 +02:00
|
|
|
MockIndex::Faux(faux) => faux.get("handle_update").call(update),
|
2021-10-04 12:15:21 +02:00
|
|
|
}
|
|
|
|
}
|
2021-03-15 10:17:41 +01:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
pub fn uuid(&self) -> Uuid {
|
|
|
|
match self {
|
|
|
|
MockIndex::Vrai(index) => index.uuid(),
|
2021-10-04 18:27:42 +02:00
|
|
|
MockIndex::Faux(faux) => faux.get("uuid").call(()),
|
2021-10-04 12:15:21 +02:00
|
|
|
}
|
|
|
|
}
|
2021-06-17 14:36:32 +02:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
pub fn stats(&self) -> Result<IndexStats> {
|
|
|
|
match self {
|
|
|
|
MockIndex::Vrai(index) => index.stats(),
|
|
|
|
MockIndex::Faux(_) => todo!(),
|
|
|
|
}
|
|
|
|
}
|
2021-06-17 14:36:32 +02:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
pub fn meta(&self) -> Result<IndexMeta> {
|
|
|
|
match self {
|
|
|
|
MockIndex::Vrai(index) => index.meta(),
|
|
|
|
MockIndex::Faux(_) => todo!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pub fn settings(&self) -> Result<Settings<Checked>> {
|
|
|
|
match self {
|
|
|
|
MockIndex::Vrai(index) => index.settings(),
|
|
|
|
MockIndex::Faux(_) => todo!(),
|
|
|
|
}
|
|
|
|
}
|
2021-03-15 11:01:14 +01:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
pub fn retrieve_documents<S: AsRef<str>>(
|
|
|
|
&self,
|
|
|
|
offset: usize,
|
|
|
|
limit: usize,
|
|
|
|
attributes_to_retrieve: Option<Vec<S>>,
|
|
|
|
) -> Result<Vec<Map<String, Value>>> {
|
|
|
|
match self {
|
2021-10-06 13:01:02 +02:00
|
|
|
MockIndex::Vrai(index) => {
|
|
|
|
index.retrieve_documents(offset, limit, attributes_to_retrieve)
|
|
|
|
}
|
2021-10-04 12:15:21 +02:00
|
|
|
MockIndex::Faux(_) => todo!(),
|
|
|
|
}
|
|
|
|
}
|
2021-04-01 16:44:42 +02:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
pub fn retrieve_document<S: AsRef<str>>(
|
|
|
|
&self,
|
|
|
|
doc_id: String,
|
|
|
|
attributes_to_retrieve: Option<Vec<S>>,
|
|
|
|
) -> Result<Map<String, Value>> {
|
|
|
|
match self {
|
|
|
|
MockIndex::Vrai(index) => index.retrieve_document(doc_id, attributes_to_retrieve),
|
|
|
|
MockIndex::Faux(_) => todo!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn size(&self) -> u64 {
|
|
|
|
match self {
|
|
|
|
MockIndex::Vrai(index) => index.size(),
|
|
|
|
MockIndex::Faux(_) => todo!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn snapshot(&self, path: impl AsRef<Path>) -> Result<()> {
|
|
|
|
match self {
|
|
|
|
MockIndex::Vrai(index) => index.snapshot(path),
|
2021-10-06 13:01:02 +02:00
|
|
|
MockIndex::Faux(faux) => faux.get("snapshot").call(path.as_ref()),
|
2021-10-04 12:15:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn inner(&self) -> &milli::Index {
|
|
|
|
match self {
|
|
|
|
MockIndex::Vrai(index) => index.inner(),
|
|
|
|
MockIndex::Faux(_) => todo!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn update_primary_key(&self, primary_key: Option<String>) -> Result<IndexMeta> {
|
|
|
|
match self {
|
|
|
|
MockIndex::Vrai(index) => index.update_primary_key(primary_key),
|
|
|
|
MockIndex::Faux(_) => todo!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pub fn perform_search(&self, query: SearchQuery) -> Result<SearchResult> {
|
|
|
|
match self {
|
|
|
|
MockIndex::Vrai(index) => index.perform_search(query),
|
2021-10-06 13:01:02 +02:00
|
|
|
MockIndex::Faux(faux) => faux.get("perform_search").call(query),
|
2021-10-04 12:15:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn dump(&self, path: impl AsRef<Path>) -> Result<()> {
|
|
|
|
match self {
|
|
|
|
MockIndex::Vrai(index) => index.dump(path),
|
2021-10-05 13:53:22 +02:00
|
|
|
MockIndex::Faux(faux) => faux.get("dump").call(path.as_ref()),
|
2021-10-04 12:15:21 +02:00
|
|
|
}
|
|
|
|
}
|
2021-03-15 11:01:14 +01:00
|
|
|
}
|
2021-09-27 16:48:03 +02:00
|
|
|
|
2021-10-04 12:15:21 +02:00
|
|
|
#[test]
|
|
|
|
fn test_faux_index() {
|
2021-10-04 18:27:42 +02:00
|
|
|
let faux = Mocker::default();
|
2021-10-06 13:01:02 +02:00
|
|
|
faux.when("snapshot")
|
2021-10-04 18:27:42 +02:00
|
|
|
.times(2)
|
2021-10-06 13:01:02 +02:00
|
|
|
.then(|_: &Path| -> Result<()> { Ok(()) });
|
2021-10-04 12:15:21 +02:00
|
|
|
|
|
|
|
let index = MockIndex::faux(faux);
|
|
|
|
|
|
|
|
let path = PathBuf::from("hello");
|
|
|
|
index.snapshot(&path).unwrap();
|
|
|
|
index.snapshot(&path).unwrap();
|
2021-09-27 16:48:03 +02:00
|
|
|
}
|
2021-10-04 18:27:42 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_panic]
|
|
|
|
fn test_faux_unexisting_method_stub() {
|
|
|
|
let faux = Mocker::default();
|
|
|
|
|
|
|
|
let index = MockIndex::faux(faux);
|
|
|
|
|
|
|
|
let path = PathBuf::from("hello");
|
|
|
|
index.snapshot(&path).unwrap();
|
|
|
|
index.snapshot(&path).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_panic]
|
|
|
|
fn test_faux_panic() {
|
|
|
|
let faux = Mocker::default();
|
2021-10-06 13:01:02 +02:00
|
|
|
faux.when("snapshot")
|
2021-10-04 18:27:42 +02:00
|
|
|
.times(2)
|
|
|
|
.then(|_: &Path| -> Result<()> {
|
|
|
|
panic!();
|
|
|
|
});
|
|
|
|
|
|
|
|
let index = MockIndex::faux(faux);
|
|
|
|
|
|
|
|
let path = PathBuf::from("hello");
|
|
|
|
index.snapshot(&path).unwrap();
|
|
|
|
index.snapshot(&path).unwrap();
|
|
|
|
}
|
2021-03-04 12:38:55 +01:00
|
|
|
}
|