test.py 28.2 KB
Newer Older
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
1
# Copyright (C) 2023-2024  Nexedi SA and Contributors.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
19 20 21 22 23 24 25 26 27 28 29
# Unit-tests for generic software for Amarisoft 4G/5G stack.
#
# Here we only verify generated configuration because it is not possible to run
# Amarisoft software on testnodes due to licensing restrictions. End-to-end
# testing complements unit-testing by verifying how LTE works for real on
# dedicated hardware test setup.
#
# Here we test:
#
# - enb     (see TestENB_*)
# - uesim   (see TestUEsim_*)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
30
#
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
31
# Currently there is no tests for core-network here, because for core-network
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
32
# there is no difference in between generic and ORS modes and core-network is
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
33
# already verified by test_ors.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
34

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
35

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
36
import os
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
37
import json
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
38 39 40
import io
import yaml
import pcpp
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
41
import xmltodict
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
42

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
43 44 45 46
import sys
sys.path.insert(0, '../ru')
import xbuildout

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
47
import unittest
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
48 49
from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
50
setUpModule, _AmariTestCase = makeModuleSetUpAndTestCaseClass(
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
51 52 53
    os.path.abspath(
        os.path.join(os.path.dirname(__file__), '..', 'software.cfg')))

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
54

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
55
# ---- building blocks to construct cell/peer parameters ----
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
56
#
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
57 58 59 60 61
# - TDD/FDD indicate TDD/FDD mode.
# - LTE/NR  indicate LTE/NR cell with given downlink frequency.
# - BW      indicates specified bandwidth.
# - CENB    indicates a ENB-kind cell.
# - CUE     indicates an UE-kind cell.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
62
# - TAC     indicates specified Tracking Area Code.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
63
# - LTE_PEER/NR_PEER indicate an LTE/NR ENB-PEER-kind cell.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
64
# - X2_PEER/XN_PEER  indicate an LTE/NR ENB peer.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
65

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
66
# TDD/FDD are basic parameters to indicate TDD/FDD mode.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
67 68
TDD = {'rf_mode': 'tdd'}
FDD = {'rf_mode': 'fdd'}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
69

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
70
# LTE/NR return basic parameters for an LTE/NR cell with given downlink frequency.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
71
def LTE(dl_earfcn):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
72
    return {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
73 74
        'cell_type':    'lte',
        'dl_earfcn':    dl_earfcn,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
75
    }
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
76
def NR(dl_nr_arfcn, nr_band):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
77 78 79 80
    return {
        'cell_type':    'nr',
        'dl_nr_arfcn':  dl_nr_arfcn,
        'nr_band':      nr_band,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
81 82
    }

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
83
# BW returns basic parameters to indicate specified bandwidth.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
84 85
def BW(bandwidth):
    return {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
86 87 88
        'bandwidth':    bandwidth,
    }

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
89
# CENB returns basic parameters to indicate a ENB-kind cell.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
90
def CENB(cell_id, pci):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
91 92 93 94
    return {
        'cell_kind':    'enb',
        'cell_id':      '0x%02x' % cell_id,
        'pci':          pci,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
95 96
    }

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
97
# CUE indicates an UE-kind cell.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
98
CUE = {'cell_kind': 'ue'}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
99

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
100
#  TAC returns basic parameters to indicate specified Tracking Area Code.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
101 102
def TAC(tac):
    return {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
103 104 105
        'tac':          '0x%x' % tac,
    }

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
106
# LTE_PEER/NR_PEER return basic parameters to indicate an LTE/NR ENB-PEER-kind cell.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
107 108
def LTE_PEER(e_cell_id, pci, tac):
    return {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
109 110 111 112
        'cell_kind':    'enb_peer',
        'e_cell_id':    '0x%07x' % e_cell_id,
        'pci':          pci,
        'tac':          '0x%x' % tac,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
113 114 115
    }
def NR_PEER(nr_cell_id, gnb_id_bits, pci, tac):
    return {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
116 117 118 119 120
        'cell_kind':    'enb_peer',
        'nr_cell_id':   '0x%09x' % nr_cell_id,
        'gnb_id_bits':  gnb_id_bits,
        'pci':          pci,
        'tac':          tac,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
121 122
    }

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
123
# X2_PEER/XN_PEER return basic parameters to indicate an LTE/NR ENB peer.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
124 125 126 127 128
def X2_PEER(x2_addr):
    return {
        'peer_type':    'lte',
        'x2_addr':      x2_addr,
    }
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
129
def XN_PEER(xn_addr):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
130 131 132 133
    return {
        'peer_type':    'nr',
        'xn_addr':      xn_addr,
    }
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
134

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
135
# --------
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
136

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
137 138 139 140 141 142 143
# AmariTestCase is base class for all tests.
class AmariTestCase(_AmariTestCase):
    maxDiff = None  # show full diff in test run log on an error

    # stress correctness of ru_ref/cell_ref/... usage throughout all places in
    # buildout code - special characters should not lead to wrong templates or
    # code injection.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
