diff options
Diffstat (limited to 'tests/auto/corelib/io/qfile/tst_qfile.cpp')
-rw-r--r-- | tests/auto/corelib/io/qfile/tst_qfile.cpp | 611 |
1 files changed, 511 insertions, 100 deletions
diff --git a/tests/auto/corelib/io/qfile/tst_qfile.cpp b/tests/auto/corelib/io/qfile/tst_qfile.cpp index 625de41795..d69cc167d6 100644 --- a/tests/auto/corelib/io/qfile/tst_qfile.cpp +++ b/tests/auto/corelib/io/qfile/tst_qfile.cpp @@ -1,6 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // Copyright (C) 2021 Intel Corporation. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #define _CRT_SECURE_NO_WARNINGS 1 @@ -13,11 +13,13 @@ #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> @@ -48,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> @@ -171,6 +175,9 @@ 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(); @@ -195,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(); @@ -218,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 @@ -275,6 +289,10 @@ private slots: void moveToTrash_data(); void moveToTrash(); + void moveToTrashDuplicateName(); + void moveToTrashOpenFile_data(); + void moveToTrashOpenFile(); + void moveToTrashXdgSafety(); void stdfilesystem(); @@ -402,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(); @@ -420,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) @@ -625,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"); @@ -1097,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]; @@ -1112,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; @@ -1199,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 @@ -1213,7 +1239,6 @@ void tst_QFile::invalidFile_data() QTest::newRow( "pipe" ) << QString( "fail|invalid" ); #endif } - void tst_QFile::invalidFile() { QFETCH( QString, fileName ); @@ -1267,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); }); @@ -1357,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" @@ -1388,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 @@ -1414,10 +1441,26 @@ 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() @@ -1429,8 +1472,9 @@ void tst_QFile::setPermissions_data() void tst_QFile::setPermissions() { -#ifdef Q_OS_QNX - QSKIP("This test doesn't pass on QNX and no one has cared to investigate."); +#ifdef Q_OS_UNIX + if (::getuid() == 0) + QSKIP("Running this test as root doesn't make sense"); #endif QFETCH(bool, opened); @@ -1634,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")); @@ -1738,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); } @@ -1875,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"); + + QTest::newRow("/dev/null") << QString("/dev/null") << false; + QTest::newRow("/dev/tty") << QString("/dev/tty") << true; + QTest::newRow("/dev/zero") << QString("/dev/zero") << false; +} - QFile null("/dev/null"); - QVERIFY(null.open(QFile::ReadOnly)); - QVERIFY(null.isSequential()); +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() @@ -2252,19 +2334,24 @@ private: class MyHandler : public QAbstractFileEngineHandler { + Q_DISABLE_COPY_MOVE(MyHandler) public: - inline QAbstractFileEngine *create(const QString &) const override + MyHandler() = default; + std::unique_ptr<QAbstractFileEngine> create(const QString &) const override { - return new MyEngine(1); + return std::make_unique<MyEngine>(1); } }; class MyHandler2 : public QAbstractFileEngineHandler { + Q_DISABLE_COPY_MOVE(MyHandler2) public: - inline QAbstractFileEngine *create(const QString &) const override + MyHandler2() = default; + + std::unique_ptr<QAbstractFileEngine> create(const QString &) const override { - return new MyEngine(2); + return std::make_unique<MyEngine>(2); } }; #endif @@ -2292,8 +2379,11 @@ void tst_QFile::fileEngineHandler() #ifdef QT_BUILD_INTERNAL class MyRecursiveHandler : public QAbstractFileEngineHandler { + Q_DISABLE_COPY_MOVE(MyRecursiveHandler) public: - inline QAbstractFileEngine *create(const QString &fileName) const override + MyRecursiveHandler() = default; + + std::unique_ptr<QAbstractFileEngine> create(const QString &fileName) const override { if (fileName.startsWith(":!")) { QDir dir; @@ -2304,9 +2394,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 @@ -2331,7 +2421,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); @@ -2434,7 +2524,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(); @@ -2552,19 +2642,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); @@ -2574,12 +2687,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 @@ -2587,22 +2696,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 @@ -2614,6 +2760,7 @@ static void unixPipe_helper(int pipes[2]) c = 2; qt_safe_write(fd, &c, 1); })); + thr->start(); // synchronize with the thread having started @@ -2622,12 +2769,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)); } @@ -2636,8 +2782,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(); } @@ -2651,36 +2795,84 @@ void tst_QFile::unixPipe_data() void tst_QFile::unixPipe() { -#ifdef Q_OS_ANDROID - if (QNativeInterface::QAndroidApplication::sdkVersion() >= 31) - QSKIP("Crashes on Android 12 (QTBUG-105736)"); -#endif 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() { -#ifdef Q_OS_ANDROID - if (QNativeInterface::QAndroidApplication::sdkVersion() >= 31) - QSKIP("Crashes on Android 12 (QTBUG-105736)"); -#endif +#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"); @@ -2950,7 +3142,7 @@ void tst_QFile::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(); @@ -2959,7 +3151,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 @@ -3350,7 +3542,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)); @@ -3497,7 +3689,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) ); } @@ -3511,7 +3703,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) ); } @@ -3527,21 +3719,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)) ); } @@ -3595,7 +3787,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; @@ -3609,11 +3801,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); @@ -3776,26 +3973,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); @@ -3807,7 +4012,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; @@ -3816,7 +4021,7 @@ void tst_QFile::moveToTrash_data() void tst_QFile::moveToTrash() { -#if defined(Q_OS_ANDROID) or defined(Q_OS_WEBOS) +#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); @@ -3849,6 +4054,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 @@ -3896,6 +4102,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); @@ -3914,6 +4121,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) |