|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the test suite of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include <QTest> |
|
43 |
|
44 #include <QtAlgorithms> |
|
45 #include <QFile> |
|
46 #include <QFileInfo> |
|
47 #include <qplatformdefs.h> |
|
48 |
|
49 #include <QDebug> |
|
50 |
|
51 #include <cstdlib> |
|
52 #include <cstdio> |
|
53 |
|
54 #ifdef Q_OS_WIN |
|
55 |
|
56 #include <windows.h> |
|
57 |
|
58 #ifndef Q_OS_WINCE |
|
59 #include <io.h> |
|
60 #endif |
|
61 |
|
62 #ifndef FSCTL_SET_SPARSE |
|
63 // MinGW doesn't define this. |
|
64 #define FSCTL_SET_SPARSE (0x900C4) |
|
65 #endif |
|
66 |
|
67 #endif // Q_OS_WIN |
|
68 |
|
69 class tst_LargeFile |
|
70 : public QObject |
|
71 { |
|
72 Q_OBJECT |
|
73 |
|
74 public: |
|
75 tst_LargeFile() |
|
76 : blockSize(1 << 12) |
|
77 , maxSizeBits() |
|
78 , fd_(-1) |
|
79 , stream_(0) |
|
80 { |
|
81 #if defined(QT_LARGEFILE_SUPPORT) && !defined(Q_OS_MAC) |
|
82 maxSizeBits = 36; // 64 GiB |
|
83 #elif defined(Q_OS_MAC) |
|
84 // HFS+ does not support sparse files, so we limit file size for the test |
|
85 // on Mac OS. |
|
86 maxSizeBits = 32; // 4 GiB |
|
87 #else |
|
88 maxSizeBits = 24; // 16 MiB |
|
89 #endif |
|
90 } |
|
91 |
|
92 private: |
|
93 void sparseFileData(); |
|
94 QByteArray const &getDataBlock(int index, qint64 position); |
|
95 |
|
96 private slots: |
|
97 // The LargeFile test case was designed to be run in order as a single unit |
|
98 |
|
99 void initTestCase(); |
|
100 void cleanupTestCase(); |
|
101 |
|
102 void init(); |
|
103 void cleanup(); |
|
104 |
|
105 // Create and fill large file |
|
106 void createSparseFile(); |
|
107 void fillFileSparsely(); |
|
108 void closeSparseFile(); |
|
109 |
|
110 // Verify file was created |
|
111 void fileCreated(); |
|
112 |
|
113 // Positioning in large files |
|
114 void filePositioning(); |
|
115 void fdPositioning(); |
|
116 void streamPositioning(); |
|
117 |
|
118 // Read data from file |
|
119 void openFileForReading(); |
|
120 void readFile(); |
|
121 |
|
122 // Map/unmap large file |
|
123 void mapFile(); |
|
124 void mapOffsetOverflow(); |
|
125 |
|
126 void closeFile() { largeFile.close(); } |
|
127 |
|
128 // Test data |
|
129 void fillFileSparsely_data() { sparseFileData(); } |
|
130 void filePositioning_data() { sparseFileData(); } |
|
131 void fdPositioning_data() { sparseFileData(); } |
|
132 void streamPositioning_data() { sparseFileData(); } |
|
133 void readFile_data() { sparseFileData(); } |
|
134 void mapFile_data() { sparseFileData(); } |
|
135 |
|
136 private: |
|
137 const int blockSize; |
|
138 int maxSizeBits; |
|
139 |
|
140 QFile largeFile; |
|
141 |
|
142 QVector<QByteArray> generatedBlocks; |
|
143 |
|
144 int fd_; |
|
145 FILE *stream_; |
|
146 }; |
|
147 |
|
148 /* |
|
149 Convenience function to hide reinterpret_cast when copying a POD directly |
|
150 into a QByteArray. |
|
151 */ |
|
152 template <class T> |
|
153 static inline void appendRaw(QByteArray &array, T data) |
|
154 { |
|
155 array.append(reinterpret_cast<char *>(&data), sizeof(T)); |
|
156 } |
|
157 |
|
158 /* |
|
159 Pad array with filler up to size. On return, array.size() returns size. |
|
160 */ |
|
161 static inline void topUpWith(QByteArray &array, QByteArray filler, int size) |
|
162 { |
|
163 Q_ASSERT(filler.size() > 0); |
|
164 |
|
165 for (int i = (size - array.size()) / filler.size(); i > 0; --i) |
|
166 array.append(filler); |
|
167 |
|
168 if (array.size() < size) { |
|
169 Q_ASSERT(size - array.size() < filler.size()); |
|
170 array.append(filler.left(size - array.size())); |
|
171 } |
|
172 } |
|
173 |
|
174 /* |
|
175 Generate a unique data block containing identifiable data. Unaligned, |
|
176 overlapping and partial blocks should not compare equal. |
|
177 */ |
|
178 static inline QByteArray generateDataBlock(int blockSize, QString text, qint64 userBits = -1) |
|
179 { |
|
180 QByteArray block; |
|
181 block.reserve(blockSize); |
|
182 |
|
183 // Use of counter and randomBits means content of block will be dependent |
|
184 // on the generation order. For (file-)systems that do not support sparse |
|
185 // files, these can be removed so the test file can be reused and doesn't |
|
186 // have to be generated for every run. |
|
187 |
|
188 static qint64 counter = 0; |
|
189 |
|
190 qint64 randomBits = ((qint64)qrand() << 32) |
|
191 | ((qint64)qrand() & 0x00000000ffffffff); |
|
192 |
|
193 appendRaw(block, randomBits); |
|
194 appendRaw(block, userBits); |
|
195 appendRaw(block, counter); |
|
196 appendRaw(block, (qint32)0xdeadbeef); |
|
197 appendRaw(block, blockSize); |
|
198 |
|
199 QByteArray userContent = text.toUtf8(); |
|
200 appendRaw(block, userContent.size()); |
|
201 block.append(userContent); |
|
202 appendRaw(block, (qint64)0); |
|
203 |
|
204 // size, so far |
|
205 appendRaw(block, block.size()); |
|
206 |
|
207 QByteArray filler("0123456789"); |
|
208 block.append(filler.right(10 - block.size() % 10)); |
|
209 topUpWith(block, filler, blockSize - 2 * sizeof(qint64)); |
|
210 |
|
211 appendRaw(block, counter); |
|
212 appendRaw(block, userBits); |
|
213 appendRaw(block, randomBits); |
|
214 |
|
215 Q_ASSERT( block.size() >= blockSize ); |
|
216 block.resize(blockSize); |
|
217 |
|
218 ++counter; |
|
219 return block; |
|
220 } |
|
221 |
|
222 /* |
|
223 Generates data blocks the first time they are requested. Keeps copies for reuse. |
|
224 */ |
|
225 QByteArray const &tst_LargeFile::getDataBlock(int index, qint64 position) |
|
226 { |
|
227 if (index >= generatedBlocks.size()) |
|
228 generatedBlocks.resize(index + 1); |
|
229 |
|
230 if (generatedBlocks[index].isNull()) { |
|
231 QString text = QString("Current %1-byte block (index = %2) " |
|
232 "starts %3 bytes into the file '%4'.") |
|
233 .arg(blockSize) |
|
234 .arg(index) |
|
235 .arg(position) |
|
236 .arg("qt_largefile.tmp"); |
|
237 |
|
238 generatedBlocks[index] = generateDataBlock(blockSize, text, (qint64)1 << index); |
|
239 } |
|
240 |
|
241 return generatedBlocks[index]; |
|
242 } |
|
243 |
|
244 void tst_LargeFile::initTestCase() |
|
245 { |
|
246 QFile file("qt_largefile.tmp"); |
|
247 QVERIFY( !file.exists() || file.remove() ); |
|
248 } |
|
249 |
|
250 void tst_LargeFile::cleanupTestCase() |
|
251 { |
|
252 if (largeFile.isOpen()) |
|
253 largeFile.close(); |
|
254 |
|
255 QFile file("qt_largefile.tmp"); |
|
256 QVERIFY( !file.exists() || file.remove() ); |
|
257 } |
|
258 |
|
259 void tst_LargeFile::init() |
|
260 { |
|
261 fd_ = -1; |
|
262 stream_ = 0; |
|
263 } |
|
264 |
|
265 void tst_LargeFile::cleanup() |
|
266 { |
|
267 if (-1 != fd_) |
|
268 QT_CLOSE(fd_); |
|
269 if (stream_) |
|
270 ::fclose(stream_); |
|
271 } |
|
272 |
|
273 void tst_LargeFile::sparseFileData() |
|
274 { |
|
275 QTest::addColumn<int>("index"); |
|
276 QTest::addColumn<qint64>("position"); |
|
277 QTest::addColumn<QByteArray>("block"); |
|
278 |
|
279 QTest::newRow(QString("block[%1] @%2)") |
|
280 .arg(0).arg(0) |
|
281 .toLocal8Bit().constData()) |
|
282 << 0 << (qint64)0 << getDataBlock(0, 0); |
|
283 |
|
284 // While on Linux sparse files scale well, on Windows, testing at every |
|
285 // power of 2 leads to very large files. i += 4 gives us a good coverage |
|
286 // without taxing too much on resources. |
|
287 for (int index = 12; index <= maxSizeBits; index += 4) { |
|
288 qint64 position = (qint64)1 << index; |
|
289 QByteArray block = getDataBlock(index, position); |
|
290 |
|
291 QTest::newRow( |
|
292 QString("block[%1] @%2)") |
|
293 .arg(index).arg(position) |
|
294 .toLocal8Bit().constData()) |
|
295 << index << position << block; |
|
296 } |
|
297 } |
|
298 |
|
299 void tst_LargeFile::createSparseFile() |
|
300 { |
|
301 #if defined(Q_OS_WIN32) |
|
302 // On Windows platforms, we must explicitly set the file to be sparse, |
|
303 // so disk space is not allocated for the full file when writing to it. |
|
304 HANDLE handle = ::CreateFileA("qt_largefile.tmp", |
|
305 GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); |
|
306 QVERIFY( INVALID_HANDLE_VALUE != handle ); |
|
307 |
|
308 DWORD bytes; |
|
309 if (!::DeviceIoControl(handle, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, |
|
310 &bytes, NULL)) { |
|
311 QWARN("Unable to set test file as sparse. " |
|
312 "Limiting test file to 16MiB."); |
|
313 maxSizeBits = 24; |
|
314 } |
|
315 |
|
316 int fd = ::_open_osfhandle((intptr_t)handle, 0); |
|
317 QVERIFY( -1 != fd ); |
|
318 QVERIFY( largeFile.open(fd, QIODevice::WriteOnly | QIODevice::Unbuffered) ); |
|
319 #else // !Q_OS_WIN32 |
|
320 largeFile.setFileName("qt_largefile.tmp"); |
|
321 QVERIFY( largeFile.open(QIODevice::WriteOnly | QIODevice::Unbuffered) ); |
|
322 #endif |
|
323 } |
|
324 |
|
325 void tst_LargeFile::closeSparseFile() |
|
326 { |
|
327 #if defined(Q_OS_WIN32) |
|
328 int fd = largeFile.handle(); |
|
329 #endif |
|
330 |
|
331 largeFile.close(); |
|
332 |
|
333 #if defined(Q_OS_WIN32) |
|
334 if (-1 != fd) |
|
335 ::_close(fd); |
|
336 #endif |
|
337 } |
|
338 |
|
339 void tst_LargeFile::fillFileSparsely() |
|
340 { |
|
341 QFETCH( qint64, position ); |
|
342 QFETCH( QByteArray, block ); |
|
343 QCOMPARE( block.size(), blockSize ); |
|
344 |
|
345 static int lastKnownGoodIndex = 0; |
|
346 struct ScopeGuard { |
|
347 ScopeGuard(tst_LargeFile* test) |
|
348 : this_(test) |
|
349 , failed(true) |
|
350 { |
|
351 QFETCH( int, index ); |
|
352 index_ = index; |
|
353 } |
|
354 |
|
355 ~ScopeGuard() |
|
356 { |
|
357 if (failed) { |
|
358 this_->maxSizeBits = lastKnownGoodIndex; |
|
359 QWARN( qPrintable( |
|
360 QString("QFile::error %1: '%2'. Maximum size bits reset to %3.") |
|
361 .arg(this_->largeFile.error()) |
|
362 .arg(this_->largeFile.errorString()) |
|
363 .arg(this_->maxSizeBits)) ); |
|
364 } else |
|
365 lastKnownGoodIndex = qMax<int>(index_, lastKnownGoodIndex); |
|
366 } |
|
367 |
|
368 private: |
|
369 tst_LargeFile * const this_; |
|
370 int index_; |
|
371 |
|
372 public: |
|
373 bool failed; |
|
374 }; |
|
375 |
|
376 ScopeGuard resetMaxSizeBitsOnFailure(this); |
|
377 |
|
378 QVERIFY( largeFile.seek(position) ); |
|
379 QCOMPARE( largeFile.pos(), position ); |
|
380 |
|
381 QCOMPARE( largeFile.write(block), (qint64)blockSize ); |
|
382 QCOMPARE( largeFile.pos(), position + blockSize ); |
|
383 QVERIFY( largeFile.flush() ); |
|
384 |
|
385 resetMaxSizeBitsOnFailure.failed = false; |
|
386 } |
|
387 |
|
388 void tst_LargeFile::fileCreated() |
|
389 { |
|
390 QFileInfo info("qt_largefile.tmp"); |
|
391 |
|
392 QVERIFY( info.exists() ); |
|
393 QVERIFY( info.isFile() ); |
|
394 QVERIFY( info.size() >= ((qint64)1 << maxSizeBits) + blockSize ); |
|
395 } |
|
396 |
|
397 void tst_LargeFile::filePositioning() |
|
398 { |
|
399 QFETCH( qint64, position ); |
|
400 |
|
401 QFile file("qt_largefile.tmp"); |
|
402 QVERIFY( file.open(QIODevice::ReadOnly) ); |
|
403 |
|
404 QVERIFY( file.seek(position) ); |
|
405 QCOMPARE( file.pos(), position ); |
|
406 } |
|
407 |
|
408 void tst_LargeFile::fdPositioning() |
|
409 { |
|
410 QFETCH( qint64, position ); |
|
411 |
|
412 fd_ = QT_OPEN("qt_largefile.tmp", |
|
413 QT_OPEN_RDONLY | QT_OPEN_LARGEFILE); |
|
414 QVERIFY( -1 != fd_ ); |
|
415 |
|
416 QFile file; |
|
417 QVERIFY( file.open(fd_, QIODevice::ReadOnly) ); |
|
418 QCOMPARE( file.pos(), (qint64)0 ); |
|
419 QVERIFY( file.seek(position) ); |
|
420 QCOMPARE( file.pos(), position ); |
|
421 |
|
422 file.close(); |
|
423 |
|
424 QCOMPARE( QT_LSEEK(fd_, QT_OFF_T(0), SEEK_SET), QT_OFF_T(0) ); |
|
425 QCOMPARE( QT_LSEEK(fd_, QT_OFF_T(position), SEEK_SET), QT_OFF_T(position) ); |
|
426 |
|
427 QVERIFY( file.open(fd_, QIODevice::ReadOnly) ); |
|
428 QCOMPARE( QT_LSEEK(fd_, QT_OFF_T(0), SEEK_CUR), QT_OFF_T(position) ); |
|
429 QCOMPARE( file.pos(), position ); |
|
430 QVERIFY( file.seek(0) ); |
|
431 QCOMPARE( file.pos(), (qint64)0 ); |
|
432 |
|
433 file.close(); |
|
434 |
|
435 QVERIFY( !QT_CLOSE(fd_) ); |
|
436 fd_ = -1; |
|
437 } |
|
438 |
|
439 void tst_LargeFile::streamPositioning() |
|
440 { |
|
441 QFETCH( qint64, position ); |
|
442 |
|
443 #if defined(QT_LARGEFILE_SUPPORT) && defined(Q_CC_MSVC) && _MSC_VER < 1400 |
|
444 if (position >= (qint64)1 << 31) |
|
445 QSKIP("MSVC 2003 doesn't have 64 bit versions of fseek/ftell.", SkipSingle); |
|
446 #endif |
|
447 |
|
448 stream_ = QT_FOPEN("qt_largefile.tmp", "rb"); |
|
449 QVERIFY( 0 != stream_ ); |
|
450 |
|
451 QFile file; |
|
452 QVERIFY( file.open(stream_, QIODevice::ReadOnly) ); |
|
453 QCOMPARE( file.pos(), (qint64)0 ); |
|
454 QVERIFY( file.seek(position) ); |
|
455 QCOMPARE( file.pos(), position ); |
|
456 |
|
457 file.close(); |
|
458 |
|
459 QVERIFY( !QT_FSEEK(stream_, QT_OFF_T(0), SEEK_SET) ); |
|
460 QCOMPARE( QT_FTELL(stream_), QT_OFF_T(0) ); |
|
461 QVERIFY( !QT_FSEEK(stream_, QT_OFF_T(position), SEEK_SET) ); |
|
462 QCOMPARE( QT_FTELL(stream_), QT_OFF_T(position) ); |
|
463 |
|
464 QVERIFY( file.open(stream_, QIODevice::ReadOnly) ); |
|
465 QCOMPARE( QT_FTELL(stream_), QT_OFF_T(position) ); |
|
466 QCOMPARE( file.pos(), position ); |
|
467 QVERIFY( file.seek(0) ); |
|
468 QCOMPARE( file.pos(), (qint64)0 ); |
|
469 |
|
470 file.close(); |
|
471 |
|
472 QVERIFY( !::fclose(stream_) ); |
|
473 stream_ = 0; |
|
474 } |
|
475 |
|
476 void tst_LargeFile::openFileForReading() |
|
477 { |
|
478 largeFile.setFileName("qt_largefile.tmp"); |
|
479 QVERIFY( largeFile.open(QIODevice::ReadOnly) ); |
|
480 } |
|
481 |
|
482 void tst_LargeFile::readFile() |
|
483 { |
|
484 QFETCH( qint64, position ); |
|
485 QFETCH( QByteArray, block ); |
|
486 QCOMPARE( block.size(), blockSize ); |
|
487 |
|
488 QVERIFY( largeFile.size() >= position + blockSize ); |
|
489 |
|
490 QVERIFY( largeFile.seek(position) ); |
|
491 QCOMPARE( largeFile.pos(), position ); |
|
492 |
|
493 QCOMPARE( largeFile.read(blockSize), block ); |
|
494 QCOMPARE( largeFile.pos(), position + blockSize ); |
|
495 } |
|
496 |
|
497 void tst_LargeFile::mapFile() |
|
498 { |
|
499 QFETCH( qint64, position ); |
|
500 QFETCH( QByteArray, block ); |
|
501 QCOMPARE( block.size(), blockSize ); |
|
502 |
|
503 // Keep full block mapped to facilitate OS and/or internal reuse by Qt. |
|
504 uchar *baseAddress = largeFile.map(position, blockSize); |
|
505 QVERIFY( baseAddress ); |
|
506 QVERIFY( qEqual(block.begin(), block.end(), reinterpret_cast<char*>(baseAddress)) ); |
|
507 |
|
508 for (int offset = 1; offset < blockSize; ++offset) { |
|
509 uchar *address = largeFile.map(position + offset, blockSize - offset); |
|
510 |
|
511 QVERIFY( address ); |
|
512 if ( !qEqual(block.begin() + offset, block.end(), reinterpret_cast<char*>(address)) ) { |
|
513 qDebug() << "Expected:" << block.toHex(); |
|
514 qDebug() << "Actual :" << QByteArray(reinterpret_cast<char*>(address), blockSize).toHex(); |
|
515 QVERIFY(false); |
|
516 } |
|
517 |
|
518 QVERIFY( largeFile.unmap( address ) ); |
|
519 } |
|
520 |
|
521 QVERIFY( largeFile.unmap( baseAddress ) ); |
|
522 } |
|
523 |
|
524 void tst_LargeFile::mapOffsetOverflow() |
|
525 { |
|
526 // Out-of-range mappings should fail, and not silently clip the offset |
|
527 for (int i = 50; i < 63; ++i) { |
|
528 uchar *address = 0; |
|
529 |
|
530 address = largeFile.map(((qint64)1 << i), blockSize); |
|
531 QVERIFY( !address ); |
|
532 |
|
533 address = largeFile.map(((qint64)1 << i) + blockSize, blockSize); |
|
534 QVERIFY( !address ); |
|
535 } |
|
536 } |
|
537 |
|
538 QTEST_APPLESS_MAIN(tst_LargeFile) |
|
539 #include "tst_largefile.moc" |
|
540 |