testsynccfapi.cpp 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225
  1. /*
  2. * This software is in the public domain, furnished "as is", without technical
  3. * support, and with no warranty, express or implied, as to its usefulness for
  4. * any purpose.
  5. *
  6. */
  7. #include <QtTest>
  8. #include "syncenginetestutils.h"
  9. #include "common/vfs.h"
  10. #include "config.h"
  11. #include <syncengine.h>
  12. #include "vfs/cfapi/cfapiwrapper.h"
  13. namespace cfapi {
  14. using namespace OCC::CfApiWrapper;
  15. }
  16. #define CFVERIFY_VIRTUAL(folder, path) \
  17. QVERIFY(QFileInfo((folder).localPath() + (path)).exists()); \
  18. QVERIFY(cfapi::isSparseFile((folder).localPath() + (path))); \
  19. QVERIFY(dbRecord((folder), (path)).isValid()); \
  20. QCOMPARE(dbRecord((folder), (path))._type, ItemTypeVirtualFile);
  21. #define CFVERIFY_NONVIRTUAL(folder, path) \
  22. QVERIFY(QFileInfo((folder).localPath() + (path)).exists()); \
  23. QVERIFY(!cfapi::isSparseFile((folder).localPath() + (path))); \
  24. QVERIFY(dbRecord((folder), (path)).isValid()); \
  25. QCOMPARE(dbRecord((folder), (path))._type, ItemTypeFile);
  26. #define CFVERIFY_GONE(folder, path) \
  27. QVERIFY(!QFileInfo((folder).localPath() + (path)).exists()); \
  28. QVERIFY(!dbRecord((folder), (path)).isValid());
  29. using namespace OCC;
  30. enum ErrorKind : int {
  31. NoError = 0,
  32. // Lower code are corresponding to HTTP error code
  33. Timeout = 1000,
  34. };
  35. void setPinState(const QString &path, PinState state, cfapi::SetPinRecurseMode mode)
  36. {
  37. Q_ASSERT(mode == cfapi::Recurse || mode == cfapi::NoRecurse);
  38. const auto p = QDir::toNativeSeparators(path);
  39. const auto handle = cfapi::handleForPath(p);
  40. Q_ASSERT(handle);
  41. const auto result = cfapi::setPinState(p, state, mode);
  42. Q_ASSERT(result);
  43. if (mode == cfapi::NoRecurse) {
  44. const auto result = cfapi::setPinState(p, PinState::Inherited, cfapi::ChildrenOnly);
  45. Q_ASSERT(result);
  46. }
  47. }
  48. bool itemInstruction(const ItemCompletedSpy &spy, const QString &path, const SyncInstructions instr)
  49. {
  50. auto item = spy.findItem(path);
  51. return item->_instruction == instr;
  52. }
  53. SyncJournalFileRecord dbRecord(FakeFolder &folder, const QString &path)
  54. {
  55. SyncJournalFileRecord record;
  56. folder.syncJournal().getFileRecord(path, &record);
  57. return record;
  58. }
  59. void triggerDownload(FakeFolder &folder, const QByteArray &path)
  60. {
  61. auto &journal = folder.syncJournal();
  62. SyncJournalFileRecord record;
  63. journal.getFileRecord(path, &record);
  64. if (!record.isValid())
  65. return;
  66. record._type = ItemTypeVirtualFileDownload;
  67. journal.setFileRecord(record);
  68. journal.schedulePathForRemoteDiscovery(record._path);
  69. }
  70. void markForDehydration(FakeFolder &folder, const QByteArray &path)
  71. {
  72. auto &journal = folder.syncJournal();
  73. SyncJournalFileRecord record;
  74. journal.getFileRecord(path, &record);
  75. if (!record.isValid())
  76. return;
  77. record._type = ItemTypeVirtualFileDehydration;
  78. journal.setFileRecord(record);
  79. journal.schedulePathForRemoteDiscovery(record._path);
  80. }
  81. QSharedPointer<Vfs> setupVfs(FakeFolder &folder)
  82. {
  83. auto cfapiVfs = QSharedPointer<Vfs>(createVfsFromPlugin(Vfs::WindowsCfApi).release());
  84. QObject::connect(&folder.syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged,
  85. cfapiVfs.data(), &Vfs::fileStatusChanged);
  86. folder.switchToVfs(cfapiVfs);
  87. ::setPinState(folder.localPath(), PinState::Unspecified, cfapi::NoRecurse);
  88. return cfapiVfs;
  89. }
  90. class TestSyncCfApi : public QObject
  91. {
  92. Q_OBJECT
  93. private slots:
  94. void testVirtualFileLifecycle_data()
  95. {
  96. QTest::addColumn<bool>("doLocalDiscovery");
  97. QTest::newRow("full local discovery") << true;
  98. QTest::newRow("skip local discovery") << false;
  99. }
  100. void testVirtualFileLifecycle()
  101. {
  102. QFETCH(bool, doLocalDiscovery);
  103. FakeFolder fakeFolder{ FileInfo() };
  104. setupVfs(fakeFolder);
  105. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  106. ItemCompletedSpy completeSpy(fakeFolder);
  107. auto cleanup = [&]() {
  108. completeSpy.clear();
  109. if (!doLocalDiscovery)
  110. fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem);
  111. };
  112. cleanup();
  113. // Create a virtual file for a new remote file
  114. fakeFolder.remoteModifier().mkdir("A");
  115. fakeFolder.remoteModifier().insert("A/a1", 64);
  116. auto someDate = QDateTime(QDate(1984, 07, 30), QTime(1,3,2));
  117. fakeFolder.remoteModifier().setModTime("A/a1", someDate);
  118. QVERIFY(fakeFolder.syncOnce());
  119. CFVERIFY_VIRTUAL(fakeFolder, "A/a1");
  120. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1").size(), 64);
  121. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1").lastModified(), someDate);
  122. QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
  123. QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_NEW));
  124. cleanup();
  125. // Another sync doesn't actually lead to changes
  126. QVERIFY(fakeFolder.syncOnce());
  127. CFVERIFY_VIRTUAL(fakeFolder, "A/a1");
  128. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1").size(), 64);
  129. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1").lastModified(), someDate);
  130. QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
  131. QVERIFY(completeSpy.isEmpty());
  132. cleanup();
  133. // Not even when the remote is rediscovered
  134. fakeFolder.syncJournal().forceRemoteDiscoveryNextSync();
  135. QVERIFY(fakeFolder.syncOnce());
  136. CFVERIFY_VIRTUAL(fakeFolder, "A/a1");
  137. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1").size(), 64);
  138. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1").lastModified(), someDate);
  139. QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
  140. QVERIFY(completeSpy.isEmpty());
  141. cleanup();
  142. // Neither does a remote change
  143. fakeFolder.remoteModifier().appendByte("A/a1");
  144. QVERIFY(fakeFolder.syncOnce());
  145. CFVERIFY_VIRTUAL(fakeFolder, "A/a1");
  146. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1").size(), 65);
  147. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1").lastModified(), someDate);
  148. QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
  149. QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_UPDATE_METADATA));
  150. QCOMPARE(dbRecord(fakeFolder, "A/a1")._fileSize, 65);
  151. cleanup();
  152. // If the local virtual file is removed, this will be propagated remotely
  153. if (!doLocalDiscovery)
  154. fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, { "A" });
  155. fakeFolder.localModifier().remove("A/a1");
  156. QVERIFY(fakeFolder.syncOnce());
  157. QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
  158. QVERIFY(!fakeFolder.currentRemoteState().find("A/a1"));
  159. QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_REMOVE));
  160. QVERIFY(!dbRecord(fakeFolder, "A/a1").isValid());
  161. cleanup();
  162. // Recreate a1 before carrying on with the other tests
  163. fakeFolder.remoteModifier().insert("A/a1", 65);
  164. fakeFolder.remoteModifier().setModTime("A/a1", someDate);
  165. QVERIFY(fakeFolder.syncOnce());
  166. CFVERIFY_VIRTUAL(fakeFolder, "A/a1");
  167. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1").size(), 65);
  168. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1").lastModified(), someDate);
  169. QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
  170. QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_NEW));
  171. cleanup();
  172. // Remote rename is propagated
  173. fakeFolder.remoteModifier().rename("A/a1", "A/a1m");
  174. QVERIFY(fakeFolder.syncOnce());
  175. QVERIFY(!QFileInfo(fakeFolder.localPath() + "A/a1").exists());
  176. CFVERIFY_VIRTUAL(fakeFolder, "A/a1m");
  177. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1m").size(), 65);
  178. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1m").lastModified(), someDate);
  179. QVERIFY(!fakeFolder.currentRemoteState().find("A/a1"));
  180. QVERIFY(fakeFolder.currentRemoteState().find("A/a1m"));
  181. QVERIFY(
  182. itemInstruction(completeSpy, "A/a1m", CSYNC_INSTRUCTION_RENAME)
  183. || (itemInstruction(completeSpy, "A/a1m", CSYNC_INSTRUCTION_NEW)
  184. && itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_REMOVE)));
  185. QVERIFY(!dbRecord(fakeFolder, "A/a1").isValid());
  186. cleanup();
  187. // Remote remove is propagated
  188. fakeFolder.remoteModifier().remove("A/a1m");
  189. QVERIFY(fakeFolder.syncOnce());
  190. QVERIFY(!QFileInfo(fakeFolder.localPath() + "A/a1m").exists());
  191. QVERIFY(!fakeFolder.currentRemoteState().find("A/a1m"));
  192. QVERIFY(itemInstruction(completeSpy, "A/a1m", CSYNC_INSTRUCTION_REMOVE));
  193. QVERIFY(!dbRecord(fakeFolder, "A/a1").isValid());
  194. QVERIFY(!dbRecord(fakeFolder, "A/a1m").isValid());
  195. cleanup();
  196. // Edge case: Local virtual file but no db entry for some reason
  197. fakeFolder.remoteModifier().insert("A/a2", 32);
  198. fakeFolder.remoteModifier().insert("A/a3", 33);
  199. QVERIFY(fakeFolder.syncOnce());
  200. CFVERIFY_VIRTUAL(fakeFolder, "A/a2");
  201. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a2").size(), 32);
  202. CFVERIFY_VIRTUAL(fakeFolder, "A/a3");
  203. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a3").size(), 33);
  204. cleanup();
  205. fakeFolder.syncEngine().journal()->deleteFileRecord("A/a2");
  206. fakeFolder.syncEngine().journal()->deleteFileRecord("A/a3");
  207. fakeFolder.remoteModifier().remove("A/a3");
  208. fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::FilesystemOnly);
  209. QVERIFY(fakeFolder.syncOnce());
  210. CFVERIFY_VIRTUAL(fakeFolder, "A/a2");
  211. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a2").size(), 32);
  212. QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_UPDATE_METADATA));
  213. QVERIFY(!QFileInfo(fakeFolder.localPath() + "A/a3").exists());
  214. QVERIFY(itemInstruction(completeSpy, "A/a3", CSYNC_INSTRUCTION_REMOVE));
  215. QVERIFY(!dbRecord(fakeFolder, "A/a3").isValid());
  216. cleanup();
  217. }
  218. void testVirtualFileConflict()
  219. {
  220. FakeFolder fakeFolder{ FileInfo() };
  221. setupVfs(fakeFolder);
  222. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  223. ItemCompletedSpy completeSpy(fakeFolder);
  224. auto cleanup = [&]() {
  225. completeSpy.clear();
  226. };
  227. cleanup();
  228. // Create a virtual file for a new remote file
  229. fakeFolder.remoteModifier().mkdir("A");
  230. fakeFolder.remoteModifier().insert("A/a1", 11);
  231. fakeFolder.remoteModifier().insert("A/a2", 12);
  232. fakeFolder.remoteModifier().mkdir("B");
  233. fakeFolder.remoteModifier().insert("B/b1", 21);
  234. QVERIFY(fakeFolder.syncOnce());
  235. CFVERIFY_VIRTUAL(fakeFolder, "A/a1");
  236. CFVERIFY_VIRTUAL(fakeFolder, "A/a2");
  237. CFVERIFY_VIRTUAL(fakeFolder, "B/b1");
  238. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1").size(), 11);
  239. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a2").size(), 12);
  240. QCOMPARE(QFileInfo(fakeFolder.localPath() + "B/b1").size(), 21);
  241. cleanup();
  242. // All the files are touched on the server
  243. fakeFolder.remoteModifier().appendByte("A/a1");
  244. fakeFolder.remoteModifier().appendByte("A/a2");
  245. fakeFolder.remoteModifier().appendByte("B/b1");
  246. // A: the correct file and a conflicting file are added
  247. // B: user adds a *directory* locally
  248. fakeFolder.localModifier().remove("A/a1");
  249. fakeFolder.localModifier().insert("A/a1", 12);
  250. fakeFolder.localModifier().remove("A/a2");
  251. fakeFolder.localModifier().insert("A/a2", 10);
  252. fakeFolder.localModifier().remove("B/b1");
  253. fakeFolder.localModifier().mkdir("B/b1");
  254. fakeFolder.localModifier().insert("B/b1/foo");
  255. QVERIFY(fakeFolder.syncOnce());
  256. // Everything is CONFLICT
  257. QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_CONFLICT));
  258. QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_CONFLICT));
  259. QVERIFY(itemInstruction(completeSpy, "B/b1", CSYNC_INSTRUCTION_CONFLICT));
  260. // conflict files should exist
  261. QCOMPARE(fakeFolder.syncJournal().conflictRecordPaths().size(), 2);
  262. // nothing should have the virtual file tag
  263. CFVERIFY_NONVIRTUAL(fakeFolder, "A/a1");
  264. CFVERIFY_NONVIRTUAL(fakeFolder, "A/a2");
  265. CFVERIFY_NONVIRTUAL(fakeFolder, "B/b1");
  266. cleanup();
  267. }
  268. void testWithNormalSync()
  269. {
  270. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  271. setupVfs(fakeFolder);
  272. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  273. ItemCompletedSpy completeSpy(fakeFolder);
  274. auto cleanup = [&]() {
  275. completeSpy.clear();
  276. };
  277. cleanup();
  278. // No effect sync
  279. QVERIFY(fakeFolder.syncOnce());
  280. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  281. cleanup();
  282. // Existing files are propagated just fine in both directions
  283. fakeFolder.localModifier().appendByte("A/a1");
  284. fakeFolder.localModifier().insert("A/a3");
  285. fakeFolder.remoteModifier().appendByte("A/a2");
  286. QVERIFY(fakeFolder.syncOnce());
  287. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  288. cleanup();
  289. // New files on the remote create virtual files
  290. fakeFolder.remoteModifier().insert("A/new", 42);
  291. QVERIFY(fakeFolder.syncOnce());
  292. CFVERIFY_VIRTUAL(fakeFolder, "A/new");
  293. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/new").size(), 42);
  294. QVERIFY(fakeFolder.currentRemoteState().find("A/new"));
  295. QVERIFY(itemInstruction(completeSpy, "A/new", CSYNC_INSTRUCTION_NEW));
  296. cleanup();
  297. }
  298. void testVirtualFileDownload()
  299. {
  300. FakeFolder fakeFolder{ FileInfo() };
  301. setupVfs(fakeFolder);
  302. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  303. ItemCompletedSpy completeSpy(fakeFolder);
  304. auto cleanup = [&]() {
  305. completeSpy.clear();
  306. };
  307. cleanup();
  308. // Create a virtual file for remote files
  309. fakeFolder.remoteModifier().mkdir("A");
  310. fakeFolder.remoteModifier().insert("A/a1");
  311. fakeFolder.remoteModifier().insert("A/a2");
  312. fakeFolder.remoteModifier().insert("A/a3");
  313. fakeFolder.remoteModifier().insert("A/a4");
  314. fakeFolder.remoteModifier().insert("A/a5");
  315. fakeFolder.remoteModifier().insert("A/a6");
  316. fakeFolder.remoteModifier().insert("A/a7");
  317. fakeFolder.remoteModifier().insert("A/b1");
  318. fakeFolder.remoteModifier().insert("A/b2");
  319. fakeFolder.remoteModifier().insert("A/b3");
  320. fakeFolder.remoteModifier().insert("A/b4");
  321. QVERIFY(fakeFolder.syncOnce());
  322. CFVERIFY_VIRTUAL(fakeFolder, "A/a1");
  323. CFVERIFY_VIRTUAL(fakeFolder, "A/a2");
  324. CFVERIFY_VIRTUAL(fakeFolder, "A/a3");
  325. CFVERIFY_VIRTUAL(fakeFolder, "A/a4");
  326. CFVERIFY_VIRTUAL(fakeFolder, "A/a5");
  327. CFVERIFY_VIRTUAL(fakeFolder, "A/a6");
  328. CFVERIFY_VIRTUAL(fakeFolder, "A/a7");
  329. CFVERIFY_VIRTUAL(fakeFolder, "A/b1");
  330. CFVERIFY_VIRTUAL(fakeFolder, "A/b2");
  331. CFVERIFY_VIRTUAL(fakeFolder, "A/b3");
  332. CFVERIFY_VIRTUAL(fakeFolder, "A/b4");
  333. cleanup();
  334. // Download by changing the db entry
  335. triggerDownload(fakeFolder, "A/a1");
  336. triggerDownload(fakeFolder, "A/a2");
  337. triggerDownload(fakeFolder, "A/a3");
  338. triggerDownload(fakeFolder, "A/a4");
  339. triggerDownload(fakeFolder, "A/a5");
  340. triggerDownload(fakeFolder, "A/a6");
  341. triggerDownload(fakeFolder, "A/a7");
  342. triggerDownload(fakeFolder, "A/b1");
  343. triggerDownload(fakeFolder, "A/b2");
  344. triggerDownload(fakeFolder, "A/b3");
  345. triggerDownload(fakeFolder, "A/b4");
  346. // Remote complications
  347. fakeFolder.remoteModifier().appendByte("A/a2");
  348. fakeFolder.remoteModifier().remove("A/a3");
  349. fakeFolder.remoteModifier().rename("A/a4", "A/a4m");
  350. fakeFolder.remoteModifier().appendByte("A/b2");
  351. fakeFolder.remoteModifier().remove("A/b3");
  352. fakeFolder.remoteModifier().rename("A/b4", "A/b4m");
  353. // Local complications
  354. fakeFolder.localModifier().remove("A/a5");
  355. fakeFolder.localModifier().insert("A/a5");
  356. fakeFolder.localModifier().remove("A/a6");
  357. fakeFolder.localModifier().insert("A/a6");
  358. QVERIFY(fakeFolder.syncOnce());
  359. QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_SYNC));
  360. QCOMPARE(completeSpy.findItem("A/a1")->_type, ItemTypeVirtualFileDownload);
  361. QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_SYNC));
  362. QCOMPARE(completeSpy.findItem("A/a2")->_type, ItemTypeVirtualFileDownload);
  363. QVERIFY(itemInstruction(completeSpy, "A/a3", CSYNC_INSTRUCTION_REMOVE));
  364. QVERIFY(itemInstruction(completeSpy, "A/a4m", CSYNC_INSTRUCTION_NEW));
  365. QVERIFY(itemInstruction(completeSpy, "A/a4", CSYNC_INSTRUCTION_REMOVE));
  366. QVERIFY(itemInstruction(completeSpy, "A/a5", CSYNC_INSTRUCTION_CONFLICT));
  367. QVERIFY(itemInstruction(completeSpy, "A/a6", CSYNC_INSTRUCTION_CONFLICT));
  368. QVERIFY(itemInstruction(completeSpy, "A/a7", CSYNC_INSTRUCTION_SYNC));
  369. QVERIFY(itemInstruction(completeSpy, "A/b1", CSYNC_INSTRUCTION_SYNC));
  370. QVERIFY(itemInstruction(completeSpy, "A/b2", CSYNC_INSTRUCTION_SYNC));
  371. QVERIFY(itemInstruction(completeSpy, "A/b3", CSYNC_INSTRUCTION_REMOVE));
  372. QVERIFY(itemInstruction(completeSpy, "A/b4m", CSYNC_INSTRUCTION_NEW));
  373. QVERIFY(itemInstruction(completeSpy, "A/b4", CSYNC_INSTRUCTION_REMOVE));
  374. CFVERIFY_NONVIRTUAL(fakeFolder, "A/a1");
  375. CFVERIFY_NONVIRTUAL(fakeFolder, "A/a2");
  376. CFVERIFY_GONE(fakeFolder, "A/a3");
  377. CFVERIFY_GONE(fakeFolder, "A/a4");
  378. CFVERIFY_NONVIRTUAL(fakeFolder, "A/a4m");
  379. CFVERIFY_NONVIRTUAL(fakeFolder, "A/a5");
  380. CFVERIFY_NONVIRTUAL(fakeFolder, "A/a6");
  381. CFVERIFY_NONVIRTUAL(fakeFolder, "A/a7");
  382. CFVERIFY_NONVIRTUAL(fakeFolder, "A/b1");
  383. CFVERIFY_NONVIRTUAL(fakeFolder, "A/b2");
  384. CFVERIFY_GONE(fakeFolder, "A/b3");
  385. CFVERIFY_GONE(fakeFolder, "A/b4");
  386. CFVERIFY_NONVIRTUAL(fakeFolder, "A/b4m");
  387. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  388. }
  389. void testVirtualFileDownloadResume()
  390. {
  391. FakeFolder fakeFolder{ FileInfo() };
  392. setupVfs(fakeFolder);
  393. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  394. ItemCompletedSpy completeSpy(fakeFolder);
  395. auto cleanup = [&]() {
  396. completeSpy.clear();
  397. fakeFolder.syncJournal().wipeErrorBlacklist();
  398. };
  399. cleanup();
  400. // Create a virtual file for remote files
  401. fakeFolder.remoteModifier().mkdir("A");
  402. fakeFolder.remoteModifier().insert("A/a1");
  403. QVERIFY(fakeFolder.syncOnce());
  404. CFVERIFY_VIRTUAL(fakeFolder, "A/a1");
  405. cleanup();
  406. // Download by changing the db entry
  407. triggerDownload(fakeFolder, "A/a1");
  408. fakeFolder.serverErrorPaths().append("A/a1", 500);
  409. QVERIFY(!fakeFolder.syncOnce());
  410. QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_SYNC));
  411. QVERIFY(cfapi::isSparseFile(fakeFolder.localPath() + "A/a1"));
  412. QVERIFY(QFileInfo(fakeFolder.localPath() + "A/a1").exists());
  413. QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeVirtualFileDownload);
  414. cleanup();
  415. fakeFolder.serverErrorPaths().clear();
  416. QVERIFY(fakeFolder.syncOnce());
  417. QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_SYNC));
  418. CFVERIFY_NONVIRTUAL(fakeFolder, "A/a1");
  419. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  420. }
  421. void testNewFilesNotVirtual()
  422. {
  423. FakeFolder fakeFolder{ FileInfo() };
  424. setupVfs(fakeFolder);
  425. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  426. fakeFolder.remoteModifier().mkdir("A");
  427. fakeFolder.remoteModifier().insert("A/a1");
  428. QVERIFY(fakeFolder.syncOnce());
  429. CFVERIFY_VIRTUAL(fakeFolder, "A/a1");
  430. ::setPinState(fakeFolder.localPath(), PinState::AlwaysLocal, cfapi::NoRecurse);
  431. // Create a new remote file, it'll not be virtual
  432. fakeFolder.remoteModifier().insert("A/a2");
  433. QVERIFY(fakeFolder.syncOnce());
  434. CFVERIFY_NONVIRTUAL(fakeFolder, "A/a2");
  435. }
  436. void testDownloadRecursive()
  437. {
  438. FakeFolder fakeFolder{ FileInfo() };
  439. setupVfs(fakeFolder);
  440. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  441. // Create a virtual file for remote files
  442. fakeFolder.remoteModifier().mkdir("A");
  443. fakeFolder.remoteModifier().mkdir("A/Sub");
  444. fakeFolder.remoteModifier().mkdir("A/Sub/SubSub");
  445. fakeFolder.remoteModifier().mkdir("A/Sub2");
  446. fakeFolder.remoteModifier().mkdir("B");
  447. fakeFolder.remoteModifier().mkdir("B/Sub");
  448. fakeFolder.remoteModifier().insert("A/a1");
  449. fakeFolder.remoteModifier().insert("A/a2");
  450. fakeFolder.remoteModifier().insert("A/Sub/a3");
  451. fakeFolder.remoteModifier().insert("A/Sub/a4");
  452. fakeFolder.remoteModifier().insert("A/Sub/SubSub/a5");
  453. fakeFolder.remoteModifier().insert("A/Sub2/a6");
  454. fakeFolder.remoteModifier().insert("B/b1");
  455. fakeFolder.remoteModifier().insert("B/Sub/b2");
  456. QVERIFY(fakeFolder.syncOnce());
  457. CFVERIFY_VIRTUAL(fakeFolder, "A/a1");
  458. CFVERIFY_VIRTUAL(fakeFolder, "A/a2");
  459. CFVERIFY_VIRTUAL(fakeFolder, "A/Sub/a3");
  460. CFVERIFY_VIRTUAL(fakeFolder, "A/Sub/a4");
  461. CFVERIFY_VIRTUAL(fakeFolder, "A/Sub/SubSub/a5");
  462. CFVERIFY_VIRTUAL(fakeFolder, "A/Sub2/a6");
  463. CFVERIFY_VIRTUAL(fakeFolder, "B/b1");
  464. CFVERIFY_VIRTUAL(fakeFolder, "B/Sub/b2");
  465. // Download All file in the directory A/Sub
  466. // (as in Folder::downloadVirtualFile)
  467. fakeFolder.syncJournal().markVirtualFileForDownloadRecursively("A/Sub");
  468. QVERIFY(fakeFolder.syncOnce());
  469. CFVERIFY_VIRTUAL(fakeFolder, "A/a1");
  470. CFVERIFY_VIRTUAL(fakeFolder, "A/a2");
  471. CFVERIFY_NONVIRTUAL(fakeFolder, "A/Sub/a3");
  472. CFVERIFY_NONVIRTUAL(fakeFolder, "A/Sub/a4");
  473. CFVERIFY_NONVIRTUAL(fakeFolder, "A/Sub/SubSub/a5");
  474. CFVERIFY_VIRTUAL(fakeFolder, "A/Sub2/a6");
  475. CFVERIFY_VIRTUAL(fakeFolder, "B/b1");
  476. CFVERIFY_VIRTUAL(fakeFolder, "B/Sub/b2");
  477. // Add a file in a subfolder that was downloaded
  478. // Currently, this continue to add it as a virtual file.
  479. fakeFolder.remoteModifier().insert("A/Sub/SubSub/a7");
  480. QVERIFY(fakeFolder.syncOnce());
  481. CFVERIFY_VIRTUAL(fakeFolder, "A/Sub/SubSub/a7");
  482. // Now download all files in "A"
  483. fakeFolder.syncJournal().markVirtualFileForDownloadRecursively("A");
  484. QVERIFY(fakeFolder.syncOnce());
  485. CFVERIFY_NONVIRTUAL(fakeFolder, "A/a1");
  486. CFVERIFY_NONVIRTUAL(fakeFolder, "A/a2");
  487. CFVERIFY_NONVIRTUAL(fakeFolder, "A/Sub/a3");
  488. CFVERIFY_NONVIRTUAL(fakeFolder, "A/Sub/a4");
  489. CFVERIFY_NONVIRTUAL(fakeFolder, "A/Sub/SubSub/a5");
  490. CFVERIFY_NONVIRTUAL(fakeFolder, "A/Sub/SubSub/a7");
  491. CFVERIFY_NONVIRTUAL(fakeFolder, "A/Sub2/a6");
  492. CFVERIFY_VIRTUAL(fakeFolder, "B/b1");
  493. CFVERIFY_VIRTUAL(fakeFolder, "B/Sub/b2");
  494. // Now download remaining files in "B"
  495. fakeFolder.syncJournal().markVirtualFileForDownloadRecursively("B");
  496. QVERIFY(fakeFolder.syncOnce());
  497. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  498. }
  499. void testRenameVirtual()
  500. {
  501. FakeFolder fakeFolder{ FileInfo() };
  502. setupVfs(fakeFolder);
  503. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  504. ItemCompletedSpy completeSpy(fakeFolder);
  505. auto cleanup = [&]() {
  506. completeSpy.clear();
  507. };
  508. cleanup();
  509. fakeFolder.remoteModifier().insert("file1", 128, 'C');
  510. fakeFolder.remoteModifier().insert("file2", 256, 'C');
  511. fakeFolder.remoteModifier().insert("file3", 256, 'C');
  512. QVERIFY(fakeFolder.syncOnce());
  513. CFVERIFY_VIRTUAL(fakeFolder, "file1");
  514. CFVERIFY_VIRTUAL(fakeFolder, "file2");
  515. CFVERIFY_VIRTUAL(fakeFolder, "file3");
  516. cleanup();
  517. fakeFolder.localModifier().rename("file1", "renamed1");
  518. fakeFolder.localModifier().rename("file2", "renamed2");
  519. triggerDownload(fakeFolder, "file2");
  520. triggerDownload(fakeFolder, "file3");
  521. QVERIFY(fakeFolder.syncOnce());
  522. CFVERIFY_GONE(fakeFolder, "file1");
  523. CFVERIFY_VIRTUAL(fakeFolder, "renamed1");
  524. QVERIFY(fakeFolder.currentRemoteState().find("renamed1"));
  525. QVERIFY(itemInstruction(completeSpy, "renamed1", CSYNC_INSTRUCTION_RENAME));
  526. // file2 has a conflict between the download request and the rename:
  527. // the rename wins, the download is ignored
  528. CFVERIFY_GONE(fakeFolder, "file2");
  529. CFVERIFY_VIRTUAL(fakeFolder, "renamed2");
  530. QVERIFY(fakeFolder.currentRemoteState().find("renamed2"));
  531. QVERIFY(itemInstruction(completeSpy, "renamed2", CSYNC_INSTRUCTION_RENAME));
  532. QVERIFY(itemInstruction(completeSpy, "file3", CSYNC_INSTRUCTION_SYNC));
  533. CFVERIFY_NONVIRTUAL(fakeFolder, "file3");
  534. cleanup();
  535. }
  536. void testRenameVirtual2()
  537. {
  538. FakeFolder fakeFolder{ FileInfo() };
  539. setupVfs(fakeFolder);
  540. ItemCompletedSpy completeSpy(fakeFolder);
  541. auto cleanup = [&]() {
  542. completeSpy.clear();
  543. };
  544. cleanup();
  545. fakeFolder.remoteModifier().insert("case3", 128, 'C');
  546. fakeFolder.remoteModifier().insert("case4", 256, 'C');
  547. QVERIFY(fakeFolder.syncOnce());
  548. triggerDownload(fakeFolder, "case4");
  549. QVERIFY(fakeFolder.syncOnce());
  550. CFVERIFY_VIRTUAL(fakeFolder, "case3");
  551. CFVERIFY_NONVIRTUAL(fakeFolder, "case4");
  552. cleanup();
  553. // Case 1: non-virtual, foo -> bar (tested elsewhere)
  554. // Case 2: virtual, foo -> bar (tested elsewhere)
  555. // Case 3: virtual, foo.oc -> bar.oc (db hydrate)
  556. fakeFolder.localModifier().rename("case3", "case3-rename");
  557. triggerDownload(fakeFolder, "case3");
  558. // Case 4: non-virtual foo -> bar (db dehydrate)
  559. fakeFolder.localModifier().rename("case4", "case4-rename");
  560. markForDehydration(fakeFolder, "case4");
  561. QVERIFY(fakeFolder.syncOnce());
  562. // Case 3: the rename went though, hydration is forgotten
  563. CFVERIFY_GONE(fakeFolder, "case3");
  564. CFVERIFY_VIRTUAL(fakeFolder, "case3-rename");
  565. QVERIFY(!fakeFolder.currentRemoteState().find("case3"));
  566. QVERIFY(fakeFolder.currentRemoteState().find("case3-rename"));
  567. QVERIFY(itemInstruction(completeSpy, "case3-rename", CSYNC_INSTRUCTION_RENAME));
  568. // Case 4: the rename went though, dehydration is forgotten
  569. CFVERIFY_GONE(fakeFolder, "case4");
  570. CFVERIFY_NONVIRTUAL(fakeFolder, "case4-rename");
  571. QVERIFY(!fakeFolder.currentRemoteState().find("case4"));
  572. QVERIFY(fakeFolder.currentRemoteState().find("case4-rename"));
  573. QVERIFY(itemInstruction(completeSpy, "case4-rename", CSYNC_INSTRUCTION_RENAME));
  574. }
  575. // Dehydration via sync works
  576. void testSyncDehydration()
  577. {
  578. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  579. // empty files would not work, so, we're gonna remove and re-insert them with 1MB data
  580. fakeFolder.remoteModifier().remove("A/a1");
  581. fakeFolder.remoteModifier().remove("A/a2");
  582. fakeFolder.remoteModifier().insert("A/a1", 1024 * 1024);
  583. fakeFolder.remoteModifier().insert("A/a2", 1024 * 1024);
  584. setupVfs(fakeFolder);
  585. QVERIFY(fakeFolder.syncOnce());
  586. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  587. ItemCompletedSpy completeSpy(fakeFolder);
  588. auto cleanup = [&]() {
  589. completeSpy.clear();
  590. };
  591. cleanup();
  592. //
  593. // Mark for dehydration and check
  594. //
  595. QVERIFY(fakeFolder.currentLocalState().find("A/a1"));
  596. markForDehydration(fakeFolder, "A/a1");
  597. markForDehydration(fakeFolder, "A/a2");
  598. fakeFolder.remoteModifier().appendByte("A/a2");
  599. // expect: normal dehydration
  600. markForDehydration(fakeFolder, "B/b1");
  601. fakeFolder.remoteModifier().remove("B/b1");
  602. // expect: local removal
  603. markForDehydration(fakeFolder, "B/b2");
  604. fakeFolder.remoteModifier().rename("B/b2", "B/b3");
  605. // expect: B/b2 is gone, B/b3 is NEW placeholder
  606. markForDehydration(fakeFolder, "C/c1");
  607. fakeFolder.localModifier().appendByte("C/c1");
  608. // expect: no dehydration, upload of c1
  609. markForDehydration(fakeFolder, "C/c2");
  610. fakeFolder.localModifier().appendByte("C/c2");
  611. fakeFolder.remoteModifier().appendByte("C/c2");
  612. fakeFolder.remoteModifier().appendByte("C/c2");
  613. // expect: no dehydration, conflict
  614. QVERIFY(fakeFolder.syncOnce());
  615. auto isDehydrated = [&](const QString &path) {
  616. return cfapi::isSparseFile(fakeFolder.localPath() + path)
  617. && QFileInfo(fakeFolder.localPath() + path).exists();
  618. };
  619. auto hasDehydratedDbEntries = [&](const QString &path) {
  620. SyncJournalFileRecord rec;
  621. fakeFolder.syncJournal().getFileRecord(path, &rec);
  622. return rec.isValid() && rec._type == ItemTypeVirtualFile;
  623. };
  624. QVERIFY(isDehydrated("A/a1"));
  625. QVERIFY(hasDehydratedDbEntries("A/a1"));
  626. QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_SYNC));
  627. QCOMPARE(completeSpy.findItem("A/a1")->_type, ItemTypeVirtualFileDehydration);
  628. QCOMPARE(completeSpy.findItem("A/a1")->_file, QStringLiteral("A/a1"));
  629. QVERIFY(isDehydrated("A/a2"));
  630. QVERIFY(hasDehydratedDbEntries("A/a2"));
  631. QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_SYNC));
  632. QCOMPARE(completeSpy.findItem("A/a2")->_type, ItemTypeVirtualFileDehydration);
  633. QVERIFY(!QFileInfo(fakeFolder.localPath() + "B/b1").exists());
  634. QVERIFY(!fakeFolder.currentRemoteState().find("B/b1"));
  635. QVERIFY(itemInstruction(completeSpy, "B/b1", CSYNC_INSTRUCTION_REMOVE));
  636. QVERIFY(!QFileInfo(fakeFolder.localPath() + "B/b2").exists());
  637. QVERIFY(!fakeFolder.currentRemoteState().find("B/b2"));
  638. QVERIFY(isDehydrated("B/b3"));
  639. QVERIFY(hasDehydratedDbEntries("B/b3"));
  640. QVERIFY(itemInstruction(completeSpy, "B/b2", CSYNC_INSTRUCTION_REMOVE));
  641. QVERIFY(itemInstruction(completeSpy, "B/b3", CSYNC_INSTRUCTION_NEW));
  642. QCOMPARE(fakeFolder.currentRemoteState().find("C/c1")->size, 25);
  643. QVERIFY(itemInstruction(completeSpy, "C/c1", CSYNC_INSTRUCTION_SYNC));
  644. QCOMPARE(fakeFolder.currentRemoteState().find("C/c2")->size, 26);
  645. QVERIFY(itemInstruction(completeSpy, "C/c2", CSYNC_INSTRUCTION_CONFLICT));
  646. cleanup();
  647. auto expectedRemoteState = fakeFolder.currentRemoteState();
  648. QVERIFY(fakeFolder.syncOnce());
  649. QCOMPARE(fakeFolder.currentRemoteState(), expectedRemoteState);
  650. QVERIFY(isDehydrated("A/a1"));
  651. QVERIFY(hasDehydratedDbEntries("A/a1"));
  652. QVERIFY(isDehydrated("A/a2"));
  653. QVERIFY(hasDehydratedDbEntries("A/a2"));
  654. QVERIFY(!QFileInfo(fakeFolder.localPath() + "B/b1").exists());
  655. QVERIFY(!QFileInfo(fakeFolder.localPath() + "B/b2").exists());
  656. QVERIFY(isDehydrated("B/b3"));
  657. QVERIFY(hasDehydratedDbEntries("B/b3"));
  658. QVERIFY(QFileInfo(fakeFolder.localPath() + "C/c1").exists());
  659. QVERIFY(dbRecord(fakeFolder, "C/c1").isValid());
  660. QVERIFY(!isDehydrated("C/c1"));
  661. QVERIFY(!hasDehydratedDbEntries("C/c1"));
  662. QVERIFY(QFileInfo(fakeFolder.localPath() + "C/c2").exists());
  663. QVERIFY(dbRecord(fakeFolder, "C/c2").isValid());
  664. QVERIFY(!isDehydrated("C/c2"));
  665. QVERIFY(!hasDehydratedDbEntries("C/c2"));
  666. }
  667. void testWipeVirtualSuffixFiles()
  668. {
  669. // TODO: Part of this test related to A/a3 is always failing on CI but never fails locally
  670. // I had to comment it out as this prevents from running all other tests with no working ways to fix that
  671. FakeFolder fakeFolder{ FileInfo{} };
  672. setupVfs(fakeFolder);
  673. // Create a suffix-vfs baseline
  674. fakeFolder.remoteModifier().mkdir("A");
  675. fakeFolder.remoteModifier().mkdir("A/B");
  676. fakeFolder.remoteModifier().insert("f1");
  677. fakeFolder.remoteModifier().insert("A/a1");
  678. // fakeFolder.remoteModifier().insert("A/a3");
  679. fakeFolder.remoteModifier().insert("A/B/b1");
  680. fakeFolder.localModifier().mkdir("A");
  681. fakeFolder.localModifier().mkdir("A/B");
  682. fakeFolder.localModifier().insert("f2");
  683. fakeFolder.localModifier().insert("A/a2");
  684. fakeFolder.localModifier().insert("A/B/b2");
  685. QVERIFY(fakeFolder.syncOnce());
  686. CFVERIFY_VIRTUAL(fakeFolder, "f1");
  687. CFVERIFY_VIRTUAL(fakeFolder, "A/a1");
  688. // CFVERIFY_VIRTUAL(fakeFolder, "A/a3");
  689. CFVERIFY_VIRTUAL(fakeFolder, "A/B/b1");
  690. // Make local changes to a3
  691. // fakeFolder.localModifier().remove("A/a3");
  692. // fakeFolder.localModifier().insert("A/a3", 100);
  693. // Now wipe the virtuals
  694. SyncEngine::wipeVirtualFiles(fakeFolder.localPath(), fakeFolder.syncJournal(), *fakeFolder.syncEngine().syncOptions()._vfs);
  695. CFVERIFY_GONE(fakeFolder, "f1");
  696. CFVERIFY_GONE(fakeFolder, "A/a1");
  697. //QVERIFY(QFileInfo(fakeFolder.localPath() + "A/a3").exists());
  698. // QVERIFY(!dbRecord(fakeFolder, "A/a3").isValid());
  699. CFVERIFY_GONE(fakeFolder, "A/B/b1");
  700. fakeFolder.switchToVfs(QSharedPointer<Vfs>(new VfsOff));
  701. // ItemCompletedSpy completeSpy(fakeFolder);
  702. QVERIFY(fakeFolder.syncOnce());
  703. QVERIFY(fakeFolder.currentLocalState().find("A"));
  704. QVERIFY(fakeFolder.currentLocalState().find("A/B"));
  705. QVERIFY(fakeFolder.currentLocalState().find("A/B/b1"));
  706. QVERIFY(fakeFolder.currentLocalState().find("A/B/b2"));
  707. QVERIFY(fakeFolder.currentLocalState().find("A/a1"));
  708. QVERIFY(fakeFolder.currentLocalState().find("A/a2"));
  709. // QVERIFY(fakeFolder.currentLocalState().find("A/a3"));
  710. QVERIFY(fakeFolder.currentLocalState().find("f1"));
  711. QVERIFY(fakeFolder.currentLocalState().find("f2"));
  712. // a3 has a conflict
  713. // QVERIFY(itemInstruction(completeSpy, "A/a3", CSYNC_INSTRUCTION_CONFLICT));
  714. // conflict files should exist
  715. // QCOMPARE(fakeFolder.syncJournal().conflictRecordPaths().size(), 1);
  716. }
  717. void testNewVirtuals()
  718. {
  719. FakeFolder fakeFolder{ FileInfo() };
  720. setupVfs(fakeFolder);
  721. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  722. fakeFolder.remoteModifier().mkdir("local");
  723. fakeFolder.remoteModifier().mkdir("online");
  724. fakeFolder.remoteModifier().mkdir("unspec");
  725. QVERIFY(fakeFolder.syncOnce());
  726. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  727. ::setPinState(fakeFolder.localPath() + "local", PinState::AlwaysLocal, cfapi::Recurse);
  728. ::setPinState(fakeFolder.localPath() + "online", PinState::OnlineOnly, cfapi::Recurse);
  729. ::setPinState(fakeFolder.localPath() + "unspec", PinState::Unspecified, cfapi::Recurse);
  730. // Test 1: root is Unspecified
  731. fakeFolder.remoteModifier().insert("file1", 1024 * 1024);
  732. fakeFolder.remoteModifier().insert("online/file1", 1024 * 1024);
  733. fakeFolder.remoteModifier().insert("local/file1", 1024 * 1024);
  734. fakeFolder.remoteModifier().insert("unspec/file1", 1024 * 1024);
  735. QVERIFY(fakeFolder.syncOnce());
  736. CFVERIFY_VIRTUAL(fakeFolder, "file1");
  737. CFVERIFY_VIRTUAL(fakeFolder, "online/file1");
  738. CFVERIFY_NONVIRTUAL(fakeFolder, "local/file1");
  739. CFVERIFY_VIRTUAL(fakeFolder, "unspec/file1");
  740. // Test 2: change root to AlwaysLocal
  741. ::setPinState(fakeFolder.localPath(), PinState::AlwaysLocal, cfapi::Recurse);
  742. // Need to force pin state for the subfolders again
  743. ::setPinState(fakeFolder.localPath() + "local", PinState::AlwaysLocal, cfapi::Recurse);
  744. ::setPinState(fakeFolder.localPath() + "online", PinState::OnlineOnly, cfapi::Recurse);
  745. ::setPinState(fakeFolder.localPath() + "unspec", PinState::Unspecified, cfapi::Recurse);
  746. fakeFolder.remoteModifier().insert("file2", 1024 * 1024);
  747. fakeFolder.remoteModifier().insert("online/file2", 1024 * 1024);
  748. fakeFolder.remoteModifier().insert("local/file2", 1024 * 1024);
  749. fakeFolder.remoteModifier().insert("unspec/file2", 1024 * 1024);
  750. QVERIFY(fakeFolder.syncOnce());
  751. CFVERIFY_NONVIRTUAL(fakeFolder, "file2");
  752. CFVERIFY_VIRTUAL(fakeFolder, "online/file2");
  753. CFVERIFY_NONVIRTUAL(fakeFolder, "local/file2");
  754. CFVERIFY_VIRTUAL(fakeFolder, "unspec/file2");
  755. // root file1 was hydrated due to its new pin state
  756. CFVERIFY_NONVIRTUAL(fakeFolder, "file1");
  757. // file1 is unchanged in the explicitly pinned subfolders
  758. CFVERIFY_VIRTUAL(fakeFolder, "online/file1");
  759. CFVERIFY_NONVIRTUAL(fakeFolder, "local/file1");
  760. CFVERIFY_VIRTUAL(fakeFolder, "unspec/file1");
  761. // Test 3: change root to OnlineOnly
  762. ::setPinState(fakeFolder.localPath(), PinState::OnlineOnly, cfapi::Recurse);
  763. // Need to force pin state for the subfolders again
  764. ::setPinState(fakeFolder.localPath() + "local", PinState::AlwaysLocal, cfapi::Recurse);
  765. ::setPinState(fakeFolder.localPath() + "online", PinState::OnlineOnly, cfapi::Recurse);
  766. ::setPinState(fakeFolder.localPath() + "unspec", PinState::Unspecified, cfapi::Recurse);
  767. fakeFolder.remoteModifier().insert("file3", 1024 * 1024);
  768. fakeFolder.remoteModifier().insert("online/file3", 1024 * 1024);
  769. fakeFolder.remoteModifier().insert("local/file3", 1024 * 1024);
  770. fakeFolder.remoteModifier().insert("unspec/file3", 1024 * 1024);
  771. QVERIFY(fakeFolder.syncOnce());
  772. CFVERIFY_VIRTUAL(fakeFolder, "file3");
  773. CFVERIFY_VIRTUAL(fakeFolder, "online/file3");
  774. CFVERIFY_NONVIRTUAL(fakeFolder, "local/file3");
  775. CFVERIFY_VIRTUAL(fakeFolder, "unspec/file3");
  776. // root file1 was dehydrated due to its new pin state
  777. CFVERIFY_VIRTUAL(fakeFolder, "file1");
  778. // file1 is unchanged in the explicitly pinned subfolders
  779. CFVERIFY_VIRTUAL(fakeFolder, "online/file1");
  780. CFVERIFY_NONVIRTUAL(fakeFolder, "local/file1");
  781. CFVERIFY_VIRTUAL(fakeFolder, "unspec/file1");
  782. }
  783. void testAvailability()
  784. {
  785. FakeFolder fakeFolder{ FileInfo() };
  786. auto vfs = setupVfs(fakeFolder);
  787. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  788. fakeFolder.remoteModifier().mkdir("local");
  789. fakeFolder.remoteModifier().mkdir("local/sub");
  790. fakeFolder.remoteModifier().mkdir("online");
  791. fakeFolder.remoteModifier().mkdir("online/sub");
  792. fakeFolder.remoteModifier().mkdir("unspec");
  793. QVERIFY(fakeFolder.syncOnce());
  794. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  795. ::setPinState(fakeFolder.localPath() + "local", PinState::AlwaysLocal, cfapi::Recurse);
  796. ::setPinState(fakeFolder.localPath() + "online", PinState::OnlineOnly, cfapi::Recurse);
  797. ::setPinState(fakeFolder.localPath() + "unspec", PinState::Unspecified, cfapi::Recurse);
  798. fakeFolder.remoteModifier().insert("file1", 1024 * 1024);
  799. fakeFolder.remoteModifier().insert("online/file1", 1024 * 1024);
  800. fakeFolder.remoteModifier().insert("online/file2", 1024 * 1024);
  801. fakeFolder.remoteModifier().insert("local/file1", 1024 * 1024);
  802. fakeFolder.remoteModifier().insert("local/file2", 1024 * 1024);
  803. fakeFolder.remoteModifier().insert("unspec/file1", 1024 * 1024);
  804. QVERIFY(fakeFolder.syncOnce());
  805. // root is unspecified
  806. QCOMPARE(*vfs->availability("file1"), VfsItemAvailability::AllDehydrated);
  807. QCOMPARE(*vfs->availability("local"), VfsItemAvailability::AlwaysLocal);
  808. QCOMPARE(*vfs->availability("local/file1"), VfsItemAvailability::AlwaysLocal);
  809. QCOMPARE(*vfs->availability("online"), VfsItemAvailability::OnlineOnly);
  810. QCOMPARE(*vfs->availability("online/file1"), VfsItemAvailability::OnlineOnly);
  811. QCOMPARE(*vfs->availability("unspec"), VfsItemAvailability::AllDehydrated);
  812. QCOMPARE(*vfs->availability("unspec/file1"), VfsItemAvailability::AllDehydrated);
  813. // Subitem pin states can ruin "pure" availabilities
  814. ::setPinState(fakeFolder.localPath() + "local/sub", PinState::OnlineOnly, cfapi::NoRecurse);
  815. QCOMPARE(*vfs->availability("local"), VfsItemAvailability::AllHydrated);
  816. ::setPinState(fakeFolder.localPath() + "online/sub", PinState::Unspecified, cfapi::NoRecurse);
  817. QCOMPARE(*vfs->availability("online"), VfsItemAvailability::AllDehydrated);
  818. triggerDownload(fakeFolder, "unspec/file1");
  819. ::setPinState(fakeFolder.localPath() + "local/file2", PinState::OnlineOnly, cfapi::NoRecurse);
  820. ::setPinState(fakeFolder.localPath() + "online/file2", PinState::AlwaysLocal, cfapi::NoRecurse);
  821. QVERIFY(fakeFolder.syncOnce());
  822. QCOMPARE(*vfs->availability("unspec"), VfsItemAvailability::AllHydrated);
  823. QCOMPARE(*vfs->availability("local"), VfsItemAvailability::Mixed);
  824. QCOMPARE(*vfs->availability("online"), VfsItemAvailability::Mixed);
  825. vfs->setPinState("local", PinState::AlwaysLocal);
  826. vfs->setPinState("online", PinState::OnlineOnly);
  827. QVERIFY(fakeFolder.syncOnce());
  828. QCOMPARE(*vfs->availability("online"), VfsItemAvailability::OnlineOnly);
  829. QCOMPARE(*vfs->availability("local"), VfsItemAvailability::AlwaysLocal);
  830. auto r = vfs->availability("nonexistant");
  831. QVERIFY(!r);
  832. QCOMPARE(r.error(), Vfs::AvailabilityError::NoSuchItem);
  833. }
  834. void testPinStateLocals()
  835. {
  836. FakeFolder fakeFolder{ FileInfo() };
  837. auto vfs = setupVfs(fakeFolder);
  838. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  839. fakeFolder.remoteModifier().mkdir("local");
  840. fakeFolder.remoteModifier().mkdir("online");
  841. fakeFolder.remoteModifier().mkdir("unspec");
  842. QVERIFY(fakeFolder.syncOnce());
  843. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  844. ::setPinState(fakeFolder.localPath() + "local", PinState::AlwaysLocal, cfapi::NoRecurse);
  845. ::setPinState(fakeFolder.localPath() + "online", PinState::OnlineOnly, cfapi::NoRecurse);
  846. ::setPinState(fakeFolder.localPath() + "unspec", PinState::Unspecified, cfapi::NoRecurse);
  847. fakeFolder.localModifier().insert("file1", 1024 * 1024);
  848. fakeFolder.localModifier().insert("online/file1", 1024 * 1024);
  849. fakeFolder.localModifier().insert("online/file2", 1024 * 1024);
  850. fakeFolder.localModifier().insert("local/file1", 1024 * 1024);
  851. fakeFolder.localModifier().insert("unspec/file1", 1024 * 1024);
  852. QVERIFY(fakeFolder.syncOnce());
  853. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  854. // root is unspecified
  855. QCOMPARE(*vfs->pinState("file1"), PinState::Unspecified);
  856. QCOMPARE(*vfs->pinState("local/file1"), PinState::AlwaysLocal);
  857. QCOMPARE(*vfs->pinState("online/file1"), PinState::Unspecified);
  858. QCOMPARE(*vfs->pinState("unspec/file1"), PinState::Unspecified);
  859. // Sync again: bad pin states of new local files usually take effect on second sync
  860. QVERIFY(fakeFolder.syncOnce());
  861. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  862. // When a file in an online-only folder is renamed, it retains its pin
  863. fakeFolder.localModifier().rename("online/file1", "online/file1rename");
  864. fakeFolder.remoteModifier().rename("online/file2", "online/file2rename");
  865. QVERIFY(fakeFolder.syncOnce());
  866. QCOMPARE(*vfs->pinState("online/file1rename"), PinState::Unspecified);
  867. QCOMPARE(*vfs->pinState("online/file2rename"), PinState::Unspecified);
  868. // When a folder is renamed, the pin states inside should be retained
  869. fakeFolder.localModifier().rename("online", "onlinerenamed1");
  870. QVERIFY(fakeFolder.syncOnce());
  871. QCOMPARE(*vfs->pinState("onlinerenamed1"), PinState::OnlineOnly);
  872. QCOMPARE(*vfs->pinState("onlinerenamed1/file1rename"), PinState::Unspecified);
  873. fakeFolder.remoteModifier().rename("onlinerenamed1", "onlinerenamed2");
  874. QVERIFY(fakeFolder.syncOnce());
  875. QCOMPARE(*vfs->pinState("onlinerenamed2"), PinState::OnlineOnly);
  876. QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::Unspecified);
  877. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  878. // When a file is deleted and later a new file has the same name, the old pin
  879. // state isn't preserved.
  880. QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::Unspecified);
  881. fakeFolder.remoteModifier().remove("onlinerenamed2/file1rename");
  882. QVERIFY(fakeFolder.syncOnce());
  883. QVERIFY(!vfs->pinState("onlinerenamed2/file1rename"));
  884. fakeFolder.remoteModifier().insert("onlinerenamed2/file1rename", 1024 * 1024);
  885. QVERIFY(fakeFolder.syncOnce());
  886. QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::OnlineOnly);
  887. // When a file is hydrated or dehydrated due to pin state it retains its pin state
  888. vfs->setPinState("onlinerenamed2/file1rename", PinState::AlwaysLocal);
  889. QVERIFY(fakeFolder.syncOnce());
  890. QVERIFY(fakeFolder.currentLocalState().find("onlinerenamed2/file1rename"));
  891. QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::AlwaysLocal);
  892. vfs->setPinState("onlinerenamed2", PinState::Unspecified);
  893. vfs->setPinState("onlinerenamed2/file1rename", PinState::OnlineOnly);
  894. QVERIFY(fakeFolder.syncOnce());
  895. CFVERIFY_VIRTUAL(fakeFolder, "onlinerenamed2/file1rename");
  896. QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::OnlineOnly);
  897. }
  898. void testEmptyFolderInOnlineOnlyRoot()
  899. {
  900. FakeFolder fakeFolder{ FileInfo() };
  901. setupVfs(fakeFolder);
  902. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  903. ItemCompletedSpy completeSpy(fakeFolder);
  904. auto cleanup = [&]() {
  905. completeSpy.clear();
  906. };
  907. cleanup();
  908. // OnlineOnly forced on the root
  909. ::setPinState(fakeFolder.localPath(), PinState::OnlineOnly, cfapi::NoRecurse);
  910. // No effect sync
  911. QVERIFY(fakeFolder.syncOnce());
  912. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  913. cleanup();
  914. // Add an empty folder which should propagate
  915. fakeFolder.localModifier().mkdir("A");
  916. QVERIFY(fakeFolder.syncOnce());
  917. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  918. cleanup();
  919. }
  920. void testIncompatiblePins()
  921. {
  922. FakeFolder fakeFolder{ FileInfo() };
  923. auto vfs = setupVfs(fakeFolder);
  924. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  925. fakeFolder.remoteModifier().mkdir("local");
  926. fakeFolder.remoteModifier().mkdir("online");
  927. QVERIFY(fakeFolder.syncOnce());
  928. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  929. ::setPinState(fakeFolder.localPath() + "local", PinState::AlwaysLocal, cfapi::NoRecurse);
  930. ::setPinState(fakeFolder.localPath() + "online", PinState::OnlineOnly, cfapi::NoRecurse);
  931. fakeFolder.localModifier().insert("local/file1", 1024 * 1024);
  932. fakeFolder.localModifier().insert("online/file1", 1024 * 1024);
  933. QVERIFY(fakeFolder.syncOnce());
  934. ::setPinState(fakeFolder.localPath() + "local/file1", PinState::Unspecified, cfapi::Recurse);
  935. markForDehydration(fakeFolder, "local/file1");
  936. triggerDownload(fakeFolder, "online/file1");
  937. // the sync sets the changed files pin states to unspecified
  938. QVERIFY(fakeFolder.syncOnce());
  939. CFVERIFY_NONVIRTUAL(fakeFolder, "online/file1");
  940. CFVERIFY_VIRTUAL(fakeFolder, "local/file1");
  941. QCOMPARE(*vfs->pinState("online/file1"), PinState::Unspecified);
  942. QCOMPARE(*vfs->pinState("local/file1"), PinState::Unspecified);
  943. // no change on another sync
  944. QVERIFY(fakeFolder.syncOnce());
  945. CFVERIFY_NONVIRTUAL(fakeFolder, "online/file1");
  946. CFVERIFY_VIRTUAL(fakeFolder, "local/file1");
  947. }
  948. void testOpeningOnlineFileTriggersDownload_data()
  949. {
  950. QTest::addColumn<int>("errorKind");
  951. QTest::newRow("no error") << static_cast<int>(NoError);
  952. QTest::newRow("400") << 400;
  953. QTest::newRow("401") << 401;
  954. QTest::newRow("403") << 403;
  955. QTest::newRow("404") << 404;
  956. QTest::newRow("500") << 500;
  957. QTest::newRow("503") << 503;
  958. QTest::newRow("Timeout") << static_cast<int>(Timeout);
  959. }
  960. void testOpeningOnlineFileTriggersDownload()
  961. {
  962. QFETCH(int, errorKind);
  963. FakeFolder fakeFolder{ FileInfo() };
  964. setupVfs(fakeFolder);
  965. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  966. fakeFolder.remoteModifier().mkdir("online");
  967. fakeFolder.remoteModifier().mkdir("online/sub");
  968. QVERIFY(fakeFolder.syncOnce());
  969. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  970. ::setPinState(fakeFolder.localPath() + "online", PinState::OnlineOnly, cfapi::Recurse);
  971. fakeFolder.remoteModifier().insert("online/sub/file1", 10 * 1024 * 1024);
  972. QVERIFY(fakeFolder.syncOnce());
  973. CFVERIFY_VIRTUAL(fakeFolder, "online/sub/file1");
  974. // Setup error case if needed
  975. if (errorKind == Timeout) {
  976. fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) -> QNetworkReply * {
  977. if (req.url().path().endsWith("online/sub/file1")) {
  978. return new FakeHangingReply(op, req, this);
  979. }
  980. return nullptr;
  981. });
  982. } else if (errorKind != NoError) {
  983. fakeFolder.serverErrorPaths().append("online/sub/file1", errorKind);
  984. }
  985. // So the test that test timeout finishes fast
  986. QScopedValueRollback<int> setHttpTimeout(AbstractNetworkJob::httpTimeout, errorKind == Timeout ? 1 : 10000);
  987. // Simulate another process requesting the open
  988. QEventLoop loop;
  989. std::thread t([&] {
  990. QFile file(fakeFolder.localPath() + "online/sub/file1");
  991. if (file.open(QFile::ReadOnly)) {
  992. file.readAll();
  993. file.close();
  994. }
  995. QMetaObject::invokeMethod(&loop, &QEventLoop::quit, Qt::QueuedConnection);
  996. });
  997. loop.exec();
  998. t.detach();
  999. if (errorKind == NoError) {
  1000. CFVERIFY_NONVIRTUAL(fakeFolder, "online/sub/file1");
  1001. } else {
  1002. CFVERIFY_VIRTUAL(fakeFolder, "online/sub/file1");
  1003. }
  1004. // Nothing should change
  1005. ItemCompletedSpy completeSpy(fakeFolder);
  1006. QVERIFY(fakeFolder.syncOnce());
  1007. QVERIFY(completeSpy.isEmpty());
  1008. if (errorKind == NoError) {
  1009. CFVERIFY_NONVIRTUAL(fakeFolder, "online/sub/file1");
  1010. } else {
  1011. CFVERIFY_VIRTUAL(fakeFolder, "online/sub/file1");
  1012. }
  1013. }
  1014. };
  1015. QTEST_GUILESS_MAIN(TestSyncCfApi)
  1016. #include "testsynccfapi.moc"