Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
#include <QtTest/private/qcomparisontesthelper_p.h>
#include <QStandardPaths>
#include <QScopeGuard>
#include <qfile.h>
#include <qdir.h>
#include <qcoreapplication.h>
#include <qtemporaryfile.h>
#include <qtemporarydir.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qstorageinfo.h>
#ifdef Q_OS_UNIX
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifndef Q_OS_VXWORKS
#include <pwd.h>
#endif
#endif
#ifdef Q_OS_HARMONY
#include <time.h>
#endif
#ifdef Q_OS_WIN
#include <qt_windows.h>
#include <private/qwinregistry_p.h>
#include <lm.h>
#endif
#include <qplatformdefs.h>
#include <qdebug.h>
#include <private/qfileinfo_p.h>
#include "../../../../shared/filesystem.h"
#if defined(Q_OS_MACOS)
#include <Foundation/Foundation.h>
#endif
#if QT_CONFIG(process)
#include <QProcess>
#endif
#if defined(Q_OS_VXWORKS) || defined(Q_OS_HARMONY)
#define Q_NO_SYMLINKS
#endif
#if defined(Q_OS_WIN)
bool IsUserAdmin();
#endif
using namespace Qt::StringLiterals;
inline bool qIsLikelyToBeFat(const QString &path)
{
QByteArray name = QStorageInfo(path).fileSystemType().toLower();
return name.contains("fat") || name.contains("msdos");
}
inline bool qIsLikelyToBeNfs(const QString &path)
{
#ifdef Q_OS_WIN
Q_UNUSED(path);
return false;
#else
QByteArray type = QStorageInfo(path).fileSystemType();
const char *name = type.constData();
return (qstrncmp(name, "nfs", 3) == 0
|| qstrncmp(name, "autofs", 6) == 0
|| qstrncmp(name, "autofsng", 8) == 0
|| qstrncmp(name, "cachefs", 7) == 0);
#endif
}
#if defined(Q_OS_WIN)
static QByteArray msgInsufficientPrivileges(const QString &errorMessage)
{
return "Insufficient privileges (" + errorMessage.toLocal8Bit() + ')';
}
#endif // Q_OS_WIN
static QString seedAndTemplate()
{
QString base;
#if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID)
// use XDG_RUNTIME_DIR as it's a fully-capable FS
base = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
#endif
if (base.isEmpty())
base = QDir::tempPath();
return base + "/tst_qfileinfo-XXXXXX";
}
static QByteArray msgDoesNotExist(const QString &name)
{
return (QLatin1Char('"') + QDir::toNativeSeparators(name)
+ QLatin1String("\" does not exist.")).toLocal8Bit();
}
static QByteArray msgIsNoDirectory(const QString &name)
{
return (QLatin1Char('"') + QDir::toNativeSeparators(name)
+ QLatin1String("\" is not a directory.")).toLocal8Bit();
}
static QByteArray msgIsNotRoot(const QString &name)
{
return (QLatin1Char('"') + QDir::toNativeSeparators(name)
+ QLatin1String("\" is no root directory.")).toLocal8Bit();
}
class tst_QFileInfo : public QObject
{
Q_OBJECT
public:
tst_QFileInfo() : m_currentDir(QDir::currentPath()), m_dir(seedAndTemplate())
{}
private slots:
void initTestCase();
void cleanupTestCase();
void getSetCheck();
void copy();
void isFile_data();
void isFile();
void isDir_data();
void isDir();
void isOther_data();
void isOther();
void isRoot_data();
void isRoot();
void exists_data();
void exists();
void deletedFileLinuxProcExists();
void absolutePath_data();
void absolutePath();
void absFilePath_data();
void absFilePath();
void canonicalPath();
void canonicalFilePath_data();
void canonicalFilePath();
#ifdef Q_OS_WIN
void canonicalPathWindowsSymlinks();
#endif
void canonicalFilePathInUnreadableDir();
void fileName_data();
void fileName();
void bundleName_data();
void bundleName();
void dir_data();
void dir();
void suffix_data();
void suffix();
void completeSuffix_data();
void completeSuffix();
void baseName_data();
void baseName();
void completeBaseName_data();
void completeBaseName();
void permission_data();
void permission();
void size_data();
void size();
void systemFiles();
void compareCompiles();
void compare_data();
void compare();
void consistent_data();
void consistent();
void fileTimes_data();
void fileTimes();
void setFileTimes();
void fakeFileTimes_data();
void fakeFileTimes();
void isSymLink_data();
void isSymLink();
void isSymbolicLink_data();
void isSymbolicLink();
void isShortcut_data();
void isShortcut();
void isAlias_data();
void isAlias();
void link_data();
void link();
void isHidden_data();
void isHidden();
void isHiddenFromFinder_data();
void isHiddenFromFinder();
void isBundle_data();
void isBundle();
void isNativePath_data();
void isNativePath();
void refresh();
#if defined(Q_OS_WIN)
void ntfsJunctionPointsAndSymlinks_data();
void ntfsJunctionPointsAndSymlinks();
void brokenShortcut();
#endif
void isWritable();
void isExecutable();
void testDecomposedUnicodeNames_data();
void testDecomposedUnicodeNames();
void equalOperator() const;
void equalOperatorWithDifferentSlashes() const;
void notEqualOperator() const;
void detachingOperations();
void owner();
void group();
void invalidState_data();
void invalidState();
void nonExistingFile();
void stdfilesystem();
void readSymLink();
#if defined(Q_OS_DARWIN)
void fileSystemCaseSensitivity_data();
void fileSystemCaseSensitivity();
#endif
private:
const QString m_currentDir;
static inline QString m_sourceFile;
QString m_proFile;
QString m_resourcesDir;
QTemporaryDir m_dir;
QSharedPointer<QTemporaryDir> m_dataDir;
QTemporaryDir m_tempSubDir;
bool uncServerAvailable = false;
};
void tst_QFileInfo::initTestCase()
{
m_dataDir = QEXTRACTTESTDATA("/testdata");
QVERIFY(m_dataDir);
const QString dataPath = m_dataDir->path();
QVERIFY(!dataPath.isEmpty());
m_sourceFile = dataPath + QLatin1String("/tst_qfileinfo.cpp");
m_resourcesDir = dataPath + QLatin1String("/resources");
m_proFile = dataPath + QLatin1String("/tst_qfileinfo.pro");
QVERIFY2(m_dir.isValid(),
("Failed to create temporary dir: " + m_dir.errorString()).toUtf8());
QVERIFY(QDir::setCurrent(m_dir.path()));
#ifdef Q_OS_WIN
// "When used with directories, _access determines only whether the specified directory exists"
if (_waccess(qUtf16Printable("//" + QTest::uncServerName() + "/testshare"), 0) == 0)
uncServerAvailable = true;
#endif
}
void tst_QFileInfo::cleanupTestCase()
{
QDir::setCurrent(m_currentDir); // Release temporary directory so that it can be deleted on Windows
}
// Testing get/set functions
void tst_QFileInfo::getSetCheck()
{
QFileInfo obj1;
// bool QFileInfo::caching()
// void QFileInfo::setCaching(bool)
obj1.setCaching(false);
QCOMPARE(false, obj1.caching());
obj1.setCaching(true);
QCOMPARE(true, obj1.caching());
}
static QFileInfoPrivate* getPrivate(QFileInfo &info)
{
return (*reinterpret_cast<QFileInfoPrivate**>(&info));
}
void tst_QFileInfo::copy()
{
QTemporaryFile t;
QVERIFY2(t.open(), qPrintable(t.errorString()));
QFileInfo info(t.fileName());
QVERIFY(info.exists());
//copy constructor
QFileInfo info2(info);
QFileInfoPrivate *privateInfo = getPrivate(info);
QFileInfoPrivate *privateInfo2 = getPrivate(info2);
QCOMPARE(privateInfo, privateInfo2);
//operator =
QFileInfo info3 = info;
QFileInfoPrivate *privateInfo3 = getPrivate(info3);
QCOMPARE(privateInfo, privateInfo3);
QCOMPARE(privateInfo2, privateInfo3);
//refreshing info3 will detach it
QFile file(info.absoluteFilePath());
QVERIFY(file.open(QFile::WriteOnly));
QCOMPARE(file.write("JAJAJAA"), qint64(7));
file.flush();
QTest::qWait(250);
#if defined(Q_OS_WIN)
file.close();
#endif
info3.refresh();
privateInfo3 = getPrivate(info3);
QVERIFY(privateInfo != privateInfo3);
QVERIFY(privateInfo2 != privateInfo3);
QCOMPARE(privateInfo, privateInfo2);
}
void tst_QFileInfo::isFile_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("expected");
QTest::newRow("data0") << QDir::currentPath() << false;
QTest::newRow("data1") << m_sourceFile << true;
QTest::newRow("data2") << ":/tst_qfileinfo/resources/" << false;
QTest::newRow("data3") << ":/tst_qfileinfo/resources/file1" << true;
QTest::newRow("data4") << ":/tst_qfileinfo/resources/afilethatshouldnotexist" << false;
}
void tst_QFileInfo::isFile()
{
QFETCH(QString, path);
QFETCH(bool, expected);
QFileInfo fi(path);
QCOMPARE(fi.isFile(), expected);
}
void tst_QFileInfo::isDir_data()
{
// create a broken symlink
QFile::remove("brokenlink.lnk");
QFile::remove("dummyfile");
QFile file3("dummyfile");
QVERIFY(file3.open(QIODevice::WriteOnly));
if (file3.link("brokenlink.lnk")) {
file3.remove();
QFileInfo info3("brokenlink.lnk");
QVERIFY( info3.isSymLink() );
}
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("expected");
QTest::newRow("data0") << QDir::currentPath() << true;
QTest::newRow("data1") << m_sourceFile << false;
QTest::newRow("data2") << ":/tst_qfileinfo/resources/" << true;
QTest::newRow("data3") << ":/tst_qfileinfo/resources/file1" << false;
QTest::newRow("data4") << ":/tst_qfileinfo/resources/afilethatshouldnotexist" << false;
QTest::newRow("simple dir") << m_resourcesDir << true;
QTest::newRow("simple dir with slash") << (m_resourcesDir + QLatin1Char('/')) << true;
QTest::newRow("broken link") << "brokenlink.lnk" << false;
#if defined(Q_OS_WIN)
QTest::newRow("drive 1") << "c:" << true;
QTest::newRow("drive 2") << "c:/" << true;
//QTest::newRow("drive 2") << "t:s" << false;
#endif
#if defined(Q_OS_WIN)
const QString uncRoot = QStringLiteral("//") + QTest::uncServerName();
QTest::newRow("unc 1") << uncRoot << true;
QTest::newRow("unc 2") << uncRoot + QLatin1Char('/') << true;
QTest::newRow("unc 3") << uncRoot + "/testshare" << uncServerAvailable;
QTest::newRow("unc 4") << uncRoot + "/testshare/" << uncServerAvailable;
QTest::newRow("unc 5") << uncRoot + "/testshare/tmp" << uncServerAvailable;
QTest::newRow("unc 6") << uncRoot + "/testshare/tmp/" << uncServerAvailable;
QTest::newRow("unc 7") << uncRoot + "/testshare/adirthatshouldnotexist" << false;
#endif
}
void tst_QFileInfo::isDir()
{
QFETCH(QString, path);
QFETCH(bool, expected);
const bool isDir = QFileInfo(path).isDir();
if (expected)
QVERIFY2(isDir, msgIsNoDirectory(path).constData());
else
QVERIFY(!isDir);
}
void tst_QFileInfo::isOther_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("expected");
m_tempSubDir.remove();
m_tempSubDir = QTemporaryDir(m_dir.path() + "/isother_test_dir.XXXXXX"_L1);
const QString prefix = m_tempSubDir.path() + u'/';
const QString filePath = prefix + "file"_L1;
QFile file(filePath);
if (file.open(QFile::WriteOnly)) {
file.write("JAJAJAA");
file.close();
}
const QString dirPath = m_dir.path();
#ifndef Q_NO_SYMLINKS
const QString linkToFile = prefix + "link-to-file"_L1;
file.link(linkToFile);
const QString linkToDir = prefix + "link-to-dir"_L1;
QFile::link(dirPath, linkToDir);
const QString brokenLink = prefix + "broken-symlink"_L1;
QFile dummy(prefix + "dummyfile"_L1);
(void)dummy.open(QIODevice::WriteOnly);
dummy.link(brokenLink);
dummy.remove();
#endif
QTest::newRow("regular-file") << filePath << false;
#ifndef Q_NO_SYMLINKS
QTest::newRow("symlink-to-regular-file") << linkToFile << false;
#endif
QTest::newRow("dir") << dirPath << false;
#ifndef Q_NO_SYMLINKS
QTest::newRow("symlink-to-dir") << linkToDir << false;
QTest::newRow("broken-symlink") << brokenLink << false;
#endif
QTest::newRow("qresources-file") << ":/tst_qfileinfo/resources/file1" << false;
QTest::newRow("qresources-broken-dir") << ":/I/do_not_expect_this_path_to_exist/" << false;
QTest::newRow("qresources-broken-file") << ":/tst_qfileinfo/resources/ghost-file" << false;
#ifdef Q_OS_UNIX
auto addSpecialRow = [&prefix](const QString &s, uint type) {
const QString name = prefix + s;
if (::mknod(name.toLatin1().constData(), 0777 | type, dev_t{}) == 0)
QTest::addRow("%s", qPrintable(name)) << name << true;
else
qDebug("mknod call failed: %s", strerror(errno));
};
addSpecialRow("fifo-test", S_IFIFO);
addSpecialRow("socket-test", S_IFSOCK);
QTest::newRow("character-device") << u"/dev/null"_s << true;
#ifndef Q_NO_SYMLINKS
const QString linkToSpecial = prefix + "link-to-dev-null"_L1;
QFile::link(u"/dev/null"_s, linkToSpecial);
QTest::newRow("symlink-to-special") << linkToSpecial << true;
#endif
#elif defined(Q_OS_WIN)
const QString name = prefix + "win-CreateSymbolicLink-file"_L1;
const auto res = FileSystem::createSymbolicLink(name.toLatin1().constData(),
file.fileName().toLatin1().constData());
if (res.dwErr == ERROR_SUCCESS)
QTest::newRow("win-CreateSymbolicLink-file") << name << false;
else if (res.dwErr == ERROR_PRIVILEGE_NOT_HELD)
qDebug() << msgInsufficientPrivileges(res.errorMessage);
else
qDebug() << res.errorMessage;
const QString winLnk = prefix + "winLnk.lnk"_L1;
QFile lnkTarget(prefix + "lnkTarget"_L1);
(void)lnkTarget.open(QIODevice::WriteOnly);
lnkTarget.link(winLnk);
QTest::newRow("winLnk") << winLnk << true;
const QString brokenWinLnk = prefix + "broken_winLnk.lnk"_L1;
const QString dummyPath = prefix + "lnkDummy"_L1;
lnkTarget.copy(dummyPath);
QFile lnkDummy(dummyPath);
lnkDummy.link(brokenWinLnk);
lnkDummy.remove();
QTest::newRow("broken-winLnk") << brokenWinLnk << true;
#endif // Q_OS_UNIX
}
void tst_QFileInfo::isOther()
{
QFETCH(QString, path);
QFETCH(bool, expected);
QFileInfo info(path);
QCOMPARE(info.isOther(), expected);
if (!QByteArrayView{QTest::currentDataTag()}.contains("broken"))
QVERIFY(info.exists());
#if QT_CONFIG(cxx17_filesystem) // This code doesn't work in QNX on the CI
if (QByteArrayView{QTest::currentDataTag()}.contains("winLnk"))
QEXPECT_FAIL("", "QFileInfo::isOther() returns true for '.lnk' files on Windows, "
"std::filesystem::is_other() returns false", Continue);
QCOMPARE_EQ(info.isOther(), std::filesystem::is_other(path.toStdString()));
#endif
}
void tst_QFileInfo::isRoot_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("expected");
QTest::newRow("data0") << QDir::currentPath() << false;
QTest::newRow("data1") << "/" << true;
QTest::newRow("data2") << "*" << false;
QTest::newRow("data3") << "/*" << false;
QTest::newRow("data4") << ":/tst_qfileinfo/resources/" << false;
QTest::newRow("data5") << ":/" << true;
QTest::newRow("simple dir") << m_resourcesDir << false;
QTest::newRow("simple dir with slash") << (m_resourcesDir + QLatin1Char('/')) << false;
#if defined(Q_OS_WIN)
QTest::newRow("drive 1") << "c:" << false;
QTest::newRow("drive 2") << "c:/" << true;
QTest::newRow("drive 3") << "p:/" << false;
#endif
#if defined(Q_OS_WIN)
const QString uncRoot = QStringLiteral("//") + QTest::uncServerName();
QTest::newRow("unc 1") << uncRoot << true;
QTest::newRow("unc 2") << uncRoot + QLatin1Char('/') << true;
QTest::newRow("unc 3") << uncRoot + "/testshare" << false;
QTest::newRow("unc 4") << uncRoot + "/testshare/" << false;
QTest::newRow("unc 7") << "//ahostthatshouldnotexist" << false;
#endif
}
void tst_QFileInfo::isRoot()
{
QFETCH(QString, path);
QFETCH(bool, expected);
const bool isRoot = QFileInfo(path).isRoot();
if (expected)
QVERIFY2(isRoot, msgIsNotRoot(path).constData());
else
QVERIFY(!isRoot);
}
void tst_QFileInfo::exists_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("expected");
QTest::newRow("data0") << QDir::currentPath() << true;
QTest::newRow("data1") << m_sourceFile << true;
QTest::newRow("data2") << "/I/do_not_expect_this_path_to_exist/" << false;
QTest::newRow("data3") << ":/tst_qfileinfo/resources/" << true;
QTest::newRow("data4") << ":/tst_qfileinfo/resources/file1" << true;
QTest::newRow("data5") << ":/I/do_not_expect_this_path_to_exist/" << false;
QTest::newRow("data6") << (m_resourcesDir + "/*") << false;
QTest::newRow("data7") << (m_resourcesDir + "/*.foo") << false;
QTest::newRow("data8") << (m_resourcesDir + "/*.ext1") << false;
QTest::newRow("data9") << (m_resourcesDir + "/file?.ext1") << false;
QTest::newRow("data10") << "." << true;
QTest::newRow("data11") << ". " << false;
QTest::newRow("empty") << "" << false;
QTest::newRow("simple dir") << m_resourcesDir << true;
QTest::newRow("simple dir with slash") << (m_resourcesDir + QLatin1Char('/')) << true;
#if defined(Q_OS_WIN)
const QString uncRoot = QStringLiteral("//") + QTest::uncServerName();
QTest::newRow("unc 1") << uncRoot << true;
QTest::newRow("unc 2") << uncRoot + QLatin1Char('/') << true;
QTest::newRow("unc 3") << uncRoot + "/testshare" << uncServerAvailable;
QTest::newRow("unc 4") << uncRoot + "/testshare/" << uncServerAvailable;
QTest::newRow("unc 5") << uncRoot + "/testshare/tmp" << uncServerAvailable;
QTest::newRow("unc 6") << uncRoot + "/testshare/tmp/" << uncServerAvailable;
QTest::newRow("unc 7") << uncRoot + "/testshare/adirthatshouldnotexist" << false;
QTest::newRow("unc 8") << uncRoot + "/asharethatshouldnotexist" << false;
QTest::newRow("unc 9") << "//ahostthatshouldnotexist" << false;
#endif
}
void tst_QFileInfo::exists()
{
QFETCH(QString, path);
QFETCH(bool, expected);
QFileInfo fi(path);
const bool exists = fi.exists();
QCOMPARE(exists, QFileInfo::exists(path));
if (expected)
QVERIFY2(exists, msgDoesNotExist(path).constData());
else
QVERIFY(!exists);
}
void tst_QFileInfo::deletedFileLinuxProcExists()
{
#ifdef Q_OS_LINUX
static const char msg[] = "Hello, World\n";
QFileInfo fi("/proc/self/fd/");
if (!fi.isDir())
QSKIP("/proc appears not to be mounted");
QFile f("removed_file.txt");
QVERIFY(f.open(QIODevice::ReadWrite | QIODevice::Unbuffered));
f.write(msg, strlen(msg));
fi.setFile(fi.filePath() + QString::number(f.handle()));
QVERIFY(fi.exists());
QCOMPARE(fi.size(), strlen(msg));
QFile::remove("removed_file.txt");
fi.refresh();
QVERIFY(fi.exists());
fi.refresh();
QCOMPARE(fi.size(), strlen(msg)); // this stats, so may change flags
QVERIFY(fi.exists());
#else
QSKIP("Linux-only test");
#endif
}
void tst_QFileInfo::absolutePath_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("path");
QTest::addColumn<QString>("filename");
QString drivePrefix;
#if defined(Q_OS_WIN)
drivePrefix = QDir::currentPath().left(2);
QString nonCurrentDrivePrefix =
drivePrefix.left(1).compare("X", Qt::CaseInsensitive) == 0 ? QString("Y:") : QString("X:");
// Make sure drive-relative paths return correct absolute paths.
QTest::newRow("<current drive>:my.dll") << drivePrefix + "my.dll" << QDir::currentPath() << "my.dll";
QTest::newRow("<not current drive>:my.dll") << nonCurrentDrivePrefix + "my.dll"
<< nonCurrentDrivePrefix + "/"
<< "my.dll";
#endif
QTest::newRow("0") << "/machine/share/dir1/" << drivePrefix + "/machine/share/dir1" << "";
QTest::newRow("1") << "/machine/share/dir1" << drivePrefix + "/machine/share" << "dir1";
QTest::newRow("2") << "/usr/local/bin" << drivePrefix + "/usr/local" << "bin";
QTest::newRow("3") << "/usr/local/bin/" << drivePrefix + "/usr/local/bin" << "";
QTest::newRow("/test") << "/test" << drivePrefix + "/" << "test";
#if defined(Q_OS_WIN)
QTest::newRow("c:\\autoexec.bat") << "c:\\autoexec.bat" << "C:/"
<< "autoexec.bat";
QTest::newRow("c:autoexec.bat") << QDir::currentPath().left(2) + "autoexec.bat" << QDir::currentPath()
<< "autoexec.bat";
#endif
QTest::newRow("QTBUG-19995.1") << drivePrefix + "/System/Library/StartupItems/../Frameworks"
<< drivePrefix + "/System/Library"
<< "Frameworks";
QTest::newRow("QTBUG-19995.2") << drivePrefix + "/System/Library/StartupItems/../Frameworks/"
<< drivePrefix + "/System/Library/Frameworks" << "";
}
void tst_QFileInfo::absolutePath()
{
QFETCH(QString, file);
QFETCH(QString, path);
QFETCH(QString, filename);
QFileInfo fi(file);
QCOMPARE(fi.absolutePath(), path);
QCOMPARE(fi.fileName(), filename);
}
void tst_QFileInfo::absFilePath_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("expected");
QTest::newRow("relativeFile") << "tmp.txt" << QDir::currentPath() + "/tmp.txt";
QTest::newRow("relativeFileInSubDir") << "temp/tmp.txt" << QDir::currentPath() + "/" + "temp/tmp.txt";
QString drivePrefix;
#if defined(Q_OS_WIN)
QString curr = QDir::currentPath();
curr.remove(0, 2); // Make it a absolute path with no drive specifier: \depot\qt-4.2\tests\auto\qfileinfo
QTest::newRow(".") << curr << QDir::currentPath();
QTest::newRow("absFilePath") << "c:\\home\\andy\\tmp.txt" << "C:/home/andy/tmp.txt";
// Make sure drive-relative paths return correct absolute paths.
drivePrefix = QDir::currentPath().left(2);
QString nonCurrentDrivePrefix =
drivePrefix.left(1).compare("X", Qt::CaseInsensitive) == 0 ? QString("Y:") : QString("X:");
QTest::newRow("absFilePathWithoutSlash") << drivePrefix + "tmp.txt" << QDir::currentPath() + "/tmp.txt";
QTest::newRow("<current drive>:my.dll") << drivePrefix + "temp/my.dll" << QDir::currentPath() + "/temp/my.dll";
QTest::newRow("<not current drive>:my.dll") << nonCurrentDrivePrefix + "temp/my.dll"
<< nonCurrentDrivePrefix + "/temp/my.dll";
#else
QTest::newRow("absFilePath") << "/home/andy/tmp.txt" << "/home/andy/tmp.txt";
#endif
QTest::newRow("QTBUG-19995") << drivePrefix + "/System/Library/StartupItems/../Frameworks"
<< drivePrefix + "/System/Library/Frameworks";
}
void tst_QFileInfo::absFilePath()
{
QFETCH(QString, file);
QFETCH(QString, expected);
QFileInfo fi(file);
#if defined(Q_OS_WIN)
QVERIFY(QString::compare(fi.absoluteFilePath(), expected, Qt::CaseInsensitive) == 0);
#else
QCOMPARE(fi.absoluteFilePath(), expected);
#endif
}
void tst_QFileInfo::canonicalPath()
{
QTemporaryFile tempFile;
tempFile.setAutoRemove(true);
QVERIFY2(tempFile.open(), qPrintable(tempFile.errorString()));
QFileInfo fi(tempFile.fileName());
QCOMPARE(fi.canonicalPath(), QFileInfo(QDir::tempPath()).canonicalFilePath());
}
void tst_QFileInfo::canonicalFilePath_data()
{
// like touch(1)
static auto touch = [](const QString &fileName, QByteArrayView contents = {}) {
QFile f(fileName);
QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString()));
if (!contents.isEmpty())
f.write(contents.data(), contents.size());
};
QTest::addColumn<QString>("fileName");
QTest::addColumn<QString>("expected");
QTest::addColumn<void (*)()>("setupFunction");
constexpr void (*noSetupFunction)() = nullptr;
QTest::newRow("empty") << QString() << QString() << noSetupFunction;
// ASSUMPTION: we are running in a canonical path.
// These two tests will verify that the test environment is correct.
QTest::newRow("current-dir") << QDir::currentPath() << QDir::currentPath() << noSetupFunction;
QTest::newRow(".") << "." << "@" << noSetupFunction;
// non-existent or failing paths
#if defined(Q_OS_UNIX)
// If this file exists, you can't log in to run this test ...
QTest::newRow("system-doesnt-exist") << "/etc/nologin" << QString() << noSetupFunction;
// A path with a non-directory as a directory component also doesn't exist:
QTest::newRow("system-bad-path") << "/dev/null/sub/dir/n'existe.pas" << QString() << noSetupFunction;
#else
QTest::newRow("system-doesnt-exist") << "c:/this-does-not-exist.txt" << QString() << noSetupFunction;
QTest::newRow("system-bad-path") << qEnvironmentVariable("COMSPEC") + "/sub/dir/n'existe.pas"
<< QString() << noSetupFunction;
#endif
QTest::newRow("user-doesnt-exist")
<< QDir::currentPath() + "/this-does-not-exist.txt" << QString() << noSetupFunction;
QTest::newRow("user-bad-path") << "subdir/file.txt" << QString() << +[] { touch("subdir"); };
#ifndef Q_OS_WIN
// This used to crash on macOS
QTest::addRow("root") << "/" << "/" << noSetupFunction;
// This used to crash on Mac, verify that it doesn't anymore.
if (QString tmpdir = QDir::tempPath(); !tmpdir.isEmpty())
QTest::newRow("past-root-dir") << tmpdir + "/../../../../../../../../../../../../../../../../../"
<< "/" << noSetupFunction;
#else
for (const QFileInfo &drive : QDir::drives()) {
QTest::addRow("drive-%c", char(drive.path().at(0).unicode()))
<< drive.filePath() << drive.filePath() << noSetupFunction;
}
if (QString tmpdir = QDir::tempPath(); !tmpdir.isEmpty())
QTest::newRow("past-root-dir") << tmpdir + "/../../../../../../../../../../../../../../../../../"
<< tmpdir.left(3) << noSetupFunction;
#endif
// simple file
QTest::newRow("simple-file") << "tmp.canon" << "@/tmp.canon" << +[] { touch("tmp.canon"); };
// un-openable file (though root can open it)
QTest::newRow("unopenable-file") << "unopenable.txt" << "@/unopenable.txt" << +[] {
QFile f("unopenable.txt");
QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString()));
f.setPermissions({});
};
#ifndef Q_NO_SYMLINKS
// symlink to files
static auto simpleFileSymlink = +[] {
QFile f("file.txt");
QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString()));
QVERIFY2(f.link("link.lnk"), qPrintable(f.errorString()));
};
QTest::newRow("symlink-file") << "link.lnk" << "@/file.txt" << simpleFileSymlink;
QTest::newRow("symlink-source-file") << "link.lnk" << QFileInfo(m_sourceFile).canonicalFilePath() << +[] {
QFile(m_sourceFile).link("link.lnk");
};
QTest::newRow("broken-symlink") << "file.lnk" << QString() << +[] {
simpleFileSymlink();
QFile::remove("file.txt");
};
#endif
#ifdef Q_OS_UNIX
#ifndef Q_NO_SYMLINKS
// QFile::link() creates absolute links
QTest::newRow("symlink-file-relative") << "link.lnk" << "@/file.txt" << +[] {
touch("file.txt");
symlink("file.txt", "link.lnk");
};
// symlink to dir
QTest::newRow("symlink-cur-dir") << "dir.lnk" << "@" << +[] {
symlink(".", "dir.lnk");
};
// path containing symlinked directory
QTest::newRow("path-contains-symlinks1") << "dir.lnk/file.txt" << "@/file.txt" << +[] {
touch("file.txt");
symlink(".", "dir.lnk");
};
QTest::newRow("path-contains-symlinks2") << "dir.lnk/file.txt" << "@/subdir/file.txt" << +[] {
mkdir("subdir", 0777);
touch("subdir/file.txt");
symlink("subdir", "dir.lnk");
};
// path containing symlinked directory to symlinked file
QTest::newRow("symlinked-path-symlinked-file1") << "dir.lnk/file.lnk" << QFileInfo(m_sourceFile).canonicalFilePath() << +[] {
symlink(m_sourceFile.toLocal8Bit(), "file.lnk");
symlink(".", "dir.lnk");
};
QTest::newRow("symlinked-path-symlinked-file2") << "dir.lnk/file.lnk" << QFileInfo(m_sourceFile).canonicalFilePath() << +[] {
mkdir("subdir", 0777);
symlink(m_sourceFile.toLocal8Bit(), "subdir/file.lnk");
symlink("subdir", "dir.lnk");
};
QTest::newRow("symlink-loop") << "peter" << QString() << +[] {
symlink("peter", "paul");
symlink("paul", "peter");
};
#endif
// On Darwin, this may change Unicode normalization forms
static const QString composed = u"caf\u00e9.txt"_s;
static const QString decomposed = u"cafe\u0301.txt"_s;
QTest::newRow("nfc-path") << composed << "@/" + QFile::decodeName(QFile::encodeName(composed))
<< +[] { touch(composed); };
QTest::newRow("nfd-path") << decomposed << "@/" + QFile::decodeName(QFile::encodeName(decomposed))
<< +[] { touch(decomposed); };
#endif
}
void tst_QFileInfo::canonicalFilePath()
{
using SetupFunction = void (*)();
struct SetupCleanup {
QTemporaryDir dir{ QDir::current().filePath(u"canonicalFilePath_"_s + QTest::currentDataTag() + "-XXXXXX") };
QString oldCurrentPath = QDir::currentPath();
SetupCleanup(SetupFunction setupFunction)
{
QVERIFY(dir.isValid());
QDir::setCurrent(dir.path());
if (setupFunction)
setupFunction();
}
~SetupCleanup()
{
QDir::setCurrent(oldCurrentPath);
}
};
QFETCH(QString, fileName);
QFETCH(QString, expected);
QFETCH(SetupFunction, setupFunction);
SetupCleanup sc(setupFunction);
if (QTest::currentTestFailed())
return;
if (expected.startsWith(u'@'))
expected.replace(0, 1, QDir::currentPath());
QFileInfo fi(fileName);
QCOMPARE(fi.canonicalFilePath(), expected);
}
#if defined(Q_OS_WIN)
void tst_QFileInfo::canonicalPathWindowsSymlinks()
{
const QString linkTarget = QStringLiteral("res");
const auto result = FileSystem::createSymbolicLink(linkTarget, m_resourcesDir);
if (result.dwErr == ERROR_PRIVILEGE_NOT_HELD)
QSKIP(msgInsufficientPrivileges(result.errorMessage));
QVERIFY2(result.dwErr == ERROR_SUCCESS, qPrintable(result.errorMessage));
QString currentPath = QDir::currentPath();
QVERIFY(QDir::setCurrent(linkTarget));
const QString actualCanonicalPath = QFileInfo("file1").canonicalFilePath();
QVERIFY(QDir::setCurrent(currentPath));
QCOMPARE(actualCanonicalPath, m_resourcesDir + QStringLiteral("/file1"));
QDir::current().rmdir(linkTarget);
}
#endif
void tst_QFileInfo::canonicalFilePathInUnreadableDir()
{
// Unreadable dir with a file inside (usually; root can trasverse it).
// Done as a separate test because QTemporaryDir can't remove this, so we
// want to have the explicit cleanup code.
// note: qt_error_string() may be non-sensical!
QVERIFY2(QDir().mkdir("unreadable-dir"), qPrintable(qt_error_string()));
auto cleanupUnreadableDir = qScopeGuard([] {
auto mode = QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner;
QFile::remove("unreadable-dir-file.txt");
QFile::setPermissions("unreadable-dir", mode);
QFile::remove("unreadable-dir/file.txt");
QDir().rmdir("unreadable-dir");
});
QFile f("unreadable-dir/file.txt");
QVERIFY(f.open(QIODevice::WriteOnly));
f.write("Readable file in unreadable dir");
f.link("unreadable-dir-file.txt");
f.close();
QFile::setPermissions("unreadable-dir", QFileDevice::Permissions{});
bool unreadableDirIsUnreadable = true;
#ifdef Q_OS_WIN
// _access() can only tell whether dirs exist, not readability/transversability
unreadableDirIsUnreadable = QDir("unreadable-dir").entryList().isEmpty();
#else
unreadableDirIsUnreadable = (access("unreadable-dir", X_OK) != 0);
#endif
if (!unreadableDirIsUnreadable)
QSKIP("Unreadable dir was readable - running as root?");
// We CANNOT resolve the canonical path
QFileInfo unreadableDirFile("unreadable-dir/file.txt");
QCOMPARE(unreadableDirFile.canonicalFilePath(), QString());
unreadableDirFile.setFile("unreadable-dir-file.txt");
QCOMPARE(unreadableDirFile.canonicalFilePath(), QString());
}
void tst_QFileInfo::fileName_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("expected");
QTest::newRow("relativeFile") << "tmp.txt" << "tmp.txt";
QTest::newRow("relativeFileInSubDir") << "temp/tmp.txt" << "tmp.txt";
#if defined(Q_OS_WIN)
QTest::newRow("absFilePath") << "c:\\home\\andy\\tmp.txt" << "tmp.txt";
QTest::newRow("driveWithNoSlash") << "c:tmp.txt" << "tmp.txt";
#else
QTest::newRow("absFilePath") << "/home/andy/tmp.txt" << "tmp.txt";
#endif
QTest::newRow("resource1") << ":/tst_qfileinfo/resources/file1.ext1" << "file1.ext1";
QTest::newRow("resource2") << ":/tst_qfileinfo/resources/file1.ext1.ext2" << "file1.ext1.ext2";
QTest::newRow("ending slash [small]") << QString::fromLatin1("/a/") << QString::fromLatin1("");
QTest::newRow("no ending slash [small]") << QString::fromLatin1("/a") << QString::fromLatin1("a");
QTest::newRow("ending slash") << QString::fromLatin1("/somedir/") << QString::fromLatin1("");
QTest::newRow("no ending slash") << QString::fromLatin1("/somedir") << QString::fromLatin1("somedir");
}
void tst_QFileInfo::fileName()
{
QFETCH(QString, file);
QFETCH(QString, expected);
QFileInfo fi(file);
QCOMPARE(fi.fileName(), expected);
}
void tst_QFileInfo::bundleName_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("expected");
QTest::newRow("root") << "/" << "";
QTest::newRow("etc") << "/etc" << "";
#ifdef Q_OS_DARWIN
QTest::newRow("safari") << "/Applications/Safari.app" << "Safari";
#endif
}
void tst_QFileInfo::bundleName()
{
QFETCH(QString, file);
QFETCH(QString, expected);
QFileInfo fi(file);
QCOMPARE(fi.bundleName(), expected);
}
void tst_QFileInfo::dir_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<bool>("absPath");
QTest::addColumn<QString>("expected");
QTest::newRow("relativeFile") << "tmp.txt" << false << ".";
QTest::newRow("relativeFileAbsPath") << "tmp.txt" << true << QDir::currentPath();
QTest::newRow("relativeFileInSubDir") << "temp/tmp.txt" << false << "temp";
QTest::newRow("relativeFileInSubDirAbsPath") << "temp/tmp.txt" << true << QDir::currentPath() + "/temp";
QTest::newRow("absFilePath") << QDir::currentPath() + "/tmp.txt" << false << QDir::currentPath();
QTest::newRow("absFilePathAbsPath") << QDir::currentPath() + "/tmp.txt" << true << QDir::currentPath();
QTest::newRow("resource1") << ":/tst_qfileinfo/resources/file1.ext1" << true << ":/tst_qfileinfo/resources";
#if defined(Q_OS_WIN)
QTest::newRow("driveWithSlash") << "C:/file1.ext1.ext2" << true << "C:/";
QTest::newRow("driveWithoutSlash") << QDir::currentPath().left(2) + "file1.ext1.ext2" << false << QDir::currentPath().left(2);
#endif
}
void tst_QFileInfo::dir()
{
QFETCH(QString, file);
QFETCH(bool, absPath);
QFETCH(QString, expected);
QFileInfo fi(file);
if (absPath) {
QCOMPARE(fi.absolutePath(), expected);
QCOMPARE(fi.absoluteDir().path(), expected);
} else {
QCOMPARE(fi.path(), expected);
QCOMPARE(fi.dir().path(), expected);
}
}
void tst_QFileInfo::suffix_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("expected");
QTest::newRow("noextension0") << "file" << "";
QTest::newRow("noextension1") << "/path/to/file" << "";
QTest::newRow("data0") << "file.tar" << "tar";
QTest::newRow("data1") << "file.tar.gz" << "gz";
QTest::newRow("data2") << "/path/file/file.tar.gz" << "gz";
QTest::newRow("data3") << "/path/file.tar" << "tar";
QTest::newRow("resource1") << ":/tst_qfileinfo/resources/file1.ext1" << "ext1";
QTest::newRow("resource2") << ":/tst_qfileinfo/resources/file1.ext1.ext2" << "ext2";
QTest::newRow("hidden1") << ".ext1" << "ext1";
QTest::newRow("hidden1") << ".ext" << "ext";
QTest::newRow("hidden1") << ".ex" << "ex";
QTest::newRow("hidden1") << ".e" << "e";
QTest::newRow("hidden2") << ".ext1.ext2" << "ext2";
QTest::newRow("hidden2") << ".ext.ext2" << "ext2";
QTest::newRow("hidden2") << ".ex.ext2" << "ext2";
QTest::newRow("hidden2") << ".e.ext2" << "ext2";
QTest::newRow("hidden2") << "..ext2" << "ext2";
#ifdef Q_OS_WIN
QTest::newRow("driveWithSlash") << "c:/file1.ext1.ext2" << "ext2";
QTest::newRow("driveWithoutSlash") << "c:file1.ext1.ext2" << "ext2";
#endif
}
void tst_QFileInfo::suffix()
{
QFETCH(QString, file);
QFETCH(QString, expected);
QFileInfo fi(file);
QCOMPARE(fi.suffix(), expected);
}
void tst_QFileInfo::completeSuffix_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("expected");
QTest::newRow("noextension0") << "file" << "";
QTest::newRow("noextension1") << "/path/to/file" << "";
QTest::newRow("data0") << "file.tar" << "tar";
QTest::newRow("data1") << "file.tar.gz" << "tar.gz";
QTest::newRow("data2") << "/path/file/file.tar.gz" << "tar.gz";
QTest::newRow("data3") << "/path/file.tar" << "tar";
QTest::newRow("resource1") << ":/tst_qfileinfo/resources/file1.ext1" << "ext1";
QTest::newRow("resource2") << ":/tst_qfileinfo/resources/file1.ext1.ext2" << "ext1.ext2";
#ifdef Q_OS_WIN
QTest::newRow("driveWithSlash") << "c:/file1.ext1.ext2" << "ext1.ext2";
QTest::newRow("driveWithoutSlash") << "c:file1.ext1.ext2" << "ext1.ext2";
#endif
}
void tst_QFileInfo::completeSuffix()
{
QFETCH(QString, file);
QFETCH(QString, expected);
QFileInfo fi(file);
QCOMPARE(fi.completeSuffix(), expected);
}
void tst_QFileInfo::baseName_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("expected");
QTest::newRow("data0") << "file.tar" << "file";
QTest::newRow("data1") << "file.tar.gz" << "file";
QTest::newRow("data2") << "/path/file/file.tar.gz" << "file";
QTest::newRow("data3") << "/path/file.tar" << "file";
QTest::newRow("data4") << "/path/file" << "file";
QTest::newRow("resource1") << ":/tst_qfileinfo/resources/file1.ext1" << "file1";
QTest::newRow("resource2") << ":/tst_qfileinfo/resources/file1.ext1.ext2" << "file1";
#ifdef Q_OS_WIN
QTest::newRow("driveWithSlash") << "c:/file1.ext1.ext2" << "file1";
QTest::newRow("driveWithoutSlash") << "c:file1.ext1.ext2" << "file1";
#endif
}
void tst_QFileInfo::baseName()
{
QFETCH(QString, file);
QFETCH(QString, expected);
QFileInfo fi(file);
QCOMPARE(fi.baseName(), expected);
}
void tst_QFileInfo::completeBaseName_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("expected");
QTest::newRow("data0") << "file.tar" << "file";
QTest::newRow("data1") << "file.tar.gz" << "file.tar";
QTest::newRow("data2") << "/path/file/file.tar.gz" << "file.tar";
QTest::newRow("data3") << "/path/file.tar" << "file";
QTest::newRow("data4") << "/path/file" << "file";
QTest::newRow("resource1") << ":/tst_qfileinfo/resources/file1.ext1" << "file1";
QTest::newRow("resource2") << ":/tst_qfileinfo/resources/file1.ext1.ext2" << "file1.ext1";
#ifdef Q_OS_WIN
QTest::newRow("driveWithSlash") << "c:/file1.ext1.ext2" << "file1.ext1";
QTest::newRow("driveWithoutSlash") << "c:file1.ext1.ext2" << "file1.ext1";
#endif
}
void tst_QFileInfo::completeBaseName()
{
QFETCH(QString, file);
QFETCH(QString, expected);
QFileInfo fi(file);
QCOMPARE(fi.completeBaseName(), expected);
}
void tst_QFileInfo::permission_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<int>("perms");
QTest::addColumn<bool>("expected");
#ifndef Q_OS_HARMONY
QTest::newRow("data0") << QCoreApplication::instance()->applicationFilePath() << int(QFile::ExeUser) << true;
#endif
QTest::newRow("data1") << m_sourceFile << int(QFile::ReadUser) << true;
QTest::newRow("resource1") << ":/tst_qfileinfo/resources/file1.ext1" << int(QFile::ReadUser) << true;
QTest::newRow("resource2") << ":/tst_qfileinfo/resources/file1.ext1" << int(QFile::WriteUser) << false;
QTest::newRow("resource3") << ":/tst_qfileinfo/resources/file1.ext1" << int(QFile::ExeUser) << false;
}
void tst_QFileInfo::permission()
{
QFETCH(QString, file);
QFETCH(int, perms);
QFETCH(bool, expected);
QFileInfo fi(file);
QCOMPARE(fi.permission(QFile::Permissions(perms)), expected);
}
void tst_QFileInfo::size_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<int>("size");
QTest::newRow("resource1") << ":/tst_qfileinfo/resources/file1.ext1" << 0;
QFile::remove("file1");
QFile file("file1");
QVERIFY(file.open(QFile::WriteOnly));
QCOMPARE(file.write("JAJAJAA"), qint64(7));
QTest::newRow("created-file") << "file1" << 7;
QTest::newRow("resource2") << ":/tst_qfileinfo/resources/file1.ext1.ext2" << 0;
}
void tst_QFileInfo::size()
{
QFETCH(QString, file);
QFileInfo fi(file);
(void)fi.permissions();
QTEST(int(fi.size()), "size");
}
void tst_QFileInfo::systemFiles()
{
#if !defined(Q_OS_WIN)
QSKIP("This is a Windows only test");
#endif
QFileInfo fi("c:\\pagefile.sys");
QVERIFY2(fi.exists(), msgDoesNotExist(fi.absoluteFilePath()).constData());
QVERIFY(fi.size() > 0);
QVERIFY(fi.lastModified().isValid());
QVERIFY(fi.metadataChangeTime().isValid());
QCOMPARE(fi.metadataChangeTime(), fi.lastModified()); // On Windows, they're the same
QVERIFY(fi.birthTime().isValid());
QVERIFY(fi.birthTime() <= fi.lastModified());
}
void tst_QFileInfo::compareCompiles()
{
QTestPrivate::testEqualityOperatorsCompile<QFileInfo>();
}
void tst_QFileInfo::compare_data()
{
QTest::addColumn<QString>("file1");
QTest::addColumn<QString>("file2");
QTest::addColumn<bool>("same");
QString caseChangedSource = m_sourceFile;
caseChangedSource.replace("info", "Info");
QTest::newRow("data0")
<< m_sourceFile
<< m_sourceFile
<< true;
QTest::newRow("data1")
<< m_sourceFile
<< QString::fromLatin1("/tst_qfileinfo.cpp")
<< false;
QTest::newRow("data2")
<< QString::fromLatin1("tst_qfileinfo.cpp")
<< QDir::currentPath() + QString::fromLatin1("/tst_qfileinfo.cpp")
<< true;
QTest::newRow("casesense1")
<< caseChangedSource
<< m_sourceFile
#if defined(Q_OS_WIN)
<< true;
#elif defined(Q_OS_DARWIN)
<< (pathconf(QDir::currentPath().toLatin1().constData(), _PC_CASE_SENSITIVE) != 1);
#else
<< false;
#endif
}
void tst_QFileInfo::compare()
{
#if defined(Q_OS_DARWIN)
if (qstrcmp(QTest::currentDataTag(), "casesense1") == 0)
QSKIP("Qt thinks all UNIX filesystems are case sensitive, see QTBUG-28246");
#endif
QFETCH(QString, file1);
QFETCH(QString, file2);
QFETCH(bool, same);
QFileInfo fi1(file1), fi2(file2);
QT_TEST_EQUALITY_OPS(fi1, fi2, same);
}
void tst_QFileInfo::consistent_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("expected");
#if defined(Q_OS_WIN)
QTest::newRow("slashes") << QString::fromLatin1("\\a\\a\\a\\a") << QString::fromLatin1("/a/a/a/a");
#endif
QTest::newRow("ending slash") << QString::fromLatin1("/a/somedir/") << QString::fromLatin1("/a/somedir/");
QTest::newRow("no ending slash") << QString::fromLatin1("/a/somedir") << QString::fromLatin1("/a/somedir");
}
void tst_QFileInfo::consistent()
{
QFETCH(QString, file);
QFETCH(QString, expected);
QFileInfo fi(file);
QCOMPARE(fi.filePath(), expected);
QCOMPARE(fi.dir().path() + QLatin1Char('/') + fi.fileName(), expected);
}
void tst_QFileInfo::fileTimes_data()
{
QTest::addColumn<QString>("fileName");
QTest::newRow("simple") << QString::fromLatin1("simplefile.txt");
QTest::newRow( "longfile" ) << QString::fromLatin1("longFileNamelongFileNamelongFileNamelongFileName"
"longFileNamelongFileNamelongFileNamelongFileName"
"longFileNamelongFileNamelongFileNamelongFileName"
"longFileNamelongFileNamelongFileNamelongFileName"
"longFileNamelongFileNamelongFileNamelongFileName.txt");
QTest::newRow( "longfile absolutepath" ) << QFileInfo(QString::fromLatin1("longFileNamelongFileNamelongFileNamelongFileName"
"longFileNamelongFileNamelongFileNamelongFileName"
"longFileNamelongFileNamelongFileNamelongFileName"
"longFileNamelongFileNamelongFileNamelongFileName"
"longFileNamelongFileNamelongFileNamelongFileName.txt")).absoluteFilePath();
}
void tst_QFileInfo::fileTimes()
{
auto datePairString = [](const QDateTime &actual, const QDateTime &before) {
return (actual.toString(Qt::ISODateWithMs) + " (should be >) " + before.toString(Qt::ISODateWithMs))
.toLatin1();
};
// HarmonyOS (using the HongMeng kernel) uses the coarse clock
// to assign file timestamps.
auto fsClockNow = [] {
#ifdef Q_OS_HARMONY
timespec ts;
clock_gettime(CLOCK_REALTIME_COARSE, &ts);
return QDateTime::fromSecsSinceEpoch(ts.tv_sec, QTimeZone::UTC)
.addMSecs(ts.tv_nsec / 1000000);
#else
return QDateTime::currentDateTimeUtc();
#endif
};
QFETCH(QString, fileName);
int sleepTime = 100;
// on Linux and Windows, the filesystem timestamps may be slightly out of
// sync with the system clock (maybe they're using CLOCK_REALTIME_COARSE),
// so add a margin of error to our comparisons
int fsClockSkew = 10;
#ifdef Q_OS_WIN
fsClockSkew = 500;
#endif
// NFS clocks may be WAY out of sync
if (qIsLikelyToBeNfs(fileName))
QSKIP("This test doesn't work on NFS");
bool noAccessTime = false;
{
// try to guess if file times on this filesystem round to the second
QFileInfo cwd(".");
if (cwd.lastModified(QTimeZone::UTC).toMSecsSinceEpoch() % 1000 == 0
&& cwd.lastRead(QTimeZone::UTC).toMSecsSinceEpoch() % 1000 == 0) {
fsClockSkew = sleepTime = 1000;
noAccessTime = qIsLikelyToBeFat(fileName);
if (noAccessTime) {
// FAT filesystems (but maybe not exFAT) store timestamps with 2-second
// granularity and access time with 1-day granularity
fsClockSkew = sleepTime = 2000;
}
}
}
if (QFile::exists(fileName)) {
QVERIFY(QFile::remove(fileName));
}
QDateTime beforeBirth, beforeWrite, beforeMetadataChange, beforeRead;
QDateTime birthTime, writeTime, metadataChangeTime, readTime;
// --- Create file and write to it
beforeBirth = fsClockNow().addMSecs(-fsClockSkew);
{
QFile file(fileName);
QVERIFY(file.open(QFile::WriteOnly | QFile::Text));
QFileInfo fileInfo(fileName);
birthTime = fileInfo.birthTime(QTimeZone::UTC);
QVERIFY2(!birthTime.isValid() || birthTime > beforeBirth,
datePairString(birthTime, beforeBirth));
QTest::qSleep(sleepTime);
beforeWrite = fsClockNow().addMSecs(-fsClockSkew);
QTextStream ts(&file);
ts << fileName << Qt::endl;
}
{
QFileInfo fileInfo(fileName);
writeTime = fileInfo.lastModified(QTimeZone::UTC);
QVERIFY2(writeTime > beforeWrite, datePairString(writeTime, beforeWrite));
QCOMPARE(fileInfo.birthTime(QTimeZone::UTC), birthTime); // mustn't have changed
}
// --- Change the file's metadata
QTest::qSleep(sleepTime);
beforeMetadataChange = fsClockNow().addMSecs(-fsClockSkew);
{
QFile file(fileName);
file.setPermissions(file.permissions());
}
{
QFileInfo fileInfo(fileName);
metadataChangeTime = fileInfo.metadataChangeTime(QTimeZone::UTC);
QVERIFY2(metadataChangeTime > beforeMetadataChange,
datePairString(metadataChangeTime, beforeMetadataChange));
QVERIFY(metadataChangeTime >= writeTime); // not all filesystems can store both times
QCOMPARE(fileInfo.birthTime(QTimeZone::UTC), birthTime); // mustn't have changed
}
// --- Read the file
QTest::qSleep(sleepTime);
beforeRead = fsClockNow().addMSecs(-fsClockSkew);
{
QFile file(fileName);
QVERIFY(file.open(QFile::ReadOnly | QFile::Text));
QTextStream ts(&file);
QString line = ts.readLine();
QCOMPARE(line, fileName);
}
QFileInfo fileInfo(fileName);
readTime = fileInfo.lastRead(QTimeZone::UTC);
QCOMPARE(fileInfo.lastModified(QTimeZone::UTC), writeTime); // mustn't have changed
QCOMPARE(fileInfo.birthTime(QTimeZone::UTC), birthTime); // mustn't have changed
QVERIFY(readTime.isValid());
#if defined(Q_OS_QNX) || defined(Q_OS_ANDROID) || defined(Q_OS_HARMONY)
noAccessTime = true;
#elif defined(Q_OS_WIN)
//In Vista the last-access timestamp is not updated when the file is accessed/touched (by default).
//To enable this the HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\NtfsDisableLastAccessUpdate
//is set to 0, in the test machine.
const auto disabledAccessTimes =
QWinRegistryKey(HKEY_LOCAL_MACHINE,
LR"(SYSTEM\CurrentControlSet\Control\FileSystem)")
.value<DWORD>(L"NtfsDisableLastAccessUpdate");
if (disabledAccessTimes.value_or(0) != 0)
noAccessTime = true;
#endif
if (noAccessTime)
return;
QVERIFY2(readTime > beforeRead, datePairString(readTime, beforeRead));
QVERIFY(writeTime < beforeRead);
}
void tst_QFileInfo::setFileTimes()
{
QByteArray data("OLE\nOLE\nOLE");
QTemporaryFile file;
QVERIFY(file.open());
QCOMPARE(file.write(data), data.size());
QCOMPARE(file.size(), data.size());
QDateTime before = QDateTime::currentDateTimeUtc().addMSecs(-5000);
QVERIFY(file.setFileTime(before, QFile::FileModificationTime));
const QDateTime mtime = file.fileTime(QFile::FileModificationTime).toUTC();
if (mtime.time().msec() == 0)
{
const QTime beforeTime = before.time();
const QTime beforeTimeWithMSCutOff{beforeTime.hour(), beforeTime.minute(), beforeTime.second(), 0};
before.setTime(beforeTimeWithMSCutOff);
}
QCOMPARE(mtime, before);
}
void tst_QFileInfo::fakeFileTimes_data()
{
QTest::addColumn<QDateTime>("when");
// This is 2^{31} seconds before 1970-01-01 15:14:8,
// i.e. shortly after the start of time_t, in any time-zone:
#if !defined(Q_OS_QNX) && !defined(Q_OS_HARMONY) // qnx6 filesystem stores timestamps as 32-bit unsigned;
// pre-epoch values overflow silently to ~2038
// OHOS filesystem doesn't support pre-epoch timestamps
QTest::newRow("early") << QDateTime(QDate(1901, 12, 14), QTime(12, 0));
#endif
// QTBUG-12006 claims XP handled this (2010-Mar-26 8:46:10) wrong due to an MS API bug:
QTest::newRow("XP-bug") << QDateTime::fromSecsSinceEpoch(1269593170);
}
void tst_QFileInfo::fakeFileTimes()
{
QFETCH(QDateTime, when);
QFile file("faketimefile.txt");
QVERIFY(file.open(QIODevice::WriteOnly));
file.write("\n", 1);
file.close();
/*
QFile's setFileTime calls QFSFileEngine::setFileTime() which fails unless
the file is open at the time. Of course, when writing, close() changes
modification time, so need to re-open for read in order to setFileTime().
*/
QVERIFY(file.open(QIODevice::ReadOnly));
bool ok = file.setFileTime(when, QFileDevice::FileModificationTime);
file.close();
if (ok)
QCOMPARE(QFileInfo(file.fileName()).lastModified(QTimeZone::UTC), when);
else
QSKIP("Unable to set file metadata to contrived values");
}
void tst_QFileInfo::isSymLink_data()
{
#ifndef Q_NO_SYMLINKS
QFile::remove("link.lnk");
QFile::remove("brokenlink.lnk");
QFile::remove("dummyfile");
QFile::remove("relative/link.lnk");
QFile file1(m_sourceFile);
QVERIFY(file1.link("link.lnk"));
QFile file2("dummyfile");
QVERIFY(file2.open(QIODevice::WriteOnly));
QVERIFY(file2.link("brokenlink.lnk"));
file2.remove();
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("isSymLink");
QTest::addColumn<QString>("linkTarget");
QTest::newRow("existent file") << m_sourceFile << false << "";
QTest::newRow("link") << "link.lnk" << true << QFileInfo(m_sourceFile).absoluteFilePath();
QTest::newRow("broken link") << "brokenlink.lnk" << true << QFileInfo("dummyfile").absoluteFilePath();
QTest::newRow("nonexistent") << "thispathdoesntexist.lnk" << false << QString();
#ifndef Q_OS_WIN
QDir::current().mkdir("relative");
QFile::link("../dummyfile", "relative/link.lnk");
QTest::newRow("relative link") << "relative/link.lnk" << true << QFileInfo("dummyfile").absoluteFilePath();
#endif
#endif
}
void tst_QFileInfo::isSymLink()
{
#ifdef Q_NO_SYMLINKS
QSKIP("No symlink support");
#else
QFETCH(QString, path);
QFETCH(bool, isSymLink);
QFETCH(QString, linkTarget);
QFileInfo fi(path);
QCOMPARE(fi.isSymLink(), isSymLink);
QCOMPARE(fi.symLinkTarget(), linkTarget);
#endif
}
void tst_QFileInfo::isShortcut_data()
{
QFile::remove("link.lnk");
QFile::remove("symlink.lnk");
QFile::remove("link");
QFile::remove("symlink");
QFile::remove("directory.lnk");
QFile::remove("directory");
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("isShortcut");
QFile regularFile(m_sourceFile);
QTest::newRow("regular")
<< regularFile.fileName() << false;
QTest::newRow("directory")
<< QDir::currentPath() << false;
#if defined(Q_OS_WIN)
// windows shortcuts
QVERIFY(regularFile.link("link.lnk"));
QTest::newRow("shortcut")
<< "link.lnk" << true;
QVERIFY(regularFile.link("link"));
QTest::newRow("invalid-shortcut")
<< "link" << false;
QVERIFY(QFile::link(QDir::currentPath(), "directory.lnk"));
QTest::newRow("directory-shortcut")
<< "directory.lnk" << true;
#endif
}
void tst_QFileInfo::isShortcut()
{
QFETCH(QString, path);
QFETCH(bool, isShortcut);
QFileInfo fi(path);
QCOMPARE(fi.isShortcut(), isShortcut);
}
void tst_QFileInfo::isAlias_data()
{
QFile::remove("symlink");
QFile::remove("file-alias");
QFile::remove("directory-alias");
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("isAlias");
QFile regularFile(m_sourceFile);
QTest::newRow("regular-file") << regularFile.fileName() << false;
QTest::newRow("directory") << QDir::currentPath() << false;
#if defined(Q_OS_MACOS)
auto createAlias = [](const QString &target, const QString &alias) {
NSURL *targetUrl = [NSURL fileURLWithPath:target.toNSString()];
NSURL *aliasUrl = [NSURL fileURLWithPath:alias.toNSString()];
NSData *bookmarkData = [targetUrl bookmarkDataWithOptions:NSURLBookmarkCreationSuitableForBookmarkFile
includingResourceValuesForKeys:nil relativeToURL:nil error:nullptr];
Q_ASSERT(bookmarkData);
bool success = [NSURL writeBookmarkData:bookmarkData toURL:aliasUrl
options:NSURLBookmarkCreationSuitableForBookmarkFile error:nullptr];
Q_ASSERT(success);
};
regularFile.link("symlink");
QTest::newRow("symlink") << "symlink" << false;
createAlias(regularFile.fileName(), QDir::current().filePath("file-alias"));
QTest::newRow("file-alias") << "file-alias" << true;
createAlias(QDir::currentPath(), QDir::current().filePath("directory-alias"));
QTest::newRow("directory-alias") << "directory-alias" << true;
regularFile.copy("non-existing-file");
createAlias("non-existing-file", QDir::current().filePath("non-existing-file-alias"));
QDir::current().remove("non-existing-file");
QTest::newRow("non-existing-file-alias") << "non-existing-file-alias" << true;
#endif
}
void tst_QFileInfo::isAlias()
{
QFETCH(QString, path);
QFETCH(bool, isAlias);
QFileInfo fi(path);
QCOMPARE(fi.isAlias(), isAlias);
}
void tst_QFileInfo::isSymbolicLink_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("isSymbolicLink");
QFile regularFile(m_sourceFile);
QTest::newRow("regular")
<< regularFile.fileName() << false;
QTest::newRow("directory")
<< QDir::currentPath() << false;
QTest::newRow("nonexistent")
<< "thispathdoesntexist.lnk" << false;
#ifndef Q_NO_SYMLINKS
#if defined(Q_OS_WIN)
const auto creationResult = FileSystem::createSymbolicLink("symlink", m_sourceFile);
if (creationResult.dwErr == ERROR_PRIVILEGE_NOT_HELD) {
qWarning() << qPrintable(msgInsufficientPrivileges(creationResult.errorMessage));
} else {
QVERIFY2(creationResult.dwErr == ERROR_SUCCESS, qPrintable(creationResult.errorMessage));
QTest::newRow("NTFS-symlink")
<< "symlink" << true;
}
#else // Unix:
QVERIFY(regularFile.link("symlink.lnk"));
QTest::newRow("symlink.lnk")
<< "symlink.lnk" << true;
QVERIFY(regularFile.link("symlink"));
QTest::newRow("symlink")
<< "symlink" << true;
QVERIFY(QFile::link(QDir::currentPath(), "directory"));
QTest::newRow("directory-symlink")
<< "directory" << true;
#endif
#endif // !Q_NO_SYMLINKS
}
void tst_QFileInfo::isSymbolicLink()
{
QFETCH(QString, path);
QFETCH(bool, isSymbolicLink);
QFileInfo fi(path);
QCOMPARE(fi.isSymbolicLink(), isSymbolicLink);
}
void tst_QFileInfo::link_data()
{
QFile::remove("link");
QFile::remove("link.lnk");
QFile::remove("brokenlink");
QFile::remove("brokenlink.lnk");
QFile::remove("dummyfile");
QFile::remove("relative/link");
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("isShortcut");
QTest::addColumn<bool>("isSymbolicLink");
QTest::addColumn<QString>("linkTarget");
QFile file1(m_sourceFile);
QFile file2("dummyfile");
QVERIFY(file2.open(QIODevice::WriteOnly));
QTest::newRow("existent file") << m_sourceFile << false << false << "";
#if defined(Q_OS_WIN)
// windows shortcuts
QVERIFY(file1.link("link.lnk"));
QTest::newRow("link.lnk")
<< "link.lnk" << true << false << QFileInfo(m_sourceFile).absoluteFilePath();
QVERIFY(file2.link("brokenlink.lnk"));
QTest::newRow("broken link.lnk")
<< "brokenlink.lnk" << true << false << QFileInfo("dummyfile").absoluteFilePath();
#endif
#ifndef Q_NO_SYMLINKS
#if defined(Q_OS_WIN)
auto creationResult = FileSystem::createSymbolicLink("link", m_sourceFile);
if (creationResult.dwErr == ERROR_PRIVILEGE_NOT_HELD) {
qWarning() << qPrintable(msgInsufficientPrivileges(creationResult.errorMessage));
} else {
QVERIFY2(creationResult.dwErr == ERROR_SUCCESS, qPrintable(creationResult.errorMessage));
QTest::newRow("link")
<< "link" << false << true << QFileInfo(m_sourceFile).absoluteFilePath();
}
creationResult = FileSystem::createSymbolicLink("brokenlink", "dummyfile");
if (creationResult.dwErr == ERROR_PRIVILEGE_NOT_HELD) {
qWarning() << qPrintable(msgInsufficientPrivileges(creationResult.errorMessage));
} else {
QVERIFY2(creationResult.dwErr == ERROR_SUCCESS, qPrintable(creationResult.errorMessage));
QTest::newRow("broken link")
<< "brokenlink" << false << true << QFileInfo("dummyfile").absoluteFilePath();
}
#else // Unix:
QVERIFY(file1.link("link"));
QTest::newRow("link")
<< "link" << false << true << QFileInfo(m_sourceFile).absoluteFilePath();
QVERIFY(file2.link("brokenlink"));
QTest::newRow("broken link")
<< "brokenlink" << false << true << QFileInfo("dummyfile").absoluteFilePath();
QDir::current().mkdir("relative");
QFile::link("../dummyfile", "relative/link");
QTest::newRow("relative link")
<< "relative/link" << false << true << QFileInfo("dummyfile").absoluteFilePath();
#endif
#endif // !Q_NO_SYMLINKS
file2.remove();
}
void tst_QFileInfo::link()
{
QFETCH(QString, path);
QFETCH(bool, isShortcut);
QFETCH(bool, isSymbolicLink);
QFETCH(QString, linkTarget);
QFileInfo fi(path);
QCOMPARE(fi.isShortcut(), isShortcut);
QCOMPARE(fi.isSymbolicLink(), isSymbolicLink);
QCOMPARE(fi.symLinkTarget(), linkTarget);
}
void tst_QFileInfo::isHidden_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("isHidden");
const auto drives = QDir::drives();
for (const QFileInfo& info : drives)
QTest::newRow(qPrintable("drive." + info.path())) << info.path() << false;
#if defined(Q_OS_WIN)
QVERIFY(QDir("./hidden-directory").exists() || QDir().mkdir("./hidden-directory"));
QVERIFY(SetFileAttributesW(reinterpret_cast<LPCWSTR>(QString("./hidden-directory").utf16()),FILE_ATTRIBUTE_HIDDEN));
QTest::newRow("hidden-directory") << QString::fromLatin1("hidden-directory") << true;
QTest::newRow("C:/path/to/hidden-directory") << QDir::currentPath() + QString::fromLatin1("/hidden-directory") << true;
QTest::newRow("C:/path/to/hidden-directory/.") << QDir::currentPath() + QString::fromLatin1("/hidden-directory/.") << true;
#endif
#if defined(Q_OS_UNIX)
QVERIFY(QDir("./.hidden-directory").exists() || QDir().mkdir("./.hidden-directory"));
QTest::newRow(".hidden-directory") << QString(".hidden-directory") << true;
QTest::newRow(".hidden-directory/") << QString(".hidden-directory/") << true;
QTest::newRow(".hidden-directory//") << QString(".hidden-directory//") << true;
QTest::newRow(".hidden-directory/.") << QString(".hidden-directory/.") << true;
QTest::newRow(".hidden-directory//.") << QString(".hidden-directory//.") << true;
QTest::newRow(".hidden-directory/..") << QString(".hidden-directory/..") << true;
QTest::newRow(".hidden-directory//..") << QString(".hidden-directory//..") << true;
QTest::newRow("/path/to/.hidden-directory") << QDir::currentPath() + QString("/.hidden-directory") << true;
QTest::newRow("/path/to/.hidden-directory/") << QDir::currentPath() + QString("/.hidden-directory/") << true;
QTest::newRow("/path/to/.hidden-directory//") << QDir::currentPath() + QString("/.hidden-directory//") << true;
QTest::newRow("/path/to/.hidden-directory/.") << QDir::currentPath() + QString("/.hidden-directory/.") << true;
QTest::newRow("/path/to/.hidden-directory//.") << QDir::currentPath() + QString("/.hidden-directory//.") << true;
QTest::newRow("/path/to/.hidden-directory/..") << QDir::currentPath() + QString("/.hidden-directory/..") << true;
QTest::newRow("/path/to/.hidden-directory//..") << QDir::currentPath() + QString("/.hidden-directory//..") << true;
#endif
#if defined(Q_OS_DARWIN)
// /bin has the hidden attribute on OS X
QTest::newRow("/bin/") << QString::fromLatin1("/bin/") << true;
#elif !defined(Q_OS_WIN)
QTest::newRow("/bin/") << QString::fromLatin1("/bin/") << false;
#endif
#ifdef Q_OS_DARWIN
QTest::newRow("mac_etc") << QString::fromLatin1("/etc") << true;
QTest::newRow("mac_private_etc") << QString::fromLatin1("/private/etc") << false;
QTest::newRow("mac_Applications") << QString::fromLatin1("/Applications") << false;
#endif
}
void tst_QFileInfo::isHidden()
{
QFETCH(QString, path);
QFETCH(bool, isHidden);
QFileInfo fi(path);
QCOMPARE(fi.isHidden(), isHidden);
}
void tst_QFileInfo::isHiddenFromFinder_data()
{
#ifndef UF_HIDDEN
QSKIP("Only supported on OSes with UF_HIDDEN flag");
#endif
QTest::addColumn<bool>("isSymlink");
QTest::addColumn<bool>("isHidden");
QTest::newRow("regular-visible") << false << false;
QTest::newRow("symlink-visible") << true << false;
QTest::newRow("regular-hidden") << false << true;
QTest::newRow("symlink-hidden") << true << true;
}
void tst_QFileInfo::isHiddenFromFinder()
{
auto setHiddenFromFinder = [](const char *filename) {
#ifdef UF_HIDDEN
QT_STATBUF buf;
QT_STAT(filename, &buf);
lchflags(filename, buf.st_flags | UF_HIDDEN);
#else
Q_UNUSED(filename);
#endif
};
QFETCH(bool, isSymlink);
QFETCH(bool, isHidden);
const char *filename = "test_foobar.txt";
const char *symlinkname = "test_foobar.lnk";
QFile testFile(filename);
QVERIFY(testFile.open(QIODevice::WriteOnly | QIODevice::Truncate));
testFile.write(QByteArray("world"));
testFile.close();
if (isSymlink) {
testFile.link(symlinkname);
if (!isHidden)
setHiddenFromFinder(filename); // symlink points to hidden file!
filename = symlinkname; // we're checking the symlink
}
if (isHidden)
setHiddenFromFinder(filename);
QFileInfo fi(filename);
QCOMPARE(fi.isHidden(), isHidden);
testFile.remove();
QFile::remove(symlinkname);
}
void tst_QFileInfo::isBundle_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("isBundle");
QTest::newRow("root") << QString::fromLatin1("/") << false;
#ifdef Q_OS_DARWIN
QTest::newRow("mac_Applications") << QString::fromLatin1("/Applications") << false;
QTest::newRow("mac_Applications") << QString::fromLatin1("/Applications/Safari.app") << true;
#endif
}
void tst_QFileInfo::isBundle()
{
QFETCH(QString, path);
QFETCH(bool, isBundle);
QFileInfo fi(path);
QCOMPARE(fi.isBundle(), isBundle);
}
void tst_QFileInfo::isNativePath_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("isNativePath");
QTest::newRow("default-constructed") << QString() << false;
QTest::newRow("empty") << QString("") << false;
QTest::newRow("local root") << QString::fromLatin1("/") << true;
QTest::newRow("local non-existent file") << QString::fromLatin1("/abrakadabra.boo") << true;
QTest::newRow("qresource root") << QString::fromLatin1(":/") << false;
}
void tst_QFileInfo::isNativePath()
{
QFETCH(QString, path);
QFETCH(bool, isNativePath);
QFileInfo info(path);
if (path.isNull())
info = QFileInfo();
QCOMPARE(info.isNativePath(), isNativePath);
}
void tst_QFileInfo::refresh()
{
#if defined(Q_OS_WIN)
int sleepTime = 3000;
#else
int sleepTime = 2000;
#endif
QFile::remove("file1");
QFile file("file1");
QVERIFY(file.open(QFile::WriteOnly));
QCOMPARE(file.write("JAJAJAA"), qint64(7));
file.flush();
QFileInfo info(file);
QDateTime lastModified = info.lastModified(QTimeZone::UTC);
QCOMPARE(info.size(), qint64(7));
QTest::qSleep(sleepTime);
QCOMPARE(file.write("JOJOJO"), qint64(6));
file.flush();
QCOMPARE(info.lastModified(QTimeZone::UTC), lastModified);
QCOMPARE(info.size(), qint64(7));
#if defined(Q_OS_WIN)
file.close();
#endif
info.refresh();
QCOMPARE(info.size(), qint64(13));
QVERIFY(info.lastModified(QTimeZone::UTC) > lastModified);
QFileInfo info2 = info;
QCOMPARE(info2.size(), info.size());
info2.refresh();
QCOMPARE(info2.size(), info.size());
}
#if defined(Q_OS_WIN)
struct NtfsTestResource {
enum Type { None, SymLink, Junction };
explicit NtfsTestResource(Type tp = None, const QString &s = QString(), const QString &t = QString())
: source(s), target(t), type(tp) {}
QString source;
QString target;
Type type;
};
Q_DECLARE_METATYPE(NtfsTestResource)
void tst_QFileInfo::ntfsJunctionPointsAndSymlinks_data()
{
QTest::addColumn<NtfsTestResource>("resource");
QTest::addColumn<QString>("path");
QTest::addColumn<QString>("linkTarget");
QTest::addColumn<QString>("canonicalFilePath");
QDir pwd;
pwd.mkdir("target");
{
//Directory symlinks
QDir target("target");
QVERIFY(target.exists());
QString absTarget = QDir::toNativeSeparators(target.absolutePath());
QString absSymlink = QDir::toNativeSeparators(pwd.absolutePath()).append("\\abs_symlink");
QString relTarget = "target";
QString relSymlink = "rel_symlink";
QString fileInTarget(absTarget);
fileInTarget.append("\\file");
QString fileInSymlink(absSymlink);
fileInSymlink.append("\\file");
QFile file(fileInTarget);
QVERIFY2(file.open(QIODevice::ReadWrite), qPrintable(file.errorString()));
file.close();
QVERIFY2(file.exists(), msgDoesNotExist(file.fileName()).constData());
QTest::newRow("absolute dir symlink")
<< NtfsTestResource(NtfsTestResource::SymLink, absSymlink, absTarget)
<< absSymlink << QDir::fromNativeSeparators(absTarget) << target.canonicalPath();
QTest::newRow("relative dir symlink")
<< NtfsTestResource(NtfsTestResource::SymLink, relSymlink, relTarget)
<< relSymlink << QDir::fromNativeSeparators(absTarget) << target.canonicalPath();
QTest::newRow("file in symlink dir")
<< NtfsTestResource()
<< fileInSymlink << "" << target.canonicalPath().append("/file");
}
{
//File symlinks
pwd.mkdir("relative");
QDir relativeDir("relative");
QFileInfo target(m_sourceFile);
QString absTarget = QDir::toNativeSeparators(target.absoluteFilePath());
QString absSymlink = QDir::toNativeSeparators(pwd.absolutePath()).append("\\abs_symlink.cpp");
QString relTarget = QDir::toNativeSeparators(pwd.relativeFilePath(target.absoluteFilePath()));
QString relSymlink = "rel_symlink.cpp";
QString relToRelTarget = QDir::toNativeSeparators(relativeDir.relativeFilePath(target.absoluteFilePath()));
QString relToRelSymlink = "relative/rel_symlink";
QTest::newRow("absolute file symlink")
<< NtfsTestResource(NtfsTestResource::SymLink, absSymlink, absTarget)
<< absSymlink << QDir::fromNativeSeparators(absTarget) << target.canonicalFilePath();
QTest::newRow("relative file symlink")
<< NtfsTestResource(NtfsTestResource::SymLink, relSymlink, relTarget)
<< relSymlink << QDir::fromNativeSeparators(absTarget) << target.canonicalFilePath();
QTest::newRow("relative to relative file symlink")
<< NtfsTestResource(NtfsTestResource::SymLink, relToRelSymlink, relToRelTarget)
<< relToRelSymlink << QDir::fromNativeSeparators(absTarget) << target.canonicalFilePath();
}
if (uncServerAvailable) {
// Symlink to UNC share
pwd.mkdir("unc");
QString uncTarget = QStringLiteral("//") + QTest::uncServerName() + "/testshare";
QString uncSymlink = QDir::toNativeSeparators(pwd.absolutePath().append("\\unc\\link_to_unc"));
QTest::newRow("UNC symlink")
<< NtfsTestResource(NtfsTestResource::SymLink, uncSymlink, uncTarget)
<< QDir::fromNativeSeparators(uncSymlink) << QDir::fromNativeSeparators(uncTarget) << uncTarget;
}
//Junctions
QString target = "target";
QString junction = "junction_pwd";
QFileInfo targetInfo(target);
QTest::newRow("junction_pwd")
<< NtfsTestResource(NtfsTestResource::Junction, junction, target)
<< junction << QString() << QString();
QFileInfo fileInJunction(targetInfo.absoluteFilePath().append("/file"));
QFile file(fileInJunction.absoluteFilePath());
QVERIFY2(file.open(QIODevice::ReadWrite), qPrintable(file.errorString()));
file.close();
QVERIFY2(file.exists(), msgDoesNotExist(file.fileName()).constData());
QTest::newRow("file in junction")
<< NtfsTestResource()
<< fileInJunction.absoluteFilePath() << QString() << fileInJunction.canonicalFilePath();
target = QDir::rootPath();
junction = "junction_root";
targetInfo.setFile(target);
QTest::newRow("junction_root")
<< NtfsTestResource(NtfsTestResource::Junction, junction, target)
<< junction << QString() << QString();
//Mountpoint
wchar_t buffer[MAX_PATH];
QString rootPath = QDir::toNativeSeparators(QDir::rootPath());
QVERIFY(GetVolumeNameForVolumeMountPoint((wchar_t*)rootPath.utf16(), buffer, MAX_PATH));
QString rootVolume = QString::fromWCharArray(buffer);
junction = "mountpoint";
rootVolume.replace("\\\\?\\","\\??\\");
QTest::newRow("mountpoint")
<< NtfsTestResource(NtfsTestResource::Junction, junction, rootVolume)
<< junction << QString() << QString();
}
void tst_QFileInfo::ntfsJunctionPointsAndSymlinks()
{
QFETCH(NtfsTestResource, resource);
QFETCH(QString, path);
QFETCH(QString, linkTarget);
QFETCH(QString, canonicalFilePath);
bool isSymLink = false;
bool isJunction = false;
FileSystem::Result creationResult;
switch (resource.type) {
case NtfsTestResource::None:
break;
case NtfsTestResource::SymLink:
isSymLink = true;
creationResult = FileSystem::createSymbolicLink(resource.source, resource.target);
break;
case NtfsTestResource::Junction:
isJunction = true;
creationResult = FileSystem::createNtfsJunction(resource.target, resource.source);
if (creationResult.dwErr == ERROR_NOT_SUPPORTED) // Special value indicating non-NTFS drive
QSKIP(qPrintable(creationResult.errorMessage));
break;
}
if (creationResult.dwErr == ERROR_PRIVILEGE_NOT_HELD)
QSKIP(msgInsufficientPrivileges(creationResult.errorMessage));
QVERIFY2(creationResult.dwErr == ERROR_SUCCESS, qPrintable(creationResult.errorMessage));
QFileInfo fi(path);
auto guard = qScopeGuard([&fi, this]() {
// Ensure that junctions, mountpoints are removed. If this fails, do not remove
// temporary directory to prevent it from trashing the system.
if (fi.isDir()) {
if (!QDir().rmdir(fi.filePath())) {
qWarning("Unable to remove NTFS junction '%ls', keeping '%ls'.",
qUtf16Printable(fi.fileName()),
qUtf16Printable(QDir::toNativeSeparators(m_dir.path())));
m_dir.setAutoRemove(false);
}
}
});
const QString actualCanonicalFilePath = fi.canonicalFilePath();
QCOMPARE(fi.isJunction(), isJunction);
QCOMPARE(fi.isSymbolicLink(), isSymLink);
if (isSymLink) {
QCOMPARE(fi.symLinkTarget(), linkTarget);
QCOMPARE(actualCanonicalFilePath, canonicalFilePath);
}
if (isJunction) {
if (creationResult.target.startsWith(uR"(\??\)"))
creationResult.target = creationResult.target.sliced(4);
// resolve volume to drive letter
static const QRegularExpression matchVolumeRe(uR"(^Volume\{([a-z]|[0-9]|-)+\}\\)"_s,
QRegularExpression::CaseInsensitiveOption);
auto matchVolume = matchVolumeRe.match(creationResult.target);
if (matchVolume.hasMatch()) {
Q_ASSERT(matchVolume.capturedStart() == 0);
DWORD len;
wchar_t buffer[MAX_PATH];
const QString volumeName = uR"(\\?\)"_s + matchVolume.captured();
if (GetVolumePathNamesForVolumeName(reinterpret_cast<LPCWSTR>(volumeName.utf16()),
buffer, MAX_PATH, &len) != 0) {
creationResult.target.replace(0, matchVolume.capturedLength(),
QString::fromWCharArray(buffer));
}
}
QCOMPARE(fi.junctionTarget(), QDir::fromNativeSeparators(creationResult.target));
QCOMPARE(actualCanonicalFilePath, QFileInfo(creationResult.link).canonicalFilePath());
}
}
void tst_QFileInfo::brokenShortcut()
{
QString linkName("borkenlink.lnk");
QFile::remove(linkName);
QFile file(linkName);
QVERIFY(file.open(QFile::WriteOnly));
file.write("b0rk");
file.close();
QFileInfo info(linkName);
QVERIFY(!info.isSymbolicLink());
QVERIFY(info.isShortcut());
QVERIFY(!info.exists());
QFile::remove(linkName);
QDir current; // QTBUG-21863
QVERIFY(current.mkdir(linkName));
QFileInfo dirInfo(linkName);
QVERIFY(!dirInfo.isSymbolicLink());
QVERIFY(!dirInfo.isShortcut());
QVERIFY(dirInfo.isDir());
current.rmdir(linkName);
}
#endif
void tst_QFileInfo::isWritable()
{
QFile tempfile("tempfile.txt");
QVERIFY(tempfile.open(QIODevice::WriteOnly));
tempfile.write("This file is generated by the QFileInfo autotest.");
tempfile.close();
// isWritable returns true when file is writable
QVERIFY(QFileInfo("tempfile.txt").isWritable());
QFile::setPermissions("tempfile.txt",
QFileDevice::ReadOwner | QFileDevice::ReadUser | QFileDevice::ReadGroup);
// isWritable returns false when read-only, unless root
bool isRoot = false;
#if defined (Q_OS_UNIX)
isRoot = (geteuid() == 0);
#endif
QCOMPARE(QFileInfo("tempfile.txt").isWritable(), isRoot);
tempfile.remove();
// isWritable returns false when file does not exist
QVERIFY(!QFileInfo("tempfile.txt").isWritable());
#if defined(Q_OS_WIN)
QFileInfo fi("c:\\pagefile.sys");
QVERIFY2(fi.exists(), msgDoesNotExist(fi.absoluteFilePath()).constData());
QVERIFY(!fi.isWritable());
#endif
#if defined (Q_OS_WIN)
QNtfsPermissionCheckGuard permissionGuard;
QFileInfo fi2(QFile::decodeName(qgetenv("SystemRoot") + "/system.ini"));
QVERIFY(fi2.exists());
QCOMPARE(fi2.isWritable(), IsUserAdmin());
#endif
#if defined (Q_OS_QNX) // On QNX /etc is usually on a read-only filesystem
QCOMPARE(QFileInfo("/etc/passwd").isWritable(), (geteuid() == 0));
#elif defined (Q_OS_UNIX) && !defined(Q_OS_VXWORKS) // VxWorks does not have users/groups
for (const char *attempt : { "/etc/passwd", "/etc/machine-id", "/proc/version" }) {
if (access(attempt, F_OK) == -1)
continue;
QCOMPARE(QFileInfo(attempt).isWritable(), ::access(attempt, W_OK) == 0);
}
#endif
}
void tst_QFileInfo::isExecutable()
{
#ifdef Q_OS_HARMONY
QSKIP("Shared libraries on HarmonyOS do not have the executable bit set");
#endif
QString appPath = QCoreApplication::applicationDirPath();
#ifdef Q_OS_ANDROID
QDir dir(appPath);
QVERIFY(dir.exists());
dir.setNameFilters({ "libtst_qfileinfo*.so" });
QStringList entries = dir.entryList();
QCOMPARE(entries.size(), 1);
appPath += "/" + entries[0];
#else
appPath += "/tst_qfileinfo";
# if defined(Q_OS_WIN)
appPath += ".exe";
# endif
#endif
QFileInfo fi(appPath);
QVERIFY(fi.exists());
QCOMPARE(fi.isExecutable(), true);
QCOMPARE(QFileInfo(m_proFile).isExecutable(), false);
#ifdef Q_OS_UNIX
QFile::remove("link.lnk");
// Symlink to executable
QFile appFile(appPath);
QVERIFY(appFile.link("link.lnk"));
QCOMPARE(QFileInfo("link.lnk").isExecutable(), true);
QFile::remove("link.lnk");
// Symlink to .pro file
QFile proFile(m_proFile);
QVERIFY(proFile.link("link.lnk"));
QCOMPARE(QFileInfo("link.lnk").isExecutable(), false);
QFile::remove("link.lnk");
#endif
}
void tst_QFileInfo::testDecomposedUnicodeNames_data()
{
QTest::addColumn<QString>("filePath");
QTest::addColumn<QString>("fileName");
QTest::addColumn<bool>("exists");
QString currPath = QDir::currentPath();
QTest::newRow("latin-only") << currPath + "/4.pdf" << "4.pdf" << true;
QTest::newRow("one-decomposed uni") << currPath + QString::fromUtf8("/4 ä.pdf") << QString::fromUtf8("4 ä.pdf") << true;
QTest::newRow("many-decomposed uni") << currPath + QString::fromUtf8("/4 äääcopy.pdf") << QString::fromUtf8("4 äääcopy.pdf") << true;
QTest::newRow("no decomposed") << currPath + QString::fromUtf8("/4 øøøcopy.pdf") << QString::fromUtf8("4 øøøcopy.pdf") << true;
}
// This is a helper class that ensures that files created during the test
// will be removed afterwards, even if the test fails or throws an exception.
class NativeFileCreator
{
public:
NativeFileCreator(const QString &filePath)
: m_filePath(filePath), m_error(0)
{
#ifdef Q_OS_UNIX
int fd = open(m_filePath.normalized(QString::NormalizationForm_D).toUtf8().constData(), O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
if (fd >= 0)
close(fd);
else
m_error = errno;
#endif
}
~NativeFileCreator()
{
#ifdef Q_OS_UNIX
if (m_error == 0)
unlink(m_filePath.normalized(QString::NormalizationForm_D).toUtf8().constData());
#endif
}
int error() const
{
return m_error;
}
private:
QString m_filePath;
int m_error;
};
void tst_QFileInfo::testDecomposedUnicodeNames()
{
#ifndef Q_OS_DARWIN
QSKIP("This is a OS X only test (unless you know more about filesystems, then maybe you should try it ;)");
#else
QFETCH(QString, filePath);
NativeFileCreator nativeFileCreator(filePath);
int error = nativeFileCreator.error();
QVERIFY2(error == 0, qPrintable(QString("Couldn't create native file %1: %2").arg(filePath).arg(strerror(error))));
QFileInfo file(filePath);
QTEST(file.fileName(), "fileName");
QTEST(file.exists(), "exists");
#endif
}
void tst_QFileInfo::equalOperator() const
{
/* Compare two default constructed values. Yes, to me it seems it should be the opposite too, but
* this is how the code was written. */
QVERIFY(!(QFileInfo() == QFileInfo()));
}
void tst_QFileInfo::equalOperatorWithDifferentSlashes() const
{
const QFileInfo fi1("/usr");
const QFileInfo fi2("/usr/");
QCOMPARE(fi1, fi2);
}
void tst_QFileInfo::notEqualOperator() const
{
/* Compare two default constructed values. Yes, to me it seems it should be the opposite too, but
* this is how the code was written. */
QVERIFY(QFileInfo() != QFileInfo());
}
void tst_QFileInfo::detachingOperations()
{
QFileInfo info1;
QVERIFY(info1.caching());
info1.setCaching(false);
{
QFileInfo info2 = info1;
QVERIFY(!info1.caching());
QVERIFY(!info2.caching());
info2.setCaching(true);
QVERIFY(info2.caching());
info1.setFile("foo");
QVERIFY(!info1.caching());
}
{
QFile file("foo");
info1.setFile(file);
QVERIFY(!info1.caching());
}
info1.setFile(QDir(), "foo");
QVERIFY(!info1.caching());
{
QFileInfo info3;
QVERIFY(info3.caching());
info3 = info1;
QVERIFY(!info3.caching());
}
info1.refresh();
QVERIFY(!info1.caching());
QVERIFY(info1.makeAbsolute());
QVERIFY(!info1.caching());
}
#if defined(Q_OS_WIN)
bool IsUserAdmin()
{
BOOL b;
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
PSID AdministratorsGroup;
b = AllocateAndInitializeSid(
&NtAuthority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&AdministratorsGroup);
if (b) {
if (!CheckTokenMembership( NULL, AdministratorsGroup, &b))
b = false;
FreeSid(AdministratorsGroup);
}
return b != FALSE;
}
#endif // Q_OS_WIN
void tst_QFileInfo::owner()
{
QString userName;
#if defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS) && !defined(Q_OS_INTEGRITY) && !defined(Q_OS_HARMONY)
{
passwd *user = getpwuid(geteuid());
QVERIFY(user);
char *usernameBuf = user->pw_name;
userName = QString::fromLocal8Bit(usernameBuf);
}
#endif
#if defined(Q_OS_WIN)
wchar_t usernameBuf[1024];
DWORD bufSize = 1024;
if (GetUserNameW(usernameBuf, &bufSize)) {
userName = QString::fromWCharArray(usernameBuf);
if (IsUserAdmin()) {
// Special case : If the user is a member of Administrators group, all files
// created by the current user are owned by the Administrators group.
LPLOCALGROUP_USERS_INFO_0 pBuf = NULL;
DWORD dwLevel = 0;
DWORD dwFlags = LG_INCLUDE_INDIRECT ;
DWORD dwPrefMaxLen = MAX_PREFERRED_LENGTH;
DWORD dwEntriesRead = 0;
DWORD dwTotalEntries = 0;
NET_API_STATUS nStatus;
nStatus = NetUserGetLocalGroups(0, usernameBuf, dwLevel, dwFlags, (LPBYTE *) &pBuf,
dwPrefMaxLen, &dwEntriesRead, &dwTotalEntries);
// Check if the current user is a member of Administrators group
if (nStatus == NERR_Success && pBuf){
for (int i = 0; i < (int)dwEntriesRead; i++) {
QString groupName = QString::fromWCharArray(pBuf[i].lgrui0_name);
if (!groupName.compare(QLatin1String("Administrators")))
userName = groupName;
}
}
if (pBuf != NULL)
NetApiBufferFree(pBuf);
}
}
QNtfsPermissionCheckGuard permissionGuard;
#endif
if (userName.isEmpty())
QSKIP("Can't retrieve the user name");
QString fileName("ownertest.txt");
QVERIFY(!QFile::exists(fileName) || QFile::remove(fileName));
{
QFile testFile(fileName);
QVERIFY(testFile.open(QIODevice::WriteOnly | QIODevice::Text));
QByteArray testData("testfile");
QVERIFY(testFile.write(testData) != -1);
}
QFileInfo fi(fileName);
QVERIFY2(fi.exists(), msgDoesNotExist(fi.absoluteFilePath()).constData());
QCOMPARE(fi.owner(), userName);
QFile::remove(fileName);
}
void tst_QFileInfo::group()
{
QString expected;
#if defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS) && !defined(Q_OS_INTEGRITY) && !defined(Q_OS_HARMONY)
struct group *gr;
gid_t gid = getegid();
errno = 0;
gr = getgrgid(gid);
QVERIFY2(gr, qPrintable(
QString("getgrgid returned 0: %1, cannot determine my own group")
.arg(QString::fromLocal8Bit(strerror(errno)))));
expected = QString::fromLocal8Bit(gr->gr_name);
#endif
QString fileName("ownertest.txt");
if (QFile::exists(fileName))
QFile::remove(fileName);
QFile testFile(fileName);
QVERIFY(testFile.open(QIODevice::WriteOnly | QIODevice::Text));
QByteArray testData("testfile");
QVERIFY(testFile.write(testData) != -1);
testFile.close();
QFileInfo fi(fileName);
QVERIFY2(fi.exists(), msgDoesNotExist(fi.absoluteFilePath()).constData());
QCOMPARE(fi.group(), expected);
}
static void stateCheck(const QFileInfo &info, const QString &dirname, const QString &filename)
{
QCOMPARE(info.size(), qint64(0));
QVERIFY(!info.exists());
QString path;
QString abspath;
if (!dirname.isEmpty()) {
path = ".";
abspath = dirname + '/' + filename;
}
QCOMPARE(info.filePath(), filename);
QCOMPARE(info.absoluteFilePath(), abspath);
QCOMPARE(info.canonicalFilePath(), QString());
QCOMPARE(info.fileName(), filename);
QCOMPARE(info.baseName(), filename);
QCOMPARE(info.completeBaseName(), filename);
QCOMPARE(info.suffix(), QString());
QCOMPARE(info.bundleName(), QString());
QCOMPARE(info.completeSuffix(), QString());
QVERIFY(info.isRelative());
QCOMPARE(info.path(), path);
QCOMPARE(info.absolutePath(), dirname);
QCOMPARE(info.canonicalPath(), QString());
QCOMPARE(info.dir().path(), ".");
QCOMPARE(info.absoluteDir().path(), QDir(dirname).path());
QVERIFY(!info.isReadable());
QVERIFY(!info.isWritable());
QVERIFY(!info.isExecutable());
QVERIFY(!info.isHidden());
QVERIFY(!info.isFile());
QVERIFY(!info.isDir());
QVERIFY(!info.isSymbolicLink());
QVERIFY(!info.isShortcut());
QVERIFY(!info.isBundle());
QVERIFY(!info.isRoot());
QCOMPARE(info.isNativePath(), !filename.isEmpty());
QCOMPARE(info.symLinkTarget(), QString());
QCOMPARE(info.ownerId(), uint(-2));
QCOMPARE(info.groupId(), uint(-2));
QCOMPARE(info.owner(), QString());
QCOMPARE(info.group(), QString());
QCOMPARE(info.permissions(), QFile::Permissions());
QVERIFY(!info.birthTime(QTimeZone::UTC).isValid());
QVERIFY(!info.metadataChangeTime(QTimeZone::UTC).isValid());
QVERIFY(!info.lastRead(QTimeZone::UTC).isValid());
QVERIFY(!info.lastModified(QTimeZone::UTC).isValid());
};
void tst_QFileInfo::invalidState_data()
{
QTest::addColumn<int>("mode");
QTest::newRow("default") << 0;
QTest::newRow("empty") << 1;
QTest::newRow("copy-of-default") << 2;
QTest::newRow("copy-of-empty") << 3;
}
void tst_QFileInfo::invalidState()
{
// Shouldn't crash or produce warnings
QFETCH(int, mode);
const QFileInfo &info = (mode & 1 ? QFileInfo("") : QFileInfo());
if (mode & 2) {
QFileInfo copy(info);
stateCheck(copy, QString(), QString());
} else {
stateCheck(info, QString(), QString());
}
}
void tst_QFileInfo::nonExistingFile()
{
QString dirname = QDir::currentPath();
QString cdirname = QFileInfo(dirname).canonicalFilePath();
if (dirname != cdirname)
QDir::setCurrent(cdirname); // chdir() to our canonical path
QString filename = "non-existing-file-foobar";
QFileInfo info(filename);
stateCheck(info, dirname, filename);
}
void tst_QFileInfo::stdfilesystem()
{
#if QT_CONFIG(cxx17_filesystem)
namespace fs = std::filesystem;
// Verify constructing with fs::path leads to valid objects
{
// We compare using absoluteFilePath since QFileInfo::operator== ends up using
// canonicalFilePath which evaluates to empty-string for non-existent paths causing
// these tests to always succeed.
QDir base{ "../" }; // Used for the QFileInfo(QDir, <path>) ctor
auto doCompare = [&base](const char *filepath) {
QCOMPARE(QFileInfo(fs::path(filepath)).absoluteFilePath(),
QFileInfo(QString::fromLocal8Bit(filepath)).absoluteFilePath());
QCOMPARE(QFileInfo(base, fs::path(filepath)).absoluteFilePath(),
QFileInfo(base, QString::fromLocal8Bit(filepath)).absoluteFilePath());
};
#define COMPARE_CONSTRUCTION(filepath) \
doCompare(filepath); if (QTest::currentTestFailed()) return
COMPARE_CONSTRUCTION("./file");
#ifdef Q_OS_WIN32
COMPARE_CONSTRUCTION("C:\\path\\to\\file.txt");
COMPARE_CONSTRUCTION("x:\\path/to\\file.txt");
COMPARE_CONSTRUCTION("D:/path/TO/file.txt");
COMPARE_CONSTRUCTION("//sharename/folder/file.txt");
#endif
COMPARE_CONSTRUCTION("/path/TO/file.txt");
COMPARE_CONSTRUCTION("./path/TO/file.txt");
COMPARE_CONSTRUCTION("../file.txt");
#if !(defined(__GLIBCXX__) && defined(Q_OS_WIN32))
COMPARE_CONSTRUCTION("./filæ.txt");
#endif
// Test unicode strings
QCOMPARE(QFileInfo(fs::path(u"./filæ.txt")).absoluteFilePath(),
QFileInfo(u"./filæ.txt"_s).absoluteFilePath());
QCOMPARE(QFileInfo(base, fs::path(u"./filæ.txt")).absoluteFilePath(),
QFileInfo(base, u"./filæ.txt"_s).absoluteFilePath());
#ifdef __cpp_char8_t
QCOMPARE(QFileInfo(fs::path(u8"./filæ.txt")).absoluteFilePath(),
QFileInfo(u8"./filæ.txt").absoluteFilePath());
QCOMPARE(QFileInfo(base, fs::path(u8"./filæ.txt")).absoluteFilePath(),
QFileInfo(base, u8"./filæ.txt").absoluteFilePath());
#endif
#undef COMPARE_CONSTRUCTION
{
// One proper comparison with operator== for each ctor
QFile file(QStringLiteral("./filesystem_test_file.txt"));
if (!file.open(QFile::NewOnly))
QVERIFY(file.exists());
file.close();
QFileInfo pinfo{ fs::path{ "./filesystem_test_file.txt" } };
QFileInfo info{ QStringLiteral("./filesystem_test_file.txt") };
QCOMPARE(pinfo, info);
}
{
// And once more for QFileInfo(QDir, <path>)
const QString &subdir = QStringLiteral("./filesystem_test_dir/");
base = QDir(QStringLiteral("."));
if (!base.exists(subdir))
QVERIFY(base.mkdir(subdir));
base.cd(subdir);
QFile file{ base.filePath(QStringLiteral("./filesystem_test_file.txt")) };
if (!file.open(QFile::NewOnly))
QVERIFY(file.exists());
file.close();
QFileInfo pinfo{ base, fs::path{ "filesystem_test_file.txt" } };
QFileInfo info{ base, QStringLiteral("filesystem_test_file.txt") };
QCOMPARE(pinfo, info);
}
}
// Verify that functions returning path all point to the same place
{
#define COMPARE_PATHS(actual, expected) \
QCOMPARE(QString::fromStdU16String(actual.u16string()), expected)
QFile file(QStringLiteral("./orig"));
if (!file.open(QFile::NewOnly))
QVERIFY(file.exists());
file.close();
QFileInfo info{ QStringLiteral("./orig") };
COMPARE_PATHS(info.filesystemPath(), info.path());
COMPARE_PATHS(info.filesystemAbsolutePath(), info.absolutePath());
COMPARE_PATHS(info.filesystemCanonicalPath(), info.canonicalPath());
COMPARE_PATHS(info.filesystemFilePath(), info.filePath());
COMPARE_PATHS(info.filesystemAbsoluteFilePath(), info.absoluteFilePath());
COMPARE_PATHS(info.filesystemCanonicalFilePath(), info.canonicalFilePath());
#ifndef Q_NO_SYMLINKS
QVERIFY(file.link(QStringLiteral("./filesystem_test_symlink.lnk")));
info = QFileInfo{ "./filesystem_test_symlink.lnk" };
COMPARE_PATHS(info.filesystemSymLinkTarget(), info.symLinkTarget());
#endif
#undef COMPARE_PATHS
}
#else
QSKIP("Not supported");
#endif
}
void tst_QFileInfo::readSymLink()
{
#ifdef Q_NO_SYMLINKS
QSKIP("No symlink support");
#endif
QString symLinkName("./a.link");
const auto tidier = qScopeGuard([symLinkName]() { QFile::remove(symLinkName); });
#ifdef Q_OS_WIN
QVERIFY2(CreateSymbolicLink(L"a.link", L"..\\..\\a", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)
!= 0,
"Failed to create symlink for test");
#else
QVERIFY2(QFile::link("../../a", symLinkName), "Failed to create symlink for test");
#endif
QFileInfo info(symLinkName);
QCOMPARE(info.readSymLink(), QString("../../a"));
}
#if defined(Q_OS_DARWIN)
void tst_QFileInfo::fileSystemCaseSensitivity_data()
{
QTest::addColumn<QString>("fileSystemType");
QTest::addColumn<QString>("volumeName");
QTest::addColumn<Qt::CaseSensitivity>("caseSensitivity");
QTest::newRow("APFS") << "APFS" << "apfs" << Qt::CaseInsensitive;
QTest::newRow("APFS case-sensitive") << "Case-sensitive APFS" << "apfs_case_sensitive" << Qt::CaseSensitive;
QTest::newRow("HFS+") << "HFS+" << "hfs" << Qt::CaseInsensitive;
QTest::newRow("HFS+ case-sensitive") << "Case-sensitive HFS+" << "hfs_case_sensitive" << Qt::CaseSensitive;
QTest::newRow("FAT32") << "MS-DOS FAT32" << "fat32" << Qt::CaseInsensitive;
QTest::newRow("ExFAT") << "ExFAT" << "exfat" << Qt::CaseInsensitive;
}
void tst_QFileInfo::fileSystemCaseSensitivity()
{
#if !QT_CONFIG(process)
QSKIP("No QProcess available");
#else
QTemporaryDir tmpDir;
QVERIFY2(tmpDir.isValid(), qPrintable(tmpDir.errorString()));
QFETCH(QString, fileSystemType);
QFETCH(QString, volumeName);
QString imageName = tmpDir.filePath(QString("%2.sparseimage").arg(volumeName));
QVERIFY(QProcess::execute("hdiutil", { "create", "-quiet",
"-type", "SPARSE", "-size", "50m", "-fs", fileSystemType,
"-volname", volumeName, imageName }) == 0);
QVERIFY(QProcess::execute("hdiutil", { "attach", "-quiet",
"-mountroot", tmpDir.path(), imageName }) == 0);
QDir mountPoint(tmpDir.filePath(volumeName));
QVERIFY(mountPoint.exists());
auto cleanup = qScopeGuard([&] {
QVERIFY(QProcess::execute("hdiutil", { "detach",
"-quiet", mountPoint.absolutePath() }) == 0);
});
QFileInfo lowerCase(mountPoint.filePath("foo"));
{
QFile file(lowerCase.filePath());
QVERIFY(file.open(QFile::WriteOnly));
}
QFileInfo upperCase(mountPoint.filePath("FOO"));
{
QFile file(upperCase.filePath());
QVERIFY(file.open(QFile::WriteOnly));
}
QFETCH(Qt::CaseSensitivity, caseSensitivity);
QCOMPARE(lowerCase == upperCase, caseSensitivity == Qt::CaseInsensitive);
#endif // QT_CONFIG(process)
}
#endif // defined(Q_OS_DARWIN)
QTEST_MAIN(tst_QFileInfo)
#include "tst_qfileinfo.moc"