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 
10 #include "folderwatcher.h"
11 #include "common/utility.h"
12 
touch(const QString & file)13 void touch(const QString &file)
14 {
15 #ifdef Q_OS_WIN
16     OCC::Utility::writeRandomFile(file);
17 #else
18     QString cmd;
19     cmd = QString("touch %1").arg(file);
20     qDebug() << "Command: " << cmd;
21     system(cmd.toLocal8Bit());
22 #endif
23 }
24 
mkdir(const QString & file)25 void mkdir(const QString &file)
26 {
27 #ifdef Q_OS_WIN
28     QDir dir;
29     dir.mkdir(file);
30 #else
31     QString cmd = QString("mkdir %1").arg(file);
32     qDebug() << "Command: " << cmd;
33     system(cmd.toLocal8Bit());
34 #endif
35 }
36 
rmdir(const QString & file)37 void rmdir(const QString &file)
38 {
39 #ifdef Q_OS_WIN
40     QDir dir;
41     dir.rmdir(file);
42 #else
43     QString cmd = QString("rmdir %1").arg(file);
44     qDebug() << "Command: " << cmd;
45     system(cmd.toLocal8Bit());
46 #endif
47 }
48 
rm(const QString & file)49 void rm(const QString &file)
50 {
51 #ifdef Q_OS_WIN
52     QFile::remove(file);
53 #else
54     QString cmd = QString("rm %1").arg(file);
55     qDebug() << "Command: " << cmd;
56     system(cmd.toLocal8Bit());
57 #endif
58 }
59 
mv(const QString & file1,const QString & file2)60 void mv(const QString &file1, const QString &file2)
61 {
62 #ifdef Q_OS_WIN
63     QFile::rename(file1, file2);
64 #else
65     QString cmd = QString("mv %1 %2").arg(file1, file2);
66     qDebug() << "Command: " << cmd;
67     system(cmd.toLocal8Bit());
68 #endif
69 }
70 
71 using namespace OCC;
72 
73 class TestFolderWatcher : public QObject
74 {
75     Q_OBJECT
76 
77     QTemporaryDir _root;
78     QString _rootPath;
79     QScopedPointer<FolderWatcher> _watcher;
80     QScopedPointer<QSignalSpy> _pathChangedSpy;
81 
waitForPathChanged(const QString & path)82     bool waitForPathChanged(const QString &path)
83     {
84         QElapsedTimer t;
85         t.start();
86         while (t.elapsed() < 5000) {
87             // Check if it was already reported as changed by the watcher
88             for (int i = 0; i < _pathChangedSpy->size(); ++i) {
89                 const auto &args = _pathChangedSpy->at(i);
90                 if (args.first().toString() == path)
91                     return true;
92             }
93             // Wait a bit and test again (don't bother checking if we timed out or not)
94             _pathChangedSpy->wait(200);
95         }
96         return false;
97     }
98 
99 #ifdef Q_OS_LINUX
100 #define CHECK_WATCH_COUNT(n) QCOMPARE(_watcher->testLinuxWatchCount(), (n))
101 #else
102 #define CHECK_WATCH_COUNT(n) do {} while (false)
103 #endif
104 
105 public:
TestFolderWatcher()106     TestFolderWatcher()
107     {
108         QDir rootDir(_root.path());
109         _rootPath = rootDir.canonicalPath();
110         qDebug() << "creating test directory tree in " << _rootPath;
111 
112         rootDir.mkpath("a1/b1/c1");
113         rootDir.mkpath("a1/b1/c2");
114         rootDir.mkpath("a1/b2/c1");
115         rootDir.mkpath("a1/b3/c3");
116         rootDir.mkpath("a2/b3/c3");
117         Utility::writeRandomFile( _rootPath+"/a1/random.bin");
118         Utility::writeRandomFile( _rootPath+"/a1/b2/todelete.bin");
119         Utility::writeRandomFile( _rootPath+"/a2/renamefile");
120         Utility::writeRandomFile( _rootPath+"/a1/movefile");
121 
122         _watcher.reset(new FolderWatcher);
123         _watcher->init(_rootPath);
124         _pathChangedSpy.reset(new QSignalSpy(_watcher.data(), SIGNAL(pathChanged(QString))));
125     }
126 
countFolders(const QString & path)127     int countFolders(const QString &path)
128     {
129         int n = 0;
130         for (const auto &sub : QDir(path).entryList(QDir::Dirs | QDir::NoDotAndDotDot))
131             n += 1 + countFolders(path + '/' + sub);
132         return n;
133     }
134 
135 private slots:
init()136     void init()
137     {
138         _pathChangedSpy->clear();
139         CHECK_WATCH_COUNT(countFolders(_rootPath) + 1);
140     }
141 
cleanup()142     void cleanup()
143     {
144         CHECK_WATCH_COUNT(countFolders(_rootPath) + 1);
145     }
146 
testACreate()147     void testACreate() { // create a new file
148         QString file(_rootPath + "/foo.txt");
149         QString cmd;
150         cmd = QString("echo \"xyz\" > %1").arg(file);
151         qDebug() << "Command: " << cmd;
152         system(cmd.toLocal8Bit());
153 
154         QVERIFY(waitForPathChanged(file));
155     }
156 
testATouch()157     void testATouch() { // touch an existing file.
158         QString file(_rootPath + "/a1/random.bin");
159         touch(file);
160         QVERIFY(waitForPathChanged(file));
161     }
162 
testMove3LevelDirWithFile()163     void testMove3LevelDirWithFile() {
164         QString file(_rootPath + "/a0/b/c/empty.txt");
165         mkdir(_rootPath + "/a0");
166         mkdir(_rootPath + "/a0/b");
167         mkdir(_rootPath + "/a0/b/c");
168         touch(file);
169         mv(_rootPath + "/a0", _rootPath + "/a");
170         QVERIFY(waitForPathChanged(_rootPath + "/a/b/c/empty.txt"));
171     }
172 
173 
testCreateADir()174     void testCreateADir() {
175         QString file(_rootPath+"/a1/b1/new_dir");
176         mkdir(file);
177         QVERIFY(waitForPathChanged(file));
178 
179         // Notifications from that new folder arrive too
180         QString file2(_rootPath + "/a1/b1/new_dir/contained");
181         touch(file2);
182         QVERIFY(waitForPathChanged(file2));
183     }
184 
testRemoveADir()185     void testRemoveADir() {
186         QString file(_rootPath+"/a1/b3/c3");
187         rmdir(file);
188         QVERIFY(waitForPathChanged(file));
189     }
190 
testRemoveAFile()191     void testRemoveAFile() {
192         QString file(_rootPath+"/a1/b2/todelete.bin");
193         QVERIFY(QFile::exists(file));
194         rm(file);
195         QVERIFY(!QFile::exists(file));
196 
197         QVERIFY(waitForPathChanged(file));
198     }
199 
testRenameAFile()200     void testRenameAFile() {
201         QString file1(_rootPath+"/a2/renamefile");
202         QString file2(_rootPath+"/a2/renamefile.renamed");
203         QVERIFY(QFile::exists(file1));
204         mv(file1, file2);
205         QVERIFY(QFile::exists(file2));
206 
207         QVERIFY(waitForPathChanged(file1));
208         QVERIFY(waitForPathChanged(file2));
209     }
210 
testMoveAFile()211     void testMoveAFile() {
212         QString old_file(_rootPath+"/a1/movefile");
213         QString new_file(_rootPath+"/a2/movefile.renamed");
214         QVERIFY(QFile::exists(old_file));
215         mv(old_file, new_file);
216         QVERIFY(QFile::exists(new_file));
217 
218         QVERIFY(waitForPathChanged(old_file));
219         QVERIFY(waitForPathChanged(new_file));
220     }
221 
testRenameDirectorySameBase()222     void testRenameDirectorySameBase() {
223         QString old_file(_rootPath+"/a1/b1");
224         QString new_file(_rootPath+"/a1/brename");
225         QVERIFY(QFile::exists(old_file));
226         mv(old_file, new_file);
227         QVERIFY(QFile::exists(new_file));
228 
229         QVERIFY(waitForPathChanged(old_file));
230         QVERIFY(waitForPathChanged(new_file));
231 
232         // Verify that further notifications end up with the correct paths
233 
234         QString file(_rootPath+"/a1/brename/c1/random.bin");
235         touch(file);
236         QVERIFY(waitForPathChanged(file));
237 
238         QString dir(_rootPath+"/a1/brename/newfolder");
239         mkdir(dir);
240         QVERIFY(waitForPathChanged(dir));
241     }
242 
testRenameDirectoryDifferentBase()243     void testRenameDirectoryDifferentBase() {
244         QString old_file(_rootPath+"/a1/brename");
245         QString new_file(_rootPath+"/bren");
246         QVERIFY(QFile::exists(old_file));
247         mv(old_file, new_file);
248         QVERIFY(QFile::exists(new_file));
249 
250         QVERIFY(waitForPathChanged(old_file));
251         QVERIFY(waitForPathChanged(new_file));
252 
253         // Verify that further notifications end up with the correct paths
254 
255         QString file(_rootPath+"/bren/c1/random.bin");
256         touch(file);
257         QVERIFY(waitForPathChanged(file));
258 
259         QString dir(_rootPath+"/bren/newfolder2");
260         mkdir(dir);
261         QVERIFY(waitForPathChanged(dir));
262     }
263 };
264 
265 #ifdef Q_OS_MAC
266     QTEST_MAIN(TestFolderWatcher)
267 #else
268     QTEST_GUILESS_MAIN(TestFolderWatcher)
269 #endif
270 
271 #include "testfolderwatcher.moc"
272