144
    default_partition_reference = _AmariTestCase.default_partition_reference + \
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
145 146
                                  ' ${a:b}\n[c]\n;'

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
147
    # faster edit/try cycle during development
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
148
    if 1:   # XXX disable by default
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
149 150
        instance_max_retry = 1
        report_max_retry = 1
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

    @classmethod
    def requestDefaultInstance(cls, state='started'):
        inst = super().requestDefaultInstance(state=state)
        cls.requestAllShared(inst)
        return inst

    # requestAllShared should add all shared instances of the testcase over imain.
    @classmethod
    def requestAllShared(cls, imain):
        raise NotImplementedError

    # requestShared requests one shared instance over imain with specified subreference and parameters.
    @classmethod
    def requestShared(cls, imain, subref, ctx):
        cls.slap.request(
            software_release=cls.getSoftwareURL(),
            software_type=cls.getInstanceSoftwareType(),
            partition_reference=cls.ref(subref),
            # XXX StandaloneSlapOS rejects filter_kw with "Can only request on embedded computer"
            #filter_kw = {'instance_guid': imain.getInstanceGuid()},
            partition_parameter_kw={'_': json.dumps(ctx)},
            shared=True)

    # ref returns full reference of shared instance with given subreference.
    #
    # for example if reference of main instance is 'MAIN-INSTANCE'
    #
    #   ref('RU') = 'MAIN-INSTANCE.RU'
    @classmethod
    def ref(cls, subref):
        return '%s.%s' % (cls.default_partition_reference, subref)

    # ipath returns path for a file inside main instance.
    @classmethod
    def ipath(cls, path):
        assert path[:1] != '/', path
        return '%s/%s' % (cls.computer_partition_root_path, path)


Kirill Smelkov's avatar
.  
Kirill Smelkov committed
191 192
# ---- eNB + base class for similar services that do radio ----

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
193
# RFTestCase4 is base class for tests of all services that do radio.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
194
#
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
195 196 197 198 199 200 201 202 203 204 205 206 207 208
# It instantiates a service with several Radio Units and Cells attached.
#
# 4 RU x 4 CELL are requested to verify all {FDD,TDD}·{LTE,NR} combinations.
#
# In requested instances mostly non-overlapping range of numbers are
# assigned to parameters according to the following scheme:
#
#   0+          cell_id
#   0x10+       pci
#   0x100+      tac
#   10+         tx_gain
#   20+         rx_gain
#   xxx+i·100   dl_arfcn
#   5,10,15,20  bandwidth
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
209 210
#   100+        root_sequence_index
#   1000+       inactivity_timer
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
211 212 213 214
#
# this allows to quickly see offhand to which cell/ru and parameter a
# particular number belongs to.
#
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
215
# Subclasses should define:
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
216
#
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
217 218 219
# - RUcfg(i) to return primary parameters specific for i'th RU configuration
#   like ru_type - to verify particular RU driver, sdr_dev, sfp_port and so on.
# - CELLcfg(i) to tune parameters of i'th cell, for example cell_kind.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
220
# - .rf_cfg with loaded service config
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
221
class RFTestCase4(AmariTestCase):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
    @classmethod
    def requestAllShared(cls, imain):
        def RU(i):
            ru = cls.RUcfg(i)
            ru |= {'n_antenna_dl': 4, 'n_antenna_ul': 2}
            ru |= {'tx_gain': 10+i, 'rx_gain':  20+i, 'txrx_active': 'INACTIVE'}
            cls.requestShared(imain, 'RU%d' % i, ru)

        def CELL(i, ctx):
            cell = {
                'ru': {
                    'ru_type': 'ru_ref',
                    'ru_ref':   cls.ref('RU%d' % i),
                }
            }
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
237
            cell |= cls.CELLcfg(i)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
