Front Haul Interface Library update to third seed code contribution
[o-du/phy.git] / fhi_lib / test / master.py
1 #!/usr/bin/python
2 #******************************************************************************
3 #
4 #   Copyright (c) 2019 Intel.
5 #
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
9 #
10 #       http://www.apache.org/licenses/LICENSE-2.0
11 #
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.
17 #
18 #******************************************************************************/
19
20 """This script run test cases with O-DU and O-RU
21 """
22 import logging
23 import sys
24 import argparse
25 import re
26 import subprocess
27 import os
28 import shutil
29 from itertools import dropwhile
30 from datetime import datetime
31 import json
32
33 nNumRbsPerSymF1 = [
34     #  5MHz    10MHz   15MHz   20 MHz  25 MHz  30 MHz  40 MHz  50MHz   60 MHz  70 MHz  80 MHz   90 MHz  100 MHz
35         [25,    52,     79,     106,    133,    160,    216,    270,    0,         0,      0,      0,      0],         # Numerology 0 (15KHz)
36         [11,    24,     38,     51,     65,     78,     106,    133,    162,       0,    217,    245,    273],         # Numerology 1 (30KHz)
37         [0,     11,     18,     24,     31,     38,     51,     65,     79,        0,    107,    121,    135]          # Numerology 2 (60KHz)
38 ]
39
40 nNumRbsPerSymF2 = [
41     # 50Mhz  100MHz  200MHz   400MHz
42     [66,    132,    264,     0],       # Numerology 2 (60KHz)
43     [32,    66,     132,     264]      # Numerology 3 (120KHz)
44 ]
45
46
47 nRChBwOptions_keys = ['5','10','15','20', '25', '30', '40', '50', '60','70', '80', '90', '100', '200', '400']
48 nRChBwOptions_values = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14]
49 nRChBwOptions = dict(zip(nRChBwOptions_keys, nRChBwOptions_values))
50
51 nRChBwOptions_keys_mu2and3 = ['50', '100', '200', '400']
52 nRChBwOptions_values_mu2and3 = [0,1,2,3]
53 nRChBwOptions_mu2and3 = dict(zip(nRChBwOptions_keys_mu2and3, nRChBwOptions_values_mu2and3))
54
55 # table of all test cases
56 #                 (cat, mu, bw, test case)
57 all_test_cases =   [(0,   0,  5,   0),
58                   (0,   0,  10,  0),
59                   (0,   0,  20,  0),
60                   (0,   1,  100, 0),
61                   (0,   3,  100, 0),
62                   (1,   1,  100, 0)]
63 #Cat B
64 """ all_test_cases  =  [(1,   1,  100, 0),
65                     (1,   1,  100, 1),
66                     (1,   1,  100, 101),
67                     (1,   1,  100, 102),
68                     (1,   1,  100, 103),
69                     (1,   1,  100, 104),
70                     (1,   1,  100, 105),
71                     #(1,   1,  100, 106), 25G not enough
72                     (1,   1,  100, 107),
73                     (1,   1,  100, 108),
74                     #(1,   1,  100, 109), 25G not enough
75                     (1,   1,  100, 201),
76                     #(1,   1,  100, 202), 25G not enough
77                     (1,   1,  100, 203),
78                     (1,   1,  100, 204),
79                     (1,   1,  100, 205),
80                     (1,   1,  100, 206),
81                     (1,   1,  100, 211),
82                     #(1,   1,  100, 212), 25G not enough
83                     (1,   1,  100, 213),
84                     (1,   1,  100, 214),
85                     (1,   1,  100, 215),
86                     (1,   1,  100, 216)
87 ]
88  """
89 dic_dir = dict({0:'DL', 1:'UL'})
90 dic_xu  = dict({0:'o-du', 1:'o-ru'})
91
92 def init_logger(console_level, logfile_level):
93     """Initializes console and logfile logger with given logging levels"""
94     # File logger
95     logging.basicConfig(filename="runtests.log",
96                         filemode='w',
97                         format="%(asctime)s: %(levelname)s: %(message)s",
98                         level=logfile_level)
99     # Console logger
100     logger = logging.getLogger()
101     handler = logging.StreamHandler()
102     handler.setLevel(console_level)
103     formatter = logging.Formatter("%(levelname)s: %(message)s")
104     handler.setFormatter(formatter)
105     logger.addHandler(handler)
106
107 def parse_args(args):
108     """Configures parser and parses command line configuration"""
109     # Parser configuration
110     parser = argparse.ArgumentParser(description="Run test cases: category numerology bandwidth test_num")
111
112     parser.add_argument("--cat", type=int, default=0, help="Category: 0 (A) or 1 (B)", metavar="cat", dest="category")
113     parser.add_argument("--mu", type=int, default=0, help="numerology [0,1,3]", metavar="num", dest="numerology")
114     parser.add_argument("--bw",  type=int, default=20, help="bandwidth [5,10,20,100]", metavar="bw", dest="bandwidth")
115     parser.add_argument("--testcase", type=int, default=0, help="test case number", metavar="testcase", dest="testcase")
116
117     # Parse arguments
118     options = parser.parse_args(args)
119     #parser.print_help()
120     logging.debug("Options: category=%d num=%d bw=%d testcase=%d",
121                   options.category, options.numerology, options.bandwidth, options.testcase)
122     return options
123
124 def is_comment(s):
125     """ function to check if a line
126          starts with some character.
127          Here # for comment
128     """
129     # return true if a line starts with #
130     return s.startswith('#')
131
132 class GetOutOfLoops( Exception ):
133     pass
134
135 def compare_resuts(cat, mu, bw, tcase, xran_path, test_cfg, direction):
136     res = 0
137
138     if mu < 3:
139         nDlRB = nNumRbsPerSymF1[mu][nRChBwOptions.get(str(nDLBandwidth))]
140         nUlRB = nNumRbsPerSymF1[mu][nRChBwOptions.get(str(nULBandwidth))]
141     elif (mu >=2) & (mu <= 3):
142         nDlRB = nNumRbsPerSymF2[mu - 2][nRChBwOptions_mu2and3.get(str(nDLBandwidth))]
143         nUlRB = nNumRbsPerSymF2[mu - 2][nRChBwOptions_mu2and3.get(str(nULBandwidth))]
144         print(nDlRB, nUlRB)
145     else:
146         print("Incorrect arguments\n")
147         res = -1
148         return res
149
150     if 'compression' in globals():
151         comp = compression
152     else:
153         comp = 0
154
155     print("compare results: {} [compression {}]\n".format(dic_dir.get(direction), comp))
156
157     #if cat == 1:
158     #    print("WARNING: Skip checking IQs and BF Weights for CAT B for now\n");
159     #    return res
160
161     #get slot config
162     if nFrameDuplexType == 1:
163         SlotConfig = []
164         for i in range(nTddPeriod):
165             if i == 0:
166                 SlotConfig.insert(i, sSlotConfig0)
167             elif i == 1:
168                 SlotConfig.insert(i, sSlotConfig1)
169             elif i == 2:
170                 SlotConfig.insert(i, sSlotConfig2)
171             elif i == 3:
172                 SlotConfig.insert(i, sSlotConfig3)
173             elif i == 4:
174                 SlotConfig.insert(i, sSlotConfig4)
175             elif i == 5:
176                 SlotConfig.insert(i, sSlotConfig5)
177             elif i == 6:
178                 SlotConfig.insert(i, sSlotConfig6)
179             elif i == 7:
180                 SlotConfig.insert(i, sSlotConfig7)
181             elif i == 8:
182                 SlotConfig.insert(i, sSlotConfig8)
183             elif i == 9:
184                 SlotConfig.insert(i, sSlotConfig9)
185             else :
186                 raise Exception('i should not exceed nTddPeriod %d. The value of i was: {}'.format(nTddPeriod, i))
187         #print(SlotConfig, type(sSlotConfig0))
188     try:
189
190         if (direction == 1) & (cat == 1): #UL
191             flowId = ccNum*antNumUL
192         else:
193             flowId = ccNum*antNum
194
195         for i in range(0, flowId):
196             #read ref and test files
197             tst = []
198             ref = []
199             if direction == 0:
200                 # DL
201                 nRB = nDlRB
202                 file_tst = xran_path+"/app/logs/"+"o-ru-rx_log_ant"+str(i)+".txt"
203                 file_ref = xran_path+"/app/logs/"+"o-du-play_ant"+str(i)+".txt"
204             elif direction == 1:
205                 # UL
206                 nRB = nUlRB
207                 file_tst = xran_path+"/app/logs/"+"o-du-rx_log_ant"+str(i)+".txt"
208                 file_ref = xran_path+"/app/logs/"+"o-ru-play_ant"+str(i)+".txt"
209             else:
210                 raise Exception('Direction is not supported %d'.format(direction))
211
212             print("test result   :", file_tst)
213             print("test reference:", file_ref)
214             if os.path.exists(file_tst):
215                 try:
216                     file_tst = open(file_tst, 'r')
217                 except OSError:
218                     print ("Could not open/read file:", file_tst)
219                     sys.exit()
220             else:
221                 print(file_tst, "doesn't exist")
222                 res = -1
223                 return res
224             if os.path.exists(file_ref):
225                 try:
226                     file_ref = open(file_ref, 'r')
227                 except OSError:
228                     print ("Could not open/read file:", file_ref)
229                     sys.exit()
230             else:
231                 print(file_tst, "doesn't exist")
232                 res = -1
233                 return res
234
235             tst = file_tst.readlines()
236             ref = file_ref.readlines()
237
238             print(len(tst))
239             print(len(ref))
240
241             file_tst.close();
242             file_ref.close();
243
244             print(numSlots)
245
246             for slot_idx in range(0, numSlots):
247                 for sym_idx in range(0, 14):
248                     if nFrameDuplexType==1:
249                         #skip sym if TDD
250                         if direction == 0:
251                             #DL
252                             sym_dir = SlotConfig[slot_idx%nTddPeriod][sym_idx]
253                             if(sym_dir != 0):
254                                 continue
255                         elif direction == 1:
256                             #UL
257                             sym_dir = SlotConfig[slot_idx%nTddPeriod][sym_idx]
258                             if(sym_dir != 1):
259                                 continue
260
261                     #print("Check:","[",i,"]", slot_idx, sym_idx)
262                     for line_idx in range(0, nRB*12):
263                         offset = (slot_idx*nRB*12*14) + sym_idx*nRB*12 + line_idx
264                         line_tst = tst[offset].rstrip()
265                         line_ref = ref[offset].rstrip()
266                         if comp == 1:
267                             # discard LSB bits as BFP compression is not Bit Exact
268                             tst_i_value = int(line_tst.split(" ")[0]) & 0xFF80
269                             tst_q_value = int(line_tst.split(" ")[1]) & 0xFF80
270                             ref_i_value = int(line_ref.split(" ")[0]) & 0xFF80
271                             ref_q_value = int(line_ref.split(" ")[1]) & 0xFF80
272
273                             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, " ")
274                             if (tst_i_value != ref_i_value) or  (tst_q_value != ref_q_value) :
275                                 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, " ")
276                                 res = -1
277                                 raise GetOutOfLoops
278                         else:
279                             #if line_idx == 0:
280                                 #print("Check:", offset,"[",i,"]", slot_idx, sym_idx,":",line_tst, line_ref)
281                             if line_ref != line_tst:
282                                 print("FAIL:","ant:[",i,"]:",offset, slot_idx, sym_idx, line_idx,":","tst:", line_tst, "ref:", line_ref)
283                                 res = -1
284                                 raise GetOutOfLoops
285     except GetOutOfLoops:
286         pass
287
288     return res
289
290 def parse_dat_file(cat, mu, bw, tcase, xran_path, test_cfg):
291     #parse config files
292     logging.info("parse config files %s\n", test_cfg[0])
293     lineList = list()
294     sep = '#'
295     with open(test_cfg[0],'r') as fh:
296         for curline in dropwhile(is_comment, fh):
297             my_line = curline.rstrip().split(sep, 1)[0].strip()
298             if my_line:
299                 lineList.append(my_line)
300     global_env = {}
301     local_env = {}
302
303     for line in lineList:
304         exe_line = line.replace(":", ",")
305         if exe_line.find("/") > 0 :
306             exe_line = exe_line.replace('./', "'")
307             exe_line = exe_line+"'"
308
309         code = compile(str(exe_line), '<string>', 'exec')
310         exec (code, global_env, local_env)
311
312     for k, v in local_env.items():
313         globals()[k] = v
314         print(k, v)
315
316     return local_env
317
318 def make_copy_mlog(cat, mu, bw, tcase, xran_path):
319     res = 0
320
321     src_bin = xran_path+"/app/mlog-o-du-c0.bin"
322     src_csv = xran_path+"/app/mlog-o-du-hist.csv"
323     dst_bin = xran_path+"/app/mlog-o-du-c0-cat"+str(cat)+"-mu"+str(mu)+"-bw"+str(bw)+"-tcase"+str(tcase)+".bin"
324     dst_csv = xran_path+"/app/mlog-o-du-hist-cat"+str(cat)+"-mu"+str(mu)+"-bw"+str(bw)+"-tcase"+str(tcase)+".csv"
325
326     try:
327         d_bin  = shutil.copyfile(src_bin, dst_bin)
328         d_csv  = shutil.copyfile(src_csv, dst_csv)
329     except IOError:
330         logging.info("MLog is not present\n")
331         res = 1
332     else:
333         logging.info("Mlog was copied\n")
334
335
336     src_bin = xran_path+"/app/mlog-o-ru-c0.bin"
337     src_csv = xran_path+"/app/mlog-o-ru-hist.csv"
338     dst_bin = xran_path+"/app/mlog-o-ru-c0-cat"+str(cat)+"-mu"+str(mu)+"-bw"+str(bw)+"-tcase"+str(tcase)+".bin"
339     dst_csv = xran_path+"/app/mlog-o-ru-hist-cat"+str(cat)+"-mu"+str(mu)+"-bw"+str(bw)+"-tcase"+str(tcase)+".csv"
340
341
342     try:
343         d_bin  = shutil.copyfile(src_bin, dst_bin)
344         d_csv  = shutil.copyfile(src_csv, dst_csv)
345     except IOError:
346         logging.info("MLog is not present\n")
347         res = 1
348     else:
349         logging.info("Mlog was copied\n")
350
351
352     return res
353
354
355 def run_tcase(cat, mu, bw, tcase, xran_path):
356     if cat == 1:
357         test_config = xran_path+"/app/usecase/cat_b/mu{0:d}_{1:d}mhz".format(mu, bw)
358     elif cat == 0 :
359         test_config = xran_path+"/app/usecase/mu{0:d}_{1:d}mhz".format(mu, bw)
360     else:
361         print("Incorrect arguments\n")
362
363     if(tcase > 0) :
364         test_config = test_config+"/"+str(tcase)
365
366     app = xran_path+"/app/build/sample-app"
367
368     logging.debug("run: %s %s", app, test_config)
369     logging.debug("Started script: master.py, XRAN path %s", xran_path)
370
371     test_cfg = []
372     #TODO: add detection of ETH ports
373     eth_cp_dev = ["0000:22:02.1", "0000:22:0a.1"]
374     eth_up_dev = ["0000:22:02.0", "0000:22:0a.0"]
375
376     test_cfg.append(test_config+"/config_file_o_du.dat")
377     test_cfg.append(test_config+"/config_file_o_ru.dat")
378
379     wd = os.getcwd()
380     os.chdir(xran_path+"/app/")
381
382     processes     = []
383     logfile_xu    = []
384     log_file_name = []
385
386     os.system('rm -rf ./logs')
387
388     for i in range(2):
389         log_file_name.append("sampleapp_log_{}_cat_{}_mu{}_{}mhz_tst_{}.log".format(dic_xu.get(i),cat, mu, bw, tcase))
390         with open(log_file_name[i], "w") as f:
391             #, stdout=f, stderr=f
392             p = subprocess.Popen([app, test_cfg[i], eth_up_dev[i], eth_cp_dev[i]], stdout=f, stderr=f)
393             logfile_xu.insert(i, f)
394         processes.append((p, logfile_xu[i]))
395
396     logging.info("Running O-DU and O-RU see output in: %s %s\n", logfile_xu[0].name, logfile_xu[1].name)
397     for p, f in processes:
398         p.wait()
399         p.communicate()[0]
400         if p.returncode != 0:
401             print("Application {} failed p.returncode:{}".format(dic_xu.get(i), p.returncode))
402             print("FAIL")
403             logging.info("FAIL\n")
404             logging.shutdown()
405             sys.exit(p.returncode)
406
407         f.close()
408
409     logging.info("O-DU and O-RU are done\n")
410
411     make_copy_mlog(cat, mu, bw, tcase, xran_path)
412
413     usecase_cfg = parse_dat_file(cat, mu, bw, tcase, xran_path, test_cfg)
414
415     res = compare_resuts(cat, mu, bw, tcase, xran_path, test_cfg, 0)
416     if res != 0:
417         os.chdir(wd)
418         print("FAIL")
419         return res
420
421     res = compare_resuts(cat, mu, bw, tcase, xran_path, test_cfg, 1)
422     if res != 0:
423         os.chdir(wd)
424         print("FAIL")
425         return res
426
427     os.chdir(wd)
428     print("PASS")
429     return res
430
431 def main():
432     test_results = []
433     test_executed_total = 0
434     run_total = 0
435     cat   = 0
436     mu    = 0
437     bw    = 0
438     tcase = 0
439     """Processes input files to produce IACA files"""
440     # Find path to XRAN
441     xran_path = os.getenv("XRAN_DIR")
442
443     # Set up logging with given level (DEBUG, INFO, ERROR) for console end logfile
444     init_logger(logging.INFO, logging.DEBUG)
445     logging.info("Started script: master.py, XRAN path %s", xran_path)
446
447     # Parse input arguments
448     if len(sys.argv) == 1 :
449         run_total = len(all_test_cases)
450         print(run_total)
451         print("Run All test cases {}\n".format(run_total))
452     else:
453         options = parse_args(sys.argv[1:])
454         cat     = options.category
455         mu      = options.numerology
456         bw      = options.bandwidth
457         tcase   = options.testcase
458
459
460     if (run_total):
461         for test_run_ix in range(0, run_total):
462             cat     = all_test_cases[test_run_ix][0]
463             mu      = all_test_cases[test_run_ix][1]
464             bw      = all_test_cases[test_run_ix][2]
465             tcase   = all_test_cases[test_run_ix][3]
466
467             res = run_tcase(cat, mu, bw, tcase, xran_path)
468             if (res != 0):
469                 test_results.append((cat, mu, bw, tcase,'FAIL'))
470                 break;
471
472             test_results.append((cat, mu, bw, tcase,'PASS'))
473     else:
474         res = run_tcase(cat, mu, bw, tcase, xran_path)
475         if (res != 0):
476             test_results.append((cat, mu, bw, tcase,'FAIL'))
477         test_results.append((cat, mu, bw, tcase,'PASS'))
478
479     with open('testresult.txt', 'w') as reshandle:
480         json.dump(test_results, reshandle)
481
482     return res
483
484 if __name__ == '__main__':
485     START_TIME = datetime.now()
486     res = main()
487     END_TIME = datetime.now()
488     logging.debug("Start time: %s, end time: %s", START_TIME, END_TIME)
489     logging.info("Execution time: %s", END_TIME - START_TIME)
490     logging.shutdown()
491     sys.exit(res)