a70e71ab6e5f044b2dac66a9448553adec88f402
[nfvbench.git] / test / test_nfvbench.py
1 #!/usr/bin/env python
2 # Copyright 2016 Cisco Systems, Inc.  All rights reserved.
3 #
4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
5 #    not use this file except in compliance with the License. You may obtain
6 #    a copy of the License at
7 #
8 #         http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #    Unless required by applicable law or agreed to in writing, software
11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 #    License for the specific language governing permissions and limitations
14 #    under the License.
15 #
16
17 from attrdict import AttrDict
18 from nfvbench.config import get_err_config
19 from nfvbench.connection import SSH
20 from nfvbench.credentials import Credentials
21 from nfvbench.network import Interface
22 from nfvbench.network import Network
23 from nfvbench.specs import Encaps
24 import nfvbench.traffic_gen.traffic_utils as traffic_utils
25 import os
26 import pytest
27
28 __location__ = os.path.realpath(os.path.join(os.getcwd(),
29                                              os.path.dirname(__file__)))
30
31
32 @pytest.fixture
33 def ssh(monkeypatch):
34     def mock_init(self, ssh_access, *args, **kwargs):
35         self.ssh_access = ssh_access
36         if ssh_access.private_key:
37             self.pkey = self._get_pkey(ssh_access.private_key)
38         else:
39             self.pkey = None
40         self._client = False
41         self.connect_timeout = 2
42         self.connect_retry_count = 1
43         self.connect_retry_wait_sec = 1
44         super(SSH, self).__init__()
45
46     monkeypatch.setattr(SSH, '__init__', mock_init)
47
48
49 @pytest.fixture
50 def openstack_vxlan_spec():
51     return AttrDict(
52         {
53             'openstack': AttrDict({
54                 'vswitch': "VTS",
55                 'encaps': Encaps.VxLAN}
56             ),
57             'run_spec': AttrDict({
58                 'use_vpp': True
59             })
60         }
61     )
62
63 # =========================================================================
64 # PVP Chain tests
65 # =========================================================================
66
67 def test_chain_interface():
68     iface = Interface('testname', 'vpp', tx_packets=1234, rx_packets=4321)
69     assert iface.name == 'testname'
70     assert iface.device == 'vpp'
71     assert iface.get_packet_count('tx') == 1234
72     assert iface.get_packet_count('rx') == 4321
73     assert iface.get_packet_count('wrong_key') == 0
74
75
76 @pytest.fixture(scope='session')
77 def iface1():
78     return Interface('iface1', 'trex', tx_packets=10000, rx_packets=1234)
79
80
81 @pytest.fixture(scope='session')
82 def iface2():
83     return Interface('iface2', 'n9k', tx_packets=1234, rx_packets=9901)
84
85
86 @pytest.fixture(scope='session')
87 def iface3():
88     return Interface('iface3', 'n9k', tx_packets=9900, rx_packets=1234)
89
90
91 @pytest.fixture(scope='session')
92 def iface4():
93     return Interface('iface4', 'vpp', tx_packets=1234, rx_packets=9801)
94
95
96 @pytest.fixture(scope='session')
97 def net1(iface1, iface2, iface3, iface4):
98     return Network([iface1, iface2, iface3, iface4], reverse=False)
99
100
101 @pytest.fixture(scope='session')
102 def net2(iface1, iface2, iface3):
103     return Network([iface1, iface2, iface3], reverse=True)
104
105
106 def test_chain_network(net1, net2, iface1, iface2, iface3, iface4):
107     assert [iface1, iface2, iface3, iface4] == net1.get_interfaces()
108     assert [iface3, iface2, iface1] == net2.get_interfaces()
109     net2.add_interface(iface4)
110     assert [iface4, iface3, iface2, iface1] == net2.get_interfaces()
111
112
113 """
114 def test_chain_analysis(net1, monkeypatch, openstack_vxlan_spec):
115     def mock_empty(self, *args, **kwargs):
116         pass
117
118     monkeypatch.setattr(ServiceChain, '_setup', mock_empty)
119
120     f = ServiceChain(AttrDict({'service_chain': 'DUMMY'}), [], {'tor': {}}, openstack_vxlan_spec,
121                      lambda x, y, z: None)
122     result = f.get_analysis([net1])
123     assert result[1]['packet_drop_count'] == 99
124     assert result[1]['packet_drop_percentage'] == 0.99
125     assert result[2]['packet_drop_count'] == 1
126     assert result[2]['packet_drop_percentage'] == 0.01
127     assert result[3]['packet_drop_count'] == 99
128     assert result[3]['packet_drop_percentage'] == 0.99
129
130     net1.reverse = True
131     result = f.get_analysis([net1])
132     assert result[1]['packet_drop_count'] == 0
133     assert result[1]['packet_drop_percentage'] == 0.0
134     assert result[2]['packet_drop_count'] == 0
135     assert result[2]['packet_drop_percentage'] == 0.0
136     assert result[3]['packet_drop_count'] == 0
137     assert result[3]['packet_drop_percentage'] == 0.0
138
139
140 @pytest.fixture
141 def pvp_chain(monkeypatch, openstack_vxlan_spec):
142     tor_vni1 = Interface('vni-4097', 'n9k', 50, 77)
143     vsw_vni1 = Interface('vxlan_tunnel0', 'vpp', 77, 48)
144     vsw_vif1 = Interface('VirtualEthernet0/0/2', 'vpp', 48, 77)
145     vsw_vif2 = Interface('VirtualEthernet0/0/3', 'vpp', 77, 47)
146     vsw_vni2 = Interface('vxlan_tunnel1', 'vpp', 43, 77)
147     tor_vni2 = Interface('vni-4098', 'n9k', 77, 40)
148
149     def mock_init(self, *args, **kwargs):
150         self.vni_ports = [4097, 4098]
151         self.specs = openstack_vxlan_spec
152         self.clients = {
153             'vpp': AttrDict({
154                 'set_interface_counters': lambda: None,
155             })
156         }
157         self.worker = AttrDict({
158             'run': lambda: None,
159         })
160
161     def mock_empty(self, *args, **kwargs):
162         pass
163
164     def mock_get_network(self, traffic_port, vni_id, reverse=False):
165         if vni_id == 0:
166             return Network([tor_vni1, vsw_vni1, vsw_vif1], reverse)
167         else:
168             return Network([tor_vni2, vsw_vni2, vsw_vif2], reverse)
169
170     def mock_get_data(self):
171         return {}
172
173     monkeypatch.setattr(PVPChain, '_get_network', mock_get_network)
174     monkeypatch.setattr(PVPChain, '_get_data', mock_get_data)
175     monkeypatch.setattr(PVPChain, '_setup', mock_empty)
176     monkeypatch.setattr(VxLANWorker, '_clear_interfaces', mock_empty)
177     monkeypatch.setattr(PVPChain, '_generate_traffic', mock_empty)
178     monkeypatch.setattr(PVPChain, '__init__', mock_init)
179     return PVPChain(None, None, {'vm': None, 'vpp': None, 'tor': None, 'traffic': None}, None)
180
181
182 def test_pvp_chain_run(pvp_chain):
183     result = pvp_chain.run()
184     expected_result = {
185         'raw_data': {},
186         'stats': None,
187         'packet_analysis': {
188             'direction-forward': [
189                 OrderedDict([
190                     ('interface', 'vni-4097'),
191                     ('device', 'n9k'),
192                     ('packet_count', 50)
193                 ]),
194                 OrderedDict([
195                     ('interface', 'vxlan_tunnel0'),
196                     ('device', 'vpp'),
197                     ('packet_count', 48),
198                     ('packet_drop_count', 2),
199                     ('packet_drop_percentage', 4.0)
200                 ]),
201                 OrderedDict([
202                     ('interface', 'VirtualEthernet0/0/2'),
203                     ('device', 'vpp'),
204                     ('packet_count', 48),
205                     ('packet_drop_count', 0),
206                     ('packet_drop_percentage', 0.0)
207                 ]),
208                 OrderedDict([
209                     ('interface', 'VirtualEthernet0/0/3'),
210                     ('device', 'vpp'),
211                     ('packet_count', 47),
212                     ('packet_drop_count', 1),
213                     ('packet_drop_percentage', 2.0)
214                 ]),
215                 OrderedDict([
216                     ('interface', 'vxlan_tunnel1'),
217                     ('device', 'vpp'),
218                     ('packet_count', 43),
219                     ('packet_drop_count', 4),
220                     ('packet_drop_percentage', 8.0)
221                 ]),
222                 OrderedDict([
223                     ('interface', 'vni-4098'),
224                     ('device', 'n9k'),
225                     ('packet_count', 40),
226                     ('packet_drop_count', 3),
227                     ('packet_drop_percentage', 6.0)
228                 ])
229             ],
230             'direction-reverse': [
231                 OrderedDict([
232                     ('interface', 'vni-4098'),
233                     ('device', 'n9k'),
234                     ('packet_count', 77)
235                 ]),
236                 OrderedDict([
237                     ('interface', 'vxlan_tunnel1'),
238                     ('device', 'vpp'),
239                     ('packet_count', 77),
240                     ('packet_drop_count', 0),
241                     ('packet_drop_percentage', 0.0)
242                 ]),
243                 OrderedDict([
244                     ('interface', 'VirtualEthernet0/0/3'),
245                     ('device', 'vpp'),
246                     ('packet_count', 77),
247                     ('packet_drop_count', 0),
248                     ('packet_drop_percentage', 0.0)
249                 ]),
250                 OrderedDict([
251                     ('interface', 'VirtualEthernet0/0/2'),
252                     ('device', 'vpp'),
253                     ('packet_count', 77),
254                     ('packet_drop_count', 0),
255                     ('packet_drop_percentage', 0.0)
256                 ]),
257                 OrderedDict([
258                     ('interface', 'vxlan_tunnel0'),
259                     ('device', 'vpp'),
260                     ('packet_count', 77),
261                     ('packet_drop_count', 0),
262                     ('packet_drop_percentage', 0.0)
263                 ]),
264                 OrderedDict([
265                     ('interface', 'vni-4097'),
266                     ('device', 'n9k'),
267                     ('packet_count', 77),
268                     ('packet_drop_count', 0),
269                     ('packet_drop_percentage', 0.0)
270                 ])
271             ]
272         }
273     }
274     assert result == expected_result
275 """
276
277
278 # =========================================================================
279 # PVVP Chain tests
280 # =========================================================================
281
282 """
283 @pytest.fixture
284 def pvvp_chain(monkeypatch, openstack_vxlan_spec):
285     tor_vni1 = Interface('vni-4097', 'n9k', 50, 77)
286     vsw_vni1 = Interface('vxlan_tunnel0', 'vpp', 77, 48)
287     vsw_vif1 = Interface('VirtualEthernet0/0/2', 'vpp', 48, 77)
288     vsw_vif3 = Interface('VirtualEthernet0/0/0', 'vpp', 77, 47)
289     vsw_vif4 = Interface('VirtualEthernet0/0/1', 'vpp', 45, 77)
290     vsw_vif2 = Interface('VirtualEthernet0/0/3', 'vpp', 77, 44)
291     vsw_vni2 = Interface('vxlan_tunnel1', 'vpp', 43, 77)
292     tor_vni2 = Interface('vni-4098', 'n9k', 77, 40)
293
294     def mock_init(self, *args, **kwargs):
295         self.vni_ports = [4099, 4100]
296         self.v2vnet = V2VNetwork()
297         self.specs = openstack_vxlan_spec
298         self.clients = {
299             'vpp': AttrDict({
300                 'get_v2v_network': lambda reverse=None: Network([vsw_vif3, vsw_vif4], reverse),
301                 'set_interface_counters': lambda pvvp=None: None,
302                 'set_v2v_counters': lambda: None,
303             })
304         }
305         self.worker = AttrDict({
306             'run': lambda: None,
307         })
308
309     def mock_empty(self, *args, **kwargs):
310         pass
311
312     def mock_get_network(self, traffic_port, vni_id, reverse=False):
313         if vni_id == 0:
314             return Network([tor_vni1, vsw_vni1, vsw_vif1], reverse)
315         else:
316             return Network([tor_vni2, vsw_vni2, vsw_vif2], reverse)
317
318     def mock_get_data(self):
319         return {}
320
321     monkeypatch.setattr(PVVPChain, '_get_network', mock_get_network)
322     monkeypatch.setattr(PVVPChain, '_get_data', mock_get_data)
323     monkeypatch.setattr(PVVPChain, '_setup', mock_empty)
324     monkeypatch.setattr(VxLANWorker, '_clear_interfaces', mock_empty)
325     monkeypatch.setattr(PVVPChain, '_generate_traffic', mock_empty)
326     monkeypatch.setattr(PVVPChain, '__init__', mock_init)
327
328     return PVVPChain(None, None, {'vm': None, 'vpp': None, 'tor': None, 'traffic': None}, None)
329
330
331 def test_pvvp_chain_run(pvvp_chain):
332     result = pvvp_chain.run()
333
334     expected_result = {
335         'raw_data': {},
336         'stats': None,
337         'packet_analysis':
338             {'direction-forward': [
339                 OrderedDict([
340                     ('interface', 'vni-4097'),
341                     ('device', 'n9k'),
342                     ('packet_count', 50)
343                 ]),
344                 OrderedDict([
345                     ('interface', 'vxlan_tunnel0'),
346                     ('device', 'vpp'),
347                     ('packet_count', 48),
348                     ('packet_drop_count', 2),
349                     ('packet_drop_percentage', 4.0)
350                 ]),
351                 OrderedDict([
352                     ('interface', 'VirtualEthernet0/0/2'),
353                     ('device', 'vpp'),
354                     ('packet_count', 48),
355                     ('packet_drop_count', 0),
356                     ('packet_drop_percentage', 0.0)
357                 ]),
358                 OrderedDict([
359                     ('interface', 'VirtualEthernet0/0/0'),
360                     ('device', 'vpp'),
361                     ('packet_count', 47),
362                     ('packet_drop_count', 1),
363                     ('packet_drop_percentage', 2.0)
364                 ]),
365                 OrderedDict([
366                     ('interface', 'VirtualEthernet0/0/1'),
367                     ('device', 'vpp'),
368                     ('packet_count', 45),
369                     ('packet_drop_count', 2),
370                     ('packet_drop_percentage', 4.0)
371                 ]),
372                 OrderedDict([
373                     ('interface', 'VirtualEthernet0/0/3'),
374                     ('device', 'vpp'),
375                     ('packet_count', 44),
376                     ('packet_drop_count', 1),
377                     ('packet_drop_percentage', 2.0)
378                 ]),
379                 OrderedDict([
380                     ('interface', 'vxlan_tunnel1'),
381                     ('device', 'vpp'),
382                     ('packet_count', 43),
383                     ('packet_drop_count', 1),
384                     ('packet_drop_percentage', 2.0)
385                 ]),
386                 OrderedDict([
387                     ('interface', 'vni-4098'),
388                     ('device', 'n9k'),
389                     ('packet_count', 40),
390                     ('packet_drop_count', 3),
391                     ('packet_drop_percentage', 6.0)
392                 ])
393             ],
394             'direction-reverse': [
395                 OrderedDict([
396                     ('interface', 'vni-4098'),
397                     ('device', 'n9k'),
398                     ('packet_count', 77)
399                 ]),
400                 OrderedDict([
401                     ('interface', 'vxlan_tunnel1'),
402                     ('device', 'vpp'),
403                     ('packet_count', 77),
404                     ('packet_drop_count', 0),
405                     ('packet_drop_percentage', 0.0)
406                 ]),
407                 OrderedDict([
408                     ('interface', 'VirtualEthernet0/0/3'),
409                     ('device', 'vpp'),
410                     ('packet_count', 77),
411                     ('packet_drop_count', 0),
412                     ('packet_drop_percentage', 0.0)
413                 ]),
414                 OrderedDict([
415                     ('interface', 'VirtualEthernet0/0/1'),
416                     ('device', 'vpp'),
417                     ('packet_count', 77),
418                     ('packet_drop_count', 0),
419                     ('packet_drop_percentage', 0.0)
420                 ]),
421                 OrderedDict([
422                     ('interface', 'VirtualEthernet0/0/0'),
423                     ('device', 'vpp'),
424                     ('packet_count', 77),
425                     ('packet_drop_count', 0),
426                     ('packet_drop_percentage', 0.0)
427                 ]),
428                 OrderedDict([
429                     ('interface', 'VirtualEthernet0/0/2'),
430                     ('device', 'vpp'),
431                     ('packet_count', 77),
432                     ('packet_drop_count', 0),
433                     ('packet_drop_percentage', 0.0)
434                 ]),
435                 OrderedDict([
436                     ('interface', 'vxlan_tunnel0'),
437                     ('device', 'vpp'),
438                     ('packet_count', 77),
439                     ('packet_drop_count', 0),
440                     ('packet_drop_percentage', 0.0)
441                 ]),
442                 OrderedDict([
443                     ('interface', 'vni-4097'),
444                     ('device', 'n9k'),
445                     ('packet_count', 77),
446                     ('packet_drop_count', 0),
447                     ('packet_drop_percentage', 0.0)
448                 ])
449             ]}
450     }
451     assert result == expected_result
452 """
453
454 # =========================================================================
455 # Traffic client tests
456 # =========================================================================
457
458 def test_parse_rate_str():
459     parse_rate_str = traffic_utils.parse_rate_str
460     try:
461         assert parse_rate_str('100%') == {'rate_percent': '100.0'}
462         assert parse_rate_str('37.5%') == {'rate_percent': '37.5'}
463         assert parse_rate_str('100%') == {'rate_percent': '100.0'}
464         assert parse_rate_str('60pps') == {'rate_pps': '60'}
465         assert parse_rate_str('60kpps') == {'rate_pps': '60000'}
466         assert parse_rate_str('6Mpps') == {'rate_pps': '6000000'}
467         assert parse_rate_str('6gpps') == {'rate_pps': '6000000000'}
468         assert parse_rate_str('80bps') == {'rate_bps': '80'}
469         assert parse_rate_str('80bps') == {'rate_bps': '80'}
470         assert parse_rate_str('80kbps') == {'rate_bps': '80000'}
471         assert parse_rate_str('80kBps') == {'rate_bps': '640000'}
472         assert parse_rate_str('80Mbps') == {'rate_bps': '80000000'}
473         assert parse_rate_str('80 MBps') == {'rate_bps': '640000000'}
474         assert parse_rate_str('80Gbps') == {'rate_bps': '80000000000'}
475     except Exception as exc:
476         assert False, exc.message
477
478     def should_raise_error(str):
479         try:
480             parse_rate_str(str)
481         except Exception:
482             return True
483         else:
484             assert False
485
486     assert should_raise_error('101')
487     assert should_raise_error('201%')
488     assert should_raise_error('10Kbps')
489     assert should_raise_error('0kbps')
490     assert should_raise_error('0pps')
491     assert should_raise_error('-1bps')
492
493 def test_rate_conversion():
494     assert traffic_utils.load_to_bps(50, 10000000000) == pytest.approx(5000000000.0)
495     assert traffic_utils.load_to_bps(37, 10000000000) == pytest.approx(3700000000.0)
496     assert traffic_utils.load_to_bps(100, 10000000000) == pytest.approx(10000000000.0)
497
498     assert traffic_utils.bps_to_load(5000000000.0, 10000000000) == pytest.approx(50.0)
499     assert traffic_utils.bps_to_load(3700000000.0, 10000000000) == pytest.approx(37.0)
500     assert traffic_utils.bps_to_load(10000000000.0, 10000000000) == pytest.approx(100.0)
501
502     assert traffic_utils.bps_to_pps(500000, 64) == pytest.approx(744.047619048)
503     assert traffic_utils.bps_to_pps(388888, 1518) == pytest.approx(31.6066319896)
504     assert traffic_utils.bps_to_pps(9298322222, 340.3) == pytest.approx(3225895.85831)
505
506     assert traffic_utils.pps_to_bps(744.047619048, 64) == pytest.approx(500000)
507     assert traffic_utils.pps_to_bps(31.6066319896, 1518) == pytest.approx(388888)
508     assert traffic_utils.pps_to_bps(3225895.85831, 340.3) == pytest.approx(9298322222)
509
510
511 """
512 @pytest.fixture
513 def traffic_client(monkeypatch):
514
515     def mock_init(self, *args, **kwargs):
516         self.run_config = {
517             'bidirectional': False,
518             'l2frame_size': '64',
519             'duration_sec': 30,
520             'rates': [{'rate_percent': '10'}, {'rate_pps': '1'}]
521         }
522
523         self.config = AttrDict({
524             'generator_config': {
525                 'intf_speed': 10000000000
526             },
527             'ndr_run': True,
528             'pdr_run': True,
529             'single_run': False,
530             'attempts': 1,
531             'measurement': {
532                 'NDR': 0.0,
533                 'PDR': 0.1,
534                 'load_epsilon': 0.1
535             }
536         })
537
538         self.runner = AttrDict({
539             'time_elapsed': lambda: 30,
540             'stop': lambda: None,
541             'client': AttrDict({'get_stats': lambda: None})
542         })
543
544         self.current_load = None
545         self.dummy_stats = {
546             50.0: 72.6433562831,
547             25.0: 45.6095059858,
548             12.5: 0.0,
549             18.75: 27.218642979,
550             15.625: 12.68585861,
551             14.0625: 2.47154392563,
552             13.28125: 0.000663797066801,
553             12.890625: 0.0,
554             13.0859375: 0.0,
555             13.18359375: 0.00359387347122,
556             13.671875: 0.307939922531,
557             13.4765625: 0.0207718516156,
558             13.57421875: 0.0661795060969
559         }
560
561     def mock_modify_load(self, load):
562         self.run_config['rates'][0] = {'rate_percent': str(load)}
563         self.current_load = load
564
565     def mock_run_traffic(self):
566         yield {
567             'overall': {
568                 'drop_rate_percent': self.dummy_stats[self.current_load],
569                 'rx': {
570                     'total_pkts': 1,
571                     'avg_delay_usec': 0.0,
572                     'max_delay_usec': 0.0,
573                     'min_delay_usec': 0.0
574                 }
575             }
576         }
577
578     monkeypatch.setattr(TrafficClient, '__init__', mock_init)
579     monkeypatch.setattr(TrafficClient, 'modify_load', mock_modify_load)
580     monkeypatch.setattr(TrafficClient, 'run_traffic', mock_run_traffic)
581
582     return TrafficClient()
583
584
585 def test_ndr_pdr_search(traffic_client):
586     expected_results = {
587         'pdr': {
588             'l2frame_size': '64',
589             'initial_rate_type': 'rate_percent',
590             'stats': {
591                 'overall': {
592                     'drop_rate_percent': 0.0661795060969,
593                     'min_delay_usec': 0.0,
594                     'avg_delay_usec': 0.0,
595                     'max_delay_usec': 0.0
596                 }
597             },
598             'load_percent_per_direction': 13.57421875,
599             'rate_percent': 13.57422547,
600             'rate_bps': 1357422547.0,
601             'rate_pps': 2019974.0282738095,
602             'duration_sec': 30
603         },
604         'ndr': {
605             'l2frame_size': '64',
606             'initial_rate_type': 'rate_percent',
607             'stats': {
608                 'overall': {
609                     'drop_rate_percent': 0.0,
610                     'min_delay_usec': 0.0,
611                     'avg_delay_usec': 0.0,
612                     'max_delay_usec': 0.0
613                 }
614             },
615             'load_percent_per_direction': 13.0859375,
616             'rate_percent': 13.08594422,
617             'rate_bps': 1308594422.0,
618             'rate_pps': 1947313.1279761905,
619             'duration_sec': 30
620         }
621     }
622
623     results = traffic_client.get_ndr_and_pdr()
624     assert len(results) == 2
625     for result in results.values():
626         result.pop('timestamp_sec')
627         result.pop('time_taken_sec')
628     assert results == expected_results
629 """
630
631 # =========================================================================
632 # Other tests
633 # =========================================================================
634
635 def test_no_credentials():
636     cred = Credentials('/completely/wrong/path/openrc', None, False)
637     if cred.rc_auth_url:
638         # shouldn't get valid data unless user set environment variables
639         assert False
640     else:
641         assert True
642
643 def test_config():
644     refcfg = {1: 100, 2: {21: 100, 22: 200}, 3: None}
645     assert(get_err_config({}, refcfg) is None)
646     assert(get_err_config({1: 10}, refcfg) is None)
647     assert(get_err_config({2: {21: 1000}}, refcfg) is None)
648     assert(get_err_config({3: "abc"}, refcfg) is None)
649     # correctly fails
650     assert(get_err_config({4: 0}, refcfg) == {4: 0})
651     assert(get_err_config({2: {21: 100, 30: 50}}, refcfg) == {2: {30: 50}})
652     assert(get_err_config({2: {0: 1, 1: 2}}, refcfg) == {2: {0: 1, 1: 2}})
653     assert(get_err_config({2: {0: 1, 1: 2}, 5: 5}, refcfg) == {2: {0: 1, 1: 2}, 5: 5})
654     # invalid value type
655     assert(get_err_config({1: 'abc', 2: {21: 0}}, refcfg) == {1: 'abc'})
656     assert(get_err_config({2: 100}, refcfg) == {2: 100})
657     # both correctly fail and invalid value type
658     assert(get_err_config({2: 100, 5: 10}, refcfg) == {2: 100, 5: 10})