realm.cpp 68 KB

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