# SPDX-License-Identifier: LicenseRef-CSSL-1.0 #--------------------------------------------------------------------- # Python for CI of OAI-eNB + COTS-UE # # Required Python Version # Python 3.x # # Required Python Package # pexpect #--------------------------------------------------------------------- #----------------------------------------------------------- # Import #----------------------------------------------------------- import re # reg import fileinput import logging import os import time import subprocess import constants as CONST #----------------------------------------------------------- # Class Declaration #----------------------------------------------------------- class HTMLManagement(): def __init__(self): self.htmlFile = '' self.htmlHeaderCreated = False self.htmlFooterCreated = False self.repository = '' self.branch = '' self.nbTestXMLfiles = 0 self.htmlTabRefs = [] self.htmlTabNames = [] self.htmlTabIcons = [] self.testXMLfiles = [] self.htmleNBFailureMsg = '' self.htmlUEFailureMsg = '' self.startTime = int(round(time.time() * 1000)) self.testCaseIdx = '' self.desc = '' #----------------------------------------------------------- # HTML structure creation functions #----------------------------------------------------------- def CreateHtmlHeader(self): if (not self.htmlHeaderCreated): logging.info('\u001B[1m----------------------------------------\u001B[0m') logging.info('\u001B[1m Creating HTML header \u001B[0m') logging.info('\u001B[1m----------------------------------------\u001B[0m') self.htmlFile = open('test_results.html', 'w') self.htmlFile.write('\n') self.htmlFile.write('\n') self.htmlFile.write('\n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' Test Results for TEMPLATE_JOB_NAME job build #TEMPLATE_BUILD_ID\n') self.htmlFile.write('\n') self.htmlFile.write('
\n') self.htmlFile.write('
\n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write('
\n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' TEMPLATE_JOB_NAME -- Build-ID: TEMPLATE_BUILD_ID\n') self.htmlFile.write('
\n') self.htmlFile.write('
\n') self.htmlFile.write('
TEMPLATE_STAGE_NAME
\n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') commit_id = subprocess.check_output("git log -n1 --pretty=format:\"%H\" ", shell=True, universal_newlines=True) commit_id = commit_id.strip() self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') commit_message = subprocess.check_output("git log -n1 --pretty=format:\"%s\" ", shell=True, universal_newlines=True) commit_message = commit_message.strip() self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write('
Build Start Time (UTC) TEMPLATE_BUILD_TIME
GIT Repository ' + self.repository + '
Test Branch ' + self.branch + '
Commit ID ' + commit_id + '
Commit Message ' + commit_message + '
\n') self.htmlFile.write('
\n') self.htmlFile.write(' \n') self.htmlFile.write('
\n') self.htmlFile.close() def CreateHtmlTabHeader(self): if (not self.htmlHeaderCreated): if (not os.path.isfile('test_results.html')): self.CreateHtmlHeader('none') self.htmlFile = open('test_results.html', 'a') if (self.nbTestXMLfiles == 1): self.htmlFile.write('
\n') self.htmlFile.write('

Test Summary for ' + self.testXMLfiles[0] + '

\n') else: self.htmlFile.write('
\n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.close() self.htmlHeaderCreated = True def CreateHtmlTabFooter(self, passStatus): if ((not self.htmlFooterCreated) and (self.htmlHeaderCreated)): self.htmlFile = open('test_results.html', 'a') self.htmlFile.write(' \n') self.htmlFile.write(' \n') if passStatus: self.htmlFile.write(' \n') else: self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write('
Relative Time (s)Test IndexTest DescTest OptionsTest StatusInfo
Final Tab StatusPASS FAIL
\n') self.htmlFile.write('
\n') self.htmlFile.close() if passStatus: cmd = "sed -i -e 's/__STATE_" + self.htmlTabNames[0] + "__//' test_results.html" subprocess.run(cmd, shell=True) else: cmd = "sed -i -e 's/__STATE_" + self.htmlTabNames[0] + r"__/<\/span>/' test_results.html" subprocess.run(cmd, shell=True) self.htmlFooterCreated = False def CreateHtmlFooter(self, passStatus): if (os.path.isfile('test_results.html')): # Tagging the 1st tab as active so it is automatically opened. firstTabFound = False for line in fileinput.FileInput("test_results.html", inplace=1): if re.search('tab-pane fade', line) and not firstTabFound: firstTabFound = True print(line.replace('tab-pane fade', 'tab-pane fade in active'), end ='') else: print(line, end ='') self.htmlFile = open('test_results.html', 'a') self.htmlFile.write('
\n') self.htmlFile.write('

\n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') if passStatus: self.htmlFile.write(' \n') else: self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write('
Final StatusPASS FAIL
\n') self.htmlFile.write('

\n') self.htmlFile.write('
End of Test Report -- Copyright 2026 OpenAirInterface. All Rights Reserved.
\n') self.htmlFile.write('
\n') self.htmlFile.write('\n') self.htmlFile.close() def CreateHtmlTestRow(self, options, status, processesStatus, machine='eNB'): if (self.htmlFooterCreated or (not self.htmlHeaderCreated)): return self.htmlFile = open('test_results.html', 'a') currentTime = int(round(time.time() * 1000)) - self.startTime self.htmlFile.write(' \n') self.htmlFile.write(' ' + format(currentTime / 1000, '.1f') + '\n') self.htmlFile.write(' ' + self.testCaseIdx + '\n') self.htmlFile.write(' ' + self.desc + '\n') self.htmlFile.write(' ' + str(options) + '\n') if (str(status) == 'OK'): self.htmlFile.write(' ' + str(status) + '\n') elif (str(status) == 'KO'): if (processesStatus == 0): self.htmlFile.write(' ' + str(status) + '\n') elif (processesStatus == CONST.ENB_PROCESS_FAILED): self.htmlFile.write(' KO - eNB process not found\n') elif (processesStatus == CONST.OAI_UE_PROCESS_FAILED): self.htmlFile.write(' KO - OAI UE process not found\n') elif (processesStatus == CONST.ENB_PROCESS_SEG_FAULT) or (processesStatus == CONST.OAI_UE_PROCESS_SEG_FAULT): self.htmlFile.write(' KO - ' + machine + ' process ended in Segmentation Fault\n') elif (processesStatus == CONST.ENB_PROCESS_ASSERTION) or (processesStatus == CONST.OAI_UE_PROCESS_ASSERTION): self.htmlFile.write(' KO - ' + machine + ' process ended in Assertion\n') elif (processesStatus == CONST.ENB_PROCESS_REALTIME_ISSUE): self.htmlFile.write(' KO - ' + machine + ' process faced Real Time issue(s)\n') elif (processesStatus == CONST.ENB_PROCESS_NOLOGFILE_TO_ANALYZE) or (processesStatus == CONST.OAI_UE_PROCESS_NOLOGFILE_TO_ANALYZE): self.htmlFile.write(' OK?\n') elif (processesStatus == CONST.ENB_PROCESS_SLAVE_RRU_NOT_SYNCED): self.htmlFile.write(' KO - ' + machine + ' Slave RRU could not synch\n') elif (processesStatus == CONST.OAI_UE_PROCESS_COULD_NOT_SYNC): self.htmlFile.write(' KO - UE could not sync\n') elif (processesStatus == CONST.HSS_PROCESS_FAILED): self.htmlFile.write(' KO - HSS process not found\n') elif (processesStatus == CONST.MME_PROCESS_FAILED): self.htmlFile.write(' KO - MME process not found\n') elif (processesStatus == CONST.SPGW_PROCESS_FAILED): self.htmlFile.write(' KO - SPGW process not found\n') elif (processesStatus == CONST.UE_IP_ADDRESS_ISSUE): self.htmlFile.write(' KO - Could not retrieve UE IP address\n') elif (processesStatus == CONST.PHYSIM_IMAGE_ABSENT): self.htmlFile.write(' KO - No such image oai-physim\n') elif (processesStatus == CONST.OC_LOGIN_FAIL): self.htmlFile.write(' KO - Could not log onto cluster\n') elif (processesStatus == CONST.OC_PROJECT_FAIL): self.htmlFile.write(' KO - Could not register into cluster project\n') elif (processesStatus == CONST.OC_IS_FAIL): self.htmlFile.write(' KO - Could not create Image Stream\n') elif (processesStatus == CONST.OC_PHYSIM_DEPLOY_FAIL): self.htmlFile.write(' KO - Could not properly deploy physim on cluster\n') else: self.htmlFile.write(' ' + str(status) + '\n') else: self.htmlFile.write(' ' + str(status) + '\n') if (len(str(self.htmleNBFailureMsg)) > 2): cellBgColor = 'white' result = re.search('ended with|faced real time issues', self.htmleNBFailureMsg) if result is not None: cellBgColor = 'red' else: result = re.search('showed|Reestablishment|Could not copy eNB logfile', self.htmleNBFailureMsg) if result is not None: cellBgColor = 'orange' self.htmlFile.write('
' + self.htmleNBFailureMsg + '
\n') self.htmleNBFailureMsg = '' elif (len(str(self.htmlUEFailureMsg)) > 2): cellBgColor = 'white' result = re.search('ended with|faced real time issues', self.htmlUEFailureMsg) if result is not None: cellBgColor = 'red' else: result = re.search('showed|Could not copy UE logfile|oaitun_ue1 interface is either NOT mounted or NOT configured', self.htmlUEFailureMsg) if result is not None: cellBgColor = 'orange' self.htmlFile.write('
' + self.htmlUEFailureMsg + '
\n') self.htmlUEFailureMsg = '' else: self.htmlFile.write(' -\n') self.htmlFile.write(' \n') self.htmlFile.close() #for the moment it is limited to 4 columns, to be made generic later def CreateHtmlDataLogTable(self, DataLog): if (self.htmlFooterCreated or (not self.htmlHeaderCreated)): return self.htmlFile = open('test_results.html', 'a') # TabHeader self.htmlFile.write(' \n') self.htmlFile.write(' ---- ' + DataLog['Title'] + ' ---- \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' '+ DataLog['ColNames'][0] +'\n') self.htmlFile.write(' ' + DataLog['ColNames'][1] + '\n') self.htmlFile.write(' '+ DataLog['ColNames'][2] +'\n') self.htmlFile.write(' \n') for k in DataLog['Data']: # TestRow avg = DataLog['Data'][k][0] maxval = DataLog['Data'][k][1] count = DataLog['Data'][k][2] valnorm = float(DataLog['Data'][k][3]) dev = DataLog['DeviationThreshold'][k] ref = DataLog['Ref'][k] self.htmlFile.write(' \n') self.htmlFile.write(' ' + k + ' \n') self.htmlFile.write(f' {avg}; {maxval}; {count}\n') if valnorm > 1.0 + dev or valnorm < 1.0 - dev: self.htmlFile.write(f' {valnorm} (Avg over Ref = {avg} over {ref}; max allowed deviation = {dev})\n') else: self.htmlFile.write(f' {valnorm} (Avg over Ref = {avg} over {ref}; max allowed deviation = {dev})\n') self.htmlFile.write(' \n') self.htmlFile.close() def CreateHtmlTestRowQueue(self, options, status, infoList): if ((not self.htmlFooterCreated) and (self.htmlHeaderCreated)): self.htmlFile = open('test_results.html', 'a') currentTime = int(round(time.time() * 1000)) - self.startTime addOrangeBK = False self.htmlFile.write(' \n') self.htmlFile.write(' ' + format(currentTime / 1000, '.1f') + '\n') self.htmlFile.write(' ' + self.testCaseIdx + '\n') self.htmlFile.write(' ' + self.desc + '\n') self.htmlFile.write(' ' + str(options) + '\n') if (str(status) == 'OK'): self.htmlFile.write(f' {status}\n') elif (str(status) == 'KO'): self.htmlFile.write(f' {status}\n') elif str(status) == 'SKIP': self.htmlFile.write(f' {status}\n') else: addOrangeBK = True self.htmlFile.write(f' {status}\n') if (addOrangeBK): self.htmlFile.write(' ') else: self.htmlFile.write(' ') for i in infoList: # add custom style to have elements side-by-side to reduce need for vertical space self.htmlFile.write(f'
{i}
') self.htmlFile.write(' ') self.htmlFile.write(' \n') self.htmlFile.close() def CreateHtmlTestRowPhySimTestResult(self, testSummary, testResult): if (self.htmlFooterCreated or (not self.htmlHeaderCreated)): return self.htmlFile = open('test_results.html', 'a') if bool(testResult) == False and bool(testSummary) == False: self.htmlFile.write(' \n') self.htmlFile.write(' ----PHYSIM TESTING FAILED - Unable to recover the test logs ---- \n') self.htmlFile.write(' \n') else: # Tab header self.htmlFile.write(' \n') self.htmlFile.write(' ---- PHYSIM TEST SUMMARY---- \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' LogFile Name\n') self.htmlFile.write(' Nb Tests\n') self.htmlFile.write(' Nb Failure\n') self.htmlFile.write(' Nb Pass\n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' physim_log.txt \n') self.htmlFile.write(' ' + str(testSummary['Nbtests']) + ' \n') if testSummary['Nbfail'] == 0: self.htmlFile.write(' ' + str(testSummary['Nbfail']) + '\n') else: self.htmlFile.write(' ' + str(testSummary['Nbfail']) + '\n') self.htmlFile.write(' ' + str(testSummary['Nbpass']) + ' \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' ---- PHYSIM TEST DETAIL INFO---- \n') self.htmlFile.write(' \n') self.htmlFile.write(' \n') self.htmlFile.write(' Test Name\n') self.htmlFile.write(' Test Description\n') self.htmlFile.write(' Test Status\n') self.htmlFile.write(' Info\n') self.htmlFile.write(' \n') y = '' for key, value in testResult.items(): x = key.split(".") if x[2] != y: self.htmlFile.write(' \n') self.htmlFile.write(' "' + x[2] + '" series \n') self.htmlFile.write(' \n') y = x[2] self.htmlFile.write(' \n') self.htmlFile.write(' ' + key + ' \n') self.htmlFile.write(' ' + value[0] + '\n') if 'PASS' in value: self.htmlFile.write(' ' + value[2] + '\n') else: self.htmlFile.write(' ' + value[2] + '\n') self.htmlFile.write(f'
{value[1]}
\n') self.htmlFile.close()