testsyncmove.cpp 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048
  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 "common/result.h"
  9. #include "syncenginetestutils.h"
  10. #include <syncengine.h>
  11. using namespace OCC;
  12. struct OperationCounter {
  13. int nGET = 0;
  14. int nPUT = 0;
  15. int nMOVE = 0;
  16. int nDELETE = 0;
  17. void reset() { *this = {}; }
  18. auto functor() {
  19. return [&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) {
  20. if (op == QNetworkAccessManager::GetOperation)
  21. ++nGET;
  22. if (op == QNetworkAccessManager::PutOperation)
  23. ++nPUT;
  24. if (op == QNetworkAccessManager::DeleteOperation)
  25. ++nDELETE;
  26. if (req.attribute(QNetworkRequest::CustomVerbAttribute) == "MOVE")
  27. ++nMOVE;
  28. return nullptr;
  29. };
  30. }
  31. };
  32. bool itemSuccessful(const ItemCompletedSpy &spy, const QString &path, const SyncInstructions instr)
  33. {
  34. auto item = spy.findItem(path);
  35. return item->_status == SyncFileItem::Success && item->_instruction == instr;
  36. }
  37. bool itemConflict(const ItemCompletedSpy &spy, const QString &path)
  38. {
  39. auto item = spy.findItem(path);
  40. return item->_status == SyncFileItem::Conflict && item->_instruction == CSYNC_INSTRUCTION_CONFLICT;
  41. }
  42. bool itemSuccessfulMove(const ItemCompletedSpy &spy, const QString &path)
  43. {
  44. return itemSuccessful(spy, path, CSYNC_INSTRUCTION_RENAME);
  45. }
  46. QStringList findConflicts(const FileInfo &dir)
  47. {
  48. QStringList conflicts;
  49. for (const auto &item : dir.children) {
  50. if (item.name.contains("(conflicted copy")) {
  51. conflicts.append(item.path());
  52. }
  53. }
  54. return conflicts;
  55. }
  56. bool expectAndWipeConflict(FileModifier &local, FileInfo state, const QString path)
  57. {
  58. PathComponents pathComponents(path);
  59. auto base = state.find(pathComponents.parentDirComponents());
  60. if (!base)
  61. return false;
  62. for (const auto &item : qAsConst(base->children)) {
  63. if (item.name.startsWith(pathComponents.fileName()) && item.name.contains("(conflicted copy")) {
  64. local.remove(item.path());
  65. return true;
  66. }
  67. }
  68. return false;
  69. }
  70. class TestSyncMove : public QObject
  71. {
  72. Q_OBJECT
  73. private slots:
  74. void testMoveCustomRemoteRoot()
  75. {
  76. FileInfo subFolder(QStringLiteral("AS"), { { QStringLiteral("f1"), 4 } });
  77. FileInfo folder(QStringLiteral("A"), { subFolder });
  78. FileInfo fileInfo({}, { folder });
  79. FakeFolder fakeFolder(fileInfo, folder, QStringLiteral("/A"));
  80. auto &localModifier = fakeFolder.localModifier();
  81. OperationCounter counter;
  82. fakeFolder.setServerOverride(counter.functor());
  83. // Move file and then move it back again
  84. {
  85. counter.reset();
  86. localModifier.rename(QStringLiteral("AS/f1"), QStringLiteral("f1"));
  87. ItemCompletedSpy completeSpy(fakeFolder);
  88. QVERIFY(fakeFolder.syncOnce());
  89. QCOMPARE(counter.nGET, 0);
  90. QCOMPARE(counter.nPUT, 0);
  91. QCOMPARE(counter.nMOVE, 1);
  92. QCOMPARE(counter.nDELETE, 0);
  93. QVERIFY(itemSuccessful(completeSpy, "f1", CSYNC_INSTRUCTION_RENAME));
  94. QVERIFY(fakeFolder.currentRemoteState().find("A/f1"));
  95. QVERIFY(!fakeFolder.currentRemoteState().find("A/AS/f1"));
  96. }
  97. }
  98. void testRemoteChangeInMovedFolder()
  99. {
  100. // issue #5192
  101. FakeFolder fakeFolder{ FileInfo{ QString(), { FileInfo{ QStringLiteral("folder"), { FileInfo{ QStringLiteral("folderA"), { { QStringLiteral("file.txt"), 400 } } }, QStringLiteral("folderB") } } } } };
  102. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  103. // Edit a file in a moved directory.
  104. fakeFolder.remoteModifier().setContents("folder/folderA/file.txt", 'a');
  105. fakeFolder.remoteModifier().rename("folder/folderA", "folder/folderB/folderA");
  106. fakeFolder.syncOnce();
  107. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  108. auto oldState = fakeFolder.currentLocalState();
  109. QVERIFY(oldState.find("folder/folderB/folderA/file.txt"));
  110. QVERIFY(!oldState.find("folder/folderA/file.txt"));
  111. // This sync should not remove the file
  112. fakeFolder.syncOnce();
  113. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  114. QCOMPARE(fakeFolder.currentLocalState(), oldState);
  115. }
  116. void testSelectiveSyncMovedFolder()
  117. {
  118. // issue #5224
  119. FakeFolder fakeFolder{ FileInfo{ QString(), { FileInfo{ QStringLiteral("parentFolder"), { FileInfo{ QStringLiteral("subFolderA"), { { QStringLiteral("fileA.txt"), 400 } } }, FileInfo{ QStringLiteral("subFolderB"), { { QStringLiteral("fileB.txt"), 400 } } } } } } } };
  120. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  121. auto expectedServerState = fakeFolder.currentRemoteState();
  122. // Remove subFolderA with selectiveSync:
  123. fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
  124. { "parentFolder/subFolderA/" });
  125. fakeFolder.syncEngine().journal()->schedulePathForRemoteDiscovery(QByteArrayLiteral("parentFolder/subFolderA/"));
  126. fakeFolder.syncOnce();
  127. {
  128. // Nothing changed on the server
  129. QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState);
  130. // The local state should not have subFolderA
  131. auto remoteState = fakeFolder.currentRemoteState();
  132. remoteState.remove("parentFolder/subFolderA");
  133. QCOMPARE(fakeFolder.currentLocalState(), remoteState);
  134. }
  135. // Rename parentFolder on the server
  136. fakeFolder.remoteModifier().rename("parentFolder", "parentFolderRenamed");
  137. expectedServerState = fakeFolder.currentRemoteState();
  138. fakeFolder.syncOnce();
  139. {
  140. QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState);
  141. auto remoteState = fakeFolder.currentRemoteState();
  142. // The subFolderA should still be there on the server.
  143. QVERIFY(remoteState.find("parentFolderRenamed/subFolderA/fileA.txt"));
  144. // But not on the client because of the selective sync
  145. remoteState.remove("parentFolderRenamed/subFolderA");
  146. QCOMPARE(fakeFolder.currentLocalState(), remoteState);
  147. }
  148. // Rename it again, locally this time.
  149. fakeFolder.localModifier().rename("parentFolderRenamed", "parentThirdName");
  150. fakeFolder.syncOnce();
  151. {
  152. auto remoteState = fakeFolder.currentRemoteState();
  153. // The subFolderA should still be there on the server.
  154. QVERIFY(remoteState.find("parentThirdName/subFolderA/fileA.txt"));
  155. // But not on the client because of the selective sync
  156. remoteState.remove("parentThirdName/subFolderA");
  157. QCOMPARE(fakeFolder.currentLocalState(), remoteState);
  158. expectedServerState = fakeFolder.currentRemoteState();
  159. ItemCompletedSpy completeSpy(fakeFolder);
  160. fakeFolder.syncOnce(); // This sync should do nothing
  161. QCOMPARE(completeSpy.count(), 0);
  162. QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState);
  163. QCOMPARE(fakeFolder.currentLocalState(), remoteState);
  164. }
  165. }
  166. void testLocalMoveDetection()
  167. {
  168. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  169. int nPUT = 0;
  170. int nDELETE = 0;
  171. fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &, QIODevice *) {
  172. if (op == QNetworkAccessManager::PutOperation)
  173. ++nPUT;
  174. if (op == QNetworkAccessManager::DeleteOperation)
  175. ++nDELETE;
  176. return nullptr;
  177. });
  178. // For directly editing the remote checksum
  179. FileInfo &remoteInfo = fakeFolder.remoteModifier();
  180. // Simple move causing a remote rename
  181. fakeFolder.localModifier().rename("A/a1", "A/a1m");
  182. QVERIFY(fakeFolder.syncOnce());
  183. QCOMPARE(fakeFolder.currentLocalState(), remoteInfo);
  184. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(remoteInfo));
  185. QCOMPARE(nPUT, 0);
  186. // Move-and-change, causing a upload and delete
  187. fakeFolder.localModifier().rename("A/a2", "A/a2m");
  188. fakeFolder.localModifier().appendByte("A/a2m");
  189. QVERIFY(fakeFolder.syncOnce());
  190. QCOMPARE(fakeFolder.currentLocalState(), remoteInfo);
  191. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(remoteInfo));
  192. QCOMPARE(nPUT, 1);
  193. QCOMPARE(nDELETE, 1);
  194. // Move-and-change, mtime+content only
  195. fakeFolder.localModifier().rename("B/b1", "B/b1m");
  196. fakeFolder.localModifier().setContents("B/b1m", 'C');
  197. QVERIFY(fakeFolder.syncOnce());
  198. QCOMPARE(fakeFolder.currentLocalState(), remoteInfo);
  199. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(remoteInfo));
  200. QCOMPARE(nPUT, 2);
  201. QCOMPARE(nDELETE, 2);
  202. // Move-and-change, size+content only
  203. auto mtime = fakeFolder.remoteModifier().find("B/b2")->lastModified;
  204. fakeFolder.localModifier().rename("B/b2", "B/b2m");
  205. fakeFolder.localModifier().appendByte("B/b2m");
  206. fakeFolder.localModifier().setModTime("B/b2m", mtime);
  207. QVERIFY(fakeFolder.syncOnce());
  208. QCOMPARE(fakeFolder.currentLocalState(), remoteInfo);
  209. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(remoteInfo));
  210. QCOMPARE(nPUT, 3);
  211. QCOMPARE(nDELETE, 3);
  212. // Move-and-change, content only -- c1 has no checksum, so we fail to detect this!
  213. // NOTE: This is an expected failure.
  214. mtime = fakeFolder.remoteModifier().find("C/c1")->lastModified;
  215. fakeFolder.localModifier().rename("C/c1", "C/c1m");
  216. fakeFolder.localModifier().setContents("C/c1m", 'C');
  217. fakeFolder.localModifier().setModTime("C/c1m", mtime);
  218. QVERIFY(fakeFolder.syncOnce());
  219. QCOMPARE(nPUT, 3);
  220. QCOMPARE(nDELETE, 3);
  221. QVERIFY(!(fakeFolder.currentLocalState() == remoteInfo));
  222. // cleanup, and upload a file that will have a checksum in the db
  223. fakeFolder.localModifier().remove("C/c1m");
  224. fakeFolder.localModifier().insert("C/c3");
  225. QVERIFY(fakeFolder.syncOnce());
  226. QCOMPARE(fakeFolder.currentLocalState(), remoteInfo);
  227. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(remoteInfo));
  228. QCOMPARE(nPUT, 4);
  229. QCOMPARE(nDELETE, 4);
  230. // Move-and-change, content only, this time while having a checksum
  231. mtime = fakeFolder.remoteModifier().find("C/c3")->lastModified;
  232. fakeFolder.localModifier().rename("C/c3", "C/c3m");
  233. fakeFolder.localModifier().setContents("C/c3m", 'C');
  234. fakeFolder.localModifier().setModTime("C/c3m", mtime);
  235. QVERIFY(fakeFolder.syncOnce());
  236. QCOMPARE(nPUT, 5);
  237. QCOMPARE(nDELETE, 5);
  238. QCOMPARE(fakeFolder.currentLocalState(), remoteInfo);
  239. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(remoteInfo));
  240. }
  241. void testDuplicateFileId_data()
  242. {
  243. QTest::addColumn<QString>("prefix");
  244. // There have been bugs related to how the original
  245. // folder and the folder with the duplicate tree are
  246. // ordered. Test both cases here.
  247. QTest::newRow("first ordering") << "O"; // "O" > "A"
  248. QTest::newRow("second ordering") << "0"; // "0" < "A"
  249. }
  250. // If the same folder is shared in two different ways with the same
  251. // user, the target user will see duplicate file ids. We need to make
  252. // sure the move detection and sync still do the right thing in that
  253. // case.
  254. void testDuplicateFileId()
  255. {
  256. QFETCH(QString, prefix);
  257. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  258. auto &remote = fakeFolder.remoteModifier();
  259. remote.mkdir("A/W");
  260. remote.insert("A/W/w1");
  261. remote.mkdir("A/Q");
  262. // Duplicate every entry in A under O/A
  263. remote.mkdir(prefix);
  264. remote.children[prefix].addChild(remote.children["A"]);
  265. // This already checks that the rename detection doesn't get
  266. // horribly confused if we add new files that have the same
  267. // fileid as existing ones
  268. QVERIFY(fakeFolder.syncOnce());
  269. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  270. OperationCounter counter;
  271. fakeFolder.setServerOverride(counter.functor());
  272. // Try a remote file move
  273. remote.rename("A/a1", "A/W/a1m");
  274. remote.rename(prefix + "/A/a1", prefix + "/A/W/a1m");
  275. QVERIFY(fakeFolder.syncOnce());
  276. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  277. QCOMPARE(counter.nGET, 0);
  278. // And a remote directory move
  279. remote.rename("A/W", "A/Q/W");
  280. remote.rename(prefix + "/A/W", prefix + "/A/Q/W");
  281. QVERIFY(fakeFolder.syncOnce());
  282. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  283. QCOMPARE(counter.nGET, 0);
  284. // Partial file removal (in practice, A/a2 may be moved to O/a2, but we don't care)
  285. remote.rename(prefix + "/A/a2", prefix + "/a2");
  286. remote.remove("A/a2");
  287. QVERIFY(fakeFolder.syncOnce());
  288. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  289. QCOMPARE(counter.nGET, 0);
  290. // Local change plus remote move at the same time
  291. fakeFolder.localModifier().appendByte(prefix + "/a2");
  292. remote.rename(prefix + "/a2", prefix + "/a3");
  293. QVERIFY(fakeFolder.syncOnce());
  294. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  295. QCOMPARE(counter.nGET, 1);
  296. counter.reset();
  297. // remove localy, and remote move at the same time
  298. fakeFolder.localModifier().remove("A/Q/W/a1m");
  299. remote.rename("A/Q/W/a1m", "A/Q/W/a1p");
  300. remote.rename(prefix + "/A/Q/W/a1m", prefix + "/A/Q/W/a1p");
  301. QVERIFY(fakeFolder.syncOnce());
  302. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  303. QCOMPARE(counter.nGET, 1);
  304. counter.reset();
  305. }
  306. void testMovePropagation()
  307. {
  308. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  309. auto &local = fakeFolder.localModifier();
  310. auto &remote = fakeFolder.remoteModifier();
  311. OperationCounter counter;
  312. fakeFolder.setServerOverride(counter.functor());
  313. // Move
  314. {
  315. counter.reset();
  316. local.rename("A/a1", "A/a1m");
  317. remote.rename("B/b1", "B/b1m");
  318. ItemCompletedSpy completeSpy(fakeFolder);
  319. QVERIFY(fakeFolder.syncOnce());
  320. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  321. QCOMPARE(counter.nGET, 0);
  322. QCOMPARE(counter.nPUT, 0);
  323. QCOMPARE(counter.nMOVE, 1);
  324. QCOMPARE(counter.nDELETE, 0);
  325. QVERIFY(itemSuccessfulMove(completeSpy, "A/a1m"));
  326. QVERIFY(itemSuccessfulMove(completeSpy, "B/b1m"));
  327. QCOMPARE(completeSpy.findItem("A/a1m")->_file, QStringLiteral("A/a1"));
  328. QCOMPARE(completeSpy.findItem("A/a1m")->_renameTarget, QStringLiteral("A/a1m"));
  329. QCOMPARE(completeSpy.findItem("B/b1m")->_file, QStringLiteral("B/b1"));
  330. QCOMPARE(completeSpy.findItem("B/b1m")->_renameTarget, QStringLiteral("B/b1m"));
  331. }
  332. // Touch+Move on same side
  333. counter.reset();
  334. local.rename("A/a2", "A/a2m");
  335. local.setContents("A/a2m", 'A');
  336. remote.rename("B/b2", "B/b2m");
  337. remote.setContents("B/b2m", 'A');
  338. QVERIFY(fakeFolder.syncOnce());
  339. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  340. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState()));
  341. QCOMPARE(counter.nGET, 1);
  342. QCOMPARE(counter.nPUT, 1);
  343. QCOMPARE(counter.nMOVE, 0);
  344. QCOMPARE(counter.nDELETE, 1);
  345. QCOMPARE(remote.find("A/a2m")->contentChar, 'A');
  346. QCOMPARE(remote.find("B/b2m")->contentChar, 'A');
  347. // Touch+Move on opposite sides
  348. counter.reset();
  349. local.rename("A/a1m", "A/a1m2");
  350. remote.setContents("A/a1m", 'B');
  351. remote.rename("B/b1m", "B/b1m2");
  352. local.setContents("B/b1m", 'B');
  353. QVERIFY(fakeFolder.syncOnce());
  354. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  355. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState()));
  356. QCOMPARE(counter.nGET, 2);
  357. QCOMPARE(counter.nPUT, 2);
  358. QCOMPARE(counter.nMOVE, 0);
  359. QCOMPARE(counter.nDELETE, 0);
  360. // All these files existing afterwards is debatable. Should we propagate
  361. // the rename in one direction and grab the new contents in the other?
  362. // Currently there's no propagation job that would do that, and this does
  363. // at least not lose data.
  364. QCOMPARE(remote.find("A/a1m")->contentChar, 'B');
  365. QCOMPARE(remote.find("B/b1m")->contentChar, 'B');
  366. QCOMPARE(remote.find("A/a1m2")->contentChar, 'W');
  367. QCOMPARE(remote.find("B/b1m2")->contentChar, 'W');
  368. // Touch+create on one side, move on the other
  369. {
  370. counter.reset();
  371. local.appendByte("A/a1m");
  372. local.insert("A/a1mt");
  373. remote.rename("A/a1m", "A/a1mt");
  374. remote.appendByte("B/b1m");
  375. remote.insert("B/b1mt");
  376. local.rename("B/b1m", "B/b1mt");
  377. ItemCompletedSpy completeSpy(fakeFolder);
  378. QVERIFY(fakeFolder.syncOnce());
  379. QVERIFY(expectAndWipeConflict(local, fakeFolder.currentLocalState(), "A/a1mt"));
  380. QVERIFY(expectAndWipeConflict(local, fakeFolder.currentLocalState(), "B/b1mt"));
  381. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  382. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState()));
  383. QCOMPARE(counter.nGET, 3);
  384. QCOMPARE(counter.nPUT, 1);
  385. QCOMPARE(counter.nMOVE, 0);
  386. QCOMPARE(counter.nDELETE, 0);
  387. QVERIFY(itemSuccessful(completeSpy, "A/a1m", CSYNC_INSTRUCTION_NEW));
  388. QVERIFY(itemSuccessful(completeSpy, "B/b1m", CSYNC_INSTRUCTION_NEW));
  389. QVERIFY(itemConflict(completeSpy, "A/a1mt"));
  390. QVERIFY(itemConflict(completeSpy, "B/b1mt"));
  391. }
  392. // Create new on one side, move to new on the other
  393. {
  394. counter.reset();
  395. local.insert("A/a1N", 13);
  396. remote.rename("A/a1mt", "A/a1N");
  397. remote.insert("B/b1N", 13);
  398. local.rename("B/b1mt", "B/b1N");
  399. ItemCompletedSpy completeSpy(fakeFolder);
  400. QVERIFY(fakeFolder.syncOnce());
  401. QVERIFY(expectAndWipeConflict(local, fakeFolder.currentLocalState(), "A/a1N"));
  402. QVERIFY(expectAndWipeConflict(local, fakeFolder.currentLocalState(), "B/b1N"));
  403. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  404. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState()));
  405. QCOMPARE(counter.nGET, 2);
  406. QCOMPARE(counter.nPUT, 0);
  407. QCOMPARE(counter.nMOVE, 0);
  408. QCOMPARE(counter.nDELETE, 1);
  409. QVERIFY(itemSuccessful(completeSpy, "A/a1mt", CSYNC_INSTRUCTION_REMOVE));
  410. QVERIFY(itemSuccessful(completeSpy, "B/b1mt", CSYNC_INSTRUCTION_REMOVE));
  411. QVERIFY(itemConflict(completeSpy, "A/a1N"));
  412. QVERIFY(itemConflict(completeSpy, "B/b1N"));
  413. }
  414. // Local move, remote move
  415. counter.reset();
  416. local.rename("C/c1", "C/c1mL");
  417. remote.rename("C/c1", "C/c1mR");
  418. QVERIFY(fakeFolder.syncOnce());
  419. // end up with both files
  420. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  421. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState()));
  422. QCOMPARE(counter.nGET, 1);
  423. QCOMPARE(counter.nPUT, 1);
  424. QCOMPARE(counter.nMOVE, 0);
  425. QCOMPARE(counter.nDELETE, 0);
  426. // Rename/rename conflict on a folder
  427. counter.reset();
  428. remote.rename("C", "CMR");
  429. local.rename("C", "CML");
  430. QVERIFY(fakeFolder.syncOnce());
  431. // End up with both folders
  432. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  433. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState()));
  434. QCOMPARE(counter.nGET, 3); // 3 files in C
  435. QCOMPARE(counter.nPUT, 3);
  436. QCOMPARE(counter.nMOVE, 0);
  437. QCOMPARE(counter.nDELETE, 0);
  438. // Folder move
  439. {
  440. counter.reset();
  441. local.rename("A", "AM");
  442. remote.rename("B", "BM");
  443. ItemCompletedSpy completeSpy(fakeFolder);
  444. QVERIFY(fakeFolder.syncOnce());
  445. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  446. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState()));
  447. QCOMPARE(counter.nGET, 0);
  448. QCOMPARE(counter.nPUT, 0);
  449. QCOMPARE(counter.nMOVE, 1);
  450. QCOMPARE(counter.nDELETE, 0);
  451. QVERIFY(itemSuccessfulMove(completeSpy, "AM"));
  452. QVERIFY(itemSuccessfulMove(completeSpy, "BM"));
  453. QCOMPARE(completeSpy.findItem("AM")->_file, QStringLiteral("A"));
  454. QCOMPARE(completeSpy.findItem("AM")->_renameTarget, QStringLiteral("AM"));
  455. QCOMPARE(completeSpy.findItem("BM")->_file, QStringLiteral("B"));
  456. QCOMPARE(completeSpy.findItem("BM")->_renameTarget, QStringLiteral("BM"));
  457. }
  458. // Folder move with contents touched on the same side
  459. {
  460. counter.reset();
  461. local.setContents("AM/a2m", 'C');
  462. // We must change the modtime for it is likely that it did not change between sync.
  463. // (Previous version of the client (<=2.5) would not need this because it was always doing
  464. // checksum comparison for all renames. But newer version no longer does it if the file is
  465. // renamed because the parent folder is renamed)
  466. local.setModTime("AM/a2m", QDateTime::currentDateTimeUtc().addDays(3));
  467. local.rename("AM", "A2");
  468. remote.setContents("BM/b2m", 'C');
  469. remote.rename("BM", "B2");
  470. ItemCompletedSpy completeSpy(fakeFolder);
  471. QVERIFY(fakeFolder.syncOnce());
  472. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  473. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState()));
  474. QCOMPARE(counter.nGET, 1);
  475. QCOMPARE(counter.nPUT, 1);
  476. QCOMPARE(counter.nMOVE, 1);
  477. QCOMPARE(counter.nDELETE, 0);
  478. QCOMPARE(remote.find("A2/a2m")->contentChar, 'C');
  479. QCOMPARE(remote.find("B2/b2m")->contentChar, 'C');
  480. QVERIFY(itemSuccessfulMove(completeSpy, "A2"));
  481. QVERIFY(itemSuccessfulMove(completeSpy, "B2"));
  482. }
  483. // Folder rename with contents touched on the other tree
  484. counter.reset();
  485. remote.setContents("A2/a2m", 'D');
  486. // setContents alone may not produce updated mtime if the test is fast
  487. // and since we don't use checksums here, that matters.
  488. remote.appendByte("A2/a2m");
  489. local.rename("A2", "A3");
  490. local.setContents("B2/b2m", 'D');
  491. local.appendByte("B2/b2m");
  492. remote.rename("B2", "B3");
  493. QVERIFY(fakeFolder.syncOnce());
  494. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  495. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState()));
  496. QCOMPARE(counter.nGET, 1);
  497. QCOMPARE(counter.nPUT, 1);
  498. QCOMPARE(counter.nMOVE, 1);
  499. QCOMPARE(counter.nDELETE, 0);
  500. QCOMPARE(remote.find("A3/a2m")->contentChar, 'D');
  501. QCOMPARE(remote.find("B3/b2m")->contentChar, 'D');
  502. // Folder rename with contents touched on both ends
  503. counter.reset();
  504. remote.setContents("A3/a2m", 'R');
  505. remote.appendByte("A3/a2m");
  506. local.setContents("A3/a2m", 'L');
  507. local.appendByte("A3/a2m");
  508. local.appendByte("A3/a2m");
  509. local.rename("A3", "A4");
  510. remote.setContents("B3/b2m", 'R');
  511. remote.appendByte("B3/b2m");
  512. local.setContents("B3/b2m", 'L');
  513. local.appendByte("B3/b2m");
  514. local.appendByte("B3/b2m");
  515. remote.rename("B3", "B4");
  516. QVERIFY(fakeFolder.syncOnce());
  517. auto currentLocal = fakeFolder.currentLocalState();
  518. auto conflicts = findConflicts(currentLocal.children["A4"]);
  519. QCOMPARE(conflicts.size(), 1);
  520. for (const auto& c : conflicts) {
  521. QCOMPARE(currentLocal.find(c)->contentChar, 'L');
  522. local.remove(c);
  523. }
  524. conflicts = findConflicts(currentLocal.children["B4"]);
  525. QCOMPARE(conflicts.size(), 1);
  526. for (const auto& c : qAsConst(conflicts)) {
  527. QCOMPARE(currentLocal.find(c)->contentChar, 'L');
  528. local.remove(c);
  529. }
  530. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  531. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState()));
  532. QCOMPARE(counter.nGET, 2);
  533. QCOMPARE(counter.nPUT, 0);
  534. QCOMPARE(counter.nMOVE, 1);
  535. QCOMPARE(counter.nDELETE, 0);
  536. QCOMPARE(remote.find("A4/a2m")->contentChar, 'R');
  537. QCOMPARE(remote.find("B4/b2m")->contentChar, 'R');
  538. // Rename a folder and rename the contents at the same time
  539. counter.reset();
  540. local.rename("A4/a2m", "A4/a2m2");
  541. local.rename("A4", "A5");
  542. remote.rename("B4/b2m", "B4/b2m2");
  543. remote.rename("B4", "B5");
  544. QVERIFY(fakeFolder.syncOnce());
  545. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  546. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState()));
  547. QCOMPARE(counter.nGET, 0);
  548. QCOMPARE(counter.nPUT, 0);
  549. QCOMPARE(counter.nMOVE, 2);
  550. QCOMPARE(counter.nDELETE, 0);
  551. }
  552. // These renames can be troublesome on windows
  553. void testRenameCaseOnly()
  554. {
  555. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  556. auto &local = fakeFolder.localModifier();
  557. auto &remote = fakeFolder.remoteModifier();
  558. OperationCounter counter;
  559. fakeFolder.setServerOverride(counter.functor());
  560. local.rename("A/a1", "A/A1");
  561. remote.rename("A/a2", "A/A2");
  562. QVERIFY(fakeFolder.syncOnce());
  563. QCOMPARE(fakeFolder.currentLocalState(), remote);
  564. QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState()));
  565. QCOMPARE(counter.nGET, 0);
  566. QCOMPARE(counter.nPUT, 0);
  567. QCOMPARE(counter.nMOVE, 1);
  568. QCOMPARE(counter.nDELETE, 0);
  569. }
  570. // Check interaction of moves with file type changes
  571. void testMoveAndTypeChange()
  572. {
  573. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  574. auto &local = fakeFolder.localModifier();
  575. auto &remote = fakeFolder.remoteModifier();
  576. // Touch on one side, rename and mkdir on the other
  577. {
  578. local.appendByte("A/a1");
  579. remote.rename("A/a1", "A/a1mq");
  580. remote.mkdir("A/a1");
  581. remote.appendByte("B/b1");
  582. local.rename("B/b1", "B/b1mq");
  583. local.mkdir("B/b1");
  584. ItemCompletedSpy completeSpy(fakeFolder);
  585. QVERIFY(fakeFolder.syncOnce());
  586. // BUG: This doesn't behave right
  587. //QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  588. }
  589. }
  590. // https://github.com/owncloud/client/issues/6629#issuecomment-402450691
  591. // When a file is moved and the server mtime was not in sync, the local mtime should be kept
  592. void testMoveAndMTimeChange()
  593. {
  594. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  595. OperationCounter counter;
  596. fakeFolder.setServerOverride(counter.functor());
  597. // Changing the mtime on the server (without invalidating the etag)
  598. fakeFolder.remoteModifier().find("A/a1")->lastModified = QDateTime::currentDateTimeUtc().addSecs(-50000);
  599. fakeFolder.remoteModifier().find("A/a2")->lastModified = QDateTime::currentDateTimeUtc().addSecs(-40000);
  600. // Move a few files
  601. fakeFolder.remoteModifier().rename("A/a1", "A/a1_server_renamed");
  602. fakeFolder.localModifier().rename("A/a2", "A/a2_local_renamed");
  603. QVERIFY(fakeFolder.syncOnce());
  604. QCOMPARE(counter.nGET, 0);
  605. QCOMPARE(counter.nPUT, 0);
  606. QCOMPARE(counter.nMOVE, 1);
  607. QCOMPARE(counter.nDELETE, 0);
  608. // Another sync should do nothing
  609. QVERIFY(fakeFolder.syncOnce());
  610. QCOMPARE(counter.nGET, 0);
  611. QCOMPARE(counter.nPUT, 0);
  612. QCOMPARE(counter.nMOVE, 1);
  613. QCOMPARE(counter.nDELETE, 0);
  614. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  615. }
  616. // Test for https://github.com/owncloud/client/issues/6694
  617. void testInvertFolderHierarchy()
  618. {
  619. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  620. fakeFolder.remoteModifier().mkdir("A/Empty");
  621. fakeFolder.remoteModifier().mkdir("A/Empty/Foo");
  622. fakeFolder.remoteModifier().mkdir("C/AllEmpty");
  623. fakeFolder.remoteModifier().mkdir("C/AllEmpty/Bar");
  624. fakeFolder.remoteModifier().insert("A/Empty/f1");
  625. fakeFolder.remoteModifier().insert("A/Empty/Foo/f2");
  626. fakeFolder.remoteModifier().mkdir("C/AllEmpty/f3");
  627. fakeFolder.remoteModifier().mkdir("C/AllEmpty/Bar/f4");
  628. QVERIFY(fakeFolder.syncOnce());
  629. OperationCounter counter;
  630. fakeFolder.setServerOverride(counter.functor());
  631. // "Empty" is after "A", alphabetically
  632. fakeFolder.localModifier().rename("A/Empty", "Empty");
  633. fakeFolder.localModifier().rename("A", "Empty/A");
  634. // "AllEmpty" is before "C", alphabetically
  635. fakeFolder.localModifier().rename("C/AllEmpty", "AllEmpty");
  636. fakeFolder.localModifier().rename("C", "AllEmpty/C");
  637. auto expectedState = fakeFolder.currentLocalState();
  638. QVERIFY(fakeFolder.syncOnce());
  639. QCOMPARE(fakeFolder.currentLocalState(), expectedState);
  640. QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
  641. QCOMPARE(counter.nDELETE, 0);
  642. QCOMPARE(counter.nGET, 0);
  643. QCOMPARE(counter.nPUT, 0);
  644. // Now, the revert, but "crossed"
  645. fakeFolder.localModifier().rename("Empty/A", "A");
  646. fakeFolder.localModifier().rename("AllEmpty/C", "C");
  647. fakeFolder.localModifier().rename("Empty", "C/Empty");
  648. fakeFolder.localModifier().rename("AllEmpty", "A/AllEmpty");
  649. expectedState = fakeFolder.currentLocalState();
  650. QVERIFY(fakeFolder.syncOnce());
  651. QCOMPARE(fakeFolder.currentLocalState(), expectedState);
  652. QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
  653. QCOMPARE(counter.nDELETE, 0);
  654. QCOMPARE(counter.nGET, 0);
  655. QCOMPARE(counter.nPUT, 0);
  656. // Reverse on remote
  657. fakeFolder.remoteModifier().rename("A/AllEmpty", "AllEmpty");
  658. fakeFolder.remoteModifier().rename("C/Empty", "Empty");
  659. fakeFolder.remoteModifier().rename("C", "AllEmpty/C");
  660. fakeFolder.remoteModifier().rename("A", "Empty/A");
  661. expectedState = fakeFolder.currentRemoteState();
  662. QVERIFY(fakeFolder.syncOnce());
  663. QCOMPARE(fakeFolder.currentLocalState(), expectedState);
  664. QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
  665. QCOMPARE(counter.nDELETE, 0);
  666. QCOMPARE(counter.nGET, 0);
  667. QCOMPARE(counter.nPUT, 0);
  668. }
  669. void testDeepHierarchy_data()
  670. {
  671. QTest::addColumn<bool>("local");
  672. QTest::newRow("remote") << false;
  673. QTest::newRow("local") << true;
  674. }
  675. void testDeepHierarchy()
  676. {
  677. QFETCH(bool, local);
  678. FakeFolder fakeFolder { FileInfo::A12_B12_C12_S12() };
  679. auto &modifier = local ? fakeFolder.localModifier() : fakeFolder.remoteModifier();
  680. modifier.mkdir("FolA");
  681. modifier.mkdir("FolA/FolB");
  682. modifier.mkdir("FolA/FolB/FolC");
  683. modifier.mkdir("FolA/FolB/FolC/FolD");
  684. modifier.mkdir("FolA/FolB/FolC/FolD/FolE");
  685. modifier.insert("FolA/FileA.txt");
  686. modifier.insert("FolA/FolB/FileB.txt");
  687. modifier.insert("FolA/FolB/FolC/FileC.txt");
  688. modifier.insert("FolA/FolB/FolC/FolD/FileD.txt");
  689. modifier.insert("FolA/FolB/FolC/FolD/FolE/FileE.txt");
  690. QVERIFY(fakeFolder.syncOnce());
  691. OperationCounter counter;
  692. fakeFolder.setServerOverride(counter.functor());
  693. modifier.insert("FolA/FileA2.txt");
  694. modifier.insert("FolA/FolB/FileB2.txt");
  695. modifier.insert("FolA/FolB/FolC/FileC2.txt");
  696. modifier.insert("FolA/FolB/FolC/FolD/FileD2.txt");
  697. modifier.insert("FolA/FolB/FolC/FolD/FolE/FileE2.txt");
  698. modifier.rename("FolA", "FolA_Renamed");
  699. modifier.rename("FolA_Renamed/FolB", "FolB_Renamed");
  700. modifier.rename("FolB_Renamed/FolC", "FolA");
  701. modifier.rename("FolA/FolD", "FolA/FolD_Renamed");
  702. modifier.mkdir("FolB_Renamed/New");
  703. modifier.rename("FolA/FolD_Renamed/FolE", "FolB_Renamed/New/FolE");
  704. auto expected = local ? fakeFolder.currentLocalState() : fakeFolder.currentRemoteState();
  705. QVERIFY(fakeFolder.syncOnce());
  706. QCOMPARE(fakeFolder.currentLocalState(), expected);
  707. QCOMPARE(fakeFolder.currentRemoteState(), expected);
  708. QCOMPARE(counter.nDELETE, local ? 1 : 0); // FolC was is renamed to an existing name, so it is not considered as renamed
  709. // There was 5 inserts
  710. QCOMPARE(counter.nGET, local ? 0 : 5);
  711. QCOMPARE(counter.nPUT, local ? 5 : 0);
  712. }
  713. void renameOnBothSides()
  714. {
  715. FakeFolder fakeFolder { FileInfo::A12_B12_C12_S12() };
  716. OperationCounter counter;
  717. fakeFolder.setServerOverride(counter.functor());
  718. // Test that renaming a file within a directory that was renamed on the other side actually do a rename.
  719. // 1) move the folder alphabeticaly before
  720. fakeFolder.remoteModifier().rename("A/a1", "A/a1m");
  721. fakeFolder.localModifier().rename("A", "_A");
  722. fakeFolder.localModifier().rename("B/b1", "B/b1m");
  723. fakeFolder.remoteModifier().rename("B", "_B");
  724. QVERIFY(fakeFolder.syncOnce());
  725. QCOMPARE(fakeFolder.currentRemoteState(), fakeFolder.currentRemoteState());
  726. QVERIFY(fakeFolder.currentRemoteState().find("_A/a1m"));
  727. QVERIFY(fakeFolder.currentRemoteState().find("_B/b1m"));
  728. QCOMPARE(counter.nDELETE, 0);
  729. QCOMPARE(counter.nGET, 0);
  730. QCOMPARE(counter.nPUT, 0);
  731. QCOMPARE(counter.nMOVE, 2);
  732. counter.reset();
  733. // 2) move alphabetically after
  734. fakeFolder.remoteModifier().rename("_A/a2", "_A/a2m");
  735. fakeFolder.localModifier().rename("_B/b2", "_B/b2m");
  736. fakeFolder.localModifier().rename("_A", "S/A");
  737. fakeFolder.remoteModifier().rename("_B", "S/B");
  738. QVERIFY(fakeFolder.syncOnce());
  739. QCOMPARE(fakeFolder.currentRemoteState(), fakeFolder.currentRemoteState());
  740. QVERIFY(fakeFolder.currentRemoteState().find("S/A/a2m"));
  741. QVERIFY(fakeFolder.currentRemoteState().find("S/B/b2m"));
  742. QCOMPARE(counter.nDELETE, 0);
  743. QCOMPARE(counter.nGET, 0);
  744. QCOMPARE(counter.nPUT, 0);
  745. QCOMPARE(counter.nMOVE, 2);
  746. }
  747. void moveFileToDifferentFolderOnBothSides()
  748. {
  749. FakeFolder fakeFolder { FileInfo::A12_B12_C12_S12() };
  750. OperationCounter counter;
  751. fakeFolder.setServerOverride(counter.functor());
  752. // Test that moving a file within to different folder on both side does the right thing.
  753. fakeFolder.remoteModifier().rename("B/b1", "A/b1");
  754. fakeFolder.localModifier().rename("B/b1", "C/b1");
  755. fakeFolder.localModifier().rename("B/b2", "A/b2");
  756. fakeFolder.remoteModifier().rename("B/b2", "C/b2");
  757. QVERIFY(fakeFolder.syncOnce());
  758. QCOMPARE(fakeFolder.currentRemoteState(), fakeFolder.currentRemoteState());
  759. QVERIFY(fakeFolder.currentRemoteState().find("A/b1"));
  760. QVERIFY(fakeFolder.currentRemoteState().find("C/b1"));
  761. QVERIFY(fakeFolder.currentRemoteState().find("A/b2"));
  762. QVERIFY(fakeFolder.currentRemoteState().find("C/b2"));
  763. QCOMPARE(counter.nMOVE, 0); // Unfortunately, we can't really make a move in this case
  764. QCOMPARE(counter.nGET, 2);
  765. QCOMPARE(counter.nPUT, 2);
  766. QCOMPARE(counter.nDELETE, 0);
  767. counter.reset();
  768. }
  769. // Test that deletes don't run before renames
  770. void testRenameParallelism()
  771. {
  772. FakeFolder fakeFolder{ FileInfo{} };
  773. fakeFolder.remoteModifier().mkdir("A");
  774. fakeFolder.remoteModifier().insert("A/file");
  775. QVERIFY(fakeFolder.syncOnce());
  776. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  777. fakeFolder.localModifier().mkdir("B");
  778. fakeFolder.localModifier().rename("A/file", "B/file");
  779. fakeFolder.localModifier().remove("A");
  780. QVERIFY(fakeFolder.syncOnce());
  781. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  782. }
  783. void testRenameParallelismWithBlacklist()
  784. {
  785. constexpr auto testFileName = "blackListFile";
  786. FakeFolder fakeFolder{ FileInfo{} };
  787. fakeFolder.remoteModifier().mkdir("A");
  788. fakeFolder.remoteModifier().insert("A/file");
  789. QVERIFY(fakeFolder.syncOnce());
  790. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  791. fakeFolder.remoteModifier().insert(testFileName);
  792. fakeFolder.serverErrorPaths().append(testFileName, 500); // will be blacklisted
  793. QVERIFY(!fakeFolder.syncOnce());
  794. fakeFolder.remoteModifier().mkdir("B");
  795. fakeFolder.remoteModifier().rename("A/file", "B/file");
  796. fakeFolder.remoteModifier().remove("A");
  797. QVERIFY(!fakeFolder.syncOnce());
  798. auto folderA = fakeFolder.currentLocalState().find("A");
  799. QCOMPARE(folderA, nullptr);
  800. }
  801. void testMovedWithError_data()
  802. {
  803. QTest::addColumn<Vfs::Mode>("vfsMode");
  804. QTest::newRow("Vfs::Off") << Vfs::Off;
  805. QTest::newRow("Vfs::WithSuffix") << Vfs::WithSuffix;
  806. #ifdef Q_OS_WIN32
  807. if (isVfsPluginAvailable(Vfs::WindowsCfApi))
  808. {
  809. QTest::newRow("Vfs::WindowsCfApi") << Vfs::WindowsCfApi;
  810. } else {
  811. QWARN("Skipping Vfs::WindowsCfApi");
  812. }
  813. #endif
  814. }
  815. void testMovedWithError()
  816. {
  817. QFETCH(Vfs::Mode, vfsMode);
  818. const auto getName = [vfsMode] (const QString &s)
  819. {
  820. if (vfsMode == Vfs::WithSuffix)
  821. {
  822. return QStringLiteral("%1" APPLICATION_DOTVIRTUALFILE_SUFFIX).arg(s);
  823. }
  824. return s;
  825. };
  826. const QString src = "folder/folderA/file.txt";
  827. const QString dest = "folder/folderB/file.txt";
  828. FakeFolder fakeFolder{ FileInfo{ QString(), { FileInfo{ QStringLiteral("folder"), { FileInfo{ QStringLiteral("folderA"), { { QStringLiteral("file.txt"), 400 } } }, QStringLiteral("folderB") } } } } };
  829. auto syncOpts = fakeFolder.syncEngine().syncOptions();
  830. syncOpts._parallelNetworkJobs = 0;
  831. fakeFolder.syncEngine().setSyncOptions(syncOpts);
  832. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  833. if (vfsMode != Vfs::Off)
  834. {
  835. auto vfs = QSharedPointer<Vfs>(createVfsFromPlugin(vfsMode).release());
  836. QVERIFY(vfs);
  837. fakeFolder.switchToVfs(vfs);
  838. fakeFolder.syncJournal().internalPinStates().setForPath("", PinState::OnlineOnly);
  839. // make files virtual
  840. fakeFolder.syncOnce();
  841. }
  842. fakeFolder.serverErrorPaths().append(src, 403);
  843. fakeFolder.localModifier().rename(getName(src), getName(dest));
  844. QVERIFY(!fakeFolder.currentLocalState().find(getName(src)));
  845. QVERIFY(fakeFolder.currentLocalState().find(getName(dest)));
  846. QVERIFY(fakeFolder.currentRemoteState().find(src));
  847. QVERIFY(!fakeFolder.currentRemoteState().find(dest));
  848. // sync1 file gets detected as error, instruction is still NEW_FILE
  849. fakeFolder.syncOnce();
  850. // sync2 file is in error state, checkErrorBlacklisting sets instruction to IGNORED
  851. fakeFolder.syncOnce();
  852. if (vfsMode != Vfs::Off)
  853. {
  854. fakeFolder.syncJournal().internalPinStates().setForPath("", PinState::AlwaysLocal);
  855. fakeFolder.syncOnce();
  856. }
  857. QVERIFY(fakeFolder.currentLocalState().find(getName(src)));
  858. QVERIFY(!fakeFolder.currentLocalState().find(getName(dest)));
  859. if (vfsMode == Vfs::WithSuffix)
  860. {
  861. // the placeholder was not restored as it is still in error state
  862. QVERIFY(!fakeFolder.currentLocalState().find(getName(dest)));
  863. }
  864. QVERIFY(fakeFolder.currentRemoteState().find(src));
  865. QVERIFY(!fakeFolder.currentRemoteState().find(dest));
  866. }
  867. void testRenameDeepHierarchy()
  868. {
  869. FakeFolder fakeFolder{FileInfo{}};
  870. fakeFolder.remoteModifier().mkdir("FolA");
  871. fakeFolder.remoteModifier().mkdir("FolA/FolB2");
  872. fakeFolder.remoteModifier().mkdir("FolA/FolB");
  873. fakeFolder.remoteModifier().mkdir("FolA/FolB/FolC");
  874. fakeFolder.remoteModifier().mkdir("FolA/FolB/FolC/FolD");
  875. fakeFolder.remoteModifier().mkdir("FolA/FolB/FolC/FolD/FolE");
  876. fakeFolder.remoteModifier().insert("FolA/FileA.txt");
  877. auto fileA = fakeFolder.remoteModifier().find("FolA/FileA.txt");
  878. QVERIFY(fileA);
  879. fileA->extraDavProperties = "<oc:checksums><checksum>SHA1:22596363b3de40b06f981fb85d82312e8c0ed511</checksum></oc:checksums>";
  880. fakeFolder.remoteModifier().insert("FolA/FolB/FileB.txt");
  881. auto fileB = fakeFolder.remoteModifier().find("FolA/FolB/FileB.txt");
  882. QVERIFY(fileB);
  883. fileB->extraDavProperties = "<oc:checksums><checksum>SHA1:22596363b3de40b06f981fb85d82312e8c0ed511</checksum></oc:checksums>";
  884. fakeFolder.remoteModifier().insert("FolA/FolB/FolC/FileC.txt");
  885. auto fileC = fakeFolder.remoteModifier().find("FolA/FolB/FolC/FileC.txt");
  886. QVERIFY(fileC);
  887. fileC->extraDavProperties = "<oc:checksums><checksum>SHA1:22596363b3de40b06f981fb85d82312e8c0ed511</checksum></oc:checksums>";
  888. fakeFolder.remoteModifier().insert("FolA/FolB/FolC/FolD/FileD.txt");
  889. auto fileD = fakeFolder.remoteModifier().find("FolA/FolB/FolC/FolD/FileD.txt");
  890. QVERIFY(fileD);
  891. fileD->extraDavProperties = "<oc:checksums><checksum>SHA1:22596363b3de40b06f981fb85d82312e8c0ed511</checksum></oc:checksums>";
  892. fakeFolder.remoteModifier().insert("FolA/FolB/FolC/FolD/FolE/FileE.txt");
  893. auto fileE = fakeFolder.remoteModifier().find("FolA/FolB/FolC/FolD/FolE/FileE.txt");
  894. QVERIFY(fileE);
  895. fileE->extraDavProperties = "<oc:checksums><checksum>SHA1:22596363b3de40b06f981fb85d82312e8c0ed511</checksum></oc:checksums>";
  896. fakeFolder.syncEngine().setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::DatabaseAndFilesystem);
  897. QVERIFY(fakeFolder.syncOnce());
  898. fakeFolder.remoteModifier().rename("FolA/FolB", "FolA/FolB2/FolB");
  899. fakeFolder.syncEngine().setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::DatabaseAndFilesystem);
  900. QVERIFY(fakeFolder.syncOnce());
  901. fakeFolder.remoteModifier().insert("FolA/FolB2/FolB/FolC/FolD/FileD2.txt");
  902. auto fileD2 = fakeFolder.remoteModifier().find("FolA/FolB2/FolB/FolC/FolD/FileD2.txt");
  903. QVERIFY(fileD2);
  904. fileD2->extraDavProperties = "<oc:checksums><checksum>SHA1:22596363b3de40b06f981fb85d82312e8c0ed511</checksum></oc:checksums>";
  905. fakeFolder.remoteModifier().appendByte("FolA/FolB2/FolB/FolC/FolD/FileD.txt");
  906. auto fileDMoved = fakeFolder.remoteModifier().find("FolA/FolB2/FolB/FolC/FolD/FileD.txt");
  907. QVERIFY(fileDMoved);
  908. fileDMoved->extraDavProperties = "<oc:checksums><checksum>SHA1:22596363b3de40b06f981fb85d82312e8c0ed522</checksum></oc:checksums>";
  909. fakeFolder.syncEngine().setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::FilesystemOnly);
  910. QVERIFY(fakeFolder.syncOnce());
  911. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  912. }
  913. };
  914. QTEST_GUILESS_MAIN(TestSyncMove)
  915. #include "testsyncmove.moc"