diff --git a/openmp/runtime/CMakeLists.txt b/openmp/runtime/CMakeLists.txt index 93eb14f10a50a..4597a03b0a255 100644 --- a/openmp/runtime/CMakeLists.txt +++ b/openmp/runtime/CMakeLists.txt @@ -469,6 +469,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 5dd7f4b33612d..791f250a3b9ec 100644 --- a/openmp/runtime/src/CMakeLists.txt +++ b/openmp/runtime/src/CMakeLists.txt @@ -174,17 +174,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 +305,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/unittests/CMakeLists.txt b/openmp/runtime/unittests/CMakeLists.txt new file mode 100644 index 0000000000000..3c56512bcf120 --- /dev/null +++ b/openmp/runtime/unittests/CMakeLists.txt @@ -0,0 +1,76 @@ +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) +libomp_get_libflags(LIBOMP_TEST_LIBFLAGS) +# Replace the libomp exports with the test exports exporting all symbols. +if(LIBOMP_HAVE_VERSION_SCRIPT_FLAG) + string(REPLACE "${LIBOMP_SRC_DIR}/exports_so.txt" + "${LIBOMP_SRC_DIR}/exports_test_so.txt" + LIBOMP_TEST_LDFLAGS "${LIBOMP_TEST_LDFLAGS}") +endif() +# Duplicate setting of LIBOMP_LINKER_LANGUAGE to match libomp. +if(NOT ${LIBOMP_USE_STDCPPLIB}) + set(LIBOMP_LINKER_LANGUAGE C) + set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES) +else() + set(LIBOMP_LINKER_LANGUAGE CXX) +endif() + +# 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..a22a729da9272 --- /dev/null +++ b/openmp/runtime/unittests/String/TestKmpStr.cpp @@ -0,0 +1,239 @@ +//===- 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 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")); +} + +// 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