Skip to content

Commit fbad911

Browse files
committed
Add feature source test for QgsVectorLayerCache
While it's not a QgsFeatureSource subclass (yet), it behaves just like one so when can run the feature source conformance test suite over it. Fix a few minor issues identified by the test suite, and one potential crash (requesting an invalid id from a cache iterator crashes qgis)
1 parent 7d9cc13 commit fbad911

7 files changed

+202
-1
lines changed

python/core/qgsvectorlayercache.sip

+36
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,42 @@ class QgsVectorLayerCache : QObject
195195
:rtype: QgsVectorLayer
196196
%End
197197

198+
QgsCoordinateReferenceSystem sourceCrs() const;
199+
%Docstring
200+
Returns the coordinate reference system for features in the cache.
201+
:rtype: QgsCoordinateReferenceSystem
202+
%End
203+
204+
QgsFields fields() const;
205+
%Docstring
206+
Returns the fields associated with features in the cache.
207+
:rtype: QgsFields
208+
%End
209+
210+
QgsWkbTypes::Type wkbType() const;
211+
%Docstring
212+
Returns the geometry type for features in the cache.
213+
:rtype: QgsWkbTypes.Type
214+
%End
215+
216+
217+
int __len__() const;
218+
%Docstring
219+
Returns the number of features contained in the source, or -1
220+
if the feature count is unknown.
221+
:rtype: int
222+
%End
223+
%MethodCode
224+
sipRes = sipCpp->featureCount();
225+
%End
226+
227+
long featureCount() const;
228+
%Docstring
229+
Returns the number of features contained in the source, or -1
230+
if the feature count is unknown.
231+
:rtype: long
232+
%End
233+
198234
protected:
199235

200236
void requestCompleted( const QgsFeatureRequest &featureRequest, const QgsFeatureIds &fids );

