1#!/usr/bin/env python3.4 2# 3# Copyright 2022 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the 'License'); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an 'AS IS' BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import collections 18import csv 19import itertools 20import json 21import re 22import numpy 23import os 24from acts import context 25from acts import base_test 26from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger 27from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils 28from acts_contrib.test_utils.cellular.performance.CellularThroughputBaseTest import CellularThroughputBaseTest 29from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils 30 31from functools import partial 32 33LONG_SLEEP = 10 34MEDIUM_SLEEP = 2 35IPERF_TIMEOUT = 10 36SHORT_SLEEP = 1 37SUBFRAME_LENGTH = 0.001 38STOP_COUNTER_LIMIT = 3 39 40 41class CellularFr2PeakThroughputTest(CellularThroughputBaseTest): 42 """Base class to test cellular FR2 throughput 43 44 This class implements cellular FR2 throughput tests on a callbox setup. 45 The class setups up the callbox in the desired configurations, configures 46 and connects the phone, and runs traffic/iperf throughput. 47 """ 48 49 def __init__(self, controllers): 50 super().__init__(controllers) 51 base_test.BaseTestClass.__init__(self, controllers) 52 self.testcase_metric_logger = ( 53 BlackboxMappedMetricLogger.for_test_case()) 54 self.testclass_metric_logger = ( 55 BlackboxMappedMetricLogger.for_test_class()) 56 self.publish_testcase_metrics = True 57 58 def process_testcase_results(self): 59 """Publish test case metrics and save results""" 60 if self.current_test_name not in self.testclass_results: 61 return 62 testcase_data = self.testclass_results[self.current_test_name] 63 results_file_path = os.path.join( 64 context.get_current_context().get_full_output_path(), 65 '{}.json'.format(self.current_test_name)) 66 with open(results_file_path, 'w') as results_file: 67 json.dump(wputils.serialize_dict(testcase_data), 68 results_file, 69 indent=4) 70 testcase_result = testcase_data['results'][0] 71 metric_map = { 72 'tcp_udp_tput': testcase_result.get('iperf_throughput', 73 float('nan')) 74 } 75 if testcase_data['testcase_params']['endc_combo_config'][ 76 'nr_cell_count']: 77 metric_map.update({ 78 'nr_min_dl_tput': 79 testcase_result['nr_tput_result']['total']['DL']['min_tput'], 80 'nr_max_dl_tput': 81 testcase_result['nr_tput_result']['total']['DL']['max_tput'], 82 'nr_avg_dl_tput': 83 testcase_result['nr_tput_result']['total']['DL'] 84 ['average_tput'], 85 'nr_theoretical_dl_tput': 86 testcase_result['nr_tput_result']['total']['DL'] 87 ['theoretical_tput'], 88 'nr_dl_bler': 89 testcase_result['nr_bler_result']['total']['DL']['nack_ratio'] 90 * 100, 91 'nr_min_dl_tput': 92 testcase_result['nr_tput_result']['total']['UL']['min_tput'], 93 'nr_max_dl_tput': 94 testcase_result['nr_tput_result']['total']['UL']['max_tput'], 95 'nr_avg_dl_tput': 96 testcase_result['nr_tput_result']['total']['UL'] 97 ['average_tput'], 98 'nr_theoretical_dl_tput': 99 testcase_result['nr_tput_result']['total']['UL'] 100 ['theoretical_tput'], 101 'nr_ul_bler': 102 testcase_result['nr_bler_result']['total']['UL']['nack_ratio'] 103 * 100 104 }) 105 if testcase_data['testcase_params']['endc_combo_config'][ 106 'lte_cell_count']: 107 metric_map.update({ 108 'lte_min_dl_tput': 109 testcase_result['lte_tput_result']['total']['DL']['min_tput'], 110 'lte_max_dl_tput': 111 testcase_result['lte_tput_result']['total']['DL']['max_tput'], 112 'lte_avg_dl_tput': 113 testcase_result['lte_tput_result']['total']['DL'] 114 ['average_tput'], 115 'lte_theoretical_dl_tput': 116 testcase_result['lte_tput_result']['total']['DL'] 117 ['theoretical_tput'], 118 'lte_dl_bler': 119 testcase_result['lte_bler_result']['total']['DL']['nack_ratio'] 120 * 100, 121 'lte_min_dl_tput': 122 testcase_result['lte_tput_result']['total']['UL']['min_tput'], 123 'lte_max_dl_tput': 124 testcase_result['lte_tput_result']['total']['UL']['max_tput'], 125 'lte_avg_dl_tput': 126 testcase_result['lte_tput_result']['total']['UL'] 127 ['average_tput'], 128 'lte_theoretical_dl_tput': 129 testcase_result['lte_tput_result']['total']['UL'] 130 ['theoretical_tput'], 131 'lte_ul_bler': 132 testcase_result['lte_bler_result']['total']['UL']['nack_ratio'] 133 * 100 134 }) 135 if self.publish_testcase_metrics: 136 for metric_name, metric_value in metric_map.items(): 137 self.testcase_metric_logger.add_metric(metric_name, 138 metric_value) 139 140 def process_testclass_results(self): 141 """Saves CSV with all test results to enable comparison.""" 142 results_file_path = os.path.join( 143 context.get_current_context().get_full_output_path(), 144 'results.csv') 145 with open(results_file_path, 'w', newline='') as csvfile: 146 field_names = [ 147 'Test Name', 'NR DL Min. Throughput', 'NR DL Max. Throughput', 148 'NR DL Avg. Throughput', 'NR DL Theoretical Throughput', 149 'NR UL Min. Throughput', 'NR UL Max. Throughput', 150 'NR UL Avg. Throughput', 'NR UL Theoretical Throughput', 151 'NR DL BLER (%)', 'NR UL BLER (%)', 'LTE DL Min. Throughput', 152 'LTE DL Max. Throughput', 'LTE DL Avg. Throughput', 153 'LTE DL Theoretical Throughput', 'LTE UL Min. Throughput', 154 'LTE UL Max. Throughput', 'LTE UL Avg. Throughput', 155 'LTE UL Theoretical Throughput', 'LTE DL BLER (%)', 156 'LTE UL BLER (%)', 'TCP/UDP Throughput' 157 ] 158 writer = csv.DictWriter(csvfile, fieldnames=field_names) 159 writer.writeheader() 160 161 for testcase_name, testcase_results in self.testclass_results.items( 162 ): 163 for result in testcase_results['results']: 164 row_dict = { 165 'Test Name': testcase_name, 166 'TCP/UDP Throughput': 167 result.get('iperf_throughput', 0) 168 } 169 if testcase_results['testcase_params'][ 170 'endc_combo_config']['nr_cell_count']: 171 row_dict.update({ 172 'NR DL Min. Throughput': 173 result['nr_tput_result']['total']['DL'] 174 ['min_tput'], 175 'NR DL Max. Throughput': 176 result['nr_tput_result']['total']['DL'] 177 ['max_tput'], 178 'NR DL Avg. Throughput': 179 result['nr_tput_result']['total']['DL'] 180 ['average_tput'], 181 'NR DL Theoretical Throughput': 182 result['nr_tput_result']['total']['DL'] 183 ['theoretical_tput'], 184 'NR UL Min. Throughput': 185 result['nr_tput_result']['total']['UL'] 186 ['min_tput'], 187 'NR UL Max. Throughput': 188 result['nr_tput_result']['total']['UL'] 189 ['max_tput'], 190 'NR UL Avg. Throughput': 191 result['nr_tput_result']['total']['UL'] 192 ['average_tput'], 193 'NR UL Theoretical Throughput': 194 result['nr_tput_result']['total']['UL'] 195 ['theoretical_tput'], 196 'NR DL BLER (%)': 197 result['nr_bler_result']['total']['DL'] 198 ['nack_ratio'] * 100, 199 'NR UL BLER (%)': 200 result['nr_bler_result']['total']['UL'] 201 ['nack_ratio'] * 100 202 }) 203 if testcase_results['testcase_params'][ 204 'endc_combo_config']['lte_cell_count']: 205 row_dict.update({ 206 'LTE DL Min. Throughput': 207 result['lte_tput_result']['total']['DL'] 208 ['min_tput'], 209 'LTE DL Max. Throughput': 210 result['lte_tput_result']['total']['DL'] 211 ['max_tput'], 212 'LTE DL Avg. Throughput': 213 result['lte_tput_result']['total']['DL'] 214 ['average_tput'], 215 'LTE DL Theoretical Throughput': 216 result['lte_tput_result']['total']['DL'] 217 ['theoretical_tput'], 218 'LTE UL Min. Throughput': 219 result['lte_tput_result']['total']['UL'] 220 ['min_tput'], 221 'LTE UL Max. Throughput': 222 result['lte_tput_result']['total']['UL'] 223 ['max_tput'], 224 'LTE UL Avg. Throughput': 225 result['lte_tput_result']['total']['UL'] 226 ['average_tput'], 227 'LTE UL Theoretical Throughput': 228 result['lte_tput_result']['total']['UL'] 229 ['theoretical_tput'], 230 'LTE DL BLER (%)': 231 result['lte_bler_result']['total']['DL'] 232 ['nack_ratio'] * 100, 233 'LTE UL BLER (%)': 234 result['lte_bler_result']['total']['UL'] 235 ['nack_ratio'] * 100 236 }) 237 writer.writerow(row_dict) 238 239 def get_per_cell_power_sweeps(self, testcase_params): 240 """Function to get per cell power sweep lists 241 242 Args: 243 testcase_params: dict containing all test case params 244 Returns: 245 cell_power_sweeps: list of cell power sweeps for each cell under test 246 """ 247 cell_power_sweeps = [] 248 for cell in testcase_params['endc_combo_config']['cell_list']: 249 if cell['cell_type'] == 'LTE': 250 sweep = [self.testclass_params['lte_cell_power']] 251 else: 252 sweep = [self.testclass_params['nr_cell_power']] 253 cell_power_sweeps.append(sweep) 254 return cell_power_sweeps 255 256 def generate_endc_combo_config(self, test_config): 257 """Function to generate ENDC combo config from CSV test config 258 259 Args: 260 test_config: dict containing ENDC combo config from CSV 261 Returns: 262 endc_combo_config: dictionary with all ENDC combo settings 263 """ 264 endc_combo_config = collections.OrderedDict() 265 cell_config_list = [] 266 267 lte_cell_count = 1 268 lte_carriers = [1] 269 lte_scc_list = [] 270 endc_combo_config['lte_pcc'] = 1 271 lte_cell = { 272 'cell_type': 273 'LTE', 274 'cell_number': 275 1, 276 'pcc': 277 1, 278 'band': 279 test_config['lte_band'], 280 'dl_bandwidth': 281 test_config['lte_bandwidth'], 282 'ul_enabled': 283 1, 284 'duplex_mode': 285 test_config['lte_duplex_mode'], 286 'dl_mimo_config': 287 'D{nss}U{nss}'.format(nss=test_config['lte_dl_mimo_config']), 288 'ul_mimo_config': 289 'D{nss}U{nss}'.format(nss=test_config['lte_ul_mimo_config']), 290 'transmission_mode': 291 'TM1' 292 } 293 cell_config_list.append(lte_cell) 294 295 nr_cell_count = 0 296 nr_dl_carriers = [] 297 nr_ul_carriers = [] 298 for nr_cell_idx in range(1, test_config['num_dl_cells'] + 1): 299 nr_cell = { 300 'cell_type': 301 'NR5G', 302 'cell_number': 303 nr_cell_idx, 304 'band': 305 test_config['nr_band'], 306 'duplex_mode': 307 test_config['nr_duplex_mode'], 308 'channel': 309 test_config['nr_channel'], 310 'dl_mimo_config': 311 'N{nss}X{nss}'.format(nss=test_config['nr_dl_mimo_config']), 312 'dl_bandwidth_class': 313 'A', 314 'dl_bandwidth': 315 test_config['nr_bandwidth'], 316 'ul_enabled': 317 1 if nr_cell_idx <= test_config['num_ul_cells'] else 0, 318 'ul_bandwidth_class': 319 'A', 320 'ul_mimo_config': 321 'N{nss}X{nss}'.format(nss=test_config['nr_ul_mimo_config']), 322 'subcarrier_spacing': 323 'MU3' 324 } 325 cell_config_list.append(nr_cell) 326 nr_cell_count = nr_cell_count + 1 327 nr_dl_carriers.append(nr_cell_idx) 328 if nr_cell_idx <= test_config['num_ul_cells']: 329 nr_ul_carriers.append(nr_cell_idx) 330 331 endc_combo_config['lte_cell_count'] = lte_cell_count 332 endc_combo_config['nr_cell_count'] = nr_cell_count 333 endc_combo_config['nr_dl_carriers'] = nr_dl_carriers 334 endc_combo_config['nr_ul_carriers'] = nr_ul_carriers 335 endc_combo_config['cell_list'] = cell_config_list 336 endc_combo_config['lte_scc_list'] = lte_scc_list 337 endc_combo_config['lte_carriers'] = lte_carriers 338 return endc_combo_config 339 340 def generate_test_cases(self, bands, channels, nr_mcs_pair_list, 341 num_dl_cells_list, num_ul_cells_list, 342 dl_mimo_config, ul_mimo_config, **kwargs): 343 """Function that auto-generates test cases for a test class.""" 344 test_cases = [] 345 for band, channel, num_ul_cells, num_dl_cells, nr_mcs_pair in itertools.product( 346 bands, channels, num_ul_cells_list, num_dl_cells_list, 347 nr_mcs_pair_list): 348 if num_ul_cells > num_dl_cells: 349 continue 350 if channel not in cputils.PCC_PRESET_MAPPING[band]: 351 continue 352 test_config = { 353 'lte_band': 2, 354 'lte_bandwidth': 'BW20', 355 'lte_duplex_mode': 'FDD', 356 'lte_dl_mimo_config': 1, 357 'lte_ul_mimo_config': 1, 358 'nr_band': band, 359 'nr_bandwidth': 'BW100', 360 'nr_duplex_mode': 'TDD', 361 'nr_channel': channel, 362 'num_dl_cells': num_dl_cells, 363 'num_ul_cells': num_ul_cells, 364 'nr_dl_mimo_config': dl_mimo_config, 365 'nr_ul_mimo_config': ul_mimo_config 366 } 367 endc_combo_config = self.generate_endc_combo_config(test_config) 368 test_name = 'test_fr2_{}_{}_DL_{}CC_mcs{}_{}x{}_UL_{}CC_mcs{}_{}x{}'.format( 369 band, channel, num_dl_cells, nr_mcs_pair[0], dl_mimo_config, 370 dl_mimo_config, num_ul_cells, nr_mcs_pair[1], ul_mimo_config, 371 ul_mimo_config) 372 test_params = collections.OrderedDict( 373 endc_combo_config=endc_combo_config, 374 nr_dl_mcs=nr_mcs_pair[0], 375 nr_ul_mcs=nr_mcs_pair[1], 376 **kwargs) 377 setattr(self, test_name, 378 partial(self._test_throughput_bler, test_params)) 379 test_cases.append(test_name) 380 return test_cases 381 382 383class CellularFr2DlPeakThroughputTest(CellularFr2PeakThroughputTest): 384 """Base class to test cellular FR2 throughput 385 386 This class implements cellular FR2 throughput tests on a callbox setup. 387 The class setups up the callbox in the desired configurations, configures 388 and connects the phone, and runs traffic/iperf throughput. 389 """ 390 391 def __init__(self, controllers): 392 super().__init__(controllers) 393 self.testclass_params = self.user_params['throughput_test_params'] 394 self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], 395 ['low', 'mid', 'high'], 396 [(16, 4), (27, 4)], 397 list(range(1, 9)), 398 list(range(1, 3)), 399 force_contiguous_nr_channel=True, 400 dl_mimo_config=2, 401 ul_mimo_config=1, 402 schedule_scenario="FULL_TPUT", 403 traffic_direction='DL', 404 transform_precoding=0, 405 lte_dl_mcs=4, 406 lte_dl_mcs_table='QAM256', 407 lte_ul_mcs=4, 408 lte_ul_mcs_table='QAM64') 409 410 411class CellularFr2CpOfdmUlPeakThroughputTest(CellularFr2PeakThroughputTest): 412 413 def __init__(self, controllers): 414 super().__init__(controllers) 415 self.testclass_params = self.user_params['throughput_test_params'] 416 self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], 417 ['low', 'mid', 'high'], 418 [(4, 16), (4, 27)], [1], [1], 419 force_contiguous_nr_channel=True, 420 dl_mimo_config=2, 421 ul_mimo_config=1, 422 schedule_scenario="FULL_TPUT", 423 traffic_direction='UL', 424 transform_precoding=0, 425 lte_dl_mcs=4, 426 lte_dl_mcs_table='QAM256', 427 lte_ul_mcs=4, 428 lte_ul_mcs_table='QAM64') 429 self.tests.extend( 430 self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], 431 ['low', 'mid', 'high'], 432 [(4, 16), (4, 27)], [1], [1], 433 force_contiguous_nr_channel=True, 434 dl_mimo_config=2, 435 ul_mimo_config=2, 436 schedule_scenario="FULL_TPUT", 437 traffic_direction='UL', 438 transform_precoding=0, 439 lte_dl_mcs=4, 440 lte_dl_mcs_table='QAM256', 441 lte_ul_mcs=4, 442 lte_ul_mcs_table='QAM64')) 443 self.tests.extend( 444 self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], 445 ['low', 'mid', 'high'], 446 [(4, 16), (4, 27)], [2], [2], 447 force_contiguous_nr_channel=True, 448 dl_mimo_config=2, 449 ul_mimo_config=2, 450 schedule_scenario="FULL_TPUT", 451 traffic_direction='UL', 452 transform_precoding=0, 453 lte_dl_mcs=4, 454 lte_dl_mcs_table='QAM256', 455 lte_ul_mcs=4, 456 lte_ul_mcs_table='QAM64')) 457 self.tests.extend( 458 self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], 459 ['low', 'mid', 'high'], 460 [(4, 16), (4, 27)], [4], [4], 461 force_contiguous_nr_channel=True, 462 dl_mimo_config=2, 463 ul_mimo_config=2, 464 schedule_scenario="FULL_TPUT", 465 traffic_direction='UL', 466 transform_precoding=0, 467 lte_dl_mcs=4, 468 lte_dl_mcs_table='QAM256', 469 lte_ul_mcs=4, 470 lte_ul_mcs_table='QAM64')) 471 472 473class CellularFr2DftsOfdmUlPeakThroughputTest(CellularFr2PeakThroughputTest): 474 475 def __init__(self, controllers): 476 super().__init__(controllers) 477 self.testclass_params = self.user_params['throughput_test_params'] 478 self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], 479 ['low', 'mid', 'high'], 480 [(4, 16), (4, 27)], [1], [1], 481 force_contiguous_nr_channel=True, 482 dl_mimo_config=2, 483 ul_mimo_config=1, 484 schedule_scenario="FULL_TPUT", 485 traffic_direction='UL', 486 transform_precoding=1, 487 lte_dl_mcs=4, 488 lte_dl_mcs_table='QAM256', 489 lte_ul_mcs=4, 490 lte_ul_mcs_table='QAM64') 491 self.tests.extend( 492 self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], 493 ['low', 'mid', 'high'], 494 [(4, 16), (4, 27)], [1], [1], 495 force_contiguous_nr_channel=True, 496 dl_mimo_config=2, 497 ul_mimo_config=2, 498 schedule_scenario="FULL_TPUT", 499 traffic_direction='UL', 500 transform_precoding=1, 501 lte_dl_mcs=4, 502 lte_dl_mcs_table='QAM256', 503 lte_ul_mcs=4, 504 lte_ul_mcs_table='QAM64')) 505 self.tests.extend( 506 self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], 507 ['low', 'mid', 'high'], 508 [(4, 16), (4, 27)], [2], [2], 509 force_contiguous_nr_channel=True, 510 dl_mimo_config=2, 511 ul_mimo_config=2, 512 schedule_scenario="FULL_TPUT", 513 traffic_direction='UL', 514 transform_precoding=1, 515 lte_dl_mcs=4, 516 lte_dl_mcs_table='QAM256', 517 lte_ul_mcs=4, 518 lte_ul_mcs_table='QAM64')) 519 self.tests.extend( 520 self.generate_test_cases(['N257', 'N258', 'N260', 'N261'], 521 ['low', 'mid', 'high'], 522 [(4, 16), (4, 27)], [4], [4], 523 force_contiguous_nr_channel=True, 524 dl_mimo_config=2, 525 ul_mimo_config=2, 526 schedule_scenario="FULL_TPUT", 527 traffic_direction='UL', 528 transform_precoding=1, 529 lte_dl_mcs=4, 530 lte_dl_mcs_table='QAM256', 531 lte_ul_mcs=4, 532 lte_ul_mcs_table='QAM64')) 533 534 535class CellularFr2DlFrequencySweepPeakThroughputTest( 536 CellularFr2PeakThroughputTest): 537 """Base class to test cellular FR2 throughput 538 539 This class implements cellular FR2 throughput tests on a callbox setup. 540 The class setups up the callbox in the desired configurations, configures 541 and connects the phone, and runs traffic/iperf throughput. 542 """ 543 544 def __init__(self, controllers): 545 super().__init__(controllers) 546 self.testclass_params = self.user_params['throughput_test_params'] 547 self.tests = self.generate_test_cases( 548 ['N257', 'N258', 'N260', 'N261'], 549 self.user_params['throughput_test_params']['frequency_sweep'], 550 [(16, 4), (27, 4)], 551 force_contiguous_nr_channel=False, 552 dl_mimo_config=2, 553 ul_mimo_config=1, 554 schedule_scenario="FULL_TPUT", 555 traffic_direction='DL', 556 transform_precoding=0, 557 lte_dl_mcs=4, 558 lte_dl_mcs_table='QAM256', 559 lte_ul_mcs=4, 560 lte_ul_mcs_table='QAM64') 561 562 def generate_test_cases(self, bands, channels, nr_mcs_pair_list, 563 num_dl_cells_list, num_ul_cells_list, 564 dl_mimo_config, ul_mimo_config, **kwargs): 565 """Function that auto-generates test cases for a test class.""" 566 test_cases = [] 567 for band, channel, num_ul_cells, num_dl_cells, nr_mcs_pair in itertools.product( 568 bands, channels, num_ul_cells_list, num_dl_cells_list, 569 nr_mcs_pair_list): 570 if num_ul_cells > num_dl_cells: 571 continue 572 if channel not in cputils.PCC_PRESET_MAPPING[band]: 573 continue 574 test_config = { 575 'lte_band': 2, 576 'lte_bandwidth': 'BW20', 577 'lte_duplex_mode': 'FDD', 578 'lte_dl_mimo_config': 1, 579 'lte_ul_mimo_config': 1, 580 'nr_band': band, 581 'nr_bandwidth': 'BW100', 582 'nr_duplex_mode': 'TDD', 583 'nr_channel': channel, 584 'num_dl_cells': num_dl_cells, 585 'num_ul_cells': num_ul_cells, 586 'nr_dl_mimo_config': dl_mimo_config, 587 'nr_ul_mimo_config': ul_mimo_config 588 } 589 endc_combo_config = self.generate_endc_combo_config(test_config) 590 test_name = 'test_fr2_{}_{}_DL_{}CC_mcs{}_{}x{}_UL_{}CC_mcs{}_{}x{}'.format( 591 band, channel, num_dl_cells, nr_mcs_pair[0], dl_mimo_config, 592 dl_mimo_config, num_ul_cells, nr_mcs_pair[1], ul_mimo_config, 593 ul_mimo_config) 594 test_params = collections.OrderedDict( 595 endc_combo_config=endc_combo_config, 596 nr_dl_mcs=nr_mcs_pair[0], 597 nr_ul_mcs=nr_mcs_pair[1], 598 **kwargs) 599 setattr(self, test_name, 600 partial(self._test_throughput_bler, test_params)) 601 test_cases.append(test_name) 602 return test_cases 603