From ceeca5fcea1ef305a3e15b60e81301cabdf5913f Mon Sep 17 00:00:00 2001 From: stoorx Date: Mon, 24 Feb 2025 13:44:21 +0300 Subject: [PATCH 1/6] Refactor `Values()` generator function to resolve more complicated type transformations --- googletest/include/gtest/gtest-param-test.h | 7 ++-- .../include/gtest/internal/gtest-param-util.h | 38 ++++++------------- 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/googletest/include/gtest/gtest-param-test.h b/googletest/include/gtest/gtest-param-test.h index 9e023f96d..e305e715d 100644 --- a/googletest/include/gtest/gtest-param-test.h +++ b/googletest/include/gtest/gtest-param-test.h @@ -332,9 +332,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). diff --git a/googletest/include/gtest/internal/gtest-param-util.h b/googletest/include/gtest/internal/gtest-param-util.h index a092a86ad..01e3e9e1c 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,30 +806,6 @@ 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 { - public: - explicit ValueArray(Ts... v) : v_(FlatTupleConstructTag{}, std::move(v)...) {} - - template - operator ParamGenerator() const { // NOLINT - return ValuesIn(MakeVector(std::make_index_sequence())); - } - - 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 @@ -1020,9 +1006,7 @@ class ParamGeneratorConverter : public ParamGeneratorInterface { Func converter_; }; // class ParamGeneratorConverter -template > +template class ParamConverterGenerator { public: ParamConverterGenerator(ParamGenerator g) // NOLINT From d3414c2c46b76121264659598c78b44a0e61b6c9 Mon Sep 17 00:00:00 2001 From: stoorx Date: Mon, 24 Feb 2025 14:26:21 +0300 Subject: [PATCH 2/6] Refactor `Combine()` generator function --- googletest/include/gtest/gtest-param-test.h | 9 ++++--- .../include/gtest/internal/gtest-param-util.h | 26 +++++-------------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/googletest/include/gtest/gtest-param-test.h b/googletest/include/gtest/gtest-param-test.h index e305e715d..f5caed68c 100644 --- a/googletest/include/gtest/gtest-param-test.h +++ b/googletest/include/gtest/gtest-param-test.h @@ -404,9 +404,12 @@ 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 internal::ParamGenerator>( + new internal::CartesianProductGenerator, T...>( + std::forward(generators)...)); } // ConvertGenerator() wraps a parameter generator in order to cast each produced diff --git a/googletest/include/gtest/internal/gtest-param-util.h b/googletest/include/gtest/internal/gtest-param-util.h index 01e3e9e1c..b2bdf16de 100644 --- a/googletest/include/gtest/internal/gtest-param-util.h +++ b/googletest/include/gtest/internal/gtest-param-util.h @@ -807,14 +807,14 @@ internal::ParamGenerator ValuesIn( namespace internal { -template -class CartesianProductGenerator - : public ParamGeneratorInterface<::std::tuple> { +template +class CartesianProductGenerator : public ParamGeneratorInterface { public: - typedef ::std::tuple ParamType; + using ParamType = R; + + explicit CartesianProductGenerator(ParamGenerator&&... g) + : generators_(std::forward(g)...) {} - CartesianProductGenerator(const std::tuple...>& g) - : generators_(g) {} ~CartesianProductGenerator() override = default; ParamIteratorInterface* Begin() const override { @@ -924,20 +924,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: From 7049d96dd7b567c417b257f163ccde64ad84f0da Mon Sep 17 00:00:00 2001 From: stoorx Date: Mon, 24 Feb 2025 16:02:28 +0300 Subject: [PATCH 3/6] Add `CombineAs()` generator function `CombineAs()` allows to construct the required type directly from combined arguments. As it would be a composition of `ConvertGenerator()` and `Combine()`, but without `std::tuple` in between. --- googletest/include/gtest/gtest-param-test.h | 46 ++++++++++++- .../include/gtest/internal/gtest-param-util.h | 3 +- googletest/test/googletest-param-test-test.cc | 64 +++++++++++++++++++ 3 files changed, 109 insertions(+), 4 deletions(-) diff --git a/googletest/include/gtest/gtest-param-test.h b/googletest/include/gtest/gtest-param-test.h index f5caed68c..5565f3468 100644 --- a/googletest/include/gtest/gtest-param-test.h +++ b/googletest/include/gtest/gtest-param-test.h @@ -360,6 +360,47 @@ internal::ParamGenerator> Values(Ts... vs) { // 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. // @@ -407,9 +448,8 @@ inline internal::ParamGenerator Bool() { return Values(false, true); } template internal::ParamGenerator> Combine( internal::ParamGenerator&&... generators) { - return internal::ParamGenerator>( - new internal::CartesianProductGenerator, T...>( - std::forward(generators)...)); + return CombineAs, T...>( + std::forward(generators)...); } // ConvertGenerator() wraps a parameter generator in order to cast each produced diff --git a/googletest/include/gtest/internal/gtest-param-util.h b/googletest/include/gtest/internal/gtest-param-util.h index b2bdf16de..8c7c34fb7 100644 --- a/googletest/include/gtest/internal/gtest-param-util.h +++ b/googletest/include/gtest/internal/gtest-param-util.h @@ -902,7 +902,8 @@ class CartesianProductGenerator : public ParamGeneratorInterface { 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; diff --git a/googletest/test/googletest-param-test-test.cc b/googletest/test/googletest-param-test-test.cc index 10d429c9a..1b46fa28a 100644 --- a/googletest/test/googletest-param-test-test.cc +++ b/googletest/test/googletest-param-test-test.cc @@ -588,6 +588,70 @@ 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) { + struct CopyConstructible { + CopyConstructible(const CopyConstructible& other) = default; + + bool operator==(const CopyConstructible& other) const { + return x == other.x && s == other.s; + } + + int x; + 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) { From cb67f16d48fae81d2bca2c66b92682f7c251bb98 Mon Sep 17 00:00:00 2001 From: stoorx Date: Wed, 28 May 2025 01:01:11 +0300 Subject: [PATCH 4/6] Add the documentation for `CombineAs()` param generator --- docs/reference/testing.md | 35 ++++++++++++++++----- docs/samples.md | 2 +- googletest/include/gtest/gtest.h | 21 +++++++------ googletest/samples/sample8_unittest.cc | 42 ++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 19 deletions(-) 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.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/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 From 18172295df70e0747ad93c51e49d1056e2174fe2 Mon Sep 17 00:00:00 2001 From: stoorx Date: Thu, 12 Jun 2025 00:19:27 +0300 Subject: [PATCH 5/6] Fix `CombineAsTest/CopyConstructible` test --- googletest/test/googletest-param-test-test.cc | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/googletest/test/googletest-param-test-test.cc b/googletest/test/googletest-param-test-test.cc index 1b46fa28a..ff6de58e8 100644 --- a/googletest/test/googletest-param-test-test.cc +++ b/googletest/test/googletest-param-test-test.cc @@ -633,22 +633,27 @@ TEST(CombineAsTest, NonDefaultConstructible) { } TEST(CombineAsTest, CopyConstructible) { - struct 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 x == other.x && s == other.s; + return i_ == other.i_ && s_ == other.s_; } - int x; - std::string 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"}}; + Values(CopyConstructible(0, "A"), CopyConstructible(1, "B"))); + CopyConstructible expected_values[] = {CopyConstructible(0, "A"), + CopyConstructible(1, "B")}; VerifyGenerator(gen, expected_values); } From 94f19b4a03aa317b4851e70c835c3b49f1518803 Mon Sep 17 00:00:00 2001 From: stoorx Date: Fri, 11 Jul 2025 21:39:13 +0300 Subject: [PATCH 6/6] Explicitly include header due to build fail on MSVC --- googletest/include/gtest/gtest-param-test.h | 1 + 1 file changed, 1 insertion(+) diff --git a/googletest/include/gtest/gtest-param-test.h b/googletest/include/gtest/gtest-param-test.h index 5565f3468..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