testsyncjournaldb.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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::fromDbValue("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::fromDbValue("NV");
  67. record._fileSize = 289055;
  68. _db.setFileRecord(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::fromDbValue(" ");
  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::fromDbValue("RW");
  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. record._remotePerm = RemotePermissions::fromDbValue("RW");
  179. _db.setFileRecord(record);
  180. };
  181. auto getEtag = [&](const QByteArray &path) {
  182. SyncJournalFileRecord record;
  183. _db.getFileRecord(path, &record);
  184. return record._etag;
  185. };
  186. makeEntry("foodir", ItemTypeDirectory);
  187. makeEntry("otherdir", ItemTypeDirectory);
  188. makeEntry("foo%", ItemTypeDirectory); // wildcards don't apply
  189. makeEntry("foodi_", ItemTypeDirectory); // wildcards don't apply
  190. makeEntry("foodir/file", ItemTypeFile);
  191. makeEntry("foodir/subdir", ItemTypeDirectory);
  192. makeEntry("foodir/subdir/file", ItemTypeFile);
  193. makeEntry("foodir/otherdir", ItemTypeDirectory);
  194. makeEntry("fo", ItemTypeDirectory); // prefix, but does not match
  195. makeEntry("foodir/sub", ItemTypeDirectory); // prefix, but does not match
  196. makeEntry("foodir/subdir/subsubdir", ItemTypeDirectory);
  197. makeEntry("foodir/subdir/subsubdir/file", ItemTypeFile);
  198. makeEntry("foodir/subdir/otherdir", ItemTypeDirectory);
  199. _db.schedulePathForRemoteDiscovery(QByteArray("foodir/subdir"));
  200. // Direct effects of parent directories being set to _invalid_
  201. QCOMPARE(getEtag("foodir"), invalidEtag);
  202. QCOMPARE(getEtag("foodir/subdir"), invalidEtag);
  203. QCOMPARE(getEtag("foodir/subdir/subsubdir"), initialEtag);
  204. QCOMPARE(getEtag("foodir/file"), initialEtag);
  205. QCOMPARE(getEtag("foodir/subdir/file"), initialEtag);
  206. QCOMPARE(getEtag("foodir/subdir/subsubdir/file"), initialEtag);
  207. QCOMPARE(getEtag("fo"), initialEtag);
  208. QCOMPARE(getEtag("foo%"), initialEtag);
  209. QCOMPARE(getEtag("foodi_"), initialEtag);
  210. QCOMPARE(getEtag("otherdir"), initialEtag);
  211. QCOMPARE(getEtag("foodir/otherdir"), initialEtag);
  212. QCOMPARE(getEtag("foodir/sub"), initialEtag);
  213. QCOMPARE(getEtag("foodir/subdir/otherdir"), initialEtag);
  214. // Indirect effects: setFileRecord() calls filter etags
  215. initialEtag = "etag2";
  216. makeEntry("foodir", ItemTypeDirectory);
  217. QCOMPARE(getEtag("foodir"), invalidEtag);
  218. makeEntry("foodir/subdir", ItemTypeDirectory);
  219. QCOMPARE(getEtag("foodir/subdir"), invalidEtag);
  220. makeEntry("foodir/subdir/subsubdir", ItemTypeDirectory);
  221. QCOMPARE(getEtag("foodir/subdir/subsubdir"), initialEtag);
  222. makeEntry("fo", ItemTypeDirectory);
  223. QCOMPARE(getEtag("fo"), initialEtag);
  224. makeEntry("foodir/sub", ItemTypeDirectory);
  225. QCOMPARE(getEtag("foodir/sub"), initialEtag);
  226. }
  227. void testRecursiveDelete()
  228. {
  229. auto makeEntry = [&](const QByteArray &path) {
  230. SyncJournalFileRecord record;
  231. record._path = path;
  232. record._remotePerm = RemotePermissions::fromDbValue("RW");
  233. _db.setFileRecord(record);
  234. };
  235. QByteArrayList elements;
  236. elements
  237. << "foo"
  238. << "foo/file"
  239. << "bar"
  240. << "moo"
  241. << "moo/file"
  242. << "foo%bar"
  243. << "foo bla bar/file"
  244. << "fo_"
  245. << "fo_/file";
  246. for (const auto& elem : elements)
  247. makeEntry(elem);
  248. auto checkElements = [&]() {
  249. bool ok = true;
  250. for (const auto& elem : elements) {
  251. SyncJournalFileRecord record;
  252. _db.getFileRecord(elem, &record);
  253. if (!record.isValid()) {
  254. qWarning() << "Missing record: " << elem;
  255. ok = false;
  256. }
  257. }
  258. return ok;
  259. };
  260. _db.deleteFileRecord("moo", true);
  261. elements.removeAll("moo");
  262. elements.removeAll("moo/file");
  263. QVERIFY(checkElements());
  264. _db.deleteFileRecord("fo_", true);
  265. elements.removeAll("fo_");
  266. elements.removeAll("fo_/file");
  267. QVERIFY(checkElements());
  268. _db.deleteFileRecord("foo%bar", true);
  269. elements.removeAll("foo%bar");
  270. QVERIFY(checkElements());
  271. }
  272. void testPinState()
  273. {
  274. auto make = [&](const QByteArray &path, PinState state) {
  275. _db.internalPinStates().setForPath(path, state);
  276. auto pinState = _db.internalPinStates().rawForPath(path);
  277. QVERIFY(pinState);
  278. QCOMPARE(*pinState, state);
  279. };
  280. auto get = [&](const QByteArray &path) -> PinState {
  281. auto state = _db.internalPinStates().effectiveForPath(path);
  282. if (!state) {
  283. QTest::qFail("couldn't read pin state", __FILE__, __LINE__);
  284. return PinState::Inherited;
  285. }
  286. return *state;
  287. };
  288. auto getRecursive = [&](const QByteArray &path) -> PinState {
  289. auto state = _db.internalPinStates().effectiveForPathRecursive(path);
  290. if (!state) {
  291. QTest::qFail("couldn't read pin state", __FILE__, __LINE__);
  292. return PinState::Inherited;
  293. }
  294. return *state;
  295. };
  296. auto getRaw = [&](const QByteArray &path) -> PinState {
  297. auto state = _db.internalPinStates().rawForPath(path);
  298. if (!state) {
  299. QTest::qFail("couldn't read pin state", __FILE__, __LINE__);
  300. return PinState::Inherited;
  301. }
  302. return *state;
  303. };
  304. _db.internalPinStates().wipeForPathAndBelow("");
  305. auto list = _db.internalPinStates().rawList();
  306. QCOMPARE(list->size(), 0);
  307. // Make a thrice-nested setup
  308. make("", PinState::AlwaysLocal);
  309. make("local", PinState::AlwaysLocal);
  310. make("online", PinState::OnlineOnly);
  311. make("inherit", PinState::Inherited);
  312. for (auto base : {"local/", "online/", "inherit/"}) {
  313. make(QByteArray(base) + "inherit", PinState::Inherited);
  314. make(QByteArray(base) + "local", PinState::AlwaysLocal);
  315. make(QByteArray(base) + "online", PinState::OnlineOnly);
  316. for (auto base2 : {"local/", "online/", "inherit/"}) {
  317. make(QByteArray(base) + base2 + "inherit", PinState::Inherited);
  318. make(QByteArray(base) + base2 + "local", PinState::AlwaysLocal);
  319. make(QByteArray(base) + base2 + "online", PinState::OnlineOnly);
  320. }
  321. }
  322. list = _db.internalPinStates().rawList();
  323. QCOMPARE(list->size(), 4 + 9 + 27);
  324. // Baseline direct checks (the fallback for unset root pinstate is AlwaysLocal)
  325. QCOMPARE(get(""), PinState::AlwaysLocal);
  326. QCOMPARE(get("local"), PinState::AlwaysLocal);
  327. QCOMPARE(get("online"), PinState::OnlineOnly);
  328. QCOMPARE(get("inherit"), PinState::AlwaysLocal);
  329. QCOMPARE(get("nonexistant"), PinState::AlwaysLocal);
  330. QCOMPARE(get("online/local"), PinState::AlwaysLocal);
  331. QCOMPARE(get("local/online"), PinState::OnlineOnly);
  332. QCOMPARE(get("inherit/local"), PinState::AlwaysLocal);
  333. QCOMPARE(get("inherit/online"), PinState::OnlineOnly);
  334. QCOMPARE(get("inherit/inherit"), PinState::AlwaysLocal);
  335. QCOMPARE(get("inherit/nonexistant"), PinState::AlwaysLocal);
  336. // Inheriting checks, level 1
  337. QCOMPARE(get("local/inherit"), PinState::AlwaysLocal);
  338. QCOMPARE(get("local/nonexistant"), PinState::AlwaysLocal);
  339. QCOMPARE(get("online/inherit"), PinState::OnlineOnly);
  340. QCOMPARE(get("online/nonexistant"), PinState::OnlineOnly);
  341. // Inheriting checks, level 2
  342. QCOMPARE(get("local/inherit/inherit"), PinState::AlwaysLocal);
  343. QCOMPARE(get("local/local/inherit"), PinState::AlwaysLocal);
  344. QCOMPARE(get("local/local/nonexistant"), PinState::AlwaysLocal);
  345. QCOMPARE(get("local/online/inherit"), PinState::OnlineOnly);
  346. QCOMPARE(get("local/online/nonexistant"), PinState::OnlineOnly);
  347. QCOMPARE(get("online/inherit/inherit"), PinState::OnlineOnly);
  348. QCOMPARE(get("online/local/inherit"), PinState::AlwaysLocal);
  349. QCOMPARE(get("online/local/nonexistant"), PinState::AlwaysLocal);
  350. QCOMPARE(get("online/online/inherit"), PinState::OnlineOnly);
  351. QCOMPARE(get("online/online/nonexistant"), PinState::OnlineOnly);
  352. // Spot check the recursive variant
  353. QCOMPARE(getRecursive(""), PinState::Inherited);
  354. QCOMPARE(getRecursive("local"), PinState::Inherited);
  355. QCOMPARE(getRecursive("online"), PinState::Inherited);
  356. QCOMPARE(getRecursive("inherit"), PinState::Inherited);
  357. QCOMPARE(getRecursive("online/local"), PinState::Inherited);
  358. QCOMPARE(getRecursive("online/local/inherit"), PinState::AlwaysLocal);
  359. QCOMPARE(getRecursive("inherit/inherit/inherit"), PinState::AlwaysLocal);
  360. QCOMPARE(getRecursive("inherit/online/inherit"), PinState::OnlineOnly);
  361. QCOMPARE(getRecursive("inherit/online/local"), PinState::AlwaysLocal);
  362. make("local/local/local/local", PinState::AlwaysLocal);
  363. QCOMPARE(getRecursive("local/local/local"), PinState::AlwaysLocal);
  364. QCOMPARE(getRecursive("local/local/local/local"), PinState::AlwaysLocal);
  365. // Check changing the root pin state
  366. make("", PinState::OnlineOnly);
  367. QCOMPARE(get("local"), PinState::AlwaysLocal);
  368. QCOMPARE(get("online"), PinState::OnlineOnly);
  369. QCOMPARE(get("inherit"), PinState::OnlineOnly);
  370. QCOMPARE(get("nonexistant"), PinState::OnlineOnly);
  371. make("", PinState::AlwaysLocal);
  372. QCOMPARE(get("local"), PinState::AlwaysLocal);
  373. QCOMPARE(get("online"), PinState::OnlineOnly);
  374. QCOMPARE(get("inherit"), PinState::AlwaysLocal);
  375. QCOMPARE(get("nonexistant"), PinState::AlwaysLocal);
  376. // Wiping
  377. QCOMPARE(getRaw("local/local"), PinState::AlwaysLocal);
  378. _db.internalPinStates().wipeForPathAndBelow("local/local");
  379. QCOMPARE(getRaw("local"), PinState::AlwaysLocal);
  380. QCOMPARE(getRaw("local/local"), PinState::Inherited);
  381. QCOMPARE(getRaw("local/local/local"), PinState::Inherited);
  382. QCOMPARE(getRaw("local/local/online"), PinState::Inherited);
  383. list = _db.internalPinStates().rawList();
  384. QCOMPARE(list->size(), 4 + 9 + 27 - 4);
  385. // Wiping everything
  386. _db.internalPinStates().wipeForPathAndBelow("");
  387. QCOMPARE(getRaw(""), PinState::Inherited);
  388. QCOMPARE(getRaw("local"), PinState::Inherited);
  389. QCOMPARE(getRaw("online"), PinState::Inherited);
  390. list = _db.internalPinStates().rawList();
  391. QCOMPARE(list->size(), 0);
  392. }
  393. private:
  394. SyncJournalDb _db;
  395. };
  396. QTEST_APPLESS_MAIN(TestSyncJournalDB)
  397. #include "testsyncjournaldb.moc"