zeek/auxil/spicy/3rdparty/tinyformat/tinyformat_test.cpp
Patrick Kelley 8fd444092b initial
2025-05-07 15:35:15 -04:00

354 lines
15 KiB
C++

#if defined(__linux__) && defined(__clang__)
// Workaround for bug in gcc 4.4 standard library headers when compling with
// clang in C++11 mode.
namespace std { class type_info; }
#endif
#if defined(_MSC_VER) && _MSC_VER == 1800
// Disable spurious warning about printf format string mismatch in VS 2012
# pragma warning(disable:4313)
#endif
#include <stdexcept>
#include <climits>
#include <limits>
#include <cfloat>
#include <cstddef>
// Throw instead of abort() so we can test error conditions.
#define TINYFORMAT_ERROR(reason) \
throw std::runtime_error(reason);
#include "tinyformat.h"
#include <cassert>
#if 0
// Compare result of tfm::format() to C's sprintf().
template<typename... Args>
void compareSprintf(const Args&... args)
{
std::string tfmResult = tfm::format(args...);
char sprintfResult[200];
sprintf(sprintfResult, args...);
if (tfmResult != sprintfResult)
{
std::cout << tfmResult << std::endl;
std::cout << sprintfResult << std::endl;
assert(0 && "results didn't match, see above.");
}
}
#endif
#define EXPECT_ERROR(expression) \
try \
{ \
expression; \
std::cout << "test failed, line " << __LINE__ << "\n"; \
std::cout << "expected exception in " #expression << "\n"; \
++nfailed; \
} \
catch (std::runtime_error&) {} \
#define CHECK_EQUAL(a, b) \
if (!((a) == (b))) \
{ \
std::cout << "test failed, line " << __LINE__ << "\n"; \
std::cout << (a) << " != " << (b) << "\n"; \
std::cout << "[" #a ", " #b "]\n"; \
++nfailed; \
}
// Test wrapping to create our own function which calls through to tfm::format
struct TestWrap
{
std::ostringstream m_oss;
// template<typename... Args>
// std::string error(int code, const char* fmt, const Args&... args);
# define MAKE_ERROR_FUNC(n) \
template<TINYFORMAT_ARGTYPES(n)> \
std::string error(int code, const char* fmt, TINYFORMAT_VARARGS(n)) \
{ \
m_oss.clear(); \
m_oss << code << ": "; \
tfm::format(m_oss, fmt, TINYFORMAT_PASSARGS(n)); \
return m_oss.str(); \
}
TINYFORMAT_FOREACH_ARGNUM(MAKE_ERROR_FUNC)
};
struct TestExceptionDef : public std::runtime_error
{
# define MAKE_CONSTRUCTOR(n) \
template<TINYFORMAT_ARGTYPES(n)> \
TestExceptionDef(const char* fmt, TINYFORMAT_VARARGS(n)) \
: std::runtime_error(tfm::format(fmt, TINYFORMAT_PASSARGS(n))) \
{ }
TINYFORMAT_FOREACH_ARGNUM(MAKE_CONSTRUCTOR)
};
struct MyInt {
public:
MyInt(int value) : m_value(value) {}
int value() const { return m_value; }
private:
int m_value;
};
std::ostream& operator<<(std::ostream& os, const MyInt& obj) {
os << obj.value();
return os;
}
int unitTests()
{
int nfailed = 0;
# if defined(_MSC_VER) && _MSC_VER < 1900 // VC++ older than 2015
// floats are printed with three digit exponents on windows, which messes
// up the tests. Turn this off for consistency:
_set_output_format(_TWO_DIGIT_EXPONENT);
# endif
//------------------------------------------------------------
// Test various basic format specs against results of sprintf
CHECK_EQUAL(tfm::format("%s", "asdf"), "asdf");
CHECK_EQUAL(tfm::format("%d", 1234), "1234");
CHECK_EQUAL(tfm::format("%i", -5678), "-5678");
CHECK_EQUAL(tfm::format("%o", 012), "12");
CHECK_EQUAL(tfm::format("%u", 123456u), "123456");
CHECK_EQUAL(tfm::format("%x", 0xdeadbeef), "deadbeef");
CHECK_EQUAL(tfm::format("%X", 0xDEADBEEF), "DEADBEEF");
CHECK_EQUAL(tfm::format("%e", 1.23456e10), "1.234560e+10");
CHECK_EQUAL(tfm::format("%E", -1.23456E10), "-1.234560E+10");
CHECK_EQUAL(tfm::format("%f", -9.8765), "-9.876500");
CHECK_EQUAL(tfm::format("%F", 9.8765), "9.876500");
# ifndef _MSC_VER
CHECK_EQUAL(tfm::format("%a", -1.671111047267913818359375), "-0x1.abcdefp+0");
CHECK_EQUAL(tfm::format("%A", 1.671111047267913818359375), "0X1.ABCDEFP+0");
# else
CHECK_EQUAL(tfm::format("%a", -1.671111047267913818359375), "-0x1.abcdef0000000p+0");
CHECK_EQUAL(tfm::format("%A", 1.671111047267913818359375), "0X1.ABCDEF0000000P+0");
# endif
CHECK_EQUAL(tfm::format("%g", 10), "10");
CHECK_EQUAL(tfm::format("%G", 100), "100");
CHECK_EQUAL(tfm::format("%c", 65), "A");
CHECK_EQUAL(tfm::format("%hc", (short)65), "A");
CHECK_EQUAL(tfm::format("%lc", (long)65), "A");
CHECK_EQUAL(tfm::format("%s", "asdf_123098"), "asdf_123098");
// Test "%%" - (note plain "%%" format gives warning with gcc printf)
CHECK_EQUAL(tfm::format("%%%s", "asdf"), "%asdf");
// Check that 0-argument formatting is printf-compatible
CHECK_EQUAL(tfm::format("100%%"), "100%");
// Test printing of pointers. Note that there's no standard numerical
// representation so this is platform and OS dependent.
//
// Also test special case when %p is used with `char*` pointer types. In
// this case the implementation needs to take care to cast to void*
// before invoking `operator<<`; it must not dereference the pointer and
// print as a string.
# ifdef _MSC_VER
# ifdef _WIN64
CHECK_EQUAL(tfm::format("%p", (void*)0x12345), "0000000000012345");
CHECK_EQUAL(tfm::format("%p", (const char*)0x10), "0000000000000010");
# else
CHECK_EQUAL(tfm::format("%p", (void*)0x12345), "00012345");
CHECK_EQUAL(tfm::format("%p", (const char*)0x10), "00000010");
# endif // _WIN64
# else
CHECK_EQUAL(tfm::format("%p", (void*)0x12345), "0x12345");
CHECK_EQUAL(tfm::format("%p", (const char*)0x10), "0x10");
CHECK_EQUAL(tfm::format("%p", (char*)0x10), "0x10");
CHECK_EQUAL(tfm::format("%p", (signed char*)0x10), "0x10");
CHECK_EQUAL(tfm::format("%p", (unsigned char*)0x10), "0x10");
# endif // !_MSC_VER
// chars with int format specs are printed as ints:
CHECK_EQUAL(tfm::format("%hhd", (char)65), "65");
CHECK_EQUAL(tfm::format("%hhu", (unsigned char)65), "65");
CHECK_EQUAL(tfm::format("%hhd", (signed char)65), "65");
// Bools with string format spec are printed as "true" or "false".
CHECK_EQUAL(tfm::format("%s", true), "true");
CHECK_EQUAL(tfm::format("%d", true), "1");
//------------------------------------------------------------
// Simple tests of posix positional arguments
CHECK_EQUAL(tfm::format("%2$d %1$d", 10, 20), "20 10");
// Allow positional arguments to be unreferenced. This is a slight
// generalization of posix printf, which appears to allow only trailing
// arguments to be unreferenced. See
// http://pubs.opengroup.org/onlinepubs/9699919799/functions/printf.html
CHECK_EQUAL(tfm::format("%1$d", 10, 20), "10");
CHECK_EQUAL(tfm::format("%2$d", 10, 20), "20");
//------------------------------------------------------------
// Test precision & width
CHECK_EQUAL(tfm::format("%10d", -10), " -10");
CHECK_EQUAL(tfm::format("%.4d", 10), "0010");
CHECK_EQUAL(tfm::format("%10.4f", 1234.1234567890), " 1234.1235");
CHECK_EQUAL(tfm::format("%.f", 10.1), "10");
// Per C++ spec, iostreams ignore the precision for "%a" to avoid precision
// loss. This is a printf incompatibility.
# ifndef _MSC_VER
CHECK_EQUAL(tfm::format("%.1a", 1.13671875), "0x1.23p+0");
CHECK_EQUAL(tfm::format("%14a", 1.671111047267913818359375), " 0x1.abcdefp+0");
# else
// MSVC workaround
CHECK_EQUAL(tfm::format("%.1a", 1.13671875), "0x1.2300000000000p+0");
CHECK_EQUAL(tfm::format("%21a", 1.671111047267913818359375), " 0x1.abcdef0000000p+0");
# endif
CHECK_EQUAL(tfm::format("%.2s", "asdf"), "as"); // strings truncate to precision
CHECK_EQUAL(tfm::format("%.2s", std::string("asdf")), "as");
// Test variable precision & width
CHECK_EQUAL(tfm::format("%*.4f", 10, 1234.1234567890), " 1234.1235");
CHECK_EQUAL(tfm::format("%10.*f", 4, 1234.1234567890), " 1234.1235");
CHECK_EQUAL(tfm::format("%*.*f", 10, 4, 1234.1234567890), " 1234.1235");
CHECK_EQUAL(tfm::format("%*.*f", -10, 4, 1234.1234567890), "1234.1235 ");
CHECK_EQUAL(tfm::format("%.*f", -4, 1234.1234567890), "1234.123457"); // negative precision ignored
// Test variable precision & width with positional arguments
CHECK_EQUAL(tfm::format("%1$*2$.4f", 1234.1234567890, 10), " 1234.1235");
CHECK_EQUAL(tfm::format("%1$10.*2$f", 1234.1234567890, 4), " 1234.1235");
CHECK_EQUAL(tfm::format("%1$*3$.*2$f", 1234.1234567890, 4, 10), " 1234.1235");
CHECK_EQUAL(tfm::format("%1$*2$.*3$f", 1234.1234567890, -10, 4), "1234.1235 ");
// Test padding for infinity and NaN
// (Visual Studio 12.0 and earlier print these in a different way)
# if !defined(_MSC_VER) || _MSC_VER >= 1900
CHECK_EQUAL(tfm::format("%.3d", std::numeric_limits<double>::infinity()), "inf");
CHECK_EQUAL(tfm::format("%.4d", std::numeric_limits<double>::infinity()), " inf");
CHECK_EQUAL(tfm::format("%04.0f", std::numeric_limits<double>::infinity()), " inf");
CHECK_EQUAL(tfm::format("%.3d", std::numeric_limits<double>::quiet_NaN()), "nan");
CHECK_EQUAL(tfm::format("%.4d", std::numeric_limits<double>::quiet_NaN()), " nan");
CHECK_EQUAL(tfm::format("%04.0f", std::numeric_limits<double>::quiet_NaN()), " nan");
# endif
//------------------------------------------------------------
// Test flags
CHECK_EQUAL(tfm::format("%#x", 0x271828), "0x271828");
CHECK_EQUAL(tfm::format("%#o", 0x271828), "011614050");
CHECK_EQUAL(tfm::format("%#f", 3.0), "3.000000");
CHECK_EQUAL(tfm::format("%+d", 3), "+3");
CHECK_EQUAL(tfm::format("%+d", 0), "+0");
CHECK_EQUAL(tfm::format("%+d", -3), "-3");
CHECK_EQUAL(tfm::format("%010d", 100), "0000000100");
CHECK_EQUAL(tfm::format("%010d", -10), "-000000010"); // sign should extend
CHECK_EQUAL(tfm::format("%#010X", 0xBEEF), "0X0000BEEF");
CHECK_EQUAL(tfm::format("% d", 10), " 10");
CHECK_EQUAL(tfm::format("% d", -10), "-10");
// Test flags with variable precision & width
CHECK_EQUAL(tfm::format("%+.2d", 3), "+03");
CHECK_EQUAL(tfm::format("%+.2d", -3), "-03");
// flag override precedence
CHECK_EQUAL(tfm::format("%+ d", 10), "+10"); // '+' overrides ' '
CHECK_EQUAL(tfm::format("% +d", 10), "+10");
CHECK_EQUAL(tfm::format("%-010d", 10), "10 "); // '-' overrides '0'
CHECK_EQUAL(tfm::format("%0-10d", 10), "10 ");
//------------------------------------------------------------
// Check that length modifiers are ignored
CHECK_EQUAL(tfm::format("%hd", (short)1000), "1000");
CHECK_EQUAL(tfm::format("%ld", (long)100000), "100000");
CHECK_EQUAL(tfm::format("%lld", (long long)100000), "100000");
CHECK_EQUAL(tfm::format("%zd", (size_t)100000), "100000");
CHECK_EQUAL(tfm::format("%td", (ptrdiff_t)100000), "100000");
CHECK_EQUAL(tfm::format("%jd", 100000), "100000");
// printf incompatibilities:
// compareSprintf("%6.4x", 10); // precision & width can't be supported independently
// compareSprintf("%.4d", -10); // negative numbers + precision don't quite work.
//------------------------------------------------------------
// General "complicated" format spec test
CHECK_EQUAL(tfm::format("%0.10f:%04d:%+g:%s:%#X:%c:%%:%%asdf",
1.234, 42, 3.13, "str", 0XDEAD, (int)'X'),
"1.2340000000:0042:+3.13:str:0XDEAD:X:%:%asdf");
CHECK_EQUAL(tfm::format("%2$0.10f:%3$0*4$d:%1$+g:%6$s:%5$#X:%7$c:%%:%%asdf",
3.13, 1.234, 42, 4, 0XDEAD, "str", (int)'X'),
"1.2340000000:0042:+3.13:str:0XDEAD:X:%:%asdf");
//------------------------------------------------------------
// Error handling
// Test wrong number of args
EXPECT_ERROR( tfm::format("%d", 5, 10) )
EXPECT_ERROR( tfm::format("%d %d", 1) )
// Unterminated format spec
EXPECT_ERROR( tfm::format("%123", 10) )
// Types used to specify variable width/precision must be convertible to int.
EXPECT_ERROR( tfm::format("%0*d", "thing that can't convert to int", 42) )
EXPECT_ERROR( tfm::format("%0.*d", "thing that can't convert to int", 42) )
// Error required if not enough args for variable width/precision
EXPECT_ERROR( tfm::format("%*d", 1) )
EXPECT_ERROR( tfm::format("%.*d", 1) )
EXPECT_ERROR( tfm::format("%*.*d", 1, 2) )
// Error required if positional argument refers to non-existent argument
EXPECT_ERROR( tfm::format("%2$d", 1) )
EXPECT_ERROR( tfm::format("%0$d", 1) )
EXPECT_ERROR( tfm::format("%1$.*3$d", 1, 2) )
EXPECT_ERROR( tfm::format("%1$.*0$d", 1, 2) )
EXPECT_ERROR( tfm::format("%1$.*$d", 1, 2) )
EXPECT_ERROR( tfm::format("%3$*4$.*2$d", 1, 2, 3) )
EXPECT_ERROR( tfm::format("%3$*0$.*2$d", 1, 2, 3) )
// Unhandled C99 format spec
EXPECT_ERROR( tfm::format("%n", 10) )
//------------------------------------------------------------
// Misc
volatile int i = 1234;
CHECK_EQUAL(tfm::format("%d", i), "1234");
#ifdef TEST_WCHAR_T_COMPILE
// Test wchar_t handling - should fail to compile!
tfm::format("%ls", L"blah");
#endif
// Test that formatting is independent of underlying stream state.
std::ostringstream oss;
oss.width(20);
oss.precision(10);
oss.fill('*');
oss.setf(std::ios::scientific);
tfm::format(oss, "%f", 10.1234123412341234);
CHECK_EQUAL(oss.str(), "10.123412");
// Test formatting a custom object
MyInt myobj(42);
CHECK_EQUAL(tfm::format("myobj: %s", myobj), "myobj: 42");
// Test that interface wrapping works correctly
TestWrap wrap;
CHECK_EQUAL(wrap.error(10, "someformat %s:%d:%d", "asdf", 2, 4),
"10: someformat asdf:2:4");
TestExceptionDef ex("blah %d", 100);
CHECK_EQUAL(ex.what(), std::string("blah 100"));
// Test tfm::printf by swapping the std::cout stream buffer to capture data
// which would noramlly go to the stdout
std::ostringstream coutCapture;
std::streambuf* coutBuf = std::cout.rdbuf(coutCapture.rdbuf());
tfm::printf("%s %s %d\n", "printf", "test", 1);
tfm::printfln("%s %s %d", "printfln", "test", 1);
std::cout.rdbuf(coutBuf); // restore buffer
CHECK_EQUAL(coutCapture.str(), "printf test 1\nprintfln test 1\n");
return nfailed;
}
int main()
{
try
{
return unitTests();
}
catch (std::runtime_error & e)
{
std::cout << "Failure due to uncaught exception: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}