Custom type with Combine(). Fix for #3781

This commit is contained in:
Baruch Burstein 2022-07-31 22:50:04 +03:00
parent dd7a9d29a3
commit 3280a930bf
5 changed files with 180 additions and 10 deletions

View File

@ -109,6 +109,7 @@ namespace:
| `ValuesIn(container)` or `ValuesIn(begin,end)` | Yields values from a C-style array, an STL-style container, or an iterator range `[begin, end)`. |
| `Bool()` | Yields sequence `{false, true}`. |
| `Combine(g1, g2, ..., gN)` | Yields as `std::tuple` *n*-tuples all combinations (Cartesian product) of the values generated by the given *n* generators `g1`, `g2`, ..., `gN`. |
| `ConvertGenerator<T>(g)` | Yields values generated by generator `g`, `static_cast` to `T`. |
The optional last argument *`name_generator`* is a function or functor that
generates custom test name suffixes based on the test parameters. The function

View File

@ -407,6 +407,46 @@ internal::CartesianProductHolder<Generator...> Combine(const Generator&... g) {
return internal::CartesianProductHolder<Generator...>(g...);
}
// ConvertGenerator() wraps a parameter generator in order to cast each prduced
// value through a known type before supplying it to the test suite
//
// Synopsis:
// ConvertGenerator<T>(gen)
// - returns a generator producing the same elements as generated by gen, but
// each element is static_cast to type T before being returned
//
// It is useful when using the Combine() function to get the generated
// parameters in a custom type instead of std::tuple
//
// Example:
//
// This will instantiate tests in test suite AnimalTest each one with
// the parameter values tuple("cat", BLACK), tuple("cat", WHITE),
// tuple("dog", BLACK), and tuple("dog", WHITE):
//
// enum Color { BLACK, GRAY, WHITE };
// struct ParamType {
// using TupleT = std::tuple<const char*, Color>;
// std::string animal;
// Color color;
// ParamType(TupleT t) : animal(std::get<0>(t)), color(std::get<1>(t)) {}
// };
// class AnimalTest
// : public testing::TestWithParam<ParamType> {...};
//
// TEST_P(AnimalTest, AnimalLooksNice) {...}
//
// INSTANTIATE_TEST_SUITE_P(AnimalVariations, AnimalTest,
// ConvertGenerator<ParamType::TupleT>(
// Combine(Values("cat", "dog"),
// Values(BLACK, WHITE))));
//
template <typename T>
internal::ParamConverterGenerator<T> ConvertGenerator(
internal::ParamGenerator<T> gen) {
return internal::ParamConverterGenerator<T>(gen);
}
#define TEST_P(test_suite_name, test_name) \
class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
: public test_suite_name { \

View File

@ -1625,7 +1625,7 @@ class GTEST_API_ AssertHelper {
// the GetParam() method.
//
// Use it with one of the parameter generator defining functions, like Range(),
// Values(), ValuesIn(), Bool(), and Combine().
// Values(), ValuesIn(), Bool(), Combine(), and ConvertGenerator<T>().
//
// class FooTest : public ::testing::TestWithParam<int> {
// protected:

View File

@ -950,6 +950,77 @@ class CartesianProductHolder {
std::tuple<Gen...> generators_;
};
template <typename From, typename To>
class ParamGeneratorConverter : public ParamGeneratorInterface<To> {
public:
ParamGeneratorConverter(ParamGenerator<From> gen)
: generator_(std::move(gen)) {}
ParamIteratorInterface<To>* Begin() const override {
return new Iterator(this, generator_.begin(), generator_.end());
}
ParamIteratorInterface<To>* End() const override {
return new Iterator(this, generator_.end(), generator_.end());
}
private:
class Iterator : public ParamIteratorInterface<To> {
public:
Iterator(const ParamGeneratorInterface<To>* base, ParamIterator<From> it,
ParamIterator<From> end)
: base_(base), it_(it), end_(end) {
if (it_ != end_) value_ = std::make_shared<To>(static_cast<To>(*it_));
}
~Iterator() override {}
const ParamGeneratorInterface<To>* BaseGenerator() const override {
return base_;
}
void Advance() override {
++it_;
if (it_ != end_) value_ = std::make_shared<To>(static_cast<To>(*it_));
}
ParamIteratorInterface<To>* Clone() const override {
return new Iterator(*this);
}
const To* Current() const override { return value_.get(); }
bool Equals(const ParamIteratorInterface<To>& other) const override {
// Having the same base generator guarantees that the other
// iterator is of the same type and we can downcast.
GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
<< "The program attempted to compare iterators "
<< "from different generators." << std::endl;
const ParamIterator<From> other_it =
CheckedDowncastToActualType<const Iterator>(&other)->it_;
return it_ == other_it;
}
private:
Iterator(const Iterator& other) = default;
const ParamGeneratorInterface<To>* const base_;
ParamIterator<From> it_;
ParamIterator<From> end_;
std::shared_ptr<To> value_;
}; // class ParamGeneratorConverter::Iterator
ParamGenerator<From> generator_;
}; // class ParamGeneratorConverter
template <class Gen>
class ParamConverterGenerator {
public:
ParamConverterGenerator(ParamGenerator<Gen> g) : generator(g) {}
template <typename T>
operator ParamGenerator<T>() const {
return ParamGenerator<T>(new ParamGeneratorConverter<Gen, T>(generator));
}
private:
ParamGenerator<Gen> generator;
};
} // namespace internal
} // namespace testing

