From 35959a31d7f58fe9f787649b4302944e9afe4024 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 15 Dec 2019 08:54:16 -0800 Subject: [PATCH] Move OS-specific APIs to a separate header --- CMakeLists.txt | 2 +- include/fmt/core.h | 3 +- include/fmt/format-inl.h | 118 ++--------------------------- include/fmt/format.h | 126 ++++++++----------------------- include/fmt/os.h | 97 ++++++++++++++++++++++++ src/posix.cc | 156 +++++++++++++++++++++++++++++---------- test/format-test.cc | 138 +++------------------------------- test/posix-test.cc | 117 +++++++++++++++++++++++++++++ 8 files changed, 384 insertions(+), 373 deletions(-) create mode 100644 include/fmt/os.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b6b79ef..2623ab9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,7 +151,7 @@ endfunction() # Define the fmt library, its includes and the needed defines. add_headers(FMT_HEADERS chrono.h color.h compile.h core.h format.h format-inl.h - locale.h ostream.h posix.h printf.h ranges.h) + locale.h os.h ostream.h posix.h printf.h ranges.h) set(FMT_SOURCES src/format.cc src/posix.cc) add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst) diff --git a/include/fmt/core.h b/include/fmt/core.h index b48dac1b..b5156e20 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1503,8 +1503,7 @@ FMT_API void vprint(string_view format_str, format_args args); /** \rst Prints formatted data to the file *f*. For wide format strings, - *f* should be in wide-oriented mode set via ``fwide(f, 1)`` or - ``_setmode(_fileno(f), _O_U8TEXT)`` on Windows. + *f* should be in wide-oriented mode set via ``fwide(f, 1)``. **Example**:: diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 1146d8ce..f1fa067f 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -21,27 +21,6 @@ # include #endif -#if FMT_USE_WINDOWS_H -# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN) -# define WIN32_LEAN_AND_MEAN -# endif -# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX) -# include -# else -# define NOMINMAX -# include -# undef NOMINMAX -# endif -#endif - -#if FMT_EXCEPTIONS -# define FMT_TRY try -# define FMT_CATCH(x) catch (x) -#else -# define FMT_TRY if (true) -# define FMT_CATCH(x) if (false) -#endif - #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable : 4702) // unreachable code @@ -73,8 +52,6 @@ inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) { # define FMT_SNPRINTF fmt_snprintf #endif // _MSC_VER -using format_func = void (*)(internal::buffer&, int, string_view); - // A portable thread-safe version of strerror. // Sets buffer to point to a string describing the error code. // This can be either a pointer to a string stored in buffer, @@ -168,15 +145,6 @@ FMT_FUNC void format_error_code(internal::buffer& out, int error_code, assert(out.size() <= inline_buffer_size); } -// A wrapper around fwrite that throws on error. -FMT_FUNC void fwrite_fully(const void* ptr, size_t size, size_t count, - FILE* stream) { - size_t written = std::fwrite(ptr, size, count, stream); - if (written < count) { - FMT_THROW(system_error(errno, "cannot write to file")); - } -} - FMT_FUNC void report_error(format_func func, int error_code, string_view message) FMT_NOEXCEPT { memory_buffer full_message; @@ -185,6 +153,14 @@ FMT_FUNC void report_error(format_func func, int error_code, (void)std::fwrite(full_message.data(), full_message.size(), 1, stderr); std::fputc('\n', stderr); } + +// A wrapper around fwrite that throws on error. +FMT_FUNC void fwrite_fully(const void* ptr, size_t size, size_t count, + FILE* stream) { + size_t written = std::fwrite(ptr, size, count, stream); + if (written < count) + FMT_THROW(system_error(errno, "cannot write to file")); +} } // namespace internal #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) @@ -1312,77 +1288,6 @@ FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) { buffer_.push_back(0); } -#if FMT_USE_WINDOWS_H -FMT_FUNC internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) { - if (int error_code = convert(s)) { - FMT_THROW(windows_error(error_code, - "cannot convert string from UTF-16 to UTF-8")); - } -} - -FMT_FUNC int internal::utf16_to_utf8::convert(wstring_view s) { - if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER; - int s_size = static_cast(s.size()); - if (s_size == 0) { - // WideCharToMultiByte does not support zero length, handle separately. - buffer_.resize(1); - buffer_[0] = 0; - return 0; - } - - int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0, - nullptr, nullptr); - if (length == 0) return GetLastError(); - buffer_.resize(length + 1); - length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0], - length, nullptr, nullptr); - if (length == 0) return GetLastError(); - buffer_[length] = 0; - return 0; -} - -FMT_FUNC void windows_error::init(int err_code, string_view format_str, - format_args args) { - error_code_ = err_code; - memory_buffer buffer; - internal::format_windows_error(buffer, err_code, vformat(format_str, args)); - std::runtime_error& base = *this; - base = std::runtime_error(to_string(buffer)); -} - -FMT_FUNC void internal::format_windows_error(internal::buffer& out, - int error_code, - string_view message) FMT_NOEXCEPT { - FMT_TRY { - wmemory_buffer buf; - buf.resize(inline_buffer_size); - for (;;) { - wchar_t* system_message = &buf[0]; - int result = FormatMessageW( - FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, - error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), system_message, - static_cast(buf.size()), nullptr); - if (result != 0) { - utf16_to_utf8 utf8_message; - if (utf8_message.convert(system_message) == ERROR_SUCCESS) { - internal::writer w(out); - w.write(message); - w.write(": "); - w.write(utf8_message); - return; - } - break; - } - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) - break; // Can't get error message, report error code instead. - buf.resize(buf.size() * 2); - } - } - FMT_CATCH(...) {} - format_error_code(out, error_code, message); -} -#endif // FMT_USE_WINDOWS_H - FMT_FUNC void format_system_error(internal::buffer& out, int error_code, string_view message) FMT_NOEXCEPT { FMT_TRY { @@ -1417,13 +1322,6 @@ FMT_FUNC void report_system_error(int error_code, report_error(format_system_error, error_code, message); } -#if FMT_USE_WINDOWS_H -FMT_FUNC void report_windows_error(int error_code, - fmt::string_view message) FMT_NOEXCEPT { - report_error(internal::format_windows_error, error_code, message); -} -#endif - FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { memory_buffer buffer; internal::vformat_to(buffer, format_str, diff --git a/include/fmt/format.h b/include/fmt/format.h index 6662e334..bec24906 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -69,7 +69,7 @@ # define FMT_HAS_BUILTIN(x) 0 #endif -#if __cplusplus == 201103L || __cplusplus == 201402L +#if __cplusplus == 201103L || __cplusplus == 201402L # if defined(__clang__) # define FMT_FALLTHROUGH [[clang::fallthrough]] # elif FMT_GCC_VERSION >= 700 @@ -78,7 +78,7 @@ # define FMT_FALLTHROUGH # endif #elif (FMT_HAS_CPP_ATTRIBUTE(fallthrough) && (__cplusplus >= 201703)) || \ - (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) + (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # define FMT_FALLTHROUGH [[fallthrough]] #else # define FMT_FALLTHROUGH @@ -110,6 +110,14 @@ FMT_END_NAMESPACE # endif #endif +#if FMT_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + #ifndef FMT_USE_USER_DEFINED_LITERALS // For Intel and NVIDIA compilers both they and the system gcc/msc support UDLs. # if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \ @@ -953,39 +961,6 @@ class utf8_to_utf16 { std::wstring str() const { return {&buffer_[0], size()}; } }; -// Define FMT_USE_WINDOWS_H to 0 to disable use of windows.h. -// All the functionality that relies on it will be disabled too. -#ifndef _WIN32 -# define FMT_USE_WINDOWS_H 0 -#elif !defined(FMT_USE_WINDOWS_H) -# define FMT_USE_WINDOWS_H 1 -#endif - -#if FMT_USE_WINDOWS_H -// A converter from UTF-16 to UTF-8. -// It is only provided for Windows since other systems support UTF-8 natively. -class utf16_to_utf8 { - private: - memory_buffer buffer_; - - public: - utf16_to_utf8() {} - FMT_API explicit utf16_to_utf8(wstring_view s); - operator string_view() const { return string_view(&buffer_[0], size()); } - size_t size() const { return buffer_.size() - 1; } - const char* c_str() const { return &buffer_[0]; } - std::string str() const { return std::string(&buffer_[0], size()); } - - // Performs conversion returning a system error code instead of - // throwing exception on conversion error. This method may still throw - // in case of memory allocation error. - FMT_API int convert(wstring_view s); -}; - -FMT_API void format_windows_error(internal::buffer& out, int error_code, - string_view message) FMT_NOEXCEPT; -#endif - template struct null {}; // Workaround an array initialization issue in gcc 4.8. @@ -2660,6 +2635,14 @@ void handle_dynamic_spec(int& value, arg_ref ref, break; } } + +using format_func = void (*)(internal::buffer&, int, string_view); + +FMT_API void format_error_code(buffer& out, int error_code, + string_view message) FMT_NOEXCEPT; + +FMT_API void report_error(format_func func, int error_code, + string_view message) FMT_NOEXCEPT; } // namespace internal template @@ -2779,55 +2762,6 @@ FMT_API void format_system_error(internal::buffer& out, int error_code, FMT_API void report_system_error(int error_code, string_view message) FMT_NOEXCEPT; -#if FMT_USE_WINDOWS_H - -/** A Windows error. */ -class windows_error : public system_error { - private: - FMT_API void init(int error_code, string_view format_str, format_args args); - - public: - /** - \rst - Constructs a :class:`fmt::windows_error` object with the description - of the form - - .. parsed-literal:: - **: ** - - where ** is the formatted message and ** is the - system message corresponding to the error code. - *error_code* is a Windows error code as given by ``GetLastError``. - If *error_code* is not a valid error code such as -1, the system message - will look like "error -1". - - **Example**:: - - // This throws a windows_error with the description - // cannot open file 'madeup': The system cannot find the file specified. - // or similar (system message may vary). - const char *filename = "madeup"; - LPOFSTRUCT of = LPOFSTRUCT(); - HFILE file = OpenFile(filename, &of, OF_READ); - if (file == HFILE_ERROR) { - throw fmt::windows_error(GetLastError(), - "cannot open file '{}'", filename); - } - \endrst - */ - template - windows_error(int error_code, string_view message, const Args&... args) { - init(error_code, message, make_format_args(args...)); - } -}; - -// Reports a Windows error without throwing an exception. -// Can be used to report errors from destructors. -FMT_API void report_windows_error(int error_code, - string_view message) FMT_NOEXCEPT; - -#endif - /** Fast integer formatter. */ class format_int { private: @@ -3513,18 +3447,18 @@ FMT_CONSTEXPR internal::udl_arg operator"" _a(const wchar_t* s, #endif // FMT_USE_USER_DEFINED_LITERALS FMT_END_NAMESPACE -#define FMT_STRING_IMPL(s, ...) \ - [] { \ - /* Use a macro-like name to avoid shadowing warnings. */ \ - struct FMT_STRING : fmt::compile_string { \ - using char_type = typename std::remove_cv::type>::type>::type; \ - __VA_ARGS__ FMT_CONSTEXPR \ - operator fmt::basic_string_view() const { \ - return {s, sizeof(s) / sizeof(char_type) - 1}; \ - } \ - }; \ - return FMT_STRING(); \ +#define FMT_STRING_IMPL(s, ...) \ + [] { \ + /* Use a macro-like name to avoid shadowing warnings. */ \ + struct FMT_STRING : fmt::compile_string { \ + using char_type = typename std::remove_cv::type>::type>::type; \ + __VA_ARGS__ FMT_CONSTEXPR \ + operator fmt::basic_string_view() const { \ + return {s, sizeof(s) / sizeof(char_type) - 1}; \ + } \ + }; \ + return FMT_STRING(); \ }() /** diff --git a/include/fmt/os.h b/include/fmt/os.h new file mode 100644 index 00000000..de6747ac --- /dev/null +++ b/include/fmt/os.h @@ -0,0 +1,97 @@ +// Formatting library for C++ - optional OS-specific functionality +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_OS_H_ +#define FMT_OS_H_ + +#include "format.h" + +FMT_BEGIN_NAMESPACE + +// Define FMT_USE_WINDOWS_H to 0 to disable use of windows.h. +// All the functionality that relies on it will be disabled too. +#ifndef _WIN32 +# define FMT_USE_WINDOWS_H 0 +#elif !defined(FMT_USE_WINDOWS_H) +# define FMT_USE_WINDOWS_H 1 +#endif + +#if FMT_USE_WINDOWS_H +namespace internal { +// A converter from UTF-16 to UTF-8. +// It is only provided for Windows since other systems support UTF-8 natively. +class utf16_to_utf8 { + private: + memory_buffer buffer_; + + public: + utf16_to_utf8() {} + FMT_API explicit utf16_to_utf8(wstring_view s); + operator string_view() const { return string_view(&buffer_[0], size()); } + size_t size() const { return buffer_.size() - 1; } + const char* c_str() const { return &buffer_[0]; } + std::string str() const { return std::string(&buffer_[0], size()); } + + // Performs conversion returning a system error code instead of + // throwing exception on conversion error. This method may still throw + // in case of memory allocation error. + FMT_API int convert(wstring_view s); +}; + +FMT_API void format_windows_error(internal::buffer& out, int error_code, + string_view message) FMT_NOEXCEPT; +} // namespace internal + +/** A Windows error. */ +class windows_error : public system_error { + private: + FMT_API void init(int error_code, string_view format_str, format_args args); + + public: + /** + \rst + Constructs a :class:`fmt::windows_error` object with the description + of the form + + .. parsed-literal:: + **: ** + + where ** is the formatted message and ** is the + system message corresponding to the error code. + *error_code* is a Windows error code as given by ``GetLastError``. + If *error_code* is not a valid error code such as -1, the system message + will look like "error -1". + + **Example**:: + + // This throws a windows_error with the description + // cannot open file 'madeup': The system cannot find the file specified. + // or similar (system message may vary). + const char *filename = "madeup"; + LPOFSTRUCT of = LPOFSTRUCT(); + HFILE file = OpenFile(filename, &of, OF_READ); + if (file == HFILE_ERROR) { + throw fmt::windows_error(GetLastError(), + "cannot open file '{}'", filename); + } + \endrst + */ + template + windows_error(int error_code, string_view message, const Args&... args) { + init(error_code, message, make_format_args(args...)); + } +}; + +// Reports a Windows error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_windows_error(int error_code, + string_view message) FMT_NOEXCEPT; +#endif + +FMT_END_NAMESPACE + +#endif // FMT_OS_H_ diff --git a/src/posix.cc b/src/posix.cc index 3db72b76..46e0d6cd 100644 --- a/src/posix.cc +++ b/src/posix.cc @@ -11,38 +11,43 @@ #endif #include "fmt/posix.h" +#include "fmt/os.h" #include #if FMT_USE_FCNTL -#include -#include +# include +# include -#ifndef _WIN32 -# include -#else -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include +# ifndef _WIN32 +# include +# else +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# include + +# define O_CREAT _O_CREAT +# define O_TRUNC _O_TRUNC + +# ifndef S_IRUSR +# define S_IRUSR _S_IREAD +# endif + +# ifndef S_IWUSR +# define S_IWUSR _S_IWRITE +# endif + +# ifdef __MINGW32__ +# define _SH_DENYNO 0x40 +# endif +# endif // _WIN32 +#endif // FMT_USE_FCNTL + +#if FMT_USE_WINDOWS_H # include - -# define O_CREAT _O_CREAT -# define O_TRUNC _O_TRUNC - -# ifndef S_IRUSR -# define S_IRUSR _S_IREAD -# endif - -# ifndef S_IWUSR -# define S_IWUSR _S_IWRITE -# endif - -# ifdef __MINGW32__ -# define _SH_DENYNO 0x40 -# endif -#endif // _WIN32 -#endif // FMT_USE_FCNTL +#endif #ifdef fileno # undef fileno @@ -68,6 +73,81 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; } FMT_BEGIN_NAMESPACE +#if FMT_USE_WINDOWS_H +internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) { + if (int error_code = convert(s)) { + FMT_THROW(windows_error(error_code, + "cannot convert string from UTF-16 to UTF-8")); + } +} + +int internal::utf16_to_utf8::convert(wstring_view s) { + if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER; + int s_size = static_cast(s.size()); + if (s_size == 0) { + // WideCharToMultiByte does not support zero length, handle separately. + buffer_.resize(1); + buffer_[0] = 0; + return 0; + } + + int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0, + nullptr, nullptr); + if (length == 0) return GetLastError(); + buffer_.resize(length + 1); + length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0], + length, nullptr, nullptr); + if (length == 0) return GetLastError(); + buffer_[length] = 0; + return 0; +} + +void windows_error::init(int err_code, string_view format_str, + format_args args) { + error_code_ = err_code; + memory_buffer buffer; + internal::format_windows_error(buffer, err_code, vformat(format_str, args)); + std::runtime_error& base = *this; + base = std::runtime_error(to_string(buffer)); +} + +void internal::format_windows_error(internal::buffer& out, int error_code, + string_view message) FMT_NOEXCEPT { + FMT_TRY { + wmemory_buffer buf; + buf.resize(inline_buffer_size); + for (;;) { + wchar_t* system_message = &buf[0]; + int result = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, + error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), system_message, + static_cast(buf.size()), nullptr); + if (result != 0) { + utf16_to_utf8 utf8_message; + if (utf8_message.convert(system_message) == ERROR_SUCCESS) { + internal::writer w(out); + w.write(message); + w.write(": "); + w.write(utf8_message); + return; + } + break; + } + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + break; // Can't get error message, report error code instead. + buf.resize(buf.size() * 2); + } + } + FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +void report_windows_error(int error_code, + fmt::string_view message) FMT_NOEXCEPT { + report_error(internal::format_windows_error, error_code, message); +} +#endif // FMT_USE_WINDOWS_H + buffered_file::~buffered_file() FMT_NOEXCEPT { if (file_ && FMT_SYSTEM(fclose(file_)) != 0) report_system_error(errno, "cannot close file"); @@ -99,12 +179,12 @@ int buffered_file::fileno() const { #if FMT_USE_FCNTL file::file(cstring_view path, int oflag) { int mode = S_IRUSR | S_IWUSR; -#if defined(_WIN32) && !defined(__MINGW32__) +# if defined(_WIN32) && !defined(__MINGW32__) fd_ = -1; FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode)); -#else +# else FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode))); -#endif +# endif if (fd_ == -1) FMT_THROW(system_error(errno, "cannot open file {}", path.c_str())); } @@ -126,7 +206,7 @@ void file::close() { } long long file::size() const { -#ifdef _WIN32 +# ifdef _WIN32 // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT // is less than 0x0500 as is the case with some default MinGW builds. // Both functions support large file sizes. @@ -140,7 +220,7 @@ long long file::size() const { } unsigned long long long_size = size_upper; return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower; -#else +# else using Stat = struct stat; Stat file_stat = Stat(); if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1) @@ -148,7 +228,7 @@ long long file::size() const { static_assert(sizeof(long long) >= sizeof(file_stat.st_size), "return type of file::size is not large enough"); return file_stat.st_size; -#endif +# endif } std::size_t file::read(void* buffer, std::size_t count) { @@ -195,15 +275,15 @@ void file::pipe(file& read_end, file& write_end) { read_end.close(); write_end.close(); int fds[2] = {}; -#ifdef _WIN32 +# ifdef _WIN32 // Make the default pipe capacity same as on Linux 2.6.11+. enum { DEFAULT_CAPACITY = 65536 }; int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY)); -#else +# else // Don't retry as the pipe function doesn't return EINTR. // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html int result = FMT_POSIX_CALL(pipe(fds)); -#endif +# endif if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe")); // The following assignments don't throw because read_fd and write_fd // are closed. @@ -223,15 +303,15 @@ buffered_file file::fdopen(const char* mode) { } long getpagesize() { -#ifdef _WIN32 +# ifdef _WIN32 SYSTEM_INFO si; GetSystemInfo(&si); return si.dwPageSize; -#else +# else long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE)); if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size")); return size; -#endif +# endif } #endif // FMT_USE_FCNTL FMT_END_NAMESPACE diff --git a/test/format-test.cc b/test/format-test.cc index 195ac570..c6828b3f 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -417,67 +417,6 @@ TEST(UtilTest, UTF8ToUTF16EmptyString) { EXPECT_EQ(s.size(), u.size()); } -#ifdef _WIN32 -TEST(UtilTest, UTF16ToUTF8) { - std::string s = "ёжик"; - fmt::internal::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A"); - EXPECT_EQ(s, u.str()); - EXPECT_EQ(s.size(), u.size()); -} - -TEST(UtilTest, UTF16ToUTF8EmptyString) { - std::string s = ""; - fmt::internal::utf16_to_utf8 u(L""); - EXPECT_EQ(s, u.str()); - EXPECT_EQ(s.size(), u.size()); -} - -template -void check_utf_conversion_error( - const char* message, - fmt::basic_string_view str = fmt::basic_string_view(0, 1)) { - fmt::memory_buffer out; - fmt::internal::format_windows_error(out, ERROR_INVALID_PARAMETER, message); - fmt::system_error error(0, ""); - try { - (Converter)(str); - } catch (const fmt::system_error& e) { - error = e; - } - EXPECT_EQ(ERROR_INVALID_PARAMETER, error.error_code()); - EXPECT_EQ(fmt::to_string(out), error.what()); -} - -TEST(UtilTest, UTF16ToUTF8Error) { - check_utf_conversion_error( - "cannot convert string from UTF-16 to UTF-8"); -} - -TEST(UtilTest, UTF16ToUTF8Convert) { - fmt::internal::utf16_to_utf8 u; - EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(fmt::wstring_view(0, 1))); - EXPECT_EQ(ERROR_INVALID_PARAMETER, - u.convert(fmt::wstring_view(L"foo", INT_MAX + 1u))); -} -#endif // _WIN32 - -typedef void (*FormatErrorMessage)(fmt::internal::buffer& out, - int error_code, string_view message); - -template -void check_throw_error(int error_code, FormatErrorMessage format) { - fmt::system_error error(0, ""); - try { - throw Error(error_code, "test {}", "error"); - } catch (const fmt::system_error& e) { - error = e; - } - fmt::memory_buffer message; - format(message, error_code, "test error"); - EXPECT_EQ(to_string(message), error.what()); - EXPECT_EQ(error_code, error.error_code()); -} - TEST(UtilTest, FormatSystemError) { fmt::memory_buffer message; fmt::format_system_error(message, EDOM, "test"); @@ -506,7 +445,17 @@ TEST(UtilTest, SystemError) { fmt::system_error e(EDOM, "test"); EXPECT_EQ(fmt::format("test: {}", get_system_error(EDOM)), e.what()); EXPECT_EQ(EDOM, e.error_code()); - check_throw_error(EDOM, fmt::format_system_error); + + fmt::system_error error(0, ""); + try { + throw fmt::system_error(EDOM, "test {}", "error"); + } catch (const fmt::system_error& e) { + error = e; + } + fmt::memory_buffer message; + fmt::format_system_error(message, EDOM, "test error"); + EXPECT_EQ(to_string(message), error.what()); + EXPECT_EQ(EDOM, error.error_code()); } TEST(UtilTest, ReportSystemError) { @@ -517,69 +466,6 @@ TEST(UtilTest, ReportSystemError) { to_string(out)); } -#ifdef _WIN32 - -TEST(UtilTest, FormatWindowsError) { - 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(&message), 0, 0); - fmt::internal::utf16_to_utf8 utf8_message(message); - LocalFree(message); - fmt::memory_buffer actual_message; - fmt::internal::format_windows_error(actual_message, ERROR_FILE_EXISTS, - "test"); - EXPECT_EQ(fmt::format("test: {}", utf8_message.str()), - fmt::to_string(actual_message)); - actual_message.resize(0); - fmt::internal::format_windows_error(actual_message, ERROR_FILE_EXISTS, - fmt::string_view(0, max_value())); - EXPECT_EQ(fmt::format("error {}", ERROR_FILE_EXISTS), - fmt::to_string(actual_message)); -} - -TEST(UtilTest, FormatLongWindowsError) { - LPWSTR message = 0; - // this error code is not available on all Windows platforms and - // Windows SDKs, so do not fail the test if the error string cannot - // be retrieved. - const int provisioning_not_allowed = - 0x80284013L /*TBS_E_PROVISIONING_NOT_ALLOWED*/; - if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - 0, static_cast(provisioning_not_allowed), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&message), 0, 0) == 0) { - return; - } - fmt::internal::utf16_to_utf8 utf8_message(message); - LocalFree(message); - fmt::memory_buffer actual_message; - fmt::internal::format_windows_error(actual_message, provisioning_not_allowed, - "test"); - EXPECT_EQ(fmt::format("test: {}", utf8_message.str()), - fmt::to_string(actual_message)); -} - -TEST(UtilTest, WindowsError) { - check_throw_error(ERROR_FILE_EXISTS, - fmt::internal::format_windows_error); -} - -TEST(UtilTest, ReportWindowsError) { - fmt::memory_buffer out; - fmt::internal::format_windows_error(out, ERROR_FILE_EXISTS, "test error"); - out.push_back('\n'); - EXPECT_WRITE(stderr, - fmt::report_windows_error(ERROR_FILE_EXISTS, "test error"), - fmt::to_string(out)); -} - -#endif // _WIN32 - TEST(StringViewTest, Ctor) { EXPECT_STREQ("abc", string_view("abc").data()); EXPECT_EQ(3u, string_view("abc").size()); @@ -2611,7 +2497,7 @@ TEST(FormatTest, FormatU8String) { } TEST(FormatTest, EmphasisNonHeaderOnly) { - // ensure this compiles even if FMT_HEADER_ONLY is not defined. + // Ensure this compiles even if FMT_HEADER_ONLY is not defined. EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold error"), "\x1b[1mbold error\x1b[0m"); } diff --git a/test/posix-test.cc b/test/posix-test.cc index 24d211d3..e3507e69 100644 --- a/test/posix-test.cc +++ b/test/posix-test.cc @@ -9,6 +9,7 @@ #include #include +#include "fmt/os.h" #include "fmt/posix.h" #include "gtest-extra.h" #include "util.h" @@ -20,6 +21,122 @@ using fmt::buffered_file; using fmt::error_code; +#ifdef _WIN32 + +#include + +TEST(UtilTest, UTF16ToUTF8) { + std::string s = "ёжик"; + fmt::internal::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A"); + EXPECT_EQ(s, u.str()); + EXPECT_EQ(s.size(), u.size()); +} + +TEST(UtilTest, UTF16ToUTF8EmptyString) { + std::string s = ""; + fmt::internal::utf16_to_utf8 u(L""); + EXPECT_EQ(s, u.str()); + EXPECT_EQ(s.size(), u.size()); +} + +template +void check_utf_conversion_error( + const char* message, + fmt::basic_string_view str = fmt::basic_string_view(0, 1)) { + fmt::memory_buffer out; + fmt::internal::format_windows_error(out, ERROR_INVALID_PARAMETER, message); + fmt::system_error error(0, ""); + try { + (Converter)(str); + } catch (const fmt::system_error& e) { + error = e; + } + EXPECT_EQ(ERROR_INVALID_PARAMETER, error.error_code()); + EXPECT_EQ(fmt::to_string(out), error.what()); +} + +TEST(UtilTest, UTF16ToUTF8Error) { + check_utf_conversion_error( + "cannot convert string from UTF-16 to UTF-8"); +} + +TEST(UtilTest, UTF16ToUTF8Convert) { + fmt::internal::utf16_to_utf8 u; + EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(fmt::wstring_view(0, 1))); + EXPECT_EQ(ERROR_INVALID_PARAMETER, + u.convert(fmt::wstring_view(L"foo", INT_MAX + 1u))); +} + +TEST(UtilTest, FormatWindowsError) { + 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(&message), 0, 0); + fmt::internal::utf16_to_utf8 utf8_message(message); + LocalFree(message); + fmt::memory_buffer actual_message; + fmt::internal::format_windows_error(actual_message, ERROR_FILE_EXISTS, + "test"); + EXPECT_EQ(fmt::format("test: {}", utf8_message.str()), + fmt::to_string(actual_message)); + actual_message.resize(0); + auto max_size = fmt::internal::max_value(); + fmt::internal::format_windows_error(actual_message, ERROR_FILE_EXISTS, + fmt::string_view(0, max_size)); + EXPECT_EQ(fmt::format("error {}", ERROR_FILE_EXISTS), + fmt::to_string(actual_message)); +} + +TEST(UtilTest, FormatLongWindowsError) { + LPWSTR message = 0; + // this error code is not available on all Windows platforms and + // Windows SDKs, so do not fail the test if the error string cannot + // be retrieved. + const int provisioning_not_allowed = + 0x80284013L /*TBS_E_PROVISIONING_NOT_ALLOWED*/; + if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + 0, static_cast(provisioning_not_allowed), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message), 0, 0) == 0) { + return; + } + fmt::internal::utf16_to_utf8 utf8_message(message); + LocalFree(message); + fmt::memory_buffer actual_message; + fmt::internal::format_windows_error(actual_message, provisioning_not_allowed, + "test"); + EXPECT_EQ(fmt::format("test: {}", utf8_message.str()), + fmt::to_string(actual_message)); +} + +TEST(UtilTest, WindowsError) { + fmt::system_error error(0, ""); + try { + throw fmt::windows_error(ERROR_FILE_EXISTS, "test {}", "error"); + } catch (const fmt::system_error& e) { + error = e; + } + fmt::memory_buffer message; + fmt::internal::format_windows_error(message, ERROR_FILE_EXISTS, "test error"); + EXPECT_EQ(to_string(message), error.what()); + EXPECT_EQ(ERROR_FILE_EXISTS, error.error_code()); +} + +TEST(UtilTest, ReportWindowsError) { + fmt::memory_buffer out; + fmt::internal::format_windows_error(out, ERROR_FILE_EXISTS, "test error"); + out.push_back('\n'); + EXPECT_WRITE(stderr, + fmt::report_windows_error(ERROR_FILE_EXISTS, "test error"), + fmt::to_string(out)); +} + +#endif // _WIN32 + #if FMT_USE_FCNTL using fmt::file;