tests/auto/qfile/largefile/tst_largefile.cpp
changeset 3 41300fa6a67c
child 4 3b1da2848fc7
equal deleted inserted replaced
2:56cd8111b7f7 3:41300fa6a67c
       
     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