diff --git a/docs/reference/testing.md b/docs/reference/testing.md index dc479423..dd0b982c 100644 --- a/docs/reference/testing.md +++ b/docs/reference/testing.md @@ -102,13 +102,22 @@ namespace: -| Parameter Generator | Behavior | -| ------------------- | ---------------------------------------------------- | -| `Range(begin, end [, step])` | Yields values `{begin, begin+step, begin+step+step, ...}`. The values do not include `end`. `step` defaults to 1. | -| `Values(v1, v2, ..., vN)` | Yields values `{v1, v2, ..., vN}`. | -| `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`. | +| Parameter Generator | Behavior | +| ---------------------------- | -------------------------------------------- | +| `Range(begin, end [, step])` | Yields values `{begin, begin+step, | +: : begin+step+step, ...}`. The values do not : +: : include `end`. `step` defaults to 1. : +| `Values(v1, v2, ..., vN)` | Yields values `{v1, v2, ..., vN}`. | +| `ValuesIn(container)` or | Yields values from a C-style array, an | +: `ValuesIn(begin,end)` : 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(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 diff --git a/googletest/include/gtest/gtest-param-test.h b/googletest/include/gtest/gtest-param-test.h index 2d38b96c..10d6a079 100644 --- a/googletest/include/gtest/gtest-param-test.h +++ b/googletest/include/gtest/gtest-param-test.h @@ -407,6 +407,46 @@ internal::CartesianProductHolder Combine(const Generator&... g) { return internal::CartesianProductHolder(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(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; +// std::string animal; +// Color color; +// ParamType(TupleT t) : animal(std::get<0>(t)), color(std::get<1>(t)) {} +// }; +// class AnimalTest +// : public testing::TestWithParam {...}; +// +// TEST_P(AnimalTest, AnimalLooksNice) {...} +// +// INSTANTIATE_TEST_SUITE_P(AnimalVariations, AnimalTest, +// ConvertGenerator( +// Combine(Values("cat", "dog"), +// Values(BLACK, WHITE)))); +// +template +internal::ParamConverterGenerator ConvertGenerator( + internal::ParamGenerator gen) { + return internal::ParamConverterGenerator(gen); +} + #define TEST_P(test_suite_name, test_name) \ class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ : public test_suite_name, private ::testing::internal::GTestNonCopyable {\ diff --git a/googletest/include/gtest/gtest.h b/googletest/include/gtest/gtest.h index 81e124fa..7963e8c7 100644 --- a/googletest/include/gtest/gtest.h +++ b/googletest/include/gtest/gtest.h @@ -1636,7 +1636,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(). // // class FooTest : public ::testing::TestWithParam { // protected: diff --git a/googletest/include/gtest/internal/gtest-param-util.h b/googletest/include/gtest/internal/gtest-param-util.h index e7af2f90..fb989e0b 100644 --- a/googletest/include/gtest/internal/gtest-param-util.h +++ b/googletest/include/gtest/internal/gtest-param-util.h @@ -950,6 +950,78 @@ class CartesianProductHolder { std::tuple generators_; }; +template +class ParamGeneratorConverter : public ParamGeneratorInterface { + public: + ParamGeneratorConverter(ParamGenerator gen) // NOLINT + : generator_(std::move(gen)) {} + + ParamIteratorInterface* Begin() const override { + return new Iterator(this, generator_.begin(), generator_.end()); + } + ParamIteratorInterface* End() const override { + return new Iterator(this, generator_.end(), generator_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, ParamIterator it, + ParamIterator end) + : base_(base), it_(it), end_(end) { + if (it_ != end_) value_ = std::make_shared(static_cast(*it_)); + } + ~Iterator() override {} + + const ParamGeneratorInterface* BaseGenerator() const override { + return base_; + } + void Advance() override { + ++it_; + if (it_ != end_) value_ = std::make_shared(static_cast(*it_)); + } + ParamIteratorInterface* Clone() const override { + return new Iterator(*this); + } + const To* Current() const override { return value_.get(); } + bool Equals(const ParamIteratorInterface& 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 other_it = + CheckedDowncastToActualType(&other)->it_; + return it_ == other_it; + } + + private: + Iterator(const Iterator& other) = default; + + const ParamGeneratorInterface* const base_; + ParamIterator it_; + ParamIterator end_; + std::shared_ptr value_; + }; // class ParamGeneratorConverter::Iterator + + ParamGenerator generator_; +}; // class ParamGeneratorConverter + +template +class ParamConverterGenerator { + public: + ParamConverterGenerator(ParamGenerator g) // NOLINT + : generator_(std::move(g)) {} + + template + operator ParamGenerator() const { // NOLINT + return ParamGenerator(new ParamGeneratorConverter(generator_)); + } + + private: + ParamGenerator generator_; +}; + } // namespace internal } // namespace testing diff --git a/googletest/test/googletest-param-test-test.cc b/googletest/test/googletest-param-test-test.cc index e3090ae4..63db2860 100644 --- a/googletest/test/googletest-param-test-test.cc +++ b/googletest/test/googletest-param-test-test.cc @@ -40,6 +40,7 @@ #include #include #include +#include #include #include "gtest/gtest.h" @@ -51,6 +52,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 +404,7 @@ TEST(BoolTest, BoolWorks) { TEST(CombineTest, CombineWithTwoParameters) { const char* foo = "foo"; const char* bar = "bar"; - const ParamGenerator > gen = + const ParamGenerator> gen = Combine(Values(foo, bar), Values(3, 4)); std::tuple expected_values[] = { @@ -413,7 +415,7 @@ TEST(CombineTest, CombineWithTwoParameters) { // Tests that Combine() with three parameters generates the expected sequence. TEST(CombineTest, CombineWithThreeParameters) { - const ParamGenerator > gen = + const ParamGenerator> gen = Combine(Values(0, 1), Values(3, 4), Values(5, 6)); std::tuple expected_values[] = { std::make_tuple(0, 3, 5), std::make_tuple(0, 3, 6), @@ -427,7 +429,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 > gen = + const ParamGenerator> gen = Combine(Values(42), Values(0, 1)); std::tuple expected_values[] = {std::make_tuple(42, 0), @@ -439,7 +441,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 > gen = + const ParamGenerator> gen = Combine(Values(0, 1), Values(42)); std::tuple expected_values[] = {std::make_tuple(0, 42), @@ -450,7 +452,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 > gen = + const ParamGenerator> gen = Combine(Range(0, 0), Values(0, 1)); VerifyGeneratorIsEmpty(gen); } @@ -458,7 +460,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 > gen = + const ParamGenerator> gen = Combine(Values(0, 1), Range(1, 1)); VerifyGeneratorIsEmpty(gen); } @@ -469,7 +471,7 @@ TEST(CombineTest, CombineWithMaxNumberOfParameters) { const char* foo = "foo"; const char* bar = "bar"; const ParamGenerator< - std::tuple > + std::tuple> 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 +499,11 @@ class NonDefaultConstructAssignString { }; TEST(CombineTest, NonDefaultConstructAssign) { - const ParamGenerator > gen = + const ParamGenerator> gen = Combine(Values(0, 1), Values(NonDefaultConstructAssignString("A"), NonDefaultConstructAssignString("B"))); - ParamGenerator >::iterator + ParamGenerator>::iterator it = gen.begin(); EXPECT_EQ(0, std::get<0>(*it)); @@ -523,6 +525,64 @@ TEST(CombineTest, NonDefaultConstructAssign) { EXPECT_TRUE(it == gen.end()); } +template +class ConstructFromT { + public: + explicit ConstructFromT(const T& t) : t_(t) {} + template ::type = 0> + ConstructFromT(Args&&... args) : t_(std::forward(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>> gen = + ConvertGenerator>( + Combine(Values(foo, bar), Values(3, 4))); + + ConstructFromT> expected_values[] = { + {foo, 3}, {foo, 4}, {bar, 3}, {bar, 4}}; + VerifyGenerator(gen, expected_values); +} + +TEST(ConvertTest, NonDefaultConstructAssign) { + const ParamGenerator< + ConstructFromT>> + gen = ConvertGenerator>( + Combine(Values(0, 1), Values(NonDefaultConstructAssignString("A"), + NonDefaultConstructAssignString("B")))); + + ParamGenerator>>::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) {