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