Compare commits

...

7 Commits

Author SHA1 Message Date
Stoorx
d3be365fbb
Merge 94f19b4a03aa317b4851e70c835c3b49f1518803 into 1b96fa13f549387b7549cc89e1a785cf143a1a50 2025-11-22 19:49:14 +00:00
stoorx
94f19b4a03 Explicitly include <array> header due to build fail on MSVC 2025-11-22 22:48:57 +03:00
stoorx
18172295df Fix CombineAsTest/CopyConstructible test 2025-11-22 22:48:57 +03:00
stoorx
cb67f16d48 Add the documentation for CombineAs<R>() param generator 2025-11-22 22:48:57 +03:00
stoorx
7049d96dd7 Add CombineAs<R>() generator function
`CombineAs<R>()` 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.
2025-11-22 22:48:56 +03:00
stoorx
d3414c2c46 Refactor Combine() generator function 2025-11-22 22:48:56 +03:00
stoorx
ceeca5fcea Refactor Values() generator function to resolve more complicated type transformations 2025-11-22 22:48:56 +03:00
7 changed files with 219 additions and 72 deletions

View File

@ -103,14 +103,15 @@ namespace:
<span id="param-generators"></span>
| 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<T>(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<R>(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<T>(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<R>()` 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<MyParam>(Values(1, 1.2), Bool()));
```
### TYPED_TEST_SUITE {#TYPED_TEST_SUITE}
`TYPED_TEST_SUITE(`*`TestFixtureName`*`,`*`Types`*`)`

View File

@ -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<R>()` 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

View File

@ -174,6 +174,7 @@ TEST_P(DerivedTest, DoesBlah) {
#endif // 0
#include <array>
#include <functional>
#include <iterator>
#include <utility>
@ -332,9 +333,10 @@ internal::ParamGenerator<typename Container::value_type> ValuesIn(
// INSTANTIATE_TEST_SUITE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5));
//
//
template <typename... T>
internal::ValueArray<T...> Values(T... v) {
return internal::ValueArray<T...>(std::move(v)...);
template <typename... Ts>
internal::ParamGenerator<std::common_type_t<Ts...>> Values(Ts... vs) {
return ValuesIn(
std::array<std::common_type_t<Ts...>, sizeof...(Ts)>{std::move(vs)...});
}
// Bool() allows generating tests with parameters in a set of (false, true).
@ -359,6 +361,47 @@ internal::ValueArray<T...> Values(T... v) {
//
inline internal::ParamGenerator<bool> 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<MyClass>(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<Animal> {...};
//
// TEST_P(AnimalTest, AnimalLooksNice) {...}
//
// INSTANTIATE_TEST_SUITE_P(AnimalVariations, AnimalTest,
// CombineAs<Animal>(Values("cat", "dog"),
// Values(BLACK, WHITE)));
//
template <typename R, typename... T>
internal::ParamGenerator<R> CombineAs(
internal::ParamGenerator<T>&&... generators) {
return internal::ParamGenerator<R>(
new internal::CartesianProductGenerator<R, T...>(
std::forward<decltype(generators)>(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> Bool() { return Values(false, true); }
// INSTANTIATE_TEST_SUITE_P(TwoBoolSequence, FlagDependentTest,
// Combine(Bool(), Bool()));
//
template <typename... Generator>
internal::CartesianProductHolder<Generator...> Combine(const Generator&... g) {
return internal::CartesianProductHolder<Generator...>(g...);
template <typename... T>
internal::ParamGenerator<std::tuple<T...>> Combine(
internal::ParamGenerator<T>&&... generators) {
return CombineAs<std::tuple<T...>, T...>(
std::forward<decltype(generators)>(generators)...);
}
// ConvertGenerator() wraps a parameter generator in order to cast each produced

View File

@ -61,15 +61,15 @@
#include <vector>
#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<T>().
// Values(), ValuesIn(), Bool(), Combine(), CombineAs<R>(), and
// ConvertGenerator<T>().
//
// class FooTest : public ::testing::TestWithParam<int> {
// protected:

View File

@ -180,6 +180,11 @@ class ParamGeneratorInterface {
virtual ParamIteratorInterface<T>* End() const = 0;
};
template <class GeneratedT,
typename StdFunction =
std::function<const GeneratedT&(const GeneratedT&)>>
class ParamConverterGenerator;
// Wraps ParamGeneratorInterface<T> 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 <typename R>
operator ParamGenerator<R>() {
return ParamConverterGenerator<T>(*this);
}
private:
std::shared_ptr<const ParamGeneratorInterface<T>> impl_;
};
@ -796,39 +806,15 @@ internal::ParamGenerator<typename Container::value_type> ValuesIn(
const Container& container);
namespace internal {
// Used in the Values() function to provide polymorphic capabilities.
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4100)
template <typename... Ts>
class ValueArray {
template <typename R, typename... T>
class CartesianProductGenerator : public ParamGeneratorInterface<R> {
public:
explicit ValueArray(Ts... v) : v_(FlatTupleConstructTag{}, std::move(v)...) {}
using ParamType = R;
template <typename T>
operator ParamGenerator<T>() const { // NOLINT
return ValuesIn(MakeVector<T>(std::make_index_sequence<sizeof...(Ts)>()));
}
explicit CartesianProductGenerator(ParamGenerator<T>&&... g)
: generators_(std::forward<decltype(g)>(g)...) {}
private:
template <typename T, size_t... I>
std::vector<T> MakeVector(std::index_sequence<I...>) const {
return std::vector<T>{static_cast<T>(v_.template Get<I>())...};
}
FlatTuple<Ts...> v_;
};
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4100
template <typename... T>
class CartesianProductGenerator
: public ParamGeneratorInterface<::std::tuple<T...>> {
public:
typedef ::std::tuple<T...> ParamType;
CartesianProductGenerator(const std::tuple<ParamGenerator<T>...>& g)
: generators_(g) {}
~CartesianProductGenerator() override = default;
ParamIteratorInterface<ParamType>* Begin() const override {
@ -916,7 +902,8 @@ class CartesianProductGenerator
void ComputeCurrentValue() {
if (!AtEnd())
current_value_ = std::make_shared<ParamType>(*std::get<I>(current_)...);
current_value_ =
std::make_shared<ParamType>(ParamType{*std::get<I>(current_)...});
}
bool AtEnd() const {
bool at_end = false;
@ -938,20 +925,6 @@ class CartesianProductGenerator
std::tuple<ParamGenerator<T>...> generators_;
};
template <class... Gen>
class CartesianProductHolder {
public:
CartesianProductHolder(const Gen&... g) : generators_(g...) {}
template <typename... T>
operator ParamGenerator<::std::tuple<T...>>() const {
return ParamGenerator<::std::tuple<T...>>(
new CartesianProductGenerator<T...>(generators_));
}
private:
std::tuple<Gen...> generators_;
};
template <typename From, typename To, typename Func>
class ParamGeneratorConverter : public ParamGeneratorInterface<To> {
public:
@ -1020,9 +993,7 @@ class ParamGeneratorConverter : public ParamGeneratorInterface<To> {
Func converter_;
}; // class ParamGeneratorConverter
template <class GeneratedT,
typename StdFunction =
std::function<const GeneratedT&(const GeneratedT&)>>
template <class GeneratedT, typename StdFunction>
class ParamConverterGenerator {
public:
ParamConverterGenerator(ParamGenerator<GeneratedT> g) // NOLINT

View File

@ -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<R>().
struct WellTypedPrimeTableTestParams {
bool force_on_the_fly;
int max_precalculated;
};
class WellTypedPrimeTableTest
: public TestWithParam<WellTypedPrimeTableTestParams> {
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<R>() 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<WellTypedPrimeTableTestParams>(Bool(), Values(1, 10)));
} // namespace

View File

@ -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<DefaultConstructible>);
ParamGenerator<DefaultConstructible> gen =
testing::CombineAs<DefaultConstructible>(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<NonDefaultConstructible>);
ParamGenerator<NonDefaultConstructible> gen =
testing::CombineAs<NonDefaultConstructible>(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<CopyConstructible>);
ParamGenerator<CopyConstructible> gen = testing::CombineAs<CopyConstructible>(
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<ConstructFromT<int8_t>> gen =
ConvertGenerator(Values("0", std::string("1")), [](const std::string& s) {