mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-11-04 00:04:25 -05:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									73d10afe31
								
							
						
					
					
						commit
						ef145afca6
					
				@ -109,6 +109,52 @@ class TestCase(_TestCase):
 | 
			
		||||
        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 @@ class TestCase(_TestCase):
 | 
			
		||||
                                           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 @@ class TestCase(_TestCase):
 | 
			
		||||
                '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):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user