testchunkingng.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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 <syncengine.h>
  10. using namespace OCC;
  11. /* Upload a 1/3 of a file of given size.
  12. * fakeFolder needs to be synchronized */
  13. static void partialUpload(FakeFolder &fakeFolder, const QString &name, int size)
  14. {
  15. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  16. QCOMPARE(fakeFolder.uploadState().children.count(), 0); // The state should be clean
  17. fakeFolder.localModifier().insert(name, size);
  18. // Abort when the upload is at 1/3
  19. int sizeWhenAbort = -1;
  20. auto con = QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::transmissionProgress,
  21. [&](const ProgressInfo &progress) {
  22. if (progress.completedSize() > (progress.totalSize() /3 )) {
  23. sizeWhenAbort = progress.completedSize();
  24. fakeFolder.syncEngine().abort();
  25. }
  26. });
  27. QVERIFY(!fakeFolder.syncOnce()); // there should have been an error
  28. QObject::disconnect(con);
  29. QVERIFY(sizeWhenAbort > 0);
  30. QVERIFY(sizeWhenAbort < size);
  31. QCOMPARE(fakeFolder.uploadState().children.count(), 1); // the transfer was done with chunking
  32. auto upStateChildren = fakeFolder.uploadState().children.first().children;
  33. QCOMPARE(sizeWhenAbort, std::accumulate(upStateChildren.cbegin(), upStateChildren.cend(), 0,
  34. [](int s, const FileInfo &i) { return s + i.size; }));
  35. }
  36. class TestChunkingNG : public QObject
  37. {
  38. Q_OBJECT
  39. private slots:
  40. void testFileUpload() {
  41. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  42. fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"chunking", "1.0"} } } });
  43. const int size = 300 * 1000 * 1000; // 300 MB
  44. fakeFolder.localModifier().insert("A/a0", size);
  45. QVERIFY(fakeFolder.syncOnce());
  46. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  47. QCOMPARE(fakeFolder.uploadState().children.count(), 1); // the transfer was done with chunking
  48. QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size);
  49. // Check that another upload of the same file also work.
  50. fakeFolder.localModifier().appendByte("A/a0");
  51. QVERIFY(fakeFolder.syncOnce());
  52. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  53. QCOMPARE(fakeFolder.uploadState().children.count(), 2); // the transfer was done with chunking
  54. }
  55. void testResume () {
  56. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  57. fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"chunking", "1.0"} } } });
  58. const int size = 300 * 1000 * 1000; // 300 MB
  59. partialUpload(fakeFolder, "A/a0", size);
  60. QCOMPARE(fakeFolder.uploadState().children.count(), 1);
  61. auto chunkingId = fakeFolder.uploadState().children.first().name;
  62. // Add a fake file to make sure it gets deleted
  63. fakeFolder.uploadState().children.first().insert("10000", size);
  64. QVERIFY(fakeFolder.syncOnce());
  65. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  66. QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size);
  67. // The same chunk id was re-used
  68. QCOMPARE(fakeFolder.uploadState().children.count(), 1);
  69. QCOMPARE(fakeFolder.uploadState().children.first().name, chunkingId);
  70. }
  71. // We modify the file locally after it has been partially uploaded
  72. void testRemoveStale1() {
  73. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  74. fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"chunking", "1.0"} } } });
  75. const int size = 300 * 1000 * 1000; // 300 MB
  76. partialUpload(fakeFolder, "A/a0", size);
  77. QCOMPARE(fakeFolder.uploadState().children.count(), 1);
  78. auto chunkingId = fakeFolder.uploadState().children.first().name;
  79. fakeFolder.localModifier().setContents("A/a0", 'B');
  80. fakeFolder.localModifier().appendByte("A/a0");
  81. QVERIFY(fakeFolder.syncOnce());
  82. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  83. QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size + 1);
  84. // A different chunk id was used, and the previous one is removed
  85. QCOMPARE(fakeFolder.uploadState().children.count(), 1);
  86. QVERIFY(fakeFolder.uploadState().children.first().name != chunkingId);
  87. }
  88. // We remove the file locally after it has been partially uploaded
  89. void testRemoveStale2() {
  90. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  91. fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"chunking", "1.0"} } } });
  92. const int size = 300 * 1000 * 1000; // 300 MB
  93. partialUpload(fakeFolder, "A/a0", size);
  94. QCOMPARE(fakeFolder.uploadState().children.count(), 1);
  95. fakeFolder.localModifier().remove("A/a0");
  96. QVERIFY(fakeFolder.syncOnce());
  97. QCOMPARE(fakeFolder.uploadState().children.count(), 0);
  98. }
  99. void testCreateConflictWhileSyncing() {
  100. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  101. fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"chunking", "1.0"} } } });
  102. const int size = 150 * 1000 * 1000; // 150 MB
  103. // Put a file on the server and download it.
  104. fakeFolder.remoteModifier().insert("A/a0", size);
  105. QVERIFY(fakeFolder.syncOnce());
  106. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  107. // Modify the file localy and start the upload
  108. fakeFolder.localModifier().setContents("A/a0", 'B');
  109. fakeFolder.localModifier().appendByte("A/a0");
  110. // But in the middle of the sync, modify the file on the server
  111. QMetaObject::Connection con = QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::transmissionProgress,
  112. [&](const ProgressInfo &progress) {
  113. if (progress.completedSize() > (progress.totalSize() / 2 )) {
  114. fakeFolder.remoteModifier().setContents("A/a0", 'C');
  115. QObject::disconnect(con);
  116. }
  117. });
  118. QVERIFY(!fakeFolder.syncOnce());
  119. // There was a precondition failed error, this means wen need to sync again
  120. QCOMPARE(fakeFolder.syncEngine().isAnotherSyncNeeded(), ImmediateFollowUp);
  121. QCOMPARE(fakeFolder.uploadState().children.count(), 1); // We did not clean the chunks at this point
  122. // Now we will download the server file and create a conflict
  123. QVERIFY(fakeFolder.syncOnce());
  124. auto localState = fakeFolder.currentLocalState();
  125. // A0 is the one from the server
  126. QCOMPARE(localState.find("A/a0")->size, size);
  127. QCOMPARE(localState.find("A/a0")->contentChar, 'C');
  128. // There is a conflict file with our version
  129. auto &stateAChildren = localState.find("A")->children;
  130. auto it = std::find_if(stateAChildren.cbegin(), stateAChildren.cend(), [&](const FileInfo &fi) {
  131. return fi.name.startsWith("a0_conflict");
  132. });
  133. QVERIFY(it != stateAChildren.cend());
  134. QCOMPARE(it->contentChar, 'B');
  135. QCOMPARE(it->size, size+1);
  136. // Remove the conflict file so the comparison works!
  137. fakeFolder.localModifier().remove("A/" + it->name);
  138. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  139. QCOMPARE(fakeFolder.uploadState().children.count(), 0); // The last sync cleaned the chunks
  140. }
  141. void testModifyLocalFileWhileUploading() {
  142. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  143. fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"chunking", "1.0"} } } });
  144. const int size = 150 * 1000 * 1000; // 150 MB
  145. fakeFolder.localModifier().insert("A/a0", size);
  146. // middle of the sync, modify the file
  147. QMetaObject::Connection con = QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::transmissionProgress,
  148. [&](const ProgressInfo &progress) {
  149. if (progress.completedSize() > (progress.totalSize() / 2 )) {
  150. fakeFolder.localModifier().setContents("A/a0", 'B');
  151. fakeFolder.localModifier().appendByte("A/a0");
  152. QObject::disconnect(con);
  153. }
  154. });
  155. QVERIFY(!fakeFolder.syncOnce());
  156. // There should be a followup sync
  157. QCOMPARE(fakeFolder.syncEngine().isAnotherSyncNeeded(), ImmediateFollowUp);
  158. QCOMPARE(fakeFolder.uploadState().children.count(), 1); // We did not clean the chunks at this point
  159. auto chunkingId = fakeFolder.uploadState().children.first().name;
  160. // Now we make a new sync which should upload the file for good.
  161. QVERIFY(fakeFolder.syncOnce());
  162. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  163. QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size+1);
  164. // A different chunk id was used, and the previous one is removed
  165. QCOMPARE(fakeFolder.uploadState().children.count(), 1);
  166. QVERIFY(fakeFolder.uploadState().children.first().name != chunkingId);
  167. }
  168. void testResumeServerDeletedChunks() {
  169. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  170. fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"chunking", "1.0"} } } });
  171. const int size = 300 * 1000 * 1000; // 300 MB
  172. partialUpload(fakeFolder, "A/a0", size);
  173. QCOMPARE(fakeFolder.uploadState().children.count(), 1);
  174. auto chunkingId = fakeFolder.uploadState().children.first().name;
  175. // Delete the chunks on the server
  176. fakeFolder.uploadState().children.clear();
  177. QVERIFY(fakeFolder.syncOnce());
  178. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  179. QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size);
  180. // A different chunk id was used
  181. QCOMPARE(fakeFolder.uploadState().children.count(), 1);
  182. QVERIFY(fakeFolder.uploadState().children.first().name != chunkingId);
  183. }
  184. };
  185. QTEST_GUILESS_MAIN(TestChunkingNG)
  186. #include "testchunkingng.moc"