diff --git a/src/corelib/io/qstandardpaths.cpp b/src/corelib/io/qstandardpaths.cpp index 55f824cdebd..c6103b3f2ff 100644 --- a/src/corelib/io/qstandardpaths.cpp +++ b/src/corelib/io/qstandardpaths.cpp @@ -309,6 +309,27 @@ QString QStandardPaths::displayName(StandardLocation type) } #endif +/*! + \fn void QStandardPaths::enableTestMode(bool testMode) + + Enables "test mode" in QStandardPaths, which changes writable locations + to point to test directories, in order to prevent auto tests from reading from + or writing to the current user's configuration. + + This affects the locations into which test programs might write files: + GenericDataLocation, DataLocation, ConfigLocation, + GenericCacheLocation, CacheLocation. + Other locations are not affected. + + On Unix, XDG_DATA_HOME is set to ~/.qttest/share, XDG_CONFIG_HOME is + set to ~/.qttest/config, and XDG_CACHE_HOME is set to ~/.qttest/cache. + + On Mac, data goes to "~/.qttest/Application Support", cache goes to + ~/.qttest/Cache, and config goes to ~/.qttest/Preferences. + + On Windows, everything goes to a "qttest" directory under Application Data. +*/ + QT_END_NAMESPACE #endif // QT_NO_STANDARDPATHS diff --git a/src/corelib/io/qstandardpaths.h b/src/corelib/io/qstandardpaths.h index e647f46f186..e393809431d 100644 --- a/src/corelib/io/qstandardpaths.h +++ b/src/corelib/io/qstandardpaths.h @@ -91,6 +91,8 @@ public: static QString findExecutable(const QString &executableName, const QStringList &paths = QStringList()); + static void enableTestMode(bool testMode); + private: // prevent construction QStandardPaths(); diff --git a/src/corelib/io/qstandardpaths_json.cpp b/src/corelib/io/qstandardpaths_json.cpp index 7d7a0a9f289..c7cb858f0f6 100644 --- a/src/corelib/io/qstandardpaths_json.cpp +++ b/src/corelib/io/qstandardpaths_json.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #ifndef QT_NO_STANDARDPATHS @@ -62,6 +63,23 @@ public: Q_GLOBAL_STATIC(QStandardPathsPrivate, configCache); +static bool qsp_testMode = false; + +void QStandardPaths::enableTestMode(bool testMode) +{ + qsp_testMode = testMode; +} + +static void appendOrganizationAndApp(QString &path) +{ + const QString org = QCoreApplication::organizationName(); + if (!org.isEmpty()) + path += QLatin1Char('/') + org; + const QString appName = QCoreApplication::applicationName(); + if (!appName.isEmpty()) + path += QLatin1Char('/') + appName; +} + QString QStandardPaths::writableLocation(StandardLocation type) { switch (type) { @@ -73,6 +91,30 @@ QString QStandardPaths::writableLocation(StandardLocation type) break; } + if (qsp_testMode) { + const QString qttestDir = QDir::homePath() + QLatin1String("/.qttest"); + QString path; + switch (type) { + case GenericDataLocation: + case DataLocation: + path = qttestDir + QLatin1String("/share"); + if (type == DataLocation) + appendOrganizationAndApp(path); + return path; + case GenericCacheLocation: + case CacheLocation: + path = qttestDir + QLatin1String("/cache"); + if (type == CacheLocation) + appendOrganizationAndApp(path); + return path; + case ConfigLocation: + return qttestDir + QLatin1String("/config"); + default: + break; + } + } + + QJsonObject * localConfigObject = configCache()->object.loadAcquire(); if (localConfigObject == 0) { QString configHome = QFile::decodeName(qgetenv("PATH_CONFIG_HOME")); diff --git a/src/corelib/io/qstandardpaths_mac.cpp b/src/corelib/io/qstandardpaths_mac.cpp index 2890ead48ab..53dfdaa3923 100644 --- a/src/corelib/io/qstandardpaths_mac.cpp +++ b/src/corelib/io/qstandardpaths_mac.cpp @@ -90,6 +90,13 @@ OSType translateLocation(QStandardPaths::StandardLocation type) } } +static bool qsp_testMode = false; + +void QStandardPaths::enableTestMode(bool testMode) +{ + qsp_testMode = testMode; +} + /* Constructs a full unicode path from a FSRef. */ @@ -101,6 +108,16 @@ static QString getFullPath(const FSRef &ref) return QString(); } +static void appendOrganizationAndApp(QString &path) +{ + const QString org = QCoreApplication::organizationName(); + if (!org.isEmpty()) + path += QLatin1Char('/') + org; + const QString appName = QCoreApplication::applicationName(); + if (!appName.isEmpty()) + path += QLatin1Char('/') + appName; +} + static QString macLocation(QStandardPaths::StandardLocation type, short domain) { // http://developer.apple.com/documentation/Carbon/Reference/Folder_Manager/Reference/reference.html @@ -111,17 +128,36 @@ static QString macLocation(QStandardPaths::StandardLocation type, short domain) QString path = getFullPath(ref); - if (type == QStandardPaths::DataLocation || type == QStandardPaths::CacheLocation) { - if (!QCoreApplication::organizationName().isEmpty()) - path += QLatin1Char('/') + QCoreApplication::organizationName(); - if (!QCoreApplication::applicationName().isEmpty()) - path += QLatin1Char('/') + QCoreApplication::applicationName(); - } - return path; + if (type == QStandardPaths::DataLocation || type == QStandardPaths::CacheLocation) + appendOrganizationAndApp(path); + return path; } QString QStandardPaths::writableLocation(StandardLocation type) { + if (qsp_testMode) { + const QString qttestDir = QDir::homePath() + QLatin1String("/.qttest"); + QString path; + switch (type) { + case GenericDataLocation: + case DataLocation: + path = qttestDir + QLatin1String("/Application Support"); + if (type == DataLocation) + appendOrganizationAndApp(path); + return path; + case GenericCacheLocation: + case CacheLocation: + path = qttestDir + QLatin1String("/Cache"); + if (type == CacheLocation) + appendOrganizationAndApp(path); + return path; + case ConfigLocation: + return qttestDir + QLatin1String("/Preferences"); + default: + break; + } + } + switch (type) { case HomeLocation: return QDir::homePath(); diff --git a/src/corelib/io/qstandardpaths_unix.cpp b/src/corelib/io/qstandardpaths_unix.cpp index 1a2ae96edbb..3ccac09990e 100644 --- a/src/corelib/io/qstandardpaths_unix.cpp +++ b/src/corelib/io/qstandardpaths_unix.cpp @@ -63,6 +63,13 @@ static void appendOrganizationAndApp(QString &path) path += QLatin1Char('/') + appName; } +static bool qsp_testMode = false; + +void QStandardPaths::enableTestMode(bool testMode) +{ + qsp_testMode = testMode; +} + QString QStandardPaths::writableLocation(StandardLocation type) { switch (type) { @@ -75,6 +82,8 @@ QString QStandardPaths::writableLocation(StandardLocation type) { // http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html QString xdgCacheHome = QFile::decodeName(qgetenv("XDG_CACHE_HOME")); + if (qsp_testMode) + xdgCacheHome = QDir::homePath() + QLatin1String("/.qttest/cache"); if (xdgCacheHome.isEmpty()) xdgCacheHome = QDir::homePath() + QLatin1String("/.cache"); if (type == QStandardPaths::CacheLocation) @@ -85,6 +94,8 @@ QString QStandardPaths::writableLocation(StandardLocation type) case GenericDataLocation: { QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME")); + if (qsp_testMode) + xdgDataHome = QDir::homePath() + QLatin1String("/.qttest/share"); if (xdgDataHome.isEmpty()) xdgDataHome = QDir::homePath() + QLatin1String("/.local/share"); if (type == QStandardPaths::DataLocation) @@ -95,6 +106,8 @@ QString QStandardPaths::writableLocation(StandardLocation type) { // http://standards.freedesktop.org/basedir-spec/latest/ QString xdgConfigHome = QFile::decodeName(qgetenv("XDG_CONFIG_HOME")); + if (qsp_testMode) + xdgConfigHome = QDir::homePath() + QLatin1String("/.qttest/config"); if (xdgConfigHome.isEmpty()) xdgConfigHome = QDir::homePath() + QLatin1String("/.config"); return xdgConfigHome; @@ -140,7 +153,7 @@ QString QStandardPaths::writableLocation(StandardLocation type) if (xdgConfigHome.isEmpty()) xdgConfigHome = QDir::homePath() + QLatin1String("/.config"); QFile file(xdgConfigHome + QLatin1String("/user-dirs.dirs")); - if (file.open(QIODevice::ReadOnly)) { + if (!qsp_testMode && file.open(QIODevice::ReadOnly)) { QHash lines; QTextStream stream(&file); // Only look for lines like: XDG_DESKTOP_DIR="$HOME/Desktop" diff --git a/src/corelib/io/qstandardpaths_win.cpp b/src/corelib/io/qstandardpaths_win.cpp index 8bd32eb1d47..a2c53a4b4d1 100644 --- a/src/corelib/io/qstandardpaths_win.cpp +++ b/src/corelib/io/qstandardpaths_win.cpp @@ -85,6 +85,13 @@ static QString convertCharArray(const wchar_t *path) return QDir::fromNativeSeparators(QString::fromWCharArray(path)); } +static bool qsp_testMode = false; + +void QStandardPaths::enableTestMode(bool testMode) +{ + qsp_testMode = testMode; +} + QString QStandardPaths::writableLocation(StandardLocation type) { QString result; @@ -105,6 +112,8 @@ QString QStandardPaths::writableLocation(StandardLocation type) if (SHGetSpecialFolderPath(0, path, CSIDL_LOCAL_APPDATA, FALSE)) #endif result = convertCharArray(path); + if (qsp_testMode) + result += QLatin1String("/qttest"); if (type != GenericDataLocation) { if (!QCoreApplication::organizationName().isEmpty()) result += QLatin1Char('/') + QCoreApplication::organizationName(); diff --git a/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp b/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp index a6eabbbed66..a389efa5ca8 100644 --- a/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp +++ b/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp @@ -60,6 +60,7 @@ class tst_qstandardpaths : public QObject private slots: void testDefaultLocations(); void testCustomLocations(); + void enableTestMode(); void testLocateAll(); void testDataLocation(); void testFindExecutable(); @@ -69,6 +70,7 @@ private slots: void testAllWritableLocations(); private: +#ifdef Q_XDG_PLATFORM void setCustomLocations() { m_localConfigDir = m_localConfigTempDir.path(); m_globalConfigDir = m_globalConfigTempDir.path(); @@ -80,13 +82,12 @@ private: qputenv("XDG_DATA_DIRS", QFile::encodeName(m_globalAppDir)); } void setDefaultLocations() { -#ifdef Q_XDG_PLATFORM qputenv("XDG_CONFIG_HOME", QByteArray()); qputenv("XDG_CONFIG_DIRS", QByteArray()); qputenv("XDG_DATA_HOME", QByteArray()); qputenv("XDG_DATA_DIRS", QByteArray()); -#endif } +#endif // Config dirs QString m_localConfigDir; @@ -156,6 +157,58 @@ void tst_qstandardpaths::testCustomLocations() #endif } +void tst_qstandardpaths::enableTestMode() +{ + QStandardPaths::enableTestMode(true); + +#ifdef Q_XDG_PLATFORM + setCustomLocations(); // for the global config dir + const QString qttestDir = QDir::homePath() + QLatin1String("/.qttest"); + + // ConfigLocation + const QString configDir = qttestDir + QLatin1String("/config"); + QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation), configDir); + const QStringList confDirs = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation); + QCOMPARE(confDirs, QStringList() << configDir << m_globalConfigDir); + + // GenericDataLocation + const QString dataDir = qttestDir + QLatin1String("/share"); + QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::DataLocation), dataDir); + const QStringList gdDirs = QStandardPaths::standardLocations(QStandardPaths::DataLocation); + QCOMPARE(gdDirs, QStringList() << dataDir << m_globalAppDir); + + // CacheLocation + const QString cacheDir = qttestDir + QLatin1String("/cache"); + QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::CacheLocation), cacheDir); + const QStringList cacheDirs = QStandardPaths::standardLocations(QStandardPaths::CacheLocation); + QCOMPARE(cacheDirs, QStringList() << cacheDir); +#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 LocationHash; + LocationHash testLocations; + testLocations.insert(QStandardPaths::DataLocation, QStandardPaths::writableLocation(QStandardPaths::DataLocation)); + testLocations.insert(QStandardPaths::GenericDataLocation, QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)); + testLocations.insert(QStandardPaths::ConfigLocation, QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)); + testLocations.insert(QStandardPaths::CacheLocation, QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + testLocations.insert(QStandardPaths::GenericCacheLocation, QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation)); + // On Windows, what should "Program Files" become, in test mode? + //testLocations.insert(QStandardPaths::ApplicationsLocation, QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation)); + + QStandardPaths::enableTestMode(false); + + for (LocationHash::const_iterator it = testLocations.constBegin(); it != testLocations.constEnd(); ++it) + QVERIFY2(QStandardPaths::writableLocation(it.key()) != it.value(), qPrintable(it.value())); + + // Check that this is also true with no env vars set +#ifdef Q_XDG_PLATFORM + setDefaultLocations(); + for (LocationHash::const_iterator it = testLocations.constBegin(); it != testLocations.constEnd(); ++it) + QVERIFY2(QStandardPaths::writableLocation(it.key()) != it.value(), qPrintable(it.value())); +#endif +} + void tst_qstandardpaths::testLocateAll() { #ifdef Q_XDG_PLATFORM