diff --git a/.github/workflows/runner-ci.yaml b/.github/workflows/runner-ci.yaml index 3d1384443..3983123a8 100644 --- a/.github/workflows/runner-ci.yaml +++ b/.github/workflows/runner-ci.yaml @@ -19,13 +19,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} diff --git a/enet/Makefile b/enet/Makefile new file mode 100644 index 000000000..c431fbcd1 --- /dev/null +++ b/enet/Makefile @@ -0,0 +1,5 @@ +NAME := test-enet +LOCAL_SRCS := main.c +DEP_LIBS := unity + +include $(binary.mk) diff --git a/enet/main.c b/enet/main.c new file mode 100644 index 000000000..dc7e2365d --- /dev/null +++ b/enet/main.c @@ -0,0 +1,293 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unity_fixture.h" + +#define _TP_DST "dddddd" /* overridden in setup */ +#define _TP_SRC "ssssss" /* overridden in setup */ +#define _TP_ETHTYPE "tt" /* overridden in setup */ +#define _TP_10DIG "0123456789" +#define TEST_HEADER _TP_DST _TP_SRC _TP_ETHTYPE +#define TEST_HEADER_LEN (sizeof((TEST_HEADER)) - 1) +#define TEST_PAYLOAD _TP_10DIG _TP_10DIG _TP_10DIG _TP_10DIG _TP_10DIG _TP_10DIG _TP_10DIG +#define TEST_PAYLOAD_LEN (sizeof((TEST_PAYLOAD)) - 1) +#define TEST_PACKET TEST_HEADER TEST_PAYLOAD +#define TEST_PACKET_LEN (sizeof((TEST_PACKET)) - 1) + +#define MAX_PAYLOAD_LEN (1500) +#define MAX_BUF_LEN (ETH_HLEN + MAX_PAYLOAD_LEN + 4) /* header len + MTU + crc */ + + +typedef union { + struct { + struct ether_header header; + uint8_t payload[MAX_PAYLOAD_LEN]; + } __attribute__((packed)); + uint8_t raw_buf[MAX_BUF_LEN]; +} ether_frame_t; + + +static int ctrl_sock = -1, send_sock = -1, recv_sock = -1; +static uint8_t *dynamic_buf; +static struct ifreq ifr; +static ether_frame_t send_frame; +static ether_frame_t recv_frame; +static struct sockaddr self_hwaddr; +static struct sockaddr_ll src_addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_ALL), +}; +static struct sockaddr_ll dst_addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_ALL), +}; + + +static inline void make_rand_frame(uint8_t *buf, size_t buf_sz) +{ + for (size_t i = 0; i < buf_sz; i++) { + buf[i] = rand() & 0xff; + } +} + + +static inline uint64_t now_us(void) +{ + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return (now.tv_sec * 1000 * 1000) + (now.tv_nsec / 1000); +} + + +static inline void set_iface_loopback(int fd, struct ifreq *ifr, bool enable) +{ + int err; + struct ethtool_value loopback = { + .cmd = ETHTOOL_SLOOPBACK, + .data = enable, + }; + ifr->ifr_data = (char *)&loopback; + + err = ioctl(fd, SIOCETHTOOL, ifr); + TEST_ASSERT_EQUAL_MESSAGE(0, err, strerror(errno)); + + loopback.cmd = ETHTOOL_GLOOPBACK; + loopback.data = -1; + err = ioctl(fd, SIOCETHTOOL, ifr); + TEST_ASSERT_EQUAL_MESSAGE(0, err, strerror(errno)); + TEST_ASSERT_EQUAL_MESSAGE(enable, loopback.data, "loopback was not set"); +} + + +static inline int setup_socket(const struct sockaddr_ll *addr, const char *name) +{ + int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (s < 0) { + perror(name); + TEST_FAIL_MESSAGE("socket creation failed"); + } + + if (addr != NULL) { + if (bind(s, (const struct sockaddr *)addr, sizeof(*addr)) < 0) { + perror(name); + TEST_FAIL_MESSAGE("bind failed"); + } + } + + return s; +} + + +TEST_GROUP(enet); + +TEST_SETUP(enet) +{ + int err; + + srand(time(NULL)); + ctrl_sock = setup_socket(NULL, "ctrl socket"); + + err = ioctl(ctrl_sock, SIOCGIFFLAGS, &ifr); + TEST_ASSERT_EQUAL_MESSAGE(0, err, strerror(errno)); + + ifr.ifr_flags |= IFF_PROMISC | IFF_UP | IFF_RUNNING; + err = ioctl(ctrl_sock, SIOCSIFFLAGS, &ifr); + TEST_ASSERT_EQUAL_MESSAGE(0, err, strerror(errno)); + + err = ioctl(ctrl_sock, SIOCGIFHWADDR, &ifr); + TEST_ASSERT_EQUAL_MESSAGE(0, err, strerror(errno)); + self_hwaddr = ifr.ifr_hwaddr; + + memcpy(dst_addr.sll_addr, self_hwaddr.sa_data, ETH_ALEN); + memcpy(src_addr.sll_addr, self_hwaddr.sa_data, ETH_ALEN); + + err = ioctl(ctrl_sock, SIOCGIFINDEX, &ifr); + TEST_ASSERT_EQUAL_MESSAGE(0, err, strerror(errno)); + dst_addr.sll_ifindex = ifr.ifr_ifindex; + src_addr.sll_ifindex = ifr.ifr_ifindex; + + /* setup send frame header */ + memcpy(send_frame.header.ether_dhost, self_hwaddr.sa_data, ETH_ALEN); + memcpy(send_frame.header.ether_shost, self_hwaddr.sa_data, ETH_ALEN); + memcpy(&send_frame.header.ether_type, _TP_ETHTYPE, sizeof(_TP_ETHTYPE) - 1); +} + +TEST_TEAR_DOWN(enet) +{ + if (send_sock > 0) { + close(send_sock); + send_sock = -1; + } + + if (recv_sock >= 0) { + close(recv_sock); + recv_sock = -1; + } + + if (ctrl_sock >= 0) { + set_iface_loopback(ctrl_sock, &ifr, false); + close(ctrl_sock); + ctrl_sock = -1; + } + + if (dynamic_buf != NULL) { + free(dynamic_buf); + dynamic_buf = NULL; + } +} + +TEST(enet, selftest) +{ + int err; + struct ethtool_test test_config = { + .cmd = ETHTOOL_TEST, + .flags = ETH_TEST_FL_OFFLINE, + .len = 0, + }; + + ifr.ifr_data = (char *)&test_config; + err = ioctl(ctrl_sock, SIOCETHTOOL, &ifr); + if (err < 0 && errno == EOPNOTSUPP) { + TEST_IGNORE_MESSAGE("selftest not supported"); + } + TEST_ASSERT_EQUAL_MESSAGE(0, err, strerror(errno)); + + TEST_ASSERT_EQUAL(0, test_config.flags & ETH_TEST_FL_FAILED); + TEST_ASSERT_NOT_EQUAL(0, test_config.flags & ETH_TEST_FL_OFFLINE); +} + +TEST(enet, one_packet) +{ + set_iface_loopback(ctrl_sock, &ifr, true); + + ssize_t result; + struct sockaddr_ll from_addr; + socklen_t from_addr_len = sizeof(from_addr); + + send_sock = setup_socket(&src_addr, "send socket"); + recv_sock = setup_socket(&src_addr, "recv socket"); + + memcpy(send_frame.payload, TEST_PAYLOAD, TEST_PAYLOAD_LEN); + + result = sendto(send_sock, send_frame.raw_buf, TEST_PACKET_LEN, 0, (struct sockaddr *)&dst_addr, sizeof(dst_addr)); + TEST_ASSERT_EQUAL_MESSAGE(TEST_PACKET_LEN, result, strerror(errno)); + + result = recvfrom(recv_sock, recv_frame.raw_buf, TEST_PACKET_LEN, 0, (struct sockaddr *)&from_addr, &from_addr_len); + TEST_ASSERT_EQUAL_MESSAGE(TEST_PACKET_LEN, result, result < 0 ? strerror(errno) : "length mismatch"); + TEST_ASSERT_EQUAL(sizeof(src_addr), from_addr_len); + TEST_ASSERT_EQUAL_MEMORY(src_addr.sll_addr, from_addr.sll_addr, ETH_ALEN); + TEST_ASSERT_EQUAL_MEMORY(send_frame.raw_buf, recv_frame.raw_buf, TEST_PACKET_LEN); +} + + +TEST(enet, load) +{ + set_iface_loopback(ctrl_sock, &ifr, true); + + const size_t payload_size = 1024; + const size_t total_bytes = 10 * 1024 * 1024; /* 10 MB */ + ssize_t left = total_bytes; + ssize_t result; + + send_sock = setup_socket(&src_addr, "send socket"); + recv_sock = setup_socket(&src_addr, "recv socket"); + + while (left > 0) { + const size_t current_payload_size = min(payload_size, left); + const size_t current_frame_size = current_payload_size + ETH_HLEN; + + make_rand_frame(send_frame.payload, payload_size); + + result = sendto(send_sock, send_frame.raw_buf, current_frame_size, 0, (struct sockaddr *)&dst_addr, sizeof(dst_addr)); + TEST_ASSERT_EQUAL_MESSAGE(current_frame_size, result, strerror(errno)); + + do { + /* filter-out packets not addressed to this device */ + result = recv(recv_sock, recv_frame.raw_buf, current_frame_size, 0); + } while (result != current_frame_size && memcmp(recv_frame.header.ether_dhost, self_hwaddr.sa_data, ETH_HLEN) != 0); + TEST_ASSERT_EQUAL_MESSAGE(current_frame_size, result, strerror(errno)); + TEST_ASSERT_EQUAL_MEMORY(send_frame.raw_buf, recv_frame.raw_buf, current_frame_size); + + left -= current_payload_size; + } +} + +TEST(enet, more_data_than_mtu) +{ + set_iface_loopback(ctrl_sock, &ifr, true); + + const size_t bufsz = 4096; + + dynamic_buf = malloc(bufsz); + TEST_ASSERT_NOT_EQUAL_MESSAGE(NULL, dynamic_buf, "malloc"); + + struct ether_header *header = (void *)dynamic_buf; + memcpy(header->ether_dhost, self_hwaddr.sa_data, ETH_ALEN); + memcpy(header->ether_shost, self_hwaddr.sa_data, ETH_ALEN); + memcpy(&header->ether_type, _TP_ETHTYPE, sizeof(_TP_ETHTYPE) - 1); + make_rand_frame(dynamic_buf + ETH_HLEN, bufsz - ETH_HLEN); + + send_sock = setup_socket(&src_addr, "send_socket"); + + TEST_ASSERT_EQUAL(-1, sendto(send_sock, dynamic_buf, bufsz, 0, (struct sockaddr *)&dst_addr, sizeof(dst_addr))); + TEST_ASSERT_EQUAL(ENOBUFS, errno); +} + +TEST_GROUP_RUNNER(enet) +{ + RUN_TEST_CASE(enet, selftest); + RUN_TEST_CASE(enet, one_packet); + RUN_TEST_CASE(enet, load); + RUN_TEST_CASE(enet, more_data_than_mtu); +} + +static void runner(void) +{ + RUN_TEST_GROUP(enet); +} + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "Usage: test-enet \n"); + return EXIT_FAILURE; + } + size_t len = strlen(argv[1]); + if (len >= IFNAMSIZ) { + fprintf(stderr, "Error: interface name too long\n"); + return EXIT_FAILURE; + } + strcpy(ifr.ifr_name, argv[1]); + return (UnityMain(argc, (const char **)argv, runner) == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/enet/test.yaml b/enet/test.yaml new file mode 100644 index 000000000..1fcd2fdc3 --- /dev/null +++ b/enet/test.yaml @@ -0,0 +1,12 @@ +test: + type: unity + tests: + - name: unit + execute: test-enet en1 + targets: + value: [armv7a7-imx6ull-evk, armv7m7-imxrt106x-evk, ia32-generic-qemu] + + - name: unit + execute: test-enet en2 + targets: + value: [armv7a7-imx6ull-evk] diff --git a/ioctl/Makefile b/ioctl/Makefile index 04f7fc09c..08f9c7136 100644 --- a/ioctl/Makefile +++ b/ioctl/Makefile @@ -3,3 +3,4 @@ LOCAL_LDFLAGS := -lpthread LOCAL_LDFLAGS += -z stack-size=12288 $(eval $(call add_unity_test, test-ioctl)) +$(eval $(call add_unity_test, test-ioctl-nested)) diff --git a/ioctl/test-ioctl-nested.c b/ioctl/test-ioctl-nested.c new file mode 100644 index 000000000..cde2b7399 --- /dev/null +++ b/ioctl/test-ioctl-nested.c @@ -0,0 +1,167 @@ +/* + * Phoenix-RTOS + * + * Tests for nested ioctls: + * - SIOCIFCONF + * + * (nested - passed structure has a pointer + * to arbitrary memory -> needs flattening + * in userspace) + * + * Copyright 2025 Phoenix Systems + * Author: Julian UziembÅ‚o + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "unity_fixture.h" + +#define GET_IFADDRS(fd, ifc_ptr) \ + do { \ + TEST_ASSERT_EQUAL_MESSAGE(0, ioctl(fd, SIOCGIFCONF, (ifc_ptr)), strerror(errno)); \ + (ifc_ptr)->ifc_req = malloc((ifc_ptr)->ifc_len); \ + TEST_ASSERT_NOT_NULL_MESSAGE((ifc_ptr)->ifc_req, strerror(errno)); \ + TEST_ASSERT_EQUAL_MESSAGE(0, ioctl(fd, SIOCGIFCONF, (ifc_ptr)), strerror(errno)); \ + } while (0) + + +static int fd = -1; +static struct ifconf current_ifc; +static int nifaces; + + +TEST_GROUP(test_ioctl_nested); + + +TEST_SETUP(test_ioctl_nested) +{ + fd = socket(AF_INET, SOCK_STREAM, 0); + TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(0, fd, strerror(errno)); +} + + +TEST_TEAR_DOWN(test_ioctl_nested) +{ + if (current_ifc.ifc_req != NULL) { + free(current_ifc.ifc_req); + memset(¤t_ifc, 0, sizeof(current_ifc)); + } +} + + +TEST(test_ioctl_nested, ifconf) +{ + GET_IFADDRS(fd, ¤t_ifc); + nifaces = (current_ifc.ifc_len / sizeof(*current_ifc.ifc_req)); +} + + +TEST(test_ioctl_nested, ifconf_not_enough_space) +{ + struct ifreq ifr = { 0 }; + struct ifconf ifc = { + .ifc_req = &ifr, + .ifc_len = sizeof(ifr), + }; + + int res = ioctl(fd, SIOCGIFCONF, &ifc); + TEST_ASSERT_EQUAL_MESSAGE(0, res, strerror(errno)); + TEST_ASSERT_EQUAL(sizeof(ifr), ifc.ifc_len); + + /* ifr_name should be 3 characters in lwip. + if net stack is ever changed - this should change too */ + TEST_ASSERT_EQUAL(3, strnlen(ifc.ifc_req->ifr_name, IFNAMSIZ)); +} + + +TEST(test_ioctl_nested, ifconf_null_with_size) +{ + struct ifconf ifc = { + .ifc_req = NULL, + .ifc_len = nifaces * sizeof(struct ifreq), + }; + + errno = 0; + int res = ioctl(fd, SIOCGIFCONF, &ifc); + TEST_ASSERT_EQUAL_MESSAGE(-1, res, strerror(errno)); + TEST_ASSERT_EQUAL_MESSAGE(EINVAL, errno, strerror(errno)); + TEST_ASSERT_EQUAL(nifaces * sizeof(struct ifreq), ifc.ifc_len); +} + + +TEST(test_ioctl_nested, ifconf_null_with_smaller_size) +{ + struct ifconf ifc = { + .ifc_req = NULL, + .ifc_len = 1, + }; + + errno = 0; + int res = ioctl(fd, SIOCGIFCONF, &ifc); + TEST_ASSERT_EQUAL_MESSAGE(-1, res, strerror(errno)); + TEST_ASSERT_EQUAL_MESSAGE(EINVAL, errno, strerror(errno)); + TEST_ASSERT_EQUAL(1, ifc.ifc_len); /* size should not be changed */ +} + + +TEST(test_ioctl_nested, ifconf_null_0_size) +{ + struct ifconf ifc = { + .ifc_req = NULL, + .ifc_len = 0, + }; + + int res = ioctl(fd, SIOCGIFCONF, &ifc); + TEST_ASSERT_EQUAL_MESSAGE(0, res, strerror(errno)); + TEST_ASSERT_EQUAL(nifaces * sizeof(struct ifreq), ifc.ifc_len); +} + + +TEST(test_ioctl_nested, ifconf_nonnull_0_size) +{ + struct ifreq ifr = { .ifr_name = { 't', 'e', 's', 't', '\0' } }; + struct ifconf ifc = { + .ifc_req = &ifr, + .ifc_len = 0, + }; + + int res = ioctl(fd, SIOCGIFCONF, &ifc); + TEST_ASSERT_EQUAL_MESSAGE(0, res, strerror(errno)); + TEST_ASSERT_EQUAL(nifaces * sizeof(struct ifreq), ifc.ifc_len); + TEST_ASSERT_EQUAL_MEMORY("test", ifc.ifc_req->ifr_name, strnlen(ifc.ifc_req->ifr_name, IFNAMSIZ)); +} + + +TEST_GROUP_RUNNER(test_ioctl_nested) +{ + RUN_TEST_CASE(test_ioctl_nested, ifconf); + RUN_TEST_CASE(test_ioctl_nested, ifconf_not_enough_space); + RUN_TEST_CASE(test_ioctl_nested, ifconf_null_with_smaller_size); + RUN_TEST_CASE(test_ioctl_nested, ifconf_null_with_size); + RUN_TEST_CASE(test_ioctl_nested, ifconf_null_0_size); + RUN_TEST_CASE(test_ioctl_nested, ifconf_nonnull_0_size); +} + + +void runner(void) +{ + RUN_TEST_GROUP(test_ioctl_nested); +} + + +int main(int argc, char *argv[]) +{ + return (UnityMain(argc, (const char **)argv, runner) == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/ioctl/test-ioctl.c b/ioctl/test-ioctl.c index cf3785b96..6885cbd91 100644 --- a/ioctl/test-ioctl.c +++ b/ioctl/test-ioctl.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -158,7 +157,7 @@ TEST(ioctl, regular_file) int fdReg = open(PATH_REG, O_RDWR | O_CREAT | O_TRUNC, S_IFREG); TEST_ASSERT_NOT_EQUAL_INT(-1, fdReg); errno = 0; - int ret = ioctl(fdReg, TEST_IOCTL_SIG, NULL); + int ret = ioctl(fdReg, TEST_IOCTL_SIG); TEST_ASSERT_NOT_EQUAL_INT(0, ret); close(fdReg); remove(PATH_REG); diff --git a/ioctl/test-route.py b/ioctl/test-route.py new file mode 100644 index 000000000..fb49dccdb --- /dev/null +++ b/ioctl/test-route.py @@ -0,0 +1,89 @@ +import psh.tools.psh as psh + + +ROUTE_ROW0 = "%-15s %-15s %-15s %-5s %-6s %-6s %3s %s" % ( + "Destination", + "Gateway", + "Genmask", + "Flags", + "Metric", + "Ref", + "Use", + "Iface", +) +DEFAULT_ROUTE_ARGS_LO0 = ( + "127.0.0.0", + "127.0.0.1", + "255.0.0.0", + "UG", + 0, + 0, + 0, + "lo0", +) + + +def format_route_row( + destination_addr, gateway_addr, mask, sFlags, metric, refcnt, use, iface +): + return "%-15s %-15s %-15s %-5s %-6d %-6d %3d %s" % ( + destination_addr, + gateway_addr, + mask, + sFlags, + metric, + refcnt, + use, + iface, + ) + + +def match_literal_line(line): + return rf"{line}[\r\n]+" + + +def match_anything_until(until): + return rf"[\s\S]*?({until}){{1}}" + + +def route_assert_matches(pexpect_proc, expected_lines): + pexpect_proc.sendline("route") + pexpect_proc.expect(r"".join(expected_lines)) + pexpect_proc.expect_exact("(psh)%") + + +@psh.run +def harness(p): + expected_lines = [ + match_literal_line("route"), + match_literal_line(ROUTE_ROW0), + match_anything_until(format_route_row(*DEFAULT_ROUTE_ARGS_LO0)), + ] + route_assert_matches(p, expected_lines) + + # lo0 should be always available + psh.assert_cmd( + p, "route add 123.123.123.123 netmask 255.255.255.255 dev lo0" + ) + expected_lines.insert( + 2, + match_literal_line( + format_route_row( + "123.123.123.123", + "127.0.0.1", + "255.255.255.255", + "U", + 100, + 0, + 0, + "lo0", + ) + ), + ) + route_assert_matches(p, expected_lines) + + psh.assert_cmd( + p, "route del 123.123.123.123 netmask 255.255.255.255 dev lo0" + ) + expected_lines.pop(2) + route_assert_matches(p, expected_lines) diff --git a/ioctl/test.yaml b/ioctl/test.yaml index 31ba22740..ea25da601 100644 --- a/ioctl/test.yaml +++ b/ioctl/test.yaml @@ -1,7 +1,21 @@ test: - type: unity - tests: - - name: unit - execute: test-ioctl - targets: - exclude: [host-generic-pc] + # type: unity + tests: + - name: test-ioctl + type: unity + execute: test-ioctl + targets: + exclude: [host-generic-pc] + + # nested ioctls are all socket-related (at least for now), so enable only on targets with lwIP + + - name: test-ioctl-nested + type: unity + execute: test-ioctl-nested + targets: + value: [ia32-generic-qemu, armv7a7-imx6ull-evk, armv7m7-imxrt106x-evk, armv7m7-imxrt117x-evk, sparcv8leon-gr740-mini, riscv64-gr765-vcu118, riscv64-grfpga-artya7] + + - name: test-route + harness: test-route.py + targets: + value: [ia32-generic-qemu, armv7a7-imx6ull-evk, armv7m7-imxrt106x-evk, armv7m7-imxrt117x-evk, sparcv8leon-gr740-mini, riscv64-gr765-vcu118, riscv64-grfpga-artya7] diff --git a/libc/scanf-advanced/stdio_scanf_advanced.c b/libc/scanf-advanced/stdio_scanf_advanced.c index 82371fe13..ed69a057c 100644 --- a/libc/scanf-advanced/stdio_scanf_advanced.c +++ b/libc/scanf-advanced/stdio_scanf_advanced.c @@ -708,15 +708,6 @@ TEST(stdio_scanf_cspn, c_ascii) TEST_ASSERT_EQUAL_INT(1, fscanf(filep, "%c", &c)); TEST_ASSERT_EQUAL_CHAR(i, c); - /* - * This fseek is used because of issue #639 - * https://github.com/phoenix-rtos/phoenix-rtos-project/issues/639 - */ - -#ifdef __phoenix__ - fseek(filep, i, SEEK_SET); -#endif - c = 0; TEST_ASSERT_EQUAL_INT(1, test_vsscanfWrapper(&buff[i - 1], "%c", &c)); TEST_ASSERT_EQUAL_CHAR(i, c); diff --git a/libtrace/Makefile b/libtrace/Makefile new file mode 100644 index 000000000..71b348107 --- /dev/null +++ b/libtrace/Makefile @@ -0,0 +1,6 @@ +NAME := test-libtrace +SRCS := $(wildcard $(call my-dir)*.c) +DEP_LIBS := unity +LIBS := libtrace + +include $(binary.mk) diff --git a/libtrace/main.c b/libtrace/main.c new file mode 100644 index 000000000..5ef5f097d --- /dev/null +++ b/libtrace/main.c @@ -0,0 +1,144 @@ +/* + * Phoenix-RTOS + * + * libtrace tests + * + * Copyright 2025 by Phoenix Systems + * Author: Adam Greloch + * + * %LICENSE% + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unity_fixture.h" + +/* assumes TMP_DIR path is an (existing) ramdisk - otherwise tests will take *much* longer */ +#define TMP_DIR "/tmp" +#define BUF_SIZE (1 << 16) + +#define FILE_NOT_DIR_PATH (TMP_DIR "/file") + + +TEST_GROUP(test_libtrace); + + +TEST_SETUP(test_libtrace) +{ + struct stat st; + errno = 0; + if (stat(TMP_DIR, &st) < 0) { + FAIL(TMP_DIR " not found"); + } + + int fd = open(FILE_NOT_DIR_PATH, (O_WRONLY | O_CREAT | O_TRUNC), DEFFILEMODE); + if (fd >= 0) { + close(fd); + } + else { + FAIL("open"); + } +} + + +TEST_TEAR_DOWN(test_libtrace) +{ + remove(FILE_NOT_DIR_PATH); +} + + +TEST(test_libtrace, test_libtrace_err) +{ + trace_ctx_t ctx, og_ctx; + + TEST_ASSERT_EQUAL_INT(-EINVAL, trace_init(NULL, true)); + + TEST_ASSERT_EQUAL_INT(0, trace_init(&ctx, true)); + memcpy(&og_ctx, &ctx, sizeof(trace_ctx_t)); + + TEST_ASSERT_EQUAL_INT(-ENOENT, trace_record(&ctx, 100, 1000, BUF_SIZE, FILE_NOT_DIR_PATH)); + TEST_ASSERT_EQUAL_INT(0, memcmp(&ctx, &og_ctx, sizeof(trace_ctx_t))); + + /* no corresponding trace_start() */ + TEST_ASSERT_EQUAL_INT(-EINVAL, trace_stopAndGather(&ctx, BUF_SIZE, TMP_DIR "/libtrace_err")); + TEST_ASSERT_EQUAL_INT(0, memcmp(&ctx, &og_ctx, sizeof(trace_ctx_t))); + + TEST_ASSERT_EQUAL_INT(-EINVAL, trace_record(&ctx, 100, 1000, 0, TMP_DIR "/libtrace_err")); + TEST_ASSERT_EQUAL_INT(0, memcmp(&ctx, &og_ctx, sizeof(trace_ctx_t))); + + TEST_ASSERT_EQUAL_INT(0, trace_start(&ctx)); + TEST_ASSERT_EQUAL_INT(-EINVAL, trace_stopAndGather(&ctx, 0, TMP_DIR "/libtrace_err")); + TEST_ASSERT_EQUAL_INT(0, memcmp(&ctx, &og_ctx, sizeof(trace_ctx_t))); + + /* + * on EINVAL, trace_stopAndGather should still be able to stop the trace + * if trace_start(&ctx) succeeds, it means we have started a new trace + */ + TEST_ASSERT_EQUAL_INT(0, trace_start(&ctx)); + TEST_ASSERT_EQUAL_INT(0, trace_stopAndGather(&ctx, BUF_SIZE, TMP_DIR "/libtrace_err")); + TEST_ASSERT_EQUAL_INT(0, memcmp(&ctx, &og_ctx, sizeof(trace_ctx_t))); +} + + +TEST(test_libtrace, test_libtrace_start_stop) +{ + trace_ctx_t ctx, og_ctx; + + TEST_ASSERT_EQUAL_INT(0, trace_init(&ctx, true)); + memcpy(&og_ctx, &ctx, sizeof(trace_ctx_t)); + + for (size_t rep = 0; rep < 3; rep++) { + TEST_ASSERT_EQUAL_INT(0, trace_start(&ctx)); + + struct timespec ts; + for (size_t i = 0; i < 100; i++) { + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + } + + /* may print a warning about read buffer utilization - it is ok */ + TEST_ASSERT_EQUAL_INT(0, trace_stopAndGather(&ctx, BUF_SIZE, TMP_DIR "/libtrace_start_stop")); + TEST_ASSERT_EQUAL_INT(0, memcmp(&ctx, &og_ctx, sizeof(trace_ctx_t))); + } +} + + +TEST(test_libtrace, test_libtrace_record) +{ + trace_ctx_t ctx, og_ctx; + + TEST_ASSERT_EQUAL_INT(0, trace_init(&ctx, true)); + memcpy(&og_ctx, &ctx, sizeof(trace_ctx_t)); + + for (size_t rep = 0; rep < 3; rep++) { + TEST_ASSERT_EQUAL_INT(0, trace_record(&ctx, 100, 100, BUF_SIZE, TMP_DIR "/libtrace_record")); + TEST_ASSERT_EQUAL_INT(0, memcmp(&ctx, &og_ctx, sizeof(trace_ctx_t))); + } +} + + +TEST_GROUP_RUNNER(test_libtrace) +{ + RUN_TEST_CASE(test_libtrace, test_libtrace_err); + RUN_TEST_CASE(test_libtrace, test_libtrace_start_stop); + RUN_TEST_CASE(test_libtrace, test_libtrace_record); +} + + +void runner(void) +{ + RUN_TEST_GROUP(test_libtrace); +} + + +int main(int argc, char *argv[]) +{ + return UnityMain(argc, (const char **)argv, runner) == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/libtrace/test.yaml b/libtrace/test.yaml new file mode 100644 index 000000000..607cd57a4 --- /dev/null +++ b/libtrace/test.yaml @@ -0,0 +1,15 @@ +test: + type: unity + tests: + - name: test-libtrace + execute: test-libtrace + targets: + # only for targets with: 1) rootfs 2) tracing buffer-mem support + value: [ + ia32-generic-qemu, + riscv64-generic-qemu, + armv7a9-zynq7000-zedboard, + armv7a9-zynq7000-qemu, + armv7a7-imx6ull-evk, + aarch64a53-zynqmp-qemu + ] diff --git a/lsb_vsx/lsb_vsx.py b/lsb_vsx/lsb_vsx.py index 30a5a5b21..dfd6738ab 100644 --- a/lsb_vsx/lsb_vsx.py +++ b/lsb_vsx/lsb_vsx.py @@ -27,13 +27,13 @@ def get_subtests_numbers(name: str) -> Iterator[int]: def harness(dut: Dut, ctx: TestContext, result: TestResult, **kwargs) -> TestResult: - start = r"(?m)^(.+?\|){2}TCM Start\r?\n$" - tc_start = r"(?m)^(.+?\|){2}TP Start\r?\n$" - status = r"(?m)^(.+?\|){2}(?PPASS|FAIL|UNRESOLVED|UNSUPPORTED|NOTINUSE|UNTESTED|UNINITIATED|NORESULT|INVALID RESULT)\r?\n$" # noqa: E501 - tc_end = r"(?m)^(.+?\|){2}IC End\r?\n$" - final = r"(?m)^(.+?\|){2}TC End.+?\r?\n$" - msg_line = r"(?m)^(.*?\|){2}(?P.+?)\r?\n$" - vsx_error = r"(?m)^.*?(?Perror: .+?)\r?\n$" + start = r"(?m)^(.+?\|){2}TCM Start\r?$\n" + tc_start = r"(?m)^(.+?\|){2}TP Start\r?$\n" + status = r"(?m)^(.+?\|){2}(?PPASS|FAIL|UNRESOLVED|UNSUPPORTED|NOTINUSE|UNTESTED|UNINITIATED|NORESULT|INVALID RESULT)\r?$\n" # noqa: E501 + tc_end = r"(?m)^(.+?\|){2}IC End\r?$\n" + final = r"(?m)^(.+?\|){2}TC End.+?\r?$\n" + msg_line = r"(?m)^(.*?\|){2}(?P.+?)\r?$\n" + vsx_error = r"(?m)^.*?(?Perror: .+?)\r?$\n" stats = {"OK": 0, "FAIL": 0, "SKIP": 0} vsx_error_msg = "" diff --git a/lsb_vsx/test.yaml b/lsb_vsx/test.yaml index a13739b53..2c7580f19 100644 --- a/lsb_vsx/test.yaml +++ b/lsb_vsx/test.yaml @@ -901,7 +901,8 @@ test: kwargs: testcase_count: 30 - # 1: system doesn't support users group + # 1: system doesn't support users group + # additionally: https://github.com/phoenix-rtos/phoenix-rtos-project/issues/1245 - name: T.fstat execute: run-test /tset/POSIX.os/files/fstat/T.fstat{2-4} kwargs: diff --git a/meterfs/Makefile b/meterfs/Makefile index 436847cdb..666217e19 100644 --- a/meterfs/Makefile +++ b/meterfs/Makefile @@ -29,3 +29,4 @@ $(eval $(call add_meterfs_test, test_meterfs_allocate)) $(eval $(call add_meterfs_test, test_meterfs_openclose)) $(eval $(call add_meterfs_test, test_meterfs_writeread)) $(eval $(call add_meterfs_test, test_meterfs_miscellaneous)) +$(eval $(call add_meterfs_test, test_meterfs_migration)) diff --git a/meterfs/file.h b/meterfs/file.h index 6e5784f2a..56eb4deeb 100644 --- a/meterfs/file.h +++ b/meterfs/file.h @@ -43,6 +43,9 @@ int file_allocate(const char *name, size_t sectors, size_t filesz, size_t record int file_resize(id_t fid, size_t filesz, size_t recordsz); +int file_reset(id_t fid); + + int file_getInfo(id_t fid, size_t *sectors, size_t *filesz, size_t *recordsz, size_t *recordcnt); diff --git a/meterfs/file_pc.c b/meterfs/file_pc.c index 2e7f9903f..8ccd4444b 100644 --- a/meterfs/file_pc.c +++ b/meterfs/file_pc.c @@ -109,6 +109,18 @@ int file_resize(id_t fid, size_t filesz, size_t recordsz) } +int file_reset(id_t fid) +{ + meterfs_i_devctl_t iptr; + meterfs_o_devctl_t optr; + + iptr.type = meterfs_reset; + iptr.id = fid; + + return hostflashsrv_devctl(&iptr, &optr); +} + + int file_getInfo(id_t fid, size_t *sectors, size_t *filesz, size_t *recordsz, size_t *recordcnt) { meterfs_i_devctl_t iptr; diff --git a/meterfs/file_phx.c b/meterfs/file_phx.c index 2fefde393..c476d31bc 100644 --- a/meterfs/file_phx.c +++ b/meterfs/file_phx.c @@ -189,6 +189,22 @@ int file_resize(id_t fid, size_t filesz, size_t recordsz) } +int file_reset(id_t fid) +{ + msg_t msg; + meterfs_i_devctl_t *iptr = (meterfs_i_devctl_t *)msg.i.raw; + + file_prepareDevCtl(&msg); + + iptr->type = meterfs_reset; + iptr->id = fid; + + TEST_ASSERT_EQUAL(0, msgSend(meterfs.port, &msg)); + + return msg.o.err; +} + + int file_getInfo(id_t fid, size_t *sectors, size_t *filesz, size_t *recordsz, size_t *recordcnt) { msg_t msg; diff --git a/meterfs/test_meterfs_allocate.c b/meterfs/test_meterfs_allocate.c index 7f5e5dcfb..6a463f84a 100644 --- a/meterfs/test_meterfs_allocate.c +++ b/meterfs/test_meterfs_allocate.c @@ -38,7 +38,7 @@ TEST_TEAR_DOWN(meterfs_allocate) */ TEST(meterfs_allocate, big_file) { - file_info_t info = { ((fsInfo.sz + fsInfo.sectorsz) / fsInfo.sectorsz) + 1u, fsInfo.sz + fsInfo.sectorsz, fsInfo.sectorsz / 100u, 0 }; + file_info_t info = { ((fsInfo.sz + fsInfo.sectorsz) / fsInfo.sectorsz) + 1U, fsInfo.sz + fsInfo.sectorsz, fsInfo.sectorsz / 100U, 0 }; TEST_ASSERT_EQUAL(-EINVAL, file_allocate("file0", info.sectors, info.filesz, info.recordsz)); } @@ -50,17 +50,17 @@ TEST(meterfs_allocate, many_files) size_t i, availableSectors; char fileName[32]; - availableSectors = (fsInfo.sz / fsInfo.sectorsz) - 1u; + availableSectors = (fsInfo.sz / fsInfo.sectorsz) - 1U; - for (i = 0; i < (fsInfo.fileLimit + 10u); ++i) { + for (i = 0; i < (fsInfo.fileLimit + 10U); ++i) { (void)memset(fileName, 0, sizeof(fileName)); (void)snprintf(fileName, sizeof(fileName), "file%zu", i); - if ((i < fsInfo.fileLimit) && (availableSectors >= 2u)) { - TEST_ASSERT_EQUAL_MESSAGE(0, file_allocate(fileName, 2, fsInfo.sectorsz / 2u, fsInfo.sectorsz / 200u), fileName); - availableSectors -= 2u; + if ((i < fsInfo.fileLimit) && (availableSectors >= 2U)) { + TEST_ASSERT_EQUAL_MESSAGE(0, file_allocate(fileName, 2, fsInfo.sectorsz / 2U, fsInfo.sectorsz / 200U), fileName); + availableSectors -= 2U; } else { - TEST_ASSERT_EQUAL_MESSAGE(-ENOMEM, file_allocate(fileName, 2, fsInfo.sectorsz / 2u, fsInfo.sectorsz / 200u), fileName); + TEST_ASSERT_EQUAL_MESSAGE(-ENOMEM, file_allocate(fileName, 2, fsInfo.sectorsz / 2U, fsInfo.sectorsz / 200U), fileName); } } } @@ -69,16 +69,16 @@ TEST(meterfs_allocate, many_files) /* Test case of allocating files with not allowed name length. */ TEST(meterfs_allocate, file_name_len) { - TEST_ASSERT_EQUAL(-EINVAL, file_allocate("file01234", 2, fsInfo.sectorsz / 2u, fsInfo.sectorsz / 100u)); - TEST_ASSERT_EQUAL(0, file_allocate("file0123", 2, fsInfo.sectorsz / 2u, fsInfo.sectorsz / 100u)); - TEST_ASSERT_EQUAL(-EINVAL, file_allocate("", 2, fsInfo.sectorsz / 2u, fsInfo.sectorsz / 100u)); + TEST_ASSERT_EQUAL(-EINVAL, file_allocate("file01234", 2, fsInfo.sectorsz / 2U, fsInfo.sectorsz / 100U)); + TEST_ASSERT_EQUAL(0, file_allocate("file0123", 2, fsInfo.sectorsz / 2U, fsInfo.sectorsz / 100U)); + TEST_ASSERT_EQUAL(-EINVAL, file_allocate("", 2, fsInfo.sectorsz / 2U, fsInfo.sectorsz / 100U)); } /* Test case of allocating file with records bigger than flash sector size. */ TEST(meterfs_allocate, big_record) { - TEST_ASSERT_EQUAL(0, file_allocate("file0", 6, fsInfo.sectorsz * 5u, fsInfo.sectorsz + 1u)); + TEST_ASSERT_EQUAL(0, file_allocate("file0", 6, fsInfo.sectorsz * 5U, fsInfo.sectorsz + 1U)); } @@ -86,18 +86,18 @@ TEST(meterfs_allocate, big_record) TEST(meterfs_allocate, var_init_args) { TEST_ASSERT_EQUAL(-EINVAL, file_allocate("file0", 0, 0, 0)); - TEST_ASSERT_EQUAL(-EINVAL, file_allocate("file1", 0, fsInfo.sectorsz / 2u, fsInfo.sectorsz / 100u)); - TEST_ASSERT_EQUAL(-EINVAL, file_allocate("file2", 1, fsInfo.sectorsz / 2u, fsInfo.sectorsz / 100u)); - TEST_ASSERT_EQUAL(-EINVAL, file_allocate("file3", 2, fsInfo.sectorsz / 100u, fsInfo.sectorsz / 2u)); - TEST_ASSERT_EQUAL(-EINVAL, file_allocate("file4", 3, fsInfo.sectorsz * 100u, fsInfo.sectorsz / 100u)); - TEST_ASSERT_EQUAL(-EINVAL, file_allocate("file5", 7, fsInfo.sectorsz / 2u, 0)); - - TEST_ASSERT_EQUAL(0, file_allocate("file6", 4, fsInfo.sectorsz / 100u, fsInfo.sectorsz / 100u)); - TEST_ASSERT_EQUAL(0, file_allocate("file7", 6, fsInfo.sectorsz / 2u, fsInfo.sectorsz / 10u)); - TEST_ASSERT_EQUAL(0, file_allocate("file8", 8, fsInfo.sectorsz / 100u, fsInfo.sectorsz / 200u)); - TEST_ASSERT_EQUAL(0, file_allocate("file9", 12, fsInfo.sectorsz / 200u, fsInfo.sectorsz / 400u)); - TEST_ASSERT_EQUAL(0, file_allocate("file10", 10, fsInfo.sectorsz / 2u, fsInfo.sectorsz / 100u)); - TEST_ASSERT_EQUAL(0, file_allocate("file11", 9, fsInfo.sectorsz / 2u, fsInfo.sectorsz / 100u)); + TEST_ASSERT_EQUAL(-EINVAL, file_allocate("file1", 0, fsInfo.sectorsz / 2U, fsInfo.sectorsz / 100U)); + TEST_ASSERT_EQUAL(-EINVAL, file_allocate("file2", 1, fsInfo.sectorsz / 2U, fsInfo.sectorsz / 100U)); + TEST_ASSERT_EQUAL(-EINVAL, file_allocate("file3", 2, fsInfo.sectorsz / 100U, fsInfo.sectorsz / 2U)); + TEST_ASSERT_EQUAL(-EINVAL, file_allocate("file4", 3, fsInfo.sectorsz * 100U, fsInfo.sectorsz / 100U)); + TEST_ASSERT_EQUAL(-EINVAL, file_allocate("file5", 7, fsInfo.sectorsz / 2U, 0)); + + TEST_ASSERT_EQUAL(0, file_allocate("file6", 4, fsInfo.sectorsz / 100U, fsInfo.sectorsz / 100U)); + TEST_ASSERT_EQUAL(0, file_allocate("file7", 6, fsInfo.sectorsz / 2U, fsInfo.sectorsz / 10U)); + TEST_ASSERT_EQUAL(0, file_allocate("file8", 8, fsInfo.sectorsz / 100U, fsInfo.sectorsz / 200U)); + TEST_ASSERT_EQUAL(0, file_allocate("file9", 12, fsInfo.sectorsz / 200U, fsInfo.sectorsz / 400U)); + TEST_ASSERT_EQUAL(0, file_allocate("file10", 10, fsInfo.sectorsz / 2U, fsInfo.sectorsz / 100U)); + TEST_ASSERT_EQUAL(0, file_allocate("file11", 9, fsInfo.sectorsz / 2U, fsInfo.sectorsz / 100U)); } diff --git a/meterfs/test_meterfs_migration.c b/meterfs/test_meterfs_migration.c new file mode 100644 index 000000000..b1720f434 --- /dev/null +++ b/meterfs/test_meterfs_migration.c @@ -0,0 +1,210 @@ +/* + * Phoenix-RTOS + * + * Meterfs migration test + * + * Takes in a path to the meterfs partition image in pre-v1 meterfs format then + * initializes v1 meterfs on it to provoke migration. + * + * The migration is tested by iterating over fault injection scenarios. In each + * iteration, the migration is attempted first with parametrized injected + * faults. If it succeeds, the iteration ends, otherwise a second migration + * attempt is performed with no faults injected - this time the migration must + * recover and succeed. + * + * Copyright 2026 Phoenix Systems + * Author: Adam Greloch + * + * %LICENSE% + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "file.h" + +#if !defined(METERFS_DEBUG_UTILS) || !METERFS_DEBUG_UTILS +#error "METERFS_DEBUG_UTILS must be enabled for the migration test" +#endif + +#define BUF_SIZE 4096 + +static struct { + char *meterfsPath; + char buf[BUF_SIZE]; +} common; + +#define EXIT_REBOOT_TRIGGER 42 + +#define WRITE_TRIGGER_MAX 64 +#define WRITE_TRIGGER_STEP 16 + +#define REBOOT_TRIGGER_MAX 200 +#define REBOOT_TRIGGER_STEP 1 + +#define TMPDIR_PATH "/tmp" +#define TMPFILE_PATH (TMPDIR_PATH "/meterfs_migration_test") + + +TEST_GROUP(meterfs_migration); + + +TEST_SETUP(meterfs_migration) +{ + if (mkdir(TMPDIR_PATH, 0755) == -1) { + if (errno != EEXIST) { + FAIL("mkdir"); + } + } +} + + +TEST_TEAR_DOWN(meterfs_migration) +{ +} + + +static int copyFile(const char *path1, const char *path2) +{ + int inFd, outFd; + + inFd = open(path1, O_RDONLY); + if (inFd < 0) { + return -1; + } + + struct stat st; + if (fstat(inFd, &st) < 0) { + close(inFd); + return -1; + } + + outFd = open(path2, O_WRONLY | O_CREAT | O_TRUNC, st.st_mode); + if (outFd < 0) { + close(inFd); + return -1; + } + + off_t offs = 0; + ssize_t sent; + int err = 0; + + while (offs < st.st_size) { + sent = sendfile(outFd, inFd, &offs, st.st_size - offs); + if (sent <= 0) { + err = -1; + break; + } + } + + close(inFd); + close(outFd); + + return err; +} + + +static void exitOnRebootTrigger(void) +{ + exit(EXIT_REBOOT_TRIGGER); +} + + +static void initMeterfs(int unreliableWriteTrigger, int rebootTrigger) +{ + meterfs_debugCtx_t debugCtx = { + .rebootTrigger = rebootTrigger, + .unreliableWriteTrigger = unreliableWriteTrigger, + .dryErase = true, + .onRebootCb = exitOnRebootTrigger, + }; + + hostflashsrv_setDebugCtx(&debugCtx); + + if (copyFile(common.meterfsPath, TMPFILE_PATH) != 0) { + exit(EXIT_FAILURE); + } + + if (file_init(TMPFILE_PATH) != 0) { + exit(EXIT_FAILURE); + } +} + + +static int fork_migrate(int unreliableWriteTrigger, int rebootTrigger) +{ + pid_t pid, wpid; + int status; + + pid = fork(); + if (pid < 0) { + FAIL("fork"); + } + else if (pid == 0) { + initMeterfs(unreliableWriteTrigger, rebootTrigger); + exit(EXIT_SUCCESS); + } + else { + wpid = wait(&status); + if (wpid < 0) { + FAIL("wait"); + } + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } + else { + FAIL("unexpected wait status"); + } + } +} + + +TEST(meterfs_migration, test_migration) +{ + for (int wt = 0; wt < WRITE_TRIGGER_MAX; wt += WRITE_TRIGGER_STEP) { + for (int rt = 0; rt < REBOOT_TRIGGER_MAX; rt += REBOOT_TRIGGER_STEP) { + int exit = fork_migrate(wt, rt); + TEST_ASSERT_NOT_EQUAL(exit, EXIT_FAILURE); + + if (exit == EXIT_REBOOT_TRIGGER) { + exit = fork_migrate(0, 0); + } + + TEST_ASSERT_EQUAL(exit, EXIT_SUCCESS); + } + } +} + + +TEST_GROUP_RUNNER(meterfs_migration) +{ + RUN_TEST_CASE(meterfs_migration, test_migration); +} + + +void runner(void) +{ + RUN_TEST_GROUP(meterfs_migration); +} + + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + (void)printf("Usage: %s METERFS_TO_MIGRATE_PATH\n", argv[0]); + return 1; + } + + common.meterfsPath = argv[1]; + + return (UnityMain(argc, (const char **)argv, runner) == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/meterfs/test_meterfs_miscellaneous.c b/meterfs/test_meterfs_miscellaneous.c index f38289040..b2048fd0e 100644 --- a/meterfs/test_meterfs_miscellaneous.c +++ b/meterfs/test_meterfs_miscellaneous.c @@ -63,7 +63,7 @@ TEST_TEAR_DOWN(meterfs_miscellaneous) TEST(meterfs_miscellaneous, resize_getinfo) { file_info_t info; - file_info_t pattern = { 4, fsInfo.sectorsz / 2u, fsInfo.sectorsz / 200u, 0 }; + file_info_t pattern = { 4, fsInfo.sectorsz / 2U, fsInfo.sectorsz / 200U, 0 }; common.fd = common_preallocOpenFile("file0", pattern.sectors, pattern.filesz, pattern.recordsz); TEST_ASSERT_EQUAL(0, file_getInfo(common.fd, &info.sectors, &info.filesz, &info.recordsz, &info.recordcnt)); @@ -92,10 +92,10 @@ TEST(meterfs_miscellaneous, resize_getinfo) /* Test case of resizing file to size bigger than allowed by sectors num. */ TEST(meterfs_miscellaneous, resize_bigger) { - file_info_t pattern = { 2, fsInfo.sectorsz / 2u, fsInfo.sectorsz / 200u, 0 }; + file_info_t pattern = { 2, fsInfo.sectorsz / 2U, fsInfo.sectorsz / 200U, 0 }; common.fd = common_preallocOpenFile("file0", pattern.sectors, pattern.filesz, pattern.recordsz); - pattern.filesz = 2u * fsInfo.sectorsz; + pattern.filesz = 2U * fsInfo.sectorsz; pattern.recordsz *= 2; TEST_ASSERT_EQUAL(-EINVAL, file_resize(common.fd, pattern.filesz, pattern.recordsz)); @@ -106,7 +106,7 @@ TEST(meterfs_miscellaneous, resize_bigger) /* Test case of using lookup multiple times in a row. */ TEST(meterfs_miscellaneous, multi_lookup) { - file_info_t info = { 2, fsInfo.sectorsz / 2u, fsInfo.sectorsz / 200u, 0 }; + file_info_t info = { 2, fsInfo.sectorsz / 2U, fsInfo.sectorsz / 200U, 0 }; const char *name = "file0"; int i; diff --git a/meterfs/test_meterfs_writeread.c b/meterfs/test_meterfs_writeread.c index e24d00f00..9eeb1ccca 100644 --- a/meterfs/test_meterfs_writeread.c +++ b/meterfs/test_meterfs_writeread.c @@ -39,7 +39,7 @@ static void turnCheck(int fd, file_info_t *info, char *bufftx, char *buffrx, uns { unsigned int i; - if ((iterNum >= 10000u) || (info->recordsz < 5u)) { + if ((iterNum >= 10000U) || (info->recordsz < 5U)) { return; } @@ -51,9 +51,9 @@ static void turnCheck(int fd, file_info_t *info, char *bufftx, char *buffrx, uns TEST_ASSERT_EQUAL_HEX8_ARRAY(bufftx, buffrx, info->recordsz); - if (info->recordsz > 2u) { - (void)memset(buffrx + 1, 'x', info->recordsz - 2u); - TEST_ASSERT_EQUAL(info->recordsz - 2u, file_read(fd, 1, buffrx + 1, info->recordsz - 2u)); + if (info->recordsz > 2U) { + (void)memset(buffrx + 1, 'x', info->recordsz - 2U); + TEST_ASSERT_EQUAL(info->recordsz - 2U, file_read(fd, 1, buffrx + 1, info->recordsz - 2U)); TEST_ASSERT_EQUAL_HEX8_ARRAY(bufftx, buffrx, info->recordsz); } } @@ -81,16 +81,16 @@ TEST_TEAR_DOWN(meterfs_writeread) TEST(meterfs_writeread, small_records) { size_t i, writeLen; - file_info_t info = { ((5u * 255u) / fsInfo.sectorsz) + 2u, 5 * 255, 5, 0 }; + file_info_t info = { ((5U * 255U) / fsInfo.sectorsz) + 2U, 5 * 255, 5, 0 }; common.fd = common_preallocOpenFile("file0", info.sectors, info.filesz, info.recordsz); - for (i = 0; i < 255u; ++i) { + for (i = 0; i < 255U; ++i) { (void)snprintf(common.buffMsg, sizeof(common.buffMsg), "iter=%zu", i); - writeLen = i % (info.recordsz + 1u); - (i % 2u) ? (void)snprintf(common.pattern, sizeof(common.pattern), "aaaaa") : (void)snprintf(common.pattern, sizeof(common.pattern), "zzzzz"); + writeLen = i % (info.recordsz + 1U); + (i % 2U) ? (void)snprintf(common.pattern, sizeof(common.pattern), "aaaaa") : (void)snprintf(common.pattern, sizeof(common.pattern), "zzzzz"); - if (writeLen != 0u) { + if (writeLen != 0U) { TEST_ASSERT_EQUAL_MESSAGE(info.recordsz, file_write(common.fd, common.pattern, writeLen), common.buffMsg); common_readContent(common.fd, info.recordcnt * info.recordsz, common.buffRec, info.recordsz, common.pattern, writeLen, common.buffMsg); info.recordcnt++; @@ -115,9 +115,9 @@ TEST(meterfs_writeread, file_overflow) common.fd = common_preallocOpenFile("file0", info.sectors, info.filesz, info.recordsz); - for (i = 0; i < 255u; ++i) { + for (i = 0; i < 255U; ++i) { (void)snprintf(common.buffMsg, sizeof(common.buffMsg), "iter=%zu", i); - (i % 2u) ? (void)snprintf(common.pattern, sizeof(common.pattern), "aaaaa") : (void)snprintf(common.pattern, sizeof(common.pattern), "zzzzz"); + (i % 2U) ? (void)snprintf(common.pattern, sizeof(common.pattern), "aaaaa") : (void)snprintf(common.pattern, sizeof(common.pattern), "zzzzz"); TEST_ASSERT_EQUAL_MESSAGE(info.recordsz, file_write(common.fd, common.pattern, info.recordsz), common.buffMsg); @@ -126,7 +126,7 @@ TEST(meterfs_writeread, file_overflow) common_readContent(common.fd, 0, common.buffRec, info.recordsz, common.pattern, info.recordsz, common.buffMsg); } else { - (i % 2u) ? (void)snprintf(common.pattern, sizeof(common.pattern), "zzzzz") : (void)snprintf(common.pattern, sizeof(common.pattern), "aaaaa"); + (i % 2U) ? (void)snprintf(common.pattern, sizeof(common.pattern), "zzzzz") : (void)snprintf(common.pattern, sizeof(common.pattern), "aaaaa"); common_readContent(common.fd, 0, common.buffRec, info.recordsz, common.pattern, info.recordsz, common.buffMsg); } @@ -141,21 +141,21 @@ TEST(meterfs_writeread, file_overflow) TEST(meterfs_writeread, big_records) { size_t i, writeLen; - file_info_t info = { ((5u * 255u) / fsInfo.sectorsz) + 2u, 2 * 255, 2, 0 }; + file_info_t info = { ((5U * 255U) / fsInfo.sectorsz) + 2U, 2 * 255, 2, 0 }; common.fd = common_preallocOpenFile("file0", info.sectors, info.filesz, info.recordsz); - for (i = 0; i < 255u; ++i) { + for (i = 0; i < 255U; ++i) { (void)snprintf(common.buffMsg, sizeof(common.buffMsg), "iter=%zu", i); - writeLen = i % 6u; - (i % 2u) ? (void)snprintf(common.pattern, sizeof(common.pattern), "aaaaa") : (void)snprintf(common.pattern, sizeof(common.pattern), "zzzzz"); + writeLen = i % 6U; + (i % 2U) ? (void)snprintf(common.pattern, sizeof(common.pattern), "aaaaa") : (void)snprintf(common.pattern, sizeof(common.pattern), "zzzzz"); if (writeLen > info.recordsz) { TEST_ASSERT_EQUAL_MESSAGE(info.recordsz, file_write(common.fd, common.pattern, writeLen), common.buffMsg); common_readContent(common.fd, info.recordcnt * info.recordsz, common.buffRec, info.recordsz, common.pattern, info.recordsz, common.buffMsg); info.recordcnt++; } - else if (writeLen == 0u) { + else if (writeLen == 0U) { /* Resolving case of writing zero length record. */ TEST_ASSERT_EQUAL_MESSAGE(-EINVAL, file_write(common.fd, common.pattern, writeLen), common.buffMsg); } @@ -197,7 +197,7 @@ TEST(meterfs_writeread, file_end) TEST(meterfs_writeread, many_records) { int i; - const size_t headerSectorcnt = 6u; + const size_t headerSectorcnt = 6U; file_info_t info = { (fsInfo.sz / fsInfo.sectorsz) - headerSectorcnt, 36000, 12, 0 }; common.fd = common_preallocOpenFile("file0", info.sectors, info.filesz, info.recordsz); @@ -227,7 +227,7 @@ TEST(meterfs_writeread, many_records) /* Test case of fulfilling all sectors and turning big file to the beginning. */ TEST(meterfs_writeread, file_turn_big) { - file_info_t info = { (fsInfo.sz / fsInfo.sectorsz) / 2u, fsInfo.sectorsz / 4u, fsInfo.sectorsz / 4u, 0 }; + file_info_t info = { (fsInfo.sz / fsInfo.sectorsz) / 2U, fsInfo.sectorsz / 4U, fsInfo.sectorsz / 4U, 0 }; common.fd = common_preallocOpenFile("file0", info.sectors, info.filesz, info.recordsz); @@ -240,7 +240,7 @@ TEST(meterfs_writeread, file_turn_big) /* Test case of fulfilling all sectors and turning small file to the beginning. */ TEST(meterfs_writeread, file_turn_small) { - file_info_t info = { 2, fsInfo.sectorsz / 10u, fsInfo.sectorsz / 10u }; + file_info_t info = { 2, fsInfo.sectorsz / 10U, fsInfo.sectorsz / 10U }; common.fd = common_preallocOpenFile("file0", info.sectors, info.filesz, info.recordsz); @@ -250,6 +250,78 @@ TEST(meterfs_writeread, file_turn_small) } +/* Tests basic reset correctness - record count set to 0 and file size untouched. */ +TEST(meterfs_writeread, reset_file_simple) +{ + size_t maxRecordcnt = 20U; + file_info_t info = { + .sectors = (fsInfo.sz / fsInfo.sectorsz) / 2U, + .filesz = sizeof(common.buffMsg) * maxRecordcnt, + .recordsz = sizeof(common.buffMsg), + .recordcnt = 0 + }; + common.fd = common_preallocOpenFile("file0", info.sectors, info.filesz, info.recordsz); + size_t i; + + for (i = 0; i < maxRecordcnt / 2U; i++) { + (void)memset(common.buffMsg, 'x', sizeof(common.buffMsg)); + TEST_ASSERT_EQUAL_MESSAGE(info.recordsz, file_write(common.fd, common.buffMsg, info.recordsz), common.buffMsg); + } + + TEST_ASSERT_EQUAL(0, file_getInfo(common.fd, &info.sectors, &info.filesz, &info.recordsz, &info.recordcnt)); + TEST_ASSERT_EQUAL(maxRecordcnt / 2U, info.recordcnt); + + TEST_ASSERT_EQUAL(0, file_reset(common.fd)); + TEST_ASSERT_EQUAL(0, file_getInfo(common.fd, &info.sectors, &info.filesz, &info.recordsz, &info.recordcnt)); + TEST_ASSERT_EQUAL(0, info.recordcnt); + TEST_ASSERT_EQUAL(sizeof(common.buffMsg) * maxRecordcnt, info.filesz); + + TEST_ASSERT_EQUAL(0, file_close(common.fd)); +} + + +/* Tests reset correctness when interleaved with possibly overlapping writes. */ +TEST(meterfs_writeread, reset_file) +{ + size_t maxRecordcnt = 20U; + file_info_t info = { + .sectors = (fsInfo.sz / fsInfo.sectorsz) / 2U, + .filesz = sizeof(common.buffMsg) * maxRecordcnt, + .recordsz = sizeof(common.buffMsg), + .recordcnt = 0 + }; + size_t maxWrites = 10U * maxRecordcnt; + size_t recordcnt; + const char *format = "a00000%06zu"; + size_t i, j; + + common.fd = common_preallocOpenFile("file0", info.sectors, info.filesz, info.recordsz); + + for (j = 1; j < maxWrites; j++) { + for (i = 0; i < j; i++) { + (void)snprintf(common.buffMsg, sizeof(common.buffMsg), format, i); + TEST_ASSERT_EQUAL_MESSAGE(info.recordsz, file_write(common.fd, common.buffMsg, info.recordsz), common.buffMsg); + } + + TEST_ASSERT_EQUAL(0, file_getInfo(common.fd, &info.sectors, &info.filesz, &info.recordsz, &info.recordcnt)); + recordcnt = j <= maxRecordcnt ? j : maxRecordcnt; + TEST_ASSERT_EQUAL(recordcnt, info.recordcnt); + + for (i = 0; i < j && i < maxRecordcnt; i++) { + (void)snprintf(common.buffMsg, sizeof(common.buffMsg), format, i + j - recordcnt); + common_readContent(common.fd, i * sizeof(common.buffMsg), common.buffRec, info.recordsz, common.buffMsg, info.recordsz, common.buffMsg); + } + + TEST_ASSERT_EQUAL(0, file_reset(common.fd)); + TEST_ASSERT_EQUAL(0, file_getInfo(common.fd, &info.sectors, &info.filesz, &info.recordsz, &info.recordcnt)); + TEST_ASSERT_EQUAL(0, info.recordcnt); + TEST_ASSERT_EQUAL(sizeof(common.buffMsg) * maxRecordcnt, info.filesz); + } + + TEST_ASSERT_EQUAL(0, file_close(common.fd)); +} + + TEST_GROUP_RUNNER(meterfs_writeread) { RUN_TEST_CASE(meterfs_writeread, small_records); @@ -259,6 +331,8 @@ TEST_GROUP_RUNNER(meterfs_writeread) RUN_TEST_CASE(meterfs_writeread, many_records); RUN_TEST_CASE(meterfs_writeread, file_turn_big); RUN_TEST_CASE(meterfs_writeread, file_turn_small); + RUN_TEST_CASE(meterfs_writeread, reset_file_simple); + RUN_TEST_CASE(meterfs_writeread, reset_file); } diff --git a/psh/test-perf.py b/psh/test-perf.py new file mode 100644 index 000000000..780e266af --- /dev/null +++ b/psh/test-perf.py @@ -0,0 +1,77 @@ +# Phoenix-RTOS +# +# phoenix-rtos-tests +# +# psh perf command test +# +# Copyright 2025 Phoenix Systems +# Author: Adam Greloch +# +# %LICENSE% +# + +import psh.tools.psh as psh +from psh.tools.common import assert_file_created, assert_file_deleted, \ + assert_present, assert_dir_created, assert_deleted_rec +from time import sleep + + +ROOT_TEST_DIR = "/tmp/test_perf_trace" +START_STOP_REPEATS = 3 + + +def assert_perf_h(p): + psh.assert_prompt_after_cmd(p, "perf", result='fail') + psh.assert_prompt_after_cmd(p, "perf -h", result='success') + + +def assert_perf_err(p): + # should also pass dest directory with `-o` when not compiled + # with RTT support + psh.assert_prompt_after_cmd(p, "perf -m trace", result='fail') + + fname = ROOT_TEST_DIR + "/notadir" + assert_file_created(p, fname) + psh.assert_prompt_after_cmd(p, f"perf -m trace -o {fname}", result='fail') + assert_file_deleted(p, fname) + + +def assert_trace_output_present(p, path): + files = psh.ls(p, path) + assert_present("channel_event0", files, dir=False) + assert_present("channel_meta0", files, dir=False) + + +def assert_perf_basic(p): + traceDir1 = ROOT_TEST_DIR + "/trace_basic" + psh.assert_prompt_after_cmd( + p, f"perf -m trace -o {traceDir1} -t 500", result='success') + assert_trace_output_present(p, traceDir1) + + +def assert_perf_start_stop(p): + for _ in range(START_STOP_REPEATS): + traceDir = ROOT_TEST_DIR + "/trace_start_stop" + psh.assert_prompt_after_cmd(p, f"perf -m trace -o {traceDir} -j stop", result='fail') + + psh.assert_prompt_after_cmd(p, f"perf -m trace -o {traceDir} -j start", result='success') + psh.assert_prompt_after_cmd(p, f"perf -m trace -o {traceDir} -j start", result='fail') + + sleep(0.5) + + psh.assert_prompt_after_cmd(p, f"perf -m trace -o {traceDir} -j stop", result='success') + psh.assert_prompt_after_cmd(p, f"perf -m trace -o {traceDir} -j stop", result='fail') + + assert_trace_output_present(p, traceDir) + + +@psh.run +def harness(p): + assert_dir_created(p, ROOT_TEST_DIR) + + assert_perf_h(p) + assert_perf_err(p) + assert_perf_basic(p) + assert_perf_start_stop(p) + + assert_deleted_rec(p, ROOT_TEST_DIR) diff --git a/psh/test-trace-to-file.py b/psh/test-trace-to-file.py new file mode 100644 index 000000000..d865f7e13 --- /dev/null +++ b/psh/test-trace-to-file.py @@ -0,0 +1,33 @@ +# Phoenix-RTOS +# +# phoenix-rtos-tests +# +# psh perf command test - save trace to fs +# +# Copyright 2025 Phoenix Systems +# Author: Adam Greloch +# +# %LICENSE% +# + +import psh.tools.psh as psh +from psh.tools.common import assert_present, assert_dir_created + + +# NOTE: path expected in CI - the emitted trace is later captured by QEMU +# host for correctness validation +TRACE_DIR = "/test_perf_trace" + + +@psh.run +def harness(p): + assert_dir_created(p, TRACE_DIR) + + traceDir = TRACE_DIR + psh.assert_prompt_after_cmd( + p, f"perf -m trace -o {traceDir} -t 1000", result='success') + files = psh.ls(p, traceDir) + assert_present("channel_event0", files, dir=False) + assert_present("channel_meta0", files, dir=False) + + # TRACE_DIR left on fs intentionally diff --git a/psh/test.yaml b/psh/test.yaml index 6ccd03eb8..25531b2a5 100644 --- a/psh/test.yaml +++ b/psh/test.yaml @@ -117,3 +117,22 @@ test: - name: exit harness: test-exit.py + + - name: perf + harness: test-perf.py + targets: + # only for targets with: 1) rootfs 2) tracing buffer-mem support + # aarch64a53-zynqmp-qemu: excluded due to https://github.com/phoenix-rtos/phoenix-rtos-project/issues/1386 + value: [ + ia32-generic-qemu, + riscv64-generic-qemu, + armv7a9-zynq7000-zedboard, + armv7a9-zynq7000-qemu, + armv7a7-imx6ull-evk + ] + + - name: trace-to-file + harness: test-trace-to-file.py + targets: + # only for QEMU targets with MBR + value: [ia32-generic-qemu, riscv64-generic-qemu] diff --git a/sys/Makefile b/sys/Makefile index 5bdf242ac..285c63293 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -18,3 +18,4 @@ endef $(eval $(call add_test_sys,cond)) $(eval $(call add_test_sys,mutex)) +$(eval $(call add_test_sys,perf)) diff --git a/sys/perf/perf.c b/sys/perf/perf.c new file mode 100644 index 000000000..2518ae4c3 --- /dev/null +++ b/sys/perf/perf.c @@ -0,0 +1,177 @@ +/* + * Phoenix-RTOS + * + * test-sys-perf + * + * Test for perf subsystem + * + * Copyright 2025 Phoenix Systems + * Author: Adam Greloch + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + + +#include +#include +#include +#include +#include + +#include + + +#define BUF_SIZE 64 + +static char buf[BUF_SIZE]; + + +TEST_GROUP(perf_test_common); +TEST_GROUP(perf_test_trace); + + +static void emit_kernel_events(void) +{ + for (size_t rep = 0; rep < 100; rep++) { + usleep(1); + } +} + + +static void read_events(perf_mode_t mode, size_t nchans) +{ + size_t total = 0; + int rv; + for (size_t chan = 0; chan < nchans; chan++) { + rv = perf_read(mode, buf, BUF_SIZE, chan); + TEST_ASSERT_GREATER_OR_EQUAL_INT(0, rv); + total += rv; + } + TEST_ASSERT_GREATER_THAN_size_t(0, total); +} + + +/* + *--------------------------------- invalid params tests -----------------------------------* + */ + + +TEST_SETUP(perf_test_common) +{ +} + + +TEST_TEAR_DOWN(perf_test_common) +{ +} + + +TEST(perf_test_common, invalid_mode) +{ + TEST_ASSERT_EQUAL_INT(-ENOSYS, perf_start(-1, 0, NULL, 0)); + TEST_ASSERT_EQUAL_INT(-ENOSYS, perf_read(-1, buf, BUF_SIZE, 0)); + TEST_ASSERT_EQUAL_INT(-ENOSYS, perf_stop(-1)); + TEST_ASSERT_EQUAL_INT(-ENOSYS, perf_finish(-1)); +} + + +TEST(perf_test_common, invalid_calls_when_perf_off) +{ + for (perf_mode_t mode = 0; mode < perf_mode_count; mode++) { + TEST_ASSERT_EQUAL_INT(-EINVAL, perf_read(mode, buf, BUF_SIZE, 0)); + + /* may be -EINVAL or -ENOSYS depending whether mode supports perf_stop */ + TEST_ASSERT_LESS_THAN_INT(0, perf_stop(mode)); + + TEST_ASSERT_EQUAL_INT(-EINVAL, perf_finish(mode)); + } +} + + +TEST(perf_test_common, start_read_finish) +{ + int rv; + + for (perf_mode_t mode = 0; mode < perf_mode_count; mode++) { + for (size_t rep = 0; rep < 5; rep++) { + rv = perf_start(mode, 0, NULL, 0); + if (rv <= 0) { + TEST_ASSERT_EQUAL_INT(-ENOSYS, rv); + break; + } + else { + emit_kernel_events(); + read_events(mode, rv); + TEST_ASSERT_EQUAL_INT(EOK, perf_finish(mode)); + } + } + } +} + + +/* + *--------------------------------- perf_mode_trace tests ---------------------------------* + */ + + +TEST_SETUP(perf_test_trace) +{ +} + + +TEST_TEAR_DOWN(perf_test_trace) +{ +} + + +TEST(perf_test_trace, trace_start_stop_finish) +{ + int rv; + + for (size_t rep = 0; rep < 5; rep++) { + rv = perf_start(perf_mode_trace, 0, NULL, 0); + if (rv <= 0) { + TEST_ASSERT_EQUAL_INT(-ENOSYS, rv); + TEST_IGNORE_MESSAGE("RTT perf target untestable on CI"); + } + usleep(100); + emit_kernel_events(); + TEST_ASSERT_GREATER_THAN_INT(0, perf_stop(perf_mode_trace)); + read_events(perf_mode_trace, rv); + TEST_ASSERT_EQUAL_INT(EOK, perf_finish(perf_mode_trace)); + } +} + + +/* +/////////////////////////////////////////////////////////////////////////////////////////////// +*/ + + +TEST_GROUP_RUNNER(perf_test_common) +{ + RUN_TEST_CASE(perf_test_common, invalid_mode); + RUN_TEST_CASE(perf_test_common, invalid_calls_when_perf_off); + RUN_TEST_CASE(perf_test_common, start_read_finish); +} + + +TEST_GROUP_RUNNER(perf_test_trace) +{ + RUN_TEST_CASE(perf_test_trace, trace_start_stop_finish); +} + + +void runner(void) +{ + RUN_TEST_GROUP(perf_test_common); + RUN_TEST_GROUP(perf_test_trace); +} + + +int main(int argc, char *argv[]) +{ + return (UnityMain(argc, (const char **)argv, runner) == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/sys/test.yaml b/sys/test.yaml index 8b1c5dc8b..ec342da6b 100644 --- a/sys/test.yaml +++ b/sys/test.yaml @@ -6,3 +6,6 @@ test: - name: cond execute: test-sys-cond + + - name: perf + execute: test-sys-perf diff --git a/trunner/config.py b/trunner/config.py index a74671d6a..254e86fab 100644 --- a/trunner/config.py +++ b/trunner/config.py @@ -9,7 +9,7 @@ from trunner.ctx import TestContext from trunner.harness import PyHarness, unity_harness -from trunner.types import AppOptions, BootloaderOptions, TestOptions, ShellOptions +from trunner.types import AppOptions, BootloaderOptions, TestOptions, ShellOptions, TestType class ParserError(Exception): @@ -55,15 +55,14 @@ def __init__(self, ctx: TestContext): def _parse_type(self, config: dict): test_type = config.get("type", self.main.type) - if not test_type: - test_type = "harness" + t_type = TestType(test_type) - if test_type == "harness": + if t_type in (TestType.EMPTY, TestType.HARNESS): self._parse_pyharness(config) - elif test_type == "unity": + elif t_type is TestType.UNITY: self._parse_unity() else: - raise ParserError("unknown key!") + raise ParserError(f"unknown test type: {test_type}") def _parse_pyharness(self, config: dict): path = config.get("harness", self.raw_main.get("harness")) diff --git a/trunner/types.py b/trunner/types.py index 78696afba..3843ec4f3 100644 --- a/trunner/types.py +++ b/trunner/types.py @@ -91,11 +91,34 @@ def __lt__(self, other): return NotImplemented +class TestType(str, Enum): + @staticmethod + def _generate_next_value_(name: str, start: int, count: int, last_values: list) -> str: + return name.lower() + + @classmethod + def _missing_(cls, value: object) -> Enum | None: + if value is None or value == "": + return cls.EMPTY + + if not isinstance(value, str): + return cls.UNSUPPORTED + + lowercase_val = value.lower() + return next((member for member in cls if member.value == lowercase_val), cls.UNSUPPORTED) + + HARNESS = auto() + PYTEST = auto() + UNITY = auto() + EMPTY = auto() + UNSUPPORTED = auto() + + class TestResult: def __init__(self, name=None, msg: str = "", status: Optional[Status] = None): self.msg = msg self.summary = "" - self.status = Status.OK if status is None else status + self._status = status self._name = name self.subname = "" @@ -143,6 +166,27 @@ def to_str(self, verbosity: int = 0) -> str: out.append("") return "\n".join(out) + @property + def status(self) -> Status: + if self._status is not None: + return self._status + if self.subresults: + return self._compute_status() + + return Status.OK + + @status.setter + def status(self, value: Status): + self._status = value + + def _compute_status(self) -> Status: + if any(sub.status == Status.FAIL for sub in self.subresults): + return Status.FAIL + elif all(sub.status == Status.SKIP for sub in self.subresults): + return Status.SKIP + else: + return Status.OK + def to_junit_testcase(self, target: str): out = junitparser.TestCase(f"{target}:{self.full_name}") out.classname = self.name