/*************************************************************************** testqgsvectorlayercache.cpp -------------------------------------- Date : 20.2.2013 Copyright : (C) 2013 Matthias Kuhn Email : matthias at opengis dot ch *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include #include //qgis includes... #include "qgsfeatureiterator.h" #include #include #include #include #include #include /** @ingroup UnitTests * This is a unit test for the vector layer cache * * @see QgsVectorLayerCache */ class TestVectorLayerCache : public QObject { Q_OBJECT public: TestVectorLayerCache() : mVectorLayerCache( 0 ) , mFeatureIdIndex( 0 ) , mPointsLayer( 0 ) {} private slots: void initTestCase(); // will be called before the first testfunction is executed. void cleanupTestCase(); // will be called after the last testfunction was executed. void init(); // will be called before each testfunction is executed. void cleanup(); // will be called after every testfunction. void testCacheOverflow(); // Test cache will work if too many features to cache them all are present void testCacheAttrActions(); // Test attribute add/ attribute delete void testFeatureActions(); // Test adding/removing features works void testSubsetRequest(); void testFullCache(); void testFullCacheThroughRequest(); void testCanUseCacheForRequest(); void onCommittedFeaturesAdded( const QString&, const QgsFeatureList& ); private: QgsVectorLayerCache* mVectorLayerCache; QgsCacheIndexFeatureId* mFeatureIdIndex; QgsVectorLayer* mPointsLayer; QgsFeatureList mAddedFeatures; QMap mTmpFiles; }; // runs before all tests void TestVectorLayerCache::initTestCase() { QgsApplication::init(); QgsApplication::initQgis(); QgsApplication::showSettings(); // Backup test shape file and attributes QStringList backupFiles; backupFiles << QStringLiteral( "points.shp" ) << QStringLiteral( "points.shx" ) << QStringLiteral( "points.dbf" ) << QStringLiteral( "points.prj" ); QString myDataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt QString myTestDataDir = myDataDir + '/'; Q_FOREACH ( const QString& f, backupFiles ) { QString origFileName = myTestDataDir + f; QFileInfo origFileInfo( origFileName ); QString tmpFileName = QDir::tempPath() + '/' + origFileInfo.baseName() + '_' + QString::number( qApp->applicationPid() ) + '.' + origFileInfo.completeSuffix(); qDebug() << "Copy " << origFileName << " " << tmpFileName; qDebug() << QFile::copy( origFileName, tmpFileName ); mTmpFiles.insert( origFileName, tmpFileName ); } // // load a vector layer // QString myPointsFileName = mTmpFiles.value( myTestDataDir + "points.shp" ); QFileInfo myPointFileInfo( myPointsFileName ); mPointsLayer = new QgsVectorLayer( myPointFileInfo.filePath(), myPointFileInfo.completeBaseName(), QStringLiteral( "ogr" ) ); } void TestVectorLayerCache::init() { mVectorLayerCache = new QgsVectorLayerCache( mPointsLayer, 10 ); mFeatureIdIndex = new QgsCacheIndexFeatureId( mVectorLayerCache ); mVectorLayerCache->addCacheIndex( mFeatureIdIndex ); } void TestVectorLayerCache::cleanup() { delete mVectorLayerCache; } //runs after all tests void TestVectorLayerCache::cleanupTestCase() { delete mPointsLayer; // Clean tmp files QMap::const_iterator it; for ( it = mTmpFiles.constBegin(); it != mTmpFiles.constEnd(); ++it ) { QString tmpFileName = it.value(); qDebug() << "Remove " << tmpFileName; QFile::remove( tmpFileName ); } // also clean up newly created .qix file QFile::remove( QStringLiteral( TEST_DATA_DIR ) + "/points.qix" ); QgsApplication::exitQgis(); } void TestVectorLayerCache::testCacheOverflow() { QgsFeature f; // Verify we get all features, even if there are too many to fit into the cache QgsFeatureIterator it = mVectorLayerCache->getFeatures(); int i = 0; while ( it.nextFeature( f ) ) { i++; } it.close(); QVERIFY( i == 17 ); } void TestVectorLayerCache::testCacheAttrActions() { QgsFeature f; // Add an attribute, make sure it is returned also if a cached feature is requested mPointsLayer->startEditing(); QVariant::Type attrType = QVariant::Int; mPointsLayer->addAttribute( QgsField( QStringLiteral( "newAttr" ), attrType, QStringLiteral( "Int" ), 5, 0 ) ); mPointsLayer->commitChanges(); QVERIFY( mVectorLayerCache->featureAtId( 15, f ) ); QVERIFY( f.attribute( "newAttr" ).isValid() ); QgsFields allFields = mPointsLayer->fields(); int idx = allFields.indexFromName( QStringLiteral( "newAttr" ) ); mPointsLayer->startEditing(); mPointsLayer->deleteAttribute( idx ); mPointsLayer->commitChanges(); QVERIFY( mVectorLayerCache->featureAtId( 15, f ) ); QVERIFY( !f.attribute( "newAttr" ).isValid() ); } void TestVectorLayerCache::testFeatureActions() { QgsFeature f; // Get a random feature to clone mPointsLayer->getFeatures( QgsFeatureRequest().setFilterFid( 1 ) ).nextFeature( f ); // Add feature... mPointsLayer->startEditing(); QVERIFY( mPointsLayer->addFeature( f ) ); connect( mPointsLayer, SIGNAL( committedFeaturesAdded( QString, QgsFeatureList ) ), SLOT( onCommittedFeaturesAdded( QString, QgsFeatureList ) ) ); mPointsLayer->commitChanges(); disconnect( mPointsLayer, SIGNAL( committedFeaturesAdded( QString, QgsFeatureList ) ), this, SLOT( onCommittedFeaturesAdded( QString, QgsFeatureList ) ) ); QgsFeatureId fid = mAddedFeatures.last().id(); QVERIFY( mVectorLayerCache->featureAtId( fid, f ) ); // Delete feature... mPointsLayer->startEditing(); QVERIFY( mPointsLayer->deleteFeature( fid ) ); QVERIFY( !mVectorLayerCache->featureAtId( fid, f ) ); mPointsLayer->rollBack(); } void TestVectorLayerCache::testSubsetRequest() { QgsFeature f; QgsFields fields = mPointsLayer->fields(); QStringList requiredFields; requiredFields << QStringLiteral( "Class" ) << QStringLiteral( "Cabin Crew" ); mVectorLayerCache->featureAtId( 16, f ); QVariant a = f.attribute( 3 ); QgsFeatureIterator itSubset = mVectorLayerCache->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( requiredFields, fields ) ); while ( itSubset.nextFeature( f ) ) {} itSubset.close(); mVectorLayerCache->featureAtId( 16, f ); QVERIFY( a == f.attribute( 3 ) ); } void TestVectorLayerCache::testFullCache() { // cache is too small to fit all features QgsVectorLayerCache cache( mPointsLayer, 2 ); QVERIFY( !cache.hasFullCache() ); QVERIFY( cache.cacheSize() < mPointsLayer->featureCount() ); // but we set it to full cache cache.setFullCache( true ); // so now it should have sufficient size for all features QVERIFY( cache.cacheSize() >= mPointsLayer->featureCount() ); QVERIFY( cache.hasFullCache() ); // double check that everything is indeed in the cache QgsFeatureIterator it = mPointsLayer->getFeatures(); QgsFeature f; while ( it.nextFeature( f ) ) { QVERIFY( cache.isFidCached( f.id() ) ); } } void TestVectorLayerCache::testFullCacheThroughRequest() { // make sure cache is sufficient size for all features QgsVectorLayerCache cache( mPointsLayer, mPointsLayer->featureCount() * 2 ); QVERIFY( !cache.hasFullCache() ); // now request all features from cache QgsFeatureIterator it = cache.getFeatures( QgsFeatureRequest() ); QgsFeature f; while ( it.nextFeature( f ) ) { // suck in all features } // cache should now contain all features it = mPointsLayer->getFeatures(); while ( it.nextFeature( f ) ) { QVERIFY( cache.isFidCached( f.id() ) ); } // so it should be a full cache! QVERIFY( cache.hasFullCache() ); } void TestVectorLayerCache::testCanUseCacheForRequest() { //first get some feature ids from layer QgsFeature f; QgsFeatureIterator it = mPointsLayer->getFeatures(); it.nextFeature( f ); QgsFeatureId id1 = f.id(); it.nextFeature( f ); QgsFeatureId id2 = f.id(); QgsVectorLayerCache cache( mPointsLayer, 10 ); // initially nothing in cache, so can't use it to fulfill the request QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id1 ), it ) ); QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id2 ), it ) ); QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFids( QgsFeatureIds() << id1 << id2 ), it ) ); QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterRect( QgsRectangle( 1, 2, 3, 4 ) ), it ) ); QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterExpression( "$x<5" ), it ) ); // get just the first feature into the cache it = cache.getFeatures( QgsFeatureRequest().setFilterFid( id1 ) ); while ( it.nextFeature( f ) ) { } QCOMPARE( cache.cachedFeatureIds(), QgsFeatureIds() << id1 ); QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id1 ), it ) ); //verify that the returned iterator was correct QVERIFY( it.nextFeature( f ) ); QCOMPARE( f.id(), id1 ); QVERIFY( !it.nextFeature( f ) ); QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id2 ), it ) ); QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFids( QgsFeatureIds() << id1 << id2 ), it ) ); QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterRect( QgsRectangle( 1, 2, 3, 4 ) ), it ) ); QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterExpression( "$x<5" ), it ) ); // get feature 2 into cache it = cache.getFeatures( QgsFeatureRequest().setFilterFid( id2 ) ); while ( it.nextFeature( f ) ) { } QCOMPARE( cache.cachedFeatureIds(), QgsFeatureIds() << id1 << id2 ); QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id1 ), it ) ); QVERIFY( it.nextFeature( f ) ); QCOMPARE( f.id(), id1 ); QVERIFY( !it.nextFeature( f ) ); QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id2 ), it ) ); QVERIFY( it.nextFeature( f ) ); QCOMPARE( f.id(), id2 ); QVERIFY( !it.nextFeature( f ) ); QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFids( QgsFeatureIds() << id1 << id2 ), it ) ); QVERIFY( it.nextFeature( f ) ); QgsFeatureIds result; result << f.id(); QVERIFY( it.nextFeature( f ) ); result << f.id(); QCOMPARE( result, QgsFeatureIds() << id1 << id2 ); QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterRect( QgsRectangle( 1, 2, 3, 4 ) ), it ) ); QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterExpression( "$x<5" ), it ) ); // can only use rect/expression requests if cache has everything cache.setFullCache( true ); QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id1 ), it ) ); QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id2 ), it ) ); QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFids( QgsFeatureIds() << id1 << id2 ), it ) ); QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterRect( QgsRectangle( 1, 2, 3, 4 ) ), it ) ); QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterExpression( "$x<5" ), it ) ); } void TestVectorLayerCache::onCommittedFeaturesAdded( const QString& layerId, const QgsFeatureList& features ) { Q_UNUSED( layerId ) mAddedFeatures.append( features ); } QTEST_MAIN( TestVectorLayerCache ) #include "testqgsvectorlayercache.moc"