// ***************************************************************** -*- C++ -*- /* * Copyright (C) 2004-2021 Exiv2 authors * This program is part of the Exiv2 distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. */ #include #include #include "slice.hpp" #include "types.hpp" #include using namespace Exiv2; template class slice; /*! * This namespace contains the helper-function get_test_data. It is intented * to be used for test with the slice fixture: it returns the appropriate * data to the constructor of slice. For (const) T==std::vector it returns the * fixtures meber vec_, for (const) T==int* it returns vec_.data() * * Due to C++98's limitations, this requires a separate traits class, that * specifies the return type *and* a specialization of get_test_data for each * case (maybe some can be reduced with SFINAE, but that ain't improving * readability either). * * Unfortunately, C++11 will probably only make the return_type_traits go away, * but not the template specializations of get_test_data (for that we need * C++17, so see you in 2025). */ namespace cpp_98_boilerplate { template struct return_type_traits { typedef T type; }; template struct return_type_traits > { typedef typename std::vector& type; }; template struct return_type_traits > { typedef const typename std::vector& type; }; template typename return_type_traits::type get_test_data(slice& st); } // namespace cpp_98_boilerplate /*! * Fixture for slice testing. Has one public vector of ints with size vec_size * that is filled with the numbers from 0 to vec_size - 1. * * The vector vec_ is used to construct slices either from a std::vector, or * from raw C-arrays. Which type is used, is set by the template parameter * T. Thus we guarantee, that the interface is completely independent of the * underlying datatype. * * @tparam T Type that is used to construct a slice for testing. */ template class slice : public ::testing::Test { public: static const size_t vec_size = 10; void SetUp() override { vec_.reserve(vec_size); for (unsigned int i = 0; i < vec_size; ++i) { vec_.push_back(i); } } Slice getTestSlice(size_t begin = 1, size_t end = vec_size - 1) { return Slice(cpp_98_boilerplate::get_test_data(*this), begin, end); } // TODO: once we have C++11: use initializer list std::vector vec_; }; // specializations of get_test_data are provided here, since they must have the // full definition of slice available namespace cpp_98_boilerplate { template <> int* get_test_data(slice& st) { return st.vec_.data(); } template <> const int* get_test_data(slice& st) { return st.vec_.data(); } template <> std::vector& get_test_data >(slice >& st) { return st.vec_; } template <> const std::vector& get_test_data >(slice >& st) { return st.vec_; } } // namespace cpp_98_boilerplate /*! * Fixture to run test for mutable slices. * * It adds nothing new, it is just a separate class, so that we can run * different tests on it. */ template class mutableSlice : public slice { }; TYPED_TEST_CASE_P(slice); TYPED_TEST_CASE_P(mutableSlice); TYPED_TEST_P(slice, atAccess) { // typedef Slice slice_t; // const size_t begin = 1; // const size_t end = this->vec_.size() - 1; Slice sl = this->getTestSlice(); ASSERT_EQ(this->vec_.size() - 2, sl.size()); for (unsigned int i = 0; i < sl.size(); ++i) { ASSERT_EQ(this->vec_.at(i + 1), sl.at(i)); } } // TODO C++11: test range based for loop TYPED_TEST_P(slice, iteratorAccess) { Slice sl = this->getTestSlice(); std::vector::const_iterator vec_it = this->vec_.begin() + 1; for (typename Slice::const_iterator it = sl.cbegin(); it < sl.cend(); ++it, ++vec_it) { ASSERT_EQ(*it, *vec_it); } ASSERT_THROW(sl.at(sl.size()), std::out_of_range); } TYPED_TEST_P(slice, constructionFailsFromInvalidRange) { // start > end ASSERT_THROW(this->getTestSlice(2, 1), std::out_of_range); } TYPED_TEST_P(slice, constructionFailsWithZeroLength) { ASSERT_THROW(this->getTestSlice(1, 1), std::out_of_range); } /*! * Test the construction of subSlices and their behavior. */ TYPED_TEST_P(slice, subSliceSuccessfulConstruction) { typedef Slice slice_t; // 0 1 2 3 4 5 6 7 8 9 // | | center_vals // | | middle slice_t center_vals = this->getTestSlice(3, 7); ASSERT_EQ(center_vals.size(), static_cast(4)); ASSERT_NO_THROW(center_vals.subSlice(1, 3)); ASSERT_NO_THROW(center_vals.subSlice(1, center_vals.size())); } TYPED_TEST_P(slice, subSliceFunctions) { Slice middle = this->getTestSlice(3, 7).subSlice(1, 3); ASSERT_EQ(middle.size(), static_cast(2)); ASSERT_EQ(middle.at(1), static_cast::value_type>(5)); } TYPED_TEST_P(slice, subSliceFailedConstruction) { // 0 1 2 3 4 5 6 7 8 9 // | | middle Slice middle = this->getTestSlice(4, 6); ASSERT_THROW(middle.subSlice(1, 5), std::out_of_range); ASSERT_THROW(middle.subSlice(2, 1), std::out_of_range); ASSERT_THROW(middle.subSlice(2, 2), std::out_of_range); } /*! try to cause integer overflows in a sub-optimal implementation */ TYPED_TEST_P(slice, subSliceConstructionOverflowResistance) { Slice center_vals = this->getTestSlice(3, 7); ASSERT_THROW(center_vals.subSlice(std::numeric_limits::max() - 2, 3), std::out_of_range); ASSERT_THROW(center_vals.subSlice(2, std::numeric_limits::max() - 1), std::out_of_range); } /*! * This function's purpose is only to check whether we can pass all slices by * constant reference. */ template void checkConstSliceValueAt(const Slice& sl, typename Slice::value_type value, size_t index) { ASSERT_EQ(sl.at(index), value); } /*! * Check that the contents of the slice are ascending via an iterator based for * loop. */ template void checkConstSliceIterator(const Slice& sl, typename Slice::value_type first_value) { for (typename Slice::const_iterator it = sl.cbegin(); it < sl.cend(); ++it) { ASSERT_EQ(*it, first_value++); } } template void checkSubSlice(const Slice& sl) { ASSERT_EQ(sl.at(1), sl.subSlice(1, sl.size()).at(0)); } /*! * Test that all slices can be also passed as const references and still work */ TYPED_TEST_P(slice, constMethodsPreserveConst) { typedef Slice slice_t; // 0 1 2 3 4 5 6 7 8 9 // | | center_vals slice_t center_vals = this->getTestSlice(3, 7); // check at() const works checkConstSliceValueAt(center_vals, 4, 1); checkConstSliceIterator(center_vals, 3); checkSubSlice(center_vals); } /*! * Test the non-const iterators */ TYPED_TEST_P(mutableSlice, iterators) { typedef Slice slice_t; slice_t sl = this->getTestSlice(); ASSERT_EQ(*sl.begin(), static_cast(1)); ASSERT_EQ(*sl.end(), static_cast(this->vec_size - 1)); for (typename slice_t::iterator it = sl.begin(); it < sl.end(); ++it) { *it = 2 * (*it); } ASSERT_EQ(this->vec_.at(0), 0); for (size_t j = 1; j < this->vec_size - 1; ++j) { ASSERT_EQ(this->vec_.at(j), static_cast(2 * j)); ASSERT_EQ(this->vec_.at(j), sl.at(j - 1)); } ASSERT_EQ(this->vec_.at(this->vec_size - 1), static_cast(this->vec_size - 1)); } /*! * Test the non-const version of at() */ TYPED_TEST_P(mutableSlice, at) { typedef Slice slice_t; slice_t sl = this->getTestSlice(2, 4); sl.at(0) = 6; sl.at(1) = 12; ASSERT_EQ(this->vec_.at(2), 6); ASSERT_EQ(this->vec_.at(3), 12); for (size_t j = 0; j < this->vec_size - 1; ++j) { if (j == 2 || j == 3) { continue; } ASSERT_EQ(this->vec_.at(j), static_cast(j)); } } TEST(pointerSlice, failedConstructionFromNullpointer) { ASSERT_THROW(Slice(NULL, 1, 2), std::invalid_argument); } /*! * Test the construction of an invalid slices from a container (so that a proper * range check can be conducted) */ TEST(containerSlice, failedConstructionFromContainer) { std::vector tmp(10); // slice end too large ASSERT_THROW(Slice >(tmp, 1, tmp.size() + 1), std::out_of_range); } /*! * Test all functions from the makeSlice* family. */ TEST(containerSlice, makeSlice) { std::string str = "this is a sentence"; Slice is = makeSlice(str, 5, 7); ASSERT_TRUE(std::equal(is.begin(), is.end(), "is")); Slice sl_this = makeSliceUntil(str, 4); ASSERT_TRUE(std::equal(sl_this.begin(), sl_this.end(), "this")); Slice sl_sentence = makeSliceFrom(str, 10); ASSERT_TRUE(std::equal(sl_sentence.begin(), sl_sentence.end(), "sentence")); Slice sl_full = makeSlice(str); ASSERT_TRUE(std::equal(sl_full.begin(), sl_full.end(), str.c_str())); } struct stringSlice : public ::testing::Test { std::string sentence; void SetUp() override { sentence = "this is a sentence"; } }; TEST_F(stringSlice, at) { const Slice is_a = makeSlice(static_cast(this->sentence), 5, 10); ASSERT_EQ(is_a.at(0), 'i'); ASSERT_EQ(is_a.at(4), ' '); } TEST_F(stringSlice, atFailure) { const Slice is_a = makeSlice(static_cast(this->sentence), 5, 10); ASSERT_THROW(is_a.at(5), std::out_of_range); } TEST_F(stringSlice, size) { const Slice is_a = makeSlice(static_cast(this->sentence), 5, 10); ASSERT_EQ(is_a.size(), static_cast(5)); } TEST_F(stringSlice, mutateString) { Slice is_a_mutable = makeSlice(this->sentence, 5, 10); for (Slice::iterator it = is_a_mutable.begin(); it < is_a_mutable.end(); ++it) { *it = ' '; } ASSERT_STREQ(this->sentence.c_str(), "this sentence"); } template struct dataBufSlice : public ::testing::Test { static byte data[4]; // = {0xde, 0xad, 0xbe, 0xef}; DataBuf buf; void SetUp() override { buf = DataBuf(data, sizeof(data)); } }; template byte dataBufSlice::data[4] = {0xde, 0xad, 0xbe, 0xef}; TYPED_TEST_CASE_P(dataBufSlice); TYPED_TEST_P(dataBufSlice, successfulConstruction) { // just check that makeSlice appears to work ASSERT_EQ(makeSlice(static_cast(this->buf), 1, 3).size(), static_cast(2)); } TYPED_TEST_P(dataBufSlice, failedConstruction) { // check that we get an exception when end is larger than LONG_MAX ASSERT_THROW( makeSlice(static_cast(this->buf), 1, static_cast(std::numeric_limits::max()) + 1), std::invalid_argument); // check that we get an exception when end is larger than the DataBuf ASSERT_THROW(makeSlice(static_cast(this->buf), 1, 5), std::out_of_range); } // // GTest boilerplate to get the tests running for all the different types // REGISTER_TYPED_TEST_CASE_P(slice, atAccess, iteratorAccess, constructionFailsFromInvalidRange, constructionFailsWithZeroLength, subSliceSuccessfulConstruction, subSliceFunctions, subSliceFailedConstruction, subSliceConstructionOverflowResistance, constMethodsPreserveConst); typedef ::testing::Types, std::vector, int*, const int*> test_types_t; INSTANTIATE_TYPED_TEST_CASE_P(, slice, test_types_t); REGISTER_TYPED_TEST_CASE_P(mutableSlice, iterators, at); typedef ::testing::Types, int*> mut_test_types_t; INSTANTIATE_TYPED_TEST_CASE_P(, mutableSlice, mut_test_types_t); REGISTER_TYPED_TEST_CASE_P(dataBufSlice, successfulConstruction, failedConstruction); typedef ::testing::Types data_buf_types_t; INSTANTIATE_TYPED_TEST_CASE_P(, dataBufSlice, data_buf_types_t);