diff --git a/projects/rocprim/CHANGELOG.md b/projects/rocprim/CHANGELOG.md index f405848e979..1e80deb3644 100644 --- a/projects/rocprim/CHANGELOG.md +++ b/projects/rocprim/CHANGELOG.md @@ -95,6 +95,8 @@ The following is the complete list of affected functions and how their default a ### Added +* Added `-b` option to `rmake.py`. This option will allow users to build benchmarks only instead of having +to build tests as well with `-c` option. * Added extended tests to `rtest.py`. These tests are extra tests that did not fit the criteria of smoke and regression tests. These tests will take much longer to run relative to smoke and regression tests. * Use `python rtest.py [--emulation|-e|--test|-t]=extended` to run these tests. * Added regression tests to `rtest.py`. Regression tests are a subset of tests that caused hardware problems for past emulation environments. diff --git a/projects/rocprim/rmake.py b/projects/rocprim/rmake.py index dd9cc3993d8..2ac6e3e66de 100644 --- a/projects/rocprim/rmake.py +++ b/projects/rocprim/rmake.py @@ -2,254 +2,234 @@ """ Copyright (c) 2021-2025 Advanced Micro Devices, Inc. All rights reserved. Manage build and installation""" -import re -import sys import os import subprocess import argparse -import ctypes -import pathlib -from fnmatch import fnmatchcase - -args = {} -param = {} -OS_info = {} - -def parse_args(): - """Parse command-line arguments""" - parser = argparse.ArgumentParser(description=""" - Checks build arguments - """) - - default_gpus = 'gfx906:xnack-,gfx1030,gfx1100,gfx1101,gfx1102,gfx1151,gfx1200,gfx1201' - - parser.add_argument('-g', '--debug', required=False, default=False, action='store_true', - help='Generate Debug build (default: False)') - parser.add_argument( '--build_dir', type=str, required=False, default="build", - help='Build directory path (default: build)') - parser.add_argument( '--deps_dir', type=str, required=False, default=None, - help='Dependencies directory path (default: build/deps)') - parser.add_argument( '--skip_ld_conf_entry', required=False, default=False) - parser.add_argument( '--static', required=False, default=False, dest='static_lib', action='store_true', - help='Generate static library build (default: False)') - parser.add_argument('-c', '--clients', required=False, default=False, dest='build_clients', action='store_true', - help='Generate all client builds (default: False)') - parser.add_argument('-t', '--tests', required=False, default=False, dest='build_tests', action='store_true', - help='Generate unit tests only (default: False)') - parser.add_argument('-i', '--install', required=False, default=False, dest='install', action='store_true', - help='Install after build (default: False)') - parser.add_argument( '--cmake-darg', required=False, dest='cmake_dargs', action='append', default=[], - help='List of additional cmake defines for builds (e.g. CMAKE_CXX_COMPILER_LAUNCHER=ccache)') - parser.add_argument('-a', '--architecture', dest='gpu_architecture', required=False, default=default_gpus, #:sramecc+:xnack-" ) #gfx1030" ) #gfx906" ) # gfx1030" ) - help='Set GPU architectures, e.g. all, gfx000, gfx803, gfx906:xnack-;gfx1030;gfx1100 (optional, default: all)') - parser.add_argument('-v', '--verbose', required=False, default=False, action='store_true', - help='Verbose build (default: False)') - return parser.parse_args() - -def os_detect(): - inf_file = "/etc/os-release" - if os.path.exists(inf_file): - with open(inf_file) as f: - for line in f: - if "=" in line: - k,v = line.strip().split("=") - OS_info[k] = v.replace('"','') - else: - OS_info["ID"] = 'windows' - OS_info["VERSION_ID"] = 10 - OS_info["NUM_PROC"] = os.cpu_count() - print(OS_info) - -def create_dir(dir_path): - if os.path.isabs(dir_path): - full_path = dir_path - else: - fullpath = os.path.join( os.getcwd(), dir_path ) - pathlib.Path(fullpath).mkdir(parents=True, exist_ok=True) - return - -def delete_dir(dir_path) : - if (not os.path.exists(dir_path)): - return - if (OS_info["ID"] == 'windows'): - run_cmd( "RMDIR" , f"/S /Q {dir_path}") - else: - linux_path = pathlib.Path(dir_path).absolute() - run_cmd( "rm" , f"-rf {linux_path}") - -def cmake_path(os_path): - if OS_info["ID"] == "windows": - return os_path.replace("\\", "/") - else: - return os.path.realpath(os_path) - -def config_cmd(): - global args - global OS_info - cwd_path = os.getcwd() - src_path = cwd_path.replace("\\", "/") - - print( f"***************** {src_path}") - cmake_executable = "" - cmake_options = [] - cmake_platform_opts = [] - if (OS_info["ID"] == 'windows'): - # we don't have ROCM on windows but have hip, ROCM can be downloaded if required - # CMAKE_PREFIX_PATH set to rocm_path and HIP_PATH set BY SDK Installer - raw_rocm_path = cmake_path(os.getenv('HIP_PATH', "C:/hip")) - rocm_path = f'"{raw_rocm_path}"' # guard against spaces in path - cmake_executable = "cmake.exe" - toolchain = os.path.join( src_path, "toolchain-windows.cmake" ) - #set CPACK_PACKAGING_INSTALL_PREFIX= defined as blank as it is appended to end of path for archive creation - cmake_platform_opts.append( f"-DWIN32=ON -DCPACK_PACKAGING_INSTALL_PREFIX=") #" -DCPACK_PACKAGING_INSTALL_PREFIX={rocm_path}" - cmake_platform_opts.append( f"-DCMAKE_INSTALL_PREFIX=\"C:/hipSDK\"" ) - - # MSVC requires acknowledgement of using extended aligned storage. - # Before VS 2017 15.8, has non-conforming alignment. VS 2017 15.8 fixes this, but inherently changes layouts of - # aligned storage with extended alignment, and thus binary compatibility with such types. - cmake_platform_opts.append( "-DCMAKE_CXX_FLAGS=\"-D_ENABLE_EXTENDED_ALIGNED_STORAGE\"") - - rocm_cmake_path = '"' + cmake_path(os.getenv("ROCM_CMAKE_PATH", "C:/hipSDK")) + '"' - generator = f"-G Ninja" - # "-G \"Visual Studio 16 2019\" -A x64" # -G NMake ") # - cmake_options.append( generator ) - else: - rocm_path = os.getenv( 'ROCM_PATH', "/opt/rocm") - rocm_cmake_path = '"' + rocm_path + '"' - if (OS_info["ID"] in ['centos', 'rhel']): - cmake_executable = "cmake3" +import platform as pf +import shutil + +class AutoBuilder: + def __parse_args__(self): + """Parse command-line arguments""" + self.default_gpus = 'gfx906:xnack-,gfx1030,gfx1100,gfx1101,gfx1102,gfx1151,gfx1200,gfx1201' + + self.parser.add_argument('-a', '--architecture', dest='gpu_architecture', required=False, default=self.default_gpus, + help='Set GPU architectures, e.g. all, gfx000, gfx803, gfx906:xnack-;gfx1030;gfx1100 (optional, default: all)') + self.parser.add_argument('-b', '--benchmarks', required=False, default=False, dest='build_bench', action='store_true', + help='Generate benchmarks only (default: False)') + self.parser.add_argument('-c', '--clients', required=False, default=False, dest='build_clients', action='store_true', + help='Generate all client builds (default: False)') + self.parser.add_argument('-g', '--debug', required=False, default=False, action='store_true', + help='Generate Debug build (default: False)') + self.parser.add_argument('-i', '--install', required=False, default=False, dest='install', action='store_true', + help='Install after build (default: False)') + self.parser.add_argument('-t', '--tests', required=False, default=False, dest='build_tests', action='store_true', + help='Generate unit tests only (default: False)') + self.parser.add_argument('-v', '--verbose', required=False, default=False, action='store_true', + help='Verbose build (default: False)') + self.parser.add_argument( '--build_dir', type=str, required=False, default="build", + help='Build directory path (default: build)') + self.parser.add_argument( '--cmake-darg', required=False, dest='cmake_dargs', action='append', default=[], + help='List of additional cmake defines for builds (e.g. CMAKE_CXX_COMPILER_LAUNCHER=ccache)') + self.parser.add_argument( '--deps_dir', type=str, required=False, default=None, + help='Dependencies directory path (default: build/deps)') + self.parser.add_argument( '--skip_ld_conf_entry', required=False, default=False) + self.parser.add_argument( '--static', required=False, default=False, dest='static_lib', action='store_true', + help='Generate static library build (default: False)') + + self.args = self.parser.parse_args() + + def __init__(self): + self.parser = argparse.ArgumentParser(description="Checks build arguments") + self.__parse_args__() + + sysInfo = pf.uname() + + # getting os information + self.OS_info = { + "Machine" : sysInfo.machine, + "Node Name" : sysInfo.node, + "Num Processor" : os.cpu_count(), + "Processor" : sysInfo.processor, + "Release" : sysInfo.release, + "System" : sysInfo.system, + "Version" : sysInfo.version, + } + + self.custom_cmake_args = set() + self.cmake_options = [] + + for item in self.args.cmake_dargs: + self.custom_cmake_args.add(item.split('=')[0]) # get the argument name + + self.lib_dir = os.path.dirname(os.path.abspath(__file__)) + self.toolchain = f'toolchain-linux.cmake' if self.OS_info['System'] == 'Linux' else f'toolchain-windows.cmake' + + def __mk_dir__(self, dir_path: str): + if os.path.isabs(dir_path): + full_path = dir_path else: - cmake_executable = "cmake" - toolchain = "toolchain-linux.cmake" - cmake_platform_opts = [f"-DROCM_DIR:PATH={rocm_path}", f"-DCPACK_PACKAGING_INSTALL_PREFIX={rocm_path}"] + full_path = os.path.join(self.lib_dir, dir_path) - tools = f"-DCMAKE_TOOLCHAIN_FILE={toolchain}" - cmake_options.append( tools ) + try: + os.makedirs(full_path) + except FileExistsError: + ... # file already exists - cmake_options.extend( cmake_platform_opts) - - - # build type - cmake_config = "" - build_dir = args.build_dir - if not args.debug: - build_path = os.path.join(build_dir, "release") - cmake_config="Release" - else: - build_path = os.path.join(build_dir, "debug") - cmake_config="Debug" - - cmake_options.append( f"-DCMAKE_BUILD_TYPE={cmake_config}" ) #--build {build_path}" ) - - if args.deps_dir is None: - deps_dir = os.path.abspath(os.path.join(build_dir, 'deps')).replace('\\','/') - else: - deps_dir = args.deps_dir - - if (OS_info["ID"] == 'windows'): - cmake_base_options = f"-DROCM_PATH={rocm_path} -DCMAKE_PREFIX_PATH:PATH={rocm_path[:-1]};{rocm_cmake_path[1:]}" # -DCMAKE_INSTALL_PREFIX=rocmath-install" #-DCMAKE_INSTALL_LIBDIR= - else: - cmake_base_options = f"-DROCM_PATH={rocm_path} -DCMAKE_PREFIX_PATH:PATH={rocm_path[:-1]},{rocm_cmake_path[1:-1]}" # -DCMAKE_INSTALL_PREFIX=rocmath-install" #-DCMAKE_INSTALL_LIBDIR= + def __rm_dir__(self, dir_path: str): + if os.path.isabs(dir_path): + full_path = dir_path + else: + full_path = os.path.join(self.lib_dir, dir_path) + try: + if self.OS_info['System'] == 'Linux': + subprocess.run(f'rm -rf "{full_path}"', shell=True) + else: + subprocess.run(f'RMDIR /S /Q {full_path}', shell=True) + + except FileNotFoundError: + ... # no file to remove + + def __insert_cmake_args__(self, arg, val): + # This is to prevent the case of having duplicate cmake args from the argument --cmake-darg + if arg not in self.custom_cmake_args: + self.cmake_options.append(f'-D{arg}={val}') - cmake_options.append( cmake_base_options ) - - print( cmake_options ) - - # clean - delete_dir( build_path ) - - create_dir( os.path.join(build_path, "clients") ) - os.chdir( build_path ) - - # packaging options - cmake_pack_options = f"-DCPACK_SET_DESTDIR=OFF -DCPACK_INCLUDE_TOPLEVEL_DIRECTORY=OFF" - cmake_options.append( cmake_pack_options ) - - if args.static_lib: - cmake_options.append( f"-DBUILD_SHARED_LIBS=OFF" ) - - if args.skip_ld_conf_entry: - cmake_options.append( f"-DROCM_DISABLE_LDCONFIG=ON" ) - - if args.build_tests: - cmake_options.append( f"-DBUILD_TEST=ON -DBUILD_DIR={build_dir}" ) - - if args.build_clients: - cmake_options.append( f"-DBUILD_TEST=ON -DBUILD_BENCHMARK=ON -DBUILD_EXAMPLE=ON -DBUILD_DIR={build_dir}" ) - - cmake_options.append( f"-DAMDGPU_TARGETS={args.gpu_architecture}" ) - - if args.cmake_dargs: - for i in args.cmake_dargs: - cmake_options.append( f"-D{i}" ) - - cmake_options.append( f"{src_path}") - -# case "${ID}" in -# centos|rhel) -# cmake_options="${cmake_options} -DCMAKE_FIND_ROOT_PATH=/usr/lib64/llvm7.0/lib/cmake/" -# ;; -# windows) -# cmake_options="${cmake_options} -DWIN32=ON -DROCM_PATH=${rocm_path} -DROCM_DIR:PATH=${rocm_path} -DCMAKE_PREFIX_PATH:PATH=${rocm_path}" -# cmake_options="${cmake_options} --debug-trycompile -DCMAKE_MAKE_PROGRAM=nmake.exe -DCMAKE_TOOLCHAIN_FILE=toolchain-windows.cmake" -# # -G '"NMake Makefiles JOM"'" -# ;; -# esac - cmd_opts = " ".join(cmake_options) - - return cmake_executable, cmd_opts - - -def make_cmd(): - global args - global OS_info - - make_options = [] - - if (OS_info["ID"] == 'windows'): - make_executable = "cmake.exe --build ." # ninja" - if args.verbose: - make_options.append( "--verbose" ) - make_options.append( "--target all" ) - if args.install: - make_options.append( "--target package --target install" ) - else: - nproc = OS_info["NUM_PROC"] - make_executable = f"make -j {nproc}" - if args.verbose: - make_options.append( "VERBOSE=1" ) - if args.install: - make_options.append( "install" ) - cmd_opts = " ".join(make_options) - - return make_executable, cmd_opts - -def run_cmd(exe, opts): - program = f"{exe} {opts}" - if sys.platform.startswith('win'): - sh = True - else: - sh = True - print(program) - proc = subprocess.run(program, check=True, stderr=subprocess.STDOUT, shell=sh) - #proc = subprocess.Popen(cmd, cwd=os.getcwd()) - #cwd=os.path.join(workingdir,"..",".."), stdout=fout, stderr=fout, - # env=os.environ.copy()) - #proc.wait() - return proc.returncode - -def main(): - global args - os_detect() - args = parse_args() - # configure - exe, opts = config_cmd() - run_cmd(exe, opts) - - # make/build/install - exe, opts = make_cmd() - run_cmd(exe, opts) + def __get_cmake_cmd__(self): + + m = ' Current Working Directory ' + print(f'{m:-^100}\n\t{self.lib_dir}') + print() + + self.__insert_cmake_args__('CMAKE_TOOLCHAIN_FILE', self.toolchain) + self.__insert_cmake_args__('CPACK_INCLUDE_TOPLEVEL_DIRECTORY', 'OFF') + self.__insert_cmake_args__('CPACK_SET_DESTDIR', 'OFF') + self.__insert_cmake_args__('GPU_TARGETS', self.args.gpu_architecture) + + cmake_exe = '' + if self.args.debug: + build_path = os.path.join(self.args.build_dir, 'debug') + cmake_config = 'Debug' + else: + build_path = os.path.join(self.args.build_dir, 'release') + cmake_config = 'Release' + + self.build_path = build_path + + self.cmake_options.append(f"-DCMAKE_BUILD_TYPE={cmake_config}") + + if self.OS_info['System'] == 'Linux': + cmake_exe = shutil.which('cmake3') + + if cmake_exe is None: + cmake_exe = shutil.which('cmake') + + if cmake_exe is None: + raise(SystemError('Did not find cmake or cmake3 in system')) + + rocm_path = os.getenv('ROCM_PATH', '/opt/rocm') + + self.__insert_cmake_args__('CMAKE_CXX_FLAGS', f'"-w"') + self.__insert_cmake_args__('CMAKE_PREFIX_PATH:PATH', rocm_path) + self.__insert_cmake_args__('CPACK_PACKAGING_INSTALL_PREFIX', rocm_path) + self.__insert_cmake_args__('ROCM_DIR:PATH', rocm_path) + else: + cmake_exe = shutil.which('cmake.exe') + + if cmake_exe is None: + cmake_exe = shutil.which('cmake3.exe') + + if cmake_exe is None: + raise(SystemError('Did not find cmake or cmake3 in system')) + + rocm_path = os.getenv('ROCM_PATH', 'C:/hip') + rocm_cmake_path = os.getenv('ROCM_CMAKE_PATH', r'C:/hipSDK') + + rocm_path.replace('\\', '/') + rocm_cmake_path.replace('\\', '/') + + if '-G Ninja' not in self.custom_cmake_args: + self.cmake_options.append(f'-G Ninja') + self.__insert_cmake_args__('WIN32', 'ON') + self.__insert_cmake_args__('CPACK_PACKAGING_INSTALL_PREFIX', '') + self.__insert_cmake_args__('CMAKE_INSTALL_PREFIX', rocm_cmake_path) + self.__insert_cmake_args__('CMAKE_CXX_FLAGS', f'"-D_ENABLE_EXTENDED_ALIGNED_STORAGE -w"') + self.__insert_cmake_args__('CMAKE_PREFIX_PATH:PATH', f'{rocm_path};{rocm_cmake_path}') + + self.__insert_cmake_args__('ROCM_PATH', rocm_path) + + if self.args.static_lib: self.__insert_cmake_args__('BUILD_SHARED_LIBS', 'OFF') + if self.args.skip_ld_conf_entry: self.__insert_cmake_args__('ROCM_DISABLE_LDCONFIG', 'ON') + + if self.args.build_clients: + self.args.build_tests = True + self.args.build_bench = True + self.__insert_cmake_args__('BUILD_EXAMPLE', 'ON') + + if self.args.build_tests: self.__insert_cmake_args__('BUILD_TEST', 'ON') + if self.args.build_bench: self.__insert_cmake_args__('BUILD_BENCHMARK', 'ON') + + if self.args.cmake_dargs: + self.cmake_options += [f'-D{i}' for i in self.args.cmake_dargs] + + # putting '' around paths to avoid white space in pathing + if self.OS_info['System'] == 'Linux': + command_str = f"{cmake_exe}" + else: + command_str = f'"{cmake_exe}"' + + m = ' CMake Options ' + print(f'{m:-^100}') + for op in self.cmake_options: + print(f'\t{op}') + command_str += f' {op}' + print() + + command_str += f' "{self.lib_dir}"' + m = ' Final Command ' + print(f'{m:-^100}') + print(command_str) + print() + return command_str + + def __call__(self): + m = ' System Information ' + print() + print(f'{m:-^100}') + + for k in self.OS_info: + print(f'\t {k}: {self.OS_info[k]}') + print() + + cmake_command = self.__get_cmake_cmd__() + + self.__rm_dir__(self.build_path) + self.__rm_dir__('build') + self.__mk_dir__(self.build_path) + + curr_dir = os.path.abspath(os.curdir) + os.chdir(self.build_path) + + subprocess.run(cmake_command, shell=True) + + if self.OS_info['System'] == 'Linux': + v = '' + if self.args.verbose: + v = ' VERBOSE=1' + subprocess.run(f' make -j {self.OS_info["Num Processor"]}{v}', shell=True) + + if self.args.install: + subprocess.run(f'make install', shell=True) + else: + v = '' + if self.args.verbose: + v = ' --verbose' + subprocess.run(f'ninja -j {self.OS_info["Num Processor"]}{v}', shell=True) + if self.args.install: + subprocess.run(f'ninja install', shell=True) + os.chdir(curr_dir) if __name__ == '__main__': - main() + builder = AutoBuilder() + builder() diff --git a/projects/rocprim/rtest.py b/projects/rocprim/rtest.py index 029b5665df6..4d1426e8bad 100644 --- a/projects/rocprim/rtest.py +++ b/projects/rocprim/rtest.py @@ -2,352 +2,179 @@ """Copyright (c) 2021-2024 Advanced Micro Devices, Inc. All rights reserved. Run tests on build""" -import re import os import sys import subprocess -import shlex import argparse -import pathlib -import platform -from genericpath import exists -from fnmatch import fnmatchcase from xml.dom import minidom -import multiprocessing -import time +import platform as pf -args = {} -OS_info = {} +#TODO Implement time outs when its added to the xml +#TODO Implement VRAM limit when its added to the xml -timeout = False -test_proc = None -stop = 0 - -test_script = [ 'cd %IDIR%', '%XML%' ] - -def parse_args(): - """Parse command-line arguments""" - parser = argparse.ArgumentParser(description=""" - Checks build arguments - """) - parser.add_argument('-e', '--emulation', required=False, default="", - help='Test set to run from rtest.xml (optional, eg.smoke). At least one but not both of -e or -t must be set') - parser.add_argument('-t', '--test', required=False, default="", - help='Test set to run from rtest.xml (optional, e.g. osdb). At least one but not both of -e or -t must be set') - parser.add_argument('-g', '--debug', required=False, default=False, action='store_true', - help='Test Debug build (optional, default: false)') - parser.add_argument('-o', '--output', type=str, required=False, default="xml", - help='Test output file (optional, default: test_detail.xml)') - parser.add_argument( '--install_dir', type=str, required=False, default="build", - help='Installation directory where build or release folders are (optional, default: build)') - parser.add_argument( '--fail_test', default=False, required=False, action='store_true', - help='Return as if test failed (optional, default: false)') - # parser.add_argument('-v', '--verbose', required=False, default = False, action='store_true', - # help='Verbose install (optional, default: False)') - return parser.parse_args() +class TestRunner(): + def __get_vram__(self): + if self.OS_info['System'] == 'Linux': + process = subprocess.run('rocm-smi --showmeminfo vram', shell=True, stdout=subprocess.PIPE) + gpu_id = os.getenv('HIP_VISIBLE_DEVICES', '0') + for l in process.stdout.decode().splitlines(): + if 'Total Memory' in l and f'GPU[{gpu_id}]' in l: + self.OS_info['VRAM'] = float(l.split()[-1]) / (1024 ** 3) + break -def vram_detect(): - global OS_info - OS_info["VRAM"] = 0 - if os.name == "nt": - cmd = "hipinfo.exe" - process = subprocess.run([cmd], stdout=subprocess.PIPE) - for line_in in process.stdout.decode().splitlines(): - if 'totalGlobalMem' in line_in: - OS_info["VRAM"] = float(line_in.split()[1]) - break - else: - cmd = "rocminfo" - process = subprocess.run([cmd], stdout=subprocess.PIPE) - for line_in in process.stdout.decode().splitlines(): - match = re.search(r'.*Size:.*([0-9]+)\(.*\).*KB', line_in, re.IGNORECASE) - if match: - OS_info["VRAM"] = float(match.group(1))/(1024*1024) - break + def __parse_args__(self): + self.parser.add_argument('-e', '--emulation', required=False, default='', + help='Test set to run from rtest.xml (optional, eg.smoke). At least one but not both of -e or -t must be set') + self.parser.add_argument('-t', '--test', required=False, default='', + help='Test set to run from rtest.xml (optional, e.g. osdb). At least one but not both of -e or -t must be set') + self.parser.add_argument('-g', '--debug', required=False, default=False, action='store_true', + help='Test Debug build (optional, default: false)') + self.parser.add_argument('-o', '--output', type=str, required=False, default=None, + help='Test output file (optional, default: None [output to stdout])') + self.parser.add_argument( '--install_dir', type=str, required=False, default="build", + help='Installation directory where build or release folders are (optional, default: build)') + self.parser.add_argument( '--test_dir', type=str, required=False, default=None, + help='Test directory where rocprim tests are (optinal, default=None)') + self.parser.add_argument( '--fail_test', default=False, required=False, action='store_true', + help='Return as if test failed (optional, default: false)') + self.args = self.parser.parse_args() + + def __init__(self): + self.parser = argparse.ArgumentParser(description=""" + Checks build arguments + """) + + self.__parse_args__() + + if (self.args.emulation != '') ^ (self.args.test != ''): + if self.args.emulation != '': + self.test_choice = self.args.emulation + else: + self.test_choice = self.args.test + else: + raise ValueError('At least one but not both of -e/--emulation or -t/--test must be set') + + sysInfo = pf.uname() -def os_detect(): - global OS_info - if os.name == "nt": - OS_info["ID"] = platform.system() - else: - inf_file = "/etc/os-release" - if os.path.exists(inf_file): - with open(inf_file) as f: - for line in f: - if "=" in line: - k,v = line.strip().split("=") - OS_info[k] = v.replace('"','') - OS_info["NUM_PROC"] = os.cpu_count() - vram_detect() - print(OS_info) + # getting os information + self.OS_info = { + "Machine" : sysInfo.machine, + "Node Name" : sysInfo.node, + "Num Processor" : os.cpu_count(), + "Processor" : sysInfo.processor, + "Release" : sysInfo.release, + "System" : sysInfo.system, + "Version" : sysInfo.version, + } + self.__get_vram__() -def create_dir(dir_path): - if os.path.isabs(dir_path): - full_path = dir_path - else: - full_path = os.path.join( os.getcwd(), dir_path ) - return pathlib.Path(full_path).mkdir(parents=True, exist_ok=True) + m = ' System Information ' -def delete_dir(dir_path) : - if (not os.path.exists(dir_path)): - return - if os.name == "nt": - return run_cmd( "RMDIR" , f"/S /Q {dir_path}") - else: - linux_path = pathlib.Path(dir_path).absolute() - return run_cmd( "rm" , f"-rf {linux_path}") + print() + print(f'{m:-^100}') + + for k in self.OS_info: + print(f'\t {k}: {self.OS_info[k]}') + print() -class TimerProcess(multiprocessing.Process): + self.lib_dir = os.path.dirname(os.path.abspath(__file__)) + self.xml_path = os.path.join(self.lib_dir, r'rtest.xml') - def __init__(self, start, stop, kill_pid): - multiprocessing.Process.__init__(self) - self.quit = multiprocessing.Event() - self.timed_out = multiprocessing.Event() - self.start_time = start - self.max_time = stop - self.kill_pid = kill_pid + if self.args.test_dir: + if not os.path.isdir(self.args.test_dir): + raise ValueError(f'Value {self.args.test_dir} is not a directory!') - def run(self): - while not self.quit.is_set(): - #print( f'time_stop {self.start_time} limit {self.max_time}') - if (self.max_time == 0): - return - t = time.monotonic() - if ( t - self.start_time > self.max_time ): - print( f'killing {self.kill_pid} t {t}') - if os.name == "nt": - cmd = ['TASKKILL', '/F', '/T', '/PID', str(self.kill_pid)] - proc = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr) + self.test_dir = self.args.test_dir + else: + # find the test dir with default install dir + if self.args.install_dir == 'build': + # if its debug mode + if self.args.debug: + self.test_dir = os.path.join(self.lib_dir, f'build/debug/test') + # if its release mode + elif os.path.isdir(os.path.join(self.lib_dir, f'build/release/test')): + self.test_dir = os.path.join(self.lib_dir, f'build/release/test') + elif os.path.isdir(os.path.join(self.lib_dir, f'build/test')): + self.test_dir = os.path.join(self.lib_dir, f'build/test') else: - os.kill(self.kill_pid, signal.SIGKILL) - self.timed_out.set() - self.stop() - pass - - def stop(self): - self.quit.set() - - def stopped(self): - return self.timed_out.is_set() - - -def time_stop(start, pid): - global timeout, stop - while (True): - print( f'time_stop {start} limit {stop}') - t = time.monotonic() - if (stop == 0): - return - if ( (stop > 0) and (t - start > stop) ): - print( f'killing {pid} t {t}') - if os.name == "nt": - cmd = ['TASKKILL', '/F', '/T', '/PID', str(pid)] - proc = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr) + raise FileNotFoundError(f'Did not find test directory') else: - test_proc.kill() - timeout = True - stop = 0 - time.sleep(0) - -def run_cmd(cmd, test = False, time_limit = 0): - global args - global test_proc, timer_thread - global stop - if (cmd.startswith('cd ')): - return os.chdir(cmd[3:]) - if (cmd.startswith('mkdir ')): - return create_dir(cmd[6:]) - cmdline = f"{cmd}" - print(cmdline) - try: - if not test: - proc = subprocess.run(cmdline, check=True, stderr=subprocess.STDOUT, shell=True) - status = proc.returncode + # if its an actual directory AND it has test directory + if os.path.isdir(os.path.join(self.args.install_dir, f'test')): + self.test_dir = os.path.join(self.args.install_dir, f'test') + else: + raise ValueError(f'{self.args.install_dir} is not a valid install directory!') + + if self.args.output: + self.output = open(os.path.abspath(self.args.output), 'w') + self.output_path = os.path.abspath(self.args.output) else: - error = False - timeout = False - test_proc = subprocess.Popen(cmdline, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) - if time_limit > 0: - start = time.monotonic() - #p = multiprocessing.Process(target=time_stop, args=(start, test_proc.pid)) - p = TimerProcess(start, time_limit, test_proc.pid) - p.start() - while True: - output = test_proc.stdout.readline() - if output == '' and test_proc.poll() is not None: - break - elif output: - outstring = output.strip() - print (outstring) - error = error or re.search(r'FAILED', outstring) - status = test_proc.poll() - if time_limit > 0: - p.stop() - p.join() - timeout = p.stopped() - print(f"timeout {timeout}") - if error: - status = 1 - elif timeout: - status = 2 - else: - status = test_proc.returncode - except: - import traceback - exc = traceback.format_exc() - print( "Python Exception: {0}".format(exc) ) - status = 3 - return status - -def batch(script, xml): - global OS_info - global args - # - cwd = pathlib.os.curdir - rtest_cwd_path = os.path.abspath( os.path.join( cwd, 'rtest.xml') ) - - if os.path.isfile(rtest_cwd_path) and os.path.dirname(rtest_cwd_path).endswith( "staging" ): - # if in a staging directory then test locally - test_dir = cwd - else: - # deal with windows pathing - install_dir = '//'.join(args.install_dir.split('\\')) - - if args.debug: - build_type = "debug" - else: - #check if we have a release folder in build - if os.path.isdir(f'{install_dir}//release//test'): - build_type = "release" - else: - build_type = "" + self.output = None + self.output_path = None - if len(build_type) > 0: - test_dir = f"{install_dir}//{build_type}//test" - else: - test_dir = f"{install_dir}//test" - fail = False - for i in range(len(script)): - cmdline = script[i] - xcmd = cmdline.replace('%IDIR%', test_dir) - cmd = xcmd.replace('%ODIR%', args.output) - if cmd.startswith('tdir '): - if pathlib.Path(cmd[5:]).exists(): - return 0 # all further cmds skipped - else: - continue - error = False - if cmd.startswith('%XML%'): - # run the matching tests listed in the xml test file - var_subs = {} - for var in xml.getElementsByTagName('var'): - name = var.getAttribute('name') - val = var.getAttribute('value') - var_subs[name] = val - for test in xml.getElementsByTagName('test'): - sets = test.getAttribute('sets') - runset = sets.split(',') + if self.OS_info['System'] == 'Windows': + self.lib_dir = self.lib_dir.replace('\\', '/') + self.xml_path = self.xml_path.replace('\\', '/') + self.test_dir = self.test_dir.replace('\\', '/') + + if self.output_path: + self.output_path = self.output_path.replace('\\', '/') + self.output = open(os.path.abspath(self.output_path), 'w') + + m = ' Current Paths' + print(f'{m:-^100}') + print(f'Working Directory: {self.lib_dir}') + print(f'rtest.xml: {self.xml_path}') + print(f'Test Directory: {self.test_dir}') + print(f'Output File: {self.output_path}') + + print() - A, B = args.test != '', args.emulation != '' - if not (A ^ B): - raise ValueError('At least one but not both of -e/--emulation or -t/--test must be set') + def __call__(self): + xml_file = minidom.parse(self.xml_path) - if args.test in runset: - for run in test.getElementsByTagName('run'): - name = run.getAttribute('name') - vram_limit = run.getAttribute('vram_min') - if vram_limit: - if OS_info["VRAM"] < float(vram_limit): - print( f'***\n*** Skipped: {name} due to VRAM req.\n***') - continue - if name: - print( f'***\n*** Running: {name}\n***') - time_limit = run.getAttribute('time_max') - if time_limit: - timeout = float(time_limit) - else: - timeout = 0 + curr_dir = os.curdir - raw_cmd = run.firstChild.data - var_cmd = raw_cmd.format_map(var_subs) - error = run_cmd(var_cmd, True, timeout) - if (error == 2): - print( f'***\n*** Timed out when running: {name}\n***') - - if args.emulation in runset: - for run in test.getElementsByTagName('run'): - name = run.getAttribute('name') - vram_limit = run.getAttribute('vram_min') - if vram_limit: - if OS_info["VRAM"] < float(vram_limit): - print( f'***\n*** Skipped: {name} due to VRAM req.\n***') - continue - if name: - print( f'***\n*** Running: {name}\n***') - time_limit = run.getAttribute('time_max') - if time_limit: - timeout = float(time_limit) - else: - timeout = 0 + os.chdir(self.test_dir) - raw_cmd = run.firstChild.data - var_cmd = raw_cmd.format_map(var_subs) - error = run_cmd(var_cmd, True, timeout) - if (error == 2): - print( f'***\n*** Timed out when running: {name}\n***') - else: - error = run_cmd(cmd) - fail = fail or error - if (fail): - if (cmd == "%XML%"): - print(f"FAILED xml test suite!") - else: - print(f"ERROR running: {cmd}") - if (os.curdir != cwd): - os.chdir( cwd ) - return 1 - if (os.curdir != cwd): - os.chdir( cwd ) - - return 0 - -def run_tests(): - global test_script - global xmlDoc + cmd_values = {} + for var in xml_file.getElementsByTagName('var'): + name, val = var.getAttribute('name'), var.getAttribute('value') + cmd_values[name] = val - # install - cwd = os.curdir + noMatch = True + for test in xml_file.getElementsByTagName('test'): + sets = test.getAttribute('sets') + if self.test_choice == sets: - xmlPath = os.path.join( cwd, 'rtest.xml') - xmlDoc = minidom.parse( xmlPath ) + for run in test.getElementsByTagName('run'): + temp = run.firstChild.data + temp = temp.replace('{', '') + temp = temp.replace('}', '') + cmd_list = temp.split() - scripts = [] - scripts.append( test_script ) - for i in scripts: - if (batch(i, xmlDoc)): - #print("Failure in script. ABORTING") - if (os.curdir != cwd): - os.chdir( cwd ) - return 1 - if (os.curdir != cwd): - os.chdir( cwd ) - return 0 + cmd_str = '' + for var in cmd_list: + cmd_str += cmd_values[var] -def main(): - global args - global timer_thread + m = 'Final Command' + print(f'{m:-^100}') + print(cmd_str) + print() - os_detect() - args = parse_args() + subprocess.run(cmd_str, shell=True, stdout=self.output) + noMatch = False + break - status = run_tests() + os.chdir(curr_dir) + if noMatch: + raise ValueError(f'Test value passed in: "{self.test_choice}" does not match any known test suite') - if args.fail_test: - status = 1 - if (status): - sys.exit(status) + if self.args.fail_test: + sys.exit(1) if __name__ == '__main__': - main() \ No newline at end of file + runner = TestRunner() + runner() \ No newline at end of file