testsyncjournaldb.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  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. #include <QtTest>
  7. #include <sqlite3.h>
  8. #include "common/syncjournaldb.h"
  9. #include "common/syncjournalfilerecord.h"
  10. using namespace OCC;
  11. class TestSyncJournalDB : public QObject
  12. {
  13. Q_OBJECT
  14. QTemporaryDir _tempDir;
  15. public:
  16. TestSyncJournalDB()
  17. : _db((_tempDir.path() + "/sync.db"))
  18. {
  19. QVERIFY(_tempDir.isValid());
  20. }
  21. qint64 dropMsecs(QDateTime time)
  22. {
  23. return Utility::qDateTimeToTime_t(time);
  24. }
  25. private slots:
  26. void initTestCase()
  27. {
  28. }
  29. void cleanupTestCase()
  30. {
  31. const QString file = _db.databaseFilePath();
  32. QFile::remove(file);
  33. }
  34. void testFileRecord()
  35. {
  36. SyncJournalFileRecord record;
  37. QVERIFY(_db.getFileRecord(QByteArrayLiteral("nonexistant"), &record));
  38. QVERIFY(!record.isValid());
  39. record._path = "foo";
  40. // Use a value that exceeds uint32 and isn't representable by the
  41. // signed int being cast to uint64 either (like uint64::max would be)
  42. record._inode = std::numeric_limits<quint32>::max() + 12ull;
  43. record._modtime = dropMsecs(QDateTime::currentDateTime());
  44. record._type = ItemTypeDirectory;
  45. record._etag = "789789";
  46. record._fileId = "abcd";
  47. record._remotePerm = RemotePermissions("RW");
  48. record._fileSize = 213089055;
  49. record._checksumHeader = "MD5:mychecksum";
  50. QVERIFY(_db.setFileRecord(record));
  51. SyncJournalFileRecord storedRecord;
  52. QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo"), &storedRecord));
  53. QVERIFY(storedRecord == record);
  54. // Update checksum
  55. record._checksumHeader = "Adler32:newchecksum";
  56. _db.updateFileRecordChecksum("foo", "newchecksum", "Adler32");
  57. QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo"), &storedRecord));
  58. QVERIFY(storedRecord == record);
  59. // Update metadata
  60. record._modtime = dropMsecs(QDateTime::currentDateTime().addDays(1));
  61. // try a value that only fits uint64, not int64
  62. record._inode = std::numeric_limits<quint64>::max() - std::numeric_limits<quint32>::max() - 1;
  63. record._type = ItemTypeFile;
  64. record._etag = "789FFF";
  65. record._fileId = "efg";
  66. record._remotePerm = RemotePermissions("NV");
  67. record._fileSize = 289055;
  68. _db.setFileRecordMetadata(record);
  69. QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo"), &storedRecord));
  70. QVERIFY(storedRecord == record);
  71. QVERIFY(_db.deleteFileRecord("foo"));
  72. QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo"), &record));
  73. QVERIFY(!record.isValid());
  74. }
  75. void testFileRecordChecksum()
  76. {
  77. // Try with and without a checksum
  78. {
  79. SyncJournalFileRecord record;
  80. record._path = "foo-checksum";
  81. record._remotePerm = RemotePermissions("RW");
  82. record._checksumHeader = "MD5:mychecksum";
  83. record._modtime = Utility::qDateTimeToTime_t(QDateTime::currentDateTimeUtc());
  84. QVERIFY(_db.setFileRecord(record));
  85. SyncJournalFileRecord storedRecord;
  86. QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo-checksum"), &storedRecord));
  87. QVERIFY(storedRecord._path == record._path);
  88. QVERIFY(storedRecord._remotePerm == record._remotePerm);
  89. QVERIFY(storedRecord._checksumHeader == record._checksumHeader);
  90. // qDebug()<< "OOOOO " << storedRecord._modtime.toTime_t() << record._modtime.toTime_t();
  91. // Attention: compare time_t types here, as QDateTime seem to maintain
  92. // milliseconds internally, which disappear in sqlite. Go for full seconds here.
  93. QVERIFY(storedRecord._modtime == record._modtime);
  94. QVERIFY(storedRecord == record);
  95. }
  96. {
  97. SyncJournalFileRecord record;
  98. record._path = "foo-nochecksum";
  99. record._remotePerm = RemotePermissions("RWN");
  100. record._modtime = Utility::qDateTimeToTime_t(QDateTime::currentDateTimeUtc());
  101. QVERIFY(_db.setFileRecord(record));
  102. SyncJournalFileRecord storedRecord;
  103. QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo-nochecksum"), &storedRecord));
  104. QVERIFY(storedRecord == record);
  105. }
  106. }
  107. void testDownloadInfo()
  108. {
  109. using Info = SyncJournalDb::DownloadInfo;
  110. Info record = _db.getDownloadInfo("nonexistant");
  111. QVERIFY(!record._valid);
  112. record._errorCount = 5;
  113. record._etag = "ABCDEF";
  114. record._valid = true;
  115. record._tmpfile = "/tmp/foo";
  116. _db.setDownloadInfo("foo", record);
  117. Info storedRecord = _db.getDownloadInfo("foo");
  118. QVERIFY(storedRecord == record);
  119. _db.setDownloadInfo("foo", Info());
  120. Info wipedRecord = _db.getDownloadInfo("foo");
  121. QVERIFY(!wipedRecord._valid);
  122. }
  123. void testUploadInfo()
  124. {
  125. using Info = SyncJournalDb::UploadInfo;
  126. Info record = _db.getUploadInfo("nonexistant");
  127. QVERIFY(!record._valid);
  128. record._errorCount = 5;
  129. record._chunk = 12;
  130. record._transferid = 812974891;
  131. record._size = 12894789147;
  132. record._modtime = dropMsecs(QDateTime::currentDateTime());
  133. record._valid = true;
  134. _db.setUploadInfo("foo", record);
  135. Info storedRecord = _db.getUploadInfo("foo");
  136. QVERIFY(storedRecord == record);
  137. _db.setUploadInfo("foo", Info());
  138. Info wipedRecord = _db.getUploadInfo("foo");
  139. QVERIFY(!wipedRecord._valid);
  140. }
  141. void testNumericId()
  142. {
  143. SyncJournalFileRecord record;
  144. // Typical 8-digit padded id
  145. record._fileId = "00000001abcd";
  146. QCOMPARE(record.numericFileId(), QByteArray("00000001"));
  147. // When the numeric id overflows the 8-digit boundary
  148. record._fileId = "123456789ocidblaabcd";
  149. QCOMPARE(record.numericFileId(), QByteArray("123456789"));
  150. }
  151. void testConflictRecord()
  152. {
  153. ConflictRecord record;
  154. record.path = "abc";
  155. record.baseFileId = "def";
  156. record.baseModtime = 1234;
  157. record.baseEtag = "ghi";
  158. QVERIFY(!_db.conflictRecord(record.path).isValid());
  159. _db.setConflictRecord(record);
  160. auto newRecord = _db.conflictRecord(record.path);
  161. QVERIFY(newRecord.isValid());
  162. QCOMPARE(newRecord.path, record.path);
  163. QCOMPARE(newRecord.baseFileId, record.baseFileId);
  164. QCOMPARE(newRecord.baseModtime, record.baseModtime);
  165. QCOMPARE(newRecord.baseEtag, record.baseEtag);
  166. _db.deleteConflictRecord(record.path);
  167. QVERIFY(!_db.conflictRecord(record.path).isValid());
  168. }
  169. void testAvoidReadFromDbOnNextSync()
  170. {
  171. auto invalidEtag = QByteArray("_invalid_");
  172. auto initialEtag = QByteArray("etag");
  173. auto makeEntry = [&](const QByteArray &path, ItemType type) {
  174. SyncJournalFileRecord record;
  175. record._path = path;
  176. record._type = type;
  177. record._etag = initialEtag;
  178. _db.setFileRecord(record);
  179. };
  180. auto getEtag = [&](const QByteArray &path) {
  181. SyncJournalFileRecord record;
  182. _db.getFileRecord(path, &record);
  183. return record._etag;
  184. };
  185. makeEntry("foodir", ItemTypeDirectory);
  186. makeEntry("otherdir", ItemTypeDirectory);
  187. makeEntry("foo%", ItemTypeDirectory); // wildcards don't apply
  188. makeEntry("foodi_", ItemTypeDirectory); // wildcards don't apply
  189. makeEntry("foodir/file", ItemTypeFile);
  190. makeEntry("foodir/subdir", ItemTypeDirectory);
  191. makeEntry("foodir/subdir/file", ItemTypeFile);
  192. makeEntry("foodir/otherdir", ItemTypeDirectory);
  193. makeEntry("fo", ItemTypeDirectory); // prefix, but does not match
  194. makeEntry("foodir/sub", ItemTypeDirectory); // prefix, but does not match
  195. makeEntry("foodir/subdir/subsubdir", ItemTypeDirectory);
  196. makeEntry("foodir/subdir/subsubdir/file", ItemTypeFile);
  197. makeEntry("foodir/subdir/otherdir", ItemTypeDirectory);
  198. _db.avoidReadFromDbOnNextSync(QByteArray("foodir/subdir"));
  199. // Direct effects of parent directories being set to _invalid_
  200. QCOMPARE(getEtag("foodir"), invalidEtag);
  201. QCOMPARE(getEtag("foodir/subdir"), invalidEtag);
  202. QCOMPARE(getEtag("foodir/subdir/subsubdir"), initialEtag);
  203. QCOMPARE(getEtag("foodir/file"), initialEtag);
  204. QCOMPARE(getEtag("foodir/subdir/file"), initialEtag);
  205. QCOMPARE(getEtag("foodir/subdir/subsubdir/file"), initialEtag);
  206. QCOMPARE(getEtag("fo"), initialEtag);
  207. QCOMPARE(getEtag("foo%"), initialEtag);
  208. QCOMPARE(getEtag("foodi_"), initialEtag);
  209. QCOMPARE(getEtag("otherdir"), initialEtag);
  210. QCOMPARE(getEtag("foodir/otherdir"), initialEtag);
  211. QCOMPARE(getEtag("foodir/sub"), initialEtag);
  212. QCOMPARE(getEtag("foodir/subdir/otherdir"), initialEtag);
  213. // Indirect effects: setFileRecord() calls filter etags
  214. initialEtag = "etag2";
  215. makeEntry("foodir", ItemTypeDirectory);
  216. QCOMPARE(getEtag("foodir"), invalidEtag);
  217. makeEntry("foodir/subdir", ItemTypeDirectory);
  218. QCOMPARE(getEtag("foodir/subdir"), invalidEtag);
  219. makeEntry("foodir/subdir/subsubdir", ItemTypeDirectory);
  220. QCOMPARE(getEtag("foodir/subdir/subsubdir"), initialEtag);
  221. makeEntry("fo", ItemTypeDirectory);
  222. QCOMPARE(getEtag("fo"), initialEtag);
  223. makeEntry("foodir/sub", ItemTypeDirectory);
  224. QCOMPARE(getEtag("foodir/sub"), initialEtag);
  225. }
  226. void testRecursiveDelete()
  227. {
  228. auto makeEntry = [&](const QByteArray &path) {
  229. SyncJournalFileRecord record;
  230. record._path = path;
  231. _db.setFileRecord(record);
  232. };
  233. QByteArrayList elements;
  234. elements
  235. << "foo"
  236. << "foo/file"
  237. << "bar"
  238. << "moo"
  239. << "moo/file"
  240. << "foo%bar"
  241. << "foo bla bar/file"
  242. << "fo_"
  243. << "fo_/file";
  244. for (const auto& elem : elements)
  245. makeEntry(elem);
  246. auto checkElements = [&]() {
  247. bool ok = true;
  248. for (const auto& elem : elements) {
  249. SyncJournalFileRecord record;
  250. _db.getFileRecord(elem, &record);
  251. if (!record.isValid()) {
  252. qWarning() << "Missing record: " << elem;
  253. ok = false;
  254. }
  255. }
  256. return ok;
  257. };
  258. _db.deleteFileRecord("moo", true);
  259. elements.removeAll("moo");
  260. elements.removeAll("moo/file");
  261. QVERIFY(checkElements());
  262. _db.deleteFileRecord("fo_", true);
  263. elements.removeAll("fo_");
  264. elements.removeAll("fo_/file");
  265. QVERIFY(checkElements());
  266. _db.deleteFileRecord("foo%bar", true);
  267. elements.removeAll("foo%bar");
  268. QVERIFY(checkElements());
  269. }
  270. private:
  271. SyncJournalDb _db;
  272. };
  273. QTEST_APPLESS_MAIN(TestSyncJournalDB)
  274. #include "testsyncjournaldb.moc"