RMUL2025/lib/printf/test/test_suite.cpp

449 lines
14 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// @author (c) Eyal Rozenberg <eyalroz1@gmx.com>
// 2021-2022, Haifa, Palestine/Israel
// \author (c) Marco Paland (info@paland.com)
// 2017-2019, PALANDesign Hannover, Germany
//
// \license The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// \brief printf unit tests
//
///////////////////////////////////////////////////////////////////////////////
#define PRINTF_VISIBILITY static
#if PRINTF_INCLUDE_CONFIG_H
#include <printf_config.h>
#endif
#include <printf/printf.c>
#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD
// Disable aliasing so as not to interfere with the standard library headers
# undef printf
# undef sprintf_
# undef vsprintf_
# undef snprintf_
# undef vsnprintf_
# undef vprintf_
#endif
// use the 'catch' test framework
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include <cstring>
#include <sstream>
#include <cmath>
#include <limits>
#include <climits>
#include <iostream>
#include <iomanip>
#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) || defined(__MINGW32__)
#include <sys/types.h>
#elif defined(_WIN32)
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#else
// Let's just cross our fingers and hope `ssize_t` is defined.
#endif
#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_SOFT
// Re-enable aliasing
# define printf printf_
# define sprintf sprintf_
# define vsprintf vsprintf_
# define snprintf snprintf_
# define vsnprintf vsnprintf_
# define vprintf vprintf_
#endif
#define CAPTURE_AND_PRINT(printer_, ...) \
do { \
INFO( #printer_ << \
" arguments (ignore the equations; interpret \"expr" \
"\" := expr\" as just \"expr\"): "); \
CAPTURE( __VA_ARGS__); \
printer_(__VA_ARGS__); \
} while(0)
#define CAPTURE_AND_PRINT_WITH_RETVAL(retval, printer_, ...) \
do { \
INFO( #printer_ << \
" arguments (ignore the equations; interpret \"expr" \
"\" := expr\" as just \"expr\"): "); \
CAPTURE( __VA_ARGS__); \
retval = printer_(__VA_ARGS__); \
} while(0)
#define PRINTING_CHECK_WITH_BUF_SIZE(expected_, dummy, printer_, buffer_, buffer_size, ...) \
do { \
INFO( #printer_ << " arguments, replicated ( \"arg := arg\" " \
"):\n----"); \
CAPTURE( __VA_ARGS__); \
std::memset(buffer_, 0xCC, base_buffer_size); \
printer_(buffer_, buffer_size, __VA_ARGS__); \
if (!strcmp(buffer_, expected_)) { \
buffer_[strlen(expected_) + 1] = '\0'; \
} \
INFO( "----"); \
INFO( "Resulting buffer contents: " << '"' << buffer_ << '"'); \
CHECK(!strcmp(buffer_, expected_)); \
} while(0)
#define PRINTING_CHECK(expected_, dummy, printer_, buffer_, ...) \
do { \
INFO( #printer_ << " arguments, replicated ( \"arg := arg\" " \
"):\n----"); \
CAPTURE( __VA_ARGS__); \
std::memset(buffer_, 0xCC, base_buffer_size); \
printer_(buffer_, __VA_ARGS__); \
if (!strcmp(buffer_, expected_)) { \
buffer_[strlen(expected_) + 1] = '\0'; \
} \
INFO( "----"); \
INFO( "Resulting buffer contents: " << '"' << buffer_ << '"'); \
CHECK(!strcmp(buffer_, expected_)); \
} while(0)
// Multi-compiler-compatible local warning suppression
#if defined(_MSC_VER)
#define DISABLE_WARNING_PUSH __pragma(warning( push ))
#define DISABLE_WARNING_POP __pragma(warning( pop ))
#define DISABLE_WARNING(warningNumber) __pragma(warning( disable : warningNumber ))
// TODO: find the right warning number for this
#define DISABLE_WARNING_PRINTF_FORMAT
#define DISABLE_WARNING_PRINTF_FORMAT_EXTRA_ARGS
#define DISABLE_WARNING_PRINTF_FORMAT_OVERFLOW
#define DISABLE_WARNING_PRINTF_FORMAT_INVALID_SPECIFIER
#elif defined(__GNUC__) || defined(__clang__)
#define DO_PRAGMA(X) _Pragma(#X)
#define DISABLE_WARNING_PUSH DO_PRAGMA(GCC diagnostic push)
#define DISABLE_WARNING_POP DO_PRAGMA(GCC diagnostic pop)
#define DISABLE_WARNING(warningName) DO_PRAGMA(GCC diagnostic ignored #warningName)
#define DISABLE_WARNING_PRINTF_FORMAT DISABLE_WARNING(-Wformat)
#define DISABLE_WARNING_PRINTF_FORMAT_EXTRA_ARGS DISABLE_WARNING(-Wformat-extra-args)
#if defined(__clang__)
#define DISABLE_WARNING_PRINTF_FORMAT_OVERFLOW
#define DISABLE_WARNING_PRINTF_FORMAT_INVALID_SPECIFIER DISABLE_WARNING(-Wformat-invalid-specifier)
#else
#define DISABLE_WARNING_PRINTF_FORMAT_OVERFLOW DISABLE_WARNING(-Wformat-overflow)
#define DISABLE_WARNING_PRINTF_FORMAT_INVALID_SPECIFIER
#endif
#else
#define DISABLE_WARNING_PUSH
#define DISABLE_WARNING_POP
#define DISABLE_WARNING_PRINTF_FORMAT
#define DISABLE_WARNING_PRINTF_FORMAT_EXTRA_ARGS
#define DISABLE_WARNING_PRINTF_FORMAT_INVALID_SPECIFIER
#endif
#ifdef TEST_WITH_NON_STANDARD_FORMAT_STRINGS
DISABLE_WARNING_PUSH
DISABLE_WARNING_PRINTF_FORMAT
DISABLE_WARNING_PRINTF_FORMAT_EXTRA_ARGS
DISABLE_WARNING_PRINTF_FORMAT_INVALID_SPECIFIER
#endif
#if defined(_MSC_VER)
DISABLE_WARNING(4996) // Discouragement of use of std::sprintf()
DISABLE_WARNING(4310) // Casting to smaller type
DISABLE_WARNING(4127) // Constant conditional expression
#endif
constexpr const size_t base_buffer_size { 100 };
// This macro is idempotent here, but for other platforms may
// be defined differently
#define mkstr(_str) _str
// dummy putchar
static char printf_buffer[base_buffer_size];
static size_t printf_idx = 0U;
void putchar_(char character)
{
printf_buffer[printf_idx++] = character;
}
void _out_fct(char character, void* arg)
{
(void)arg;
printf_buffer[printf_idx++] = character;
}
#ifndef STRINGIFY
#define STRINGIFY(_x) #_x
#endif
#define PRINTF_TEST_CASE(unstringified_name) TEST_CASE(STRINGIFY(unstringified_name), "[]")
PRINTF_TEST_CASE(printf) {
printf_idx = 0U;
memset(printf_buffer, 0xCC, base_buffer_size);
INFO("printf_ format string and arguments: ");
CAPTURE("% d", 4232);
CHECK(printf_("% d", 4232) == 5);
INFO("printf_ format string and arguments: ");
CAPTURE("% d", 4232);
CHECK(printf_buffer[5] == (char)0xCC);
printf_buffer[5] = 0;
INFO("printf_ format string and arguments: ");
CAPTURE("% d", 4232);
CHECK(!strcmp(printf_buffer, " 4232"));
}
PRINTF_TEST_CASE(fctprintf) {
printf_idx = 0U;
memset(printf_buffer, 0xCC, base_buffer_size);
fctprintf(&_out_fct, nullptr, "This is a test of %X", 0x12EFU);
INFO("fctprintf format string and arguments: ");
CAPTURE("This is a test of %X", 0x12EFU);
CHECK(!strncmp(printf_buffer, "This is a test of 12EF", 22U));
CHECK(printf_buffer[22] == (char)0xCC);
}
// output function type
typedef void (*out_fct_type_)(char character, void* arg);
static void vfctprintf_builder_1(out_fct_type_ f, char* buffer, ...)
{
va_list args;
va_start(args, buffer);
CAPTURE_AND_PRINT(vfctprintf, f, nullptr, "This is a test of %X", args);
va_end(args);
}
PRINTF_TEST_CASE(vfctprintf) {
printf_idx = 0U;
memset(printf_buffer, 0xCC, base_buffer_size);
vfctprintf_builder_1(&_out_fct, nullptr, 0x12EFU);
CHECK(!strncmp(printf_buffer, "This is a test of 12EF", 22U));
CHECK(printf_buffer[22] == (char)0xCC);
}
PRINTF_TEST_CASE(snprintf_) {
char buffer[base_buffer_size];
PRINTING_CHECK_WITH_BUF_SIZE("-1000", ==, snprintf_, buffer, base_buffer_size, "%d", -1000);
PRINTING_CHECK_WITH_BUF_SIZE("-1", ==, snprintf_, buffer, 3U, "%d", -1000);
}
static void vprintf_builder_1(char* buffer, ...)
{
va_list args;
va_start(args, buffer);
vprintf_("%d", args);
va_end(args);
}
static void vsprintf_builder_1(char* buffer, ...)
{
va_list args;
va_start(args, buffer);
CAPTURE_AND_PRINT(vsprintf_, buffer, "%d", args);
va_end(args);
}
static void vsnprintf_builder_1(char* buffer, ...)
{
va_list args;
va_start(args, buffer);
CAPTURE_AND_PRINT(vsnprintf_, buffer, 100U, "%d", args);
va_end(args);
}
static void vsprintf_builder_3(char* buffer, ...)
{
va_list args;
va_start(args, buffer);
CAPTURE_AND_PRINT(vsprintf_, buffer, "%d %d %s", args);
va_end(args);
}
static void vsnprintf_builder_3(char* buffer, ...)
{
va_list args;
va_start(args, buffer);
CAPTURE_AND_PRINT(vsnprintf_, buffer, 100U, "%d %d %s", args);
va_end(args);
}
PRINTF_TEST_CASE(vprintf) {
char buffer[base_buffer_size];
printf_idx = 0U;
memset(printf_buffer, 0xCC, base_buffer_size);
vprintf_builder_1(buffer, 2345);
CHECK(printf_buffer[4] == (char)0xCC);
printf_buffer[4] = 0;
CHECK(!strcmp(printf_buffer, "2345"));
}
PRINTF_TEST_CASE(vsprintf) {
char buffer[base_buffer_size];
vsprintf_builder_1(buffer, -1);
CHECK(!strcmp(buffer, "-1"));
vsprintf_builder_3(buffer, 3, -1000, "test");
CHECK(!strcmp(buffer, "3 -1000 test"));
}
PRINTF_TEST_CASE(vsnprintf_) {
char buffer[base_buffer_size];
vsnprintf_builder_1(buffer, -1);
CHECK(!strcmp(buffer, "-1"));
vsnprintf_builder_3(buffer, 3, -1000, "test");
CHECK(!strcmp(buffer, "3 -1000 test"));
}
PRINTF_TEST_CASE(writeback_specifier) {
char buffer[base_buffer_size];
struct {
char char_;
short short_;
int int_;
long long_;
long long long_long_;
} num_written;
num_written.int_ = 1234;
printf_("%n", &num_written.int_);
CHECK(num_written.int_ == 0);
num_written.int_ = 1234;
printf_("foo%nbar", &num_written.int_);
CHECK(num_written.int_ == 3);
num_written.int_ = 1234;
PRINTING_CHECK("", ==, sprintf_, buffer, "%n", &num_written.int_);
CHECK(num_written.int_ == 0);
num_written.int_ = 1234;
PRINTING_CHECK("foobar", ==, sprintf_, buffer, "foo%nbar", &num_written.int_);
CHECK(num_written.int_ == 3);
}
PRINTF_TEST_CASE(ret_value)
{
char buffer[base_buffer_size];
int ret;
ret = snprintf_(buffer, 6, "0%s", "1234");
CHECK(!strcmp(buffer, "01234"));
CHECK(ret == 5);
std::memset(buffer, 0xCC, sizeof(buffer));
ret = snprintf_(buffer, 6, "0%s", "12345");
CHECK(!strcmp(buffer, "01234"));
CHECK(ret == 6); // "5" is truncated
std::memset(buffer, 0xCC, sizeof(buffer));
ret = snprintf_(buffer, 6, "0%s", "1234567");
CHECK(!strcmp(buffer, "01234"));
CHECK(ret == 8); // "567" are truncated
std::memset(buffer, 0xCC, sizeof(buffer));
DISABLE_WARNING_PUSH
DISABLE_WARNING_PRINTF_FORMAT_OVERFLOW
ret = snprintf_(buffer, 6, "0%s", (const char *) NULL);
DISABLE_WARNING_POP
CHECK(!strcmp(buffer, "0(nul"));
CHECK(ret == 7); // "l)" is truncated
std::memset(buffer, 0xCC, sizeof(buffer));
ret = snprintf_(buffer, 10, "hello, world");
CHECK(ret == 12);
std::memset(buffer, 0xCC, sizeof(buffer));
ret = snprintf_(buffer, 3, "%d", 10000);
CHECK(ret == 5);
CHECK(strlen(buffer) == 2U);
CHECK(buffer[0] == '1');
CHECK(buffer[1] == '0');
CHECK(buffer[2] == '\0');
}
#if PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
PRINTF_TEST_CASE(brute_force_float)
{
char buffer[base_buffer_size];
#if PRINTF_SUPPORT_DECIMAL_SPECIFIERS
// brute force float
bool any_failed = false;
std::stringstream sstr;
sstr.precision(5);
for (float i = -100000; i < 100000; i += (float) 1) {
sprintf_(buffer, "%.5f", (double) (i / 10000));
sstr.str("");
sstr << std::fixed << i / 10000;
if (strcmp(buffer, sstr.str().c_str()) != 0) {
std::cerr
<< ": sprintf_(\"%.5f\", " << std::setw(6) << i << ") = " << std::setw(10) << buffer << " , "
<< "expected " << std::setw(10) << sstr.str().c_str() << "\n";
any_failed = true;
}
}
CHECK(not any_failed);
#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
// This is tested when _both_ decimal and exponential specifiers are supported.
// brute force exp
sstr.setf(std::ios::scientific, std::ios::floatfield);
any_failed = false;
long n = 0;
for (float i = (float) -1e20; i < (float) 1e20; i += (float) 1e15, n++) {
sprintf_(buffer, "%.5f", (double) i);
sstr.str("");
sstr << i;
if (strcmp(buffer, sstr.str().c_str()) != 0) {
std::cerr
<< n << ": sprintf_(\"%.5f\", " << std::setw(18) << std::setprecision(30) << i << ") = " << std::setw(15)
<< buffer << " , "
<< "expected " << std::setw(12) << sstr.str().c_str() << "\n";
any_failed = true;
}
}
CHECK(not any_failed);
#endif
#endif
}
#endif // PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
#include "test_suite_main_testcases.hpp"
#ifdef TEST_WITH_NON_STANDARD_FORMAT_STRINGS
DISABLE_WARNING_POP
#endif