1/****************************************************************************
2**
3** Copyright (C) 2020 The Qt Company Ltd.
4** Copyright (C) 2020 Intel Corporation.
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the test suite of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:GPL-EXCEPT$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU
20** General Public License version 3 as published by the Free Software
21** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#include <qstandardpaths.h>
31#include <QtTest/QtTest>
32#include <qdebug.h>
33#include <qfileinfo.h>
34#include <qplatformdefs.h>
35#include <qregularexpression.h>
36#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
37# include <qt_windows.h>
38#endif
39
40#ifdef Q_OS_UNIX
41#include <unistd.h>
42#include <sys/types.h>
43#include <pwd.h>
44#endif
45
46#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_ANDROID)
47#define Q_XDG_PLATFORM
48#endif
49
50// Update this when adding new enum values; update enumNames too
51static const int MaxStandardLocation = QStandardPaths::AppConfigLocation;
52
53class tst_qstandardpaths : public QObject
54{
55 Q_OBJECT
56
57private slots:
58 void initTestCase();
59 void dump();
60 void testDefaultLocations();
61 void testCustomLocations();
62 void enableTestMode();
63 void testLocateAll();
64 void testDataLocation();
65 void testAppConfigLocation();
66 void testFindExecutable_data();
67 void testFindExecutable();
68 void testFindExecutableLinkToDirectory();
69 void testRuntimeDirectory();
70 void testCustomRuntimeDirectory_data();
71 void testCustomRuntimeDirectory();
72 void testAllWritableLocations_data();
73 void testAllWritableLocations();
74 void testCleanPath();
75 void testXdgPathCleanup();
76
77private:
78#ifdef Q_XDG_PLATFORM
79 void setCustomLocations() {
80 m_localConfigDir = m_localConfigTempDir.path();
81 m_globalConfigDir = m_globalConfigTempDir.path();
82 qputenv(varName: "XDG_CONFIG_HOME", value: QFile::encodeName(fileName: m_localConfigDir));
83 qputenv(varName: "XDG_CONFIG_DIRS", value: QFile::encodeName(fileName: m_globalConfigDir));
84 m_localAppDir = m_localAppTempDir.path();
85 m_globalAppDir = m_globalAppTempDir.path();
86 qputenv(varName: "XDG_DATA_HOME", value: QFile::encodeName(fileName: m_localAppDir));
87 qputenv(varName: "XDG_DATA_DIRS", value: QFile::encodeName(fileName: m_globalAppDir));
88 }
89 void setDefaultLocations() {
90 qputenv(varName: "XDG_CONFIG_HOME", value: QByteArray());
91 qputenv(varName: "XDG_CONFIG_DIRS", value: QByteArray());
92 qputenv(varName: "XDG_DATA_HOME", value: QByteArray());
93 qputenv(varName: "XDG_DATA_DIRS", value: QByteArray());
94 }
95#endif
96
97 // Config dirs
98 QString m_localConfigDir;
99 QTemporaryDir m_localConfigTempDir;
100 QString m_globalConfigDir;
101 QTemporaryDir m_globalConfigTempDir;
102
103 // App dirs
104 QString m_localAppDir;
105 QTemporaryDir m_localAppTempDir;
106 QString m_globalAppDir;
107 QTemporaryDir m_globalAppTempDir;
108};
109
110static const char * const enumNames[MaxStandardLocation + 1 - int(QStandardPaths::DesktopLocation)] = {
111 "DesktopLocation",
112 "DocumentsLocation",
113 "FontsLocation",
114 "ApplicationsLocation",
115 "MusicLocation",
116 "MoviesLocation",
117 "PicturesLocation",
118 "TempLocation",
119 "HomeLocation",
120 "DataLocation",
121 "CacheLocation",
122 "GenericDataLocation",
123 "RuntimeLocation",
124 "ConfigLocation",
125 "DownloadLocation",
126 "GenericCacheLocation",
127 "GenericConfigLocation",
128 "AppDataLocation",
129 "AppConfigLocation"
130};
131
132void tst_qstandardpaths::initTestCase()
133{
134#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
135 // Disable WOW64 redirection, see testFindExecutable()
136 if (QSysInfo::buildCpuArchitecture() != QSysInfo::currentCpuArchitecture()) {
137 void *oldMode;
138 const bool disabledDisableWow64FsRedirection = Wow64DisableWow64FsRedirection(&oldMode) == TRUE;
139 if (!disabledDisableWow64FsRedirection)
140 qErrnoWarning("Wow64DisableWow64FsRedirection() failed");
141 QVERIFY(disabledDisableWow64FsRedirection);
142 }
143#endif // Q_OS_WIN && !Q_OS_WINRT
144 QVERIFY2(m_localConfigTempDir.isValid(), qPrintable(m_localConfigTempDir.errorString()));
145 QVERIFY2(m_globalConfigTempDir.isValid(), qPrintable(m_globalConfigTempDir.errorString()));
146 QVERIFY2(m_localAppTempDir.isValid(), qPrintable(m_localAppTempDir.errorString()));
147 QVERIFY2(m_globalAppTempDir.isValid(), qPrintable(m_globalAppTempDir.errorString()));
148}
149
150void tst_qstandardpaths::dump()
151{
152#ifdef Q_XDG_PLATFORM
153 setDefaultLocations();
154#endif
155 // This is not a test. It merely dumps the output.
156 for (int i = QStandardPaths::DesktopLocation; i <= MaxStandardLocation; ++i) {
157 QStandardPaths::StandardLocation s = QStandardPaths::StandardLocation(i);
158 qDebug() << enumNames[i]
159 << QStandardPaths::writableLocation(type: s)
160 << QStandardPaths::standardLocations(type: s);
161 }
162}
163
164void tst_qstandardpaths::testDefaultLocations()
165{
166#ifdef Q_XDG_PLATFORM
167 setDefaultLocations();
168
169 const QString expectedConfHome = QDir::homePath() + QString::fromLatin1(str: "/.config");
170 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation), expectedConfHome);
171 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation), expectedConfHome);
172 const QStringList confDirs = QStandardPaths::standardLocations(type: QStandardPaths::ConfigLocation);
173 QCOMPARE(confDirs.count(), 2);
174 QVERIFY(confDirs.contains(expectedConfHome));
175 QCOMPARE(QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation), confDirs);
176
177 const QStringList genericDataDirs = QStandardPaths::standardLocations(type: QStandardPaths::GenericDataLocation);
178 QCOMPARE(genericDataDirs.count(), 3);
179 const QString expectedDataHome = QDir::homePath() + QString::fromLatin1(str: "/.local/share");
180 QCOMPARE(genericDataDirs.at(0), expectedDataHome);
181 QCOMPARE(genericDataDirs.at(1), QString::fromLatin1("/usr/local/share"));
182 QCOMPARE(genericDataDirs.at(2), QString::fromLatin1("/usr/share"));
183#endif
184}
185
186#ifdef Q_XDG_PLATFORM
187static void createTestFile(const QString &fileName)
188{
189 QFile file(fileName);
190 QVERIFY(file.open(QIODevice::WriteOnly));
191 QVERIFY(file.write("Hello"));
192}
193#endif
194
195void tst_qstandardpaths::testCustomLocations()
196{
197#ifdef Q_XDG_PLATFORM
198 setCustomLocations();
199
200 // test writableLocation()
201 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation), m_localConfigDir);
202 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation), m_localConfigDir);
203
204 // test locate()
205 const QString thisFileName = QString::fromLatin1(str: "aFile");
206 createTestFile(fileName: m_localConfigDir + QLatin1Char('/') + thisFileName);
207 const QString thisFile = QStandardPaths::locate(type: QStandardPaths::ConfigLocation, fileName: thisFileName);
208 QVERIFY(!thisFile.isEmpty());
209 QVERIFY(thisFile.endsWith(thisFileName));
210
211 const QString subdir = QString::fromLatin1(str: "subdir");
212 const QString subdirPath = m_localConfigDir + QLatin1Char('/') + subdir;
213 QVERIFY(QDir().mkdir(subdirPath));
214 const QString dir = QStandardPaths::locate(type: QStandardPaths::ConfigLocation, fileName: subdir, options: QStandardPaths::LocateDirectory);
215 QCOMPARE(dir, subdirPath);
216 const QString thisDirAsFile = QStandardPaths::locate(type: QStandardPaths::ConfigLocation, fileName: subdir);
217 QVERIFY(thisDirAsFile.isEmpty()); // not a file
218
219 const QStringList dirs = QStandardPaths::standardLocations(type: QStandardPaths::ConfigLocation);
220 QCOMPARE(dirs, QStringList() << m_localConfigDir << m_globalConfigDir);
221#endif
222}
223
224void tst_qstandardpaths::enableTestMode()
225{
226 QVERIFY(!QStandardPaths::isTestModeEnabled());
227 QStandardPaths::setTestModeEnabled(true);
228 QVERIFY(QStandardPaths::isTestModeEnabled());
229
230#ifdef Q_XDG_PLATFORM
231 setCustomLocations(); // for the global config dir
232 const QString qttestDir = QDir::homePath() + QLatin1String("/.qttest");
233
234 // ConfigLocation
235 const QString configDir = qttestDir + QLatin1String("/config");
236 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation), configDir);
237 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation), configDir);
238 const QStringList confDirs = QStandardPaths::standardLocations(type: QStandardPaths::ConfigLocation);
239 QCOMPARE(confDirs, QStringList() << configDir << m_globalConfigDir);
240
241 // GenericDataLocation
242 const QString dataDir = qttestDir + QLatin1String("/share");
243 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), dataDir);
244 const QStringList gdDirs = QStandardPaths::standardLocations(type: QStandardPaths::GenericDataLocation);
245 QCOMPARE(gdDirs, QStringList() << dataDir << m_globalAppDir);
246
247 // GenericCacheLocation
248 const QString cacheDir = qttestDir + QLatin1String("/cache");
249 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation), cacheDir);
250 const QStringList cacheDirs = QStandardPaths::standardLocations(type: QStandardPaths::GenericCacheLocation);
251 QCOMPARE(cacheDirs, QStringList() << cacheDir);
252#endif
253
254 // On all platforms, we want to ensure that the writableLocation is different in test mode and real mode.
255 // Check this for locations where test programs typically write. Not desktop, download, music etc...
256 typedef QHash<QStandardPaths::StandardLocation, QString> LocationHash;
257 LocationHash testLocations;
258 testLocations.insert(akey: QStandardPaths::AppDataLocation, avalue: QStandardPaths::writableLocation(type: QStandardPaths::AppDataLocation));
259 testLocations.insert(akey: QStandardPaths::AppLocalDataLocation, avalue: QStandardPaths::writableLocation(type: QStandardPaths::AppLocalDataLocation));
260 testLocations.insert(akey: QStandardPaths::GenericDataLocation, avalue: QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation));
261 testLocations.insert(akey: QStandardPaths::ConfigLocation, avalue: QStandardPaths::writableLocation(type: QStandardPaths::ConfigLocation));
262 testLocations.insert(akey: QStandardPaths::GenericConfigLocation, avalue: QStandardPaths::writableLocation(type: QStandardPaths::GenericConfigLocation));
263 testLocations.insert(akey: QStandardPaths::CacheLocation, avalue: QStandardPaths::writableLocation(type: QStandardPaths::CacheLocation));
264 testLocations.insert(akey: QStandardPaths::GenericCacheLocation, avalue: QStandardPaths::writableLocation(type: QStandardPaths::GenericCacheLocation));
265 // On Windows, what should "Program Files" become, in test mode?
266 //testLocations.insert(QStandardPaths::ApplicationsLocation, QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation));
267
268 QStandardPaths::setTestModeEnabled(false);
269
270 for (LocationHash::const_iterator it = testLocations.constBegin(); it != testLocations.constEnd(); ++it)
271 QVERIFY2(QStandardPaths::writableLocation(it.key()) != it.value(), qPrintable(it.value()));
272
273 // Check that this is also true with no env vars set
274#ifdef Q_XDG_PLATFORM
275 setDefaultLocations();
276 for (LocationHash::const_iterator it = testLocations.constBegin(); it != testLocations.constEnd(); ++it)
277 QVERIFY2(QStandardPaths::writableLocation(it.key()) != it.value(), qPrintable(it.value()));
278#endif
279}
280
281void tst_qstandardpaths::testLocateAll()
282{
283#ifdef Q_XDG_PLATFORM
284 setCustomLocations();
285 const QStringList appsDirs = QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, fileName: "applications", options: QStandardPaths::LocateDirectory);
286 QCOMPARE(appsDirs.count(), 0); // they don't exist yet
287 const QStringList expectedAppsDirs = QStringList() << m_localAppDir + QLatin1String("/applications")
288 << m_globalAppDir + QLatin1String("/applications");
289 QDir().mkdir(dirName: expectedAppsDirs.at(i: 0));
290 QDir().mkdir(dirName: expectedAppsDirs.at(i: 1));
291 const QStringList appsDirs2 = QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, fileName: "applications", options: QStandardPaths::LocateDirectory);
292 QCOMPARE(appsDirs2, expectedAppsDirs);
293
294 const QStringList appsDirs3 = QStandardPaths::standardLocations(type: QStandardPaths::ApplicationsLocation);
295 QCOMPARE(appsDirs3, expectedAppsDirs);
296
297 const QString thisFileName = QString::fromLatin1(str: "aFile");
298 const QStringList expectedFiles = QStringList() << m_localConfigDir + QLatin1Char('/') + thisFileName
299 << m_globalConfigDir + QLatin1Char('/') + thisFileName;
300 createTestFile(fileName: expectedFiles.at(i: 0));
301 createTestFile(fileName: expectedFiles.at(i: 1));
302 const QStringList allFiles = QStandardPaths::locateAll(type: QStandardPaths::ConfigLocation, fileName: thisFileName);
303 QCOMPARE(allFiles, expectedFiles);
304#endif
305}
306
307void tst_qstandardpaths::testDataLocation()
308{
309 // On all platforms, DataLocation should be GenericDataLocation / organization name / app name
310 // This allows one app to access the data of another app.
311 // Android and WinRT are an exception to this case, owing to the fact that
312 // applications are sandboxed.
313#if !defined(Q_OS_ANDROID) && !defined(Q_OS_WINRT)
314 const QString base = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation);
315 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation), base + "/tst_qstandardpaths");
316 QCoreApplication::instance()->setOrganizationName("Qt");
317 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation), base + "/Qt/tst_qstandardpaths");
318 QCoreApplication::instance()->setApplicationName("QtTest");
319 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation), base + "/Qt/QtTest");
320#endif
321
322#ifdef Q_XDG_PLATFORM
323 setDefaultLocations();
324 const QString expectedAppDataDir = QDir::homePath() + QString::fromLatin1(str: "/.local/share/Qt/QtTest");
325 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation), expectedAppDataDir);
326 const QStringList appDataDirs = QStandardPaths::standardLocations(type: QStandardPaths::AppLocalDataLocation);
327 QCOMPARE(appDataDirs.count(), 3);
328 QCOMPARE(appDataDirs.at(0), expectedAppDataDir);
329 QCOMPARE(appDataDirs.at(1), QString::fromLatin1("/usr/local/share/Qt/QtTest"));
330 QCOMPARE(appDataDirs.at(2), QString::fromLatin1("/usr/share/Qt/QtTest"));
331#endif
332
333 // reset for other tests
334 QCoreApplication::setOrganizationName(QString());
335 QCoreApplication::setApplicationName(QString());
336}
337
338void tst_qstandardpaths::testAppConfigLocation()
339{
340 // On all platforms where applications are not sandboxed,
341 // AppConfigLocation should be GenericConfigLocation / organization name / app name
342#if !defined(Q_OS_ANDROID) && !defined(Q_OS_WINRT)
343 const QString base = QStandardPaths::writableLocation(type: QStandardPaths::GenericConfigLocation);
344 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation), base + "/tst_qstandardpaths");
345 QCoreApplication::setOrganizationName("Qt");
346 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation), base + "/Qt/tst_qstandardpaths");
347 QCoreApplication::setApplicationName("QtTest");
348 QCOMPARE(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation), base + "/Qt/QtTest");
349 // reset for other tests
350 QCoreApplication::setOrganizationName(QString());
351 QCoreApplication::setApplicationName(QString());
352#endif
353}
354
355#ifndef Q_OS_WIN
356// Find "sh" on Unix.
357// It may exist twice, in /bin/sh and /usr/bin/sh, in that case use the PATH order.
358static inline QFileInfo findSh()
359{
360 QLatin1String sh("/sh");
361 QByteArray pEnv = qgetenv(varName: "PATH");
362 const QLatin1Char pathSep(':');
363 const QStringList rawPaths = QString::fromLocal8Bit(str: pEnv.constData()).split(sep: pathSep, behavior: Qt::SkipEmptyParts);
364 foreach (const QString &path, rawPaths) {
365 if (QFile::exists(fileName: path + sh))
366 return path + sh;
367 }
368 return QFileInfo();
369}
370#endif
371
372void tst_qstandardpaths::testFindExecutable_data()
373{
374#ifdef SKIP_FINDEXECUTABLE
375 // Test needs to be skipped or Q_ASSERT below will cancel the test
376 // and report FAIL regardless of BLACKLIST contents
377 QSKIP("QTBUG-64404");
378#endif
379
380 QTest::addColumn<QString>(name: "directory");
381 QTest::addColumn<QString>(name: "needle");
382 QTest::addColumn<QString>(name: "expected");
383#ifdef Q_OS_WIN
384# ifndef Q_OS_WINRT
385 const QFileInfo cmdFi = QFileInfo(QDir::cleanPath(QString::fromLocal8Bit(qgetenv("COMSPEC"))));
386 const QString cmdPath = cmdFi.absoluteFilePath();
387
388 Q_ASSERT(cmdFi.exists());
389 QTest::newRow("win-cmd")
390 << QString() << QString::fromLatin1("cmd.eXe") << cmdPath;
391 QTest::newRow("win-full-path")
392 << QString() << cmdPath << cmdPath;
393 QTest::newRow("win-relative-path")
394 << cmdFi.absolutePath() << QString::fromLatin1("./cmd.exe") << cmdPath;
395 QTest::newRow("win-cmd-nosuffix")
396 << QString() << QString::fromLatin1("cmd") << cmdPath;
397
398 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8) {
399 // The logo executable on Windows 8 is perfectly suited for testing that the
400 // suffix mechanism is not thrown off by dots in the name.
401 // Note: Requires disabling WOW64 redirection, see initTestCase()
402 const QString logo = QLatin1String("microsoft.windows.softwarelogo.showdesktop");
403 const QString logoPath = cmdFi.absolutePath() + QLatin1Char('/') + logo + QLatin1String(".exe");
404 QTest::newRow("win8-logo")
405 << QString() << (logo + QLatin1String(".exe")) << logoPath;
406 QTest::newRow("win8-logo-nosuffix")
407 << QString() << logo << logoPath;
408 }
409# endif // Q_OS_WINRT
410#else
411 const QFileInfo shFi = findSh();
412 Q_ASSERT(shFi.exists());
413 const QString shPath = shFi.absoluteFilePath();
414 QTest::newRow(dataTag: "unix-sh")
415 << QString() << QString::fromLatin1(str: "sh") << shPath;
416 QTest::newRow(dataTag: "unix-sh-fullpath")
417 << QString() << shPath << shPath;
418 QTest::newRow(dataTag: "unix-sh-relativepath")
419 << QString(shFi.absolutePath()) << QString::fromLatin1(str: "./sh") << shPath;
420#endif
421 QTest::newRow(dataTag: "idontexist")
422 << QString() << QString::fromLatin1(str: "idontexist") << QString();
423 QTest::newRow(dataTag: "empty")
424 << QString() << QString() << QString();
425}
426
427void tst_qstandardpaths::testFindExecutable()
428{
429 QFETCH(QString, directory);
430 QFETCH(QString, needle);
431 QFETCH(QString, expected);
432 const bool changeDirectory = !directory.isEmpty();
433 const QString currentDirectory = QDir::currentPath();
434 if (changeDirectory)
435 QVERIFY(QDir::setCurrent(directory));
436 const QString result = QStandardPaths::findExecutable(executableName: needle);
437 if (changeDirectory)
438 QVERIFY(QDir::setCurrent(currentDirectory));
439
440#ifdef Q_OS_WIN
441 const Qt::CaseSensitivity sensitivity = Qt::CaseInsensitive;
442#else
443 const Qt::CaseSensitivity sensitivity = Qt::CaseSensitive;
444#endif
445 QVERIFY2(!result.compare(expected, sensitivity),
446 qPrintable(QString::fromLatin1("Actual: '%1', Expected: '%2'").arg(result, expected)));
447}
448
449void tst_qstandardpaths::testFindExecutableLinkToDirectory()
450{
451 // WinRT has no link support
452#ifndef Q_OS_WINRT
453 // link to directory
454 const QString target = QDir::tempPath() + QDir::separator() + QLatin1String("link.lnk");
455 QFile::remove(fileName: target);
456 QFile appFile(QCoreApplication::applicationDirPath());
457 QVERIFY(appFile.link(target));
458 QVERIFY(QStandardPaths::findExecutable(target).isEmpty());
459 QFile::remove(fileName: target);
460#endif
461}
462
463using RuntimeDirSetup = QString (*)(QDir &);
464Q_DECLARE_METATYPE(RuntimeDirSetup);
465
466void tst_qstandardpaths::testRuntimeDirectory()
467{
468#ifdef Q_XDG_PLATFORM
469 const QString runtimeDir = QStandardPaths::writableLocation(type: QStandardPaths::RuntimeLocation);
470 QVERIFY(!runtimeDir.isEmpty());
471#endif
472}
473
474#ifdef Q_XDG_PLATFORM
475static QString fallbackXdgRuntimeDir()
476{
477 static QString username = [] {
478 struct passwd *pw = getpwuid(uid: geteuid());
479 return QString::fromLocal8Bit(str: pw->pw_name);
480 }();
481
482 // QDir::temp() might change from call to call
483 return QDir::temp().filePath(fileName: "runtime-" + username);
484}
485#endif
486
487static QString updateRuntimeDir(const QString &path)
488{
489 qputenv(varName: "XDG_RUNTIME_DIR", value: QFile::encodeName(fileName: path));
490 return path;
491}
492
493static void clearRuntimeDir()
494{
495 qunsetenv(varName: "XDG_RUNTIME_DIR");
496#ifdef Q_XDG_PLATFORM
497#ifndef Q_OS_WASM
498 QTest::ignoreMessage(type: QtWarningMsg,
499 qPrintable("QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '"
500 + fallbackXdgRuntimeDir() + '\''));
501#endif
502#endif
503}
504
505void tst_qstandardpaths::testCustomRuntimeDirectory_data()
506{
507#if defined(Q_XDG_PLATFORM)
508 QTest::addColumn<RuntimeDirSetup>(name: "setup");
509 auto addRow = [](const char *name, RuntimeDirSetup f) {
510 QTest::newRow(dataTag: name) << f;
511 };
512
513
514# if defined(Q_OS_UNIX)
515 if (::getuid() == 0)
516 QSKIP("Running this test as root doesn't make sense");
517# endif
518
519 addRow("environment:non-existing", [](QDir &d) {
520 return updateRuntimeDir(path: d.filePath(fileName: "runtime"));
521 });
522
523 addRow("environment:existing", [](QDir &d) {
524 QString p = d.filePath(fileName: "runtime");
525 d.mkdir(dirName: "runtime");
526 QFile::setPermissions(filename: p, permissionSpec: QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
527 return updateRuntimeDir(path: p);
528 });
529
530 addRow("environment-to-existing-wrong-perm", [](QDir &d) {
531 QString p = d.filePath(fileName: "runtime");
532 d.mkdir(dirName: "runtime");
533 QFile::setPermissions(filename: p, permissionSpec: QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
534 QFile::ExeGroup | QFile::ExeOther);
535 updateRuntimeDir(path: p);
536 QTest::ignoreMessage(type: QtWarningMsg,
537 message: QString("QStandardPaths: wrong permissions on runtime directory %1, "
538 "0711 instead of 0700")
539 .arg(a: p).toLatin1());
540 return fallbackXdgRuntimeDir();
541 });
542
543 addRow("environment:wrong-owner", [](QDir &) {
544 QT_STATBUF st;
545 QT_STAT(file: "/", buf: &st);
546
547 updateRuntimeDir(path: "/");
548 QTest::ignoreMessage(type: QtWarningMsg,
549 message: QString("QStandardPaths: runtime directory '/' is not owned by UID "
550 "%1, but a directory permissions %2 owned by UID %3 GID %4")
551 .arg(a: getuid())
552 .arg(a: st.st_mode & 07777, fieldWidth: 4, base: 8, fillChar: QChar('0'))
553 .arg(a: st.st_uid)
554 .arg(a: st.st_gid).toLatin1());
555 return fallbackXdgRuntimeDir();
556 });
557
558 addRow("environment:file", [](QDir &d) {
559 QString p = d.filePath(fileName: "file");
560 QFile f(p);
561 f.open(flags: QIODevice::WriteOnly);
562 f.setPermissions(QFile::ReadOwner | QFile::WriteOwner);
563
564 updateRuntimeDir(path: p);
565 QTest::ignoreMessage(type: QtWarningMsg,
566 message: QString("QStandardPaths: runtime directory '%1' is not a directory, "
567 "but a regular file permissions 0600 owned by UID %2 GID %3")
568 .arg(a: p).arg(a: getuid()).arg(a: getgid()).toLatin1());
569 return fallbackXdgRuntimeDir();
570 });
571
572 addRow("environment:broken-symlink", [](QDir &d) {
573 QString p = d.filePath(fileName: "link");
574 QFile::link(oldname: d.filePath(fileName: "this-goes-nowhere"), newName: p);
575 updateRuntimeDir(path: p);
576 QTest::ignoreMessage(type: QtWarningMsg,
577 message: QString("QStandardPaths: runtime directory '%1' is not a directory, "
578 "but a broken symlink")
579 .arg(a: p).toLatin1());
580 return fallbackXdgRuntimeDir();
581 });
582
583 addRow("environment:symlink-to-dir", [](QDir &d) {
584 QString p = d.filePath(fileName: "link");
585 d.mkdir(dirName: "dir");
586 QFile::link(oldname: d.filePath(fileName: "dir"), newName: p);
587 QFile::setPermissions(filename: p, permissionSpec: QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
588 updateRuntimeDir(path: p);
589 QTest::ignoreMessage(type: QtWarningMsg,
590 message: QString("QStandardPaths: runtime directory '%1' is not a directory, "
591 "but a symbolic link to a directory permissions 0700 owned by UID %2 GID %3")
592 .arg(a: p).arg(a: getuid()).arg(a: getgid()).toLatin1());
593 return fallbackXdgRuntimeDir();
594 });
595
596 addRow("no-environment:non-existing", [](QDir &) {
597 clearRuntimeDir();
598 return fallbackXdgRuntimeDir();
599 });
600
601 addRow("no-environment:existing", [](QDir &d) {
602 clearRuntimeDir();
603 QString p = fallbackXdgRuntimeDir();
604 d.mkdir(dirName: p); // probably has wrong permissions
605 QFile::setPermissions(filename: p, permissionSpec: QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
606 return p;
607 });
608
609 addRow("no-environment:fallback-is-file", [](QDir &) {
610 QString p = fallbackXdgRuntimeDir();
611 QFile f(p);
612 f.open(flags: QIODevice::WriteOnly);
613 f.setPermissions(QFile::ReadOwner | QFile::WriteOwner);
614
615 clearRuntimeDir();
616 QTest::ignoreMessage(type: QtWarningMsg,
617 message: QString("QStandardPaths: runtime directory '%1' is not a directory, "
618 "but a regular file permissions 0600 owned by UID %2 GID %3")
619 .arg(a: p).arg(a: getuid()).arg(a: getgid()).toLatin1());
620 return QString();
621 });
622
623 addRow("environment-and-fallback-are-files", [](QDir &d) {
624 QString p = d.filePath(fileName: "file1");
625 QFile f(p);
626 f.open(flags: QIODevice::WriteOnly);
627 f.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup);
628 updateRuntimeDir(path: p);
629 QTest::ignoreMessage(type: QtWarningMsg,
630 message: QString("QStandardPaths: runtime directory '%1' is not a directory, "
631 "but a regular file permissions 0640 owned by UID %2 GID %3")
632 .arg(a: p).arg(a: getuid()).arg(a: getgid()).toLatin1());
633
634 f.close();
635 f.setFileName(fallbackXdgRuntimeDir());
636 f.open(flags: QIODevice::WriteOnly);
637 f.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup);
638 QTest::ignoreMessage(type: QtWarningMsg,
639 message: QString("QStandardPaths: runtime directory '%1' is not a directory, "
640 "but a regular file permissions 0640 owned by UID %2 GID %3")
641 .arg(a: f.fileName()).arg(a: getuid()).arg(a: getgid()).toLatin1());
642
643 return QString();
644 });
645#endif
646}
647
648void tst_qstandardpaths::testCustomRuntimeDirectory()
649{
650#if defined(Q_OS_UNIX)
651 if (::getuid() == 0)
652 QSKIP("Running this test as root doesn't make sense");
653#endif
654
655#ifdef Q_XDG_PLATFORM
656 struct EnvVarRestorer
657 {
658 ~EnvVarRestorer()
659 {
660 qputenv(varName: "XDG_RUNTIME_DIR", value: origRuntimeDir);
661 qputenv(varName: "TMPDIR", value: origTempDir);
662 }
663 const QByteArray origRuntimeDir = qgetenv(varName: "XDG_RUNTIME_DIR");
664 const QByteArray origTempDir = qgetenv(varName: "TMPDIR");
665 };
666 EnvVarRestorer restorer;
667
668 // set up the environment to point to a place we control
669 QTemporaryDir tempDir;
670 QVERIFY2(tempDir.isValid(), qPrintable(tempDir.errorString()));
671
672 QDir d(tempDir.path());
673 qputenv(varName: "TMPDIR", value: QFile::encodeName(fileName: tempDir.path()));
674
675 QFETCH(RuntimeDirSetup, setup);
676 QString expected = setup(d);
677
678 QString runtimeDir = QStandardPaths::writableLocation(type: QStandardPaths::RuntimeLocation);
679 QCOMPARE(runtimeDir, expected);
680
681 if (!runtimeDir.isEmpty()) {
682 QFileInfo runtimeInfo(runtimeDir);
683 QVERIFY(runtimeInfo.isDir());
684 QVERIFY(!runtimeInfo.isSymLink());
685 auto expectedPerms = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner
686 | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser;
687 QCOMPARE(QString::number(runtimeInfo.permissions(), 16),
688 QString::number(expectedPerms, 16));
689 }
690#endif
691}
692
693Q_DECLARE_METATYPE(QStandardPaths::StandardLocation)
694void tst_qstandardpaths::testAllWritableLocations_data()
695{
696 QTest::addColumn<QStandardPaths::StandardLocation>(name: "location");
697 QTest::newRow(dataTag: "DesktopLocation") << QStandardPaths::DesktopLocation;
698 QTest::newRow(dataTag: "DocumentsLocation") << QStandardPaths::DocumentsLocation;
699 QTest::newRow(dataTag: "FontsLocation") << QStandardPaths::FontsLocation;
700 QTest::newRow(dataTag: "ApplicationsLocation") << QStandardPaths::ApplicationsLocation;
701 QTest::newRow(dataTag: "MusicLocation") << QStandardPaths::MusicLocation;
702 QTest::newRow(dataTag: "MoviesLocation") << QStandardPaths::MoviesLocation;
703 QTest::newRow(dataTag: "PicturesLocation") << QStandardPaths::PicturesLocation;
704 QTest::newRow(dataTag: "TempLocation") << QStandardPaths::TempLocation;
705 QTest::newRow(dataTag: "HomeLocation") << QStandardPaths::HomeLocation;
706 QTest::newRow(dataTag: "AppLocalDataLocation") << QStandardPaths::AppLocalDataLocation;
707 QTest::newRow(dataTag: "DownloadLocation") << QStandardPaths::DownloadLocation;
708}
709
710void tst_qstandardpaths::testAllWritableLocations()
711{
712 QFETCH(QStandardPaths::StandardLocation, location);
713 QStandardPaths::writableLocation(type: location);
714 QStandardPaths::displayName(type: location);
715
716 // Currently all desktop locations return their writable location
717 // with "Unix-style" paths (i.e. they use a slash, not backslash).
718 QString loc = QStandardPaths::writableLocation(type: location);
719 if (loc.size() > 1) // workaround for unlikely case of locations that return '/'
720 QCOMPARE(loc.endsWith(QLatin1Char('/')), false);
721 QVERIFY(loc.isEmpty() || loc.contains(QLatin1Char('/')));
722 QVERIFY(!loc.contains(QLatin1Char('\\')));
723}
724
725void tst_qstandardpaths::testCleanPath()
726{
727#if QT_CONFIG(regularexpression)
728 const QRegularExpression filter(QStringLiteral("\\\\"));
729 QVERIFY(filter.isValid());
730 for (int i = 0; i <= QStandardPaths::GenericCacheLocation; ++i) {
731 const QStringList paths = QStandardPaths::standardLocations(type: QStandardPaths::StandardLocation(i));
732 QVERIFY2(paths.filter(filter).isEmpty(),
733 qPrintable(QString::fromLatin1("Backslash found in %1 %2")
734 .arg(i).arg(paths.join(QLatin1Char(',')))));
735 }
736#else
737 QSKIP("regularexpression feature disabled");
738#endif
739}
740
741void tst_qstandardpaths::testXdgPathCleanup()
742{
743#ifdef Q_XDG_PLATFORM
744 setCustomLocations();
745 const QString uncleanGlobalAppDir = "/./" + QFile::encodeName(fileName: m_globalAppDir);
746 qputenv(varName: "XDG_DATA_DIRS", value: QFile::encodeName(fileName: uncleanGlobalAppDir) + "::relative/path");
747 const QStringList appsDirs = QStandardPaths::standardLocations(type: QStandardPaths::ApplicationsLocation);
748 QVERIFY(!appsDirs.contains("/applications"));
749 QVERIFY(!appsDirs.contains(uncleanGlobalAppDir + "/applications"));
750 QVERIFY(!appsDirs.contains("relative/path/applications"));
751#endif
752}
753
754QTEST_MAIN(tst_qstandardpaths)
755
756#include "tst_qstandardpaths.moc"
757

source code of qtbase/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp