summaryrefslogtreecommitdiffstats
path: root/tests/auto/corelib/io/qfile/tst_qfile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/corelib/io/qfile/tst_qfile.cpp')
-rw-r--r--tests/auto/corelib/io/qfile/tst_qfile.cpp697
1 files changed, 548 insertions, 149 deletions
diff --git a/tests/auto/corelib/io/qfile/tst_qfile.cpp b/tests/auto/corelib/io/qfile/tst_qfile.cpp
index 8ea01b4680..00fc1b2aa4 100644
--- a/tests/auto/corelib/io/qfile/tst_qfile.cpp
+++ b/tests/auto/corelib/io/qfile/tst_qfile.cpp
@@ -1,31 +1,6 @@
-/****************************************************************************
-**
-** Copyright (C) 2021 The Qt Company Ltd.
-** Copyright (C) 2021 Intel Corporation.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2021 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#define _CRT_SECURE_NO_WARNINGS 1
@@ -38,16 +13,22 @@
#include <QDir>
#include <QFile>
#include <QFileInfo>
-#include <QTemporaryDir>
-#include <QTemporaryFile>
#include <QOperatingSystemVersion>
+#include <QRandomGenerator>
#include <QStorageInfo>
#include <QScopeGuard>
+#include <QStandardPaths>
+#include <QTemporaryDir>
+#include <QTemporaryFile>
#include <private/qabstractfileengine_p.h>
#include <private/qfsfileengine_p.h>
#include <private/qfilesystemengine_p.h>
+#ifdef Q_OS_WIN
+#include <QtCore/private/qfunctions_win_p.h>
+#endif
+
#include <QtTest/private/qemulationdetector_p.h>
#ifdef Q_OS_WIN
@@ -69,10 +50,12 @@ QT_END_NAMESPACE
# include <unistd.h>
# include <private/qcore_unix_p.h>
#endif
-#ifdef Q_OS_MAC
+#ifdef Q_OS_DARWIN
# include <sys/mount.h>
#elif defined(Q_OS_LINUX)
+# include <sys/eventfd.h>
# include <sys/vfs.h>
+# include <sys/wait.h>
#elif defined(Q_OS_FREEBSD)
# include <sys/param.h>
# include <sys/mount.h>
@@ -99,9 +82,7 @@ QT_END_NAMESPACE
# undef fileno
#endif
-#if defined(Q_OS_WIN)
-#include "../../../network-settings.h"
-#endif
+#include "../../../../shared/filesystem.h"
#ifndef STDIN_FILENO
#define STDIN_FILENO 0
@@ -119,6 +100,8 @@ QT_END_NAMESPACE
#define QT_OPEN_BINARY 0
#endif
+using namespace Qt::StringLiterals;
+
Q_DECLARE_METATYPE(QFile::FileError)
@@ -192,7 +175,11 @@ private slots:
#ifdef Q_OS_WIN
void permissionsNtfs_data();
void permissionsNtfs();
+#if QT_DEPRECATED_SINCE(6,6)
+ void deprecatedNtfsPermissionCheck();
#endif
+#endif
+ void setPermissions_data();
void setPermissions();
void copy();
void copyAfterFail();
@@ -215,8 +202,12 @@ private slots:
void flush();
void bufferedRead();
#ifdef Q_OS_UNIX
+ void isSequential_data();
void isSequential();
#endif
+ void decodeName_data();
+ void decodeName();
+ void encodeName_data() { decodeName_data(); }
void encodeName();
void truncate();
void seekToPos();
@@ -238,12 +229,15 @@ private slots:
void writeLargeDataBlock();
void readFromWriteOnlyFile();
void writeToReadOnlyFile();
-#if defined(Q_OS_LINUX) || defined(Q_OS_AIX) || defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
+#if defined(Q_OS_LINUX)
+ void virtualFile_data();
void virtualFile();
#endif
-#ifdef Q_OS_UNIX
+#if defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
void unixPipe_data();
void unixPipe();
+ void unixFifo_data() { unixPipe_data(); }
+ void unixFifo();
void socketPair_data() { unixPipe_data(); }
void socketPair();
#endif
@@ -295,6 +289,10 @@ private slots:
void moveToTrash_data();
void moveToTrash();
+ void moveToTrashDuplicateName();
+ void moveToTrashOpenFile_data();
+ void moveToTrashOpenFile();
+ void moveToTrashXdgSafety();
void stdfilesystem();
@@ -422,7 +420,8 @@ void tst_QFile::cleanup()
// Clean out everything except the readonly-files.
const QDir dir(m_temporaryDir.path());
- foreach (const QFileInfo &fi, dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)) {
+ const auto entries = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot);
+ for (const QFileInfo &fi : entries) {
const QString fileName = fi.fileName();
if (fileName != QLatin1String(noReadFile) && fileName != QLatin1String(readOnlyFile)) {
const QString absoluteFilePath = fi.absoluteFilePath();
@@ -440,6 +439,8 @@ void tst_QFile::cleanup()
tst_QFile::tst_QFile() : m_oldDir(QDir::currentPath())
{
+ QStandardPaths::setTestModeEnabled(true);
+ QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation));
}
static QByteArray msgOpenFailed(QIODevice::OpenMode om, const QFile &file)
@@ -565,7 +566,7 @@ void tst_QFile::exists()
QVERIFY(!file.exists());
#if defined(Q_OS_WIN)
- const QString uncPath = "//" + QtNetworkSettings::winServerName() + "/testshare/readme.txt";
+ const QString uncPath = "//" + QTest::uncServerName() + "/testshare/readme.txt";
QFile unc(uncPath);
QVERIFY2(unc.exists(), msgFileDoesNotExist(uncPath).constData());
#endif
@@ -631,7 +632,7 @@ void tst_QFile::open_data()
QTest::newRow("//./PhysicalDrive0") << QString("//./PhysicalDrive0") << int(QIODevice::ReadOnly)
<< false << QFile::OpenError;
}
- QTest::newRow("uncFile") << "//" + QtNetworkSettings::winServerName() + "/testshare/test.pri" << int(QIODevice::ReadOnly)
+ QTest::newRow("uncFile") << "//" + QTest::uncServerName() + "/testshare/test.pri" << int(QIODevice::ReadOnly)
<< true << QFile::NoError;
#endif
}
@@ -645,7 +646,7 @@ void tst_QFile::open()
QFETCH( bool, ok );
-#if defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS)
+#if defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS) && !defined(Q_OS_WASM)
if (::getuid() == 0)
// root and Chuck Norris don't care for file permissions. Skip.
QSKIP("Running this test as root doesn't make sense");
@@ -705,7 +706,7 @@ void tst_QFile::size_data()
QTest::newRow( "exist01" ) << m_testFile << (qint64)245;
#if defined(Q_OS_WIN)
// Only test UNC on Windows./
- QTest::newRow("unc") << "//" + QString(QtNetworkSettings::winServerName() + "/testshare/test.pri") << (qint64)34;
+ QTest::newRow("unc") << "//" + QString(QTest::uncServerName() + "/testshare/test.pri") << (qint64)34;
#endif
}
@@ -1003,7 +1004,6 @@ void tst_QFile::readAllStdin()
process.start(m_stdinProcess, QStringList(QStringLiteral("all")));
QVERIFY2(process.waitForStarted(), qPrintable(process.errorString()));
for (int i = 0; i < 5; ++i) {
- QTest::qWait(1000);
process.write(lotsOfData);
while (process.bytesToWrite() > 0)
QVERIFY(process.waitForBytesWritten());
@@ -1038,7 +1038,6 @@ void tst_QFile::readLineStdin()
QIODevice::Text | QIODevice::ReadWrite);
QVERIFY2(process.waitForStarted(), qPrintable(process.errorString()));
for (int i = 0; i < 5; ++i) {
- QTest::qWait(1000);
process.write(lotsOfData);
while (process.bytesToWrite() > 0)
QVERIFY(process.waitForBytesWritten());
@@ -1119,7 +1118,7 @@ void tst_QFile::missingEndOfLine()
void tst_QFile::readBlock()
{
QFile f( m_testFile );
- f.open( QIODevice::ReadOnly );
+ QVERIFY( f.open( QIODevice::ReadOnly ) );
int length = 0;
char p[256];
@@ -1134,7 +1133,7 @@ void tst_QFile::readBlock()
void tst_QFile::getch()
{
QFile f( m_testFile );
- f.open( QIODevice::ReadOnly );
+ QVERIFY( f.open( QIODevice::ReadOnly ) );
char c;
int i = 0;
@@ -1221,6 +1220,11 @@ static inline QChar invalidDriveLetter()
void tst_QFile::invalidFile_data()
{
QTest::addColumn<QString>("fileName");
+
+#if defined(Q_OS_WASM)
+ QSKIP("No invalid files on wasm");
+#endif
+
#if !defined(Q_OS_WIN)
QTest::newRow( "x11" ) << QString( "qwe//" );
#else
@@ -1235,7 +1239,6 @@ void tst_QFile::invalidFile_data()
QTest::newRow( "pipe" ) << QString( "fail|invalid" );
#endif
}
-
void tst_QFile::invalidFile()
{
QFETCH( QString, fileName );
@@ -1289,8 +1292,7 @@ void tst_QFile::createFilePermissions()
QFETCH(QFile::Permissions, permissions);
#ifdef Q_OS_WIN
- QScopedValueRollback<int> ntfsMode(qt_ntfs_permission_lookup);
- ++qt_ntfs_permission_lookup;
+ QNtfsPermissionCheckGuard permissionGuard;
#endif
#ifdef Q_OS_UNIX
auto restoreMask = qScopeGuard([oldMask = umask(0)] { umask(oldMask); });
@@ -1302,7 +1304,7 @@ void tst_QFile::createFilePermissions()
QFile::ReadOwner, QFile::WriteOwner, QFile::ExeOwner
};
- const QString fileName = u"createme.txt"_qs;
+ const QString fileName = u"createme.txt"_s;
QFile::remove(fileName);
QVERIFY(!QFile::exists(fileName));
@@ -1379,7 +1381,10 @@ void tst_QFile::permissions_data()
QTest::addColumn<bool>("expected");
QTest::addColumn<bool>("create");
+#ifndef Q_OS_WASM
+ // Application path is empty on wasm
QTest::newRow("data0") << QCoreApplication::instance()->applicationFilePath() << uint(QFile::ExeUser) << true << false;
+#endif
QTest::newRow("data1") << m_testSourceFile << uint(QFile::ReadUser) << true << false;
QTest::newRow("readonly") << QString::fromLatin1("readonlyfile") << uint(QFile::WriteUser) << false << false;
QTest::newRow("longfile") << QString::fromLatin1("longFileNamelongFileNamelongFileNamelongFileName"
@@ -1410,11 +1415,11 @@ void tst_QFile::permissions()
QFile::Permissions staticResult = QFile::permissions(file) & perms;
if (create) {
- QFile::remove(file);
+ QVERIFY(QFile::remove(file));
}
#if defined(Q_OS_WIN)
- if (qt_ntfs_permission_lookup)
+ if (qAreNtfsPermissionChecksEnabled())
QEXPECT_FAIL("readonly", "QTBUG-25630", Abort);
#endif
#ifdef Q_OS_UNIX
@@ -1436,27 +1441,61 @@ void tst_QFile::permissionsNtfs_data()
void tst_QFile::permissionsNtfs()
{
- QScopedValueRollback<int> ntfsMode(qt_ntfs_permission_lookup);
- qt_ntfs_permission_lookup++;
+ QNtfsPermissionCheckGuard permissionGuard;
permissions();
}
+
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+#if QT_DEPRECATED_SINCE(6,6)
+void tst_QFile::deprecatedNtfsPermissionCheck()
+{
+ QScopedValueRollback<int> guard(qt_ntfs_permission_lookup);
+
+ QCOMPARE(qAreNtfsPermissionChecksEnabled(), false);
+ qt_ntfs_permission_lookup++;
+ QCOMPARE(qAreNtfsPermissionChecksEnabled(), true);
+ qt_ntfs_permission_lookup--;
+ QCOMPARE(qAreNtfsPermissionChecksEnabled(), false);
+}
+#endif
+QT_WARNING_POP
+
#endif
+void tst_QFile::setPermissions_data()
+{
+ QTest::addColumn<bool>("opened");
+ QTest::newRow("closed") << false; // chmod()
+ QTest::newRow("opened") << true; // fchmod()
+}
+
void tst_QFile::setPermissions()
{
- if ( QFile::exists( "createme.txt" ) )
- QFile::remove( "createme.txt" );
+#ifdef Q_OS_UNIX
+ if (::getuid() == 0)
+ QSKIP("Running this test as root doesn't make sense");
+#endif
+ QFETCH(bool, opened);
+
+ auto remove = []() { QFile::remove("createme.txt"); };
+ auto guard = qScopeGuard(remove);
+ remove();
QVERIFY( !QFile::exists( "createme.txt" ) );
QFile f("createme.txt");
QVERIFY2(f.open(QIODevice::WriteOnly | QIODevice::Truncate), msgOpenFailed(f).constData());
f.putChar('a');
- f.close();
+ if (!opened)
+ f.close();
QFile::Permissions perms(QFile::WriteUser | QFile::ReadUser);
+ QVERIFY(f.setPermissions(QFile::ReadUser));
+ QVERIFY((f.permissions() & perms) == QFile::ReadUser);
QVERIFY(f.setPermissions(perms));
QVERIFY((f.permissions() & perms) == perms);
+ // we should end the test with the file in writeable state
}
void tst_QFile::copy()
@@ -1562,16 +1601,11 @@ void tst_QFile::copyFallback()
#if defined(Q_OS_WIN)
static QString getWorkingDirectoryForLink(const QString &linkFileName)
{
- bool neededCoInit = false;
QString ret;
+ QComHelper comHelper;
IShellLink *psl;
HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&psl);
- if (hres == CO_E_NOTINITIALIZED) { // COM was not initialized
- neededCoInit = true;
- CoInitialize(NULL);
- hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&psl);
- }
if (SUCCEEDED(hres)) { // Get pointer to the IPersistFile interface.
IPersistFile *ppf;
@@ -1590,10 +1624,6 @@ static QString getWorkingDirectoryForLink(const QString &linkFileName)
psl->Release();
}
- if (neededCoInit) {
- CoUninitialize();
- }
-
return ret;
}
#endif
@@ -1648,7 +1678,7 @@ void tst_QFile::absolutePathLinkToRelativePath()
QFile::remove("myDir/myLink.lnk");
QDir dir;
dir.mkdir("myDir");
- QFile("myDir/test.txt").open(QFile::WriteOnly);
+ QVERIFY(QFile("myDir/test.txt").open(QFile::WriteOnly));
#ifdef Q_OS_WIN
QVERIFY(QFile::link("test.txt", "myDir/myLink.lnk"));
@@ -1752,7 +1782,7 @@ void tst_QFile::writeTextFile()
QCOMPARE(file.write(in), qlonglong(in.size()));
file.close();
- file.open(QFile::ReadOnly);
+ QVERIFY(file.open(QFile::ReadOnly));
QCOMPARE(file.readAll(), out);
}
@@ -1792,7 +1822,7 @@ void tst_QFile::largeUncFileSupport()
qint64 size = Q_INT64_C(8589934592);
qint64 dataOffset = Q_INT64_C(8589914592);
QByteArray knownData("LargeFile content at offset 8589914592");
- QString largeFile("//" + QtNetworkSettings::winServerName() + "/testsharelargefile/file.bin");
+ QString largeFile("//" + QTest::uncServerName() + "/testsharelargefile/file.bin");
const QByteArray largeFileEncoded = QFile::encodeName(largeFile);
{
@@ -1889,26 +1919,64 @@ void tst_QFile::bufferedRead()
}
#ifdef Q_OS_UNIX
-void tst_QFile::isSequential()
+void tst_QFile::isSequential_data()
{
- QFile zero("/dev/zero");
- QVERIFY2(zero.open(QFile::ReadOnly), msgOpenFailed(zero).constData());
- QVERIFY(zero.isSequential());
+ QTest::addColumn<QString>("deviceName");
+ QTest::addColumn<bool>("acceptFailOpen");
- QFile null("/dev/null");
- QVERIFY(null.open(QFile::ReadOnly));
- QVERIFY(null.isSequential());
+ QTest::newRow("/dev/null") << QString("/dev/null") << false;
+ QTest::newRow("/dev/tty") << QString("/dev/tty") << true;
+ QTest::newRow("/dev/zero") << QString("/dev/zero") << false;
+}
+
+void tst_QFile::isSequential()
+{
+ QFETCH(QString, deviceName);
+ QFETCH(bool, acceptFailOpen);
- // /dev/tty will fail to open if we don't have a controlling TTY
- QFile tty("/dev/tty");
- if (tty.open(QFile::ReadOnly))
- QVERIFY(tty.isSequential());
+ if (access(deviceName.toUtf8().data(), R_OK) == 0) {
+ QFile device(deviceName);
+ QVERIFY2(device.open(QFile::ReadOnly) || acceptFailOpen, msgOpenFailed(device).constData());
+ QVERIFY(!device.isOpen() || device.isSequential());
+ }
}
#endif
+void tst_QFile::decodeName_data()
+{
+ QTest::addColumn<QByteArray>("bytearray");
+ QTest::addColumn<QString>("qstring");
+
+ QTest::newRow("null") << QByteArray() << QString();
+ QTest::newRow("simple") << "/path/to/file"_ba << u"/path/to/file"_s;
+
+#ifndef Q_OS_WIN
+# ifdef Q_OS_DARWIN
+ // Mac always expects filenames in UTF-8... and decomposed...
+ QTest::newRow("filé") << "/path/to/file\xCC\x81"_ba << u"/path/to/filé"_s;
+# else
+ QTest::newRow("filé") << "/path/to/fil\xC3\xA9"_ba << u"/path/to/filé"_s;
+# endif
+ QTest::newRow("fraction-slash")
+ << "/path\342\201\204to\342\201\204file"_ba << u"/path⁄to⁄file"_s;
+ QTest::newRow("fraction-slash-u16") << "/path\u2044to\u2044file"_ba << u"/path⁄to⁄file"_s;
+#endif // !Q_OS_WIN
+}
+
+void tst_QFile::decodeName()
+{
+ QFETCH(QByteArray, bytearray);
+ QFETCH(QString, qstring);
+
+ QCOMPARE(QFile::decodeName(bytearray), qstring);
+}
+
void tst_QFile::encodeName()
{
- QCOMPARE(QFile::encodeName(QString()), QByteArray());
+ QFETCH(QString, qstring);
+ QFETCH(QByteArray, bytearray);
+
+ QCOMPARE(QFile::encodeName(qstring), bytearray);
}
void tst_QFile::truncate()
@@ -2267,18 +2335,18 @@ private:
class MyHandler : public QAbstractFileEngineHandler
{
public:
- inline QAbstractFileEngine *create(const QString &) const override
+ std::unique_ptr<QAbstractFileEngine> create(const QString &) const override
{
- return new MyEngine(1);
+ return std::make_unique<MyEngine>(1);
}
};
class MyHandler2 : public QAbstractFileEngineHandler
{
public:
- inline QAbstractFileEngine *create(const QString &) const override
+ std::unique_ptr<QAbstractFileEngine> create(const QString &) const override
{
- return new MyEngine(2);
+ return std::make_unique<MyEngine>(2);
}
};
#endif
@@ -2307,7 +2375,7 @@ void tst_QFile::fileEngineHandler()
class MyRecursiveHandler : public QAbstractFileEngineHandler
{
public:
- inline QAbstractFileEngine *create(const QString &fileName) const override
+ std::unique_ptr<QAbstractFileEngine> create(const QString &fileName) const override
{
if (fileName.startsWith(":!")) {
QDir dir;
@@ -2318,9 +2386,9 @@ public:
const QString realFile = m_dataDir->filePath(fileName.mid(2));
#endif
if (dir.exists(realFile))
- return new QFSFileEngine(realFile);
+ return std::make_unique<QFSFileEngine>(realFile);
}
- return 0;
+ return nullptr;
}
#ifdef BUILTIN_TESTDATA
@@ -2345,7 +2413,7 @@ void tst_QFile::useQFileInAFileHandler()
void tst_QFile::getCharFF()
{
QFile file("file.txt");
- file.open(QFile::ReadWrite);
+ QVERIFY(file.open(QFile::ReadWrite));
file.write("\xff\xff\xff");
file.flush();
file.seek(0);
@@ -2448,7 +2516,7 @@ void tst_QFile::fullDisk()
QVERIFY(!file.isOpen());
QCOMPARE(file.error(), QFile::ResourceError);
- file.open(QIODevice::WriteOnly);
+ QVERIFY2(file.open(QIODevice::WriteOnly), msgOpenFailed(file).constData());
QCOMPARE(file.error(), QFile::NoError);
QVERIFY(file.flush()); // Shouldn't inherit write buffer
file.close();
@@ -2473,7 +2541,7 @@ void tst_QFile::writeLargeDataBlock_data()
#if defined(Q_OS_WIN) && !defined(QT_NO_NETWORK)
// Some semi-randomness to avoid collisions.
QTest::newRow("unc file")
- << QString("//" + QtNetworkSettings::winServerName() + "/TESTSHAREWRITABLE/largefile-%1-%2.txt")
+ << QString("//" + QTest::uncServerName() + "/TESTSHAREWRITABLE/largefile-%1-%2.txt")
.arg(QHostInfo::localHostName())
.arg(QTime::currentTime().msec()) << (int)OpenQFile;
#endif
@@ -2566,19 +2634,42 @@ void tst_QFile::writeToReadOnlyFile()
QCOMPARE(file.write(&c, 1), qint64(-1));
}
-#if defined(Q_OS_LINUX) || defined(Q_OS_AIX) || defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
+#if defined(Q_OS_LINUX)
// This platform have 0-sized virtual files
+void tst_QFile::virtualFile_data()
+{
+ QTest::addColumn<QIODevice::OpenMode>("mode");
+ QTest::newRow("buffered") << QIODevice::OpenMode();
+ QTest::newRow("unbuffered") << QIODevice::OpenMode(QIODevice::Unbuffered);
+}
+
void tst_QFile::virtualFile()
{
- // test if QFile works with virtual files
- QString fname;
-#if defined(Q_OS_LINUX)
- fname = "/proc/self/maps";
-#elif defined(Q_OS_AIX)
- fname = QString("/proc/%1/map").arg(getpid());
-#else // defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
- fname = "/proc/curproc/map";
-#endif
+ QFETCH(QIODevice::OpenMode, mode);
+
+ // We need to test a large-ish /proc file on Linux, one that is usually
+ // over 4 kB (because the kernel writes in chunks of that), has a
+ // cross-platform file format, and is definitely readable. The best
+ // candidate and the one we can verify anything in is /proc/<PID>/maps.
+ // However, our act of reading may change the map because we allocate
+ // memory, so we fork() here so we have a frozen snapshot of the file.
+
+ int efd = eventfd(0, EFD_CLOEXEC);
+ pid_t pid = fork();
+ if (pid == 0) {
+ // child
+ uint64_t val;
+ eventfd_read(efd, &val);
+ _exit(0);
+ }
+ QVERIFY2(pid > 0, "fork failed: " + qt_error_string().toLocal8Bit());
+ auto waitForChild = qScopeGuard([=] {
+ eventfd_write(efd, 1);
+ close(efd);
+ waitpid(pid, nullptr, 0);
+ });
+
+ QString fname = u"/proc/%1/maps"_s.arg(pid);
// consistency check
QFileInfo fi(fname);
@@ -2588,12 +2679,8 @@ void tst_QFile::virtualFile()
// open the file
QFile f(fname);
- QVERIFY2(f.open(QIODevice::ReadOnly), msgOpenFailed(f).constData());
- if (QTestPrivate::isRunningArmOnX86())
- QEXPECT_FAIL("","QEMU does not read /proc/self/maps size correctly", Continue);
+ QVERIFY2(f.open(QIODevice::ReadOnly | mode), msgOpenFailed(f).constData());
QCOMPARE(f.size(), Q_INT64_C(0));
- if (QTestPrivate::isRunningArmOnX86())
- QEXPECT_FAIL("","QEMU does not read /proc/self/maps size correctly", Continue);
QVERIFY(f.atEnd());
// read data
@@ -2601,22 +2688,59 @@ void tst_QFile::virtualFile()
QCOMPARE(data.size(), 16);
QCOMPARE(f.pos(), Q_INT64_C(16));
+ // seeking
+ QVERIFY(f.seek(1));
+ QCOMPARE(f.pos(), Q_INT64_C(1));
+ QVERIFY(f.seek(0));
+ QCOMPARE(f.pos(), Q_INT64_C(0));
+
// line-reading
- data = f.readLine();
- QVERIFY(!data.isEmpty());
+ QList<QByteArray> lines;
+ for (data = f.readLine(); !data.isEmpty(); data = f.readLine()) {
+ // chop the newline -- not using .trimmed() so cut exactly one byte
+ data.chop(1);
+ lines += std::move(data);
+ }
+
+ if (!QT_CONFIG(static) && !QTestPrivate::isRunningArmOnX86()) {
+ // we must be able to find QtCore and QtTest somewhere
+ static const char corelib[] = "libQt" QT_STRINGIFY(QT_VERSION_MAJOR) "Core";
+ static const char testlib[] = "libQt" QT_STRINGIFY(QT_VERSION_MAJOR) "Test";
+ auto contains = [&](QByteArrayView text, quintptr ptr = 0) {
+ // this is not the same a QList::contains()
+ return std::any_of(lines.constBegin(), lines.constEnd(), [=](QByteArrayView entry) {
+ if (!entry.contains(text))
+ return false;
+ if (!ptr)
+ return true;
+ qsizetype dash = entry.indexOf('-');
+ qsizetype space = entry.indexOf(' ', dash);
+ quintptr start = entry.left(dash).toULong(nullptr, 16);
+ quintptr end = entry.left(space).mid(dash + 1).toULong(nullptr, 16);
+ return start <= ptr && ptr <= end;
+ });
+ };
+ QVERIFY(contains(corelib, quintptr(f.metaObject())));
+ QVERIFY(contains(testlib));
+ }
// read all:
+ QVERIFY(f.seek(0));
data = f.readAll();
QVERIFY(f.pos() != 0);
QVERIFY(!data.isEmpty());
- // seeking
- QVERIFY(f.seek(1));
- QCOMPARE(f.pos(), Q_INT64_C(1));
+ QCOMPARE(data, lines.join('\n') + '\n');
}
#endif // defined(Q_OS_LINUX) || defined(Q_OS_AIX) || defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
-#ifdef Q_OS_UNIX
+#if defined (Q_OS_UNIX) && !defined(Q_OS_WASM)
+// wasm does not have working fifo
+// https://github.com/nodejs/node/issues/38344
+// wasm does not have blocking pipe I/O
+// https://github.com/emscripten-core/emscripten/issues/13214
+// wasm does not, by default, have socketpair
+// https://emscripten.org/docs/porting/networking.html
static void unixPipe_helper(int pipes[2])
{
// start a thread and wait for it to write a first byte
@@ -2628,6 +2752,7 @@ static void unixPipe_helper(int pipes[2])
c = 2;
qt_safe_write(fd, &c, 1);
}));
+
thr->start();
// synchronize with the thread having started
@@ -2636,12 +2761,11 @@ static void unixPipe_helper(int pipes[2])
QCOMPARE(c, '\1');
QFETCH(bool, useStdio);
- QElapsedTimer timer;
- timer.start();
QFile f;
if (useStdio) {
FILE *fh = fdopen(pipes[0], "rb");
- QVERIFY(f.open(fh, QIODevice::ReadOnly | QIODevice::Unbuffered));
+ QVERIFY(f.open(fh, QIODevice::ReadOnly | QIODevice::Unbuffered, QFileDevice::AutoCloseHandle));
+ pipes[0] = -1; // QFile fclose()s the FILE* and that close()s the fd
} else {
QVERIFY(f.open(pipes[0], QIODevice::ReadOnly | QIODevice::Unbuffered));
}
@@ -2650,8 +2774,6 @@ static void unixPipe_helper(int pipes[2])
c = 0;
QCOMPARE(f.read(&c, 1), 1);
QCOMPARE(c, '\2');
- int elapsed = timer.elapsed();
- QVERIFY2(elapsed >= Timeout, QByteArray::number(elapsed));
thr->wait();
}
@@ -2668,25 +2790,81 @@ void tst_QFile::unixPipe()
int pipes[2] = { -1, -1 };
QVERIFY2(pipe(pipes) == 0, qPrintable(qt_error_string()));
unixPipe_helper(pipes);
- qt_safe_close(pipes[0]);
+ if (pipes[0] != -1)
+ qt_safe_close(pipes[0]);
qt_safe_close(pipes[1]);
}
+void tst_QFile::unixFifo()
+{
+ QByteArray fifopath = []() -> QByteArray {
+ QByteArray dir = qgetenv("XDG_RUNTIME_DIR");
+ if (dir.isEmpty())
+ dir = QFile::encodeName(QDir::tempPath());
+
+ // try to create a FIFO
+ for (int attempts = 10; attempts; --attempts) {
+ QByteArray fifopath = dir + "/tst_qfile_fifo." +
+ QByteArray::number(QRandomGenerator::global()->generate());
+ int ret = mkfifo(fifopath, 0600);
+ if (ret == 0)
+ return fifopath;
+ }
+
+ qWarning("Failed to create a FIFO at %s; last error was %s",
+ dir.constData(), strerror(errno));
+ return {};
+ }();
+ if (fifopath.isEmpty())
+ return;
+
+ auto removeFifo = qScopeGuard([&fifopath] { unlink(fifopath); });
+
+ // with a FIFO, the two open() system calls synchronize
+ QScopedPointer<QThread> thr(QThread::create([&fifopath]() {
+ int fd = qt_safe_open(fifopath, O_WRONLY);
+ QTest::qSleep(500);
+ char c = 2;
+ qt_safe_write(fd, &c, 1);
+ qt_safe_close(fd);
+ }));
+ thr->start();
+
+ QFETCH(bool, useStdio);
+ QFile f;
+ if (useStdio) {
+ FILE *fh = fopen(fifopath, "rb");
+ QVERIFY(f.open(fh, QIODevice::ReadOnly | QIODevice::Unbuffered, QFileDevice::AutoCloseHandle));
+ } else {
+ f.setFileName(QFile::decodeName(fifopath));
+ QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Unbuffered));
+ }
+
+ char c = 0;
+ QCOMPARE(f.read(&c, 1), 1); // this ought to block
+ QCOMPARE(c, '\2');
+ thr->wait();
+}
+
void tst_QFile::socketPair()
{
+#if defined(Q_OS_VXWORKS)
+ QSKIP("socketpair is not available on Vxworks");
+#else
int pipes[2] = { -1, -1 };
QVERIFY2(socketpair(AF_UNIX, SOCK_STREAM, 0, pipes) == 0, qPrintable(qt_error_string()));
unixPipe_helper(pipes);
- qt_safe_close(pipes[0]);
+ if (pipes[0] != -1)
+ qt_safe_close(pipes[0]);
qt_safe_close(pipes[1]);
-}
#endif
+}
+#endif /* UNIX && !WASM; */
void tst_QFile::textFile()
{
- const char *openMode = QOperatingSystemVersion::current().type() != QOperatingSystemVersion::Windows
- ? "w" : "wt";
- StdioFileGuard fs(fopen("writeabletextfile", openMode));
+ // The "t" is ignored everywhere except on Windows
+ StdioFileGuard fs(fopen("writeabletextfile", "wt"));
QVERIFY(fs);
QFile f;
QByteArray part1("This\nis\na\nfile\nwith\nnewlines\n");
@@ -2898,7 +3076,7 @@ void tst_QFile::miscWithUncPathAsCurrentDir()
{
#if defined(Q_OS_WIN)
QString current = QDir::currentPath();
- const QString path = QLatin1String("//") + QtNetworkSettings::winServerName()
+ const QString path = QLatin1String("//") + QTest::uncServerName()
+ QLatin1String("/testshare");
QVERIFY2(QDir::setCurrent(path), qPrintable(QDir::toNativeSeparators(path)));
QFile file("test.pri");
@@ -2935,7 +3113,7 @@ void tst_QFile::handle()
// test if the QFile and the handle remain in sync
QVERIFY(file.getChar(&c));
- QCOMPARE(c, '*');
+ QCOMPARE(c, '/');
// same, but read from QFile first now
file.close();
@@ -2950,13 +3128,13 @@ void tst_QFile::handle()
QCOMPARE(QT_READ(fd, &c, 1), 1);
#endif
- QCOMPARE(c, '*');
+ QCOMPARE(c, '/');
//test round trip of adopted stdio file handle
QFile file2;
StdioFileGuard fp(fopen(qPrintable(m_testSourceFile), "r"));
QVERIFY(fp);
- file2.open(fp, QIODevice::ReadOnly);
+ QVERIFY(file2.open(fp, QIODevice::ReadOnly));
QCOMPARE(int(file2.handle()), int(QT_FILENO(fp)));
QCOMPARE(int(file2.handle()), int(QT_FILENO(fp)));
fp.close();
@@ -2965,7 +3143,7 @@ void tst_QFile::handle()
#ifdef Q_OS_UNIX
QFile file3;
fd = QT_OPEN(qPrintable(m_testSourceFile), QT_OPEN_RDONLY);
- file3.open(fd, QIODevice::ReadOnly);
+ QVERIFY(file3.open(fd, QIODevice::ReadOnly));
QCOMPARE(int(file3.handle()), fd);
QT_CLOSE(fd);
#endif
@@ -3277,13 +3455,15 @@ void tst_QFile::mapResource_data()
QString validFile = ":/tst_qfileinfo/resources/file1.ext1";
QString invalidFile = ":/tst_qfileinfo/resources/filefoo.ext1";
+ const char modes[] = "invalid";
for (int i = 0; i < 2; ++i) {
QString file = (i == 0) ? validFile : invalidFile;
- QTest::newRow("0, 0") << 0 << 0 << QFile::UnspecifiedError << file;
- QTest::newRow("0, BIG") << 0 << 4096 << QFile::UnspecifiedError << file;
- QTest::newRow("-1, 0") << -1 << 0 << QFile::UnspecifiedError << file;
- QTest::newRow("0, -1") << 0 << -1 << QFile::UnspecifiedError << file;
+ const char *mode = i == 0 ? modes + 2 : modes;
+ QTest::addRow("0, 0 (%s)", mode) << 0 << 0 << QFile::UnspecifiedError << file;
+ QTest::addRow("0, BIG (%s)", mode) << 0 << 4096 << QFile::UnspecifiedError << file;
+ QTest::addRow("-1, 0 (%s)", mode) << -1 << 0 << QFile::UnspecifiedError << file;
+ QTest::addRow("0, -1 (%s)", mode) << 0 << -1 << QFile::UnspecifiedError << file;
}
QTest::newRow("0, 1") << 0 << 1 << QFile::NoError << validFile;
@@ -3354,7 +3534,7 @@ void tst_QFile::mapOpenMode()
*memory = 'a';
file.unmap(memory);
file.close();
- file.open(QIODevice::OpenMode(openMode));
+ QVERIFY(file.open(QIODevice::OpenMode(openMode)));
file.seek(0);
char c;
QVERIFY(file.getChar(&c));
@@ -3501,7 +3681,7 @@ void tst_QFile::openStandardStreamsFileDescriptors()
{
QFile in;
- in.open(STDIN_FILENO, QIODevice::ReadOnly);
+ QVERIFY(in.open(STDIN_FILENO, QIODevice::ReadOnly));
QCOMPARE( in.pos(), streamCurrentPosition(STDIN_FILENO) );
QCOMPARE( in.size(), streamExpectedSize(STDIN_FILENO) );
}
@@ -3515,7 +3695,7 @@ void tst_QFile::openStandardStreamsFileDescriptors()
{
QFile err;
- err.open(STDERR_FILENO, QIODevice::WriteOnly);
+ QVERIFY(err.open(STDERR_FILENO, QIODevice::WriteOnly));
QCOMPARE( err.pos(), streamCurrentPosition(STDERR_FILENO) );
QCOMPARE( err.size(), streamExpectedSize(STDERR_FILENO) );
}
@@ -3531,21 +3711,21 @@ void tst_QFile::openStandardStreamsBufferedStreams()
// Using streams
{
QFile in;
- in.open(stdin, QIODevice::ReadOnly);
+ QVERIFY(in.open(stdin, QIODevice::ReadOnly));
QCOMPARE( in.pos(), streamCurrentPosition(stdin) );
QCOMPARE( in.size(), streamExpectedSize(QT_FILENO(stdin)) );
}
{
QFile out;
- out.open(stdout, QIODevice::WriteOnly);
+ QVERIFY(out.open(stdout, QIODevice::WriteOnly));
QCOMPARE( out.pos(), streamCurrentPosition(stdout) );
QCOMPARE( out.size(), streamExpectedSize(QT_FILENO(stdout)) );
}
{
QFile err;
- err.open(stderr, QIODevice::WriteOnly);
+ QVERIFY(err.open(stderr, QIODevice::WriteOnly));
QCOMPARE( err.pos(), streamCurrentPosition(stderr) );
QCOMPARE( err.size(), streamExpectedSize(QT_FILENO(stderr)) );
}
@@ -3599,7 +3779,7 @@ void tst_QFile::caseSensitivity()
{
#if defined(Q_OS_WIN)
const bool caseSensitive = false;
-#elif defined(Q_OS_MAC)
+#elif defined(Q_OS_DARWIN)
const bool caseSensitive = pathconf(QDir::currentPath().toLatin1().constData(), _PC_CASE_SENSITIVE);
#else
const bool caseSensitive = true;
@@ -3613,11 +3793,16 @@ void tst_QFile::caseSensitivity()
QVERIFY(f.write(testData));
f.close();
}
- QStringList alternates;
QFileInfo fi(filename);
QVERIFY(fi.exists());
- alternates << "file.txt" << "File.TXT" << "fIlE.TxT" << fi.absoluteFilePath().toUpper() << fi.absoluteFilePath().toLower();
- foreach (QString alt, alternates) {
+ const auto alternates = {
+ u"file.txt"_s,
+ u"File.TXT"_s,
+ u"fIlE.TxT"_s,
+ fi.absoluteFilePath().toUpper(),
+ fi.absoluteFilePath().toLower(),
+ };
+ for (const QString &alt : alternates) {
QFileInfo fi2(alt);
QCOMPARE(fi2.exists(), !caseSensitive);
QCOMPARE(fi.size() == fi2.size(), !caseSensitive);
@@ -3780,26 +3965,34 @@ void tst_QFile::moveToTrash_data()
// success cases
{
- QTemporaryFile temp;
+ QTemporaryFile temp(QDir::tempPath() + "/tst_qfile-moveToTrash-XXXXXX");
if (!temp.open())
QSKIP("Failed to create temporary file!");
QTest::newRow("temporary file") << temp.fileName() << true << true;
+#if defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
+ if (QDir::tempPath() == "/tmp")
+ QTest::newRow("var-temporary file") << "/var" + temp.fileName() << true << true;
+#endif
}
{
- QTemporaryDir tempDir;
+ QTemporaryDir tempDir(QDir::tempPath() + "/tst_qfile-moveToTrash-XXXXXX");
if (!tempDir.isValid())
QSKIP("Failed to create temporary directory!");
tempDir.setAutoRemove(false);
QTest::newRow("temporary dir")
<< tempDir.path() + QLatin1Char('/')
<< true << true;
+#if defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
+ if (QDir::tempPath() == "/tmp")
+ QTest::newRow("var-temporary dir") << "/var" + tempDir.path() << true << true;
+#endif
}
{
- QTemporaryDir homeDir(QDir::homePath() + QLatin1String("/XXXXXX"));
+ QTemporaryDir homeDir(QDir::homePath() + QLatin1String("/tst_qfile.moveToTrash-XXXXXX"));
if (!homeDir.isValid())
QSKIP("Failed to create temporary directory in $HOME!");
QTemporaryFile homeFile(homeDir.path()
- + QLatin1String("/tst_qfile-XXXXXX"));
+ + QLatin1String("/tst_qfile-moveToTrash-XXXXX"));
if (!homeFile.open())
QSKIP("Failed to create temporary file in $HOME");
homeDir.setAutoRemove(false);
@@ -3811,7 +4004,7 @@ void tst_QFile::moveToTrash_data()
<< homeDir.path() + QLatin1Char('/')
<< true << true;
}
- QTest::newRow("relative") << QStringLiteral("tst_qfile_moveToTrash.tmp") << true << true;
+ QTest::newRow("relative") << QStringLiteral("tst_qfile-moveToTrash.tmp") << true << true;
// failure cases
QTest::newRow("root") << QDir::rootPath() << false << false;
@@ -3820,8 +4013,8 @@ void tst_QFile::moveToTrash_data()
void tst_QFile::moveToTrash()
{
-#ifdef Q_OS_ANDROID
- QSKIP("Android doesn't implement a trash bin");
+#if defined(Q_OS_ANDROID) or defined(Q_OS_WEBOS) or defined(Q_OS_VXWORKS)
+ QSKIP("This platform doesn't implement a trash bin");
#endif
QFETCH(QString, source);
QFETCH(bool, create);
@@ -3853,6 +4046,7 @@ void tst_QFile::moveToTrash()
};
ensureFile(source, create);
+ if (!QFileInfo::exists(source) && create) return;
/* This test makes assumptions about the file system layout
which might be wrong - moveToTrash may fail if the file lives
@@ -3900,6 +4094,7 @@ void tst_QFile::moveToTrash()
// static version
{
ensureFile(source, create);
+ if (!QFileInfo::exists(source) && create) return;
QString pathInTrash;
const bool success = QFile::moveToTrash(source, &pathInTrash);
QCOMPARE(success, result);
@@ -3918,6 +4113,210 @@ void tst_QFile::moveToTrash()
}
}
+void tst_QFile::moveToTrashDuplicateName()
+{
+#if defined(Q_OS_ANDROID) || defined(Q_OS_WEBOS) || defined(Q_OS_VXWORKS)
+ QSKIP("This platform doesn't implement a trash bin");
+#endif
+ QString origFileName = []() {
+ QTemporaryFile temp(QDir::homePath() + "/tst_qfile.moveToTrashOpenFile.XXXXXX");
+ temp.setAutoRemove(false);
+ if (!temp.open())
+ qWarning("Failed to create temporary file: %ls", qUtf16Printable(temp.errorString()));
+ return temp.fileName();
+ }();
+
+ QFile f1(origFileName);
+ QFile f2(origFileName);
+ [&] {
+ QByteArrayView message1 = "Hello, World\n";
+ QVERIFY2(f1.open(QIODevice::ReadWrite | QIODevice::Unbuffered), qPrintable(f1.errorString()));
+ f1.write(message1.data(), message1.size());
+ QVERIFY2(f1.moveToTrash(), qPrintable(f1.errorString()));
+
+ QByteArrayView message2 = "Good morning, Vietnam!\n";
+ QVERIFY2(f2.open(QIODevice::ReadWrite | QIODevice::Unbuffered | QIODevice::NewOnly),
+ qPrintable(f2.errorString()));
+ f2.write(message2.data(), message2.size());
+ QVERIFY2(f2.moveToTrash(), qPrintable(f2.errorString()));
+
+ QCOMPARE_NE(f1.fileName(), f2.fileName());
+ }();
+ f1.remove();
+ if (!f2.fileName().isEmpty())
+ f2.remove();
+ QFile::remove(origFileName);
+}
+
+void tst_QFile::moveToTrashOpenFile_data()
+{
+ QTest::addColumn<bool>("useStatic");
+ QTest::addColumn<bool>("success");
+
+ // QFile::moveToTrash() non-static member closes the file before trashing,
+ // so this must always succeed.
+ QTest::newRow("member") << false << true;
+
+ // QFile::moveToTrash() static member cannot close the file because it
+ // operates on another QFile, so this operation will fail on OSes that do
+ // not permit deleting open files.
+ QTest::newRow("static") << true
+#ifdef Q_OS_WIN
+ << false;
+#else
+ << true;
+#endif
+}
+
+void tst_QFile::moveToTrashOpenFile()
+{
+#if defined(Q_OS_ANDROID) || defined(Q_OS_WEBOS) || defined(Q_OS_VXWORKS)
+ QSKIP("This platform doesn't implement a trash bin");
+#endif
+ QFETCH(bool, useStatic);
+ QFETCH(bool, success);
+ const QByteArrayView contents = "Hello, World\n";
+
+ QString newFileName, origFileName;
+ auto cleanup = qScopeGuard([&] {
+ if (!origFileName.isEmpty())
+ QFile::remove(origFileName);
+ if (!newFileName.isEmpty() && newFileName != origFileName)
+ QFile::remove(newFileName);
+ });
+
+ origFileName = []() {
+ QTemporaryFile temp(QDir::homePath() + "/tst_qfile.moveToTrashOpenFile.XXXXXX");
+ temp.setAutoRemove(false);
+ if (!temp.open())
+ qWarning("Failed to create temporary file: %ls", qUtf16Printable(temp.errorString()));
+ return temp.fileName();
+ }();
+
+ QFile f;
+ f.setFileName(origFileName);
+ QVERIFY2(f.open(QIODevice::ReadWrite | QIODevice::Unbuffered), qPrintable(f.errorString()));
+ f.write(contents.data(), contents.size());
+
+ QString errorString;
+ auto doMoveToTrash = [&](QFile *f) {
+ if (!f->moveToTrash())
+ errorString = f->errorString();
+ newFileName = f->fileName();
+ };
+ if (useStatic) {
+ // it's the same as the static QFile::moveToTrash(), but gives us
+ // the error string
+ QFile other(origFileName);
+ doMoveToTrash(&other);
+ } else {
+ doMoveToTrash(&f);
+ }
+ QCOMPARE_NE(f.fileName(), QString());
+
+ if (success) {
+ QCOMPARE(errorString, QString());
+ QCOMPARE_NE(newFileName, origFileName); // must have changed!
+ QVERIFY(!QFile::exists(origFileName));
+ QVERIFY(QFile::exists(newFileName));
+ QCOMPARE(QFileInfo(newFileName).size(), contents.size());
+ } else {
+ QCOMPARE_NE(errorString, QString());
+ QCOMPARE(newFileName, origFileName); // mustn't have changed!
+ QVERIFY(QFile::exists(origFileName));
+ QCOMPARE(QFileInfo(origFileName).size(), contents.size());
+ }
+}
+
+void tst_QFile::moveToTrashXdgSafety()
+{
+#if defined(Q_OS_VXWORKS)
+ QSKIP("This platform doesn't implement a trash bin");
+#endif
+#if defined(Q_OS_WIN) || defined(Q_OS_DARWIN) || defined(Q_OS_ANDROID) || defined(Q_OS_WEBOS)
+ QSKIP("This test is specific to XDG Unix systems");
+#else
+ QDir(m_temporaryDir.path()).mkdir("emptydir");
+
+ // See if we can find a writable volume to conduct our tests on
+ QString volumeRoot;
+ QStorageInfo homeVolume(QDir::homePath());
+ auto isVolumeSuitable = [this](const QString &rootPath) {
+ return QFile::link(m_temporaryDir.path() + "/emptydir", rootPath + "/.Trash");
+ };
+ for (const QStorageInfo &volume : QStorageInfo::mountedVolumes()) {
+ if (volume.isRoot())
+ continue;
+ if (volume == homeVolume)
+ continue;
+
+ if (isVolumeSuitable(volume.rootPath())) {
+ volumeRoot = volume.rootPath();
+ break;
+ }
+ }
+
+# ifdef Q_OS_LINUX
+ // fallback to /dev/shm, which is usually a tmpfs but is ignored by
+ // QStorageInfo as a virtual filesystem
+ if (volumeRoot.isEmpty() && isVolumeSuitable("/dev/shm"))
+ volumeRoot = "/dev/shm";
+# endif
+
+ if (volumeRoot.isEmpty())
+ QSKIP("Could not find any suitable volume to run this test with");
+
+ QDir genericTrashDir = volumeRoot + "/.Trash";
+ auto cleanup = qScopeGuard([&] {
+ if (QFileInfo(genericTrashDir.path()).isDir())
+ genericTrashDir.removeRecursively();
+ else
+ QFile::remove(genericTrashDir.path());
+ });
+
+ QString testFileName = volumeRoot + "/tst_qfile.moveToTrashSafety." + QString::number(getpid());
+ auto tryTrashing = [&] {
+ static int counter = 0;
+ QFile f(testFileName + u'.' + QString::number(counter++));
+ if (!f.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
+ qWarning("Failed to create temporary file: %ls", qUtf16Printable(f.errorString()));
+ return false;
+ }
+ bool ok = f.moveToTrash();
+ f.remove();
+ f.close();
+ return ok;
+ };
+
+ QTest::ignoreMessage(QtCriticalMsg,
+ "Warning: '" + QFile::encodeName(genericTrashDir.absolutePath())
+ + "' is a symlink to '" + QFile::encodeName(m_temporaryDir.path())
+ + "/emptydir'");
+ QVERIFY(tryTrashing());
+ QVERIFY(genericTrashDir.entryList(QDir::NoDotAndDotDot).isEmpty());
+
+ QFile::remove(genericTrashDir.path());
+ genericTrashDir.mkdir(genericTrashDir.path(), QFile::ExeOwner | QFile::ReadOwner);
+ QTest::ignoreMessage(QtCriticalMsg, "Warning: '" + QFile::encodeName(genericTrashDir.absolutePath())
+ + "' doesn't have sticky bit set!");
+ QVERIFY(tryTrashing());
+ QVERIFY(genericTrashDir.entryList(QDir::NoDotAndDotDot).isEmpty());
+
+ if (geteuid() != 0) {
+ // set the sticky bit, but make the dir unwritable; there'll be no
+ // warning and we should just fall back to the next option
+ chmod(QFile::encodeName(genericTrashDir.path()), 01555);
+ QVERIFY(tryTrashing());
+ QVERIFY(genericTrashDir.entryList(QDir::NoDotAndDotDot).isEmpty());
+
+ // ditto for our user's subdir now
+ chmod(QFile::encodeName(genericTrashDir.path()), 01755);
+ genericTrashDir.mkdir(QString::number(getuid()), QFile::ReadOwner);
+ QVERIFY(tryTrashing());
+ }
+#endif
+}
+
void tst_QFile::stdfilesystem()
{
#if QT_CONFIG(cxx17_filesystem)