238 239 240 241 242 243 244 245
            cell |= ctx
            cls.requestShared(imain, 'RU%d.CELL' % i, cell)

        RU(1);  CELL(1, FDD | LTE(   100)    | BW( 5))
        RU(2);  CELL(2, TDD | LTE( 40200)    | BW(10))
        RU(3);  CELL(3, FDD | NR (300300,74) | BW(15))
        RU(4);  CELL(4, TDD | NR (470400,40) | BW(20))

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
246
    def test_rf_cfg_txrx_gain(t):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
247 248 249 250 251 252 253
        # NOTE even though setting tx_gain/rx_gain does not make any difference
        #      for CPRI case, we still do set it there for consistency. For the
        #      reference: for CPRI case the real tx/rx gain is set in RU
        #      configuration and is verified by RU tests.
        t.assertEqual(t.rf_cfg['tx_gain'], [11]*4 + [12]*4 + [13]*4 + [14]*4)
        t.assertEqual(t.rf_cfg['rx_gain'], [21]*2 + [22]*2 + [23]*2 + [24]*2)

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
254

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
255
# ENBTestCase4 provides base class for unit-testing eNB service.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
256
#
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
257
# It instantiates enb with 4 Radio Units x 4 Cells and verifies generated
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
258
# enb.cfg to match what is expected.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
259
class ENBTestCase4(RFTestCase4):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
260 261 262 263
    @classmethod
    def getInstanceSoftwareType(cls):
        return "enb"

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
264 265 266
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
267
        cls.enb_cfg = cls.rf_cfg = yamlpp_load(cls.ipath('etc/enb.cfg'))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
268

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
269 270
    @classmethod
    def getInstanceParameterDict(cls):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
271
        return {'_': json.dumps({
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
272 273 274 275 276
            'testing':      True,
            'enb_id':       '0x17',
            'gnb_id':       '0x23',
            'gnb_id_bits':  30,
        })}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
277

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
278
    @classmethod
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
279
    def requestAllShared(cls, imain):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
280 281
        super().requestAllShared(imain)

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
282 283
        def _(subref, ctx):
            return cls.requestShared(imain, subref, ctx)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
284
        _('PEER4',      X2_PEER('44.1.1.1'))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
285
        _('PEER5',      XN_PEER('55.1.1.1'))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
286

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
287 288 289 290 291 292 293 294
        _('PEERCELL4',  LTE(700)      | LTE_PEER(0x12345,    35, 0x123))
        _('PEERCELL5',  NR(520000,38) |  NR_PEER(0x77712,22, 75, 0x321))
        cls.ho_inter = [
            dict(rat='eutra', cell_id=0x12345, n_id_cell=35, dl_earfcn=  700, tac=0x123),
            dict(rat='nr',    nr_cell_id=0x77712, gnb_id_bits=22, n_id_cell=75,
                 dl_nr_arfcn=520000, ul_nr_arfcn=520000, ssb_nr_arfcn=520090, band=38,
                 tac = 0x321),
        ]
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
295

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
296 297 298 299
    def CELLcfg(i):
        return CENB(i, 0x10+i) | TAC(0x100+i) | {
                 'root_sequence_index': 100+i,
                 'inactivity_timer':    1000+i}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
300

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
301
    # basic enb parameters
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
302
    def test_enb_cfg_basic(t):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
303
        assertMatch(t, t.enb_cfg, dict(
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
304 305 306
            enb_id=0x17, gnb_id=0x23, gnb_id_bits=30,
            x2_peers=['44.1.1.1'], xn_peers=['55.1.1.1'],
        ))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
307

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
308
    # basic cell parameters
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
309
    def test_enb_cfg_cell(t):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
