Skip to content
Permalink
Browse files

Add support for unordered comparison of features of layers

This is useful when an algorithm returns features in no particular order
and sorting features by attributes does not help because there may be
features with the same attributes, giving non-unique sorting orders.
  • Loading branch information
wonder-sk committed May 9, 2018
1 parent 73d10af commit ef145afca686ecc10ae813c43be90132154c9b58
Showing with 117 additions and 62 deletions.
  1. +117 −62 python/testing/__init__.py
@@ -109,6 +109,52 @@ def checkLayersEqual(self, layer_expected, layer_result, use_asserts=False, **kw
except KeyError:
topo_equal_check = False

try:
unordered = compare['unordered']
except KeyError:
unordered = False

if unordered:
features_expected = [f for f in layer_expected.getFeatures(request)]
for feat in layer_result.getFeatures(request):
feat_expected_equal = None
for feat_expected in features_expected:
if self.checkGeometriesEqual(feat.geometry(), feat_expected.geometry(),
feat.id(), feat_expected.id(),
False, precision, topo_equal_check) and \
self.checkAttributesEqual(feat, feat_expected, layer_expected.fields(), False, compare):
feat_expected_equal = feat_expected
break

if feat_expected_equal is not None:
features_expected.remove(feat_expected_equal)
else:
if use_asserts:
_TestCase.assertTrue(
self, False,
'Unexpected result feature: fid {}, geometry: {}, attributes: {}'.format(
feat.id(),
feat.geometry().constGet().asWkt(precision) if feat.geometry() else 'NULL',
feat.attributes())
)
else:
return False

if len(features_expected) != 0:
if use_asserts:
lst_missing = []
for feat in features_expected:
lst_missing.append('fid {}, geometry: {}, attributes: {}'.format(
feat.id(),
feat.geometry().constGet().asWkt(precision) if feat.geometry() else 'NULL',
feat.attributes())
)
_TestCase.assertTrue(self, False, 'Some expected features not found in results:\n' + '\n'.join(lst_missing))
else:
return False

return True

def sort_by_pk_or_fid(f):
if 'pk' in kwargs and kwargs['pk'] is not None:
key = kwargs['pk']
@@ -129,68 +175,12 @@ def sort_by_pk_or_fid(f):
feats[0].id(),
feats[1].id(),
use_asserts, precision, topo_equal_check)
if not eq and use_asserts:
if not eq and not use_asserts:
return False

for attr_expected, field_expected in zip(feats[0].attributes(), layer_expected.fields().toList()):
try:
cmp = compare['fields'][field_expected.name()]
except KeyError:
try:
cmp = compare['fields']['__all__']
except KeyError:
cmp = {}

# Skip field
if 'skip' in cmp:
continue

if use_asserts:
_TestCase.assertIn(
self,
field_expected.name().lower(),
[name.lower() for name in feats[1].fields().names()])

attr_result = feats[1][field_expected.name()]
field_result = [fld for fld in layer_expected.fields().toList() if fld.name() == field_expected.name()][0]

# Cast field to a given type
if 'cast' in cmp:
if cmp['cast'] == 'int':
attr_expected = int(attr_expected) if attr_expected else None
attr_result = int(attr_result) if attr_result else None
if cmp['cast'] == 'float':
attr_expected = float(attr_expected) if attr_expected else None
attr_result = float(attr_result) if attr_result else None
if cmp['cast'] == 'str':
attr_expected = str(attr_expected) if attr_expected else None
attr_result = str(attr_result) if attr_result else None

# Round field (only numeric so it works with __all__)
if 'precision' in cmp and field_expected.type() in [QVariant.Int, QVariant.Double, QVariant.LongLong]:
if not attr_expected == NULL:
attr_expected = round(attr_expected, cmp['precision'])
if not attr_result == NULL:
attr_result = round(attr_result, cmp['precision'])

if use_asserts:
_TestCase.assertEqual(
self,
attr_expected,
attr_result,
'Features {}/{} differ in attributes\n\n * Field expected: {} ({})\n * result : {} ({})\n\n * Expected: {} != Result : {}'.format(
feats[0].id(),
feats[1].id(),
field_expected.name(),
field_expected.typeName(),
field_result.name(),
field_result.typeName(),
repr(attr_expected),
repr(attr_result)
)
)
elif attr_expected != attr_result:
return False
eq = self.checkAttributesEqual(feats[0], feats[1], layer_expected.fields(), use_asserts, compare)
if not eq and not use_asserts:
return False

return True

@@ -227,13 +217,78 @@ def checkGeometriesEqual(self, geom0, geom1, geom0_id, geom1_id, use_asserts=Fal
'Features (Expected fid: {}, Result fid: {}) differ in geometry: \n\n Expected geometry:\n {}\n\n Result geometry:\n {}'.format(
geom0_id,
geom1_id,
geom0.constGet().asWkt(precision) if geom0 is not None else 'NULL',
geom1.constGet().asWkt(precision) if geom1 is not None else 'NULL'
geom0.constGet().asWkt(precision) if not geom0.isNull() else 'NULL',
geom1.constGet().asWkt(precision) if not geom1.isNull() else 'NULL'
)
)
else:
return equal

def checkAttributesEqual(self, feat0, feat1, fields_expected, use_asserts, compare):
""" Checks whether attributes of two features are the same """

for attr_expected, field_expected in zip(feat0.attributes(), fields_expected.toList()):
try:
cmp = compare['fields'][field_expected.name()]
except KeyError:
try:
cmp = compare['fields']['__all__']
except KeyError:
cmp = {}

# Skip field
if 'skip' in cmp:
continue

if use_asserts:
_TestCase.assertIn(
self,
field_expected.name().lower(),
[name.lower() for name in feat1.fields().names()])

attr_result = feat1[field_expected.name()]
field_result = [fld for fld in fields_expected.toList() if fld.name() == field_expected.name()][0]

# Cast field to a given type
if 'cast' in cmp:
if cmp['cast'] == 'int':
attr_expected = int(attr_expected) if attr_expected else None
attr_result = int(attr_result) if attr_result else None
if cmp['cast'] == 'float':
attr_expected = float(attr_expected) if attr_expected else None
attr_result = float(attr_result) if attr_result else None
if cmp['cast'] == 'str':
attr_expected = str(attr_expected) if attr_expected else None
attr_result = str(attr_result) if attr_result else None

# Round field (only numeric so it works with __all__)
if 'precision' in cmp and field_expected.type() in [QVariant.Int, QVariant.Double, QVariant.LongLong]:
if not attr_expected == NULL:
attr_expected = round(attr_expected, cmp['precision'])
if not attr_result == NULL:
attr_result = round(attr_result, cmp['precision'])

if use_asserts:
_TestCase.assertEqual(
self,
attr_expected,
attr_result,
'Features {}/{} differ in attributes\n\n * Field expected: {} ({})\n * result : {} ({})\n\n * Expected: {} != Result : {}'.format(
feat0.id(),
feat1.id(),
field_expected.name(),
field_expected.typeName(),
field_result.name(),
field_result.typeName(),
repr(attr_expected),
repr(attr_result)
)
)
elif attr_expected != attr_result:
return False

return True


class _UnexpectedSuccess(Exception):

0 comments on commit ef145af

Please sign in to comment.
You can’t perform that action at this time.