Commit bf8b29fb authored by Victor Zverovich's avatar Victor Zverovich

Add initial support for printf format specifications.

parent 7042d143
......@@ -91,19 +91,24 @@ enable_testing()
include_directories(.)
set(TEST_MAIN_SRC test/test-main.cc test/gtest-extra.cc test/gtest-extra.h)
set(TEST_MAIN_SRC
test/test-main.cc test/gtest-extra.cc test/gtest-extra.h test/util.cc)
add_library(test-main ${TEST_MAIN_SRC})
target_link_libraries(test-main gtest format)
cxx_test(gtest-extra-test test-main)
cxx_test(format-test test-main)
if (CMAKE_COMPILER_IS_GNUCXX)
set_target_properties(format-test PROPERTIES COMPILE_FLAGS
"-Wall -Wextra -pedantic -Wno-long-long -Wno-variadic-macros")
endif ()
if (CPP11_FLAG)
set_target_properties(format-test PROPERTIES COMPILE_FLAGS ${CPP11_FLAG})
endif ()
cxx_test(printf-test test-main)
foreach (target format-test printf-test)
if (CMAKE_COMPILER_IS_GNUCXX)
set_target_properties(${target} PROPERTIES COMPILE_FLAGS
"-Wall -Wextra -pedantic -Wno-long-long -Wno-variadic-macros")
endif ()
if (CPP11_FLAG)
set_target_properties(${target} PROPERTIES COMPILE_FLAGS ${CPP11_FLAG})
endif ()
endforeach ()
cxx_test(util-test test-main)
if (HAVE_OPEN)
add_executable(posix-test test/posix-test.cc ${TEST_MAIN_SRC})
......
This diff is collapsed.
......@@ -186,6 +186,15 @@ class BasicStringRef {
typedef BasicStringRef<char> StringRef;
typedef BasicStringRef<wchar_t> WStringRef;
/**
A formatting error such as invalid format string.
*/
class FormatError : public std::runtime_error {
public:
explicit FormatError(const std::string &message)
: std::runtime_error(message) {}
};
namespace internal {
// The number of characters to store in the Array object, representing the
......@@ -509,17 +518,29 @@ void FormatWinErrorMessage(
fmt::Writer &out, int error_code, fmt::StringRef message);
#endif
} // namespace internal
struct SimpleErrorReporter {
void operator()(const void *, fmt::StringRef message) const {
throw fmt::FormatError(message);
}
};
/**
A formatting error such as invalid format string.
*/
class FormatError : public std::runtime_error {
public:
explicit FormatError(const std::string &message)
: std::runtime_error(message) {}
// Throws Exception(message) if format contains '}', otherwise throws
// FormatError reporting unmatched '{'. The idea is that unmatched '{'
// should override other errors.
template <typename Char>
struct FormatErrorReporter {
int num_open_braces;
void operator()(const Char *s, fmt::StringRef message) const;
};
// Parses a nonnegative integer advancing s to the end of the parsed input.
// This function assumes that the first character of s is a digit.
template <typename Char>
int ParseNonnegativeInt(
const Char *&s, const char *&error) FMT_NOEXCEPT(true);
} // namespace internal
/**
An error returned by an operating system or a language runtime,
for example a file opening error.
......@@ -881,6 +902,8 @@ class BasicWriter {
};
};
static const ArgInfo DUMMY_ARG;
// An argument action that does nothing.
struct NullArgAction {
void operator()() const {}
......@@ -978,12 +1001,8 @@ class BasicWriter {
private:
std::size_t num_args_;
const ArgInfo *args_;
int num_open_braces_;
int next_arg_index_;
void ReportError(const Char *s, StringRef message) const;
unsigned ParseUInt(const Char *&s) const;
fmt::internal::FormatErrorReporter<Char> report_error_;
// Parses argument index and returns an argument with this index.
const ArgInfo &ParseArgIndex(const Char *&s);
......@@ -995,6 +1014,18 @@ class BasicWriter {
BasicStringRef<Char> format, std::size_t num_args, const ArgInfo *args);
};
// Printf format string parser.
class PrintfParser {
private:
std::size_t num_args_;
const ArgInfo *args_;
int next_arg_index_;
public:
void Format(BasicWriter<Char> &writer,
BasicStringRef<Char> format, std::size_t num_args, const ArgInfo *args);
};
public:
/**
Constructs a ``BasicWriter`` object.
......@@ -1052,6 +1083,11 @@ class BasicWriter {
FormatParser().Format(*this, format, num_args, args);
}
inline void vprintf(BasicStringRef<Char> format,
std::size_t num_args, const ArgInfo *args) {
PrintfParser().Format(*this, format, num_args, args);
}
/**
\rst
Formats a string sending the output to the writer. Arguments are
......@@ -1112,6 +1148,12 @@ class BasicWriter {
Arg arg_array[] = {args...};
VFormat(format, sizeof...(Args), arg_array);
}
template<typename... Args>
void printf(BasicStringRef<Char> format, const Args & ... args) {
Arg arg_array[] = {args...};
vprintf(format, sizeof...(Args), arg_array);
}
#endif
BasicWriter &operator<<(int value) {
......@@ -1726,7 +1768,7 @@ inline Formatter<ANSITerminalSink> PrintColored(Color c, StringRef format) {
Formats a string similarly to Python's `str.format
<http://docs.python.org/3/library/stdtypes.html#str.format>`__ function
and returns an :cpp:class:`fmt::BasicWriter` object containing the output.
This version of the Format function uses C++11 features such as
variadic templates and rvalue references. For C++98 version, see
the :cpp:func:`fmt::Format()` overload above.
......@@ -1773,6 +1815,20 @@ void Print(std::FILE *f, StringRef format, const Args & ... args) {
std::fwrite(w.data(), 1, w.size(), f);
}
template<typename... Args>
inline Writer sprintf(StringRef format, const Args & ... args) {
Writer w;
w.printf(format, args...);
return std::move(w);
}
template<typename... Args>
void printf(StringRef format, const Args & ... args) {
Writer w;
w.printf(format, args...);
std::fwrite(w.data(), 1, w.size(), stdout);
}
#endif // FMT_USE_VARIADIC_TEMPLATES && FMT_USE_RVALUE_REFERENCES
/**
......
......@@ -49,6 +49,7 @@ FILE *FOpen(const char *filename, const char *mode) {
#endif
#include "format.h"
#include "util.h"
#include "gtest-extra.h"
#include <stdint.h>
......@@ -110,19 +111,6 @@ struct WriteChecker {
#define CHECK_WRITE_WCHAR(value) \
EXPECT_PRED_FORMAT1(WriteChecker<wchar_t>(), value)
// Increment a number in a string.
void Increment(char *s) {
for (int i = static_cast<int>(std::strlen(s)) - 1; i >= 0; --i) {
if (s[i] != '9') {
++s[i];
break;
}
s[i] = '0';
}
}
enum {BUFFER_SIZE = 256};
#ifdef _MSC_VER
# define vsnprintf vsprintf_s
#endif
......@@ -134,235 +122,14 @@ void SPrintf(char *buffer, const char *format, ...) {
va_end(args);
}
std::string ReadFile(fmt::StringRef filename) {
std::string ReadFile(StringRef filename) {
std::ifstream out(filename.c_str());
std::stringstream content;
content << out.rdbuf();
return content.str();
}
std::string GetSystemErrorMessage(int error_code) {
#ifndef _WIN32
return strerror(error_code);
#else
enum { BUFFER_SIZE = 200 };
char buffer[BUFFER_SIZE];
EXPECT_EQ(0, strerror_s(buffer, BUFFER_SIZE, error_code));
std::size_t max_len = BUFFER_SIZE - 1;
EXPECT_LT(std::strlen(buffer), max_len);
return buffer;
#endif
}
}
TEST(UtilTest, Increment) {
char s[10] = "123";
Increment(s);
EXPECT_STREQ("124", s);
s[2] = '8';
Increment(s);
EXPECT_STREQ("129", s);
Increment(s);
EXPECT_STREQ("130", s);
s[1] = s[2] = '9';
Increment(s);
EXPECT_STREQ("200", s);
}
// Tests fmt::internal::CountDigits for integer type Int.
template <typename Int>
void TestCountDigits(Int) {
for (Int i = 0; i < 10; ++i)
EXPECT_EQ(1u, fmt::internal::CountDigits(i));
for (Int i = 1, n = 1,
end = std::numeric_limits<Int>::max() / 10; n <= end; ++i) {
n *= 10;
EXPECT_EQ(i, fmt::internal::CountDigits(n - 1));
EXPECT_EQ(i + 1, fmt::internal::CountDigits(n));
}
}
TEST(UtilTest, CountDigits) {
TestCountDigits(uint32_t());
TestCountDigits(uint64_t());
}
#ifdef _WIN32
TEST(UtilTest, UTF16ToUTF8) {
std::string s = "ёжик";
fmt::internal::UTF16ToUTF8 u(L"\x0451\x0436\x0438\x043A");
EXPECT_EQ(s, fmt::str(u));
EXPECT_EQ(s.size(), u.size());
}
TEST(UtilTest, UTF8ToUTF16) {
std::string s = "лошадка";
fmt::internal::UTF8ToUTF16 u(s.c_str());
EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", fmt::str(u));
EXPECT_EQ(7, u.size());
}
template <typename Converter>
void CheckUTFConversionError(const char *message) {
fmt::Writer out;
fmt::internal::FormatWinErrorMessage(out, ERROR_INVALID_PARAMETER, message);
fmt::SystemError error("", 0);
try {
Converter(0);
} catch (const fmt::SystemError &e) {
error = e;
}
EXPECT_EQ(ERROR_INVALID_PARAMETER, error.error_code());
EXPECT_EQ(out.str(), error.what());
}
TEST(UtilTest, UTF16ToUTF8Error) {
CheckUTFConversionError<fmt::internal::UTF16ToUTF8>(
"cannot convert string from UTF-16 to UTF-8");
}
TEST(UtilTest, UTF8ToUTF16Error) {
CheckUTFConversionError<fmt::internal::UTF8ToUTF16>(
"cannot convert string from UTF-8 to UTF-16");
}
TEST(UtilTest, UTF16ToUTF8Convert) {
fmt::internal::UTF16ToUTF8 u;
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.Convert(0));
}
#endif // _WIN32
TEST(UtilTest, StrError) {
using fmt::internal::StrError;
char *message = 0;
char buffer[BUFFER_SIZE];
#ifndef NDEBUG
EXPECT_DEBUG_DEATH(StrError(EDOM, message = 0, 0), "Assertion");
EXPECT_DEBUG_DEATH(StrError(EDOM, message = buffer, 0), "Assertion");
#endif
buffer[0] = 'x';
#ifdef _GNU_SOURCE
// Use invalid error code to make sure that StrError returns an error
// message in the buffer rather than a pointer to a static string.
int error_code = -1;
#else
int error_code = EDOM;
#endif
int result = StrError(error_code, message = buffer, 1);
EXPECT_EQ(buffer, message); // Message should point to buffer.
EXPECT_EQ(ERANGE, result);
EXPECT_STREQ("", message);
result = StrError(error_code, message = buffer, BUFFER_SIZE);
EXPECT_EQ(0, result);
std::size_t message_size = std::strlen(message);
EXPECT_GE(BUFFER_SIZE - 1u, message_size);
EXPECT_EQ(GetSystemErrorMessage(error_code), message);
result = StrError(error_code, message = buffer, message_size);
EXPECT_EQ(ERANGE, result);
}
TEST(UtilTest, SystemError) {
fmt::SystemError e(fmt::StringRef("test"), 42);
EXPECT_STREQ("test", e.what());
EXPECT_EQ(42, e.error_code());
}
typedef void (*FormatErrorMessage)(
fmt::Writer &out, int error_code, fmt::StringRef message);
template <typename Sink>
void CheckErrorSink(int error_code, FormatErrorMessage format) {
fmt::SystemError error("", 0);
Sink sink(error_code);
fmt::Writer w;
w << "test";
try {
sink(w);
} catch (const fmt::SystemError &e) {
error = e;
}
fmt::Writer message;
format(message, error_code, "test");
EXPECT_EQ(str(message), error.what());
EXPECT_EQ(error_code, error.error_code());
}
template <typename Sink>
void CheckThrowError(int error_code, FormatErrorMessage format,
fmt::Formatter<Sink> (*throw_error)(int error_code, StringRef format)) {
fmt::SystemError error("", 0);
try {
throw_error(error_code, "test {}") << "error";
} catch (const fmt::SystemError &e) {
error = e;
}
fmt::Writer message;
format(message, error_code, "test error");
EXPECT_EQ(str(message), error.what());
EXPECT_EQ(error_code, error.error_code());
}
TEST(UtilTest, FormatSystemErrorMessage) {
fmt::Writer message;
fmt::internal::FormatSystemErrorMessage(message, EDOM, "test");
EXPECT_EQ(str(fmt::Format("test: {}")
<< GetSystemErrorMessage(EDOM)), fmt::str(message));
}
TEST(UtilTest, SystemErrorSink) {
CheckErrorSink<fmt::SystemErrorSink>(
EDOM, fmt::internal::FormatSystemErrorMessage);
}
TEST(UtilTest, ThrowSystemError) {
CheckThrowError(EDOM,
fmt::internal::FormatSystemErrorMessage, fmt::ThrowSystemError);
}
TEST(ErrorTest, ReportSystemError) {
fmt::Writer out;
fmt::internal::FormatSystemErrorMessage(out, EDOM, "test error");
out << '\n';
EXPECT_WRITE(stderr, fmt::ReportSystemError(EDOM, "test error"), out.str());
}
#ifdef _WIN32
TEST(UtilTest, FormatWinErrorMessage) {
LPWSTR message = 0;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0,
ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&message), 0, 0);
fmt::internal::UTF16ToUTF8 utf8_message(message);
LocalFree(message);
fmt::Writer actual_message;
fmt::internal::FormatWinErrorMessage(
actual_message, ERROR_FILE_EXISTS, "test");
EXPECT_EQ(str(fmt::Format("test: {}") << fmt::str(utf8_message)),
fmt::str(actual_message));
}
TEST(UtilTest, WinErrorSink) {
CheckErrorSink<fmt::WinErrorSink>(
ERROR_FILE_EXISTS, fmt::internal::FormatWinErrorMessage);
}
TEST(UtilTest, ThrowWinError) {
CheckThrowError(ERROR_FILE_EXISTS,
fmt::internal::FormatWinErrorMessage, fmt::ThrowWinError);
}
TEST(ErrorTest, ReportWinError) {
fmt::Writer out;
fmt::internal::FormatWinErrorMessage(out, ERROR_FILE_EXISTS, "test error");
out << '\n';
EXPECT_WRITE(stderr,
fmt::ReportWinError(ERROR_FILE_EXISTS, "test error"), out.str());
}
#endif // _WIN32
class TestString {
private:
std::string value_;
......@@ -849,18 +616,15 @@ TEST(FormatterTest, ArgErrors) {
"argument index is out of range in format");
char format[BUFFER_SIZE];
SPrintf(format, "{%u", UINT_MAX);
SPrintf(format, "{%u", INT_MAX);
EXPECT_THROW_MSG(Format(format), FormatError, "unmatched '{' in format");
SPrintf(format, "{%u}", UINT_MAX);
SPrintf(format, "{%u}", INT_MAX);
EXPECT_THROW_MSG(Format(format), FormatError,
"argument index is out of range in format");
SPrintf(format, "{%u", UINT_MAX);
Increment(format + 1);
SPrintf(format, "{%u", INT_MAX + 1u);
EXPECT_THROW_MSG(Format(format), FormatError, "unmatched '{' in format");
std::size_t size = std::strlen(format);
format[size] = '}';
format[size + 1] = 0;
SPrintf(format, "{%u}", INT_MAX + 1u);
EXPECT_THROW_MSG(Format(format), FormatError, "number is too big in format");
}
......
/*
printf tests.
Copyright (c) 2012-2014, Victor Zverovich
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <climits>
#include <cstring>
#include "format.h"
#include "gtest-extra.h"
#include "util.h"
#if FMT_USE_VARIADIC_TEMPLATES && FMT_USE_RVALUE_REFERENCES
using fmt::Format;
using fmt::FormatError;
// Returns a number UINT_MAX + 1 formatted as a string.
std::string GetBigNumber() {
char format[BUFFER_SIZE];
sprintf(format, "%u", UINT_MAX);
Increment(format + 1);
return format;
}
const unsigned BIG_NUM = INT_MAX + 1u;
TEST(PrintfTest, NoArgs) {
EXPECT_EQ("test", str(fmt::sprintf("test")));
}
TEST(PrintfTest, Escape) {
EXPECT_EQ("%", str(fmt::sprintf("%%")));
EXPECT_EQ("before %", str(fmt::sprintf("before %%")));
EXPECT_EQ("% after", str(fmt::sprintf("%% after")));
EXPECT_EQ("before % after", str(fmt::sprintf("before %% after")));
EXPECT_EQ("%s", str(fmt::sprintf("%%s")));
}
TEST(PrintfTest, PositionalArgs) {
EXPECT_EQ("42", str(fmt::sprintf("%1$d", 42)));
EXPECT_EQ("before 42", str(fmt::sprintf("before %1$d", 42)));
EXPECT_EQ("42 after", str(fmt::sprintf("%1$d after",42)));
EXPECT_EQ("before 42 after", str(fmt::sprintf("before %1$d after", 42)));
EXPECT_EQ("answer = 42", str(fmt::sprintf("%1$s = %2$d", "answer", 42)));
EXPECT_EQ("42 is the answer",
str(fmt::sprintf("%2$d is the %1$s", "answer", 42)));
EXPECT_EQ("abracadabra", str(fmt::sprintf("%1$s%2$s%1$s", "abra", "cad")));
}
TEST(PrintfTest, AutomaticArgIndexing) {
EXPECT_EQ("abc", str(fmt::sprintf("%c%c%c", 'a', 'b', 'c')));
}
TEST(PrintfTest, NumberIsTooBigInArgIndex) {
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%{}$", BIG_NUM))),
FormatError, "invalid format string");
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%{}$d", BIG_NUM))),
FormatError, "number is too big in format");
}
TEST(PrintfTest, SwitchArgIndexing) {
EXPECT_THROW_MSG(fmt::sprintf("%1$d%", 1, 2),
FormatError, "invalid format string");
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%1$d%{}d", BIG_NUM)), 1, 2),
FormatError, "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(fmt::sprintf("%1$d%d", 1, 2),
FormatError, "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(fmt::sprintf("%d%1$", 1, 2),
FormatError, "invalid format string");
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%d%{}$d", BIG_NUM)), 1, 2),
FormatError, "number is too big in format");
EXPECT_THROW_MSG(fmt::sprintf("%d%1$d", 1, 2),
FormatError, "cannot switch from automatic to manual argument indexing");
// Indexing errors override width errors.
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%d%1${}d", BIG_NUM)), 1, 2),
FormatError, "cannot switch from automatic to manual argument indexing");
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%1$d%{}d", BIG_NUM)), 1, 2),
FormatError, "cannot switch from manual to automatic argument indexing");
}
TEST(PrintfTest, InvalidArgIndex) {
EXPECT_THROW_MSG(fmt::sprintf("%0$d", 42), FormatError,
"argument index is out of range in format");
EXPECT_THROW_MSG(fmt::sprintf("%2$d", 42), FormatError,
"argument index is out of range in format");
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%{}$d", INT_MAX)), 42),
FormatError, "argument index is out of range in format");
EXPECT_THROW_MSG(fmt::sprintf("%2$", 42),
FormatError, "invalid format string");
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%{}$d", BIG_NUM)), 42),
FormatError, "number is too big in format");
}
TEST(PrintfTest, Width) {
EXPECT_EQ(" abc", str(fmt::sprintf("%5s", "abc")));
EXPECT_EQ(" abc", str(fmt::sprintf("%1$5s", "abc")));
// Width cannot be specified twice.
EXPECT_THROW_MSG(fmt::sprintf("%5-5d", 42), FormatError,
"unknown format code '-' for integer");
}
TEST(PrintfTest, InvalidWidth) {
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%{}d", BIG_NUM)), 42),
FormatError, "number is too big in format");
EXPECT_THROW_MSG(fmt::sprintf(str(Format("%1${}d", BIG_NUM)), 42),
FormatError, "number is too big in format");
}
// TODO
TEST(PrintfTest, Align) {
EXPECT_EQ(" abc", str(fmt::sprintf("%5s", "abc")));
EXPECT_EQ("abc ", str(fmt::sprintf("%-5s", "abc")));
}
#endif
/*
Utility tests.
Copyright (c) 2012-2014, Victor Zverovich
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <cstring>
#include "gtest-extra.h"
#include "format.h"
#include "util.h"
using fmt::StringRef;
namespace {
std::string GetSystemErrorMessage(int error_code) {
#ifndef _WIN32
return strerror(error_code);
#else
enum { BUFFER_SIZE = 200 };
char buffer[BUFFER_SIZE];
EXPECT_EQ(0, strerror_s(buffer, BUFFER_SIZE, error_code));
std::size_t max_len = BUFFER_SIZE - 1;
EXPECT_LT(std::strlen(buffer), max_len);
return buffer;
#endif
}
}
TEST(UtilTest, Increment) {
char s[10] = "123";
Increment(s);
EXPECT_STREQ("124", s);
s[2] = '8';
Increment(s);
EXPECT_STREQ("129", s);
Increment(s);
EXPECT_STREQ("130", s);
s[1] = s[2] = '9';
Increment(s);
EXPECT_STREQ("200", s);
}
// Tests fmt::internal::CountDigits for integer type Int.
template <typename Int>
void TestCountDigits(Int) {
for (Int i = 0; i < 10; ++i)
EXPECT_EQ(1u, fmt::internal::CountDigits(i));
for (Int i = 1, n = 1,
end = std::numeric_limits<Int>::max() / 10; n <= end; ++i) {
n *= 10;
EXPECT_EQ(i, fmt::internal::CountDigits(n - 1));
EXPECT_EQ(i + 1, fmt::internal::CountDigits(n));
}
}
TEST(UtilTest, CountDigits) {
TestCountDigits(uint32_t());
TestCountDigits(uint64_t());
}
#ifdef _WIN32
TEST(UtilTest, UTF16ToUTF8) {
std::string s = "ёжик";
fmt::internal::UTF16ToUTF8 u(L"\x0451\x0436\x0438\x043A");
EXPECT_EQ(s, fmt::str(u));
EXPECT_EQ(s.size(), u.size());
}
TEST(UtilTest, UTF8ToUTF16) {
std::string s = "лошадка";
fmt::internal::UTF8ToUTF16 u(s.c_str());
EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", fmt::str(u));
EXPECT_EQ(7, u.size());
}
template <typename Converter>
void CheckUTFConversionError(const char *message) {
fmt::Writer out;
fmt::internal::FormatWinErrorMessage(out, ERROR_INVALID_PARAMETER, message);
fmt::SystemError error("", 0);
try {
Converter(0);
} catch (const fmt::SystemError &e) {
error = e;
}
EXPECT_EQ(ERROR_INVALID_PARAMETER, error.error_code());
EXPECT_EQ(out.str(), error.what());
}
TEST(UtilTest, UTF16ToUTF8Error) {
CheckUTFConversionError<fmt::internal::UTF16ToUTF8>(
"cannot convert string from UTF-16 to UTF-8");
}
TEST(UtilTest, UTF8ToUTF16Error) {
CheckUTFConversionError<fmt::internal::UTF8ToUTF16>(
"cannot convert string from UTF-8 to UTF-16");
}
TEST(UtilTest, UTF16ToUTF8Convert) {
fmt::internal::UTF16ToUTF8 u;
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.Convert(0));
}
#endif // _WIN32
TEST(UtilTest, StrError) {
using fmt::internal::StrError;
char *message = 0;
char buffer[BUFFER_SIZE];
#ifndef NDEBUG
EXPECT_DEBUG_DEATH(StrError(EDOM, message = 0, 0), "Assertion");
EXPECT_DEBUG_DEATH(StrError(EDOM, message = buffer, 0), "Assertion");
#endif
buffer[0] = 'x';
#ifdef _GNU_SOURCE
// Use invalid error code to make sure that StrError returns an error
// message in the buffer rather than a pointer to a static string.
int error_code = -1;
#else
int error_code = EDOM;
#endif
int result = StrError(error_code, message = buffer, 1);
EXPECT_EQ(buffer, message); // Message should point to buffer.
EXPECT_EQ(ERANGE, result);
EXPECT_STREQ("", message);
result = StrError(error_code, message = buffer, BUFFER_SIZE);
EXPECT_EQ(0, result);
std::size_t message_size = std::strlen(message);
EXPECT_GE(BUFFER_SIZE - 1u, message_size);
EXPECT_EQ(GetSystemErrorMessage(error_code), message);
result = StrError(error_code, message = buffer, message_size);
EXPECT_EQ(ERANGE, result);
}
TEST(UtilTest, SystemError) {
fmt::SystemError e(StringRef("test"), 42);
EXPECT_STREQ("test", e.what());
EXPECT_EQ(42, e.error_code());
}
typedef void (*FormatErrorMessage)(
fmt::Writer &out, int error_code, StringRef message);
template <typename Sink>
void CheckErrorSink(int error_code, FormatErrorMessage format) {
fmt::SystemError error("", 0);
Sink sink(error_code);
fmt::Writer w;
w << "test";
try {
sink(w);
} catch (const fmt::SystemError &e) {
error = e;
}
fmt::Writer message;
format(message, error_code, "test");
EXPECT_EQ(str(message), error.what());
EXPECT_EQ(error_code, error.error_code());
}
template <typename Sink>
void CheckThrowError(int error_code, FormatErrorMessage format,
fmt::Formatter<Sink> (*throw_error)(int error_code, StringRef format)) {
fmt::SystemError error("", 0);
try {
throw_error(error_code, "test {}") << "error";
} catch (const fmt::SystemError &e) {
error = e;
}
fmt::Writer message;
format(message, error_code, "test error");
EXPECT_EQ(str(message), error.what());
EXPECT_EQ(error_code, error.error_code());
}
TEST(UtilTest, FormatSystemErrorMessage) {
fmt::Writer message;
fmt::internal::FormatSystemErrorMessage(message, EDOM, "test");
EXPECT_EQ(str(fmt::Format("test: {}")
<< GetSystemErrorMessage(EDOM)), fmt::str(message));
}
TEST(UtilTest, SystemErrorSink) {
CheckErrorSink<fmt::SystemErrorSink>(
EDOM, fmt::internal::FormatSystemErrorMessage);
}
TEST(UtilTest, ThrowSystemError) {
CheckThrowError(EDOM,
fmt::internal::FormatSystemErrorMessage, fmt::ThrowSystemError);
}
TEST(UtilTest, ReportSystemError) {
fmt::Writer out;
fmt::internal::FormatSystemErrorMessage(out, EDOM, "test error");
out << '\n';
EXPECT_WRITE(stderr, fmt::ReportSystemError(EDOM, "test error"), out.str());
}
#ifdef _WIN32
TEST(UtilTest, FormatWinErrorMessage) {
LPWSTR message = 0;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0,
ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&message), 0, 0);
fmt::internal::UTF16ToUTF8 utf8_message(message);
LocalFree(message);
fmt::Writer actual_message;
fmt::internal::FormatWinErrorMessage(
actual_message, ERROR_FILE_EXISTS, "test");
EXPECT_EQ(str(fmt::Format("test: {}") << fmt::str(utf8_message)),
fmt::str(actual_message));
}
TEST(UtilTest, WinErrorSink) {
CheckErrorSink<fmt::WinErrorSink>(
ERROR_FILE_EXISTS, fmt::internal::FormatWinErrorMessage);
}
TEST(UtilTest, ThrowWinError) {
CheckThrowError(ERROR_FILE_EXISTS,
fmt::internal::FormatWinErrorMessage, fmt::ThrowWinError);
}
TEST(UtilTest, ReportWinError) {
fmt::Writer out;
fmt::internal::FormatWinErrorMessage(out, ERROR_FILE_EXISTS, "test error");
out << '\n';
EXPECT_WRITE(stderr,
fmt::ReportWinError(ERROR_FILE_EXISTS, "test error"), out.str());
}
#endif // _WIN32
/*
Test utilities.
Copyright (c) 2012-2014, Victor Zverovich
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "util.h"
#include <cstring>
void Increment(char *s) {
for (int i = static_cast<int>(std::strlen(s)) - 1; i >= 0; --i) {
if (s[i] != '9') {
++s[i];
break;
}
s[i] = '0';
}
}
/*
Test utilities.
Copyright (c) 2012-2014, Victor Zverovich
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
enum {BUFFER_SIZE = 256};
// Increment a number in a string.
void Increment(char *s);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment