简体   繁体   English

教 Google-Test 如何打印特征矩阵

[英]Teach Google-Test how to print Eigen Matrix

Introduction介绍

I am writing tests on Eigen matrices using Google's testing framework Google-Mock, as already discussed in another question .我正在使用 Google 的测试框架 Google-Mock 对 Eigen 矩阵编写测试,正如另一个问题中已经讨论过的那样。

With the following code I was able to add a custom Matcher to match Eigen matrices to a given precision.使用以下代码,我能够添加自定义Matcher以将特征矩阵匹配到给定的精度。

MATCHER_P2(EigenApproxEqual, expect, prec,
           std::string(negation ? "isn't" : "is") + " approx equal to" +
               ::testing::PrintToString(expect) + "\nwith precision " +
               ::testing::PrintToString(prec)) {
    return arg.isApprox(expect, prec);
}

What this does is to compare two Eigen matrices by their isApprox method , and if they don't match Google-Mock will print a corresponding error message, which will contain the expected, and the actual values of the matrices.这样做的目的是通过isApprox方法比较两个 Eigen 矩阵,如果它们不匹配,Google-Mock 将打印相应的错误消息,其中将包含矩阵的预期值和实际值。 Or, it should at least...或者,至少应该...

The Problem问题

Take the following simple test case:采用以下简单的测试用例:

TEST(EigenPrint, Simple) {
    Eigen::Matrix2d A, B;
    A << 0., 1., 2., 3.;
    B << 0., 2., 1., 3.;

    EXPECT_THAT(A, EigenApproxEqual(B, 1e-7));
}

This test will fail because A , and B are not equal.该测试将失败,因为AB不相等。 Unfortunately, the corresponding error message looks like this:不幸的是,相应的错误消息如下所示:

gtest_eigen_print.cpp:31: Failure
Value of: A
Expected: is approx equal to32-byte object <00-00 00-00 00-00 00-00 00-00 00-00 00-00 F0-3F 00-00 00-00 00-00 00-40 00-00 00-00 00-00 08-40>
with precision 1e-07
  Actual: 32-byte object <00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-40 00-00 00-00 00-00 F0-3F 00-00 00-00 00-00 08-40>

As you can see, Google-Test prints a hex-dump of the matrices, instead of a nicer representation of their values.如您所见,Google-Test 打印了矩阵的十六进制转储,而不是更好地表示它们的值。 The Google-documentation says the following about printing values of custom types: 谷歌文档说明了以下关于打印自定义类型值的内容:

This printer knows how to print built-in C++ types, native arrays, STL containers, and any type that supports the << operator .此打印机知道如何打印内置 C++ 类型、本机数组、STL 容器以及任何支持 << 运算符的类型 For other types, it prints the raw bytes in the value and hopes that you the user can figure it out.对于其他类型,它会打印值中的原始字节,并希望您的用户能够弄清楚。

The Eigen matrix comes with an operator<< .本征矩阵带有operator<< However, Google-Test, or the C++ compiler, rather, ignores it.但是,Google-Test 或 C++ 编译器会忽略它。 To my understanding, for the following reason: The signature of this operator reads ( IO.h (line 240) )据我了解,原因如下:该操作员的签名为( IO.h(第 240 行)

template<typename Derived>
std::ostream &operator<< (std::ostream &s, const DenseBase<Derived> &m);

Ie it takes a const DenseBase<Derived>& .即它需要一个const DenseBase<Derived>& The Google-test hex-dump default printer on the other hand is the default implementation of a template function.另一方面,Google-test hex-dump 默认打印机是模板函数的默认实现。 You can find the implementation here .您可以在此处找到实现。 (Follow the call-tree starting from PrintTo to see that this is the case, or prove me wrong. ;)) (按照从PrintTo开始的调用树来查看是否是这种情况,或者证明我错了。;))

So, the Google-Test default printer is a better match because it takes a const Derived & , and not only its base class const DenseBase<Derived> & .因此,Google-Test 默认打印机是一个更好的匹配,因为它需要一个const Derived & ,而不仅仅是它的基类const DenseBase<Derived> &


