xref: /aosp_15_r20/external/bcc/tests/python/utils.py (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
1from pyroute2 import NSPopen
2import traceback
3import shutil
4
5import logging, os, sys, re
6
7if 'PYTHON_TEST_LOGFILE' in os.environ:
8    logfile=os.environ['PYTHON_TEST_LOGFILE']
9    logging.basicConfig(level=logging.ERROR, filename=logfile, filemode='a')
10else:
11    logging.basicConfig(level=logging.ERROR, stream=sys.stderr)
12
13logger = logging.getLogger()
14
15def has_executable(name):
16    path = shutil.which(name)
17    if path is None:
18        raise Exception(name + ": command not found")
19    return path
20
21# This is a decorator that will allow for logging tests, but flagging them as
22# "known to fail". These tests legitimately fail and represent actual bugs, but
23# as these are already documented the test status can be "green" without these
24# tests, similar to catch2's [!mayfail] tag.
25# This is done using the existing python unittest concept of an "expected failure",
26# but it is only done after the fact, if the test fails or raises an exception.
27# It gives all tests a chance to succeed, but if they fail it logs them and
28# continues.
29def mayFail(message):
30    def decorator(func):
31        def wrapper(*args, **kwargs):
32            res = None
33            err = None
34            try:
35                res = func(*args, **kwargs)
36            except BaseException as e:
37                logger.critical("WARNING! Test %s failed, but marked as passed because it is decorated with @mayFail." %
38                       args[0])
39                logger.critical("\tThe reason why this mayFail was: %s" % message)
40                logger.critical("\tThe failure was: \"%s\"" % e)
41                logger.critical("\tStacktrace: \"%s\"" % traceback.format_exc())
42                testcase=args[0]
43                testcase.TestResult().addExpectedFailure(testcase, e)
44                err = e
45            finally:
46                if err != None:
47                    raise err
48                else:
49                    return res
50        return wrapper
51    return decorator
52
53# This is a decorator that will skip tests if any binary in the list is not in PATH.
54def skipUnlessHasBinaries(binaries, message):
55    def decorator(func):
56        def wrapper(self, *args, **kwargs):
57            missing = []
58            for binary in binaries:
59                if shutil.which(binary) is None:
60                    missing.append(binary)
61
62            if len(missing):
63                missing_binaries = ", ".join(missing)
64                self.skipTest(f"Missing binaries: {missing_binaries}. {message}")
65            else:
66                func(self, *args, **kwargs)
67        return wrapper
68    return decorator
69
70class NSPopenWithCheck(NSPopen):
71    """
72    A wrapper for NSPopen that additionally checks if the program
73    to be executed is available from the system path or not.
74    If found, it proceeds with the usual NSPopen() call.
75    Otherwise, it raises an exception.
76    """
77
78    def __init__(self, nsname, *argv, **kwarg):
79        name = list(argv)[0][0]
80        has_executable(name)
81        super(NSPopenWithCheck, self).__init__(nsname, *argv, **kwarg)
82
83KERNEL_VERSION_PATTERN = r"v?(?P<major>[0-9]+)\.(?P<minor>[0-9]+).*"
84
85def kernel_version_ge(major, minor):
86    # True if running kernel is >= X.Y
87    match = re.match(KERNEL_VERSION_PATTERN, os.uname()[2])
88    x = int(match.group("major"))
89    y = int(match.group("minor"))
90    if x > major:
91        return True
92    if x < major:
93        return False
94    if minor and y < minor:
95        return False
96    return True
97