You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
452 lines
13 KiB
C++
452 lines
13 KiB
C++
// ***************************************************************** -*- 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 <exiv2/exiv2.hpp>
|
|
#include <stdint.h>
|
|
#include "slice.hpp"
|
|
#include "types.hpp"
|
|
#include <gtest/gtest.h>
|
|
|
|
using namespace Exiv2;
|
|
|
|
template <typename T>
|
|
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 member 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 <typename T>
|
|
struct return_type_traits
|
|
{
|
|
typedef T type;
|
|
};
|
|
|
|
template <typename U>
|
|
struct return_type_traits<std::vector<U> >
|
|
{
|
|
typedef typename std::vector<U>& type;
|
|
};
|
|
|
|
template <typename U>
|
|
struct return_type_traits<const std::vector<U> >
|
|
{
|
|
typedef const typename std::vector<U>& type;
|
|
};
|
|
|
|
template <typename T>
|
|
typename return_type_traits<T>::type get_test_data(slice<T>& 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 <typename T>
|
|
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<T> getTestSlice(size_t begin = 1, size_t end = vec_size - 1)
|
|
{
|
|
return Slice<T>(cpp_98_boilerplate::get_test_data<T>(*this), begin, end);
|
|
}
|
|
|
|
// TODO: once we have C++11: use initializer list
|
|
std::vector<int> 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<int*>(slice<int*>& st)
|
|
{
|
|
return st.vec_.data();
|
|
}
|
|
|
|
template <>
|
|
const int* get_test_data<const int*>(slice<const int*>& st)
|
|
{
|
|
return st.vec_.data();
|
|
}
|
|
|
|
template <>
|
|
std::vector<int>& get_test_data<std::vector<int> >(slice<std::vector<int> >& st)
|
|
{
|
|
return st.vec_;
|
|
}
|
|
|
|
template <>
|
|
const std::vector<int>& get_test_data<const std::vector<int> >(slice<const std::vector<int> >& 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 <typename T>
|
|
class mutableSlice : public slice<T>
|
|
{
|
|
};
|
|
|
|
TYPED_TEST_CASE_P(slice);
|
|
TYPED_TEST_CASE_P(mutableSlice);
|
|
|
|
TYPED_TEST_P(slice, atAccess)
|
|
{
|
|
// typedef Slice<TypeParam> slice_t;
|
|
// const size_t begin = 1;
|
|
// const size_t end = this->vec_.size() - 1;
|
|
Slice<TypeParam> 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<TypeParam> sl = this->getTestSlice();
|
|
|
|
auto vec_it = this->vec_.begin() + 1;
|
|
for (auto 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<TypeParam> 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<size_t>(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<TypeParam> middle = this->getTestSlice(3, 7).subSlice(1, 3);
|
|
|
|
ASSERT_EQ(middle.size(), static_cast<size_t>(2));
|
|
ASSERT_EQ(middle.at(1), static_cast<typename Slice<TypeParam>::value_type>(5));
|
|
}
|
|
|
|
TYPED_TEST_P(slice, subSliceFailedConstruction)
|
|
{
|
|
// 0 1 2 3 4 5 6 7 8 9
|
|
// | | middle
|
|
Slice<TypeParam> 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<TypeParam> center_vals = this->getTestSlice(3, 7);
|
|
|
|
ASSERT_THROW(center_vals.subSlice(std::numeric_limits<size_t>::max() - 2, 3), std::out_of_range);
|
|
ASSERT_THROW(center_vals.subSlice(2, std::numeric_limits<size_t>::max() - 1), std::out_of_range);
|
|
}
|
|
|
|
/*!
|
|
* This function's purpose is only to check whether we can pass all slices by
|
|
* constant reference.
|
|
*/
|
|
template <typename T>
|
|
void checkConstSliceValueAt(const Slice<T>& sl, typename Slice<T>::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 <typename T>
|
|
void checkConstSliceIterator(const Slice<T>& sl, typename Slice<T>::value_type first_value)
|
|
{
|
|
for (auto it = sl.cbegin(); it < sl.cend(); ++it) {
|
|
ASSERT_EQ(*it, first_value++);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void checkSubSlice(const Slice<T>& 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<TypeParam> 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<TypeParam> slice_t;
|
|
slice_t sl = this->getTestSlice();
|
|
|
|
ASSERT_EQ(*sl.begin(), static_cast<typename slice_t::value_type>(1));
|
|
ASSERT_EQ(*sl.end(), static_cast<typename slice_t::value_type>(this->vec_size - 1));
|
|
|
|
for (auto 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<typename slice_t::value_type>(2 * j));
|
|
ASSERT_EQ(this->vec_.at(j), sl.at(j - 1));
|
|
}
|
|
ASSERT_EQ(this->vec_.at(this->vec_size - 1), static_cast<typename slice_t::value_type>(this->vec_size - 1));
|
|
}
|
|
|
|
/*!
|
|
* Test the non-const version of at()
|
|
*/
|
|
TYPED_TEST_P(mutableSlice, at)
|
|
{
|
|
typedef Slice<TypeParam> 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<typename slice_t::value_type>(j));
|
|
}
|
|
}
|
|
|
|
TEST(pointerSlice, failedConstructionFromNullpointer)
|
|
{
|
|
ASSERT_THROW(Slice<long*>(nullptr, 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<int> tmp(10);
|
|
// slice end too large
|
|
ASSERT_THROW(Slice<std::vector<int> >(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<std::string> is = makeSlice(str, 5, 7);
|
|
ASSERT_TRUE(std::equal(is.begin(), is.end(), "is"));
|
|
|
|
Slice<std::string> sl_this = makeSliceUntil(str, 4);
|
|
ASSERT_TRUE(std::equal(sl_this.begin(), sl_this.end(), "this"));
|
|
|
|
Slice<std::string> sl_sentence = makeSliceFrom(str, 10);
|
|
ASSERT_TRUE(std::equal(sl_sentence.begin(), sl_sentence.end(), "sentence"));
|
|
|
|
Slice<std::string> 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<const std::string> is_a = makeSlice(static_cast<const std::string&>(this->sentence), 5, 10);
|
|
|
|
ASSERT_EQ(is_a.at(0), 'i');
|
|
ASSERT_EQ(is_a.at(4), ' ');
|
|
}
|
|
|
|
TEST_F(stringSlice, atFailure)
|
|
{
|
|
const Slice<const std::string> is_a = makeSlice(static_cast<const std::string&>(this->sentence), 5, 10);
|
|
ASSERT_THROW(is_a.at(5), std::out_of_range);
|
|
}
|
|
|
|
TEST_F(stringSlice, size)
|
|
{
|
|
const Slice<const std::string> is_a = makeSlice(static_cast<const std::string&>(this->sentence), 5, 10);
|
|
ASSERT_EQ(is_a.size(), static_cast<size_t>(5));
|
|
}
|
|
|
|
TEST_F(stringSlice, mutateString)
|
|
{
|
|
Slice<std::string> is_a_mutable = makeSlice(this->sentence, 5, 10);
|
|
|
|
for (Slice<std::string>::iterator it = is_a_mutable.begin(); it < is_a_mutable.end(); ++it) {
|
|
*it = ' ';
|
|
}
|
|
|
|
ASSERT_STREQ(this->sentence.c_str(), "this sentence");
|
|
}
|
|
|
|
template <typename T>
|
|
struct dataBufSlice : public ::testing::Test
|
|
{
|
|
static byte data[4]; // = {0xde, 0xad, 0xbe, 0xef};
|
|
DataBuf buf;
|
|
|
|
void SetUp() override
|
|
{
|
|
buf = DataBuf(data, sizeof(data));
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
byte dataBufSlice<T>::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<TypeParam>(this->buf), 1, 3).size(), static_cast<size_t>(2));
|
|
}
|
|
|
|
TYPED_TEST_P(dataBufSlice, failedConstruction)
|
|
{
|
|
// check that we get an exception when end is larger than LONG_MAX
|
|
ASSERT_THROW(
|
|
makeSlice(static_cast<TypeParam>(this->buf), 1, static_cast<size_t>(std::numeric_limits<long>::max()) + 1),
|
|
std::invalid_argument);
|
|
|
|
// check that we get an exception when end is larger than the DataBuf
|
|
ASSERT_THROW(makeSlice(static_cast<TypeParam>(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<const std::vector<int>, std::vector<int>, int*, const int*> test_types_t;
|
|
INSTANTIATE_TYPED_TEST_CASE_P(slice, slice, test_types_t);
|
|
|
|
REGISTER_TYPED_TEST_CASE_P(mutableSlice, iterators, at);
|
|
typedef ::testing::Types<std::vector<int>, int*> mut_test_types_t;
|
|
INSTANTIATE_TYPED_TEST_CASE_P(slice, mutableSlice, mut_test_types_t);
|
|
|
|
REGISTER_TYPED_TEST_CASE_P(dataBufSlice, successfulConstruction, failedConstruction);
|
|
typedef ::testing::Types<DataBuf&, const DataBuf&> data_buf_types_t;
|
|
INSTANTIATE_TYPED_TEST_CASE_P(slice, dataBufSlice, data_buf_types_t);
|