testsynccfapi.cpp 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250
  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. [[maybe_unused]] const auto result = 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. if (!journal.getFileRecord(path, &record) || !record.isValid()) {
  64. return;
  65. }
  66. record._type = ItemTypeVirtualFileDownload;
  67. QVERIFY(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. if (!journal.getFileRecord(path, &record) || !record.isValid()) {
  75. return;
  76. }
  77. record._type = ItemTypeVirtualFileDehydration;
  78. QVERIFY(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. QVERIFY(fakeFolder.syncEngine().journal()->deleteFileRecord("A/a2"));
  206. QVERIFY(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. QVERIFY(fakeFolder.syncJournal().wipeErrorBlacklist() != -1);
  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. return fakeFolder.syncJournal().getFileRecord(path, &rec) && rec.isValid() && rec._type == ItemTypeVirtualFile;
  622. };
  623. QVERIFY(isDehydrated("A/a1"));
  624. QVERIFY(hasDehydratedDbEntries("A/a1"));
  625. QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_SYNC));
  626. QCOMPARE(completeSpy.findItem("A/a1")->_type, ItemTypeVirtualFileDehydration);
  627. QCOMPARE(completeSpy.findItem("A/a1")->_file, QStringLiteral("A/a1"));
  628. QVERIFY(isDehydrated("A/a2"));
  629. QVERIFY(hasDehydratedDbEntries("A/a2"));
  630. QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_SYNC));
  631. QCOMPARE(completeSpy.findItem("A/a2")->_type, ItemTypeVirtualFileDehydration);
  632. QVERIFY(!QFileInfo(fakeFolder.localPath() + "B/b1").exists());
  633. QVERIFY(!fakeFolder.currentRemoteState().find("B/b1"));
  634. QVERIFY(itemInstruction(completeSpy, "B/b1", CSYNC_INSTRUCTION_REMOVE));
  635. QVERIFY(!QFileInfo(fakeFolder.localPath() + "B/b2").exists());
  636. QVERIFY(!fakeFolder.currentRemoteState().find("B/b2"));
  637. QVERIFY(isDehydrated("B/b3"));
  638. QVERIFY(hasDehydratedDbEntries("B/b3"));
  639. QVERIFY(itemInstruction(completeSpy, "B/b2", CSYNC_INSTRUCTION_REMOVE));
  640. QVERIFY(itemInstruction(completeSpy, "B/b3", CSYNC_INSTRUCTION_NEW));
  641. QCOMPARE(fakeFolder.currentRemoteState().find("C/c1")->size, 25);
  642. QVERIFY(itemInstruction(completeSpy, "C/c1", CSYNC_INSTRUCTION_SYNC));
  643. QCOMPARE(fakeFolder.currentRemoteState().find("C/c2")->size, 26);
  644. QVERIFY(itemInstruction(completeSpy, "C/c2", CSYNC_INSTRUCTION_CONFLICT));
  645. cleanup();
  646. auto expectedRemoteState = fakeFolder.currentRemoteState();
  647. QVERIFY(fakeFolder.syncOnce());
  648. QCOMPARE(fakeFolder.currentRemoteState(), expectedRemoteState);
  649. QVERIFY(isDehydrated("A/a1"));
  650. QVERIFY(hasDehydratedDbEntries("A/a1"));
  651. QVERIFY(isDehydrated("A/a2"));
  652. QVERIFY(hasDehydratedDbEntries("A/a2"));
  653. QVERIFY(!QFileInfo(fakeFolder.localPath() + "B/b1").exists());
  654. QVERIFY(!QFileInfo(fakeFolder.localPath() + "B/b2").exists());
  655. QVERIFY(isDehydrated("B/b3"));
  656. QVERIFY(hasDehydratedDbEntries("B/b3"));
  657. QVERIFY(QFileInfo(fakeFolder.localPath() + "C/c1").exists());
  658. QVERIFY(dbRecord(fakeFolder, "C/c1").isValid());
  659. QVERIFY(!isDehydrated("C/c1"));
  660. QVERIFY(!hasDehydratedDbEntries("C/c1"));
  661. QVERIFY(QFileInfo(fakeFolder.localPath() + "C/c2").exists());
  662. QVERIFY(dbRecord(fakeFolder, "C/c2").isValid());
  663. QVERIFY(!isDehydrated("C/c2"));
  664. QVERIFY(!hasDehydratedDbEntries("C/c2"));
  665. }
  666. void testWipeVirtualSuffixFiles()
  667. {
  668. // TODO: Part of this test related to A/a3 is always failing on CI but never fails locally
  669. // I had to comment it out as this prevents from running all other tests with no working ways to fix that
  670. FakeFolder fakeFolder{ FileInfo{} };
  671. setupVfs(fakeFolder);
  672. // Create a suffix-vfs baseline
  673. fakeFolder.remoteModifier().mkdir("A");
  674. fakeFolder.remoteModifier().mkdir("A/B");
  675. fakeFolder.remoteModifier().insert("f1");
  676. fakeFolder.remoteModifier().insert("A/a1");
  677. // fakeFolder.remoteModifier().insert("A/a3");
  678. fakeFolder.remoteModifier().insert("A/B/b1");
  679. fakeFolder.localModifier().mkdir("A");
  680. fakeFolder.localModifier().mkdir("A/B");
  681. fakeFolder.localModifier().insert("f2");
  682. fakeFolder.localModifier().insert("A/a2");
  683. fakeFolder.localModifier().insert("A/B/b2");
  684. QVERIFY(fakeFolder.syncOnce());
  685. CFVERIFY_VIRTUAL(fakeFolder, "f1");
  686. CFVERIFY_VIRTUAL(fakeFolder, "A/a1");
  687. // CFVERIFY_VIRTUAL(fakeFolder, "A/a3");
  688. CFVERIFY_VIRTUAL(fakeFolder, "A/B/b1");
  689. // Make local changes to a3
  690. // fakeFolder.localModifier().remove("A/a3");
  691. // fakeFolder.localModifier().insert("A/a3", 100);
  692. // Now wipe the virtuals
  693. SyncEngine::wipeVirtualFiles(fakeFolder.localPath(), fakeFolder.syncJournal(), *fakeFolder.syncEngine().syncOptions()._vfs);
  694. CFVERIFY_GONE(fakeFolder, "f1");
  695. CFVERIFY_GONE(fakeFolder, "A/a1");
  696. //QVERIFY(QFileInfo(fakeFolder.localPath() + "A/a3").exists());
  697. // QVERIFY(!dbRecord(fakeFolder, "A/a3").isValid());
  698. CFVERIFY_GONE(fakeFolder, "A/B/b1");
  699. fakeFolder.switchToVfs(QSharedPointer<Vfs>(new VfsOff));
  700. // ItemCompletedSpy completeSpy(fakeFolder);
  701. QVERIFY(fakeFolder.syncOnce());
  702. QVERIFY(fakeFolder.currentLocalState().find("A"));
  703. QVERIFY(fakeFolder.currentLocalState().find("A/B"));
  704. QVERIFY(fakeFolder.currentLocalState().find("A/B/b1"));
  705. QVERIFY(fakeFolder.currentLocalState().find("A/B/b2"));
  706. QVERIFY(fakeFolder.currentLocalState().find("A/a1"));
  707. QVERIFY(fakeFolder.currentLocalState().find("A/a2"));
  708. // QVERIFY(fakeFolder.currentLocalState().find("A/a3"));
  709. QVERIFY(fakeFolder.currentLocalState().find("f1"));
  710. QVERIFY(fakeFolder.currentLocalState().find("f2"));
  711. // a3 has a conflict
  712. // QVERIFY(itemInstruction(completeSpy, "A/a3", CSYNC_INSTRUCTION_CONFLICT));
  713. // conflict files should exist
  714. // QCOMPARE(fakeFolder.syncJournal().conflictRecordPaths().size(), 1);
  715. }
  716. void testNewVirtuals()
  717. {
  718. FakeFolder fakeFolder{ FileInfo() };
  719. setupVfs(fakeFolder);
  720. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  721. fakeFolder.remoteModifier().mkdir("local");
  722. fakeFolder.remoteModifier().mkdir("online");
  723. fakeFolder.remoteModifier().mkdir("unspec");
  724. QVERIFY(fakeFolder.syncOnce());
  725. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  726. ::setPinState(fakeFolder.localPath() + "local", PinState::AlwaysLocal, cfapi::Recurse);
  727. ::setPinState(fakeFolder.localPath() + "online", PinState::OnlineOnly, cfapi::Recurse);
  728. ::setPinState(fakeFolder.localPath() + "unspec", PinState::Unspecified, cfapi::Recurse);
  729. // Test 1: root is Unspecified
  730. fakeFolder.remoteModifier().insert("file1", 1024 * 1024);
  731. fakeFolder.remoteModifier().insert("online/file1", 1024 * 1024);
  732. fakeFolder.remoteModifier().insert("local/file1", 1024 * 1024);
  733. fakeFolder.remoteModifier().insert("unspec/file1", 1024 * 1024);
  734. QVERIFY(fakeFolder.syncOnce());
  735. CFVERIFY_VIRTUAL(fakeFolder, "file1");
  736. CFVERIFY_VIRTUAL(fakeFolder, "online/file1");
  737. CFVERIFY_NONVIRTUAL(fakeFolder, "local/file1");
  738. CFVERIFY_VIRTUAL(fakeFolder, "unspec/file1");
  739. // Test 2: change root to AlwaysLocal
  740. ::setPinState(fakeFolder.localPath(), PinState::AlwaysLocal, cfapi::Recurse);
  741. // Need to force pin state for the subfolders again
  742. ::setPinState(fakeFolder.localPath() + "local", PinState::AlwaysLocal, cfapi::Recurse);
  743. ::setPinState(fakeFolder.localPath() + "online", PinState::OnlineOnly, cfapi::Recurse);
  744. ::setPinState(fakeFolder.localPath() + "unspec", PinState::Unspecified, cfapi::Recurse);
  745. fakeFolder.remoteModifier().insert("file2", 1024 * 1024);
  746. fakeFolder.remoteModifier().insert("online/file2", 1024 * 1024);
  747. fakeFolder.remoteModifier().insert("local/file2", 1024 * 1024);
  748. fakeFolder.remoteModifier().insert("unspec/file2", 1024 * 1024);
  749. QVERIFY(fakeFolder.syncOnce());
  750. CFVERIFY_NONVIRTUAL(fakeFolder, "file2");
  751. CFVERIFY_VIRTUAL(fakeFolder, "online/file2");
  752. CFVERIFY_NONVIRTUAL(fakeFolder, "local/file2");
  753. CFVERIFY_VIRTUAL(fakeFolder, "unspec/file2");
  754. // root file1 was hydrated due to its new pin state
  755. CFVERIFY_NONVIRTUAL(fakeFolder, "file1");
  756. // file1 is unchanged in the explicitly pinned subfolders
  757. CFVERIFY_VIRTUAL(fakeFolder, "online/file1");
  758. CFVERIFY_NONVIRTUAL(fakeFolder, "local/file1");
  759. CFVERIFY_VIRTUAL(fakeFolder, "unspec/file1");
  760. // Test 3: change root to OnlineOnly
  761. ::setPinState(fakeFolder.localPath(), PinState::OnlineOnly, cfapi::Recurse);
  762. // Need to force pin state for the subfolders again
  763. ::setPinState(fakeFolder.localPath() + "local", PinState::AlwaysLocal, cfapi::Recurse);
  764. ::setPinState(fakeFolder.localPath() + "online", PinState::OnlineOnly, cfapi::Recurse);
  765. ::setPinState(fakeFolder.localPath() + "unspec", PinState::Unspecified, cfapi::Recurse);
  766. fakeFolder.remoteModifier().insert("file3", 1024 * 1024);
  767. fakeFolder.remoteModifier().insert("online/file3", 1024 * 1024);
  768. fakeFolder.remoteModifier().insert("local/file3", 1024 * 1024);
  769. fakeFolder.remoteModifier().insert("unspec/file3", 1024 * 1024);
  770. QVERIFY(fakeFolder.syncOnce());
  771. CFVERIFY_VIRTUAL(fakeFolder, "file3");
  772. CFVERIFY_VIRTUAL(fakeFolder, "online/file3");
  773. CFVERIFY_NONVIRTUAL(fakeFolder, "local/file3");
  774. CFVERIFY_VIRTUAL(fakeFolder, "unspec/file3");
  775. // root file1 was dehydrated due to its new pin state
  776. CFVERIFY_VIRTUAL(fakeFolder, "file1");
  777. // file1 is unchanged in the explicitly pinned subfolders
  778. CFVERIFY_VIRTUAL(fakeFolder, "online/file1");
  779. CFVERIFY_NONVIRTUAL(fakeFolder, "local/file1");
  780. CFVERIFY_VIRTUAL(fakeFolder, "unspec/file1");
  781. }
  782. void testAvailability()
  783. {
  784. FakeFolder fakeFolder{ FileInfo() };
  785. auto vfs = setupVfs(fakeFolder);
  786. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  787. fakeFolder.remoteModifier().mkdir("local");
  788. fakeFolder.remoteModifier().mkdir("local/sub");
  789. fakeFolder.remoteModifier().mkdir("online");
  790. fakeFolder.remoteModifier().mkdir("online/sub");
  791. fakeFolder.remoteModifier().mkdir("unspec");
  792. QVERIFY(fakeFolder.syncOnce());
  793. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  794. ::setPinState(fakeFolder.localPath() + "local", PinState::AlwaysLocal, cfapi::Recurse);
  795. ::setPinState(fakeFolder.localPath() + "online", PinState::OnlineOnly, cfapi::Recurse);
  796. ::setPinState(fakeFolder.localPath() + "unspec", PinState::Unspecified, cfapi::Recurse);
  797. fakeFolder.remoteModifier().insert("file1", 1024 * 1024);
  798. fakeFolder.remoteModifier().insert("online/file1", 1024 * 1024);
  799. fakeFolder.remoteModifier().insert("online/file2", 1024 * 1024);
  800. fakeFolder.remoteModifier().insert("local/file1", 1024 * 1024);
  801. fakeFolder.remoteModifier().insert("local/file2", 1024 * 1024);
  802. fakeFolder.remoteModifier().insert("unspec/file1", 1024 * 1024);
  803. QVERIFY(fakeFolder.syncOnce());
  804. // root is unspecified
  805. QCOMPARE(*vfs->availability("file1"), VfsItemAvailability::AllDehydrated);
  806. QCOMPARE(*vfs->availability("local"), VfsItemAvailability::AlwaysLocal);
  807. QCOMPARE(*vfs->availability("local/file1"), VfsItemAvailability::AlwaysLocal);
  808. QCOMPARE(*vfs->availability("online"), VfsItemAvailability::OnlineOnly);
  809. QCOMPARE(*vfs->availability("online/file1"), VfsItemAvailability::OnlineOnly);
  810. QCOMPARE(*vfs->availability("unspec"), VfsItemAvailability::AllDehydrated);
  811. QCOMPARE(*vfs->availability("unspec/file1"), VfsItemAvailability::AllDehydrated);
  812. // Subitem pin states can ruin "pure" availabilities
  813. ::setPinState(fakeFolder.localPath() + "local/sub", PinState::OnlineOnly, cfapi::NoRecurse);
  814. QCOMPARE(*vfs->availability("local"), VfsItemAvailability::AllHydrated);
  815. ::setPinState(fakeFolder.localPath() + "online/sub", PinState::Unspecified, cfapi::NoRecurse);
  816. QCOMPARE(*vfs->availability("online"), VfsItemAvailability::AllDehydrated);
  817. triggerDownload(fakeFolder, "unspec/file1");
  818. ::setPinState(fakeFolder.localPath() + "local/file2", PinState::OnlineOnly, cfapi::NoRecurse);
  819. ::setPinState(fakeFolder.localPath() + "online/file2", PinState::AlwaysLocal, cfapi::NoRecurse);
  820. QVERIFY(fakeFolder.syncOnce());
  821. QCOMPARE(*vfs->availability("unspec"), VfsItemAvailability::AllHydrated);
  822. QCOMPARE(*vfs->availability("local"), VfsItemAvailability::Mixed);
  823. QCOMPARE(*vfs->availability("online"), VfsItemAvailability::Mixed);
  824. QVERIFY(vfs->setPinState("local", PinState::AlwaysLocal));
  825. QVERIFY(vfs->setPinState("online", PinState::OnlineOnly));
  826. QVERIFY(fakeFolder.syncOnce());
  827. QCOMPARE(*vfs->availability("online"), VfsItemAvailability::OnlineOnly);
  828. QCOMPARE(*vfs->availability("local"), VfsItemAvailability::AlwaysLocal);
  829. auto r = vfs->availability("nonexistent");
  830. QVERIFY(!r);
  831. QCOMPARE(r.error(), Vfs::AvailabilityError::NoSuchItem);
  832. }
  833. void testPinStateLocals()
  834. {
  835. FakeFolder fakeFolder{ FileInfo() };
  836. auto vfs = setupVfs(fakeFolder);
  837. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  838. fakeFolder.remoteModifier().mkdir("local");
  839. fakeFolder.remoteModifier().mkdir("online");
  840. fakeFolder.remoteModifier().mkdir("unspec");
  841. QVERIFY(fakeFolder.syncOnce());
  842. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  843. ::setPinState(fakeFolder.localPath() + "local", PinState::AlwaysLocal, cfapi::NoRecurse);
  844. ::setPinState(fakeFolder.localPath() + "online", PinState::OnlineOnly, cfapi::NoRecurse);
  845. ::setPinState(fakeFolder.localPath() + "unspec", PinState::Unspecified, cfapi::NoRecurse);
  846. fakeFolder.localModifier().insert("file1", 1024 * 1024);
  847. fakeFolder.localModifier().insert("online/file1", 1024 * 1024);
  848. fakeFolder.localModifier().insert("online/file2", 1024 * 1024);
  849. fakeFolder.localModifier().insert("local/file1", 1024 * 1024);
  850. fakeFolder.localModifier().insert("unspec/file1", 1024 * 1024);
  851. QVERIFY(fakeFolder.syncOnce());
  852. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  853. // root is unspecified
  854. QCOMPARE(*vfs->pinState("file1"), PinState::Unspecified);
  855. QCOMPARE(*vfs->pinState("local/file1"), PinState::AlwaysLocal);
  856. QCOMPARE(*vfs->pinState("online/file1"), PinState::Unspecified);
  857. QCOMPARE(*vfs->pinState("unspec/file1"), PinState::Unspecified);
  858. // Sync again: bad pin states of new local files usually take effect on second sync
  859. QVERIFY(fakeFolder.syncOnce());
  860. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  861. // When a file in an online-only folder is renamed, it retains its pin
  862. fakeFolder.localModifier().rename("online/file1", "online/file1rename");
  863. fakeFolder.remoteModifier().rename("online/file2", "online/file2rename");
  864. QVERIFY(fakeFolder.syncOnce());
  865. QCOMPARE(*vfs->pinState("online/file1rename"), PinState::Unspecified);
  866. QCOMPARE(*vfs->pinState("online/file2rename"), PinState::Unspecified);
  867. // When a folder is renamed, the pin states inside should be retained
  868. fakeFolder.localModifier().rename("online", "onlinerenamed1");
  869. QVERIFY(fakeFolder.syncOnce());
  870. QCOMPARE(*vfs->pinState("onlinerenamed1"), PinState::OnlineOnly);
  871. QCOMPARE(*vfs->pinState("onlinerenamed1/file1rename"), PinState::Unspecified);
  872. fakeFolder.remoteModifier().rename("onlinerenamed1", "onlinerenamed2");
  873. QVERIFY(fakeFolder.syncOnce());
  874. QCOMPARE(*vfs->pinState("onlinerenamed2"), PinState::OnlineOnly);
  875. QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::Unspecified);
  876. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  877. // When a file is deleted and later a new file has the same name, the old pin
  878. // state isn't preserved.
  879. QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::Unspecified);
  880. fakeFolder.remoteModifier().remove("onlinerenamed2/file1rename");
  881. QVERIFY(fakeFolder.syncOnce());
  882. QVERIFY(!vfs->pinState("onlinerenamed2/file1rename"));
  883. fakeFolder.remoteModifier().insert("onlinerenamed2/file1rename", 1024 * 1024);
  884. QVERIFY(fakeFolder.syncOnce());
  885. QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::OnlineOnly);
  886. // When a file is hydrated or dehydrated due to pin state it retains its pin state
  887. QVERIFY(vfs->setPinState("onlinerenamed2/file1rename", PinState::AlwaysLocal));
  888. QVERIFY(fakeFolder.syncOnce());
  889. QVERIFY(fakeFolder.currentLocalState().find("onlinerenamed2/file1rename"));
  890. QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::AlwaysLocal);
  891. QVERIFY(vfs->setPinState("onlinerenamed2", PinState::Unspecified));
  892. QVERIFY(vfs->setPinState("onlinerenamed2/file1rename", PinState::OnlineOnly));
  893. QVERIFY(fakeFolder.syncOnce());
  894. CFVERIFY_VIRTUAL(fakeFolder, "onlinerenamed2/file1rename");
  895. QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::OnlineOnly);
  896. }
  897. void testEmptyFolderInOnlineOnlyRoot()
  898. {
  899. FakeFolder fakeFolder{ FileInfo() };
  900. setupVfs(fakeFolder);
  901. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  902. ItemCompletedSpy completeSpy(fakeFolder);
  903. auto cleanup = [&]() {
  904. completeSpy.clear();
  905. };
  906. cleanup();
  907. // OnlineOnly forced on the root
  908. ::setPinState(fakeFolder.localPath(), PinState::OnlineOnly, cfapi::NoRecurse);
  909. // No effect sync
  910. QVERIFY(fakeFolder.syncOnce());
  911. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  912. cleanup();
  913. // Add an empty folder which should propagate
  914. fakeFolder.localModifier().mkdir("A");
  915. QVERIFY(fakeFolder.syncOnce());
  916. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  917. cleanup();
  918. }
  919. void testIncompatiblePins()
  920. {
  921. FakeFolder fakeFolder{ FileInfo() };
  922. auto vfs = setupVfs(fakeFolder);
  923. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  924. fakeFolder.remoteModifier().mkdir("local");
  925. fakeFolder.remoteModifier().mkdir("online");
  926. QVERIFY(fakeFolder.syncOnce());
  927. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  928. ::setPinState(fakeFolder.localPath() + "local", PinState::AlwaysLocal, cfapi::NoRecurse);
  929. ::setPinState(fakeFolder.localPath() + "online", PinState::OnlineOnly, cfapi::NoRecurse);
  930. fakeFolder.localModifier().insert("local/file1", 1024 * 1024);
  931. fakeFolder.localModifier().insert("online/file1", 1024 * 1024);
  932. QVERIFY(fakeFolder.syncOnce());
  933. ::setPinState(fakeFolder.localPath() + "local/file1", PinState::Unspecified, cfapi::Recurse);
  934. markForDehydration(fakeFolder, "local/file1");
  935. triggerDownload(fakeFolder, "online/file1");
  936. // the sync sets the changed files pin states to unspecified
  937. QVERIFY(fakeFolder.syncOnce());
  938. CFVERIFY_NONVIRTUAL(fakeFolder, "online/file1");
  939. CFVERIFY_VIRTUAL(fakeFolder, "local/file1");
  940. QCOMPARE(*vfs->pinState("online/file1"), PinState::Unspecified);
  941. QCOMPARE(*vfs->pinState("local/file1"), PinState::Unspecified);
  942. // no change on another sync
  943. QVERIFY(fakeFolder.syncOnce());
  944. CFVERIFY_NONVIRTUAL(fakeFolder, "online/file1");
  945. CFVERIFY_VIRTUAL(fakeFolder, "local/file1");
  946. }
  947. void testOpeningOnlineFileTriggersDownload_data()
  948. {
  949. QTest::addColumn<int>("errorKind");
  950. QTest::newRow("no error") << static_cast<int>(NoError);
  951. QTest::newRow("400") << 400;
  952. QTest::newRow("401") << 401;
  953. QTest::newRow("403") << 403;
  954. QTest::newRow("404") << 404;
  955. QTest::newRow("500") << 500;
  956. QTest::newRow("503") << 503;
  957. QTest::newRow("Timeout") << static_cast<int>(Timeout);
  958. }
  959. void testOpeningOnlineFileTriggersDownload()
  960. {
  961. QFETCH(int, errorKind);
  962. FakeFolder fakeFolder{ FileInfo() };
  963. setupVfs(fakeFolder);
  964. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  965. fakeFolder.remoteModifier().mkdir("online");
  966. fakeFolder.remoteModifier().mkdir("online/sub");
  967. QVERIFY(fakeFolder.syncOnce());
  968. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  969. ::setPinState(fakeFolder.localPath() + "online", PinState::OnlineOnly, cfapi::Recurse);
  970. fakeFolder.remoteModifier().insert("online/sub/file1", 10 * 1024 * 1024);
  971. QVERIFY(fakeFolder.syncOnce());
  972. CFVERIFY_VIRTUAL(fakeFolder, "online/sub/file1");
  973. // Setup error case if needed
  974. if (errorKind == Timeout) {
  975. fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) -> QNetworkReply * {
  976. if (req.url().path().endsWith("online/sub/file1")) {
  977. return new FakeHangingReply(op, req, this);
  978. }
  979. return nullptr;
  980. });
  981. } else if (errorKind != NoError) {
  982. fakeFolder.serverErrorPaths().append("online/sub/file1", errorKind);
  983. }
  984. // So the test that test timeout finishes fast
  985. QScopedValueRollback<int> setHttpTimeout(AbstractNetworkJob::httpTimeout, errorKind == Timeout ? 1 : 10000);
  986. // Simulate another process requesting the open
  987. QEventLoop loop;
  988. std::thread t([&] {
  989. QFile file(fakeFolder.localPath() + "online/sub/file1");
  990. if (file.open(QFile::ReadOnly)) {
  991. file.readAll();
  992. file.close();
  993. }
  994. QMetaObject::invokeMethod(&loop, &QEventLoop::quit, Qt::QueuedConnection);
  995. });
  996. loop.exec();
  997. t.detach();
  998. if (errorKind == NoError) {
  999. CFVERIFY_NONVIRTUAL(fakeFolder, "online/sub/file1");
  1000. } else {
  1001. CFVERIFY_VIRTUAL(fakeFolder, "online/sub/file1");
  1002. }
  1003. // Nothing should change
  1004. ItemCompletedSpy completeSpy(fakeFolder);
  1005. QVERIFY(fakeFolder.syncOnce());
  1006. QVERIFY(completeSpy.isEmpty());
  1007. if (errorKind == NoError) {
  1008. CFVERIFY_NONVIRTUAL(fakeFolder, "online/sub/file1");
  1009. } else {
  1010. CFVERIFY_VIRTUAL(fakeFolder, "online/sub/file1");
  1011. }
  1012. }
  1013. void testDataFingerPrint()
  1014. {
  1015. FakeFolder fakeFolder{ FileInfo{} };
  1016. setupVfs(fakeFolder);
  1017. fakeFolder.remoteModifier().mkdir("a");
  1018. fakeFolder.remoteModifier().mkdir("a/b");
  1019. fakeFolder.remoteModifier().mkdir("a/b/d");
  1020. fakeFolder.remoteModifier().insert("a/b/otherFile.txt");
  1021. //Server support finger print, but none is set.
  1022. fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint></oc:data-fingerprint>";
  1023. fakeFolder.syncEngine().setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::DatabaseAndFilesystem);
  1024. QVERIFY(fakeFolder.syncOnce());
  1025. fakeFolder.remoteModifier().remove("a/b/otherFile.txt");
  1026. fakeFolder.remoteModifier().remove("a/b/d");
  1027. fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint>initial_finger_print</oc:data-fingerprint>";
  1028. fakeFolder.syncEngine().setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::DatabaseAndFilesystem);
  1029. QVERIFY(fakeFolder.syncOnce());
  1030. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  1031. }
  1032. };
  1033. QTEST_GUILESS_MAIN(TestSyncCfApi)
  1034. #include "testsynccfapi.moc"