realm.cpp 67 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921
  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. TestSyncManager init_sync_manager;
  398. SyncServer server;
  399. SyncTestFile config(server, "default");
  400. config.cache = false;
  401. config.schema = Schema{
  402. {"object", {
  403. {"value", PropertyType::Int},
  404. }},
  405. };
  406. SyncTestFile config2(server, "default");
  407. config2.cache = false;
  408. config2.schema = config.schema;
  409. std::mutex mutex;
  410. SECTION("can open synced Realms that don't already exist") {
  411. std::atomic<bool> called{false};
  412. auto task = Realm::get_synchronized_realm(config);
  413. task->start([&](auto ref, auto error) {
  414. std::lock_guard<std::mutex> lock(mutex);
  415. REQUIRE(!error);
  416. called = true;
  417. REQUIRE(Realm::get_shared_realm(std::move(ref))->read_group().get_table("class_object"));
  418. });
  419. util::EventLoop::main().run_until([&]{ return called.load(); });
  420. std::lock_guard<std::mutex> lock(mutex);
  421. REQUIRE(called);
  422. }
  423. SECTION("downloads Realms which exist on the server") {
  424. {
  425. auto realm = Realm::get_shared_realm(config2);
  426. realm->begin_transaction();
  427. sync::create_object(realm->read_group(), *realm->read_group().get_table("class_object"));
  428. realm->commit_transaction();
  429. wait_for_upload(*realm);
  430. }
  431. std::atomic<bool> called{false};
  432. auto task = Realm::get_synchronized_realm(config);
  433. task->start([&](auto ref, auto error) {
  434. std::lock_guard<std::mutex> lock(mutex);
  435. REQUIRE(!error);
  436. called = true;
  437. REQUIRE(Realm::get_shared_realm(std::move(ref))->read_group().get_table("class_object"));
  438. });
  439. util::EventLoop::main().run_until([&]{ return called.load(); });
  440. std::lock_guard<std::mutex> lock(mutex);
  441. REQUIRE(called);
  442. }
  443. SECTION("downloads latest state for Realms which already exist locally") {
  444. wait_for_upload(*Realm::get_shared_realm(config));
  445. {
  446. auto realm = Realm::get_shared_realm(config2);
  447. realm->begin_transaction();
  448. sync::create_object(realm->read_group(), *realm->read_group().get_table("class_object"));
  449. realm->commit_transaction();
  450. wait_for_upload(*realm);
  451. }
  452. std::atomic<bool> called{false};
  453. auto task = Realm::get_synchronized_realm(config);
  454. task->start([&](auto ref, auto error) {
  455. std::lock_guard<std::mutex> lock(mutex);
  456. REQUIRE(!error);
  457. called = true;
  458. REQUIRE(Realm::get_shared_realm(std::move(ref))->read_group().get_table("class_object")->size() == 1);
  459. });
  460. util::EventLoop::main().run_until([&]{ return called.load(); });
  461. std::lock_guard<std::mutex> lock(mutex);
  462. REQUIRE(called);
  463. }
  464. SECTION("can download partial Realms") {
  465. config.sync_config->is_partial = true;
  466. config2.sync_config->is_partial = true;
  467. {
  468. auto realm = Realm::get_shared_realm(config2);
  469. realm->begin_transaction();
  470. sync::create_object(realm->read_group(), *realm->read_group().get_table("class_object"));
  471. realm->commit_transaction();
  472. wait_for_upload(*realm);
  473. }
  474. std::atomic<bool> called{false};
  475. auto task = Realm::get_synchronized_realm(config);
  476. task->start([&](auto, auto error) {
  477. std::lock_guard<std::mutex> lock(mutex);
  478. REQUIRE(!error);
  479. called = true;
  480. });
  481. util::EventLoop::main().run_until([&]{ return called.load(); });
  482. std::lock_guard<std::mutex> lock(mutex);
  483. REQUIRE(called);
  484. // No subscriptions, so no objects
  485. REQUIRE(Realm::get_shared_realm(config)->read_group().get_table("class_object")->size() == 0);
  486. }
  487. SECTION("can download multiple Realms at a time") {
  488. SyncTestFile config1(server, "realm1");
  489. SyncTestFile config2(server, "realm2");
  490. SyncTestFile config3(server, "realm3");
  491. SyncTestFile config4(server, "realm4");
  492. std::vector<std::shared_ptr<AsyncOpenTask>> tasks = {
  493. Realm::get_synchronized_realm(config1),
  494. Realm::get_synchronized_realm(config2),
  495. Realm::get_synchronized_realm(config3),
  496. Realm::get_synchronized_realm(config4),
  497. };
  498. std::atomic<int> completed{0};
  499. for (auto& task : tasks) {
  500. task->start([&](auto, auto) {
  501. ++completed;
  502. });
  503. }
  504. util::EventLoop::main().run_until([&]{ return completed == 4; });
  505. }
  506. }
  507. #endif
  508. TEST_CASE("SharedRealm: notifications") {
  509. if (!util::EventLoop::has_implementation())
  510. return;
  511. TestFile config;
  512. config.cache = false;
  513. config.schema_version = 0;
  514. config.schema = Schema{
  515. {"object", {
  516. {"value", PropertyType::Int}
  517. }},
  518. };
  519. struct Context : BindingContext {
  520. size_t* change_count;
  521. Context(size_t* out) : change_count(out) { }
  522. void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
  523. {
  524. ++*change_count;
  525. }
  526. };
  527. size_t change_count = 0;
  528. auto realm = Realm::get_shared_realm(config);
  529. realm->m_binding_context.reset(new Context{&change_count});
  530. realm->m_binding_context->realm = realm;
  531. SECTION("local notifications are sent synchronously") {
  532. realm->begin_transaction();
  533. REQUIRE(change_count == 0);
  534. realm->commit_transaction();
  535. REQUIRE(change_count == 1);
  536. }
  537. SECTION("remote notifications are sent asynchronously") {
  538. auto r2 = Realm::get_shared_realm(config);
  539. r2->begin_transaction();
  540. r2->commit_transaction();
  541. REQUIRE(change_count == 0);
  542. util::EventLoop::main().run_until([&]{ return change_count > 0; });
  543. REQUIRE(change_count == 1);
  544. }
  545. SECTION("refresh() from within changes_available() refreshes") {
  546. struct Context : BindingContext {
  547. Realm& realm;
  548. Context(Realm& realm) : realm(realm) { }
  549. void changes_available() override
  550. {
  551. REQUIRE(realm.refresh());
  552. }
  553. };
  554. realm->m_binding_context.reset(new Context{*realm});
  555. realm->set_auto_refresh(false);
  556. auto r2 = Realm::get_shared_realm(config);
  557. r2->begin_transaction();
  558. r2->commit_transaction();
  559. realm->notify();
  560. // Should return false as the realm was already advanced
  561. REQUIRE_FALSE(realm->refresh());
  562. }
  563. SECTION("refresh() from within did_change() is a no-op") {
  564. struct Context : BindingContext {
  565. Realm& realm;
  566. Context(Realm& realm) : realm(realm) { }
  567. void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
  568. {
  569. // Create another version so that refresh() could do something
  570. auto r2 = Realm::get_shared_realm(realm.config());
  571. r2->begin_transaction();
  572. r2->commit_transaction();
  573. // Should be a no-op
  574. REQUIRE_FALSE(realm.refresh());
  575. }
  576. };
  577. realm->m_binding_context.reset(new Context{*realm});
  578. auto r2 = Realm::get_shared_realm(config);
  579. r2->begin_transaction();
  580. r2->commit_transaction();
  581. REQUIRE(realm->refresh());
  582. realm->m_binding_context.reset();
  583. // Should advance to the version created in the previous did_change()
  584. REQUIRE(realm->refresh());
  585. // No more versions, so returns false
  586. REQUIRE_FALSE(realm->refresh());
  587. }
  588. SECTION("begin_write() from within did_change() produces recursive notifications") {
  589. struct Context : BindingContext {
  590. Realm& realm;
  591. size_t calls = 0;
  592. Context(Realm& realm) : realm(realm) { }
  593. void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
  594. {
  595. ++calls;
  596. if (realm.is_in_transaction())
  597. return;
  598. // Create another version so that begin_write() advances the version
  599. auto r2 = Realm::get_shared_realm(realm.config());
  600. r2->begin_transaction();
  601. r2->commit_transaction();
  602. realm.begin_transaction();
  603. realm.cancel_transaction();
  604. }
  605. };
  606. auto context = new Context{*realm};
  607. realm->m_binding_context.reset(context);
  608. auto r2 = Realm::get_shared_realm(config);
  609. r2->begin_transaction();
  610. r2->commit_transaction();
  611. REQUIRE(realm->refresh());
  612. REQUIRE(context->calls == 2);
  613. // Despite not sending a new notification we did advance the version, so
  614. // no more versions to refresh to
  615. REQUIRE_FALSE(realm->refresh());
  616. }
  617. }
  618. TEST_CASE("SharedRealm: schema updating from external changes") {
  619. TestFile config;
  620. config.cache = false;
  621. config.schema_version = 0;
  622. config.schema_mode = SchemaMode::Additive;
  623. config.schema = Schema{
  624. {"object", {
  625. {"value", PropertyType::Int, Property::IsPrimary{true}},
  626. {"value 2", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  627. }},
  628. };
  629. SECTION("newly added columns update table columns but are not added to properties") {
  630. auto r1 = Realm::get_shared_realm(config);
  631. auto r2 = Realm::get_shared_realm(config);
  632. auto test = [&] {
  633. r2->begin_transaction();
  634. r2->read_group().get_table("class_object")->insert_column(0, type_String, "new col");
  635. r2->commit_transaction();
  636. auto& object_schema = *r1->schema().find("object");
  637. REQUIRE(object_schema.persisted_properties.size() == 2);
  638. REQUIRE(object_schema.persisted_properties[0].table_column == 0);
  639. r1->refresh();
  640. REQUIRE(object_schema.persisted_properties[0].table_column == 1);
  641. };
  642. SECTION("with an active read transaction") {
  643. r1->read_group();
  644. test();
  645. }
  646. SECTION("without an active read transaction") {
  647. r1->invalidate();
  648. test();
  649. }
  650. }
  651. SECTION("beginning a read transaction checks for incompatible changes") {
  652. auto r = Realm::get_shared_realm(config);
  653. r->invalidate();
  654. auto& sg = TestHelper::get_shared_group(r);
  655. WriteTransaction wt(sg);
  656. auto& table = *wt.get_table("class_object");
  657. SECTION("removing a property") {
  658. table.remove_column(0);
  659. wt.commit();
  660. REQUIRE_THROWS_WITH(r->refresh(),
  661. Catch::Matchers::Contains("Property 'object.value' has been removed."));
  662. }
  663. SECTION("change property type") {
  664. table.remove_column(1);
  665. table.add_column(type_Float, "value 2");
  666. wt.commit();
  667. REQUIRE_THROWS_WITH(r->refresh(),
  668. Catch::Matchers::Contains("Property 'object.value 2' has been changed from 'int' to 'float'"));
  669. }
  670. SECTION("make property optional") {
  671. table.remove_column(1);
  672. table.add_column(type_Int, "value 2", true);
  673. wt.commit();
  674. REQUIRE_THROWS_WITH(r->refresh(),
  675. Catch::Matchers::Contains("Property 'object.value 2' has been made optional"));
  676. }
  677. SECTION("recreate column with no changes") {
  678. table.remove_column(1);
  679. table.add_column(type_Int, "value 2");
  680. wt.commit();
  681. REQUIRE_NOTHROW(r->refresh());
  682. }
  683. SECTION("remove index from non-PK") {
  684. table.remove_search_index(1);
  685. wt.commit();
  686. REQUIRE_NOTHROW(r->refresh());
  687. }
  688. }
  689. }
  690. TEST_CASE("SharedRealm: closed realm") {
  691. TestFile config;
  692. config.schema_version = 1;
  693. config.schema = Schema{
  694. {"object", {
  695. {"value", PropertyType::Int}
  696. }},
  697. };
  698. auto realm = Realm::get_shared_realm(config);
  699. realm->close();
  700. REQUIRE(realm->is_closed());
  701. REQUIRE_THROWS_AS(realm->read_group(), ClosedRealmException);
  702. REQUIRE_THROWS_AS(realm->begin_transaction(), ClosedRealmException);
  703. REQUIRE(!realm->is_in_transaction());
  704. REQUIRE_THROWS_AS(realm->commit_transaction(), InvalidTransactionException);
  705. REQUIRE_THROWS_AS(realm->cancel_transaction(), InvalidTransactionException);
  706. REQUIRE_THROWS_AS(realm->refresh(), ClosedRealmException);
  707. REQUIRE_THROWS_AS(realm->invalidate(), ClosedRealmException);
  708. REQUIRE_THROWS_AS(realm->compact(), ClosedRealmException);
  709. }
  710. TEST_CASE("ShareRealm: in-memory mode from buffer") {
  711. TestFile config;
  712. config.schema_version = 1;
  713. config.schema = Schema{
  714. {"object", {
  715. {"value", PropertyType::Int}
  716. }},
  717. };
  718. SECTION("Save and open Realm from in-memory buffer") {
  719. // Write in-memory copy of Realm to a buffer
  720. auto realm = Realm::get_shared_realm(config);
  721. OwnedBinaryData realm_buffer = realm->write_copy();
  722. // Open the buffer as a new (immutable in-memory) Realm
  723. realm::Realm::Config config2;
  724. config2.in_memory = true;
  725. config2.schema_mode = SchemaMode::Immutable;
  726. config2.realm_data = realm_buffer.get();
  727. auto realm2 = Realm::get_shared_realm(config2);
  728. // Verify that it can read the schema and that it is the same
  729. REQUIRE(realm->schema().size() == 1);
  730. auto it = realm->schema().find("object");
  731. REQUIRE(it != realm->schema().end());
  732. REQUIRE(it->persisted_properties.size() == 1);
  733. REQUIRE(it->persisted_properties[0].name == "value");
  734. REQUIRE(it->persisted_properties[0].table_column == 0);
  735. // Test invalid configs
  736. realm::Realm::Config config3;
  737. config3.realm_data = realm_buffer.get();
  738. REQUIRE_THROWS(Realm::get_shared_realm(config3)); // missing in_memory and immutable
  739. config3.in_memory = true;
  740. config3.schema_mode = SchemaMode::Immutable;
  741. config3.path = "path";
  742. REQUIRE_THROWS(Realm::get_shared_realm(config3)); // both buffer and path
  743. config3.path = "";
  744. config3.encryption_key = {'a'};
  745. REQUIRE_THROWS(Realm::get_shared_realm(config3)); // both buffer and encryption
  746. }
  747. }
  748. TEST_CASE("ShareRealm: realm closed in did_change callback") {
  749. TestFile config;
  750. config.schema_version = 1;
  751. config.schema = Schema{
  752. {"object", {
  753. {"value", PropertyType::Int}
  754. }},
  755. };
  756. config.cache = false;
  757. config.automatic_change_notifications = false;
  758. auto r1 = Realm::get_shared_realm(config);
  759. r1->begin_transaction();
  760. auto table = r1->read_group().get_table("class_object");
  761. table->add_empty_row();
  762. r1->commit_transaction();
  763. // Cannot be a member var of Context since Realm.close will free the context.
  764. static SharedRealm* shared_realm;
  765. shared_realm = &r1;
  766. struct Context : public BindingContext {
  767. void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
  768. {
  769. (*shared_realm)->close();
  770. (*shared_realm).reset();
  771. }
  772. };
  773. SECTION("did_change") {
  774. r1->m_binding_context.reset(new Context());
  775. r1->invalidate();
  776. auto r2 = Realm::get_shared_realm(config);
  777. r2->begin_transaction();
  778. r2->read_group().get_table("class_object")->add_empty_row(1);
  779. r2->commit_transaction();
  780. r2.reset();
  781. r1->notify();
  782. }
  783. SECTION("did_change with async results") {
  784. r1->m_binding_context.reset(new Context());
  785. Results results(r1, table->where());
  786. auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
  787. // Should not be called.
  788. REQUIRE(false);
  789. });
  790. auto r2 = Realm::get_shared_realm(config);
  791. r2->begin_transaction();
  792. r2->read_group().get_table("class_object")->add_empty_row(1);
  793. r2->commit_transaction();
  794. r2.reset();
  795. auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
  796. coordinator->on_change();
  797. r1->notify();
  798. }
  799. SECTION("refresh") {
  800. r1->m_binding_context.reset(new Context());
  801. auto r2 = Realm::get_shared_realm(config);
  802. r2->begin_transaction();
  803. r2->read_group().get_table("class_object")->add_empty_row(1);
  804. r2->commit_transaction();
  805. r2.reset();
  806. REQUIRE_FALSE(r1->refresh());
  807. }
  808. shared_realm = nullptr;
  809. }
  810. TEST_CASE("RealmCoordinator: schema cache") {
  811. TestFile config;
  812. auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path);
  813. Schema cache_schema;
  814. uint64_t cache_sv = -1, cache_tv = -1;
  815. Schema schema{
  816. {"object", {
  817. {"value", PropertyType::Int}
  818. }},
  819. };
  820. Schema schema2{
  821. {"object", {
  822. {"value", PropertyType::Int},
  823. }},
  824. {"object 2", {
  825. {"value", PropertyType::Int},
  826. }},
  827. };
  828. SECTION("valid initial schema sets cache") {
  829. coordinator->cache_schema(schema, 5, 10);
  830. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  831. REQUIRE(cache_schema == schema);
  832. REQUIRE(cache_sv == 5);
  833. REQUIRE(cache_tv == 10);
  834. }
  835. SECTION("cache can be updated with newer schema") {
  836. coordinator->cache_schema(schema, 5, 10);
  837. coordinator->cache_schema(schema2, 6, 11);
  838. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  839. REQUIRE(cache_schema == schema2);
  840. REQUIRE(cache_sv == 6);
  841. REQUIRE(cache_tv == 11);
  842. }
  843. SECTION("empty schema is ignored") {
  844. coordinator->cache_schema(Schema{}, 5, 10);
  845. REQUIRE_FALSE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  846. coordinator->cache_schema(schema, 5, 10);
  847. coordinator->cache_schema(Schema{}, 5, 10);
  848. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  849. REQUIRE(cache_schema == schema);
  850. REQUIRE(cache_sv == 5);
  851. REQUIRE(cache_tv == 10);
  852. }
  853. SECTION("schema for older transaction is ignored") {
  854. coordinator->cache_schema(schema, 5, 10);
  855. coordinator->cache_schema(schema2, 4, 8);
  856. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  857. REQUIRE(cache_schema == schema);
  858. REQUIRE(cache_sv == 5);
  859. REQUIRE(cache_tv == 10);
  860. coordinator->advance_schema_cache(10, 20);
  861. coordinator->cache_schema(schema, 6, 15);
  862. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  863. REQUIRE(cache_tv == 20); // should not have dropped to 15
  864. }
  865. SECTION("advance_schema() from transaction version bumps transaction version") {
  866. coordinator->cache_schema(schema, 5, 10);
  867. coordinator->advance_schema_cache(10, 12);
  868. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  869. REQUIRE(cache_schema == schema);
  870. REQUIRE(cache_sv == 5);
  871. REQUIRE(cache_tv == 12);
  872. }
  873. SECTION("advance_schema() ending before transaction version does nothing") {
  874. coordinator->cache_schema(schema, 5, 10);
  875. coordinator->advance_schema_cache(8, 9);
  876. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  877. REQUIRE(cache_schema == schema);
  878. REQUIRE(cache_sv == 5);
  879. REQUIRE(cache_tv == 10);
  880. }
  881. SECTION("advance_schema() extending over transaction version bumps version") {
  882. coordinator->cache_schema(schema, 5, 10);
  883. coordinator->advance_schema_cache(3, 15);
  884. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  885. REQUIRE(cache_schema == schema);
  886. REQUIRE(cache_sv == 5);
  887. REQUIRE(cache_tv == 15);
  888. }
  889. SECTION("advance_schema() with no cahced schema does nothing") {
  890. coordinator->advance_schema_cache(3, 15);
  891. REQUIRE_FALSE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  892. }
  893. }
  894. TEST_CASE("SharedRealm: coordinator schema cache") {
  895. TestFile config;
  896. config.cache = false;
  897. auto r = Realm::get_shared_realm(config);
  898. auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
  899. Schema cache_schema;
  900. uint64_t cache_sv = -1, cache_tv = -1;
  901. Schema schema{
  902. {"object", {
  903. {"value", PropertyType::Int}
  904. }},
  905. };
  906. Schema schema2{
  907. {"object", {
  908. {"value", PropertyType::Int},
  909. }},
  910. {"object 2", {
  911. {"value", PropertyType::Int},
  912. }},
  913. };
  914. class ExternalWriter {
  915. private:
  916. std::unique_ptr<Replication> history;
  917. std::unique_ptr<SharedGroup> shared_group;
  918. std::unique_ptr<Group> read_only_group;
  919. public:
  920. WriteTransaction wt;
  921. ExternalWriter(Realm::Config const& config)
  922. : wt([&]() -> SharedGroup& {
  923. Realm::open_with_config(config, history, shared_group, read_only_group, nullptr);
  924. return *shared_group;
  925. }())
  926. {
  927. }
  928. };
  929. auto external_write = [&](Realm::Config const& config, auto&& fn) {
  930. ExternalWriter wt(config);
  931. fn(wt.wt);
  932. wt.wt.commit();
  933. };
  934. SECTION("is initially empty for uninitialized file") {
  935. REQUIRE_FALSE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  936. }
  937. r->update_schema(schema);
  938. SECTION("is populated after calling update_schema()") {
  939. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  940. REQUIRE(cache_sv == 0);
  941. REQUIRE(cache_schema == schema);
  942. REQUIRE(cache_schema.begin()->persisted_properties[0].table_column == 0);
  943. }
  944. coordinator = nullptr;
  945. r = nullptr;
  946. r = Realm::get_shared_realm(config);
  947. coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
  948. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  949. SECTION("is populated after opening an initialized file") {
  950. REQUIRE(cache_sv == 0);
  951. REQUIRE(cache_tv == 2); // with in-realm history the version doesn't reset
  952. REQUIRE(cache_schema == schema);
  953. REQUIRE(cache_schema.begin()->persisted_properties[0].table_column == 0);
  954. }
  955. SECTION("transaction version is bumped after a local write") {
  956. auto tv = cache_tv;
  957. r->begin_transaction();
  958. r->commit_transaction();
  959. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  960. REQUIRE(cache_tv == tv + 1);
  961. }
  962. SECTION("notify() without a read transaction does not bump transaction version") {
  963. auto tv = cache_tv;
  964. SECTION("non-schema change") {
  965. external_write(config, [](auto& wt) {
  966. wt.get_table("class_object")->add_empty_row();
  967. });
  968. }
  969. SECTION("schema change") {
  970. external_write(config, [](auto& wt) {
  971. wt.add_table("class_object 2");
  972. });
  973. }
  974. r->notify();
  975. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  976. REQUIRE(cache_tv == tv);
  977. REQUIRE(cache_schema == schema);
  978. }
  979. SECTION("notify() with a read transaction bumps transaction version") {
  980. r->read_group();
  981. external_write(config, [](auto& wt) {
  982. wt.get_table("class_object")->add_empty_row();
  983. });
  984. r->notify();
  985. auto tv = cache_tv;
  986. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  987. REQUIRE(cache_tv == tv + 1);
  988. }
  989. SECTION("notify() with a read transaction updates schema folloing external schema change") {
  990. r->read_group();
  991. external_write(config, [](auto& wt) {
  992. wt.add_table("class_object 2");
  993. });
  994. r->notify();
  995. auto tv = cache_tv;
  996. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  997. REQUIRE(cache_tv == tv + 1);
  998. REQUIRE(cache_schema.size() == 2);
  999. REQUIRE(cache_schema.find("object 2") != cache_schema.end());
  1000. }
  1001. SECTION("transaction version is bumped after refresh() following external non-schema write") {
  1002. external_write(config, [](auto& wt) {
  1003. wt.get_table("class_object")->add_empty_row();
  1004. });
  1005. r->refresh();
  1006. auto tv = cache_tv;
  1007. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  1008. REQUIRE(cache_tv == tv + 1);
  1009. }
  1010. SECTION("schema is reread following refresh() over external schema change") {
  1011. external_write(config, [](auto& wt) {
  1012. wt.add_table("class_object 2");
  1013. });
  1014. r->refresh();
  1015. auto tv = cache_tv;
  1016. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  1017. REQUIRE(cache_tv == tv + 1);
  1018. REQUIRE(cache_schema.size() == 2);
  1019. REQUIRE(cache_schema.find("object 2") != cache_schema.end());
  1020. }
  1021. SECTION("update_schema() to version already on disk updates cache") {
  1022. r->read_group();
  1023. external_write(config, [](auto& wt) {
  1024. auto table = wt.add_table("class_object 2");
  1025. table->add_column(type_Int, "value");
  1026. });
  1027. auto tv = cache_tv;
  1028. r->update_schema(schema2);
  1029. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  1030. REQUIRE(cache_tv == tv + 1); // only +1 because update_schema() did not perform a write
  1031. REQUIRE(cache_schema.size() == 2);
  1032. REQUIRE(cache_schema.find("object 2") != cache_schema.end());
  1033. }
  1034. SECTION("update_schema() to version already on disk updates cache") {
  1035. r->read_group();
  1036. external_write(config, [](auto& wt) {
  1037. auto table = wt.add_table("class_object 2");
  1038. table->add_column(type_Int, "value");
  1039. });
  1040. auto tv = cache_tv;
  1041. r->update_schema(schema2);
  1042. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  1043. REQUIRE(cache_tv == tv + 1); // only +1 because update_schema() did not perform a write
  1044. REQUIRE(cache_schema.size() == 2);
  1045. REQUIRE(cache_schema.find("object 2") != cache_schema.end());
  1046. }
  1047. SECTION("update_schema() to version populated on disk while waiting for the write lock updates cache") {
  1048. r->read_group();
  1049. // We want to commit the write while we're waiting on the write lock on
  1050. // this thread, which can't really be done in a properly synchronized manner
  1051. std::chrono::microseconds wait_time{5000};
  1052. #if REALM_ANDROID
  1053. // When running on device or in an emulator we need to wait longer due
  1054. // to them being slow
  1055. wait_time *= 10;
  1056. #endif
  1057. bool did_run = false;
  1058. JoiningThread thread([&] {
  1059. ExternalWriter writer(config);
  1060. if (writer.wt.get_table("class_object 2"))
  1061. return;
  1062. did_run = true;
  1063. auto table = writer.wt.add_table("class_object 2");
  1064. table->add_column(type_Int, "value");
  1065. std::this_thread::sleep_for(wait_time * 2);
  1066. writer.wt.commit();
  1067. });
  1068. std::this_thread::sleep_for(wait_time);
  1069. auto tv = cache_tv;
  1070. r->update_schema(Schema{
  1071. {"object", {{"value", PropertyType::Int}}},
  1072. {"object 2", {{"value", PropertyType::Int}}},
  1073. });
  1074. // just skip the test if the timing was wrong to avoid spurious failures
  1075. if (!did_run)
  1076. return;
  1077. REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
  1078. REQUIRE(cache_tv == tv + 1); // only +1 because update_schema()'s write was rolled back
  1079. REQUIRE(cache_schema.size() == 2);
  1080. REQUIRE(cache_schema.find("object 2") != cache_schema.end());
  1081. }
  1082. }
  1083. TEST_CASE("SharedRealm: dynamic schema mode doesn't invalidate object schema pointers when schema hasn't changed") {
  1084. TestFile config;
  1085. config.cache = false;
  1086. // Prepopulate the Realm with the schema.
  1087. Realm::Config config_with_schema = config;
  1088. config_with_schema.schema_version = 1;
  1089. config_with_schema.schema_mode = SchemaMode::Automatic;
  1090. config_with_schema.schema = Schema{
  1091. {"object", {
  1092. {"value", PropertyType::Int, Property::IsPrimary{true}},
  1093. {"value 2", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  1094. }}
  1095. };
  1096. auto r1 = Realm::get_shared_realm(config_with_schema);
  1097. // Retrieve the object schema in dynamic mode.
  1098. auto r2 = Realm::get_shared_realm(config);
  1099. auto* object_schema = &*r2->schema().find("object");
  1100. // Perform an empty write to create a new version, resulting in the other Realm needing to re-read the schema.
  1101. r1->begin_transaction();
  1102. r1->commit_transaction();
  1103. // Advance to the latest version, and verify the object schema is at the same location in memory.
  1104. r2->read_group();
  1105. REQUIRE(object_schema == &*r2->schema().find("object"));
  1106. }
  1107. TEST_CASE("SharedRealm: SchemaChangedFunction") {
  1108. struct Context : BindingContext {
  1109. size_t* change_count;
  1110. Schema* schema;
  1111. Context(size_t* count_out, Schema* schema_out) : change_count(count_out), schema(schema_out) { }
  1112. void schema_did_change(Schema const& changed_schema) override
  1113. {
  1114. ++*change_count;
  1115. *schema = changed_schema;
  1116. }
  1117. };
  1118. size_t schema_changed_called = 0;
  1119. Schema changed_fixed_schema;
  1120. TestFile config;
  1121. config.cache = false;
  1122. auto dynamic_config = config;
  1123. config.schema = Schema{
  1124. {"object1", {
  1125. {"value", PropertyType::Int},
  1126. }},
  1127. {"object2", {
  1128. {"value", PropertyType::Int},
  1129. }}
  1130. };
  1131. config.schema_version = 1;
  1132. auto r1 = Realm::get_shared_realm(config);
  1133. r1->m_binding_context.reset(new Context(&schema_changed_called, &changed_fixed_schema));
  1134. SECTION("Fixed schema") {
  1135. SECTION("update_schema") {
  1136. auto new_schema = Schema{
  1137. {"object3", {
  1138. {"value", PropertyType::Int},
  1139. }}
  1140. };
  1141. r1->update_schema(new_schema, 2);
  1142. REQUIRE(schema_changed_called == 1);
  1143. REQUIRE(changed_fixed_schema.find("object3")->property_for_name("value")->table_column == 0);
  1144. }
  1145. SECTION("Open a new Realm instance with same config won't trigger") {
  1146. auto r2 = Realm::get_shared_realm(config);
  1147. REQUIRE(schema_changed_called == 0);
  1148. }
  1149. SECTION("Non schema related transaction doesn't trigger") {
  1150. auto r2 = Realm::get_shared_realm(config);
  1151. r2->begin_transaction();
  1152. r2->commit_transaction();
  1153. r1->refresh();
  1154. REQUIRE(schema_changed_called == 0);
  1155. }
  1156. SECTION("Schema is changed by another Realm") {
  1157. auto r2 = Realm::get_shared_realm(config);
  1158. r2->begin_transaction();
  1159. r2->read_group().get_table("class_object1")->insert_column(0, type_String, "new col");
  1160. r2->commit_transaction();
  1161. r1->refresh();
  1162. REQUIRE(schema_changed_called == 1);
  1163. REQUIRE(changed_fixed_schema.find("object1")->property_for_name("value")->table_column == 1);
  1164. }
  1165. // This is not a valid use case. m_schema won't be refreshed.
  1166. SECTION("Schema is changed by this Realm won't trigger") {
  1167. r1->begin_transaction();
  1168. r1->read_group().get_table("class_object1")->insert_column(0, type_String, "new col");
  1169. r1->commit_transaction();
  1170. REQUIRE(schema_changed_called == 0);
  1171. }
  1172. }
  1173. SECTION("Dynamic schema") {
  1174. size_t dynamic_schema_changed_called = 0;
  1175. Schema changed_dynamic_schema;
  1176. auto r2 = Realm::get_shared_realm(dynamic_config);
  1177. r2->m_binding_context.reset(new Context(&dynamic_schema_changed_called, &changed_dynamic_schema));
  1178. SECTION("set_schema_subset") {
  1179. auto new_schema = Schema{
  1180. {"object1", {
  1181. {"value", PropertyType::Int},
  1182. }}
  1183. };
  1184. r2->set_schema_subset(new_schema);
  1185. REQUIRE(schema_changed_called == 0);
  1186. REQUIRE(dynamic_schema_changed_called == 1);
  1187. REQUIRE(changed_dynamic_schema.find("object1")->property_for_name("value")->table_column == 0);
  1188. }
  1189. SECTION("Non schema related transaction will alway trigger in dynamic mode") {
  1190. auto r1 = Realm::get_shared_realm(config);
  1191. // An empty transaction will trigger the schema changes always in dynamic mode.
  1192. r1->begin_transaction();
  1193. r1->commit_transaction();
  1194. r2->refresh();
  1195. REQUIRE(dynamic_schema_changed_called == 1);
  1196. REQUIRE(changed_dynamic_schema.find("object1")->property_for_name("value")->table_column == 0);
  1197. }
  1198. SECTION("Schema is changed by another Realm") {
  1199. r1->begin_transaction();
  1200. r1->read_group().get_table("class_object1")->insert_column(0, type_String, "new col");
  1201. r1->commit_transaction();
  1202. r2->refresh();
  1203. REQUIRE(dynamic_schema_changed_called == 1);
  1204. REQUIRE(changed_dynamic_schema.find("object1")->property_for_name("value")->table_column == 1);
  1205. }
  1206. }
  1207. }
  1208. #ifndef _WIN32
  1209. TEST_CASE("SharedRealm: compact on launch") {
  1210. // Make compactable Realm
  1211. TestFile config;
  1212. config.cache = false;
  1213. config.automatic_change_notifications = false;
  1214. int num_opens = 0;
  1215. config.should_compact_on_launch_function = [&](size_t total_bytes, size_t used_bytes) {
  1216. REQUIRE(total_bytes > used_bytes);
  1217. num_opens++;
  1218. return num_opens != 2;
  1219. };
  1220. config.schema = Schema{
  1221. {"object", {
  1222. {"value", PropertyType::String}
  1223. }},
  1224. };
  1225. REQUIRE(num_opens == 0);
  1226. auto r = Realm::get_shared_realm(config);
  1227. REQUIRE(num_opens == 1);
  1228. r->begin_transaction();
  1229. auto table = r->read_group().get_table("class_object");
  1230. size_t count = 1000;
  1231. table->add_empty_row(count);
  1232. for (size_t i = 0; i < count; ++i)
  1233. table->set_string(0, i, util::format("Foo_%1", i % 10).c_str());
  1234. r->commit_transaction();
  1235. REQUIRE(table->size() == count);
  1236. r->close();
  1237. SECTION("compact reduces the file size") {
  1238. // Confirm expected sizes before and after opening the Realm
  1239. size_t size_before = size_t(File(config.path).get_size());
  1240. r = Realm::get_shared_realm(config);
  1241. REQUIRE(num_opens == 2);
  1242. r->close();
  1243. REQUIRE(size_t(File(config.path).get_size()) == size_before); // File size after returning false
  1244. r = Realm::get_shared_realm(config);
  1245. REQUIRE(num_opens == 3);
  1246. REQUIRE(size_t(File(config.path).get_size()) < size_before); // File size after returning true
  1247. // Validate that the file still contains what it should
  1248. REQUIRE(r->read_group().get_table("class_object")->size() == count);
  1249. // Registering for a collection notification shouldn't crash when compact on launch is used.
  1250. Results results(r, *r->read_group().get_table("class_object"));
  1251. results.async([](std::exception_ptr) { });
  1252. r->close();
  1253. }
  1254. SECTION("compact function does not get invoked if realm is open on another thread") {
  1255. r = Realm::get_shared_realm(config);
  1256. REQUIRE(num_opens == 2);
  1257. std::thread([&]{
  1258. auto r2 = Realm::get_shared_realm(config);
  1259. REQUIRE(num_opens == 2);
  1260. }).join();
  1261. r->close();
  1262. std::thread([&]{
  1263. auto r3 = Realm::get_shared_realm(config);
  1264. REQUIRE(num_opens == 3);
  1265. }).join();
  1266. }
  1267. }
  1268. #endif
  1269. struct ModeAutomatic {
  1270. static SchemaMode mode() { return SchemaMode::Automatic; }
  1271. static bool should_call_init_on_version_bump() { return false; }
  1272. };
  1273. struct ModeAdditive {
  1274. static SchemaMode mode() { return SchemaMode::Additive; }
  1275. static bool should_call_init_on_version_bump() { return false; }
  1276. };
  1277. struct ModeManual {
  1278. static SchemaMode mode() { return SchemaMode::Manual; }
  1279. static bool should_call_init_on_version_bump() { return false; }
  1280. };
  1281. struct ModeResetFile {
  1282. static SchemaMode mode() { return SchemaMode::ResetFile; }
  1283. static bool should_call_init_on_version_bump() { return true; }
  1284. };
  1285. TEMPLATE_TEST_CASE("SharedRealm: update_schema with initialization_function", "[init][update_schema]",
  1286. ModeAutomatic, ModeAdditive, ModeManual, ModeResetFile) {
  1287. TestFile config;
  1288. config.schema_mode = TestType::mode();
  1289. bool initialization_function_called = false;
  1290. uint64_t schema_version_in_callback = -1;
  1291. Schema schema_in_callback;
  1292. auto initialization_function = [&initialization_function_called, &schema_version_in_callback,
  1293. &schema_in_callback](auto shared_realm) {
  1294. REQUIRE(shared_realm->is_in_transaction());
  1295. initialization_function_called = true;
  1296. schema_version_in_callback = shared_realm->schema_version();
  1297. schema_in_callback = shared_realm->schema();
  1298. };
  1299. Schema schema{
  1300. {"object", {
  1301. {"value", PropertyType::String}
  1302. }},
  1303. };
  1304. SECTION("call initialization function directly by update_schema") {
  1305. // Open in dynamic mode with no schema specified
  1306. auto realm = Realm::get_shared_realm(config);
  1307. REQUIRE_FALSE(initialization_function_called);
  1308. realm->update_schema(schema, 0, nullptr, initialization_function);
  1309. REQUIRE(initialization_function_called);
  1310. REQUIRE(schema_version_in_callback == 0);
  1311. REQUIRE(schema_in_callback.compare(schema).size() == 0);
  1312. }
  1313. config.schema_version = 0;
  1314. config.schema = schema;
  1315. SECTION("initialization function should be called for unversioned realm") {
  1316. config.initialization_function = initialization_function;
  1317. Realm::get_shared_realm(config);
  1318. REQUIRE(initialization_function_called);
  1319. REQUIRE(schema_version_in_callback == 0);
  1320. REQUIRE(schema_in_callback.compare(schema).size() == 0);
  1321. }
  1322. SECTION("initialization function for versioned realm") {
  1323. // Initialize v0
  1324. Realm::get_shared_realm(config);
  1325. config.schema_version = 1;
  1326. config.initialization_function = initialization_function;
  1327. Realm::get_shared_realm(config);
  1328. REQUIRE(initialization_function_called == TestType::should_call_init_on_version_bump());
  1329. if (TestType::should_call_init_on_version_bump()) {
  1330. REQUIRE(schema_version_in_callback == 1);
  1331. REQUIRE(schema_in_callback.compare(schema).size() == 0);
  1332. }
  1333. }
  1334. }
  1335. TEST_CASE("BindingContext is notified about delivery of change notifications") {
  1336. _impl::RealmCoordinator::assert_no_open_realms();
  1337. InMemoryTestFile config;
  1338. config.cache = false;
  1339. config.automatic_change_notifications = false;
  1340. auto r = Realm::get_shared_realm(config);
  1341. r->update_schema({
  1342. {"object", {
  1343. {"value", PropertyType::Int}
  1344. }},
  1345. });
  1346. auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
  1347. auto table = r->read_group().get_table("class_object");
  1348. SECTION("BindingContext notified even if no callbacks are registered") {
  1349. static int binding_context_start_notify_calls = 0;
  1350. static int binding_context_end_notify_calls = 0;
  1351. struct Context : BindingContext {
  1352. void will_send_notifications() override
  1353. {
  1354. ++binding_context_start_notify_calls;
  1355. }
  1356. void did_send_notifications() override
  1357. {
  1358. ++binding_context_end_notify_calls;
  1359. }
  1360. };
  1361. r->m_binding_context.reset(new Context());
  1362. SECTION("local commit") {
  1363. binding_context_start_notify_calls = 0;
  1364. binding_context_end_notify_calls = 0;
  1365. coordinator->on_change();
  1366. r->begin_transaction();
  1367. REQUIRE(binding_context_start_notify_calls == 1);
  1368. REQUIRE(binding_context_end_notify_calls == 1);
  1369. r->cancel_transaction();
  1370. }
  1371. SECTION("remote commit") {
  1372. binding_context_start_notify_calls = 0;
  1373. binding_context_end_notify_calls = 0;
  1374. JoiningThread([&] {
  1375. auto r2 = coordinator->get_realm();
  1376. r2->begin_transaction();
  1377. auto table2 = r2->read_group().get_table("class_object");
  1378. table2->add_empty_row();
  1379. r2->commit_transaction();
  1380. });
  1381. advance_and_notify(*r);
  1382. REQUIRE(binding_context_start_notify_calls == 1);
  1383. REQUIRE(binding_context_end_notify_calls == 1);
  1384. }
  1385. }
  1386. SECTION("notify BindingContext before and after sending notifications") {
  1387. static int binding_context_start_notify_calls = 0;
  1388. static int binding_context_end_notify_calls = 0;
  1389. static int notification_calls = 0;
  1390. Results results1(r, table->where().greater_equal(0, 0));
  1391. Results results2(r, table->where().less(0, 10));
  1392. auto token1 = results1.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
  1393. REQUIRE_FALSE(err);
  1394. ++notification_calls;
  1395. });
  1396. auto token2 = results2.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
  1397. REQUIRE_FALSE(err);
  1398. ++notification_calls;
  1399. });
  1400. struct Context : BindingContext {
  1401. void will_send_notifications() override
  1402. {
  1403. REQUIRE(notification_calls == 0);
  1404. REQUIRE(binding_context_end_notify_calls == 0);
  1405. ++binding_context_start_notify_calls;
  1406. }
  1407. void did_send_notifications() override
  1408. {
  1409. REQUIRE(notification_calls == 2);
  1410. REQUIRE(binding_context_start_notify_calls == 1);
  1411. ++binding_context_end_notify_calls;
  1412. }
  1413. };
  1414. r->m_binding_context.reset(new Context());
  1415. SECTION("local commit") {
  1416. binding_context_start_notify_calls = 0;
  1417. binding_context_end_notify_calls = 0;
  1418. notification_calls = 0;
  1419. coordinator->on_change();
  1420. r->begin_transaction();
  1421. table->add_empty_row();
  1422. r->commit_transaction();
  1423. REQUIRE(binding_context_start_notify_calls == 1);
  1424. REQUIRE(binding_context_end_notify_calls == 1);
  1425. }
  1426. SECTION("remote commit") {
  1427. binding_context_start_notify_calls = 0;
  1428. binding_context_end_notify_calls = 0;
  1429. notification_calls = 0;
  1430. JoiningThread([&] {
  1431. auto r2 = coordinator->get_realm();
  1432. r2->begin_transaction();
  1433. auto table2 = r2->read_group().get_table("class_object");
  1434. table2->add_empty_row();
  1435. r2->commit_transaction();
  1436. });
  1437. advance_and_notify(*r);
  1438. REQUIRE(binding_context_start_notify_calls == 1);
  1439. REQUIRE(binding_context_end_notify_calls == 1);
  1440. }
  1441. }
  1442. SECTION("did_send() is skipped if the Realm is closed first") {
  1443. Results results(r, table->where());
  1444. bool do_close = true;
  1445. auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
  1446. if (do_close)
  1447. r->close();
  1448. });
  1449. struct FailOnDidSend : BindingContext {
  1450. void did_send_notifications() override
  1451. {
  1452. FAIL("did_send_notifications() should not have been called");
  1453. }
  1454. };
  1455. struct CloseOnWillChange : FailOnDidSend {
  1456. Realm& realm;
  1457. CloseOnWillChange(Realm& realm) : realm(realm) {}
  1458. void will_send_notifications() override
  1459. {
  1460. realm.close();
  1461. }
  1462. };
  1463. SECTION("closed in notification callback for notify()") {
  1464. r->m_binding_context.reset(new FailOnDidSend);
  1465. coordinator->on_change();
  1466. r->notify();
  1467. }
  1468. SECTION("closed in notification callback for refresh()") {
  1469. do_close = false;
  1470. coordinator->on_change();
  1471. r->notify();
  1472. do_close = true;
  1473. JoiningThread([&] {
  1474. auto r = coordinator->get_realm();
  1475. r->begin_transaction();
  1476. r->read_group().get_table("class_object")->add_empty_row();
  1477. r->commit_transaction();
  1478. });
  1479. r->m_binding_context.reset(new FailOnDidSend);
  1480. coordinator->on_change();
  1481. r->refresh();
  1482. }
  1483. SECTION("closed in will_send() for notify()") {
  1484. r->m_binding_context.reset(new CloseOnWillChange(*r));
  1485. coordinator->on_change();
  1486. r->notify();
  1487. }
  1488. SECTION("closed in will_send() for refresh()") {
  1489. do_close = false;
  1490. coordinator->on_change();
  1491. r->notify();
  1492. do_close = true;
  1493. JoiningThread([&] {
  1494. auto r = coordinator->get_realm();
  1495. r->begin_transaction();
  1496. r->read_group().get_table("class_object")->add_empty_row();
  1497. r->commit_transaction();
  1498. });
  1499. r->m_binding_context.reset(new CloseOnWillChange(*r));
  1500. coordinator->on_change();
  1501. r->refresh();
  1502. }
  1503. }
  1504. }
  1505. TEST_CASE("Statistics on Realms") {
  1506. _impl::RealmCoordinator::assert_no_open_realms();
  1507. InMemoryTestFile config;
  1508. config.cache = false;
  1509. config.automatic_change_notifications = false;
  1510. auto r = Realm::get_shared_realm(config);
  1511. r->update_schema({
  1512. {"object", {
  1513. {"value", PropertyType::Int}
  1514. }},
  1515. });
  1516. SECTION("compute_size") {
  1517. auto s = r->read_group().compute_aggregated_byte_size();
  1518. REQUIRE(s > 0);
  1519. }
  1520. }
  1521. #if REALM_PLATFORM_APPLE
  1522. TEST_CASE("BindingContext is notified in case of notifier errors") {
  1523. _impl::RealmCoordinator::assert_no_open_realms();
  1524. class OpenFileLimiter {
  1525. public:
  1526. OpenFileLimiter()
  1527. {
  1528. // Set the max open files to zero so that opening new files will fail
  1529. getrlimit(RLIMIT_NOFILE, &m_old);
  1530. rlimit rl = m_old;
  1531. rl.rlim_cur = 0;
  1532. setrlimit(RLIMIT_NOFILE, &rl);
  1533. }
  1534. ~OpenFileLimiter()
  1535. {
  1536. setrlimit(RLIMIT_NOFILE, &m_old);
  1537. }
  1538. private:
  1539. rlimit m_old;
  1540. };
  1541. InMemoryTestFile config;
  1542. config.cache = false;
  1543. config.automatic_change_notifications = false;
  1544. auto r = Realm::get_shared_realm(config);
  1545. r->update_schema({
  1546. {"object", {
  1547. {"value", PropertyType::Int}
  1548. }},
  1549. });
  1550. auto coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
  1551. auto table = r->read_group().get_table("class_object");
  1552. Results results(r, *r->read_group().get_table("class_object"));
  1553. static int binding_context_start_notify_calls = 0;
  1554. static int binding_context_end_notify_calls = 0;
  1555. static bool error_called = false;
  1556. struct Context : BindingContext {
  1557. void will_send_notifications() override
  1558. {
  1559. REQUIRE_FALSE(error_called);
  1560. ++binding_context_start_notify_calls;
  1561. }
  1562. void did_send_notifications() override
  1563. {
  1564. REQUIRE(error_called);
  1565. ++binding_context_end_notify_calls;
  1566. }
  1567. };
  1568. r->m_binding_context.reset(new Context());
  1569. SECTION("realm on background thread could not be opened") {
  1570. OpenFileLimiter limiter;
  1571. auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr err) {
  1572. REQUIRE(err);
  1573. REQUIRE_FALSE(error_called);
  1574. error_called = true;
  1575. });
  1576. advance_and_notify(*r);
  1577. REQUIRE(error_called);
  1578. REQUIRE(binding_context_start_notify_calls == 1);
  1579. REQUIRE(binding_context_end_notify_calls == 1);
  1580. }
  1581. }
  1582. #endif
  1583. TEST_CASE("RealmCoordinator: get_unbound_realm()") {
  1584. TestFile config;
  1585. config.schema = Schema{
  1586. {"object", {
  1587. {"value", PropertyType::Int}
  1588. }},
  1589. };
  1590. ThreadSafeReference<Realm> ref;
  1591. std::thread([&] { ref = _impl::RealmCoordinator::get_coordinator(config)->get_unbound_realm(); }).join();
  1592. SECTION("checks thread after being resolved") {
  1593. auto realm = Realm::get_shared_realm(std::move(ref));
  1594. REQUIRE_NOTHROW(realm->verify_thread());
  1595. std::thread([&] {
  1596. REQUIRE_THROWS(realm->verify_thread());
  1597. }).join();
  1598. }
  1599. SECTION("delivers notifications to the thread it is resolved on") {
  1600. if (!util::EventLoop::has_implementation())
  1601. return;
  1602. auto realm = Realm::get_shared_realm(std::move(ref));
  1603. Results results(realm, ObjectStore::table_for_object_type(realm->read_group(), "object")->where());
  1604. bool called = false;
  1605. auto token = results.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
  1606. called = true;
  1607. });
  1608. util::EventLoop::main().run_until([&] { return called; });
  1609. }
  1610. SECTION("does not check thread if resolved using an execution context") {
  1611. auto realm = Realm::get_shared_realm(std::move(ref), AbstractExecutionContextID(1));
  1612. REQUIRE_NOTHROW(realm->verify_thread());
  1613. std::thread([&] {
  1614. REQUIRE_NOTHROW(realm->verify_thread());
  1615. }).join();
  1616. }
  1617. SECTION("resolves to existing cached Realm for the thread if caching is enabled") {
  1618. auto r1 = Realm::get_shared_realm(config);
  1619. auto r2 = Realm::get_shared_realm(std::move(ref));
  1620. REQUIRE(r1 == r2);
  1621. }
  1622. SECTION("resolves to existing cached Realm for the execution context if caching is enabled") {
  1623. config.execution_context = AbstractExecutionContextID(1);
  1624. auto r1 = Realm::get_shared_realm(config);
  1625. config.execution_context = AbstractExecutionContextID(2);
  1626. auto r2 = Realm::get_shared_realm(config);
  1627. auto r3 = Realm::get_shared_realm(std::move(ref), AbstractExecutionContextID(1));
  1628. REQUIRE(r1 == r3);
  1629. REQUIRE(r1 != r2);
  1630. REQUIRE(r2 != r3);
  1631. }
  1632. SECTION("resolves to a new Realm if caching is disabled") {
  1633. // Cache disabled for local realm, enabled for unbound
  1634. config.cache = false;
  1635. auto r1 = Realm::get_shared_realm(config);
  1636. auto r2 = Realm::get_shared_realm(std::move(ref));
  1637. REQUIRE(r1 != r2);
  1638. // New unbound with cache disabled
  1639. std::thread([&] { ref = _impl::RealmCoordinator::get_coordinator(config)->get_unbound_realm(); }).join();
  1640. auto r3 = Realm::get_shared_realm(std::move(ref));
  1641. REQUIRE(r1 != r3);
  1642. REQUIRE(r2 != r3);
  1643. // New local with cache enabled should grab the resolved unbound
  1644. config.cache = true;
  1645. auto r4 = Realm::get_shared_realm(config);
  1646. REQUIRE(r4 == r2);
  1647. }
  1648. }