Skip to content

Commit e88cb95

Browse files
Abseil Teamcopybara-github
Abseil Team
authored andcommitted
Add a testing::ConvertGenerator overload that accepts a converting functor. This allows the use of classes that do not have a converting ctor to the desired type.
PiperOrigin-RevId: 733383835 Change-Id: I6fbf79db0509b3d4fe8305a83ed47fceaa820e47
1 parent 24a9e94 commit e88cb95

File tree

4 files changed

+278
-20
lines changed

4 files changed

+278
-20
lines changed

docs/reference/testing.md

+98-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ namespace:
110110
| `ValuesIn(container)` or `ValuesIn(begin,end)` | Yields values from a C-style array, an STL-style container, or an iterator range `[begin, end)`. |
111111
| `Bool()` | Yields sequence `{false, true}`. |
112112
| `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`. |
113-
| `ConvertGenerator<T>(g)` | Yields values generated by generator `g`, `static_cast` to `T`. |
113+
| `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. |
114114
115115
The optional last argument *`name_generator`* is a function or functor that
116116
generates custom test name suffixes based on the test parameters. The function
@@ -137,6 +137,103 @@ For more information, see
137137
See also
138138
[`GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST`](#GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST).
139139

140+
###### Using `ConvertGenerator`
141+
142+
The functions listed in the table above appear to return generators that create
143+
values of the desired types, but this is not generally the case. Rather, they
144+
typically return factory objects that convert to the the desired generators.
145+
This affords some flexibility in allowing you to specify values of types that
146+
are different from, yet implicitly convertible to, the actual parameter type
147+
required by your fixture class.
148+
149+
For example, you can do the following with a fixture that requires an `int`
150+
parameter:
151+
152+
```cpp
153+
INSTANTIATE_TEST_SUITE_P(MyInstantiation, MyTestSuite,
154+
testing::Values(1, 1.2)); // Yes, Values() supports heterogeneous argument types.
155+
```
156+
157+
It might seem obvious that `1.2` &mdash; a `double` &mdash; will be converted to
158+
an `int` but in actuality it requires some template gymnastics involving the
159+
indirection described in the previous paragraph.
160+
161+
What if your parameter type is not implicitly convertible from the generated
162+
type but is *explicitly* convertible? There will be no automatic conversion, but
163+
you can force it by applying `ConvertGenerator<T>`. The compiler can
164+
automatically deduce the target type (your fixture's parameter type), but
165+
because of the aforementioned indirection it cannot decide what the generated
166+
type should be. You need to tell it, by providing the type `T` explicitly. Thus
167+
`T` should not be your fixture's parameter type, but rather an intermediate type
168+
that is supported by the factory object, and which can be `static_cast` to the
169+
fixture's parameter type:
170+
171+
```cpp
172+
// The fixture's parameter type.
173+
class MyParam {
174+
public:
175+
// Explicit converting ctor.
176+
explicit MyParam(const std::tuple<int, bool>& t);
177+
...
178+
};
179+
180+
INSTANTIATE_TEST_SUITE_P(MyInstantiation, MyTestSuite,
181+
ConvertGenerator<std::tuple<int, bool>>(Combine(Values(0.1, 1.2), Bool())));
182+
```
183+
184+
In this example `Combine` supports the generation of `std::tuple<int, bool>>`
185+
objects (even though the provided values for the first tuple element are
186+
`double`s) and those `tuple`s get converted into `MyParam` objects by virtue of
187+
the call to `ConvertGenerator`.
188+
189+
For parameter types that are not convertible from the generated types you can
190+
provide a callable that does the conversion. The callable accepts an object of
191+
the generated type and returns an object of the fixture's parameter type. The
192+
generated type can often be deduced by the compiler from the callable's call
193+
signature so you do not usually need specify it explicitly (but see a caveat
194+
below).
195+
196+
```cpp
197+
// The fixture's parameter type.
198+
class MyParam {
199+
public:
200+
MyParam(int, bool);
201+
...
202+
};
203+
204+
INSTANTIATE_TEST_SUITE_P(MyInstantiation, MyTestSuite,
205+
ConvertGenerator(Combine(Values(1, 1.2), Bool()),
206+
[](const std::tuple<int i, bool>& t){
207+
const auto [i, b] = t;
208+
return MyParam(i, b);
209+
}));
210+
```
211+
212+
The callable may be anything that can be used to initialize a `std::function`
213+
with the appropriate call signature. Note the callable's return object gets
214+
`static_cast` to the fixture's parameter type, so it does not have to be of that
215+
exact type, only convertible to it.
216+
217+
**Caveat:** Consider the following example.
218+
219+
```cpp
220+
INSTANTIATE_TEST_SUITE_P(MyInstantiation, MyTestSuite,
221+
ConvertGenerator(Values(std::string("s")), [](std::string_view s) { ... }));
222+
```
223+
224+
The `string` argument gets copied into the factory object returned by `Values`.
225+
Then, because the generated type deduced from the lambda is `string_view`, the
226+
factory object spawns a generator that holds a `string_view` referencing that
227+
`string`. Unfortunately, by the time this generator gets invoked, the factory
228+
object is gone and the `string_view` is dangling.
229+
230+
To overcome this problem you can specify the generated type explicitly:
231+
`ConvertGenerator<std::string>(Values(std::string("s")), [](std::string_view s)
232+
{ ... })`. Alternatively, you can change the lambda's signature to take a
233+
`std::string` or a `const std::string&` (the latter will not leave you with a
234+
dangling reference because the type deduction strips off the reference and the
235+
`const`).
236+
140237
### TYPED_TEST_SUITE {#TYPED_TEST_SUITE}
141238

142239
`TYPED_TEST_SUITE(`*`TestFixtureName`*`,`*`Types`*`)`

googletest/include/gtest/gtest-param-test.h

+62-5
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ TEST_P(DerivedTest, DoesBlah) {
174174

175175
#endif // 0
176176

177+
#include <functional>
177178
#include <iterator>
178179
#include <utility>
179180

@@ -413,7 +414,8 @@ internal::CartesianProductHolder<Generator...> Combine(const Generator&... g) {
413414
// Synopsis:
414415
// ConvertGenerator<T>(gen)
415416
// - returns a generator producing the same elements as generated by gen, but
416-
// each element is static_cast to type T before being returned
417+
// each T-typed element is static_cast to a type deduced from the interface
418+
// that accepts this generator, and then returned
417419
//
418420
// It is useful when using the Combine() function to get the generated
419421
// parameters in a custom type instead of std::tuple
@@ -441,10 +443,65 @@ internal::CartesianProductHolder<Generator...> Combine(const Generator&... g) {
441443
// Combine(Values("cat", "dog"),
442444
// Values(BLACK, WHITE))));
443445
//
444-
template <typename T>
445-
internal::ParamConverterGenerator<T> ConvertGenerator(
446-
internal::ParamGenerator<T> gen) {
447-
return internal::ParamConverterGenerator<T>(gen);
446+
template <typename RequestedT>
447+
internal::ParamConverterGenerator<RequestedT> ConvertGenerator(
448+
internal::ParamGenerator<RequestedT> gen) {
449+
return internal::ParamConverterGenerator<RequestedT>(std::move(gen));
450+
}
451+
452+
// As above, but takes a callable as a second argument. The callable converts
453+
// the generated parameter to the test fixture's parameter type. This allows you
454+
// to use a parameter type that does not have a converting constructor from the
455+
// generated type.
456+
//
457+
// Example:
458+
//
459+
// This will instantiate tests in test suite AnimalTest each one with
460+
// the parameter values tuple("cat", BLACK), tuple("cat", WHITE),
461+
// tuple("dog", BLACK), and tuple("dog", WHITE):
462+
//
463+
// enum Color { BLACK, GRAY, WHITE };
464+
// struct ParamType {
465+
// std::string animal;
466+
// Color color;
467+
// };
468+
// class AnimalTest
469+
// : public testing::TestWithParam<ParamType> {...};
470+
//
471+
// TEST_P(AnimalTest, AnimalLooksNice) {...}
472+
//
473+
// INSTANTIATE_TEST_SUITE_P(
474+
// AnimalVariations, AnimalTest,
475+
// ConvertGenerator(Combine(Values("cat", "dog"), Values(BLACK, WHITE)),
476+
// [](std::tuple<std::string, Color> t) {
477+
// return ParamType{.animal = std::get<0>(t),
478+
// .color = std::get<1>(t)};
479+
// }));
480+
//
481+
template <typename T, int&... ExplicitArgumentBarrier, typename Gen,
482+
typename Func,
483+
typename StdFunction = decltype(std::function(std::declval<Func>()))>
484+
internal::ParamConverterGenerator<T, StdFunction> ConvertGenerator(Gen&& gen,
485+
Func&& f) {
486+
return internal::ParamConverterGenerator<T, StdFunction>(
487+
std::forward<Gen>(gen), std::forward<Func>(f));
488+
}
489+
490+
// As above, but infers the T from the supplied std::function instead of
491+
// having the caller specify it.
492+
template <int&... ExplicitArgumentBarrier, typename Gen, typename Func,
493+
typename StdFunction = decltype(std::function(std::declval<Func>()))>
494+
auto ConvertGenerator(Gen&& gen, Func&& f) {
495+
constexpr bool is_single_arg_std_function =
496+
internal::IsSingleArgStdFunction<StdFunction>::value;
497+
if constexpr (is_single_arg_std_function) {
498+
return ConvertGenerator<
499+
typename internal::FuncSingleParamType<StdFunction>::type>(
500+
std::forward<Gen>(gen), std::forward<Func>(f));
501+
} else {
502+
static_assert(is_single_arg_std_function,
503+
"The call signature must contain a single argument.");
504+
}
448505
}
449506

450507
#define TEST_P(test_suite_name, test_name) \

googletest/include/gtest/internal/gtest-param-util.h

+48-14
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include <ctype.h>
4040

4141
#include <cassert>
42+
#include <functional>
4243
#include <iterator>
4344
#include <map>
4445
#include <memory>
@@ -529,8 +530,7 @@ class ParameterizedTestSuiteInfo : public ParameterizedTestSuiteInfoBase {
529530
// prefix). test_base_name is the name of an individual test without
530531
// parameter index. For the test SequenceA/FooTest.DoBar/1 FooTest is
531532
// test suite base name and DoBar is test base name.
532-
void AddTestPattern(const char*,
533-
const char* test_base_name,
533+
void AddTestPattern(const char*, const char* test_base_name,
534534
TestMetaFactoryBase<ParamType>* meta_factory,
535535
CodeLocation code_location) {
536536
tests_.emplace_back(
@@ -952,11 +952,11 @@ class CartesianProductHolder {
952952
std::tuple<Gen...> generators_;
953953
};
954954

955-
template <typename From, typename To>
955+
template <typename From, typename To, typename Func>
956956
class ParamGeneratorConverter : public ParamGeneratorInterface<To> {
957957
public:
958-
ParamGeneratorConverter(ParamGenerator<From> gen) // NOLINT
959-
: generator_(std::move(gen)) {}
958+
ParamGeneratorConverter(ParamGenerator<From> gen, Func converter) // NOLINT
959+
: generator_(std::move(gen)), converter_(std::move(converter)) {}
960960

961961
ParamIteratorInterface<To>* Begin() const override {
962962
return new Iterator(this, generator_.begin(), generator_.end());
@@ -965,13 +965,21 @@ class ParamGeneratorConverter : public ParamGeneratorInterface<To> {
965965
return new Iterator(this, generator_.end(), generator_.end());
966966
}
967967

968+
// Returns the std::function wrapping the user-supplied converter callable. It
969+
// is used by the iterator (see class Iterator below) to convert the object
970+
// (of type FROM) returned by the ParamGenerator to an object of a type that
971+
// can be static_cast to type TO.
972+
const Func& TypeConverter() const { return converter_; }
973+
968974
private:
969975
class Iterator : public ParamIteratorInterface<To> {
970976
public:
971-
Iterator(const ParamGeneratorInterface<To>* base, ParamIterator<From> it,
977+
Iterator(const ParamGeneratorConverter* base, ParamIterator<From> it,
972978
ParamIterator<From> end)
973979
: base_(base), it_(it), end_(end) {
974-
if (it_ != end_) value_ = std::make_shared<To>(static_cast<To>(*it_));
980+
if (it_ != end_)
981+
value_ =
982+
std::make_shared<To>(static_cast<To>(base->TypeConverter()(*it_)));
975983
}
976984
~Iterator() override = default;
977985

@@ -980,7 +988,9 @@ class ParamGeneratorConverter : public ParamGeneratorInterface<To> {
980988
}
981989
void Advance() override {
982990
++it_;
983-
if (it_ != end_) value_ = std::make_shared<To>(static_cast<To>(*it_));
991+
if (it_ != end_)
992+
value_ =
993+
std::make_shared<To>(static_cast<To>(base_->TypeConverter()(*it_)));
984994
}
985995
ParamIteratorInterface<To>* Clone() const override {
986996
return new Iterator(*this);
@@ -1000,30 +1010,54 @@ class ParamGeneratorConverter : public ParamGeneratorInterface<To> {
10001010
private:
10011011
Iterator(const Iterator& other) = default;
10021012

1003-
const ParamGeneratorInterface<To>* const base_;
1013+
const ParamGeneratorConverter* const base_;
10041014
ParamIterator<From> it_;
10051015
ParamIterator<From> end_;
10061016
std::shared_ptr<To> value_;
10071017
}; // class ParamGeneratorConverter::Iterator
10081018

10091019
ParamGenerator<From> generator_;
1020+
Func converter_;
10101021
}; // class ParamGeneratorConverter
10111022

1012-
template <class Gen>
1023+
template <class GeneratedT,
1024+
typename StdFunction =
1025+
std::function<const GeneratedT&(const GeneratedT&)>>
10131026
class ParamConverterGenerator {
10141027
public:
1015-
ParamConverterGenerator(ParamGenerator<Gen> g) // NOLINT
1016-
: generator_(std::move(g)) {}
1028+
ParamConverterGenerator(ParamGenerator<GeneratedT> g) // NOLINT
1029+
: generator_(std::move(g)), converter_(Identity) {}
1030+
1031+
ParamConverterGenerator(ParamGenerator<GeneratedT> g, StdFunction converter)
1032+
: generator_(std::move(g)), converter_(std::move(converter)) {}
10171033

10181034
template <typename T>
10191035
operator ParamGenerator<T>() const { // NOLINT
1020-
return ParamGenerator<T>(new ParamGeneratorConverter<Gen, T>(generator_));
1036+
return ParamGenerator<T>(
1037+
new ParamGeneratorConverter<GeneratedT, T, StdFunction>(generator_,
1038+
converter_));
10211039
}
10221040

10231041
private:
1024-
ParamGenerator<Gen> generator_;
1042+
static const GeneratedT& Identity(const GeneratedT& v) { return v; }
1043+
1044+
ParamGenerator<GeneratedT> generator_;
1045+
StdFunction converter_;
1046+
};
1047+
1048+
// Template to determine the param type of a single-param std::function.
1049+
template <typename T>
1050+
struct FuncSingleParamType;
1051+
template <typename R, typename P>
1052+
struct FuncSingleParamType<std::function<R(P)>> {
1053+
using type = std::remove_cv_t<std::remove_reference_t<P>>;
10251054
};
10261055

1056+
template <typename T>
1057+
struct IsSingleArgStdFunction : public std::false_type {};
1058+
template <typename R, typename P>
1059+
struct IsSingleArgStdFunction<std::function<R(P)>> : public std::true_type {};
1060+
10271061
} // namespace internal
10281062
} // namespace testing
10291063

0 commit comments

Comments
 (0)