310
        assertMatch(t, t.enb_cfg['cell_list'],  [
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
311
          dict( # CELL1
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
312 313
            uldl_config=NO,   rf_port=0,        n_antenna_dl=4,  n_antenna_ul=2,
            dl_earfcn=100,    ul_earfcn=18100,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
314
            n_rb_dl=25,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
315
            cell_id=0x1,      n_id_cell=0x11,   tac=0x101,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
316
            root_sequence_index=101,  inactivity_timer=1001,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
317 318
          ),
          dict( # CELL2
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
319
            uldl_config=2,    rf_port=1,        n_antenna_dl=4,  n_antenna_ul=2,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
320
            dl_earfcn=40200,  ul_earfcn=40200,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
321 322
            n_rb_dl=50,
            cell_id=0x2,      n_id_cell=0x12,   tac=0x102,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
323
            root_sequence_index=102,  inactivity_timer=1002,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
324 325 326
          ),
        ])

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
327
        assertMatch(t, t.enb_cfg['nr_cell_list'],  [
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
328 329
          dict( # CELL3
            tdd_ul_dl_config=NO, rf_port=2,           n_antenna_dl=4,       n_antenna_ul=2,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
330
            dl_nr_arfcn=300300,  ul_nr_arfcn=290700,  ssb_nr_arfcn=300270,  band=74,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
331 332
            bandwidth=15,
            cell_id=0x3,         n_id_cell=0x13,      tac=NO,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
333
            root_sequence_index=103,  inactivity_timer=1003,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
334 335 336
          ),

          dict( # CELL4
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
337 338 339
            tdd_ul_dl_config={'pattern1': dict(
                period=5, dl_slots=7, dl_symbols=6, ul_slots=2, ul_symbols=4,
            )},
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
340
                                 rf_port=3,           n_antenna_dl=4,       n_antenna_ul=2,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
341
            dl_nr_arfcn=470400,  ul_nr_arfcn=470400,  ssb_nr_arfcn=470430,  band=40,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
342 343
            bandwidth=20,
            cell_id=0x4,         n_id_cell=0x14,      tac=NO,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
344
            root_sequence_index=104,  inactivity_timer=1004,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
345 346 347 348
          ),
        ])


Kirill Smelkov's avatar
.  
Kirill Smelkov committed
349
    # Carrier Aggregation
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
350
    def test_enb_cfg_ca(t):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
351
        assertMatch(t, t.enb_cfg['cell_list'],  [
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
352
          { # CELL1
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
353 354
            'scell_list':           [{'cell_id': 2}],                   # LTE + LTE
            'en_dc_scg_cell_list':  [{'cell_id': 3}, {'cell_id': 4}],   # LTE + NR
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
355 356
          },
          { # CELL2
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
357 358
            'scell_list':           [{'cell_id': 1}],                   # LTE + LTE
            'en_dc_scg_cell_list':  [{'cell_id': 3}, {'cell_id': 4}],   # LTE + NR
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
359
          },
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
360
        ])
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
361

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
362
        assertMatch(t, t.enb_cfg['nr_cell_list'], [
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
363
          { # CELL3
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
364
            'scell_list':           [{'cell_id': 4}],                   # NR  + NR
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
365 366
          },
          { # CELL4
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
367
            'scell_list':           [{'cell_id': 3}],                   # NR  + NR
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
368 369
          },
        ])
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
370

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
371

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
372
    # Handover
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
373
    def test_enb_cfg_ho(t):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
374
        assertMatch(t, t.enb_cfg['cell_list'],  [
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
375 376
          { # CELL1
            'ncell_list':   [
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
377 378 379
              dict(rat='eutra', cell_id= 0x1702, n_id_cell=0x12, dl_earfcn=40200, tac=0x102), # CELL2
              dict(rat='nr',    cell_id=      3),                                             # CELL3
              dict(rat='nr',    cell_id=      4),                                             # CELL4
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
380
            ] + t.ho_inter,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
381 382 383
          },
          { # CELL2
            'ncell_list':   [
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
384
              dict(rat='eutra', cell_id= 0x1701, n_id_cell=0x11, dl_earfcn=  100, tac=0x101), # CELL1
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
385 386
              dict(rat='nr',    cell_id=      3),                                             # CELL3
              dict(rat='nr',    cell_id=      4),                                             # CELL4
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
387
            ] + t.ho_inter,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
388 389
          },
        ])
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
390
        assertMatch(t, t.enb_cfg['nr_cell_list'], [
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
391 392 393
          { # CELL3
            'ncell_list':   [
              dict(rat='eutra', cell_id= 0x1701, n_id_cell=0x11, dl_earfcn=  100, tac=0x101), # CELL1
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
394 395
              dict(rat='eutra', cell_id= 0x1702, n_id_cell=0x12, dl_earfcn=40200, tac=0x102), # CELL2
              dict(rat='nr',    cell_id=      4),                                             # CELL4
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
396
            ] + t.ho_inter,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
397 398 399 400
          },
          { # CELL4
            'ncell_list':   [
              dict(rat='eutra', cell_id= 0x1701, n_id_cell=0x11, dl_earfcn=  100, tac=0x101), # CELL1
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
401 402
              dict(rat='eutra', cell_id= 0x1702, n_id_cell=0x12, dl_earfcn=40200, tac=0x102), # CELL2
              dict(rat='nr',    cell_id=      3),                                             # CELL3
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
403
            ] + t.ho_inter,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
404 405
          },
        ])
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
406 407


