Skip to content

Commit

Permalink
Add support for unordered comparison of features of layers
Browse files Browse the repository at this point in the history
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 10, 2018
1 parent 73d10af commit ef145af
Showing 1 changed file with 117 additions and 62 deletions.
179 changes: 117 additions & 62 deletions python/testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand All @@ -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

Expand Down Expand Up @@ -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):

Expand Down

0 comments on commit ef145af

Please sign in to comment.