blob: 18a4ba408c86ce8506ef856c16741c22a8ced623 [file]
//===-- Unittests for putwchar --------------------------------------------===//
//
// 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 "hdr/errno_macros.h"
#include "hdr/stdint_proxy.h"
#include "hdr/wchar_macros.h" // For WEOF
#include "src/stdio/fclose.h"
#include "src/stdio/ferror.h"
#include "src/stdio/fopen.h"
#include "src/stdio/fread.h"
#include "src/stdio/stdout.h"
#include "src/wchar/fwide.h"
#include "src/wchar/putwchar.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
#include "test/UnitTest/Test.h"
using LlvmLibcPutwcharTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
TEST_F(LlvmLibcPutwcharTest, WriteASCII) {
auto FILENAME =
libc_make_test_file_path(APPEND_LIBC_TEST("putwchar_ascii.test"));
// Redirect stdout
::FILE *original_stdout = LIBC_NAMESPACE::stdout;
LIBC_NAMESPACE::stdout = LIBC_NAMESPACE::fopen(FILENAME, "w");
ASSERT_FALSE(LIBC_NAMESPACE::stdout == nullptr);
// Initial orientation
EXPECT_EQ(LIBC_NAMESPACE::fwide(LIBC_NAMESPACE::stdout, 0), 0);
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'a'), static_cast<wint_t>(L'a'));
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'b'), static_cast<wint_t>(L'b'));
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'c'), static_cast<wint_t>(L'c'));
// Orientation should be wide
EXPECT_GT(LIBC_NAMESPACE::fwide(LIBC_NAMESPACE::stdout, 0), 0);
// Restore stdout
ASSERT_EQ(LIBC_NAMESPACE::fclose(LIBC_NAMESPACE::stdout), 0);
LIBC_NAMESPACE::stdout = original_stdout;
// Read back raw bytes to verify
::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "r");
ASSERT_FALSE(file == nullptr);
unsigned char buffer[5] = {0};
ASSERT_EQ(LIBC_NAMESPACE::fread(buffer, 1, 3, file), size_t(3));
EXPECT_EQ(buffer[0], static_cast<unsigned char>('a'));
EXPECT_EQ(buffer[1], static_cast<unsigned char>('b'));
EXPECT_EQ(buffer[2], static_cast<unsigned char>('c'));
ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
}
TEST_F(LlvmLibcPutwcharTest, WriteUtf8) {
auto FILENAME =
libc_make_test_file_path(APPEND_LIBC_TEST("putwchar_utf8.test"));
// Redirect stdout
::FILE *original_stdout = LIBC_NAMESPACE::stdout;
LIBC_NAMESPACE::stdout = LIBC_NAMESPACE::fopen(FILENAME, "w");
ASSERT_FALSE(LIBC_NAMESPACE::stdout == nullptr);
// a -> L'a' (1-byte)
// ¢ -> L'¢' (2-byte)
// € -> L'€' (3-byte)
// 𐍈 -> L'𐍈' (4-byte)
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'a'), static_cast<wint_t>(L'a'));
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'¢'), static_cast<wint_t>(L'¢'));
#if WINT_MAX > 0xFFFF
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'€'), static_cast<wint_t>(L'€'));
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'𐍈'), static_cast<wint_t>(L'𐍈'));
constexpr size_t EXPECTED_BYTES = 10;
#else
constexpr size_t EXPECTED_BYTES = 3;
#endif
// Restore stdout
ASSERT_EQ(LIBC_NAMESPACE::fclose(LIBC_NAMESPACE::stdout), 0);
LIBC_NAMESPACE::stdout = original_stdout;
// Read back raw bytes and verify UTF-8 sequences exactly on disk
::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "r");
ASSERT_FALSE(file == nullptr);
unsigned char buffer[20] = {0};
ASSERT_EQ(LIBC_NAMESPACE::fread(buffer, 1, EXPECTED_BYTES, file),
EXPECTED_BYTES);
// a
EXPECT_EQ(buffer[0], static_cast<unsigned char>(0x61));
// ¢
EXPECT_EQ(buffer[1], static_cast<unsigned char>(0xC2));
EXPECT_EQ(buffer[2], static_cast<unsigned char>(0xA2));
#if WINT_MAX > 0xFFFF
// €
EXPECT_EQ(buffer[3], static_cast<unsigned char>(0xE2));
EXPECT_EQ(buffer[4], static_cast<unsigned char>(0x82));
EXPECT_EQ(buffer[5], static_cast<unsigned char>(0xAC));
// 𐍈
EXPECT_EQ(buffer[6], static_cast<unsigned char>(0xF0));
EXPECT_EQ(buffer[7], static_cast<unsigned char>(0x90));
EXPECT_EQ(buffer[8], static_cast<unsigned char>(0x8D));
EXPECT_EQ(buffer[9], static_cast<unsigned char>(0x88));
#endif
ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
}
TEST_F(LlvmLibcPutwcharTest, InvalidStream) {
auto FILENAME =
libc_make_test_file_path(APPEND_LIBC_TEST("putwchar_invalid.test"));
// Create the file first
::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
ASSERT_FALSE(file == nullptr);
ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
// Redirect stdout using a read-only file stream
::FILE *original_stdout = LIBC_NAMESPACE::stdout;
LIBC_NAMESPACE::stdout = LIBC_NAMESPACE::fopen(FILENAME, "r");
ASSERT_FALSE(LIBC_NAMESPACE::stdout == nullptr);
// Try to write to read-only stdout
ASSERT_ERRNO_SUCCESS();
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'a'), static_cast<wint_t>(WEOF));
ASSERT_ERRNO_EQ(EBADF);
EXPECT_NE(LIBC_NAMESPACE::ferror(LIBC_NAMESPACE::stdout), 0);
// Restore stdout
ASSERT_EQ(LIBC_NAMESPACE::fclose(LIBC_NAMESPACE::stdout), 0);
LIBC_NAMESPACE::stdout = original_stdout;
}
TEST_F(LlvmLibcPutwcharTest, ByteModeFailure) {
auto FILENAME =
libc_make_test_file_path(APPEND_LIBC_TEST("putwchar_bytemode.test"));
::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w+");
ASSERT_FALSE(file == nullptr);
// Redirect stdout
::FILE *original_stdout = LIBC_NAMESPACE::stdout;
LIBC_NAMESPACE::stdout = file;
// Orient to byte mode
EXPECT_LT(LIBC_NAMESPACE::fwide(LIBC_NAMESPACE::stdout, -1), 0);
// Writing should fail and set errno to EINVAL
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'a'), static_cast<wint_t>(WEOF));
ASSERT_ERRNO_EQ(EINVAL);
EXPECT_NE(LIBC_NAMESPACE::ferror(LIBC_NAMESPACE::stdout), 0);
// Restore stdout
ASSERT_EQ(LIBC_NAMESPACE::fclose(LIBC_NAMESPACE::stdout), 0);
LIBC_NAMESPACE::stdout = original_stdout;
}
TEST_F(LlvmLibcPutwcharTest, RealStdoutNoRedirection) {
// This test writes directly to the real un-redirected stdout.
// While it cannot be programmatically verified by the test runner,
// it is extremely useful for developers running tests manually in a terminal
// to visually verify characters are outputted correctly.
// Print: "putwchar test: [a¢€𐍈]\n"
constexpr wchar_t PREFIX[] = L"putwchar test: [";
for (size_t i = 0; PREFIX[i] != L'\0'; ++i) {
EXPECT_EQ(LIBC_NAMESPACE::putwchar(PREFIX[i]),
static_cast<wint_t>(PREFIX[i]));
}
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'a'), static_cast<wint_t>(L'a'));
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'¢'), static_cast<wint_t>(L'¢'));
#if WINT_MAX > 0xFFFF
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'€'), static_cast<wint_t>(L'€'));
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'𐍈'), static_cast<wint_t>(L'𐍈'));
#endif
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L']'), static_cast<wint_t>(L']'));
EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'\n'), static_cast<wint_t>(L'\n'));
}