Kirill Smelkov's avatar
.  
Kirill Smelkov committed
408
# ---- RU mixins to be used with RFTestCase4 ----
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
409

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
410 411
# SDR4 is mixin to verify SDR driver wrt all LTE/NR x FDD/TDD modes.
class SDR4:
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
412 413 414 415 416 417 418 419 420
    @classmethod
    def RUcfg(cls, i):
        return {
            'ru_type':      'sdr',
            'ru_link_type': 'sdr',
            'sdr_dev_list': [2*i,2*i+1],
        }

    # radio units configuration
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
421
    def test_rf_cfg_ru(t):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
422
        assertMatch(t, t.rf_cfg['rf_driver'],  dict(
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
423 424 425 426 427 428 429 430
          args='dev0=/dev/sdr2,dev1=/dev/sdr3,dev2=/dev/sdr4,dev3=/dev/sdr5,' +
               'dev4=/dev/sdr6,dev5=/dev/sdr7,dev6=/dev/sdr8,dev7=/dev/sdr9',
          cpri_mapping=NO,
          cpri_mult=NO,
          cpri_rx_delay=NO,
          cpri_tx_delay=NO,
          cpri_tx_dbm=NO,
        ))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
431 432


Kirill Smelkov's avatar
.  
Kirill Smelkov committed
433 434
# Lopcomm4 is mixin to verify Lopcomm driver wrt all LTE/NR x FDD/TDD modes.
class Lopcomm4:
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
    @classmethod
    def RUcfg(cls, i):
        return {
            'ru_type':      'lopcomm',
            'ru_link_type': 'cpri',
            'cpri_link':    {
                'sdr_dev':  0,
                'sfp_port': i,
                'mult':     4,
                'mapping':  'hw',
                'rx_delay': 40+i,
                'tx_delay': 50+i,
                'tx_dbm':   60+i
            },
            'mac_addr':     '00:0A:45:00:00:%02x' % i,
        }

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
452
    # radio units configuration in enb.cfg
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
453
    def test_rf_cfg_ru(t):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
454
        assertMatch(t, t.rf_cfg['rf_driver'],  dict(
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
455 456 457 458 459 460 461 462
          args='dev0=/dev/sdr0@1,dev1=/dev/sdr0@2,dev2=/dev/sdr0@3,dev3=/dev/sdr0@4',
          cpri_mapping='hw,hw,hw,hw',
          cpri_mult='4,4,4,4',
          cpri_rx_delay='41,42,43,44',
          cpri_tx_delay='51,52,53,54',
          cpri_tx_dbm='61,62,63,64',
        ))

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
463
    # RU configuration in cu_config.xml
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
464
    def test_ru_cu_config_xml(t):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484
        def uctx(rf_mode, cell_type, dl_arfcn, ul_arfcn, bw, dl_freq, ul_freq, tx_gain, rx_gain):
            return {
                'tx-array-carriers': {
                  'rw-duplex-scheme':              rf_mode,
                  'rw-type':                       cell_type,
                  'absolute-frequency-center':    '%d' % dl_arfcn,
                  'center-of-channel-bandwidth':  '%d' % dl_freq,
                  'channel-bandwidth':            '%d' % bw,
                  'gain':                         '%d' % tx_gain,
                  'active':                       'INACTIVE',
                },
                'rx-array-carriers': {
                  'absolute-frequency-center':    '%d' % ul_arfcn,
                  'center-of-channel-bandwidth':  '%d' % ul_freq,
                  'channel-bandwidth':            '%d' % bw,
                  # XXX no rx_gain
                  'active':                       'INACTIVE',
                },
            }

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
485
        _ = t._test_ru_cu_config_xml
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
486

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
487 488 489
        #       rf_mode  ctype dl_arfcn ul_arfcn   bw      dl_freq     ul_freq     txg rxg
        _(1, uctx('FDD', 'LTE',    100,   18100,  5000000, 2120000000, 1930000000, 11, 21))
        _(2, uctx('TDD', 'LTE',  40200,   40200, 10000000, 2551000000, 2551000000, 12, 22))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
490
        _(3, uctx('FDD',  'NR', 300300,  290700, 15000000, 1501500000, 1453500000, 13, 23))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
491
        _(4, uctx('TDD',  'NR', 470400,  470400, 20000000, 2352000000, 2352000000, 14, 24))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
492

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
493
    def _test_ru_cu_config_xml(t, i, uctx):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
