[libc][test] Add EXPECT_STREQ and ASSERT_STREQ macros for integration tests (#212729)

Add EXPECT_STREQ and ASSERT_STREQ macros to IntegrationTest/test.h using
cpp::string_view. Note that, unlike the unit test macro (but like
googletest), this version treats NULL as distinct from "". The
inconsistency is unfortunate, but I think it's important as the code is
used for testing functions like getenv(), where "" and NULL have very
different meanings.

We should probably follow this up with a change to make the unit test
macro behave the same way.

Update integration tests to use the new macros instead of handrolled
string equality functions (my_streq), strcmp, inline_strcmp, or
string_view wrappers.
This commit is contained in:
Pavel Labath 2026-07-30 15:46:02 +02:00 committed by GitHub
parent 6e9a51c805
commit cc623d6e99
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 66 additions and 124 deletions

View file

@ -14,6 +14,7 @@ add_object_library(
libc.hdr.stdint_proxy
libc.src.__support.OSUtil.osutil
libc.src.__support.CPP.atomic
libc.src.__support.CPP.string_view
libc.src.__support.macros.properties.architectures
${arch_specific_deps}
)

View file

@ -9,6 +9,7 @@
#ifndef LLVM_LIBC_UTILS_INTEGRATION_TEST_TEST_H
#define LLVM_LIBC_UTILS_INTEGRATION_TEST_TEST_H
#include "src/__support/CPP/string_view.h"
#include "src/__support/OSUtil/exit.h"
#include "src/__support/OSUtil/io.h"
#include "src/__support/macros/properties/architectures.h"
@ -46,6 +47,18 @@
LIBC_NAMESPACE::internal::exit(127); \
}
#define __CHECK_STREQ(file, line, val1, val2, should_exit) \
if (const char *__val1_str = (val1), *__val2_str = (val2); \
__val1_str != __val2_str && \
(!__val1_str || !__val2_str || \
LIBC_NAMESPACE::cpp::string_view(__val1_str) != \
LIBC_NAMESPACE::cpp::string_view(__val2_str))) { \
LIBC_NAMESPACE::write_to_stderr(file ":" __AS_STRING( \
line) ": Expected '" #val1 "' to be equal to '" #val2 "'\n"); \
if (should_exit) \
LIBC_NAMESPACE::internal::exit(127); \
}
////////////////////////////////////////////////////////////////////////////////
// Boolean checks are handled as comparison to the true / false values.
@ -66,6 +79,14 @@
#define ASSERT_NE(val1, val2) \
__CHECK_NE(__FILE__, __LINE__, (val1), (val2), true)
////////////////////////////////////////////////////////////////////////////////
// String equality.
#define EXPECT_STREQ(val1, val2) \
__CHECK_STREQ(__FILE__, __LINE__, (val1), (val2), false)
#define ASSERT_STREQ(val1, val2) \
__CHECK_STREQ(__FILE__, __LINE__, (val1), (val2), true)
////////////////////////////////////////////////////////////////////////////////
// Errno checks.

View file

@ -7,7 +7,6 @@
//===----------------------------------------------------------------------===//
#include "hdr/stdint_proxy.h" // uintptr_t
#include "src/__support/CPP/string_view.h"
#include "src/pthread/pthread_create.h"
#include "src/pthread/pthread_getname_np.h"
#include "src/pthread/pthread_join.h"
@ -22,8 +21,6 @@
#include <errno.h>
#include <pthread.h>
using string_view = LIBC_NAMESPACE::cpp::string_view;
char child_thread_name_buffer[16];
pthread_mutex_t mutex;
@ -49,8 +46,7 @@ TEST_MAIN() {
ASSERT_EQ(
LIBC_NAMESPACE::pthread_getname_np(main_thread, thread_name_buffer, 16),
0);
ASSERT_EQ(string_view(MAIN_THREAD_NAME),
string_view(reinterpret_cast<const char *>(thread_name_buffer)));
ASSERT_STREQ(MAIN_THREAD_NAME, thread_name_buffer);
pthread_t th;
ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, child_func, nullptr),
@ -59,8 +55,7 @@ TEST_MAIN() {
const char CHILD_THREAD_NAME[] = "child_thread";
ASSERT_EQ(LIBC_NAMESPACE::pthread_setname_np(th, CHILD_THREAD_NAME), 0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_getname_np(th, thread_name_buffer, 16), 0);
ASSERT_EQ(string_view(CHILD_THREAD_NAME),
string_view(reinterpret_cast<const char *>(thread_name_buffer)));
ASSERT_STREQ(CHILD_THREAD_NAME, thread_name_buffer);
ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_unlock(&mutex), 0);
@ -68,9 +63,7 @@ TEST_MAIN() {
ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
ASSERT_EQ(uintptr_t(retval), uintptr_t(nullptr));
// Make sure that the child thread saw it name correctly.
ASSERT_EQ(
string_view(CHILD_THREAD_NAME),
string_view(reinterpret_cast<const char *>(child_thread_name_buffer)));
ASSERT_STREQ(CHILD_THREAD_NAME, child_thread_name_buffer);
LIBC_NAMESPACE::pthread_mutex_destroy(&mutex);

View file

@ -14,23 +14,6 @@
#include "test/IntegrationTest/test.h"
static bool my_streq(const char *lhs, const char *rhs) {
if (lhs == rhs)
return true;
if (((lhs == static_cast<char *>(nullptr)) &&
(rhs != static_cast<char *>(nullptr))) ||
((lhs != static_cast<char *>(nullptr)) &&
(rhs == static_cast<char *>(nullptr)))) {
return false;
}
const char *l, *r;
for (l = lhs, r = rhs; *l != '\0' && *r != '\0'; ++l, ++r)
if (*l != *r)
return false;
return *l == '\0' && *r == '\0';
}
static int my_strlen(const char *str) {
const char *other = str;
while (*other)
@ -40,13 +23,13 @@ static int my_strlen(const char *str) {
TEST_MAIN(int argc, char **argv, [[maybe_unused]] char **envp) {
ASSERT_EQ(argc, 5);
ASSERT_TRUE(my_streq(argv[1], "%s %c %d"));
ASSERT_STREQ(argv[1], "%s %c %d");
ASSERT_EQ(my_strlen(argv[1]), 8);
ASSERT_TRUE(my_streq(argv[2], "First arg"));
ASSERT_STREQ(argv[2], "First arg");
ASSERT_EQ(my_strlen(argv[2]), 9);
ASSERT_TRUE(my_streq(argv[3], "a"));
ASSERT_STREQ(argv[3], "a");
ASSERT_EQ(my_strlen(argv[3]), 1);
ASSERT_TRUE(my_streq(argv[4], "0"));
ASSERT_STREQ(argv[4], "0");
ASSERT_EQ(my_strlen(argv[4]), 1);
#ifndef INTEGRATION_DISABLE_PRINTF
@ -54,7 +37,7 @@ TEST_MAIN(int argc, char **argv, [[maybe_unused]] char **envp) {
ASSERT_EQ(
LIBC_NAMESPACE::sprintf(buf, argv[1], argv[2], argv[3][0], argv[4][0]),
14);
ASSERT_TRUE(my_streq(buf, "First arg a 48"));
ASSERT_STREQ(buf, "First arg a 48");
#endif
return 0;

View file

@ -28,7 +28,6 @@ if(${LIBC_TARGET_OS} STREQUAL "linux")
libc.src.errno.errno
libc.src.stdlib.getenv
libc.src.stdlib.setenv
libc.src.string.strcmp
)
add_integration_test(
@ -41,7 +40,6 @@ if(${LIBC_TARGET_OS} STREQUAL "linux")
libc.src.errno.errno
libc.src.stdlib.getenv
libc.src.stdlib.putenv
libc.src.string.strcmp
libc.src.unistd.environ
)
@ -58,7 +56,6 @@ if(${LIBC_TARGET_OS} STREQUAL "linux")
libc.src.stdlib.setenv
libc.src.stdlib.unsetenv
libc.src.string.memory_utils.inline_memcpy
libc.src.string.strcmp
libc.src.unistd.environ
)

View file

@ -7,26 +7,17 @@
//===----------------------------------------------------------------------===//
#include "src/stdlib/getenv.h"
#include "src/string/memory_utils/inline_strcmp.h"
#include "test/IntegrationTest/test.h"
TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
[[maybe_unused]] char **envp) {
auto comp = [](char l, char r) -> int { return l - r; };
ASSERT_TRUE(LIBC_NAMESPACE::getenv("") == nullptr);
ASSERT_TRUE(LIBC_NAMESPACE::getenv("=") == nullptr);
ASSERT_TRUE(LIBC_NAMESPACE::getenv("MISSING ENV VARIABLE") == nullptr);
ASSERT_FALSE(LIBC_NAMESPACE::getenv("PATH") == nullptr);
ASSERT_EQ(LIBC_NAMESPACE::inline_strcmp(LIBC_NAMESPACE::getenv("FRANCE"),
"Paris", comp),
0);
ASSERT_NE(LIBC_NAMESPACE::inline_strcmp(LIBC_NAMESPACE::getenv("FRANCE"),
"Berlin", comp),
0);
ASSERT_EQ(LIBC_NAMESPACE::inline_strcmp(LIBC_NAMESPACE::getenv("GERMANY"),
"Berlin", comp),
0);
ASSERT_STREQ(LIBC_NAMESPACE::getenv("FRANCE"), "Paris");
ASSERT_STREQ(LIBC_NAMESPACE::getenv("GERMANY"), "Berlin");
ASSERT_TRUE(LIBC_NAMESPACE::getenv("FRANC") == nullptr);
ASSERT_TRUE(LIBC_NAMESPACE::getenv("FRANCE1") == nullptr);

View file

@ -13,7 +13,6 @@
#include "src/stdlib/getenv.h"
#include "src/stdlib/putenv.h"
#include "src/string/strcmp.h"
#include "src/unistd/environ.h"
#include "test/IntegrationTest/test.h"
@ -31,7 +30,7 @@ TEST_MAIN() {
ASSERT_EQ(LIBC_NAMESPACE::putenv(set_var), 0);
char *value = LIBC_NAMESPACE::getenv("PUTENV_TEST");
ASSERT_TRUE(value != nullptr);
ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, "hello"), 0);
ASSERT_STREQ(value, "hello");
}
// Test: Overwrite existing variable
@ -39,7 +38,7 @@ TEST_MAIN() {
ASSERT_EQ(LIBC_NAMESPACE::putenv(replace_var), 0);
char *value = LIBC_NAMESPACE::getenv("PUTENV_TEST");
ASSERT_TRUE(value != nullptr);
ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, "world"), 0);
ASSERT_STREQ(value, "world");
}
// Test: The pointer itself is used (not a copy)
@ -54,7 +53,7 @@ TEST_MAIN() {
ASSERT_EQ(LIBC_NAMESPACE::putenv(empty_value), 0);
char *value = LIBC_NAMESPACE::getenv("PUTENV_EMPTY");
ASSERT_TRUE(value != nullptr);
ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, ""), 0);
ASSERT_STREQ(value, "");
}
// Test: Special characters in value
@ -62,7 +61,7 @@ TEST_MAIN() {
ASSERT_EQ(LIBC_NAMESPACE::putenv(special_chars), 0);
char *value = LIBC_NAMESPACE::getenv("PUTENV_SPECIAL");
ASSERT_TRUE(value != nullptr);
ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, "!@#$%^&*()"), 0);
ASSERT_STREQ(value, "!@#$%^&*()");
}
// Test: No '=' removes the variable (POSIX behavior)