My Question我的问题

My question is the following.我的问题如下。 How can I tell the compiler to prefer the Eigen specific operator << over the Google-test hex-dump?我如何告诉编译器更喜欢 Eigen 特定operator <<而不是 Google 测试十六进制转储? Under the assumption that I cannot modify the class definition of the Eigen matrix.假设我不能修改本征矩阵的类定义。


My Attempts我的尝试

So far, I've tried the following things.到目前为止,我已经尝试了以下内容。

Defining a function定义函数

template <class Derived>
void PrintTo(const Eigen::DensBase<Derived> &m, std::ostream *o);

won't work for the same reason for which operator<< doesn't work.不会因为operator<<不起作用的相同原因而起作用。

The only thing which I found that worked is to use Eigen's plugin mechanism .我发现唯一有用的是使用 Eigen 的插件机制

With a file eigen_matrix_addons.hpp :使用文件eigen_matrix_addons.hpp

friend void PrintTo(const Derived &m, ::std::ostream *o) {
    *o << "\n" << m;
}

and the following include directive和以下包含指令

#define EIGEN_MATRIXBASE_PLUGIN "eigen_matrix_addons.hpp"
#include <Eigen/Dense>

the test will produce the following output:测试将产生以下输出:

gtest_eigen_print.cpp:31: Failure
Value of: A
Expected: is approx equal to
0 2
1 3
with precision 1e-07
  Actual:
0 1
2 3

What's wrong with that?这有什么问题吗?

For Eigen matrices this is probably an acceptable solution.对于 Eigen 矩阵,这可能是一个可接受的解决方案。 However, I know that I will have to apply the same thing to other template classes, very soon, which unfortunately, do not offer a plugin mechanism like Eigen's, and whose definitions I don't have direct access to.但是,我知道很快我将不得不将同样的事情应用于其他模板类,不幸的是,它们不提供像 Eigen 那样的插件机制,而且我无法直接访问其定义。

Hence, my question is: Is there a way to point the compiler to the right operator<< , or PrintTo function, without modifying the class' definition itself?因此,我的问题是:有没有办法在不修改类定义本身的情况下将编译器指向正确的operator<<PrintTo函数?


The Full Code完整代码

#include <Eigen/Dense>

#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>

// A GMock matcher for Eigen matrices.
MATCHER_P2(EigenApproxEqual, expect, prec,
           std::string(negation ? "isn't" : "is") + " approx equal to" +
               ::testing::PrintToString(expect) + "\nwith precision " +
               ::testing::PrintToString(prec)) {
    return arg.isApprox(expect, prec);
}

TEST(EigenPrint, Simple) {
    Eigen::Matrix2d A, B;
    A << 0., 1., 2., 3.;
    B << 0., 2., 1., 3.;

    EXPECT_THAT(A, EigenApproxEqual(B, 1e-7));
}

Edit: Further Attempts编辑:进一步尝试

I made some progress with an SFINAE approach.我使用 SFINAE 方法取得了一些进展。

First, I defined a trait for Eigen types.首先,我为 Eigen 类型定义了一个特征。 With it we can use std::enable_if to provide template functions only for types that fulfill this trait.有了它,我们可以使用std::enable_if为满足此特征的类型提供模板函数。

#include <type_traits>
#include <Eigen/Dense>

template <class Derived>
struct is_eigen : public std::is_base_of<Eigen::DenseBase<Derived>, Derived> {
};

My first thought was to provide such a version of PrintTo .我的第一个想法是提供这样一个版本的PrintTo Unfortunately, the compiler complains about an ambiguity between this function, and the Google-Test internal default.不幸的是,编译器抱怨此函数与 Google-Test 内部默认值之间存在歧义。 Is there a way to disambiguate and point the compiler to my function?有没有办法消除歧义并将编译器指向我的函数?

