1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | |
30 | #include <QtTest/QtTest> |
31 | #include <qcoreapplication.h> |
32 | #include <qstring.h> |
33 | #include <qtemporarydir.h> |
34 | #include <qfile.h> |
35 | #include <qdir.h> |
36 | #include <qset.h> |
37 | #include <qtextcodec.h> |
38 | #include <QtTest/private/qtesthelpers_p.h> |
39 | #ifdef Q_OS_WIN |
40 | # include <windows.h> |
41 | #endif |
42 | #ifdef Q_OS_UNIX // for geteuid() |
43 | # include <sys/types.h> |
44 | # include <unistd.h> |
45 | #endif |
46 | #include "emulationdetector.h" |
47 | |
48 | class tst_QTemporaryDir : public QObject |
49 | { |
50 | Q_OBJECT |
51 | public: |
52 | public slots: |
53 | void initTestCase(); |
54 | void cleanupTestCase(); |
55 | |
56 | private slots: |
57 | void construction(); |
58 | void fileTemplate(); |
59 | void fileTemplate_data(); |
60 | void getSetCheck(); |
61 | void fileName(); |
62 | void filePath_data(); |
63 | void filePath(); |
64 | void autoRemove(); |
65 | void nonWritableCurrentDir(); |
66 | void openOnRootDrives(); |
67 | void stressTest(); |
68 | void rename(); |
69 | |
70 | void QTBUG_4796_data(); |
71 | void QTBUG_4796(); |
72 | |
73 | void QTBUG43352_failedSetPermissions(); |
74 | |
75 | private: |
76 | QString m_previousCurrent; |
77 | }; |
78 | |
79 | void tst_QTemporaryDir::initTestCase() |
80 | { |
81 | m_previousCurrent = QDir::currentPath(); |
82 | QDir::setCurrent(QDir::tempPath()); |
83 | QVERIFY(QDir("test-XXXXXX" ).exists() || QDir().mkdir("test-XXXXXX" )); |
84 | QCoreApplication::setApplicationName("tst_qtemporarydir" ); |
85 | } |
86 | |
87 | void tst_QTemporaryDir::cleanupTestCase() |
88 | { |
89 | QVERIFY(QDir().rmdir("test-XXXXXX" )); |
90 | |
91 | QDir::setCurrent(m_previousCurrent); |
92 | } |
93 | |
94 | void tst_QTemporaryDir::construction() |
95 | { |
96 | QTemporaryDir dir; |
97 | QString tmp = QDir::tempPath(); |
98 | QCOMPARE(dir.path().left(tmp.size()), tmp); |
99 | QVERIFY(dir.path().contains("tst_qtemporarydir" )); |
100 | QVERIFY(QFileInfo(dir.path()).isDir()); |
101 | QCOMPARE(dir.errorString(), QString()); |
102 | } |
103 | |
104 | // Testing get/set functions |
105 | void tst_QTemporaryDir::getSetCheck() |
106 | { |
107 | QTemporaryDir obj1; |
108 | // bool QTemporaryDir::autoRemove() |
109 | // void QTemporaryDir::setAutoRemove(bool) |
110 | obj1.setAutoRemove(false); |
111 | QCOMPARE(false, obj1.autoRemove()); |
112 | obj1.setAutoRemove(true); |
113 | QCOMPARE(true, obj1.autoRemove()); |
114 | } |
115 | |
116 | static QString hanTestText() |
117 | { |
118 | QString text; |
119 | text += QChar(0x65B0); |
120 | text += QChar(0x5E10); |
121 | text += QChar(0x6237); |
122 | return text; |
123 | } |
124 | |
125 | static QString umlautTestText() |
126 | { |
127 | QString text; |
128 | text += QChar(0xc4); |
129 | text += QChar(0xe4); |
130 | text += QChar(0xd6); |
131 | text += QChar(0xf6); |
132 | text += QChar(0xdc); |
133 | text += QChar(0xfc); |
134 | text += QChar(0xdf); |
135 | return text; |
136 | } |
137 | |
138 | void tst_QTemporaryDir::fileTemplate_data() |
139 | { |
140 | QTest::addColumn<QString>(name: "constructorTemplate" ); |
141 | QTest::addColumn<QString>(name: "prefix" ); |
142 | QTest::addColumn<QString>(name: "suffix" ); |
143 | |
144 | QTest::newRow(dataTag: "default" ) << "" << "tst_qtemporarydir-" << "" ; |
145 | |
146 | QTest::newRow(dataTag: "xxx-suffix" ) << "qt_XXXXXXxxx" << "qt_" << "xxx" ; |
147 | QTest::newRow(dataTag: "xXx-suffix" ) << "qt_XXXXXXxXx" << "qt_" << "xXx" ; |
148 | QTest::newRow(dataTag: "no-suffix" ) << "qt_XXXXXX" << "qt_" << "" ; |
149 | QTest::newRow(dataTag: "10X" ) << "qt_XXXXXXXXXX" << "qt_" << "" ; |
150 | QTest::newRow(dataTag: "4Xsuffix" ) << "qt_XXXXXX_XXXX" << "qt_" << "_XXXX" ; |
151 | QTest::newRow(dataTag: "4Xprefix" ) << "qt_XXXX" << "qt_XXXX" << "" ; |
152 | QTest::newRow(dataTag: "5Xprefix" ) << "qt_XXXXX" << "qt_XXXXX" << "" ; |
153 | if (QTestPrivate::canHandleUnicodeFileNames()) { |
154 | // Test Umlauts (contained in Latin1) |
155 | QString prefix = "qt_" + umlautTestText(); |
156 | QTest::newRow(dataTag: "Umlauts" ) << (prefix + "XXXXXX" ) << prefix << "" ; |
157 | // test non-Latin1 |
158 | prefix = "qt_" + hanTestText(); |
159 | QTest::newRow(dataTag: "Chinese" ) << (prefix + "XXXXXX" + umlautTestText()) << prefix << umlautTestText(); |
160 | } |
161 | } |
162 | |
163 | void tst_QTemporaryDir::fileTemplate() |
164 | { |
165 | QFETCH(QString, constructorTemplate); |
166 | QFETCH(QString, prefix); |
167 | QFETCH(QString, suffix); |
168 | |
169 | QTemporaryDir tempDir(constructorTemplate); |
170 | |
171 | QVERIFY(tempDir.isValid()); |
172 | |
173 | QString dirName = QDir(tempDir.path()).dirName(); |
174 | if (prefix.length()) { |
175 | QCOMPARE(dirName.left(prefix.length()), prefix); |
176 | QCOMPARE(dirName.right(suffix.length()), suffix); |
177 | } |
178 | } |
179 | |
180 | |
181 | /* |
182 | This tests whether the temporary dir really gets placed in QDir::tempPath |
183 | */ |
184 | void tst_QTemporaryDir::fileName() |
185 | { |
186 | // Get QDir::tempPath and make an absolute path. |
187 | QString tempPath = QDir::tempPath(); |
188 | QString absoluteTempPath = QDir(tempPath).absolutePath(); |
189 | QTemporaryDir dir; |
190 | dir.setAutoRemove(true); |
191 | QString fileName = dir.path(); |
192 | QVERIFY2(fileName.contains("/tst_qtemporarydir-" ), qPrintable(fileName)); |
193 | QVERIFY(QDir(fileName).exists()); |
194 | // Get path to the temp dir, without the file name. |
195 | QString absoluteFilePath = QFileInfo(fileName).absolutePath(); |
196 | #if defined(Q_OS_WIN) |
197 | absoluteFilePath = absoluteFilePath.toLower(); |
198 | absoluteTempPath = absoluteTempPath.toLower(); |
199 | #endif |
200 | QCOMPARE(absoluteFilePath, absoluteTempPath); |
201 | } |
202 | |
203 | void tst_QTemporaryDir::filePath_data() |
204 | { |
205 | QTest::addColumn<QString>(name: "templatePath" ); |
206 | QTest::addColumn<QString>(name: "fileName" ); |
207 | |
208 | QTest::newRow(dataTag: "0" ) << QString() << "/tmpfile" ; |
209 | QTest::newRow(dataTag: "1" ) << QString() << "tmpfile" ; |
210 | QTest::newRow(dataTag: "2" ) << "XXXXX" << "tmpfile" ; |
211 | QTest::newRow(dataTag: "3" ) << "YYYYY" << "subdir/file" ; |
212 | } |
213 | |
214 | void tst_QTemporaryDir::filePath() |
215 | { |
216 | QFETCH(QString, templatePath); |
217 | QFETCH(QString, fileName); |
218 | |
219 | QTemporaryDir dir(templatePath); |
220 | const QString filePath = dir.filePath(fileName); |
221 | const QString expectedFilePath = QDir::isAbsolutePath(path: fileName) ? |
222 | QString() : dir.path() + QLatin1Char('/') + fileName; |
223 | QCOMPARE(filePath, expectedFilePath); |
224 | } |
225 | |
226 | void tst_QTemporaryDir::autoRemove() |
227 | { |
228 | // Test auto remove |
229 | QString dirName; |
230 | { |
231 | QTemporaryDir dir("tempXXXXXX" ); |
232 | dir.setAutoRemove(true); |
233 | QVERIFY(dir.isValid()); |
234 | dirName = dir.path(); |
235 | } |
236 | #ifdef Q_OS_WIN |
237 | // Windows seems unreliable here: sometimes it says the directory still exists, |
238 | // immediately after we deleted it. |
239 | QTRY_VERIFY(!QDir(dirName).exists()); |
240 | #else |
241 | QVERIFY(!QDir(dirName).exists()); |
242 | #endif |
243 | |
244 | // Test if disabling auto remove works. |
245 | { |
246 | QTemporaryDir dir("tempXXXXXX" ); |
247 | dir.setAutoRemove(false); |
248 | QVERIFY(dir.isValid()); |
249 | dirName = dir.path(); |
250 | } |
251 | QVERIFY(QDir(dirName).exists()); |
252 | QVERIFY(QDir().rmdir(dirName)); |
253 | QVERIFY(!QDir(dirName).exists()); |
254 | |
255 | // Do not explicitly call setAutoRemove (tests if it really is the default as documented) |
256 | { |
257 | QTemporaryDir dir("tempXXXXXX" ); |
258 | QVERIFY(dir.isValid()); |
259 | dirName = dir.path(); |
260 | } |
261 | #ifdef Q_OS_WIN |
262 | QTRY_VERIFY(!QDir(dirName).exists()); |
263 | #else |
264 | QVERIFY(!QDir(dirName).exists()); |
265 | #endif |
266 | |
267 | // Test autoremove with files and subdirs in the temp dir |
268 | { |
269 | QTemporaryDir tempDir("tempXXXXXX" ); |
270 | QVERIFY(tempDir.isValid()); |
271 | dirName = tempDir.path(); |
272 | QDir dir(dirName); |
273 | QVERIFY(dir.mkdir(QString::fromLatin1("dir1" ))); |
274 | QVERIFY(dir.mkdir(QString::fromLatin1("dir2" ))); |
275 | QVERIFY(dir.mkdir(QString::fromLatin1("dir2/nested" ))); |
276 | QFile file(dirName + "/dir1/file" ); |
277 | QVERIFY(file.open(QIODevice::WriteOnly)); |
278 | QCOMPARE(file.write("Hello" ), 5LL); |
279 | file.close(); |
280 | QVERIFY(file.setPermissions(QFile::ReadUser)); |
281 | } |
282 | #ifdef Q_OS_WIN |
283 | QTRY_VERIFY(!QDir(dirName).exists()); |
284 | #else |
285 | QVERIFY(!QDir(dirName).exists()); |
286 | #endif |
287 | } |
288 | |
289 | void tst_QTemporaryDir::nonWritableCurrentDir() |
290 | { |
291 | #ifdef Q_OS_UNIX |
292 | |
293 | # if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) |
294 | const char nonWritableDir[] = "/data" ; |
295 | # else |
296 | const char nonWritableDir[] = "/home" ; |
297 | # endif |
298 | |
299 | if (::geteuid() == 0) |
300 | QSKIP("not valid running this test as root" ); |
301 | |
302 | struct ChdirOnReturn |
303 | { |
304 | ChdirOnReturn(const QString& d) : dir(d) {} |
305 | ~ChdirOnReturn() { |
306 | QDir::setCurrent(dir); |
307 | } |
308 | QString dir; |
309 | }; |
310 | |
311 | const QFileInfo nonWritableDirFi = QFileInfo(QLatin1String(nonWritableDir)); |
312 | QVERIFY(nonWritableDirFi.isDir()); |
313 | |
314 | if (EmulationDetector::isRunningArmOnX86()) { |
315 | if (nonWritableDirFi.ownerId() == ::geteuid()) { |
316 | QSKIP("Sysroot directories are owned by the current user" ); |
317 | } |
318 | } |
319 | |
320 | QVERIFY(!nonWritableDirFi.isWritable()); |
321 | |
322 | ChdirOnReturn cor(QDir::currentPath()); |
323 | QVERIFY(QDir::setCurrent(nonWritableDirFi.absoluteFilePath())); |
324 | // QTemporaryDir("tempXXXXXX") is probably a bad idea in any app |
325 | // where the current dir could anything... |
326 | QTemporaryDir dir("tempXXXXXX" ); |
327 | dir.setAutoRemove(true); |
328 | QVERIFY(!dir.isValid()); |
329 | QVERIFY(!dir.errorString().isEmpty()); |
330 | QVERIFY(dir.path().isEmpty()); |
331 | #endif |
332 | } |
333 | |
334 | void tst_QTemporaryDir::openOnRootDrives() |
335 | { |
336 | #if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) |
337 | unsigned int lastErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); |
338 | #endif |
339 | // If it's possible to create a file in the root directory, it |
340 | // must be possible to create a temp dir there too. |
341 | foreach (const QFileInfo &driveInfo, QDir::drives()) { |
342 | QFile testFile(driveInfo.filePath() + "XXXXXX" ); |
343 | if (testFile.open(flags: QIODevice::ReadWrite)) { |
344 | testFile.remove(); |
345 | QTemporaryDir dir(driveInfo.filePath() + "XXXXXX" ); |
346 | dir.setAutoRemove(true); |
347 | QVERIFY(dir.isValid()); |
348 | } |
349 | } |
350 | #if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) |
351 | SetErrorMode(lastErrorMode); |
352 | #endif |
353 | } |
354 | |
355 | void tst_QTemporaryDir::stressTest() |
356 | { |
357 | const int iterations = 1000; |
358 | QTemporaryDir rootDir; |
359 | QVERIFY(rootDir.isValid()); |
360 | |
361 | QSet<QString> names; |
362 | const QString pattern = rootDir.path() + QStringLiteral("/XXXXXX" ); |
363 | for (int i = 0; i < iterations; ++i) { |
364 | QTemporaryDir dir(pattern); |
365 | dir.setAutoRemove(false); |
366 | QVERIFY2(dir.isValid(), |
367 | qPrintable(QString::fromLatin1("Failed to create #%1 under %2: %3." ) |
368 | .arg(i) |
369 | .arg(QDir::toNativeSeparators(pattern)) |
370 | .arg(dir.errorString()))); |
371 | QVERIFY(!names.contains(dir.path())); |
372 | names.insert(value: dir.path()); |
373 | } |
374 | } |
375 | |
376 | void tst_QTemporaryDir::rename() |
377 | { |
378 | // This test checks what happens if the temporary dir is renamed. |
379 | // Then the autodelete feature can't possibly find it. |
380 | |
381 | QDir dir; |
382 | QVERIFY(!dir.exists("temporary-dir.renamed" )); |
383 | |
384 | QString tempname; |
385 | { |
386 | QTemporaryDir tempDir(dir.filePath(fileName: "temporary-dir.XXXXXX" )); |
387 | |
388 | QVERIFY(tempDir.isValid()); |
389 | tempname = tempDir.path(); |
390 | |
391 | QVERIFY(QDir().rename(tempname, "temporary-dir.renamed" )); |
392 | QVERIFY(!QDir(tempname).exists()); |
393 | dir.setPath("temporary-dir.renamed" ); |
394 | QCOMPARE(dir.path(), QString("temporary-dir.renamed" )); |
395 | QVERIFY(dir.exists()); |
396 | } |
397 | |
398 | // Auto-delete couldn't find it |
399 | QVERIFY(dir.exists()); |
400 | // Clean up by hand |
401 | QVERIFY(dir.removeRecursively()); |
402 | QVERIFY(!dir.exists()); |
403 | } |
404 | |
405 | void tst_QTemporaryDir::QTBUG_4796_data() |
406 | { |
407 | QTest::addColumn<QString>(name: "prefix" ); |
408 | QTest::addColumn<QString>(name: "suffix" ); |
409 | QTest::addColumn<bool>(name: "openResult" ); |
410 | |
411 | QString unicode = QString::fromUtf8(str: "\xc3\xa5\xc3\xa6\xc3\xb8" ); |
412 | |
413 | QTest::newRow(dataTag: "<empty>" ) << QString() << QString() << true; |
414 | QTest::newRow(dataTag: "." ) << QString("." ) << QString() << true; |
415 | QTest::newRow(dataTag: ".." ) << QString(".." ) << QString() << true; |
416 | QTest::newRow(dataTag: "blaXXXXXX" ) << QString("bla" ) << QString() << true; |
417 | QTest::newRow(dataTag: "does-not-exist/qt_temp.XXXXXX" ) << QString("does-not-exist/qt_temp" ) << QString() << false; |
418 | QTest::newRow(dataTag: "XXXXXX<unicode>" ) << QString() << unicode << true; |
419 | QTest::newRow(dataTag: "<unicode>XXXXXX" ) << unicode << QString() << true; |
420 | } |
421 | |
422 | void tst_QTemporaryDir::QTBUG_4796() // unicode support |
423 | { |
424 | QVERIFY(QDir("test-XXXXXX" ).exists()); |
425 | |
426 | struct CleanOnReturn |
427 | { |
428 | ~CleanOnReturn() |
429 | { |
430 | foreach (const QString &tempName, tempNames) |
431 | QVERIFY(QDir(tempName).removeRecursively()); |
432 | } |
433 | |
434 | void reset() |
435 | { |
436 | tempNames.clear(); |
437 | } |
438 | |
439 | QStringList tempNames; |
440 | }; |
441 | |
442 | CleanOnReturn cleaner; |
443 | |
444 | QFETCH(QString, prefix); |
445 | QFETCH(QString, suffix); |
446 | QFETCH(bool, openResult); |
447 | |
448 | { |
449 | QString fileTemplate1 = prefix + QString("XX" ) + suffix; |
450 | QString fileTemplate2 = prefix + QString("XXXX" ) + suffix; |
451 | QString fileTemplate3 = prefix + QString("XXXXXX" ) + suffix; |
452 | QString fileTemplate4 = prefix + QString("XXXXXXXX" ) + suffix; |
453 | |
454 | QTemporaryDir dir1(fileTemplate1); |
455 | QTemporaryDir dir2(fileTemplate2); |
456 | QTemporaryDir dir3(fileTemplate3); |
457 | QTemporaryDir dir4(fileTemplate4); |
458 | QTemporaryDir dir5("test-XXXXXX/" + fileTemplate1); |
459 | QTemporaryDir dir6("test-XXXXXX/" + fileTemplate3); |
460 | |
461 | QCOMPARE(dir1.isValid(), openResult); |
462 | QCOMPARE(dir2.isValid(), openResult); |
463 | QCOMPARE(dir3.isValid(), openResult); |
464 | QCOMPARE(dir4.isValid(), openResult); |
465 | QCOMPARE(dir5.isValid(), openResult); |
466 | QCOMPARE(dir6.isValid(), openResult); |
467 | |
468 | // make sure the dir exists under the *correct* name |
469 | if (openResult) { |
470 | cleaner.tempNames << dir1.path() |
471 | << dir2.path() |
472 | << dir3.path() |
473 | << dir4.path() |
474 | << dir5.path() |
475 | << dir6.path(); |
476 | |
477 | QDir currentDir; |
478 | QString fileName1 = currentDir.relativeFilePath(fileName: dir1.path()); |
479 | QString fileName2 = currentDir.relativeFilePath(fileName: dir2.path()); |
480 | QString fileName3 = currentDir.relativeFilePath(fileName: dir3.path()); |
481 | QString fileName4 = currentDir.relativeFilePath(fileName: dir4.path()); |
482 | QString fileName5 = currentDir.relativeFilePath(fileName: dir5.path()); |
483 | QString fileName6 = currentDir.relativeFilePath(fileName: dir6.path()); |
484 | |
485 | QVERIFY(fileName1.startsWith(prefix)); |
486 | QVERIFY(fileName2.startsWith(prefix)); |
487 | QVERIFY(fileName5.startsWith("test-XXXXXX/" + prefix)); |
488 | QVERIFY(fileName6.startsWith("test-XXXXXX/" + prefix)); |
489 | |
490 | if (!prefix.isEmpty()) { |
491 | QVERIFY(fileName3.startsWith(prefix)); |
492 | QVERIFY(fileName4.startsWith(prefix)); |
493 | } |
494 | } |
495 | } |
496 | |
497 | #ifdef Q_OS_WIN |
498 | QTest::qWait(20); |
499 | #endif |
500 | foreach (const QString &tempName, cleaner.tempNames) |
501 | QVERIFY2(!QDir(tempName).exists(), qPrintable(tempName)); |
502 | |
503 | cleaner.reset(); |
504 | } |
505 | |
506 | void tst_QTemporaryDir::QTBUG43352_failedSetPermissions() |
507 | { |
508 | QString path = QStandardPaths::writableLocation(type: QStandardPaths::DownloadLocation) + QStringLiteral("/" ); |
509 | int count = QDir(path).entryList().size(); |
510 | |
511 | { |
512 | QTemporaryDir dir(path); |
513 | } |
514 | |
515 | QCOMPARE(QDir(path).entryList().size(), count); |
516 | } |
517 | |
518 | QTEST_MAIN(tst_QTemporaryDir) |
519 | #include "tst_qtemporarydir.moc" |
520 | |