From 85595dc2f9037e504ec4a775c24056ebdb3b4ec6 Mon Sep 17 00:00:00 2001 From: Heiko Strathmann Date: Mon, 22 Jan 2018 12:56:11 +0000 Subject: [PATCH] systematically test CSGObject::equals --- src/shogun/base/SGObject.h | 22 ++- src/shogun/lib/SGString.cpp | 33 +++- src/shogun/lib/SGString.h | 5 +- tests/unit/base/MockObject.h | 149 +++++++++++++++++ tests/unit/base/SGObject_unittest.cc | 229 +++++++++++++++++++++------ 5 files changed, 386 insertions(+), 52 deletions(-) diff --git a/src/shogun/base/SGObject.h b/src/shogun/base/SGObject.h index 0c00283c0eb..2f68b0e51f6 100644 --- a/src/shogun/base/SGObject.h +++ b/src/shogun/base/SGObject.h @@ -500,12 +500,32 @@ class CSGObject */ template void watch_param( - const std::string& name, T* value, AnyParameterProperties properties) + const std::string& name, T* value, + AnyParameterProperties properties = + AnyParameterProperties(MS_NOT_AVAILABLE, GRADIENT_NOT_AVAILABLE)) { BaseTag tag(name); create_parameter(tag, AnyParameter(make_any_ref(value), properties)); } + /** Puts a pointer to some parameter array into the parameter map. + * + * @param name name of the parameter array + * @param value pointer to the first element of the parameter array + * @param properties properties of the parameter (e.g. if model selection is + * supported) + */ + template + void watch_param( + const std::string& name, T** value, S* len, + AnyParameterProperties properties = + AnyParameterProperties(MS_NOT_AVAILABLE, GRADIENT_NOT_AVAILABLE)) + { + BaseTag tag(name); + create_parameter( + tag, AnyParameter(make_any_ref_array(value, len), properties)); + } + public: /** Updates the hash of current parameter combination */ virtual void update_parameter_hash(); diff --git a/src/shogun/lib/SGString.cpp b/src/shogun/lib/SGString.cpp index 80ed9237248..b3d10c4b07f 100644 --- a/src/shogun/lib/SGString.cpp +++ b/src/shogun/lib/SGString.cpp @@ -35,11 +35,34 @@ bool SGString::operator==(const SGString & other) const if (other.slen != slen) return false; - for (int i = 0; i < slen; i++) - { - if (other.string[i] != string[i]) - return false; - } + if (string != other.string) + return false; + + return true; +} + +template +bool SGString::equals(const SGString& other) const +{ + // avoid comparing elements when both are same. + // the case where both matrices are uninitialized is handled here as well. + if (*this == other) + return true; + + // both empty + if (!(slen || other.slen)) + return true; + + // only one empty + if (!string || !other.string) + return false; + + // different size + if (slen != other.slen) + return false; + + // content + return std::equal(string, string + slen, other.string); return true; } diff --git a/src/shogun/lib/SGString.h b/src/shogun/lib/SGString.h index 71427147327..4cf9337b5bc 100644 --- a/src/shogun/lib/SGString.h +++ b/src/shogun/lib/SGString.h @@ -35,9 +35,12 @@ template class SGString /** copy constructor */ SGString(const SGString &orig); - /** equality operator */ + /** @return true iff pointer and size are equal */ bool operator==(const SGString & other) const; + /** @return true iff content is equal */ + bool equals(const SGString& other) const; + /** free string */ void free_string(); diff --git a/tests/unit/base/MockObject.h b/tests/unit/base/MockObject.h index bcdab1f12e1..3f9c9019be2 100644 --- a/tests/unit/base/MockObject.h +++ b/tests/unit/base/MockObject.h @@ -1,8 +1,157 @@ #include +#include +#include +#include +#include +#include +#include #include namespace shogun { + /** @brief Mock class to test clone and equals for CSGObject. + * Serves as a parameter of type CSGObject for @see CCloneEqualsMock. + */ + template + class CCloneEqualsMockParameter : public CSGObject + { + public: + CCloneEqualsMockParameter() + { + m_was_cloned = false; + m_some_value = 1; + + watch_param("some_value", &m_some_value); + } + const char* get_name() const + { + return "CloneEqualsMockParameter"; + } + + virtual CSGObject* clone() + { + auto* clone = new CCloneEqualsMockParameter(); + clone->m_was_cloned = true; + SG_REF(clone); + return clone; + } + + bool m_was_cloned; + T m_some_value; + }; + + /** @brief Mock class to test clone and equals for CSGObject. + * Has members that cover (hopefully) all possibilities of parameters. + */ + template + class CCloneEqualsMock : public CSGObject + { + public: + CCloneEqualsMock() + { + m_basic = 1; + watch_param("basic", &m_basic); + + m_object = new CCloneEqualsMockParameter(); + watch_param("object", &m_object); + + m_sg_vector = SGVector(2); + m_sg_vector.set_const(m_basic); + watch_param("sg_vector", &m_sg_vector); + + m_sg_matrix = SGMatrix(3, 4); + m_sg_matrix.set_const(m_basic); + watch_param("sg_matrix", &m_sg_matrix); + + m_sg_sparse_vector = SGSparseVector(4); + for (auto i : range(m_sg_sparse_vector.num_feat_entries)) + { + SGSparseVectorEntry entry; + entry.feat_index = i * 2; + entry.entry = 2; + m_sg_sparse_vector.features[i] = entry; + } + watch_param("sg_sparse_vector", &m_sg_sparse_vector); + + m_sg_sparse_matrix = + SGSparseMatrix(m_sg_sparse_vector.num_feat_entries, 6); + for (auto i : range(m_sg_sparse_matrix.num_vectors)) + { + SGSparseVector vec(m_sg_sparse_matrix.num_features); + for (auto j : range(vec.num_feat_entries)) + { + SGSparseVectorEntry entry; + entry.feat_index = i * 2; + entry.entry = 3; + vec.features[j] = entry; + } + m_sg_sparse_matrix.sparse_matrix[i] = vec; + } + watch_param("sg_sparse_matrix", &m_sg_sparse_matrix); + + m_vector_basic_len = 5; + m_vector_basic = new T[m_vector_basic_len]; + for (auto i : range(m_vector_basic_len)) + m_vector_basic[i] = m_basic; + watch_param("vector_basic", &m_vector_basic, &m_vector_basic_len); + + m_vector_sg_string_len = 7; + m_vector_sg_string = new SGString[m_vector_sg_string_len]; + for (auto i : range(m_vector_sg_string_len)) + { + m_vector_sg_string[i] = SGString(i + 1, true); + for (auto j : range(m_vector_sg_string[i].slen)) + m_vector_sg_string[i].string[j] = 1; + } + watch_param( + "vector_sg_string", &m_vector_sg_string, + &m_vector_sg_string_len); + + m_vector_object_len = 6; + m_vector_object = + new CCloneEqualsMockParameter*[m_vector_object_len]; + for (auto i : range(m_vector_object_len)) + m_vector_object[i] = new CCloneEqualsMockParameter(); + watch_param( + "vector_object", &m_vector_object, &m_vector_object_len); + } + + ~CCloneEqualsMock() + { + delete m_object; + delete m_vector_basic; + for (auto i : range(m_vector_object_len)) + delete m_vector_object[i]; + delete m_vector_object; + + for (auto i : range(m_vector_sg_string_len)) + m_vector_sg_string[i].free_string(); + delete m_vector_sg_string; + } + + const char* get_name() const + { + return "CloneEqualsMock"; + } + + T m_basic; + CCloneEqualsMockParameter* m_object; + + SGVector m_sg_vector; + SGMatrix m_sg_matrix; + + SGSparseVector m_sg_sparse_vector; + SGSparseMatrix m_sg_sparse_matrix; + + T* m_vector_basic; + index_t m_vector_basic_len; + + SGString* m_vector_sg_string; + index_t m_vector_sg_string_len; + + CCloneEqualsMockParameter** m_vector_object; + index_t m_vector_object_len; + }; /** @brief Used to test the tags-parameter framework * Allows testing of registering new member and avoiding diff --git a/tests/unit/base/SGObject_unittest.cc b/tests/unit/base/SGObject_unittest.cc index c43a9999b39..8a6455f9082 100644 --- a/tests/unit/base/SGObject_unittest.cc +++ b/tests/unit/base/SGObject_unittest.cc @@ -27,76 +27,215 @@ using namespace shogun; -TEST(SGObject, equals_same_instance) +TEST(SGObject, CCloneEqualsMock_allocate_delete) { - auto kernel = some(); - EXPECT_TRUE(kernel->equals(kernel)); + auto obj = some>(); } -TEST(SGObject, equals_null) +template +class SGObjectEquals : public ::testing::Test { - auto kernel = some(); - EXPECT_FALSE(kernel->equals(nullptr)); +}; +// TODO: SGString doesn't support complex128_t, so omitted here +typedef ::testing::Types + CloneEqualsTypes; +TYPED_TEST_CASE(SGObjectEquals, CloneEqualsTypes); + +TYPED_TEST(SGObjectEquals, same) +{ + auto obj1 = some>(); + auto obj2 = some>(); + + EXPECT_TRUE(obj1->equals(obj1)); + EXPECT_TRUE(obj1->equals(obj2)); + EXPECT_TRUE(obj2->equals(obj1)); } -TEST(SGObject, equals_different_type) +TYPED_TEST(SGObjectEquals, different_null) +{ + auto obj1 = some>(); + + EXPECT_FALSE(obj1->equals(nullptr)); +} + +TYPED_TEST(SGObjectEquals, different_basic) +{ + auto obj1 = some>(); + auto obj2 = some>(); + + obj1->m_basic -= 1; + EXPECT_FALSE(obj1->equals(obj2)); + EXPECT_FALSE(obj2->equals(obj1)); +} + +TYPED_TEST(SGObjectEquals, different_object) +{ + auto obj1 = some>(); + auto obj2 = some>(); + + obj1->m_object->m_some_value -= 1; + EXPECT_FALSE(obj1->equals(obj2)); + EXPECT_FALSE(obj2->equals(obj1)); + obj1->m_object->m_some_value += 1; + + delete obj1->m_object; + obj1->m_object = nullptr; + EXPECT_FALSE(obj1->equals(obj2)); + EXPECT_FALSE(obj2->equals(obj1)); +} + +TYPED_TEST(SGObjectEquals, different_sg_vector) +{ + auto obj1 = some>(); + auto obj2 = some>(); + + obj1->m_sg_vector[0] -= 1; + EXPECT_FALSE(obj1->equals(obj2)); + EXPECT_FALSE(obj2->equals(obj1)); +} + +TYPED_TEST(SGObjectEquals, different_sg_sparse_vector) +{ + auto obj1 = some>(); + auto obj2 = some>(); + + obj1->m_sg_sparse_vector.features[0].entry -= 1; + EXPECT_FALSE(obj1->equals(obj2)); + EXPECT_FALSE(obj2->equals(obj1)); +} + +TYPED_TEST(SGObjectEquals, different_sg_sparse_matrix) { - auto kernel = some(); - auto kernel2 = some(); + auto obj1 = some>(); + auto obj2 = some>(); - EXPECT_FALSE(kernel->equals(kernel2)); - EXPECT_FALSE(kernel2->equals(kernel)); + obj1->m_sg_sparse_matrix.sparse_matrix[0].features[0].entry -= 1; + EXPECT_FALSE(obj1->equals(obj2)); + EXPECT_FALSE(obj2->equals(obj1)); } -TEST(SGObject, equals_basic_member) +TYPED_TEST(SGObjectEquals, different_sg_matrix) { - auto kernel = some(1); - auto kernel2 = some(1); - EXPECT_TRUE(kernel->equals(kernel2)); - EXPECT_TRUE(kernel2->equals(kernel)); + auto obj1 = some>(); + auto obj2 = some>(); - kernel->set_width(2); - EXPECT_FALSE(kernel->equals(kernel2)); - EXPECT_FALSE(kernel2->equals(kernel)); + obj1->m_sg_matrix(0, 0) = 0; + EXPECT_FALSE(obj1->equals(obj2)); + EXPECT_FALSE(obj2->equals(obj1)); } -TEST(SGObject, equals_object_member) +TYPED_TEST(SGObjectEquals, different_vector_basic) { - SGMatrix data(3, 10); - SGMatrix data2(3, 10); - auto feats = some>(data); - auto feats2 = some>(data2); + auto obj1 = some>(); + auto obj2 = some>(); - auto kernel = some(); - auto kernel2 = some(); + obj1->m_vector_basic[0] -= 1; + EXPECT_FALSE(obj1->equals(obj2)); + EXPECT_FALSE(obj2->equals(obj1)); +} + +TYPED_TEST(SGObjectEquals, different_vector_sg_string) +{ + auto obj1 = some>(); + auto obj2 = some>(); + + obj1->m_vector_sg_string[0].string[0] -= 1; + EXPECT_FALSE(obj1->equals(obj2)); + EXPECT_FALSE(obj2->equals(obj1)); +} + +TYPED_TEST(SGObjectEquals, different_vector_object) +{ + auto obj1 = some>(); + auto obj2 = some>(); + + obj1->m_vector_object[0]->m_some_value -= 1; + EXPECT_FALSE(obj1->equals(obj2)); + EXPECT_FALSE(obj2->equals(obj1)); + obj1->m_vector_object[0]->m_some_value += 1; + + delete obj1->m_vector_object[0]; + obj1->m_vector_object[0] = nullptr; + EXPECT_FALSE(obj1->equals(obj2)); + EXPECT_FALSE(obj2->equals(obj1)); +} + +TEST(SGObject, equals_different_type) +{ + auto obj1 = some>(); + auto obj2 = some>(); - kernel->init(feats, feats); - kernel2->init(feats2, feats2); + EXPECT_FALSE(obj1->equals(obj2)); + EXPECT_FALSE(obj2->equals(obj1)); +} - EXPECT_TRUE(kernel->equals(kernel2)); - EXPECT_TRUE(kernel2->equals(kernel)); +TEST(SGObject, clone_not_null_and_correct_type) +{ + auto object = some(); + auto clone = object->clone(); + ASSERT_NE(clone, nullptr); + ASSERT_TRUE(!strcmp(object->get_name(), clone->get_name())); +} - data(1, 1) = 1; - EXPECT_FALSE(kernel->equals(kernel2)); - EXPECT_FALSE(kernel2->equals(kernel)); +TEST(SGObject, clone_basic_parameter) +{ + auto object = some(); + object->put("log_width", 2.0); + ASSERT_EQ(object->get("log_width"), 2); + + auto clone = object->clone(); + EXPECT_EQ( + object->get("log_width"), + clone->get("log_width")); } -TEST(SGObject, equals_other_has_NULL_parameter) +TEST(SGObject, clone_sgmatrix_parameter) { - SGMatrix data(3,10); - for (index_t i=0; i data(10, 10); + for (auto i : range(data.num_rows * data.num_cols)) + data.matrix[i] = CMath::randn_double(); - CDenseFeatures* feats=new CDenseFeatures(data); - CGaussianKernel* kernel=new CGaussianKernel(); - CGaussianKernel* kernel2=new CGaussianKernel(); - kernel2->init(feats, feats); + auto object = some>(data); + ASSERT_TRUE(data.equals(object->get_feature_matrix())); - EXPECT_FALSE(kernel->equals(kernel2)); - EXPECT_FALSE(kernel2->equals(kernel)); + auto clone = object->clone(); + EXPECT_NE( + data.data(), clone->get>("feature_matrix").data()); + ASSERT_TRUE(data.equals(clone->get>("feature_matrix"))); +} - SG_UNREF(kernel); - SG_UNREF(kernel2); +TEST(SGObject, clone_sgvector_parameter) +{ + SGVector data(10); + for (auto i : range(data.vlen)) + data.vector[i] = CMath::randn_double(); + + auto object = some(data); + ASSERT_TRUE(data.equals(object->get_labels())); + + auto clone = object->clone(); + EXPECT_NE(data.data(), clone->get>("labels").data()); + EXPECT_TRUE(data.equals(clone->get>("labels"))); +} + +TEST(SGObject, clone_sgobject_parameter) +{ + SGMatrix data(10, 10); + for (auto i : range(data.num_rows * data.num_cols)) + data.matrix[i] = CMath::randn_double(); + + auto object_parameter = some>(data); + auto object = some(); + object->init(object_parameter, object_parameter); + ASSERT_TRUE(object_parameter->equals(object->get("lhs"))); + ASSERT_TRUE(object_parameter->equals(object->get("rhs"))); + + auto clone = object->clone(); + EXPECT_NE(object_parameter, clone->get("lhs")); + EXPECT_NE(object_parameter, clone->get("rhs")); + EXPECT_TRUE(object_parameter->equals(clone->get("lhs"))); + EXPECT_TRUE(object_parameter->equals(clone->get("rhs"))); } TEST(SGObject,DISABLED_ref_copy_constructor)