vectorxf matrixxd c++ templates eigen googletest gmock

c++ - matrixxd - Enseña a Google-Test cómo imprimir Eigen Matrix



vector eigen (3)

Introducción

Estoy escribiendo pruebas en matrices Eigen utilizando el marco de prueba de Google, Google-Mock, como ya se ha comentado en otra pregunta .

Con el siguiente código, pude agregar un Matcher personalizado para hacer coincidir las matrices de Eigen con una precisión dada.

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); }

Lo que esto hace es comparar dos matrices Eigen por su método isApprox , y si no coinciden, Google-Mock imprimirá un mensaje de error correspondiente, que contendrá los valores esperados y los valores reales de las matrices. O, al menos debería ...

El problema

Tome el siguiente caso de prueba simple:

TEST(EigenPrint, Simple) { Eigen::Matrix2d A, B; A << 0., 1., 2., 3.; B << 0., 2., 1., 3.; EXPECT_THAT(A, EigenApproxEqual(B, 1e-7)); }

Esta prueba fallará porque A y B no son iguales. Desafortunadamente, el mensaje de error correspondiente se ve así:

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>

Como puede ver, Google-Test imprime un volcado hexadecimal de las matrices, en lugar de una representación más agradable de sus valores. La Google-documentation dice lo siguiente sobre la impresión de valores de tipos personalizados:

Esta impresora sabe cómo imprimir tipos de C ++ incorporados, matrices nativas, contenedores STL y cualquier tipo que admita el operador << . Para otros tipos, imprime los bytes sin procesar en el valor y espera que el usuario pueda averiguarlo.

La matriz Eigen viene con un operator<< . Sin embargo, Google-Test, o el compilador de C ++, más bien, lo ignora. A mi entender, por la siguiente razón: La firma de este operador lee ( IO.h (línea 240) )

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

Es decir, se necesita una const DenseBase<Derived>& . La impresora predeterminada de Google-test hex-dump, por otro lado, es la implementación predeterminada de una función de plantilla. Puedes encontrar la implementación here . (Siga el árbol de PrintTo que comienza en PrintTo para ver si este es el caso, o demuestre que estoy equivocado.))

Por lo tanto, la impresora predeterminada de Google-Test es una mejor coincidencia porque toma una const Derived & , y no solo su clase base const DenseBase<Derived> & .

Mi pregunta

Mi pregunta es la siguiente. ¿Cómo puedo decirle al compilador que prefiera al operator << específico de Eigen operator << en lugar del volcado de prueba de Google? Suponiendo que no puedo modificar la definición de clase de la matriz Eigen.

Mis intentos

Hasta ahora, he intentado las siguientes cosas.

Definiendo una función

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

no funcionará por la misma razón por la cual el operator<< no funciona.

Lo único que encontré que funcionó es usar el mecanismo de complemento de Eigen.

Con un archivo eigen_matrix_addons.hpp :

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

y lo siguiente incluye directiva

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

La prueba producirá el siguiente resultado:

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

¿Qué está mal con eso?

Para matrices Eigen, esta es probablemente una solución aceptable. Sin embargo, sé que tendré que aplicar lo mismo a otras clases de plantillas, muy pronto, que desafortunadamente, no ofrecen un mecanismo de complemento como el de Eigen, y cuyas definiciones no tengo acceso directo.

Por lo tanto, mi pregunta es: ¿hay una manera de apuntar el compilador al operator<< correcto operator<< , o la función PrintTo , sin modificar la definición de la clase en sí?

El codigo completo

#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)); }

Edición: Más intentos

Hice algún progreso con un enfoque SFINAE.

Primero, definí un rasgo para los tipos Eigen. Con él podemos usar std::enable_if para proporcionar funciones de plantilla solo para los tipos que cumplen con este rasgo.

#include <type_traits> #include <Eigen/Dense> template <class Derived> struct is_eigen : public std::is_base_of<Eigen::DenseBase<Derived>, Derived> { };

Mi primer pensamiento fue proporcionar una versión de PrintTo . Desafortunadamente, el compilador se queja de una ambigüedad entre esta función y el defecto interno de Google-Test. ¿Hay alguna manera de desambiguar y apuntar el compilador a mi función?

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; } }

Otro enfoque es sobrecargar el operator<< para el tipo Eigen. Realmente funciona. Sin embargo, el inconveniente es que es una sobrecarga global del operador ostream. Por lo tanto, es imposible definir cualquier formato específico de prueba (por ejemplo, la nueva línea adicional) sin que este cambio también afecte el código de no prueba. Por lo tanto, preferiría un PrintTo especializado como el de arriba.

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; }

Edición: Siguiendo la respuesta de @Alex

En el siguiente código implemento la solución de @Alex e implemento una pequeña función que convierte las referencias de matrices Eigen al tipo imprimible.

#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))); }


Los problemas que encuentra son problemas de resolución de sobrecarga.

Google Test implementa una función de plantilla.

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

La biblioteca Eigen define una función de impresora que se basa en la derivación. Por lo tanto

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 }

Ambos tienen diseño cuestionable.

Google Test no debe proporcionar una implementación de PrintTo sino que debe verificar en el momento de la compilación si el usuario proporciona un PrintTo y, de lo contrario, debe llamar a una función de impresión predeterminada diferente, PrintToDefault . El PrintTo proporciona una mejor coincidencia que la que proporcionó (según la resolución de sobrecarga).

por otro lado, el operator<< de Eigen operator<< se basa en la derivación y la resolución de sobrecarga también preferirá una función de plantilla.

Eigen podría proporcionar una clase base de CRTP que hereda el operator<< que es un tipo mejor coincidente.

Lo que puede hacer es heredar de eigen y proporcionar una sobrecarga de CRTP a su clase heredada para evitar el problema.

#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 }


Me siento obligado a dar una nueva respuesta que creo que es más simple y mejor que las otras, aunque es tan simple que me he perdido algo. Es muy similar a las soluciones que ya ha probado, pero no es lo mismo.

Esencialmente, no tienes que saltar a través de los aros del plugin para modificar la clase. La advertencia es que, sí, tienes que definir una función PrintTo para cada tipo ( Matrix2d , Matrix3d , etc.); una plantilla de función no funcionará. Pero como se trata de una prueba de unidad, asumo que sabes todos tus tipos y eso no es un problema.

Básicamente, tome su código del complemento y simplemente póngalo en la prueba de la unidad como lo estaba tratando de hacer con la plantilla habilitada con SFINAE:

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

Nada sofisticado. Esto funciona para mí y debería hacer lo que quiera de acuerdo con su caso y pregunta de prueba.


Teniendo en cuenta la respuesta del OP, quiero hacer algunas aclaraciones. A diferencia de la solución derivada del OP, en realidad quería decorar la clase en lugar de usar un envoltorio de función dentro de la aserción.

En aras de la simplicidad, en lugar de utilizar un predicado de coincidencia de prueba de Google, sobrecargué el operator== .

La idea

En lugar de utilizar la propia clase Eigen, usamos un envoltorio que es un reemplazo completo de Eigen. Así que cada vez que creamos una instancia de Eigen , creamos una instancia de WrapEigen .

Porque no intentamos alterar la implementación de la derivación de Eigen está bien.

Además queremos añadir funciones a la envoltura. Hago esto aquí con múltiples herencias de functor como clases llamadas StreamBase y EqualBase . Usamos CRTP en estos funtores para obtener las firmas correctas.

Para guardar la posibilidad de escribir, utilicé un constructor de plantillas variad en Wrapper . Se llama el constructor de base correspondiente si existe.

Ejemplo de trabajo

#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. }