From 978a58ea1ebd8274a43cbe5a624b4a8d45bdef65 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 9 Jul 2024 14:21:37 -0700 Subject: [PATCH] unittests: Add type annotations to the helpers module --- run_mypy.py | 1 + unittests/helpers.py | 83 +++++++++++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/run_mypy.py b/run_mypy.py index 35a90e8e9..f72e96b3d 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -84,6 +84,7 @@ additional = [ 'tools', 'docs/genrefman.py', 'docs/refman', + 'unittests/helpers.py', ] if os.name == 'posix': diff --git a/unittests/helpers.py b/unittests/helpers.py index 761241308..5e9192955 100644 --- a/unittests/helpers.py +++ b/unittests/helpers.py @@ -1,3 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright © 2024 Intel Corporation + +from __future__ import annotations import subprocess import os import shutil @@ -17,20 +21,25 @@ from mesonbuild.mesonlib import ( from mesonbuild.options import OptionKey from run_tests import get_fake_env +if T.TYPE_CHECKING: + from typing_extensions import ParamSpec + + P = ParamSpec('P') + R = T.TypeVar('R') + -def is_ci(): - if os.environ.get('MESON_CI_JOBNAME') not in {None, 'thirdparty'}: - return True - return False +def is_ci() -> bool: + return os.environ.get('MESON_CI_JOBNAME', 'thirdparty') != 'thirdparty' -def skip_if_not_base_option(feature): + +def skip_if_not_base_option(feature: str) -> T.Callable[[T.Callable[P, R]], T.Callable[P, R]]: """Skip tests if The compiler does not support a given base option. for example, ICC doesn't currently support b_sanitize. """ - def actual(f): + def actual(f: T.Callable[P, R]) -> T.Callable[P, R]: @functools.wraps(f) - def wrapped(*args, **kwargs): + def wrapped(*args: P.args, **kwargs: P.kwargs) -> R: env = get_fake_env() cc = detect_c_compiler(env, MachineChoice.HOST) key = OptionKey(feature) @@ -41,7 +50,8 @@ def skip_if_not_base_option(feature): return wrapped return actual -def skipIfNoPkgconfig(f): + +def skipIfNoPkgconfig(f: T.Callable[P, R]) -> T.Callable[P, R]: ''' Skip this test if no pkg-config is found, unless we're on CI. This allows users to run our test suite without having @@ -51,19 +61,20 @@ def skipIfNoPkgconfig(f): Note: Yes, we provide pkg-config even while running Windows CI ''' @functools.wraps(f) - def wrapped(*args, **kwargs): + def wrapped(*args: P.args, **kwargs: P.kwargs) -> R: if not is_ci() and shutil.which('pkg-config') is None: raise unittest.SkipTest('pkg-config not found') return f(*args, **kwargs) return wrapped -def skipIfNoPkgconfigDep(depname): + +def skipIfNoPkgconfigDep(depname: str) -> T.Callable[[T.Callable[P, R]], T.Callable[P, R]]: ''' Skip this test if the given pkg-config dep is not found, unless we're on CI. ''' - def wrapper(func): + def wrapper(func: T.Callable[P, R]) -> T.Callable[P, R]: @functools.wraps(func) - def wrapped(*args, **kwargs): + def wrapped(*args: P.args, **kwargs: P.kwargs) -> R: if not is_ci() and shutil.which('pkg-config') is None: raise unittest.SkipTest('pkg-config not found') if not is_ci() and subprocess.call(['pkg-config', '--exists', depname]) != 0: @@ -72,7 +83,8 @@ def skipIfNoPkgconfigDep(depname): return wrapped return wrapper -def skip_if_no_cmake(f): + +def skip_if_no_cmake(f: T.Callable[P, R]) -> T.Callable[P, R]: ''' Skip this test if no cmake is found, unless we're on CI. This allows users to run our test suite without having @@ -80,16 +92,17 @@ def skip_if_no_cmake(f): silently skip the test because of misconfiguration. ''' @functools.wraps(f) - def wrapped(*args, **kwargs): + def wrapped(*args: P.args, **kwargs: P.kwargs) -> R: if not is_ci() and shutil.which('cmake') is None: raise unittest.SkipTest('cmake not found') return f(*args, **kwargs) return wrapped -def skip_if_not_language(lang: str): - def wrapper(func): + +def skip_if_not_language(lang: str) -> T.Callable[[T.Callable[P, R]], T.Callable[P, R]]: + def wrapper(func: T.Callable[P, R]) -> T.Callable[P, R]: @functools.wraps(func) - def wrapped(*args, **kwargs): + def wrapped(*args: P.args, **kwargs: P.kwargs) -> R: try: compiler_from_language(get_fake_env(), lang, MachineChoice.HOST) except EnvironmentException: @@ -98,13 +111,14 @@ def skip_if_not_language(lang: str): return wrapped return wrapper -def skip_if_env_set(key): + +def skip_if_env_set(key: str) -> T.Callable[[T.Callable[P, R]], T.Callable[P, R]]: ''' Skip a test if a particular env is set, except when running under CI ''' - def wrapper(func): + def wrapper(func: T.Callable[P, R]) -> T.Callable[P, R]: @functools.wraps(func) - def wrapped(*args, **kwargs): + def wrapped(*args: P.args, **kwargs: P.kwargs) -> R: old = None if key in os.environ: if not is_ci(): @@ -118,26 +132,27 @@ def skip_if_env_set(key): return wrapped return wrapper -def skipIfNoExecutable(exename): + +def skipIfNoExecutable(exename: str) -> T.Callable[[T.Callable[P, R]], T.Callable[P, R]]: ''' Skip this test if the given executable is not found. ''' - def wrapper(func): + def wrapper(func: T.Callable[P, R]) -> T.Callable[P, R]: @functools.wraps(func) - def wrapped(*args, **kwargs): + def wrapped(*args: P.args, **kwargs: P.kwargs) -> R: if shutil.which(exename) is None: raise unittest.SkipTest(exename + ' not found') return func(*args, **kwargs) return wrapped return wrapper -def is_tarball(): - if not os.path.isdir('docs'): - return True - return False + +def is_tarball() -> bool: + return not os.path.isdir('docs') + @contextmanager -def chdir(path: str): +def chdir(path: str) -> T.Iterator[None]: curdir = os.getcwd() os.chdir(path) try: @@ -145,6 +160,7 @@ def chdir(path: str): finally: os.chdir(curdir) + def get_dynamic_section_entry(fname: str, entry: str) -> T.Optional[str]: if is_cygwin() or is_osx(): raise unittest.SkipTest('Test only applicable to ELF platforms') @@ -162,9 +178,11 @@ def get_dynamic_section_entry(fname: str, entry: str) -> T.Optional[str]: return str(m.group(1)) return None # The file did not contain the specified entry. + def get_soname(fname: str) -> T.Optional[str]: return get_dynamic_section_entry(fname, 'soname') + def get_rpath(fname: str) -> T.Optional[str]: raw = get_dynamic_section_entry(fname, r'(?:rpath|runpath)') # Get both '' and None here @@ -178,11 +196,12 @@ def get_rpath(fname: str) -> T.Optional[str]: return None return final + def get_classpath(fname: str) -> T.Optional[str]: with zipfile.ZipFile(fname) as zip: with zip.open('META-INF/MANIFEST.MF') as member: contents = member.read().decode().strip() - lines = [] + lines: T.List[str] = [] for line in contents.splitlines(): if line.startswith(' '): # continuation line @@ -194,6 +213,7 @@ def get_classpath(fname: str) -> T.Optional[str]: } return manifest.get('class-path') + def get_path_without_cmd(cmd: str, path: str) -> str: pathsep = os.pathsep paths = OrderedSet([Path(p).resolve() for p in path.split(pathsep)]) @@ -206,10 +226,11 @@ def get_path_without_cmd(cmd: str, path: str) -> str: path = pathsep.join([str(p) for p in paths]) return path -def xfail_if_jobname(name: str): + +def xfail_if_jobname(name: str) -> T.Callable[[T.Callable[P, R]], T.Callable[P, R]]: if os.environ.get('MESON_CI_JOBNAME') == name: return unittest.expectedFailure - def wrapper(func): + def wrapper(func: T.Callable[P, R]) -> T.Callable[P, R]: return func return wrapper