From dc97113ca160beea3ac33a781bac52829456ff60 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 21 Feb 2026 15:51:56 -0800 Subject: [PATCH] Cleanup the C API --- CMakeLists.txt | 58 ++----------- test/CMakeLists.txt | 5 ++ test/c-test.c | 92 ++++++++++++++++++++ test/test_c.c | 202 -------------------------------------------- 4 files changed, 106 insertions(+), 251 deletions(-) create mode 100644 test/c-test.c delete mode 100644 test/test_c.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 30ce6b29..cfa521e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -383,6 +383,14 @@ target_include_directories(fmt-header-only $ $) +add_library(fmt-c STATIC src/fmt-c.cc) +target_compile_features(fmt-c INTERFACE c_std_11) +if (MSVC) + target_compile_options(fmt-c PUBLIC /Zc:preprocessor) +endif () +target_link_libraries(fmt-c PUBLIC fmt::fmt) +add_library(fmt::fmt-c ALIAS fmt-c) + # Install targets. if (FMT_INSTALL) include(CMakePackageConfigHelpers) @@ -422,7 +430,7 @@ if (FMT_INSTALL) ${project_config} INSTALL_DESTINATION ${FMT_CMAKE_DIR}) - set(INSTALL_TARGETS fmt fmt-header-only) + set(INSTALL_TARGETS fmt fmt-header-only fmt-c) set(INSTALL_FILE_SET) if (FMT_USE_CMAKE_MODULES) @@ -533,51 +541,3 @@ if (FMT_MASTER_PROJECT AND EXISTS ${gitignore}) set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.md) include(CPack) endif () - -# C API Wrapper -add_library(fmt_c STATIC src/fmt-c.cc) - -target_compile_features(fmt_c PUBLIC cxx_std_11) -target_link_libraries(fmt_c PUBLIC fmt::fmt) - -target_include_directories(fmt_c PUBLIC - $ - $ -) - -set_target_properties(fmt_c PROPERTIES - VERSION ${FMT_VERSION} - SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} - DEBUG_POSTFIX "${CMAKE_DEBUG_POSTFIX}" - C_VISIBILITY_PRESET default - CXX_VISIBILITY_PRESET hidden -) - -add_library(fmt::fmt_c ALIAS fmt_c) -if(FMT_INSTALL) - install(TARGETS fmt_c - EXPORT fmt-targets - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) - install(FILES include/fmt/fmt-c.h - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/fmt -) -endif() - -if(FMT_TEST) - enable_language(C) - message(STATUS "Adding C API test executable") - - add_executable(test-c-api test/test_c.c) - target_link_libraries(test-c-api PRIVATE fmt::fmt_c) - set_target_properties(test-c-api PROPERTIES - C_STANDARD 11 - C_STANDARD_REQUIRED ON - ) - if(MSVC) - target_compile_options(test-c-api PRIVATE /Zc:preprocessor) - endif() - - add_test(NAME c-api-test COMMAND test-c-api) -endif() - diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3f7d24ab..a086082b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -242,3 +242,8 @@ if (FMT_CUDA_TEST) add_test(NAME cuda-test COMMAND fmt-in-cuda-test) endif () endif () + +enable_language(C) +add_executable(c-test c-test.c) +target_link_libraries(c-test PRIVATE fmt::fmt-c) +add_test(NAME c-test COMMAND c-test) diff --git a/test/c-test.c b/test/c-test.c new file mode 100644 index 00000000..71056841 --- /dev/null +++ b/test/c-test.c @@ -0,0 +1,92 @@ +// Formatting library for C++ - the C API tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/fmt-c.h" + +#include +#include +#include + +#define ASSERT_STR_EQ(actual, expected) \ + do { \ + if (strcmp(actual, expected) != 0) { \ + fprintf(stderr, \ + "\nAssertion failed:\n Expected: \"%s\"\n Got: \"%s\"\n", \ + expected, actual); \ + exit(1); \ + } \ + } while (0) + +#define ASSERT_INT_EQ(actual, expected) \ + do { \ + if ((actual) != (expected)) { \ + fprintf(stderr, "\nAssertion failed:\n Expected: %d\n Got: %d\n", \ + expected, actual); \ + exit(1); \ + } \ + } while (0) + +static inline int fmt_vformat_cstr(char* buf, size_t size, int result) { + if (result >= 0 && (size_t)result < size) + buf[result] = '\0'; + else if (size > 0) + buf[size - 1] = '\0'; + return result; +} + +#define fmt_format_cstr(buf, size, ...) \ + fmt_vformat_cstr(buf, size, fmt_format(buf, size, __VA_ARGS__)) + +void test_types(void) { + char buf[100]; + + fmt_format_cstr(buf, sizeof(buf), "{}", 42); + ASSERT_STR_EQ(buf, "42"); + + fmt_format_cstr(buf, sizeof(buf), "{}", 123u); + ASSERT_STR_EQ(buf, "123"); + + fmt_format_cstr(buf, sizeof(buf), "{}", (bool)true); + ASSERT_STR_EQ(buf, "true"); + + fmt_format_cstr(buf, sizeof(buf), "{}", (char)'x'); + ASSERT_STR_EQ(buf, "x"); + + fmt_format_cstr(buf, sizeof(buf), "{}", 1.2f); + ASSERT_STR_EQ(buf, "1.2"); + + fmt_format_cstr(buf, sizeof(buf), "{}", 3.14159); + ASSERT_STR_EQ(buf, "3.14159"); + + fmt_format_cstr(buf, sizeof(buf), "{}", 1.2l); + ASSERT_STR_EQ(buf, "1.2"); + + fmt_format_cstr(buf, sizeof(buf), "{}", "foo"); + ASSERT_STR_EQ(buf, "foo"); + + fmt_format_cstr(buf, sizeof(buf), "{}", (void*)0x12345678); + ASSERT_STR_EQ(buf, "0x12345678"); +} + +void test_zero_arguments(void) { + char buf[100]; + int ret = fmt_format_cstr(buf, sizeof(buf), "No arguments"); + ASSERT_STR_EQ(buf, "No arguments"); +} + +void test_buffer_size_query(void) { + int size = fmt_format(NULL, 0, "Test string: {}", 42); + ASSERT_INT_EQ(size, 15); +} + +int main(void) { + printf("Running C API tests\n"); + test_types(); + test_zero_arguments(); + test_buffer_size_query(); + printf("C API tests passed\n"); +} diff --git a/test/test_c.c b/test/test_c.c deleted file mode 100644 index 3ee83047..00000000 --- a/test/test_c.c +++ /dev/null @@ -1,202 +0,0 @@ -/* Test suite for fmt C API */ -#include -#include -#include -#include - -#include "fmt/fmt-c.h" - -#define ASSERT_STR_EQ(actual, expected) \ - do { \ - if (strcmp(actual, expected) != 0) { \ - fprintf(stderr, \ - "\nAssertion failed:\n Expected: \"%s\"\n Got: \"%s\"\n", \ - expected, actual); \ - exit(1); \ - } \ - } while (0) - -#define ASSERT_INT_EQ(actual, expected) \ - do { \ - if ((actual) != (expected)) { \ - fprintf(stderr, "\nAssertion failed:\n Expected: %d\n Got: %d\n", \ - expected, actual); \ - exit(1); \ - } \ - } while (0) - -#define ASSERT_TRUE(cond) \ - do { \ - if (!(cond)) { \ - fprintf(stderr, "\nAssertion failed: %s\n", #cond); \ - exit(1); \ - } \ - } while (0) - -// Helper to manually null-terminate buffer after formatting -void terminate(char* buf, int size, size_t capacity) { - if (size >= 0 && (size_t)size < capacity) { - buf[size] = '\0'; - } else if (capacity > 0) { - buf[capacity - 1] = '\0'; - } -} - -void test_basic_integer(void) { - char buf[100]; - int ret = fmt_format(buf, sizeof(buf), "Number: {}", 42); - terminate(buf, ret, sizeof(buf)); - ASSERT_STR_EQ(buf, "Number: 42"); - ASSERT_INT_EQ(ret, 10); -} - -void test_multiple_integers(void) { - char buf[100]; - int ret = fmt_format(buf, sizeof(buf), "{} + {} = {}", 1, 2, 3); - terminate(buf, ret, sizeof(buf)); - ASSERT_STR_EQ(buf, "1 + 2 = 3"); -} - -void test_unsigned_integers(void) { - char buf[100]; - unsigned int x = 4294967295U; - int ret = fmt_format(buf, sizeof(buf), "{}", x); - terminate(buf, ret, sizeof(buf)); - ASSERT_STR_EQ(buf, "4294967295"); -} - -void test_floating_point(void) { - char buf[100]; - int ret = fmt_format(buf, sizeof(buf), "Pi = {}", 3.14159); - terminate(buf, ret, sizeof(buf)); - ASSERT_TRUE(strncmp(buf, "Pi = 3.14159", 12) == 0); -} - -void test_float_type(void) { - char buf[100]; - float f = 1.234f; - int ret = fmt_format(buf, sizeof(buf), "Float: {:.3f}", f); - terminate(buf, ret, sizeof(buf)); - ASSERT_STR_EQ(buf, "Float: 1.234"); -} - -void test_long_double_type(void) { - char buf[100]; - long double ld = 12345.6789L; - int ret = fmt_format(buf, sizeof(buf), "{:.4f}", ld); - terminate(buf, ret, sizeof(buf)); - ASSERT_STR_EQ(buf, "12345.6789"); -} - -void test_mixed_floating_types(void) { - char buf[200]; - float f = 1.5f; - double d = 2.5; - long double ld = 3.5L; - - int ret = fmt_format(buf, sizeof(buf), "{} {} {}", f, d, ld); - terminate(buf, ret, sizeof(buf)); - ASSERT_STR_EQ(buf, "1.5 2.5 3.5"); -} - -void test_strings(void) { - char buf[100]; - int ret = fmt_format(buf, sizeof(buf), "Hello, {}!", "from fmt!"); - terminate(buf, ret, sizeof(buf)); - ASSERT_STR_EQ(buf, "Hello, from fmt!!"); -} - -void test_pointers(void) { - char buf[100]; - void* ptr = (void*)0x12345678; - int ret = fmt_format(buf, sizeof(buf), "{}", ptr); - terminate(buf, ret, sizeof(buf)); - ASSERT_TRUE(strstr(buf, "12345678") != NULL); -} - -void test_booleans(void) { - char buf[100]; - int ret = fmt_format(buf, sizeof(buf), "{} {}", (bool)true, (bool)false); - terminate(buf, ret, sizeof(buf)); - ASSERT_STR_EQ(buf, "true false"); -} - -void test_characters(void) { - char buf[100]; - int ret = fmt_format(buf, sizeof(buf), "Char: {}", (char)'A'); - terminate(buf, ret, sizeof(buf)); - ASSERT_STR_EQ(buf, "Char: A"); -} - -void test_mixed_types(void) { - char buf[100]; - int ret = - fmt_format(buf, sizeof(buf), "{} {} {} {}", 42, 3.14, "text", (bool)true); - terminate(buf, ret, sizeof(buf)); - ASSERT_TRUE(strstr(buf, "42") != NULL); - ASSERT_TRUE(strstr(buf, "3.14") != NULL); - ASSERT_TRUE(strstr(buf, "text") != NULL); - ASSERT_TRUE(strstr(buf, "true") != NULL); -} - -void test_zero_arguments(void) { - char buf[100]; - int ret = fmt_vformat(buf, sizeof(buf), "No arguments", NULL, 0); - terminate(buf, ret, sizeof(buf)); - ASSERT_STR_EQ(buf, "No arguments"); -} - -void test_buffer_size_query(void) { - int size = fmt_format(NULL, 0, "Test string: {}", 42); - ASSERT_INT_EQ(size, 15); -} - -void test_long_strings(void) { - char buf[1000]; - const char* long_str = - "This is a very long string that contains a lot of text " - "to test the buffer handling capabilities of the formatter"; - int ret = fmt_format(buf, sizeof(buf), "Message: {}", long_str); - terminate(buf, ret, sizeof(buf)); - ASSERT_TRUE(strstr(buf, long_str) != NULL); -} - -void test_multiple_calls(void) { - char buf[100]; - - int ret = fmt_format(buf, sizeof(buf), "{} {}", 1, 2); - terminate(buf, ret, sizeof(buf)); - ASSERT_STR_EQ(buf, "1 2"); - - ret = fmt_format(buf, sizeof(buf), "{} {}", "hello", 3.14); - terminate(buf, ret, sizeof(buf)); - ASSERT_TRUE(strstr(buf, "hello") != NULL); - - ret = fmt_format(buf, sizeof(buf), "{}", (bool)true); - terminate(buf, ret, sizeof(buf)); - ASSERT_STR_EQ(buf, "true"); -} - -int main(void) { - printf("=== Running fmt C API Tests ===\n\n"); - - test_basic_integer(); - test_multiple_integers(); - test_unsigned_integers(); - test_floating_point(); - test_float_type(); - test_long_double_type(); - test_mixed_floating_types(); - test_strings(); - test_pointers(); - test_booleans(); - test_characters(); - test_mixed_types(); - test_zero_arguments(); - test_buffer_size_query(); - test_long_strings(); - test_multiple_calls(); - - printf("\n=== All tests passed! ===\n"); - return 0; -}