diff --git a/.gitmodules b/.gitmodules index f38184b43..174780b2f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "ci"] path = ci url = git@github.com:bao-project/bao-ci.git +[submodule "tests/bao-tests"] + path = tests/bao-tests + url = git@github.com:bao-project/bao-tests.git +[submodule "tests/bao-nix"] + path = tests/bao-nix + url = git@github.com:bao-project/bao-nix.git diff --git a/Makefile b/Makefile index 0ce2a03e7..f94d501c4 100644 --- a/Makefile +++ b/Makefile @@ -47,13 +47,13 @@ scripts_dir:=$(cur_dir)/scripts ci_dir:=$(cur_dir)/ci src_dirs:= -include $(ci_dir)/ci.mk +-include $(ci_dir)/ci.mk targets:=$(MAKECMDGOALS) ifeq ($(targets),) targets:=all endif -non_build_targets+=ci clean +non_build_targets+=ci clean tests build_targets:=$(strip $(foreach target, $(targets), \ $(if $(findstring $(target),$(non_build_targets)),,$(target)))) @@ -342,3 +342,9 @@ $(call ci, format, $(all_c_files)) .PHONY: ci ci: license-check format-check + +tests_dir := $(cur_dir)/tests +-include $(tests_dir)/tests.mk + +.PHONY: tests +tests: framework \ No newline at end of file diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 000000000..807770c76 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,135 @@ +# Bao Test Framework + +## Overview + +The primary goal of the Bao Test Framework is to provide the infrastructure for the testing process of Bao Project components. It encompasses three core components: (i) a C library designed for real-time test handling, (ii) a Python tool for comprehensive test management, and (iii) a build system based on Nix. +The C library and Python tool are provided in the `bao-tests` repository while the Nix packages required are available in the `bao-nix` repository. Lastly, a folder `recipes` contains all the recipes and tests. + + + +## How to use + +**1. Create a folder inside `recipes` with the following structure** + +``` + tests + ├── bao-tests + ├── bao-nix + ├── recipes + │ ├── RECIPE-EXAMPLE + │ │ ├── configs + │ │ ├── src +``` + +The name `RECIPE-EXAMPLE` will be used as an argument when using `make`. +The folder `configs` is used to store all the bao configs (link to docs) related to the recipe in question. +Lastly, the folder `src` is used for test sources. + +**2. Create a `.nix` file in the `RECIPE-EXAMPLE` folder.** + +The following code is an example of a Nix recipe to build the system with a single baremetal guest. + +```nix +{ + pkgs ? import (fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/refs/tags/22.11.tar.gz"; + sha256 = "sha256:11w3wn2yjhaa5pv20gbfbirvjq6i3m7pqrq2msf0g7cv44vijwgw"; + }) {}, + platform ? " ", + bao_cfg_repo ? " ", + bao_cfg ? " ", + list_tests ? " ", + list_suites ? " ", + log_level ? " ", + GIC_VERSION ? " ", + IRQC ? " ", + IPIC ? " ", +}: + +with pkgs; + +let + packages = rec { + + setup-cfg = callPackage ../../bao-nix/pkgs/setup-cfg/setup-cfg.nix{ + inherit platform; + inherit GIC_VERSION; + inherit IRQC; + inherit IPIC; + bao-tests = ../../bao-tests; + tests_srcs = ./src; + baremetal_patch = ./baremetal.patch; + }; + + #Build toolchain + toolchain = callPackage ../../bao-nix/pkgs/toolchains/${setup-cfg.toolchain_name}.nix{}; + + #Build guests + guests = [ + (callPackage (../../bao-nix/pkgs/guest/tf/baremetal.nix) + { + inherit setup-cfg; + inherit toolchain; + guest_name = "baremetal"; + list_tests = ""; + list_suites = "CPU_BOOT_CHECK"; + inherit log_level; + } + ) + ]; + + bao_cfg_repo = ./configs; + + #Build Hypervisor + bao = callPackage ../../bao-nix/pkgs/bao/bao.nix + { + inherit setup-cfg; + inherit toolchain; + inherit bao_cfg_repo; + inherit bao_cfg; + inherit guests; + #bao_srcs_path = /home/mafs/bao-hypervisor; + }; + + # Build Firmware + firmware = callPackage ../../bao-nix/pkgs/firmware/${platform}.nix { + inherit toolchain; + inherit platform; + inherit setup-cfg; + }; + inherit pkgs; + }; +in + packages + +``` + +The main fields in the recipe that should be modified for other uses are: + +* **guests** - This array should include all the the guest used in the bao configuration. +* **list_tests** - For each guest that will execute tests, this field should be filled with test names. +* **list_suits** - Similar to the previous field, but should be filled with suites (groups of tests) instead. + +**3. Implement the tests** + +Create a `.c` file in the `src` folder and implement the tests following the example below: + +```c +#include "testf.h" + +BAO_TEST(SUITE_NAME, TEST_NAME) +{ + printf("Hello World!!!\n"); +} +``` + +Every source file that implements tests should include the `testf.h` header. +A test is defined using the macro `BAO_TEST` with `SUITE_NAME` and `TEST_NAME` as arguments to identify the test in the previous step. + +**4. Run the framework** + +From the **bao-hypervisor** main directory, to run the test framework you should use the following command: + +``` +make tests PLATFORM=platform RECIPE=RECIPE_EXAMPLE +``` \ No newline at end of file diff --git a/tests/bao-nix b/tests/bao-nix new file mode 160000 index 000000000..f9ab3c122 --- /dev/null +++ b/tests/bao-nix @@ -0,0 +1 @@ +Subproject commit f9ab3c122f450180fd4d67f0850b0cb94d38b702 diff --git a/tests/bao-tests b/tests/bao-tests new file mode 160000 index 000000000..5dd718a7e --- /dev/null +++ b/tests/bao-tests @@ -0,0 +1 @@ +Subproject commit 5dd718a7e3a91a355a2dc6e2a9b41be769edf138 diff --git a/tests/recipes/single-baremetal/baremetal.patch b/tests/recipes/single-baremetal/baremetal.patch new file mode 100644 index 000000000..2dacf237f --- /dev/null +++ b/tests/recipes/single-baremetal/baremetal.patch @@ -0,0 +1,32 @@ +diff --git a/setup.mk b/setup.mk +index 3a230aa..2edf9ee 100644 +--- a/setup.mk ++++ b/setup.mk +@@ -29,6 +29,14 @@ src_dirs+=$(src_dir) $(core_dir) $(platform_dir) + SRC_DIRS+=$(src_dirs) + INC_DIRS+=$(addsuffix /inc, $(src_dirs)) + ++# Test framework setup ++include $(TESTF_REPO_DIR)/src/bao-test.mk ++SRC_DIRS+=$(TESTF_SRC_DIR) $(TESTF_TESTS_DIR) ++C_SRC+=$(TESTF_SRCS) ++INC_DIRS+=$(TESTF_INC_DIR) ++CFLAGS+=$(TESTF_FLAGS) ++# End of test framework setup ++ + ifeq ($(wildcard $(platform_dir)),) + $(error unsupported platform $(PLATFORM)) + endif +diff --git a/src/main.c b/src/main.c +index 9a9e972..69cfa6f 100644 +--- a/src/main.c ++++ b/src/main.c +@@ -54,6 +54,8 @@ void main(void){ + printf("Bao bare-metal test guest\n"); + spin_unlock(&print_lock); + ++ testf_entry(); ++ + irq_set_handler(UART_IRQ_ID, uart_rx_handler); + irq_set_handler(TIMER_IRQ_ID, timer_handler); + irq_set_handler(IPI_IRQ_ID, ipi_handler); \ No newline at end of file diff --git a/tests/recipes/single-baremetal/configs/qemu-aarch64-virt.c b/tests/recipes/single-baremetal/configs/qemu-aarch64-virt.c new file mode 100644 index 000000000..78a22e84e --- /dev/null +++ b/tests/recipes/single-baremetal/configs/qemu-aarch64-virt.c @@ -0,0 +1,58 @@ +#include + +VM_IMAGE(baremetal_image, XSTR(BAO_WRKDIR_IMGS/baremetal.bin)) + +struct config config = { + + CONFIG_HEADER + + .vmlist_size = 1, + .vmlist = (struct vm_config[]) { + { + .image = { + .base_addr = 0x50000000, + .load_addr = VM_IMAGE_OFFSET(baremetal_image), + .size = VM_IMAGE_SIZE(baremetal_image) + }, + + .entry = 0x50000000, + + .platform = { + .cpu_num = 4, + + .region_num = 1, + .regions = (struct vm_mem_region[]) { + { + .base = 0x50000000, + .size = 0x4000000 + } + }, + + .dev_num = 2, + .devs = (struct vm_dev_region[]) { + { + /* PL011 */ + .pa = 0x9000000, + .va = 0x9000000, + .size = 0x10000, + .interrupt_num = 1, + .interrupts = (irqid_t[]) {33} + }, + { + /* Arch timer interrupt */ + .interrupt_num = 1, + .interrupts = + (irqid_t[]) {27} + } + }, + + .arch = { + .gic = { + .gicd_addr = (paddr_t) 0x08000000, + .gicr_addr = (paddr_t) 0x080A0000, + } + } + }, + } + }, +}; \ No newline at end of file diff --git a/tests/recipes/single-baremetal/configs/qemu-riscv64-virt.c b/tests/recipes/single-baremetal/configs/qemu-riscv64-virt.c new file mode 100644 index 000000000..f038ff9e1 --- /dev/null +++ b/tests/recipes/single-baremetal/configs/qemu-riscv64-virt.c @@ -0,0 +1,52 @@ +#include + +VM_IMAGE(baremetal_image, XSTR(BAO_WRKDIR_IMGS/baremetal.bin)) + +struct config config = { + + CONFIG_HEADER + + .vmlist_size = 1, + .vmlist = (struct vm_config[]) { + { + .image = { + .base_addr = 0x80200000, + .load_addr = VM_IMAGE_OFFSET(baremetal_image), + .size = VM_IMAGE_SIZE(baremetal_image) + }, + + .entry = 0x80200000, + + .platform = { + .cpu_num = 4, + + .region_num = 1, + .regions = (struct vm_mem_region[]) { + { + .base = 0x80200000, + .size = 0x4000000 + } + }, + + .dev_num = 1, + .devs = (struct vm_dev_region[]) { + { + .pa = 0x10000000, + .va = 0x10000000, + .size = 0x1000, + .interrupt_num = 1, + .interrupts = (irqid_t[]) {10} + }, + }, + + .arch = { + .irqc = { + .plic = { + .base = 0xc000000, + }, + }, + }, + }, + }, + } +}; diff --git a/tests/recipes/single-baremetal/default.nix b/tests/recipes/single-baremetal/default.nix new file mode 100644 index 000000000..f8eeeb786 --- /dev/null +++ b/tests/recipes/single-baremetal/default.nix @@ -0,0 +1,71 @@ +{ + pkgs ? import (fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/refs/tags/22.11.tar.gz"; + sha256 = "sha256:11w3wn2yjhaa5pv20gbfbirvjq6i3m7pqrq2msf0g7cv44vijwgw"; + }) {}, + platform ? " ", + bao_cfg_repo ? " ", + bao_cfg ? " ", + list_tests ? " ", + list_suites ? " ", + log_level ? " ", + GIC_VERSION ? " ", + IRQC ? " ", + IPIC ? " ", +}: + +with pkgs; + +let + packages = rec { + + setup-cfg = callPackage ../../bao-nix/pkgs/setup-cfg/setup-cfg.nix{ + inherit platform; + inherit GIC_VERSION; + inherit IRQC; + inherit IPIC; + bao-tests = ../../bao-tests; + tests_srcs = ./src; + baremetal_patch = ./baremetal.patch; + }; + + #Build toolchain + toolchain = callPackage ../../bao-nix/pkgs/toolchains/${setup-cfg.toolchain_name}.nix{}; + + #Build guests + guests = [ + (callPackage (../../bao-nix/pkgs/guest/tf/baremetal.nix) + { + inherit setup-cfg; + inherit toolchain; + guest_name = "baremetal"; + list_tests = ""; + list_suites = "CPU_BOOT_CHECK IRQ_CHECK"; + inherit log_level; + } + ) + ]; + + bao_cfg_repo = ./configs; + + #Build Hypervisor + bao = callPackage ../../bao-nix/pkgs/bao/bao.nix + { + inherit setup-cfg; + inherit toolchain; + inherit bao_cfg_repo; + inherit bao_cfg; + inherit guests; + #bao_srcs_path = /home/mafs/bao-hypervisor; + }; + + # Build Firmware + firmware = callPackage ../../bao-nix/pkgs/firmware/${platform}.nix { + inherit toolchain; + inherit platform; + inherit setup-cfg; + }; + inherit pkgs; + }; +in + packages diff --git a/tests/recipes/single-baremetal/src/boot.c b/tests/recipes/single-baremetal/src/boot.c new file mode 100644 index 000000000..84f2a77e8 --- /dev/null +++ b/tests/recipes/single-baremetal/src/boot.c @@ -0,0 +1,7 @@ +#include "testf.h" + +BAO_TEST(CPU_BOOT_CHECK,BOOT) +{ + TESTF_PASS("System booted successfully!\n"); +} + diff --git a/tests/recipes/single-baremetal/src/irq.c b/tests/recipes/single-baremetal/src/irq.c new file mode 100644 index 000000000..17d239a09 --- /dev/null +++ b/tests/recipes/single-baremetal/src/irq.c @@ -0,0 +1,66 @@ +#include "testf.h" +// #include +// #include +// #include +// #include +// #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void test_interrupt_timer_callback(); +void uart_rx_handler(); + + +volatile bool irq_en_timer = false; +volatile bool irq_en_uart = false; + +#define TIMER_INTERVAL (TIME_MS(100)) +#define TEST_TIME_WAIT (TIME_MS(100)) +#define TEST_TIMEOUT "200" + +BAO_TEST(IRQ_CHECK1, TIMER) +{ + COMMAND_SEND_TIMEOUT(TEST_TIMEOUT); + + irq_set_handler(TIMER_IRQ_ID, test_interrupt_timer_callback); + timer_set(TIMER_INTERVAL); + irq_enable(TIMER_IRQ_ID); + irq_set_prio(TIMER_IRQ_ID, IRQ_MAX_PRIO); + + while(!irq_en_timer); + EXPECTED_TRUE(irq_en_timer); +} + +BAO_TEST(IRQ_CHECK, UART) +{ + COMMAND_SEND_TIMEOUT(TEST_TIMEOUT); + + irq_set_handler(33, uart_rx_handler); + uart_enable_rxirq(); + irq_enable(33); + irq_set_prio(33, IRQ_MAX_PRIO); + COMMAND_SEND_CHAR("a"); + + timer_wait(TEST_TIME_WAIT); + EXPECTED_TRUE(irq_en_uart); +} + +void test_interrupt_timer_callback() +{ + irq_en_timer = true; + timer_set(TIMER_INTERVAL); +} + +void uart_rx_handler(){ + uart_clear_rxirq(); + irq_en_uart = true; +} \ No newline at end of file diff --git a/tests/recipes/single-freertos/configs/qemu-aarch64-virt.c b/tests/recipes/single-freertos/configs/qemu-aarch64-virt.c new file mode 100644 index 000000000..c8f085775 --- /dev/null +++ b/tests/recipes/single-freertos/configs/qemu-aarch64-virt.c @@ -0,0 +1,72 @@ +#include + +VM_IMAGE(freertos_image, XSTR(BAO_WRKDIR_IMGS/freertos.bin)) + +struct config config = { + + CONFIG_HEADER + + .shmemlist_size = 1, + .shmemlist = (struct shmem[]) { + [0] = { .size = 0x00010000, } + }, + + .vmlist_size = 1, + .vmlist = (struct vm_config[]) { + { + .image = { + .base_addr = 0x0, + .load_addr = VM_IMAGE_OFFSET(freertos_image), + .size = VM_IMAGE_SIZE(freertos_image) + }, + + .entry = 0x0, + + .platform = { + .cpu_num = 1, + + .region_num = 1, + .regions = (struct vm_mem_region[]) { + { + .base = 0x0, + .size = 0x8000000 + } + }, + + .ipc_num = 1, + .ipcs = (struct ipc[]) { + { + .base = 0x70000000, + .size = 0x00010000, + .shmem_id = 0, + .interrupt_num = 1, + .interrupts = (irqid_t[]) {52} + } + }, + + .dev_num = 2, + .devs = (struct vm_dev_region[]) { + { + /* PL011 */ + .pa = 0x9000000, + .va = 0xff000000, + .size = 0x10000, + .interrupt_num = 1, + .interrupts = (irqid_t[]) {33} + }, + { + .interrupt_num = 1, + .interrupts = (irqid_t[]) {27} + } + }, + + .arch = { + .gic = { + .gicd_addr = 0xf9010000, + .gicr_addr = 0xf9020000, + } + } + }, + }, + }, +}; \ No newline at end of file diff --git a/tests/recipes/single-freertos/configs/qemu-riscv64-virt.c b/tests/recipes/single-freertos/configs/qemu-riscv64-virt.c new file mode 100644 index 000000000..3aba5bf55 --- /dev/null +++ b/tests/recipes/single-freertos/configs/qemu-riscv64-virt.c @@ -0,0 +1,52 @@ +#include + +VM_IMAGE(baremetal_image, XSTR(BAO_WRKDIR_IMGS/freertos.bin)) + +struct config config = { + + CONFIG_HEADER + + .vmlist_size = 1, + .vmlist = (struct vm_config[]) { + { + .image = { + .base_addr = 0x80200000, + .load_addr = VM_IMAGE_OFFSET(baremetal_image), + .size = VM_IMAGE_SIZE(baremetal_image) + }, + + .entry = 0x80200000, + + .platform = { + .cpu_num = 4, + + .region_num = 1, + .regions = (struct vm_mem_region[]) { + { + .base = 0x80200000, + .size = 0x4000000 + } + }, + + .dev_num = 1, + .devs = (struct vm_dev_region[]) { + { + .pa = 0x10000000, + .va = 0x10000000, + .size = 0x1000, + .interrupt_num = 1, + .interrupts = (irqid_t[]) {10} + }, + }, + + .arch = { + .irqc = { + .plic = { + .base = 0xc000000, + }, + }, + }, + }, + }, + } +}; diff --git a/tests/recipes/single-freertos/default.nix b/tests/recipes/single-freertos/default.nix new file mode 100644 index 000000000..a25a88181 --- /dev/null +++ b/tests/recipes/single-freertos/default.nix @@ -0,0 +1,71 @@ +{ + pkgs ? import (fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/refs/tags/22.11.tar.gz"; + sha256 = "sha256:11w3wn2yjhaa5pv20gbfbirvjq6i3m7pqrq2msf0g7cv44vijwgw"; + }) {}, + platform ? " ", + bao_cfg_repo ? " ", + bao_cfg ? " ", + list_tests ? " ", + list_suites ? " ", + log_level ? " ", + GIC_VERSION ? " ", + IRQC ? " ", + IPIC ? " ", +}: + +with pkgs; + +let + packages = rec { + + setup-cfg = callPackage ../../bao-nix/pkgs/setup-cfg/setup-cfg.nix{ + inherit platform; + inherit GIC_VERSION; + inherit IRQC; + inherit IPIC; + bao-tests = ../../bao-tests; + tests_srcs = ./src; + baremetal_patch = ./baremetal.patch; + }; + + #Build toolchain + toolchain = callPackage ../../bao-nix/pkgs/toolchains/${setup-cfg.toolchain_name}.nix{}; + + #Build guests + guests = [ + (callPackage (../../bao-nix/pkgs/guest/tf/freertos.nix) + { + inherit setup-cfg; + inherit toolchain; + guest_name = "freertos"; + list_tests = ""; + list_suites = "CPU_BOOT_CHECK IRQ_CHECK"; + inherit log_level; + } + ) + ]; + + bao_cfg_repo = ./configs; + + #Build Hypervisor + bao = callPackage ../../bao-nix/pkgs/bao/bao.nix + { + inherit setup-cfg; + inherit toolchain; + inherit bao_cfg_repo; + inherit bao_cfg; + inherit guests; + #bao_srcs_path = /home/mafs/bao-hypervisor; + }; + + # Build Firmware + firmware = callPackage ../../bao-nix/pkgs/firmware/${platform}.nix { + inherit toolchain; + inherit platform; + inherit setup-cfg; + }; + inherit pkgs; + }; +in + packages diff --git a/tests/recipes/single-freertos/src/boot.c b/tests/recipes/single-freertos/src/boot.c new file mode 100644 index 000000000..84f2a77e8 --- /dev/null +++ b/tests/recipes/single-freertos/src/boot.c @@ -0,0 +1,7 @@ +#include "testf.h" + +BAO_TEST(CPU_BOOT_CHECK,BOOT) +{ + TESTF_PASS("System booted successfully!\n"); +} + diff --git a/tests/recipes/single-freertos/src/irq.c b/tests/recipes/single-freertos/src/irq.c new file mode 100644 index 000000000..77a1024f0 --- /dev/null +++ b/tests/recipes/single-freertos/src/irq.c @@ -0,0 +1,64 @@ +#include "testf.h" +// #include +// #include +// #include +// #include +// #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void test_interrupt_timer_callback(); +void uart_rx_handler(); + + +volatile bool irq_en_timer = false; +volatile bool irq_en_uart = false; + +#define TIMER_INTERVAL (TIME_MS(10)) +#define TEST_TIME_WAIT (TIME_MS(100)) +#define TEST_TIMEOUT "200" + +BAO_TEST(IRQ_CHECK, TIMER) +{ + COMMAND_SEND_TIMEOUT(TEST_TIMEOUT); + + irq_set_handler(TIMER_IRQ_ID, test_interrupt_timer_callback); + timer_set(TIMER_INTERVAL); + irq_enable(TIMER_IRQ_ID); + irq_set_prio(TIMER_IRQ_ID, IRQ_MAX_PRIO); + while(!irq_en_timer); + EXPECTED_TRUE(irq_en_timer); +} + +BAO_TEST(IRQ_CHECK, UART) +{ + COMMAND_SEND_TIMEOUT(TEST_TIMEOUT); + + uart_enable_rxirq(); + irq_set_handler(UART_IRQ_ID, uart_rx_handler); + irq_set_prio(UART_IRQ_ID, IRQ_MAX_PRIO); + irq_enable(UART_IRQ_ID); + COMMAND_SEND_CHAR("a"); + while(!irq_en_uart); + EXPECTED_TRUE(irq_en_uart); +} + +void test_interrupt_timer_callback() +{ + irq_en_timer = true; + timer_set(TIMER_INTERVAL); +} + +void uart_rx_handler(){ + uart_clear_rxirq(); + irq_en_uart = true; +} \ No newline at end of file diff --git a/tests/tests.mk b/tests/tests.mk new file mode 100644 index 000000000..d50e97fef --- /dev/null +++ b/tests/tests.mk @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) Bao Project and Contributors. All rights reserved + +bao-tests_dir = $(tests_dir)/bao-tests +bao-nix_dir = $(tests_dir)/bao-nix +recipes_dir = $(tests_dir)/recipes + +LOG_LEVEL?=2 +ECHO?=tf + +irq_flags:= + +ifdef GIC_VERSION +irq_flags+=-gicv +irq_flags+=$(GIC_VERSION) +endif + +ifdef IRQC +irq_flags+=-irqc +irq_flags+=$(IRQC) +endif + +ifdef IPIC +irq_flags+=-ipic +irq_flags+=$(IPIC) +endif + + +.PHONY: test-framework +framework: + +ifndef RECIPE + @echo "Error: RECIPE variable is not defined. Please specify the path to the recipe file." + @exit 1 +endif + + @echo "Running Bao Tests Framework..." + python3 $(bao-tests_dir)/framework/test_framework.py \ + -recipe $(recipes_dir)/$(RECIPE) \ + -log_level $(LOG_LEVEL) \ + -echo $(ECHO) \ + -platform $(PLATFORM) \ + $(irq_flags) +