qt5base-lts/tests/auto/qfile/largefile/tst_largefile.cpp
Jason McDonald 7bd778117c Remove Q_ASSERT's from large file autotest.
These assertions were made obsolete by the previous change to this test.

Change-Id: I3156b273a727f753a6c093c8fc7453b694c777ee
Task-number: QTBUG-17582
Reviewed-by: Rohan McGovern
(cherry picked from commit 57f6ed6664f55ccee62b81750a2b3520249503fd)
2011-05-18 10:46:45 +10:00

539 lines
14 KiB
C++

/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QTest>
#include <QtAlgorithms>
#include <QFile>
#include <QFileInfo>
#include <qplatformdefs.h>
#include <QDebug>
#include <cstdlib>
#include <cstdio>
#ifdef Q_OS_WIN
#include <windows.h>
#ifndef Q_OS_WINCE
#include <io.h>
#endif
#ifndef FSCTL_SET_SPARSE
// MinGW doesn't define this.
#define FSCTL_SET_SPARSE (0x900C4)
#endif
#endif // Q_OS_WIN
class tst_LargeFile
: public QObject
{
Q_OBJECT
public:
tst_LargeFile()
: blockSize(1 << 12)
, maxSizeBits()
, fd_(-1)
, stream_(0)
{
#if defined(QT_LARGEFILE_SUPPORT) && !defined(Q_OS_MAC)
maxSizeBits = 36; // 64 GiB
#elif defined(Q_OS_MAC)
// HFS+ does not support sparse files, so we limit file size for the test
// on Mac OS.
maxSizeBits = 32; // 4 GiB
#else
maxSizeBits = 24; // 16 MiB
#endif
}
private:
void sparseFileData();
QByteArray const &getDataBlock(int index, qint64 position);
private slots:
// The LargeFile test case was designed to be run in order as a single unit
void initTestCase();
void cleanupTestCase();
void init();
void cleanup();
// Create and fill large file
void createSparseFile();
void fillFileSparsely();
void closeSparseFile();
// Verify file was created
void fileCreated();
// Positioning in large files
void filePositioning();
void fdPositioning();
void streamPositioning();
// Read data from file
void openFileForReading();
void readFile();
// Map/unmap large file
void mapFile();
void mapOffsetOverflow();
void closeFile() { largeFile.close(); }
// Test data
void fillFileSparsely_data() { sparseFileData(); }
void filePositioning_data() { sparseFileData(); }
void fdPositioning_data() { sparseFileData(); }
void streamPositioning_data() { sparseFileData(); }
void readFile_data() { sparseFileData(); }
void mapFile_data() { sparseFileData(); }
private:
const int blockSize;
int maxSizeBits;
QFile largeFile;
QVector<QByteArray> generatedBlocks;
int fd_;
FILE *stream_;
};
/*
Convenience function to hide reinterpret_cast when copying a POD directly
into a QByteArray.
*/
template <class T>
static inline void appendRaw(QByteArray &array, T data)
{
array.append(reinterpret_cast<char *>(&data), sizeof(T));
}
/*
Pad array with filler up to size. On return, array.size() returns size.
*/
static inline void topUpWith(QByteArray &array, QByteArray filler, int size)
{
for (int i = (size - array.size()) / filler.size(); i > 0; --i)
array.append(filler);
if (array.size() < size) {
array.append(filler.left(size - array.size()));
}
}
/*
Generate a unique data block containing identifiable data. Unaligned,
overlapping and partial blocks should not compare equal.
*/
static inline QByteArray generateDataBlock(int blockSize, QString text, qint64 userBits = -1)
{
QByteArray block;
block.reserve(blockSize);
// Use of counter and randomBits means content of block will be dependent
// on the generation order. For (file-)systems that do not support sparse
// files, these can be removed so the test file can be reused and doesn't
// have to be generated for every run.
static qint64 counter = 0;
qint64 randomBits = ((qint64)qrand() << 32)
| ((qint64)qrand() & 0x00000000ffffffff);
appendRaw(block, randomBits);
appendRaw(block, userBits);
appendRaw(block, counter);
appendRaw(block, (qint32)0xdeadbeef);
appendRaw(block, blockSize);
QByteArray userContent = text.toUtf8();
appendRaw(block, userContent.size());
block.append(userContent);
appendRaw(block, (qint64)0);
// size, so far
appendRaw(block, block.size());
QByteArray filler("0123456789");
block.append(filler.right(10 - block.size() % 10));
topUpWith(block, filler, blockSize - 3 * sizeof(qint64));
appendRaw(block, counter);
appendRaw(block, userBits);
appendRaw(block, randomBits);
++counter;
return block;
}
/*
Generates data blocks the first time they are requested. Keeps copies for reuse.
*/
QByteArray const &tst_LargeFile::getDataBlock(int index, qint64 position)
{
if (index >= generatedBlocks.size())
generatedBlocks.resize(index + 1);
if (generatedBlocks[index].isNull()) {
QString text = QString("Current %1-byte block (index = %2) "
"starts %3 bytes into the file '%4'.")
.arg(blockSize)
.arg(index)
.arg(position)
.arg("qt_largefile.tmp");
generatedBlocks[index] = generateDataBlock(blockSize, text, (qint64)1 << index);
}
return generatedBlocks[index];
}
void tst_LargeFile::initTestCase()
{
QFile file("qt_largefile.tmp");
QVERIFY( !file.exists() || file.remove() );
}
void tst_LargeFile::cleanupTestCase()
{
if (largeFile.isOpen())
largeFile.close();
QFile file("qt_largefile.tmp");
QVERIFY( !file.exists() || file.remove() );
}
void tst_LargeFile::init()
{
fd_ = -1;
stream_ = 0;
}
void tst_LargeFile::cleanup()
{
if (-1 != fd_)
QT_CLOSE(fd_);
if (stream_)
::fclose(stream_);
}
void tst_LargeFile::sparseFileData()
{
QTest::addColumn<int>("index");
QTest::addColumn<qint64>("position");
QTest::addColumn<QByteArray>("block");
QTest::newRow(QString("block[%1] @%2)")
.arg(0).arg(0)
.toLocal8Bit().constData())
<< 0 << (qint64)0 << getDataBlock(0, 0);
// While on Linux sparse files scale well, on Windows, testing at every
// power of 2 leads to very large files. i += 4 gives us a good coverage
// without taxing too much on resources.
for (int index = 12; index <= maxSizeBits; index += 4) {
qint64 position = (qint64)1 << index;
QByteArray block = getDataBlock(index, position);
QTest::newRow(
QString("block[%1] @%2)")
.arg(index).arg(position)
.toLocal8Bit().constData())
<< index << position << block;
}
}
void tst_LargeFile::createSparseFile()
{
#if defined(Q_OS_WIN32)
// On Windows platforms, we must explicitly set the file to be sparse,
// so disk space is not allocated for the full file when writing to it.
HANDLE handle = ::CreateFileA("qt_largefile.tmp",
GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
QVERIFY( INVALID_HANDLE_VALUE != handle );
DWORD bytes;
if (!::DeviceIoControl(handle, FSCTL_SET_SPARSE, NULL, 0, NULL, 0,
&bytes, NULL)) {
QWARN("Unable to set test file as sparse. "
"Limiting test file to 16MiB.");
maxSizeBits = 24;
}
int fd = ::_open_osfhandle((intptr_t)handle, 0);
QVERIFY( -1 != fd );
QVERIFY( largeFile.open(fd, QIODevice::WriteOnly | QIODevice::Unbuffered) );
#else // !Q_OS_WIN32
largeFile.setFileName("qt_largefile.tmp");
QVERIFY( largeFile.open(QIODevice::WriteOnly | QIODevice::Unbuffered) );
#endif
}
void tst_LargeFile::closeSparseFile()
{
#if defined(Q_OS_WIN32)
int fd = largeFile.handle();
#endif
largeFile.close();
#if defined(Q_OS_WIN32)
if (-1 != fd)
::_close(fd);
#endif
}
void tst_LargeFile::fillFileSparsely()
{
QFETCH( qint64, position );
QFETCH( QByteArray, block );
QCOMPARE( block.size(), blockSize );
static int lastKnownGoodIndex = 0;
struct ScopeGuard {
ScopeGuard(tst_LargeFile* test)
: this_(test)
, failed(true)
{
QFETCH( int, index );
index_ = index;
}
~ScopeGuard()
{
if (failed) {
this_->maxSizeBits = lastKnownGoodIndex;
QWARN( qPrintable(
QString("QFile::error %1: '%2'. Maximum size bits reset to %3.")
.arg(this_->largeFile.error())
.arg(this_->largeFile.errorString())
.arg(this_->maxSizeBits)) );
} else
lastKnownGoodIndex = qMax<int>(index_, lastKnownGoodIndex);
}
private:
tst_LargeFile * const this_;
int index_;
public:
bool failed;
};
ScopeGuard resetMaxSizeBitsOnFailure(this);
QVERIFY( largeFile.seek(position) );
QCOMPARE( largeFile.pos(), position );
QCOMPARE( largeFile.write(block), (qint64)blockSize );
QCOMPARE( largeFile.pos(), position + blockSize );
QVERIFY( largeFile.flush() );
resetMaxSizeBitsOnFailure.failed = false;
}
void tst_LargeFile::fileCreated()
{
QFileInfo info("qt_largefile.tmp");
QVERIFY( info.exists() );
QVERIFY( info.isFile() );
QVERIFY( info.size() >= ((qint64)1 << maxSizeBits) + blockSize );
}
void tst_LargeFile::filePositioning()
{
QFETCH( qint64, position );
QFile file("qt_largefile.tmp");
QVERIFY( file.open(QIODevice::ReadOnly) );
QVERIFY( file.seek(position) );
QCOMPARE( file.pos(), position );
}
void tst_LargeFile::fdPositioning()
{
QFETCH( qint64, position );
fd_ = QT_OPEN("qt_largefile.tmp",
QT_OPEN_RDONLY | QT_OPEN_LARGEFILE);
QVERIFY( -1 != fd_ );
QFile file;
QVERIFY( file.open(fd_, QIODevice::ReadOnly) );
QCOMPARE( file.pos(), (qint64)0 );
QVERIFY( file.seek(position) );
QCOMPARE( file.pos(), position );
file.close();
QCOMPARE( QT_LSEEK(fd_, QT_OFF_T(0), SEEK_SET), QT_OFF_T(0) );
QCOMPARE( QT_LSEEK(fd_, QT_OFF_T(position), SEEK_SET), QT_OFF_T(position) );
QVERIFY( file.open(fd_, QIODevice::ReadOnly) );
QCOMPARE( QT_LSEEK(fd_, QT_OFF_T(0), SEEK_CUR), QT_OFF_T(position) );
QCOMPARE( file.pos(), position );
QVERIFY( file.seek(0) );
QCOMPARE( file.pos(), (qint64)0 );
file.close();
QVERIFY( !QT_CLOSE(fd_) );
fd_ = -1;
}
void tst_LargeFile::streamPositioning()
{
QFETCH( qint64, position );
#if defined(QT_LARGEFILE_SUPPORT) && defined(Q_CC_MSVC) && _MSC_VER < 1400
if (position >= (qint64)1 << 31)
QSKIP("MSVC 2003 doesn't have 64 bit versions of fseek/ftell.", SkipSingle);
#endif
stream_ = QT_FOPEN("qt_largefile.tmp", "rb");
QVERIFY( 0 != stream_ );
QFile file;
QVERIFY( file.open(stream_, QIODevice::ReadOnly) );
QCOMPARE( file.pos(), (qint64)0 );
QVERIFY( file.seek(position) );
QCOMPARE( file.pos(), position );
file.close();
QVERIFY( !QT_FSEEK(stream_, QT_OFF_T(0), SEEK_SET) );
QCOMPARE( QT_FTELL(stream_), QT_OFF_T(0) );
QVERIFY( !QT_FSEEK(stream_, QT_OFF_T(position), SEEK_SET) );
QCOMPARE( QT_FTELL(stream_), QT_OFF_T(position) );
QVERIFY( file.open(stream_, QIODevice::ReadOnly) );
QCOMPARE( QT_FTELL(stream_), QT_OFF_T(position) );
QCOMPARE( file.pos(), position );
QVERIFY( file.seek(0) );
QCOMPARE( file.pos(), (qint64)0 );
file.close();
QVERIFY( !::fclose(stream_) );
stream_ = 0;
}
void tst_LargeFile::openFileForReading()
{
largeFile.setFileName("qt_largefile.tmp");
QVERIFY( largeFile.open(QIODevice::ReadOnly) );
}
void tst_LargeFile::readFile()
{
QFETCH( qint64, position );
QFETCH( QByteArray, block );
QCOMPARE( block.size(), blockSize );
QVERIFY( largeFile.size() >= position + blockSize );
QVERIFY( largeFile.seek(position) );
QCOMPARE( largeFile.pos(), position );
QCOMPARE( largeFile.read(blockSize), block );
QCOMPARE( largeFile.pos(), position + blockSize );
}
void tst_LargeFile::mapFile()
{
QFETCH( qint64, position );
QFETCH( QByteArray, block );
QCOMPARE( block.size(), blockSize );
// Keep full block mapped to facilitate OS and/or internal reuse by Qt.
uchar *baseAddress = largeFile.map(position, blockSize);
QVERIFY( baseAddress );
QVERIFY( qEqual(block.begin(), block.end(), reinterpret_cast<char*>(baseAddress)) );
for (int offset = 1; offset < blockSize; ++offset) {
uchar *address = largeFile.map(position + offset, blockSize - offset);
QVERIFY( address );
if ( !qEqual(block.begin() + offset, block.end(), reinterpret_cast<char*>(address)) ) {
qDebug() << "Expected:" << block.toHex();
qDebug() << "Actual :" << QByteArray(reinterpret_cast<char*>(address), blockSize).toHex();
QVERIFY(false);
}
QVERIFY( largeFile.unmap( address ) );
}
QVERIFY( largeFile.unmap( baseAddress ) );
}
void tst_LargeFile::mapOffsetOverflow()
{
#if defined(Q_OS_MAC)
QSKIP("mmap'ping beyond EOF may succeed; generate bus error on access", SkipAll);
#endif
// Out-of-range mappings should fail, and not silently clip the offset
for (int i = 50; i < 63; ++i) {
uchar *address = 0;
address = largeFile.map(((qint64)1 << i), blockSize);
QVERIFY( !address );
address = largeFile.map(((qint64)1 << i) + blockSize, blockSize);
QVERIFY( !address );
}
}
QTEST_APPLESS_MAIN(tst_LargeFile)
#include "tst_largefile.moc"