494 495 496 497 498 499
        cu_xml = t.ipath('etc/%s' % xbuildout.encode('%s-cu_config.xml' % t.ref('RU%d' % i)))
        with open(cu_xml, 'r') as f:
            cu = f.read()
        cu = xmltodict.parse(cu)

        assertMatch(t, cu, {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
500 501 502
          'xc:config': {
            'user-plane-configuration': {
              'tx-endpoints': [
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
503 504 505 506
                {'name': 'TXA0P00C00', 'e-axcid': {'eaxc-id': '0'}},
                {'name': 'TXA0P00C01', 'e-axcid': {'eaxc-id': '1'}},
                {'name': 'TXA0P01C00', 'e-axcid': {'eaxc-id': '2'}},
                {'name': 'TXA0P01C01', 'e-axcid': {'eaxc-id': '3'}},
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
507 508 509 510 511 512 513 514
              ],
              'tx-links': [
                {'name': 'TXA0P00C00', 'tx-endpoint': 'TXA0P00C00'},
                {'name': 'TXA0P00C01', 'tx-endpoint': 'TXA0P00C01'},
                {'name': 'TXA0P01C00', 'tx-endpoint': 'TXA0P01C00'},
                {'name': 'TXA0P01C01', 'tx-endpoint': 'TXA0P01C01'},
              ],
              'rx-endpoints': [
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
515
                {'name': 'RXA0P00C00',   'e-axcid': {'eaxc-id': '0'}},
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
516
                {'name': 'PRACH0P00C00', 'e-axcid': {'eaxc-id': '8'}},
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
517
                {'name': 'RXA0P00C01',   'e-axcid': {'eaxc-id': '1'}},
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
518
                {'name': 'PRACH0P00C01', 'e-axcid': {'eaxc-id': '24'}},
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
519 520 521 522 523 524 525
              ],
              'rx-links': [
                {'name': 'RXA0P00C00',   'rx-endpoint': 'RXA0P00C00'},
                {'name': 'PRACH0P00C00', 'rx-endpoint': 'PRACH0P00C00'},
                {'name': 'RXA0P00C01',   'rx-endpoint': 'RXA0P00C01'},
                {'name': 'PRACH0P00C01', 'rx-endpoint': 'PRACH0P00C01'},
              ],
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
526
            } | uctx
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
527 528
          }
        })
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
529

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
530

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
531 532 533 534 535 536 537 538 539 540 541 542
# Sunwave4 is mixin to verify Sunwave driver wrt all LTE/NR x FDD/TDD modes.
class Sunwave4:
    @classmethod
    def RUcfg(cls, i):
        return {
            'ru_type':      'sunwave',
            'ru_link_type': 'cpri',
            'cpri_link':    {
                'sdr_dev':  1,
                'sfp_port': i,
                'mult':     5,
                'mapping':  'bf1',
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
543 544 545
                'rx_delay': 140+i,
                'tx_delay': 150+i,
                'tx_dbm':   160+i
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
546
            },
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
547
            'mac_addr':     '00:FA:FE:00:00:%02x' % i,
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
548 549 550
        }

    # radio units configuration in enb.cfg
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
551
    def test_rf_cfg_ru(t):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
552
        assertMatch(t, t.rf_cfg['rf_driver'],  dict(
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
553
          args='dev0=/dev/sdr1@1,dev1=/dev/sdr1@2,dev2=/dev/sdr1@3,dev3=/dev/sdr1@4',
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
554
          cpri_mapping='bf1,bf1,bf1,bf1',
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
555
          cpri_mult='5,5,5,5',
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
556 557 558
          cpri_rx_delay='141,142,143,144',
          cpri_tx_delay='151,152,153,154',
          cpri_tx_dbm='161,162,163,164',
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
559 560
        ))

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
561 562
# RUMultiType4 is mixin to verify that different RU types can be used at the same time.
class RUMultiType4:
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
563 564
    # ENB does not support mixing SDR + CPRI - verify only with CPRI-based units
    # see https://support.amarisoft.com/issues/26021 for details
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
565
    @classmethod
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
566 567 568 569 570 571
    def RUcfg(cls, i):
        assert 1 <= i <= 4, i
        if i in (1,2):
            return Lopcomm4.RUcfg(i)
        else:
            return Sunwave4.RUcfg(i)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
572

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
573
    # radio units configuration in enb.cfg
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
574
    def test_rf_cfg_ru(t):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