namespace Eigen {                                                             
// This function will cause the following compiler error, when defined inside 
// the Eigen namespace.                                                       
//     gmock-1.7.0/gtest/include/gtest/gtest-printers.h:600:5: error:         
//          call to 'PrintTo' is ambiguous                                    
//        PrintTo(value, os);                                                 
//        ^~~~~~~                                                             
//                                                                            
// It will simply be ignore when defined in the global namespace.             
template <class Derived,                                                      
          class = typename std::enable_if<is_eigen<Derived>::value>::type>    
void PrintTo(const Derived &m, ::std::ostream *o) {                           
    *o << "\n" << m;                                                          
}                                                                             
}    

Another approach is to overload the operator<< for the Eigen type.另一种方法是为 Eigen 类型重载operator<< It does actually work.它确实有效。 However, the downside is that it is a global overload of the ostream operator.但是,缺点是它是 ostream 运算符的全局重载。 So, it is impossible to define any test-specific formatting (eg the additional new-line) without this change also affecting non-testing code.因此,如果此更改不影响非测试代码,则不可能定义任何特定于测试的格式(例如,额外的换行符)。 Hence, I would prefer a specialized PrintTo like the one above.因此,我更喜欢像上面那样的专门的PrintTo

template <class Derived,
          class = typename std::enable_if<is_eigen<Derived>::value>::type>
::std::ostream &operator<<(::std::ostream &o, const Derived &m) {
    o << "\n" << static_cast<const Eigen::DenseBase<Derived> &>(m);
    return o;
}

Edit: Following @Alex's Answer编辑:按照@Alex的回答

In the following code I implement the solution by @Alex and implement a small function that converts references of Eigen matrices to the printable type.在下面的代码中,我实现了@Alex 的解决方案,并实现了一个将特征矩阵的引用转换为可打印类型的小函数。

#include <Eigen/Dense>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>

MATCHER_P(EigenEqual, expect,
          std::string(negation ? "isn't" : "is") + " equal to" +
              ::testing::PrintToString(expect)) {
    return arg == expect;
}

template <class Base>
class EigenPrintWrap : public Base {
    friend void PrintTo(const EigenPrintWrap &m, ::std::ostream *o) {
        *o << "\n" << m;
    }
};

template <class Base>
const EigenPrintWrap<Base> &print_wrap(const Base &base) {
    return static_cast<const EigenPrintWrap<Base> &>(base);
}

TEST(Eigen, Matrix) {
    Eigen::Matrix2i A, B;

    A << 1, 2,
         3, 4;
    B = A.transpose();

    EXPECT_THAT(print_wrap(A), EigenEqual(print_wrap(B)));
}

The problems you encounter are overload resolution problems. 遇到的问题是重载解决问题。

google test implements a template function 谷歌测试实现了模板功能

namespace testing { namespace internal {

template <typename T>
void PrintTo(const T& value, std::ostream *o) { /* do smth */ }

} }

The Eigen library defines a printer function that is based on derivation. 特征库定义了基于推导的打印机功能。 Hence 于是

struct EigenBase { };
std::ostream& operator<< (std::ostream& stream, const EigenBase& m) { /* do smth */ }

struct Eigen : public EigenBase { };

void f1() {
  Eigen e;
  std::cout << e; // works
}

void f2() {
  Eigen e;
  print_to(eigen, &std::cout); // works
}

Both do have questionable design. 两者都有可疑的设计。

Google Test should not provide an implementation of PrintTo but should instead check at compile time whether the user provides a PrintTo and otherwise call a different default printing function PrintToDefault . Google Test不应该提供PrintTo的实现,而应该在编译时检查用户是否提供PrintTo ,否则调用另一个默认打印功能PrintToDefault The PrintTo provides is a better match than the one you provided (according to overload resolution). PrintTo提供的匹配比您提供的更好(根据重载分辨率)。

on the other hand Eigen's operator<< is based on derivation and a template function will also be preferred by overload resolution. 另一方面,Eigen的operator<<基于推导,模板函数也将是重载决策的首选。

Eigen could provide a CRTP base class that inherits the operator<< which a better matching type. Eigen可以提供一个CRTP基类,它继承了一个更好的匹配类型的operator<<

