1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include <emulationdetector.h>
31#include <QtTest/QtTest>
32#ifdef QT_BUILD_INTERNAL
33#include <private/qfilesystemmodel_p.h>
34#endif
35#include <QFileSystemModel>
36#include <QFileIconProvider>
37#include <QTreeView>
38#include <QHeaderView>
39#include <QStandardPaths>
40#include <QTime>
41#include <QStyle>
42#include <QtGlobal>
43#include <QTemporaryDir>
44#if defined(Q_OS_WIN)
45# include <qt_windows.h> // for SetFileAttributes
46#endif
47#include <private/qfilesystemengine_p.h>
48
49#include <algorithm>
50
51#define WAITTIME 1000
52
53// Will try to wait for the condition while allowing event processing
54// for a maximum of 5 seconds.
55#define TRY_WAIT(expr, timedOut) \
56 do { \
57 *timedOut = true; \
58 const int step = 50; \
59 for (int __i = 0; __i < 5000; __i += step) { \
60 if (expr) { \
61 *timedOut = false; \
62 break; \
63 } \
64 QTest::qWait(step); \
65 } \
66 } while(0)
67
68Q_DECLARE_METATYPE(QDir::Filters)
69Q_DECLARE_METATYPE(QFileDevice::Permissions)
70
71Q_LOGGING_CATEGORY(lcFileSystemModel, "qt.widgets.tests.qfilesystemmodel")
72
73class tst_QFileSystemModel : public QObject {
74 Q_OBJECT
75
76private slots:
77 void initTestCase();
78 void cleanup();
79
80 void indexPath();
81
82 void rootPath();
83 void readOnly();
84 void iconProvider();
85
86 void rowCount();
87
88 void rowsInserted_data();
89 void rowsInserted();
90
91 void rowsRemoved_data();
92 void rowsRemoved();
93
94 void dataChanged_data();
95 void dataChanged();
96
97 void filters_data();
98 void filters();
99
100 void nameFilters();
101
102 void setData_data();
103 void setData();
104
105 void sortPersistentIndex();
106 void sort_data();
107 void sort();
108
109 void mkdir();
110 void deleteFile();
111 void deleteDirectory();
112
113 void caseSensitivity();
114
115 void drives_data();
116 void drives();
117 void dirsBeforeFiles();
118
119 void roleNames_data();
120 void roleNames();
121
122 void permissions_data();
123 void permissions();
124
125 void doNotUnwatchOnFailedRmdir();
126 void specialFiles();
127
128 void fileInfo();
129
130protected:
131 bool createFiles(QFileSystemModel *model, const QString &test_path,
132 const QStringList &initial_files, int existingFileCount = 0,
133 const QStringList &initial_dirs = QStringList());
134 QModelIndex prepareTestModelRoot(QFileSystemModel *model, const QString &test_path,
135 QSignalSpy **spy2 = nullptr, QSignalSpy **spy3 = nullptr);
136
137private:
138 QString flatDirTestPath;
139 QTemporaryDir m_tempDir;
140};
141
142void tst_QFileSystemModel::cleanup()
143{
144 QDir dir(flatDirTestPath);
145 if (dir.exists()) {
146 const QDir::Filters filters = QDir::AllEntries | QDir::System | QDir::Hidden | QDir::NoDotAndDotDot;
147 const QFileInfoList list = dir.entryInfoList(filters);
148 for (const QFileInfo &fi : list) {
149 if (fi.isDir()) {
150 QVERIFY(dir.rmdir(fi.fileName()));
151 } else {
152 QFile dead(fi.absoluteFilePath());
153 dead.setPermissions(QFile::ReadUser | QFile::ReadOwner | QFile::ExeOwner | QFile::ExeUser | QFile::WriteUser | QFile::WriteOwner | QFile::WriteOther);
154 QVERIFY(dead.remove());
155 }
156 }
157 QVERIFY(dir.entryInfoList(filters).isEmpty());
158 }
159}
160
161void tst_QFileSystemModel::initTestCase()
162{
163 QVERIFY2(m_tempDir.isValid(), qPrintable(m_tempDir.errorString()));
164 flatDirTestPath = m_tempDir.path();
165}
166
167void tst_QFileSystemModel::indexPath()
168{
169#if !defined(Q_OS_WIN)
170 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
171 int depth = QDir::currentPath().count(c: '/');
172 model->setRootPath(QDir::currentPath());
173 QString backPath;
174 for (int i = 0; i <= depth * 2 + 1; ++i) {
175 backPath += "../";
176 QModelIndex idx = model->index(path: backPath);
177 QVERIFY(i != depth - 1 ? idx.isValid() : !idx.isValid());
178 }
179#endif
180}
181
182void tst_QFileSystemModel::rootPath()
183{
184 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
185 QCOMPARE(model->rootPath(), QString(QDir().path()));
186
187 QSignalSpy rootChanged(model.data(), &QFileSystemModel::rootPathChanged);
188 QModelIndex root = model->setRootPath(model->rootPath());
189 root = model->setRootPath("this directory shouldn't exist");
190 QCOMPARE(rootChanged.count(), 0);
191
192 QString oldRootPath = model->rootPath();
193 const QStringList documentPaths = QStandardPaths::standardLocations(type: QStandardPaths::DocumentsLocation);
194 QVERIFY(!documentPaths.isEmpty());
195 QString documentPath = documentPaths.front();
196 // In particular on Linux, ~/Documents (the first
197 // DocumentsLocation) may not exist, so choose ~ in that case:
198 if (!QFile::exists(fileName: documentPath)) {
199 documentPath = QDir::homePath();
200 qWarning(msg: "%s: first documentPath \"%s\" does not exist. Using ~ (\"%s\") instead.",
201 Q_FUNC_INFO, qPrintable(documentPaths.front()), qPrintable(documentPath));
202 }
203 root = model->setRootPath(documentPath);
204
205 QTRY_VERIFY(model->rowCount(root) >= 0);
206 QCOMPARE(model->rootPath(), QString(documentPath));
207 QCOMPARE(rootChanged.count(), oldRootPath == model->rootPath() ? 0 : 1);
208 QCOMPARE(model->rootDirectory().absolutePath(), documentPath);
209
210 model->setRootPath(QDir::rootPath());
211 int oldCount = rootChanged.count();
212 oldRootPath = model->rootPath();
213 root = model->setRootPath(documentPath + QLatin1String("/."));
214 QTRY_VERIFY(model->rowCount(root) >= 0);
215 QCOMPARE(model->rootPath(), documentPath);
216 QCOMPARE(rootChanged.count(), oldRootPath == model->rootPath() ? oldCount : oldCount + 1);
217 QCOMPARE(model->rootDirectory().absolutePath(), documentPath);
218
219 QDir newdir = documentPath;
220 if (newdir.cdUp()) {
221 oldCount = rootChanged.count();
222 oldRootPath = model->rootPath();
223 root = model->setRootPath(documentPath + QLatin1String("/.."));
224 QTRY_VERIFY(model->rowCount(root) >= 0);
225 QCOMPARE(model->rootPath(), newdir.path());
226 QCOMPARE(rootChanged.count(), oldCount + 1);
227 QCOMPARE(model->rootDirectory().absolutePath(), newdir.path());
228 }
229
230#ifdef Q_OS_WIN
231 // check case insensitive root node on windows, tests QTBUG-71701
232 QModelIndex index = model->setRootPath(QString("\\\\localhost\\c$"));
233 QVERIFY(index.isValid());
234 QCOMPARE(model->rootPath(), QString("//localhost/c$"));
235
236 index = model->setRootPath(QString("\\\\localhost\\C$"));
237 QVERIFY(index.isValid());
238 QCOMPARE(model->rootPath(), QString("//localhost/C$"));
239
240 index = model->setRootPath(QString("\\\\LOCALHOST\\C$"));
241 QVERIFY(index.isValid());
242 QCOMPARE(model->rootPath(), QString("//LOCALHOST/C$"));
243#endif
244}
245
246void tst_QFileSystemModel::readOnly()
247{
248 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
249 QCOMPARE(model->isReadOnly(), true);
250 QTemporaryFile file(flatDirTestPath + QStringLiteral("/XXXXXX.dat"));
251 QVERIFY2(file.open(), qPrintable(file.errorString()));
252 const QString fileName = file.fileName();
253 file.close();
254
255 const QFileInfo fileInfo(fileName);
256 QTRY_VERIFY(QDir(flatDirTestPath).entryInfoList().contains(fileInfo));
257 QModelIndex root = model->setRootPath(flatDirTestPath);
258
259 QTRY_VERIFY(model->rowCount(root) > 0);
260 QVERIFY(!(model->flags(model->index(fileName)) & Qt::ItemIsEditable));
261 model->setReadOnly(false);
262 QCOMPARE(model->isReadOnly(), false);
263 QVERIFY(model->flags(model->index(fileName)) & Qt::ItemIsEditable);
264}
265
266class CustomFileIconProvider : public QFileIconProvider
267{
268public:
269 CustomFileIconProvider() : QFileIconProvider()
270 {
271 auto style = QApplication::style();
272 mb = style->standardIcon(standardIcon: QStyle::SP_MessageBoxCritical);
273 dvd = style->standardIcon(standardIcon: QStyle::SP_DriveDVDIcon);
274 }
275
276 QIcon icon(const QFileInfo &info) const override
277 {
278 if (info.isDir())
279 return mb;
280
281 return QFileIconProvider::icon(info);
282 }
283 QIcon icon(IconType type) const override
284 {
285 if (type == QFileIconProvider::Folder)
286 return dvd;
287
288 return QFileIconProvider::icon(type);
289 }
290private:
291 QIcon mb;
292 QIcon dvd;
293};
294
295void tst_QFileSystemModel::iconProvider()
296{
297 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
298 QVERIFY(model->iconProvider());
299 QScopedPointer<QFileIconProvider> provider(new QFileIconProvider);
300 model->setIconProvider(provider.data());
301 QCOMPARE(model->iconProvider(), provider.data());
302 model->setIconProvider(nullptr);
303 provider.reset();
304
305 QScopedPointer<QFileSystemModel> myModel(new QFileSystemModel);
306 const QStringList documentPaths = QStandardPaths::standardLocations(type: QStandardPaths::DocumentsLocation);
307 QVERIFY(!documentPaths.isEmpty());
308 myModel->setRootPath(documentPaths.constFirst());
309 //We change the provider, icons must be updated
310 provider.reset(other: new CustomFileIconProvider);
311 myModel->setIconProvider(provider.data());
312
313 QPixmap mb = QApplication::style()->standardIcon(standardIcon: QStyle::SP_MessageBoxCritical).pixmap(w: 50, h: 50);
314 QCOMPARE(myModel->fileIcon(myModel->index(QDir::homePath())).pixmap(50, 50), mb);
315}
316
317bool tst_QFileSystemModel::createFiles(QFileSystemModel *model, const QString &test_path,
318 const QStringList &initial_files, int existingFileCount,
319 const QStringList &initial_dirs)
320{
321 qCDebug(lcFileSystemModel) << (model->rowCount(parent: model->index(path: test_path))) << existingFileCount << initial_files;
322 bool timedOut = false;
323 TRY_WAIT((model->rowCount(model->index(test_path)) == existingFileCount), &timedOut);
324 if (timedOut)
325 return false;
326
327 QDir dir(test_path);
328 if (!dir.exists()) {
329 qWarning() << "error" << test_path << "doesn't exist";
330 return false;
331 }
332 for (const auto &initial_dir : initial_dirs) {
333 if (!dir.mkdir(dirName: initial_dir)) {
334 qWarning() << "error" << "failed to make" << initial_dir;
335 return false;
336 }
337 qCDebug(lcFileSystemModel) << test_path + '/' + initial_dir << (QFile::exists(fileName: test_path + '/' + initial_dir));
338 }
339 for (const auto &initial_file : initial_files) {
340 QFile file(test_path + '/' + initial_file);
341 if (!file.open(flags: QIODevice::WriteOnly | QIODevice::Append)) {
342 qDebug() << "failed to open file" << initial_file;
343 return false;
344 }
345 if (!file.resize(sz: 1024 + file.size())) {
346 qDebug() << "failed to resize file" << initial_file;
347 return false;
348 }
349 if (!file.flush()) {
350 qDebug() << "failed to flush file" << initial_file;
351 return false;
352 }
353 file.close();
354#if defined(Q_OS_WIN)
355 if (initial_file[0] == '.') {
356 const QString hiddenFile = QDir::toNativeSeparators(file.fileName());
357 const auto nativeHiddenFile = reinterpret_cast<const wchar_t *>(hiddenFile.utf16());
358#ifndef Q_OS_WINRT
359 DWORD currentAttributes = ::GetFileAttributes(nativeHiddenFile);
360#else // !Q_OS_WINRT
361 WIN32_FILE_ATTRIBUTE_DATA attributeData;
362 if (!::GetFileAttributesEx(nativeHiddenFile, GetFileExInfoStandard, &attributeData))
363 attributeData.dwFileAttributes = 0xFFFFFFFF;
364 DWORD currentAttributes = attributeData.dwFileAttributes;
365#endif // Q_OS_WINRT
366 if (currentAttributes == 0xFFFFFFFF) {
367 qErrnoWarning("failed to get file attributes: %s", qPrintable(hiddenFile));
368 return false;
369 }
370 if (!::SetFileAttributes(nativeHiddenFile, currentAttributes | FILE_ATTRIBUTE_HIDDEN)) {
371 qErrnoWarning("failed to set file hidden: %s", qPrintable(hiddenFile));
372 return false;
373 }
374 }
375#endif
376 qCDebug(lcFileSystemModel) << test_path + '/' + initial_file << (QFile::exists(fileName: test_path + '/' + initial_file));
377 }
378 return true;
379}
380
381QModelIndex tst_QFileSystemModel::prepareTestModelRoot(QFileSystemModel *model, const QString &test_path,
382 QSignalSpy **spy2, QSignalSpy **spy3)
383{
384 if (model->rowCount(parent: model->index(path: test_path)) != 0)
385 return QModelIndex();
386
387 if (spy2)
388 *spy2 = new QSignalSpy(model, &QFileSystemModel::rowsInserted);
389 if (spy3)
390 *spy3 = new QSignalSpy(model, &QFileSystemModel::rowsAboutToBeInserted);
391
392 QStringList files = { "b", "d", "f", "h", "j", ".a", ".c", ".e", ".g" };
393
394 if (!createFiles(model, test_path, initial_files: files))
395 return QModelIndex();
396
397 QModelIndex root = model->setRootPath(test_path);
398 if (!root.isValid())
399 return QModelIndex();
400
401 bool timedOut = false;
402 TRY_WAIT(model->rowCount(root) == 5, &timedOut);
403 if (timedOut)
404 return QModelIndex();
405
406 return root;
407}
408
409void tst_QFileSystemModel::rowCount()
410{
411 QSignalSpy *spy2 = nullptr;
412 QSignalSpy *spy3 = nullptr;
413 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
414 QModelIndex root = prepareTestModelRoot(model: model.data(), test_path: flatDirTestPath, spy2: &spy2, spy3: &spy3);
415 QVERIFY(root.isValid());
416
417 QVERIFY(spy2 && spy2->count() > 0);
418 QVERIFY(spy3 && spy3->count() > 0);
419}
420
421void tst_QFileSystemModel::rowsInserted_data()
422{
423 QTest::addColumn<int>(name: "count");
424 QTest::addColumn<Qt::SortOrder>(name: "ascending");
425 for (int i = 0; i < 4; ++i) {
426 const QByteArray iB = QByteArray::number(i);
427 QTest::newRow(dataTag: ("Qt::AscendingOrder " + iB).constData()) << i << Qt::AscendingOrder;
428 QTest::newRow(dataTag: ("Qt::DescendingOrder " + iB).constData()) << i << Qt::DescendingOrder;
429 }
430}
431
432static inline QString lastEntry(const QModelIndex &root)
433{
434 const QAbstractItemModel *model = root.model();
435 return model->index(row: model->rowCount(parent: root) - 1, column: 0, parent: root).data().toString();
436}
437
438void tst_QFileSystemModel::rowsInserted()
439{
440 const QString tmp = flatDirTestPath;
441 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
442 QModelIndex root = prepareTestModelRoot(model: model.data(), test_path: tmp);
443 QVERIFY(root.isValid());
444
445 QFETCH(Qt::SortOrder, ascending);
446 QFETCH(int, count);
447 model->sort(column: 0, order: ascending);
448
449 QSignalSpy spy0(model.data(), &QAbstractItemModel::rowsInserted);
450 QSignalSpy spy1(model.data(), &QAbstractItemModel::rowsAboutToBeInserted);
451 int oldCount = model->rowCount(parent: root);
452 QStringList files;
453 for (int i = 0; i < count; ++i)
454 files.append(t: QLatin1Char('c') + QString::number(i));
455 QVERIFY(createFiles(model.data(), tmp, files, 5));
456 QTRY_COMPARE(model->rowCount(root), oldCount + count);
457 int totalRowsInserted = 0;
458 for (int i = 0; i < spy0.count(); ++i) {
459 int start = spy0[i].value(i: 1).toInt();
460 int end = spy0[i].value(i: 2).toInt();
461 totalRowsInserted += end - start + 1;
462 }
463 QCOMPARE(totalRowsInserted, count);
464 const QString expected = ascending == Qt::AscendingOrder ? QStringLiteral("j") : QStringLiteral("b");
465 QTRY_COMPARE(lastEntry(root), expected);
466
467 if (spy0.count() > 0) {
468 if (count == 0)
469 QCOMPARE(spy0.count(), 0);
470 else
471 QVERIFY(spy0.count() >= 1);
472 }
473 if (count == 0) QCOMPARE(spy1.count(), 0); else QVERIFY(spy1.count() >= 1);
474
475 QVERIFY(createFiles(model.data(), tmp, QStringList(".hidden_file"), 5 + count));
476
477 if (count != 0)
478 QTRY_VERIFY(spy0.count() >= 1);
479 else
480 QTRY_COMPARE(spy0.count(), 0);
481 if (count != 0)
482 QTRY_VERIFY(spy1.count() >= 1);
483 else
484 QTRY_COMPARE(spy1.count(), 0);
485}
486
487void tst_QFileSystemModel::rowsRemoved_data()
488{
489 rowsInserted_data();
490}
491
492void tst_QFileSystemModel::rowsRemoved()
493{
494 const QString tmp = flatDirTestPath;
495 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
496 QModelIndex root = prepareTestModelRoot(model: model.data(), test_path: tmp);
497 QVERIFY(root.isValid());
498
499 QFETCH(int, count);
500 QFETCH(Qt::SortOrder, ascending);
501 model->sort(column: 0, order: ascending);
502
503 QSignalSpy spy0(model.data(), &QAbstractItemModel::rowsRemoved);
504 QSignalSpy spy1(model.data(), &QAbstractItemModel::rowsAboutToBeRemoved);
505 int oldCount = model->rowCount(parent: root);
506 for (int i = count - 1; i >= 0; --i) {
507 const QString fileName = model->index(row: i, column: 0, parent: root).data().toString();
508 qCDebug(lcFileSystemModel) << "removing" << fileName;
509 QVERIFY(QFile::remove(tmp + QLatin1Char('/') + fileName));
510 }
511 for (int i = 0 ; i < 10; ++i) {
512 if (count != 0) {
513 if (i == 10 || spy0.count() != 0) {
514 QVERIFY(spy0.count() >= 1);
515 QVERIFY(spy1.count() >= 1);
516 }
517 } else {
518 if (i == 10 || spy0.count() == 0) {
519 QCOMPARE(spy0.count(), 0);
520 QCOMPARE(spy1.count(), 0);
521 }
522 }
523 QStringList lst;
524 for (int i = 0; i < model->rowCount(parent: root); ++i)
525 lst.append(t: model->index(row: i, column: 0, parent: root).data().toString());
526 if (model->rowCount(parent: root) == oldCount - count)
527 break;
528 qCDebug(lcFileSystemModel) << "still have:" << lst << QFile::exists(fileName: tmp + QLatin1String("/.a"));
529 QDir tmpLister(tmp);
530 qCDebug(lcFileSystemModel) << tmpLister.entryList();
531 }
532 QTRY_COMPARE(model->rowCount(root), oldCount - count);
533
534 QVERIFY(QFile::exists(tmp + QLatin1String("/.a")));
535 QVERIFY(QFile::remove(tmp + QLatin1String("/.a")));
536 QVERIFY(QFile::remove(tmp + QLatin1String("/.c")));
537
538 if (count != 0) {
539 QVERIFY(spy0.count() >= 1);
540 QVERIFY(spy1.count() >= 1);
541 } else {
542 QCOMPARE(spy0.count(), 0);
543 QCOMPARE(spy1.count(), 0);
544 }
545}
546
547void tst_QFileSystemModel::dataChanged_data()
548{
549 rowsInserted_data();
550}
551
552void tst_QFileSystemModel::dataChanged()
553{
554 QSKIP("This can't be tested right now since we don't watch files, only directories.");
555
556 const QString tmp = flatDirTestPath;
557 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
558 QModelIndex root = prepareTestModelRoot(model: model.data(), test_path: tmp);
559 QVERIFY(root.isValid());
560
561 QFETCH(int, count);
562 QFETCH(Qt::SortOrder, ascending);
563 model->sort(column: 0, order: ascending);
564
565 QSignalSpy spy(model.data(), &QAbstractItemModel::dataChanged);
566 QStringList files;
567 for (int i = 0; i < count; ++i)
568 files.append(t: model->index(row: i, column: 0, parent: root).data().toString());
569 createFiles(model: model.data(), test_path: tmp, initial_files: files);
570
571 QTest::qWait(WAITTIME);
572
573 if (count != 0) QVERIFY(spy.count() >= 1); else QCOMPARE(spy.count(), 0);
574}
575
576void tst_QFileSystemModel::filters_data()
577{
578 QTest::addColumn<QStringList>(name: "files");
579 QTest::addColumn<QStringList>(name: "dirs");
580 QTest::addColumn<QDir::Filters>(name: "dirFilters");
581 QTest::addColumn<QStringList>(name: "nameFilters");
582 QTest::addColumn<int>(name: "rowCount");
583
584 const QStringList abcList{QLatin1String("a"), QLatin1String("b"), QLatin1String("c")};
585 const QStringList zList{QLatin1String("Z")};
586
587 QTest::newRow(dataTag: "no dirs") << abcList << QStringList() << QDir::Filters(QDir::Dirs) << QStringList() << 2;
588 QTest::newRow(dataTag: "no dirs - dot") << abcList << QStringList() << (QDir::Dirs | QDir::NoDot) << QStringList() << 1;
589 QTest::newRow(dataTag: "no dirs - dotdot") << abcList << QStringList() << (QDir::Dirs | QDir::NoDotDot) << QStringList() << 1;
590 QTest::newRow(dataTag: "no dirs - dotanddotdot") << abcList << QStringList() << (QDir::Dirs | QDir::NoDotAndDotDot) << QStringList() << 0;
591 QTest::newRow(dataTag: "one dir - dot") << abcList << zList << (QDir::Dirs | QDir::NoDot) << QStringList() << 2;
592 QTest::newRow(dataTag: "one dir - dotdot") << abcList << zList << (QDir::Dirs | QDir::NoDotDot) << QStringList() << 2;
593 QTest::newRow(dataTag: "one dir - dotanddotdot") << abcList << zList << (QDir::Dirs | QDir::NoDotAndDotDot) << QStringList() << 1;
594 QTest::newRow(dataTag: "one dir") << abcList << zList << QDir::Filters(QDir::Dirs) << QStringList() << 3;
595 QTest::newRow(dataTag: "no dir + hidden") << abcList << QStringList() << (QDir::Dirs | QDir::Hidden) << QStringList() << 2;
596 QTest::newRow(dataTag: "dir+hid+files") << abcList << QStringList() <<
597 (QDir::Dirs | QDir::Files | QDir::Hidden) << QStringList() << 5;
598 QTest::newRow(dataTag: "dir+file+hid-dot .A") << abcList << QStringList{QLatin1String(".A")} <<
599 (QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot) << QStringList() << 4;
600 QTest::newRow(dataTag: "dir+files+hid+dot A") << abcList << QStringList{QLatin1String("AFolder")} <<
601 (QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot) << QStringList{QLatin1String("A*")} << 2;
602 QTest::newRow(dataTag: "dir+files+hid+dot+cas1") << abcList << zList <<
603 (QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot | QDir::CaseSensitive) << zList << 1;
604 QTest::newRow(dataTag: "dir+files+hid+dot+cas2") << abcList << zList <<
605 (QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot | QDir::CaseSensitive) << QStringList{QLatin1String("a")} << 1;
606 QTest::newRow(dataTag: "dir+files+hid+dot+cas+alldir") << abcList << zList <<
607 (QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot | QDir::CaseSensitive | QDir::AllDirs) << zList << 1;
608
609 QTest::newRow(dataTag: "case sensitive") << QStringList{QLatin1String("Antiguagdb"), QLatin1String("Antiguamtd"),
610 QLatin1String("Antiguamtp"), QLatin1String("afghanistangdb"), QLatin1String("afghanistanmtd")}
611 << QStringList() << QDir::Filters(QDir::Files) << QStringList() << 5;
612}
613
614void tst_QFileSystemModel::filters()
615{
616 QString tmp = flatDirTestPath;
617 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
618 QVERIFY(createFiles(model.data(), tmp, QStringList()));
619 QModelIndex root = model->setRootPath(tmp);
620 QFETCH(QStringList, files);
621 QFETCH(QStringList, dirs);
622 QFETCH(QDir::Filters, dirFilters);
623 QFETCH(QStringList, nameFilters);
624 QFETCH(int, rowCount);
625
626 if (nameFilters.count() > 0)
627 model->setNameFilters(nameFilters);
628 model->setNameFilterDisables(false);
629 model->setFilter(dirFilters);
630
631 QVERIFY(createFiles(model.data(), tmp, files, 0, dirs));
632 QTRY_COMPARE(model->rowCount(root), rowCount);
633
634 // Make sure that we do what QDir does
635 QDir xFactor(tmp);
636 QStringList dirEntries;
637
638 if (nameFilters.count() > 0)
639 dirEntries = xFactor.entryList(nameFilters, filters: dirFilters);
640 else
641 dirEntries = xFactor.entryList(filters: dirFilters);
642
643 QCOMPARE(dirEntries.count(), rowCount);
644
645 QStringList modelEntries;
646
647 for (int i = 0; i < rowCount; ++i)
648 modelEntries.append(t: model->data(index: model->index(row: i, column: 0, parent: root), role: QFileSystemModel::FileNameRole).toString());
649
650 std::sort(first: dirEntries.begin(), last: dirEntries.end());
651 std::sort(first: modelEntries.begin(), last: modelEntries.end());
652 QCOMPARE(dirEntries, modelEntries);
653
654#ifdef Q_OS_LINUX
655 if (files.count() >= 3 && rowCount >= 3 && rowCount != 5) {
656 QString fileName1 = (tmp + '/' + files.at(i: 0));
657 QString fileName2 = (tmp + '/' + files.at(i: 1));
658 QString fileName3 = (tmp + '/' + files.at(i: 2));
659 QFile::Permissions originalPermissions = QFile::permissions(filename: fileName1);
660 QVERIFY(QFile::setPermissions(fileName1, QFile::WriteOwner));
661 QVERIFY(QFile::setPermissions(fileName2, QFile::ReadOwner));
662 QVERIFY(QFile::setPermissions(fileName3, QFile::ExeOwner));
663
664 model->setFilter((QDir::Files | QDir::Readable));
665 QTRY_COMPARE(model->rowCount(root), 1);
666
667 model->setFilter((QDir::Files | QDir::Writable));
668 QTRY_COMPARE(model->rowCount(root), 1);
669
670 model->setFilter((QDir::Files | QDir::Executable));
671 QTRY_COMPARE(model->rowCount(root), 1);
672
673 // reset permissions
674 QVERIFY(QFile::setPermissions(fileName1, originalPermissions));
675 QVERIFY(QFile::setPermissions(fileName2, originalPermissions));
676 QVERIFY(QFile::setPermissions(fileName3, originalPermissions));
677 }
678#endif
679}
680
681void tst_QFileSystemModel::nameFilters()
682{
683 QStringList list;
684 list << "a" << "b" << "c";
685 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
686 model->setNameFilters(list);
687 model->setNameFilterDisables(false);
688 QCOMPARE(model->nameFilters(), list);
689
690 QString tmp = flatDirTestPath;
691 QVERIFY(createFiles(model.data(), tmp, list));
692 QModelIndex root = model->setRootPath(tmp);
693 QTRY_COMPARE(model->rowCount(root), 3);
694
695 QStringList filters;
696 filters << "a" << "b";
697 model->setNameFilters(filters);
698 QTRY_COMPARE(model->rowCount(root), 2);
699}
700void tst_QFileSystemModel::setData_data()
701{
702 QTest::addColumn<QString>(name: "subdirName");
703 QTest::addColumn<QStringList>(name: "files");
704 QTest::addColumn<QString>(name: "oldFileName");
705 QTest::addColumn<QString>(name: "newFileName");
706 QTest::addColumn<bool>(name: "success");
707 /*QTest::newRow("outside current dir") << (QStringList() << "a" << "b" << "c")
708 << flatDirTestPath + '/' + "a"
709 << QDir::temp().absolutePath() + '/' + "a"
710 << false;
711 */
712
713 const QStringList abcList{QLatin1String("a"), QLatin1String("b"), QLatin1String("c")};
714 QTest::newRow(dataTag: "in current dir")
715 << QString()
716 << abcList
717 << "a"
718 << "d"
719 << true;
720 QTest::newRow(dataTag: "in subdir")
721 << "s"
722 << abcList
723 << "a"
724 << "d"
725 << true;
726}
727
728void tst_QFileSystemModel::setData()
729{
730 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
731 QSignalSpy spy(model.data(), &QFileSystemModel::fileRenamed);
732 QFETCH(QString, subdirName);
733 QFETCH(QStringList, files);
734 QFETCH(QString, oldFileName);
735 QFETCH(QString, newFileName);
736 QFETCH(bool, success);
737
738 QString tmp = flatDirTestPath;
739 if (!subdirName.isEmpty()) {
740 QDir dir(tmp);
741 QVERIFY(dir.mkdir(subdirName));
742 tmp.append(s: '/' + subdirName);
743 }
744 QVERIFY(createFiles(model.data(), tmp, files));
745 QModelIndex tmpIdx = model->setRootPath(flatDirTestPath);
746 if (!subdirName.isEmpty()) {
747 tmpIdx = model->index(path: tmp);
748 model->fetchMore(parent: tmpIdx);
749 }
750 QTRY_COMPARE(model->rowCount(tmpIdx), files.count());
751
752 QModelIndex idx = model->index(path: tmp + '/' + oldFileName);
753 QCOMPARE(idx.isValid(), true);
754 QCOMPARE(model->setData(idx, newFileName), false);
755
756 model->setReadOnly(false);
757 QCOMPARE(model->setData(idx, newFileName), success);
758 model->setReadOnly(true);
759 if (success) {
760 QCOMPARE(spy.count(), 1);
761 QList<QVariant> arguments = spy.takeFirst();
762 QCOMPARE(model->data(idx, QFileSystemModel::FileNameRole).toString(), newFileName);
763 QCOMPARE(model->fileInfo(idx).filePath(), tmp + '/' + newFileName);
764 QCOMPARE(model->index(arguments.at(0).toString()), model->index(tmp));
765 QCOMPARE(arguments.at(1).toString(), oldFileName);
766 QCOMPARE(arguments.at(2).toString(), newFileName);
767 QCOMPARE(QFile::rename(tmp + '/' + newFileName, tmp + '/' + oldFileName), true);
768 }
769 QTRY_COMPARE(model->rowCount(tmpIdx), files.count());
770 // cleanup
771 if (!subdirName.isEmpty())
772 QVERIFY(QDir(tmp).removeRecursively());
773}
774
775void tst_QFileSystemModel::sortPersistentIndex()
776{
777 QTemporaryFile file(flatDirTestPath + QStringLiteral("/XXXXXX.dat"));
778 QVERIFY2(file.open(), qPrintable(file.errorString()));
779 const QFileInfo fileInfo(file.fileName());
780 file.close();
781 QTRY_VERIFY(QDir(flatDirTestPath).entryInfoList().contains(fileInfo));
782 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
783 QModelIndex root = model->setRootPath(flatDirTestPath);
784 QTRY_VERIFY(model->rowCount(root) > 0);
785
786 QPersistentModelIndex idx = model->index(row: 0, column: 1, parent: root);
787 model->sort(column: 0, order: Qt::AscendingOrder);
788 model->sort(column: 0, order: Qt::DescendingOrder);
789 QVERIFY(idx.column() != 0);
790}
791
792class MyFriendFileSystemModel : public QFileSystemModel
793{
794 friend class tst_QFileSystemModel;
795 Q_DECLARE_PRIVATE(QFileSystemModel)
796};
797
798void tst_QFileSystemModel::sort_data()
799{
800 QTest::addColumn<bool>(name: "fileDialogMode");
801 QTest::newRow(dataTag: "standard usage") << false;
802 QTest::newRow(dataTag: "QFileDialog usage") << true;
803}
804
805void tst_QFileSystemModel::sort()
806{
807 QFETCH(bool, fileDialogMode);
808
809 QScopedPointer<MyFriendFileSystemModel> myModel(new MyFriendFileSystemModel);
810 QTreeView tree;
811 tree.setWindowTitle(QTest::currentTestFunction());
812
813 if (fileDialogMode && EmulationDetector::isRunningArmOnX86())
814 QSKIP("Crashes in QEMU. QTBUG-70572");
815
816#ifdef QT_BUILD_INTERNAL
817 if (fileDialogMode)
818 myModel->d_func()->disableRecursiveSort = true;
819#endif
820
821 QDir dir(flatDirTestPath);
822 const QString dirPath = dir.absolutePath();
823
824 //Create a file that will be at the end when sorting by name (For Mac, the default)
825 //but if we sort by size descending it will be the first
826 QFile tempFile(dirPath + "/plop2.txt");
827 tempFile.open(flags: QIODevice::WriteOnly | QIODevice::Text);
828 QTextStream out(&tempFile);
829 out << "The magic number is: " << 49 << "\n";
830 tempFile.close();
831
832 QFile tempFile2(dirPath + "/plop.txt");
833 tempFile2.open(flags: QIODevice::WriteOnly | QIODevice::Text);
834 QTextStream out2(&tempFile2);
835 out2 << "The magic number is : " << 49 << " but i write some stuff in the file \n";
836 tempFile2.close();
837
838 myModel->setRootPath("");
839 myModel->setFilter(QDir::AllEntries | QDir::System | QDir::Hidden);
840 tree.setSortingEnabled(true);
841 tree.setModel(myModel.data());
842 tree.show();
843 tree.resize(w: 800, h: 800);
844 QVERIFY(QTest::qWaitForWindowExposed(&tree));
845 tree.header()->setSortIndicator(logicalIndex: 1, order: Qt::DescendingOrder);
846 tree.header()->setSectionResizeMode(logicalIndex: 0, mode: QHeaderView::ResizeToContents);
847 QStringList dirsToOpen;
848 do {
849 dirsToOpen << dir.absolutePath();
850 } while (dir.cdUp());
851
852 for (int i = dirsToOpen.size() -1 ; i > 0 ; --i) {
853 QString path = dirsToOpen[i];
854 tree.expand(index: myModel->index(path, column: 0));
855 }
856 tree.expand(index: myModel->index(path: dirPath, column: 0));
857 QModelIndex parent = myModel->index(path: dirPath, column: 0);
858 QList<QString> expectedOrder;
859 expectedOrder << tempFile2.fileName() << tempFile.fileName() << dirPath + QChar('/') + ".." << dirPath + QChar('/') + ".";
860
861 if (fileDialogMode) {
862 QTRY_COMPARE(myModel->rowCount(parent), expectedOrder.count());
863 // File dialog Mode means sub trees are not sorted, only the current root.
864 // There's no way we can check that the sub tree is "not sorted"; just check if it
865 // has the same contents of the expected list
866 QList<QString> actualRows;
867 for(int i = 0; i < myModel->rowCount(parent); ++i)
868 {
869 actualRows << dirPath + QChar('/') + myModel->index(row: i, column: 1, parent).data(arole: QFileSystemModel::FileNameRole).toString();
870 }
871
872 std::sort(first: expectedOrder.begin(), last: expectedOrder.end());
873 std::sort(first: actualRows.begin(), last: actualRows.end());
874
875 QCOMPARE(actualRows, expectedOrder);
876 } else {
877 for(int i = 0; i < myModel->rowCount(parent); ++i)
878 {
879 QTRY_COMPARE(dirPath + QChar('/') + myModel->index(i, 1, parent).data(QFileSystemModel::FileNameRole).toString(), expectedOrder.at(i));
880 }
881 }
882}
883
884void tst_QFileSystemModel::mkdir()
885{
886 QString tmp = flatDirTestPath;
887 QString newFolderPath = QDir::toNativeSeparators(pathName: tmp + '/' + "NewFoldermkdirtest4");
888 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
889 QModelIndex tmpDir = model->index(path: tmp);
890 QVERIFY(tmpDir.isValid());
891 QDir bestatic(newFolderPath);
892 if (bestatic.exists()) {
893 if (!bestatic.rmdir(dirName: newFolderPath))
894 qWarning() << "unable to remove" << newFolderPath;
895 QTest::qWait(WAITTIME);
896 }
897 model->mkdir(parent: tmpDir, name: "NewFoldermkdirtest3");
898 model->mkdir(parent: tmpDir, name: "NewFoldermkdirtest5");
899 QModelIndex idx = model->mkdir(parent: tmpDir, name: "NewFoldermkdirtest4");
900 QVERIFY(idx.isValid());
901 int oldRow = idx.row();
902 idx = model->index(path: newFolderPath);
903 QVERIFY(idx.isValid());
904 QVERIFY(model->remove(idx));
905 QVERIFY(!bestatic.exists());
906 QVERIFY(0 != idx.row());
907 QCOMPARE(oldRow, idx.row());
908}
909
910void tst_QFileSystemModel::deleteFile()
911{
912 QString newFilePath = QDir::temp().filePath(fileName: "NewFileDeleteTest");
913 QFile newFile(newFilePath);
914 if (newFile.exists()) {
915 if (!newFile.remove())
916 qWarning() << "unable to remove" << newFilePath;
917 QTest::qWait(WAITTIME);
918 }
919 if (!newFile.open(flags: QIODevice::WriteOnly | QIODevice::Text)) {
920 qWarning() << "unable to create" << newFilePath;
921 }
922 newFile.close();
923 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
924 QModelIndex idx = model->index(path: newFilePath);
925 QVERIFY(idx.isValid());
926 QVERIFY(model->remove(idx));
927 QVERIFY(!newFile.exists());
928}
929
930void tst_QFileSystemModel::deleteDirectory()
931{
932 // QTBUG-65683: Verify that directories can be removed recursively despite
933 // file system watchers being active on them or their sub-directories (Windows).
934 // Create a temporary directory, a nested directory and expand a treeview
935 // to show them to ensure watcher creation. Then delete the directory.
936 QTemporaryDir dirToBeDeleted(flatDirTestPath + QStringLiteral("/deleteDirectory-XXXXXX"));
937 QVERIFY(dirToBeDeleted.isValid());
938 const QString dirToBeDeletedPath = dirToBeDeleted.path();
939 const QString nestedTestDir = QStringLiteral("test");
940 QVERIFY(QDir(dirToBeDeletedPath).mkpath(nestedTestDir));
941 const QString nestedTestDirPath = dirToBeDeletedPath + QLatin1Char('/') + nestedTestDir;
942 QFile testFile(nestedTestDirPath + QStringLiteral("/test.txt"));
943 QVERIFY(testFile.open(QIODevice::WriteOnly | QIODevice::Text));
944 testFile.write(data: "Hello\n");
945 testFile.close();
946
947 QFileSystemModel model;
948 const QModelIndex rootIndex = model.setRootPath(flatDirTestPath);
949 QTreeView treeView;
950 treeView.setWindowTitle(QTest::currentTestFunction());
951 treeView.setModel(&model);
952 treeView.setRootIndex(rootIndex);
953
954 const QModelIndex dirToBeDeletedPathIndex = model.index(path: dirToBeDeletedPath);
955 QVERIFY(dirToBeDeletedPathIndex.isValid());
956 treeView.setExpanded(index: dirToBeDeletedPathIndex, expand: true);
957 const QModelIndex nestedTestDirIndex = model.index(path: nestedTestDirPath);
958 QVERIFY(nestedTestDirIndex.isValid());
959 treeView.setExpanded(index: nestedTestDirIndex, expand: true);
960
961 treeView.show();
962 QVERIFY(QTest::qWaitForWindowExposed(&treeView));
963
964 QVERIFY(model.remove(dirToBeDeletedPathIndex));
965 dirToBeDeleted.setAutoRemove(false);
966}
967
968static QString flipCase(QString s)
969{
970 for (int i = 0, size = s.size(); i < size; ++i) {
971 const QChar c = s.at(i);
972 if (c.isUpper())
973 s[i] = c.toLower();
974 else if (c.isLower())
975 s[i] = c.toUpper();
976 }
977 return s;
978}
979
980void tst_QFileSystemModel::caseSensitivity()
981{
982 QString tmp = flatDirTestPath;
983 QStringList files;
984 files << "a" << "c" << "C";
985 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
986 QVERIFY(createFiles(model.data(), tmp, files));
987 QModelIndex root = model->index(path: tmp);
988 QStringList paths;
989 QModelIndexList indexes;
990 QCOMPARE(model->rowCount(root), 0);
991 for (int i = 0; i < files.count(); ++i) {
992 const QString path = tmp + '/' + files.at(i);
993 const QModelIndex index = model->index(path);
994 QVERIFY(index.isValid());
995 paths.append(t: path);
996 indexes.append(t: index);
997 }
998
999 if (!QFileSystemEngine::isCaseSensitive()) {
1000 // QTBUG-31103, QTBUG-64147: Verify that files can be accessed by paths with fLipPeD case.
1001 for (int i = 0; i < paths.count(); ++i) {
1002 const QModelIndex flippedCaseIndex = model->index(path: flipCase(s: paths.at(i)));
1003 QCOMPARE(indexes.at(i), flippedCaseIndex);
1004 }
1005 }
1006}
1007
1008void tst_QFileSystemModel::drives_data()
1009{
1010 QTest::addColumn<QString>(name: "path");
1011 QTest::newRow(dataTag: "current") << QDir::currentPath();
1012 QTest::newRow(dataTag: "slash") << "/";
1013 QTest::newRow(dataTag: "My Computer") << "My Computer";
1014}
1015
1016void tst_QFileSystemModel::drives()
1017{
1018 QFETCH(QString, path);
1019 QFileSystemModel model;
1020 model.setRootPath(path);
1021 model.fetchMore(parent: QModelIndex());
1022 QFileInfoList drives = QDir::drives();
1023 int driveCount = 0;
1024 foreach(const QFileInfo& driveRoot, drives)
1025 if (driveRoot.exists())
1026 driveCount++;
1027 QTRY_COMPARE(model.rowCount(), driveCount);
1028}
1029
1030void tst_QFileSystemModel::dirsBeforeFiles()
1031{
1032 auto diagnosticMsg = [](int row, const QFileInfo &left, const QFileInfo &right) -> QByteArray {
1033 QString message;
1034 QDebug(&message).noquote() << "Unexpected sort order at #" << row << ':' << left << right;
1035 return message.toLocal8Bit();
1036 };
1037 QTemporaryDir testDir(flatDirTestPath);
1038 QVERIFY2(testDir.isValid(), qPrintable(testDir.errorString()));
1039 QDir dir(testDir.path());
1040
1041 const int itemCount = 3;
1042 for (int i = 0; i < itemCount; ++i) {
1043 QLatin1Char c('a' + char(i));
1044 QVERIFY(dir.mkdir(c + QLatin1String("-dir")));
1045 QFile file(dir.filePath(fileName: c + QLatin1String("-file")));
1046 QVERIFY(file.open(QIODevice::ReadWrite));
1047 file.close();
1048 }
1049
1050 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
1051 QModelIndex root = model->setRootPath(dir.absolutePath());
1052 // Wait for model to be notified by the file system watcher
1053 QTRY_COMPARE(model->rowCount(root), 2 * itemCount);
1054 // sort explicitly - dirs before files (except on macOS), and then by name
1055 model->sort(column: 0);
1056 // Ensure that no file occurs before any directory (see QFileSystemModelSorter):
1057 for (int i = 1, count = model->rowCount(parent: root); i < count; ++i) {
1058 const QFileInfo previous = model->fileInfo(index: model->index(row: i - 1, column: 0, parent: root));
1059 const QFileInfo current = model->fileInfo(index: model->index(row: i, column: 0, parent: root));
1060#ifndef Q_OS_MAC
1061 QVERIFY2(!(previous.isFile() && current.isDir()), diagnosticMsg(i, previous, current).constData());
1062#else
1063 QVERIFY2(previous.fileName() < current.fileName(), diagnosticMsg(i, previous, current).constData());
1064#endif
1065 }
1066}
1067
1068void tst_QFileSystemModel::roleNames_data()
1069{
1070 QTest::addColumn<int>(name: "role");
1071 QTest::addColumn<QByteArray>(name: "roleName");
1072 QTest::newRow(dataTag: "decoration") << int(Qt::DecorationRole) << QByteArray("fileIcon");
1073 QTest::newRow(dataTag: "display") << int(Qt::DisplayRole) << QByteArray("display");
1074 QTest::newRow(dataTag: "fileIcon") << int(QFileSystemModel::FileIconRole) << QByteArray("fileIcon");
1075 QTest::newRow(dataTag: "filePath") << int(QFileSystemModel::FilePathRole) << QByteArray("filePath");
1076 QTest::newRow(dataTag: "fileName") << int(QFileSystemModel::FileNameRole) << QByteArray("fileName");
1077 QTest::newRow(dataTag: "filePermissions") << int(QFileSystemModel::FilePermissions) << QByteArray("filePermissions");
1078}
1079
1080void tst_QFileSystemModel::roleNames()
1081{
1082 QFileSystemModel model;
1083 QHash<int, QByteArray> roles = model.roleNames();
1084
1085 QFETCH(int, role);
1086 QVERIFY(roles.contains(role));
1087
1088 QFETCH(QByteArray, roleName);
1089 QCOMPARE(roles.values(role).count(), 1);
1090 QCOMPARE(roles.value(role), roleName);
1091}
1092
1093static inline QByteArray permissionRowName(bool readOnly, int permission)
1094{
1095 QByteArray result = readOnly ? QByteArrayLiteral("ro") : QByteArrayLiteral("rw");
1096 result += QByteArrayLiteral("-0");
1097 result += QByteArray::number(permission, base: 16);
1098 return result;
1099}
1100
1101void tst_QFileSystemModel::permissions_data()
1102{
1103 QTest::addColumn<QFileDevice::Permissions>(name: "permissions");
1104 QTest::addColumn<bool>(name: "readOnly");
1105
1106 static const int permissions[] = {
1107 QFile::WriteOwner,
1108 QFile::ReadOwner,
1109 QFile::WriteOwner|QFile::ReadOwner,
1110 };
1111 for (int permission : permissions) {
1112 QTest::newRow(dataTag: permissionRowName(readOnly: false, permission).constData()) << QFileDevice::Permissions(permission) << false;
1113 QTest::newRow(dataTag: permissionRowName(readOnly: true, permission).constData()) << QFileDevice::Permissions(permission) << true;
1114 }
1115}
1116
1117void tst_QFileSystemModel::permissions() // checks QTBUG-20503
1118{
1119 QFETCH(QFileDevice::Permissions, permissions);
1120 QFETCH(bool, readOnly);
1121
1122 const QString tmp = flatDirTestPath;
1123 const QString file = tmp + QLatin1String("/f");
1124 QScopedPointer<QFileSystemModel> model(new QFileSystemModel);
1125 QVERIFY(createFiles(model.data(), tmp, QStringList{QLatin1String("f")}));
1126
1127 QVERIFY(QFile::setPermissions(file, permissions));
1128
1129 const QModelIndex root = model->setRootPath(tmp);
1130
1131 model->setReadOnly(readOnly);
1132
1133 QCOMPARE(model->isReadOnly(), readOnly);
1134
1135 QTRY_COMPARE(model->rowCount(root), 1);
1136
1137 const QFile::Permissions modelPermissions = model->permissions(index: model->index(row: 0, column: 0, parent: root));
1138 const QFile::Permissions modelFileInfoPermissions = model->fileInfo(index: model->index(row: 0, column: 0, parent: root)).permissions();
1139 const QFile::Permissions fileInfoPermissions = QFileInfo(file).permissions();
1140
1141 QCOMPARE(modelPermissions, modelFileInfoPermissions);
1142 QCOMPARE(modelFileInfoPermissions, fileInfoPermissions);
1143 QCOMPARE(fileInfoPermissions, modelPermissions);
1144}
1145
1146void tst_QFileSystemModel::doNotUnwatchOnFailedRmdir()
1147{
1148 const QString tmp = flatDirTestPath;
1149
1150 QFileSystemModel model;
1151
1152 const QTemporaryDir tempDir(tmp + '/' + QStringLiteral("doNotUnwatchOnFailedRmdir-XXXXXX"));
1153 QVERIFY(tempDir.isValid());
1154
1155 const QModelIndex rootIndex = model.setRootPath(tempDir.path());
1156
1157 // create a file in the directory so to prevent it from deletion
1158 {
1159 QFile file(tempDir.path() + '/' + QStringLiteral("file1"));
1160 QVERIFY(file.open(QIODevice::WriteOnly));
1161 }
1162
1163 QCOMPARE(model.rmdir(rootIndex), false);
1164
1165 // create another file
1166 {
1167 QFile file(tempDir.path() + '/' + QStringLiteral("file2"));
1168 QVERIFY(file.open(QIODevice::WriteOnly));
1169 }
1170
1171 // the model must now detect this second file
1172 QTRY_COMPARE(model.rowCount(rootIndex), 2);
1173}
1174
1175static QSet<QString> fileListUnderIndex(const QFileSystemModel *model, const QModelIndex &parent)
1176{
1177 QSet<QString> fileNames;
1178 const int rowCount = model->rowCount(parent);
1179 for (int i = 0; i < rowCount; ++i)
1180 fileNames.insert(value: model->index(row: i, column: 0, parent).data(arole: QFileSystemModel::FileNameRole).toString());
1181 return fileNames;
1182}
1183
1184void tst_QFileSystemModel::specialFiles()
1185{
1186#ifndef Q_OS_UNIX
1187 QSKIP("Not implemented");
1188#endif
1189
1190 QFileSystemModel model;
1191
1192 model.setFilter(QDir::AllEntries | QDir::System | QDir::Hidden);
1193
1194 // Can't simply verify if the model returns a valid model index for a special file
1195 // as it will always return a valid index for existing files,
1196 // even if the file is not visible with the given filter.
1197
1198 const QModelIndex rootIndex = model.setRootPath(QStringLiteral("/dev/"));
1199 const QString testFileName = QStringLiteral("null");
1200
1201 QTRY_VERIFY(fileListUnderIndex(&model, rootIndex).contains(testFileName));
1202
1203 model.setFilter(QDir::AllEntries | QDir::Hidden);
1204
1205 QTRY_VERIFY(!fileListUnderIndex(&model, rootIndex).contains(testFileName));
1206}
1207
1208void tst_QFileSystemModel::fileInfo()
1209{
1210 QFileSystemModel model;
1211 QModelIndex idx;
1212
1213 QVERIFY(model.fileInfo(idx).filePath().isEmpty());
1214
1215 const QString dirPath = flatDirTestPath;
1216 QDir dir(dirPath);
1217 const QString subdir = QStringLiteral("subdir");
1218 QVERIFY(dir.mkdir(subdir));
1219 const QString subdirPath = dir.absoluteFilePath(fileName: subdir);
1220
1221 idx = model.setRootPath(subdirPath);
1222 QCOMPARE(model.fileInfo(idx), QFileInfo(subdirPath));
1223 idx = model.setRootPath(dirPath);
1224 QCOMPARE(model.fileInfo(idx), QFileInfo(dirPath));
1225}
1226
1227QTEST_MAIN(tst_QFileSystemModel)
1228#include "tst_qfilesystemmodel.moc"
1229
1230

source code of qtbase/tests/auto/widgets/dialogs/qfilesystemmodel/tst_qfilesystemmodel.cpp