realm.cpp 67 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2016 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. #include "catch2/catch.hpp"
  19. #include "util/event_loop.hpp"
  20. #include "util/test_file.hpp"
  21. #include "util/test_utils.hpp"
  22. #include "binding_context.hpp"
  23. #include "object_schema.hpp"
  24. #include "object_store.hpp"
  25. #include "property.hpp"
  26. #include "results.hpp"
  27. #include "schema.hpp"
  28. #include "thread_safe_reference.hpp"
  29. #include "impl/realm_coordinator.hpp"
  30. #if REALM_ENABLE_SYNC
  31. #include "sync/async_open_task.hpp"
  32. #endif
  33. #include <realm/group.hpp>
  34. #include <realm/util/scope_exit.hpp>
  35. namespace realm {
  36. class TestHelper {
  37. public:
  38. static SharedGroup& get_shared_group(SharedRealm const& shared_realm)
  39. {
  40. return *Realm::Internal::get_shared_group(*shared_realm);
  41. }
  42. static void begin_read(SharedRealm const& shared_realm, VersionID version)
  43. {
  44. Realm::Internal::begin_read(*shared_realm, version);
  45. }
  46. };
  47. }
  48. using namespace realm;
  49. TEST_CASE("SharedRealm: get_shared_realm()") {
  50. TestFile config;
  51. config.schema_version = 1;
  52. config.schema = Schema{
  53. {"object", {
  54. {"value", PropertyType::Int}
  55. }},
  56. };
  57. SECTION("should return the same instance when caching is enabled") {
  58. auto realm1 = Realm::get_shared_realm(config);
  59. auto realm2 = Realm::get_shared_realm(config);
  60. REQUIRE(realm1.get() == realm2.get());
  61. }
  62. SECTION("should return different instances when caching is disabled") {
  63. config.cache = false;
  64. auto realm1 = Realm::get_shared_realm(config);
  65. auto realm2 = Realm::get_shared_realm(config);
  66. REQUIRE(realm1.get() != realm2.get());
  67. }
  68. SECTION("should validate that the config is sensible") {
  69. SECTION("bad encryption key") {
  70. config.encryption_key = std::vector<char>(2, 0);
  71. REQUIRE_THROWS(Realm::get_shared_realm(config));
  72. }
  73. SECTION("schema without schema version") {
  74. config.schema_version = ObjectStore::NotVersioned;
  75. REQUIRE_THROWS(Realm::get_shared_realm(config));
  76. }
  77. SECTION("migration function for immutable") {
  78. config.schema_mode = SchemaMode::Immutable;
  79. config.migration_function = [](auto, auto, auto) { };
  80. REQUIRE_THROWS(Realm::get_shared_realm(config));
  81. }
  82. SECTION("migration function for read-only") {
  83. config.schema_mode = SchemaMode::ReadOnlyAlternative;
  84. config.migration_function = [](auto, auto, auto) { };
  85. REQUIRE_THROWS(Realm::get_shared_realm(config));
  86. }
  87. SECTION("migration function for additive-only") {
  88. config.schema_mode = SchemaMode::Additive;
  89. config.migration_function = [](auto, auto, auto) { };
  90. REQUIRE_THROWS(Realm::get_shared_realm(config));
  91. }
  92. SECTION("initialization function for immutable") {
  93. config.schema_mode = SchemaMode::Immutable;
  94. config.initialization_function = [](auto) { };
  95. REQUIRE_THROWS(Realm::get_shared_realm(config));
  96. }
  97. SECTION("initialization function for read-only") {
  98. config.schema_mode = SchemaMode::ReadOnlyAlternative;
  99. config.initialization_function = [](auto) { };
  100. REQUIRE_THROWS(Realm::get_shared_realm(config));
  101. }
  102. }
  103. SECTION("should reject mismatched config") {
  104. SECTION("cached") { }
  105. SECTION("uncached") { config.cache = false; }
  106. SECTION("schema version") {
  107. auto realm = Realm::get_shared_realm(config);
  108. config.schema_version = 2;
  109. REQUIRE_THROWS(Realm::get_shared_realm(config));
  110. config.schema = util::none;
  111. config.schema_version = ObjectStore::NotVersioned;
  112. REQUIRE_NOTHROW(Realm::get_shared_realm(config));
  113. }
  114. SECTION("schema mode") {
  115. auto realm = Realm::get_shared_realm(config);
  116. config.schema_mode = SchemaMode::Manual;
  117. REQUIRE_THROWS(Realm::get_shared_realm(config));
  118. }
  119. SECTION("durability") {
  120. auto realm = Realm::get_shared_realm(config);
  121. config.in_memory = true;
  122. REQUIRE_THROWS(Realm::get_shared_realm(config));
  123. }
  124. SECTION("schema") {
  125. auto realm = Realm::get_shared_realm(config);
  126. config.schema = Schema{
  127. {"object", {
  128. {"value", PropertyType::Int},
  129. {"value2", PropertyType::Int}
  130. }},
  131. };
  132. REQUIRE_THROWS(Realm::get_shared_realm(config));
  133. }
  134. }
  135. // Windows doesn't use fifos
  136. #ifndef _WIN32
  137. SECTION("should be able to set a FIFO fallback path") {
  138. std::string fallback_dir = tmp_dir() + "/fallback/";
  139. realm::util::try_make_dir(fallback_dir);
  140. TestFile config;
  141. config.fifo_files_fallback_path = fallback_dir;
  142. config.schema_version = 1;
  143. config.schema = Schema{
  144. {"object", {
  145. {"value", PropertyType::Int}
  146. }},
  147. };
  148. realm::util::make_dir(config.path + ".note");
  149. auto realm = Realm::get_shared_realm(config);
  150. auto fallback_file = util::format("%1realm_%2.note", fallback_dir, std::hash<std::string>()(config.path)); // Mirror internal implementation
  151. REQUIRE(File::exists(fallback_file));
  152. realm::util::remove_dir(config.path + ".note");
  153. realm::util::remove_dir_recursive(fallback_dir);
  154. }
  155. SECTION("automatically append dir separator to end of fallback path") {
  156. std::string fallback_dir = tmp_dir() + "/fallback";
  157. realm::util::try_make_dir(fallback_dir);
  158. TestFile config;
  159. config.fifo_files_fallback_path = fallback_dir;
  160. config.schema_version = 1;
  161. config.schema = Schema{
  162. {"object", {
  163. {"value", PropertyType::Int}
  164. }},
  165. };
  166. realm::util::make_dir(config.path + ".note");
  167. auto realm = Realm::get_shared_realm(config);
  168. auto fallback_file = util::format("%1/realm_%2.note", fallback_dir, std::hash<std::string>()(config.path)); // Mirror internal implementation
  169. REQUIRE(File::exists(fallback_file));
  170. realm::util::remove_dir(config.path + ".note");
  171. realm::util::remove_dir_recursive(fallback_dir);
  172. }
  173. #endif
  174. SECTION("should verify that the schema is valid") {
  175. config.schema = Schema{
  176. {"object",
  177. {{"value", PropertyType::Int}},
  178. {{"invalid backlink", PropertyType::LinkingObjects|PropertyType::Array, "object", "value"}}
  179. }
  180. };
  181. REQUIRE_THROWS_WITH(Realm::get_shared_realm(config),
  182. Catch::Matchers::Contains("origin of linking objects property"));
  183. }
  184. SECTION("should apply the schema if one is supplied") {
  185. Realm::get_shared_realm(config);
  186. {
  187. Group g(config.path);
  188. auto table = ObjectStore::table_for_object_type(g, "object");
  189. REQUIRE(table);
  190. REQUIRE(table->get_column_count() == 1);
  191. REQUIRE(table->get_column_name(0) == "value");
  192. }
  193. config.schema_version = 2;
  194. config.schema = Schema{
  195. {"object", {
  196. {"value", PropertyType::Int},
  197. {"value2", PropertyType::Int}
  198. }},
  199. };
  200. bool migration_called = false;
  201. config.migration_function = [&](SharedRealm old_realm, SharedRealm new_realm, Schema&) {
  202. migration_called = true;
  203. REQUIRE(ObjectStore::table_for_object_type(old_realm->read_group(), "object")->get_column_count() == 1);
  204. REQUIRE(ObjectStore::table_for_object_type(new_realm->read_group(), "object")->get_column_count() == 2);
  205. };
  206. Realm::get_shared_realm(config);
  207. REQUIRE(migration_called);
  208. }
  209. SECTION("should properly roll back from migration errors") {
  210. Realm::get_shared_realm(config);
  211. config.schema_version = 2;
  212. config.schema = Schema{
  213. {"object", {
  214. {"value", PropertyType::Int},
  215. {"value2", PropertyType::Int}
  216. }},
  217. };
  218. bool migration_called = false;
  219. config.migration_function = [&](SharedRealm old_realm, SharedRealm new_realm, Schema&) {
  220. REQUIRE(ObjectStore::table_for_object_type(old_realm->read_group(), "object")->get_column_count() == 1);
  221. REQUIRE(ObjectStore::table_for_object_type(new_realm->read_group(), "object")->get_column_count() == 2);
  222. if (!migration_called) {
  223. migration_called = true;
  224. throw "error";
  225. }
  226. };
  227. REQUIRE_THROWS_WITH(Realm::get_shared_realm(config), "error");
  228. REQUIRE(migration_called);
  229. REQUIRE_NOTHROW(Realm::get_shared_realm(config));
  230. }
  231. SECTION("should read the schema from the file if none is supplied") {
  232. Realm::get_shared_realm(config);
  233. config.schema = util::none;
  234. auto realm = Realm::get_shared_realm(config);
  235. REQUIRE(realm->schema().size() == 1);
  236. auto it = realm->schema().find("object");
  237. REQUIRE(it != realm->schema().end());
  238. REQUIRE(it->persisted_properties.size() == 1);
  239. REQUIRE(it->persisted_properties[0].name == "value");
  240. REQUIRE(it->persisted_properties[0].table_column == 0);
  241. }
  242. SECTION("should read the proper schema from the file if a custom version is supplied") {
  243. Realm::get_shared_realm(config);
  244. config.schema = util::none;
  245. config.cache = false;
  246. config.schema_mode = SchemaMode::Additive;
  247. config.schema_version = 0;
  248. auto realm = Realm::get_shared_realm(config);
  249. REQUIRE(realm->schema().size() == 1);
  250. auto& shared_group = TestHelper::get_shared_group(realm);
  251. shared_group.begin_read();
  252. shared_group.pin_version();
  253. VersionID old_version = shared_group.get_version_of_current_transaction();
  254. realm->close();
  255. config.schema = Schema{
  256. {"object", {
  257. {"value", PropertyType::Int}
  258. }},
  259. {"object1", {
  260. {"value", PropertyType::Int}
  261. }},
  262. };
  263. config.schema_version = 1;
  264. realm = Realm::get_shared_realm(config);
  265. REQUIRE(realm->schema().size() == 2);
  266. config.schema = util::none;
  267. auto old_realm = Realm::get_shared_realm(config);
  268. TestHelper::begin_read(old_realm, old_version);
  269. REQUIRE(old_realm->schema().size() == 1);
  270. }
  271. SECTION("should sensibly handle opening an uninitialized file without a schema specified") {
  272. SECTION("cached") { }
  273. SECTION("uncached") { config.cache = false; }
  274. // create an empty file
  275. File(config.path, File::mode_Write);
  276. // open the empty file, but don't initialize the schema
  277. Realm::Config config_without_schema = config;
  278. config_without_schema.schema = util::none;
  279. config_without_schema.schema_version = ObjectStore::NotVersioned;
  280. auto realm = Realm::get_shared_realm(config_without_schema);
  281. REQUIRE(realm->schema().empty());
  282. REQUIRE(realm->schema_version() == ObjectStore::NotVersioned);
  283. // verify that we can get another Realm instance
  284. REQUIRE_NOTHROW(Realm::get_shared_realm(config_without_schema));
  285. // verify that we can also still open the file with a proper schema
  286. auto realm2 = Realm::get_shared_realm(config);
  287. REQUIRE_FALSE(realm2->schema().empty());
  288. REQUIRE(realm2->schema_version() == 1);
  289. }
  290. SECTION("should populate the table columns in the schema when opening as immutable") {
  291. Realm::get_shared_realm(config);
  292. config.schema_mode = SchemaMode::Immutable;
  293. auto realm = Realm::get_shared_realm(config);
  294. auto it = realm->schema().find("object");
  295. REQUIRE(it != realm->schema().end());
  296. REQUIRE(it->persisted_properties.size() == 1);
  297. REQUIRE(it->persisted_properties[0].name == "value");
  298. REQUIRE(it->persisted_properties[0].table_column == 0);
  299. }
  300. SECTION("should support using different table subsets on different threads") {
  301. config.cache = false;
  302. auto realm1 = Realm::get_shared_realm(config);
  303. config.schema = Schema{
  304. {"object 2", {
  305. {"value", PropertyType::Int}
  306. }},
  307. };
  308. auto realm2 = Realm::get_shared_realm(config);
  309. config.schema = util::none;
  310. auto realm3 = Realm::get_shared_realm(config);
  311. config.schema = Schema{
  312. {"object", {
  313. {"value", PropertyType::Int}
  314. }},
  315. };
  316. auto realm4 = Realm::get_shared_realm(config);
  317. realm1->refresh();
  318. realm2->refresh();
  319. REQUIRE(realm1->schema().size() == 1);
  320. REQUIRE(realm1->schema().find("object") != realm1->schema().end());
  321. REQUIRE(realm2->schema().size() == 1);
  322. REQUIRE(realm2->schema().find("object 2") != realm2->schema().end());
  323. REQUIRE(realm3->schema().size() == 2);
  324. REQUIRE(realm3->schema().find("object") != realm3->schema().end());
  325. REQUIRE(realm3->schema().find("object 2") != realm3->schema().end());
  326. REQUIRE(realm4->schema().size() == 1);
  327. REQUIRE(realm4->schema().find("object") != realm4->schema().end());
  328. }
  329. // The ExternalCommitHelper implementation on Windows doesn't rely on files
  330. #ifndef _WIN32
  331. SECTION("should throw when creating the notification pipe fails") {
  332. util::try_make_dir(config.path + ".note");
  333. auto sys_fallback_file = util::format("%1realm_%2.note", SharedGroupOptions::get_sys_tmp_dir(), std::hash<std::string>()(config.path)); // Mirror internal implementation
  334. util::try_make_dir(sys_fallback_file);
  335. REQUIRE_THROWS(Realm::get_shared_realm(config));
  336. util::remove_dir(config.path + ".note");
  337. util::remove_dir(sys_fallback_file);
  338. }
  339. #endif
  340. SECTION("should get different instances on different threads") {
  341. auto realm1 = Realm::get_shared_realm(config);
  342. std::thread([&]{
  343. auto realm2 = Realm::get_shared_realm(config);
  344. REQUIRE(realm1 != realm2);
  345. }).join();
  346. }
  347. SECTION("should detect use of Realm on incorrect thread") {
  348. auto realm = Realm::get_shared_realm(config);
  349. std::thread([&]{
  350. REQUIRE_THROWS_AS(realm->verify_thread(), IncorrectThreadException);
  351. }).join();
  352. }
  353. SECTION("should get different instances for different explicit execuction contexts") {
  354. config.execution_context = 0;
  355. auto realm1 = Realm::get_shared_realm(config);
  356. config.execution_context = 1;
  357. auto realm2 = Realm::get_shared_realm(config);
  358. REQUIRE(realm1 != realm2);
  359. config.execution_context = util::none;
  360. auto realm3 = Realm::get_shared_realm(config);
  361. REQUIRE(realm1 != realm3);
  362. REQUIRE(realm2 != realm3);
  363. }
  364. SECTION("can use Realm with explicit execution context on different thread") {
  365. config.execution_context = 1;
  366. auto realm = Realm::get_shared_realm(config);
  367. std::thread([&]{
  368. REQUIRE_NOTHROW(realm->verify_thread());
  369. }).join();
  370. }
  371. SECTION("should get same instance for same explicit execution context on different thread") {
  372. config.execution_context = 1;
  373. auto realm1 = Realm::get_shared_realm(config);
  374. std::thread([&]{
  375. auto realm2 = Realm::get_shared_realm(config);
  376. REQUIRE(realm1 == realm2);
  377. }).join();
  378. }
  379. SECTION("should not modify the schema when fetching from the cache") {
  380. auto realm = Realm::get_shared_realm(config);
  381. auto object_schema = &*realm->schema().find("object");
  382. Realm::get_shared_realm(config);
  383. REQUIRE(object_schema == &*realm->schema().find("object"));
  384. }
  385. }
  386. #if REALM_ENABLE_SYNC
  387. TEST_CASE("Get Realm using Async Open", "[asyncOpen]") {
  388. TestFile local_config;
  389. local_config.schema_version = 1;
  390. local_config.schema = Schema{
  391. {"object", {
  392. {"value", PropertyType::Int}
  393. }},
  394. };
  395. if (!util::EventLoop::has_implementation())
  396. return;
  397. auto cleanup = util::make_scope_exit([=]() noexcept { SyncManager::shared().reset_for_testing(); });
  398. SyncManager::shared().configure(tmp_dir(), SyncManager::MetadataMode::NoEncryption);
  399. SyncServer server;
  400. SyncTestFile config(server, "default");
  401. config.cache = false;
  402. config.schema = Schema{
  403. {"object", {
  404. {"value", PropertyType::Int},
  405. }},
  406. };
  407. SyncTestFile config2(server, "default");
  408. config2.cache = false;
  409. config2.schema = config.schema;
  410. std::mutex mutex;
  411. SECTION("can open synced Realms that don't already exist") {
  412. std::atomic<bool> called{false};
  413. auto task = Realm::get_synchronized_realm(config);
  414. task->start([&](auto ref, auto error) {
  415. std::lock_guard<std::mutex> lock(mutex);
  416. REQUIRE(!error);
  417. called = true;
  418. REQUIRE(Realm::get_shared_realm(std::move(ref))->read_group().get_table("class_object"));
  419. });
  420. util::EventLoop::main().run_until([&]{ return called.load(); });
  421. std::lock_guard<std::mutex> lock(mutex);
  422. REQUIRE(called);
  423. }
  424. SECTION("downloads Realms which exist on the server") {
  425. {
  426. auto realm = Realm::get_shared_realm(config2);
  427. realm->begin_transaction();
  428. sync::create_object(realm->read_group(), *realm->read_group().get_table("class_object"));
  429. realm->commit_transaction();
  430. wait_for_upload(*realm);
  431. }
  432. std::atomic<bool> called{false};
  433. auto task = Realm::get_synchronized_realm(config);
  434. task->start([&](auto ref, auto error) {
  435. std::lock_guard<std::mutex> lock(mutex);
  436. REQUIRE(!error);
  437. called = true;
  438. REQUIRE(Realm::get_shared_realm(std::move(ref))->read_group().get_table("class_object"));
  439. });
  440. util::EventLoop::main().run_until([&]{ return called.load(); });
  441. std::lock_guard<std::mutex> lock(mutex);
  442. REQUIRE(called);
  443. }
  444. SECTION("downloads latest state for Realms which already exist locally") {
  445. wait_for_upload(*Realm::get_shared_realm(config));
  446. {
  447. auto realm = Realm::get_shared_realm(config2);
  448. realm->begin_transaction();
  449. sync::create_object(realm->read_group(), *realm->read_group().get_table("class_object"));
  450. realm->commit_transaction();
  451. wait_for_upload(*realm);
  452. }
  453. std::atomic<bool> called{false};
  454. auto task = Realm::get_synchronized_realm(config);
  455. task->start([&](auto ref, auto error) {
  456. std::lock_guard<std::mutex> lock(mutex);
  457. REQUIRE(!error);
  458. called = true;
  459. REQUIRE(Realm::get_shared_realm(std::move(ref))->read_group().get_table("class_object")->size() == 1);
  460. });
  461. util::EventLoop::main().run_until([&]{ return called.load(); });
  462. std::lock_guard<std::mutex> lock(mutex);
  463. REQUIRE(called);
  464. }
  465. SECTION("can download partial Realms") {
  466. config.sync_config->is_partial = true;
  467. config2.sync_config->is_partial = true;
  468. {
  469. auto realm = Realm::get_shared_realm(config2);
  470. realm->begin_transaction();
  471. sync::create_object(realm->read_group(), *realm->read_group().get_table("class_object"));
  472. realm->commit_transaction();
  473. wait_for_upload(*realm);
  474. }
  475. std::atomic<bool> called{false};
  476. auto task = Realm::get_synchronized_realm(config);
  477. task->start([&](auto, auto error) {
  478. std::lock_guard<std::mutex> lock(mutex);
  479. REQUIRE(!error);
  480. called = true;
  481. });
  482. util::EventLoop::main().run_until([&]{ return called.load(); });
  483. std::lock_guard<std::mutex> lock(mutex);
  484. REQUIRE(called);
  485. // No subscriptions, so no objects
  486. REQUIRE(Realm::get_shared_realm(config)->read_group().get_table("class_object")->size() == 0);
  487. }
  488. SECTION("can download multiple Realms at a time") {
  489. SyncTestFile config1(server, "realm1");
  490. SyncTestFile config2(server, "realm2");
  491. SyncTestFile config3(server, "realm3");
  492. SyncTestFile config4(server, "realm4");
  493. std::vector<std::shared_ptr<AsyncOpenTask>> tasks = {
  494. Realm::get_synchronized_realm(config1),
  495. Realm::get_synchronized_realm(config2),
  496. Realm::get_synchronized_realm(config3),
  497. Realm::get_synchronized_realm(config4),
  498. };
  499. std::atomic<int> completed{0};
  500. for (auto& task : tasks) {
  501. task->start([&](auto, auto) {
  502. ++completed;
  503. });
  504. }
  505. util::EventLoop::main().run_until([&]{ return completed == 4; });
  506. }
  507. }
  508. #endif
  509. TEST_CASE("SharedRealm: notifications") {
  510. if (!util::EventLoop::has_implementation())
  511. return;
  512. TestFile config;
  513. config.cache = false;
  514. config.schema_version = 0;
  515. config.schema = Schema{
  516. {"object", {
  517. {"value", PropertyType::Int}
  518. }},
  519. };
  520. struct Context : BindingContext {
  521. size_t* change_count;
  522. Context(size_t* out) : change_count(out) { }
  523. void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
  524. {
  525. ++*change_count;
  526. }
  527. };
  528. size_t change_count = 0;
  529. auto realm = Realm::get_shared_realm(config);
  530. realm->m_binding_context.reset(new Context{&change_count});
  531. realm->m_binding_context->realm = realm;
  532. SECTION("local notifications are sent synchronously") {
  533. realm->begin_transaction();
  534. REQUIRE(change_count == 0);
  535. realm->commit_transaction();
  536. REQUIRE(change_count == 1);
  537. }
  538. SECTION("remote notifications are sent asynchronously") {
  539. auto r2 = Realm::get_shared_realm(config);
  540. r2->begin_transaction();
  541. r2->commit_transaction();
  542. REQUIRE(change_count == 0);
  543. util::EventLoop::main().run_until([&]{ return change_count > 0; });
  544. REQUIRE(change_count == 1);
  545. }
  546. SECTION("refresh() from within changes_available() refreshes") {
  547. struct Context : BindingContext {
  548. Realm& realm;
  549. Context(Realm& realm) : realm(realm) { }
  550. void changes_available() override
  551. {
  552. REQUIRE(realm.refresh());
  553. }
  554. };
  555. realm->m_binding_context.reset(new Context{*realm});
  556. realm->set_auto_refresh(false);
  557. auto r2 = Realm::get_shared_realm(config);
  558. r2->begin_transaction();
  559. r2->commit_transaction();
  560. realm->notify();
  561. // Should return false as the realm was already advanced
  562. REQUIRE_FALSE(realm->refresh());
  563. }
  564. SECTION("refresh() from within did_change() is a no-op") {
  565. struct Context : BindingContext {
  566. Realm& realm;
  567. Context(Realm& realm) : realm(realm) { }
  568. void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
  569. {
  570. // Create another version so that refresh() could do something
  571. auto r2 = Realm::get_shared_realm(realm.config());
  572. r2->begin_transaction();
  573. r2->commit_transaction();
  574. // Should be a no-op
  575. REQUIRE_FALSE(realm.refresh());
  576. }
  577. };
  578. realm->m_binding_context.reset(new Context{*realm});
  579. auto r2 = Realm::get_shared_realm(config);
  580. r2->begin_transaction();
  581. r2->commit_transaction();
  582. REQUIRE(realm->refresh());
  583. realm->m_binding_context.reset();
  584. // Should advance to the version created in the previous did_change()
  585. REQUIRE(realm->refresh());
  586. // No more versions, so returns false
  587. REQUIRE_FALSE(realm->refresh());
  588. }
  589. SECTION("begin_write() from within did_change() produces recursive notifications") {
  590. struct Context : BindingContext {
  591. Realm& realm;
  592. size_t calls = 0;
  593. Context(Realm& realm) : realm(realm) { }
  594. void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
  595. {
  596. ++calls;
  597. if (realm.is_in_transaction())
  598. return;
  599. // Create another version so that begin_write() advances the version
  600. auto r2 = Realm::get_shared_realm(realm.config());
  601. r2->begin_transaction();
  602. r2->commit_transaction();
  603. realm.begin_transaction();
  604. realm.cancel_transaction();
  605. }
  606. };
  607. auto context = new Context{*realm};
  608. realm->m_binding_context.reset(context);
  609. auto r2 = Realm::get_shared_realm(config);
  610. r2->begin_transaction();
  611. r2->commit_transaction();
  612. REQUIRE(realm->refresh());
  613. REQUIRE(context->calls == 2);
  614. // Despite not sending a new notification we did advance the version, so
  615. // no more versions to refresh to
  616. REQUIRE_FALSE(realm->refresh());
  617. }
  618. }
  619. TEST_CASE("SharedRealm: schema updating from external changes") {
  620. TestFile config;
  621. config.cache = false;
  622. config.schema_version = 0;
  623. config.schema_mode = SchemaMode::Additive;
  624. config.schema = Schema{
  625. {"object", {
  626. {"value", PropertyType::Int, Property::IsPrimary{true}},
  627. {"value 2", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  628. }},
  629. };
  630. SECTION("newly added columns update table columns but are not added to properties") {
  631. auto r1 = Realm::get_shared_realm(config);
  632. auto r2 = Realm::get_shared_realm(config);
  633. auto test = [&] {
  634. r2->begin_transaction();
  635. r2->read_group().get_table("class_object")->insert_column(0, type_String, "new col");
  636. r2->commit_transaction();
  637. auto& object_schema = *r1->schema().find("object");
  638. REQUIRE(object_schema.persisted_properties.size() == 2);
  639. REQUIRE(object_schema.persisted_properties[0].table_column == 0);
  640. r1->refresh();
  641. REQUIRE(object_schema.persisted_properties[0].table_column == 1);
  642. };
  643. SECTION("with an active read transaction") {
  644. r1->read_group();
  645. test();
  646. }
  647. SECTION("without an active read transaction") {
  648. r1->invalidate();
  649. test();
  650. }
  651. }
  652. SECTION("beginning a read transaction checks for incompatible changes") {
  653. auto r = Realm::get_shared_realm(config);
  654. r->invalidate();
  655. auto& sg = TestHelper::get_shared_group(r);
  656. WriteTransaction wt(sg);
  657. auto& table = *wt.get_table("class_object");
  658. SECTION("removing a property") {
  659. table.remove_column(0);
  660. wt.commit();
  661. REQUIRE_THROWS_WITH(r->refresh(),
  662. Catch::Matchers::Contains("Property 'object.value' has been removed."));
  663. }
  664. SECTION("change property type") {
  665. table.remove_column(1);
  666. table.add_column(type_Float, "value 2");
  667. wt.commit();
  668. REQUIRE_THROWS_WITH(r->refresh(),
  669. Catch::Matchers::Contains("Property 'object.value 2' has been changed from 'int' to 'float'"));
  670. }
  671. SECTION("make property optional") {
  672. table.remove_column(1);
  673. table.add_column(type_Int, "value 2", true);
  674. wt.commit();
  675. REQUIRE_THROWS_WITH(r->refresh(),
  676. Catch::Matchers::Contains("Property 'object.value 2' has been made optional"));
  677. }
  678. SECTION("recreate column with no changes") {
  679. table.remove_column(1);
  680. table.add_column(type_Int, "value 2");
  681. wt.commit();
  682. REQUIRE_NOTHROW(r->refresh());
  683. }
  684. SECTION("remove index from non-PK") {
  685. table.remove_search_index(1);
  686. wt.commit();
  687. REQUIRE_NOTHROW(r->refresh());
  688. }
  689. }
  690. }
  691. TEST_CASE("SharedRealm: closed realm") {
  692. TestFile config;
  693. config.schema_version = 1;
  694. config.schema = Schema{
  695. {"object", {
  696. {"value", PropertyType::Int}
  697. }},
  698. };
  699. auto realm = Realm::get_shared_realm(config);
  700. realm->close();
  701. REQUIRE(realm->is_closed());
  702. REQUIRE_THROWS_AS(realm->read_group(), ClosedRealmException);
  703. REQUIRE_THROWS_AS(realm->begin_transaction(), ClosedRealmException);
  704. REQUIRE(!realm->is_in_transaction());
  705. REQUIRE_THROWS_AS(realm->commit_transaction(), InvalidTransactionException);
  706. REQUIRE_THROWS_AS(realm->cancel_transaction(), InvalidTransactionException);
  707. REQUIRE_THROWS_AS(realm->refresh(), ClosedRealmException);
  708. REQUIRE_THROWS_AS(realm->invalidate(), ClosedRealmException);
  709. REQUIRE_THROWS_AS(realm->compact(), ClosedRealmException);
  710. }
  711. TEST_CASE("ShareRealm: in-memory mode from buffer") {
  712. TestFile config;
  713. config.schema_version = 1;
  714. config.schema = Schema{
  715. {"object", {
  716. {"value", PropertyType::Int}
  717. }},
  718. };
  719. SECTION("Save and open Realm from in-memory buffer") {
  720. // Write in-memory copy of Realm to a buffer
  721. auto realm = Realm::get_shared_realm(config);
  722. OwnedBinaryData realm_buffer = realm->write_copy();
  723. // Open the buffer as a new (immutable in-memory) Realm
  724. realm::Realm::Config config2;
  725. config2.in_memory = true;
  726. config2.schema_mode = SchemaMode::Immutable;
  727. config2.realm_data = realm_buffer.get();
  728. auto realm2 = Realm::get_shared_realm(config2);
  729. // Verify that it can read the schema and that it is the same
  730. REQUIRE(realm->schema().size() == 1);
  731. auto it = realm->schema().find("object");
  732. REQUIRE(it != realm->schema().end());
  733. REQUIRE(it->persisted_properties.size() == 1);
  734. REQUIRE(it->persisted_properties[0].name == "value");
  735. REQUIRE(it->persisted_properties[0].table_column == 0);
  736. // Test invalid configs
  737. realm::Realm::Config config3;
  738. config3.realm_data = realm_buffer.get();
  739. REQUIRE_THROWS(Realm::get_shared_realm(config3)); // missing in_memory and immutable
  740. config3.in_memory = true;
  741. config3.schema_mode = SchemaMode::Immutable;
  742. config3.path = "path";
  743. REQUIRE_THROWS(Realm::get_shared_realm(config3)); // both buffer and path
  744. config3.path = "";
  745. config3.encryption_key = {'a'};
  746. REQUIRE_THROWS(Realm::get_shared_realm(config3)); // both buffer and encryption
  747. }
  748. }
  749. TEST_CASE("ShareRealm: realm closed in did_change callback") {
  750. TestFile config;
  751. config.schema_version = 1;
  752. config.schema = Schema{
  753. {"object", {
  754. {"value", PropertyType::Int}
  755. }},
  756. };
  757. config.cache = false;
  758. config.automatic_change_notifications = false;
  759. auto r1 = Realm::get_shared_realm(config);
  760. r1->begin_transaction();
  761. auto table = r1->read_group().get_table("class_object");
  762. table->add_empty_row();
  763. r1->commit_transaction();
  764. // Cannot be a member var of Context since Realm.close will free the context.
  765. static SharedRealm* shared_realm;
  766. shared_realm = &r1;
  767. struct Context : public BindingContext {
  768. void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
  769. {
  770. (*shared_realm)->close();
  771. (*shared_realm).reset();
  772. }
  773. };
  774. SECTION("did_change") {
  775. r1->m_binding_context.reset(new Context());
  776. r1->invalidate();
  777. auto r2 = Realm::get_shared_realm(config);
  778. r2->begin_transaction();
  779. r2->read_group().get_table("class_object")->add_empty_row(1);
  780. r2->commit_transaction();
  781. r2.reset();
  782. r1->notify();
  783. }
  784. SECTION("did_change with async results") {
  785. r1->m_binding_context.reset(new Context());
  786. Results results(r1, table->where());
  787. auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
  788. // Should not be called.
  789. REQUIRE(false);
  790. });
  791. auto r2 = Realm::get_shared_realm(config);
  792. r2->begin_transaction();
  793. r2->read_group().get_table("class_object")->add_empty_row(1);
  794. r2->commit_transaction();
  795. r2.reset();
  796. auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
  797. coordinator->on_change();
  798. r1->notify();
  799. }
  800. SECTION("refresh") {
  801. r1->m_binding_context.reset(new Context());
  802. auto r2 = Realm::get_shared_realm(config);
  803. r2->begin_transaction();
  804. r2->read_group().get_table("class_object")->add_empty_row(1);
  805. r2->commit_transaction();
  806. r2.reset();
  807. REQUIRE_FALSE(r1->refresh());
  808. }
  809. shared_realm = nullptr;
  810. }
  811. TEST_CASE("RealmCoordinator: schema cache") {
  812. TestFile config;
  813. auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path);
  814. Schema cache_schema;
  815. uint64_t cache_sv = -1, cache_tv = -1;
  816. Schema schema{
  817. {"object", {
  818. {"value", PropertyType::Int}
  819. }},
  820. };
  821. Schema schema2{
  822. {"object", {
  823. {"value", PropertyType::Int},
  824. }},
  825. {"object 2", {
  826. {"value", PropertyType::Int},
  827. }},
  828. };
  829. SECTION("valid initial schema sets cache") {
  830. coordinator->cache_schema(schema, 5, 10);
  831. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  832. REQUIRE(cache_schema == schema);
  833. REQUIRE(cache_sv == 5);
  834. REQUIRE(cache_tv == 10);
  835. }
  836. SECTION("cache can be updated with newer schema") {
  837. coordinator->cache_schema(schema, 5, 10);
  838. coordinator->cache_schema(schema2, 6, 11);
  839. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  840. REQUIRE(cache_schema == schema2);
  841. REQUIRE(cache_sv == 6);
  842. REQUIRE(cache_tv == 11);
  843. }
  844. SECTION("empty schema is ignored") {
  845. coordinator->cache_schema(Schema{}, 5, 10);
  846. REQUIRE_FALSE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  847. coordinator->cache_schema(schema, 5, 10);
  848. coordinator->cache_schema(Schema{}, 5, 10);
  849. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  850. REQUIRE(cache_schema == schema);
  851. REQUIRE(cache_sv == 5);
  852. REQUIRE(cache_tv == 10);
  853. }
  854. SECTION("schema for older transaction is ignored") {
  855. coordinator->cache_schema(schema, 5, 10);
  856. coordinator->cache_schema(schema2, 4, 8);
  857. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  858. REQUIRE(cache_schema == schema);
  859. REQUIRE(cache_sv == 5);
  860. REQUIRE(cache_tv == 10);
  861. coordinator->advance_schema_cache(10, 20);
  862. coordinator->cache_schema(schema, 6, 15);
  863. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  864. REQUIRE(cache_tv == 20); // should not have dropped to 15
  865. }
  866. SECTION("advance_schema() from transaction version bumps transaction version") {
  867. coordinator->cache_schema(schema, 5, 10);
  868. coordinator->advance_schema_cache(10, 12);
  869. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  870. REQUIRE(cache_schema == schema);
  871. REQUIRE(cache_sv == 5);
  872. REQUIRE(cache_tv == 12);
  873. }
  874. SECTION("advance_schema() ending before transaction version does nothing") {
  875. coordinator->cache_schema(schema, 5, 10);
  876. coordinator->advance_schema_cache(8, 9);
  877. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  878. REQUIRE(cache_schema == schema);
  879. REQUIRE(cache_sv == 5);
  880. REQUIRE(cache_tv == 10);
  881. }
  882. SECTION("advance_schema() extending over transaction version bumps version") {
  883. coordinator->cache_schema(schema, 5, 10);
  884. coordinator->advance_schema_cache(3, 15);
  885. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  886. REQUIRE(cache_schema == schema);
  887. REQUIRE(cache_sv == 5);
  888. REQUIRE(cache_tv == 15);
  889. }
  890. SECTION("advance_schema() with no cahced schema does nothing") {
  891. coordinator->advance_schema_cache(3, 15);
  892. REQUIRE_FALSE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  893. }
  894. }
  895. TEST_CASE("SharedRealm: coordinator schema cache") {
  896. TestFile config;
  897. config.cache = false;
  898. auto r = Realm::get_shared_realm(config);
  899. auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
  900. Schema cache_schema;
  901. uint64_t cache_sv = -1, cache_tv = -1;
  902. Schema schema{
  903. {"object", {
  904. {"value", PropertyType::Int}
  905. }},
  906. };
  907. Schema schema2{
  908. {"object", {
  909. {"value", PropertyType::Int},
  910. }},
  911. {"object 2", {
  912. {"value", PropertyType::Int},
  913. }},
  914. };
  915. class ExternalWriter {
  916. private:
  917. std::unique_ptr<Replication> history;
  918. std::unique_ptr<SharedGroup> shared_group;
  919. std::unique_ptr<Group> read_only_group;
  920. public:
  921. WriteTransaction wt;
  922. ExternalWriter(Realm::Config const& config)
  923. : wt([&]() -> SharedGroup& {
  924. Realm::open_with_config(config, history, shared_group, read_only_group, nullptr);
  925. return *shared_group;
  926. }())
  927. {
  928. }
  929. };
  930. auto external_write = [&](Realm::Config const& config, auto&& fn) {
  931. ExternalWriter wt(config);
  932. fn(wt.wt);
  933. wt.wt.commit();
  934. };
  935. SECTION("is initially empty for uninitialized file") {
  936. REQUIRE_FALSE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  937. }
  938. r->update_schema(schema);
  939. SECTION("is empty after calling update_schema()") {
  940. REQUIRE_FALSE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  941. }
  942. Realm::get_shared_realm(config);
  943. SECTION("is populated after getting another Realm without a schema specified") {
  944. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  945. REQUIRE(cache_sv == 0);
  946. REQUIRE(cache_schema == schema);
  947. REQUIRE(cache_schema.begin()->persisted_properties[0].table_column == 0);
  948. }
  949. coordinator = nullptr;
  950. r = nullptr;
  951. r = Realm::get_shared_realm(config);
  952. coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
  953. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  954. SECTION("is populated after opening an initialized file") {
  955. REQUIRE(cache_sv == 0);
  956. REQUIRE(cache_tv == 2); // with in-realm history the version doesn't reset
  957. REQUIRE(cache_schema == schema);
  958. REQUIRE(cache_schema.begin()->persisted_properties[0].table_column == 0);
  959. }
  960. SECTION("transaction version is bumped after a local write") {
  961. auto tv = cache_tv;
  962. r->begin_transaction();
  963. r->commit_transaction();
  964. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  965. REQUIRE(cache_tv == tv + 1);
  966. }
  967. SECTION("notify() without a read transaction does not bump transaction version") {
  968. auto tv = cache_tv;
  969. SECTION("non-schema change") {
  970. external_write(config, [](auto& wt) {
  971. wt.get_table("class_object")->add_empty_row();
  972. });
  973. }
  974. SECTION("schema change") {
  975. external_write(config, [](auto& wt) {
  976. wt.add_table("class_object 2");
  977. });
  978. }
  979. r->notify();
  980. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  981. REQUIRE(cache_tv == tv);
  982. REQUIRE(cache_schema == schema);
  983. }
  984. SECTION("notify() with a read transaction bumps transaction version") {
  985. r->read_group();
  986. external_write(config, [](auto& wt) {
  987. wt.get_table("class_object")->add_empty_row();
  988. });
  989. r->notify();
  990. auto tv = cache_tv;
  991. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  992. REQUIRE(cache_tv == tv + 1);
  993. }
  994. SECTION("notify() with a read transaction updates schema folloing external schema change") {
  995. r->read_group();
  996. external_write(config, [](auto& wt) {
  997. wt.add_table("class_object 2");
  998. });
  999. r->notify();
  1000. auto tv = cache_tv;
  1001. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  1002. REQUIRE(cache_tv == tv + 1);
  1003. REQUIRE(cache_schema.size() == 2);
  1004. REQUIRE(cache_schema.find("object 2") != cache_schema.end());
  1005. }
  1006. SECTION("transaction version is bumped after refresh() following external non-schema write") {
  1007. external_write(config, [](auto& wt) {
  1008. wt.get_table("class_object")->add_empty_row();
  1009. });
  1010. r->refresh();
  1011. auto tv = cache_tv;
  1012. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  1013. REQUIRE(cache_tv == tv + 1);
  1014. }
  1015. SECTION("schema is reread following refresh() over external schema change") {
  1016. external_write(config, [](auto& wt) {
  1017. wt.add_table("class_object 2");
  1018. });
  1019. r->refresh();
  1020. auto tv = cache_tv;
  1021. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  1022. REQUIRE(cache_tv == tv + 1);
  1023. REQUIRE(cache_schema.size() == 2);
  1024. REQUIRE(cache_schema.find("object 2") != cache_schema.end());
  1025. }
  1026. SECTION("update_schema() to version already on disk updates cache") {
  1027. r->read_group();
  1028. external_write(config, [](auto& wt) {
  1029. auto table = wt.add_table("class_object 2");
  1030. table->add_column(type_Int, "value");
  1031. });
  1032. auto tv = cache_tv;
  1033. r->update_schema(schema2);
  1034. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  1035. REQUIRE(cache_tv == tv + 1); // only +1 because update_schema() did not perform a write
  1036. REQUIRE(cache_schema.size() == 2);
  1037. REQUIRE(cache_schema.find("object 2") != cache_schema.end());
  1038. }
  1039. SECTION("update_schema() to version already on disk updates cache") {
  1040. r->read_group();
  1041. external_write(config, [](auto& wt) {
  1042. auto table = wt.add_table("class_object 2");
  1043. table->add_column(type_Int, "value");
  1044. });
  1045. auto tv = cache_tv;
  1046. r->update_schema(schema2);
  1047. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  1048. REQUIRE(cache_tv == tv + 1); // only +1 because update_schema() did not perform a write
  1049. REQUIRE(cache_schema.size() == 2);
  1050. REQUIRE(cache_schema.find("object 2") != cache_schema.end());
  1051. }
  1052. SECTION("update_schema() to version populated on disk while waiting for the write lock updates cache") {
  1053. r->read_group();
  1054. // We want to commit the write while we're waiting on the write lock on
  1055. // this thread, which can't really be done in a properly synchronized manner
  1056. std::chrono::microseconds wait_time{5000};
  1057. #if REALM_ANDROID
  1058. // When running on device or in an emulator we need to wait longer due
  1059. // to them being slow
  1060. wait_time *= 10;
  1061. #endif
  1062. bool did_run = false;
  1063. JoiningThread thread([&] {
  1064. ExternalWriter writer(config);
  1065. if (writer.wt.get_table("class_object 2"))
  1066. return;
  1067. did_run = true;
  1068. auto table = writer.wt.add_table("class_object 2");
  1069. table->add_column(type_Int, "value");
  1070. std::this_thread::sleep_for(wait_time * 2);
  1071. writer.wt.commit();
  1072. });
  1073. std::this_thread::sleep_for(wait_time);
  1074. auto tv = cache_tv;
  1075. r->update_schema(Schema{
  1076. {"object", {{"value", PropertyType::Int}}},
  1077. {"object 2", {{"value", PropertyType::Int}}},
  1078. });
  1079. // just skip the test if the timing was wrong to avoid spurious failures
  1080. if (!did_run)
  1081. return;
  1082. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  1083. REQUIRE(cache_tv == tv + 1); // only +1 because update_schema()'s write was rolled back
  1084. REQUIRE(cache_schema.size() == 2);
  1085. REQUIRE(cache_schema.find("object 2") != cache_schema.end());
  1086. }
  1087. }
  1088. TEST_CASE("SharedRealm: dynamic schema mode doesn't invalidate object schema pointers when schema hasn't changed") {
  1089. TestFile config;
  1090. config.cache = false;
  1091. // Prepopulate the Realm with the schema.
  1092. Realm::Config config_with_schema = config;
  1093. config_with_schema.schema_version = 1;
  1094. config_with_schema.schema_mode = SchemaMode::Automatic;
  1095. config_with_schema.schema = Schema{
  1096. {"object", {
  1097. {"value", PropertyType::Int, Property::IsPrimary{true}},
  1098. {"value 2", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  1099. }}
  1100. };
  1101. auto r1 = Realm::get_shared_realm(config_with_schema);
  1102. // Retrieve the object schema in dynamic mode.
  1103. auto r2 = Realm::get_shared_realm(config);
  1104. auto* object_schema = &*r2->schema().find("object");
  1105. // Perform an empty write to create a new version, resulting in the other Realm needing to re-read the schema.
  1106. r1->begin_transaction();
  1107. r1->commit_transaction();
  1108. // Advance to the latest version, and verify the object schema is at the same location in memory.
  1109. r2->read_group();
  1110. REQUIRE(object_schema == &*r2->schema().find("object"));
  1111. }
  1112. TEST_CASE("SharedRealm: SchemaChangedFunction") {
  1113. struct Context : BindingContext {
  1114. size_t* change_count;
  1115. Schema* schema;
  1116. Context(size_t* count_out, Schema* schema_out) : change_count(count_out), schema(schema_out) { }
  1117. void schema_did_change(Schema const& changed_schema) override
  1118. {
  1119. ++*change_count;
  1120. *schema = changed_schema;
  1121. }
  1122. };
  1123. size_t schema_changed_called = 0;
  1124. Schema changed_fixed_schema;
  1125. TestFile config;
  1126. config.cache = false;
  1127. auto dynamic_config = config;
  1128. config.schema = Schema{
  1129. {"object1", {
  1130. {"value", PropertyType::Int},
  1131. }},
  1132. {"object2", {
  1133. {"value", PropertyType::Int},
  1134. }}
  1135. };
  1136. config.schema_version = 1;
  1137. auto r1 = Realm::get_shared_realm(config);
  1138. r1->m_binding_context.reset(new Context(&schema_changed_called, &changed_fixed_schema));
  1139. SECTION("Fixed schema") {
  1140. SECTION("update_schema") {
  1141. auto new_schema = Schema{
  1142. {"object3", {
  1143. {"value", PropertyType::Int},
  1144. }}
  1145. };
  1146. r1->update_schema(new_schema, 2);
  1147. REQUIRE(schema_changed_called == 1);
  1148. REQUIRE(changed_fixed_schema.find("object3")->property_for_name("value")->table_column == 0);
  1149. }
  1150. SECTION("Open a new Realm instance with same config won't trigger") {
  1151. auto r2 = Realm::get_shared_realm(config);
  1152. REQUIRE(schema_changed_called == 0);
  1153. }
  1154. SECTION("Non schema related transaction doesn't trigger") {
  1155. auto r2 = Realm::get_shared_realm(config);
  1156. r2->begin_transaction();
  1157. r2->commit_transaction();
  1158. r1->refresh();
  1159. REQUIRE(schema_changed_called == 0);
  1160. }
  1161. SECTION("Schema is changed by another Realm") {
  1162. auto r2 = Realm::get_shared_realm(config);
  1163. r2->begin_transaction();
  1164. r2->read_group().get_table("class_object1")->insert_column(0, type_String, "new col");
  1165. r2->commit_transaction();
  1166. r1->refresh();
  1167. REQUIRE(schema_changed_called == 1);
  1168. REQUIRE(changed_fixed_schema.find("object1")->property_for_name("value")->table_column == 1);
  1169. }
  1170. // This is not a valid use case. m_schema won't be refreshed.
  1171. SECTION("Schema is changed by this Realm won't trigger") {
  1172. r1->begin_transaction();
  1173. r1->read_group().get_table("class_object1")->insert_column(0, type_String, "new col");
  1174. r1->commit_transaction();
  1175. REQUIRE(schema_changed_called == 0);
  1176. }
  1177. }
  1178. SECTION("Dynamic schema") {
  1179. size_t dynamic_schema_changed_called = 0;
  1180. Schema changed_dynamic_schema;
  1181. auto r2 = Realm::get_shared_realm(dynamic_config);
  1182. r2->m_binding_context.reset(new Context(&dynamic_schema_changed_called, &changed_dynamic_schema));
  1183. SECTION("set_schema_subset") {
  1184. auto new_schema = Schema{
  1185. {"object1", {
  1186. {"value", PropertyType::Int},
  1187. }}
  1188. };
  1189. r2->set_schema_subset(new_schema);
  1190. REQUIRE(schema_changed_called == 0);
  1191. REQUIRE(dynamic_schema_changed_called == 1);
  1192. REQUIRE(changed_dynamic_schema.find("object1")->property_for_name("value")->table_column == 0);
  1193. }
  1194. SECTION("Non schema related transaction will alway trigger in dynamic mode") {
  1195. auto r1 = Realm::get_shared_realm(config);
  1196. // An empty transaction will trigger the schema changes always in dynamic mode.
  1197. r1->begin_transaction();
  1198. r1->commit_transaction();
  1199. r2->refresh();
  1200. REQUIRE(dynamic_schema_changed_called == 1);
  1201. REQUIRE(changed_dynamic_schema.find("object1")->property_for_name("value")->table_column == 0);
  1202. }
  1203. SECTION("Schema is changed by another Realm") {
  1204. r1->begin_transaction();
  1205. r1->read_group().get_table("class_object1")->insert_column(0, type_String, "new col");
  1206. r1->commit_transaction();
  1207. r2->refresh();
  1208. REQUIRE(dynamic_schema_changed_called == 1);
  1209. REQUIRE(changed_dynamic_schema.find("object1")->property_for_name("value")->table_column == 1);
  1210. }
  1211. }
  1212. }
  1213. #ifndef _WIN32
  1214. TEST_CASE("SharedRealm: compact on launch") {
  1215. // Make compactable Realm
  1216. TestFile config;
  1217. config.cache = false;
  1218. config.automatic_change_notifications = false;
  1219. int num_opens = 0;
  1220. config.should_compact_on_launch_function = [&](size_t total_bytes, size_t used_bytes) {
  1221. REQUIRE(total_bytes > used_bytes);
  1222. num_opens++;
  1223. return num_opens != 2;
  1224. };
  1225. config.schema = Schema{
  1226. {"object", {
  1227. {"value", PropertyType::String}
  1228. }},
  1229. };
  1230. REQUIRE(num_opens == 0);
  1231. auto r = Realm::get_shared_realm(config);
  1232. REQUIRE(num_opens == 1);
  1233. r->begin_transaction();
  1234. auto table = r->read_group().get_table("class_object");
  1235. size_t count = 1000;
  1236. table->add_empty_row(count);
  1237. for (size_t i = 0; i < count; ++i)
  1238. table->set_string(0, i, util::format("Foo_%1", i % 10).c_str());
  1239. r->commit_transaction();
  1240. REQUIRE(table->size() == count);
  1241. r->close();
  1242. SECTION("compact reduces the file size") {
  1243. // Confirm expected sizes before and after opening the Realm
  1244. size_t size_before = size_t(File(config.path).get_size());
  1245. r = Realm::get_shared_realm(config);
  1246. REQUIRE(num_opens == 2);
  1247. r->close();
  1248. REQUIRE(size_t(File(config.path).get_size()) == size_before); // File size after returning false
  1249. r = Realm::get_shared_realm(config);
  1250. REQUIRE(num_opens == 3);
  1251. REQUIRE(size_t(File(config.path).get_size()) < size_before); // File size after returning true
  1252. // Validate that the file still contains what it should
  1253. REQUIRE(r->read_group().get_table("class_object")->size() == count);
  1254. // Registering for a collection notification shouldn't crash when compact on launch is used.
  1255. Results results(r, *r->read_group().get_table("class_object"));
  1256. results.async([](std::exception_ptr) { });
  1257. r->close();
  1258. }
  1259. SECTION("compact function does not get invoked if realm is open on another thread") {
  1260. r = Realm::get_shared_realm(config);
  1261. REQUIRE(num_opens == 2);
  1262. std::thread([&]{
  1263. auto r2 = Realm::get_shared_realm(config);
  1264. REQUIRE(num_opens == 2);
  1265. }).join();
  1266. r->close();
  1267. std::thread([&]{
  1268. auto r3 = Realm::get_shared_realm(config);
  1269. REQUIRE(num_opens == 3);
  1270. }).join();
  1271. }
  1272. }
  1273. #endif
  1274. struct ModeAutomatic {
  1275. static SchemaMode mode() { return SchemaMode::Automatic; }
  1276. static bool should_call_init_on_version_bump() { return false; }
  1277. };
  1278. struct ModeAdditive {
  1279. static SchemaMode mode() { return SchemaMode::Additive; }
  1280. static bool should_call_init_on_version_bump() { return false; }
  1281. };
  1282. struct ModeManual {
  1283. static SchemaMode mode() { return SchemaMode::Manual; }
  1284. static bool should_call_init_on_version_bump() { return false; }
  1285. };
  1286. struct ModeResetFile {
  1287. static SchemaMode mode() { return SchemaMode::ResetFile; }
  1288. static bool should_call_init_on_version_bump() { return true; }
  1289. };
  1290. TEMPLATE_TEST_CASE("SharedRealm: update_schema with initialization_function", "[init][update_schema]",
  1291. ModeAutomatic, ModeAdditive, ModeManual, ModeResetFile) {
  1292. TestFile config;
  1293. config.schema_mode = TestType::mode();
  1294. bool initialization_function_called = false;
  1295. uint64_t schema_version_in_callback = -1;
  1296. Schema schema_in_callback;
  1297. auto initialization_function = [&initialization_function_called, &schema_version_in_callback,
  1298. &schema_in_callback](auto shared_realm) {
  1299. REQUIRE(shared_realm->is_in_transaction());
  1300. initialization_function_called = true;
  1301. schema_version_in_callback = shared_realm->schema_version();
  1302. schema_in_callback = shared_realm->schema();
  1303. };
  1304. Schema schema{
  1305. {"object", {
  1306. {"value", PropertyType::String}
  1307. }},
  1308. };
  1309. SECTION("call initialization function directly by update_schema") {
  1310. // Open in dynamic mode with no schema specified
  1311. auto realm = Realm::get_shared_realm(config);
  1312. REQUIRE_FALSE(initialization_function_called);
  1313. realm->update_schema(schema, 0, nullptr, initialization_function);
  1314. REQUIRE(initialization_function_called);
  1315. REQUIRE(schema_version_in_callback == 0);
  1316. REQUIRE(schema_in_callback.compare(schema).size() == 0);
  1317. }
  1318. config.schema_version = 0;
  1319. config.schema = schema;
  1320. SECTION("initialization function should be called for unversioned realm") {
  1321. config.initialization_function = initialization_function;
  1322. Realm::get_shared_realm(config);
  1323. REQUIRE(initialization_function_called);
  1324. REQUIRE(schema_version_in_callback == 0);
  1325. REQUIRE(schema_in_callback.compare(schema).size() == 0);
  1326. }
  1327. SECTION("initialization function for versioned realm") {
  1328. // Initialize v0
  1329. Realm::get_shared_realm(config);
  1330. config.schema_version = 1;
  1331. config.initialization_function = initialization_function;
  1332. Realm::get_shared_realm(config);
  1333. REQUIRE(initialization_function_called == TestType::should_call_init_on_version_bump());
  1334. if (TestType::should_call_init_on_version_bump()) {
  1335. REQUIRE(schema_version_in_callback == 1);
  1336. REQUIRE(schema_in_callback.compare(schema).size() == 0);
  1337. }
  1338. }
  1339. }
  1340. TEST_CASE("BindingContext is notified about delivery of change notifications") {
  1341. _impl::RealmCoordinator::assert_no_open_realms();
  1342. InMemoryTestFile config;
  1343. config.cache = false;
  1344. config.automatic_change_notifications = false;
  1345. auto r = Realm::get_shared_realm(config);
  1346. r->update_schema({
  1347. {"object", {
  1348. {"value", PropertyType::Int}
  1349. }},
  1350. });
  1351. auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
  1352. auto table = r->read_group().get_table("class_object");
  1353. SECTION("BindingContext notified even if no callbacks are registered") {
  1354. static int binding_context_start_notify_calls = 0;
  1355. static int binding_context_end_notify_calls = 0;
  1356. struct Context : BindingContext {
  1357. void will_send_notifications() override
  1358. {
  1359. ++binding_context_start_notify_calls;
  1360. }
  1361. void did_send_notifications() override
  1362. {
  1363. ++binding_context_end_notify_calls;
  1364. }
  1365. };
  1366. r->m_binding_context.reset(new Context());
  1367. SECTION("local commit") {
  1368. binding_context_start_notify_calls = 0;
  1369. binding_context_end_notify_calls = 0;
  1370. coordinator->on_change();
  1371. r->begin_transaction();
  1372. REQUIRE(binding_context_start_notify_calls == 1);
  1373. REQUIRE(binding_context_end_notify_calls == 1);
  1374. r->cancel_transaction();
  1375. }
  1376. SECTION("remote commit") {
  1377. binding_context_start_notify_calls = 0;
  1378. binding_context_end_notify_calls = 0;
  1379. JoiningThread([&] {
  1380. auto r2 = coordinator->get_realm();
  1381. r2->begin_transaction();
  1382. auto table2 = r2->read_group().get_table("class_object");
  1383. table2->add_empty_row();
  1384. r2->commit_transaction();
  1385. });
  1386. advance_and_notify(*r);
  1387. REQUIRE(binding_context_start_notify_calls == 1);
  1388. REQUIRE(binding_context_end_notify_calls == 1);
  1389. }
  1390. }
  1391. SECTION("notify BindingContext before and after sending notifications") {
  1392. static int binding_context_start_notify_calls = 0;
  1393. static int binding_context_end_notify_calls = 0;
  1394. static int notification_calls = 0;
  1395. Results results1(r, table->where().greater_equal(0, 0));
  1396. Results results2(r, table->where().less(0, 10));
  1397. auto token1 = results1.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
  1398. REQUIRE_FALSE(err);
  1399. ++notification_calls;
  1400. });
  1401. auto token2 = results2.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
  1402. REQUIRE_FALSE(err);
  1403. ++notification_calls;
  1404. });
  1405. struct Context : BindingContext {
  1406. void will_send_notifications() override
  1407. {
  1408. REQUIRE(notification_calls == 0);
  1409. REQUIRE(binding_context_end_notify_calls == 0);
  1410. ++binding_context_start_notify_calls;
  1411. }
  1412. void did_send_notifications() override
  1413. {
  1414. REQUIRE(notification_calls == 2);
  1415. REQUIRE(binding_context_start_notify_calls == 1);
  1416. ++binding_context_end_notify_calls;
  1417. }
  1418. };
  1419. r->m_binding_context.reset(new Context());
  1420. SECTION("local commit") {
  1421. binding_context_start_notify_calls = 0;
  1422. binding_context_end_notify_calls = 0;
  1423. notification_calls = 0;
  1424. coordinator->on_change();
  1425. r->begin_transaction();
  1426. table->add_empty_row();
  1427. r->commit_transaction();
  1428. REQUIRE(binding_context_start_notify_calls == 1);
  1429. REQUIRE(binding_context_end_notify_calls == 1);
  1430. }
  1431. SECTION("remote commit") {
  1432. binding_context_start_notify_calls = 0;
  1433. binding_context_end_notify_calls = 0;
  1434. notification_calls = 0;
  1435. JoiningThread([&] {
  1436. auto r2 = coordinator->get_realm();
  1437. r2->begin_transaction();
  1438. auto table2 = r2->read_group().get_table("class_object");
  1439. table2->add_empty_row();
  1440. r2->commit_transaction();
  1441. });
  1442. advance_and_notify(*r);
  1443. REQUIRE(binding_context_start_notify_calls == 1);
  1444. REQUIRE(binding_context_end_notify_calls == 1);
  1445. }
  1446. }
  1447. SECTION("did_send() is skipped if the Realm is closed first") {
  1448. Results results(r, table->where());
  1449. bool do_close = true;
  1450. auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
  1451. if (do_close)
  1452. r->close();
  1453. });
  1454. struct FailOnDidSend : BindingContext {
  1455. void did_send_notifications() override
  1456. {
  1457. FAIL("did_send_notifications() should not have been called");
  1458. }
  1459. };
  1460. struct CloseOnWillChange : FailOnDidSend {
  1461. Realm& realm;
  1462. CloseOnWillChange(Realm& realm) : realm(realm) {}
  1463. void will_send_notifications() override
  1464. {
  1465. realm.close();
  1466. }
  1467. };
  1468. SECTION("closed in notification callback for notify()") {
  1469. r->m_binding_context.reset(new FailOnDidSend);
  1470. coordinator->on_change();
  1471. r->notify();
  1472. }
  1473. SECTION("closed in notification callback for refresh()") {
  1474. do_close = false;
  1475. coordinator->on_change();
  1476. r->notify();
  1477. do_close = true;
  1478. JoiningThread([&] {
  1479. auto r = coordinator->get_realm();
  1480. r->begin_transaction();
  1481. r->read_group().get_table("class_object")->add_empty_row();
  1482. r->commit_transaction();
  1483. });
  1484. r->m_binding_context.reset(new FailOnDidSend);
  1485. coordinator->on_change();
  1486. r->refresh();
  1487. }
  1488. SECTION("closed in will_send() for notify()") {
  1489. r->m_binding_context.reset(new CloseOnWillChange(*r));
  1490. coordinator->on_change();
  1491. r->notify();
  1492. }
  1493. SECTION("closed in will_send() for refresh()") {
  1494. do_close = false;
  1495. coordinator->on_change();
  1496. r->notify();
  1497. do_close = true;
  1498. JoiningThread([&] {
  1499. auto r = coordinator->get_realm();
  1500. r->begin_transaction();
  1501. r->read_group().get_table("class_object")->add_empty_row();
  1502. r->commit_transaction();
  1503. });
  1504. r->m_binding_context.reset(new CloseOnWillChange(*r));
  1505. coordinator->on_change();
  1506. r->refresh();
  1507. }
  1508. }
  1509. }
  1510. TEST_CASE("Statistics on Realms") {
  1511. _impl::RealmCoordinator::assert_no_open_realms();
  1512. InMemoryTestFile config;
  1513. config.cache = false;
  1514. config.automatic_change_notifications = false;
  1515. auto r = Realm::get_shared_realm(config);
  1516. r->update_schema({
  1517. {"object", {
  1518. {"value", PropertyType::Int}
  1519. }},
  1520. });
  1521. SECTION("compute_size") {
  1522. auto s = r->read_group().compute_aggregated_byte_size();
  1523. REQUIRE(s > 0);
  1524. }
  1525. }
  1526. #if REALM_PLATFORM_APPLE
  1527. TEST_CASE("BindingContext is notified in case of notifier errors") {
  1528. _impl::RealmCoordinator::assert_no_open_realms();
  1529. class OpenFileLimiter {
  1530. public:
  1531. OpenFileLimiter()
  1532. {
  1533. // Set the max open files to zero so that opening new files will fail
  1534. getrlimit(RLIMIT_NOFILE, &m_old);
  1535. rlimit rl = m_old;
  1536. rl.rlim_cur = 0;
  1537. setrlimit(RLIMIT_NOFILE, &rl);
  1538. }
  1539. ~OpenFileLimiter()
  1540. {
  1541. setrlimit(RLIMIT_NOFILE, &m_old);
  1542. }
  1543. private:
  1544. rlimit m_old;
  1545. };
  1546. InMemoryTestFile config;
  1547. config.cache = false;
  1548. config.automatic_change_notifications = false;
  1549. auto r = Realm::get_shared_realm(config);
  1550. r->update_schema({
  1551. {"object", {
  1552. {"value", PropertyType::Int}
  1553. }},
  1554. });
  1555. auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
  1556. auto table = r->read_group().get_table("class_object");
  1557. Results results(r, *r->read_group().get_table("class_object"));
  1558. static int binding_context_start_notify_calls = 0;
  1559. static int binding_context_end_notify_calls = 0;
  1560. static bool error_called = false;
  1561. struct Context : BindingContext {
  1562. void will_send_notifications() override
  1563. {
  1564. REQUIRE_FALSE(error_called);
  1565. ++binding_context_start_notify_calls;
  1566. }
  1567. void did_send_notifications() override
  1568. {
  1569. REQUIRE(error_called);
  1570. ++binding_context_end_notify_calls;
  1571. }
  1572. };
  1573. r->m_binding_context.reset(new Context());
  1574. SECTION("realm on background thread could not be opened") {
  1575. OpenFileLimiter limiter;
  1576. auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
  1577. REQUIRE(err);
  1578. REQUIRE_FALSE(error_called);
  1579. error_called = true;
  1580. });
  1581. advance_and_notify(*r);
  1582. REQUIRE(error_called);
  1583. REQUIRE(binding_context_start_notify_calls == 1);
  1584. REQUIRE(binding_context_end_notify_calls == 1);
  1585. }
  1586. }
  1587. #endif
  1588. TEST_CASE("RealmCoordinator: get_unbound_realm()") {
  1589. TestFile config;
  1590. config.schema = Schema{
  1591. {"object", {
  1592. {"value", PropertyType::Int}
  1593. }},
  1594. };
  1595. ThreadSafeReference<Realm> ref;
  1596. std::thread([&] { ref = _impl::RealmCoordinator::get_coordinator(config)->get_unbound_realm(); }).join();
  1597. SECTION("checks thread after being resolved") {
  1598. auto realm = Realm::get_shared_realm(std::move(ref));
  1599. REQUIRE_NOTHROW(realm->verify_thread());
  1600. std::thread([&] {
  1601. REQUIRE_THROWS(realm->verify_thread());
  1602. }).join();
  1603. }
  1604. SECTION("delivers notifications to the thread it is resolved on") {
  1605. if (!util::EventLoop::has_implementation())
  1606. return;
  1607. auto realm = Realm::get_shared_realm(std::move(ref));
  1608. Results results(realm, ObjectStore::table_for_object_type(realm->read_group(), "object")->where());
  1609. bool called = false;
  1610. auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
  1611. called = true;
  1612. });
  1613. util::EventLoop::main().run_until([&] { return called; });
  1614. }
  1615. SECTION("does not check thread if resolved using an execution context") {
  1616. auto realm = Realm::get_shared_realm(std::move(ref), AbstractExecutionContextID(1));
  1617. REQUIRE_NOTHROW(realm->verify_thread());
  1618. std::thread([&] {
  1619. REQUIRE_NOTHROW(realm->verify_thread());
  1620. }).join();
  1621. }
  1622. SECTION("resolves to existing cached Realm for the thread if caching is enabled") {
  1623. auto r1 = Realm::get_shared_realm(config);
  1624. auto r2 = Realm::get_shared_realm(std::move(ref));
  1625. REQUIRE(r1 == r2);
  1626. }
  1627. SECTION("resolves to existing cached Realm for the execution context if caching is enabled") {
  1628. config.execution_context = AbstractExecutionContextID(1);
  1629. auto r1 = Realm::get_shared_realm(config);
  1630. config.execution_context = AbstractExecutionContextID(2);
  1631. auto r2 = Realm::get_shared_realm(config);
  1632. auto r3 = Realm::get_shared_realm(std::move(ref), AbstractExecutionContextID(1));
  1633. REQUIRE(r1 == r3);
  1634. REQUIRE(r1 != r2);
  1635. REQUIRE(r2 != r3);
  1636. }
  1637. SECTION("resolves to a new Realm if caching is disabled") {
  1638. // Cache disabled for local realm, enabled for unbound
  1639. config.cache = false;
  1640. auto r1 = Realm::get_shared_realm(config);
  1641. auto r2 = Realm::get_shared_realm(std::move(ref));
  1642. REQUIRE(r1 != r2);
  1643. // New unbound with cache disabled
  1644. std::thread([&] { ref = _impl::RealmCoordinator::get_coordinator(config)->get_unbound_realm(); }).join();
  1645. auto r3 = Realm::get_shared_realm(std::move(ref));
  1646. REQUIRE(r1 != r3);
  1647. REQUIRE(r2 != r3);
  1648. // New local with cache enabled should grab the resolved unbound
  1649. config.cache = true;
  1650. auto r4 = Realm::get_shared_realm(config);
  1651. REQUIRE(r4 == r2);
  1652. }
  1653. }