What you can do is inherit from eigen and provide a CRTP overload to your inherited class avoiding the issue. 你可以做的是从eigen继承并为你继承的类提供CRTP重载,避免这个问题。

#include <gtest/gtest.h>
#include <iostream>


class EigenBase {
};

std::ostream &operator<<(std::ostream &o, const EigenBase &r) {
    o << "operator<< EigenBase called";
    return o;
}

template <typename T>
void print_to(const T &t, std::ostream *o) {
    *o << "Google Print To Called";
}

class EigenSub : public EigenBase {};

template <typename T>
struct StreamBase {
    typedef T value_type;

    // friend function is inline and static
    friend std::ostream &operator<<(std::ostream &o, const value_type &r) {
        o << "operator<< from CRTP called";
        return o;
    }

    friend void print_to(const value_type &t, std::ostream *o) {
        *o << "print_to from CRTP called";

    }
};

// this is were the magic appears, because the oeprators are actually
// defined with signatures matching the MyEigenSub class.
class MyEigenSub : public EigenSub, public StreamBase<MyEigenSub> {
};

TEST(EigenBasePrint, t1) {
    EigenBase e;
    std::cout << e << std::endl; // works
}

TEST(EigenBasePrint, t2) {
    EigenBase e;
    print_to(e, &std::cout); // works
}

TEST(EigenSubPrint, t3) {
    EigenSub e;
    std::cout << e << std::endl; // works
}

TEST(EigenCRTPPrint, t4) {
    MyEigenSub e;
    std::cout << e << std::endl; // operator<< from CRTP called
}

TEST(EigenCRTPPrint, t5) {
    MyEigenSub e;
    print_to(e, &std::cout); // prints print_to from CRTP called
}

Considering the OPs answer I want to do some clarifications. 考虑到OP的答案,我想做一些澄清。 Unlike the derived solution from the OP I actually wanted to decorate the class instead of using a function wrapper inside the assertion. 与OP的派生解决方案不同,我实际上想要修饰类而不是在断言中使用函数包装器。

For the sake of simplicity instead of using a google test match predicate I overloaded operator== . 为了简单而不是使用谷歌测试匹配谓词我重载operator==

The Idea 想法

Instead of using the Eigen class itself we use a wrapper that is a complete replacement of Eigen. 我们不使用Eigen类本身,而是使用一个完全替代Eigen的包装器。 So whenever we would create an instance of Eigen we create an instance of WrapEigen instead. 因此,每当我们创建一个Eigen实例时,我们就会创建一个WrapEigen实例。

Because we do not intent to alter the implementation of Eigen derivation is fine. 因为我们不打算改变Eigen推导的实现是好的。

Furthermore we want to add functions to the wrapper. 此外,我们希望向包装器添加功能。 I do this here with multiple inheritance of functor like classes named StreamBase and EqualBase . 我在这里做了这个函数的多重继承,比如名为StreamBaseEqualBase类。 We use CRTP in these functors to get the signatures right. 我们在这些仿函数中使用CRTP来获得正确的签名。

In order to save potential typing I used a variadic template constructor in Wrapper . 为了节省潜在的输入,我在Wrapper使用了一个可变参数模板构造函数。 It calls the corresponding base constructor if one exists. 它调用相应的基础构造函数(如果存在)。

Working Example 工作实例

#include <gtest/gtest.h>
#include <iostream>
#include <utility>

using namespace testing::internal;

struct EigenBase {
    explicit EigenBase(int i) : priv_(i) {}
    friend std::ostream &operator<<(std::ostream &o, const EigenBase &r) {
        o << r.priv_;
        return o;
    }
    friend bool operator==(const EigenBase& a, const EigenBase& b) {
        return a.priv_ == b.priv_;
    }
    int priv_;
};

struct Eigen : public EigenBase {
    explicit Eigen(int i) : EigenBase(i)  {}
};

template <typename T, typename U>
struct StreamBase {
    typedef T value_type;
    typedef const value_type &const_reference;

