diff --git a/src/encryptable.rs b/src/encryptable.rs index e5a0f17..12ea743 100644 --- a/src/encryptable.rs +++ b/src/encryptable.rs @@ -66,7 +66,7 @@ impl CblRef for Encryptable { impl From<*mut CBLEncryptable> for Encryptable { fn from(cbl_ref: *mut CBLEncryptable) -> Self { - Self::reference(cbl_ref) + Self::take_ownership(cbl_ref) } } @@ -80,6 +80,11 @@ impl Encryptable { } } + /// Takes ownership of the CBL ref, the reference counter is not increased so dropping the instance will free the ref. + pub(crate) const fn take_ownership(cbl_ref: *mut CBLEncryptable) -> Self { + Self { cbl_ref } + } + //////// /// Creates Encryptable object with null value. diff --git a/src/replicator.rs b/src/replicator.rs index c265058..5305648 100644 --- a/src/replicator.rs +++ b/src/replicator.rs @@ -25,7 +25,7 @@ use std::{ }; use crate::{ CblRef, Database, Dict, Document, Error, ListenerToken, MutableDict, Result, check_error, - release, retain, + release, slice::{from_str, self}, c_api::{ CBLListener_Remove, CBLAuth_CreatePassword, CBLAuth_CreateSession, CBLAuthenticator, @@ -107,6 +107,14 @@ impl Clone for Endpoint { } } +impl Drop for Endpoint { + fn drop(&mut self) { + unsafe { + release(self.get_ref()); + } + } +} + /** An opaque object representing authentication credentials for a remote server. */ #[derive(Debug, PartialEq, Eq)] pub struct Authenticator { @@ -786,7 +794,7 @@ impl Replicator { database: config .database .as_ref() - .map(|d| retain(d.get_ref())) + .map(|d| d.get_ref()) .unwrap_or(ptr::null_mut()), endpoint: config.endpoint.get_ref(), replicatorType: config.replicator_type.clone().into(), diff --git a/tests/database_tests.rs b/tests/database_tests.rs index 2eb449a..8753655 100644 --- a/tests/database_tests.rs +++ b/tests/database_tests.rs @@ -22,7 +22,7 @@ extern crate lazy_static; use self::couchbase_lite::*; use self::tempdir::TempDir; use lazy_static::lazy_static; -use utils::init_logging; +use utils::{init_logging, LeakChecker}; pub mod utils; @@ -44,6 +44,7 @@ fn delete_file() { pub const DB_NAME: &str = "test_db"; init_logging(); + let _leak_checker = LeakChecker::new(); let tmp_dir = TempDir::new("cbl_rust").expect("create temp dir"); let cfg = DatabaseConfiguration { @@ -66,6 +67,7 @@ fn copy_file() { pub const DB_NAME_BACKUP: &str = "test_db_backup"; init_logging(); + let _leak_checker = LeakChecker::new(); // Initial DB let tmp_dir = TempDir::new("cbl_rust").expect("create temp dir"); @@ -147,6 +149,9 @@ fn db_properties() { #[test] #[cfg(feature = "enterprise")] fn db_encryption_key() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let tmp_dir = TempDir::new("cbl_rust").expect("create temp dir"); let cfg_no_encryption = DatabaseConfiguration { directory: tmp_dir.path(), diff --git a/tests/document_tests.rs b/tests/document_tests.rs index 8aac1d0..8c3a32d 100644 --- a/tests/document_tests.rs +++ b/tests/document_tests.rs @@ -3,11 +3,15 @@ extern crate couchbase_lite; use self::couchbase_lite::*; use std::time::Duration; +use utils::{init_logging, LeakChecker}; pub mod utils; #[test] fn document_new() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let document = Document::new(); assert_ne!(document.id(), ""); assert_eq!(document.revision_id(), None); @@ -18,6 +22,9 @@ fn document_new() { #[test] fn document_new_with_id() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let document = Document::new_with_id("foo"); assert_eq!(document.id(), "foo"); assert_eq!(document.revision_id(), None); @@ -70,6 +77,9 @@ fn document_sequence() { #[test] fn document_properties() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let mut document = Document::new(); let mut properties = MutableDict::new(); properties.at("foo").put_bool(false); @@ -87,6 +97,9 @@ fn document_properties() { #[test] fn document_properties_as_json() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let mut document = Document::new(); document .set_properties_as_json(r#"{"foo":true,"bar":true}"#) diff --git a/tests/fleece_tests.rs b/tests/fleece_tests.rs index e10507f..abe270e 100644 --- a/tests/fleece_tests.rs +++ b/tests/fleece_tests.rs @@ -20,9 +20,15 @@ extern crate couchbase_lite; use couchbase_lite::*; +use utils::{init_logging, LeakChecker}; + +pub mod utils; #[test] fn empty_values() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let v = Value::default(); assert_eq!(v.get_type(), ValueType::Undefined); assert!(!v.is_type(ValueType::Bool)); @@ -38,6 +44,9 @@ fn empty_values() { #[test] fn basic_values() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let doc = Fleece::parse_json(r#"{"i":1234,"f":12.34,"a":[1, 2],"s":"Foo"}"#).unwrap(); let dict = doc.as_dict(); assert_eq!(dict.count(), 4); @@ -82,6 +91,9 @@ fn basic_values() { #[test] fn nested_borrow_check() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let v: Value; let mut str = String::new(); @@ -116,6 +128,9 @@ fn borrow_check() { #[test] fn dict_to_hash_set() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let mut mut_dict = MutableDict::new(); mut_dict.at("id1").put_bool(true); @@ -132,6 +147,9 @@ fn dict_to_hash_set() { #[test] fn mutable_dict() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let mut dict = MutableDict::new(); assert_eq!(dict.count(), 0); assert_eq!(dict.get("a"), Value::UNDEFINED); @@ -152,6 +170,9 @@ fn mutable_dict() { #[test] fn mutable_dict_to_from_hash_map() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let mut dict = MutableDict::new(); dict.at("id1").put_string("value1"); @@ -170,6 +191,9 @@ fn mutable_dict_to_from_hash_map() { #[test] fn dict_exact_size_iterator() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let mut mut_dict = MutableDict::new(); mut_dict.at("1").put_string("value1"); mut_dict.at("2").put_string("value2"); @@ -182,6 +206,9 @@ fn dict_exact_size_iterator() { #[test] fn dict_from_iterator() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let dict: MutableDict = Fleece::parse_json(r#"{"1": "value1","f":12.34}"#) .unwrap() .as_dict() @@ -202,6 +229,9 @@ fn dict_from_iterator() { #[test] fn array_at() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let mut mut_arr = MutableArray::new(); assert!(mut_arr.at(0).is_none()); mut_arr.append().put_string("value1"); @@ -210,6 +240,9 @@ fn array_at() { #[test] fn array_exact_size_iterator() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let mut mut_arr = MutableArray::new(); mut_arr.append().put_string("value1"); mut_arr.append().put_string("value2"); @@ -222,6 +255,9 @@ fn array_exact_size_iterator() { #[test] fn array_from_iterator() { + init_logging(); + let _leak_checker = LeakChecker::new(); + let arr: MutableArray = Fleece::parse_json(r#"["value1","value2"]"#) .unwrap() .as_array() diff --git a/tests/utils.rs b/tests/utils.rs index 8cac5ba..bea1b23 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -11,11 +11,6 @@ use std::{ #[cfg(feature = "enterprise")] use std::collections::HashMap; -// Enables check for leaks of native CBL objects after `with_db()` finishes. -// WARNING: These checks only work if one test method runs at a time, i.e. testing is single -// threaded. Run as `cargo test -- --test-threads=1` or you'll get false positives. -const LEAK_CHECK: Option<&'static str> = option_env!("LEAK_CHECK"); - pub const DB_NAME: &str = "test_db"; const LEVEL_PREFIX: [&str; 5] = ["((", "_", "", "WARNING: ", "***ERROR: "]; @@ -36,6 +31,54 @@ pub fn init_logging() { logging::set_console_level(logging::Level::None); } +pub struct LeakChecker { + is_checking: bool, + start_instance_count: usize, + end_instance_count: usize, +} + +impl LeakChecker { + pub fn new() -> Self { + if option_env!("LEAK_CHECK").is_some() { + LeakChecker { + is_checking: true, + start_instance_count: instance_count(), + end_instance_count: 0, + } + } else { + LeakChecker { + is_checking: false, + start_instance_count: 0, + end_instance_count: 0, + } + } + } +} + +impl Drop for LeakChecker { + fn drop(&mut self) { + if self.is_checking { + info!("Checking if Couchbase Lite objects were leaked by this test"); + self.end_instance_count = instance_count(); + + if self.start_instance_count != self.end_instance_count { + info!("Leaks detected :-("); + info!( + "Instances before: {} | Instances after: {}", + self.start_instance_count, self.end_instance_count + ); + dump_instances(); + panic!("Memory leaks detected"); + // NOTE: This failure is likely to happen if the tests run multi-threaded, as happens by + // default. Looking for changes in the `instance_count()` is intrinsically not thread safe. + // Either run tests with `cargo test -- --test-threads`, or turn off `LEAK_CHECKS`. + } else { + info!("All good :-)"); + } + } + } +} + // Test wrapper function -- takes care of creating and deleting the database. pub fn with_db(f: F) where @@ -43,7 +86,7 @@ where { init_logging(); - let start_inst_count = instance_count(); + let _leak_checker = LeakChecker::new(); { let tmp_dir = TempDir::new("cbl_rust").expect("create temp dir"); @@ -59,21 +102,6 @@ where db.delete().unwrap(); } - - if LEAK_CHECK.is_some() { - info!("Checking if Couchbase Lite objects were leaked by this test"); - dump_instances(); - assert_eq!( - instance_count(), - start_inst_count, - "Native object leak: {} objects, was {}", - instance_count(), - start_inst_count - ); - // NOTE: This failure is likely to happen if the tests run multi-threaded, as happens by - // default. Looking for changes in the `instance_count()` is intrinsically not thread safe. - // Either run tests with `cargo test -- --test-threads`, or turn off `LEAK_CHECKS`. - } } // Replication @@ -129,6 +157,8 @@ pub struct ReplicationTwoDbsTester { central_database: Database, replicator: Replicator, replicator_continuous: bool, + // Keep _leak_checker at the end, fields in a struct are dropped in declaration order + _leak_checker: LeakChecker, } #[cfg(feature = "enterprise")] @@ -139,6 +169,8 @@ impl ReplicationTwoDbsTester { ) -> Self { init_logging(); + let _leak_checker = LeakChecker::new(); + // Create databases let tmp_dir = TempDir::new("cbl_rust").expect("create temp dir"); let tmp_dir_path = tmp_dir.path(); @@ -180,6 +212,7 @@ impl ReplicationTwoDbsTester { central_database, replicator, replicator_continuous, + _leak_checker, } } @@ -255,6 +288,8 @@ pub struct ReplicationThreeDbsTester { replicator_1_continuous: bool, replicator_2: Replicator, replicator_2_continuous: bool, + // Keep _leak_checker at the end, fields in a struct are dropped in declaration order + _leak_checker: LeakChecker, } #[cfg(feature = "enterprise")] @@ -267,6 +302,8 @@ impl ReplicationThreeDbsTester { ) -> Self { init_logging(); + let _leak_checker = LeakChecker::new(); + // Create databases let tmp_dir = TempDir::new("cbl_rust").expect("create temp dir"); let local_database_1_configuration = DatabaseConfiguration { @@ -328,6 +365,7 @@ impl ReplicationThreeDbsTester { replicator_1_continuous, replicator_2, replicator_2_continuous, + _leak_checker, } }