575
        assertMatch(t, t.rf_cfg['rf_driver'],  dict(
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
576 577 578 579 580 581 582
          args='dev0=/dev/sdr0@1,dev1=/dev/sdr0@2,dev2=/dev/sdr1@3,dev3=/dev/sdr1@4',
          cpri_mapping='hw,hw,bf1,bf1',
          cpri_mult='4,4,5,5',
          cpri_rx_delay='41,42,143,144',
          cpri_tx_delay='51,52,153,154',
          cpri_tx_dbm='61,62,163,164',
        ))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
583 584


Kirill Smelkov's avatar
.  
Kirill Smelkov committed
585 586 587 588 589
# instantiate eNB tests
class TestENB_SDR4        (ENBTestCase4, SDR4):         pass
class TestENB_Lopcomm4    (ENBTestCase4, Lopcomm4):     pass
class TestENB_Sunwave4    (ENBTestCase4, Sunwave4):     pass
class TestENB_RUMultiType4(ENBTestCase4, RUMultiType4): pass
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
590 591


Kirill Smelkov's avatar
.  
Kirill Smelkov committed
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
# ---- UEsim ----

# UEsimTestCase4 provides base class for unit-testing UEsim service.
#
# It is similar to ENBTestCase4 but configures UE cells instead of eNB cells.
class UEsimTestCase4(RFTestCase4):
    @classmethod
    def getInstanceSoftwareType(cls):
        return "ue"

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.ue_cfg = cls.rf_cfg = yamlpp_load(cls.ipath('etc/ue.cfg'))

    @classmethod
    def getInstanceParameterDict(cls):
        return {'_': json.dumps({
            'testing':      True,
        })}

    @classmethod
    def CELLcfg(cls, i):
        return CUE

    @classmethod
    def requestAllShared(cls, imain):
        super().requestAllShared(imain)

        def UE(i):
            ue = {
                'ue_type':  ('lte', 'nr') [(i-1) % 2],
                'rue_addr': 'host%d'    % i,
                'sim_algo': ('xor', 'milenage', 'tuak') [i-1],
                'imsi':     '%015d'     % i,
                'opc':      '%032x'     % i,
                'amf':      '0x%04x'    % (0x9000+i),
                'sqn':      '%012x'     % i,
                'k':        'FFFF%028x' % i,
                'impi':     'impi%d@rapid.space' % i,
            }
            cls.requestShared(imain, 'UE%d' % i, ue)

        UE(1)
        UE(2)
        UE(3)

    # ue parameters
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
640
    def test_ue_cfg_ue(t):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668
        assertMatch(t, t.ue_cfg['ue_list'], [
          dict(
            as_release=13,  ue_category=13,   rue_addr='host1',
            sim_algo='xor', amf =0x9001,      impi='impi1@rapid.space',
            sqn ='000000000001',
            imsi='000000000000001',
            opc ='00000000000000000000000000000001',
            K   ='FFFF0000000000000000000000000001',
          ),
          dict(
            as_release=15,  ue_category='nr', rue_addr='host2',
            sim_algo='milenage', amf =0x9002, impi='impi2@rapid.space',
            sqn ='000000000002',
            imsi='000000000000002',
            opc ='00000000000000000000000000000002',
            K   ='FFFF0000000000000000000000000002',
          ),
          dict(
            as_release=13,  ue_category=13,   rue_addr='host3',
            sim_algo='tuak', amf =0x9003,     impi='impi3@rapid.space',
            sqn ='000000000003',
            imsi='000000000000003',
            opc ='00000000000000000000000000000003',
            K   ='FFFF0000000000000000000000000003',
          ),
        ])

    # cells
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
669
    def test_ue_cfg_cell(t):
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708
        assertMatch(t, t.ue_cfg['cell_groups'], [
          dict(
            group_type='lte',
            cells=[
              dict( # CELL1
                rf_port=0,        n_antenna_dl=4,  n_antenna_ul=2,
                dl_earfcn=100,    ul_earfcn=18100,
                bandwidth=5,
              ),
              dict( # CELL2
                rf_port=1,        n_antenna_dl=4,  n_antenna_ul=2,
                dl_earfcn=40200,  ul_earfcn=40200,
                bandwidth=10,
              ),
            ]
          ),
          dict(
            group_type='nr',
            cells=[
              dict( # CELL3
                rf_port=2,           n_antenna_dl=4,      n_antenna_ul=2,
                dl_nr_arfcn=300300,  ul_nr_arfcn=290700,  ssb_nr_arfcn=300270,  band=74,
                bandwidth=15,
              ),
              dict( # CELL4
                rf_port=3,           n_antenna_dl=4,      n_antenna_ul=2,
                dl_nr_arfcn=470400,  ul_nr_arfcn=470400,  ssb_nr_arfcn=470430,  band=40,
                bandwidth=20,
              ),
            ]
          )
        ])


