diff --git a/tests/auto/corelib/io/qstorageinfo/tst_qstorageinfo.cpp b/tests/auto/corelib/io/qstorageinfo/tst_qstorageinfo.cpp index 98bd8edfd01..98d1a9a2ebe 100644 --- a/tests/auto/corelib/io/qstorageinfo/tst_qstorageinfo.cpp +++ b/tests/auto/corelib/io/qstorageinfo/tst_qstorageinfo.cpp @@ -10,6 +10,13 @@ #include +#ifdef Q_OS_WIN +# include // _get_osfhandle +# include +#else +# include +#endif + #include "../../../../manual/qstorageinfo/printvolumes.cpp" #ifdef Q_OS_LINUX @@ -28,8 +35,7 @@ private slots: void currentStorage(); void storageList_data(); void storageList(); - void tempFile(); - void caching(); + void freeSpaceUpdate(); #if defined(Q_OS_LINUX) && defined(QT_BUILD_INTERNAL) void testParseMountInfo_data(); @@ -207,80 +213,88 @@ void tst_QStorageInfo::storageList() QCOMPARE(other.isReady(), storage.isReady()); } -static bool checkFilesystemGoodForWriting(QTemporaryFile &file, QStorageInfo &storage) +static QString suitableDirectoryForWriting() { + std::initializer_list inadvisableFs = { #ifdef Q_OS_LINUX - auto reconstructAt = [](auto *where, auto &&... how) { - // it's very difficult to convince QTemporaryFile to change the path... - std::destroy_at(where); - q20::construct_at(where, std::forward(how)...); - }; - if (storage.fileSystemType() == "btrfs") { - // let's see if we can find another, writable FS - QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); - if (!runtimeDir.isEmpty()) { - reconstructAt(&file, runtimeDir + "/XXXXXX"); - if (file.open()) { - storage.setPath(file.fileName()); - if (storage.fileSystemType() != "btrfs") - return true; - } - } - QTest::qSkip("btrfs does not synchronously update free space; this test would fail", - __FILE__, __LINE__); - return false; - } -#elif defined(Q_OS_DARWIN) - Q_UNUSED(file); - if (storage.fileSystemType() == "apfs") { - QTest::qSkip("APFS does not synchronously update free space; this test would fail", - __FILE__, __LINE__); - return false; - } -#else - Q_UNUSED(file); - Q_UNUSED(storage); + // See comment below. If we can get a tmpfs, let's try it. + "btrfs", + "xfs", #endif - return true; + }; + + QString tempDir = QDir::tempPath(); + QString fsType = QStorageInfo(tempDir).fileSystemType(); + if (std::find(std::begin(inadvisableFs), std::end(inadvisableFs), fsType) + != std::end(inadvisableFs)) { + // the RuntimeLocation on Linux is almost always a tmpfs + QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); + if (!runtimeDir.isEmpty()) + return runtimeDir; + } + + return tempDir; } -void tst_QStorageInfo::tempFile() +void tst_QStorageInfo::freeSpaceUpdate() { - QTemporaryFile file; + // Some filesystems don't update the free space unless we ask that the OS + // flush its buffers to disk and even then the update may not be entirely + // synchronous. So we always ask the OS to flush them and we may keep + // trying to write until the free space changes (with a maximum so we don't + // exhaust and cause other problems). + // + // In the past, we had this issue with APFS (Apple systems), BTRFS and XFS + // (Linux). Current testing is that APFS and XFS always succeed after the + // first block is written and BTRFS almost always by the second block. + + auto flushAndSync = [](QFile &file) { + file.flush(); + +#ifdef Q_OS_WIN + FlushFileBuffers(HANDLE(_get_osfhandle(file.handle()))); +#elif _POSIX_VERSION >= 200112L + fsync(file.handle()); + sync(); +#endif + }; + + QTemporaryFile file(suitableDirectoryForWriting() + "/tst_qstorageinfo.XXXXXX"); QVERIFY2(file.open(), qPrintable(file.errorString())); QStorageInfo storage1(file.fileName()); - if (!checkFilesystemGoodForWriting(file, storage1)) - return; - - qint64 free = storage1.bytesFree(); - QCOMPARE_NE(free, -1); - - file.write(QByteArray(1024*1024, '1')); - file.flush(); - file.close(); - - QStorageInfo storage2(file.fileName()); - QCOMPARE_NE(free, storage2.bytesFree()); -} - -void tst_QStorageInfo::caching() -{ - QTemporaryFile file; - QVERIFY2(file.open(), qPrintable(file.errorString())); - - QStorageInfo storage1(file.fileName()); - if (!checkFilesystemGoodForWriting(file, storage1)) - return; + qInfo() << "Testing on" << storage1; qint64 free = storage1.bytesFree(); QStorageInfo storage2(storage1); QCOMPARE(free, storage2.bytesFree()); QCOMPARE_NE(free, -1); - file.write(QByteArray(1024*1024, '\0')); - file.flush(); + // let's see if we can make it change + QByteArray block(1024 * 1024 / 2, '\0'); + // let's try and keep to less than ~10% of the free disk space + int maxIterations = 25; + if (free < 256 * block.size()) + maxIterations = free / 10 / block.size(); + if (maxIterations == 0) + QSKIP("Not enough free disk space to continue"); + + file.write(block); + flushAndSync(file); + for (int i = 0; i < maxIterations; ++i) { + QStorageInfo storage3(file.fileName()); + qint64 nowFree = storage3.bytesFree(); + if (nowFree != free) + break; + + // grow some more + file.write(block); + flushAndSync(file); + } + // qDebug() << "Needed to grow" << file.fileName() << "to" << file.size(); + + QCOMPARE(storage1, storage2); QCOMPARE(free, storage1.bytesFree()); QCOMPARE(free, storage2.bytesFree()); storage2.refresh();