diff --git a/openmp/cmake/modules/LibompHandleFlags.cmake b/openmp/cmake/modules/LibompHandleFlags.cmake index c36a88fb862ae..77ccb3ecaff34 100644 --- a/openmp/cmake/modules/LibompHandleFlags.cmake +++ b/openmp/cmake/modules/LibompHandleFlags.cmake @@ -97,6 +97,7 @@ endfunction() # Linker flags function(libomp_get_ldflags ldflags) + cmake_parse_arguments(ARG "FOR_UNITTESTS" "" "" ${ARGN}) set(ldflags_local) libomp_append(ldflags_local "${CMAKE_LINK_DEF_FILE_FLAG}${CMAKE_CURRENT_BINARY_DIR}/${LIBOMP_LIB_NAME}.def" IF_DEFINED CMAKE_LINK_DEF_FILE_FLAG) @@ -105,7 +106,11 @@ function(libomp_get_ldflags ldflags) libomp_append(ldflags_local "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}${LIBOMP_VERSION_MAJOR}.${LIBOMP_VERSION_MINOR}" IF_DEFINED CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG) libomp_append(ldflags_local -Wl,--as-needed LIBOMP_HAVE_AS_NEEDED_FLAG) - libomp_append(ldflags_local "-Wl,--version-script=${LIBOMP_SRC_DIR}/exports_so.txt" LIBOMP_HAVE_VERSION_SCRIPT_FLAG) + if(ARG_FOR_UNITTESTS) + libomp_append(ldflags_local "-Wl,--version-script=${LIBOMP_SRC_DIR}/exports_test_so.txt" LIBOMP_HAVE_VERSION_SCRIPT_FLAG) + else() + libomp_append(ldflags_local "-Wl,--version-script=${LIBOMP_SRC_DIR}/exports_so.txt" LIBOMP_HAVE_VERSION_SCRIPT_FLAG) + endif() libomp_append(ldflags_local -static-libgcc LIBOMP_HAVE_STATIC_LIBGCC_FLAG) libomp_append(ldflags_local -Wl,-z,noexecstack LIBOMP_HAVE_Z_NOEXECSTACK_FLAG) libomp_append(ldflags_local -no-intel-extensions LIBOMP_HAVE_NO_INTEL_EXTENSIONS_FLAG) diff --git a/openmp/runtime/CMakeLists.txt b/openmp/runtime/CMakeLists.txt index 93948b941f0dc..9332ca30128d8 100644 --- a/openmp/runtime/CMakeLists.txt +++ b/openmp/runtime/CMakeLists.txt @@ -118,6 +118,19 @@ if(NOT DEFINED CMAKE_MACOSX_RPATH) set(CMAKE_MACOSX_RPATH TRUE) endif() +# Remove any cmake-automatic linking of the standard C++ library. +# We neither need (nor want) the standard C++ library dependency even though we compile C++ files. +if(NOT LIBOMP_USE_STDCPPLIB) + set(LIBOMP_LINKER_LANGUAGE C) + set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES) +else() + set(LIBOMP_LINKER_LANGUAGE CXX) +endif() + +if(UNIX) + set(LIBOMP_DL_LIBS ${CMAKE_DL_LIBS}) +endif() + # User specified flags. These are appended to the configured flags. set(LIBOMP_CXXFLAGS "" CACHE STRING "Appended user specified C++ compiler flags.") @@ -446,6 +459,7 @@ endif() add_subdirectory(src) add_subdirectory(test) +add_subdirectory(unittests) # make these variables available for tools: set(LIBOMP_LIBRARY_DIR ${LIBOMP_LIBRARY_DIR} PARENT_SCOPE) diff --git a/openmp/runtime/src/CMakeLists.txt b/openmp/runtime/src/CMakeLists.txt index 0c0804776a774..53f83c006b04f 100644 --- a/openmp/runtime/src/CMakeLists.txt +++ b/openmp/runtime/src/CMakeLists.txt @@ -153,19 +153,6 @@ libomp_get_asmflags(LIBOMP_CONFIGURED_ASMFLAGS) set_source_files_properties(${LIBOMP_CXXFILES} PROPERTIES COMPILE_FLAGS "${LIBOMP_CONFIGURED_CXXFLAGS}") set_source_files_properties(${LIBOMP_ASMFILES} ${LIBOMP_GNUASMFILES} PROPERTIES COMPILE_FLAGS "${LIBOMP_CONFIGURED_ASMFLAGS}") -# Remove any cmake-automatic linking of the standard C++ library. -# We neither need (nor want) the standard C++ library dependency even though we compile c++ files. -if(NOT ${LIBOMP_USE_STDCPPLIB}) - set(LIBOMP_LINKER_LANGUAGE C) - set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES) -else() - set(LIBOMP_LINKER_LANGUAGE CXX) -endif() - -if(UNIX) - set(LIBOMP_DL_LIBS ${CMAKE_DL_LIBS}) -endif() - # Disable libstdc++ assertions, even in an LLVM_ENABLE_ASSERTIONS build, to # avoid an unwanted dependency on libstdc++.so. add_compile_definitions(_GLIBCXX_NO_ASSERTIONS) @@ -174,17 +161,26 @@ if(NOT WIN32) endif() # Add the OpenMP library -libomp_get_ldflags(LIBOMP_CONFIGURED_LDFLAGS) +# First, create an OBJECT library with all the runtime sources. +# This allows the unittests later to access internal symbols which don't export +# in libomp. +add_library(obj.omp OBJECT ${LIBOMP_SOURCE_FILES}) +set_property(TARGET obj.omp PROPERTY FOLDER "OpenMP/Libraries") +set_property(TARGET obj.omp PROPERTY POSITION_INDEPENDENT_CODE ON) + +libomp_get_ldflags(LIBOMP_CONFIGURED_LDFLAGS) libomp_get_libflags(LIBOMP_CONFIGURED_LIBFLAGS) -# Build libomp library. Add LLVMSupport dependency if building in-tree with libomptarget profiling enabled. + +# Build libomp library. Add LLVMSupport dependency if building in-tree with +# libomptarget profiling enabled. if(OPENMP_STANDALONE_BUILD OR (NOT OPENMP_ENABLE_LIBOMP_PROFILING)) - add_library(omp ${LIBOMP_LIBRARY_KIND} ${LIBOMP_SOURCE_FILES}) + add_library(omp ${LIBOMP_LIBRARY_KIND} $) set_property(TARGET omp PROPERTY FOLDER "OpenMP/Libraries") # Linking command will include libraries in LIBOMP_CONFIGURED_LIBFLAGS target_link_libraries(omp ${LIBOMP_CONFIGURED_LIBFLAGS} ${LIBOMP_DL_LIBS}) else() - add_llvm_library(omp ${LIBOMP_LIBRARY_KIND} ${LIBOMP_SOURCE_FILES} PARTIAL_SOURCES_INTENDED + add_llvm_library(omp ${LIBOMP_LIBRARY_KIND} $ PARTIAL_SOURCES_INTENDED LINK_LIBS ${LIBOMP_CONFIGURED_LIBFLAGS} ${LIBOMP_DL_LIBS} LINK_COMPONENTS Support BUILDTREE_ONLY @@ -296,7 +292,7 @@ set(LIBOMPTARGET_OPENMP_HOST_RTL_FOLDER "${LIBOMP_LIBRARY_DIR}" CACHE STRING # objects depend on : .inc files add_custom_target(libomp-needed-headers DEPENDS kmp_i18n_id.inc kmp_i18n_default.inc) set_target_properties(libomp-needed-headers PROPERTIES FOLDER "OpenMP/Sourcegenning") -add_dependencies(omp libomp-needed-headers) +add_dependencies(obj.omp libomp-needed-headers) # Windows specific build rules if(WIN32) diff --git a/openmp/runtime/test/Unit/lit.cfg.py b/openmp/runtime/test/Unit/lit.cfg.py new file mode 100644 index 0000000000000..01bd3d961bf00 --- /dev/null +++ b/openmp/runtime/test/Unit/lit.cfg.py @@ -0,0 +1,22 @@ +# -*- Python -*- + +# Configuration file for the 'lit' test runner. + +import os +import subprocess + +import lit.formats + +# name: The name of this test suite. +config.name = "OpenMP-Unit" + +# suffixes: A list of file extensions to treat as test files. +config.suffixes = [] + +# test_source_root: The root path where tests are located. +# test_exec_root: The root path where tests should be run. +config.test_exec_root = config.openmp_unittests_dir +config.test_source_root = config.test_exec_root + +# testFormat: The test format to use to interpret tests. +config.test_format = lit.formats.GoogleTest(config.llvm_build_mode, "Tests") diff --git a/openmp/runtime/test/Unit/lit.site.cfg.py.in b/openmp/runtime/test/Unit/lit.site.cfg.py.in new file mode 100644 index 0000000000000..dd5c075b5f112 --- /dev/null +++ b/openmp/runtime/test/Unit/lit.site.cfg.py.in @@ -0,0 +1,8 @@ +@AUTO_GEN_COMMENT@ + +config.openmp_unittests_dir = "@CMAKE_CURRENT_BINARY_DIR@/../unittests" +config.llvm_build_mode = lit_config.substitute("@LLVM_BUILD_MODE@") + +# Let the main config do the real work. +lit_config.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/../test/Unit/lit.cfg.py") + diff --git a/openmp/runtime/test/lit.cfg b/openmp/runtime/test/lit.cfg index 72da1ba1411f8..f35794f09f641 100644 --- a/openmp/runtime/test/lit.cfg +++ b/openmp/runtime/test/lit.cfg @@ -41,6 +41,9 @@ config.name = 'libomp' # suffixes: A list of file extensions to treat as test files. config.suffixes = ['.c', '.cpp'] +# Exclude Unit tests - they are run separately via check-libomp-unit +config.excludes = ['Unit'] + if config.test_fortran_compiler: lit_config.note("OpenMP Fortran tests enabled") config.suffixes += ['.f90', '.F90'] diff --git a/openmp/runtime/unittests/CMakeLists.txt b/openmp/runtime/unittests/CMakeLists.txt new file mode 100644 index 0000000000000..400f411551e7b --- /dev/null +++ b/openmp/runtime/unittests/CMakeLists.txt @@ -0,0 +1,65 @@ +add_custom_target(OpenMPUnitTests) +set_target_properties(OpenMPUnitTests PROPERTIES FOLDER "OpenMP/Tests") + +if(WIN32 OR STUBS_LIBRARY) + message(WARNING "OpenMP unittests disabled due to stub library or Windows") + return() +endif() + +# Build a testing version of libomp that exports all symbols for unit tests. +# This library uses the same compiled objects as libomp, but with all symbols +# exported to allow testing internal functions. + +libomp_get_ldflags(LIBOMP_TEST_LDFLAGS FOR_UNITTESTS) +libomp_get_libflags(LIBOMP_TEST_LIBFLAGS) + +# Create the testing library from the same objects as libomp. +add_library(omp_testing SHARED $) +set_property(TARGET omp_testing PROPERTY FOLDER "OpenMP/Libraries") +target_link_libraries(omp_testing PUBLIC default_gtest) +target_link_libraries(omp_testing PUBLIC ${LIBOMP_TEST_LIBFLAGS} ${LIBOMP_DL_LIBS}) +set_target_properties(omp_testing PROPERTIES + PREFIX "" + SUFFIX "" + OUTPUT_NAME "libomp_testing${LIBOMP_LIBRARY_SUFFIX}" + LINK_FLAGS "${LIBOMP_TEST_LDFLAGS}" + LINKER_LANGUAGE ${LIBOMP_LINKER_LANGUAGE} + EXCLUDE_FROM_ALL TRUE # Don't build by default, only when tests need it +) +target_include_directories(omp_testing PUBLIC + ${LIBOMP_INCLUDE_DIR} + ${LIBOMP_SRC_DIR} +) + +# Make the targets default_gtest and default_gtest_main available. +build_gtest() + +function(add_openmp_unittest test_name) + add_unittest(OpenMPUnitTests ${test_name} ${ARGN}) + + # Link against the testing library which exports all symbols. + target_link_libraries(${test_name} PRIVATE omp_testing) + + if(TARGET default_gtest) + target_link_libraries(${test_name} PRIVATE default_gtest_main default_gtest) + else () + target_link_libraries(${test_name} PRIVATE llvm_gtest_main llvm_gtest) + endif () +endfunction() + +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/../test/Unit/lit.site.cfg.py.in + ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py + MAIN_CONFIG + ${CMAKE_CURRENT_SOURCE_DIR}/../test/Unit/lit.cfg.py +) + +add_openmp_testsuite( + check-libomp-unit + "Running libomp unit tests" + ${CMAKE_CURRENT_BINARY_DIR} + EXCLUDE_FROM_CHECK_ALL + DEPENDS OpenMPUnitTests +) + +add_subdirectory(String) diff --git a/openmp/runtime/unittests/README.md b/openmp/runtime/unittests/README.md new file mode 100644 index 0000000000000..974f877654776 --- /dev/null +++ b/openmp/runtime/unittests/README.md @@ -0,0 +1,9 @@ +# libomp Unit Tests + +Usage: +``` +cd /runtimes/runtimes-bins +ninja check-libomp-unit +``` + +Note: unit tests are currently not supported on Windows diff --git a/openmp/runtime/unittests/String/CMakeLists.txt b/openmp/runtime/unittests/String/CMakeLists.txt new file mode 100644 index 0000000000000..be072c76a10a6 --- /dev/null +++ b/openmp/runtime/unittests/String/CMakeLists.txt @@ -0,0 +1,3 @@ +add_openmp_unittest(StringTests + TestKmpStr.cpp +) diff --git a/openmp/runtime/unittests/String/TestKmpStr.cpp b/openmp/runtime/unittests/String/TestKmpStr.cpp new file mode 100644 index 0000000000000..87497b93c7538 --- /dev/null +++ b/openmp/runtime/unittests/String/TestKmpStr.cpp @@ -0,0 +1,331 @@ +//===- TestKmpStr.cpp - Tests for kmp_str utilities ----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "kmp_str.h" +#include "gtest/gtest.h" +#include + +namespace { + +// Test basic string buffer initialization +TEST(KmpStrTest, BufferInit) { + kmp_str_buf_t buffer; + __kmp_str_buf_init(&buffer); + + EXPECT_NE(buffer.str, nullptr); + EXPECT_GT(buffer.size, 0u); + EXPECT_EQ(buffer.used, 0); + EXPECT_EQ(buffer.str[0], '\0'); +} + +// Test string buffer clear +TEST(KmpStrTest, BufferClear) { + kmp_str_buf_t buffer; + __kmp_str_buf_init(&buffer); + __kmp_str_buf_print(&buffer, "test string"); + + EXPECT_GT(buffer.used, 0); + + __kmp_str_buf_clear(&buffer); + EXPECT_EQ(buffer.used, 0); + EXPECT_EQ(buffer.str[0], '\0'); + + __kmp_str_buf_free(&buffer); +} + +// Test string buffer print +TEST(KmpStrTest, BufferPrint) { + kmp_str_buf_t buffer; + __kmp_str_buf_init(&buffer); + + __kmp_str_buf_print(&buffer, "Hello, %s!", "World"); + + EXPECT_STREQ(buffer.str, "Hello, World!"); + EXPECT_EQ(buffer.used, 13); + + __kmp_str_buf_free(&buffer); +} + +// Test string buffer concatenation +TEST(KmpStrTest, BufferCat) { + kmp_str_buf_t buffer; + __kmp_str_buf_init(&buffer); + + __kmp_str_buf_cat(&buffer, "Hello", 5); + __kmp_str_buf_cat(&buffer, " ", 1); + __kmp_str_buf_cat(&buffer, "World", 5); + + EXPECT_STREQ(buffer.str, "Hello World"); + + __kmp_str_buf_free(&buffer); +} + +// Test string buffer reservation +TEST(KmpStrTest, BufferReserve) { + kmp_str_buf_t buffer; + __kmp_str_buf_init(&buffer); + + size_t large_size = 2048; + __kmp_str_buf_reserve(&buffer, large_size); + + EXPECT_GE(buffer.size, large_size); + + __kmp_str_buf_free(&buffer); +} + +// Test basic string to int conversion +TEST(KmpStrTest, BasicStrToInt) { + EXPECT_EQ(__kmp_basic_str_to_int("0"), 0); + EXPECT_EQ(__kmp_basic_str_to_int("1"), 1); + EXPECT_EQ(__kmp_basic_str_to_int("42"), 42); + EXPECT_EQ(__kmp_basic_str_to_int("123"), 123); +} + +// Test basic string to int conversion with invalid inputs +TEST(KmpStrTest, BasicStrToIntInvalid) { + // Empty string returns 0 + EXPECT_EQ(__kmp_basic_str_to_int(""), 0); + + // Strings starting with non-digits return 0 + EXPECT_EQ(__kmp_basic_str_to_int("abc"), 0); + EXPECT_EQ(__kmp_basic_str_to_int("hello"), 0); + EXPECT_EQ(__kmp_basic_str_to_int("xyz123"), 0); + + // Special characters return 0 + EXPECT_EQ(__kmp_basic_str_to_int("!@#"), 0); + EXPECT_EQ(__kmp_basic_str_to_int("+42"), 0); + EXPECT_EQ(__kmp_basic_str_to_int("-42"), 0); + + // Leading whitespace causes early stop (returns 0) + EXPECT_EQ(__kmp_basic_str_to_int(" 42"), 0); + EXPECT_EQ(__kmp_basic_str_to_int("\t42"), 0); + + // Mixed content: parses digits until first non-digit + EXPECT_EQ(__kmp_basic_str_to_int("123abc"), 123); + EXPECT_EQ(__kmp_basic_str_to_int("42 "), 42); + EXPECT_EQ(__kmp_basic_str_to_int("7.5"), 7); +} + +// Test string match +TEST(KmpStrTest, StrMatch) { + const char *data = "Hello World"; + + // Test exact match (len == 0) + EXPECT_TRUE(__kmp_str_match("Hello World", 0, data)); + EXPECT_FALSE(__kmp_str_match("Hello", 0, data)); // Not exact (data is longer) + + // Test prefix match (len < 0) + EXPECT_TRUE( + __kmp_str_match("Hello", -1, data)); // "Hello" is prefix of "Hello World" + EXPECT_FALSE(__kmp_str_match("World", -1, data)); // "World" is not a prefix + + // Test minimum length match (len > 0) + EXPECT_TRUE(__kmp_str_match("Hello", 5, data)); // At least 5 chars match + EXPECT_TRUE(__kmp_str_match("Hello", 3, data)); // At least 3 chars match + EXPECT_FALSE(__kmp_str_match("World", 5, data)); // First chars don't match +} + +// Test string contains +TEST(KmpStrTest, StrContains) { + const char *data = "Hello World"; + + EXPECT_TRUE(__kmp_str_contains("Hello", 5, data)); + EXPECT_TRUE(__kmp_str_contains("World", 5, data)); + EXPECT_TRUE(__kmp_str_contains("lo Wo", 5, data)); + EXPECT_FALSE(__kmp_str_contains("Goodbye", 7, data)); +} + +// Test string match for true/false values +TEST(KmpStrTest, MatchBool) { + // Test true values + EXPECT_TRUE(__kmp_str_match_true("true")); + EXPECT_TRUE(__kmp_str_match_true("TRUE")); + EXPECT_TRUE(__kmp_str_match_true("on")); + EXPECT_TRUE(__kmp_str_match_true("ON")); + EXPECT_TRUE(__kmp_str_match_true("1")); + EXPECT_TRUE(__kmp_str_match_true("yes")); + EXPECT_TRUE(__kmp_str_match_true("YES")); + + // Test false values + EXPECT_TRUE(__kmp_str_match_false("false")); + EXPECT_TRUE(__kmp_str_match_false("FALSE")); + EXPECT_TRUE(__kmp_str_match_false("off")); + EXPECT_TRUE(__kmp_str_match_false("OFF")); + EXPECT_TRUE(__kmp_str_match_false("0")); + EXPECT_TRUE(__kmp_str_match_false("no")); + EXPECT_TRUE(__kmp_str_match_false("NO")); + + // Note: Trailing characters after a valid prefix still match due to + // minimum-length prefix matching (e.g., "true" uses len=1, "yes" uses len=1) + EXPECT_TRUE(__kmp_str_match_true("true ")); + EXPECT_TRUE(__kmp_str_match_false("false ")); + EXPECT_TRUE(__kmp_str_match_true("truex")); + EXPECT_TRUE(__kmp_str_match_false("falsex")); + + // Partial prefixes also match due to minimum-length matching + EXPECT_TRUE(__kmp_str_match_true("t")); + EXPECT_TRUE(__kmp_str_match_true("tru")); + EXPECT_TRUE(__kmp_str_match_false("f")); + EXPECT_TRUE(__kmp_str_match_false("fals")); + EXPECT_TRUE(__kmp_str_match_true("y")); + EXPECT_TRUE(__kmp_str_match_true("yess")); + EXPECT_TRUE(__kmp_str_match_false("n")); + EXPECT_TRUE(__kmp_str_match_false("noo")); + + // "on" and "off" require at least 2 characters + EXPECT_TRUE(__kmp_str_match_true("on")); + EXPECT_TRUE(__kmp_str_match_false("of")); + EXPECT_TRUE(__kmp_str_match_false("off")); + + // "enabled" and "disabled" require exact match (len=0) + EXPECT_TRUE(__kmp_str_match_true("enabled")); + EXPECT_TRUE(__kmp_str_match_false("disabled")); +} + +// Test string match for invalid bool values +TEST(KmpStrTest, MatchBoolInvalid) { + // Empty string is neither true nor false + EXPECT_FALSE(__kmp_str_match_true("")); + EXPECT_FALSE(__kmp_str_match_false("")); + + // Random strings are neither true nor false + EXPECT_FALSE(__kmp_str_match_true("hello")); + EXPECT_FALSE(__kmp_str_match_false("hello")); + EXPECT_FALSE(__kmp_str_match_true("abc")); + EXPECT_FALSE(__kmp_str_match_false("abc")); + + // Numbers other than 0/1 are neither true nor false + EXPECT_FALSE(__kmp_str_match_true("2")); + EXPECT_FALSE(__kmp_str_match_false("2")); + EXPECT_FALSE(__kmp_str_match_true("42")); + EXPECT_FALSE(__kmp_str_match_false("42")); + EXPECT_FALSE(__kmp_str_match_true("-1")); + EXPECT_FALSE(__kmp_str_match_false("-1")); + + // Leading whitespace prevents matching + EXPECT_FALSE(__kmp_str_match_true(" true")); + EXPECT_FALSE(__kmp_str_match_false(" false")); + + // "on" and "off" require at least 2 characters + EXPECT_FALSE(__kmp_str_match_true("o")); + EXPECT_FALSE(__kmp_str_match_false("o")); + + // "enabled" and "disabled" require exact match (len=0) + EXPECT_FALSE(__kmp_str_match_true("enable")); + EXPECT_FALSE(__kmp_str_match_false("disable")); + + // True values don't match as false and vice versa + EXPECT_FALSE(__kmp_str_match_false("true")); + EXPECT_FALSE(__kmp_str_match_false("1")); + EXPECT_FALSE(__kmp_str_match_false("yes")); + EXPECT_FALSE(__kmp_str_match_true("false")); + EXPECT_FALSE(__kmp_str_match_true("0")); + EXPECT_FALSE(__kmp_str_match_true("no")); +} + +// Test string replace +TEST(KmpStrTest, StrReplace) { + char str[] = "Hello World"; + __kmp_str_replace(str, ' ', '_'); + EXPECT_STREQ(str, "Hello_World"); + + __kmp_str_replace(str, 'o', '0'); + EXPECT_STREQ(str, "Hell0_W0rld"); +} + +// Test string split +TEST(KmpStrTest, StrSplit) { + char str[] = "key=value"; + char *head = nullptr; + char *tail = nullptr; + + __kmp_str_split(str, '=', &head, &tail); + + EXPECT_STREQ(head, "key"); + EXPECT_STREQ(tail, "value"); +} + +// Test file name parsing +TEST(KmpStrTest, FileNameInit) { + const char *path = "/path/to/file.txt"; + kmp_str_fname_t fname; + __kmp_str_fname_init(&fname, path); + + EXPECT_NE(fname.path, nullptr); + EXPECT_STREQ(fname.path, path); + EXPECT_NE(fname.base, nullptr); + EXPECT_STREQ(fname.base, "file.txt"); + + __kmp_str_fname_free(&fname); +} + +// Test string format +TEST(KmpStrTest, StrFormat) { + char *result = __kmp_str_format("Number: %d, String: %s", 42, "test"); + + EXPECT_NE(result, nullptr); + EXPECT_STREQ(result, "Number: 42, String: test"); + + __kmp_str_free(&result); + EXPECT_EQ(result, nullptr); +} + +// Test string buffer concatenate buffers +TEST(KmpStrTest, BufferCatBuf) { + kmp_str_buf_t buf1, buf2; + __kmp_str_buf_init(&buf1); + __kmp_str_buf_init(&buf2); + + __kmp_str_buf_print(&buf1, "Hello"); + __kmp_str_buf_print(&buf2, " World"); + + __kmp_str_buf_catbuf(&buf1, &buf2); + + EXPECT_STREQ(buf1.str, "Hello World"); + + __kmp_str_buf_free(&buf1); + __kmp_str_buf_free(&buf2); +} + +// Test size string parsing +TEST(KmpStrTest, StrToSize) { + size_t result; + const char *error = nullptr; + + __kmp_str_to_size("100", &result, 1, &error); + EXPECT_EQ(error, nullptr); + EXPECT_EQ(result, 100u); + + __kmp_str_to_size("1K", &result, 1024, &error); + EXPECT_EQ(error, nullptr); + EXPECT_EQ(result, 1024u); + + __kmp_str_to_size("2M", &result, 1024, &error); + EXPECT_EQ(error, nullptr); + EXPECT_EQ(result, 2u * 1024u * 1024u); +} + +// Test uint string parsing +TEST(KmpStrTest, StrToUint) { + kmp_uint64 result; + const char *error = nullptr; + + __kmp_str_to_uint("0", &result, &error); + EXPECT_EQ(error, nullptr); + EXPECT_EQ(result, 0u); + + __kmp_str_to_uint("42", &result, &error); + EXPECT_EQ(error, nullptr); + EXPECT_EQ(result, 42u); + + __kmp_str_to_uint("1234567890", &result, &error); + EXPECT_EQ(error, nullptr); + EXPECT_EQ(result, 1234567890u); +} + +} // namespace