# 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'): 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 xe_name = ((os.path.basename(self.name)).split("."))[0] + ".xe" test_bin_path = os.path.join('bin', xe_name) 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)), end=' ') 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