Add tests
This commit is contained in:
parent
8b3f5a8478
commit
10eba4fdbf
2 changed files with 263 additions and 34 deletions
|
@ -109,5 +109,15 @@ error_chain! {
|
||||||
description("provided value doesn't match value type")
|
description("provided value doesn't match value type")
|
||||||
display("provided value of type {} doesn't match attribute value type {}", provided, expected)
|
display("provided value of type {} doesn't match attribute value type {}", provided, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StoreNotFound(path: String) {
|
||||||
|
description("the Store provided does not exist or is not yet open.")
|
||||||
|
display("the Store at {:?} does not exist or is not yet open.", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
StoreConnectionStillActive(path: String) {
|
||||||
|
description("the Store provided has active connections and cannot be closed.")
|
||||||
|
display("the Store at {:?} has active connections and cannot be closed.", path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
287
src/stores.rs
287
src/stores.rs
|
@ -16,7 +16,6 @@ use std::collections::{
|
||||||
|
|
||||||
use std::path::{
|
use std::path::{
|
||||||
Path,
|
Path,
|
||||||
PathBuf,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
|
@ -38,6 +37,7 @@ use mentat_db::{
|
||||||
TxObserver,
|
TxObserver,
|
||||||
TxReport,
|
TxReport,
|
||||||
};
|
};
|
||||||
|
use mentat_db::db;
|
||||||
|
|
||||||
use mentat_tolstoy::Syncer;
|
use mentat_tolstoy::Syncer;
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ use query::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Stores {
|
pub struct Stores {
|
||||||
stores: BTreeMap<PathBuf, Store>,
|
stores: BTreeMap<String, Store>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stores {
|
impl Stores {
|
||||||
|
@ -76,45 +76,39 @@ impl Stores {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stores {
|
impl Stores {
|
||||||
fn is_open(&self, path: PathBuf) -> bool {
|
fn is_open(&self, path: &str) -> bool {
|
||||||
self.stores.contains_key(&path)
|
self.stores.contains_key(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open<'p, P>(&mut self, path: P) -> Result<&mut Store> where P: Into<&'p Path> {
|
pub fn open(&mut self, path: &str) -> Result<&mut Store> {
|
||||||
let path = path.into();
|
let p = path.to_string();
|
||||||
let canonical = path.canonicalize()?;
|
Ok(self.stores.entry(p).or_insert(Store::open(path)?))
|
||||||
Ok(self.stores.entry(canonical).or_insert(Store::open(path.to_str().unwrap())?))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get<'p, P>(&mut self, path: P) -> Result<Option<&Store>> where P: Into<&'p Path> {
|
pub fn get(&self, path: &str) -> Result<Option<&Store>> {
|
||||||
let canonical = path.into().canonicalize()?;
|
Ok(self.stores.get(path))
|
||||||
Ok(self.stores.get(&canonical))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut<'p, P>(&mut self, path: P) -> Result<Option<&mut Store>> where P: Into<&'p Path> {
|
pub fn get_mut(&mut self, path: &str) -> Result<Option<&mut Store>> {
|
||||||
let canonical = path.into().canonicalize()?;
|
Ok(self.stores.get_mut(path))
|
||||||
Ok(self.stores.get_mut(&canonical))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn connect<'p, P>(&mut self, path: P) -> Result<Store> where P: Into<&'p Path> {
|
pub fn connect(&mut self, path: &str) -> Result<Store> {
|
||||||
let path = path.into();
|
let store = self.stores.get_mut(path).ok_or(ErrorKind::StoreNotFound(path.to_string()))?;
|
||||||
let canonical = path.canonicalize()?;
|
let connection = ::new_connection(path)?;
|
||||||
let store = self.stores.get_mut(&canonical).unwrap();
|
store.fork(connection)
|
||||||
let connection = ::new_connection(path.to_str().unwrap())?;
|
|
||||||
Ok(store.fork(connection))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_connections_for_store(&self, path: &PathBuf) -> usize {
|
fn open_connections_for_store(&self, path: &str) -> Result<usize> {
|
||||||
Arc::strong_count(self.stores.get(path).unwrap().conn())
|
Ok(Arc::strong_count(self.stores.get(path).ok_or(ErrorKind::StoreNotFound(path.to_string()))?.conn()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close<'p, P>(&mut self, path: P) -> Result<()> where P: Into<&'p Path> {
|
pub fn close(&mut self, path: &str) -> Result<()> {
|
||||||
let canonical = path.into().canonicalize()?;
|
if self.open_connections_for_store(path)? <= 1 {
|
||||||
if self.open_connections_for_store(&canonical) <= 1 {
|
self.stores.remove(path).ok_or(ErrorKind::StoreNotFound(path.to_string()))?;
|
||||||
self.stores.remove(&canonical);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
bail!(ErrorKind::StoreConnectionStillActive(path.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,10 +131,6 @@ impl Store {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_in_memory() -> Result<Store> {
|
|
||||||
Store::open("")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a totally blank store with no bootstrap schema. Use `open` instead.
|
/// Returns a totally blank store with no bootstrap schema. Use `open` instead.
|
||||||
pub fn open_empty(path: &str) -> Result<Store> {
|
pub fn open_empty(path: &str) -> Result<Store> {
|
||||||
if !path.is_empty() {
|
if !path.is_empty() {
|
||||||
|
@ -178,11 +168,11 @@ impl Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Store {
|
impl Store {
|
||||||
pub fn fork(&mut self, sqlite: rusqlite::Connection) -> Store {
|
pub fn fork(&mut self, sqlite: rusqlite::Connection) -> Result<Store> {
|
||||||
Store {
|
Ok(Store {
|
||||||
conn: self.conn.clone(),
|
conn: self.conn.clone(),
|
||||||
sqlite: sqlite,
|
sqlite: sqlite,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dismantle(self) -> (rusqlite::Connection, Arc<Conn>) {
|
pub fn dismantle(self) -> (rusqlite::Connection, Arc<Conn>) {
|
||||||
|
@ -293,6 +283,7 @@ mod tests {
|
||||||
|
|
||||||
use mentat_core::{
|
use mentat_core::{
|
||||||
CachedAttributes,
|
CachedAttributes,
|
||||||
|
HasSchema,
|
||||||
TypedValue,
|
TypedValue,
|
||||||
ValueType,
|
ValueType,
|
||||||
};
|
};
|
||||||
|
@ -720,4 +711,232 @@ mod tests {
|
||||||
assert_eq!(o.txids, tx_ids);
|
assert_eq!(o.txids, tx_ids);
|
||||||
assert_eq!(o.changes, changesets);
|
assert_eq!(o.changes, changesets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stores_open_new_store() {
|
||||||
|
let mut manager = Stores::new();
|
||||||
|
let store = manager.open("test.db").expect("Expected a store to be opened");
|
||||||
|
assert_eq!(1, Arc::strong_count(store.conn()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stores_open_new_in_memory_store() {
|
||||||
|
let mut manager = Stores::new();
|
||||||
|
let path = "";
|
||||||
|
let store = manager.open(path).expect("Expected a store to be opened");
|
||||||
|
assert_eq!(1, Arc::strong_count(store.conn()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stores_open_existing_store() {
|
||||||
|
let mut manager = Stores::new();
|
||||||
|
{
|
||||||
|
let store1 = manager.open("").expect("Expected a store to be opened");
|
||||||
|
assert_eq!(1, Arc::strong_count(store1.conn()));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let store2 = manager.open("").expect("Expected a store to be opened");
|
||||||
|
assert_eq!(1, Arc::strong_count(store2.conn()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stores_get_open_store() {
|
||||||
|
let mut manager = Stores::new();
|
||||||
|
let path = "";
|
||||||
|
{
|
||||||
|
let store = manager.open(path).expect("Expected a store to be opened");
|
||||||
|
assert_eq!(1, Arc::strong_count(store.conn()));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let store_ref = manager.get(path).expect("Expected a store to be fetched").unwrap();
|
||||||
|
assert_eq!(1, Arc::strong_count(store_ref.conn()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stores_get_closed_store() {
|
||||||
|
let manager = Stores::new();
|
||||||
|
match manager.get("").expect("Expected a store to be fetched") {
|
||||||
|
None => (),
|
||||||
|
Some(_) => panic!("Store is not open and so none should be returned"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stores_get_mut_open_store() {
|
||||||
|
let mut manager = Stores::new();
|
||||||
|
let path = "";
|
||||||
|
{
|
||||||
|
let store = manager.open(path).expect("Expected a store to be opened");
|
||||||
|
assert_eq!(1, Arc::strong_count(store.conn()));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let store_ref = manager.get_mut(path).expect("Expected a store to be fetched").unwrap();
|
||||||
|
assert_eq!(1, Arc::strong_count(store_ref.conn()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stores_get_mut_closed_store() {
|
||||||
|
let mut manager = Stores::new();
|
||||||
|
match manager.get_mut("").expect("Expected a store to be fetched") {
|
||||||
|
None => (),
|
||||||
|
Some(_) => panic!("Store is not open and so none should be returned"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stores_connect_open_store() {
|
||||||
|
let mut manager = Stores::new();
|
||||||
|
let path = "";
|
||||||
|
{
|
||||||
|
let store1 = manager.open(path).expect("Expected a store to be opened");
|
||||||
|
assert_eq!(1, Arc::strong_count(store1.conn()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// forking an open store leads to a ref count of 2 on the shared conn.
|
||||||
|
let store2 = manager.connect(path).expect("expected a new store");
|
||||||
|
assert_eq!(2, Arc::strong_count(store2.conn()));
|
||||||
|
|
||||||
|
{
|
||||||
|
// fetching a reference to the original store also has a ref count of 2 on the shared conn
|
||||||
|
let store3 = manager.get(path).expect("Expected a store to be fetched").unwrap();
|
||||||
|
assert_eq!(2, Arc::strong_count(store3.conn()));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// forking again, in it's own scope increases the refcount.
|
||||||
|
let store4 = manager.connect(path).expect("expected a new store");
|
||||||
|
assert_eq!(3, Arc::strong_count(store2.conn()));
|
||||||
|
assert_eq!(3, Arc::strong_count(store4.conn()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// but now that scope is over, the original refcount is restored.
|
||||||
|
// let store5 = manager.get(path).expect("Expected a store to be fetched").unwrap();
|
||||||
|
assert_eq!(2, Arc::strong_count(store2.conn()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stores_connect_closed_store() {
|
||||||
|
let mut manager = Stores::new();
|
||||||
|
let path = "";
|
||||||
|
let err = manager.connect(path).err();
|
||||||
|
match err.unwrap() {
|
||||||
|
Error(ErrorKind::StoreNotFound(message), _) => { assert_eq!(path, message); },
|
||||||
|
x => panic!("expected Store Not Found error, got {:?}", x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stores_close_store_with_one_reference() {
|
||||||
|
let mut manager = Stores::new();
|
||||||
|
let path = "";
|
||||||
|
{
|
||||||
|
let store = manager.open(path).expect("Expected a store to be opened");
|
||||||
|
assert_eq!(1, Arc::strong_count(store.conn()));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(manager.close(path).is_ok());
|
||||||
|
|
||||||
|
assert!(manager.get(path).expect("expected an empty result").is_none())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stores_close_store_with_multiple_references() {
|
||||||
|
let mut manager = Stores::new();
|
||||||
|
let path = "";
|
||||||
|
{
|
||||||
|
let store1 = manager.open(path).expect("Expected a store to be opened");
|
||||||
|
assert_eq!(1, Arc::strong_count(store1.conn()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// forking an open store leads to a ref count of 2 on the shared conn.
|
||||||
|
let store2 = manager.connect(path).expect("expected a new store");
|
||||||
|
assert_eq!(2, Arc::strong_count(store2.conn()));
|
||||||
|
|
||||||
|
let err = manager.close(path).err();
|
||||||
|
match err.unwrap() {
|
||||||
|
Error(ErrorKind::StoreConnectionStillActive(message), _) => { assert_eq!(path, message); },
|
||||||
|
x => panic!("expected StoreConnectionStillActive error, got {:?}", x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stores_close_store_with_scoped_multiple_references() {
|
||||||
|
let mut manager = Stores::new();
|
||||||
|
let path = "";
|
||||||
|
{
|
||||||
|
let store1 = manager.open(path).expect("Expected a store to be opened");
|
||||||
|
assert_eq!(1, Arc::strong_count(store1.conn()));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// forking an open store leads to a ref count of 2 on the shared conn.
|
||||||
|
let store2 = manager.connect(path).expect("expected a new store");
|
||||||
|
assert_eq!(2, Arc::strong_count(store2.conn()));
|
||||||
|
|
||||||
|
let err = manager.close(path).err();
|
||||||
|
match err.unwrap() {
|
||||||
|
Error(ErrorKind::StoreConnectionStillActive(message), _) => { assert_eq!(path, message); },
|
||||||
|
x => panic!("expected StoreConnectionStillActive error, got {:?}", x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// outside of the scope, there should only be one strong reference so we can close the connection
|
||||||
|
assert!(manager.close(path).is_ok());
|
||||||
|
assert!(manager.get(path).expect("expected an empty result").is_none())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stores_close_unopened_store() {
|
||||||
|
let mut manager = Stores::new();
|
||||||
|
let path = "";
|
||||||
|
|
||||||
|
let err = manager.close(path).err();
|
||||||
|
match err.unwrap() {
|
||||||
|
Error(ErrorKind::StoreNotFound(message), _) => { assert_eq!(path, message); },
|
||||||
|
x => panic!("expected StoreNotFound error, got {:?}", x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stores_connect_perform_mutable_operations() {
|
||||||
|
let mut manager = Stores::new();
|
||||||
|
let path = "test.db";
|
||||||
|
|
||||||
|
{
|
||||||
|
let store1 = manager.open(path).expect("Expected a store to be opened");
|
||||||
|
assert_eq!(1, Arc::strong_count(store1.conn()));
|
||||||
|
let mut in_progress = store1.begin_transaction().expect("begun");
|
||||||
|
in_progress.transact(r#"[
|
||||||
|
{ :db/ident :foo/bar
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/index true
|
||||||
|
:db/unique :db.unique/identity
|
||||||
|
:db/valueType :db.type/long },
|
||||||
|
{ :db/ident :foo/baz
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/valueType :db.type/boolean }
|
||||||
|
{ :db/ident :foo/x
|
||||||
|
:db/cardinality :db.cardinality/many
|
||||||
|
:db/valueType :db.type/long }]"#).expect("transact");
|
||||||
|
|
||||||
|
in_progress.commit().expect("commit");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// forking an open store leads to a ref count of 2 on the shared conn.
|
||||||
|
// we should be able to perform write operations on this connection
|
||||||
|
let mut store2 = manager.connect(path).expect("expected a new store");
|
||||||
|
assert_eq!(2, Arc::strong_count(store2.conn()));
|
||||||
|
let mut in_progress = store2.begin_transaction().expect("begun");
|
||||||
|
in_progress.transact(r#"[
|
||||||
|
{:foo/bar 15, :foo/baz false, :foo/x [1, 2, 3]}
|
||||||
|
{:foo/bar 99, :foo/baz true}
|
||||||
|
{:foo/bar -2, :foo/baz true}
|
||||||
|
]"#).expect("transact");
|
||||||
|
in_progress.commit().expect("commit");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue