[MERGED] osmo-gsm-tester[master]: config: Fix combination of lists

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

Neels Hofmeyr gerrit-no-reply at lists.osmocom.org
Sat Sep 16 19:51:34 UTC 2017


Neels Hofmeyr has submitted this change and it was merged.

Change subject: config: Fix combination of lists
......................................................................


config: Fix combination of lists

This commit fixes combination of resources containing lists.

For lists containing complex types, it has been decided to handle them
as sorted list, where position in list matters. In this case, combine is
called recursively for each element in dest and src sharing position in
the list, and assumes that if one list is shorter than the other, then
it has to be combined against empty set for that tye.
For instance this is useful when defining trx_list properties, where a
BTS can have a different amount of TRX but we may be interested in
restricting the first TRX and don't care about extra TRX.

For lists containing simple types (eg. integers or strings), we just want
to merge both lists and we only need to check if the value is already there,
ie. handle them as unsortered sets. This case won't work if we call combine
for each element of the list because for a simple case it will just end up
checking if a[i] == b[i].
This kind of operation for simple types is needed in later commits where
cipher attribute is introduced. Without this patch, having following 2
scenarios and trying them to use together "-s foosuite:cipher-a50+ciphera51"
will fail:

cipher_a50.conf:
  bts:
  - ciphers:
    - 'a5 0'

cipher_a51.conf
  bts:
  - ciphers:
    - 'a5 1'

ValueError: cannot combine dicts, conflicting items (values 'a5 0' and 'a5 1')

Change-Id: Ib7a38f10eb9de338a77bf1fa3afceb9df1532015
---
M selftest/config_test.ok
M selftest/config_test.py
M src/osmo_gsm_tester/config.py
M src/osmo_gsm_tester/util.py
4 files changed, 158 insertions(+), 3 deletions(-)

Approvals:
  Neels Hofmeyr: Looks good to me, approved
  Jenkins Builder: Verified



diff --git a/selftest/config_test.ok b/selftest/config_test.ok
index 80b5a06..e1fb87d 100644
--- a/selftest/config_test.ok
+++ b/selftest/config_test.ok
@@ -93,3 +93,20 @@
 Validation: Error
 --- imsi[]: ERR: ValueError: Invalid IMSI: None
 Validation: Error
+- Combine dicts:
+- Combine dicts 2:
+- Combine lists:
+- Combine lists 2:
+- Combine lists 3:
+ValueError expected
+- Combine lists 4:
+ValueError expected
+- Combine lists 5:
+ValueError expected
+- Combine lists 6:
+- Combine lists 7:
+- Combine lists 8:
+- Combine lists 9:
+- Combine lists 10:
+- Combine lists 13:
+- Combine lists 14:
diff --git a/selftest/config_test.py b/selftest/config_test.py
index 61ec73a..fa86adc 100755
--- a/selftest/config_test.py
+++ b/selftest/config_test.py
@@ -112,4 +112,105 @@
 c['imsi'][2] = None
 val(c)
 
+print('- Combine dicts:')
+a = {'times': '2'}
+b = {'type': 'osmo-bts-trx'}
+res = {'times': '2', 'type': 'osmo-bts-trx'}
+config.combine(a, b)
+assert a == res
+
+print('- Combine dicts 2:')
+a = {'times': '1', 'label': 'foo', 'type': 'osmo-bts-trx'}
+b = {'type': 'osmo-bts-trx'}
+res = {'times': '1', 'label': 'foo', 'type': 'osmo-bts-trx'}
+config.combine(a, b)
+assert a == res
+
+print('- Combine lists:')
+a = { 'a_list': ['x', 'y', 'z'] }
+b = { 'a_list': ['y'] }
+res = {'a_list': ['x', 'y', 'z']}
+config.combine(a, b)
+assert a == res
+
+print('- Combine lists 2:')
+a = { 'a_list': ['x'] }
+b = { 'a_list': ['w', 'u', 'x', 'y', 'z'] }
+res = {'a_list': ['x', 'w', 'u', 'y', 'z']}
+config.combine(a, b)
+assert a == res
+
+print('- Combine lists 3:')
+a = { 'a_list': ['x', 3] }
+b = { 'a_list': ['y', 'z'] }
+try:
+    config.combine(a, b)
+except ValueError:
+    print("ValueError expected")
+
+print('- Combine lists 4:')
+a = { 'a_list': [2, 3] }
+b = { 'a_list': ['y', 'z'] }
+try:
+    config.combine(a, b)
+except ValueError:
+    print("ValueError expected")
+
+print('- Combine lists 5:')
+a = { 'a_list': [{}, {}] }
+b = { 'a_list': ['y', 'z'] }
+try:
+    config.combine(a, b)
+except ValueError:
+    print("ValueError expected")
+
+print('- Combine lists 6:')
+a = { 'a_list': [{}, {}] }
+b = { 'a_list': [{}] }
+res = {'a_list': [{}, {}]}
+config.combine(a, b)
+assert a == res
+
+print('- Combine lists 7:')
+a = { 'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}] }
+b = { 'type': 'osmo-bts-trx', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}] }
+res = {'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}], 'type': 'osmo-bts-trx'}
+config.combine(a, b)
+assert a == res
+
+print('- Combine lists 8:')
+a = { 'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}] }
+b = { 'type': 'osmo-bts-trx', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}] }
+res = {'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}], 'type': 'osmo-bts-trx'}
+config.combine(a, b)
+assert a == res
+
+print('- Combine lists 9:')
+a = { 'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}] }
+b = { 'type': 'osmo-bts-trx', 'trx': [{'nominal power': '10'}] }
+res = {'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}], 'type': 'osmo-bts-trx'}
+config.combine(a, b)
+assert a == res
+
+print('- Combine lists 10:')
+a = { 'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}] }
+b = { 'type': 'osmo-bts-trx', 'trx': [{}, {'nominal power': '12'}] }
+res = {'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}], 'type': 'osmo-bts-trx'}
+config.combine(a, b)
+assert a == res
+
+print('- Combine lists 13:')
+a = { 'times': '1', 'label': 'foo', 'trx': [{}, {'nominal power': '12'}] }
+b = { 'type': 'osmo-bts-trx', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}] }
+res = {'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}], 'type': 'osmo-bts-trx'}
+config.combine(a, b)
+assert a == res
+
+print('- Combine lists 14:')
+a = { 'times': '1', 'label': 'foo', 'trx': [] }
+b = { 'type': 'osmo-bts-trx', 'trx': [] }
+res = {'times': '1', 'label': 'foo', 'trx': [], 'type': 'osmo-bts-trx'}
+config.combine(a, b)
+assert a == res
+
 # vim: expandtab tabstop=4 shiftwidth=4
diff --git a/src/osmo_gsm_tester/config.py b/src/osmo_gsm_tester/config.py
index 27ce428..0721c30 100644
--- a/src/osmo_gsm_tester/config.py
+++ b/src/osmo_gsm_tester/config.py
@@ -245,9 +245,23 @@
     if is_list(dest):
         if not is_list(src):
             raise ValueError('cannot combine list with a value of type: %r' % type(src))
-        for i in range(len(src)):
-            log.ctx(idx=i)
-            combine(dest[i], src[i])
+        # Validate that all elements in both lists are of the same type:
+        t = util.list_validate_same_elem_type(src + dest)
+        if t is None:
+            return # both lists are empty, return
+        # For lists of complex objects, we expect them to be sorted lists:
+        if t in (dict, list, tuple):
+            for i in range(len(dest)):
+                log.ctx(idx=i)
+                src_it = src[i] if i < len(src) else util.empty_instance_type(t)
+                combine(dest[i], src_it)
+            for i in range(len(dest), len(src)):
+                log.ctx(idx=i)
+                dest.append(src[i])
+        else: # for lists of basic elements, we handle them as unsorted sets:
+            for elem in src:
+                if elem not in dest:
+                    dest.append(elem)
         return
     if dest == src:
         return
diff --git a/src/osmo_gsm_tester/util.py b/src/osmo_gsm_tester/util.py
index 197dc97..bb4c524 100644
--- a/src/osmo_gsm_tester/util.py
+++ b/src/osmo_gsm_tester/util.py
@@ -314,4 +314,27 @@
         return True
     raise ValueError('Invalid BOOL field: %r' % val)
 
+def list_validate_same_elem_type(li):
+    '''
+    Checks that all elements in the list are of the same type and returns that type.
+    If the list is empty, returns None
+    If one of the elements is not of the same type, it throws a ValueError exception.
+    '''
+    if len(li) == 0:
+        return None
+    t = type(li[0])
+    for elem in li:
+        if type(elem) != t:
+            raise ValueError('List contains elements of different types: %r vs %r' % (t, type(elem)))
+    return t
+
+def empty_instance_type(t):
+    if t == dict:
+        return {}
+    elif t == list:
+        return []
+    elif t == tuple:
+        return ()
+    raise ValueError('type %r not supported!' % t)
+
 # vim: expandtab tabstop=4 shiftwidth=4

-- 
To view, visit https://gerrit.osmocom.org/3722
To unsubscribe, visit https://gerrit.osmocom.org/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ib7a38f10eb9de338a77bf1fa3afceb9df1532015
Gerrit-PatchSet: 10
Gerrit-Project: osmo-gsm-tester
Gerrit-Branch: master
Gerrit-Owner: Pau Espin Pedrol <pespin at sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: Neels Hofmeyr <nhofmeyr at sysmocom.de>
Gerrit-Reviewer: Pau Espin Pedrol <pespin at sysmocom.de>



More information about the gerrit-log mailing list