View File

@ -51,6 +51,7 @@ using ::std::vector;
using ::testing::AddGlobalTestEnvironment;
using ::testing::Bool;
using ::testing::Combine;
using ::testing::ConvertGenerator;
using ::testing::Message;
using ::testing::Range;
using ::testing::TestWithParam;
@ -402,7 +403,7 @@ TEST(BoolTest, BoolWorks) {
TEST(CombineTest, CombineWithTwoParameters) {
const char* foo = "foo";
const char* bar = "bar";
const ParamGenerator<std::tuple<const char*, int> > gen =
const ParamGenerator<std::tuple<const char*, int>> gen =
Combine(Values(foo, bar), Values(3, 4));
std::tuple<const char*, int> expected_values[] = {
@ -413,7 +414,7 @@ TEST(CombineTest, CombineWithTwoParameters) {
// Tests that Combine() with three parameters generates the expected sequence.
TEST(CombineTest, CombineWithThreeParameters) {
const ParamGenerator<std::tuple<int, int, int> > gen =
const ParamGenerator<std::tuple<int, int, int>> gen =
Combine(Values(0, 1), Values(3, 4), Values(5, 6));
std::tuple<int, int, int> expected_values[] = {
std::make_tuple(0, 3, 5), std::make_tuple(0, 3, 6),
@ -427,7 +428,7 @@ TEST(CombineTest, CombineWithThreeParameters) {
// sequence generates a sequence with the number of elements equal to the
// number of elements in the sequence generated by the second parameter.
TEST(CombineTest, CombineWithFirstParameterSingleValue) {
const ParamGenerator<std::tuple<int, int> > gen =
const ParamGenerator<std::tuple<int, int>> gen =
Combine(Values(42), Values(0, 1));
std::tuple<int, int> expected_values[] = {std::make_tuple(42, 0),
@ -439,7 +440,7 @@ TEST(CombineTest, CombineWithFirstParameterSingleValue) {
// sequence generates a sequence with the number of elements equal to the
// number of elements in the sequence generated by the first parameter.
TEST(CombineTest, CombineWithSecondParameterSingleValue) {
const ParamGenerator<std::tuple<int, int> > gen =
const ParamGenerator<std::tuple<int, int>> gen =
Combine(Values(0, 1), Values(42));
std::tuple<int, int> expected_values[] = {std::make_tuple(0, 42),
@ -450,7 +451,7 @@ TEST(CombineTest, CombineWithSecondParameterSingleValue) {
// Tests that when the first parameter produces an empty sequence,
// Combine() produces an empty sequence, too.
TEST(CombineTest, CombineWithFirstParameterEmptyRange) {
const ParamGenerator<std::tuple<int, int> > gen =
const ParamGenerator<std::tuple<int, int>> gen =
Combine(Range(0, 0), Values(0, 1));
VerifyGeneratorIsEmpty(gen);
}
@ -458,7 +459,7 @@ TEST(CombineTest, CombineWithFirstParameterEmptyRange) {
// Tests that when the second parameter produces an empty sequence,
// Combine() produces an empty sequence, too.
TEST(CombineTest, CombineWithSecondParameterEmptyRange) {
const ParamGenerator<std::tuple<int, int> > gen =
const ParamGenerator<std::tuple<int, int>> gen =
Combine(Values(0, 1), Range(1, 1));
VerifyGeneratorIsEmpty(gen);
}
@ -469,7 +470,7 @@ TEST(CombineTest, CombineWithMaxNumberOfParameters) {
const char* foo = "foo";
const char* bar = "bar";
const ParamGenerator<
std::tuple<const char*, int, int, int, int, int, int, int, int, int> >
std::tuple<const char*, int, int, int, int, int, int, int, int, int>>
gen =
Combine(Values(foo, bar), Values(1), Values(2), Values(3), Values(4),
Values(5), Values(6), Values(7), Values(8), Values(9));
@ -497,11 +498,11 @@ class NonDefaultConstructAssignString {
};
TEST(CombineTest, NonDefaultConstructAssign) {
const ParamGenerator<std::tuple<int, NonDefaultConstructAssignString> > gen =
const ParamGenerator<std::tuple<int, NonDefaultConstructAssignString>> gen =
Combine(Values(0, 1), Values(NonDefaultConstructAssignString("A"),
NonDefaultConstructAssignString("B")));
ParamGenerator<std::tuple<int, NonDefaultConstructAssignString> >::iterator
ParamGenerator<std::tuple<int, NonDefaultConstructAssignString>>::iterator
it = gen.begin();
EXPECT_EQ(0, std::get<0>(*it));
@ -523,6 +524,63 @@ TEST(CombineTest, NonDefaultConstructAssign) {
EXPECT_TRUE(it == gen.end());
}
template <typename T>
class ConstructFromT {
public:
ConstructFromT(const T& t) : t_(t) {}
template <typename... Args>
ConstructFromT(Args&&... args) : t_(std::forward<Args>(args)...) {}
bool operator==(const ConstructFromT& other) const { return other.t_ == t_; }
const T& get() const { return t_; }
private:
T t_;
};
TEST(ConvertTest, CombineWithTwoParameters) {
const char* foo = "foo";
const char* bar = "bar";
const ParamGenerator<ConstructFromT<std::tuple<const char*, int>>> gen =
ConvertGenerator<std::tuple<const char*, int>>(
Combine(Values(foo, bar), Values(3, 4)));
ConstructFromT<std::tuple<const char*, int>> expected_values[] = {
{foo, 3}, {foo, 4}, {bar, 3}, {bar, 4}};
VerifyGenerator(gen, expected_values);
}
TEST(ConvertTest, NonDefaultConstructAssign) {
const ParamGenerator<
ConstructFromT<std::tuple<int, NonDefaultConstructAssignString>>>
gen = ConvertGenerator<std::tuple<int, NonDefaultConstructAssignString>>(
Combine(Values(0, 1), Values(NonDefaultConstructAssignString("A"),
NonDefaultConstructAssignString("B"))));
ParamGenerator<ConstructFromT<
std::tuple<int, NonDefaultConstructAssignString>>>::iterator it =
gen.begin();
EXPECT_EQ(0, std::get<0>(it->get()));
EXPECT_EQ("A", std::get<1>(it->get()).str());
++it;
EXPECT_EQ(0, std::get<0>(it->get()));
EXPECT_EQ("B", std::get<1>(it->get()).str());
++it;
EXPECT_EQ(1, std::get<0>(it->get()));
EXPECT_EQ("A", std::get<1>(it->get()).str());
++it;
EXPECT_EQ(1, std::get<0>(it->get()));
EXPECT_EQ("B", std::get<1>(it->get()).str());
++it;
EXPECT_TRUE(it == gen.end());
}
// Tests that an generator produces correct sequence after being
// assigned from another generator.
TEST(ParamGeneratorTest, AssignmentWorks) {