diff --git a/tests/xua_unit_tests/conftest.py b/tests/xua_unit_tests/conftest.py index 351a61fb..e2e1a54a 100644 --- a/tests/xua_unit_tests/conftest.py +++ b/tests/xua_unit_tests/conftest.py @@ -1,11 +1,14 @@ # Copyright 2021 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. +from __future__ import print_function +from builtins import str import os.path import pytest import subprocess target = os.environ.get('TARGET', 'all_possible') print("target = ", target) + def pytest_collect_file(parent, path): if(path.ext == ".xe"): if(target == 'all_possible'): @@ -27,24 +30,9 @@ class UnityTestSource(pytest.File): # |-- runners/ <- Auto-generated buildable source of test binaries # |-- src/ <- Unity test functions # `-- wscript <- Build system file used to generate/build runners - print("self.name = ",self.name) - #xe_name = ((os.path.basename(self.name)).split("."))[0] + ".xe" - #test_bin_path = os.path.join('bin', xe_name) - # - #test_root_dir_name = os.path.basename(os.path.dirname(__file__)) - #test_src_path = os.path.basename(str(self.fspath)) - #test_src_name = os.path.splitext(test_src_path)[0] + xe_name = ((os.path.basename(self.name)).split("."))[0] + ".xe" + test_bin_path = os.path.join('bin', xe_name) - #test_bin_name_si = os.path.join( - # test_src_name + '_single_issue.xe') - #test_bin_path_si = os.path.join('bin', - # test_bin_name_si) - #yield UnityTestExecutable.from_parent(self, name=test_bin_path_si) - - #test_bin_name_di = os.path.join( - # test_src_name + '_dual_issue.xe') - #test_bin_path_di = os.path.join('bin', - # test_bin_name_di) yield UnityTestExecutable.from_parent(self, name=self.name) @@ -83,7 +71,7 @@ class UnityTestExecutable(pytest.Item): test_case = test_report[2] result = test_report[3] failure_reason = None - print('\n {}()'.format(test_case)), + print(('\n {}()'.format(test_case)), end=' ') if result == 'PASS': unity_pass = True continue diff --git a/tests/xua_unit_tests/generate_unity_runners.py b/tests/xua_unit_tests/generate_unity_runners.py new file mode 100644 index 00000000..f2fe0f04 --- /dev/null +++ b/tests/xua_unit_tests/generate_unity_runners.py @@ -0,0 +1,120 @@ +# Copyright 2021 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. +import glob +import os.path +import subprocess +import sys + +UNITY_TEST_DIR = 'src' +UNITY_TEST_PREFIX = 'test_' +UNITY_RUNNER_DIR = 'runners' +UNITY_RUNNER_SUFFIX = '_Runner' +project_root = os.path.join('..', '..', '..') + +def get_ruby(): + """ + Check ruby is avaliable and return the command to invoke it. + """ + interpreter_name = 'ruby' + try: + dev_null = open(os.devnull, 'w') + # Call the version command to check the interpreter can be run + subprocess.check_call([interpreter_name, '--version'], + stdout=dev_null, + close_fds=True) + except OSError as e: + print("Failed to run Ruby interpreter: {}".format(e), file=sys.stderr) + exit(1) # TODO: Check this is the correct way to kill xwaf on error + + return interpreter_name + +def get_unity_runner_generator(project_root_path): + """ + Check the Unity generate_test_runner script is avaliable, and return the + path to it. + """ + unity_runner_generator = os.path.join( + project_root_path, 'Unity', 'auto', 'generate_test_runner.rb') + if not os.path.exists(unity_runner_generator): + print("Unity repo not found in workspace", file=sys.stderr) + exit(1) # TODO: Check this is the correct way to kill xwaf on error + return unity_runner_generator + + +def get_test_name(test_path): + """ + Return the test name by removing the extension from the filename. + """ + return os.path.splitext(os.path.basename(test_path))[0] + + +def get_file_type(filename): + """ + Return the extension from the filename. + """ + return filename.rsplit('.')[-1:][0] + + +def generate_unity_runner(project_root_path, unity_test_path, unity_runner_dir, + unity_runner_suffix): + """ + Invoke the Unity runner generation script for the given test file, and + return the path to the generated file. The output directory will be created + if it does not already exist. + """ + runner_path = os.path.join(os.path.join(unity_runner_dir, get_test_name(unity_test_path))) + if not os.path.exists(runner_path): + os.makedirs(runner_path) + + unity_runner_path = os.path.join( + runner_path, get_test_name(unity_test_path) + unity_runner_suffix + + '.' + 'c') + + try: + subprocess.check_call([get_ruby(), + get_unity_runner_generator(project_root_path), + unity_test_path, + unity_runner_path]) + except OSError as e: + print("Ruby generator failed for {}\n\t{}".format(unity_test_path, e), + file=sys.stderr) + exit(1) # TODO: Check this is the correct way to kill xwaf on error + + +def find_unity_test_paths(unity_test_dir, unity_test_prefix): + """ + Return a list of all file paths with the unity_test_prefix found in the + unity_test_dir. + """ + return glob.glob(os.path.join(unity_test_dir, unity_test_prefix+'*')) + + +def find_unity_tests(unity_test_dir, unity_test_prefix): + """ + Return a dictionary of all {test names, test language} pairs with the + unity_test_prefix found in the unity_test_dir. + """ + unity_test_paths = find_unity_test_paths(unity_test_dir, unity_test_prefix) + print('unity_test_paths = ', unity_test_paths) + return {get_test_name(path): get_file_type(path) + for path in unity_test_paths} + +def find_unity_test_paths(unity_test_dir, unity_test_prefix): + """ + Return a list of all file paths with the unity_test_prefix found in the + unity_test_dir. + """ + return glob.glob(os.path.join(unity_test_dir, unity_test_prefix+'*')) + + +def generate_runners(): + UNITY_TESTS = find_unity_tests(UNITY_TEST_DIR, UNITY_TEST_PREFIX) + print('UNITY_TESTS = ',UNITY_TESTS) + unity_test_paths = find_unity_test_paths(UNITY_TEST_DIR, UNITY_TEST_PREFIX) + print('unity_test_paths = ',unity_test_paths) + for unity_test_path in unity_test_paths: + generate_unity_runner(project_root, unity_test_path, UNITY_RUNNER_DIR, UNITY_RUNNER_SUFFIX) + + +if __name__ == "__main__": + generate_runners() diff --git a/tests/xua_unit_tests/wscript b/tests/xua_unit_tests/wscript index 952519a9..fcae029c 100644 --- a/tests/xua_unit_tests/wscript +++ b/tests/xua_unit_tests/wscript @@ -3,11 +3,10 @@ import glob import os.path import subprocess import sys -from waflib import Options, Errors +from waflib import Options from waflib.Build import BuildContext, CleanContext -#TARGETS = ['xcore200', 'xcoreai'] -TARGETS = ['xcore200'] # Target xcore200 only for the time being +TARGETS = ['xcore200', 'xcoreai'] def get_ruby(): """ @@ -68,6 +67,7 @@ def generate_unity_runner(project_root_path, unity_test_path, unity_runner_dir, unity_runner_path = os.path.join( runner_path, get_test_name(unity_test_path) + unity_runner_suffix + '.' + 'c') + try: subprocess.check_call([get_ruby(), get_unity_runner_generator(project_root_path), @@ -79,38 +79,27 @@ def generate_unity_runner(project_root_path, unity_test_path, unity_runner_dir, exit(1) # TODO: Check this is the correct way to kill xwaf on error -def set_common_build_config(waf_conf, project_root_path, unity_test_path, - unity_runner_build_flags): +def add_unity_runner_build_config(waf_conf, project_root_path, unity_test_path, + unity_runner_build_flags, target): """ - Set the common xwaf config variables. + Add a config to xwaf to build each Unity test runner into an xCORE + executable. """ + print(f"get_test_name(unity_test_path) = {get_test_name(unity_test_path)}. target = {target}") + waf_conf.setenv(get_test_name(unity_test_path) + '_' + target) waf_conf.load('xwaf.compiler_xcc') waf_conf.env.XCC_FLAGS = unity_runner_build_flags waf_conf.env.PROJECT_ROOT = project_root_path # TODO: can the xwaf boilerplate help here? -def add_single_issue_unity_runner_build_config(waf_conf, project_root_path, - unity_test_path, - unity_runner_build_flags, target): - """ - Add a single issue config to xwaf to build each Unity test runner into an - xCORE executable. - """ - waf_conf.setenv(get_test_name(unity_test_path) + '_single_issue' + '_' + target) - set_common_build_config(waf_conf, project_root_path, unity_test_path, - unity_runner_build_flags + '-mno-dual-issue') - - def prepare_unity_test_for_build(waf_conf, project_root_path, unity_test_path, unity_runner_dir, unity_runner_suffix, target): generate_unity_runner(project_root_path, unity_test_path, unity_runner_dir, unity_runner_suffix) runner_build_flags = '' # Could extract flags from the test name - add_single_issue_unity_runner_build_config(waf_conf, project_root_path, - unity_test_path, - runner_build_flags, target) - + add_unity_runner_build_config(waf_conf, project_root_path, unity_test_path, + runner_build_flags, target) def find_unity_test_paths(unity_test_dir, unity_test_prefix): @@ -118,12 +107,8 @@ def find_unity_test_paths(unity_test_dir, unity_test_prefix): Return a list of all file paths with the unity_test_prefix found in the unity_test_dir. """ - file_list = [] - for root, dirs, files in os.walk(unity_test_dir): - for f in files: - if f.startswith(unity_test_prefix): - file_list.append(os.path.join(root,f)) - return file_list + return glob.glob(os.path.join(unity_test_dir, unity_test_prefix+'*')) + def find_unity_tests(unity_test_dir, unity_test_prefix): """ @@ -155,17 +140,19 @@ def generate_all_unity_runners(waf_conf, project_root_path, def create_waf_contexts(configs): for trgt in TARGETS: for test_name, test_language in configs.items(): - # Single issue test configurations + print(f"test_name {test_name}, test_language {test_language}") for ctx in (BuildContext, CleanContext): raw_context = ctx.__name__.replace('Context', '').lower() - class si_tmp(ctx): - cmd = raw_context + '_' + test_name + '_single_issue' + '_' + trgt - variant = test_name + '_single_issue' + '_' + trgt - source = test_name + class tmp(ctx): + cmd = raw_context + '_' + test_name + '_' + trgt + variant = test_name + '_' + trgt + #cmd = raw_context + '_' + test_name + #variant = test_name language = test_language target = trgt runner = test_name + print(f"cmd {cmd}, variant {variant}, language {language}") UNITY_TEST_DIR = 'src' @@ -177,8 +164,8 @@ UNITY_TESTS = find_unity_tests(UNITY_TEST_DIR, UNITY_TEST_PREFIX) create_waf_contexts(UNITY_TESTS) def options(opt): - opt.load('xwaf.xcommon') opt.add_option('--target', action='store', default='xcore200') + opt.load('xwaf.xcommon') def configure(conf): # TODO: move the call to generate_all_unity_runners() to build() @@ -191,6 +178,7 @@ def configure(conf): def build(bld): if not bld.variant: + print('Adding test runners to build queue') trgt = [ c for c in TARGETS if c == bld.options.target ] @@ -198,34 +186,25 @@ def build(bld): if len(trgt) == 0: bld.fatal('specify a target with --target.\nAvailable targets: {}'.format(', '.join(TARGETS))) return - print('Adding test runners to build queue') + for name in UNITY_TESTS: - Options.commands.insert(0, 'build_' + name + '_single_issue' + '_' + trgt[0]) + Options.commands.insert(0, 'build_' + name + '_' + trgt[0]) + #Options.commands.insert(0, 'build_' + name) print('Build queue {}'.format(Options.commands)) else: print('Building runner {}'.format(bld.runner)) - if(bld.target == 'xcoreai'): - bld.env.TARGET_ARCH = 'XCORE-AI-EXPLORER' - else: - bld.env.TARGET_ARCH = 'XCORE-200-EXPLORER' - bld.env.XSCOPE = bld.path.find_resource('config.xscope') - # The issue mode for each build is set during the configure step, - # as the string bld.env.XCC_FLAGS. We append this to the list last to - # ensure it takes precedence over other flags set here. - bld.env.XCC_FLAGS = ['-O2', '-g', '-Wall', '-DUNITY_SUPPORT_64', - '-DUNITY_INCLUDE_DOUBLE', bld.env.XCC_FLAGS] - depends_on = ['lib_xud', 'lib_xua', 'Unity'] - include = ['.'] - source = [ - os.path.join(UNITY_TEST_DIR, - '{}.{}'.format(bld.source, bld.language)), - os.path.join(UNITY_RUNNER_DIR, - '{}{}.{}'.format(bld.source, UNITY_RUNNER_SUFFIX, - 'c'))] + depends_on = ['lib_xua', + 'lib_xud', + 'lib_spdif', + 'lib_mic_array', + 'lib_logging', + 'lib_xassert', + 'Unity'] + makefile_opts = {} - makefile_opts['SOURCE_DIRS'] = [os.path.join('src',bld.runner), os.path.join('runners',bld.runner)] + makefile_opts['SOURCE_DIRS'] = ['src', os.path.join('runners',bld.runner)] if(bld.target == 'xcoreai'): print('TARGET XCOREAI') makefile_opts['TARGET'] = ['XCORE-AI-EXPLORER'] @@ -233,13 +212,29 @@ def build(bld): print('TARGET XCORE200') makefile_opts['TARGET'] = ['XCORE-200-EXPLORER'] - makefile_opts['INCLUDE_DIRS'] = ['src'] - makefile_opts['XCC_FLAGS'] = ['-O2', '-g', '-Wall', '-DUNITY_SUPPORT_64', '-DUNITY_INCLUDE_DOUBLE'] + makefile_opts['INCLUDE_DIRS'] = ['src', + '../../lib_xua/src/core/pdm_mics', + '../../lib_xua/api', + '../../../lib_xud/lib_xud/src/user/class'] + + makefile_opts['XCC_FLAGS'] = ['-O2', '-g', '-Wall', '-DUNITY_SUPPORT_64', '-DUNITY_INCLUDE_DOUBLE', '-DXUD_CORE_CLOCK=600'] makefile_opts['APP_NAME'] = [bld.variant] makefile_opts['USED_MODULES'] = depends_on makefile_opts['XCOMMON_MAKEFILE'] = ['Makefile.common'] bld.do_xcommon(makefile_opts) + +def test(bld): + # Call pytest to run Unity tests inside axe or xsim + try: + test_output = subprocess.check_output(['pytest']) + except subprocess.CalledProcessError as e: + # pytest exits non-zero if an assertion fails + test_output = e.output + print(test_output) + + +# TODO: ensure clean deletes the runners dir/ def dist(ctx): ctx.load('xwaf.xcommon')