View file

@ -13,7 +13,6 @@
#include "src/stdlib/getenv.h"
#include "src/stdlib/setenv.h"
#include "src/string/strcmp.h"
#include "test/IntegrationTest/test.h"
@ -30,7 +29,7 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
// Verify it was set
char *value = LIBC_NAMESPACE::getenv("SETENV_TEST_VAR");
ASSERT_NE(value, nullptr);
ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, "test_value"), 0);
ASSERT_STREQ(value, "test_value");
}
// Test: OverwriteExisting
@ -38,16 +37,12 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
// Set initial value
ASSERT_EQ(LIBC_NAMESPACE::setenv("OVERWRITE_VAR", "original", 1), 0);
ASSERT_ERRNO_SUCCESS();
ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("OVERWRITE_VAR"),
"original"),
0);
ASSERT_STREQ(LIBC_NAMESPACE::getenv("OVERWRITE_VAR"), "original");
// Overwrite with new value (overwrite = 1)
ASSERT_EQ(LIBC_NAMESPACE::setenv("OVERWRITE_VAR", "replaced", 1), 0);
ASSERT_ERRNO_SUCCESS();
ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("OVERWRITE_VAR"),
"replaced"),
0);
ASSERT_STREQ(LIBC_NAMESPACE::getenv("OVERWRITE_VAR"), "replaced");
}
// Test: NoOverwriteFlag
@ -55,23 +50,17 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
// Set initial value
ASSERT_EQ(LIBC_NAMESPACE::setenv("NO_OVERWRITE_VAR", "original", 1), 0);
ASSERT_ERRNO_SUCCESS();
ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("NO_OVERWRITE_VAR"),
"original"),
0);
ASSERT_STREQ(LIBC_NAMESPACE::getenv("NO_OVERWRITE_VAR"), "original");
// Try to set with overwrite = 0 (should not change)
ASSERT_EQ(LIBC_NAMESPACE::setenv("NO_OVERWRITE_VAR", "ignored", 0), 0);
ASSERT_ERRNO_SUCCESS();
ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("NO_OVERWRITE_VAR"),
"original"),
0);
ASSERT_STREQ(LIBC_NAMESPACE::getenv("NO_OVERWRITE_VAR"), "original");
// Verify it still works with overwrite = 1
ASSERT_EQ(LIBC_NAMESPACE::setenv("NO_OVERWRITE_VAR", "changed", 1), 0);
ASSERT_ERRNO_SUCCESS();
ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("NO_OVERWRITE_VAR"),
"changed"),
0);
ASSERT_STREQ(LIBC_NAMESPACE::getenv("NO_OVERWRITE_VAR"), "changed");
}
// Note: passing nullptr for name or value is undefined behavior per POSIX.
@ -101,7 +90,7 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
char *value = LIBC_NAMESPACE::getenv("EMPTY_VALUE_VAR");
ASSERT_NE(value, nullptr);
ASSERT_EQ(LIBC_NAMESPACE::strcmp(value, ""), 0);
ASSERT_STREQ(value, "");
}
// Test: MultipleVariables
@ -116,12 +105,9 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
ASSERT_ERRNO_SUCCESS();
// Verify all are set correctly
ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("VAR1"), "value1"),
0);
ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("VAR2"), "value2"),
0);
ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("VAR3"), "value3"),
0);
ASSERT_STREQ(LIBC_NAMESPACE::getenv("VAR1"), "value1");
ASSERT_STREQ(LIBC_NAMESPACE::getenv("VAR2"), "value2");
ASSERT_STREQ(LIBC_NAMESPACE::getenv("VAR3"), "value3");
}
// Test: LongValues
@ -134,9 +120,7 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
ASSERT_EQ(LIBC_NAMESPACE::setenv(long_name, long_value, 1), 0);
ASSERT_ERRNO_SUCCESS();
ASSERT_EQ(
LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv(long_name), long_value),
0);
ASSERT_STREQ(LIBC_NAMESPACE::getenv(long_name), long_value);
}
// Test: SpecialCharacters
@ -144,9 +128,7 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
// Test with special characters in value (but not in name)
ASSERT_EQ(LIBC_NAMESPACE::setenv("SPECIAL_CHARS", "!@#$%^&*()", 1), 0);
ASSERT_ERRNO_SUCCESS();
ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("SPECIAL_CHARS"),
"!@#$%^&*()"),
0);
ASSERT_STREQ(LIBC_NAMESPACE::getenv("SPECIAL_CHARS"), "!@#$%^&*()");
}
// Test: ReplaceMultipleTimes
@ -154,21 +136,15 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
// Replace the same variable multiple times
ASSERT_EQ(LIBC_NAMESPACE::setenv("MULTI_REPLACE", "value1", 1), 0);
ASSERT_ERRNO_SUCCESS();
ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("MULTI_REPLACE"),
"value1"),
0);
ASSERT_STREQ(LIBC_NAMESPACE::getenv("MULTI_REPLACE"), "value1");
ASSERT_EQ(LIBC_NAMESPACE::setenv("MULTI_REPLACE", "value2", 1), 0);
ASSERT_ERRNO_SUCCESS();
ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("MULTI_REPLACE"),
"value2"),
0);
ASSERT_STREQ(LIBC_NAMESPACE::getenv("MULTI_REPLACE"), "value2");
ASSERT_EQ(LIBC_NAMESPACE::setenv("MULTI_REPLACE", "value3", 1), 0);
ASSERT_ERRNO_SUCCESS();
ASSERT_EQ(LIBC_NAMESPACE::strcmp(LIBC_NAMESPACE::getenv("MULTI_REPLACE"),
"value3"),
0);
ASSERT_STREQ(LIBC_NAMESPACE::getenv("MULTI_REPLACE"), "value3");
}
return 0;