src/core/qgscachedfeatureiterator.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,26 @@ QgsCachedFeatureIterator::QgsCachedFeatureIterator( QgsVectorLayerCache *vlCache
4343

4444
bool QgsCachedFeatureIterator::fetchFeature( QgsFeature &f )
4545
{
46+
f.setValid( false );
47+
4648
if ( mClosed )
4749
return false;
4850

4951
while ( mFeatureIdIterator != mFeatureIds.constEnd() )
5052
{
53+
if ( !mVectorLayerCache->mCache.contains( *mFeatureIdIterator ) )
54+
{
55+
++mFeatureIdIterator;
56+
continue;
57+
}
58+
5159
f = QgsFeature( *mVectorLayerCache->mCache[*mFeatureIdIterator]->feature() );
5260
++mFeatureIdIterator;
5361
if ( mRequest.acceptFeature( f ) )
62+
{
63+
f.setValid( true );
5464
return true;
65+
}
5566
}
5667
close();
5768
return false;
@@ -79,6 +90,11 @@ QgsCachedFeatureWriterIterator::QgsCachedFeatureWriterIterator( QgsVectorLayerCa
7990

8091
bool QgsCachedFeatureWriterIterator::fetchFeature( QgsFeature &f )
8192
{
93+
if ( mClosed )
94+
{
95+
f.setValid( false );
96+
return false;
97+
}
8298
if ( mFeatIt.nextFeature( f ) )
8399
{
84100
// As long as features can be fetched from the provider: Write them to cache

src/core/qgsvectorlayercache.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,26 @@ QgsVectorLayer *QgsVectorLayerCache::layer()
177177
return mLayer;
178178
}
179179

180+
QgsCoordinateReferenceSystem QgsVectorLayerCache::sourceCrs() const
181+
{
182+
return mLayer->crs();
183+
}
184+
185+
QgsWkbTypes::Type QgsVectorLayerCache::wkbType() const
186+
{
187+
return mLayer->wkbType();
188+
}
189+
190+
QgsFields QgsVectorLayerCache::fields() const
191+
{
192+
return mLayer->fields();
193+
}
194+
195+
long QgsVectorLayerCache::featureCount() const
196+
{
197+
return mLayer->featureCount();
198+
}
199+
180200
void QgsVectorLayerCache::requestCompleted( const QgsFeatureRequest &featureRequest, const QgsFeatureIds &fids )
181201
{
182202
// If a request is too large for the cache don't notify to prevent from indexing incomplete requests

src/core/qgsvectorlayercache.h

+33
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,39 @@ class CORE_EXPORT QgsVectorLayerCache : public QObject
248248
*/
249249
QgsVectorLayer *layer();
250250

251+
/**
252+
* Returns the coordinate reference system for features in the cache.
253+
*/
254+
QgsCoordinateReferenceSystem sourceCrs() const;
255+
256+
/**
257+
* Returns the fields associated with features in the cache.
258+
*/
259+
QgsFields fields() const;
260+
261+
/**
262+
* Returns the geometry type for features in the cache.
263+
*/
264+
QgsWkbTypes::Type wkbType() const;
265+
266+
#ifdef SIP_RUN
267+
268+
/**
269+
* Returns the number of features contained in the source, or -1
270+
* if the feature count is unknown.
271+
*/
272+
int __len__() const;
273+
% MethodCode
274+
sipRes = sipCpp->featureCount();
275+
% End
276+
#endif
277+
278+
/**
279+
* Returns the number of features contained in the source, or -1
280+
* if the feature count is unknown.
281+
*/
282+
long featureCount() const;
283+
251284
protected:
252285

253286
/**

tests/src/python/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ ADD_PYTHON_TEST(PyQgsVectorColorRamp test_qgsvectorcolorramp.py)
148148
ADD_PYTHON_TEST(PyQgsVectorFileWriter test_qgsvectorfilewriter.py)
149149
ADD_PYTHON_TEST(PyQgsVectorFileWriterTask test_qgsvectorfilewritertask.py)
150150
ADD_PYTHON_TEST(PyQgsVectorLayer test_qgsvectorlayer.py)
151+
ADD_PYTHON_TEST(PyQgsVectorLayerCache test_qgsvectorlayercache.py)
151152
ADD_PYTHON_TEST(PyQgsVectorLayerEditBuffer test_qgsvectorlayereditbuffer.py)
152153
ADD_PYTHON_TEST(PyQgsVectorLayerUtils test_qgsvectorlayerutils.py)
153154
ADD_PYTHON_TEST(PyQgsZonalStatistics test_qgszonalstatistics.py)

tests/src/python/featuresourcetestbase.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def assert_query(self, source, expression, expected):
139139
self.assertEqual(request.acceptFeature(f), f['pk'] in expected)
140140

141141
def runGetFeatureTests(self, source):
142-
assert len([f for f in source.getFeatures()]) == 5
142+
self.assertEqual(len([f for f in source.getFeatures()]), 5)
143143
self.assert_query(source, 'name ILIKE \'QGIS\'', [])
144144
self.assert_query(source, '"name" IS NULL', [5])
145145
self.assert_query(source, '"name" IS NOT NULL', [1, 2, 3, 4])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for QgsVectorLayerCache.
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
"""
9+
__author__ = 'Nyall Dawson'
10+
__date__ = '08/06/2017'
11+
__copyright__ = 'Copyright 2017, The QGIS Project'
12+
# This will get replaced with a git SHA1 when you do a git archive
13+
__revision__ = '$Format:%H$'
14+
15+
import qgis # NOQA
16+
17+
import os
18+
19+
from qgis.PyQt.QtCore import QVariant, Qt
20+
from qgis.PyQt.QtGui import QPainter
21+
from qgis.PyQt.QtXml import QDomDocument
22+
23+
from qgis.core import (QgsWkbTypes,
24+
QgsVectorLayer,
25+
QgsVectorLayerCache,
26+
QgsRectangle,
27+
QgsFeature,
28+
QgsFeatureRequest,
29+
QgsGeometry,
30+
QgsPointXY,
31+
QgsField,
32+
QgsFields,
33+
QgsCoordinateReferenceSystem,
34+
QgsProject,
35+
QgsPoint,
36+
NULL)
37+
from qgis.testing import start_app, unittest
38+
from featuresourcetestbase import FeatureSourceTestCase
39+
from utilities import unitTestDataPath
40+
start_app()
41+
42+
43+
class TestQgsVectorLayerCache(unittest.TestCase, FeatureSourceTestCase):
44+
45+
@classmethod
46+
def getSource(cls):
47+
cache = QgsVectorLayerCache(cls.vl, 100)
48+
return cache
49+
50+
@classmethod
51+
def setUpClass(cls):
52+
"""Run before all tests"""
53+
# Create test layer for FeatureSourceTestCase
54+
cls.vl = QgsVectorLayer(
55+
'Point?crs=epsg:4326&field=pk:integer&field=cnt:integer&field=name:string(0)&field=name2:string(0)&field=num_char:string&key=pk',
56+
'test', 'memory')
57+
assert (cls.vl.isValid())
58+
59+
f1 = QgsFeature(5)
60+
f1.setAttributes([5, -200, NULL, 'NuLl', '5'])
61+
f1.setGeometry(QgsGeometry.fromWkt('Point (-71.123 78.23)'))
62+
63+
f2 = QgsFeature(3)
64+
f2.setAttributes([3, 300, 'Pear', 'PEaR', '3'])
65+
66+
f3 = QgsFeature(1)
67+
f3.setAttributes([1, 100, 'Orange', 'oranGe', '1'])
68+
f3.setGeometry(QgsGeometry.fromWkt('Point (-70.332 66.33)'))
69+
70+
f4 = QgsFeature(2)
71+
f4.setAttributes([2, 200, 'Apple', 'Apple', '2'])
72+
f4.setGeometry(QgsGeometry.fromWkt('Point (-68.2 70.8)'))
73+
74+
f5 = QgsFeature(4)
75+
f5.setAttributes([4, 400, 'Honey', 'Honey', '4'])
76+
f5.setGeometry(QgsGeometry.fromWkt('Point (-65.32 78.3)'))
77+
78+
assert cls.vl.dataProvider().addFeatures([f1, f2, f3, f4, f5])
79+
cls.source = QgsVectorLayerCache(cls.vl, 100)
80+
81+
def testGetFeaturesSubsetAttributes2(self):
82+
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
83+
its features as direct copies (due to implicit sharing of QgsFeature)
84+
"""
85+
pass
86+
87+
def testGetFeaturesNoGeometry(self):
88+
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
89+
its features as direct copies (due to implicit sharing of QgsFeature)
90+
"""
91+
pass
92+
93+
94+
if __name__ == '__main__':
95+
unittest.main()

0 commit comments

Comments
 (0)