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) {