diff --git a/Jenkinsfile b/Jenkinsfile
index ef501285..3ba5bc40 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -27,7 +27,7 @@ pipeline {
xcoreLibraryChecks("${REPO}")
}
}
- stage('Tests') {
+ stage('XS2 Tests') {
failFast true
parallel {
stage('Legacy tests') {
@@ -35,6 +35,57 @@ pipeline {
runXmostest("${REPO}", 'legacy_tests')
}
}
+ stage('Unit tests') {
+ steps {
+ dir("${REPO}") {
+ dir('tests') {
+ dir('xua_unit_tests') {
+ viewEnv() {
+ withVenv {
+ runWaf('.', "configure clean build --target=xcore200")
+ runWaf('.', "configure clean build --target=xcoreai")
+ stash name: 'xua_unit_tests', includes: 'bin/*xcoreai.xe, '
+ runPython("TARGET=XCORE200 pytest -n 1")
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ stage('xcore.ai Verification') {
+ agent {
+ label 'xcore.ai-explorer'
+ }
+ stages{
+ stage('Get View') {
+ steps {
+ xcorePrepareSandbox("${VIEW}", "${REPO}")
+ }
+ }
+ stage('Unit tests') {
+ steps {
+ dir("${REPO}") {
+ dir('tests') {
+ dir('xua_unit_tests') {
+ viewEnv() {
+ withVenv {
+ unstash 'xua_unit_tests'
+ runPython("TARGET=XCOREAI pytest -n 1 --junitxml pytest_result.xml")
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } // stages
+ post {
+ cleanup {
+ cleanWs()
+ }
}
}
stage('xCORE builds') {
diff --git a/tests/xua_unit_tests/config.xscope b/tests/xua_unit_tests/config.xscope
new file mode 100644
index 00000000..bfdf1f86
--- /dev/null
+++ b/tests/xua_unit_tests/config.xscope
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/xua_unit_tests/conftest.py b/tests/xua_unit_tests/conftest.py
new file mode 100644
index 00000000..351a61fb
--- /dev/null
+++ b/tests/xua_unit_tests/conftest.py
@@ -0,0 +1,127 @@
+# Copyright 2021 XMOS LIMITED.
+# This Software is subject to the terms of the XMOS Public Licence: Version 1.
+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'):
+ return UnityTestSource.from_parent(parent, fspath=path)
+ if(target == 'XCOREAI' and ('xcoreai' in path.basename)):
+ return UnityTestSource.from_parent(parent, fspath=path)
+ if(target == 'XCORE200' and ('xcore200' in path.basename)):
+ return UnityTestSource.from_parent(parent, fspath=path)
+
+
+class UnityTestSource(pytest.File):
+ def collect(self):
+ # Find the binary built from the runner for this test file
+ #
+ # Assume the following directory layout:
+ # unit_tests/ <- Test root directory
+ # |-- bin/ <- Compiled binaries of the test runners
+ # |-- conftest.py <- This 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]
+
+ #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)
+
+
+class UnityTestExecutable(pytest.Item):
+ def __init__(self, name, parent):
+ super(UnityTestExecutable, self).__init__(name, parent)
+ self._nodeid = self.name # Override the naming to suit C better
+
+ def runtest(self):
+ # Run the binary in the simulator
+ simulator_fail = False
+ test_output = None
+ try:
+ if('xcore200' in self.name):
+ print("run axe for executable ", self.name)
+ test_output = subprocess.check_output(['axe', self.name], text=True)
+ else:
+ print("run xrun for executable ", self.name)
+ test_output = subprocess.check_output(['xrun', '--io', '--id', '0', self.name], text=True, stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as e:
+ # Unity exits non-zero if an assertion fails
+ simulator_fail = True
+ test_output = e.output
+
+ # Parse the Unity output
+ unity_pass = False
+ test_output = test_output.split('\n')
+ for line in test_output:
+ if 'test' in line:
+ test_report = line.split(':')
+ # Unity output is as follows:
+ # :::PASS
+ # :::FAIL:
+ test_source = test_report[0]
+ line_number = test_report[1]
+ test_case = test_report[2]
+ result = test_report[3]
+ failure_reason = None
+ print('\n {}()'.format(test_case)),
+ if result == 'PASS':
+ unity_pass = True
+ continue
+ if result == 'FAIL':
+ failure_reason = test_report[4]
+ print('') # Insert line break after test_case print
+ raise UnityTestException(self, {'test_source': test_source,
+ 'line_number': line_number,
+ 'test_case': test_case,
+ 'failure_reason':
+ failure_reason})
+
+ if simulator_fail:
+ raise Exception(self, "Simulation failed.")
+ if not unity_pass:
+ raise Exception(self, "Unity test output not found.")
+ print('') # Insert line break after final test_case which passed
+
+ def repr_failure(self, excinfo):
+ if isinstance(excinfo.value, UnityTestException):
+ return '\n'.join([str(self.parent).strip('<>'),
+ '{}:{}:{}()'.format(
+ excinfo.value[1]['test_source'],
+ excinfo.value[1]['line_number'],
+ excinfo.value[1]['test_case']),
+ 'Failure reason:',
+ excinfo.value[1]['failure_reason']])
+ else:
+ return str(excinfo.value)
+
+ def reportinfo(self):
+ # It's not possible to give sensible line number info for an executable
+ # so we return it as 0.
+ #
+ # The source line number will instead be recovered from the Unity print
+ # statements.
+ return self.fspath, 0, self.name
+
+
+class UnityTestException(Exception):
+ pass
diff --git a/tests/xua_unit_tests/src/test_hid/test_hid.xc b/tests/xua_unit_tests/src/test_hid/test_hid.xc
new file mode 100644
index 00000000..d92a7efd
--- /dev/null
+++ b/tests/xua_unit_tests/src/test_hid/test_hid.xc
@@ -0,0 +1,7 @@
+// Copyright 2021 XMOS LIMITED.
+// This Software is subject to the terms of the XMOS Public Licence: Version 1.
+#include "xua_unit_tests.h"
+
+void test_null(){
+ TEST_ASSERT_MESSAGE(1, "Success!");
+}
diff --git a/tests/xua_unit_tests/src/xua_unit_tests.h b/tests/xua_unit_tests/src/xua_unit_tests.h
new file mode 100644
index 00000000..bb7275b3
--- /dev/null
+++ b/tests/xua_unit_tests/src/xua_unit_tests.h
@@ -0,0 +1,25 @@
+// Copyright 2018-2021 XMOS LIMITED.
+// This Software is subject to the terms of the XMOS Public Licence: Version 1.
+#ifndef VTB_UNIT_TESTS_H_
+#define VTB_UNIT_TESTS_H_
+
+#include "unity.h"
+
+#ifdef __XC__
+
+#include
+#include
+#include
+
+#include
+
+#include "audio_test_tools.h"
+#include "voice_toolbox.h"
+#include "voice_toolbox_fp.h"
+#include "vtb_references.h"
+
+#define TEST_ASM 1
+
+#endif // __XC__
+
+#endif /* VTB_UNIT_TESTS_H_ */
diff --git a/tests/xua_unit_tests/wscript b/tests/xua_unit_tests/wscript
new file mode 100644
index 00000000..961aa216
--- /dev/null
+++ b/tests/xua_unit_tests/wscript
@@ -0,0 +1,274 @@
+from __future__ import print_function
+import glob
+import os.path
+import subprocess
+import sys
+from waflib import Options, Errors
+from waflib.Build import BuildContext, CleanContext
+
+TARGETS = ['xcore200', 'xcoreai']
+
+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 set_common_build_config(waf_conf, project_root_path, unity_test_path,
+ unity_runner_build_flags):
+ """
+ Set the common xwaf config variables.
+ """
+ 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 add_dual_issue_unity_runner_build_config(waf_conf, project_root_path,
+ unity_test_path,
+ unity_runner_build_flags, target):
+ """
+ Add a dual issue config to xwaf to build each Unity test runner into an
+ xCORE executable.
+ """
+ waf_conf.setenv(get_test_name(unity_test_path) + '_dual_issue' + '_' + target)
+ set_common_build_config(waf_conf, project_root_path, unity_test_path,
+ unity_runner_build_flags + '-mdual-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_dual_issue_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):
+ """
+ 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
+
+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)
+ return {get_test_name(path): get_file_type(path)
+ for path in unity_test_paths}
+
+
+def generate_all_unity_runners(waf_conf, project_root_path,
+ unity_test_dir, unity_test_prefix,
+ unity_runner_dir, unity_runner_suffix):
+ """
+ Generate a runner and a build config for each test file in the
+ unity_test_dir.
+ """
+ # FIXME: pass unity_tests in?
+ unity_test_paths = find_unity_test_paths(unity_test_dir, unity_test_prefix)
+ for trgt in TARGETS:
+ for unity_test_path in unity_test_paths:
+ prepare_unity_test_for_build(waf_conf, project_root_path,
+ unity_test_path,
+ unity_runner_dir, unity_runner_suffix, trgt)
+
+
+# TODO: can the xwaf boilerplate help here?
+def create_waf_contexts(configs):
+ for trgt in TARGETS:
+ for test_name, test_language in configs.items():
+ # Single issue test configurations
+ 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
+ language = test_language
+ target = trgt
+ runner = test_name
+
+ # Dual issue test configurations
+ for ctx in (BuildContext, CleanContext):
+ raw_context = ctx.__name__.replace('Context', '').lower()
+
+ class di_tmp(ctx):
+ cmd = raw_context + '_' + test_name + '_dual_issue' + '_' + trgt
+ variant = test_name + '_dual_issue' + '_' + trgt
+ source = test_name
+ language = test_language
+ target = trgt
+ runner = test_name
+
+
+UNITY_TEST_DIR = 'src'
+UNITY_TEST_PREFIX = 'test_'
+UNITY_RUNNER_DIR = 'runners'
+UNITY_RUNNER_SUFFIX = '_Runner'
+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')
+
+def configure(conf):
+ # TODO: move the call to generate_all_unity_runners() to build()
+ project_root = os.path.join('..', '..', '..')
+ generate_all_unity_runners(conf, project_root,
+ UNITY_TEST_DIR, UNITY_TEST_PREFIX,
+ UNITY_RUNNER_DIR, UNITY_RUNNER_SUFFIX)
+ conf.load('xwaf.xcommon')
+
+
+def build(bld):
+ if not bld.variant:
+ trgt = [
+ c for c in TARGETS if c == bld.options.target
+ ]
+
+ 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 + '_dual_issue' + '_' + trgt[0])
+ 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_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'))]
+ makefile_opts = {}
+ makefile_opts['SOURCE_DIRS'] = [os.path.join('src',bld.runner), os.path.join('runners',bld.runner)]
+ if(bld.target == 'xcoreai'):
+ print('TARGET XCOREAI')
+ makefile_opts['TARGET'] = ['XCORE-AI-EXPLORER']
+ else:
+ 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['APP_NAME'] = [bld.variant]
+ makefile_opts['USED_MODULES'] = depends_on
+ makefile_opts['XCOMMON_MAKEFILE'] = ['Makefile.common']
+ bld.do_xcommon(makefile_opts)
+
+def dist(ctx):
+ ctx.load('xwaf.xcommon')
+
+def distcheck(ctx):
+ ctx.load('xwaf.xcommon')