realm.cpp 67 KB

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