diff --git a/src/realm/alloc_slab.cpp b/src/realm/alloc_slab.cpp index 2c8915229fb..2075241fee4 100644 --- a/src/realm/alloc_slab.cpp +++ b/src/realm/alloc_slab.cpp @@ -752,9 +752,13 @@ ref_type SlabAlloc::attach_file(const std::string& file_path, Config& cfg) size = initial_size; } ref_type top_ref; + note_reader_start(this); + util::ScopeExit reader_end_guard([this]() noexcept { + note_reader_end(this); + }); + size_t expected_size = size_t(-1); try { - note_reader_start(this); // we'll read header and (potentially) footer File::Map map_header(m_file, File::access_ReadOnly, sizeof(Header)); // if file is too small we'll catch it in validate_header - but we need to prevent an invalid mapping first @@ -767,7 +771,7 @@ ref_type SlabAlloc::attach_file(const std::string& file_path, Config& cfg) realm::util::encryption_read_barrier(map_footer, footer_offset, sizeof(StreamingFooter)); auto header = reinterpret_cast(map_header.get_addr()); auto footer = reinterpret_cast(map_footer.get_addr() + footer_offset); - top_ref = validate_header(header, footer, size, path); // Throws + top_ref = validate_header(header, footer, size, path, cfg.encryption_key != nullptr); // Throws m_attach_mode = cfg.is_shared ? attach_SharedFile : attach_UnsharedFile; m_data = map_header.get_addr(); // <-- needed below @@ -824,29 +828,19 @@ ref_type SlabAlloc::attach_file(const std::string& file_path, Config& cfg) expected_size = round_up_to_page_size(logical_size); } } - catch (const DecryptionFailed&) { - note_reader_end(this); - throw InvalidDatabase("Realm file decryption failed", path); - } catch (const InvalidDatabase&) { - note_reader_end(this); throw; } + catch (const DecryptionFailed&) { + throw InvalidDatabase("decryption failed", path); + } catch (const std::exception& e) { - // Catch all for other exceptions - note_reader_end(this); - // we end up here if any of the file or mapping operations fail. - throw InvalidDatabase(util::format("Realm file initial open failed: %1", e.what()), path); + throw InvalidDatabase(e.what(), path); } catch (...) { - note_reader_end(this); - throw InvalidDatabase("Realm file initial open failed: unknown error", path); + throw InvalidDatabase("unknown error", path); } m_baseline = 0; - auto handler = [this]() noexcept { - note_reader_end(this); - }; - auto reader_end_guard = make_scope_exit(handler); // make sure that any call to begin_read cause any slab to be placed in free // lists correctly m_free_space_state = free_space_Invalid; @@ -973,7 +967,7 @@ void SlabAlloc::throw_header_exception(std::string msg, const Header& header, co { char buf[256]; snprintf(buf, sizeof(buf), - ". top_ref[0]: %" PRIX64 ", top_ref[1]: %" PRIX64 ", " + " top_ref[0]: %" PRIX64 ", top_ref[1]: %" PRIX64 ", " "mnemonic: %X %X %X %X, fmt[0]: %d, fmt[1]: %d, flags: %X", header.m_top_ref[0], header.m_top_ref[1], header.m_mnemonic[0], header.m_mnemonic[1], header.m_mnemonic[2], header.m_mnemonic[3], header.m_file_format[0], header.m_file_format[1], @@ -992,18 +986,30 @@ ref_type SlabAlloc::validate_header(const char* data, size_t size, const std::st } ref_type SlabAlloc::validate_header(const Header* header, const StreamingFooter* footer, size_t size, - const std::string& path) + const std::string& path, bool is_encrypted) { // Verify that size is sane and 8-byte aligned - if (REALM_UNLIKELY(size < sizeof(Header) || size % 8 != 0)) { - std::string msg = "Realm file has bad size (" + util::to_string(size) + ")"; - throw InvalidDatabase(msg, path); - } + if (REALM_UNLIKELY(size < sizeof(Header))) + throw InvalidDatabase(util::format("file is non-empty but too small (%1 bytes) to be a valid Realm.", size), + path); + if (REALM_UNLIKELY(size % 8 != 0)) + throw InvalidDatabase(util::format("file has an invalid size (%1).", size), path); // First four bytes of info block is file format id if (REALM_UNLIKELY(!(char(header->m_mnemonic[0]) == 'T' && char(header->m_mnemonic[1]) == '-' && - char(header->m_mnemonic[2]) == 'D' && char(header->m_mnemonic[3]) == 'B'))) - throw_header_exception("Invalid mnemonic", *header, path); + char(header->m_mnemonic[2]) == 'D' && char(header->m_mnemonic[3]) == 'B'))) { + if (is_encrypted) { + // Encrypted files check the hmac on read, so there's a lot less + // which could go wrong and have us still reach this point + throw_header_exception("header has invalid mnemonic. The file does not appear to be Realm file.", *header, + path); + } + else { + throw_header_exception("header has invalid mnemonic. The file is either not a Realm file, is an " + "encrypted Realm file but no encryption key was supplied, or is corrupted.", + *header, path); + } + } // Last bit in info block indicates which top_ref block is valid int slot_selector = ((header->m_flags & SlabAlloc::flags_SelectBit) != 0 ? 1 : 0); @@ -1012,22 +1018,27 @@ ref_type SlabAlloc::validate_header(const Header* header, const StreamingFooter* auto top_ref = header->m_top_ref[slot_selector]; if (slot_selector == 0 && top_ref == 0xFFFFFFFFFFFFFFFFULL) { if (REALM_UNLIKELY(size < sizeof(Header) + sizeof(StreamingFooter))) { - std::string msg = "Invalid streaming format size (" + util::to_string(size) + ")"; - throw InvalidDatabase(msg, path); + throw InvalidDatabase( + util::format("file is in streaming format but too small (%1 bytes) to be a valid Realm.", size), + path); } top_ref = footer->m_top_ref; if (REALM_UNLIKELY(footer->m_magic_cookie != footer_magic_cookie)) { - std::string msg = "Invalid streaming format cookie (" + util::to_string(footer->m_magic_cookie) + ")"; - throw InvalidDatabase(msg, path); + throw InvalidDatabase(util::format("file is in streaming format but has an invalid footer cookie (%1). " + "The file is probably truncated.", + footer->m_magic_cookie), + path); } } if (REALM_UNLIKELY(top_ref % 8 != 0)) { - std::string msg = "Top ref not aligned (" + util::to_string(top_ref) + ")"; - throw_header_exception(msg, *header, path); + throw_header_exception("top ref is not aligned", *header, path); } if (REALM_UNLIKELY(top_ref >= size)) { - std::string msg = "Top ref outside file (size = " + util::to_string(size) + ")"; - throw_header_exception(msg, *header, path); + throw_header_exception( + util::format( + "top ref is outside of the file (size: %1, top_ref: %2). The file has probably been truncated.", size, + top_ref), + *header, path); } return ref_type(top_ref); } diff --git a/src/realm/alloc_slab.hpp b/src/realm/alloc_slab.hpp index 465b3265cd9..d2530953a33 100644 --- a/src/realm/alloc_slab.hpp +++ b/src/realm/alloc_slab.hpp @@ -631,7 +631,7 @@ class SlabAlloc : public Allocator { /// Returns the top_ref for the latest commit. ref_type validate_header(const char* data, size_t len, const std::string& path); ref_type validate_header(const Header* header, const StreamingFooter* footer, size_t size, - const std::string& path); + const std::string& path, bool is_encrypted = false); void throw_header_exception(std::string msg, const Header& header, const std::string& path); static bool is_file_on_streaming_form(const Header& header); @@ -668,7 +668,10 @@ class SlabAlloc::DetachGuard { struct InvalidDatabase : FileAccessError { InvalidDatabase(const std::string& msg, const std::string& path) - : FileAccessError(ErrorCodes::InvalidDatabase, msg, path, 0) + : FileAccessError(ErrorCodes::InvalidDatabase, + path.empty() ? "Failed to memory buffer:" + msg + : util::format("Failed to open Realm file at path '%1': %2", path, msg), + path) { } }; diff --git a/src/realm/collection.hpp b/src/realm/collection.hpp index 71533fe833d..311bd6658cc 100644 --- a/src/realm/collection.hpp +++ b/src/realm/collection.hpp @@ -131,8 +131,30 @@ class CollectionBase { CollectionBase(CollectionBase&&) noexcept = default; CollectionBase& operator=(const CollectionBase&) noexcept = default; CollectionBase& operator=(CollectionBase&&) noexcept = default; + + void validate_index(const char* msg, size_t index, size_t size) const; }; +inline std::string_view collection_type_name(ColKey col, bool uppercase = false) +{ + if (col.is_list()) + return uppercase ? "List" : "list"; + if (col.is_set()) + return uppercase ? "Set" : "set"; + if (col.is_dictionary()) + return uppercase ? "Dictionary" : "dictionary"; + return ""; +} + +inline void CollectionBase::validate_index(const char* msg, size_t index, size_t size) const +{ + if (index >= size) { + throw OutOfBounds(util::format("%1 on %2 '%3.%4'", msg, collection_type_name(get_col_key()), + get_table()->get_class_name(), get_property_name()), + index, size); + } +} + template inline void check_column_type(ColKey col) diff --git a/src/realm/db.cpp b/src/realm/db.cpp index 6f6edab0d87..1731234ebbb 100644 --- a/src/realm/db.cpp +++ b/src/realm/db.cpp @@ -652,6 +652,7 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions opti { // Exception safety: Since do_open() is called from constructors, if it // throws, it must leave the file closed. + using util::format; REALM_ASSERT(!is_attached()); @@ -813,19 +814,16 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions opti --retries_left; continue; } - std::stringstream ss; - ss << "Info size doesn't match, " << info_size << " " << sizeof(SharedInfo) << "."; - throw IncompatibleLockFile(ss.str()); + throw IncompatibleLockFile(path, format("Architecture mismatch: SharedInfo size is %1 but should be %2.", + info_size, sizeof(SharedInfo))); } if (info->shared_info_version != g_shared_info_version) { if (retries_left) { --retries_left; continue; } - std::stringstream ss; - ss << "Shared info version doesn't match, " << info->shared_info_version << " " << g_shared_info_version - << "."; - throw IncompatibleLockFile(ss.str()); + throw IncompatibleLockFile(path, format("Version mismatch: SharedInfo version is %1 but should be %2.", + info->shared_info_version, g_shared_info_version)); } // Validate compatible sizes of mutex and condvar types. Sizes of all // other fields are architecture independent, so if condvar and mutex @@ -837,10 +835,8 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions opti --retries_left; continue; } - std::stringstream ss; - ss << "Mutex size doesn't match: " << info->size_of_mutex << " " << sizeof(info->shared_controlmutex) - << "."; - throw IncompatibleLockFile(ss.str()); + throw IncompatibleLockFile(path, format("Architecture mismatch: Mutex size is %1 but should be %2.", + info->size_of_mutex, sizeof(info->shared_controlmutex))); } if (info->size_of_condvar != sizeof info->room_to_write) { @@ -848,10 +844,9 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions opti --retries_left; continue; } - std::stringstream ss; - ss << "Condition var size doesn't match: " << info->size_of_condvar << " " << sizeof(info->room_to_write) - << "."; - throw IncompatibleLockFile(ss.str()); + throw IncompatibleLockFile( + path, format("Architecture mismatch: Condition variable size is %1 but should be %2.", + info->size_of_condvar, sizeof(info->room_to_write))); } m_writemutex.set_shared_part(info->shared_writemutex, m_lockfile_prefix, "write"); m_controlmutex.set_shared_part(info->shared_controlmutex, m_lockfile_prefix, "control"); @@ -859,7 +854,8 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions opti // even though fields match wrt alignment and size, there may still be incompatibilities // between implementations, so lets ask one of the mutexes if it thinks it'll work. if (!m_controlmutex.is_valid()) { - throw IncompatibleLockFile("Control mutex is invalid."); + throw IncompatibleLockFile( + path, "Control mutex is invalid. This suggests that incompatible pthread libraries are in use."); } // OK! lock file appears valid. We can now continue operations under the protection @@ -995,8 +991,9 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions opti good_history_type = (stored_hist_type == Replication::hist_None); if (!good_history_type) throw IncompatibleHistories( - util::format("Expected a Realm without history, but found history type %1", - stored_hist_type), + util::format("Realm file at path '%1' has history type '%2', but is being opened " + "with replication disabled.", + path, Replication::history_type_name(stored_hist_type)), path); break; case Replication::hist_OutOfRealm: @@ -1007,27 +1004,28 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions opti stored_hist_type == Replication::hist_None); if (!good_history_type) throw IncompatibleHistories( - util::format( - "Expected a Realm with no or in-realm history, but found history type %1", - stored_hist_type), + util::format("Realm file at path '%1' has history type '%2', but is being opened in " + "local history mode.", + path, Replication::history_type_name(stored_hist_type)), path); break; case Replication::hist_SyncClient: good_history_type = ((stored_hist_type == Replication::hist_SyncClient) || (top_ref == 0)); if (!good_history_type) throw IncompatibleHistories( - util::format( - "Expected an empty or synced Realm, but found history type %1, top ref %2", - stored_hist_type, top_ref), + util::format("Realm file at path '%1' has history type '%2', but is being opened in " + "synchronized history mode.", + path, Replication::history_type_name(stored_hist_type)), path); break; case Replication::hist_SyncServer: good_history_type = ((stored_hist_type == Replication::hist_SyncServer) || (top_ref == 0)); if (!good_history_type) - throw IncompatibleHistories(util::format("Expected a Realm containing a server-side " - "history, but found history type %1, top ref %2", - stored_hist_type, top_ref), - path); + throw IncompatibleHistories( + util::format("Realm file at path '%1' has history type '%2', but is being opened in " + "server history mode.", + path, Replication::history_type_name(stored_hist_type)), + path); break; } @@ -1120,10 +1118,9 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions opti // throw the same kind of exception, as would have been thrown // with a bumped SharedInfo file format version, if there isn't. if (info->file_format_version != target_file_format_version) { - std::stringstream ss; - ss << "File format version doesn't match: " << info->file_format_version << " " - << target_file_format_version << "."; - throw IncompatibleLockFile(ss.str()); + throw IncompatibleLockFile(path, + format("Version mismatch: File format version is %1 but should be %2.", + info->file_format_version, target_file_format_version)); } // Even though this session participant is not the session initiator, diff --git a/src/realm/db.hpp b/src/realm/db.hpp index da40afb3d22..9c0df254f73 100644 --- a/src/realm/db.hpp +++ b/src/realm/db.hpp @@ -45,11 +45,19 @@ using TransactionRef = std::shared_ptr; /// Thrown by DB::create() if the lock file is already open in another /// process which can't share mutexes with this process -struct IncompatibleLockFile : RuntimeError { - IncompatibleLockFile(const std::string& msg) - : RuntimeError( +struct IncompatibleLockFile : FileAccessError { + IncompatibleLockFile(const std::string& path, const std::string& msg) + : FileAccessError( ErrorCodes::IncompatibleLockFile, - "Realm file is currently open in another process which cannot share access with this process. " + msg) + util::format( + "Realm file '%1' is currently open in another process which cannot share access with this process. " + "This could either be due to the existing process being a different architecture or due to the " + "existing process using an incompatible version of Realm. " + "If the other process is Realm Studio, you may need to update it (or update Realm if your Studio " + "version is too new), and if using an iOS simulator, make sure that you are using a 64-bit " + "simulator. Underlying problem: %2", + path, msg), + path) { } }; @@ -65,7 +73,8 @@ struct IncompatibleLockFile : RuntimeError { /// (Replication::is_upgradable_history_schema()). struct IncompatibleHistories : FileAccessError { IncompatibleHistories(const std::string& msg, const std::string& path) - : FileAccessError(ErrorCodes::IncompatibleHistories, "Incompatible histories. " + msg, path, 0) + : FileAccessError(ErrorCodes::IncompatibleHistories, + msg + " Synchronized Realms cannot be opened in non-sync mode, and vice versa.", path) { } }; diff --git a/src/realm/dictionary.cpp b/src/realm/dictionary.cpp index 6edb154ae0e..c7e1f577ba9 100644 --- a/src/realm/dictionary.cpp +++ b/src/realm/dictionary.cpp @@ -946,7 +946,7 @@ bool Dictionary::try_erase(Mixed key) void Dictionary::erase(Mixed key) { if (!try_erase(key)) { - throw KeyNotFound("Dictionary::erase"); + throw KeyNotFound(util::format("Cannot remove key %1 from dictionary: key not found", key)); } } diff --git a/src/realm/error_codes.cpp b/src/realm/error_codes.cpp index fd8000b1db9..5374136456f 100644 --- a/src/realm/error_codes.cpp +++ b/src/realm/error_codes.cpp @@ -111,6 +111,7 @@ ErrorCategory ErrorCodes::error_categories(Error code) case ReadOnlyProperty: case SyntaxError: case TableNameInUse: + case TopLevelObject: case TypeMismatch: case UnexpectedPrimaryKey: return ErrorCategory().set(ErrorCategory::invalid_argument).set(ErrorCategory::logic_error); @@ -241,6 +242,7 @@ static const std::pair error_codes_map[] = {"FunctionSyntaxError", ErrorCodes::FunctionSyntaxError}, {"GCMError", ErrorCodes::GCMError}, {"HTTPError", ErrorCodes::HTTPError}, + {"IllegalCombination", ErrorCodes::IllegalCombination}, {"IllegalOperation", ErrorCodes::IllegalOperation}, {"IncomingWebhookAlreadyExists", ErrorCodes::IncomingWebhookAlreadyExists}, {"IncomingWebhookAuthFailed", ErrorCodes::IncomingWebhookAuthFailed}, @@ -251,7 +253,6 @@ static const std::pair error_codes_map[] = {"IncompatibleSession", ErrorCodes::IncompatibleSession}, {"InternalServerError", ErrorCodes::InternalServerError}, {"InvalidArgument", ErrorCodes::InvalidArgument}, - {"InvalidCombination", ErrorCodes::IllegalCombination}, {"InvalidDatabase", ErrorCodes::InvalidDatabase}, {"InvalidDictionaryKey", ErrorCodes::InvalidDictionaryKey}, {"InvalidDictionaryValue", ErrorCodes::InvalidDictionaryValue}, @@ -322,6 +323,7 @@ static const std::pair error_codes_map[] = {"SyntaxError", ErrorCodes::SyntaxError}, {"SystemError", ErrorCodes::SystemError}, {"TableNameInUse", ErrorCodes::TableNameInUse}, + {"TopLevelObject", ErrorCodes::TopLevelObject}, {"TwilioError", ErrorCodes::TwilioError}, {"TypeMismatch", ErrorCodes::TypeMismatch}, {"UnexpectedPrimaryKey", ErrorCodes::UnexpectedPrimaryKey}, diff --git a/src/realm/error_codes.h b/src/realm/error_codes.h index 2ce800da5e7..ce68eebb539 100644 --- a/src/realm/error_codes.h +++ b/src/realm/error_codes.h @@ -86,6 +86,7 @@ typedef enum realm_errno { RLM_ERR_INVALID_SCHEMA_VERSION = 2018, RLM_ERR_INVALID_SCHEMA_CHANGE = 2019, RLM_ERR_MIGRATION_FAILED = 2020, + RLM_ERR_TOP_LEVEL_OBJECT = 2021, RLM_ERR_INVALID_ARGUMENT = 3000, RLM_ERR_PROPERTY_TYPE_MISMATCH = 3001, diff --git a/src/realm/error_codes.hpp b/src/realm/error_codes.hpp index 50a6fb146e9..f9a6b714245 100644 --- a/src/realm/error_codes.hpp +++ b/src/realm/error_codes.hpp @@ -128,6 +128,7 @@ class ErrorCodes { InvalidSchemaVersion = RLM_ERR_INVALID_SCHEMA_VERSION, InvalidSchemaChange = RLM_ERR_INVALID_SCHEMA_CHANGE, MigrationFailed = RLM_ERR_MIGRATION_FAILED, + TopLevelObject = RLM_ERR_TOP_LEVEL_OBJECT, BadServerUrl = RLM_ERR_BAD_SERVER_URL, InvalidArgument = RLM_ERR_INVALID_ARGUMENT, @@ -228,6 +229,11 @@ class ErrorCodes { static ErrorCategory error_categories(Error code); static std::string_view error_string(Error code); static Error from_string(std::string_view str); + + static bool is_category(Error code, ErrorCategory::Type category) + { + return error_categories(code).test(category); + } }; std::ostream& operator<<(std::ostream& stream, ErrorCodes::Error code); diff --git a/src/realm/exceptions.hpp b/src/realm/exceptions.hpp index 72412eb9018..96fe3b239a7 100644 --- a/src/realm/exceptions.hpp +++ b/src/realm/exceptions.hpp @@ -153,6 +153,10 @@ struct AddressSpaceExhausted : RuntimeError { struct InvalidArgument : LogicError { ~InvalidArgument() noexcept override; InvalidArgument(ErrorCodes::Error code, const std::string& msg); + InvalidArgument(const std::string& msg) + : InvalidArgument(ErrorCodes::InvalidArgument, msg) + { + } }; struct InvalidColumnKey : InvalidArgument { @@ -203,8 +207,11 @@ struct NotNullable : InvalidArgument { ~NotNullable() noexcept override; template NotNullable(const T& object_type, const U& property_name) - : InvalidArgument(ErrorCodes::PropertyNotNullable, - util::format("Property '%2' of class '%1' cannot be NULL", object_type, property_name)) + : NotNullable(util::format("Cannot set non-nullable property '%1.%2' to NULL", object_type, property_name)) + { + } + NotNullable(const std::string& msg) + : InvalidArgument(ErrorCodes::PropertyNotNullable, msg) { } }; @@ -214,7 +221,7 @@ struct PropertyTypeMismatch : InvalidArgument { template PropertyTypeMismatch(const T& object_type, const U& property_name) : InvalidArgument(ErrorCodes::TypeMismatch, - util::format("Type mismatch for property '%2' of class '%1'", object_type, property_name)) + util::format("Type mismatch for property '%1.%2'", object_type, property_name)) { } }; diff --git a/src/realm/list.hpp b/src/realm/list.hpp index 1b125e8de29..6aa99a5699b 100644 --- a/src/realm/list.hpp +++ b/src/realm/list.hpp @@ -277,6 +277,9 @@ class Lst final : public CollectionBaseImpl> { } } } + +private: + T do_get(size_t ndx, const char* msg) const; }; // Specialization of Lst: @@ -662,11 +665,15 @@ inline CollectionBasePtr Lst::clone_collection() const template inline T Lst::get(size_t ndx) const +{ + return do_get(ndx, "get()"); +} + +template +inline T Lst::do_get(size_t ndx, const char* msg) const { const auto current_size = size(); - if (ndx >= current_size) { - throw OutOfBounds(util::format("get() on %1", CollectionBase::get_property_name()), ndx, current_size); - } + CollectionBase::validate_index(msg, ndx, current_size); auto value = m_tree->get(ndx); if constexpr (std::is_same_v) { @@ -823,10 +830,8 @@ template void Lst::move(size_t from, size_t to) { auto sz = size(); - if (from >= sz || to >= sz) { - throw OutOfBounds(util::format("move() on %1", CollectionBase::get_property_name()), from >= sz ? from : to, - sz); - } + CollectionBase::validate_index("move()", from, sz); + CollectionBase::validate_index("move()", to, sz); if (from != to) { if (Replication* repl = this->m_obj.get_replication()) { @@ -854,10 +859,8 @@ template void Lst::swap(size_t ndx1, size_t ndx2) { auto sz = size(); - if (ndx1 >= sz || ndx2 >= sz) { - throw OutOfBounds(util::format("swap() on %1", CollectionBase::get_property_name()), ndx1 >= sz ? ndx1 : ndx2, - sz); - } + CollectionBase::validate_index("swap()", ndx1, sz); + CollectionBase::validate_index("swap()", ndx2, sz); if (ndx1 != ndx2) { if (Replication* repl = this->m_obj.get_replication()) { @@ -876,7 +879,7 @@ T Lst::set(size_t ndx, T value) util::format("List: %1", CollectionBase::get_property_name())); // get will check for ndx out of bounds - T old = get(ndx); + T old = do_get(ndx, "set()"); if (Replication* repl = this->m_obj.get_replication()) { repl->list_set(*this, ndx, value); } @@ -895,8 +898,7 @@ void Lst::insert(size_t ndx, T value) util::format("List: %1", CollectionBase::get_property_name())); auto sz = size(); - if (ndx > sz) - throw OutOfBounds(util::format("insert() on %1", CollectionBase::get_property_name()), ndx, sz + 1); + CollectionBase::validate_index("insert()", ndx, sz + 1); ensure_created(); @@ -911,7 +913,7 @@ template T Lst::remove(size_t ndx) { // get will check for ndx out of bounds - T old = get(ndx); + T old = do_get(ndx, "remove()"); if (Replication* repl = this->m_obj.get_replication()) { repl->list_erase(*this, ndx); } @@ -1083,9 +1085,7 @@ inline void LnkLst::swap(size_t ndx1, size_t ndx2) inline ObjKey LnkLst::get(size_t ndx) const { const auto current_size = size(); - if (ndx >= current_size) { - throw OutOfBounds(util::format("get() on %1", CollectionBase::get_property_name()), ndx, current_size); - } + CollectionBase::validate_index("get()", ndx, current_size); return m_list.m_tree->get(virtual2real(ndx)); } @@ -1107,8 +1107,8 @@ inline void LnkLst::insert(size_t ndx, ObjKey value) REALM_ASSERT(!value.is_unresolved()); if (get_target_table()->is_embedded() && value != ObjKey()) throw IllegalOperation( - util::format("Cannot insert an already managed object into a list of embedded objects: %1", - CollectionBase::get_property_name())); + util::format("Cannot insert an already managed object into list of embedded objects '%1.%2'", + get_table()->get_class_name(), CollectionBase::get_property_name())); update_if_needed(); m_list.insert(virtual2real(ndx), value); @@ -1120,7 +1120,8 @@ inline ObjKey LnkLst::set(size_t ndx, ObjKey value) REALM_ASSERT(!value.is_unresolved()); if (get_target_table()->is_embedded() && value != ObjKey()) throw IllegalOperation( - util::format("Cannot replace embedded object: %1", CollectionBase::get_property_name())); + util::format("Cannot insert an already managed object into list of embedded objects '%1.%2'", + get_table()->get_class_name(), CollectionBase::get_property_name())); update_if_needed(); ObjKey old = m_list.set(virtual2real(ndx), value); diff --git a/src/realm/obj.cpp b/src/realm/obj.cpp index a94ea8b90fa..886ebdc1c1a 100644 --- a/src/realm/obj.cpp +++ b/src/realm/obj.cpp @@ -516,7 +516,7 @@ Obj Obj::get_parent_object() const update_if_needed(); if (!m_table->is_embedded()) { - throw std::runtime_error("Object is not embedded"); + throw LogicError(ErrorCodes::TopLevelObject, "Object is not embedded"); } m_table->for_each_backlink_column([&](ColKey backlink_col_key) { if (get_backlink_cnt(backlink_col_key) == 1) { @@ -1072,12 +1072,7 @@ void Obj::to_json(std::ostream& out, size_t link_depth, const std::map -int64_t Obj::get(ColKey) const; -template <> -bool Obj::get(ColKey) const; - template <> int64_t Obj::_get(ColKey::Idx col_ndx) const; -template <> -StringData Obj::_get(ColKey::Idx col_ndx) const; -template <> -BinaryData Obj::_get(ColKey::Idx col_ndx) const; -template <> -ObjKey Obj::_get(ColKey::Idx col_ndx) const; struct Obj::FatPathElement { Obj obj; // Object which embeds... diff --git a/src/realm/object-store/audit.mm b/src/realm/object-store/audit.mm index e9b335eaefa..f5d5c624f3a 100644 --- a/src/realm/object-store/audit.mm +++ b/src/realm/object-store/audit.mm @@ -1004,8 +1004,8 @@ throw LogicError(ErrorCodes::InvalidName, audit_user = parent_sync_config.user; if (parent_sync_config.flx_sync_requested && audit_user == parent_sync_config.user) { - throw LogicError(ErrorCodes::InvalidArgument, "Auditing a flexible sync realm requires setting the audit " - "user to a user associated with a partition-based sync app."); + throw InvalidArgument("Auditing a flexible sync realm requires setting the audit " + "user to a user associated with a partition-based sync app."); } if (!m_logger) diff --git a/src/realm/object-store/collection.cpp b/src/realm/object-store/collection.cpp index 7c54b23f571..11f81f755f0 100644 --- a/src/realm/object-store/collection.cpp +++ b/src/realm/object-store/collection.cpp @@ -73,7 +73,7 @@ Collection& Collection::operator=(Collection&&) = default; bool Collection::is_valid() const { - if (!m_realm) + if (!m_realm || !m_coll_base) return false; m_realm->verify_thread(); if (!m_realm->is_in_read_transaction()) @@ -99,55 +99,41 @@ TableKey Collection::get_parent_table_key() const return m_coll_base->get_table()->get_key(); } -static StringData object_name(Table const& table) -{ - return ObjectStore::object_type_for_table_name(table.get_name()); -} - void Collection::validate(const Obj& obj) const { if (!obj.is_valid()) - throw std::invalid_argument("Object has been deleted or invalidated"); + throw StaleAccessor("Object has been deleted or invalidated"); // FIXME: This does not work for TypedLink. auto target = m_coll_base->get_target_table(); if (obj.get_table() != target) - throw std::invalid_argument(util::format("Object of type (%1) does not match List type (%2)", - object_name(*obj.get_table()), object_name(*target))); -} - -void Collection::not_supported(const char* operation) const -{ - auto col_key = m_coll_base->get_col_key(); - const char* type_name = get_data_type_name(DataType(col_key.get_type())); - if (col_key.is_list()) - throw IllegalOperation(util::format("Cannot %1 '%2' array: operation not supported", operation, type_name)); - else if (col_key.is_dictionary()) - throw IllegalOperation( - util::format("Cannot %1 '%2' dictionary: operation not supported", operation, type_name)); - else if (col_key.is_set()) - throw IllegalOperation(util::format("Cannot %1 '%2' set: operation not supported", operation, type_name)); - - throw LogicError(ErrorCodes::BrokenInvariant, "Unexpected collection type"); + throw InvalidArgument(ErrorCodes::ObjectTypeMismatch, + util::format("Object of type (%1) does not match %2 type (%3)", + obj.get_table()->get_class_name(), type_name(), target->get_class_name())); } void Collection::verify_attached() const { - if (!is_valid()) { - std::string coll_type = "Collection"; - if (is_array(m_type)) - coll_type = "List"; - else if (is_dictionary(m_type)) - coll_type = "Dictionary"; - else if (is_set(m_type)) - coll_type = "Set"; - throw LogicError(ErrorCodes::InvalidatedObject, util::format("Access to invalidated %1 object", coll_type)); + if (REALM_LIKELY(is_valid())) { + return; + } + if (!m_coll_base) { + throw LogicError(ErrorCodes::InvalidatedObject, + util::format("%1 is invalid and was never initialized.", type_name())); } + + throw LogicError(ErrorCodes::InvalidatedObject, + util::format("%1 is no longer valid. Either the parent object was deleted or the containing " + "Realm has been invalidated or closed.", + type_name())); } void Collection::verify_in_transaction() const { verify_attached(); - m_realm->verify_in_write(); + if (REALM_UNLIKELY(!m_realm->is_in_transaction())) { + throw WrongTransactionState( + util::format("Cannot modify managed %1 outside of a write transaction.", type_name())); + } } size_t Collection::size() const @@ -163,7 +149,7 @@ const ObjectSchema& Collection::get_object_schema() const REALM_ASSERT(get_type() == PropertyType::Object); auto object_schema = m_object_schema.load(); if (!object_schema) { - auto object_type = object_name(*m_coll_base->get_target_table()); + auto object_type = m_coll_base->get_target_table()->get_class_name(); auto it = m_realm->schema().find(object_type); REALM_ASSERT(it != m_realm->schema().end()); m_object_schema = object_schema = &*it; diff --git a/src/realm/object-store/collection.hpp b/src/realm/object-store/collection.hpp index b5783fdfc05..58a7afe9d04 100644 --- a/src/realm/object-store/collection.hpp +++ b/src/realm/object-store/collection.hpp @@ -126,7 +126,6 @@ class Collection { Collection& operator=(Collection&&); void validate(const Obj&) const; - void not_supported(const char* operation) const; template void validate_embedded(Context& ctx, T&& value, CreatePolicy policy) const; @@ -138,13 +137,15 @@ class Collection { private: Collection(std::shared_ptr&& r, CollectionBasePtr&& coll, PropertyType type); + + virtual const char* type_name() const noexcept = 0; }; template void Collection::validate_embedded(Context& ctx, T&& value, CreatePolicy policy) const { if (!policy.copy && ctx.template unbox(value, CreatePolicy::Skip).is_valid()) - throw IllegalOperation("Cannot add an existing managed embedded object to a List."); + throw IllegalOperation(util::format("Cannot add an existing managed embedded object to a %1.", type_name())); } } // namespace object_store diff --git a/src/realm/object-store/dictionary.cpp b/src/realm/object-store/dictionary.cpp index d2a7f3c1aac..d902754ea32 100644 --- a/src/realm/object-store/dictionary.cpp +++ b/src/realm/object-store/dictionary.cpp @@ -288,6 +288,7 @@ Dictionary::Iterator Dictionary::end() const return dict().end(); } +namespace { class NotificationHandler { public: NotificationHandler(realm::Dictionary& dict, Dictionary::CBFunc cb) @@ -334,6 +335,7 @@ class NotificationHandler { std::unique_ptr m_prev_dict; Dictionary::CBFunc m_cb; }; +} // namespace NotificationToken Dictionary::add_key_based_notification_callback(CBFunc cb, KeyPathArray key_path_array) & { diff --git a/src/realm/object-store/dictionary.hpp b/src/realm/object-store/dictionary.hpp index be4004b7e12..edda0421bb6 100644 --- a/src/realm/object-store/dictionary.hpp +++ b/src/realm/object-store/dictionary.hpp @@ -124,6 +124,11 @@ class Dictionary : public object_store::Collection { Iterator end() const; private: + const char* type_name() const noexcept override + { + return "Dictionary"; + } + realm::Dictionary& dict() const noexcept { REALM_ASSERT_DEBUG(dynamic_cast(m_coll_base.get())); diff --git a/src/realm/object-store/impl/collection_notifier.cpp b/src/realm/object-store/impl/collection_notifier.cpp index 5b4a171436c..d446f4b06b3 100644 --- a/src/realm/object-store/impl/collection_notifier.cpp +++ b/src/realm/object-store/impl/collection_notifier.cpp @@ -211,7 +211,10 @@ void CollectionNotifier::suppress_next_notification(uint64_t token) std::lock_guard lock(m_realm_mutex); REALM_ASSERT(m_realm); m_realm->verify_thread(); - m_realm->verify_in_write(); + if (!m_realm->is_in_transaction()) { + throw WrongTransactionState("Suppressing the notification from a write transaction must be done from " + "inside the write transaction."); + } } util::CheckedLockGuard lock(m_callback_mutex); diff --git a/src/realm/object-store/impl/list_notifier.hpp b/src/realm/object-store/impl/list_notifier.hpp index b70b496bebb..44149851150 100644 --- a/src/realm/object-store/impl/list_notifier.hpp +++ b/src/realm/object-store/impl/list_notifier.hpp @@ -25,8 +25,8 @@ #include -namespace realm { -namespace _impl { +namespace realm::_impl { +// Despite the name, this also supports Set and index-based notifications on Dictionary class ListNotifier : public CollectionNotifier { public: ListNotifier(std::shared_ptr realm, CollectionBase const& list, PropertyType type); @@ -39,8 +39,8 @@ class ListNotifier : public CollectionNotifier { ColKey m_col; ObjKey m_obj; - // The last-seen size of the LinkView so that we can report row deletions - // when the LinkView itself is deleted + // The last-seen size of the collection so that we can report row deletions + // when the collection itself is deleted size_t m_prev_size; TransactionChangeInfo* m_info; @@ -52,7 +52,6 @@ class ListNotifier : public CollectionNotifier { void release_data() noexcept override; bool do_add_required_change_info(TransactionChangeInfo& info) override; }; -} // namespace _impl -} // namespace realm +} // namespace realm::_impl #endif // REALM_LIST_NOTIFIER_HPP diff --git a/src/realm/object-store/list.hpp b/src/realm/object-store/list.hpp index 1f12b2c43a4..020f3414fee 100644 --- a/src/realm/object-store/list.hpp +++ b/src/realm/object-store/list.hpp @@ -111,6 +111,11 @@ class List : public object_store::Collection { void assign(Context&, T&& value, CreatePolicy = CreatePolicy::SetLink); private: + const char* type_name() const noexcept override + { + return "List"; + } + LstBase& list_base() const noexcept { REALM_ASSERT_DEBUG(dynamic_cast(m_coll_base.get())); diff --git a/src/realm/object-store/object_accessor.hpp b/src/realm/object-store/object_accessor.hpp index 2283b08a9bd..ed3037ac007 100644 --- a/src/realm/object-store/object_accessor.hpp +++ b/src/realm/object-store/object_accessor.hpp @@ -83,7 +83,7 @@ struct ValueUpdater { policy2.create = false; auto link = child_ctx.template unbox(value, policy2); if (!policy.copy && link && link.get_table()->is_embedded()) - throw std::logic_error("Cannot set a link to an existing managed embedded object"); + throw InvalidArgument("Cannot set a link to an existing managed embedded object"); ObjKey curr_link; if (policy.diff) @@ -391,7 +391,8 @@ Object Object::get_for_primary_key(ContextType& ctx, std::shared_ptr cons if (!table) return Object(realm, object_schema, Obj()); if (ctx.is_null(primary_value) && !is_nullable(primary_prop->type)) - throw std::logic_error("Invalid null value for non-nullable primary key."); + throw NotNullable(util::format("Invalid null value for non-nullable primary key '%1.%2'.", object_schema.name, + primary_prop->name)); auto primary_key_value = switch_on_type(primary_prop->type, [&](auto* t) { return Mixed(ctx.template unbox>(primary_value)); @@ -406,7 +407,8 @@ ObjKey Object::get_for_primary_key_in_migration(ContextType& ctx, Table const& t { bool is_null = ctx.is_null(primary_value); if (is_null && !is_nullable(primary_prop.type)) - throw std::logic_error("Invalid null value for non-nullable primary key."); + throw NotNullable(util::format("Invalid null value for non-nullable primary key '%1.%2'.", + table.get_class_name(), primary_prop.name)); if (primary_prop.type == PropertyType::String) { return table.find_first(primary_prop.column_key, ctx.template unbox(primary_value)); } diff --git a/src/realm/object-store/object_schema.cpp b/src/realm/object-store/object_schema.cpp index 512f47eb521..aca79449a58 100644 --- a/src/realm/object-store/object_schema.cpp +++ b/src/realm/object-store/object_schema.cpp @@ -448,4 +448,17 @@ bool operator==(ObjectSchema const& a, ObjectSchema const& b) noexcept return std::tie(a.name, a.table_type, a.primary_key, a.persisted_properties, a.computed_properties) == std::tie(b.name, b.table_type, b.primary_key, b.persisted_properties, b.computed_properties); } + +std::ostream& operator<<(std::ostream& o, ObjectSchema::ObjectType table_type) +{ + switch (table_type) { + case ObjectSchema::ObjectType::TopLevel: + return o << "TopLevel"; + case ObjectSchema::ObjectType::Embedded: + return o << "Embedded"; + case ObjectSchema::ObjectType::TopLevelAsymmetric: + return o << "TopLevelAsymmetric"; + } + return o << "Invalid table type: " << uint8_t(table_type); +} } // namespace realm diff --git a/src/realm/object-store/object_schema.hpp b/src/realm/object-store/object_schema.hpp index 4486cfb7b2e..aef4692aada 100644 --- a/src/realm/object-store/object_schema.hpp +++ b/src/realm/object-store/object_schema.hpp @@ -93,18 +93,7 @@ class ObjectSchema { void set_primary_key_property() noexcept; }; -inline std::ostream& operator<<(std::ostream& o, ObjectSchema::ObjectType table_type) -{ - switch (table_type) { - case ObjectSchema::ObjectType::TopLevel: - return o << "TopLevel"; - case ObjectSchema::ObjectType::Embedded: - return o << "Embedded"; - case ObjectSchema::ObjectType::TopLevelAsymmetric: - return o << "TopLevelAsymmetric"; - } - return o << "Invalid table type: " << uint8_t(table_type); -} +std::ostream& operator<<(std::ostream& o, ObjectSchema::ObjectType table_type); } // namespace realm diff --git a/src/realm/object-store/results.cpp b/src/realm/object-store/results.cpp index 55c4cdcf8a3..af4d6adb98b 100644 --- a/src/realm/object-store/results.cpp +++ b/src/realm/object-store/results.cpp @@ -31,19 +31,14 @@ #include namespace realm { - -static std::string unsupported_operation_msg(ColKey column, Table const& table, const char* operation) +[[noreturn]] static void unsupported_operation(ColKey column, Table const& table, const char* operation) { auto type = ObjectSchema::from_core_type(column); + std::string_view collection_type = column.is_collection() ? collection_type_name(column) : "property"; const char* column_type = string_for_property_type(type & ~PropertyType::Collection); - if (is_array(type)) - return util::format("Cannot %1 '%2' array: operation not supported", operation, column_type); - if (is_set(type)) - return util::format("Cannot %1 '%2' set: operation not supported", operation, column_type); - if (is_dictionary(type)) - return util::format("Cannot %1 '%2' dictionary: operation not supported", operation, column_type); - return util::format("Cannot %1 property '%2': operation not supported for '%3' properties", operation, - table.get_column_name(column), column_type); + throw IllegalOperation(util::format("Operation '%1' not supported for %2%3 %4 '%5.%6'", operation, column_type, + column.is_nullable() ? "?" : "", collection_type, table.get_class_name(), + table.get_column_name(column))); } Results::Results() = default; @@ -562,7 +557,6 @@ size_t Results::index_of(Query&& q) return row ? index_of(const_cast(*m_table).get_object(row)) : not_found; } - namespace { struct CollectionAggregateAdaptor { const CollectionBase& list; @@ -583,6 +577,23 @@ struct CollectionAggregateAdaptor { return list.avg(); } }; + +struct ReturnIndexHelper { + ObjKey key; + size_t index = npos; + operator ObjKey*() + { + return &key; + } + operator size_t*() + { + return &index; + } + operator bool() + { + return key || index != npos; + } +}; } // anonymous namespace template @@ -624,11 +635,10 @@ util::Optional Results::aggregate(ColKey column, const char* name, Aggreg // which is the collection if it's not a link collection and the target // of the links otherwise if (m_mode == Mode::Collection && do_get_type() != PropertyType::Object) { - throw IllegalOperation{ - unsupported_operation_msg(m_collection->get_col_key(), *m_collection->get_table(), name)}; + unsupported_operation(m_collection->get_col_key(), *m_collection->get_table(), name); } else { - throw IllegalOperation{unsupported_operation_msg(column, *m_table, name)}; + unsupported_operation(column, *m_table, name); } } @@ -655,7 +665,7 @@ util::Optional Results::sum(ColKey column) util::Optional Results::average(ColKey column) { - return aggregate(column, "avg", [column](auto&& helper) { + return aggregate(column, "average", [column](auto&& helper) { return helper.avg(column); }); } @@ -809,7 +819,7 @@ static std::vector parse_keypath(StringData keypath, Schema const& schem { auto check = [&](bool condition, const char* fmt, auto... args) { if (!condition) { - throw std::invalid_argument( + throw InvalidArgument( util::format("Cannot sort on key path '%1': %2.", keypath, util::format(fmt, args...))); } }; @@ -853,10 +863,10 @@ Results Results::sort(std::vector> const& keypaths) auto type = get_type(); if (type != PropertyType::Object) { if (keypaths.size() != 1) - throw std::invalid_argument(util::format("Cannot sort array of '%1' on more than one key path", - string_for_property_type(type & ~PropertyType::Flags))); + throw InvalidArgument(util::format("Cannot sort array of '%1' on more than one key path", + string_for_property_type(type & ~PropertyType::Flags))); if (keypaths[0].first != "self") - throw std::invalid_argument( + throw InvalidArgument( util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'", keypaths[0].first, string_for_property_type(type & ~PropertyType::Flags))); return sort({{{}}, {keypaths[0].second}}); @@ -928,10 +938,10 @@ Results Results::distinct(std::vector const& keypaths) const auto type = get_type(); if (type != PropertyType::Object) { if (keypaths.size() != 1) - throw std::invalid_argument(util::format("Cannot sort array of '%1' on more than one key path", - string_for_property_type(type & ~PropertyType::Flags))); + throw InvalidArgument(util::format("Cannot sort array of '%1' on more than one key path", + string_for_property_type(type & ~PropertyType::Flags))); if (keypaths[0] != "self") - throw std::invalid_argument( + throw InvalidArgument( util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'", keypaths[0], string_for_property_type(type & ~PropertyType::Flags))); return distinct(DistinctDescriptor({{ColKey()}})); @@ -1000,7 +1010,8 @@ void Results::prepare_async(ForCallback force) NO_THREAD_SAFETY_ANALYSIS return; if (m_update_policy == UpdatePolicy::Never) { if (force) - throw std::logic_error("Cannot create asynchronous query for snapshotted Results."); + throw LogicError(ErrorCodes::IllegalOperation, + "Cannot create asynchronous query for snapshotted Results."); return; } diff --git a/src/realm/object-store/sectioned_results.cpp b/src/realm/object-store/sectioned_results.cpp index 133c40016bf..87d29bd3050 100644 --- a/src/realm/object-store/sectioned_results.cpp +++ b/src/realm/object-store/sectioned_results.cpp @@ -16,8 +16,8 @@ // //////////////////////////////////////////////////////////////////////////// -#include #include + #include namespace realm { @@ -325,16 +325,16 @@ void SectionedResults::calculate_sections_if_required() { if (m_results.m_update_policy == Results::UpdatePolicy::Never) return; - else if ((m_results.is_frozen() || !m_results.has_changed()) && has_performed_initial_evalutation) + if ((m_results.is_frozen() || !m_results.has_changed()) && has_performed_initial_evalutation) return; + { util::CheckedUniqueLock lock(m_results.m_mutex); m_results.ensure_up_to_date(); } calculate_sections(); - if (!has_performed_initial_evalutation) - has_performed_initial_evalutation = true; + has_performed_initial_evalutation = true; } template @@ -373,7 +373,7 @@ void SectionedResults::calculate_sections() // Disallow links as section keys. It would be uncommon to use them to begin with // and if the object acting as the key was deleted bad things would happen. if (key.is_type(type_Link, type_TypedLink)) { - throw std::logic_error("Links are not supported as section keys."); + throw InvalidArgument("Links are not supported as section keys."); } auto it = m_sections.find(key); @@ -425,11 +425,11 @@ ResultsSection SectionedResults::operator[](Mixed key) util::CheckedUniqueLock lock(m_mutex); check_valid(); calculate_sections_if_required(); - auto it = m_sections.find(key); - if (it == m_sections.end()) { - throw std::logic_error("Key does not exist for any sections."); + if (auto it = m_sections.find(key); it != m_sections.end()) { + return ResultsSection(this, it->second.key); } - return ResultsSection(this, it->second.key); + + throw InvalidArgument(util::format("Section key %1 not found.", key)); } NotificationToken SectionedResults::add_notification_callback(SectionedResultsNotificatonCallback callback, diff --git a/src/realm/object-store/sectioned_results.hpp b/src/realm/object-store/sectioned_results.hpp index ffd24ed4fe5..d1ac4b2b9bc 100644 --- a/src/realm/object-store/sectioned_results.hpp +++ b/src/realm/object-store/sectioned_results.hpp @@ -19,7 +19,7 @@ #ifndef REALM_SECTIONED_RESULTS_HPP #define REALM_SECTIONED_RESULTS_HPP -#include +#include #include @@ -105,7 +105,7 @@ class ResultsSection { class SectionedResults { public: SectionedResults() = default; - using SectionKeyFunc = util::UniqueFunction; + using SectionKeyFunc = util::UniqueFunction realm)>; /** * Returns a `ResultsSection` which will be bound to a section key present at the given index in diff --git a/src/realm/object-store/set.hpp b/src/realm/object-store/set.hpp index edcb59faa0e..0de1a9b0e82 100644 --- a/src/realm/object-store/set.hpp +++ b/src/realm/object-store/set.hpp @@ -113,6 +113,11 @@ class Set : public Collection { }; private: + const char* type_name() const noexcept override + { + return "Set"; + } + SetBase& set_base() const noexcept { REALM_ASSERT_DEBUG(dynamic_cast(m_coll_base.get())); diff --git a/src/realm/object-store/shared_realm.cpp b/src/realm/object-store/shared_realm.cpp index 92a8713827c..112f32e99bc 100644 --- a/src/realm/object-store/shared_realm.cpp +++ b/src/realm/object-store/shared_realm.cpp @@ -189,26 +189,26 @@ std::shared_ptr Realm::get_synchronized_realm(Config config) std::shared_ptr Realm::sync_session() const { - return m_coordinator->sync_session(); + return m_coordinator ? m_coordinator->sync_session() : nullptr; } sync::SubscriptionSet Realm::get_latest_subscription_set() { + verify_open(); // If there is a subscription store, then return the latest set if (auto flx_sub_store = m_coordinator->sync_session()->get_flx_subscription_store()) { return flx_sub_store->get_latest(); } - // Otherwise, throw runtime_error throw std::runtime_error("Flexible sync is not enabled"); } sync::SubscriptionSet Realm::get_active_subscription_set() { + verify_open(); // If there is a subscription store, then return the active set if (auto flx_sub_store = m_coordinator->sync_session()->get_flx_subscription_store()) { return flx_sub_store->get_active(); } - // Otherwise, throw runtime_error throw std::runtime_error("Flexible sync is not enabled"); } #endif @@ -352,6 +352,8 @@ Schema Realm::get_full_schema() void Realm::set_schema_subset(Schema schema) { + verify_thread(); + verify_open(); REALM_ASSERT(m_dynamic_schema); REALM_ASSERT(m_schema_version != ObjectStore::NotVersioned); @@ -556,6 +558,8 @@ void Realm::notify_schema_changed() static void check_can_create_write_transaction(const Realm* realm) { + realm->verify_thread(); + realm->verify_open(); if (realm->config().immutable() || realm->config().read_only()) { throw WrongTransactionState("Can't perform transactions on read-only Realms."); } @@ -593,7 +597,8 @@ bool Realm::verify_notifications_available(bool throw_on_error) const { if (is_frozen()) { if (throw_on_error) - throw WrongTransactionState("Notifications are not available on frozen lists since they do not change."); + throw WrongTransactionState( + "Notifications are not available on frozen collections since they do not change."); return false; } if (config().immutable()) { @@ -659,11 +664,13 @@ util::Optional Realm::latest_snapshot_version() const void Realm::enable_wait_for_change() { + verify_open(); m_coordinator->enable_wait_for_change(); } bool Realm::wait_for_change() { + verify_open(); if (m_frozen_version) { return false; } @@ -672,6 +679,7 @@ bool Realm::wait_for_change() void Realm::wait_for_change_release() { + verify_open(); m_coordinator->wait_for_change_release(); } @@ -833,7 +841,6 @@ void Realm::run_writes() auto Realm::async_begin_transaction(util::UniqueFunction&& the_write_block, bool notify_only) -> AsyncHandle { - verify_thread(); check_can_create_write_transaction(this); if (m_is_running_async_commit_completions) { throw WrongTransactionState("Can't begin a write transaction from inside a commit completion callback."); @@ -859,7 +866,7 @@ auto Realm::async_begin_transaction(util::UniqueFunction&& the_write_blo auto Realm::async_commit_transaction(util::UniqueFunction&& completion, bool allow_grouping) -> AsyncHandle { - verify_thread(); + check_can_create_write_transaction(this); if (m_is_running_async_commit_completions) { throw WrongTransactionState("Can't commit a write transaction from inside a commit completion callback."); } @@ -922,6 +929,7 @@ auto Realm::async_commit_transaction(util::UniqueFunctionflx_sync_requested) { - throw std::logic_error("Realm cannot be converted if flexible sync is enabled"); + throw IllegalOperation("Cannot convert Realms to flexible sync Realms"); } #endif @@ -1205,7 +1211,7 @@ bool Realm::do_refresh() } if (m_config.immutable()) { - throw std::logic_error("Can't refresh a read-only Realm."); + throw WrongTransactionState("Can't refresh an immutable Realm."); } // can't be any new changes if we're in a write transaction @@ -1248,7 +1254,7 @@ bool Realm::do_refresh() void Realm::set_auto_refresh(bool auto_refresh) { if (is_frozen() && auto_refresh) { - throw std::logic_error("Auto-refresh cannot be enabled for frozen Realms."); + throw WrongTransactionState("Auto-refresh cannot be enabled for frozen Realms."); } m_auto_refresh = auto_refresh; } @@ -1318,9 +1324,10 @@ void Realm::delete_files(const std::string& realm_file_path, bool* did_delete_re DB::delete_files(path, did_delete_realm); }); if (!lock_successful) { - throw LogicError( + throw FileAccessError( ErrorCodes::DeleteOnOpenRealm, - util::format("Cannot delete files of an open Realm: '%1' is still in use.", realm_file_path)); + util::format("Cannot delete files of an open Realm: '%1' is still in use.", realm_file_path), + realm_file_path); } } catch (const FileAccessError& e) { diff --git a/src/realm/object-store/shared_realm.hpp b/src/realm/object-store/shared_realm.hpp index 9febd6da484..c161ebca7d9 100644 --- a/src/realm/object-store/shared_realm.hpp +++ b/src/realm/object-store/shared_realm.hpp @@ -202,9 +202,8 @@ class Realm : public std::enable_shared_from_this { std::shared_ptr sync_session() const; - // Returns the latest/active subscription set for a FLX-sync enabled realm. If FLX sync is not currently - // enabled for this realm, calling this will cause future connections to the server to be opened in FLX - // sync mode if they aren't already. + // Returns the latest/active subscription set for a FLX-sync enabled realm. + // Throws an exception for a non-FLX realm sync::SubscriptionSet get_latest_subscription_set(); sync::SubscriptionSet get_active_subscription_set(); #endif diff --git a/src/realm/object-store/sync/app.cpp b/src/realm/object-store/sync/app.cpp index 9eb9d601c4e..581cf7bab1b 100644 --- a/src/realm/object-store/sync/app.cpp +++ b/src/realm/object-store/sync/app.cpp @@ -233,15 +233,15 @@ App::App(const Config& config) REALM_ASSERT(m_config.transport); if (m_config.platform.empty()) { - throw std::runtime_error("You must specify the Platform in App::Config"); + throw InvalidArgument("You must specify the Platform in App::Config"); } if (m_config.platform_version.empty()) { - throw std::runtime_error("You must specify the Platform Version in App::Config"); + throw InvalidArgument("You must specify the Platform Version in App::Config"); } if (m_config.sdk_version.empty()) { - throw std::runtime_error("You must specify the SDK Version in App::Config"); + throw InvalidArgument("You must specify the SDK Version in App::Config"); } // change the scheme in the base url to ws from http to satisfy the sync client diff --git a/src/realm/object-store/sync/app_credentials.cpp b/src/realm/object-store/sync/app_credentials.cpp index 32a508dabcd..c16c797e4b1 100644 --- a/src/realm/object-store/sync/app_credentials.cpp +++ b/src/realm/object-store/sync/app_credentials.cpp @@ -56,7 +56,7 @@ IdentityProvider provider_type_from_enum(AuthProvider provider) case AuthProvider::SERVER_API_KEY: return IdentityProviderServerAPIKey; } - throw std::runtime_error("unknown provider type in provider_type_from_enum"); + throw InvalidArgument("unknown provider type in provider_type_from_enum"); } AuthProvider enum_from_provider_type(const IdentityProvider& provider) diff --git a/src/realm/object-store/sync/impl/sync_metadata.cpp b/src/realm/object-store/sync/impl/sync_metadata.cpp index 3ccec9f3a27..c32040aac14 100644 --- a/src/realm/object-store/sync/impl/sync_metadata.cpp +++ b/src/realm/object-store/sync/impl/sync_metadata.cpp @@ -122,7 +122,7 @@ SyncMetadataManager::SyncMetadataManager(std::string path, bool should_encrypt, constexpr uint64_t SCHEMA_VERSION = 6; if (!REALM_PLATFORM_APPLE && should_encrypt && !encryption_key) - throw std::invalid_argument("Metadata Realm encryption was specified, but no encryption key was provided."); + throw InvalidArgument("Metadata Realm encryption was specified, but no encryption key was provided."); m_metadata_config.automatic_change_notifications = false; m_metadata_config.path = path; diff --git a/src/realm/object-store/thread_safe_reference.cpp b/src/realm/object-store/thread_safe_reference.cpp index 69f735034c3..7249c66e2f0 100644 --- a/src/realm/object-store/thread_safe_reference.cpp +++ b/src/realm/object-store/thread_safe_reference.cpp @@ -162,7 +162,7 @@ class ThreadSafeReference::PayloadImpl : public ThreadSafeReference::Pa // the current write transaction then the collection cannot be // handed over and would just be empty when resolved. if (q.view_owner_obj_key() != m_query->view_owner_obj_key()) { - throw std::logic_error( + throw WrongTransactionState( "Cannot create a ThreadSafeReference to Results backed by a collection of objects " "inside the write transaction which created the collection."); } diff --git a/src/realm/object-store/util/apple/scheduler.hpp b/src/realm/object-store/util/apple/scheduler.hpp index 7d4b59b7618..023c24617f2 100644 --- a/src/realm/object-store/util/apple/scheduler.hpp +++ b/src/realm/object-store/util/apple/scheduler.hpp @@ -17,6 +17,8 @@ //////////////////////////////////////////////////////////////////////////// #include + +#include #include #include @@ -158,7 +160,7 @@ DispatchQueueScheduler::DispatchQueueScheduler(dispatch_queue_t queue) auto msg = util::format( "Invalid queue '%1' (%2): Realms can only be confined to serial queues or the main queue.", dispatch_queue_get_label(queue) ?: "", class_getName(cls)); - throw std::logic_error(msg); + throw InvalidArgument(msg); } } dispatch_retain(m_queue); diff --git a/src/realm/replication.cpp b/src/realm/replication.cpp index b8804014c18..8cc151f244d 100644 --- a/src/realm/replication.cpp +++ b/src/realm/replication.cpp @@ -23,6 +23,24 @@ using namespace realm; using namespace realm::util; +const char* Replication::history_type_name(int type) +{ + switch (type) { + case hist_None: + return "None"; + case hist_OutOfRealm: + return "Local out of Realm"; + case hist_InRealm: + return "Local in-Realm"; + case hist_SyncClient: + return "SyncClient"; + case hist_SyncServer: + return "SyncServer"; + default: + return "Unknown"; + } +} + void Replication::initialize(DB&) { // Nothing needs to be done here diff --git a/src/realm/replication.hpp b/src/realm/replication.hpp index 6fc619392f5..02c09261fda 100644 --- a/src/realm/replication.hpp +++ b/src/realm/replication.hpp @@ -235,6 +235,8 @@ class Replication { hist_SyncServer = 4 }; + static const char* history_type_name(int); + /// Returns the type of history maintained by this Replication /// implementation, or \ref hist_None if no history is maintained by it. /// diff --git a/src/realm/set.hpp b/src/realm/set.hpp index 6bd3eae0d0e..b93a547c632 100644 --- a/src/realm/set.hpp +++ b/src/realm/set.hpp @@ -69,9 +69,7 @@ class Set final : public CollectionBaseImpl> { T get(size_t ndx) const { const auto current_size = size(); - if (ndx >= current_size) { - throw OutOfBounds(util::format("get() on %1", CollectionBase::get_property_name()), ndx, current_size); - } + CollectionBase::validate_index("get()", ndx, current_size); return m_tree->get(ndx); } diff --git a/src/realm/sort_descriptor.cpp b/src/realm/sort_descriptor.cpp index f221126a970..95eea93df21 100644 --- a/src/realm/sort_descriptor.cpp +++ b/src/realm/sort_descriptor.cpp @@ -445,7 +445,10 @@ DescriptorType DescriptorOrdering::get_type(size_t index) const const BaseDescriptor* DescriptorOrdering::operator[](size_t ndx) const { - return m_descriptors.at(ndx).get(); // may throw std::out_of_range + if (ndx >= m_descriptors.size()) { + throw OutOfBounds("DescriptorOrdering[]", ndx, m_descriptors.size()); + } + return m_descriptors[ndx].get(); } bool DescriptorOrdering::will_apply_sort() const diff --git a/src/realm/string_data.hpp b/src/realm/string_data.hpp index 2ae547d219d..a93b0d867d5 100644 --- a/src/realm/string_data.hpp +++ b/src/realm/string_data.hpp @@ -84,32 +84,32 @@ uint_least64_t cityhash_64(const unsigned char* data, size_t len) noexcept; class StringData { public: /// Construct a null reference. - StringData() noexcept; + constexpr StringData() noexcept = default; /// If \a external_data is 'null', \a data_size must be zero. - StringData(const char* external_data, size_t data_size) noexcept; + constexpr StringData(const char* external_data, size_t data_size) noexcept; template - StringData(const std::basic_string&); + constexpr StringData(const std::basic_string&); template operator std::basic_string() const; template - StringData(const util::Optional>&); + constexpr StringData(const util::Optional>&); - StringData(std::string_view sv); + constexpr StringData(std::string_view sv); - StringData(const null&) noexcept; + constexpr StringData(const null&) noexcept {} /// Initialize from a zero terminated C style string. Pass null to construct /// a null reference. - StringData(const char* c_str) noexcept; + constexpr StringData(const char* c_str) noexcept; - char operator[](size_t i) const noexcept; + constexpr char operator[](size_t i) const noexcept; - const char* data() const noexcept; - size_t size() const noexcept; + constexpr const char* data() const noexcept; + constexpr size_t size() const noexcept; /// Is this a null reference? /// @@ -127,10 +127,10 @@ class StringData { /// of the result of calling this function. In other words, a StringData /// object is converted to true if it is not the null reference, otherwise /// it is converted to false. - bool is_null() const noexcept; + constexpr bool is_null() const noexcept; - friend bool operator==(const StringData&, const StringData&) noexcept; - friend bool operator!=(const StringData&, const StringData&) noexcept; + friend constexpr bool operator==(const StringData&, const StringData&) noexcept; + friend constexpr bool operator!=(const StringData&, const StringData&) noexcept; //@{ /// Trivial bytewise lexicographical comparison. @@ -140,9 +140,9 @@ class StringData { friend bool operator>=(const StringData&, const StringData&) noexcept; //@} - bool begins_with(StringData) const noexcept; - bool ends_with(StringData) const noexcept; - bool contains(StringData) const noexcept; + constexpr bool begins_with(StringData) const noexcept; + constexpr bool ends_with(StringData) const noexcept; + constexpr bool contains(StringData) const noexcept; bool contains(StringData d, const std::array &charmap) const noexcept; // Wildcard matching ('?' for single char, '*' for zero or more chars) @@ -152,17 +152,17 @@ class StringData { //@{ /// Undefined behavior if \a n, \a i, or i+n is greater than /// size(). - StringData prefix(size_t n) const noexcept; - StringData suffix(size_t n) const noexcept; - StringData substr(size_t i, size_t n) const noexcept; - StringData substr(size_t i) const noexcept; + constexpr StringData prefix(size_t n) const noexcept; + constexpr StringData suffix(size_t n) const noexcept; + constexpr StringData substr(size_t i, size_t n) const noexcept; + constexpr StringData substr(size_t i) const noexcept; //@} template friend std::basic_ostream& operator<<(std::basic_ostream&, const StringData&); - explicit operator bool() const noexcept; - explicit operator std::string_view() const noexcept + constexpr explicit operator bool() const noexcept; + constexpr explicit operator std::string_view() const noexcept { return std::string_view(m_data, m_size); } @@ -172,8 +172,8 @@ class StringData { size_t hash() const noexcept; private: - const char* m_data; - size_t m_size; + const char* m_data = nullptr; + size_t m_size = 0; static bool matchlike(const StringData& text, const StringData& pattern) noexcept; static bool matchlike_ins(const StringData& text, const StringData& pattern_upper, @@ -186,27 +186,21 @@ class StringData { // Implementation: -inline StringData::StringData() noexcept - : m_data(nullptr) - , m_size(0) -{ -} - -inline StringData::StringData(const char* external_data, size_t data_size) noexcept +constexpr inline StringData::StringData(const char* external_data, size_t data_size) noexcept : m_data(external_data) , m_size(data_size) { REALM_ASSERT_DEBUG(external_data || data_size == 0); } -inline StringData::StringData(std::string_view sv) +constexpr inline StringData::StringData(std::string_view sv) : m_data(sv.data()) , m_size(sv.size()) { } template -inline StringData::StringData(const std::basic_string& s) +constexpr inline StringData::StringData(const std::basic_string& s) : m_data(s.data()) , m_size(s.size()) { @@ -219,52 +213,45 @@ inline StringData::operator std::basic_string() const } template -inline StringData::StringData(const util::Optional>& s) +constexpr inline StringData::StringData(const util::Optional>& s) : m_data(s ? s->data() : nullptr) , m_size(s ? s->size() : 0) { } -inline StringData::StringData(const null&) noexcept - : m_data(nullptr) - , m_size(0) -{ -} - -inline StringData::StringData(const char* c_str) noexcept +constexpr inline StringData::StringData(const char* c_str) noexcept : m_data(c_str) - , m_size(0) { if (c_str) m_size = std::char_traits::length(c_str); } -inline char StringData::operator[](size_t i) const noexcept +constexpr inline char StringData::operator[](size_t i) const noexcept { return m_data[i]; } -inline const char* StringData::data() const noexcept +constexpr inline const char* StringData::data() const noexcept { return m_data; } -inline size_t StringData::size() const noexcept +constexpr inline size_t StringData::size() const noexcept { return m_size; } -inline bool StringData::is_null() const noexcept +constexpr inline bool StringData::is_null() const noexcept { return !m_data; } -inline bool operator==(const StringData& a, const StringData& b) noexcept +constexpr inline bool operator==(const StringData& a, const StringData& b) noexcept { return a.m_size == b.m_size && a.is_null() == b.is_null() && safe_equal(a.m_data, a.m_data + a.m_size, b.m_data); } -inline bool operator!=(const StringData& a, const StringData& b) noexcept +constexpr inline bool operator!=(const StringData& a, const StringData& b) noexcept { return !(a == b); } @@ -294,21 +281,21 @@ inline bool operator>=(const StringData& a, const StringData& b) noexcept return !(a < b); } -inline bool StringData::begins_with(StringData d) const noexcept +constexpr inline bool StringData::begins_with(StringData d) const noexcept { if (is_null() && !d.is_null()) return false; return d.m_size <= m_size && safe_equal(m_data, m_data + d.m_size, d.m_data); } -inline bool StringData::ends_with(StringData d) const noexcept +constexpr inline bool StringData::ends_with(StringData d) const noexcept { if (is_null() && !d.is_null()) return false; return d.m_size <= m_size && safe_equal(m_data + m_size - d.m_size, m_data + m_size, d.m_data); } -inline bool StringData::contains(StringData d) const noexcept +constexpr inline bool StringData::contains(StringData d) const noexcept { if (is_null() && !d.is_null()) return false; @@ -362,22 +349,22 @@ inline bool StringData::like(StringData d) const noexcept return matchlike(*this, d); } -inline StringData StringData::prefix(size_t n) const noexcept +constexpr inline StringData StringData::prefix(size_t n) const noexcept { return substr(0, n); } -inline StringData StringData::suffix(size_t n) const noexcept +constexpr inline StringData StringData::suffix(size_t n) const noexcept { return substr(m_size - n); } -inline StringData StringData::substr(size_t i, size_t n) const noexcept +constexpr inline StringData StringData::substr(size_t i, size_t n) const noexcept { return StringData(m_data + i, n); } -inline StringData StringData::substr(size_t i) const noexcept +constexpr inline StringData StringData::substr(size_t i) const noexcept { return substr(i, m_size - i); } @@ -395,7 +382,7 @@ inline std::basic_ostream& operator<<(std::basic_ostream& out, const return out; } -inline StringData::operator bool() const noexcept +constexpr inline StringData::operator bool() const noexcept { return !is_null(); } diff --git a/src/realm/table.cpp b/src/realm/table.cpp index 86dbb92d693..fe480e43d4c 100644 --- a/src/realm/table.cpp +++ b/src/realm/table.cpp @@ -316,6 +316,19 @@ const char* get_data_type_name(DataType type) noexcept } return "unknown"; } + +std::ostream& operator<<(std::ostream& o, Table::Type table_type) +{ + switch (table_type) { + case Table::Type::TopLevel: + return o << "TopLevel"; + case Table::Type::Embedded: + return o << "Embedded"; + case Table::Type::TopLevelAsymmetric: + return o << "TopLevelAsymmetric"; + } + return o << "Invalid table type: " << uint8_t(table_type); +} } // namespace realm void LinkChain::add(ColKey ck) @@ -328,8 +341,9 @@ void LinkChain::add(ColKey ck) } else { // Only last column in link chain is allowed to be non-link - throw std::runtime_error(util::format("%1.%2 is not an object reference property", - m_current_table->get_name(), m_current_table->get_column_name(ck))); + throw LogicError(ErrorCodes::TypeMismatch, + util::format("%1.%2 is not an object reference property", m_current_table->get_class_name(), + m_current_table->get_column_name(ck))); } m_link_cols.push_back(ck); } @@ -1064,7 +1078,8 @@ void Table::set_table_type(Type table_type) } if (m_table_type == Type::TopLevelAsymmetric || table_type == Type::TopLevelAsymmetric) { - throw std::logic_error(util::format("Cannot change '%1' to/from asymmetric.", get_name())); + throw LogicError(ErrorCodes::MigrationFailed, util::format("Cannot change '%1' from %2 to %3", + get_class_name(), m_table_type, table_type)); } REALM_ASSERT_EX(table_type == Type::TopLevel || table_type == Type::Embedded, table_type); @@ -1075,7 +1090,7 @@ void Table::set_embedded(bool embedded) { if (Replication* repl = get_repl()) { if (repl->get_history_type() == Replication::HistoryType::hist_SyncClient) { - throw IllegalOperation(util::format("Cannot change '%1' to embedded when using Sync.", get_name())); + throw IllegalOperation(util::format("Cannot change '%1' to embedded when using Sync.", get_class_name())); } } @@ -1086,7 +1101,8 @@ void Table::set_embedded(bool embedded) // Embedded objects cannot have a primary key. if (get_primary_key_column()) { - throw IllegalOperation(util::format("Cannot change '%1' to embedded when using a primary key.", get_name())); + throw IllegalOperation( + util::format("Cannot change '%1' to embedded when using a primary key.", get_class_name())); } // `has_backlink_columns` indicates if the table is embedded in any other table. @@ -1099,7 +1115,7 @@ void Table::set_embedded(bool embedded) throw IllegalOperation( util::format("Cannot change '%1' to embedded without backlink columns. Objects must be embedded in " "at least one other class.", - get_name())); + get_class_name())); } else if (size() > 0) { for (auto object : *this) { @@ -1115,21 +1131,22 @@ void Table::set_embedded(bool embedded) if (!forward_col_mapped) { throw IllegalOperation(util::format("There is a dynamic/untyped link from a Mixed property " "'%1.%2' which prevents migrating class '%3' to embedded", - source_table->get_name(), - source_table->get_column_name(source_col), get_name())); + source_table->get_class_name(), + source_table->get_column_name(source_col), + get_class_name())); } } backlink_count += cur_backlinks; if (backlink_count > 1) { throw IllegalOperation( - util::format("At least one object in '%1' has multiple backlinks.", get_name())); + util::format("At least one object in '%1' has multiple backlinks.", get_class_name())); } return IteratorControl::AdvanceToNext; // continue }); if (backlink_count == 0) { throw IllegalOperation(util::format( - "At least one object in '%1' does not have a backlink (data would get lost).", get_name())); + "At least one object in '%1' does not have a backlink (data would get lost).", get_class_name())); } } } @@ -1883,7 +1900,7 @@ StringData Table::get_name() const noexcept return static_cast(parent)->get_table_name(get_key()); } -std::string Table::get_class_name() const noexcept +StringData Table::get_class_name() const noexcept { return Group::table_name_to_class_name(get_name()); } diff --git a/src/realm/table.hpp b/src/realm/table.hpp index 56920605c58..dec492e2907 100644 --- a/src/realm/table.hpp +++ b/src/realm/table.hpp @@ -111,7 +111,7 @@ class Table { StringData get_name() const noexcept; // Get table name with class prefix removed - std::string get_class_name() const noexcept; + StringData get_class_name() const noexcept; const char* get_state() const noexcept; @@ -869,18 +869,7 @@ class Table { friend class AggregateHelper; }; -inline std::ostream& operator<<(std::ostream& o, Table::Type table_type) -{ - switch (table_type) { - case Table::Type::TopLevel: - return o << "TopLevel"; - case Table::Type::Embedded: - return o << "Embedded"; - case Table::Type::TopLevelAsymmetric: - return o << "TopLevelAsymmetric"; - } - return o << "Invalid table type: " << uint8_t(table_type); -} +std::ostream& operator<<(std::ostream& o, Table::Type table_type); class ColKeyIterator { public: @@ -992,7 +981,8 @@ class LinkChain { { auto ck = m_current_table->get_column_key(col_name); if (!ck) { - throw std::runtime_error(util::format("%1 has no property %2", m_current_table->get_name(), col_name)); + throw std::runtime_error( + util::format("%1 has no property %2", m_current_table->get_class_name(), col_name)); } add(ck); return *this; diff --git a/src/realm/timestamp.hpp b/src/realm/timestamp.hpp index c4c95acfc07..d2b24c3fa70 100644 --- a/src/realm/timestamp.hpp +++ b/src/realm/timestamp.hpp @@ -64,7 +64,7 @@ class Timestamp { // +1.1 seconds (1100 milliseconds after the epoch) is constructed by Timestamp(1, 100000000) // -1.1 seconds (1100 milliseconds before the epoch) is constructed by Timestamp(-1, -100000000) // - Timestamp(int64_t seconds, int32_t nanoseconds) + constexpr Timestamp(int64_t seconds, int32_t nanoseconds) : m_seconds(seconds) , m_nanoseconds(nanoseconds) , m_is_null(false) @@ -74,46 +74,40 @@ class Timestamp { const bool both_non_positive = seconds <= 0 && nanoseconds <= 0; REALM_ASSERT_EX(both_non_negative || both_non_positive, both_non_negative, both_non_positive); } - Timestamp(realm::null) - : m_is_null(true) - { - } + constexpr Timestamp() = default; + constexpr Timestamp(realm::null) {} + Timestamp(const Timestamp&) = default; + Timestamp& operator=(const Timestamp&) = default; template - Timestamp(std::chrono::time_point tp) + constexpr Timestamp(std::chrono::time_point tp) : m_is_null(false) { int64_t native_nano = std::chrono::duration_cast(tp.time_since_epoch()).count(); m_seconds = native_nano / nanoseconds_per_second; m_nanoseconds = static_cast(native_nano % nanoseconds_per_second); } - Timestamp() - : Timestamp(null{}) - { - } - - Timestamp& operator=(const Timestamp& rhs) = default; - bool is_null() const + constexpr bool is_null() const { return m_is_null; } - int64_t get_seconds() const noexcept + constexpr int64_t get_seconds() const noexcept { REALM_ASSERT(!m_is_null); return m_seconds; } - int32_t get_nanoseconds() const noexcept + constexpr int32_t get_nanoseconds() const noexcept { REALM_ASSERT(!m_is_null); return m_nanoseconds; } template - std::chrono::time_point get_time_point() const + constexpr std::chrono::time_point get_time_point() const { REALM_ASSERT(!m_is_null); @@ -124,12 +118,12 @@ class Timestamp { } template - explicit operator std::chrono::time_point() const + constexpr explicit operator std::chrono::time_point() const { return get_time_point(); } - bool operator==(const Timestamp& rhs) const + constexpr bool operator==(const Timestamp& rhs) const { if (is_null() && rhs.is_null()) return true; @@ -139,11 +133,11 @@ class Timestamp { return m_seconds == rhs.m_seconds && m_nanoseconds == rhs.m_nanoseconds; } - bool operator!=(const Timestamp& rhs) const + constexpr bool operator!=(const Timestamp& rhs) const { return !(*this == rhs); } - bool operator>(const Timestamp& rhs) const + constexpr bool operator>(const Timestamp& rhs) const { if (is_null()) { return false; @@ -153,7 +147,7 @@ class Timestamp { } return (m_seconds > rhs.m_seconds) || (m_seconds == rhs.m_seconds && m_nanoseconds > rhs.m_nanoseconds); } - bool operator<(const Timestamp& rhs) const + constexpr bool operator<(const Timestamp& rhs) const { if (rhs.is_null()) { return false; @@ -163,7 +157,7 @@ class Timestamp { } return (m_seconds < rhs.m_seconds) || (m_seconds == rhs.m_seconds && m_nanoseconds < rhs.m_nanoseconds); } - bool operator<=(const Timestamp& rhs) const + constexpr bool operator<=(const Timestamp& rhs) const { if (is_null()) { return true; @@ -173,7 +167,7 @@ class Timestamp { } return *this < rhs || *this == rhs; } - bool operator>=(const Timestamp& rhs) const + constexpr bool operator>=(const Timestamp& rhs) const { if (rhs.is_null()) { return true; @@ -184,16 +178,19 @@ class Timestamp { return *this > rhs || *this == rhs; } - size_t hash() const noexcept; + constexpr size_t hash() const noexcept + { + return size_t(m_seconds) ^ size_t(m_nanoseconds); + } template friend std::basic_ostream& operator<<(std::basic_ostream& out, const Timestamp&); static constexpr int32_t nanoseconds_per_second = 1000000000; private: - int64_t m_seconds; - int32_t m_nanoseconds; - bool m_is_null; + int64_t m_seconds = 0; + int32_t m_nanoseconds = 0; + bool m_is_null = true; }; // LCOV_EXCL_START @@ -224,26 +221,21 @@ inline std::basic_ostream& operator<<(std::basic_ostream& out, const } // LCOV_EXCL_STOP -inline size_t Timestamp::hash() const noexcept -{ - return size_t(m_seconds) ^ size_t(m_nanoseconds); -} - } // namespace realm namespace std { template <> struct numeric_limits { static constexpr bool is_integer = false; - static realm::Timestamp min() + static constexpr realm::Timestamp min() { return realm::Timestamp(numeric_limits::min(), 0); } - static realm::Timestamp lowest() + static constexpr realm::Timestamp lowest() { return realm::Timestamp(numeric_limits::lowest(), 0); } - static realm::Timestamp max() + static constexpr realm::Timestamp max() { return realm::Timestamp(numeric_limits::max(), 0); } diff --git a/src/realm/util/encrypted_file_mapping.cpp b/src/realm/util/encrypted_file_mapping.cpp index 673ad547990..438312a8f43 100644 --- a/src/realm/util/encrypted_file_mapping.cpp +++ b/src/realm/util/encrypted_file_mapping.cpp @@ -16,11 +16,15 @@ * **************************************************************************/ +#include + +#if REALM_ENABLE_ENCRYPTION #include +#include #include #include +#include -#if REALM_ENABLE_ENCRYPTION #include #include #include @@ -45,9 +49,6 @@ #include #endif -#include -#include - namespace realm { namespace util { @@ -643,10 +644,11 @@ void EncryptedFileMapping::reclaim_page(size_t page_ndx) void* addr = page_addr(page_ndx); void* addr2 = ::mmap(addr, 1 << m_page_shift, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); if (addr != addr2) { - if (addr2 == 0) - throw std::system_error(errno, std::system_category(), std::string("using mmap() to clear page failed")); - else - throw std::runtime_error("internal error in mmap()"); + if (addr2 == 0) { + int err = errno; + throw SystemError(err, get_errno_msg("using mmap() to clear page failed", err)); + } + throw std::runtime_error("internal error in mmap()"); } #endif } diff --git a/src/realm/util/fifo_helper.cpp b/src/realm/util/fifo_helper.cpp index 5c2e36d94a4..aa2e3fd25e8 100644 --- a/src/realm/util/fifo_helper.cpp +++ b/src/realm/util/fifo_helper.cpp @@ -18,13 +18,15 @@ #include +#include +#include + #include #include #include // FIFOs do not work on Windows. -namespace realm { -namespace util { +namespace realm::util { namespace { void check_is_fifo(const std::string& path) @@ -33,7 +35,7 @@ void check_is_fifo(const std::string& path) struct stat stat_buf; if (stat(path.c_str(), &stat_buf) == 0) { if ((stat_buf.st_mode & S_IFMT) != S_IFIFO) { - throw std::runtime_error(path + " exists and it is not a fifo."); + throw FileAccessError(ErrorCodes::FileAlreadyExists, path + " exists and is not a fifo.", path); } } #endif @@ -62,26 +64,20 @@ void create_fifo(std::string path) int ret = mkfifo(path.c_str(), mode); if (ret == -1) { int err = errno; - // the fifo already existing isn't an error - if (err != EEXIST) { #ifdef REALM_ANDROID - // Workaround for a mkfifo bug on Blackberry devices: - // When the fifo already exists, mkfifo fails with error ENOSYS which is not correct. - // In this case, we use stat to check if the path exists and it is a fifo. - if (err == ENOSYS) { - check_is_fifo(path); - } - else { - throw std::system_error(err, std::system_category()); - } -#else - throw std::system_error(err, std::system_category()); -#endif + // Workaround for a mkfifo bug on Blackberry devices: + // When the fifo already exists, mkfifo fails with error ENOSYS which is not correct. + // In this case, we use stat to check if the path exists and it is a fifo. + if (err == ENOSYS) { + err = EEXIST; } - else { +#endif + // the fifo already existing isn't an error + if (err == EEXIST) { // If the file already exists, verify it is a FIFO return check_is_fifo(path); } + throw SystemError(err, get_errno_msg("mkfifo() failed: ", err)); } #endif } @@ -96,5 +92,4 @@ bool try_create_fifo(const std::string& path) } } -} // namespace util -} // namespace realm +} // namespace realm::util diff --git a/src/realm/util/fifo_helper.hpp b/src/realm/util/fifo_helper.hpp index 6ca8d75b0e6..e117158050d 100644 --- a/src/realm/util/fifo_helper.hpp +++ b/src/realm/util/fifo_helper.hpp @@ -21,8 +21,7 @@ #include -namespace realm { -namespace util { +namespace realm::util { // Attempts to create a FIFO file at the location determined by `path`. // If creating the FIFO at this location fails, an exception is thrown. @@ -37,7 +36,6 @@ inline std::string normalize_dir(const std::string& path) { return (!path.empty() && path.back() != '/') ? path + '/' : path; } -} // namespace util -} // namespace realm +} // namespace realm::util #endif // REALM_UTIL_FIFO_HELPER_HPP diff --git a/src/realm/util/file.cpp b/src/realm/util/file.cpp index 456aa8076cd..006abf86bb3 100644 --- a/src/realm/util/file.cpp +++ b/src/realm/util/file.cpp @@ -184,17 +184,20 @@ struct WindowsFileHandleHolder { namespace realm::util { +namespace { /// Thrown if create_Always was specified and the file did already /// exist. -class File::Exists : public FileAccessError { +class Exists : public FileAccessError { public: - Exists(const std::string& msg, const std::string& path, int err) - : FileAccessError(ErrorCodes::FileAlreadyExists, std::string("File already exists. ") + msg, path, err) + Exists(const std::string& msg, const std::string& path) + : FileAccessError(ErrorCodes::FileAlreadyExists, util::format("%1file at path '%2' already exists.", msg, path), path) { } }; +} // anonymous namespace + bool try_make_dir(const std::string& path) { @@ -224,8 +227,7 @@ void make_dir(const std::string& path) { if (try_make_dir(path)) // Throws return; - std::string msg = get_errno_msg("make_dir() failed: ", EEXIST); - throw File::Exists(msg, path, EEXIST); + throw Exists("make_dir() failed: ", path); } @@ -442,7 +444,7 @@ void File::open_internal(const std::string& path, AccessMode a, CreateMode c, in case ERROR_PATH_NOT_FOUND: throw FileAccessError(ErrorCodes::FileNotFound, msg, path, int(err)); case ERROR_FILE_EXISTS: - throw Exists(msg, path, int(err)); + throw Exists(error_prefix, path); default: throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, int(err)); } @@ -499,7 +501,7 @@ void File::open_internal(const std::string& path, AccessMode a, CreateMode c, in case ENOENT: throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err); case EEXIST: - throw Exists(msg, path, err); + throw Exists(error_prefix, path); default: throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err); // LCOV_EXCL_LINE } diff --git a/src/realm/util/file.hpp b/src/realm/util/file.hpp index 1a038bf4002..a9cf7fb11e0 100644 --- a/src/realm/util/file.hpp +++ b/src/realm/util/file.hpp @@ -610,9 +610,6 @@ class File { class Streambuf; - // Exceptions - class Exists; - private: #ifdef _WIN32 void* m_fd = nullptr; diff --git a/test/object-store/backup.cpp b/test/object-store/backup.cpp index 52d1c5be089..f6adb9fbec0 100644 --- a/test/object-store/backup.cpp +++ b/test/object-store/backup.cpp @@ -43,21 +43,6 @@ #include #include -namespace realm { -class TestHelper { -public: - static DBRef& get_db(SharedRealm const& shared_realm) - { - return Realm::Internal::get_db(*shared_realm); - } - - static void begin_read(SharedRealm const& shared_realm, VersionID version) - { - Realm::Internal::begin_read(*shared_realm, version); - } -}; -} // namespace realm - using namespace realm; TEST_CASE("Automated backup") { diff --git a/test/object-store/collection_fixtures.hpp b/test/object-store/collection_fixtures.hpp index 27ef549fd1d..1d5f3faec38 100644 --- a/test/object-store/collection_fixtures.hpp +++ b/test/object-store/collection_fixtures.hpp @@ -34,25 +34,13 @@ namespace realm::collection_fixtures { -struct less { - template - auto operator()(T&& a, U&& b) const noexcept - { - return Mixed(a).compare(Mixed(b)) < 0; - } -}; -struct greater { - template - auto operator()(T&& a, U&& b) const noexcept - { - return Mixed(a).compare(Mixed(b)) > 0; - } -}; +template +constexpr bool always_false = false; template inline T get(Mixed) { - abort(); + static_assert(always_false); } template <> @@ -93,12 +81,13 @@ struct Base { using Wrapped = T; using Boxed = T; using AvgType = double; - enum { is_optional = false }; + constexpr static bool is_optional = false; + constexpr static bool can_sum = std::is_arithmetic::value; + constexpr static bool can_average = std::is_arithmetic::value; + constexpr static bool can_minmax = std::is_arithmetic::value; + constexpr static bool can_sort = true; + constexpr static PropertyType property_type = prop_type; - static PropertyType property_type() - { - return prop_type; - } static std::any to_any(T value) { return value; @@ -130,76 +119,54 @@ struct Base { { return T{}; } - static bool can_sum() - { - return std::is_arithmetic::value; - } - static bool can_average() - { - return std::is_arithmetic::value; - } - static bool can_minmax() - { - return std::is_arithmetic::value; - } - static bool can_sort() - { - return true; - } }; struct Int : Base { + constexpr static const char* name = "int"; static std::vector values() { return {3, 1, 2}; } - static int64_t min() + constexpr static int64_t min() { return 1; } - static int64_t max() + constexpr static int64_t max() { return 3; } - static int64_t sum() + constexpr static int64_t sum() { return 6; } - static double average() + constexpr static double average() { return 2.0; } }; struct Bool : Base { + constexpr static const char* name = "bool"; + constexpr static bool can_sum = false; + constexpr static bool can_average = false; + constexpr static bool can_minmax = false; static std::vector values() { return {true, false}; } - static bool can_sum() - { - return false; - } - static bool can_average() - { - return false; - } - static bool can_minmax() - { - return false; - } }; struct Float : Base { + constexpr static const char* name = "float"; static std::vector values() { return {3.3f, 1.1f, 2.2f}; } - static float min() + constexpr static float min() { return 1.1f; } - static float max() + constexpr static float max() { return 3.3f; } @@ -214,15 +181,16 @@ struct Float : Base { }; struct Double : Base { + constexpr static const char* name = "double"; static std::vector values() { return {3.3, 1.1, 2.2}; } - static double min() + constexpr static double min() { return 1.1; } - static double max() + constexpr static double max() { return 3.3; } @@ -237,6 +205,7 @@ struct Double : Base { }; struct String : Base { + constexpr static const char* name = "string"; using Boxed = std::string; static std::vector values() { @@ -249,7 +218,9 @@ struct String : Base { }; struct Binary : Base { + constexpr static const char* name = "data"; using Boxed = std::string; + constexpr static bool can_sort = false; static std::any to_any(BinaryData value) { return value ? std::string(value) : std::any(); @@ -258,21 +229,15 @@ struct Binary : Base { { return {BinaryData("c", 1), BinaryData("a", 1), BinaryData("b", 1)}; } - static bool can_sort() - { - return false; - } }; struct Date : Base { + constexpr static const char* name = "date"; + constexpr static bool can_minmax = true; static std::vector values() { return {Timestamp(3, 3), Timestamp(1, 1), Timestamp(2, 2)}; } - static bool can_minmax() - { - return true; - } static Timestamp min() { return Timestamp(1, 1); @@ -284,18 +249,20 @@ struct Date : Base { }; struct MixedVal : Base { + constexpr static const char* name = "mixed"; using AvgType = Decimal128; - enum { is_optional = true }; + constexpr static bool is_optional = true; + constexpr static bool can_sum = true; + constexpr static bool can_average = true; + constexpr static bool can_minmax = true; + constexpr static PropertyType property_type = PropertyType::Mixed | PropertyType::Nullable; + static std::vector values() { return {Mixed{realm::UUID()}, Mixed{int64_t(1)}, Mixed{}, Mixed{"hello world"}, Mixed{Timestamp(1, 1)}, Mixed{Decimal128("300")}, Mixed{double(2.2)}, Mixed{float(3.3)}}; } - static PropertyType property_type() - { - return PropertyType::Mixed | PropertyType::Nullable; - } static Mixed min() { return Mixed{int64_t(1)}; @@ -316,21 +283,10 @@ struct MixedVal : Base { { return Mixed{0}; } - static bool can_sum() - { - return true; - } - static bool can_average() - { - return true; - } - static bool can_minmax() - { - return true; - } }; struct OID : Base { + constexpr static const char* name = "object id"; static std::vector values() { return {ObjectId("bbbbbbbbbbbbbbbbbbbbbbbb"), ObjectId("aaaaaaaaaaaaaaaaaaaaaaaa")}; @@ -338,6 +294,7 @@ struct OID : Base { }; struct UUID : Base { + constexpr static const char* name = "uuid"; static std::vector values() { return {realm::UUID("3b241101-e2bb-4255-8caf-4136c566a962"), @@ -346,7 +303,12 @@ struct UUID : Base { }; struct Decimal : Base { + constexpr static const char* name = "decimal128"; using AvgType = Decimal128; + constexpr static bool can_sum = true; + constexpr static bool can_average = true; + constexpr static bool can_minmax = true; + static std::vector values() { return {Decimal128("876.54e32"), Decimal128("123.45e6")}; @@ -367,29 +329,16 @@ struct Decimal : Base { { return ((Decimal128("123.45e6") + Decimal128("876.54e32")) / Decimal128(2)); } - static bool can_sum() - { - return true; - } - static bool can_average() - { - return true; - } - static bool can_minmax() - { - return true; - } }; template struct BoxedOptional : BaseT { + static const inline std::string name = std::string(BaseT::name) + "?"; using Type = util::Optional; using Boxed = Type; - enum { is_optional = true }; - static PropertyType property_type() - { - return BaseT::property_type() | PropertyType::Nullable; - } + constexpr static bool is_optional = true; + constexpr static PropertyType property_type = BaseT::property_type | PropertyType::Nullable; + static std::vector values() { std::vector ret; @@ -416,11 +365,10 @@ struct BoxedOptional : BaseT { template struct UnboxedOptional : BaseT { - enum { is_optional = true }; - static PropertyType property_type() - { - return BaseT::property_type() | PropertyType::Nullable; - } + static const inline std::string name = std::string(BaseT::name) + "?"; + constexpr static bool is_optional = true; + constexpr static PropertyType property_type = BaseT::property_type | PropertyType::Nullable; + static auto values() -> decltype(BaseT::values()) { auto ret = BaseT::values(); diff --git a/test/object-store/dictionary.cpp b/test/object-store/dictionary.cpp index 07992ee8247..c8013fbf250 100644 --- a/test/object-store/dictionary.cpp +++ b/test/object-store/dictionary.cpp @@ -78,7 +78,7 @@ TEMPLATE_TEST_CASE("dictionary types", "[dictionary]", cf::MixedVal, cf::Int, cf config.automatic_change_notifications = false; config.schema = Schema{ {"object", - {{"value", PropertyType::Dictionary | TestType::property_type()}, + {{"value", PropertyType::Dictionary | TestType::property_type}, {"links", PropertyType::Dictionary | PropertyType::Object | PropertyType::Nullable, "target"}}}, {"target", {{"value", PropertyType::Int}, {"self_link", PropertyType::Object | PropertyType::Nullable, "target"}}}, @@ -147,7 +147,7 @@ TEMPLATE_TEST_CASE("dictionary types", "[dictionary]", cf::MixedVal, cf::Int, cf } SECTION("value type") { - REQUIRE(values_as_results.get_type() == TestType::property_type()); + REQUIRE(values_as_results.get_type() == TestType::property_type); } SECTION("size()") { @@ -165,16 +165,24 @@ TEMPLATE_TEST_CASE("dictionary types", "[dictionary]", cf::MixedVal, cf::Int, cf SECTION("verify_attached()") { object_store::Dictionary unattached; REQUIRE_NOTHROW(dict.verify_attached()); - REQUIRE_THROWS_WITH(unattached.verify_attached(), "Access to invalidated Dictionary object"); + REQUIRE_EXCEPTION(unattached.verify_attached(), InvalidatedObject, + "Dictionary is invalid and was never initialized."); + r->invalidate(); + REQUIRE_EXCEPTION(dict.verify_attached(), InvalidatedObject, + "Dictionary is no longer valid. Either the parent object was deleted or the containing " + "Realm has been invalidated or closed."); } SECTION("verify_in_transaction()") { object_store::Dictionary unattached; - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(unattached.verify_in_transaction(), ErrorCodes::InvalidatedObject); + REQUIRE_EXCEPTION(unattached.verify_in_transaction(), InvalidatedObject, + "Dictionary is invalid and was never initialized."); REQUIRE_NOTHROW(dict.verify_in_transaction()); r->commit_transaction(); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(dict.verify_in_transaction(), ErrorCodes::WrongTransactionState); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(unattached.verify_in_transaction(), ErrorCodes::InvalidatedObject); + REQUIRE_EXCEPTION(dict.verify_in_transaction(), WrongTransactionState, + "Cannot modify managed Dictionary outside of a write transaction."); + REQUIRE_EXCEPTION(unattached.verify_in_transaction(), InvalidatedObject, + "Dictionary is invalid and was never initialized."); } SECTION("clear()") { @@ -234,10 +242,12 @@ TEMPLATE_TEST_CASE("dictionary types", "[dictionary]", cf::MixedVal, cf::Int, cf REQUIRE(dict.contains(key)); dict.erase(key); REQUIRE(!dict.contains(key)); - REQUIRE_THROWS(dict.erase(key)); + REQUIRE_EXCEPTION(dict.erase(key), KeyNotFound, + util::format("Cannot remove key \"%1\" from dictionary: key not found", key)); } REQUIRE(dict.size() == 0); - REQUIRE_THROWS(dict.erase(keys[0])); + REQUIRE_EXCEPTION(dict.erase(keys[0]), KeyNotFound, + "Cannot remove key \"key_0\" from dictionary: key not found"); } SECTION("try_erase()") { @@ -286,10 +296,10 @@ TEMPLATE_TEST_CASE("dictionary types", "[dictionary]", cf::MixedVal, cf::Int, cf found_keys.push_back(pair.first); found_values.push_back(pair.second); } - std::sort(begin(keys), end(keys), cf::less()); - std::sort(begin(mixed_values), end(mixed_values), cf::less()); - std::sort(begin(found_keys), end(found_keys), cf::less()); - std::sort(begin(found_values), end(found_values), cf::less()); + std::sort(begin(keys), end(keys), std::less()); + std::sort(begin(mixed_values), end(mixed_values), std::less()); + std::sort(begin(found_keys), end(found_keys), std::less()); + std::sort(begin(found_values), end(found_values), std::less()); REQUIRE(keys == found_keys); REQUIRE(mixed_values == found_values); } @@ -350,7 +360,7 @@ TEMPLATE_TEST_CASE("dictionary types", "[dictionary]", cf::MixedVal, cf::Int, cf SECTION("keys sorted") { SECTION("ascending") { auto sorted = keys_as_results.sort({{"self", true}}); - std::sort(begin(keys), end(keys), cf::less()); + std::sort(begin(keys), end(keys), std::less()); verify_keys_ordered(sorted); // check the same but by generic descriptor DescriptorOrdering ordering; @@ -360,7 +370,7 @@ TEMPLATE_TEST_CASE("dictionary types", "[dictionary]", cf::MixedVal, cf::Int, cf } SECTION("descending") { auto sorted = keys_as_results.sort({{"self", false}}); - std::sort(begin(keys), end(keys), cf::greater()); + std::sort(begin(keys), end(keys), std::greater()); verify_keys_ordered(sorted); // check the same but by descriptor DescriptorOrdering ordering; @@ -372,12 +382,12 @@ TEMPLATE_TEST_CASE("dictionary types", "[dictionary]", cf::MixedVal, cf::Int, cf SECTION("values sorted") { SECTION("ascending") { auto sorted = values_as_results.sort({{"self", true}}); - std::sort(begin(values), end(values), cf::less()); + std::sort(begin(values), end(values), std::less()); verify_values_ordered(sorted); } SECTION("descending") { auto sorted = values_as_results.sort({{"self", false}}); - std::sort(begin(values), end(values), cf::greater()); + std::sort(begin(values), end(values), std::greater()); verify_values_ordered(sorted); } } @@ -486,43 +496,67 @@ TEMPLATE_TEST_CASE("dictionary types", "[dictionary]", cf::MixedVal, cf::Int, cf } SECTION("min()") { - if (!TestType::can_minmax()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(values_as_results.min(), ErrorCodes::IllegalOperation); - return; + if constexpr (!TestType::can_minmax) { + REQUIRE_EXCEPTION( + dict.min(), IllegalOperation, + util::format("Operation 'min' not supported for %1 dictionary 'object.value'", TestType::name)); + REQUIRE_EXCEPTION( + values_as_results.min(), IllegalOperation, + util::format("Operation 'min' not supported for %1 dictionary 'object.value'", TestType::name)); + } + else { + REQUIRE(Mixed(TestType::min()) == values_as_results.min()); + dict.remove_all(); + REQUIRE(!values_as_results.min()); } - REQUIRE(Mixed(TestType::min()) == values_as_results.min()); - dict.remove_all(); - REQUIRE(!values_as_results.min()); } SECTION("max()") { - if (!TestType::can_minmax()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(values_as_results.max(), ErrorCodes::IllegalOperation); - return; + if constexpr (!TestType::can_minmax) { + REQUIRE_EXCEPTION( + dict.max(), IllegalOperation, + util::format("Operation 'max' not supported for %1 dictionary 'object.value'", TestType::name)); + REQUIRE_EXCEPTION( + values_as_results.max(), IllegalOperation, + util::format("Operation 'max' not supported for %1 dictionary 'object.value'", TestType::name)); + } + else { + REQUIRE(Mixed(TestType::max()) == values_as_results.max()); + dict.remove_all(); + REQUIRE(!values_as_results.max()); } - REQUIRE(Mixed(TestType::max()) == values_as_results.max()); - dict.remove_all(); - REQUIRE(!values_as_results.max()); } SECTION("sum()") { - if (!TestType::can_sum()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(values_as_results.sum(), ErrorCodes::IllegalOperation); - return; + if constexpr (!TestType::can_sum) { + REQUIRE_EXCEPTION( + dict.sum(), IllegalOperation, + util::format("Operation 'sum' not supported for %1 dictionary 'object.value'", TestType::name)); + REQUIRE_EXCEPTION( + values_as_results.sum(), IllegalOperation, + util::format("Operation 'sum' not supported for %1 dictionary 'object.value'", TestType::name)); + } + else { + REQUIRE(cf::get(*values_as_results.sum()) == TestType::sum()); + dict.remove_all(); + REQUIRE(values_as_results.sum() == 0); } - REQUIRE(cf::get(*values_as_results.sum()) == TestType::sum()); - dict.remove_all(); - REQUIRE(values_as_results.sum() == 0); } SECTION("average()") { - if (!TestType::can_average()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(values_as_results.average(), ErrorCodes::IllegalOperation); - return; + if constexpr (!TestType::can_average) { + REQUIRE_EXCEPTION( + dict.average(), IllegalOperation, + util::format("Operation 'average' not supported for %1 dictionary 'object.value'", TestType::name)); + REQUIRE_EXCEPTION( + values_as_results.average(), IllegalOperation, + util::format("Operation 'average' not supported for %1 dictionary 'object.value'", TestType::name)); + } + else { + REQUIRE(cf::get(*values_as_results.average()) == TestType::average()); + dict.remove_all(); + REQUIRE(!values_as_results.average()); } - REQUIRE(cf::get(*values_as_results.average()) == TestType::average()); - dict.remove_all(); - REQUIRE(!values_as_results.average()); } SECTION("handover") { @@ -532,7 +566,7 @@ TEMPLATE_TEST_CASE("dictionary types", "[dictionary]", cf::MixedVal, cf::Int, cf REQUIRE(dict == dict2); ThreadSafeReference ref(values_as_results); auto results2 = ref.resolve(r).sort({{"self", true}}); - std::sort(begin(values), end(values), cf::less()); + std::sort(begin(values), end(values), std::less()); for (size_t i = 0; i < values.size(); ++i) { REQUIRE(results2.get(i) == values[i]); } @@ -880,10 +914,10 @@ TEST_CASE("embedded dictionary", "[dictionary]") { r->begin_transaction(); SECTION("rejects boxed Obj and Object") { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(dict.insert(ctx, "foo", std::any(target->get_object(5))), - ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(dict.insert(ctx, "foo", std::any(Object(r, target->get_object(5)))), - ErrorCodes::IllegalOperation); + REQUIRE_EXCEPTION(dict.insert(ctx, "foo", std::any(target->get_object(5))), IllegalOperation, + "Cannot add an existing managed embedded object to a Dictionary."); + REQUIRE_EXCEPTION(dict.insert(ctx, "foo", std::any(Object(r, target->get_object(5)))), IllegalOperation, + "Cannot add an existing managed embedded object to a Dictionary."); } SECTION("creates new object for dictionary") { @@ -911,7 +945,7 @@ TEMPLATE_TEST_CASE("dictionary of objects", "[dictionary][links]", cf::MixedVal, config.automatic_change_notifications = false; config.schema = Schema{ {"object", {{"links", PropertyType::Dictionary | PropertyType::Object | PropertyType::Nullable, "target"}}}, - {"target", {{"value", TestType::property_type()}}}, + {"target", {{"value", TestType::property_type}}}, }; auto r = Realm::get_shared_realm(config); @@ -940,60 +974,76 @@ TEMPLATE_TEST_CASE("dictionary of objects", "[dictionary][links]", cf::MixedVal, } SECTION("min()") { - if (!TestType::can_minmax()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(dict.min(col_target_value), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(values_as_results.min(col_target_value), - ErrorCodes::IllegalOperation); - return; - } - REQUIRE(Mixed(TestType::min()) == dict.min(col_target_value)); - REQUIRE(Mixed(TestType::min()) == values_as_results.min(col_target_value)); - dict.remove_all(); - REQUIRE(!dict.min(col_target_value)); - REQUIRE(!values_as_results.min(col_target_value)); + if constexpr (!TestType::can_minmax) { + REQUIRE_EXCEPTION( + dict.min(col_target_value), IllegalOperation, + util::format("Operation 'min' not supported for %1 property 'target.value'", TestType::name)); + REQUIRE_EXCEPTION( + values_as_results.min(col_target_value), IllegalOperation, + util::format("Operation 'min' not supported for %1 property 'target.value'", TestType::name)); + } + else { + REQUIRE(Mixed(TestType::min()) == dict.min(col_target_value)); + REQUIRE(Mixed(TestType::min()) == values_as_results.min(col_target_value)); + dict.remove_all(); + REQUIRE(!dict.min(col_target_value)); + REQUIRE(!values_as_results.min(col_target_value)); + } } SECTION("max()") { - if (!TestType::can_minmax()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(dict.max(col_target_value), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(values_as_results.max(col_target_value), - ErrorCodes::IllegalOperation); - return; - } - REQUIRE(Mixed(TestType::max()) == dict.max(col_target_value)); - REQUIRE(Mixed(TestType::max()) == values_as_results.max(col_target_value)); - dict.remove_all(); - REQUIRE(!dict.max(col_target_value)); - REQUIRE(!values_as_results.max(col_target_value)); + if constexpr (!TestType::can_minmax) { + REQUIRE_EXCEPTION( + dict.max(col_target_value), IllegalOperation, + util::format("Operation 'max' not supported for %1 property 'target.value'", TestType::name)); + REQUIRE_EXCEPTION( + values_as_results.max(col_target_value), IllegalOperation, + util::format("Operation 'max' not supported for %1 property 'target.value'", TestType::name)); + } + else { + REQUIRE(Mixed(TestType::max()) == dict.max(col_target_value)); + REQUIRE(Mixed(TestType::max()) == values_as_results.max(col_target_value)); + dict.remove_all(); + REQUIRE(!dict.max(col_target_value)); + REQUIRE(!values_as_results.max(col_target_value)); + } } SECTION("sum()") { - if (!TestType::can_sum()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(dict.sum(col_target_value), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(values_as_results.sum(col_target_value), - ErrorCodes::IllegalOperation); - return; - } - REQUIRE(cf::get(dict.sum(col_target_value)) == TestType::sum()); - REQUIRE(cf::get(*values_as_results.sum(col_target_value)) == TestType::sum()); - dict.remove_all(); - REQUIRE(dict.sum(col_target_value) == 0); - REQUIRE(values_as_results.sum(col_target_value) == 0); + if constexpr (!TestType::can_sum) { + REQUIRE_EXCEPTION( + dict.sum(col_target_value), IllegalOperation, + util::format("Operation 'sum' not supported for %1 property 'target.value'", TestType::name)); + REQUIRE_EXCEPTION( + values_as_results.sum(col_target_value), IllegalOperation, + util::format("Operation 'sum' not supported for %1 property 'target.value'", TestType::name)); + } + else { + REQUIRE(cf::get(dict.sum(col_target_value)) == TestType::sum()); + REQUIRE(cf::get(*values_as_results.sum(col_target_value)) == TestType::sum()); + dict.remove_all(); + REQUIRE(dict.sum(col_target_value) == 0); + REQUIRE(values_as_results.sum(col_target_value) == 0); + } } SECTION("average()") { - if (!TestType::can_average()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(dict.average(col_target_value), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(values_as_results.average(col_target_value), - ErrorCodes::IllegalOperation); - return; - } - REQUIRE(cf::get(*dict.average(col_target_value)) == TestType::average()); - REQUIRE(cf::get(*values_as_results.average(col_target_value)) == - TestType::average()); - dict.remove_all(); - REQUIRE(!dict.average(col_target_value)); - REQUIRE(!values_as_results.average(col_target_value)); + if constexpr (!TestType::can_average) { + REQUIRE_EXCEPTION( + dict.average(col_target_value), IllegalOperation, + util::format("Operation 'average' not supported for %1 property 'target.value'", TestType::name)); + REQUIRE_EXCEPTION( + values_as_results.average(col_target_value), IllegalOperation, + util::format("Operation 'average' not supported for %1 property 'target.value'", TestType::name)); + } + else { + REQUIRE(cf::get(*dict.average(col_target_value)) == TestType::average()); + REQUIRE(cf::get(*values_as_results.average(col_target_value)) == + TestType::average()); + dict.remove_all(); + REQUIRE(!dict.average(col_target_value)); + REQUIRE(!values_as_results.average(col_target_value)); + } } } diff --git a/test/object-store/frozen_objects.cpp b/test/object-store/frozen_objects.cpp index 9117fb6e09a..1ebe64fd1b9 100644 --- a/test/object-store/frozen_objects.cpp +++ b/test/object-store/frozen_objects.cpp @@ -43,21 +43,6 @@ #include -namespace realm { -class TestHelper { -public: - static DBRef& get_db(SharedRealm const& shared_realm) - { - return Realm::Internal::get_db(*shared_realm); - } - - static void begin_read(SharedRealm const& shared_realm, VersionID version) - { - Realm::Internal::begin_read(*shared_realm, version); - } -}; -} // namespace realm - using namespace realm; using util::any_cast; @@ -102,12 +87,14 @@ TEST_CASE("Freeze Realm", "[freeze_realm]") { SECTION("auto_refresh") { REQUIRE(!frozen_realm->auto_refresh()); - REQUIRE_THROWS(frozen_realm->set_auto_refresh(true)); + REQUIRE_EXCEPTION(frozen_realm->set_auto_refresh(true), WrongTransactionState, + "Auto-refresh cannot be enabled for frozen Realms."); REQUIRE(!frozen_realm->auto_refresh()); } SECTION("begin_transaction() throws") { - REQUIRE_THROWS(frozen_realm->begin_transaction()); + REQUIRE_EXCEPTION(frozen_realm->begin_transaction(), WrongTransactionState, + "Can't perform transactions on a frozen Realm"); } SECTION("can call methods on another thread") { @@ -179,7 +166,9 @@ TEST_CASE("Freeze Results", "[freeze_results]") { } SECTION("add_notification throws") { - REQUIRE_THROWS(frozen_results.add_notification_callback([&](CollectionChangeSet) {})); + REQUIRE_EXCEPTION(frozen_results.add_notification_callback([&](CollectionChangeSet) {}), + WrongTransactionState, + "Notifications are not available on frozen collections since they do not change."); } SECTION("Result constructor - Empty") { @@ -189,7 +178,8 @@ TEST_CASE("Freeze Results", "[freeze_results]") { JoiningThread thread([&] { REQUIRE(frozen_res.is_frozen()); REQUIRE(frozen_res.size() == 0); - REQUIRE_THROWS(frozen_res.get_any(0)); + REQUIRE_EXCEPTION(frozen_res.get_any(0), OutOfBounds, + "Requested index 0 calling get_any() on Results when empty"); }); } @@ -351,8 +341,12 @@ TEST_CASE("Freeze List", "[freeze_list]") { } SECTION("add_notification throws") { - REQUIRE_THROWS(frozen_link_list.add_notification_callback([&](CollectionChangeSet) {})); - REQUIRE_THROWS(frozen_primitive_list.add_notification_callback([&](CollectionChangeSet) {})); + REQUIRE_EXCEPTION(frozen_link_list.add_notification_callback([&](CollectionChangeSet) {}), + WrongTransactionState, + "Notifications are not available on frozen collections since they do not change."); + REQUIRE_EXCEPTION(frozen_primitive_list.add_notification_callback([&](CollectionChangeSet) {}), + WrongTransactionState, + "Notifications are not available on frozen collections since they do not change."); } SECTION("read across threads") { @@ -416,7 +410,8 @@ TEST_CASE("Freeze Object", "[freeze_object]") { } SECTION("add_notification throws") { - REQUIRE_THROWS(frozen_obj.add_notification_callback([&](CollectionChangeSet) {})); + REQUIRE_EXCEPTION(frozen_obj.add_notification_callback([&](CollectionChangeSet) {}), WrongTransactionState, + "Notifications are not available on frozen collections since they do not change."); } SECTION("read across threads") { @@ -491,8 +486,12 @@ TEST_CASE("Freeze dictionary", "[freeze_dictionary]") { } SECTION("add_notification throws") { - REQUIRE_THROWS(frozen_obj_dict.add_notification_callback([&](CollectionChangeSet) {})); - REQUIRE_THROWS(frozen_int_dict.add_notification_callback([&](CollectionChangeSet) {})); + REQUIRE_EXCEPTION(frozen_obj_dict.add_notification_callback([&](CollectionChangeSet) {}), + WrongTransactionState, + "Notifications are not available on frozen collections since they do not change."); + REQUIRE_EXCEPTION(frozen_int_dict.add_notification_callback([&](CollectionChangeSet) {}), + WrongTransactionState, + "Notifications are not available on frozen collections since they do not change."); } SECTION("read across threads") { diff --git a/test/object-store/list.cpp b/test/object-store/list.cpp index 44fa8406f68..133806cc430 100644 --- a/test/object-store/list.cpp +++ b/test/object-store/list.cpp @@ -854,14 +854,14 @@ TEST_CASE("list") { results = list.sort({{{col_target_value}}, {false}}); for (size_t i = 0; i < 10; ++i) REQUIRE(results.get(i).get_key() == target_keys[9 - i]); - REQUIRE_THROWS_WITH(results.get(10), "Requested index 10 calling get() on Results when max is 9"); + REQUIRE_EXCEPTION(results.get(10), OutOfBounds, "Requested index 10 calling get() on Results when max is 9"); REQUIRE(results.get_mode() == Results::Mode::TableView); // Zero sort columns should leave it in Collection mode results = list.sort(SortDescriptor()); for (size_t i = 0; i < 10; ++i) REQUIRE(results.get(i).get_key() == target_keys[i]); - REQUIRE_THROWS_WITH(results.get(10), "Requested index 10 calling get() on Results when max is 9"); + REQUIRE_EXCEPTION(results.get(10), OutOfBounds, "Requested index 10 calling get() on Results when max is 9"); REQUIRE(results.get_mode() == Results::Mode::Collection); } @@ -889,7 +889,8 @@ TEST_CASE("list") { SECTION("get()") { for (size_t i = 0; i < 5; ++i) REQUIRE(results.get(i).get_key() == target_keys[i * 2]); - REQUIRE_THROWS_WITH(results.get(5), "Requested index 5 calling get() on Results when max is 4"); + REQUIRE_EXCEPTION(results.get(5), OutOfBounds, + "Requested index 5 calling get() on Results when max is 4"); REQUIRE(results.get_mode() == Results::Mode::TableView); } @@ -912,7 +913,8 @@ TEST_CASE("list") { results = list.as_results().distinct(DistinctDescriptor()); for (size_t i = 0; i < 10; ++i) REQUIRE(results.get(i).get_key() == target_keys[i]); - REQUIRE_THROWS_WITH(results.get(10), "Requested index 10 calling get() on Results when max is 9"); + REQUIRE_EXCEPTION(results.get(10), OutOfBounds, + "Requested index 10 calling get() on Results when max is 9"); REQUIRE(results.get_mode() == Results::Mode::Collection); } } @@ -1036,7 +1038,8 @@ TEST_CASE("list") { } SECTION("throws for rows from the wrong table") { - REQUIRE_THROWS(list.add(obj)); + REQUIRE_EXCEPTION(list.add(obj), ObjectTypeMismatch, + "Object of type (origin) does not match List type (target)"); } r->cancel_transaction(); } @@ -1052,11 +1055,13 @@ TEST_CASE("list") { } SECTION("throws for rows from the wrong table") { - REQUIRE_THROWS(list.insert(0, obj)); + REQUIRE_EXCEPTION(list.insert(0, obj), ObjectTypeMismatch, + "Object of type (origin) does not match List type (target)"); } SECTION("throws for out of bounds insertions") { - REQUIRE_THROWS(list.insert(11, target_keys[5])); + REQUIRE_EXCEPTION(list.insert(11, target_keys[5]), OutOfBounds, + "Requested index 11 calling insert() on list 'origin.array' when max is 10"); REQUIRE_NOTHROW(list.insert(10, target_keys[5])); } r->cancel_transaction(); @@ -1073,11 +1078,13 @@ TEST_CASE("list") { } SECTION("throws for rows from the wrong table") { - REQUIRE_THROWS(list.set(0, obj)); + REQUIRE_EXCEPTION(list.set(0, obj), ObjectTypeMismatch, + "Object of type (origin) does not match List type (target)"); } SECTION("throws for out of bounds sets") { - REQUIRE_THROWS(list.set(10, target_keys[5])); + REQUIRE_EXCEPTION(list.set(10, target_keys[5]), OutOfBounds, + "Requested index 10 calling set() on list 'origin.array' when max is 9"); } r->cancel_transaction(); } @@ -1108,8 +1115,10 @@ TEST_CASE("list") { } SECTION("throws for row in wrong table") { - REQUIRE_THROWS(list.find(obj)); - REQUIRE_THROWS(list.as_results().index_of(obj)); + REQUIRE_EXCEPTION(list.find(obj), ObjectTypeMismatch, + "Object of type (origin) does not match List type (target)"); + REQUIRE_EXCEPTION(list.as_results().index_of(obj), ObjectTypeMismatch, + "Object of type 'origin' does not match Results type 'target'"); } } @@ -1158,9 +1167,11 @@ TEST_CASE("list") { } SECTION("throws for object in wrong table") { - REQUIRE_THROWS(list.add(ctx, std::any(origin->get_object(0)))); + REQUIRE_EXCEPTION(list.add(ctx, std::any(origin->get_object(0))), ObjectTypeMismatch, + "Object of type (origin) does not match List type (target)"); realm::Object object(r, *r->schema().find("origin"), origin->get_object(0)); - REQUIRE_THROWS(list.add(ctx, std::any(object))); + REQUIRE_EXCEPTION(list.add(ctx, std::any(object)), ObjectTypeMismatch, + "Object of type (origin) does not match List type (target)"); } r->cancel_transaction(); @@ -1185,7 +1196,8 @@ TEST_CASE("list") { } SECTION("throws for object in wrong table") { - REQUIRE_THROWS(list.find(ctx, std::any(obj))); + REQUIRE_EXCEPTION(list.find(ctx, std::any(obj)), ObjectTypeMismatch, + "Object of type (origin) does not match List type (target)"); } } @@ -1481,9 +1493,12 @@ TEST_CASE("embedded List") { SECTION("add(), insert(), and set() to existing object is not allowed") { List list(r, *lv); r->begin_transaction(); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(list.add(target->get_object(0)), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(list.insert(0, target->get_object(0)), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(list.set(0, target->get_object(0)), ErrorCodes::IllegalOperation); + REQUIRE_EXCEPTION(list.add(target->get_object(0)), IllegalOperation, + "Cannot insert an already managed object into list of embedded objects 'origin.array'"); + REQUIRE_EXCEPTION(list.insert(0, target->get_object(0)), IllegalOperation, + "Cannot insert an already managed object into list of embedded objects 'origin.array'"); + REQUIRE_EXCEPTION(list.set(0, target->get_object(0)), IllegalOperation, + "Cannot insert an already managed object into list of embedded objects 'origin.array'"); r->cancel_transaction(); } @@ -1508,13 +1523,16 @@ TEST_CASE("embedded List") { r->begin_transaction(); list.remove(1); REQUIRE(list.find(obj1) == npos); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(list.as_results().index_of(obj1), ErrorCodes::StaleAccessor); + REQUIRE_EXCEPTION(list.as_results().index_of(obj1), StaleAccessor, + "Attempting to access an invalid object"); r->cancel_transaction(); } SECTION("throws for row in wrong table") { - REQUIRE_THROWS(list.find(obj)); - REQUIRE_THROWS(list.as_results().index_of(obj)); + REQUIRE_EXCEPTION(list.find(obj), ObjectTypeMismatch, + "Object of type (origin) does not match List type (target)"); + REQUIRE_EXCEPTION(list.as_results().index_of(obj), ObjectTypeMismatch, + "Object of type 'origin' does not match Results type 'target'"); } } @@ -1615,7 +1633,8 @@ TEST_CASE("embedded List") { } SECTION("throws for object in wrong table") { - REQUIRE_THROWS(list.find(ctx, std::any(obj))); + REQUIRE_EXCEPTION(list.find(ctx, std::any(obj)), ObjectTypeMismatch, + "Object of type (origin) does not match List type (target)"); } } @@ -1729,27 +1748,26 @@ TEST_CASE("list of embedded objects") { } SECTION("invalid indices") { + realm->begin_transaction(); // Insertions - REQUIRE_THROWS(list.insert_embedded(-1)); // Negative - REQUIRE_THROWS(list.insert_embedded(1)); // At index > size() + REQUIRE_EXCEPTION( + list.insert_embedded(-1), OutOfBounds, + "Requested index 18446744073709551615 calling insert() on list 'parent.array' when max is 0"); // Negative + REQUIRE_EXCEPTION(list.insert_embedded(1), OutOfBounds, // At index > size() + "Requested index 1 calling insert() on list 'parent.array' when max is 0"); // Sets - REQUIRE_THROWS(list.set_embedded(-1)); // Negative - REQUIRE_THROWS(list.set_embedded(0)); // At index == size() - REQUIRE_THROWS(list.set_embedded(1)); // At index > size() + REQUIRE_EXCEPTION(list.set_embedded(-1), OutOfBounds, // Negative + "Requested index 18446744073709551615 calling set() on list 'parent.array' when empty"); + REQUIRE_EXCEPTION(list.set_embedded(0), OutOfBounds, // At index == size() + "Requested index 0 calling set() on list 'parent.array' when empty"); + REQUIRE_EXCEPTION(list.set_embedded(1), OutOfBounds, // At index > size() + "Requested index 1 calling set() on list 'parent.array' when empty"); + realm->cancel_transaction(); } } #if REALM_ENABLE_SYNC -namespace realm { -class TestHelper { -public: - static std::shared_ptr transaction(Realm& shared_realm) - { - return Realm::Internal::get_transaction_ref(shared_realm); - } -}; -} // namespace realm TEST_CASE("list with unresolved links") { TestSyncManager init_sync_manager({}, {false}); diff --git a/test/object-store/migrations.cpp b/test/object-store/migrations.cpp index 50259806354..4b55ba4f682 100644 --- a/test/object-store/migrations.cpp +++ b/test/object-store/migrations.cpp @@ -46,6 +46,12 @@ using util::any_cast; #define VERIFY_SCHEMA(r, m) verify_schema((r), __LINE__, m) +#define REQUIRE_UPDATE_FAILS(r, schema, msg) \ + REQUIRE_EXCEPTION((r).update_schema(schema), SchemaMismatch, Catch::Matchers::ContainsSubstring(msg)); + +#define INVALID_SCHEMA_CHANGE(r, schema, msg) \ + REQUIRE_EXCEPTION((r).update_schema(schema), InvalidSchemaChange, Catch::Matchers::ContainsSubstring(msg)); + #define REQUIRE_UPDATE_SUCCEEDS(r, s, version) \ do { \ REQUIRE_NOTHROW((r).update_schema(s, version)); \ @@ -59,10 +65,10 @@ using util::any_cast; REQUIRE_UPDATE_SUCCEEDS(r, schema2, 0); \ } while (0) -#define REQUIRE_MIGRATION_NEEDED(r, schema1, schema2) \ +#define REQUIRE_MIGRATION_NEEDED(r, schema1, schema2, msg) \ do { \ REQUIRE_UPDATE_SUCCEEDS(r, schema1, 0); \ - REQUIRE_THROWS((r).update_schema(schema2)); \ + REQUIRE_UPDATE_FAILS(r, schema2, msg); \ REQUIRE((r).schema() == schema1); \ REQUIRE_UPDATE_SUCCEEDS(r, schema2, 1); \ } while (0) @@ -284,7 +290,7 @@ TEST_CASE("migration: Automatic") { }}, }; auto schema2 = add_property(schema1, "object", {"col2", PropertyType::Int}); - REQUIRE_MIGRATION_NEEDED(*realm, schema1, schema2); + REQUIRE_MIGRATION_NEEDED(*realm, schema1, schema2, "Property 'object.col2' has been added."); } SECTION("remove property from existing object schema") { @@ -296,7 +302,8 @@ TEST_CASE("migration: Automatic") { {"col2", PropertyType::Int}, }}, }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, remove_property(schema, "object", "col2")); + REQUIRE_MIGRATION_NEEDED(*realm, schema, remove_property(schema, "object", "col2"), + "Property 'object.col2' has been removed."); } SECTION("migratation which replaces a persisted property with a computed one") { @@ -318,7 +325,7 @@ TEST_CASE("migration: Automatic") { schema2.find("object")->computed_properties.emplace_back(new_property); REQUIRE_UPDATE_SUCCEEDS(*realm, schema1, 0); - REQUIRE_THROWS((*realm).update_schema(schema2)); + REQUIRE_UPDATE_FAILS(*realm, schema2, "Property 'object.link' has been removed."); REQUIRE((*realm).schema() == schema1); REQUIRE_NOTHROW((*realm).update_schema( schema2, 1, [](SharedRealm, SharedRealm, Schema&) { /* empty but present migration handler */ })); @@ -334,7 +341,8 @@ TEST_CASE("migration: Automatic") { {"value", PropertyType::Int}, }}, }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, set_type(schema, "object", "value", PropertyType::Float)); + REQUIRE_MIGRATION_NEEDED(*realm, schema, set_type(schema, "object", "value", PropertyType::Float), + "Property 'object.value' has been changed from 'int' to 'float'."); } SECTION("make property nullable") { @@ -346,7 +354,8 @@ TEST_CASE("migration: Automatic") { {"value", PropertyType::Int}, }}, }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, set_optional(schema, "object", "value", true)); + REQUIRE_MIGRATION_NEEDED(*realm, schema, set_optional(schema, "object", "value", true), + "Property 'object.value' has been made optional."); } SECTION("make property required") { @@ -358,7 +367,8 @@ TEST_CASE("migration: Automatic") { {"value", PropertyType::Int | PropertyType::Nullable}, }}, }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, set_optional(schema, "object", "value", false)); + REQUIRE_MIGRATION_NEEDED(*realm, schema, set_optional(schema, "object", "value", false), + "Property 'object.value' has been made required."); } SECTION("change link target") { @@ -378,7 +388,8 @@ TEST_CASE("migration: Automatic") { {"value", PropertyType::Object | PropertyType::Nullable, "target 1"}, }}, }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, set_target(schema, "origin", "value", "target 2")); + REQUIRE_MIGRATION_NEEDED(*realm, schema, set_target(schema, "origin", "value", "target 2"), + "Property 'origin.value' has been changed from '' to ''"); } SECTION("add pk") { @@ -390,7 +401,8 @@ TEST_CASE("migration: Automatic") { {"value", PropertyType::Int}, }}, }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, set_primary_key(schema, "object", "value")); + REQUIRE_MIGRATION_NEEDED(*realm, schema, set_primary_key(schema, "object", "value"), + "Primary Key for class 'object' has been added."); } SECTION("remove pk") { @@ -402,7 +414,8 @@ TEST_CASE("migration: Automatic") { {"value", PropertyType::Int, Property::IsPrimary{true}}, }}, }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, set_primary_key(schema, "object", "")); + REQUIRE_MIGRATION_NEEDED(*realm, schema, set_primary_key(schema, "object", ""), + "Primary Key for class 'object' has been removed."); } SECTION("adding column and table in same migration doesn't add duplicate columns") { @@ -450,7 +463,8 @@ TEST_CASE("migration: Automatic") { {"value", PropertyType::Int}, }}, }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, set_table_type(schema, "object", ObjectType::TopLevel)); + REQUIRE_MIGRATION_NEEDED(*realm, schema, set_table_type(schema, "object", ObjectType::TopLevel), + "Class 'object' has been changed from Embedded to TopLevel."); } SECTION("change table from top-level to embedded without version bump") { @@ -463,7 +477,8 @@ TEST_CASE("migration: Automatic") { {"value", PropertyType::Int}, }}, }; - REQUIRE_MIGRATION_NEEDED(*realm, schema, set_table_type(schema, "object", ObjectType::Embedded)); + REQUIRE_MIGRATION_NEEDED(*realm, schema, set_table_type(schema, "object", ObjectType::Embedded), + "Class 'object' has been changed from TopLevel to Embedded."); } } @@ -521,7 +536,8 @@ TEST_CASE("migration: Automatic") { auto realm = Realm::get_shared_realm(config); realm->update_schema({}, 1); realm->update_schema({}, 2); - REQUIRE_THROWS(realm->update_schema({}, 0)); + REQUIRE_EXCEPTION(realm->update_schema({}, 0), InvalidSchemaVersion, + "Provided schema version 0 is less than last set version 2."); } SECTION("insert duplicate keys for existing PK during migration") { @@ -556,7 +572,8 @@ TEST_CASE("migration: Automatic") { realm->commit_transaction(); schema = set_primary_key(schema, "object", "value"); - REQUIRE_THROWS(realm->update_schema(schema, 2, nullptr)); + REQUIRE_EXCEPTION(realm->update_schema(schema, 2, nullptr), MigrationFailed, + "Primary key property 'class_object.value' has duplicate values after migration."); } SECTION("throwing an exception from migration function rolls back all changes") { @@ -570,11 +587,14 @@ TEST_CASE("migration: Automatic") { auto realm = Realm::get_shared_realm(config); realm->update_schema(schema1, 1); - REQUIRE_THROWS(realm->update_schema(schema2, 2, [](SharedRealm, SharedRealm realm, Schema&) { - auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); - table->create_object(); - throw 5; - })); + REQUIRE_THROWS_AS(realm->update_schema(schema2, 2, + [](SharedRealm, SharedRealm realm, Schema&) { + auto table = ObjectStore::table_for_object_type( + realm->read_group(), "object"); + table->create_object(); + throw 5; + }), + int); auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); REQUIRE(table->size() == 0); @@ -582,26 +602,6 @@ TEST_CASE("migration: Automatic") { REQUIRE(realm->schema() == schema1); } - SECTION("change table to embedded - table has primary key") { - Schema schema = { - {"child_table", - { - {"value", PropertyType::Int, Property::IsPrimary{true}}, - }}, - {"parent_table", - { - {"child_property", PropertyType::Object | PropertyType::Nullable, "child_table"}, - }}, - }; - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema, 1); - auto child_table = ObjectStore::table_for_object_type(realm->read_group(), "child_table"); - REQUIRE_FALSE(child_table->is_embedded()); - - REQUIRE_THROWS( - realm->update_schema(set_table_type(schema, "child_table", ObjectType::Embedded), 2, nullptr)); - } - SECTION("change table to embedded - no migration block") { Schema schema = { {"object", @@ -614,7 +614,9 @@ TEST_CASE("migration: Automatic") { auto child_table = ObjectStore::table_for_object_type(realm->read_group(), "object"); REQUIRE_FALSE(child_table->is_embedded()); - REQUIRE_THROWS(realm->update_schema(set_table_type(schema, "object", ObjectType::Embedded), 2, nullptr)); + REQUIRE_EXCEPTION( + realm->update_schema(set_table_type(schema, "object", ObjectType::Embedded), 2, nullptr), InvalidName, + "asdf"); } SECTION("change table to embedded - table has no backlinks") { @@ -629,8 +631,9 @@ TEST_CASE("migration: Automatic") { auto child_table = ObjectStore::table_for_object_type(realm->read_group(), "object"); REQUIRE_FALSE(child_table->is_embedded()); - REQUIRE_THROWS(realm->update_schema(set_table_type(schema, "object", ObjectType::Embedded), 2, - [](auto, auto, auto&) {})); + REQUIRE_EXCEPTION(realm->update_schema(set_table_type(schema, "object", ObjectType::Embedded), 2, + [](auto, auto, auto&) {}), + InvalidName, "asdf"); } SECTION("change table to embedded - multiple incoming link per object") { @@ -659,8 +662,9 @@ TEST_CASE("migration: Automatic") { REQUIRE(child_table->size() == 1); REQUIRE_FALSE(child_table->is_embedded()); - REQUIRE_THROWS( - realm->update_schema(set_table_type(schema, "child_table", ObjectType::Embedded), 2, nullptr)); + REQUIRE_EXCEPTION( + realm->update_schema(set_table_type(schema, "child_table", ObjectType::Embedded), 2, nullptr), + InvalidName, "asdf"); } SECTION("change table to embedded - adding more links in migration block") { @@ -1479,44 +1483,6 @@ TEST_CASE("migration: Automatic") { } } } - SECTION("change table to embedded - incoming links stored in a set") { - InMemoryTestFile config; - config.automatic_handle_backlicks_in_migrations = true; - Schema schema = { - { - "child_table", - {{"value", PropertyType::Int}}, - }, - {"parent_table", - { - {"child_property", PropertyType::Set | PropertyType::Object, "child_table"}, - }}, - }; - auto realm = Realm::get_shared_realm(config); - realm->update_schema(schema, 1); - realm->begin_transaction(); - auto child_table = ObjectStore::table_for_object_type(realm->read_group(), "child_table"); - Obj child_object = child_table->create_object(); - child_object.set("value", 42); - - auto parent_table = ObjectStore::table_for_object_type(realm->read_group(), "parent_table"); - auto parent_object = parent_table->create_object(); - ColKey col_links = parent_table->get_column_key("child_property"); - auto child_object_key = child_object.get_key(); - object_store::Set set_links(realm, parent_object, col_links); - set_links.insert(child_object_key); - // this should not create a new ref (set does not allow dups) - set_links.insert(child_object_key); - realm->commit_transaction(); - REQUIRE(parent_table->size() == 1); - REQUIRE(child_table->size() == 1); - REQUIRE(set_links.size() == 1); - REQUIRE_FALSE(child_table->is_embedded()); - REQUIRE_FALSE(parent_table->is_embedded()); - - REQUIRE_THROWS(realm->update_schema(set_table_type(schema, "child_table", ObjectType::Embedded), 2, - [](auto, auto, auto&) {})); - } SECTION("change table to embedded - multiple links stored in linked list") { InMemoryTestFile config; config.automatic_handle_backlicks_in_migrations = true; @@ -1993,7 +1959,8 @@ TEST_CASE("migration: Automatic") { CppContext ctx2(new_realm); obj = Object::get_for_primary_key(ctx, new_realm, "all types", std::any(INT64_C(1))); REQUIRE(obj.is_valid()); - REQUIRE_THROWS(obj.get_property_value(ctx, "bool")); + REQUIRE_EXCEPTION(obj.get_property_value(ctx, "bool"), InvalidProperty, + "Property 'all types.bool' does not exist"); }); } @@ -2002,8 +1969,10 @@ TEST_CASE("migration: Automatic") { CppContext ctx(old_realm); Object obj = Object::get_for_primary_key(ctx, old_realm, "all types", std::any(INT64_C(1))); REQUIRE(obj.is_valid()); - REQUIRE_THROWS(obj.set_property_value(ctx, "bool", std::any(false))); - REQUIRE_THROWS(old_realm->begin_transaction()); + REQUIRE_EXCEPTION(obj.set_property_value(ctx, "bool", std::any(false)), WrongTransactionState, + "Cannot modify managed objects outside of a write transaction."); + REQUIRE_EXCEPTION(old_realm->begin_transaction(), WrongTransactionState, + "Can't perform transactions on read-only Realms."); }); } @@ -2018,9 +1987,12 @@ TEST_CASE("migration: Automatic") { CppContext ctx(new_realm); Object obj = Object::get_for_primary_key(ctx, new_realm, "all types", std::any(INT64_C(1))); REQUIRE(obj.is_valid()); - REQUIRE_THROWS(obj.get_property_value(ctx, "bool")); - REQUIRE_THROWS(obj.get_property_value(ctx, "object")); - REQUIRE_THROWS(obj.get_property_value(ctx, "array")); + REQUIRE_EXCEPTION(obj.get_property_value(ctx, "bool"), InvalidProperty, + "Property 'all types.bool' does not exist"); + REQUIRE_EXCEPTION(obj.get_property_value(ctx, "object"), InvalidProperty, + "Property 'all types.object' does not exist"); + REQUIRE_EXCEPTION(obj.get_property_value(ctx, "array"), InvalidProperty, + "Property 'all types.array' does not exist"); }); } @@ -2699,7 +2671,7 @@ TEST_CASE("migration: Immutable") { {"value 2", PropertyType::Int}, }}, }; - REQUIRE_THROWS(realm->update_schema(schema)); + INVALID_SCHEMA_CHANGE(*realm, schema, "Property 'object.value 2' has been added."); } SECTION("bump schema version") { @@ -2710,7 +2682,8 @@ TEST_CASE("migration: Immutable") { }}, }; auto realm = realm_with_schema(schema); - REQUIRE_THROWS(realm->update_schema(schema, 1)); + REQUIRE_EXCEPTION(realm->update_schema(schema, 1), InvalidSchemaVersion, + "Provided schema version 1 is less than last set version 0."); } } } @@ -2829,7 +2802,6 @@ TEST_CASE("migration: ReadOnly") { } SECTION("disallowed mismatches") { - SECTION("missing columns in table") { auto realm = realm_with_schema({ {"object", @@ -2844,7 +2816,7 @@ TEST_CASE("migration: ReadOnly") { {"value 2", PropertyType::Int}, }}, }; - REQUIRE_THROWS(realm->update_schema(schema)); + INVALID_SCHEMA_CHANGE(*realm, schema, "Property 'object.value 2' has been added."); } } } @@ -3058,7 +3030,7 @@ TEST_CASE("migration: AdditiveDiscovered") { REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object 2")); } - DYNAMIC_SECTION("embedded orphan types" << mode_string) { + SECTION("embedded orphan types") { if (mode == SchemaMode::AdditiveDiscovered) { // in discovered mode, adding embedded orphan types is allowed but ignored REQUIRE_NOTHROW(realm->update_schema( @@ -3068,17 +3040,11 @@ TEST_CASE("migration: AdditiveDiscovered") { REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")); REQUIRE(!ObjectStore::table_for_object_type(realm->read_group(), "origin")); } - else { - // explicitly included embedded orphan types is an error - REQUIRE_THROWS(realm->update_schema( - add_table(schema, {"origin", - ObjectType::Embedded, - {{"link", PropertyType::Object | PropertyType::Nullable, "object"}}}))); - } } DYNAMIC_SECTION("cannot change existing table type" << mode_string) { - REQUIRE_THROWS(realm->update_schema(set_table_type(schema, "object", ObjectType::Embedded))); + INVALID_SCHEMA_CHANGE(*realm, set_table_type(schema, "object", ObjectType::Embedded), + "Class 'object' has been changed from TopLevel to Embedded."); } DYNAMIC_SECTION("indexes are updated when schema version is bumped" << mode_string) { @@ -3119,12 +3085,15 @@ TEST_CASE("migration: AdditiveDiscovered") { } DYNAMIC_SECTION("cannot change existing property types" << mode_string) { - REQUIRE_THROWS(realm->update_schema(set_type(schema, "object", "value", PropertyType::Float))); + INVALID_SCHEMA_CHANGE(*realm, set_type(schema, "object", "value", PropertyType::String), + "Property 'object.value' has been changed from 'int' to 'string'."); } DYNAMIC_SECTION("cannot change existing property nullability" << mode_string) { - REQUIRE_THROWS(realm->update_schema(set_optional(schema, "object", "value", true))); - REQUIRE_THROWS(realm->update_schema(set_optional(schema, "object", "value 2", false))); + INVALID_SCHEMA_CHANGE(*realm, set_optional(schema, "object", "value", true), + "Property 'object.value' has been made optional."); + INVALID_SCHEMA_CHANGE(*realm, set_optional(schema, "object", "value 2", false), + "Property 'object.value 2' has been made required."); } DYNAMIC_SECTION("cannot change existing link targets" << mode_string) { @@ -3133,11 +3102,13 @@ TEST_CASE("migration: AdditiveDiscovered") { { {"link", PropertyType::Object | PropertyType::Nullable, "object"}, }}))); - REQUIRE_THROWS(realm->update_schema(set_target(realm->schema(), "object 2", "link", "object 2"))); + INVALID_SCHEMA_CHANGE(*realm, set_target(realm->schema(), "object 2", "link", "object 2"), + "Property 'object 2.link' has been changed from '' to ''."); } DYNAMIC_SECTION("cannot change primary keys" << mode_string) { - REQUIRE_THROWS(realm->update_schema(set_primary_key(schema, "object", "value"))); + INVALID_SCHEMA_CHANGE(*realm, set_primary_key(schema, "object", "value"), + "Primary Key for class 'object' has been added."); REQUIRE_NOTHROW( realm->update_schema(add_table(schema, {"object 2", @@ -3145,7 +3116,8 @@ TEST_CASE("migration: AdditiveDiscovered") { {"pk", PropertyType::Int, Property::IsPrimary{true}}, }}))); - REQUIRE_THROWS(realm->update_schema(set_primary_key(realm->schema(), "object 2", ""))); + INVALID_SCHEMA_CHANGE(*realm, set_primary_key(realm->schema(), "object 2", ""), + "Primary Key for class 'object 2' has been removed."); } DYNAMIC_SECTION("schema version is allowed to go down" << mode_string) { @@ -3268,7 +3240,8 @@ TEST_CASE("migration: AdditiveDiscovered") { config2.schema = add_property(schema, "object", {"value 3", PropertyType::Int}); auto realm2 = Realm::get_shared_realm(config2); - REQUIRE_THROWS(realm->update_schema(add_property(schema, "object", {"value 3", PropertyType::Float}))); + INVALID_SCHEMA_CHANGE(*realm, add_property(schema, "object", {"value 3", PropertyType::Float}), + "Property 'object.value 3' has been changed from 'int' to 'float'."); REQUIRE(realm->schema().find("object")->persisted_properties.size() == 2); } @@ -3300,7 +3273,8 @@ TEST_CASE("migration: AdditiveDiscovered") { auto new_schema = add_property(remove_property(schema, "object", "value"), "object", {"value", PropertyType::Float}); // will deadlock if it tries to start a write transaction - REQUIRE_THROWS(realm2->update_schema(new_schema)); + INVALID_SCHEMA_CHANGE(*realm2, new_schema, + "Property 'object.value' has been changed from 'int' to 'float'."); } } } @@ -3326,107 +3300,138 @@ TEST_CASE("migration: Manual") { realm->update_schema(schema); auto col_keys = realm->read_group().get_table("class_object")->get_column_keys(); -#define REQUIRE_MIGRATION(schema, migration) \ +#define REQUIRE_MIGRATION(schema, migration, msg) \ do { \ Schema new_schema = (schema); \ - REQUIRE_THROWS(realm->update_schema(new_schema)); \ + REQUIRE_EXCEPTION(realm->update_schema(new_schema), SchemaMismatch, \ + Catch::Matchers::ContainsSubstring(msg)); \ REQUIRE(realm->schema_version() == 0); \ - REQUIRE_THROWS(realm->update_schema(new_schema, 1, [](SharedRealm, SharedRealm, Schema&) {})); \ + REQUIRE_EXCEPTION(realm->update_schema(new_schema, 1, [](SharedRealm, SharedRealm, Schema&) {}), \ + SchemaMismatch, Catch::Matchers::ContainsSubstring(msg)); \ REQUIRE(realm->schema_version() == 0); \ REQUIRE_NOTHROW(realm->update_schema(new_schema, 1, migration)); \ REQUIRE(realm->schema_version() == 1); \ } while (false) SECTION("add new table") { - REQUIRE_MIGRATION(add_table(schema, {"new table", - { - {"value", PropertyType::Int}, - }}), - [](SharedRealm, SharedRealm realm, Schema&) { - realm->read_group().add_table("class_new table")->add_column(type_Int, "value"); - }); + REQUIRE_MIGRATION( + add_table(schema, {"new table", + { + {"value", PropertyType::Int}, + }}), + [](SharedRealm, SharedRealm realm, Schema&) { + realm->read_group().add_table("class_new table")->add_column(type_Int, "value"); + }, + "Class 'new table' has been added."); } SECTION("add property to table") { - REQUIRE_MIGRATION(add_property(schema, "object", {"new", PropertyType::Int}), - [&](SharedRealm, SharedRealm realm, Schema&) { - get_table(realm, "object")->add_column(type_Int, "new"); - }); + REQUIRE_MIGRATION( + add_property(schema, "object", {"new", PropertyType::Int}), + [&](SharedRealm, SharedRealm realm, Schema&) { + get_table(realm, "object")->add_column(type_Int, "new"); + }, + "Property 'object.new' has been added."); } SECTION("remove property from table") { - REQUIRE_MIGRATION(remove_property(schema, "object", "value"), [&](SharedRealm, SharedRealm realm, Schema&) { - get_table(realm, "object")->remove_column(col_keys[1]); - }); + REQUIRE_MIGRATION( + remove_property(schema, "object", "value"), + [&](SharedRealm, SharedRealm realm, Schema&) { + get_table(realm, "object")->remove_column(col_keys[1]); + }, + "Property 'object.value' has been removed."); } SECTION("add primary key to table") { - REQUIRE_MIGRATION(set_primary_key(schema, "link origin", "not a pk"), - [&](SharedRealm, SharedRealm realm, Schema&) { - auto table = get_table(realm, "link origin"); - table->set_primary_key_column(table->get_column_key("not a pk")); - }); + REQUIRE_MIGRATION( + set_primary_key(schema, "link origin", "not a pk"), + [&](SharedRealm, SharedRealm realm, Schema&) { + auto table = get_table(realm, "link origin"); + table->set_primary_key_column(table->get_column_key("not a pk")); + }, + "Primary Key for class 'link origin' has been added."); } SECTION("remove primary key from table") { - REQUIRE_MIGRATION(set_primary_key(schema, "object", ""), [&](SharedRealm, SharedRealm realm, Schema&) { - get_table(realm, "object")->set_primary_key_column({}); - }); + REQUIRE_MIGRATION( + set_primary_key(schema, "object", ""), + [&](SharedRealm, SharedRealm realm, Schema&) { + get_table(realm, "object")->set_primary_key_column({}); + }, + "Primary Key for class 'object' has been removed."); } SECTION("change primary key") { - REQUIRE_MIGRATION(set_primary_key(schema, "object", "value"), [&](SharedRealm, SharedRealm realm, Schema&) { - get_table(realm, "object")->set_primary_key_column(col_keys[1]); - }); + REQUIRE_MIGRATION( + set_primary_key(schema, "object", "value"), + [&](SharedRealm, SharedRealm realm, Schema&) { + get_table(realm, "object")->set_primary_key_column(col_keys[1]); + }, + "Primary Key for class 'object' has changed from 'pk' to 'value'."); } SECTION("change property type") { - REQUIRE_MIGRATION(set_type(schema, "object", "value", PropertyType::Date), - [&](SharedRealm, SharedRealm realm, Schema&) { - auto table = get_table(realm, "object"); - table->remove_column(col_keys[1]); - auto col = table->add_column(type_Timestamp, "value"); - table->add_search_index(col); - }); + REQUIRE_MIGRATION( + set_type(schema, "object", "value", PropertyType::Date), + [&](SharedRealm, SharedRealm realm, Schema&) { + auto table = get_table(realm, "object"); + table->remove_column(col_keys[1]); + auto col = table->add_column(type_Timestamp, "value"); + table->add_search_index(col); + }, + "Property 'object.value' has been changed from 'int' to 'date'."); } SECTION("change link target") { - REQUIRE_MIGRATION(set_target(schema, "link origin", "object", "link origin"), - [&](SharedRealm, SharedRealm realm, Schema&) { - auto table = get_table(realm, "link origin"); - table->remove_column(table->get_column_keys()[1]); - table->add_column(*table, "object"); - }); + REQUIRE_MIGRATION( + set_target(schema, "link origin", "object", "link origin"), + [&](SharedRealm, SharedRealm realm, Schema&) { + auto table = get_table(realm, "link origin"); + table->remove_column(table->get_column_keys()[1]); + table->add_column(*table, "object"); + }, + "Property 'link origin.object' has been changed from '' to ''."); } SECTION("change linklist target") { - REQUIRE_MIGRATION(set_target(schema, "link origin", "array", "link origin"), - [&](SharedRealm, SharedRealm realm, Schema&) { - auto table = get_table(realm, "link origin"); - table->remove_column(table->get_column_keys()[2]); - table->add_column_list(*table, "array"); - }); + REQUIRE_MIGRATION( + set_target(schema, "link origin", "array", "link origin"), + [&](SharedRealm, SharedRealm realm, Schema&) { + auto table = get_table(realm, "link origin"); + table->remove_column(table->get_column_keys()[2]); + table->add_column_list(*table, "array"); + }, + "Property 'link origin.array' has been changed from 'array' to 'array'."); } SECTION("make property optional") { - REQUIRE_MIGRATION(set_optional(schema, "object", "value", true), - [&](SharedRealm, SharedRealm realm, Schema&) { - auto table = get_table(realm, "object"); - table->remove_column(col_keys[1]); - auto col = table->add_column(type_Int, "value", true); - table->add_search_index(col); - }); + REQUIRE_MIGRATION( + set_optional(schema, "object", "value", true), + [&](SharedRealm, SharedRealm realm, Schema&) { + auto table = get_table(realm, "object"); + table->remove_column(col_keys[1]); + auto col = table->add_column(type_Int, "value", true); + table->add_search_index(col); + }, + "Property 'object.value' has been made optional."); } SECTION("make property required") { - REQUIRE_MIGRATION(set_optional(schema, "object", "optional", false), - [&](SharedRealm, SharedRealm realm, Schema&) { - auto table = get_table(realm, "object"); - table->remove_column(col_keys[2]); - table->add_column(type_Int, "optional", false); - }); + REQUIRE_MIGRATION( + set_optional(schema, "object", "optional", false), + [&](SharedRealm, SharedRealm realm, Schema&) { + auto table = get_table(realm, "object"); + table->remove_column(col_keys[2]); + table->add_column(type_Int, "optional", false); + }, + "Property 'object.optional' has been made required."); } SECTION("add index") { - REQUIRE_MIGRATION(set_indexed(schema, "object", "optional", true), - [&](SharedRealm, SharedRealm realm, Schema&) { - get_table(realm, "object")->add_search_index(col_keys[2]); - }); + REQUIRE_MIGRATION( + set_indexed(schema, "object", "optional", true), + [&](SharedRealm, SharedRealm realm, Schema&) { + get_table(realm, "object")->add_search_index(col_keys[2]); + }, + "Property 'object.optional' has been made indexed."); } SECTION("remove index") { - REQUIRE_MIGRATION(set_indexed(schema, "object", "value", false), - [&](SharedRealm, SharedRealm realm, Schema&) { - get_table(realm, "object")->remove_search_index(col_keys[1]); - }); + REQUIRE_MIGRATION( + set_indexed(schema, "object", "value", false), + [&](SharedRealm, SharedRealm realm, Schema&) { + get_table(realm, "object")->remove_search_index(col_keys[1]); + }, + "Property 'object.value' has been made unindexed."); } SECTION("reorder properties") { auto schema2 = schema; @@ -3438,7 +3443,8 @@ TEST_CASE("migration: Manual") { SECTION("cannot lower schema version") { REQUIRE_NOTHROW(realm->update_schema(schema, 1, [](SharedRealm, SharedRealm, Schema&) {})); REQUIRE(realm->schema_version() == 1); - REQUIRE_THROWS(realm->update_schema(schema, 0, [](SharedRealm, SharedRealm, Schema&) {})); + REQUIRE_EXCEPTION(realm->update_schema(schema, 0, [](SharedRealm, SharedRealm, Schema&) {}), + InvalidSchemaVersion, "Provided schema version 0 is less than last set version 1."); REQUIRE(realm->schema_version() == 1); } @@ -3448,12 +3454,14 @@ TEST_CASE("migration: Manual") { auto realm2 = Realm::get_shared_realm(config); // will deadlock if it tries to start a write transaction REQUIRE_NOTHROW(realm2->update_schema(schema)); - REQUIRE_THROWS(realm2->update_schema(remove_property(schema, "object", "value"))); + REQUIRE_UPDATE_FAILS(*realm2, remove_property(schema, "object", "value"), + "Property 'object.value' has been removed."); } SECTION("null migration callback should throw SchemaMismatchException") { Schema new_schema = remove_property(schema, "object", "value"); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(realm->update_schema(new_schema, 1, nullptr), ErrorCodes::SchemaMismatch); + REQUIRE_EXCEPTION(realm->update_schema(new_schema, 1, nullptr), SchemaMismatch, + Catch::Matchers::ContainsSubstring("Property 'object.value' has been removed.")); } } diff --git a/test/object-store/object.cpp b/test/object-store/object.cpp index 9725685505c..8e013ad2ba3 100644 --- a/test/object-store/object.cpp +++ b/test/object-store/object.cpp @@ -35,10 +35,6 @@ #include #include -#if REALM_ENABLE_AUTH_TESTS -#include "sync/flx_sync_harness.hpp" -#endif // REALM_ENABLE_AUTH_TESTS - #include using namespace realm; @@ -485,7 +481,8 @@ TEST_CASE("object") { write([&] { obj.remove(); }); - REQUIRE_THROWS(require_change(object)); + REQUIRE_EXCEPTION(require_change(object), InvalidatedObject, + "Accessing object of type table which has been invalidated or deleted"); } SECTION("keypath filtered notifications") { @@ -1139,10 +1136,8 @@ TEST_CASE("object") { } SECTION("create throws for missing values if there is no default") { - REQUIRE_THROWS(create(AnyDict{ - {"_id", INT64_C(1)}, - {"float", 6.6f}, - })); + REQUIRE_EXCEPTION(create(AnyDict{{"_id", INT64_C(1)}, {"float", 6.6f}}), MissingPropertyValue, + "Missing value for property 'all types.bool'"); } SECTION("create always sets the PK first") { @@ -1599,22 +1594,25 @@ TEST_CASE("object") { {"uuid", UUID("3b241101-aaaa-bbbb-cccc-4136c566a962")}, {"dictionary", AnyDict{{"key", "value"s}}}, }); - REQUIRE_THROWS(create(AnyDict{ - {"_id", INT64_C(1)}, - {"bool", true}, - {"int", INT64_C(5)}, - {"float", 2.2f}, - {"double", 3.3}, - {"string", "hello"s}, - {"data", "olleh"s}, - {"date", Timestamp(10, 20)}, - {"object", AnyDict{{"_id", INT64_C(10)}, {"value", INT64_C(10)}}}, - {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}}, - {"object id", ObjectId("000000000000000000000001")}, - {"decimal", Decimal128("1.23e45")}, - {"uuid", UUID("3b241101-aaaa-bbbb-cccc-4136c566a962")}, - {"dictionary", AnyDict{{"key", "value"s}}}, - })); + REQUIRE_EXCEPTION(create(AnyDict{ + {"_id", INT64_C(1)}, + {"bool", true}, + {"int", INT64_C(5)}, + {"float", 2.2f}, + {"double", 3.3}, + {"string", "hello"s}, + {"data", "olleh"s}, + {"date", Timestamp(10, 20)}, + {"object", AnyDict{{"_id", INT64_C(10)}, {"value", INT64_C(10)}}}, + {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}}, + {"object id", ObjectId("000000000000000000000001")}, + {"decimal", Decimal128("1.23e45")}, + {"uuid", UUID("3b241101-aaaa-bbbb-cccc-4136c566a962")}, + {"dictionary", AnyDict{{"key", "value"s}}}, + }), + ObjectAlreadyExists, + "Attempting to create an object of type 'all types' with an existing primary key value " + "'not implemented'"); } SECTION("create with explicit null pk does not fall back to default") { @@ -1748,13 +1746,17 @@ TEST_CASE("object") { auto linking = util::any_cast(linkobj.get_property_value(d, "origin")); REQUIRE(linking.size() == 1); - REQUIRE_THROWS(obj.set_property_value(d, "_id", std::any(INT64_C(5)))); - REQUIRE_THROWS(obj.set_property_value(d, "not a property", std::any(INT64_C(5)))); + REQUIRE_EXCEPTION(obj.set_property_value(d, "_id", std::any(INT64_C(5))), ModifyPrimaryKey, + "Cannot modify primary key after creation: 'all types._id'"); + REQUIRE_EXCEPTION(obj.set_property_value(d, "not a property", std::any(INT64_C(5))), InvalidProperty, + "Property 'all types.not a property' does not exist"); r->commit_transaction(); - REQUIRE_THROWS(obj.get_property_value(d, "not a property")); - REQUIRE_THROWS(obj.set_property_value(d, "int", std::any(INT64_C(5)))); + REQUIRE_EXCEPTION(obj.get_property_value(d, "not a property"), InvalidProperty, + "Property 'all types.not a property' does not exist"); + REQUIRE_EXCEPTION(obj.set_property_value(d, "int", std::any(INT64_C(5))), WrongTransactionState, + "Cannot modify managed objects outside of a write transaction."); } SECTION("setter has correct create policy") { @@ -1993,9 +1995,8 @@ TEST_CASE("Embedded Object") { SECTION("throws when given a managed object") { realm->begin_transaction(); - REQUIRE_THROWS_WITH( - obj.set_property_value(ctx, "object", obj.get_property_value(ctx, "object")), - "Cannot set a link to an existing managed embedded object"); + REQUIRE_EXCEPTION(obj.set_property_value(ctx, "object", obj.get_property_value(ctx, "object")), + InvalidArgument, "Cannot set a link to an existing managed embedded object"); realm->cancel_transaction(); } @@ -2169,33 +2170,25 @@ TEST_CASE("Embedded Object") { } } -#if REALM_ENABLE_AUTH_TESTS +#if REALM_ENABLE_SYNC TEST_CASE("Asymmetric Object") { Schema schema{ {"asymmetric", ObjectSchema::ObjectType::TopLevelAsymmetric, - { - {"_id", PropertyType::Int, Property::IsPrimary{true}}, - {"location", PropertyType::Int}, - {"reading", PropertyType::Int}, - }}, + {{"_id", PropertyType::Int, Property::IsPrimary{true}}}}, {"asymmetric_link", ObjectSchema::ObjectType::TopLevelAsymmetric, { {"_id", PropertyType::Int, Property::IsPrimary{true}}, {"location", PropertyType::Mixed | PropertyType::Nullable}, }}, - {"table", - { - {"_id", PropertyType::Int, Property::IsPrimary{true}}, - {"location", PropertyType::Int}, - {"reading", PropertyType::Int}, - }}, + {"table", {{"_id", PropertyType::Int, Property::IsPrimary{true}}}}, }; - realm::app::FLXSyncTestHarness harness("asymmetric_sync", {schema}); - SyncTestFile config(harness.app()->current_user(), schema, SyncConfig::FLXSyncEnabled{}); + TestSyncManager tsm({}, {.start_immediately = false}); + SyncTestFile config(tsm.fake_user(), schema, SyncConfig::FLXSyncEnabled{}); + config.sync_config->flx_sync_requested = true; auto realm = Realm::get_shared_realm(config); { @@ -2213,35 +2206,24 @@ TEST_CASE("Asymmetric Object") { }; SECTION("Basic object creation") { - auto obj = create( - AnyDict{ - {"_id", INT64_C(1)}, - {"location", INT64_C(10)}, - {"reading", INT64_C(20)}, - }, - "asymmetric"); + auto obj = create(AnyDict{{"_id", INT64_C(1)}}, "asymmetric"); // Object returned is not valid. REQUIRE(!obj.obj().is_valid()); // Object gets deleted immediately. - REQUIRE(Results(realm, realm->read_group().get_table("class_table")).size() == 0); + REQUIRE(ObjectStore::is_empty(realm->read_group())); } SECTION("Outgoing link not allowed") { - auto obj = create( - AnyDict{ - {"_id", INT64_C(1)}, - {"location", INT64_C(10)}, - {"reading", INT64_C(20)}, - }, - "table"); + auto obj = create(AnyDict{{"_id", INT64_C(1)}}, "table"); auto table = realm->read_group().get_table("class_table"); - REQUIRE_THROWS(create( - AnyDict{ - {"_id", INT64_C(1)}, - {"location", Mixed(ObjLink{table->get_key(), obj.obj().get_key()})}, - }, - "asymmetric_link")); + REQUIRE_EXCEPTION(create( + AnyDict{ + {"_id", INT64_C(1)}, + {"location", Mixed(ObjLink{table->get_key(), obj.obj().get_key()})}, + }, + "asymmetric_link"), + IllegalOperation, "Links not allowed in asymmetric tables"); } } -#endif // REALM_ENABLE_AUTH_TESTS +#endif // REALM_ENABLE_SYNC diff --git a/test/object-store/primitive_list.cpp b/test/object-store/primitive_list.cpp index be8bf18c974..29d65ddff1c 100644 --- a/test/object-store/primitive_list.cpp +++ b/test/object-store/primitive_list.cpp @@ -128,7 +128,7 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", cf::MixedVal, cf::Int, cf:: config.cache = false; config.automatic_change_notifications = false; config.schema = Schema{ - {"object", {{"value", PropertyType::Array | TestType::property_type()}}}, + {"object", {{"value", PropertyType::Array | TestType::property_type}}}, }; auto r = Realm::get_shared_realm(config); auto r2 = Realm::get_shared_realm(config); @@ -163,8 +163,8 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", cf::MixedVal, cf::Int, cf:: } SECTION("get_type()") { - REQUIRE(list.get_type() == TestType::property_type()); - REQUIRE(results.get_type() == TestType::property_type()); + REQUIRE(list.get_type() == TestType::property_type); + REQUIRE(results.get_type() == TestType::property_type); } SECTION("get_object_type()") { @@ -205,22 +205,30 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", cf::MixedVal, cf::Int, cf:: SECTION("invalidate") { r->invalidate(); - REQUIRE_THROWS(list.verify_attached()); + REQUIRE_EXCEPTION(list.verify_attached(), InvalidatedObject, + "List is no longer valid. Either the parent object was deleted or the containing Realm " + "has been invalidated or closed."); } SECTION("close") { r->close(); - REQUIRE_THROWS(list.verify_attached()); + REQUIRE_EXCEPTION(list.verify_attached(), InvalidatedObject, + "List is no longer valid. Either the parent object was deleted or the containing Realm " + "has been invalidated or closed."); } SECTION("delete row") { obj.remove(); - REQUIRE_THROWS(list.verify_attached()); + REQUIRE_EXCEPTION(list.verify_attached(), InvalidatedObject, + "List is no longer valid. Either the parent object was deleted or the containing Realm " + "has been invalidated or closed."); } SECTION("rollback transaction creating list") { r->cancel_transaction(); - REQUIRE_THROWS(list.verify_attached()); + REQUIRE_EXCEPTION(list.verify_attached(), InvalidatedObject, + "List is no longer valid. Either the parent object was deleted or the containing Realm " + "has been invalidated or closed."); } } @@ -229,22 +237,29 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", cf::MixedVal, cf::Int, cf:: SECTION("invalidate") { r->invalidate(); - REQUIRE_THROWS(list.verify_in_transaction()); + REQUIRE_EXCEPTION(list.verify_in_transaction(), InvalidatedObject, + "List is no longer valid. Either the parent object was deleted or the containing Realm " + "has been invalidated or closed."); } SECTION("close") { r->close(); - REQUIRE_THROWS(list.verify_in_transaction()); + REQUIRE_EXCEPTION(list.verify_in_transaction(), InvalidatedObject, + "List is no longer valid. Either the parent object was deleted or the containing Realm " + "has been invalidated or closed."); } SECTION("delete row") { obj.remove(); - REQUIRE_THROWS(list.verify_in_transaction()); + REQUIRE_EXCEPTION(list.verify_in_transaction(), InvalidatedObject, + "List is no longer valid. Either the parent object was deleted or the containing Realm " + "has been invalidated or closed."); } SECTION("end write") { r->commit_transaction(); - REQUIRE_THROWS(list.verify_in_transaction()); + REQUIRE_EXCEPTION(list.verify_in_transaction(), WrongTransactionState, + "Cannot modify managed List outside of a write transaction."); } } @@ -335,10 +350,14 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", cf::MixedVal, cf::Int, cf:: REQUIRE(util::any_cast(list.get(ctx, i)) == Boxed(values[i])); REQUIRE(util::any_cast(results.get(ctx, i)) == Boxed(values[i])); } - REQUIRE_THROWS(list.get(values.size())); - REQUIRE_THROWS(results.get(values.size())); - REQUIRE_THROWS(list.get(ctx, values.size())); - REQUIRE_THROWS(results.get(ctx, values.size())); + size_t n = values.size(); + auto list_msg = + util::format("Requested index %1 calling get() on list 'object.value' when max is %2", n, n - 1); + auto results_msg = util::format("Requested index %1 calling get() on Results when max is %2", n, n - 1); + REQUIRE_THROWS_OUT_OF_BOUNDS(list.get(values.size()), n, n, list_msg); + REQUIRE_THROWS_OUT_OF_BOUNDS(results.get(values.size()), n, n, results_msg); + REQUIRE_THROWS_OUT_OF_BOUNDS(list.get(ctx, values.size()), n, n, list_msg); + REQUIRE_THROWS_OUT_OF_BOUNDS(results.get(ctx, values.size()), n, n, results_msg); } SECTION("first()") { @@ -377,7 +396,9 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", cf::MixedVal, cf::Int, cf:: REQUIRE(results.get_any(i) == val); } - REQUIRE_THROWS(list.set(list.size(), static_cast(values[0]))); + size_t n = values.size(); + auto msg = util::format("Requested index %1 calling set() on list 'object.value' when max is %2", n, n - 1); + REQUIRE_THROWS_OUT_OF_BOUNDS(list.set(list.size(), static_cast(values[0])), n, n, msg); } SECTION("find()") { @@ -405,14 +426,14 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", cf::MixedVal, cf::Int, cf:: } SECTION("sorted index_of()") { auto sorted = list.sort({{"self", true}}); - std::sort(begin(values), end(values), cf::less()); + std::sort(begin(values), end(values), std::less()); for (size_t i = 0; i < values.size(); ++i) { CAPTURE(i); REQUIRE(sorted.index_of(values[i]) == i); } sorted = list.sort({{"self", false}}); - std::sort(begin(values), end(values), cf::greater()); + std::sort(begin(values), end(values), std::greater()); for (size_t i = 0; i < values.size(); ++i) { CAPTURE(i); REQUIRE(sorted.index_of(values[i]) == i); @@ -421,7 +442,7 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", cf::MixedVal, cf::Int, cf:: #if 0 SECTION("filtered index_of()") { - REQUIRE_THROWS(results.index_of(table->get(0))); + REQUIRE_EXCEPTION(results.index_of(table->get(0)), InvalidSession, "asdf"); auto q = TestType::unwrap(values[0], [&] (auto v) { return table->get_subtable(0, 0)->column(0) != v; }); auto filtered = list.filter(std::move(q)); for (size_t i = 1; i < values.size(); ++i) { @@ -436,23 +457,23 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", cf::MixedVal, cf::Int, cf:: auto sorted = list.sort(SortDescriptor({{col}}, {true})); auto sorted2 = list.sort({{"self", true}}); - std::sort(begin(values), end(values), cf::less()); + std::sort(begin(values), end(values), std::less()); REQUIRE(sorted == values); REQUIRE(sorted2 == values); sorted = list.sort(SortDescriptor({{col}}, {false})); sorted2 = list.sort({{"self", false}}); - std::sort(begin(values), end(values), cf::greater()); + std::sort(begin(values), end(values), std::greater()); REQUIRE(sorted == values); REQUIRE(sorted2 == values); auto execption_string = util::format("Cannot sort on key path 'not self': arrays of '%1' can only be sorted on 'self'", - string_for_property_type(TestType::property_type() & ~PropertyType::Flags)); + string_for_property_type(TestType::property_type & ~PropertyType::Flags)); REQUIRE_THROWS_WITH(list.sort({{"not self", true}}), execption_string); REQUIRE_THROWS_WITH(list.sort({{"self", true}, {"self", false}}), util::format("Cannot sort array of '%1' on more than one key path", - string_for_property_type(TestType::property_type() & ~PropertyType::Flags))); + string_for_property_type(TestType::property_type & ~PropertyType::Flags))); } SECTION("distinct()") { @@ -472,10 +493,10 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", cf::MixedVal, cf::Int, cf:: REQUIRE_THROWS_WITH( results.distinct({{"not self"}}), util::format("Cannot sort on key path 'not self': arrays of '%1' can only be sorted on 'self'", - string_for_property_type(TestType::property_type() & ~PropertyType::Flags))); + string_for_property_type(TestType::property_type & ~PropertyType::Flags))); REQUIRE_THROWS_WITH(results.distinct({{"self"}, {"self"}}), util::format("Cannot sort array of '%1' on more than one key path", - string_for_property_type(TestType::property_type() & ~PropertyType::Flags))); + string_for_property_type(TestType::property_type & ~PropertyType::Flags))); } #if 0 SECTION("filter()") { @@ -494,57 +515,75 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", cf::MixedVal, cf::Int, cf:: #endif SECTION("min()") { - if (!TestType::can_minmax()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(list.min(), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.min(), ErrorCodes::IllegalOperation); - return; + if constexpr (!TestType::can_minmax) { + REQUIRE_EXCEPTION( + list.min(), IllegalOperation, + util::format("Operation 'min' not supported for %1 list 'object.value'", TestType::name)); + REQUIRE_EXCEPTION( + results.min(), IllegalOperation, + util::format("Operation 'min' not supported for %1 list 'object.value'", TestType::name)); + } + else { + REQUIRE(cf::get(*list.min()) == TestType::min()); + REQUIRE(cf::get(*results.min()) == TestType::min()); + list.remove_all(); + REQUIRE(list.min() == util::none); + REQUIRE(results.min() == util::none); } - - REQUIRE(cf::get(*list.min()) == TestType::min()); - REQUIRE(cf::get(*results.min()) == TestType::min()); - list.remove_all(); - REQUIRE(list.min() == util::none); - REQUIRE(results.min() == util::none); } SECTION("max()") { - if (!TestType::can_minmax()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(list.max(), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.max(), ErrorCodes::IllegalOperation); - return; + if constexpr (!TestType::can_minmax) { + REQUIRE_EXCEPTION( + list.max(), IllegalOperation, + util::format("Operation 'max' not supported for %1 list 'object.value'", TestType::name)); + REQUIRE_EXCEPTION( + results.max(), IllegalOperation, + util::format("Operation 'max' not supported for %1 list 'object.value'", TestType::name)); + } + else { + REQUIRE(cf::get(list.max().value()) == TestType::max()); + REQUIRE(cf::get(results.max().value()) == TestType::max()); + list.remove_all(); + REQUIRE(list.max() == util::none); + REQUIRE(results.max() == util::none); } - - REQUIRE(cf::get(list.max().value()) == TestType::max()); - REQUIRE(cf::get(results.max().value()) == TestType::max()); - list.remove_all(); - REQUIRE(list.max() == util::none); - REQUIRE(results.max() == util::none); } SECTION("sum()") { - if (!TestType::can_sum()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(list.sum(), ErrorCodes::IllegalOperation); - return; + if constexpr (!TestType::can_sum) { + REQUIRE_EXCEPTION( + list.sum(), IllegalOperation, + util::format("Operation 'sum' not supported for %1 list 'object.value'", TestType::name)); + REQUIRE_EXCEPTION( + results.sum(), IllegalOperation, + util::format("Operation 'sum' not supported for %1 list 'object.value'", TestType::name)); + } + else { + REQUIRE(cf::get(list.sum()) == TestType::sum()); + REQUIRE(cf::get(*results.sum()) == TestType::sum()); + list.remove_all(); + REQUIRE(cf::get(list.sum()) == TestType::empty_sum_value()); + REQUIRE(cf::get(*results.sum()) == TestType::empty_sum_value()); } - - REQUIRE(cf::get(list.sum()) == TestType::sum()); - REQUIRE(cf::get(*results.sum()) == TestType::sum()); - list.remove_all(); - REQUIRE(cf::get(list.sum()) == TestType::empty_sum_value()); - REQUIRE(cf::get(*results.sum()) == TestType::empty_sum_value()); } SECTION("average()") { - if (!TestType::can_average()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(list.average(), ErrorCodes::IllegalOperation); - return; + if constexpr (!TestType::can_average) { + REQUIRE_EXCEPTION( + list.average(), IllegalOperation, + util::format("Operation 'average' not supported for %1 list 'object.value'", TestType::name)); + REQUIRE_EXCEPTION( + results.average(), IllegalOperation, + util::format("Operation 'average' not supported for %1 list 'object.value'", TestType::name)); + } + else { + REQUIRE(cf::get(*list.average()) == TestType::average()); + REQUIRE(cf::get(*results.average()) == TestType::average()); + list.remove_all(); + REQUIRE(list.average() == util::none); + REQUIRE(results.average() == util::none); } - - REQUIRE(cf::get(*list.average()) == TestType::average()); - REQUIRE(cf::get(*results.average()) == TestType::average()); - list.remove_all(); - REQUIRE(list.average() == util::none); - REQUIRE(results.average() == util::none); } SECTION("operator==()") { diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index 4b062b30dfd..b71b1525d17 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -132,53 +132,62 @@ TEST_CASE("SharedRealm: get_shared_realm()") { SECTION("should validate that the config is sensible") { SECTION("bad encryption key") { config.encryption_key = std::vector(2, 0); - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION(Realm::get_shared_realm(config), InvalidEncryptionKey, + "Encryption key must be 64 bytes."); } SECTION("schema without schema version") { config.schema_version = ObjectStore::NotVersioned; - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination, + "A schema version must be specified when the schema is specified"); } SECTION("migration function for immutable") { config.schema_mode = SchemaMode::Immutable; config.migration_function = [](auto, auto, auto) {}; - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination, + "Realms opened in immutable mode do not use a migration function"); } SECTION("migration function for read-only") { config.schema_mode = SchemaMode::ReadOnly; config.migration_function = [](auto, auto, auto) {}; - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination, + "Realms opened in read-only mode do not use a migration function"); } SECTION("migration function for additive discovered") { config.schema_mode = SchemaMode::AdditiveDiscovered; config.migration_function = [](auto, auto, auto) {}; - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination, + "Realms opened in Additive-only schema mode do not use a migration function"); } SECTION("migration function for additive explicit") { config.schema_mode = SchemaMode::AdditiveExplicit; config.migration_function = [](auto, auto, auto) {}; - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination, + "Realms opened in Additive-only schema mode do not use a migration function"); } SECTION("initialization function for immutable") { config.schema_mode = SchemaMode::Immutable; config.initialization_function = [](auto) {}; - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination, + "Realms opened in immutable mode do not use an initialization function"); } SECTION("initialization function for read-only") { config.schema_mode = SchemaMode::ReadOnly; config.initialization_function = [](auto) {}; - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination, + "Realms opened in read-only mode do not use an initialization function"); } SECTION("in-memory encrypted realms are rejected") { config.in_memory = true; config.encryption_key = make_test_encryption_key(); - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination, + "Encryption is not supported for in-memory realms"); } } @@ -186,7 +195,9 @@ TEST_CASE("SharedRealm: get_shared_realm()") { SECTION("schema version") { auto realm = Realm::get_shared_realm(config); config.schema_version = 2; - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION( + Realm::get_shared_realm(config), MismatchedConfig, + Catch::Matchers::Matches("Realm at path '.*' already opened with different schema version.")); config.schema = util::none; config.schema_version = ObjectStore::NotVersioned; @@ -196,13 +207,17 @@ TEST_CASE("SharedRealm: get_shared_realm()") { SECTION("schema mode") { auto realm = Realm::get_shared_realm(config); config.schema_mode = SchemaMode::Manual; - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION( + Realm::get_shared_realm(config), MismatchedConfig, + Catch::Matchers::Matches("Realm at path '.*' already opened with a different schema mode.")); } SECTION("durability") { auto realm = Realm::get_shared_realm(config); config.in_memory = true; - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION( + Realm::get_shared_realm(config), MismatchedConfig, + Catch::Matchers::Matches("Realm at path '.*' already opened with different inMemory settings.")); } SECTION("schema") { @@ -210,7 +225,9 @@ TEST_CASE("SharedRealm: get_shared_realm()") { config.schema = Schema{ {"object", {{"value", PropertyType::Int}, {"value2", PropertyType::Int}}}, }; - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION( + Realm::get_shared_realm(config), SchemaMismatch, + Catch::Matchers::ContainsSubstring("Migration is required due to the following errors:")); } } @@ -397,7 +414,7 @@ TEST_CASE("SharedRealm: get_shared_realm()") { REQUIRE(it->persisted_properties[0].column_key == table->get_column_key("value")); SECTION("refreshing an immutable Realm throws") { - REQUIRE_THROWS_WITH(realm->refresh(), "Can't refresh a read-only Realm."); + REQUIRE_THROWS_WITH(realm->refresh(), "Can't refresh an immutable Realm."); } } @@ -438,7 +455,8 @@ TEST_CASE("SharedRealm: get_shared_realm()") { util::format("%1realm_%2.note", util::normalize_dir(DBOptions::get_sys_tmp_dir()), std::hash()(config.path)); // Mirror internal implementation REQUIRE(util::try_make_dir(sys_fallback_file)); - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION(Realm::get_shared_realm(config), FileAlreadyExists, + Catch::Matchers::EndsWith(".note exists and is not a fifo.")); util::remove_dir(config.path + ".note"); util::remove_dir(sys_fallback_file); } @@ -1086,7 +1104,8 @@ TEST_CASE("SharedRealm: convert") { SECTION("cannot convert from local realm to flx sync") { SyncTestFile sync_config(tsm.app()->current_user(), schema, SyncConfig::FLXSyncEnabled{}); auto local_realm = Realm::get_shared_realm(local_config1); - REQUIRE_THROWS(local_realm->convert(sync_config)); + REQUIRE_EXCEPTION(local_realm->convert(sync_config), IllegalOperation, + "Cannot convert Realms to flexible sync Realms"); } SECTION("can copy a local realm to a local realm") { @@ -2415,19 +2434,48 @@ TEST_CASE("SharedRealm: close()") { auto realm = Realm::get_shared_realm(config); SECTION("all functions throw ClosedRealmException after close") { - realm->close(); + const char* msg = "Cannot access realm that has been closed."; + realm->close(); REQUIRE(realm->is_closed()); + REQUIRE_EXCEPTION(realm->verify_open(), ClosedRealm, msg); + + REQUIRE_EXCEPTION(realm->update_schema(Schema{}), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->rename_property(Schema{}, "", "", ""), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->set_schema_subset(Schema{}), ClosedRealm, msg); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(realm->read_group(), ErrorCodes::ClosedRealm); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(realm->begin_transaction(), ErrorCodes::ClosedRealm); + REQUIRE_EXCEPTION(realm->begin_transaction(), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->commit_transaction(), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->cancel_transaction(), ClosedRealm, msg); REQUIRE(!realm->is_in_transaction()); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(realm->commit_transaction(), ErrorCodes::WrongTransactionState); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(realm->cancel_transaction(), ErrorCodes::WrongTransactionState); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(realm->refresh(), ErrorCodes::ClosedRealm); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(realm->invalidate(), ErrorCodes::ClosedRealm); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(realm->compact(), ErrorCodes::ClosedRealm); + REQUIRE_EXCEPTION(realm->async_begin_transaction(nullptr), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->async_commit_transaction(nullptr), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->async_cancel_transaction(0), ClosedRealm, msg); + REQUIRE_FALSE(realm->is_in_async_transaction()); + + REQUIRE_EXCEPTION(realm->freeze(), ClosedRealm, msg); + REQUIRE_FALSE(realm->is_frozen()); + REQUIRE_EXCEPTION(realm->get_number_of_versions(), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->read_transaction_version(), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->duplicate(), ClosedRealm, msg); + + REQUIRE_EXCEPTION(realm->enable_wait_for_change(), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->wait_for_change(), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->wait_for_change_release(), ClosedRealm, msg); + + REQUIRE_NOTHROW(realm->notify()); + REQUIRE_EXCEPTION(realm->refresh(), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->invalidate(), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->compact(), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->convert(realm->config()), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->write_copy(), ClosedRealm, msg); + +#if REALM_ENABLE_SYNC + REQUIRE_FALSE(realm->sync_session()); + REQUIRE_EXCEPTION(realm->get_latest_subscription_set(), ClosedRealm, msg); + REQUIRE_EXCEPTION(realm->get_active_subscription_set(), ClosedRealm, msg); +#endif } SECTION("fully closes database file even with live notifiers") { @@ -2483,7 +2531,7 @@ TEST_CASE("Realm::delete_files()") { } SECTION("Trying to delete files of an open Realm fails.") { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(Realm::delete_files(path), ErrorCodes::DeleteOnOpenRealm); + REQUIRE_THROWS_WITH_CODE(Realm::delete_files(path), ErrorCodes::DeleteOnOpenRealm); REQUIRE(util::File::exists(path + ".lock")); REQUIRE(util::File::exists(path)); REQUIRE(util::File::exists(path + ".management")); @@ -2553,16 +2601,19 @@ TEST_CASE("ShareRealm: in-memory mode from buffer") { // Test invalid configs realm::Realm::Config config3; config3.realm_data = realm_buffer.get(); - REQUIRE_THROWS(Realm::get_shared_realm(config3)); // missing in_memory and immutable + REQUIRE_EXCEPTION(Realm::get_shared_realm(config3), IllegalCombination, + "In-memory realms initialized from memory buffers can only be opened in read-only mode"); config3.in_memory = true; config3.schema_mode = SchemaMode::Immutable; config3.path = "path"; - REQUIRE_THROWS(Realm::get_shared_realm(config3)); // both buffer and path + REQUIRE_EXCEPTION(Realm::get_shared_realm(config3), IllegalCombination, + "Specifying both memory buffer and path is invalid"); config3.path = ""; - config3.encryption_key = {'a'}; - REQUIRE_THROWS(Realm::get_shared_realm(config3)); // both buffer and encryption + config3.encryption_key = std::vector(64, 'a'); + REQUIRE_EXCEPTION(Realm::get_shared_realm(config3), IllegalCombination, + "Memory buffers do not support encryption"); } } @@ -3529,7 +3580,7 @@ TEST_CASE("RealmCoordinator: get_unbound_realm()") { auto realm = Realm::get_shared_realm(std::move(ref)); REQUIRE_NOTHROW(realm->verify_thread()); std::thread([&] { - REQUIRE_THROWS(realm->verify_thread()); + REQUIRE_EXCEPTION(realm->verify_thread(), WrongThread, "Realm accessed from incorrect thread."); }).join(); } diff --git a/test/object-store/results.cpp b/test/object-store/results.cpp index 22928ef5503..afdc829ae45 100644 --- a/test/object-store/results.cpp +++ b/test/object-store/results.cpp @@ -104,9 +104,7 @@ struct TestContext : CppContext { TEST_CASE("notifications: async delivery") { -#ifndef _WIN32 _impl::RealmCoordinator::assert_no_open_realms(); -#endif InMemoryTestFile config; config.automatic_change_notifications = false; @@ -843,9 +841,7 @@ TEST_CASE("notifications: async delivery") { } TEST_CASE("notifications: skip") { -#ifndef _WIN32 _impl::RealmCoordinator::assert_no_open_realms(); -#endif InMemoryTestFile config; config.cache = false; config.automatic_change_notifications = false; @@ -979,14 +975,16 @@ TEST_CASE("notifications: skip") { } SECTION("skipping must be done from within a write transaction") { - REQUIRE_THROWS(token1.suppress_next()); + REQUIRE_EXCEPTION( + token1.suppress_next(), WrongTransactionState, + "Suppressing the notification from a write transaction must be done from inside the write transaction."); } SECTION("skipping must be done from the Realm's thread") { advance_and_notify(*r); r->begin_transaction(); std::thread([&] { - REQUIRE_THROWS(token1.suppress_next()); + REQUIRE_EXCEPTION(token1.suppress_next(), WrongThread, "Realm accessed from incorrect thread."); }).join(); r->cancel_transaction(); } @@ -1209,9 +1207,7 @@ TEST_CASE("notifications: skip") { } TEST_CASE("notifications: TableView delivery") { -#ifndef _WIN32 _impl::RealmCoordinator::assert_no_open_realms(); -#endif InMemoryTestFile config; config.automatic_change_notifications = false; @@ -1420,9 +1416,7 @@ TEST_CASE("notifications: sync") { #endif TEST_CASE("notifications: results") { -#ifndef _WIN32 _impl::RealmCoordinator::assert_no_open_realms(); -#endif InMemoryTestFile config; config.cache = false; config.automatic_change_notifications = false; @@ -3139,9 +3133,7 @@ TEST_CASE("results: notifications after move") { } TEST_CASE("results: notifier with no callbacks") { -#ifndef _WIN32 _impl::RealmCoordinator::assert_no_open_realms(); -#endif InMemoryTestFile config; config.cache = false; config.automatic_change_notifications = false; @@ -3217,33 +3209,6 @@ TEST_CASE("results: notifier with no callbacks") { } } -TEST_CASE("results: error messages") { - InMemoryTestFile config; - config.schema = Schema{ - {"object", - { - {"value", PropertyType::String}, - }}, - }; - - auto r = Realm::get_shared_realm(config); - auto table = r->read_group().get_table("class_object"); - Results results(r, table); - - r->begin_transaction(); - table->create_object(); - r->commit_transaction(); - - SECTION("out of bounds access") { - REQUIRE_THROWS_WITH(results.get(5), "Requested index 5 calling get() on Results when max is 0"); - } - - SECTION("unsupported aggregate operation") { - REQUIRE_THROWS_WITH(results.sum("value"), - "Cannot sum property 'value': operation not supported for 'string' properties"); - } -} - TEST_CASE("results: snapshots") { InMemoryTestFile config; config.cache = false; @@ -3544,7 +3509,8 @@ TEST_CASE("results: snapshots") { Query q = table->column(col_value) > 0; Results results(r, q.find_all()); auto snapshot = results.snapshot(); - CHECK_THROWS(snapshot.add_notification_callback([](CollectionChangeSet) {})); + REQUIRE_EXCEPTION(snapshot.add_notification_callback([](CollectionChangeSet) {}), IllegalOperation, + "Cannot create asynchronous query for snapshotted Results."); } SECTION("accessors should return none for detached row") { @@ -3555,7 +3521,6 @@ TEST_CASE("results: snapshots") { Results results(r, table); auto snapshot = results.snapshot(); write([=] { - ; table->clear(); }); @@ -3994,7 +3959,7 @@ TEMPLATE_TEST_CASE("results: get()", "", ResultsFromTable, ResultsFromQuery CHECK(results.get(i).get(col_value) == i); for (int i = 0; i < 10; ++i) CHECK(results.get(i).get(col_value) == i); - CHECK_THROWS(results.get(11)); + REQUIRE_EXCEPTION(results.get(10), OutOfBounds, "Requested index 10 calling get() on Results when max is 9"); } SECTION("sequential in decreasing order") { for (int i = 9; i >= 0; --i) @@ -4096,10 +4061,10 @@ TEMPLATE_TEST_CASE("results: accessor interface", "", ResultsFromTable, ResultsF Results empty_results = TestType::call(r, table); CppContext ctx(r, &empty_results.get_object_schema()); -#ifndef _WIN32 SECTION("no objects") { SECTION("get()") { - CHECK_THROWS_WITH(empty_results.get(ctx, 0), "Requested index 0 calling get() on Results when empty"); + REQUIRE_EXCEPTION(empty_results.get(ctx, 0), OutOfBounds, + "Requested index 0 calling get() on Results when empty"); } SECTION("first()") { CHECK_FALSE(empty_results.first(ctx)); @@ -4108,7 +4073,6 @@ TEMPLATE_TEST_CASE("results: accessor interface", "", ResultsFromTable, ResultsF CHECK_FALSE(empty_results.last(ctx)); } } -#endif r->begin_transaction(); auto other_obj = r->read_group().get_table("class_different type")->create_object(); @@ -4122,7 +4086,8 @@ TEMPLATE_TEST_CASE("results: accessor interface", "", ResultsFromTable, ResultsF SECTION("get()") { for (int i = 0; i < 10; ++i) CHECK(util::any_cast(results.get(ctx, i)).get_column_value("value") == i); - CHECK_THROWS_WITH(results.get(ctx, 10), "Requested index 10 calling get() on Results when max is 9"); + REQUIRE_EXCEPTION(results.get(ctx, 10), OutOfBounds, + "Requested index 10 calling get() on Results when max is 9"); } SECTION("first()") { @@ -4139,17 +4104,17 @@ TEMPLATE_TEST_CASE("results: accessor interface", "", ResultsFromTable, ResultsF REQUIRE(results.index_of(ctx, std::any(results.get(i))) == i); } SECTION("wrong object type") { - CHECK_THROWS_WITH(results.index_of(ctx, std::any(other_obj)), + REQUIRE_EXCEPTION(results.index_of(ctx, std::any(other_obj)), ObjectTypeMismatch, "Object of type 'different type' does not match Results type 'object'"); } SECTION("wrong realm") { auto obj = r2->read_group().get_table("class_object")->get_object(0); - CHECK_THROWS_WITH(results.index_of(ctx, std::any(obj)), + REQUIRE_EXCEPTION(results.index_of(ctx, std::any(obj)), ObjectTypeMismatch, "Object of type 'object' does not match Results type 'object'"); } SECTION("detached object") { Obj detached_obj; - CHECK_THROWS_WITH(results.index_of(ctx, std::any(detached_obj)), + REQUIRE_EXCEPTION(results.index_of(ctx, std::any(detached_obj)), StaleAccessor, "Attempting to access an invalid object"); } } @@ -4275,8 +4240,9 @@ TEMPLATE_TEST_CASE("results: aggregate", "[query][aggregate]", ResultsFromTable, REQUIRE(results.max(col_float)->get_float() == 2.f); REQUIRE(results.max(col_double)->get_double() == 2.0); REQUIRE(results.max(col_date)->get_timestamp() == Timestamp(2, 0)); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.max(col_int_list), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.max(col_invalid), ErrorCodes::InvalidProperty); + REQUIRE_EXCEPTION(results.max(col_int_list), IllegalOperation, + "Operation 'max' not supported for int list 'object.int list'"); + REQUIRE_EXCEPTION(results.max(col_invalid), InvalidProperty, "Invalid column key"); } SECTION("min") { @@ -4284,26 +4250,31 @@ TEMPLATE_TEST_CASE("results: aggregate", "[query][aggregate]", ResultsFromTable, REQUIRE(results.min(col_float)->get_float() == 0.f); REQUIRE(results.min(col_double)->get_double() == 0.0); REQUIRE(results.min(col_date)->get_timestamp() == Timestamp(0, 0)); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.min(col_int_list), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.min(col_invalid), ErrorCodes::InvalidProperty); + REQUIRE_EXCEPTION(results.min(col_int_list), IllegalOperation, + "Operation 'min' not supported for int list 'object.int list'"); + REQUIRE_EXCEPTION(results.min(col_invalid), InvalidProperty, "Invalid column key"); } SECTION("average") { REQUIRE(results.average(col_int) == 1.0); REQUIRE(results.average(col_float) == 1.0); REQUIRE(results.average(col_double) == 1.0); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.average(col_int_list), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.average(col_date), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.average(col_invalid), ErrorCodes::InvalidProperty); + REQUIRE_EXCEPTION(results.average(col_int_list), IllegalOperation, + "Operation 'average' not supported for int list 'object.int list'"); + REQUIRE_EXCEPTION(results.average(col_date), IllegalOperation, + "Operation 'average' not supported for date? property 'object.date'"); + REQUIRE_EXCEPTION(results.average(col_invalid), InvalidProperty, "Invalid column key"); } SECTION("sum") { REQUIRE(results.sum(col_int)->get_int() == 2); REQUIRE(results.sum(col_float)->get_double() == 2.0); REQUIRE(results.sum(col_double)->get_double() == 2.0); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.sum(col_int_list), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.sum(col_date), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.sum(col_invalid), ErrorCodes::InvalidProperty); + REQUIRE_EXCEPTION(results.sum(col_int_list), IllegalOperation, + "Operation 'sum' not supported for int list 'object.int list'"); + REQUIRE_EXCEPTION(results.sum(col_date), IllegalOperation, + "Operation 'sum' not supported for date? property 'object.date'"); + REQUIRE_EXCEPTION(results.sum(col_invalid), InvalidProperty, "Invalid column key"); } } @@ -4338,14 +4309,16 @@ TEMPLATE_TEST_CASE("results: aggregate", "[query][aggregate]", ResultsFromTable, REQUIRE(!results.average(col_int)); REQUIRE(!results.average(col_float)); REQUIRE(!results.average(col_double)); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.average(col_date), ErrorCodes::IllegalOperation); + REQUIRE_EXCEPTION(results.average(col_date), IllegalOperation, + "Operation 'average' not supported for date? property 'object.date'"); } SECTION("sum") { REQUIRE(results.sum(col_int)->get_int() == 0); REQUIRE(results.sum(col_float)->get_double() == 0.0); REQUIRE(results.sum(col_double)->get_double() == 0.0); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.sum(col_date), ErrorCodes::IllegalOperation); + REQUIRE_EXCEPTION(results.sum(col_date), IllegalOperation, + "Operation 'sum' not supported for date? property 'object.date'"); } } @@ -4370,14 +4343,16 @@ TEMPLATE_TEST_CASE("results: aggregate", "[query][aggregate]", ResultsFromTable, REQUIRE(!results.average(col_int)); REQUIRE(!results.average(col_float)); REQUIRE(!results.average(col_double)); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.average(col_date), ErrorCodes::IllegalOperation); + REQUIRE_EXCEPTION(results.average(col_date), IllegalOperation, + "Operation 'average' not supported for date? property 'object.date'"); } SECTION("sum") { REQUIRE(results.sum(col_int)->get_int() == 0); REQUIRE(results.sum(col_float)->get_double() == 0.0); REQUIRE(results.sum(col_double)->get_double() == 0.0); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(results.sum(col_date), ErrorCodes::IllegalOperation); + REQUIRE_EXCEPTION(results.sum(col_date), IllegalOperation, + "Operation 'sum' not supported for date? property 'object.date'"); } } } @@ -4795,14 +4770,13 @@ TEST_CASE("results: limit", "[limit]") { SECTION("does not support further filtering") { auto limited = r.limit(0); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(limited.filter(table->where()), ErrorCodes::IllegalOperation); + REQUIRE_EXCEPTION(limited.filter(table->where()), IllegalOperation, + "Filtering a Results with a limit is not yet implemented"); } } TEST_CASE("notifications: objects with PK recreated") { -#ifndef _WIN32 _impl::RealmCoordinator::assert_no_open_realms(); -#endif InMemoryTestFile config; config.cache = false; config.automatic_change_notifications = false; diff --git a/test/object-store/schema.cpp b/test/object-store/schema.cpp index 58268e213d4..5749c6a92cc 100644 --- a/test/object-store/schema.cpp +++ b/test/object-store/schema.cpp @@ -82,6 +82,8 @@ struct StringMaker { }; } // namespace Catch +using namespace Catch::Matchers; + TEST_CASE("ObjectSchema") { SECTION("Aliases are still present in schema returned from the Realm") { TestFile config; @@ -359,8 +361,9 @@ TEST_CASE("Schema") { Schema schema = { {"object", {{"link", PropertyType::Object | PropertyType::Nullable}}}, }; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Property 'object.link' of type 'object' has unknown object type ''"); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.link' of type 'object' has unknown object type ''")); } SECTION("allow asymmetric tables") { @@ -373,7 +376,9 @@ TEST_CASE("Schema") { {{"_id", PropertyType::Int, Property::IsPrimary{true}}, {"street", PropertyType::String}}}, }; REQUIRE_NOTHROW(schema.validate(SchemaValidationMode::SyncFLX)); - REQUIRE_THROWS(schema.validate(SchemaValidationMode::SyncPBS)); + REQUIRE_EXCEPTION(schema.validate(SchemaValidationMode::SyncPBS), SchemaValidationFailed, + Catch::Matchers::ContainsSubstring( + "Asymmetric table 'location' not allowed in partition based sync")); } SECTION("asymmetric tables not allowed in local realm") { @@ -382,7 +387,8 @@ TEST_CASE("Schema") { ObjectSchema::ObjectType::TopLevelAsymmetric, {{"_id", PropertyType::Int, Property::IsPrimary{true}}, {"street", PropertyType::String}}}, }; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Asymmetric table 'location' not allowed in a local Realm"); + REQUIRE_EXCEPTION(schema.validate(), SchemaValidationFailed, + ContainsSubstring("Asymmetric table 'location' not allowed in a local Realm")); } SECTION("rejects link properties with asymmetric target object") { @@ -392,9 +398,10 @@ TEST_CASE("Schema") { ObjectSchema::ObjectType::TopLevelAsymmetric, {{"_id", PropertyType::Int, Property::IsPrimary{true}}}}, }; - REQUIRE_THROWS_CONTAINING( - schema.validate(SchemaValidationMode::SyncFLX), - "Property 'object.link' of type 'object' cannot be a link to an asymmetric object."); + REQUIRE_EXCEPTION( + schema.validate(SchemaValidationMode::SyncFLX), SchemaValidationFailed, + ContainsSubstring( + "Property 'object.link' of type 'object' cannot be a link to an asymmetric object.")); } SECTION("rejects link properties with asymmetric origin object") { @@ -404,9 +411,9 @@ TEST_CASE("Schema") { {{"link", PropertyType::Object | PropertyType::Nullable, "link target"}}}, {"link target", {{"value", PropertyType::Int}}}, }; - REQUIRE_THROWS_CONTAINING(schema.validate(SchemaValidationMode::SyncFLX), - "Asymmetric table with property 'object.link' of type 'object' cannot have a " - "non-embedded object type."); + REQUIRE_EXCEPTION(schema.validate(SchemaValidationMode::SyncFLX), SchemaValidationFailed, + ContainsSubstring("Asymmetric table with property 'object.link' of type 'object' " + "cannot have a non-embedded object type.")); } SECTION("allow embedded objects with asymmetric sync") { @@ -417,29 +424,32 @@ TEST_CASE("Schema") { {"link", PropertyType::Object | PropertyType::Nullable, "link target"}}}, {"link target", ObjectSchema::ObjectType::Embedded, {{"value", PropertyType::Int}}}, }; - schema.validate(SchemaValidationMode::SyncFLX); + REQUIRE_NOTHROW(schema.validate(SchemaValidationMode::SyncFLX)); } SECTION("rejects array properties with no target object") { Schema schema = { {"object", {{"array", PropertyType::Array | PropertyType::Object}}}, }; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Property 'object.array' of type 'array' has unknown object type ''"); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.array' of type 'array' has unknown object type ''")); } SECTION("rejects link properties with a target not in the schema") { Schema schema = {{"object", {{"link", PropertyType::Object | PropertyType::Nullable, "invalid target"}}}}; - REQUIRE_THROWS_CONTAINING( - schema.validate(), - "Property 'object.link' of type 'object' has unknown object type 'invalid target'"); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring( + "Property 'object.link' of type 'object' has unknown object type 'invalid target'")); } SECTION("rejects array properties with a target not in the schema") { Schema schema = {{"object", {{"array", PropertyType::Array | PropertyType::Object, "invalid target"}}}}; - REQUIRE_THROWS_CONTAINING( - schema.validate(), - "Property 'object.array' of type 'array' has unknown object type 'invalid target'"); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring( + "Property 'object.array' of type 'array' has unknown object type 'invalid target'")); } SECTION("allows embedded objects in lists and dictionaries") { @@ -460,9 +470,9 @@ TEST_CASE("Schema") { {"target", ObjectSchema::ObjectType::Embedded, {{"value", PropertyType::Int}}}, {"object", {{"set", PropertyType::Object | PropertyType::Set, "target"}}}, }; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Set property 'object.set' cannot contain embedded object type 'target'. Set " - "semantics are not applicable to embedded objects."); + REQUIRE_EXCEPTION(schema.validate(), SchemaValidationFailed, + ContainsSubstring("Set property 'object.set' cannot contain embedded object type " + "'target'. Set semantics are not applicable to embedded objects.")); } SECTION("rejects explicitly included embedded object orphans") { @@ -471,9 +481,10 @@ TEST_CASE("Schema") { ObjectSchema::ObjectType::Embedded, {{"link", PropertyType::Object | PropertyType::Nullable, "target"}}}}; REQUIRE_NOTHROW(schema.validate()); - REQUIRE_THROWS_CONTAINING( - schema.validate(SchemaValidationMode::RejectEmbeddedOrphans), - "Embedded object 'origin' is unreachable by any link path from top level objects."); + REQUIRE_EXCEPTION( + schema.validate(SchemaValidationMode::RejectEmbeddedOrphans), SchemaValidationFailed, + ContainsSubstring( + "Embedded object 'origin' is unreachable by any link path from top level objects.")); } SECTION("allows embedded object chains starting from a top level object") { @@ -547,9 +558,10 @@ TEST_CASE("Schema") { {"EmbeddedObject", ObjectSchema::ObjectType::Embedded, {{"link_to_self", PropertyType::Object | PropertyType::Nullable, "EmbeddedObject"}}}}; - REQUIRE_THROWS_CONTAINING( - schema.validate(), - "Cycles containing embedded objects are not currently supported: 'EmbeddedObject.link_to_self'"); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring( + "Cycles containing embedded objects are not currently supported: 'EmbeddedObject.link_to_self'")); } SECTION("rejects embedded objects loop to itself from a list") { @@ -559,9 +571,10 @@ TEST_CASE("Schema") { {"EmbeddedObject", ObjectSchema::ObjectType::Embedded, {{"link_to_self", PropertyType::Object | PropertyType::Array, "EmbeddedObject"}}}}; - REQUIRE_THROWS_CONTAINING( - schema.validate(), - "Cycles containing embedded objects are not currently supported: 'EmbeddedObject.link_to_self'"); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring( + "Cycles containing embedded objects are not currently supported: 'EmbeddedObject.link_to_self'")); } SECTION("rejects embedded objects loop via different embedded object") { @@ -574,8 +587,9 @@ TEST_CASE("Schema") { {"EmbeddedObjectB", ObjectSchema::ObjectType::Embedded, {{"link_to_a", PropertyType::Object | PropertyType::Nullable, "EmbeddedObjectA"}}}}; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Cycles containing embedded objects are not currently " - "supported: 'EmbeddedObjectA.link_to_b.link_to_a'"); + REQUIRE_EXCEPTION(schema.validate(), SchemaValidationFailed, + ContainsSubstring("Cycles containing embedded objects are not currently supported: " + "'EmbeddedObjectA.link_to_b.link_to_a'")); } SECTION("rejects with descriptions of all embedded object loops") { @@ -592,22 +606,13 @@ TEST_CASE("Schema") { {"EmbeddedObjectC", ObjectSchema::ObjectType::Embedded, {{"link_to_a", PropertyType::Object | PropertyType::Nullable, "EmbeddedObjectA"}}}}; - std::string message; - try { - schema.validate(); - } - catch (const std::exception& e) { - message = e.what(); - } - bool found_loop_on_a = message.find("EmbeddedObjectA.link_to_c.link_to_a") != std::string::npos || - message.find("EmbeddedObjectA.link_to_b.link_to_a") != std::string::npos; - bool found_loop_on_b = message.find("EmbeddedObjectB.link_to_a.link_to_b") != std::string::npos || - message.find("EmbeddedObjectB.link_to_a.link_to_c.link_to_a") != std::string::npos; - bool found_loop_on_c = message.find("EmbeddedObjectC.link_to_a.link_to_c") != std::string::npos || - message.find("EmbeddedObjectC.link_to_a.link_to_b.link_to_a") != std::string::npos; - REQUIRE(found_loop_on_a); - REQUIRE(found_loop_on_b); - REQUIRE(found_loop_on_c); + REQUIRE_THROWS_WITH(schema.validate(), + (ContainsSubstring("EmbeddedObjectA.link_to_c.link_to_a") || + ContainsSubstring("EmbeddedObjectA.link_to_b.link_to_a")) && + (ContainsSubstring("EmbeddedObjectB.link_to_a.link_to_b") || + ContainsSubstring("EmbeddedObjectB.link_to_a.link_to_c.link_to_a")) && + (ContainsSubstring("EmbeddedObjectC.link_to_a.link_to_c") || + ContainsSubstring("EmbeddedObjectC.link_to_a.link_to_b.link_to_a"))); } SECTION("allows top level loops") { @@ -665,8 +670,9 @@ TEST_CASE("Schema") { {"value", PropertyType::Int}, }, {{"incoming", PropertyType::Array | PropertyType::LinkingObjects, "", ""}}}}; - REQUIRE_THROWS_CONTAINING( - schema.validate(), "Property 'object.incoming' of type 'linking objects' has unknown object type ''"); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.incoming' of type 'linking objects' has unknown object type ''")); } SECTION("rejects linking objects without a source property") { @@ -675,9 +681,10 @@ TEST_CASE("Schema") { {"value", PropertyType::Int}, }, {{"incoming", PropertyType::Array | PropertyType::LinkingObjects, "object", ""}}}}; - REQUIRE_THROWS_CONTAINING( - schema.validate(), - "Property 'object.incoming' of type 'linking objects' must have an origin property name."); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring( + "Property 'object.incoming' of type 'linking objects' must have an origin property name.")); } SECTION("rejects linking objects with invalid source object") { @@ -687,9 +694,9 @@ TEST_CASE("Schema") { {"value", PropertyType::Int}, }, {{"incoming", PropertyType::Array | PropertyType::LinkingObjects, "not an object type", ""}}}}; - REQUIRE_THROWS_CONTAINING( - schema.validate(), - "Property 'object.incoming' of type 'linking objects' has unknown object type 'not an object type'"); + REQUIRE_EXCEPTION(schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.incoming' of type 'linking objects' has unknown " + "object type 'not an object type'")); } SECTION("rejects linking objects with invalid source property") { @@ -698,8 +705,9 @@ TEST_CASE("Schema") { {"value", PropertyType::Int}, }, {{"incoming", PropertyType::Array | PropertyType::LinkingObjects, "object", "value"}}}}; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.value' declared as origin of linking " - "objects property 'object.incoming' is not a link"); + REQUIRE_EXCEPTION(schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.value' declared as origin of linking objects " + "property 'object.incoming' is not a link")); schema = {{"object", { @@ -711,9 +719,9 @@ TEST_CASE("Schema") { { {"value", PropertyType::Int}, }}}; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Property 'object.link' declared as origin of linking objects property " - "'object.incoming' links to type 'object 2'"); + REQUIRE_EXCEPTION(schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.link' declared as origin of linking objects " + "property 'object.incoming' links to type 'object 2'")); } SECTION("rejects non-array linking objects") { @@ -722,8 +730,8 @@ TEST_CASE("Schema") { {"link", PropertyType::Object | PropertyType::Nullable, "object"}, }, {{"incoming", PropertyType::LinkingObjects, "object", "link"}}}}; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Linking Objects property 'object.incoming' must be an array."); + REQUIRE_EXCEPTION(schema.validate(), SchemaValidationFailed, + ContainsSubstring("Linking Objects property 'object.incoming' must be an array.")); } SECTION("rejects target object types for non-link properties") { @@ -742,7 +750,8 @@ TEST_CASE("Schema") { for (auto& prop : schema.begin()->persisted_properties) { REQUIRE_NOTHROW(schema.validate()); prop.object_type = "object"; - REQUIRE_THROWS_CONTAINING(schema.validate(), "cannot have an object type."); + REQUIRE_EXCEPTION(schema.validate(), SchemaValidationFailed, + ContainsSubstring("cannot have an object type.")); prop.object_type = ""; } } @@ -776,14 +785,15 @@ TEST_CASE("Schema") { SECTION("rejects non-nullable link properties") { Schema schema = {{"object", {{"link", PropertyType::Object, "target"}}}, {"target", {{"value", PropertyType::Int}}}}; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.link' of type 'object' must be nullable."); + REQUIRE_EXCEPTION(schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.link' of type 'object' must be nullable.")); } SECTION("rejects non-nullable dictionary properties") { Schema schema = {{"object", {{"dictionary", PropertyType::Dictionary | PropertyType::Object, "target"}}}, {"target", {{"value", PropertyType::Int}}}}; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Property 'object.dictionary' of type 'object' must be nullable."); + REQUIRE_EXCEPTION(schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.dictionary' of type 'object' must be nullable.")); } SECTION("rejects nullable array properties") { @@ -791,15 +801,16 @@ TEST_CASE("Schema") { {"object", {{"array", PropertyType::Array | PropertyType::Object | PropertyType::Nullable, "target"}}}, {"target", {{"value", PropertyType::Int}}}}; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Property 'object.array' of type 'array' cannot be nullable."); + REQUIRE_EXCEPTION(schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.array' of type 'array' cannot be nullable.")); } SECTION("rejects nullable set properties") { Schema schema = { {"object", {{"set", PropertyType::Set | PropertyType::Object | PropertyType::Nullable, "target"}}}, {"target", {{"value", PropertyType::Int}}}}; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Property 'object.set' of type 'set' cannot be nullable."); + REQUIRE_EXCEPTION(schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.set' of type 'set' cannot be nullable.")); } SECTION("rejects nullable linking objects") { @@ -810,8 +821,9 @@ TEST_CASE("Schema") { }, {{"incoming", PropertyType::LinkingObjects | PropertyType::Array | PropertyType::Nullable, "object", "link"}}}}; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Property 'object.incoming' of type 'linking objects' cannot be nullable."); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.incoming' of type 'linking objects' cannot be nullable.")); } SECTION("rejects duplicate primary keys") { @@ -820,8 +832,9 @@ TEST_CASE("Schema") { {"pk1", PropertyType::Int, Property::IsPrimary{true}}, {"pk2", PropertyType::Int, Property::IsPrimary{true}}, }}}; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Properties 'pk2' and 'pk1' are both marked as the primary key of 'object'."); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring("Properties 'pk2' and 'pk1' are both marked as the primary key of 'object'.")); } SECTION("rejects primary key on embedded table") { @@ -831,7 +844,8 @@ TEST_CASE("Schema") { {"pk1", PropertyType::Int, Property::IsPrimary{true}}, {"int", PropertyType::Int}, }}}; - REQUIRE_THROWS_CONTAINING(schema.validate(), "Embedded object type 'object' cannot have a primary key."); + REQUIRE_EXCEPTION(schema.validate(), SchemaValidationFailed, + ContainsSubstring("Embedded object type 'object' cannot have a primary key.")); } SECTION("rejects invalid primary key types") { @@ -841,40 +855,49 @@ TEST_CASE("Schema") { }}}; schema.begin()->primary_key_property()->type = PropertyType::Mixed; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Property 'object.pk' of type 'mixed' cannot be made the primary key."); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.pk' of type 'mixed' cannot be made the primary key.")); schema.begin()->primary_key_property()->type = PropertyType::Bool; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Property 'object.pk' of type 'bool' cannot be made the primary key."); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.pk' of type 'bool' cannot be made the primary key.")); schema.begin()->primary_key_property()->type = PropertyType::Float; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Property 'object.pk' of type 'float' cannot be made the primary key."); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.pk' of type 'float' cannot be made the primary key.")); schema.begin()->primary_key_property()->type = PropertyType::Double; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Property 'object.pk' of type 'double' cannot be made the primary key."); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.pk' of type 'double' cannot be made the primary key.")); schema.begin()->primary_key_property()->type = PropertyType::Object; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Property 'object.pk' of type 'object' cannot be made the primary key."); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.pk' of type 'object' cannot be made the primary key.")); schema.begin()->primary_key_property()->type = PropertyType::LinkingObjects; - REQUIRE_THROWS_CONTAINING( - schema.validate(), "Property 'object.pk' of type 'linking objects' cannot be made the primary key."); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.pk' of type 'linking objects' cannot be made the primary key.")); schema.begin()->primary_key_property()->type = PropertyType::Data; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Property 'object.pk' of type 'data' cannot be made the primary key."); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.pk' of type 'data' cannot be made the primary key.")); schema.begin()->primary_key_property()->type = PropertyType::Date; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Property 'object.pk' of type 'date' cannot be made the primary key."); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.pk' of type 'date' cannot be made the primary key.")); schema.begin()->primary_key_property()->type = PropertyType::Decimal; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Property 'object.pk' of type 'decimal' cannot be made the primary key."); + REQUIRE_EXCEPTION( + schema.validate(), SchemaValidationFailed, + ContainsSubstring("Property 'object.pk' of type 'decimal' cannot be made the primary key.")); } SECTION("allows valid primary key types") { @@ -907,8 +930,8 @@ TEST_CASE("Schema") { {"value", PropertyType::Int}, }}}; schema.begin()->primary_key = "nonexistent"; - REQUIRE_THROWS_CONTAINING(schema.validate(), - "Specified primary key 'object.nonexistent' does not exist."); + REQUIRE_EXCEPTION(schema.validate(), SchemaValidationFailed, + ContainsSubstring("Specified primary key 'object.nonexistent' does not exist.")); } SECTION("rejects indexes for types that cannot be indexed") { diff --git a/test/object-store/sectioned_results.cpp b/test/object-store/sectioned_results.cpp index 6dbc0532324..06cab5e5686 100644 --- a/test/object-store/sectioned_results.cpp +++ b/test/object-store/sectioned_results.cpp @@ -36,11 +36,7 @@ namespace realm::sectioned_results_fixtures { template struct Base { using Type = T; - - static PropertyType property_type() - { - return prop_type; - } + constexpr static PropertyType property_type = prop_type; }; struct Int : Base { @@ -68,10 +64,7 @@ struct Int : Base { return value.get_int() % 2; } - static size_t expected_size() - { - return 2; - } + constexpr static size_t expected_size = 2; }; struct Bool : Base { @@ -99,10 +92,7 @@ struct Bool : Base { return value.get_bool(); } - static size_t expected_size() - { - return 2; - } + constexpr static size_t expected_size = 2; }; struct Float : Base { @@ -130,10 +120,7 @@ struct Float : Base { return (int(value.get_float()) % 2) ? 1.0 : 0.0; } - static size_t expected_size() - { - return 2; - } + constexpr static size_t expected_size = 2; }; struct Double : Base { @@ -161,10 +148,7 @@ struct Double : Base { return (int(value.get_double()) % 2) ? 1.0 : 0.0; } - static size_t expected_size() - { - return 2; - } + constexpr static size_t expected_size = 2; }; struct String : Base { @@ -196,10 +180,7 @@ struct String : Base { return str.size() > 0 ? str.prefix(1) : str; } - static size_t expected_size() - { - return 5; - } + constexpr static size_t expected_size = 5; }; struct Binary : Base { @@ -230,10 +211,7 @@ struct Binary : Base { return value.get_binary(); } - static size_t expected_size() - { - return 6; - } + constexpr static size_t expected_size = 6; }; struct Date : Base { @@ -263,10 +241,7 @@ struct Date : Base { return value.get_timestamp().get_seconds() < 10 ? Timestamp(1, 1) : Timestamp(2, 1); } - static size_t expected_size() - { - return 2; - } + constexpr static size_t expected_size = 2; }; struct MixedVal : Base { @@ -301,15 +276,8 @@ struct MixedVal : Base { return Mixed::is_numeric(value.get_type()) ? "Numerics" : "Alphanumeric"; } - static PropertyType property_type() - { - return PropertyType::Mixed | PropertyType::Nullable; - } - - static size_t expected_size() - { - return 3; - } + constexpr static PropertyType property_type = PropertyType::Mixed | PropertyType::Nullable; + constexpr static size_t expected_size = 3; }; struct OID : Base { @@ -345,10 +313,7 @@ struct OID : Base { return value.get_object_id(); } - static size_t expected_size() - { - return 2; - } + constexpr static size_t expected_size = 2; }; struct UUID : Base { @@ -388,10 +353,7 @@ struct UUID : Base { return value.get_uuid(); } - static size_t expected_size() - { - return 2; - } + constexpr static size_t expected_size = 2; }; struct Decimal : Base { @@ -425,20 +387,14 @@ struct Decimal : Base { return value.get_decimal() < Decimal128("876.54e32") ? Decimal128("1") : Decimal128("0"); } - static size_t expected_size() - { - return 2; - } + constexpr static size_t expected_size = 2; }; template struct BoxedOptional : BaseT { using Type = util::Optional; using Boxed = Type; - static PropertyType property_type() - { - return BaseT::property_type() | PropertyType::Nullable; - } + constexpr static PropertyType property_type = BaseT::property_type | PropertyType::Nullable; static std::vector values() { std::vector ret; @@ -448,10 +404,7 @@ struct BoxedOptional : BaseT { return ret; } - static size_t expected_size() - { - return BaseT::expected_size() + 1; - } + constexpr static size_t expected_size = BaseT::expected_size + 1; static std::vector expected_sorted() { @@ -472,11 +425,10 @@ struct BoxedOptional : BaseT { template struct UnboxedOptional : BaseT { - enum { is_optional = true }; - static PropertyType property_type() - { - return BaseT::property_type() | PropertyType::Nullable; - } + constexpr static bool is_optional = true; + constexpr static PropertyType property_type = BaseT::property_type | PropertyType::Nullable; + constexpr static size_t expected_size = BaseT::expected_size + 1; + static auto values() -> decltype(BaseT::values()) { auto ret = BaseT::values(); @@ -490,10 +442,6 @@ struct UnboxedOptional : BaseT { return ret; } - static size_t expected_size() - { - return BaseT::expected_size() + 1; - } static auto expected_sorted() -> decltype(BaseT::values()) { @@ -627,7 +575,7 @@ TEST_CASE("sectioned results", "[sectioned_results]") { REQUIRE(sectioned_results["a"].size() == 3); REQUIRE(sectioned_results["b"].size() == 1); REQUIRE(sectioned_results["o"].size() == 1); - REQUIRE_THROWS(sectioned_results["x"]); + REQUIRE_EXCEPTION(sectioned_results["x"], InvalidArgument, "Section key \"x\" not found."); REQUIRE(algo_run_count == 5); } @@ -645,7 +593,7 @@ TEST_CASE("sectioned results", "[sectioned_results]") { REQUIRE(sectioned_results["ap"].size() == 3); REQUIRE(sectioned_results["ba"].size() == 1); REQUIRE(sectioned_results["or"].size() == 1); - REQUIRE_THROWS(sectioned_results["a"]); + REQUIRE_EXCEPTION(sectioned_results["a"], InvalidArgument, "Section key \"a\" not found."); REQUIRE(algo_run_count == 5); } @@ -654,7 +602,8 @@ TEST_CASE("sectioned results", "[sectioned_results]") { auto sr = sorted.sectioned_results([](Mixed value, SharedRealm) { return value.get_link(); }); - REQUIRE_THROWS(sr.size()); // Trigger calculation + REQUIRE_EXCEPTION(sr.size(), InvalidArgument, + "Links are not supported as section keys."); // Trigger calculation // Even after sectioning has failed, the sectioned results // object should left in a sensible state. REQUIRE(sr.is_valid()); @@ -671,7 +620,8 @@ TEST_CASE("sectioned results", "[sectioned_results]") { auto obj = Object(realm, value.get_link()); return Mixed(obj.obj().get(col_typed_link)); }); - REQUIRE_THROWS(sr.size()); // Trigger calculation + REQUIRE_EXCEPTION(sr.size(), InvalidArgument, + "Links are not supported as section keys."); // Trigger calculation REQUIRE(sr.is_valid()); } @@ -1573,7 +1523,7 @@ TEMPLATE_TEST_CASE("sectioned results primitive types", "[sectioned_results]", c auto r = Realm::get_shared_realm(config); r->update_schema({ {"object", - {{"value_col", TestType::property_type()}, {"array_col", PropertyType::Array | TestType::property_type()}}}, + {{"value_col", TestType::property_type}, {"array_col", PropertyType::Array | TestType::property_type}}}, }); auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); @@ -1601,7 +1551,7 @@ TEMPLATE_TEST_CASE("sectioned results primitive types", "[sectioned_results]", c algo_run_count++; return TestType::comparison_value(value); }); - REQUIRE(sectioned_results.size() == TestType::expected_size()); + REQUIRE(sectioned_results.size() == TestType::expected_size); auto size = sectioned_results.size(); auto results_idx = 0; for (size_t section_idx = 0; section_idx < size; section_idx++) { @@ -1629,7 +1579,7 @@ TEMPLATE_TEST_CASE("sectioned results primitive types", "[sectioned_results]", c }); std::reverse(exp_values_sorted.begin(), exp_values_sorted.end()); std::reverse(exp_keys.begin(), exp_keys.end()); - REQUIRE(sectioned_results.size() == TestType::expected_size()); + REQUIRE(sectioned_results.size() == TestType::expected_size); auto size = sectioned_results.size(); auto results_idx = 0; for (size_t section_idx = 0; section_idx < size; section_idx++) { @@ -1738,7 +1688,7 @@ TEMPLATE_TEST_CASE("sectioned results primitive types", "[sectioned_results]", c auto frozen_realm = r->freeze(); auto frozen_sr = sectioned_results.freeze(frozen_realm); auto size = frozen_sr.size(); - REQUIRE(size == TestType::expected_size()); + REQUIRE(size == TestType::expected_size); auto results_idx = 0; for (size_t section_idx = 0; section_idx < size; section_idx++) { auto section = frozen_sr[section_idx]; @@ -1762,7 +1712,7 @@ TEMPLATE_TEST_CASE("sectioned results primitive types", "[sectioned_results]", c return TestType::comparison_value(value); }); auto size = sectioned_results.size(); - REQUIRE(size == TestType::expected_size()); + REQUIRE(size == TestType::expected_size); auto results_idx = 0; for (size_t section_idx = 0; section_idx < size; section_idx++) { auto section = sectioned_results[section_idx]; diff --git a/test/object-store/set.cpp b/test/object-store/set.cpp index 9282a9c6e0e..0dfe54f8a8a 100644 --- a/test/object-store/set.cpp +++ b/test/object-store/set.cpp @@ -81,7 +81,7 @@ TEMPLATE_PRODUCT_TEST_CASE("set all types", "[set]", (CreateNewSet, ReuseSet), auto r = Realm::get_shared_realm(config); r->update_schema({ {"table", - {{"value_set", PropertyType::Set | Test::property_type()}, + {{"value_set", PropertyType::Set | Test::property_type}, {"link_set", PropertyType::Set | PropertyType::Object, "table2"}}}, {"table2", {{"id", PropertyType::Int, Property::IsPrimary{true}}}}, }); @@ -116,15 +116,16 @@ TEMPLATE_PRODUCT_TEST_CASE("set all types", "[set]", (CreateNewSet, ReuseSet), REQUIRE(set().is_valid()); REQUIRE_NOTHROW(set().verify_attached()); object_store::Set unattached; - REQUIRE_THROWS(unattached.verify_attached()); - REQUIRE(!unattached.is_valid()); + REQUIRE_EXCEPTION(unattached.verify_attached(), InvalidatedObject, + "Set is invalid and was never initialized."); + REQUIRE_FALSE(unattached.is_valid()); } SECTION("basic value operations") { REQUIRE(set().size() == 0); - REQUIRE(set().get_type() == Test::property_type()); - REQUIRE(set_as_results().get_type() == Test::property_type()); - write([&]() { + REQUIRE(set().get_type() == Test::property_type); + REQUIRE(set_as_results().get_type() == Test::property_type); + write([&] { for (size_t i = 0; i < values.size(); ++i) { auto result = set().insert(T(values[i])); REQUIRE(result.first < values.size()); @@ -172,7 +173,7 @@ TEMPLATE_PRODUCT_TEST_CASE("set all types", "[set]", (CreateNewSet, ReuseSet), } }; SECTION("remove()") { - write([&]() { + write([&] { for (size_t i = 0; i < values.size(); ++i) { auto result = set().remove(T(values[i])); REQUIRE(result.first < values.size()); @@ -184,7 +185,7 @@ TEMPLATE_PRODUCT_TEST_CASE("set all types", "[set]", (CreateNewSet, ReuseSet), check_empty(); } SECTION("remove_any()") { - write([&]() { + write([&] { for (size_t i = 0; i < values.size(); ++i) { auto result = set().remove_any(Mixed(T(values[i]))); REQUIRE(result.first < values.size()); @@ -196,7 +197,7 @@ TEMPLATE_PRODUCT_TEST_CASE("set all types", "[set]", (CreateNewSet, ReuseSet), check_empty(); } SECTION("remove(ctx)") { - write([&]() { + write([&] { for (size_t i = 0; i < values.size(); ++i) { auto result = set().remove(ctx, Test::to_any(T(values[i]))); REQUIRE(result.first < values.size()); @@ -208,88 +209,108 @@ TEMPLATE_PRODUCT_TEST_CASE("set all types", "[set]", (CreateNewSet, ReuseSet), check_empty(); } SECTION("remove_all()") { - write([&]() { + write([&] { set().remove_all(); }); check_empty(); } SECTION("delete_all()") { - write([&]() { + write([&] { set().delete_all(); }); check_empty(); } SECTION("Results::clear()") { - write([&]() { + write([&] { set_as_results().clear(); }); check_empty(); } SECTION("min()") { - if (!Test::can_minmax()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set().min(), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set_as_results().min(), ErrorCodes::IllegalOperation); - return; + if constexpr (!Test::can_minmax) { + REQUIRE_EXCEPTION( + set().min(), IllegalOperation, + util::format("Operation 'min' not supported for %1 set 'table.value_set'", Test::name)); + REQUIRE_EXCEPTION( + set_as_results().min(), IllegalOperation, + util::format("Operation 'min' not supported for %1 set 'table.value_set'", Test::name)); + } + else { + REQUIRE(Mixed(Test::min()) == set().min()); + REQUIRE(Mixed(Test::min()) == set_as_results().min()); + write([&] { + set().remove_all(); + }); + REQUIRE(!set().min()); + REQUIRE(!set_as_results().min()); } - REQUIRE(Mixed(Test::min()) == set().min()); - REQUIRE(Mixed(Test::min()) == set_as_results().min()); - write([&]() { - set().remove_all(); - }); - REQUIRE(!set().min()); - REQUIRE(!set_as_results().min()); } SECTION("max()") { - if (!Test::can_minmax()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set().max(), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set_as_results().max(), ErrorCodes::IllegalOperation); - return; + if constexpr (!Test::can_minmax) { + REQUIRE_EXCEPTION( + set().max(), IllegalOperation, + util::format("Operation 'max' not supported for %1 set 'table.value_set'", Test::name)); + REQUIRE_EXCEPTION( + set_as_results().max(), IllegalOperation, + util::format("Operation 'max' not supported for %1 set 'table.value_set'", Test::name)); + } + else { + REQUIRE(Mixed(Test::max()) == set().max()); + REQUIRE(Mixed(Test::max()) == set_as_results().max()); + write([&] { + set().remove_all(); + }); + REQUIRE(!set().max()); + REQUIRE(!set_as_results().max()); } - REQUIRE(Mixed(Test::max()) == set().max()); - REQUIRE(Mixed(Test::max()) == set_as_results().max()); - write([&]() { - set().remove_all(); - }); - REQUIRE(!set().max()); - REQUIRE(!set_as_results().max()); } SECTION("sum()") { - if (!Test::can_sum()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set().sum(), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set_as_results().sum(), ErrorCodes::IllegalOperation); - return; + if constexpr (!Test::can_sum) { + REQUIRE_EXCEPTION( + set().sum(), IllegalOperation, + util::format("Operation 'sum' not supported for %1 set 'table.value_set'", Test::name)); + REQUIRE_EXCEPTION( + set_as_results().sum(), IllegalOperation, + util::format("Operation 'sum' not supported for %1 set 'table.value_set'", Test::name)); + } + else { + REQUIRE(cf::get(set().sum()) == Test::sum()); + REQUIRE(cf::get(*set_as_results().sum()) == Test::sum()); + write([&] { + set().remove_all(); + }); + REQUIRE(set().sum() == 0); + REQUIRE(set_as_results().sum() == 0); } - REQUIRE(cf::get(set().sum()) == Test::sum()); - REQUIRE(cf::get(*set_as_results().sum()) == Test::sum()); - write([&]() { - set().remove_all(); - }); - REQUIRE(set().sum() == 0); - REQUIRE(set_as_results().sum() == 0); } SECTION("average()") { - if (!Test::can_average()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set().average(), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set_as_results().average(), ErrorCodes::IllegalOperation); - return; + if constexpr (!Test::can_average) { + REQUIRE_EXCEPTION( + set().average(), IllegalOperation, + util::format("Operation 'average' not supported for %1 set 'table.value_set'", Test::name)); + REQUIRE_EXCEPTION( + set_as_results().average(), IllegalOperation, + util::format("Operation 'average' not supported for %1 set 'table.value_set'", Test::name)); + } + else { + REQUIRE(cf::get(*set().average()) == Test::average()); + REQUIRE(cf::get(*set_as_results().average()) == Test::average()); + write([&] { + set().remove_all(); + }); + REQUIRE(!set().average()); + REQUIRE(!set_as_results().average()); } - REQUIRE(cf::get(*set().average()) == Test::average()); - REQUIRE(cf::get(*set_as_results().average()) == Test::average()); - write([&]() { - set().remove_all(); - }); - REQUIRE(!set().average()); - REQUIRE(!set_as_results().average()); } SECTION("sort") { SECTION("ascending") { auto sorted = set_as_results().sort({{"self", true}}); - std::sort(begin(values), end(values), cf::less()); + std::sort(begin(values), end(values), std::less()); REQUIRE(sorted == values); } SECTION("descending") { auto sorted = set_as_results().sort({{"self", false}}); - std::sort(begin(values), end(values), cf::greater()); + std::sort(begin(values), end(values), std::greater()); REQUIRE(sorted == values); } } @@ -312,7 +333,7 @@ TEMPLATE_PRODUCT_TEST_CASE("set of links to all types", "[set]", (CreateNewSet, config.automatic_change_notifications = false; auto r = Realm::get_shared_realm(config); r->update_schema({{"table", {{"link_set", PropertyType::Set | PropertyType::Object, "table2"}}}, - {"table2", {{"value", Test::property_type()}}}}); + {"table2", {{"value", Test::property_type}}}}); auto table = r->read_group().get_table("class_table"); ColKey col_set = table->get_column_key("link_set"); auto target = r->read_group().get_table("class_table2"); @@ -353,7 +374,8 @@ TEMPLATE_PRODUCT_TEST_CASE("set of links to all types", "[set]", (CreateNewSet, REQUIRE(set().is_valid()); REQUIRE_NOTHROW(set().verify_attached()); object_store::Set unattached; - REQUIRE_THROWS(unattached.verify_attached()); + REQUIRE_EXCEPTION(unattached.verify_attached(), InvalidatedObject, + "Set is invalid and was never initialized."); REQUIRE(!unattached.is_valid()); } @@ -361,7 +383,7 @@ TEMPLATE_PRODUCT_TEST_CASE("set of links to all types", "[set]", (CreateNewSet, REQUIRE(set().size() == 0); REQUIRE(set().get_type() == PropertyType::Object); REQUIRE(set_as_results().get_type() == PropertyType::Object); - write([&]() { + write([&] { for (auto key : keys) { auto result = set().insert(key); REQUIRE(result.first < values.size()); @@ -385,14 +407,14 @@ TEMPLATE_PRODUCT_TEST_CASE("set of links to all types", "[set]", (CreateNewSet, } } - auto check_empty = [&]() { + auto check_empty = [&] { REQUIRE(set().size() == 0); for (auto key : keys) { REQUIRE(set().find(key) == realm::not_found); } }; SECTION("remove()") { - write([&]() { + write([&] { for (auto key : keys) { auto result = set().remove(key); REQUIRE(result.first < keys.size()); @@ -404,100 +426,120 @@ TEMPLATE_PRODUCT_TEST_CASE("set of links to all types", "[set]", (CreateNewSet, check_empty(); } SECTION("remove_all()") { - write([&]() { + write([&] { set().remove_all(); }); check_empty(); REQUIRE(target->size() != 0); } SECTION("delete_all()") { - write([&]() { + write([&] { set().delete_all(); }); check_empty(); REQUIRE(target->size() == 0); } SECTION("Results::clear()") { - write([&]() { + write([&] { set_as_results().clear(); }); check_empty(); REQUIRE(target->size() == 0); } SECTION("min()") { - if (!Test::can_minmax()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set().min(target_col), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set_as_results().min(target_col), ErrorCodes::IllegalOperation); - return; + if constexpr (!Test::can_minmax) { + REQUIRE_EXCEPTION( + set().min(target_col), IllegalOperation, + util::format("Operation 'min' not supported for %1 property 'table2.value'", Test::name)); + REQUIRE_EXCEPTION( + set_as_results().min(target_col), IllegalOperation, + util::format("Operation 'min' not supported for %1 property 'table2.value'", Test::name)); + } + else { + REQUIRE(Mixed(Test::min()) == set().min(target_col)); + REQUIRE(Mixed(Test::min()) == set_as_results().min(target_col)); + write([&] { + set().remove_all(); + }); + REQUIRE(!set().min(target_col)); + REQUIRE(!set_as_results().min(target_col)); } - REQUIRE(Mixed(Test::min()) == set().min(target_col)); - REQUIRE(Mixed(Test::min()) == set_as_results().min(target_col)); - write([&]() { - set().remove_all(); - }); - REQUIRE(!set().min(target_col)); - REQUIRE(!set_as_results().min(target_col)); } SECTION("max()") { - if (!Test::can_minmax()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set().max(target_col), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set_as_results().max(target_col), ErrorCodes::IllegalOperation); - return; + if constexpr (!Test::can_minmax) { + REQUIRE_EXCEPTION( + set().max(target_col), IllegalOperation, + util::format("Operation 'max' not supported for %1 property 'table2.value'", Test::name)); + REQUIRE_EXCEPTION( + set_as_results().max(target_col), IllegalOperation, + util::format("Operation 'max' not supported for %1 property 'table2.value'", Test::name)); + } + else { + REQUIRE(Mixed(Test::max()) == set().max(target_col)); + REQUIRE(Mixed(Test::max()) == set_as_results().max(target_col)); + write([&] { + set().remove_all(); + }); + REQUIRE(!set().max(target_col)); + REQUIRE(!set_as_results().max(target_col)); } - REQUIRE(Mixed(Test::max()) == set().max(target_col)); - REQUIRE(Mixed(Test::max()) == set_as_results().max(target_col)); - write([&]() { - set().remove_all(); - }); - REQUIRE(!set().max(target_col)); - REQUIRE(!set_as_results().max(target_col)); } SECTION("sum()") { - if (!Test::can_sum()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set().sum(target_col), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set_as_results().sum(target_col), ErrorCodes::IllegalOperation); - return; + if constexpr (!Test::can_sum) { + REQUIRE_EXCEPTION( + set().sum(target_col), IllegalOperation, + util::format("Operation 'sum' not supported for %1 property 'table2.value'", Test::name)); + REQUIRE_EXCEPTION( + set_as_results().sum(target_col), IllegalOperation, + util::format("Operation 'sum' not supported for %1 property 'table2.value'", Test::name)); + } + else { + REQUIRE(cf::get(set().sum(target_col)) == Test::sum()); + REQUIRE(cf::get(*set_as_results().sum(target_col)) == Test::sum()); + write([&] { + set().remove_all(); + }); + REQUIRE(set().sum(target_col) == 0); + REQUIRE(set_as_results().sum(target_col) == 0); } - REQUIRE(cf::get(set().sum(target_col)) == Test::sum()); - REQUIRE(cf::get(*set_as_results().sum(target_col)) == Test::sum()); - write([&]() { - set().remove_all(); - }); - REQUIRE(set().sum(target_col) == 0); - REQUIRE(set_as_results().sum(target_col) == 0); } SECTION("average()") { - if (!Test::can_average()) { - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set().average(target_col), ErrorCodes::IllegalOperation); - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(set_as_results().average(target_col), - ErrorCodes::IllegalOperation); - return; + if constexpr (!Test::can_average) { + REQUIRE_EXCEPTION( + set().average(target_col), IllegalOperation, + util::format("Operation 'average' not supported for %1 property 'table2.value'", Test::name)); + REQUIRE_EXCEPTION( + set_as_results().average(target_col), IllegalOperation, + util::format("Operation 'average' not supported for %1 property 'table2.value'", Test::name)); + } + else { + REQUIRE(cf::get(*set().average(target_col)) == Test::average()); + REQUIRE(cf::get(*set_as_results().average(target_col)) == Test::average()); + write([&] { + set().remove_all(); + }); + REQUIRE(!set().average(target_col)); + REQUIRE(!set_as_results().average(target_col)); } - REQUIRE(cf::get(*set().average(target_col)) == Test::average()); - REQUIRE(cf::get(*set_as_results().average(target_col)) == Test::average()); - write([&]() { - set().remove_all(); - }); - REQUIRE(!set().average(target_col)); - REQUIRE(!set_as_results().average(target_col)); } SECTION("sort") { - if (!Test::can_sort()) { + if constexpr (!Test::can_sort) { REQUIRE_THROWS_CONTAINING(set_as_results().sort({{"value", true}}), "is of unsupported type"); - return; } - SECTION("ascending") { - auto sorted = set_as_results().sort({{"value", true}}); - std::sort(begin(values), end(values), cf::less()); - for (size_t i = 0; i < values.size(); ++i) { - REQUIRE(sorted.get(i).template get(target_col) == values[i]); + else { + SECTION("ascending") { + auto sorted = set_as_results().sort({{"value", true}}); + std::sort(begin(values), end(values), std::less()); + for (size_t i = 0; i < values.size(); ++i) { + REQUIRE(sorted.get(i).template get(target_col) == values[i]); + } } - } - SECTION("descending") { - auto sorted = set_as_results().sort({{"value", false}}); - std::sort(begin(values), end(values), cf::greater()); - for (size_t i = 0; i < values.size(); ++i) { - REQUIRE(sorted.get(i).template get(target_col) == values[i]); + SECTION("descending") { + auto sorted = set_as_results().sort({{"value", false}}); + std::sort(begin(values), end(values), std::greater()); + for (size_t i = 0; i < values.size(); ++i) { + REQUIRE(sorted.get(i).template get(target_col) == values[i]); + } } } } @@ -565,7 +607,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) SECTION("basics") { auto set = TestType::get_set(r, obj, col_int_set); - write([&]() { + write([&] { CHECK(set().insert(123).second); CHECK(set().insert(456).second); CHECK(set().insert(0).second); @@ -581,7 +623,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) CHECK(set().find_any(456) == 3); CHECK(set().find(999) == size_t(-1)); - write([&]() { + write([&] { CHECK(set().remove(123).second); CHECK(!set().remove(123).second); CHECK(set().remove_any(-1).second); @@ -589,7 +631,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) CHECK(set().size() == 2); - write([&]() { + write([&] { obj.remove(); }); CHECK(!set().is_valid()); @@ -599,7 +641,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) auto set = TestType::get_set(r, obj, col_decimal_set); auto results = set().as_results(); - write([&]() { + write([&] { CHECK(set().insert(Decimal128(5)).second); CHECK(set().insert(Decimal128(realm::null())).second); CHECK(set().insert(Decimal128(7)).second); @@ -616,13 +658,13 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) auto set = TestType::get_set(r, obj, col_link_set); Obj target1, target2, target3; - write([&]() { + write([&] { target1 = table2->create_object_with_primary_key(123); target2 = table2->create_object_with_primary_key(456); target3 = table2->create_object_with_primary_key(789); }); - write([&]() { + write([&] { CHECK(set().insert(target1).second); CHECK(!set().insert(target1).second); CHECK(set().insert(target2).second); @@ -636,17 +678,17 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) CHECK(set().find(target2) != size_t(-1)); CHECK(set().find(target3) != size_t(-1)); - write([&]() { + write([&] { target2.invalidate(); }); // Invalidating the object changes the reported size of the set(). CHECK(set().size() == 2); - CHECK_THROWS(set().find(target2)); + REQUIRE_EXCEPTION(set().find(target2), StaleAccessor, "Object has been deleted or invalidated"); // Resurrect the tombstone of target2. - write([&]() { + write([&] { target2 = table2->create_object_with_primary_key(456); }); CHECK(set().find(target2)); @@ -656,7 +698,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) SECTION("max / min / sum / avg") { auto set = TestType::get_set(r, obj, col_int_set); - write([&]() { + write([&] { CHECK(set().insert(123).second); CHECK(set().insert(456).second); CHECK(set().insert(0).second); @@ -697,7 +739,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) SECTION("modifying the set sends change notifications") { Obj target1, target2, target3; - write([&]() { + write([&] { target1 = table2->create_object_with_primary_key(123); target2 = table2->create_object_with_primary_key(456); target3 = table2->create_object_with_primary_key(789); @@ -705,7 +747,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) auto token = require_change(); - write([&]() { + write([&] { CHECK(link_set.insert(target1).second); CHECK(!link_set.insert(target1).second); CHECK(link_set.insert(target2).second); @@ -733,13 +775,13 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) auto token = require_change(); Obj target1, target2, target3; - write([&]() { + write([&] { target1 = table2->create_object_with_primary_key(123); target2 = table2->create_object_with_primary_key(456); target3 = table2->create_object_with_primary_key(789); }); - write([&]() { + write([&] { CHECK(link_set.insert(target1).second); CHECK(!link_set.insert(target1).second); CHECK(link_set.insert(target2).second); @@ -764,14 +806,14 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) auto token = require_change(); Obj target1, target2, target3, target4; - write([&]() { + write([&] { target1 = table2->create_object_with_primary_key(123); target2 = table2->create_object_with_primary_key(456); target3 = table2->create_object_with_primary_key(789); target4 = table2->create_object_with_primary_key(101); }); - write([&]() { + write([&] { CHECK(link_set.insert(target1).second); CHECK(link_set.insert(target2).second); CHECK(link_set.insert(target3).second); @@ -790,13 +832,13 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) auto token = require_change(); Obj target1, target2, target3; - write([&]() { + write([&] { target1 = table2->create_object_with_primary_key(123); target2 = table2->create_object_with_primary_key(456); target3 = table2->create_object_with_primary_key(789); }); - write([&]() { + write([&] { CHECK(link_set.insert(target1).second); CHECK(!link_set.insert(target1).second); CHECK(link_set.insert(target2).second); @@ -824,7 +866,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) REQUIRE(link_set.size() == 0); REQUIRE(!change.collection_root_was_deleted); - write([&]() { + write([&] { obj.remove(); }); REQUIRE(change.deletions.empty()); @@ -882,7 +924,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) }; Obj target; - write([&]() { + write([&] { target = table2->create_object_with_primary_key(42); target.set(col_table2_value, 42); link_set.insert(target); @@ -977,13 +1019,13 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) auto set = TestType::get_set(r, obj, col_link_set); Obj target1, target2, target3; - write([&]() { + write([&] { target1 = table2->create_object_with_primary_key(123); target2 = table2->create_object_with_primary_key(456); target3 = table2->create_object_with_primary_key(789); }); - write([&]() { + write([&] { CHECK(set().insert(target1).second); CHECK(!set().insert(target1).second); CHECK(set().insert(target2).second); @@ -1005,7 +1047,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) auto set2 = TestType::get_set(r, other_obj, other_col_link_set); std::vector targets; - write([&]() { + write([&] { targets.push_back(table2->create_object_with_primary_key(123)); targets.push_back(table2->create_object_with_primary_key(456)); targets.push_back(table2->create_object_with_primary_key(789)); @@ -1014,7 +1056,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) targets.push_back(table2->create_object_with_primary_key(987)); }); - write([&]() { + write([&] { for (auto& obj : targets) { CHECK(set().insert(obj).second); } @@ -1039,7 +1081,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) auto set2 = TestType::get_set(r, other_obj, other_col_link_set); std::vector targets; - write([&]() { + write([&] { targets.push_back(table2->create_object_with_primary_key(123)); targets.push_back(table2->create_object_with_primary_key(456)); targets.push_back(table2->create_object_with_primary_key(789)); @@ -1049,13 +1091,13 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) }); std::vector other_targets; - write([&]() { + write([&] { other_targets.push_back(other_table2->create_object_with_primary_key(111)); other_targets.push_back(other_table2->create_object_with_primary_key(222)); other_targets.push_back(other_table2->create_object_with_primary_key(333)); }); - write([&]() { + write([&] { for (auto& obj : targets) { CHECK(set().insert(obj).second); } @@ -1077,7 +1119,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) // (123, 456, 789, (321, 654, 987), 111, 222, 333) REQUIRE(set2().intersects(set())); REQUIRE(set().intersects(set2())); - write([&]() { + write([&] { set2().remove(targets[0]); set2().remove(targets[1]); set2().remove(targets[2]); @@ -1092,7 +1134,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) auto set2 = TestType::get_set(r, other_obj, other_col_link_set); std::vector targets; - write([&]() { + write([&] { targets.push_back(table2->create_object_with_primary_key(123)); targets.push_back(table2->create_object_with_primary_key(456)); targets.push_back(table2->create_object_with_primary_key(789)); @@ -1102,13 +1144,13 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) }); std::vector other_targets; - write([&]() { + write([&] { other_targets.push_back(other_table2->create_object_with_primary_key(111)); other_targets.push_back(other_table2->create_object_with_primary_key(222)); other_targets.push_back(other_table2->create_object_with_primary_key(333)); }); - write([&]() { + write([&] { for (auto& obj : targets) { CHECK(set().insert(obj).second); } @@ -1139,7 +1181,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) auto set2 = TestType::get_set(r, other_obj, other_col_link_set); std::vector targets; - write([&]() { + write([&] { targets.push_back(table2->create_object_with_primary_key(123)); targets.push_back(table2->create_object_with_primary_key(456)); targets.push_back(table2->create_object_with_primary_key(789)); @@ -1149,13 +1191,13 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) }); std::vector other_targets; - write([&]() { + write([&] { other_targets.push_back(other_table2->create_object_with_primary_key(111)); other_targets.push_back(other_table2->create_object_with_primary_key(222)); other_targets.push_back(other_table2->create_object_with_primary_key(333)); }); - write([&]() { + write([&] { for (auto& obj : targets) { CHECK(set().insert(obj).second); } @@ -1186,7 +1228,7 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) auto set2 = TestType::get_set(r, other_obj, other_col_link_set); std::vector targets; - write([&]() { + write([&] { targets.push_back(table2->create_object_with_primary_key(123)); targets.push_back(table2->create_object_with_primary_key(456)); targets.push_back(table2->create_object_with_primary_key(789)); @@ -1196,13 +1238,13 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) }); std::vector other_targets; - write([&]() { + write([&] { other_targets.push_back(other_table2->create_object_with_primary_key(111)); other_targets.push_back(other_table2->create_object_with_primary_key(222)); other_targets.push_back(other_table2->create_object_with_primary_key(333)); }); - write([&]() { + write([&] { for (auto& obj : targets) { CHECK(set().insert(obj).second); } @@ -1232,39 +1274,39 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) auto set = TestType::get_set(r, obj, col_decimal_set); List list{r, obj, col_decimal_list}; - write([&]() { + write([&] { CHECK(set().insert(Decimal128(5)).second); CHECK(set().insert(Decimal128(realm::null())).second); CHECK(set().insert(Decimal128(7)).second); }); - write([&]() { + write([&] { list.add(Decimal128(4)); list.add(Decimal128(realm::null())); list.add(Decimal128(7)); list.add(Decimal128(4)); }); REQUIRE(set().intersects(list)); - write([&]() { + write([&] { set().assign_union(list); // set == { null, 4, 5, 7 } }); REQUIRE(set().size() == 4); REQUIRE(set().is_strict_superset_of(list)); - write([&]() { + write([&] { set().assign_difference(list); // set == { 5 } }); REQUIRE(set().size() == 1); - write([&]() { + write([&] { CHECK(set().insert(Decimal128(4)).second); // set == { 4, 5 } set().assign_symmetric_difference(list); // set == { null, 5, 7 } }); REQUIRE(set().size() == 3); - write([&]() { + write([&] { set().assign_intersection(list); // set == { null, 7 } }); REQUIRE(set().size() == 2); REQUIRE(set().is_strict_subset_of(list)); - write([&]() { + write([&] { CHECK(set().insert(Decimal128(4)).second); // set == { null, 4, 7 } }); REQUIRE(set().set_equals(list)); diff --git a/test/object-store/sync/app.cpp b/test/object-store/sync/app.cpp index 52305ea5095..6cf6d18930d 100644 --- a/test/object-store/sync/app.cpp +++ b/test/object-store/sync/app.cpp @@ -2990,7 +2990,7 @@ TEMPLATE_TEST_CASE("app: partition types", "[sync][app][partition]", cf::Int, cf const std::string partition_key_col_name = "partition_key_prop"; const std::string table_name = "class_partition_test_type"; REQUIRE(!base_url.empty()); - auto partition_property = Property(partition_key_col_name, TestType::property_type()); + auto partition_property = Property(partition_key_col_name, TestType::property_type); Schema schema = {{Group::table_name_to_class_name(table_name), { {valid_pk_name, PropertyType::Int, true}, diff --git a/test/object-store/sync/client_reset.cpp b/test/object-store/sync/client_reset.cpp index 83dd1ac1802..92bf1e99538 100644 --- a/test/object-store/sync/client_reset.cpp +++ b/test/object-store/sync/client_reset.cpp @@ -1619,10 +1619,10 @@ TEMPLATE_TEST_CASE("client reset types", "[client reset][local]", cf::MixedVal, }}, {"test type", {{"_id", PropertyType::Int, Property::IsPrimary{true}}, - {"value", TestType::property_type()}, - {"list", PropertyType::Array | TestType::property_type()}, - {"dictionary", PropertyType::Dictionary | TestType::property_type()}, - {"set", PropertyType::Set | TestType::property_type()}}}, + {"value", TestType::property_type}, + {"list", PropertyType::Array | TestType::property_type}, + {"dictionary", PropertyType::Dictionary | TestType::property_type}, + {"set", PropertyType::Set | TestType::property_type}}}, }; SyncTestFile config2(init_sync_manager.app(), "default"); diff --git a/test/object-store/sync/flx_sync.cpp b/test/object-store/sync/flx_sync.cpp index 28586d0e9c2..f47ce22685d 100644 --- a/test/object-store/sync/flx_sync.cpp +++ b/test/object-store/sync/flx_sync.cpp @@ -1372,7 +1372,7 @@ TEST_CASE("flx: connect to FLX with partition value returns an error", "[sync][f SyncTestFile config(harness.app()->current_user(), harness.schema(), SyncConfig::FLXSyncEnabled{}); config.sync_config->partition_value = "\"foobar\""; - REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(Realm::get_shared_realm(config), ErrorCodes::IllegalCombination); + REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination, "asdf"); } TEST_CASE("flx: connect to PBS as FLX returns an error", "[sync][flx][app]") { @@ -1967,7 +1967,7 @@ TEST_CASE("flx: asymmetric sync", "[sync][flx][app]") { }; SyncTestFile config(harness->app(), bson::Bson{}, schema); - REQUIRE_THROWS(Realm::get_shared_realm(config)); + REQUIRE_EXCEPTION(Realm::get_shared_realm(config), InvalidSession, "asdf"); } // Add any new test sections above this point diff --git a/test/object-store/sync/metadata.cpp b/test/object-store/sync/metadata.cpp index 48acbd0ac66..5b5f5f6d6ce 100644 --- a/test/object-store/sync/metadata.cpp +++ b/test/object-store/sync/metadata.cpp @@ -451,7 +451,8 @@ TEST_CASE("sync_metadata: encryption", "[sync]") { } // Verify that the file is actually encrypted - REQUIRE_THROWS_AS(Group(metadata_path), InvalidDatabase); + REQUIRE_EXCEPTION(Group(metadata_path), InvalidDatabase, + Catch::Matchers::ContainsSubstring("invalid mnemonic")); } SECTION("leaves existing unencrypted files unencrypted") { @@ -480,10 +481,11 @@ TEST_CASE("sync_metadata: encryption", "[sync]") { REQUIRE(manager.get_current_user_identity() == none); } // New file should be encrypted - REQUIRE_THROWS_AS(Group(metadata_path), InvalidDatabase); + REQUIRE_EXCEPTION(Group(metadata_path), InvalidDatabase, + Catch::Matchers::ContainsSubstring("invalid mnemonic")); } #else - REQUIRE_THROWS(SyncMetadataManager(metadata_path, true, none)); + REQUIRE_EXCEPTION(SyncMetadataManager(metadata_path, true, none), InvalidSession, "asdf"); #endif } } diff --git a/test/object-store/thread_safe_reference.cpp b/test/object-store/thread_safe_reference.cpp index d5ac4a67aad..86834ae655f 100644 --- a/test/object-store/thread_safe_reference.cpp +++ b/test/object-store/thread_safe_reference.cpp @@ -867,14 +867,18 @@ TEST_CASE("thread safe reference") { } SECTION("object results") { - REQUIRE_THROWS(create_ref([](auto& r) { - auto obj = - create_object(r, "int array object", {{"value", AnyVector{AnyDict{{"value", INT64_C(0)}}}}}); - Results results = List(r, obj.obj(), get_table(*r, "int array object")->get_column_key("value")) - .sort({{"value", true}}); - REQUIRE(results.size() == 1); - return results; - })); + REQUIRE_EXCEPTION(create_ref([](auto& r) { + auto obj = create_object(r, "int array object", + {{"value", AnyVector{AnyDict{{"value", INT64_C(0)}}}}}); + Results results = + List(r, obj.obj(), get_table(*r, "int array object")->get_column_key("value")) + .sort({{"value", true}}); + REQUIRE(results.size() == 1); + return results; + }), + WrongTransactionState, + "Cannot create a ThreadSafeReference to Results backed by a collection of objects " + "inside the write transaction which created the collection."); } SECTION("int results") { @@ -992,14 +996,18 @@ TEST_CASE("thread safe reference") { } SECTION("object results") { - REQUIRE_THROWS(create_ref([](auto& r) { - auto obj = - create_object(r, "int array object", {{"value", AnyVector{AnyDict{{"value", INT64_C(0)}}}}}); - Results results = List(r, obj.obj(), get_table(*r, "int array object")->get_column_key("value")) - .sort({{"value", true}}); - REQUIRE(results.size() == 1); - return results; - })); + REQUIRE_EXCEPTION(create_ref([](auto& r) { + auto obj = create_object(r, "int array object", + {{"value", AnyVector{AnyDict{{"value", INT64_C(0)}}}}}); + Results results = + List(r, obj.obj(), get_table(*r, "int array object")->get_column_key("value")) + .sort({{"value", true}}); + REQUIRE(results.size() == 1); + return results; + }), + WrongTransactionState, + "Cannot create a ThreadSafeReference to Results backed by a collection of objects " + "inside the write transaction which created the collection."); } SECTION("int results") { diff --git a/test/object-store/transaction_log_parsing.cpp b/test/object-store/transaction_log_parsing.cpp index 415b1d0a457..2dcd8d29fef 100644 --- a/test/object-store/transaction_log_parsing.cpp +++ b/test/object-store/transaction_log_parsing.cpp @@ -267,7 +267,8 @@ TEST_CASE("Transaction log parsing: schema change validation") { table->remove_column(table->get_column_key("indexed")); wt->commit(); - REQUIRE_THROWS(r->refresh()); + REQUIRE_EXCEPTION(r->refresh(), InvalidSchemaChange, + Catch::Matchers::ContainsSubstring("Property 'table.indexed' has been removed.")); } SECTION("removing a table is not allowed") { @@ -275,7 +276,8 @@ TEST_CASE("Transaction log parsing: schema change validation") { wt->remove_table("class_table"); wt->commit(); - REQUIRE_THROWS(r->refresh()); + REQUIRE_EXCEPTION(r->refresh(), InvalidSchemaChange, + Catch::Matchers::ContainsSubstring("Class 'table' has been removed.")); } } diff --git a/test/object-store/util/test_file.cpp b/test/object-store/util/test_file.cpp index fe12b9829e9..2171f7e0dd6 100644 --- a/test/object-store/util/test_file.cpp +++ b/test/object-store/util/test_file.cpp @@ -117,10 +117,14 @@ static const std::string fake_refresh_token = ENCODE_FAKE_JWT("not_a_real_token" static const std::string fake_access_token = ENCODE_FAKE_JWT("also_not_real"); static const std::string fake_device_id = "123400000000000000000000"; +static std::shared_ptr get_fake_user(app::App& app, const std::string& user_name) +{ + return app.sync_manager()->get_user(user_name, fake_refresh_token, fake_access_token, app.base_url(), + fake_device_id); +} + SyncTestFile::SyncTestFile(std::shared_ptr app, std::string name, std::string user_name) - : SyncTestFile(app->sync_manager()->get_user(user_name, fake_refresh_token, fake_access_token, app->base_url(), - fake_device_id), - bson::Bson(name)) + : SyncTestFile(get_fake_user(*app, user_name), bson::Bson(name)) { } @@ -319,7 +323,7 @@ TestAppSession::~TestAppSession() } } -#endif +#endif // REALM_ENABLE_AUTH_TESTS // MARK: - TestSyncManager @@ -363,6 +367,11 @@ TestSyncManager::~TestSyncManager() } } +std::shared_ptr TestSyncManager::fake_user(const std::string& name) +{ + return get_fake_user(*m_app, name); +} + #endif // REALM_ENABLE_SYNC #if REALM_HAVE_CLANG_FEATURE(thread_sanitizer) diff --git a/test/object-store/util/test_file.hpp b/test/object-store/util/test_file.hpp index de433430776..3cd5f4b2664 100644 --- a/test/object-store/util/test_file.hpp +++ b/test/object-store/util/test_file.hpp @@ -247,6 +247,8 @@ class TestSyncManager { return m_sync_server; } + std::shared_ptr fake_user(const std::string& name = "test"); + // Capture the token refresh callback so that we can invoke it later with // the desired result struct TransportCallback { diff --git a/test/object-store/util/test_utils.cpp b/test/object-store/util/test_utils.cpp index eeb980a8970..1aa386dfcf4 100644 --- a/test/object-store/util/test_utils.cpp +++ b/test/object-store/util/test_utils.cpp @@ -20,6 +20,7 @@ #include "test_utils.hpp" #include +#include #include #include @@ -39,6 +40,32 @@ namespace realm { +bool ExceptionMatcher::match(Exception const& ex) const +{ + return ex.code() == m_code && ex.what() == m_message; +} + +std::string ExceptionMatcher::describe() const +{ + return util::format("Exception(%1, \"%2\")", ErrorCodes::error_string(m_code), m_message); +} + +bool OutOfBoundsMatcher::match(OutOfBounds const& ex) const +{ + return ex.code() == ErrorCodes::OutOfBounds && ex.index == m_index && ex.size == m_size && ex.what() == m_message; +} + +std::string OutOfBoundsMatcher::describe() const +{ + return util::format("OutOfBounds(index=%1, size=%2, \"%3\")", m_index, m_size, m_message); +} + +std::ostream& operator<<(std::ostream& os, const Exception& e) +{ + os << util::get_type_name(e) << "(" << e.code_string() << ", \"" << e.what() << "\")"; + return os; +} + bool create_dummy_realm(std::string path) { Realm::Config config; @@ -111,6 +138,25 @@ std::string encode_fake_jwt(const std::string& in, util::Optional exp, return encoded_prefix + "." + encoded_body + "." + suffix; } +std::string random_string(std::string::size_type length) +{ + static auto& chrs = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + thread_local static std::mt19937 rg{std::random_device{}()}; + thread_local static std::uniform_int_distribution pick(0, sizeof(chrs) - 2); + std::string s; + s.reserve(length); + while (length--) + s += chrs[pick(rg)]; + return s; +} + +int64_t random_int() +{ + thread_local std::mt19937_64 rng(std::random_device{}()); + return rng(); +} + static bool file_is_on_exfat(const std::string& path) { #if REALM_PLATFORM_APPLE diff --git a/test/object-store/util/test_utils.hpp b/test/object-store/util/test_utils.hpp index a5f37abec7d..2e586e4538f 100644 --- a/test/object-store/util/test_utils.hpp +++ b/test/object-store/util/test_utils.hpp @@ -29,6 +29,89 @@ namespace fs = std::filesystem; namespace realm { +template +class ExceptionMatcher final : public Catch::Matchers::MatcherBase { +public: + ExceptionMatcher(ErrorCodes::Error code, MessageMatcher&& matcher) + : m_code(code) + , m_matcher(std::move(matcher)) + { + } + + bool match(Exception const& ex) const override + { + return ex.code() == m_code && m_matcher.match(ex.what()); + } + + std::string describe() const override + { + return util::format("Exception(%1, \"%2\")", ErrorCodes::error_string(m_code), m_matcher.describe()); + } + +private: + ErrorCodes::Error m_code; + MessageMatcher m_matcher; +}; + +template <> +class ExceptionMatcher final : public Catch::Matchers::MatcherBase { +public: + ExceptionMatcher(ErrorCodes::Error code, std::string_view msg) + : m_code(code) + , m_message(msg) + { + } + + bool match(Exception const& ex) const override; + std::string describe() const override; + +private: + ErrorCodes::Error m_code; + std::string m_message; +}; + +class OutOfBoundsMatcher final : public Catch::Matchers::MatcherBase { +public: + OutOfBoundsMatcher(size_t index, size_t size, std::string_view msg) + : m_index(index) + , m_size(size) + , m_message(msg) + { + } + + bool match(OutOfBounds const& ex) const override; + std::string describe() const override; + +private: + size_t m_index, m_size; + std::string m_message; +}; + +namespace _impl { +template +ExceptionMatcher make_exception_matcher(ErrorCodes::Error code, T&& matcher) +{ + return ExceptionMatcher(code, std::move(matcher)); +} +inline ExceptionMatcher make_exception_matcher(ErrorCodes::Error code, const char* msg) +{ + return ExceptionMatcher(code, msg); +} +inline ExceptionMatcher make_exception_matcher(ErrorCodes::Error code, std::string_view msg) +{ + return ExceptionMatcher(code, msg); +} +inline ExceptionMatcher make_exception_matcher(ErrorCodes::Error code, const std::string& msg) +{ + return ExceptionMatcher(code, msg); +} +inline ExceptionMatcher make_exception_matcher(ErrorCodes::Error code, std::string&& msg) +{ + return ExceptionMatcher(code, msg); +} +} // namespace _impl + +std::ostream& operator<<(std::ostream&, const Exception&); /// Open a Realm at a given path, creating its files. bool create_dummy_realm(std::string path); @@ -40,24 +123,8 @@ void catch2_ensure_section_run_workaround(bool did_run_a_section, std::string se std::string encode_fake_jwt(const std::string& in, util::Optional exp = {}, util::Optional iat = {}); -static inline std::string random_string(std::string::size_type length) -{ - static auto& chrs = "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - thread_local static std::mt19937 rg{std::random_device{}()}; - thread_local static std::uniform_int_distribution pick(0, sizeof(chrs) - 2); - std::string s; - s.reserve(length); - while (length--) - s += chrs[pick(rg)]; - return s; -} - -static inline int64_t random_int() -{ - thread_local std::mt19937_64 rng(std::random_device{}()); - return rng(); -} +std::string random_string(std::string::size_type length); +int64_t random_int(); bool chmod_supported(const std::string& path); int get_permissions(const std::string& path); @@ -102,6 +169,24 @@ std::string get_parent_directory(const std::string& path); #define REQUIRE_THROWS_CONTAINING(expr, msg) REQUIRE_THROWS_WITH(expr, Catch::Matchers::ContainsSubstring(msg)) +namespace { +} + +#define REQUIRE_EXCEPTION(expr, c, msg) \ + REQUIRE_THROWS_MATCHES(expr, realm::Exception, _impl::make_exception_matcher(realm::ErrorCodes::c, msg)) +#define REQUIRE_THROWS_OUT_OF_BOUNDS(expr, index, size, msg) \ + REQUIRE_THROWS_MATCHES(expr, OutOfBounds, OutOfBoundsMatcher(index, size, msg)); + +#define REQUIRE_THROWS_WITH_CODE(expr, err) \ + do { \ + try { \ + expr; \ + } \ + catch (const Exception& e) { \ + REQUIRE(e.code() == err); \ + } \ + } while (0) + #define REQUIRE_THROW_LOGIC_ERROR_WITH_CODE(expr, err) \ do { \ try { \ diff --git a/test/test_string_data.cpp b/test/test_string_data.cpp index 92c28f3ead6..d5748908a6a 100644 --- a/test/test_string_data.cpp +++ b/test/test_string_data.cpp @@ -261,7 +261,6 @@ TEST(StringData_Like) StringData empty(""); StringData f("f"); StringData foo("foo"); - StringData bar("bar"); StringData foobar("foobar"); StringData foofoo("foofoo"); StringData foobarfoo("foobarfoo"); @@ -319,7 +318,6 @@ TEST(StringData_Like_CaseInsensitive) StringData empty(""); StringData f("f"); StringData foo("FoO"); - StringData bar("bAr"); StringData foobar("FOOBAR"); StringData foofoo("FOOfoo"); StringData foobarfoo("FoObArFoO"); diff --git a/test/test_table.cpp b/test/test_table.cpp index 1aa8016207d..bcf4fb5b218 100644 --- a/test/test_table.cpp +++ b/test/test_table.cpp @@ -689,7 +689,6 @@ TEST(Table_AggregateFuzz) ObjKey key; size_t cnt; int64_t i; - Timestamp ts; Mixed m; // Test methods on Table