View file

@ -17,7 +17,6 @@
#include "src/stdlib/setenv.h"
#include "src/stdlib/unsetenv.h"
#include "src/string/memory_utils/inline_memcpy.h"
#include "src/string/strcmp.h"
#include "src/unistd/environ.h"
#include "test/IntegrationTest/test.h"
@ -59,13 +58,13 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
// Test: Unset then re-set
{
ASSERT_EQ(setenv("REUSE_VAR", "first", 1), 0);
ASSERT_EQ(strcmp(getenv("REUSE_VAR"), "first"), 0);
ASSERT_STREQ(getenv("REUSE_VAR"), "first");
ASSERT_EQ(unsetenv("REUSE_VAR"), 0);
ASSERT_TRUE(getenv("REUSE_VAR") == nullptr);
ASSERT_EQ(setenv("REUSE_VAR", "second", 1), 0);
ASSERT_EQ(strcmp(getenv("REUSE_VAR"), "second"), 0);
ASSERT_STREQ(getenv("REUSE_VAR"), "second");
}
// Test: Unset multiple variables
@ -80,8 +79,8 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
ASSERT_TRUE(getenv("MULTI_B") == nullptr);
ASSERT_TRUE(getenv("MULTI_C") != nullptr);
ASSERT_EQ(strcmp(getenv("MULTI_A"), "a"), 0);
ASSERT_EQ(strcmp(getenv("MULTI_C"), "c"), 0);
ASSERT_STREQ(getenv("MULTI_A"), "a");
ASSERT_STREQ(getenv("MULTI_C"), "c");
}
// Test: Unset same variable twice is harmless
@ -98,7 +97,7 @@ TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
// Verify it is in environ
bool found = false;
for (char **env = environ; *env != nullptr; ++env) {
if (strcmp(*env, "ENV_CHECK=val") == 0) {
if (cpp::string_view(*env) == "ENV_CHECK=val") {
found = true;
break;
}

View file

@ -8,27 +8,18 @@
#include "test/IntegrationTest/test.h"
static bool my_streq(const char *lhs, const char *rhs) {
const char *l, *r;
for (l = lhs, r = rhs; *l != '\0' && *r != '\0'; ++l, ++r)
if (*l != *r)
return false;
return *l == '\0' && *r == '\0';
}
TEST_MAIN(int argc, char **argv, char **envp) {
ASSERT_TRUE(argc == 4);
ASSERT_TRUE(my_streq(argv[1], "1"));
ASSERT_TRUE(my_streq(argv[2], "2"));
ASSERT_TRUE(my_streq(argv[3], "3"));
ASSERT_STREQ(argv[1], "1");
ASSERT_STREQ(argv[2], "2");
ASSERT_STREQ(argv[3], "3");
bool found_france = false;
bool found_germany = false;
for (; *envp != nullptr; ++envp) {
if (my_streq(*envp, "FRANCE=Paris"))
if (LIBC_NAMESPACE::cpp::string_view(*envp) == "FRANCE=Paris")
found_france = true;
if (my_streq(*envp, "GERMANY=Berlin"))
if (LIBC_NAMESPACE::cpp::string_view(*envp) == "GERMANY=Berlin")
found_germany = true;
}

View file

@ -8,27 +8,18 @@
#include "test/IntegrationTest/test.h"
static bool my_streq(const char *lhs, const char *rhs) {
const char *l, *r;
for (l = lhs, r = rhs; *l != '\0' && *r != '\0'; ++l, ++r)
if (*l != *r)
return false;
return *l == '\0' && *r == '\0';
}
TEST_MAIN(int argc, char **argv, char **envp) {
ASSERT_TRUE(argc == 4);
ASSERT_TRUE(my_streq(argv[1], "1"));
ASSERT_TRUE(my_streq(argv[2], "2"));
ASSERT_TRUE(my_streq(argv[3], "3"));
ASSERT_STREQ(argv[1], "1");
ASSERT_STREQ(argv[2], "2");
ASSERT_STREQ(argv[3], "3");
bool found_france = false;
bool found_germany = false;
for (; *envp != nullptr; ++envp) {
if (my_streq(*envp, "FRANCE=Paris"))
if (LIBC_NAMESPACE::cpp::string_view(*envp) == "FRANCE=Paris")
found_france = true;
if (my_streq(*envp, "GERMANY=Berlin"))
if (LIBC_NAMESPACE::cpp::string_view(*envp) == "GERMANY=Berlin")
found_germany = true;
}