diff --git a/docs/reference/testing.md b/docs/reference/testing.md index ea43721e5..ae8a079fc 100644 --- a/docs/reference/testing.md +++ b/docs/reference/testing.md @@ -103,14 +103,15 @@ 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`. | -| `ConvertGenerator(g)` or `ConvertGenerator(g, func)` | Yields values generated by generator `g`, `static_cast` from `T`. (Note: `T` might not be what you expect. See [*Using ConvertGenerator*](#using-convertgenerator) below.) The second overload uses `func` to perform the conversion. | +| 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`. | +| `CombineAs(g1, g2, ..., gN)` | Yields as `R` *n*-instances all combinations (Cartesian product) of the values generated by the given *n* generators `g1`, `g2`, ..., `gN`. | +| `ConvertGenerator(g)` or `ConvertGenerator(g, func)` | Yields values generated by generator `g`, `static_cast` from `T`. (Note: `T` might not be what you expect. See [*Using ConvertGenerator*](#using-convertgenerator) below.) The second overload uses `func` to perform the conversion. | The optional last argument *`name_generator`* is a function or functor that generates custom test name suffixes based on the test parameters. The function @@ -234,6 +235,24 @@ To overcome this problem you can specify the generated type explicitly: dangling reference because the type deduction strips off the reference and the `const`). +###### Using `CombineAs` + +If you think the code above is too complicated, and you do not want to deal +with tuples, you may want to use `CombineAs()` which works like a combination +of `Combine()` + `ConvertGenerator()` + tuple unpacking function. + +```cpp +// The fixture's parameter type. +class MyParam { + public: + MyParam(int, bool); + ... +}; + +INSTANTIATE_TEST_SUITE_P(MyInstantiation, MyTestSuite, + CombineAs(Values(1, 1.2), Bool())); +``` + ### TYPED_TEST_SUITE {#TYPED_TEST_SUITE} `TYPED_TEST_SUITE(`*`TestFixtureName`*`,`*`Types`*`)` diff --git a/docs/samples.md b/docs/samples.md index dedc59098..e75323e1f 100644 --- a/docs/samples.md +++ b/docs/samples.md @@ -15,7 +15,7 @@ variety of googletest features. derived fixtures. * Sample #6 demonstrates type-parameterized tests. * Sample #7 teaches the basics of value-parameterized tests. -* Sample #8 shows using `Combine()` in value-parameterized tests. +* Sample #8 shows using `Combine()` and `CombineAs()` in value-parameterized tests. * Sample #9 shows use of the listener API to modify Google Test's console output and the use of its reflection API to inspect test results. * Sample #10 shows use of the listener API to implement a primitive memory diff --git a/googletest/include/gtest/gtest-param-test.h b/googletest/include/gtest/gtest-param-test.h index 9e023f96d..dc38559f7 100644 --- a/googletest/include/gtest/gtest-param-test.h +++ b/googletest/include/gtest/gtest-param-test.h @@ -174,6 +174,7 @@ TEST_P(DerivedTest, DoesBlah) { #endif // 0 +#include #include #include #include @@ -332,9 +333,10 @@ internal::ParamGenerator ValuesIn( // INSTANTIATE_TEST_SUITE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5)); // // -template -internal::ValueArray Values(T... v) { - return internal::ValueArray(std::move(v)...); +template +internal::ParamGenerator> Values(Ts... vs) { + return ValuesIn( + std::array, sizeof...(Ts)>{std::move(vs)...}); } // Bool() allows generating tests with parameters in a set of (false, true). @@ -359,6 +361,47 @@ internal::ValueArray Values(T... v) { // inline internal::ParamGenerator Bool() { return Values(false, true); } +// CombineAs() allows the user to combine two or more sequences to produce +// values of a Cartesian product of those sequences' elements converted to +// the required type. +// +// Synopsis: +// CombineAs(gen1, gen2, ..., genN) +// - returns a generator producing sequences with elements coming from +// the Cartesian product of elements from the sequences generated by +// gen1, gen2, ..., genN. The sequence elements will have a type of +// Myclass where elements from sequences produced by gen1, gen2, ..., genN +// was provided to the MyClass constructor parameters. +// +// Example: +// +// This will instantiate tests in test suite AnimalTest each one with +// the parameter values Animal("cat", BLACK), Animal("cat", WHITE), +// Animal("dog", BLACK), and Animal("dog", WHITE): +// enum Color { BLACK, GRAY, WHITE }; +// +// struct Animal { +// std::string name; +// Color color; +// }; +// +// class AnimalTest +// : public testing::TestWithParam {...}; +// +// TEST_P(AnimalTest, AnimalLooksNice) {...} +// +// INSTANTIATE_TEST_SUITE_P(AnimalVariations, AnimalTest, +// CombineAs(Values("cat", "dog"), +// Values(BLACK, WHITE))); +// +template +internal::ParamGenerator CombineAs( + internal::ParamGenerator&&... generators) { + return internal::ParamGenerator( + new internal::CartesianProductGenerator( + std::forward(generators)...)); +} + // Combine() allows the user to combine two or more sequences to produce // values of a Cartesian product of those sequences' elements. // @@ -403,9 +446,11 @@ inline internal::ParamGenerator Bool() { return Values(false, true); } // INSTANTIATE_TEST_SUITE_P(TwoBoolSequence, FlagDependentTest, // Combine(Bool(), Bool())); // -template -internal::CartesianProductHolder Combine(const Generator&... g) { - return internal::CartesianProductHolder(g...); +template +internal::ParamGenerator> Combine( + internal::ParamGenerator&&... generators) { + return CombineAs, T...>( + std::forward(generators)...); } // ConvertGenerator() wraps a parameter generator in order to cast each produced diff --git a/googletest/include/gtest/gtest.h b/googletest/include/gtest/gtest.h index 69994ee9d..8beb45f59 100644 --- a/googletest/include/gtest/gtest.h +++ b/googletest/include/gtest/gtest.h @@ -61,15 +61,15 @@ #include #include "gtest/gtest-assertion-result.h" // IWYU pragma: export -#include "gtest/gtest-death-test.h" // IWYU pragma: export -#include "gtest/gtest-matchers.h" // IWYU pragma: export -#include "gtest/gtest-message.h" // IWYU pragma: export -#include "gtest/gtest-param-test.h" // IWYU pragma: export -#include "gtest/gtest-printers.h" // IWYU pragma: export -#include "gtest/gtest-test-part.h" // IWYU pragma: export -#include "gtest/gtest-typed-test.h" // IWYU pragma: export -#include "gtest/gtest_pred_impl.h" // IWYU pragma: export -#include "gtest/gtest_prod.h" // IWYU pragma: export +#include "gtest/gtest-death-test.h" // IWYU pragma: export +#include "gtest/gtest-matchers.h" // IWYU pragma: export +#include "gtest/gtest-message.h" // IWYU pragma: export +#include "gtest/gtest-param-test.h" // IWYU pragma: export +#include "gtest/gtest-printers.h" // IWYU pragma: export +#include "gtest/gtest-test-part.h" // IWYU pragma: export +#include "gtest/gtest-typed-test.h" // IWYU pragma: export +#include "gtest/gtest_pred_impl.h" // IWYU pragma: export +#include "gtest/gtest_prod.h" // IWYU pragma: export #include "gtest/internal/gtest-internal.h" #include "gtest/internal/gtest-string.h" @@ -1664,7 +1664,8 @@ class GTEST_API_ AssertHelper { // the GetParam() method. // // Use it with one of the parameter generator defining functions, like Range(), -// Values(), ValuesIn(), Bool(), Combine(), and ConvertGenerator(). +// Values(), ValuesIn(), Bool(), Combine(), CombineAs(), 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 a092a86ad..8c7c34fb7 100644 --- a/googletest/include/gtest/internal/gtest-param-util.h +++ b/googletest/include/gtest/internal/gtest-param-util.h @@ -180,6 +180,11 @@ class ParamGeneratorInterface { virtual ParamIteratorInterface* End() const = 0; }; +template > +class ParamConverterGenerator; + // Wraps ParamGeneratorInterface and provides general generator syntax // compatible with the STL Container concept. // This class implements copy initialization semantics and the contained @@ -201,6 +206,11 @@ class ParamGenerator { iterator begin() const { return iterator(impl_->Begin()); } iterator end() const { return iterator(impl_->End()); } + template + operator ParamGenerator() { + return ParamConverterGenerator(*this); + } + private: std::shared_ptr> impl_; }; @@ -796,39 +806,15 @@ internal::ParamGenerator ValuesIn( const Container& container); namespace internal { -// Used in the Values() function to provide polymorphic capabilities. -GTEST_DISABLE_MSC_WARNINGS_PUSH_(4100) - -template -class ValueArray { +template +class CartesianProductGenerator : public ParamGeneratorInterface { public: - explicit ValueArray(Ts... v) : v_(FlatTupleConstructTag{}, std::move(v)...) {} + using ParamType = R; - template - operator ParamGenerator() const { // NOLINT - return ValuesIn(MakeVector(std::make_index_sequence())); - } + explicit CartesianProductGenerator(ParamGenerator&&... g) + : generators_(std::forward(g)...) {} - private: - template - std::vector MakeVector(std::index_sequence) const { - return std::vector{static_cast(v_.template Get())...}; - } - - FlatTuple v_; -}; - -GTEST_DISABLE_MSC_WARNINGS_POP_() // 4100 - -template -class CartesianProductGenerator - : public ParamGeneratorInterface<::std::tuple> { - public: - typedef ::std::tuple ParamType; - - CartesianProductGenerator(const std::tuple...>& g) - : generators_(g) {} ~CartesianProductGenerator() override = default; ParamIteratorInterface* Begin() const override { @@ -916,7 +902,8 @@ class CartesianProductGenerator void ComputeCurrentValue() { if (!AtEnd()) - current_value_ = std::make_shared(*std::get(current_)...); + current_value_ = + std::make_shared(ParamType{*std::get(current_)...}); } bool AtEnd() const { bool at_end = false; @@ -938,20 +925,6 @@ class CartesianProductGenerator std::tuple...> generators_; }; -template -class CartesianProductHolder { - public: - CartesianProductHolder(const Gen&... g) : generators_(g...) {} - template - operator ParamGenerator<::std::tuple>() const { - return ParamGenerator<::std::tuple>( - new CartesianProductGenerator(generators_)); - } - - private: - std::tuple generators_; -}; - template class ParamGeneratorConverter : public ParamGeneratorInterface { public: @@ -1020,9 +993,7 @@ class ParamGeneratorConverter : public ParamGeneratorInterface { Func converter_; }; // class ParamGeneratorConverter -template > +template class ParamConverterGenerator { public: ParamConverterGenerator(ParamGenerator g) // NOLINT diff --git a/googletest/samples/sample8_unittest.cc b/googletest/samples/sample8_unittest.cc index 4df81df0c..009338b00 100644 --- a/googletest/samples/sample8_unittest.cc +++ b/googletest/samples/sample8_unittest.cc @@ -81,6 +81,7 @@ class HybridPrimeTable : public PrimeTable { using ::testing::Bool; using ::testing::Combine; +using ::testing::CombineAs; using ::testing::TestWithParam; using ::testing::Values; @@ -151,4 +152,45 @@ TEST_P(PrimeTableTest, CanGetNextPrime) { INSTANTIATE_TEST_SUITE_P(MeaningfulTestParameters, PrimeTableTest, Combine(Bool(), Values(1, 10))); +// But now you look at the PrimeTableTest and you think dealing with std::tuple +// requires some boilerplate to unwrap it. You can use combined parameters of +// custom types easily with help of CombineAs(). +struct WellTypedPrimeTableTestParams { + bool force_on_the_fly; + int max_precalculated; +}; + +class WellTypedPrimeTableTest + : public TestWithParam { + protected: + void SetUp() override { + // It is possible to avoid destructuring of the std::tuple if we use struct + // as the test parameter type. + table_ = new HybridPrimeTable(GetParam().force_on_the_fly, + GetParam().max_precalculated); + } + void TearDown() override { + delete table_; + table_ = nullptr; + } + HybridPrimeTable* table_ = nullptr; +}; + +// The tests are made the same way as above +TEST_P(WellTypedPrimeTableTest, CanGetNextPrime) { + EXPECT_EQ(2, table_->GetNextPrime(0)); + EXPECT_EQ(3, table_->GetNextPrime(2)); + EXPECT_EQ(5, table_->GetNextPrime(3)); + EXPECT_EQ(7, table_->GetNextPrime(5)); + EXPECT_EQ(11, table_->GetNextPrime(7)); + EXPECT_EQ(131, table_->GetNextPrime(128)); +} + +// Here we instantiate our tests with combined parameters and straightforward +// typing. CombineAs() allows you to generate all possible combinations of +// values the same way as Combine() does, but also converts them to the desired +// type. +INSTANTIATE_TEST_SUITE_P(MeaningfulTestParameters, WellTypedPrimeTableTest, + CombineAs(Bool(), Values(1, 10))); + } // namespace diff --git a/googletest/test/googletest-param-test-test.cc b/googletest/test/googletest-param-test-test.cc index 10d429c9a..ff6de58e8 100644 --- a/googletest/test/googletest-param-test-test.cc +++ b/googletest/test/googletest-param-test-test.cc @@ -588,6 +588,75 @@ TEST(ConvertTest, NonDefaultConstructAssign) { EXPECT_TRUE(it == gen.end()); } +TEST(CombineAsTest, DefaultConstructible) { + struct DefaultConstructible { + int x; + std::string s; + + bool operator==(const DefaultConstructible& other) const { + return x == other.x && s == other.s; + } + }; + + static_assert(std::is_default_constructible_v); + ParamGenerator gen = + testing::CombineAs(Values(0, 1), Values("A", "B")); + + DefaultConstructible expected_values[] = { + {0, "A"}, {0, "B"}, {1, "A"}, {1, "B"}}; + VerifyGenerator(gen, expected_values); +} + +TEST(CombineAsTest, NonDefaultConstructible) { + class NonDefaultConstructible { + public: + NonDefaultConstructible(const int i_arg, std::string s_arg) + : i_(i_arg), s_(std::move(s_arg)) {} + + bool operator==(const NonDefaultConstructible& other) const { + return i_ == other.i_ && s_ == other.s_; + } + + private: + int i_; + std::string s_; + }; + + static_assert(!std::is_default_constructible_v); + ParamGenerator gen = + testing::CombineAs(Values(0, 1), + Values("A", "B")); + + NonDefaultConstructible expected_values[] = { + {0, "A"}, {0, "B"}, {1, "A"}, {1, "B"}}; + VerifyGenerator(gen, expected_values); +} + +TEST(CombineAsTest, CopyConstructible) { + class CopyConstructible { + public: + CopyConstructible(const CopyConstructible& other) = default; + + CopyConstructible(const int i_arg, std::string s_arg) + : i_(i_arg), s_(std::move(s_arg)) {} + + bool operator==(const CopyConstructible& other) const { + return i_ == other.i_ && s_ == other.s_; + } + + private: + int i_; + std::string s_; + }; + + static_assert(std::is_copy_constructible_v); + ParamGenerator gen = testing::CombineAs( + Values(CopyConstructible(0, "A"), CopyConstructible(1, "B"))); + CopyConstructible expected_values[] = {CopyConstructible(0, "A"), + CopyConstructible(1, "B")}; + VerifyGenerator(gen, expected_values); +} + TEST(ConvertTest, WithConverterLambdaAndDeducedType) { const ParamGenerator> gen = ConvertGenerator(Values("0", std::string("1")), [](const std::string& s) {