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 
8 #include <QtTest>
9 #include "syncenginetestutils.h"
10 #include "csync_exclude.h"
11 
12 using namespace OCC;
13 
14 class StatusPushSpy : public QSignalSpy
15 {
16     SyncEngine &_syncEngine;
17 public:
StatusPushSpy(SyncEngine & syncEngine)18     StatusPushSpy(SyncEngine &syncEngine)
19         : QSignalSpy(&syncEngine.syncFileStatusTracker(), SIGNAL(fileStatusChanged(const QString&, SyncFileStatus)))
20         , _syncEngine(syncEngine)
21     { }
22 
statusOf(const QString & relativePath) const23     SyncFileStatus statusOf(const QString &relativePath) const {
24         QFileInfo file(_syncEngine.localPath(), relativePath);
25         // Start from the end to get the latest status
26         for (int i = size() - 1; i >= 0; --i) {
27             if (QFileInfo(at(i)[0].toString()) == file)
28                 return at(i)[1].value<SyncFileStatus>();
29         }
30         return SyncFileStatus();
31     }
32 
statusEmittedBefore(const QString & firstPath,const QString & secondPath) const33     bool statusEmittedBefore(const QString &firstPath, const QString &secondPath) const {
34         QFileInfo firstFile(_syncEngine.localPath(), firstPath);
35         QFileInfo secondFile(_syncEngine.localPath(), secondPath);
36         // Start from the end to get the latest status
37         int i = size() - 1;
38         for (; i >= 0; --i) {
39             if (QFileInfo(at(i)[0].toString()) == secondFile)
40                 break;
41             else if (QFileInfo(at(i)[0].toString()) == firstFile)
42                 return false;
43         }
44         for (; i >= 0; --i) {
45             if (QFileInfo(at(i)[0].toString()) == firstFile)
46                 return true;
47         }
48         return false;
49     }
50 };
51 
52 class TestSyncFileStatusTracker : public QObject
53 {
54     Q_OBJECT
55 
verifyThatPushMatchesPull(const FakeFolder & fakeFolder,const StatusPushSpy & statusSpy)56     void verifyThatPushMatchesPull(const FakeFolder &fakeFolder, const StatusPushSpy &statusSpy) {
57         QString root = fakeFolder.localPath();
58         QDirIterator it(root, QDir::AllEntries | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
59         while (it.hasNext()) {
60             QString filePath = it.next().mid(root.size());
61             SyncFileStatus pushedStatus = statusSpy.statusOf(filePath);
62             if (pushedStatus != SyncFileStatus())
63                 QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(filePath), pushedStatus);
64         }
65     }
66 
67 private slots:
parentsGetSyncStatusUploadDownload()68     void parentsGetSyncStatusUploadDownload() {
69         FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
70         fakeFolder.localModifier().appendByte("B/b1");
71         fakeFolder.remoteModifier().appendByte("C/c1");
72         StatusPushSpy statusSpy(fakeFolder.syncEngine());
73 
74         fakeFolder.scheduleSync();
75         fakeFolder.execUntilBeforePropagation();
76         verifyThatPushMatchesPull(fakeFolder, statusSpy);
77         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync));
78         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusSync));
79         QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusSync));
80         QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusSync));
81         QCOMPARE(statusSpy.statusOf("C/c1"), SyncFileStatus(SyncFileStatus::StatusSync));
82         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
83         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a1"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
84         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("B/b2"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
85         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("C/c2"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
86         statusSpy.clear();
87 
88         fakeFolder.execUntilFinished();
89         verifyThatPushMatchesPull(fakeFolder, statusSpy);
90         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate));
91         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
92         QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
93         QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
94         QCOMPARE(statusSpy.statusOf("C/c1"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
95 
96         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
97     }
98 
parentsGetSyncStatusNewFileUploadDownload()99     void parentsGetSyncStatusNewFileUploadDownload() {
100         FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
101         fakeFolder.localModifier().insert("B/b0");
102         fakeFolder.remoteModifier().insert("C/c0");
103         StatusPushSpy statusSpy(fakeFolder.syncEngine());
104 
105         fakeFolder.scheduleSync();
106         fakeFolder.execUntilBeforePropagation();
107         verifyThatPushMatchesPull(fakeFolder, statusSpy);
108         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync));
109         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusSync));
110         QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusSync));
111         QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusSync));
112         QCOMPARE(statusSpy.statusOf("C/c0"), SyncFileStatus(SyncFileStatus::StatusSync));
113         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
114         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a1"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
115         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("B/b1"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
116         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("C/c1"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
117         statusSpy.clear();
118 
119         fakeFolder.execUntilFinished();
120         verifyThatPushMatchesPull(fakeFolder, statusSpy);
121         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate));
122         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
123         QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
124         QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
125         QCOMPARE(statusSpy.statusOf("C/c0"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
126 
127         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
128     }
129 
parentsGetSyncStatusNewDirDownload()130     void parentsGetSyncStatusNewDirDownload() {
131         FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
132         fakeFolder.remoteModifier().mkdir("D");
133         fakeFolder.remoteModifier().insert("D/d0");
134         StatusPushSpy statusSpy(fakeFolder.syncEngine());
135 
136         fakeFolder.scheduleSync();
137         fakeFolder.execUntilBeforePropagation();
138         verifyThatPushMatchesPull(fakeFolder, statusSpy);
139         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync));
140         QCOMPARE(statusSpy.statusOf("D"), SyncFileStatus(SyncFileStatus::StatusSync));
141         QCOMPARE(statusSpy.statusOf("D/d0"), SyncFileStatus(SyncFileStatus::StatusSync));
142 
143         fakeFolder.execUntilItemCompleted("D");
144         verifyThatPushMatchesPull(fakeFolder, statusSpy);
145         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync));
146         QCOMPARE(statusSpy.statusOf("D"), SyncFileStatus(SyncFileStatus::StatusSync));
147         QCOMPARE(statusSpy.statusOf("D/d0"), SyncFileStatus(SyncFileStatus::StatusSync));
148 
149         fakeFolder.execUntilFinished();
150         verifyThatPushMatchesPull(fakeFolder, statusSpy);
151         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate));
152         QCOMPARE(statusSpy.statusOf("D"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
153         QCOMPARE(statusSpy.statusOf("D/d0"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
154 
155         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
156     }
157 
parentsGetSyncStatusNewDirUpload()158     void parentsGetSyncStatusNewDirUpload() {
159         FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
160         fakeFolder.localModifier().mkdir("D");
161         fakeFolder.localModifier().insert("D/d0");
162         StatusPushSpy statusSpy(fakeFolder.syncEngine());
163 
164         fakeFolder.scheduleSync();
165         fakeFolder.execUntilBeforePropagation();
166         verifyThatPushMatchesPull(fakeFolder, statusSpy);
167         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync));
168         QCOMPARE(statusSpy.statusOf("D"), SyncFileStatus(SyncFileStatus::StatusSync));
169         QCOMPARE(statusSpy.statusOf("D/d0"), SyncFileStatus(SyncFileStatus::StatusSync));
170 
171         fakeFolder.execUntilItemCompleted("D");
172         verifyThatPushMatchesPull(fakeFolder, statusSpy);
173         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync));
174         QCOMPARE(statusSpy.statusOf("D"), SyncFileStatus(SyncFileStatus::StatusSync));
175         QCOMPARE(statusSpy.statusOf("D/d0"), SyncFileStatus(SyncFileStatus::StatusSync));
176 
177         fakeFolder.execUntilFinished();
178         verifyThatPushMatchesPull(fakeFolder, statusSpy);
179         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate));
180         QCOMPARE(statusSpy.statusOf("D"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
181         QCOMPARE(statusSpy.statusOf("D/d0"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
182 
183         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
184     }
185 
parentsGetSyncStatusDeleteUpDown()186     void parentsGetSyncStatusDeleteUpDown() {
187         FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
188         fakeFolder.remoteModifier().remove("B/b1");
189         fakeFolder.localModifier().remove("C/c1");
190         StatusPushSpy statusSpy(fakeFolder.syncEngine());
191 
192         fakeFolder.scheduleSync();
193         fakeFolder.execUntilBeforePropagation();
194         verifyThatPushMatchesPull(fakeFolder, statusSpy);
195         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync));
196         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusSync));
197         // Discovered as remotely removed, pending for local removal.
198         QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusSync));
199         QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusSync));
200         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
201         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("B/b2"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
202         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("C/c2"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
203         statusSpy.clear();
204 
205         fakeFolder.execUntilFinished();
206         verifyThatPushMatchesPull(fakeFolder, statusSpy);
207         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate));
208         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
209         QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
210 
211         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
212     }
213 
warningStatusForExcludedFile()214     void warningStatusForExcludedFile() {
215         FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
216         fakeFolder.syncEngine().excludedFiles().addManualExclude("A/a1");
217         fakeFolder.syncEngine().excludedFiles().addManualExclude("B");
218         fakeFolder.localModifier().appendByte("A/a1");
219         fakeFolder.localModifier().appendByte("B/b1");
220         StatusPushSpy statusSpy(fakeFolder.syncEngine());
221 
222         fakeFolder.scheduleSync();
223         fakeFolder.execUntilBeforePropagation();
224         verifyThatPushMatchesPull(fakeFolder, statusSpy);
225         QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusExcluded));
226         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusExcluded));
227         QEXPECT_FAIL("", "csync will stop at ignored directories without traversing children, so we don't currently push the status for newly ignored children of an ignored directory.", Continue);
228         QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusExcluded));
229 
230         fakeFolder.execUntilFinished();
231         verifyThatPushMatchesPull(fakeFolder, statusSpy);
232         QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusExcluded));
233         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusExcluded));
234         QEXPECT_FAIL("", "csync will stop at ignored directories without traversing children, so we don't currently push the status for newly ignored children of an ignored directory.", Continue);
235         QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusExcluded));
236         QEXPECT_FAIL("", "csync will stop at ignored directories without traversing children, so we don't currently push the status for newly ignored children of an ignored directory.", Continue);
237         QCOMPARE(statusSpy.statusOf("B/b2"), SyncFileStatus(SyncFileStatus::StatusExcluded));
238         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(""), SyncFileStatus(SyncFileStatus::StatusUpToDate));
239         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
240         statusSpy.clear();
241 
242         // Clears the exclude expr above
243         fakeFolder.syncEngine().excludedFiles().clearManualExcludes();
244         fakeFolder.scheduleSync();
245         fakeFolder.execUntilBeforePropagation();
246         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync));
247         QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusSync));
248         QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusSync));
249         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusSync));
250         QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusSync));
251         statusSpy.clear();
252 
253         fakeFolder.execUntilFinished();
254         verifyThatPushMatchesPull(fakeFolder, statusSpy);
255         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate));
256         QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
257         QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
258         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
259         QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
260 
261         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
262     }
263 
warningStatusForExcludedFile_CasePreserving()264     void warningStatusForExcludedFile_CasePreserving() {
265         FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
266         fakeFolder.syncEngine().excludedFiles().addManualExclude("B");
267         fakeFolder.serverErrorPaths().append("A/a1");
268         fakeFolder.localModifier().appendByte("A/a1");
269 
270         fakeFolder.syncOnce();
271         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(""), SyncFileStatus(SyncFileStatus::StatusWarning));
272         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A"), SyncFileStatus(SyncFileStatus::StatusWarning));
273         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a1"), SyncFileStatus(SyncFileStatus::StatusError));
274         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("B"), SyncFileStatus(SyncFileStatus::StatusExcluded));
275 
276         // Should still get the status for different casing on macOS and Windows.
277         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("a"), SyncFileStatus(Utility::fsCasePreserving() ? SyncFileStatus::StatusWarning : SyncFileStatus::StatusNone));
278         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/A1"), SyncFileStatus(Utility::fsCasePreserving() ? SyncFileStatus::StatusError : SyncFileStatus::StatusNone));
279         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("b"), SyncFileStatus(Utility::fsCasePreserving() ? SyncFileStatus::StatusExcluded : SyncFileStatus::StatusNone));
280     }
281 
parentsGetWarningStatusForError()282     void parentsGetWarningStatusForError() {
283         FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
284         fakeFolder.serverErrorPaths().append("A/a1");
285         fakeFolder.serverErrorPaths().append("B/b0");
286         fakeFolder.localModifier().appendByte("A/a1");
287         fakeFolder.localModifier().insert("B/b0");
288         StatusPushSpy statusSpy(fakeFolder.syncEngine());
289 
290         fakeFolder.scheduleSync();
291         fakeFolder.execUntilBeforePropagation();
292         verifyThatPushMatchesPull(fakeFolder, statusSpy);
293         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync));
294         QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusSync));
295         QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusSync));
296         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusSync));
297         QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusSync));
298         statusSpy.clear();
299 
300         fakeFolder.execUntilFinished();
301         verifyThatPushMatchesPull(fakeFolder, statusSpy);
302         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusWarning));
303         QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusWarning));
304         QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusError));
305         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a2"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
306         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusWarning));
307         QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusError));
308         statusSpy.clear();
309 
310         // Remove the error and start a second sync, the blacklist should kick in
311         fakeFolder.serverErrorPaths().clear();
312         fakeFolder.scheduleSync();
313         fakeFolder.execUntilBeforePropagation();
314         verifyThatPushMatchesPull(fakeFolder, statusSpy);
315         // A/a1 and B/b0 should be on the black list for the next few seconds
316         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusWarning));
317         QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusWarning));
318         QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusError));
319         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusWarning));
320         QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusError));
321         statusSpy.clear();
322         fakeFolder.execUntilFinished();
323         verifyThatPushMatchesPull(fakeFolder, statusSpy);
324         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusWarning));
325         QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusWarning));
326         QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusError));
327         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a2"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
328         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusWarning));
329         QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusError));
330         statusSpy.clear();
331 
332         // Start a third sync, this time together with a real file to sync
333         fakeFolder.localModifier().appendByte("C/c1");
334         fakeFolder.scheduleSync();
335         fakeFolder.execUntilBeforePropagation();
336         verifyThatPushMatchesPull(fakeFolder, statusSpy);
337         // The root should show SYNC even though there is an error underneath,
338         // since C/c1 is syncing and the SYNC status has priority.
339         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync));
340         QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusWarning));
341         QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusError));
342         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusWarning));
343         QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusError));
344         QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusSync));
345         QCOMPARE(statusSpy.statusOf("C/c1"), SyncFileStatus(SyncFileStatus::StatusSync));
346         statusSpy.clear();
347         fakeFolder.execUntilFinished();
348         verifyThatPushMatchesPull(fakeFolder, statusSpy);
349         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusWarning));
350         QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusWarning));
351         QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusError));
352         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a2"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
353         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusWarning));
354         QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusError));
355         QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
356         QCOMPARE(statusSpy.statusOf("C/c1"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
357         statusSpy.clear();
358 
359         // Another sync after clearing the blacklist entry, everything should return to order.
360         fakeFolder.syncEngine().journal()->wipeErrorBlacklistEntry("A/a1");
361         fakeFolder.syncEngine().journal()->wipeErrorBlacklistEntry("B/b0");
362         fakeFolder.scheduleSync();
363         fakeFolder.execUntilBeforePropagation();
364         verifyThatPushMatchesPull(fakeFolder, statusSpy);
365         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync));
366         QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusSync));
367         QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusSync));
368         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusSync));
369         QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusSync));
370         statusSpy.clear();
371         fakeFolder.execUntilFinished();
372         verifyThatPushMatchesPull(fakeFolder, statusSpy);
373         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate));
374         QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
375         QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
376         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
377         QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
378 
379         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
380     }
381 
parentsGetWarningStatusForError_SibblingStartsWithPath()382     void parentsGetWarningStatusForError_SibblingStartsWithPath() {
383         // A is a parent of A/a1, but A/a is not even if it's a substring of A/a1
384         FakeFolder fakeFolder{{QString{},{
385             {QStringLiteral("A"), {
386                 {QStringLiteral("a"), 4},
387                 {QStringLiteral("a1"), 4}
388             }}}}};
389         fakeFolder.serverErrorPaths().append("A/a1");
390         fakeFolder.localModifier().appendByte("A/a1");
391 
392         fakeFolder.scheduleSync();
393         fakeFolder.execUntilBeforePropagation();
394         // The SyncFileStatusTraker won't push any status for all of them, test with a pull.
395         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(""), SyncFileStatus(SyncFileStatus::StatusSync));
396         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A"), SyncFileStatus(SyncFileStatus::StatusSync));
397         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a1"), SyncFileStatus(SyncFileStatus::StatusSync));
398         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
399 
400         fakeFolder.execUntilFinished();
401         // We use string matching for paths in the implementation,
402         // an error should affect only parents and not every path that starts with the problem path.
403         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(""), SyncFileStatus(SyncFileStatus::StatusWarning));
404         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A"), SyncFileStatus(SyncFileStatus::StatusWarning));
405         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a1"), SyncFileStatus(SyncFileStatus::StatusError));
406         QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
407     }
408 
409     // Even for status pushes immediately following each other, macOS
410     // can sometimes have 1s delays between updates, so make sure that
411     // children are marked as OK before their parents do.
childOKEmittedBeforeParent()412     void childOKEmittedBeforeParent() {
413         FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
414         fakeFolder.localModifier().appendByte("B/b1");
415         fakeFolder.remoteModifier().appendByte("C/c1");
416         StatusPushSpy statusSpy(fakeFolder.syncEngine());
417 
418         fakeFolder.syncOnce();
419         verifyThatPushMatchesPull(fakeFolder, statusSpy);
420         QVERIFY(statusSpy.statusEmittedBefore("B/b1", "B"));
421         QVERIFY(statusSpy.statusEmittedBefore("C/c1", "C"));
422         QVERIFY(statusSpy.statusEmittedBefore("B", ""));
423         QVERIFY(statusSpy.statusEmittedBefore("C", ""));
424         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate));
425         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
426         QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
427         QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
428         QCOMPARE(statusSpy.statusOf("C/c1"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
429     }
430 
sharedStatus()431     void sharedStatus() {
432         SyncFileStatus sharedUpToDateStatus(SyncFileStatus::StatusUpToDate);
433         sharedUpToDateStatus.setShared(true);
434 
435         FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
436         fakeFolder.remoteModifier().insert("S/s0");
437         fakeFolder.remoteModifier().appendByte("S/s1");
438         fakeFolder.remoteModifier().insert("B/b3");
439         fakeFolder.remoteModifier().find("B/b3")->extraDavProperties = "<oc:share-types><oc:share-type>0</oc:share-type></oc:share-types>";
440         fakeFolder.remoteModifier().find("A/a1")->isShared = true; // becomes shared
441         fakeFolder.remoteModifier().find("A", true); // change the etags of the parent
442 
443         StatusPushSpy statusSpy(fakeFolder.syncEngine());
444 
445         fakeFolder.scheduleSync();
446         fakeFolder.execUntilBeforePropagation();
447         verifyThatPushMatchesPull(fakeFolder, statusSpy);
448         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync));
449         // We don't care about the shared flag for the sync status,
450         // Mac and Windows won't show it and we can't know it for new files.
451         QCOMPARE(statusSpy.statusOf("S").tag(), SyncFileStatus::StatusSync);
452         QCOMPARE(statusSpy.statusOf("S/s0").tag(), SyncFileStatus::StatusSync);
453         QCOMPARE(statusSpy.statusOf("S/s1").tag(), SyncFileStatus::StatusSync);
454 
455         fakeFolder.execUntilFinished();
456         verifyThatPushMatchesPull(fakeFolder, statusSpy);
457         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate));
458         QCOMPARE(statusSpy.statusOf("S"), sharedUpToDateStatus);
459         QEXPECT_FAIL("", "We currently only know if a new file is shared on the second sync, after a PROPFIND.", Continue);
460         QCOMPARE(statusSpy.statusOf("S/s0"), sharedUpToDateStatus);
461         QCOMPARE(statusSpy.statusOf("S/s1"), sharedUpToDateStatus);
462         QCOMPARE(statusSpy.statusOf("B/b1").shared(), false);
463         QCOMPARE(statusSpy.statusOf("B/b3"), sharedUpToDateStatus);
464         QCOMPARE(statusSpy.statusOf("A/a1"), sharedUpToDateStatus);
465 
466         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
467     }
468 
renameError()469     void renameError() {
470         FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
471         fakeFolder.serverErrorPaths().append("A/a1");
472         fakeFolder.localModifier().rename("A/a1", "A/a1m");
473         fakeFolder.localModifier().rename("B/b1", "B/b1m");
474         StatusPushSpy statusSpy(fakeFolder.syncEngine());
475 
476         fakeFolder.scheduleSync();
477         fakeFolder.execUntilBeforePropagation();
478 
479         verifyThatPushMatchesPull(fakeFolder, statusSpy);
480 
481         QCOMPARE(statusSpy.statusOf("A/a1m"), SyncFileStatus(SyncFileStatus::StatusSync));
482         QCOMPARE(statusSpy.statusOf("A/a1"), statusSpy.statusOf("A/a1notexist"));
483         QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusSync));
484         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync));
485         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusSync));
486         QCOMPARE(statusSpy.statusOf("B/b1m"), SyncFileStatus(SyncFileStatus::StatusSync));
487 
488         fakeFolder.execUntilFinished();
489         verifyThatPushMatchesPull(fakeFolder, statusSpy);
490         QCOMPARE(statusSpy.statusOf("A/a1m"), SyncFileStatus(SyncFileStatus::StatusError));
491         QCOMPARE(statusSpy.statusOf("A/a1"), statusSpy.statusOf("A/a1notexist"));
492         QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusWarning));
493         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusWarning));
494         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
495         QCOMPARE(statusSpy.statusOf("B/b1m"), SyncFileStatus(SyncFileStatus::StatusUpToDate));
496         statusSpy.clear();
497 
498         QVERIFY(!fakeFolder.syncOnce());
499         verifyThatPushMatchesPull(fakeFolder, statusSpy);
500         statusSpy.clear();
501         QVERIFY(!fakeFolder.syncOnce());
502         verifyThatPushMatchesPull(fakeFolder, statusSpy);
503         QCOMPARE(statusSpy.statusOf("A/a1m"), SyncFileStatus(SyncFileStatus::StatusError));
504         QCOMPARE(statusSpy.statusOf("A/a1"), statusSpy.statusOf("A/a1notexist"));
505         QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusWarning));
506         QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusWarning));
507         QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusNone));
508         QCOMPARE(statusSpy.statusOf("B/b1m"), SyncFileStatus(SyncFileStatus::StatusNone));
509         statusSpy.clear();
510     }
511 
512 };
513 
514 QTEST_GUILESS_MAIN(TestSyncFileStatusTracker)
515 #include "testsyncfilestatustracker.moc"
516