It was initially foreseen to just check the program exit code, but most executables don't return with 0 (so the CI would always fail). Add a separate check that will check for the Bye., only going through the last X lines instead of the entire file.
162 lines
5.3 KiB
Python
162 lines
5.3 KiB
Python
#/*
|
|
# * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
|
|
# * contributor license agreements. See the NOTICE file distributed with
|
|
# * this work for additional information regarding copyright ownership.
|
|
# * The OpenAirInterface Software Alliance licenses this file to You under
|
|
# * the OAI Public License, Version 1.1 (the "License"); you may not use this file
|
|
# * except in compliance with the License.
|
|
# * You may obtain a copy of the License at
|
|
# *
|
|
# * http://www.openairinterface.org/?page_id=698
|
|
# *
|
|
# * Unless required by applicable law or agreed to in writing, software
|
|
# * distributed under the License is distributed on an "AS IS" BASIS,
|
|
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# * See the License for the specific language governing permissions and
|
|
# * limitations under the License.
|
|
# *-------------------------------------------------------------------------------
|
|
# * For more information about the OpenAirInterface (OAI) Software Alliance:
|
|
# * contact@openairinterface.org
|
|
# */
|
|
#---------------------------------------------------------------------
|
|
|
|
import re
|
|
import os
|
|
import logging
|
|
from collections import deque
|
|
|
|
class Default:
|
|
def run(file, opt=None):
|
|
success = True
|
|
logs = []
|
|
with open(file, "r") as f:
|
|
for line in f.readlines():
|
|
result = re.search('[Aa]ssertion', line)
|
|
if result:
|
|
logs.append(line)
|
|
success = False
|
|
return success, "\n".join(logs)
|
|
|
|
class ContainsString:
|
|
def run(file, needle):
|
|
with open(file, "r") as f:
|
|
for line in f.readlines():
|
|
if re.search(needle, line):
|
|
return True, ""
|
|
return False, f"could not find '{needle}' in logs"
|
|
|
|
class LastLineContains:
|
|
def run(file, string):
|
|
with open(file, "r") as f:
|
|
last_line = deque(f, maxlen=1).pop()
|
|
success = string in last_line
|
|
log = "" if success else f"could not find '{string}' in last line"
|
|
return success, log
|
|
|
|
class EndsWithBye:
|
|
def run(file, option):
|
|
# no option: we ignore any
|
|
with open(file, "r") as f:
|
|
n = 0
|
|
for line in reversed(f.readlines()):
|
|
if re.search(r'^Bye.\n', line, re.MULTILINE) is not None:
|
|
return True, ""
|
|
n = n + 1
|
|
if n > 50: # if not found in last 50 lines, assume it does not exist
|
|
break
|
|
return False, "No Bye. message found, did not stop properly"
|
|
|
|
class RetxCheck():
|
|
def _parseList(s):
|
|
if not s:
|
|
return []
|
|
return [int(n) for n in s.split(",")]
|
|
|
|
def _get_ul_dl(s):
|
|
dl, ul = (None, None)
|
|
for o in s.split(";", 1):
|
|
if o.startswith("dl="):
|
|
dl = o[3:]
|
|
elif o.startswith("ul="):
|
|
ul = o[3:]
|
|
else:
|
|
raise ValueError(f"unrecognized option {s}: needs format '[ul=,dl=]X,Y,...[;ul=Z,V,...]'")
|
|
return dl, ul
|
|
|
|
def _parseRetxCheckers(options):
|
|
if not "=" in options:
|
|
dlul = RetxCheck._parseList(options)
|
|
return dlul, dlul
|
|
dl, ul = RetxCheck._get_ul_dl(options)
|
|
return RetxCheck._parseList(dl), RetxCheck._parseList(ul)
|
|
|
|
def _analyzeUeRetx(rounds, checkers, regex):
|
|
perc = list(0 for i in checkers) # results in %
|
|
stats = list(False for i in checkers) # status if succeeded
|
|
tmp = re.match(regex, rounds)
|
|
if tmp is None:
|
|
logging.error('_analyzeUeRetx: did not match regex for DL retx analysis')
|
|
return stats
|
|
retx_data = [float(x) for x in tmp.groups()]
|
|
for i in range(0, len(perc)):
|
|
#case where numerator > denumerator with denum ==0 is disregarded, cannot hapen in principle, will lead to 0%
|
|
perc[i] = 0 if (retx_data[i] == 0) else 100 * retx_data[i + 1] / retx_data[i]
|
|
#treating % > 100 , % > requirement
|
|
stats[i] = perc[i] <= 100 and perc[i] <= checkers[i]
|
|
return stats
|
|
|
|
def _filter_retx_rounds(l):
|
|
# map to round number if False, else None
|
|
l = [i+1 if x is False else None for i, x in enumerate(l)]
|
|
# filter out None
|
|
return [x for x in l if x is not None]
|
|
|
|
def run(file, options):
|
|
# aggregate statistics per UE
|
|
dlsch_ulsch_stats = {}
|
|
status = True
|
|
with open(file, "r") as f:
|
|
for line in f.readlines():
|
|
keys = {'dlsch_rounds','ulsch_rounds'}
|
|
for k in keys:
|
|
result = re.search(k, line)
|
|
if result is None:
|
|
continue
|
|
result = re.search('UE (?:RNTI )?([0-9a-f]{4})', line)
|
|
if result is None:
|
|
logging.error(f'did not find RNTI while matching key {k}')
|
|
status = False
|
|
continue
|
|
rnti = result.group(1)
|
|
if not rnti in dlsch_ulsch_stats:
|
|
dlsch_ulsch_stats[rnti] = {}
|
|
dlsch_ulsch_stats[rnti][k]=re.sub(r'^.*\]\s+', r'' , line.rstrip())
|
|
|
|
dl, ul = RetxCheck._parseRetxCheckers(options)
|
|
msgs = []
|
|
for ue in dlsch_ulsch_stats:
|
|
dlulstat = dlsch_ulsch_stats[ue]
|
|
|
|
for key in dlulstat:
|
|
msgs.append(dlulstat[key])
|
|
|
|
retx_dl = RetxCheck._analyzeUeRetx(dlulstat['dlsch_rounds'], dl, r'^.*dlsch_rounds\s+(\d+)\/(\d+)\/(\d+)\/(\d+),\s+dlsch_errors\s+(\d+)')
|
|
retx_ul = RetxCheck._analyzeUeRetx(dlulstat['ulsch_rounds'], ul, r'^.*ulsch_rounds\s+(\d+)\/(\d+)\/(\d+)\/(\d+),\s+ulsch_errors\s+(\d+)')
|
|
|
|
if False in retx_dl or False in retx_ul:
|
|
dlstr = RetxCheck._filter_retx_rounds(retx_dl)
|
|
ulstr = RetxCheck._filter_retx_rounds(retx_ul)
|
|
msg = f"UE {ue}: retx rounds"
|
|
if dlstr:
|
|
msg = f"{msg} DL {dlstr}"
|
|
if ulstr:
|
|
msg = f"{msg} UL {ulstr}"
|
|
msg = f"{msg} exceeded threshold"
|
|
msgs.append('!!! Failure: ' + msg)
|
|
logging.error(f'\u001B[1;37;41m {msg}\u001B[0m')
|
|
status = False
|
|
else:
|
|
msg = f"UE {ue} retransmissions within bounds"
|
|
logging.debug(msg)
|
|
return status, "\n".join(msgs)
|