Home > Mobile >  Is there a way to teach gtest to print user-defined types using libfmt's formatter?
Is there a way to teach gtest to print user-defined types using libfmt's formatter?

Time:07-13

I'm wondering if there is a way to make gtest understand a user-defined types libfmt's formatter, for the means of printing a readable error output? I know how I can teach gtest to understand user-defined types via adding the stream insertion operator operator<< for this very user-defined type, e.g.

std::ostream& operator<<(std::ostream& stream, CpuTimes const& anything);
std::ostream& operator<<(std::ostream& stream, CpuStats const& anything);

This is well documented here.

But I heavily use libfmt, which requires a formatting function to be implemented to produce a readable and printable output of a user-defined type. This so-called fmt::formatter is actually a template specialization, e.g.

namespace fmt {

template <> struct formatter<CpuTimes> : basics::fmt::ParseContextEmpty {
    format_context::iterator format(CpuTimes const& times, format_context& ctx);
};
template <> struct formatter<CpuStats> : basics::fmt::ParseContextEmpty {
    format_context::iterator format(CpuStats const& stats, format_context& ctx);
};
template <> struct formatter<CpuLimits> : basics::fmt::ParseContextEmpty {
    format_context::iterator format(CpuLimits const& limits, format_context& ctx);
};
} // namespace fmt

For gtest to understand this format, you have to write the same implementation for the operator<< over and over for every type you want gtest to print properly, like

std::ostream& operator<<(std::ostream& stream, CpuTimes const& anything) {
    return stream << fmt::format("{}", anything);
}
std::ostream& operator<<(std::ostream& stream, CpuStats const& anything) {
    return stream << fmt::format("{}", anything);
}
std::ostream& operator<<(std::ostream& stream, CpuLimits const& anything) {
    return stream << fmt::format("{}", anything);
}

Is there a way to spare my writing this boilerplate code?

CodePudding user response:

To avoid problems caused by ambiguity of PrintTo or operator<< namespace is needed.

Here is some demo when namespaces is used:

#include <fmt/format.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>

namespace me {
struct Foo {
    int x = 0;
    double y = 0;
};

bool operator==(const Foo& a, const Foo& b)
{
    return a.x == b.x && a.y == b.y;
}
}

template <>
struct fmt::formatter<me::Foo> {
    char presentation = 'f';

    constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin())
    {
        auto it = ctx.begin(), end = ctx.end();
        if (it != end && (*it == 'f' || *it == 'e'))
            presentation = *it  ;
        if (it != end && *it != '}')
            throw format_error("invalid format");
        return it;
    }

    template <typename FormatContext>
    auto format(const me::Foo& p, FormatContext& ctx) -> decltype(ctx.out())
    {
        return presentation == 'f'
            ? format_to(ctx.out(), "({}, {:.1f})", p.x, p.y)
            : format_to(ctx.out(), "({}, {:.1e})", p.x, p.y);
    }
};

#if VERSION == 1

namespace me {
template <typename T>
void PrintTo(const T& value, ::std::ostream* os)
{
    *os << fmt::format(FMT_STRING("{}"), value);
}
}

#elif VERSION == 2

namespace me {
template <typename T>
std::ostream& operator<<(std::ostream& out, const T& value)
{
    ::std::operator<<(out, fmt::format(FMT_STRING("{}"), value));
    return out;
}
}
#endif

class MagicTest : public testing::Test { };

TEST_F(MagicTest, CheckFmtFormater)
{
    EXPECT_EQ(fmt::format("{}", me::Foo {}), "(0, 0.0)");
}

TEST_F(MagicTest, FailOnPurpuse)
{
    EXPECT_EQ(me::Foo {}, (me::Foo { 1, 0 }));
}

Dropping me namespace cause all implementations to have ambiguity problems.

Note that namespace causes that argument-dependent lookup is used.

  • Related