#! /usr/bin/env python3 # vim: set tabstop=4 shiftwidth=4 expandtab set textwidth=79 # ===========================================================================79 # Filename: run_tests.py # # Description: Converting run_tests.sh to python # # Based on run_test.sh # # Same as run_test.sh: # 1. output into .xml file is the same # 2. colorization of output # # Differences with run_test.sh # 1. Does not depend upon .elf file extension for filetype. # Uses the unix 'file' command to get filetype # 2. Added usage function # 3. Added run switches (which didn't exist in run_test.sh) # See the print_usage() function for a descriptionm of # the supported switches. # 4. Default search directories, rather than just one. # 5. Removed use of $RISCV env var. # 6. requires python3 # 7. Added support to ignore tests via the --test_ignore_pyfile # switch # 8. Added support to supply switches to the riscv_sim command # via the --test_switch_pyfile. # # Author(s): Bill McSpadden (bill@riscv.org) # # History: See revision control log # ===========================================================================79 # ===========================================================================79 # Necessary imports for this script: import os # needed for os command interactions import glob # needed for file list gathering useing wildcards import re # regular expression import sys # needed for command line arguments import getopt # needed for command line arguments import collections # needed for dequeues import subprocess # needed for subprocesses where stdout is needed from pathlib import Path from inspect import currentframe, getframeinfo from abc import ABC, abstractmethod from copy import deepcopy # Necessary imports for this script # ===========================================================================79 # ===========================================================================79 # General data for use in this script. # ===========================================================================79 # Data structure for sim command line arguments # TODO: make the key a regex sim_test_command_line_switch_dict = { } my_ignore_test_tuple = [] # Allowed command line options opts, args = getopt.getopt ( sys.argv[1:], "dhuo:", [ "help", "usage", "outfile=", "test_dir=", "32bit=", "64bit=", "c_sim=", "ocaml_sim=", "sailcov=", "clean_build=", "test_dir=", "test_switch_pyfile=", "test_ignore_pyfile=", "debug" ] ) # Variables to be overridden with command line switches xml_outfile = "./tests.xml" run_32bit_tests = True run_64bit_tests = True run_csim = True run_ocamlsim = False sailcov = False clean_build = True test_dir_list = [ "isa", "riscv-tests" ] sail_riscv_rootdir = 'SAIL_RISCV_ROOTDIR' test_switch_pyfile = '' test_ignore_pyfile = '' debug = False # Variables for tracking test status and test output RED = '\033[0;91m' GREEN = '\033[0;92m' YELLOW = '\033[0;93m' NC = '\033[0m' test_pass = 0 test_fail = 0 all_pass = 0 all_fail = 0 SUITE_XML = "" SUITES_XML = "" # ===========================================================================79 # ===========================================================================79 # Function prototypes: # ==================================== # Print Levels: # print() Normal python print function. Goes to stdout # debug_print() Print debug information. Goes to stdout. # error_print() Print error message. Goes to stdout. Generally most errors should be fatal errors # fatal_print() Print fatal error message. Exit with status 1. Generally most errors should be fatal errors. Goes to stdout. # TRACE() For bringup debug only. TRACE() instances should be removed. def debug_print (text = "") : if debug : cf = currentframe() of = cf.f_back fi = getframeinfo(of) filename = os.path.basename(fi.filename) print("debug: file: " + filename + " line: " + str(of.f_lineno) + " : " + text) return def error_print (text = "") : cf = currentframe() of = cf.f_back fi = getframeinfo(of) filename = os.path.basename(fi.filename) print("error: file: " + filename + " line: " + str(of.f_lineno) + " : " + text) return def fatal_print (text = "") : cf = currentframe() of = cf.f_back fi = getframeinfo(of) filename = os.path.basename(fi.filename) print("fatal error: file: " + filename + " line: " + str(of.f_lineno) + " : " + text) sys.exit(1) return # never taken def TRACE(text = "") : cf = currentframe() of = cf.f_back fi = getframeinfo(of) filename = os.path.basename(fi.filename) print("TRACE: file: " + filename + " line: " + str(of.f_lineno) + " : " + text) return # Print Levels: # ==================================== # ==================================== # Support for command line options def print_usage(invocation) : print(invocation + " usage: " + invocation + " []") print(" Typically, invoke this script in the directory above where the elf-file tests live.") print(" The script looks into test_dir, finds all of the elf files and then runs the simulator") print(" with each elf file.") print("") print(" Output logs are put into [dir]/.cout (for C sim) or [dir/].out (for ocaml sim).") print("") print(" Some tests require specific command line switches to properly run. To add these") print(" command line switches, you must use the '--test_switch_pyfile=' switch.") print("") print(" options:") print(" -h --help -u -usage print out help/usage message") print(" -o/--outfile= name of xml tests results file to be generated. default: ./tests.xml ") print(" --32bit=[yes|y|no|n] run 32-bit tests. default: yes") print(" --64bit=[yes|y|no|n] run 64-bit tests. default: yes") print(" --c_sim=[yes|y|no|n] run the C simulator. default: yes") print(" --ocaml_sim=[yes|y|no|n] run the Ocaml simulator. default: no") print(" --sailcov=[yes|y|no|n] compile and run to get Sail model coverage. default: no. ") print(" NOTE: sets 'clean_build' to yes. Coverage is gathered seperately for ") print(" 32 and 64 bit models") print(" --clean_build=[yes|y|no|n] do a 'make clean' before running 32/64/c_sim/ocaml_sim set of tests. default: yes") print(" --test_dir= directory where test elf files live. default: ./isa ./riscv-tests") print(" --test_switch_pyfile= a python fragment file that allows the user to pass in command line switches to the") print(" riscv_sim command on a per-test basis. The format of the file should be:") print(" sim_test_command_line_switch_dict = {") print(" \"\" : \" [ ...] \",") print(" \"\" : \" [ ...] \",") print(" }") print(" --test_ignore_pyfile= contains a tuple (immutable list) of tests to be ignored. The format of the file should be:") print(" ignore_test_tuple = [") print(" \"\",") print(" \"\",") print(" ]") print(" -d,--debug turn on debug output") def process_command_line_args(opts) : global xml_outfile global run_32bit_tests global run_32bit_tests global run_64bit_tests global run_csim global run_ocamlsim global sailcov global clean_build global test_dir_list global test_switch_pyfile global test_ignore_pyfile global debug for opt, arg in opts : if opt in ('-h', '--help', '-u', '--usage') : print_usage(sys.argv[0]) sys.exit(0) elif opt in ('-o', "--outfile") : xml_outfile = arg elif opt in ('--32bit') : if arg in ('yes', 'y') : run_32bit_tests = True elif arg in ('no', 'n') : run_32bit_tests = False else : fatal_print("invalid argument to '--32bit' switch: " + arg) elif opt in ('--64bit') : if arg in ('yes', 'y') : run_64bit_tests = True elif arg in ('no', 'n') : run_64bit_tests = False else : fatal_print("invalid argument to '--64bit' switch: " + arg) elif opt in ('--c_sim') : if arg in ('yes', 'y') : run_csim = True elif arg in ('no', 'n') : run_csim = False else : fatal_print("invalid argument to '--run_csim' switch: " + arg) elif opt in ('--ocaml_sim') : if arg in ('yes', 'y') : run_ocamlsim = True elif arg in ('no', 'n') : run_ocamlsim = False else : fatal_print("invalid argument to '--run_ocamlsim' switch: " + arg) elif opt in ('--sailcov') : if arg in ('yes', 'y') : sailcov = True elif arg in ('no', 'n') : sailcov = False else : fatal_print("invalid argument to '--sailcov' switch: " + arg) sys.exit(1) elif opt in ('--clean_build') : if arg in ('yes', 'y') : clean_build = True elif arg in ('no', 'n') : clean_build = False else : fatal_print("invalid argument to '--run_ocamlsim' switch: " + arg) sys.exit(1) elif opt in ('--test_dir') : if not os.path.exists(arg) : fatal_print("test_dir path, '" + arg + "', does not exist") test_dir_list = [] test_dir_list.append(arg) elif opt in ('--test_switch_pyfile') : if not os.path.isfile(arg) : fatal_print("--test_switch_pyfile argument error. file, '" + arg + "', does not exist") test_switch_pyfile = arg elif opt in ('--test_ignore_pyfile') : if not os.path.isfile(arg) : fatal_print("--test_ignore_pyfile argument error. file, '" + arg + "', does not exist") sys.exit(1) test_ignore_pyfile = arg elif opt in ('-d', '--debug') : debug = True else : fatal_print("unexpected command line option: " + opt) # print_optional_settings AFTER the inmplicit overrides have happened def print_optional_settings() : global xml_outfile global run_32bit_tests global run_32bit_tests global run_64bit_tests global run_csim global run_ocamlsim global sailcov global clean_build global test_dir_list global test_switch_pyfile global test_ignore_pyfile global debug print('================================================================') print('Run time variable settings: ') print(' {:32}'.format('debug: ') + str(debug)) print(' {:32}'.format('outfile: ') + xml_outfile) print(' {:32}'.format('run_32bit_tests: ') + str(run_32bit_tests)) print(' {:32}'.format('run_64bit_tests: ') + str(run_64bit_tests)) print(' {:32}'.format('run_csim: ') + str(run_csim)) print(' {:32}'.format('run_ocamlsim: ') + str(run_ocamlsim)) print(' {:32}'.format('sailcov: ') + str(sailcov)) print(' {:32}'.format('clean_build: ') + str(clean_build)) print(' {:32}'.format('test_dir_list: ') + str(test_dir_list)) print(' {:32}'.format('test_ignore_pyfile: ') + test_ignore_pyfile) print(' {:32}'.format('ignore_test: ') + str(my_ignore_test_tuple)) print(' {:32}'.format('test_switch_pyfile: ') + test_switch_pyfile) print(' {:32}'.format('sim_test_comand_line_switch: ') + str(sim_test_command_line_switch)) print('================================================================') # Support for command line options # ==================================== # ==================================== # Functions from run_tests.sh def green(test_str, ok_fail_str) : global test_pass global SUITE_XML global GREEN global NC test_pass += 1 print(test_str + ':' + GREEN + ok_fail_str + NC) SUITE_XML += ' \n' def yellow(test_str, ok_fail_str) : global test_fail global SUITE_XML global YELLOW global NC test_fail += 1 print(test_str + ':' + YELLOW + ok_fail_str + NC) SUITE_XML += ' \n ' + ok_fail_str + '\n \n' def red(test_str, ok_fail_str) : global test_fail global SUITE_XML global RED global NC test_fail += 1 print(test_str + ':' + RED + ok_fail_str + NC) SUITE_XML += ' \n ' + ok_fail_str + '\n \n' def finish_suite(suite_name) : global test_pass global test_fail global all_pass global all_fail global SUITE_XML global SUITES_XML print(suite_name + ': Passed ' + str(test_pass) + ' out of ' + str(test_pass + test_fail) + '\n\n') date_tmp = subprocess.check_output("date", shell=True, text=True) date = date_tmp.rstrip() SUITES_XML += ' \n' + SUITE_XML + ' \n' SUITE_XML="" all_pass += test_pass all_fail += test_fail test_pass = 0 test_fail = 0 # Functions from run_tests.sh # ==================================== # ==================================== # Functions for determining file types # # TODO: there MUST be an equivalent to the 'file' command in python. # Replace the 'file' command with a python equivalent. def is_elf(filename) : cmd = "file -b " + filename + " | awk 'BEGIN { FS = \",\" } ; { print $1 } ' | grep -q \"ELF\" " ret = os.system(cmd) if ret == 0 : return 1 else : return 0 def is_32bit(filename) : cmd = "file -b " + filename + " | awk 'BEGIN { FS = \",\" } ; { print $1 } ' | grep -q \"32-bit\" " ret = os.system(cmd) if ret == 0 : return 1 else : return 0 def is_64bit(filename) : cmd = "file -b " + filename + " | awk 'BEGIN { FS = \",\" } ; { print $1 } ' | grep -q \"64-bit\" " ret = os.system(cmd) if ret == 0 : return 1 else : return 0 def is_riscv(filename) : cmd = "file -b " + filename + " | awk 'BEGIN { FS = \",\" } ; { print $2 } ' | grep -q \"RISC-V\" " ret = os.system(cmd) if ret == 0 : return 1 else : return 0 def is_riscv_elf(filename) : return is_elf(filename) and is_riscv(filename) def is_riscv_elf_32(filename) : return is_riscv_elf(filename) and is_32bit(filename) def is_riscv_elf_64(filename) : return is_riscv_elf(filename) and is_64bit(filename) def ignore_test(testname) : for t in my_ignore_test_tuple : debug_print("ignore testname: " + os.path.basename(testname) + " t: " + t) if t == os.path.basename(testname) : return True else : continue return False # Functions for determining file types # ==================================== # Function prototypes # ===========================================================================79 # ===========================================================================79 # Start of execution.... debug_print("starting...") debug_print("abspath to this script: " + os.path.abspath(sys.argv[0])) debug_print("opts: " + str(opts)) process_command_line_args(opts) # ==================================== # Implicit overrides of program varaibles if sailcov : clean_build = True if test_switch_pyfile : exec(open(test_switch_pyfile).read()) # check to see if variable set if 'sim_test_command_line_switch_dict' in locals() : sim_test_command_line_switch = sim_test_command_line_switch_dict; else : fatal_print("the python variable, sim_test_command_line_switch_dict, is not properly set in " + test_switch_pyfile) sys.exit(1) if test_ignore_pyfile : exec(open(test_ignore_pyfile).read()) if 'ignore_test_tuple' in locals() : my_ignore_test_tuple = ignore_test_tuple else : fatal_print("the python variable, ignore_test_tuple, is not properly set in " + test_ignore_pyfile) # Debug print out of important program variables if debug : print_optional_settings() # TODO: check that only 1 dir in test_dir_list exists for d in test_dir_list : if os.path.exists(d) : TESTDIR = d else : pass debug_print('TESTDIR : ' + TESTDIR) # DIR points to the invocation directory. DIR = os.getcwd() SEARCH_DIR = DIR while SEARCH_DIR != '/' : if os.path.isfile(SEARCH_DIR + '/' + sail_riscv_rootdir) : RISCVDIR = SEARCH_DIR break if SEARCH_DIR == '/' : fatal_print("can't find root directory of repository") SEARCH_DIR = os.path.dirname(SEARCH_DIR) debug_print("RISCVDIR: " + RISCVDIR) if sailcov : MAKE_SAILCOV = "SAILCOV=true" else : MAKE_SAILCOV = "" if os.path.isfile(DIR + xml_outfile) != False : os.remove(DIR + xml_outfile) # TODO: Do you really want to run the tests from the RISCVDIR? # TODO: check for success/failure of chdir os.chdir(RISCVDIR) debug_print("DIR + '/' + TESTDIR + '/' + * :" + DIR + '/' + TESTDIR + '/' + "*") # Do 'make clean' to avoid cross-arch pollution. if clean_build : cmd = "make ARCH=RV32 clean" ret_val = os.system(cmd) if ret_val != 0 : fatal_print("non-zero exit value from command: '" + cmd + "'") else : pass else : pass if run_ocamlsim : if run_32bit_tests : print("Building 32-bit RISCV specification...") cmd = "make ARCH=RV32 ocaml_emulator/riscv_ocaml_sim_RV32" ret_val = os.system(cmd) if ret_val == 0 : green("Building 32-bit RISCV OCaml emulator", "ok") else : debug_print("non-zero exit value from command: '" + cmd + "'") red("Building 32-bit RISCV OCaml emulator","fail") if run_32bit_tests and run_ocamlsim : for test in glob.glob(DIR + '/' + TESTDIR + '/' + "*") : debug_print("test: " + test) if not is_riscv_elf_32(test) : continue if ignore_test(test) : debug_print("ignoring test: " + test) continue # skip F/D tests on OCaml for now pat = re.compile('(rv32ud)') mo = pat.search(test) if mo != None : continue pat = re.compile('(rv32uf)') mo = pat.search(test) if mo != None : continue outfile = test + ".out" sim_switch = "" for key in sim_test_command_line_switch : pat = re.compile(key) mo = pat.search(test) if mo != None: sim_switch = sim_test_command_line_switch[key] break cmd = "timeout 5 " + RISCVDIR + "/ocaml_emulator/riscv_ocaml_sim_RV32" + " " + sim_switch + " " + test + " > " + outfile + " 2>&1 && grep -q SUCCESS " + outfile ret_val = os.system(cmd) if ret_val == 0 : green("OCaml-32 " + os.path.basename(test), "ok") else : red("OCaml-32 " + os.path.basename(test), "fail") else : pass finish_suite("32-bit RISCV OCaml-simulator tests") if clean_build : cmd = "make ARCH=RV32 clean" ret_val = os.system(cmd) if ret_val != 0 : fatal_print("non-zero exit value from command: '" + cmd + "'") sys.exit(1) else : pass else : pass print("Building 32-bit RISCV specification...") if run_csim : if run_32bit_tests : cmd = "make ARCH=RV32 " + MAKE_SAILCOV + " c_emulator/riscv_sim_RV32" ret_val = os.system(cmd) if ret_val == 0 : green("Building 32-bit RISCV C emulator", "ok") else : red("Building 32-bit RISCV C emulator","fail") error_print("non-zero exit value from command: '" + cmd + "'") if run_32bit_tests and run_csim : for test in glob.glob(DIR + '/' + TESTDIR + '/' + "*") : if not is_riscv_elf_32(test) : continue if ignore_test(test) : debug_print("ignoring test: " + test) continue outfile = test + ".cout" sim_switch = "" for key in sim_test_command_line_switch : pat = re.compile(key) mo = pat.search(test) if mo != None: sim_switch = sim_test_command_line_switch[key] break if sailcov : run_sailcov = " --sailcov-file sailcov_RV32" else : run_sailcov = "" cmd = "timeout 5 " + RISCVDIR + "/c_emulator/riscv_sim_RV32" + run_sailcov + " " + sim_switch + " " + test + " > " + outfile + " 2>&1 && grep -q SUCCESS " + outfile debug_print("cmd: '" + cmd + "'") ret_val = os.system(cmd) if ret_val == 0 : green("C-32 " + os.path.basename(test), "ok") else : red("C-32 " + os.path.basename(test), "fail") else : pass finish_suite("32-bit RISCV C-simulator tests") if clean_build : cmd = "make ARCH=RV64 clean" ret_val = os.system(cmd) if ret_val != 0 : fatal_print("non-zero exit value from command: '" + cmd + "'") else : pass else : pass print("Building 64-bit RISCV specification...") if run_ocamlsim : if run_64-bit_tests : cmd = "make ARCH=RV64 ocaml_emulator/riscv_ocaml_sim_RV64" ret_val = os.system(cmd) if ret_val == 0 : green("Building 64-bit RISCV OCaml emulator", "ok") else : error_print("non-zero exit value from command: '" + cmd + "'") red("Building 64-bit RISCV OCaml emulator","fail") if run_64bit_tests and run_ocamlsim : for test in glob.glob(DIR + '/' + TESTDIR + '/' + "*") : debug_print("test: " + test) if not is_riscv_elf_64(test) : continue if ignore_test(test) : debug_print("ignoring test: " + test) continue # skip F/D tests on OCaml for now pat = re.compile('(rv64ud)') mo = pat.search(test) if mo != None : continue pat = re.compile('(rv64uf)') mo = pat.search(test) if mo != None : continue outfile = test + ".out" sim_switch = "" for key in sim_test_command_line_switch : pat = re.compile(key) mo = pat.search(test) if mo != None: sim_switch = sim_test_command_line_switch[key] break cmd = "timeout 5 " + RISCVDIR + "/ocaml_emulator/riscv_ocaml_sim_RV64" + " " + sim_switch + " " + test + " > " + outfile + " 2>&1 && grep -q SUCCESS " + outfile ret_val = os.system(cmd) if ret_val == 0 : green("OCaml-64 " + os.path.basename(test), "ok") else : red("OCaml-64 " + os.path.basename(test), "fail") else : pass finish_suite("64-bit RISCV OCaml-simulator tests") if clean_build : cmd = "make ARCH=RV64 clean" ret_val = os.system(cmd) if ret_val != 0 : fatal_print("non-zero exit value from command: '" + cmd + "'") else : pass else : pass print("Building 64-bit RISCV specification...") if run_csim : if run_64bit_tests : cmd = "make ARCH=RV64 " + MAKE_SAILCOV + " c_emulator/riscv_sim_RV64" ret_val = os.system(cmd) if ret_val == 0 : green("Building 64-bit RISCV C emulator", "ok") else : red("Building 64-bit RISCV C emulator","fail") error_print("non-zero exit value from command: '" + cmd + "'") if run_64bit_tests and run_csim : for test in glob.glob(DIR + '/' + TESTDIR + '/' + "*") : debug_print("test: " + test) if not is_riscv_elf_64(test) : continue if ignore_test(test) : debug_print("ignoring test: " + test) continue outfile = test + ".cout" sim_switch = "" for key in sim_test_command_line_switch : pat = re.compile(key) mo = pat.search(test) if mo != None: sim_switch = sim_test_command_line_switch[key] break if sailcov : run_sailcov = " --sailcov-file sailcov_RV64" else : run_sailcov = "" cmd = "timeout 5 " + RISCVDIR + "/c_emulator/riscv_sim_RV64" + run_sailcov + " " + sim_switch + " " + test + " > " + outfile + " 2>&1 && grep -q SUCCESS " + outfile ret_val = os.system(cmd) if ret_val == 0 : green("C-64 " + os.path.basename(test), "ok") else : red("C-64 " + os.path.basename(test), "fail") else : pass finish_suite("64-bit RISCV C-simulator tests") print('Passed ' + str(all_pass) + ' out of ' + str(all_pass + all_fail) + '\n\n') XML = '\n' + SUITES_XML + '\n' xml_outfile_fh = open(DIR + '/' + xml_outfile, 'w') print(XML, file = xml_outfile_fh) xml_outfile_fh.close() if all_fail > 0 : sys.exit(1) else : sys.exit(0)