diff --git a/include/fmt/async.h b/include/fmt/async.h index 9938d7c7..4f81df61 100644 --- a/include/fmt/async.h +++ b/include/fmt/async.h @@ -90,6 +90,7 @@ namespace async { namespace detail { namespace detail = fmt::detail; template using decay_t = typename std::decay::type; +template using add_const_t = typename std::add_const::type; enum class store_method { numeric, // stored by libfmt as numeric value, no need for extra storage @@ -188,7 +189,7 @@ struct arg_transformer { template , Context>> using transformed_arg_type = conditional_t::custom_store, typename custom_store_method::transformed_type, - conditional_t::value && !stored_as_string_object::value, basic_string_view, std::add_const_t>&> + conditional_t::value && !stored_as_string_object::value, basic_string_view, add_const_t>&> >; template @@ -205,7 +206,7 @@ struct async_entry_constructor { private: using range = typename range_builder<0, sizeof...(Args)>::type; template - constexpr async_entry_constructor(void* buf, const S& format_str, index_list, Args... args) : pEntry(reinterpret_cast(buf)), pBuffer(get_buffer_store(buf)) { + FMT_CONSTEXPR async_entry_constructor(void* buf, const S& format_str, index_list, Args... args) : pEntry(reinterpret_cast(buf)), pBuffer(get_buffer_store(buf)) { auto p = new(buf) Entry(format_str, store(std::forward(args))...); p->set_dtor(Dtor::value ? Dtor::destruct : nullptr); } @@ -257,7 +258,7 @@ private: return basic_string_view(pStart, sv.size()); } - static constexpr char* get_buffer_store(void* buf) { + static FMT_CONSTEXPR char* get_buffer_store(void* buf) { char* const pentry = reinterpret_cast(buf); // entry will be constructed here char* const pobjs = pentry + sizeof(Entry); // objects will be stored starting here char* const pbufs = pobjs + get_obj_size(); // buffers will be stored starting here diff --git a/include/fmt/test.cpp b/include/fmt/test.cpp deleted file mode 100644 index ddfbc87a..00000000 --- a/include/fmt/test.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#define FMT_HEADER_ONLY -#include "async.h" - -using namespace fmt; - -#include - - - -int main() { - fmt::print("entry header: {}\n", sizeof(basic_async_entry)); - auto entry = make_async_entry("The answer is {}\n", 42); - async::print(entry); - - char buf[2000]; - size_t size = store_async_entry(buf, "This {} is {}{}\n", "answer", std::to_string(4), 2); - fmt::print("entry size: {}\n", size); - auto& entryref1 = entry; - auto& entryref = reinterpret_cast(buf[0]); - async::print(entryref); - - std::cout << &entryref << "\n" << &(entryref.arg_store_) << "\n" << sizeof(entryref.arg_store_) << std::endl; -} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7ae5659d..2c1aa605 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -88,6 +88,7 @@ function(add_fmt_test name) endfunction() add_fmt_test(assert-test) +add_fmt_test(async-test) add_fmt_test(chrono-test) add_fmt_test(color-test) add_fmt_test(core-test) diff --git a/test/async-test.cc b/test/async-test.cc new file mode 100644 index 00000000..0ab6a8bf --- /dev/null +++ b/test/async-test.cc @@ -0,0 +1,103 @@ + +#ifdef WIN32 +# define _CRT_SECURE_NO_WARNINGS +#endif + +#include "fmt/async.h" +#include "gtest-extra.h" + +#define TWENTY_ARGS "{} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} " +static const char multiple_brackets[] = TWENTY_ARGS; +static fmt::string_view get_format_string(size_t num_args) { return fmt::string_view(&multiple_brackets[(20 - num_args)*3], num_args * 3); } + +namespace trivial_entry_test { +template +inline void make_async_entry_test(Args&&... args) { + std::string formatted = fmt::format(std::forward(args)...); + // format_arg_store containing named_args are not copy-constructible, auto&& is required. + auto&& entry = fmt::make_async_entry(std::forward(args)...); + EXPECT_EQ(formatted, fmt::async::format(entry)); +} + +template +inline void make_async_entry_test_args(Args&&... args) { + make_async_entry_test(get_format_string(sizeof...(args)), std::forward(args)...); +} + +void make_async_entry_and_alter(const std::string& s) { + std::string str = s; + std::string formatted = fmt::format("{}", str); + auto entry = fmt::make_async_entry("{}", str); + str.front() = formatted.front() = '#'; + str.back() = formatted.back() = '#'; + EXPECT_EQ(formatted, fmt::format("{}", str)); + EXPECT_EQ(formatted, fmt::async::format(entry)); +} +} + +namespace stored_entry_test { +static char buf[1 * 1024 * 1024] = {}; // 1M should be enough for this test +static fmt::basic_async_entry& entry = reinterpret_cast&>(buf); + +template +inline void make_async_entry_test(Args&&... args) { + std::string formatted = fmt::format(std::forward(args)...); + // FIXME: how to test entry_size? + size_t entry_size = fmt::store_async_entry(buf, std::forward(args)...); + EXPECT_EQ(formatted, fmt::async::format(entry)); +} + +template +inline void make_async_entry_test_args(Args&&... args) { + make_async_entry_test(get_format_string(sizeof...(args)), std::forward(args)...); +} + +void make_async_entry_and_alter(const std::string& s) { + std::string str = s; + std::string formatted = fmt::format("{}", str); + size_t entry_size = fmt::store_async_entry(buf, "{}", str); + str.front() = str.back() = '#'; + EXPECT_EQ(formatted, fmt::async::format(entry)); + formatted.front() = formatted.back() = '#'; + EXPECT_EQ(formatted, fmt::format("{}", str)); +} +} + +TEST(AsyncTest, TrivialEntry) { + using namespace trivial_entry_test; + // basic test + make_async_entry_test("The answer is {}", 42); + // index + make_async_entry_test("The answer of {2}*{1} is {0}", 42, 6, 7); + + // named args + using namespace fmt::literals; + make_async_entry_test("The answer of {}*{a} is {product}", 6, "product"_a=42, "a"_a=7); + + // long arg list (>=16, as max_packed_args = 15) + make_async_entry_test_args(short(1), (unsigned short)2, 3, 4U, 5L, 6UL, 7LL, 8ULL, 9.0F, 10.0, 11, 12, 13, 14, 15, 16, 17, 18); + make_async_entry_test(TWENTY_ARGS "{narg}", 1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,0/*ignore*/,"narg"_a="bingo"); + + // this API copies only reference. + make_async_entry_and_alter("[change me]"); +} + + +TEST(AsyncTest, StoredEntry) { + using namespace stored_entry_test; + // basic test + make_async_entry_test("The answer is {}", 42); + // index + make_async_entry_test("The answer of {2}*{1} is {0}", 42, 6, 7); + + // named args + using namespace fmt::literals; + // make_async_entry_test("The answer of {}*{a} is {product}", 6, "product"_a=42, "a"_a=7); + + // long arg list (>=16, as max_packed_args = 15) + make_async_entry_test_args(short(1), (unsigned short)2, 3, 4U, 5L, 6UL, 7LL, 8ULL, 9.0F, 10.0, 11, 12, 13, 14, 15, 16, 17, 18); + // make_async_entry_test(TWENTY_ARGS "{narg}", 1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,0/*ignore*/,"narg"_a="bingo"); + + // this API copies only reference. + make_async_entry_and_alter("[change me]"); +}