/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the FOO module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** 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 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "../../shared/filesystem.h" class tst_QAbstractFileEngine : public QObject { Q_OBJECT public slots: void cleanupTestCase(); private slots: void customHandler(); void fileIO_data(); void fileIO(); void mounting_data(); void mounting(); private: QStringList filesForRemoval; }; class ReferenceFileEngine : public QAbstractFileEngine { public: ReferenceFileEngine(const QString &fileName) : fileName_(QDir::cleanPath(fileName)) , position_(-1) , openForRead_(false) , openForWrite_(false) { } bool open(QIODevice::OpenMode openMode) { if (openForRead_ || openForWrite_) { qWarning("%s: file is already open for %s", Q_FUNC_INFO, (openForRead_ ? "reading" : "writing")); return false; } openFile_ = resolveFile(openMode & QIODevice::WriteOnly); if (!openFile_) return false; position_ = 0; if (openMode & QIODevice::ReadOnly) openForRead_ = true; if (openMode & QIODevice::WriteOnly) { openForWrite_ = true; QMutexLocker lock(&openFile_->mutex); if (openMode & QIODevice::Truncate || !(openForRead_ || openMode & QIODevice::Append)) openFile_->content.clear(); if (openMode & QIODevice::Append) position_ = openFile_->content.size(); } return true; } bool close() { openFile_.clear(); openForRead_ = false; openForWrite_ = false; position_ = -1; return true; } qint64 size() const { QSharedPointer file = resolveFile(false); if (!file) return 0; QMutexLocker lock(&file->mutex); return file->content.size(); } qint64 pos() const { if (!openForRead_ && !openForWrite_) { qWarning("%s: file is not open", Q_FUNC_INFO); return -1; } return position_; } bool seek(qint64 pos) { if (!openForRead_ && !openForWrite_) { qWarning("%s: file is not open", Q_FUNC_INFO); return false; } if (pos >= 0) { position_ = pos; return true; } return false; } bool flush() { if (!openForRead_ && !openForWrite_) { qWarning("%s: file is not open", Q_FUNC_INFO); return false; } return true; } bool remove() { QMutexLocker lock(&fileSystemMutex); int count = fileSystem.remove(fileName_); return (count == 1); } bool copy(const QString &newName) { QMutexLocker lock(&fileSystemMutex); if (!fileSystem.contains(fileName_) || fileSystem.contains(newName)) return false; fileSystem.insert(newName, fileSystem.value(fileName_)); return true; } bool rename(const QString &newName) { QMutexLocker lock(&fileSystemMutex); if (!fileSystem.contains(fileName_) || fileSystem.contains(newName)) return false; fileSystem.insert(newName, fileSystem.take(fileName_)); return true; } // bool link(const QString &newName) // { // Q_UNUSED(newName) // return false; // } // bool mkdir(const QString &dirName, bool createParentDirectories) const // { // Q_UNUSED(dirName) // Q_UNUSED(createParentDirectories) // return false; // } // bool rmdir(const QString &dirName, bool recurseParentDirectories) const // { // Q_UNUSED(dirName) // Q_UNUSED(recurseParentDirectories) // return false; // } bool setSize(qint64 size) { if (size < 0) return false; QSharedPointer file = resolveFile(false); if (!file) return false; QMutexLocker lock(&file->mutex); file->content.resize(size); if (openForRead_ || openForWrite_) if (position_ > size) position_ = size; return (file->content.size() == size); } FileFlags fileFlags(FileFlags type) const { QSharedPointer file = resolveFile(false); if (file) { QMutexLocker lock(&file->mutex); return (file->fileFlags & type); } return FileFlags(); } // bool setPermissions(uint perms) // { // Q_UNUSED(perms) // return false; // } QString fileName(FileName file) const { switch (file) { case DefaultName: return QLatin1String("DefaultName"); case BaseName: return QLatin1String("BaseName"); case PathName: return QLatin1String("PathName"); case AbsoluteName: return QLatin1String("AbsoluteName"); case AbsolutePathName: return QLatin1String("AbsolutePathName"); case LinkName: return QLatin1String("LinkName"); case CanonicalName: return QLatin1String("CanonicalName"); case CanonicalPathName: return QLatin1String("CanonicalPathName"); case BundleName: return QLatin1String("BundleName"); default: break; } return QString(); } uint ownerId(FileOwner owner) const { QSharedPointer file = resolveFile(false); if (file) { switch (owner) { case OwnerUser: { QMutexLocker lock(&file->mutex); return file->userId; } case OwnerGroup: { QMutexLocker lock(&file->mutex); return file->groupId; } } } return -2; } QString owner(FileOwner owner) const { QSharedPointer file = resolveFile(false); if (file) { uint ownerId; switch (owner) { case OwnerUser: { QMutexLocker lock(&file->mutex); ownerId = file->userId; } { QMutexLocker lock(&fileSystemMutex); return fileSystemUsers.value(ownerId); } case OwnerGroup: { QMutexLocker lock(&file->mutex); ownerId = file->groupId; } { QMutexLocker lock(&fileSystemMutex); return fileSystemGroups.value(ownerId); } } } return QString(); } QDateTime fileTime(FileTime time) const { QSharedPointer file = resolveFile(false); if (file) { QMutexLocker lock(&file->mutex); switch (time) { case CreationTime: return file->creation; case ModificationTime: return file->modification; case AccessTime: return file->access; } } return QDateTime(); } void setFileName(const QString &file) { if (openForRead_ || openForWrite_) qWarning("%s: Can't set file name while file is open", Q_FUNC_INFO); else fileName_ = file; } // typedef QAbstractFileEngineIterator Iterator; // Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) // { // Q_UNUSED(filters) // Q_UNUSED(filterNames) // return 0; // } // Iterator *endEntryList() // { // return 0; // } qint64 read(char *data, qint64 maxLen) { if (!openForRead_) { qWarning("%s: file must be open for reading", Q_FUNC_INFO); return -1; } if (openFile_.isNull()) { qWarning("%s: file must not be null", Q_FUNC_INFO); return -1; } QMutexLocker lock(&openFile_->mutex); qint64 readSize = qMin(openFile_->content.size() - position_, maxLen); if (readSize < 0) return -1; qMemCopy(data, openFile_->content.constData() + position_, readSize); position_ += readSize; return readSize; } qint64 write(const char *data, qint64 length) { if (!openForWrite_) { qWarning("%s: file must be open for writing", Q_FUNC_INFO); return -1; } if (openFile_.isNull()) { qWarning("%s: file must not be null", Q_FUNC_INFO); return -1; } if (length < 0) return -1; QMutexLocker lock(&openFile_->mutex); if (openFile_->content.size() == position_) openFile_->content.append(data, length); else { if (position_ + length > openFile_->content.size()) openFile_->content.resize(position_ + length); openFile_->content.replace(position_, length, data, length); } qint64 writeSize = qMin(length, openFile_->content.size() - position_); position_ += writeSize; return writeSize; } protected: // void setError(QFile::FileError error, const QString &str); struct File { File() : userId(0) , groupId(0) , fileFlags( ReadOwnerPerm | WriteOwnerPerm | ExeOwnerPerm | ReadUserPerm | WriteUserPerm | ExeUserPerm | ReadGroupPerm | WriteGroupPerm | ExeGroupPerm | ReadOtherPerm | WriteOtherPerm | ExeOtherPerm | FileType | ExistsFlag) { } QMutex mutex; uint userId, groupId; QAbstractFileEngine::FileFlags fileFlags; QDateTime creation, modification, access; QByteArray content; }; QSharedPointer resolveFile(bool create) const { if (openForRead_ || openForWrite_) { if (!openFile_) qWarning("%s: file should not be null", Q_FUNC_INFO); return openFile_; } QMutexLocker lock(&fileSystemMutex); if (create) { QSharedPointer &p = fileSystem[fileName_]; if (p.isNull()) p = QSharedPointer(new File); return p; } return fileSystem.value(fileName_); } static QMutex fileSystemMutex; static QHash fileSystemUsers, fileSystemGroups; static QHash > fileSystem; private: QString fileName_; qint64 position_; bool openForRead_; bool openForWrite_; mutable QSharedPointer openFile_; }; class MountingFileEngine : public QFSFileEngine { public: class Iterator : public QAbstractFileEngineIterator { public: Iterator(QDir::Filters filters, const QStringList &filterNames) : QAbstractFileEngineIterator(filters, filterNames) { names.append("foo"); names.append("bar"); index = -1; } QString currentFileName() const { return names.at(index); } bool hasNext() const { return index < names.size() - 1; } QString next() { if (!hasNext()) return QString(); ++index; return currentFilePath(); } QStringList names; int index; }; MountingFileEngine(QString fileName) : QFSFileEngine(fileName) { } Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) { return new Iterator(filters, filterNames); } FileFlags fileFlags(FileFlags type) const { if (fileName(DefaultName).endsWith(".tar")) { FileFlags ret = QFSFileEngine::fileFlags(type); //make this file in file system appear to be a directory ret &= ~FileType; ret |= DirectoryType; return ret; } else { //file inside the archive return ExistsFlag | FileType; } } }; QMutex ReferenceFileEngine::fileSystemMutex; QHash ReferenceFileEngine::fileSystemUsers, ReferenceFileEngine::fileSystemGroups; QHash > ReferenceFileEngine::fileSystem; class FileEngineHandler : QAbstractFileEngineHandler { QAbstractFileEngine *create(const QString &fileName) const { if (fileName.endsWith(".tar") || fileName.contains(".tar/")) return new MountingFileEngine(fileName); if (fileName.startsWith("QFSFileEngine:")) return new QFSFileEngine(fileName.mid(14)); if (fileName.startsWith("reference-file-engine:")) return new ReferenceFileEngine(fileName.mid(22)); if (fileName.startsWith("resource:")) return QAbstractFileEngine::create(QLatin1String(":/tst_qabstractfileengine/resources/") + fileName.mid(9)); return 0; } }; void tst_QAbstractFileEngine::cleanupTestCase() { bool failed = false; FileEngineHandler handler; Q_FOREACH(QString file, filesForRemoval) if (!QFile::remove(file) || QFile::exists(file)) { failed = true; qDebug() << "Couldn't remove file:" << file; } QVERIFY(!failed); } void tst_QAbstractFileEngine::customHandler() { QScopedPointer file; { file.reset(QAbstractFileEngine::create("resource:file.txt")); QVERIFY(file); } { FileEngineHandler handler; QFile file("resource:file.txt"); QVERIFY(file.exists()); } { QFile file("resource:file.txt"); QVERIFY(!file.exists()); } } void tst_QAbstractFileEngine::fileIO_data() { QTest::addColumn("fileName"); QTest::addColumn("readContent"); QTest::addColumn("writeContent"); QTest::addColumn("fileExists"); QString resourceTxtFile(":/tst_qabstractfileengine/resources/file.txt"); QByteArray readContent("This is a simple text file.\n"); QByteArray writeContent("This contains two lines of text.\n"); QTest::newRow("resource") << resourceTxtFile << readContent << QByteArray() << true; QTest::newRow("native") << "native-file.txt" << readContent << writeContent << false; QTest::newRow("Forced QFSFileEngine") << "QFSFileEngine:QFSFileEngine-file.txt" << readContent << writeContent << false; QTest::newRow("Custom FE") << "reference-file-engine:file.txt" << readContent << writeContent << false; QTest::newRow("Forced QFSFileEngine (native)") << "QFSFileEngine:native-file.txt" << readContent << writeContent << true; QTest::newRow("native (Forced QFSFileEngine)") << "QFSFileEngine-file.txt" << readContent << writeContent << true; QTest::newRow("Custom FE (2)") << "reference-file-engine:file.txt" << readContent << writeContent << true; } void tst_QAbstractFileEngine::fileIO() { QFETCH(QString, fileName); QFETCH(QByteArray, readContent); QFETCH(QByteArray, writeContent); QFETCH(bool, fileExists); FileEngineHandler handler; { QFile file(fileName); QCOMPARE(file.exists(), fileExists); if (!fileExists) { QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Unbuffered)); filesForRemoval.append(fileName); QCOMPARE(file.write(readContent), qint64(readContent.size())); } } // // File content is: readContent // qint64 fileSize = readContent.size(); { // Reading QFile file(fileName); QVERIFY(!file.isOpen()); QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Unbuffered)); QVERIFY(file.isOpen()); QCOMPARE(file.size(), fileSize); QCOMPARE(file.pos(), qint64(0)); QCOMPARE(file.size(), fileSize); QCOMPARE(file.readAll(), readContent); QCOMPARE(file.pos(), fileSize); file.close(); QVERIFY(!file.isOpen()); QCOMPARE(file.size(), fileSize); } if (writeContent.isEmpty()) return; { // Writing / appending QFile file(fileName); QVERIFY(!file.isOpen()); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Unbuffered)); QVERIFY(file.isOpen()); QCOMPARE(file.size(), fileSize); QCOMPARE(file.pos(), fileSize); QCOMPARE(file.write(writeContent), qint64(writeContent.size())); fileSize += writeContent.size(); QCOMPARE(file.pos(), fileSize); QCOMPARE(file.size(), fileSize); file.close(); QVERIFY(!file.isOpen()); QCOMPARE(file.size(), fileSize); } // // File content is: readContent + writeContent // { // Reading and Writing QFile file(fileName); QVERIFY(!file.isOpen()); QVERIFY(file.open(QIODevice::ReadWrite | QIODevice::Unbuffered)); QVERIFY(file.isOpen()); QCOMPARE(file.size(), fileSize); QCOMPARE(file.pos(), qint64(0)); QCOMPARE(file.readAll(), readContent + writeContent); QCOMPARE(file.pos(), fileSize); QCOMPARE(file.size(), fileSize); QVERIFY(file.seek(writeContent.size())); QCOMPARE(file.pos(), qint64(writeContent.size())); QCOMPARE(file.size(), fileSize); QCOMPARE(file.write(readContent), qint64(readContent.size())); QCOMPARE(file.pos(), fileSize); QCOMPARE(file.size(), fileSize); QVERIFY(file.seek(0)); QCOMPARE(file.pos(), qint64(0)); QCOMPARE(file.size(), fileSize); QCOMPARE(file.write(writeContent), qint64(writeContent.size())); QCOMPARE(file.pos(), qint64(writeContent.size())); QCOMPARE(file.size(), fileSize); QVERIFY(file.seek(0)); QCOMPARE(file.read(writeContent.size()), writeContent); QCOMPARE(file.pos(), qint64(writeContent.size())); QCOMPARE(file.size(), fileSize); QCOMPARE(file.readAll(), readContent); QCOMPARE(file.pos(), fileSize); QCOMPARE(file.size(), fileSize); file.close(); QVERIFY(!file.isOpen()); QCOMPARE(file.size(), fileSize); } // // File content is: writeContent + readContent // { // Writing QFile file(fileName); QVERIFY(!file.isOpen()); QVERIFY(file.open(QIODevice::ReadWrite | QIODevice::Unbuffered)); QVERIFY(file.isOpen()); QCOMPARE(file.size(), fileSize); QCOMPARE(file.pos(), qint64(0)); QCOMPARE(file.write(writeContent), qint64(writeContent.size())); QCOMPARE(file.pos(), qint64(writeContent.size())); QCOMPARE(file.size(), fileSize); QVERIFY(file.resize(writeContent.size())); QCOMPARE(file.size(), qint64(writeContent.size())); file.close(); QVERIFY(!file.isOpen()); QCOMPARE(file.size(), qint64(writeContent.size())); QVERIFY(file.resize(fileSize)); QCOMPARE(file.size(), fileSize); } // // File content is: writeContent + // File size is : (readContent + writeContent).size() // { // Writing / extending QFile file(fileName); QVERIFY(!file.isOpen()); QVERIFY(file.open(QIODevice::ReadWrite | QIODevice::Unbuffered)); QVERIFY(file.isOpen()); QCOMPARE(file.size(), fileSize); QCOMPARE(file.pos(), qint64(0)); QVERIFY(file.seek(1024)); QCOMPARE(file.pos(), qint64(1024)); QCOMPARE(file.size(), fileSize); fileSize = 1024 + writeContent.size(); QCOMPARE(file.write(writeContent), qint64(writeContent.size())); QCOMPARE(file.pos(), fileSize); QCOMPARE(file.size(), fileSize); QVERIFY(file.seek(1028)); QCOMPARE(file.pos(), qint64(1028)); QCOMPARE(file.size(), fileSize); fileSize = 1028 + writeContent.size(); QCOMPARE(file.write(writeContent), qint64(writeContent.size())); QCOMPARE(file.pos(), fileSize); QCOMPARE(file.size(), fileSize); file.close(); QVERIFY(!file.isOpen()); QCOMPARE(file.size(), fileSize); } // // File content is: writeContent + + writeContent // File size is : 1024 + writeContent.size() // { // Writing / truncating QFile file(fileName); QVERIFY(!file.isOpen()); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Unbuffered)); QVERIFY(file.isOpen()); QCOMPARE(file.size(), qint64(0)); QCOMPARE(file.pos(), qint64(0)); fileSize = readContent.size(); QCOMPARE(file.write(readContent), fileSize); QCOMPARE(file.pos(), fileSize); QCOMPARE(file.size(), fileSize); file.close(); QVERIFY(!file.isOpen()); QCOMPARE(file.size(), fileSize); } // // File content is: readContent // } void tst_QAbstractFileEngine::mounting_data() { QTest::addColumn("fileName"); QTest::newRow("native") << "test.tar"; QTest::newRow("Forced QFSFileEngine") << "QFSFileEngine:test.tar"; } void tst_QAbstractFileEngine::mounting() { FileSystem fs; QVERIFY(fs.createFile("test.tar")); FileEngineHandler handler; QFETCH(QString, fileName); QVERIFY(QFileInfo(fileName).isDir()); QDir dir(fileName); QCOMPARE(dir.entryList(), (QStringList() << "bar" << "foo")); QDir dir2; bool found = false; foreach (QFileInfo info, dir2.entryInfoList()) { if (info.fileName() == QLatin1String("test.tar")) { QVERIFY(!found); found = true; QVERIFY(info.isDir()); } } QVERIFY(found); } QTEST_APPLESS_MAIN(tst_QAbstractFileEngine) #include "tst_qabstractfileengine.moc"