diff --git a/tests/sys/snprintf/Makefile b/tests/sys/snprintf/Makefile new file mode 100644 index 0000000000000..d5e587b491b31 --- /dev/null +++ b/tests/sys/snprintf/Makefile @@ -0,0 +1,7 @@ +include ../Makefile.sys_common + +# avrlibc's snprintf doesn't support uint64_t / int64_t and even fails +# to compile due to PRI*64 macros not being defined +FEATURES_BLACKLIST := arch_avr8 + +include $(RIOTBASE)/Makefile.include diff --git a/tests/sys/snprintf/Makefile.board.dep b/tests/sys/snprintf/Makefile.board.dep new file mode 100644 index 0000000000000..d2f05e988ee00 --- /dev/null +++ b/tests/sys/snprintf/Makefile.board.dep @@ -0,0 +1,4 @@ +# newlib's printf is known to be incomplete, despite being bloated +ifneq (,$(filter newlib,$(USEMODULE))) + USEPKG += mpaland-printf +endif diff --git a/tests/sys/snprintf/README.md b/tests/sys/snprintf/README.md new file mode 100644 index 0000000000000..8a034210d0165 --- /dev/null +++ b/tests/sys/snprintf/README.md @@ -0,0 +1,6 @@ +# snprintf + +This test aims to test if the stdio implementations correctly implements +standard format specifiers. Instead of relying on the transport of stdout to +be fast and reliable, it will use snprintf to format in-memory and compare +in the app with correctness. diff --git a/tests/sys/snprintf/main.c b/tests/sys/snprintf/main.c new file mode 100644 index 0000000000000..228c884f898b5 --- /dev/null +++ b/tests/sys/snprintf/main.c @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2024 Marian Buschsieweke + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief snprintf test (correctness of standard format specifier + * implementation) + * + * @author Marian Buschsieweke + * + * @} + */ + +#include +#include +#include +#include +#include + +static bool failed = false; + +static void check(const char *expected, const char *got, int retval) +{ + if (retval != (int)strlen(expected)) { + failed = true; + printf("snprintf() returned %d, but expected %u\n", + retval, (unsigned)strlen(expected)); + } + + if (strcmp(expected, got) != 0) { + printf("Expected: \"%s\"\n" + "Got: \"%s\"\n", + expected, got); + failed = true; + } +} + +static void test_int8(void) +{ + static uint8_t u8 = 10; + static int8_t s8 = -3; + + /* memory barrier to prevent the compiler from constant folding on the + * snprintf calls */ + __asm__ volatile ("" ::: "memory"); + + char buf[32]; + size_t len; + + len = snprintf(buf, sizeof(buf), "%" PRIu8, u8); + check("10", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIx8, u8); + check("a", buf, len); + + len = snprintf(buf, sizeof(buf), "%#" PRIx8, u8); + check("0xa", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIX8, u8); + check("A", buf, len); + + len = snprintf(buf, sizeof(buf), "%#" PRIX8, u8); + check("0XA", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIo8, u8); + check("12", buf, len); + + len = snprintf(buf, sizeof(buf), "%#" PRIo8, u8); + check("012", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRId8, s8); + check("-3", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIi8, s8); + check("-3", buf, len); +} + +static void test_int16(void) +{ + static uint16_t u16 = 45054; + static int16_t s16 = -1337; + + /* memory barrier to prevent the compiler from constant folding on the + * snprintf calls */ + __asm__ volatile ("" ::: "memory"); + + char buf[32]; + size_t len; + + len = snprintf(buf, sizeof(buf), "%" PRIu16, u16); + check("45054", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIx16, u16); + check("affe", buf, len); + + len = snprintf(buf, sizeof(buf), "%#" PRIx16, u16); + check("0xaffe", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIX16, u16); + check("AFFE", buf, len); + + len = snprintf(buf, sizeof(buf), "%#" PRIX16, u16); + check("0XAFFE", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIo16, u16); + check("127776", buf, len); + + len = snprintf(buf, sizeof(buf), "%#" PRIo16, u16); + check("0127776", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRId16, s16); + check("-1337", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIi16, s16); + check("-1337", buf, len); +} + +static void test_int32(void) +{ + static uint32_t u32 = 2952663863; + static int32_t s32 = -2147483648; + + /* memory barrier to prevent the compiler from constant folding on the + * snprintf calls */ + __asm__ volatile ("" ::: "memory"); + + char buf[32]; + size_t len; + + len = snprintf(buf, sizeof(buf), "%" PRIu32, u32); + check("2952663863", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIx32, u32); + check("affe1337", buf, len); + + len = snprintf(buf, sizeof(buf), "%#" PRIx32, u32); + check("0xaffe1337", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIX32, u32); + check("AFFE1337", buf, len); + + len = snprintf(buf, sizeof(buf), "%#" PRIX32, u32); + check("0XAFFE1337", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIo32, u32); + check("25777411467", buf, len); + + len = snprintf(buf, sizeof(buf), "%#" PRIo32, u32); + check("025777411467", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRId32, s32); + check("-2147483648", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIi32, s32); + check("-2147483648", buf, len); +} + +static void test_int64(void) +{ + static uint64_t u64 = 16045690984050070327ULL; + static int64_t s64 = -9223372036854775807LL; + + /* memory barrier to prevent the compiler from constant folding on the + * snprintf calls */ + __asm__ volatile ("" ::: "memory"); + + char buf[32]; + size_t len; + + len = snprintf(buf, sizeof(buf), "%" PRIu64, u64); + check("16045690984050070327", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIx64, u64); + check("deadbeefaffe1337", buf, len); + + len = snprintf(buf, sizeof(buf), "%#" PRIx64, u64); + check("0xdeadbeefaffe1337", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIX64, u64); + check("DEADBEEFAFFE1337", buf, len); + + len = snprintf(buf, sizeof(buf), "%#" PRIX64, u64); + check("0XDEADBEEFAFFE1337", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIo64, u64); + check("1572555756765777411467", buf, len); + + len = snprintf(buf, sizeof(buf), "%#" PRIo64, u64); + check("01572555756765777411467", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRId64, s64); + check("-9223372036854775807", buf, len); + + len = snprintf(buf, sizeof(buf), "%" PRIi64, s64); + check("-9223372036854775807", buf, len); +} + +static void test_size(void) +{ + static size_t s = 42; + static ssize_t ss = -1; + static ptrdiff_t p = 1337; + + /* memory barrier to prevent the compiler from constant folding on the + * snprintf calls */ + __asm__ volatile ("" ::: "memory"); + + char buf[32]; + size_t len; + + len = snprintf(buf, sizeof(buf), "%zu", s); + check("42", buf, len); + + len = snprintf(buf, sizeof(buf), "%zd", ss); + check("-1", buf, len); + + len = snprintf(buf, sizeof(buf), "%td", p); + check("1337", buf, len); + + len = snprintf(buf, sizeof(buf), "%tu", p); + check("1337", buf, len); +} + +static void test_flags_widths(void) +{ + static uint16_t u16 = 42; + static int16_t s16 = -42; + + /* memory barrier to prevent the compiler from constant folding on the + * snprintf calls */ + __asm__ volatile ("" ::: "memory"); + + char buf[32]; + size_t len; + + len = snprintf(buf, sizeof(buf), "%03" PRIu16, u16); + check("042", buf, len); + + len = snprintf(buf, sizeof(buf), "%3" PRIu16, u16); + check(" 42", buf, len); + + len = snprintf(buf, sizeof(buf), "%-3" PRIu16, u16); + check("42 ", buf, len); + + len = snprintf(buf, sizeof(buf), "%*" PRIu16, 8, u16); + check(" 42", buf, len); + + len = snprintf(buf, sizeof(buf), "%-*" PRIu16, 8, u16); + check("42 ", buf, len); + + len = snprintf(buf, sizeof(buf), "%+" PRId16, (int16_t)u16); + check("+42", buf, len); + + len = snprintf(buf, sizeof(buf), "%04" PRId16, s16); + check("-042", buf, len); + + len = snprintf(buf, sizeof(buf), "%4" PRId16, s16); + check(" -42", buf, len); + + len = snprintf(buf, sizeof(buf), "%-4" PRId16, s16); + check("-42 ", buf, len); +} + +int main(void) +{ + puts("Testing snprintf() implementation..."); + test_int8(); + test_int16(); + test_int32(); + test_int64(); + test_size(); + test_flags_widths(); + + if (failed) { + puts("TEST FAILED!"); + } + else { + puts("Test succeeded"); + } + + return 0; +} diff --git a/tests/sys/snprintf/tests/01-run.py b/tests/sys/snprintf/tests/01-run.py new file mode 100755 index 0000000000000..4f3fcac1cc9a0 --- /dev/null +++ b/tests/sys/snprintf/tests/01-run.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2024 Marian Buschsieweke +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +# @author Marian Buschsieweke + +import sys +from testrunner import run + + +def testfunc(child): + child.expect_exact("Testing snprintf() implementation...") + child.expect_exact("Test succeeded") + + +if __name__ == "__main__": + sys.exit(run(testfunc))