309 lines
9.4 KiB
C++
309 lines
9.4 KiB
C++
/*******************************************************************\
|
|
|
|
Module: Unit tests for range
|
|
|
|
Author: Romain Brenguier, romain.brenguier@diffblue.com
|
|
|
|
\*******************************************************************/
|
|
|
|
#include <vector>
|
|
|
|
#include <testing-utils/use_catch.h>
|
|
#include <util/range.h>
|
|
|
|
/// Trivial example template function requiring a container to have a
|
|
/// `value_type`.
|
|
template <typename containert>
|
|
typename containert::value_type front(containert container)
|
|
{
|
|
return *container.begin();
|
|
}
|
|
|
|
SCENARIO("range tests", "[core][util][range]")
|
|
{
|
|
GIVEN("A vector with three strings")
|
|
{
|
|
std::vector<std::string> list;
|
|
list.emplace_back("abc");
|
|
list.emplace_back("cdef");
|
|
list.emplace_back("acdef");
|
|
THEN("Use range-for to compute the total length")
|
|
{
|
|
const auto range = make_range(list);
|
|
std::size_t total_length = 0;
|
|
for(const auto &s : range)
|
|
total_length += s.length();
|
|
REQUIRE(total_length == 12);
|
|
}
|
|
THEN("Use map to compute individual lengths")
|
|
{
|
|
const auto length_range =
|
|
make_range(list).map([](const std::string &s) { return s.length(); });
|
|
auto it = length_range.begin();
|
|
REQUIRE(*it == 3);
|
|
++it;
|
|
REQUIRE(*it == 4);
|
|
++it;
|
|
REQUIRE(*it == 5);
|
|
++it;
|
|
REQUIRE(it == length_range.end());
|
|
}
|
|
THEN("Filter using lengths")
|
|
{
|
|
const auto filtered_range = make_range(list).filter(
|
|
[&](const std::string &s) { return s.length() == 4; });
|
|
auto it = filtered_range.begin();
|
|
REQUIRE(*it == "cdef");
|
|
++it;
|
|
REQUIRE(it == filtered_range.end());
|
|
}
|
|
|
|
THEN("Drop first 2 elements")
|
|
{
|
|
auto range = make_range(list);
|
|
auto drop_range = range.drop(2);
|
|
auto it = drop_range.begin();
|
|
REQUIRE(*it == "acdef");
|
|
drop_range = std::move(drop_range).drop(1);
|
|
REQUIRE(drop_range.empty());
|
|
// Check the original is unmodified
|
|
REQUIRE(!range.empty());
|
|
REQUIRE(*range.begin() == "abc");
|
|
}
|
|
THEN("Drop first 5 elements")
|
|
{
|
|
auto range = make_range(list);
|
|
auto skip_range = range.drop(5);
|
|
REQUIRE(skip_range.empty());
|
|
// Check the original is unmodified
|
|
REQUIRE(!range.empty());
|
|
REQUIRE(*range.begin() == "abc");
|
|
}
|
|
THEN("Drop first 2 elements, move version")
|
|
{
|
|
auto range = make_range(list);
|
|
range = std::move(range).drop(2);
|
|
REQUIRE(!range.empty());
|
|
auto it = range.begin();
|
|
REQUIRE(*it == "acdef");
|
|
range = std::move(range).drop(1);
|
|
REQUIRE(range.empty());
|
|
}
|
|
THEN("Drop first 5 elements, move version")
|
|
{
|
|
auto range = make_range(list);
|
|
range = std::move(range).drop(5);
|
|
REQUIRE(range.empty());
|
|
}
|
|
|
|
THEN(
|
|
"A const instance of a `filter_iteratort` can mutate the input "
|
|
"collection.")
|
|
{
|
|
const auto it =
|
|
make_range(list)
|
|
.filter([&](const std::string &s) { return s.length() == 3; })
|
|
.begin();
|
|
*it += "x";
|
|
REQUIRE(*list.begin() == "abcx");
|
|
}
|
|
THEN("Filter, map and use range-for on the same list")
|
|
{
|
|
const auto range =
|
|
make_range(list)
|
|
.filter([&](const std::string &s) -> bool { return s[0] == 'a'; })
|
|
.map([&](const std::string &s) { return s.length(); });
|
|
// Note that everything is performed on the fly, so none of the filter
|
|
// and map functions have been computed yet, and no intermediary container
|
|
// is created.
|
|
std::size_t total = 0;
|
|
for(const auto &l : range)
|
|
total += l;
|
|
REQUIRE(total == 8);
|
|
}
|
|
}
|
|
GIVEN("A const vector of ints")
|
|
{
|
|
const std::vector<int> input{1, 2, 3, 4};
|
|
THEN("Filter the vector using range.")
|
|
{
|
|
const auto odds_range =
|
|
make_range(input).filter([](const int number) { return number % 2; });
|
|
const std::vector<int> odds{odds_range.begin(), odds_range.end()};
|
|
const std::vector<int> expected_odds{1, 3};
|
|
REQUIRE(odds == expected_odds);
|
|
}
|
|
THEN(
|
|
"The unit testing template function requiring `value_type` works with "
|
|
"`std::vector`.")
|
|
{
|
|
REQUIRE(front(input) == 1);
|
|
}
|
|
THEN(
|
|
"A range can be used with a template function expecting a container "
|
|
"which has a `value_type`.")
|
|
{
|
|
REQUIRE(front(make_range(input)) == 1);
|
|
}
|
|
THEN("Map over the vector using range.")
|
|
{
|
|
const auto plus_one_range =
|
|
make_range(input).map([](const int number) { return number + 1; });
|
|
const std::vector<int> plus_one_collection{plus_one_range.begin(),
|
|
plus_one_range.end()};
|
|
const std::vector<int> expected_output{2, 3, 4, 5};
|
|
REQUIRE(plus_one_collection == expected_output);
|
|
};
|
|
}
|
|
GIVEN("Two const vectors of ints")
|
|
{
|
|
const std::vector<int> input1{1, 2};
|
|
const std::vector<int> input2{3, 4};
|
|
THEN("Concat the vectors using range.")
|
|
{
|
|
const auto range = make_range(input1).concat(make_range(input2));
|
|
const std::vector<int> output{range.begin(), range.end()};
|
|
const std::vector<int> expected{1, 2, 3, 4};
|
|
REQUIRE(output == expected);
|
|
};
|
|
}
|
|
GIVEN("Two non-const vectors of ints.")
|
|
{
|
|
std::vector<int> input1{1, 2};
|
|
std::vector<int> input2{3, 4};
|
|
THEN(
|
|
"Const instances of `concat_iteratort` should enable the input "
|
|
"collections to be mutated.")
|
|
{
|
|
const auto concat_range = make_range(input1).concat(make_range(input2));
|
|
int x = 5;
|
|
for(auto it = concat_range.begin(); it != concat_range.end(); ++it, ++x)
|
|
{
|
|
const auto const_it = it;
|
|
*const_it = x;
|
|
}
|
|
std::vector<int> expected_result1{5, 6};
|
|
std::vector<int> expected_result2{7, 8};
|
|
REQUIRE(input1 == expected_result1);
|
|
REQUIRE(input2 == expected_result2);
|
|
}
|
|
}
|
|
}
|
|
|
|
class move_onlyt
|
|
{
|
|
public:
|
|
move_onlyt(move_onlyt &&) = default;
|
|
move_onlyt &operator=(move_onlyt &&) = default;
|
|
move_onlyt(const move_onlyt &) = delete;
|
|
move_onlyt &operator=(const move_onlyt &) = delete;
|
|
|
|
explicit move_onlyt(int value) : value{value} {};
|
|
int value = 0;
|
|
};
|
|
|
|
bool is_odd(const move_onlyt &move_only)
|
|
{
|
|
return move_only.value % 2 != 0;
|
|
}
|
|
|
|
const auto add = [](int left) {
|
|
return [=](const move_onlyt &right) { return left + right.value; };
|
|
};
|
|
|
|
SCENARIO(
|
|
"Range tests, with collections of move only typed values.",
|
|
"[core][util][range]")
|
|
{
|
|
GIVEN("A vector of move only typed values.")
|
|
{
|
|
std::vector<move_onlyt> input;
|
|
for(int i = 1; i <= 10; ++i)
|
|
input.emplace_back(i);
|
|
THEN("Values from a range of made from the vector can be moved.")
|
|
{
|
|
const auto input_range = make_range(input);
|
|
move_onlyt destination{std::move(*input_range.begin())};
|
|
REQUIRE(destination.value == 1);
|
|
}
|
|
THEN("A range of made from the vector can be filtered.")
|
|
{
|
|
const auto odds_filter = make_range(input).filter(is_odd);
|
|
const std::size_t total =
|
|
std::distance(odds_filter.begin(), odds_filter.end());
|
|
REQUIRE(total == 5);
|
|
auto iterator = odds_filter.begin();
|
|
REQUIRE((iterator++)->value == 1);
|
|
REQUIRE((iterator++)->value == 3);
|
|
REQUIRE((iterator++)->value == 5);
|
|
REQUIRE((iterator++)->value == 7);
|
|
REQUIRE((iterator++)->value == 9);
|
|
}
|
|
THEN("Values from a filtered range made from the vector can be moved.")
|
|
{
|
|
std::vector<move_onlyt> odds;
|
|
for(move_onlyt &odd : make_range(input).filter(is_odd))
|
|
odds.emplace_back(std::move(odd));
|
|
|
|
REQUIRE(odds.size() == 5);
|
|
REQUIRE(odds[0].value == 1);
|
|
REQUIRE(odds[1].value == 3);
|
|
REQUIRE(odds[2].value == 5);
|
|
REQUIRE(odds[3].value == 7);
|
|
REQUIRE(odds[4].value == 9);
|
|
}
|
|
THEN("Map can be applied to a range of move only typed values.")
|
|
{
|
|
std::vector<int> results;
|
|
for(int result : make_range(input).map(add(1)))
|
|
results.push_back(result);
|
|
|
|
const std::vector<int> expected_results{2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
|
|
REQUIRE(results == expected_results);
|
|
}
|
|
}
|
|
GIVEN("Two vectors containing move only types values.")
|
|
{
|
|
std::vector<move_onlyt> input1;
|
|
for(int i = 1; i <= 3; ++i)
|
|
input1.emplace_back(i);
|
|
std::vector<move_onlyt> input2;
|
|
for(int i = 7; i <= 9; ++i)
|
|
input2.emplace_back(i);
|
|
|
|
THEN("Values from concatenated ranges made from the vector can be moved.")
|
|
{
|
|
std::vector<move_onlyt> both_inputs;
|
|
for(move_onlyt &input : make_range(input1).concat(make_range(input2)))
|
|
both_inputs.emplace_back(std::move(input));
|
|
|
|
REQUIRE(both_inputs.size() == 6);
|
|
REQUIRE(both_inputs[0].value == 1);
|
|
REQUIRE(both_inputs[1].value == 2);
|
|
REQUIRE(both_inputs[2].value == 3);
|
|
REQUIRE(both_inputs[3].value == 7);
|
|
REQUIRE(both_inputs[4].value == 8);
|
|
REQUIRE(both_inputs[5].value == 9);
|
|
}
|
|
}
|
|
GIVEN("A const vector of ints.")
|
|
{
|
|
const std::vector<int> input{1, 2, 3, 4, 5};
|
|
|
|
THEN("The vector can be mapped into a range of move-only types")
|
|
{
|
|
std::vector<move_onlyt> results;
|
|
const auto make_move_only = [](int i) { return move_onlyt{i}; };
|
|
for(auto &incremented : make_range(input).map(make_move_only))
|
|
results.emplace_back(std::move(incremented));
|
|
|
|
REQUIRE(results.size() == 5);
|
|
REQUIRE(results[0].value == 1);
|
|
REQUIRE(results[1].value == 2);
|
|
REQUIRE(results[2].value == 3);
|
|
REQUIRE(results[3].value == 4);
|
|
REQUIRE(results[4].value == 5);
|
|
}
|
|
}
|
|
}
|