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 |
51 | static const int MaxStandardLocation = QStandardPaths::AppConfigLocation; |
52 | |
53 | class tst_qstandardpaths : public QObject |
54 | { |
55 | Q_OBJECT |
56 | |
57 | private 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 | |
77 | private: |
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 | |
110 | static 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 | |
132 | void 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 | |
150 | void 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 | |
164 | void 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 |
187 | static 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 | |
195 | void 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 | |
224 | void 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 | |
281 | void 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 | |
307 | void 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 | |
338 | void 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. |
358 | static 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 | |
372 | void 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 | |
427 | void 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 | |
449 | void 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 | |
463 | using RuntimeDirSetup = QString (*)(QDir &); |
464 | Q_DECLARE_METATYPE(RuntimeDirSetup); |
465 | |
466 | void 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 |
475 | static 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 | |
487 | static QString updateRuntimeDir(const QString &path) |
488 | { |
489 | qputenv(varName: "XDG_RUNTIME_DIR" , value: QFile::encodeName(fileName: path)); |
490 | return path; |
491 | } |
492 | |
493 | static 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 | |
505 | void 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 | |
648 | void 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 | |
693 | Q_DECLARE_METATYPE(QStandardPaths::StandardLocation) |
694 | void 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 | |
710 | void 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 | |
725 | void 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 | |
741 | void 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 | |
754 | QTEST_MAIN(tst_qstandardpaths) |
755 | |
756 | #include "tst_qstandardpaths.moc" |
757 | |