2 #******************************************************************************
4 # Copyright (c) 2019 Intel.
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
10 # http://www.apache.org/licenses/LICENSE-2.0
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
18 #******************************************************************************/
20 """This script runs test cases with O-DU and O-RU
29 from itertools import dropwhile
30 from datetime import datetime
31 from time import gmtime, strftime
33 from threading import Timer
36 timeout_sec = 60*3 #3 min max
38 nLteNumRbsPerSymF1 = [
39 # 5MHz 10MHz 15MHz 20 MHz
40 [25, 50, 75, 100] # LTE Numerology 0 (15KHz)
44 # 5MHz 10MHz 15MHz 20 MHz 25 MHz 30 MHz 40 MHz 50MHz 60 MHz 70 MHz 80 MHz 90 MHz 100 MHz
45 [25, 52, 79, 106, 133, 160, 216, 270, 0, 0, 0, 0, 0], # Numerology 0 (15KHz)
46 [11, 24, 38, 51, 65, 78, 106, 133, 162, 0, 217, 245, 273], # Numerology 1 (30KHz)
47 [0, 11, 18, 24, 31, 38, 51, 65, 79, 0, 107, 121, 135] # Numerology 2 (60KHz)
51 # 50Mhz 100MHz 200MHz 400MHz
52 [66, 132, 264, 0], # Numerology 2 (60KHz)
53 [32, 66, 132, 264] # Numerology 3 (120KHz)
57 nRChBwOptions_keys = ['5','10','15','20', '25', '30', '40', '50', '60','70', '80', '90', '100', '200', '400']
58 nRChBwOptions_values = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14]
59 nRChBwOptions = dict(zip(nRChBwOptions_keys, nRChBwOptions_values))
61 nRChBwOptions_keys_mu2and3 = ['50', '100', '200', '400']
62 nRChBwOptions_values_mu2and3 = [0,1,2,3]
63 nRChBwOptions_mu2and3 = dict(zip(nRChBwOptions_keys_mu2and3, nRChBwOptions_values_mu2and3))
65 # values for Jenkins server
66 eth_cp_dev = ["0000:19:02.1", "0000:19:0a.1"]
67 eth_up_dev = ["0000:19:02.0", "0000:19:0a.0"]
69 # table of all test cases
70 # (ran, cat, mu, bw, test case)
72 NR_test_cases_A = [(0, 0, 0, 5, 0),
81 LTE_test_cases_A = [(1, 0, 0, 5, 0),
87 NR_test_cases_B = [(0, 1, 1, 100, 0),
95 #(0, 1, 1, 100, 106), 25G not enough
98 #(0, 1, 1, 100, 109), 25G not enough
100 #(0, 1, 1, 100, 202), 25G not enough
101 #(0, 1, 1, 100, 203),
106 #(0, 1, 1, 100, 212), 25G not enough
113 LTE_test_cases_B = [(1, 1, 0, 5, 0),
119 # (0, 1, 1, 100, 301), 25G not enough
127 all_test_cases = NR_test_cases_A + LTE_test_cases_A + LTE_test_cases_B + NR_test_cases_B + V_test_cases_B
129 dic_dir = dict({0:'DL', 1:'UL'})
130 dic_xu = dict({0:'o-du', 1:'o-ru'})
131 dic_ran_tech = dict({0:'5g_nr', 1:'lte'})
133 def init_logger(console_level, logfile_level):
134 """Initializes console and logfile logger with given logging levels"""
136 logging.basicConfig(filename="runtests.log",
138 format="%(asctime)s: %(levelname)s: %(message)s",
141 logger = logging.getLogger()
142 handler = logging.StreamHandler()
143 handler.setLevel(console_level)
144 formatter = logging.Formatter("%(levelname)s: %(message)s")
145 handler.setFormatter(formatter)
146 logger.addHandler(handler)
148 def parse_args(args):
149 """Configures parser and parses command line configuration"""
150 # Parser configuration
151 parser = argparse.ArgumentParser(description="Run test cases: category numerology bandwidth test_num")
153 parser.add_argument("--ran", type=int, default=0, help="Radio Access Tehcnology 0 (5G NR) or 1 (LTE)", metavar="ran", dest="rantech")
154 parser.add_argument("--cat", type=int, default=0, help="Category: 0 (A) or 1 (B)", metavar="cat", dest="category")
155 parser.add_argument("--mu", type=int, default=0, help="numerology [0,1,3]", metavar="num", dest="numerology")
156 parser.add_argument("--bw", type=int, default=20, help="bandwidth [5,10,20,100]", metavar="bw", dest="bandwidth")
157 parser.add_argument("--testcase", type=int, default=0, help="test case number", metavar="testcase", dest="testcase")
158 parser.add_argument("--verbose", type=int, default=0, help="enable verbose output", metavar="verbose", dest="verbose")
161 options = parser.parse_args(args)
163 logging.debug("Options: ran=%d category=%d num=%d bw=%d testcase=%d",
164 options.rantech, options.category, options.numerology, options.bandwidth, options.testcase)
168 """ function to check if a line
169 starts with some character.
172 # return true if a line starts with #
173 return s.startswith('#')
175 class GetOutOfLoops( Exception ):
178 def get_re_map(nRB, direction):
183 if 'nPrbElemDl' in globals():
185 for i in range(0, nPrbElm):
186 elm = str('PrbElemDl'+str(i))
189 PrbElemContent.insert(i,list(globals()[elm]))
190 xRBStart = PrbElemContent[i][0]
191 xRBSize = PrbElemContent[i][1]
192 #print(PrbElemContent,"RBStart: ", xRBStart, "RBSize: ",xRBSize, list(range(xRBStart, xRBStart + xRBSize)))
193 prb_map = prb_map + list(range(xRBStart*12, xRBStart*12 + xRBSize*12))
199 if 'nPrbElemUl' in globals():
201 for i in range(0, nPrbElm):
202 elm = str('PrbElemUl'+str(i))
204 if (elm in globals()):
205 PrbElemContent.insert(i,list(globals()[elm]))
206 xRBStart = PrbElemContent[i][0]
207 xRBSize = PrbElemContent[i][1]
208 #print(PrbElemContent,"RBStart: ", xRBStart, "RBSize: ",xRBSize, list(range(xRBStart, xRBStart + xRBSize)))
209 prb_map = prb_map + list(range(xRBStart*12, xRBStart*12 + xRBSize*12))
214 prb_map = list(range(0, nRB*12))
218 def compare_resuts(rantech, cat, mu, bw, tcase, xran_path, test_cfg, direction):
223 nDlRB = nLteNumRbsPerSymF1[mu][nRChBwOptions.get(str(nDLBandwidth))]
224 nUlRB = nLteNumRbsPerSymF1[mu][nRChBwOptions.get(str(nULBandwidth))]
226 print("Incorrect arguments\n")
231 nDlRB = nNumRbsPerSymF1[mu][nRChBwOptions.get(str(nDLBandwidth))]
232 nUlRB = nNumRbsPerSymF1[mu][nRChBwOptions.get(str(nULBandwidth))]
233 elif (mu >=2) & (mu <= 3):
234 nDlRB = nNumRbsPerSymF2[mu - 2][nRChBwOptions_mu2and3.get(str(nDLBandwidth))]
235 nUlRB = nNumRbsPerSymF2[mu - 2][nRChBwOptions_mu2and3.get(str(nULBandwidth))]
238 print("Incorrect arguments\n")
242 if 'compression' in globals():
247 if 'srsEanble' in globals():
252 print("compare results: {} [compression {}]\n".format(dic_dir.get(direction), comp))
255 # print("WARNING: Skip checking IQs and BF Weights for CAT B for now\n");
259 if nFrameDuplexType == 1:
261 for i in range(nTddPeriod):
263 SlotConfig.insert(i, sSlotConfig0)
265 SlotConfig.insert(i, sSlotConfig1)
267 SlotConfig.insert(i, sSlotConfig2)
269 SlotConfig.insert(i, sSlotConfig3)
271 SlotConfig.insert(i, sSlotConfig4)
273 SlotConfig.insert(i, sSlotConfig5)
275 SlotConfig.insert(i, sSlotConfig6)
277 SlotConfig.insert(i, sSlotConfig7)
279 SlotConfig.insert(i, sSlotConfig8)
281 SlotConfig.insert(i, sSlotConfig9)
283 raise Exception('i should not exceed nTddPeriod %d. The value of i was: {}'.format(nTddPeriod, i))
284 #print(SlotConfig, type(sSlotConfig0))
287 if (direction == 1) & (cat == 1): #UL
288 flowId = ccNum*antNumUL
290 flowId = ccNum*antNum
293 re_map = get_re_map(nDlRB, direction)
295 re_map = get_re_map(nUlRB, direction)
297 raise Exception('Direction is not supported %d'.format(direction))
299 for i in range(0, flowId):
300 #read ref and test files
306 file_tst = xran_path+"/app/logs/"+"o-ru-rx_log_ant"+str(i)+".txt"
307 file_ref = xran_path+"/app/logs/"+"o-du-play_ant"+str(i)+".txt"
311 file_tst = xran_path+"/app/logs/"+"o-du-rx_log_ant"+str(i)+".txt"
312 file_ref = xran_path+"/app/logs/"+"o-ru-play_ant"+str(i)+".txt"
314 raise Exception('Direction is not supported %d'.format(direction))
316 print("test result :", file_tst)
317 print("test reference:", file_ref)
318 if os.path.exists(file_tst):
320 file_tst = open(file_tst, 'r')
322 print ("Could not open/read file:", file_tst)
325 print(file_tst, "doesn't exist")
328 if os.path.exists(file_ref):
330 file_ref = open(file_ref, 'r')
332 print ("Could not open/read file:", file_ref)
335 print(file_tst, "doesn't exist")
339 tst = file_tst.readlines()
340 ref = file_ref.readlines()
350 for slot_idx in range(0, numSlots):
351 for sym_idx in range(0, 14):
352 if nFrameDuplexType==1:
356 sym_dir = SlotConfig[slot_idx%nTddPeriod][sym_idx]
361 sym_dir = SlotConfig[slot_idx%nTddPeriod][sym_idx]
365 #print("Check:","[",i,"]", slot_idx, sym_idx)
366 for line_idx in re_map:
367 offset = (slot_idx*nRB*12*14) + sym_idx*nRB*12 + line_idx
369 line_tst = tst[offset].rstrip()
372 print("FAIL:","IndexError on tst: ant:[",i,"]:",offset, slot_idx, sym_idx, line_idx, len(tst))
375 line_ref = ref[offset].rstrip()
378 print("FAIL:","IndexError on ref: ant:[",i,"]:",offset, slot_idx, sym_idx, line_idx, len(ref))
382 # discard LSB bits as BFP compression is not "bit exact"
383 tst_i_value = int(line_tst.split(" ")[0]) & 0xFF80
384 tst_q_value = int(line_tst.split(" ")[1]) & 0xFF80
385 ref_i_value = int(line_ref.split(" ")[0]) & 0xFF80
386 ref_q_value = int(line_ref.split(" ")[1]) & 0xFF80
388 #print("check:","ant:[",i,"]:",offset, slot_idx, sym_idx, line_idx,":","tst: ", tst_i_value, " ", tst_q_value, " " , "ref: ", ref_i_value, " ", ref_q_value, " ")
389 if (tst_i_value != ref_i_value) or (tst_q_value != ref_q_value) :
390 print("FAIL:","ant:[",i,"]:",offset, slot_idx, sym_idx, line_idx,":","tst: ", tst_i_value, " ", tst_q_value, " " , "ref: ", ref_i_value, " ", ref_q_value, " ")
395 #print("Check:", offset,"[",i,"]", slot_idx, sym_idx,":",line_tst, line_ref)
396 if line_ref != line_tst:
397 print("FAIL:","ant:[",i,"]:",offset, slot_idx, sym_idx, line_idx,":","tst:", line_tst, "ref:", line_ref)
400 except GetOutOfLoops:
403 #if (direction == 0) | (cat == 0) | (srs_enb == 0): #DL or Cat A
407 print("compare results: {} [compression {}]\n".format('SRS', comp))
412 flowId = ccNum*antElmTRx
413 for i in range(0, flowId):
414 #read ref and test files
421 file_tst = xran_path+"/app/logs/"+"o-du-srs_log_ant"+str(i)+".txt"
422 file_ref = xran_path+"/app/logs/"+"o-ru-play_srs_ant"+str(i)+".txt"
424 raise Exception('Direction is not supported %d'.format(direction))
426 print("test result :", file_tst)
427 print("test reference:", file_ref)
428 if os.path.exists(file_tst):
430 file_tst = open(file_tst, 'r')
432 print ("Could not open/read file:", file_tst)
435 print(file_tst, "doesn't exist")
438 if os.path.exists(file_ref):
440 file_ref = open(file_ref, 'r')
442 print ("Could not open/read file:", file_ref)
445 print(file_tst, "doesn't exist")
449 tst = file_tst.readlines()
450 ref = file_ref.readlines()
460 for slot_idx in range(0, numSlots):
461 for sym_idx in range(0, 14):
462 if symbMask & (1 << sym_idx):
463 print("SRS check sym ", sym_idx)
464 if nFrameDuplexType==1:
468 sym_dir = SlotConfig[slot_idx%nTddPeriod][sym_idx]
473 sym_dir = SlotConfig[slot_idx%nTddPeriod][sym_idx]
477 #print("Check:","[",i,"]", slot_idx, sym_idx)
478 for line_idx in range(0, nRB*12):
479 offset = (slot_idx*nRB*12*14) + sym_idx*nRB*12 + line_idx
481 line_tst = tst[offset].rstrip()
484 print("FAIL:","IndexError on tst: ant:[",i,"]:",offset, slot_idx, sym_idx, line_idx, len(tst))
487 line_ref = ref[offset].rstrip()
490 print("FAIL:","IndexError on ref: ant:[",i,"]:",offset, slot_idx, sym_idx, line_idx, len(ref))
492 if False : #SRS sent as not compressed
494 # discard LSB bits as BFP compression is not Bit Exact
495 tst_i_value = int(line_tst.split(" ")[0]) & 0xFF80
496 tst_q_value = int(line_tst.split(" ")[1]) & 0xFF80
497 ref_i_value = int(line_ref.split(" ")[0]) & 0xFF80
498 ref_q_value = int(line_ref.split(" ")[1]) & 0xFF80
500 print("check:","ant:[",i,"]:",offset, slot_idx, sym_idx, line_idx,":","tst: ", tst_i_value, " ", tst_q_value, " " , "ref: ", ref_i_value, " ", ref_q_value, " ")
501 if (tst_i_value != ref_i_value) or (tst_q_value != ref_q_value) :
502 print("FAIL:","ant:[",i,"]:",offset, slot_idx, sym_idx, line_idx,":","tst: ", tst_i_value, " ", tst_q_value, " " , "ref: ", ref_i_value, " ", ref_q_value, " ")
507 #print("Check:", offset,"[",i,"]", slot_idx, sym_idx,":",line_tst, line_ref)
508 if line_ref != line_tst:
509 print("FAIL:","ant:[",i,"]:",offset, slot_idx, sym_idx, line_idx,":","tst:", line_tst, "ref:", line_ref)
512 except GetOutOfLoops:
518 def parse_dat_file(rantech, cat, mu, bw, tcase, xran_path, test_cfg):
520 logging.info("parse config files %s\n", test_cfg[0])
523 with open(test_cfg[0],'r') as fh:
524 for curline in dropwhile(is_comment, fh):
525 my_line = curline.rstrip().split(sep, 1)[0].strip()
527 lineList.append(my_line)
531 for line in lineList:
532 exe_line = line.replace(":", ",")
533 if exe_line.find("/") > 0 :
534 exe_line = exe_line.replace('./', "'")
535 exe_line = exe_line+"'"
537 code = compile(str(exe_line), '<string>', 'exec')
538 exec (code, global_env, local_env)
540 for k, v in local_env.items():
546 def del_dat_file_vars(local_env):
548 for k, v in local_env.items():
553 def make_copy_mlog(rantech, cat, mu, bw, tcase, xran_path):
556 src_bin = xran_path+"/app/mlog-o-du-c0.bin"
557 src_csv = xran_path+"/app/mlog-o-du-hist.csv"
558 dst_bin = xran_path+"/app/mlog-o-du-c0-ran"+str(rantech)+"-cat"+str(cat)+"-mu"+str(mu)+"-bw"+str(bw)+"-tcase"+str(tcase)+".bin"
559 dst_csv = xran_path+"/app/mlog-o-du-hist-ran"+str(rantech)+"-cat"+str(cat)+"-mu"+str(mu)+"-bw"+str(bw)+"-tcase"+str(tcase)+".csv"
562 d_bin = shutil.copyfile(src_bin, dst_bin)
563 d_csv = shutil.copyfile(src_csv, dst_csv)
565 logging.info("MLog is not present\n")
569 logging.info("Mlog was copied\n")
572 print("Destination path:", d_bin)
573 print("Destination path:", d_csv)
575 d_bin = shutil.copyfile(src_bin, dst_bin)
576 d_csv = shutil.copyfile(src_csv, dst_csv)
578 src_bin = xran_path+"/app/mlog-o-ru-c0.bin"
579 src_csv = xran_path+"/app/mlog-o-ru-hist.csv"
580 dst_bin = xran_path+"/app/mlog-o-ru-c0-ran"+str(rantech)+"-cat"+str(cat)+"-mu"+str(mu)+"-bw"+str(bw)+"-tcase"+str(tcase)+".bin"
581 dst_csv = xran_path+"/app/mlog-o-ru-hist-ran"+str(rantech)+"-cat"+str(cat)+"-mu"+str(mu)+"-bw"+str(bw)+"-tcase"+str(tcase)+".csv"
583 d_bin = shutil.copyfile(src_bin, dst_bin)
584 d_csv = shutil.copyfile(src_csv, dst_csv)
587 d_bin = shutil.copyfile(src_bin, dst_bin)
588 d_csv = shutil.copyfile(src_csv, dst_csv)
590 logging.info("MLog is not present\n")
594 logging.info("Mlog was copied\n")
599 def run_tcase(rantech, cat, mu, bw, tcase, verbose, xran_path):
601 if rantech == 1: #LTE
603 test_config =xran_path+"/app/usecase/lte_b/mu{0:d}_{1:d}mhz".format(mu, bw)
605 test_config =xran_path+"/app/usecase/lte_a/mu{0:d}_{1:d}mhz".format(mu, bw)
607 print("Incorrect cat arguments\n")
609 elif rantech == 0: #5G NR
611 test_config =xran_path+"/app/usecase/cat_b/mu{0:d}_{1:d}mhz".format(mu, bw)
613 test_config =xran_path+"/app/usecase/mu{0:d}_{1:d}mhz".format(mu, bw)
615 print("Incorrect cat argument\n")
618 print("Incorrect rantech argument\n")
622 test_config = test_config+"/"+str(tcase)
624 app = xran_path+"/app/build/sample-app"
626 logging.debug("run: %s %s", app, test_config)
627 logging.debug("Started script: master.py, XRAN path %s", xran_path)
631 test_cfg.append(test_config+"/config_file_o_du.dat")
632 test_cfg.append(test_config+"/config_file_o_ru.dat")
635 os.chdir(xran_path+"/app/")
642 os.system('pkill -9 "sample-app"')
643 os.system('rm -rf ./logs')
646 log_file_name.append("sampleapp_log_{}_{}_cat_{}_mu{}_{}mhz_tst_{}.log".format(dic_ran_tech.get(rantech), dic_xu.get(i),cat, mu, bw, tcase))
647 with open(log_file_name[i], "w") as f:
648 run_cmd = [app, "-c", test_cfg[i], "-p", "2", eth_up_dev[i], eth_cp_dev[i]]
649 #, stdout=f, stderr=f
651 p = subprocess.Popen(run_cmd)
653 p = subprocess.Popen(run_cmd, stdout=f, stderr=f)
654 t = Timer(timeout_sec, p.kill)
657 logfile_xu.insert(i, f)
658 processes.append((p, logfile_xu[i]))
660 logging.info("Running O-DU and O-RU see output in:\n O-DU: %s\n O-RU: %s\n", xran_path+"/app/"+logfile_xu[0].name, xran_path+"/app/"+logfile_xu[1].name)
661 #while (gmtime().tm_sec % 30) <> 0:
663 print(strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()))
665 for p, f in processes:
668 if p.returncode != 0:
669 print("Application {} failed p.returncode:{}".format(dic_xu.get(i), p.returncode))
671 #logging.info("FAIL\n")
673 #sys.exit(p.returncode)
681 logging.info("O-DU and O-RU are done\n")
683 make_copy_mlog(rantech, cat, mu, bw, tcase, xran_path)
685 usecase_cfg = parse_dat_file(rantech, cat, mu, bw, tcase, xran_path, test_cfg)
687 res = compare_resuts(rantech, cat, mu, bw, tcase, xran_path, test_cfg, 0)
693 res = compare_resuts(rantech, cat, mu, bw, tcase, xran_path, test_cfg, 1)
702 del_dat_file_vars(usecase_cfg)
708 test_executed_total = 0
714 """Processes input files to produce IACA files"""
716 if os.getenv("XRAN_DIR") is not None:
717 xran_path = os.getenv("XRAN_DIR")
719 print("please set 'export XRAN_DIR' in the OS")
722 # Set up logging with given level (DEBUG, INFO, ERROR) for console end logfile
723 init_logger(logging.INFO, logging.DEBUG)
724 host_name = socket.gethostname()
725 logging.info("host: %s Started script: master.py from XRAN path %s",host_name, xran_path)
727 #custom config for dev station
728 if host_name == "sc12-xran-sub6":
729 eth_cp_dev[0] = "0000:21:02.1"
730 eth_cp_dev[1] = "0000:21:0a.1"
731 eth_up_dev[0] = "0000:21:02.0"
732 eth_up_dev[1] = "0000:21:0a.0"
734 # Parse input arguments
735 if len(sys.argv) == 1 :
736 run_total = len(all_test_cases)
738 print("Run All test cases {}\n".format(run_total))
740 options = parse_args(sys.argv[1:])
741 rantech = options.rantech
742 cat = options.category
743 mu = options.numerology
744 bw = options.bandwidth
745 tcase = options.testcase
746 verbose = options.verbose
750 for test_run_ix in range(0, run_total):
751 rantech = all_test_cases[test_run_ix][0]
752 cat = all_test_cases[test_run_ix][1]
753 mu = all_test_cases[test_run_ix][2]
754 bw = all_test_cases[test_run_ix][3]
755 tcase = all_test_cases[test_run_ix][4]
758 logging.info("Test# %d out of %d: ran %d cat %d mu %d bw %d test case %d\n",test_run_ix, run_total, rantech, cat, mu, bw, tcase)
759 res = run_tcase(rantech, cat, mu, bw, tcase, verbose, xran_path)
761 test_results.append((rantech, cat, mu, bw, tcase,'FAIL'))
764 test_results.append((rantech, cat, mu, bw, tcase,'PASS'))
766 with open('testresult.txt', 'w') as reshandle:
767 json.dump(test_results, reshandle)
769 res = run_tcase(rantech, cat, mu, bw, tcase, verbose, xran_path)
771 test_results.append((rantech, cat, mu, bw, tcase,'FAIL'))
772 test_results.append((rantech, cat, mu, bw, tcase,'PASS'))
774 with open('testresult.txt', 'w') as reshandle:
775 json.dump(test_results, reshandle)
779 if __name__ == '__main__':
780 START_TIME = datetime.now()
782 END_TIME = datetime.now()
783 logging.debug("Start time: %s, end time: %s", START_TIME, END_TIME)
784 logging.info("Execution time: %s", END_TIME - START_TIME)