    friend void PrintTo(const value_type &t, std::ostream *o) {
        *o << static_cast<const U&>(t);
    }
};

template <typename T, typename U>
struct EqualBase {
    typedef T value_type;
    typedef const T &const_reference;

    friend bool operator==(const_reference a, const_reference b) {
        return static_cast<const U&>(a) 
            == static_cast<const U&>(b);
    }
};

template <typename T, typename U>
struct Wrapper 
    : public T,
      public StreamBase<Wrapper<T,U>, U>,
      public EqualBase<Wrapper<T,U>, U> {
    template <typename... Args>
    Wrapper(Args&&... args) : T(std::forward<Args>(args)...) { }
};

TEST(EigenPrint, t1) {
    Eigen e(10);
    Eigen f(11);
    ASSERT_EQ(e,f); // calls gtest::PrintTo
}

TEST(WrapEigenPrint, t1) {
    typedef Wrapper<Eigen, EigenBase> WrapEigen;
    WrapEigen e(10);
    WrapEigen f(11);
    ASSERT_EQ(e,f); // calls our own.
}

I feel compelled to provide a new answer that I believe is simpler and better than the others, although it is so simple that I may have missed something. 我觉得有必要提供一个我认为比其他人更简单和更好的新答案,尽管它很简单,我可能错过了一些东西。 It's very similar to solutions that you have already tried but it's not quite the same. 它与你已经尝试过的解决方案非常相似,但它们并不完全相同。

Essentially, you don't have to jump through the plugin hoops of modifying the class. 从本质上讲,您不必跳过修改类的插件箍。 The caveat is that, yes, you have to define a PrintTo function for each type ( Matrix2d , Matrix3d , etc); 需要注意的是,您必须为每种类型( Matrix2dMatrix3d等)定义PrintTo函数; a function template won't work. 功能模板不起作用。 But since this is a unit test, I assume that you know what all your types are and so that's not an issue. 但由于这是一个单元测试,我假设你知道你的所有类型是什么,所以这不是问题。

So essentially take your code from the plugin and just put it in the unit test like you were trying to do with the templated SFINAE-enabled one: 所以基本上从插件中获取代码并将其放入单元测试中,就像您尝试使用模板化的SFINAE一样:

namespace Eigen
{
    void PrintTo(const Matrix2d &m, std::ostream *os)
    {
      *os << std::endl << m << std::endl;
    }
}

Nothing fancy. 没有什么花哨。 This works for me and should do what you want according to your test case and question. 这对我有用,应根据您的测试用例和问题做您想做的事。

This answer is based on OP's solution at the end of the question.该答案基于问题末尾 OP 的解决方案。 However, for some reason PrintTo was not working in my case, I had to implement operator<< instead:但是,由于某种原因PrintTo在我的情况下不起作用,我不得不改为实施operator<<

template <class Base>
class EigenPrintWrap : public Base
{
    friend std::ostream &operator<<(std::ostream &os, const EigenPrintWrap &m)
    {
        os << std::endl << static_cast<Base>(m) << std::endl;
        return os;
    }
};

template <class Base>
const EigenPrintWrap<Base> &print_wrap(const Base &base)
{
    return static_cast<const EigenPrintWrap<Base> &>(base);
}

Note that casting to Base is important, otherwise operator<< will recurse infinitely.请注意,转换为Base很重要,否则operator<<将无限递归。

I then use it like this:然后我像这样使用它:

// helper for comparing Eigen types with ASSERT_PRED2
template <typename T>
inline bool is_approx(const T &lhs, const T &rhs)
{
    return lhs.isApprox(rhs, 1e-8);
}

// for more convenient use
#define ASSERT_MATRIX_ALMOST_EQUAL(m1, m2) \
    ASSERT_PRED2(is_approx<Eigen::MatrixXd>, print_wrap(m1), print_wrap(m2))

TEST(Eigen, Matrix) {
    Eigen::Matrix2i A, B;

    A << 1, 2,
         3, 4;
    B = A.transpose();

    ASSERT_MATRIX_ALMOST_EQUAL(A, B);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM