Skip to content

Commit a0b2605

Browse files
committedDec 16, 2016
Add some data modification tests to providertestbase
Tests for addFeatures, deleteFeatures, changeAttributeValues and changeGeometryValues Implemented for memory, ogr and spatialite providers
1 parent 3c39a1c commit a0b2605

File tree

4 files changed

+199
-13
lines changed

4 files changed

+199
-13
lines changed
 

‎tests/src/python/providertestbase.py

Lines changed: 154 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
QgsAbstractFeatureIterator,
2424
QgsExpressionContextScope,
2525
QgsExpressionContext,
26+
QgsVectorDataProvider,
2627
NULL
2728
)
2829

@@ -44,14 +45,17 @@ class ProviderTestCase(object):
4445
evaluation are equal.
4546
'''
4647

47-
def testGetFeatures(self):
48+
def testGetFeatures(self, provider=None, extra_features=[], skip_features=[], changed_attributes={}, changed_geometries={}):
4849
""" Test that expected results are returned when fetching all features """
4950

5051
# IMPORTANT - we do not use `for f in provider.getFeatures()` as we are also
5152
# testing that existing attributes & geometry in f are overwritten correctly
5253
# (for f in ... uses a new QgsFeature for every iteration)
5354

54-
it = self.provider.getFeatures()
55+
if not provider:
56+
provider = self.provider
57+
58+
it = provider.getFeatures()
5559
f = QgsFeature()
5660
attributes = {}
5761
geometries = {}
@@ -73,16 +77,38 @@ def testGetFeatures(self):
7377
1: [1, 100, 'Orange', 'oranGe', '1'],
7478
2: [2, 200, 'Apple', 'Apple', '2'],
7579
4: [4, 400, 'Honey', 'Honey', '4']}
76-
self.assertEqual(attributes, expected_attributes, 'Expected {}, got {}'.format(expected_attributes, attributes))
7780

7881
expected_geometries = {1: 'Point (-70.332 66.33)',
7982
2: 'Point (-68.2 70.8)',
8083
3: None,
8184
4: 'Point(-65.32 78.3)',
8285
5: 'Point(-71.123 78.23)'}
86+
for f in extra_features:
87+
expected_attributes[f[0]] = f.attributes()
88+
if f.hasGeometry():
89+
expected_geometries[f[0]] = f.geometry().exportToWkt()
90+
else:
91+
expected_geometries[f[0]] = None
92+
93+
for i in skip_features:
94+
del expected_attributes[i]
95+
del expected_geometries[i]
96+
for i, a in changed_attributes.items():
97+
for attr_idx, v in a.items():
98+
expected_attributes[i][attr_idx] = v
99+
for i, g, in changed_geometries.items():
100+
if g:
101+
expected_geometries[i] = g.exportToWkt()
102+
else:
103+
expected_geometries[i] = None
104+
105+
self.assertEqual(attributes, expected_attributes, 'Expected {}, got {}'.format(expected_attributes, attributes))
106+
107+
self.assertEqual(len(expected_geometries), len(geometries))
108+
83109
for pk, geom in list(expected_geometries.items()):
84110
if geom:
85-
assert compareWkt(geom, geometries[pk]), "Geometry {} mismatch Expected:\n{}\nGot:\n{}\n".format(pk, geom, geometries[pk].exportToWkt())
111+
assert compareWkt(geom, geometries[pk]), "Geometry {} mismatch Expected:\n{}\nGot:\n{}\n".format(pk, geom, geometries[pk])
86112
else:
87113
self.assertFalse(geometries[pk], 'Expected null geometry for {}'.format(pk))
88114

@@ -652,3 +678,127 @@ def testGetFeaturesWithGeometry(self):
652678

653679
assert f.hasGeometry(), 'Expected geometry, got none'
654680
self.assertTrue(f.isValid())
681+
682+
def testAddFeature(self):
683+
if not getattr(self, 'getEditableLayer', None):
684+
return
685+
686+
l = self.getEditableLayer()
687+
self.assertTrue(l.isValid())
688+
689+
f1 = QgsFeature()
690+
f1.setAttributes([6, -220, NULL, 'String', '15'])
691+
f1.setGeometry(QgsGeometry.fromWkt('Point (-72.345 71.987)'))
692+
693+
f2 = QgsFeature()
694+
f2.setAttributes([7, 330, 'Coconut', 'CoCoNut', '13'])
695+
696+
if l.dataProvider().capabilities() & QgsVectorDataProvider.AddFeatures:
697+
# expect success
698+
result, added = l.dataProvider().addFeatures([f1, f2])
699+
self.assertTrue(result, 'Provider reported AddFeatures capability, but returned False to addFeatures')
700+
f1.setId(added[0].id())
701+
f2.setId(added[1].id())
702+
703+
# check result
704+
self.testGetFeatures(l.dataProvider(), [f1, f2])
705+
706+
# add empty list, should return true for consistency
707+
self.assertTrue(l.dataProvider().addFeatures([]))
708+
709+
else:
710+
# expect fail
711+
self.assertFalse(l.dataProvider().addFeatures([f1, f2]), 'Provider reported no AddFeatures capability, but returned true to addFeatures')
712+
713+
def testDeleteFeatures(self):
714+
if not getattr(self, 'getEditableLayer', None):
715+
return
716+
717+
l = self.getEditableLayer()
718+
self.assertTrue(l.isValid())
719+
720+
#find 2 features to delete
721+
features = [f for f in l.dataProvider().getFeatures()]
722+
to_delete = [f.id() for f in features if f.attributes()[0] in [1, 3]]
723+
724+
if l.dataProvider().capabilities() & QgsVectorDataProvider.DeleteFeatures:
725+
# expect success
726+
result = l.dataProvider().deleteFeatures(to_delete)
727+
self.assertTrue(result, 'Provider reported DeleteFeatures capability, but returned False to deleteFeatures')
728+
729+
# check result
730+
self.testGetFeatures(l.dataProvider(), skip_features=[1, 3])
731+
732+
# delete empty list, should return true for consistency
733+
self.assertTrue(l.dataProvider().deleteFeatures([]))
734+
735+
else:
736+
# expect fail
737+
self.assertFalse(l.dataProvider().deleteFeatures(to_delete),
738+
'Provider reported no DeleteFeatures capability, but returned true to deleteFeatures')
739+
740+
def testChangeAttributes(self):
741+
if not getattr(self, 'getEditableLayer', None):
742+
return
743+
744+
l = self.getEditableLayer()
745+
self.assertTrue(l.isValid())
746+
747+
#find 2 features to change
748+
features = [f for f in l.dataProvider().getFeatures()]
749+
# need to keep order here
750+
to_change = [f for f in features if f.attributes()[0] == 1]
751+
to_change.extend([f for f in features if f.attributes()[0] == 3])
752+
# changes by feature id, for changeAttributeValues call
753+
changes = {to_change[0].id(): {1: 501, 3: 'new string'}, to_change[1].id(): {1: 502, 4: 'NEW'}}
754+
# changes by pk, for testing after retrieving changed features
755+
new_attr_map = {1: {1: 501, 3: 'new string'}, 3: {1: 502, 4: 'NEW'}}
756+
757+
if l.dataProvider().capabilities() & QgsVectorDataProvider.ChangeAttributeValues:
758+
# expect success
759+
result = l.dataProvider().changeAttributeValues(changes)
760+
self.assertTrue(result, 'Provider reported ChangeAttributeValues capability, but returned False to changeAttributeValues')
761+
762+
# check result
763+
self.testGetFeatures(l.dataProvider(), changed_attributes=new_attr_map)
764+
765+
# change empty list, should return true for consistency
766+
self.assertTrue(l.dataProvider().changeAttributeValues({}))
767+
768+
else:
769+
# expect fail
770+
self.assertFalse(l.dataProvider().changeAttributeValues(changes),
771+
'Provider reported no ChangeAttributeValues capability, but returned true to changeAttributeValues')
772+
773+
def testChangeGeometries(self):
774+
if not getattr(self, 'getEditableLayer', None):
775+
return
776+
777+
l = self.getEditableLayer()
778+
self.assertTrue(l.isValid())
779+
780+
# find 2 features to change
781+
features = [f for f in l.dataProvider().getFeatures()]
782+
to_change = [f for f in features if f.attributes()[0] == 1]
783+
to_change.extend([f for f in features if f.attributes()[0] == 3])
784+
# changes by feature id, for changeGeometryValues call
785+
changes = {to_change[0].id(): QgsGeometry.fromWkt('Point (10 20)'), to_change[1].id(): QgsGeometry()}
786+
# changes by pk, for testing after retrieving changed features
787+
new_geom_map = {1: QgsGeometry.fromWkt('Point ( 10 20 )'), 3: QgsGeometry()}
788+
789+
if l.dataProvider().capabilities() & QgsVectorDataProvider.ChangeGeometries:
790+
# expect success
791+
result = l.dataProvider().changeGeometryValues(changes)
792+
self.assertTrue(result,
793+
'Provider reported ChangeGeometries capability, but returned False to changeGeometryValues')
794+
795+
# check result
796+
self.testGetFeatures(l.dataProvider(), changed_geometries=new_geom_map)
797+
798+
# change empty list, should return true for consistency
799+
self.assertTrue(l.dataProvider().changeGeometryValues({}))
800+
801+
else:
802+
# expect fail
803+
self.assertFalse(l.dataProvider().changeGeometryValues(changes),
804+
'Provider reported no ChangeGeometries capability, but returned true to changeGeometryValues')

‎tests/src/python/test_provider_memory.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,11 @@
4646
class TestPyQgsMemoryProvider(unittest.TestCase, ProviderTestCase):
4747

4848
@classmethod
49-
def setUpClass(cls):
50-
"""Run before all tests"""
51-
# Create test layer
52-
cls.vl = QgsVectorLayer('Point?crs=epsg:4326&field=pk:integer&field=cnt:integer&field=name:string(0)&field=name2:string(0)&field=num_char:string&key=pk',
53-
'test', 'memory')
54-
assert (cls.vl.isValid())
55-
cls.provider = cls.vl.dataProvider()
49+
def createLayer(cls):
50+
vl = QgsVectorLayer(
51+
'Point?crs=epsg:4326&field=pk:integer&field=cnt:integer&field=name:string(0)&field=name2:string(0)&field=num_char:string&key=pk',
52+
'test', 'memory')
53+
assert (vl.isValid())
5654

5755
f1 = QgsFeature()
5856
f1.setAttributes([5, -200, NULL, 'NuLl', '5'])
@@ -73,7 +71,16 @@ def setUpClass(cls):
7371
f5.setAttributes([4, 400, 'Honey', 'Honey', '4'])
7472
f5.setGeometry(QgsGeometry.fromWkt('Point (-65.32 78.3)'))
7573

76-
cls.provider.addFeatures([f1, f2, f3, f4, f5])
74+
vl.dataProvider().addFeatures([f1, f2, f3, f4, f5])
75+
return vl
76+
77+
@classmethod
78+
def setUpClass(cls):
79+
"""Run before all tests"""
80+
# Create test layer
81+
cls.vl = cls.createLayer()
82+
assert (cls.vl.isValid())
83+
cls.provider = cls.vl.dataProvider()
7784

7885
# poly layer
7986
cls.poly_vl = QgsVectorLayer('Polygon?crs=epsg:4326&field=pk:integer&key=pk',
@@ -102,6 +109,9 @@ def setUpClass(cls):
102109
def tearDownClass(cls):
103110
"""Run after all tests"""
104111

112+
def getEditableLayer(self):
113+
return self.createLayer()
114+
105115
def testGetFeaturesSubsetAttributes2(self):
106116
""" Override and skip this test for memory provider, as it's actually more efficient for the memory provider to return
107117
its features as direct copies (due to implicit sharing of QgsFeature)

‎tests/src/python/test_provider_shapefile.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,17 @@ def tearDownClass(cls):
7676
for dirname in cls.dirs_to_cleanup:
7777
shutil.rmtree(dirname, True)
7878

79+
def getEditableLayer(self):
80+
tmpdir = tempfile.mkdtemp()
81+
self.dirs_to_cleanup.append(tmpdir)
82+
srcpath = os.path.join(TEST_DATA_DIR, 'provider')
83+
for file in glob.glob(os.path.join(srcpath, 'shapefile.*')):
84+
shutil.copy(os.path.join(srcpath, file), tmpdir)
85+
datasource = os.path.join(tmpdir, 'shapefile.shp')
86+
87+
vl = QgsVectorLayer('{}|layerid=0'.format(datasource), 'test', 'ogr')
88+
return vl
89+
7990
def enableCompiler(self):
8091
QSettings().setValue('/qgis/compileExpressions', True)
8192

‎tests/src/python/test_provider_spatialite.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,13 +154,28 @@ def setUpClass(cls):
154154
cur.execute("COMMIT")
155155
con.close()
156156

157+
cls.dirs_to_cleanup = []
158+
157159
@classmethod
158160
def tearDownClass(cls):
159161
"""Run after all tests"""
160162
# for the time being, keep the file to check with qgis
161163
# if os.path.exists(cls.dbname) :
162164
# os.remove(cls.dbname)
163-
pass
165+
for dirname in cls.dirs_to_cleanup:
166+
shutil.rmtree(dirname, True)
167+
168+
def getEditableLayer(self):
169+
tmpdir = tempfile.mkdtemp()
170+
self.dirs_to_cleanup.append(tmpdir)
171+
srcpath = os.path.join(TEST_DATA_DIR, 'provider')
172+
datasource = os.path.join(tmpdir, 'spatialite.db')
173+
shutil.copy(os.path.join(srcpath, 'spatialite.db'), datasource)
174+
175+
vl = QgsVectorLayer(
176+
'dbname=\'{}\' table="somedata" (geom) sql='.format(datasource), 'test',
177+
'spatialite')
178+
return vl
164179

165180
def setUp(self):
166181
"""Run before each test."""

0 commit comments

Comments
 (0)
Please sign in to comment.