mirror of
https://github.com/google/googletest.git
synced 2025-03-20 10:53:47 +00:00
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
This commit is contained in:
parent
24a9e940d4
commit
e88cb95b92
@ -110,7 +110,7 @@ namespace:
|
||||
| `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)` | Yields values generated by generator `g`, `static_cast` to `T`. |
|
||||
| `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
|
||||
@ -137,6 +137,103 @@ For more information, see
|
||||
See also
|
||||
[`GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST`](#GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST).
|
||||
|
||||
###### Using `ConvertGenerator`
|
||||
|
||||
The functions listed in the table above appear to return generators that create
|
||||
values of the desired types, but this is not generally the case. Rather, they
|
||||
typically return factory objects that convert to the the desired generators.
|
||||
This affords some flexibility in allowing you to specify values of types that
|
||||
are different from, yet implicitly convertible to, the actual parameter type
|
||||
required by your fixture class.
|
||||
|
||||
For example, you can do the following with a fixture that requires an `int`
|
||||
parameter:
|
||||
|
||||
```cpp
|
||||
INSTANTIATE_TEST_SUITE_P(MyInstantiation, MyTestSuite,
|
||||
testing::Values(1, 1.2)); // Yes, Values() supports heterogeneous argument types.
|
||||
```
|
||||
|
||||
It might seem obvious that `1.2` — a `double` — will be converted to
|
||||
an `int` but in actuality it requires some template gymnastics involving the
|
||||
indirection described in the previous paragraph.
|
||||
|
||||
What if your parameter type is not implicitly convertible from the generated
|
||||
type but is *explicitly* convertible? There will be no automatic conversion, but
|
||||
you can force it by applying `ConvertGenerator<T>`. The compiler can
|
||||
automatically deduce the target type (your fixture's parameter type), but
|
||||
because of the aforementioned indirection it cannot decide what the generated
|
||||
type should be. You need to tell it, by providing the type `T` explicitly. Thus
|
||||
`T` should not be your fixture's parameter type, but rather an intermediate type
|
||||
that is supported by the factory object, and which can be `static_cast` to the
|
||||
fixture's parameter type:
|
||||
|
||||
```cpp
|
||||
// The fixture's parameter type.
|
||||
class MyParam {
|
||||
public:
|
||||
// Explicit converting ctor.
|
||||
explicit MyParam(const std::tuple<int, bool>& t);
|
||||
...
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(MyInstantiation, MyTestSuite,
|
||||
ConvertGenerator<std::tuple<int, bool>>(Combine(Values(0.1, 1.2), Bool())));
|
||||
```
|
||||
|
||||
In this example `Combine` supports the generation of `std::tuple<int, bool>>`
|
||||
objects (even though the provided values for the first tuple element are
|
||||
`double`s) and those `tuple`s get converted into `MyParam` objects by virtue of
|
||||
the call to `ConvertGenerator`.
|
||||
|
||||
For parameter types that are not convertible from the generated types you can
|
||||
provide a callable that does the conversion. The callable accepts an object of
|
||||
the generated type and returns an object of the fixture's parameter type. The
|
||||
generated type can often be deduced by the compiler from the callable's call
|
||||
signature so you do not usually need specify it explicitly (but see a caveat
|
||||
below).
|
||||
|
||||
```cpp
|
||||
// The fixture's parameter type.
|
||||
class MyParam {
|
||||
public:
|
||||
MyParam(int, bool);
|
||||
...
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(MyInstantiation, MyTestSuite,
|
||||
ConvertGenerator(Combine(Values(1, 1.2), Bool()),
|
||||
[](const std::tuple<int i, bool>& t){
|
||||
const auto [i, b] = t;
|
||||
return MyParam(i, b);
|
||||
}));
|
||||
```
|
||||
|
||||
The callable may be anything that can be used to initialize a `std::function`
|
||||
with the appropriate call signature. Note the callable's return object gets
|
||||
`static_cast` to the fixture's parameter type, so it does not have to be of that
|
||||
exact type, only convertible to it.
|
||||
|
||||
**Caveat:** Consider the following example.
|
||||
|
||||
```cpp
|
||||
INSTANTIATE_TEST_SUITE_P(MyInstantiation, MyTestSuite,
|
||||
ConvertGenerator(Values(std::string("s")), [](std::string_view s) { ... }));
|
||||
```
|
||||
|
||||
The `string` argument gets copied into the factory object returned by `Values`.
|
||||
Then, because the generated type deduced from the lambda is `string_view`, the
|
||||
factory object spawns a generator that holds a `string_view` referencing that
|
||||
`string`. Unfortunately, by the time this generator gets invoked, the factory
|
||||
object is gone and the `string_view` is dangling.
|
||||
|
||||
To overcome this problem you can specify the generated type explicitly:
|
||||
`ConvertGenerator<std::string>(Values(std::string("s")), [](std::string_view s)
|
||||
{ ... })`. Alternatively, you can change the lambda's signature to take a
|
||||
`std::string` or a `const std::string&` (the latter will not leave you with a
|
||||
dangling reference because the type deduction strips off the reference and the
|
||||
`const`).
|
||||
|
||||
### TYPED_TEST_SUITE {#TYPED_TEST_SUITE}
|
||||
|
||||
`TYPED_TEST_SUITE(`*`TestFixtureName`*`,`*`Types`*`)`
|
||||
|
@ -174,6 +174,7 @@ TEST_P(DerivedTest, DoesBlah) {
|
||||
|
||||
#endif // 0
|
||||
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
@ -413,7 +414,8 @@ internal::CartesianProductHolder<Generator...> Combine(const Generator&... g) {
|
||||
// Synopsis:
|
||||
// ConvertGenerator<T>(gen)
|
||||
// - returns a generator producing the same elements as generated by gen, but
|
||||
// each element is static_cast to type T before being returned
|
||||
// each T-typed element is static_cast to a type deduced from the interface
|
||||
// that accepts this generator, and then returned
|
||||
//
|
||||
// It is useful when using the Combine() function to get the generated
|
||||
// parameters in a custom type instead of std::tuple
|
||||
@ -441,10 +443,65 @@ internal::CartesianProductHolder<Generator...> Combine(const Generator&... g) {
|
||||
// Combine(Values("cat", "dog"),
|
||||
// Values(BLACK, WHITE))));
|
||||
//
|
||||
template <typename T>
|
||||
internal::ParamConverterGenerator<T> ConvertGenerator(
|
||||
internal::ParamGenerator<T> gen) {
|
||||
return internal::ParamConverterGenerator<T>(gen);
|
||||
template <typename RequestedT>
|
||||
internal::ParamConverterGenerator<RequestedT> ConvertGenerator(
|
||||
internal::ParamGenerator<RequestedT> gen) {
|
||||
return internal::ParamConverterGenerator<RequestedT>(std::move(gen));
|
||||
}
|
||||
|
||||
// As above, but takes a callable as a second argument. The callable converts
|
||||
// the generated parameter to the test fixture's parameter type. This allows you
|
||||
// to use a parameter type that does not have a converting constructor from the
|
||||
// generated type.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// This will instantiate tests in test suite AnimalTest each one with
|
||||
// the parameter values tuple("cat", BLACK), tuple("cat", WHITE),
|
||||
// tuple("dog", BLACK), and tuple("dog", WHITE):
|
||||
//
|
||||
// enum Color { BLACK, GRAY, WHITE };
|
||||
// struct ParamType {
|
||||
// std::string animal;
|
||||
// Color color;
|
||||
// };
|
||||
// class AnimalTest
|
||||
// : public testing::TestWithParam<ParamType> {...};
|
||||
//
|
||||
// TEST_P(AnimalTest, AnimalLooksNice) {...}
|
||||
//
|
||||
// INSTANTIATE_TEST_SUITE_P(
|
||||
// AnimalVariations, AnimalTest,
|
||||
// ConvertGenerator(Combine(Values("cat", "dog"), Values(BLACK, WHITE)),
|
||||
// [](std::tuple<std::string, Color> t) {
|
||||
// return ParamType{.animal = std::get<0>(t),
|
||||
// .color = std::get<1>(t)};
|
||||
// }));
|
||||
//
|
||||
template <typename T, int&... ExplicitArgumentBarrier, typename Gen,
|
||||
typename Func,
|
||||
typename StdFunction = decltype(std::function(std::declval<Func>()))>
|
||||
internal::ParamConverterGenerator<T, StdFunction> ConvertGenerator(Gen&& gen,
|
||||
Func&& f) {
|
||||
return internal::ParamConverterGenerator<T, StdFunction>(
|
||||
std::forward<Gen>(gen), std::forward<Func>(f));
|
||||
}
|
||||
|
||||
// As above, but infers the T from the supplied std::function instead of
|
||||
// having the caller specify it.
|
||||
template <int&... ExplicitArgumentBarrier, typename Gen, typename Func,
|
||||
typename StdFunction = decltype(std::function(std::declval<Func>()))>
|
||||
auto ConvertGenerator(Gen&& gen, Func&& f) {
|
||||
constexpr bool is_single_arg_std_function =
|
||||
internal::IsSingleArgStdFunction<StdFunction>::value;
|
||||
if constexpr (is_single_arg_std_function) {
|
||||
return ConvertGenerator<
|
||||
typename internal::FuncSingleParamType<StdFunction>::type>(
|
||||
std::forward<Gen>(gen), std::forward<Func>(f));
|
||||
} else {
|
||||
static_assert(is_single_arg_std_function,
|
||||
"The call signature must contain a single argument.");
|
||||
}
|
||||
}
|
||||
|
||||
#define TEST_P(test_suite_name, test_name) \
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include <ctype.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@ -529,8 +530,7 @@ class ParameterizedTestSuiteInfo : public ParameterizedTestSuiteInfoBase {
|
||||
// prefix). test_base_name is the name of an individual test without
|
||||
// parameter index. For the test SequenceA/FooTest.DoBar/1 FooTest is
|
||||
// test suite base name and DoBar is test base name.
|
||||
void AddTestPattern(const char*,
|
||||
const char* test_base_name,
|
||||
void AddTestPattern(const char*, const char* test_base_name,
|
||||
TestMetaFactoryBase<ParamType>* meta_factory,
|
||||
CodeLocation code_location) {
|
||||
tests_.emplace_back(
|
||||
@ -952,11 +952,11 @@ class CartesianProductHolder {
|
||||
std::tuple<Gen...> generators_;
|
||||
};
|
||||
|
||||
template <typename From, typename To>
|
||||
template <typename From, typename To, typename Func>
|
||||
class ParamGeneratorConverter : public ParamGeneratorInterface<To> {
|
||||
public:
|
||||
ParamGeneratorConverter(ParamGenerator<From> gen) // NOLINT
|
||||
: generator_(std::move(gen)) {}
|
||||
ParamGeneratorConverter(ParamGenerator<From> gen, Func converter) // NOLINT
|
||||
: generator_(std::move(gen)), converter_(std::move(converter)) {}
|
||||
|
||||
ParamIteratorInterface<To>* Begin() const override {
|
||||
return new Iterator(this, generator_.begin(), generator_.end());
|
||||
@ -965,13 +965,21 @@ class ParamGeneratorConverter : public ParamGeneratorInterface<To> {
|
||||
return new Iterator(this, generator_.end(), generator_.end());
|
||||
}
|
||||
|
||||
// Returns the std::function wrapping the user-supplied converter callable. It
|
||||
// is used by the iterator (see class Iterator below) to convert the object
|
||||
// (of type FROM) returned by the ParamGenerator to an object of a type that
|
||||
// can be static_cast to type TO.
|
||||
const Func& TypeConverter() const { return converter_; }
|
||||
|
||||
private:
|
||||
class Iterator : public ParamIteratorInterface<To> {
|
||||
public:
|
||||
Iterator(const ParamGeneratorInterface<To>* base, ParamIterator<From> it,
|
||||
Iterator(const ParamGeneratorConverter* base, ParamIterator<From> it,
|
||||
ParamIterator<From> end)
|
||||
: base_(base), it_(it), end_(end) {
|
||||
if (it_ != end_) value_ = std::make_shared<To>(static_cast<To>(*it_));
|
||||
if (it_ != end_)
|
||||
value_ =
|
||||
std::make_shared<To>(static_cast<To>(base->TypeConverter()(*it_)));
|
||||
}
|
||||
~Iterator() override = default;
|
||||
|
||||
@ -980,7 +988,9 @@ class ParamGeneratorConverter : public ParamGeneratorInterface<To> {
|
||||
}
|
||||
void Advance() override {
|
||||
++it_;
|
||||
if (it_ != end_) value_ = std::make_shared<To>(static_cast<To>(*it_));
|
||||
if (it_ != end_)
|
||||
value_ =
|
||||
std::make_shared<To>(static_cast<To>(base_->TypeConverter()(*it_)));
|
||||
}
|
||||
ParamIteratorInterface<To>* Clone() const override {
|
||||
return new Iterator(*this);
|
||||
@ -1000,30 +1010,54 @@ class ParamGeneratorConverter : public ParamGeneratorInterface<To> {
|
||||
private:
|
||||
Iterator(const Iterator& other) = default;
|
||||
|
||||
const ParamGeneratorInterface<To>* const base_;
|
||||
const ParamGeneratorConverter* const base_;
|
||||
ParamIterator<From> it_;
|
||||
ParamIterator<From> end_;
|
||||
std::shared_ptr<To> value_;
|
||||
}; // class ParamGeneratorConverter::Iterator
|
||||
|
||||
ParamGenerator<From> generator_;
|
||||
Func converter_;
|
||||
}; // class ParamGeneratorConverter
|
||||
|
||||
template <class Gen>
|
||||
template <class GeneratedT,
|
||||
typename StdFunction =
|
||||
std::function<const GeneratedT&(const GeneratedT&)>>
|
||||
class ParamConverterGenerator {
|
||||
public:
|
||||
ParamConverterGenerator(ParamGenerator<Gen> g) // NOLINT
|
||||
: generator_(std::move(g)) {}
|
||||
ParamConverterGenerator(ParamGenerator<GeneratedT> g) // NOLINT
|
||||
: generator_(std::move(g)), converter_(Identity) {}
|
||||
|
||||
ParamConverterGenerator(ParamGenerator<GeneratedT> g, StdFunction converter)
|
||||
: generator_(std::move(g)), converter_(std::move(converter)) {}
|
||||
|
||||
template <typename T>
|
||||
operator ParamGenerator<T>() const { // NOLINT
|
||||
return ParamGenerator<T>(new ParamGeneratorConverter<Gen, T>(generator_));
|
||||
return ParamGenerator<T>(
|
||||
new ParamGeneratorConverter<GeneratedT, T, StdFunction>(generator_,
|
||||
converter_));
|
||||
}
|
||||
|
||||
private:
|
||||
ParamGenerator<Gen> generator_;
|
||||
static const GeneratedT& Identity(const GeneratedT& v) { return v; }
|
||||
|
||||
ParamGenerator<GeneratedT> generator_;
|
||||
StdFunction converter_;
|
||||
};
|
||||
|
||||
// Template to determine the param type of a single-param std::function.
|
||||
template <typename T>
|
||||
struct FuncSingleParamType;
|
||||
template <typename R, typename P>
|
||||
struct FuncSingleParamType<std::function<R(P)>> {
|
||||
using type = std::remove_cv_t<std::remove_reference_t<P>>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct IsSingleArgStdFunction : public std::false_type {};
|
||||
template <typename R, typename P>
|
||||
struct IsSingleArgStdFunction<std::function<R(P)>> : public std::true_type {};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace testing
|
||||
|
||||
|
@ -35,12 +35,17 @@
|
||||
#include "test/googletest-param-test-test.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
@ -583,6 +588,71 @@ TEST(ConvertTest, NonDefaultConstructAssign) {
|
||||
EXPECT_TRUE(it == gen.end());
|
||||
}
|
||||
|
||||
TEST(ConvertTest, WithConverterLambdaAndDeducedType) {
|
||||
const ParamGenerator<ConstructFromT<int8_t>> gen =
|
||||
ConvertGenerator(Values("0", std::string("1")), [](const std::string& s) {
|
||||
size_t pos;
|
||||
int64_t value = std::stoll(s, &pos);
|
||||
EXPECT_EQ(pos, s.size());
|
||||
return value;
|
||||
});
|
||||
|
||||
ConstructFromT<int8_t> expected_values[] = {ConstructFromT<int8_t>(0),
|
||||
ConstructFromT<int8_t>(1)};
|
||||
VerifyGenerator(gen, expected_values);
|
||||
}
|
||||
|
||||
TEST(ConvertTest, WithConverterLambdaAndExplicitType) {
|
||||
auto convert_generator = ConvertGenerator<std::string>(
|
||||
Values("0", std::string("1")), [](std::string_view s) {
|
||||
size_t pos;
|
||||
int64_t value = std::stoll(std::string(s), &pos);
|
||||
EXPECT_EQ(pos, s.size());
|
||||
return value;
|
||||
});
|
||||
constexpr bool is_correct_type = std::is_same_v<
|
||||
decltype(convert_generator),
|
||||
testing::internal::ParamConverterGenerator<
|
||||
std::string, std::function<int64_t(std::string_view)>>>;
|
||||
EXPECT_TRUE(is_correct_type);
|
||||
const ParamGenerator<ConstructFromT<int8_t>> gen = convert_generator;
|
||||
|
||||
ConstructFromT<int8_t> expected_values[] = {ConstructFromT<int8_t>(0),
|
||||
ConstructFromT<int8_t>(1)};
|
||||
VerifyGenerator(gen, expected_values);
|
||||
}
|
||||
|
||||
TEST(ConvertTest, WithConverterFunctionPointer) {
|
||||
int64_t (*func_ptr)(const std::string&) = [](const std::string& s) {
|
||||
size_t pos;
|
||||
int64_t value = std::stoll(s, &pos);
|
||||
EXPECT_EQ(pos, s.size());
|
||||
return value;
|
||||
};
|
||||
const ParamGenerator<ConstructFromT<int8_t>> gen =
|
||||
ConvertGenerator(Values("0", std::string("1")), func_ptr);
|
||||
|
||||
ConstructFromT<int8_t> expected_values[] = {ConstructFromT<int8_t>(0),
|
||||
ConstructFromT<int8_t>(1)};
|
||||
VerifyGenerator(gen, expected_values);
|
||||
}
|
||||
|
||||
TEST(ConvertTest, WithConverterFunctionReference) {
|
||||
int64_t (*func_ptr)(const std::string&) = [](const std::string& s) {
|
||||
size_t pos;
|
||||
int64_t value = std::stoll(s, &pos);
|
||||
EXPECT_EQ(pos, s.size());
|
||||
return value;
|
||||
};
|
||||
int64_t (&func_ref)(const std::string&) = *func_ptr;
|
||||
const ParamGenerator<ConstructFromT<int8_t>> gen =
|
||||
ConvertGenerator(Values("0", std::string("1")), func_ref);
|
||||
|
||||
ConstructFromT<int8_t> expected_values[] = {ConstructFromT<int8_t>(0),
|
||||
ConstructFromT<int8_t>(1)};
|
||||
VerifyGenerator(gen, expected_values);
|
||||
}
|
||||
|
||||
// Tests that an generator produces correct sequence after being
|
||||
// assigned from another generator.
|
||||
TEST(ParamGeneratorTest, AssignmentWorks) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user