diff options
Diffstat (limited to 'tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp')
-rw-r--r-- | tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp | 544 |
1 files changed, 410 insertions, 134 deletions
diff --git a/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp b/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp index cba7dfe9fc..4bb7042790 100644 --- a/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp +++ b/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp @@ -1,38 +1,15 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QtTest/QtTest> +// Copyright (C) 2020 The Qt Company Ltd. +// Copyright (C) 2020 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + #include <qstandardpaths.h> +#include <QTest> +#include <QOperatingSystemVersion> #include <qdebug.h> -#include <qstandardpaths.h> #include <qfileinfo.h> -#include <qsysinfo.h> +#include <qplatformdefs.h> #include <qregularexpression.h> +#include <qsysinfo.h> #if defined(Q_OS_WIN) # include <qt_windows.h> #endif @@ -40,16 +17,61 @@ #ifdef Q_OS_UNIX #include <unistd.h> #include <sys/types.h> +#include <pwd.h> #endif -#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_ANDROID) +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && !defined(Q_OS_ANDROID) #define Q_XDG_PLATFORM #endif -#include "emulationdetector.h" +using namespace Qt::StringLiterals; // Update this when adding new enum values; update enumNames too -static const int MaxStandardLocation = QStandardPaths::AppConfigLocation; +static const int MaxStandardLocation = QStandardPaths::GenericStateLocation; + +static QString genericCacheLoc() +{ + return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); +} +static QString cacheLoc() +{ + return QStandardPaths::writableLocation(QStandardPaths::CacheLocation); +} + +static QString genericStateLoc() +{ + return QStandardPaths::writableLocation(QStandardPaths::GenericStateLocation); +} +static QString stateLoc() +{ + return QStandardPaths::writableLocation(QStandardPaths::StateLocation); +} + +static QString genericDataLoc() +{ + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); +} +static QString appDataLoc() +{ + return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); +} +static QString appLocalDataLoc() +{ + return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); +} + +static QString genericConfigLoc() +{ + return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); +} +static QString configLoc() +{ + return QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); +} +static QString appConfigLoc() +{ + return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); +} class tst_qstandardpaths : public QObject { @@ -58,6 +80,7 @@ class tst_qstandardpaths : public QObject private slots: void initTestCase(); void dump(); + void init(); void testDefaultLocations(); void testCustomLocations(); void enableTestMode(); @@ -68,6 +91,7 @@ private slots: void testFindExecutable(); void testFindExecutableLinkToDirectory(); void testRuntimeDirectory(); + void testCustomRuntimeDirectory_data(); void testCustomRuntimeDirectory(); void testAllWritableLocations_data(); void testAllWritableLocations(); @@ -83,14 +107,17 @@ private: qputenv("XDG_CONFIG_DIRS", QFile::encodeName(m_globalConfigDir)); m_localAppDir = m_localAppTempDir.path(); m_globalAppDir = m_globalAppTempDir.path(); + m_stateDir = m_stateTempDir.path(); qputenv("XDG_DATA_HOME", QFile::encodeName(m_localAppDir)); qputenv("XDG_DATA_DIRS", QFile::encodeName(m_globalAppDir)); + qputenv("XDG_STATE_HOME", QFile::encodeName(m_stateDir)); } void setDefaultLocations() { - qputenv("XDG_CONFIG_HOME", QByteArray()); - qputenv("XDG_CONFIG_DIRS", QByteArray()); - qputenv("XDG_DATA_HOME", QByteArray()); - qputenv("XDG_DATA_DIRS", QByteArray()); + qputenv("XDG_CONFIG_HOME", nullptr); + qputenv("XDG_CONFIG_DIRS", nullptr); + qputenv("XDG_DATA_HOME", nullptr); + qputenv("XDG_DATA_DIRS", nullptr); + qputenv("XDG_STATE_HOME", nullptr); } #endif @@ -105,6 +132,8 @@ private: QTemporaryDir m_localAppTempDir; QString m_globalAppDir; QTemporaryDir m_globalAppTempDir; + QString m_stateDir; + QTemporaryDir m_stateTempDir; }; static const char * const enumNames[MaxStandardLocation + 1 - int(QStandardPaths::DesktopLocation)] = { @@ -117,7 +146,7 @@ static const char * const enumNames[MaxStandardLocation + 1 - int(QStandardPaths "PicturesLocation", "TempLocation", "HomeLocation", - "DataLocation", + "AppLocalDataLocation", "CacheLocation", "GenericDataLocation", "RuntimeLocation", @@ -126,7 +155,11 @@ static const char * const enumNames[MaxStandardLocation + 1 - int(QStandardPaths "GenericCacheLocation", "GenericConfigLocation", "AppDataLocation", - "AppConfigLocation" + "AppConfigLocation", + "PublicShareLocation", + "TemplatesLocation", + "StateLocation", + "GenericStateLocation" }; void tst_qstandardpaths::initTestCase() @@ -145,6 +178,7 @@ void tst_qstandardpaths::initTestCase() QVERIFY2(m_globalConfigTempDir.isValid(), qPrintable(m_globalConfigTempDir.errorString())); QVERIFY2(m_localAppTempDir.isValid(), qPrintable(m_localAppTempDir.errorString())); QVERIFY2(m_globalAppTempDir.isValid(), qPrintable(m_globalAppTempDir.errorString())); + QVERIFY2(m_stateTempDir.isValid(), qPrintable(m_stateTempDir.errorString())); } void tst_qstandardpaths::dump() @@ -161,25 +195,37 @@ void tst_qstandardpaths::dump() } } +void tst_qstandardpaths::init() +{ + // Some unittests set a custom org/app names, restore the original ones + // before each unittest is run + static const QString org = QCoreApplication::organizationName(); + static const QString app = QCoreApplication::applicationName(); + QCoreApplication::setOrganizationName(org); + QCoreApplication::setApplicationName(app); +} + void tst_qstandardpaths::testDefaultLocations() { #ifdef Q_XDG_PLATFORM setDefaultLocations(); const QString expectedConfHome = QDir::homePath() + QString::fromLatin1("/.config"); - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation), expectedConfHome); - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation), expectedConfHome); + QCOMPARE(configLoc(), expectedConfHome); + QCOMPARE(genericConfigLoc(), expectedConfHome); const QStringList confDirs = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation); - QCOMPARE(confDirs.count(), 2); + QCOMPARE(confDirs.size(), 2); QVERIFY(confDirs.contains(expectedConfHome)); QCOMPARE(QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation), confDirs); const QStringList genericDataDirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); - QCOMPARE(genericDataDirs.count(), 3); + QCOMPARE(genericDataDirs.size(), 3); const QString expectedDataHome = QDir::homePath() + QString::fromLatin1("/.local/share"); QCOMPARE(genericDataDirs.at(0), expectedDataHome); QCOMPARE(genericDataDirs.at(1), QString::fromLatin1("/usr/local/share")); QCOMPARE(genericDataDirs.at(2), QString::fromLatin1("/usr/share")); + const QString expectedGenericStateLocation = QDir::homePath() + QString::fromLatin1("/.local/state"); + QCOMPARE(genericStateLoc(), expectedGenericStateLocation); #endif } @@ -198,8 +244,8 @@ void tst_qstandardpaths::testCustomLocations() setCustomLocations(); // test writableLocation() - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation), m_localConfigDir); - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation), m_localConfigDir); + QCOMPARE(configLoc(), m_localConfigDir); + QCOMPARE(genericConfigLoc(), m_localConfigDir); // test locate() const QString thisFileName = QString::fromLatin1("aFile"); @@ -231,37 +277,76 @@ void tst_qstandardpaths::enableTestMode() setCustomLocations(); // for the global config dir const QString qttestDir = QDir::homePath() + QLatin1String("/.qttest"); - // ConfigLocation + // *Config*Location const QString configDir = qttestDir + QLatin1String("/config"); - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation), configDir); - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation), configDir); + QCOMPARE(configLoc(), configDir); + QCOMPARE(genericConfigLoc(), configDir); const QStringList confDirs = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation); QCOMPARE(confDirs, QStringList() << configDir << m_globalConfigDir); + // AppConfigLocation should be "GenericConfigLocation/organization-name/app-name" + QCOMPARE(appConfigLoc(), configDir + "/tst_qstandardpaths"_L1); - // GenericDataLocation + // *Data*Location const QString dataDir = qttestDir + QLatin1String("/share"); - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), dataDir); + QCOMPARE(genericDataLoc(), dataDir); const QStringList gdDirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); QCOMPARE(gdDirs, QStringList() << dataDir << m_globalAppDir); + // AppDataLocation/AppLocalDataLocation should be + // "GenericDataLocation/organization-name/app-name" + QCOMPARE(appDataLoc(), dataDir + "/tst_qstandardpaths"_L1); + QCOMPARE(appLocalDataLoc(), dataDir + "/tst_qstandardpaths"_L1); - // GenericCacheLocation + // *CacheLocation const QString cacheDir = qttestDir + QLatin1String("/cache"); - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation), cacheDir); + QCOMPARE(genericCacheLoc(), cacheDir); const QStringList cacheDirs = QStandardPaths::standardLocations(QStandardPaths::GenericCacheLocation); QCOMPARE(cacheDirs, QStringList() << cacheDir); + // CacheLocation should be "GenericCacheLocation/organization-name/app-name" + QCOMPARE(cacheLoc(), cacheDir + "/tst_qstandardpaths"_L1); + + // *StateLocation + const QString stateDir = qttestDir + QLatin1String("/state"); + QCOMPARE(genericStateLoc(), stateDir); + const QStringList stateDirs = QStandardPaths::standardLocations(QStandardPaths::GenericStateLocation); + QCOMPARE(stateDirs, QStringList() << stateDir); + // StateLocation should be "GenericStateLocation/organization-name/app-name" + QCOMPARE(stateLoc(), stateDir + "/tst_qstandardpaths"_L1); + + QCoreApplication::setOrganizationName("Qt"); + QCOMPARE(appConfigLoc(), configDir + "/Qt/tst_qstandardpaths"_L1); + QCOMPARE(appDataLoc(), dataDir + "/Qt/tst_qstandardpaths"_L1); + QCOMPARE(appLocalDataLoc(), dataDir + "/Qt/tst_qstandardpaths"_L1); + QCOMPARE(cacheLoc(), cacheDir + "/Qt/tst_qstandardpaths"_L1); + QCOMPARE(stateLoc(), stateDir + "/Qt/tst_qstandardpaths"_L1); + + QCoreApplication::setApplicationName("QtTest"); + QCOMPARE(appConfigLoc(), configDir + "/Qt/QtTest"_L1); + QCOMPARE(appDataLoc(), dataDir + "/Qt/QtTest"_L1); + QCOMPARE(appLocalDataLoc(), dataDir + "/Qt/QtTest"_L1); + QCOMPARE(cacheLoc(), cacheDir + "/Qt/QtTest"_L1); + QCOMPARE(stateLoc(), stateDir + "/Qt/QtTest"_L1); + + // Check these are unaffected by org/app names + QCOMPARE(genericConfigLoc(), configDir); + QCOMPARE(configLoc(), configDir); + QCOMPARE(genericDataLoc(), dataDir); + QCOMPARE(genericCacheLoc(), cacheDir); + QCOMPARE(genericStateLoc(), stateDir); #endif // On all platforms, we want to ensure that the writableLocation is different in test mode and real mode. // Check this for locations where test programs typically write. Not desktop, download, music etc... typedef QHash<QStandardPaths::StandardLocation, QString> LocationHash; LocationHash testLocations; - testLocations.insert(QStandardPaths::AppDataLocation, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); - testLocations.insert(QStandardPaths::AppLocalDataLocation, QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); - testLocations.insert(QStandardPaths::GenericDataLocation, QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)); - testLocations.insert(QStandardPaths::ConfigLocation, QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)); - testLocations.insert(QStandardPaths::GenericConfigLocation, QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)); - testLocations.insert(QStandardPaths::CacheLocation, QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - testLocations.insert(QStandardPaths::GenericCacheLocation, QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation)); + testLocations.insert(QStandardPaths::AppDataLocation, appDataLoc()); + testLocations.insert(QStandardPaths::AppLocalDataLocation, appLocalDataLoc()); + testLocations.insert(QStandardPaths::GenericDataLocation, genericDataLoc()); + testLocations.insert(QStandardPaths::ConfigLocation, configLoc()); + testLocations.insert(QStandardPaths::GenericConfigLocation, genericConfigLoc()); + testLocations.insert(QStandardPaths::CacheLocation, cacheLoc()); + testLocations.insert(QStandardPaths::GenericCacheLocation, genericCacheLoc()); + testLocations.insert(QStandardPaths::StateLocation, stateLoc()); + testLocations.insert(QStandardPaths::GenericStateLocation, genericStateLoc()); // On Windows, what should "Program Files" become, in test mode? //testLocations.insert(QStandardPaths::ApplicationsLocation, QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation)); @@ -283,7 +368,7 @@ void tst_qstandardpaths::testLocateAll() #ifdef Q_XDG_PLATFORM setCustomLocations(); const QStringList appsDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "applications", QStandardPaths::LocateDirectory); - QCOMPARE(appsDirs.count(), 0); // they don't exist yet + QCOMPARE(appsDirs.size(), 0); // they don't exist yet const QStringList expectedAppsDirs = QStringList() << m_localAppDir + QLatin1String("/applications") << m_globalAppDir + QLatin1String("/applications"); QDir().mkdir(expectedAppsDirs.at(0)); @@ -306,33 +391,29 @@ void tst_qstandardpaths::testLocateAll() void tst_qstandardpaths::testDataLocation() { - // On all platforms, DataLocation should be GenericDataLocation / organization name / app name + // On all platforms, AppLocalDataLocation should be GenericDataLocation / organization name / app name // This allows one app to access the data of another app. // Android is an exception to this case, owing to the fact that // applications are sandboxed. #if !defined(Q_OS_ANDROID) - const QString base = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation), base + "/tst_qstandardpaths"); + const QString base = genericDataLoc(); + QCOMPARE(appLocalDataLoc(), base + "/tst_qstandardpaths"); QCoreApplication::instance()->setOrganizationName("Qt"); - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation), base + "/Qt/tst_qstandardpaths"); + QCOMPARE(appLocalDataLoc(), base + "/Qt/tst_qstandardpaths"); QCoreApplication::instance()->setApplicationName("QtTest"); - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation), base + "/Qt/QtTest"); + QCOMPARE(appLocalDataLoc(), base + "/Qt/QtTest"); #endif #ifdef Q_XDG_PLATFORM setDefaultLocations(); const QString expectedAppDataDir = QDir::homePath() + QString::fromLatin1("/.local/share/Qt/QtTest"); - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation), expectedAppDataDir); + QCOMPARE(appLocalDataLoc(), expectedAppDataDir); const QStringList appDataDirs = QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation); - QCOMPARE(appDataDirs.count(), 3); + QCOMPARE(appDataDirs.size(), 3); QCOMPARE(appDataDirs.at(0), expectedAppDataDir); QCOMPARE(appDataDirs.at(1), QString::fromLatin1("/usr/local/share/Qt/QtTest")); QCOMPARE(appDataDirs.at(2), QString::fromLatin1("/usr/share/Qt/QtTest")); #endif - - // reset for other tests - QCoreApplication::setOrganizationName(QString()); - QCoreApplication::setApplicationName(QString()); } void tst_qstandardpaths::testAppConfigLocation() @@ -340,15 +421,12 @@ void tst_qstandardpaths::testAppConfigLocation() // On all platforms where applications are not sandboxed, // AppConfigLocation should be GenericConfigLocation / organization name / app name #if !defined(Q_OS_ANDROID) - const QString base = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation), base + "/tst_qstandardpaths"); + const QString base = genericConfigLoc(); + QCOMPARE(appConfigLoc(), base + "/tst_qstandardpaths"); QCoreApplication::setOrganizationName("Qt"); - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation), base + "/Qt/tst_qstandardpaths"); + QCOMPARE(appConfigLoc(), base + "/Qt/tst_qstandardpaths"); QCoreApplication::setApplicationName("QtTest"); - QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation), base + "/Qt/QtTest"); - // reset for other tests - QCoreApplication::setOrganizationName(QString()); - QCoreApplication::setApplicationName(QString()); + QCOMPARE(appConfigLoc(), base + "/Qt/QtTest"); #endif } @@ -361,9 +439,9 @@ static inline QFileInfo findSh() QByteArray pEnv = qgetenv("PATH"); const QLatin1Char pathSep(':'); const QStringList rawPaths = QString::fromLocal8Bit(pEnv.constData()).split(pathSep, Qt::SkipEmptyParts); - foreach (const QString &path, rawPaths) { + for (const QString &path : rawPaths) { if (QFile::exists(path + sh)) - return path + sh; + return QFileInfo(path + sh); } return QFileInfo(); } @@ -455,6 +533,9 @@ void tst_qstandardpaths::testFindExecutableLinkToDirectory() QFile::remove(target); } +using RuntimeDirSetup = std::optional<QString> (*)(QDir &); +Q_DECLARE_METATYPE(RuntimeDirSetup); + void tst_qstandardpaths::testRuntimeDirectory() { #ifdef Q_XDG_PLATFORM @@ -463,6 +544,195 @@ void tst_qstandardpaths::testRuntimeDirectory() #endif } +// INTEGRITY PJF System doesn't support user ID related APIs. getpwuid is not defined. +// testCustomRuntimeDirectory_data test will always FAIL for INTEGRITY. +#if defined(Q_XDG_PLATFORM) && !defined(Q_OS_INTEGRITY) +static QString fallbackXdgRuntimeDir() +{ + static QString username = [] { + struct passwd *pw = getpwuid(geteuid()); + return QString::fromLocal8Bit(pw->pw_name); + }(); + + // QDir::temp() might change from call to call + return QDir::temp().filePath("runtime-" + username); +} +#endif + +[[maybe_unused]] static QString updateRuntimeDir(const QString &path) +{ + qputenv("XDG_RUNTIME_DIR", QFile::encodeName(path)); + return path; +} + +[[maybe_unused]] static void clearRuntimeDir() +{ + qunsetenv("XDG_RUNTIME_DIR"); +#ifdef Q_XDG_PLATFORM +#if !defined(Q_OS_WASM) && !defined(Q_OS_INTEGRITY) + QTest::ignoreMessage(QtWarningMsg, + qPrintable("QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '" + + fallbackXdgRuntimeDir() + '\'')); +#endif +#endif +} + +void tst_qstandardpaths::testCustomRuntimeDirectory_data() +{ +#ifdef Q_OS_INTEGRITY + QSKIP("Test requires getgid/getpwuid API that are not available on INTEGRITY"); +#elif defined(Q_XDG_PLATFORM) + QTest::addColumn<RuntimeDirSetup>("setup"); + auto addRow = [](const char *name, RuntimeDirSetup f) { + QTest::newRow(name) << f; + }; + + +# if defined(Q_OS_UNIX) + if (::getuid() == 0) + QSKIP("Running this test as root doesn't make sense"); +# endif + + addRow("environment:non-existing", [](QDir &d) -> std::optional<QString> { + return updateRuntimeDir(d.filePath("runtime")); + }); + + addRow("environment:existing", [](QDir &d) -> std::optional<QString> { + QString p = d.filePath("runtime"); + d.mkdir("runtime"); + QFile::setPermissions(p, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); + return updateRuntimeDir(p); + }); + + addRow("environment-to-existing-wrong-perm", [](QDir &d) -> std::optional<QString> { + QString p = d.filePath("runtime"); + d.mkdir("runtime"); + QFile::setPermissions(p, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | + QFile::ExeGroup | QFile::ExeOther); + updateRuntimeDir(p); + QTest::ignoreMessage(QtWarningMsg, + QString("QStandardPaths: wrong permissions on runtime directory %1, " + "0711 instead of 0700") + .arg(p).toLatin1()); + return fallbackXdgRuntimeDir(); + }); + + addRow("environment:wrong-owner", [](QDir &) -> std::optional<QString> { + QT_STATBUF st; + QT_STAT("/", &st); + + updateRuntimeDir("/"); + QTest::ignoreMessage(QtWarningMsg, + QString("QStandardPaths: runtime directory '/' is not owned by UID " + "%1, but a directory permissions %2 owned by UID %3 GID %4") + .arg(getuid()) + .arg(st.st_mode & 07777, 4, 8, QChar('0')) + .arg(st.st_uid) + .arg(st.st_gid).toLatin1()); + return fallbackXdgRuntimeDir(); + }); + + // static so that it can be used in RuntimeDirSetup callable without capturing + static auto failedToOpen = [](const QFile &f) { + qCritical("QFile::Open: failed to open '%s': %s", + qPrintable(f.fileName()), qPrintable(f.errorString())); + return std::nullopt; + }; + + addRow("environment:file", [](QDir &d) -> std::optional<QString> { + QString p = d.filePath("file"); + QFile f(p); + if (!f.open(QIODevice::WriteOnly)) + return failedToOpen(f); + f.setPermissions(QFile::ReadOwner | QFile::WriteOwner); + + updateRuntimeDir(p); + QTest::ignoreMessage(QtWarningMsg, + QString("QStandardPaths: runtime directory '%1' is not a directory, " + "but a regular file permissions 0600 owned by UID %2 GID %3") + .arg(p).arg(getuid()).arg(getgid()).toLatin1()); + return fallbackXdgRuntimeDir(); + }); + + addRow("environment:broken-symlink", [](QDir &d) -> std::optional<QString> { + QString p = d.filePath("link"); + QFile::link(d.filePath("this-goes-nowhere"), p); + updateRuntimeDir(p); + QTest::ignoreMessage(QtWarningMsg, + QString("QStandardPaths: runtime directory '%1' is not a directory, " + "but a broken symlink") + .arg(p).toLatin1()); + return fallbackXdgRuntimeDir(); + }); + + addRow("environment:symlink-to-dir", [](QDir &d) -> std::optional<QString> { + QString p = d.filePath("link"); + d.mkdir("dir"); + QFile::link(d.filePath("dir"), p); + QFile::setPermissions(p, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); + updateRuntimeDir(p); + QTest::ignoreMessage(QtWarningMsg, + QString("QStandardPaths: runtime directory '%1' is not a directory, " + "but a symbolic link to a directory permissions 0700 owned by UID %2 GID %3") + .arg(p).arg(getuid()).arg(getgid()).toLatin1()); + return fallbackXdgRuntimeDir(); + }); + + addRow("no-environment:non-existing", [](QDir &) -> std::optional<QString> { + clearRuntimeDir(); + return fallbackXdgRuntimeDir(); + }); + + addRow("no-environment:existing", [](QDir &d) -> std::optional<QString> { + clearRuntimeDir(); + QString p = fallbackXdgRuntimeDir(); + d.mkdir(p); // probably has wrong permissions + QFile::setPermissions(p, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); + return p; + }); + + addRow("no-environment:fallback-is-file", [](QDir &) -> std::optional<QString> { + QString p = fallbackXdgRuntimeDir(); + QFile f(p); + if (!f.open(QIODevice::WriteOnly)) + return failedToOpen(f); + f.setPermissions(QFile::ReadOwner | QFile::WriteOwner); + + clearRuntimeDir(); + QTest::ignoreMessage(QtWarningMsg, + QString("QStandardPaths: runtime directory '%1' is not a directory, " + "but a regular file permissions 0600 owned by UID %2 GID %3") + .arg(p).arg(getuid()).arg(getgid()).toLatin1()); + return QString(); + }); + + addRow("environment-and-fallback-are-files", [](QDir &d) -> std::optional<QString> { + QString p = d.filePath("file1"); + QFile f(p); + if (!f.open(QIODevice::WriteOnly)) + return failedToOpen(f); + f.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup); + updateRuntimeDir(p); + QTest::ignoreMessage(QtWarningMsg, + QString("QStandardPaths: runtime directory '%1' is not a directory, " + "but a regular file permissions 0640 owned by UID %2 GID %3") + .arg(p).arg(getuid()).arg(getgid()).toLatin1()); + + f.close(); + f.setFileName(fallbackXdgRuntimeDir()); + if (!f.open(QIODevice::WriteOnly)) + return failedToOpen(f); + f.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup); + QTest::ignoreMessage(QtWarningMsg, + QString("QStandardPaths: runtime directory '%1' is not a directory, " + "but a regular file permissions 0640 owned by UID %2 GID %3") + .arg(f.fileName()).arg(getuid()).arg(getgid()).toLatin1()); + + return QString(); + }); +#endif +} + void tst_qstandardpaths::testCustomRuntimeDirectory() { #if defined(Q_OS_UNIX) @@ -473,63 +743,40 @@ void tst_qstandardpaths::testCustomRuntimeDirectory() #ifdef Q_XDG_PLATFORM struct EnvVarRestorer { - EnvVarRestorer() : origRuntimeDir(qgetenv("XDG_RUNTIME_DIR")) {} - ~EnvVarRestorer() { qputenv("XDG_RUNTIME_DIR", origRuntimeDir.constData()); } - const QByteArray origRuntimeDir; + ~EnvVarRestorer() + { + qputenv("XDG_RUNTIME_DIR", origRuntimeDir); + qputenv("TMPDIR", origTempDir); + } + const QByteArray origRuntimeDir = qgetenv("XDG_RUNTIME_DIR"); + const QByteArray origTempDir = qgetenv("TMPDIR"); }; EnvVarRestorer restorer; - // When $XDG_RUNTIME_DIR points to a directory with wrong ownership, QStandardPaths should warn - QByteArray rootOwnedFileName = "/tmp"; - if (EmulationDetector::isRunningArmOnX86()) { - // Directory "tmp" under toolchain sysroot is detected by qemu and has same uid as current user. - // Try /opt instead, it might not be located in the sysroot. - QFileInfo rootOwnedFile = QFileInfo(QString::fromLatin1(rootOwnedFileName)); - if (rootOwnedFile.ownerId() == ::geteuid()) { - rootOwnedFileName = "/opt"; - } + // set up the environment to point to a place we control + QTemporaryDir tempDir; + QVERIFY2(tempDir.isValid(), qPrintable(tempDir.errorString())); + + QDir d(tempDir.path()); + qputenv("TMPDIR", QFile::encodeName(tempDir.path())); + + QFETCH(RuntimeDirSetup, setup); + std::optional<QString> opt = setup(d); + QVERIFY(opt); + QString expected = *opt; + + QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); + QCOMPARE(runtimeDir, expected); + + if (!runtimeDir.isEmpty()) { + QFileInfo runtimeInfo(runtimeDir); + QVERIFY(runtimeInfo.isDir()); + QVERIFY(!runtimeInfo.isSymLink()); + auto expectedPerms = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner + | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser; + QCOMPARE(QString::number(runtimeInfo.permissions(), 16), + QString::number(expectedPerms, 16)); } - qputenv("XDG_RUNTIME_DIR", QFile::encodeName(rootOwnedFileName)); - - // It's very unlikely that /tmp is 0600 or that we can chmod it - // The call below outputs - // "QStandardPaths: wrong ownership on runtime directory /tmp, 0 instead of $UID" - // but we can't reliably expect that it's owned by uid 0, I think. - const uid_t uid = geteuid(); - QTest::ignoreMessage(QtWarningMsg, - qPrintable(QString::fromLatin1("QStandardPaths: wrong ownership on runtime directory " + rootOwnedFileName + ", 0 instead of %1").arg(uid))); - const QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); - QVERIFY2(runtimeDir.isEmpty(), qPrintable(runtimeDir)); - - // When $XDG_RUNTIME_DIR points to a directory with wrong permissions, QStandardPaths should warn - const QByteArray wrongPermissionFileName = "wrong_permissions"; - QDir::current().mkdir(wrongPermissionFileName); - QFile wrongPermissionFile(wrongPermissionFileName); - const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser; - QVERIFY(wrongPermissionFile.setPermissions(wantedPerms | QFile::ExeGroup)); - - qputenv("XDG_RUNTIME_DIR", wrongPermissionFileName); - QTest::ignoreMessage(QtWarningMsg, - qPrintable(QString::fromLatin1("QStandardPaths: wrong permissions on runtime directory " + wrongPermissionFileName + ", 7710 instead of 7700"))); - const QString wrongPermissionRuntimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); - QVERIFY(wrongPermissionRuntimeDir.isEmpty()); - QDir::current().rmdir(wrongPermissionFileName); - - // When $XDG_RUNTIME_DIR points to a non-existing directory, QStandardPaths should create it first - const QByteArray nonExistingDir = "does_not_exist"; - qputenv("XDG_RUNTIME_DIR", nonExistingDir); - const QString nonExistingRuntimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); - QVERIFY2(!nonExistingRuntimeDir.compare(nonExistingDir), qPrintable(nonExistingRuntimeDir)); - QVERIFY(QDir::current().exists(nonExistingRuntimeDir)); - QDir::current().rmdir(nonExistingRuntimeDir); - - // When $XDG_RUNTIME_DIR points to a file, QStandardPaths should warn - const QString file = QFINDTESTDATA("tst_qstandardpaths.cpp"); - QVERIFY(!file.isEmpty()); - qputenv("XDG_RUNTIME_DIR", QFile::encodeName(file)); - QTest::ignoreMessage(QtWarningMsg, qPrintable(QString::fromLatin1("QStandardPaths: XDG_RUNTIME_DIR points to '%1' which is not a directory").arg(file))); - const QString noRuntimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); - QVERIFY2(noRuntimeDir.isEmpty(), qPrintable(file)); #endif } @@ -591,6 +838,35 @@ void tst_qstandardpaths::testXdgPathCleanup() QVERIFY(!appsDirs.contains("/applications")); QVERIFY(!appsDirs.contains(uncleanGlobalAppDir + "/applications")); QVERIFY(!appsDirs.contains("relative/path/applications")); + + const QString uncleanGlobalConfigDir = "/./" + QFile::encodeName(m_globalConfigDir); + qputenv("XDG_CONFIG_DIRS", QFile::encodeName(uncleanGlobalConfigDir) + "::relative/path"); + const QStringList configDirs = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation); + QVERIFY(!configDirs.contains("relative/path"_L1)); + QVERIFY(!configDirs.contains(""_L1)); + + // Relative paths in XDG_* env vars are ignored + const QString relative("./someRelativeDir"); + + qputenv("XDG_CACHE_HOME", relative.toLatin1()); + const QString cacheDir = cacheLoc(); + QCOMPARE_NE(cacheDir, relative); + + qputenv("XDG_STATE_HOME", relative.toLatin1()); + const QString stateDir = stateLoc(); + QCOMPARE_NE(stateDir, relative); + + qputenv("XDG_DATA_HOME", relative.toLatin1()); + const QString localDataDir = genericDataLoc(); + QCOMPARE_NE(localDataDir, relative); + + qputenv("XDG_CONFIG_HOME", relative.toLatin1()); + const QString localConfig = configLoc(); + QCOMPARE_NE(localConfig, relative); + + qputenv("XDG_RUNTIME_DIR", relative.toLatin1()); + const QString runtimeDir = genericDataLoc(); + QCOMPARE_NE(runtimeDir, relative); #endif } |