init
This commit is contained in:
32
lib_audio_dsp/test/unit_tests/CMakeLists.txt
Normal file
32
lib_audio_dsp/test/unit_tests/CMakeLists.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake)
|
||||
project(unit_test)
|
||||
|
||||
set(APP_HW_TARGET XCORE-AI-EXPLORER)
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
set(APP_DEPENDENT_MODULES lib_unity lib_audio_dsp)
|
||||
|
||||
set(APP_INCLUDES build/dummy_pipeline ${CMAKE_CURRENT_LIST_DIR})
|
||||
file(GLOB APP_C_SRCS CONFIGURE_DEPENDS RELATIVE ${CMAKE_CURRENT_LIST_DIR} src/*.c build/dummy_pipeline/*.c)
|
||||
file(GLOB ADSP_ADDITIONAL_STAGE_CONFIG ${CMAKE_CURRENT_LIST_DIR}/stages/*.yaml )
|
||||
|
||||
file(GLOB tests RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS src/test*.c)
|
||||
foreach(test_file ${tests})
|
||||
get_filename_component(test_name ${test_file} NAME_WE)
|
||||
set(SOURCE_FILES_${test_name} ${test_file})
|
||||
set(APP_COMPILER_FLAGS_${test_name}
|
||||
-O0
|
||||
-g
|
||||
-report
|
||||
-Wall
|
||||
# -Werror
|
||||
-fxscope
|
||||
-DDEBUG_PRINT_ENABLE=1)
|
||||
endforeach()
|
||||
|
||||
set(LIB_AUDIO_DSP_DISABLE_OPTIMISATION ON)
|
||||
set(LIB_UNITY_AUTO_TEST_RUNNER ON)
|
||||
set(LIB_UNITY_USE_FIXTURE OFF)
|
||||
set(LIB_UNITY_USE_MEMORY OFF)
|
||||
XMOS_REGISTER_APP()
|
||||
86
lib_audio_dsp/test/unit_tests/conftest.py
Normal file
86
lib_audio_dsp/test/unit_tests/conftest.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# Copyright 2021-2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
import pytest
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import re
|
||||
import os
|
||||
|
||||
from audio_dsp.design import pipeline
|
||||
from stages.dummy import Dummy
|
||||
|
||||
|
||||
def gen_dummy_pipeline():
|
||||
p = pipeline.Pipeline(1, identifier="dummy")
|
||||
d = p.stage(Dummy, p.i, label="dummy")
|
||||
p.set_outputs(d)
|
||||
pipeline.generate_dsp_main(p, out_dir=Path(__file__).parent / "build/dummy_pipeline")
|
||||
|
||||
|
||||
def pytest_configure():
|
||||
worker_id = os.environ.get("PYTEST_XDIST_WORKER")
|
||||
if worker_id is None:
|
||||
# dont run on the worker threads
|
||||
gen_dummy_pipeline()
|
||||
if not Path("bin").exists():
|
||||
subprocess.run(["cmake", "-B", "build"], check=True)
|
||||
subprocess.run(["cmake", "--build", "build"], check=True)
|
||||
|
||||
## Begin pytest magic to convert xe files into tests
|
||||
|
||||
def pytest_collect_file(parent, path):
|
||||
"""Custom collection function to inform pytest that xe files contain tests."""
|
||||
if path.ext == ".xe":
|
||||
return UnityTestSource.from_parent(parent, path=Path(path))
|
||||
|
||||
class UnityTestSource(pytest.File):
|
||||
"""
|
||||
Each xe file contains 1 pytest test.
|
||||
"""
|
||||
def collect(self):
|
||||
yield UnityTestExecutable.from_parent(self, xe=self.path, name=self.path.stem)
|
||||
|
||||
|
||||
class UnityTestExecutable(pytest.Item):
|
||||
"""
|
||||
Run the xe file in xsim, this is the work of the test.
|
||||
"""
|
||||
def __init__(self, xe, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.xe=xe
|
||||
self.fail_reason=[]
|
||||
|
||||
def runtest(self):
|
||||
"""
|
||||
fancy test output processing.
|
||||
"""
|
||||
proc = subprocess.run(["xsim", self.xe], text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
self.add_report_section("call", "stdout", proc.stdout)
|
||||
unity_result_pattern=r"^(?P<path>[^\n:]+):(?P<line>\d+):(?P<name>[^:]+):(?P<status>PASS|FAIL)(: (?P<message>.*))?$"
|
||||
unlikely_repl = "unlikely_repl"
|
||||
|
||||
result = [i for i in re.finditer(unity_result_pattern, proc.stdout, re.MULTILINE)]
|
||||
all_out = [i for i in re.sub(unity_result_pattern, unlikely_repl, proc.stdout, flags=re.MULTILINE).split(unlikely_repl)]
|
||||
|
||||
for match, output in zip(result, all_out):
|
||||
file, line, test_name, status, message = match.group("path", "line", "name", "status", "message")
|
||||
fail_reason = f"{test_name}:{line}:{message}"
|
||||
self.add_report_section("call", f"{status} {test_name}", output + "\n" + fail_reason)
|
||||
if status == "FAIL":
|
||||
self.fail_reason.append(fail_reason)
|
||||
if proc.returncode:
|
||||
raise UnityTestException
|
||||
|
||||
def repr_failure(self, excinfo):
|
||||
if isinstance(excinfo.value, UnityTestException):
|
||||
return "Failure summary:\n\t" + "\n\t".join(self.fail_reason)
|
||||
return super().repr_failure(excinfo)
|
||||
|
||||
def reportinfo(self):
|
||||
return self.path, 0, self.xe.stem
|
||||
|
||||
|
||||
class UnityTestException(Exception):
|
||||
pass
|
||||
|
||||
## End pytest magic
|
||||
0
lib_audio_dsp/test/unit_tests/src/empty.c
Normal file
0
lib_audio_dsp/test/unit_tests/src/empty.c
Normal file
153
lib_audio_dsp/test/unit_tests/src/test_adsp_control.c
Normal file
153
lib_audio_dsp/test/unit_tests/src/test_adsp_control.c
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
/// Unit tests for adsp_control.c
|
||||
|
||||
#include <unity.h>
|
||||
|
||||
#include <stages/adsp_control.h>
|
||||
#include <stages/dummy.h>
|
||||
#include <adsp_generated_dummy.h>
|
||||
#include <adsp_instance_id_dummy.h>
|
||||
#include <cmds.h>
|
||||
#include <stdio.h>
|
||||
#include <swlock.h>
|
||||
|
||||
void setUp(){}
|
||||
void tearDown(){}
|
||||
|
||||
/// Test that basic read and write works.
|
||||
void test_basic_control(void) {
|
||||
|
||||
adsp_pipeline_t* p = adsp_dummy_pipeline_init();
|
||||
module_instance_t* dummy_inst = &p->modules[dummy_stage_index];
|
||||
adsp_controller_t ctrl;
|
||||
adsp_controller_init(&ctrl, p);
|
||||
|
||||
int test_val = 5;
|
||||
adsp_stage_control_cmd_t cmd = {
|
||||
.instance_id = dummy_stage_index,
|
||||
.cmd_id = CMD_DUMMY_DUMMY_FIELD,
|
||||
.payload_len = sizeof(int),
|
||||
.payload = &test_val
|
||||
};
|
||||
|
||||
// First write succeeds as stage isn't busy
|
||||
adsp_control_status_t status = adsp_write_module_config(&ctrl, &cmd);
|
||||
TEST_ASSERT_EQUAL(ADSP_CONTROL_SUCCESS, status);
|
||||
|
||||
// Now the stage is busy
|
||||
status = adsp_write_module_config(&ctrl, &cmd);
|
||||
TEST_ASSERT_EQUAL(ADSP_CONTROL_BUSY, status);
|
||||
// Now the stage is busy
|
||||
status = adsp_read_module_config(&ctrl, &cmd);
|
||||
TEST_ASSERT_EQUAL(ADSP_CONTROL_BUSY, status);
|
||||
|
||||
// Stage processes control
|
||||
dummy_control(dummy_inst->state, &dummy_inst->control);
|
||||
|
||||
*(int*)cmd.payload = 0; // clear the payload, ready for read.
|
||||
|
||||
// request a read, stage must process it so it is busy
|
||||
status = adsp_read_module_config(&ctrl, &cmd);
|
||||
TEST_ASSERT_EQUAL(ADSP_CONTROL_BUSY, status);
|
||||
|
||||
// The stage is busy
|
||||
status = adsp_write_module_config(&ctrl, &cmd);
|
||||
TEST_ASSERT_EQUAL(ADSP_CONTROL_BUSY, status);
|
||||
|
||||
// Stage processes control
|
||||
dummy_control(dummy_inst->state, &dummy_inst->control);
|
||||
|
||||
|
||||
// re-request the read, stage must process it so it is busy
|
||||
status = adsp_read_module_config(&ctrl, &cmd);
|
||||
TEST_ASSERT_EQUAL(ADSP_CONTROL_SUCCESS, status);
|
||||
TEST_ASSERT_EQUAL(test_val, *(int*)cmd.payload);
|
||||
}
|
||||
|
||||
/// Test that when there are two controllers, only the controller who made
|
||||
/// the request will get a response.
|
||||
void test_adsp_control_2_controllers(void) {
|
||||
adsp_pipeline_t* p = adsp_dummy_pipeline_init();
|
||||
module_instance_t* dummy_inst = &p->modules[dummy_stage_index];
|
||||
adsp_controller_t ctrl_a;
|
||||
adsp_controller_init(&ctrl_a, p);
|
||||
adsp_controller_t ctrl_b;
|
||||
adsp_controller_init(&ctrl_b, p);
|
||||
|
||||
int test_val = 5;
|
||||
adsp_stage_control_cmd_t cmd = {
|
||||
.instance_id = dummy_stage_index,
|
||||
.cmd_id = CMD_DUMMY_DUMMY_FIELD,
|
||||
.payload_len = sizeof(int),
|
||||
.payload = &test_val
|
||||
};
|
||||
|
||||
// First write succeeds as stage isn't busy
|
||||
adsp_control_status_t status = adsp_write_module_config(&ctrl_a, &cmd);
|
||||
TEST_ASSERT_EQUAL(ADSP_CONTROL_SUCCESS, status);
|
||||
// stage is now busy for both controllers.
|
||||
status = adsp_write_module_config(&ctrl_a, &cmd);
|
||||
TEST_ASSERT_EQUAL(ADSP_CONTROL_BUSY, status);
|
||||
status = adsp_write_module_config(&ctrl_b, &cmd);
|
||||
TEST_ASSERT_EQUAL(ADSP_CONTROL_BUSY, status);
|
||||
|
||||
// Stage processes control
|
||||
dummy_control(dummy_inst->state, &dummy_inst->control);
|
||||
|
||||
*(int*)cmd.payload = 0; // clear the payload, ready for read.
|
||||
|
||||
// request a read, stage must process it so it is busy
|
||||
status = adsp_read_module_config(&ctrl_a, &cmd);
|
||||
TEST_ASSERT_EQUAL(ADSP_CONTROL_BUSY, status);
|
||||
|
||||
// Stage processes control
|
||||
dummy_control(dummy_inst->state, &dummy_inst->control);
|
||||
|
||||
|
||||
// Even though a read was processed, b still gets busy
|
||||
// because it didn't ask for it.
|
||||
status = adsp_read_module_config(&ctrl_b, &cmd);
|
||||
TEST_ASSERT_EQUAL(ADSP_CONTROL_BUSY, status);
|
||||
// but a gets the result.
|
||||
status = adsp_read_module_config(&ctrl_a, &cmd);
|
||||
TEST_ASSERT_EQUAL(ADSP_CONTROL_SUCCESS, status);
|
||||
TEST_ASSERT_EQUAL(test_val, *(int*)cmd.payload);
|
||||
}
|
||||
|
||||
|
||||
/// Ensure that busy is returned when the lock is taken for both read
|
||||
/// and write.
|
||||
void test_lock_contention(void) {
|
||||
|
||||
adsp_pipeline_t* p = adsp_dummy_pipeline_init();
|
||||
module_instance_t* dummy_inst = &p->modules[dummy_stage_index];
|
||||
adsp_controller_t ctrl;
|
||||
adsp_controller_init(&ctrl, p);
|
||||
|
||||
int test_val = 5;
|
||||
adsp_stage_control_cmd_t cmd = {
|
||||
.instance_id = dummy_stage_index,
|
||||
.cmd_id = CMD_DUMMY_DUMMY_FIELD,
|
||||
.payload_len = sizeof(int),
|
||||
.payload = &test_val
|
||||
};
|
||||
|
||||
// take the lock to emulate contention.
|
||||
swlock_acquire(&dummy_inst->control.lock);
|
||||
|
||||
// Get busy even though the stage isn't doing anything because the lock
|
||||
// is taken.
|
||||
adsp_control_status_t status = adsp_write_module_config(&ctrl, &cmd);
|
||||
TEST_ASSERT_EQUAL(ADSP_CONTROL_BUSY, status);
|
||||
|
||||
swlock_release(&dummy_inst->control.lock);
|
||||
|
||||
status = adsp_write_module_config(&ctrl, &cmd);
|
||||
TEST_ASSERT_EQUAL(ADSP_CONTROL_SUCCESS, status);
|
||||
|
||||
// Stage processes control
|
||||
dummy_control(dummy_inst->state, &dummy_inst->control);
|
||||
}
|
||||
|
||||
41
lib_audio_dsp/test/unit_tests/stages/dummy.h
Normal file
41
lib_audio_dsp/test/unit_tests/stages/dummy.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
// Minimal stage required to process some control.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stages/bump_allocator.h>
|
||||
#include <stages/adsp_module.h>
|
||||
#include <string.h>
|
||||
#include <dummy_config.h>
|
||||
|
||||
typedef struct {
|
||||
dummy_config_t config;
|
||||
} dummy_state_t;
|
||||
|
||||
#define DUMMY_STAGE_REQUIRED_MEMORY 0
|
||||
|
||||
static inline void dummy_init(module_instance_t* state, adsp_bump_allocator_t* alloc, int index, int nin, int nout, int chan) {
|
||||
|
||||
}
|
||||
|
||||
static inline void dummy_process(int32_t **input, int32_t **output, void *app_data_state) {}
|
||||
|
||||
static inline void dummy_control(dummy_state_t *module_state, module_control_t *control) {
|
||||
|
||||
dummy_state_t *state = module_state;
|
||||
dummy_config_t *config = control->config;
|
||||
|
||||
if(control->config_rw_state == config_write_pending)
|
||||
{
|
||||
// Finish the write by updating the working copy with the new config
|
||||
memcpy(&state->config, config, sizeof(dummy_config_t));
|
||||
control->config_rw_state = config_none_pending;
|
||||
}
|
||||
else if(control->config_rw_state == config_read_pending)
|
||||
{
|
||||
memcpy(config, &state->config, sizeof(dummy_config_t));
|
||||
control->config_rw_state = config_read_updated;
|
||||
}
|
||||
}
|
||||
12
lib_audio_dsp/test/unit_tests/stages/dummy.py
Normal file
12
lib_audio_dsp/test/unit_tests/stages/dummy.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# Copyright 2024-2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
from audio_dsp.design.stage import Stage
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class Dummy(Stage):
|
||||
"""Stage that does nothing but has control"""
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(config=Path(__file__).parent / "dummy.yaml", **kwargs)
|
||||
self.create_outputs(self.n_in)
|
||||
6
lib_audio_dsp/test/unit_tests/stages/dummy.yaml
Normal file
6
lib_audio_dsp/test/unit_tests/stages/dummy.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
module:
|
||||
dummy:
|
||||
dummy_field:
|
||||
type: int
|
||||
help: Some help string
|
||||
Reference in New Issue
Block a user