# instantiate UEsim tests
class TestUEsim_SDR4        (UEsimTestCase4, SDR4):         pass
class TestUEsim_Lopcomm4    (UEsimTestCase4, Lopcomm4):     pass
class TestUEsim_Sunwave4    (UEsimTestCase4, Sunwave4):     pass
class TestUEsim_RUMultiType4(UEsimTestCase4, RUMultiType4): pass
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
709

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
710

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726
# ---- misc ----

# yamlpp_load loads yaml config file after preprocessing it.
#
# preprocessing is needed to e.g. remove // and /* comments.
def yamlpp_load(path):
    with open(path, 'r') as f:
        data = f.read()     # original input
    p = pcpp.Preprocessor()
    p.parse(data)
    f = io.StringIO()
    p.write(f)
    data_ = f.getvalue()    # preprocessed input
    return yaml.load(data_, Loader=yaml.Loader)


Kirill Smelkov's avatar
.  
Kirill Smelkov committed
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768
# assertMatch recursively matches data structure against specified pattern.
#
# - dict match by verifying v[k] == vok[k] for keys from the pattern.
#   vok[k]=NO means v[k] must be absent
# - list match by matching all elements individually
# - atomic types like int and str match by equality
class NOClass:
    def __repr__(self):
        return 'ø'
NO = NOClass()
def assertMatch(t: unittest.TestCase, v, vok):
    v_ = _matchCollect(v, vok)
    t.assertEqual(v_, vok)

def _matchCollect(v, vok):
    if type(v) is not type(vok):
        return v
    if type(v) is dict:
        v_ = {}
        for k in vok:
            v_[k] = _matchCollect(v.get(k, NO), vok[k])
        return v_
    if type(v) is list:
        v_ = []
        for i in range(max(len(v), len(vok))):
            e   = NO
            eok = NO
            if i < len(v):
                e = v[i]
            if i < len(vok):
                eok = vok[i]

            if e is not NO:
                if eok is not NO:
                    v_.append(_matchCollect(e, eok))
                else:
                    v_.append(e)
        return v_

    # other types, e.g. atomic int/str/... - return as is
    assert type(v) is not tuple, v
    return v
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
769

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
class TestAssertMatch(unittest.TestCase):
    def test_assertMatch(t):
        y, n = True, False
        testv = [   # [](match, v, vok)
            (y, 12,  12),
            (n, 12,  13),
            (n, 12,  '12'),
            (y, 'a', 'a'),
            (n, 'a', 'ab'),
            (y, [],  []),
            (n, [],  [1]),
            (y, [1], [1]),
            (n, [1,2], [1]),
            (y, [1,2], [1,2]),
            (n, [1,2], ['a',2]),
            (y, {}, {}),
            (y, {'a': 1}, {}),
            (y, {'a': 1}, {'a': 1}),
            (n, {'a': 1}, {'a': 2}),
            (n, {'a': 1}, {'a': NO}),
            (y, {},       {'a': NO}),
            (y, {'b': 2}, {'a': NO}),
            (n, {'a': 1, 'b': 2}, {'a': NO}),
            (n, {'a': 1, 'b': 2}, {'a': NO, 'b': 2}),
            (y, {'a': 1, 'b': 2}, {         'b': 2}),
            (y, {'a': [1, 2, {'aa': 33, 'bb': 44}]},
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
796
                {'a': [1, 2, {'aa': 33, 'cc': NO}]}),
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
797
            (n, {'a': [1, 2, {'aa': 33, 'bb': 44}]},
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
798
                {'a': [1, 2, {'aa': 35, 'cc': NO}]}),
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
799 800 801
        ]

        for mok, v, vok in testv:
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
802 803 804 805 806 807
            with t.subTest(mok=mok, v=v, vok=vok):
                if mok:
                    assertMatch(t, v, vok)
                else:
                    t.assertRaises(t.failureException,
                        assertMatch, t, v, vok)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
808 809 810


# hide base TestCases from unittest discovery so that their test_ methods are
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
811
# run only on leaf test classes.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
812 813 814 815 816 817
def __dir__():
    d = list(sorted(globals().keys()))
    abstract = {'AmariTestCase', 'RFTestCase4', 'ENBTestCase4', 'UEsimTestCase4'}
    for _ in abstract:
        d.remove(_)
    return d