testsyncjournaldb.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  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. // Attention: compare time_t types here, as QDateTime seem to maintain
  91. // milliseconds internally, which disappear in sqlite. Go for full seconds here.
  92. QVERIFY(storedRecord._modtime == record._modtime);
  93. QVERIFY(storedRecord == record);
  94. }
  95. {
  96. SyncJournalFileRecord record;
  97. record._path = "foo-nochecksum";
  98. record._remotePerm = RemotePermissions::fromDbValue("RW");
  99. record._modtime = Utility::qDateTimeToTime_t(QDateTime::currentDateTimeUtc());
  100. QVERIFY(_db.setFileRecord(record));
  101. SyncJournalFileRecord storedRecord;
  102. QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo-nochecksum"), &storedRecord));
  103. QVERIFY(storedRecord == record);
  104. }
  105. }
  106. void testDownloadInfo()
  107. {
  108. using Info = SyncJournalDb::DownloadInfo;
  109. Info record = _db.getDownloadInfo("nonexistant");
  110. QVERIFY(!record._valid);
  111. record._errorCount = 5;
  112. record._etag = "ABCDEF";
  113. record._valid = true;
  114. record._tmpfile = "/tmp/foo";
  115. _db.setDownloadInfo("foo", record);
  116. Info storedRecord = _db.getDownloadInfo("foo");
  117. QVERIFY(storedRecord == record);
  118. _db.setDownloadInfo("foo", Info());
  119. Info wipedRecord = _db.getDownloadInfo("foo");
  120. QVERIFY(!wipedRecord._valid);
  121. }
  122. void testUploadInfo()
  123. {
  124. using Info = SyncJournalDb::UploadInfo;
  125. Info record = _db.getUploadInfo("nonexistant");
  126. QVERIFY(!record._valid);
  127. record._errorCount = 5;
  128. record._chunk = 12;
  129. record._transferid = 812974891;
  130. record._size = 12894789147;
  131. record._modtime = dropMsecs(QDateTime::currentDateTime());
  132. record._valid = true;
  133. _db.setUploadInfo("foo", record);
  134. Info storedRecord = _db.getUploadInfo("foo");
  135. QVERIFY(storedRecord == record);
  136. _db.setUploadInfo("foo", Info());
  137. Info wipedRecord = _db.getUploadInfo("foo");
  138. QVERIFY(!wipedRecord._valid);
  139. }
  140. void testNumericId()
  141. {
  142. SyncJournalFileRecord record;
  143. // Typical 8-digit padded id
  144. record._fileId = "00000001abcd";
  145. QCOMPARE(record.numericFileId(), QByteArray("00000001"));
  146. // When the numeric id overflows the 8-digit boundary
  147. record._fileId = "123456789ocidblaabcd";
  148. QCOMPARE(record.numericFileId(), QByteArray("123456789"));
  149. }
  150. void testConflictRecord()
  151. {
  152. ConflictRecord record;
  153. record.path = "abc";
  154. record.baseFileId = "def";
  155. record.baseModtime = 1234;
  156. record.baseEtag = "ghi";
  157. QVERIFY(!_db.conflictRecord(record.path).isValid());
  158. _db.setConflictRecord(record);
  159. auto newRecord = _db.conflictRecord(record.path);
  160. QVERIFY(newRecord.isValid());
  161. QCOMPARE(newRecord.path, record.path);
  162. QCOMPARE(newRecord.baseFileId, record.baseFileId);
  163. QCOMPARE(newRecord.baseModtime, record.baseModtime);
  164. QCOMPARE(newRecord.baseEtag, record.baseEtag);
  165. _db.deleteConflictRecord(record.path);
  166. QVERIFY(!_db.conflictRecord(record.path).isValid());
  167. }
  168. void testAvoidReadFromDbOnNextSync()
  169. {
  170. auto invalidEtag = QByteArray("_invalid_");
  171. auto initialEtag = QByteArray("etag");
  172. auto makeEntry = [&](const QByteArray &path, ItemType type) {
  173. SyncJournalFileRecord record;
  174. record._path = path;
  175. record._type = type;
  176. record._etag = initialEtag;
  177. record._remotePerm = RemotePermissions::fromDbValue("RW");
  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.schedulePathForRemoteDiscovery(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. record._remotePerm = RemotePermissions::fromDbValue("RW");
  232. _db.setFileRecord(record);
  233. };
  234. QByteArrayList elements;
  235. elements
  236. << "foo"
  237. << "foo/file"
  238. << "bar"
  239. << "moo"
  240. << "moo/file"
  241. << "foo%bar"
  242. << "foo bla bar/file"
  243. << "fo_"
  244. << "fo_/file";
  245. for (const auto& elem : elements)
  246. makeEntry(elem);
  247. auto checkElements = [&]() {
  248. bool ok = true;
  249. for (const auto& elem : elements) {
  250. SyncJournalFileRecord record;
  251. _db.getFileRecord(elem, &record);
  252. if (!record.isValid()) {
  253. qWarning() << "Missing record: " << elem;
  254. ok = false;
  255. }
  256. }
  257. return ok;
  258. };
  259. _db.deleteFileRecord("moo", true);
  260. elements.removeAll("moo");
  261. elements.removeAll("moo/file");
  262. QVERIFY(checkElements());
  263. _db.deleteFileRecord("fo_", true);
  264. elements.removeAll("fo_");
  265. elements.removeAll("fo_/file");
  266. QVERIFY(checkElements());
  267. _db.deleteFileRecord("foo%bar", true);
  268. elements.removeAll("foo%bar");
  269. QVERIFY(checkElements());
  270. }
  271. void testPinState()
  272. {
  273. auto make = [&](const QByteArray &path, PinState state) {
  274. _db.internalPinStates().setForPath(path, state);
  275. auto pinState = _db.internalPinStates().rawForPath(path);
  276. QVERIFY(pinState);
  277. QCOMPARE(*pinState, state);
  278. };
  279. auto get = [&](const QByteArray &path) -> PinState {
  280. auto state = _db.internalPinStates().effectiveForPath(path);
  281. if (!state) {
  282. QTest::qFail("couldn't read pin state", __FILE__, __LINE__);
  283. return PinState::Inherited;
  284. }
  285. return *state;
  286. };
  287. auto getRecursive = [&](const QByteArray &path) -> PinState {
  288. auto state = _db.internalPinStates().effectiveForPathRecursive(path);
  289. if (!state) {
  290. QTest::qFail("couldn't read pin state", __FILE__, __LINE__);
  291. return PinState::Inherited;
  292. }
  293. return *state;
  294. };
  295. auto getRaw = [&](const QByteArray &path) -> PinState {
  296. auto state = _db.internalPinStates().rawForPath(path);
  297. if (!state) {
  298. QTest::qFail("couldn't read pin state", __FILE__, __LINE__);
  299. return PinState::Inherited;
  300. }
  301. return *state;
  302. };
  303. _db.internalPinStates().wipeForPathAndBelow("");
  304. auto list = _db.internalPinStates().rawList();
  305. QCOMPARE(list->size(), 0);
  306. // Make a thrice-nested setup
  307. make("", PinState::AlwaysLocal);
  308. make("local", PinState::AlwaysLocal);
  309. make("online", PinState::OnlineOnly);
  310. make("inherit", PinState::Inherited);
  311. for (auto base : {"local/", "online/", "inherit/"}) {
  312. make(QByteArray(base) + "inherit", PinState::Inherited);
  313. make(QByteArray(base) + "local", PinState::AlwaysLocal);
  314. make(QByteArray(base) + "online", PinState::OnlineOnly);
  315. for (auto base2 : {"local/", "online/", "inherit/"}) {
  316. make(QByteArray(base) + base2 + "inherit", PinState::Inherited);
  317. make(QByteArray(base) + base2 + "local", PinState::AlwaysLocal);
  318. make(QByteArray(base) + base2 + "online", PinState::OnlineOnly);
  319. }
  320. }
  321. list = _db.internalPinStates().rawList();
  322. QCOMPARE(list->size(), 4 + 9 + 27);
  323. // Baseline direct checks (the fallback for unset root pinstate is AlwaysLocal)
  324. QCOMPARE(get(""), PinState::AlwaysLocal);
  325. QCOMPARE(get("local"), PinState::AlwaysLocal);
  326. QCOMPARE(get("online"), PinState::OnlineOnly);
  327. QCOMPARE(get("inherit"), PinState::AlwaysLocal);
  328. QCOMPARE(get("nonexistant"), PinState::AlwaysLocal);
  329. QCOMPARE(get("online/local"), PinState::AlwaysLocal);
  330. QCOMPARE(get("local/online"), PinState::OnlineOnly);
  331. QCOMPARE(get("inherit/local"), PinState::AlwaysLocal);
  332. QCOMPARE(get("inherit/online"), PinState::OnlineOnly);
  333. QCOMPARE(get("inherit/inherit"), PinState::AlwaysLocal);
  334. QCOMPARE(get("inherit/nonexistant"), PinState::AlwaysLocal);
  335. // Inheriting checks, level 1
  336. QCOMPARE(get("local/inherit"), PinState::AlwaysLocal);
  337. QCOMPARE(get("local/nonexistant"), PinState::AlwaysLocal);
  338. QCOMPARE(get("online/inherit"), PinState::OnlineOnly);
  339. QCOMPARE(get("online/nonexistant"), PinState::OnlineOnly);
  340. // Inheriting checks, level 2
  341. QCOMPARE(get("local/inherit/inherit"), PinState::AlwaysLocal);
  342. QCOMPARE(get("local/local/inherit"), PinState::AlwaysLocal);
  343. QCOMPARE(get("local/local/nonexistant"), PinState::AlwaysLocal);
  344. QCOMPARE(get("local/online/inherit"), PinState::OnlineOnly);
  345. QCOMPARE(get("local/online/nonexistant"), PinState::OnlineOnly);
  346. QCOMPARE(get("online/inherit/inherit"), PinState::OnlineOnly);
  347. QCOMPARE(get("online/local/inherit"), PinState::AlwaysLocal);
  348. QCOMPARE(get("online/local/nonexistant"), PinState::AlwaysLocal);
  349. QCOMPARE(get("online/online/inherit"), PinState::OnlineOnly);
  350. QCOMPARE(get("online/online/nonexistant"), PinState::OnlineOnly);
  351. // Spot check the recursive variant
  352. QCOMPARE(getRecursive(""), PinState::Inherited);
  353. QCOMPARE(getRecursive("local"), PinState::Inherited);
  354. QCOMPARE(getRecursive("online"), PinState::Inherited);
  355. QCOMPARE(getRecursive("inherit"), PinState::Inherited);
  356. QCOMPARE(getRecursive("online/local"), PinState::Inherited);
  357. QCOMPARE(getRecursive("online/local/inherit"), PinState::AlwaysLocal);
  358. QCOMPARE(getRecursive("inherit/inherit/inherit"), PinState::AlwaysLocal);
  359. QCOMPARE(getRecursive("inherit/online/inherit"), PinState::OnlineOnly);
  360. QCOMPARE(getRecursive("inherit/online/local"), PinState::AlwaysLocal);
  361. make("local/local/local/local", PinState::AlwaysLocal);
  362. QCOMPARE(getRecursive("local/local/local"), PinState::AlwaysLocal);
  363. QCOMPARE(getRecursive("local/local/local/local"), PinState::AlwaysLocal);
  364. // Check changing the root pin state
  365. make("", PinState::OnlineOnly);
  366. QCOMPARE(get("local"), PinState::AlwaysLocal);
  367. QCOMPARE(get("online"), PinState::OnlineOnly);
  368. QCOMPARE(get("inherit"), PinState::OnlineOnly);
  369. QCOMPARE(get("nonexistant"), PinState::OnlineOnly);
  370. make("", PinState::AlwaysLocal);
  371. QCOMPARE(get("local"), PinState::AlwaysLocal);
  372. QCOMPARE(get("online"), PinState::OnlineOnly);
  373. QCOMPARE(get("inherit"), PinState::AlwaysLocal);
  374. QCOMPARE(get("nonexistant"), PinState::AlwaysLocal);
  375. // Wiping
  376. QCOMPARE(getRaw("local/local"), PinState::AlwaysLocal);
  377. _db.internalPinStates().wipeForPathAndBelow("local/local");
  378. QCOMPARE(getRaw("local"), PinState::AlwaysLocal);
  379. QCOMPARE(getRaw("local/local"), PinState::Inherited);
  380. QCOMPARE(getRaw("local/local/local"), PinState::Inherited);
  381. QCOMPARE(getRaw("local/local/online"), PinState::Inherited);
  382. list = _db.internalPinStates().rawList();
  383. QCOMPARE(list->size(), 4 + 9 + 27 - 4);
  384. // Wiping everything
  385. _db.internalPinStates().wipeForPathAndBelow("");
  386. QCOMPARE(getRaw(""), PinState::Inherited);
  387. QCOMPARE(getRaw("local"), PinState::Inherited);
  388. QCOMPARE(getRaw("online"), PinState::Inherited);
  389. list = _db.internalPinStates().rawList();
  390. QCOMPARE(list->size(), 0);
  391. }
  392. private:
  393. SyncJournalDb _db;
  394. };
  395. QTEST_APPLESS_MAIN(TestSyncJournalDB)
  396. #include "testsyncjournaldb.moc"